feat(v0.7): Walk-away Workbench (Band A + B) — multi-agent parallel build
Implements docs/PLAN_WALKAWAY_WORKBENCH.md (27 tasks, waves R0→W0→W1×14→W2→W3→W4)
via module-builder agents. 23 tasks built, 0 blocked.
Band A (finish the walk-away loop): A1 Web Push + lock-screen approve/deny
(web-push dep), A2 voice dictation, A3 quick-reply chips + saved-prompt palette,
A4 activity timeline, A5 stuck/idle alert.
Band B (workbench above the terminal): B1 read-only git diff viewer, B2 statusLine
telemetry → per-tab cost/context/PR gauges, B3 create git worktrees from the UI,
B4 plan-mode / permission-mode relay.
New: src/push/* (subscription store + VAPID push), src/http/{diff,statusline}.ts,
src/session/timeline.ts, public/{diff,timeline,quickreply,push-ui,...}.ts, sw-push,
statusLine script; extends hook intake, manager, server routes (Origin/CSRF guards
+ per-IP rate limits on state-changing ones; loopback-only ingest), terminal-session,
tabs, projects detail, service worker, setup-hooks (statusLine + ntfy bridge).
Orchestrator reconciled a W0 contract gap: added the 21 v0.7 Config fields to the
Config interface in types.ts (T-types had left them only in config.ts's return).
Verified: both tsc clean, full vitest + coverage 91.4/84.1/92.2/93.4 (≥80×4),
build:web OK. W4 review: no CRITICAL/HIGH; all security checks pass. Follow-ups
(non-blocking): move approve.mode validation into parseClientMessage, drop CSP
ws:/wss: wildcard, validate worktree base ref, +2 targeted tests.
This commit is contained in:
@@ -32,6 +32,7 @@ import type {
|
||||
Session,
|
||||
SessionMeta,
|
||||
ServerMessage,
|
||||
TimelineEvent,
|
||||
WebSocketLike,
|
||||
} from '../types.js';
|
||||
import { WS_OPEN } from '../types.js';
|
||||
@@ -95,6 +96,9 @@ export function createSession(
|
||||
...process.env,
|
||||
WEBTERM_SESSION: id,
|
||||
WEBTERM_HOOK_URL: `http://127.0.0.1:${cfg.port}/hook`,
|
||||
// B2: statusLine scripts POST telemetry here; WEBTERM_NTFY_* vars forwarded
|
||||
// via ...process.env spread above (H3 — token set in env, not here).
|
||||
WEBTERM_STATUSLINE_URL: `http://127.0.0.1:${cfg.port}/hook/status`,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -116,12 +120,25 @@ export function createSession(
|
||||
cwd: cwd ?? null,
|
||||
tmuxName: tName,
|
||||
pty,
|
||||
// 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
|
||||
telemetry: null, // B2: updated by manager.handleStatusLine
|
||||
};
|
||||
|
||||
// onData: persist to scrollback, refresh liveness, broadcast to all clients.
|
||||
pty.onData((chunk) => {
|
||||
session.buffer.append(chunk);
|
||||
session.lastOutputAt = Date.now();
|
||||
// A5 §3.4: re-arm the stuck flag on every output so each silent round can
|
||||
// alert at most once. If we were stuck, recover to 'working' and broadcast
|
||||
// so clients clear the stuck badge immediately — this is the ONLY reliable
|
||||
// place to do this (same as lastOutputAt; T-manager never sees raw output).
|
||||
session.stuckNotified = false;
|
||||
if (session.claudeStatus === 'stuck') {
|
||||
session.claudeStatus = 'working';
|
||||
broadcast(session, { type: 'status', status: 'working' });
|
||||
}
|
||||
broadcast(session, { type: 'output', data: chunk });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user