@dyyz1993/pi-coding-agent 0.69.18 → 0.69.25

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 (129) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/cli/args.d.ts +1 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +12 -2
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +15 -1
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +216 -0
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/extensions/client-channel.d.ts +61 -0
  11. package/dist/core/extensions/client-channel.d.ts.map +1 -0
  12. package/dist/core/extensions/client-channel.js +67 -0
  13. package/dist/core/extensions/client-channel.js.map +1 -0
  14. package/dist/core/extensions/index.d.ts +3 -2
  15. package/dist/core/extensions/index.d.ts.map +1 -1
  16. package/dist/core/extensions/index.js +1 -0
  17. package/dist/core/extensions/index.js.map +1 -1
  18. package/dist/core/extensions/loader.d.ts.map +1 -1
  19. package/dist/core/extensions/loader.js +21 -0
  20. package/dist/core/extensions/loader.js.map +1 -1
  21. package/dist/core/extensions/runner.d.ts +1 -0
  22. package/dist/core/extensions/runner.d.ts.map +1 -1
  23. package/dist/core/extensions/runner.js +31 -0
  24. package/dist/core/extensions/runner.js.map +1 -1
  25. package/dist/core/extensions/types.d.ts +86 -34
  26. package/dist/core/extensions/types.d.ts.map +1 -1
  27. package/dist/core/extensions/types.js.map +1 -1
  28. package/dist/core/include-resolver.d.ts +18 -0
  29. package/dist/core/include-resolver.d.ts.map +1 -0
  30. package/dist/core/include-resolver.js +304 -0
  31. package/dist/core/include-resolver.js.map +1 -0
  32. package/dist/core/resource-loader.d.ts.map +1 -1
  33. package/dist/core/resource-loader.js +17 -4
  34. package/dist/core/resource-loader.js.map +1 -1
  35. package/dist/core/session-manager.d.ts +8 -4
  36. package/dist/core/session-manager.d.ts.map +1 -1
  37. package/dist/core/session-manager.js +29 -6
  38. package/dist/core/session-manager.js.map +1 -1
  39. package/dist/core/storage.d.ts +24 -0
  40. package/dist/core/storage.d.ts.map +1 -0
  41. package/dist/core/storage.js +55 -0
  42. package/dist/core/storage.js.map +1 -0
  43. package/dist/core/tools/path-security.d.ts +15 -0
  44. package/dist/core/tools/path-security.d.ts.map +1 -0
  45. package/dist/core/tools/path-security.js +76 -0
  46. package/dist/core/tools/path-security.js.map +1 -0
  47. package/dist/core/tools/strip-markdown.d.ts +2 -0
  48. package/dist/core/tools/strip-markdown.d.ts.map +1 -0
  49. package/dist/core/tools/strip-markdown.js +8 -0
  50. package/dist/core/tools/strip-markdown.js.map +1 -0
  51. package/dist/index.d.ts +5 -4
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +3 -2
  54. package/dist/index.js.map +1 -1
  55. package/dist/main.d.ts.map +1 -1
  56. package/dist/main.js +1 -0
  57. package/dist/main.js.map +1 -1
  58. package/dist/modes/interactive/components/extension-selector.d.ts +4 -1
  59. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  60. package/dist/modes/interactive/components/extension-selector.js +54 -12
  61. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  62. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  63. package/dist/modes/interactive/interactive-mode.js +2 -1
  64. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  65. package/dist/modes/print-mode.d.ts +2 -0
  66. package/dist/modes/print-mode.d.ts.map +1 -1
  67. package/dist/modes/print-mode.js +8 -1
  68. package/dist/modes/print-mode.js.map +1 -1
  69. package/dist/modes/rpc/rpc-client-types.d.ts +11 -0
  70. package/dist/modes/rpc/rpc-client-types.d.ts.map +1 -1
  71. package/dist/modes/rpc/rpc-client-types.js.map +1 -1
  72. package/dist/modes/rpc/rpc-client.d.ts +11 -0
  73. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  74. package/dist/modes/rpc/rpc-client.js +14 -0
  75. package/dist/modes/rpc/rpc-client.js.map +1 -1
  76. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  77. package/dist/modes/rpc/rpc-mode.js +36 -1
  78. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  79. package/dist/modes/rpc/rpc-types.d.ts +26 -0
  80. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  81. package/dist/modes/rpc/rpc-types.js.map +1 -1
  82. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  83. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  84. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  85. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  86. package/examples/extensions/with-deps/package-lock.json +2 -2
  87. package/examples/extensions/with-deps/package.json +1 -1
  88. package/package.json +9 -5
  89. package/dist/rules-engine/cache.d.ts +0 -4
  90. package/dist/rules-engine/cache.d.ts.map +0 -1
  91. package/dist/rules-engine/cache.js +0 -32
  92. package/dist/rules-engine/cache.js.map +0 -1
  93. package/dist/rules-engine/config.d.ts +0 -8
  94. package/dist/rules-engine/config.d.ts.map +0 -1
  95. package/dist/rules-engine/config.js +0 -56
  96. package/dist/rules-engine/config.js.map +0 -1
  97. package/dist/rules-engine/index.d.ts +0 -10
  98. package/dist/rules-engine/index.d.ts.map +0 -1
  99. package/dist/rules-engine/index.js +0 -393
  100. package/dist/rules-engine/index.js.map +0 -1
  101. package/dist/rules-engine/injector.d.ts +0 -5
  102. package/dist/rules-engine/injector.d.ts.map +0 -1
  103. package/dist/rules-engine/injector.js +0 -57
  104. package/dist/rules-engine/injector.js.map +0 -1
  105. package/dist/rules-engine/loader.d.ts +0 -8
  106. package/dist/rules-engine/loader.d.ts.map +0 -1
  107. package/dist/rules-engine/loader.js +0 -190
  108. package/dist/rules-engine/loader.js.map +0 -1
  109. package/dist/rules-engine/matcher.d.ts +0 -3
  110. package/dist/rules-engine/matcher.d.ts.map +0 -1
  111. package/dist/rules-engine/matcher.js +0 -48
  112. package/dist/rules-engine/matcher.js.map +0 -1
  113. package/dist/rules-engine/types.d.ts +0 -150
  114. package/dist/rules-engine/types.d.ts.map +0 -1
  115. package/dist/rules-engine/types.js +0 -2
  116. package/dist/rules-engine/types.js.map +0 -1
  117. package/examples/extensions/auto-session-title.ts +0 -82
  118. package/examples/extensions/file-snapshot.ts +0 -417
  119. package/examples/extensions/subagent/README.md +0 -172
  120. package/examples/extensions/subagent/agents/planner.md +0 -37
  121. package/examples/extensions/subagent/agents/reviewer.md +0 -35
  122. package/examples/extensions/subagent/agents/scout.md +0 -50
  123. package/examples/extensions/subagent/agents/worker.md +0 -24
  124. package/examples/extensions/subagent/agents.ts +0 -126
  125. package/examples/extensions/subagent/index.ts +0 -987
  126. package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
  127. package/examples/extensions/subagent/prompts/implement.md +0 -10
  128. package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
  129. package/examples/extensions/subagent-v2/index.ts +0 -849
