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>
103 lines
4.7 KiB
TypeScript
103 lines
4.7 KiB
TypeScript
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);
|
|
});
|
|
});
|