@sayknow-cli/coding-agent 0.3.7 → 0.3.9

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 (272) hide show
  1. package/dist/types/append-only-mode.d.ts +31 -0
  2. package/dist/types/capability/rule.d.ts +8 -0
  3. package/dist/types/cli/completion-cli.d.ts +41 -0
  4. package/dist/types/cli/config-cli.d.ts +1 -0
  5. package/dist/types/cli/fixture-report.d.ts +20 -0
  6. package/dist/types/cli/fixture-report.test.d.ts +1 -0
  7. package/dist/types/cli/nofile-limit.d.ts +11 -0
  8. package/dist/types/cli/notify-cli.d.ts +7 -0
  9. package/dist/types/cli/update-cli.d.ts +22 -0
  10. package/dist/types/cli.d.ts +124 -1
  11. package/dist/types/commands/completion.d.ts +29 -0
  12. package/dist/types/commands/config.d.ts +3 -0
  13. package/dist/types/commands/deep-interview.d.ts +3 -0
  14. package/dist/types/config/file-lock.d.ts +7 -6
  15. package/dist/types/config/keybindings.d.ts +2 -2
  16. package/dist/types/config/model-profile-activation.d.ts +6 -0
  17. package/dist/types/config/model-profiles.d.ts +1 -0
  18. package/dist/types/config/settings-schema.d.ts +119 -1
  19. package/dist/types/config/settings.d.ts +8 -0
  20. package/dist/types/discovery/index.d.ts +1 -0
  21. package/dist/types/export/html/template.generated.d.ts +1 -1
  22. package/dist/types/export/ttsr.d.ts +14 -3
  23. package/dist/types/extensibility/extensions/runner.d.ts +2 -1
  24. package/dist/types/extensibility/extensions/types.d.ts +8 -0
  25. package/dist/types/goals/runtime.d.ts +1 -0
  26. package/dist/types/hooks/native-skill-hook.d.ts +12 -0
  27. package/dist/types/main.d.ts +4 -1
  28. package/dist/types/modes/components/assistant-message.d.ts +5 -1
  29. package/dist/types/modes/components/model-selector.d.ts +4 -0
  30. package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
  31. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  32. package/dist/types/modes/components/status-line.d.ts +9 -0
  33. package/dist/types/modes/components/thinking-selector.d.ts +2 -2
  34. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  35. package/dist/types/modes/controllers/event-controller.d.ts +5 -0
  36. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  37. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  38. package/dist/types/modes/interactive-mode.d.ts +4 -0
  39. package/dist/types/modes/print-mode.d.ts +13 -1
  40. package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
  41. package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
  42. package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -0
  43. package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
  44. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
  45. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
  46. package/dist/types/modes/types.d.ts +3 -0
  47. package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
  48. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  49. package/dist/types/notifications/config-commands.d.ts +9 -0
  50. package/dist/types/notifications/config.d.ts +30 -1
  51. package/dist/types/notifications/daemon-paths.d.ts +1 -0
  52. package/dist/types/notifications/index.d.ts +8 -3
  53. package/dist/types/notifications/lifecycle-commands.d.ts +12 -3
  54. package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
  55. package/dist/types/notifications/reply-sent-store.d.ts +53 -0
  56. package/dist/types/notifications/rich-draft.d.ts +68 -0
  57. package/dist/types/notifications/rich-render.d.ts +90 -0
  58. package/dist/types/notifications/telegram-daemon.d.ts +45 -3
  59. package/dist/types/notifications/telegram-reference.d.ts +7 -5
  60. package/dist/types/notifications/threaded-render.d.ts +14 -0
  61. package/dist/types/runtime-api-key.d.ts +7 -0
  62. package/dist/types/sdk.d.ts +3 -0
  63. package/dist/types/session/agent-session.d.ts +61 -6
  64. package/dist/types/session/agent-storage.d.ts +1 -0
  65. package/dist/types/session/file-mention-pruning.d.ts +8 -0
  66. package/dist/types/session/messages.d.ts +4 -0
  67. package/dist/types/session/session-manager.d.ts +17 -1
  68. package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
  69. package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
  70. package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
  71. package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
  72. package/dist/types/skc-runtime/state-writer.d.ts +3 -0
  73. package/dist/types/skc-runtime/tmux-common.d.ts +1 -0
  74. package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
  75. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
  76. package/dist/types/skill-state/active-state.d.ts +18 -1
  77. package/dist/types/system-prompt.d.ts +18 -2
  78. package/dist/types/task/executor.d.ts +3 -0
  79. package/dist/types/task/fork-context-budget.d.ts +7 -0
  80. package/dist/types/task/token-log.d.ts +30 -0
  81. package/dist/types/task/token-log.test.d.ts +1 -0
  82. package/dist/types/tools/bisect.d.ts +81 -0
  83. package/dist/types/tools/computer.d.ts +2 -2
  84. package/dist/types/tools/index.d.ts +1 -0
  85. package/dist/types/tools/search.d.ts +2 -2
  86. package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
  87. package/dist/types/utils/file-mentions.d.ts +14 -0
  88. package/dist/types/utils/git.d.ts +32 -0
  89. package/dist/types/utils/pasted-image-path.d.ts +30 -0
  90. package/dist/types/web/scrapers/youtube.d.ts +8 -0
  91. package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
  92. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
  93. package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
  94. package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
  95. package/package.json +8 -8
  96. package/src/append-only-mode.ts +58 -0
  97. package/src/autoresearch/prompt.md +1 -1
  98. package/src/autoresearch/tools/log-experiment.ts +1 -1
  99. package/src/capability/rule.ts +9 -0
  100. package/src/cli/completion-cli.ts +234 -0
  101. package/src/cli/config-cli.ts +61 -9
  102. package/src/cli/fast-help.ts +1 -0
  103. package/src/cli/fixture-report.test.ts +230 -0
  104. package/src/cli/fixture-report.ts +171 -0
  105. package/src/cli/nofile-limit.ts +48 -0
  106. package/src/cli/notify-cli.ts +103 -17
  107. package/src/cli/telegram-cli.ts +1 -1
  108. package/src/cli/update-cli.ts +148 -33
  109. package/src/cli.ts +29 -1
  110. package/src/commands/completion.ts +84 -0
  111. package/src/commands/config.ts +2 -0
  112. package/src/commands/contribution-prep.ts +1 -1
  113. package/src/commands/deep-interview.ts +2 -1
  114. package/src/config/file-lock.ts +73 -34
  115. package/src/config/keybindings.ts +3 -3
  116. package/src/config/model-profile-activation.ts +65 -0
  117. package/src/config/model-profiles.ts +31 -2
  118. package/src/config/settings-schema.ts +76 -1
  119. package/src/config/settings.ts +22 -0
  120. package/src/defaults/skc/skills/team/SKILL.md +17 -14
  121. package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
  122. package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
  123. package/src/defaults/skc-defaults.ts +9 -0
  124. package/src/discovery/helpers.ts +10 -0
  125. package/src/discovery/index.ts +1 -0
  126. package/src/export/html/template.generated.ts +1 -1
  127. package/src/export/html/template.js +60 -2
  128. package/src/export/ttsr.ts +96 -18
  129. package/src/extensibility/extensions/runner.ts +183 -219
  130. package/src/extensibility/extensions/types.ts +9 -0
  131. package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
  132. package/src/goals/runtime.ts +10 -2
  133. package/src/hindsight/backend.ts +1 -3
  134. package/src/hooks/native-skill-hook.ts +124 -4
  135. package/src/internal-urls/docs-index.generated.ts +17 -8
  136. package/src/lsp/client.ts +1 -0
  137. package/src/main.ts +95 -43
  138. package/src/memories/index.ts +12 -1
  139. package/src/modes/components/assistant-message.ts +241 -82
  140. package/src/modes/components/custom-editor.ts +1 -0
  141. package/src/modes/components/footer.ts +7 -2
  142. package/src/modes/components/model-selector.ts +54 -11
  143. package/src/modes/components/queued-message-selector.ts +92 -0
  144. package/src/modes/components/settings-selector.ts +4 -0
  145. package/src/modes/components/status-line.ts +167 -54
  146. package/src/modes/components/thinking-selector.ts +10 -8
  147. package/src/modes/components/tool-execution.ts +44 -19
  148. package/src/modes/components/welcome.ts +181 -44
  149. package/src/modes/controllers/command-controller.ts +40 -4
  150. package/src/modes/controllers/event-controller.ts +77 -49
  151. package/src/modes/controllers/extension-ui-controller.ts +12 -11
  152. package/src/modes/controllers/input-controller.ts +192 -35
  153. package/src/modes/controllers/selector-controller.ts +168 -24
  154. package/src/modes/interactive-mode.ts +56 -16
  155. package/src/modes/print-mode.ts +43 -4
  156. package/src/modes/prompt-action-autocomplete.ts +21 -20
  157. package/src/modes/rpc/rpc-mode.ts +32 -12
  158. package/src/modes/rpc/rpc-socket-security.ts +13 -1
  159. package/src/modes/rpc/rpc-types.ts +9 -0
  160. package/src/modes/shared/agent-wire/command-validation.ts +4 -0
  161. package/src/modes/shared/agent-wire/event-contract.ts +22 -0
  162. package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
  163. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  164. package/src/modes/types.ts +3 -0
  165. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  166. package/src/modes/utils/injected-user-submission.ts +94 -0
  167. package/src/modes/utils/ui-helpers.ts +20 -7
  168. package/src/notifications/config-commands.ts +20 -0
  169. package/src/notifications/config.ts +52 -5
  170. package/src/notifications/daemon-paths.ts +2 -0
  171. package/src/notifications/index.ts +144 -18
  172. package/src/notifications/lifecycle-commands.ts +74 -23
  173. package/src/notifications/lifecycle-control-runtime.ts +8 -3
  174. package/src/notifications/lifecycle-orchestrator.ts +19 -6
  175. package/src/notifications/reply-sent-store.ts +134 -0
  176. package/src/notifications/rich-draft.ts +107 -0
  177. package/src/notifications/rich-render.ts +142 -0
  178. package/src/notifications/telegram-daemon-control.ts +5 -3
  179. package/src/notifications/telegram-daemon.ts +606 -98
  180. package/src/notifications/telegram-reference.ts +17 -16
  181. package/src/notifications/threaded-render.ts +45 -2
  182. package/src/prompts/agents/architect.md +19 -10
  183. package/src/prompts/agents/critic.md +16 -18
  184. package/src/prompts/agents/executor.md +14 -11
  185. package/src/prompts/agents/planner.md +18 -13
  186. package/src/prompts/ci-green-request.md +3 -8
  187. package/src/prompts/goals/goal-continuation.md +0 -5
  188. package/src/prompts/goals/goal-mode-active.md +0 -5
  189. package/src/prompts/system/agent-creation-architect.md +2 -2
  190. package/src/prompts/system/plan-mode-active.md +1 -1
  191. package/src/prompts/system/plan-mode-subagent.md +3 -3
  192. package/src/prompts/system/project-prompt.md +0 -13
  193. package/src/prompts/system/subagent-system-prompt.md +3 -2
  194. package/src/prompts/system/system-prompt.md +29 -17
  195. package/src/prompts/system/volatile-project-context.md +10 -0
  196. package/src/prompts/system/web-search.md +4 -7
  197. package/src/prompts/tools/apply-patch.md +6 -0
  198. package/src/prompts/tools/ast-edit.md +1 -0
  199. package/src/prompts/tools/bash.md +1 -1
  200. package/src/prompts/tools/bisect.md +22 -0
  201. package/src/prompts/tools/browser.md +1 -1
  202. package/src/prompts/tools/computer.md +2 -2
  203. package/src/prompts/tools/cron.md +0 -2
  204. package/src/prompts/tools/eval.md +2 -2
  205. package/src/prompts/tools/find.md +1 -1
  206. package/src/prompts/tools/github.md +6 -6
  207. package/src/prompts/tools/goal.md +1 -3
  208. package/src/prompts/tools/image-gen.md +12 -5
  209. package/src/prompts/tools/irc.md +5 -4
  210. package/src/prompts/tools/job.md +2 -2
  211. package/src/prompts/tools/lsp.md +1 -1
  212. package/src/prompts/tools/monitor.md +2 -2
  213. package/src/prompts/tools/patch.md +2 -2
  214. package/src/prompts/tools/replace.md +2 -21
  215. package/src/prompts/tools/rewind.md +1 -1
  216. package/src/prompts/tools/search-tool-bm25.md +6 -11
  217. package/src/prompts/tools/search.md +1 -1
  218. package/src/prompts/tools/skill.md +3 -2
  219. package/src/prompts/tools/ssh.md +2 -1
  220. package/src/prompts/tools/subagent.md +2 -2
  221. package/src/prompts/tools/task.md +4 -5
  222. package/src/prompts/tools/todo-write.md +1 -1
  223. package/src/prompts/tools/vim.md +1 -0
  224. package/src/prompts/tools/web-search.md +2 -0
  225. package/src/prompts/tools/write.md +8 -2
  226. package/src/runtime-api-key.ts +16 -0
  227. package/src/sdk.ts +88 -36
  228. package/src/session/agent-session.ts +1013 -141
  229. package/src/session/agent-storage.ts +4 -0
  230. package/src/session/file-mention-pruning.ts +64 -0
  231. package/src/session/messages.ts +4 -0
  232. package/src/session/session-manager.ts +103 -4
  233. package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
  234. package/src/skc-runtime/deep-interview-recorder.ts +62 -8
  235. package/src/skc-runtime/deep-interview-state.ts +44 -2
  236. package/src/skc-runtime/session-state-sidecar.ts +263 -20
  237. package/src/skc-runtime/state-runtime.ts +12 -2
  238. package/src/skc-runtime/state-writer.ts +15 -0
  239. package/src/skc-runtime/tmux-common.ts +1 -0
  240. package/src/skc-runtime/ultragoal-guard.ts +55 -66
  241. package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
  242. package/src/skc-runtime/ultragoal-runtime.ts +1672 -159
  243. package/src/skc-runtime/workflow-manifest.generated.json +113 -2
  244. package/src/skc-runtime/workflow-manifest.ts +74 -1
  245. package/src/skill-state/active-state.ts +141 -5
  246. package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
  247. package/src/slash-commands/builtin-registry.ts +296 -72
  248. package/src/system-prompt.ts +57 -16
  249. package/src/task/executor.ts +132 -24
  250. package/src/task/fork-context-advisory.ts +2 -7
  251. package/src/task/fork-context-budget.ts +9 -0
  252. package/src/task/index.ts +13 -6
  253. package/src/task/output-manager.ts +20 -8
  254. package/src/task/receipt.ts +26 -7
  255. package/src/task/token-log.test.ts +188 -0
  256. package/src/task/token-log.ts +179 -0
  257. package/src/tools/bisect.ts +367 -0
  258. package/src/tools/computer.ts +246 -43
  259. package/src/tools/index.ts +3 -0
  260. package/src/tools/output-meta.ts +116 -4
  261. package/src/tools/search.ts +8 -2
  262. package/src/tools/skill.ts +15 -0
  263. package/src/tools/telegram-send.ts +6 -0
  264. package/src/tools/ultragoal-ask-guard.ts +39 -5
  265. package/src/utils/file-mentions.ts +39 -7
  266. package/src/utils/git.ts +73 -0
  267. package/src/utils/pasted-image-path.ts +170 -0
  268. package/src/web/scrapers/youtube.ts +24 -12
  269. package/src/workflow/workflow-intent-diff.ts +29 -6
  270. package/src/workflow/workflow-intent-report-consumer.ts +221 -0
  271. package/src/workflow/workflow-intent-report-schema.ts +129 -0
  272. package/src/workflow/workflow-intent-report.ts +135 -0
