@deepseek-harness-tui/dsh-tui 0.8.6 → 0.8.7

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 (105) hide show
  1. package/README.md +13 -3
  2. package/bin/dsh-tui.js +269 -218
  3. package/cordis.patch.yml +48 -22
  4. package/lib/types/cc/figures.d.ts.map +1 -1
  5. package/lib/types/cc/figures.js +14 -13
  6. package/lib/types/cc/syntaxRuns.d.ts +16 -0
  7. package/lib/types/cc/syntaxRuns.d.ts.map +1 -0
  8. package/lib/types/cc/syntaxRuns.js +79 -0
  9. package/lib/types/commands.d.ts +14 -0
  10. package/lib/types/commands.d.ts.map +1 -1
  11. package/lib/types/commands.js +20 -1
  12. package/lib/types/components/Chat/SubagentMessage.d.ts +19 -0
  13. package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
  14. package/lib/types/components/Chat/SubagentMessage.js +75 -0
  15. package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
  16. package/lib/types/components/EffortInputBorder.js +2 -2
  17. package/lib/types/components/EffortTierBadge.js +1 -1
  18. package/lib/types/components/HelpMenu.d.ts +8 -1
  19. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  20. package/lib/types/components/HelpMenu.js +15 -4
  21. package/lib/types/components/LogoV2.d.ts +3 -1
  22. package/lib/types/components/LogoV2.d.ts.map +1 -1
  23. package/lib/types/components/LogoV2.js +2 -2
  24. package/lib/types/components/MessageList.d.ts +6 -2
  25. package/lib/types/components/MessageList.d.ts.map +1 -1
  26. package/lib/types/components/MessageList.js +17 -6
  27. package/lib/types/components/ModelPicker.js +2 -2
  28. package/lib/types/components/PromptInput.d.ts +1 -1
  29. package/lib/types/components/PromptInput.d.ts.map +1 -1
  30. package/lib/types/components/PromptInput.js +75 -15
  31. package/lib/types/components/SkillsPicker.js +2 -2
  32. package/lib/types/components/SplitDiffView.d.ts +1 -13
  33. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  34. package/lib/types/components/SplitDiffView.js +6 -105
  35. package/lib/types/components/SubagentCard.d.ts +9 -0
  36. package/lib/types/components/SubagentCard.d.ts.map +1 -0
  37. package/lib/types/components/SubagentCard.js +26 -0
  38. package/lib/types/components/SubagentDashboard.d.ts +13 -0
  39. package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
  40. package/lib/types/components/SubagentDashboard.js +50 -0
  41. package/lib/types/components/SubagentDetailScene.d.ts +16 -0
  42. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
  43. package/lib/types/components/SubagentDetailScene.js +141 -0
  44. package/lib/types/components/SyntaxText.d.ts +15 -0
  45. package/lib/types/components/SyntaxText.d.ts.map +1 -0
  46. package/lib/types/components/SyntaxText.js +34 -0
  47. package/lib/types/components/ThinkingToggle.d.ts +3 -10
  48. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  49. package/lib/types/components/ThinkingToggle.js +4 -9
  50. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  51. package/lib/types/components/design-system/ListItem.js +1 -1
  52. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  53. package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
  54. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
  55. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
  57. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  58. package/lib/types/components/messages/UserPromptMessage.js +15 -2
  59. package/lib/types/dsh-adapter/channel.d.ts +53 -2
  60. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  61. package/lib/types/dsh-adapter/channel.js +294 -7
  62. package/lib/types/dsh-adapter/contract.d.ts +27 -4
  63. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/contract.js +56 -10
  65. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  66. package/lib/types/dsh-adapter/index.js +9 -3
  67. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  68. package/lib/types/dsh-adapter/plugin.js +38 -0
  69. package/lib/types/dsh-adapter/shortcuts.js +3 -3
  70. package/lib/types/dsh-adapter/subagents.d.ts +87 -0
  71. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
  72. package/lib/types/dsh-adapter/subagents.js +204 -0
  73. package/lib/types/herdr.d.ts +28 -0
  74. package/lib/types/herdr.d.ts.map +1 -0
  75. package/lib/types/herdr.js +133 -0
  76. package/lib/types/i18n.d.ts +215 -31
  77. package/lib/types/i18n.d.ts.map +1 -1
  78. package/lib/types/i18n.js +65 -14
  79. package/lib/types/ink/ink.d.ts +4 -0
  80. package/lib/types/ink/ink.d.ts.map +1 -1
  81. package/lib/types/ink/ink.js +46 -15
  82. package/lib/types/minimalMode.d.ts +13 -0
  83. package/lib/types/minimalMode.d.ts.map +1 -0
  84. package/lib/types/minimalMode.js +17 -0
  85. package/lib/types/screens/Chat.d.ts +1 -1
  86. package/lib/types/screens/Chat.d.ts.map +1 -1
  87. package/lib/types/screens/Chat.js +94 -43
  88. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  89. package/lib/types/screens/StatusLine.js +6 -2
  90. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  91. package/lib/types/screens/TrajectoryScene.js +4 -2
  92. package/lib/types/theme.d.ts +8 -0
  93. package/lib/types/theme.d.ts.map +1 -1
  94. package/lib/types/theme.js +38 -14
  95. package/lib/types/tips.js +3 -3
  96. package/lib/types/update.d.ts +18 -0
  97. package/lib/types/update.d.ts.map +1 -1
  98. package/lib/types/update.js +73 -2
  99. package/lib/types/utils/execFileNoThrow.d.ts +3 -20
  100. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  101. package/lib/types/utils/execFileNoThrow.js +53 -32
  102. package/lib/types/utils/externalEditor.d.ts +12 -11
  103. package/lib/types/utils/externalEditor.d.ts.map +1 -1
  104. package/lib/types/utils/externalEditor.js +11 -11
  105. package/package.json +64 -58
