@ashx-j/lunr 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/builtin-extensions/index.d.ts.map +1 -1
  2. package/dist/builtin-extensions/index.js +3 -6
  3. package/dist/builtin-extensions/index.js.map +1 -1
  4. package/dist/builtin-extensions/lunr-todos.d.ts +7 -6
  5. package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
  6. package/dist/builtin-extensions/lunr-todos.js +24 -11
  7. package/dist/builtin-extensions/lunr-todos.js.map +1 -1
  8. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.d.ts.map +1 -1
  9. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js +9 -5
  10. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js.map +1 -1
  11. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.d.ts.map +1 -1
  12. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js +31 -3
  13. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js.map +1 -1
  14. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.d.ts +1 -0
  15. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.d.ts.map +1 -1
  16. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js +14 -4
  17. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js.map +1 -1
  18. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +4 -3
  20. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +12 -1
  23. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +3 -0
  25. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  27. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +8 -0
  28. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  29. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +22 -2
  30. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  31. package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
  32. package/dist/builtin-extensions/pi-web-access/index.js +38 -230
  33. package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
  34. package/dist/builtin-extensions/pi-web-access/render-search-chrome.d.ts +35 -0
  35. package/dist/builtin-extensions/pi-web-access/render-search-chrome.d.ts.map +1 -0
  36. package/dist/builtin-extensions/pi-web-access/render-search-chrome.js +93 -0
  37. package/dist/builtin-extensions/pi-web-access/render-search-chrome.js.map +1 -0
  38. package/dist/bun/cli.d.ts.map +1 -1
  39. package/dist/bun/cli.js +1 -0
  40. package/dist/bun/cli.js.map +1 -1
  41. package/dist/catalog/models.json +1 -1
  42. package/dist/catalog/providers/cerebras.json +1 -1
  43. package/dist/catalog/providers/cloudflare-ai-gateway.json +1 -1
  44. package/dist/catalog/providers/cloudflare-workers-ai.json +1 -1
  45. package/dist/catalog/providers/huggingface.json +1 -1
  46. package/dist/catalog/providers/opencode-go.json +1 -1
  47. package/dist/catalog/providers/openrouter.json +1 -1
  48. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  49. package/dist/catalog/publication.json +3 -3
  50. package/dist/cli.d.ts.map +1 -1
  51. package/dist/cli.js +4 -1
  52. package/dist/cli.js.map +1 -1
  53. package/dist/core/context-breakdown.d.ts +21 -1
  54. package/dist/core/context-breakdown.d.ts.map +1 -1
  55. package/dist/core/context-breakdown.js +58 -2
  56. package/dist/core/context-breakdown.js.map +1 -1
  57. package/dist/core/extensions/host-static.d.ts +2 -0
  58. package/dist/core/extensions/host-static.d.ts.map +1 -0
  59. package/dist/core/extensions/host-static.js +15 -0
  60. package/dist/core/extensions/host-static.js.map +1 -0
  61. package/dist/core/extensions/loader.d.ts +10 -0
  62. package/dist/core/extensions/loader.d.ts.map +1 -1
  63. package/dist/core/extensions/loader.js +75 -34
  64. package/dist/core/extensions/loader.js.map +1 -1
  65. package/dist/core/official-catalog.d.ts.map +1 -1
  66. package/dist/core/official-catalog.js +11 -4
  67. package/dist/core/official-catalog.js.map +1 -1
  68. package/dist/core/package-manager.d.ts +3 -0
  69. package/dist/core/package-manager.d.ts.map +1 -1
  70. package/dist/core/package-manager.js +11 -5
  71. package/dist/core/package-manager.js.map +1 -1
  72. package/dist/core/permission-mode-control.d.ts +27 -0
  73. package/dist/core/permission-mode-control.d.ts.map +1 -0
  74. package/dist/core/permission-mode-control.js +39 -0
  75. package/dist/core/permission-mode-control.js.map +1 -0
  76. package/dist/core/resource-loader.d.ts +4 -1
  77. package/dist/core/resource-loader.d.ts.map +1 -1
  78. package/dist/core/resource-loader.js +13 -5
  79. package/dist/core/resource-loader.js.map +1 -1
  80. package/dist/core/settings-manager.d.ts.map +1 -1
  81. package/dist/core/settings-manager.js.map +1 -1
  82. package/dist/core/slash-commands.d.ts.map +1 -1
  83. package/dist/core/slash-commands.js +0 -1
  84. package/dist/core/slash-commands.js.map +1 -1
  85. package/dist/core/timings.d.ts +1 -0
  86. package/dist/core/timings.d.ts.map +1 -1
  87. package/dist/core/timings.js +6 -1
  88. package/dist/core/timings.js.map +1 -1
  89. package/dist/core/tools/read.d.ts.map +1 -1
  90. package/dist/core/tools/read.js +14 -20
  91. package/dist/core/tools/read.js.map +1 -1
  92. package/dist/core/tools/render-utils.d.ts +2 -0
  93. package/dist/core/tools/render-utils.d.ts.map +1 -1
  94. package/dist/core/tools/render-utils.js +10 -0
  95. package/dist/core/tools/render-utils.js.map +1 -1
  96. package/dist/main.d.ts +8 -0
  97. package/dist/main.d.ts.map +1 -1
  98. package/dist/main.js +63 -16
  99. package/dist/main.js.map +1 -1
  100. package/dist/migrations.d.ts.map +1 -1
  101. package/dist/migrations.js +3 -0
  102. package/dist/migrations.js.map +1 -1
  103. package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
  104. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  105. package/dist/modes/interactive/components/assistant-message.js +1 -0
  106. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  107. package/dist/modes/interactive/components/context-view.d.ts.map +1 -1
  108. package/dist/modes/interactive/components/context-view.js +3 -1
  109. package/dist/modes/interactive/components/context-view.js.map +1 -1
  110. package/dist/modes/interactive/components/plan-message.d.ts +17 -0
  111. package/dist/modes/interactive/components/plan-message.d.ts.map +1 -0
  112. package/dist/modes/interactive/components/plan-message.js +43 -0
  113. package/dist/modes/interactive/components/plan-message.js.map +1 -0
  114. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  115. package/dist/modes/interactive/components/settings-selector.js +1 -1
  116. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  117. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  118. package/dist/modes/interactive/components/tool-execution.js +3 -1
  119. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  120. package/dist/modes/interactive/components/usage-view.d.ts +4 -25
  121. package/dist/modes/interactive/components/usage-view.d.ts.map +1 -1
  122. package/dist/modes/interactive/components/usage-view.js +17 -69
  123. package/dist/modes/interactive/components/usage-view.js.map +1 -1
  124. package/dist/modes/interactive/components/user-message.d.ts +1 -0
  125. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/user-message.js +1 -0
  127. package/dist/modes/interactive/components/user-message.js.map +1 -1
  128. package/dist/modes/interactive/interactive-mode.d.ts +23 -6
  129. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  130. package/dist/modes/interactive/interactive-mode.js +267 -148
  131. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  132. package/dist/modes/interactive/smooth-streaming.d.ts +39 -6
  133. package/dist/modes/interactive/smooth-streaming.d.ts.map +1 -1
  134. package/dist/modes/interactive/smooth-streaming.js +160 -73
  135. package/dist/modes/interactive/smooth-streaming.js.map +1 -1
  136. package/dist/utils/clipboard-image.d.ts.map +1 -1
  137. package/dist/utils/clipboard-image.js +7 -3
  138. package/dist/utils/clipboard-image.js.map +1 -1
  139. package/docs/terminal-setup.md +13 -0
  140. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  141. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  142. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  143. package/examples/extensions/gondolin/package-lock.json +2 -2
  144. package/examples/extensions/gondolin/package.json +1 -1
  145. package/examples/extensions/sandbox/package-lock.json +2 -2
  146. package/examples/extensions/sandbox/package.json +1 -1
  147. package/examples/extensions/with-deps/package-lock.json +2 -2
  148. package/examples/extensions/with-deps/package.json +1 -1
  149. package/package.json +4 -4
