@dyyz1993/pi-coding-agent 0.69.10 → 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.
Files changed (38) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/dist/core/agent-session.d.ts.map +1 -1
  3. package/dist/core/agent-session.js +11 -0
  4. package/dist/core/agent-session.js.map +1 -1
  5. package/dist/core/extensions/index.d.ts +1 -1
  6. package/dist/core/extensions/index.d.ts.map +1 -1
  7. package/dist/core/extensions/index.js.map +1 -1
  8. package/dist/core/extensions/loader.d.ts.map +1 -1
  9. package/dist/core/extensions/loader.js +4 -0
  10. package/dist/core/extensions/loader.js.map +1 -1
  11. package/dist/core/extensions/runner.d.ts +7 -1
  12. package/dist/core/extensions/runner.d.ts.map +1 -1
  13. package/dist/core/extensions/runner.js +84 -2
  14. package/dist/core/extensions/runner.js.map +1 -1
  15. package/dist/core/extensions/types.d.ts +46 -1
  16. package/dist/core/extensions/types.d.ts.map +1 -1
  17. package/dist/core/extensions/types.js.map +1 -1
  18. package/dist/modes/rpc/rpc-client.d.ts +29 -1
  19. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  20. package/dist/modes/rpc/rpc-client.js +60 -0
  21. package/dist/modes/rpc/rpc-client.js.map +1 -1
  22. package/dist/modes/rpc/rpc-mode.d.ts +1 -1
  23. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  24. package/dist/modes/rpc/rpc-mode.js +123 -0
  25. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  26. package/dist/modes/rpc/rpc-types.d.ts +209 -0
  27. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  28. package/dist/modes/rpc/rpc-types.js.map +1 -1
  29. package/docs/extensions.md +106 -1
  30. package/examples/extensions/README.md +1 -0
  31. package/examples/extensions/auto-session-title.ts +82 -0
  32. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  33. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  34. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  35. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  36. package/examples/extensions/with-deps/package-lock.json +2 -2
  37. package/examples/extensions/with-deps/package.json +1 -1
  38. 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"]}
@@ -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.7",
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.7",
9
+ "version": "0.69.8",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.69.7",
4
+ "version": "0.69.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.69.7",
4
+ "version": "0.69.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-qwen-cli",
3
3
  "private": true,
4
- "version": "0.69.7",
4
+ "version": "0.69.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "0.69.7",
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.7",
9
+ "version": "0.69.8",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.69.7",
4
+ "version": "0.69.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyyz1993/pi-coding-agent",
3
- "version": "0.69.10",
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.10",
44
- "@dyyz1993/pi-ai": "^0.69.10",
45
- "@dyyz1993/pi-tui": "^0.69.10",
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",