@alumbwe/anvil 1.0.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 (529) hide show
  1. package/README.md +84 -0
  2. package/bin/anvil +2 -0
  3. package/bin/tree-sitter.wasm +0 -0
  4. package/package.json +67 -0
  5. package/src/__tests__/README.md +256 -0
  6. package/src/__tests__/bash-mode.test.ts +442 -0
  7. package/src/__tests__/cli-args.test.ts +181 -0
  8. package/src/__tests__/cli-harness.test.ts +52 -0
  9. package/src/__tests__/e2e/first-time-login.test.ts +173 -0
  10. package/src/__tests__/e2e/logout-relogin-flow.test.ts +110 -0
  11. package/src/__tests__/e2e/returning-user-auth.test.ts +113 -0
  12. package/src/__tests__/e2e-cli.test.ts +183 -0
  13. package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +176 -0
  14. package/src/__tests__/helpers/mock-api-client.ts +64 -0
  15. package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +89 -0
  16. package/src/__tests__/helpers/terminal-watchdog-fixture.ts +75 -0
  17. package/src/__tests__/home-directory-detection.test.ts +213 -0
  18. package/src/__tests__/integration/api-integration.test.ts +288 -0
  19. package/src/__tests__/integration/credentials-storage.test.ts +486 -0
  20. package/src/__tests__/integration/local-agents.test.ts +1187 -0
  21. package/src/__tests__/integration/login-flow-instrumentation.test.ts +134 -0
  22. package/src/__tests__/integration/login-polling-working.test.ts +285 -0
  23. package/src/__tests__/integration/queue-editing.test.tsx +261 -0
  24. package/src/__tests__/integration/usage-refresh-on-completion.test.ts +168 -0
  25. package/src/__tests__/integration-tmux.test.ts +188 -0
  26. package/src/__tests__/launcher-avx2-fallback.test.ts +630 -0
  27. package/src/__tests__/launcher-disconnect.test.ts +104 -0
  28. package/src/__tests__/mocks/hover-toggle-controller.ts +71 -0
  29. package/src/__tests__/open-tui-native-bundle.test.ts +378 -0
  30. package/src/__tests__/path-completion.test.ts +243 -0
  31. package/src/__tests__/project-files-chat-id.test.ts +34 -0
  32. package/src/__tests__/release/proxy-http-get.test.ts +667 -0
  33. package/src/__tests__/release/wrapper-safety.test.ts +412 -0
  34. package/src/__tests__/renderer-cleanup.test.ts +174 -0
  35. package/src/__tests__/rerender-perf.integration.test.ts +283 -0
  36. package/src/__tests__/terminal-reset-sequences.test.ts +36 -0
  37. package/src/__tests__/terminal-watchdog.test.ts +388 -0
  38. package/src/__tests__/test-utils.ts +271 -0
  39. package/src/__tests__/tmux-poc.ts +150 -0
  40. package/src/__tests__/unit/agent-mode-toggle.test.ts +137 -0
  41. package/src/__tests__/unit/copy-button.test.ts +157 -0
  42. package/src/__tests__/unit/create-run-config.test.ts +66 -0
  43. package/src/__tests__/unit/publish-confirmation.test.ts +70 -0
  44. package/src/__tests__/unit/segmented-control.test.ts +152 -0
  45. package/src/__tests__/utils/env.test.ts +169 -0
  46. package/src/__tests__/utils/project-picker.test.ts +39 -0
  47. package/src/agents/bundled-agents.generated.d.ts +14 -0
  48. package/src/agents/bundled-agents.generated.ts +3692 -0
  49. package/src/app.tsx +399 -0
  50. package/src/chat.tsx +1698 -0
  51. package/src/cli-args.ts +134 -0
  52. package/src/commands/__tests__/bash-command.test.ts +445 -0
  53. package/src/commands/__tests__/command-args.test.ts +349 -0
  54. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
  55. package/src/commands/__tests__/copy-conversation.test.ts +226 -0
  56. package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
  57. package/src/commands/__tests__/freebuff-command-aliases.test.ts +56 -0
  58. package/src/commands/__tests__/image.test.ts +99 -0
  59. package/src/commands/__tests__/init.test.ts +456 -0
  60. package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
  61. package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
  62. package/src/commands/__tests__/prompt-builders.test.ts +28 -0
  63. package/src/commands/__tests__/router-input.test.ts +298 -0
  64. package/src/commands/ads.ts +44 -0
  65. package/src/commands/command-registry.ts +726 -0
  66. package/src/commands/copy-conversation.ts +392 -0
  67. package/src/commands/help.ts +13 -0
  68. package/src/commands/image.ts +20 -0
  69. package/src/commands/init.ts +121 -0
  70. package/src/commands/process-diagnostics.ts +120 -0
  71. package/src/commands/prompt-builders.ts +100 -0
  72. package/src/commands/publish.ts +203 -0
  73. package/src/commands/router-utils.ts +80 -0
  74. package/src/commands/router.ts +487 -0
  75. package/src/commands/usage.ts +26 -0
  76. package/src/components/__tests__/ad-banner.test.tsx +83 -0
  77. package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
  78. package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +91 -0
  79. package/src/components/__tests__/freebuff-model-selector.test.tsx +519 -0
  80. package/src/components/__tests__/freebuff-offer-invariants.test.ts +54 -0
  81. package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +212 -0
  82. package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
  83. package/src/components/__tests__/grid-layout.test.tsx +1051 -0
  84. package/src/components/__tests__/message-block.completion.test.tsx +104 -0
  85. package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
  86. package/src/components/__tests__/message-with-agents.test.tsx +579 -0
  87. package/src/components/__tests__/multiline-input.test.tsx +1132 -0
  88. package/src/components/__tests__/queue-panel.test.tsx +398 -0
  89. package/src/components/__tests__/selectable-list.test.ts +232 -0
  90. package/src/components/__tests__/status-bar.test.tsx +44 -0
  91. package/src/components/__tests__/status-indicator.test.tsx +204 -0
  92. package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
  93. package/src/components/__tests__/user-error-banner.test.tsx +102 -0
  94. package/src/components/ad-banner.tsx +323 -0
  95. package/src/components/agent-checklist.tsx +350 -0
  96. package/src/components/agent-mode-toggle.tsx +241 -0
  97. package/src/components/anvil-mark.tsx +52 -0
  98. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
  99. package/src/components/ask-user/__tests__/validation.test.ts +213 -0
  100. package/src/components/ask-user/components/accordion-question.tsx +153 -0
  101. package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
  102. package/src/components/ask-user/components/options-list.tsx +133 -0
  103. package/src/components/ask-user/components/question-header.tsx +80 -0
  104. package/src/components/ask-user/components/question-option.tsx +89 -0
  105. package/src/components/ask-user/constants.ts +38 -0
  106. package/src/components/ask-user/index.tsx +631 -0
  107. package/src/components/ask-user/utils/validation.ts +85 -0
  108. package/src/components/attachment-card.tsx +65 -0
  109. package/src/components/blocks/agent-block-grid.tsx +58 -0
  110. package/src/components/blocks/agent-branch-item.tsx +311 -0
  111. package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
  112. package/src/components/blocks/agent-list-branch.tsx +77 -0
  113. package/src/components/blocks/ask-user-branch.tsx +81 -0
  114. package/src/components/blocks/block-helpers.ts +11 -0
  115. package/src/components/blocks/blocks-renderer.tsx +289 -0
  116. package/src/components/blocks/content-with-markdown.tsx +33 -0
  117. package/src/components/blocks/image-block.tsx +128 -0
  118. package/src/components/blocks/implementor-row.tsx +429 -0
  119. package/src/components/blocks/single-block.tsx +201 -0
  120. package/src/components/blocks/thinking-block.tsx +67 -0
  121. package/src/components/blocks/tool-block-group.tsx +57 -0
  122. package/src/components/blocks/tool-branch.tsx +206 -0
  123. package/src/components/blocks/user-content-copy.tsx +157 -0
  124. package/src/components/bottom-banner.tsx +136 -0
  125. package/src/components/build-mode-buttons.tsx +110 -0
  126. package/src/components/button.tsx +66 -0
  127. package/src/components/chat-header.tsx +77 -0
  128. package/src/components/chat-history-screen.tsx +413 -0
  129. package/src/components/chat-input-bar.tsx +523 -0
  130. package/src/components/clickable-title-box.tsx +66 -0
  131. package/src/components/clickable.tsx +119 -0
  132. package/src/components/collapse-button.tsx +34 -0
  133. package/src/components/copy-button.tsx +203 -0
  134. package/src/components/elapsed-timer.tsx +55 -0
  135. package/src/components/error-boundary.tsx +55 -0
  136. package/src/components/feedback-container.tsx +181 -0
  137. package/src/components/feedback-icon-button.tsx +89 -0
  138. package/src/components/feedback-input-mode.tsx +344 -0
  139. package/src/components/file-attachment-card.tsx +98 -0
  140. package/src/components/freebuff-active-session-summary.tsx +63 -0
  141. package/src/components/freebuff-landing-screen.tsx +865 -0
  142. package/src/components/freebuff-model-selector.tsx +1087 -0
  143. package/src/components/freebuff-referral-banner.tsx +604 -0
  144. package/src/components/freebuff-superseded-screen.tsx +62 -0
  145. package/src/components/grid-layout.tsx +80 -0
  146. package/src/components/help-banner.tsx +127 -0
  147. package/src/components/highlighted-text.tsx +52 -0
  148. package/src/components/image-card.tsx +167 -0
  149. package/src/components/image-thumbnail.tsx +114 -0
  150. package/src/components/input-cursor.tsx +77 -0
  151. package/src/components/input-mode-banner.tsx +57 -0
  152. package/src/components/load-previous-button.tsx +48 -0
  153. package/src/components/login-modal.tsx +475 -0
  154. package/src/components/message-block.tsx +346 -0
  155. package/src/components/message-footer.tsx +258 -0
  156. package/src/components/message-with-agents.tsx +506 -0
  157. package/src/components/mode-divider.tsx +43 -0
  158. package/src/components/multiline-input.tsx +1241 -0
  159. package/src/components/out-of-credits-banner.tsx +160 -0
  160. package/src/components/pending-attachments-banner.tsx +116 -0
  161. package/src/components/pending-bash-message.tsx +59 -0
  162. package/src/components/progress-bar.tsx +81 -0
  163. package/src/components/project-picker-screen.tsx +485 -0
  164. package/src/components/publish-confirmation.tsx +493 -0
  165. package/src/components/publish-container.tsx +687 -0
  166. package/src/components/queue-panel.tsx +310 -0
  167. package/src/components/raised-pill.tsx +95 -0
  168. package/src/components/renderers/plan-box.tsx +59 -0
  169. package/src/components/review-screen.tsx +112 -0
  170. package/src/components/scroll-to-bottom-button.tsx +34 -0
  171. package/src/components/segmented-control.tsx +187 -0
  172. package/src/components/selectable-list.tsx +248 -0
  173. package/src/components/selected-chips.tsx +80 -0
  174. package/src/components/separator.tsx +40 -0
  175. package/src/components/session-ended-banner.tsx +226 -0
  176. package/src/components/shimmer-text.tsx +248 -0
  177. package/src/components/status-bar.tsx +313 -0
  178. package/src/components/subscription-limit-banner.tsx +206 -0
  179. package/src/components/suggested-prompts.tsx +184 -0
  180. package/src/components/suggestion-menu.tsx +215 -0
  181. package/src/components/terminal-command-display.tsx +151 -0
  182. package/src/components/terminal-link.tsx +97 -0
  183. package/src/components/terminal-thinking-orb.tsx +95 -0
  184. package/src/components/text-attachment-card.tsx +77 -0
  185. package/src/components/thinking.tsx +109 -0
  186. package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
  187. package/src/components/tools/__tests__/code-search.test.tsx +45 -0
  188. package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
  189. package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
  190. package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
  191. package/src/components/tools/apply-patch.tsx +95 -0
  192. package/src/components/tools/code-search.tsx +45 -0
  193. package/src/components/tools/composio.tsx +152 -0
  194. package/src/components/tools/diff-viewer.tsx +76 -0
  195. package/src/components/tools/glob.tsx +61 -0
  196. package/src/components/tools/gravity-index.tsx +77 -0
  197. package/src/components/tools/list-directory.tsx +61 -0
  198. package/src/components/tools/read-docs.tsx +37 -0
  199. package/src/components/tools/read-files.tsx +91 -0
  200. package/src/components/tools/read-subtree.tsx +44 -0
  201. package/src/components/tools/read-url.tsx +33 -0
  202. package/src/components/tools/registry.ts +126 -0
  203. package/src/components/tools/render-ui.tsx +122 -0
  204. package/src/components/tools/run-terminal-command.tsx +79 -0
  205. package/src/components/tools/skill.tsx +29 -0
  206. package/src/components/tools/str-replace.tsx +77 -0
  207. package/src/components/tools/suggest-followups.tsx +369 -0
  208. package/src/components/tools/task-completed.tsx +16 -0
  209. package/src/components/tools/tool-call-item.tsx +273 -0
  210. package/src/components/tools/types.ts +55 -0
  211. package/src/components/tools/web-search.tsx +33 -0
  212. package/src/components/tools/write-file.tsx +16 -0
  213. package/src/components/tools/write-todos.tsx +95 -0
  214. package/src/components/top-banner.tsx +178 -0
  215. package/src/components/usage-banner.tsx +244 -0
  216. package/src/components/user-error-banner.tsx +56 -0
  217. package/src/components/validation-error-popover.tsx +192 -0
  218. package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
  219. package/src/contexts/chat-runtime-context.tsx +240 -0
  220. package/src/data/slash-commands.test.ts +24 -0
  221. package/src/data/slash-commands.ts +249 -0
  222. package/src/entry.ts +12 -0
  223. package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +49 -0
  224. package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
  225. package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
  226. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
  227. package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
  228. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
  229. package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
  230. package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
  231. package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
  232. package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
  233. package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
  234. package/src/hooks/__tests__/use-input-history.test.ts +699 -0
  235. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
  236. package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
  237. package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
  238. package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
  239. package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
  240. package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
  241. package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
  242. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
  243. package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
  244. package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
  245. package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
  246. package/src/hooks/__tests__/use-timeout.test.ts +330 -0
  247. package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
  248. package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
  249. package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
  250. package/src/hooks/helpers/send-message.ts +654 -0
  251. package/src/hooks/stream-state.ts +83 -0
  252. package/src/hooks/use-activity-query.ts +668 -0
  253. package/src/hooks/use-agent-validation.ts +74 -0
  254. package/src/hooks/use-ask-user-bridge.ts +92 -0
  255. package/src/hooks/use-auth-query.ts +253 -0
  256. package/src/hooks/use-auth-state.ts +152 -0
  257. package/src/hooks/use-chat-input.ts +105 -0
  258. package/src/hooks/use-chat-keyboard.ts +321 -0
  259. package/src/hooks/use-chat-messages.ts +254 -0
  260. package/src/hooks/use-chat-state.ts +165 -0
  261. package/src/hooks/use-chat-streaming.ts +180 -0
  262. package/src/hooks/use-chat-ui.ts +131 -0
  263. package/src/hooks/use-clipboard.ts +151 -0
  264. package/src/hooks/use-connection-status.ts +142 -0
  265. package/src/hooks/use-directory-browser.ts +88 -0
  266. package/src/hooks/use-elapsed-time.ts +116 -0
  267. package/src/hooks/use-event.ts +34 -0
  268. package/src/hooks/use-exit-handler.ts +66 -0
  269. package/src/hooks/use-fetch-login-url.ts +65 -0
  270. package/src/hooks/use-fingerprint.ts +61 -0
  271. package/src/hooks/use-freebuff-ctrl-c-exit.ts +23 -0
  272. package/src/hooks/use-freebuff-session-progress.ts +34 -0
  273. package/src/hooks/use-freebuff-session.ts +806 -0
  274. package/src/hooks/use-freebuff-streak-query.ts +61 -0
  275. package/src/hooks/use-gravity-ad.ts +656 -0
  276. package/src/hooks/use-grid-layout.ts +73 -0
  277. package/src/hooks/use-input-history.ts +173 -0
  278. package/src/hooks/use-login-keyboard-handlers.ts +75 -0
  279. package/src/hooks/use-login-polling.ts +114 -0
  280. package/src/hooks/use-logo.tsx +161 -0
  281. package/src/hooks/use-message-queue.ts +387 -0
  282. package/src/hooks/use-now.ts +20 -0
  283. package/src/hooks/use-path-tab-completion.ts +88 -0
  284. package/src/hooks/use-publish-mutation.ts +60 -0
  285. package/src/hooks/use-queue-controls.ts +68 -0
  286. package/src/hooks/use-queue-ui.ts +74 -0
  287. package/src/hooks/use-scroll-management.ts +184 -0
  288. package/src/hooks/use-searchable-list.ts +98 -0
  289. package/src/hooks/use-send-message.ts +773 -0
  290. package/src/hooks/use-sheen-animation.tsx +88 -0
  291. package/src/hooks/use-subscription-query.ts +71 -0
  292. package/src/hooks/use-suggestion-engine.ts +783 -0
  293. package/src/hooks/use-terminal-breakpoints.ts +77 -0
  294. package/src/hooks/use-terminal-dimensions.ts +46 -0
  295. package/src/hooks/use-terminal-focus.ts +29 -0
  296. package/src/hooks/use-terminal-layout.ts +189 -0
  297. package/src/hooks/use-theme.tsx +140 -0
  298. package/src/hooks/use-timeout.ts +85 -0
  299. package/src/hooks/use-update-preference.ts +66 -0
  300. package/src/hooks/use-usage-monitor.ts +55 -0
  301. package/src/hooks/use-usage-query.ts +124 -0
  302. package/src/hooks/use-user-details-query.ts +93 -0
  303. package/src/hooks/use-why-did-you-update.ts +177 -0
  304. package/src/index.tsx +441 -0
  305. package/src/init/__tests__/init-direnv.test.ts +526 -0
  306. package/src/init/init-app.ts +34 -0
  307. package/src/init/init-direnv.ts +133 -0
  308. package/src/login/constants.ts +60 -0
  309. package/src/login/login-flow.ts +204 -0
  310. package/src/login/plain-login.ts +108 -0
  311. package/src/login/utils.ts +183 -0
  312. package/src/native/ripgrep.ts +80 -0
  313. package/src/polyfills/bun-strip-ansi.ts +12 -0
  314. package/src/pre-init/tree-sitter-wasm.ts +96 -0
  315. package/src/project-files.ts +102 -0
  316. package/src/smoke/terminal-command-broker.ts +439 -0
  317. package/src/state/__tests__/chat-store-focus.test.ts +22 -0
  318. package/src/state/__tests__/feedback-store.test.ts +257 -0
  319. package/src/state/chat-history-store.ts +30 -0
  320. package/src/state/chat-store.ts +544 -0
  321. package/src/state/feedback-store.ts +160 -0
  322. package/src/state/freebuff-model-store.ts +42 -0
  323. package/src/state/freebuff-session-store.ts +53 -0
  324. package/src/state/login-store.ts +150 -0
  325. package/src/state/message-block-store.ts +149 -0
  326. package/src/state/publish-store.ts +146 -0
  327. package/src/state/queue-panel-store.ts +26 -0
  328. package/src/state/review-store.ts +24 -0
  329. package/src/testing/env.ts +46 -0
  330. package/src/types/chat-state.ts +15 -0
  331. package/src/types/chat.ts +241 -0
  332. package/src/types/contracts/send-message.ts +12 -0
  333. package/src/types/env.ts +98 -0
  334. package/src/types/freebuff-session.ts +17 -0
  335. package/src/types/function-params.ts +28 -0
  336. package/src/types/react19-compat.d.ts +19 -0
  337. package/src/types/store.ts +111 -0
  338. package/src/types/theme-system.ts +144 -0
  339. package/src/types/utils.ts +3 -0
  340. package/src/utils/__tests__/active-run.test.ts +140 -0
  341. package/src/utils/__tests__/activity-tracker.test.ts +440 -0
  342. package/src/utils/__tests__/agent-display.test.ts +139 -0
  343. package/src/utils/__tests__/analytics-client.test.ts +262 -0
  344. package/src/utils/__tests__/anonymous-id.test.ts +74 -0
  345. package/src/utils/__tests__/anvil-api.test.ts +578 -0
  346. package/src/utils/__tests__/arrays.test.ts +264 -0
  347. package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
  348. package/src/utils/__tests__/block-processor.test.ts +810 -0
  349. package/src/utils/__tests__/chat-history.test.ts +203 -0
  350. package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
  351. package/src/utils/__tests__/chat-meta.test.ts +130 -0
  352. package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
  353. package/src/utils/__tests__/clipboard.test.ts +910 -0
  354. package/src/utils/__tests__/code-search-summary.test.ts +84 -0
  355. package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
  356. package/src/utils/__tests__/error-handling.test.ts +658 -0
  357. package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
  358. package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
  359. package/src/utils/__tests__/feedback-submission.test.ts +26 -0
  360. package/src/utils/__tests__/fetch-usage.test.ts +355 -0
  361. package/src/utils/__tests__/fingerprint.test.ts +144 -0
  362. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
  363. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
  364. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
  365. package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
  366. package/src/utils/__tests__/format-timeout.test.ts +87 -0
  367. package/src/utils/__tests__/freebuff-instance-owner.test.ts +69 -0
  368. package/src/utils/__tests__/freebuff-model-navigation.test.ts +102 -0
  369. package/src/utils/__tests__/freebuff-premium-reset.test.ts +79 -0
  370. package/src/utils/__tests__/freebuff-referral-cache.test.ts +116 -0
  371. package/src/utils/__tests__/freebuff-session-api.test.ts +215 -0
  372. package/src/utils/__tests__/freebuff-session-display.test.ts +23 -0
  373. package/src/utils/__tests__/freebuff-streak-line.test.ts +217 -0
  374. package/src/utils/__tests__/image-dimensions.test.ts +249 -0
  375. package/src/utils/__tests__/image-processor.test.ts +209 -0
  376. package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
  377. package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
  378. package/src/utils/__tests__/layout-helpers.test.ts +36 -0
  379. package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
  380. package/src/utils/__tests__/log-shipper.test.ts +53 -0
  381. package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
  382. package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
  383. package/src/utils/__tests__/message-updater.test.ts +645 -0
  384. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
  385. package/src/utils/__tests__/pending-attachments.test.ts +277 -0
  386. package/src/utils/__tests__/polling-backoff.test.ts +51 -0
  387. package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
  388. package/src/utils/__tests__/run-state-storage.test.ts +919 -0
  389. package/src/utils/__tests__/safe-json.test.ts +77 -0
  390. package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
  391. package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
  392. package/src/utils/__tests__/send-message-timer.test.ts +64 -0
  393. package/src/utils/__tests__/settings.test.ts +99 -0
  394. package/src/utils/__tests__/strings.test.ts +192 -0
  395. package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
  396. package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
  397. package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
  398. package/src/utils/__tests__/terminal-io.test.ts +137 -0
  399. package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
  400. package/src/utils/__tests__/text-layout.test.ts +82 -0
  401. package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
  402. package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
  403. package/src/utils/__tests__/trace-writer.test.ts +223 -0
  404. package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
  405. package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
  406. package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
  407. package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
  408. package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
  409. package/src/utils/active-run.ts +131 -0
  410. package/src/utils/activity-tracker.ts +66 -0
  411. package/src/utils/agent-display.ts +87 -0
  412. package/src/utils/agent-helpers.ts +27 -0
  413. package/src/utils/agent-id-utils.ts +14 -0
  414. package/src/utils/ai-message-id.ts +12 -0
  415. package/src/utils/analytics.ts +361 -0
  416. package/src/utils/anonymous-id.ts +87 -0
  417. package/src/utils/anvil-api.ts +612 -0
  418. package/src/utils/anvil-client.ts +217 -0
  419. package/src/utils/arrays.ts +117 -0
  420. package/src/utils/auth.ts +258 -0
  421. package/src/utils/bash-context-processor.ts +47 -0
  422. package/src/utils/bash-messages.ts +108 -0
  423. package/src/utils/block-margins.ts +35 -0
  424. package/src/utils/block-operations.ts +523 -0
  425. package/src/utils/block-processor.ts +213 -0
  426. package/src/utils/chat-history.ts +253 -0
  427. package/src/utils/chat-input-key-intercept.ts +52 -0
  428. package/src/utils/chat-meta.ts +90 -0
  429. package/src/utils/chat-scroll-accel.ts +151 -0
  430. package/src/utils/clipboard-image.ts +588 -0
  431. package/src/utils/clipboard.ts +397 -0
  432. package/src/utils/code-search-summary.ts +70 -0
  433. package/src/utils/collapse-helpers.ts +256 -0
  434. package/src/utils/config-dir.ts +24 -0
  435. package/src/utils/constants.ts +190 -0
  436. package/src/utils/create-event-handler-state.ts +73 -0
  437. package/src/utils/create-run-config.ts +121 -0
  438. package/src/utils/detect-shell.ts +116 -0
  439. package/src/utils/directory-browser.ts +72 -0
  440. package/src/utils/engagement.ts +55 -0
  441. package/src/utils/env.ts +92 -0
  442. package/src/utils/error-handling.ts +255 -0
  443. package/src/utils/error-messages.ts +62 -0
  444. package/src/utils/exit-cleanly.ts +86 -0
  445. package/src/utils/feedback-helpers.ts +103 -0
  446. package/src/utils/feedback-submission.ts +22 -0
  447. package/src/utils/fetch-usage.ts +76 -0
  448. package/src/utils/fingerprint.ts +245 -0
  449. package/src/utils/format-elapsed-time.ts +33 -0
  450. package/src/utils/format-session-units.ts +6 -0
  451. package/src/utils/format-timeout.ts +28 -0
  452. package/src/utils/format-validation-errors-for-message.ts +84 -0
  453. package/src/utils/freebuff-agent-selection.ts +33 -0
  454. package/src/utils/freebuff-instance-owner.ts +66 -0
  455. package/src/utils/freebuff-model-navigation.ts +50 -0
  456. package/src/utils/freebuff-premium-reset.ts +55 -0
  457. package/src/utils/freebuff-referral-cache.ts +51 -0
  458. package/src/utils/freebuff-session-api.ts +243 -0
  459. package/src/utils/freebuff-session-display.ts +21 -0
  460. package/src/utils/freebuff-streak-line.ts +93 -0
  461. package/src/utils/git.ts +17 -0
  462. package/src/utils/helpers.ts +47 -0
  463. package/src/utils/image-display.ts +67 -0
  464. package/src/utils/image-handler.ts +325 -0
  465. package/src/utils/image-processor.ts +133 -0
  466. package/src/utils/image-thumbnail.ts +78 -0
  467. package/src/utils/implementor-helpers.ts +787 -0
  468. package/src/utils/input-modes.ts +174 -0
  469. package/src/utils/keyboard-actions.ts +392 -0
  470. package/src/utils/keypad-keys.ts +47 -0
  471. package/src/utils/layout-helpers.ts +33 -0
  472. package/src/utils/lazy-response-ads.ts +1 -0
  473. package/src/utils/local-agent-registry.ts +474 -0
  474. package/src/utils/log-shipper.ts +126 -0
  475. package/src/utils/logger.ts +327 -0
  476. package/src/utils/markdown-renderer.tsx +1090 -0
  477. package/src/utils/math.ts +3 -0
  478. package/src/utils/message-block-helpers.ts +684 -0
  479. package/src/utils/message-history.ts +143 -0
  480. package/src/utils/message-tree-utils.ts +67 -0
  481. package/src/utils/message-updater.ts +279 -0
  482. package/src/utils/open-file.ts +146 -0
  483. package/src/utils/open-url.ts +63 -0
  484. package/src/utils/path-completion.ts +111 -0
  485. package/src/utils/path-helpers.ts +38 -0
  486. package/src/utils/pending-attachments.ts +360 -0
  487. package/src/utils/polling-backoff.ts +59 -0
  488. package/src/utils/project-picker.ts +12 -0
  489. package/src/utils/queue-panel-actions.ts +70 -0
  490. package/src/utils/recent-projects.ts +156 -0
  491. package/src/utils/renderer-cleanup.ts +233 -0
  492. package/src/utils/response-ad-positions.ts +1 -0
  493. package/src/utils/run-state-storage.ts +603 -0
  494. package/src/utils/safe-json.ts +153 -0
  495. package/src/utils/sdk-event-handlers.ts +571 -0
  496. package/src/utils/send-message-helpers.ts +210 -0
  497. package/src/utils/send-message-timer.ts +110 -0
  498. package/src/utils/settings.ts +226 -0
  499. package/src/utils/skill-registry.ts +94 -0
  500. package/src/utils/spawn-agent-matcher.ts +55 -0
  501. package/src/utils/status-indicator-state.ts +107 -0
  502. package/src/utils/stream-chunk-processor.ts +57 -0
  503. package/src/utils/strings.ts +271 -0
  504. package/src/utils/subscription.ts +31 -0
  505. package/src/utils/syntax-highlighter.tsx +19 -0
  506. package/src/utils/terminal-color-detection.ts +474 -0
  507. package/src/utils/terminal-command-broker.ts +467 -0
  508. package/src/utils/terminal-enter-detection.ts +63 -0
  509. package/src/utils/terminal-images.ts +229 -0
  510. package/src/utils/terminal-io.ts +85 -0
  511. package/src/utils/terminal-protocol-controller.ts +173 -0
  512. package/src/utils/terminal-reset-sequences.ts +33 -0
  513. package/src/utils/terminal-title.ts +84 -0
  514. package/src/utils/terminal-watchdog.ts +366 -0
  515. package/src/utils/text-layout.ts +149 -0
  516. package/src/utils/theme-config.ts +141 -0
  517. package/src/utils/theme-system.ts +1206 -0
  518. package/src/utils/think-tag-parser.ts +95 -0
  519. package/src/utils/time-format.ts +21 -0
  520. package/src/utils/trace-writer.ts +162 -0
  521. package/src/utils/ui-constants.ts +69 -0
  522. package/src/utils/update-checker.ts +110 -0
  523. package/src/utils/usage-banner-state.ts +146 -0
  524. package/src/utils/validation-error-formatting.ts +86 -0
  525. package/src/utils/validation-error-helpers.ts +19 -0
  526. package/src/utils/windows-terminal-health.ts +124 -0
  527. package/src/utils/word-wrap-utils.ts +53 -0
  528. package/src/utils/write-file-atomic.ts +54 -0
  529. package/src/utils/yield-to-event-loop.ts +9 -0
