@wrongstack/tui 0.296.4 → 0.298.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 (301) hide show
  1. package/dist/app-action-type.d.ts +12 -1
  2. package/dist/app-props.d.ts +11 -1
  3. package/dist/app-settings-type.d.ts +5 -1
  4. package/dist/app-state.d.ts +4 -0
  5. package/dist/components/auth-panel.d.ts +4 -0
  6. package/dist/components/confirm-prompt.d.ts +14 -0
  7. package/dist/components/history/entry.d.ts +7 -1
  8. package/dist/components/history/index.d.ts +1 -1
  9. package/dist/components/history/tool-group.d.ts +1 -1
  10. package/dist/components/history/types.d.ts +6 -0
  11. package/dist/components/scrollable-history.d.ts +201 -2
  12. package/dist/components/settings-picker-model.d.ts +17 -1
  13. package/dist/components/settings-picker.d.ts +5 -1
  14. package/dist/components/status-bar-types.d.ts +31 -5
  15. package/dist/components/status-bar.d.ts +1 -1
  16. package/dist/heap-watchdog.d.ts +6 -42
  17. package/dist/history-entry.d.ts +9 -0
  18. package/dist/hooks/use-statusbar-view-model.d.ts +1 -0
  19. package/dist/hooks/use-tui-slash-commands.d.ts +1 -1
  20. package/dist/index.js +853 -360
  21. package/dist/markdown.d.ts +2 -0
  22. package/dist/run-tui-options.d.ts +10 -1
  23. package/dist/settings-contracts.d.ts +4 -0
  24. package/dist/token-previews.d.ts +3 -2
  25. package/dist/tui-memory-counters.d.ts +38 -0
  26. package/package.json +10 -8
  27. package/dist/app-action-type.d.ts.map +0 -1
  28. package/dist/app-initial-state.d.ts.map +0 -1
  29. package/dist/app-key-handler.d.ts.map +0 -1
  30. package/dist/app-props.d.ts.map +0 -1
  31. package/dist/app-reducer.d.ts.map +0 -1
  32. package/dist/app-settings-type.d.ts.map +0 -1
  33. package/dist/app-state-core-types.d.ts.map +0 -1
  34. package/dist/app-state-fleet.d.ts.map +0 -1
  35. package/dist/app-state.d.ts.map +0 -1
  36. package/dist/app-status-region.d.ts.map +0 -1
  37. package/dist/app-ui-state.d.ts.map +0 -1
  38. package/dist/app-view-contract.d.ts.map +0 -1
  39. package/dist/app-view-state.d.ts.map +0 -1
  40. package/dist/app-view.d.ts.map +0 -1
  41. package/dist/app.d.ts.map +0 -1
  42. package/dist/auth-panel-model.d.ts.map +0 -1
  43. package/dist/brain-contracts.d.ts.map +0 -1
  44. package/dist/brain-panel-model.d.ts.map +0 -1
  45. package/dist/checkpoint-retention.d.ts.map +0 -1
  46. package/dist/clipboard.d.ts.map +0 -1
  47. package/dist/components/agents-monitor-constants.d.ts.map +0 -1
  48. package/dist/components/agents-monitor.d.ts.map +0 -1
  49. package/dist/components/animation-style.d.ts.map +0 -1
  50. package/dist/components/audit-panel.d.ts.map +0 -1
  51. package/dist/components/auth-panel-model.d.ts.map +0 -1
  52. package/dist/components/auth-panel.d.ts.map +0 -1
  53. package/dist/components/autonomy-picker.d.ts.map +0 -1
  54. package/dist/components/brain-decision-prompt.d.ts.map +0 -1
  55. package/dist/components/brain-panel-model.d.ts.map +0 -1
  56. package/dist/components/brain-panel.d.ts.map +0 -1
  57. package/dist/components/checkpoint-timeline.d.ts.map +0 -1
  58. package/dist/components/clear-confirm-panel.d.ts.map +0 -1
  59. package/dist/components/composer-status-chip.d.ts.map +0 -1
  60. package/dist/components/confirm-prompt.d.ts.map +0 -1
  61. package/dist/components/connections-panel.d.ts.map +0 -1
  62. package/dist/components/context-panel.d.ts.map +0 -1
  63. package/dist/components/continue-confirm-panel.d.ts.map +0 -1
  64. package/dist/components/coordinator-panel.d.ts.map +0 -1
  65. package/dist/components/cron-jobs.d.ts.map +0 -1
  66. package/dist/components/design-picker.d.ts.map +0 -1
  67. package/dist/components/enhance-panel.d.ts.map +0 -1
  68. package/dist/components/entry-error-boundary.d.ts.map +0 -1
  69. package/dist/components/esc-confirm-prompt.d.ts.map +0 -1
  70. package/dist/components/exit-confirm-panel.d.ts.map +0 -1
  71. package/dist/components/f-key-picker.d.ts.map +0 -1
  72. package/dist/components/file-picker.d.ts.map +0 -1
  73. package/dist/components/fleet-monitor.d.ts.map +0 -1
  74. package/dist/components/fleet-panel.d.ts.map +0 -1
  75. package/dist/components/goal-kanban-panel.d.ts.map +0 -1
  76. package/dist/components/goal-panel.d.ts.map +0 -1
  77. package/dist/components/help-overlay.d.ts.map +0 -1
  78. package/dist/components/help-panel.d.ts.map +0 -1
  79. package/dist/components/history/assistant.d.ts.map +0 -1
  80. package/dist/components/history/banner.d.ts.map +0 -1
  81. package/dist/components/history/basic-format.d.ts.map +0 -1
  82. package/dist/components/history/code-block-diff-helpers.d.ts.map +0 -1
  83. package/dist/components/history/code-block.d.ts.map +0 -1
  84. package/dist/components/history/copy-icon.d.ts.map +0 -1
  85. package/dist/components/history/entry.d.ts.map +0 -1
  86. package/dist/components/history/index.d.ts.map +0 -1
  87. package/dist/components/history/replay.d.ts.map +0 -1
  88. package/dist/components/history/sage-output-format.d.ts.map +0 -1
  89. package/dist/components/history/stream-box.d.ts.map +0 -1
  90. package/dist/components/history/tool-arg-format.d.ts.map +0 -1
  91. package/dist/components/history/tool-card.d.ts.map +0 -1
  92. package/dist/components/history/tool-group.d.ts.map +0 -1
  93. package/dist/components/history/tool-output-summary.d.ts.map +0 -1
  94. package/dist/components/history/tool-visual-command.d.ts.map +0 -1
  95. package/dist/components/history/tool-visual-domain.d.ts.map +0 -1
  96. package/dist/components/history/tool-visual-format.d.ts.map +0 -1
  97. package/dist/components/history/tool-visual-lsp.d.ts.map +0 -1
  98. package/dist/components/history/tool-visual-misc.d.ts.map +0 -1
  99. package/dist/components/history/tool-visual-mode-workdir.d.ts.map +0 -1
  100. package/dist/components/history/tool-visual-types.d.ts.map +0 -1
  101. package/dist/components/history/types.d.ts.map +0 -1
  102. package/dist/components/history/utils.d.ts.map +0 -1
  103. package/dist/components/history/visual-lines.d.ts.map +0 -1
  104. package/dist/components/history/visual-memory-logs.d.ts.map +0 -1
  105. package/dist/components/history.d.ts.map +0 -1
  106. package/dist/components/input.d.ts.map +0 -1
  107. package/dist/components/kanban-panel.d.ts.map +0 -1
  108. package/dist/components/key-hint-bar.d.ts.map +0 -1
  109. package/dist/components/live-activity-strip.d.ts.map +0 -1
  110. package/dist/components/mailbox-panel.d.ts.map +0 -1
  111. package/dist/components/mcp-picker.d.ts.map +0 -1
  112. package/dist/components/memory-context-widget.d.ts.map +0 -1
  113. package/dist/components/mode-picker.d.ts.map +0 -1
  114. package/dist/components/model-picker.d.ts.map +0 -1
  115. package/dist/components/monitor-shell.d.ts.map +0 -1
  116. package/dist/components/phase-monitor.d.ts.map +0 -1
  117. package/dist/components/phase-panel.d.ts.map +0 -1
  118. package/dist/components/plan-panel.d.ts.map +0 -1
  119. package/dist/components/plugin-picker.d.ts.map +0 -1
  120. package/dist/components/powerline-rail.d.ts.map +0 -1
  121. package/dist/components/process-list.d.ts.map +0 -1
  122. package/dist/components/project-picker.d.ts.map +0 -1
  123. package/dist/components/prompt-picker.d.ts.map +0 -1
  124. package/dist/components/provider-colors.d.ts.map +0 -1
  125. package/dist/components/queue-panel.d.ts.map +0 -1
  126. package/dist/components/refine-countdown-panel.d.ts.map +0 -1
  127. package/dist/components/refine-failure-panel.d.ts.map +0 -1
  128. package/dist/components/resume-picker.d.ts.map +0 -1
  129. package/dist/components/scrollable-history.d.ts.map +0 -1
  130. package/dist/components/sdd-board-overlay.d.ts.map +0 -1
  131. package/dist/components/send-mode-picker.d.ts.map +0 -1
  132. package/dist/components/sessions-panel.d.ts.map +0 -1
  133. package/dist/components/settings-picker-filter.d.ts.map +0 -1
  134. package/dist/components/settings-picker-jumps.d.ts.map +0 -1
  135. package/dist/components/settings-picker-model.d.ts.map +0 -1
  136. package/dist/components/settings-picker-row-list.d.ts.map +0 -1
  137. package/dist/components/settings-picker.d.ts.map +0 -1
  138. package/dist/components/shadow-panel.d.ts.map +0 -1
  139. package/dist/components/shell-command-warning.d.ts.map +0 -1
  140. package/dist/components/slash-confirm-panel.d.ts.map +0 -1
  141. package/dist/components/slash-menu.d.ts.map +0 -1
  142. package/dist/components/status-bar-chips.d.ts.map +0 -1
  143. package/dist/components/status-bar-format.d.ts.map +0 -1
  144. package/dist/components/status-bar-helpers.d.ts.map +0 -1
  145. package/dist/components/status-bar-types.d.ts.map +0 -1
  146. package/dist/components/status-bar.d.ts.map +0 -1
  147. package/dist/components/statusline-detail-panel.d.ts.map +0 -1
  148. package/dist/components/statusline-picker.d.ts.map +0 -1
  149. package/dist/components/suggestions.d.ts.map +0 -1
  150. package/dist/components/todos-monitor.d.ts.map +0 -1
  151. package/dist/components/tools-picker.d.ts.map +0 -1
  152. package/dist/components/worktree-monitor.d.ts.map +0 -1
  153. package/dist/components/worktree-panel.d.ts.map +0 -1
  154. package/dist/connections-health.d.ts.map +0 -1
  155. package/dist/connections-slash.d.ts.map +0 -1
  156. package/dist/context-fill.d.ts.map +0 -1
  157. package/dist/context-slash.d.ts.map +0 -1
  158. package/dist/cron-slash.d.ts.map +0 -1
  159. package/dist/esc-close-panels.d.ts.map +0 -1
  160. package/dist/exit-slash.d.ts.map +0 -1
  161. package/dist/f-key-panels.d.ts.map +0 -1
  162. package/dist/file-search.d.ts.map +0 -1
  163. package/dist/fn-keys.d.ts.map +0 -1
  164. package/dist/git-info.d.ts.map +0 -1
  165. package/dist/heap-watchdog.d.ts.map +0 -1
  166. package/dist/height-cache.d.ts.map +0 -1
  167. package/dist/highlight.d.ts.map +0 -1
  168. package/dist/history-archive.d.ts.map +0 -1
  169. package/dist/history-entry.d.ts.map +0 -1
  170. package/dist/history-retention.d.ts.map +0 -1
  171. package/dist/hit-test.d.ts.map +0 -1
  172. package/dist/hooks/fleet-chat-coalescer.d.ts.map +0 -1
  173. package/dist/hooks/subagent-history-format.d.ts.map +0 -1
  174. package/dist/hooks/use-app-picker-keys.d.ts.map +0 -1
  175. package/dist/hooks/use-app-runtime-refs.d.ts.map +0 -1
  176. package/dist/hooks/use-app-session-state.d.ts.map +0 -1
  177. package/dist/hooks/use-auth-panel.d.ts.map +0 -1
  178. package/dist/hooks/use-autonomous-coordinator.d.ts.map +0 -1
  179. package/dist/hooks/use-autonomy-drivers.d.ts.map +0 -1
  180. package/dist/hooks/use-brain-events.d.ts.map +0 -1
  181. package/dist/hooks/use-brain-panel.d.ts.map +0 -1
  182. package/dist/hooks/use-brain-risk-sync.d.ts.map +0 -1
  183. package/dist/hooks/use-chip-staleness-guard.d.ts.map +0 -1
  184. package/dist/hooks/use-client-telemetry.d.ts.map +0 -1
  185. package/dist/hooks/use-core-tui-commands.d.ts.map +0 -1
  186. package/dist/hooks/use-director-fleet-bridge.d.ts.map +0 -1
  187. package/dist/hooks/use-enhance-runtime-state.d.ts.map +0 -1
  188. package/dist/hooks/use-exit-command.d.ts.map +0 -1
  189. package/dist/hooks/use-file-search.d.ts.map +0 -1
  190. package/dist/hooks/use-fleet-generation-gate.d.ts.map +0 -1
  191. package/dist/hooks/use-git-session-status.d.ts.map +0 -1
  192. package/dist/hooks/use-help-panel.d.ts.map +0 -1
  193. package/dist/hooks/use-history-copy-notice.d.ts.map +0 -1
  194. package/dist/hooks/use-history-viewport-sync.d.ts.map +0 -1
  195. package/dist/hooks/use-initial-prompt.d.ts.map +0 -1
  196. package/dist/hooks/use-input-history-persistence.d.ts.map +0 -1
  197. package/dist/hooks/use-interrupt-ladder.d.ts.map +0 -1
  198. package/dist/hooks/use-kanban-board-focus.d.ts.map +0 -1
  199. package/dist/hooks/use-live-settings-state.d.ts.map +0 -1
  200. package/dist/hooks/use-live-todos.d.ts.map +0 -1
  201. package/dist/hooks/use-mailbox-view-model.d.ts.map +0 -1
  202. package/dist/hooks/use-mode-picker.d.ts.map +0 -1
  203. package/dist/hooks/use-model-pick.d.ts.map +0 -1
  204. package/dist/hooks/use-mouse-tracking.d.ts.map +0 -1
  205. package/dist/hooks/use-next-steps-auto-submit.d.ts.map +0 -1
  206. package/dist/hooks/use-panel-controllers.d.ts.map +0 -1
  207. package/dist/hooks/use-paste-handling.d.ts.map +0 -1
  208. package/dist/hooks/use-picker-keys-types.d.ts.map +0 -1
  209. package/dist/hooks/use-picker-keys.d.ts.map +0 -1
  210. package/dist/hooks/use-prompt-picker.d.ts.map +0 -1
  211. package/dist/hooks/use-provider-event-bridge.d.ts.map +0 -1
  212. package/dist/hooks/use-queue-manager.d.ts.map +0 -1
  213. package/dist/hooks/use-session-interrupt-controller.d.ts.map +0 -1
  214. package/dist/hooks/use-settings-auto-save.d.ts.map +0 -1
  215. package/dist/hooks/use-shadow-panel.d.ts.map +0 -1
  216. package/dist/hooks/use-slash-picker.d.ts.map +0 -1
  217. package/dist/hooks/use-stable-key-handler.d.ts.map +0 -1
  218. package/dist/hooks/use-statusbar-view-model.d.ts.map +0 -1
  219. package/dist/hooks/use-statusline-hidden-sync.d.ts.map +0 -1
  220. package/dist/hooks/use-statusline-state.d.ts.map +0 -1
  221. package/dist/hooks/use-stream-chip-expiration.d.ts.map +0 -1
  222. package/dist/hooks/use-subagent-events.d.ts.map +0 -1
  223. package/dist/hooks/use-terminal-render-lifecycle.d.ts.map +0 -1
  224. package/dist/hooks/use-todos-auto-clear.d.ts.map +0 -1
  225. package/dist/hooks/use-token-counter-refresh.d.ts.map +0 -1
  226. package/dist/hooks/use-tui-activity.d.ts.map +0 -1
  227. package/dist/hooks/use-tui-controllers.d.ts.map +0 -1
  228. package/dist/hooks/use-tui-environment-state.d.ts.map +0 -1
  229. package/dist/hooks/use-tui-event-bridge.d.ts.map +0 -1
  230. package/dist/hooks/use-tui-slash-commands.d.ts.map +0 -1
  231. package/dist/hooks/use-working-dir-chip.d.ts.map +0 -1
  232. package/dist/index.d.ts.map +0 -1
  233. package/dist/index.js.map +0 -7
  234. package/dist/ink.d.ts.map +0 -1
  235. package/dist/input-editing.d.ts.map +0 -1
  236. package/dist/input-key-router.d.ts.map +0 -1
  237. package/dist/input-tokens.d.ts.map +0 -1
  238. package/dist/input-validation/action-helpers.d.ts.map +0 -1
  239. package/dist/input-validation/action-types.d.ts.map +0 -1
  240. package/dist/input-validation/allow-lists.d.ts.map +0 -1
  241. package/dist/input-validation/limits.d.ts.map +0 -1
  242. package/dist/input-validation/result.d.ts.map +0 -1
  243. package/dist/input-validation/state-entries.d.ts.map +0 -1
  244. package/dist/input-validation/terminal.d.ts.map +0 -1
  245. package/dist/input-validation.d.ts.map +0 -1
  246. package/dist/kanban-audit.d.ts.map +0 -1
  247. package/dist/kanban-slash.d.ts.map +0 -1
  248. package/dist/kill-slash.d.ts.map +0 -1
  249. package/dist/layout-engine.d.ts.map +0 -1
  250. package/dist/layout-store.d.ts.map +0 -1
  251. package/dist/mailbox-view-model-types.d.ts.map +0 -1
  252. package/dist/markdown-table.d.ts.map +0 -1
  253. package/dist/markdown.d.ts.map +0 -1
  254. package/dist/memory-context-monitor.d.ts.map +0 -1
  255. package/dist/memory-lifecycle-entry.d.ts.map +0 -1
  256. package/dist/memory-slash-format.d.ts.map +0 -1
  257. package/dist/memory-slash.d.ts.map +0 -1
  258. package/dist/mouse.d.ts.map +0 -1
  259. package/dist/on-panel-open.d.ts.map +0 -1
  260. package/dist/overlay-key-router.d.ts.map +0 -1
  261. package/dist/paste-accumulator.d.ts.map +0 -1
  262. package/dist/prompt-refinement-start.d.ts.map +0 -1
  263. package/dist/ps-slash.d.ts.map +0 -1
  264. package/dist/queue-slash.d.ts.map +0 -1
  265. package/dist/reducers/activity.d.ts.map +0 -1
  266. package/dist/reducers/composer.d.ts.map +0 -1
  267. package/dist/reducers/conversation.d.ts.map +0 -1
  268. package/dist/reducers/dialogs.d.ts.map +0 -1
  269. package/dist/reducers/fleet.d.ts.map +0 -1
  270. package/dist/reducers/helpers.d.ts.map +0 -1
  271. package/dist/reducers/panel-pickers.d.ts.map +0 -1
  272. package/dist/reducers/settings-panel.d.ts.map +0 -1
  273. package/dist/reducers/settings-values.d.ts.map +0 -1
  274. package/dist/reducers/workspace-panels.d.ts.map +0 -1
  275. package/dist/rehydrate-history.d.ts.map +0 -1
  276. package/dist/run-blocks-controller.d.ts.map +0 -1
  277. package/dist/run-tui-client-registration.d.ts.map +0 -1
  278. package/dist/run-tui-options.d.ts.map +0 -1
  279. package/dist/run-tui-title-controller.d.ts.map +0 -1
  280. package/dist/run-tui.d.ts.map +0 -1
  281. package/dist/running-tools.d.ts.map +0 -1
  282. package/dist/scroll-anchor.d.ts.map +0 -1
  283. package/dist/sdd-lifecycle-entry.d.ts.map +0 -1
  284. package/dist/settings-contracts.d.ts.map +0 -1
  285. package/dist/shared-types.d.ts.map +0 -1
  286. package/dist/slash-command-search.d.ts.map +0 -1
  287. package/dist/steering-preamble.d.ts.map +0 -1
  288. package/dist/submit-controller.d.ts.map +0 -1
  289. package/dist/submit-history.d.ts.map +0 -1
  290. package/dist/submit-prompt-refinement.d.ts.map +0 -1
  291. package/dist/terminal-modes.d.ts.map +0 -1
  292. package/dist/terminal-silence.d.ts.map +0 -1
  293. package/dist/terminal-title.d.ts.map +0 -1
  294. package/dist/terminal-width.d.ts.map +0 -1
  295. package/dist/theme.d.ts.map +0 -1
  296. package/dist/thinking-word.d.ts.map +0 -1
  297. package/dist/token-previews.d.ts.map +0 -1
  298. package/dist/tool-glyph.d.ts.map +0 -1
  299. package/dist/tui-host-capabilities.d.ts.map +0 -1
  300. package/dist/ui-contracts.d.ts.map +0 -1
  301. package/dist/ui-glyphs.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/app.tsx
