nastechai-desktop 18.1.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 (546) hide show
  1. package/.prettierrc +11 -0
  2. package/DESIGN.md +167 -0
  3. package/README.md +141 -0
  4. package/assets/icon.icns +0 -0
  5. package/assets/icon.ico +0 -0
  6. package/assets/icon.png +0 -0
  7. package/components.json +21 -0
  8. package/electron/backend-env.cjs +112 -0
  9. package/electron/backend-env.test.cjs +111 -0
  10. package/electron/backend-probes.cjs +106 -0
  11. package/electron/backend-probes.test.cjs +82 -0
  12. package/electron/backend-ready.cjs +66 -0
  13. package/electron/bootstrap-platform.cjs +91 -0
  14. package/electron/bootstrap-platform.test.cjs +111 -0
  15. package/electron/bootstrap-runner.cjs +720 -0
  16. package/electron/bootstrap-runner.test.cjs +138 -0
  17. package/electron/connection-config.cjs +254 -0
  18. package/electron/connection-config.test.cjs +329 -0
  19. package/electron/dashboard-token.cjs +99 -0
  20. package/electron/dashboard-token.test.cjs +142 -0
  21. package/electron/desktop-uninstall.cjs +232 -0
  22. package/electron/desktop-uninstall.test.cjs +246 -0
  23. package/electron/entitlements.mac.inherit.plist +14 -0
  24. package/electron/entitlements.mac.plist +14 -0
  25. package/electron/fs-read-dir.cjs +109 -0
  26. package/electron/fs-read-dir.test.cjs +364 -0
  27. package/electron/gateway-ws-probe.cjs +188 -0
  28. package/electron/gateway-ws-probe.test.cjs +122 -0
  29. package/electron/git-root.cjs +54 -0
  30. package/electron/git-root.test.cjs +40 -0
  31. package/electron/git-worktrees.cjs +174 -0
  32. package/electron/hardening.cjs +184 -0
  33. package/electron/hardening.test.cjs +116 -0
  34. package/electron/main.cjs +5762 -0
  35. package/electron/oauth-net-request.cjs +20 -0
  36. package/electron/oauth-net-request.test.cjs +34 -0
  37. package/electron/preload.cjs +135 -0
  38. package/electron/session-windows.cjs +99 -0
  39. package/electron/session-windows.test.cjs +177 -0
  40. package/electron/update-remote.cjs +56 -0
  41. package/electron/update-remote.test.cjs +78 -0
  42. package/electron/vscode-marketplace.cjs +331 -0
  43. package/electron/vscode-marketplace.test.cjs +113 -0
  44. package/electron/windows-child-process.test.cjs +57 -0
  45. package/electron/windows-user-env.cjs +76 -0
  46. package/electron/windows-user-env.test.cjs +90 -0
  47. package/electron/workspace-cwd.cjs +38 -0
  48. package/electron/workspace-cwd.test.cjs +45 -0
  49. package/eslint.config.mjs +122 -0
  50. package/index.html +17 -0
  51. package/package.json +254 -0
  52. package/pr-assets/session-source-folders.png +0 -0
  53. package/preview-demo.html +65 -0
  54. package/public/apple-touch-icon.png +0 -0
  55. package/public/ds-assets/filler-bg0.jpg +0 -0
  56. package/public/nastech-frames/nastech-frame-0.png +0 -0
  57. package/public/nastech-frames/nastech-frame-1.png +0 -0
  58. package/public/nastech-frames/nastech-frame-2.png +0 -0
  59. package/public/nastech-frames/nastech-frame-3.png +0 -0
  60. package/public/nastech-frames/nastech-frame-4.png +0 -0
  61. package/public/nastech-frames/nastech-frame-5.png +0 -0
  62. package/public/nastech-frames/nastech-frame-6.png +0 -0
  63. package/public/nastech-frames/nastech-frame-7.png +0 -0
  64. package/public/nastech-girl.jpg +0 -0
  65. package/public/nastech-sprite.png +0 -0
  66. package/public/nastech.png +0 -0
  67. package/scripts/after-pack.cjs +41 -0
  68. package/scripts/assert-dist-built.cjs +70 -0
  69. package/scripts/assert-dist-built.test.cjs +84 -0
  70. package/scripts/assert-root-install.cjs +13 -0
  71. package/scripts/before-build.cjs +11 -0
  72. package/scripts/before-pack.cjs +78 -0
  73. package/scripts/before-pack.test.cjs +53 -0
  74. package/scripts/click-session.mjs +51 -0
  75. package/scripts/dev-no-hmr.mjs +22 -0
  76. package/scripts/diag-jump.mjs +115 -0
  77. package/scripts/diag-scroll-reset.mjs +229 -0
  78. package/scripts/eval.mjs +21 -0
  79. package/scripts/leak-typing.mjs +222 -0
  80. package/scripts/measure-jump.mjs +108 -0
  81. package/scripts/measure-latency.mjs +184 -0
  82. package/scripts/measure-real-stream.mjs +252 -0
  83. package/scripts/measure-submit.mjs +179 -0
  84. package/scripts/measure-synthetic-stream.mjs +322 -0
  85. package/scripts/notarize-artifact.cjs +77 -0
  86. package/scripts/notarize.cjs +100 -0
  87. package/scripts/patch-electron-builder-mac-binary.cjs +59 -0
  88. package/scripts/probe-renderer.mjs +38 -0
  89. package/scripts/probe-thread.mjs +40 -0
  90. package/scripts/profile-long-stream.mjs +191 -0
  91. package/scripts/profile-real-stream.mjs +137 -0
  92. package/scripts/profile-synth-stream.mjs +103 -0
  93. package/scripts/profile-typing-lag.md +381 -0
  94. package/scripts/profile-typing.mjs +260 -0
  95. package/scripts/reload-renderer.mjs +25 -0
  96. package/scripts/reload.mjs +36 -0
  97. package/scripts/set-exe-identity.cjs +94 -0
  98. package/scripts/stage-native-deps.cjs +159 -0
  99. package/scripts/test-desktop.mjs +425 -0
  100. package/scripts/write-build-stamp.cjs +126 -0
  101. package/src/app/agents/index.tsx +398 -0
  102. package/src/app/artifacts/index.test.ts +62 -0
  103. package/src/app/artifacts/index.tsx +906 -0
  104. package/src/app/chat/chat-drop-overlay.tsx +48 -0
  105. package/src/app/chat/chat-swap-overlay.tsx +47 -0
  106. package/src/app/chat/composer/attachments.tsx +114 -0
  107. package/src/app/chat/composer/completion-drawer.tsx +63 -0
  108. package/src/app/chat/composer/context-menu.tsx +172 -0
  109. package/src/app/chat/composer/controls.tsx +289 -0
  110. package/src/app/chat/composer/drop-affordance.ts +2 -0
  111. package/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
  112. package/src/app/chat/composer/focus.ts +134 -0
  113. package/src/app/chat/composer/help-hint.tsx +59 -0
  114. package/src/app/chat/composer/hooks/use-at-completions.ts +141 -0
  115. package/src/app/chat/composer/hooks/use-live-completion-adapter.ts +119 -0
  116. package/src/app/chat/composer/hooks/use-mic-recorder.ts +291 -0
  117. package/src/app/chat/composer/hooks/use-slash-completions.ts +114 -0
  118. package/src/app/chat/composer/hooks/use-voice-conversation.ts +390 -0
  119. package/src/app/chat/composer/hooks/use-voice-recorder.ts +116 -0
  120. package/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
  121. package/src/app/chat/composer/index.tsx +1611 -0
  122. package/src/app/chat/composer/inline-refs.ts +138 -0
  123. package/src/app/chat/composer/model-pill.tsx +86 -0
  124. package/src/app/chat/composer/queue-panel.tsx +130 -0
  125. package/src/app/chat/composer/rich-editor.test.ts +18 -0
  126. package/src/app/chat/composer/rich-editor.ts +165 -0
  127. package/src/app/chat/composer/skin-slash-popover.tsx +61 -0
  128. package/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
  129. package/src/app/chat/composer/status-stack/index.tsx +202 -0
  130. package/src/app/chat/composer/status-stack/status-row.tsx +155 -0
  131. package/src/app/chat/composer/text-utils.test.ts +77 -0
  132. package/src/app/chat/composer/text-utils.ts +107 -0
  133. package/src/app/chat/composer/trigger-popover.test.tsx +42 -0
  134. package/src/app/chat/composer/trigger-popover.tsx +116 -0
  135. package/src/app/chat/composer/types.ts +64 -0
  136. package/src/app/chat/composer/url-dialog.tsx +82 -0
  137. package/src/app/chat/composer/voice-activity.tsx +252 -0
  138. package/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
  139. package/src/app/chat/hooks/use-composer-actions.ts +525 -0
  140. package/src/app/chat/hooks/use-file-drop-zone.ts +118 -0
  141. package/src/app/chat/index.tsx +390 -0
  142. package/src/app/chat/perf-probe.tsx +269 -0
  143. package/src/app/chat/right-rail/index.ts +1 -0
  144. package/src/app/chat/right-rail/preview-console-state.ts +82 -0
  145. package/src/app/chat/right-rail/preview-console.tsx +290 -0
  146. package/src/app/chat/right-rail/preview-file.tsx +559 -0
  147. package/src/app/chat/right-rail/preview-pane.test.tsx +43 -0
  148. package/src/app/chat/right-rail/preview-pane.tsx +657 -0
  149. package/src/app/chat/right-rail/preview.tsx +171 -0
  150. package/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
  151. package/src/app/chat/scroll-to-bottom-button.tsx +74 -0
  152. package/src/app/chat/sidebar/cron-jobs-section.tsx +325 -0
  153. package/src/app/chat/sidebar/index.tsx +1219 -0
  154. package/src/app/chat/sidebar/load-more-row.tsx +30 -0
  155. package/src/app/chat/sidebar/order.test.ts +21 -0
  156. package/src/app/chat/sidebar/order.ts +17 -0
  157. package/src/app/chat/sidebar/profile-switcher.tsx +516 -0
  158. package/src/app/chat/sidebar/session-actions-menu.tsx +264 -0
  159. package/src/app/chat/sidebar/session-row.tsx +257 -0
  160. package/src/app/chat/sidebar/virtual-session-list.tsx +154 -0
  161. package/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
  162. package/src/app/chat/sidebar/workspace-groups.ts +326 -0
  163. package/src/app/chat/thread-loading.test.ts +34 -0
  164. package/src/app/chat/thread-loading.ts +26 -0
  165. package/src/app/command-center/index.tsx +654 -0
  166. package/src/app/command-palette/index.tsx +513 -0
  167. package/src/app/command-palette/marketplace-theme-page.tsx +157 -0
  168. package/src/app/cron/index.tsx +942 -0
  169. package/src/app/cron/job-state.ts +29 -0
  170. package/src/app/desktop-controller.tsx +938 -0
  171. package/src/app/floating-hud.ts +22 -0
  172. package/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
  173. package/src/app/gateway/hooks/use-gateway-boot.ts +387 -0
  174. package/src/app/gateway/hooks/use-gateway-request.ts +138 -0
  175. package/src/app/hooks/use-keybinds.ts +186 -0
  176. package/src/app/hooks/use-refresh-hotkey.ts +45 -0
  177. package/src/app/hooks/use-route-enum-param.ts +38 -0
  178. package/src/app/index.tsx +1 -0
  179. package/src/app/layout-constants.ts +13 -0
  180. package/src/app/messaging/index.tsx +648 -0
  181. package/src/app/messaging/platform-icon.tsx +93 -0
  182. package/src/app/model-picker-overlay.tsx +42 -0
  183. package/src/app/model-visibility-overlay.tsx +31 -0
  184. package/src/app/overlays/overlay-chrome.tsx +66 -0
  185. package/src/app/overlays/overlay-search-input.tsx +33 -0
  186. package/src/app/overlays/overlay-split-layout.tsx +130 -0
  187. package/src/app/overlays/overlay-view.tsx +91 -0
  188. package/src/app/page-search-shell.tsx +75 -0
  189. package/src/app/profiles/create-profile-dialog.tsx +154 -0
  190. package/src/app/profiles/delete-profile-dialog.tsx +65 -0
  191. package/src/app/profiles/index.tsx +671 -0
  192. package/src/app/profiles/rename-profile-dialog.tsx +125 -0
  193. package/src/app/right-sidebar/files/dnd-manager.ts +27 -0
  194. package/src/app/right-sidebar/files/ipc.test.ts +100 -0
  195. package/src/app/right-sidebar/files/ipc.ts +161 -0
  196. package/src/app/right-sidebar/files/remote-picker.tsx +177 -0
  197. package/src/app/right-sidebar/files/tree.tsx +224 -0
  198. package/src/app/right-sidebar/files/use-project-tree.test.ts +190 -0
  199. package/src/app/right-sidebar/files/use-project-tree.ts +268 -0
  200. package/src/app/right-sidebar/index.test.tsx +75 -0
  201. package/src/app/right-sidebar/index.tsx +395 -0
  202. package/src/app/right-sidebar/store.ts +15 -0
  203. package/src/app/right-sidebar/terminal/buffer.ts +65 -0
  204. package/src/app/right-sidebar/terminal/index.tsx +98 -0
  205. package/src/app/right-sidebar/terminal/persistent.tsx +122 -0
  206. package/src/app/right-sidebar/terminal/selection.ts +75 -0
  207. package/src/app/right-sidebar/terminal/use-terminal-session.ts +504 -0
  208. package/src/app/routes.ts +88 -0
  209. package/src/app/session/hooks/use-context-suggestions.ts +58 -0
  210. package/src/app/session/hooks/use-cwd-actions.ts +109 -0
  211. package/src/app/session/hooks/use-message-stream.ts +957 -0
  212. package/src/app/session/hooks/use-model-controls.test.tsx +198 -0
  213. package/src/app/session/hooks/use-model-controls.ts +106 -0
  214. package/src/app/session/hooks/use-nastech-config.ts +74 -0
  215. package/src/app/session/hooks/use-preview-routing.test.tsx +168 -0
  216. package/src/app/session/hooks/use-preview-routing.ts +223 -0
  217. package/src/app/session/hooks/use-prompt-actions.test.tsx +316 -0
  218. package/src/app/session/hooks/use-prompt-actions.ts +1030 -0
  219. package/src/app/session/hooks/use-route-resume.test.tsx +136 -0
  220. package/src/app/session/hooks/use-route-resume.ts +115 -0
  221. package/src/app/session/hooks/use-session-actions.test.tsx +119 -0
  222. package/src/app/session/hooks/use-session-actions.ts +885 -0
  223. package/src/app/session/hooks/use-session-state-cache.test.tsx +118 -0
  224. package/src/app/session/hooks/use-session-state-cache.ts +191 -0
  225. package/src/app/session-picker-overlay.tsx +32 -0
  226. package/src/app/session-switcher.tsx +107 -0
  227. package/src/app/settings/about-settings.tsx +173 -0
  228. package/src/app/settings/appearance-settings.tsx +162 -0
  229. package/src/app/settings/config-settings.tsx +384 -0
  230. package/src/app/settings/constants.ts +545 -0
  231. package/src/app/settings/credential-key-ui.tsx +373 -0
  232. package/src/app/settings/env-credentials.tsx +198 -0
  233. package/src/app/settings/env-var-actions-menu.tsx +136 -0
  234. package/src/app/settings/field-copy.ts +56 -0
  235. package/src/app/settings/gateway-settings.tsx +620 -0
  236. package/src/app/settings/helpers.test.ts +138 -0
  237. package/src/app/settings/helpers.ts +151 -0
  238. package/src/app/settings/index.tsx +237 -0
  239. package/src/app/settings/keys-settings.tsx +96 -0
  240. package/src/app/settings/mcp-settings.tsx +271 -0
  241. package/src/app/settings/model-settings.test.tsx +157 -0
  242. package/src/app/settings/model-settings.tsx +559 -0
  243. package/src/app/settings/notifications-settings.tsx +150 -0
  244. package/src/app/settings/primitives.tsx +115 -0
  245. package/src/app/settings/providers-settings.test.tsx +100 -0
  246. package/src/app/settings/providers-settings.tsx +258 -0
  247. package/src/app/settings/sessions-settings.tsx +276 -0
  248. package/src/app/settings/toolset-config-panel.test.tsx +289 -0
  249. package/src/app/settings/toolset-config-panel.tsx +449 -0
  250. package/src/app/settings/types.ts +42 -0
  251. package/src/app/settings/uninstall-section.tsx +185 -0
  252. package/src/app/settings/use-deep-link-highlight.ts +60 -0
  253. package/src/app/shell/app-shell.tsx +167 -0
  254. package/src/app/shell/gateway-menu-panel.tsx +150 -0
  255. package/src/app/shell/hooks/use-overlay-routing.ts +71 -0
  256. package/src/app/shell/hooks/use-status-snapshot.ts +57 -0
  257. package/src/app/shell/hooks/use-statusbar-items.tsx +403 -0
  258. package/src/app/shell/keybind-panel.tsx +220 -0
  259. package/src/app/shell/model-edit-submenu.test.tsx +84 -0
  260. package/src/app/shell/model-edit-submenu.tsx +245 -0
  261. package/src/app/shell/model-menu-panel.tsx +295 -0
  262. package/src/app/shell/sidebar-label.tsx +22 -0
  263. package/src/app/shell/statusbar-controls.tsx +185 -0
  264. package/src/app/shell/titlebar-controls.tsx +244 -0
  265. package/src/app/shell/titlebar.test.ts +26 -0
  266. package/src/app/shell/titlebar.ts +45 -0
  267. package/src/app/shell/use-group-registry.ts +39 -0
  268. package/src/app/skills/index.test.tsx +103 -0
  269. package/src/app/skills/index.tsx +371 -0
  270. package/src/app/types.ts +99 -0
  271. package/src/app/updates-overlay.tsx +369 -0
  272. package/src/components/Backdrop.tsx +114 -0
  273. package/src/components/assistant-ui/ansi-text.tsx +34 -0
  274. package/src/components/assistant-ui/clarify-tool.tsx +281 -0
  275. package/src/components/assistant-ui/directive-text.test.ts +39 -0
  276. package/src/components/assistant-ui/directive-text.tsx +389 -0
  277. package/src/components/assistant-ui/markdown-text.test.ts +204 -0
  278. package/src/components/assistant-ui/markdown-text.tsx +497 -0
  279. package/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
  280. package/src/components/assistant-ui/message-render-boundary.tsx +48 -0
  281. package/src/components/assistant-ui/streaming.test.tsx +739 -0
  282. package/src/components/assistant-ui/thread-list.tsx +307 -0
  283. package/src/components/assistant-ui/thread-virtualizer.tsx +512 -0
  284. package/src/components/assistant-ui/thread.tsx +1474 -0
  285. package/src/components/assistant-ui/todo-tool.tsx +109 -0
  286. package/src/components/assistant-ui/tool-approval-group.test.tsx +158 -0
  287. package/src/components/assistant-ui/tool-approval.test.tsx +81 -0
  288. package/src/components/assistant-ui/tool-approval.tsx +209 -0
  289. package/src/components/assistant-ui/tool-fallback-model.test.ts +66 -0
  290. package/src/components/assistant-ui/tool-fallback-model.ts +1368 -0
  291. package/src/components/assistant-ui/tool-fallback.tsx +466 -0
  292. package/src/components/assistant-ui/tooltip-icon-button.tsx +33 -0
  293. package/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
  294. package/src/components/assistant-ui/user-message-text.tsx +150 -0
  295. package/src/components/boot-failure-overlay.tsx +246 -0
  296. package/src/components/boot-failure-reauth.test.ts +100 -0
  297. package/src/components/boot-failure-reauth.ts +81 -0
  298. package/src/components/brand-mark.tsx +19 -0
  299. package/src/components/chat/activity-timer-text.tsx +24 -0
  300. package/src/components/chat/activity-timer.test.tsx +43 -0
  301. package/src/components/chat/activity-timer.ts +64 -0
  302. package/src/components/chat/code-card.tsx +78 -0
  303. package/src/components/chat/compact-markdown.tsx +113 -0
  304. package/src/components/chat/composer-dock.ts +31 -0
  305. package/src/components/chat/diff-lines.tsx +54 -0
  306. package/src/components/chat/disclosure-row.tsx +63 -0
  307. package/src/components/chat/generated-image-context.tsx +19 -0
  308. package/src/components/chat/generated-image-result.tsx +174 -0
  309. package/src/components/chat/image-generation-placeholder.tsx +279 -0
  310. package/src/components/chat/intro-copy.jsonl +75 -0
  311. package/src/components/chat/intro.tsx +182 -0
  312. package/src/components/chat/preview-attachment.tsx +125 -0
  313. package/src/components/chat/shiki-highlighter.tsx +107 -0
  314. package/src/components/chat/status-row.tsx +70 -0
  315. package/src/components/chat/status-section.tsx +42 -0
  316. package/src/components/chat/terminal-output.tsx +50 -0
  317. package/src/components/chat/zoomable-image.tsx +177 -0
  318. package/src/components/desktop-install-overlay.tsx +595 -0
  319. package/src/components/desktop-onboarding-overlay.test.tsx +100 -0
  320. package/src/components/desktop-onboarding-overlay.tsx +1286 -0
  321. package/src/components/error-boundary.tsx +77 -0
  322. package/src/components/gateway-connecting-overlay.test.tsx +143 -0
  323. package/src/components/gateway-connecting-overlay.tsx +183 -0
  324. package/src/components/haptics-provider.tsx +19 -0
  325. package/src/components/language-switcher.test.tsx +53 -0
  326. package/src/components/language-switcher.tsx +175 -0
  327. package/src/components/model-picker.tsx +340 -0
  328. package/src/components/model-visibility-dialog.tsx +155 -0
  329. package/src/components/notifications.tsx +196 -0
  330. package/src/components/page-loader.tsx +34 -0
  331. package/src/components/pane-shell/context.ts +14 -0
  332. package/src/components/pane-shell/index.ts +4 -0
  333. package/src/components/pane-shell/pane-shell.test.tsx +333 -0
  334. package/src/components/pane-shell/pane-shell.tsx +330 -0
  335. package/src/components/prompt-overlays.tsx +234 -0
  336. package/src/components/session-picker.tsx +108 -0
  337. package/src/components/status-dot.tsx +26 -0
  338. package/src/components/ui/action-status.tsx +25 -0
  339. package/src/components/ui/alert.tsx +53 -0
  340. package/src/components/ui/badge.tsx +35 -0
  341. package/src/components/ui/braille-spinner.tsx +61 -0
  342. package/src/components/ui/button.tsx +81 -0
  343. package/src/components/ui/checkbox.tsx +27 -0
  344. package/src/components/ui/codicon.tsx +20 -0
  345. package/src/components/ui/command.tsx +111 -0
  346. package/src/components/ui/confirm-dialog.tsx +109 -0
  347. package/src/components/ui/context-menu.tsx +141 -0
  348. package/src/components/ui/control.ts +25 -0
  349. package/src/components/ui/copy-button.test.tsx +36 -0
  350. package/src/components/ui/copy-button.tsx +229 -0
  351. package/src/components/ui/dialog.tsx +152 -0
  352. package/src/components/ui/disclosure-caret.tsx +20 -0
  353. package/src/components/ui/dropdown-menu.tsx +291 -0
  354. package/src/components/ui/error-state.tsx +50 -0
  355. package/src/components/ui/fade-text.tsx +110 -0
  356. package/src/components/ui/glyph-spinner.tsx +63 -0
  357. package/src/components/ui/input.tsx +22 -0
  358. package/src/components/ui/kbd.tsx +37 -0
  359. package/src/components/ui/loader.tsx +558 -0
  360. package/src/components/ui/log-view.tsx +17 -0
  361. package/src/components/ui/pagination.tsx +114 -0
  362. package/src/components/ui/popover.tsx +44 -0
  363. package/src/components/ui/scroll-area.tsx +43 -0
  364. package/src/components/ui/search-field.tsx +80 -0
  365. package/src/components/ui/segmented-control.tsx +51 -0
  366. package/src/components/ui/select.tsx +92 -0
  367. package/src/components/ui/separator.tsx +26 -0
  368. package/src/components/ui/sheet.tsx +116 -0
  369. package/src/components/ui/sidebar.tsx +674 -0
  370. package/src/components/ui/skeleton.tsx +7 -0
  371. package/src/components/ui/switch.tsx +49 -0
  372. package/src/components/ui/tabs.tsx +36 -0
  373. package/src/components/ui/text-tab.tsx +43 -0
  374. package/src/components/ui/textarea.tsx +11 -0
  375. package/src/components/ui/tool-icon.tsx +65 -0
  376. package/src/components/ui/tooltip.tsx +69 -0
  377. package/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
  378. package/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
  379. package/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
  380. package/src/global.d.ts +457 -0
  381. package/src/hooks/use-image-download.ts +85 -0
  382. package/src/hooks/use-media-query.ts +24 -0
  383. package/src/hooks/use-mobile.ts +3 -0
  384. package/src/hooks/use-resize-observer.ts +38 -0
  385. package/src/hooks/use-worktree-info.ts +68 -0
  386. package/src/i18n/catalog.ts +12 -0
  387. package/src/i18n/context.test.tsx +232 -0
  388. package/src/i18n/context.tsx +183 -0
  389. package/src/i18n/define-locale.ts +41 -0
  390. package/src/i18n/en.ts +1779 -0
  391. package/src/i18n/index.ts +20 -0
  392. package/src/i18n/ja.ts +1890 -0
  393. package/src/i18n/languages.test.ts +43 -0
  394. package/src/i18n/languages.ts +86 -0
  395. package/src/i18n/runtime.test.ts +75 -0
  396. package/src/i18n/runtime.ts +53 -0
  397. package/src/i18n/types.ts +1452 -0
  398. package/src/i18n/zh-hant.ts +1849 -0
  399. package/src/i18n/zh.ts +1923 -0
  400. package/src/lib/ansi.test.ts +123 -0
  401. package/src/lib/ansi.ts +175 -0
  402. package/src/lib/chat-messages.test.ts +708 -0
  403. package/src/lib/chat-messages.ts +885 -0
  404. package/src/lib/chat-runtime.test.ts +18 -0
  405. package/src/lib/chat-runtime.ts +335 -0
  406. package/src/lib/clipboard.ts +28 -0
  407. package/src/lib/commit-changelog.test.ts +114 -0
  408. package/src/lib/commit-changelog.ts +177 -0
  409. package/src/lib/completion-sound.ts +519 -0
  410. package/src/lib/desktop-fs.test.ts +116 -0
  411. package/src/lib/desktop-fs.ts +113 -0
  412. package/src/lib/desktop-slash-commands.test.ts +126 -0
  413. package/src/lib/desktop-slash-commands.ts +286 -0
  414. package/src/lib/embedded-images.test.ts +35 -0
  415. package/src/lib/embedded-images.ts +60 -0
  416. package/src/lib/external-link.test.tsx +168 -0
  417. package/src/lib/external-link.tsx +303 -0
  418. package/src/lib/gateway-events.test.ts +27 -0
  419. package/src/lib/gateway-events.ts +49 -0
  420. package/src/lib/gateway-ws-url.test.ts +78 -0
  421. package/src/lib/gateway-ws-url.ts +91 -0
  422. package/src/lib/generated-images.test.ts +97 -0
  423. package/src/lib/generated-images.ts +116 -0
  424. package/src/lib/haptics.ts +129 -0
  425. package/src/lib/icons.ts +203 -0
  426. package/src/lib/incremental-external-store-runtime.ts +188 -0
  427. package/src/lib/katex-memo.ts +260 -0
  428. package/src/lib/keybinds/actions.ts +125 -0
  429. package/src/lib/keybinds/combo.test.ts +86 -0
  430. package/src/lib/keybinds/combo.ts +169 -0
  431. package/src/lib/local-preview.ts +126 -0
  432. package/src/lib/markdown-code.test.ts +23 -0
  433. package/src/lib/markdown-code.ts +195 -0
  434. package/src/lib/markdown-preprocess.ts +386 -0
  435. package/src/lib/media.remote.test.ts +58 -0
  436. package/src/lib/media.ts +111 -0
  437. package/src/lib/model-status-label.test.ts +31 -0
  438. package/src/lib/model-status-label.ts +103 -0
  439. package/src/lib/mutable-ref.ts +6 -0
  440. package/src/lib/preview-targets.test.ts +27 -0
  441. package/src/lib/preview-targets.ts +63 -0
  442. package/src/lib/profile-color.ts +58 -0
  443. package/src/lib/provider-setup-errors.test.ts +26 -0
  444. package/src/lib/provider-setup-errors.ts +12 -0
  445. package/src/lib/query-client.ts +13 -0
  446. package/src/lib/remend-tail.test.ts +105 -0
  447. package/src/lib/remend-tail.ts +108 -0
  448. package/src/lib/runtime-readiness.test.ts +65 -0
  449. package/src/lib/runtime-readiness.ts +147 -0
  450. package/src/lib/session-export.ts +57 -0
  451. package/src/lib/session-search.test.ts +58 -0
  452. package/src/lib/session-search.ts +19 -0
  453. package/src/lib/session-source.ts +62 -0
  454. package/src/lib/speech-text.ts +35 -0
  455. package/src/lib/statusbar.ts +91 -0
  456. package/src/lib/storage.test.ts +25 -0
  457. package/src/lib/storage.ts +107 -0
  458. package/src/lib/todos.test.ts +35 -0
  459. package/src/lib/todos.ts +51 -0
  460. package/src/lib/tool-result-summary.test.ts +106 -0
  461. package/src/lib/tool-result-summary.ts +467 -0
  462. package/src/lib/update-copy.test.ts +38 -0
  463. package/src/lib/update-copy.ts +44 -0
  464. package/src/lib/use-enter-animation.ts +100 -0
  465. package/src/lib/utils.ts +6 -0
  466. package/src/lib/voice-playback.ts +128 -0
  467. package/src/lib/yolo-session.ts +26 -0
  468. package/src/main.tsx +43 -0
  469. package/src/nastech.test.ts +49 -0
  470. package/src/nastech.ts +718 -0
  471. package/src/store/activity.ts +100 -0
  472. package/src/store/boot.ts +91 -0
  473. package/src/store/clarify.test.ts +81 -0
  474. package/src/store/clarify.ts +69 -0
  475. package/src/store/command-palette.ts +20 -0
  476. package/src/store/compaction.test.ts +53 -0
  477. package/src/store/compaction.ts +38 -0
  478. package/src/store/completion-sound.ts +32 -0
  479. package/src/store/composer-input-history.test.ts +147 -0
  480. package/src/store/composer-input-history.ts +158 -0
  481. package/src/store/composer-queue.test.ts +148 -0
  482. package/src/store/composer-queue.ts +239 -0
  483. package/src/store/composer-status.test.ts +99 -0
  484. package/src/store/composer-status.ts +277 -0
  485. package/src/store/composer.test.ts +106 -0
  486. package/src/store/composer.ts +184 -0
  487. package/src/store/cron.ts +19 -0
  488. package/src/store/gateway.ts +290 -0
  489. package/src/store/haptics.ts +17 -0
  490. package/src/store/keybinds.ts +139 -0
  491. package/src/store/layout.ts +176 -0
  492. package/src/store/model-presets.test.ts +51 -0
  493. package/src/store/model-presets.ts +86 -0
  494. package/src/store/model-visibility.test.ts +37 -0
  495. package/src/store/model-visibility.ts +108 -0
  496. package/src/store/native-notifications.test.ts +192 -0
  497. package/src/store/native-notifications.ts +203 -0
  498. package/src/store/notifications.ts +165 -0
  499. package/src/store/onboarding.test.ts +372 -0
  500. package/src/store/onboarding.ts +866 -0
  501. package/src/store/panes.test.ts +146 -0
  502. package/src/store/panes.ts +145 -0
  503. package/src/store/preview.test.ts +135 -0
  504. package/src/store/preview.ts +466 -0
  505. package/src/store/profile.test.ts +89 -0
  506. package/src/store/profile.ts +365 -0
  507. package/src/store/prompts.test.ts +121 -0
  508. package/src/store/prompts.ts +115 -0
  509. package/src/store/session-switcher.test.ts +115 -0
  510. package/src/store/session-switcher.ts +128 -0
  511. package/src/store/session-sync.ts +25 -0
  512. package/src/store/session.test.ts +131 -0
  513. package/src/store/session.ts +255 -0
  514. package/src/store/subagents.test.ts +111 -0
  515. package/src/store/subagents.ts +260 -0
  516. package/src/store/thread-scroll.ts +46 -0
  517. package/src/store/todos.test.ts +47 -0
  518. package/src/store/todos.ts +64 -0
  519. package/src/store/tool-diffs.ts +23 -0
  520. package/src/store/tool-dismiss.ts +45 -0
  521. package/src/store/tool-view.ts +91 -0
  522. package/src/store/translucency.ts +38 -0
  523. package/src/store/updates.test.ts +77 -0
  524. package/src/store/updates.ts +315 -0
  525. package/src/store/voice-playback.ts +24 -0
  526. package/src/store/windows.test.ts +143 -0
  527. package/src/store/windows.ts +77 -0
  528. package/src/styles.css +1235 -0
  529. package/src/themes/color.ts +142 -0
  530. package/src/themes/context.tsx +339 -0
  531. package/src/themes/index.ts +3 -0
  532. package/src/themes/install.test.ts +119 -0
  533. package/src/themes/install.ts +95 -0
  534. package/src/themes/presets.test.ts +33 -0
  535. package/src/themes/presets.ts +293 -0
  536. package/src/themes/profile-theme.test.ts +41 -0
  537. package/src/themes/types.ts +66 -0
  538. package/src/themes/use-skin-command.ts +60 -0
  539. package/src/themes/user-themes.test.ts +63 -0
  540. package/src/themes/user-themes.ts +122 -0
  541. package/src/themes/vscode.test.ts +171 -0
  542. package/src/themes/vscode.ts +343 -0
  543. package/src/types/nastech.ts +646 -0
  544. package/src/vite-env.d.ts +1 -0
  545. package/tsconfig.json +25 -0
  546. package/vite.config.ts +56 -0
