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,467 @@
|
|
|
1
|
+
// Heuristic JSON → human summary for tool results. Default view; technical
|
|
2
|
+
// mode still gets the raw JSON section.
|
|
3
|
+
|
|
4
|
+
const WRAPPER_KEYS = ['data', 'result', 'output', 'response', 'payload'] as const
|
|
5
|
+
|
|
6
|
+
const PRIORITY_KEYS = [
|
|
7
|
+
'title',
|
|
8
|
+
'name',
|
|
9
|
+
'path',
|
|
10
|
+
'file',
|
|
11
|
+
'filepath',
|
|
12
|
+
'url',
|
|
13
|
+
'href',
|
|
14
|
+
'link',
|
|
15
|
+
'status',
|
|
16
|
+
'id',
|
|
17
|
+
'message',
|
|
18
|
+
'summary',
|
|
19
|
+
'description'
|
|
20
|
+
] as const
|
|
21
|
+
|
|
22
|
+
const ERROR_KEYS = ['error', 'errors', 'failure', 'exception'] as const
|
|
23
|
+
// 'stderr' deliberately excluded: many CLIs emit informational lines on
|
|
24
|
+
// stderr (npm progress, git's hint:, gcc's `In file included from`) that
|
|
25
|
+
// aren't errors. Treating those as error signal flipped tool cards into
|
|
26
|
+
// destructive styling for healthy commands.
|
|
27
|
+
const ERROR_MSG_KEYS = ['message', 'reason', 'detail'] as const
|
|
28
|
+
const NON_ERROR_TEXT = new Set(['', '0', 'false', 'none', 'null', 'nil', 'ok', 'success', 'n/a', 'na'])
|
|
29
|
+
|
|
30
|
+
type Json = Record<string, unknown>
|
|
31
|
+
|
|
32
|
+
const isRecord = (v: unknown): v is Json => Boolean(v && typeof v === 'object' && !Array.isArray(v))
|
|
33
|
+
|
|
34
|
+
function tryJson(value: string): unknown {
|
|
35
|
+
const t = value.trim()
|
|
36
|
+
|
|
37
|
+
if (!t) {
|
|
38
|
+
return ''
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!/^[{[]|^"/.test(t)) {
|
|
42
|
+
return value
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(t)
|
|
47
|
+
} catch {
|
|
48
|
+
return value
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const norm = (v: unknown): unknown => (typeof v === 'string' ? tryJson(v) : v)
|
|
53
|
+
|
|
54
|
+
const titleCase = (k: string) =>
|
|
55
|
+
k
|
|
56
|
+
.split(/[_\-.]+/)
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
.map(p => `${p[0]?.toUpperCase() ?? ''}${p.slice(1)}`)
|
|
59
|
+
.join(' ')
|
|
60
|
+
|
|
61
|
+
const pluralize = (n: number, noun: string) => `${n} ${noun}${n === 1 ? '' : 's'}`
|
|
62
|
+
|
|
63
|
+
function clipInline(value: string, max = 180): string {
|
|
64
|
+
const c = value.replace(/\s+/g, ' ').trim()
|
|
65
|
+
|
|
66
|
+
return c.length > max ? `${c.slice(0, max - 1)}…` : c
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function clipBlock(value: string, maxChars = 1800, maxLines = 18): string {
|
|
70
|
+
const t = value.trim()
|
|
71
|
+
|
|
72
|
+
if (!t) {
|
|
73
|
+
return ''
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const lines = t.split('\n')
|
|
77
|
+
let text = lines.slice(0, maxLines).join('\n')
|
|
78
|
+
const clipped = lines.length > maxLines || text.length > maxChars
|
|
79
|
+
|
|
80
|
+
if (text.length > maxChars) {
|
|
81
|
+
text = text.slice(0, maxChars - 1).trimEnd()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return clipped && !text.endsWith('…') ? `${text}…` : text
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function firstString(record: Json, keys: readonly string[]): string {
|
|
88
|
+
for (const k of keys) {
|
|
89
|
+
const v = record[k]
|
|
90
|
+
|
|
91
|
+
if (typeof v === 'string' && v.trim()) {
|
|
92
|
+
return v.trim()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return ''
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function orderedKeys(keys: string[]): string[] {
|
|
100
|
+
const priority = PRIORITY_KEYS.filter(k => keys.includes(k))
|
|
101
|
+
const rest = keys.filter(k => !priority.includes(k as never))
|
|
102
|
+
|
|
103
|
+
return [...priority, ...rest]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const isWrapperKey = (k: string) => (WRAPPER_KEYS as readonly string[]).includes(k)
|
|
107
|
+
const skipField = (k: string, v: unknown) => isWrapperKey(k) || ((k === 'success' || k === 'ok') && v === true)
|
|
108
|
+
|
|
109
|
+
function summarizeScalar(v: unknown): string {
|
|
110
|
+
if (typeof v === 'string') {
|
|
111
|
+
return clipInline(v)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (typeof v === 'number' || typeof v === 'boolean') {
|
|
115
|
+
return String(v)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return ''
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function summarizeRecordInline(record: Json, depth: number): string {
|
|
122
|
+
if (depth > 3) {
|
|
123
|
+
return pluralize(Object.keys(record).length, 'field')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const title = firstString(record, ['title', 'name', 'path', 'file', 'filepath', 'url', 'href', 'link', 'id'])
|
|
127
|
+
const status = firstString(record, ['status', 'category', 'type'])
|
|
128
|
+
const message = firstString(record, ['snippet', 'summary', 'description', 'message'])
|
|
129
|
+
|
|
130
|
+
if (title && status) {
|
|
131
|
+
return `${clipInline(title, 110)} (${clipInline(status, 54)})`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (title && message && title !== message) {
|
|
135
|
+
return `${clipInline(title, 90)} - ${clipInline(message, 84)}`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (title) {
|
|
139
|
+
return clipInline(title, 150)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const pairs = orderedKeys(Object.keys(record))
|
|
143
|
+
.filter(k => !skipField(k, record[k]))
|
|
144
|
+
.map(k => {
|
|
145
|
+
const s = summarizeScalar(record[k])
|
|
146
|
+
|
|
147
|
+
return s ? `${titleCase(k)}: ${s}` : ''
|
|
148
|
+
})
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
.slice(0, 2)
|
|
151
|
+
|
|
152
|
+
return pairs.length ? pairs.join(' · ') : pluralize(Object.keys(record).length, 'field')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function summarizeListItem(item: unknown, depth: number): string {
|
|
156
|
+
const v = norm(item)
|
|
157
|
+
|
|
158
|
+
if (typeof v === 'string') {
|
|
159
|
+
return clipInline(v)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (typeof v === 'number' || typeof v === 'boolean') {
|
|
163
|
+
return String(v)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (v == null) {
|
|
167
|
+
return ''
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (Array.isArray(v)) {
|
|
171
|
+
return pluralize(v.length, 'item')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (isRecord(v)) {
|
|
175
|
+
return summarizeRecordInline(v, depth + 1)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return clipInline(String(v))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function formatFieldValue(value: unknown, depth: number): string {
|
|
182
|
+
const v = norm(value)
|
|
183
|
+
const scalar = summarizeScalar(v)
|
|
184
|
+
|
|
185
|
+
if (scalar) {
|
|
186
|
+
return scalar
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (v == null) {
|
|
190
|
+
return ''
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (Array.isArray(v)) {
|
|
194
|
+
if (!v.length) {
|
|
195
|
+
return ''
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const scalars = v.map(summarizeScalar).filter(Boolean)
|
|
199
|
+
|
|
200
|
+
if (scalars.length === v.length && v.length <= 4) {
|
|
201
|
+
return clipInline(scalars.join(', '))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const first = summarizeListItem(v[0], depth + 1)
|
|
205
|
+
|
|
206
|
+
return first ? `${pluralize(v.length, 'item')} (${first})` : pluralize(v.length, 'item')
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (isRecord(v)) {
|
|
210
|
+
return summarizeRecordInline(v, depth + 1)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return clipInline(String(v))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// "Returned N items" / "0 items" / "Returned an empty object" are all
|
|
217
|
+
// noise — better to render nothing and let the title carry the signal.
|
|
218
|
+
function formatArraySummary(value: unknown[], depth: number): string {
|
|
219
|
+
if (!value.length) {
|
|
220
|
+
return ''
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const max = 6
|
|
224
|
+
|
|
225
|
+
const lines = value
|
|
226
|
+
.slice(0, max)
|
|
227
|
+
.map(item => summarizeListItem(item, depth + 1))
|
|
228
|
+
.filter(Boolean)
|
|
229
|
+
.map(l => `- ${l}`)
|
|
230
|
+
|
|
231
|
+
if (!lines.length) {
|
|
232
|
+
return ''
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (value.length > max) {
|
|
236
|
+
const remaining = value.length - max
|
|
237
|
+
lines.push(`- … ${remaining} more ${remaining === 1 ? 'item' : 'items'}`)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return lines.join('\n')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function formatRecordSummary(record: Json, depth: number): string {
|
|
244
|
+
const keys = Object.keys(record)
|
|
245
|
+
|
|
246
|
+
if (!keys.length) {
|
|
247
|
+
return ''
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (depth <= 2) {
|
|
251
|
+
const direct = firstString(record, ['message', 'summary', 'description', 'preview', 'text', 'content'])
|
|
252
|
+
const meaningful = keys.filter(k => !skipField(k, record[k]) && !isWrapperKey(k))
|
|
253
|
+
|
|
254
|
+
if (direct && meaningful.length <= 1) {
|
|
255
|
+
return clipBlock(direct)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const candidates = orderedKeys(keys).filter(k => !skipField(k, record[k]))
|
|
260
|
+
const max = 8
|
|
261
|
+
const lines: string[] = []
|
|
262
|
+
|
|
263
|
+
for (const k of candidates) {
|
|
264
|
+
const v = formatFieldValue(record[k], depth + 1)
|
|
265
|
+
|
|
266
|
+
if (!v) {
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
lines.push(`- ${titleCase(k)}: ${v}`)
|
|
271
|
+
|
|
272
|
+
if (lines.length >= max) {
|
|
273
|
+
break
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!lines.length) {
|
|
278
|
+
return ''
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (candidates.length > lines.length) {
|
|
282
|
+
const remaining = candidates.length - lines.length
|
|
283
|
+
lines.push(`- … ${remaining} more ${remaining === 1 ? 'field' : 'fields'}`)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return lines.join('\n')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function formatSummaryValue(value: unknown, depth: number): string {
|
|
290
|
+
if (depth > 4) {
|
|
291
|
+
return ''
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const v = norm(value)
|
|
295
|
+
|
|
296
|
+
if (typeof v === 'string') {
|
|
297
|
+
return clipBlock(v)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (typeof v === 'number' || typeof v === 'boolean') {
|
|
301
|
+
return String(v)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (v == null) {
|
|
305
|
+
return ''
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (Array.isArray(v)) {
|
|
309
|
+
return formatArraySummary(v, depth + 1)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (isRecord(v)) {
|
|
313
|
+
return formatRecordSummary(v, depth + 1)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return clipInline(String(v))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function unwrapPayload(value: unknown): unknown {
|
|
320
|
+
let cur: unknown = norm(value)
|
|
321
|
+
|
|
322
|
+
for (let i = 0; i < 4; i += 1) {
|
|
323
|
+
if (!isRecord(cur)) {
|
|
324
|
+
return cur
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const record = cur
|
|
328
|
+
const key = WRAPPER_KEYS.find(k => record[k] != null)
|
|
329
|
+
|
|
330
|
+
if (!key) {
|
|
331
|
+
return record
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
cur = norm(record[key])
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return cur
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function hasMeaningfulErrorValue(value: unknown): boolean {
|
|
341
|
+
const v = norm(value)
|
|
342
|
+
|
|
343
|
+
if (v == null) {
|
|
344
|
+
return false
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (typeof v === 'string') {
|
|
348
|
+
return !NON_ERROR_TEXT.has(v.trim().toLowerCase())
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (typeof v === 'boolean') {
|
|
352
|
+
return v
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (typeof v === 'number') {
|
|
356
|
+
return v !== 0
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (Array.isArray(v)) {
|
|
360
|
+
return v.some(hasMeaningfulErrorValue)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (isRecord(v)) {
|
|
364
|
+
return Object.keys(v).length > 0
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return true
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function hasErrorSignal(record: Json): boolean {
|
|
371
|
+
const status = typeof record.status === 'string' ? record.status : ''
|
|
372
|
+
|
|
373
|
+
return (
|
|
374
|
+
record.success === false ||
|
|
375
|
+
record.ok === false ||
|
|
376
|
+
/\b(error|failed|failure|fatal|exception)\b/i.test(status) ||
|
|
377
|
+
ERROR_KEYS.some(k => hasMeaningfulErrorValue(record[k]))
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function valueErrorText(value: unknown): string {
|
|
382
|
+
const v = norm(value)
|
|
383
|
+
|
|
384
|
+
if (typeof v === 'string') {
|
|
385
|
+
return hasMeaningfulErrorValue(v) ? clipBlock(v, 700, 12) : ''
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (Array.isArray(v)) {
|
|
389
|
+
return clipBlock(v.map(valueErrorText).filter(Boolean).slice(0, 3).join('; '), 700, 12)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (isRecord(v)) {
|
|
393
|
+
const direct = firstString(v, ERROR_MSG_KEYS)
|
|
394
|
+
|
|
395
|
+
if (direct) {
|
|
396
|
+
return clipBlock(direct, 700, 12)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return ''
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function findNestedError(value: unknown, depth: number, seen: Set<unknown>): string {
|
|
404
|
+
if (depth > 5) {
|
|
405
|
+
return ''
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const v = norm(value)
|
|
409
|
+
|
|
410
|
+
if (!v || typeof v !== 'object' || seen.has(v)) {
|
|
411
|
+
return ''
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
seen.add(v)
|
|
415
|
+
|
|
416
|
+
if (Array.isArray(v)) {
|
|
417
|
+
for (const item of v) {
|
|
418
|
+
const nested = findNestedError(item, depth + 1, seen)
|
|
419
|
+
|
|
420
|
+
if (nested) {
|
|
421
|
+
return nested
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return ''
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const record = v as Json
|
|
429
|
+
|
|
430
|
+
for (const k of ERROR_KEYS) {
|
|
431
|
+
if (!hasMeaningfulErrorValue(record[k])) {
|
|
432
|
+
continue
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const text = valueErrorText(record[k])
|
|
436
|
+
|
|
437
|
+
if (text) {
|
|
438
|
+
return text
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (hasErrorSignal(record)) {
|
|
443
|
+
const direct = firstString(record, ERROR_MSG_KEYS)
|
|
444
|
+
|
|
445
|
+
if (direct) {
|
|
446
|
+
return clipBlock(direct, 700, 12)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
for (const k of [...ERROR_KEYS, ...WRAPPER_KEYS, 'details', 'meta']) {
|
|
451
|
+
const nested = findNestedError(record[k], depth + 1, seen)
|
|
452
|
+
|
|
453
|
+
if (nested) {
|
|
454
|
+
return nested
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return ''
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function formatToolResultSummary(value: unknown): string {
|
|
462
|
+
return formatSummaryValue(unwrapPayload(value), 0) || formatSummaryValue(value, 0)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export function extractToolErrorMessage(value: unknown): string {
|
|
466
|
+
return findNestedError(value, 0, new Set())
|
|
467
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { resolveUpdateCopy } from './update-copy'
|
|
4
|
+
|
|
5
|
+
const copy = {
|
|
6
|
+
availableTitle: 'New update available',
|
|
7
|
+
availableBody: 'A new version of NasTech is ready to install.',
|
|
8
|
+
availableTitleBackend: 'Backend update available',
|
|
9
|
+
availableBodyBackend: 'A newer version of the connected NasTech backend is ready to install.',
|
|
10
|
+
availableBodyNoChangelog: 'A newer version is ready. Release notes aren’t available for this install type.'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('resolveUpdateCopy', () => {
|
|
14
|
+
it('client target with commits: client title + client body', () => {
|
|
15
|
+
const r = resolveUpdateCopy({ target: 'client', shownItems: 5, copy })
|
|
16
|
+
expect(r.title).toBe('New update available')
|
|
17
|
+
expect(r.body).toBe('A new version of NasTech is ready to install.')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('backend target with commits: names the backend in title and body', () => {
|
|
21
|
+
const r = resolveUpdateCopy({ target: 'backend', shownItems: 5, copy })
|
|
22
|
+
expect(r.title).toBe('Backend update available')
|
|
23
|
+
expect(r.body).toContain('backend')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('no changelog (pip/non-git backend): degrades honestly, still names backend target in title', () => {
|
|
27
|
+
const r = resolveUpdateCopy({ target: 'backend', shownItems: 0, copy })
|
|
28
|
+
expect(r.title).toBe('Backend update available')
|
|
29
|
+
// Body must NOT pretend there are notes — it states they're unavailable.
|
|
30
|
+
expect(r.body).toBe(copy.availableBodyNoChangelog)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('no changelog on client: same honest degrade', () => {
|
|
34
|
+
const r = resolveUpdateCopy({ target: 'client', shownItems: 0, copy })
|
|
35
|
+
expect(r.title).toBe('New update available')
|
|
36
|
+
expect(r.body).toBe(copy.availableBodyNoChangelog)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure copy-selection for the updates overlay's "available" state.
|
|
3
|
+
*
|
|
4
|
+
* Names the update target (client vs the connected backend in remote mode) and
|
|
5
|
+
* degrades honestly when there's no commit changelog to show (e.g. a pip /
|
|
6
|
+
* non-git backend where `git log` yields nothing) instead of generic filler.
|
|
7
|
+
*
|
|
8
|
+
* Extracted from updates-overlay.tsx so the wording logic is unit-testable.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type UpdateTarget = 'client' | 'backend'
|
|
12
|
+
|
|
13
|
+
export interface UpdateCopyStrings {
|
|
14
|
+
availableTitle: string
|
|
15
|
+
availableBody: string
|
|
16
|
+
availableTitleBackend: string
|
|
17
|
+
availableBodyBackend: string
|
|
18
|
+
availableBodyNoChangelog: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ResolveUpdateCopyInput {
|
|
22
|
+
target: UpdateTarget
|
|
23
|
+
/** Number of commit rows actually shown in the changelog. 0 → no notes. */
|
|
24
|
+
shownItems: number
|
|
25
|
+
copy: UpdateCopyStrings
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UpdateCopyResult {
|
|
29
|
+
title: string
|
|
30
|
+
body: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveUpdateCopy({ target, shownItems, copy }: ResolveUpdateCopyInput): UpdateCopyResult {
|
|
34
|
+
const title = target === 'backend' ? copy.availableTitleBackend : copy.availableTitle
|
|
35
|
+
|
|
36
|
+
const body =
|
|
37
|
+
shownItems === 0
|
|
38
|
+
? copy.availableBodyNoChangelog
|
|
39
|
+
: target === 'backend'
|
|
40
|
+
? copy.availableBodyBackend
|
|
41
|
+
: copy.availableBody
|
|
42
|
+
|
|
43
|
+
return { title, body }
|
|
44
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One-shot enter animation via the Web Animations API.
|
|
5
|
+
*
|
|
6
|
+
* Returns a callback ref. The animation fires exactly once when the element
|
|
7
|
+
* first attaches to the DOM and never replays for an already-mounted node —
|
|
8
|
+
* this is deliberate. CSS-transition + `@starting-style` is fragile here
|
|
9
|
+
* because:
|
|
10
|
+
* - Streaming deltas constantly invalidate ancestor state, which can
|
|
11
|
+
* re-trigger transitions on unrelated descendants.
|
|
12
|
+
* - `@starting-style` only covers DOM insertion / first-match, but any
|
|
13
|
+
* style restart during the message lifecycle replays the transition.
|
|
14
|
+
* - Some Chromium versions reset transitions when an attribute on an
|
|
15
|
+
* ancestor toggles, even if the descendant's properties never change.
|
|
16
|
+
*
|
|
17
|
+
* `el.animate(...)` runs against the element directly and is independent of
|
|
18
|
+
* CSS rule churn — it plays once, finishes, and is done. If the element
|
|
19
|
+
* unmounts and re-mounts, the callback ref runs again and replays it
|
|
20
|
+
* (correct behaviour).
|
|
21
|
+
*
|
|
22
|
+
* `enabled` is captured at mount-time only — flipping it later doesn't
|
|
23
|
+
* suddenly play the animation on existing nodes.
|
|
24
|
+
*/
|
|
25
|
+
const playedAnimationKeys = new Set<string>()
|
|
26
|
+
const playedAnimationOrder: string[] = []
|
|
27
|
+
const MAX_TRACKED_KEYS = 2048
|
|
28
|
+
|
|
29
|
+
function hasPlayedAnimation(key: string): boolean {
|
|
30
|
+
return playedAnimationKeys.has(key)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function rememberPlayedAnimation(key: string): void {
|
|
34
|
+
if (playedAnimationKeys.has(key)) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
playedAnimationKeys.add(key)
|
|
39
|
+
playedAnimationOrder.push(key)
|
|
40
|
+
|
|
41
|
+
if (playedAnimationOrder.length > MAX_TRACKED_KEYS) {
|
|
42
|
+
const evicted = playedAnimationOrder.shift()
|
|
43
|
+
|
|
44
|
+
if (evicted) {
|
|
45
|
+
playedAnimationKeys.delete(evicted)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function scheduleMicrotask(cb: () => void): void {
|
|
51
|
+
if (typeof queueMicrotask === 'function') {
|
|
52
|
+
queueMicrotask(cb)
|
|
53
|
+
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void Promise.resolve().then(cb)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useEnterAnimation(enabled: boolean, animationKey?: string): (el: HTMLElement | null) => void {
|
|
61
|
+
const enabledRef = useRef(enabled)
|
|
62
|
+
const keyRef = useRef(animationKey)
|
|
63
|
+
|
|
64
|
+
enabledRef.current = enabled
|
|
65
|
+
keyRef.current = animationKey
|
|
66
|
+
|
|
67
|
+
return useCallback((el: HTMLElement | null) => {
|
|
68
|
+
if (!el || !enabledRef.current || typeof window === 'undefined') {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const key = keyRef.current
|
|
77
|
+
|
|
78
|
+
if (key && hasPlayedAnimation(key)) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
el.animate(
|
|
83
|
+
[
|
|
84
|
+
{ opacity: 0, transform: 'translateY(0.375rem)' },
|
|
85
|
+
{ opacity: 1, transform: 'translateY(0)' }
|
|
86
|
+
],
|
|
87
|
+
{ duration: 180, easing: 'cubic-bezier(0.16, 1, 0.3, 1)', fill: 'both' }
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if (key) {
|
|
91
|
+
// In React StrictMode the first mount can be immediately torn down.
|
|
92
|
+
// Only persist "played" once the element survives to the microtask tick.
|
|
93
|
+
scheduleMicrotask(() => {
|
|
94
|
+
if (el.isConnected) {
|
|
95
|
+
rememberPlayedAnimation(key)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}, [])
|
|
100
|
+
}
|