Scoped half of boocode_code_review_v2 §1 #10 — publish the agent status BooCoder already observes (the config-injection notify-hook is the documented follow-on, clean-room from superset ELv2). - agent_status_updated WS frame (working|blocked|idle|error), server+web parity. - Published from the dispatcher's turn boundaries (warm-acp/opencode/sdk/pty: working at start, idle/error at end) + the permission flow (blocked/working). Best-effort, never breaks a turn. - Clean-room normalizeAgentEvent helper (superset's vendor-event -> Start/blocked /Stop collapse, event names as facts) + 25 tests — reused by the follow-on. - AgentComposerBar status dot (distinct from the WS-liveness dot), tracked per (chat,agent) by a useAgentStatus map in CoderPane. Built by 2 parallel agents vs a pinned frame contract. Server 545 + coder 294 tests passing (25 new); web tsc + builds clean; ws-frames parity green. Clears the actionable review backlog (#1/#3/#4/#6-#12). Builds on v2.7.5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { normalizeAgentEvent } from '../normalize-agent-status.js';
|
|
|
|
describe('normalizeAgentEvent', () => {
|
|
describe('working bucket', () => {
|
|
const cases = [
|
|
'SessionStart',
|
|
'UserPromptSubmit',
|
|
'UserPromptSubmitted',
|
|
'PostToolUse',
|
|
'PostToolUseFailure',
|
|
'BeforeAgent',
|
|
'AfterTool',
|
|
'task_started',
|
|
];
|
|
for (const name of cases) {
|
|
it(`maps ${name} → working`, () => {
|
|
expect(normalizeAgentEvent(name)).toBe('working');
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('blocked bucket', () => {
|
|
const cases = [
|
|
'PreToolUse',
|
|
'Notification',
|
|
'PermissionRequest',
|
|
'exec_approval_request',
|
|
'apply_patch_approval_request',
|
|
'request_user_input',
|
|
];
|
|
for (const name of cases) {
|
|
it(`maps ${name} → blocked`, () => {
|
|
expect(normalizeAgentEvent(name)).toBe('blocked');
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('done bucket', () => {
|
|
const cases = [
|
|
'Stop',
|
|
'AfterAgent',
|
|
'SessionEnd',
|
|
'task_complete',
|
|
'agent-turn-complete',
|
|
];
|
|
for (const name of cases) {
|
|
it(`maps ${name} → done`, () => {
|
|
expect(normalizeAgentEvent(name)).toBe('done');
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('unknown / nullish → null', () => {
|
|
it('returns null for an unrecognized event', () => {
|
|
expect(normalizeAgentEvent('SomeRandomEvent')).toBeNull();
|
|
});
|
|
it('returns null for empty string', () => {
|
|
expect(normalizeAgentEvent('')).toBeNull();
|
|
});
|
|
it('returns null for undefined', () => {
|
|
expect(normalizeAgentEvent(undefined)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('case- and separator-insensitive matching', () => {
|
|
it('matches snake_case spelling of a PascalCase event', () => {
|
|
expect(normalizeAgentEvent('session_start')).toBe('working');
|
|
expect(normalizeAgentEvent('post_tool_use')).toBe('working');
|
|
expect(normalizeAgentEvent('pre_tool_use')).toBe('blocked');
|
|
});
|
|
it('matches camelCase spelling', () => {
|
|
expect(normalizeAgentEvent('userPromptSubmitted')).toBe('working');
|
|
expect(normalizeAgentEvent('postToolUse')).toBe('working');
|
|
expect(normalizeAgentEvent('preToolUse')).toBe('blocked');
|
|
expect(normalizeAgentEvent('sessionEnd')).toBe('done');
|
|
});
|
|
it('matches arbitrary case', () => {
|
|
expect(normalizeAgentEvent('STOP')).toBe('done');
|
|
expect(normalizeAgentEvent('notification')).toBe('blocked');
|
|
});
|
|
});
|
|
});
|