@@ -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 { 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; position?: \"before\" | \"at\" }\n\t| { id?: string; type: \"navigate_tree\"; targetId: string; summarize?: boolean; skipFiles?: boolean }\n\t| { id?: string; type: \"rollback_preview\"; targetId: 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\t| { id?: string; type: \"get_tree\" }\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\t// Remote tools\n\t| { id?: string; type: \"register_remote_tool\"; tool: { name: string; description: string; parameters: object } }\n\t| { id?: string; type: \"unregister_remote_tool\"; name: string }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"remote_tool_result\";\n\t\t\ttoolCallId: string;\n\t\t\tresult: { content: Array<{ type: string; text: string }>; isError: boolean };\n\t };\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 * Entry in the session tree.\n */\nexport interface TreeEntry {\n\tid: string;\n\tparentId: string | null;\n\ttype: string;\n\tlabel?: string;\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\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: { entries: Array<{ id: string; parentId: string | null; type: string; label?: string }> };\n\t }\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// Remote tools\n\t| { id?: string; type: \"response\"; command: \"register_remote_tool\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"unregister_remote_tool\"; success: true }\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/** Emitted when a remote tool is called by the child LLM */\nexport interface RpcRemoteToolCall {\n\ttype: \"remote_tool_call\";\n\ttoolCallId: string;\n\ttoolName: string;\n\targs: Record<string, unknown>;\n}\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 { 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; position?: \"before\" | \"at\" }\n\t| { id?: string; type: \"navigate_tree\"; targetId: string; summarize?: boolean; skipFiles?: boolean }\n\t| { id?: string; type: \"rollback_preview\"; targetId: 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\t| { id?: string; type: \"get_full_messages\"; afterEntryId?: string; limit?: number }\n\t| { id?: string; type: \"get_tree\" }\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// Set Cwd\n\t| { id?: string; type: \"set_cwd\"; cwd: string }\n\n\t// Agents files\n\t| { id?: string; type: \"get_agents_files\" }\n\n\t// Remote tools\n\t| { id?: string; type: \"register_remote_tool\"; tool: { name: string; description: string; parameters: object } }\n\t| { id?: string; type: \"unregister_remote_tool\"; name: string }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"remote_tool_result\";\n\t\t\ttoolCallId: string;\n\t\t\tresult: { content: Array<{ type: string; text: string }>; isError: boolean };\n\t };\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 * Entry in the session tree.\n */\nexport interface TreeEntry {\n\tid: string;\n\tparentId: string | null;\n\ttype: string;\n\tlabel?: string;\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\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_full_messages\";\n\t\t\tsuccess: true;\n\t\t\tdata: {\n\t\t\t\tmessages: AgentMessage[];\n\t\t\t\thasMore: boolean;\n\t\t\t\ttotalCount: number;\n\t\t\t\tnextCursor: string | null;\n\t\t\t};\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: { entries: Array<{ id: string; parentId: string | null; type: string; label?: string }> };\n\t }\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// Set Cwd\n\t| { id?: string; type: \"response\"; command: \"set_cwd\"; 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// Remote tools\n\t| { id?: string; type: \"response\"; command: \"register_remote_tool\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"unregister_remote_tool\"; success: true }\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| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"select\";\n\t\t\ttitle: string;\n\t\t\toptions: string[];\n\t\t\tmultiple?: boolean;\n\t\t\ttimeout?: number;\n\t }\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/** Emitted when a remote tool is called by the child LLM */\nexport interface RpcRemoteToolCall {\n\ttype: \"remote_tool_call\";\n\ttoolCallId: string;\n\ttoolName: string;\n\targs: Record<string, unknown>;\n}\n\n// ============================================================================\n// Helper type for extracting command types\n// ============================================================================\n\nexport type RpcCommandType = RpcCommand[\"type\"];\n"]}
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "0.69.14",
3
+ "version": "0.69.21",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "0.69.14",
9
+ "version": "0.69.21",
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.14",
4
+ "version": "0.69.21",
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.14",
4
+ "version": "0.69.21",
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.14",
4
+ "version": "0.69.21",
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.14",
3
+ "version": "0.69.21",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "0.69.14",
9
+ "version": "0.69.21",
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.14",
4
+ "version": "0.69.21",
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.18",
3
+ "version": "0.69.25",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -36,15 +36,15 @@
36
36
  "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
37
37
  "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
38
38
  "test": "vitest --run",
39
+ "lint:extensions": "eslint extensions/",
39
40
  "prepublishOnly": "npm run clean && npm run build"
40
41
  },
41
42
  "dependencies": {
43
+ "@dyyz1993/pi-agent-core": "^0.69.25",
44
+ "@dyyz1993/pi-ai": "^0.69.25",
45
+ "@dyyz1993/pi-tui": "^0.69.25",
42
46
  "@mariozechner/jiti": "^2.6.2",
43
- "@dyyz1993/pi-agent-core": "^0.69.17",
44
- "@dyyz1993/pi-ai": "^0.69.17",
45
- "@dyyz1993/pi-tui": "^0.69.17",
46
47
  "@silvia-odwyer/photon-node": "^0.3.4",
47
- "typebox": "^1.1.24",
48
48
  "chalk": "^5.5.0",
49
49
  "cli-highlight": "^2.1.11",
50
50
  "diff": "^8.0.2",
@@ -57,6 +57,7 @@
57
57
  "minimatch": "^10.2.3",
58
58
  "proper-lockfile": "^4.1.2",
59
59
  "strip-ansi": "^7.1.0",
60
+ "typebox": "^1.1.24",
60
61
  "undici": "^7.19.1",
61
62
  "uuid": "^11.1.0",
62
63
  "yaml": "^2.8.2"
@@ -71,13 +72,16 @@
71
72
  "@mariozechner/clipboard": "^0.3.2"
72
73
  },
