@mariozechner/pi-coding-agent 0.34.2 → 0.35.0

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 (251) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/README.md +233 -105
  3. package/dist/cli/args.d.ts +3 -4
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +13 -18
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/config.d.ts +2 -2
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +3 -3
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +39 -50
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +166 -197
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  16. package/dist/core/compaction/branch-summarization.js +3 -3
  17. package/dist/core/compaction/branch-summarization.js.map +1 -1
  18. package/dist/core/compaction/compaction.d.ts +1 -1
  19. package/dist/core/compaction/compaction.d.ts.map +1 -1
  20. package/dist/core/compaction/compaction.js +6 -5
  21. package/dist/core/compaction/compaction.js.map +1 -1
  22. package/dist/core/event-bus.d.ts +9 -0
  23. package/dist/core/event-bus.d.ts.map +1 -0
  24. package/dist/core/event-bus.js +25 -0
  25. package/dist/core/event-bus.js.map +1 -0
  26. package/dist/core/exec.d.ts +1 -1
  27. package/dist/core/exec.d.ts.map +1 -1
  28. package/dist/core/exec.js +1 -1
  29. package/dist/core/exec.js.map +1 -1
  30. package/dist/core/extensions/index.d.ts +10 -0
  31. package/dist/core/extensions/index.d.ts.map +1 -0
  32. package/dist/core/extensions/index.js +9 -0
  33. package/dist/core/extensions/index.js.map +1 -0
  34. package/dist/core/extensions/loader.d.ts +21 -0
  35. package/dist/core/extensions/loader.d.ts.map +1 -0
  36. package/dist/core/extensions/loader.js +400 -0
  37. package/dist/core/extensions/loader.js.map +1 -0
  38. package/dist/core/extensions/runner.d.ts +88 -0
  39. package/dist/core/extensions/runner.d.ts.map +1 -0
  40. package/dist/core/{hooks → extensions}/runner.js +52 -141
  41. package/dist/core/extensions/runner.js.map +1 -0
  42. package/dist/core/extensions/types.d.ts +461 -0
  43. package/dist/core/extensions/types.d.ts.map +1 -0
  44. package/dist/core/{hooks → extensions}/types.js +7 -4
  45. package/dist/core/extensions/types.js.map +1 -0
  46. package/dist/core/extensions/wrapper.d.ts +25 -0
  47. package/dist/core/extensions/wrapper.d.ts.map +1 -0
  48. package/dist/core/{hooks/tool-wrapper.js → extensions/wrapper.js} +39 -24
  49. package/dist/core/extensions/wrapper.js.map +1 -0
  50. package/dist/core/index.d.ts +2 -2
  51. package/dist/core/index.d.ts.map +1 -1
  52. package/dist/core/index.js +3 -2
  53. package/dist/core/index.js.map +1 -1
  54. package/dist/core/messages.d.ts +7 -7
  55. package/dist/core/messages.d.ts.map +1 -1
  56. package/dist/core/messages.js +4 -4
  57. package/dist/core/messages.js.map +1 -1
  58. package/dist/core/prompt-templates.d.ts +40 -0
  59. package/dist/core/prompt-templates.d.ts.map +1 -0
  60. package/dist/core/{slash-commands.js → prompt-templates.js} +31 -31
  61. package/dist/core/prompt-templates.js.map +1 -0
  62. package/dist/core/sdk.d.ts +29 -52
  63. package/dist/core/sdk.d.ts.map +1 -1
  64. package/dist/core/sdk.js +111 -211
  65. package/dist/core/sdk.js.map +1 -1
  66. package/dist/core/session-manager.d.ts +17 -17
  67. package/dist/core/session-manager.d.ts.map +1 -1
  68. package/dist/core/session-manager.js +25 -10
  69. package/dist/core/session-manager.js.map +1 -1
  70. package/dist/core/settings-manager.d.ts +3 -6
  71. package/dist/core/settings-manager.d.ts.map +1 -1
  72. package/dist/core/settings-manager.js +4 -11
  73. package/dist/core/settings-manager.js.map +1 -1
  74. package/dist/core/system-prompt.d.ts.map +1 -1
  75. package/dist/core/system-prompt.js +4 -2
  76. package/dist/core/system-prompt.js.map +1 -1
  77. package/dist/index.d.ts +4 -5
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +5 -6
  80. package/dist/index.js.map +1 -1
  81. package/dist/main.d.ts.map +1 -1
  82. package/dist/main.js +36 -33
  83. package/dist/main.js.map +1 -1
  84. package/dist/migrations.d.ts +7 -2
  85. package/dist/migrations.d.ts.map +1 -1
  86. package/dist/migrations.js +93 -4
  87. package/dist/migrations.js.map +1 -1
  88. package/dist/modes/interactive/components/bordered-loader.d.ts +1 -1
  89. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  90. package/dist/modes/interactive/components/bordered-loader.js +1 -1
  91. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  92. package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
  93. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  94. package/dist/modes/interactive/components/branch-summary-message.js +1 -1
  95. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  96. package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
  97. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  98. package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
  99. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  100. package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
  101. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  102. package/dist/modes/interactive/components/custom-editor.js +4 -4
  103. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  104. package/dist/modes/interactive/components/custom-message.d.ts +18 -0
  105. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  106. package/dist/modes/interactive/components/{hook-message.js → custom-message.js} +3 -3
  107. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  108. package/dist/modes/interactive/components/dynamic-border.d.ts +2 -2
  109. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/dynamic-border.js +2 -2
  111. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  112. package/dist/modes/interactive/components/{hook-editor.d.ts → extension-editor.d.ts} +3 -3
  113. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  114. package/dist/modes/interactive/components/{hook-editor.js → extension-editor.js} +4 -4
  115. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  116. package/dist/modes/interactive/components/{hook-input.d.ts → extension-input.d.ts} +3 -3
  117. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  118. package/dist/modes/interactive/components/{hook-input.js → extension-input.js} +3 -3
  119. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  120. package/dist/modes/interactive/components/{hook-selector.d.ts → extension-selector.d.ts} +3 -3
  121. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  122. package/dist/modes/interactive/components/{hook-selector.js → extension-selector.js} +3 -3
  123. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  124. package/dist/modes/interactive/components/footer.d.ts +3 -3
  125. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/footer.js +8 -8
  127. package/dist/modes/interactive/components/footer.js.map +1 -1
  128. package/dist/modes/interactive/components/tool-execution.d.ts +3 -3
  129. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/tool-execution.js +9 -9
  131. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  132. package/dist/modes/interactive/interactive-mode.d.ts +37 -44
  133. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  134. package/dist/modes/interactive/interactive-mode.js +143 -189
  135. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  136. package/dist/modes/print-mode.d.ts.map +1 -1
  137. package/dist/modes/print-mode.js +10 -33
  138. package/dist/modes/print-mode.js.map +1 -1
  139. package/dist/modes/rpc/rpc-client.d.ts +3 -3
  140. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  141. package/dist/modes/rpc/rpc-client.js +3 -3
  142. package/dist/modes/rpc/rpc-client.js.map +1 -1
  143. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  144. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  145. package/dist/modes/rpc/rpc-mode.js +33 -57
  146. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  147. package/dist/modes/rpc/rpc-types.d.ts +16 -16
  148. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  149. package/dist/modes/rpc/rpc-types.js.map +1 -1
  150. package/docs/extensions.md +1053 -0
  151. package/docs/rpc.md +4 -4
  152. package/docs/sdk.md +62 -93
  153. package/docs/session.md +22 -19
  154. package/docs/skills.md +1 -1
  155. package/docs/tui.md +1 -1
  156. package/examples/README.md +9 -15
  157. package/examples/extensions/README.md +141 -0
  158. package/examples/{hooks → extensions}/auto-commit-on-exit.ts +3 -3
  159. package/examples/extensions/chalk-logger.ts +26 -0
  160. package/examples/{hooks → extensions}/confirm-destructive.ts +3 -3
  161. package/examples/{hooks → extensions}/custom-compaction.ts +6 -6
  162. package/examples/{hooks → extensions}/dirty-repo-guard.ts +8 -4
  163. package/examples/{hooks → extensions}/file-trigger.ts +3 -3
  164. package/examples/{hooks → extensions}/git-checkpoint.ts +3 -3
  165. package/examples/{hooks → extensions}/handoff.ts +3 -3
  166. package/examples/extensions/hello.ts +25 -0
  167. package/examples/{hooks → extensions}/permission-gate.ts +3 -3
  168. package/examples/{hooks → extensions}/pirate.ts +5 -5
  169. package/examples/{hooks → extensions}/plan-mode.ts +6 -6
  170. package/examples/{hooks → extensions}/protected-paths.ts +3 -3
  171. package/examples/{hooks → extensions}/qna.ts +3 -3
  172. package/examples/{custom-tools/question/index.ts → extensions/question.ts} +13 -17
  173. package/examples/{hooks → extensions}/snake.ts +3 -3
  174. package/examples/{hooks → extensions}/status-line.ts +3 -3
  175. package/examples/{custom-tools → extensions}/subagent/README.md +15 -15
  176. package/examples/{custom-tools → extensions}/subagent/index.ts +22 -43
  177. package/examples/{custom-tools/todo/index.ts → extensions/todo.ts} +122 -39
  178. package/examples/{hooks → extensions}/tools.ts +5 -5
  179. package/examples/extensions/with-deps/index.ts +40 -0
  180. package/examples/extensions/with-deps/package-lock.json +31 -0
  181. package/examples/extensions/with-deps/package.json +16 -0
  182. package/examples/sdk/01-minimal.ts +1 -1
  183. package/examples/sdk/05-tools.ts +7 -41
  184. package/examples/sdk/06-extensions.ts +81 -0
  185. package/examples/sdk/08-prompt-templates.ts +42 -0
  186. package/examples/sdk/12-full-control.ts +10 -29
  187. package/examples/sdk/README.md +5 -5
  188. package/package.json +4 -4
  189. package/dist/core/custom-tools/index.d.ts +0 -7
  190. package/dist/core/custom-tools/index.d.ts.map +0 -1
  191. package/dist/core/custom-tools/index.js +0 -6
  192. package/dist/core/custom-tools/index.js.map +0 -1
  193. package/dist/core/custom-tools/loader.d.ts +0 -30
  194. package/dist/core/custom-tools/loader.d.ts.map +0 -1
  195. package/dist/core/custom-tools/loader.js +0 -276
  196. package/dist/core/custom-tools/loader.js.map +0 -1
  197. package/dist/core/custom-tools/types.d.ts +0 -144
  198. package/dist/core/custom-tools/types.d.ts.map +0 -1
  199. package/dist/core/custom-tools/types.js +0 -8
  200. package/dist/core/custom-tools/types.js.map +0 -1
  201. package/dist/core/custom-tools/wrapper.d.ts +0 -15
  202. package/dist/core/custom-tools/wrapper.d.ts.map +0 -1
  203. package/dist/core/custom-tools/wrapper.js +0 -23
  204. package/dist/core/custom-tools/wrapper.js.map +0 -1
  205. package/dist/core/hooks/index.d.ts +0 -6
  206. package/dist/core/hooks/index.d.ts.map +0 -1
  207. package/dist/core/hooks/index.js +0 -6
  208. package/dist/core/hooks/index.js.map +0 -1
  209. package/dist/core/hooks/loader.d.ts +0 -146
  210. package/dist/core/hooks/loader.d.ts.map +0 -1
  211. package/dist/core/hooks/loader.js +0 -275
  212. package/dist/core/hooks/loader.js.map +0 -1
  213. package/dist/core/hooks/runner.d.ts +0 -173
  214. package/dist/core/hooks/runner.d.ts.map +0 -1
  215. package/dist/core/hooks/runner.js.map +0 -1
  216. package/dist/core/hooks/tool-wrapper.d.ts +0 -17
  217. package/dist/core/hooks/tool-wrapper.d.ts.map +0 -1
  218. package/dist/core/hooks/tool-wrapper.js.map +0 -1
  219. package/dist/core/hooks/types.d.ts +0 -767
  220. package/dist/core/hooks/types.d.ts.map +0 -1
  221. package/dist/core/hooks/types.js.map +0 -1
  222. package/dist/core/slash-commands.d.ts +0 -40
  223. package/dist/core/slash-commands.d.ts.map +0 -1
  224. package/dist/core/slash-commands.js.map +0 -1
  225. package/dist/modes/interactive/components/hook-editor.d.ts.map +0 -1
  226. package/dist/modes/interactive/components/hook-editor.js.map +0 -1
  227. package/dist/modes/interactive/components/hook-input.d.ts.map +0 -1
  228. package/dist/modes/interactive/components/hook-input.js.map +0 -1
  229. package/dist/modes/interactive/components/hook-message.d.ts +0 -18
  230. package/dist/modes/interactive/components/hook-message.d.ts.map +0 -1
  231. package/dist/modes/interactive/components/hook-message.js.map +0 -1
  232. package/dist/modes/interactive/components/hook-selector.d.ts.map +0 -1
  233. package/dist/modes/interactive/components/hook-selector.js.map +0 -1
  234. package/docs/custom-tools.md +0 -514
  235. package/docs/extension-loading.md +0 -1004
  236. package/docs/hooks.md +0 -979
  237. package/docs/session-tree-plan.md +0 -441
  238. package/examples/custom-tools/README.md +0 -114
  239. package/examples/custom-tools/hello/index.ts +0 -21
  240. package/examples/hooks/README.md +0 -60
  241. package/examples/hooks/todo/index.ts +0 -134
  242. package/examples/sdk/06-hooks.ts +0 -61
  243. package/examples/sdk/08-slash-commands.ts +0 -42
  244. /package/examples/{custom-tools → extensions}/subagent/agents/planner.md +0 -0
  245. /package/examples/{custom-tools → extensions}/subagent/agents/reviewer.md +0 -0
  246. /package/examples/{custom-tools → extensions}/subagent/agents/scout.md +0 -0
  247. /package/examples/{custom-tools → extensions}/subagent/agents/worker.md +0 -0
  248. /package/examples/{custom-tools → extensions}/subagent/agents.ts +0 -0
  249. /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/implement-and-review.md +0 -0
  250. /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/implement.md +0 -0
  251. /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/scout-and-plan.md +0 -0