@@ -27,6 +27,7 @@ import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScop
27
27
  import { refreshModelCandidatesForInit } from "../../core/model-runtime.js";
28
28
  import { getModelTiersBridge } from "../../core/model-tiers.js";
29
29
  import { registerPermissionModeBridge } from "../../core/permission-mode.js";
30
+ import { nextModeForGoal, registerPermissionModeControlBridge } from "../../core/permission-mode-control.js";
30
31
  import { AUTO_MODE_ADDENDUM, getPermissionMode, nextPermissionMode, registerApprovalHandler, resetPermissions, restorePermissionModeAfterPlan, setPermissionMode, } from "../../core/permissions.js";
31
32
  import { PLAN_MODE_ADDENDUM } from "../../core/plan-mode.js";
32
33
  import * as processRegistry from "../../core/process-registry.js";
@@ -37,9 +38,8 @@ import { SessionManager, sessionEntryToContextMessages } from "../../core/sessio
37
38
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
38
39
  // lunr: moved to core/swarm.ts so the gateway can reuse it.
39
40
  import { buildSwarmPrompt } from "../../core/swarm.js";
40
- import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
41
- import { collectUsageHistory } from "../../core/usage-history.js";
42
41
  import { time } from "../../core/timings.js";
42
+ import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
43
43
  import { getPlanUsageResult } from "../../core/usage-service.js";
44
44
  import { modelToUserEntry } from "../../core/user-models.js";
45
45
  import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
@@ -71,6 +71,7 @@ import { formatKeyText, keyDisplayText, keyText } from "./components/keybinding-
71
71
  import { LoginDialogComponent } from "./components/login-dialog.js";
72
72
  import { ModelSelectorComponent } from "./components/model-selector.js";
73
73
  import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
74
+ import { PlanMessageComponent } from "./components/plan-message.js";
74
75
  import { ProcessesSelectorComponent } from "./components/processes-selector.js";
75
76
  import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
76
77
  import { SessionSelectorComponent } from "./components/session-selector.js";
@@ -81,11 +82,11 @@ import { updateThinkingRunTimings } from "./components/thinking-summary.js";
81
82
  import { ToolExecutionComponent } from "./components/tool-execution.js";
82
83
  import { TreeSelectorComponent } from "./components/tree-selector.js";
83
84
  import { TrustSelectorComponent } from "./components/trust-selector.js";
84
- import { renderTokenUsageBox, renderUsageBox } from "./components/usage-view.js";
85
+ import { renderUsageBox } from "./components/usage-view.js";
85
86
  import { UserMessageComponent } from "./components/user-message.js";
86
87
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
87
88
  import { getModelSearchText } from "./model-search.js";
88
- import { countMessageGraphemes, sliceMessageContent } from "./smooth-streaming.js";
89
+ import { computeSmoothRevealStep, countGraphemesBeforeToolCall, countMessageGraphemes, sliceMessageContent, } from "./smooth-streaming.js";
89
90
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, getThemeName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
90
91
  import { InteractiveThemeController } from "./theme/theme-controller.js";
