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,322 @@
|
|
|
1
|
+
// Measure render cost of a synthetic stream driven through the live $messages atom.
|
|
2
|
+
//
|
|
3
|
+
// Why synthetic: the user's LLM credits are depleted; we can't fire a real stream.
|
|
4
|
+
// The synthetic stream exercises the exact same React pipeline (assistant-ui runtime →
|
|
5
|
+
// repository.addOrUpdateMessage → MessagePrimitive re-render → markdown reflow) as a
|
|
6
|
+
// real stream. The only thing it does NOT exercise is the gateway → SSE → optimistic-
|
|
7
|
+
// merge path, which is orthogonal to the rendering question.
|
|
8
|
+
//
|
|
9
|
+
// What we record:
|
|
10
|
+
// 1) rAF frame intervals (long-frame histogram; >33ms = perceived jank, >100ms = bad)
|
|
11
|
+
// 2) PerformanceObserver `longtask` entries (task >50ms blocks input)
|
|
12
|
+
// 3) MutationObserver: per-message mutation count & inter-mutation latency
|
|
13
|
+
// 4) Optional: typing latency overlay — typing into composer while streaming
|
|
14
|
+
//
|
|
15
|
+
// Output is plain text suitable for terminal + a JSON sidecar for diffing across runs.
|
|
16
|
+
|
|
17
|
+
import { writeFileSync } from 'node:fs'
|
|
18
|
+
|
|
19
|
+
const CDP_HTTP = 'http://127.0.0.1:9222'
|
|
20
|
+
const TOKENS = Number(process.env.TOKENS || 300)
|
|
21
|
+
const INTERVAL_MS = Number(process.env.INTERVAL_MS || 16)
|
|
22
|
+
// Upstream flush throttle to apply in the synthetic driver. Mirrors what the
|
|
23
|
+
// real gateway path does in `use-message-stream.scheduleDeltaFlush`. 0
|
|
24
|
+
// disables (worst-case, every token = one React commit).
|
|
25
|
+
const FLUSH_MIN_MS = Number(process.env.FLUSH_MIN_MS || 0)
|
|
26
|
+
const CHUNK = process.env.CHUNK || 'lorem ipsum '
|
|
27
|
+
const TYPE_WHILE_STREAMING = process.env.TYPE_WHILE_STREAMING === '1'
|
|
28
|
+
const LABEL = process.env.LABEL || 'baseline'
|
|
29
|
+
const OUT = process.env.OUT || `frame-times-${LABEL}.json`
|
|
30
|
+
|
|
31
|
+
async function getTarget() {
|
|
32
|
+
const list = await (await fetch(`${CDP_HTTP}/json`)).json()
|
|
33
|
+
const t = list.find((t) => t.type === 'page' && /5174/.test(t.url))
|
|
34
|
+
if (!t) throw new Error('renderer not found')
|
|
35
|
+
return t
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class CDP {
|
|
39
|
+
constructor(ws) { this.ws = ws; this.id = 0; this.pending = new Map() }
|
|
40
|
+
static async open(url) {
|
|
41
|
+
const ws = new WebSocket(url)
|
|
42
|
+
await new Promise((r, j) => {
|
|
43
|
+
ws.addEventListener('open', r, { once: true })
|
|
44
|
+
ws.addEventListener('error', (e) => j(e), { once: true })
|
|
45
|
+
})
|
|
46
|
+
const cdp = new CDP(ws)
|
|
47
|
+
ws.addEventListener('message', (ev) => {
|
|
48
|
+
const m = JSON.parse(ev.data.toString())
|
|
49
|
+
if (m.id != null && cdp.pending.has(m.id)) {
|
|
50
|
+
const { resolve, reject } = cdp.pending.get(m.id)
|
|
51
|
+
cdp.pending.delete(m.id)
|
|
52
|
+
if (m.error) reject(new Error(m.error.message))
|
|
53
|
+
else resolve(m.result)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
return cdp
|
|
57
|
+
}
|
|
58
|
+
send(method, params) {
|
|
59
|
+
const id = ++this.id
|
|
60
|
+
return new Promise((res, rej) => {
|
|
61
|
+
this.pending.set(id, { resolve: res, reject: rej })
|
|
62
|
+
this.ws.send(JSON.stringify({ id, method, params }))
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
async eval(expr) {
|
|
66
|
+
const r = await this.send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true })
|
|
67
|
+
if (r.exceptionDetails) throw new Error(r.exceptionDetails.exception?.description || 'eval')
|
|
68
|
+
return r.result.value
|
|
69
|
+
}
|
|
70
|
+
close() { this.ws.close() }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function pct(arr, p) {
|
|
74
|
+
if (!arr.length) return 0
|
|
75
|
+
const i = Math.min(arr.length - 1, Math.floor(arr.length * p))
|
|
76
|
+
return arr[i]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function main() {
|
|
80
|
+
const target = await getTarget()
|
|
81
|
+
const cdp = await CDP.open(target.webSocketDebuggerUrl)
|
|
82
|
+
|
|
83
|
+
// Sanity check driver is loaded.
|
|
84
|
+
const probeOk = await cdp.eval('!!window.__PERF_DRIVE__ && !!window.__PERF_DRIVE__.stream')
|
|
85
|
+
if (!probeOk) {
|
|
86
|
+
console.error('__PERF_DRIVE__ not on window — did you reload the renderer after editing perf-probe.tsx?')
|
|
87
|
+
cdp.close()
|
|
88
|
+
process.exit(2)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Install recorders.
|
|
92
|
+
await cdp.eval(`
|
|
93
|
+
(() => {
|
|
94
|
+
window.__FT__ = { times: [], stop: false }
|
|
95
|
+
let last = performance.now()
|
|
96
|
+
const tick = () => {
|
|
97
|
+
if (window.__FT__.stop) return
|
|
98
|
+
const now = performance.now()
|
|
99
|
+
window.__FT__.times.push(now - last)
|
|
100
|
+
last = now
|
|
101
|
+
requestAnimationFrame(tick)
|
|
102
|
+
}
|
|
103
|
+
requestAnimationFrame(tick)
|
|
104
|
+
|
|
105
|
+
window.__LT__ = { entries: [], stop: false }
|
|
106
|
+
try {
|
|
107
|
+
const po = new PerformanceObserver((list) => {
|
|
108
|
+
if (window.__LT__.stop) return
|
|
109
|
+
for (const e of list.getEntries()) {
|
|
110
|
+
window.__LT__.entries.push({ name: e.name, duration: e.duration, startTime: e.startTime })
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
po.observe({ entryTypes: ['longtask'] })
|
|
114
|
+
window.__LT__.po = po
|
|
115
|
+
} catch {}
|
|
116
|
+
|
|
117
|
+
window.__MO__ = { mutations: [], stop: false, currentMsg: null }
|
|
118
|
+
const arm = () => {
|
|
119
|
+
const all = document.querySelectorAll('[data-slot="aui_assistant-message-root"]')
|
|
120
|
+
const last = all[all.length - 1]
|
|
121
|
+
if (!last || last === window.__MO__.currentMsg) return
|
|
122
|
+
window.__MO__.currentMsg = last
|
|
123
|
+
if (window.__MO__.obs) window.__MO__.obs.disconnect()
|
|
124
|
+
const obs = new MutationObserver((muts) => {
|
|
125
|
+
if (window.__MO__.stop) return
|
|
126
|
+
const t = performance.now()
|
|
127
|
+
window.__MO__.mutations.push({ t, count: muts.length, len: last.textContent.length })
|
|
128
|
+
})
|
|
129
|
+
obs.observe(last, { childList: true, subtree: true, characterData: true })
|
|
130
|
+
window.__MO__.obs = obs
|
|
131
|
+
}
|
|
132
|
+
window.__MO__.arm = arm
|
|
133
|
+
|
|
134
|
+
// Optional: typing observer — fires keystroke timings if asked.
|
|
135
|
+
window.__TYP__ = { times: [], stop: false, lastKey: 0 }
|
|
136
|
+
return 'recorders armed'
|
|
137
|
+
})()
|
|
138
|
+
`)
|
|
139
|
+
|
|
140
|
+
// Baseline state.
|
|
141
|
+
const base = JSON.parse(await cdp.eval(`
|
|
142
|
+
JSON.stringify({
|
|
143
|
+
assistantCount: document.querySelectorAll('[data-slot="aui_assistant-message-root"]').length,
|
|
144
|
+
atomCount: window.__PERF_DRIVE__.snapshotMsgs()
|
|
145
|
+
})
|
|
146
|
+
`))
|
|
147
|
+
console.log('baseline:', base)
|
|
148
|
+
|
|
149
|
+
// Drive a synthetic stream.
|
|
150
|
+
const streamStart = Date.now()
|
|
151
|
+
await cdp.eval(`window.__PERF_DRIVE__.stream({ chunk: ${JSON.stringify(CHUNK)}, intervalMs: ${INTERVAL_MS}, totalTokens: ${TOKENS}, flushMinMs: ${FLUSH_MIN_MS} })`)
|
|
152
|
+
|
|
153
|
+
// After the first paint, arm MO on the new message.
|
|
154
|
+
await new Promise((r) => setTimeout(r, 200))
|
|
155
|
+
await cdp.eval('window.__MO__.arm()')
|
|
156
|
+
|
|
157
|
+
// Optional: type while streaming.
|
|
158
|
+
if (TYPE_WHILE_STREAMING) {
|
|
159
|
+
await new Promise((r) => setTimeout(r, 400))
|
|
160
|
+
await cdp.eval(`(() => {
|
|
161
|
+
const ed = document.querySelector('[contenteditable="true"]')
|
|
162
|
+
ed.focus()
|
|
163
|
+
window.__TYP__.startedAt = performance.now()
|
|
164
|
+
const text = 'the quick brown fox jumps over the lazy dog '
|
|
165
|
+
let i = 0
|
|
166
|
+
const tick = () => {
|
|
167
|
+
if (i >= text.length) return
|
|
168
|
+
const t0 = performance.now()
|
|
169
|
+
document.execCommand('insertText', false, text[i])
|
|
170
|
+
// requestAnimationFrame to wait for next paint
|
|
171
|
+
requestAnimationFrame(() => {
|
|
172
|
+
window.__TYP__.times.push(performance.now() - t0)
|
|
173
|
+
})
|
|
174
|
+
i++
|
|
175
|
+
setTimeout(tick, 60)
|
|
176
|
+
}
|
|
177
|
+
tick()
|
|
178
|
+
return 'typing'
|
|
179
|
+
})()`)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Wait for stream to complete + small grace.
|
|
183
|
+
const expectedMs = TOKENS * INTERVAL_MS + 1500
|
|
184
|
+
await new Promise((r) => setTimeout(r, expectedMs))
|
|
185
|
+
|
|
186
|
+
// Pull recordings.
|
|
187
|
+
const data = JSON.parse(await cdp.eval(`
|
|
188
|
+
(() => {
|
|
189
|
+
window.__FT__.stop = true
|
|
190
|
+
window.__LT__.stop = true
|
|
191
|
+
window.__MO__.stop = true
|
|
192
|
+
window.__TYP__.stop = true
|
|
193
|
+
try { window.__LT__.po && window.__LT__.po.disconnect() } catch {}
|
|
194
|
+
try { window.__MO__.obs && window.__MO__.obs.disconnect() } catch {}
|
|
195
|
+
return JSON.stringify({
|
|
196
|
+
frames: window.__FT__.times,
|
|
197
|
+
longtasks: window.__LT__.entries,
|
|
198
|
+
mutations: window.__MO__.mutations,
|
|
199
|
+
typing: window.__TYP__.times,
|
|
200
|
+
finalText: (() => { const a = document.querySelectorAll('[data-slot="aui_assistant-message-root"]'); return a.length ? a[a.length-1].textContent.length : 0 })()
|
|
201
|
+
})
|
|
202
|
+
})()
|
|
203
|
+
`))
|
|
204
|
+
|
|
205
|
+
// Reset DOM back to baseline so we don't accumulate fake messages.
|
|
206
|
+
await cdp.eval('window.__PERF_DRIVE__.reset()')
|
|
207
|
+
|
|
208
|
+
// Analysis (trim warm-up: drop frames before first mutation timestamp).
|
|
209
|
+
const firstMut = data.mutations[0]?.t
|
|
210
|
+
const frames = data.frames
|
|
211
|
+
|
|
212
|
+
// Sum durations to figure out when each frame happened (relative to recorder start).
|
|
213
|
+
const frameTimeline = []
|
|
214
|
+
let acc = 0
|
|
215
|
+
for (const f of frames) { acc += f; frameTimeline.push(acc) }
|
|
216
|
+
|
|
217
|
+
// Mutations are in performance.now() ms; frames started recording when we installed
|
|
218
|
+
// the recorder (before stream). To align: compute total stream window from frames
|
|
219
|
+
// after mutation activity began. Simpler heuristic: drop first 500ms of frames as warm-up.
|
|
220
|
+
const WARMUP_MS = 500
|
|
221
|
+
let dropIdx = 0
|
|
222
|
+
for (let i = 0; i < frames.length; i++) {
|
|
223
|
+
if (frameTimeline[i] >= WARMUP_MS) { dropIdx = i; break }
|
|
224
|
+
}
|
|
225
|
+
const streamFrames = frames.slice(dropIdx)
|
|
226
|
+
|
|
227
|
+
const buckets = { '<=16.7': 0, '16.7-33': 0, '33-50': 0, '50-100': 0, '100-200': 0, '>200': 0 }
|
|
228
|
+
let frameTotal = 0
|
|
229
|
+
let maxFrame = 0
|
|
230
|
+
for (const f of streamFrames) {
|
|
231
|
+
frameTotal += f
|
|
232
|
+
if (f > maxFrame) maxFrame = f
|
|
233
|
+
if (f <= 16.7) buckets['<=16.7']++
|
|
234
|
+
else if (f <= 33) buckets['16.7-33']++
|
|
235
|
+
else if (f <= 50) buckets['33-50']++
|
|
236
|
+
else if (f <= 100) buckets['50-100']++
|
|
237
|
+
else if (f <= 200) buckets['100-200']++
|
|
238
|
+
else buckets['>200']++
|
|
239
|
+
}
|
|
240
|
+
const sortedFrames = [...streamFrames].sort((a, b) => a - b)
|
|
241
|
+
const fAvgFps = streamFrames.length ? (streamFrames.length / (frameTotal / 1000)).toFixed(1) : 'n/a'
|
|
242
|
+
const fP50 = pct(sortedFrames, 0.5).toFixed(1)
|
|
243
|
+
const fP95 = pct(sortedFrames, 0.95).toFixed(1)
|
|
244
|
+
const fP99 = pct(sortedFrames, 0.99).toFixed(1)
|
|
245
|
+
const slowFrames = streamFrames.filter((f) => f > 33).length
|
|
246
|
+
const veryslowFrames = streamFrames.filter((f) => f > 100).length
|
|
247
|
+
|
|
248
|
+
const ltDur = data.longtasks.map((e) => e.duration).sort((a, b) => a - b)
|
|
249
|
+
const ltMs = ltDur.reduce((a, b) => a + b, 0)
|
|
250
|
+
const ltMax = ltDur.length ? ltDur[ltDur.length - 1] : 0
|
|
251
|
+
const ltP95 = pct(ltDur, 0.95)
|
|
252
|
+
|
|
253
|
+
// Mutation cadence.
|
|
254
|
+
const mutDurs = []
|
|
255
|
+
for (let i = 1; i < data.mutations.length; i++) mutDurs.push(data.mutations[i].t - data.mutations[i - 1].t)
|
|
256
|
+
mutDurs.sort((a, b) => a - b)
|
|
257
|
+
const mutP50 = pct(mutDurs, 0.5)
|
|
258
|
+
const mutP95 = pct(mutDurs, 0.95)
|
|
259
|
+
const mutMax = mutDurs.length ? mutDurs[mutDurs.length - 1] : 0
|
|
260
|
+
|
|
261
|
+
// Typing latency (optional).
|
|
262
|
+
let typingSummary = null
|
|
263
|
+
if (TYPE_WHILE_STREAMING && data.typing.length) {
|
|
264
|
+
const t = [...data.typing].sort((a, b) => a - b)
|
|
265
|
+
typingSummary = {
|
|
266
|
+
n: t.length,
|
|
267
|
+
p50: pct(t, 0.5).toFixed(1),
|
|
268
|
+
p95: pct(t, 0.95).toFixed(1),
|
|
269
|
+
max: t[t.length - 1].toFixed(1)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const result = {
|
|
274
|
+
label: LABEL,
|
|
275
|
+
timestamp: new Date().toISOString(),
|
|
276
|
+
config: { TOKENS, INTERVAL_MS, CHUNK, TYPE_WHILE_STREAMING, FLUSH_MIN_MS },
|
|
277
|
+
streamWallMs: Date.now() - streamStart,
|
|
278
|
+
frames: {
|
|
279
|
+
total: streamFrames.length,
|
|
280
|
+
avgFps: fAvgFps,
|
|
281
|
+
windowS: (frameTotal / 1000).toFixed(1),
|
|
282
|
+
p50: fP50,
|
|
283
|
+
p95: fP95,
|
|
284
|
+
p99: fP99,
|
|
285
|
+
max: maxFrame.toFixed(1),
|
|
286
|
+
slow33: slowFrames,
|
|
287
|
+
veryslow100: veryslowFrames,
|
|
288
|
+
histogram: buckets
|
|
289
|
+
},
|
|
290
|
+
longtasks: {
|
|
291
|
+
n: data.longtasks.length,
|
|
292
|
+
totalMs: ltMs.toFixed(0),
|
|
293
|
+
maxMs: ltMax.toFixed(1),
|
|
294
|
+
p95Ms: ltP95.toFixed(1)
|
|
295
|
+
},
|
|
296
|
+
mutations: {
|
|
297
|
+
n: data.mutations.length,
|
|
298
|
+
finalTextLen: data.finalText,
|
|
299
|
+
interMutP50ms: mutP50.toFixed(1),
|
|
300
|
+
interMutP95ms: mutP95.toFixed(1),
|
|
301
|
+
interMutMaxMs: mutMax.toFixed(1)
|
|
302
|
+
},
|
|
303
|
+
typing: typingSummary
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
writeFileSync(OUT, JSON.stringify(result, null, 2))
|
|
307
|
+
|
|
308
|
+
console.log('\n=== SYNTHETIC STREAM RESULTS ===')
|
|
309
|
+
console.log('label:', LABEL, '| tokens:', TOKENS, '@', INTERVAL_MS, 'ms')
|
|
310
|
+
console.log('streamWallMs:', result.streamWallMs)
|
|
311
|
+
console.log('FRAMES: avgFps', fAvgFps, '| p50', fP50, 'ms | p95', fP95, 'ms | p99', fP99, 'ms | max', maxFrame.toFixed(1), 'ms')
|
|
312
|
+
console.log('FRAMES histogram:', buckets)
|
|
313
|
+
console.log('FRAMES slow(>33):', slowFrames, '/ veryslow(>100):', veryslowFrames, 'of', streamFrames.length)
|
|
314
|
+
console.log('LONGTASKS:', data.longtasks.length, '| total', ltMs.toFixed(0), 'ms | max', ltMax.toFixed(1), 'ms | p95', ltP95.toFixed(1), 'ms')
|
|
315
|
+
console.log('MUTATIONS:', data.mutations.length, '| finalLen', data.finalText, 'chars | inter p50', mutP50.toFixed(1), 'ms | p95', mutP95.toFixed(1), 'ms')
|
|
316
|
+
if (typingSummary) console.log('TYPING-WHILE-STREAMING latency: p50', typingSummary.p50, 'ms | p95', typingSummary.p95, 'ms | n=', typingSummary.n)
|
|
317
|
+
console.log('written to', OUT)
|
|
318
|
+
|
|
319
|
+
cdp.close()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
main().catch((e) => { console.error(e); process.exit(1) })
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const fs = require('node:fs')
|
|
2
|
+
const os = require('node:os')
|
|
3
|
+
const path = require('node:path')
|
|
4
|
+
const { execFile } = require('node:child_process')
|
|
5
|
+
|
|
6
|
+
function run(command, args) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
execFile(command, args, (error, stdout, stderr) => {
|
|
9
|
+
if (error) {
|
|
10
|
+
// Intentionally omit args from the rejection message: callers pass
|
|
11
|
+
// notarization credentials (key id, issuer, key file path) here, and
|
|
12
|
+
// surfacing them in error output would land in CI logs.
|
|
13
|
+
reject(new Error(`${command} failed: ${stderr?.trim() || stdout?.trim() || error.message}`))
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
resolve()
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function inlineKeyLooksValid(value) {
|
|
22
|
+
return value.includes('BEGIN PRIVATE KEY') && value.includes('END PRIVATE KEY')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveApiKeyPath(rawValue) {
|
|
26
|
+
const value = String(rawValue || '').trim()
|
|
27
|
+
if (!value) return { keyPath: '', cleanup: () => {} }
|
|
28
|
+
|
|
29
|
+
if (fs.existsSync(value)) {
|
|
30
|
+
return { keyPath: value, cleanup: () => {} }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!inlineKeyLooksValid(value)) {
|
|
34
|
+
throw new Error('APPLE_API_KEY must be a file path or inline .p8 key content')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const tempPath = path.join(os.tmpdir(), `nastech-notary-${Date.now()}-${process.pid}.p8`)
|
|
38
|
+
fs.writeFileSync(tempPath, value, 'utf8')
|
|
39
|
+
return {
|
|
40
|
+
keyPath: tempPath,
|
|
41
|
+
cleanup: () => fs.rmSync(tempPath, { force: true })
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main() {
|
|
46
|
+
const artifactPath = process.argv[2]
|
|
47
|
+
if (!artifactPath || !fs.existsSync(artifactPath)) {
|
|
48
|
+
throw new Error(`Missing artifact to notarize: ${artifactPath || '(none)'}`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const profile = String(process.env.APPLE_NOTARY_PROFILE || '').trim()
|
|
52
|
+
if (profile) {
|
|
53
|
+
await run('xcrun', ['notarytool', 'submit', artifactPath, '--keychain-profile', profile, '--wait'])
|
|
54
|
+
await run('xcrun', ['stapler', 'staple', '-v', artifactPath])
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const keyId = String(process.env.APPLE_API_KEY_ID || '').trim()
|
|
59
|
+
const issuer = String(process.env.APPLE_API_ISSUER || '').trim()
|
|
60
|
+
const rawApiKey = process.env.APPLE_API_KEY
|
|
61
|
+
if (!rawApiKey || !keyId || !issuer) {
|
|
62
|
+
throw new Error('APPLE_API_KEY, APPLE_API_KEY_ID, and APPLE_API_ISSUER are required')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { keyPath, cleanup } = resolveApiKeyPath(rawApiKey)
|
|
66
|
+
try {
|
|
67
|
+
await run('xcrun', ['notarytool', 'submit', artifactPath, '--key', keyPath, '--key-id', keyId, '--issuer', issuer, '--wait'])
|
|
68
|
+
await run('xcrun', ['stapler', 'staple', '-v', artifactPath])
|
|
69
|
+
} finally {
|
|
70
|
+
cleanup()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
main().catch(() => {
|
|
75
|
+
console.error('Notarization failed. Check configuration and command output in secure CI logs.')
|
|
76
|
+
process.exit(1)
|
|
77
|
+
})
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const fs = require('node:fs')
|
|
2
|
+
const os = require('node:os')
|
|
3
|
+
const path = require('node:path')
|
|
4
|
+
const { execFile } = require('node:child_process')
|
|
5
|
+
|
|
6
|
+
function run(command, args) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
execFile(command, args, (error, stdout, stderr) => {
|
|
9
|
+
if (error) {
|
|
10
|
+
reject(
|
|
11
|
+
new Error(
|
|
12
|
+
`${command} ${args.join(' ')} failed: ${stderr?.trim() || stdout?.trim() || error.message}`
|
|
13
|
+
)
|
|
14
|
+
)
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
resolve({ stdout, stderr })
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function inlineKeyLooksValid(value) {
|
|
23
|
+
return value.includes('BEGIN PRIVATE KEY') && value.includes('END PRIVATE KEY')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveApiKeyPath(rawValue) {
|
|
27
|
+
const value = String(rawValue || '').trim()
|
|
28
|
+
if (!value) return { keyPath: '', cleanup: () => {} }
|
|
29
|
+
|
|
30
|
+
if (fs.existsSync(value)) {
|
|
31
|
+
return { keyPath: value, cleanup: () => {} }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!inlineKeyLooksValid(value)) {
|
|
35
|
+
throw new Error('APPLE_API_KEY must be a file path or inline .p8 key content')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const tempPath = path.join(os.tmpdir(), `nastech-notary-${Date.now()}-${process.pid}.p8`)
|
|
39
|
+
fs.writeFileSync(tempPath, value, 'utf8')
|
|
40
|
+
return {
|
|
41
|
+
keyPath: tempPath,
|
|
42
|
+
cleanup: () => {
|
|
43
|
+
try {
|
|
44
|
+
fs.rmSync(tempPath, { force: true })
|
|
45
|
+
} catch {
|
|
46
|
+
// Best-effort cleanup.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
exports.default = async function notarize(context) {
|
|
53
|
+
const { electronPlatformName, appOutDir, packager } = context
|
|
54
|
+
if (electronPlatformName !== 'darwin') return
|
|
55
|
+
|
|
56
|
+
const appName = packager.appInfo.productFilename
|
|
57
|
+
const appPath = path.join(appOutDir, `${appName}.app`)
|
|
58
|
+
if (!fs.existsSync(appPath)) {
|
|
59
|
+
throw new Error(`Cannot notarize missing app bundle: ${appPath}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const profile = String(process.env.APPLE_NOTARY_PROFILE || '').trim()
|
|
63
|
+
if (profile) {
|
|
64
|
+
const zipPath = path.join(appOutDir, `${appName}.zip`)
|
|
65
|
+
await run('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', appPath, zipPath])
|
|
66
|
+
await run('xcrun', ['notarytool', 'submit', zipPath, '--keychain-profile', profile, '--wait'])
|
|
67
|
+
await run('xcrun', ['stapler', 'staple', '-v', appPath])
|
|
68
|
+
try {
|
|
69
|
+
fs.rmSync(zipPath, { force: true })
|
|
70
|
+
} catch {
|
|
71
|
+
// Best-effort cleanup.
|
|
72
|
+
}
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const keyId = String(process.env.APPLE_API_KEY_ID || '').trim()
|
|
77
|
+
const issuer = String(process.env.APPLE_API_ISSUER || '').trim()
|
|
78
|
+
const rawApiKey = process.env.APPLE_API_KEY
|
|
79
|
+
if (!rawApiKey || !keyId || !issuer) {
|
|
80
|
+
console.log(
|
|
81
|
+
'Skipping notarization: APPLE_API_KEY, APPLE_API_KEY_ID, and APPLE_API_ISSUER are not fully configured.'
|
|
82
|
+
)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const { keyPath, cleanup } = resolveApiKeyPath(rawApiKey)
|
|
87
|
+
const zipPath = path.join(appOutDir, `${appName}.zip`)
|
|
88
|
+
try {
|
|
89
|
+
await run('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', appPath, zipPath])
|
|
90
|
+
await run('xcrun', ['notarytool', 'submit', zipPath, '--key', keyPath, '--key-id', keyId, '--issuer', issuer, '--wait'])
|
|
91
|
+
await run('xcrun', ['stapler', 'staple', '-v', appPath])
|
|
92
|
+
} finally {
|
|
93
|
+
try {
|
|
94
|
+
fs.rmSync(zipPath, { force: true })
|
|
95
|
+
} catch {
|
|
96
|
+
// Best-effort cleanup.
|
|
97
|
+
}
|
|
98
|
+
cleanup()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const fs = require('node:fs')
|
|
2
|
+
const path = require('node:path')
|
|
3
|
+
|
|
4
|
+
if (process.platform !== 'darwin') {
|
|
5
|
+
process.exit(0)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const desktopRoot = path.resolve(__dirname, '..')
|
|
9
|
+
const repoRoot = path.resolve(desktopRoot, '..', '..')
|
|
10
|
+
const electronMacPath = path.join(repoRoot, 'node_modules', 'app-builder-lib', 'out', 'electron', 'electronMac.js')
|
|
11
|
+
|
|
12
|
+
const marker = 'nastech-macos-electron-binary-fallback'
|
|
13
|
+
const needle = ` await Promise.all([
|
|
14
|
+
doRename(path.join(contentsPath, "MacOS"), electronBranding.productName, appPlist.CFBundleExecutable),
|
|
15
|
+
(0, builder_util_1.unlinkIfExists)(path.join(appOutDir, "LICENSE")),
|
|
16
|
+
(0, builder_util_1.unlinkIfExists)(path.join(appOutDir, "LICENSES.chromium.html")),
|
|
17
|
+
]);`
|
|
18
|
+
const replacement = ` // ${marker}: electron-builder 26.8.x can sometimes copy
|
|
19
|
+
// Electron.app without its main MacOS/Electron binary before this rename.
|
|
20
|
+
// Restore it from the installed Electron runtime so local desktop installs
|
|
21
|
+
// do not fail with ENOENT during macOS arm64 packaging.
|
|
22
|
+
const macosDir = path.join(contentsPath, "MacOS");
|
|
23
|
+
const bundledElectronBinary = path.join(macosDir, electronBranding.productName);
|
|
24
|
+
if (!fs.existsSync(bundledElectronBinary)) {
|
|
25
|
+
const candidates = [
|
|
26
|
+
path.join(packager.info.framework.distMacOsAppName, "Contents", "MacOS", electronBranding.productName),
|
|
27
|
+
path.join(process.cwd(), "..", "..", "node_modules", "electron", "dist", "Electron.app", "Contents", "MacOS", electronBranding.productName),
|
|
28
|
+
];
|
|
29
|
+
const sourceBinary = candidates.find(candidate => fs.existsSync(candidate));
|
|
30
|
+
if (sourceBinary == null) {
|
|
31
|
+
throw new Error("Electron binary missing from packaged app and Electron runtime: " + bundledElectronBinary);
|
|
32
|
+
}
|
|
33
|
+
await (0, promises_1.copyFile)(sourceBinary, bundledElectronBinary);
|
|
34
|
+
await (0, promises_1.chmod)(bundledElectronBinary, 0o755);
|
|
35
|
+
}
|
|
36
|
+
await Promise.all([
|
|
37
|
+
doRename(macosDir, electronBranding.productName, appPlist.CFBundleExecutable),
|
|
38
|
+
(0, builder_util_1.unlinkIfExists)(path.join(appOutDir, "LICENSE")),
|
|
39
|
+
(0, builder_util_1.unlinkIfExists)(path.join(appOutDir, "LICENSES.chromium.html")),
|
|
40
|
+
]);`
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(electronMacPath)) {
|
|
43
|
+
console.warn(`[patch-electron-builder] skipped: ${electronMacPath} not found`)
|
|
44
|
+
process.exit(0)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const source = fs.readFileSync(electronMacPath, 'utf8')
|
|
48
|
+
if (source.includes(marker)) {
|
|
49
|
+
console.log('[patch-electron-builder] macOS Electron binary fallback already applied')
|
|
50
|
+
process.exit(0)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!source.includes(needle)) {
|
|
54
|
+
console.warn('[patch-electron-builder] skipped: expected electronMac.js shape not found')
|
|
55
|
+
process.exit(0)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fs.writeFileSync(electronMacPath, source.replace(needle, replacement))
|
|
59
|
+
console.log('[patch-electron-builder] applied macOS Electron binary fallback')
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// quick probe — read state of the renderer
|
|
2
|
+
const list = await (await fetch('http://127.0.0.1:9222/json/list')).json()
|
|
3
|
+
const tgt = list.find(t => t.type === 'page' && t.url.startsWith('http'))
|
|
4
|
+
console.log('target:', tgt?.url)
|
|
5
|
+
if (!tgt) process.exit(1)
|
|
6
|
+
const ws = new WebSocket(tgt.webSocketDebuggerUrl)
|
|
7
|
+
let id = 0
|
|
8
|
+
const pending = new Map()
|
|
9
|
+
ws.addEventListener('message', ev => {
|
|
10
|
+
const m = JSON.parse(ev.data)
|
|
11
|
+
if (m.id != null && pending.has(m.id)) {
|
|
12
|
+
pending.get(m.id)(m)
|
|
13
|
+
pending.delete(m.id)
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
await new Promise(r => ws.addEventListener('open', r))
|
|
17
|
+
const send = (method, params = {}) =>
|
|
18
|
+
new Promise(r => {
|
|
19
|
+
const i = ++id
|
|
20
|
+
pending.set(i, r)
|
|
21
|
+
ws.send(JSON.stringify({ id: i, method, params }))
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const r = await send('Runtime.evaluate', {
|
|
25
|
+
expression: `({
|
|
26
|
+
url: location.href,
|
|
27
|
+
title: document.title,
|
|
28
|
+
rootChildren: document.getElementById('root')?.children.length ?? 0,
|
|
29
|
+
rootInner: (document.getElementById('root')?.innerHTML ?? '').slice(0, 300),
|
|
30
|
+
hasComposer: !!document.querySelector('[data-slot="composer-rich-input"]'),
|
|
31
|
+
bootStage: (document.querySelector('[data-slot*="boot"]')?.getAttribute('data-slot')) ?? null,
|
|
32
|
+
bodyText: document.body.innerText.slice(0, 300),
|
|
33
|
+
errorCount: window.__errors?.length ?? 'n/a'
|
|
34
|
+
})`,
|
|
35
|
+
returnByValue: true
|
|
36
|
+
})
|
|
37
|
+
console.log('raw:', JSON.stringify(r, null, 2))
|
|
38
|
+
ws.close()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Probe the cloud shadows thread state — count messages, turn pairs,
|
|
2
|
+
// thread height, composer state
|
|
3
|
+
const list = await (await fetch('http://127.0.0.1:9222/json/list')).json()
|
|
4
|
+
const tgt = list.find(t => t.type === 'page' && t.url.startsWith('http'))
|
|
5
|
+
const ws = new WebSocket(tgt.webSocketDebuggerUrl)
|
|
6
|
+
let id = 0
|
|
7
|
+
const pending = new Map()
|
|
8
|
+
ws.addEventListener('message', ev => {
|
|
9
|
+
const m = JSON.parse(ev.data)
|
|
10
|
+
if (m.id != null && pending.has(m.id)) {
|
|
11
|
+
pending.get(m.id)(m)
|
|
12
|
+
pending.delete(m.id)
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
await new Promise(r => ws.addEventListener('open', r))
|
|
16
|
+
const send = (m, p = {}) =>
|
|
17
|
+
new Promise(r => {
|
|
18
|
+
const i = ++id
|
|
19
|
+
pending.set(i, r)
|
|
20
|
+
ws.send(JSON.stringify({ id: i, method: m, params: p }))
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const r = await send('Runtime.evaluate', {
|
|
24
|
+
expression: `JSON.stringify({
|
|
25
|
+
url: location.href,
|
|
26
|
+
title: document.title,
|
|
27
|
+
turnPairs: document.querySelectorAll('[data-slot="aui_turn-pair"]').length,
|
|
28
|
+
assistantMsgs: document.querySelectorAll('[data-slot="aui_assistant-message-root"]').length,
|
|
29
|
+
userMsgs: document.querySelectorAll('[data-message-role="user"], [data-slot="aui_user-message-root"]').length,
|
|
30
|
+
totalDomNodes: document.querySelectorAll('*').length,
|
|
31
|
+
threadViewportScrollHeight: document.querySelector('[data-slot="aui_thread-viewport"]')?.scrollHeight ?? null,
|
|
32
|
+
threadViewportClientHeight: document.querySelector('[data-slot="aui_thread-viewport"]')?.clientHeight ?? null,
|
|
33
|
+
threadViewportScrollTop: document.querySelector('[data-slot="aui_thread-viewport"]')?.scrollTop ?? null,
|
|
34
|
+
composer: !!document.querySelector('[data-slot="composer-rich-input"]'),
|
|
35
|
+
busy: !!document.querySelector('[aria-label*="Stop"]')
|
|
36
|
+
})`,
|
|
37
|
+
returnByValue: true
|
|
38
|
+
})
|
|
39
|
+
console.log(JSON.parse(r.result.result.value))
|
|
40
|
+
ws.close()
|