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,720 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * bootstrap-runner.cjs
5
+ *
6
+ * Drives apps/desktop's first-launch install of NasTech Agent by spawning
7
+ * scripts/install.ps1 stage-by-stage and streaming progress events back to
8
+ * the renderer.
9
+ *
10
+ * Wired from electron/main.cjs:
11
+ * const { runBootstrap } = require('./bootstrap-runner.cjs')
12
+ * const result = await runBootstrap({
13
+ * installStamp, // INSTALL_STAMP from main.cjs (may be null in dev)
14
+ * activeRoot, // ACTIVE_NASTECH_ROOT
15
+ * sourceRepoRoot, // SOURCE_REPO_ROOT (for dev install.ps1 lookup)
16
+ * nastechHome, // NASTECH_HOME
17
+ * logRoot, // NASTECH_HOME/logs
18
+ * emit: ev => {...} // event sink (sender.send or similar)
19
+ * })
20
+ *
21
+ * Emits events with shape:
22
+ * { type: 'manifest', stages: [{name, title, category, needs_user_input}, ...] }
23
+ * { type: 'stage', name, state: 'running'|'succeeded'|'skipped'|'failed',
24
+ * json?, durationMs?, error? }
25
+ * { type: 'log', stage?, line, stream: 'stdout'|'stderr' } // raw line from install.ps1
26
+ * { type: 'complete', marker: <written marker payload> }
27
+ * { type: 'failed', stage?, error } // bootstrap aborted
28
+ *
29
+ * Resolves with the same shape as the final 'complete' or 'failed' event so
30
+ * callers can await either way.
31
+ *
32
+ * NOT implemented yet (deferred to Phase 1E / 1F):
33
+ * - User-facing retry / cancel from the renderer (event channels exist;
34
+ * no UI consumes them yet)
35
+ */
36
+
37
+ const fs = require('node:fs')
38
+ const fsp = require('node:fs/promises')
39
+ const path = require('node:path')
40
+ const https = require('node:https')
41
+ const { spawn } = require('node:child_process')
42
+
43
+ const STAMP_COMMIT_RE = /^[0-9a-f]{7,40}$/i
44
+
45
+ // Stages flagged needs_user_input=true in the manifest are skipped by the
46
+ // runner (passed -NonInteractive to install.ps1, which the install script
47
+ // itself handles by emitting skipped=true frames). The renderer / 1E onboarding
48
+ // overlay takes over for those concerns (API keys, model, persona, gateway).
49
+ // We let install.ps1's own -NonInteractive logic drive this rather than
50
+ // filtering client-side -- single source of truth.
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // install.ps1 source resolution
54
+ // ---------------------------------------------------------------------------
55
+
56
+ function installScriptName() {
57
+ return process.platform === 'win32' ? 'install.ps1' : 'install.sh'
58
+ }
59
+
60
+ function installScriptKind() {
61
+ return process.platform === 'win32' ? 'powershell' : 'posix'
62
+ }
63
+
64
+ function resolveLocalInstallScript(sourceRepoRoot) {
65
+ if (!sourceRepoRoot) return null
66
+ const candidate = path.join(sourceRepoRoot, 'scripts', installScriptName())
67
+ try {
68
+ fs.accessSync(candidate, fs.constants.R_OK)
69
+ return candidate
70
+ } catch {
71
+ return null
72
+ }
73
+ }
74
+
75
+ function bootstrapCacheDir(nastechHome) {
76
+ return path.join(nastechHome, 'bootstrap-cache')
77
+ }
78
+
79
+ // The install.sh / install.ps1 that ships inside the already-installed agent
80
+ // checkout under ~/.nastech/nastech-agent. Used as a last-resort fallback when
81
+ // the pinned commit can't be fetched from GitHub (e.g. a locally-built desktop
82
+ // app stamped to an unpushed HEAD).
83
+ function installedAgentInstallScript(nastechHome) {
84
+ if (!nastechHome) return null
85
+ const candidate = path.join(nastechHome, 'nastech-agent', 'scripts', installScriptName())
86
+ try {
87
+ fs.accessSync(candidate, fs.constants.R_OK)
88
+ return candidate
89
+ } catch {
90
+ return null
91
+ }
92
+ }
93
+
94
+ function cachedScriptPath(nastechHome, commit) {
95
+ return path.join(bootstrapCacheDir(nastechHome), `install-${commit}.${process.platform === 'win32' ? 'ps1' : 'sh'}`)
96
+ }
97
+
98
+ function downloadInstallScript(commit, destPath) {
99
+ // Fetch from GitHub raw at the pinned commit. The raw URL with a SHA
100
+ // is immutable (unlike a branch ref), so we don't need integrity
101
+ // verification beyond "did the file we wrote pass a syntax probe."
102
+ const scriptName = installScriptName()
103
+ const url = `https://raw.githubusercontent.com/nastech-ai/NasTech-Agent/${commit}/scripts/${scriptName}`
104
+ return new Promise((resolve, reject) => {
105
+ fs.mkdirSync(path.dirname(destPath), { recursive: true })
106
+ const tmpPath = destPath + '.tmp'
107
+ const out = fs.createWriteStream(tmpPath)
108
+ https
109
+ .get(url, res => {
110
+ if (res.statusCode === 301 || res.statusCode === 302) {
111
+ // GitHub raw shouldn't redirect for a SHA URL, but follow once
112
+ // defensively.
113
+ out.close()
114
+ fs.unlinkSync(tmpPath)
115
+ https
116
+ .get(res.headers.location, res2 => {
117
+ if (res2.statusCode !== 200) {
118
+ reject(
119
+ new Error(
120
+ `Failed to download ${scriptName}: HTTP ${res2.statusCode} from redirect ${res.headers.location}`
121
+ )
122
+ )
123
+ return
124
+ }
125
+ const out2 = fs.createWriteStream(tmpPath)
126
+ res2.pipe(out2)
127
+ out2.on('finish', () => {
128
+ out2.close()
129
+ fs.renameSync(tmpPath, destPath)
130
+ resolve(destPath)
131
+ })
132
+ out2.on('error', reject)
133
+ })
134
+ .on('error', reject)
135
+ return
136
+ }
137
+ if (res.statusCode !== 200) {
138
+ out.close()
139
+ try {
140
+ fs.unlinkSync(tmpPath)
141
+ } catch {
142
+ void 0
143
+ }
144
+ reject(new Error(`Failed to download ${scriptName}: HTTP ${res.statusCode} from ${url}`))
145
+ return
146
+ }
147
+ res.pipe(out)
148
+ out.on('finish', () => {
149
+ out.close()
150
+ fs.renameSync(tmpPath, destPath)
151
+ resolve(destPath)
152
+ })
153
+ out.on('error', err => {
154
+ try {
155
+ fs.unlinkSync(tmpPath)
156
+ } catch {
157
+ void 0
158
+ }
159
+ reject(err)
160
+ })
161
+ })
162
+ .on('error', err => {
163
+ try {
164
+ fs.unlinkSync(tmpPath)
165
+ } catch {
166
+ void 0
167
+ }
168
+ reject(err)
169
+ })
170
+ })
171
+ }
172
+
173
+ async function resolveInstallScript({ installStamp, sourceRepoRoot, nastechHome, emit, _download = downloadInstallScript }) {
174
+ // 1. Dev shortcut: prefer a local checkout's installer so we can iterate
175
+ // without pushing. SOURCE_REPO_ROOT comes from main.cjs (path.resolve
176
+ // of APP_ROOT/../..).
177
+ const localScript = resolveLocalInstallScript(sourceRepoRoot)
178
+ if (localScript) {
179
+ emit({ type: 'log', line: `[bootstrap] using local ${installScriptName()} at ${localScript}` })
180
+ return { path: localScript, source: 'local', kind: installScriptKind() }
181
+ }
182
+
183
+ // 2. Packaged path: download from GitHub at the pinned commit (1B's stamp).
184
+ if (!installStamp || !installStamp.commit || !STAMP_COMMIT_RE.test(installStamp.commit)) {
185
+ throw new Error(
186
+ `Cannot resolve ${installScriptName()}: no SOURCE_REPO_ROOT and no install stamp. ` +
187
+ 'This packaged build was produced without a valid build-time stamp.'
188
+ )
189
+ }
190
+
191
+ const cached = cachedScriptPath(nastechHome, installStamp.commit)
192
+ try {
193
+ await fsp.access(cached, fs.constants.R_OK)
194
+ emit({
195
+ type: 'log',
196
+ line: `[bootstrap] using cached ${installScriptName()} for ${installStamp.commit.slice(0, 12)}`
197
+ })
198
+ return { path: cached, source: 'cache', commit: installStamp.commit, kind: installScriptKind() }
199
+ } catch {
200
+ // not cached; download
201
+ }
202
+
203
+ emit({
204
+ type: 'log',
205
+ line: `[bootstrap] fetching ${installScriptName()} for ${installStamp.commit.slice(0, 12)} from GitHub`
206
+ })
207
+ try {
208
+ await _download(installStamp.commit, cached)
209
+ emit({ type: 'log', line: `[bootstrap] saved to ${cached}` })
210
+ return { path: cached, source: 'download', commit: installStamp.commit, kind: installScriptKind() }
211
+ } catch (err) {
212
+ // The pinned commit may not be fetchable from GitHub -- most commonly a
213
+ // locally-built desktop app stamped to an unpushed HEAD (see
214
+ // write-build-stamp.cjs fromLocalGit). Fall back to the installer that
215
+ // ships inside the already-installed agent checkout so dev/self-builds can
216
+ // still bootstrap instead of dying with a fatal 404.
217
+ const installed = installedAgentInstallScript(nastechHome)
218
+ if (installed) {
219
+ emit({
220
+ type: 'log',
221
+ line:
222
+ `[bootstrap] GitHub fetch failed (${err.message}); ` +
223
+ `falling back to installed agent ${installScriptName()} at ${installed}`
224
+ })
225
+ try {
226
+ fs.mkdirSync(path.dirname(cached), { recursive: true })
227
+ fs.copyFileSync(installed, cached)
228
+ return { path: cached, source: 'installed-agent', commit: installStamp.commit, kind: installScriptKind() }
229
+ } catch {
230
+ // Cache copy failed (read-only FS, etc.) -- use the source path directly.
231
+ return { path: installed, source: 'installed-agent', commit: installStamp.commit, kind: installScriptKind() }
232
+ }
233
+ }
234
+ throw err
235
+ }
236
+ }
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // powershell wrapper
240
+ // ---------------------------------------------------------------------------
241
+
242
+ // Canonical PowerShell 5.1 location under a Windows root (%SystemRoot%).
243
+ function powershellUnderRoot(root) {
244
+ return path.join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')
245
+ }
246
+
247
+ // Resolve the PowerShell interpreter to spawn.
248
+ //
249
+ // Spawning bare 'powershell.exe' trusts PATH to contain
250
+ // %SystemRoot%\System32\WindowsPowerShell\v1.0. On machines whose PATH was
251
+ // trimmed, truncated, or stored as a non-expanding REG_SZ (so %SystemRoot%
252
+ // never expands), that lookup fails and the spawn dies with ENOENT before
253
+ // install.ps1 ever runs — the installer stalls at "0 of 0 steps". Resolve by
254
+ // absolute path first, then fall back to PATH (powershell 5.1, then pwsh 7),
255
+ // then a bare name as a last resort.
256
+ function resolveWindowsPowerShell() {
257
+ for (const v of ['SystemRoot', 'windir']) {
258
+ const root = process.env[v]
259
+ if (root) {
260
+ const candidate = powershellUnderRoot(root)
261
+ try {
262
+ if (fs.statSync(candidate).isFile()) return candidate
263
+ } catch {
264
+ void 0
265
+ }
266
+ }
267
+ }
268
+ const pathDirs = (process.env.PATH || process.env.Path || '').split(path.delimiter).filter(Boolean)
269
+ for (const exe of ['powershell.exe', 'pwsh.exe']) {
270
+ for (const dir of pathDirs) {
271
+ const candidate = path.join(dir, exe)
272
+ try {
273
+ if (fs.statSync(candidate).isFile()) return candidate
274
+ } catch {
275
+ void 0
276
+ }
277
+ }
278
+ }
279
+ return 'powershell.exe'
280
+ }
281
+
282
+ function spawnPowerShell(scriptPath, args, { emit, stageName, abortSignal, nastechHome } = {}) {
283
+ return new Promise((resolve, reject) => {
284
+ const ps = process.platform === 'win32' ? resolveWindowsPowerShell() : 'pwsh'
285
+ const fullArgs = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', scriptPath, ...args]
286
+
287
+ const child = spawn(ps, fullArgs, {
288
+ stdio: ['ignore', 'pipe', 'pipe'],
289
+ env: {
290
+ ...process.env,
291
+ // Pass NASTECH_HOME through so install.ps1 respects the caller's
292
+ // choice rather than re-computing the default.
293
+ NASTECH_HOME: nastechHome || process.env.NASTECH_HOME || ''
294
+ }
295
+ })
296
+
297
+ let stdout = ''
298
+ let stderr = ''
299
+ let killed = false
300
+
301
+ const onAbort = () => {
302
+ killed = true
303
+ try {
304
+ child.kill('SIGTERM')
305
+ } catch {
306
+ void 0
307
+ }
308
+ }
309
+ if (abortSignal) {
310
+ if (abortSignal.aborted) {
311
+ onAbort()
312
+ } else {
313
+ abortSignal.addEventListener('abort', onAbort, { once: true })
314
+ }
315
+ }
316
+
317
+ child.stdout.setEncoding('utf8')
318
+ child.stderr.setEncoding('utf8')
319
+
320
+ // Stream stdout line-by-line so the renderer sees progress in real time.
321
+ let stdoutBuf = ''
322
+ child.stdout.on('data', chunk => {
323
+ stdout += chunk
324
+ stdoutBuf += chunk
325
+ let nl
326
+ while ((nl = stdoutBuf.indexOf('\n')) !== -1) {
327
+ const line = stdoutBuf.slice(0, nl).replace(/\r$/, '')
328
+ stdoutBuf = stdoutBuf.slice(nl + 1)
329
+ if (line) emit && emit({ type: 'log', stage: stageName, line, stream: 'stdout' })
330
+ }
331
+ })
332
+
333
+ let stderrBuf = ''
334
+ child.stderr.on('data', chunk => {
335
+ stderr += chunk
336
+ stderrBuf += chunk
337
+ let nl
338
+ while ((nl = stderrBuf.indexOf('\n')) !== -1) {
339
+ const line = stderrBuf.slice(0, nl).replace(/\r$/, '')
340
+ stderrBuf = stderrBuf.slice(nl + 1)
341
+ if (line) emit && emit({ type: 'log', stage: stageName, line, stream: 'stderr' })
342
+ }
343
+ })
344
+
345
+ child.on('error', err => {
346
+ if (abortSignal) abortSignal.removeEventListener('abort', onAbort)
347
+ reject(err)
348
+ })
349
+
350
+ child.on('close', (code, signal) => {
351
+ if (abortSignal) abortSignal.removeEventListener('abort', onAbort)
352
+ // Flush any trailing bytes
353
+ if (stdoutBuf) emit && emit({ type: 'log', stage: stageName, line: stdoutBuf, stream: 'stdout' })
354
+ if (stderrBuf) emit && emit({ type: 'log', stage: stageName, line: stderrBuf, stream: 'stderr' })
355
+ resolve({ stdout, stderr, code, signal, killed })
356
+ })
357
+ })
358
+ }
359
+
360
+ function spawnBash(scriptPath, args, { emit, stageName, abortSignal, nastechHome } = {}) {
361
+ return new Promise((resolve, reject) => {
362
+ const child = spawn('bash', [scriptPath, ...args], {
363
+ stdio: ['ignore', 'pipe', 'pipe'],
364
+ env: {
365
+ ...process.env,
366
+ NASTECH_HOME: nastechHome || process.env.NASTECH_HOME || ''
367
+ }
368
+ })
369
+
370
+ let stdout = ''
371
+ let stderr = ''
372
+ let killed = false
373
+
374
+ const onAbort = () => {
375
+ killed = true
376
+ try {
377
+ child.kill('SIGTERM')
378
+ } catch {
379
+ void 0
380
+ }
381
+ }
382
+ if (abortSignal) {
383
+ if (abortSignal.aborted) {
384
+ onAbort()
385
+ } else {
386
+ abortSignal.addEventListener('abort', onAbort, { once: true })
387
+ }
388
+ }
389
+
390
+ child.stdout.setEncoding('utf8')
391
+ child.stderr.setEncoding('utf8')
392
+
393
+ let stdoutBuf = ''
394
+ child.stdout.on('data', chunk => {
395
+ stdout += chunk
396
+ stdoutBuf += chunk
397
+ let nl
398
+ while ((nl = stdoutBuf.indexOf('\n')) !== -1) {
399
+ const line = stdoutBuf.slice(0, nl).replace(/\r$/, '')
400
+ stdoutBuf = stdoutBuf.slice(nl + 1)
401
+ if (line) emit && emit({ type: 'log', stage: stageName, line, stream: 'stdout' })
402
+ }
403
+ })
404
+
405
+ let stderrBuf = ''
406
+ child.stderr.on('data', chunk => {
407
+ stderr += chunk
408
+ stderrBuf += chunk
409
+ let nl
410
+ while ((nl = stderrBuf.indexOf('\n')) !== -1) {
411
+ const line = stderrBuf.slice(0, nl).replace(/\r$/, '')
412
+ stderrBuf = stderrBuf.slice(nl + 1)
413
+ if (line) emit && emit({ type: 'log', stage: stageName, line, stream: 'stderr' })
414
+ }
415
+ })
416
+
417
+ child.on('error', err => {
418
+ if (abortSignal) abortSignal.removeEventListener('abort', onAbort)
419
+ reject(err)
420
+ })
421
+
422
+ child.on('close', (code, signal) => {
423
+ if (abortSignal) abortSignal.removeEventListener('abort', onAbort)
424
+ if (stdoutBuf) emit && emit({ type: 'log', stage: stageName, line: stdoutBuf, stream: 'stdout' })
425
+ if (stderrBuf) emit && emit({ type: 'log', stage: stageName, line: stderrBuf, stream: 'stderr' })
426
+ resolve({ stdout, stderr, code, signal, killed })
427
+ })
428
+ })
429
+ }
430
+
431
+ // ---------------------------------------------------------------------------
432
+ // Manifest + stage dispatch
433
+ // ---------------------------------------------------------------------------
434
+
435
+ // Build the install.ps1 pin args (-Commit / -Branch) from the install-stamp
436
+ // so the repository stage clones the exact SHA the .exe was tested with
437
+ // instead of falling back to install.ps1's default ($Branch = "main").
438
+ function buildPinArgs(installStamp) {
439
+ const args = []
440
+ if (installStamp && installStamp.commit) {
441
+ args.push('-Commit', installStamp.commit)
442
+ }
443
+ if (installStamp && installStamp.branch) {
444
+ args.push('-Branch', installStamp.branch)
445
+ }
446
+ return args
447
+ }
448
+
449
+ function buildPosixPinArgs({ installStamp, activeRoot, nastechHome }) {
450
+ const args = ['--dir', activeRoot, '--nastech-home', nastechHome]
451
+ if (installStamp && installStamp.branch) {
452
+ args.push('--branch', installStamp.branch)
453
+ }
454
+ if (installStamp && installStamp.commit) {
455
+ args.push('--commit', installStamp.commit)
456
+ }
457
+ return args
458
+ }
459
+
460
+ async function fetchManifest({ scriptPath, installerKind, emit, nastechHome, activeRoot, installStamp }) {
461
+ const isPosix = installerKind === 'posix'
462
+ const args = isPosix
463
+ ? ['--manifest', ...buildPosixPinArgs({ installStamp, activeRoot, nastechHome })]
464
+ : ['-Manifest', ...buildPinArgs(installStamp)]
465
+ const result = await (isPosix ? spawnBash : spawnPowerShell)(scriptPath, args, {
466
+ emit,
467
+ stageName: '__manifest__',
468
+ nastechHome
469
+ })
470
+ if (result.code !== 0) {
471
+ throw new Error(
472
+ `${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest'} failed: exit ${result.code}\n${result.stderr || result.stdout}`
473
+ )
474
+ }
475
+ // The manifest is the LAST JSON line on stdout (install.ps1 may print
476
+ // banner / info lines first depending on Console.OutputEncoding effects).
477
+ // Find the last line that parses as JSON with a `stages` field.
478
+ const lines = result.stdout.split(/\r?\n/).filter(Boolean)
479
+ for (let i = lines.length - 1; i >= 0; i--) {
480
+ try {
481
+ const parsed = JSON.parse(lines[i])
482
+ if (parsed && Array.isArray(parsed.stages)) {
483
+ return parsed
484
+ }
485
+ } catch {
486
+ void 0
487
+ }
488
+ }
489
+ throw new Error(
490
+ `${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest'} produced no parseable JSON payload\n${result.stdout}`
491
+ )
492
+ }
493
+
494
+ // Parse the JSON result frame from a stage run. The protocol guarantees
495
+ // exactly one JSON line per stage in -Json or -Stage mode (post #27224 fix
496
+ // for the double-emit bug we addressed in the install.ps1 PR).
497
+ function parseStageResult(stdout) {
498
+ const lines = stdout.split(/\r?\n/).filter(Boolean)
499
+ for (let i = lines.length - 1; i >= 0; i--) {
500
+ try {
501
+ const parsed = JSON.parse(lines[i])
502
+ if (parsed && typeof parsed.ok === 'boolean' && typeof parsed.stage === 'string') {
503
+ return parsed
504
+ }
505
+ } catch {
506
+ void 0
507
+ }
508
+ }
509
+ return null
510
+ }
511
+
512
+ async function runStage({ scriptPath, installerKind, stage, emit, nastechHome, activeRoot, abortSignal, installStamp }) {
513
+ const startedAt = Date.now()
514
+ emit({ type: 'stage', name: stage.name, state: 'running' })
515
+
516
+ const isPosix = installerKind === 'posix'
517
+ const args = isPosix
518
+ ? [
519
+ '--stage',
520
+ stage.name,
521
+ '--non-interactive',
522
+ '--json',
523
+ ...buildPosixPinArgs({ installStamp, activeRoot, nastechHome })
524
+ ]
525
+ : ['-Stage', stage.name, '-NonInteractive', '-Json', ...buildPinArgs(installStamp)]
526
+ const result = await (isPosix ? spawnBash : spawnPowerShell)(scriptPath, args, {
527
+ emit,
528
+ stageName: stage.name,
529
+ abortSignal,
530
+ nastechHome
531
+ })
532
+
533
+ const durationMs = Date.now() - startedAt
534
+
535
+ if (result.killed) {
536
+ const ev = { type: 'stage', name: stage.name, state: 'failed', durationMs, error: 'cancelled by user' }
537
+ emit(ev)
538
+ return ev
539
+ }
540
+
541
+ const json = parseStageResult(result.stdout)
542
+
543
+ if (!json) {
544
+ const ev = {
545
+ type: 'stage',
546
+ name: stage.name,
547
+ state: 'failed',
548
+ durationMs,
549
+ error: `${isPosix ? 'install.sh --stage' : 'install.ps1 -Stage'} ${stage.name} produced no JSON result frame (exit=${result.code})`,
550
+ json: null
551
+ }
552
+ emit(ev)
553
+ return ev
554
+ }
555
+
556
+ if (json.ok && json.skipped) {
557
+ const ev = { type: 'stage', name: stage.name, state: 'skipped', durationMs, json }
558
+ emit(ev)
559
+ return ev
560
+ }
561
+ if (json.ok) {
562
+ const ev = { type: 'stage', name: stage.name, state: 'succeeded', durationMs, json }
563
+ emit(ev)
564
+ return ev
565
+ }
566
+ const ev = {
567
+ type: 'stage',
568
+ name: stage.name,
569
+ state: 'failed',
570
+ durationMs,
571
+ json,
572
+ error: json.reason || `exit code ${result.code}`
573
+ }
574
+ emit(ev)
575
+ return ev
576
+ }
577
+
578
+ // ---------------------------------------------------------------------------
579
+ // Per-run log file
580
+ // ---------------------------------------------------------------------------
581
+
582
+ function openRunLog(logRoot) {
583
+ fs.mkdirSync(logRoot, { recursive: true })
584
+ const ts = new Date().toISOString().replace(/[:.]/g, '-')
585
+ const logPath = path.join(logRoot, `bootstrap-${ts}.log`)
586
+ const stream = fs.createWriteStream(logPath, { flags: 'a' })
587
+ return { path: logPath, stream }
588
+ }
589
+
590
+ // ---------------------------------------------------------------------------
591
+ // Public entrypoint
592
+ // ---------------------------------------------------------------------------
593
+
594
+ async function runBootstrap(opts) {
595
+ const {
596
+ installStamp,
597
+ activeRoot,
598
+ sourceRepoRoot,
599
+ nastechHome,
600
+ logRoot,
601
+ onEvent,
602
+ abortSignal,
603
+ writeMarker // callback to write the bootstrap-complete marker; main.cjs provides
604
+ } = opts
605
+
606
+ // Bail before spawning anything if the user already cancelled — otherwise an
607
+ // already-aborted signal would still fetch the manifest (a spawn) before the
608
+ // in-loop abort check fires.
609
+ if (abortSignal && abortSignal.aborted) {
610
+ if (typeof onEvent === 'function') {
611
+ try {
612
+ onEvent({ type: 'failed', error: 'bootstrap cancelled by user' })
613
+ } catch {
614
+ void 0
615
+ }
616
+ }
617
+ return { ok: false, cancelled: true }
618
+ }
619
+
620
+ const runLog = openRunLog(logRoot || path.join(nastechHome, 'logs'))
621
+
622
+ // Tee every event to the runLog AND the caller's onEvent. This gives us a
623
+ // forensic trail per bootstrap run AND lets the renderer subscribe live.
624
+ const emit = ev => {
625
+ try {
626
+ runLog.stream.write(JSON.stringify(ev) + '\n')
627
+ } catch {
628
+ void 0
629
+ }
630
+ try {
631
+ if (typeof onEvent === 'function') onEvent(ev)
632
+ } catch (err) {
633
+ // Don't let a subscriber bug crash the bootstrap
634
+ runLog.stream.write(`emit error: ${err && err.message}\n`)
635
+ }
636
+ }
637
+
638
+ emit({
639
+ type: 'log',
640
+ line:
641
+ `[bootstrap] starting at ${new Date().toISOString()}; ` +
642
+ `activeRoot=${activeRoot}; ` +
643
+ `stamp=${installStamp ? installStamp.commit.slice(0, 12) : '<none>'}; ` +
644
+ `runLog=${runLog.path}`
645
+ })
646
+
647
+ try {
648
+ // 1. Resolve the platform installer.
649
+ const scriptInfo = await resolveInstallScript({ installStamp, sourceRepoRoot, nastechHome, emit })
650
+ const installerKind = scriptInfo.kind || 'powershell'
651
+
652
+ // 2. Fetch manifest
653
+ const manifest = await fetchManifest({
654
+ scriptPath: scriptInfo.path,
655
+ installerKind,
656
+ emit,
657
+ nastechHome,
658
+ activeRoot,
659
+ installStamp
660
+ })
661
+ emit({
662
+ type: 'manifest',
663
+ stages: manifest.stages,
664
+ protocolVersion: manifest.protocol_version || manifest.protocolVersion || null
665
+ })
666
+
667
+ // 3. Iterate stages in order. Stages flagged needs_user_input are still
668
+ // invoked -- install.ps1's own -NonInteractive handler in those stages
669
+ // emits skipped=true. We trust the protocol rather than filtering
670
+ // client-side.
671
+ for (const stage of manifest.stages) {
672
+ if (abortSignal && abortSignal.aborted) {
673
+ emit({ type: 'failed', error: 'bootstrap cancelled by user' })
674
+ return { ok: false, cancelled: true }
675
+ }
676
+ const ev = await runStage({
677
+ scriptPath: scriptInfo.path,
678
+ installerKind,
679
+ stage,
680
+ emit,
681
+ nastechHome,
682
+ activeRoot,
683
+ abortSignal,
684
+ installStamp
685
+ })
686
+ if (ev.state === 'failed') {
687
+ emit({ type: 'failed', stage: stage.name, error: ev.error || 'stage failed' })
688
+ return { ok: false, failedStage: stage.name, error: ev.error }
689
+ }
690
+ }
691
+
692
+ // 4. Write the bootstrap-complete marker.
693
+ const markerPayload = {
694
+ pinnedCommit: installStamp ? installStamp.commit : null,
695
+ pinnedBranch: installStamp ? installStamp.branch : null
696
+ }
697
+ const marker = typeof writeMarker === 'function' ? writeMarker(markerPayload) : markerPayload
698
+ emit({ type: 'complete', marker })
699
+ return { ok: true, marker }
700
+ } catch (err) {
701
+ emit({ type: 'failed', error: err.message || String(err) })
702
+ return { ok: false, error: err.message || String(err) }
703
+ } finally {
704
+ try {
705
+ runLog.stream.end()
706
+ } catch {
707
+ void 0
708
+ }
709
+ }
710
+ }
711
+
712
+ module.exports = {
713
+ runBootstrap,
714
+ // Exposed for testability
715
+ parseStageResult,
716
+ resolveLocalInstallScript,
717
+ resolveInstallScript,
718
+ installedAgentInstallScript,
719
+ cachedScriptPath
720
+ }