@ashx-j/lunr 0.1.7 → 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 (108) hide show
  1. package/dist/builtin-extensions/lunr-todos.d.ts +7 -6
  2. package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
  3. package/dist/builtin-extensions/lunr-todos.js +24 -11
  4. package/dist/builtin-extensions/lunr-todos.js.map +1 -1
  5. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.d.ts.map +1 -1
  6. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js +9 -5
  7. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js.map +1 -1
  8. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.d.ts.map +1 -1
  9. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js +31 -3
  10. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js.map +1 -1
  11. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.d.ts +1 -0
  12. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.d.ts.map +1 -1
  13. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js +14 -4
  14. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  16. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +4 -3
  17. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  18. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +12 -1
  20. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +3 -0
  22. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  23. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +8 -0
  25. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +22 -2
  27. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  28. package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
  29. package/dist/builtin-extensions/pi-web-access/index.js +38 -230
  30. package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
  31. package/dist/builtin-extensions/pi-web-access/render-search-chrome.d.ts +35 -0
  32. package/dist/builtin-extensions/pi-web-access/render-search-chrome.d.ts.map +1 -0
  33. package/dist/builtin-extensions/pi-web-access/render-search-chrome.js +93 -0
  34. package/dist/builtin-extensions/pi-web-access/render-search-chrome.js.map +1 -0
  35. package/dist/catalog/models.json +1 -1
  36. package/dist/catalog/providers/cloudflare-workers-ai.json +1 -1
  37. package/dist/catalog/providers/huggingface.json +1 -1
  38. package/dist/catalog/providers/opencode-go.json +1 -1
  39. package/dist/catalog/providers/openrouter.json +1 -1
  40. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  41. package/dist/catalog/publication.json +3 -3
  42. package/dist/core/context-breakdown.d.ts +21 -1
  43. package/dist/core/context-breakdown.d.ts.map +1 -1
  44. package/dist/core/context-breakdown.js +58 -2
  45. package/dist/core/context-breakdown.js.map +1 -1
  46. package/dist/core/permission-mode-control.d.ts +27 -0
  47. package/dist/core/permission-mode-control.d.ts.map +1 -0
  48. package/dist/core/permission-mode-control.js +39 -0
  49. package/dist/core/permission-mode-control.js.map +1 -0
  50. package/dist/core/settings-manager.d.ts.map +1 -1
  51. package/dist/core/settings-manager.js.map +1 -1
  52. package/dist/core/slash-commands.d.ts.map +1 -1
  53. package/dist/core/slash-commands.js +0 -1
  54. package/dist/core/slash-commands.js.map +1 -1
  55. package/dist/core/tools/read.d.ts.map +1 -1
  56. package/dist/core/tools/read.js +14 -20
  57. package/dist/core/tools/read.js.map +1 -1
  58. package/dist/core/tools/render-utils.d.ts +2 -0
  59. package/dist/core/tools/render-utils.d.ts.map +1 -1
  60. package/dist/core/tools/render-utils.js +10 -0
  61. package/dist/core/tools/render-utils.js.map +1 -1
  62. package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
  63. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  64. package/dist/modes/interactive/components/assistant-message.js +1 -0
  65. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  66. package/dist/modes/interactive/components/context-view.d.ts.map +1 -1
  67. package/dist/modes/interactive/components/context-view.js +3 -1
  68. package/dist/modes/interactive/components/context-view.js.map +1 -1
  69. package/dist/modes/interactive/components/plan-message.d.ts +17 -0
  70. package/dist/modes/interactive/components/plan-message.d.ts.map +1 -0
  71. package/dist/modes/interactive/components/plan-message.js +43 -0
  72. package/dist/modes/interactive/components/plan-message.js.map +1 -0
  73. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  74. package/dist/modes/interactive/components/settings-selector.js +1 -1
  75. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  76. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  77. package/dist/modes/interactive/components/tool-execution.js +3 -1
  78. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  79. package/dist/modes/interactive/components/usage-view.d.ts +4 -25
  80. package/dist/modes/interactive/components/usage-view.d.ts.map +1 -1
  81. package/dist/modes/interactive/components/usage-view.js +17 -69
  82. package/dist/modes/interactive/components/usage-view.js.map +1 -1
  83. package/dist/modes/interactive/components/user-message.d.ts +1 -0
  84. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/user-message.js +1 -0
  86. package/dist/modes/interactive/components/user-message.js.map +1 -1
  87. package/dist/modes/interactive/interactive-mode.d.ts +17 -6
  88. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  89. package/dist/modes/interactive/interactive-mode.js +196 -114
  90. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  91. package/dist/modes/interactive/smooth-streaming.d.ts +39 -6
  92. package/dist/modes/interactive/smooth-streaming.d.ts.map +1 -1
  93. package/dist/modes/interactive/smooth-streaming.js +160 -73
  94. package/dist/modes/interactive/smooth-streaming.js.map +1 -1
  95. package/dist/utils/clipboard-image.d.ts.map +1 -1
  96. package/dist/utils/clipboard-image.js +7 -3
  97. package/dist/utils/clipboard-image.js.map +1 -1
  98. package/docs/terminal-setup.md +13 -0
  99. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  100. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  101. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  102. package/examples/extensions/gondolin/package-lock.json +2 -2
  103. package/examples/extensions/gondolin/package.json +1 -1
  104. package/examples/extensions/sandbox/package-lock.json +2 -2
  105. package/examples/extensions/sandbox/package.json +1 -1
  106. package/examples/extensions/with-deps/package-lock.json +2 -2
  107. package/examples/extensions/with-deps/package.json +1 -1
  108. 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 = [];
