@code-yeongyu/senpi 2026.5.24 → 2026.5.29-3
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 +33 -0
- package/dist/cli/args.d.ts +0 -6
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -2
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +4 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +117 -81
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +18 -24
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.d.ts.map +1 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.js +4 -2
- package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.js.map +1 -1
- package/dist/core/extensions/builtin/session-observer/overlay.d.ts.map +1 -1
- package/dist/core/extensions/builtin/session-observer/overlay.js +0 -5
- package/dist/core/extensions/builtin/session-observer/overlay.js.map +1 -1
- package/dist/core/extensions/builtin/session-observer/scanner.d.ts.map +1 -1
- package/dist/core/extensions/builtin/session-observer/scanner.js +2 -0
- package/dist/core/extensions/builtin/session-observer/scanner.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +21 -9
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/output-guard.d.ts +1 -0
- package/dist/core/output-guard.d.ts.map +1 -1
- package/dist/core/output-guard.js +52 -22
- package/dist/core/output-guard.js.map +1 -1
- package/dist/core/session-work-barrier.d.ts +9 -0
- package/dist/core/session-work-barrier.d.ts.map +1 -0
- package/dist/core/session-work-barrier.js +50 -0
- package/dist/core/session-work-barrier.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +0 -15
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +1 -1
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +6 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +3 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +64 -7
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +15 -3
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/settings.md +3 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +18 -24
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +249 -39
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +349 -144
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +46 -29
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +5 -2
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js +2 -17
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +40 -55
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/input.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js +2 -2
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts +7 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +12 -2
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.d.ts +3 -0
- package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.js +38 -0
- package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.js.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +4 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +5 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +66 -14
- package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/npm-shrinkwrap.json +13 -13
- package/package.json +6 -7
- package/dist/modes/neo-mode.d.ts +0 -43
- package/dist/modes/neo-mode.d.ts.map +0 -1
- package/dist/modes/neo-mode.js +0 -142
- package/dist/modes/neo-mode.js.map +0 -1
- package/dist/neo-tui-bin/senpi-neo-tui-linux-x64 +0 -0
|
@@ -37,6 +37,7 @@ export declare class RpcClient {
|
|
|
37
37
|
private pendingRequests;
|
|
38
38
|
private requestId;
|
|
39
39
|
private stderr;
|
|
40
|
+
private exitError;
|
|
40
41
|
private options;
|
|
41
42
|
constructor(options?: RpcClientOptions);
|
|
42
43
|
/**
|
|
@@ -218,6 +219,8 @@ export declare class RpcClient {
|
|
|
218
219
|
*/
|
|
219
220
|
promptAndWait(message: string, images?: ImageContent[], timeout?: number): Promise<AgentEvent[]>;
|
|
220
221
|
private handleLine;
|
|
222
|
+
private createProcessExitError;
|
|
223
|
+
private rejectPendingRequests;
|
|
221
224
|
private send;
|
|
222
225
|
private getData;
|
|
223
226
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC Client for programmatic access to the coding agent.\n *\n * Spawns the agent in RPC mode and provides a typed API for all operations.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport type { AgentEvent, AgentMessage, ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { ImageContent } from \"@earendil-works/pi-ai\";\nimport type { SessionStats } from \"../../core/agent-session.ts\";\nimport type { BashResult } from \"../../core/bash-executor.ts\";\nimport type { CompactionResult } from \"../../core/compaction/index.ts\";\nimport { attachJsonlLineReader, serializeJsonLine } from \"./jsonl.ts\";\nimport type { RpcCommand, RpcResponse, RpcSessionState, RpcSlashCommand } from \"./rpc-types.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Distributive Omit that works with union types */\ntype DistributiveOmit<T, K extends keyof T> = T extends unknown ? Omit<T, K> : never;\n\n/** RpcCommand without the id field (for internal send) */\ntype RpcCommandBody = DistributiveOmit<RpcCommand, \"id\">;\n\nexport interface RpcClientOptions {\n\t/** Path to the CLI entry point (default: searches for dist/cli.js) */\n\tcliPath?: string;\n\t/** Working directory for the agent */\n\tcwd?: string;\n\t/** Environment variables */\n\tenv?: Record<string, string>;\n\t/** Provider to use */\n\tprovider?: string;\n\t/** Model ID to use */\n\tmodel?: string;\n\t/** Additional CLI arguments */\n\targs?: string[];\n}\n\nexport interface ModelInfo {\n\tprovider: string;\n\tid: string;\n\tcontextWindow: number;\n\treasoning: boolean;\n}\n\nexport type RpcEventListener = (event: AgentEvent) => void;\n\n// ============================================================================\n// RPC Client\n// ============================================================================\n\nexport class RpcClient {\n\tprivate process: ChildProcess | null = null;\n\tprivate stopReadingStdout: (() => void) | null = null;\n\tprivate eventListeners: RpcEventListener[] = [];\n\tprivate pendingRequests: Map<string, { resolve: (response: RpcResponse) => void; reject: (error: Error) => void }> =\n\t\tnew Map();\n\tprivate requestId = 0;\n\tprivate stderr = \"\";\n\tprivate options: RpcClientOptions;\n\n\tconstructor(options: RpcClientOptions = {}) {\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Start the RPC agent process.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.process) {\n\t\t\tthrow new Error(\"Client already started\");\n\t\t}\n\n\t\tconst cliPath = this.options.cliPath ?? \"dist/cli.js\";\n\t\tconst args = [\"--mode\", \"rpc\"];\n\n\t\tif (this.options.provider) {\n\t\t\targs.push(\"--provider\", this.options.provider);\n\t\t}\n\t\tif (this.options.model) {\n\t\t\targs.push(\"--model\", this.options.model);\n\t\t}\n\t\tif (this.options.args) {\n\t\t\targs.push(...this.options.args);\n\t\t}\n\n\t\tthis.process = spawn(\"node\", [cliPath, ...args], {\n\t\t\tcwd: this.options.cwd,\n\t\t\tenv: { ...process.env, ...this.options.env },\n\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\t// Collect stderr for debugging\n\t\tthis.process.stderr?.on(\"data\", (data) => {\n\t\t\tthis.stderr += data.toString();\n\t\t\tprocess.stderr.write(data);\n\t\t});\n\n\t\t// Set up strict JSONL reader for stdout.\n\t\tthis.stopReadingStdout = attachJsonlLineReader(this.process.stdout!, (line) => {\n\t\t\tthis.handleLine(line);\n\t\t});\n\n\t\t// Wait a moment for process to initialize\n\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\n\t\tif (this.process.exitCode !== null) {\n\t\t\tthrow new Error(`Agent process exited immediately with code ${this.process.exitCode}. Stderr: ${this.stderr}`);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the RPC agent process.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (!this.process) return;\n\n\t\tthis.stopReadingStdout?.();\n\t\tthis.stopReadingStdout = null;\n\t\tthis.process.kill(\"SIGTERM\");\n\n\t\t// Wait for process to exit\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.process?.kill(\"SIGKILL\");\n\t\t\t\tresolve();\n\t\t\t}, 1000);\n\n\t\t\tthis.process?.on(\"exit\", () => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\n\t\tthis.process = null;\n\t\tthis.pendingRequests.clear();\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t */\n\tonEvent(listener: RpcEventListener): () => void {\n\t\tthis.eventListeners.push(listener);\n\t\treturn () => {\n\t\t\tconst index = this.eventListeners.indexOf(listener);\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.eventListeners.splice(index, 1);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Get collected stderr output (useful for debugging).\n\t */\n\tgetStderr(): string {\n\t\treturn this.stderr;\n\t}\n\n\t// =========================================================================\n\t// Command Methods\n\t// =========================================================================\n\n\t/**\n\t * Send a prompt to the agent.\n\t * Returns immediately after sending; use onEvent() to receive streaming events.\n\t * Use waitForIdle() to wait for completion.\n\t */\n\tasync prompt(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"prompt\", message, images });\n\t}\n\n\t/**\n\t * Queue a steering message to interrupt the agent mid-run.\n\t */\n\tasync steer(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"steer\", message, images });\n\t}\n\n\t/**\n\t * Queue a follow-up message to be processed after the agent finishes.\n\t */\n\tasync followUp(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"follow_up\", message, images });\n\t}\n\n\t/**\n\t * Abort current operation.\n\t */\n\tasync abort(): Promise<void> {\n\t\tawait this.send({ type: \"abort\" });\n\t}\n\n\t/**\n\t * Start a new session, optionally with parent tracking.\n\t * @param parentSession - Optional parent session path for lineage tracking\n\t * @returns Object with `cancelled: true` if an extension cancelled the new session\n\t */\n\tasync newSession(parentSession?: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"new_session\", parentSession });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get current session state.\n\t */\n\tasync getState(): Promise<RpcSessionState> {\n\t\tconst response = await this.send({ type: \"get_state\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set model by provider and ID.\n\t */\n\tasync setModel(provider: string, modelId: string): Promise<{ provider: string; id: string }> {\n\t\tconst response = await this.send({ type: \"set_model\", provider, modelId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Cycle to next model.\n\t */\n\tasync cycleModel(): Promise<{\n\t\tmodel: { provider: string; id: string };\n\t\tthinkingLevel: ThinkingLevel;\n\t\tisScoped: boolean;\n\t} | null> {\n\t\tconst response = await this.send({ type: \"cycle_model\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get list of available models.\n\t */\n\tasync getAvailableModels(): Promise<ModelInfo[]> {\n\t\tconst response = await this.send({ type: \"get_available_models\" });\n\t\treturn this.getData<{ models: ModelInfo[] }>(response).models;\n\t}\n\n\t/**\n\t * Set thinking level.\n\t */\n\tasync setThinkingLevel(level: ThinkingLevel): Promise<void> {\n\t\tawait this.send({ type: \"set_thinking_level\", level });\n\t}\n\n\t/**\n\t * Cycle thinking level.\n\t */\n\tasync cycleThinkingLevel(): Promise<{ level: ThinkingLevel } | null> {\n\t\tconst response = await this.send({ type: \"cycle_thinking_level\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set steering mode.\n\t */\n\tasync setSteeringMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_steering_mode\", mode });\n\t}\n\n\t/**\n\t * Set follow-up mode.\n\t */\n\tasync setFollowUpMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_follow_up_mode\", mode });\n\t}\n\n\t/**\n\t * Compact session context.\n\t */\n\tasync compact(customInstructions?: string): Promise<CompactionResult> {\n\t\tconst response = await this.send({ type: \"compact\", customInstructions });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set auto-compaction enabled/disabled.\n\t */\n\tasync setAutoCompaction(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_compaction\", enabled });\n\t}\n\n\t/**\n\t * Set auto-retry enabled/disabled.\n\t */\n\tasync setAutoRetry(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_retry\", enabled });\n\t}\n\n\t/**\n\t * Abort in-progress retry.\n\t */\n\tasync abortRetry(): Promise<void> {\n\t\tawait this.send({ type: \"abort_retry\" });\n\t}\n\n\t/**\n\t * Execute a bash command.\n\t */\n\tasync bash(command: string): Promise<BashResult> {\n\t\tconst response = await this.send({ type: \"bash\", command });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Abort running bash command.\n\t */\n\tasync abortBash(): Promise<void> {\n\t\tawait this.send({ type: \"abort_bash\" });\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tasync getSessionStats(): Promise<SessionStats> {\n\t\tconst response = await this.send({ type: \"get_session_stats\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t */\n\tasync exportHtml(outputPath?: string): Promise<{ path: string }> {\n\t\tconst response = await this.send({ type: \"export_html\", outputPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Switch to a different session file.\n\t * @returns Object with `cancelled: true` if an extension cancelled the switch\n\t */\n\tasync switchSession(sessionPath: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"switch_session\", sessionPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Fork from a specific message.\n\t * @returns Object with `text` (the message text) and `cancelled` (if extension cancelled)\n\t */\n\tasync fork(entryId: string): Promise<{ text: string; cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"fork\", entryId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Clone the current active branch into a new session.\n\t * @returns Object with `cancelled: true` if an extension cancelled the clone\n\t */\n\tasync clone(): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"clone\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get messages available for forking.\n\t */\n\tasync getForkMessages(): Promise<Array<{ entryId: string; text: string }>> {\n\t\tconst response = await this.send({ type: \"get_fork_messages\" });\n\t\treturn this.getData<{ messages: Array<{ entryId: string; text: string }> }>(response).messages;\n\t}\n\n\t/**\n\t * Get text of last assistant message.\n\t */\n\tasync getLastAssistantText(): Promise<string | null> {\n\t\tconst response = await this.send({ type: \"get_last_assistant_text\" });\n\t\treturn this.getData<{ text: string | null }>(response).text;\n\t}\n\n\t/**\n\t * Set the session display name.\n\t */\n\tasync setSessionName(name: string): Promise<void> {\n\t\tawait this.send({ type: \"set_session_name\", name });\n\t}\n\n\t/**\n\t * Get all messages in the session.\n\t */\n\tasync getMessages(): Promise<AgentMessage[]> {\n\t\tconst response = await this.send({ type: \"get_messages\" });\n\t\treturn this.getData<{ messages: AgentMessage[] }>(response).messages;\n\t}\n\n\t/**\n\t * Get available commands (extension commands, prompt templates, skills).\n\t */\n\tasync getCommands(): Promise<RpcSlashCommand[]> {\n\t\tconst response = await this.send({ type: \"get_commands\" });\n\t\treturn this.getData<{ commands: RpcSlashCommand[] }>(response).commands;\n\t}\n\n\t// =========================================================================\n\t// Helpers\n\t// =========================================================================\n\n\t/**\n\t * Wait for agent to become idle (no streaming).\n\t * Resolves when agent_end event is received.\n\t */\n\twaitForIdle(timeout = 60000): Promise<void> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout waiting for agent to become idle. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Collect events until agent becomes idle.\n\t */\n\tcollectEvents(timeout = 60000): Promise<AgentEvent[]> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst events: AgentEvent[] = [];\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout collecting events. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tevents.push(event);\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve(events);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Send prompt and wait for completion, returning all events.\n\t */\n\tasync promptAndWait(message: string, images?: ImageContent[], timeout = 60000): Promise<AgentEvent[]> {\n\t\tconst eventsPromise = this.collectEvents(timeout);\n\t\tawait this.prompt(message, images);\n\t\treturn eventsPromise;\n\t}\n\n\t// =========================================================================\n\t// Internal\n\t// =========================================================================\n\n\tprivate handleLine(line: string): void {\n\t\ttry {\n\t\t\tconst data = JSON.parse(line);\n\n\t\t\t// Check if it's a response to a pending request\n\t\t\tif (data.type === \"response\" && data.id && this.pendingRequests.has(data.id)) {\n\t\t\t\tconst pending = this.pendingRequests.get(data.id)!;\n\t\t\t\tthis.pendingRequests.delete(data.id);\n\t\t\t\tpending.resolve(data as RpcResponse);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Otherwise it's an event\n\t\t\tfor (const listener of this.eventListeners) {\n\t\t\t\tlistener(data as AgentEvent);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore non-JSON lines\n\t\t}\n\t}\n\n\tprivate async send(command: RpcCommandBody): Promise<RpcResponse> {\n\t\tif (!this.process?.stdin) {\n\t\t\tthrow new Error(\"Client not started\");\n\t\t}\n\n\t\tconst id = `req_${++this.requestId}`;\n\t\tconst fullCommand = { ...command, id } as RpcCommand;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.pendingRequests.set(id, { resolve, reject });\n\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.pendingRequests.delete(id);\n\t\t\t\treject(new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.stderr}`));\n\t\t\t}, 30000);\n\n\t\t\tthis.pendingRequests.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve(response);\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\treject(error);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthis.process!.stdin!.write(serializeJsonLine(fullCommand));\n\t\t});\n\t}\n\n\tprivate getData<T>(response: RpcResponse): T {\n\t\tif (!response.success) {\n\t\t\tconst errorResponse = response as Extract<RpcResponse, { success: false }>;\n\t\t\tthrow new Error(errorResponse.error);\n\t\t}\n\t\t// Type assertion: we trust response.data matches T based on the command sent.\n\t\t// This is safe because each public method specifies the correct T for its command.\n\t\tconst successResponse = response as Extract<RpcResponse, { success: true; data: unknown }>;\n\t\treturn successResponse.data as T;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC Client for programmatic access to the coding agent.\n *\n * Spawns the agent in RPC mode and provides a typed API for all operations.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport type { AgentEvent, AgentMessage, ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { ImageContent } from \"@earendil-works/pi-ai\";\nimport type { SessionStats } from \"../../core/agent-session.ts\";\nimport type { BashResult } from \"../../core/bash-executor.ts\";\nimport type { CompactionResult } from \"../../core/compaction/index.ts\";\nimport { attachJsonlLineReader, serializeJsonLine } from \"./jsonl.ts\";\nimport type { RpcCommand, RpcResponse, RpcSessionState, RpcSlashCommand } from \"./rpc-types.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Distributive Omit that works with union types */\ntype DistributiveOmit<T, K extends keyof T> = T extends unknown ? Omit<T, K> : never;\n\n/** RpcCommand without the id field (for internal send) */\ntype RpcCommandBody = DistributiveOmit<RpcCommand, \"id\">;\n\nexport interface RpcClientOptions {\n\t/** Path to the CLI entry point (default: searches for dist/cli.js) */\n\tcliPath?: string;\n\t/** Working directory for the agent */\n\tcwd?: string;\n\t/** Environment variables */\n\tenv?: Record<string, string>;\n\t/** Provider to use */\n\tprovider?: string;\n\t/** Model ID to use */\n\tmodel?: string;\n\t/** Additional CLI arguments */\n\targs?: string[];\n}\n\nexport interface ModelInfo {\n\tprovider: string;\n\tid: string;\n\tcontextWindow: number;\n\treasoning: boolean;\n}\n\nexport type RpcEventListener = (event: AgentEvent) => void;\n\n// ============================================================================\n// RPC Client\n// ============================================================================\n\nexport class RpcClient {\n\tprivate process: ChildProcess | null = null;\n\tprivate stopReadingStdout: (() => void) | null = null;\n\tprivate eventListeners: RpcEventListener[] = [];\n\tprivate pendingRequests: Map<string, { resolve: (response: RpcResponse) => void; reject: (error: Error) => void }> =\n\t\tnew Map();\n\tprivate requestId = 0;\n\tprivate stderr = \"\";\n\tprivate exitError: Error | null = null;\n\tprivate options: RpcClientOptions;\n\n\tconstructor(options: RpcClientOptions = {}) {\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Start the RPC agent process.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.process) {\n\t\t\tthrow new Error(\"Client already started\");\n\t\t}\n\n\t\tthis.exitError = null;\n\n\t\tconst cliPath = this.options.cliPath ?? \"dist/cli.js\";\n\t\tconst args = [\"--mode\", \"rpc\"];\n\n\t\tif (this.options.provider) {\n\t\t\targs.push(\"--provider\", this.options.provider);\n\t\t}\n\t\tif (this.options.model) {\n\t\t\targs.push(\"--model\", this.options.model);\n\t\t}\n\t\tif (this.options.args) {\n\t\t\targs.push(...this.options.args);\n\t\t}\n\n\t\tconst childProcess = spawn(\"node\", [cliPath, ...args], {\n\t\t\tcwd: this.options.cwd,\n\t\t\tenv: { ...process.env, ...this.options.env },\n\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t});\n\t\tthis.process = childProcess;\n\n\t\t// Collect stderr for debugging\n\t\tchildProcess.stderr?.on(\"data\", (data) => {\n\t\t\tthis.stderr += data.toString();\n\t\t\tprocess.stderr.write(data);\n\t\t});\n\n\t\tchildProcess.once(\"exit\", (code, signal) => {\n\t\t\tif (this.process !== childProcess) return;\n\t\t\tconst error = this.createProcessExitError(code, signal);\n\t\t\tthis.exitError = error;\n\t\t\tthis.rejectPendingRequests(error);\n\t\t});\n\t\tchildProcess.once(\"error\", (error) => {\n\t\t\tif (this.process !== childProcess) return;\n\t\t\tconst processError = new Error(`Agent process error: ${error.message}. Stderr: ${this.stderr}`);\n\t\t\tthis.exitError = processError;\n\t\t\tthis.rejectPendingRequests(processError);\n\t\t});\n\t\tchildProcess.stdin?.on(\"error\", (error) => {\n\t\t\tif (this.process !== childProcess) return;\n\t\t\tconst stdinError =\n\t\t\t\tthis.exitError ?? new Error(`Agent process stdin error: ${error.message}. Stderr: ${this.stderr}`);\n\t\t\tthis.exitError = stdinError;\n\t\t\tthis.rejectPendingRequests(stdinError);\n\t\t});\n\n\t\t// Set up strict JSONL reader for stdout.\n\t\tthis.stopReadingStdout = attachJsonlLineReader(childProcess.stdout!, (line) => {\n\t\t\tthis.handleLine(line);\n\t\t});\n\n\t\t// Wait a moment for process to initialize\n\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\n\t\tif (this.process.exitCode !== null) {\n\t\t\tconst error = this.exitError ?? this.createProcessExitError(this.process.exitCode, this.process.signalCode);\n\t\t\tthis.exitError = error;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Stop the RPC agent process.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (!this.process) return;\n\n\t\tthis.stopReadingStdout?.();\n\t\tthis.stopReadingStdout = null;\n\t\tthis.process.kill(\"SIGTERM\");\n\n\t\t// Wait for process to exit\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.process?.kill(\"SIGKILL\");\n\t\t\t\tresolve();\n\t\t\t}, 1000);\n\n\t\t\tthis.process?.on(\"exit\", () => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\n\t\tthis.process = null;\n\t\tthis.pendingRequests.clear();\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t */\n\tonEvent(listener: RpcEventListener): () => void {\n\t\tthis.eventListeners.push(listener);\n\t\treturn () => {\n\t\t\tconst index = this.eventListeners.indexOf(listener);\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.eventListeners.splice(index, 1);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Get collected stderr output (useful for debugging).\n\t */\n\tgetStderr(): string {\n\t\treturn this.stderr;\n\t}\n\n\t// =========================================================================\n\t// Command Methods\n\t// =========================================================================\n\n\t/**\n\t * Send a prompt to the agent.\n\t * Returns immediately after sending; use onEvent() to receive streaming events.\n\t * Use waitForIdle() to wait for completion.\n\t */\n\tasync prompt(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"prompt\", message, images });\n\t}\n\n\t/**\n\t * Queue a steering message to interrupt the agent mid-run.\n\t */\n\tasync steer(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"steer\", message, images });\n\t}\n\n\t/**\n\t * Queue a follow-up message to be processed after the agent finishes.\n\t */\n\tasync followUp(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"follow_up\", message, images });\n\t}\n\n\t/**\n\t * Abort current operation.\n\t */\n\tasync abort(): Promise<void> {\n\t\tawait this.send({ type: \"abort\" });\n\t}\n\n\t/**\n\t * Start a new session, optionally with parent tracking.\n\t * @param parentSession - Optional parent session path for lineage tracking\n\t * @returns Object with `cancelled: true` if an extension cancelled the new session\n\t */\n\tasync newSession(parentSession?: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"new_session\", parentSession });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get current session state.\n\t */\n\tasync getState(): Promise<RpcSessionState> {\n\t\tconst response = await this.send({ type: \"get_state\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set model by provider and ID.\n\t */\n\tasync setModel(provider: string, modelId: string): Promise<{ provider: string; id: string }> {\n\t\tconst response = await this.send({ type: \"set_model\", provider, modelId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Cycle to next model.\n\t */\n\tasync cycleModel(): Promise<{\n\t\tmodel: { provider: string; id: string };\n\t\tthinkingLevel: ThinkingLevel;\n\t\tisScoped: boolean;\n\t} | null> {\n\t\tconst response = await this.send({ type: \"cycle_model\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get list of available models.\n\t */\n\tasync getAvailableModels(): Promise<ModelInfo[]> {\n\t\tconst response = await this.send({ type: \"get_available_models\" });\n\t\treturn this.getData<{ models: ModelInfo[] }>(response).models;\n\t}\n\n\t/**\n\t * Set thinking level.\n\t */\n\tasync setThinkingLevel(level: ThinkingLevel): Promise<void> {\n\t\tawait this.send({ type: \"set_thinking_level\", level });\n\t}\n\n\t/**\n\t * Cycle thinking level.\n\t */\n\tasync cycleThinkingLevel(): Promise<{ level: ThinkingLevel } | null> {\n\t\tconst response = await this.send({ type: \"cycle_thinking_level\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set steering mode.\n\t */\n\tasync setSteeringMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_steering_mode\", mode });\n\t}\n\n\t/**\n\t * Set follow-up mode.\n\t */\n\tasync setFollowUpMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_follow_up_mode\", mode });\n\t}\n\n\t/**\n\t * Compact session context.\n\t */\n\tasync compact(customInstructions?: string): Promise<CompactionResult> {\n\t\tconst response = await this.send({ type: \"compact\", customInstructions });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set auto-compaction enabled/disabled.\n\t */\n\tasync setAutoCompaction(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_compaction\", enabled });\n\t}\n\n\t/**\n\t * Set auto-retry enabled/disabled.\n\t */\n\tasync setAutoRetry(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_retry\", enabled });\n\t}\n\n\t/**\n\t * Abort in-progress retry.\n\t */\n\tasync abortRetry(): Promise<void> {\n\t\tawait this.send({ type: \"abort_retry\" });\n\t}\n\n\t/**\n\t * Execute a bash command.\n\t */\n\tasync bash(command: string): Promise<BashResult> {\n\t\tconst response = await this.send({ type: \"bash\", command });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Abort running bash command.\n\t */\n\tasync abortBash(): Promise<void> {\n\t\tawait this.send({ type: \"abort_bash\" });\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tasync getSessionStats(): Promise<SessionStats> {\n\t\tconst response = await this.send({ type: \"get_session_stats\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t */\n\tasync exportHtml(outputPath?: string): Promise<{ path: string }> {\n\t\tconst response = await this.send({ type: \"export_html\", outputPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Switch to a different session file.\n\t * @returns Object with `cancelled: true` if an extension cancelled the switch\n\t */\n\tasync switchSession(sessionPath: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"switch_session\", sessionPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Fork from a specific message.\n\t * @returns Object with `text` (the message text) and `cancelled` (if extension cancelled)\n\t */\n\tasync fork(entryId: string): Promise<{ text: string; cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"fork\", entryId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Clone the current active branch into a new session.\n\t * @returns Object with `cancelled: true` if an extension cancelled the clone\n\t */\n\tasync clone(): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"clone\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get messages available for forking.\n\t */\n\tasync getForkMessages(): Promise<Array<{ entryId: string; text: string }>> {\n\t\tconst response = await this.send({ type: \"get_fork_messages\" });\n\t\treturn this.getData<{ messages: Array<{ entryId: string; text: string }> }>(response).messages;\n\t}\n\n\t/**\n\t * Get text of last assistant message.\n\t */\n\tasync getLastAssistantText(): Promise<string | null> {\n\t\tconst response = await this.send({ type: \"get_last_assistant_text\" });\n\t\treturn this.getData<{ text: string | null }>(response).text;\n\t}\n\n\t/**\n\t * Set the session display name.\n\t */\n\tasync setSessionName(name: string): Promise<void> {\n\t\tawait this.send({ type: \"set_session_name\", name });\n\t}\n\n\t/**\n\t * Get all messages in the session.\n\t */\n\tasync getMessages(): Promise<AgentMessage[]> {\n\t\tconst response = await this.send({ type: \"get_messages\" });\n\t\treturn this.getData<{ messages: AgentMessage[] }>(response).messages;\n\t}\n\n\t/**\n\t * Get available commands (extension commands, prompt templates, skills).\n\t */\n\tasync getCommands(): Promise<RpcSlashCommand[]> {\n\t\tconst response = await this.send({ type: \"get_commands\" });\n\t\treturn this.getData<{ commands: RpcSlashCommand[] }>(response).commands;\n\t}\n\n\t// =========================================================================\n\t// Helpers\n\t// =========================================================================\n\n\t/**\n\t * Wait for agent to become idle (no streaming).\n\t * Resolves when agent_end event is received.\n\t */\n\twaitForIdle(timeout = 60000): Promise<void> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout waiting for agent to become idle. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Collect events until agent becomes idle.\n\t */\n\tcollectEvents(timeout = 60000): Promise<AgentEvent[]> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst events: AgentEvent[] = [];\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout collecting events. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tevents.push(event);\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve(events);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Send prompt and wait for completion, returning all events.\n\t */\n\tasync promptAndWait(message: string, images?: ImageContent[], timeout = 60000): Promise<AgentEvent[]> {\n\t\tconst eventsPromise = this.collectEvents(timeout);\n\t\tawait this.prompt(message, images);\n\t\treturn eventsPromise;\n\t}\n\n\t// =========================================================================\n\t// Internal\n\t// =========================================================================\n\n\tprivate handleLine(line: string): void {\n\t\ttry {\n\t\t\tconst data = JSON.parse(line);\n\n\t\t\t// Check if it's a response to a pending request\n\t\t\tif (data.type === \"response\" && data.id && this.pendingRequests.has(data.id)) {\n\t\t\t\tconst pending = this.pendingRequests.get(data.id)!;\n\t\t\t\tthis.pendingRequests.delete(data.id);\n\t\t\t\tpending.resolve(data as RpcResponse);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Otherwise it's an event\n\t\t\tfor (const listener of this.eventListeners) {\n\t\t\t\tlistener(data as AgentEvent);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore non-JSON lines\n\t\t}\n\t}\n\n\tprivate createProcessExitError(code: number | null, signal: NodeJS.Signals | null): Error {\n\t\treturn new Error(`Agent process exited (code=${code} signal=${signal}). Stderr: ${this.stderr}`);\n\t}\n\n\tprivate rejectPendingRequests(error: Error): void {\n\t\tfor (const pending of this.pendingRequests.values()) {\n\t\t\tpending.reject(error);\n\t\t}\n\t\tthis.pendingRequests.clear();\n\t}\n\n\tprivate async send(command: RpcCommandBody): Promise<RpcResponse> {\n\t\tconst childProcess = this.process;\n\t\tconst stdin = childProcess?.stdin;\n\t\tif (!childProcess || !stdin) {\n\t\t\tthrow new Error(\"Client not started\");\n\t\t}\n\t\tif (this.exitError) {\n\t\t\tthrow this.exitError;\n\t\t}\n\t\tif (childProcess.exitCode !== null) {\n\t\t\tconst error = this.createProcessExitError(childProcess.exitCode, childProcess.signalCode);\n\t\t\tthis.exitError = error;\n\t\t\tthrow error;\n\t\t}\n\t\tif (stdin.destroyed || !stdin.writable) {\n\t\t\tconst error = new Error(`Agent process stdin is not writable. Stderr: ${this.stderr}`);\n\t\t\tthis.exitError = error;\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst id = `req_${++this.requestId}`;\n\t\tconst fullCommand = { ...command, id } as RpcCommand;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.pendingRequests.delete(id);\n\t\t\t\treject(new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.stderr}`));\n\t\t\t}, 30000);\n\n\t\t\tthis.pendingRequests.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve(response);\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\treject(error);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tstdin.write(serializeJsonLine(fullCommand));\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst writeError = error instanceof Error ? error : new Error(String(error));\n\t\t\t\tconst pending = this.pendingRequests.get(id);\n\t\t\t\tthis.pendingRequests.delete(id);\n\t\t\t\tpending?.reject(writeError);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate getData<T>(response: RpcResponse): T {\n\t\tif (!response.success) {\n\t\t\tconst errorResponse = response as Extract<RpcResponse, { success: false }>;\n\t\t\tthrow new Error(errorResponse.error);\n\t\t}\n\t\t// Type assertion: we trust response.data matches T based on the command sent.\n\t\t// This is safe because each public method specifies the correct T for its command.\n\t\tconst successResponse = response as Extract<RpcResponse, { success: true; data: unknown }>;\n\t\treturn successResponse.data as T;\n\t}\n}\n"]}
|
|
@@ -15,6 +15,7 @@ export class RpcClient {
|
|
|
15
15
|
pendingRequests = new Map();
|
|
16
16
|
requestId = 0;
|
|
17
17
|
stderr = "";
|
|
18
|
+
exitError = null;
|
|
18
19
|
options;
|
|
19
20
|
constructor(options = {}) {
|
|
20
21
|
this.options = options;
|
|
@@ -26,6 +27,7 @@ export class RpcClient {
|
|
|
26
27
|
if (this.process) {
|
|
27
28
|
throw new Error("Client already started");
|
|
28
29
|
}
|
|
30
|
+
this.exitError = null;
|
|
29
31
|
const cliPath = this.options.cliPath ?? "dist/cli.js";
|
|
30
32
|
const args = ["--mode", "rpc"];
|
|
31
33
|
if (this.options.provider) {
|
|
@@ -37,24 +39,48 @@ export class RpcClient {
|
|
|
37
39
|
if (this.options.args) {
|
|
38
40
|
args.push(...this.options.args);
|
|
39
41
|
}
|
|
40
|
-
|
|
42
|
+
const childProcess = spawn("node", [cliPath, ...args], {
|
|
41
43
|
cwd: this.options.cwd,
|
|
42
44
|
env: { ...process.env, ...this.options.env },
|
|
43
45
|
stdio: ["pipe", "pipe", "pipe"],
|
|
44
46
|
});
|
|
47
|
+
this.process = childProcess;
|
|
45
48
|
// Collect stderr for debugging
|
|
46
|
-
|
|
49
|
+
childProcess.stderr?.on("data", (data) => {
|
|
47
50
|
this.stderr += data.toString();
|
|
48
51
|
process.stderr.write(data);
|
|
49
52
|
});
|
|
53
|
+
childProcess.once("exit", (code, signal) => {
|
|
54
|
+
if (this.process !== childProcess)
|
|
55
|
+
return;
|
|
56
|
+
const error = this.createProcessExitError(code, signal);
|
|
57
|
+
this.exitError = error;
|
|
58
|
+
this.rejectPendingRequests(error);
|
|
59
|
+
});
|
|
60
|
+
childProcess.once("error", (error) => {
|
|
61
|
+
if (this.process !== childProcess)
|
|
62
|
+
return;
|
|
63
|
+
const processError = new Error(`Agent process error: ${error.message}. Stderr: ${this.stderr}`);
|
|
64
|
+
this.exitError = processError;
|
|
65
|
+
this.rejectPendingRequests(processError);
|
|
66
|
+
});
|
|
67
|
+
childProcess.stdin?.on("error", (error) => {
|
|
68
|
+
if (this.process !== childProcess)
|
|
69
|
+
return;
|
|
70
|
+
const stdinError = this.exitError ?? new Error(`Agent process stdin error: ${error.message}. Stderr: ${this.stderr}`);
|
|
71
|
+
this.exitError = stdinError;
|
|
72
|
+
this.rejectPendingRequests(stdinError);
|
|
73
|
+
});
|
|
50
74
|
// Set up strict JSONL reader for stdout.
|
|
51
|
-
this.stopReadingStdout = attachJsonlLineReader(
|
|
75
|
+
this.stopReadingStdout = attachJsonlLineReader(childProcess.stdout, (line) => {
|
|
52
76
|
this.handleLine(line);
|
|
53
77
|
});
|
|
54
78
|
// Wait a moment for process to initialize
|
|
55
79
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
56
80
|
if (this.process.exitCode !== null) {
|
|
57
|
-
|
|
81
|
+
const error = this.exitError ?? this.createProcessExitError(this.process.exitCode, this.process.signalCode);
|
|
82
|
+
this.exitError = error;
|
|
83
|
+
throw error;
|
|
58
84
|
}
|
|
59
85
|
}
|
|
60
86
|
/**
|
|
@@ -371,14 +397,37 @@ export class RpcClient {
|
|
|
371
397
|
// Ignore non-JSON lines
|
|
372
398
|
}
|
|
373
399
|
}
|
|
400
|
+
createProcessExitError(code, signal) {
|
|
401
|
+
return new Error(`Agent process exited (code=${code} signal=${signal}). Stderr: ${this.stderr}`);
|
|
402
|
+
}
|
|
403
|
+
rejectPendingRequests(error) {
|
|
404
|
+
for (const pending of this.pendingRequests.values()) {
|
|
405
|
+
pending.reject(error);
|
|
406
|
+
}
|
|
407
|
+
this.pendingRequests.clear();
|
|
408
|
+
}
|
|
374
409
|
async send(command) {
|
|
375
|
-
|
|
410
|
+
const childProcess = this.process;
|
|
411
|
+
const stdin = childProcess?.stdin;
|
|
412
|
+
if (!childProcess || !stdin) {
|
|
376
413
|
throw new Error("Client not started");
|
|
377
414
|
}
|
|
415
|
+
if (this.exitError) {
|
|
416
|
+
throw this.exitError;
|
|
417
|
+
}
|
|
418
|
+
if (childProcess.exitCode !== null) {
|
|
419
|
+
const error = this.createProcessExitError(childProcess.exitCode, childProcess.signalCode);
|
|
420
|
+
this.exitError = error;
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
if (stdin.destroyed || !stdin.writable) {
|
|
424
|
+
const error = new Error(`Agent process stdin is not writable. Stderr: ${this.stderr}`);
|
|
425
|
+
this.exitError = error;
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
378
428
|
const id = `req_${++this.requestId}`;
|
|
379
429
|
const fullCommand = { ...command, id };
|
|
380
430
|
return new Promise((resolve, reject) => {
|
|
381
|
-
this.pendingRequests.set(id, { resolve, reject });
|
|
382
431
|
const timeout = setTimeout(() => {
|
|
383
432
|
this.pendingRequests.delete(id);
|
|
384
433
|
reject(new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.stderr}`));
|
|
@@ -393,7 +442,15 @@ export class RpcClient {
|
|
|
393
442
|
reject(error);
|
|
394
443
|
},
|
|
395
444
|
});
|
|
396
|
-
|
|
445
|
+
try {
|
|
446
|
+
stdin.write(serializeJsonLine(fullCommand));
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
const writeError = error instanceof Error ? error : new Error(String(error));
|
|
450
|
+
const pending = this.pendingRequests.get(id);
|
|
451
|
+
this.pendingRequests.delete(id);
|
|
452
|
+
pending?.reject(writeError);
|
|
453
|
+
}
|
|
397
454
|
});
|
|
398
455
|
}
|
|
399
456
|
getData(response) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-client.js","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC Client for programmatic access to the coding agent.\n *\n * Spawns the agent in RPC mode and provides a typed API for all operations.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport type { AgentEvent, AgentMessage, ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { ImageContent } from \"@earendil-works/pi-ai\";\nimport type { SessionStats } from \"../../core/agent-session.ts\";\nimport type { BashResult } from \"../../core/bash-executor.ts\";\nimport type { CompactionResult } from \"../../core/compaction/index.ts\";\nimport { attachJsonlLineReader, serializeJsonLine } from \"./jsonl.ts\";\nimport type { RpcCommand, RpcResponse, RpcSessionState, RpcSlashCommand } from \"./rpc-types.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Distributive Omit that works with union types */\ntype DistributiveOmit<T, K extends keyof T> = T extends unknown ? Omit<T, K> : never;\n\n/** RpcCommand without the id field (for internal send) */\ntype RpcCommandBody = DistributiveOmit<RpcCommand, \"id\">;\n\nexport interface RpcClientOptions {\n\t/** Path to the CLI entry point (default: searches for dist/cli.js) */\n\tcliPath?: string;\n\t/** Working directory for the agent */\n\tcwd?: string;\n\t/** Environment variables */\n\tenv?: Record<string, string>;\n\t/** Provider to use */\n\tprovider?: string;\n\t/** Model ID to use */\n\tmodel?: string;\n\t/** Additional CLI arguments */\n\targs?: string[];\n}\n\nexport interface ModelInfo {\n\tprovider: string;\n\tid: string;\n\tcontextWindow: number;\n\treasoning: boolean;\n}\n\nexport type RpcEventListener = (event: AgentEvent) => void;\n\n// ============================================================================\n// RPC Client\n// ============================================================================\n\nexport class RpcClient {\n\tprivate process: ChildProcess | null = null;\n\tprivate stopReadingStdout: (() => void) | null = null;\n\tprivate eventListeners: RpcEventListener[] = [];\n\tprivate pendingRequests: Map<string, { resolve: (response: RpcResponse) => void; reject: (error: Error) => void }> =\n\t\tnew Map();\n\tprivate requestId = 0;\n\tprivate stderr = \"\";\n\tprivate options: RpcClientOptions;\n\n\tconstructor(options: RpcClientOptions = {}) {\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Start the RPC agent process.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.process) {\n\t\t\tthrow new Error(\"Client already started\");\n\t\t}\n\n\t\tconst cliPath = this.options.cliPath ?? \"dist/cli.js\";\n\t\tconst args = [\"--mode\", \"rpc\"];\n\n\t\tif (this.options.provider) {\n\t\t\targs.push(\"--provider\", this.options.provider);\n\t\t}\n\t\tif (this.options.model) {\n\t\t\targs.push(\"--model\", this.options.model);\n\t\t}\n\t\tif (this.options.args) {\n\t\t\targs.push(...this.options.args);\n\t\t}\n\n\t\tthis.process = spawn(\"node\", [cliPath, ...args], {\n\t\t\tcwd: this.options.cwd,\n\t\t\tenv: { ...process.env, ...this.options.env },\n\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\t// Collect stderr for debugging\n\t\tthis.process.stderr?.on(\"data\", (data) => {\n\t\t\tthis.stderr += data.toString();\n\t\t\tprocess.stderr.write(data);\n\t\t});\n\n\t\t// Set up strict JSONL reader for stdout.\n\t\tthis.stopReadingStdout = attachJsonlLineReader(this.process.stdout!, (line) => {\n\t\t\tthis.handleLine(line);\n\t\t});\n\n\t\t// Wait a moment for process to initialize\n\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\n\t\tif (this.process.exitCode !== null) {\n\t\t\tthrow new Error(`Agent process exited immediately with code ${this.process.exitCode}. Stderr: ${this.stderr}`);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the RPC agent process.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (!this.process) return;\n\n\t\tthis.stopReadingStdout?.();\n\t\tthis.stopReadingStdout = null;\n\t\tthis.process.kill(\"SIGTERM\");\n\n\t\t// Wait for process to exit\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.process?.kill(\"SIGKILL\");\n\t\t\t\tresolve();\n\t\t\t}, 1000);\n\n\t\t\tthis.process?.on(\"exit\", () => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\n\t\tthis.process = null;\n\t\tthis.pendingRequests.clear();\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t */\n\tonEvent(listener: RpcEventListener): () => void {\n\t\tthis.eventListeners.push(listener);\n\t\treturn () => {\n\t\t\tconst index = this.eventListeners.indexOf(listener);\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.eventListeners.splice(index, 1);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Get collected stderr output (useful for debugging).\n\t */\n\tgetStderr(): string {\n\t\treturn this.stderr;\n\t}\n\n\t// =========================================================================\n\t// Command Methods\n\t// =========================================================================\n\n\t/**\n\t * Send a prompt to the agent.\n\t * Returns immediately after sending; use onEvent() to receive streaming events.\n\t * Use waitForIdle() to wait for completion.\n\t */\n\tasync prompt(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"prompt\", message, images });\n\t}\n\n\t/**\n\t * Queue a steering message to interrupt the agent mid-run.\n\t */\n\tasync steer(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"steer\", message, images });\n\t}\n\n\t/**\n\t * Queue a follow-up message to be processed after the agent finishes.\n\t */\n\tasync followUp(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"follow_up\", message, images });\n\t}\n\n\t/**\n\t * Abort current operation.\n\t */\n\tasync abort(): Promise<void> {\n\t\tawait this.send({ type: \"abort\" });\n\t}\n\n\t/**\n\t * Start a new session, optionally with parent tracking.\n\t * @param parentSession - Optional parent session path for lineage tracking\n\t * @returns Object with `cancelled: true` if an extension cancelled the new session\n\t */\n\tasync newSession(parentSession?: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"new_session\", parentSession });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get current session state.\n\t */\n\tasync getState(): Promise<RpcSessionState> {\n\t\tconst response = await this.send({ type: \"get_state\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set model by provider and ID.\n\t */\n\tasync setModel(provider: string, modelId: string): Promise<{ provider: string; id: string }> {\n\t\tconst response = await this.send({ type: \"set_model\", provider, modelId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Cycle to next model.\n\t */\n\tasync cycleModel(): Promise<{\n\t\tmodel: { provider: string; id: string };\n\t\tthinkingLevel: ThinkingLevel;\n\t\tisScoped: boolean;\n\t} | null> {\n\t\tconst response = await this.send({ type: \"cycle_model\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get list of available models.\n\t */\n\tasync getAvailableModels(): Promise<ModelInfo[]> {\n\t\tconst response = await this.send({ type: \"get_available_models\" });\n\t\treturn this.getData<{ models: ModelInfo[] }>(response).models;\n\t}\n\n\t/**\n\t * Set thinking level.\n\t */\n\tasync setThinkingLevel(level: ThinkingLevel): Promise<void> {\n\t\tawait this.send({ type: \"set_thinking_level\", level });\n\t}\n\n\t/**\n\t * Cycle thinking level.\n\t */\n\tasync cycleThinkingLevel(): Promise<{ level: ThinkingLevel } | null> {\n\t\tconst response = await this.send({ type: \"cycle_thinking_level\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set steering mode.\n\t */\n\tasync setSteeringMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_steering_mode\", mode });\n\t}\n\n\t/**\n\t * Set follow-up mode.\n\t */\n\tasync setFollowUpMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_follow_up_mode\", mode });\n\t}\n\n\t/**\n\t * Compact session context.\n\t */\n\tasync compact(customInstructions?: string): Promise<CompactionResult> {\n\t\tconst response = await this.send({ type: \"compact\", customInstructions });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set auto-compaction enabled/disabled.\n\t */\n\tasync setAutoCompaction(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_compaction\", enabled });\n\t}\n\n\t/**\n\t * Set auto-retry enabled/disabled.\n\t */\n\tasync setAutoRetry(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_retry\", enabled });\n\t}\n\n\t/**\n\t * Abort in-progress retry.\n\t */\n\tasync abortRetry(): Promise<void> {\n\t\tawait this.send({ type: \"abort_retry\" });\n\t}\n\n\t/**\n\t * Execute a bash command.\n\t */\n\tasync bash(command: string): Promise<BashResult> {\n\t\tconst response = await this.send({ type: \"bash\", command });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Abort running bash command.\n\t */\n\tasync abortBash(): Promise<void> {\n\t\tawait this.send({ type: \"abort_bash\" });\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tasync getSessionStats(): Promise<SessionStats> {\n\t\tconst response = await this.send({ type: \"get_session_stats\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t */\n\tasync exportHtml(outputPath?: string): Promise<{ path: string }> {\n\t\tconst response = await this.send({ type: \"export_html\", outputPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Switch to a different session file.\n\t * @returns Object with `cancelled: true` if an extension cancelled the switch\n\t */\n\tasync switchSession(sessionPath: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"switch_session\", sessionPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Fork from a specific message.\n\t * @returns Object with `text` (the message text) and `cancelled` (if extension cancelled)\n\t */\n\tasync fork(entryId: string): Promise<{ text: string; cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"fork\", entryId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Clone the current active branch into a new session.\n\t * @returns Object with `cancelled: true` if an extension cancelled the clone\n\t */\n\tasync clone(): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"clone\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get messages available for forking.\n\t */\n\tasync getForkMessages(): Promise<Array<{ entryId: string; text: string }>> {\n\t\tconst response = await this.send({ type: \"get_fork_messages\" });\n\t\treturn this.getData<{ messages: Array<{ entryId: string; text: string }> }>(response).messages;\n\t}\n\n\t/**\n\t * Get text of last assistant message.\n\t */\n\tasync getLastAssistantText(): Promise<string | null> {\n\t\tconst response = await this.send({ type: \"get_last_assistant_text\" });\n\t\treturn this.getData<{ text: string | null }>(response).text;\n\t}\n\n\t/**\n\t * Set the session display name.\n\t */\n\tasync setSessionName(name: string): Promise<void> {\n\t\tawait this.send({ type: \"set_session_name\", name });\n\t}\n\n\t/**\n\t * Get all messages in the session.\n\t */\n\tasync getMessages(): Promise<AgentMessage[]> {\n\t\tconst response = await this.send({ type: \"get_messages\" });\n\t\treturn this.getData<{ messages: AgentMessage[] }>(response).messages;\n\t}\n\n\t/**\n\t * Get available commands (extension commands, prompt templates, skills).\n\t */\n\tasync getCommands(): Promise<RpcSlashCommand[]> {\n\t\tconst response = await this.send({ type: \"get_commands\" });\n\t\treturn this.getData<{ commands: RpcSlashCommand[] }>(response).commands;\n\t}\n\n\t// =========================================================================\n\t// Helpers\n\t// =========================================================================\n\n\t/**\n\t * Wait for agent to become idle (no streaming).\n\t * Resolves when agent_end event is received.\n\t */\n\twaitForIdle(timeout = 60000): Promise<void> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout waiting for agent to become idle. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Collect events until agent becomes idle.\n\t */\n\tcollectEvents(timeout = 60000): Promise<AgentEvent[]> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst events: AgentEvent[] = [];\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout collecting events. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tevents.push(event);\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve(events);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Send prompt and wait for completion, returning all events.\n\t */\n\tasync promptAndWait(message: string, images?: ImageContent[], timeout = 60000): Promise<AgentEvent[]> {\n\t\tconst eventsPromise = this.collectEvents(timeout);\n\t\tawait this.prompt(message, images);\n\t\treturn eventsPromise;\n\t}\n\n\t// =========================================================================\n\t// Internal\n\t// =========================================================================\n\n\tprivate handleLine(line: string): void {\n\t\ttry {\n\t\t\tconst data = JSON.parse(line);\n\n\t\t\t// Check if it's a response to a pending request\n\t\t\tif (data.type === \"response\" && data.id && this.pendingRequests.has(data.id)) {\n\t\t\t\tconst pending = this.pendingRequests.get(data.id)!;\n\t\t\t\tthis.pendingRequests.delete(data.id);\n\t\t\t\tpending.resolve(data as RpcResponse);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Otherwise it's an event\n\t\t\tfor (const listener of this.eventListeners) {\n\t\t\t\tlistener(data as AgentEvent);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore non-JSON lines\n\t\t}\n\t}\n\n\tprivate async send(command: RpcCommandBody): Promise<RpcResponse> {\n\t\tif (!this.process?.stdin) {\n\t\t\tthrow new Error(\"Client not started\");\n\t\t}\n\n\t\tconst id = `req_${++this.requestId}`;\n\t\tconst fullCommand = { ...command, id } as RpcCommand;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.pendingRequests.set(id, { resolve, reject });\n\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.pendingRequests.delete(id);\n\t\t\t\treject(new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.stderr}`));\n\t\t\t}, 30000);\n\n\t\t\tthis.pendingRequests.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve(response);\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\treject(error);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthis.process!.stdin!.write(serializeJsonLine(fullCommand));\n\t\t});\n\t}\n\n\tprivate getData<T>(response: RpcResponse): T {\n\t\tif (!response.success) {\n\t\t\tconst errorResponse = response as Extract<RpcResponse, { success: false }>;\n\t\t\tthrow new Error(errorResponse.error);\n\t\t}\n\t\t// Type assertion: we trust response.data matches T based on the command sent.\n\t\t// This is safe because each public method specifies the correct T for its command.\n\t\tconst successResponse = response as Extract<RpcResponse, { success: true; data: unknown }>;\n\t\treturn successResponse.data as T;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rpc-client.js","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC Client for programmatic access to the coding agent.\n *\n * Spawns the agent in RPC mode and provides a typed API for all operations.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport type { AgentEvent, AgentMessage, ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { ImageContent } from \"@earendil-works/pi-ai\";\nimport type { SessionStats } from \"../../core/agent-session.ts\";\nimport type { BashResult } from \"../../core/bash-executor.ts\";\nimport type { CompactionResult } from \"../../core/compaction/index.ts\";\nimport { attachJsonlLineReader, serializeJsonLine } from \"./jsonl.ts\";\nimport type { RpcCommand, RpcResponse, RpcSessionState, RpcSlashCommand } from \"./rpc-types.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Distributive Omit that works with union types */\ntype DistributiveOmit<T, K extends keyof T> = T extends unknown ? Omit<T, K> : never;\n\n/** RpcCommand without the id field (for internal send) */\ntype RpcCommandBody = DistributiveOmit<RpcCommand, \"id\">;\n\nexport interface RpcClientOptions {\n\t/** Path to the CLI entry point (default: searches for dist/cli.js) */\n\tcliPath?: string;\n\t/** Working directory for the agent */\n\tcwd?: string;\n\t/** Environment variables */\n\tenv?: Record<string, string>;\n\t/** Provider to use */\n\tprovider?: string;\n\t/** Model ID to use */\n\tmodel?: string;\n\t/** Additional CLI arguments */\n\targs?: string[];\n}\n\nexport interface ModelInfo {\n\tprovider: string;\n\tid: string;\n\tcontextWindow: number;\n\treasoning: boolean;\n}\n\nexport type RpcEventListener = (event: AgentEvent) => void;\n\n// ============================================================================\n// RPC Client\n// ============================================================================\n\nexport class RpcClient {\n\tprivate process: ChildProcess | null = null;\n\tprivate stopReadingStdout: (() => void) | null = null;\n\tprivate eventListeners: RpcEventListener[] = [];\n\tprivate pendingRequests: Map<string, { resolve: (response: RpcResponse) => void; reject: (error: Error) => void }> =\n\t\tnew Map();\n\tprivate requestId = 0;\n\tprivate stderr = \"\";\n\tprivate exitError: Error | null = null;\n\tprivate options: RpcClientOptions;\n\n\tconstructor(options: RpcClientOptions = {}) {\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Start the RPC agent process.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.process) {\n\t\t\tthrow new Error(\"Client already started\");\n\t\t}\n\n\t\tthis.exitError = null;\n\n\t\tconst cliPath = this.options.cliPath ?? \"dist/cli.js\";\n\t\tconst args = [\"--mode\", \"rpc\"];\n\n\t\tif (this.options.provider) {\n\t\t\targs.push(\"--provider\", this.options.provider);\n\t\t}\n\t\tif (this.options.model) {\n\t\t\targs.push(\"--model\", this.options.model);\n\t\t}\n\t\tif (this.options.args) {\n\t\t\targs.push(...this.options.args);\n\t\t}\n\n\t\tconst childProcess = spawn(\"node\", [cliPath, ...args], {\n\t\t\tcwd: this.options.cwd,\n\t\t\tenv: { ...process.env, ...this.options.env },\n\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t});\n\t\tthis.process = childProcess;\n\n\t\t// Collect stderr for debugging\n\t\tchildProcess.stderr?.on(\"data\", (data) => {\n\t\t\tthis.stderr += data.toString();\n\t\t\tprocess.stderr.write(data);\n\t\t});\n\n\t\tchildProcess.once(\"exit\", (code, signal) => {\n\t\t\tif (this.process !== childProcess) return;\n\t\t\tconst error = this.createProcessExitError(code, signal);\n\t\t\tthis.exitError = error;\n\t\t\tthis.rejectPendingRequests(error);\n\t\t});\n\t\tchildProcess.once(\"error\", (error) => {\n\t\t\tif (this.process !== childProcess) return;\n\t\t\tconst processError = new Error(`Agent process error: ${error.message}. Stderr: ${this.stderr}`);\n\t\t\tthis.exitError = processError;\n\t\t\tthis.rejectPendingRequests(processError);\n\t\t});\n\t\tchildProcess.stdin?.on(\"error\", (error) => {\n\t\t\tif (this.process !== childProcess) return;\n\t\t\tconst stdinError =\n\t\t\t\tthis.exitError ?? new Error(`Agent process stdin error: ${error.message}. Stderr: ${this.stderr}`);\n\t\t\tthis.exitError = stdinError;\n\t\t\tthis.rejectPendingRequests(stdinError);\n\t\t});\n\n\t\t// Set up strict JSONL reader for stdout.\n\t\tthis.stopReadingStdout = attachJsonlLineReader(childProcess.stdout!, (line) => {\n\t\t\tthis.handleLine(line);\n\t\t});\n\n\t\t// Wait a moment for process to initialize\n\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\n\t\tif (this.process.exitCode !== null) {\n\t\t\tconst error = this.exitError ?? this.createProcessExitError(this.process.exitCode, this.process.signalCode);\n\t\t\tthis.exitError = error;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Stop the RPC agent process.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (!this.process) return;\n\n\t\tthis.stopReadingStdout?.();\n\t\tthis.stopReadingStdout = null;\n\t\tthis.process.kill(\"SIGTERM\");\n\n\t\t// Wait for process to exit\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.process?.kill(\"SIGKILL\");\n\t\t\t\tresolve();\n\t\t\t}, 1000);\n\n\t\t\tthis.process?.on(\"exit\", () => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\n\t\tthis.process = null;\n\t\tthis.pendingRequests.clear();\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t */\n\tonEvent(listener: RpcEventListener): () => void {\n\t\tthis.eventListeners.push(listener);\n\t\treturn () => {\n\t\t\tconst index = this.eventListeners.indexOf(listener);\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.eventListeners.splice(index, 1);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Get collected stderr output (useful for debugging).\n\t */\n\tgetStderr(): string {\n\t\treturn this.stderr;\n\t}\n\n\t// =========================================================================\n\t// Command Methods\n\t// =========================================================================\n\n\t/**\n\t * Send a prompt to the agent.\n\t * Returns immediately after sending; use onEvent() to receive streaming events.\n\t * Use waitForIdle() to wait for completion.\n\t */\n\tasync prompt(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"prompt\", message, images });\n\t}\n\n\t/**\n\t * Queue a steering message to interrupt the agent mid-run.\n\t */\n\tasync steer(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"steer\", message, images });\n\t}\n\n\t/**\n\t * Queue a follow-up message to be processed after the agent finishes.\n\t */\n\tasync followUp(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"follow_up\", message, images });\n\t}\n\n\t/**\n\t * Abort current operation.\n\t */\n\tasync abort(): Promise<void> {\n\t\tawait this.send({ type: \"abort\" });\n\t}\n\n\t/**\n\t * Start a new session, optionally with parent tracking.\n\t * @param parentSession - Optional parent session path for lineage tracking\n\t * @returns Object with `cancelled: true` if an extension cancelled the new session\n\t */\n\tasync newSession(parentSession?: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"new_session\", parentSession });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get current session state.\n\t */\n\tasync getState(): Promise<RpcSessionState> {\n\t\tconst response = await this.send({ type: \"get_state\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set model by provider and ID.\n\t */\n\tasync setModel(provider: string, modelId: string): Promise<{ provider: string; id: string }> {\n\t\tconst response = await this.send({ type: \"set_model\", provider, modelId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Cycle to next model.\n\t */\n\tasync cycleModel(): Promise<{\n\t\tmodel: { provider: string; id: string };\n\t\tthinkingLevel: ThinkingLevel;\n\t\tisScoped: boolean;\n\t} | null> {\n\t\tconst response = await this.send({ type: \"cycle_model\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get list of available models.\n\t */\n\tasync getAvailableModels(): Promise<ModelInfo[]> {\n\t\tconst response = await this.send({ type: \"get_available_models\" });\n\t\treturn this.getData<{ models: ModelInfo[] }>(response).models;\n\t}\n\n\t/**\n\t * Set thinking level.\n\t */\n\tasync setThinkingLevel(level: ThinkingLevel): Promise<void> {\n\t\tawait this.send({ type: \"set_thinking_level\", level });\n\t}\n\n\t/**\n\t * Cycle thinking level.\n\t */\n\tasync cycleThinkingLevel(): Promise<{ level: ThinkingLevel } | null> {\n\t\tconst response = await this.send({ type: \"cycle_thinking_level\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set steering mode.\n\t */\n\tasync setSteeringMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_steering_mode\", mode });\n\t}\n\n\t/**\n\t * Set follow-up mode.\n\t */\n\tasync setFollowUpMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_follow_up_mode\", mode });\n\t}\n\n\t/**\n\t * Compact session context.\n\t */\n\tasync compact(customInstructions?: string): Promise<CompactionResult> {\n\t\tconst response = await this.send({ type: \"compact\", customInstructions });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set auto-compaction enabled/disabled.\n\t */\n\tasync setAutoCompaction(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_compaction\", enabled });\n\t}\n\n\t/**\n\t * Set auto-retry enabled/disabled.\n\t */\n\tasync setAutoRetry(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_retry\", enabled });\n\t}\n\n\t/**\n\t * Abort in-progress retry.\n\t */\n\tasync abortRetry(): Promise<void> {\n\t\tawait this.send({ type: \"abort_retry\" });\n\t}\n\n\t/**\n\t * Execute a bash command.\n\t */\n\tasync bash(command: string): Promise<BashResult> {\n\t\tconst response = await this.send({ type: \"bash\", command });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Abort running bash command.\n\t */\n\tasync abortBash(): Promise<void> {\n\t\tawait this.send({ type: \"abort_bash\" });\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tasync getSessionStats(): Promise<SessionStats> {\n\t\tconst response = await this.send({ type: \"get_session_stats\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t */\n\tasync exportHtml(outputPath?: string): Promise<{ path: string }> {\n\t\tconst response = await this.send({ type: \"export_html\", outputPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Switch to a different session file.\n\t * @returns Object with `cancelled: true` if an extension cancelled the switch\n\t */\n\tasync switchSession(sessionPath: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"switch_session\", sessionPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Fork from a specific message.\n\t * @returns Object with `text` (the message text) and `cancelled` (if extension cancelled)\n\t */\n\tasync fork(entryId: string): Promise<{ text: string; cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"fork\", entryId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Clone the current active branch into a new session.\n\t * @returns Object with `cancelled: true` if an extension cancelled the clone\n\t */\n\tasync clone(): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"clone\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get messages available for forking.\n\t */\n\tasync getForkMessages(): Promise<Array<{ entryId: string; text: string }>> {\n\t\tconst response = await this.send({ type: \"get_fork_messages\" });\n\t\treturn this.getData<{ messages: Array<{ entryId: string; text: string }> }>(response).messages;\n\t}\n\n\t/**\n\t * Get text of last assistant message.\n\t */\n\tasync getLastAssistantText(): Promise<string | null> {\n\t\tconst response = await this.send({ type: \"get_last_assistant_text\" });\n\t\treturn this.getData<{ text: string | null }>(response).text;\n\t}\n\n\t/**\n\t * Set the session display name.\n\t */\n\tasync setSessionName(name: string): Promise<void> {\n\t\tawait this.send({ type: \"set_session_name\", name });\n\t}\n\n\t/**\n\t * Get all messages in the session.\n\t */\n\tasync getMessages(): Promise<AgentMessage[]> {\n\t\tconst response = await this.send({ type: \"get_messages\" });\n\t\treturn this.getData<{ messages: AgentMessage[] }>(response).messages;\n\t}\n\n\t/**\n\t * Get available commands (extension commands, prompt templates, skills).\n\t */\n\tasync getCommands(): Promise<RpcSlashCommand[]> {\n\t\tconst response = await this.send({ type: \"get_commands\" });\n\t\treturn this.getData<{ commands: RpcSlashCommand[] }>(response).commands;\n\t}\n\n\t// =========================================================================\n\t// Helpers\n\t// =========================================================================\n\n\t/**\n\t * Wait for agent to become idle (no streaming).\n\t * Resolves when agent_end event is received.\n\t */\n\twaitForIdle(timeout = 60000): Promise<void> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout waiting for agent to become idle. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Collect events until agent becomes idle.\n\t */\n\tcollectEvents(timeout = 60000): Promise<AgentEvent[]> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst events: AgentEvent[] = [];\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout collecting events. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tevents.push(event);\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve(events);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Send prompt and wait for completion, returning all events.\n\t */\n\tasync promptAndWait(message: string, images?: ImageContent[], timeout = 60000): Promise<AgentEvent[]> {\n\t\tconst eventsPromise = this.collectEvents(timeout);\n\t\tawait this.prompt(message, images);\n\t\treturn eventsPromise;\n\t}\n\n\t// =========================================================================\n\t// Internal\n\t// =========================================================================\n\n\tprivate handleLine(line: string): void {\n\t\ttry {\n\t\t\tconst data = JSON.parse(line);\n\n\t\t\t// Check if it's a response to a pending request\n\t\t\tif (data.type === \"response\" && data.id && this.pendingRequests.has(data.id)) {\n\t\t\t\tconst pending = this.pendingRequests.get(data.id)!;\n\t\t\t\tthis.pendingRequests.delete(data.id);\n\t\t\t\tpending.resolve(data as RpcResponse);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Otherwise it's an event\n\t\t\tfor (const listener of this.eventListeners) {\n\t\t\t\tlistener(data as AgentEvent);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore non-JSON lines\n\t\t}\n\t}\n\n\tprivate createProcessExitError(code: number | null, signal: NodeJS.Signals | null): Error {\n\t\treturn new Error(`Agent process exited (code=${code} signal=${signal}). Stderr: ${this.stderr}`);\n\t}\n\n\tprivate rejectPendingRequests(error: Error): void {\n\t\tfor (const pending of this.pendingRequests.values()) {\n\t\t\tpending.reject(error);\n\t\t}\n\t\tthis.pendingRequests.clear();\n\t}\n\n\tprivate async send(command: RpcCommandBody): Promise<RpcResponse> {\n\t\tconst childProcess = this.process;\n\t\tconst stdin = childProcess?.stdin;\n\t\tif (!childProcess || !stdin) {\n\t\t\tthrow new Error(\"Client not started\");\n\t\t}\n\t\tif (this.exitError) {\n\t\t\tthrow this.exitError;\n\t\t}\n\t\tif (childProcess.exitCode !== null) {\n\t\t\tconst error = this.createProcessExitError(childProcess.exitCode, childProcess.signalCode);\n\t\t\tthis.exitError = error;\n\t\t\tthrow error;\n\t\t}\n\t\tif (stdin.destroyed || !stdin.writable) {\n\t\t\tconst error = new Error(`Agent process stdin is not writable. Stderr: ${this.stderr}`);\n\t\t\tthis.exitError = error;\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst id = `req_${++this.requestId}`;\n\t\tconst fullCommand = { ...command, id } as RpcCommand;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.pendingRequests.delete(id);\n\t\t\t\treject(new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.stderr}`));\n\t\t\t}, 30000);\n\n\t\t\tthis.pendingRequests.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve(response);\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\treject(error);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tstdin.write(serializeJsonLine(fullCommand));\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst writeError = error instanceof Error ? error : new Error(String(error));\n\t\t\t\tconst pending = this.pendingRequests.get(id);\n\t\t\t\tthis.pendingRequests.delete(id);\n\t\t\t\tpending?.reject(writeError);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate getData<T>(response: RpcResponse): T {\n\t\tif (!response.success) {\n\t\t\tconst errorResponse = response as Extract<RpcResponse, { success: false }>;\n\t\t\tthrow new Error(errorResponse.error);\n\t\t}\n\t\t// Type assertion: we trust response.data matches T based on the command sent.\n\t\t// This is safe because each public method specifies the correct T for its command.\n\t\tconst successResponse = response as Extract<RpcResponse, { success: true; data: unknown }>;\n\t\treturn successResponse.data as T;\n\t}\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;AAqB/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,CAksBjF","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 { takeOverStdout, writeRawStdout } from \"../../core/output-guard.ts\";\nimport { killTrackedDetachedChildren } from \"../../utils/shell.ts\";\nimport { type Theme, theme } from \"../interactive/theme/theme.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\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\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.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\tunsubscribe = session.subscribe((event) => {\n\t\t\toutput(event);\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);\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 \"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 = await session.modelRegistry.getAvailable();\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 = await session.modelRegistry.getAvailable();\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\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 result = await session.executeBash(command.command);\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_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:${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(undefined, 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): 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\tawait runtimeHost.dispose();\n\t\tdetachInput();\n\t\tprocess.stdin.pause();\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\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}\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}\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;AA0B/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,CA8sBjF","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 { killTrackedDetachedChildren } from \"../../utils/shell.ts\";\nimport { type Theme, theme } from \"../interactive/theme/theme.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\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.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(event);\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 \"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 = await session.modelRegistry.getAvailable();\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 = await session.modelRegistry.getAvailable();\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\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 result = await session.executeBash(command.command);\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_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:${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(undefined, 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"]}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
12
|
*/
|
|
13
13
|
import * as crypto from "node:crypto";
|
|
14
|
-
import { takeOverStdout, writeRawStdout } from "../../core/output-guard.js";
|
|
14
|
+
import { flushRawStdout, takeOverStdout, waitForRawStdoutBackpressure, writeRawStdout, } from "../../core/output-guard.js";
|
|
15
15
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
16
16
|
import { theme } from "../interactive/theme/theme.js";
|
|
17
17
|
import { attachJsonlLineReader, serializeJsonLine } from "./jsonl.js";
|
|
@@ -23,6 +23,7 @@ export async function runRpcMode(runtimeHost) {
|
|
|
23
23
|
takeOverStdout();
|
|
24
24
|
let session = runtimeHost.session;
|
|
25
25
|
let unsubscribe;
|
|
26
|
+
let unsubscribeBackpressure;
|
|
26
27
|
const output = (obj) => {
|
|
27
28
|
writeRawStdout(serializeJsonLine(obj));
|
|
28
29
|
};
|
|
@@ -258,9 +259,13 @@ export async function runRpcMode(runtimeHost) {
|
|
|
258
259
|
},
|
|
259
260
|
});
|
|
260
261
|
unsubscribe?.();
|
|
262
|
+
unsubscribeBackpressure?.();
|
|
261
263
|
unsubscribe = session.subscribe((event) => {
|
|
262
264
|
output(event);
|
|
263
265
|
});
|
|
266
|
+
unsubscribeBackpressure = session.agent.subscribe(async () => {
|
|
267
|
+
await waitForRawStdoutBackpressure();
|
|
268
|
+
});
|
|
264
269
|
};
|
|
265
270
|
const registerSignalHandlers = () => {
|
|
266
271
|
const signals = ["SIGTERM"];
|
|
@@ -270,7 +275,7 @@ export async function runRpcMode(runtimeHost) {
|
|
|
270
275
|
for (const signal of signals) {
|
|
271
276
|
const handler = () => {
|
|
272
277
|
killTrackedDetachedChildren();
|
|
273
|
-
void shutdown(signal === "SIGHUP" ? 129 : 143);
|
|
278
|
+
void shutdown(signal === "SIGHUP" ? 129 : 143, signal);
|
|
274
279
|
};
|
|
275
280
|
process.on(signal, handler);
|
|
276
281
|
signalCleanupHandlers.push(() => process.off(signal, handler));
|
|
@@ -529,7 +534,7 @@ export async function runRpcMode(runtimeHost) {
|
|
|
529
534
|
* Called after handling each command when waiting for the next command.
|
|
530
535
|
*/
|
|
531
536
|
let detachInput = () => { };
|
|
532
|
-
async function shutdown(exitCode = 0) {
|
|
537
|
+
async function shutdown(exitCode = 0, signal) {
|
|
533
538
|
if (shuttingDown) {
|
|
534
539
|
process.exit(exitCode);
|
|
535
540
|
}
|
|
@@ -538,9 +543,13 @@ export async function runRpcMode(runtimeHost) {
|
|
|
538
543
|
cleanup();
|
|
539
544
|
}
|
|
540
545
|
unsubscribe?.();
|
|
546
|
+
unsubscribeBackpressure?.();
|
|
541
547
|
await runtimeHost.dispose();
|
|
542
548
|
detachInput();
|
|
543
549
|
process.stdin.pause();
|
|
550
|
+
if (signal !== "SIGTERM") {
|
|
551
|
+
await flushRawStdout();
|
|
552
|
+
}
|
|
544
553
|
process.exit(exitCode);
|
|
545
554
|
}
|
|
546
555
|
async function checkShutdownRequested() {
|
|
@@ -555,6 +564,7 @@ export async function runRpcMode(runtimeHost) {
|
|
|
555
564
|
}
|
|
556
565
|
catch (parseError) {
|
|
557
566
|
output(error(undefined, "parse", `Failed to parse command: ${parseError instanceof Error ? parseError.message : String(parseError)}`));
|
|
567
|
+
await waitForRawStdoutBackpressure();
|
|
558
568
|
return;
|
|
559
569
|
}
|
|
560
570
|
// Handle extension UI responses
|
|
@@ -575,11 +585,13 @@ export async function runRpcMode(runtimeHost) {
|
|
|
575
585
|
const response = await handleCommand(command);
|
|
576
586
|
if (response) {
|
|
577
587
|
output(response);
|
|
588
|
+
await waitForRawStdoutBackpressure();
|
|
578
589
|
}
|
|
579
590
|
await checkShutdownRequested();
|
|
580
591
|
}
|
|
581
592
|
catch (commandError) {
|
|
582
593
|
output(error(command.id, command.type, commandError instanceof Error ? commandError.message : String(commandError)));
|
|
594
|
+
await waitForRawStdoutBackpressure();
|
|
583
595
|
}
|
|
584
596
|
};
|
|
585
597
|
const onInputEnd = () => {
|