91
92
  function isExpandable(obj) {
@@ -253,7 +254,6 @@ export class InteractiveMode {
253
254
  // flashing whole chunks.
254
255
  streamingTargetMessage = undefined;
255
256
  streamingDisplayedLength = 0;
256
- lastSmoothRenderAt = 0;
257
257
  smoothStreamingTimer = undefined;
258
258
  // Tool execution tracking: toolCallId -> component
259
259
  pendingTools = new Map();
@@ -282,6 +282,9 @@ export class InteractiveMode {
282
282
  // lunr: mode in effect before the current plan stretch. Used by approve / `/plan off`
283
283
  // / `/plan <text>` to leave plan. Shift+Tab and `/mode` pick the destination themselves.
284
284
  previousPermissionMode;
285
+ // lunr: mode in effect before `/goal` forced session auto. Dedicated so it cannot
286
+ // collide with plan restore.
287
+ preGoalPermissionMode;
285
288
  // lunr: in-memory redo stack for /undo and /redo (leaf entry ids; cleared on any
286
289
  // new user message, never persisted — undone turns reappear after restart)
287
290
  redoStack = [];
@@ -321,6 +324,7 @@ export class InteractiveMode {
321
324
  autoTrustOnReloadCwd;
322
325
  themeController;
323
326
  deferredBuiltinsAttached = false;
327
+ deferredBuiltinAttachPromise;
324
328
  // Convenience accessors
325
329
  get session() {
326
330
  return this.runtimeHost.session;
@@ -342,6 +346,7 @@ export class InteractiveMode {
342
346
  this.resetExtensionUI();
343
347
  // lunr: reset permission mode to configured default + clear session approvals
344
348
  this.previousPermissionMode = undefined;
349
+ this.preGoalPermissionMode = undefined;
345
350
  resetPermissions(this.settingsManager.getDefaultPermissionMode());
346
351
  // lunr: clear process registry and rollback state
347
352
  processRegistry.clearRegistry();
@@ -361,8 +366,17 @@ export class InteractiveMode {
361
366
  this.version = VERSION;
362
367
  this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
363
368
  this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
369
+ this.ui.onTextSelected = (text) => {
370
+ void copyToClipboard(text)
371
+ .then(() => this.showStatus("Copied."))
372
+ .catch((error) => this.showError(error instanceof Error ? error.message : String(error)));
373
+ };
364
374
  // lunr: register the permission-mode footer bridge (provider-side: InteractiveMode owns the state).
365
375
  registerPermissionModeBridge(() => getPermissionMode());
376
+ registerPermissionModeControlBridge({
377
+ enterGoalAuto: () => this.enterGoalAuto(),
378
+ leaveGoalAuto: () => this.leaveGoalAuto(),
379
+ });
366
380
  // lunr: reset permission mode to configured default on startup.
367
381
  resetPermissions(this.settingsManager.getDefaultPermissionMode());
368
382
  this.headerContainer = new Container();
@@ -626,13 +640,15 @@ export class InteractiveMode {
626
640
  this.headerContainer.addChild(this.builtInHeader);
627
641
  }
628
642
  this.ui.requestRender();
629
- // Initialize extensions first so resources are shown before messages
643
+ // Light extensions first so resources are shown before messages. Deferred
644
+ // MCP/LSP/web/intercom attach in the background so the editor is usable.
630
645
  await this.rebindCurrentSession();
631
646
  time("rebindCurrentSession");
632
- await this.attachDeferredBuiltinExtensions();
633
- time("attachDeferredBuiltins");
634
- // Render initial messages AFTER showing loaded resources
635
647
  this.renderInitialMessages();
648
+ this.deferredBuiltinAttachPromise = this.attachDeferredBuiltinExtensions();
649
+ void this.deferredBuiltinAttachPromise.then(() => {
650
+ time("attachDeferredBuiltins");
651
+ });
636
652
  // Set up theme file watcher
637
653
  onThemeChange(() => {
638
654
  this.ui.invalidate();
@@ -643,9 +659,16 @@ export class InteractiveMode {
643
659
  this.footerDataProvider.onBranchChange(() => {
644
660
  this.ui.requestRender();
645
661
  });
646
- // Initialize available provider count for footer display
647
- await this.updateAvailableProviderCount();
648
- time("updateAvailableProviderCount");
662
+ // Cache-only footer snapshot. Do not hold time-to-type on it.
663
+ void this.updateAvailableProviderCount().then(() => {
664
+ time("updateAvailableProviderCount");
665
+ });
666
+ }
667
+ /** Wait until deferred builtins have attached (or were skipped). */
668
+ async waitForDeferredBuiltins() {
669
+ if (this.deferredBuiltinAttachPromise) {
670
+ await this.deferredBuiltinAttachPromise;
671
+ }
649
672
  }
650
673
  /**
651
674
  * Update terminal title with session name and cwd.
@@ -665,7 +688,9 @@ export class InteractiveMode {
665
688
  * Initializes the UI, shows warnings, processes initial messages, and starts the interactive loop.
666
689
  */
667
690
  async run() {
668
- await this.init();
691
+ if (!this.isInitialized) {
692
+ await this.init();
693
+ }
669
694
  // Check tmux keyboard setup asynchronously
670
695
  this.checkTmuxKeyboardSetup().then((warning) => {
671
696
  if (warning) {
@@ -688,7 +713,7 @@ export class InteractiveMode {
688
713
  // Process initial messages
689
714
  if (initialMessage) {
690
715
  try {
691
- await this.session.prompt(initialMessage, { images: initialImages });
716
+ await this.promptAfterDeferredBuiltins(initialMessage, { images: initialImages });
692
717
  }
693
718
  catch (error) {
694
719
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -698,7 +723,7 @@ export class InteractiveMode {
698
723
  if (initialMessages) {
699
724
  for (const message of initialMessages) {
700
725
  try {
701
- await this.session.prompt(message);
726
+ await this.promptAfterDeferredBuiltins(message);
702
727
  }
703
728
  catch (error) {
704
729
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -710,7 +735,7 @@ export class InteractiveMode {
710
735
  while (true) {
711
736
  const userInput = await this.getUserInput();
712
737
  try {
713
- await this.session.prompt(userInput);
738
+ await this.promptAfterDeferredBuiltins(userInput);
714
739
  }
715
740
  catch (error) {
716
741
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -1203,19 +1228,41 @@ export class InteractiveMode {
1203
1228
  /**
1204
1229
  * Initialize the extension system with TUI-based UI context.
1205
1230
  */
1231
+ async awaitDeferredBuiltinsForPrompt() {
1232
+ await this.waitForDeferredBuiltins();
1233
+ }
1234
+ async promptAfterDeferredBuiltins(text, options) {
1235
+ await this.awaitDeferredBuiltinsForPrompt();
1236
+ await this.session.prompt(text, options);
1237
+ }
1238
+ async sendUserMessageAfterDeferredBuiltins(text) {
1239
+ await this.awaitDeferredBuiltinsForPrompt();
1240
+ await this.session.sendUserMessage(text);
1241
+ }
1206
1242
  async attachDeferredBuiltinExtensions() {
1207
1243
  if (this.deferredBuiltinsAttached || !this.options.deferredBuiltinFactories) {
1208
1244
  return;
1209
1245
  }
1210
- this.deferredBuiltinsAttached = true;
1211
- const factories = await this.options.deferredBuiltinFactories();
1212
- if (factories.length === 0)
1213
- return;
1214
- this.options.onDeferredBuiltinsAttached?.(factories);
1215
- await this.session.attachInlineExtensions(factories);
1216
- this.setupAutocompleteProvider();
1217
- this.setupExtensionShortcuts(this.session.extensionRunner);
1218
- this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
1246
+ this.setExtensionStatus("deferred-builtins", "loading tools…");
1247
+ try {
1248
+ const factories = await this.options.deferredBuiltinFactories();
1249
+ this.deferredBuiltinsAttached = true;
1250
+ if (factories.length === 0)
1251
+ return;
1252
+ this.options.onDeferredBuiltinsAttached?.(factories);
1253
+ await this.session.attachInlineExtensions(factories);
1254
+ this.setupAutocompleteProvider();
1255
+ this.setupExtensionShortcuts(this.session.extensionRunner);
1256
+ this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
1257
+ }
1258
+ catch (error) {
1259
+ this.deferredBuiltinsAttached = false;
1260
+ const message = error instanceof Error ? error.message : String(error);
1261
+ this.showError(`Failed to load deferred extensions: ${message}`);
1262
+ }
1263
+ finally {
1264
+ this.setExtensionStatus("deferred-builtins", undefined);
1265
+ }
1219
1266
  }
1220
1267
  async bindCurrentSessionExtensions() {
1221
1268
  const uiContext = this.createExtensionUIContext();
@@ -1329,6 +1376,7 @@ export class InteractiveMode {
1329
1376
  return bootRows;
1330
1377
  }
1331
1378
  async rebindCurrentSession(options = {}) {
1379
+ await this.waitForDeferredBuiltins();
1332
1380
  this.unsubscribe?.();
1333
1381
  this.unsubscribe = undefined;
1334
1382
  this.applyRuntimeSettings();
@@ -1345,7 +1393,7 @@ export class InteractiveMode {
1345
1393
  await this.bindCurrentSessionExtensions();
1346
1394
  this.subscribeToAgent();
1347
1395
  }
1348
- await this.updateAvailableProviderCount();
1396
+ void this.updateAvailableProviderCount();
1349
1397
  this.updateEditorBorderColor();
1350
1398
  this.updateTerminalTitle();
1351
1399
  }
@@ -2217,11 +2265,6 @@ export class InteractiveMode {
2217
2265
  await this.handleUsageCommand();
2218
2266
  return;
2219
2267
  }
2220
- if (text === "/token-usage") {
2221
- this.editor.setText("");
2222
- this.handleTokenUsageCommand();
2223
- return;
2224
- }
2225
2268
  if (text === "/context") {
2226
2269
  this.editor.setText("");
2227
2270
  this.handleContextCommand();
@@ -2376,7 +2419,7 @@ export class InteractiveMode {
2376
2419
  if (this.isExtensionCommand(text)) {
2377
2420
  this.editor.addToHistory?.(text);
2378
2421
  this.editor.setText("");
2379
- await this.session.prompt(text);
2422
+ await this.promptAfterDeferredBuiltins(text);
2380
2423
  }
2381
2424
  else {
2382
2425
  this.queueCompactionMessage(text, "steer");
@@ -2388,7 +2431,7 @@ export class InteractiveMode {
2388
2431
  if (this.session.isStreaming) {
2389
2432
  this.editor.addToHistory?.(text);
2390
2433
  this.editor.setText("");
2391
- await this.session.prompt(text, { streamingBehavior: "steer" });
2434
+ await this.promptAfterDeferredBuiltins(text, { streamingBehavior: "steer" });
2392
2435
  this.updatePendingMessagesDisplay();
2393
2436
  this.ui.requestRender();
2394
2437
  return;
@@ -2405,9 +2448,17 @@ export class InteractiveMode {
2405
2448
  this.editor.addToHistory?.(text);
2406
2449
  };
2407
2450
  }
2408
- /** Smooth streaming tick rate (ms) and base reveal speed (graphemes per tick). */
2409
- static SMOOTH_STREAMING_TICK_MS = 20;
2410
- static SMOOTH_STREAMING_BASE_GRAPHEMES_PER_TICK = 4;
2451
+ /** Smooth streaming tick rate (~30 FPS). TUI already caps paints at 16ms. */
2452
+ static SMOOTH_STREAMING_TICK_MS = 33;
2453
+ getSmoothStreamingOptions() {
2454
+ return { hideThinking: this.hideThinkingBlock };
2455
+ }
2456
+ clearSmoothStreamingTimer() {
2457
+ if (this.smoothStreamingTimer) {
2458
+ clearInterval(this.smoothStreamingTimer);
2459
+ this.smoothStreamingTimer = undefined;
2460
+ }
2461
+ }
2411
2462
  startSmoothStreaming() {
2412
2463
  if (this.smoothStreamingTimer)
2413
2464
  return;
@@ -2417,38 +2468,96 @@ export class InteractiveMode {
2417
2468
  // Never keep the process alive just to finish a reveal animation.
2418
2469
  this.smoothStreamingTimer.unref?.();
2419
2470
  }
2471
+ /** Apply the current reveal slice and sync tool cards that have crossed the frontier. */
2472
+ paintSmoothStreamingFrame() {
2473
+ const target = this.streamingTargetMessage;
2474
+ if (!this.streamingComponent || !target)
2475
+ return;
2476
+ const opts = this.getSmoothStreamingOptions();
2477
+ const revealed = sliceMessageContent(target, this.streamingDisplayedLength, opts);
2478
+ this.streamingComponent.updateContent(revealed);
2479
+ this.syncRevealedStreamingTools(revealed, target);
2480
+ this.ui.requestRender();
2481
+ }
2482
+ /**
2483
+ * Create/update tool cards only once smooth streaming has revealed past their
2484
+ * leading text. Args always come from the full target so later arg deltas apply.
2485
+ */
2486
+ syncRevealedStreamingTools(revealed, target) {
2487
+ const revealedIds = new Set();
2488
+ for (const block of revealed.content) {
2489
+ if (block.type === "toolCall")
2490
+ revealedIds.add(block.id);
2491
+ }
2492
+ for (const content of target.content) {
2493
+ if (content.type !== "toolCall" || !revealedIds.has(content.id))
2494
+ continue;
2495
+ if (!this.pendingTools.has(content.id)) {
2496
+ const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
2497
+ showImages: this.settingsManager.getShowImages(),
2498
+ imageWidthCells: this.settingsManager.getImageWidthCells(),
2499
+ }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
2500
+ component.setExpanded(this.toolOutputExpanded);
2501
+ this.addToolComponentToChat(component);
2502
+ this.pendingTools.set(content.id, component);
2503
+ }
2504
+ else {
2505
+ const component = this.pendingTools.get(content.id);
2506
+ if (component) {
2507
+ component.updateArgs(content.arguments);
2508
+ }
2509
+ }
2510
+ }
2511
+ }
2420
2512
  advanceSmoothStreaming() {
2421
2513
  const target = this.streamingTargetMessage;
2422
2514
  if (!this.streamingComponent || !target) {
2423
2515
  this.stopSmoothStreaming();
2424
2516
  return;
2425
2517
  }
2426
- const total = countMessageGraphemes(target);
2427
- if (this.streamingDisplayedLength >= total)
2518
+ const opts = this.getSmoothStreamingOptions();
2519
+ const total = countMessageGraphemes(target, opts);
2520
+ if (this.streamingDisplayedLength >= total) {
2521
+ // Caught up: stop the interval until new backlog arrives.
2522
+ this.clearSmoothStreamingTimer();
2428
2523
  return;
2429
- // Catch-up acceleration: the further the display lags behind the model,
2430
- // the more graphemes each tick reveals.
2524
+ }
2431
2525
  const backlog = total - this.streamingDisplayedLength;
2432
- this.streamingDisplayedLength += Math.max(InteractiveMode.SMOOTH_STREAMING_BASE_GRAPHEMES_PER_TICK, Math.ceil(backlog / 8));
2526
+ this.streamingDisplayedLength += computeSmoothRevealStep(backlog);
2433
2527
  if (this.streamingDisplayedLength > total) {
2434
2528
  this.streamingDisplayedLength = total;
2435
2529
  }
2436
- this.streamingComponent.updateContent(sliceMessageContent(target, this.streamingDisplayedLength));
2437
- // Throttle renders to ~30 FPS so the TUI isn't redrawn every 20 ms tick.
2438
- const now = Date.now();
2439
- if (now - this.lastSmoothRenderAt >= 33) {
2440
- this.lastSmoothRenderAt = now;
2441
- this.ui.requestRender();
2530
+ this.paintSmoothStreamingFrame();
2531
+ if (this.streamingDisplayedLength >= total) {
2532
+ this.clearSmoothStreamingTimer();
2442
2533
  }
2443
2534
  }
2444
2535
  stopSmoothStreaming() {
2445
- if (this.smoothStreamingTimer) {
2446
- clearInterval(this.smoothStreamingTimer);
2447
- this.smoothStreamingTimer = undefined;
2448
- }
2536
+ this.clearSmoothStreamingTimer();
2449
2537
  this.streamingTargetMessage = undefined;
2450
2538
  this.streamingDisplayedLength = 0;
2451
- this.lastSmoothRenderAt = 0;
2539
+ }
2540
+ /** Apply a mid-stream smoothStreaming setting change without rewinding. */
2541
+ applySmoothStreamingSettingChange(enabled) {
2542
+ this.settingsManager.setSmoothStreaming(enabled);
2543
+ if (!this.streamingComponent || !this.streamingMessage)
2544
+ return;
2545
+ if (enabled) {
2546
+ // Already showing the full live message; start the reveal cursor at the
2547
+ // current visible length so the next deltas only animate new text.
2548
+ this.streamingTargetMessage = this.streamingMessage;
2549
+ const opts = this.getSmoothStreamingOptions();
2550
+ this.streamingDisplayedLength = countMessageGraphemes(this.streamingMessage, opts);
2551
+ this.paintSmoothStreamingFrame();
2552
+ this.startSmoothStreaming();
2553
+ return;
2554
+ }
2555
+ // Off: stop the timer and snap to the full target immediately.
2556
+ this.clearSmoothStreamingTimer();
2557
+ this.streamingTargetMessage = this.streamingMessage;
2558
+ this.streamingComponent.updateContent(this.streamingMessage);
2559
+ this.syncRevealedStreamingTools(this.streamingMessage, this.streamingMessage);
2560
+ this.ui.requestRender();
2452
2561
  }
2453
2562
  subscribeToAgent() {
2454
2563
  this.unsubscribe = this.session.subscribe(async (event) => {
@@ -2526,7 +2635,7 @@ export class InteractiveMode {
2526
2635
  this.streamingDisplayedLength = 0;
2527
2636
  this.chatContainer.addChild(this.streamingComponent);
2528
2637
  if (this.settingsManager.getSmoothStreaming()) {
2529
- this.streamingComponent.updateContent(sliceMessageContent(this.streamingMessage, 0));
2638
+ this.streamingComponent.updateContent(sliceMessageContent(this.streamingMessage, 0, this.getSmoothStreamingOptions()));
2530
2639
  }
2531
2640
  else {
2532
2641
  this.streamingComponent.updateContent(this.streamingMessage);
@@ -2542,40 +2651,32 @@ export class InteractiveMode {
2542
2651
  // so smooth-streaming reveal lag doesn't inflate durations.
2543
2652
  updateThinkingRunTimings(this.thinkingRunTimings, this.streamingMessage.content, Date.now(), false);
2544
2653
  if (this.settingsManager.getSmoothStreaming()) {
2545
- // Smooth streaming: only track the target here; the timer
2546
- // reveals it grapheme by grapheme.
2547
- this.startSmoothStreaming();
2654
+ // Smooth streaming: timer reveals graphemes. Tool cards sync from
2655
+ // the reveal frontier so they don't jump ahead of leading text.
2656
+ const opts = this.getSmoothStreamingOptions();
2657
+ const total = countMessageGraphemes(this.streamingMessage, opts);
2658
+ if (this.streamingDisplayedLength >= total) {
2659
+ // Already caught up (or tool-only): paint so new tool cards appear.
2660
+ this.paintSmoothStreamingFrame();
2661
+ this.clearSmoothStreamingTimer();
2662
+ }
2663
+ else {
2664
+ this.startSmoothStreaming();
2665
+ }
2548
2666
  }
2549
2667
  else {
2550
2668
  this.streamingComponent.updateContent(this.streamingMessage);
2669
+ this.syncRevealedStreamingTools(this.streamingMessage, this.streamingMessage);
2670
+ this.ui.requestRender();
2551
2671
  }
2552
- for (const content of this.streamingMessage.content) {
2553
- if (content.type === "toolCall") {
2554
- if (!this.pendingTools.has(content.id)) {
2555
- const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
2556
- showImages: this.settingsManager.getShowImages(),
2557
- imageWidthCells: this.settingsManager.getImageWidthCells(),
2558
- }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
2559
- component.setExpanded(this.toolOutputExpanded);
2560
- this.addToolComponentToChat(component);
2561
- this.pendingTools.set(content.id, component);
2562
- }
2563
- else {
2564
- const component = this.pendingTools.get(content.id);
2565
- if (component) {
2566
- component.updateArgs(content.arguments);
2567
- }
2568
- }
2569
- }
2570
- }
2571
- this.ui.requestRender();
2572
2672
  }
2573
2673
  break;
2574
2674
  case "message_end":
2575
2675
  if (event.message.role === "user")
2576
2676
  break;
2577
2677
  if (this.streamingComponent && event.message.role === "assistant") {
2578
- // Stop the reveal timer and render the full message immediately.
2678
+ // Snap remaining graphemes on end (abort/error and normal stop).
2679
+ // Avoids multi-second leftover animation; paint always follows below.
2579
2680
  this.stopSmoothStreaming();
2580
2681
  this.streamingMessage = event.message;
2581
2682
  let errorMessage;
@@ -2618,6 +2719,18 @@ export class InteractiveMode {
2618
2719
  this.ui.requestRender();
2619
2720
  break;
2620
2721
  case "tool_execution_start": {
2722
+ // If smooth streaming still has leading text unrevealed, flush through
2723
+ // this tool so the running card is not hidden behind the typewriter.
2724
+ if (this.settingsManager.getSmoothStreaming() &&
2725
+ this.streamingTargetMessage &&
2726
+ this.streamingComponent) {
2727
+ const opts = this.getSmoothStreamingOptions();
2728
+ const needed = countGraphemesBeforeToolCall(this.streamingTargetMessage, event.toolCallId, opts);
2729
+ if (this.streamingDisplayedLength < needed) {
2730
+ this.streamingDisplayedLength = needed;
2731
+ }
2732
+ this.paintSmoothStreamingFrame();
2733
+ }
2621
2734
  let component = this.pendingTools.get(event.toolCallId);
2622
2735
  if (!component) {
2623
2736
  component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
@@ -3082,7 +3195,7 @@ export class InteractiveMode {
3082
3195
  if (this.isShuttingDown)
3083
3196
  return;
3084
3197
  this.isShuttingDown = true;
3085
- this.stopSmoothStreaming?.();
3198
+ this.stopSmoothStreaming();
3086
3199
  // Keep signal handlers registered until terminal cleanup has completed.
3087
3200
  // `signal-exit` checks the listener list during the same SIGTERM/SIGHUP
3088
3201
  // dispatch and re-sends the signal if only its own listeners remain.
@@ -3272,7 +3385,7 @@ export class InteractiveMode {
3272
3385
  if (this.isExtensionCommand(text)) {
3273
3386
  this.editor.addToHistory?.(text);
3274
3387
  this.editor.setText("");
3275
- await this.session.prompt(text);
3388
+ await this.promptAfterDeferredBuiltins(text);
3276
3389
  }
3277
3390
  else {
3278
3391
  this.queueCompactionMessage(text, "followUp");
@@ -3284,7 +3397,7 @@ export class InteractiveMode {
3284
3397
  if (this.session.isStreaming) {
3285
3398
  this.editor.addToHistory?.(text);
3286
3399
  this.editor.setText("");
3287
- await this.session.prompt(text, { streamingBehavior: "followUp" });
3400
+ await this.promptAfterDeferredBuiltins(text, { streamingBehavior: "followUp" });
3288
3401
  this.updatePendingMessagesDisplay();
3289
3402
  this.ui.requestRender();
3290
3403
  }
@@ -3383,10 +3496,25 @@ export class InteractiveMode {
3383
3496
  // Rebuild chat from session messages
3384
3497
  this.chatContainer.clear();
3385
3498
  this.rebuildChatFromMessages();
3386
- // If streaming, re-add the streaming component with updated visibility and re-render
3499
+ // If streaming, re-add the streaming component with updated visibility and re-render.
3500
+ // Re-slice to the current revealed length so hide-thinking does not dump the tail.
3387
3501
  if (this.streamingComponent && this.streamingMessage) {
3388
3502
  this.streamingComponent.setHideThinkingBlock(this.hideThinkingBlock);
3389
- this.streamingComponent.updateContent(this.streamingMessage);
3503
+ if (this.settingsManager.getSmoothStreaming() && this.streamingTargetMessage) {
3504
+ const opts = this.getSmoothStreamingOptions();
3505
+ const total = countMessageGraphemes(this.streamingTargetMessage, opts);
3506
+ if (this.streamingDisplayedLength > total) {
3507
+ this.streamingDisplayedLength = total;
3508
+ }
3509
+ this.paintSmoothStreamingFrame();
3510
+ // Restart the timer if new (or still-pending) backlog exists.
3511
+ if (this.streamingDisplayedLength < total) {
3512
+ this.startSmoothStreaming();
3513
+ }
3514
+ }
3515
+ else {
3516
+ this.streamingComponent.updateContent(this.streamingMessage);
3517
+ }
3390
3518
  this.chatContainer.addChild(this.streamingComponent);
3391
3519
  }
3392
3520
  this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
@@ -3561,7 +3689,7 @@ export class InteractiveMode {
3561
3689
  // When retry is pending, queue messages for the retry turn
3562
3690
  for (const message of queuedMessages) {
3563
3691
  if (this.isExtensionCommand(message.text)) {
3564
- await this.session.prompt(message.text);
3692
+ await this.promptAfterDeferredBuiltins(message.text);
3565
3693
  }
3566
3694
  else if (message.mode === "followUp") {
3567
3695
  await this.session.followUp(message.text);
@@ -3578,7 +3706,7 @@ export class InteractiveMode {
3578
3706
  if (firstPromptIndex === -1) {
3579
3707
  // All extension commands - execute them all
3580
3708
  for (const message of queuedMessages) {
3581
- await this.session.prompt(message.text);
3709
+ await this.promptAfterDeferredBuiltins(message.text);
3582
3710
  }
3583
3711
  return;
3584
3712
  }
@@ -3587,16 +3715,16 @@ export class InteractiveMode {
3587
3715
  const firstPrompt = queuedMessages[firstPromptIndex];
3588
3716
  const rest = queuedMessages.slice(firstPromptIndex + 1);
3589
3717
  for (const message of preCommands) {
3590
- await this.session.prompt(message.text);
3718
+ await this.promptAfterDeferredBuiltins(message.text);
3591
3719
  }
3592
3720
  // Send first prompt (starts streaming)
3593
- const promptPromise = this.session.prompt(firstPrompt.text).catch((error) => {
3721
+ const promptPromise = this.promptAfterDeferredBuiltins(firstPrompt.text).catch((error) => {
3594
3722
  restoreQueue(error);
3595
3723
  });
3596
3724
  // Queue remaining messages
3597
3725
  for (const message of rest) {
3598
3726
  if (this.isExtensionCommand(message.text)) {
3599
- await this.session.prompt(message.text);
3727
+ await this.promptAfterDeferredBuiltins(message.text);
3600
3728
  }
3601
3729
  else if (message.mode === "followUp") {
3602
3730
  await this.session.followUp(message.text);
@@ -3783,7 +3911,7 @@ export class InteractiveMode {
3783
3911
  this.settingsManager.setQuietStartup(enabled);
3784
3912
  },
3785
3913
  onSmoothStreamingChange: (enabled) => {
3786
- this.settingsManager.setSmoothStreaming(enabled);
3914
+ this.applySmoothStreamingSettingChange(enabled);
3787
3915
  },
3788
3916
  onSessionRetentionDaysChange: (days) => {
3789
3917
  this.settingsManager.setSessionRetentionDays(days);
@@ -5282,7 +5410,7 @@ export class InteractiveMode {
5282
5410
  return;
5283
5411
  }
5284
5412
  }
5285
- await this.session.sendUserMessage(prompt);
5413
+ await this.sendUserMessageAfterDeferredBuiltins(prompt);
5286
5414
  this.showStatus("AGENTS.md is being generated — run /reload when it finishes to load it into context.");
5287
5415
  }
5288
5416
  async handleSwarmCommand(task) {
@@ -5301,7 +5429,7 @@ export class InteractiveMode {
5301
5429
  }
5302
5430
  this.swarmMode = true;
5303
5431
  this.setExtensionStatus("swarm", "swarm ● active");
5304
- await this.session.sendUserMessage(buildSwarmPrompt(task));
5432
+ await this.sendUserMessageAfterDeferredBuiltins(buildSwarmPrompt(task));
5305
5433
  }
5306
5434
  async handleResearchCommand(args) {
5307
5435
  const usage = "Usage: /research [--depth N] [--breadth N] <question> — deep research with cited sources. depth: reflection rounds (1-4, default 2); breadth: subtopics per round (1-8, default 4).";
@@ -5349,7 +5477,7 @@ export class InteractiveMode {
5349
5477
  }
5350
5478
  this.researchMode = true;
5351
5479
  this.setExtensionStatus("research", "research ● active");
5352
- await this.session.sendUserMessage(buildResearchPrompt(question, depth, breadth));
5480
+ await this.sendUserMessageAfterDeferredBuiltins(buildResearchPrompt(question, depth, breadth));
5353
5481
  }
5354
5482
  // lunr: /undo persists the rewind by forking the session to just before the
5355
5483
  // last user message (same fork mechanism /rollback uses, but WITHOUT touching files).
@@ -5869,41 +5997,24 @@ export class InteractiveMode {
5869
5997
  this.chatContainer.addChild(new Text(info, 1, 0));
5870
5998
  this.ui.requestRender();
5871
5999
  }
5872
- // lunr: shared per-model session aggregation for /usage and /token-usage.
5873
- collectSessionModelRows() {
5874
- const perModelMap = new Map();
5875
- for (const entry of this.sessionManager.getEntries()) {
5876
- if (entry.type !== "message" || entry.message.role !== "assistant")
5877
- continue;
5878
- const message = entry.message;
5879
- const usage = message.usage;
5880
- const key = `${message.provider}/${message.responseModel ?? message.model}`;
5881
- let row = perModelMap.get(key);
5882
- if (!row) {
5883
- row = { model: key, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
5884
- perModelMap.set(key, row);
5885
- }
5886
- row.input += usage.input;
5887
- row.output += usage.output;
5888
- row.cacheRead += usage.cacheRead;
5889
- row.cacheWrite += usage.cacheWrite;
5890
- row.total += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
5891
- }
5892
- return Array.from(perModelMap.values()).sort((a, b) => b.total - a.total);
5893
- }
5894
- // lunr: 30-day history from session files on disk; best-effort — on any
5895
- // error the section is simply omitted.
5896
- collectUsageHistorySafe() {
5897
- try {
5898
- return collectUsageHistory({ sinceMs: Date.now() - 30 * 24 * 60 * 60 * 1000 });
5899
- }
5900
- catch {
6000
+ collectUsageBreakdown() {
6001
+ const model = this.session.model;
6002
+ const contextWindow = model?.contextWindow ?? 0;
6003
+ if (!model || contextWindow <= 0)
5901
6004
  return undefined;
5902
- }
6005
+ const tools = this.session
6006
+ .getActiveToolNames()
6007
+ .map((name) => this.session.getToolDefinition(name))
6008
+ .filter((definition) => definition !== undefined);
6009
+ return computeContextBreakdown({
6010
+ systemPrompt: this.session.systemPrompt,
6011
+ tools,
6012
+ messages: this.session.messages,
6013
+ contextWindow,
6014
+ });
5903
6015
  }
5904
- // lunr: /usage — simple token totals (session + last 30 days), context window
5905
- // bar, and subscription plan usage (when the current provider has a usage
5906
- // adapter). Per-model rows live in /token-usage.
6016
+ // lunr: /usage — current session only: token totals, live context split,
6017
+ // context-window bar, and subscription plan usage.
5907
6018
  async handleUsageCommand() {
5908
6019
  const stats = this.session.getSessionStats();
5909
6020
  const sessionTotals = stats.tokens.total > 0
@@ -5918,24 +6029,14 @@ export class InteractiveMode {
5918
6029
  const context = this.session.getContextUsage();
5919
6030
  const provider = this.session.model?.provider;
5920
6031
  const planResult = provider ? await getPlanUsageResult(provider, this.session.modelRuntime) : {};
5921
- const history = this.collectUsageHistorySafe();
5922
- const lines = renderUsageBox({ sessionTotals, context, plan: planResult.usage, history }, this.ui.terminal.columns);
6032
+ const breakdown = this.collectUsageBreakdown();
6033
+ const lines = renderUsageBox({ sessionTotals, context, plan: planResult.usage, breakdown }, this.ui.terminal.columns);
5923
6034
  this.chatContainer.addChild(new Spacer(1));
5924
6035
  this.chatContainer.addChild(new Text(lines.join("\n"), 1, 0));
5925
6036
  if (planResult.error)
5926
6037
  this.showStatus(planResult.error);
5927
6038
  this.ui.requestRender();
5928
6039
  }
5929
- // lunr: /token-usage — per-model token breakdown for the current session and
5930
- // the last 30 days (from session files on disk).
5931
- handleTokenUsageCommand() {
5932
- const sessionRows = this.collectSessionModelRows();
5933
- const history = this.collectUsageHistorySafe();
5934
- const lines = renderTokenUsageBox({ sessionRows, history }, this.ui.terminal.columns);
5935
- this.chatContainer.addChild(new Spacer(1));
5936
- this.chatContainer.addChild(new Text(lines.join("\n"), 1, 0));
5937
- this.ui.requestRender();
5938
- }
5939
6040
  // lunr: /context — estimated breakdown of what consumes the context window
5940
6041
  // (system prompt incl. project context files, tool definitions, and the live
5941
6042
  // session messages). Uses session.messages, so post-compaction renders match
@@ -5947,16 +6048,11 @@ export class InteractiveMode {
5947
6048
  this.showStatus("No model selected or context window unknown.");
5948
6049
  return;
5949
6050
  }
5950
- const tools = this.session
5951
- .getActiveToolNames()
5952
- .map((name) => this.session.getToolDefinition(name))
5953
- .filter((definition) => definition !== undefined);
5954
- const breakdown = computeContextBreakdown({
5955
- systemPrompt: this.session.systemPrompt,
5956
- tools,
5957
- messages: this.session.messages,
5958
- contextWindow,
5959
- });
6051
+ const breakdown = this.collectUsageBreakdown();
6052
+ if (!breakdown) {
6053
+ this.showStatus("No model selected or context window unknown.");
6054
+ return;
6055
+ }
5960
6056
  const lines = renderContextBox({ breakdown, model: `${model.provider}/${model.id}` }, this.ui.terminal.columns);
5961
6057
  this.chatContainer.addChild(new Spacer(1));
5962
6058
  this.chatContainer.addChild(new Text(lines.join("\n"), 1, 0));
@@ -5982,7 +6078,7 @@ export class InteractiveMode {
5982
6078
  else {
5983
6079
  this.applyPermissionMode("plan");
5984
6080
  }
5985
- await this.session.sendUserMessage(args);
6081
+ await this.sendUserMessageAfterDeferredBuiltins(args);
5986
6082
  return;
5987
6083
  }
5988
6084
  const next = sub === "" ? !inPlan : sub === "on";
@@ -6048,6 +6144,21 @@ export class InteractiveMode {
6048
6144
  this.session.setSystemPromptAppend(undefined);
6049
6145
  }
6050
6146
  }
6147
+ // lunr: `/goal` forces session auto without writing settings.
6148
+ enterGoalAuto() {
6149
+ const next = nextModeForGoal(getPermissionMode(), "enter", this.preGoalPermissionMode);
6150
+ this.preGoalPermissionMode = next.saved;
6151
+ if (next.mode !== getPermissionMode()) {
6152
+ this.applyPermissionMode(next.mode, { silent: true });
6153
+ }
6154
+ }
6155
+ leaveGoalAuto() {
6156
+ const next = nextModeForGoal(getPermissionMode(), "leave", this.preGoalPermissionMode);
6157
+ this.preGoalPermissionMode = next.saved;
6158
+ if (next.mode !== getPermissionMode()) {
6159
+ this.applyPermissionMode(next.mode, { silent: true });
6160
+ }
6161
+ }
6051
6162
  applyPermissionMode(mode, opts) {
6052
6163
  const prev = getPermissionMode();
6053
6164
  if (mode === "plan" && prev !== "plan") {
@@ -6143,7 +6254,13 @@ export class InteractiveMode {
6143
6254
  // lunr: approval dialog for present_plan (plan mode). The "with feedback"
6144
6255
  // options collect a one-line note via showExtensionInput that becomes part of
6145
6256
  // the result text the model sees. Esc/close counts as Decline.
6257
+ showPlanInChat(summary) {
6258
+ this.chatContainer.addChild(new Spacer(1));
6259
+ this.chatContainer.addChild(new PlanMessageComponent(summary, this.getMarkdownThemeWithSettings(), this.outputPad, this.settingsManager.getGutterRail()));
6260
+ this.ui.requestRender();
6261
+ }
6146
6262
  async showPlanApprovalDialog(summary) {
6263
+ this.showPlanInChat(summary);
6147
6264
  return new Promise((resolve) => {
6148
6265
  this.showSelector((done) => {
6149
6266
  const selector = new ExtensionSelectorComponent("▶ Approve plan?", ["Approve", "Approve with feedback", "Decline", "Decline with feedback"], (option) => {
@@ -6167,7 +6284,7 @@ export class InteractiveMode {
6167
6284
  }, () => {
6168
6285
  done();
6169
6286
  resolve("reject");
6170
- }, { message: summary.slice(0, 500) });
6287
+ });
6171
6288
  return { component: selector, focus: selector };
6172
6289
  });
6173
6290
  });
@@ -6383,6 +6500,8 @@ ${cycleThinkingLevel ? `| \`${cycleThinkingLevel}\` | Cycle thinking level |\n`
6383
6500
  async handleClearCommand() {
6384
6501
  this.clearStatusIndicator();
6385
6502
  this.ui.setChatScroll(0);
6503
+ // Deferred factories must be on builtinRoster before /new rebuilds the runtime.
6504
+ await this.waitForDeferredBuiltins();
6386
6505
  // lunr: preserve the in-flight model across /new so a /model switch survives.
6387
6506
  const previousModel = this.session.model;
6388
6507
  try {