apex-code 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/dist/cli/agent-lifecycle.d.ts +24 -0
- package/dist/cli/agent-lifecycle.d.ts.map +1 -0
- package/dist/cli/agent-lifecycle.js +126 -0
- package/dist/cli/agent-lifecycle.js.map +1 -0
- package/dist/cli/args.d.ts +13 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +80 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +160 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +265 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/delegation/runtime.d.ts +682 -1
- package/dist/core/delegation/runtime.d.ts.map +1 -1
- package/dist/core/delegation/runtime.js +1339 -66
- package/dist/core/delegation/runtime.js.map +1 -1
- package/dist/core/sdk.d.ts +61 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +316 -22
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/tools/delegate.d.ts +8 -0
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +33 -3
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/workspace/git-observer.d.ts +16 -0
- package/dist/core/workspace/git-observer.d.ts.map +1 -1
- package/dist/core/workspace/git-observer.js +8 -1
- package/dist/core/workspace/git-observer.js.map +1 -1
- package/dist/core/workspace/git-worktree-owner.d.ts +89 -0
- package/dist/core/workspace/git-worktree-owner.d.ts.map +1 -0
- package/dist/core/workspace/git-worktree-owner.js +240 -0
- package/dist/core/workspace/git-worktree-owner.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +20 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/acp/server.d.ts +43 -1
- package/dist/modes/acp/server.d.ts.map +1 -1
- package/dist/modes/acp/server.js +78 -0
- package/dist/modes/acp/server.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +38 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +110 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/npm-shrinkwrap.json +5 -5
- package/package.json +2 -2
package/dist/modes/acp/server.js
CHANGED
|
@@ -203,6 +203,84 @@ export class AcpServer {
|
|
|
203
203
|
this.#write({ jsonrpc: "2.0", id, result: {} });
|
|
204
204
|
return;
|
|
205
205
|
}
|
|
206
|
+
// Child lifecycle parity (spec 2026-09-09, protocol section): every
|
|
207
|
+
// operation routes through the same AgentSession pass-throughs RPC
|
|
208
|
+
// calls, with the same payload shapes and the same unknown-ID
|
|
209
|
+
// rejection surfacing as -32000 errors. There is no second,
|
|
210
|
+
// host-supplied lifecycle path.
|
|
211
|
+
case "agent/list": {
|
|
212
|
+
this.#write({ jsonrpc: "2.0", id, result: this.#host.getSession().listChildRuns() });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
case "agent/send": {
|
|
216
|
+
if (typeof record.runId !== "string" || typeof record.input !== "string")
|
|
217
|
+
throw new Error("agent/send requires runId and input.");
|
|
218
|
+
await this.#host.getSession().sendChildInput(record.runId, record.input);
|
|
219
|
+
this.#write({ jsonrpc: "2.0", id, result: null });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
case "agent/spawn": {
|
|
223
|
+
if (typeof record.agentType !== "string" || typeof record.task !== "string")
|
|
224
|
+
throw new Error("agent/spawn requires agentType and task.");
|
|
225
|
+
const workspace = record.workspace;
|
|
226
|
+
const idempotencyKey = typeof record.idempotencyKey === "string" ? record.idempotencyKey : undefined;
|
|
227
|
+
const timeoutMs = typeof record.timeoutMs === "number" ? record.timeoutMs : undefined;
|
|
228
|
+
const spawned = await this.#host.getSession().startChildRun(record.agentType, record.task, {
|
|
229
|
+
...(workspace ? { workspace } : {}),
|
|
230
|
+
...(idempotencyKey !== undefined ? { idempotencyKey } : {}),
|
|
231
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
232
|
+
});
|
|
233
|
+
this.#write({ jsonrpc: "2.0", id, result: spawned });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
case "agent/wait": {
|
|
237
|
+
if (typeof record.runId !== "string")
|
|
238
|
+
throw new Error("agent/wait requires runId.");
|
|
239
|
+
// Identical to RPC's agent/wait: the awaited latest settled turn
|
|
240
|
+
// with the registry status after settlement.
|
|
241
|
+
const waited = await this.#host.getSession().waitChildRunResult(record.runId);
|
|
242
|
+
this.#write({ jsonrpc: "2.0", id, result: waited });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
case "agent/status": {
|
|
246
|
+
if (typeof record.runId !== "string")
|
|
247
|
+
throw new Error("agent/status requires runId.");
|
|
248
|
+
// Non-blocking: the snapshot is built from the record/entry alone.
|
|
249
|
+
const status = this.#host.getSession().childRunStatus(record.runId);
|
|
250
|
+
this.#write({ jsonrpc: "2.0", id, result: status });
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
case "agent/resume": {
|
|
254
|
+
if (typeof record.runId !== "string")
|
|
255
|
+
throw new Error("agent/resume requires runId.");
|
|
256
|
+
const input = typeof record.input === "string" ? record.input : undefined;
|
|
257
|
+
const status = await this.#host.getSession().resumeChildRun(record.runId, input);
|
|
258
|
+
this.#write({ jsonrpc: "2.0", id, result: { status } });
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
case "agent/recover": {
|
|
262
|
+
if (typeof record.runId !== "string")
|
|
263
|
+
throw new Error("agent/recover requires runId.");
|
|
264
|
+
// Identical to RPC's agent/recover: the verified reactivation
|
|
265
|
+
// payload `{workspaceState, dirty}`; refusals surface as -32000.
|
|
266
|
+
const recovered = await this.#host.getSession().recoverChildWorkspace(record.runId);
|
|
267
|
+
this.#write({ jsonrpc: "2.0", id, result: recovered });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
case "agent/interrupt":
|
|
271
|
+
case "agent/close": {
|
|
272
|
+
if (typeof record.runId !== "string")
|
|
273
|
+
throw new Error(`${method} requires runId.`);
|
|
274
|
+
const session = this.#host.getSession();
|
|
275
|
+
if (method === "agent/interrupt") {
|
|
276
|
+
const reason = typeof record.reason === "string" ? record.reason : undefined;
|
|
277
|
+
session.interruptChildRun(record.runId, reason);
|
|
278
|
+
}
|
|
279
|
+
else
|
|
280
|
+
session.closeChildRun(record.runId);
|
|
281
|
+
this.#write({ jsonrpc: "2.0", id, result: null });
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
206
284
|
default:
|
|
207
285
|
this.#write({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
|
|
208
286
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/modes/acp/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Readable, Writable } from \"node:stream\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\nimport type { InputSource } from \"../../core/extensions/types.ts\";\nimport { takeOverStdout } from \"../../core/output-guard.ts\";\nimport type { PermissionAnswer } from \"../../core/permissions/responder.ts\";\nimport { attachJsonlLineReader, serializeJsonLine } from \"../rpc/jsonl.ts\";\nimport { translateJsonEvent } from \"./translate.ts\";\n\n/**\n * The ACP server (spec 2026-08-31-acp-adapter.md): an in-process ACP v1 agent\n * over newline-delimited JSON-RPC on stdio. The host adapters between the\n * protocol and the live AgentSession; the server never imports session types,\n * so tests drive it entirely through fakes.\n */\n\n/** The slice of AgentSession the protocol actually drives. */\nexport interface AcpPromptableSession {\n\tprompt(text: string, options: { source: InputSource }): Promise<unknown>;\n\tabort(): Promise<void> | void;\n}\n\nexport interface AcpHost {\n\tgetSession(): AcpPromptableSession;\n\tcreateSession(cwd: string): Promise<AcpPromptableSession>;\n\tloadSession(sessionId: string, cwd: string): Promise<AcpPromptableSession>;\n\tsetMode?(mode: string): Promise<void> | void;\n}\n\nconst PERMISSION_OPTIONS = [\n\t{ optionId: \"allow-once\", name: \"Allow once\", kind: \"allow_once\" },\n\t// `optionId` and `kind` are the wire contract; only the display name is ours to fix.\n\t// The gate writes this to `session`, so \"always\" was never true.\n\t{ optionId: \"allow-always\", name: \"Allow for this session\", kind: \"allow_always\" },\n\t{ optionId: \"reject-once\", name: \"Reject\", kind: \"reject_once\" },\n\t{ optionId: \"reject-always\", name: \"Reject always\", kind: \"reject_always\" },\n] as const;\n\nexport class AcpServer {\n\treadonly #input: Readable;\n\treadonly #output: Writable;\n\treadonly #host: AcpHost;\n\treadonly #pendingPermissions = new Map<string, (answer: PermissionAnswer) => void>();\n\treadonly #seenToolCalls = new Set<string>();\n\t#currentSessionId: string | undefined;\n\n\tconstructor(deps: { input: Readable; output: Writable; host: AcpHost }) {\n\t\tthis.#input = deps.input;\n\t\tthis.#output = deps.output;\n\t\tthis.#host = deps.host;\n\t}\n\n\tstart(): void {\n\t\tattachJsonlLineReader(this.#input, (line) => this.handleLine(line));\n\t}\n\n\tget currentSessionId(): string | undefined {\n\t\treturn this.#currentSessionId;\n\t}\n\n\thandleLine(line: string): void {\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(line);\n\t\t} catch {\n\t\t\t// ACP is line-delimited JSON; anything else on the wire is dropped, not fatal.\n\t\t\treturn;\n\t\t}\n\t\tif (typeof parsed !== \"object\" || parsed === null) return;\n\t\tconst message = parsed as Record<string, unknown>;\n\t\tconst { id, method, params } = message;\n\n\t\tif (typeof method !== \"string\") {\n\t\t\t// No method: a response to one of our server->client requests (permission).\n\t\t\tif (id !== undefined) this.#resolvePermission(String(id), message.result);\n\t\t\treturn;\n\t\t}\n\t\tif (id === undefined) {\n\t\t\tif (method === \"session/cancel\") {\n\t\t\t\tvoid this.#host.getSession().abort();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tvoid this.#dispatch(id, method, params);\n\t}\n\n\t/** Entry point for live session events; translates and emits session/update. */\n\tonSessionEvent(event: unknown): void {\n\t\tif (this.#currentSessionId === undefined) return;\n\t\tconst translated = translateJsonEvent(event);\n\t\tif (!translated) return;\n\t\tif (translated.kind === \"message\") {\n\t\t\tthis.#notify(this.#currentSessionId, translated.update);\n\t\t\treturn;\n\t\t}\n\t\tconst seen = this.#seenToolCalls.has(translated.toolCallId);\n\t\tthis.#seenToolCalls.add(translated.toolCallId);\n\t\tthis.#notify(this.#currentSessionId, {\n\t\t\tsessionUpdate: seen ? \"tool_call_update\" : \"tool_call\",\n\t\t\t...translated.update,\n\t\t});\n\t}\n\n\t/**\n\t * The PermissionResponder bridge: turns one ACP permission round trip into\n\t * the gate's answer shape. Rule persistence itself stays in the gate\n\t * (ADR 0010) -- `persist` is a request, not a written rule.\n\t */\n\t/**\n\t * `canPersistSession` mirrors the gate's `sessionScope`. When the tool's\n\t * `ruleForCall()` yields nothing, the gate has no rule to write, so neither\n\t * standing choice can keep its promise and neither is offered.\n\t */\n\taskPermission(\n\t\tsessionId: string,\n\t\ttoolName: string,\n\t\tdescription: string,\n\t\tcanPersistSession = true,\n\t): Promise<PermissionAnswer> {\n\t\tconst id = `perm_${randomUUID()}`;\n\t\treturn new Promise<PermissionAnswer>((resolve) => {\n\t\t\tthis.#pendingPermissions.set(id, resolve);\n\t\t\tthis.#write({\n\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\tid,\n\t\t\t\tmethod: \"session/request_permission\",\n\t\t\t\tparams: {\n\t\t\t\t\tsessionId,\n\t\t\t\t\ttoolCall: { toolCallId: id, title: `${toolName}: ${description}`, kind: \"other\", status: \"pending\" },\n\t\t\t\t\t// Both standing choices need a rule to stand on. Offering either when\n\t\t\t\t\t// ruleForCall() yields nothing promises a decision that outlives the\n\t\t\t\t\t// call and then does not.\n\t\t\t\t\toptions: canPersistSession\n\t\t\t\t\t\t? PERMISSION_OPTIONS\n\t\t\t\t\t\t: PERMISSION_OPTIONS.filter(\n\t\t\t\t\t\t\t\t(option) => option.kind !== \"allow_always\" && option.kind !== \"reject_always\",\n\t\t\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t}\n\n\t#write(value: unknown): void {\n\t\tthis.#output.write(serializeJsonLine(value));\n\t}\n\n\t#notify(sessionId: string, update: Record<string, unknown>): void {\n\t\tthis.#write({ jsonrpc: \"2.0\", method: \"session/update\", params: { sessionId, update } });\n\t}\n\n\t#resolvePermission(id: string, result: unknown): void {\n\t\tconst resolve = this.#pendingPermissions.get(id);\n\t\tif (!resolve) return;\n\t\tthis.#pendingPermissions.delete(id);\n\t\tconst outcome = (result as { outcome?: { outcome?: string; optionId?: string } } | undefined)?.outcome;\n\t\tif (outcome?.outcome !== \"selected\" || typeof outcome.optionId !== \"string\") {\n\t\t\t// Cancelled, malformed, or unknown -- fail closed.\n\t\t\tresolve({ allow: false });\n\t\t\treturn;\n\t\t}\n\t\tif (outcome.optionId.startsWith(\"allow\")) {\n\t\t\tresolve({ allow: true, persist: outcome.optionId === \"allow-always\" });\n\t\t\treturn;\n\t\t}\n\t\tresolve({ allow: false, persist: outcome.optionId === \"reject-always\" });\n\t}\n\n\tasync #dispatch(id: unknown, method: string, params: unknown): Promise<void> {\n\t\tconst record = (typeof params === \"object\" && params !== null ? params : {}) as Record<string, unknown>;\n\t\ttry {\n\t\t\tswitch (method) {\n\t\t\t\tcase \"initialize\": {\n\t\t\t\t\tthis.#write({\n\t\t\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tresult: {\n\t\t\t\t\t\t\tprotocolVersion: 1,\n\t\t\t\t\t\t\tagentCapabilities: { loadSession: true },\n\t\t\t\t\t\t\tpromptCapabilities: { image: false, audio: false, embeddedContext: false },\n\t\t\t\t\t\t\tagentInfo: { name: \"apex-code\", title: \"Apex Code\" },\n\t\t\t\t\t\t\tauthMethods: [],\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session/new\": {\n\t\t\t\t\tconst cwd = typeof record.cwd === \"string\" ? record.cwd : process.cwd();\n\t\t\t\t\t// The host rebinds its live session; the returned session is driven\n\t\t\t\t\t// through host.getSession() afterwards.\n\t\t\t\t\tawait this.#host.createSession(cwd);\n\t\t\t\t\tthis.#currentSessionId = `sess_${randomUUID()}`;\n\t\t\t\t\tthis.#seenToolCalls.clear();\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: { sessionId: this.#currentSessionId } });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session/load\": {\n\t\t\t\t\tconst sessionId = typeof record.sessionId === \"string\" ? record.sessionId : \"\";\n\t\t\t\t\tconst cwd = typeof record.cwd === \"string\" ? record.cwd : process.cwd();\n\t\t\t\t\tawait this.#host.loadSession(sessionId, cwd);\n\t\t\t\t\tthis.#currentSessionId = sessionId;\n\t\t\t\t\tthis.#seenToolCalls.clear();\n\t\t\t\t\t// History replay is deferred (plan, ACP.2): the session reattaches\n\t\t\t\t\t// and continues, but prior entries are not streamed in v1.\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: null });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session/prompt\": {\n\t\t\t\t\tif (this.#currentSessionId === undefined || record.sessionId !== this.#currentSessionId) {\n\t\t\t\t\t\tthis.#write({\n\t\t\t\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\terror: { code: -32002, message: \"Unknown session; call session/new first\" },\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst prompt = Array.isArray(record.prompt) ? record.prompt : [];\n\t\t\t\t\tconst text = prompt\n\t\t\t\t\t\t.filter((block): block is { type: \"text\"; text: string } => {\n\t\t\t\t\t\t\tconst candidate = block as { type?: string; text?: string };\n\t\t\t\t\t\t\treturn candidate.type === \"text\" && typeof candidate.text === \"string\";\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.map((block) => block.text)\n\t\t\t\t\t\t.join(\"\\n\");\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.#host.getSession().prompt(text, { source: \"acp\" });\n\t\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: { stopReason: \"end_turn\" } });\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst messageText = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\tif (/abort/i.test(messageText)) {\n\t\t\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: { stopReason: \"cancelled\" } });\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session/set_mode\": {\n\t\t\t\t\tconst mode = typeof record.mode === \"string\" ? record.mode : \"\";\n\t\t\t\t\tawait this.#host.setMode?.(mode);\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: {} });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, error: { code: -32601, message: `Method not found: ${method}` } });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.#write({\n\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\tid,\n\t\t\t\terror: { code: -32000, message: error instanceof Error ? error.message : String(error) },\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * Run in ACP mode: ACP v1 agent over stdio for the runtime's session. ACP owns\n * stdout from here on (output guard takes it over).\n */\nexport async function runAcpMode(runtime: AgentSessionRuntime, bridge?: { current?: AcpServer }): Promise<never> {\n\ttakeOverStdout();\n\tconst host: AcpHost = {\n\t\tgetSession: () => runtime.session,\n\t\tcreateSession: async () => {\n\t\t\tawait runtime.newSession();\n\t\t\treturn runtime.session;\n\t\t},\n\t\tloadSession: async () => runtime.session,\n\t\tsetMode: async (mode) => {\n\t\t\tawait runtime.session.setPermissionMode(mode as Parameters<typeof runtime.session.setPermissionMode>[0]);\n\t\t},\n\t};\n\tconst server = new AcpServer({ input: process.stdin, output: process.stdout, host });\n\tif (bridge) bridge.current = server;\n\truntime.session.subscribe((event) => server.onSessionEvent(event));\n\tserver.start();\n\t// The ACP connection owns the process lifetime; it ends when stdin closes.\n\treturn new Promise(() => {});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/modes/acp/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Readable, Writable } from \"node:stream\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\nimport type {\n\tChildRunPolicySnapshot,\n\tChildRunStatus,\n\tChildTurnOutcome,\n\tChildWorkspaceRequest,\n\tChildWorkspaceState,\n} from \"../../core/delegation/runtime.ts\";\nimport type { InputSource } from \"../../core/extensions/types.ts\";\nimport { takeOverStdout } from \"../../core/output-guard.ts\";\nimport type { PermissionAnswer } from \"../../core/permissions/responder.ts\";\nimport { attachJsonlLineReader, serializeJsonLine } from \"../rpc/jsonl.ts\";\nimport { translateJsonEvent } from \"./translate.ts\";\n\n/**\n * The ACP server (spec 2026-08-31-acp-adapter.md): an in-process ACP v1 agent\n * over newline-delimited JSON-RPC on stdio. The host adapters between the\n * protocol and the live AgentSession; the server never imports session types,\n * so tests drive it entirely through fakes.\n */\n\n/** The slice of AgentSession the protocol actually drives, including the child lifecycle pass-throughs RPC calls. */\nexport interface AcpPromptableSession {\n\tprompt(text: string, options: { source: InputSource }): Promise<unknown>;\n\tabort(): Promise<void> | void;\n\tlistChildRuns(): unknown[];\n\tstartChildRun(\n\t\tagentType: string,\n\t\ttask: string,\n\t\trequest?: { workspace?: ChildWorkspaceRequest; idempotencyKey?: string; timeoutMs?: number },\n\t): Promise<{ handleId: string; created: boolean }>;\n\twaitChildRun(runId: string): Promise<unknown>;\n\t/**\n\t * The awaited turn's payload with the registry status after settlement, plus\n\t * the run's additive linkage fields (`artifactDir`, `sessionFile`,\n\t * `parentSessionId`, `policy`, `sandboxEnforced`) when the registry snapshot\n\t * knows them -- identical to RPC's `agent/wait` payload.\n\t */\n\twaitChildRunResult(runId: string): Promise<{\n\t\tstatus: string;\n\t\toutput: string;\n\t\toutcome: ChildTurnOutcome;\n\t\tartifactDir?: string;\n\t\tsessionFile?: string;\n\t\tparentSessionId?: string;\n\t\tpolicy?: ChildRunPolicySnapshot;\n\t\tsandboxEnforced?: boolean;\n\t}>;\n\t/** Non-blocking status snapshot for one child run (`agent/status`). */\n\tchildRunStatus(runId: string): ChildRunStatus;\n\t/**\n\t * Explicit recovery of a retained child worktree (`agent/recover`), the\n\t * pass-through `AgentSession.recoverChildWorkspace` calls. Identical payload\n\t * to RPC's `agent/recover`: `{workspaceState: \"active\", dirty}`.\n\t */\n\trecoverChildWorkspace(runId: string): Promise<{ workspaceState: ChildWorkspaceState; dirty: boolean }>;\n\tsendChildInput(runId: string, input: string): Promise<void>;\n\tresumeChildRun(runId: string, input?: string): Promise<string>;\n\tinterruptChildRun(runId: string, reason?: string): void;\n\tcloseChildRun(runId: string): void;\n}\n\nexport interface AcpHost {\n\tgetSession(): AcpPromptableSession;\n\tcreateSession(cwd: string): Promise<AcpPromptableSession>;\n\tloadSession(sessionId: string, cwd: string): Promise<AcpPromptableSession>;\n\tsetMode?(mode: string): Promise<void> | void;\n}\n\nconst PERMISSION_OPTIONS = [\n\t{ optionId: \"allow-once\", name: \"Allow once\", kind: \"allow_once\" },\n\t// `optionId` and `kind` are the wire contract; only the display name is ours to fix.\n\t// The gate writes this to `session`, so \"always\" was never true.\n\t{ optionId: \"allow-always\", name: \"Allow for this session\", kind: \"allow_always\" },\n\t{ optionId: \"reject-once\", name: \"Reject\", kind: \"reject_once\" },\n\t{ optionId: \"reject-always\", name: \"Reject always\", kind: \"reject_always\" },\n] as const;\n\nexport class AcpServer {\n\treadonly #input: Readable;\n\treadonly #output: Writable;\n\treadonly #host: AcpHost;\n\treadonly #pendingPermissions = new Map<string, (answer: PermissionAnswer) => void>();\n\treadonly #seenToolCalls = new Set<string>();\n\t#currentSessionId: string | undefined;\n\n\tconstructor(deps: { input: Readable; output: Writable; host: AcpHost }) {\n\t\tthis.#input = deps.input;\n\t\tthis.#output = deps.output;\n\t\tthis.#host = deps.host;\n\t}\n\n\tstart(): void {\n\t\tattachJsonlLineReader(this.#input, (line) => this.handleLine(line));\n\t}\n\n\tget currentSessionId(): string | undefined {\n\t\treturn this.#currentSessionId;\n\t}\n\n\thandleLine(line: string): void {\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(line);\n\t\t} catch {\n\t\t\t// ACP is line-delimited JSON; anything else on the wire is dropped, not fatal.\n\t\t\treturn;\n\t\t}\n\t\tif (typeof parsed !== \"object\" || parsed === null) return;\n\t\tconst message = parsed as Record<string, unknown>;\n\t\tconst { id, method, params } = message;\n\n\t\tif (typeof method !== \"string\") {\n\t\t\t// No method: a response to one of our server->client requests (permission).\n\t\t\tif (id !== undefined) this.#resolvePermission(String(id), message.result);\n\t\t\treturn;\n\t\t}\n\t\tif (id === undefined) {\n\t\t\tif (method === \"session/cancel\") {\n\t\t\t\tvoid this.#host.getSession().abort();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tvoid this.#dispatch(id, method, params);\n\t}\n\n\t/** Entry point for live session events; translates and emits session/update. */\n\tonSessionEvent(event: unknown): void {\n\t\tif (this.#currentSessionId === undefined) return;\n\t\tconst translated = translateJsonEvent(event);\n\t\tif (!translated) return;\n\t\tif (translated.kind === \"message\") {\n\t\t\tthis.#notify(this.#currentSessionId, translated.update);\n\t\t\treturn;\n\t\t}\n\t\tconst seen = this.#seenToolCalls.has(translated.toolCallId);\n\t\tthis.#seenToolCalls.add(translated.toolCallId);\n\t\tthis.#notify(this.#currentSessionId, {\n\t\t\tsessionUpdate: seen ? \"tool_call_update\" : \"tool_call\",\n\t\t\t...translated.update,\n\t\t});\n\t}\n\n\t/**\n\t * The PermissionResponder bridge: turns one ACP permission round trip into\n\t * the gate's answer shape. Rule persistence itself stays in the gate\n\t * (ADR 0010) -- `persist` is a request, not a written rule.\n\t */\n\t/**\n\t * `canPersistSession` mirrors the gate's `sessionScope`. When the tool's\n\t * `ruleForCall()` yields nothing, the gate has no rule to write, so neither\n\t * standing choice can keep its promise and neither is offered.\n\t */\n\taskPermission(\n\t\tsessionId: string,\n\t\ttoolName: string,\n\t\tdescription: string,\n\t\tcanPersistSession = true,\n\t): Promise<PermissionAnswer> {\n\t\tconst id = `perm_${randomUUID()}`;\n\t\treturn new Promise<PermissionAnswer>((resolve) => {\n\t\t\tthis.#pendingPermissions.set(id, resolve);\n\t\t\tthis.#write({\n\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\tid,\n\t\t\t\tmethod: \"session/request_permission\",\n\t\t\t\tparams: {\n\t\t\t\t\tsessionId,\n\t\t\t\t\ttoolCall: { toolCallId: id, title: `${toolName}: ${description}`, kind: \"other\", status: \"pending\" },\n\t\t\t\t\t// Both standing choices need a rule to stand on. Offering either when\n\t\t\t\t\t// ruleForCall() yields nothing promises a decision that outlives the\n\t\t\t\t\t// call and then does not.\n\t\t\t\t\toptions: canPersistSession\n\t\t\t\t\t\t? PERMISSION_OPTIONS\n\t\t\t\t\t\t: PERMISSION_OPTIONS.filter(\n\t\t\t\t\t\t\t\t(option) => option.kind !== \"allow_always\" && option.kind !== \"reject_always\",\n\t\t\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t}\n\n\t#write(value: unknown): void {\n\t\tthis.#output.write(serializeJsonLine(value));\n\t}\n\n\t#notify(sessionId: string, update: Record<string, unknown>): void {\n\t\tthis.#write({ jsonrpc: \"2.0\", method: \"session/update\", params: { sessionId, update } });\n\t}\n\n\t#resolvePermission(id: string, result: unknown): void {\n\t\tconst resolve = this.#pendingPermissions.get(id);\n\t\tif (!resolve) return;\n\t\tthis.#pendingPermissions.delete(id);\n\t\tconst outcome = (result as { outcome?: { outcome?: string; optionId?: string } } | undefined)?.outcome;\n\t\tif (outcome?.outcome !== \"selected\" || typeof outcome.optionId !== \"string\") {\n\t\t\t// Cancelled, malformed, or unknown -- fail closed.\n\t\t\tresolve({ allow: false });\n\t\t\treturn;\n\t\t}\n\t\tif (outcome.optionId.startsWith(\"allow\")) {\n\t\t\tresolve({ allow: true, persist: outcome.optionId === \"allow-always\" });\n\t\t\treturn;\n\t\t}\n\t\tresolve({ allow: false, persist: outcome.optionId === \"reject-always\" });\n\t}\n\n\tasync #dispatch(id: unknown, method: string, params: unknown): Promise<void> {\n\t\tconst record = (typeof params === \"object\" && params !== null ? params : {}) as Record<string, unknown>;\n\t\ttry {\n\t\t\tswitch (method) {\n\t\t\t\tcase \"initialize\": {\n\t\t\t\t\tthis.#write({\n\t\t\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tresult: {\n\t\t\t\t\t\t\tprotocolVersion: 1,\n\t\t\t\t\t\t\tagentCapabilities: { loadSession: true },\n\t\t\t\t\t\t\tpromptCapabilities: { image: false, audio: false, embeddedContext: false },\n\t\t\t\t\t\t\tagentInfo: { name: \"apex-code\", title: \"Apex Code\" },\n\t\t\t\t\t\t\tauthMethods: [],\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session/new\": {\n\t\t\t\t\tconst cwd = typeof record.cwd === \"string\" ? record.cwd : process.cwd();\n\t\t\t\t\t// The host rebinds its live session; the returned session is driven\n\t\t\t\t\t// through host.getSession() afterwards.\n\t\t\t\t\tawait this.#host.createSession(cwd);\n\t\t\t\t\tthis.#currentSessionId = `sess_${randomUUID()}`;\n\t\t\t\t\tthis.#seenToolCalls.clear();\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: { sessionId: this.#currentSessionId } });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session/load\": {\n\t\t\t\t\tconst sessionId = typeof record.sessionId === \"string\" ? record.sessionId : \"\";\n\t\t\t\t\tconst cwd = typeof record.cwd === \"string\" ? record.cwd : process.cwd();\n\t\t\t\t\tawait this.#host.loadSession(sessionId, cwd);\n\t\t\t\t\tthis.#currentSessionId = sessionId;\n\t\t\t\t\tthis.#seenToolCalls.clear();\n\t\t\t\t\t// History replay is deferred (plan, ACP.2): the session reattaches\n\t\t\t\t\t// and continues, but prior entries are not streamed in v1.\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: null });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session/prompt\": {\n\t\t\t\t\tif (this.#currentSessionId === undefined || record.sessionId !== this.#currentSessionId) {\n\t\t\t\t\t\tthis.#write({\n\t\t\t\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\terror: { code: -32002, message: \"Unknown session; call session/new first\" },\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst prompt = Array.isArray(record.prompt) ? record.prompt : [];\n\t\t\t\t\tconst text = prompt\n\t\t\t\t\t\t.filter((block): block is { type: \"text\"; text: string } => {\n\t\t\t\t\t\t\tconst candidate = block as { type?: string; text?: string };\n\t\t\t\t\t\t\treturn candidate.type === \"text\" && typeof candidate.text === \"string\";\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.map((block) => block.text)\n\t\t\t\t\t\t.join(\"\\n\");\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.#host.getSession().prompt(text, { source: \"acp\" });\n\t\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: { stopReason: \"end_turn\" } });\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst messageText = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\tif (/abort/i.test(messageText)) {\n\t\t\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: { stopReason: \"cancelled\" } });\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session/set_mode\": {\n\t\t\t\t\tconst mode = typeof record.mode === \"string\" ? record.mode : \"\";\n\t\t\t\t\tawait this.#host.setMode?.(mode);\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: {} });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Child lifecycle parity (spec 2026-09-09, protocol section): every\n\t\t\t\t// operation routes through the same AgentSession pass-throughs RPC\n\t\t\t\t// calls, with the same payload shapes and the same unknown-ID\n\t\t\t\t// rejection surfacing as -32000 errors. There is no second,\n\t\t\t\t// host-supplied lifecycle path.\n\t\t\t\tcase \"agent/list\": {\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: this.#host.getSession().listChildRuns() });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"agent/send\": {\n\t\t\t\t\tif (typeof record.runId !== \"string\" || typeof record.input !== \"string\")\n\t\t\t\t\t\tthrow new Error(\"agent/send requires runId and input.\");\n\t\t\t\t\tawait this.#host.getSession().sendChildInput(record.runId, record.input);\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: null });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"agent/spawn\": {\n\t\t\t\t\tif (typeof record.agentType !== \"string\" || typeof record.task !== \"string\")\n\t\t\t\t\t\tthrow new Error(\"agent/spawn requires agentType and task.\");\n\t\t\t\t\tconst workspace = record.workspace as ChildWorkspaceRequest | undefined;\n\t\t\t\t\tconst idempotencyKey = typeof record.idempotencyKey === \"string\" ? record.idempotencyKey : undefined;\n\t\t\t\t\tconst timeoutMs = typeof record.timeoutMs === \"number\" ? record.timeoutMs : undefined;\n\t\t\t\t\tconst spawned = await this.#host.getSession().startChildRun(record.agentType, record.task, {\n\t\t\t\t\t\t...(workspace ? { workspace } : {}),\n\t\t\t\t\t\t...(idempotencyKey !== undefined ? { idempotencyKey } : {}),\n\t\t\t\t\t\t...(timeoutMs !== undefined ? { timeoutMs } : {}),\n\t\t\t\t\t});\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: spawned });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"agent/wait\": {\n\t\t\t\t\tif (typeof record.runId !== \"string\") throw new Error(\"agent/wait requires runId.\");\n\t\t\t\t\t// Identical to RPC's agent/wait: the awaited latest settled turn\n\t\t\t\t\t// with the registry status after settlement.\n\t\t\t\t\tconst waited = await this.#host.getSession().waitChildRunResult(record.runId);\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: waited });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"agent/status\": {\n\t\t\t\t\tif (typeof record.runId !== \"string\") throw new Error(\"agent/status requires runId.\");\n\t\t\t\t\t// Non-blocking: the snapshot is built from the record/entry alone.\n\t\t\t\t\tconst status = this.#host.getSession().childRunStatus(record.runId);\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: status });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"agent/resume\": {\n\t\t\t\t\tif (typeof record.runId !== \"string\") throw new Error(\"agent/resume requires runId.\");\n\t\t\t\t\tconst input = typeof record.input === \"string\" ? record.input : undefined;\n\t\t\t\t\tconst status = await this.#host.getSession().resumeChildRun(record.runId, input);\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: { status } });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"agent/recover\": {\n\t\t\t\t\tif (typeof record.runId !== \"string\") throw new Error(\"agent/recover requires runId.\");\n\t\t\t\t\t// Identical to RPC's agent/recover: the verified reactivation\n\t\t\t\t\t// payload `{workspaceState, dirty}`; refusals surface as -32000.\n\t\t\t\t\tconst recovered = await this.#host.getSession().recoverChildWorkspace(record.runId);\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: recovered });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"agent/interrupt\":\n\t\t\t\tcase \"agent/close\": {\n\t\t\t\t\tif (typeof record.runId !== \"string\") throw new Error(`${method} requires runId.`);\n\t\t\t\t\tconst session = this.#host.getSession();\n\t\t\t\t\tif (method === \"agent/interrupt\") {\n\t\t\t\t\t\tconst reason = typeof record.reason === \"string\" ? record.reason : undefined;\n\t\t\t\t\t\tsession.interruptChildRun(record.runId, reason);\n\t\t\t\t\t} else session.closeChildRun(record.runId);\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, result: null });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tthis.#write({ jsonrpc: \"2.0\", id, error: { code: -32601, message: `Method not found: ${method}` } });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.#write({\n\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\tid,\n\t\t\t\terror: { code: -32000, message: error instanceof Error ? error.message : String(error) },\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * Run in ACP mode: ACP v1 agent over stdio for the runtime's session. ACP owns\n * stdout from here on (output guard takes it over).\n */\nexport async function runAcpMode(runtime: AgentSessionRuntime, bridge?: { current?: AcpServer }): Promise<never> {\n\ttakeOverStdout();\n\tconst host: AcpHost = {\n\t\tgetSession: () => runtime.session,\n\t\tcreateSession: async () => {\n\t\t\tawait runtime.newSession();\n\t\t\treturn runtime.session;\n\t\t},\n\t\tloadSession: async () => runtime.session,\n\t\tsetMode: async (mode) => {\n\t\t\tawait runtime.session.setPermissionMode(mode as Parameters<typeof runtime.session.setPermissionMode>[0]);\n\t\t},\n\t};\n\tconst server = new AcpServer({ input: process.stdin, output: process.stdout, host });\n\tif (bridge) bridge.current = server;\n\truntime.session.subscribe((event) => server.onSessionEvent(event));\n\tserver.start();\n\t// The ACP connection owns the process lifetime; it ends when stdin closes.\n\treturn new Promise(() => {});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AA4B/E,YAAY,EACX,UAAU,EACV,qBAAqB,EACrB,sBAAsB,EACtB,WAAW,EACX,eAAe,GACf,MAAM,gBAAgB,CAAC;AAExB;;;GAGG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC,KAAK,CAAC,CA+vBjF","sourcesContent":["/**\n * RPC mode: Headless operation with JSON stdin/stdout protocol.\n *\n * Used for embedding the agent in other applications.\n * Receives commands as JSON on stdin, outputs events and responses as JSON on stdout.\n *\n * Protocol:\n * - Commands: JSON objects with `type` field, optional `id` for correlation\n * - Responses: JSON objects with `type: \"response\"`, `command`, `success`, and optional `data`/`error`\n * - Events: AgentSessionEvent objects streamed as they occur\n * - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response\n */\n\nimport * as crypto from \"node:crypto\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\nimport type {\n\tExtensionUIContext,\n\tExtensionUIDialogOptions,\n\tExtensionWidgetOptions,\n\tWorkingIndicatorOptions,\n} from \"../../core/extensions/index.ts\";\nimport {\n\tflushRawStdout,\n\ttakeOverStdout,\n\twaitForRawStdoutBackpressure,\n\twriteRawStdout,\n} from \"../../core/output-guard.ts\";\nimport { slugifySkillCommandName } from \"../../core/skills.ts\";\nimport { killTrackedDetachedChildren } from \"../../utils/shell.ts\";\nimport { type Theme, theme } from \"../interactive/theme/theme.ts\";\nimport { toJsonEvent } from \"../json-event.ts\";\nimport { attachJsonlLineReader, serializeJsonLine } from \"./jsonl.ts\";\nimport type {\n\tRpcCommand,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcResponse,\n\tRpcSessionState,\n\tRpcSlashCommand,\n} from \"./rpc-types.ts\";\n\n// Re-export types for consumers\nexport type {\n\tRpcCommand,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcResponse,\n\tRpcSessionState,\n} from \"./rpc-types.ts\";\n\n/**\n * Run in RPC mode.\n * Listens for JSON commands on stdin, outputs events and responses on stdout.\n */\nexport async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<never> {\n\ttakeOverStdout();\n\tlet session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet unsubscribeBackpressure: (() => void) | undefined;\n\n\tconst output = (obj: RpcResponse | RpcExtensionUIRequest | object) => {\n\t\twriteRawStdout(serializeJsonLine(obj));\n\t};\n\n\tconst success = <T extends RpcCommand[\"type\"]>(\n\t\tid: string | undefined,\n\t\tcommand: T,\n\t\tdata?: object | null,\n\t): RpcResponse => {\n\t\tif (data === undefined) {\n\t\t\treturn { id, type: \"response\", command, success: true } as RpcResponse;\n\t\t}\n\t\treturn { id, type: \"response\", command, success: true, data } as RpcResponse;\n\t};\n\n\tconst error = (id: string | undefined, command: string, message: string): RpcResponse => {\n\t\treturn { id, type: \"response\", command, success: false, error: message };\n\t};\n\n\t// Pending extension UI requests waiting for response\n\tconst pendingExtensionRequests = new Map<\n\t\tstring,\n\t\t{ resolve: (value: any) => void; reject: (error: Error) => void }\n\t>();\n\n\t// Shutdown request flag\n\tlet shutdownRequested = false;\n\tlet shuttingDown = false;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\n\t/** Helper for dialog methods with signal/timeout support */\n\tfunction createDialogPromise<T>(\n\t\topts: ExtensionUIDialogOptions | undefined,\n\t\tdefaultValue: T,\n\t\trequest: Record<string, unknown>,\n\t\tparseResponse: (response: RpcExtensionUIResponse) => T,\n\t): Promise<T> {\n\t\tif (opts?.signal?.aborted) return Promise.resolve(defaultValue);\n\n\t\tconst id = crypto.randomUUID();\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlet timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n\t\t\tconst cleanup = () => {\n\t\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\t\topts?.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tpendingExtensionRequests.delete(id);\n\t\t\t};\n\n\t\t\tconst onAbort = () => {\n\t\t\t\tcleanup();\n\t\t\t\tresolve(defaultValue);\n\t\t\t};\n\t\t\topts?.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\tif (opts?.timeout) {\n\t\t\t\ttimeoutId = setTimeout(() => {\n\t\t\t\t\tcleanup();\n\t\t\t\t\tresolve(defaultValue);\n\t\t\t\t}, opts.timeout);\n\t\t\t}\n\n\t\t\tpendingExtensionRequests.set(id, {\n\t\t\t\tresolve: (response: RpcExtensionUIResponse) => {\n\t\t\t\t\tcleanup();\n\t\t\t\t\tresolve(parseResponse(response));\n\t\t\t\t},\n\t\t\t\treject,\n\t\t\t});\n\t\t\toutput({ type: \"extension_ui_request\", id, ...request } as RpcExtensionUIRequest);\n\t\t});\n\t}\n\n\t/**\n\t * Create an extension UI context that uses the RPC protocol.\n\t */\n\tconst createExtensionUIContext = (): ExtensionUIContext => ({\n\t\tselect: (title, options, opts) =>\n\t\t\tcreateDialogPromise(opts, undefined, { method: \"select\", title, options, timeout: opts?.timeout }, (r) =>\n\t\t\t\t\"cancelled\" in r && r.cancelled ? undefined : \"value\" in r ? r.value : undefined,\n\t\t\t),\n\n\t\tconfirm: (title, message, opts) =>\n\t\t\tcreateDialogPromise(opts, false, { method: \"confirm\", title, message, timeout: opts?.timeout }, (r) =>\n\t\t\t\t\"cancelled\" in r && r.cancelled ? false : \"confirmed\" in r ? r.confirmed : false,\n\t\t\t),\n\n\t\tinput: (title, placeholder, opts) =>\n\t\t\tcreateDialogPromise(opts, undefined, { method: \"input\", title, placeholder, timeout: opts?.timeout }, (r) =>\n\t\t\t\t\"cancelled\" in r && r.cancelled ? undefined : \"value\" in r ? r.value : undefined,\n\t\t\t),\n\n\t\tnotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void {\n\t\t\t// Fire and forget - no response needed\n\t\t\toutput({\n\t\t\t\ttype: \"extension_ui_request\",\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tmethod: \"notify\",\n\t\t\t\tmessage,\n\t\t\t\tnotifyType: type,\n\t\t\t} as RpcExtensionUIRequest);\n\t\t},\n\n\t\tonTerminalInput(): () => void {\n\t\t\t// Raw terminal input not supported in RPC mode\n\t\t\treturn () => {};\n\t\t},\n\n\t\tsetStatus(key: string, text: string | undefined): void {\n\t\t\t// Fire and forget - no response needed\n\t\t\toutput({\n\t\t\t\ttype: \"extension_ui_request\",\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tmethod: \"setStatus\",\n\t\t\t\tstatusKey: key,\n\t\t\t\tstatusText: text,\n\t\t\t} as RpcExtensionUIRequest);\n\t\t},\n\n\t\tsetWorkingMessage(_message?: string): void {\n\t\t\t// Working message not supported in RPC mode - requires TUI loader access\n\t\t},\n\n\t\tsetWorkingVisible(_visible: boolean): void {\n\t\t\t// Working visibility not supported in RPC mode - requires TUI loader access\n\t\t},\n\n\t\tsetWorkingIndicator(_options?: WorkingIndicatorOptions): void {\n\t\t\t// Working indicator customization not supported in RPC mode - requires TUI loader access\n\t\t},\n\n\t\tsetHiddenThinkingLabel(_label?: string): void {\n\t\t\t// Hidden thinking label not supported in RPC mode - requires TUI message rendering access\n\t\t},\n\n\t\tsetWidget(key: string, content: unknown, options?: ExtensionWidgetOptions): void {\n\t\t\t// Only support string arrays in RPC mode - factory functions are ignored\n\t\t\tif (content === undefined || Array.isArray(content)) {\n\t\t\t\toutput({\n\t\t\t\t\ttype: \"extension_ui_request\",\n\t\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\t\tmethod: \"setWidget\",\n\t\t\t\t\twidgetKey: key,\n\t\t\t\t\twidgetLines: content as string[] | undefined,\n\t\t\t\t\twidgetPlacement: options?.placement,\n\t\t\t\t} as RpcExtensionUIRequest);\n\t\t\t}\n\t\t\t// Component factories are not supported in RPC mode - would need TUI access\n\t\t},\n\n\t\tsetFooter(_factory: unknown): void {\n\t\t\t// Custom footer not supported in RPC mode - requires TUI access\n\t\t},\n\n\t\tsetHeader(_factory: unknown): void {\n\t\t\t// Custom header not supported in RPC mode - requires TUI access\n\t\t},\n\n\t\tsetTitle(title: string): void {\n\t\t\t// Fire and forget - host can implement terminal title control\n\t\t\toutput({\n\t\t\t\ttype: \"extension_ui_request\",\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tmethod: \"setTitle\",\n\t\t\t\ttitle,\n\t\t\t} as RpcExtensionUIRequest);\n\t\t},\n\n\t\tasync custom() {\n\t\t\t// Custom UI not supported in RPC mode\n\t\t\treturn undefined as never;\n\t\t},\n\n\t\tpasteToEditor(text: string): void {\n\t\t\t// Paste handling not supported in RPC mode - falls back to setEditorText\n\t\t\tthis.setEditorText(text);\n\t\t},\n\n\t\tsetEditorText(text: string): void {\n\t\t\t// Fire and forget - host can implement editor control\n\t\t\toutput({\n\t\t\t\ttype: \"extension_ui_request\",\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tmethod: \"set_editor_text\",\n\t\t\t\ttext,\n\t\t\t} as RpcExtensionUIRequest);\n\t\t},\n\n\t\tgetEditorText(): string {\n\t\t\t// Synchronous method can't wait for RPC response\n\t\t\t// Host should track editor state locally if needed\n\t\t\treturn \"\";\n\t\t},\n\n\t\tasync editor(title: string, prefill?: string): Promise<string | undefined> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingExtensionRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcExtensionUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(undefined);\n\t\t\t\t\t\t} else if (\"value\" in response) {\n\t\t\t\t\t\t\tresolve(response.value);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve(undefined);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\treject,\n\t\t\t\t});\n\t\t\t\toutput({ type: \"extension_ui_request\", id, method: \"editor\", title, prefill } as RpcExtensionUIRequest);\n\t\t\t});\n\t\t},\n\n\t\taddAutocompleteProvider(): void {\n\t\t\t// Autocomplete provider composition is not supported in RPC mode\n\t\t},\n\n\t\tsetEditorComponent(): void {\n\t\t\t// Custom editor components not supported in RPC mode\n\t\t},\n\n\t\tgetEditorComponent() {\n\t\t\t// Custom editor components not supported in RPC mode\n\t\t\treturn undefined;\n\t\t},\n\n\t\tget theme() {\n\t\t\treturn theme;\n\t\t},\n\n\t\tgetAllThemes() {\n\t\t\treturn [];\n\t\t},\n\n\t\tgetTheme(_name: string) {\n\t\t\treturn undefined;\n\t\t},\n\n\t\tsetTheme(_theme: string | Theme) {\n\t\t\t// Theme switching not supported in RPC mode\n\t\t\treturn { success: false, error: \"Theme switching not supported in RPC mode\" };\n\t\t},\n\n\t\tgetToolsExpanded() {\n\t\t\t// Tool expansion not supported in RPC mode - no TUI\n\t\t\treturn false;\n\t\t},\n\n\t\tsetToolsExpanded(_expanded: boolean) {\n\t\t\t// Tool expansion not supported in RPC mode - no TUI\n\t\t},\n\t});\n\n\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\n\t});\n\n\tconst rebindSession = async (): Promise<void> => {\n\t\tsession = runtimeHost.session;\n\t\tawait session.bindExtensions({\n\t\t\tuiContext: createExtensionUIContext(),\n\t\t\tmode: \"rpc\",\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.waitForIdle(),\n\t\t\t\tnewSession: async (options) => runtimeHost.newSession(options),\n\t\t\t\tfork: async (entryId, forkOptions) => {\n\t\t\t\t\tconst result = await runtimeHost.fork(entryId, forkOptions);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, options) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: options?.summarize,\n\t\t\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\t\t\tlabel: options?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, options) => {\n\t\t\t\t\treturn runtimeHost.switchSession(sessionPath, options);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tshutdownHandler: () => {\n\t\t\t\tshutdownRequested = true;\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\toutput({ type: \"extension_error\", extensionPath: err.extensionPath, event: err.event, error: err.error });\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\toutput(toJsonEvent(event));\n\t\t\tif (event.type === \"agent_settled\") {\n\t\t\t\tvoid checkShutdownRequested();\n\t\t\t}\n\t\t});\n\t\tunsubscribeBackpressure = session.agent.subscribe(async () => {\n\t\t\tawait waitForRawStdoutBackpressure();\n\t\t});\n\t};\n\n\tconst registerSignalHandlers = (): void => {\n\t\tconst signals: NodeJS.Signals[] = [\"SIGTERM\"];\n\t\tif (process.platform !== \"win32\") {\n\t\t\tsignals.push(\"SIGHUP\");\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst handler = () => {\n\t\t\t\tkillTrackedDetachedChildren();\n\t\t\t\tvoid shutdown(signal === \"SIGHUP\" ? 129 : 143, signal);\n\t\t\t};\n\t\t\tprocess.on(signal, handler);\n\t\t\tsignalCleanupHandlers.push(() => process.off(signal, handler));\n\t\t}\n\t};\n\n\tawait rebindSession();\n\tregisterSignalHandlers();\n\n\t// Handle a single command\n\tconst handleCommand = async (command: RpcCommand): Promise<RpcResponse | undefined> => {\n\t\tconst id = command.id;\n\n\t\tswitch (command.type) {\n\t\t\t// =================================================================\n\t\t\t// Prompting\n\t\t\t// =================================================================\n\n\t\t\tcase \"prompt\": {\n\t\t\t\t// Start prompt handling immediately, but emit the authoritative response only after\n\t\t\t\t// prompt preflight succeeds. Queued and immediately handled prompts also count as success.\n\t\t\t\tlet preflightSucceeded = false;\n\t\t\t\tvoid session\n\t\t\t\t\t.prompt(command.message, {\n\t\t\t\t\t\timages: command.images,\n\t\t\t\t\t\tstreamingBehavior: command.streamingBehavior,\n\t\t\t\t\t\tsource: \"rpc\",\n\t\t\t\t\t\tpreflightResult: (didSucceed) => {\n\t\t\t\t\t\t\tif (didSucceed) {\n\t\t\t\t\t\t\t\tpreflightSucceeded = true;\n\t\t\t\t\t\t\t\toutput(success(id, \"prompt\"));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\t.catch((e) => {\n\t\t\t\t\t\tif (!preflightSucceeded) {\n\t\t\t\t\t\t\toutput(error(id, \"prompt\", e.message));\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tcase \"steer\": {\n\t\t\t\tawait session.steer(command.message, command.images);\n\t\t\t\treturn success(id, \"steer\");\n\t\t\t}\n\n\t\t\tcase \"follow_up\": {\n\t\t\t\tawait session.followUp(command.message, command.images);\n\t\t\t\treturn success(id, \"follow_up\");\n\t\t\t}\n\n\t\t\tcase \"abort\": {\n\t\t\t\tawait session.abort();\n\t\t\t\treturn success(id, \"abort\");\n\t\t\t}\n\n\t\t\tcase \"clear_queue\": {\n\t\t\t\treturn success(id, \"clear_queue\", session.clearQueue());\n\t\t\t}\n\n\t\t\tcase \"new_session\": {\n\t\t\t\tconst options = command.parentSession ? { parentSession: command.parentSession } : undefined;\n\t\t\t\tconst result = await runtimeHost.newSession(options);\n\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\tawait rebindSession();\n\t\t\t\t}\n\t\t\t\treturn success(id, \"new_session\", result);\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// State\n\t\t\t// =================================================================\n\n\t\t\tcase \"get_state\": {\n\t\t\t\tconst state: RpcSessionState = {\n\t\t\t\t\tmodel: session.model,\n\t\t\t\t\tthinkingLevel: session.thinkingLevel,\n\t\t\t\t\tisStreaming: session.isStreaming,\n\t\t\t\t\tisCompacting: session.isCompacting,\n\t\t\t\t\tsteeringMode: session.steeringMode,\n\t\t\t\t\tfollowUpMode: session.followUpMode,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tsessionId: session.sessionId,\n\t\t\t\t\tsessionName: session.sessionName,\n\t\t\t\t\tautoCompactionEnabled: session.autoCompactionEnabled,\n\t\t\t\t\tmessageCount: session.messages.length,\n\t\t\t\t\tpendingMessageCount: session.pendingMessageCount,\n\t\t\t\t};\n\t\t\t\treturn success(id, \"get_state\", state);\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Model\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_model\": {\n\t\t\t\tconst models = session.modelRuntime.getAvailableSnapshot();\n\t\t\t\tconst model = models.find((m) => m.provider === command.provider && m.id === command.modelId);\n\t\t\t\tif (!model) {\n\t\t\t\t\treturn error(id, \"set_model\", `Model not found: ${command.provider}/${command.modelId}`);\n\t\t\t\t}\n\t\t\t\tawait session.setModel(model);\n\t\t\t\treturn success(id, \"set_model\", model);\n\t\t\t}\n\n\t\t\tcase \"cycle_model\": {\n\t\t\t\tconst result = await session.cycleModel();\n\t\t\t\tif (!result) {\n\t\t\t\t\treturn success(id, \"cycle_model\", null);\n\t\t\t\t}\n\t\t\t\treturn success(id, \"cycle_model\", result);\n\t\t\t}\n\n\t\t\tcase \"get_available_models\": {\n\t\t\t\tconst models = session.modelRuntime.getAvailableSnapshot();\n\t\t\t\treturn success(id, \"get_available_models\", { models });\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Thinking\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_thinking_level\": {\n\t\t\t\tsession.setThinkingLevel(command.level);\n\t\t\t\treturn success(id, \"set_thinking_level\");\n\t\t\t}\n\n\t\t\tcase \"cycle_thinking_level\": {\n\t\t\t\tconst level = session.cycleThinkingLevel();\n\t\t\t\tif (!level) {\n\t\t\t\t\treturn success(id, \"cycle_thinking_level\", null);\n\t\t\t\t}\n\t\t\t\treturn success(id, \"cycle_thinking_level\", { level });\n\t\t\t}\n\n\t\t\tcase \"get_available_thinking_levels\": {\n\t\t\t\tconst levels = session.getAvailableThinkingLevels();\n\t\t\t\treturn success(id, \"get_available_thinking_levels\", { levels });\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Queue Modes\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_steering_mode\": {\n\t\t\t\tsession.setSteeringMode(command.mode);\n\t\t\t\treturn success(id, \"set_steering_mode\");\n\t\t\t}\n\n\t\t\tcase \"set_follow_up_mode\": {\n\t\t\t\tsession.setFollowUpMode(command.mode);\n\t\t\t\treturn success(id, \"set_follow_up_mode\");\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Compaction\n\t\t\t// =================================================================\n\n\t\t\tcase \"compact\": {\n\t\t\t\tconst result = await session.compact(command.customInstructions);\n\t\t\t\treturn success(id, \"compact\", result);\n\t\t\t}\n\n\t\t\tcase \"set_auto_compaction\": {\n\t\t\t\tsession.setAutoCompactionEnabled(command.enabled);\n\t\t\t\treturn success(id, \"set_auto_compaction\");\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Retry\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_auto_retry\": {\n\t\t\t\tsession.setAutoRetryEnabled(command.enabled);\n\t\t\t\treturn success(id, \"set_auto_retry\");\n\t\t\t}\n\n\t\t\tcase \"abort_retry\": {\n\t\t\t\tsession.abortRetry();\n\t\t\t\treturn success(id, \"abort_retry\");\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Bash\n\t\t\t// =================================================================\n\n\t\t\tcase \"bash\": {\n\t\t\t\tconst eventResult = await session.extensionRunner.emitUserBash({\n\t\t\t\t\ttype: \"user_bash\",\n\t\t\t\t\tcommand: command.command,\n\t\t\t\t\texcludeFromContext: command.excludeFromContext ?? false,\n\t\t\t\t\tcwd: session.sessionManager.getCwd(),\n\t\t\t\t});\n\n\t\t\t\tif (eventResult?.result) {\n\t\t\t\t\tsession.recordBashResult(command.command, eventResult.result, {\n\t\t\t\t\t\texcludeFromContext: command.excludeFromContext,\n\t\t\t\t\t});\n\t\t\t\t\treturn success(id, \"bash\", eventResult.result);\n\t\t\t\t}\n\n\t\t\t\tconst result = await session.executeBash(command.command, undefined, {\n\t\t\t\t\texcludeFromContext: command.excludeFromContext,\n\t\t\t\t\tid,\n\t\t\t\t\toperations: eventResult?.operations,\n\t\t\t\t});\n\t\t\t\treturn success(id, \"bash\", result);\n\t\t\t}\n\n\t\t\tcase \"abort_bash\": {\n\t\t\t\tsession.abortBash();\n\t\t\t\treturn success(id, \"abort_bash\");\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Session\n\t\t\t// =================================================================\n\n\t\t\tcase \"get_session_stats\": {\n\t\t\t\tconst stats = session.getSessionStats();\n\t\t\t\treturn success(id, \"get_session_stats\", stats);\n\t\t\t}\n\n\t\t\tcase \"export_html\": {\n\t\t\t\tconst path = await session.exportToHtml(command.outputPath);\n\t\t\t\treturn success(id, \"export_html\", { path });\n\t\t\t}\n\n\t\t\tcase \"switch_session\": {\n\t\t\t\tconst result = await runtimeHost.switchSession(command.sessionPath);\n\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\tawait rebindSession();\n\t\t\t\t}\n\t\t\t\treturn success(id, \"switch_session\", result);\n\t\t\t}\n\n\t\t\tcase \"fork\": {\n\t\t\t\tconst result = await runtimeHost.fork(command.entryId);\n\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\tawait rebindSession();\n\t\t\t\t}\n\t\t\t\treturn success(id, \"fork\", { text: result.selectedText, cancelled: result.cancelled });\n\t\t\t}\n\n\t\t\tcase \"clone\": {\n\t\t\t\tconst leafId = session.sessionManager.getLeafId();\n\t\t\t\tif (!leafId) {\n\t\t\t\t\treturn error(id, \"clone\", \"Cannot clone session: no current entry selected\");\n\t\t\t\t}\n\t\t\t\tconst result = await runtimeHost.fork(leafId, { position: \"at\" });\n\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\tawait rebindSession();\n\t\t\t\t}\n\t\t\t\treturn success(id, \"clone\", { cancelled: result.cancelled });\n\t\t\t}\n\n\t\t\tcase \"get_fork_messages\": {\n\t\t\t\tconst messages = session.getUserMessagesForForking();\n\t\t\t\treturn success(id, \"get_fork_messages\", { messages });\n\t\t\t}\n\n\t\t\tcase \"get_entries\": {\n\t\t\t\tconst sessionManager = session.sessionManager;\n\t\t\t\tlet entries = sessionManager.getEntries();\n\t\t\t\tif (command.since !== undefined) {\n\t\t\t\t\tconst sinceIndex = entries.findIndex((e) => e.id === command.since);\n\t\t\t\t\tif (sinceIndex === -1) {\n\t\t\t\t\t\treturn error(id, \"get_entries\", `Entry not found: ${command.since}`);\n\t\t\t\t\t}\n\t\t\t\t\tentries = entries.slice(sinceIndex + 1);\n\t\t\t\t}\n\t\t\t\treturn success(id, \"get_entries\", { entries, leafId: sessionManager.getLeafId() });\n\t\t\t}\n\n\t\t\tcase \"get_tree\": {\n\t\t\t\tconst sessionManager = session.sessionManager;\n\t\t\t\treturn success(id, \"get_tree\", { tree: sessionManager.getTree(), leafId: sessionManager.getLeafId() });\n\t\t\t}\n\n\t\t\tcase \"get_last_assistant_text\": {\n\t\t\t\tconst text = session.getLastAssistantText();\n\t\t\t\treturn success(id, \"get_last_assistant_text\", { text });\n\t\t\t}\n\n\t\t\tcase \"set_session_name\": {\n\t\t\t\tconst name = command.name.trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\treturn error(id, \"set_session_name\", \"Session name cannot be empty\");\n\t\t\t\t}\n\t\t\t\tsession.setSessionName(name);\n\t\t\t\treturn success(id, \"set_session_name\");\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Messages\n\t\t\t// =================================================================\n\n\t\t\tcase \"get_messages\": {\n\t\t\t\treturn success(id, \"get_messages\", { messages: session.messages });\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Commands (available for invocation via prompt)\n\t\t\t// =================================================================\n\n\t\t\tcase \"get_commands\": {\n\t\t\t\tconst commands: RpcSlashCommand[] = [];\n\n\t\t\t\tfor (const command of session.extensionRunner.getRegisteredCommands()) {\n\t\t\t\t\tcommands.push({\n\t\t\t\t\t\tname: command.invocationName,\n\t\t\t\t\t\tdescription: command.description,\n\t\t\t\t\t\tsource: \"extension\",\n\t\t\t\t\t\tsourceInfo: command.sourceInfo,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tfor (const template of session.promptTemplates) {\n\t\t\t\t\tcommands.push({\n\t\t\t\t\t\tname: template.name,\n\t\t\t\t\t\tdescription: template.description,\n\t\t\t\t\t\tsource: \"prompt\",\n\t\t\t\t\t\tsourceInfo: template.sourceInfo,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tfor (const skill of session.resourceLoader.getSkills().skills) {\n\t\t\t\t\tcommands.push({\n\t\t\t\t\t\tname: `skill:${slugifySkillCommandName(skill.name)}`,\n\t\t\t\t\t\tdescription: skill.description,\n\t\t\t\t\t\tsource: \"skill\",\n\t\t\t\t\t\tsourceInfo: skill.sourceInfo,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn success(id, \"get_commands\", { commands });\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tconst unknownCommand = command as { type: string };\n\t\t\t\treturn error(id, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Check if shutdown was requested and perform shutdown if so.\n\t * Called after handling each command when waiting for the next command.\n\t */\n\tlet detachInput = () => {};\n\n\tasync function shutdown(exitCode = 0, signal?: NodeJS.Signals): Promise<never> {\n\t\tif (shuttingDown) {\n\t\t\tprocess.exit(exitCode);\n\t\t}\n\t\tshuttingDown = true;\n\t\tfor (const cleanup of signalCleanupHandlers) {\n\t\t\tcleanup();\n\t\t}\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\n\t\tawait runtimeHost.dispose();\n\t\tdetachInput();\n\t\tprocess.stdin.pause();\n\t\tif (signal !== \"SIGTERM\") {\n\t\t\tawait flushRawStdout();\n\t\t}\n\t\tprocess.exit(exitCode);\n\t}\n\n\tasync function checkShutdownRequested(): Promise<void> {\n\t\tif (!shutdownRequested) return;\n\t\tawait shutdown();\n\t}\n\n\tconst handleInputLine = async (line: string) => {\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(line);\n\t\t} catch (parseError: unknown) {\n\t\t\toutput(\n\t\t\t\terror(\n\t\t\t\t\tundefined,\n\t\t\t\t\t\"parse\",\n\t\t\t\t\t`Failed to parse command: ${parseError instanceof Error ? parseError.message : String(parseError)}`,\n\t\t\t\t),\n\t\t\t);\n\t\t\tawait waitForRawStdoutBackpressure();\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle extension UI responses\n\t\tif (\n\t\t\ttypeof parsed === \"object\" &&\n\t\t\tparsed !== null &&\n\t\t\t\"type\" in parsed &&\n\t\t\tparsed.type === \"extension_ui_response\"\n\t\t) {\n\t\t\tconst response = parsed as RpcExtensionUIResponse;\n\t\t\tconst pending = pendingExtensionRequests.get(response.id);\n\t\t\tif (pending) {\n\t\t\t\tpendingExtensionRequests.delete(response.id);\n\t\t\t\tpending.resolve(response);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst command = parsed as RpcCommand;\n\t\ttry {\n\t\t\tconst response = await handleCommand(command);\n\t\t\tif (response) {\n\t\t\t\toutput(response);\n\t\t\t\tawait waitForRawStdoutBackpressure();\n\t\t\t}\n\t\t\tawait checkShutdownRequested();\n\t\t} catch (commandError: unknown) {\n\t\t\toutput(\n\t\t\t\terror(\n\t\t\t\t\tcommand.id,\n\t\t\t\t\tcommand.type,\n\t\t\t\t\tcommandError instanceof Error ? commandError.message : String(commandError),\n\t\t\t\t),\n\t\t\t);\n\t\t\tawait waitForRawStdoutBackpressure();\n\t\t}\n\t};\n\n\tconst onInputEnd = () => {\n\t\tvoid shutdown();\n\t};\n\tprocess.stdin.on(\"end\", onInputEnd);\n\n\tdetachInput = (() => {\n\t\tconst detachJsonl = attachJsonlLineReader(process.stdin, (line) => {\n\t\t\tvoid handleInputLine(line);\n\t\t});\n\t\treturn () => {\n\t\t\tdetachJsonl();\n\t\t\tprocess.stdin.off(\"end\", onInputEnd);\n\t\t};\n\t})();\n\n\t// Keep process alive forever\n\treturn new Promise(() => {});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rpc-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AA4B/E,YAAY,EACX,UAAU,EACV,qBAAqB,EACrB,sBAAsB,EACtB,WAAW,EACX,eAAe,GACf,MAAM,gBAAgB,CAAC;AAExB;;;GAGG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC,KAAK,CAAC,CAsyBjF","sourcesContent":["/**\n * RPC mode: Headless operation with JSON stdin/stdout protocol.\n *\n * Used for embedding the agent in other applications.\n * Receives commands as JSON on stdin, outputs events and responses as JSON on stdout.\n *\n * Protocol:\n * - Commands: JSON objects with `type` field, optional `id` for correlation\n * - Responses: JSON objects with `type: \"response\"`, `command`, `success`, and optional `data`/`error`\n * - Events: AgentSessionEvent objects streamed as they occur\n * - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response\n */\n\nimport * as crypto from \"node:crypto\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\nimport type {\n\tExtensionUIContext,\n\tExtensionUIDialogOptions,\n\tExtensionWidgetOptions,\n\tWorkingIndicatorOptions,\n} from \"../../core/extensions/index.ts\";\nimport {\n\tflushRawStdout,\n\ttakeOverStdout,\n\twaitForRawStdoutBackpressure,\n\twriteRawStdout,\n} from \"../../core/output-guard.ts\";\nimport { slugifySkillCommandName } from \"../../core/skills.ts\";\nimport { killTrackedDetachedChildren } from \"../../utils/shell.ts\";\nimport { type Theme, theme } from \"../interactive/theme/theme.ts\";\nimport { toJsonEvent } from \"../json-event.ts\";\nimport { attachJsonlLineReader, serializeJsonLine } from \"./jsonl.ts\";\nimport type {\n\tRpcCommand,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcResponse,\n\tRpcSessionState,\n\tRpcSlashCommand,\n} from \"./rpc-types.ts\";\n\n// Re-export types for consumers\nexport type {\n\tRpcCommand,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcResponse,\n\tRpcSessionState,\n} from \"./rpc-types.ts\";\n\n/**\n * Run in RPC mode.\n * Listens for JSON commands on stdin, outputs events and responses on stdout.\n */\nexport async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<never> {\n\ttakeOverStdout();\n\tlet session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet unsubscribeBackpressure: (() => void) | undefined;\n\n\tconst output = (obj: RpcResponse | RpcExtensionUIRequest | object) => {\n\t\twriteRawStdout(serializeJsonLine(obj));\n\t};\n\n\tconst success = <T extends RpcCommand[\"type\"]>(\n\t\tid: string | undefined,\n\t\tcommand: T,\n\t\tdata?: object | null,\n\t): RpcResponse => {\n\t\tif (data === undefined) {\n\t\t\treturn { id, type: \"response\", command, success: true } as RpcResponse;\n\t\t}\n\t\treturn { id, type: \"response\", command, success: true, data } as RpcResponse;\n\t};\n\n\tconst error = (id: string | undefined, command: string, message: string): RpcResponse => {\n\t\treturn { id, type: \"response\", command, success: false, error: message };\n\t};\n\n\t// Pending extension UI requests waiting for response\n\tconst pendingExtensionRequests = new Map<\n\t\tstring,\n\t\t{ resolve: (value: any) => void; reject: (error: Error) => void }\n\t>();\n\n\t// Shutdown request flag\n\tlet shutdownRequested = false;\n\tlet shuttingDown = false;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\n\t/** Helper for dialog methods with signal/timeout support */\n\tfunction createDialogPromise<T>(\n\t\topts: ExtensionUIDialogOptions | undefined,\n\t\tdefaultValue: T,\n\t\trequest: Record<string, unknown>,\n\t\tparseResponse: (response: RpcExtensionUIResponse) => T,\n\t): Promise<T> {\n\t\tif (opts?.signal?.aborted) return Promise.resolve(defaultValue);\n\n\t\tconst id = crypto.randomUUID();\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlet timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n\t\t\tconst cleanup = () => {\n\t\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\t\topts?.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tpendingExtensionRequests.delete(id);\n\t\t\t};\n\n\t\t\tconst onAbort = () => {\n\t\t\t\tcleanup();\n\t\t\t\tresolve(defaultValue);\n\t\t\t};\n\t\t\topts?.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\tif (opts?.timeout) {\n\t\t\t\ttimeoutId = setTimeout(() => {\n\t\t\t\t\tcleanup();\n\t\t\t\t\tresolve(defaultValue);\n\t\t\t\t}, opts.timeout);\n\t\t\t}\n\n\t\t\tpendingExtensionRequests.set(id, {\n\t\t\t\tresolve: (response: RpcExtensionUIResponse) => {\n\t\t\t\t\tcleanup();\n\t\t\t\t\tresolve(parseResponse(response));\n\t\t\t\t},\n\t\t\t\treject,\n\t\t\t});\n\t\t\toutput({ type: \"extension_ui_request\", id, ...request } as RpcExtensionUIRequest);\n\t\t});\n\t}\n\n\t/**\n\t * Create an extension UI context that uses the RPC protocol.\n\t */\n\tconst createExtensionUIContext = (): ExtensionUIContext => ({\n\t\tselect: (title, options, opts) =>\n\t\t\tcreateDialogPromise(opts, undefined, { method: \"select\", title, options, timeout: opts?.timeout }, (r) =>\n\t\t\t\t\"cancelled\" in r && r.cancelled ? undefined : \"value\" in r ? r.value : undefined,\n\t\t\t),\n\n\t\tconfirm: (title, message, opts) =>\n\t\t\tcreateDialogPromise(opts, false, { method: \"confirm\", title, message, timeout: opts?.timeout }, (r) =>\n\t\t\t\t\"cancelled\" in r && r.cancelled ? false : \"confirmed\" in r ? r.confirmed : false,\n\t\t\t),\n\n\t\tinput: (title, placeholder, opts) =>\n\t\t\tcreateDialogPromise(opts, undefined, { method: \"input\", title, placeholder, timeout: opts?.timeout }, (r) =>\n\t\t\t\t\"cancelled\" in r && r.cancelled ? undefined : \"value\" in r ? r.value : undefined,\n\t\t\t),\n\n\t\tnotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void {\n\t\t\t// Fire and forget - no response needed\n\t\t\toutput({\n\t\t\t\ttype: \"extension_ui_request\",\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tmethod: \"notify\",\n\t\t\t\tmessage,\n\t\t\t\tnotifyType: type,\n\t\t\t} as RpcExtensionUIRequest);\n\t\t},\n\n\t\tonTerminalInput(): () => void {\n\t\t\t// Raw terminal input not supported in RPC mode\n\t\t\treturn () => {};\n\t\t},\n\n\t\tsetStatus(key: string, text: string | undefined): void {\n\t\t\t// Fire and forget - no response needed\n\t\t\toutput({\n\t\t\t\ttype: \"extension_ui_request\",\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tmethod: \"setStatus\",\n\t\t\t\tstatusKey: key,\n\t\t\t\tstatusText: text,\n\t\t\t} as RpcExtensionUIRequest);\n\t\t},\n\n\t\tsetWorkingMessage(_message?: string): void {\n\t\t\t// Working message not supported in RPC mode - requires TUI loader access\n\t\t},\n\n\t\tsetWorkingVisible(_visible: boolean): void {\n\t\t\t// Working visibility not supported in RPC mode - requires TUI loader access\n\t\t},\n\n\t\tsetWorkingIndicator(_options?: WorkingIndicatorOptions): void {\n\t\t\t// Working indicator customization not supported in RPC mode - requires TUI loader access\n\t\t},\n\n\t\tsetHiddenThinkingLabel(_label?: string): void {\n\t\t\t// Hidden thinking label not supported in RPC mode - requires TUI message rendering access\n\t\t},\n\n\t\tsetWidget(key: string, content: unknown, options?: ExtensionWidgetOptions): void {\n\t\t\t// Only support string arrays in RPC mode - factory functions are ignored\n\t\t\tif (content === undefined || Array.isArray(content)) {\n\t\t\t\toutput({\n\t\t\t\t\ttype: \"extension_ui_request\",\n\t\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\t\tmethod: \"setWidget\",\n\t\t\t\t\twidgetKey: key,\n\t\t\t\t\twidgetLines: content as string[] | undefined,\n\t\t\t\t\twidgetPlacement: options?.placement,\n\t\t\t\t} as RpcExtensionUIRequest);\n\t\t\t}\n\t\t\t// Component factories are not supported in RPC mode - would need TUI access\n\t\t},\n\n\t\tsetFooter(_factory: unknown): void {\n\t\t\t// Custom footer not supported in RPC mode - requires TUI access\n\t\t},\n\n\t\tsetHeader(_factory: unknown): void {\n\t\t\t// Custom header not supported in RPC mode - requires TUI access\n\t\t},\n\n\t\tsetTitle(title: string): void {\n\t\t\t// Fire and forget - host can implement terminal title control\n\t\t\toutput({\n\t\t\t\ttype: \"extension_ui_request\",\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tmethod: \"setTitle\",\n\t\t\t\ttitle,\n\t\t\t} as RpcExtensionUIRequest);\n\t\t},\n\n\t\tasync custom() {\n\t\t\t// Custom UI not supported in RPC mode\n\t\t\treturn undefined as never;\n\t\t},\n\n\t\tpasteToEditor(text: string): void {\n\t\t\t// Paste handling not supported in RPC mode - falls back to setEditorText\n\t\t\tthis.setEditorText(text);\n\t\t},\n\n\t\tsetEditorText(text: string): void {\n\t\t\t// Fire and forget - host can implement editor control\n\t\t\toutput({\n\t\t\t\ttype: \"extension_ui_request\",\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tmethod: \"set_editor_text\",\n\t\t\t\ttext,\n\t\t\t} as RpcExtensionUIRequest);\n\t\t},\n\n\t\tgetEditorText(): string {\n\t\t\t// Synchronous method can't wait for RPC response\n\t\t\t// Host should track editor state locally if needed\n\t\t\treturn \"\";\n\t\t},\n\n\t\tasync editor(title: string, prefill?: string): Promise<string | undefined> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingExtensionRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcExtensionUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(undefined);\n\t\t\t\t\t\t} else if (\"value\" in response) {\n\t\t\t\t\t\t\tresolve(response.value);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve(undefined);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\treject,\n\t\t\t\t});\n\t\t\t\toutput({ type: \"extension_ui_request\", id, method: \"editor\", title, prefill } as RpcExtensionUIRequest);\n\t\t\t});\n\t\t},\n\n\t\taddAutocompleteProvider(): void {\n\t\t\t// Autocomplete provider composition is not supported in RPC mode\n\t\t},\n\n\t\tsetEditorComponent(): void {\n\t\t\t// Custom editor components not supported in RPC mode\n\t\t},\n\n\t\tgetEditorComponent() {\n\t\t\t// Custom editor components not supported in RPC mode\n\t\t\treturn undefined;\n\t\t},\n\n\t\tget theme() {\n\t\t\treturn theme;\n\t\t},\n\n\t\tgetAllThemes() {\n\t\t\treturn [];\n\t\t},\n\n\t\tgetTheme(_name: string) {\n\t\t\treturn undefined;\n\t\t},\n\n\t\tsetTheme(_theme: string | Theme) {\n\t\t\t// Theme switching not supported in RPC mode\n\t\t\treturn { success: false, error: \"Theme switching not supported in RPC mode\" };\n\t\t},\n\n\t\tgetToolsExpanded() {\n\t\t\t// Tool expansion not supported in RPC mode - no TUI\n\t\t\treturn false;\n\t\t},\n\n\t\tsetToolsExpanded(_expanded: boolean) {\n\t\t\t// Tool expansion not supported in RPC mode - no TUI\n\t\t},\n\t});\n\n\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\n\t});\n\n\tconst rebindSession = async (): Promise<void> => {\n\t\tsession = runtimeHost.session;\n\t\tawait session.bindExtensions({\n\t\t\tuiContext: createExtensionUIContext(),\n\t\t\tmode: \"rpc\",\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.waitForIdle(),\n\t\t\t\tnewSession: async (options) => runtimeHost.newSession(options),\n\t\t\t\tfork: async (entryId, forkOptions) => {\n\t\t\t\t\tconst result = await runtimeHost.fork(entryId, forkOptions);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, options) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: options?.summarize,\n\t\t\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\t\t\tlabel: options?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, options) => {\n\t\t\t\t\treturn runtimeHost.switchSession(sessionPath, options);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tshutdownHandler: () => {\n\t\t\t\tshutdownRequested = true;\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\toutput({ type: \"extension_error\", extensionPath: err.extensionPath, event: err.event, error: err.error });\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\toutput(toJsonEvent(event));\n\t\t\tif (event.type === \"agent_settled\") {\n\t\t\t\tvoid checkShutdownRequested();\n\t\t\t}\n\t\t});\n\t\tunsubscribeBackpressure = session.agent.subscribe(async () => {\n\t\t\tawait waitForRawStdoutBackpressure();\n\t\t});\n\t};\n\n\tconst registerSignalHandlers = (): void => {\n\t\tconst signals: NodeJS.Signals[] = [\"SIGTERM\"];\n\t\tif (process.platform !== \"win32\") {\n\t\t\tsignals.push(\"SIGHUP\");\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst handler = () => {\n\t\t\t\tkillTrackedDetachedChildren();\n\t\t\t\tvoid shutdown(signal === \"SIGHUP\" ? 129 : 143, signal);\n\t\t\t};\n\t\t\tprocess.on(signal, handler);\n\t\t\tsignalCleanupHandlers.push(() => process.off(signal, handler));\n\t\t}\n\t};\n\n\tawait rebindSession();\n\tregisterSignalHandlers();\n\n\t// Handle a single command\n\tconst handleCommand = async (command: RpcCommand): Promise<RpcResponse | undefined> => {\n\t\tconst id = command.id;\n\n\t\tswitch (command.type) {\n\t\t\t// =================================================================\n\t\t\t// Prompting\n\t\t\t// =================================================================\n\n\t\t\tcase \"prompt\": {\n\t\t\t\t// Start prompt handling immediately, but emit the authoritative response only after\n\t\t\t\t// prompt preflight succeeds. Queued and immediately handled prompts also count as success.\n\t\t\t\tlet preflightSucceeded = false;\n\t\t\t\tvoid session\n\t\t\t\t\t.prompt(command.message, {\n\t\t\t\t\t\timages: command.images,\n\t\t\t\t\t\tstreamingBehavior: command.streamingBehavior,\n\t\t\t\t\t\tsource: \"rpc\",\n\t\t\t\t\t\tpreflightResult: (didSucceed) => {\n\t\t\t\t\t\t\tif (didSucceed) {\n\t\t\t\t\t\t\t\tpreflightSucceeded = true;\n\t\t\t\t\t\t\t\toutput(success(id, \"prompt\"));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\t.catch((e) => {\n\t\t\t\t\t\tif (!preflightSucceeded) {\n\t\t\t\t\t\t\toutput(error(id, \"prompt\", e.message));\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tcase \"steer\": {\n\t\t\t\tawait session.steer(command.message, command.images);\n\t\t\t\treturn success(id, \"steer\");\n\t\t\t}\n\n\t\t\tcase \"follow_up\": {\n\t\t\t\tawait session.followUp(command.message, command.images);\n\t\t\t\treturn success(id, \"follow_up\");\n\t\t\t}\n\n\t\t\tcase \"abort\": {\n\t\t\t\tawait session.abort();\n\t\t\t\treturn success(id, \"abort\");\n\t\t\t}\n\n\t\t\tcase \"clear_queue\": {\n\t\t\t\treturn success(id, \"clear_queue\", session.clearQueue());\n\t\t\t}\n\n\t\t\tcase \"new_session\": {\n\t\t\t\tconst options = command.parentSession ? { parentSession: command.parentSession } : undefined;\n\t\t\t\tconst result = await runtimeHost.newSession(options);\n\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\tawait rebindSession();\n\t\t\t\t}\n\t\t\t\treturn success(id, \"new_session\", result);\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// State\n\t\t\t// =================================================================\n\n\t\t\tcase \"get_state\": {\n\t\t\t\tconst state: RpcSessionState = {\n\t\t\t\t\tmodel: session.model,\n\t\t\t\t\tthinkingLevel: session.thinkingLevel,\n\t\t\t\t\tisStreaming: session.isStreaming,\n\t\t\t\t\tisCompacting: session.isCompacting,\n\t\t\t\t\tsteeringMode: session.steeringMode,\n\t\t\t\t\tfollowUpMode: session.followUpMode,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tsessionId: session.sessionId,\n\t\t\t\t\tsessionName: session.sessionName,\n\t\t\t\t\tautoCompactionEnabled: session.autoCompactionEnabled,\n\t\t\t\t\tmessageCount: session.messages.length,\n\t\t\t\t\tpendingMessageCount: session.pendingMessageCount,\n\t\t\t\t};\n\t\t\t\treturn success(id, \"get_state\", state);\n\t\t\t}\n\n\t\t\tcase \"agent/list\":\n\t\t\t\treturn success(id, \"agent/list\", session.listChildRuns());\n\t\t\tcase \"agent/spawn\": {\n\t\t\t\tconst result = await session.startChildRun(command.agentType, command.task, {\n\t\t\t\t\tworkspace: command.workspace,\n\t\t\t\t\tidempotencyKey: command.idempotencyKey,\n\t\t\t\t\ttimeoutMs: command.timeoutMs,\n\t\t\t\t});\n\t\t\t\treturn success(id, \"agent/spawn\", result);\n\t\t\t}\n\t\t\tcase \"agent/wait\": {\n\t\t\t\tconst result = await session.waitChildRunResult(command.childId);\n\t\t\t\treturn success(id, \"agent/wait\", result);\n\t\t\t}\n\t\t\tcase \"agent/status\": {\n\t\t\t\tconst result = session.childRunStatus(command.childId);\n\t\t\t\treturn success(id, \"agent/status\", result);\n\t\t\t}\n\t\t\tcase \"agent/recover\": {\n\t\t\t\t// Explicit workspace recovery (spec 2026-09-09, \"Workspace states and\n\t\t\t\t// explicit recovery\"): the pass-through verifies read-only and returns\n\t\t\t\t// `{workspaceState, dirty}`; refusals surface as the error response.\n\t\t\t\tconst result = await session.recoverChildWorkspace(command.childId);\n\t\t\t\treturn success(id, \"agent/recover\", result);\n\t\t\t}\n\t\t\tcase \"agent/send\":\n\t\t\t\tawait session.sendChildInput(command.childId, command.input);\n\t\t\t\treturn success(id, \"agent/send\");\n\t\t\tcase \"agent/resume\": {\n\t\t\t\tconst status = await session.resumeChildRun(command.childId, command.input);\n\t\t\t\treturn success(id, \"agent/resume\", { status });\n\t\t\t}\n\t\t\tcase \"agent/interrupt\":\n\t\t\t\tsession.interruptChildRun(command.childId, command.reason);\n\t\t\t\treturn success(id, \"agent/interrupt\");\n\t\t\tcase \"agent/close\":\n\t\t\t\tsession.closeChildRun(command.childId);\n\t\t\t\treturn success(id, \"agent/close\");\n\n\t\t\t// =================================================================\n\t\t\t// Model\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_model\": {\n\t\t\t\tconst models = session.modelRuntime.getAvailableSnapshot();\n\t\t\t\tconst model = models.find((m) => m.provider === command.provider && m.id === command.modelId);\n\t\t\t\tif (!model) {\n\t\t\t\t\treturn error(id, \"set_model\", `Model not found: ${command.provider}/${command.modelId}`);\n\t\t\t\t}\n\t\t\t\tawait session.setModel(model);\n\t\t\t\treturn success(id, \"set_model\", model);\n\t\t\t}\n\n\t\t\tcase \"cycle_model\": {\n\t\t\t\tconst result = await session.cycleModel();\n\t\t\t\tif (!result) {\n\t\t\t\t\treturn success(id, \"cycle_model\", null);\n\t\t\t\t}\n\t\t\t\treturn success(id, \"cycle_model\", result);\n\t\t\t}\n\n\t\t\tcase \"get_available_models\": {\n\t\t\t\tconst models = session.modelRuntime.getAvailableSnapshot();\n\t\t\t\treturn success(id, \"get_available_models\", { models });\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Thinking\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_thinking_level\": {\n\t\t\t\tsession.setThinkingLevel(command.level);\n\t\t\t\treturn success(id, \"set_thinking_level\");\n\t\t\t}\n\n\t\t\tcase \"cycle_thinking_level\": {\n\t\t\t\tconst level = session.cycleThinkingLevel();\n\t\t\t\tif (!level) {\n\t\t\t\t\treturn success(id, \"cycle_thinking_level\", null);\n\t\t\t\t}\n\t\t\t\treturn success(id, \"cycle_thinking_level\", { level });\n\t\t\t}\n\n\t\t\tcase \"get_available_thinking_levels\": {\n\t\t\t\tconst levels = session.getAvailableThinkingLevels();\n\t\t\t\treturn success(id, \"get_available_thinking_levels\", { levels });\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Queue Modes\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_steering_mode\": {\n\t\t\t\tsession.setSteeringMode(command.mode);\n\t\t\t\treturn success(id, \"set_steering_mode\");\n\t\t\t}\n\n\t\t\tcase \"set_follow_up_mode\": {\n\t\t\t\tsession.setFollowUpMode(command.mode);\n\t\t\t\treturn success(id, \"set_follow_up_mode\");\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Compaction\n\t\t\t// =================================================================\n\n\t\t\tcase \"compact\": {\n\t\t\t\tconst result = await session.compact(command.customInstructions);\n\t\t\t\treturn success(id, \"compact\", result);\n\t\t\t}\n\n\t\t\tcase \"set_auto_compaction\": {\n\t\t\t\tsession.setAutoCompactionEnabled(command.enabled);\n\t\t\t\treturn success(id, \"set_auto_compaction\");\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Retry\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_auto_retry\": {\n\t\t\t\tsession.setAutoRetryEnabled(command.enabled);\n\t\t\t\treturn success(id, \"set_auto_retry\");\n\t\t\t}\n\n\t\t\tcase \"abort_retry\": {\n\t\t\t\tsession.abortRetry();\n\t\t\t\treturn success(id, \"abort_retry\");\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Bash\n\t\t\t// =================================================================\n\n\t\t\tcase \"bash\": {\n\t\t\t\tconst eventResult = await session.extensionRunner.emitUserBash({\n\t\t\t\t\ttype: \"user_bash\",\n\t\t\t\t\tcommand: command.command,\n\t\t\t\t\texcludeFromContext: command.excludeFromContext ?? false,\n\t\t\t\t\tcwd: session.sessionManager.getCwd(),\n\t\t\t\t});\n\n\t\t\t\tif (eventResult?.result) {\n\t\t\t\t\tsession.recordBashResult(command.command, eventResult.result, {\n\t\t\t\t\t\texcludeFromContext: command.excludeFromContext,\n\t\t\t\t\t});\n\t\t\t\t\treturn success(id, \"bash\", eventResult.result);\n\t\t\t\t}\n\n\t\t\t\tconst result = await session.executeBash(command.command, undefined, {\n\t\t\t\t\texcludeFromContext: command.excludeFromContext,\n\t\t\t\t\tid,\n\t\t\t\t\toperations: eventResult?.operations,\n\t\t\t\t});\n\t\t\t\treturn success(id, \"bash\", result);\n\t\t\t}\n\n\t\t\tcase \"abort_bash\": {\n\t\t\t\tsession.abortBash();\n\t\t\t\treturn success(id, \"abort_bash\");\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Session\n\t\t\t// =================================================================\n\n\t\t\tcase \"get_session_stats\": {\n\t\t\t\tconst stats = session.getSessionStats();\n\t\t\t\treturn success(id, \"get_session_stats\", stats);\n\t\t\t}\n\n\t\t\tcase \"export_html\": {\n\t\t\t\tconst path = await session.exportToHtml(command.outputPath);\n\t\t\t\treturn success(id, \"export_html\", { path });\n\t\t\t}\n\n\t\t\tcase \"switch_session\": {\n\t\t\t\tconst result = await runtimeHost.switchSession(command.sessionPath);\n\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\tawait rebindSession();\n\t\t\t\t}\n\t\t\t\treturn success(id, \"switch_session\", result);\n\t\t\t}\n\n\t\t\tcase \"fork\": {\n\t\t\t\tconst result = await runtimeHost.fork(command.entryId);\n\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\tawait rebindSession();\n\t\t\t\t}\n\t\t\t\treturn success(id, \"fork\", { text: result.selectedText, cancelled: result.cancelled });\n\t\t\t}\n\n\t\t\tcase \"clone\": {\n\t\t\t\tconst leafId = session.sessionManager.getLeafId();\n\t\t\t\tif (!leafId) {\n\t\t\t\t\treturn error(id, \"clone\", \"Cannot clone session: no current entry selected\");\n\t\t\t\t}\n\t\t\t\tconst result = await runtimeHost.fork(leafId, { position: \"at\" });\n\t\t\t\tif (!result.cancelled) {\n\t\t\t\t\tawait rebindSession();\n\t\t\t\t}\n\t\t\t\treturn success(id, \"clone\", { cancelled: result.cancelled });\n\t\t\t}\n\n\t\t\tcase \"get_fork_messages\": {\n\t\t\t\tconst messages = session.getUserMessagesForForking();\n\t\t\t\treturn success(id, \"get_fork_messages\", { messages });\n\t\t\t}\n\n\t\t\tcase \"get_entries\": {\n\t\t\t\tconst sessionManager = session.sessionManager;\n\t\t\t\tlet entries = sessionManager.getEntries();\n\t\t\t\tif (command.since !== undefined) {\n\t\t\t\t\tconst sinceIndex = entries.findIndex((e) => e.id === command.since);\n\t\t\t\t\tif (sinceIndex === -1) {\n\t\t\t\t\t\treturn error(id, \"get_entries\", `Entry not found: ${command.since}`);\n\t\t\t\t\t}\n\t\t\t\t\tentries = entries.slice(sinceIndex + 1);\n\t\t\t\t}\n\t\t\t\treturn success(id, \"get_entries\", { entries, leafId: sessionManager.getLeafId() });\n\t\t\t}\n\n\t\t\tcase \"get_tree\": {\n\t\t\t\tconst sessionManager = session.sessionManager;\n\t\t\t\treturn success(id, \"get_tree\", { tree: sessionManager.getTree(), leafId: sessionManager.getLeafId() });\n\t\t\t}\n\n\t\t\tcase \"get_last_assistant_text\": {\n\t\t\t\tconst text = session.getLastAssistantText();\n\t\t\t\treturn success(id, \"get_last_assistant_text\", { text });\n\t\t\t}\n\n\t\t\tcase \"set_session_name\": {\n\t\t\t\tconst name = command.name.trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\treturn error(id, \"set_session_name\", \"Session name cannot be empty\");\n\t\t\t\t}\n\t\t\t\tsession.setSessionName(name);\n\t\t\t\treturn success(id, \"set_session_name\");\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Messages\n\t\t\t// =================================================================\n\n\t\t\tcase \"get_messages\": {\n\t\t\t\treturn success(id, \"get_messages\", { messages: session.messages });\n\t\t\t}\n\n\t\t\t// =================================================================\n\t\t\t// Commands (available for invocation via prompt)\n\t\t\t// =================================================================\n\n\t\t\tcase \"get_commands\": {\n\t\t\t\tconst commands: RpcSlashCommand[] = [];\n\n\t\t\t\tfor (const command of session.extensionRunner.getRegisteredCommands()) {\n\t\t\t\t\tcommands.push({\n\t\t\t\t\t\tname: command.invocationName,\n\t\t\t\t\t\tdescription: command.description,\n\t\t\t\t\t\tsource: \"extension\",\n\t\t\t\t\t\tsourceInfo: command.sourceInfo,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tfor (const template of session.promptTemplates) {\n\t\t\t\t\tcommands.push({\n\t\t\t\t\t\tname: template.name,\n\t\t\t\t\t\tdescription: template.description,\n\t\t\t\t\t\tsource: \"prompt\",\n\t\t\t\t\t\tsourceInfo: template.sourceInfo,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tfor (const skill of session.resourceLoader.getSkills().skills) {\n\t\t\t\t\tcommands.push({\n\t\t\t\t\t\tname: `skill:${slugifySkillCommandName(skill.name)}`,\n\t\t\t\t\t\tdescription: skill.description,\n\t\t\t\t\t\tsource: \"skill\",\n\t\t\t\t\t\tsourceInfo: skill.sourceInfo,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn success(id, \"get_commands\", { commands });\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tconst unknownCommand = command as { type: string };\n\t\t\t\treturn error(id, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Check if shutdown was requested and perform shutdown if so.\n\t * Called after handling each command when waiting for the next command.\n\t */\n\tlet detachInput = () => {};\n\n\tasync function shutdown(exitCode = 0, signal?: NodeJS.Signals): Promise<never> {\n\t\tif (shuttingDown) {\n\t\t\tprocess.exit(exitCode);\n\t\t}\n\t\tshuttingDown = true;\n\t\tfor (const cleanup of signalCleanupHandlers) {\n\t\t\tcleanup();\n\t\t}\n\t\tunsubscribe?.();\n\t\tunsubscribeBackpressure?.();\n\t\tawait runtimeHost.dispose();\n\t\tdetachInput();\n\t\tprocess.stdin.pause();\n\t\tif (signal !== \"SIGTERM\") {\n\t\t\tawait flushRawStdout();\n\t\t}\n\t\tprocess.exit(exitCode);\n\t}\n\n\tasync function checkShutdownRequested(): Promise<void> {\n\t\tif (!shutdownRequested) return;\n\t\tawait shutdown();\n\t}\n\n\tconst handleInputLine = async (line: string) => {\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(line);\n\t\t} catch (parseError: unknown) {\n\t\t\toutput(\n\t\t\t\terror(\n\t\t\t\t\tundefined,\n\t\t\t\t\t\"parse\",\n\t\t\t\t\t`Failed to parse command: ${parseError instanceof Error ? parseError.message : String(parseError)}`,\n\t\t\t\t),\n\t\t\t);\n\t\t\tawait waitForRawStdoutBackpressure();\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle extension UI responses\n\t\tif (\n\t\t\ttypeof parsed === \"object\" &&\n\t\t\tparsed !== null &&\n\t\t\t\"type\" in parsed &&\n\t\t\tparsed.type === \"extension_ui_response\"\n\t\t) {\n\t\t\tconst response = parsed as RpcExtensionUIResponse;\n\t\t\tconst pending = pendingExtensionRequests.get(response.id);\n\t\t\tif (pending) {\n\t\t\t\tpendingExtensionRequests.delete(response.id);\n\t\t\t\tpending.resolve(response);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst command = parsed as RpcCommand;\n\t\ttry {\n\t\t\tconst response = await handleCommand(command);\n\t\t\tif (response) {\n\t\t\t\toutput(response);\n\t\t\t\tawait waitForRawStdoutBackpressure();\n\t\t\t}\n\t\t\tawait checkShutdownRequested();\n\t\t} catch (commandError: unknown) {\n\t\t\toutput(\n\t\t\t\terror(\n\t\t\t\t\tcommand.id,\n\t\t\t\t\tcommand.type,\n\t\t\t\t\tcommandError instanceof Error ? commandError.message : String(commandError),\n\t\t\t\t),\n\t\t\t);\n\t\t\tawait waitForRawStdoutBackpressure();\n\t\t}\n\t};\n\n\tconst onInputEnd = () => {\n\t\tvoid shutdown();\n\t};\n\tprocess.stdin.on(\"end\", onInputEnd);\n\n\tdetachInput = (() => {\n\t\tconst detachJsonl = attachJsonlLineReader(process.stdin, (line) => {\n\t\t\tvoid handleInputLine(line);\n\t\t});\n\t\treturn () => {\n\t\t\tdetachJsonl();\n\t\t\tprocess.stdin.off(\"end\", onInputEnd);\n\t\t};\n\t})();\n\n\t// Keep process alive forever\n\treturn new Promise(() => {});\n}\n"]}
|
|
@@ -362,6 +362,44 @@ export async function runRpcMode(runtimeHost) {
|
|
|
362
362
|
};
|
|
363
363
|
return success(id, "get_state", state);
|
|
364
364
|
}
|
|
365
|
+
case "agent/list":
|
|
366
|
+
return success(id, "agent/list", session.listChildRuns());
|
|
367
|
+
case "agent/spawn": {
|
|
368
|
+
const result = await session.startChildRun(command.agentType, command.task, {
|
|
369
|
+
workspace: command.workspace,
|
|
370
|
+
idempotencyKey: command.idempotencyKey,
|
|
371
|
+
timeoutMs: command.timeoutMs,
|
|
372
|
+
});
|
|
373
|
+
return success(id, "agent/spawn", result);
|
|
374
|
+
}
|
|
375
|
+
case "agent/wait": {
|
|
376
|
+
const result = await session.waitChildRunResult(command.childId);
|
|
377
|
+
return success(id, "agent/wait", result);
|
|
378
|
+
}
|
|
379
|
+
case "agent/status": {
|
|
380
|
+
const result = session.childRunStatus(command.childId);
|
|
381
|
+
return success(id, "agent/status", result);
|
|
382
|
+
}
|
|
383
|
+
case "agent/recover": {
|
|
384
|
+
// Explicit workspace recovery (spec 2026-09-09, "Workspace states and
|
|
385
|
+
// explicit recovery"): the pass-through verifies read-only and returns
|
|
386
|
+
// `{workspaceState, dirty}`; refusals surface as the error response.
|
|
387
|
+
const result = await session.recoverChildWorkspace(command.childId);
|
|
388
|
+
return success(id, "agent/recover", result);
|
|
389
|
+
}
|
|
390
|
+
case "agent/send":
|
|
391
|
+
await session.sendChildInput(command.childId, command.input);
|
|
392
|
+
return success(id, "agent/send");
|
|
393
|
+
case "agent/resume": {
|
|
394
|
+
const status = await session.resumeChildRun(command.childId, command.input);
|
|
395
|
+
return success(id, "agent/resume", { status });
|
|
396
|
+
}
|
|
397
|
+
case "agent/interrupt":
|
|
398
|
+
session.interruptChildRun(command.childId, command.reason);
|
|
399
|
+
return success(id, "agent/interrupt");
|
|
400
|
+
case "agent/close":
|
|
401
|
+
session.closeChildRun(command.childId);
|
|
402
|
+
return success(id, "agent/close");
|
|
365
403
|
// =================================================================
|
|
366
404
|
// Model
|
|
367
405
|
// =================================================================
|