@code-yeongyu/senpi 2026.6.28-4 → 2026.6.30-2
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 +41 -0
- package/README.md +2 -1
- package/dist/core/agent-session.d.ts +2 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +54 -28
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/command-runner.d.ts +25 -0
- package/dist/core/extensions/builtin/hooks/command-runner.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/command-runner.js +152 -0
- package/dist/core/extensions/builtin/hooks/command-runner.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/command.d.ts +11 -0
- package/dist/core/extensions/builtin/hooks/command.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/command.js +180 -0
- package/dist/core/extensions/builtin/hooks/command.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/config-loader.d.ts +19 -0
- package/dist/core/extensions/builtin/hooks/config-loader.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/config-loader.js +125 -0
- package/dist/core/extensions/builtin/hooks/config-loader.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/diagnostics.d.ts +10 -0
- package/dist/core/extensions/builtin/hooks/diagnostics.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/diagnostics.js +14 -0
- package/dist/core/extensions/builtin/hooks/diagnostics.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/dispatcher.d.ts +60 -0
- package/dist/core/extensions/builtin/hooks/dispatcher.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/dispatcher.js +161 -0
- package/dist/core/extensions/builtin/hooks/dispatcher.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/handler.d.ts +12 -0
- package/dist/core/extensions/builtin/hooks/handler.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/handler.js +128 -0
- package/dist/core/extensions/builtin/hooks/handler.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/index.d.ts +6 -0
- package/dist/core/extensions/builtin/hooks/index.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/index.js +216 -0
- package/dist/core/extensions/builtin/hooks/index.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts +29 -0
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js +276 -0
- package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/matcher.d.ts +7 -0
- package/dist/core/extensions/builtin/hooks/matcher.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/matcher.js +109 -0
- package/dist/core/extensions/builtin/hooks/matcher.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/output-bounds.d.ts +29 -0
- package/dist/core/extensions/builtin/hooks/output-bounds.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/output-bounds.js +47 -0
- package/dist/core/extensions/builtin/hooks/output-bounds.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/output-parser.d.ts +27 -0
- package/dist/core/extensions/builtin/hooks/output-parser.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/output-parser.js +168 -0
- package/dist/core/extensions/builtin/hooks/output-parser.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/plugin-loader.d.ts +11 -0
- package/dist/core/extensions/builtin/hooks/plugin-loader.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/plugin-loader.js +138 -0
- package/dist/core/extensions/builtin/hooks/plugin-loader.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/plugin-manifest.d.ts +47 -0
- package/dist/core/extensions/builtin/hooks/plugin-manifest.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/plugin-manifest.js +86 -0
- package/dist/core/extensions/builtin/hooks/plugin-manifest.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/prompt-adapter.d.ts +30 -0
- package/dist/core/extensions/builtin/hooks/prompt-adapter.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/prompt-adapter.js +59 -0
- package/dist/core/extensions/builtin/hooks/prompt-adapter.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/safety.d.ts +14 -0
- package/dist/core/extensions/builtin/hooks/safety.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/safety.js +179 -0
- package/dist/core/extensions/builtin/hooks/safety.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/schema.d.ts +3 -0
- package/dist/core/extensions/builtin/hooks/schema.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/schema.js +94 -0
- package/dist/core/extensions/builtin/hooks/schema.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/stop-adapter.d.ts +18 -0
- package/dist/core/extensions/builtin/hooks/stop-adapter.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/stop-adapter.js +282 -0
- package/dist/core/extensions/builtin/hooks/stop-adapter.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/tool-adapter.d.ts +11 -0
- package/dist/core/extensions/builtin/hooks/tool-adapter.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/tool-adapter.js +158 -0
- package/dist/core/extensions/builtin/hooks/tool-adapter.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.d.ts +24 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.js +102 -0
- package/dist/core/extensions/builtin/hooks/trust-storage.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/trust.d.ts +34 -0
- package/dist/core/extensions/builtin/hooks/trust.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/trust.js +194 -0
- package/dist/core/extensions/builtin/hooks/trust.js.map +1 -0
- package/dist/core/extensions/builtin/hooks/types.d.ts +134 -0
- package/dist/core/extensions/builtin/hooks/types.d.ts.map +1 -0
- package/dist/core/extensions/builtin/hooks/types.js +36 -0
- package/dist/core/extensions/builtin/hooks/types.js.map +1 -0
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +2 -0
- package/dist/core/extensions/builtin/index.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/runner.d.ts +5 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +20 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +25 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +28 -1
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/package-manager.d.ts +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +14 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts +14 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +38 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +15 -7
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +13 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts +27 -0
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -0
- package/dist/modes/interactive/components/status-indicator.js +64 -0
- package/dist/modes/interactive/components/status-indicator.js.map +1 -0
- package/dist/modes/interactive/components/user-message.d.ts +6 -2
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +15 -4
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +9 -12
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +123 -125
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +15 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +14 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +16 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +26 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/version-check.d.ts +1 -0
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +7 -0
- package/dist/utils/version-check.js.map +1 -1
- package/docs/extensions.md +464 -0
- package/docs/index.md +1 -1
- package/docs/packages.md +9 -6
- package/docs/rpc.md +58 -0
- package/docs/settings.md +1 -0
- package/examples/extensions/README.md +1 -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/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +3 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +9 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +2 -14
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +12 -4
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +10 -24
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +8 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +2 -14
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +15 -45
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +15 -45
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +167 -32
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts +119 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +119 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts +20 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +18 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +8 -32
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +46 -65
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +20 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +18 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js +12 -12
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js +12 -12
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js +12 -12
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js +15 -15
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts +25 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js +109 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +3 -0
- package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/npm-shrinkwrap.json +9 -9
- 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 \"@earendil-works/pi-agent-core\";\nimport type { ImageContent, Model } from \"@earendil-works/pi-ai\";\nimport type { SessionStats } from \"../../core/agent-session.ts\";\nimport type { BashResult } from \"../../core/bash-executor.ts\";\nimport type { CompactionResult } from \"../../core/compaction/index.ts\";\nimport type { SourceInfo } from \"../../core/source-info.ts\";\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; excludeFromContext?: boolean }\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"]}
|
|
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 \"@earendil-works/pi-agent-core\";\nimport type { ImageContent, Model } from \"@earendil-works/pi-ai\";\nimport type { SessionStats } from \"../../core/agent-session.ts\";\nimport type { BashResult } from \"../../core/bash-executor.ts\";\nimport type { CompactionResult } from \"../../core/compaction/index.ts\";\nimport type { SessionEntry, SessionTreeNode } from \"../../core/session-manager.ts\";\nimport type { SourceInfo } from \"../../core/source-info.ts\";\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; excludeFromContext?: boolean }\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_entries\"; since?: string }\n\t| { id?: string; type: \"get_tree\" }\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_entries\";\n\t\t\tsuccess: true;\n\t\t\tdata: { entries: SessionEntry[]; leafId: string | null };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_tree\";\n\t\t\tsuccess: true;\n\t\t\tdata: { tree: SessionTreeNode[]; leafId: string | null };\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"]}
|
|
@@ -5,6 +5,7 @@ export interface LatestPiRelease {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function comparePackageVersions(leftVersion: string, rightVersion: string): number | undefined;
|
|
7
7
|
export declare function isNewerPackageVersion(candidateVersion: string, currentVersion: string): boolean;
|
|
8
|
+
export declare function getReleaseChangelogUrl(version: string): string;
|
|
8
9
|
export declare function getLatestPiRelease(currentVersion: string, options?: {
|
|
9
10
|
timeoutMs?: number;
|
|
10
11
|
}): Promise<LatestPiRelease | undefined>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOpG;AAED,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAM/F;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED,wBAAsB,kBAAkB,CACvC,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAoBtC;AAED,wBAAsB,kBAAkB,CACvC,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAUvG"}
|
|
@@ -3,6 +3,8 @@ import { PACKAGE_NAME } from "../config.js";
|
|
|
3
3
|
import { getPiUserAgent } from "./pi-user-agent.js";
|
|
4
4
|
const LATEST_VERSION_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
|
|
5
5
|
const DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;
|
|
6
|
+
const RELEASE_CHANGELOG_BASE_URL = "https://github.com/code-yeongyu/senpi/blob";
|
|
7
|
+
const RELEASE_CHANGELOG_PATH = "packages/coding-agent/CHANGELOG.md";
|
|
6
8
|
export function comparePackageVersions(leftVersion, rightVersion) {
|
|
7
9
|
const left = valid(leftVersion.trim());
|
|
8
10
|
const right = valid(rightVersion.trim());
|
|
@@ -18,6 +20,11 @@ export function isNewerPackageVersion(candidateVersion, currentVersion) {
|
|
|
18
20
|
}
|
|
19
21
|
return candidateVersion.trim() !== currentVersion.trim();
|
|
20
22
|
}
|
|
23
|
+
export function getReleaseChangelogUrl(version) {
|
|
24
|
+
const trimmedVersion = version.trim();
|
|
25
|
+
const tag = trimmedVersion.startsWith("v") ? trimmedVersion : `v${trimmedVersion}`;
|
|
26
|
+
return `${RELEASE_CHANGELOG_BASE_URL}/${tag}/${RELEASE_CHANGELOG_PATH}`;
|
|
27
|
+
}
|
|
21
28
|
export async function getLatestPiRelease(currentVersion, options = {}) {
|
|
22
29
|
if (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE)
|
|
23
30
|
return undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,kBAAkB,GAAG,8BAA8B,kBAAkB,CAAC,YAAY,CAAC,SAAS,CAAC;AACnG,MAAM,gCAAgC,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/utils/version-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,kBAAkB,GAAG,8BAA8B,kBAAkB,CAAC,YAAY,CAAC,SAAS,CAAC;AACnG,MAAM,gCAAgC,GAAG,KAAK,CAAC;AAC/C,MAAM,0BAA0B,GAAG,4CAA4C,CAAC;AAChF,MAAM,sBAAsB,GAAG,oCAAoC,CAAC;AAQpE,MAAM,UAAU,sBAAsB,CAAC,WAAmB,EAAE,YAAoB;IAC/E,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC;IACzC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,gBAAwB,EAAE,cAAsB;IACrF,MAAM,UAAU,GAAG,sBAAsB,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;IAC5E,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,UAAU,GAAG,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,gBAAgB,CAAC,IAAI,EAAE,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAe;IACrD,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IACtC,MAAM,GAAG,GAAG,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,cAAc,EAAE,CAAC;IACnF,OAAO,GAAG,0BAA0B,IAAI,GAAG,IAAI,sBAAsB,EAAE,CAAC;AACzE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACvC,cAAsB,EACtB,OAAO,GAA2B,EAAE;IAEpC,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAElF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,EAAE;QAChD,OAAO,EAAE;YACR,YAAY,EAAE,cAAc,CAAC,cAAc,CAAC;YAC5C,MAAM,EAAE,kBAAkB;SAC1B;QACD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,gCAAgC,CAAC;KAClF,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAEnC,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAiE,CAAC;IACrG,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAC9D,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,WAAW,GAChB,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACvG,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9F,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACvC,cAAsB,EACtB,OAAO,GAA2B,EAAE;IAEpC,OAAO,CAAC,MAAM,kBAAkB,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,cAAsB;IAChE,IAAI,CAAC;QACJ,MAAM,aAAa,GAAG,MAAM,kBAAkB,CAAC,cAAc,CAAC,CAAC;QAC/D,IAAI,aAAa,IAAI,qBAAqB,CAAC,aAAa,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,CAAC;YACnF,OAAO,aAAa,CAAC;QACtB,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC","sourcesContent":["import { compare, valid } from \"semver\";\nimport { PACKAGE_NAME } from \"../config.ts\";\nimport { getPiUserAgent } from \"./pi-user-agent.ts\";\n\nconst LATEST_VERSION_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;\nconst DEFAULT_VERSION_CHECK_TIMEOUT_MS = 10000;\nconst RELEASE_CHANGELOG_BASE_URL = \"https://github.com/code-yeongyu/senpi/blob\";\nconst RELEASE_CHANGELOG_PATH = \"packages/coding-agent/CHANGELOG.md\";\n\nexport interface LatestPiRelease {\n\tversion: string;\n\tpackageName?: string;\n\tnote?: string;\n}\n\nexport function comparePackageVersions(leftVersion: string, rightVersion: string): number | undefined {\n\tconst left = valid(leftVersion.trim());\n\tconst right = valid(rightVersion.trim());\n\tif (!left || !right) {\n\t\treturn undefined;\n\t}\n\treturn compare(left, right);\n}\n\nexport function isNewerPackageVersion(candidateVersion: string, currentVersion: string): boolean {\n\tconst comparison = comparePackageVersions(candidateVersion, currentVersion);\n\tif (comparison !== undefined) {\n\t\treturn comparison > 0;\n\t}\n\treturn candidateVersion.trim() !== currentVersion.trim();\n}\n\nexport function getReleaseChangelogUrl(version: string): string {\n\tconst trimmedVersion = version.trim();\n\tconst tag = trimmedVersion.startsWith(\"v\") ? trimmedVersion : `v${trimmedVersion}`;\n\treturn `${RELEASE_CHANGELOG_BASE_URL}/${tag}/${RELEASE_CHANGELOG_PATH}`;\n}\n\nexport async function getLatestPiRelease(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number } = {},\n): Promise<LatestPiRelease | undefined> {\n\tif (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE) return undefined;\n\n\tconst response = await fetch(LATEST_VERSION_URL, {\n\t\theaders: {\n\t\t\t\"User-Agent\": getPiUserAgent(currentVersion),\n\t\t\taccept: \"application/json\",\n\t\t},\n\t\tsignal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_VERSION_CHECK_TIMEOUT_MS),\n\t});\n\tif (!response.ok) return undefined;\n\n\tconst data = (await response.json()) as { packageName?: unknown; version?: unknown; note?: unknown };\n\tif (typeof data.version !== \"string\" || !data.version.trim()) {\n\t\treturn undefined;\n\t}\n\tconst packageName =\n\t\ttypeof data.packageName === \"string\" && data.packageName.trim() ? data.packageName.trim() : undefined;\n\tconst note = typeof data.note === \"string\" && data.note.trim() ? data.note.trim() : undefined;\n\treturn { version: data.version.trim(), packageName, note };\n}\n\nexport async function getLatestPiVersion(\n\tcurrentVersion: string,\n\toptions: { timeoutMs?: number } = {},\n): Promise<string | undefined> {\n\treturn (await getLatestPiRelease(currentVersion, options))?.version;\n}\n\nexport async function checkForNewPiVersion(currentVersion: string): Promise<LatestPiRelease | undefined> {\n\ttry {\n\t\tconst latestRelease = await getLatestPiRelease(currentVersion);\n\t\tif (latestRelease && isNewerPackageVersion(latestRelease.version, currentVersion)) {\n\t\t\treturn latestRelease;\n\t\t}\n\t\treturn undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n"]}
|
package/docs/extensions.md
CHANGED
|
@@ -38,6 +38,11 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
|
|
|
38
38
|
- [Events](#events)
|
|
39
39
|
- [Lifecycle Overview](#lifecycle-overview)
|
|
40
40
|
- [Resource Events](#resource-events)
|
|
41
|
+
- [Builtin Hooks](#builtin-hooks)
|
|
42
|
+
- [Hook setup workflow](#hook-setup-workflow)
|
|
43
|
+
- [Hook input and output](#hook-input-and-output)
|
|
44
|
+
- [Common hook recipes](#common-hook-recipes)
|
|
45
|
+
- [Hook troubleshooting](#hook-troubleshooting)
|
|
41
46
|
- [Session Events](#session-events)
|
|
42
47
|
- [Agent Events](#agent-events)
|
|
43
48
|
- [Model Events](#model-events)
|
|
@@ -322,6 +327,9 @@ user sends another prompt ◄─────────────────
|
|
|
322
327
|
├─► session_start { reason: "fork", previousSessionFile }
|
|
323
328
|
└─► resources_discover { reason: "startup" }
|
|
324
329
|
|
|
330
|
+
/name or pi.setSessionName()
|
|
331
|
+
└─► session_info_changed
|
|
332
|
+
|
|
325
333
|
/compact or auto-compaction
|
|
326
334
|
├─► session_before_compact (can cancel or customize)
|
|
327
335
|
└─► session_compact
|
|
@@ -379,6 +387,451 @@ pi.on("resources_discover", async (event, _ctx) => {
|
|
|
379
387
|
});
|
|
380
388
|
```
|
|
381
389
|
|
|
390
|
+
### Builtin Hooks
|
|
391
|
+
|
|
392
|
+
The builtin `hooks` extension runs trusted command hooks from JSON config files. It is intended for local guardrails and migration from Claude-style command hooks, while keeping the extension API as the primary senpi customization surface.
|
|
393
|
+
|
|
394
|
+
Default senpi hook files are discovered from:
|
|
395
|
+
|
|
396
|
+
| Source | Timing | Notes |
|
|
397
|
+
|--------|--------|-------|
|
|
398
|
+
| `~/.senpi/agent/hooks.json` | pre-session | Global hook config. |
|
|
399
|
+
| Global package `pi.hooks` or `hooks/*.json` | pre-session | Package hook config loaded after the global hook file. |
|
|
400
|
+
| `.senpi/hooks.json` | pre-session | Project hook config, loaded after project trust. |
|
|
401
|
+
| Project package `pi.hooks` or `hooks/*.json` | pre-session | Package hook config loaded after the project hook file. |
|
|
402
|
+
| Temporary package sources and SDK/host-provided `new DefaultResourceLoader({ additionalHookPaths })` | pre-session | Package/plugin hook JSON files resolved before `session_start`. |
|
|
403
|
+
| `resources_discover` `hookPaths` | runtime | Late sources. They can affect later hook events in the current runtime and `SessionStart` on `/reload` or the next session. |
|
|
404
|
+
|
|
405
|
+
#### Hook setup workflow
|
|
406
|
+
|
|
407
|
+
Use builtin hooks when you already have Claude/Codex-style JSON hook configs or when you want a small shell-script guardrail. Use a TypeScript extension instead when the behavior needs rich senpi APIs, UI prompts, custom tools, long-lived state, or non-command handlers.
|
|
408
|
+
|
|
409
|
+
For a project-local hook:
|
|
410
|
+
|
|
411
|
+
1. Create `.senpi/hooks.json`.
|
|
412
|
+
2. Put command scripts under `.senpi/hooks/` or another project path.
|
|
413
|
+
3. Start `senpi` in that project.
|
|
414
|
+
4. Run `/hooks list` and copy the real hook id.
|
|
415
|
+
5. Run `/hooks trust <id>` once for each command hook you want to execute.
|
|
416
|
+
6. Trigger the event, then use `/hooks diagnostics` if it did not run.
|
|
417
|
+
|
|
418
|
+
For a global hook, use `~/.senpi/agent/hooks.json` and a stable global script path. For team-shared hooks, prefer a senpi package or plugin manifest so the scripts and hook JSON move together.
|
|
419
|
+
|
|
420
|
+
Trust is tied to the command hook's canonical hash. Changing the command text, `commandWindows`, timeout, matcher, or status message makes the hook untrusted again until you review and trust the new hash. Disabling a hook with `/hooks disable <id>` keeps the trust entry but skips execution; `/hooks enable <id>` re-enables it.
|
|
421
|
+
|
|
422
|
+
Recommended first check after adding a hook:
|
|
423
|
+
|
|
424
|
+
```text
|
|
425
|
+
/hooks list
|
|
426
|
+
/hooks diagnostics
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
`/hooks list` should show the source, matcher, trust state, disabled state, and redacted command preview. `/hooks diagnostics` should be empty or contain only diagnostics you intentionally accept.
|
|
430
|
+
|
|
431
|
+
#### Minimal project hook
|
|
432
|
+
|
|
433
|
+
Create `.senpi/hooks.json`:
|
|
434
|
+
|
|
435
|
+
```json
|
|
436
|
+
{
|
|
437
|
+
"hooks": {
|
|
438
|
+
"PreToolUse": [
|
|
439
|
+
{
|
|
440
|
+
"matcher": "bash",
|
|
441
|
+
"hooks": [
|
|
442
|
+
{
|
|
443
|
+
"type": "command",
|
|
444
|
+
"command": "node .senpi/hooks/log-pretool.mjs",
|
|
445
|
+
"timeout": 10,
|
|
446
|
+
"statusMessage": "Checking bash command"
|
|
447
|
+
}
|
|
448
|
+
]
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Create `.senpi/hooks/log-pretool.mjs`:
|
|
456
|
+
|
|
457
|
+
```javascript
|
|
458
|
+
let stdin = "";
|
|
459
|
+
process.stdin.setEncoding("utf8");
|
|
460
|
+
process.stdin.on("data", (chunk) => {
|
|
461
|
+
stdin += chunk;
|
|
462
|
+
});
|
|
463
|
+
process.stdin.on("end", () => {
|
|
464
|
+
const input = JSON.parse(stdin);
|
|
465
|
+
const toolInput = input.toolInput ?? input.tool_input ?? {};
|
|
466
|
+
const command = String(toolInput.command ?? "");
|
|
467
|
+
if (command.includes("rm -rf")) {
|
|
468
|
+
process.stdout.write(JSON.stringify({
|
|
469
|
+
hookSpecificOutput: {
|
|
470
|
+
hookEventName: "PreToolUse",
|
|
471
|
+
permissionDecision: "deny",
|
|
472
|
+
permissionDecisionReason: "Refusing destructive shell command"
|
|
473
|
+
}
|
|
474
|
+
}));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
process.stdout.write(JSON.stringify({
|
|
478
|
+
hookSpecificOutput: {
|
|
479
|
+
hookEventName: "PreToolUse",
|
|
480
|
+
permissionDecision: "allow"
|
|
481
|
+
}
|
|
482
|
+
}));
|
|
483
|
+
});
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Start senpi in the project and inspect hooks:
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
senpi
|
|
490
|
+
/hooks list
|
|
491
|
+
/hooks trust hk_example_PreToolUse_0_0
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
The real hook id is shown by `/hooks list`. A command hook must be trusted before it runs. Project hook trust is writable only when the project is trusted.
|
|
495
|
+
|
|
496
|
+
Exact command grammar:
|
|
497
|
+
|
|
498
|
+
```text
|
|
499
|
+
Usage: /hooks [list|diagnostics|trust <id>|disable <id>|enable <id>|reload]
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
`/hooks list` shows executable hooks, ids, trust state, disabled state, matcher, status message, and a redacted command preview. `/hooks diagnostics` shows malformed, unsupported, untrusted, and skipped hook details. `/hooks reload` runs the normal extension/resource reload flow.
|
|
503
|
+
|
|
504
|
+
#### Command handler JSON
|
|
505
|
+
|
|
506
|
+
Only command handlers are executable in builtin hooks v1:
|
|
507
|
+
|
|
508
|
+
```json
|
|
509
|
+
{
|
|
510
|
+
"type": "command",
|
|
511
|
+
"command": "node ./hooks/check.mjs",
|
|
512
|
+
"commandWindows": "node .\\hooks\\check.mjs",
|
|
513
|
+
"timeout": 30,
|
|
514
|
+
"statusMessage": "Running hook"
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
`command` is required and runs through the host shell with the hook input JSON on stdin. `commandWindows` or `command_windows` can override the command on Windows. `timeout` is seconds and defaults to 600. `statusMessage` is display text only.
|
|
519
|
+
|
|
520
|
+
Hook commands inherit a minimal environment (`PATH`, home/user/shell/temp variables, and Windows shell basics). Hooks loaded through the plugin manifest helper also receive `PLUGIN_ROOT`, `PLUGIN_DATA`, `CLAUDE_PLUGIN_ROOT`, and `CLAUDE_PLUGIN_DATA`. Every command receives `SENPI_HOOK_SOURCE` and `SENPI_HOOK_EVENT`. Do not put API keys or tokens in hook config, command text, status messages, stdout, stderr, or diagnostics. Read secrets inside the command from your own secret store and avoid echoing them.
|
|
521
|
+
|
|
522
|
+
#### Event map
|
|
523
|
+
|
|
524
|
+
| Event | senpi source | Supported behavior |
|
|
525
|
+
|-------|--------------|--------------------|
|
|
526
|
+
| `SessionStart` | `session_start` | Runs on startup/reload/new/resume/fork. `additionalContext` is recorded as hidden hook context. Decisions are diagnostics only. |
|
|
527
|
+
| `UserPromptSubmit` | `input` plus `before_agent_start` | Can block the prompt. `additionalContext` is injected as hidden context for the turn. `systemMessage` appends to the turn system prompt. |
|
|
528
|
+
| `PreToolUse` | `tool_call` | Can deny/block, ask (represented as block), allow/approve, mutate `updatedInput` only with `permissionDecision: "allow"`, and add context for the matching tool result. |
|
|
529
|
+
| `PostToolUse` | `tool_result` | Can block by replacing the tool result with an error, replace tool output with `updatedToolOutput`, and append `additionalContext`. |
|
|
530
|
+
| `PreCompact` | `session_before_compact` | Can block/cancel compaction with `decision: "block"` or exit code 2. `additionalContext` and `customInstructions` are diagnostic-only. |
|
|
531
|
+
| `PostCompact` | `session_compact` | Runs after accepted compactions. Output fields are currently diagnostic-only. |
|
|
532
|
+
| `Stop` | `agent_end` | Can block by sending a follow-up prompt from `additionalContext` or `reason`. Reentry is capped at eight blocks per turn. |
|
|
533
|
+
|
|
534
|
+
Matchers are strings. For tool events, matchers match tool names. For lifecycle events, `*`, event names, reasons such as `startup`, `reload`, `manual`, or `overflow`, comma/pipe-separated literals, and JavaScript regular expressions are supported.
|
|
535
|
+
|
|
536
|
+
Command stdout may be empty or JSON. Universal JSON fields include `decision`, `reason`, `additionalContext`, `continue`, `stopReason`, `suppressOutput`, `systemMessage`, and `hookSpecificOutput`. `hookSpecificOutput.hookEventName` must match the current event when present. Exit code `2` is treated as a block decision with stderr as the reason.
|
|
537
|
+
|
|
538
|
+
#### Hook input and output
|
|
539
|
+
|
|
540
|
+
Every command receives one JSON object on stdin. The object always includes `event` and `cwd`, and includes session/transcript fields when senpi has them:
|
|
541
|
+
|
|
542
|
+
| Field | Meaning |
|
|
543
|
+
|-------|---------|
|
|
544
|
+
| `event` | Hook event name, such as `PreToolUse` or `UserPromptSubmit`. |
|
|
545
|
+
| `cwd` | Current project working directory. |
|
|
546
|
+
| `session_id` | Session id when available. |
|
|
547
|
+
| `transcript_path` | Session transcript path when available. |
|
|
548
|
+
|
|
549
|
+
Tool events include both senpi-style camelCase and Claude-compatible snake_case fields:
|
|
550
|
+
|
|
551
|
+
```json
|
|
552
|
+
{
|
|
553
|
+
"event": "PreToolUse",
|
|
554
|
+
"toolName": "bash",
|
|
555
|
+
"toolInput": { "command": "npm test" },
|
|
556
|
+
"tool_name": "bash",
|
|
557
|
+
"tool_input": { "command": "npm test" },
|
|
558
|
+
"cwd": "/repo"
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Prompt hooks receive the raw prompt:
|
|
563
|
+
|
|
564
|
+
```json
|
|
565
|
+
{
|
|
566
|
+
"event": "UserPromptSubmit",
|
|
567
|
+
"prompt": "summarize this repo",
|
|
568
|
+
"cwd": "/repo"
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Output must be valid JSON if you want senpi to apply a decision. Empty stdout means no decision. Prefer event-specific output inside `hookSpecificOutput`:
|
|
573
|
+
|
|
574
|
+
```json
|
|
575
|
+
{
|
|
576
|
+
"hookSpecificOutput": {
|
|
577
|
+
"hookEventName": "PreToolUse",
|
|
578
|
+
"permissionDecision": "deny",
|
|
579
|
+
"permissionDecisionReason": "Do not run destructive commands"
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
`PreToolUse` supports `permissionDecision: "allow" | "deny" | "ask"`. `updatedInput` is applied only when `permissionDecision` is `"allow"`. `additionalContext` is saved as hidden context for the active tool result.
|
|
585
|
+
|
|
586
|
+
```json
|
|
587
|
+
{
|
|
588
|
+
"hookSpecificOutput": {
|
|
589
|
+
"hookEventName": "PreToolUse",
|
|
590
|
+
"permissionDecision": "allow",
|
|
591
|
+
"updatedInput": { "command": "npm test -- --runInBand" },
|
|
592
|
+
"additionalContext": "The hook serialized this test command."
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
`UserPromptSubmit` can block or add context, but it cannot replace the prompt through Claude/Codex-compatible fields:
|
|
598
|
+
|
|
599
|
+
```json
|
|
600
|
+
{
|
|
601
|
+
"decision": "block",
|
|
602
|
+
"reason": "Please include an issue number before starting release work."
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
`PostToolUse` can replace visible tool output with `updatedToolOutput` and can add `additionalContext`:
|
|
607
|
+
|
|
608
|
+
```json
|
|
609
|
+
{
|
|
610
|
+
"hookSpecificOutput": {
|
|
611
|
+
"hookEventName": "PostToolUse",
|
|
612
|
+
"updatedToolOutput": "Output redacted by local hook.",
|
|
613
|
+
"additionalContext": "The raw tool output was replaced because it matched the secret scanner."
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
`Stop` can ask senpi to continue with a follow-up prompt by returning `decision: "block"` plus `additionalContext` or `reason`. senpi caps repeated Stop follow-ups at eight per turn to prevent loops.
|
|
619
|
+
|
|
620
|
+
#### Common hook recipes
|
|
621
|
+
|
|
622
|
+
Block dangerous shell commands:
|
|
623
|
+
|
|
624
|
+
```javascript
|
|
625
|
+
let stdin = "";
|
|
626
|
+
process.stdin.setEncoding("utf8");
|
|
627
|
+
process.stdin.on("data", (chunk) => {
|
|
628
|
+
stdin += chunk;
|
|
629
|
+
});
|
|
630
|
+
process.stdin.on("end", () => {
|
|
631
|
+
const input = JSON.parse(stdin);
|
|
632
|
+
const toolInput = input.toolInput ?? input.tool_input ?? {};
|
|
633
|
+
const command = String(toolInput.command ?? "");
|
|
634
|
+
|
|
635
|
+
if (/\brm\s+-rf\b/.test(command)) {
|
|
636
|
+
process.stdout.write(JSON.stringify({
|
|
637
|
+
hookSpecificOutput: {
|
|
638
|
+
hookEventName: "PreToolUse",
|
|
639
|
+
permissionDecision: "deny",
|
|
640
|
+
permissionDecisionReason: "Blocked rm -rf"
|
|
641
|
+
}
|
|
642
|
+
}));
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
Add per-prompt project context:
|
|
648
|
+
|
|
649
|
+
```json
|
|
650
|
+
{
|
|
651
|
+
"hooks": {
|
|
652
|
+
"UserPromptSubmit": [
|
|
653
|
+
{
|
|
654
|
+
"hooks": [
|
|
655
|
+
{
|
|
656
|
+
"type": "command",
|
|
657
|
+
"command": "node .senpi/hooks/prompt-context.mjs",
|
|
658
|
+
"statusMessage": "Loading prompt context"
|
|
659
|
+
}
|
|
660
|
+
]
|
|
661
|
+
}
|
|
662
|
+
]
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
```javascript
|
|
668
|
+
process.stdin.resume();
|
|
669
|
+
process.stdin.on("end", () => {
|
|
670
|
+
process.stdout.write(JSON.stringify({
|
|
671
|
+
hookSpecificOutput: {
|
|
672
|
+
hookEventName: "UserPromptSubmit",
|
|
673
|
+
additionalContext: "Prefer the repository's AGENTS.md rules over generic defaults."
|
|
674
|
+
}
|
|
675
|
+
}));
|
|
676
|
+
});
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
Stop when a final answer needs a required follow-up:
|
|
680
|
+
|
|
681
|
+
```javascript
|
|
682
|
+
process.stdin.resume();
|
|
683
|
+
process.stdin.on("end", () => {
|
|
684
|
+
process.stdout.write(JSON.stringify({
|
|
685
|
+
decision: "block",
|
|
686
|
+
hookSpecificOutput: {
|
|
687
|
+
hookEventName: "Stop",
|
|
688
|
+
additionalContext: "Before finishing, run the focused regression test and summarize the result."
|
|
689
|
+
}
|
|
690
|
+
}));
|
|
691
|
+
});
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Distribute hooks from a package:
|
|
695
|
+
|
|
696
|
+
```json
|
|
697
|
+
{
|
|
698
|
+
"name": "team-senpi-hooks",
|
|
699
|
+
"pi": {
|
|
700
|
+
"hooks": ["./hooks/pretool.json"]
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
```json
|
|
706
|
+
{
|
|
707
|
+
"hooks": {
|
|
708
|
+
"PreToolUse": [
|
|
709
|
+
{
|
|
710
|
+
"matcher": "bash|edit|write",
|
|
711
|
+
"hooks": [
|
|
712
|
+
{
|
|
713
|
+
"type": "command",
|
|
714
|
+
"command": "node ./hooks/check.mjs",
|
|
715
|
+
"statusMessage": "Running team hook"
|
|
716
|
+
}
|
|
717
|
+
]
|
|
718
|
+
}
|
|
719
|
+
]
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
Package hook paths are resolved relative to the package root. If you need plugin-root environment variables, use a `.codex-plugin/plugin.json` manifest and `${PLUGIN_ROOT}` in the command.
|
|
725
|
+
|
|
726
|
+
#### Plugin hooks
|
|
727
|
+
|
|
728
|
+
Plugin hook manifests are package integration inputs. Installed senpi packages can expose hook JSON files through `package.json` under `pi.hooks` or by placing JSON files under `hooks/`; those sources are discovered before `session_start` in global, project, or temporary package scope.
|
|
729
|
+
|
|
730
|
+
Plugin hook manifests live at `.codex-plugin/plugin.json` under the plugin root. A manifest can point to hook JSON files:
|
|
731
|
+
|
|
732
|
+
```json
|
|
733
|
+
{
|
|
734
|
+
"name": "example-hooks-plugin",
|
|
735
|
+
"hooks": "./hooks/pretool.json"
|
|
736
|
+
}
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
It can also inline hook config directly:
|
|
740
|
+
|
|
741
|
+
```json
|
|
742
|
+
{
|
|
743
|
+
"name": "inline-hooks-plugin",
|
|
744
|
+
"hooks": {
|
|
745
|
+
"hooks": {
|
|
746
|
+
"SessionStart": [
|
|
747
|
+
{
|
|
748
|
+
"matcher": "startup|reload",
|
|
749
|
+
"hooks": [
|
|
750
|
+
{
|
|
751
|
+
"type": "command",
|
|
752
|
+
"command": "node ${PLUGIN_ROOT}/hooks/session-start.mjs"
|
|
753
|
+
}
|
|
754
|
+
]
|
|
755
|
+
}
|
|
756
|
+
]
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
`hooks` may also be an array mixing paths, nested arrays, and inline hook objects. Hook paths must stay inside the plugin root. The manifest helper also understands a plugin default hook file at `hooks/hooks.json` unless the host disables default plugin hooks. Plugin commands that reference `${PLUGIN_ROOT}`, `$PLUGIN_ROOT`, or `%PLUGIN_ROOT%` must resolve to existing files inside the plugin root.
|
|
763
|
+
|
|
764
|
+
SDK hosts can still provide explicit pre-session hook sources:
|
|
765
|
+
|
|
766
|
+
```typescript
|
|
767
|
+
const loader = new DefaultResourceLoader({
|
|
768
|
+
cwd,
|
|
769
|
+
agentDir,
|
|
770
|
+
additionalHookPaths: ["/absolute/path/to/package-hooks.json"],
|
|
771
|
+
});
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
Pre-session sources are visible to `SessionStart` on initial startup.
|
|
775
|
+
|
|
776
|
+
#### Runtime hook sources
|
|
777
|
+
|
|
778
|
+
Extensions can return late hook paths from `resources_discover`:
|
|
779
|
+
|
|
780
|
+
```typescript
|
|
781
|
+
import { join } from "node:path";
|
|
782
|
+
import type { ExtensionAPI } from "@code-yeongyu/senpi";
|
|
783
|
+
|
|
784
|
+
export default function (pi: ExtensionAPI) {
|
|
785
|
+
pi.on("resources_discover", (event, ctx) => {
|
|
786
|
+
if (event.reason !== "reload") return {};
|
|
787
|
+
return {
|
|
788
|
+
hookPaths: [join(ctx.cwd, ".senpi", "generated-hooks.json")],
|
|
789
|
+
};
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
These paths are runtime sources. They can affect later hook events in the current runtime, but their `SessionStart` hooks do not run for the already-started initial session. On `/hooks reload` or `/reload`, runtime `SessionStart` hooks are visible to the reloaded session. If a runtime source contains `SessionStart`, senpi records a diagnostic: `Runtime SessionStart hooks are loaded for reload or the next session only.`
|
|
795
|
+
|
|
796
|
+
#### Not yet supported
|
|
797
|
+
|
|
798
|
+
The builtin hooks implementation intentionally does not claim full Claude hook coverage. Unsupported shapes produce warnings or diagnostics and are not executed.
|
|
799
|
+
|
|
800
|
+
| Area | Not yet supported |
|
|
801
|
+
|------|-------------------|
|
|
802
|
+
| Events | `PermissionRequest`, `PermissionDenied`, `SubagentStart`, `SubagentStop`, `Notification`, `Setup`, `UserPromptExpansion`, `PostToolUseFailure`, `PostToolBatch`, `TaskCreated`, `TaskCompleted`, `StopFailure`, `TeammateIdle`, `InstructionsLoaded`, `ConfigChange`, `CwdChanged`, `FileChanged`, `WorktreeCreate`, `WorktreeRemove`, `MessageDisplay`, `SessionEnd`, `Elicitation`, `ElicitationResult`. |
|
|
803
|
+
| Handler types | `prompt`, `agent`, `http`, and `mcp_tool`. |
|
|
804
|
+
| Command forms | Exec-form commands, object-valued `command`, separate `args`, `shell`, and command handlers with `async: true`. |
|
|
805
|
+
| Command fields | `if`, `asyncRewake`, `terminalSequence`, and `continueOnBlock`. |
|
|
806
|
+
| Prompt mutation | UserPromptSubmit prompt replacement fields such as `prompt`, `updatedPrompt`, and `replacementPrompt`. |
|
|
807
|
+
| Async behavior | Claude-style async hooks and `asyncRewake`. |
|
|
808
|
+
| PreCompact mutation | `customInstructions` does not change compaction in builtin hooks v1. |
|
|
809
|
+
|
|
810
|
+
#### omo-codex migration notes
|
|
811
|
+
|
|
812
|
+
For omo-codex or Claude-style hook configs, migrate the lowest-risk checks first:
|
|
813
|
+
|
|
814
|
+
- Keep existing `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`, `PreCompact`, `PostCompact`, and `Stop` command hooks if they use shell command strings.
|
|
815
|
+
- Move `http`, `mcp_tool`, `prompt`, and `agent` hooks to senpi extensions or tools.
|
|
816
|
+
- Replace `if` conditions with logic inside the command script or with matcher strings for tool/lifecycle selection.
|
|
817
|
+
- Treat `Notification`, subagent/task/teammate/worktree events, and prompt replacement as deferred; document them in plugin README files instead of assuming execution.
|
|
818
|
+
- When the host uses the plugin manifest helper, use `PLUGIN_ROOT` and `PLUGIN_DATA` for plugin-relative scripts and state. Keep secrets out of manifests and hook JSON.
|
|
819
|
+
|
|
820
|
+
#### Hook troubleshooting
|
|
821
|
+
|
|
822
|
+
If a hook does not run:
|
|
823
|
+
|
|
824
|
+
1. Run `/hooks list` and confirm the hook is present, enabled, and trusted.
|
|
825
|
+
2. Run `/hooks diagnostics` and check for malformed JSON, unsupported fields, untrusted hashes, skipped runtime `SessionStart`, or plugin containment errors.
|
|
826
|
+
3. Confirm the matcher matches the event. Tool events match tool names such as `bash`, `edit`, `write`, `Bash`, `Edit`, and `Write`; lifecycle events match reasons such as `startup`, `reload`, `manual`, and `overflow`.
|
|
827
|
+
4. Confirm the command path works from the project cwd. For plugin commands using `${PLUGIN_ROOT}`, the resolved file must exist inside the plugin root.
|
|
828
|
+
5. Keep stdout as valid JSON or empty. Human-readable logs should go to stderr, and secret-bearing logs should be avoided entirely.
|
|
829
|
+
6. After editing hook JSON or scripts, run `/hooks reload`, then `/hooks list` again. If the trusted hash changed, trust the hook again.
|
|
830
|
+
|
|
831
|
+
If a hook runs but has no effect, check the event support table. Some fields are diagnostic-only by design, such as `PreCompact` `additionalContext` and `customInstructions`, `PostCompact` output fields, and unsupported prompt replacement fields for `UserPromptSubmit`.
|
|
832
|
+
|
|
833
|
+
If a hook times out, increase `timeout` in seconds or make the script observe stdin and exit quickly. The default timeout is 600 seconds. Invalid timeout values such as `0`, negative numbers, `NaN`, or infinity are rejected and the command is not started.
|
|
834
|
+
|
|
382
835
|
### Session Events
|
|
383
836
|
|
|
384
837
|
See [Session Format](session-format.md) for session storage internals and the SessionManager API.
|
|
@@ -395,6 +848,17 @@ pi.on("session_start", async (event, ctx) => {
|
|
|
395
848
|
});
|
|
396
849
|
```
|
|
397
850
|
|
|
851
|
+
#### session_info_changed
|
|
852
|
+
|
|
853
|
+
Fired when the current session display name is set via `/name`, RPC, or `pi.setSessionName()`.
|
|
854
|
+
|
|
855
|
+
```typescript
|
|
856
|
+
pi.on("session_info_changed", async (event, ctx) => {
|
|
857
|
+
// event.name - current normalized name, or undefined if cleared
|
|
858
|
+
ctx.ui.notify(`Session renamed: ${event.name ?? "(none)"}`, "info");
|
|
859
|
+
});
|
|
860
|
+
```
|
|
861
|
+
|
|
398
862
|
#### session_before_switch
|
|
399
863
|
|
|
400
864
|
Fired before starting a new session (`/new`) or switching sessions (`/resume`).
|
package/docs/index.md
CHANGED
|
@@ -43,7 +43,7 @@ For the full first-run flow, see [Quickstart](quickstart.md).
|
|
|
43
43
|
|
|
44
44
|
## Customization
|
|
45
45
|
|
|
46
|
-
- [Extensions](extensions.md) - TypeScript modules for tools, commands, events, and
|
|
46
|
+
- [Extensions](extensions.md) - TypeScript modules for tools, commands, events, custom UI, and builtin command hooks.
|
|
47
47
|
- [Skills](skills.md) - Agent Skills for reusable on-demand capabilities.
|
|
48
48
|
- [Prompt templates](prompt-templates.md) - reusable prompts that expand from slash commands.
|
|
49
49
|
- [Themes](themes.md) - built-in and custom terminal themes.
|