@@ -343,6 +346,7 @@ export class InteractiveMode {
343
346
  this.resetExtensionUI();
344
347
  // lunr: reset permission mode to configured default + clear session approvals
345
348
  this.previousPermissionMode = undefined;
349
+ this.preGoalPermissionMode = undefined;
346
350
  resetPermissions(this.settingsManager.getDefaultPermissionMode());
347
351
  // lunr: clear process registry and rollback state
348
352
  processRegistry.clearRegistry();
@@ -362,8 +366,17 @@ export class InteractiveMode {
362
366
  this.version = VERSION;
363
367
  this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
364
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
+ };
365
374
  // lunr: register the permission-mode footer bridge (provider-side: InteractiveMode owns the state).
366
375
  registerPermissionModeBridge(() => getPermissionMode());
376
+ registerPermissionModeControlBridge({
377
+ enterGoalAuto: () => this.enterGoalAuto(),
378
+ leaveGoalAuto: () => this.leaveGoalAuto(),
379
+ });
367
380
  // lunr: reset permission mode to configured default on startup.
368
381
  resetPermissions(this.settingsManager.getDefaultPermissionMode());
369
382
  this.headerContainer = new Container();
@@ -2252,11 +2265,6 @@ export class InteractiveMode {
2252
2265
  await this.handleUsageCommand();
2253
2266
  return;
2254
2267
  }
2255
- if (text === "/token-usage") {
2256
- this.editor.setText("");
2257
- this.handleTokenUsageCommand();
2258
- return;
2259
- }
2260
2268
  if (text === "/context") {
2261
2269
  this.editor.setText("");
2262
2270
  this.handleContextCommand();
@@ -2440,9 +2448,17 @@ export class InteractiveMode {
2440
2448
  this.editor.addToHistory?.(text);
2441
2449
  };
2442
2450
  }
2443
- /** Smooth streaming tick rate (ms) and base reveal speed (graphemes per tick). */
2444
- static SMOOTH_STREAMING_TICK_MS = 20;
2445
- 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
+ }
2446
2462
  startSmoothStreaming() {
2447
2463
  if (this.smoothStreamingTimer)
2448
2464
  return;
@@ -2452,38 +2468,96 @@ export class InteractiveMode {
2452
2468
  // Never keep the process alive just to finish a reveal animation.
2453
2469
  this.smoothStreamingTimer.unref?.();
2454
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
+ }
2455
2512
  advanceSmoothStreaming() {
2456
2513
  const target = this.streamingTargetMessage;
2457
2514
  if (!this.streamingComponent || !target) {
2458
2515
  this.stopSmoothStreaming();
2459
2516
  return;
2460
2517
  }
2461
- const total = countMessageGraphemes(target);
2462
- 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();
2463
2523
  return;
2464
- // Catch-up acceleration: the further the display lags behind the model,
2465
- // the more graphemes each tick reveals.
2524
+ }
2466
2525
  const backlog = total - this.streamingDisplayedLength;
2467
- this.streamingDisplayedLength += Math.max(InteractiveMode.SMOOTH_STREAMING_BASE_GRAPHEMES_PER_TICK, Math.ceil(backlog / 8));
2526
+ this.streamingDisplayedLength += computeSmoothRevealStep(backlog);
2468
2527
  if (this.streamingDisplayedLength > total) {
2469
2528
  this.streamingDisplayedLength = total;
2470
2529
  }
2471
- this.streamingComponent.updateContent(sliceMessageContent(target, this.streamingDisplayedLength));
2472
- // Throttle renders to ~30 FPS so the TUI isn't redrawn every 20 ms tick.
2473
- const now = Date.now();
2474
- if (now - this.lastSmoothRenderAt >= 33) {
2475
- this.lastSmoothRenderAt = now;
2476
- this.ui.requestRender();
2530
+ this.paintSmoothStreamingFrame();
2531
+ if (this.streamingDisplayedLength >= total) {
2532
+ this.clearSmoothStreamingTimer();
2477
2533
  }
2478
2534
  }
2479
2535
  stopSmoothStreaming() {
2480
- if (this.smoothStreamingTimer) {
2481
- clearInterval(this.smoothStreamingTimer);
2482
- this.smoothStreamingTimer = undefined;
2483
- }
2536
+ this.clearSmoothStreamingTimer();
2484
2537
  this.streamingTargetMessage = undefined;
2485
2538
  this.streamingDisplayedLength = 0;
2486
- 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();
2487
2561
  }
2488
2562
  subscribeToAgent() {
2489
2563
  this.unsubscribe = this.session.subscribe(async (event) => {
@@ -2561,7 +2635,7 @@ export class InteractiveMode {
2561
2635
  this.streamingDisplayedLength = 0;
2562
2636
  this.chatContainer.addChild(this.streamingComponent);
2563
2637
  if (this.settingsManager.getSmoothStreaming()) {
2564
- this.streamingComponent.updateContent(sliceMessageContent(this.streamingMessage, 0));
2638
+ this.streamingComponent.updateContent(sliceMessageContent(this.streamingMessage, 0, this.getSmoothStreamingOptions()));
2565
2639
  }
2566
2640
  else {
2567
2641
  this.streamingComponent.updateContent(this.streamingMessage);
@@ -2577,40 +2651,32 @@ export class InteractiveMode {
2577
2651
  // so smooth-streaming reveal lag doesn't inflate durations.
2578
2652
  updateThinkingRunTimings(this.thinkingRunTimings, this.streamingMessage.content, Date.now(), false);
2579
2653
  if (this.settingsManager.getSmoothStreaming()) {
2580
- // Smooth streaming: only track the target here; the timer
2581
- // reveals it grapheme by grapheme.
2582
- 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
+ }
2583
2666
  }
2584
2667
  else {
2585
2668
  this.streamingComponent.updateContent(this.streamingMessage);
2669
+ this.syncRevealedStreamingTools(this.streamingMessage, this.streamingMessage);
2670
+ this.ui.requestRender();
2586
2671
  }
2587
- for (const content of this.streamingMessage.content) {
2588
- if (content.type === "toolCall") {
2589
- if (!this.pendingTools.has(content.id)) {
2590
- const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
2591
- showImages: this.settingsManager.getShowImages(),
2592
- imageWidthCells: this.settingsManager.getImageWidthCells(),
2593
- }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
2594
- component.setExpanded(this.toolOutputExpanded);
2595
- this.addToolComponentToChat(component);
2596
- this.pendingTools.set(content.id, component);
2597
- }
2598
- else {
2599
- const component = this.pendingTools.get(content.id);
2600
- if (component) {
2601
- component.updateArgs(content.arguments);
2602
- }
2603
- }
2604
- }
2605
- }
2606
- this.ui.requestRender();
2607
2672
  }
2608
2673
  break;
2609
2674
  case "message_end":
2610
2675
  if (event.message.role === "user")
2611
2676
  break;
2612
2677
  if (this.streamingComponent && event.message.role === "assistant") {
2613
- // 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.
2614
2680
  this.stopSmoothStreaming();
2615
2681
  this.streamingMessage = event.message;
2616
2682
  let errorMessage;
@@ -2653,6 +2719,18 @@ export class InteractiveMode {
2653
2719
  this.ui.requestRender();
2654
2720
  break;
2655
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
+ }
2656
2734
  let component = this.pendingTools.get(event.toolCallId);
2657
2735
  if (!component) {
2658
2736
  component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
@@ -3117,7 +3195,7 @@ export class InteractiveMode {
3117
3195
  if (this.isShuttingDown)
3118
3196
  return;
3119
3197
  this.isShuttingDown = true;
3120
- this.stopSmoothStreaming?.();
3198
+ this.stopSmoothStreaming();
3121
3199
  // Keep signal handlers registered until terminal cleanup has completed.
3122
3200
  // `signal-exit` checks the listener list during the same SIGTERM/SIGHUP
3123
3201
  // dispatch and re-sends the signal if only its own listeners remain.
@@ -3418,10 +3496,25 @@ export class InteractiveMode {
3418
3496
  // Rebuild chat from session messages
3419
3497
  this.chatContainer.clear();
3420
3498
  this.rebuildChatFromMessages();
3421
- // 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.
3422
3501
  if (this.streamingComponent && this.streamingMessage) {
3423
3502
  this.streamingComponent.setHideThinkingBlock(this.hideThinkingBlock);
3424
- 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
+ }
3425
3518
  this.chatContainer.addChild(this.streamingComponent);
3426
3519
  }
3427
3520
  this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
@@ -3818,7 +3911,7 @@ export class InteractiveMode {
3818
3911
  this.settingsManager.setQuietStartup(enabled);
3819
3912
  },
3820
3913
  onSmoothStreamingChange: (enabled) => {
3821
- this.settingsManager.setSmoothStreaming(enabled);
3914
+ this.applySmoothStreamingSettingChange(enabled);
3822
3915
  },
3823
3916
  onSessionRetentionDaysChange: (days) => {
3824
3917
  this.settingsManager.setSessionRetentionDays(days);
@@ -5904,41 +5997,24 @@ export class InteractiveMode {
5904
5997
  this.chatContainer.addChild(new Text(info, 1, 0));
5905
5998
  this.ui.requestRender();
5906
5999
  }
5907
- // lunr: shared per-model session aggregation for /usage and /token-usage.
5908
- collectSessionModelRows() {
5909
- const perModelMap = new Map();
5910
- for (const entry of this.sessionManager.getEntries()) {
5911
- if (entry.type !== "message" || entry.message.role !== "assistant")
5912
- continue;
5913
- const message = entry.message;
5914
- const usage = message.usage;
5915
- const key = `${message.provider}/${message.responseModel ?? message.model}`;
5916
- let row = perModelMap.get(key);
5917
- if (!row) {
5918
- row = { model: key, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
5919
- perModelMap.set(key, row);
5920
- }
5921
- row.input += usage.input;
5922
- row.output += usage.output;
5923
- row.cacheRead += usage.cacheRead;
5924
- row.cacheWrite += usage.cacheWrite;
5925
- row.total += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
5926
- }
5927
- return Array.from(perModelMap.values()).sort((a, b) => b.total - a.total);
5928
- }
5929
- // lunr: 30-day history from session files on disk; best-effort — on any
5930
- // error the section is simply omitted.
5931
- collectUsageHistorySafe() {
5932
- try {
5933
- return collectUsageHistory({ sinceMs: Date.now() - 30 * 24 * 60 * 60 * 1000 });
5934
- }
5935
- catch {
6000
+ collectUsageBreakdown() {
6001
+ const model = this.session.model;
6002
+ const contextWindow = model?.contextWindow ?? 0;
6003
+ if (!model || contextWindow <= 0)
5936
6004
  return undefined;
5937
- }
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
+ });
5938
6015
  }
5939
- // lunr: /usage — simple token totals (session + last 30 days), context window
5940
- // bar, and subscription plan usage (when the current provider has a usage
5941
- // 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.
5942
6018
  async handleUsageCommand() {
5943
6019
  const stats = this.session.getSessionStats();
5944
6020
  const sessionTotals = stats.tokens.total > 0
@@ -5953,24 +6029,14 @@ export class InteractiveMode {
5953
6029
  const context = this.session.getContextUsage();
5954
6030
  const provider = this.session.model?.provider;
5955
6031
  const planResult = provider ? await getPlanUsageResult(provider, this.session.modelRuntime) : {};
5956
- const history = this.collectUsageHistorySafe();
5957
- 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);
5958
6034
  this.chatContainer.addChild(new Spacer(1));
5959
6035
  this.chatContainer.addChild(new Text(lines.join("\n"), 1, 0));
5960
6036
  if (planResult.error)
5961
6037
  this.showStatus(planResult.error);
5962
6038
  this.ui.requestRender();
5963
6039
  }
5964
- // lunr: /token-usage — per-model token breakdown for the current session and
5965
- // the last 30 days (from session files on disk).
5966
- handleTokenUsageCommand() {
5967
- const sessionRows = this.collectSessionModelRows();
5968
- const history = this.collectUsageHistorySafe();
5969
- const lines = renderTokenUsageBox({ sessionRows, history }, this.ui.terminal.columns);
5970
- this.chatContainer.addChild(new Spacer(1));
5971
- this.chatContainer.addChild(new Text(lines.join("\n"), 1, 0));
5972
- this.ui.requestRender();
5973
- }
5974
6040
  // lunr: /context — estimated breakdown of what consumes the context window
5975
6041
  // (system prompt incl. project context files, tool definitions, and the live
5976
6042
  // session messages). Uses session.messages, so post-compaction renders match
@@ -5982,16 +6048,11 @@ export class InteractiveMode {
5982
6048
  this.showStatus("No model selected or context window unknown.");
5983
6049
  return;
5984
6050
  }
5985
- const tools = this.session
5986
- .getActiveToolNames()
5987
- .map((name) => this.session.getToolDefinition(name))
5988
- .filter((definition) => definition !== undefined);
5989
- const breakdown = computeContextBreakdown({
5990
- systemPrompt: this.session.systemPrompt,
5991
- tools,
5992
- messages: this.session.messages,
5993
- contextWindow,
5994
- });
6051
+ const breakdown = this.collectUsageBreakdown();
6052
+ if (!breakdown) {
6053
+ this.showStatus("No model selected or context window unknown.");
6054
+ return;
6055
+ }
5995
6056
  const lines = renderContextBox({ breakdown, model: `${model.provider}/${model.id}` }, this.ui.terminal.columns);
5996
6057
  this.chatContainer.addChild(new Spacer(1));
5997
6058
  this.chatContainer.addChild(new Text(lines.join("\n"), 1, 0));
@@ -6083,6 +6144,21 @@ export class InteractiveMode {
6083
6144
  this.session.setSystemPromptAppend(undefined);
6084
6145
  }
6085
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
+ }
6086
6162
  applyPermissionMode(mode, opts) {
6087
6163
  const prev = getPermissionMode();
6088
6164
  if (mode === "plan" && prev !== "plan") {
@@ -6178,7 +6254,13 @@ export class InteractiveMode {
6178
6254
  // lunr: approval dialog for present_plan (plan mode). The "with feedback"
6179
6255
  // options collect a one-line note via showExtensionInput that becomes part of
6180
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
+ }
6181
6262
  async showPlanApprovalDialog(summary) {
6263
+ this.showPlanInChat(summary);
6182
6264
  return new Promise((resolve) => {
6183
6265
  this.showSelector((done) => {
6184
6266
  const selector = new ExtensionSelectorComponent("▶ Approve plan?", ["Approve", "Approve with feedback", "Decline", "Decline with feedback"], (option) => {
@@ -6202,7 +6284,7 @@ export class InteractiveMode {
6202
6284
  }, () => {
6203
6285
  done();
6204
6286
  resolve("reject");
6205
- }, { message: summary.slice(0, 500) });
6287
+ });
6206
6288
  return { component: selector, focus: selector };
6207
6289
  });
6208
6290
  });