@@ -87,6 +87,10 @@ export default class Ink {
87
87
  backFrame;
88
88
  lastPoolResetTime = performance.now();
89
89
  drainTimer = null;
90
+ // Every scheduled microtask carries the generation that created it. Immediate
91
+ // renders invalidate older trailing work before it can append an old frame.
92
+ renderGeneration = 0;
93
+ pendingRenderGeneration = null;
90
94
  lastYogaCounters = {
91
95
  ms: 0,
92
96
  visited: 0,
@@ -195,8 +199,19 @@ export default class Ink {
195
199
  // effects have committed, so the native cursor tracks the caret without
196
200
  // a one-keystroke lag. Same event-loop tick, so throughput is unchanged.
197
201
  // Test env uses onImmediateRender (direct onRender, no throttle) so
198
- // existing synchronous lastFrame() tests are unaffected.
199
- const deferredRender = () => queueMicrotask(this.onRender);
202
+ // existing synchronous lastFrame() tests are unaffected. Keep a
203
+ // generation on the microtask: an immediate render may supersede the
204
+ // leading frame before its deferred callback runs.
205
+ const deferredRender = () => {
206
+ const generation = ++this.renderGeneration;
207
+ this.pendingRenderGeneration = generation;
208
+ queueMicrotask(() => {
209
+ if (this.pendingRenderGeneration !== generation || this.renderGeneration !== generation)
210
+ return;
211
+ this.pendingRenderGeneration = null;
212
+ this.onRender();
213
+ });
214
+ };
200
215
  this.scheduleRender = throttle(deferredRender, FRAME_INTERVAL_MS, {
201
216
  leading: true,
202
217
  trailing: true
@@ -220,7 +235,7 @@ export default class Ink {
220
235
  this.rootNode.focusManager = this.focusManager;
221
236
  this.renderer = createRenderer(this.rootNode, this.stylePool);
222
237
  this.rootNode.onRender = this.scheduleRender;
223
- this.rootNode.onImmediateRender = this.onRender;
238
+ this.rootNode.onImmediateRender = this.renderNow;
224
239
  this.rootNode.onComputeLayout = () => {
225
240
  // Calculate layout during React's commit phase so useLayoutEffect hooks
226
241
  // have access to fresh layout data
@@ -452,6 +467,17 @@ export default class Ink {
452
467
  return;
453
468
  this.log.requestViewportReanchor();
454
469
  }
470
+ /** Render synchronously and invalidate older trailing/drain callbacks. */
471
+ renderNow() {
472
+ this.renderGeneration++;
473
+ this.pendingRenderGeneration = null;
474
+ this.scheduleRender.cancel?.();
475
+ if (this.drainTimer !== null) {
476
+ clearTimeout(this.drainTimer);
477
+ this.drainTimer = null;
478
+ }
479
+ this.onRender();
480
+ }
455
481
  onRender() {
456
482
  if (this.isUnmounted || this.isPaused) {
457
483
  return;
@@ -689,6 +715,12 @@ export default class Ink {
689
715
  // and no move is emitted.
690
716
  const decl = this.cursorDeclaration;
691
717
  const rect = decl !== null ? nodeCache.get(decl.node) : undefined;
718
+ // Keep the declared target in the same full-frame coordinate system as
719
+ // frame.cursor and displayCursor. Main-screen cursor moves are relative:
720
+ // subtracting the scrollback height from target alone makes the physical
721
+ // cursor climb that height on every park/preamble cycle, so later streaming
722
+ // diffs overwrite thinking, tool, and assistant rows. The terminal maps the
723
+ // full-frame relative move onto its viewport/scrollback position itself.
692
724
  const target = decl !== null && rect !== undefined ? {
693
725
  x: rect.x + decl.relativeX,
694
726
  y: rect.y + decl.relativeY
@@ -781,15 +813,14 @@ export default class Ink {
781
813
  // trailing-edge throttle invocation, timerId is undefined, and lodash's
782
814
  // debounce sees timeSinceLastCall >= wait (last call was at the start
783
815
  // of this window) → leadingEdge fires IMMEDIATELY → double render ~0.1ms
784
- // apart → jank. Use a plain timeout. If a wheel event arrives first,
785
- // its scheduleRender path fires a render which clears this timer at
786
- // the top of onRender — no double.
816
+ // apart → jank. Use a plain timeout. If a wheel event or immediate
817
+ // render arrives first, renderNow cancels this timer — no double.
787
818
  //
788
819
  // Drain frames are cheap (DECSTBM + ~10 patches, ~200 bytes) so run at
789
820
  // quarter interval (~250fps, setTimeout practical floor) for max scroll
790
821
  // speed. Regular renders stay at FRAME_INTERVAL_MS via the throttle.
791
822
  if (frame.scrollDrainPending) {
792
- this.drainTimer = setTimeout(() => this.onRender(), FRAME_INTERVAL_MS >> 2);
823
+ this.drainTimer = setTimeout(this.renderNow, FRAME_INTERVAL_MS >> 2);
793
824
  }
794
825
  const yogaMs = getLastYogaMs();
795
826
  const commitMs = getLastCommitMs();
@@ -825,12 +856,12 @@ export default class Ink {
825
856
  // Flush pending React updates and render before pausing.
826
857
  // @ts-ignore -- ported CC build; type drift tolerated flushSyncFromReconciler exists in react-reconciler 0.31 but not in @types/react-reconciler
827
858
  reconciler.flushSyncFromReconciler();
828
- this.onRender();
859
+ this.renderNow();
829
860
  this.isPaused = true;
830
861
  }
831
862
  resume() {
832
863
  this.isPaused = false;
833
- this.onRender();
864
+ this.renderNow();
834
865
  }
835
866
  /**
836
867
  * Reset frame buffers so the next render writes the full screen from scratch.
@@ -872,7 +903,7 @@ export default class Ink {
872
903
  // diff sees no content. onRender resets the flag at frame end.
873
904
  this.prevFrameContaminated = true;
874
905
  }
875
- this.onRender();
906
+ this.renderNow();
876
907
  }
877
908
  /**
878
909
  * Establish a genuinely fresh terminal page: clear both the visible screen
@@ -895,7 +926,7 @@ export default class Ink {
895
926
  this.repaint();
896
927
  this.prevFrameContaminated = true;
897
928
  }
898
- this.onRender();
929
+ this.renderNow();
899
930
  }
900
931
  /**
901
932
  * Mark the previous frame as untrustworthy for blit, forcing the next
@@ -1004,7 +1035,7 @@ export default class Ink {
1004
1035
  // cursor position. Idempotent when nothing drifted — the user sees
1005
1036
  // no change, at O(viewport) bytes once per >5s idle gap.
1006
1037
  this.log.requestViewportReanchor();
1007
- this.onRender();
1038
+ this.renderNow();
1008
1039
  return;
1009
1040
  }
1010
1041
  // Mouse tracking — idempotent, safe to re-assert on every stdin gap.
@@ -1171,7 +1202,7 @@ export default class Ink {
1171
1202
  // drops it silently unless allow-passthrough is on — no regression).
1172
1203
  void setClipboard(text).then(raw => {
1173
1204
  if (raw)
1174
- this.options.stdout.write(raw);
1205
+ this.writeRaw(raw);
1175
1206
  });
1176
1207
  }
1177
1208
  return text;
@@ -1388,7 +1419,7 @@ export default class Ink {
1388
1419
  return () => this.selectionListeners.delete(cb);
1389
1420
  }
1390
1421
  notifySelectionChange() {
1391
- this.onRender();
1422
+ this.renderNow();
1392
1423
  for (const cb of this.selectionListeners)
1393
1424
  cb();
1394
1425
  }
@@ -1596,7 +1627,7 @@ export default class Ink {
1596
1627
  if (this.isUnmounted) {
1597
1628
  return;
1598
1629
  }
1599
- this.onRender();
1630
+ this.renderNow();
1600
1631
  this.unsubscribeExit();
1601
1632
  if (typeof this.restoreConsole === 'function') {
1602
1633
  this.restoreConsole();
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Minimal mode: one process-wide flag gating decorative UI (header splash,
3
+ * emoji status glyphs, non-tool text colors, status-bar extras). The flag is
4
+ * owned by the channel (settings `dsh-tui.minimal`, applied live through
5
+ * scope.watch); leaf components read it here instead of threading a prop
6
+ * through four render layers. Code highlighting and tool-name colors are
7
+ * deliberately NOT gated — minimal keeps those.
8
+ */
9
+ /** True when minimal mode is active (settings `dsh-tui.minimal`). */
10
+ export declare function isMinimalMode(): boolean;
11
+ /** @internal channel-owned setter; also called once at boot. */
12
+ export declare function setMinimalMode(enabled: boolean): void;
13
+ //# sourceMappingURL=minimalMode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"minimalMode.d.ts","sourceRoot":"","sources":["../../src/minimalMode.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,qEAAqE;AACrE,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAErD"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Minimal mode: one process-wide flag gating decorative UI (header splash,
3
+ * emoji status glyphs, non-tool text colors, status-bar extras). The flag is
4
+ * owned by the channel (settings `dsh-tui.minimal`, applied live through
5
+ * scope.watch); leaf components read it here instead of threading a prop
6
+ * through four render layers. Code highlighting and tool-name colors are
7
+ * deliberately NOT gated — minimal keeps those.
8
+ */
9
+ let minimal = false;
10
+ /** True when minimal mode is active (settings `dsh-tui.minimal`). */
11
+ export function isMinimalMode() {
12
+ return minimal;
13
+ }
14
+ /** @internal channel-owned setter; also called once at boot. */
15
+ export function setMinimalMode(enabled) {
16
+ minimal = enabled;
17
+ }
@@ -44,5 +44,5 @@ export declare function Chat({ channel, questionStore, approvalStore, extensionD
44
44
  * persisted flag when the host does not supply one.
45
45
  */
46
46
  trajectorySeen?: boolean;
47
- }): React.JSX.Element;
47
+ }): React.JSX.Element | null;
48
48
  //# sourceMappingURL=Chat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/screens/Chat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB,OAAO,EAAqB,KAAK,OAAO,EAAsE,MAAM,2BAA2B,CAAA;AAC/I,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAyI3D,wBAAgB,IAAI,CAAC,EACnB,OAAO,EACP,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,MAAM,EACN,QAAQ,EACR,UAAkB,EAClB,cAAc,EAAE,kBAAkB,GACnC,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,EAAE,aAAa,CAAA;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAA;IACjC,oEAAoE;IACpE,eAAe,CAAC,EAAE,cAAc,CAAA;IAChC,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,eAAe,CAAA;IACpC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,qBAioEA"}
1
+ {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/screens/Chat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB,OAAO,EAAqB,KAAK,OAAO,EAAsE,MAAM,2BAA2B,CAAA;AAC/I,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AA4I3D,wBAAgB,IAAI,CAAC,EACnB,OAAO,EACP,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,MAAM,EACN,QAAQ,EACR,UAAkB,EAClB,cAAc,EAAE,kBAAkB,GACnC,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,EAAE,aAAa,CAAA;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAA;IACjC,oEAAoE;IACpE,eAAe,CAAC,EAAE,cAAc,CAAA;IAChC,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,eAAe,CAAA;IACpC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,4BAisEA"}
@@ -47,7 +47,10 @@ import { HistorySearchDialog } from '../components/HistorySearchDialog.js';
47
47
  import { RewindPicker } from '../components/RewindPicker.js';
48
48
  import { BtwPanel } from '../components/BtwPanel.js';
49
49
  import { TipsPanel } from '../components/TipsPanel.js';
50
+ import { SubagentDashboard } from '../components/SubagentDashboard.js';
51
+ import { SubagentDetailScene } from '../components/SubagentDetailScene.js';
50
52
  import { setClipboard } from '../ink/termio/osc.js';
53
+ import { TerminalWriteContext } from '../ink/useTerminalNotification.js';
51
54
  import instances from '../ink/instances.js';
52
55
  import { useAnimationFrame } from '../ink/hooks/use-animation-frame.js';
53
56
  import { TrajectoryScene } from './TrajectoryScene.js';
@@ -135,6 +138,7 @@ let fallbackApprovalStore;
135
138
  let fallbackDialogStore;
136
139
  let fallbackStatusStore;
137
140
  export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, extensionShortcuts, onExit, onUpdate, fullscreen = false, trajectorySeen: trajectorySeenProp, }) {
141
+ const writeRaw = React.useContext(TerminalWriteContext);
138
142
  // Re-render whenever the channel mutates; rows/status are read fresh below.
139
143
  React.useSyncExternalStore(channel.subscribe, () => channel.version);
140
144
  // Re-render on language switches so the whole UI hot-swaps its strings.
@@ -230,8 +234,6 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
230
234
  const [thinkingVisible, setThinkingVisible] = React.useState(true);
231
235
  const [thinkingOpen, setThinkingOpen] = React.useState(false);
232
236
  const [thinkingFocus, setThinkingFocus] = React.useState(0);
233
- /** Mid-conversation toggle waiting for Enter confirmation (CC semantics). */
234
- const [thinkingConfirm, setThinkingConfirm] = React.useState(null);
235
237
  /** ctrl+r history search dialog (ported from CC's HistorySearchDialog). */
236
238
  const [historyOpen, setHistoryOpen] = React.useState(false);
237
239
  const [historyQuery, setHistoryQuery] = React.useState('');
@@ -265,6 +267,15 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
265
267
  };
266
268
  /** /tips usage-tips overlay: pure UI state, no session side effects. */
267
269
  const [tipsOpen, setTipsOpen] = React.useState(false);
270
+ /** Subagent dashboard (Ctrl+A): displays active/completed subagents. */
271
+ const [subagentDashboardOpen, setSubagentDashboardOpen] = React.useState(false);
272
+ /** Detail view for a specific subagent (opened from dashboard). */
273
+ const [subagentDetailId, setSubagentDetailId] = React.useState(null);
274
+ /**
275
+ * Hidden `/deepseek` easter egg: each invocation bumps this key so the
276
+ * logo header remounts and replays the whale spout + text shimmer.
277
+ */
278
+ const [logoNonce, setLogoNonce] = React.useState(0);
268
279
  React.useEffect(() => () => btwAbortRef.current?.abort(), []);
269
280
  /**
270
281
  * The trajectory scene (issue #80 evolution). Unlike every other overlay
@@ -1063,6 +1074,18 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1063
1074
  });
1064
1075
  return true;
1065
1076
  }
1077
+ case 'deepseek': {
1078
+ // Hidden easter egg: replay the logo header's whale spout + text
1079
+ // shimmer. The command is intentionally not in the suggestion/help
1080
+ // catalogs; PromptInput recognizes it through HIDDEN_COMMAND_NAMES.
1081
+ setHelpOpen(false);
1082
+ setLogoNonce(n => n + 1);
1083
+ // Bring the logo back into view if the transcript has scrolled.
1084
+ setTimeout(() => {
1085
+ handle?.scrollTo(0);
1086
+ }, 0);
1087
+ return true;
1088
+ }
1066
1089
  case 'tips':
1067
1090
  setHelpOpen(false);
1068
1091
  setTipsOpen(true);
@@ -1353,12 +1376,35 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1353
1376
  // the field draft editor belong to it alone.
1354
1377
  if (settingsOpen)
1355
1378
  return;
1379
+ // Subagent dashboard or detail scene: it owns the keyboard while open.
1380
+ if (subagentDashboardOpen || subagentDetailId !== null)
1381
+ return;
1356
1382
  // A plugin scene (dsh-tui-scenes) or the trajectory scene owns the whole
1357
1383
  // screen while open: every key belongs to it. Unguarded, an Esc meant to
1358
1384
  // CLOSE the scene also reached the chat:cancel branch below whenever a
1359
1385
  // turn was in flight — closing the view and killing the turn in one key.
1360
1386
  if (sceneOpen || channel.pluginScene !== undefined)
1361
1387
  return;
1388
+ // Mouse wheel scrolls the transcript even while a question/approval/
1389
+ // dialog panel is open — those panels own arrow/Enter/Esc keys, but the
1390
+ // transcript above them should still be scrollable in fullscreen mode.
1391
+ // Help is different: its own ScrollBox owns wheel input, so Chat must
1392
+ // yield before stopping propagation or both covered layers would move.
1393
+ // Events only arrive with mouse tracking on; inline mode never sees
1394
+ // them, so this is a no-op there.
1395
+ if (key.wheelUp || key.wheelDown) {
1396
+ if (helpOpen)
1397
+ return;
1398
+ handle?.scrollBy(key.wheelUp ? -3 : 3);
1399
+ event.stopImmediatePropagation();
1400
+ return;
1401
+ }
1402
+ // Help is modal over Chat. Chat's listener registers before PromptInput's,
1403
+ // so yield every remaining key before any global/custom shortcut, search,
1404
+ // selection, or working-turn cancellation branch can mutate hidden state.
1405
+ // PromptInput then owns Esc, navigation, Tab guards, and ordinary typing.
1406
+ if (helpOpen)
1407
+ return;
1362
1408
  // The questionnaire / approval panel / managed plugin dialog owns the
1363
1409
  // keyboard while one is pending (the panel's own useInput handles
1364
1410
  // ↑/↓/Space/Tab/Enter/Esc; the prompt input is unmounted, so nothing
@@ -1370,16 +1416,6 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1370
1416
  const plainReturn = returnCandidate && returnNow - lastModalEnterAtRef.current >= 80;
1371
1417
  if (plainReturn)
1372
1418
  lastModalEnterAtRef.current = returnNow;
1373
- // Mouse wheel scrolls the transcript — in fullscreen there is no
1374
- // terminal scrollback (alt-screen), so this is the only way back.
1375
- // Imperative scrollBy: no React re-render per notch (CC semantics).
1376
- // Events only arrive with mouse tracking on; inline mode never sees
1377
- // them, so this is a no-op there.
1378
- if (key.wheelUp || key.wheelDown) {
1379
- handle?.scrollBy(key.wheelUp ? -3 : 3);
1380
- event.stopImmediatePropagation();
1381
- return;
1382
- }
1383
1419
  // Esc clears a settled mouse selection first (CC precedence), ahead of
1384
1420
  // every other Esc meaning below (close pickers, interrupt the turn).
1385
1421
  // hasSelection() is an imperative read — no subscription needed.
@@ -1447,33 +1483,14 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1447
1483
  return;
1448
1484
  }
1449
1485
  if (thinkingOpen) {
1450
- if (thinkingConfirm !== null) {
1451
- // Confirmation state: Enter applies, Esc backs out to the select.
1452
- if (plainReturn) {
1453
- const enabled = thinkingConfirm;
1454
- setThinkingVisible(enabled);
1455
- setThinkingConfirm(null);
1456
- setThinkingOpen(false);
1457
- channel.notify(t('thinking-toggled', { state: enabled ? t('thinking-on') : t('thinking-off') }));
1458
- }
1459
- else if (key.escape) {
1460
- setThinkingConfirm(null);
1461
- }
1462
- }
1463
- else if (key.upArrow || key.downArrow) {
1486
+ if (key.upArrow || key.downArrow) {
1464
1487
  setThinkingFocus(index => (index === 0 ? 1 : 0));
1465
1488
  }
1466
1489
  else if (plainReturn) {
1467
- const enabled = thinkingFocus === 0;
1468
- const midConversation = channel.rows.some(row => row.kind === 'assistant');
1469
- if (midConversation && enabled !== thinkingVisible) {
1470
- setThinkingConfirm(enabled);
1471
- }
1472
- else {
1473
- setThinkingVisible(enabled);
1474
- setThinkingOpen(false);
1475
- channel.notify(t('thinking-toggled', { state: enabled ? t('thinking-on') : t('thinking-off') }));
1476
- }
1490
+ const visible = thinkingFocus === 0;
1491
+ setThinkingVisible(visible);
1492
+ setThinkingOpen(false);
1493
+ channel.notify(t('thinking-toggled', { state: visible ? t('thinking-on') : t('thinking-off') }));
1477
1494
  }
1478
1495
  else if (key.escape) {
1479
1496
  setThinkingOpen(false);
@@ -1850,6 +1867,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1850
1867
  openScene();
1851
1868
  return;
1852
1869
  }
1870
+ if (isMod(key) && input === 'a') {
1871
+ // Ctrl+A opens the subagent dashboard.
1872
+ setSubagentDashboardOpen(true);
1873
+ return;
1874
+ }
1853
1875
  if (isMod(key) && input === 'p' && loadedContextVisible) {
1854
1876
  // Ctrl+P toggles the startup loaded-context panel while it is on
1855
1877
  // screen (transcript still empty); once rows take over and the
@@ -1865,7 +1887,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1865
1887
  setHistoryOpen(true);
1866
1888
  return;
1867
1889
  }
1868
- if (key.shift && key.upArrow && !selectionActive) {
1890
+ if (key.shift && key.upArrow && !selectionActive && !helpOpen) {
1869
1891
  enterSelection();
1870
1892
  }
1871
1893
  else if (selectionActive) {
@@ -1883,7 +1905,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1883
1905
  setSelectedId(null);
1884
1906
  }
1885
1907
  }
1886
- else if (key.escape && channel.working) {
1908
+ else if (key.escape && channel.working && !helpOpen) {
1887
1909
  // CC's chat:cancel — esc interrupts a running turn (the prompt input
1888
1910
  // only sees esc when idle, where it has the double-tap-clear meaning).
1889
1911
  // With messages queued for delivery, interrupt-and-deliver them right
@@ -1899,8 +1921,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1899
1921
  }
1900
1922
  event.stopImmediatePropagation();
1901
1923
  }
1902
- else if (isMod(key) && input === 'o') {
1924
+ else if (isMod(key) && input === 'o' && !helpOpen) {
1903
1925
  // Leaving transcript mode (Ctrl+O) — search was already handled above.
1926
+ // Help is modal: toggling this state behind the overlay is invisible,
1927
+ // then the next `/` unexpectedly opens transcript search instead of
1928
+ // slash-command completion after Help closes.
1904
1929
  setExpanded(previous => !previous);
1905
1930
  // The toggle rewrites every thinking row's layout at once. The
1906
1931
  // ordinary scroll-based diff pushes rows into terminal scrollback on
@@ -1913,7 +1938,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1913
1938
  const ink = instances.get(process.stdout) ?? instances.values().next().value;
1914
1939
  ink?.reanchorViewport();
1915
1940
  }
1916
- else if (input === '/' && !key.ctrl && !key.meta && !key.super) {
1941
+ else if (input === '/' && !key.ctrl && !key.meta && !key.super && !helpOpen) {
1917
1942
  // `/` in transcript mode (Ctrl+O expanded, CC's REPL semantics:
1918
1943
  // search is active on the transcript screen where `/` isn't a command).
1919
1944
  if (expanded) {
@@ -2011,6 +2036,31 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2011
2036
  const screen = _jsx(Settings, { channel: channel, onClose: () => setSettingsOpen(false) });
2012
2037
  return fullscreen ? screen : _jsx(AlternateScreen, { children: screen });
2013
2038
  }
2039
+ // Subagent detail scene: displays detailed view of a specific subagent.
2040
+ // Like the browser and settings, it replaces the conversation entirely.
2041
+ if (subagentDetailId !== null) {
2042
+ const subagent = channel.subagents.find(s => s.agentId === subagentDetailId);
2043
+ if (!subagent) {
2044
+ // Agent not found, go back to dashboard
2045
+ setSubagentDetailId(null);
2046
+ setSubagentDashboardOpen(true);
2047
+ return null;
2048
+ }
2049
+ const scene = (_jsx(SubagentDetailScene, { subagent: subagent, onInterrupt: (id) => channel.subagentControl.interrupt(id), onBack: () => {
2050
+ setSubagentDetailId(null);
2051
+ setSubagentDashboardOpen(true);
2052
+ } }));
2053
+ return fullscreen ? scene : _jsx(AlternateScreen, { children: scene });
2054
+ }
2055
+ // Subagent dashboard: displays all active and completed subagents.
2056
+ // Like the browser and settings, it replaces the conversation entirely.
2057
+ if (subagentDashboardOpen) {
2058
+ const dashboard = (_jsx(SubagentDashboard, { subagents: [...channel.subagents], onSelect: (id) => {
2059
+ setSubagentDashboardOpen(false);
2060
+ setSubagentDetailId(id);
2061
+ }, onClose: () => setSubagentDashboardOpen(false) }));
2062
+ return fullscreen ? dashboard : _jsx(AlternateScreen, { children: dashboard });
2063
+ }
2014
2064
  /** Prompt input is inert while a modal dialog owns the keyboard. */
2015
2065
  const promptSelectionActive = selectionActive || modelPickerOpen || skillsPickerOpen || workspacePickerOpen || workspaceFlow !== null || activityPickerOpen ||
2016
2066
  effortSliderOpen || presetPickerOpen || themePickerOpen || thinkingOpen || historyOpen || rewindOpen || searchOpen ||
@@ -2044,8 +2094,9 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2044
2094
  seekRow(lastUser.id);
2045
2095
  else
2046
2096
  handle?.scrollToBottom();
2047
- } })), _jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd }), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: toggleLoadedContext })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, showAll: showAllMessages, thinkingVisible: thinkingVisible, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
2097
+ } })), _jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd, whale: channel.whale }, logoNonce), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: toggleLoadedContext })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, activityFrames: channel.activityFrames, showAll: showAllMessages, thinkingVisible: thinkingVisible, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
2048
2098
  (channel.activityEnabled &&
2099
+ !channel.minimal &&
2049
2100
  channel.workingActivity !== undefined &&
2050
2101
  channel.workingActivity.line !== '' &&
2051
2102
  channel.workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: channel.workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
@@ -2055,7 +2106,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2055
2106
  // inflating the reading next to a real upload number).
2056
2107
  suffix: `${lastUploadTokens > 0 ? ` · ↑ ${formatTokens(lastUploadTokens)}` : ''} · ↓ ${formatTokens(Math.round(channel.responseChars / 4))} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), _jsx(GoalTodoPanel, { channel: channel }), statusEntries.length > 0 && (_jsx(Text, { dimColor: true, wrap: "truncate", children: statusEntries.map(entry => entry.text).join(' · ') })), approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : dialogSnapshot !== null ? (_jsx(ExtensionDialog, { dialog: dialogSnapshot, onDecide: value => dialogs.decide(dialogSnapshot.key, value), onCancel: () => dialogs.cancel(dialogSnapshot.key) }, dialogSnapshot.key)) : tipsOpen ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(TipsPanel, { onClose: () => setTipsOpen(false) }) })) : btw !== null ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(BtwPanel, { question: btw.question, answer: btw.answer, error: btw.error, streaming: !btw.done, onClose: closeBtw, onCopy: () => {
2057
2108
  void setClipboard(btw.answer ?? '').then(raw => { if (raw)
2058
- process.stdout.write(raw); });
2109
+ writeRaw?.(raw); });
2059
2110
  channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
2060
2111
  } }) })) : questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent() }, questionSnapshot.key)) : (_jsx(PromptInput, { channel: channel, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => { setHistoryFill(null); }, onRewindRequest: openRewind, controllerRef: promptControllerRef })), _jsx(StatusLine, { channel: channel, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
2061
2112
  ? undefined
@@ -2063,7 +2114,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2063
2114
  band: wakeBand,
2064
2115
  hint: trajectorySeen ? undefined : `${modLabel}t`,
2065
2116
  tick: Math.floor(wakeTime / 120),
2066
- } }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 8), children: [thinkingOpen && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: thinkingFocus, confirmationPending: thinkingConfirm })), workspacePickerOpen && workspaceTargets.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspacePicker, { targets: workspaceTargets, focusIndex: workspaceIndex, currentCwd: channel.cwd }) })), workspaceFlow !== null && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceFlowPicker, { title: workspaceFlow.title, choices: workspaceFlow.choices, focusIndex: workspaceFlowIndex, busy: workspaceFlowBusy, input: workspaceFlowInput }) })), modelPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : (_jsx(ModelPicker, { models: models, focusIndex: modelIndex, currentModel: `${channel.provider}/${channel.model}` })) })), skillsPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: skillsList === null ? (_jsx(SkillsPickerLoading, {})) : (_jsx(SkillsPicker, { skills: skillsList, focusIndex: skillsIndex })) })), activityPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: activityIndex, currentPreset: channel.activityFrames }) })), effortSliderOpen && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: effortIndex, currentId: channel.reasoningEffort }) })), presetPickerOpen && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: presetIndex, currentPreset: channel.agentPreset }) })), themePickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: themeIndex, currentTheme: themeName }) })), historyOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: historyQuery, cursorOffset: historyCursor, matches: historyMatches, focusIndex: historyFocus }) })), rewindOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: rewindIndex, confirmRow: rewindConfirm, modes: rewindModes, modeIndex: rewindModeIndex, busy: rewindBusy }) })), searchOpen && _jsx(TranscriptSearchBar, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }));
2117
+ } }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 8), children: [thinkingOpen && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: thinkingFocus })), workspacePickerOpen && workspaceTargets.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspacePicker, { targets: workspaceTargets, focusIndex: workspaceIndex, currentCwd: channel.cwd }) })), workspaceFlow !== null && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceFlowPicker, { title: workspaceFlow.title, choices: workspaceFlow.choices, focusIndex: workspaceFlowIndex, busy: workspaceFlowBusy, input: workspaceFlowInput }) })), modelPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : (_jsx(ModelPicker, { models: models, focusIndex: modelIndex, currentModel: `${channel.provider}/${channel.model}` })) })), skillsPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: skillsList === null ? (_jsx(SkillsPickerLoading, {})) : (_jsx(SkillsPicker, { skills: skillsList, focusIndex: skillsIndex })) })), activityPickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: activityIndex, currentPreset: channel.activityFrames }) })), effortSliderOpen && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: effortIndex, currentId: channel.reasoningEffort }) })), presetPickerOpen && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: presetIndex, currentPreset: channel.agentPreset }) })), themePickerOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: themeIndex, currentTheme: themeName }) })), historyOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: historyQuery, cursorOffset: historyCursor, matches: historyMatches, focusIndex: historyFocus }) })), rewindOpen && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: rewindIndex, confirmRow: rewindConfirm, modes: rewindModes, modeIndex: rewindModeIndex, busy: rewindBusy }) })), searchOpen && _jsx(TranscriptSearchBar, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }));
2067
2118
  }
2068
2119
  /**
2069
2120
  * The pinned prompt header shown above the ScrollBox while the user has
@@ -1 +1 @@
1
- {"version":3,"file":"StatusLine.d.ts","sourceRoot":"","sources":["../../../src/screens/StatusLine.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAQvD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,eAAuB,EACvB,QAAgB,EAChB,IAAI,GACL,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACvD,qBAsPA;AAED,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKvF"}
1
+ {"version":3,"file":"StatusLine.d.ts","sourceRoot":"","sources":["../../../src/screens/StatusLine.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAQvD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,eAAuB,EACvB,QAAgB,EAChB,IAAI,GACL,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACvD,qBA0PA;AAED,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKvF"}
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text, useTerminalSize, useTheme } from '../ui.js';
3
3
  import { formatTokens } from '../cc/format.js';
4
4
  import { t } from '../i18n.js';
5
- import { formatContextUsage, normalizeStatusBar } from '../tuiDisplayPrefs.js';
5
+ import { formatContextUsage, DEFAULT_STATUS_BAR, normalizeStatusBar } from '../tuiDisplayPrefs.js';
6
6
  import { Byline } from '../components/design-system/Byline.js';
7
7
  import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
8
8
  import { modeDisplayName } from '../sessionModes.js';
@@ -19,7 +19,11 @@ import { renderContextBar, renderTpsGauge, renderTpsSparkline, speedColor, } fro
19
19
  export function StatusLine({ channel, selectionActive = false, helpOpen = false, wake, }) {
20
20
  const { columns } = useTerminalSize();
21
21
  const [themeName] = useTheme();
22
- const statusBar = normalizeStatusBar(channel.statusBar);
22
+ const statusBar = channel.minimal
23
+ // Minimal mode overrides every field switch: model + cwd only, so the
24
+ // footer can never grow decorations regardless of saved preferences.
25
+ ? { ...DEFAULT_STATUS_BAR, compact: true, model: true, cwd: true }
26
+ : normalizeStatusBar(channel.statusBar);
23
27
  const usage = channel.lastUsage;
24
28
  const contextUsed = usage === undefined
25
29
  ? undefined
@@ -1 +1 @@
1
- {"version":3,"file":"TrajectoryScene.d.ts","sourceRoot":"","sources":["../../../src/screens/TrajectoryScene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAczB,OAAO,EAML,KAAK,SAAS,EACf,MAAM,oCAAoC,CAAA;AAE3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AA+BxD,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,SAAS,CAAA;AAEnD,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,KAAK,EACL,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB,sDAAsD;IACtD,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CAuYlB"}
1
+ {"version":3,"file":"TrajectoryScene.d.ts","sourceRoot":"","sources":["../../../src/screens/TrajectoryScene.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAczB,OAAO,EAML,KAAK,SAAS,EACf,MAAM,oCAAoC,CAAA;AAE3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AA+BxD,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,SAAS,CAAA;AAEnD,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,KAAK,EACL,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,KAAK,EAAE,SAAS,CAAA;IAChB,sDAAsD;IACtD,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CAyYlB"}
@@ -227,12 +227,14 @@ export function TrajectoryScene({ channel, build, onClose, }) {
227
227
  return move(-ledgerRows);
228
228
  if (key.pageDown)
229
229
  return move(ledgerRows);
230
- if (input === 'g') {
230
+ // Bare-letter jumps must not fire on Ctrl+G (the prompt's external-editor
231
+ // key) or other modified chords that share the letter.
232
+ if (input === 'g' && !key.ctrl && !key.meta && !key.super) {
231
233
  setCursor(0);
232
234
  setFollow(false);
233
235
  return;
234
236
  }
235
- if (input === 'G') {
237
+ if (input === 'G' && !key.ctrl && !key.meta && !key.super) {
236
238
  setCursor(Math.max(0, filtered.length - 1));
237
239
  setFollow(true);
238
240
  return;
@@ -104,6 +104,14 @@ export type Theme = {
104
104
  rainbow_blue_shimmer: string;
105
105
  rainbow_indigo_shimmer: string;
106
106
  rainbow_violet_shimmer: string;
107
+ subagentBullet: string;
108
+ subagentDescription: string;
109
+ subagentModel: string;
110
+ subagentElapsed: string;
111
+ subagentToolName: string;
112
+ subagentStatusRunning: string;
113
+ subagentStatusCompleted: string;
114
+ subagentStatusFailed: string;
107
115
  };
108
116
  /** The built-in theme names, in display order. */
109
117
  export declare const THEME_NAMES: readonly ["dark", "dark-ansi", "light"];
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/theme.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/theme.ts"],"names":[],"mappings":"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"}