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:
Yaojia Wang
2026-06-30 17:42:18 +02:00
parent 4f1d3ebc6b
commit d6809c65c4
54 changed files with 13171 additions and 200 deletions

View File

@@ -0,0 +1,170 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import type { PushSubscriptionRecord } from '../../src/types.js'
import {
isValidSubscriptionRecord,
loadSubscriptionStore,
} from '../../src/push/subscription-store.js'
const dirs: string[] = []
function tmpFile(name = 'push-subs.json'): string {
const dir = mkdtempSync(join(tmpdir(), 'webterm-subs-'))
dirs.push(dir)
return join(dir, name)
}
function rec(endpoint: string, createdAt = 1000): PushSubscriptionRecord {
return { endpoint, keys: { p256dh: 'pub-' + endpoint, auth: 'auth-' + endpoint }, createdAt }
}
afterEach(() => {
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true })
vi.restoreAllMocks()
})
describe('isValidSubscriptionRecord', () => {
it('accepts a well-formed record', () => {
expect(isValidSubscriptionRecord(rec('https://push/1'))).toBe(true)
})
it('rejects non-objects and missing/invalid fields', () => {
expect(isValidSubscriptionRecord(null)).toBe(false)
expect(isValidSubscriptionRecord('x')).toBe(false)
expect(isValidSubscriptionRecord({})).toBe(false)
expect(isValidSubscriptionRecord({ endpoint: '', keys: { p256dh: 'a', auth: 'b' }, createdAt: 1 })).toBe(false)
expect(isValidSubscriptionRecord({ endpoint: 'e', keys: { p256dh: 'a' }, createdAt: 1 })).toBe(false)
expect(isValidSubscriptionRecord({ endpoint: 'e', keys: { p256dh: 1, auth: 'b' }, createdAt: 1 })).toBe(false)
expect(isValidSubscriptionRecord({ endpoint: 'e', keys: { p256dh: 'a', auth: 'b' } })).toBe(false)
expect(isValidSubscriptionRecord({ endpoint: 'e', keys: { p256dh: 'a', auth: 'b' }, createdAt: NaN })).toBe(false)
})
})
describe('loadSubscriptionStore — loading', () => {
it('starts empty when the file is missing', () => {
const store = loadSubscriptionStore(tmpFile(), 50)
expect(store.list()).toEqual([])
})
it('starts empty (and logs) on malformed JSON', () => {
const err = vi.spyOn(console, 'error').mockImplementation(() => {})
const path = tmpFile()
writeFileSync(path, '{ not valid json')
const store = loadSubscriptionStore(path, 50)
expect(store.list()).toEqual([])
expect(err).toHaveBeenCalled()
})
it('filters out invalid records on load', () => {
const path = tmpFile()
writeFileSync(
path,
JSON.stringify([rec('https://push/ok'), { endpoint: 123 }, { foo: 'bar' }]),
)
const store = loadSubscriptionStore(path, 50)
expect(store.list().map((r) => r.endpoint)).toEqual(['https://push/ok'])
})
it('returns empty when the JSON is not an array', () => {
const path = tmpFile()
writeFileSync(path, JSON.stringify({ endpoint: 'x' }))
expect(loadSubscriptionStore(path, 50).list()).toEqual([])
})
})
describe('SubscriptionStore — list immutability', () => {
it('returns a frozen copy that cannot be mutated', () => {
const store = loadSubscriptionStore(tmpFile(), 50)
store.add(rec('https://push/1'))
const snapshot = store.list()
expect(Object.isFrozen(snapshot)).toBe(true)
expect(() => (snapshot as PushSubscriptionRecord[]).push(rec('https://push/2'))).toThrow()
// original store is unaffected
expect(store.list()).toHaveLength(1)
})
})
describe('SubscriptionStore — add', () => {
it('appends valid records', () => {
const store = loadSubscriptionStore(tmpFile(), 50)
store.add(rec('https://push/1'))
store.add(rec('https://push/2'))
expect(store.list().map((r) => r.endpoint)).toEqual(['https://push/1', 'https://push/2'])
})
it('throws on an invalid record (fail-fast at the boundary)', () => {
const store = loadSubscriptionStore(tmpFile(), 50)
expect(() => store.add({ endpoint: '' } as unknown as PushSubscriptionRecord)).toThrow()
})
it('deduplicates by endpoint, replacing the prior record', () => {
const store = loadSubscriptionStore(tmpFile(), 50)
store.add(rec('https://push/1', 1000))
store.add({ endpoint: 'https://push/1', keys: { p256dh: 'new', auth: 'new' }, createdAt: 2000 })
const list = store.list()
expect(list).toHaveLength(1)
expect(list[0]?.keys.p256dh).toBe('new')
expect(list[0]?.createdAt).toBe(2000)
})
it('enforces a FIFO cap at maxSubs, evicting the oldest', () => {
const store = loadSubscriptionStore(tmpFile(), 2)
store.add(rec('https://push/1'))
store.add(rec('https://push/2'))
store.add(rec('https://push/3'))
expect(store.list().map((r) => r.endpoint)).toEqual(['https://push/2', 'https://push/3'])
})
})
describe('SubscriptionStore — remove / prune', () => {
it('removes a record by endpoint and is a no-op for unknown endpoints', () => {
const store = loadSubscriptionStore(tmpFile(), 50)
store.add(rec('https://push/1'))
store.add(rec('https://push/2'))
store.remove('https://push/1')
store.remove('https://push/absent')
expect(store.list().map((r) => r.endpoint)).toEqual(['https://push/2'])
})
it('prunes a batch of dead endpoints', () => {
const store = loadSubscriptionStore(tmpFile(), 50)
store.add(rec('https://push/1'))
store.add(rec('https://push/2'))
store.add(rec('https://push/3'))
store.prune(['https://push/1', 'https://push/3'])
expect(store.list().map((r) => r.endpoint)).toEqual(['https://push/2'])
})
})
describe('SubscriptionStore — persist', () => {
it('writes the file with 0600 permissions and round-trips through load', async () => {
const path = tmpFile()
const store = loadSubscriptionStore(path, 50)
store.add(rec('https://push/1', 11))
store.add(rec('https://push/2', 22))
await store.persist()
const mode = statSync(path).mode & 0o777
expect(mode).toBe(0o600)
const onDisk = JSON.parse(readFileSync(path, 'utf8'))
expect(onDisk.map((r: PushSubscriptionRecord) => r.endpoint)).toEqual([
'https://push/1',
'https://push/2',
])
const reloaded = loadSubscriptionStore(path, 50)
expect(reloaded.list().map((r) => r.endpoint)).toEqual(['https://push/1', 'https://push/2'])
})
it('logs but does not throw when the destination is unwritable', async () => {
const err = vi.spyOn(console, 'error').mockImplementation(() => {})
// a path whose parent directory does not exist
const store = loadSubscriptionStore(join(tmpFile(), 'nope', 'subs.json'), 50)
store.add(rec('https://push/1'))
await expect(store.persist()).resolves.toBeUndefined()
expect(err).toHaveBeenCalled()
})
})