nastechai-desktop 18.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (546) hide show
  1. package/.prettierrc +11 -0
  2. package/DESIGN.md +167 -0
  3. package/README.md +141 -0
  4. package/assets/icon.icns +0 -0
  5. package/assets/icon.ico +0 -0
  6. package/assets/icon.png +0 -0
  7. package/components.json +21 -0
  8. package/electron/backend-env.cjs +112 -0
  9. package/electron/backend-env.test.cjs +111 -0
  10. package/electron/backend-probes.cjs +106 -0
  11. package/electron/backend-probes.test.cjs +82 -0
  12. package/electron/backend-ready.cjs +66 -0
  13. package/electron/bootstrap-platform.cjs +91 -0
  14. package/electron/bootstrap-platform.test.cjs +111 -0
  15. package/electron/bootstrap-runner.cjs +720 -0
  16. package/electron/bootstrap-runner.test.cjs +138 -0
  17. package/electron/connection-config.cjs +254 -0
  18. package/electron/connection-config.test.cjs +329 -0
  19. package/electron/dashboard-token.cjs +99 -0
  20. package/electron/dashboard-token.test.cjs +142 -0
  21. package/electron/desktop-uninstall.cjs +232 -0
  22. package/electron/desktop-uninstall.test.cjs +246 -0
  23. package/electron/entitlements.mac.inherit.plist +14 -0
  24. package/electron/entitlements.mac.plist +14 -0
  25. package/electron/fs-read-dir.cjs +109 -0
  26. package/electron/fs-read-dir.test.cjs +364 -0
  27. package/electron/gateway-ws-probe.cjs +188 -0
  28. package/electron/gateway-ws-probe.test.cjs +122 -0
  29. package/electron/git-root.cjs +54 -0
  30. package/electron/git-root.test.cjs +40 -0
  31. package/electron/git-worktrees.cjs +174 -0
  32. package/electron/hardening.cjs +184 -0
  33. package/electron/hardening.test.cjs +116 -0
  34. package/electron/main.cjs +5762 -0
  35. package/electron/oauth-net-request.cjs +20 -0
  36. package/electron/oauth-net-request.test.cjs +34 -0
  37. package/electron/preload.cjs +135 -0
  38. package/electron/session-windows.cjs +99 -0
  39. package/electron/session-windows.test.cjs +177 -0
  40. package/electron/update-remote.cjs +56 -0
  41. package/electron/update-remote.test.cjs +78 -0
  42. package/electron/vscode-marketplace.cjs +331 -0
  43. package/electron/vscode-marketplace.test.cjs +113 -0
  44. package/electron/windows-child-process.test.cjs +57 -0
  45. package/electron/windows-user-env.cjs +76 -0
  46. package/electron/windows-user-env.test.cjs +90 -0
  47. package/electron/workspace-cwd.cjs +38 -0
  48. package/electron/workspace-cwd.test.cjs +45 -0
  49. package/eslint.config.mjs +122 -0
  50. package/index.html +17 -0
  51. package/package.json +254 -0
  52. package/pr-assets/session-source-folders.png +0 -0
  53. package/preview-demo.html +65 -0
  54. package/public/apple-touch-icon.png +0 -0
  55. package/public/ds-assets/filler-bg0.jpg +0 -0
  56. package/public/nastech-frames/nastech-frame-0.png +0 -0
  57. package/public/nastech-frames/nastech-frame-1.png +0 -0
  58. package/public/nastech-frames/nastech-frame-2.png +0 -0
  59. package/public/nastech-frames/nastech-frame-3.png +0 -0
  60. package/public/nastech-frames/nastech-frame-4.png +0 -0
  61. package/public/nastech-frames/nastech-frame-5.png +0 -0
  62. package/public/nastech-frames/nastech-frame-6.png +0 -0
  63. package/public/nastech-frames/nastech-frame-7.png +0 -0
  64. package/public/nastech-girl.jpg +0 -0
  65. package/public/nastech-sprite.png +0 -0
  66. package/public/nastech.png +0 -0
  67. package/scripts/after-pack.cjs +41 -0
  68. package/scripts/assert-dist-built.cjs +70 -0
  69. package/scripts/assert-dist-built.test.cjs +84 -0
  70. package/scripts/assert-root-install.cjs +13 -0
  71. package/scripts/before-build.cjs +11 -0
  72. package/scripts/before-pack.cjs +78 -0
  73. package/scripts/before-pack.test.cjs +53 -0
  74. package/scripts/click-session.mjs +51 -0
  75. package/scripts/dev-no-hmr.mjs +22 -0
  76. package/scripts/diag-jump.mjs +115 -0
  77. package/scripts/diag-scroll-reset.mjs +229 -0
  78. package/scripts/eval.mjs +21 -0
  79. package/scripts/leak-typing.mjs +222 -0
  80. package/scripts/measure-jump.mjs +108 -0
  81. package/scripts/measure-latency.mjs +184 -0
  82. package/scripts/measure-real-stream.mjs +252 -0
  83. package/scripts/measure-submit.mjs +179 -0
  84. package/scripts/measure-synthetic-stream.mjs +322 -0
  85. package/scripts/notarize-artifact.cjs +77 -0
  86. package/scripts/notarize.cjs +100 -0
  87. package/scripts/patch-electron-builder-mac-binary.cjs +59 -0
  88. package/scripts/probe-renderer.mjs +38 -0
  89. package/scripts/probe-thread.mjs +40 -0
  90. package/scripts/profile-long-stream.mjs +191 -0
  91. package/scripts/profile-real-stream.mjs +137 -0
  92. package/scripts/profile-synth-stream.mjs +103 -0
  93. package/scripts/profile-typing-lag.md +381 -0
  94. package/scripts/profile-typing.mjs +260 -0
  95. package/scripts/reload-renderer.mjs +25 -0
  96. package/scripts/reload.mjs +36 -0
  97. package/scripts/set-exe-identity.cjs +94 -0
  98. package/scripts/stage-native-deps.cjs +159 -0
  99. package/scripts/test-desktop.mjs +425 -0
  100. package/scripts/write-build-stamp.cjs +126 -0
  101. package/src/app/agents/index.tsx +398 -0
  102. package/src/app/artifacts/index.test.ts +62 -0
  103. package/src/app/artifacts/index.tsx +906 -0
  104. package/src/app/chat/chat-drop-overlay.tsx +48 -0
  105. package/src/app/chat/chat-swap-overlay.tsx +47 -0
  106. package/src/app/chat/composer/attachments.tsx +114 -0
  107. package/src/app/chat/composer/completion-drawer.tsx +63 -0
  108. package/src/app/chat/composer/context-menu.tsx +172 -0
  109. package/src/app/chat/composer/controls.tsx +289 -0
  110. package/src/app/chat/composer/drop-affordance.ts +2 -0
  111. package/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
  112. package/src/app/chat/composer/focus.ts +134 -0
  113. package/src/app/chat/composer/help-hint.tsx +59 -0
  114. package/src/app/chat/composer/hooks/use-at-completions.ts +141 -0
  115. package/src/app/chat/composer/hooks/use-live-completion-adapter.ts +119 -0
  116. package/src/app/chat/composer/hooks/use-mic-recorder.ts +291 -0
  117. package/src/app/chat/composer/hooks/use-slash-completions.ts +114 -0
  118. package/src/app/chat/composer/hooks/use-voice-conversation.ts +390 -0
  119. package/src/app/chat/composer/hooks/use-voice-recorder.ts +116 -0
  120. package/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
  121. package/src/app/chat/composer/index.tsx +1611 -0
  122. package/src/app/chat/composer/inline-refs.ts +138 -0
  123. package/src/app/chat/composer/model-pill.tsx +86 -0
  124. package/src/app/chat/composer/queue-panel.tsx +130 -0
  125. package/src/app/chat/composer/rich-editor.test.ts +18 -0
  126. package/src/app/chat/composer/rich-editor.ts +165 -0
  127. package/src/app/chat/composer/skin-slash-popover.tsx +61 -0
  128. package/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
  129. package/src/app/chat/composer/status-stack/index.tsx +202 -0
  130. package/src/app/chat/composer/status-stack/status-row.tsx +155 -0
  131. package/src/app/chat/composer/text-utils.test.ts +77 -0
  132. package/src/app/chat/composer/text-utils.ts +107 -0
  133. package/src/app/chat/composer/trigger-popover.test.tsx +42 -0
  134. package/src/app/chat/composer/trigger-popover.tsx +116 -0
  135. package/src/app/chat/composer/types.ts +64 -0
  136. package/src/app/chat/composer/url-dialog.tsx +82 -0
  137. package/src/app/chat/composer/voice-activity.tsx +252 -0
  138. package/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
  139. package/src/app/chat/hooks/use-composer-actions.ts +525 -0
  140. package/src/app/chat/hooks/use-file-drop-zone.ts +118 -0
  141. package/src/app/chat/index.tsx +390 -0
  142. package/src/app/chat/perf-probe.tsx +269 -0
  143. package/src/app/chat/right-rail/index.ts +1 -0
  144. package/src/app/chat/right-rail/preview-console-state.ts +82 -0
  145. package/src/app/chat/right-rail/preview-console.tsx +290 -0
  146. package/src/app/chat/right-rail/preview-file.tsx +559 -0
  147. package/src/app/chat/right-rail/preview-pane.test.tsx +43 -0
  148. package/src/app/chat/right-rail/preview-pane.tsx +657 -0
  149. package/src/app/chat/right-rail/preview.tsx +171 -0
  150. package/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
  151. package/src/app/chat/scroll-to-bottom-button.tsx +74 -0
  152. package/src/app/chat/sidebar/cron-jobs-section.tsx +325 -0
  153. package/src/app/chat/sidebar/index.tsx +1219 -0
  154. package/src/app/chat/sidebar/load-more-row.tsx +30 -0
  155. package/src/app/chat/sidebar/order.test.ts +21 -0
  156. package/src/app/chat/sidebar/order.ts +17 -0
  157. package/src/app/chat/sidebar/profile-switcher.tsx +516 -0
  158. package/src/app/chat/sidebar/session-actions-menu.tsx +264 -0
  159. package/src/app/chat/sidebar/session-row.tsx +257 -0
  160. package/src/app/chat/sidebar/virtual-session-list.tsx +154 -0
  161. package/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
  162. package/src/app/chat/sidebar/workspace-groups.ts +326 -0
  163. package/src/app/chat/thread-loading.test.ts +34 -0
  164. package/src/app/chat/thread-loading.ts +26 -0
  165. package/src/app/command-center/index.tsx +654 -0
  166. package/src/app/command-palette/index.tsx +513 -0
  167. package/src/app/command-palette/marketplace-theme-page.tsx +157 -0
  168. package/src/app/cron/index.tsx +942 -0
  169. package/src/app/cron/job-state.ts +29 -0
  170. package/src/app/desktop-controller.tsx +938 -0
  171. package/src/app/floating-hud.ts +22 -0
  172. package/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
  173. package/src/app/gateway/hooks/use-gateway-boot.ts +387 -0
  174. package/src/app/gateway/hooks/use-gateway-request.ts +138 -0
  175. package/src/app/hooks/use-keybinds.ts +186 -0
  176. package/src/app/hooks/use-refresh-hotkey.ts +45 -0
  177. package/src/app/hooks/use-route-enum-param.ts +38 -0
  178. package/src/app/index.tsx +1 -0
  179. package/src/app/layout-constants.ts +13 -0
  180. package/src/app/messaging/index.tsx +648 -0
  181. package/src/app/messaging/platform-icon.tsx +93 -0
  182. package/src/app/model-picker-overlay.tsx +42 -0
  183. package/src/app/model-visibility-overlay.tsx +31 -0
  184. package/src/app/overlays/overlay-chrome.tsx +66 -0
  185. package/src/app/overlays/overlay-search-input.tsx +33 -0
  186. package/src/app/overlays/overlay-split-layout.tsx +130 -0
  187. package/src/app/overlays/overlay-view.tsx +91 -0
  188. package/src/app/page-search-shell.tsx +75 -0
  189. package/src/app/profiles/create-profile-dialog.tsx +154 -0
  190. package/src/app/profiles/delete-profile-dialog.tsx +65 -0
  191. package/src/app/profiles/index.tsx +671 -0
  192. package/src/app/profiles/rename-profile-dialog.tsx +125 -0
  193. package/src/app/right-sidebar/files/dnd-manager.ts +27 -0
  194. package/src/app/right-sidebar/files/ipc.test.ts +100 -0
  195. package/src/app/right-sidebar/files/ipc.ts +161 -0
  196. package/src/app/right-sidebar/files/remote-picker.tsx +177 -0
  197. package/src/app/right-sidebar/files/tree.tsx +224 -0
  198. package/src/app/right-sidebar/files/use-project-tree.test.ts +190 -0
  199. package/src/app/right-sidebar/files/use-project-tree.ts +268 -0
  200. package/src/app/right-sidebar/index.test.tsx +75 -0
  201. package/src/app/right-sidebar/index.tsx +395 -0
  202. package/src/app/right-sidebar/store.ts +15 -0
  203. package/src/app/right-sidebar/terminal/buffer.ts +65 -0
  204. package/src/app/right-sidebar/terminal/index.tsx +98 -0
  205. package/src/app/right-sidebar/terminal/persistent.tsx +122 -0
  206. package/src/app/right-sidebar/terminal/selection.ts +75 -0
  207. package/src/app/right-sidebar/terminal/use-terminal-session.ts +504 -0
  208. package/src/app/routes.ts +88 -0
  209. package/src/app/session/hooks/use-context-suggestions.ts +58 -0
  210. package/src/app/session/hooks/use-cwd-actions.ts +109 -0
  211. package/src/app/session/hooks/use-message-stream.ts +957 -0
  212. package/src/app/session/hooks/use-model-controls.test.tsx +198 -0
  213. package/src/app/session/hooks/use-model-controls.ts +106 -0
  214. package/src/app/session/hooks/use-nastech-config.ts +74 -0
  215. package/src/app/session/hooks/use-preview-routing.test.tsx +168 -0
  216. package/src/app/session/hooks/use-preview-routing.ts +223 -0
  217. package/src/app/session/hooks/use-prompt-actions.test.tsx +316 -0
  218. package/src/app/session/hooks/use-prompt-actions.ts +1030 -0
  219. package/src/app/session/hooks/use-route-resume.test.tsx +136 -0
  220. package/src/app/session/hooks/use-route-resume.ts +115 -0
  221. package/src/app/session/hooks/use-session-actions.test.tsx +119 -0
  222. package/src/app/session/hooks/use-session-actions.ts +885 -0
  223. package/src/app/session/hooks/use-session-state-cache.test.tsx +118 -0
  224. package/src/app/session/hooks/use-session-state-cache.ts +191 -0
  225. package/src/app/session-picker-overlay.tsx +32 -0
  226. package/src/app/session-switcher.tsx +107 -0
  227. package/src/app/settings/about-settings.tsx +173 -0
  228. package/src/app/settings/appearance-settings.tsx +162 -0
  229. package/src/app/settings/config-settings.tsx +384 -0
  230. package/src/app/settings/constants.ts +545 -0
  231. package/src/app/settings/credential-key-ui.tsx +373 -0
  232. package/src/app/settings/env-credentials.tsx +198 -0
  233. package/src/app/settings/env-var-actions-menu.tsx +136 -0
  234. package/src/app/settings/field-copy.ts +56 -0
  235. package/src/app/settings/gateway-settings.tsx +620 -0
  236. package/src/app/settings/helpers.test.ts +138 -0
  237. package/src/app/settings/helpers.ts +151 -0
  238. package/src/app/settings/index.tsx +237 -0
  239. package/src/app/settings/keys-settings.tsx +96 -0
  240. package/src/app/settings/mcp-settings.tsx +271 -0
  241. package/src/app/settings/model-settings.test.tsx +157 -0
  242. package/src/app/settings/model-settings.tsx +559 -0
  243. package/src/app/settings/notifications-settings.tsx +150 -0
  244. package/src/app/settings/primitives.tsx +115 -0
  245. package/src/app/settings/providers-settings.test.tsx +100 -0
  246. package/src/app/settings/providers-settings.tsx +258 -0
  247. package/src/app/settings/sessions-settings.tsx +276 -0
  248. package/src/app/settings/toolset-config-panel.test.tsx +289 -0
  249. package/src/app/settings/toolset-config-panel.tsx +449 -0
  250. package/src/app/settings/types.ts +42 -0
  251. package/src/app/settings/uninstall-section.tsx +185 -0
  252. package/src/app/settings/use-deep-link-highlight.ts +60 -0
  253. package/src/app/shell/app-shell.tsx +167 -0
  254. package/src/app/shell/gateway-menu-panel.tsx +150 -0
  255. package/src/app/shell/hooks/use-overlay-routing.ts +71 -0
  256. package/src/app/shell/hooks/use-status-snapshot.ts +57 -0
  257. package/src/app/shell/hooks/use-statusbar-items.tsx +403 -0
  258. package/src/app/shell/keybind-panel.tsx +220 -0
  259. package/src/app/shell/model-edit-submenu.test.tsx +84 -0
  260. package/src/app/shell/model-edit-submenu.tsx +245 -0
  261. package/src/app/shell/model-menu-panel.tsx +295 -0
  262. package/src/app/shell/sidebar-label.tsx +22 -0
  263. package/src/app/shell/statusbar-controls.tsx +185 -0
  264. package/src/app/shell/titlebar-controls.tsx +244 -0
  265. package/src/app/shell/titlebar.test.ts +26 -0
  266. package/src/app/shell/titlebar.ts +45 -0
  267. package/src/app/shell/use-group-registry.ts +39 -0
  268. package/src/app/skills/index.test.tsx +103 -0
  269. package/src/app/skills/index.tsx +371 -0
  270. package/src/app/types.ts +99 -0
  271. package/src/app/updates-overlay.tsx +369 -0
  272. package/src/components/Backdrop.tsx +114 -0
  273. package/src/components/assistant-ui/ansi-text.tsx +34 -0
  274. package/src/components/assistant-ui/clarify-tool.tsx +281 -0
  275. package/src/components/assistant-ui/directive-text.test.ts +39 -0
  276. package/src/components/assistant-ui/directive-text.tsx +389 -0
  277. package/src/components/assistant-ui/markdown-text.test.ts +204 -0
  278. package/src/components/assistant-ui/markdown-text.tsx +497 -0
  279. package/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
  280. package/src/components/assistant-ui/message-render-boundary.tsx +48 -0
  281. package/src/components/assistant-ui/streaming.test.tsx +739 -0
  282. package/src/components/assistant-ui/thread-list.tsx +307 -0
  283. package/src/components/assistant-ui/thread-virtualizer.tsx +512 -0
  284. package/src/components/assistant-ui/thread.tsx +1474 -0
  285. package/src/components/assistant-ui/todo-tool.tsx +109 -0
  286. package/src/components/assistant-ui/tool-approval-group.test.tsx +158 -0
  287. package/src/components/assistant-ui/tool-approval.test.tsx +81 -0
  288. package/src/components/assistant-ui/tool-approval.tsx +209 -0
  289. package/src/components/assistant-ui/tool-fallback-model.test.ts +66 -0
  290. package/src/components/assistant-ui/tool-fallback-model.ts +1368 -0
  291. package/src/components/assistant-ui/tool-fallback.tsx +466 -0
  292. package/src/components/assistant-ui/tooltip-icon-button.tsx +33 -0
  293. package/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
  294. package/src/components/assistant-ui/user-message-text.tsx +150 -0
  295. package/src/components/boot-failure-overlay.tsx +246 -0
  296. package/src/components/boot-failure-reauth.test.ts +100 -0
  297. package/src/components/boot-failure-reauth.ts +81 -0
  298. package/src/components/brand-mark.tsx +19 -0
  299. package/src/components/chat/activity-timer-text.tsx +24 -0
  300. package/src/components/chat/activity-timer.test.tsx +43 -0
  301. package/src/components/chat/activity-timer.ts +64 -0
  302. package/src/components/chat/code-card.tsx +78 -0
  303. package/src/components/chat/compact-markdown.tsx +113 -0
  304. package/src/components/chat/composer-dock.ts +31 -0
  305. package/src/components/chat/diff-lines.tsx +54 -0
  306. package/src/components/chat/disclosure-row.tsx +63 -0
  307. package/src/components/chat/generated-image-context.tsx +19 -0
  308. package/src/components/chat/generated-image-result.tsx +174 -0
  309. package/src/components/chat/image-generation-placeholder.tsx +279 -0
  310. package/src/components/chat/intro-copy.jsonl +75 -0
  311. package/src/components/chat/intro.tsx +182 -0
  312. package/src/components/chat/preview-attachment.tsx +125 -0
  313. package/src/components/chat/shiki-highlighter.tsx +107 -0
  314. package/src/components/chat/status-row.tsx +70 -0
  315. package/src/components/chat/status-section.tsx +42 -0
  316. package/src/components/chat/terminal-output.tsx +50 -0
  317. package/src/components/chat/zoomable-image.tsx +177 -0
  318. package/src/components/desktop-install-overlay.tsx +595 -0
  319. package/src/components/desktop-onboarding-overlay.test.tsx +100 -0
  320. package/src/components/desktop-onboarding-overlay.tsx +1286 -0
  321. package/src/components/error-boundary.tsx +77 -0
  322. package/src/components/gateway-connecting-overlay.test.tsx +143 -0
  323. package/src/components/gateway-connecting-overlay.tsx +183 -0
  324. package/src/components/haptics-provider.tsx +19 -0
  325. package/src/components/language-switcher.test.tsx +53 -0
  326. package/src/components/language-switcher.tsx +175 -0
  327. package/src/components/model-picker.tsx +340 -0
  328. package/src/components/model-visibility-dialog.tsx +155 -0
  329. package/src/components/notifications.tsx +196 -0
  330. package/src/components/page-loader.tsx +34 -0
  331. package/src/components/pane-shell/context.ts +14 -0
  332. package/src/components/pane-shell/index.ts +4 -0
  333. package/src/components/pane-shell/pane-shell.test.tsx +333 -0
  334. package/src/components/pane-shell/pane-shell.tsx +330 -0
  335. package/src/components/prompt-overlays.tsx +234 -0
  336. package/src/components/session-picker.tsx +108 -0
  337. package/src/components/status-dot.tsx +26 -0
  338. package/src/components/ui/action-status.tsx +25 -0
  339. package/src/components/ui/alert.tsx +53 -0
  340. package/src/components/ui/badge.tsx +35 -0
  341. package/src/components/ui/braille-spinner.tsx +61 -0
  342. package/src/components/ui/button.tsx +81 -0
  343. package/src/components/ui/checkbox.tsx +27 -0
  344. package/src/components/ui/codicon.tsx +20 -0
  345. package/src/components/ui/command.tsx +111 -0
  346. package/src/components/ui/confirm-dialog.tsx +109 -0
  347. package/src/components/ui/context-menu.tsx +141 -0
  348. package/src/components/ui/control.ts +25 -0
  349. package/src/components/ui/copy-button.test.tsx +36 -0
  350. package/src/components/ui/copy-button.tsx +229 -0
  351. package/src/components/ui/dialog.tsx +152 -0
  352. package/src/components/ui/disclosure-caret.tsx +20 -0
  353. package/src/components/ui/dropdown-menu.tsx +291 -0
  354. package/src/components/ui/error-state.tsx +50 -0
  355. package/src/components/ui/fade-text.tsx +110 -0
  356. package/src/components/ui/glyph-spinner.tsx +63 -0
  357. package/src/components/ui/input.tsx +22 -0
  358. package/src/components/ui/kbd.tsx +37 -0
  359. package/src/components/ui/loader.tsx +558 -0
  360. package/src/components/ui/log-view.tsx +17 -0
  361. package/src/components/ui/pagination.tsx +114 -0
  362. package/src/components/ui/popover.tsx +44 -0
  363. package/src/components/ui/scroll-area.tsx +43 -0
  364. package/src/components/ui/search-field.tsx +80 -0
  365. package/src/components/ui/segmented-control.tsx +51 -0
  366. package/src/components/ui/select.tsx +92 -0
  367. package/src/components/ui/separator.tsx +26 -0
  368. package/src/components/ui/sheet.tsx +116 -0
  369. package/src/components/ui/sidebar.tsx +674 -0
  370. package/src/components/ui/skeleton.tsx +7 -0
  371. package/src/components/ui/switch.tsx +49 -0
  372. package/src/components/ui/tabs.tsx +36 -0
  373. package/src/components/ui/text-tab.tsx +43 -0
  374. package/src/components/ui/textarea.tsx +11 -0
  375. package/src/components/ui/tool-icon.tsx +65 -0
  376. package/src/components/ui/tooltip.tsx +69 -0
  377. package/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
  378. package/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
  379. package/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
  380. package/src/global.d.ts +457 -0
  381. package/src/hooks/use-image-download.ts +85 -0
  382. package/src/hooks/use-media-query.ts +24 -0
  383. package/src/hooks/use-mobile.ts +3 -0
  384. package/src/hooks/use-resize-observer.ts +38 -0
  385. package/src/hooks/use-worktree-info.ts +68 -0
  386. package/src/i18n/catalog.ts +12 -0
  387. package/src/i18n/context.test.tsx +232 -0
  388. package/src/i18n/context.tsx +183 -0
  389. package/src/i18n/define-locale.ts +41 -0
  390. package/src/i18n/en.ts +1779 -0
  391. package/src/i18n/index.ts +20 -0
  392. package/src/i18n/ja.ts +1890 -0
  393. package/src/i18n/languages.test.ts +43 -0
  394. package/src/i18n/languages.ts +86 -0
  395. package/src/i18n/runtime.test.ts +75 -0
  396. package/src/i18n/runtime.ts +53 -0
  397. package/src/i18n/types.ts +1452 -0
  398. package/src/i18n/zh-hant.ts +1849 -0
  399. package/src/i18n/zh.ts +1923 -0
  400. package/src/lib/ansi.test.ts +123 -0
  401. package/src/lib/ansi.ts +175 -0
  402. package/src/lib/chat-messages.test.ts +708 -0
  403. package/src/lib/chat-messages.ts +885 -0
  404. package/src/lib/chat-runtime.test.ts +18 -0
  405. package/src/lib/chat-runtime.ts +335 -0
  406. package/src/lib/clipboard.ts +28 -0
  407. package/src/lib/commit-changelog.test.ts +114 -0
  408. package/src/lib/commit-changelog.ts +177 -0
  409. package/src/lib/completion-sound.ts +519 -0
  410. package/src/lib/desktop-fs.test.ts +116 -0
  411. package/src/lib/desktop-fs.ts +113 -0
  412. package/src/lib/desktop-slash-commands.test.ts +126 -0
  413. package/src/lib/desktop-slash-commands.ts +286 -0
  414. package/src/lib/embedded-images.test.ts +35 -0
  415. package/src/lib/embedded-images.ts +60 -0
  416. package/src/lib/external-link.test.tsx +168 -0
  417. package/src/lib/external-link.tsx +303 -0
  418. package/src/lib/gateway-events.test.ts +27 -0
  419. package/src/lib/gateway-events.ts +49 -0
  420. package/src/lib/gateway-ws-url.test.ts +78 -0
  421. package/src/lib/gateway-ws-url.ts +91 -0
  422. package/src/lib/generated-images.test.ts +97 -0
  423. package/src/lib/generated-images.ts +116 -0
  424. package/src/lib/haptics.ts +129 -0
  425. package/src/lib/icons.ts +203 -0
  426. package/src/lib/incremental-external-store-runtime.ts +188 -0
  427. package/src/lib/katex-memo.ts +260 -0
  428. package/src/lib/keybinds/actions.ts +125 -0
  429. package/src/lib/keybinds/combo.test.ts +86 -0
  430. package/src/lib/keybinds/combo.ts +169 -0
  431. package/src/lib/local-preview.ts +126 -0
  432. package/src/lib/markdown-code.test.ts +23 -0
  433. package/src/lib/markdown-code.ts +195 -0
  434. package/src/lib/markdown-preprocess.ts +386 -0
  435. package/src/lib/media.remote.test.ts +58 -0
  436. package/src/lib/media.ts +111 -0
  437. package/src/lib/model-status-label.test.ts +31 -0
  438. package/src/lib/model-status-label.ts +103 -0
  439. package/src/lib/mutable-ref.ts +6 -0
  440. package/src/lib/preview-targets.test.ts +27 -0
  441. package/src/lib/preview-targets.ts +63 -0
  442. package/src/lib/profile-color.ts +58 -0
  443. package/src/lib/provider-setup-errors.test.ts +26 -0
  444. package/src/lib/provider-setup-errors.ts +12 -0
  445. package/src/lib/query-client.ts +13 -0
  446. package/src/lib/remend-tail.test.ts +105 -0
  447. package/src/lib/remend-tail.ts +108 -0
  448. package/src/lib/runtime-readiness.test.ts +65 -0
  449. package/src/lib/runtime-readiness.ts +147 -0
  450. package/src/lib/session-export.ts +57 -0
  451. package/src/lib/session-search.test.ts +58 -0
  452. package/src/lib/session-search.ts +19 -0
  453. package/src/lib/session-source.ts +62 -0
  454. package/src/lib/speech-text.ts +35 -0
  455. package/src/lib/statusbar.ts +91 -0
  456. package/src/lib/storage.test.ts +25 -0
  457. package/src/lib/storage.ts +107 -0
  458. package/src/lib/todos.test.ts +35 -0
  459. package/src/lib/todos.ts +51 -0
  460. package/src/lib/tool-result-summary.test.ts +106 -0
  461. package/src/lib/tool-result-summary.ts +467 -0
  462. package/src/lib/update-copy.test.ts +38 -0
  463. package/src/lib/update-copy.ts +44 -0
  464. package/src/lib/use-enter-animation.ts +100 -0
  465. package/src/lib/utils.ts +6 -0
  466. package/src/lib/voice-playback.ts +128 -0
  467. package/src/lib/yolo-session.ts +26 -0
  468. package/src/main.tsx +43 -0
  469. package/src/nastech.test.ts +49 -0
  470. package/src/nastech.ts +718 -0
  471. package/src/store/activity.ts +100 -0
  472. package/src/store/boot.ts +91 -0
  473. package/src/store/clarify.test.ts +81 -0
  474. package/src/store/clarify.ts +69 -0
  475. package/src/store/command-palette.ts +20 -0
  476. package/src/store/compaction.test.ts +53 -0
  477. package/src/store/compaction.ts +38 -0
  478. package/src/store/completion-sound.ts +32 -0
  479. package/src/store/composer-input-history.test.ts +147 -0
  480. package/src/store/composer-input-history.ts +158 -0
  481. package/src/store/composer-queue.test.ts +148 -0
  482. package/src/store/composer-queue.ts +239 -0
  483. package/src/store/composer-status.test.ts +99 -0
  484. package/src/store/composer-status.ts +277 -0
  485. package/src/store/composer.test.ts +106 -0
  486. package/src/store/composer.ts +184 -0
  487. package/src/store/cron.ts +19 -0
  488. package/src/store/gateway.ts +290 -0
  489. package/src/store/haptics.ts +17 -0
  490. package/src/store/keybinds.ts +139 -0
  491. package/src/store/layout.ts +176 -0
  492. package/src/store/model-presets.test.ts +51 -0
  493. package/src/store/model-presets.ts +86 -0
  494. package/src/store/model-visibility.test.ts +37 -0
  495. package/src/store/model-visibility.ts +108 -0
  496. package/src/store/native-notifications.test.ts +192 -0
  497. package/src/store/native-notifications.ts +203 -0
  498. package/src/store/notifications.ts +165 -0
  499. package/src/store/onboarding.test.ts +372 -0
  500. package/src/store/onboarding.ts +866 -0
  501. package/src/store/panes.test.ts +146 -0
  502. package/src/store/panes.ts +145 -0
  503. package/src/store/preview.test.ts +135 -0
  504. package/src/store/preview.ts +466 -0
  505. package/src/store/profile.test.ts +89 -0
  506. package/src/store/profile.ts +365 -0
  507. package/src/store/prompts.test.ts +121 -0
  508. package/src/store/prompts.ts +115 -0
  509. package/src/store/session-switcher.test.ts +115 -0
  510. package/src/store/session-switcher.ts +128 -0
  511. package/src/store/session-sync.ts +25 -0
  512. package/src/store/session.test.ts +131 -0
  513. package/src/store/session.ts +255 -0
  514. package/src/store/subagents.test.ts +111 -0
  515. package/src/store/subagents.ts +260 -0
  516. package/src/store/thread-scroll.ts +46 -0
  517. package/src/store/todos.test.ts +47 -0
  518. package/src/store/todos.ts +64 -0
  519. package/src/store/tool-diffs.ts +23 -0
  520. package/src/store/tool-dismiss.ts +45 -0
  521. package/src/store/tool-view.ts +91 -0
  522. package/src/store/translucency.ts +38 -0
  523. package/src/store/updates.test.ts +77 -0
  524. package/src/store/updates.ts +315 -0
  525. package/src/store/voice-playback.ts +24 -0
  526. package/src/store/windows.test.ts +143 -0
  527. package/src/store/windows.ts +77 -0
  528. package/src/styles.css +1235 -0
  529. package/src/themes/color.ts +142 -0
  530. package/src/themes/context.tsx +339 -0
  531. package/src/themes/index.ts +3 -0
  532. package/src/themes/install.test.ts +119 -0
  533. package/src/themes/install.ts +95 -0
  534. package/src/themes/presets.test.ts +33 -0
  535. package/src/themes/presets.ts +293 -0
  536. package/src/themes/profile-theme.test.ts +41 -0
  537. package/src/themes/types.ts +66 -0
  538. package/src/themes/use-skin-command.ts +60 -0
  539. package/src/themes/user-themes.test.ts +63 -0
  540. package/src/themes/user-themes.ts +122 -0
  541. package/src/themes/vscode.test.ts +171 -0
  542. package/src/themes/vscode.ts +343 -0
  543. package/src/types/nastech.ts +646 -0
  544. package/src/vite-env.d.ts +1 -0
  545. package/tsconfig.json +25 -0
  546. package/vite.config.ts +56 -0
@@ -0,0 +1,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()