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>
227 lines
8.4 KiB
TypeScript
227 lines
8.4 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import type { Event, Part } from '@opencode-ai/sdk/v2/client';
|
|
import {
|
|
stripDcpTags,
|
|
eventSessionId,
|
|
resolvePartDedupeKey,
|
|
mapToolStatus,
|
|
toolPartToSnapshot,
|
|
toolCalledSnapshot,
|
|
toolSuccessSnapshot,
|
|
toolFailedSnapshot,
|
|
classifyPartDelta,
|
|
classifyUpdatedPart,
|
|
errToString,
|
|
errMsg,
|
|
type DedupState,
|
|
} from '../opencode-event-map.js';
|
|
|
|
/**
|
|
* Pure opencode Event → AgentEvent translation + dedup gate (v2.7 audit reshape).
|
|
* Mirrors the original `dispatchEvent` / `handleUpdatedPart` arms verbatim — no
|
|
* I/O, so it's unit-testable. The slimmed backend keeps the routing + side effects.
|
|
*/
|
|
|
|
function freshDedup(): DedupState {
|
|
return { streamedPartKeys: new Set(), partTypeById: new Map() };
|
|
}
|
|
|
|
describe('stripDcpTags', () => {
|
|
it('removes a complete dcp tag', () => {
|
|
expect(stripDcpTags('hi <dcp-message-id>m1</dcp-message-id> there')).toBe('hi there');
|
|
});
|
|
it('leaves untagged text untouched', () => {
|
|
expect(stripDcpTags('plain text <div>')).toBe('plain text <div>');
|
|
});
|
|
});
|
|
|
|
describe('eventSessionId', () => {
|
|
it('reads properties.sessionID for a normal event', () => {
|
|
const ev = { type: 'session.idle', properties: { sessionID: 's1' } } as unknown as Event;
|
|
expect(eventSessionId(ev)).toBe('s1');
|
|
});
|
|
it('reads properties.part.sessionID for message.part.updated', () => {
|
|
const ev = {
|
|
type: 'message.part.updated',
|
|
properties: { part: { sessionID: 's2' } },
|
|
} as unknown as Event;
|
|
expect(eventSessionId(ev)).toBe('s2');
|
|
});
|
|
it('returns null when there is no session', () => {
|
|
const ev = { type: 'server.connected', properties: {} } as unknown as Event;
|
|
expect(eventSessionId(ev)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('resolvePartDedupeKey', () => {
|
|
it('prefers the part id', () => {
|
|
expect(resolvePartDedupeKey({ id: 'p1', messageID: 'm1' }, 'text')).toBe('text:p1');
|
|
});
|
|
it('falls back to the message id', () => {
|
|
expect(resolvePartDedupeKey({ id: ' ', messageID: 'm1' }, 'reasoning')).toBe('reasoning:message:m1');
|
|
});
|
|
it('returns null when neither is present', () => {
|
|
expect(resolvePartDedupeKey({ id: '', messageID: '' }, 'text')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('mapToolStatus', () => {
|
|
it('maps the opencode tool states to ACP statuses', () => {
|
|
expect(mapToolStatus('pending')).toBe('pending');
|
|
expect(mapToolStatus('running')).toBe('in_progress');
|
|
expect(mapToolStatus('completed')).toBe('completed');
|
|
expect(mapToolStatus('error')).toBe('failed');
|
|
expect(mapToolStatus(undefined)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('session.next.tool.* snapshot builders', () => {
|
|
it('toolCalledSnapshot → in_progress with tool title + raw input', () => {
|
|
expect(toolCalledSnapshot({ callID: 'c1', tool: 'read_file', input: { path: 'a.ts' } })).toEqual({
|
|
toolCallId: 'c1',
|
|
title: 'read_file',
|
|
kind: null,
|
|
status: 'in_progress',
|
|
rawInput: { path: 'a.ts' },
|
|
rawOutput: undefined,
|
|
});
|
|
});
|
|
it('toolSuccessSnapshot → completed with joined text content', () => {
|
|
const snap = toolSuccessSnapshot({ callID: 'c1', content: [{ text: 'foo' }, { text: 'bar' }, { other: 1 }] });
|
|
expect(snap.status).toBe('completed');
|
|
expect(snap.title).toBe('c1');
|
|
expect(snap.rawOutput).toBe('foobar');
|
|
});
|
|
it('toolSuccessSnapshot → empty output when content is missing', () => {
|
|
expect(toolSuccessSnapshot({ callID: 'c1' }).rawOutput).toBe('');
|
|
});
|
|
it('toolFailedSnapshot → failed with stringified error', () => {
|
|
const snap = toolFailedSnapshot({ callID: 'c1', error: 'boom' });
|
|
expect(snap.status).toBe('failed');
|
|
expect(snap.title).toBe('c1');
|
|
expect(snap.rawOutput).toBe('boom');
|
|
});
|
|
});
|
|
|
|
describe('toolPartToSnapshot', () => {
|
|
it('extracts input/output/title/status from the tool state', () => {
|
|
const part = {
|
|
type: 'tool',
|
|
callID: 'c1',
|
|
tool: 'grep',
|
|
state: { status: 'completed', input: { q: 'x' }, output: 'result', title: 'Grep run' },
|
|
} as unknown as Parameters<typeof toolPartToSnapshot>[0];
|
|
expect(toolPartToSnapshot(part)).toEqual({
|
|
toolCallId: 'c1',
|
|
title: 'Grep run',
|
|
kind: null,
|
|
status: 'completed',
|
|
rawInput: { q: 'x' },
|
|
rawOutput: 'result',
|
|
});
|
|
});
|
|
it('falls back to the tool name and uses error as output', () => {
|
|
const part = {
|
|
type: 'tool',
|
|
callID: 'c2',
|
|
tool: 'edit',
|
|
state: { status: 'error', error: 'nope' },
|
|
} as unknown as Parameters<typeof toolPartToSnapshot>[0];
|
|
const snap = toolPartToSnapshot(part);
|
|
expect(snap.title).toBe('edit');
|
|
expect(snap.status).toBe('failed');
|
|
expect(snap.rawOutput).toBe('nope');
|
|
});
|
|
});
|
|
|
|
describe('classifyPartDelta (message.part.delta dedup recording)', () => {
|
|
it('records a reasoning key and emits a reasoning event', () => {
|
|
const st = freshDedup();
|
|
const e = classifyPartDelta({ partID: 'p1', field: 'reasoning', delta: 'thinking' }, st);
|
|
expect(e).toEqual({ type: 'reasoning', text: 'thinking' });
|
|
expect(st.streamedPartKeys.has('reasoning:p1')).toBe(true);
|
|
});
|
|
it('records a text key, strips dcp, and emits text', () => {
|
|
const st = freshDedup();
|
|
const e = classifyPartDelta({ partID: 'p2', field: 'text', delta: 'hi <dcp-message-id>m</dcp-message-id>' }, st);
|
|
expect(e).toEqual({ type: 'text', text: 'hi ' });
|
|
expect(st.streamedPartKeys.has('text:p2')).toBe(true);
|
|
});
|
|
it('still records the text key even when the cleaned delta is empty', () => {
|
|
const st = freshDedup();
|
|
const e = classifyPartDelta({ partID: 'p3', field: 'text', delta: '<dcp-message-id>m</dcp-message-id>' }, st);
|
|
expect(e).toBeNull();
|
|
expect(st.streamedPartKeys.has('text:p3')).toBe(true);
|
|
});
|
|
it('uses the recorded part type when the field is absent', () => {
|
|
const st = freshDedup();
|
|
st.partTypeById.set('p4', 'reasoning');
|
|
const e = classifyPartDelta({ partID: 'p4', delta: 'more' }, st);
|
|
expect(e).toEqual({ type: 'reasoning', text: 'more' });
|
|
});
|
|
it('returns null for an unknown field', () => {
|
|
expect(classifyPartDelta({ partID: 'p5', field: 'other', delta: 'x' }, freshDedup())).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('classifyUpdatedPart (message.part.updated dedup gate)', () => {
|
|
function textPart(over: Partial<Part> = {}): Part {
|
|
return {
|
|
type: 'text',
|
|
id: 'p1',
|
|
messageID: 'm1',
|
|
sessionID: 's1',
|
|
text: 'final text',
|
|
time: { start: 1, end: 2 },
|
|
...over,
|
|
} as unknown as Part;
|
|
}
|
|
|
|
it('drops a terminal part already streamed via deltas', () => {
|
|
const st = freshDedup();
|
|
st.streamedPartKeys.add('text:p1');
|
|
expect(classifyUpdatedPart(textPart(), st)).toBeNull();
|
|
// the key is consumed
|
|
expect(st.streamedPartKeys.has('text:p1')).toBe(false);
|
|
});
|
|
it('emits a finished (ended) text part not seen via deltas', () => {
|
|
const st = freshDedup();
|
|
expect(classifyUpdatedPart(textPart(), st)).toEqual({ type: 'text', text: 'final text' });
|
|
expect(st.partTypeById.get('p1')).toBe('text');
|
|
});
|
|
it('does not emit a part that has not ended yet', () => {
|
|
const st = freshDedup();
|
|
expect(classifyUpdatedPart(textPart({ time: { start: 1 } as never }), st)).toBeNull();
|
|
});
|
|
it('strips dcp tags from the finished text', () => {
|
|
const st = freshDedup();
|
|
const part = textPart({ text: 'a <dcp-message-id>m</dcp-message-id>b' });
|
|
expect(classifyUpdatedPart(part, st)).toEqual({ type: 'text', text: 'a b' });
|
|
});
|
|
it('maps a running tool part to tool_call', () => {
|
|
const st = freshDedup();
|
|
const part = { type: 'tool', callID: 'c1', tool: 'grep', state: { status: 'running' } } as unknown as Part;
|
|
const e = classifyUpdatedPart(part, st);
|
|
expect(e?.type).toBe('tool_call');
|
|
});
|
|
it('maps a completed tool part to tool_update', () => {
|
|
const st = freshDedup();
|
|
const part = { type: 'tool', callID: 'c1', tool: 'grep', state: { status: 'completed', output: 'x' } } as unknown as Part;
|
|
const e = classifyUpdatedPart(part, st);
|
|
expect(e?.type).toBe('tool_update');
|
|
});
|
|
});
|
|
|
|
describe('error formatters', () => {
|
|
it('errMsg unwraps Error.message', () => {
|
|
expect(errMsg(new Error('x'))).toBe('x');
|
|
expect(errMsg('plain')).toBe('plain');
|
|
});
|
|
it('errToString handles null/string/Error/object', () => {
|
|
expect(errToString(null)).toBe('unknown error');
|
|
expect(errToString('s')).toBe('s');
|
|
expect(errToString(new Error('e'))).toBe('e');
|
|
expect(errToString({ a: 1 })).toBe('{"a":1}');
|
|
});
|
|
});
|