@@ -0,0 +1,885 @@
1
+ import type { ThreadMessageLike } from '@assistant-ui/react'
2
+
3
+ import { mediaDisplayLabel, mediaMarkdownHref } from '@/lib/media'
4
+ import { parseTodos } from '@/lib/todos'
5
+ import type { SessionMessage, UsageStats } from '@/types/nastech'
6
+
7
+ export type ChatMessagePart = Exclude<ThreadMessageLike['content'], string>[number]
8
+
9
+ export type ChatMessage = {
10
+ id: string
11
+ role: SessionMessage['role']
12
+ parts: ChatMessagePart[]
13
+ timestamp?: number
14
+ pending?: boolean
15
+ error?: string
16
+ branchGroupId?: string
17
+ hidden?: boolean
18
+ /** Composer attachment ref strings (`@file:...`, `@image:...`) sent with this user message. */
19
+ attachmentRefs?: string[]
20
+ }
21
+
22
+ export type GatewayEventPayload = {
23
+ text?: string
24
+ rendered?: string
25
+ status?: string
26
+ message?: string
27
+ id?: string
28
+ name?: string
29
+ tool_id?: string
30
+ tool_call_id?: string
31
+ args?: unknown
32
+ arguments?: unknown
33
+ context?: string
34
+ input?: unknown
35
+ preview?: string
36
+ result?: unknown
37
+ summary?: string
38
+ error?: string | boolean
39
+ inline_diff?: string
40
+ duration_s?: number
41
+ todos?: unknown
42
+ model?: string
43
+ provider?: string
44
+ reasoning_effort?: string
45
+ service_tier?: string
46
+ fast?: boolean
47
+ yolo?: boolean
48
+ running?: boolean
49
+ cwd?: string
50
+ branch?: string
51
+ credential_warning?: string
52
+ personality?: string
53
+ usage?: Partial<UsageStats>
54
+ // clarify.request
55
+ request_id?: string
56
+ question?: string
57
+ choices?: string[] | null
58
+ // approval.request (dangerous command / execute_code) — session-keyed
59
+ command?: string
60
+ description?: string
61
+ // secret.request (skill credential capture)
62
+ env_var?: string
63
+ prompt?: string
64
+ }
65
+
66
+ export function textPart(text: string): ChatMessagePart {
67
+ return { type: 'text', text }
68
+ }
69
+
70
+ export function reasoningPart(text: string): ChatMessagePart {
71
+ return { type: 'reasoning', text }
72
+ }
73
+
74
+ const MEDIA_LINE_RE = /(^|\n)[\t ]*[`"']?MEDIA:\s*(?<line>`[^`\n]+`|"[^"\n]+"|'[^'\n]+'|\S+)[`"']?[\t ]*(\n|$)/g
75
+
76
+ const MEDIA_TAG_RE = /[`"']?MEDIA:\s*(?<inline>`[^`\n]+`|"[^"\n]+"|'[^'\n]+'|\S+)[`"']?/g
77
+
78
+ function unquoteMediaPath(value: string): string {
79
+ const trimmed = value.trim()
80
+ const quote = trimmed[0]
81
+
82
+ return quote && quote === trimmed.at(-1) && ['"', "'", '`'].includes(quote) ? trimmed.slice(1, -1) : trimmed
83
+ }
84
+
85
+ function mediaLink(value: string): string {
86
+ const path = unquoteMediaPath(value)
87
+
88
+ return `[${mediaDisplayLabel(path)}](${mediaMarkdownHref(path)})`
89
+ }
90
+
91
+ export function renderMediaTags(text: string): string {
92
+ return text
93
+ .replace(
94
+ MEDIA_LINE_RE,
95
+ (_match, lead: string, value: string, trailer: string) => `${lead}${mediaLink(value)}${trailer}`
96
+ )
97
+ .replace(MEDIA_TAG_RE, (_match, value: string) => mediaLink(value))
98
+ .replace(/[ \t]+\n/g, '\n')
99
+ .replace(/\n{3,}/g, '\n\n')
100
+ }
101
+
102
+ export function assistantTextPart(text: string): ChatMessagePart {
103
+ return textPart(renderMediaTags(text))
104
+ }
105
+
106
+ export function chatMessageText(message: ChatMessage): string {
107
+ return message.parts
108
+ .filter((part): part is Extract<ChatMessagePart, { type: 'text' }> => part.type === 'text')
109
+ .map(part => part.text)
110
+ .join('')
111
+ }
112
+
113
+ const ATTACHED_CONTEXT_MARKER_RE = /(?:^|\n)--- Attached Context ---\s*\n/
114
+ const CONTEXT_WARNINGS_MARKER_RE = /(?:^|\n)--- Context Warnings ---[\s\S]*$/
115
+ const CONTEXT_REF_RE = /@(file|folder|url|image|tool|terminal):(?:"[^"\n]+"|'[^'\n]+'|`[^`\n]+`|\S+)/g
116
+
117
+ function textFromUnknown(value: unknown, depth = 0): string {
118
+ if (typeof value === 'string') {
119
+ return value
120
+ }
121
+
122
+ if (value === null || value === undefined) {
123
+ return ''
124
+ }
125
+
126
+ if (depth > 2) {
127
+ return ''
128
+ }
129
+
130
+ if (Array.isArray(value)) {
131
+ return value.map(item => textFromUnknown(item, depth + 1)).join('')
132
+ }
133
+
134
+ if (typeof value === 'object') {
135
+ const row = value as Record<string, unknown>
136
+ const textValue = row.text ?? row.output_text ?? row.content ?? row.message
137
+ const nestedText = textFromUnknown(textValue, depth + 1)
138
+
139
+ if (nestedText) {
140
+ return nestedText
141
+ }
142
+
143
+ try {
144
+ return JSON.stringify(value)
145
+ } catch {
146
+ return ''
147
+ }
148
+ }
149
+
150
+ return String(value)
151
+ }
152
+
153
+ function displayContentForMessage(role: SessionMessage['role'], content: unknown): string {
154
+ const textContent = textFromUnknown(content)
155
+
156
+ if (role !== 'user') {
157
+ return textContent
158
+ }
159
+
160
+ const marker = textContent.match(ATTACHED_CONTEXT_MARKER_RE)
161
+
162
+ if (!marker || marker.index === undefined) {
163
+ return textContent.replace(CONTEXT_WARNINGS_MARKER_RE, '').trim()
164
+ }
165
+
166
+ const visibleText = textContent.slice(0, marker.index).replace(CONTEXT_WARNINGS_MARKER_RE, '').trim()
167
+ const attachedContext = textContent.slice(marker.index + marker[0].length)
168
+ const refs = [...new Set(Array.from(attachedContext.matchAll(CONTEXT_REF_RE)).map(match => match[0]))]
169
+
170
+ return [refs.join('\n'), visibleText].filter(Boolean).join('\n\n') || visibleText
171
+ }
172
+
173
+ export function appendTextPart(parts: ChatMessagePart[], delta: string): ChatMessagePart[] {
174
+ const next = [...parts]
175
+ const last = next.at(-1)
176
+
177
+ if (last?.type === 'text') {
178
+ next[next.length - 1] = { ...last, text: `${last.text}${delta}` }
179
+
180
+ return next
181
+ }
182
+
183
+ next.push(textPart(delta))
184
+
185
+ return next
186
+ }
187
+
188
+ export function appendAssistantTextPart(parts: ChatMessagePart[], delta: string): ChatMessagePart[] {
189
+ const next = appendTextPart(parts, delta)
190
+ const last = next.at(-1)
191
+
192
+ if (last?.type === 'text') {
193
+ const current = last.text
194
+
195
+ const deltaMayContainMedia =
196
+ delta.includes('MEDIA:') || delta.includes('DIA:') || delta.includes('EDIA:') || delta.includes('IA:')
197
+
198
+ const needsMediaPass = deltaMayContainMedia || current.includes('MEDIA:')
199
+ const nextText = needsMediaPass ? renderMediaTags(current) : current
200
+ next[next.length - 1] = nextText === current ? last : { ...last, text: nextText }
201
+ }
202
+
203
+ return next
204
+ }
205
+
206
+ export function appendReasoningPart(parts: ChatMessagePart[], delta: string): ChatMessagePart[] {
207
+ const next = [...parts]
208
+ const last = next.at(-1)
209
+
210
+ if (last?.type === 'reasoning') {
211
+ next[next.length - 1] = { ...last, text: `${last.text}${delta}` }
212
+
213
+ return next
214
+ }
215
+
216
+ next.push(reasoningPart(delta))
217
+
218
+ return next
219
+ }
220
+
221
+ export function hasToolPart(message: ChatMessage): boolean {
222
+ return message.parts.some(part => part.type === 'tool-call')
223
+ }
224
+
225
+ function toolId(payload: GatewayEventPayload | undefined): string {
226
+ return payload?.tool_id || payload?.tool_call_id || payload?.id || ''
227
+ }
228
+
229
+ let liveToolCounter = 0
230
+
231
+ function nextLiveToolId(name: string): string {
232
+ liveToolCounter += 1
233
+
234
+ return `live-tool:${name}:${liveToolCounter}`
235
+ }
236
+
237
+ function firstStringField(record: Record<string, unknown>, keys: readonly string[]): string {
238
+ for (const key of keys) {
239
+ const value = record[key]
240
+
241
+ if (typeof value === 'string' && value.trim()) {
242
+ return value.trim()
243
+ }
244
+ }
245
+
246
+ return ''
247
+ }
248
+
249
+ function normalizeToolMatchValue(value: string): string {
250
+ return value.trim().toLowerCase()
251
+ }
252
+
253
+ function collectToolMatchValues(query: string, context: string, preview: string): string[] {
254
+ return [...new Set([query, context, preview].map(normalizeToolMatchValue).filter(Boolean))]
255
+ }
256
+
257
+ function toolPayloadMatchValues(payload: GatewayEventPayload | undefined): string[] {
258
+ const payloadArgs = liveToolArgs(payload)
259
+ const query = firstStringField(payloadArgs, ['search_term', 'query'])
260
+ const context = typeof payload?.context === 'string' ? payload.context.trim() : ''
261
+ const preview = typeof payload?.preview === 'string' ? payload.preview.trim() : ''
262
+
263
+ return collectToolMatchValues(query, context, preview)
264
+ }
265
+
266
+ function toolPartMatchValues(part: ChatMessagePart): string[] {
267
+ if (part.type !== 'tool-call' || !part.args || typeof part.args !== 'object') {
268
+ return []
269
+ }
270
+
271
+ const args = part.args as Record<string, unknown>
272
+ const query = firstStringField(args, ['search_term', 'query'])
273
+ const context = typeof args.context === 'string' ? args.context.trim() : ''
274
+ const preview = typeof args.preview === 'string' ? args.preview.trim() : ''
275
+
276
+ return collectToolMatchValues(query, context, preview)
277
+ }
278
+
279
+ function hasToolMatchOverlap(left: string[], right: string[]): boolean {
280
+ if (!left.length || !right.length) {
281
+ return false
282
+ }
283
+
284
+ const rightSet = new Set(right)
285
+
286
+ return left.some(value => rightSet.has(value))
287
+ }
288
+
289
+ function findToolPartIndex(
290
+ parts: ChatMessagePart[],
291
+ name: string,
292
+ stableId: string,
293
+ payload: GatewayEventPayload | undefined,
294
+ phase: 'running' | 'complete'
295
+ ): number {
296
+ const matchValues = toolPayloadMatchValues(payload)
297
+ const overlaps = (index: number) => hasToolMatchOverlap(matchValues, toolPartMatchValues(parts[index]))
298
+
299
+ if (stableId) {
300
+ const stableIndex = parts.findIndex(part => part.type === 'tool-call' && part.toolCallId === stableId)
301
+
302
+ if (stableIndex >= 0) {
303
+ return stableIndex
304
+ }
305
+
306
+ // Some live streams start without an id, then complete with one. Fall
307
+ // through to pending same-name/context matching so the completion updates
308
+ // the synthetic live row instead of appending a duplicate completed row.
309
+ if (phase === 'running' && !matchValues.length) {
310
+ return -1
311
+ }
312
+ }
313
+
314
+ const pendingIndices = parts
315
+ .map((part, index) => ({ part, index }))
316
+ .filter(({ part }) => part.type === 'tool-call' && part.toolName === name && part.result === undefined)
317
+ .map(({ index }) => index)
318
+
319
+ if (pendingIndices.length === 0) {
320
+ return -1
321
+ }
322
+
323
+ if (matchValues.length) {
324
+ const contextualIndex = pendingIndices.find(overlaps)
325
+
326
+ if (contextualIndex !== undefined) {
327
+ return contextualIndex
328
+ }
329
+ }
330
+
331
+ if (pendingIndices.length === 1) {
332
+ const [singlePendingIndex] = pendingIndices
333
+
334
+ if (phase === 'running' && matchValues.length && !overlaps(singlePendingIndex)) {
335
+ return stableId ? singlePendingIndex : -1
336
+ }
337
+
338
+ return singlePendingIndex
339
+ }
340
+
341
+ // Completion events without stable IDs frequently arrive after multiple
342
+ // same-name starts (parallel tool calls). Resolve them oldest-first so we
343
+ // don't collapse an entire burst into a single row.
344
+ if (phase === 'complete') {
345
+ return pendingIndices[0]
346
+ }
347
+
348
+ if (stableId) {
349
+ return pendingIndices[0]
350
+ }
351
+
352
+ // For progress/running events with no stable id, update the most-recent
353
+ // pending same-name tool instead of creating a phantom extra row.
354
+ return pendingIndices.at(-1) ?? -1
355
+ }
356
+
357
+ // Carry todo state across sparse progress payloads: if this todo event lacks
358
+ // a `todos` field, fall back to whatever we previously stored on the part.
359
+ function carryTodos(payload: GatewayEventPayload | undefined, ...prev: unknown[]): { todos: unknown } | undefined {
360
+ if (payload && Object.hasOwn(payload, 'todos')) {
361
+ const next = parseTodos(payload.todos)
362
+
363
+ return next === null ? undefined : { todos: next }
364
+ }
365
+
366
+ if (payload?.name !== 'todo') {
367
+ return undefined
368
+ }
369
+
370
+ for (const p of prev) {
371
+ const carried = parseTodos(recordFromUnknown(p)?.todos)
372
+
373
+ if (carried !== null) {
374
+ return { todos: carried }
375
+ }
376
+ }
377
+
378
+ return undefined
379
+ }
380
+
381
+ function toolArgs(payload: GatewayEventPayload | undefined, prevArgs?: unknown): Record<string, unknown> {
382
+ const prev = parseMaybeJsonObject(prevArgs)
383
+ const eventArgs = liveToolArgs(payload)
384
+
385
+ return {
386
+ ...prev,
387
+ ...eventArgs,
388
+ ...(payload?.context ? { context: payload.context } : {}),
389
+ ...(payload?.preview ? { preview: payload.preview } : {}),
390
+ ...carryTodos(payload, prevArgs)
391
+ }
392
+ }
393
+
394
+ function toolResult(
395
+ payload: GatewayEventPayload | undefined,
396
+ prevResult?: unknown,
397
+ prevArgs?: unknown
398
+ ): Record<string, unknown> {
399
+ const parsedResult = parseMaybeJsonObject(payload?.result)
400
+
401
+ return {
402
+ ...parsedResult,
403
+ ...(payload?.inline_diff ? { inline_diff: payload.inline_diff } : {}),
404
+ ...(payload?.summary ? { summary: payload.summary } : {}),
405
+ ...(payload?.message ? { message: payload.message } : {}),
406
+ ...(payload?.preview ? { preview: payload.preview } : {}),
407
+ ...(payload?.duration_s !== undefined ? { duration_s: payload.duration_s } : {}),
408
+ ...carryTodos(payload, prevResult, prevArgs),
409
+ ...(payload?.error ? { error: payload.error } : {})
410
+ }
411
+ }
412
+
413
+ export function upsertToolPart(
414
+ parts: ChatMessagePart[],
415
+ payload: GatewayEventPayload | undefined,
416
+ phase: 'running' | 'complete'
417
+ ): ChatMessagePart[] {
418
+ const stableId = toolId(payload)
419
+ const name = payload?.name || 'tool'
420
+ const next = [...parts]
421
+
422
+ const index = findToolPartIndex(next, name, stableId, payload, phase)
423
+
424
+ const prev = index >= 0 ? next[index] : null
425
+ const prevArgs = prev && 'args' in prev ? prev.args : undefined
426
+ const prevResult = prev && 'result' in prev ? prev.result : undefined
427
+ const args = toolArgs(payload, prevArgs)
428
+
429
+ const id =
430
+ stableId ||
431
+ (prev && 'toolCallId' in prev && typeof prev.toolCallId === 'string' ? prev.toolCallId : '') ||
432
+ nextLiveToolId(name)
433
+
434
+ const base = {
435
+ type: 'tool-call' as const,
436
+ toolCallId: id,
437
+ toolName: name,
438
+ args: args as never,
439
+ argsText: JSON.stringify(args),
440
+ ...(phase === 'complete' && { result: toolResult(payload, prevResult, prevArgs), isError: Boolean(payload?.error) })
441
+ } satisfies ChatMessagePart
442
+
443
+ if (index === -1) {
444
+ return [...next, base]
445
+ }
446
+
447
+ next[index] = { ...next[index], ...base }
448
+
449
+ return next
450
+ }
451
+
452
+ function recordFromUnknown(value: unknown): Record<string, unknown> | null {
453
+ return value && typeof value === 'object' ? (value as Record<string, unknown>) : null
454
+ }
455
+
456
+ function parseMaybeJsonObject(value: unknown): Record<string, unknown> {
457
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
458
+ return value as Record<string, unknown>
459
+ }
460
+
461
+ if (typeof value !== 'string' || !value.trim()) {
462
+ return {}
463
+ }
464
+
465
+ try {
466
+ const parsed = JSON.parse(value)
467
+
468
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {}
469
+ } catch {
470
+ return {}
471
+ }
472
+ }
473
+
474
+ function firstNonEmptyObject(...values: unknown[]): Record<string, unknown> {
475
+ for (const value of values) {
476
+ const parsed = parseMaybeJsonObject(value)
477
+
478
+ if (Object.keys(parsed).length > 0) {
479
+ return parsed
480
+ }
481
+ }
482
+
483
+ return {}
484
+ }
485
+
486
+ function liveToolArgs(payload: GatewayEventPayload | undefined): Record<string, unknown> {
487
+ const direct = firstNonEmptyObject(payload?.args, payload?.arguments)
488
+ const input = firstNonEmptyObject(payload?.input)
489
+ const fn = recordFromUnknown(input.function)
490
+
491
+ const nested = firstNonEmptyObject(
492
+ input.args,
493
+ input.arguments,
494
+ input.parameters,
495
+ input.input,
496
+ fn?.arguments,
497
+ fn?.args,
498
+ fn?.parameters
499
+ )
500
+
501
+ return {
502
+ ...input,
503
+ ...nested,
504
+ ...direct
505
+ }
506
+ }
507
+
508
+ function parseStoredToolResult(content: unknown): unknown {
509
+ if (content && typeof content === 'object') {
510
+ return content
511
+ }
512
+
513
+ const textContent = textFromUnknown(content)
514
+
515
+ if (!textContent.trim()) {
516
+ return ''
517
+ }
518
+
519
+ try {
520
+ return JSON.parse(textContent)
521
+ } catch {
522
+ return textContent
523
+ }
524
+ }
525
+
526
+ function toolPartFromStoredCall(call: unknown, fallbackIndex: number): ChatMessagePart {
527
+ const row = recordFromUnknown(call) ?? {}
528
+ const fn = recordFromUnknown(row.function)
529
+ const id = String(row.id || row.tool_call_id || `stored-tool-${fallbackIndex}`)
530
+
531
+ const toolName = String(
532
+ row.name || row.tool_name || fn?.name || (recordFromUnknown(row.input)?.name as string | undefined) || 'tool'
533
+ )
534
+
535
+ const args = firstNonEmptyObject(fn?.arguments, row.arguments, row.args, row.input)
536
+
537
+ return {
538
+ type: 'tool-call',
539
+ toolCallId: id,
540
+ toolName,
541
+ args: args as never,
542
+ argsText: Object.keys(args).length ? JSON.stringify(args) : ''
543
+ }
544
+ }
545
+
546
+ function applyStoredToolResult(messages: ChatMessage[], toolMessage: SessionMessage): boolean {
547
+ const toolCallId = toolMessage.tool_call_id || undefined
548
+ const toolName = toolMessage.tool_name || toolMessage.name || 'tool'
549
+ const content = toolMessage.content || toolMessage.text || toolMessage.context || toolMessage.name
550
+
551
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
552
+ const message = messages[i]
553
+
554
+ if (message.role !== 'assistant') {
555
+ continue
556
+ }
557
+
558
+ const partIndex = message.parts.findIndex(
559
+ part =>
560
+ part.type === 'tool-call' &&
561
+ ((toolCallId && part.toolCallId === toolCallId) || (!toolCallId && part.toolName === toolName))
562
+ )
563
+
564
+ if (partIndex < 0) {
565
+ continue
566
+ }
567
+
568
+ const parts = [...message.parts]
569
+ const existing = parts[partIndex]
570
+ parts[partIndex] = {
571
+ ...existing,
572
+ result: parseStoredToolResult(content),
573
+ isError: false
574
+ } as ChatMessagePart
575
+ messages[i] = { ...message, parts }
576
+
577
+ return true
578
+ }
579
+
580
+ return false
581
+ }
582
+
583
+ function applyStoredToolResultToParts(parts: ChatMessagePart[], toolMessage: SessionMessage): ChatMessagePart[] | null {
584
+ const toolCallId = toolMessage.tool_call_id || undefined
585
+ const toolName = toolMessage.tool_name || toolMessage.name || 'tool'
586
+ const content = toolMessage.content || toolMessage.text || toolMessage.context || toolMessage.name
587
+
588
+ const partIndex = parts.findIndex(
589
+ part =>
590
+ part.type === 'tool-call' &&
591
+ ((toolCallId && part.toolCallId === toolCallId) || (!toolCallId && part.toolName === toolName))
592
+ )
593
+
594
+ if (partIndex < 0) {
595
+ return null
596
+ }
597
+
598
+ const next = [...parts]
599
+ const existing = next[partIndex]
600
+ next[partIndex] = {
601
+ ...existing,
602
+ result: parseStoredToolResult(content),
603
+ isError: false
604
+ } as ChatMessagePart
605
+
606
+ return next
607
+ }
608
+
609
+ function storedToolMessagePart(toolMessage: SessionMessage, fallbackIndex: number): ChatMessagePart {
610
+ const name = toolMessage.tool_name || toolMessage.name || 'tool'
611
+ const context = textFromUnknown(toolMessage.context || toolMessage.text || toolMessage.content || '')
612
+ const args = context ? { context } : {}
613
+
614
+ return {
615
+ type: 'tool-call',
616
+ toolCallId: toolMessage.tool_call_id || `stored-tool-message-${fallbackIndex}`,
617
+ toolName: name,
618
+ args: args as never,
619
+ argsText: Object.keys(args).length ? JSON.stringify(args) : '',
620
+ result: context ? { context } : {},
621
+ isError: false
622
+ }
623
+ }
624
+
625
+ function withUniqueToolCallIds(messages: ChatMessage[]): ChatMessage[] {
626
+ const seen = new Set<string>()
627
+
628
+ return messages.map(message => {
629
+ let changed = false
630
+
631
+ const parts = message.parts.map((part, index) => {
632
+ if (part.type !== 'tool-call') {
633
+ return part
634
+ }
635
+
636
+ const id = part.toolCallId || `${message.id}-tool-${index}`
637
+
638
+ if (!seen.has(id)) {
639
+ seen.add(id)
640
+
641
+ if (part.toolCallId) {
642
+ return part
643
+ }
644
+
645
+ changed = true
646
+
647
+ return { ...part, toolCallId: id } as ChatMessagePart
648
+ }
649
+
650
+ changed = true
651
+ const uniqueId = `${id}-${message.id}-${index}`
652
+ seen.add(uniqueId)
653
+
654
+ return { ...part, toolCallId: uniqueId } as ChatMessagePart
655
+ })
656
+
657
+ return changed ? { ...message, parts } : message
658
+ })
659
+ }
660
+
661
+ export function toChatMessages(messages: SessionMessage[]): ChatMessage[] {
662
+ const result: ChatMessage[] = []
663
+ let pendingToolParts: ChatMessagePart[] = []
664
+ let pendingToolTimestamp: number | undefined
665
+ let activeAssistantIndex: null | number = null
666
+
667
+ const clearPendingTools = () => {
668
+ pendingToolParts = []
669
+ pendingToolTimestamp = undefined
670
+ }
671
+
672
+ const appendPartsToActiveAssistant = (parts: ChatMessagePart[], timestamp?: number): boolean => {
673
+ if (activeAssistantIndex === null) {
674
+ return false
675
+ }
676
+
677
+ const active = result[activeAssistantIndex]
678
+
679
+ if (!active || active.role !== 'assistant') {
680
+ activeAssistantIndex = null
681
+
682
+ return false
683
+ }
684
+
685
+ active.parts = [...active.parts, ...parts]
686
+ active.timestamp = timestamp ?? active.timestamp
687
+
688
+ return true
689
+ }
690
+
691
+ const flushPendingTools = (index: number) => {
692
+ if (!pendingToolParts.length) {
693
+ return
694
+ }
695
+
696
+ if (!appendPartsToActiveAssistant(pendingToolParts, pendingToolTimestamp)) {
697
+ result.push({
698
+ id: `${pendingToolTimestamp || Date.now()}-${index}-tools`,
699
+ role: 'assistant',
700
+ parts: pendingToolParts,
701
+ timestamp: pendingToolTimestamp
702
+ })
703
+ activeAssistantIndex = result.length - 1
704
+ }
705
+
706
+ clearPendingTools()
707
+ }
708
+
709
+ messages.forEach((message, index) => {
710
+ if (message.role === 'tool') {
711
+ const updatedPendingToolParts = applyStoredToolResultToParts(pendingToolParts, message)
712
+
713
+ if (updatedPendingToolParts) {
714
+ pendingToolParts = updatedPendingToolParts
715
+
716
+ return
717
+ }
718
+
719
+ if (applyStoredToolResult(result, message)) {
720
+ return
721
+ }
722
+
723
+ pendingToolParts = [...pendingToolParts, storedToolMessagePart(message, index)]
724
+ pendingToolTimestamp ??= message.timestamp
725
+
726
+ return
727
+ }
728
+
729
+ const content = message.content || message.text || message.context || message.name
730
+ const displayContent = displayContentForMessage(message.role, content)
731
+ const parts: ChatMessagePart[] = []
732
+
733
+ const reasoning =
734
+ message.reasoning ||
735
+ message.reasoning_content ||
736
+ (typeof message.reasoning_details === 'string' ? message.reasoning_details : '')
737
+
738
+ if (reasoning && message.role === 'assistant') {
739
+ parts.push(reasoningPart(reasoning))
740
+ }
741
+
742
+ if (displayContent) {
743
+ parts.push(message.role === 'assistant' ? assistantTextPart(displayContent) : textPart(displayContent))
744
+ }
745
+
746
+ if (message.role === 'assistant' && Array.isArray(message.tool_calls)) {
747
+ parts.push(...message.tool_calls.map((call, callIndex) => toolPartFromStoredCall(call, callIndex)))
748
+ }
749
+
750
+ if (!parts.length) {
751
+ if (message.role !== 'assistant') {
752
+ flushPendingTools(index)
753
+ activeAssistantIndex = null
754
+ }
755
+
756
+ return
757
+ }
758
+
759
+ const isToolOnlyAssistant =
760
+ message.role === 'assistant' && parts.length > 0 && parts.every(part => part.type === 'tool-call')
761
+
762
+ if (isToolOnlyAssistant) {
763
+ pendingToolParts = [...pendingToolParts, ...parts]
764
+ pendingToolTimestamp ??= message.timestamp
765
+
766
+ return
767
+ }
768
+
769
+ if (message.role === 'assistant') {
770
+ if (pendingToolParts.length) {
771
+ if (!appendPartsToActiveAssistant(pendingToolParts, message.timestamp ?? pendingToolTimestamp)) {
772
+ parts.unshift(...pendingToolParts)
773
+ }
774
+
775
+ clearPendingTools()
776
+ }
777
+
778
+ const activeAssistant =
779
+ activeAssistantIndex !== null && result[activeAssistantIndex]?.role === 'assistant'
780
+ ? result[activeAssistantIndex]
781
+ : null
782
+
783
+ const currentHasToolCall = parts.some(part => part.type === 'tool-call')
784
+ const activeHasToolCall = Boolean(activeAssistant?.parts.some(part => part.type === 'tool-call'))
785
+
786
+ if (activeAssistant && (currentHasToolCall || activeHasToolCall)) {
787
+ activeAssistant.parts = [...activeAssistant.parts, ...parts]
788
+ activeAssistant.timestamp = message.timestamp ?? activeAssistant.timestamp
789
+
790
+ return
791
+ }
792
+ } else {
793
+ flushPendingTools(index)
794
+ }
795
+
796
+ result.push({
797
+ id: `${message.timestamp || Date.now()}-${index}-${message.role}`,
798
+ role: message.role,
799
+ parts,
800
+ timestamp: message.timestamp
801
+ })
802
+
803
+ activeAssistantIndex = message.role === 'assistant' ? result.length - 1 : null
804
+ })
805
+ flushPendingTools(messages.length)
806
+
807
+ return withUniqueToolCallIds(
808
+ result.filter(m => chatMessageText(m).trim() || m.parts.some(part => part.type !== 'text'))
809
+ )
810
+ }
811
+
812
+ export function preserveLocalAssistantErrors(
813
+ nextMessages: ChatMessage[],
814
+ currentMessages: ChatMessage[]
815
+ ): ChatMessage[] {
816
+ const localById = new Map(currentMessages.map(message => [message.id, message]))
817
+
818
+ const mergedNextMessages = nextMessages.map(message => {
819
+ if (message.role !== 'assistant' || message.error || message.hidden) {
820
+ return message
821
+ }
822
+
823
+ const local = localById.get(message.id)
824
+
825
+ if (!local || local.role !== 'assistant' || !local.error || local.hidden) {
826
+ return message
827
+ }
828
+
829
+ return {
830
+ ...message,
831
+ error: local.error,
832
+ pending: false
833
+ }
834
+ })
835
+
836
+ const existingIds = new Set(mergedNextMessages.map(message => message.id))
837
+ const preserveIds = new Set<string>()
838
+ const normalize = (value: string) => value.replace(/\s+/g, ' ').trim()
839
+ const tailUserInNext = [...mergedNextMessages].reverse().find(message => message.role === 'user' && !message.hidden)
840
+ const tailUserText = tailUserInNext ? normalize(chatMessageText(tailUserInNext)) : ''
841
+ const tailUserRefs = tailUserInNext ? (tailUserInNext.attachmentRefs ?? []).join('\n') : ''
842
+
843
+ const matchesTailUserInNext = (candidate: ChatMessage) =>
844
+ Boolean(tailUserInNext) &&
845
+ normalize(chatMessageText(candidate)) === tailUserText &&
846
+ (candidate.attachmentRefs ?? []).join('\n') === tailUserRefs
847
+
848
+ for (let index = 0; index < currentMessages.length; index += 1) {
849
+ const message = currentMessages[index]
850
+
851
+ if (message.role !== 'assistant' || !message.error || message.hidden || existingIds.has(message.id)) {
852
+ continue
853
+ }
854
+
855
+ preserveIds.add(message.id)
856
+
857
+ for (let probe = index - 1; probe >= 0; probe -= 1) {
858
+ const candidate = currentMessages[probe]
859
+
860
+ if (candidate.hidden) {
861
+ continue
862
+ }
863
+
864
+ if (candidate.role === 'user' && !existingIds.has(candidate.id) && !matchesTailUserInNext(candidate)) {
865
+ preserveIds.add(candidate.id)
866
+ }
867
+
868
+ break
869
+ }
870
+ }
871
+
872
+ if (preserveIds.size === 0) {
873
+ return mergedNextMessages
874
+ }
875
+
876
+ const preserved = currentMessages
877
+ .filter(message => preserveIds.has(message.id))
878
+ .map(message => ({ ...message, pending: false }))
879
+
880
+ return [...mergedNextMessages, ...preserved]
881
+ }
882
+
883
+ export function branchGroupForUser(userMessage: ChatMessage): string {
884
+ return `branch:${userMessage.id}`
885
+ }