@ai-agent-forge/agent-forge 0.86.4 → 0.86.5

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 (220) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/bundle/.build-manifest.json +470 -404
  3. package/dist/bundle/chunks/anthropic-messages-ALDZBUKI.js +6 -0
  4. package/dist/bundle/chunks/anthropic.js +2 -2
  5. package/dist/bundle/chunks/{azure-openai-responses-WQCZWFAH.js → azure-openai-responses-A2WJWZFB.js} +1 -1
  6. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  7. package/dist/bundle/chunks/chunk-A7GUOX2H.js +141 -0
  8. package/dist/bundle/chunks/{chunk-WGLBM2BP.js → chunk-CIX3FSBK.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-ZV3W36HK.js → chunk-EALJ5DDX.js} +99 -92
  10. package/dist/bundle/chunks/chunk-OO6DJ3CL.js +31 -0
  11. package/dist/bundle/chunks/{chunk-PE3XUHPT.js → chunk-SQFG22WN.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-V46XUTK3.js → chunk-Y7HXTASS.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-3WE3W52L.js → chunk-YJCQVXW5.js} +1 -1
  14. package/dist/bundle/chunks/github-copilot.js +1 -1
  15. package/dist/bundle/chunks/{google-generative-ai-LQ375GYC.js → google-generative-ai-NIB4WONV.js} +1 -1
  16. package/dist/bundle/chunks/{google-vertex-HXSRAKEZ.js → google-vertex-DCPYUEUO.js} +1 -1
  17. package/dist/bundle/chunks/{mistral-conversations-CSNMFR4J.js → mistral-conversations-OC6LA5XL.js} +1 -1
  18. package/dist/bundle/chunks/{openai-codex-responses-YDJSZOIH.js → openai-codex-responses-IO2X4Y7L.js} +1 -1
  19. package/dist/bundle/chunks/openai-codex.js +2 -2
  20. package/dist/bundle/chunks/{openai-completions-UPRWTQPT.js → openai-completions-XYQQHETU.js} +1 -1
  21. package/dist/bundle/chunks/{openai-responses-O63W6HQ3.js → openai-responses-DLIRN3CI.js} +1 -1
  22. package/dist/bundle/chunks/{openrouter-images-G3GT5BVT.js → openrouter-images-G4GQRUSG.js} +1 -1
  23. package/dist/bundle/chunks/openrouter.js +2 -2
  24. package/dist/bundle/chunks/{pi-messages-SLF355GF.js → pi-messages-E6RRYBO7.js} +1 -1
  25. package/dist/bundle/chunks/radius.js +2 -2
  26. package/dist/bundle/chunks/{virtual-modules-O72ICTMQ.js → virtual-modules-3IZ4MYSQ.js} +1 -1
  27. package/dist/bundle/cli.js +1 -1
  28. package/dist/bundle/index.js +1 -1
  29. package/dist/bundle/rpc-entry.js +1 -1
  30. package/dist/capabilities/builtin.d.ts +4 -2
  31. package/dist/capabilities/builtin.d.ts.map +1 -1
  32. package/dist/capabilities/builtin.js +5 -1
  33. package/dist/capabilities/builtin.js.map +1 -1
  34. package/dist/cli/plugin-command.d.ts.map +1 -1
  35. package/dist/cli/plugin-command.js +51 -5
  36. package/dist/cli/plugin-command.js.map +1 -1
  37. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  38. package/dist/core/agent-session-compaction.js +13 -5
  39. package/dist/core/agent-session-compaction.js.map +1 -1
  40. package/dist/core/agent-session-internal.d.ts +2 -4
  41. package/dist/core/agent-session-internal.d.ts.map +1 -1
  42. package/dist/core/agent-session-internal.js.map +1 -1
  43. package/dist/core/agent-session-paths.d.ts +2 -0
  44. package/dist/core/agent-session-paths.d.ts.map +1 -1
  45. package/dist/core/agent-session-paths.js +2 -1
  46. package/dist/core/agent-session-paths.js.map +1 -1
  47. package/dist/core/agent-session-projection.d.ts +10 -4
  48. package/dist/core/agent-session-projection.d.ts.map +1 -1
  49. package/dist/core/agent-session-projection.js +17 -4
  50. package/dist/core/agent-session-projection.js.map +1 -1
  51. package/dist/core/agent-session.d.ts +19 -5
  52. package/dist/core/agent-session.d.ts.map +1 -1
  53. package/dist/core/agent-session.js +30 -4
  54. package/dist/core/agent-session.js.map +1 -1
  55. package/dist/core/extensions/bootstrap-lifecycle.d.ts +1 -1
  56. package/dist/core/extensions/bootstrap-lifecycle.d.ts.map +1 -1
  57. package/dist/core/extensions/bootstrap-lifecycle.js +5 -2
  58. package/dist/core/extensions/bootstrap-lifecycle.js.map +1 -1
  59. package/dist/core/extensions/capability-runtime-store.d.ts.map +1 -1
  60. package/dist/core/extensions/capability-runtime-store.js +1 -0
  61. package/dist/core/extensions/capability-runtime-store.js.map +1 -1
  62. package/dist/core/extensions/capability-runtime-types.d.ts +16 -1
  63. package/dist/core/extensions/capability-runtime-types.d.ts.map +1 -1
  64. package/dist/core/extensions/capability-runtime-types.js.map +1 -1
  65. package/dist/core/extensions/capability-runtime.d.ts.map +1 -1
  66. package/dist/core/extensions/capability-runtime.js +38 -2
  67. package/dist/core/extensions/capability-runtime.js.map +1 -1
  68. package/dist/core/extensions/index.d.ts +1 -1
  69. package/dist/core/extensions/index.d.ts.map +1 -1
  70. package/dist/core/extensions/index.js.map +1 -1
  71. package/dist/core/extensions/lifecycle-catalog.d.ts +46 -0
  72. package/dist/core/extensions/lifecycle-catalog.d.ts.map +1 -1
  73. package/dist/core/extensions/lifecycle-catalog.js +101 -0
  74. package/dist/core/extensions/lifecycle-catalog.js.map +1 -1
  75. package/dist/core/extensions/provider-failover.d.ts +106 -0
  76. package/dist/core/extensions/provider-failover.d.ts.map +1 -0
  77. package/dist/core/extensions/provider-failover.js +692 -0
  78. package/dist/core/extensions/provider-failover.js.map +1 -0
  79. package/dist/core/facade/coding-agent-facade.d.ts +13 -6
  80. package/dist/core/facade/coding-agent-facade.d.ts.map +1 -1
  81. package/dist/core/facade/coding-agent-facade.js +3 -1
  82. package/dist/core/facade/coding-agent-facade.js.map +1 -1
  83. package/dist/core/keybindings.d.ts +6 -6
  84. package/dist/core/keybindings.d.ts.map +1 -1
  85. package/dist/core/keybindings.js +23 -6
  86. package/dist/core/keybindings.js.map +1 -1
  87. package/dist/core/model-config.d.ts +102 -0
  88. package/dist/core/model-config.d.ts.map +1 -1
  89. package/dist/core/model-config.js +46 -14
  90. package/dist/core/model-config.js.map +1 -1
  91. package/dist/core/model-resolver.d.ts.map +1 -1
  92. package/dist/core/model-resolver.js +1 -1
  93. package/dist/core/model-resolver.js.map +1 -1
  94. package/dist/core/model-runtime.d.ts +15 -0
  95. package/dist/core/model-runtime.d.ts.map +1 -1
  96. package/dist/core/model-runtime.js +30 -18
  97. package/dist/core/model-runtime.js.map +1 -1
  98. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  99. package/dist/core/remote-catalog-provider.js +4 -9
  100. package/dist/core/remote-catalog-provider.js.map +1 -1
  101. package/dist/core/sdk.d.ts.map +1 -1
  102. package/dist/core/sdk.js +107 -24
  103. package/dist/core/sdk.js.map +1 -1
  104. package/dist/core/session-manager.d.ts +2 -0
  105. package/dist/core/session-manager.d.ts.map +1 -1
  106. package/dist/core/session-manager.js +10 -5
  107. package/dist/core/session-manager.js.map +1 -1
  108. package/dist/core/settings-manager.d.ts +71 -5
  109. package/dist/core/settings-manager.d.ts.map +1 -1
  110. package/dist/core/settings-manager.js +138 -9
  111. package/dist/core/settings-manager.js.map +1 -1
  112. package/dist/core/slash-commands.d.ts.map +1 -1
  113. package/dist/core/slash-commands.js +17 -2
  114. package/dist/core/slash-commands.js.map +1 -1
  115. package/dist/core/tools/render-utils.d.ts +6 -0
  116. package/dist/core/tools/render-utils.d.ts.map +1 -1
  117. package/dist/core/tools/render-utils.js +26 -0
  118. package/dist/core/tools/render-utils.js.map +1 -1
  119. package/dist/core/tools/tool-definition.d.ts +21 -2
  120. package/dist/core/tools/tool-definition.d.ts.map +1 -1
  121. package/dist/core/tools/tool-definition.js.map +1 -1
  122. package/dist/main.d.ts.map +1 -1
  123. package/dist/main.js +3 -6
  124. package/dist/main.js.map +1 -1
  125. package/dist/modes/interactive/components/agents-view.d.ts +57 -0
  126. package/dist/modes/interactive/components/agents-view.d.ts.map +1 -0
  127. package/dist/modes/interactive/components/agents-view.js +260 -0
  128. package/dist/modes/interactive/components/agents-view.js.map +1 -0
  129. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/custom-editor.js +2 -2
  131. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  132. package/dist/modes/interactive/components/footer.d.ts +7 -0
  133. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  134. package/dist/modes/interactive/components/footer.js +51 -6
  135. package/dist/modes/interactive/components/footer.js.map +1 -1
  136. package/dist/modes/interactive/components/scoped-models-selector.d.ts +1 -1
  137. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  138. package/dist/modes/interactive/components/scoped-models-selector.js +1 -1
  139. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  140. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  141. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  142. package/dist/modes/interactive/components/settings-selector.js +12 -2
  143. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  144. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  145. package/dist/modes/interactive/components/thinking-selector.js +1 -1
  146. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  147. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  148. package/dist/modes/interactive/components/tool-execution.js +89 -10
  149. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  150. package/dist/modes/interactive/components/user-message-selector.d.ts +3 -0
  151. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  152. package/dist/modes/interactive/components/user-message-selector.js +9 -3
  153. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  154. package/dist/modes/interactive/components/workflow-view.d.ts +119 -0
  155. package/dist/modes/interactive/components/workflow-view.d.ts.map +1 -0
  156. package/dist/modes/interactive/components/workflow-view.js +638 -0
  157. package/dist/modes/interactive/components/workflow-view.js.map +1 -0
  158. package/dist/modes/interactive/interactive-commands.d.ts +22 -0
  159. package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
  160. package/dist/modes/interactive/interactive-commands.js +56 -0
  161. package/dist/modes/interactive/interactive-commands.js.map +1 -1
  162. package/dist/modes/interactive/interactive-mode.d.ts +51 -1
  163. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  164. package/dist/modes/interactive/interactive-mode.js +218 -37
  165. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  166. package/dist/modes/interactive/interactive-selectors.d.ts +2 -4
  167. package/dist/modes/interactive/interactive-selectors.d.ts.map +1 -1
  168. package/dist/modes/interactive/interactive-selectors.js +10 -1
  169. package/dist/modes/interactive/interactive-selectors.js.map +1 -1
  170. package/dist/modes/print-mode.d.ts.map +1 -1
  171. package/dist/modes/print-mode.js +5 -0
  172. package/dist/modes/print-mode.js.map +1 -1
  173. package/dist/modes/rpc/rpc-client.d.ts +3 -1
  174. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  175. package/dist/modes/rpc/rpc-client.js +4 -2
  176. package/dist/modes/rpc/rpc-client.js.map +1 -1
  177. package/dist/modes/rpc/rpc-types.d.ts +1 -0
  178. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  179. package/dist/modes/rpc/rpc-types.js.map +1 -1
  180. package/dist/plugins/ecosystem/bootstrap-factory-manifest.d.ts.map +1 -1
  181. package/dist/plugins/ecosystem/bootstrap-factory-manifest.js +7 -0
  182. package/dist/plugins/ecosystem/bootstrap-factory-manifest.js.map +1 -1
  183. package/dist/plugins/ecosystem/cli.d.ts.map +1 -1
  184. package/dist/plugins/ecosystem/cli.js +1 -1
  185. package/dist/plugins/ecosystem/cli.js.map +1 -1
  186. package/dist/plugins/ecosystem/install-store-paths.d.ts +15 -0
  187. package/dist/plugins/ecosystem/install-store-paths.d.ts.map +1 -0
  188. package/dist/plugins/ecosystem/install-store-paths.js +58 -0
  189. package/dist/plugins/ecosystem/install-store-paths.js.map +1 -0
  190. package/dist/plugins/ecosystem/node-disk-registry-source.d.ts.map +1 -1
  191. package/dist/plugins/ecosystem/node-disk-registry-source.js +3 -8
  192. package/dist/plugins/ecosystem/node-disk-registry-source.js.map +1 -1
  193. package/dist/plugins/ecosystem/node-package-install-host.d.ts.map +1 -1
  194. package/dist/plugins/ecosystem/node-package-install-host.js +9 -1
  195. package/dist/plugins/ecosystem/node-package-install-host.js.map +1 -1
  196. package/dist/plugins/ecosystem/node-package-install-persistence.d.ts.map +1 -1
  197. package/dist/plugins/ecosystem/node-package-install-persistence.js +3 -8
  198. package/dist/plugins/ecosystem/node-package-install-persistence.js.map +1 -1
  199. package/dist/profile/plugin-profiles.d.ts +2 -0
  200. package/dist/profile/plugin-profiles.d.ts.map +1 -1
  201. package/dist/profile/plugin-profiles.js +4 -0
  202. package/dist/profile/plugin-profiles.js.map +1 -1
  203. package/dist/profiles/builtin-suites.d.ts +19 -2
  204. package/dist/profiles/builtin-suites.d.ts.map +1 -1
  205. package/dist/profiles/builtin-suites.js +20 -2
  206. package/dist/profiles/builtin-suites.js.map +1 -1
  207. package/dist/profiles/suite-loader.d.ts.map +1 -1
  208. package/dist/profiles/suite-loader.js +4 -1
  209. package/dist/profiles/suite-loader.js.map +1 -1
  210. package/dist/utils/clipboard.d.ts.map +1 -1
  211. package/dist/utils/clipboard.js +10 -10
  212. package/dist/utils/clipboard.js.map +1 -1
  213. package/docs/keybindings.md +8 -8
  214. package/docs/models.md +1 -1
  215. package/docs/rpc.md +3 -1
  216. package/docs/settings.md +2 -1
  217. package/package.json +4 -4
  218. package/dist/bundle/chunks/anthropic-messages-EVFQ6MLR.js +0 -6
  219. package/dist/bundle/chunks/chunk-F6SBZUJG.js +0 -141
  220. package/dist/bundle/chunks/chunk-NUHFSC37.js +0 -25