2
- import * as path8 from "node:path";
2
+ import * as path7 from "node:path";
3
3
  import {
4
4
  applyRewindToConversation,
5
5
  DefaultSessionRewinder
@@ -1175,7 +1175,6 @@ function hasTokenDisplay(tokens) {
1175
1175
  return tokens.input > 0 || tokens.output > 0;
1176
1176
  }
1177
1177
  var RAIL_CAP = 1;
1178
- var RAIL_PAD = 2;
1179
1178
  function statusBarModelSpan(opts) {
1180
1179
  const leading = [];
1181
1180
  if (opts.yolo) leading.push(opts.monochrome ? "YOLO" : `${glyphs.warning} YOLO`);
@@ -1192,9 +1191,9 @@ function statusBarModelSpan(opts) {
1192
1191
  const workingDir = truncateChip(opts.workingDir, 28);
1193
1192
  leading.push(opts.monochrome ? workingDir : `${glyphs.workingDirectory} ${workingDir}`);
1194
1193
  }
1195
- const transitionWidth = 3;
1194
+ const sepWidth = 2;
1196
1195
  const precedingWidth = leading.reduce(
1197
- (total, text) => total + displayWidth(text) + RAIL_PAD + transitionWidth,
1196
+ (total, text) => total + displayWidth(text) + sepWidth,
1198
1197
  0
1199
1198
  );
1200
1199
  const full = opts.provider ? `${opts.provider}/${opts.model}` : opts.model;
@@ -1205,8 +1204,8 @@ function statusBarAutonomySpan(opts) {
1205
1204
  let col = RAIL_CAP;
1206
1205
  if (opts.yolo) {
1207
1206
  const yolo = opts.monochrome ? "YOLO" : `${glyphs.warning} YOLO`;
1208
- const transitionWidth = 3;
1209
- col += displayWidth(yolo) + RAIL_PAD + transitionWidth;
1207
+ const sepWidth = 2;
1208
+ col += displayWidth(yolo) + sepWidth;
1210
1209
  }
1211
1210
  const label = opts.monochrome ? opts.autonomy.toUpperCase() : `\u221E ${opts.autonomy.toUpperCase()}`;
1212
1211
  return { start: col + 1, len: displayWidth(label) };
@@ -1380,12 +1379,16 @@ var COMFORTABLE_THRESHOLD = 90;
1380
1379
  function chipColor(color, isNoColor) {
1381
1380
  return isNoColor ? void 0 : color;
1382
1381
  }
1382
+ var STACK_ORANGE = "#FD9F02";
1383
1383
  var LINE_BG_COLORS = [theme.surface, theme.surface, theme.surface, theme.surface];
1384
1384
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1385
1385
  var SPINNER_INTERVAL_MS = 1e3;
1386
1386
  function StatusBar({
1387
1387
  model,
1388
1388
  provider,
1389
+ version,
1390
+ latestVersion,
1391
+ updateAvailable,
1389
1392
  state,
1390
1393
  thinkingWord,
1391
1394
  thinkingAnimationStyle,
@@ -1414,11 +1417,15 @@ function StatusBar({
1414
1417
  memoryContextMonitor,
1415
1418
  contextStrategy,
1416
1419
  hiddenItems,
1420
+ // Component-level fallback stays 'detailed' for back-compat with callers/tests
1421
+ // that omit `mode`. The user-facing default is 'minimum' (DEFAULT_STATUSLINE_MODE),
1422
+ // applied at the app/settings layer — not here.
1417
1423
  mode = "detailed",
1418
1424
  events,
1419
1425
  sessionId,
1420
1426
  eternalStage,
1421
1427
  goalSummary,
1428
+ droppedTools,
1422
1429
  indexState,
1423
1430
  breakerCountdown,
1424
1431
  modeLabel,
@@ -1565,7 +1572,7 @@ function StatusBar({
1565
1572
  const liveActive = memoryMonitor ? activeMemoryContextCount(memoryMonitor) : 0;
1566
1573
  const reportedActive = memoryMonitor ? liveActive : Sage?.activeInContext ?? 0;
1567
1574
  memoryDetailChips.push(
1568
- /* @__PURE__ */ jsx5(Text, { color: chipColor(theme.accent, isNoColor), children: isNoColor ? "Memory " : `${STATUSLINE_ICONS.memory_context} ` }, "mem-label")
1575
+ /* @__PURE__ */ jsx5(Text, { color: chipColor(theme.accent, isNoColor), children: isNoColor ? "Memory " : `${STATUSLINE_ICONS.memory_context} Memory ` }, "mem-label")
1569
1576
  );
1570
1577
  if (Sage) {
1571
1578
  memoryDetailChips.push(
@@ -1717,6 +1724,18 @@ function StatusBar({
1717
1724
  stateStatusChip,
1718
1725
  fleetWorkingTime != null && fleetWorkingTime > 0 && showChip("elapsed") ? /* @__PURE__ */ jsx5(Text, { dimColor: !isNoColor, children: isNoColor ? fmtElapsed(fleetWorkingTime) : `${STATUSLINE_ICONS.elapsed} ${fmtElapsed(fleetWorkingTime)}` }) : null
1719
1726
  ].filter((c) => c !== null);
1727
+ const showUpdateNotice = Boolean(updateAvailable) && typeof latestVersion === "string" && latestVersion.length > 0 && latestVersion !== version;
1728
+ const versionStatusChip = version ? /* @__PURE__ */ jsxs4(Text, { children: [
1729
+ /* @__PURE__ */ jsxs4(Text, { color: isNoColor ? void 0 : theme.textSecondary, dimColor: !isNoColor, children: [
1730
+ "v",
1731
+ version
1732
+ ] }),
1733
+ showUpdateNotice ? /* @__PURE__ */ jsxs4(Text, { color: isNoColor ? void 0 : STACK_ORANGE, children: [
1734
+ " \xB7 (update v",
1735
+ latestVersion,
1736
+ ")"
1737
+ ] }) : null
1738
+ ] }) : null;
1720
1739
  const minimumChips = [
1721
1740
  // State with animation
1722
1741
  showChip("state") && thinking ? /* @__PURE__ */ jsx5(
@@ -1774,10 +1793,6 @@ function StatusBar({
1774
1793
  ) : null,
1775
1794
  // Fleet working time
1776
1795
  fleetWorkingTime != null && fleetWorkingTime > 0 && showChip("elapsed") ? /* @__PURE__ */ jsx5(Text, { dimColor: !isNoColor, children: fmtElapsed(fleetWorkingTime) }) : null,
1777
- processMemory && showChip("memory") ? /* @__PURE__ */ jsxs4(Text, { color: chipColor(memoryColor, isNoColor), children: [
1778
- "RAM ",
1779
- fmtMemory(processMemory.rss)
1780
- ] }) : null,
1781
1796
  // Work summary (compact)
1782
1797
  ...minimalWorkParts.length > 0 ? [/* @__PURE__ */ jsx5(Text, { dimColor: !isNoColor, children: minimalWorkParts.slice(0, 2).join(" \xB7 ") })] : []
1783
1798
  ].filter((c) => c !== null);
@@ -1808,7 +1823,15 @@ function StatusBar({
1808
1823
  const detailChips = [];
1809
1824
  if (mailbox && showMailbox) {
1810
1825
  detailChips.push(
1811
- mailbox.unread > 0 ? /* @__PURE__ */ jsx5(Text, { color: isNoColor ? void 0 : theme.warn, bold: true, children: `${STATUSLINE_ICONS.mailbox} ${mailbox.unread} new` }) : /* @__PURE__ */ jsx5(Text, { dimColor: !isNoColor, children: `${STATUSLINE_ICONS.mailbox} 0` }),
1826
+ mailbox.unread > 0 ? /* @__PURE__ */ jsxs4(Text, { color: isNoColor ? void 0 : theme.warn, bold: true, children: [
1827
+ STATUSLINE_ICONS.mailbox,
1828
+ " ",
1829
+ mailbox.unread,
1830
+ " new"
1831
+ ] }) : /* @__PURE__ */ jsxs4(Text, { dimColor: !isNoColor, children: [
1832
+ STATUSLINE_ICONS.mailbox,
1833
+ " 0"
1834
+ ] }),
1812
1835
  /* @__PURE__ */ jsxs4(Text, { color: isNoColor ? void 0 : theme.accent, children: [
1813
1836
  glyphs.peers,
1814
1837
  " ",
@@ -1838,6 +1861,7 @@ function StatusBar({
1838
1861
  PowerlineRail,
1839
1862
  {
1840
1863
  segments: minimumChips,
1864
+ rightAnchor: versionStatusChip,
1841
1865
  budget: Math.max(12, termWidth),
1842
1866
  monochrome: isNoColor,
1843
1867
  fillBg: LINE_BG_COLORS[0]
@@ -1849,6 +1873,7 @@ function StatusBar({
1849
1873
  PowerlineRail,
1850
1874
  {
1851
1875
  segments: isCompact ? modeChips.slice(0, 5) : modeChips,
1876
+ rightAnchor: versionStatusChip,
1852
1877
  budget: Math.max(12, termWidth),
1853
1878
  monochrome: isNoColor,
1854
1879
  fillBg: LINE_BG_COLORS[0]
@@ -1999,6 +2024,7 @@ function StatusBar({
1999
2024
  children: isNoColor ? `auto in ${autoProceedCountdown}s` : `${STATUSLINE_ICONS.auto_proceed} auto in ${autoProceedCountdown}s`
2000
2025
  }
2001
2026
  ) : null,
2027
+ droppedTools && droppedTools > 0 ? /* @__PURE__ */ jsx5(Text, { color: isNoColor ? void 0 : theme.warn, children: isNoColor ? `-${droppedTools} tools` : `${STATUSLINE_ICONS.tools} -${droppedTools}` }) : null,
2002
2028
  ...detailChips
2003
2029
  ].filter((c) => c !== null),
2004
2030
  budget: Math.max(12, termWidth),
@@ -3582,7 +3608,9 @@ var SETTINGS_PICKER_JUMP_CHORDS = Object.freeze([
3582
3608
  { mod: "alt-shift", letter: "g", field: 35, label: "Config scope" },
3583
3609
  { mod: "alt", letter: "n", field: 36, label: "Animation" },
3584
3610
  { mod: "alt", letter: "p", field: 40, label: "Show agent swarm panel" },
3585
- { mod: "alt", letter: "r", field: 41, label: "Pre-refine countdown" }
3611
+ { mod: "alt", letter: "r", field: 41, label: "Pre-refine countdown" },
3612
+ { mod: "alt-shift", letter: "s", field: 43, label: "Show SAGE Memory Inject" },
3613
+ { mod: "alt-shift", letter: "t", field: 44, label: "SAGE Memory Inject threshold" }
3586
3614
  ]);
3587
3615
  function settingsPickerJumpField(mod, letter) {
3588
3616
  const lower = letter.toLowerCase();
@@ -3629,6 +3657,7 @@ var CONTEXT_MODE_DESCS = {
3629
3657
  deep: "Larger context for complex tasks"
3630
3658
  };
3631
3659
  var STATUSLINE_MODES = ["minimum", "detailed", "no-color"];
3660
+ var DEFAULT_STATUSLINE_MODE = "minimum";
3632
3661
  var REASONING_MODES = ["auto", "on", "off"];
3633
3662
  var REASONING_EFFORTS = [
3634
3663
  "none",
@@ -3641,8 +3670,8 @@ var REASONING_EFFORTS = [
3641
3670
  ];
3642
3671
  var CACHE_TTLS = ["default", "5m", "1h"];
3643
3672
  var STATUSLINE_MODE_DESCS = {
3644
- minimum: "Single line with essential chips only",
3645
- detailed: "Full multi-line statusline (default)",
3673
+ minimum: "Single line with essential chips only (default)",
3674
+ detailed: "Full multi-line statusline",
3646
3675
  "no-color": "Multiline statusline without colors or icons"
3647
3676
  };
3648
3677
  var MAX_ITERATIONS_PRESETS = [100, 200, 500, 1e3, 0];
@@ -3708,7 +3737,7 @@ var MODE_DESC = {
3708
3737
  suggest: "Shows next-step suggestions after each turn",
3709
3738
  auto: "Self-driving \u2014 agent continues automatically"
3710
3739
  };
3711
- var SETTINGS_FIELD_COUNT = 43;
3740
+ var SETTINGS_FIELD_COUNT = 45;
3712
3741
  var THINKING_WORD_FIELD = 22;
3713
3742
  var THINKING_WORD_PRESETS = [
3714
3743
  "thinking",
@@ -3811,8 +3840,12 @@ var SETTINGS_FIELD_LABELS = [
3811
3840
  // 40
3812
3841
  "Pre-refine countdown",
3813
3842
  // 41
3814
- "Read symbols"
3843
+ "Read symbols",
3815
3844
  // 42
3845
+ "Show SAGE Memory Inject",
3846
+ // 43
3847
+ "SAGE Memory Inject threshold"
3848
+ // 44
3816
3849
  ];
3817
3850
  function resolveSettingsFieldValue(field, input) {
3818
3851
  const raw = input.trim().toLowerCase();
@@ -3837,7 +3870,8 @@ function resolveSettingsFieldValue(field, input) {
3837
3870
  [37, "breakerEnabled"],
3838
3871
  [39, "showModelReasoning"],
3839
3872
  [40, "showAgentSwarmPanel"],
3840
- [42, "readSymbols"]
3873
+ [42, "readSymbols"],
3874
+ [43, "showSageMemoryInject"]
3841
3875
  ]);
3842
3876
  const boolKey = BOOL_FIELDS.get(field);
3843
3877
  if (boolKey) {
@@ -3906,6 +3940,26 @@ function resolveSettingsFieldValue(field, input) {
3906
3940
  error: `Invalid value "${input}" for ${label}. Valid: ${values.join(", ")}.`
3907
3941
  };
3908
3942
  }
3943
+ if (field === 44) {
3944
+ const presets = SAGE_THRESHOLD_PRESETS;
3945
+ const rawMatch = raw.match(/^(\d+(?:\.\d+)?)$/);
3946
+ if (rawMatch) {
3947
+ const asFloat = Number.parseFloat(rawMatch[1]);
3948
+ if (presets.includes(asFloat)) {
3949
+ return {
3950
+ ok: true,
3951
+ patch: { sageMemoryInjectThreshold: asFloat },
3952
+ label,
3953
+ displayValue: formatSageThreshold(asFloat)
3954
+ };
3955
+ }
3956
+ }
3957
+ const options = presets.map((p) => formatSageThreshold(p)).join(", ");
3958
+ return {
3959
+ ok: false,
3960
+ error: `Invalid value "${input}" for ${label}. Available: ${options}.`
3961
+ };
3962
+ }
3909
3963
  const presetLabel = (n, zeroLabel) => n === 0 ? zeroLabel : String(n);
3910
3964
  const PRESET_FIELDS = [
3911
3965
  [1, "delayMs", DELAY_PRESETS_MS, (n) => formatSettingsDelay(n)],
@@ -3988,7 +4042,8 @@ function getSettingsFieldValue(values, field) {
3988
4042
  [37, "breakerEnabled"],
3989
4043
  [39, "showModelReasoning"],
3990
4044
  [40, "showAgentSwarmPanel"],
3991
- [42, "readSymbols"]
4045
+ [42, "readSymbols"],
4046
+ [43, "showSageMemoryInject"]
3992
4047
  ];
3993
4048
  for (const [f, key] of BOOL_KEYS) {
3994
4049
  if (field !== f) continue;
@@ -4023,7 +4078,8 @@ function getSettingsFieldValue(values, field) {
4023
4078
  [41, "preRefineSeconds", formatPreRefineSeconds],
4024
4079
  [21, "multiDiffSummaryThreshold", formatMultiDiffSummaryThreshold],
4025
4080
  [30, "maxConcurrent", (n) => presetLabel(n, "runtime default")],
4026
- [38, "breakerAutoKillResetMs", formatBreakerTimeout]
4081
+ [38, "breakerAutoKillResetMs", formatBreakerTimeout],
4082
+ [44, "sageMemoryInjectThreshold", formatSageThreshold]
4027
4083
  ];
4028
4084
  for (const [f, key, fmt2] of PRESET_KEYS) {
4029
4085
  if (field !== f) continue;
@@ -4077,7 +4133,7 @@ var SETTINGS_SECTIONS = [
4077
4133
  },
4078
4134
  {
4079
4135
  name: "Display",
4080
- fields: [39, 40, 41]
4136
+ fields: [39, 40, 41, 43, 44]
4081
4137
  }
4082
4138
  ];
4083
4139
  function formatAllSettingsSummary(values) {
@@ -4093,6 +4149,10 @@ function formatAllSettingsSummary(values) {
4093
4149
  }
4094
4150
  return lines.join("\n");
4095
4151
  }
4152
+ var SAGE_THRESHOLD_PRESETS = [0.72, 0.75, 0.85, 0.9, 0.95];
4153
+ function formatSageThreshold(t) {
4154
+ return t.toFixed(2);
4155
+ }
4096
4156
  var SETTINGS_DEFAULTS = Object.freeze({
4097
4157
  // Factory defaults mirror the core config defaults: autonomy 'auto'
4098
4158
  // (self-driving) and yolo on (auto-approve). Live values still come from
@@ -4127,7 +4187,7 @@ var SETTINGS_DEFAULTS = Object.freeze({
4127
4187
  enhanceEnabled: true,
4128
4188
  enhanceLanguage: "original",
4129
4189
  debugStream: false,
4130
- statuslineMode: "detailed",
4190
+ statuslineMode: DEFAULT_STATUSLINE_MODE,
4131
4191
  reasoningMode: "auto",
4132
4192
  reasoningEffort: "high",
4133
4193
  reasoningPreserve: false,
@@ -4139,7 +4199,9 @@ var SETTINGS_DEFAULTS = Object.freeze({
4139
4199
  breakerAutoKillResetMs: 6e4,
4140
4200
  showModelReasoning: true,
4141
4201
  showAgentSwarmPanel: true,
4142
- readSymbols: false
4202
+ readSymbols: false,
4203
+ showSageMemoryInject: false,
4204
+ sageMemoryInjectThreshold: 0.85
4143
4205
  });
4144
4206
  function resetSettingsFieldValue(field) {
4145
4207
  const result = getSettingsFieldValue(SETTINGS_DEFAULTS, field);
@@ -4192,7 +4254,9 @@ function buildResetPatch(field) {
4192
4254
  [39, "showModelReasoning"],
4193
4255
  [40, "showAgentSwarmPanel"],
4194
4256
  [41, "preRefineSeconds"],
4195
- [42, "readSymbols"]
4257
+ [42, "readSymbols"],
4258
+ [43, "showSageMemoryInject"],
4259
+ [44, "sageMemoryInjectThreshold"]
4196
4260
  ];
4197
4261
  for (const [f, key] of KEY_MAP) {
4198
4262
  if (f === field) {
@@ -4323,6 +4387,8 @@ function SettingsPicker({
4323
4387
  breakerAutoKillResetMs,
4324
4388
  showModelReasoning,
4325
4389
  showAgentSwarmPanel,
4390
+ showSageMemoryInject,
4391
+ sageMemoryInjectThreshold,
4326
4392
  readSymbols,
4327
4393
  hint
4328
4394
  }) {
@@ -4562,6 +4628,16 @@ function SettingsPicker({
4562
4628
  label: "Read symbols",
4563
4629
  value: boolVal(readSymbols),
4564
4630
  detail: "Include codebase-index symbols in read tool results"
4631
+ },
4632
+ {
4633
+ label: "Show SAGE Memory Inject",
4634
+ value: boolVal(showSageMemoryInject),
4635
+ detail: "Show SAGE memory injection blocks in tool results"
4636
+ },
4637
+ {
4638
+ label: "SAGE Memory Inject threshold",
4639
+ value: formatSageThreshold(sageMemoryInjectThreshold),
4640
+ detail: "Min relation strength for injection (0.72\u20130.95)"
4565
4641
  }
4566
4642
  ];
4567
4643
  const fieldRowIndex = [];
@@ -5875,7 +5951,7 @@ var F_KEY_PANEL_ENTRIES = [
5875
5951
  key: 12,
5876
5952
  label: "Kanban board panel",
5877
5953
  action: "toggleKanbanPanel",
5878
- helpKeys: "F12 or Ctrl+J",
5954
+ helpKeys: "F12 or Ctrl+Y",
5879
5955
  helpDescription: "kanban board panel (also /kanban)"
5880
5956
  },
5881
5957
  {
@@ -6156,7 +6232,12 @@ function useAppPickerKeys({
6156
6232
  });
6157
6233
  return;
6158
6234
  }
6159
- dispatch({ type: "replaceHistory", entries: result.entries, nextId: result.nextId });
6235
+ dispatch({
6236
+ type: "replaceHistory",
6237
+ entries: result.entries,
6238
+ nextId: result.nextId,
6239
+ contextSnapshot: result.contextSnapshot
6240
+ });
6160
6241
  dispatch({ type: "resumePickerClose" });
6161
6242
  dispatch({
6162
6243
  type: "addEntry",
@@ -6182,7 +6263,12 @@ function useAppPickerKeys({
6182
6263
  dispatch({ type: "sessionsPanelBusy", on: false });
6183
6264
  return;
6184
6265
  }
6185
- dispatch({ type: "replaceHistory", entries: result.entries, nextId: result.nextId });
6266
+ dispatch({
6267
+ type: "replaceHistory",
6268
+ entries: result.entries,
6269
+ nextId: result.nextId,
6270
+ contextSnapshot: result.contextSnapshot
6271
+ });
6186
6272
  dispatch({ type: "toggleSessionsPanel" });
6187
6273
  dispatch({
6188
6274
  type: "addEntry",
@@ -8531,7 +8617,7 @@ async function handleMemoryWrite(store, sub, rest) {
8531
8617
  try {
8532
8618
  const existing = await Sage.getSage(id);
8533
8619
  if (!existing) return { message: `No memory with id \`${id}\`.` };
8534
- await Sage.deleteSage(id, reason);
8620
+ await Sage.deleteSage(id, reason, { force: true });
8535
8621
  return { message: `Deleted \`${id}\`.` };
8536
8622
  } catch (err) {
8537
8623
  return { message: `Could not delete: ${memErr(err)}` };
@@ -9779,8 +9865,8 @@ function handleCollabDone(event, dispatch, stateRef) {
9779
9865
  // src/hooks/use-file-search.ts
9780
9866
  import * as fs2 from "node:fs/promises";
9781
9867
  import * as path2 from "node:path";
9782
- import { useEffect as useEffect14 } from "react";
9783
9868
  import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
9869
+ import { useEffect as useEffect14 } from "react";
9784
9870
 
9785
9871
  // src/file-search.ts
9786
9872
  import * as fs from "node:fs/promises";
@@ -9991,7 +10077,7 @@ var TOKEN_PREVIEWS_MAX_ENTRIES = 64;
9991
10077
  var TOKEN_PREVIEW_MAX_CHARS = 8 * 1024;
9992
10078
  var TOKEN_PREVIEW_MAX_LINES = 6;
9993
10079
  var TOKEN_PREVIEWS_MAX_CHARS = 256 * 1024;
9994
- var TEXT_FILE_ATTACHMENT_MAX_BYTES = 1024 * 1024;
10080
+ var TEXT_FILE_ATTACHMENT_INLINE_MAX_BYTES = 1024 * 1024;
9995
10081
  var TRUNCATION_MARKER = "\u2026 [preview truncated]";
9996
10082
  var ATTACHMENT_WRAPPER_RE = /^<(?:pasted|file(?: path="[^"]*")?)>\n([\s\S]*)\n<\/(?:pasted|file)>$/;
9997
10083
  function createTokenPreview(value, maxChars = TOKEN_PREVIEW_MAX_CHARS, maxLines = TOKEN_PREVIEW_MAX_LINES) {
@@ -10169,17 +10255,13 @@ function useFileSearch(options) {
10169
10255
  const absPath = path2.isAbsolute(picked) ? picked : path2.join(projectRoot, picked);
10170
10256
  try {
10171
10257
  const stat3 = await fs2.stat(absPath);
10172
- if (stat3.size > TEXT_FILE_ATTACHMENT_MAX_BYTES) {
10173
- throw new Error(
10174
- `file exceeds ${(TEXT_FILE_ATTACHMENT_MAX_BYTES / (1024 * 1024)).toFixed(0)} MiB attachment limit (${stat3.size.toLocaleString()} bytes)`
10175
- );
10176
- }
10177
- const data = await fs2.readFile(absPath, "utf8");
10178
- const bytes = Buffer.byteLength(data, "utf8");
10179
- if (bytes > TEXT_FILE_ATTACHMENT_MAX_BYTES) {
10180
- throw new Error(
10181
- `file exceeds ${(TEXT_FILE_ATTACHMENT_MAX_BYTES / (1024 * 1024)).toFixed(0)} MiB attachment limit (${bytes.toLocaleString()} UTF-8 bytes)`
10182
- );
10258
+ let data;
10259
+ if (stat3.size > TEXT_FILE_ATTACHMENT_INLINE_MAX_BYTES) {
10260
+ data = fileReadContract(picked, stat3.size);
10261
+ } else {
10262
+ const inline = await fs2.readFile(absPath, "utf8");
10263
+ const bytes = Buffer.byteLength(inline, "utf8");
10264
+ data = bytes > TEXT_FILE_ATTACHMENT_INLINE_MAX_BYTES ? fileReadContract(picked, bytes) : inline;
10183
10265
  }
10184
10266
  const token = await builder.registerFile({
10185
10267
  kind: "file",
@@ -10205,6 +10287,14 @@ function useFileSearch(options) {
10205
10287
  };
10206
10288
  return { onPickerEnter: acceptPickerSelection };
10207
10289
  }
10290
+ function fileReadContract(filePath, bytes) {
10291
+ return [
10292
+ "[File reference contract]",
10293
+ `Referenced project file: ${JSON.stringify(filePath)} (${bytes.toLocaleString()} bytes; content not inlined).`,
10294
+ "Before answering or modifying anything related to this reference, use the available file-reading tools to read the complete file.",
10295
+ "If a read is paginated or chunked, continue from the same path until EOF. Do not treat a partial read or preview as the complete file."
10296
+ ].join("\n");
10297
+ }
10208
10298
 
10209
10299
  // src/hooks/use-help-panel.ts
10210
10300
  import { useCallback as useCallback8 } from "react";
@@ -10427,6 +10517,7 @@ function retainCheckpoints(checkpoints, budget = {}) {
10427
10517
 
10428
10518
  // src/components/history/replay.ts
10429
10519
  import { SESSION_MARKER_EVENT_TYPES, sessionEventToMarker } from "@wrongstack/core/types";
10520
+ import { isFinalTurnStopReason } from "@wrongstack/tools/next-steps";
10430
10521
  var MARKER_EVENT_TYPES = SESSION_MARKER_EVENT_TYPES;
10431
10522
  var SUBAGENT_MARKER_STYLE = {
10432
10523
  agent_spawned: { color: "magenta", icon: "\u26A1" },
@@ -10464,18 +10555,18 @@ function replaySessionMessages(messages, events, startId) {
10464
10555
  backbone.push({ ts, entry });
10465
10556
  return entry;
10466
10557
  };
10467
- const appendText = (role, text, ts) => {
10558
+ const appendText = (role, text, ts, final) => {
10468
10559
  if (!text.trim()) return;
10469
10560
  push(
10470
10561
  ts,
10471
- role === "assistant" ? { kind: "assistant", text } : role === "system" ? { kind: "info", text } : { kind: "user", text }
10562
+ role === "assistant" ? { kind: "assistant", text, final } : role === "system" ? { kind: "info", text } : { kind: "user", text }
10472
10563
  );
10473
10564
  };
10474
10565
  for (const message of messages) {
10475
10566
  const ts = message.ts ?? lastTs;
10476
10567
  if (message.ts) lastTs = message.ts;
10477
10568
  if (typeof message.content === "string") {
10478
- appendText(message.role, message.content, ts);
10569
+ appendText(message.role, message.content, ts, true);
10479
10570
  continue;
10480
10571
  }
10481
10572
  for (const block of message.content) {
@@ -10483,13 +10574,20 @@ function replaySessionMessages(messages, events, startId) {
10483
10574
  push(ts, { kind: "thinking", text: block.thinking });
10484
10575
  }
10485
10576
  }
10486
- appendText(
10487
- message.role,
10488
- message.content.filter((block) => block.type === "text").map((block) => block.text).join(""),
10489
- ts
10490
- );
10577
+ const final = !message.content.some((block) => block.type === "tool_use");
10578
+ const textParts = [];
10579
+ const flushText = () => {
10580
+ const text = textParts.join("").trim();
10581
+ if (text) appendText(message.role, text, ts, final);
10582
+ textParts.length = 0;
10583
+ };
10491
10584
  for (const block of message.content) {
10585
+ if (block.type === "text") {
10586
+ textParts.push(block.text);
10587
+ continue;
10588
+ }
10492
10589
  if (block.type === "tool_use") {
10590
+ flushText();
10493
10591
  const end2 = toolEnds.get(block.id);
10494
10592
  const entry = push(ts, {
10495
10593
  kind: "tool",
@@ -10505,6 +10603,7 @@ function replaySessionMessages(messages, events, startId) {
10505
10603
  continue;
10506
10604
  }
10507
10605
  if (block.type !== "tool_result") continue;
10606
+ flushText();
10508
10607
  const existing = toolEntries.get(block.tool_use_id);
10509
10608
  if (existing) {
10510
10609
  existing.output = toolOutputPreview(block.content);
@@ -10524,6 +10623,7 @@ function replaySessionMessages(messages, events, startId) {
10524
10623
  outputLines: end?.outputLines
10525
10624
  });
10526
10625
  }
10626
+ flushText();
10527
10627
  }
10528
10628
  const dummyPending = /* @__PURE__ */ new Map();
10529
10629
  const dummyCompleted = /* @__PURE__ */ new Map();
@@ -10567,7 +10667,7 @@ function eventToEntry(ev, pendingTools, completedTools) {
10567
10667
  }
10568
10668
  const text = ev.content.filter((b) => b.type === "text").map((b) => b.text).join("");
10569
10669
  if (!text.trim()) return null;
10570
- return { kind: "assistant", text };
10670
+ return { kind: "assistant", text, final: isFinalTurnStopReason(ev.stopReason) };
10571
10671
  }
10572
10672
  case "tool_use": {
10573
10673
  pendingTools.set(ev.id, { name: ev.name, input: ev.input, ts: ev.ts });
@@ -10824,9 +10924,9 @@ function rehydrateHistory(messages, startId, toolCalls) {
10824
10924
  outputTokens: tc.outputTokens,
10825
10925
  outputLines: tc.outputLines
10826
10926
  });
10827
- const pushAssistantText = (parts) => {
10927
+ const pushAssistantText = (parts, final) => {
10828
10928
  const text = parts.join("").trim();
10829
- if (text) entries.push({ id: nextId++, kind: "assistant", text });
10929
+ if (text) entries.push({ id: nextId++, kind: "assistant", text, final });
10830
10930
  parts.length = 0;
10831
10931
  };
10832
10932
  for (const msg of messages) {
@@ -10839,23 +10939,25 @@ function rehydrateHistory(messages, startId, toolCalls) {
10839
10939
  if (msg.role === "assistant") {
10840
10940
  if (!Array.isArray(msg.content)) {
10841
10941
  const text = textOf(msg).trim();
10842
- if (text) entries.push({ id: nextId++, kind: "assistant", text });
10942
+ if (text) entries.push({ id: nextId++, kind: "assistant", text, final: true });
10843
10943
  continue;
10844
10944
  }
10945
+ const blocks = msg.content;
10946
+ const final = !blocks.some((block) => block.type === "tool_use");
10845
10947
  const textParts = [];
10846
- for (const block of msg.content) {
10948
+ for (const block of blocks) {
10847
10949
  if (block.type === "text") {
10848
10950
  textParts.push(block.text);
10849
10951
  continue;
10850
10952
  }
10851
10953
  if (block.type !== "tool_use") continue;
10852
- pushAssistantText(textParts);
10954
+ pushAssistantText(textParts, final);
10853
10955
  const tc = toolCallsById.get(block.id);
10854
10956
  if (!tc) continue;
10855
10957
  entries.push(toolEntryFor(tc));
10856
10958
  consumed.add(block.id);
10857
10959
  }
10858
- pushAssistantText(textParts);
10960
+ pushAssistantText(textParts, final);
10859
10961
  }
10860
10962
  }
10861
10963
  if (toolCalls) {
@@ -11035,7 +11137,7 @@ function createInitialState(options) {
11035
11137
  enhanceEnabled: true,
11036
11138
  enhanceLanguage: "original",
11037
11139
  debugStream: false,
11038
- statuslineMode: "detailed",
11140
+ statuslineMode: DEFAULT_STATUSLINE_MODE,
11039
11141
  reasoningMode: "auto",
11040
11142
  reasoningEffort: "high",
11041
11143
  reasoningPreserve: false,
@@ -11049,6 +11151,8 @@ function createInitialState(options) {
11049
11151
  breakerAutoKillResetMs: 6e4,
11050
11152
  showModelReasoning: true,
11051
11153
  showAgentSwarmPanel: true,
11154
+ showSageMemoryInject: false,
11155
+ sageMemoryInjectThreshold: 0.85,
11052
11156
  readSymbols: false
11053
11157
  },
11054
11158
  statuslinePicker: {
@@ -12657,11 +12761,19 @@ function reduceComposer(state, action) {
12657
12761
  const maxBannerId = banners.length > 0 ? Math.max(...banners.map((b) => b.id)) : 0;
12658
12762
  const shifted = action.entries.map((e, i) => ({ ...e, id: maxBannerId + 1 + i }));
12659
12763
  const nextId = maxBannerId + 1 + shifted.length;
12764
+ const snap = action.contextSnapshot;
12765
+ const leaderWithSnap = snap && snap.tokens > 0 ? {
12766
+ ...state.leader,
12767
+ ctxTokens: snap.tokens,
12768
+ ctxMaxTokens: snap.maxContext > 0 ? snap.maxContext : state.leader.ctxMaxTokens
12769
+ } : state.leader;
12660
12770
  return {
12661
12771
  ...state,
12662
12772
  entries: retainTuiHistory([...banners, ...shifted]),
12663
12773
  nextId,
12664
- historyGen: state.historyGen + 1
12774
+ historyGen: state.historyGen + 1,
12775
+ leader: leaderWithSnap,
12776
+ contextChipVersion: state.contextChipVersion + 1
12665
12777
  };
12666
12778
  }
12667
12779
  default:
@@ -13343,6 +13455,8 @@ function reduceSettingsPanel(state, action) {
13343
13455
  breakerAutoKillResetMs: action.breakerAutoKillResetMs,
13344
13456
  showModelReasoning: action.showModelReasoning,
13345
13457
  showAgentSwarmPanel: action.showAgentSwarmPanel,
13458
+ showSageMemoryInject: action.showSageMemoryInject,
13459
+ sageMemoryInjectThreshold: action.sageMemoryInjectThreshold,
13346
13460
  readSymbols: action.readSymbols,
13347
13461
  hint: void 0
13348
13462
  }
@@ -13708,7 +13822,7 @@ function reduceSettingsValues(state, action) {
13708
13822
  };
13709
13823
  if (f === 34) {
13710
13824
  const i = STATUSLINE_MODES.indexOf(sp.statuslineMode);
13711
- const base = i < 0 ? STATUSLINE_MODES.indexOf("detailed") : i;
13825
+ const base = i < 0 ? STATUSLINE_MODES.indexOf(DEFAULT_STATUSLINE_MODE) : i;
13712
13826
  const next = (base + action.delta + STATUSLINE_MODES.length) % STATUSLINE_MODES.length;
13713
13827
  return {
13714
13828
  ...state,
@@ -13791,6 +13905,28 @@ function reduceSettingsValues(state, action) {
13791
13905
  ...state,
13792
13906
  settingsPicker: { ...sp, readSymbols: !sp.readSymbols, hint: void 0 }
13793
13907
  };
13908
+ if (f === 43)
13909
+ return {
13910
+ ...state,
13911
+ settingsPicker: {
13912
+ ...sp,
13913
+ showSageMemoryInject: !sp.showSageMemoryInject,
13914
+ hint: void 0
13915
+ }
13916
+ };
13917
+ if (f === 44) {
13918
+ const j = SAGE_THRESHOLD_PRESETS.indexOf(sp.sageMemoryInjectThreshold);
13919
+ const base = j < 0 ? 0 : j;
13920
+ const next = (base + action.delta + SAGE_THRESHOLD_PRESETS.length) % SAGE_THRESHOLD_PRESETS.length;
13921
+ return {
13922
+ ...state,
13923
+ settingsPicker: {
13924
+ ...sp,
13925
+ sageMemoryInjectThreshold: expectDefined2(SAGE_THRESHOLD_PRESETS[next]),
13926
+ hint: void 0
13927
+ }
13928
+ };
13929
+ }
13794
13930
  return state;
13795
13931
  }
13796
13932
  case "settingsValueSet": {
@@ -14848,47 +14984,79 @@ import { useEffect as useEffect20, useRef as useRef13, useState as useState7 } f
14848
14984
  import { spawn } from "node:child_process";
14849
14985
  import { buildChildEnv } from "@wrongstack/core/utils";
14850
14986
  async function readGitInfo(cwd) {
14987
+ let added = 0;
14988
+ let deleted = 0;
14989
+ let untracked = 0;
14851
14990
  const [branchRes, numstatRes, statusRes] = await Promise.all([
14852
14991
  runGit(cwd, ["branch", "--show-current"]),
14853
- runGit(cwd, ["diff", "HEAD", "--numstat"]),
14854
- runGit(cwd, ["status", "--porcelain"])
14992
+ foldGit(cwd, ["diff", "HEAD", "--numstat"], (line) => {
14993
+ if (!line) return;
14994
+ const [a, d] = line.split(" ");
14995
+ if (a && a !== "-") added += Number.parseInt(a, 10) || 0;
14996
+ if (d && d !== "-") deleted += Number.parseInt(d, 10) || 0;
14997
+ }),
14998
+ foldGit(cwd, ["status", "--porcelain"], (line) => {
14999
+ if (line.startsWith("?? ")) untracked++;
15000
+ })
14855
15001
  ]);
14856
15002
  if (!branchRes.ok || !numstatRes.ok || !statusRes.ok) return null;
14857
15003
  const branch = branchRes.stdout.trim();
14858
15004
  const branchLabel = branch || await detachedShortSha(cwd) || "detached";
14859
- let added = 0;
14860
- let deleted = 0;
14861
- for (const line of numstatRes.stdout.split("\n")) {
14862
- if (!line) continue;
14863
- const [a, d] = line.split(" ");
14864
- if (a && a !== "-") added += Number.parseInt(a, 10) || 0;
14865
- if (d && d !== "-") deleted += Number.parseInt(d, 10) || 0;
14866
- }
14867
- let untracked = 0;
14868
- for (const line of statusRes.stdout.split("\n")) {
14869
- if (line.startsWith("?? ")) untracked++;
14870
- }
14871
15005
  return { branch: branchLabel, added, deleted, untracked };
14872
15006
  }
14873
15007
  async function detachedShortSha(cwd) {
14874
15008
  const res = await runGit(cwd, ["rev-parse", "--short", "HEAD"]);
14875
15009
  return res.ok ? res.stdout.trim() : null;
14876
15010
  }
15011
+ var MAX_RETAINED_STDOUT = 64 * 1024;
15012
+ var MAX_LINE_CHARS = 64 * 1024;
15013
+ function spawnGit(cwd, args) {
15014
+ return spawn("git", args, {
15015
+ cwd,
15016
+ env: buildChildEnv(),
15017
+ // Inherit stderr (silent) — we don't care about git's noise.
15018
+ stdio: ["ignore", "pipe", "ignore"],
15019
+ // Don't let a slow git hang the TUI.
15020
+ timeout: 3e3,
15021
+ windowsHide: true
15022
+ });
15023
+ }
15024
+ function foldGit(cwd, args, onLine) {
15025
+ return new Promise((resolve) => {
15026
+ try {
15027
+ const child = spawnGit(cwd, args);
15028
+ let carry = "";
15029
+ child.stdout?.setEncoding("utf8");
15030
+ child.stdout?.on("data", (chunk) => {
15031
+ carry += chunk;
15032
+ let start = 0;
15033
+ let nl = carry.indexOf("\n", start);
15034
+ while (nl !== -1) {
15035
+ onLine(carry.slice(start, nl));
15036
+ start = nl + 1;
15037
+ nl = carry.indexOf("\n", start);
15038
+ }
15039
+ if (start > 0) carry = carry.slice(start);
15040
+ if (carry.length > MAX_LINE_CHARS) carry = carry.slice(0, MAX_LINE_CHARS);
15041
+ });
15042
+ child.on("error", () => resolve({ ok: false }));
15043
+ child.on("close", (code) => {
15044
+ if (carry) onLine(carry);
15045
+ resolve({ ok: code === 0 });
15046
+ });
15047
+ } catch {
15048
+ resolve({ ok: false });
15049
+ }
15050
+ });
15051
+ }
14877
15052
  function runGit(cwd, args) {
14878
15053
  return new Promise((resolve) => {
14879
15054
  let stdout = "";
14880
15055
  try {
14881
- const child = spawn("git", args, {
14882
- cwd,
14883
- env: buildChildEnv(),
14884
- // Inherit stderr (silent) — we don't care about git's noise.
14885
- stdio: ["ignore", "pipe", "ignore"],
14886
- // Don't let a slow git hang the TUI.
14887
- timeout: 3e3,
14888
- windowsHide: true
14889
- });
14890
- child.stdout.setEncoding("utf8");
14891
- child.stdout.on("data", (chunk) => {
15056
+ const child = spawnGit(cwd, args);
15057
+ child.stdout?.setEncoding("utf8");
15058
+ child.stdout?.on("data", (chunk) => {
15059
+ if (stdout.length >= MAX_RETAINED_STDOUT) return;
14892
15060
  stdout += chunk;
14893
15061
  });
14894
15062
  child.on("error", () => resolve({ ok: false, stdout: "" }));
@@ -15081,7 +15249,7 @@ function useLiveSettingsState({
15081
15249
  confirmExit
15082
15250
  }) {
15083
15251
  const liveSettings = getSettings?.();
15084
- const liveStatuslineMode = liveSettings?.statuslineMode ?? "detailed";
15252
+ const liveStatuslineMode = liveSettings?.statuslineMode ?? DEFAULT_STATUSLINE_MODE;
15085
15253
  const liveAnimationStyle = liveSettings?.animationStyle ?? "rainbow";
15086
15254
  const liveThinkingWord = liveSettings?.thinkingWord ?? "thinking";
15087
15255
  const chimeRef = useRef17(chime);
@@ -15844,7 +16012,7 @@ function usePanelControllers({
15844
16012
  enhanceEnabled: s2.enhanceEnabled ?? true,
15845
16013
  enhanceLanguage: s2.enhanceLanguage ?? "original",
15846
16014
  debugStream: s2.debugStream ?? false,
15847
- statuslineMode: s2.statuslineMode ?? "detailed",
16015
+ statuslineMode: s2.statuslineMode ?? DEFAULT_STATUSLINE_MODE,
15848
16016
  reasoningMode: s2.reasoningMode ?? "auto",
15849
16017
  reasoningEffort: s2.reasoningEffort ?? "high",
15850
16018
  reasoningPreserve: s2.reasoningPreserve ?? false,
@@ -15856,6 +16024,8 @@ function usePanelControllers({
15856
16024
  breakerAutoKillResetMs: s2.breakerAutoKillResetMs ?? 6e4,
15857
16025
  showModelReasoning: s2.showModelReasoning ?? true,
15858
16026
  showAgentSwarmPanel: s2.showAgentSwarmPanel ?? true,
16027
+ showSageMemoryInject: s2.showSageMemoryInject ?? false,
16028
+ sageMemoryInjectThreshold: s2.sageMemoryInjectThreshold ?? 0.85,
15859
16029
  readSymbols: s2.readSymbols ?? false
15860
16030
  });
15861
16031
  }, [getSettings]);
@@ -16318,6 +16488,7 @@ function usePromptPicker({
16318
16488
 
16319
16489
  // src/hooks/use-provider-event-bridge.ts
16320
16490
  import { tuiStreamFlushMs } from "@wrongstack/core/utils";
16491
+ import { isFinalTurnStopReason as isFinalTurnStopReason2 } from "@wrongstack/tools/next-steps";
16321
16492
  import {
16322
16493
  DEFAULT_MIN_IMPORTANCE,
16323
16494
  DEFAULT_MIN_SCORE,
@@ -16433,6 +16604,65 @@ function shortId(node) {
16433
16604
  return value.length > 22 ? `${value.slice(0, 19)}\u2026` : value;
16434
16605
  }
16435
16606
 
16607
+ // src/tui-memory-counters.ts
16608
+ var counters = {
16609
+ appRenders: 0,
16610
+ providerTextDeltaEvents: 0,
16611
+ providerTextDeltaChars: 0,
16612
+ providerThinkingDeltaEvents: 0,
16613
+ providerThinkingDeltaChars: 0,
16614
+ providerResponses: 0,
16615
+ toolProgressEvents: 0,
16616
+ toolProgressChars: 0,
16617
+ toolExecutedEvents: 0,
16618
+ toolOutputBytes: 0,
16619
+ streamFlushes: 0,
16620
+ historyRetainedEntries: 0,
16621
+ historyGroupedEntries: 0,
16622
+ historyMountedGroups: 0,
16623
+ historyMountedEntries: 0,
16624
+ historyCachedGroups: 0,
16625
+ historyMeasuredGroups: 0,
16626
+ historyViewportRows: 0,
16627
+ historyTotalRows: 0
16628
+ };
16629
+ function add(key, amount = 1) {
16630
+ counters[key] += Number.isFinite(amount) ? Math.max(0, amount) : 0;
16631
+ }
16632
+ function recordTuiAppRender() {
16633
+ add("appRenders");
16634
+ }
16635
+ function recordProviderTextDelta(chars) {
16636
+ add("providerTextDeltaEvents");
16637
+ add("providerTextDeltaChars", chars);
16638
+ }
16639
+ function recordProviderThinkingDelta(chars) {
16640
+ add("providerThinkingDeltaEvents");
16641
+ add("providerThinkingDeltaChars", chars);
16642
+ }
16643
+ function recordProviderResponse() {
16644
+ add("providerResponses");
16645
+ }
16646
+ function recordToolProgress(chars) {
16647
+ add("toolProgressEvents");
16648
+ add("toolProgressChars", chars);
16649
+ }
16650
+ function recordToolExecuted(outputBytes) {
16651
+ add("toolExecutedEvents");
16652
+ add("toolOutputBytes", outputBytes ?? 0);
16653
+ }
16654
+ function recordStreamFlush() {
16655
+ add("streamFlushes");
16656
+ }
16657
+ function setTuiHistoryMemoryGauges(gauges) {
16658
+ for (const [key, value] of Object.entries(gauges)) {
16659
+ counters[key] = Number.isFinite(value) ? Math.max(0, value) : 0;
16660
+ }
16661
+ }
16662
+ function snapshotTuiMemoryCounters() {
16663
+ return { ...counters };
16664
+ }
16665
+
16436
16666
  // src/hooks/subagent-history-format.ts
16437
16667
  function formatHistoryDuration(ms) {
16438
16668
  if (!Number.isFinite(ms) || ms < 0) return "0s";
@@ -16544,14 +16774,11 @@ function useProviderEventBridge({
16544
16774
  }
16545
16775
  };
16546
16776
  const flush = () => {
16547
- if (pendingDeltaRef.current) {
16548
- dispatch({ type: "streamDelta", delta: pendingDeltaRef.current });
16549
- pendingDeltaRef.current = "";
16550
- }
16551
16777
  if (pendingToolStream) {
16552
16778
  dispatch({ type: "toolStreamAppend", ...pendingToolStream });
16553
16779
  pendingToolStream = null;
16554
16780
  }
16781
+ recordStreamFlush();
16555
16782
  flushTimerRef.current = null;
16556
16783
  };
16557
16784
  const scheduleFlush = () => {
@@ -16560,29 +16787,19 @@ function useProviderEventBridge({
16560
16787
  const offDelta = events.on("provider.text_delta", (e) => {
16561
16788
  if (activeRunGenerationRef.current !== sessionGenerationRef.current) return;
16562
16789
  const text = e.text.replace(/\x1b?\[200~|\x1b?\[201~/g, "");
16790
+ recordProviderTextDelta(text.length);
16563
16791
  streamingTextRef.current = retainStreamTail(
16564
16792
  streamingTextRef.current,
16565
16793
  text,
16566
16794
  MAX_ASSISTANT_STREAM_RETAINED_CHARS
16567
16795
  );
16568
- pendingDeltaRef.current = retainStreamTail(
16569
- pendingDeltaRef.current,
16570
- text,
16571
- MAX_ASSISTANT_STREAM_RETAINED_CHARS
16572
- );
16573
16796
  appendStreamSegment("assistant", text);
16574
- scheduleFlush();
16575
16797
  });
16576
16798
  const offThinking = events.on("provider.thinking_delta", (e) => {
16577
16799
  if (activeRunGenerationRef.current !== sessionGenerationRef.current) return;
16578
16800
  const text = e.text.replace(/\x1b?\[200~|\x1b?\[201~/g, "");
16801
+ recordProviderThinkingDelta(text.length);
16579
16802
  appendStreamSegment("thinking", text);
16580
- pendingDeltaRef.current = retainStreamTail(
16581
- pendingDeltaRef.current,
16582
- text,
16583
- MAX_ASSISTANT_STREAM_RETAINED_CHARS
16584
- );
16585
- scheduleFlush();
16586
16803
  });
16587
16804
  const offToolStart = events.on("tool.started", (e) => {
16588
16805
  dispatch({ type: "toolStarted", id: e.id, name: e.name });
@@ -16596,11 +16813,16 @@ function useProviderEventBridge({
16596
16813
  });
16597
16814
  const offToolProgress = events.on("tool.progress", (e) => {
16598
16815
  if (e.event.type !== "partial_output" || !e.event.text) return;
16816
+ recordToolProgress(e.event.text.length);
16599
16817
  const prev = pendingToolStream;
16600
16818
  if (prev && prev.toolUseId === e.id) {
16601
16819
  pendingToolStream = {
16602
16820
  ...prev,
16603
- text: prev.text + e.event.text,
16821
+ text: retainStreamTail(
16822
+ prev.text,
16823
+ e.event.text,
16824
+ MAX_TOOL_STREAM_RETAINED_CHARS
16825
+ ),
16604
16826
  startedAt: prev.startedAt
16605
16827
  };
16606
16828
  } else {
@@ -16615,6 +16837,7 @@ function useProviderEventBridge({
16615
16837
  scheduleFlush();
16616
16838
  });
16617
16839
  const offTool = events.on("tool.executed", (e) => {
16840
+ recordToolExecuted(e.outputBytes);
16618
16841
  if (pendingToolStream) {
16619
16842
  dispatch({ type: "toolStreamAppend", ...pendingToolStream });
16620
16843
  pendingToolStream = null;
@@ -16674,6 +16897,7 @@ function useProviderEventBridge({
16674
16897
  });
16675
16898
  const offProvResp = events.on("provider.response", (e) => {
16676
16899
  if (activeRunGenerationRef.current !== sessionGenerationRef.current) return;
16900
+ recordProviderResponse();
16677
16901
  const segments = canonicalStreamSegments(e.content ?? []);
16678
16902
  const fallbackText = contentBlocksText(e.content);
16679
16903
  streamingTextRef.current = "";
@@ -16684,14 +16908,18 @@ function useProviderEventBridge({
16684
16908
  flushTimerRef.current = null;
16685
16909
  }
16686
16910
  dispatch({ type: "streamReset" });
16911
+ const final = isFinalTurnStopReason2(e.stopReason);
16687
16912
  for (const seg of segments) {
16688
16913
  if (seg.text.trim()) {
16689
- dispatch({ type: "addEntry", entry: { kind: seg.kind, text: seg.text } });
16914
+ dispatch({
16915
+ type: "addEntry",
16916
+ entry: seg.kind === "assistant" ? { kind: "assistant", text: seg.text, final } : { kind: "thinking", text: seg.text }
16917
+ });
16690
16918
  if (seg.kind === "assistant") assistantCommittedThisRunRef.current = true;
16691
16919
  }
16692
16920
  }
16693
16921
  if (segments.length === 0 && fallbackText.trim()) {
16694
- dispatch({ type: "addEntry", entry: { kind: "assistant", text: fallbackText } });
16922
+ dispatch({ type: "addEntry", entry: { kind: "assistant", text: fallbackText, final } });
16695
16923
  assistantCommittedThisRunRef.current = true;
16696
16924
  }
16697
16925
  });
@@ -16753,18 +16981,21 @@ function useProviderEventBridge({
16753
16981
  });
16754
16982
  });
16755
16983
  const offMemoryStale = events.on("memory.staled", (e) => {
16984
+ if (!memoryEventMatchesSession(e, agent.ctx.session.id)) return;
16756
16985
  dispatch({
16757
16986
  type: "addEntry",
16758
16987
  entry: { kind: "warn", text: `SAGE stale: ${e.memoryId} \u2014 ${e.reason}` }
16759
16988
  });
16760
16989
  });
16761
16990
  const offMemoryContradicted = events.on("memory.contradicted", (e) => {
16991
+ if (!memoryEventMatchesSession(e, agent.ctx.session.id)) return;
16762
16992
  dispatch({
16763
16993
  type: "addEntry",
16764
16994
  entry: { kind: "warn", text: `SAGE contradicted: ${e.memoryId}` }
16765
16995
  });
16766
16996
  });
16767
16997
  const offMemoryHygiene = events.on("memory.hygiene_completed", (e) => {
16998
+ if (!memoryEventMatchesSession(e, agent.ctx.session.id)) return;
16768
16999
  dispatch({
16769
17000
  type: "addEntry",
16770
17001
  entry: {
@@ -16828,6 +17059,7 @@ function useProviderEventBridge({
16828
17059
  pendingSageStats.clear();
16829
17060
  });
16830
17061
  const offMemoryLifecycle = events.onPattern("memory.*", (event, payload) => {
17062
+ if (!memoryEventMatchesSession(payload, agent.ctx.session.id)) return;
16831
17063
  const lifecycle = memoryLifecycleEntry(event, payload);
16832
17064
  if (!lifecycle) return;
16833
17065
  dispatch({ type: "addEntry", entry: { kind: "memory-lifecycle", ...lifecycle } });
@@ -17266,6 +17498,8 @@ function useSettingsAutoSave(state, saveSettings, dispatch) {
17266
17498
  breakerAutoKillResetMs: sp.breakerAutoKillResetMs,
17267
17499
  showModelReasoning: sp.showModelReasoning,
17268
17500
  showAgentSwarmPanel: sp.showAgentSwarmPanel,
17501
+ showSageMemoryInject: sp.showSageMemoryInject,
17502
+ sageMemoryInjectThreshold: sp.sageMemoryInjectThreshold,
17269
17503
  readSymbols: sp.readSymbols
17270
17504
  })
17271
17505
  ).then((err) => {
@@ -17315,6 +17549,8 @@ function useSettingsAutoSave(state, saveSettings, dispatch) {
17315
17549
  state.settingsPicker.breakerAutoKillResetMs,
17316
17550
  state.settingsPicker.showModelReasoning,
17317
17551
  state.settingsPicker.showAgentSwarmPanel,
17552
+ state.settingsPicker.showSageMemoryInject,
17553
+ state.settingsPicker.sageMemoryInjectThreshold,
17318
17554
  state.settingsPicker.readSymbols,
17319
17555
  saveSettings
17320
17556
  ]);
@@ -17648,6 +17884,8 @@ function useStatusbarViewModel({
17648
17884
  clearInterval(id);
17649
17885
  };
17650
17886
  }, [taskPath]);
17887
+ const providerMaxTools = agent.ctx.provider.maxToolsCount ?? 0;
17888
+ const droppedTools = providerMaxTools > 0 ? Math.max(0, (agent.ctx.tools?.length ?? 0) - providerMaxTools) : 0;
17651
17889
  return {
17652
17890
  contextBreakdown,
17653
17891
  currentContextTokens,
@@ -17658,7 +17896,8 @@ function useStatusbarViewModel({
17658
17896
  hasVisibleFleetPanel,
17659
17897
  entriesWithLeader,
17660
17898
  planCounts,
17661
- taskCounts
17899
+ taskCounts,
17900
+ droppedTools
17662
17901
  };
17663
17902
  }
17664
17903
 
@@ -17949,131 +18188,12 @@ import { useCallback as useCallback18, useEffect as useEffect39, useMemo as useM
17949
18188
  import * as os from "node:os";
17950
18189
 
17951
18190
  // src/heap-watchdog.ts
17952
- import * as fsp from "node:fs/promises";
17953
- import * as path5 from "node:path";
17954
- import * as v8 from "node:v8";
17955
- import { wstackGlobalRoot } from "@wrongstack/core/utils";
17956
- var MB = 1024 * 1024;
17957
- function defaultHeapLogPath() {
17958
- return path5.join(wstackGlobalRoot(), "logs", "heap.jsonl");
17959
- }
17960
- function takeHeapSample() {
17961
- const m = process.memoryUsage();
17962
- const limit = v8.getHeapStatistics().heap_size_limit || 0;
17963
- return {
17964
- ts: (/* @__PURE__ */ new Date()).toISOString(),
17965
- rss: m.rss,
17966
- heapUsed: m.heapUsed,
17967
- heapTotal: m.heapTotal,
17968
- external: m.external,
17969
- heapLimit: limit,
17970
- load: limit > 0 ? m.heapUsed / limit : 0
17971
- };
17972
- }
17973
- function startHeapWatchdog(opts = {}) {
17974
- const sampleEveryMs = opts.sampleEveryMs ?? 6e4;
17975
- const logEveryMs = opts.logEveryMs ?? 3e5;
17976
- const logPath = opts.logPath ?? defaultHeapLogPath();
17977
- const warnAt = opts.warnAt ?? 0.6;
17978
- const criticalAt = opts.criticalAt ?? 0.85;
17979
- const REARM_MARGIN = 0.05;
17980
- let warnArmed = true;
17981
- let criticalArmed = true;
17982
- let lastLogAt = 0;
17983
- let writeInFlight = false;
17984
- let pendingLine;
17985
- let drainPromise;
17986
- let stopped = false;
17987
- let dirReady = false;
17988
- const writeDiagnosticLine = opts.writeDiagnosticLine ?? (async (targetPath, line) => {
17989
- if (!dirReady) {
17990
- await fsp.mkdir(path5.dirname(targetPath), { recursive: true });
17991
- dirReady = true;
17992
- }
17993
- await fsp.appendFile(targetPath, `${line}
17994
- `, "utf8");
17995
- });
17996
- const drainWrites = async (firstLine) => {
17997
- let line = firstLine;
17998
- while (line !== void 0) {
17999
- try {
18000
- await writeDiagnosticLine(logPath, line);
18001
- } catch {
18002
- }
18003
- line = pendingLine;
18004
- pendingLine = void 0;
18005
- }
18006
- writeInFlight = false;
18007
- };
18008
- const append = (line) => {
18009
- if (stopped) return;
18010
- if (writeInFlight) {
18011
- pendingLine = line;
18012
- return;
18013
- }
18014
- writeInFlight = true;
18015
- drainPromise = drainWrites(line);
18016
- };
18017
- const tick = () => {
18018
- let userTimings = 0;
18019
- try {
18020
- userTimings = performance.getEntriesByType("mark").length + performance.getEntriesByType("measure").length;
18021
- if (userTimings > 0) {
18022
- performance.clearMarks();
18023
- performance.clearMeasures();
18024
- }
18025
- } catch {
18026
- }
18027
- const s2 = takeHeapSample();
18028
- if (s2.load >= criticalAt && criticalArmed) {
18029
- criticalArmed = false;
18030
- warnArmed = false;
18031
- opts.onWarn?.(
18032
- "critical",
18033
- `Heap critical: ${Math.round(s2.heapUsed / MB)} MB of ~${Math.round(s2.heapLimit / MB)} MB V8 limit (${Math.round(s2.load * 100)}%). An out-of-memory crash is likely soon \u2014 finish/checkpoint work and restart the session. Diagnostics: ${logPath}`,
18034
- s2
18035
- );
18036
- } else if (s2.load >= warnAt && warnArmed) {
18037
- warnArmed = false;
18038
- opts.onWarn?.(
18039
- "warn",
18040
- `Heap high: ${Math.round(s2.heapUsed / MB)} MB of ~${Math.round(s2.heapLimit / MB)} MB V8 limit (${Math.round(s2.load * 100)}%). Memory diagnostics are being recorded to ${logPath}`,
18041
- s2
18042
- );
18043
- }
18044
- if (!warnArmed && s2.load < warnAt - REARM_MARGIN) warnArmed = true;
18045
- if (!criticalArmed && s2.load < criticalAt - REARM_MARGIN) criticalArmed = true;
18046
- const due = Date.now() - lastLogAt >= logEveryMs;
18047
- const crossed = s2.load >= warnAt;
18048
- if (due || crossed) {
18049
- lastLogAt = Date.now();
18050
- let extras = {};
18051
- try {
18052
- extras = opts.collectStats?.() ?? {};
18053
- } catch {
18054
- }
18055
- append(JSON.stringify({ pid: process.pid, ...s2, userTimings, ...extras }));
18056
- }
18057
- };
18058
- const timer = setInterval(tick, sampleEveryMs);
18059
- timer.unref?.();
18060
- tick();
18061
- if (process.env["NODE_ENV"] !== "production") {
18062
- append(
18063
- JSON.stringify({
18064
- pid: process.pid,
18065
- warn: "dev_reconciler",
18066
- nodeEnv: process.env["NODE_ENV"] ?? "",
18067
- ts: (/* @__PURE__ */ new Date()).toISOString()
18068
- })
18069
- );
18070
- }
18071
- return async () => {
18072
- stopped = true;
18073
- clearInterval(timer);
18074
- await drainPromise;
18075
- };
18076
- }
18191
+ import {
18192
+ defaultHeapLogPath,
18193
+ startHeapWatchdog,
18194
+ startSharedHeapWatchdog,
18195
+ takeHeapSample
18196
+ } from "@wrongstack/core/utils";
18077
18197
 
18078
18198
  // src/hooks/use-tui-activity.ts
18079
18199
  function attachmentsSize(store) {
@@ -18102,6 +18222,7 @@ function useTuiActivity({
18102
18222
  attachments,
18103
18223
  builderRef
18104
18224
  }) {
18225
+ recordTuiAppRender();
18105
18226
  const [rolledThinkingWord, setRolledThinkingWord] = useState17(() => pickRandomTuiThinkingWord());
18106
18227
  const thinkingWorking = status === "running" || status === "streaming";
18107
18228
  const prevThinkingWorkingRef = useRef28(false);
@@ -18178,30 +18299,38 @@ function useTuiActivity({
18178
18299
  return Math.min(100, Math.round(cpuDeltaUsec / 1e3 / wallMs / CPU_CORES * 100));
18179
18300
  }, [nowTick]);
18180
18301
  useEffect39(() => {
18181
- const stopHeapWatchdog = startHeapWatchdog({
18182
- collectStats: () => ({
18183
- // Keep this slice to shallow cardinalities: serializing the full
18184
- // retained graphs created a second allocation spike precisely when
18185
- // the heap was already high.
18186
- historyEntries: stateRef.current.entries.length,
18187
- messages: agentContext.state.messages.length,
18188
- runningTools: stateRef.current.runningTools.size,
18189
- stdoutQueued: process.stdout.writableLength ?? 0,
18190
- fleetSize: Object.keys(stateRef.current.fleet ?? {}).length,
18191
- queued: stateRef.current.queue?.length ?? 0,
18192
- inputHistory: stateRef.current.inputHistory?.length ?? 0,
18193
- // RAM retention sentinels — distinguish which layer holds old graphs
18194
- // after /clear so we know where to add cleanup wiring.
18195
- // attachments: DefaultAttachmentStore.items.size (cumulative
18196
- // pastes/files/images for the lifetime of this process).
18197
- // builderRefs: InputBuilder.refs.length (refs retained by the
18198
- // mounted builder until reset() is called).
18199
- // directorInFlight: coordinator in-flight task count, used to
18200
- // detect that `/clear` did not actually drain subagent work.
18201
- attachments: attachmentsSize(attachments),
18202
- builderRefs: builderRefsLength(builderRef.current),
18203
- directorInFlight: directorInFlight(agentContext)
18204
- }),
18302
+ const stopHeapWatchdog = startSharedHeapWatchdog({
18303
+ collectStats: () => {
18304
+ const currentState = stateRef.current;
18305
+ const messages = agentContext.state.messages;
18306
+ return {
18307
+ surface: "tui",
18308
+ sessionId: agentContext.session.id,
18309
+ // Keep this slice to shallow cardinalities: serializing the full
18310
+ // retained graphs created a second allocation spike precisely when
18311
+ // the heap was already high.
18312
+ historyEntries: currentState.entries.length,
18313
+ messages: messages.length,
18314
+ runningTools: currentState.runningTools.size,
18315
+ stdoutQueued: process.stdout.writableLength ?? 0,
18316
+ fleetSize: Object.keys(currentState.fleet ?? {}).length,
18317
+ queued: currentState.queue?.length ?? 0,
18318
+ inputHistory: currentState.inputHistory?.length ?? 0,
18319
+ toolStreamChars: currentState.toolStream?.text.length ?? 0,
18320
+ ...snapshotTuiMemoryCounters(),
18321
+ // RAM retention sentinels — distinguish which layer holds old graphs
18322
+ // after /clear so we know where to add cleanup wiring.
18323
+ // attachments: DefaultAttachmentStore.items.size (cumulative
18324
+ // pastes/files/images for the lifetime of this process).
18325
+ // builderRefs: InputBuilder.refs.length (refs retained by the
18326
+ // mounted builder until reset() is called).
18327
+ // directorInFlight: coordinator in-flight task count, used to
18328
+ // detect that `/clear` did not actually drain subagent work.
18329
+ attachments: attachmentsSize(attachments),
18330
+ builderRefs: builderRefsLength(builderRef.current),
18331
+ directorInFlight: directorInFlight(agentContext)
18332
+ };
18333
+ },
18205
18334
  onWarn: (level, message) => {
18206
18335
  dispatch({
18207
18336
  type: "addEntry",
@@ -19074,9 +19203,7 @@ import {
19074
19203
  setDesignOverrides
19075
19204
  } from "@wrongstack/core/design";
19076
19205
  import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
19077
- import {
19078
- useEffect as useEffect44
19079
- } from "react";
19206
+ import { useEffect as useEffect44 } from "react";
19080
19207
 
19081
19208
  // src/components/autonomy-picker.tsx
19082
19209
  import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
@@ -19241,7 +19368,9 @@ function useTuiSlashCommands({
19241
19368
  motion: pairs["motion"]
19242
19369
  });
19243
19370
  if (Object.keys(patch).length === 0) {
19244
- return { message: 'Usage: /design tune radius=lg density=compact font="\u2026" motion=snappy' };
19371
+ return {
19372
+ message: 'Usage: /design tune radius=lg density=compact font="\u2026" motion=snappy'
19373
+ };
19245
19374
  }
19246
19375
  const merged = await recordOverrides(projectRoot, patch, (/* @__PURE__ */ new Date()).toISOString());
19247
19376
  if (!merged) return { message: "No active kit. Pin one first: /design <kit-id>." };
@@ -19283,12 +19412,18 @@ ${menu}` };
19283
19412
  kitId: active.kit,
19284
19413
  outPath
19285
19414
  });
19286
- const fsp2 = await import("node:fs/promises");
19415
+ const fsp = await import("node:fs/promises");
19287
19416
  const nodePath = await import("node:path");
19288
- const abs = nodePath.join(projectRoot, result.path);
19417
+ const { resolveMaterializeTarget } = await import("@wrongstack/core/design");
19418
+ let abs;
19289
19419
  try {
19290
- await fsp2.mkdir(nodePath.dirname(abs), { recursive: true });
19291
- await fsp2.writeFile(abs, result.content);
19420
+ abs = await resolveMaterializeTarget(result.path, projectRoot);
19421
+ } catch (e) {
19422
+ return { message: e.message };
19423
+ }
19424
+ try {
19425
+ await fsp.mkdir(nodePath.dirname(abs), { recursive: true });
19426
+ await fsp.writeFile(abs, result.content);
19292
19427
  } catch (e) {
19293
19428
  return { message: `Failed to write ${result.path}: ${e.message}` };
19294
19429
  }
@@ -19562,11 +19697,11 @@ Available chords:
19562
19697
  }
19563
19698
 
19564
19699
  // src/hooks/use-working-dir-chip.ts
19565
- import * as path6 from "node:path";
19700
+ import * as path5 from "node:path";
19566
19701
  import { useEffect as useEffect45, useState as useState18 } from "react";
19567
19702
  function formatWorkingDirChip(workingDir, projectRoot) {
19568
19703
  if (!workingDir || workingDir === projectRoot) return void 0;
19569
- const impl = workingDir.includes("\\") || projectRoot.includes("\\") ? path6.win32 : path6;
19704
+ const impl = workingDir.includes("\\") || projectRoot.includes("\\") ? path5.win32 : path5;
19570
19705
  const rel = impl.relative(projectRoot, workingDir) || ".";
19571
19706
  if (rel === ".") return void 0;
19572
19707
  return rel.replaceAll(impl.sep, "/");
@@ -22642,7 +22777,7 @@ function KanbanPanel({
22642
22777
  /* @__PURE__ */ jsx24(Text, { dimColor: true, children: "|" }),
22643
22778
  /* @__PURE__ */ jsx24(Text, { children: board.title })
22644
22779
  ] }) : null,
22645
- /* @__PURE__ */ jsx24(Text, { dimColor: true, children: "| n/p board | Tab task | \u2190\u2192 move | c/a/d create/add/dup | C/T copy/transfer | Ctrl+J toggle | Esc close" })
22780
+ /* @__PURE__ */ jsx24(Text, { dimColor: true, children: "| n/p board | Tab task | \u2190\u2192 move | c/a/d create/add/dup | C/T copy/transfer | Ctrl+Y toggle | Esc close" })
22646
22781
  ] }),
22647
22782
  prompt ? /* @__PURE__ */ jsx24(PromptLine, { prompt }) : null,
22648
22783
  notice && !prompt ? /* @__PURE__ */ jsx24(Text, { color: "green", wrap: "truncate", children: notice }) : null,
@@ -23452,7 +23587,7 @@ function PhasePanel({ phases, nowTick }) {
23452
23587
  // src/components/plan-panel.tsx
23453
23588
  import { useCallback as useCallback22, useEffect as useEffect56, useMemo as useMemo11, useState as useState29 } from "react";
23454
23589
  import * as fs4 from "node:fs/promises";
23455
- import * as path7 from "node:path";
23590
+ import * as path6 from "node:path";
23456
23591
  import { resolveWstackPaths as resolveWstackPaths4, sessionScopedPath } from "@wrongstack/core/utils";
23457
23592
  import { jsx as jsx29, jsxs as jsxs28 } from "react/jsx-runtime";
23458
23593
  var STATUS_COLOR = {
@@ -23468,12 +23603,12 @@ var STATUS_ICON = {
23468
23603
  function planFilePath(projectRoot, sessionId, scope) {
23469
23604
  const base = resolveWstackPaths4({ projectRoot }).projectSessions;
23470
23605
  if (scope === "project") {
23471
- return path7.join(base, "backlog.plan.json");
23606
+ return path6.join(base, "backlog.plan.json");
23472
23607
  }
23473
23608
  if (sessionId) {
23474
23609
  return sessionScopedPath(base, sessionId, ".plan.json");
23475
23610
  }
23476
- return path7.join(base, "backlog.plan.json");
23611
+ return path6.join(base, "backlog.plan.json");
23477
23612
  }
23478
23613
  function PlanPanel({
23479
23614
  projectRoot,
@@ -24752,6 +24887,8 @@ function AppStatusRegion({ host, runtime }) {
24752
24887
  agent,
24753
24888
  agentTranscripts,
24754
24889
  appVersion,
24890
+ latestVersion,
24891
+ updateAvailable,
24755
24892
  director,
24756
24893
  events,
24757
24894
  fleetRoster,
@@ -24821,7 +24958,8 @@ function AppStatusRegion({ host, runtime }) {
24821
24958
  hasVisibleFleetPanel,
24822
24959
  entriesWithLeader,
24823
24960
  planCounts,
24824
- taskCounts
24961
+ taskCounts,
24962
+ droppedTools
24825
24963
  } = statusbar;
24826
24964
  const { mailboxStatus, mailboxPanelOpen, mailboxMessages, mailboxAgents } = mailbox;
24827
24965
  const { lowerFunctionPanelOpen } = viewState;
@@ -24833,6 +24971,8 @@ function AppStatusRegion({ host, runtime }) {
24833
24971
  provider: liveProvider,
24834
24972
  model: liveModel,
24835
24973
  version: appVersion,
24974
+ latestVersion,
24975
+ updateAvailable,
24836
24976
  state: state.status,
24837
24977
  thinkingWord: displayThinkingWord,
24838
24978
  thinkingAnimationStyle: (
@@ -24847,6 +24987,7 @@ function AppStatusRegion({ host, runtime }) {
24847
24987
  queueCount: state.queue.length,
24848
24988
  yolo: yoloLive,
24849
24989
  autonomy: autonomyLive,
24990
+ droppedTools,
24850
24991
  startedAt,
24851
24992
  fleetWorkingTime: fleetWorkingTimeMs,
24852
24993
  todos,
@@ -25232,6 +25373,17 @@ var OAUTH_LABEL = {
25232
25373
  claude: { title: "Claude Pro/Max", detail: "\u2192 anthropic-oauth" },
25233
25374
  copilot: { title: "GitHub Copilot", detail: "\u2192 github-copilot" }
25234
25375
  };
25376
+ function formatExpiry(expiresAt) {
25377
+ if (!expiresAt) return null;
25378
+ const expiry = Date.parse(expiresAt);
25379
+ if (!Number.isFinite(expiry)) return null;
25380
+ const now = Date.now();
25381
+ const msLeft = expiry - now;
25382
+ if (msLeft <= 0) return { text: "expired", color: UI_COLORS.error };
25383
+ if (msLeft < 60 * 6e4) return { text: `${Math.round(msLeft / 6e4)}m left`, color: UI_COLORS.error };
25384
+ if (msLeft < 24 * 60 * 6e4) return { text: `${Math.round(msLeft / (60 * 6e4))}h left`, color: UI_COLORS.warning };
25385
+ return { text: `${Math.round(msLeft / (24 * 60 * 6e4))}d left`, color: UI_COLORS.inactive };
25386
+ }
25235
25387
  function keySummary(keyRow) {
25236
25388
  const method = keyRow.authMethod === "oauth" ? " oauth" : "";
25237
25389
  const created = keyRow.createdAt ? ` ${keyRow.createdAt.slice(0, 10)}` : "";
@@ -25264,6 +25416,7 @@ function renderRow2(row, focused, i) {
25264
25416
  case "key": {
25265
25417
  const k = row.keyRow;
25266
25418
  const badge = badgeForKind(k.authMethod);
25419
+ const expiry = formatExpiry(k.expiresAt);
25267
25420
  return /* @__PURE__ */ jsxs38(Text, { color: rowColor, wrap: "truncate-end", children: [
25268
25421
  marker,
25269
25422
  " ",
@@ -25272,7 +25425,11 @@ function renderRow2(row, focused, i) {
25272
25425
  k.label.padEnd(20),
25273
25426
  " ",
25274
25427
  badge ? /* @__PURE__ */ jsx39(Text, { color: badge.color, children: badge.label.padEnd(8) }) : null,
25275
- /* @__PURE__ */ jsx39(Text, { dimColor: true, children: keySummary(k) })
25428
+ /* @__PURE__ */ jsx39(Text, { dimColor: true, children: keySummary(k) }),
25429
+ expiry ? /* @__PURE__ */ jsxs38(Text, { color: expiry.color, children: [
25430
+ " ",
25431
+ expiry.text
25432
+ ] }) : null
25276
25433
  ] }, `k-${k.label}`);
25277
25434
  }
25278
25435
  case "provider-action":
@@ -25971,7 +26128,7 @@ function ClearConfirmPanel({
25971
26128
  }
25972
26129
 
25973
26130
  // src/components/confirm-prompt.tsx
25974
- import { writeOut as writeOut2 } from "@wrongstack/core/utils";
26131
+ import { unifiedDiff, writeOut as writeOut2 } from "@wrongstack/core/utils";
25975
26132
  import React6 from "react";
25976
26133
 
25977
26134
  // src/highlight.ts
@@ -26413,8 +26570,8 @@ function detectLang(fenceInfo) {
26413
26570
  const tag = fenceInfo.trim().toLowerCase().split(/\s+/)[0] ?? "";
26414
26571
  return LANG_ALIASES[tag] ?? "plain";
26415
26572
  }
26416
- function langFromPath(path11) {
26417
- const m = /\.([A-Za-z0-9]+)$/.exec(path11.trim());
26573
+ function langFromPath(path10) {
26574
+ const m = /\.([A-Za-z0-9]+)$/.exec(path10.trim());
26418
26575
  const ext = m?.[1]?.toLowerCase() ?? "";
26419
26576
  return LANG_ALIASES[ext] ?? "plain";
26420
26577
  }
@@ -27726,19 +27883,19 @@ function formatToolArgs(toolName, input) {
27726
27883
  case "codebase-stats":
27727
27884
  return "index health";
27728
27885
  case "lsp_diagnostics": {
27729
- const path11 = stringOf(obj["path"]);
27886
+ const path10 = stringOf(obj["path"]);
27730
27887
  const severity = stringArrayOf(obj["severity"]) ?? stringArrayOf(obj["severities"]);
27731
- return [path11 ? shortenPath(path11, 40) : "workspace", severity?.join("+")].filter(Boolean).join(" \xB7 ");
27888
+ return [path10 ? shortenPath(path10, 40) : "workspace", severity?.join("+")].filter(Boolean).join(" \xB7 ");
27732
27889
  }
27733
27890
  case "lsp_definition":
27734
27891
  case "lsp_completion":
27735
27892
  case "lsp_rename": {
27736
- const path11 = stringOf(obj["path"]) ?? stringOf(obj["file"]);
27893
+ const path10 = stringOf(obj["path"]) ?? stringOf(obj["file"]);
27737
27894
  const line = numOf(obj["line"]);
27738
27895
  const character = numOf(obj["character"]);
27739
27896
  const position = line !== void 0 ? `L${line}:${character ?? 1}` : "";
27740
27897
  const rename = toolName === "lsp_rename" ? stringOf(obj["new_name"]) : void 0;
27741
- return [path11 ? shortenPath(path11, 38) : "", position, rename ? `\u2192 ${rename}` : ""].filter(Boolean).join(" \xB7 ");
27898
+ return [path10 ? shortenPath(path10, 38) : "", position, rename ? `\u2192 ${rename}` : ""].filter(Boolean).join(" \xB7 ");
27742
27899
  }
27743
27900
  case "set_working_dir": {
27744
27901
  const p = stringOf(obj["path"]);
@@ -28181,19 +28338,19 @@ function visualEdit(toolName, text, ok) {
28181
28338
  const parsed = tryParseJson(text);
28182
28339
  if (parsed && typeof parsed === "object") {
28183
28340
  const obj = parsed;
28184
- const path11 = typeof obj["path"] === "string" ? obj["path"] : void 0;
28341
+ const path10 = typeof obj["path"] === "string" ? obj["path"] : void 0;
28185
28342
  const replacements = typeof obj["replacements"] === "number" ? obj["replacements"] : void 0;
28186
28343
  const bytes = typeof obj["bytes"] === "number" ? obj["bytes"] : void 0;
28187
28344
  const created = obj["created"] === true;
28188
28345
  const files = Array.isArray(obj["files"]) ? obj["files"] : void 0;
28189
28346
  const results = Array.isArray(obj["results"]) ? obj["results"] : void 0;
28190
- if (toolName === "edit" && path11 !== void 0) {
28347
+ if (toolName === "edit" && path10 !== void 0) {
28191
28348
  const repText = replacements !== void 0 ? `${replacements} replacement${replacements === 1 ? "" : "s"}` : void 0;
28192
- rows.push({ kind: "ok", text: "", marker: "edit ", path: path11 });
28349
+ rows.push({ kind: "ok", text: "", marker: "edit ", path: path10 });
28193
28350
  if (repText) rows.push({ kind: "meta", text: repText });
28194
- } else if (toolName === "write" && path11 !== void 0) {
28351
+ } else if (toolName === "write" && path10 !== void 0) {
28195
28352
  const sizeText = bytes !== void 0 ? `${bytes} bytes` : created ? "new file" : "updated";
28196
- rows.push({ kind: "ok", text: "", marker: "write ", path: path11 });
28353
+ rows.push({ kind: "ok", text: "", marker: "write ", path: path10 });
28197
28354
  rows.push({ kind: "meta", text: sizeText });
28198
28355
  } else if ((toolName === "diff" || toolName === "patch") && files && files.length > 0) {
28199
28356
  rows.push({
@@ -28216,8 +28373,8 @@ function visualEdit(toolName, text, ok) {
28216
28373
  marker: "replace ",
28217
28374
  text: `${results.length} replacement${results.length === 1 ? "" : "s"} across ${fileCount} file${fileCount === 1 ? "" : "s"}`
28218
28375
  });
28219
- } else if (path11 !== void 0) {
28220
- rows.push({ kind: "ok", text: "", marker: `${toolName} `, path: path11 });
28376
+ } else if (path10 !== void 0) {
28377
+ rows.push({ kind: "ok", text: "", marker: `${toolName} `, path: path10 });
28221
28378
  } else {
28222
28379
  return void 0;
28223
28380
  }
@@ -28311,12 +28468,12 @@ function parseMatchHit(hit) {
28311
28468
  }
28312
28469
  if (hit && typeof hit === "object") {
28313
28470
  const o = hit;
28314
- const path11 = stringOf(o["file"]) ?? stringOf(o["path"]) ?? stringOf(o["url"]);
28471
+ const path10 = stringOf(o["file"]) ?? stringOf(o["path"]) ?? stringOf(o["url"]);
28315
28472
  const line = numOf(o["line"]) ?? numOf(o["lineNumber"]);
28316
28473
  const title = stringOf(o["title"]);
28317
28474
  const snippet2 = stringOf(o["snippet"]);
28318
28475
  const text = stringOf(o["text"]) ?? stringOf(o["match"]) ?? stringOf(o["preview"]) ?? [title, snippet2].filter(Boolean).join(" \u2014 ");
28319
- return { path: path11, line: line === void 0 ? void 0 : String(line), text };
28476
+ return { path: path10, line: line === void 0 ? void 0 : String(line), text };
28320
28477
  }
28321
28478
  return void 0;
28322
28479
  }
@@ -28414,8 +28571,8 @@ function splitReplaceDiffs(obj) {
28414
28571
  const record = result;
28415
28572
  const diff = stringOf(record["diff"]);
28416
28573
  if (!diff?.trim()) continue;
28417
- const path11 = stringOf(record["path"]);
28418
- items.push(path11 ? { path: path11, diff } : { diff });
28574
+ const path10 = stringOf(record["path"]);
28575
+ items.push(path10 ? { path: path10, diff } : { diff });
28419
28576
  }
28420
28577
  return items;
28421
28578
  }
@@ -28490,8 +28647,8 @@ function collectMultiFileDiffItems(toolName, output, _input) {
28490
28647
  if (!diffText?.trim()) return void 0;
28491
28648
  const blocks = splitGitStyleDiff(diffText);
28492
28649
  if (blocks.length === 0) {
28493
- const path11 = explicitFiles[0] ?? extractPatchHeaderPath(diffText);
28494
- return [{ path: path11, diff: diffText }];
28650
+ const path10 = explicitFiles[0] ?? extractPatchHeaderPath(diffText);
28651
+ return [{ path: path10, diff: diffText }];
28495
28652
  }
28496
28653
  return blocks.map((block, idx) => ({
28497
28654
  path: explicitFiles[idx] ?? block.path,
@@ -28525,9 +28682,9 @@ function newFileDiffFromWriteInput(output, input) {
28525
28682
  const obj = input;
28526
28683
  const content = stringOf(obj["content"]);
28527
28684
  if (content === void 0) return void 0;
28528
- const path11 = stringOf(output["path"]) ?? stringOf(obj["path"]) ?? "new file";
28685
+ const path10 = stringOf(output["path"]) ?? stringOf(obj["path"]) ?? "new file";
28529
28686
  const lines = content === "" ? [] : content.replace(/\n$/, "").split("\n");
28530
- const header = [`+++ ${path11}`, `@@ -0,0 +1,${lines.length} @@`];
28687
+ const header = [`+++ ${path10}`, `@@ -0,0 +1,${lines.length} @@`];
28531
28688
  return [...header, ...lines.map((line) => `+${line}`)].join("\n");
28532
28689
  }
28533
28690
  function splitGitStyleDiff(diff) {
@@ -28567,8 +28724,8 @@ function splitGitStyleDiff(diff) {
28567
28724
  }
28568
28725
  flush();
28569
28726
  if (blocks.length === 1 && !blocks[0].path) {
28570
- const path11 = extractPatchHeaderPath(blocks[0].diff);
28571
- if (path11) blocks[0] = { path: path11, diff: blocks[0].diff };
28727
+ const path10 = extractPatchHeaderPath(blocks[0].diff);
28728
+ if (path10) blocks[0] = { path: path10, diff: blocks[0].diff };
28572
28729
  }
28573
28730
  return blocks;
28574
28731
  }
@@ -28589,8 +28746,8 @@ function parseDiffGitPath(line) {
28589
28746
  function extractPatchHeaderPath(diffText) {
28590
28747
  for (const line of diffText.split("\n")) {
28591
28748
  if (line.startsWith("+++ ")) {
28592
- const path11 = line.slice(4).trim();
28593
- const cleaned = path11.replace(/^b\//, "").split(" ")[0].trim();
28749
+ const path10 = line.slice(4).trim();
28750
+ const cleaned = path10.replace(/^b\//, "").split(" ")[0].trim();
28594
28751
  return cleaned || void 0;
28595
28752
  }
28596
28753
  }
@@ -28722,13 +28879,13 @@ function formatMultiDiffSummary(summary, threshold = MULTI_DIFF_SUMMARY_THRESHOL
28722
28879
  return parts.join(" \xB7 ");
28723
28880
  }
28724
28881
  function DiffFileBlock({
28725
- path: path11,
28882
+ path: path10,
28726
28883
  preview,
28727
28884
  useColor = true,
28728
28885
  contentWidth
28729
28886
  }) {
28730
28887
  return /* @__PURE__ */ jsxs45(Box, { flexDirection: "column", marginTop: 1, children: [
28731
- /* @__PURE__ */ jsx46(Text, { dimColor: true, italic: true, children: path11 }),
28888
+ /* @__PURE__ */ jsx46(Text, { dimColor: true, italic: true, children: path10 }),
28732
28889
  /* @__PURE__ */ jsx46(
28733
28890
  DiffBlock,
28734
28891
  {
@@ -28739,7 +28896,7 @@ function DiffFileBlock({
28739
28896
  hiddenAdded: preview.hiddenAdded,
28740
28897
  hiddenRemoved: preview.hiddenRemoved,
28741
28898
  useColor,
28742
- lang: langFromPath(path11),
28899
+ lang: langFromPath(path10),
28743
28900
  contentWidth
28744
28901
  }
28745
28902
  )
@@ -28983,10 +29140,21 @@ function stringifyInput(input) {
28983
29140
  function truncate2(s2, max) {
28984
29141
  return s2.length <= max ? s2 : `${s2.slice(0, max - 1)}\u2026`;
28985
29142
  }
28986
- function hasDiff(input) {
28987
- return Boolean(
28988
- input && typeof input === "object" && "diff" in input
28989
- );
29143
+ function payloadDiff(input) {
29144
+ if (!input || typeof input !== "object") return "";
29145
+ const obj = input;
29146
+ const filePath = typeof obj["path"] === "string" ? obj["path"] : "file";
29147
+ if (typeof obj["diff"] === "string" && obj["diff"]) return obj["diff"];
29148
+ const newString = obj["new_string"];
29149
+ if (typeof newString === "string") {
29150
+ const oldString = typeof obj["old_string"] === "string" ? obj["old_string"] : "";
29151
+ return unifiedDiff(oldString, newString, { fromFile: filePath, toFile: filePath, context: 2 });
29152
+ }
29153
+ const content = obj["content"];
29154
+ if (typeof content === "string" && content) {
29155
+ return unifiedDiff("", content, { fromFile: "/dev/null", toFile: filePath, context: 0 });
29156
+ }
29157
+ return "";
28990
29158
  }
28991
29159
  var CONFIRM_DIFF_MAX_LINES = 20;
28992
29160
  function renderDiffLine(line) {
@@ -28996,7 +29164,7 @@ function renderDiffLine(line) {
28996
29164
  "\n"
28997
29165
  ] }, line);
28998
29166
  }
28999
- function renderDiff(diff, path11) {
29167
+ function renderDiff(diff, path10) {
29000
29168
  const preview = parseUnifiedDiff(diff, CONFIRM_DIFF_MAX_LINES);
29001
29169
  if (preview.rows.length > 0) {
29002
29170
  return /* @__PURE__ */ jsx47(
@@ -29009,7 +29177,7 @@ function renderDiff(diff, path11) {
29009
29177
  hiddenAdded: preview.hiddenAdded,
29010
29178
  hiddenRemoved: preview.hiddenRemoved,
29011
29179
  useColor: theme.supportsBackground,
29012
- lang: langFromPath(path11 ?? "")
29180
+ lang: langFromPath(path10 ?? "")
29013
29181
  }
29014
29182
  );
29015
29183
  }
@@ -29046,9 +29214,8 @@ function ConfirmPrompt({
29046
29214
  }
29047
29215
  });
29048
29216
  const inputSummary = stringifyInput(input);
29049
- const showDiff = hasDiff(input);
29050
29217
  const inp = input;
29051
- const diff = typeof inp?.diff === "string" ? inp.diff : "";
29218
+ const diff = payloadDiff(input);
29052
29219
  const diffPath = typeof inp?.path === "string" ? inp.path : void 0;
29053
29220
  return /* @__PURE__ */ jsxs46(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
29054
29221
  /* @__PURE__ */ jsxs46(Box, { flexDirection: "row", children: [
@@ -29061,7 +29228,7 @@ function ConfirmPrompt({
29061
29228
  "KANBAN BOUNDARY: ",
29062
29229
  boundaryReason
29063
29230
  ] }) : null,
29064
- showDiff && diff ? /* @__PURE__ */ jsx47(Box, { flexDirection: "column", marginY: 1, children: renderDiff(diff, diffPath) }) : null,
29231
+ diff ? /* @__PURE__ */ jsx47(Box, { flexDirection: "column", marginY: 1, children: renderDiff(diff, diffPath) }) : null,
29065
29232
  /* @__PURE__ */ jsx47(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
29066
29233
  /* @__PURE__ */ jsx47(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx47(Text, { children: buttonLabels(suggestedPattern).map((l) => /* @__PURE__ */ jsxs46(React6.Fragment, { children: [
29067
29234
  /* @__PURE__ */ jsx47(Text, { bold: true, color: BUTTON_COLOR[l.decision], children: l.bracket }),
@@ -30204,8 +30371,8 @@ function FilePicker({ query, matches, selected }) {
30204
30371
  ] }, m))
30205
30372
  ] });
30206
30373
  }
30207
- function highlight(path11, _query) {
30208
- return path11;
30374
+ function highlight(path10, _query) {
30375
+ return path10;
30209
30376
  }
30210
30377
 
30211
30378
  // src/components/help-panel.tsx
@@ -31553,14 +31720,14 @@ function estimateSageKvRows(cells, contentWidth) {
31553
31720
  }
31554
31721
  return rows;
31555
31722
  }
31556
- function estimateRenderGroupRows(group, contentWidth) {
31723
+ function estimateRenderGroupRows(group, contentWidth, showSageMemoryInject) {
31557
31724
  if (group.type === "tool-group") return group.data.entries.length + 2;
31558
31725
  const { entry } = group;
31559
31726
  if (entry.kind === "tool") {
31560
31727
  const { cleanOutput, sageLines } = resolveEntrySage(entry.output, entry.sageLines);
31561
31728
  const memoryLines = sageLines.slice(1);
31562
31729
  const panelContentWidth = Math.max(16, contentWidth - 4);
31563
- const sagePanelRows = memoryLines.length > 0 ? 2 + estimateTextRows(
31730
+ const sagePanelRows = showSageMemoryInject === false || memoryLines.length === 0 ? 0 : 2 + estimateTextRows(
31564
31731
  `\u{1F9E0} SAGE MEMORY INJECTED \xB7 ${entry.name} ${memoryLines.length} ${memoryLines.length === 1 ? "memory" : "memories"}${entry.sageStats ? ` \xB7 ${entry.sageStats}` : ""}`,
31565
31732
  panelContentWidth,
31566
31733
  MAX_TEXT_ESTIMATE_ROWS
@@ -31583,7 +31750,7 @@ function estimateRenderGroupRows(group, contentWidth) {
31583
31750
  const kvRows = estimateSageKvRows(kvCells, panelContentWidth);
31584
31751
  const gap = rows > 0 ? 1 : 0;
31585
31752
  return rows + labelRow + bodyRow + kvRows + gap;
31586
- }, 0) : 0;
31753
+ }, 0);
31587
31754
  if (entry.resultRenderMode === "simple") return 3 + sagePanelRows;
31588
31755
  if (STRUCTURED_DIFF_TOOLS.has(entry.name)) {
31589
31756
  const previewCap = entry.name === "replace" || entry.name === "diff" || entry.name === "patch" ? MULTI_DIFF_MAX_ROWS : DIFF_MAX_LINES;
@@ -32184,6 +32351,39 @@ function parseInline(text) {
32184
32351
  continue;
32185
32352
  }
32186
32353
  }
32354
+ if (ch === "[") {
32355
+ const textEnd = text.indexOf("]", i + 1);
32356
+ if (textEnd > i && text[textEnd + 1] === "(") {
32357
+ const urlEnd = text.indexOf(")", textEnd + 2);
32358
+ if (urlEnd > textEnd) {
32359
+ flush();
32360
+ const linkText = text.slice(i + 1, textEnd);
32361
+ tokens.push({ text: linkText, link: true });
32362
+ i = urlEnd + 1;
32363
+ continue;
32364
+ }
32365
+ }
32366
+ }
32367
+ if (text.startsWith("http://", i) || text.startsWith("https://", i)) {
32368
+ let urlEnd = i + 8;
32369
+ while (urlEnd < text.length) {
32370
+ const c = text[urlEnd];
32371
+ if (c !== void 0 && c.match(/[a-zA-Z0-9._~:/?#@!$&+,;=%\-\[\]()'*]/)) {
32372
+ urlEnd++;
32373
+ } else {
32374
+ break;
32375
+ }
32376
+ }
32377
+ const trailing = text.slice(i, urlEnd);
32378
+ const stripped = trailing.replace(/[.,;!?)]+$/, "");
32379
+ urlEnd = i + stripped.length;
32380
+ if (urlEnd > i + 8) {
32381
+ flush();
32382
+ tokens.push({ text: stripped, link: true });
32383
+ i = urlEnd;
32384
+ continue;
32385
+ }
32386
+ }
32187
32387
  plain += ch;
32188
32388
  i += 1;
32189
32389
  }
@@ -32201,7 +32401,7 @@ function InlineLine({ tokens, dim }) {
32201
32401
  return /* @__PURE__ */ jsx66(Text, { children: tokens.map((t, j) => /* @__PURE__ */ jsx66(
32202
32402
  Text,
32203
32403
  {
32204
- color: t.code ? theme.accent : "white",
32404
+ color: t.code ? theme.accent : t.link ? theme.accent : "white",
32205
32405
  bold: Boolean(t.bold),
32206
32406
  italic: Boolean(t.italic),
32207
32407
  strikethrough: Boolean(t.strike),
@@ -32358,7 +32558,7 @@ var ASSISTANT_TAIL_HEIGHT = ASSISTANT_TAIL_MARGIN_Y + ASSISTANT_TAIL_HEADER_ROWS
32358
32558
 
32359
32559
  // src/components/history/banner.tsx
32360
32560
  import { Fragment as Fragment22, jsx as jsx68, jsxs as jsxs66 } from "react/jsx-runtime";
32361
- var STACK_ORANGE = "#FD9F02";
32561
+ var STACK_ORANGE2 = "#FD9F02";
32362
32562
  var SIGNAL_PINK = "#FE2E5F";
32363
32563
  var BORDER = "#3B3C36";
32364
32564
  var MUTED = "#B6B2A8";
@@ -32392,7 +32592,7 @@ function BrandMark({ pinkRow = 1 }) {
32392
32592
  if (column === PINK_HOME && (row === pinkRow || row === pinkRow + 1)) {
32393
32593
  return SIGNAL_PINK;
32394
32594
  }
32395
- if (row < 2 && column !== PINK_HOME) return STACK_ORANGE;
32595
+ if (row < 2 && column !== PINK_HOME) return STACK_ORANGE2;
32396
32596
  return null;
32397
32597
  })
32398
32598
  );
@@ -32443,7 +32643,7 @@ var WORDMARK_LINES = Object.freeze(
32443
32643
  );
32444
32644
  function bannerGradientColor(position, length) {
32445
32645
  const progress = length > 1 ? position / (length - 1) : 0.5;
32446
- return mixHex(STACK_ORANGE, SIGNAL_PINK, progress);
32646
+ return mixHex(STACK_ORANGE2, SIGNAL_PINK, progress);
32447
32647
  }
32448
32648
  function GradientText({ text }) {
32449
32649
  return /* @__PURE__ */ jsx68(Text, { bold: true, children: [...text].map(
@@ -32468,7 +32668,7 @@ function InfoRow({
32468
32668
  }) {
32469
32669
  const labelWidth = compact ? 6 : 9;
32470
32670
  const valueWidth = Math.max(1, contentWidth - labelWidth - 3);
32471
- const color = accent ? STACK_ORANGE : MUTED;
32671
+ const color = accent ? STACK_ORANGE2 : MUTED;
32472
32672
  return /* @__PURE__ */ jsxs66(Text, { children: [
32473
32673
  /* @__PURE__ */ jsx68(Text, { color, children: icon }),
32474
32674
  /* @__PURE__ */ jsx68(Text, { color, bold: true, children: ` ${trunc(label, labelWidth).padEnd(labelWidth)} ` }),
@@ -32505,11 +32705,11 @@ function ProfileRow({
32505
32705
  const profileSegment = segmentMatch?.[1] ?? "";
32506
32706
  const segIdx = profileSegment ? displayPath.lastIndexOf(profileSegment) : -1;
32507
32707
  return /* @__PURE__ */ jsxs66(Text, { children: [
32508
- /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE, children: icon }),
32509
- /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE, bold: true, children: ` ${trunc(label, labelWidth).padEnd(labelWidth)} ` }),
32708
+ /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE2, children: icon }),
32709
+ /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE2, bold: true, children: ` ${trunc(label, labelWidth).padEnd(labelWidth)} ` }),
32510
32710
  segIdx >= 0 && profileSegment ? /* @__PURE__ */ jsxs66(Fragment22, { children: [
32511
32711
  /* @__PURE__ */ jsx68(Text, { color: MUTED, children: displayPath.slice(0, segIdx) }),
32512
- /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE, bold: true, children: profileSegment }),
32712
+ /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE2, bold: true, children: profileSegment }),
32513
32713
  /* @__PURE__ */ jsx68(Text, { color: MUTED, children: displayPath.slice(segIdx + profileSegment.length) })
32514
32714
  ] }) : /* @__PURE__ */ jsx68(Text, { color: MUTED, children: displayPath })
32515
32715
  ] });
@@ -32522,7 +32722,7 @@ function Footer({ contentWidth, compact }) {
32522
32722
  /* @__PURE__ */ jsx68(Text, { dimColor: true, children: OSC8_WRONGSTACK }),
32523
32723
  !compact || contentWidth >= 35 ? /* @__PURE__ */ jsxs66(Fragment22, { children: [
32524
32724
  /* @__PURE__ */ jsx68(Text, { dimColor: true, children: " \xB7 " }),
32525
- /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE, children: "\u2605 " }),
32725
+ /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE2, children: "\u2605 " }),
32526
32726
  /* @__PURE__ */ jsx68(Text, { dimColor: true, children: OSC8_GITHUB })
32527
32727
  ] }) : null
32528
32728
  ] }) });
@@ -32548,7 +32748,7 @@ function AutonomyAgentsSection({
32548
32748
  if (!agent) return null;
32549
32749
  const online = agent.online;
32550
32750
  const indicator = online ? "\u25CF" : "\xB7";
32551
- const color = online ? STACK_ORANGE : MUTED;
32751
+ const color = online ? STACK_ORANGE2 : MUTED;
32552
32752
  const detail = agent.detail;
32553
32753
  return /* @__PURE__ */ jsxs66(Box, { flexDirection: "row", marginRight: 2, children: [
32554
32754
  /* @__PURE__ */ jsx68(Text, { color, children: indicator }),
@@ -32595,9 +32795,9 @@ function Banner({
32595
32795
  Math.max(1, panelWidth - (unbordered ? 0 : 4))
32596
32796
  );
32597
32797
  if (unbordered) {
32598
- return /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE, bold: true, children: summary });
32798
+ return /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE2, bold: true, children: summary });
32599
32799
  }
32600
- return /* @__PURE__ */ jsx68(Box, { width: panelWidth, borderStyle: "round", borderColor: BORDER, paddingX: 1, children: /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE, bold: true, children: summary }) });
32800
+ return /* @__PURE__ */ jsx68(Box, { width: panelWidth, borderStyle: "round", borderColor: BORDER, paddingX: 1, children: /* @__PURE__ */ jsx68(Text, { color: STACK_ORANGE2, bold: true, children: summary }) });
32601
32801
  }
32602
32802
  return /* @__PURE__ */ jsxs66(
32603
32803
  Box,
@@ -32614,7 +32814,7 @@ function Banner({
32614
32814
  "v",
32615
32815
  version
32616
32816
  ] }),
32617
- entry.updateAvailable && entry.latestVersion ? /* @__PURE__ */ jsxs66(Text, { color: STACK_ORANGE, children: [
32817
+ entry.updateAvailable && entry.latestVersion ? /* @__PURE__ */ jsxs66(Text, { color: STACK_ORANGE2, children: [
32618
32818
  " \xB7 (update available: v",
32619
32819
  entry.latestVersion,
32620
32820
  ")"
@@ -32842,9 +33042,11 @@ var Entry = React8.memo(function Entry2({
32842
33042
  autonomyMode,
32843
33043
  multiDiffSummaryThreshold,
32844
33044
  todos,
32845
- showModelReasoning
33045
+ showModelReasoning,
33046
+ showSageMemoryInject
32846
33047
  }) {
32847
33048
  const openTodos = hasOpenTodos(todos);
33049
+ const isFinalTurnEntry = entry.kind === "assistant" && entry.final === true;
32848
33050
  const nextSteps = useMemo13(() => {
32849
33051
  if (entry.kind !== "assistant") return { steps: [], stripped: "" };
32850
33052
  return parseNextSteps(entry.text, true);
@@ -32853,10 +33055,17 @@ var Entry = React8.memo(function Entry2({
32853
33055
  if (!setSuggestions) return;
32854
33056
  if (entry.kind !== "assistant") return;
32855
33057
  if (openTodos) return;
33058
+ if (!isFinalTurnEntry) return;
32856
33059
  const text = entry.text ?? "";
32857
33060
  const { texts } = parseNextSteps(text, true);
32858
33061
  if (texts.length > 0) setSuggestions(texts);
32859
- }, [entry.kind, entry.text, openTodos, setSuggestions]);
33062
+ }, [
33063
+ entry.kind,
33064
+ entry.text,
33065
+ openTodos,
33066
+ isFinalTurnEntry,
33067
+ setSuggestions
33068
+ ]);
32860
33069
  switch (entry.kind) {
32861
33070
  case "user":
32862
33071
  return /* @__PURE__ */ jsx70(
@@ -32911,7 +33120,7 @@ var Entry = React8.memo(function Entry2({
32911
33120
  case "assistant": {
32912
33121
  const contentWidth = assistantContentWidth(termWidth);
32913
33122
  const { steps, stripped } = nextSteps;
32914
- const hasNext = steps.length > 0 && !openTodos;
33123
+ const hasNext = steps.length > 0 && !openTodos && isFinalTurnEntry;
32915
33124
  return /* @__PURE__ */ jsxs68(Box, { flexDirection: "column", children: [
32916
33125
  /* @__PURE__ */ jsxs68(
32917
33126
  Box,
@@ -33011,15 +33220,15 @@ var Entry = React8.memo(function Entry2({
33011
33220
  const outObj = outJson && typeof outJson === "object" ? outJson : void 0;
33012
33221
  const created = name === "write" && (outObj?.["created"] === true || /^[^\n]*\bcreated=true\b/.test(outputForFormatting));
33013
33222
  const verb = created ? "Write" : "Update";
33014
- const path11 = stringOf(inputObj?.["path"]) ?? stringOf(outObj?.["path"]);
33223
+ const path10 = stringOf(inputObj?.["path"]) ?? stringOf(outObj?.["path"]);
33015
33224
  const agg = multiDiffs2 ? summarizeMultiFileDiffs(multiDiffs2) : void 0;
33016
- const target = multiDiffs2 ? agg?.fileCount === 1 ? multiDiffs2[0].path : `${agg?.fileCount ?? multiDiffs2.length} files` : path11 ?? "file";
33225
+ const target = multiDiffs2 ? agg?.fileCount === 1 ? multiDiffs2[0].path : `${agg?.fileCount ?? multiDiffs2.length} files` : path10 ?? "file";
33017
33226
  return {
33018
33227
  verb,
33019
33228
  target,
33020
33229
  added: agg ? agg.added : diff2?.added ?? 0,
33021
33230
  removed: agg ? agg.removed : diff2?.removed ?? 0,
33022
- lang: langFromPath(multiDiffs2 ? "" : path11 ?? "")
33231
+ lang: langFromPath(multiDiffs2 ? "" : path10 ?? "")
33023
33232
  };
33024
33233
  })();
33025
33234
  return {
@@ -33103,14 +33312,14 @@ var Entry = React8.memo(function Entry2({
33103
33312
  ]
33104
33313
  }
33105
33314
  ),
33106
- /* @__PURE__ */ jsx70(
33315
+ showSageMemoryInject !== false ? /* @__PURE__ */ jsx70(
33107
33316
  SageMemoryBlock,
33108
33317
  {
33109
33318
  sageLines,
33110
33319
  toolName: entry.name,
33111
33320
  stats: entry.sageStats
33112
33321
  }
33113
- )
33322
+ ) : null
33114
33323
  ] });
33115
33324
  }
33116
33325
  const toolContentWidth = Math.max(20, termWidth - 2);
@@ -33190,7 +33399,7 @@ var Entry = React8.memo(function Entry2({
33190
33399
  ]
33191
33400
  }
33192
33401
  ),
33193
- /* @__PURE__ */ jsx70(SageMemoryBlock, { sageLines, toolName: entry.name, stats: entry.sageStats })
33402
+ showSageMemoryInject !== false ? /* @__PURE__ */ jsx70(SageMemoryBlock, { sageLines, toolName: entry.name, stats: entry.sageStats }) : null
33194
33403
  ] });
33195
33404
  }
33196
33405
  case "memory-activation": {
@@ -33467,7 +33676,8 @@ var History = memo2(function History2({
33467
33676
  autonomyMode,
33468
33677
  multiDiffSummaryThreshold,
33469
33678
  todos,
33470
- showModelReasoning
33679
+ showModelReasoning,
33680
+ showSageMemoryInject
33471
33681
  }) {
33472
33682
  const { stdout } = useStdout();
33473
33683
  const [termSize, setTermSize] = useState39({
@@ -33542,7 +33752,8 @@ var History = memo2(function History2({
33542
33752
  autonomyMode,
33543
33753
  multiDiffSummaryThreshold,
33544
33754
  todos,
33545
- showModelReasoning
33755
+ showModelReasoning,
33756
+ showSageMemoryInject
33546
33757
  }
33547
33758
  ) }, entry.id) }, staticKey),
33548
33759
  /* @__PURE__ */ jsx71(Box, { flexGrow: 1 })
@@ -33552,6 +33763,7 @@ var History = memo2(function History2({
33552
33763
  // src/components/scrollable-history.tsx
33553
33764
  import { jsx as jsx72, jsxs as jsxs70 } from "react/jsx-runtime";
33554
33765
  var LIVE_TOOL_STREAM_COPY_ID = -1;
33766
+ var SELECTION_COPY_ID = -2;
33555
33767
  function findCopyHit(hits, row, col) {
33556
33768
  for (let i = hits.length - 1; i >= 0; i--) {
33557
33769
  const hit = hits[i];
@@ -33581,7 +33793,8 @@ function copyRegistryVisibleClip(opts) {
33581
33793
  return Math.max(0, opts.mountedRows + opts.tailRows - opts.viewportRows);
33582
33794
  }
33583
33795
  function liveToolStreamCopyHit(opts) {
33584
- const headerRow = opts.mountedRows - opts.visibleClip + 1;
33796
+ const slack = opts.pinnedSlack ?? 0;
33797
+ const headerRow = opts.mountedRows - opts.visibleClip + 1 + slack;
33585
33798
  if (!opts.visible || headerRow < 0 || headerRow >= opts.viewportRows) return null;
33586
33799
  return {
33587
33800
  entryId: LIVE_TOOL_STREAM_COPY_ID,
@@ -33590,6 +33803,36 @@ function liveToolStreamCopyHit(opts) {
33590
33803
  iconCol: opts.iconCol
33591
33804
  };
33592
33805
  }
33806
+ function normalizeSelection(a, b, inProgress) {
33807
+ const topLeft = { row: Math.min(a.row, b.row), col: Math.min(a.col, b.col) };
33808
+ const bottomRight = { row: Math.max(a.row, b.row), col: Math.max(a.col, b.col) };
33809
+ return { topLeft, bottomRight, inProgress };
33810
+ }
33811
+ function isOutOfBand(col, termWidth) {
33812
+ return col < 0 || col >= termWidth;
33813
+ }
33814
+ function selectionToSlices(opts) {
33815
+ const { selection, cards, cardVisibleCols } = opts;
33816
+ const maxCol = Math.max(0, cardVisibleCols - 1);
33817
+ const result = [];
33818
+ for (const card of cards) {
33819
+ const visTop = Math.max(selection.topLeft.row, card.viewportStartRow);
33820
+ const visBot = Math.min(selection.bottomRight.row, card.viewportEndRow - 1);
33821
+ if (visTop > visBot) continue;
33822
+ const entryStartRow = visTop - card.viewportStartRow;
33823
+ const entryEndRow = visBot - card.viewportStartRow;
33824
+ const startCol = entryStartRow === selection.topLeft.row - card.viewportStartRow ? Math.max(0, Math.min(maxCol, selection.topLeft.col)) : 0;
33825
+ const endCol = entryEndRow === selection.bottomRight.row - card.viewportStartRow ? Math.max(startCol, Math.min(maxCol, selection.bottomRight.col)) : maxCol;
33826
+ result.push({
33827
+ entryId: card.entryId,
33828
+ startRow: entryStartRow,
33829
+ startCol,
33830
+ endRow: entryEndRow,
33831
+ endCol
33832
+ });
33833
+ }
33834
+ return result;
33835
+ }
33593
33836
  function buildCopyRegistry(opts) {
33594
33837
  const mountedGroupRows = opts.renderGroups.reduce(
33595
33838
  (rows, group) => rows + (opts.heightCache.getHeight(renderGroupId(group)) ?? 0),
@@ -33602,12 +33845,13 @@ function buildCopyRegistry(opts) {
33602
33845
  tailRows: opts.tailRows,
33603
33846
  viewportRows: opts.viewportRows
33604
33847
  });
33848
+ const pinnedSlack = !opts.scrolled ? Math.max(0, opts.viewportRows - mountedGroupRows - opts.tailRows) : 0;
33605
33849
  const hits = [];
33606
33850
  let offset = 0;
33607
33851
  for (const group of opts.renderGroups) {
33608
33852
  const gid = renderGroupId(group);
33609
33853
  const groupHeight = opts.heightCache.getHeight(gid) ?? 0;
33610
- const startRow = offset - visibleClip;
33854
+ const startRow = offset - visibleClip + pinnedSlack;
33611
33855
  offset += groupHeight;
33612
33856
  const groupEntryIds = group.type === "tool-group" ? group.data.entries.map((entry) => entry.id) : isCopyableEntry(group.entry) && !(group.entry.kind === "thinking" && opts.showModelReasoning === false) ? [group.entry.id] : [];
33613
33857
  const entryId = groupEntryIds[0];
@@ -33627,7 +33871,8 @@ function buildCopyRegistry(opts) {
33627
33871
  mountedRows: mountedGroupRows,
33628
33872
  visibleClip,
33629
33873
  viewportRows: opts.viewportRows,
33630
- iconCol: opts.iconCol
33874
+ iconCol: opts.iconCol,
33875
+ pinnedSlack
33631
33876
  })
33632
33877
  };
33633
33878
  }
@@ -33643,6 +33888,100 @@ function scrollbarThumb(rows, offset, total) {
33643
33888
  );
33644
33889
  return { top, size, scrollable: true };
33645
33890
  }
33891
+ function buildMountedCardSpans(opts) {
33892
+ const mountedGroupRows = opts.renderGroups.reduce(
33893
+ (rows, group) => rows + (opts.heightCache.getHeight(renderGroupId(group)) ?? 0),
33894
+ 0
33895
+ );
33896
+ const visibleClip = copyRegistryVisibleClip({
33897
+ scrolled: opts.scrolled,
33898
+ clip: opts.clip,
33899
+ mountedRows: mountedGroupRows,
33900
+ tailRows: opts.tailRows,
33901
+ viewportRows: opts.viewportRows
33902
+ });
33903
+ const spans = [];
33904
+ let offset = 0;
33905
+ const pinnedSlack = !opts.scrolled ? Math.max(0, opts.viewportRows - mountedGroupRows - opts.tailRows) : 0;
33906
+ for (const group of opts.renderGroups) {
33907
+ const gid = renderGroupId(group);
33908
+ const groupHeight = opts.heightCache.getHeight(gid) ?? 0;
33909
+ const viewportStartRow = offset - visibleClip + pinnedSlack;
33910
+ offset += groupHeight;
33911
+ const entryIds = group.type === "tool-group" ? group.data.entries.map((entry) => entry.id) : isCopyableEntry(group.entry) && !(group.entry.kind === "thinking" && opts.showModelReasoning === false) ? [group.entry.id] : [];
33912
+ const entryId = entryIds[0];
33913
+ if (entryId === void 0) continue;
33914
+ const viewportEndRow = viewportStartRow + groupHeight;
33915
+ if (viewportEndRow <= 0 || viewportStartRow >= opts.viewportRows) continue;
33916
+ spans.push({
33917
+ entryId,
33918
+ viewportStartRow,
33919
+ viewportEndRow,
33920
+ totalRows: groupHeight,
33921
+ ...entryIds.length > 1 ? { entryIds } : {}
33922
+ });
33923
+ }
33924
+ return spans;
33925
+ }
33926
+ function selectionHitAt(row, spans) {
33927
+ for (const span of spans) {
33928
+ if (row >= span.viewportStartRow && row < span.viewportEndRow) {
33929
+ return { entryId: span.entryId, ...span.entryIds ? { entryIds: span.entryIds } : {} };
33930
+ }
33931
+ }
33932
+ return null;
33933
+ }
33934
+ function assembleSelectionText(opts) {
33935
+ const { slices, entriesById, toolGroupsByHeadId } = opts;
33936
+ if (slices.length === 0) return "";
33937
+ const byEntry = /* @__PURE__ */ new Map();
33938
+ for (const slice of slices) {
33939
+ const list = byEntry.get(slice.entryId);
33940
+ if (list) list.push(slice);
33941
+ else byEntry.set(slice.entryId, [slice]);
33942
+ }
33943
+ const segments = [];
33944
+ let toolGroupCount = 0;
33945
+ for (const [entryId, entrySlices] of byEntry) {
33946
+ const head = entriesById.get(entryId);
33947
+ if (!head) continue;
33948
+ const groupMembers = toolGroupsByHeadId?.get(entryId);
33949
+ const entries = groupMembers && groupMembers.length > 1 ? groupMembers.map((id) => entriesById.get(id)).filter((entry) => entry !== void 0) : [head];
33950
+ if (entries.length === 0) continue;
33951
+ if (entries.length > 1) toolGroupCount += 1;
33952
+ const fullText = entries.length === 1 ? copyableTextForEntry(entries[0]) : copyableTextForEntries(entries);
33953
+ if (fullText.length === 0) continue;
33954
+ if (entries.length > 1) {
33955
+ segments.push(fullText);
33956
+ continue;
33957
+ }
33958
+ for (const slice of entrySlices) {
33959
+ const rows = fullText.split("\n");
33960
+ const collected = [];
33961
+ for (let r = slice.startRow; r <= slice.endRow && r < rows.length; r++) {
33962
+ const line = rows[r] ?? "";
33963
+ if (slice.startRow === slice.endRow) {
33964
+ const start = Math.max(0, Math.min(line.length, slice.startCol));
33965
+ const end = Math.max(start, Math.min(line.length, slice.endCol + 1));
33966
+ if (end > start) collected.push(line.slice(start, end));
33967
+ } else if (r === slice.startRow) {
33968
+ const start = Math.max(0, Math.min(line.length, slice.startCol));
33969
+ if (start < line.length) collected.push(line.slice(start));
33970
+ } else if (r === slice.endRow) {
33971
+ const end = Math.max(0, Math.min(line.length, slice.endCol + 1));
33972
+ if (end > 0) collected.push(line.slice(0, end));
33973
+ } else {
33974
+ collected.push(line);
33975
+ }
33976
+ }
33977
+ const seg = collected.join("\n");
33978
+ if (seg.length > 0) segments.push(seg);
33979
+ }
33980
+ }
33981
+ if (segments.length === 0) return "";
33982
+ const separator = byEntry.size > 1 || toolGroupCount > 0 ? "\n---\n" : "\n";
33983
+ return segments.join(separator);
33984
+ }
33646
33985
  function Scrollbar({
33647
33986
  rows,
33648
33987
  offset,
@@ -33692,6 +34031,7 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
33692
34031
  multiDiffSummaryThreshold,
33693
34032
  todos,
33694
34033
  showModelReasoning,
34034
+ showSageMemoryInject,
33695
34035
  layoutStore,
33696
34036
  copiedEntryId,
33697
34037
  onRequestOlderEntries
@@ -33727,6 +34067,9 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
33727
34067
  const measuredGroupIdsRef = useRef37(/* @__PURE__ */ new Set());
33728
34068
  const copyHitsRef = useRef37([]);
33729
34069
  const liveToolCopyHitRef = useRef37(null);
34070
+ const mountedGroupSpansRef = useRef37([]);
34071
+ const spansSignatureRef = useRef37("");
34072
+ const selectionRef = useRef37({ anchor: null, head: null, inProgress: false });
33730
34073
  const toolStreamRef = useRef37(toolStream);
33731
34074
  toolStreamRef.current = toolStream;
33732
34075
  const entriesByIdRef = useRef37(/* @__PURE__ */ new Map());
@@ -33734,7 +34077,8 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
33734
34077
  const preparedGroupIdsRef = useRef37(null);
33735
34078
  const preparedEstimateWidthRef = useRef37(null);
33736
34079
  const reasoningKey = showModelReasoning === false ? "|r0" : "";
33737
- const cacheKey = (layoutStore ? `${groupIdsKey}|w${termWidth}` : groupIdsKey) + reasoningKey;
34080
+ const sageKey = showSageMemoryInject === false ? "|s0" : "";
34081
+ const cacheKey = (layoutStore ? `${groupIdsKey}|w${termWidth}` : groupIdsKey) + reasoningKey + sageKey;
33738
34082
  if (preparedGroupIdsRef.current !== cacheKey || preparedEstimateWidthRef.current !== termWidth) {
33739
34083
  const widthChanged = preparedEstimateWidthRef.current !== null && preparedEstimateWidthRef.current !== termWidth;
33740
34084
  if (widthChanged) {
@@ -33761,7 +34105,7 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
33761
34105
  }
33762
34106
  if (stored.kind === "measured") measuredGroupIdsRef.current.add(id);
33763
34107
  } else {
33764
- const estimatedRows = estimateRenderGroupRows(group, termWidth);
34108
+ const estimatedRows = estimateRenderGroupRows(group, termWidth, showSageMemoryInject);
33765
34109
  heightCache.record(id, estimatedRows);
33766
34110
  const kind = group.type === "tool-group" ? "tool-group" : group.entry.kind;
33767
34111
  const text = group.type === "tool-group" ? group.data.name : group.entry.text ?? "";
@@ -33780,7 +34124,10 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
33780
34124
  const id = renderGroupId(group);
33781
34125
  const hidden = group.type !== "tool-group" && group.entry.kind === "thinking" && showModelReasoning === false;
33782
34126
  if (hidden) hiddenIds.add(id);
33783
- return [id, hidden ? 0 : estimateRenderGroupRows(group, termWidth)];
34127
+ return [
34128
+ id,
34129
+ hidden ? 0 : estimateRenderGroupRows(group, termWidth, showSageMemoryInject)
34130
+ ];
33784
34131
  })
33785
34132
  );
33786
34133
  heightCache.recordMany(
@@ -33838,6 +34185,7 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
33838
34185
  const ids = groupIdsRef.current;
33839
34186
  const nextId = next ? ids[next.index] : void 0;
33840
34187
  const normalized = next && nextId !== void 0 ? { index: next.index, clip: Math.max(0, next.clip) } : null;
34188
+ selectionRef.current = { anchor: null, head: null, inProgress: false };
33841
34189
  effectiveAnchorRef.current = normalized;
33842
34190
  setAnchor(normalized && nextId !== void 0 ? { id: nextId, clip: normalized.clip } : null);
33843
34191
  setMountBump(0);
@@ -33883,9 +34231,77 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
33883
34231
  );
33884
34232
  if (payload === null) return null;
33885
34233
  return await writeClipboardText(payload.text) ? payload.entryId : null;
34234
+ },
34235
+ beginSelection: (row, col) => {
34236
+ if (isOutOfBand(col, termWidth)) {
34237
+ selectionRef.current = { anchor: null, head: null, inProgress: false };
34238
+ return;
34239
+ }
34240
+ const cardHit = selectionHitAt(row, mountedGroupSpansRef.current);
34241
+ if (cardHit === null) {
34242
+ selectionRef.current = { anchor: null, head: null, inProgress: false };
34243
+ return;
34244
+ }
34245
+ selectionRef.current = {
34246
+ anchor: { row, col },
34247
+ head: { row, col },
34248
+ inProgress: true
34249
+ };
34250
+ },
34251
+ extendSelection: (row, col) => {
34252
+ const cur = selectionRef.current;
34253
+ if (cur.anchor === null) return;
34254
+ if (cur.inProgress === false) return;
34255
+ if (isOutOfBand(col, termWidth) || row < 0 || row >= vp) {
34256
+ selectionRef.current = { anchor: null, head: null, inProgress: false };
34257
+ return;
34258
+ }
34259
+ selectionRef.current = { ...cur, head: { row, col } };
34260
+ },
34261
+ endSelection: () => {
34262
+ const cur = selectionRef.current;
34263
+ if (cur.anchor === null) return;
34264
+ selectionRef.current = { ...cur, inProgress: false };
34265
+ },
34266
+ clearSelection: () => {
34267
+ const cur = selectionRef.current;
34268
+ if (cur.anchor === null && cur.head === null) return;
34269
+ selectionRef.current = { anchor: null, head: null, inProgress: false };
34270
+ },
34271
+ commitSelection: async () => {
34272
+ const cur = selectionRef.current;
34273
+ if (cur.anchor === null || cur.head === null) return false;
34274
+ if (cur.anchor.row === cur.head.row && cur.anchor.col === cur.head.col) {
34275
+ selectionRef.current = { anchor: null, head: null, inProgress: false };
34276
+ return false;
34277
+ }
34278
+ const rect = normalizeSelection(cur.anchor, cur.head, cur.inProgress);
34279
+ const slices = selectionToSlices({
34280
+ selection: rect,
34281
+ cards: mountedGroupSpansRef.current,
34282
+ cardVisibleCols: termWidth
34283
+ });
34284
+ const toolGroupsByHeadId = /* @__PURE__ */ new Map();
34285
+ for (const span of mountedGroupSpansRef.current) {
34286
+ if (span.entryIds && span.entryIds.length > 1) {
34287
+ toolGroupsByHeadId.set(span.entryId, span.entryIds);
34288
+ }
34289
+ }
34290
+ const text = assembleSelectionText({
34291
+ slices,
34292
+ entriesById: entriesByIdRef.current,
34293
+ ...toolGroupsByHeadId.size > 0 ? { toolGroupsByHeadId } : {}
34294
+ });
34295
+ selectionRef.current = { anchor: null, head: null, inProgress: false };
34296
+ if (text.length === 0) return false;
34297
+ try {
34298
+ return await writeClipboardText(text);
34299
+ } catch {
34300
+ return false;
34301
+ }
33886
34302
  }
33887
34303
  }),
33888
- [applyAnchor]
34304
+ [applyAnchor, termWidth, vp]
33889
34305
  );
33890
34306
  useEffect67(() => {
33891
34307
  if (!controllerRef) return void 0;
@@ -33912,6 +34328,19 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
33912
34328
  const clip2 = effectiveAnchor?.clip ?? 0;
33913
34329
  planStartIdxRef.current = plan.startIdx;
33914
34330
  const totalRows = contentRows(geometry);
34331
+ setTuiHistoryMemoryGauges({
34332
+ historyRetainedEntries: entries.length,
34333
+ historyGroupedEntries: groupedEntries.length,
34334
+ historyMountedGroups: renderGroups.length,
34335
+ historyMountedEntries: renderGroups.reduce(
34336
+ (count, group) => count + (group.type === "tool-group" ? group.data.entries.length : 1),
34337
+ 0
34338
+ ),
34339
+ historyCachedGroups: heightCache.size,
34340
+ historyMeasuredGroups: measuredGroupIdsRef.current.size,
34341
+ historyViewportRows: vp,
34342
+ historyTotalRows: totalRows
34343
+ });
33915
34344
  const offsetFromBottom = scrolled ? Math.max(0, maxTopRow(geometry) - anchorTopRow(geometry, effectiveAnchor)) : 0;
33916
34345
  const copyRegistry = buildCopyRegistry({
33917
34346
  renderGroups,
@@ -33927,6 +34356,23 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
33927
34356
  copyHitsRef.current = copyRegistry.hits;
33928
34357
  liveToolCopyHitRef.current = copyRegistry.liveHit;
33929
34358
  const copyRailHits = copyRegistry.liveHit ? [...copyRegistry.hits, copyRegistry.liveHit] : copyRegistry.hits;
34359
+ const nextSpans = buildMountedCardSpans({
34360
+ renderGroups,
34361
+ heightCache,
34362
+ scrolled,
34363
+ clip: clip2,
34364
+ tailRows: plan.mountTail ? toolTailHeight : 0,
34365
+ viewportRows: vp,
34366
+ showModelReasoning
34367
+ });
34368
+ const nextSignature = nextSpans.map((s2) => `${s2.entryId}:${s2.viewportStartRow}:${s2.viewportEndRow}`).join("|");
34369
+ if (nextSignature !== spansSignatureRef.current) {
34370
+ if (selectionRef.current.anchor !== null && !selectionRef.current.inProgress) {
34371
+ selectionRef.current = { anchor: null, head: null, inProgress: false };
34372
+ }
34373
+ spansSignatureRef.current = nextSignature;
34374
+ }
34375
+ mountedGroupSpansRef.current = nextSpans;
33930
34376
  useLayoutEffect2(() => {
33931
34377
  let changed = false;
33932
34378
  for (const group of renderGroups) {
@@ -33991,6 +34437,8 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
33991
34437
  }
33992
34438
  if (planStartIdxRef.current > 0) {
33993
34439
  requestOlderFiredRef.current = false;
34440
+ } else if (!scrolled) {
34441
+ requestOlderFiredRef.current = false;
33994
34442
  }
33995
34443
  if (changed) setMeasureTick((tick) => tick + 1);
33996
34444
  });
@@ -34029,7 +34477,8 @@ var ScrollableHistory = memo3(function ScrollableHistory2({
34029
34477
  autonomyMode,
34030
34478
  multiDiffSummaryThreshold,
34031
34479
  todos,
34032
- showModelReasoning
34480
+ showModelReasoning,
34481
+ showSageMemoryInject
34033
34482
  }
34034
34483
  )
34035
34484
  }
@@ -34525,6 +34974,7 @@ function AppView({ host, runtime }) {
34525
34974
  multiDiffSummaryThreshold: state.settingsPicker.multiDiffSummaryThreshold,
34526
34975
  todos: liveTodos,
34527
34976
  showModelReasoning: state.settingsPicker.open ? state.settingsPicker.showModelReasoning : liveSettings?.showModelReasoning ?? true,
34977
+ showSageMemoryInject: state.settingsPicker.open ? state.settingsPicker.showSageMemoryInject : liveSettings?.showSageMemoryInject ?? false,
34528
34978
  layoutStore,
34529
34979
  copiedEntryId: state.copiedEntryId,
34530
34980
  onRequestOlderEntries: runtime.onRequestOlderEntries
@@ -34676,6 +35126,8 @@ function AppView({ host, runtime }) {
34676
35126
  breakerAutoKillResetMs: state.settingsPicker.breakerAutoKillResetMs,
34677
35127
  showModelReasoning: state.settingsPicker.showModelReasoning,
34678
35128
  showAgentSwarmPanel: state.settingsPicker.showAgentSwarmPanel,
35129
+ showSageMemoryInject: state.settingsPicker.showSageMemoryInject,
35130
+ sageMemoryInjectThreshold: state.settingsPicker.sageMemoryInjectThreshold,
34679
35131
  readSymbols: state.settingsPicker.readSymbols,
34680
35132
  filter: state.settingsPicker.filter,
34681
35133
  hint: state.settingsPicker.hint
@@ -35069,7 +35521,10 @@ function createRunBlocksController(host) {
35069
35521
  }
35070
35522
  dispatch({ type: "streamReset" });
35071
35523
  if (result.status === "done" && result.finalText?.trim() && !refs.assistantCommitted.current) {
35072
- dispatch({ type: "addEntry", entry: { kind: "assistant", text: result.finalText } });
35524
+ dispatch({
35525
+ type: "addEntry",
35526
+ entry: { kind: "assistant", text: result.finalText, final: true }
35527
+ });
35073
35528
  }
35074
35529
  if (result.status === "aborted") {
35075
35530
  const reason = result.abortReason ? `Aborted (${result.abortReason}).` : "Aborted.";
@@ -36388,7 +36843,7 @@ function routeSettingsOverlayKey(host, input, key, isEnter) {
36388
36843
  enhanceEnabled: config.enhanceEnabled ?? true,
36389
36844
  enhanceLanguage: config.enhanceLanguage ?? "original",
36390
36845
  debugStream: config.debugStream ?? false,
36391
- statuslineMode: config.statuslineMode ?? "detailed",
36846
+ statuslineMode: config.statuslineMode ?? DEFAULT_STATUSLINE_MODE,
36392
36847
  reasoningMode: config.reasoningMode ?? "auto",
36393
36848
  reasoningEffort: config.reasoningEffort ?? "high",
36394
36849
  reasoningPreserve: config.reasoningPreserve ?? false,
@@ -36400,6 +36855,8 @@ function routeSettingsOverlayKey(host, input, key, isEnter) {
36400
36855
  breakerAutoKillResetMs: config.breakerAutoKillResetMs ?? 6e4,
36401
36856
  showModelReasoning: config.showModelReasoning ?? true,
36402
36857
  showAgentSwarmPanel: config.showAgentSwarmPanel ?? true,
36858
+ showSageMemoryInject: config.showSageMemoryInject ?? false,
36859
+ sageMemoryInjectThreshold: config.sageMemoryInjectThreshold ?? 0.85,
36403
36860
  readSymbols: config.readSymbols ?? false
36404
36861
  });
36405
36862
  return true;
@@ -36663,7 +37120,7 @@ function createAppKeyHandler(options) {
36663
37120
  dispatch({ type: "toggleSddBoardMonitor" });
36664
37121
  return;
36665
37122
  }
36666
- if (key.ctrl && input === "j") {
37123
+ if (key.ctrl && input === "y") {
36667
37124
  dispatch({ type: "toggleKanbanPanel" });
36668
37125
  return;
36669
37126
  }
@@ -36703,7 +37160,7 @@ function createAppKeyHandler(options) {
36703
37160
  }
36704
37161
  }
36705
37162
  }
36706
- if (state.sddBoard?.monitorOpen && !key.ctrl && !key.meta) {
37163
+ if (state.sddBoard?.monitorOpen && !key.ctrl && !key.meta && draftRef.current.buffer === "") {
36707
37164
  if (key.rightArrow) {
36708
37165
  dispatch({ type: "sddBoardFocusNext" });
36709
37166
  return;
@@ -36764,6 +37221,20 @@ function createAppKeyHandler(options) {
36764
37221
  return;
36765
37222
  }
36766
37223
  if (!overlayOpen) {
37224
+ if (mouseMode && key.mouse?.kind === "press" && key.mouse.button === "right") {
37225
+ const region = hitRegion(
37226
+ { termRows, termCols: stdout?.columns ?? 80, viewportRows: state.viewportRows },
37227
+ key.mouse.x,
37228
+ key.mouse.y
37229
+ );
37230
+ if (region?.kind === "history" && key.mouse.x <= (stdout?.columns ?? 80) - SCROLLBAR_HIT_WIDTH) {
37231
+ void historyScrollRef.current?.commitSelection().then((copied) => {
37232
+ if (copied) onHistoryCopy?.(SELECTION_COPY_ID);
37233
+ });
37234
+ return;
37235
+ }
37236
+ historyScrollRef.current?.clearSelection();
37237
+ }
36767
37238
  if (key.mouse?.kind === "wheel" && key.mouse.wheel !== 0) {
36768
37239
  if (isHistoryScrollTarget(
36769
37240
  { termRows, termCols: stdout?.columns ?? 80, viewportRows: state.viewportRows },
@@ -36773,6 +37244,7 @@ function createAppKeyHandler(options) {
36773
37244
  if (key.mouse.shift)
36774
37245
  historyScrollRef.current?.scrollPage(key.mouse.wheel > 0 ? "up" : "down");
36775
37246
  else historyScrollRef.current?.scrollBy(key.mouse.wheel > 0 ? 1 : -1);
37247
+ historyScrollRef.current?.clearSelection();
36776
37248
  return;
36777
37249
  }
36778
37250
  }
@@ -36784,16 +37256,31 @@ function createAppKeyHandler(options) {
36784
37256
  );
36785
37257
  const copyRow = region?.kind === "history" ? region.row : region?.kind === "scrollbar" ? region.cell : null;
36786
37258
  if (copyRow !== null && key.mouse.kind === "press" && historyScrollRef.current?.hasCopyTargetAt(copyRow, key.mouse.x - 1)) {
37259
+ historyScrollRef.current?.clearSelection();
36787
37260
  void historyScrollRef.current.copyAtViewportCell(copyRow, key.mouse.x - 1).then((entryId) => {
36788
37261
  if (entryId !== null) onHistoryCopy?.(entryId);
36789
37262
  }).catch(() => null);
36790
37263
  return;
36791
37264
  }
37265
+ if (mouseMode && region?.kind === "history" && key.mouse.x <= (stdout?.columns ?? 80) - SCROLLBAR_HIT_WIDTH) {
37266
+ if (key.mouse.kind === "press") {
37267
+ historyScrollRef.current?.beginSelection(region.row, key.mouse.x - 1);
37268
+ return;
37269
+ }
37270
+ if (key.mouse.kind === "move") {
37271
+ historyScrollRef.current?.extendSelection(region.row, key.mouse.x - 1);
37272
+ return;
37273
+ }
37274
+ }
36792
37275
  if (region?.kind === "scrollbar") {
37276
+ historyScrollRef.current?.clearSelection();
36793
37277
  historyScrollRef.current?.scrollToTrackCell(region.cell);
36794
37278
  return;
36795
37279
  }
36796
37280
  }
37281
+ if (mouseMode && key.mouse?.kind === "release" && key.mouse.button === "left") {
37282
+ historyScrollRef.current?.endSelection();
37283
+ }
36797
37284
  if (mouseMode && key.mouse?.kind === "press" && key.mouse.button === "left" && statusBarWrapRef.current) {
36798
37285
  const sbHeight = measureElement(statusBarWrapRef.current).height;
36799
37286
  const belowHeight = belowStatusBarRef.current ? measureElement(belowStatusBarRef.current).height : 0;
@@ -37176,8 +37663,8 @@ function App(props) {
37176
37663
  submitRef
37177
37664
  } = useAppRuntimeRefs(attachments, state);
37178
37665
  const projectName = React10.useMemo(() => {
37179
- const base = path8.basename(projectRoot);
37180
- return base && base !== path8.sep ? base : void 0;
37666
+ const base = path7.basename(projectRoot);
37667
+ return base && base !== path7.sep ? base : void 0;
37181
37668
  }, [projectRoot]);
37182
37669
  const workingDirChip = useWorkingDirChip(agent.ctx, projectRoot);
37183
37670
  const {
@@ -37913,9 +38400,9 @@ var BRACKETED_PASTE_OFF = "\x1B[?2004l";
37913
38400
 
37914
38401
  // src/run-tui-client-registration.ts
37915
38402
  import { randomUUID } from "node:crypto";
37916
- import * as path9 from "node:path";
38403
+ import * as path8 from "node:path";
37917
38404
  import { RemoteMailbox, resolveProjectDir } from "@wrongstack/core/coordination";
37918
- import { wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core/utils";
38405
+ import { wstackGlobalRoot } from "@wrongstack/core/utils";
37919
38406
  import {
37920
38407
  createHqPublisherFromEnv,
37921
38408
  startBrainTelemetryBridge,
@@ -37941,7 +38428,7 @@ function createRunTuiClientRegistration(opts) {
37941
38428
  if (!opts.projectRoot) return null;
37942
38429
  const generation = ++registrationGeneration;
37943
38430
  try {
37944
- const projectDir = resolveProjectDir(opts.projectRoot, wstackGlobalRoot2());
38431
+ const projectDir = resolveProjectDir(opts.projectRoot, wstackGlobalRoot());
37945
38432
  const mailbox = new RemoteMailbox({ projectDir, events: opts.events });
37946
38433
  const clientId = `tui@${randomUUID().slice(0, 8)}`;
37947
38434
  await mailbox.initialize();
@@ -38000,7 +38487,7 @@ function createRunTuiClientRegistration(opts) {
38000
38487
  await mailbox.registerClient({
38001
38488
  clientId,
38002
38489
  sessionId: opts.getSessionId?.() ?? opts.projectRoot,
38003
- name: `TUI [${path9.basename(opts.projectRoot)}]`,
38490
+ name: `TUI [${path8.basename(opts.projectRoot)}]`,
38004
38491
  source: "tui",
38005
38492
  pid: process.pid
38006
38493
  });
@@ -38015,7 +38502,7 @@ function createRunTuiClientRegistration(opts) {
38015
38502
  tuiHqPublisher = createHqPublisherFromEnv({
38016
38503
  clientKind: "tui",
38017
38504
  projectRoot: opts.projectRoot,
38018
- projectName: path9.basename(opts.projectRoot),
38505
+ projectName: path8.basename(opts.projectRoot),
38019
38506
  appConfig: opts.appConfig
38020
38507
  });
38021
38508
  tuiHqPublisher?.connect();
@@ -38028,7 +38515,7 @@ function createRunTuiClientRegistration(opts) {
38028
38515
  events: opts.events,
38029
38516
  sessionId: tuiSessionId,
38030
38517
  projectRoot: opts.projectRoot,
38031
- projectName: path9.basename(opts.projectRoot)
38518
+ projectName: path8.basename(opts.projectRoot)
38032
38519
  })
38033
38520
  );
38034
38521
  } catch {
@@ -38153,7 +38640,7 @@ function createRunTuiClientRegistration(opts) {
38153
38640
  }
38154
38641
 
38155
38642
  // src/run-tui-title-controller.ts
38156
- import * as path10 from "node:path";
38643
+ import * as path9 from "node:path";
38157
38644
 
38158
38645
  // src/terminal-title.ts
38159
38646
  var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -38233,7 +38720,7 @@ function createRunTuiTitleController(opts) {
38233
38720
  stdout: opts.stdout,
38234
38721
  events: opts.events,
38235
38722
  model: currentModel,
38236
- appName: opts.projectRoot ? path10.basename(opts.projectRoot) : void 0
38723
+ appName: opts.projectRoot ? path9.basename(opts.projectRoot) : void 0
38237
38724
  });
38238
38725
  };
38239
38726
  const stop = () => {
@@ -38268,6 +38755,7 @@ var origConsoleInfo = console.info;
38268
38755
  var origConsoleTable = console.table;
38269
38756
  var origConsoleTrace = console.trace;
38270
38757
  var origStderrWrite = process.stderr.write.bind(process.stderr);
38758
+ var origStdoutWrite = process.stdout.write.bind(process.stdout);
38271
38759
  var consoleNoop = (..._args) => {
38272
38760
  };
38273
38761
  var stderrNoop = ((_chunk, _encodingOrCb, _cb) => {
@@ -38297,6 +38785,7 @@ function unsilenceTerminal() {
38297
38785
  console.table = origConsoleTable;
38298
38786
  console.trace = origConsoleTrace;
38299
38787
  process.stderr.write = origStderrWrite;
38788
+ process.stdout.write = origStdoutWrite;
38300
38789
  process.off("warning", warningNoop);
38301
38790
  }
38302
38791
 
@@ -38314,6 +38803,10 @@ async function runTui(opts) {
38314
38803
  const capability = detectTerminal({ stdin, stdout });
38315
38804
  const mouseEnabled = opts.mouse ?? opts.getSettings?.().mouseMode ?? process.env.WRONGSTACK_MOUSE === "1";
38316
38805
  silenceTerminal();
38806
+ const provider = opts.agent.ctx.provider;
38807
+ if (provider?.suppressMaxToolsWarning) {
38808
+ provider.suppressMaxToolsWarning();
38809
+ }
38317
38810
  const inkStdin = stdin;
38318
38811
  const {
38319
38812
  controller: titleController,