@leanandmean/coding-agent 0.74.1-scramjet.7 → 0.74.1-scramjet.71

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 (226) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +202 -0
  3. package/README.md +37 -9
  4. package/dist/cli/args.d.ts +2 -0
  5. package/dist/cli/args.d.ts.map +1 -1
  6. package/dist/cli/args.js +45 -1
  7. package/dist/cli/args.js.map +1 -1
  8. package/dist/cli/initial-message.d.ts +1 -0
  9. package/dist/cli/initial-message.d.ts.map +1 -1
  10. package/dist/cli/initial-message.js +3 -0
  11. package/dist/cli/initial-message.js.map +1 -1
  12. package/dist/cli/list-models.d.ts +2 -0
  13. package/dist/cli/list-models.d.ts.map +1 -1
  14. package/dist/cli/list-models.js +4 -1
  15. package/dist/cli/list-models.js.map +1 -1
  16. package/dist/config.d.ts +9 -2
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +33 -22
  19. package/dist/config.js.map +1 -1
  20. package/dist/core/agent-session-runtime.d.ts +12 -3
  21. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  22. package/dist/core/agent-session-runtime.js +64 -38
  23. package/dist/core/agent-session-runtime.js.map +1 -1
  24. package/dist/core/agent-session-services.d.ts +2 -0
  25. package/dist/core/agent-session-services.d.ts.map +1 -1
  26. package/dist/core/agent-session-services.js +3 -0
  27. package/dist/core/agent-session-services.js.map +1 -1
  28. package/dist/core/agent-session.d.ts +84 -17
  29. package/dist/core/agent-session.d.ts.map +1 -1
  30. package/dist/core/agent-session.js +1082 -209
  31. package/dist/core/agent-session.js.map +1 -1
  32. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  33. package/dist/core/compaction/branch-summarization.js +21 -17
  34. package/dist/core/compaction/branch-summarization.js.map +1 -1
  35. package/dist/core/compaction/compaction.d.ts +3 -5
  36. package/dist/core/compaction/compaction.d.ts.map +1 -1
  37. package/dist/core/compaction/compaction.js +43 -10
  38. package/dist/core/compaction/compaction.js.map +1 -1
  39. package/dist/core/exec.d.ts +12 -0
  40. package/dist/core/exec.d.ts.map +1 -1
  41. package/dist/core/exec.js +74 -28
  42. package/dist/core/exec.js.map +1 -1
  43. package/dist/core/export-html/template.js +4 -0
  44. package/dist/core/extensions/index.d.ts +1 -1
  45. package/dist/core/extensions/index.d.ts.map +1 -1
  46. package/dist/core/extensions/index.js.map +1 -1
  47. package/dist/core/extensions/runner.d.ts +9 -5
  48. package/dist/core/extensions/runner.d.ts.map +1 -1
  49. package/dist/core/extensions/runner.js +160 -3
  50. package/dist/core/extensions/runner.js.map +1 -1
  51. package/dist/core/extensions/types.d.ts +85 -8
  52. package/dist/core/extensions/types.d.ts.map +1 -1
  53. package/dist/core/extensions/types.js.map +1 -1
  54. package/dist/core/keybindings.d.ts +8 -0
  55. package/dist/core/keybindings.d.ts.map +1 -1
  56. package/dist/core/model-registry.d.ts +103 -0
  57. package/dist/core/model-registry.d.ts.map +1 -1
  58. package/dist/core/model-registry.js +272 -28
  59. package/dist/core/model-registry.js.map +1 -1
  60. package/dist/core/model-resolver.d.ts.map +1 -1
  61. package/dist/core/model-resolver.js +24 -6
  62. package/dist/core/model-resolver.js.map +1 -1
  63. package/dist/core/output-throughput-history.d.ts +25 -0
  64. package/dist/core/output-throughput-history.d.ts.map +1 -0
  65. package/dist/core/output-throughput-history.js +240 -0
  66. package/dist/core/output-throughput-history.js.map +1 -0
  67. package/dist/core/output-throughput.d.ts +43 -0
  68. package/dist/core/output-throughput.d.ts.map +1 -0
  69. package/dist/core/output-throughput.js +161 -0
  70. package/dist/core/output-throughput.js.map +1 -0
  71. package/dist/core/resource-loader.d.ts +6 -0
  72. package/dist/core/resource-loader.d.ts.map +1 -1
  73. package/dist/core/resource-loader.js +76 -21
  74. package/dist/core/resource-loader.js.map +1 -1
  75. package/dist/core/scramjet-command-parser.d.ts +4 -0
  76. package/dist/core/scramjet-command-parser.d.ts.map +1 -1
  77. package/dist/core/scramjet-command-parser.js +72 -1
  78. package/dist/core/scramjet-command-parser.js.map +1 -1
  79. package/dist/core/sdk.d.ts +4 -0
  80. package/dist/core/sdk.d.ts.map +1 -1
  81. package/dist/core/sdk.js +70 -11
  82. package/dist/core/sdk.js.map +1 -1
  83. package/dist/core/session-manager.d.ts +11 -3
  84. package/dist/core/session-manager.d.ts.map +1 -1
  85. package/dist/core/session-manager.js +46 -11
  86. package/dist/core/session-manager.js.map +1 -1
  87. package/dist/core/settings-manager.d.ts +19 -6
  88. package/dist/core/settings-manager.d.ts.map +1 -1
  89. package/dist/core/settings-manager.js +131 -17
  90. package/dist/core/settings-manager.js.map +1 -1
  91. package/dist/core/skills.d.ts.map +1 -1
  92. package/dist/core/skills.js +6 -2
  93. package/dist/core/skills.js.map +1 -1
  94. package/dist/core/system-prompt.d.ts +2 -0
  95. package/dist/core/system-prompt.d.ts.map +1 -1
  96. package/dist/core/system-prompt.js +5 -2
  97. package/dist/core/system-prompt.js.map +1 -1
  98. package/dist/core/tools/bash.d.ts.map +1 -1
  99. package/dist/core/tools/bash.js +4 -3
  100. package/dist/core/tools/bash.js.map +1 -1
  101. package/dist/index.d.ts +4 -3
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +3 -2
  104. package/dist/index.js.map +1 -1
  105. package/dist/main.d.ts +119 -0
  106. package/dist/main.d.ts.map +1 -1
  107. package/dist/main.js +113 -11
  108. package/dist/main.js.map +1 -1
  109. package/dist/modes/interactive/components/armin.d.ts +4 -2
  110. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  111. package/dist/modes/interactive/components/armin.js +14 -8
  112. package/dist/modes/interactive/components/armin.js.map +1 -1
  113. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  114. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  115. package/dist/modes/interactive/components/assistant-message.js +19 -8
  116. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  117. package/dist/modes/interactive/components/daxnuts.d.ts +4 -1
  118. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
  119. package/dist/modes/interactive/components/daxnuts.js +14 -6
  120. package/dist/modes/interactive/components/daxnuts.js.map +1 -1
  121. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  122. package/dist/modes/interactive/components/extension-editor.js +5 -3
  123. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  124. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  125. package/dist/modes/interactive/components/extension-selector.js +4 -3
  126. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  127. package/dist/modes/interactive/components/footer.d.ts +4 -0
  128. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  129. package/dist/modes/interactive/components/footer.js +24 -20
  130. package/dist/modes/interactive/components/footer.js.map +1 -1
  131. package/dist/modes/interactive/components/model-selector.d.ts +3 -3
  132. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  133. package/dist/modes/interactive/components/model-selector.js +10 -7
  134. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  135. package/dist/modes/interactive/components/scoped-models-selector.d.ts +3 -1
  136. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  137. package/dist/modes/interactive/components/scoped-models-selector.js +8 -2
  138. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  139. package/dist/modes/interactive/components/settings-selector.d.ts +17 -3
  140. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  141. package/dist/modes/interactive/components/settings-selector.js +87 -16
  142. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  143. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  144. package/dist/modes/interactive/components/thinking-selector.js +2 -1
  145. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  146. package/dist/modes/interactive/components/tool-execution.d.ts +19 -1
  147. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  148. package/dist/modes/interactive/components/tool-execution.js +129 -36
  149. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  150. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  151. package/dist/modes/interactive/components/user-message.js +3 -2
  152. package/dist/modes/interactive/components/user-message.js.map +1 -1
  153. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
  154. package/dist/modes/interactive/components/visual-truncate.js +2 -2
  155. package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
  156. package/dist/modes/interactive/interactive-mode.d.ts +37 -1
  157. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  158. package/dist/modes/interactive/interactive-mode.js +902 -177
  159. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  160. package/dist/modes/interactive/spellcheck.d.ts +26 -0
  161. package/dist/modes/interactive/spellcheck.d.ts.map +1 -0
  162. package/dist/modes/interactive/spellcheck.js +144 -0
  163. package/dist/modes/interactive/spellcheck.js.map +1 -0
  164. package/dist/modes/interactive/theme/{dark.json → pi-dark.json} +3 -2
  165. package/dist/modes/interactive/theme/{light.json → pi-light.json} +27 -26
  166. package/dist/modes/interactive/theme/theme-schema.json +6 -1
  167. package/dist/modes/interactive/theme/theme.d.ts +24 -4
  168. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  169. package/dist/modes/interactive/theme/theme.js +123 -22
  170. package/dist/modes/interactive/theme/theme.js.map +1 -1
  171. package/dist/modes/print-mode.d.ts.map +1 -1
  172. package/dist/modes/print-mode.js +5 -0
  173. package/dist/modes/print-mode.js.map +1 -1
  174. package/dist/modes/rpc/rpc-client.d.ts +3 -1
  175. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  176. package/dist/modes/rpc/rpc-client.js.map +1 -1
  177. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  178. package/dist/modes/rpc/rpc-mode.js +6 -1
  179. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  180. package/dist/package-manager-cli.d.ts.map +1 -1
  181. package/dist/package-manager-cli.js +33 -14
  182. package/dist/package-manager-cli.js.map +1 -1
  183. package/dist/utils/clipboard-native.d.ts +1 -0
  184. package/dist/utils/clipboard-native.d.ts.map +1 -1
  185. package/dist/utils/clipboard-native.js.map +1 -1
  186. package/dist/utils/clipboard.d.ts +6 -0
  187. package/dist/utils/clipboard.d.ts.map +1 -1
  188. package/dist/utils/clipboard.js +278 -10
  189. package/dist/utils/clipboard.js.map +1 -1
  190. package/dist/utils/tools-manager.d.ts +1 -1
  191. package/dist/utils/tools-manager.d.ts.map +1 -1
  192. package/dist/utils/tools-manager.js +7 -6
  193. package/dist/utils/tools-manager.js.map +1 -1
  194. package/dist/utils/version-check.d.ts +17 -5
  195. package/dist/utils/version-check.d.ts.map +1 -1
  196. package/dist/utils/version-check.js +88 -21
  197. package/dist/utils/version-check.js.map +1 -1
  198. package/docs/compaction.md +21 -3
  199. package/docs/custom-provider.md +33 -4
  200. package/docs/extensions.md +150 -24
  201. package/docs/json.md +8 -0
  202. package/docs/keybindings.md +25 -1
  203. package/docs/models.md +103 -4
  204. package/docs/packages.md +2 -0
  205. package/docs/prompt-templates.md +2 -0
  206. package/docs/quickstart.md +1 -4
  207. package/docs/rpc.md +16 -6
  208. package/docs/sdk.md +48 -3
  209. package/docs/session-format.md +5 -1
  210. package/docs/settings.md +19 -5
  211. package/docs/skills.md +7 -5
  212. package/docs/terminal-setup.md +79 -2
  213. package/docs/terminal-verification.md +71 -0
  214. package/docs/themes.md +63 -10
  215. package/docs/tui.md +120 -25
  216. package/docs/usage.md +6 -4
  217. package/examples/extensions/border-status-editor.ts +2 -3
  218. package/examples/extensions/doom-overlay/doom-component.ts +3 -3
  219. package/examples/extensions/mac-system-theme.ts +3 -2
  220. package/examples/extensions/modal-editor.ts +13 -8
  221. package/examples/extensions/overlay-qa-tests.ts +22 -9
  222. package/examples/extensions/overlay-test.ts +5 -3
  223. package/examples/extensions/snake.ts +3 -1
  224. package/examples/extensions/space-invaders.ts +6 -3
  225. package/examples/extensions/subagent/index.ts +20 -17
  226. package/package.json +69 -58
