feat(v0.3): H3 — remote approve/reject (no typing)

- types/protocol: ClientMessage approve/reject; ServerMessage status.pending
- server: POST /hook/permission HELD until the client decides; approve/reject
  ws messages resolve it with {hookSpecificOutput.decision.behavior allow|deny};
  5-min timeout + release-on-disconnect fall back to Claude's own prompt
- manager.handleHookEvent threads pending flag
- setup-hooks: PermissionRequest uses the held curl (writes decision to stdout);
  status events stay fire-and-forget
- FE: terminal-session approve()/reject() + pending state; tabs approval banner
  (Approve/Reject) for the active tab's held request
- Tests: protocol approve/reject; integration ⑧ held→approve→allow. 207 green.
- Browser-verified: banner 'Claude wants to use Bash' → Approve → resolves allow.
This commit is contained in:
Yaojia Wang
2026-06-17 19:13:07 +02:00
parent 04355f05e9
commit 9a6150c355
10 changed files with 279 additions and 23 deletions

View File

@@ -107,6 +107,15 @@ describe('parseClientMessage — valid messages', () => {
expect(result.message).toEqual({ type: 'resize', cols: 120, rows: 40 })
}
})
it('parses approve / reject (H3, no payload)', () => {
const a = parseClientMessage(JSON.stringify({ type: 'approve' }))
expect(a.ok).toBe(true)
if (a.ok) expect(a.message).toEqual({ type: 'approve' })
const r = parseClientMessage(JSON.stringify({ type: 'reject' }))
expect(r.ok).toBe(true)
if (r.ok) expect(r.message).toEqual({ type: 'reject' })
})
})
// ─── parseClientMessage — invalid / error paths ───────────────────────────────