mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
feat: Approvals Inbox, one cross-session queue for prompts waiting on a human
Permission dialogs, AskUserQuestion questions and idle prompts from every session now land in a server-side inbox (web/approval-inbox.ts, one item per session, claude-mode only) and are answerable in place: a header bell + drawer on desktop, inline answer strips on the phone overview's NEEDS YOU rows, and working push Approve/Deny buttons (previously dead ends, now answered straight from sw.js with no tab open). Pending alerts survive reloads because the frontend seeds from GET /api/approvals on init. Answering sends the digit / Esc / prompt text through the existing tmux input path; option digits are accepted only when they match options parsed from the captured pane frame, and the answer path re-captures the pane first so a dialog that already left the screen refuses with 409 instead of typing into the composer. New elicitation_complete / elicitation_response hook matchers resolve question items the moment they are answered in the terminal; refreshStaleCodemanHooks heals existing cases. Verified end-to-end against a live claude session: a real AskUserQuestion dialog parsed into 5 option buttons and was answered from the drawer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,25 @@ describe('refreshStaleCodemanHooks', () => {
|
||||
expect(readFileSync(settingsPath, 'utf-8')).toBe(healed); // byte-identical: no rewrite
|
||||
});
|
||||
|
||||
it('heals a hooks block that predates the elicitation-closed matchers (Approvals Inbox)', async () => {
|
||||
// A current-at-the-time block from before elicitation_complete/response
|
||||
// existed: secret + markers all present, so ONLY the new-matcher probe can
|
||||
// mark it stale. Build one by healing, then stripping the two matchers.
|
||||
writeFileSync(settingsPath, JSON.stringify({ hooks: staleCodemanHooks() }, null, 2));
|
||||
await refreshStaleCodemanHooks(dir);
|
||||
const healed = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
healed.hooks.Notification = (healed.hooks.Notification as Array<{ matcher?: string }>).filter(
|
||||
(n) => n.matcher !== 'elicitation_complete' && n.matcher !== 'elicitation_response'
|
||||
);
|
||||
writeFileSync(settingsPath, JSON.stringify(healed, null, 2));
|
||||
expect(readFileSync(settingsPath, 'utf-8')).not.toContain('elicitation_complete');
|
||||
|
||||
await refreshStaleCodemanHooks(dir);
|
||||
const after = readFileSync(settingsPath, 'utf-8');
|
||||
expect(after).toContain('elicitation_complete');
|
||||
expect(after).toContain('elicitation_response');
|
||||
});
|
||||
|
||||
it('does not touch hooks that are not Codeman’s (no /api/hook-event)', async () => {
|
||||
const foreign = JSON.stringify(
|
||||
{ hooks: { Stop: [{ matcher: '', hooks: [{ type: 'command', command: 'echo hi', timeout: 5 }] }] } },
|
||||
|
||||
Reference in New Issue
Block a user