@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.1

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 (266) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/dist/types/cli/args.d.ts +1 -1
  3. package/dist/types/cli/dry-balance-cli.d.ts +15 -1
  4. package/dist/types/cli/gallery-cli.d.ts +43 -0
  5. package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
  6. package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
  7. package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
  8. package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
  9. package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
  10. package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
  11. package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
  12. package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
  13. package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
  14. package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
  15. package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
  16. package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
  17. package/dist/types/cli/gallery-screenshot.d.ts +35 -0
  18. package/dist/types/commands/gallery.d.ts +47 -0
  19. package/dist/types/commit/analysis/conventional.d.ts +2 -2
  20. package/dist/types/commit/analysis/summary.d.ts +2 -2
  21. package/dist/types/commit/changelog/generate.d.ts +2 -2
  22. package/dist/types/commit/changelog/index.d.ts +2 -2
  23. package/dist/types/commit/map-reduce/index.d.ts +3 -3
  24. package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
  25. package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
  26. package/dist/types/commit/model-selection.d.ts +10 -4
  27. package/dist/types/config/api-key-resolver.d.ts +34 -0
  28. package/dist/types/config/keybindings.d.ts +6 -1
  29. package/dist/types/config/model-id-affixes.d.ts +2 -0
  30. package/dist/types/config/model-registry.d.ts +25 -2
  31. package/dist/types/config/settings-schema.d.ts +41 -6
  32. package/dist/types/dap/config.d.ts +14 -1
  33. package/dist/types/dap/types.d.ts +10 -0
  34. package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
  35. package/dist/types/lsp/types.d.ts +10 -0
  36. package/dist/types/lsp/utils.d.ts +3 -2
  37. package/dist/types/main.d.ts +3 -2
  38. package/dist/types/memory-backend/index.d.ts +2 -1
  39. package/dist/types/memory-backend/resolve.d.ts +1 -1
  40. package/dist/types/memory-backend/types.d.ts +1 -1
  41. package/dist/types/modes/components/chat-block.d.ts +64 -0
  42. package/dist/types/modes/components/custom-editor.d.ts +5 -1
  43. package/dist/types/modes/components/overlay-box.d.ts +17 -0
  44. package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
  45. package/dist/types/modes/components/plan-toc.d.ts +41 -0
  46. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  47. package/dist/types/modes/components/tool-execution.d.ts +18 -0
  48. package/dist/types/modes/components/transcript-container.d.ts +11 -0
  49. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  50. package/dist/types/modes/controllers/event-controller.d.ts +0 -1
  51. package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
  52. package/dist/types/modes/controllers/input-controller.d.ts +1 -1
  53. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  54. package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
  55. package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
  56. package/dist/types/modes/index.d.ts +5 -4
  57. package/dist/types/modes/interactive-mode.d.ts +16 -6
  58. package/dist/types/modes/setup-version.d.ts +11 -0
  59. package/dist/types/modes/setup-wizard/index.d.ts +2 -1
  60. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
  61. package/dist/types/modes/theme/theme.d.ts +1 -1
  62. package/dist/types/modes/types.d.ts +19 -6
  63. package/dist/types/modes/utils/copy-targets.d.ts +21 -1
  64. package/dist/types/plan-mode/approved-plan.d.ts +27 -8
  65. package/dist/types/plan-mode/plan-protection.d.ts +4 -4
  66. package/dist/types/sdk.d.ts +3 -1
  67. package/dist/types/session/agent-session.d.ts +21 -0
  68. package/dist/types/session/messages.d.ts +12 -0
  69. package/dist/types/session/session-manager.d.ts +3 -1
  70. package/dist/types/slash-commands/types.d.ts +4 -6
  71. package/dist/types/task/executor.d.ts +14 -0
  72. package/dist/types/task/index.d.ts +1 -0
  73. package/dist/types/task/render.d.ts +3 -2
  74. package/dist/types/telemetry-export.d.ts +1 -1
  75. package/dist/types/tools/archive-reader.d.ts +5 -0
  76. package/dist/types/tools/ast-edit.d.ts +3 -0
  77. package/dist/types/tools/ast-grep.d.ts +3 -0
  78. package/dist/types/tools/bash.d.ts +1 -0
  79. package/dist/types/tools/eval-render.d.ts +1 -8
  80. package/dist/types/tools/fetch.d.ts +15 -7
  81. package/dist/types/tools/find.d.ts +8 -4
  82. package/dist/types/tools/grouped-file-output.d.ts +95 -12
  83. package/dist/types/tools/memory-render.d.ts +4 -1
  84. package/dist/types/tools/plan-mode-guard.d.ts +8 -9
  85. package/dist/types/tools/render-utils.d.ts +13 -9
  86. package/dist/types/tools/renderers.d.ts +16 -2
  87. package/dist/types/tools/search.d.ts +5 -1
  88. package/dist/types/tools/sqlite-reader.d.ts +1 -0
  89. package/dist/types/tools/todo.d.ts +3 -2
  90. package/dist/types/tools/write.d.ts +5 -0
  91. package/dist/types/tui/output-block.d.ts +16 -4
  92. package/dist/types/tui/status-line.d.ts +3 -0
  93. package/dist/types/utils/enhanced-paste.d.ts +20 -0
  94. package/dist/types/web/scrapers/github.d.ts +22 -0
  95. package/dist/types/web/search/providers/kimi.d.ts +1 -1
  96. package/dist/types/web/search/providers/perplexity.d.ts +8 -1
  97. package/dist/types/web/search/types.d.ts +1 -1
  98. package/package.json +9 -9
  99. package/scripts/dev-launch +42 -0
  100. package/scripts/dev-launch-preload.ts +19 -0
  101. package/src/auto-thinking/classifier.ts +5 -1
  102. package/src/cli/args.ts +2 -2
  103. package/src/cli/dry-balance-cli.ts +52 -17
  104. package/src/cli/gallery-cli.ts +226 -0
  105. package/src/cli/gallery-fixtures/agentic.ts +292 -0
  106. package/src/cli/gallery-fixtures/codeintel.ts +188 -0
  107. package/src/cli/gallery-fixtures/edit.ts +194 -0
  108. package/src/cli/gallery-fixtures/fs.ts +153 -0
  109. package/src/cli/gallery-fixtures/index.ts +40 -0
  110. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  111. package/src/cli/gallery-fixtures/memory.ts +81 -0
  112. package/src/cli/gallery-fixtures/misc.ts +250 -0
  113. package/src/cli/gallery-fixtures/search.ts +213 -0
  114. package/src/cli/gallery-fixtures/shell.ts +167 -0
  115. package/src/cli/gallery-fixtures/types.ts +41 -0
  116. package/src/cli/gallery-fixtures/web.ts +158 -0
  117. package/src/cli/gallery-screenshot.ts +279 -0
  118. package/src/cli-commands.ts +1 -0
  119. package/src/commands/gallery.ts +52 -0
  120. package/src/commands/launch.ts +1 -1
  121. package/src/commit/analysis/conventional.ts +2 -2
  122. package/src/commit/analysis/summary.ts +2 -2
  123. package/src/commit/changelog/generate.ts +2 -2
  124. package/src/commit/changelog/index.ts +2 -2
  125. package/src/commit/map-reduce/index.ts +3 -3
  126. package/src/commit/map-reduce/map-phase.ts +2 -2
  127. package/src/commit/map-reduce/reduce-phase.ts +2 -2
  128. package/src/commit/model-selection.ts +33 -9
  129. package/src/commit/pipeline.ts +4 -4
  130. package/src/config/api-key-resolver.ts +58 -0
  131. package/src/config/keybindings.ts +15 -6
  132. package/src/config/model-equivalence.ts +35 -12
  133. package/src/config/model-id-affixes.ts +39 -22
  134. package/src/config/model-registry.ts +41 -18
  135. package/src/config/settings-schema.ts +28 -5
  136. package/src/config/settings.ts +31 -2
  137. package/src/dap/client.ts +14 -16
  138. package/src/dap/config.ts +41 -2
  139. package/src/dap/defaults.json +1 -0
  140. package/src/dap/session.ts +1 -0
  141. package/src/dap/types.ts +10 -0
  142. package/src/debug/index.ts +40 -54
  143. package/src/edit/renderer.ts +111 -119
  144. package/src/eval/__tests__/agent-bridge.test.ts +75 -32
  145. package/src/eval/__tests__/llm-bridge.test.ts +90 -31
  146. package/src/eval/agent-bridge.ts +34 -7
  147. package/src/eval/llm-bridge.ts +8 -3
  148. package/src/extensibility/extensions/runner.ts +1 -0
  149. package/src/extensibility/plugins/doctor.ts +0 -1
  150. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  151. package/src/goals/tools/goal-tool.ts +37 -27
  152. package/src/internal-urls/docs-index.generated.ts +10 -10
  153. package/src/lsp/client.ts +104 -55
  154. package/src/lsp/types.ts +10 -0
  155. package/src/lsp/utils.ts +3 -2
  156. package/src/main.ts +53 -56
  157. package/src/memories/index.ts +12 -5
  158. package/src/memory-backend/index.ts +13 -1
  159. package/src/memory-backend/resolve.ts +3 -5
  160. package/src/memory-backend/types.ts +1 -1
  161. package/src/mnemopi/backend.ts +5 -1
  162. package/src/modes/acp/acp-agent.ts +33 -26
  163. package/src/modes/components/assistant-message.ts +2 -9
  164. package/src/modes/components/chat-block.ts +111 -0
  165. package/src/modes/components/copy-selector.ts +1 -44
  166. package/src/modes/components/custom-editor.ts +33 -1
  167. package/src/modes/components/custom-message.ts +1 -3
  168. package/src/modes/components/execution-shared.ts +1 -2
  169. package/src/modes/components/hook-message.ts +1 -3
  170. package/src/modes/components/overlay-box.ts +108 -0
  171. package/src/modes/components/plan-review-overlay.ts +799 -0
  172. package/src/modes/components/plan-toc.ts +138 -0
  173. package/src/modes/components/read-tool-group.ts +20 -4
  174. package/src/modes/components/skill-message.ts +0 -1
  175. package/src/modes/components/status-line.ts +3 -5
  176. package/src/modes/components/tips.txt +1 -0
  177. package/src/modes/components/todo-reminder.ts +0 -2
  178. package/src/modes/components/tool-execution.ts +115 -90
  179. package/src/modes/components/transcript-container.ts +84 -24
  180. package/src/modes/components/user-message.ts +1 -2
  181. package/src/modes/controllers/command-controller-shared.ts +7 -6
  182. package/src/modes/controllers/command-controller.ts +70 -57
  183. package/src/modes/controllers/event-controller.ts +41 -40
  184. package/src/modes/controllers/extension-ui-controller.ts +10 -73
  185. package/src/modes/controllers/input-controller.ts +135 -122
  186. package/src/modes/controllers/mcp-command-controller.ts +69 -60
  187. package/src/modes/controllers/selector-controller.ts +25 -27
  188. package/src/modes/controllers/streaming-reveal.ts +212 -0
  189. package/src/modes/controllers/tan-command-controller.ts +173 -0
  190. package/src/modes/index.ts +5 -4
  191. package/src/modes/interactive-mode.ts +171 -82
  192. package/src/modes/setup-version.ts +11 -0
  193. package/src/modes/setup-wizard/index.ts +3 -2
  194. package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
  195. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  196. package/src/modes/theme/theme-schema.json +1 -1
  197. package/src/modes/theme/theme.ts +8 -4
  198. package/src/modes/types.ts +19 -8
  199. package/src/modes/utils/context-usage.ts +10 -6
  200. package/src/modes/utils/copy-targets.ts +133 -27
  201. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  202. package/src/modes/utils/ui-helpers.ts +44 -46
  203. package/src/plan-mode/approved-plan.ts +66 -43
  204. package/src/plan-mode/plan-protection.ts +4 -4
  205. package/src/prompts/system/background-tan-dispatch.md +8 -0
  206. package/src/prompts/system/plan-mode-active.md +67 -58
  207. package/src/prompts/system/plan-mode-approved.md +1 -1
  208. package/src/sdk.ts +32 -60
  209. package/src/session/agent-session.ts +89 -13
  210. package/src/session/messages.ts +26 -0
  211. package/src/session/session-manager.ts +13 -5
  212. package/src/slash-commands/builtin-registry.ts +37 -10
  213. package/src/slash-commands/helpers/usage-report.ts +2 -0
  214. package/src/slash-commands/types.ts +4 -6
  215. package/src/task/executor.ts +25 -4
  216. package/src/task/index.ts +4 -0
  217. package/src/task/render.ts +212 -148
  218. package/src/telemetry-export.ts +25 -7
  219. package/src/tools/archive-reader.ts +64 -0
  220. package/src/tools/ask.ts +119 -164
  221. package/src/tools/ast-edit.ts +98 -71
  222. package/src/tools/ast-grep.ts +37 -43
  223. package/src/tools/bash.ts +50 -6
  224. package/src/tools/debug.ts +20 -8
  225. package/src/tools/eval-backends.ts +6 -17
  226. package/src/tools/eval-render.ts +21 -18
  227. package/src/tools/eval.ts +5 -4
  228. package/src/tools/fetch.ts +391 -91
  229. package/src/tools/find.ts +44 -30
  230. package/src/tools/gh-renderer.ts +81 -42
  231. package/src/tools/grouped-file-output.ts +272 -48
  232. package/src/tools/image-gen.ts +150 -103
  233. package/src/tools/inspect-image-renderer.ts +63 -41
  234. package/src/tools/inspect-image.ts +8 -1
  235. package/src/tools/job.ts +3 -4
  236. package/src/tools/memory-render.ts +4 -1
  237. package/src/tools/plan-mode-guard.ts +21 -39
  238. package/src/tools/read.ts +23 -16
  239. package/src/tools/render-utils.ts +38 -40
  240. package/src/tools/renderers.ts +16 -1
  241. package/src/tools/report-tool-issue.ts +1 -1
  242. package/src/tools/resolve.ts +14 -0
  243. package/src/tools/search-tool-bm25.ts +36 -23
  244. package/src/tools/search.ts +189 -95
  245. package/src/tools/sqlite-reader.ts +9 -12
  246. package/src/tools/todo.ts +138 -59
  247. package/src/tools/write.ts +100 -60
  248. package/src/tui/output-block.ts +60 -13
  249. package/src/tui/status-line.ts +5 -1
  250. package/src/utils/commit-message-generator.ts +9 -1
  251. package/src/utils/enhanced-paste.ts +202 -0
  252. package/src/utils/title-generator.ts +2 -1
  253. package/src/web/scrapers/github.ts +255 -3
  254. package/src/web/scrapers/youtube.ts +3 -2
  255. package/src/web/search/providers/anthropic.ts +25 -19
  256. package/src/web/search/providers/exa.ts +11 -3
  257. package/src/web/search/providers/kimi.ts +28 -17
  258. package/src/web/search/providers/parallel.ts +35 -24
  259. package/src/web/search/providers/perplexity.ts +199 -51
  260. package/src/web/search/providers/synthetic.ts +8 -6
  261. package/src/web/search/providers/tavily.ts +9 -8
  262. package/src/web/search/providers/zai.ts +8 -6
  263. package/src/web/search/render.ts +39 -54
  264. package/src/web/search/types.ts +5 -1
  265. package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
  266. package/src/eval/__tests__/shared-executors.test.ts +0 -609
