@dyyz1993/pi-coding-agent 0.69.9 → 0.69.11
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 +5 -1
- package/dist/core/agent-session.d.ts +8 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +122 -3
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +5 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +8 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +88 -2
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +66 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +29 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +60 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +123 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +209 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/extensions.md +106 -1
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/auto-session-title.ts +82 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-types.js","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * RPC protocol types for headless operation.\n *\n * Commands are sent as JSON lines on stdin.\n * Responses and events are emitted as JSON lines on stdout.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@dyyz1993/pi-agent-core\";\nimport type { ImageContent, Model } from \"@dyyz1993/pi-ai\";\nimport type { SessionStats } from \"../../core/agent-session.js\";\nimport type { BashResult } from \"../../core/bash-executor.js\";\nimport type { CompactionResult } from \"../../core/compaction/index.js\";\nimport type { SourceInfo } from \"../../core/source-info.js\";\n\n// ============================================================================\n// RPC Commands (stdin)\n// ============================================================================\n\nexport type RpcCommand =\n\t// Prompting\n\t| { id?: string; type: \"prompt\"; message: string; images?: ImageContent[]; streamingBehavior?: \"steer\" | \"followUp\" }\n\t| { id?: string; type: \"steer\"; message: string; images?: ImageContent[] }\n\t| { id?: string; type: \"follow_up\"; message: string; images?: ImageContent[] }\n\t| { id?: string; type: \"abort\" }\n\t| { id?: string; type: \"new_session\"; parentSession?: string }\n\n\t// State\n\t| { id?: string; type: \"get_state\" }\n\n\t// Model\n\t| { id?: string; type: \"set_model\"; provider: string; modelId: string }\n\t| { id?: string; type: \"cycle_model\" }\n\t| { id?: string; type: \"get_available_models\" }\n\n\t// Thinking\n\t| { id?: string; type: \"set_thinking_level\"; level: ThinkingLevel }\n\t| { id?: string; type: \"cycle_thinking_level\" }\n\n\t// Queue modes\n\t| { id?: string; type: \"set_steering_mode\"; mode: \"all\" | \"one-at-a-time\" }\n\t| { id?: string; type: \"set_follow_up_mode\"; mode: \"all\" | \"one-at-a-time\" }\n\n\t// Compaction\n\t| { id?: string; type: \"compact\"; customInstructions?: string }\n\t| { id?: string; type: \"set_auto_compaction\"; enabled: boolean }\n\n\t// Retry\n\t| { id?: string; type: \"set_auto_retry\"; enabled: boolean }\n\t| { id?: string; type: \"abort_retry\" }\n\n\t// Bash\n\t| { id?: string; type: \"bash\"; command: string }\n\t| { id?: string; type: \"abort_bash\" }\n\n\t// Session\n\t| { id?: string; type: \"get_session_stats\" }\n\t| { id?: string; type: \"export_html\"; outputPath?: string }\n\t| { id?: string; type: \"switch_session\"; sessionPath: string }\n\t| { id?: string; type: \"fork\"; entryId: string }\n\t| { id?: string; type: \"clone\" }\n\t| { id?: string; type: \"get_fork_messages\" }\n\t| { id?: string; type: \"get_last_assistant_text\" }\n\t| { id?: string; type: \"set_session_name\"; name: string }\n\n\t// Messages\n\t| { id?: string; type: \"get_messages\" }\n\n\t// Commands (available for invocation via prompt)\n\t| { id?: string; type: \"get_commands\" };\n\n// ============================================================================\n// RPC Slash Command (for get_commands response)\n// ============================================================================\n\n/** A command available for invocation via prompt */\nexport interface RpcSlashCommand {\n\t/** Command name (without leading slash) */\n\tname: string;\n\t/** Human-readable description */\n\tdescription?: string;\n\t/** What kind of command this is */\n\tsource: \"extension\" | \"prompt\" | \"skill\";\n\t/** Source metadata for the owning resource */\n\tsourceInfo: SourceInfo;\n}\n\n// ============================================================================\n// RPC State\n// ============================================================================\n\nexport interface RpcSessionState {\n\tmodel?: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\tisStreaming: boolean;\n\tisCompacting: boolean;\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\tsessionFile?: string;\n\tsessionId: string;\n\tsessionName?: string;\n\tautoCompactionEnabled: boolean;\n\tmessageCount: number;\n\tpendingMessageCount: number;\n}\n\n// ============================================================================\n// RPC Responses (stdout)\n// ============================================================================\n\n// Success responses with data\nexport type RpcResponse =\n\t// Prompting (async - events follow)\n\t| { id?: string; type: \"response\"; command: \"prompt\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"steer\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"follow_up\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"abort\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"new_session\"; success: true; data: { cancelled: boolean } }\n\n\t// State\n\t| { id?: string; type: \"response\"; command: \"get_state\"; success: true; data: RpcSessionState }\n\n\t// Model\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"set_model\";\n\t\t\tsuccess: true;\n\t\t\tdata: Model<any>;\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"cycle_model\";\n\t\t\tsuccess: true;\n\t\t\tdata: { model: Model<any>; thinkingLevel: ThinkingLevel; isScoped: boolean } | null;\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_available_models\";\n\t\t\tsuccess: true;\n\t\t\tdata: { models: Model<any>[] };\n\t }\n\n\t// Thinking\n\t| { id?: string; type: \"response\"; command: \"set_thinking_level\"; success: true }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"cycle_thinking_level\";\n\t\t\tsuccess: true;\n\t\t\tdata: { level: ThinkingLevel } | null;\n\t }\n\n\t// Queue modes\n\t| { id?: string; type: \"response\"; command: \"set_steering_mode\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"set_follow_up_mode\"; success: true }\n\n\t// Compaction\n\t| { id?: string; type: \"response\"; command: \"compact\"; success: true; data: CompactionResult }\n\t| { id?: string; type: \"response\"; command: \"set_auto_compaction\"; success: true }\n\n\t// Retry\n\t| { id?: string; type: \"response\"; command: \"set_auto_retry\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"abort_retry\"; success: true }\n\n\t// Bash\n\t| { id?: string; type: \"response\"; command: \"bash\"; success: true; data: BashResult }\n\t| { id?: string; type: \"response\"; command: \"abort_bash\"; success: true }\n\n\t// Session\n\t| { id?: string; type: \"response\"; command: \"get_session_stats\"; success: true; data: SessionStats }\n\t| { id?: string; type: \"response\"; command: \"export_html\"; success: true; data: { path: string } }\n\t| { id?: string; type: \"response\"; command: \"switch_session\"; success: true; data: { cancelled: boolean } }\n\t| { id?: string; type: \"response\"; command: \"fork\"; success: true; data: { text: string; cancelled: boolean } }\n\t| { id?: string; type: \"response\"; command: \"clone\"; success: true; data: { cancelled: boolean } }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_fork_messages\";\n\t\t\tsuccess: true;\n\t\t\tdata: { messages: Array<{ entryId: string; text: string }> };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_last_assistant_text\";\n\t\t\tsuccess: true;\n\t\t\tdata: { text: string | null };\n\t }\n\t| { id?: string; type: \"response\"; command: \"set_session_name\"; success: true }\n\n\t// Messages\n\t| { id?: string; type: \"response\"; command: \"get_messages\"; success: true; data: { messages: AgentMessage[] } }\n\n\t// Commands\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_commands\";\n\t\t\tsuccess: true;\n\t\t\tdata: { commands: RpcSlashCommand[] };\n\t }\n\n\t// Error response (any command can fail)\n\t| { id?: string; type: \"response\"; command: string; success: false; error: string };\n\n// ============================================================================\n// Extension UI Events (stdout)\n// ============================================================================\n\n/** Emitted when an extension needs user input */\nexport type RpcExtensionUIRequest =\n\t| { type: \"extension_ui_request\"; id: string; method: \"select\"; title: string; options: string[]; timeout?: number }\n\t| { type: \"extension_ui_request\"; id: string; method: \"confirm\"; title: string; message: string; timeout?: number }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"input\";\n\t\t\ttitle: string;\n\t\t\tplaceholder?: string;\n\t\t\ttimeout?: number;\n\t }\n\t| { type: \"extension_ui_request\"; id: string; method: \"editor\"; title: string; prefill?: string }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"notify\";\n\t\t\tmessage: string;\n\t\t\tnotifyType?: \"info\" | \"warning\" | \"error\";\n\t }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"setStatus\";\n\t\t\tstatusKey: string;\n\t\t\tstatusText: string | undefined;\n\t }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"setWidget\";\n\t\t\twidgetKey: string;\n\t\t\twidgetLines: string[] | undefined;\n\t\t\twidgetPlacement?: \"aboveEditor\" | \"belowEditor\";\n\t }\n\t| { type: \"extension_ui_request\"; id: string; method: \"setTitle\"; title: string }\n\t| { type: \"extension_ui_request\"; id: string; method: \"set_editor_text\"; text: string };\n\n// ============================================================================\n// Extension UI Commands (stdin)\n// ============================================================================\n\n/** Response to an extension UI request */\nexport type RpcExtensionUIResponse =\n\t| { type: \"extension_ui_response\"; id: string; value: string }\n\t| { type: \"extension_ui_response\"; id: string; confirmed: boolean }\n\t| { type: \"extension_ui_response\"; id: string; cancelled: true };\n\n// ============================================================================\n// Helper type for extracting command types\n// ============================================================================\n\nexport type RpcCommandType = RpcCommand[\"type\"];\n"]}
|
|
1
|
+
{"version":3,"file":"rpc-types.js","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * RPC protocol types for headless operation.\n *\n * Commands are sent as JSON lines on stdin.\n * Responses and events are emitted as JSON lines on stdout.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@dyyz1993/pi-agent-core\";\nimport type { ImageContent, Model } from \"@dyyz1993/pi-ai\";\nimport type { SessionStats } from \"../../core/agent-session.js\";\nimport type { BashResult } from \"../../core/bash-executor.js\";\nimport type { CompactionResult } from \"../../core/compaction/index.js\";\nimport type { ExtensionFlag } from \"../../core/extensions/types.js\";\nimport type { Settings } from \"../../core/settings-manager.js\";\nimport type { SourceInfo } from \"../../core/source-info.js\";\n\n// ============================================================================\n// RPC Commands (stdin)\n// ============================================================================\n\nexport type RpcCommand =\n\t// Prompting\n\t| { id?: string; type: \"prompt\"; message: string; images?: ImageContent[]; streamingBehavior?: \"steer\" | \"followUp\" }\n\t| { id?: string; type: \"steer\"; message: string; images?: ImageContent[] }\n\t| { id?: string; type: \"follow_up\"; message: string; images?: ImageContent[] }\n\t| { id?: string; type: \"abort\" }\n\t| { id?: string; type: \"new_session\"; parentSession?: string }\n\n\t// State\n\t| { id?: string; type: \"get_state\" }\n\n\t// Model\n\t| { id?: string; type: \"set_model\"; provider: string; modelId: string }\n\t| { id?: string; type: \"cycle_model\" }\n\t| { id?: string; type: \"get_available_models\" }\n\n\t// Thinking\n\t| { id?: string; type: \"set_thinking_level\"; level: ThinkingLevel }\n\t| { id?: string; type: \"cycle_thinking_level\" }\n\n\t// Queue modes\n\t| { id?: string; type: \"set_steering_mode\"; mode: \"all\" | \"one-at-a-time\" }\n\t| { id?: string; type: \"set_follow_up_mode\"; mode: \"all\" | \"one-at-a-time\" }\n\n\t// Compaction\n\t| { id?: string; type: \"compact\"; customInstructions?: string }\n\t| { id?: string; type: \"set_auto_compaction\"; enabled: boolean }\n\n\t// Retry\n\t| { id?: string; type: \"set_auto_retry\"; enabled: boolean }\n\t| { id?: string; type: \"abort_retry\" }\n\n\t// Bash\n\t| { id?: string; type: \"bash\"; command: string }\n\t| { id?: string; type: \"abort_bash\" }\n\n\t// Session\n\t| { id?: string; type: \"get_session_stats\" }\n\t| { id?: string; type: \"export_html\"; outputPath?: string }\n\t| { id?: string; type: \"switch_session\"; sessionPath: string }\n\t| { id?: string; type: \"fork\"; entryId: string }\n\t| { id?: string; type: \"clone\" }\n\t| { id?: string; type: \"get_fork_messages\" }\n\t| { id?: string; type: \"get_last_assistant_text\" }\n\t| { id?: string; type: \"set_session_name\"; name: string }\n\n\t// Messages\n\t| { id?: string; type: \"get_messages\" }\n\n\t// Commands (available for invocation via prompt)\n\t| { id?: string; type: \"get_commands\" }\n\n\t// Resources\n\t| { id?: string; type: \"get_skills\" }\n\t| { id?: string; type: \"get_extensions\" }\n\t| { id?: string; type: \"get_tools\" }\n\n\t// Settings\n\t| { id?: string; type: \"get_settings\"; scope?: \"global\" | \"project\" }\n\t| { id?: string; type: \"set_settings\"; settings: Partial<Settings>; scope?: \"global\" | \"project\" }\n\n\t// Context usage\n\t| { id?: string; type: \"get_context_usage\" }\n\n\t// System prompt\n\t| { id?: string; type: \"get_system_prompt\" }\n\n\t// Active tools\n\t| { id?: string; type: \"get_active_tools\" }\n\t| { id?: string; type: \"set_active_tools\"; toolNames: string[] }\n\n\t// Queue\n\t| { id?: string; type: \"get_queue\" }\n\t| { id?: string; type: \"clear_queue\" }\n\n\t// Flags\n\t| { id?: string; type: \"get_flags\" }\n\t| { id?: string; type: \"get_flag_values\" }\n\t| { id?: string; type: \"set_flag\"; name: string; value: boolean | string }\n\n\t// Reload\n\t| { id?: string; type: \"reload\" }\n\n\t// Agents files\n\t| { id?: string; type: \"get_agents_files\" };\n\n// ============================================================================\n// RPC Slash Command (for get_commands response)\n// ============================================================================\n\n/** A command available for invocation via prompt */\nexport interface RpcSlashCommand {\n\t/** Command name (without leading slash) */\n\tname: string;\n\t/** Human-readable description */\n\tdescription?: string;\n\t/** What kind of command this is */\n\tsource: \"extension\" | \"prompt\" | \"skill\";\n\t/** Source metadata for the owning resource */\n\tsourceInfo: SourceInfo;\n}\n\n/** A loaded skill */\nexport interface RpcSkill {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsourceInfo: SourceInfo;\n\tdisableModelInvocation: boolean;\n}\n\n/** A loaded extension */\nexport interface RpcExtension {\n\tpath: string;\n\tresolvedPath: string;\n\tsourceInfo: SourceInfo;\n\ttoolNames: string[];\n\tcommandNames: string[];\n}\n\n/** A registered tool */\nexport interface RpcTool {\n\tname: string;\n\tlabel: string;\n\tdescription: string;\n\tsourceInfo: SourceInfo;\n}\n\n// ============================================================================\n// RPC Types for new commands\n// ============================================================================\n\nexport interface RpcContextUsage {\n\ttokens: number | null;\n\tcontextWindow: number;\n\tpercent: number | null;\n}\n\nexport interface RpcExtensionFlag {\n\tname: string;\n\tdescription?: string;\n\ttype: \"boolean\" | \"string\";\n\tdefault?: boolean | string;\n\textensionPath: string;\n}\n\n// ============================================================================\n// RPC State\n// ============================================================================\n\nexport interface RpcSessionState {\n\tmodel?: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\tisStreaming: boolean;\n\tisCompacting: boolean;\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\tsessionFile?: string;\n\tsessionId: string;\n\tsessionName?: string;\n\tautoCompactionEnabled: boolean;\n\tmessageCount: number;\n\tpendingMessageCount: number;\n}\n\n// ============================================================================\n// RPC Responses (stdout)\n// ============================================================================\n\n// Success responses with data\nexport type RpcResponse =\n\t// Prompting (async - events follow)\n\t| { id?: string; type: \"response\"; command: \"prompt\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"steer\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"follow_up\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"abort\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"new_session\"; success: true; data: { cancelled: boolean } }\n\n\t// State\n\t| { id?: string; type: \"response\"; command: \"get_state\"; success: true; data: RpcSessionState }\n\n\t// Model\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"set_model\";\n\t\t\tsuccess: true;\n\t\t\tdata: Model<any>;\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"cycle_model\";\n\t\t\tsuccess: true;\n\t\t\tdata: { model: Model<any>; thinkingLevel: ThinkingLevel; isScoped: boolean } | null;\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_available_models\";\n\t\t\tsuccess: true;\n\t\t\tdata: { models: Model<any>[] };\n\t }\n\n\t// Thinking\n\t| { id?: string; type: \"response\"; command: \"set_thinking_level\"; success: true }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"cycle_thinking_level\";\n\t\t\tsuccess: true;\n\t\t\tdata: { level: ThinkingLevel } | null;\n\t }\n\n\t// Queue modes\n\t| { id?: string; type: \"response\"; command: \"set_steering_mode\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"set_follow_up_mode\"; success: true }\n\n\t// Compaction\n\t| { id?: string; type: \"response\"; command: \"compact\"; success: true; data: CompactionResult }\n\t| { id?: string; type: \"response\"; command: \"set_auto_compaction\"; success: true }\n\n\t// Retry\n\t| { id?: string; type: \"response\"; command: \"set_auto_retry\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"abort_retry\"; success: true }\n\n\t// Bash\n\t| { id?: string; type: \"response\"; command: \"bash\"; success: true; data: BashResult }\n\t| { id?: string; type: \"response\"; command: \"abort_bash\"; success: true }\n\n\t// Session\n\t| { id?: string; type: \"response\"; command: \"get_session_stats\"; success: true; data: SessionStats }\n\t| { id?: string; type: \"response\"; command: \"export_html\"; success: true; data: { path: string } }\n\t| { id?: string; type: \"response\"; command: \"switch_session\"; success: true; data: { cancelled: boolean } }\n\t| { id?: string; type: \"response\"; command: \"fork\"; success: true; data: { text: string; cancelled: boolean } }\n\t| { id?: string; type: \"response\"; command: \"clone\"; success: true; data: { cancelled: boolean } }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_fork_messages\";\n\t\t\tsuccess: true;\n\t\t\tdata: { messages: Array<{ entryId: string; text: string }> };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_last_assistant_text\";\n\t\t\tsuccess: true;\n\t\t\tdata: { text: string | null };\n\t }\n\t| { id?: string; type: \"response\"; command: \"set_session_name\"; success: true }\n\n\t// Messages\n\t| { id?: string; type: \"response\"; command: \"get_messages\"; success: true; data: { messages: AgentMessage[] } }\n\n\t// Commands\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_commands\";\n\t\t\tsuccess: true;\n\t\t\tdata: { commands: RpcSlashCommand[] };\n\t }\n\n\t// Skills\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_skills\";\n\t\t\tsuccess: true;\n\t\t\tdata: { skills: RpcSkill[] };\n\t }\n\n\t// Extensions\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_extensions\";\n\t\t\tsuccess: true;\n\t\t\tdata: { extensions: RpcExtension[] };\n\t }\n\n\t// Tools\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_tools\";\n\t\t\tsuccess: true;\n\t\t\tdata: { tools: RpcTool[] };\n\t }\n\n\t// Settings\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_settings\";\n\t\t\tsuccess: true;\n\t\t\tdata: Settings;\n\t }\n\t| { id?: string; type: \"response\"; command: \"set_settings\"; success: true }\n\n\t// Context usage\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_context_usage\";\n\t\t\tsuccess: true;\n\t\t\tdata: RpcContextUsage;\n\t }\n\n\t// System prompt\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_system_prompt\";\n\t\t\tsuccess: true;\n\t\t\tdata: { systemPrompt: string; appendSystemPrompt: string[] };\n\t }\n\n\t// Active tools\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_active_tools\";\n\t\t\tsuccess: true;\n\t\t\tdata: { toolNames: string[] };\n\t }\n\t| { id?: string; type: \"response\"; command: \"set_active_tools\"; success: true }\n\n\t// Queue\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_queue\";\n\t\t\tsuccess: true;\n\t\t\tdata: { steering: string[]; followUp: string[] };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"clear_queue\";\n\t\t\tsuccess: true;\n\t\t\tdata: { steering: string[]; followUp: string[] };\n\t }\n\n\t// Flags\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_flags\";\n\t\t\tsuccess: true;\n\t\t\tdata: { flags: RpcExtensionFlag[] };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_flag_values\";\n\t\t\tsuccess: true;\n\t\t\tdata: { values: Record<string, boolean | string> };\n\t }\n\t| { id?: string; type: \"response\"; command: \"set_flag\"; success: true }\n\n\t// Reload\n\t| { id?: string; type: \"response\"; command: \"reload\"; success: true }\n\n\t// Agents files\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_agents_files\";\n\t\t\tsuccess: true;\n\t\t\tdata: { agentsFiles: Array<{ path: string; content: string }> };\n\t }\n\n\t// Error response (any command can fail)\n\t| { id?: string; type: \"response\"; command: string; success: false; error: string };\n\n// ============================================================================\n// Extension UI Events (stdout)\n// ============================================================================\n\n/** Emitted when an extension needs user input */\nexport type RpcExtensionUIRequest =\n\t| { type: \"extension_ui_request\"; id: string; method: \"select\"; title: string; options: string[]; timeout?: number }\n\t| { type: \"extension_ui_request\"; id: string; method: \"confirm\"; title: string; message: string; timeout?: number }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"input\";\n\t\t\ttitle: string;\n\t\t\tplaceholder?: string;\n\t\t\ttimeout?: number;\n\t }\n\t| { type: \"extension_ui_request\"; id: string; method: \"editor\"; title: string; prefill?: string }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"notify\";\n\t\t\tmessage: string;\n\t\t\tnotifyType?: \"info\" | \"warning\" | \"error\";\n\t }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"setStatus\";\n\t\t\tstatusKey: string;\n\t\t\tstatusText: string | undefined;\n\t }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"setWidget\";\n\t\t\twidgetKey: string;\n\t\t\twidgetLines: string[] | undefined;\n\t\t\twidgetPlacement?: \"aboveEditor\" | \"belowEditor\";\n\t }\n\t| { type: \"extension_ui_request\"; id: string; method: \"setTitle\"; title: string }\n\t| { type: \"extension_ui_request\"; id: string; method: \"set_editor_text\"; text: string };\n\n// ============================================================================\n// Extension UI Commands (stdin)\n// ============================================================================\n\n/** Response to an extension UI request */\nexport type RpcExtensionUIResponse =\n\t| { type: \"extension_ui_response\"; id: string; value: string }\n\t| { type: \"extension_ui_response\"; id: string; confirmed: boolean }\n\t| { type: \"extension_ui_response\"; id: string; cancelled: true };\n\n// ============================================================================\n// Helper type for extracting command types\n// ============================================================================\n\nexport type RpcCommandType = RpcCommand[\"type\"];\n"]}
|
package/docs/extensions.md
CHANGED
|
@@ -9,6 +9,7 @@ Extensions are TypeScript modules that extend pi's behavior. They can subscribe
|
|
|
9
9
|
**Key capabilities:**
|
|
10
10
|
- **Custom tools** - Register tools the LLM can call via `pi.registerTool()`
|
|
11
11
|
- **Event interception** - Block or modify tool calls, inject context, customize compaction
|
|
12
|
+
- **UI interception** - Intercept `ctx.ui.confirm/select/input` calls from any extension, respond remotely
|
|
12
13
|
- **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
|
|
13
14
|
- **Custom UI components** - Full TUI components with keyboard input via `ctx.ui.custom()` for complex interactions
|
|
14
15
|
- **Custom commands** - Register commands like `/mycommand` via `pi.registerCommand()`
|
|
@@ -24,6 +25,7 @@ Extensions are TypeScript modules that extend pi's behavior. They can subscribe
|
|
|
24
25
|
- Interactive tools (questions, wizards, custom dialogs)
|
|
25
26
|
- Stateful tools (todo lists, connection pools)
|
|
26
27
|
- External integrations (file watchers, webhooks, CI triggers)
|
|
28
|
+
- Remote approval (forward UI dialogs to a remote service, hold until response)
|
|
27
29
|
- Games while you wait (see `snake.ts` example)
|
|
28
30
|
|
|
29
31
|
See [examples/extensions/](../examples/extensions/) for working implementations.
|
|
@@ -41,6 +43,7 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
|
|
|
41
43
|
- [Session Events](#session-events)
|
|
42
44
|
- [Agent Events](#agent-events)
|
|
43
45
|
- [Tool Events](#tool-events)
|
|
46
|
+
- [UI Interception Events](#ui-interception-events)
|
|
44
47
|
- [ExtensionContext](#extensioncontext)
|
|
45
48
|
- [ExtensionCommandContext](#extensioncommandcontext)
|
|
46
49
|
- [ExtensionAPI Methods](#extensionapi-methods)
|
|
@@ -456,6 +459,18 @@ pi.on("session_shutdown", async (event, ctx) => {
|
|
|
456
459
|
});
|
|
457
460
|
```
|
|
458
461
|
|
|
462
|
+
#### session_rename
|
|
463
|
+
|
|
464
|
+
Fired when the session display name is changed via `pi.setSessionName()`. Not emitted if the new name matches the current name.
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
pi.on("session_rename", async (event, ctx) => {
|
|
468
|
+
// event.oldName - previous name, or undefined if there was none
|
|
469
|
+
// event.newName - the new name (empty string clears the name)
|
|
470
|
+
console.log(`Session renamed: "${event.oldName}" -> "${event.newName}"`);
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
459
474
|
### Agent Events
|
|
460
475
|
|
|
461
476
|
#### before_agent_start
|
|
@@ -815,6 +830,95 @@ pi.on("input", async (event, ctx) => {
|
|
|
815
830
|
|
|
816
831
|
Transforms chain across handlers. See [input-transform.ts](../examples/extensions/input-transform.ts).
|
|
817
832
|
|
|
833
|
+
### UI Interception Events
|
|
834
|
+
|
|
835
|
+
Intercept `ctx.ui.confirm()`, `ctx.ui.select()`, and `ctx.ui.input()` calls from **any extension** or the agent itself. This allows a single extension to take over all UI dialogs -- for example, to forward permission requests to a remote service and wait for a response.
|
|
836
|
+
|
|
837
|
+
Without UI interception, each extension calls `ctx.ui.confirm()` independently and the results are invisible to other extensions. With UI interception, one extension can observe and respond to all UI dialogs on behalf of the user.
|
|
838
|
+
|
|
839
|
+
#### ui_confirm
|
|
840
|
+
|
|
841
|
+
Fired when any code calls `ctx.ui.confirm()`. Return `{ action: "responded", confirmed }` to answer the dialog without showing it to the user. Return `undefined` to fall through to the original UI.
|
|
842
|
+
|
|
843
|
+
```typescript
|
|
844
|
+
pi.on("ui_confirm", async (event, ctx) => {
|
|
845
|
+
// event.title - dialog title
|
|
846
|
+
// event.message - dialog message
|
|
847
|
+
// event.signal - AbortSignal if the caller provided one
|
|
848
|
+
// event.timeout - timeout in ms if the caller provided one
|
|
849
|
+
|
|
850
|
+
// Example: forward all confirmations to a remote service
|
|
851
|
+
const response = await fetch("https://my-server/api/confirm", {
|
|
852
|
+
method: "POST",
|
|
853
|
+
body: JSON.stringify({ title: event.title, message: event.message }),
|
|
854
|
+
});
|
|
855
|
+
const decision = await response.json();
|
|
856
|
+
|
|
857
|
+
return { action: "responded", confirmed: decision.allowed };
|
|
858
|
+
});
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
**Results:**
|
|
862
|
+
- `{ action: "responded", confirmed: boolean }` - answer the dialog immediately (first handler to respond wins)
|
|
863
|
+
- `undefined` - pass through to the original UI implementation
|
|
864
|
+
|
|
865
|
+
#### ui_select
|
|
866
|
+
|
|
867
|
+
Fired when any code calls `ctx.ui.select()`. Return `{ action: "responded", value }` to provide a selection without showing the UI.
|
|
868
|
+
|
|
869
|
+
```typescript
|
|
870
|
+
pi.on("ui_select", async (event, ctx) => {
|
|
871
|
+
// event.title - dialog title
|
|
872
|
+
// event.options - string[] of selectable options
|
|
873
|
+
// event.signal - AbortSignal if provided
|
|
874
|
+
// event.timeout - timeout in ms if provided
|
|
875
|
+
|
|
876
|
+
// Example: only intercept dangerous permission selects
|
|
877
|
+
if (event.title.includes("Dangerous")) {
|
|
878
|
+
const choice = await askRemote(event);
|
|
879
|
+
return { action: "responded", value: choice };
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return undefined; // let other UI selects pass through normally
|
|
883
|
+
});
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
**Results:**
|
|
887
|
+
- `{ action: "responded", value: string | undefined }` - answer the select (undefined = dismissed)
|
|
888
|
+
- `undefined` - pass through to the original UI
|
|
889
|
+
|
|
890
|
+
#### ui_input
|
|
891
|
+
|
|
892
|
+
Fired when any code calls `ctx.ui.input()`. Return `{ action: "responded", value }` to provide input text.
|
|
893
|
+
|
|
894
|
+
```typescript
|
|
895
|
+
pi.on("ui_input", async (event, ctx) => {
|
|
896
|
+
// event.title - dialog title
|
|
897
|
+
// event.placeholder - placeholder text if provided
|
|
898
|
+
// event.signal - AbortSignal if provided
|
|
899
|
+
// event.timeout - timeout in ms if provided
|
|
900
|
+
|
|
901
|
+
return undefined; // pass through to normal UI
|
|
902
|
+
});
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
**Results:**
|
|
906
|
+
- `{ action: "responded", value: string | undefined }` - provide the input (undefined = cancelled)
|
|
907
|
+
- `undefined` - pass through to the original UI
|
|
908
|
+
|
|
909
|
+
#### Short-circuit behavior
|
|
910
|
+
|
|
911
|
+
All three events use first-responder semantics: the first handler to return `{ action: "responded" }` wins. Remaining handlers are not called. If no handler responds, the original UI implementation runs (TUI dialog, RPC protocol, or no-op depending on the mode).
|
|
912
|
+
|
|
913
|
+
If a handler throws, the error is reported via the error listener and the next handler is tried. If all handlers fail, the original UI runs as fallback.
|
|
914
|
+
|
|
915
|
+
#### Use cases
|
|
916
|
+
|
|
917
|
+
- **Remote permission control** -- forward all `confirm`/`select` dialogs to a web dashboard or mobile app, hold the agent until the operator responds
|
|
918
|
+
- **Audit logging** -- record all UI interactions without intercepting them (return `undefined` after logging)
|
|
919
|
+
- **Headless automation** -- auto-approve or auto-deny dialogs based on rules, enabling unattended operation
|
|
920
|
+
- **Custom approval workflows** -- require multiple approvers, time-based auto-approval, or integration with ticketing systems
|
|
921
|
+
|
|
818
922
|
## ExtensionContext
|
|
819
923
|
|
|
820
924
|
All handlers receive `ctx: ExtensionContext`.
|
|
@@ -1292,7 +1396,7 @@ pi.on("session_start", async (_event, ctx) => {
|
|
|
1292
1396
|
|
|
1293
1397
|
### pi.setSessionName(name)
|
|
1294
1398
|
|
|
1295
|
-
Set the session display name (shown in session selector instead of first message).
|
|
1399
|
+
Set the session display name (shown in session selector instead of first message). Emits a `session_rename` event to all extensions if the name actually changed.
|
|
1296
1400
|
|
|
1297
1401
|
```typescript
|
|
1298
1402
|
pi.setSessionName("Refactor auth module");
|
|
@@ -2527,6 +2631,7 @@ All examples in [examples/extensions/](../examples/extensions/).
|
|
|
2527
2631
|
| `message-renderer.ts` | Custom message rendering | `registerMessageRenderer`, `sendMessage` |
|
|
2528
2632
|
| `event-bus.ts` | Inter-extension events | `pi.events` |
|
|
2529
2633
|
| **Session Metadata** |||
|
|
2634
|
+
| `auto-session-title.ts` | Auto-generate session titles | `callLLM`, `setSessionName`, `on("turn_end")`, `on("session_rename")` |
|
|
2530
2635
|
| `session-name.ts` | Name sessions for selector | `setSessionName`, `getSessionName` |
|
|
2531
2636
|
| `bookmark.ts` | Bookmark entries for /tree | `setLabel` |
|
|
2532
2637
|
| **Misc** |||
|
|
@@ -116,6 +116,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
116
116
|
|
|
117
117
|
| Extension | Description |
|
|
118
118
|
|-----------|-------------|
|
|
119
|
+
| `auto-session-title.ts` | Auto-generate session titles from the first user message via `callLLM`, `setSessionName`, `on("turn_end")`, `on("session_rename")` |
|
|
119
120
|
| `session-name.ts` | Name sessions for the session selector via `setSessionName` |
|
|
120
121
|
| `bookmark.ts` | Bookmark entries with labels for `/tree` navigation via `setLabel` |
|
|
121
122
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto Session Title Extension
|
|
3
|
+
*
|
|
4
|
+
* Automatically generates a short, descriptive title for the session
|
|
5
|
+
* based on the first user message using an LLM call.
|
|
6
|
+
*
|
|
7
|
+
* Behavior:
|
|
8
|
+
* - Triggers on the first turn_end event (turnIndex === 0)
|
|
9
|
+
* - Skips if the session already has a name
|
|
10
|
+
* - Uses pi.callLLM() with the user's first message to generate a title
|
|
11
|
+
* - Cleans LLM output: strips think tags, takes first non-empty line, truncates to 100 chars
|
|
12
|
+
* - On failure: silently ignores (does not block the session)
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* pi --extension examples/extensions/auto-session-title.ts
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ExtensionAPI, SessionEntry, SessionMessageEntry } from "@dyyz1993/pi-coding-agent";
|
|
19
|
+
|
|
20
|
+
const TITLE_PROMPT =
|
|
21
|
+
"Generate a very short title (max 50 characters) for a coding conversation that starts with this message. Output ONLY the title, nothing else. No quotes, no punctuation at the end.";
|
|
22
|
+
|
|
23
|
+
const MAX_TITLE_LENGTH = 100;
|
|
24
|
+
const MAX_LLM_TOKENS = 30;
|
|
25
|
+
|
|
26
|
+
function extractFirstUserText(entries: SessionEntry[]): string {
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (entry.type !== "message") continue;
|
|
29
|
+
const msg = (entry as SessionMessageEntry).message;
|
|
30
|
+
if (msg.role !== "user") continue;
|
|
31
|
+
const content = msg.content;
|
|
32
|
+
if (typeof content === "string") return content;
|
|
33
|
+
if (Array.isArray(content)) {
|
|
34
|
+
const texts = content
|
|
35
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text" && typeof c.text === "string")
|
|
36
|
+
.map((c) => c.text)
|
|
37
|
+
.join("\n");
|
|
38
|
+
if (texts) return texts;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function cleanLLMTitle(raw: string): string {
|
|
45
|
+
const withoutThink = raw.replace(/<think[\s\S]*?<\/think\s*>?/g, "");
|
|
46
|
+
return (
|
|
47
|
+
withoutThink
|
|
48
|
+
.split("\n")
|
|
49
|
+
.map((l) => l.trim())
|
|
50
|
+
.filter(Boolean)[0]
|
|
51
|
+
?.slice(0, MAX_TITLE_LENGTH)
|
|
52
|
+
.trim() ?? ""
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default function autoSessionTitle(pi: ExtensionAPI) {
|
|
57
|
+
pi.on("turn_end", async (event, ctx) => {
|
|
58
|
+
if (event.turnIndex !== 0) return;
|
|
59
|
+
|
|
60
|
+
if (pi.getSessionName()) return;
|
|
61
|
+
|
|
62
|
+
const entries = ctx.sessionManager.getEntries();
|
|
63
|
+
const userText = extractFirstUserText(entries);
|
|
64
|
+
if (!userText) return;
|
|
65
|
+
|
|
66
|
+
let title = "";
|
|
67
|
+
try {
|
|
68
|
+
title = await pi.callLLM({
|
|
69
|
+
systemPrompt: TITLE_PROMPT,
|
|
70
|
+
messages: [{ role: "user", content: userText }],
|
|
71
|
+
maxTokens: MAX_LLM_TOKENS,
|
|
72
|
+
});
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const cleaned = cleanLLMTitle(title);
|
|
78
|
+
if (cleaned) {
|
|
79
|
+
pi.setSessionName(cleaned);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-custom-provider",
|
|
3
|
-
"version": "0.69.
|
|
3
|
+
"version": "0.69.8",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-custom-provider",
|
|
9
|
-
"version": "0.69.
|
|
9
|
+
"version": "0.69.8",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.52.0"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-with-deps",
|
|
3
|
-
"version": "0.69.
|
|
3
|
+
"version": "0.69.8",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-with-deps",
|
|
9
|
-
"version": "0.69.
|
|
9
|
+
"version": "0.69.8",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dyyz1993/pi-coding-agent",
|
|
3
|
-
"version": "0.69.
|
|
3
|
+
"version": "0.69.11",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@mariozechner/jiti": "^2.6.2",
|
|
43
|
-
"@dyyz1993/pi-agent-core": "^0.69.
|
|
44
|
-
"@dyyz1993/pi-ai": "^0.69.
|
|
45
|
-
"@dyyz1993/pi-tui": "^0.69.
|
|
43
|
+
"@dyyz1993/pi-agent-core": "^0.69.11",
|
|
44
|
+
"@dyyz1993/pi-ai": "^0.69.11",
|
|
45
|
+
"@dyyz1993/pi-tui": "^0.69.11",
|
|
46
46
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
|
47
47
|
"typebox": "^1.1.24",
|
|
48
48
|
"chalk": "^5.5.0",
|