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.
- package/.prettierrc +11 -0
- package/DESIGN.md +167 -0
- package/README.md +141 -0
- package/assets/icon.icns +0 -0
- package/assets/icon.ico +0 -0
- package/assets/icon.png +0 -0
- package/components.json +21 -0
- package/electron/backend-env.cjs +112 -0
- package/electron/backend-env.test.cjs +111 -0
- package/electron/backend-probes.cjs +106 -0
- package/electron/backend-probes.test.cjs +82 -0
- package/electron/backend-ready.cjs +66 -0
- package/electron/bootstrap-platform.cjs +91 -0
- package/electron/bootstrap-platform.test.cjs +111 -0
- package/electron/bootstrap-runner.cjs +720 -0
- package/electron/bootstrap-runner.test.cjs +138 -0
- package/electron/connection-config.cjs +254 -0
- package/electron/connection-config.test.cjs +329 -0
- package/electron/dashboard-token.cjs +99 -0
- package/electron/dashboard-token.test.cjs +142 -0
- package/electron/desktop-uninstall.cjs +232 -0
- package/electron/desktop-uninstall.test.cjs +246 -0
- package/electron/entitlements.mac.inherit.plist +14 -0
- package/electron/entitlements.mac.plist +14 -0
- package/electron/fs-read-dir.cjs +109 -0
- package/electron/fs-read-dir.test.cjs +364 -0
- package/electron/gateway-ws-probe.cjs +188 -0
- package/electron/gateway-ws-probe.test.cjs +122 -0
- package/electron/git-root.cjs +54 -0
- package/electron/git-root.test.cjs +40 -0
- package/electron/git-worktrees.cjs +174 -0
- package/electron/hardening.cjs +184 -0
- package/electron/hardening.test.cjs +116 -0
- package/electron/main.cjs +5762 -0
- package/electron/oauth-net-request.cjs +20 -0
- package/electron/oauth-net-request.test.cjs +34 -0
- package/electron/preload.cjs +135 -0
- package/electron/session-windows.cjs +99 -0
- package/electron/session-windows.test.cjs +177 -0
- package/electron/update-remote.cjs +56 -0
- package/electron/update-remote.test.cjs +78 -0
- package/electron/vscode-marketplace.cjs +331 -0
- package/electron/vscode-marketplace.test.cjs +113 -0
- package/electron/windows-child-process.test.cjs +57 -0
- package/electron/windows-user-env.cjs +76 -0
- package/electron/windows-user-env.test.cjs +90 -0
- package/electron/workspace-cwd.cjs +38 -0
- package/electron/workspace-cwd.test.cjs +45 -0
- package/eslint.config.mjs +122 -0
- package/index.html +17 -0
- package/package.json +254 -0
- package/pr-assets/session-source-folders.png +0 -0
- package/preview-demo.html +65 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/ds-assets/filler-bg0.jpg +0 -0
- package/public/nastech-frames/nastech-frame-0.png +0 -0
- package/public/nastech-frames/nastech-frame-1.png +0 -0
- package/public/nastech-frames/nastech-frame-2.png +0 -0
- package/public/nastech-frames/nastech-frame-3.png +0 -0
- package/public/nastech-frames/nastech-frame-4.png +0 -0
- package/public/nastech-frames/nastech-frame-5.png +0 -0
- package/public/nastech-frames/nastech-frame-6.png +0 -0
- package/public/nastech-frames/nastech-frame-7.png +0 -0
- package/public/nastech-girl.jpg +0 -0
- package/public/nastech-sprite.png +0 -0
- package/public/nastech.png +0 -0
- package/scripts/after-pack.cjs +41 -0
- package/scripts/assert-dist-built.cjs +70 -0
- package/scripts/assert-dist-built.test.cjs +84 -0
- package/scripts/assert-root-install.cjs +13 -0
- package/scripts/before-build.cjs +11 -0
- package/scripts/before-pack.cjs +78 -0
- package/scripts/before-pack.test.cjs +53 -0
- package/scripts/click-session.mjs +51 -0
- package/scripts/dev-no-hmr.mjs +22 -0
- package/scripts/diag-jump.mjs +115 -0
- package/scripts/diag-scroll-reset.mjs +229 -0
- package/scripts/eval.mjs +21 -0
- package/scripts/leak-typing.mjs +222 -0
- package/scripts/measure-jump.mjs +108 -0
- package/scripts/measure-latency.mjs +184 -0
- package/scripts/measure-real-stream.mjs +252 -0
- package/scripts/measure-submit.mjs +179 -0
- package/scripts/measure-synthetic-stream.mjs +322 -0
- package/scripts/notarize-artifact.cjs +77 -0
- package/scripts/notarize.cjs +100 -0
- package/scripts/patch-electron-builder-mac-binary.cjs +59 -0
- package/scripts/probe-renderer.mjs +38 -0
- package/scripts/probe-thread.mjs +40 -0
- package/scripts/profile-long-stream.mjs +191 -0
- package/scripts/profile-real-stream.mjs +137 -0
- package/scripts/profile-synth-stream.mjs +103 -0
- package/scripts/profile-typing-lag.md +381 -0
- package/scripts/profile-typing.mjs +260 -0
- package/scripts/reload-renderer.mjs +25 -0
- package/scripts/reload.mjs +36 -0
- package/scripts/set-exe-identity.cjs +94 -0
- package/scripts/stage-native-deps.cjs +159 -0
- package/scripts/test-desktop.mjs +425 -0
- package/scripts/write-build-stamp.cjs +126 -0
- package/src/app/agents/index.tsx +398 -0
- package/src/app/artifacts/index.test.ts +62 -0
- package/src/app/artifacts/index.tsx +906 -0
- package/src/app/chat/chat-drop-overlay.tsx +48 -0
- package/src/app/chat/chat-swap-overlay.tsx +47 -0
- package/src/app/chat/composer/attachments.tsx +114 -0
- package/src/app/chat/composer/completion-drawer.tsx +63 -0
- package/src/app/chat/composer/context-menu.tsx +172 -0
- package/src/app/chat/composer/controls.tsx +289 -0
- package/src/app/chat/composer/drop-affordance.ts +2 -0
- package/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
- package/src/app/chat/composer/focus.ts +134 -0
- package/src/app/chat/composer/help-hint.tsx +59 -0
- package/src/app/chat/composer/hooks/use-at-completions.ts +141 -0
- package/src/app/chat/composer/hooks/use-live-completion-adapter.ts +119 -0
- package/src/app/chat/composer/hooks/use-mic-recorder.ts +291 -0
- package/src/app/chat/composer/hooks/use-slash-completions.ts +114 -0
- package/src/app/chat/composer/hooks/use-voice-conversation.ts +390 -0
- package/src/app/chat/composer/hooks/use-voice-recorder.ts +116 -0
- package/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
- package/src/app/chat/composer/index.tsx +1611 -0
- package/src/app/chat/composer/inline-refs.ts +138 -0
- package/src/app/chat/composer/model-pill.tsx +86 -0
- package/src/app/chat/composer/queue-panel.tsx +130 -0
- package/src/app/chat/composer/rich-editor.test.ts +18 -0
- package/src/app/chat/composer/rich-editor.ts +165 -0
- package/src/app/chat/composer/skin-slash-popover.tsx +61 -0
- package/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
- package/src/app/chat/composer/status-stack/index.tsx +202 -0
- package/src/app/chat/composer/status-stack/status-row.tsx +155 -0
- package/src/app/chat/composer/text-utils.test.ts +77 -0
- package/src/app/chat/composer/text-utils.ts +107 -0
- package/src/app/chat/composer/trigger-popover.test.tsx +42 -0
- package/src/app/chat/composer/trigger-popover.tsx +116 -0
- package/src/app/chat/composer/types.ts +64 -0
- package/src/app/chat/composer/url-dialog.tsx +82 -0
- package/src/app/chat/composer/voice-activity.tsx +252 -0
- package/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
- package/src/app/chat/hooks/use-composer-actions.ts +525 -0
- package/src/app/chat/hooks/use-file-drop-zone.ts +118 -0
- package/src/app/chat/index.tsx +390 -0
- package/src/app/chat/perf-probe.tsx +269 -0
- package/src/app/chat/right-rail/index.ts +1 -0
- package/src/app/chat/right-rail/preview-console-state.ts +82 -0
- package/src/app/chat/right-rail/preview-console.tsx +290 -0
- package/src/app/chat/right-rail/preview-file.tsx +559 -0
- package/src/app/chat/right-rail/preview-pane.test.tsx +43 -0
- package/src/app/chat/right-rail/preview-pane.tsx +657 -0
- package/src/app/chat/right-rail/preview.tsx +171 -0
- package/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
- package/src/app/chat/scroll-to-bottom-button.tsx +74 -0
- package/src/app/chat/sidebar/cron-jobs-section.tsx +325 -0
- package/src/app/chat/sidebar/index.tsx +1219 -0
- package/src/app/chat/sidebar/load-more-row.tsx +30 -0
- package/src/app/chat/sidebar/order.test.ts +21 -0
- package/src/app/chat/sidebar/order.ts +17 -0
- package/src/app/chat/sidebar/profile-switcher.tsx +516 -0
- package/src/app/chat/sidebar/session-actions-menu.tsx +264 -0
- package/src/app/chat/sidebar/session-row.tsx +257 -0
- package/src/app/chat/sidebar/virtual-session-list.tsx +154 -0
- package/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
- package/src/app/chat/sidebar/workspace-groups.ts +326 -0
- package/src/app/chat/thread-loading.test.ts +34 -0
- package/src/app/chat/thread-loading.ts +26 -0
- package/src/app/command-center/index.tsx +654 -0
- package/src/app/command-palette/index.tsx +513 -0
- package/src/app/command-palette/marketplace-theme-page.tsx +157 -0
- package/src/app/cron/index.tsx +942 -0
- package/src/app/cron/job-state.ts +29 -0
- package/src/app/desktop-controller.tsx +938 -0
- package/src/app/floating-hud.ts +22 -0
- package/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
- package/src/app/gateway/hooks/use-gateway-boot.ts +387 -0
- package/src/app/gateway/hooks/use-gateway-request.ts +138 -0
- package/src/app/hooks/use-keybinds.ts +186 -0
- package/src/app/hooks/use-refresh-hotkey.ts +45 -0
- package/src/app/hooks/use-route-enum-param.ts +38 -0
- package/src/app/index.tsx +1 -0
- package/src/app/layout-constants.ts +13 -0
- package/src/app/messaging/index.tsx +648 -0
- package/src/app/messaging/platform-icon.tsx +93 -0
- package/src/app/model-picker-overlay.tsx +42 -0
- package/src/app/model-visibility-overlay.tsx +31 -0
- package/src/app/overlays/overlay-chrome.tsx +66 -0
- package/src/app/overlays/overlay-search-input.tsx +33 -0
- package/src/app/overlays/overlay-split-layout.tsx +130 -0
- package/src/app/overlays/overlay-view.tsx +91 -0
- package/src/app/page-search-shell.tsx +75 -0
- package/src/app/profiles/create-profile-dialog.tsx +154 -0
- package/src/app/profiles/delete-profile-dialog.tsx +65 -0
- package/src/app/profiles/index.tsx +671 -0
- package/src/app/profiles/rename-profile-dialog.tsx +125 -0
- package/src/app/right-sidebar/files/dnd-manager.ts +27 -0
- package/src/app/right-sidebar/files/ipc.test.ts +100 -0
- package/src/app/right-sidebar/files/ipc.ts +161 -0
- package/src/app/right-sidebar/files/remote-picker.tsx +177 -0
- package/src/app/right-sidebar/files/tree.tsx +224 -0
- package/src/app/right-sidebar/files/use-project-tree.test.ts +190 -0
- package/src/app/right-sidebar/files/use-project-tree.ts +268 -0
- package/src/app/right-sidebar/index.test.tsx +75 -0
- package/src/app/right-sidebar/index.tsx +395 -0
- package/src/app/right-sidebar/store.ts +15 -0
- package/src/app/right-sidebar/terminal/buffer.ts +65 -0
- package/src/app/right-sidebar/terminal/index.tsx +98 -0
- package/src/app/right-sidebar/terminal/persistent.tsx +122 -0
- package/src/app/right-sidebar/terminal/selection.ts +75 -0
- package/src/app/right-sidebar/terminal/use-terminal-session.ts +504 -0
- package/src/app/routes.ts +88 -0
- package/src/app/session/hooks/use-context-suggestions.ts +58 -0
- package/src/app/session/hooks/use-cwd-actions.ts +109 -0
- package/src/app/session/hooks/use-message-stream.ts +957 -0
- package/src/app/session/hooks/use-model-controls.test.tsx +198 -0
- package/src/app/session/hooks/use-model-controls.ts +106 -0
- package/src/app/session/hooks/use-nastech-config.ts +74 -0
- package/src/app/session/hooks/use-preview-routing.test.tsx +168 -0
- package/src/app/session/hooks/use-preview-routing.ts +223 -0
- package/src/app/session/hooks/use-prompt-actions.test.tsx +316 -0
- package/src/app/session/hooks/use-prompt-actions.ts +1030 -0
- package/src/app/session/hooks/use-route-resume.test.tsx +136 -0
- package/src/app/session/hooks/use-route-resume.ts +115 -0
- package/src/app/session/hooks/use-session-actions.test.tsx +119 -0
- package/src/app/session/hooks/use-session-actions.ts +885 -0
- package/src/app/session/hooks/use-session-state-cache.test.tsx +118 -0
- package/src/app/session/hooks/use-session-state-cache.ts +191 -0
- package/src/app/session-picker-overlay.tsx +32 -0
- package/src/app/session-switcher.tsx +107 -0
- package/src/app/settings/about-settings.tsx +173 -0
- package/src/app/settings/appearance-settings.tsx +162 -0
- package/src/app/settings/config-settings.tsx +384 -0
- package/src/app/settings/constants.ts +545 -0
- package/src/app/settings/credential-key-ui.tsx +373 -0
- package/src/app/settings/env-credentials.tsx +198 -0
- package/src/app/settings/env-var-actions-menu.tsx +136 -0
- package/src/app/settings/field-copy.ts +56 -0
- package/src/app/settings/gateway-settings.tsx +620 -0
- package/src/app/settings/helpers.test.ts +138 -0
- package/src/app/settings/helpers.ts +151 -0
- package/src/app/settings/index.tsx +237 -0
- package/src/app/settings/keys-settings.tsx +96 -0
- package/src/app/settings/mcp-settings.tsx +271 -0
- package/src/app/settings/model-settings.test.tsx +157 -0
- package/src/app/settings/model-settings.tsx +559 -0
- package/src/app/settings/notifications-settings.tsx +150 -0
- package/src/app/settings/primitives.tsx +115 -0
- package/src/app/settings/providers-settings.test.tsx +100 -0
- package/src/app/settings/providers-settings.tsx +258 -0
- package/src/app/settings/sessions-settings.tsx +276 -0
- package/src/app/settings/toolset-config-panel.test.tsx +289 -0
- package/src/app/settings/toolset-config-panel.tsx +449 -0
- package/src/app/settings/types.ts +42 -0
- package/src/app/settings/uninstall-section.tsx +185 -0
- package/src/app/settings/use-deep-link-highlight.ts +60 -0
- package/src/app/shell/app-shell.tsx +167 -0
- package/src/app/shell/gateway-menu-panel.tsx +150 -0
- package/src/app/shell/hooks/use-overlay-routing.ts +71 -0
- package/src/app/shell/hooks/use-status-snapshot.ts +57 -0
- package/src/app/shell/hooks/use-statusbar-items.tsx +403 -0
- package/src/app/shell/keybind-panel.tsx +220 -0
- package/src/app/shell/model-edit-submenu.test.tsx +84 -0
- package/src/app/shell/model-edit-submenu.tsx +245 -0
- package/src/app/shell/model-menu-panel.tsx +295 -0
- package/src/app/shell/sidebar-label.tsx +22 -0
- package/src/app/shell/statusbar-controls.tsx +185 -0
- package/src/app/shell/titlebar-controls.tsx +244 -0
- package/src/app/shell/titlebar.test.ts +26 -0
- package/src/app/shell/titlebar.ts +45 -0
- package/src/app/shell/use-group-registry.ts +39 -0
- package/src/app/skills/index.test.tsx +103 -0
- package/src/app/skills/index.tsx +371 -0
- package/src/app/types.ts +99 -0
- package/src/app/updates-overlay.tsx +369 -0
- package/src/components/Backdrop.tsx +114 -0
- package/src/components/assistant-ui/ansi-text.tsx +34 -0
- package/src/components/assistant-ui/clarify-tool.tsx +281 -0
- package/src/components/assistant-ui/directive-text.test.ts +39 -0
- package/src/components/assistant-ui/directive-text.tsx +389 -0
- package/src/components/assistant-ui/markdown-text.test.ts +204 -0
- package/src/components/assistant-ui/markdown-text.tsx +497 -0
- package/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
- package/src/components/assistant-ui/message-render-boundary.tsx +48 -0
- package/src/components/assistant-ui/streaming.test.tsx +739 -0
- package/src/components/assistant-ui/thread-list.tsx +307 -0
- package/src/components/assistant-ui/thread-virtualizer.tsx +512 -0
- package/src/components/assistant-ui/thread.tsx +1474 -0
- package/src/components/assistant-ui/todo-tool.tsx +109 -0
- package/src/components/assistant-ui/tool-approval-group.test.tsx +158 -0
- package/src/components/assistant-ui/tool-approval.test.tsx +81 -0
- package/src/components/assistant-ui/tool-approval.tsx +209 -0
- package/src/components/assistant-ui/tool-fallback-model.test.ts +66 -0
- package/src/components/assistant-ui/tool-fallback-model.ts +1368 -0
- package/src/components/assistant-ui/tool-fallback.tsx +466 -0
- package/src/components/assistant-ui/tooltip-icon-button.tsx +33 -0
- package/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
- package/src/components/assistant-ui/user-message-text.tsx +150 -0
- package/src/components/boot-failure-overlay.tsx +246 -0
- package/src/components/boot-failure-reauth.test.ts +100 -0
- package/src/components/boot-failure-reauth.ts +81 -0
- package/src/components/brand-mark.tsx +19 -0
- package/src/components/chat/activity-timer-text.tsx +24 -0
- package/src/components/chat/activity-timer.test.tsx +43 -0
- package/src/components/chat/activity-timer.ts +64 -0
- package/src/components/chat/code-card.tsx +78 -0
- package/src/components/chat/compact-markdown.tsx +113 -0
- package/src/components/chat/composer-dock.ts +31 -0
- package/src/components/chat/diff-lines.tsx +54 -0
- package/src/components/chat/disclosure-row.tsx +63 -0
- package/src/components/chat/generated-image-context.tsx +19 -0
- package/src/components/chat/generated-image-result.tsx +174 -0
- package/src/components/chat/image-generation-placeholder.tsx +279 -0
- package/src/components/chat/intro-copy.jsonl +75 -0
- package/src/components/chat/intro.tsx +182 -0
- package/src/components/chat/preview-attachment.tsx +125 -0
- package/src/components/chat/shiki-highlighter.tsx +107 -0
- package/src/components/chat/status-row.tsx +70 -0
- package/src/components/chat/status-section.tsx +42 -0
- package/src/components/chat/terminal-output.tsx +50 -0
- package/src/components/chat/zoomable-image.tsx +177 -0
- package/src/components/desktop-install-overlay.tsx +595 -0
- package/src/components/desktop-onboarding-overlay.test.tsx +100 -0
- package/src/components/desktop-onboarding-overlay.tsx +1286 -0
- package/src/components/error-boundary.tsx +77 -0
- package/src/components/gateway-connecting-overlay.test.tsx +143 -0
- package/src/components/gateway-connecting-overlay.tsx +183 -0
- package/src/components/haptics-provider.tsx +19 -0
- package/src/components/language-switcher.test.tsx +53 -0
- package/src/components/language-switcher.tsx +175 -0
- package/src/components/model-picker.tsx +340 -0
- package/src/components/model-visibility-dialog.tsx +155 -0
- package/src/components/notifications.tsx +196 -0
- package/src/components/page-loader.tsx +34 -0
- package/src/components/pane-shell/context.ts +14 -0
- package/src/components/pane-shell/index.ts +4 -0
- package/src/components/pane-shell/pane-shell.test.tsx +333 -0
- package/src/components/pane-shell/pane-shell.tsx +330 -0
- package/src/components/prompt-overlays.tsx +234 -0
- package/src/components/session-picker.tsx +108 -0
- package/src/components/status-dot.tsx +26 -0
- package/src/components/ui/action-status.tsx +25 -0
- package/src/components/ui/alert.tsx +53 -0
- package/src/components/ui/badge.tsx +35 -0
- package/src/components/ui/braille-spinner.tsx +61 -0
- package/src/components/ui/button.tsx +81 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/codicon.tsx +20 -0
- package/src/components/ui/command.tsx +111 -0
- package/src/components/ui/confirm-dialog.tsx +109 -0
- package/src/components/ui/context-menu.tsx +141 -0
- package/src/components/ui/control.ts +25 -0
- package/src/components/ui/copy-button.test.tsx +36 -0
- package/src/components/ui/copy-button.tsx +229 -0
- package/src/components/ui/dialog.tsx +152 -0
- package/src/components/ui/disclosure-caret.tsx +20 -0
- package/src/components/ui/dropdown-menu.tsx +291 -0
- package/src/components/ui/error-state.tsx +50 -0
- package/src/components/ui/fade-text.tsx +110 -0
- package/src/components/ui/glyph-spinner.tsx +63 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/kbd.tsx +37 -0
- package/src/components/ui/loader.tsx +558 -0
- package/src/components/ui/log-view.tsx +17 -0
- package/src/components/ui/pagination.tsx +114 -0
- package/src/components/ui/popover.tsx +44 -0
- package/src/components/ui/scroll-area.tsx +43 -0
- package/src/components/ui/search-field.tsx +80 -0
- package/src/components/ui/segmented-control.tsx +51 -0
- package/src/components/ui/select.tsx +92 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +116 -0
- package/src/components/ui/sidebar.tsx +674 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/switch.tsx +49 -0
- package/src/components/ui/tabs.tsx +36 -0
- package/src/components/ui/text-tab.tsx +43 -0
- package/src/components/ui/textarea.tsx +11 -0
- package/src/components/ui/tool-icon.tsx +65 -0
- package/src/components/ui/tooltip.tsx +69 -0
- package/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
- package/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
- package/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/src/global.d.ts +457 -0
- package/src/hooks/use-image-download.ts +85 -0
- package/src/hooks/use-media-query.ts +24 -0
- package/src/hooks/use-mobile.ts +3 -0
- package/src/hooks/use-resize-observer.ts +38 -0
- package/src/hooks/use-worktree-info.ts +68 -0
- package/src/i18n/catalog.ts +12 -0
- package/src/i18n/context.test.tsx +232 -0
- package/src/i18n/context.tsx +183 -0
- package/src/i18n/define-locale.ts +41 -0
- package/src/i18n/en.ts +1779 -0
- package/src/i18n/index.ts +20 -0
- package/src/i18n/ja.ts +1890 -0
- package/src/i18n/languages.test.ts +43 -0
- package/src/i18n/languages.ts +86 -0
- package/src/i18n/runtime.test.ts +75 -0
- package/src/i18n/runtime.ts +53 -0
- package/src/i18n/types.ts +1452 -0
- package/src/i18n/zh-hant.ts +1849 -0
- package/src/i18n/zh.ts +1923 -0
- package/src/lib/ansi.test.ts +123 -0
- package/src/lib/ansi.ts +175 -0
- package/src/lib/chat-messages.test.ts +708 -0
- package/src/lib/chat-messages.ts +885 -0
- package/src/lib/chat-runtime.test.ts +18 -0
- package/src/lib/chat-runtime.ts +335 -0
- package/src/lib/clipboard.ts +28 -0
- package/src/lib/commit-changelog.test.ts +114 -0
- package/src/lib/commit-changelog.ts +177 -0
- package/src/lib/completion-sound.ts +519 -0
- package/src/lib/desktop-fs.test.ts +116 -0
- package/src/lib/desktop-fs.ts +113 -0
- package/src/lib/desktop-slash-commands.test.ts +126 -0
- package/src/lib/desktop-slash-commands.ts +286 -0
- package/src/lib/embedded-images.test.ts +35 -0
- package/src/lib/embedded-images.ts +60 -0
- package/src/lib/external-link.test.tsx +168 -0
- package/src/lib/external-link.tsx +303 -0
- package/src/lib/gateway-events.test.ts +27 -0
- package/src/lib/gateway-events.ts +49 -0
- package/src/lib/gateway-ws-url.test.ts +78 -0
- package/src/lib/gateway-ws-url.ts +91 -0
- package/src/lib/generated-images.test.ts +97 -0
- package/src/lib/generated-images.ts +116 -0
- package/src/lib/haptics.ts +129 -0
- package/src/lib/icons.ts +203 -0
- package/src/lib/incremental-external-store-runtime.ts +188 -0
- package/src/lib/katex-memo.ts +260 -0
- package/src/lib/keybinds/actions.ts +125 -0
- package/src/lib/keybinds/combo.test.ts +86 -0
- package/src/lib/keybinds/combo.ts +169 -0
- package/src/lib/local-preview.ts +126 -0
- package/src/lib/markdown-code.test.ts +23 -0
- package/src/lib/markdown-code.ts +195 -0
- package/src/lib/markdown-preprocess.ts +386 -0
- package/src/lib/media.remote.test.ts +58 -0
- package/src/lib/media.ts +111 -0
- package/src/lib/model-status-label.test.ts +31 -0
- package/src/lib/model-status-label.ts +103 -0
- package/src/lib/mutable-ref.ts +6 -0
- package/src/lib/preview-targets.test.ts +27 -0
- package/src/lib/preview-targets.ts +63 -0
- package/src/lib/profile-color.ts +58 -0
- package/src/lib/provider-setup-errors.test.ts +26 -0
- package/src/lib/provider-setup-errors.ts +12 -0
- package/src/lib/query-client.ts +13 -0
- package/src/lib/remend-tail.test.ts +105 -0
- package/src/lib/remend-tail.ts +108 -0
- package/src/lib/runtime-readiness.test.ts +65 -0
- package/src/lib/runtime-readiness.ts +147 -0
- package/src/lib/session-export.ts +57 -0
- package/src/lib/session-search.test.ts +58 -0
- package/src/lib/session-search.ts +19 -0
- package/src/lib/session-source.ts +62 -0
- package/src/lib/speech-text.ts +35 -0
- package/src/lib/statusbar.ts +91 -0
- package/src/lib/storage.test.ts +25 -0
- package/src/lib/storage.ts +107 -0
- package/src/lib/todos.test.ts +35 -0
- package/src/lib/todos.ts +51 -0
- package/src/lib/tool-result-summary.test.ts +106 -0
- package/src/lib/tool-result-summary.ts +467 -0
- package/src/lib/update-copy.test.ts +38 -0
- package/src/lib/update-copy.ts +44 -0
- package/src/lib/use-enter-animation.ts +100 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/voice-playback.ts +128 -0
- package/src/lib/yolo-session.ts +26 -0
- package/src/main.tsx +43 -0
- package/src/nastech.test.ts +49 -0
- package/src/nastech.ts +718 -0
- package/src/store/activity.ts +100 -0
- package/src/store/boot.ts +91 -0
- package/src/store/clarify.test.ts +81 -0
- package/src/store/clarify.ts +69 -0
- package/src/store/command-palette.ts +20 -0
- package/src/store/compaction.test.ts +53 -0
- package/src/store/compaction.ts +38 -0
- package/src/store/completion-sound.ts +32 -0
- package/src/store/composer-input-history.test.ts +147 -0
- package/src/store/composer-input-history.ts +158 -0
- package/src/store/composer-queue.test.ts +148 -0
- package/src/store/composer-queue.ts +239 -0
- package/src/store/composer-status.test.ts +99 -0
- package/src/store/composer-status.ts +277 -0
- package/src/store/composer.test.ts +106 -0
- package/src/store/composer.ts +184 -0
- package/src/store/cron.ts +19 -0
- package/src/store/gateway.ts +290 -0
- package/src/store/haptics.ts +17 -0
- package/src/store/keybinds.ts +139 -0
- package/src/store/layout.ts +176 -0
- package/src/store/model-presets.test.ts +51 -0
- package/src/store/model-presets.ts +86 -0
- package/src/store/model-visibility.test.ts +37 -0
- package/src/store/model-visibility.ts +108 -0
- package/src/store/native-notifications.test.ts +192 -0
- package/src/store/native-notifications.ts +203 -0
- package/src/store/notifications.ts +165 -0
- package/src/store/onboarding.test.ts +372 -0
- package/src/store/onboarding.ts +866 -0
- package/src/store/panes.test.ts +146 -0
- package/src/store/panes.ts +145 -0
- package/src/store/preview.test.ts +135 -0
- package/src/store/preview.ts +466 -0
- package/src/store/profile.test.ts +89 -0
- package/src/store/profile.ts +365 -0
- package/src/store/prompts.test.ts +121 -0
- package/src/store/prompts.ts +115 -0
- package/src/store/session-switcher.test.ts +115 -0
- package/src/store/session-switcher.ts +128 -0
- package/src/store/session-sync.ts +25 -0
- package/src/store/session.test.ts +131 -0
- package/src/store/session.ts +255 -0
- package/src/store/subagents.test.ts +111 -0
- package/src/store/subagents.ts +260 -0
- package/src/store/thread-scroll.ts +46 -0
- package/src/store/todos.test.ts +47 -0
- package/src/store/todos.ts +64 -0
- package/src/store/tool-diffs.ts +23 -0
- package/src/store/tool-dismiss.ts +45 -0
- package/src/store/tool-view.ts +91 -0
- package/src/store/translucency.ts +38 -0
- package/src/store/updates.test.ts +77 -0
- package/src/store/updates.ts +315 -0
- package/src/store/voice-playback.ts +24 -0
- package/src/store/windows.test.ts +143 -0
- package/src/store/windows.ts +77 -0
- package/src/styles.css +1235 -0
- package/src/themes/color.ts +142 -0
- package/src/themes/context.tsx +339 -0
- package/src/themes/index.ts +3 -0
- package/src/themes/install.test.ts +119 -0
- package/src/themes/install.ts +95 -0
- package/src/themes/presets.test.ts +33 -0
- package/src/themes/presets.ts +293 -0
- package/src/themes/profile-theme.test.ts +41 -0
- package/src/themes/types.ts +66 -0
- package/src/themes/use-skin-command.ts +60 -0
- package/src/themes/user-themes.test.ts +63 -0
- package/src/themes/user-themes.ts +122 -0
- package/src/themes/vscode.test.ts +171 -0
- package/src/themes/vscode.ts +343 -0
- package/src/types/nastech.ts +646 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +25 -0
- 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
|
+
}
|