@@ -20,7 +20,7 @@ import {
20
20
  modelsAreEqual,
21
21
  type UsageReport,
22
22
  } from "@oh-my-pi/pi-ai";
23
- import type { Component, EditorTheme, SlashCommand } from "@oh-my-pi/pi-tui";
23
+ import type { Component, EditorTheme, LoaderMessageColorFn, OverlayHandle, SlashCommand } from "@oh-my-pi/pi-tui";
24
24
  import {
25
25
  Container,
26
26
  clearRenderCache,
@@ -65,12 +65,7 @@ import { BUILTIN_SLASH_COMMANDS, loadSlashCommands } from "../extensibility/slas
65
65
  import type { Goal, GoalModeState } from "../goals/state";
66
66
  import { resolveLocalUrlToPath } from "../internal-urls";
67
67
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
68
- import {
69
- humanizePlanTitle,
70
- type PlanApprovalDetails,
71
- renameApprovedPlanFile,
72
- resolvePlanTitle,
73
- } from "../plan-mode/approved-plan";
68
+ import { humanizePlanTitle, type PlanApprovalDetails, resolveApprovedPlan } from "../plan-mode/approved-plan";
74
69
  import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
75
70
  import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
76
71
  type: "text",
@@ -94,6 +89,7 @@ import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-colo
94
89
  import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
95
90
  import type { AssistantMessageComponent } from "./components/assistant-message";
96
91
  import type { BashExecutionComponent } from "./components/bash-execution";
92
+ import { ChatBlock, type ChatBlockHost } from "./components/chat-block";
97
93
  import { CustomEditor } from "./components/custom-editor";
98
94
  import { DynamicBorder } from "./components/dynamic-border";
99
95
  import { ErrorBannerComponent } from "./components/error-banner";
@@ -101,6 +97,7 @@ import type { EvalExecutionComponent } from "./components/eval-execution";
101
97
  import type { HookEditorComponent } from "./components/hook-editor";
102
98
  import type { HookInputComponent } from "./components/hook-input";
103
99
  import type { HookSelectorComponent, HookSelectorSlider } from "./components/hook-selector";
100
+ import { PlanReviewOverlay } from "./components/plan-review-overlay";
104
101
  import { StatusLineComponent } from "./components/status-line";
105
102
  import type { ToolExecutionHandle } from "./components/tool-execution";
106
103
  import { TranscriptContainer } from "./components/transcript-container";
@@ -114,6 +111,7 @@ import { MCPCommandController } from "./controllers/mcp-command-controller";
114
111
  import { OmfgController } from "./controllers/omfg-controller";
115
112
  import { SelectorController } from "./controllers/selector-controller";
116
113
  import { SSHCommandController } from "./controllers/ssh-command-controller";
114
+ import { TanCommandController } from "./controllers/tan-command-controller";
117
115
  import { TodoCommandController } from "./controllers/todo-command-controller";
118
116
  import {
119
117
  consumeLoopLimitIteration,
@@ -273,7 +271,6 @@ export class InteractiveMode implements InteractiveModeContext {
273
271
  statusLine: StatusLineComponent;
274
272
 
275
273
  isInitialized = false;
276
- isBackgrounded = false;
277
274
  isBashMode = false;
278
275
  toolOutputExpanded = false;
279
276
  todoExpanded = false;
@@ -341,12 +338,14 @@ export class InteractiveMode implements InteractiveModeContext {
341
338
  #planModePreviousModelState: { model: Model; thinkingLevel?: ThinkingLevel } | undefined;
342
339
  #pendingModelSwitch: { model: Model; thinkingLevel?: ThinkingLevel } | undefined;
343
340
  #planModeHasEntered = false;
344
- #planReviewContainer: Container | undefined;
341
+ #planReviewOverlay: PlanReviewOverlay | undefined;
342
+ #planReviewOverlayHandle: OverlayHandle | undefined;
345
343
  readonly lspServers: LspStartupServerInfo[] | undefined = undefined;
346
344
  mcpManager?: import("../mcp").MCPManager;
347
345
  readonly #toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
348
346
 
349
347
  readonly #btwController: BtwController;
348
+ readonly #tanCommandController: TanCommandController;
350
349
  readonly #omfgController: OmfgController;
351
350
  readonly #commandController: CommandController;
352
351
  readonly #todoCommandController: TodoCommandController;
@@ -365,6 +364,7 @@ export class InteractiveMode implements InteractiveModeContext {
365
364
  #eventBus?: EventBus;
366
365
  #eventBusUnsubscribers: Array<() => void> = [];
367
366
  #welcomeComponent?: WelcomeComponent;
367
+ readonly #chatHost: ChatBlockHost = { requestRender: () => this.ui.requestRender() };
368
368
 
369
369
  constructor(
370
370
  session: AgentSession,
@@ -470,6 +470,7 @@ export class InteractiveMode implements InteractiveModeContext {
470
470
 
471
471
  this.#uiHelpers = new UiHelpers(this);
472
472
  this.#btwController = new BtwController(this);
473
+ this.#tanCommandController = new TanCommandController(this);
473
474
  this.#omfgController = new OmfgController(this);
474
475
  this.#extensionUiController = new ExtensionUiController(this);
475
476
  this.#eventController = new EventController(this);
@@ -599,8 +600,9 @@ export class InteractiveMode implements InteractiveModeContext {
599
600
  // Load initial todos
600
601
  await this.#loadTodoList();
601
602
 
602
- // Start the UI
603
- this.ui.start();
603
+ // Start the UI. Cold `omp` launch opts into clearing on the first paint so
604
+ // the initial welcome frame does not append over the previous run's scrollback.
605
+ this.ui.start({ clearScrollback: options.clearInitialTerminalHistory === true });
604
606
  pushTerminalTitle();
605
607
  setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
606
608
  this.updateEditorBorderColor();
@@ -1522,22 +1524,15 @@ export class InteractiveMode implements InteractiveModeContext {
1522
1524
  if (!state?.enabled) {
1523
1525
  throw new ToolError("Plan mode is not active.");
1524
1526
  }
1525
- const planFilePath = state.planFilePath;
1526
- const planContent = await this.#readPlanFile(planFilePath);
1527
- if (planContent === null) {
1528
- throw new ToolError(
1529
- `Plan file not found at ${planFilePath}. Write the finalized plan to ${planFilePath} before requesting approval.`,
1530
- );
1531
- }
1532
- const normalized = resolvePlanTitle({
1527
+ const { planFilePath, title } = await resolveApprovedPlan({
1533
1528
  suppliedTitle: extra?.title,
1534
- planContent,
1535
- planFilePath,
1529
+ statePlanFilePath: state.planFilePath,
1530
+ readPlan: url => this.#readPlanFile(url),
1531
+ listPlanFiles: () => this.#listLocalPlanFiles(),
1536
1532
  });
1537
1533
  const details: PlanApprovalDetails = {
1538
1534
  planFilePath,
1539
- finalPlanFilePath: `local://${normalized.fileName}`,
1540
- title: normalized.title,
1535
+ title,
1541
1536
  planExists: true,
1542
1537
  };
1543
1538
  return {
@@ -1677,22 +1672,87 @@ export class InteractiveMode implements InteractiveModeContext {
1677
1672
  }
1678
1673
  }
1679
1674
 
1680
- #renderPlanPreview(planContent: string, options?: { append?: boolean }): void {
1681
- const existingContainer = this.#planReviewContainer;
1682
- const replaceExisting = options?.append !== true && existingContainer !== undefined;
1683
- const planReviewContainer = replaceExisting ? existingContainer : new Container();
1684
- planReviewContainer.clear();
1685
- planReviewContainer.addChild(new Spacer(1));
1686
- planReviewContainer.addChild(new DynamicBorder());
1687
- planReviewContainer.addChild(new Text(theme.bold(theme.fg("accent", "Plan Review")), 1, 1));
1688
- planReviewContainer.addChild(new Spacer(1));
1689
- planReviewContainer.addChild(new Markdown(planContent, 1, 1, getMarkdownTheme()));
1690
- planReviewContainer.addChild(new DynamicBorder());
1691
- if (!replaceExisting) {
1692
- this.chatContainer.addChild(planReviewContainer);
1675
+ /** `local://` URLs of plan files in the session-local root, newest first.
1676
+ * A fallback for `resolveApprovedPlan` when the agent dropped `extra.title`,
1677
+ * so the plan it wrote is still found by scanning recent `*-plan.md` files. */
1678
+ async #listLocalPlanFiles(): Promise<string[]> {
1679
+ const localRoot = this.#resolvePlanFilePath("local://");
1680
+ try {
1681
+ const entries = await fs.readdir(localRoot, { withFileTypes: true });
1682
+ const plans = await Promise.all(
1683
+ entries
1684
+ .filter(entry => entry.isFile() && /plan\.md$/i.test(entry.name))
1685
+ .map(async name => {
1686
+ const stat = await fs.stat(path.join(localRoot, name.name)).catch(() => null);
1687
+ return { url: `local://${name.name}`, mtime: stat?.mtimeMs ?? 0 };
1688
+ }),
1689
+ );
1690
+ return plans.sort((a, b) => b.mtime - a.mtime).map(plan => plan.url);
1691
+ } catch {
1692
+ return [];
1693
1693
  }
1694
- this.#planReviewContainer = planReviewContainer;
1694
+ }
1695
+
1696
+ showPlanReview(
1697
+ planContent: string,
1698
+ title: string,
1699
+ options: string[],
1700
+ dialogOptions?: {
1701
+ helpText?: string;
1702
+ disabledIndices?: number[];
1703
+ onExternalEditor?: () => void;
1704
+ onPlanEdited?: (content: string) => void;
1705
+ onFeedbackChange?: (feedback: string) => void;
1706
+ initialIndex?: number;
1707
+ },
1708
+ extra?: { slider?: HookSelectorSlider },
1709
+ ): Promise<string | undefined> {
1710
+ this.#hidePlanReview();
1711
+ const { promise, resolve } = Promise.withResolvers<string | undefined>();
1712
+ let settled = false;
1713
+ const finish = (choice: string | undefined): void => {
1714
+ if (settled) return;
1715
+ settled = true;
1716
+ this.#hidePlanReview();
1717
+ this.ui.requestRender();
1718
+ resolve(choice);
1719
+ };
1720
+ const overlay = new PlanReviewOverlay(
1721
+ planContent,
1722
+ {
1723
+ promptTitle: title,
1724
+ options,
1725
+ disabledIndices: dialogOptions?.disabledIndices,
1726
+ helpText: dialogOptions?.helpText,
1727
+ initialIndex: dialogOptions?.initialIndex,
1728
+ slider: extra?.slider,
1729
+ externalEditorLabel: this.keybindings.getDisplayString("app.editor.external") || undefined,
1730
+ },
1731
+ {
1732
+ onPick: choice => finish(choice),
1733
+ onCancel: () => finish(undefined),
1734
+ onExternalEditor: dialogOptions?.onExternalEditor,
1735
+ onPlanEdited: dialogOptions?.onPlanEdited,
1736
+ onFeedbackChange: dialogOptions?.onFeedbackChange,
1737
+ },
1738
+ );
1739
+ this.#planReviewOverlay = overlay;
1740
+ this.#planReviewOverlayHandle = this.ui.showOverlay(overlay, {
1741
+ anchor: "bottom-center",
1742
+ width: "100%",
1743
+ maxHeight: "100%",
1744
+ margin: 0,
1745
+ fullscreen: true,
1746
+ });
1747
+ this.ui.setFocus(overlay);
1695
1748
  this.ui.requestRender();
1749
+ return promise;
1750
+ }
1751
+
1752
+ #hidePlanReview(): void {
1753
+ this.#planReviewOverlayHandle?.hide();
1754
+ this.#planReviewOverlayHandle = undefined;
1755
+ this.#planReviewOverlay = undefined;
1696
1756
  }
1697
1757
 
1698
1758
  #getEditorTerminalPath(): string | null {
@@ -1714,14 +1774,6 @@ export class InteractiveMode implements InteractiveModeContext {
1714
1774
  }
1715
1775
  }
1716
1776
 
1717
- #getPlanReviewHelpText(): string {
1718
- const externalEditorKey = this.keybindings.getDisplayString("app.editor.external");
1719
- if (!externalEditorKey) {
1720
- return "up/down navigate enter select esc cancel";
1721
- }
1722
- return `up/down navigate enter select ${externalEditorKey.toLowerCase()} open in editor esc cancel`;
1723
- }
1724
-
1725
1777
  #getPlanApprovalContextUsage(): ContextUsage | undefined {
1726
1778
  const executionModel = this.#planModePreviousModelState?.model ?? this.session.model;
1727
1779
  const contextWindow = executionModel?.contextWindow;
@@ -1780,7 +1832,7 @@ export class InteractiveMode implements InteractiveModeContext {
1780
1832
  });
1781
1833
  if (result !== null) {
1782
1834
  await Bun.write(resolvedPath, result);
1783
- this.#renderPlanPreview(result);
1835
+ this.#planReviewOverlay?.setPlanContent(result);
1784
1836
  this.showStatus("Plan updated in external editor.");
1785
1837
  }
1786
1838
  } catch (error) {
@@ -1812,19 +1864,12 @@ export class InteractiveMode implements InteractiveModeContext {
1812
1864
  planContent: string,
1813
1865
  options: {
1814
1866
  planFilePath: string;
1815
- finalPlanFilePath: string;
1816
1867
  title: string;
1817
1868
  preserveContext?: boolean;
1818
1869
  compactBeforeExecute?: boolean;
1819
1870
  executionModel?: ResolvedRoleModel;
1820
1871
  },
1821
1872
  ): Promise<void> {
1822
- await renameApprovedPlanFile({
1823
- planFilePath: options.planFilePath,
1824
- finalPlanFilePath: options.finalPlanFilePath,
1825
- getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
1826
- getSessionId: () => this.sessionManager.getSessionId(),
1827
- });
1828
1873
  const previousTools = this.#planModePreviousTools ?? this.session.getActiveToolNames();
1829
1874
 
1830
1875
  // Mark the pending abort caused by the plan-mode → compaction transition as
@@ -1843,8 +1888,8 @@ export class InteractiveMode implements InteractiveModeContext {
1843
1888
  if (!options.preserveContext) {
1844
1889
  await this.handleClearCommand();
1845
1890
  // The new session has a fresh local:// root — persist the approved plan there
1846
- // so `local://<title>.md` resolves correctly in the execution session.
1847
- const newLocalPath = resolveLocalUrlToPath(options.finalPlanFilePath, {
1891
+ // so `local://<slug>-plan.md` resolves correctly in the execution session.
1892
+ const newLocalPath = resolveLocalUrlToPath(options.planFilePath, {
1848
1893
  getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
1849
1894
  getSessionId: () => this.sessionManager.getSessionId(),
1850
1895
  });
@@ -1858,7 +1903,7 @@ export class InteractiveMode implements InteractiveModeContext {
1858
1903
  // Cancellation skips the synthetic-prompt dispatch (operator's explicit
1859
1904
  // abort is honored); failure proceeds best-effort — approval intent stands.
1860
1905
  const compactionPrompt = prompt.render(planModeCompactInstructionsPrompt, {
1861
- planFilePath: options.finalPlanFilePath,
1906
+ planFilePath: options.planFilePath,
1862
1907
  });
1863
1908
  // Pin the plan reference path BEFORE compaction so any user messages
1864
1909
  // queued during the compaction await (which `handleCompactCommand`
@@ -1866,7 +1911,7 @@ export class InteractiveMode implements InteractiveModeContext {
1866
1911
  // approved plan in `#buildPlanReferenceMessage`. Reassignment after
1867
1912
  // the try/finally is idempotent and kept for the !compactBeforeExecute
1868
1913
  // branch.
1869
- this.session.setPlanReferencePath(options.finalPlanFilePath);
1914
+ this.session.setPlanReferencePath(options.planFilePath);
1870
1915
  compactOutcome = await this.handleCompactCommand(compactionPrompt);
1871
1916
  }
1872
1917
  } finally {
@@ -1882,7 +1927,7 @@ export class InteractiveMode implements InteractiveModeContext {
1882
1927
  if (previousTools.length > 0) {
1883
1928
  await this.session.setActiveToolsByName(previousTools);
1884
1929
  }
1885
- this.session.setPlanReferencePath(options.finalPlanFilePath);
1930
+ this.session.setPlanReferencePath(options.planFilePath);
1886
1931
 
1887
1932
  if (compactOutcome === "cancelled") {
1888
1933
  // Explicit abort: honor it. `executeCompaction` already surfaced
@@ -1919,7 +1964,7 @@ export class InteractiveMode implements InteractiveModeContext {
1919
1964
  this.session.markPlanReferenceSent();
1920
1965
  const planModePrompt = prompt.render(planModeApprovedPrompt, {
1921
1966
  planContent,
1922
- finalPlanFilePath: options.finalPlanFilePath,
1967
+ planFilePath: options.planFilePath,
1923
1968
  contextPreserved: options.preserveContext === true,
1924
1969
  });
1925
1970
  await this.session.prompt(planModePrompt, { synthetic: true });
@@ -2209,7 +2254,6 @@ export class InteractiveMode implements InteractiveModeContext {
2209
2254
  return;
2210
2255
  }
2211
2256
 
2212
- this.#renderPlanPreview(planContent, { append: true });
2213
2257
  const contextUsage = this.#getPlanApprovalContextUsage();
2214
2258
  const keepContextLabel = this.#formatKeepContextLabel(contextUsage);
2215
2259
  const keepContextDisabled = this.#isKeepContextDisabled(contextUsage);
@@ -2239,23 +2283,40 @@ export class InteractiveMode implements InteractiveModeContext {
2239
2283
  },
2240
2284
  }
2241
2285
  : undefined;
2242
- const helpText = slider ? `${this.#getPlanReviewHelpText()} ◂/▸ model` : this.#getPlanReviewHelpText();
2243
-
2244
- const choice = await this.showHookSelector(
2286
+ // The overlay now owns the dynamic, focus-aware help line; the caller only
2287
+ // supplies the trailing cancel hint.
2288
+ const helpText = "esc cancel";
2289
+ // In-overlay edits (section deletes/undo) and section annotations. Deletes
2290
+ // update `editedContent` (and mirror to disk); annotations build `feedback`
2291
+ // that the Refine branch re-prompts the model with.
2292
+ let editedContent: string | undefined;
2293
+ let feedback = "";
2294
+
2295
+ const choice = await this.showPlanReview(
2296
+ planContent,
2245
2297
  "Plan mode - next step",
2246
2298
  ["Approve and execute", "Approve and compact context", keepContextLabel, "Refine plan"],
2247
2299
  {
2248
2300
  helpText,
2249
2301
  onExternalEditor: () => void this.#openPlanInExternalEditor(planFilePath),
2302
+ onPlanEdited: content => {
2303
+ editedContent = content;
2304
+ void Bun.write(this.#resolvePlanFilePath(planFilePath), content);
2305
+ },
2306
+ onFeedbackChange: value => {
2307
+ feedback = value;
2308
+ },
2250
2309
  disabledIndices: keepContextDisabled ? [PLAN_KEEP_CONTEXT_OPTION_INDEX] : undefined,
2251
2310
  },
2252
2311
  { slider },
2253
2312
  );
2254
2313
 
2255
2314
  if (choice === "Approve and execute" || choice === "Approve and compact context" || choice === keepContextLabel) {
2256
- const finalPlanFilePath = details.finalPlanFilePath || planFilePath;
2257
2315
  try {
2258
- const latestPlanContent = await this.#readPlanFile(planFilePath);
2316
+ // Prefer in-overlay edits (already in memory) over a disk re-read; the
2317
+ // `onPlanEdited` write is fire-and-forget, so reading the file here could
2318
+ // race ahead of it.
2319
+ const latestPlanContent = editedContent ?? (await this.#readPlanFile(planFilePath));
2259
2320
  if (!latestPlanContent) {
2260
2321
  this.showError(`Plan file not found at ${planFilePath}`);
2261
2322
  return;
@@ -2273,7 +2334,6 @@ export class InteractiveMode implements InteractiveModeContext {
2273
2334
  cycle && selectedTierIndex !== cycle.currentIndex ? cycle.models[selectedTierIndex] : undefined;
2274
2335
  await this.#approvePlan(latestPlanContent, {
2275
2336
  planFilePath,
2276
- finalPlanFilePath,
2277
2337
  title: details.title,
2278
2338
  preserveContext: choice !== "Approve and execute",
2279
2339
  compactBeforeExecute: choice === "Approve and compact context",
@@ -2286,6 +2346,16 @@ export class InteractiveMode implements InteractiveModeContext {
2286
2346
  }
2287
2347
  return;
2288
2348
  }
2349
+
2350
+ if (choice === "Refine plan") {
2351
+ // Section annotations entered in the overlay become a refinement prompt
2352
+ // re-submitted to the model. With no annotations, fall back to today's
2353
+ // behavior: close the overlay and let the operator type their own.
2354
+ if (feedback.trim() && this.onInputCallback) {
2355
+ this.onInputCallback(this.startPendingSubmission({ text: feedback }));
2356
+ }
2357
+ return;
2358
+ }
2289
2359
  }
2290
2360
 
2291
2361
  /**
@@ -2439,9 +2509,6 @@ export class InteractiveMode implements InteractiveModeContext {
2439
2509
  initializeHookRunner(uiContext: ExtensionUIContext, hasUI: boolean): void {
2440
2510
  this.#extensionUiController.initializeHookRunner(uiContext, hasUI);
2441
2511
  }
2442
- createBackgroundUiContext(): ExtensionUIContext {
2443
- return this.#extensionUiController.createBackgroundUiContext();
2444
- }
2445
2512
 
2446
2513
  setEditorComponent(
2447
2514
  factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => CustomEditor) | undefined,
@@ -2483,12 +2550,26 @@ export class InteractiveMode implements InteractiveModeContext {
2483
2550
  this.ui.requestRender();
2484
2551
  }
2485
2552
 
2486
- // Event handling
2487
- async handleBackgroundEvent(event: AgentSessionEvent): Promise<void> {
2488
- await this.#eventController.handleBackgroundEvent(event);
2553
+ // UI helpers
2554
+ present(content: Component | readonly Component[]): void {
2555
+ if (Array.isArray(content)) {
2556
+ for (const item of content) this.#mountChatChild(item);
2557
+ } else {
2558
+ this.#mountChatChild(content as Component);
2559
+ }
2560
+ this.ui.requestRender();
2561
+ }
2562
+
2563
+ #mountChatChild(item: Component): void {
2564
+ this.chatContainer.addChild(item);
2565
+ if (item instanceof ChatBlock) item.mount(this.#chatHost);
2566
+ }
2567
+
2568
+ resetTranscript(): void {
2569
+ this.chatContainer.dispose();
2570
+ this.chatContainer.clear();
2489
2571
  }
2490
2572
 
2491
- // UI helpers
2492
2573
  showStatus(message: string, options?: { dim?: boolean }): void {
2493
2574
  this.#uiHelpers.showStatus(message, options);
2494
2575
  }
@@ -2508,7 +2589,6 @@ export class InteractiveMode implements InteractiveModeContext {
2508
2589
  }
2509
2590
 
2510
2591
  showPinnedError(message: string): void {
2511
- if (this.isBackgrounded) return;
2512
2592
  this.errorBannerContainer.clear();
2513
2593
  this.errorBannerContainer.addChild(new ErrorBannerComponent(message));
2514
2594
  this.ui.requestRender();
@@ -2579,13 +2659,18 @@ export class InteractiveMode implements InteractiveModeContext {
2579
2659
  ensureLoadingAnimation(): void {
2580
2660
  if (!this.loadingAnimation) {
2581
2661
  this.statusContainer.clear();
2662
+ const messageColorFn = ((message: string) =>
2663
+ renderWorkingMessage(message, this.#getWorkingMessageAccent())) as LoaderMessageColorFn & {
2664
+ animated: true;
2665
+ };
2666
+ messageColorFn.animated = true;
2582
2667
  this.loadingAnimation = new Loader(
2583
2668
  this.ui,
2584
2669
  spinner => {
2585
2670
  const accent = this.#getWorkingMessageAccent();
2586
2671
  return accent ? `${accent.main}${spinner}\x1b[39m` : theme.fg("accent", spinner);
2587
2672
  },
2588
- message => renderWorkingMessage(message, this.#getWorkingMessageAccent()),
2673
+ messageColorFn,
2589
2674
  this.#defaultWorkingMessage,
2590
2675
  getSymbolTheme().spinnerFrames,
2591
2676
  );
@@ -2737,7 +2822,7 @@ export class InteractiveMode implements InteractiveModeContext {
2737
2822
  this.#omfgController.dispose();
2738
2823
  this.#extensionUiController.clearExtensionTerminalInputListeners();
2739
2824
  this.clearPinnedError();
2740
- this.#planReviewContainer = undefined;
2825
+ this.#hidePlanReview();
2741
2826
  }
2742
2827
 
2743
2828
  handleClearCommand(): Promise<void> {
@@ -2745,6 +2830,10 @@ export class InteractiveMode implements InteractiveModeContext {
2745
2830
  return this.#commandController.handleClearCommand();
2746
2831
  }
2747
2832
 
2833
+ handleFreshCommand(): Promise<void> {
2834
+ return this.#commandController.handleFreshCommand();
2835
+ }
2836
+
2748
2837
  handleDropCommand(): Promise<void> {
2749
2838
  this.#prepareSessionSwitch();
2750
2839
  return this.#commandController.handleDropCommand();
@@ -2844,8 +2933,8 @@ export class InteractiveMode implements InteractiveModeContext {
2844
2933
  }
2845
2934
  }
2846
2935
 
2847
- showDebugSelector(): void {
2848
- this.#selectorController.showDebugSelector();
2936
+ async showDebugSelector(): Promise<void> {
2937
+ await this.#selectorController.showDebugSelector();
2849
2938
  }
2850
2939
 
2851
2940
  showSessionObserver(): void {
@@ -2980,10 +3069,6 @@ export class InteractiveMode implements InteractiveModeContext {
2980
3069
  this.#inputController.handleDequeue();
2981
3070
  }
2982
3071
 
2983
- handleBackgroundCommand(): void {
2984
- this.#inputController.handleBackgroundCommand();
2985
- }
2986
-
2987
3072
  handleImagePaste(): Promise<boolean> {
2988
3073
  return this.#inputController.handleImagePaste();
2989
3074
  }
@@ -2992,6 +3077,10 @@ export class InteractiveMode implements InteractiveModeContext {
2992
3077
  return this.#btwController.start(question);
2993
3078
  }
2994
3079
 
3080
+ handleTanCommand(work: string): Promise<void> {
3081
+ return this.#tanCommandController.start(work);
3082
+ }
3083
+
2995
3084
  hasActiveBtw(): boolean {
2996
3085
  return this.#btwController.hasActiveRequest();
2997
3086
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Setup version the wizard advances a fresh install to. Bump it whenever a new
3
+ * setup scene lands (or an existing scene raises its `minVersion`).
4
+ *
5
+ * Kept in its own dependency-free module so the cold-launch gate in `main.ts`
6
+ * can answer "is the stored setup version stale?" without statically importing
7
+ * the full wizard — every scene (sign-in/OAuth, web search, theme previews) plus
8
+ * the overlay component and their TUI deps. MUST equal `max(scene.minVersion)`
9
+ * across `ALL_SCENES`; the `setup-wizard` barrel and test suite guard it.
10
+ */
11
+ export const CURRENT_SETUP_VERSION = 1;
@@ -1,4 +1,5 @@
1
1
  import type { Settings } from "../../config/settings";
2
+ import { CURRENT_SETUP_VERSION } from "../setup-version";
2
3
  import type { InteractiveModeContext } from "../types";
3
4
  import { glyphSetupScene } from "./scenes/glyph";
4
5
  import { providersSetupScene } from "./scenes/providers";
@@ -8,14 +9,14 @@ import { SetupWizardComponent } from "./wizard-overlay";
8
9
 
9
10
  export type { SetupScene, SetupSceneController, SetupSceneHost, SetupSceneResult } from "./scenes/types";
10
11
 
12
+ export { CURRENT_SETUP_VERSION };
13
+
11
14
  export const ALL_SCENES = [
12
15
  providersSetupScene,
13
16
  glyphSetupScene,
14
17
  themeSetupScene,
15
18
  ] as const satisfies readonly SetupScene[];
16
19
 
17
- export const CURRENT_SETUP_VERSION = ALL_SCENES.reduce((max, scene) => Math.max(max, scene.minVersion), 0);
18
-
19
20
  export interface SetupSceneSelectionOptions {
20
21
  resuming?: boolean;
21
22
  isTTY?: boolean;
@@ -19,7 +19,8 @@ type Availability = "checking" | boolean;
19
19
  /**
20
20
  * "Web search" panel: picks the provider the web_search tool should prefer and
21
21
  * reports whether the highlighted provider is ready to use given current
22
- * credentials (env keys or OAuth sign-ins from the Sign in tab).
22
+ * credentials (env keys or OAuth sign-ins from the Sign in tab) or an
23
+ * unauthenticated fallback.
23
24
  */
24
25
  export class WebSearchTab implements SetupTab {
25
26
  readonly id = "web-search";
@@ -91,7 +92,7 @@ export class WebSearchTab implements SetupTab {
91
92
  let ready = false;
92
93
  try {
93
94
  const provider = await getSearchProvider(id);
94
- ready = await provider.isAvailable(this.host.ctx.session.modelRegistry.authStorage);
95
+ ready = await provider.isExplicitlyAvailable(this.host.ctx.session.modelRegistry.authStorage);
95
96
  } catch {
96
97
  ready = false;
97
98
  }
@@ -82,7 +82,7 @@ export class SetupWizardComponent implements Component {
82
82
  }
83
83
 
84
84
  invalidate(): void {
85
- this.#activeScene?.invalidate();
85
+ this.#activeScene?.invalidate?.();
86
86
  }
87
87
 
88
88
  handleInput(data: string): void {
@@ -406,7 +406,7 @@
406
406
  }
407
407
  },
408
408
  "spinnerFrames": {
409
- "description": "Override the spinner frames. Use a flat array to set both `status` and `activity`, or an object to override each independently. Frames are advanced ~12.5fps for status spinners and ~60fps for activity spinners.",
409
+ "description": "Override the spinner frames. Use a flat array to set both `status` and `activity`, or an object to override each independently. Frames are advanced ~12.5fps for status spinners and ~30fps for activity spinners.",
410
410
  "oneOf": [
411
411
  {
412
412
  "type": "array",
@@ -95,6 +95,7 @@ export type SymbolKey =
95
95
  | "icon.pause"
96
96
  | "icon.loop"
97
97
  | "icon.folder"
98
+ | "icon.search"
98
99
  | "icon.scratchFolder"
99
100
  | "icon.file"
100
101
  | "icon.git"
@@ -264,6 +265,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
264
265
  "icon.pause": "⏸",
265
266
  "icon.loop": "↻",
266
267
  "icon.folder": "📁",
268
+ "icon.search": "🔍",
267
269
  "icon.scratchFolder": "🗑",
268
270
  "icon.file": "📄",
269
271
  "icon.git": "⎇",
@@ -488,6 +490,7 @@ const NERD_SYMBOLS: SymbolMap = {
488
490
  "icon.loop": "\uf021",
489
491
  // pick:  | alt:  
490
492
  "icon.folder": "\uf115",
493
+ "icon.search": "\uf002",
491
494
  // pick: | alt:
492
495
  "icon.scratchFolder": "\uf014",
493
496
  // pick:  | alt:  
@@ -701,6 +704,7 @@ const ASCII_SYMBOLS: SymbolMap = {
701
704
  "icon.pause": "||",
702
705
  "icon.loop": "loop",
703
706
  "icon.folder": "[D]",
707
+ "icon.search": "[/]",
704
708
  "icon.scratchFolder": "[T]",
705
709
  "icon.file": "[F]",
706
710
  "icon.git": "git:",
@@ -2439,10 +2443,10 @@ function getHighlightColors(t: Theme): NativeHighlightColors {
2439
2443
  * switch (which always reassigns `theme`) must invalidate every entry.
2440
2444
  *
2441
2445
  * Why this exists: animated tool blocks (eval/bash) repaint their box on every
2442
- * ~16ms border-shimmer frame, and markdown re-lexes on every streamed delta.
2443
- * Without memoization each frame re-tokenizes an unchanged code body through the
2444
- * Rust FFI — ~26ms for 100 lines, ~40ms for 150 — overrunning the 16ms frame
2445
- * budget and starving the spinner/render timers (the "TUI freeze").
2446
+ * ~33ms border-shimmer frame, and markdown re-lexes on every streamed delta.
2447
+ * Without memoization each frame can re-tokenize an unchanged code body through
2448
+ * the Rust FFI — ~26ms for 100 lines, ~40ms for 150 — consuming or overrunning
2449
+ * the 33ms frame budget and starving the spinner/render timers (the "TUI freeze").
2446
2450
  */
2447
2451
  const HIGHLIGHT_CACHE_MAX = 256;
2448
2452
  const highlightCache = new LRUCache<string, string>({ max: HIGHLIGHT_CACHE_MAX });