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,330 @@
1
+ import { useStore } from '@nanostores/react'
2
+ import {
3
+ Children,
4
+ type CSSProperties,
5
+ isValidElement,
6
+ type ReactElement,
7
+ type ReactNode,
8
+ type PointerEvent as ReactPointerEvent,
9
+ useCallback,
10
+ useContext,
11
+ useEffect,
12
+ useMemo,
13
+ useRef
14
+ } from 'react'
15
+
16
+ import { cn } from '@/lib/utils'
17
+ import { $paneStates, ensurePaneRegistered, setPaneWidthOverride } from '@/store/panes'
18
+
19
+ import { PaneShellContext, type PaneShellContextValue, type PaneSlot } from './context'
20
+
21
+ type PaneSide = 'left' | 'right'
22
+ type WidthValue = string | number
23
+
24
+ interface PaneRoleMarker {
25
+ __paneShellRole?: 'pane' | 'main'
26
+ }
27
+
28
+ export interface PaneProps {
29
+ children?: ReactNode
30
+ className?: string
31
+ defaultOpen?: boolean
32
+ /** Forces the pane closed (track→0, aria-hidden) without writing to the store — for transient route gates. */
33
+ disabled?: boolean
34
+ id: string
35
+ maxWidth?: WidthValue
36
+ minWidth?: WidthValue
37
+ resizable?: boolean
38
+ side: PaneSide
39
+ width?: WidthValue
40
+ }
41
+
42
+ export interface PaneMainProps {
43
+ children?: ReactNode
44
+ className?: string
45
+ }
46
+
47
+ export interface PaneShellProps {
48
+ children?: ReactNode
49
+ className?: string
50
+ style?: CSSProperties
51
+ }
52
+
53
+ interface CollectedPane {
54
+ defaultOpen: boolean
55
+ disabled: boolean
56
+ id: string
57
+ resizable: boolean
58
+ side: PaneSide
59
+ width: string
60
+ }
61
+
62
+ const DEFAULT_WIDTH = '16rem'
63
+ const DEFAULT_RESIZE_MIN_WIDTH = 160
64
+
65
+ const widthToCss = (value: WidthValue | undefined, fallback: string) =>
66
+ value === undefined ? fallback : typeof value === 'number' ? `${value}px` : value
67
+
68
+ const remPx = () =>
69
+ typeof window === 'undefined'
70
+ ? 16
71
+ : Number.parseFloat(window.getComputedStyle(document.documentElement).fontSize) || 16
72
+
73
+ // Resolves PaneProps.minWidth/maxWidth (number | "Npx" | "Nrem") to pixels for drag clamping.
74
+ function widthToPx(value: WidthValue | undefined) {
75
+ if (typeof value === 'number') {
76
+ return Number.isFinite(value) ? value : undefined
77
+ }
78
+
79
+ const match = value?.trim().match(/^(-?\d*\.?\d+)(px|rem)?$/)
80
+
81
+ if (!match) {
82
+ return undefined
83
+ }
84
+
85
+ return Number.parseFloat(match[1]) * (match[2] === 'rem' ? remPx() : 1)
86
+ }
87
+
88
+ function isRole(child: unknown, role: 'pane' | 'main'): child is ReactElement {
89
+ return isValidElement(child) && (child.type as PaneRoleMarker)?.__paneShellRole === role
90
+ }
91
+
92
+ function collectPanes(children: ReactNode) {
93
+ const left: CollectedPane[] = []
94
+ const right: CollectedPane[] = []
95
+ let mainCount = 0
96
+
97
+ Children.forEach(children, child => {
98
+ if (isRole(child, 'main')) {
99
+ mainCount++
100
+
101
+ return
102
+ }
103
+
104
+ if (!isRole(child, 'pane')) {
105
+ return
106
+ }
107
+
108
+ const props = child.props as PaneProps
109
+
110
+ const entry: CollectedPane = {
111
+ defaultOpen: props.defaultOpen ?? true,
112
+ disabled: props.disabled ?? false,
113
+ id: props.id,
114
+ resizable: props.resizable ?? false,
115
+ side: props.side,
116
+ width: widthToCss(props.width, DEFAULT_WIDTH)
117
+ }
118
+
119
+ ;(props.side === 'left' ? left : right).push(entry)
120
+ })
121
+
122
+ return { left, mainCount, right }
123
+ }
124
+
125
+ function trackForPane(pane: CollectedPane, states: Record<string, { open: boolean; widthOverride?: number }>) {
126
+ const stateOpen = states[pane.id]?.open ?? pane.defaultOpen
127
+ const open = !pane.disabled && stateOpen
128
+
129
+ if (!open) {
130
+ return { open: false, track: '0px' }
131
+ }
132
+
133
+ const override = pane.resizable ? states[pane.id]?.widthOverride : undefined
134
+
135
+ return { open: true, track: override !== undefined ? `${override}px` : pane.width }
136
+ }
137
+
138
+ export function PaneShell({ children, className, style }: PaneShellProps) {
139
+ const paneStates = useStore($paneStates)
140
+ const { left, mainCount, right } = useMemo(() => collectPanes(children), [children])
141
+
142
+ if (import.meta.env.DEV && mainCount > 1) {
143
+ console.warn('[PaneShell] expected at most one <PaneMain>, got', mainCount)
144
+ }
145
+
146
+ const ctxValue = useMemo(() => {
147
+ const paneById = new Map<string, PaneSlot>()
148
+ const tracks: string[] = []
149
+ const cssVars: Record<string, string> = {}
150
+ let column = 1
151
+
152
+ for (const pane of left) {
153
+ const { open, track } = trackForPane(pane, paneStates)
154
+ tracks.push(track)
155
+ paneById.set(pane.id, { column, open, side: 'left' })
156
+ cssVars[`--pane-${pane.id}-width`] = track
157
+ column++
158
+ }
159
+
160
+ tracks.push('minmax(0,1fr)')
161
+ const mainColumn = column++
162
+
163
+ for (const pane of right) {
164
+ const { open, track } = trackForPane(pane, paneStates)
165
+ tracks.push(track)
166
+ paneById.set(pane.id, { column, open, side: 'right' })
167
+ cssVars[`--pane-${pane.id}-width`] = track
168
+ column++
169
+ }
170
+
171
+ return { cssVars, gridTemplate: tracks.join(' '), mainColumn, paneById } satisfies PaneShellContextValue & {
172
+ cssVars: Record<string, string>
173
+ gridTemplate: string
174
+ }
175
+ }, [left, paneStates, right])
176
+
177
+ const composedStyle = useMemo<CSSProperties>(
178
+ () => ({ ...ctxValue.cssVars, ...style, gridTemplateColumns: ctxValue.gridTemplate }),
179
+ [ctxValue.cssVars, ctxValue.gridTemplate, style]
180
+ )
181
+
182
+ return (
183
+ <PaneShellContext.Provider value={{ mainColumn: ctxValue.mainColumn, paneById: ctxValue.paneById }}>
184
+ <div className={cn('relative grid h-full min-h-0', className)} style={composedStyle}>
185
+ {children}
186
+ </div>
187
+ </PaneShellContext.Provider>
188
+ )
189
+ }
190
+
191
+ export function Pane({
192
+ children,
193
+ className,
194
+ defaultOpen = true,
195
+ disabled = false,
196
+ id,
197
+ maxWidth,
198
+ minWidth,
199
+ resizable = false
200
+ }: PaneProps) {
201
+ const ctx = useContext(PaneShellContext)
202
+ const registered = useRef(false)
203
+ const paneRef = useRef<HTMLDivElement | null>(null)
204
+
205
+ useEffect(() => {
206
+ if (registered.current) {
207
+ return
208
+ }
209
+
210
+ registered.current = true
211
+ ensurePaneRegistered(id, { open: defaultOpen })
212
+ }, [defaultOpen, id])
213
+
214
+ const slot = ctx?.paneById.get(id)
215
+ const open = Boolean(slot?.open && !disabled)
216
+ const canResize = open && resizable
217
+ const lo = widthToPx(minWidth) ?? DEFAULT_RESIZE_MIN_WIDTH
218
+ const hi = widthToPx(maxWidth) ?? Number.POSITIVE_INFINITY
219
+ const side = slot?.side ?? 'left'
220
+
221
+ const startResize = useCallback(
222
+ (event: ReactPointerEvent<HTMLDivElement>) => {
223
+ const paneWidth = paneRef.current?.getBoundingClientRect().width ?? 0
224
+
225
+ if (!canResize || paneWidth <= 0) {
226
+ return
227
+ }
228
+
229
+ event.preventDefault()
230
+
231
+ const handle = event.currentTarget
232
+ const { pointerId, clientX: startX } = event
233
+ const dir = side === 'left' ? 1 : -1
234
+ const restoreCursor = document.body.style.cursor
235
+ const restoreSelect = document.body.style.userSelect
236
+
237
+ handle.setPointerCapture?.(pointerId)
238
+ document.body.style.cursor = 'col-resize'
239
+ document.body.style.userSelect = 'none'
240
+
241
+ const onMove = (e: PointerEvent) => {
242
+ const next = paneWidth + (e.clientX - startX) * dir
243
+ setPaneWidthOverride(id, Math.round(Math.min(hi, Math.max(lo, next))))
244
+ }
245
+
246
+ const cleanup = () => {
247
+ document.body.style.cursor = restoreCursor
248
+ document.body.style.userSelect = restoreSelect
249
+ handle.releasePointerCapture?.(pointerId)
250
+ window.removeEventListener('pointermove', onMove, true)
251
+ window.removeEventListener('pointerup', cleanup, true)
252
+ window.removeEventListener('pointercancel', cleanup, true)
253
+ window.removeEventListener('blur', cleanup)
254
+ }
255
+
256
+ window.addEventListener('pointermove', onMove, true)
257
+ window.addEventListener('pointerup', cleanup, true)
258
+ window.addEventListener('pointercancel', cleanup, true)
259
+ window.addEventListener('blur', cleanup)
260
+ },
261
+ [canResize, hi, id, lo, side]
262
+ )
263
+
264
+ if (!ctx) {
265
+ if (import.meta.env.DEV) {
266
+ console.warn(`[Pane:${id}] must be rendered inside <PaneShell>`)
267
+ }
268
+
269
+ return null
270
+ }
271
+
272
+ if (!slot) {
273
+ return null
274
+ }
275
+
276
+ return (
277
+ <div
278
+ aria-hidden={!open}
279
+ className={cn('relative row-start-1 min-w-0 overflow-hidden', !open && 'pointer-events-none', className)}
280
+ data-pane-id={id}
281
+ data-pane-open={open ? 'true' : 'false'}
282
+ data-pane-side={slot.side}
283
+ ref={paneRef}
284
+ style={{ gridColumn: `${slot.column} / ${slot.column + 1}` }}
285
+ >
286
+ {canResize && (
287
+ <div
288
+ aria-label={`Resize ${id}`}
289
+ aria-orientation="vertical"
290
+ className={cn(
291
+ 'group absolute bottom-0 top-0 z-20 w-1 cursor-col-resize [-webkit-app-region:no-drag]',
292
+ slot.side === 'left' ? 'right-0 translate-x-1/2' : 'left-0 -translate-x-1/2'
293
+ )}
294
+ onPointerDown={startResize}
295
+ role="separator"
296
+ tabIndex={0}
297
+ >
298
+ <span className="absolute inset-y-0 left-1/2 w-(--vscode-sash-hover-size,0.25rem) -translate-x-1/2 bg-(--ui-sash-hover-border) opacity-0 transition-opacity duration-100 group-hover:opacity-100 group-focus-visible:opacity-100" />
299
+ </div>
300
+ )}
301
+ {children}
302
+ </div>
303
+ )
304
+ }
305
+
306
+ ;(Pane as unknown as PaneRoleMarker).__paneShellRole = 'pane'
307
+
308
+ export function PaneMain({ children, className }: PaneMainProps) {
309
+ const ctx = useContext(PaneShellContext)
310
+
311
+ if (!ctx) {
312
+ if (import.meta.env.DEV) {
313
+ console.warn('[PaneMain] must be rendered inside <PaneShell>')
314
+ }
315
+
316
+ return null
317
+ }
318
+
319
+ return (
320
+ <div
321
+ className={cn('row-start-1 flex min-h-0 min-w-0 flex-col overflow-hidden', className)}
322
+ data-pane-main="true"
323
+ style={{ gridColumn: `${ctx.mainColumn} / ${ctx.mainColumn + 1}` }}
324
+ >
325
+ {children}
326
+ </div>
327
+ )
328
+ }
329
+
330
+ ;(PaneMain as unknown as PaneRoleMarker).__paneShellRole = 'main'
@@ -0,0 +1,234 @@
1
+ 'use client'
2
+
3
+ import { useStore } from '@nanostores/react'
4
+ import { type FormEvent, useCallback, useEffect, useState } from 'react'
5
+
6
+ import { Button } from '@/components/ui/button'
7
+ import {
8
+ Dialog,
9
+ DialogContent,
10
+ DialogDescription,
11
+ DialogFooter,
12
+ DialogHeader,
13
+ DialogTitle
14
+ } from '@/components/ui/dialog'
15
+ import { Input } from '@/components/ui/input'
16
+ import { useI18n } from '@/i18n'
17
+ import { triggerHaptic } from '@/lib/haptics'
18
+ import { KeyRound, Loader2, Lock } from '@/lib/icons'
19
+ import { $gateway } from '@/store/gateway'
20
+ import { notifyError } from '@/store/notifications'
21
+ import { $secretRequest, $sudoRequest, clearSecretRequest, clearSudoRequest } from '@/store/prompts'
22
+
23
+ // Renders the modal mid-turn prompts the gateway raises and waits on: sudo
24
+ // password and skill secret capture. (Dangerous-command / execute_code approval
25
+ // is rendered INLINE on the pending tool row instead — see
26
+ // components/assistant-ui/tool-approval.tsx — so it reads like an inline "Run"
27
+ // affordance rather than a blocking modal.) Each Python-side caller blocks the
28
+ // agent thread until the matching `*.respond` RPC lands; without a renderer the
29
+ // agent stalls until its timeout and the tool is BLOCKED (the bug this fixes —
30
+ // desktop handled clarify.request but not these). Any close path (Esc, backdrop
31
+ // click) funnels through Radix's single `onOpenChange(false)` and maps to a
32
+ // refusal, so silence is never mistaken for consent, matching the TUI. We
33
+ // deliberately do NOT add onEscapeKeyDown / onInteractOutside handlers — they'd
34
+ // fire a second `*.respond` alongside onOpenChange (double-send) or block the
35
+ // backdrop-dismiss path.
36
+
37
+ function SudoDialog() {
38
+ const { t } = useI18n()
39
+ const copy = t.prompts
40
+ const request = useStore($sudoRequest)
41
+ const gateway = useStore($gateway)
42
+ const [password, setPassword] = useState('')
43
+ const [submitting, setSubmitting] = useState(false)
44
+
45
+ useEffect(() => {
46
+ setPassword('')
47
+ setSubmitting(false)
48
+ }, [request?.requestId])
49
+
50
+ const send = useCallback(
51
+ async (value: string) => {
52
+ if (!request) {
53
+ return
54
+ }
55
+
56
+ if (!gateway) {
57
+ notifyError(new Error(copy.gatewayDisconnected), copy.sudoSendFailed)
58
+
59
+ return
60
+ }
61
+
62
+ setSubmitting(true)
63
+
64
+ try {
65
+ await gateway.request<{ status?: string }>('sudo.respond', {
66
+ password: value,
67
+ request_id: request.requestId
68
+ })
69
+ triggerHaptic('submit')
70
+ clearSudoRequest(request.sessionId, request.requestId)
71
+ } catch (error) {
72
+ notifyError(error, copy.sudoSendFailed)
73
+ setSubmitting(false)
74
+ }
75
+ },
76
+ [copy.gatewayDisconnected, copy.sudoSendFailed, gateway, request]
77
+ )
78
+
79
+ // Cancel → empty password. The backend treats an empty sudo response as a
80
+ // failed sudo (no command runs), so closing the dialog is a safe refusal.
81
+ const onOpenChange = useCallback(
82
+ (open: boolean) => {
83
+ if (!open && !submitting && request) {
84
+ void send('')
85
+ }
86
+ },
87
+ [request, send, submitting]
88
+ )
89
+
90
+ const onSubmit = useCallback(
91
+ (event: FormEvent<HTMLFormElement>) => {
92
+ event.preventDefault()
93
+ void send(password)
94
+ },
95
+ [password, send]
96
+ )
97
+
98
+ if (!request) {
99
+ return null
100
+ }
101
+
102
+ return (
103
+ <Dialog onOpenChange={onOpenChange} open>
104
+ <DialogContent showCloseButton={false}>
105
+ <DialogHeader>
106
+ <DialogTitle icon={Lock}>{copy.sudoTitle}</DialogTitle>
107
+ <DialogDescription>{copy.sudoDesc}</DialogDescription>
108
+ </DialogHeader>
109
+
110
+ <form className="grid gap-3" onSubmit={onSubmit}>
111
+ <Input
112
+ autoFocus
113
+ disabled={submitting}
114
+ onChange={event => setPassword(event.target.value)}
115
+ placeholder={copy.sudoPlaceholder}
116
+ type="password"
117
+ value={password}
118
+ />
119
+ <DialogFooter>
120
+ <Button disabled={submitting} onClick={() => void send('')} type="button" variant="ghost">
121
+ {t.common.cancel}
122
+ </Button>
123
+ <Button disabled={submitting} type="submit">
124
+ {submitting ? <Loader2 className="size-3.5 animate-spin" /> : t.common.send}
125
+ </Button>
126
+ </DialogFooter>
127
+ </form>
128
+ </DialogContent>
129
+ </Dialog>
130
+ )
131
+ }
132
+
133
+ function SecretDialog() {
134
+ const { t } = useI18n()
135
+ const copy = t.prompts
136
+ const request = useStore($secretRequest)
137
+ const gateway = useStore($gateway)
138
+ const [value, setValue] = useState('')
139
+ const [submitting, setSubmitting] = useState(false)
140
+
141
+ useEffect(() => {
142
+ setValue('')
143
+ setSubmitting(false)
144
+ }, [request?.requestId])
145
+
146
+ const send = useCallback(
147
+ async (secret: string) => {
148
+ if (!request) {
149
+ return
150
+ }
151
+
152
+ if (!gateway) {
153
+ notifyError(new Error(copy.gatewayDisconnected), copy.secretSendFailed)
154
+
155
+ return
156
+ }
157
+
158
+ setSubmitting(true)
159
+
160
+ try {
161
+ await gateway.request<{ status?: string }>('secret.respond', {
162
+ request_id: request.requestId,
163
+ value: secret
164
+ })
165
+ triggerHaptic('submit')
166
+ clearSecretRequest(request.sessionId, request.requestId)
167
+ } catch (error) {
168
+ notifyError(error, copy.secretSendFailed)
169
+ setSubmitting(false)
170
+ }
171
+ },
172
+ [copy.gatewayDisconnected, copy.secretSendFailed, gateway, request]
173
+ )
174
+
175
+ const onOpenChange = useCallback(
176
+ (open: boolean) => {
177
+ if (!open && !submitting && request) {
178
+ void send('')
179
+ }
180
+ },
181
+ [request, send, submitting]
182
+ )
183
+
184
+ const onSubmit = useCallback(
185
+ (event: FormEvent<HTMLFormElement>) => {
186
+ event.preventDefault()
187
+ void send(value)
188
+ },
189
+ [send, value]
190
+ )
191
+
192
+ if (!request) {
193
+ return null
194
+ }
195
+
196
+ return (
197
+ <Dialog onOpenChange={onOpenChange} open>
198
+ <DialogContent showCloseButton={false}>
199
+ <DialogHeader>
200
+ <DialogTitle icon={KeyRound}>{request.envVar || copy.secretTitle}</DialogTitle>
201
+ <DialogDescription>{request.prompt || copy.secretDesc}</DialogDescription>
202
+ </DialogHeader>
203
+
204
+ <form className="grid gap-3" onSubmit={onSubmit}>
205
+ <Input
206
+ autoFocus
207
+ disabled={submitting}
208
+ onChange={event => setValue(event.target.value)}
209
+ placeholder={request.envVar || copy.secretPlaceholder}
210
+ type="password"
211
+ value={value}
212
+ />
213
+ <DialogFooter>
214
+ <Button disabled={submitting} onClick={() => void send('')} type="button" variant="ghost">
215
+ {t.common.cancel}
216
+ </Button>
217
+ <Button disabled={submitting || !value} type="submit">
218
+ {submitting ? <Loader2 className="size-3.5 animate-spin" /> : t.common.send}
219
+ </Button>
220
+ </DialogFooter>
221
+ </form>
222
+ </DialogContent>
223
+ </Dialog>
224
+ )
225
+ }
226
+
227
+ export function PromptOverlays() {
228
+ return (
229
+ <>
230
+ <SudoDialog />
231
+ <SecretDialog />
232
+ </>
233
+ )
234
+ }
@@ -0,0 +1,108 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { Dialog as DialogPrimitive } from 'radix-ui'
3
+ import { useEffect, useMemo, useState } from 'react'
4
+
5
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
6
+ import { listAllProfileSessions } from '@/nastech'
7
+ import { useI18n } from '@/i18n'
8
+ import { sessionTitle } from '@/lib/chat-runtime'
9
+ import { Check, MessageCircle } from '@/lib/icons'
10
+ import { cn } from '@/lib/utils'
11
+
12
+ interface SessionPickerDialogProps {
13
+ /** Stored id of the session currently open, so it can be flagged in the list. */
14
+ activeStoredSessionId?: string | null
15
+ onOpenChange: (open: boolean) => void
16
+ onResume: (storedSessionId: string) => void
17
+ open: boolean
18
+ }
19
+
20
+ /**
21
+ * Desktop equivalent of the TUI's sessions overlay (`/resume`, `/sessions`,
22
+ * `/switch`): a focused, type-to-filter list of recent sessions that resumes
23
+ * the picked one. Mirrors the command palette's cmdk surface but scoped to
24
+ * sessions only, so `/resume` feels first-class instead of falling through to
25
+ * the headless slash worker (which can't render the picker).
26
+ */
27
+ export function SessionPickerDialog({
28
+ activeStoredSessionId,
29
+ onOpenChange,
30
+ onResume,
31
+ open
32
+ }: SessionPickerDialogProps) {
33
+ const { t } = useI18n()
34
+ const [search, setSearch] = useState('')
35
+
36
+ const sessionsQuery = useQuery({
37
+ enabled: open,
38
+ queryFn: () => listAllProfileSessions(200, 1, 'exclude'),
39
+ queryKey: ['session-picker', 'sessions']
40
+ })
41
+
42
+ useEffect(() => {
43
+ if (!open) {
44
+ setSearch('')
45
+ }
46
+ }, [open])
47
+
48
+ const sessions = useMemo(() => sessionsQuery.data?.sessions ?? [], [sessionsQuery.data])
49
+
50
+ return (
51
+ <DialogPrimitive.Root onOpenChange={onOpenChange} open={open}>
52
+ <DialogPrimitive.Portal>
53
+ <DialogPrimitive.Overlay className="fixed inset-0 z-[200] bg-black/15 backdrop-blur-[1px] data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0" />
54
+ <DialogPrimitive.Content
55
+ aria-describedby={undefined}
56
+ className="fixed left-1/2 top-[14vh] z-[210] w-[min(40rem,calc(100vw-2rem))] -translate-x-1/2 overflow-hidden rounded-xl border border-(--ui-stroke-secondary) bg-(--ui-chat-bubble-background) shadow-lg duration-150 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-top-2 data-[state=open]:zoom-in-95"
57
+ >
58
+ <DialogPrimitive.Title className="sr-only">{t.commandCenter.sections.sessions}</DialogPrimitive.Title>
59
+ <Command className="bg-transparent" loop>
60
+ <CommandInput
61
+ onValueChange={setSearch}
62
+ placeholder={t.commandCenter.searchPlaceholder}
63
+ value={search}
64
+ />
65
+ <CommandList className="max-h-[min(24rem,60vh)]">
66
+ <CommandEmpty>{t.commandCenter.noResults}</CommandEmpty>
67
+ <CommandGroup
68
+ className="**:[[cmdk-group-heading]]:uppercase **:[[cmdk-group-heading]]:tracking-wider **:[[cmdk-group-heading]]:text-[0.6875rem] **:[[cmdk-group-heading]]:text-muted-foreground/70"
69
+ heading={t.commandCenter.sections.sessions}
70
+ >
71
+ {sessions.map(session => {
72
+ const title = sessionTitle(session)
73
+ const preview = session.preview?.trim()
74
+
75
+ return (
76
+ <CommandItem
77
+ className="gap-2.5"
78
+ key={session.id}
79
+ onSelect={() => {
80
+ onResume(session.id)
81
+ onOpenChange(false)
82
+ }}
83
+ value={`${title} ${preview ?? ''} ${session.id}`}
84
+ >
85
+ <MessageCircle className="size-4 shrink-0 text-muted-foreground" />
86
+ <span className="flex min-w-0 flex-col leading-snug">
87
+ <span className="truncate">{title}</span>
88
+ {preview ? (
89
+ <span className="truncate text-xs text-muted-foreground/70">{preview}</span>
90
+ ) : null}
91
+ </span>
92
+ <Check
93
+ className={cn(
94
+ 'ml-auto size-4 shrink-0 text-foreground',
95
+ session.id !== activeStoredSessionId && 'invisible'
96
+ )}
97
+ />
98
+ </CommandItem>
99
+ )
100
+ })}
101
+ </CommandGroup>
102
+ </CommandList>
103
+ </Command>
104
+ </DialogPrimitive.Content>
105
+ </DialogPrimitive.Portal>
106
+ </DialogPrimitive.Root>
107
+ )
108
+ }
@@ -0,0 +1,26 @@
1
+ import type { ComponentProps } from 'react'
2
+
3
+ import { cn } from '@/lib/utils'
4
+
5
+ export type StatusTone = 'good' | 'muted' | 'warn' | 'bad'
6
+
7
+ const TONE_BG: Record<StatusTone, string> = {
8
+ good: 'bg-primary',
9
+ muted: 'bg-muted-foreground/40',
10
+ warn: 'bg-amber-500',
11
+ bad: 'bg-destructive'
12
+ }
13
+
14
+ interface StatusDotProps extends ComponentProps<'span'> {
15
+ tone: StatusTone
16
+ }
17
+
18
+ export function StatusDot({ className, tone, ...props }: StatusDotProps) {
19
+ return (
20
+ <span
21
+ aria-hidden="true"
22
+ className={cn('inline-block size-1.5 rounded-full', TONE_BG[tone], className)}
23
+ {...props}
24
+ />
25
+ )
26
+ }