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:
@@ -9,35 +9,20 @@ import {
|
||||
ClientSideConnection,
|
||||
type Client,
|
||||
type SessionNotification,
|
||||
type RequestPermissionRequest,
|
||||
type RequestPermissionResponse,
|
||||
type ReadTextFileRequest,
|
||||
type ReadTextFileResponse,
|
||||
type WriteTextFileRequest,
|
||||
type WriteTextFileResponse,
|
||||
type CreateTerminalRequest,
|
||||
type CreateTerminalResponse,
|
||||
type CreateElicitationRequest,
|
||||
type CreateElicitationResponse,
|
||||
type SessionConfigOption,
|
||||
type ClientSideConnection as ConnectionType,
|
||||
} from '@agentclientprotocol/sdk';
|
||||
import type { Broker } from '@boocode/server/broker';
|
||||
import type { WsFrame } from '@boocode/server/ws-frames';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { findThoughtLevelConfigId } from './acp-derive.js';
|
||||
import { resolveLaunchSpec } from './acp-spawn.js';
|
||||
import { getResolvedRegistry, type ResolvedProviderDef } from './provider-config-registry.js';
|
||||
import { createAcpNdJsonStream } from './acp-stream.js';
|
||||
import { waitForPermissionResponse, waitForElicitationResponse, cancelPendingPermission } from './permission-waiter.js';
|
||||
import { mergeTaskCommands, getTaskCommands } from './agent-commands-cache.js';
|
||||
import { readWorktreeTextFile, writeWorktreeTextFile } from './acp-client-fs.js';
|
||||
import { cancelPendingPermission } from './permission-waiter.js';
|
||||
import { mapSessionUpdate } from './acp-event-map.js';
|
||||
import {
|
||||
type AcpToolSnapshot,
|
||||
snapshotToWireToolCall,
|
||||
synthesizeCanceledSnapshots,
|
||||
} from './acp-tool-snapshot.js';
|
||||
import { type AcpToolSnapshot, synthesizeCanceledSnapshots } from './acp-tool-snapshot.js';
|
||||
import { makeFrameEmitter, type FrameEmitter } from './frame-emitter.js';
|
||||
import { buildAcpClient } from './acp-client.js';
|
||||
|
||||
export interface AcpDispatchResult {
|
||||
exitCode: number;
|
||||
@@ -111,144 +96,61 @@ async function applySessionOverrides(
|
||||
}
|
||||
|
||||
class AcpStreamContext {
|
||||
readonly textChunks: string[] = [];
|
||||
readonly reasoningChunks: string[] = [];
|
||||
readonly toolSnapshots = new Map<string, AcpToolSnapshot>();
|
||||
private aborted = false;
|
||||
/** AgentEvent → WS-frame mapping + text/reasoning/tool accumulation (shared
|
||||
* `makeFrameEmitter`). The one-shot path passes no `dcp` stripper, so text is
|
||||
* emitted verbatim — byte-identical to the prior inline switch. */
|
||||
private readonly emitter: FrameEmitter;
|
||||
|
||||
constructor(
|
||||
private readonly opts: Pick<
|
||||
AcpDispatchOpts,
|
||||
'broker' | 'sessionId' | 'chatId' | 'messageId' | 'taskId'
|
||||
>,
|
||||
opts: Pick<AcpDispatchOpts, 'broker' | 'sessionId' | 'chatId' | 'messageId' | 'taskId'>,
|
||||
private readonly worktreePath: string,
|
||||
) {}
|
||||
) {
|
||||
this.emitter = makeFrameEmitter({
|
||||
broker: opts.broker,
|
||||
sessionId: opts.sessionId,
|
||||
chatId: opts.chatId,
|
||||
assistantId: opts.messageId,
|
||||
taskId: opts.taskId,
|
||||
});
|
||||
}
|
||||
|
||||
get reasoningText(): string {
|
||||
return this.reasoningChunks.join('');
|
||||
return this.emitter.reasoningText;
|
||||
}
|
||||
|
||||
get output(): string {
|
||||
return this.textChunks.join('');
|
||||
return this.emitter.output;
|
||||
}
|
||||
|
||||
get snapshots(): AcpToolSnapshot[] {
|
||||
return [...this.toolSnapshots.values()];
|
||||
return this.emitter.snapshots;
|
||||
}
|
||||
|
||||
markAborted(): void {
|
||||
this.aborted = true;
|
||||
for (const snap of synthesizeCanceledSnapshots(this.toolSnapshots.values())) {
|
||||
this.toolSnapshots.set(snap.toolCallId, snap);
|
||||
this.publishToolSnapshot(snap);
|
||||
// Synthesize 'canceled' updates for still-running tool calls so the UI doesn't
|
||||
// leave them spinning, then emit them through the same frame path (tool_update
|
||||
// → the same `tool_call` wire frame the original published).
|
||||
for (const snap of synthesizeCanceledSnapshots(this.emitter.toolSnapshots.values())) {
|
||||
this.emitter.onEvent({ type: 'tool_update', toolCall: snap });
|
||||
}
|
||||
}
|
||||
|
||||
private canStream(): boolean {
|
||||
return !!(this.opts.broker && this.opts.sessionId && this.opts.chatId && this.opts.messageId);
|
||||
}
|
||||
|
||||
private publishToolSnapshot(snapshot: AcpToolSnapshot): void {
|
||||
if (!this.canStream()) return;
|
||||
const wire = snapshotToWireToolCall(snapshot);
|
||||
this.opts.broker!.publishFrame(this.opts.sessionId!, {
|
||||
type: 'tool_call',
|
||||
message_id: this.opts.messageId!,
|
||||
chat_id: this.opts.chatId!,
|
||||
tool_call: wire,
|
||||
} as WsFrame);
|
||||
}
|
||||
|
||||
async handleSessionUpdate(params: SessionNotification): Promise<void> {
|
||||
// v2.6 Phase 2: the case-by-case mapping now lives in the shared, pure
|
||||
// `mapSessionUpdate` (reused by the warm ACP backend). This method keeps the
|
||||
// identical broker-publishing side effects — it just translates the normalized
|
||||
// AgentEvents back into the same frames it always emitted. `this.toolSnapshots`
|
||||
// is the merge accumulator, so a later tool_call_update merges over its
|
||||
// tool_call (the prior `handleToolUpdate` behavior, byte-for-byte).
|
||||
for (const event of mapSessionUpdate(params, this.toolSnapshots)) {
|
||||
switch (event.type) {
|
||||
case 'text':
|
||||
this.textChunks.push(event.text);
|
||||
if (this.canStream()) {
|
||||
this.opts.broker!.publishFrame(this.opts.sessionId!, {
|
||||
type: 'delta',
|
||||
message_id: this.opts.messageId!,
|
||||
chat_id: this.opts.chatId!,
|
||||
content: event.text,
|
||||
} as WsFrame);
|
||||
}
|
||||
break;
|
||||
case 'reasoning':
|
||||
this.reasoningChunks.push(event.text);
|
||||
if (this.canStream()) {
|
||||
this.opts.broker!.publishFrame(this.opts.sessionId!, {
|
||||
type: 'reasoning_delta',
|
||||
message_id: this.opts.messageId!,
|
||||
chat_id: this.opts.chatId!,
|
||||
content: event.text,
|
||||
} as WsFrame);
|
||||
}
|
||||
break;
|
||||
case 'tool_call':
|
||||
case 'tool_update':
|
||||
// mapSessionUpdate already stored the merged snapshot in this.toolSnapshots.
|
||||
this.publishToolSnapshot(event.toolCall);
|
||||
break;
|
||||
case 'commands':
|
||||
if (this.opts.taskId && event.commands.length > 0) {
|
||||
mergeTaskCommands(this.opts.taskId, event.commands);
|
||||
if (this.canStream() && this.opts.sessionId) {
|
||||
const all = getTaskCommands(this.opts.taskId) ?? event.commands;
|
||||
this.opts.broker!.publishFrame(this.opts.sessionId, {
|
||||
type: 'agent_commands',
|
||||
task_id: this.opts.taskId,
|
||||
session_id: this.opts.sessionId,
|
||||
commands: all,
|
||||
} as WsFrame);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
handleSessionUpdate(params: SessionNotification): void {
|
||||
// The merge accumulator (`this.emitter.toolSnapshots`) is the same Map the
|
||||
// emitter publishes from, so a later tool_call_update merges over its tool_call.
|
||||
for (const event of mapSessionUpdate(params, this.emitter.toolSnapshots)) {
|
||||
this.emitter.onEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
buildClient(agent: string, modeId: string | undefined, taskId: string | undefined, sessionId: string | undefined): Client {
|
||||
return {
|
||||
sessionUpdate: (params) => this.handleSessionUpdate(params),
|
||||
requestPermission: async (params: RequestPermissionRequest): Promise<RequestPermissionResponse> => {
|
||||
if (taskId && sessionId) {
|
||||
return waitForPermissionResponse(taskId, sessionId, agent, modeId, params);
|
||||
}
|
||||
const firstOption = params.options[0];
|
||||
if (firstOption) {
|
||||
return { outcome: { outcome: 'selected', optionId: firstOption.optionId } };
|
||||
}
|
||||
return { outcome: { outcome: 'cancelled' } };
|
||||
},
|
||||
readTextFile: async (params: ReadTextFileRequest): Promise<ReadTextFileResponse> => {
|
||||
const content = await readWorktreeTextFile(
|
||||
this.worktreePath,
|
||||
params.path,
|
||||
params.line,
|
||||
params.limit,
|
||||
);
|
||||
return { content };
|
||||
},
|
||||
writeTextFile: async (params: WriteTextFileRequest): Promise<WriteTextFileResponse> => {
|
||||
await writeWorktreeTextFile(this.worktreePath, params.path, params.content);
|
||||
return {};
|
||||
},
|
||||
createTerminal: async (_params: CreateTerminalRequest): Promise<CreateTerminalResponse> => {
|
||||
return { terminalId: 'noop' };
|
||||
},
|
||||
unstable_createElicitation: async (params: CreateElicitationRequest): Promise<CreateElicitationResponse> => {
|
||||
if (taskId && sessionId) {
|
||||
return waitForElicitationResponse(taskId, sessionId, agent, modeId, params);
|
||||
}
|
||||
return { action: 'decline' };
|
||||
},
|
||||
};
|
||||
return buildAcpClient(this.worktreePath, () => ({
|
||||
taskId,
|
||||
sessionId,
|
||||
modeId,
|
||||
agent,
|
||||
onSessionUpdate: (params) => this.handleSessionUpdate(params),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user