@@ -0,0 +1,213 @@
1
+
2
+ import { shouldCollapseByDefault } from './constants'
3
+ import {
4
+ isImplementorAgent,
5
+ groupConsecutiveImplementors,
6
+ groupConsecutiveNonImplementorAgents,
7
+ groupConsecutiveToolBlocks,
8
+ } from './implementor-helpers'
9
+ import { isImageBlock } from '../types/chat'
10
+
11
+ import type {
12
+ ContentBlock,
13
+ AgentContentBlock,
14
+ ToolContentBlock,
15
+ TextContentBlock,
16
+ ImageContentBlock,
17
+ } from '../types/chat'
18
+ import type { ReactNode } from 'react'
19
+
20
+ /**
21
+ * Type guard for reasoning text blocks (thinking blocks)
22
+ */
23
+ export function isReasoningTextBlock(
24
+ block: ContentBlock,
25
+ ): block is Extract<ContentBlock, { type: 'text' }> {
26
+ return block.type === 'text' && block.textType === 'reasoning'
27
+ }
28
+
29
+ /**
30
+ * Handler callbacks for processing different block types.
31
+ * Each handler receives the block(s) and relevant indices, and returns a ReactNode.
32
+ */
33
+ export interface BlockProcessorHandlers {
34
+ /** Handle a group of consecutive reasoning text blocks */
35
+ onReasoningGroup: (
36
+ blocks: TextContentBlock[],
37
+ startIndex: number,
38
+ ) => ReactNode
39
+
40
+ /** Handle an image block (optional - if not provided, images are skipped) */
41
+ onImageBlock?: (block: ImageContentBlock, index: number) => ReactNode
42
+
43
+ /** Handle a group of consecutive tool blocks */
44
+ onToolGroup: (
45
+ blocks: ToolContentBlock[],
46
+ startIndex: number,
47
+ nextIndex: number,
48
+ ) => ReactNode
49
+
50
+ /** Handle a group of consecutive implementor agent blocks */
51
+ onImplementorGroup: (
52
+ blocks: AgentContentBlock[],
53
+ startIndex: number,
54
+ nextIndex: number,
55
+ ) => ReactNode
56
+
57
+ /** Handle a group of consecutive non-implementor agent blocks */
58
+ onAgentGroup: (
59
+ blocks: AgentContentBlock[],
60
+ startIndex: number,
61
+ nextIndex: number,
62
+ ) => ReactNode
63
+
64
+ /** Handle a single block that doesn't fit into any group category */
65
+ onSingleBlock: (block: ContentBlock, index: number) => ReactNode
66
+ }
67
+
68
+ /**
69
+ * Split an array of items into sub-groups based on agent size.
70
+ * Consecutive "small" agents (collapsed by default) are grouped together
71
+ * so they can share a grid row. Each "large" agent gets its own sub-group
72
+ * so it renders at full width.
73
+ */
74
+ export function splitByAgentSize<T>(
75
+ items: T[],
76
+ getAgentType: (item: T) => string,
77
+ ): T[][] {
78
+ if (items.length <= 1) return [items]
79
+
80
+ const subGroups: T[][] = []
81
+ let currentSmallGroup: T[] = []
82
+
83
+ for (const item of items) {
84
+ if (shouldCollapseByDefault(getAgentType(item))) {
85
+ currentSmallGroup.push(item)
86
+ } else {
87
+ if (currentSmallGroup.length > 0) {
88
+ subGroups.push(currentSmallGroup)
89
+ currentSmallGroup = []
90
+ }
91
+ subGroups.push([item])
92
+ }
93
+ }
94
+
95
+ if (currentSmallGroup.length > 0) {
96
+ subGroups.push(currentSmallGroup)
97
+ }
98
+
99
+ return subGroups
100
+ }
101
+
102
+ /** Convenience wrapper for splitting AgentContentBlock arrays by size. */
103
+ export function splitAgentsBySize(
104
+ agents: AgentContentBlock[],
105
+ ): AgentContentBlock[][] {
106
+ return splitByAgentSize(agents, (a) => a.agentType)
107
+ }
108
+
109
+ /**
110
+ * Process a list of content blocks, grouping consecutive blocks of the same type
111
+ * and calling the appropriate handler for each group or single block.
112
+ *
113
+ * This utility abstracts the common iteration pattern used by BlocksRenderer and AgentBody.
114
+ *
115
+ * @param blocks - The array of content blocks to process
116
+ * @param handlers - Callback handlers for each block type
117
+ * @returns An array of ReactNode elements
118
+ */
119
+ export function processBlocks(
120
+ blocks: ContentBlock[],
121
+ handlers: BlockProcessorHandlers,
122
+ ): ReactNode[] {
123
+ const nodes: ReactNode[] = []
124
+
125
+ for (let i = 0; i < blocks.length; ) {
126
+ const block = blocks[i]
127
+
128
+ // Handle reasoning text blocks (thinking)
129
+ if (isReasoningTextBlock(block)) {
130
+ const start = i
131
+ const reasoningBlocks: TextContentBlock[] = []
132
+ while (i < blocks.length) {
133
+ const currentBlock = blocks[i]
134
+ if (!isReasoningTextBlock(currentBlock)) break
135
+ reasoningBlocks.push(currentBlock)
136
+ i++
137
+ }
138
+
139
+ const node = handlers.onReasoningGroup(reasoningBlocks, start)
140
+ if (node !== null) {
141
+ nodes.push(node)
142
+ }
143
+ continue
144
+ }
145
+
146
+ // Handle image blocks
147
+ if (isImageBlock(block)) {
148
+ if (handlers.onImageBlock) {
149
+ const node = handlers.onImageBlock(block, i)
150
+ if (node !== null) {
151
+ nodes.push(node)
152
+ }
153
+ }
154
+ i++
155
+ continue
156
+ }
157
+
158
+ // Handle tool blocks
159
+ if (block.type === 'tool') {
160
+ const start = i
161
+ const { group: toolBlocks, nextIndex } = groupConsecutiveToolBlocks(
162
+ blocks,
163
+ i,
164
+ )
165
+ i = nextIndex
166
+
167
+ const node = handlers.onToolGroup(toolBlocks, start, nextIndex)
168
+ if (node !== null) {
169
+ nodes.push(node)
170
+ }
171
+ continue
172
+ }
173
+
174
+ // Handle agent blocks
175
+ if (block.type === 'agent') {
176
+ if (isImplementorAgent(block)) {
177
+ // Implementor agents
178
+ const start = i
179
+ const { group: implementors, nextIndex } = groupConsecutiveImplementors(
180
+ blocks,
181
+ i,
182
+ )
183
+ i = nextIndex
184
+
185
+ const node = handlers.onImplementorGroup(implementors, start, nextIndex)
186
+ if (node !== null) {
187
+ nodes.push(node)
188
+ }
189
+ } else {
190
+ // Non-implementor agents
191
+ const start = i
192
+ const { group: agentBlocks, nextIndex } =
193
+ groupConsecutiveNonImplementorAgents(blocks, i)
194
+ i = nextIndex
195
+
196
+ const node = handlers.onAgentGroup(agentBlocks, start, nextIndex)
197
+ if (node !== null) {
198
+ nodes.push(node)
199
+ }
200
+ }
201
+ continue
202
+ }
203
+
204
+ // Handle all other block types (text, html, etc.)
205
+ const node = handlers.onSingleBlock(block, i)
206
+ if (node !== null) {
207
+ nodes.push(node)
208
+ }
209
+ i++
210
+ }
211
+
212
+ return nodes
213
+ }
@@ -0,0 +1,253 @@
1
+ import * as fs from 'fs'
2
+ import path from 'path'
3
+
4
+ import {
5
+ CHAT_MESSAGES_FILENAME,
6
+ getFirstUserPrompt,
7
+ readChatMeta,
8
+ } from './chat-meta'
9
+ import { CHAT_LOG_FILENAME, logger } from './logger'
10
+ import { getProjectDataDir } from '../project-files'
11
+
12
+ import type { ChatMessage } from '../types/chat'
13
+
14
+ export interface ChatHistoryEntry {
15
+ chatId: string
16
+ lastPrompt: string
17
+ timestamp: Date
18
+ messageCount: number
19
+ /** True when chat-messages.json exists but can't be parsed (e.g. truncated
20
+ * by a crash mid-write). Shown in /history so the chat doesn't silently
21
+ * vanish; can be deleted but not resumed. */
22
+ unreadable?: boolean
23
+ }
24
+
25
+ function getChatsDir(dataDir: string = getProjectDataDir()): string {
26
+ return path.join(dataDir, 'chats')
27
+ }
28
+
29
+ interface ChatDirInfo {
30
+ chatId: string
31
+ chatPath: string
32
+ messagesPath: string
33
+ mtime: Date
34
+ }
35
+
36
+ /**
37
+ * List all available chats sorted by most recent first
38
+ * @param maxChats - Maximum number of chats to load (default: 500)
39
+ */
40
+ export function getAllChats(
41
+ maxChats: number = 500,
42
+ dataDir?: string,
43
+ ): ChatHistoryEntry[] {
44
+ try {
45
+ const chatsDir = getChatsDir(dataDir)
46
+
47
+ if (!fs.existsSync(chatsDir)) {
48
+ return []
49
+ }
50
+
51
+ const chatDirs = fs.readdirSync(chatsDir)
52
+
53
+ // First pass: get mtime for all chat directories (fast, no file reading)
54
+ const chatDirInfos: ChatDirInfo[] = []
55
+ for (const chatId of chatDirs) {
56
+ const chatPath = path.join(chatsDir, chatId)
57
+ try {
58
+ const stat = fs.statSync(chatPath)
59
+ if (!stat.isDirectory()) continue
60
+
61
+ chatDirInfos.push({
62
+ chatId,
63
+ chatPath,
64
+ messagesPath: path.join(chatPath, CHAT_MESSAGES_FILENAME),
65
+ mtime: stat.mtime,
66
+ })
67
+ } catch {
68
+ // Skip directories we can't stat
69
+ }
70
+ }
71
+
72
+ // Sort by mtime first (most recent first)
73
+ chatDirInfos.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
74
+
75
+ // Second pass: only read message content for the top N chats
76
+ const chats: ChatHistoryEntry[] = []
77
+ const chatsToLoad = chatDirInfos.slice(0, maxChats)
78
+
79
+ for (const info of chatsToLoad) {
80
+ try {
81
+ let messageCount = 0
82
+ let lastPrompt = '(empty chat)'
83
+
84
+ if (fs.existsSync(info.messagesPath)) {
85
+ // Prefer the sidecar summary: transcripts are unbounded, so parsing
86
+ // every full chat-messages.json here can make /history slow.
87
+ const meta = readChatMeta(info.chatPath)
88
+ if (meta) {
89
+ messageCount = meta.messageCount
90
+ lastPrompt = meta.firstPrompt
91
+ } else {
92
+ // Pre-sidecar chats, or a sidecar that no longer matches the
93
+ // messages file (rewritten by an older CLI, crash between the
94
+ // two writes): parse the full file.
95
+ const content = fs.readFileSync(info.messagesPath, 'utf8')
96
+ const messages = JSON.parse(content) as ChatMessage[]
97
+ if (!Array.isArray(messages)) {
98
+ throw new Error('chat-messages.json is not an array')
99
+ }
100
+ messageCount = messages.length
101
+ lastPrompt = getFirstUserPrompt(messages)
102
+ }
103
+ }
104
+
105
+ // Skip empty chats (no messages)
106
+ if (messageCount > 0) {
107
+ chats.push({
108
+ chatId: info.chatId,
109
+ lastPrompt,
110
+ timestamp: info.mtime,
111
+ messageCount,
112
+ })
113
+ }
114
+ } catch (error) {
115
+ logger.debug(
116
+ {
117
+ chatId: info.chatId,
118
+ error: error instanceof Error ? error.message : String(error),
119
+ },
120
+ 'Failed to read chat messages',
121
+ )
122
+ // Don't silently hide the chat: list it as unreadable so the user
123
+ // knows it exists (and can delete it) instead of thinking it was lost
124
+ chats.push({
125
+ chatId: info.chatId,
126
+ lastPrompt: '(unreadable chat)',
127
+ timestamp: info.mtime,
128
+ messageCount: 0,
129
+ unreadable: true,
130
+ })
131
+ }
132
+ }
133
+
134
+ return chats
135
+ } catch (error) {
136
+ logger.error(
137
+ { error: error instanceof Error ? error.message : String(error) },
138
+ 'Failed to list chats',
139
+ )
140
+ return []
141
+ }
142
+ }
143
+
144
+ // Older CLI versions logged the full conversation (including attachments) to
145
+ // log.jsonl on every step, leaving multi-GB files in chat directories. Delete
146
+ // any log file over this cap; with summary-only logging, healthy logs stay
147
+ // far below it.
148
+ const MAX_LOG_FILE_BYTES = 10 * 1024 * 1024
149
+ // Only delete logs from chats untouched for this long, so debug logs for
150
+ // recent chats stay available.
151
+ const MIN_LOG_AGE_MS = 14 * 24 * 60 * 60 * 1000
152
+
153
+ /**
154
+ * Delete oversized log.jsonl files from chat directories that haven't been
155
+ * touched in 14+ days. Only debug logs are removed — chat history files are
156
+ * untouched.
157
+ */
158
+ export function trimOversizedChatLogs(dataDir?: string): void {
159
+ let chatsDir: string
160
+ let chatIds: string[]
161
+ try {
162
+ chatsDir = getChatsDir(dataDir)
163
+ chatIds = fs.readdirSync(chatsDir)
164
+ } catch {
165
+ return // No project root set or no chats directory yet
166
+ }
167
+
168
+ const deleteBefore = Date.now() - MIN_LOG_AGE_MS
169
+ for (const chatId of chatIds) {
170
+ const logFile = path.join(chatsDir, chatId, CHAT_LOG_FILENAME)
171
+ try {
172
+ const stats = fs.statSync(logFile, { throwIfNoEntry: false })
173
+ if (
174
+ stats &&
175
+ stats.size > MAX_LOG_FILE_BYTES &&
176
+ stats.mtimeMs < deleteBefore
177
+ ) {
178
+ fs.unlinkSync(logFile)
179
+ }
180
+ } catch {
181
+ // Ignore errors for individual files
182
+ }
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Delete a saved chat session from local history.
188
+ */
189
+ export function deleteChatSession(chatId: string, dataDir?: string): boolean {
190
+ try {
191
+ const safeChatId = chatId.trim()
192
+ if (
193
+ !safeChatId ||
194
+ safeChatId === '.' ||
195
+ safeChatId === '..' ||
196
+ path.basename(safeChatId) !== safeChatId
197
+ ) {
198
+ logger.warn({ chatId }, 'Refusing to delete invalid chat id')
199
+ return false
200
+ }
201
+
202
+ const chatsDir = getChatsDir(dataDir)
203
+ const chatPath = path.join(chatsDir, safeChatId)
204
+
205
+ if (!fs.existsSync(chatPath)) {
206
+ return false
207
+ }
208
+
209
+ const stat = fs.statSync(chatPath)
210
+ if (!stat.isDirectory()) {
211
+ logger.warn(
212
+ { chatId, chatPath },
213
+ 'Refusing to delete non-directory chat path',
214
+ )
215
+ return false
216
+ }
217
+
218
+ fs.rmSync(chatPath, { recursive: true, force: false })
219
+ return true
220
+ } catch (error) {
221
+ logger.error(
222
+ { chatId, error: error instanceof Error ? error.message : String(error) },
223
+ 'Failed to delete chat session',
224
+ )
225
+ return false
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Format a timestamp relative to now (e.g., "2 hours ago", "yesterday")
231
+ */
232
+ export function formatRelativeTime(date: Date): string {
233
+ const now = new Date()
234
+ const diffMs = now.getTime() - date.getTime()
235
+ const diffSecs = Math.floor(diffMs / 1000)
236
+ const diffMins = Math.floor(diffSecs / 60)
237
+ const diffHours = Math.floor(diffMins / 60)
238
+ const diffDays = Math.floor(diffHours / 24)
239
+
240
+ if (diffSecs < 60) {
241
+ return 'just now'
242
+ } else if (diffMins < 60) {
243
+ return `${diffMins}m ago`
244
+ } else if (diffHours < 24) {
245
+ return `${diffHours}h ago`
246
+ } else if (diffDays === 1) {
247
+ return 'yesterday'
248
+ } else if (diffDays < 7) {
249
+ return `${diffDays}d ago`
250
+ } else {
251
+ return date.toLocaleDateString()
252
+ }
253
+ }
@@ -0,0 +1,52 @@
1
+ import { isPlainEnterKey } from './terminal-enter-detection'
2
+
3
+ type ChatInputKey = {
4
+ name?: string
5
+ sequence?: string
6
+ shift?: boolean
7
+ ctrl?: boolean
8
+ meta?: boolean
9
+ option?: boolean
10
+ }
11
+
12
+ type ChatInputKeyInterceptState = {
13
+ hasSlashSuggestions: boolean
14
+ hasMentionSuggestions: boolean
15
+ lastEditDueToNav: boolean
16
+ cursorPosition: number
17
+ inputLength: number
18
+ }
19
+
20
+ export function shouldInterceptChatInputKey(
21
+ key: ChatInputKey,
22
+ state: ChatInputKeyInterceptState,
23
+ ): boolean {
24
+ const isPlainEnter = isPlainEnterKey(key)
25
+ const isTab = key.name === 'tab' && !key.ctrl && !key.meta && !key.option
26
+ const isUp = key.name === 'up' && !key.ctrl && !key.meta && !key.option
27
+ const isDown = key.name === 'down' && !key.ctrl && !key.meta && !key.option
28
+ const isUpDown = isUp || isDown
29
+
30
+ const hasSuggestions =
31
+ state.hasSlashSuggestions || state.hasMentionSuggestions
32
+ if (hasSuggestions) {
33
+ if (isUpDown && state.lastEditDueToNav) {
34
+ return true
35
+ }
36
+ if (isPlainEnter || isTab || isUpDown) {
37
+ return true
38
+ }
39
+ }
40
+
41
+ const historyUpEnabled = state.lastEditDueToNav || state.cursorPosition === 0
42
+ const historyDownEnabled =
43
+ state.lastEditDueToNav || state.cursorPosition === state.inputLength
44
+ if (isUp && historyUpEnabled) {
45
+ return true
46
+ }
47
+ if (isDown && historyDownEnabled) {
48
+ return true
49
+ }
50
+
51
+ return false
52
+ }
@@ -0,0 +1,90 @@
1
+ import * as fs from 'fs'
2
+ import path from 'path'
3
+
4
+ import { z } from 'zod'
5
+
6
+ import { writeFileAtomic } from './write-file-atomic'
7
+
8
+ import type { ChatMessage } from '../types/chat'
9
+
10
+ export const CHAT_MESSAGES_FILENAME = 'chat-messages.json'
11
+ export const CHAT_META_FILENAME = 'chat-meta.json'
12
+
13
+ /**
14
+ * Small sidecar summary of a chat written alongside chat-messages.json.
15
+ * Transcripts are unbounded and can grow to many MB, so /history reads this
16
+ * instead of parsing every full chat-messages.json.
17
+ *
18
+ * messagesSize/messagesMtimeMs bind the sidecar to the exact messages file it
19
+ * summarizes: if the transcript is later rewritten by anything that doesn't
20
+ * refresh the sidecar (an older CLI version, a crash between the two writes),
21
+ * readChatMeta rejects the stale sidecar and callers fall back to the full
22
+ * parse instead of showing outdated data or hiding corruption.
23
+ */
24
+ const chatMetaSchema = z.object({
25
+ messageCount: z.number(),
26
+ firstPrompt: z.string(),
27
+ messagesSize: z.number(),
28
+ messagesMtimeMs: z.number(),
29
+ })
30
+
31
+ export type ChatMeta = z.infer<typeof chatMetaSchema>
32
+
33
+ /**
34
+ * Get the first user message from a list of chat messages
35
+ */
36
+ export function getFirstUserPrompt(messages: ChatMessage[]): string {
37
+ for (const msg of messages) {
38
+ if (msg?.variant === 'user' && msg.content) {
39
+ // Truncate long prompts
40
+ const content = msg.content.trim()
41
+ if (content.length > 100) {
42
+ return content.slice(0, 97) + '...'
43
+ }
44
+ return content
45
+ }
46
+ }
47
+ return '(empty chat)'
48
+ }
49
+
50
+ /**
51
+ * Write the sidecar for the given chat directory. The chat-messages.json in
52
+ * that directory must already contain exactly `messages` (its stats are
53
+ * recorded to bind the sidecar to it).
54
+ */
55
+ export function writeChatMeta(chatDir: string, messages: ChatMessage[]): void {
56
+ const stats = fs.statSync(path.join(chatDir, CHAT_MESSAGES_FILENAME))
57
+ const meta: ChatMeta = {
58
+ messageCount: messages.length,
59
+ firstPrompt: getFirstUserPrompt(messages),
60
+ messagesSize: stats.size,
61
+ messagesMtimeMs: stats.mtimeMs,
62
+ }
63
+ writeFileAtomic(path.join(chatDir, CHAT_META_FILENAME), JSON.stringify(meta))
64
+ }
65
+
66
+ /**
67
+ * Read the sidecar for a chat directory. Returns null when it is missing,
68
+ * unparsable, or stale (chat-messages.json no longer matches the recorded
69
+ * size/mtime) — callers should fall back to parsing chat-messages.json.
70
+ */
71
+ export function readChatMeta(chatDir: string): ChatMeta | null {
72
+ try {
73
+ const raw = fs.readFileSync(path.join(chatDir, CHAT_META_FILENAME), 'utf8')
74
+ const parsed = chatMetaSchema.safeParse(JSON.parse(raw))
75
+ if (!parsed.success) {
76
+ return null
77
+ }
78
+ const meta = parsed.data
79
+ const stats = fs.statSync(path.join(chatDir, CHAT_MESSAGES_FILENAME))
80
+ if (
81
+ stats.size !== meta.messagesSize ||
82
+ stats.mtimeMs !== meta.messagesMtimeMs
83
+ ) {
84
+ return null
85
+ }
86
+ return meta
87
+ } catch {
88
+ return null
89
+ }
90
+ }