@@ -14,6 +14,7 @@ import { spawn } from "child_process";
14
14
  import { APP_NAME, APP_TITLE, getAgentDir, getDebugLogPath, VERSION } from "../../config.js";
15
15
  import { parseSkillBlock } from "../../core/agent-session.js";
16
16
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
17
+ import { listAgentSessions, readAgentSession } from "../../core/agent-session-store.js";
17
18
  import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
18
19
  import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "../../core/defaults.js";
19
20
  import { createCodingAgentFacade, createCodingAgentRuntimeFacade, } from "../../core/facade/index.js";
@@ -36,6 +37,7 @@ import { killTrackedDetachedChildren } from "../../utils/shell.js";
36
37
  import { loadAllHighlightLanguages } from "../../utils/syntax-highlight.js";
37
38
  import { ensureTool } from "../../utils/tools-manager.js";
38
39
  import { checkForNewVersion } from "../../utils/version-check.js";
40
+ import { AgentsViewComponent } from "./components/agents-view.js";
39
41
  import { ArminComponent } from "./components/armin.js";
40
42
  import { AssistantMessageComponent } from "./components/assistant-message.js";
41
43
  import { BashExecutionComponent } from "./components/bash-execution.js";
@@ -55,9 +57,10 @@ import { BranchSummaryStatusIndicator, CompactionStatusIndicator, IdleStatus, Re
55
57
  import { ToolExecutionComponent } from "./components/tool-execution.js";
56
58
  import { TreeSelectorComponent } from "./components/tree-selector.js";
57
59
  import { UserMessageComponent } from "./components/user-message.js";
60
+ import { WorkflowViewComponent } from "./components/workflow-view.js";
58
61
  import { editInExternalEditor } from "./external-editor.js";
59
62
  import * as authFlow from "./interactive-auth.js";
60
- import { COMMAND_DISPATCH_ENTRIES, matchCommandDispatchEntry } from "./interactive-commands.js";
63
+ import { COMMAND_DISPATCH_ENTRIES, filterCapabilityCommandsForCompletions, matchCommandDispatchEntry, } from "./interactive-commands.js";
61
64
  import { createInteractionHostV1, hideDialogEditor, hideDialogInput, hideDialogSelector, isExpandable, showDialogConfirm, showDialogEditor, showDialogInput, showDialogNotify, showDialogSelector, } from "./interactive-dialog-ui.js";
62
65
  import { buildScopeGroups, findSourceInfoForPath, formatContextPath, formatDiagnostics, formatDisplayPath, formatMetricsSnapshot, formatPathWithSource, formatScopeGroups, getCompactPackageSourceLabel, getCompactPathLabel, getDisplaySourceInfo, getScopeGroup, getShortPath, isPackageSource, } from "./interactive-format.js";
63
66
  import { createFuzzyAutocompleteItems, formatLoginProviderCompletionDescription, getLoginProviderCompletionOptions, getLoginProviderSearchText, isAnthropicSubscriptionAuthKey, isDeadTerminalError, quoteIfNeeded, } from "./interactive-helpers.js";
@@ -136,6 +139,7 @@ export function createInteractiveTui(options) {
136
139
  openUrl: openBrowser,
137
140
  onRightClickPaste: options.onRightClickPaste,
138
141
  copyOnSelect: options.fullscreenCopyOnSelect,
142
+ wheelScrollLines: options.fullscreenWheelScrollLines ?? 1,
139
143
  copySelection: async (text) => {
140
144
  try {
141
145
  await copyToClipboard(text);
@@ -180,6 +184,41 @@ export function createInteractiveTuiReference(getTui) {
180
184
  getPrototypeOf: () => Reflect.getPrototypeOf(getTui()),
181
185
  });
182
186
  }
187
+ /**
188
+ * Provider failover footer ticker (设计 供应商能力槽位与主备切换 §4.7 Phase 3):
189
+ * while the latest `provider.failover.state` snapshot reports an open breaker, the
190
+ * footer countdown must advance once per second, so re-render on a 1s interval
191
+ * (countdown-timer.ts 同构). The interval is created on the first refresh that
192
+ * sees an open state and cleared as soon as the state leaves open — no leak.
193
+ */
194
+ export class ProviderFailoverTicker {
195
+ intervalId;
196
+ isOpen;
197
+ requestRender;
198
+ constructor(isOpen, requestRender) {
199
+ this.isOpen = isOpen;
200
+ this.requestRender = requestRender;
201
+ }
202
+ /** Re-evaluate the open state: start ticking when open, stop when not. */
203
+ refresh() {
204
+ if (this.isOpen()) {
205
+ if (this.intervalId === undefined) {
206
+ this.intervalId = setInterval(() => {
207
+ this.requestRender();
208
+ this.refresh();
209
+ }, 1000);
210
+ }
211
+ return;
212
+ }
213
+ this.dispose();
214
+ }
215
+ dispose() {
216
+ if (this.intervalId !== undefined) {
217
+ clearInterval(this.intervalId);
218
+ this.intervalId = undefined;
219
+ }
220
+ }
221
+ }
183
222
  export class InteractiveMode {
184
223
  runtimeHost;
185
224
  renderer;
@@ -209,6 +248,12 @@ export class InteractiveMode {
209
248
  activeSelectorDispose;
210
249
  /** Host-visible for ./interactive-dialog-ui.ts (S5 slice 2). */
211
250
  footer;
251
+ /**
252
+ * Re-renders the footer once per second while the provider failover breaker is
253
+ * open (§4.7 Phase 3). Optional-typed to match the prototype-based test stubs
254
+ * (constructor always assigns it on real instances).
255
+ */
256
+ failoverTicker;
212
257
  /** Host-visible for ./interactive-dialog-ui.ts (S5 slice 2). */
213
258
  footerContainer;
214
259
  /** Host-visible for ./interactive-dialog-ui.ts (S5 slice 2). */
@@ -338,6 +383,7 @@ export class InteractiveMode {
338
383
  logDirectory: getAgentDir(),
339
384
  onRightClickPaste: this.onRightClickPaste,
340
385
  fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
386
+ fullscreenWheelScrollLines: this.settingsManager.getFullscreenWheelScrollLines(),
341
387
  });
342
388
  this.ui = createInteractiveTuiReference(() => this.renderer);
343
389
  this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
@@ -370,6 +416,7 @@ export class InteractiveMode {
370
416
  this.footer.setAutoCompactEnabled(this.facade.isAutoCompactionEnabled());
371
417
  this.footerContainer = new Container();
372
418
  this.footerContainer.addChild(this.footer);
419
+ this.failoverTicker = new ProviderFailoverTicker(() => this.footer.isProviderFailoverOpen(), () => this.ui.requestRender());
373
420
  // Load hide thinking block setting
374
421
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
375
422
  this.outputPad = this.settingsManager.getOutputPad();
@@ -483,16 +530,13 @@ export class InteractiveMode {
483
530
  ...(cmd.argumentHint && { argumentHint: cmd.argumentHint }),
484
531
  }));
485
532
  const builtinCommandNames = new Set(slashCommands.map((c) => c.name));
533
+ const templateCommandNames = new Set(this.facade.getPromptTemplates().map((cmd) => cmd.name));
486
534
  const resourceSkillCommandNames = new Set(this.facade.getSkills().skills.map((skill) => `skill:${skill.name}`));
487
- const capabilityCommands = capabilityRegistrations
488
- .filter(({ definition }) => !builtinCommandNames.has(definition.name) &&
489
- !this.facade.getPromptTemplates().some((template) => template.name === definition.name) &&
490
- !resourceSkillCommandNames.has(definition.name))
491
- .map(({ definition }) => ({
535
+ const capabilityCommands = filterCapabilityCommandsForCompletions(capabilityRegistrations.map(({ definition }) => ({
492
536
  name: definition.name,
493
537
  description: definition.description,
494
538
  ...(definition.argumentHint && { argumentHint: definition.argumentHint }),
495
- }));
539
+ })), [builtinCommandNames, templateCommandNames, [...resourceSkillCommandNames]]);
496
540
  // Build skill commands from session.skills (if enabled)
497
541
  this.skillCommands.clear();
498
542
  const skillCommandList = [];
@@ -589,6 +633,7 @@ export class InteractiveMode {
589
633
  terminal,
590
634
  onRightClickPaste: this.onRightClickPaste,
591
635
  fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
636
+ fullscreenWheelScrollLines: this.settingsManager.getFullscreenWheelScrollLines(),
592
637
  });
593
638
  nextUi.setClearOnShrink(clearOnShrink);
594
639
  nextUi.onDebug = onDebug;
@@ -683,17 +728,12 @@ export class InteractiveMode {
683
728
  hint("app.exit", "to exit (empty)"),
684
729
  hint("app.suspend", "to suspend"),
685
730
  keyHint("tui.editor.deleteToLineEnd", "to delete to end"),
686
- hint("app.thinking.cycle", "to cycle thinking level"),
687
- rawKeyHint(`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`, "to cycle models"),
688
- hint("app.model.select", "to select model"),
689
731
  hint("app.tools.expand", "to expand tools"),
690
732
  hint("app.thinking.toggle", "to expand thinking"),
691
733
  hint("app.editor.external", "for external editor"),
692
734
  rawKeyHint("/", "for commands"),
693
735
  rawKeyHint("!", "to run bash"),
694
736
  rawKeyHint("!!", "to run bash (no context)"),
695
- hint("app.message.followUp", "to queue follow-up"),
696
- hint("app.message.dequeue", "to edit all queued messages"),
697
737
  hint("app.clipboard.pasteImage", "to paste image (with text fallback)"),
698
738
  rawKeyHint("drop files", "to attach"),
699
739
  ].join("\n");
@@ -1187,10 +1227,14 @@ export class InteractiveMode {
1187
1227
  this.applyFullscreenScrollbarSetting();
1188
1228
  if (this.renderer instanceof TuiAltScreen) {
1189
1229
  this.renderer.setCopyOnSelect(this.settingsManager.getFullscreenCopyOnSelect());
1230
+ this.renderer.setWheelScrollLines(this.settingsManager.getFullscreenWheelScrollLines());
1190
1231
  }
1191
1232
  this.footer.setSession(this.session);
1192
1233
  this.footer.setAutoCompactEnabled(this.facade.isAutoCompactionEnabled());
1193
1234
  this.footerDataProvider.setCwd(this.facade.getCwd());
1235
+ // The switched session carries its own failover snapshot history; re-check
1236
+ // whether the countdown ticker is needed.
1237
+ this.failoverTicker?.refresh();
1194
1238
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
1195
1239
  this.outputPad = this.settingsManager.getOutputPad();
1196
1240
  this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
@@ -1430,7 +1474,14 @@ export class InteractiveMode {
1430
1474
  // so they work correctly regardless of which editor is active
1431
1475
  this.defaultEditor.onEscape = () => {
1432
1476
  if (this.facade.isStreaming()) {
1433
- this.restoreQueuedMessagesToEditor({ abort: true });
1477
+ // 中断分级(设计 子代理运行时 §4.2,2026-10-02):Esc 只停当前
1478
+ // 回合,从不终止后台子代理(用户裁决:入口分离,同键不承载两
1479
+ // 个烈度)。仍有活跃子代理时状态行指路 /stop-agents。
1480
+ this.restoreQueuedMessagesToEditor({ abort: true, cancelAgentTasks: false });
1481
+ const activeAgents = this.facade.getActiveAgentTaskCount();
1482
+ if (activeAgents > 0) {
1483
+ this.showStatus(`${activeAgents} 个子代理仍在运行 · /stop-agents 终止全部(Esc 只停当前回合)`);
1484
+ }
1434
1485
  }
1435
1486
  else if (this.facade.isBashRunning()) {
1436
1487
  this.facade.abortBash();
@@ -1472,8 +1523,11 @@ export class InteractiveMode {
1472
1523
  this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
1473
1524
  this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
1474
1525
  this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
1475
- this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
1476
- this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand({ flashConfirmation: true, preferSelection: true }));
1526
+ // Iron law (2026-10-02 复审): the main editor must not consume
1527
+ // ctrl+g/ctrl+x — /editor and /copy are the single entries here. The
1528
+ // bindings keep their defaults for the components that have no command
1529
+ // channel (dialog editors' external-editor entry, the tree view's
1530
+ // in-place copy).
1477
1531
  this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
1478
1532
  this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
1479
1533
  this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
@@ -1629,13 +1683,22 @@ export class InteractiveMode {
1629
1683
  if (entry.clearEditorBeforeRun)
1630
1684
  this.setEditorText("");
1631
1685
  const result = entry.run(this, text, arg);
1686
+ // Clear the submitted command text after the run — but only while the
1687
+ // editor still holds it. Handlers that write their own content into
1688
+ // the editor (/dequeue restores the queue, /editor applies external
1689
+ // edits) must not have that wiped (review finding: the unconditional
1690
+ // clear lost the restored/edited content).
1691
+ const clearIfUntouched = () => {
1692
+ if (this.editor.getText().trim() === text)
1693
+ this.setEditorText("");
1694
+ };
1632
1695
  if (!(result instanceof Promise)) {
1633
1696
  if (!entry.clearEditorBeforeRun)
1634
- this.setEditorText("");
1697
+ clearIfUntouched();
1635
1698
  return { pending: undefined };
1636
1699
  }
1637
1700
  return {
1638
- pending: entry.clearEditorBeforeRun ? result : result.then(() => this.setEditorText("")),
1701
+ pending: entry.clearEditorBeforeRun ? result : result.then(clearIfUntouched),
1639
1702
  };
1640
1703
  }
1641
1704
  setEditorText(text) {
@@ -1651,6 +1714,9 @@ export class InteractiveMode {
1651
1714
  await this.init();
1652
1715
  }
1653
1716
  this.footer.invalidate();
1717
+ // Failover snapshots land as entry_appended events; the ticker decides here
1718
+ // whether the per-second countdown re-render is needed (§4.7 Phase 3).
1719
+ this.failoverTicker?.refresh();
1654
1720
  switch (event.type) {
1655
1721
  case "agent_start":
1656
1722
  this.pendingTools.clear();
@@ -2463,8 +2529,8 @@ export class InteractiveMode {
2463
2529
  throw error;
2464
2530
  }
2465
2531
  }
2466
- async handleFollowUp() {
2467
- const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
2532
+ async handleFollowUp(explicitText) {
2533
+ const text = (explicitText ?? this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
2468
2534
  if (!text)
2469
2535
  return;
2470
2536
  // Queue input during compaction (extension commands execute immediately)
@@ -2475,15 +2541,19 @@ export class InteractiveMode {
2475
2541
  // Alt+Enter queues a follow-up message (waits until agent finishes)
2476
2542
  // This handles extension commands (execute immediately), prompt template expansion, and queueing
2477
2543
  if (this.facade.isStreaming()) {
2478
- this.editor.addToHistory?.(text);
2479
- this.editor.setText("");
2544
+ // An explicit /queue <text> argument must not touch the editor draft.
2545
+ if (explicitText === undefined) {
2546
+ this.editor.addToHistory?.(text);
2547
+ this.editor.setText("");
2548
+ }
2480
2549
  await this.facade.prompt(text, { streamingBehavior: "followUp" });
2481
2550
  this.updatePendingMessagesDisplay();
2482
2551
  this.ui.requestRender();
2483
2552
  }
2484
2553
  // If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
2485
2554
  else if (this.editor.onSubmit) {
2486
- this.editor.setText("");
2555
+ if (explicitText === undefined)
2556
+ this.editor.setText("");
2487
2557
  this.editor.onSubmit(text);
2488
2558
  }
2489
2559
  }
@@ -2496,6 +2566,22 @@ export class InteractiveMode {
2496
2566
  this.showStatus(`Restored ${restored} queued message${restored > 1 ? "s" : ""} to editor`);
2497
2567
  }
2498
2568
  }
2569
+ /** /queue command host face (single-entry iron law): queues the given text for after the current run. A bare /queue cannot mirror the keybinding — the editor buffer is cleared on submit before the command runs — so it prints usage instead of silently doing nothing. */
2570
+ handleQueueCommand(text) {
2571
+ if (text === undefined || text.trim() === "") {
2572
+ this.showStatus("Usage: /queue <text>");
2573
+ return Promise.resolve();
2574
+ }
2575
+ return this.handleFollowUp(text);
2576
+ }
2577
+ /** /dequeue command host face: restore queued messages back to the editor. */
2578
+ handleDequeueCommand() {
2579
+ this.handleDequeue();
2580
+ }
2581
+ /** /editor command host face: edit the current input in an external editor. */
2582
+ handleEditorCommand() {
2583
+ return this.handleOpenExternalEditor();
2584
+ }
2499
2585
  /** Host-visible for ./interactive-auth.ts (S5 slice 3). */
2500
2586
  updateEditorBorderColor() {
2501
2587
  if (this.isBashMode) {
@@ -2689,7 +2775,9 @@ export class InteractiveMode {
2689
2775
  const text = theme.fg("dim", `Follow-up: ${message}`);
2690
2776
  this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
2691
2777
  }
2692
- const dequeueHint = this.getAppKeyDisplay("app.message.dequeue");
2778
+ // The dequeue keybinding's shipped default was cleared (iron law):
2779
+ // /queue's sibling command is the discoverable entry.
2780
+ const dequeueHint = this.getAppKeyDisplay("app.message.dequeue") || "/dequeue";
2693
2781
  const hintText = theme.fg("dim", `↳ ${dequeueHint} to edit all queued messages`);
2694
2782
  this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
2695
2783
  }
@@ -2697,10 +2785,13 @@ export class InteractiveMode {
2697
2785
  restoreQueuedMessagesToEditor(options) {
2698
2786
  const { steering, followUp } = this.clearAllQueues();
2699
2787
  const allQueued = [...steering, ...followUp];
2788
+ const abortOptions = options?.abort === true
2789
+ ? { ...(options.cancelAgentTasks === undefined ? {} : { cancelAgentTasks: options.cancelAgentTasks }) }
2790
+ : undefined;
2700
2791
  if (allQueued.length === 0) {
2701
2792
  this.updatePendingMessagesDisplay();
2702
- if (options?.abort) {
2703
- void this.facade.abort();
2793
+ if (abortOptions !== undefined) {
2794
+ void this.facade.abort(abortOptions);
2704
2795
  }
2705
2796
  return 0;
2706
2797
  }
@@ -2709,8 +2800,8 @@ export class InteractiveMode {
2709
2800
  const combinedText = [queuedText, currentText].filter((t) => t.trim()).join("\n\n");
2710
2801
  this.editor.setText(combinedText);
2711
2802
  this.updatePendingMessagesDisplay();
2712
- if (options?.abort) {
2713
- void this.facade.abort();
2803
+ if (abortOptions !== undefined) {
2804
+ void this.facade.abort(abortOptions);
2714
2805
  }
2715
2806
  return allQueued.length;
2716
2807
  }
@@ -2867,6 +2958,7 @@ export class InteractiveMode {
2867
2958
  fullscreenExitOutput: this.settingsManager.getFullscreenExitOutput(),
2868
2959
  fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
2869
2960
  fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
2961
+ fullscreenWheelScrollLines: this.settingsManager.getFullscreenWheelScrollLines(),
2870
2962
  warnings: this.settingsManager.getWarnings(),
2871
2963
  }, {
2872
2964
  onAutoCompactChange: (enabled) => {
@@ -3036,6 +3128,11 @@ export class InteractiveMode {
3036
3128
  if (this.renderer instanceof TuiAltScreen)
3037
3129
  this.renderer.setCopyOnSelect(enabled);
3038
3130
  },
3131
+ onFullscreenWheelScrollLinesChange: (lines) => {
3132
+ this.settingsManager.setFullscreenWheelScrollLines(lines);
3133
+ if (this.renderer instanceof TuiAltScreen)
3134
+ this.renderer.setWheelScrollLines(lines);
3135
+ },
3039
3136
  onWarningsChange: (warnings) => {
3040
3137
  this.settingsManager.setWarnings(warnings);
3041
3138
  },
@@ -3206,6 +3303,61 @@ export class InteractiveMode {
3206
3303
  async handleCloneCommand() {
3207
3304
  await selectorsFlow.handleCloneCommand(this);
3208
3305
  }
3306
+ /**
3307
+ * Workflow overlay (§9 L2/L3): read-only panorama + transition log from
3308
+ * `workflow.state`/`workflow.transition` entries. Actions are forwarded to
3309
+ * the editor as reviewable text; the main agent executes them via
3310
+ * workflow_advance (rendering and execution stay separated).
3311
+ */
3312
+ showWorkflowView() {
3313
+ const hasSnapshot = this.facade
3314
+ .getEntries()
3315
+ .some((entry) => entry.type === "custom" && entry.customType === "workflow.state");
3316
+ if (!hasSnapshot) {
3317
+ this.showStatus("No workflow in this session");
3318
+ return;
3319
+ }
3320
+ this.showSelector((done) => {
3321
+ const view = new WorkflowViewComponent({
3322
+ getEntries: () => this.facade.getEntries(),
3323
+ maxRows: Math.max(6, Math.floor(this.ui.terminal.rows / 2)),
3324
+ requestRender: () => this.ui.requestRender(),
3325
+ });
3326
+ view.onClose = done;
3327
+ view.onForwardAction = (text) => {
3328
+ done();
3329
+ this.editor.setText(text);
3330
+ this.ui.requestRender();
3331
+ this.showStatus("Workflow action staged — review in the editor and submit");
3332
+ };
3333
+ return { component: view, focus: view, dispose: () => view.dispose() };
3334
+ });
3335
+ }
3336
+ /**
3337
+ * Subagent session browser (/agents): read-only list + detail of this
3338
+ * session's subagent transcripts from the D-060 §3 on-disk store — the same
3339
+ * parent-scoped summary source as the subagent_list model tool (only this
3340
+ * session's children, newest first). Detail entries load lazily per
3341
+ * session; a failed load renders inside the view instead of throwing.
3342
+ */
3343
+ showAgentsView() {
3344
+ const agentDir = getAgentDir();
3345
+ const sessions = listAgentSessions({ agentDir, parentAgentSession: this.facade.getSessionId() });
3346
+ if (sessions.length === 0) {
3347
+ this.showStatus("No subagent sessions in this session");
3348
+ return;
3349
+ }
3350
+ this.showSelector((done) => {
3351
+ const view = new AgentsViewComponent({
3352
+ sessions,
3353
+ loadEntries: (sessionId) => readAgentSession({ agentDir, sessionId }),
3354
+ maxRows: Math.max(6, Math.floor(this.ui.terminal.rows / 2)),
3355
+ requestRender: () => this.ui.requestRender(),
3356
+ });
3357
+ view.onClose = done;
3358
+ return { component: view, focus: view };
3359
+ });
3360
+ }
3209
3361
  showTreeSelector(initialSelectedId) {
3210
3362
  const tree = this.facade.getTree();
3211
3363
  const realLeafId = this.facade.getLeafId();
@@ -3253,9 +3405,10 @@ export class InteractiveMode {
3253
3405
  }
3254
3406
  }
3255
3407
  // The user committed to navigating: stop the active response first.
3408
+ // 中断分级:导航清场只停回合,不终止后台子代理(设计 §4.2)。
3256
3409
  if (this.facade.isStreaming()) {
3257
3410
  this.restoreQueuedMessagesToEditor();
3258
- await this.facade.abort();
3411
+ await this.facade.abort({ cancelAgentTasks: false });
3259
3412
  }
3260
3413
  // Set up escape handler and status indicator if summarizing
3261
3414
  let showingSummaryIndicator = false;
@@ -3732,6 +3885,19 @@ export class InteractiveMode {
3732
3885
  this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${sessionName ?? name}`), 1, 0));
3733
3886
  this.ui.requestRender();
3734
3887
  }
3888
+ /**
3889
+ * 中断分级(设计 子代理运行时 §4.2,2026-10-02):/stop-agents 的唯一执行
3890
+ * 面——终止全部后台子代理(不触当前回合)。Esc 从不做这件事。
3891
+ */
3892
+ handleStopAgentsCommand() {
3893
+ const active = this.facade.getActiveAgentTaskCount();
3894
+ if (active === 0) {
3895
+ this.showStatus("No background subagents running");
3896
+ return;
3897
+ }
3898
+ this.facade.abortAgentTasks();
3899
+ this.showStatus(`Terminated ${active} background subagent${active === 1 ? "" : "s"}`);
3900
+ }
3735
3901
  handleSessionCommand() {
3736
3902
  const stats = this.facade.getSessionStats();
3737
3903
  const sessionName = this.facade.getSessionName();
@@ -3950,6 +4116,27 @@ export class InteractiveMode {
3950
4116
  const followUp = this.getAppKeyDisplay("app.message.followUp");
3951
4117
  const dequeue = this.getAppKeyDisplay("app.message.dequeue");
3952
4118
  const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
4119
+ // Iron law (2026-10-01): these shipped defaults were cleared in favor of
4120
+ // the /model command; the row only appears when a user re-configures a key.
4121
+ const selectModelRow = selectModel ? `| \`${selectModel}\` | Open model selector |\n` : "";
4122
+ // Iron law (2026-10-02 复审): ctrl+g/ctrl+x stay bound only for the
4123
+ // components without a command channel — the main editor ignores them
4124
+ // (/editor and /copy are its single entries), so the rows say where
4125
+ // each key actually acts.
4126
+ const copyMessageRow = copyMessage ? `| \`${copyMessage}\` | Copy selection in /tree |\n` : "";
4127
+ const externalEditorRow = externalEditor
4128
+ ? `| \`${externalEditor}\` | External editor (inside dialog inputs) |\n`
4129
+ : "";
4130
+ // Iron law (2026-10-01): the message queue is the /queue and /dequeue
4131
+ // commands' feature; rows only appear when a user re-configures a key.
4132
+ const followUpRow = followUp ? `| \`${followUp}\` | Queue follow-up message |\n` : "";
4133
+ const dequeueRow = dequeue ? `| \`${dequeue}\` | Restore queued messages |\n` : "";
4134
+ const cycleThinkingRow = cycleThinkingLevel ? `| \`${cycleThinkingLevel}\` | Cycle thinking level |\n` : "";
4135
+ const cycleModelsRow = cycleModelForward || cycleModelBackward
4136
+ ? `| \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |\n`
4137
+ : "";
4138
+ // Suspend has no default key on native Windows; hide the row there.
4139
+ const suspendRow = suspend ? `| \`${suspend}\` | Suspend to background |\n` : "";
3953
4140
  const hotkeys = `
3954
4141
  **Navigation**
3955
4142
  | Key | Action |
@@ -3982,16 +4169,9 @@ export class InteractiveMode {
3982
4169
  | \`${interrupt}\` | Cancel autocomplete / abort streaming |
3983
4170
  | \`${clear}\` | Clear editor (first) / exit (second) |
3984
4171
  | \`${exit}\` | Exit (when editor is empty) |
3985
- | \`${suspend}\` | Suspend to background |
3986
- | \`${cycleThinkingLevel}\` | Cycle thinking level |
3987
- | \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |
3988
- | \`${selectModel}\` | Open model selector |
3989
- | \`${expandTools}\` | Toggle tool output expansion |
4172
+ ${suspendRow}${cycleThinkingRow}${cycleModelsRow}${selectModelRow}| \`${expandTools}\` | Toggle tool output expansion |
3990
4173
  | \`${toggleThinking}\` | Toggle thinking block visibility |
3991
- | \`${externalEditor}\` | Edit message in external editor |
3992
- | \`${copyMessage}\` | Copy last assistant message |
3993
- | \`${followUp}\` | Queue follow-up message |
3994
- | \`${dequeue}\` | Restore queued messages |
4174
+ ${externalEditorRow}${copyMessageRow}${followUpRow}${dequeueRow}
3995
4175
  | \`${pasteImage}\` | Paste image or text from clipboard |
3996
4176
  | \`/\` | Slash commands |
3997
4177
  | \`!\` | Run bash command |
@@ -4120,6 +4300,7 @@ export class InteractiveMode {
4120
4300
  this.themeController.disableAutoSync();
4121
4301
  this.footer.dispose();
4122
4302
  this.footerDataProvider.dispose();
4303
+ this.failoverTicker?.dispose();
4123
4304
  if (this.unsubscribe) {
4124
4305
  this.unsubscribe();
4125
4306
  }