@@ -17,7 +17,7 @@ import { getAuthPath } from "../config.js";
17
17
  import { executeBash as executeBashCommand } from "./bash-executor.js";
18
18
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
19
19
  import { exportSessionToHtml } from "./export-html/index.js";
20
- import { expandSlashCommand } from "./slash-commands.js";
20
+ import { expandPromptTemplate } from "./prompt-templates.js";
21
21
  // ============================================================================
22
22
  // Constants
23
23
  // ============================================================================
@@ -33,7 +33,7 @@ export class AgentSession {
33
33
  sessionManager;
34
34
  settingsManager;
35
35
  _scopedModels;
36
- _fileCommands;
36
+ _promptTemplates;
37
37
  // Event subscription state
38
38
  _unsubscribeAgent;
39
39
  _eventListeners = [];
@@ -41,6 +41,8 @@ export class AgentSession {
41
41
  _steeringMessages = [];
42
42
  /** Tracks pending follow-up messages for UI display. Removed when delivered. */
43
43
  _followUpMessages = [];
44
+ /** Messages queued to be included with the next user prompt as context ("asides"). */
45
+ _pendingNextTurnMessages = [];
44
46
  // Compaction state
45
47
  _compactionAbortController = undefined;
46
48
  _autoCompactionAbortController = undefined;
@@ -54,35 +56,32 @@ export class AgentSession {
54
56
  // Bash execution state
55
57
  _bashAbortController = undefined;
56
58
  _pendingBashMessages = [];
57
- // Hook system
58
- _hookRunner = undefined;
59
+ // Extension system
60
+ _extensionRunner = undefined;
59
61
  _turnIndex = 0;
60
- // Custom tools for session lifecycle
61
- _customTools = [];
62
62
  _skillsSettings;
63
63
  // Model registry for API key resolution
64
64
  _modelRegistry;
65
- // Tool registry for hook getTools/setTools
65
+ // Tool registry for extension getTools/setTools
66
66
  _toolRegistry;
67
67
  // Function to rebuild system prompt when tools change
68
68
  _rebuildSystemPrompt;
69
- // Base system prompt (without hook appends) - used to apply fresh appends each turn
69
+ // Base system prompt (without extension appends) - used to apply fresh appends each turn
70
70
  _baseSystemPrompt;
71
71
  constructor(config) {
72
72
  this.agent = config.agent;
73
73
  this.sessionManager = config.sessionManager;
74
74
  this.settingsManager = config.settingsManager;
75
75
  this._scopedModels = config.scopedModels ?? [];
76
- this._fileCommands = config.fileCommands ?? [];
77
- this._hookRunner = config.hookRunner;
78
- this._customTools = config.customTools ?? [];
76
+ this._promptTemplates = config.promptTemplates ?? [];
77
+ this._extensionRunner = config.extensionRunner;
79
78
  this._skillsSettings = config.skillsSettings;
80
79
  this._modelRegistry = config.modelRegistry;
81
80
  this._toolRegistry = config.toolRegistry ?? new Map();
82
81
  this._rebuildSystemPrompt = config.rebuildSystemPrompt;
83
82
  this._baseSystemPrompt = config.agent.state.systemPrompt;
84
83
  // Always subscribe to agent events for internal handling
85
- // (session persistence, hooks, auto-compaction, retry logic)
84
+ // (session persistence, extensions, auto-compaction, retry logic)
86
85
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
87
86
  }
88
87
  /** Model registry for API key resolution and model discovery */
@@ -121,14 +120,14 @@ export class AgentSession {
121
120
  }
122
121
  }
123
122
  }
124
- // Emit to hooks first
125
- await this._emitHookEvent(event);
123
+ // Emit to extensions first
124
+ await this._emitExtensionEvent(event);
126
125
  // Notify all listeners
127
126
  this._emit(event);
128
127
  // Handle session persistence
129
128
  if (event.type === "message_end") {
130
- // Check if this is a hook message
131
- if (event.message.role === "hookMessage") {
129
+ // Check if this is a custom message from extensions
130
+ if (event.message.role === "custom") {
132
131
  // Persist as CustomMessageEntry
133
132
  this.sessionManager.appendCustomMessageEntry(event.message.customType, event.message.content, event.message.display, event.message.details);
134
133
  }
@@ -197,33 +196,33 @@ export class AgentSession {
197
196
  }
198
197
  return undefined;
199
198
  }
200
- /** Emit hook events based on agent events */
201
- async _emitHookEvent(event) {
202
- if (!this._hookRunner)
199
+ /** Emit extension events based on agent events */
200
+ async _emitExtensionEvent(event) {
201
+ if (!this._extensionRunner)
203
202
  return;
204
203
  if (event.type === "agent_start") {
205
204
  this._turnIndex = 0;
206
- await this._hookRunner.emit({ type: "agent_start" });
205
+ await this._extensionRunner.emit({ type: "agent_start" });
207
206
  }
208
207
  else if (event.type === "agent_end") {
209
- await this._hookRunner.emit({ type: "agent_end", messages: event.messages });
208
+ await this._extensionRunner.emit({ type: "agent_end", messages: event.messages });
210
209
  }
211
210
  else if (event.type === "turn_start") {
212
- const hookEvent = {
211
+ const extensionEvent = {
213
212
  type: "turn_start",
214
213
  turnIndex: this._turnIndex,
215
214
  timestamp: Date.now(),
216
215
  };
217
- await this._hookRunner.emit(hookEvent);
216
+ await this._extensionRunner.emit(extensionEvent);
218
217
  }
219
218
  else if (event.type === "turn_end") {
220
- const hookEvent = {
219
+ const extensionEvent = {
221
220
  type: "turn_end",
222
221
  turnIndex: this._turnIndex,
223
222
  message: event.message,
224
223
  toolResults: event.toolResults,
225
224
  };
226
- await this._hookRunner.emit(hookEvent);
225
+ await this._extensionRunner.emit(extensionEvent);
227
226
  this._turnIndex++;
228
227
  }
229
228
  }
@@ -353,35 +352,35 @@ export class AgentSession {
353
352
  get scopedModels() {
354
353
  return this._scopedModels;
355
354
  }
356
- /** File-based slash commands */
357
- get fileCommands() {
358
- return this._fileCommands;
355
+ /** File-based prompt templates */
356
+ get promptTemplates() {
357
+ return this._promptTemplates;
359
358
  }
360
359
  // =========================================================================
361
360
  // Prompting
362
361
  // =========================================================================
363
362
  /**
364
363
  * Send a prompt to the agent.
365
- * - Handles hook commands (registered via pi.registerCommand) immediately, even during streaming
366
- * - Expands file-based slash commands by default
364
+ * - Handles extension commands (registered via pi.registerCommand) immediately, even during streaming
365
+ * - Expands file-based prompt templates by default
367
366
  * - During streaming, queues via steer() or followUp() based on streamingBehavior option
368
367
  * - Validates model and API key before sending (when not streaming)
369
368
  * @throws Error if streaming and no streamingBehavior specified
370
369
  * @throws Error if no model selected or no API key available (when not streaming)
371
370
  */
372
371
  async prompt(text, options) {
373
- const expandCommands = options?.expandSlashCommands ?? true;
374
- // Handle hook commands first (execute immediately, even during streaming)
375
- // Hook commands manage their own LLM interaction via pi.sendMessage()
376
- if (expandCommands && text.startsWith("/")) {
377
- const handled = await this._tryExecuteHookCommand(text);
372
+ const expandPromptTemplates = options?.expandPromptTemplates ?? true;
373
+ // Handle extension commands first (execute immediately, even during streaming)
374
+ // Extension commands manage their own LLM interaction via pi.sendMessage()
375
+ if (expandPromptTemplates && text.startsWith("/")) {
376
+ const handled = await this._tryExecuteExtensionCommand(text);
378
377
  if (handled) {
379
- // Hook command executed, no prompt to send
378
+ // Extension command executed, no prompt to send
380
379
  return;
381
380
  }
382
381
  }
383
- // Expand file-based slash commands if requested
384
- const expandedText = expandCommands ? expandSlashCommand(text, [...this._fileCommands]) : text;
382
+ // Expand file-based prompt templates if requested
383
+ const expandedText = expandPromptTemplates ? expandPromptTemplate(text, [...this._promptTemplates]) : text;
385
384
  // If streaming, queue via steer() or followUp() based on option
386
385
  if (this.isStreaming) {
387
386
  if (!options?.streamingBehavior) {
@@ -414,7 +413,7 @@ export class AgentSession {
414
413
  if (lastAssistant) {
415
414
  await this._checkCompaction(lastAssistant, false);
416
415
  }
417
- // Build messages array (hook message if any, then user message)
416
+ // Build messages array (custom message if any, then user message)
418
417
  const messages = [];
419
418
  // Add user message
420
419
  const userContent = [{ type: "text", text: expandedText }];
@@ -426,14 +425,19 @@ export class AgentSession {
426
425
  content: userContent,
427
426
  timestamp: Date.now(),
428
427
  });
429
- // Emit before_agent_start hook event
430
- if (this._hookRunner) {
431
- const result = await this._hookRunner.emitBeforeAgentStart(expandedText, options?.images);
432
- // Add all hook messages
428
+ // Inject any pending "nextTurn" messages as context alongside the user message
429
+ for (const msg of this._pendingNextTurnMessages) {
430
+ messages.push(msg);
431
+ }
432
+ this._pendingNextTurnMessages = [];
433
+ // Emit before_agent_start extension event
434
+ if (this._extensionRunner) {
435
+ const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, options?.images);
436
+ // Add all custom messages from extensions
433
437
  if (result?.messages) {
434
438
  for (const msg of result.messages) {
435
439
  messages.push({
436
- role: "hookMessage",
440
+ role: "custom",
437
441
  customType: msg.customType,
438
442
  content: msg.content,
439
443
  display: msg.display,
@@ -442,7 +446,7 @@ export class AgentSession {
442
446
  });
443
447
  }
444
448
  }
445
- // Apply hook systemPromptAppend on top of base prompt
449
+ // Apply extension systemPromptAppend on top of base prompt
446
450
  if (result?.systemPromptAppend) {
447
451
  this.agent.setSystemPrompt(`${this._baseSystemPrompt}\n\n${result.systemPromptAppend}`);
448
452
  }
@@ -455,28 +459,28 @@ export class AgentSession {
455
459
  await this.waitForRetry();
456
460
  }
457
461
  /**
458
- * Try to execute a hook command. Returns true if command was found and executed.
462
+ * Try to execute an extension command. Returns true if command was found and executed.
459
463
  */
460
- async _tryExecuteHookCommand(text) {
461
- if (!this._hookRunner)
464
+ async _tryExecuteExtensionCommand(text) {
465
+ if (!this._extensionRunner)
462
466
  return false;
463
467
  // Parse command name and args
464
468
  const spaceIndex = text.indexOf(" ");
465
469
  const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
466
470
  const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1);
467
- const command = this._hookRunner.getCommand(commandName);
471
+ const command = this._extensionRunner.getCommand(commandName);
468
472
  if (!command)
469
473
  return false;
470
- // Get command context from hook runner (includes session control methods)
471
- const ctx = this._hookRunner.createCommandContext();
474
+ // Get command context from extension runner (includes session control methods)
475
+ const ctx = this._extensionRunner.createCommandContext();
472
476
  try {
473
477
  await command.handler(args, ctx);
474
478
  return true;
475
479
  }
476
480
  catch (err) {
477
- // Emit error via hook runner
478
- this._hookRunner.emitError({
479
- hookPath: `command:${commandName}`,
481
+ // Emit error via extension runner
482
+ this._extensionRunner.emitError({
483
+ extensionPath: `command:${commandName}`,
480
484
  event: "command",
481
485
  error: err instanceof Error ? err.message : String(err),
482
486
  });
@@ -486,35 +490,35 @@ export class AgentSession {
486
490
  /**
487
491
  * Queue a steering message to interrupt the agent mid-run.
488
492
  * Delivered after current tool execution, skips remaining tools.
489
- * Expands file-based slash commands. Errors on hook commands.
490
- * @throws Error if text is a hook command
493
+ * Expands file-based prompt templates. Errors on extension commands.
494
+ * @throws Error if text is an extension command
491
495
  */
492
496
  async steer(text) {
493
- // Check for hook commands (cannot be queued)
497
+ // Check for extension commands (cannot be queued)
494
498
  if (text.startsWith("/")) {
495
- this._throwIfHookCommand(text);
499
+ this._throwIfExtensionCommand(text);
496
500
  }
497
- // Expand file-based slash commands
498
- const expandedText = expandSlashCommand(text, [...this._fileCommands]);
501
+ // Expand file-based prompt templates
502
+ const expandedText = expandPromptTemplate(text, [...this._promptTemplates]);
499
503
  await this._queueSteer(expandedText);
500
504
  }
501
505
  /**
502
506
  * Queue a follow-up message to be processed after the agent finishes.
503
507
  * Delivered only when agent has no more tool calls or steering messages.
504
- * Expands file-based slash commands. Errors on hook commands.
505
- * @throws Error if text is a hook command
508
+ * Expands file-based prompt templates. Errors on extension commands.
509
+ * @throws Error if text is an extension command
506
510
  */
507
511
  async followUp(text) {
508
- // Check for hook commands (cannot be queued)
512
+ // Check for extension commands (cannot be queued)
509
513
  if (text.startsWith("/")) {
510
- this._throwIfHookCommand(text);
514
+ this._throwIfExtensionCommand(text);
511
515
  }
512
- // Expand file-based slash commands
513
- const expandedText = expandSlashCommand(text, [...this._fileCommands]);
516
+ // Expand file-based prompt templates
517
+ const expandedText = expandPromptTemplate(text, [...this._promptTemplates]);
514
518
  await this._queueFollowUp(expandedText);
515
519
  }
516
520
  /**
517
- * Internal: Queue a steering message (already expanded, no hook command check).
521
+ * Internal: Queue a steering message (already expanded, no extension command check).
518
522
  */
519
523
  async _queueSteer(text) {
520
524
  this._steeringMessages.push(text);
@@ -525,7 +529,7 @@ export class AgentSession {
525
529
  });
526
530
  }
527
531
  /**
528
- * Internal: Queue a follow-up message (already expanded, no hook command check).
532
+ * Internal: Queue a follow-up message (already expanded, no extension command check).
529
533
  */
530
534
  async _queueFollowUp(text) {
531
535
  this._followUpMessages.push(text);
@@ -536,41 +540,43 @@ export class AgentSession {
536
540
  });
537
541
  }
538
542
  /**
539
- * Throw an error if the text is a hook command.
543
+ * Throw an error if the text is an extension command.
540
544
  */
541
- _throwIfHookCommand(text) {
542
- if (!this._hookRunner)
545
+ _throwIfExtensionCommand(text) {
546
+ if (!this._extensionRunner)
543
547
  return;
544
548
  const spaceIndex = text.indexOf(" ");
545
549
  const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
546
- const command = this._hookRunner.getCommand(commandName);
550
+ const command = this._extensionRunner.getCommand(commandName);
547
551
  if (command) {
548
- throw new Error(`Hook command "/${commandName}" cannot be queued. Use prompt() or execute the command when not streaming.`);
552
+ throw new Error(`Extension command "/${commandName}" cannot be queued. Use prompt() or execute the command when not streaming.`);
549
553
  }
550
554
  }
551
555
  /**
552
- * Send a hook message to the session. Creates a CustomMessageEntry.
556
+ * Send a custom message to the session. Creates a CustomMessageEntry.
553
557
  *
554
558
  * Handles three cases:
555
559
  * - Streaming: queues message, processed when loop pulls from queue
556
560
  * - Not streaming + triggerTurn: appends to state/session, starts new turn
557
561
  * - Not streaming + no trigger: appends to state/session, no turn
558
562
  *
559
- * @param message Hook message with customType, content, display, details
563
+ * @param message Custom message with customType, content, display, details
560
564
  * @param options.triggerTurn If true and not streaming, triggers a new LLM turn
561
- * @param options.deliverAs When streaming, use "steer" (default) for immediate or "followUp" to wait
565
+ * @param options.deliverAs Delivery mode: "steer", "followUp", or "nextTurn"
562
566
  */
563
- async sendHookMessage(message, options) {
567
+ async sendCustomMessage(message, options) {
564
568
  const appMessage = {
565
- role: "hookMessage",
569
+ role: "custom",
566
570
  customType: message.customType,
567
571
  content: message.content,
568
572
  display: message.display,
569
573
  details: message.details,
570
574
  timestamp: Date.now(),
571
575
  };
572
- if (this.isStreaming) {
573
- // Queue for processing by agent loop
576
+ if (options?.deliverAs === "nextTurn") {
577
+ this._pendingNextTurnMessages.push(appMessage);
578
+ }
579
+ else if (this.isStreaming) {
574
580
  if (options?.deliverAs === "followUp") {
575
581
  this.agent.followUp(appMessage);
576
582
  }
@@ -579,11 +585,9 @@ export class AgentSession {
579
585
  }
580
586
  }
581
587
  else if (options?.triggerTurn) {
582
- // Send as prompt - agent loop will emit message events
583
588
  await this.agent.prompt(appMessage);
584
589
  }
585
590
  else {
586
- // Just append to agent state and session, no turn
587
591
  this.agent.appendMessage(appMessage);
588
592
  this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
589
593
  }
@@ -629,13 +633,13 @@ export class AgentSession {
629
633
  * Clears all messages and starts a new session.
630
634
  * Listeners are preserved and will continue receiving events.
631
635
  * @param options - Optional initial messages and parent session path
632
- * @returns true if completed, false if cancelled by hook
636
+ * @returns true if completed, false if cancelled by extension
633
637
  */
634
638
  async newSession(options) {
635
639
  const previousSessionFile = this.sessionFile;
636
640
  // Emit session_before_switch event with reason "new" (can be cancelled)
637
- if (this._hookRunner?.hasHandlers("session_before_switch")) {
638
- const result = (await this._hookRunner.emit({
641
+ if (this._extensionRunner?.hasHandlers("session_before_switch")) {
642
+ const result = (await this._extensionRunner.emit({
639
643
  type: "session_before_switch",
640
644
  reason: "new",
641
645
  }));
@@ -649,17 +653,17 @@ export class AgentSession {
649
653
  this.sessionManager.newSession(options);
650
654
  this._steeringMessages = [];
651
655
  this._followUpMessages = [];
656
+ this._pendingNextTurnMessages = [];
652
657
  this._reconnectToAgent();
653
- // Emit session_switch event with reason "new" to hooks
654
- if (this._hookRunner) {
655
- await this._hookRunner.emit({
658
+ // Emit session_switch event with reason "new" to extensions
659
+ if (this._extensionRunner) {
660
+ await this._extensionRunner.emit({
656
661
  type: "session_switch",
657
662
  reason: "new",
658
663
  previousSessionFile,
659
664
  });
660
665
  }
661
666
  // Emit session event to custom tools
662
- await this.emitCustomToolSessionEvent("switch", previousSessionFile);
663
667
  return true;
664
668
  }
665
669
  // =========================================================================
@@ -846,10 +850,10 @@ export class AgentSession {
846
850
  }
847
851
  throw new Error("Nothing to compact (session too small)");
848
852
  }
849
- let hookCompaction;
850
- let fromHook = false;
851
- if (this._hookRunner?.hasHandlers("session_before_compact")) {
852
- const result = (await this._hookRunner.emit({
853
+ let extensionCompaction;
854
+ let fromExtension = false;
855
+ if (this._extensionRunner?.hasHandlers("session_before_compact")) {
856
+ const result = (await this._extensionRunner.emit({
853
857
  type: "session_before_compact",
854
858
  preparation,
855
859
  branchEntries: pathEntries,
@@ -860,20 +864,20 @@ export class AgentSession {
860
864
  throw new Error("Compaction cancelled");
861
865
  }
862
866
  if (result?.compaction) {
863
- hookCompaction = result.compaction;
864
- fromHook = true;
867
+ extensionCompaction = result.compaction;
868
+ fromExtension = true;
865
869
  }
866
870
  }
867
871
  let summary;
868
872
  let firstKeptEntryId;
869
873
  let tokensBefore;
870
874
  let details;
871
- if (hookCompaction) {
872
- // Hook provided compaction content
873
- summary = hookCompaction.summary;
874
- firstKeptEntryId = hookCompaction.firstKeptEntryId;
875
- tokensBefore = hookCompaction.tokensBefore;
876
- details = hookCompaction.details;
875
+ if (extensionCompaction) {
876
+ // Extension provided compaction content
877
+ summary = extensionCompaction.summary;
878
+ firstKeptEntryId = extensionCompaction.firstKeptEntryId;
879
+ tokensBefore = extensionCompaction.tokensBefore;
880
+ details = extensionCompaction.details;
877
881
  }
878
882
  else {
879
883
  // Generate compaction result
@@ -886,17 +890,17 @@ export class AgentSession {
886
890
  if (this._compactionAbortController.signal.aborted) {
887
891
  throw new Error("Compaction cancelled");
888
892
  }
889
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook);
893
+ this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
890
894
  const newEntries = this.sessionManager.getEntries();
891
895
  const sessionContext = this.sessionManager.buildSessionContext();
892
896
  this.agent.replaceMessages(sessionContext.messages);
893
- // Get the saved compaction entry for the hook
897
+ // Get the saved compaction entry for the extension event
894
898
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
895
- if (this._hookRunner && savedCompactionEntry) {
896
- await this._hookRunner.emit({
899
+ if (this._extensionRunner && savedCompactionEntry) {
900
+ await this._extensionRunner.emit({
897
901
  type: "session_compact",
898
902
  compactionEntry: savedCompactionEntry,
899
- fromHook,
903
+ fromExtension,
900
904
  });
901
905
  }
902
906
  return {
@@ -986,35 +990,35 @@ export class AgentSession {
986
990
  this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
987
991
  return;
988
992
  }
989
- let hookCompaction;
990
- let fromHook = false;
991
- if (this._hookRunner?.hasHandlers("session_before_compact")) {
992
- const hookResult = (await this._hookRunner.emit({
993
+ let extensionCompaction;
994
+ let fromExtension = false;
995
+ if (this._extensionRunner?.hasHandlers("session_before_compact")) {
996
+ const extensionResult = (await this._extensionRunner.emit({
993
997
  type: "session_before_compact",
994
998
  preparation,
995
999
  branchEntries: pathEntries,
996
1000
  customInstructions: undefined,
997
1001
  signal: this._autoCompactionAbortController.signal,
998
1002
  }));
999
- if (hookResult?.cancel) {
1003
+ if (extensionResult?.cancel) {
1000
1004
  this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
1001
1005
  return;
1002
1006
  }
1003
- if (hookResult?.compaction) {
1004
- hookCompaction = hookResult.compaction;
1005
- fromHook = true;
1007
+ if (extensionResult?.compaction) {
1008
+ extensionCompaction = extensionResult.compaction;
1009
+ fromExtension = true;
1006
1010
  }
1007
1011
  }
1008
1012
  let summary;
1009
1013
  let firstKeptEntryId;
1010
1014
  let tokensBefore;
1011
1015
  let details;
1012
- if (hookCompaction) {
1013
- // Hook provided compaction content
1014
- summary = hookCompaction.summary;
1015
- firstKeptEntryId = hookCompaction.firstKeptEntryId;
1016
- tokensBefore = hookCompaction.tokensBefore;
1017
- details = hookCompaction.details;
1016
+ if (extensionCompaction) {
1017
+ // Extension provided compaction content
1018
+ summary = extensionCompaction.summary;
1019
+ firstKeptEntryId = extensionCompaction.firstKeptEntryId;
1020
+ tokensBefore = extensionCompaction.tokensBefore;
1021
+ details = extensionCompaction.details;
1018
1022
  }
1019
1023
  else {
1020
1024
  // Generate compaction result
@@ -1028,17 +1032,17 @@ export class AgentSession {
1028
1032
  this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
1029
1033
  return;
1030
1034
  }
1031
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook);
1035
+ this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1032
1036
  const newEntries = this.sessionManager.getEntries();
1033
1037
  const sessionContext = this.sessionManager.buildSessionContext();
1034
1038
  this.agent.replaceMessages(sessionContext.messages);
1035
- // Get the saved compaction entry for the hook
1039
+ // Get the saved compaction entry for the extension event
1036
1040
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1037
- if (this._hookRunner && savedCompactionEntry) {
1038
- await this._hookRunner.emit({
1041
+ if (this._extensionRunner && savedCompactionEntry) {
1042
+ await this._extensionRunner.emit({
1039
1043
  type: "session_compact",
1040
1044
  compactionEntry: savedCompactionEntry,
1041
- fromHook,
1045
+ fromExtension,
1042
1046
  });
1043
1047
  }
1044
1048
  const result = {
@@ -1294,13 +1298,13 @@ export class AgentSession {
1294
1298
  * Switch to a different session file.
1295
1299
  * Aborts current operation, loads messages, restores model/thinking.
1296
1300
  * Listeners are preserved and will continue receiving events.
1297
- * @returns true if switch completed, false if cancelled by hook
1301
+ * @returns true if switch completed, false if cancelled by extension
1298
1302
  */
1299
1303
  async switchSession(sessionPath) {
1300
1304
  const previousSessionFile = this.sessionManager.getSessionFile();
1301
1305
  // Emit session_before_switch event (can be cancelled)
1302
- if (this._hookRunner?.hasHandlers("session_before_switch")) {
1303
- const result = (await this._hookRunner.emit({
1306
+ if (this._extensionRunner?.hasHandlers("session_before_switch")) {
1307
+ const result = (await this._extensionRunner.emit({
1304
1308
  type: "session_before_switch",
1305
1309
  reason: "resume",
1306
1310
  targetSessionFile: sessionPath,
@@ -1313,20 +1317,20 @@ export class AgentSession {
1313
1317
  await this.abort();
1314
1318
  this._steeringMessages = [];
1315
1319
  this._followUpMessages = [];
1320
+ this._pendingNextTurnMessages = [];
1316
1321
  // Set new session
1317
1322
  this.sessionManager.setSessionFile(sessionPath);
1318
1323
  // Reload messages
1319
1324
  const sessionContext = this.sessionManager.buildSessionContext();
1320
- // Emit session_switch event to hooks
1321
- if (this._hookRunner) {
1322
- await this._hookRunner.emit({
1325
+ // Emit session_switch event to extensions
1326
+ if (this._extensionRunner) {
1327
+ await this._extensionRunner.emit({
1323
1328
  type: "session_switch",
1324
1329
  reason: "resume",
1325
1330
  previousSessionFile,
1326
1331
  });
1327
1332
  }
1328
1333
  // Emit session event to custom tools
1329
- await this.emitCustomToolSessionEvent("switch", previousSessionFile);
1330
1334
  this.agent.replaceMessages(sessionContext.messages);
1331
1335
  // Restore model if saved
1332
1336
  if (sessionContext.model) {
@@ -1345,12 +1349,12 @@ export class AgentSession {
1345
1349
  }
1346
1350
  /**
1347
1351
  * Create a branch from a specific entry.
1348
- * Emits before_branch/branch session events to hooks.
1352
+ * Emits before_branch/branch session events to extensions.
1349
1353
  *
1350
1354
  * @param entryId ID of the entry to branch from
1351
1355
  * @returns Object with:
1352
1356
  * - selectedText: The text of the selected user message (for editor pre-fill)
1353
- * - cancelled: True if a hook cancelled the branch
1357
+ * - cancelled: True if an extension cancelled the branch
1354
1358
  */
1355
1359
  async branch(entryId) {
1356
1360
  const previousSessionFile = this.sessionFile;
@@ -1361,8 +1365,8 @@ export class AgentSession {
1361
1365
  const selectedText = this._extractUserMessageText(selectedEntry.message.content);
1362
1366
  let skipConversationRestore = false;
1363
1367
  // Emit session_before_branch event (can be cancelled)
1364
- if (this._hookRunner?.hasHandlers("session_before_branch")) {
1365
- const result = (await this._hookRunner.emit({
1368
+ if (this._extensionRunner?.hasHandlers("session_before_branch")) {
1369
+ const result = (await this._extensionRunner.emit({
1366
1370
  type: "session_before_branch",
1367
1371
  entryId,
1368
1372
  }));
@@ -1371,6 +1375,8 @@ export class AgentSession {
1371
1375
  }
1372
1376
  skipConversationRestore = result?.skipConversationRestore ?? false;
1373
1377
  }
1378
+ // Clear pending messages (bound to old session state)
1379
+ this._pendingNextTurnMessages = [];
1374
1380
  if (!selectedEntry.parentId) {
1375
1381
  this.sessionManager.newSession();
1376
1382
  }
@@ -1379,15 +1385,14 @@ export class AgentSession {
1379
1385
  }
1380
1386
  // Reload messages from entries (works for both file and in-memory mode)
1381
1387
  const sessionContext = this.sessionManager.buildSessionContext();
1382
- // Emit session_branch event to hooks (after branch completes)
1383
- if (this._hookRunner) {
1384
- await this._hookRunner.emit({
1388
+ // Emit session_branch event to extensions (after branch completes)
1389
+ if (this._extensionRunner) {
1390
+ await this._extensionRunner.emit({
1385
1391
  type: "session_branch",
1386
1392
  previousSessionFile,
1387
1393
  });
1388
1394
  }
1389
1395
  // Emit session event to custom tools (with reason "branch")
1390
- await this.emitCustomToolSessionEvent("branch", previousSessionFile);
1391
1396
  if (!skipConversationRestore) {
1392
1397
  this.agent.replaceMessages(sessionContext.messages);
1393
1398
  }
@@ -1431,11 +1436,11 @@ export class AgentSession {
1431
1436
  };
1432
1437
  // Set up abort controller for summarization
1433
1438
  this._branchSummaryAbortController = new AbortController();
1434
- let hookSummary;
1435
- let fromHook = false;
1439
+ let extensionSummary;
1440
+ let fromExtension = false;
1436
1441
  // Emit session_before_tree event
1437
- if (this._hookRunner?.hasHandlers("session_before_tree")) {
1438
- const result = (await this._hookRunner.emit({
1442
+ if (this._extensionRunner?.hasHandlers("session_before_tree")) {
1443
+ const result = (await this._extensionRunner.emit({
1439
1444
  type: "session_before_tree",
1440
1445
  preparation,
1441
1446
  signal: this._branchSummaryAbortController.signal,
@@ -1444,14 +1449,14 @@ export class AgentSession {
1444
1449
  return { cancelled: true };
1445
1450
  }
1446
1451
  if (result?.summary && options.summarize) {
1447
- hookSummary = result.summary;
1448
- fromHook = true;
1452
+ extensionSummary = result.summary;
1453
+ fromExtension = true;
1449
1454
  }
1450
1455
  }
1451
1456
  // Run default summarizer if needed
1452
1457
  let summaryText;
1453
1458
  let summaryDetails;
1454
- if (options.summarize && entriesToSummarize.length > 0 && !hookSummary) {
1459
+ if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
1455
1460
  const model = this.model;
1456
1461
  const apiKey = await this._modelRegistry.getApiKey(model);
1457
1462
  if (!apiKey) {
@@ -1478,9 +1483,9 @@ export class AgentSession {
1478
1483
  modifiedFiles: result.modifiedFiles || [],
1479
1484
  };
1480
1485
  }
1481
- else if (hookSummary) {
1482
- summaryText = hookSummary.summary;
1483
- summaryDetails = hookSummary.details;
1486
+ else if (extensionSummary) {
1487
+ summaryText = extensionSummary.summary;
1488
+ summaryDetails = extensionSummary.details;
1484
1489
  }
1485
1490
  // Determine the new leaf position based on target type
1486
1491
  let newLeafId;
@@ -1510,7 +1515,7 @@ export class AgentSession {
1510
1515
  let summaryEntry;
1511
1516
  if (summaryText) {
1512
1517
  // Create summary at target position (can be null for root)
1513
- const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromHook);
1518
+ const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
1514
1519
  summaryEntry = this.sessionManager.getEntry(summaryId);
1515
1520
  }
1516
1521
  else if (newLeafId === null) {
@@ -1525,17 +1530,16 @@ export class AgentSession {
1525
1530
  const sessionContext = this.sessionManager.buildSessionContext();
1526
1531
  this.agent.replaceMessages(sessionContext.messages);
1527
1532
  // Emit session_tree event
1528
- if (this._hookRunner) {
1529
- await this._hookRunner.emit({
1533
+ if (this._extensionRunner) {
1534
+ await this._extensionRunner.emit({
1530
1535
  type: "session_tree",
1531
1536
  newLeafId: this.sessionManager.getLeafId(),
1532
1537
  oldLeafId,
1533
1538
  summaryEntry,
1534
- fromHook: summaryText ? fromHook : undefined,
1539
+ fromExtension: summaryText ? fromExtension : undefined,
1535
1540
  });
1536
1541
  }
1537
1542
  // Emit to custom tools
1538
- await this.emitCustomToolSessionEvent("tree", this.sessionFile);
1539
1543
  this._branchSummaryAbortController = undefined;
1540
1544
  return { editorText, cancelled: false, summaryEntry };
1541
1545
  }
@@ -1652,54 +1656,19 @@ export class AgentSession {
1652
1656
  return text.trim() || undefined;
1653
1657
  }
1654
1658
  // =========================================================================
1655
- // Hook System
1659
+ // Extension System
1656
1660
  // =========================================================================
1657
1661
  /**
1658
- * Check if hooks have handlers for a specific event type.
1659
- */
1660
- hasHookHandlers(eventType) {
1661
- return this._hookRunner?.hasHandlers(eventType) ?? false;
1662
- }
1663
- /**
1664
- * Get the hook runner (for setting UI context and error handlers).
1665
- */
1666
- get hookRunner() {
1667
- return this._hookRunner;
1668
- }
1669
- /**
1670
- * Get custom tools (for setting UI context in modes).
1662
+ * Check if extensions have handlers for a specific event type.
1671
1663
  */
1672
- get customTools() {
1673
- return this._customTools;
1664
+ hasExtensionHandlers(eventType) {
1665
+ return this._extensionRunner?.hasHandlers(eventType) ?? false;
1674
1666
  }
1675
1667
  /**
1676
- * Emit session event to all custom tools.
1677
- * Called on session switch, branch, tree navigation, and shutdown.
1668
+ * Get the extension runner (for setting UI context and error handlers).
1678
1669
  */
1679
- async emitCustomToolSessionEvent(reason, previousSessionFile) {
1680
- if (!this._customTools)
1681
- return;
1682
- const event = { reason, previousSessionFile };
1683
- const ctx = {
1684
- sessionManager: this.sessionManager,
1685
- modelRegistry: this._modelRegistry,
1686
- model: this.agent.state.model,
1687
- isIdle: () => !this.isStreaming,
1688
- hasPendingMessages: () => this.pendingMessageCount > 0,
1689
- abort: () => {
1690
- this.abort();
1691
- },
1692
- };
1693
- for (const { tool } of this._customTools) {
1694
- if (tool.onSession) {
1695
- try {
1696
- await tool.onSession(event, ctx);
1697
- }
1698
- catch (_err) {
1699
- // Silently ignore tool errors during session events
1700
- }
1701
- }
1702
- }
1670
+ get extensionRunner() {
1671
+ return this._extensionRunner;
1703
1672
  }
1704
1673
  }
1705
1674
  //# sourceMappingURL=agent-session.js.map