@@ -111,6 +111,8 @@ type RunnerEmitResult<TEvent extends RunnerEmitEvent> = TEvent extends { type: "
111
111
  : TEvent extends { type: "session.compacting" }
112
112
  ? SessionCompactingResult | undefined
113
113
  : undefined;
114
+ type Handler = Extension["handlers"] extends Map<string, Array<infer T>> ? T : never;
115
+ type IndexedHandler = { ext: Extension; handler: Handler };
114
116
 
115
117
  export type NewSessionHandler = (options?: {
116
118
  parentSession?: string;
@@ -173,6 +175,8 @@ const noOpUIContext: ExtensionUIContext = {
173
175
  export class ExtensionRunner {
174
176
  #uiContext: ExtensionUIContext;
175
177
  #errorListeners: Set<ExtensionErrorListener> = new Set();
178
+ #handlersByEvent: Map<string, IndexedHandler[]> = new Map();
179
+
176
180
  #getModel: () => Model | undefined = () => undefined;
177
181
  #isIdleFn: () => boolean = () => true;
178
182
  #waitForIdleFn: () => Promise<void> = async () => {};
@@ -205,8 +209,27 @@ export class ExtensionRunner {
205
209
  private readonly cwd: string,
206
210
  private readonly sessionManager: SessionManager,
207
211
  private readonly modelRegistry: ModelRegistry,
212
+ private readonly sessionMetadata?: ExtensionContext["sessionMetadata"],
208
213
  ) {
209
214
  this.#uiContext = noOpUIContext;
215
+ this.#handlersByEvent = ExtensionRunner.#indexHandlers(extensions);
216
+ }
217
+
218
+ static #indexHandlers(extensions: Extension[]): Map<string, IndexedHandler[]> {
219
+ const handlersByEvent = new Map<string, IndexedHandler[]>();
220
+ for (const ext of extensions) {
221
+ for (const [eventType, handlers] of ext.handlers) {
222
+ let indexedHandlers = handlersByEvent.get(eventType);
223
+ if (!indexedHandlers) {
224
+ indexedHandlers = [];
225
+ handlersByEvent.set(eventType, indexedHandlers);
226
+ }
227
+ for (const handler of handlers) {
228
+ indexedHandlers.push({ ext, handler });
229
+ }
230
+ }
231
+ }
232
+ return handlersByEvent;
210
233
  }
211
234
 
212
235
  initialize(
@@ -395,13 +418,7 @@ export class ExtensionRunner {
395
418
  }
396
419
 
397
420
  hasHandlers(eventType: string): boolean {
398
- for (const ext of this.extensions) {
399
- const handlers = ext.handlers.get(eventType);
400
- if (handlers && handlers.length > 0) {
401
- return true;
402
- }
403
- }
404
- return false;
421
+ return (this.#handlersByEvent.get(eventType)?.length ?? 0) > 0;
405
422
  }
406
423
 
407
424
  getMessageRenderer(customType: string): MessageRenderer | undefined {
@@ -458,6 +475,7 @@ export class ExtensionRunner {
458
475
  hasUI: this.hasUI(),
459
476
  cwd: this.cwd,
460
477
  sessionManager: this.sessionManager,
478
+ sessionMetadata: this.sessionMetadata,
461
479
  modelRegistry: this.modelRegistry,
462
480
  get model() {
463
481
  return getModel();
@@ -509,11 +527,17 @@ export class ExtensionRunner {
509
527
  ext: Extension,
510
528
  timeoutMs: number,
511
529
  ): Promise<TResult | undefined> {
530
+ let timeout: ReturnType<typeof setTimeout> | undefined;
512
531
  try {
513
- const handlerResult = await Promise.race([
514
- Promise.resolve(handler(event, ctx)),
515
- Bun.sleep(timeoutMs).then(() => EXTENSION_HANDLER_TIMEOUT),
516
- ]);
532
+ const timeoutPromise = new Promise<typeof EXTENSION_HANDLER_TIMEOUT>(resolve => {
533
+ timeout = setTimeout(() => resolve(EXTENSION_HANDLER_TIMEOUT), timeoutMs);
534
+ });
535
+ const handlerResult = await Promise.race([Promise.resolve(handler(event, ctx)), timeoutPromise]);
536
+ if (timeout !== undefined) {
537
+ clearTimeout(timeout);
538
+ timeout = undefined;
539
+ }
540
+
517
541
  if (handlerResult === EXTENSION_HANDLER_TIMEOUT) {
518
542
  const error = `handler timed out after ${timeoutMs}ms`;
519
543
  logger.warn("Extension handler timed out", {
@@ -539,73 +563,67 @@ export class ExtensionRunner {
539
563
  stack,
540
564
  });
541
565
  return undefined;
566
+ } finally {
567
+ if (timeout !== undefined) {
568
+ clearTimeout(timeout);
569
+ }
542
570
  }
543
571
  }
544
572
 
545
573
  async emit<TEvent extends RunnerEmitEvent>(event: TEvent): Promise<RunnerEmitResult<TEvent>> {
574
+ const handlers = this.#handlersByEvent.get(event.type) ?? [];
575
+ if (handlers.length === 0) return undefined as RunnerEmitResult<TEvent>;
576
+
546
577
  const ctx = this.createContext();
547
578
  let result: SessionBeforeEventResult | SessionCompactingResult | undefined;
548
579
 
549
- for (const ext of this.extensions) {
550
- const handlers = ext.handlers.get(event.type);
551
- if (!handlers || handlers.length === 0) continue;
552
-
553
- for (const handler of handlers) {
554
- const handlerResult = await this.#runHandlerWithTimeout(
555
- handler,
556
- event,
557
- ctx,
558
- ext,
559
- extensionHandlerTimeoutMs,
560
- );
561
-
562
- if (this.#isSessionBeforeEvent(event) && handlerResult) {
563
- result = handlerResult as SessionBeforeEventResult;
564
- if (result.cancel) {
565
- return result as RunnerEmitResult<TEvent>;
566
- }
567
- }
580
+ for (const { ext, handler } of handlers) {
581
+ const handlerResult = await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs);
568
582
 
569
- if (event.type === "session.compacting" && handlerResult) {
570
- result = handlerResult as SessionCompactingResult;
583
+ if (this.#isSessionBeforeEvent(event) && handlerResult) {
584
+ result = handlerResult as SessionBeforeEventResult;
585
+ if (result.cancel) {
586
+ return result as RunnerEmitResult<TEvent>;
571
587
  }
572
588
  }
589
+
590
+ if (event.type === "session.compacting" && handlerResult) {
591
+ result = handlerResult as SessionCompactingResult;
592
+ }
573
593
  }
574
594
 
575
595
  return result as RunnerEmitResult<TEvent>;
576
596
  }
577
597
 
578
598
  async emitToolResult(event: ToolResultEvent): Promise<ToolResultEventResult | undefined> {
599
+ const handlers = this.#handlersByEvent.get("tool_result") ?? [];
600
+ if (handlers.length === 0) return undefined;
601
+
579
602
  const ctx = this.createContext();
580
603
  const currentEvent: ToolResultEvent = { ...event };
581
604
  let modified = false;
582
605
 
583
- for (const ext of this.extensions) {
584
- const handlers = ext.handlers.get("tool_result");
585
- if (!handlers || handlers.length === 0) continue;
586
-
587
- for (const handler of handlers) {
588
- const handlerResult = (await this.#runHandlerWithTimeout(
589
- handler,
590
- currentEvent,
591
- ctx,
592
- ext,
593
- extensionHandlerTimeoutMs,
594
- )) as ToolResultEventResult | undefined;
595
- if (!handlerResult) continue;
596
-
597
- if (handlerResult.content !== undefined) {
598
- currentEvent.content = handlerResult.content;
599
- modified = true;
600
- }
601
- if (handlerResult.details !== undefined) {
602
- currentEvent.details = handlerResult.details;
603
- modified = true;
604
- }
605
- if (handlerResult.isError !== undefined) {
606
- currentEvent.isError = handlerResult.isError;
607
- modified = true;
608
- }
606
+ for (const { ext, handler } of handlers) {
607
+ const handlerResult = (await this.#runHandlerWithTimeout(
608
+ handler,
609
+ currentEvent,
610
+ ctx,
611
+ ext,
612
+ extensionHandlerTimeoutMs,
613
+ )) as ToolResultEventResult | undefined;
614
+ if (!handlerResult) continue;
615
+
616
+ if (handlerResult.content !== undefined) {
617
+ currentEvent.content = handlerResult.content;
618
+ modified = true;
619
+ }
620
+ if (handlerResult.details !== undefined) {
621
+ currentEvent.details = handlerResult.details;
622
+ modified = true;
623
+ }
624
+ if (handlerResult.isError !== undefined) {
625
+ currentEvent.isError = handlerResult.isError;
626
+ modified = true;
609
627
  }
610
628
  }
611
629
 
@@ -619,34 +637,32 @@ export class ExtensionRunner {
619
637
  }
620
638
 
621
639
  async emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined> {
640
+ const handlers = this.#handlersByEvent.get("tool_call") ?? [];
641
+ if (handlers.length === 0) return undefined;
642
+
622
643
  const ctx = this.createContext();
623
644
  let result: ToolCallEventResult | undefined;
624
645
 
625
- for (const ext of this.extensions) {
626
- const handlers = ext.handlers.get("tool_call");
627
- if (!handlers || handlers.length === 0) continue;
628
-
629
- for (const handler of handlers) {
630
- try {
631
- const handlerResult = await handler(event, ctx);
632
-
633
- if (handlerResult) {
634
- result = handlerResult as ToolCallEventResult;
635
- if (result.block) {
636
- return result;
637
- }
646
+ for (const { ext, handler } of handlers) {
647
+ try {
648
+ const handlerResult = await handler(event, ctx);
649
+
650
+ if (handlerResult) {
651
+ result = handlerResult as ToolCallEventResult;
652
+ if (result.block) {
653
+ return result;
638
654
  }
639
- } catch (err) {
640
- const message = err instanceof Error ? err.message : String(err);
641
- const stack = err instanceof Error ? err.stack : undefined;
642
- this.emitError({
643
- extensionPath: ext.path,
644
- event: "tool_call",
645
- error: message,
646
- stack,
647
- });
648
- return { block: true, reason: `Extension ${ext.path} failed: ${message}` };
649
655
  }
656
+ } catch (err) {
657
+ const message = err instanceof Error ? err.message : String(err);
658
+ const stack = err instanceof Error ? err.stack : undefined;
659
+ this.emitError({
660
+ extensionPath: ext.path,
661
+ event: "tool_call",
662
+ error: message,
663
+ stack,
664
+ });
665
+ return { block: true, reason: `Extension ${ext.path} failed: ${message}` };
650
666
  }
651
667
  }
652
668
 
@@ -665,23 +681,15 @@ export class ExtensionRunner {
665
681
  event: UserBashEvent | UserPythonEvent,
666
682
  eventName: "user_bash" | "user_python",
667
683
  ): Promise<R | undefined> {
684
+ const handlers = this.#handlersByEvent.get(eventName) ?? [];
685
+ if (handlers.length === 0) return undefined;
686
+
668
687
  const ctx = this.createContext();
669
688
 
670
- for (const ext of this.extensions) {
671
- const handlers = ext.handlers.get(eventName);
672
- if (!handlers || handlers.length === 0) continue;
673
-
674
- for (const handler of handlers) {
675
- const handlerResult = await this.#runHandlerWithTimeout(
676
- handler,
677
- event,
678
- ctx,
679
- ext,
680
- extensionHandlerTimeoutMs,
681
- );
682
- if (handlerResult) {
683
- return handlerResult as R;
684
- }
689
+ for (const { ext, handler } of handlers) {
690
+ const handlerResult = await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs);
691
+ if (handlerResult) {
692
+ return handlerResult as R;
685
693
  }
686
694
  }
687
695
 
@@ -696,35 +704,26 @@ export class ExtensionRunner {
696
704
  promptPaths: Array<{ path: string; extensionPath: string }>;
697
705
  themePaths: Array<{ path: string; extensionPath: string }>;
698
706
  }> {
707
+ const handlers = this.#handlersByEvent.get("resources_discover") ?? [];
708
+ if (handlers.length === 0) return { skillPaths: [], promptPaths: [], themePaths: [] };
699
709
  const ctx = this.createContext();
700
710
  const skillPaths: Array<{ path: string; extensionPath: string }> = [];
701
711
  const promptPaths: Array<{ path: string; extensionPath: string }> = [];
702
712
  const themePaths: Array<{ path: string; extensionPath: string }> = [];
703
713
 
704
- for (const ext of this.extensions) {
705
- const handlers = ext.handlers.get("resources_discover");
706
- if (!handlers || handlers.length === 0) continue;
707
-
708
- for (const handler of handlers) {
709
- const event: ResourcesDiscoverEvent = { type: "resources_discover", cwd, reason };
710
- const handlerResult = await this.#runHandlerWithTimeout(
711
- handler,
712
- event,
713
- ctx,
714
- ext,
715
- extensionHandlerTimeoutMs,
716
- );
717
- const result = handlerResult as ResourcesDiscoverResult | undefined;
718
-
719
- if (result?.skillPaths?.length) {
720
- skillPaths.push(...result.skillPaths.map(path => ({ path, extensionPath: ext.path })));
721
- }
722
- if (result?.promptPaths?.length) {
723
- promptPaths.push(...result.promptPaths.map(path => ({ path, extensionPath: ext.path })));
724
- }
725
- if (result?.themePaths?.length) {
726
- themePaths.push(...result.themePaths.map(path => ({ path, extensionPath: ext.path })));
727
- }
714
+ for (const { ext, handler } of handlers) {
715
+ const event: ResourcesDiscoverEvent = { type: "resources_discover", cwd, reason };
716
+ const handlerResult = await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs);
717
+ const result = handlerResult as ResourcesDiscoverResult | undefined;
718
+
719
+ if (result?.skillPaths?.length) {
720
+ skillPaths.push(...result.skillPaths.map(path => ({ path, extensionPath: ext.path })));
721
+ }
722
+ if (result?.promptPaths?.length) {
723
+ promptPaths.push(...result.promptPaths.map(path => ({ path, extensionPath: ext.path })));
724
+ }
725
+ if (result?.themePaths?.length) {
726
+ themePaths.push(...result.themePaths.map(path => ({ path, extensionPath: ext.path })));
728
727
  }
729
728
  }
730
729
 
@@ -737,39 +736,33 @@ export class ExtensionRunner {
737
736
  images: ImageContent[] | undefined,
738
737
  source: "interactive" | "rpc" | "extension",
739
738
  ): Promise<InputEventResult> {
739
+ const handlers = this.#handlersByEvent.get("input") ?? [];
740
+ if (handlers.length === 0) return {};
741
+
740
742
  const ctx = this.createContext();
741
743
  let currentText = text;
742
744
  let currentImages = images;
743
745
 
744
- for (const ext of this.extensions) {
745
- for (const handler of ext.handlers.get("input") ?? []) {
746
- const event: InputEvent = { type: "input", text: currentText, images: currentImages, source };
747
- const result = (await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs)) as
748
- | InputEventResult
749
- | undefined;
750
- if (result?.handled) return result;
751
- if (result?.text !== undefined) {
752
- currentText = result.text;
753
- currentImages = result.images ?? currentImages;
754
- }
746
+ for (const { ext, handler } of handlers) {
747
+ const event: InputEvent = { type: "input", text: currentText, images: currentImages, source };
748
+ const result = (await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs)) as
749
+ | InputEventResult
750
+ | undefined;
751
+ if (result?.handled) return result;
752
+ if (result?.text !== undefined) {
753
+ currentText = result.text;
754
+ currentImages = result.images ?? currentImages;
755
755
  }
756
756
  }
757
+
757
758
  return currentText !== text || currentImages !== images ? { text: currentText, images: currentImages } : {};
758
759
  }
759
760
 
760
761
  async emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {
761
- const ctx = this.createContext();
762
-
763
- // Check if any extensions actually have context handlers before cloning
764
- let hasContextHandlers = false;
765
- for (const ext of this.extensions) {
766
- if (ext.handlers.get("context")?.length) {
767
- hasContextHandlers = true;
768
- break;
769
- }
770
- }
771
- if (!hasContextHandlers) return messages;
762
+ const handlers = this.#handlersByEvent.get("context") ?? [];
763
+ if (handlers.length === 0) return messages;
772
764
 
765
+ const ctx = this.createContext();
773
766
  let currentMessages: AgentMessage[];
774
767
  try {
775
768
  currentMessages = structuredClone(messages);
@@ -780,23 +773,12 @@ export class ExtensionRunner {
780
773
  currentMessages = [...messages];
781
774
  }
782
775
 
783
- for (const ext of this.extensions) {
784
- const handlers = ext.handlers.get("context");
785
- if (!handlers || handlers.length === 0) continue;
786
-
787
- for (const handler of handlers) {
788
- const event: ContextEvent = { type: "context", messages: currentMessages };
789
- const handlerResult = await this.#runHandlerWithTimeout(
790
- handler,
791
- event,
792
- ctx,
793
- ext,
794
- extensionHandlerTimeoutMs,
795
- );
796
-
797
- if (handlerResult && (handlerResult as ContextEventResult).messages) {
798
- currentMessages = (handlerResult as ContextEventResult).messages!;
799
- }
776
+ for (const { ext, handler } of handlers) {
777
+ const event: ContextEvent = { type: "context", messages: currentMessages };
778
+ const handlerResult = await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs);
779
+
780
+ if (handlerResult && (handlerResult as ContextEventResult).messages) {
781
+ currentMessages = (handlerResult as ContextEventResult).messages!;
800
782
  }
801
783
  }
802
784
 
@@ -804,28 +786,20 @@ export class ExtensionRunner {
804
786
  }
805
787
 
806
788
  async emitBeforeProviderRequest(payload: unknown): Promise<BeforeProviderRequestEventResult> {
789
+ const handlers = this.#handlersByEvent.get("before_provider_request") ?? [];
790
+ if (handlers.length === 0) return payload;
791
+
807
792
  const ctx = this.createContext();
808
793
  let currentPayload = payload;
809
794
 
810
- for (const ext of this.extensions) {
811
- const handlers = ext.handlers.get("before_provider_request");
812
- if (!handlers || handlers.length === 0) continue;
813
-
814
- for (const handler of handlers) {
815
- const event: BeforeProviderRequestEvent = {
816
- type: "before_provider_request",
817
- payload: currentPayload,
818
- };
819
- const handlerResult = await this.#runHandlerWithTimeout(
820
- handler,
821
- event,
822
- ctx,
823
- ext,
824
- extensionHandlerTimeoutMs,
825
- );
826
- if (handlerResult !== undefined) {
827
- currentPayload = handlerResult;
828
- }
795
+ for (const { ext, handler } of handlers) {
796
+ const event: BeforeProviderRequestEvent = {
797
+ type: "before_provider_request",
798
+ payload: currentPayload,
799
+ };
800
+ const handlerResult = await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs);
801
+ if (handlerResult !== undefined) {
802
+ currentPayload = handlerResult;
829
803
  }
830
804
  }
831
805
 
@@ -833,22 +807,20 @@ export class ExtensionRunner {
833
807
  }
834
808
 
835
809
  async emitAfterProviderResponse(response: ProviderResponseMetadata, _model?: Model): Promise<void> {
810
+ const handlers = this.#handlersByEvent.get("after_provider_response") ?? [];
811
+ if (handlers.length === 0) return;
812
+
836
813
  const ctx = this.createContext();
837
814
 
838
- for (const ext of this.extensions) {
839
- const handlers = ext.handlers.get("after_provider_response");
840
- if (!handlers || handlers.length === 0) continue;
841
-
842
- for (const handler of handlers) {
843
- const event: AfterProviderResponseEvent = {
844
- type: "after_provider_response",
845
- status: response.status,
846
- headers: response.headers,
847
- requestId: response.requestId,
848
- metadata: response.metadata,
849
- };
850
- await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs);
851
- }
815
+ for (const { ext, handler } of handlers) {
816
+ const event: AfterProviderResponseEvent = {
817
+ type: "after_provider_response",
818
+ status: response.status,
819
+ headers: response.headers,
820
+ requestId: response.requestId,
821
+ metadata: response.metadata,
822
+ };
823
+ await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs);
852
824
  }
853
825
  }
854
826
 
@@ -857,39 +829,31 @@ export class ExtensionRunner {
857
829
  images: ImageContent[] | undefined,
858
830
  systemPrompt: string[],
859
831
  ): Promise<BeforeAgentStartCombinedResult | undefined> {
832
+ const handlers = this.#handlersByEvent.get("before_agent_start") ?? [];
833
+ if (handlers.length === 0) return undefined;
834
+
860
835
  const ctx = this.createContext();
861
836
  const messages: NonNullable<BeforeAgentStartEventResult["message"]>[] = [];
862
837
  let currentSystemPrompt = systemPrompt;
863
838
  let systemPromptModified = false;
864
839
 
865
- for (const ext of this.extensions) {
866
- const handlers = ext.handlers.get("before_agent_start");
867
- if (!handlers || handlers.length === 0) continue;
868
-
869
- for (const handler of handlers) {
870
- const event: BeforeAgentStartEvent = {
871
- type: "before_agent_start",
872
- prompt,
873
- images,
874
- systemPrompt: currentSystemPrompt,
875
- };
876
- const handlerResult = await this.#runHandlerWithTimeout(
877
- handler,
878
- event,
879
- ctx,
880
- ext,
881
- extensionHandlerTimeoutMs,
882
- );
840
+ for (const { ext, handler } of handlers) {
841
+ const event: BeforeAgentStartEvent = {
842
+ type: "before_agent_start",
843
+ prompt,
844
+ images,
845
+ systemPrompt: currentSystemPrompt,
846
+ };
847
+ const handlerResult = await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs);
883
848
 
884
- if (handlerResult) {
885
- const result = handlerResult as BeforeAgentStartEventResult;
886
- if (result.message) {
887
- messages.push(result.message);
888
- }
889
- if (result.systemPrompt !== undefined) {
890
- currentSystemPrompt = result.systemPrompt;
891
- systemPromptModified = true;
892
- }
849
+ if (handlerResult) {
850
+ const result = handlerResult as BeforeAgentStartEventResult;
851
+ if (result.message) {
852
+ messages.push(result.message);
853
+ }
854
+ if (result.systemPrompt !== undefined) {
855
+ currentSystemPrompt = result.systemPrompt;
856
+ systemPromptModified = true;
893
857
  }
894
858
  }
895
859
  }
@@ -285,6 +285,13 @@ export interface CompactOptions {
285
285
  onError?: (error: Error) => void;
286
286
  }
287
287
 
288
+ export interface ExtensionSessionMetadata {
289
+ kind: "main" | "sub";
290
+ taskDepth: number;
291
+ parentTaskPrefix?: string;
292
+ currentAgentType?: string;
293
+ }
294
+
288
295
  /**
289
296
  * Context passed to extension event handlers.
290
297
  */
@@ -306,6 +313,8 @@ export interface ExtensionContext {
306
313
  cwd: string;
307
314
  /** Session manager (read-only) */
308
315
  sessionManager: ReadonlySessionManager;
316
+ /** Session classification supplied by the SDK for extension policy decisions. */
317
+ sessionMetadata?: ExtensionSessionMetadata;
309
318
  /** Model registry for API key resolution */
310
319
  modelRegistry: ModelRegistry;
311
320
  /** Current model (may be undefined) */