refactor: codebase audit cleanup — dead code, dedup, module splits
Multi-agent audit + aggressive cleanup across server/web/coder/booterm, delivered behind a DEFER discipline so none of the in-flight files were touched. Removes dead code/deps/columns, dedups server + coder helpers, and splits the oversized modules (tools.ts, opencode-server.ts, sentinel-summaries, turn.ts, TerminalPane.tsx) behind stable contracts. Adds 78 parity/unit tests (server 587, coder 323); fixes two latent bugs (ChatPane queue keys, FileViewerOverlay blank-line parity). Intended tag: v2.7.12-audit-cleanup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
74
apps/coder/src/services/__tests__/acp-client.test.ts
Normal file
74
apps/coder/src/services/__tests__/acp-client.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import type { RequestPermissionRequest, CreateElicitationRequest, SessionNotification } from '@agentclientprotocol/sdk';
|
||||
import { buildAcpClient, type AcpTurnContext } from '../acp-client.js';
|
||||
|
||||
/**
|
||||
* buildAcpClient (v2.7 audit reshape): the shared ACP `Client` closures. These
|
||||
* tests cover the pure routing decisions that don't require the permission-waiter
|
||||
* broker machinery — the auto-select/decline fallbacks and the between-turns drop.
|
||||
*/
|
||||
|
||||
describe('buildAcpClient — sessionUpdate', () => {
|
||||
it('drops the update when no turn is active (resolveTurn → null)', async () => {
|
||||
const client = buildAcpClient('/wt', () => null);
|
||||
// Must resolve without throwing and without an onSessionUpdate to call.
|
||||
await expect(client.sessionUpdate({ sessionId: 's', update: {} } as unknown as SessionNotification)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('forwards the update to the active turn', async () => {
|
||||
const onSessionUpdate = vi.fn();
|
||||
const turn: AcpTurnContext = { taskId: 't', sessionId: 's', modeId: undefined, agent: 'goose', onSessionUpdate };
|
||||
const client = buildAcpClient('/wt', () => turn);
|
||||
const note = { sessionId: 's', update: {} } as unknown as SessionNotification;
|
||||
await client.sessionUpdate(note);
|
||||
expect(onSessionUpdate).toHaveBeenCalledWith(note);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildAcpClient — requestPermission fallback (no UI routing)', () => {
|
||||
function req(options: Array<{ optionId: string }>): RequestPermissionRequest {
|
||||
return { options } as unknown as RequestPermissionRequest;
|
||||
}
|
||||
|
||||
it('auto-selects the first option when there is no turn', async () => {
|
||||
const client = buildAcpClient('/wt', () => null);
|
||||
const res = await client.requestPermission(req([{ optionId: 'allow' }, { optionId: 'deny' }]));
|
||||
expect(res).toEqual({ outcome: { outcome: 'selected', optionId: 'allow' } });
|
||||
});
|
||||
|
||||
it('cancels when there is no turn and no options', async () => {
|
||||
const client = buildAcpClient('/wt', () => null);
|
||||
const res = await client.requestPermission(req([]));
|
||||
expect(res).toEqual({ outcome: { outcome: 'cancelled' } });
|
||||
});
|
||||
|
||||
it('auto-selects when the turn has no taskId (UI routing gated off)', async () => {
|
||||
const turn: AcpTurnContext = { taskId: undefined, sessionId: 's', modeId: undefined, agent: 'goose', onSessionUpdate: () => {} };
|
||||
const client = buildAcpClient('/wt', () => turn);
|
||||
const res = await client.requestPermission(req([{ optionId: 'ok' }]));
|
||||
expect(res).toEqual({ outcome: { outcome: 'selected', optionId: 'ok' } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildAcpClient — elicitation fallback', () => {
|
||||
it('declines when there is no turn', async () => {
|
||||
const client = buildAcpClient('/wt', () => null);
|
||||
const res = await client.unstable_createElicitation!({} as CreateElicitationRequest);
|
||||
expect(res).toEqual({ action: 'decline' });
|
||||
});
|
||||
|
||||
it('declines when the turn has no taskId', async () => {
|
||||
const turn: AcpTurnContext = { taskId: undefined, sessionId: 's', modeId: undefined, agent: 'goose', onSessionUpdate: () => {} };
|
||||
const client = buildAcpClient('/wt', () => turn);
|
||||
const res = await client.unstable_createElicitation!({} as CreateElicitationRequest);
|
||||
expect(res).toEqual({ action: 'decline' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildAcpClient — createTerminal', () => {
|
||||
it('returns the noop terminal id', async () => {
|
||||
const client = buildAcpClient('/wt', () => null);
|
||||
const res = await client.createTerminal!({} as never);
|
||||
expect(res).toEqual({ terminalId: 'noop' });
|
||||
});
|
||||
});
|
||||
102
apps/coder/src/services/__tests__/frame-emitter.test.ts
Normal file
102
apps/coder/src/services/__tests__/frame-emitter.test.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { Broker } from '@boocode/server/broker';
|
||||
import { makeFrameEmitter } from '../frame-emitter.js';
|
||||
import { makeDcpStreamStripper } from '../dcp-strip.js';
|
||||
import type { AcpToolSnapshot } from '../acp-tool-snapshot.js';
|
||||
|
||||
/**
|
||||
* makeFrameEmitter (v2.7 audit reshape): the AgentEvent → WS-frame mapping + turn
|
||||
* accumulators extracted from AcpStreamContext. Pure-ish over an injected broker.
|
||||
*/
|
||||
|
||||
function fakeBroker(): { broker: Broker; frames: Array<{ sid: string; frame: Record<string, unknown> }> } {
|
||||
const frames: Array<{ sid: string; frame: Record<string, unknown> }> = [];
|
||||
const broker = {
|
||||
publishFrame: (sid: string, frame: unknown) => {
|
||||
frames.push({ sid, frame: frame as Record<string, unknown> });
|
||||
},
|
||||
} as unknown as Broker;
|
||||
return { broker, frames };
|
||||
}
|
||||
|
||||
const toolSnap: AcpToolSnapshot = { toolCallId: 'c1', title: 'grep', status: 'completed', rawOutput: 'x' };
|
||||
|
||||
describe('makeFrameEmitter — streaming frames', () => {
|
||||
it('maps text/reasoning/tool events to delta/reasoning_delta/tool_call frames', () => {
|
||||
const { broker, frames } = fakeBroker();
|
||||
const em = makeFrameEmitter({ broker, sessionId: 's1', chatId: 'ch1', assistantId: 'm1' });
|
||||
|
||||
em.onEvent({ type: 'text', text: 'hello ' });
|
||||
em.onEvent({ type: 'reasoning', text: 'mulling' });
|
||||
em.onEvent({ type: 'tool_call', toolCall: toolSnap });
|
||||
|
||||
expect(frames.map((f) => f.frame.type)).toEqual(['delta', 'reasoning_delta', 'tool_call']);
|
||||
expect(frames[0]!.frame).toMatchObject({ message_id: 'm1', chat_id: 'ch1', content: 'hello ' });
|
||||
expect(frames[2]!.frame).toMatchObject({ message_id: 'm1', chat_id: 'ch1' });
|
||||
expect(em.output).toBe('hello ');
|
||||
expect(em.reasoningText).toBe('mulling');
|
||||
expect(em.snapshots).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('publishes a tool_call frame for BOTH tool_call and tool_update events', () => {
|
||||
const { broker, frames } = fakeBroker();
|
||||
const em = makeFrameEmitter({ broker, sessionId: 's1', chatId: 'ch1', assistantId: 'm1' });
|
||||
em.onEvent({ type: 'tool_update', toolCall: toolSnap });
|
||||
expect(frames).toHaveLength(1);
|
||||
expect(frames[0]!.frame.type).toBe('tool_call');
|
||||
});
|
||||
|
||||
it('publishes an agent_commands frame and merges the command cache', () => {
|
||||
const { broker, frames } = fakeBroker();
|
||||
const taskId = `task-fe-${Math.floor(performance.now())}-${frames.length}`;
|
||||
const em = makeFrameEmitter({ broker, sessionId: 's1', chatId: 'ch1', assistantId: 'm1', taskId });
|
||||
em.onEvent({ type: 'commands', commands: [{ name: 'plan' }] });
|
||||
expect(frames).toHaveLength(1);
|
||||
expect(frames[0]!.frame).toMatchObject({ type: 'agent_commands', task_id: taskId, session_id: 's1' });
|
||||
});
|
||||
|
||||
it('does not publish a commands frame without a taskId', () => {
|
||||
const { broker, frames } = fakeBroker();
|
||||
const em = makeFrameEmitter({ broker, sessionId: 's1', chatId: 'ch1', assistantId: 'm1' });
|
||||
em.onEvent({ type: 'commands', commands: [{ name: 'plan' }] });
|
||||
expect(frames).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeFrameEmitter — no broker (one-shot accumulation)', () => {
|
||||
it('accumulates output/reasoning/snapshots but publishes nothing', () => {
|
||||
const em = makeFrameEmitter({ sessionId: 's1', chatId: 'ch1', assistantId: 'm1' });
|
||||
em.onEvent({ type: 'text', text: 'abc' });
|
||||
em.onEvent({ type: 'reasoning', text: 'r' });
|
||||
em.onEvent({ type: 'tool_call', toolCall: toolSnap });
|
||||
expect(em.output).toBe('abc');
|
||||
expect(em.reasoningText).toBe('r');
|
||||
expect(em.snapshots).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeFrameEmitter — dcp stripping (opencode path contract)', () => {
|
||||
it('strips a split dcp tag across deltas and flushes the tail on finalize', () => {
|
||||
const { broker, frames } = fakeBroker();
|
||||
const em = makeFrameEmitter({ broker, sessionId: 's1', chatId: 'ch1', assistantId: 'm1', dcp: makeDcpStreamStripper() });
|
||||
|
||||
for (const chunk of ['Answer.', '<dcp', '-message', '-id>m1</dcp', '-message-id>', ' tail']) {
|
||||
em.onEvent({ type: 'text', text: chunk });
|
||||
}
|
||||
em.finalize();
|
||||
|
||||
expect(em.output).toBe('Answer. tail');
|
||||
const published = frames.filter((f) => f.frame.type === 'delta').map((f) => f.frame.content).join('');
|
||||
expect(published).toBe('Answer. tail');
|
||||
});
|
||||
|
||||
it('finalize is a no-op without a dcp stripper', () => {
|
||||
const { broker, frames } = fakeBroker();
|
||||
const em = makeFrameEmitter({ broker, sessionId: 's1', chatId: 'ch1', assistantId: 'm1' });
|
||||
em.onEvent({ type: 'text', text: 'raw <dcp-message-id>m</dcp-message-id>' });
|
||||
em.finalize();
|
||||
// No stripping without a stripper — verbatim text (prior ACP-path behavior).
|
||||
expect(em.output).toBe('raw <dcp-message-id>m</dcp-message-id>');
|
||||
expect(frames).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user