@mariozechner/pi-coding-agent 0.27.5 → 0.27.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +49 -30
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction.d.ts +3 -0
- package/dist/core/compaction.d.ts.map +1 -1
- package/dist/core/compaction.js +10 -1
- package/dist/core/compaction.js.map +1 -1
- package/dist/core/hooks/runner.d.ts.map +1 -1
- package/dist/core/hooks/runner.js +11 -3
- package/dist/core/hooks/runner.js.map +1 -1
- package/dist/core/hooks/types.d.ts +9 -1
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/model-config.d.ts +7 -2
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +7 -2
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +24 -11
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +25 -21
- package/dist/core/session-manager.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +5 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +1 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/hooks.md +109 -48
- package/examples/hooks/README.md +3 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"print-mode.d.ts","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CACjC,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,CAAC,EAAE,MAAM,EACvB,kBAAkB,CAAC,EAAE,UAAU,EAAE,GAC/B,OAAO,CAAC,IAAI,CAAC,CA4Ff","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { Attachment } from \"@mariozechner/pi-agent-core\";\nimport type { AssistantMessage } from \"@mariozechner/pi-ai\";\nimport type { AgentSession } from \"../core/agent-session.js\";\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n *\n * @param session The agent session\n * @param mode Output mode: \"text\" for final response only, \"json\" for all events\n * @param messages Array of prompts to send\n * @param initialMessage Optional first message (may contain @file content)\n * @param initialAttachments Optional attachments for the initial message\n */\nexport async function runPrintMode(\n\tsession: AgentSession,\n\tmode: \"text\" | \"json\",\n\tmessages: string[],\n\tinitialMessage?: string,\n\tinitialAttachments?: Attachment[],\n): Promise<void> {\n\t// Load entries once for session start events\n\tconst entries = session.sessionManager.
|
|
1
|
+
{"version":3,"file":"print-mode.d.ts","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CACjC,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,CAAC,EAAE,MAAM,EACvB,kBAAkB,CAAC,EAAE,UAAU,EAAE,GAC/B,OAAO,CAAC,IAAI,CAAC,CA4Ff","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { Attachment } from \"@mariozechner/pi-agent-core\";\nimport type { AssistantMessage } from \"@mariozechner/pi-ai\";\nimport type { AgentSession } from \"../core/agent-session.js\";\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n *\n * @param session The agent session\n * @param mode Output mode: \"text\" for final response only, \"json\" for all events\n * @param messages Array of prompts to send\n * @param initialMessage Optional first message (may contain @file content)\n * @param initialAttachments Optional attachments for the initial message\n */\nexport async function runPrintMode(\n\tsession: AgentSession,\n\tmode: \"text\" | \"json\",\n\tmessages: string[],\n\tinitialMessage?: string,\n\tinitialAttachments?: Attachment[],\n): Promise<void> {\n\t// Load entries once for session start events\n\tconst entries = session.sessionManager.getEntries();\n\n\t// Hook runner already has no-op UI context by default (set in main.ts)\n\t// Set up hooks for print mode (no UI)\n\tconst hookRunner = session.hookRunner;\n\tif (hookRunner) {\n\t\t// Use actual session file if configured (via --session), otherwise null\n\t\thookRunner.setSessionFile(session.sessionFile);\n\t\thookRunner.onError((err) => {\n\t\t\tconsole.error(`Hook error (${err.hookPath}): ${err.error}`);\n\t\t});\n\t\t// No-op send handler for print mode (single-shot, no async messages)\n\t\thookRunner.setSendHandler(() => {\n\t\t\tconsole.error(\"Warning: pi.send() is not supported in print mode\");\n\t\t});\n\t\t// Emit session event\n\t\tawait hookRunner.emit({\n\t\t\ttype: \"session\",\n\t\t\tentries,\n\t\t\tsessionFile: session.sessionFile,\n\t\t\tpreviousSessionFile: null,\n\t\t\treason: \"start\",\n\t\t});\n\t}\n\n\t// Emit session start event to custom tools (no UI in print mode)\n\tfor (const { tool } of session.customTools) {\n\t\tif (tool.onSession) {\n\t\t\ttry {\n\t\t\t\tawait tool.onSession({\n\t\t\t\t\tentries,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tpreviousSessionFile: null,\n\t\t\t\t\treason: \"start\",\n\t\t\t\t});\n\t\t\t} catch (_err) {\n\t\t\t\t// Silently ignore tool errors\n\t\t\t}\n\t\t}\n\t}\n\n\t// Always subscribe to enable session persistence via _handleAgentEvent\n\tsession.subscribe((event) => {\n\t\t// In JSON mode, output all events\n\t\tif (mode === \"json\") {\n\t\t\tconsole.log(JSON.stringify(event));\n\t\t}\n\t});\n\n\t// Send initial message with attachments\n\tif (initialMessage) {\n\t\tawait session.prompt(initialMessage, { attachments: initialAttachments });\n\t}\n\n\t// Send remaining messages\n\tfor (const message of messages) {\n\t\tawait session.prompt(message);\n\t}\n\n\t// In text mode, output final response\n\tif (mode === \"text\") {\n\t\tconst state = session.state;\n\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\n\t\t\t// Check for error/aborted\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Output text content\n\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\tconsole.log(content.text);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ensure stdout is fully flushed before returning\n\t// This prevents race conditions where the process exits before all output is written\n\tawait new Promise<void>((resolve, reject) => {\n\t\tprocess.stdout.write(\"\", (err) => {\n\t\t\tif (err) reject(err);\n\t\t\telse resolve();\n\t\t});\n\t});\n}\n"]}
|
package/dist/modes/print-mode.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export async function runPrintMode(session, mode, messages, initialMessage, initialAttachments) {
|
|
19
19
|
// Load entries once for session start events
|
|
20
|
-
const entries = session.sessionManager.
|
|
20
|
+
const entries = session.sessionManager.getEntries();
|
|
21
21
|
// Hook runner already has no-op UI context by default (set in main.ts)
|
|
22
22
|
// Set up hooks for print mode (no UI)
|
|
23
23
|
const hookRunner = session.hookRunner;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"print-mode.js","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,OAAqB,EACrB,IAAqB,EACrB,QAAkB,EAClB,cAAuB,EACvB,kBAAiC,EACjB;IAChB,6CAA6C;IAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"print-mode.js","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { Attachment } from \"@mariozechner/pi-agent-core\";\nimport type { AssistantMessage } from \"@mariozechner/pi-ai\";\nimport type { AgentSession } from \"../core/agent-session.js\";\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n *\n * @param session The agent session\n * @param mode Output mode: \"text\" for final response only, \"json\" for all events\n * @param messages Array of prompts to send\n * @param initialMessage Optional first message (may contain @file content)\n * @param initialAttachments Optional attachments for the initial message\n */\nexport async function runPrintMode(\n\tsession: AgentSession,\n\tmode: \"text\" | \"json\",\n\tmessages: string[],\n\tinitialMessage?: string,\n\tinitialAttachments?: Attachment[],\n): Promise<void> {\n\t// Load entries once for session start events\n\tconst entries = session.sessionManager.getEntries();\n\n\t// Hook runner already has no-op UI context by default (set in main.ts)\n\t// Set up hooks for print mode (no UI)\n\tconst hookRunner = session.hookRunner;\n\tif (hookRunner) {\n\t\t// Use actual session file if configured (via --session), otherwise null\n\t\thookRunner.setSessionFile(session.sessionFile);\n\t\thookRunner.onError((err) => {\n\t\t\tconsole.error(`Hook error (${err.hookPath}): ${err.error}`);\n\t\t});\n\t\t// No-op send handler for print mode (single-shot, no async messages)\n\t\thookRunner.setSendHandler(() => {\n\t\t\tconsole.error(\"Warning: pi.send() is not supported in print mode\");\n\t\t});\n\t\t// Emit session event\n\t\tawait hookRunner.emit({\n\t\t\ttype: \"session\",\n\t\t\tentries,\n\t\t\tsessionFile: session.sessionFile,\n\t\t\tpreviousSessionFile: null,\n\t\t\treason: \"start\",\n\t\t});\n\t}\n\n\t// Emit session start event to custom tools (no UI in print mode)\n\tfor (const { tool } of session.customTools) {\n\t\tif (tool.onSession) {\n\t\t\ttry {\n\t\t\t\tawait tool.onSession({\n\t\t\t\t\tentries,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tpreviousSessionFile: null,\n\t\t\t\t\treason: \"start\",\n\t\t\t\t});\n\t\t\t} catch (_err) {\n\t\t\t\t// Silently ignore tool errors\n\t\t\t}\n\t\t}\n\t}\n\n\t// Always subscribe to enable session persistence via _handleAgentEvent\n\tsession.subscribe((event) => {\n\t\t// In JSON mode, output all events\n\t\tif (mode === \"json\") {\n\t\t\tconsole.log(JSON.stringify(event));\n\t\t}\n\t});\n\n\t// Send initial message with attachments\n\tif (initialMessage) {\n\t\tawait session.prompt(initialMessage, { attachments: initialAttachments });\n\t}\n\n\t// Send remaining messages\n\tfor (const message of messages) {\n\t\tawait session.prompt(message);\n\t}\n\n\t// In text mode, output final response\n\tif (mode === \"text\") {\n\t\tconst state = session.state;\n\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\n\t\t\t// Check for error/aborted\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Output text content\n\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\tconsole.log(content.text);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ensure stdout is fully flushed before returning\n\t// This prevents race conditions where the process exits before all output is written\n\tawait new Promise<void>((resolve, reject) => {\n\t\tprocess.stdout.write(\"\", (err) => {\n\t\t\tif (err) reject(err);\n\t\t\telse resolve();\n\t\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;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpH;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,CA8XtE","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 * - Hook UI: Hook UI requests are emitted, client responds with hook_ui_response\n */\n\nimport * as crypto from \"node:crypto\";\nimport * as readline from \"readline\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport type { HookUIContext } from \"../../core/hooks/index.js\";\nimport type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from \"./rpc-types.js\";\n\n// Re-export types for consumers\nexport type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from \"./rpc-types.js\";\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(session: AgentSession): Promise<never> {\n\tconst output = (obj: RpcResponse | RpcHookUIRequest | object) => {\n\t\tconsole.log(JSON.stringify(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 hook UI requests waiting for response\n\tconst pendingHookRequests = new Map<string, { resolve: (value: any) => void; reject: (error: Error) => void }>();\n\n\t/**\n\t * Create a hook UI context that uses the RPC protocol.\n\t */\n\tconst createHookUIContext = (): HookUIContext => ({\n\t\tasync select(title: string, options: string[]): Promise<string | null> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(null);\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(null);\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: \"hook_ui_request\", id, method: \"select\", title, options } as RpcHookUIRequest);\n\t\t\t});\n\t\t},\n\n\t\tasync confirm(title: string, message: string): Promise<boolean> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\t} else if (\"confirmed\" in response) {\n\t\t\t\t\t\t\tresolve(response.confirmed);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve(false);\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: \"hook_ui_request\", id, method: \"confirm\", title, message } as RpcHookUIRequest);\n\t\t\t});\n\t\t},\n\n\t\tasync input(title: string, placeholder?: string): Promise<string | null> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(null);\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(null);\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: \"hook_ui_request\", id, method: \"input\", title, placeholder } as RpcHookUIRequest);\n\t\t\t});\n\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: \"hook_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 RpcHookUIRequest);\n\t\t},\n\t});\n\n\t// Load entries once for session start events\n\tconst entries = session.sessionManager.loadEntries();\n\n\t// Set up hooks with RPC-based UI context\n\tconst hookRunner = session.hookRunner;\n\tif (hookRunner) {\n\t\thookRunner.setUIContext(createHookUIContext(), false);\n\t\thookRunner.setSessionFile(session.sessionFile);\n\t\thookRunner.onError((err) => {\n\t\t\toutput({ type: \"hook_error\", hookPath: err.hookPath, event: err.event, error: err.error });\n\t\t});\n\t\t// Set up send handler for pi.send()\n\t\thookRunner.setSendHandler((text, attachments) => {\n\t\t\t// In RPC mode, just queue or prompt based on streaming state\n\t\t\tif (session.isStreaming) {\n\t\t\t\tsession.queueMessage(text);\n\t\t\t} else {\n\t\t\t\tsession.prompt(text, { attachments }).catch((e) => {\n\t\t\t\t\toutput(error(undefined, \"hook_send\", e.message));\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t\t// Emit session event\n\t\tawait hookRunner.emit({\n\t\t\ttype: \"session\",\n\t\t\tentries,\n\t\t\tsessionFile: session.sessionFile,\n\t\t\tpreviousSessionFile: null,\n\t\t\treason: \"start\",\n\t\t});\n\t}\n\n\t// Emit session start event to custom tools\n\t// Note: Tools get no-op UI context in RPC mode (host handles UI via protocol)\n\tfor (const { tool } of session.customTools) {\n\t\tif (tool.onSession) {\n\t\t\ttry {\n\t\t\t\tawait tool.onSession({\n\t\t\t\t\tentries,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tpreviousSessionFile: null,\n\t\t\t\t\treason: \"start\",\n\t\t\t\t});\n\t\t\t} catch (_err) {\n\t\t\t\t// Silently ignore tool errors\n\t\t\t}\n\t\t}\n\t}\n\n\t// Output all agent events as JSON\n\tsession.subscribe((event) => {\n\t\toutput(event);\n\t});\n\n\t// Handle a single command\n\tconst handleCommand = async (command: RpcCommand): Promise<RpcResponse> => {\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// Don't await - events will stream\n\t\t\t\tsession\n\t\t\t\t\t.prompt(command.message, {\n\t\t\t\t\t\tattachments: command.attachments,\n\t\t\t\t\t\texpandSlashCommands: false,\n\t\t\t\t\t})\n\t\t\t\t\t.catch((e) => output(error(id, \"prompt\", e.message)));\n\t\t\t\treturn success(id, \"prompt\");\n\t\t\t}\n\n\t\t\tcase \"queue_message\": {\n\t\t\t\tawait session.queueMessage(command.message);\n\t\t\t\treturn success(id, \"queue_message\");\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 \"reset\": {\n\t\t\t\tconst cancelled = !(await session.reset());\n\t\t\t\treturn success(id, \"reset\", { cancelled });\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\tqueueMode: session.queueMode,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tsessionId: session.sessionId,\n\t\t\t\t\tautoCompactionEnabled: session.autoCompactionEnabled,\n\t\t\t\t\tmessageCount: session.messages.length,\n\t\t\t\t\tqueuedMessageCount: session.queuedMessageCount,\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.getAvailableModels();\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.getAvailableModels();\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 Mode\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_queue_mode\": {\n\t\t\t\tsession.setQueueMode(command.mode);\n\t\t\t\treturn success(id, \"set_queue_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 = 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 cancelled = !(await session.switchSession(command.sessionPath));\n\t\t\t\treturn success(id, \"switch_session\", { cancelled });\n\t\t\t}\n\n\t\t\tcase \"branch\": {\n\t\t\t\tconst result = await session.branch(command.entryIndex);\n\t\t\t\treturn success(id, \"branch\", { text: result.selectedText, cancelled: result.cancelled });\n\t\t\t}\n\n\t\t\tcase \"get_branch_messages\": {\n\t\t\t\tconst messages = session.getUserMessagesForBranching();\n\t\t\t\treturn success(id, \"get_branch_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\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\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// Listen for JSON input\n\tconst rl = readline.createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t\tterminal: false,\n\t});\n\n\trl.on(\"line\", async (line: string) => {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(line);\n\n\t\t\t// Handle hook UI responses\n\t\t\tif (parsed.type === \"hook_ui_response\") {\n\t\t\t\tconst response = parsed as RpcHookUIResponse;\n\t\t\t\tconst pending = pendingHookRequests.get(response.id);\n\t\t\t\tif (pending) {\n\t\t\t\t\tpendingHookRequests.delete(response.id);\n\t\t\t\t\tpending.resolve(response);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle regular commands\n\t\t\tconst command = parsed as RpcCommand;\n\t\t\tconst response = await handleCommand(command);\n\t\t\toutput(response);\n\t\t} catch (e: any) {\n\t\t\toutput(error(undefined, \"parse\", `Failed to parse command: ${e.message}`));\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;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpH;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,CA8XtE","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 * - Hook UI: Hook UI requests are emitted, client responds with hook_ui_response\n */\n\nimport * as crypto from \"node:crypto\";\nimport * as readline from \"readline\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport type { HookUIContext } from \"../../core/hooks/index.js\";\nimport type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from \"./rpc-types.js\";\n\n// Re-export types for consumers\nexport type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from \"./rpc-types.js\";\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(session: AgentSession): Promise<never> {\n\tconst output = (obj: RpcResponse | RpcHookUIRequest | object) => {\n\t\tconsole.log(JSON.stringify(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 hook UI requests waiting for response\n\tconst pendingHookRequests = new Map<string, { resolve: (value: any) => void; reject: (error: Error) => void }>();\n\n\t/**\n\t * Create a hook UI context that uses the RPC protocol.\n\t */\n\tconst createHookUIContext = (): HookUIContext => ({\n\t\tasync select(title: string, options: string[]): Promise<string | null> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(null);\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(null);\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: \"hook_ui_request\", id, method: \"select\", title, options } as RpcHookUIRequest);\n\t\t\t});\n\t\t},\n\n\t\tasync confirm(title: string, message: string): Promise<boolean> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\t} else if (\"confirmed\" in response) {\n\t\t\t\t\t\t\tresolve(response.confirmed);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve(false);\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: \"hook_ui_request\", id, method: \"confirm\", title, message } as RpcHookUIRequest);\n\t\t\t});\n\t\t},\n\n\t\tasync input(title: string, placeholder?: string): Promise<string | null> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(null);\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(null);\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: \"hook_ui_request\", id, method: \"input\", title, placeholder } as RpcHookUIRequest);\n\t\t\t});\n\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: \"hook_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 RpcHookUIRequest);\n\t\t},\n\t});\n\n\t// Load entries once for session start events\n\tconst entries = session.sessionManager.getEntries();\n\n\t// Set up hooks with RPC-based UI context\n\tconst hookRunner = session.hookRunner;\n\tif (hookRunner) {\n\t\thookRunner.setUIContext(createHookUIContext(), false);\n\t\thookRunner.setSessionFile(session.sessionFile);\n\t\thookRunner.onError((err) => {\n\t\t\toutput({ type: \"hook_error\", hookPath: err.hookPath, event: err.event, error: err.error });\n\t\t});\n\t\t// Set up send handler for pi.send()\n\t\thookRunner.setSendHandler((text, attachments) => {\n\t\t\t// In RPC mode, just queue or prompt based on streaming state\n\t\t\tif (session.isStreaming) {\n\t\t\t\tsession.queueMessage(text);\n\t\t\t} else {\n\t\t\t\tsession.prompt(text, { attachments }).catch((e) => {\n\t\t\t\t\toutput(error(undefined, \"hook_send\", e.message));\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t\t// Emit session event\n\t\tawait hookRunner.emit({\n\t\t\ttype: \"session\",\n\t\t\tentries,\n\t\t\tsessionFile: session.sessionFile,\n\t\t\tpreviousSessionFile: null,\n\t\t\treason: \"start\",\n\t\t});\n\t}\n\n\t// Emit session start event to custom tools\n\t// Note: Tools get no-op UI context in RPC mode (host handles UI via protocol)\n\tfor (const { tool } of session.customTools) {\n\t\tif (tool.onSession) {\n\t\t\ttry {\n\t\t\t\tawait tool.onSession({\n\t\t\t\t\tentries,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tpreviousSessionFile: null,\n\t\t\t\t\treason: \"start\",\n\t\t\t\t});\n\t\t\t} catch (_err) {\n\t\t\t\t// Silently ignore tool errors\n\t\t\t}\n\t\t}\n\t}\n\n\t// Output all agent events as JSON\n\tsession.subscribe((event) => {\n\t\toutput(event);\n\t});\n\n\t// Handle a single command\n\tconst handleCommand = async (command: RpcCommand): Promise<RpcResponse> => {\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// Don't await - events will stream\n\t\t\t\tsession\n\t\t\t\t\t.prompt(command.message, {\n\t\t\t\t\t\tattachments: command.attachments,\n\t\t\t\t\t\texpandSlashCommands: false,\n\t\t\t\t\t})\n\t\t\t\t\t.catch((e) => output(error(id, \"prompt\", e.message)));\n\t\t\t\treturn success(id, \"prompt\");\n\t\t\t}\n\n\t\t\tcase \"queue_message\": {\n\t\t\t\tawait session.queueMessage(command.message);\n\t\t\t\treturn success(id, \"queue_message\");\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 \"reset\": {\n\t\t\t\tconst cancelled = !(await session.reset());\n\t\t\t\treturn success(id, \"reset\", { cancelled });\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\tqueueMode: session.queueMode,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tsessionId: session.sessionId,\n\t\t\t\t\tautoCompactionEnabled: session.autoCompactionEnabled,\n\t\t\t\t\tmessageCount: session.messages.length,\n\t\t\t\t\tqueuedMessageCount: session.queuedMessageCount,\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.getAvailableModels();\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.getAvailableModels();\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 Mode\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_queue_mode\": {\n\t\t\t\tsession.setQueueMode(command.mode);\n\t\t\t\treturn success(id, \"set_queue_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 = 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 cancelled = !(await session.switchSession(command.sessionPath));\n\t\t\t\treturn success(id, \"switch_session\", { cancelled });\n\t\t\t}\n\n\t\t\tcase \"branch\": {\n\t\t\t\tconst result = await session.branch(command.entryIndex);\n\t\t\t\treturn success(id, \"branch\", { text: result.selectedText, cancelled: result.cancelled });\n\t\t\t}\n\n\t\t\tcase \"get_branch_messages\": {\n\t\t\t\tconst messages = session.getUserMessagesForBranching();\n\t\t\t\treturn success(id, \"get_branch_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\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\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// Listen for JSON input\n\tconst rl = readline.createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t\tterminal: false,\n\t});\n\n\trl.on(\"line\", async (line: string) => {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(line);\n\n\t\t\t// Handle hook UI responses\n\t\t\tif (parsed.type === \"hook_ui_response\") {\n\t\t\t\tconst response = parsed as RpcHookUIResponse;\n\t\t\t\tconst pending = pendingHookRequests.get(response.id);\n\t\t\t\tif (pending) {\n\t\t\t\t\tpendingHookRequests.delete(response.id);\n\t\t\t\t\tpending.resolve(response);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle regular commands\n\t\t\tconst command = parsed as RpcCommand;\n\t\t\tconst response = await handleCommand(command);\n\t\t\toutput(response);\n\t\t} catch (e: any) {\n\t\t\toutput(error(undefined, \"parse\", `Failed to parse command: ${e.message}`));\n\t\t}\n\t});\n\n\t// Keep process alive forever\n\treturn new Promise(() => {});\n}\n"]}
|
|
@@ -107,7 +107,7 @@ export async function runRpcMode(session) {
|
|
|
107
107
|
},
|
|
108
108
|
});
|
|
109
109
|
// Load entries once for session start events
|
|
110
|
-
const entries = session.sessionManager.
|
|
110
|
+
const entries = session.sessionManager.getEntries();
|
|
111
111
|
// Set up hooks with RPC-based UI context
|
|
112
112
|
const hookRunner = session.hookRunner;
|
|
113
113
|
if (hookRunner) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-mode.js","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-mode.ts"],"names":[],"mappings":"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","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 * - Hook UI: Hook UI requests are emitted, client responds with hook_ui_response\n */\n\nimport * as crypto from \"node:crypto\";\nimport * as readline from \"readline\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport type { HookUIContext } from \"../../core/hooks/index.js\";\nimport type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from \"./rpc-types.js\";\n\n// Re-export types for consumers\nexport type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from \"./rpc-types.js\";\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(session: AgentSession): Promise<never> {\n\tconst output = (obj: RpcResponse | RpcHookUIRequest | object) => {\n\t\tconsole.log(JSON.stringify(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 hook UI requests waiting for response\n\tconst pendingHookRequests = new Map<string, { resolve: (value: any) => void; reject: (error: Error) => void }>();\n\n\t/**\n\t * Create a hook UI context that uses the RPC protocol.\n\t */\n\tconst createHookUIContext = (): HookUIContext => ({\n\t\tasync select(title: string, options: string[]): Promise<string | null> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(null);\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(null);\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: \"hook_ui_request\", id, method: \"select\", title, options } as RpcHookUIRequest);\n\t\t\t});\n\t\t},\n\n\t\tasync confirm(title: string, message: string): Promise<boolean> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\t} else if (\"confirmed\" in response) {\n\t\t\t\t\t\t\tresolve(response.confirmed);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve(false);\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: \"hook_ui_request\", id, method: \"confirm\", title, message } as RpcHookUIRequest);\n\t\t\t});\n\t\t},\n\n\t\tasync input(title: string, placeholder?: string): Promise<string | null> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(null);\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(null);\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: \"hook_ui_request\", id, method: \"input\", title, placeholder } as RpcHookUIRequest);\n\t\t\t});\n\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: \"hook_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 RpcHookUIRequest);\n\t\t},\n\t});\n\n\t// Load entries once for session start events\n\tconst entries = session.sessionManager.loadEntries();\n\n\t// Set up hooks with RPC-based UI context\n\tconst hookRunner = session.hookRunner;\n\tif (hookRunner) {\n\t\thookRunner.setUIContext(createHookUIContext(), false);\n\t\thookRunner.setSessionFile(session.sessionFile);\n\t\thookRunner.onError((err) => {\n\t\t\toutput({ type: \"hook_error\", hookPath: err.hookPath, event: err.event, error: err.error });\n\t\t});\n\t\t// Set up send handler for pi.send()\n\t\thookRunner.setSendHandler((text, attachments) => {\n\t\t\t// In RPC mode, just queue or prompt based on streaming state\n\t\t\tif (session.isStreaming) {\n\t\t\t\tsession.queueMessage(text);\n\t\t\t} else {\n\t\t\t\tsession.prompt(text, { attachments }).catch((e) => {\n\t\t\t\t\toutput(error(undefined, \"hook_send\", e.message));\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t\t// Emit session event\n\t\tawait hookRunner.emit({\n\t\t\ttype: \"session\",\n\t\t\tentries,\n\t\t\tsessionFile: session.sessionFile,\n\t\t\tpreviousSessionFile: null,\n\t\t\treason: \"start\",\n\t\t});\n\t}\n\n\t// Emit session start event to custom tools\n\t// Note: Tools get no-op UI context in RPC mode (host handles UI via protocol)\n\tfor (const { tool } of session.customTools) {\n\t\tif (tool.onSession) {\n\t\t\ttry {\n\t\t\t\tawait tool.onSession({\n\t\t\t\t\tentries,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tpreviousSessionFile: null,\n\t\t\t\t\treason: \"start\",\n\t\t\t\t});\n\t\t\t} catch (_err) {\n\t\t\t\t// Silently ignore tool errors\n\t\t\t}\n\t\t}\n\t}\n\n\t// Output all agent events as JSON\n\tsession.subscribe((event) => {\n\t\toutput(event);\n\t});\n\n\t// Handle a single command\n\tconst handleCommand = async (command: RpcCommand): Promise<RpcResponse> => {\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// Don't await - events will stream\n\t\t\t\tsession\n\t\t\t\t\t.prompt(command.message, {\n\t\t\t\t\t\tattachments: command.attachments,\n\t\t\t\t\t\texpandSlashCommands: false,\n\t\t\t\t\t})\n\t\t\t\t\t.catch((e) => output(error(id, \"prompt\", e.message)));\n\t\t\t\treturn success(id, \"prompt\");\n\t\t\t}\n\n\t\t\tcase \"queue_message\": {\n\t\t\t\tawait session.queueMessage(command.message);\n\t\t\t\treturn success(id, \"queue_message\");\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 \"reset\": {\n\t\t\t\tconst cancelled = !(await session.reset());\n\t\t\t\treturn success(id, \"reset\", { cancelled });\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\tqueueMode: session.queueMode,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tsessionId: session.sessionId,\n\t\t\t\t\tautoCompactionEnabled: session.autoCompactionEnabled,\n\t\t\t\t\tmessageCount: session.messages.length,\n\t\t\t\t\tqueuedMessageCount: session.queuedMessageCount,\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.getAvailableModels();\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.getAvailableModels();\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 Mode\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_queue_mode\": {\n\t\t\t\tsession.setQueueMode(command.mode);\n\t\t\t\treturn success(id, \"set_queue_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 = 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 cancelled = !(await session.switchSession(command.sessionPath));\n\t\t\t\treturn success(id, \"switch_session\", { cancelled });\n\t\t\t}\n\n\t\t\tcase \"branch\": {\n\t\t\t\tconst result = await session.branch(command.entryIndex);\n\t\t\t\treturn success(id, \"branch\", { text: result.selectedText, cancelled: result.cancelled });\n\t\t\t}\n\n\t\t\tcase \"get_branch_messages\": {\n\t\t\t\tconst messages = session.getUserMessagesForBranching();\n\t\t\t\treturn success(id, \"get_branch_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\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\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// Listen for JSON input\n\tconst rl = readline.createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t\tterminal: false,\n\t});\n\n\trl.on(\"line\", async (line: string) => {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(line);\n\n\t\t\t// Handle hook UI responses\n\t\t\tif (parsed.type === \"hook_ui_response\") {\n\t\t\t\tconst response = parsed as RpcHookUIResponse;\n\t\t\t\tconst pending = pendingHookRequests.get(response.id);\n\t\t\t\tif (pending) {\n\t\t\t\t\tpendingHookRequests.delete(response.id);\n\t\t\t\t\tpending.resolve(response);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle regular commands\n\t\t\tconst command = parsed as RpcCommand;\n\t\t\tconst response = await handleCommand(command);\n\t\t\toutput(response);\n\t\t} catch (e: any) {\n\t\t\toutput(error(undefined, \"parse\", `Failed to parse command: ${e.message}`));\n\t\t}\n\t});\n\n\t// Keep process alive forever\n\treturn new Promise(() => {});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rpc-mode.js","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-mode.ts"],"names":[],"mappings":"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","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 * - Hook UI: Hook UI requests are emitted, client responds with hook_ui_response\n */\n\nimport * as crypto from \"node:crypto\";\nimport * as readline from \"readline\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport type { HookUIContext } from \"../../core/hooks/index.js\";\nimport type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from \"./rpc-types.js\";\n\n// Re-export types for consumers\nexport type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from \"./rpc-types.js\";\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(session: AgentSession): Promise<never> {\n\tconst output = (obj: RpcResponse | RpcHookUIRequest | object) => {\n\t\tconsole.log(JSON.stringify(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 hook UI requests waiting for response\n\tconst pendingHookRequests = new Map<string, { resolve: (value: any) => void; reject: (error: Error) => void }>();\n\n\t/**\n\t * Create a hook UI context that uses the RPC protocol.\n\t */\n\tconst createHookUIContext = (): HookUIContext => ({\n\t\tasync select(title: string, options: string[]): Promise<string | null> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(null);\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(null);\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: \"hook_ui_request\", id, method: \"select\", title, options } as RpcHookUIRequest);\n\t\t\t});\n\t\t},\n\n\t\tasync confirm(title: string, message: string): Promise<boolean> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\t} else if (\"confirmed\" in response) {\n\t\t\t\t\t\t\tresolve(response.confirmed);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve(false);\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: \"hook_ui_request\", id, method: \"confirm\", title, message } as RpcHookUIRequest);\n\t\t\t});\n\t\t},\n\n\t\tasync input(title: string, placeholder?: string): Promise<string | null> {\n\t\t\tconst id = crypto.randomUUID();\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tpendingHookRequests.set(id, {\n\t\t\t\t\tresolve: (response: RpcHookUIResponse) => {\n\t\t\t\t\t\tif (\"cancelled\" in response && response.cancelled) {\n\t\t\t\t\t\t\tresolve(null);\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(null);\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: \"hook_ui_request\", id, method: \"input\", title, placeholder } as RpcHookUIRequest);\n\t\t\t});\n\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: \"hook_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 RpcHookUIRequest);\n\t\t},\n\t});\n\n\t// Load entries once for session start events\n\tconst entries = session.sessionManager.getEntries();\n\n\t// Set up hooks with RPC-based UI context\n\tconst hookRunner = session.hookRunner;\n\tif (hookRunner) {\n\t\thookRunner.setUIContext(createHookUIContext(), false);\n\t\thookRunner.setSessionFile(session.sessionFile);\n\t\thookRunner.onError((err) => {\n\t\t\toutput({ type: \"hook_error\", hookPath: err.hookPath, event: err.event, error: err.error });\n\t\t});\n\t\t// Set up send handler for pi.send()\n\t\thookRunner.setSendHandler((text, attachments) => {\n\t\t\t// In RPC mode, just queue or prompt based on streaming state\n\t\t\tif (session.isStreaming) {\n\t\t\t\tsession.queueMessage(text);\n\t\t\t} else {\n\t\t\t\tsession.prompt(text, { attachments }).catch((e) => {\n\t\t\t\t\toutput(error(undefined, \"hook_send\", e.message));\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t\t// Emit session event\n\t\tawait hookRunner.emit({\n\t\t\ttype: \"session\",\n\t\t\tentries,\n\t\t\tsessionFile: session.sessionFile,\n\t\t\tpreviousSessionFile: null,\n\t\t\treason: \"start\",\n\t\t});\n\t}\n\n\t// Emit session start event to custom tools\n\t// Note: Tools get no-op UI context in RPC mode (host handles UI via protocol)\n\tfor (const { tool } of session.customTools) {\n\t\tif (tool.onSession) {\n\t\t\ttry {\n\t\t\t\tawait tool.onSession({\n\t\t\t\t\tentries,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tpreviousSessionFile: null,\n\t\t\t\t\treason: \"start\",\n\t\t\t\t});\n\t\t\t} catch (_err) {\n\t\t\t\t// Silently ignore tool errors\n\t\t\t}\n\t\t}\n\t}\n\n\t// Output all agent events as JSON\n\tsession.subscribe((event) => {\n\t\toutput(event);\n\t});\n\n\t// Handle a single command\n\tconst handleCommand = async (command: RpcCommand): Promise<RpcResponse> => {\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// Don't await - events will stream\n\t\t\t\tsession\n\t\t\t\t\t.prompt(command.message, {\n\t\t\t\t\t\tattachments: command.attachments,\n\t\t\t\t\t\texpandSlashCommands: false,\n\t\t\t\t\t})\n\t\t\t\t\t.catch((e) => output(error(id, \"prompt\", e.message)));\n\t\t\t\treturn success(id, \"prompt\");\n\t\t\t}\n\n\t\t\tcase \"queue_message\": {\n\t\t\t\tawait session.queueMessage(command.message);\n\t\t\t\treturn success(id, \"queue_message\");\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 \"reset\": {\n\t\t\t\tconst cancelled = !(await session.reset());\n\t\t\t\treturn success(id, \"reset\", { cancelled });\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\tqueueMode: session.queueMode,\n\t\t\t\t\tsessionFile: session.sessionFile,\n\t\t\t\t\tsessionId: session.sessionId,\n\t\t\t\t\tautoCompactionEnabled: session.autoCompactionEnabled,\n\t\t\t\t\tmessageCount: session.messages.length,\n\t\t\t\t\tqueuedMessageCount: session.queuedMessageCount,\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.getAvailableModels();\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.getAvailableModels();\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 Mode\n\t\t\t// =================================================================\n\n\t\t\tcase \"set_queue_mode\": {\n\t\t\t\tsession.setQueueMode(command.mode);\n\t\t\t\treturn success(id, \"set_queue_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 = 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 cancelled = !(await session.switchSession(command.sessionPath));\n\t\t\t\treturn success(id, \"switch_session\", { cancelled });\n\t\t\t}\n\n\t\t\tcase \"branch\": {\n\t\t\t\tconst result = await session.branch(command.entryIndex);\n\t\t\t\treturn success(id, \"branch\", { text: result.selectedText, cancelled: result.cancelled });\n\t\t\t}\n\n\t\t\tcase \"get_branch_messages\": {\n\t\t\t\tconst messages = session.getUserMessagesForBranching();\n\t\t\t\treturn success(id, \"get_branch_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\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\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// Listen for JSON input\n\tconst rl = readline.createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t\tterminal: false,\n\t});\n\n\trl.on(\"line\", async (line: string) => {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(line);\n\n\t\t\t// Handle hook UI responses\n\t\t\tif (parsed.type === \"hook_ui_response\") {\n\t\t\t\tconst response = parsed as RpcHookUIResponse;\n\t\t\t\tconst pending = pendingHookRequests.get(response.id);\n\t\t\t\tif (pending) {\n\t\t\t\t\tpendingHookRequests.delete(response.id);\n\t\t\t\t\tpending.resolve(response);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle regular commands\n\t\t\tconst command = parsed as RpcCommand;\n\t\t\tconst response = await handleCommand(command);\n\t\t\toutput(response);\n\t\t} catch (e: any) {\n\t\t\toutput(error(undefined, \"parse\", `Failed to parse command: ${e.message}`));\n\t\t}\n\t});\n\n\t// Keep process alive forever\n\treturn new Promise(() => {});\n}\n"]}
|
package/docs/hooks.md
CHANGED
|
@@ -151,7 +151,7 @@ pi.on("session", async (event, ctx) => {
|
|
|
151
151
|
// event.entries: SessionEntry[] - all session entries
|
|
152
152
|
// event.sessionFile: string | null - current session file (null with --no-session)
|
|
153
153
|
// event.previousSessionFile: string | null - previous session file
|
|
154
|
-
// event.reason: "start" | "before_switch" | "switch" | "before_clear" | "clear" |
|
|
154
|
+
// event.reason: "start" | "before_switch" | "switch" | "before_clear" | "clear" |
|
|
155
155
|
// "before_branch" | "branch" | "before_compact" | "compact" | "shutdown"
|
|
156
156
|
// event.targetTurnIndex: number - only for "before_branch" and "branch"
|
|
157
157
|
|
|
@@ -178,17 +178,104 @@ pi.on("session", async (event, ctx) => {
|
|
|
178
178
|
|
|
179
179
|
For `before_branch` and `branch` events, `event.targetTurnIndex` contains the entry index being branched from.
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
- `event.cutPoint`: Where the context will be cut (`firstKeptEntryIndex`, `isSplitTurn`)
|
|
183
|
-
- `event.messagesToSummarize`: Messages that will be summarized
|
|
184
|
-
- `event.tokensBefore`: Current context token count
|
|
185
|
-
- `event.model`: Model to use for summarization
|
|
186
|
-
- `event.apiKey`: API key for the model
|
|
187
|
-
- `event.customInstructions`: Optional custom focus for summary (from `/compact` command)
|
|
181
|
+
#### Custom Compaction
|
|
188
182
|
|
|
189
|
-
|
|
183
|
+
The `before_compact` event lets you implement custom compaction strategies. Understanding the data model:
|
|
190
184
|
|
|
191
|
-
|
|
185
|
+
**How default compaction works:**
|
|
186
|
+
|
|
187
|
+
When context exceeds the threshold, pi finds a "cut point" that keeps recent turns (configurable via `settings.json` `compaction.keepRecentTokens`, default 20k):
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
Legend:
|
|
191
|
+
hdr = header usr = user message ass = assistant message
|
|
192
|
+
tool = tool result cmp = compaction entry bash = bashExecution
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
Session entries (before compaction):
|
|
197
|
+
|
|
198
|
+
index: 0 1 2 3 4 5 6 7 8 9 10
|
|
199
|
+
┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐
|
|
200
|
+
│ hdr │ cmp │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │
|
|
201
|
+
└─────┴─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘
|
|
202
|
+
↑ └───────┬───────┘ └────────────┬────────────┘
|
|
203
|
+
previousSummary messagesToSummarize messagesToKeep
|
|
204
|
+
↑
|
|
205
|
+
cutPoint.firstKeptEntryIndex = 5
|
|
206
|
+
|
|
207
|
+
After compaction (new entry appended):
|
|
208
|
+
|
|
209
|
+
index: 0 1 2 3 4 5 6 7 8 9 10 11
|
|
210
|
+
┌─────┬─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐
|
|
211
|
+
│ hdr │ cmp │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │
|
|
212
|
+
└─────┴─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘
|
|
213
|
+
└──────────┬───────────┘ └────────────────────────┬─────────────────┘
|
|
214
|
+
not sent to LLM sent to LLM
|
|
215
|
+
↑
|
|
216
|
+
firstKeptEntryIndex = 5
|
|
217
|
+
(stored in new cmp)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The session file is append-only. When loading, the session loader finds the latest compaction entry, uses its summary, then loads messages starting from `firstKeptEntryIndex`. The cut point is always a user, assistant, or bashExecution message (never a tool result, which must stay with its tool call).
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
What gets sent to the LLM as context:
|
|
224
|
+
|
|
225
|
+
5 6 7 8 9 10
|
|
226
|
+
┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐
|
|
227
|
+
│ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │
|
|
228
|
+
└────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘
|
|
229
|
+
↑ └─────────────────┬────────────────┘
|
|
230
|
+
from new cmp's messages from
|
|
231
|
+
summary firstKeptEntryIndex onwards
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Split turns:** When a single turn is too large, the cut point may land mid-turn at an assistant message. In this case `cutPoint.isSplitTurn = true`:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
Split turn example (one huge turn that exceeds keepRecentTokens):
|
|
238
|
+
|
|
239
|
+
index: 0 1 2 3 4 5 6 7 8 9
|
|
240
|
+
┌─────┬─────┬─────┬──────┬─────┬──────┬──────┬─────┬──────┬─────┐
|
|
241
|
+
│ hdr │ usr │ ass │ tool │ ass │ tool │ tool │ ass │ tool │ ass │
|
|
242
|
+
└─────┴─────┴─────┴──────┴─────┴──────┴──────┴─────┴──────┴─────┘
|
|
243
|
+
↑ ↑
|
|
244
|
+
turnStartIndex = 1 firstKeptEntryIndex = 7
|
|
245
|
+
│ │ (must be usr/ass/bash, not tool)
|
|
246
|
+
└─────────── turn prefix ───────────────┘ (idx 1-6, summarized separately)
|
|
247
|
+
└── kept messages (idx 7-9)
|
|
248
|
+
|
|
249
|
+
messagesToSummarize = [] (no complete turns before this one)
|
|
250
|
+
messagesToKeep = [ass idx 7, tool idx 8, ass idx 9]
|
|
251
|
+
|
|
252
|
+
The default compaction generates TWO summaries that get merged:
|
|
253
|
+
1. History summary (previousSummary + messagesToSummarize)
|
|
254
|
+
2. Turn prefix summary (messages from turnStartIndex to firstKeptEntryIndex)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
See [src/core/compaction.ts](../src/core/compaction.ts) for the full implementation.
|
|
258
|
+
|
|
259
|
+
**Event fields:**
|
|
260
|
+
|
|
261
|
+
| Field | Description |
|
|
262
|
+
|-------|-------------|
|
|
263
|
+
| `entries` | All session entries (header, messages, model changes, previous compactions). Use this for custom schemes that need full session history. |
|
|
264
|
+
| `cutPoint` | Where default compaction would cut. `firstKeptEntryIndex` is the entry index where kept messages start. `isSplitTurn` indicates if cutting mid-turn. |
|
|
265
|
+
| `previousSummary` | Summary from the last compaction, if any. Include this in your summary to preserve accumulated context. |
|
|
266
|
+
| `messagesToSummarize` | Messages that will be summarized and discarded (from after last compaction to cut point). |
|
|
267
|
+
| `messagesToKeep` | Messages that will be kept verbatim after the summary (from cut point to end). |
|
|
268
|
+
| `tokensBefore` | Current context token count (why compaction triggered). |
|
|
269
|
+
| `model` | Model to use for summarization. |
|
|
270
|
+
| `resolveApiKey` | Function to resolve API key for any model: `await resolveApiKey(model)` |
|
|
271
|
+
| `customInstructions` | Optional focus for summary (from `/compact <instructions>`). |
|
|
272
|
+
| `signal` | AbortSignal for cancellation. Pass to LLM calls and check periodically. |
|
|
273
|
+
|
|
274
|
+
Custom compaction hooks should honor the abort signal by passing it to `complete()` calls. This allows users to cancel compaction (e.g., via Ctrl+C during `/compact`).
|
|
275
|
+
|
|
276
|
+
See [examples/hooks/custom-compaction.ts](../examples/hooks/custom-compaction.ts) for a complete example.
|
|
277
|
+
|
|
278
|
+
**After compaction (`compact` event):**
|
|
192
279
|
- `event.compactionEntry`: The saved compaction entry
|
|
193
280
|
- `event.tokensBefore`: Token count before compaction
|
|
194
281
|
- `event.fromHook`: Whether the compaction entry was provided by a hook
|
|
@@ -258,7 +345,7 @@ pi.on("tool_result", async (event, ctx) => {
|
|
|
258
345
|
// event.content: (TextContent | ImageContent)[]
|
|
259
346
|
// event.details: tool-specific (see below)
|
|
260
347
|
// event.isError: boolean
|
|
261
|
-
|
|
348
|
+
|
|
262
349
|
// Return modified content/details, or undefined to keep original
|
|
263
350
|
return { content: [...], details: {...} };
|
|
264
351
|
});
|
|
@@ -311,7 +398,7 @@ Common fields in details:
|
|
|
311
398
|
Custom tools use `CustomToolResultEvent` with `details: unknown`. Create your own type guard to get full type safety:
|
|
312
399
|
|
|
313
400
|
```typescript
|
|
314
|
-
import {
|
|
401
|
+
import {
|
|
315
402
|
isBashToolResult,
|
|
316
403
|
type CustomToolResultEvent,
|
|
317
404
|
type HookAPI,
|
|
@@ -323,8 +410,8 @@ interface MyCustomToolDetails {
|
|
|
323
410
|
}
|
|
324
411
|
|
|
325
412
|
// Type guard that narrows both toolName and details
|
|
326
|
-
function isMyCustomToolResult(e: ToolResultEvent): e is CustomToolResultEvent & {
|
|
327
|
-
toolName: "my-custom-tool";
|
|
413
|
+
function isMyCustomToolResult(e: ToolResultEvent): e is CustomToolResultEvent & {
|
|
414
|
+
toolName: "my-custom-tool";
|
|
328
415
|
details: MyCustomToolDetails;
|
|
329
416
|
} {
|
|
330
417
|
return e.toolName === "my-custom-tool";
|
|
@@ -465,10 +552,10 @@ import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
|
|
|
465
552
|
export default function (pi: HookAPI) {
|
|
466
553
|
pi.on("session", async (event, ctx) => {
|
|
467
554
|
if (event.reason !== "start") return;
|
|
468
|
-
|
|
555
|
+
|
|
469
556
|
// Watch a trigger file
|
|
470
557
|
const triggerFile = "/tmp/agent-trigger.txt";
|
|
471
|
-
|
|
558
|
+
|
|
472
559
|
fs.watch(triggerFile, () => {
|
|
473
560
|
try {
|
|
474
561
|
const content = fs.readFileSync(triggerFile, "utf-8").trim();
|
|
@@ -480,7 +567,7 @@ export default function (pi: HookAPI) {
|
|
|
480
567
|
// File might not exist yet
|
|
481
568
|
}
|
|
482
569
|
});
|
|
483
|
-
|
|
570
|
+
|
|
484
571
|
ctx.ui.notify("Watching /tmp/agent-trigger.txt", "info");
|
|
485
572
|
});
|
|
486
573
|
}
|
|
@@ -497,7 +584,7 @@ import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
|
|
|
497
584
|
export default function (pi: HookAPI) {
|
|
498
585
|
pi.on("session", async (event, ctx) => {
|
|
499
586
|
if (event.reason !== "start") return;
|
|
500
|
-
|
|
587
|
+
|
|
501
588
|
const server = http.createServer((req, res) => {
|
|
502
589
|
let body = "";
|
|
503
590
|
req.on("data", chunk => body += chunk);
|
|
@@ -507,7 +594,7 @@ export default function (pi: HookAPI) {
|
|
|
507
594
|
res.end("OK");
|
|
508
595
|
});
|
|
509
596
|
});
|
|
510
|
-
|
|
597
|
+
|
|
511
598
|
server.listen(3333, () => {
|
|
512
599
|
ctx.ui.notify("Webhook listening on http://localhost:3333", "info");
|
|
513
600
|
});
|
|
@@ -626,35 +713,9 @@ export default function (pi: HookAPI) {
|
|
|
626
713
|
|
|
627
714
|
### Custom Compaction
|
|
628
715
|
|
|
629
|
-
Use a
|
|
716
|
+
Use a different model for summarization, or implement your own compaction strategy.
|
|
630
717
|
|
|
631
|
-
|
|
632
|
-
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
|
|
633
|
-
import type { CompactionEntry } from "@mariozechner/pi-coding-agent";
|
|
634
|
-
|
|
635
|
-
export default function (pi: HookAPI) {
|
|
636
|
-
pi.on("session", async (event, ctx) => {
|
|
637
|
-
if (event.reason !== "before_compact") return;
|
|
638
|
-
|
|
639
|
-
// Example: Use a simpler summarization approach
|
|
640
|
-
const messages = event.messagesToSummarize;
|
|
641
|
-
const summary = messages
|
|
642
|
-
.filter((m) => m.role === "user")
|
|
643
|
-
.map((m) => `- ${typeof m.content === "string" ? m.content.slice(0, 100) : "[complex]"}`)
|
|
644
|
-
.join("\n");
|
|
645
|
-
|
|
646
|
-
const compactionEntry: CompactionEntry = {
|
|
647
|
-
type: "compaction",
|
|
648
|
-
timestamp: new Date().toISOString(),
|
|
649
|
-
summary: `User requests:\n${summary}`,
|
|
650
|
-
firstKeptEntryIndex: event.cutPoint.firstKeptEntryIndex,
|
|
651
|
-
tokensBefore: event.tokensBefore,
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
return { compactionEntry };
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
```
|
|
718
|
+
See [examples/hooks/custom-compaction.ts](../examples/hooks/custom-compaction.ts) and the [Custom Compaction](#custom-compaction) section above for details.
|
|
658
719
|
|
|
659
720
|
## Mode Behavior
|
|
660
721
|
|
|
@@ -733,7 +794,7 @@ The `HookRunner` class manages hook execution:
|
|
|
733
794
|
```typescript
|
|
734
795
|
class HookRunner {
|
|
735
796
|
constructor(hooks: LoadedHook[], cwd: string, timeout?: number)
|
|
736
|
-
|
|
797
|
+
|
|
737
798
|
setUIContext(ctx: HookUIContext, hasUI: boolean): void
|
|
738
799
|
setSessionFile(path: string | null): void
|
|
739
800
|
onError(listener): () => void
|
package/examples/hooks/README.md
CHANGED
|
@@ -25,6 +25,9 @@ Prevents session changes when there are uncommitted git changes. Blocks clear/sw
|
|
|
25
25
|
### auto-commit-on-exit.ts
|
|
26
26
|
Automatically commits changes when the agent exits (shutdown event). Uses the last assistant message to generate a commit message.
|
|
27
27
|
|
|
28
|
+
### custom-compaction.ts
|
|
29
|
+
Custom context compaction that summarizes the entire conversation instead of keeping recent turns. Uses the `before_compact` hook event to intercept compaction and generate a comprehensive summary using `complete()` from the AI package. Useful when you want maximum context window space at the cost of losing exact conversation history.
|
|
30
|
+
|
|
28
31
|
## Usage
|
|
29
32
|
|
|
30
33
|
```bash
|