@gajae-code/coding-agent 0.8.2 → 0.9.1

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