73
74
  "devDependencies": {
75
+ "@eslint/js": "^10.0.1",
74
76
  "@types/diff": "^7.0.2",
75
77
  "@types/hosted-git-info": "^3.0.5",
76
78
  "@types/ms": "^2.1.0",
77
79
  "@types/node": "^24.3.0",
78
80
  "@types/proper-lockfile": "^4.1.4",
81
+ "eslint": "^10.3.0",
79
82
  "shx": "^0.4.0",
80
83
  "typescript": "^5.7.3",
84
+ "typescript-eslint": "^8.59.1",
81
85
  "vitest": "^3.2.4"
82
86
  },
83
87
  "keywords": [
@@ -1,4 +0,0 @@
1
- import type { ParsedRule, RulesConfig } from "./types.js";
2
- export declare function getRules(projectDir: string, config: RulesConfig): Promise<ParsedRule[]>;
3
- export declare function invalidateCache(): void;
4
- //# sourceMappingURL=cache.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/rules-engine/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI1D,wBAAsB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQ7F;AAED,wBAAgB,eAAe,IAAI,IAAI,CAEtC","sourcesContent":["import { resolveDirs } from \"./config.js\";\nimport { loadRules } from \"./loader.js\";\nimport type { ParsedRule, RulesConfig } from \"./types.js\";\n\nlet cache: { rules: ParsedRule[]; loadedAt: number } | null = null;\n\nexport async function getRules(projectDir: string, config: RulesConfig): Promise<ParsedRule[]> {\n\tif (cache && Date.now() - cache.loadedAt < config.cacheTTL) {\n\t\treturn cache.rules;\n\t}\n\n\tconst rules = await loadAllRules(projectDir, config);\n\tcache = { rules, loadedAt: Date.now() };\n\treturn rules;\n}\n\nexport function invalidateCache(): void {\n\tcache = null;\n}\n\nasync function loadAllRules(projectDir: string, config: RulesConfig): Promise<ParsedRule[]> {\n\tconst sources = resolveDirs(projectDir, config);\n\tconst result: ParsedRule[] = [];\n\tconst seen = new Set<string>();\n\n\tfor (const { scope, dir, source } of sources) {\n\t\tconst ruleCache = loadRules(dir);\n\t\tfor (const rule of ruleCache.rules) {\n\t\t\tif (seen.has(rule.filePath)) continue;\n\t\t\tseen.add(rule.filePath);\n\t\t\trule.scope = scope as ParsedRule[\"scope\"];\n\t\t\trule.source = source;\n\t\t\tresult.push(rule);\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
@@ -1,32 +0,0 @@
1
- import { resolveDirs } from "./config.js";
2
- import { loadRules } from "./loader.js";
3
- let cache = null;
4
- export async function getRules(projectDir, config) {
5
- if (cache && Date.now() - cache.loadedAt < config.cacheTTL) {
6
- return cache.rules;
7
- }
8
- const rules = await loadAllRules(projectDir, config);
9
- cache = { rules, loadedAt: Date.now() };
10
- return rules;
11
- }
12
- export function invalidateCache() {
13
- cache = null;
14
- }
15
- async function loadAllRules(projectDir, config) {
16
- const sources = resolveDirs(projectDir, config);
17
- const result = [];
18
- const seen = new Set();
19
- for (const { scope, dir, source } of sources) {
20
- const ruleCache = loadRules(dir);
21
- for (const rule of ruleCache.rules) {
22
- if (seen.has(rule.filePath))
23
- continue;
24
- seen.add(rule.filePath);
25
- rule.scope = scope;
26
- rule.source = source;
27
- result.push(rule);
28
- }
29
- }
30
- return result;
31
- }
32
- //# sourceMappingURL=cache.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/rules-engine/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,IAAI,KAAK,GAAqD,IAAI,CAAC;AAEnE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,UAAkB,EAAE,MAAmB,EAAyB;IAC9F,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC5D,OAAO,KAAK,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACrD,KAAK,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IACxC,OAAO,KAAK,CAAC;AAAA,CACb;AAED,MAAM,UAAU,eAAe,GAAS;IACvC,KAAK,GAAG,IAAI,CAAC;AAAA,CACb;AAED,KAAK,UAAU,YAAY,CAAC,UAAkB,EAAE,MAAmB,EAAyB;IAC3F,MAAM,OAAO,GAAG,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChD,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QAC9C,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;YACpC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACtC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxB,IAAI,CAAC,KAAK,GAAG,KAA4B,CAAC;YAC1C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["import { resolveDirs } from \"./config.js\";\nimport { loadRules } from \"./loader.js\";\nimport type { ParsedRule, RulesConfig } from \"./types.js\";\n\nlet cache: { rules: ParsedRule[]; loadedAt: number } | null = null;\n\nexport async function getRules(projectDir: string, config: RulesConfig): Promise<ParsedRule[]> {\n\tif (cache && Date.now() - cache.loadedAt < config.cacheTTL) {\n\t\treturn cache.rules;\n\t}\n\n\tconst rules = await loadAllRules(projectDir, config);\n\tcache = { rules, loadedAt: Date.now() };\n\treturn rules;\n}\n\nexport function invalidateCache(): void {\n\tcache = null;\n}\n\nasync function loadAllRules(projectDir: string, config: RulesConfig): Promise<ParsedRule[]> {\n\tconst sources = resolveDirs(projectDir, config);\n\tconst result: ParsedRule[] = [];\n\tconst seen = new Set<string>();\n\n\tfor (const { scope, dir, source } of sources) {\n\t\tconst ruleCache = loadRules(dir);\n\t\tfor (const rule of ruleCache.rules) {\n\t\t\tif (seen.has(rule.filePath)) continue;\n\t\t\tseen.add(rule.filePath);\n\t\t\trule.scope = scope as ParsedRule[\"scope\"];\n\t\t\trule.source = source;\n\t\t\tresult.push(rule);\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
@@ -1,8 +0,0 @@
1
- import type { RulesConfig } from "./types.js";
2
- export declare function loadConfig(projectDir: string): Promise<RulesConfig>;
3
- export declare function resolveDirs(projectDir: string, config: RulesConfig): Array<{
4
- scope: string;
5
- dir: string;
6
- source: string;
7
- }>;
8
- //# sourceMappingURL=config.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/rules-engine/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAmB9C,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAwBzE;AAED,wBAAgB,WAAW,CAC1B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,WAAW,GACjB,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAevD","sourcesContent":["import * as fs from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport * as path from \"node:path\";\nimport type { RulesConfig } from \"./types.js\";\n\nconst DEFAULT_CACHE_TTL = 30_000;\n\nconst DEFAULT_DIRS = {\n\tmanaged: [\"/etc/claude-code/.claude/rules\"],\n\tuser: [path.join(homedir(), \".claude\", \"rules\"), path.join(homedir(), \".config\", \"opencode\", \"rules\")],\n\tpi: [\".pi/rules\"],\n\tproject: [\".claude/rules\", \".opencode/rules\", \".trae/rules\"],\n};\n\nfunction defaultConfig(): RulesConfig {\n\treturn {\n\t\tcacheTTL: DEFAULT_CACHE_TTL,\n\t\tnotifyOnLoad: true,\n\t\tnotifyOnMatch: true,\n\t};\n}\n\nexport async function loadConfig(projectDir: string): Promise<RulesConfig> {\n\tconst configFiles = [\n\t\t\".rules-config.json\",\n\t\t\".pi/rules-config.json\",\n\t\t\".claude/rules-config.json\",\n\t\t\".opencode/rules-config.json\",\n\t];\n\n\tfor (const name of configFiles) {\n\t\tconst fp = path.resolve(projectDir, name);\n\t\ttry {\n\t\t\tconst raw = fs.readFileSync(fp, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\treturn {\n\t\t\t\t...defaultConfig(),\n\t\t\t\t...parsed,\n\t\t\t\tcacheTTL: parsed.cacheTTL ?? DEFAULT_CACHE_TTL,\n\t\t\t\tnotifyOnLoad: parsed.notifyOnLoad ?? true,\n\t\t\t\tnotifyOnMatch: parsed.notifyOnMatch ?? true,\n\t\t\t};\n\t\t} catch {}\n\t}\n\n\treturn defaultConfig();\n}\n\nexport function resolveDirs(\n\tprojectDir: string,\n\tconfig: RulesConfig,\n): Array<{ scope: string; dir: string; source: string }> {\n\tconst sources = config.dirs || DEFAULT_DIRS;\n\tconst result: Array<{ scope: string; dir: string; source: string }> = [];\n\n\tfor (const [scope, paths] of Object.entries(sources)) {\n\t\tfor (const p of paths) {\n\t\t\tresult.push({\n\t\t\t\tscope,\n\t\t\t\tdir: p.startsWith(\"/\") || p.startsWith(\"~\") ? p.replace(/^~/, homedir()) : path.resolve(projectDir, p),\n\t\t\t\tsource: p,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
@@ -1,56 +0,0 @@
1
- import * as fs from "node:fs";
2
- import { homedir } from "node:os";
3
- import * as path from "node:path";
4
- const DEFAULT_CACHE_TTL = 30_000;
5
- const DEFAULT_DIRS = {
6
- managed: ["/etc/claude-code/.claude/rules"],
7
- user: [path.join(homedir(), ".claude", "rules"), path.join(homedir(), ".config", "opencode", "rules")],
8
- pi: [".pi/rules"],
9
- project: [".claude/rules", ".opencode/rules", ".trae/rules"],
10
- };
11
- function defaultConfig() {
12
- return {
13
- cacheTTL: DEFAULT_CACHE_TTL,
14
- notifyOnLoad: true,
15
- notifyOnMatch: true,
16
- };
17
- }
18
- export async function loadConfig(projectDir) {
19
- const configFiles = [
20
- ".rules-config.json",
21
- ".pi/rules-config.json",
22
- ".claude/rules-config.json",
23
- ".opencode/rules-config.json",
24
- ];
25
- for (const name of configFiles) {
26
- const fp = path.resolve(projectDir, name);
27
- try {
28
- const raw = fs.readFileSync(fp, "utf-8");
29
- const parsed = JSON.parse(raw);
30
- return {
31
- ...defaultConfig(),
32
- ...parsed,
33
- cacheTTL: parsed.cacheTTL ?? DEFAULT_CACHE_TTL,
34
- notifyOnLoad: parsed.notifyOnLoad ?? true,
35
- notifyOnMatch: parsed.notifyOnMatch ?? true,
36
- };
37
- }
38
- catch { }
39
- }
40
- return defaultConfig();
41
- }
42
- export function resolveDirs(projectDir, config) {
43
- const sources = config.dirs || DEFAULT_DIRS;
44
- const result = [];
45
- for (const [scope, paths] of Object.entries(sources)) {
46
- for (const p of paths) {
47
- result.push({
48
- scope,
49
- dir: p.startsWith("/") || p.startsWith("~") ? p.replace(/^~/, homedir()) : path.resolve(projectDir, p),
50
- source: p,
51
- });
52
- }
53
- }
54
- return result;
55
- }
56
- //# sourceMappingURL=config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/rules-engine/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEjC,MAAM,YAAY,GAAG;IACpB,OAAO,EAAE,CAAC,gCAAgC,CAAC;IAC3C,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IACtG,EAAE,EAAE,CAAC,WAAW,CAAC;IACjB,OAAO,EAAE,CAAC,eAAe,EAAE,iBAAiB,EAAE,aAAa,CAAC;CAC5D,CAAC;AAEF,SAAS,aAAa,GAAgB;IACrC,OAAO;QACN,QAAQ,EAAE,iBAAiB;QAC3B,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,IAAI;KACnB,CAAC;AAAA,CACF;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB,EAAwB;IAC1E,MAAM,WAAW,GAAG;QACnB,oBAAoB;QACpB,uBAAuB;QACvB,2BAA2B;QAC3B,6BAA6B;KAC7B,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,OAAO;gBACN,GAAG,aAAa,EAAE;gBAClB,GAAG,MAAM;gBACT,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,iBAAiB;gBAC9C,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,IAAI;gBACzC,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,IAAI;aAC3C,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IAED,OAAO,aAAa,EAAE,CAAC;AAAA,CACvB;AAED,MAAM,UAAU,WAAW,CAC1B,UAAkB,EAClB,MAAmB,EACqC;IACxD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,YAAY,CAAC;IAC5C,MAAM,MAAM,GAA0D,EAAE,CAAC;IAEzE,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACtD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC;gBACX,KAAK;gBACL,GAAG,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;gBACtG,MAAM,EAAE,CAAC;aACT,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["import * as fs from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport * as path from \"node:path\";\nimport type { RulesConfig } from \"./types.js\";\n\nconst DEFAULT_CACHE_TTL = 30_000;\n\nconst DEFAULT_DIRS = {\n\tmanaged: [\"/etc/claude-code/.claude/rules\"],\n\tuser: [path.join(homedir(), \".claude\", \"rules\"), path.join(homedir(), \".config\", \"opencode\", \"rules\")],\n\tpi: [\".pi/rules\"],\n\tproject: [\".claude/rules\", \".opencode/rules\", \".trae/rules\"],\n};\n\nfunction defaultConfig(): RulesConfig {\n\treturn {\n\t\tcacheTTL: DEFAULT_CACHE_TTL,\n\t\tnotifyOnLoad: true,\n\t\tnotifyOnMatch: true,\n\t};\n}\n\nexport async function loadConfig(projectDir: string): Promise<RulesConfig> {\n\tconst configFiles = [\n\t\t\".rules-config.json\",\n\t\t\".pi/rules-config.json\",\n\t\t\".claude/rules-config.json\",\n\t\t\".opencode/rules-config.json\",\n\t];\n\n\tfor (const name of configFiles) {\n\t\tconst fp = path.resolve(projectDir, name);\n\t\ttry {\n\t\t\tconst raw = fs.readFileSync(fp, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\treturn {\n\t\t\t\t...defaultConfig(),\n\t\t\t\t...parsed,\n\t\t\t\tcacheTTL: parsed.cacheTTL ?? DEFAULT_CACHE_TTL,\n\t\t\t\tnotifyOnLoad: parsed.notifyOnLoad ?? true,\n\t\t\t\tnotifyOnMatch: parsed.notifyOnMatch ?? true,\n\t\t\t};\n\t\t} catch {}\n\t}\n\n\treturn defaultConfig();\n}\n\nexport function resolveDirs(\n\tprojectDir: string,\n\tconfig: RulesConfig,\n): Array<{ scope: string; dir: string; source: string }> {\n\tconst sources = config.dirs || DEFAULT_DIRS;\n\tconst result: Array<{ scope: string; dir: string; source: string }> = [];\n\n\tfor (const [scope, paths] of Object.entries(sources)) {\n\t\tfor (const p of paths) {\n\t\t\tresult.push({\n\t\t\t\tscope,\n\t\t\t\tdir: p.startsWith(\"/\") || p.startsWith(\"~\") ? p.replace(/^~/, homedir()) : path.resolve(projectDir, p),\n\t\t\t\tsource: p,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
@@ -1,10 +0,0 @@
1
- import { type ExtensionAPI } from "@dyyz1993/pi-coding-agent";
2
- export { ServerChannel } from "../core/extensions/server-channel.js";
3
- export { getRules, invalidateCache } from "./cache.js";
4
- export { loadConfig, resolveDirs } from "./config.js";
5
- export { buildCompactContext, buildSystemPromptSection, buildToolContextSection } from "./injector.js";
6
- export { loadRules, parseFrontmatter, parseRuleFile } from "./loader.js";
7
- export { matchesAnyGlob, matchGlob } from "./matcher.js";
8
- export type { InjectedPayload, LifecycleEntry, MatchedPayload, MatchRecord, ParsedRule, ReloadedPayload, RuleDetail, RuleFrontmatter, RuleScope, RuleSeverity, RulesChannelContract, RulesChannelEvent, RulesConfig, ScannedDir, SnapshotPayload, UnloadedPayload, } from "./types.js";
9
- export default function rulesEnginePlugin(pi: ExtensionAPI): void;
10
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules-engine/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAqB1E,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,YAAY,EACX,eAAe,EACf,cAAc,EACd,cAAc,EACd,WAAW,EACX,UAAU,EACV,eAAe,EACf,UAAU,EACV,eAAe,EACf,SAAS,EACT,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,eAAe,EACf,eAAe,GACf,MAAM,YAAY,CAAC;AAIpB,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EAAE,EAAE,YAAY,QAqbzD","sourcesContent":["import { Type } from \"@dyyz1993/pi-ai\";\nimport { defineTool, type ExtensionAPI } from \"@dyyz1993/pi-coding-agent\";\nimport { ServerChannel } from \"../core/extensions/server-channel.js\";\nimport { getRules, invalidateCache } from \"./cache.js\";\nimport { loadConfig } from \"./config.js\";\nimport { buildSystemPromptSection, buildToolContextSection } from \"./injector.js\";\nimport { matchesAnyGlob } from \"./matcher.js\";\nimport type {\n\tInjectedPayload,\n\tLifecycleEntry,\n\tMatchedRuleDetail,\n\tMatchRecord,\n\tParsedRule,\n\tRuleDetail,\n\tRuleSeverity,\n\tRulesChannelContract,\n\tRulesChannelEvent,\n\tRulesConfig,\n\tScannedDir,\n\tSnapshotPayload,\n} from \"./types.js\";\n\nexport { ServerChannel } from \"../core/extensions/server-channel.js\";\nexport { getRules, invalidateCache } from \"./cache.js\";\nexport { loadConfig, resolveDirs } from \"./config.js\";\nexport { buildCompactContext, buildSystemPromptSection, buildToolContextSection } from \"./injector.js\";\nexport { loadRules, parseFrontmatter, parseRuleFile } from \"./loader.js\";\nexport { matchesAnyGlob, matchGlob } from \"./matcher.js\";\nexport type {\n\tInjectedPayload,\n\tLifecycleEntry,\n\tMatchedPayload,\n\tMatchRecord,\n\tParsedRule,\n\tReloadedPayload,\n\tRuleDetail,\n\tRuleFrontmatter,\n\tRuleScope,\n\tRuleSeverity,\n\tRulesChannelContract,\n\tRulesChannelEvent,\n\tRulesConfig,\n\tScannedDir,\n\tSnapshotPayload,\n\tUnloadedPayload,\n} from \"./types.js\";\n\nconst READ_TOOLS = new Set([\"read\", \"grep\", \"glob\"]);\n\nexport default function rulesEnginePlugin(pi: ExtensionAPI) {\n\tlet config: RulesConfig | null = null;\n\tlet rules: ParsedRule[] = [];\n\tlet cachedMatchHash = \"\";\n\tlet hasSentSnapshot = false;\n\tlet _lastCwd = \"\";\n\tlet lastMessages: unknown[] = [];\n\n\tfunction rebuildMatchHistory(messages: unknown[]): MatchRecord[] {\n\t\tconst history: MatchRecord[] = [];\n\t\tfor (const msg of messages) {\n\t\t\tif ((msg as Record<string, unknown>).role !== \"toolResult\") continue;\n\t\t\tconst details = (msg as { details?: Record<string, unknown> }).details;\n\t\t\tif (!details?.rulesMatched) continue;\n\t\t\tconst rulesMatched = details.rulesMatched as MatchedRuleDetail[];\n\t\t\thistory.push({\n\t\t\t\tfilePath: (details.matchedFilePath as string) || \"\",\n\t\t\t\truleNames: rulesMatched.map((r) => r.name),\n\t\t\t\ttoolName: (msg as { toolName?: string }).toolName || \"\",\n\t\t\t\ttoolCallId: (msg as { toolCallId?: string }).toolCallId || \"\",\n\t\t\t\tseverity: rulesMatched.some((r) => r.severity === \"critical\" || r.severity === \"high\") ? \"warning\" : \"info\",\n\t\t\t\ttimestamp: (msg as { timestamp?: number }).timestamp || 0,\n\t\t\t\tmatchedRuleDetails: rulesMatched,\n\t\t\t});\n\t\t}\n\t\treturn history;\n\t}\n\n\tconst rawChannel = pi.registerChannel(\"rules-engine\");\n\tconst channel = new ServerChannel<RulesChannelContract>(rawChannel);\n\n\tchannel.handle(\"getSnapshot\", (params) => {\n\t\tconst unconditional = getUnconditionalRules();\n\t\tconst conditional = getConditionalRules();\n\t\tconst matchHistory = rebuildMatchHistory(lastMessages);\n\t\treturn {\n\t\t\ttype: \"snapshot\" as const,\n\t\t\trules: rules.map(toRuleDetail),\n\t\t\tinjectedRuleNames: unconditional.map((r) => r.name),\n\t\t\ttotalRules: rules.length,\n\t\t\tunconditionalCount: unconditional.length,\n\t\t\tconditionalCount: conditional.length,\n\t\t\tmatchHistory,\n\t\t\tlifecycleLog: [] as LifecycleEntry[],\n\t\t\tloadedAt: Date.now(),\n\t\t\tcacheTTL: config?.cacheTTL || 30000,\n\t\t};\n\t});\n\n\tfunction getUnconditionalRules(): ParsedRule[] {\n\t\treturn rules.filter((r) => r.isUnconditional);\n\t}\n\n\tfunction getConditionalRules(): ParsedRule[] {\n\t\treturn rules.filter((r) => !r.isUnconditional);\n\t}\n\n\tfunction getMatchingRules(targetPath: string): ParsedRule[] {\n\t\treturn getConditionalRules().filter((rule) => {\n\t\t\tconst globs = rule.frontmatter.paths;\n\t\t\tif (!globs || globs.length === 0) return false;\n\t\t\treturn matchesAnyGlob(globs, targetPath);\n\t\t});\n\t}\n\n\tasync function refreshRules(cwd: string): Promise<void> {\n\t\tconfig = await loadConfig(cwd);\n\t\trules = await getRules(cwd, config);\n\t}\n\n\tfunction toRuleDetail(r: ParsedRule): RuleDetail {\n\t\treturn {\n\t\t\tname: r.name,\n\t\t\ttitle: r.title,\n\t\t\tfilePath: r.filePath,\n\t\t\tscope: r.scope,\n\t\t\tsource: r.source,\n\t\t\tseverity: r.frontmatter.severity || (\"medium\" as RuleSeverity),\n\t\t\tisUnconditional: r.isUnconditional,\n\t\t\tpaths: r.frontmatter.paths || [],\n\t\t\tdescription: r.frontmatter.description,\n\t\t};\n\t}\n\n\tfunction buildSnapshot(matchHistory: MatchRecord[], lifecycleLog: LifecycleEntry[]): RulesChannelEvent {\n\t\tconst unconditional = getUnconditionalRules();\n\t\tconst conditional = getConditionalRules();\n\t\treturn {\n\t\t\ttype: \"snapshot\",\n\t\t\trules: rules.map(toRuleDetail),\n\t\t\tinjectedRuleNames: unconditional.map((r) => r.name),\n\t\t\ttotalRules: rules.length,\n\t\t\tunconditionalCount: unconditional.length,\n\t\t\tconditionalCount: conditional.length,\n\t\t\tmatchHistory,\n\t\t\tlifecycleLog,\n\t\t\tloadedAt: Date.now(),\n\t\t\tcacheTTL: config?.cacheTTL || 30000,\n\t\t};\n\t}\n\n\tfunction extractTargetPath(args: Record<string, unknown>): string | undefined {\n\t\tif (\"filePath\" in args && typeof args.filePath === \"string\") return args.filePath;\n\t\tif (\"path\" in args && typeof args.path === \"string\") return args.path;\n\t\tif (\"pattern\" in args && typeof args.pattern === \"string\") return args.pattern;\n\t\treturn undefined;\n\t}\n\n\tpi.registerTool(\n\t\tdefineTool({\n\t\t\tname: \"rules_list\",\n\t\t\tlabel: \"List Rules\",\n\t\t\tdescription: \"List all discovered rules from all configured directories across all scopes\",\n\t\t\tparameters: Type.Object({}),\n\t\t\tasync execute() {\n\t\t\t\tconst unconditional = getUnconditionalRules();\n\t\t\t\tconst conditional = getConditionalRules();\n\n\t\t\t\tconst byScope: Record<string, number> = {};\n\t\t\t\tfor (const r of rules) {\n\t\t\t\t\tbyScope[r.scope] = (byScope[r.scope] || 0) + 1;\n\t\t\t\t}\n\n\t\t\t\tlet output = `# Loaded Rules (${rules.length})\\n\\n`;\n\t\t\t\toutput += `Scopes: ${Object.entries(byScope)\n\t\t\t\t\t.map(([k, v]) => `${k}: ${v}`)\n\t\t\t\t\t.join(\", \")}\\n\\n`;\n\n\t\t\t\toutput += `**Unconditional** (${unconditional.length}):\\n`;\n\t\t\t\tfor (const rule of unconditional) {\n\t\t\t\t\toutput += `- ${rule.title} (${rule.source})\\n`;\n\t\t\t\t}\n\n\t\t\t\toutput += `\\n**Conditional** (${conditional.length}):\\n`;\n\t\t\t\tfor (const rule of conditional) {\n\t\t\t\t\toutput += `- ${rule.title} [${rule.frontmatter.paths?.join(\", \")}] (${rule.source})\\n`;\n\t\t\t\t}\n\n\t\t\t\treturn { content: [{ type: \"text\", text: output }], details: undefined };\n\t\t\t},\n\t\t}),\n\t);\n\n\tpi.registerTool(\n\t\tdefineTool({\n\t\t\tname: \"rules_match\",\n\t\t\tlabel: \"Match Rules\",\n\t\t\tdescription: \"Find conditional rules that match a given file path by glob pattern\",\n\t\t\tparameters: Type.Object({\n\t\t\t\tfilePath: Type.String({ description: \"File path to match\" }),\n\t\t\t}),\n\t\t\tasync execute(_id, params) {\n\t\t\t\tconst matching = getMatchingRules(params.filePath);\n\t\t\t\tconst unconditional = getUnconditionalRules();\n\n\t\t\t\tlet output = `# Rule Match: ${params.filePath}\\n\\n`;\n\t\t\t\toutput += `**Unconditional** (always active, ${unconditional.length}):\\n`;\n\t\t\t\tfor (const r of unconditional) {\n\t\t\t\t\toutput += `- ${r.title}\\n`;\n\t\t\t\t}\n\t\t\t\toutput += `\\n**Conditional matches** (${matching.length}):\\n`;\n\t\t\t\tfor (const r of matching) {\n\t\t\t\t\tconst sev = r.frontmatter.severity || \"medium\";\n\t\t\t\t\toutput += `- [${sev}] ${r.title} (${r.frontmatter.paths?.join(\", \")})\\n`;\n\t\t\t\t}\n\n\t\t\t\treturn { content: [{ type: \"text\", text: output }], details: undefined };\n\t\t\t},\n\t\t}),\n\t);\n\n\tpi.registerTool(\n\t\tdefineTool({\n\t\t\tname: \"rules_reload\",\n\t\t\tlabel: \"Reload Rules\",\n\t\t\tdescription: \"Force reload all rules from disk (clears cache and re-reads config)\",\n\t\t\tparameters: Type.Object({}),\n\t\t\tasync execute(_id, _params, _signal, _onUpdate, ctx) {\n\t\t\t\tinvalidateCache();\n\t\t\t\tawait refreshRules(ctx.cwd);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Rules reloaded: ${rules.length} total (${getUnconditionalRules().length} unconditional, ${getConditionalRules().length} conditional)`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t},\n\t\t}),\n\t);\n\n\tpi.registerTool(\n\t\tdefineTool({\n\t\t\tname: \"rules_show\",\n\t\t\tlabel: \"Show Rule\",\n\t\t\tdescription: \"Show the full content of a specific rule by name\",\n\t\t\tparameters: Type.Object({\n\t\t\t\tname: Type.String({ description: \"Rule name (filename without .md)\" }),\n\t\t\t}),\n\t\t\tasync execute(_id, params) {\n\t\t\t\tconst rule = rules.find((r) => r.name === params.name);\n\t\t\t\tif (!rule) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Rule '${params.name}' not found` }],\n\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tlet output = `# ${rule.title}\\n\\n`;\n\t\t\t\toutput += `- **Name**: ${rule.name}\\n`;\n\t\t\t\toutput += `- **Scope**: ${rule.scope}\\n`;\n\t\t\t\toutput += `- **Source**: ${rule.source}\\n`;\n\t\t\t\toutput += `- **File**: ${rule.filePath}\\n`;\n\t\t\t\tif (rule.frontmatter.paths?.length) {\n\t\t\t\t\toutput += `- **Paths**: ${rule.frontmatter.paths.join(\", \")}\\n`;\n\t\t\t\t}\n\t\t\t\tif (rule.frontmatter.description) {\n\t\t\t\t\toutput += `- **Description**: ${rule.frontmatter.description}\\n`;\n\t\t\t\t}\n\t\t\t\toutput += `\\n${rule.content}`;\n\n\t\t\t\treturn { content: [{ type: \"text\", text: output }], details: undefined };\n\t\t\t},\n\t\t}),\n\t);\n\n\tpi.registerCommand(\"rules\", {\n\t\tdescription: \"Rules management (list, reload, check <path>, active)\",\n\t\thandler: async (args, ctx) => {\n\t\t\tconst parts = args.trim().split(/\\s+/);\n\t\t\tconst sub = parts[0] || \"list\";\n\n\t\t\tif (sub === \"list\" || sub === \"ls\") {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`${rules.length} rules loaded (${getUnconditionalRules().length} unconditional, ${getConditionalRules().length} conditional)`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t} else if (sub === \"reload\") {\n\t\t\t\tinvalidateCache();\n\t\t\t\tawait refreshRules(ctx.cwd);\n\t\t\t\tctx.ui.notify(`Rules reloaded: ${rules.length} total`, \"info\");\n\t\t\t} else if (sub === \"check\" && parts[1]) {\n\t\t\t\tconst target = parts.slice(1).join(\" \");\n\t\t\t\tconst matching = getMatchingRules(target);\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\tmatching.length > 0\n\t\t\t\t\t\t? `${matching.length} rules match ${target}: ${matching.map((r) => r.title).join(\", \")}`\n\t\t\t\t\t\t: `No conditional rules match ${target}`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t} else if (sub === \"active\") {\n\t\t\t\tconst active = getUnconditionalRules();\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`Active: ${active.length} unconditional (in system prompt), ${getConditionalRules().length} conditional (on file match)`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tctx.ui.notify(\"Usage: /rules [list|reload|check <path>|active]\", \"info\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\t_lastCwd = ctx.cwd;\n\t\tawait refreshRules(ctx.cwd);\n\t\tctx.ui.setStatus(\"rules-engine\", `Rules: ${rules.length}`);\n\n\t\tconst unconditional = getUnconditionalRules();\n\t\tconst conditional = getConditionalRules();\n\n\t\tif (!hasSentSnapshot) {\n\t\t\thasSentSnapshot = true;\n\n\t\t\tconst scopeGroups = new Map<string, ParsedRule[]>();\n\t\t\tfor (const r of rules) {\n\t\t\t\tconst list = scopeGroups.get(r.scope) || [];\n\t\t\t\tlist.push(r);\n\t\t\t\tscopeGroups.set(r.scope, list);\n\t\t\t}\n\n\t\t\tconst scannedDirs: ScannedDir[] = [...scopeGroups.entries()].map(([scope, scopeRules]) => ({\n\t\t\t\tdir: scopeRules[0]?.source || scope,\n\t\t\t\tfileCount: scopeRules.length,\n\t\t\t\truleNames: scopeRules.map((r) => r.name),\n\t\t\t}));\n\n\t\t\tchannel.emit(\n\t\t\t\t\"snapshot\",\n\t\t\t\tbuildSnapshot(\n\t\t\t\t\t[],\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tevent: \"loaded\",\n\t\t\t\t\t\t\tmessage: `Loaded ${rules.length} rules (${unconditional.length} unconditional, ${conditional.length} conditional)`,\n\t\t\t\t\t\t\truleCount: rules.length,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\tscannedDirs,\n\t\t\t\t\t\t\t\tconfigSource: config ? \".rules-config.json\" : \"default\",\n\t\t\t\t\t\t\t\tcacheHit: false,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t});\n\n\tpi.on(\"before_agent_start\", async (event) => {\n\t\tconst unconditional = getUnconditionalRules();\n\n\t\tif (unconditional.length === 0) {\n\t\t\tchannel.emit(\"injected\", {\n\t\t\t\ttype: \"injected\",\n\t\t\t\truleNames: [],\n\t\t\t\tsystemPromptLength: event.systemPrompt.length,\n\t\t\t});\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst sources = [...new Set(unconditional.map((r) => r.source))];\n\t\tconst section = buildSystemPromptSection(unconditional, sources);\n\t\tconst newPrompt = event.systemPrompt + section;\n\n\t\tchannel.emit(\"injected\", {\n\t\t\ttype: \"injected\",\n\t\t\truleNames: unconditional.map((r) => r.name),\n\t\t\tsystemPromptLength: newPrompt.length,\n\t\t});\n\n\t\treturn {\n\t\t\tsystemPrompt: newPrompt,\n\t\t};\n\t});\n\n\tpi.on(\"tool_result\", async (event) => {\n\t\tif (!READ_TOOLS.has(event.toolName)) return undefined;\n\n\t\tconst targetPath = extractTargetPath(event.input);\n\t\tif (!targetPath) return undefined;\n\n\t\tconst matching = getMatchingRules(targetPath);\n\t\tif (matching.length === 0) return undefined;\n\n\t\tconst matchedRuleDetails: MatchedRuleDetail[] = matching.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttitle: r.title,\n\t\t\tseverity: r.frontmatter.severity || (\"medium\" as RuleSeverity),\n\t\t\tmatchedGlob:\n\t\t\t\tr.frontmatter.paths?.find((p) => matchesAnyGlob([p], targetPath)) || r.frontmatter.paths?.[0] || \"\",\n\t\t}));\n\n\t\tconst contextSection = buildToolContextSection(matching, targetPath);\n\n\t\tconst hasCritical = matching.some((r) => r.frontmatter.severity === \"critical\");\n\t\tconst hasHigh = matching.some((r) => r.frontmatter.severity === \"high\");\n\n\t\tchannel.emit(\"matched\", {\n\t\t\ttype: \"matched\",\n\t\t\tfilePath: targetPath,\n\t\t\tmatchedRules: matchedRuleDetails,\n\t\t\ttoolName: event.toolName,\n\t\t\ttoolCallId: event.toolCallId,\n\t\t\tseverity: hasCritical ? \"warning\" : hasHigh ? \"warning\" : \"info\",\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\treturn {\n\t\t\tcontent: [...event.content, { type: \"text\" as const, text: `\\n\\n${contextSection}` }],\n\t\t\tdetails: {\n\t\t\t\t...((event.details as Record<string, unknown>) || {}),\n\t\t\t\trulesMatched: matchedRuleDetails,\n\t\t\t\tmatchedFilePath: targetPath,\n\t\t\t},\n\t\t};\n\t});\n\n\tpi.on(\"context\", async (event) => {\n\t\tlastMessages = event.messages;\n\t\tconst matchHistory = rebuildMatchHistory(event.messages);\n\n\t\tconst hash = JSON.stringify(matchHistory.map((r) => `${r.toolCallId}:${r.filePath}`));\n\t\tif (hash !== cachedMatchHash) {\n\t\t\tcachedMatchHash = hash;\n\t\t\tconst unconditional = getUnconditionalRules();\n\t\t\tconst conditional = getConditionalRules();\n\t\t\tchannel.emit(\"snapshot\", {\n\t\t\t\ttype: \"snapshot\",\n\t\t\t\trules: rules.map(toRuleDetail),\n\t\t\t\tinjectedRuleNames: unconditional.map((r) => r.name),\n\t\t\t\ttotalRules: rules.length,\n\t\t\t\tunconditionalCount: unconditional.length,\n\t\t\t\tconditionalCount: conditional.length,\n\t\t\t\tmatchHistory,\n\t\t\t\tlifecycleLog: [],\n\t\t\t\tloadedAt: Date.now(),\n\t\t\t\tcacheTTL: config?.cacheTTL || 30000,\n\t\t\t});\n\t\t}\n\n\t\treturn undefined;\n\t});\n\n\tpi.on(\"session_compact\", async (_event, ctx) => {\n\t\tcachedMatchHash = \"\";\n\t\tlastMessages = [];\n\t\tctx.ui.setStatus(\"rules-engine\", `Rules: ${rules.length} (re-injected after compact)`);\n\t});\n\n\tpi.on(\"turn_end\", async () => {\n\t\tif (lastMessages.length === 0 && rules.length > 0) return;\n\t\tconst matchHistory = rebuildMatchHistory(lastMessages);\n\t\tconst hash = JSON.stringify(matchHistory.map((r) => `${r.toolCallId}:${r.filePath}`));\n\t\tif (hash !== cachedMatchHash) {\n\t\t\tcachedMatchHash = hash;\n\t\t\tchannel.emit(\"snapshot\", {\n\t\t\t\ttype: \"snapshot\",\n\t\t\t\trules: rules.map(toRuleDetail),\n\t\t\t\tinjectedRuleNames: getUnconditionalRules().map((r) => r.name),\n\t\t\t\ttotalRules: rules.length,\n\t\t\t\tunconditionalCount: getUnconditionalRules().length,\n\t\t\t\tconditionalCount: getConditionalRules().length,\n\t\t\t\tmatchHistory,\n\t\t\t\tlifecycleLog: [],\n\t\t\t\tloadedAt: Date.now(),\n\t\t\t\tcacheTTL: config?.cacheTTL || 30000,\n\t\t\t});\n\t\t}\n\t});\n\n\tpi.on(\"session_shutdown\", async (_event, ctx) => {\n\t\tchannel.emit(\"unloaded\", { type: \"unloaded\", reason: \"session_shutdown\" });\n\t\tctx.ui.setStatus(\"rules-engine\", undefined);\n\t});\n}\n"]}