feat(cockpit): quick wins — sync chip, cost budget guard, digest, recent commits (W3)

Four small, high-delight features that turn passive capture into glanceable signals.

- Sync chip on project cards: ahead/behind vs upstream + last-commit time, folded
  into the existing concurrent per-repo metadata pass (git rev-list --count
  --left-right @{u}...HEAD + git log -1 --format=%ct; no upstream → undefined, no route).
- Cost budget guard: COST_BUDGET_USD env (0 = off); a per-session one-shot latch
  (Session.budgetNotified, cost is monotonic so never re-armed) fires a single push
  on threshold crossing in manager.handleStatusLine; the already-broadcast telemetry
  frame carries the warn (tg-cost-warn styling derived from costUsd>=budget via
  /config/ui — no new ServerMessage). web-push title added to sw-push.js.
- "While you were away" digest: GET /digest?since= → {finished, needsInput, stuck,
  totalCostUsd, sessions[]} aggregate over manager.list(); FE banner on reconnect.
- Recent commits per project: src/http/git-log.ts (NUL-delimited git log → CommitInfo[]),
  GET /projects/log?path= (isValidGitDir), textContent-inert render in project detail.

All git via execFile (no shell) + validated cwd; new routes read-only; commit
messages rendered via textContent. Verified: typecheck + build:web clean, 1904 pass
at --test-timeout=30000 (two default-5s failures are slow-sandbox real-subprocess
timeout flakes — the known ring-buffer test + a new real-git-clone sync test — not
logic regressions).
This commit is contained in:
Yaojia Wang
2026-07-12 21:27:20 +02:00
parent 7551f8a4b2
commit 1dd12b035a
30 changed files with 1911 additions and 8 deletions

View File

@@ -265,6 +265,29 @@ export function createSessionManager(
if (session === undefined) return;
session.telemetry = telemetry;
broadcast(session, { type: 'telemetry', telemetry });
maybeAlertBudget(session, telemetry);
}
/**
* W3 quick-wins (b): fire a one-shot cost-budget alert when a session's cost
* first crosses COST_BUDGET_USD. Mirrors the A5 stuck-latch shape but is NEVER
* re-armed (cost is monotonic) — so it fires at most once per session. Disabled
* when the budget is 0/unset or the frame carries no cost.
*
* No new ServerMessage variant: the "warning broadcast" is the telemetry frame
* already sent above (clients derive the warn from costUsd >= costBudgetUsd via
* GET /config/ui). The one distinct new action on crossing is a 'budget' push.
*/
function maybeAlertBudget(session: Session, telemetry: StatusTelemetry): void {
if (cfg.costBudgetUsd <= 0) return; // disabled
if (session.budgetNotified) return; // already alerted (latch)
const cost = telemetry.costUsd;
if (cost === undefined || cost < cfg.costBudgetUsd) return; // no crossing
session.budgetNotified = true;
void notifyService?.notify(session, 'budget').catch((err: unknown) => {
console.error('[manager] budget notification failed', err);
});
}
/**

View File

@@ -136,6 +136,7 @@ export function createSession(
// v0.7 Walk-away Workbench fields (T-spawn-env):
timeline: Object.freeze([] as TimelineEvent[]),
stuckNotified: false, // A5: re-armed to false by each pty output
budgetNotified: false, // W3(b): cost-budget one-shot latch, never re-armed
telemetry: null, // B2: updated by manager.handleStatusLine
// W2: inject follow-up queue — empty at spawn; replaced wholesale by manager.
queue: Object.freeze([] as string[]),