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:
88
apps/coder/src/services/acp-client.ts
Normal file
88
apps/coder/src/services/acp-client.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Shared ACP `Client` builder — the callback closures every ACP connection needs
|
||||
* (worktree-scoped FS bridge + permission/elicitation routing + session updates).
|
||||
*
|
||||
* Extracted (v2.7 audit reshape) from the byte-identical `buildClient` closures in
|
||||
* `acp-dispatch.ts` (one-shot) and `backends/warm-acp.ts` (warm). The two differed
|
||||
* only in WHERE the per-turn context comes from (a fixed dispatch vs. the warm
|
||||
* backend's `activeTurn`) and a trivially-equivalent permission gate — both are now
|
||||
* supplied via the `resolveTurn` callback, so the FS/permission/elicitation wiring
|
||||
* lives once. Behavior is preserved exactly:
|
||||
* - `sessionUpdate` drops when `resolveTurn()` returns null (between turns).
|
||||
* - permission/elicitation route to the UI only when BOTH a taskId AND sessionId
|
||||
* are present (warm always has a sessionId, so this matches its prior
|
||||
* `turn?.taskId` gate); otherwise the same auto-select-first / decline fallback.
|
||||
*/
|
||||
import type {
|
||||
Client,
|
||||
SessionNotification,
|
||||
RequestPermissionRequest,
|
||||
RequestPermissionResponse,
|
||||
ReadTextFileRequest,
|
||||
ReadTextFileResponse,
|
||||
WriteTextFileRequest,
|
||||
WriteTextFileResponse,
|
||||
CreateTerminalRequest,
|
||||
CreateTerminalResponse,
|
||||
CreateElicitationRequest,
|
||||
CreateElicitationResponse,
|
||||
} from '@agentclientprotocol/sdk';
|
||||
import { readWorktreeTextFile, writeWorktreeTextFile } from './acp-client-fs.js';
|
||||
import { waitForPermissionResponse, waitForElicitationResponse } from './permission-waiter.js';
|
||||
|
||||
/** The per-turn context an ACP `Client` closure needs, resolved lazily per call. */
|
||||
export interface AcpTurnContext {
|
||||
/** Per-turn task id, for routing permission/elicitation prompts back to the UI. */
|
||||
taskId: string | undefined;
|
||||
/** BooCode session id (for permission-waiter's broker frames). */
|
||||
sessionId: string | undefined;
|
||||
/** Per-turn mode id (autonomous-mode gate in permission-waiter). */
|
||||
modeId: string | undefined;
|
||||
/** The agent name (for permission-waiter routing). */
|
||||
agent: string;
|
||||
/** Forward a session/update notification to the turn's event sink. */
|
||||
onSessionUpdate: (params: SessionNotification) => void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the ACP `Client` callbacks once per connection. `resolveTurn` is called at
|
||||
* the moment each callback fires and returns the live turn context (or null when no
|
||||
* turn is active — `sessionUpdate` then drops, matching the warm backend's
|
||||
* between-turns behavior). The FS bridge is scoped to `worktreePath`.
|
||||
*/
|
||||
export function buildAcpClient(worktreePath: string, resolveTurn: () => AcpTurnContext | null): Client {
|
||||
return {
|
||||
sessionUpdate: async (params: SessionNotification): Promise<void> => {
|
||||
const turn = resolveTurn();
|
||||
if (!turn) return; // between turns — drop (no orphan settles a future turn)
|
||||
await turn.onSessionUpdate(params);
|
||||
},
|
||||
requestPermission: async (params: RequestPermissionRequest): Promise<RequestPermissionResponse> => {
|
||||
const turn = resolveTurn();
|
||||
if (turn && turn.taskId && turn.sessionId) {
|
||||
return waitForPermissionResponse(turn.taskId, turn.sessionId, turn.agent, turn.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(worktreePath, params.path, params.line, params.limit);
|
||||
return { content };
|
||||
},
|
||||
writeTextFile: async (params: WriteTextFileRequest): Promise<WriteTextFileResponse> => {
|
||||
await writeWorktreeTextFile(worktreePath, params.path, params.content);
|
||||
return {};
|
||||
},
|
||||
createTerminal: async (_params: CreateTerminalRequest): Promise<CreateTerminalResponse> => {
|
||||
return { terminalId: 'noop' };
|
||||
},
|
||||
unstable_createElicitation: async (params: CreateElicitationRequest): Promise<CreateElicitationResponse> => {
|
||||
const turn = resolveTurn();
|
||||
if (turn && turn.taskId && turn.sessionId) {
|
||||
return waitForElicitationResponse(turn.taskId, turn.sessionId, turn.agent, turn.modeId, params);
|
||||
}
|
||||
return { action: 'decline' };
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user