@@ -22,8 +22,18 @@ const RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [
22
22
  "tui.select.confirm",
23
23
  "tui.select.cancel",
24
24
  "tui.input.copy",
25
+ "tui.viewport.pageUp",
26
+ "tui.viewport.pageDown",
25
27
  "tui.editor.deleteToLineEnd",
26
28
  ];
29
+ function deepFreeze(value) {
30
+ Object.freeze(value);
31
+ for (const nested of Object.values(value)) {
32
+ if (nested && typeof nested === "object" && !Object.isFrozen(nested))
33
+ deepFreeze(nested);
34
+ }
35
+ return value;
36
+ }
27
37
  const buildBuiltinKeybindings = (resolvedKeybindings) => {
28
38
  const builtinKeybindings = {};
29
39
  for (const [keybinding, keys] of Object.entries(resolvedKeybindings)) {
@@ -89,6 +99,7 @@ const noOpUIContext = {
89
99
  setFooter: () => { },
90
100
  setHeader: () => { },
91
101
  setTitle: () => { },
102
+ setTitleProvider: () => { },
92
103
  custom: async () => undefined,
93
104
  pasteToEditor: () => { },
94
105
  setEditorText: () => { },
@@ -115,6 +126,7 @@ export class ExtensionRunner {
115
126
  modelRegistry;
116
127
  errorListeners = new Set();
117
128
  getModel = () => undefined;
129
+ getScopedModelsFn = () => [];
118
130
  isIdleFn = () => true;
119
131
  getSignalFn = () => undefined;
120
132
  waitForIdleFn = async () => { };
@@ -173,6 +185,7 @@ export class ExtensionRunner {
173
185
  this.runtime.setThinkingLevel = actions.setThinkingLevel;
174
186
  // Context actions (required)
175
187
  this.getModel = contextActions.getModel;
188
+ this.getScopedModelsFn = contextActions.getScopedModels;
176
189
  this.isIdleFn = contextActions.isIdle;
177
190
  this.getSignalFn = contextActions.getSignal;
178
191
  this.abortFn = contextActions.abort;
@@ -421,6 +434,7 @@ export class ExtensionRunner {
421
434
  createContext() {
422
435
  const runner = this;
423
436
  const getModel = this.getModel;
437
+ const getScopedModels = this.getScopedModelsFn;
424
438
  return {
425
439
  get ui() {
426
440
  runner.assertActive();
@@ -446,6 +460,10 @@ export class ExtensionRunner {
446
460
  runner.assertActive();
447
461
  return getModel();
448
462
  },
463
+ get scopedModels() {
464
+ runner.assertActive();
465
+ return getScopedModels();
466
+ },
449
467
  isIdle: () => {
450
468
  runner.assertActive();
451
469
  return runner.isIdleFn();
@@ -521,7 +539,28 @@ export class ExtensionRunner {
521
539
  event.type === "session_before_compact" ||
522
540
  event.type === "session_before_tree");
523
541
  }
542
+ // SCRAMJET-DIVERGENCE: issue 326 — after invalidate() (session replacement/teardown),
543
+ // remaining or subsequent handler invocations are skipped instead of running against a
544
+ // dead ctx and throwing the stale-ctx error. session_shutdown is the exception: skipping
545
+ // it silently would drop teardown hooks, so that ordering violation is reported.
546
+ skipStale(eventType) {
547
+ if (!this.staleMessage)
548
+ return false;
549
+ if (eventType === "session_shutdown") {
550
+ this.emitError({
551
+ extensionPath: "<runner>",
552
+ event: "session_shutdown",
553
+ error: "session_shutdown emitted after runner invalidation; teardown hooks were skipped (ordering bug)",
554
+ });
555
+ }
556
+ else if (process.env.SCRAMJET_DEBUG || process.env.PI_DEBUG) {
557
+ console.debug(`ExtensionRunner: skipping ${eventType} handlers on invalidated runner (issue 326)`);
558
+ }
559
+ return true;
560
+ }
524
561
  async emit(event) {
562
+ if (this.skipStale(event.type))
563
+ return undefined;
525
564
  const ctx = this.createContext();
526
565
  let result;
527
566
  for (const ext of this.extensions) {
@@ -529,6 +568,8 @@ export class ExtensionRunner {
529
568
  if (!handlers || handlers.length === 0)
530
569
  continue;
531
570
  for (const handler of handlers) {
571
+ if (this.skipStale(event.type))
572
+ return result;
532
573
  try {
533
574
  const handlerResult = await handler(event, ctx);
534
575
  if (this.isSessionBeforeEvent(event) && handlerResult) {
@@ -553,6 +594,8 @@ export class ExtensionRunner {
553
594
  return result;
554
595
  }
555
596
  async emitMessageEnd(event) {
597
+ if (this.skipStale("message_end"))
598
+ return undefined;
556
599
  const ctx = this.createContext();
557
600
  let currentMessage = event.message;
558
601
  let modified = false;
@@ -561,6 +604,8 @@ export class ExtensionRunner {
561
604
  if (!handlers || handlers.length === 0)
562
605
  continue;
563
606
  for (const handler of handlers) {
607
+ if (this.skipStale("message_end"))
608
+ return modified ? currentMessage : undefined;
564
609
  try {
565
610
  const currentEvent = { ...event, message: currentMessage };
566
611
  const handlerResult = (await handler(currentEvent, ctx));
@@ -592,6 +637,8 @@ export class ExtensionRunner {
592
637
  return modified ? currentMessage : undefined;
593
638
  }
594
639
  async emitToolResult(event) {
640
+ if (this.skipStale("tool_result"))
641
+ return undefined;
595
642
  const ctx = this.createContext();
596
643
  const currentEvent = { ...event };
597
644
  let modified = false;
@@ -600,6 +647,8 @@ export class ExtensionRunner {
600
647
  if (!handlers || handlers.length === 0)
601
648
  continue;
602
649
  for (const handler of handlers) {
650
+ if (this.skipStale("tool_result"))
651
+ break;
603
652
  try {
604
653
  const handlerResult = (await handler(currentEvent, ctx));
605
654
  if (!handlerResult)
@@ -639,6 +688,8 @@ export class ExtensionRunner {
639
688
  };
640
689
  }
641
690
  async emitToolCall(event) {
691
+ if (this.skipStale("tool_call"))
692
+ return undefined;
642
693
  const ctx = this.createContext();
643
694
  let result;
644
695
  for (const ext of this.extensions) {
@@ -646,6 +697,8 @@ export class ExtensionRunner {
646
697
  if (!handlers || handlers.length === 0)
647
698
  continue;
648
699
  for (const handler of handlers) {
700
+ if (this.skipStale("tool_call"))
701
+ return result;
649
702
  const handlerResult = await handler(event, ctx);
650
703
  if (handlerResult) {
651
704
  result = handlerResult;
@@ -658,12 +711,16 @@ export class ExtensionRunner {
658
711
  return result;
659
712
  }
660
713
  async emitUserBash(event) {
714
+ if (this.skipStale("user_bash"))
715
+ return undefined;
661
716
  const ctx = this.createContext();
662
717
  for (const ext of this.extensions) {
663
718
  const handlers = ext.handlers.get("user_bash");
664
719
  if (!handlers || handlers.length === 0)
665
720
  continue;
666
721
  for (const handler of handlers) {
722
+ if (this.skipStale("user_bash"))
723
+ return undefined;
667
724
  try {
668
725
  const handlerResult = await handler(event, ctx);
669
726
  if (handlerResult) {
@@ -685,6 +742,8 @@ export class ExtensionRunner {
685
742
  return undefined;
686
743
  }
687
744
  async emitContext(messages) {
745
+ if (this.skipStale("context"))
746
+ return messages;
688
747
  const ctx = this.createContext();
689
748
  let currentMessages = structuredClone(messages);
690
749
  for (const ext of this.extensions) {
@@ -692,6 +751,8 @@ export class ExtensionRunner {
692
751
  if (!handlers || handlers.length === 0)
693
752
  continue;
694
753
  for (const handler of handlers) {
754
+ if (this.skipStale("context"))
755
+ return currentMessages;
695
756
  try {
696
757
  const event = { type: "context", messages: currentMessages };
697
758
  const handlerResult = await handler(event, ctx);
@@ -713,7 +774,45 @@ export class ExtensionRunner {
713
774
  }
714
775
  return currentMessages;
715
776
  }
716
- async emitBeforeProviderRequest(payload) {
777
+ async emitBeforeProviderCall(context, model) {
778
+ if (this.skipStale("before_provider_call"))
779
+ return context;
780
+ const ctx = this.createContext();
781
+ let systemPrompt = context.systemPrompt;
782
+ for (const ext of this.extensions) {
783
+ const handlers = ext.handlers.get("before_provider_call");
784
+ if (!handlers || handlers.length === 0)
785
+ continue;
786
+ for (const handler of handlers) {
787
+ if (this.skipStale("before_provider_call"))
788
+ return { ...context, systemPrompt };
789
+ try {
790
+ // SCRAMJET-DIVERGENCE: isolate exact routed identity from extension mutation (#491).
791
+ const modelSnapshot = deepFreeze(structuredClone(model));
792
+ const event = {
793
+ type: "before_provider_call",
794
+ model: modelSnapshot,
795
+ systemPrompt,
796
+ };
797
+ const handlerResult = (await handler(event, ctx));
798
+ if (handlerResult?.systemPrompt !== undefined)
799
+ systemPrompt = handlerResult.systemPrompt;
800
+ }
801
+ catch (err) {
802
+ this.emitError({
803
+ extensionPath: ext.path,
804
+ event: "before_provider_call",
805
+ error: err instanceof Error ? err.message : String(err),
806
+ stack: err instanceof Error ? err.stack : undefined,
807
+ });
808
+ }
809
+ }
810
+ }
811
+ return systemPrompt === context.systemPrompt ? context : { ...context, systemPrompt };
812
+ }
813
+ async emitBeforeProviderRequest(payload, model) {
814
+ if (this.skipStale("before_provider_request"))
815
+ return payload;
717
816
  const ctx = this.createContext();
718
817
  let currentPayload = payload;
719
818
  for (const ext of this.extensions) {
@@ -721,9 +820,14 @@ export class ExtensionRunner {
721
820
  if (!handlers || handlers.length === 0)
722
821
  continue;
723
822
  for (const handler of handlers) {
823
+ if (this.skipStale("before_provider_request"))
824
+ return currentPayload;
724
825
  try {
826
+ // SCRAMJET-DIVERGENCE: isolate exact routed identity from extension mutation (#491).
827
+ const modelSnapshot = deepFreeze(structuredClone(model));
725
828
  const event = {
726
829
  type: "before_provider_request",
830
+ model: modelSnapshot,
727
831
  payload: currentPayload,
728
832
  };
729
833
  const handlerResult = await handler(event, ctx);
@@ -745,7 +849,21 @@ export class ExtensionRunner {
745
849
  }
746
850
  return currentPayload;
747
851
  }
852
+ // SCRAMJET-DIVERGENCE: isolate immutable final-schema observations from extension mutation (#524).
853
+ async emitProviderRequestToolInventory(model, requestContextToolNames, inventory) {
854
+ if (this.skipStale("provider_request_tool_inventory"))
855
+ return;
856
+ const event = Object.freeze({
857
+ type: "provider_request_tool_inventory",
858
+ model: Object.freeze({ ...model }),
859
+ requestContextToolNames: Object.freeze([...requestContextToolNames]),
860
+ inventory: deepFreeze(structuredClone(inventory)),
861
+ });
862
+ await this.emit(event);
863
+ }
748
864
  async emitBeforeAgentStart(prompt, images, systemPromptSections, systemPromptOptions) {
865
+ if (this.skipStale("before_agent_start"))
866
+ return undefined;
749
867
  let currentSystemPrompt = flattenSystemPrompt(systemPromptSections);
750
868
  const ctx = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext()));
751
869
  ctx.getSystemPrompt = () => {
@@ -770,6 +888,8 @@ export class ExtensionRunner {
770
888
  systemPromptSections,
771
889
  systemPromptOptions,
772
890
  };
891
+ if (this.skipStale("before_agent_start"))
892
+ break;
773
893
  const handlerResult = await handler(event, ctx);
774
894
  if (handlerResult) {
775
895
  const result = handlerResult;
@@ -857,6 +977,8 @@ export class ExtensionRunner {
857
977
  return undefined;
858
978
  }
859
979
  async emitResourcesDiscover(cwd, reason) {
980
+ if (this.skipStale("resources_discover"))
981
+ return { skillPaths: [], promptPaths: [], themePaths: [] };
860
982
  const ctx = this.createContext();
861
983
  const skillPaths = [];
862
984
  const promptPaths = [];
@@ -866,6 +988,8 @@ export class ExtensionRunner {
866
988
  if (!handlers || handlers.length === 0)
867
989
  continue;
868
990
  for (const handler of handlers) {
991
+ if (this.skipStale("resources_discover"))
992
+ return { skillPaths, promptPaths, themePaths };
869
993
  try {
870
994
  const event = { type: "resources_discover", cwd, reason };
871
995
  const handlerResult = await handler(event, ctx);
@@ -896,11 +1020,38 @@ export class ExtensionRunner {
896
1020
  }
897
1021
  /** Emit input event. Transforms chain, "handled" short-circuits. */
898
1022
  async emitInput(text, images, source) {
1023
+ if (this.skipStale("input"))
1024
+ return { action: "continue" };
899
1025
  const ctx = this.createContext();
900
1026
  let currentText = text;
901
1027
  let currentImages = images;
1028
+ const sessionEntries = [];
1029
+ const acceptanceCallbacks = [];
1030
+ const transformResult = () => {
1031
+ const result = {
1032
+ action: "transform",
1033
+ text: currentText,
1034
+ images: currentImages,
1035
+ sessionEntries,
1036
+ };
1037
+ if (acceptanceCallbacks.length > 0) {
1038
+ result.onAccepted = () => {
1039
+ for (const callback of acceptanceCallbacks)
1040
+ callback();
1041
+ };
1042
+ }
1043
+ return result;
1044
+ };
902
1045
  for (const ext of this.extensions) {
903
1046
  for (const handler of ext.handlers.get("input") ?? []) {
1047
+ if (this.skipStale("input")) {
1048
+ return currentText !== text ||
1049
+ currentImages !== images ||
1050
+ sessionEntries.length > 0 ||
1051
+ acceptanceCallbacks.length > 0
1052
+ ? transformResult()
1053
+ : { action: "continue" };
1054
+ }
904
1055
  try {
905
1056
  const event = { type: "input", text: currentText, images: currentImages, source };
906
1057
  const result = (await handler(event, ctx));
@@ -909,6 +1060,9 @@ export class ExtensionRunner {
909
1060
  if (result?.action === "transform") {
910
1061
  currentText = result.text;
911
1062
  currentImages = result.images ?? currentImages;
1063
+ sessionEntries.push(...(result.sessionEntries ?? []));
1064
+ if (result.onAccepted)
1065
+ acceptanceCallbacks.push(result.onAccepted);
912
1066
  }
913
1067
  }
914
1068
  catch (err) {
@@ -921,8 +1075,11 @@ export class ExtensionRunner {
921
1075
  }
922
1076
  }
923
1077
  }
924
- return currentText !== text || currentImages !== images
925
- ? { action: "transform", text: currentText, images: currentImages }
1078
+ return currentText !== text ||
1079
+ currentImages !== images ||
1080
+ sessionEntries.length > 0 ||
1081
+ acceptanceCallbacks.length > 0
1082
+ ? transformResult()
926
1083
  : { action: "continue" };
927
1084
  }
928
1085
  }