@neurocode-ai/app 1.18.8

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 (577) hide show
  1. package/AGENTS.md +35 -0
  2. package/README.md +50 -0
  3. package/V1_API_MIGRATION.md +220 -0
  4. package/bunfig.toml +3 -0
  5. package/create-effect-simplification-spec.md +515 -0
  6. package/e2e/performance/AGENTS.md +13 -0
  7. package/e2e/performance/README.md +79 -0
  8. package/e2e/performance/benchmark.ts +144 -0
  9. package/e2e/performance/chrome-trace.ts +95 -0
  10. package/e2e/performance/playwright.config.ts +20 -0
  11. package/e2e/performance/playwright.uncapped.config.ts +13 -0
  12. package/e2e/performance/timeline/first-navigation-benchmark.spec.ts +87 -0
  13. package/e2e/performance/timeline/first-navigation-metrics.ts +32 -0
  14. package/e2e/performance/timeline/first-navigation-probe.ts +86 -0
  15. package/e2e/performance/timeline/home-tab-navigation-benchmark.spec.ts +114 -0
  16. package/e2e/performance/timeline/navigation-milestones.ts +128 -0
  17. package/e2e/performance/timeline/review-pane-scaling-benchmark.spec.ts +312 -0
  18. package/e2e/performance/timeline/session-parent-hydration-benchmark.spec.ts +172 -0
  19. package/e2e/performance/timeline/session-tab-flash.spec.ts +67 -0
  20. package/e2e/performance/timeline/session-tab-repaint-probe.ts +251 -0
  21. package/e2e/performance/timeline/session-tab-switch-benchmark.spec.ts +144 -0
  22. package/e2e/performance/timeline/session-tab-switch-metrics.ts +59 -0
  23. package/e2e/performance/timeline/session-tab-switch-probe.ts +226 -0
  24. package/e2e/performance/timeline/session-timeline-benchmark.fixture.ts +504 -0
  25. package/e2e/performance/timeline/session-timeline-benchmark.spec.ts +306 -0
  26. package/e2e/performance/timeline/session-timeline-profile.ts +40 -0
  27. package/e2e/performance/timeline/session-timeline-stream-probe.ts +547 -0
  28. package/e2e/performance/timeline/session-timeline-stress.fixture.ts +369 -0
  29. package/e2e/performance/timeline/timeline-test-helpers.ts +134 -0
  30. package/e2e/performance/timeline-stability/README.md +61 -0
  31. package/e2e/performance/timeline-stability/adverse.spec.ts +250 -0
  32. package/e2e/performance/timeline-stability/context-matrix.spec.ts +192 -0
  33. package/e2e/performance/timeline-stability/environment-matrix.spec.ts +113 -0
  34. package/e2e/performance/timeline-stability/file-matrix.spec.ts +121 -0
  35. package/e2e/performance/timeline-stability/file-mutation.spec.ts +113 -0
  36. package/e2e/performance/timeline-stability/fixture.test.ts +68 -0
  37. package/e2e/performance/timeline-stability/fixture.ts +566 -0
  38. package/e2e/performance/timeline-stability/interaction.spec.ts +242 -0
  39. package/e2e/performance/timeline-stability/lifecycle.spec.ts +157 -0
  40. package/e2e/performance/timeline-stability/oracle-browser.spec.ts +75 -0
  41. package/e2e/performance/timeline-stability/playwright.config.ts +17 -0
  42. package/e2e/performance/timeline-stability/scroll-interaction.spec.ts +353 -0
  43. package/e2e/performance/timeline-stability/shell-matrix.spec.ts +255 -0
  44. package/e2e/performance/timeline-stability/tool-mutation.spec.ts +106 -0
  45. package/e2e/performance/timeline-stability/tools.spec.ts +198 -0
  46. package/e2e/performance/timeline-stability/transition-matrix.spec.ts +272 -0
  47. package/e2e/performance/unit/chrome-trace-write.test.ts +15 -0
  48. package/e2e/performance/unit/first-navigation-metrics.test.ts +32 -0
  49. package/e2e/performance/unit/mock-server.test.ts +46 -0
  50. package/e2e/performance/unit/navigation-milestones.test.ts +34 -0
  51. package/e2e/performance/unit/session-tab-repaint-probe.test.ts +42 -0
  52. package/e2e/performance/unit/session-tab-switch-metrics.test.ts +86 -0
  53. package/e2e/performance/unit/session-tab-switch-probe.test.ts +55 -0
  54. package/e2e/performance/unit/session-timeline-stream-probe.test.ts +14 -0
  55. package/e2e/performance/unit/session-timeline-visual-tracking.test.ts +16 -0
  56. package/e2e/performance/unit/timeline-test-helpers.test.ts +10 -0
  57. package/e2e/performance/unit/visual-stability.test.ts +392 -0
  58. package/e2e/regression/cross-server-tab-close.spec.ts +153 -0
  59. package/e2e/regression/file-browser-sidebar-tab-switch.spec.ts +150 -0
  60. package/e2e/regression/legacy-new-session.spec.ts +41 -0
  61. package/e2e/regression/new-session-panel-corner.spec.ts +83 -0
  62. package/e2e/regression/prompt-input-v2-command-draft.spec.ts +50 -0
  63. package/e2e/regression/prompt-thinking-level.spec.ts +84 -0
  64. package/e2e/regression/remote-session-settings.spec.ts +302 -0
  65. package/e2e/regression/remote-tab-busy.spec.ts +131 -0
  66. package/e2e/regression/review-image-flash.spec.ts +202 -0
  67. package/e2e/regression/review-line-comment.spec.ts +158 -0
  68. package/e2e/regression/review-open-file.spec.ts +163 -0
  69. package/e2e/regression/review-state-persistence.spec.ts +155 -0
  70. package/e2e/regression/review-tab-switch.spec.ts +147 -0
  71. package/e2e/regression/review-terminal-stacked.spec.ts +308 -0
  72. package/e2e/regression/session-list-path-loading.spec.ts +41 -0
  73. package/e2e/regression/session-request-docks.spec.ts +221 -0
  74. package/e2e/regression/session-timeline-accessibility.spec.ts +22 -0
  75. package/e2e/regression/session-timeline-collapse-state.spec.ts +439 -0
  76. package/e2e/regression/session-timeline-context-resize.spec.ts +374 -0
  77. package/e2e/regression/session-timeline-context-state.spec.ts +31 -0
  78. package/e2e/regression/session-timeline-file-projection.spec.ts +52 -0
  79. package/e2e/regression/session-timeline-file-state.spec.ts +94 -0
  80. package/e2e/regression/session-timeline-history-root.spec.ts +242 -0
  81. package/e2e/regression/session-timeline-lifecycle-state.spec.ts +111 -0
  82. package/e2e/regression/session-timeline-locale-projection.spec.ts +25 -0
  83. package/e2e/regression/session-timeline-projection.spec.ts +287 -0
  84. package/e2e/regression/session-timeline-reasoning-projection.spec.ts +93 -0
  85. package/e2e/regression/session-timeline-reducer-projection.spec.ts +43 -0
  86. package/e2e/regression/session-timeline-shell-outline.spec.ts +228 -0
  87. package/e2e/regression/session-timeline-tool-projection.spec.ts +99 -0
  88. package/e2e/regression/session-timeline-tool-state.spec.ts +77 -0
  89. package/e2e/regression/session-timeline-transport.spec.ts +116 -0
  90. package/e2e/regression/session-todo-dock-navigation.spec.ts +190 -0
  91. package/e2e/regression/subagent-child-navigation.spec.ts +203 -0
  92. package/e2e/regression/tab-navigate-mousedown.spec.ts +127 -0
  93. package/e2e/regression/terminal-composer-focus.spec.ts +227 -0
  94. package/e2e/regression/terminal-hidden.spec.ts +117 -0
  95. package/e2e/regression/terminal-tab-switch.spec.ts +165 -0
  96. package/e2e/reproduction/timeline-suspense/index.html +34 -0
  97. package/e2e/reproduction/timeline-suspense/main.tsx +315 -0
  98. package/e2e/reproduction/timeline-suspense/playwright.config.ts +34 -0
  99. package/e2e/reproduction/timeline-suspense/timeline-suspense.repro.ts +179 -0
  100. package/e2e/reproduction/timeline-suspense/vite.config.ts +7 -0
  101. package/e2e/smoke/session-timeline.fixture.ts +315 -0
  102. package/e2e/smoke/session-timeline.spec.ts +740 -0
  103. package/e2e/tsconfig.json +20 -0
  104. package/e2e/utils/errors.ts +18 -0
  105. package/e2e/utils/mock-server.ts +443 -0
  106. package/e2e/utils/sse-transport.ts +312 -0
  107. package/e2e/utils/visual-stability/analyzer.ts +209 -0
  108. package/e2e/utils/visual-stability/capture.ts +51 -0
  109. package/e2e/utils/visual-stability/index.ts +8 -0
  110. package/e2e/utils/visual-stability/invariant.ts +112 -0
  111. package/e2e/utils/visual-stability/model.ts +47 -0
  112. package/e2e/utils/visual-stability/probe.ts +226 -0
  113. package/e2e/utils/visual-stability/regions.ts +18 -0
  114. package/e2e/utils/visual-stability/reporter.ts +63 -0
  115. package/e2e/utils/visual-stability/scenario.ts +20 -0
  116. package/e2e/utils/visual-stability.ts +54 -0
  117. package/e2e/utils/waits.ts +11 -0
  118. package/happydom.ts +75 -0
  119. package/index.html +28 -0
  120. package/package.json +96 -0
  121. package/playwright.config.ts +45 -0
  122. package/public/_headers +17 -0
  123. package/public/apple-touch-icon-v3.png +1 -0
  124. package/public/apple-touch-icon.png +1 -0
  125. package/public/assets/Inter.ttf +0 -0
  126. package/public/assets/JetBrainsMonoNerdFontMono-Regular.woff2 +0 -0
  127. package/public/favicon-96x96-v3.png +1 -0
  128. package/public/favicon-96x96.png +1 -0
  129. package/public/favicon-v3.ico +1 -0
  130. package/public/favicon-v3.svg +1 -0
  131. package/public/favicon.ico +1 -0
  132. package/public/favicon.svg +1 -0
  133. package/public/oc-theme-preload.js +40 -0
  134. package/public/site.webmanifest +1 -0
  135. package/public/social-share-zen.png +1 -0
  136. package/public/social-share.png +1 -0
  137. package/public/web-app-manifest-192x192.png +1 -0
  138. package/public/web-app-manifest-512x512.png +1 -0
  139. package/src/addons/serialize.test.ts +327 -0
  140. package/src/addons/serialize.ts +642 -0
  141. package/src/app.tsx +659 -0
  142. package/src/assets/help/home.png +0 -0
  143. package/src/assets/help/introducing-tabs.mp4 +0 -0
  144. package/src/assets/help/placeholder.png +0 -0
  145. package/src/assets/help/tabs.png +0 -0
  146. package/src/components/command-palette.ts +281 -0
  147. package/src/components/command-tooltip-keybind.test.ts +22 -0
  148. package/src/components/command-tooltip-keybind.ts +11 -0
  149. package/src/components/debug-bar.tsx +550 -0
  150. package/src/components/dialog-command-palette-v2.css +220 -0
  151. package/src/components/dialog-command-palette-v2.tsx +342 -0
  152. package/src/components/dialog-connect-provider.stories.tsx +73 -0
  153. package/src/components/dialog-connect-provider.tsx +1178 -0
  154. package/src/components/dialog-custom-provider-form.ts +158 -0
  155. package/src/components/dialog-custom-provider.test.ts +80 -0
  156. package/src/components/dialog-custom-provider.tsx +330 -0
  157. package/src/components/dialog-edit-project-v2.tsx +156 -0
  158. package/src/components/dialog-edit-project.tsx +170 -0
  159. package/src/components/dialog-fork.tsx +104 -0
  160. package/src/components/dialog-manage-models.tsx +265 -0
  161. package/src/components/dialog-release-notes.tsx +144 -0
  162. package/src/components/dialog-select-directory-v2.css +107 -0
  163. package/src/components/dialog-select-directory-v2.tsx +378 -0
  164. package/src/components/dialog-select-directory.tsx +191 -0
  165. package/src/components/dialog-select-file.tsx +189 -0
  166. package/src/components/dialog-select-mcp.tsx +92 -0
  167. package/src/components/dialog-select-model-search.test.ts +19 -0
  168. package/src/components/dialog-select-model-search.ts +18 -0
  169. package/src/components/dialog-select-model-unpaid-v2.stories.tsx +55 -0
  170. package/src/components/dialog-select-model-unpaid-v2.tsx +175 -0
  171. package/src/components/dialog-select-model-unpaid.tsx +147 -0
  172. package/src/components/dialog-select-model.tsx +549 -0
  173. package/src/components/dialog-select-server.tsx +722 -0
  174. package/src/components/dialog-settings.tsx +94 -0
  175. package/src/components/dialog-usage-exceeded.tsx +44 -0
  176. package/src/components/directory-picker-domain.test.ts +248 -0
  177. package/src/components/directory-picker-domain.ts +407 -0
  178. package/src/components/directory-picker-policy.ts +7 -0
  179. package/src/components/directory-picker.test.ts +21 -0
  180. package/src/components/directory-picker.tsx +45 -0
  181. package/src/components/edit-project.ts +122 -0
  182. package/src/components/file-tree-v2-model.test.ts +74 -0
  183. package/src/components/file-tree-v2-model.ts +107 -0
  184. package/src/components/file-tree-v2.tsx +299 -0
  185. package/src/components/file-tree.test.ts +80 -0
  186. package/src/components/file-tree.tsx +509 -0
  187. package/src/components/help-button.tsx +148 -0
  188. package/src/components/link.tsx +26 -0
  189. package/src/components/model-tooltip.tsx +124 -0
  190. package/src/components/pierre-tree.test.ts +23 -0
  191. package/src/components/prompt-input/attachments.test.ts +108 -0
  192. package/src/components/prompt-input/attachments.ts +228 -0
  193. package/src/components/prompt-input/build-request-parts.test.ts +396 -0
  194. package/src/components/prompt-input/build-request-parts.ts +213 -0
  195. package/src/components/prompt-input/context-items.tsx +98 -0
  196. package/src/components/prompt-input/contracts.ts +57 -0
  197. package/src/components/prompt-input/drag-overlay.tsx +25 -0
  198. package/src/components/prompt-input/editor-dom.test.ts +99 -0
  199. package/src/components/prompt-input/editor-dom.ts +148 -0
  200. package/src/components/prompt-input/files.ts +98 -0
  201. package/src/components/prompt-input/history-store.ts +47 -0
  202. package/src/components/prompt-input/history.test.ts +153 -0
  203. package/src/components/prompt-input/history.ts +256 -0
  204. package/src/components/prompt-input/image-attachments.css +43 -0
  205. package/src/components/prompt-input/image-attachments.tsx +168 -0
  206. package/src/components/prompt-input/paste.ts +24 -0
  207. package/src/components/prompt-input/placeholder.test.ts +48 -0
  208. package/src/components/prompt-input/placeholder.ts +20 -0
  209. package/src/components/prompt-input/slash-popover.tsx +376 -0
  210. package/src/components/prompt-input/submission-state.ts +33 -0
  211. package/src/components/prompt-input/submit.test.ts +598 -0
  212. package/src/components/prompt-input/submit.ts +634 -0
  213. package/src/components/prompt-input/transient-state.ts +46 -0
  214. package/src/components/prompt-input-v2.tsx +592 -0
  215. package/src/components/prompt-input.stories.tsx +223 -0
  216. package/src/components/prompt-input.tsx +1790 -0
  217. package/src/components/prompt-project-selector.tsx +593 -0
  218. package/src/components/prompt-workspace-selector.tsx +128 -0
  219. package/src/components/server/server-row-menu.tsx +96 -0
  220. package/src/components/server/server-row.tsx +127 -0
  221. package/src/components/session/index.ts +7 -0
  222. package/src/components/session/open-in-app-v2.tsx +98 -0
  223. package/src/components/session/open-in-app.tsx +229 -0
  224. package/src/components/session/session-context-breakdown.test.ts +61 -0
  225. package/src/components/session/session-context-breakdown.ts +132 -0
  226. package/src/components/session/session-context-format.ts +20 -0
  227. package/src/components/session/session-context-metrics.test.ts +99 -0
  228. package/src/components/session/session-context-metrics.ts +65 -0
  229. package/src/components/session/session-context-tab.tsx +343 -0
  230. package/src/components/session/session-header.tsx +568 -0
  231. package/src/components/session/session-new-design-view.tsx +16 -0
  232. package/src/components/session/session-new-view.tsx +91 -0
  233. package/src/components/session/session-sortable-tab-v2.tsx +74 -0
  234. package/src/components/session/session-sortable-tab.tsx +78 -0
  235. package/src/components/session/session-sortable-terminal-tab-v2.tsx +284 -0
  236. package/src/components/session/session-sortable-terminal-tab.tsx +193 -0
  237. package/src/components/session-context-usage.tsx +169 -0
  238. package/src/components/settings-dialog.tsx +43 -0
  239. package/src/components/settings-general.tsx +794 -0
  240. package/src/components/settings-keybinds.tsx +553 -0
  241. package/src/components/settings-list.tsx +5 -0
  242. package/src/components/settings-models.tsx +149 -0
  243. package/src/components/settings-providers.tsx +264 -0
  244. package/src/components/settings-server-picker.tsx +106 -0
  245. package/src/components/settings-servers.tsx +33 -0
  246. package/src/components/settings-v2/dialog-server-v2.tsx +134 -0
  247. package/src/components/settings-v2/dialog-settings-v2.tsx +98 -0
  248. package/src/components/settings-v2/general.tsx +722 -0
  249. package/src/components/settings-v2/index.tsx +1 -0
  250. package/src/components/settings-v2/interface-transition.stories.tsx +76 -0
  251. package/src/components/settings-v2/interface-transition.tsx +54 -0
  252. package/src/components/settings-v2/models.tsx +138 -0
  253. package/src/components/settings-v2/parts/list.tsx +6 -0
  254. package/src/components/settings-v2/parts/row.tsx +20 -0
  255. package/src/components/settings-v2/providers.tsx +264 -0
  256. package/src/components/settings-v2/servers.tsx +139 -0
  257. package/src/components/settings-v2/settings-v2.css +675 -0
  258. package/src/components/status-popover-body.tsx +514 -0
  259. package/src/components/status-popover-indicator.test.ts +36 -0
  260. package/src/components/status-popover-indicator.ts +20 -0
  261. package/src/components/status-popover.tsx +190 -0
  262. package/src/components/terminal.tsx +759 -0
  263. package/src/components/titlebar-history.test.ts +63 -0
  264. package/src/components/titlebar-history.ts +57 -0
  265. package/src/components/titlebar-session-events.test.ts +32 -0
  266. package/src/components/titlebar-session-events.ts +35 -0
  267. package/src/components/titlebar-tab-gesture.test.ts +33 -0
  268. package/src/components/titlebar-tab-gesture.ts +17 -0
  269. package/src/components/titlebar-tab-nav.css +100 -0
  270. package/src/components/titlebar-tab-nav.tsx +429 -0
  271. package/src/components/titlebar-tab-popover.css +127 -0
  272. package/src/components/titlebar-tab-popover.tsx +92 -0
  273. package/src/components/titlebar-tab-strip.tsx +333 -0
  274. package/src/components/titlebar.css +77 -0
  275. package/src/components/titlebar.tsx +776 -0
  276. package/src/components/ui/drawer.tsx +119 -0
  277. package/src/components/updater-action.test.ts +26 -0
  278. package/src/components/updater-action.ts +51 -0
  279. package/src/components/virtual-scroll-element.test.ts +18 -0
  280. package/src/components/virtual-scroll-element.ts +4 -0
  281. package/src/components/windows-app-menu.tsx +131 -0
  282. package/src/constants/file-picker.ts +89 -0
  283. package/src/context/closed-tabs.ts +40 -0
  284. package/src/context/command-keybind.test.ts +69 -0
  285. package/src/context/command.test.ts +55 -0
  286. package/src/context/command.tsx +467 -0
  287. package/src/context/comments.test.ts +188 -0
  288. package/src/context/comments.tsx +261 -0
  289. package/src/context/directory-sync.ts +155 -0
  290. package/src/context/file/content-cache.ts +88 -0
  291. package/src/context/file/path.test.ts +360 -0
  292. package/src/context/file/path.ts +151 -0
  293. package/src/context/file/tree-store.ts +174 -0
  294. package/src/context/file/types.ts +41 -0
  295. package/src/context/file/view-cache.ts +147 -0
  296. package/src/context/file/watcher.test.ts +149 -0
  297. package/src/context/file/watcher.ts +53 -0
  298. package/src/context/file-content-eviction-accounting.test.ts +65 -0
  299. package/src/context/file.tsx +303 -0
  300. package/src/context/global-sync/bootstrap.test.ts +231 -0
  301. package/src/context/global-sync/bootstrap.ts +536 -0
  302. package/src/context/global-sync/child-store.test.ts +276 -0
  303. package/src/context/global-sync/child-store.ts +396 -0
  304. package/src/context/global-sync/event-reducer.test.ts +616 -0
  305. package/src/context/global-sync/event-reducer.ts +478 -0
  306. package/src/context/global-sync/eviction.ts +28 -0
  307. package/src/context/global-sync/home-session-index.test.ts +154 -0
  308. package/src/context/global-sync/home-session-index.ts +174 -0
  309. package/src/context/global-sync/mcp.test.ts +54 -0
  310. package/src/context/global-sync/mcp.ts +19 -0
  311. package/src/context/global-sync/queue.test.ts +46 -0
  312. package/src/context/global-sync/queue.ts +87 -0
  313. package/src/context/global-sync/session-cache.test.ts +104 -0
  314. package/src/context/global-sync/session-cache.ts +62 -0
  315. package/src/context/global-sync/session-load.ts +33 -0
  316. package/src/context/global-sync/session-trim.test.ts +59 -0
  317. package/src/context/global-sync/session-trim.ts +57 -0
  318. package/src/context/global-sync/types.ts +135 -0
  319. package/src/context/global-sync/utils.test.ts +133 -0
  320. package/src/context/global-sync/utils.ts +171 -0
  321. package/src/context/global.tsx +161 -0
  322. package/src/context/highlights.tsx +233 -0
  323. package/src/context/language.tsx +243 -0
  324. package/src/context/layout-helpers.ts +38 -0
  325. package/src/context/layout-scroll.test.ts +64 -0
  326. package/src/context/layout-scroll.ts +126 -0
  327. package/src/context/layout-tabs.test.ts +82 -0
  328. package/src/context/layout-tabs.ts +103 -0
  329. package/src/context/layout.test.ts +69 -0
  330. package/src/context/layout.tsx +1075 -0
  331. package/src/context/local-agent.test.ts +29 -0
  332. package/src/context/local-agent.ts +7 -0
  333. package/src/context/local.tsx +417 -0
  334. package/src/context/mcp.ts +19 -0
  335. package/src/context/model-variant.test.ts +86 -0
  336. package/src/context/model-variant.ts +52 -0
  337. package/src/context/models.tsx +173 -0
  338. package/src/context/notification.tsx +478 -0
  339. package/src/context/permission-auto-respond.test.ts +125 -0
  340. package/src/context/permission-auto-respond.ts +60 -0
  341. package/src/context/permission.tsx +484 -0
  342. package/src/context/platform.tsx +142 -0
  343. package/src/context/prompt-state.test.ts +29 -0
  344. package/src/context/prompt-state.ts +266 -0
  345. package/src/context/prompt.tsx +170 -0
  346. package/src/context/sdk.tsx +17 -0
  347. package/src/context/server-sdk.test.ts +196 -0
  348. package/src/context/server-sdk.tsx +444 -0
  349. package/src/context/server-session-v2-reducer.test.ts +156 -0
  350. package/src/context/server-session-v2-reducer.ts +502 -0
  351. package/src/context/server-session.test.ts +1638 -0
  352. package/src/context/server-session.ts +1425 -0
  353. package/src/context/server-sync.test.ts +231 -0
  354. package/src/context/server-sync.tsx +753 -0
  355. package/src/context/server.test.ts +245 -0
  356. package/src/context/server.tsx +360 -0
  357. package/src/context/settings.test.ts +79 -0
  358. package/src/context/settings.tsx +531 -0
  359. package/src/context/sync-optimistic.test.ts +123 -0
  360. package/src/context/sync.tsx +119 -0
  361. package/src/context/tab-memory.ts +36 -0
  362. package/src/context/tabs.test.ts +107 -0
  363. package/src/context/tabs.tsx +387 -0
  364. package/src/context/terminal-title.ts +24 -0
  365. package/src/context/terminal.test.ts +91 -0
  366. package/src/context/terminal.tsx +546 -0
  367. package/src/custom-elements.d.ts +1 -0
  368. package/src/desktop-menu.test.ts +13 -0
  369. package/src/desktop-menu.ts +223 -0
  370. package/src/entry.tsx +182 -0
  371. package/src/env.d.ts +31 -0
  372. package/src/hooks/provider-catalog.test.ts +59 -0
  373. package/src/hooks/provider-catalog.ts +27 -0
  374. package/src/hooks/use-providers.ts +71 -0
  375. package/src/i18n/ar.ts +1008 -0
  376. package/src/i18n/br.ts +1024 -0
  377. package/src/i18n/bs.ts +1100 -0
  378. package/src/i18n/da.ts +1092 -0
  379. package/src/i18n/de.ts +1037 -0
  380. package/src/i18n/en.ts +1102 -0
  381. package/src/i18n/es.ts +1108 -0
  382. package/src/i18n/fr.ts +1037 -0
  383. package/src/i18n/ja.ts +1016 -0
  384. package/src/i18n/ko.ts +1015 -0
  385. package/src/i18n/no.ts +1111 -0
  386. package/src/i18n/parity.test.ts +118 -0
  387. package/src/i18n/pl.ts +1024 -0
  388. package/src/i18n/ru.ts +1103 -0
  389. package/src/i18n/th.ts +1087 -0
  390. package/src/i18n/tr.ts +1106 -0
  391. package/src/i18n/uk.ts +1105 -0
  392. package/src/i18n/zh.ts +1078 -0
  393. package/src/i18n/zht.ts +1074 -0
  394. package/src/index.css +330 -0
  395. package/src/index.ts +30 -0
  396. package/src/pages/directory-layout.tsx +122 -0
  397. package/src/pages/error-description.test.ts +17 -0
  398. package/src/pages/error-description.ts +11 -0
  399. package/src/pages/error.tsx +372 -0
  400. package/src/pages/home/home-controller.ts +108 -0
  401. package/src/pages/home/home-projects-controller.tsx +128 -0
  402. package/src/pages/home/home-projects-view.tsx +615 -0
  403. package/src/pages/home/home-projects.tsx +40 -0
  404. package/src/pages/home/home-scroll-controller.ts +145 -0
  405. package/src/pages/home/home-session-search-controller.ts +114 -0
  406. package/src/pages/home/home-sessions-controller.tsx +314 -0
  407. package/src/pages/home/home-sessions-view.tsx +550 -0
  408. package/src/pages/home/home-sessions.tsx +48 -0
  409. package/src/pages/home/legacy-home.tsx +142 -0
  410. package/src/pages/home-session-archive.test.ts +51 -0
  411. package/src/pages/home-session-archive.ts +27 -0
  412. package/src/pages/home-session-open.test.ts +31 -0
  413. package/src/pages/home-session-open.ts +14 -0
  414. package/src/pages/home.tsx +50 -0
  415. package/src/pages/layout/deep-links.ts +50 -0
  416. package/src/pages/layout/helpers.test.ts +321 -0
  417. package/src/pages/layout/helpers.ts +150 -0
  418. package/src/pages/layout/inline-editor.tsx +126 -0
  419. package/src/pages/layout/project-avatar-state.ts +49 -0
  420. package/src/pages/layout/session-tab-avatar.tsx +60 -0
  421. package/src/pages/layout/sidebar-items.tsx +335 -0
  422. package/src/pages/layout/sidebar-project.tsx +377 -0
  423. package/src/pages/layout/sidebar-shell.tsx +125 -0
  424. package/src/pages/layout/sidebar-workspace.tsx +488 -0
  425. package/src/pages/layout-new.tsx +53 -0
  426. package/src/pages/layout.tsx +2441 -0
  427. package/src/pages/new-session.tsx +282 -0
  428. package/src/pages/session/composer/index.ts +4 -0
  429. package/src/pages/session/composer/prompt-model-selection.ts +133 -0
  430. package/src/pages/session/composer/session-composer-controls.ts +129 -0
  431. package/src/pages/session/composer/session-composer-region-controller.ts +145 -0
  432. package/src/pages/session/composer/session-composer-region.tsx +168 -0
  433. package/src/pages/session/composer/session-composer-state.test.ts +138 -0
  434. package/src/pages/session/composer/session-composer-state.ts +204 -0
  435. package/src/pages/session/composer/session-followup-dock.tsx +109 -0
  436. package/src/pages/session/composer/session-permission-dock.tsx +74 -0
  437. package/src/pages/session/composer/session-question-dock.tsx +640 -0
  438. package/src/pages/session/composer/session-request-tree.ts +52 -0
  439. package/src/pages/session/composer/session-revert-dock.stories.tsx +124 -0
  440. package/src/pages/session/composer/session-revert-dock.tsx +188 -0
  441. package/src/pages/session/composer/session-todo-dock.tsx +277 -0
  442. package/src/pages/session/composer/todo-panel-motion.stories.tsx +622 -0
  443. package/src/pages/session/file-tab-scroll.test.ts +40 -0
  444. package/src/pages/session/file-tab-scroll.ts +67 -0
  445. package/src/pages/session/file-tabs.tsx +800 -0
  446. package/src/pages/session/handoff.ts +36 -0
  447. package/src/pages/session/helpers.test.ts +214 -0
  448. package/src/pages/session/helpers.ts +209 -0
  449. package/src/pages/session/message-gesture.test.ts +62 -0
  450. package/src/pages/session/message-gesture.ts +21 -0
  451. package/src/pages/session/message-id-from-hash.ts +6 -0
  452. package/src/pages/session/new-session-layout.ts +2 -0
  453. package/src/pages/session/review-tab.tsx +172 -0
  454. package/src/pages/session/session-layout.ts +30 -0
  455. package/src/pages/session/session-lineage.ts +69 -0
  456. package/src/pages/session/session-model-helpers.test.ts +151 -0
  457. package/src/pages/session/session-model-helpers.ts +63 -0
  458. package/src/pages/session/session-ownership.ts +37 -0
  459. package/src/pages/session/session-panel-layout.test.ts +19 -0
  460. package/src/pages/session/session-panel-layout.ts +6 -0
  461. package/src/pages/session/session-panel-width.test.ts +52 -0
  462. package/src/pages/session/session-panel-width.ts +19 -0
  463. package/src/pages/session/session-side-panel.tsx +865 -0
  464. package/src/pages/session/terminal-label.ts +16 -0
  465. package/src/pages/session/terminal-panel-v2.tsx +352 -0
  466. package/src/pages/session/terminal-panel.test.ts +25 -0
  467. package/src/pages/session/terminal-panel.tsx +343 -0
  468. package/src/pages/session/timeline/measure.test.ts +30 -0
  469. package/src/pages/session/timeline/measure.ts +5 -0
  470. package/src/pages/session/timeline/message-timeline.tsx +1864 -0
  471. package/src/pages/session/timeline/model.test.ts +95 -0
  472. package/src/pages/session/timeline/model.ts +128 -0
  473. package/src/pages/session/timeline/observe-element-offset.test.ts +199 -0
  474. package/src/pages/session/timeline/observe-element-offset.ts +73 -0
  475. package/src/pages/session/timeline/projection.test.ts +96 -0
  476. package/src/pages/session/timeline/projection.ts +81 -0
  477. package/src/pages/session/timeline/row-reconciliation.ts +56 -0
  478. package/src/pages/session/timeline/rows-current.test.ts +170 -0
  479. package/src/pages/session/timeline/rows.ts +344 -0
  480. package/src/pages/session/timeline/summary-diffs.test.ts +42 -0
  481. package/src/pages/session/timeline/summary-diffs.ts +20 -0
  482. package/src/pages/session/timeline/timeline-row.ts +80 -0
  483. package/src/pages/session/timeline/virtual-items.ts +3 -0
  484. package/src/pages/session/usage-exceeded-dialogs.tsx +104 -0
  485. package/src/pages/session/use-composer-commands.tsx +83 -0
  486. package/src/pages/session/use-session-commands.tsx +606 -0
  487. package/src/pages/session/use-session-hash-scroll.test.ts +16 -0
  488. package/src/pages/session/use-session-hash-scroll.ts +202 -0
  489. package/src/pages/session/v2/review-diff-kinds.test.ts +70 -0
  490. package/src/pages/session/v2/review-diff-kinds.ts +62 -0
  491. package/src/pages/session/v2/review-panel-v2-state.ts +41 -0
  492. package/src/pages/session/v2/review-panel-v2.tsx +255 -0
  493. package/src/pages/session/v2/session-file-browser-tab.tsx +181 -0
  494. package/src/pages/session/v2/session-file-list-v2.tsx +166 -0
  495. package/src/pages/session.tsx +2385 -0
  496. package/src/sst-env.d.ts +12 -0
  497. package/src/theme-preload.test.ts +46 -0
  498. package/src/updater.ts +17 -0
  499. package/src/utils/agent.ts +44 -0
  500. package/src/utils/aim.ts +138 -0
  501. package/src/utils/base64.ts +10 -0
  502. package/src/utils/comment-note.ts +88 -0
  503. package/src/utils/diffs.test.ts +75 -0
  504. package/src/utils/diffs.ts +50 -0
  505. package/src/utils/file-manager.ts +24 -0
  506. package/src/utils/id.ts +93 -0
  507. package/src/utils/notification-click.test.ts +27 -0
  508. package/src/utils/notification-click.ts +13 -0
  509. package/src/utils/path-key.ts +24 -0
  510. package/src/utils/persist.test.ts +211 -0
  511. package/src/utils/persist.ts +661 -0
  512. package/src/utils/prompt.test.ts +44 -0
  513. package/src/utils/prompt.ts +203 -0
  514. package/src/utils/refcount.test.ts +49 -0
  515. package/src/utils/refcount.ts +32 -0
  516. package/src/utils/runtime-adapters.test.ts +64 -0
  517. package/src/utils/runtime-adapters.ts +39 -0
  518. package/src/utils/same.ts +6 -0
  519. package/src/utils/scoped-cache.test.ts +69 -0
  520. package/src/utils/scoped-cache.ts +104 -0
  521. package/src/utils/search-keydown.ts +116 -0
  522. package/src/utils/server-compat.test.ts +193 -0
  523. package/src/utils/server-compat.ts +512 -0
  524. package/src/utils/server-errors.test.ts +175 -0
  525. package/src/utils/server-errors.ts +109 -0
  526. package/src/utils/server-health.test.ts +178 -0
  527. package/src/utils/server-health.ts +172 -0
  528. package/src/utils/server-protocol.test.ts +40 -0
  529. package/src/utils/server-protocol.ts +35 -0
  530. package/src/utils/server-scope.test.ts +61 -0
  531. package/src/utils/server-scope.ts +73 -0
  532. package/src/utils/server.test.ts +23 -0
  533. package/src/utils/server.ts +62 -0
  534. package/src/utils/session-message.test.ts +214 -0
  535. package/src/utils/session-message.ts +355 -0
  536. package/src/utils/session-route.test.ts +71 -0
  537. package/src/utils/session-route.ts +39 -0
  538. package/src/utils/session-title.ts +7 -0
  539. package/src/utils/session.test.ts +94 -0
  540. package/src/utils/session.ts +37 -0
  541. package/src/utils/solid-dnd.tsx +49 -0
  542. package/src/utils/sound.ts +102 -0
  543. package/src/utils/terminal-websocket-url.test.ts +80 -0
  544. package/src/utils/terminal-websocket-url.ts +35 -0
  545. package/src/utils/terminal-writer.test.ts +64 -0
  546. package/src/utils/terminal-writer.ts +65 -0
  547. package/src/utils/time.ts +22 -0
  548. package/src/utils/toast.tsx +34 -0
  549. package/src/utils/uuid.test.ts +78 -0
  550. package/src/utils/uuid.ts +12 -0
  551. package/src/utils/worktree.test.ts +58 -0
  552. package/src/utils/worktree.ts +76 -0
  553. package/src/wsl/add-server-probes.ts +57 -0
  554. package/src/wsl/context.tsx +36 -0
  555. package/src/wsl/dialog-add-server.tsx +463 -0
  556. package/src/wsl/dialog-add-wsl-server.css +392 -0
  557. package/src/wsl/settings-model.test.ts +231 -0
  558. package/src/wsl/settings-model.ts +341 -0
  559. package/src/wsl/settings.tsx +173 -0
  560. package/src/wsl/types.ts +85 -0
  561. package/sst-env.d.ts +10 -0
  562. package/test-browser/command-palette.test.ts +78 -0
  563. package/test-browser/motion-spring.test.ts +20 -0
  564. package/test-browser/prompt-attachments.test.ts +90 -0
  565. package/test-browser/prompt-persistence.test.ts +71 -0
  566. package/test-browser/prompt-scope.test.ts +14 -0
  567. package/test-browser/prompt-submission-state.test.ts +73 -0
  568. package/test-browser/prompt-transient-state.test.ts +38 -0
  569. package/test-browser/review-panel-v2-state.test.ts +65 -0
  570. package/test-browser/session-lineage.test.ts +231 -0
  571. package/test-browser/session-ownership.test.ts +45 -0
  572. package/test-browser/solid-virtual.test.ts +112 -0
  573. package/test-browser/toast-owner.test.ts +26 -0
  574. package/tsconfig.json +26 -0
  575. package/vendor/opencode-ai-client-1.17.13.tgz +0 -0
  576. package/vite.config.ts +33 -0
  577. package/vite.js +48 -0
@@ -0,0 +1,661 @@
1
+ import { Platform, usePlatform } from "@/context/platform"
2
+ import { makePersisted, type AsyncStorage, type SyncStorage } from "@solid-primitives/storage"
3
+ import { checksum } from "@neurocode-ai/core/util/encode"
4
+ import { createResource, type Accessor } from "solid-js"
5
+ import type { SetStoreFunction, Store } from "solid-js/store"
6
+ import { pathKey } from "@/utils/path-key"
7
+ import { ScopedKey, ServerScope, type ServerScope as ServerScopeValue } from "@/utils/server-scope"
8
+
9
+ type InitType = Promise<string> | string | null
10
+ type PersistedWithReady<T> = [
11
+ Store<T>,
12
+ SetStoreFunction<T>,
13
+ InitType,
14
+ Accessor<boolean> & { promise: undefined | Promise<any> },
15
+ ]
16
+
17
+ type PersistTarget = {
18
+ storage?: string
19
+ scope?: "window"
20
+ legacyStorageNames?: string[]
21
+ key: string
22
+ legacy?: string[]
23
+ migrate?: (value: unknown) => unknown
24
+ }
25
+
26
+ const LEGACY_STORAGE = "default.dat"
27
+ const GLOBAL_STORAGE = "neurocode.global.dat"
28
+ const WINDOW_STORAGE = "neurocode.window"
29
+ const LOCAL_PREFIX = "neurocode."
30
+ const fallback = new Map<string, boolean>()
31
+
32
+ const CACHE_MAX_ENTRIES = 500
33
+ const CACHE_MAX_BYTES = 8 * 1024 * 1024
34
+
35
+ type CacheEntry = { value: string; bytes: number }
36
+ const cache = new Map<string, CacheEntry>()
37
+ const cacheTotal = { bytes: 0 }
38
+
39
+ function cacheDelete(key: string) {
40
+ const entry = cache.get(key)
41
+ if (!entry) return
42
+ cacheTotal.bytes -= entry.bytes
43
+ cache.delete(key)
44
+ }
45
+
46
+ function cachePrune() {
47
+ for (;;) {
48
+ if (cache.size <= CACHE_MAX_ENTRIES && cacheTotal.bytes <= CACHE_MAX_BYTES) return
49
+ const oldest = cache.keys().next().value as string | undefined
50
+ if (!oldest) return
51
+ cacheDelete(oldest)
52
+ }
53
+ }
54
+
55
+ function cacheSet(key: string, value: string) {
56
+ const bytes = value.length * 2
57
+ if (bytes > CACHE_MAX_BYTES) {
58
+ cacheDelete(key)
59
+ return
60
+ }
61
+
62
+ const entry = cache.get(key)
63
+ if (entry) cacheTotal.bytes -= entry.bytes
64
+ cache.delete(key)
65
+ cache.set(key, { value, bytes })
66
+ cacheTotal.bytes += bytes
67
+ cachePrune()
68
+ }
69
+
70
+ function cacheGet(key: string) {
71
+ const entry = cache.get(key)
72
+ if (!entry) return
73
+ cache.delete(key)
74
+ cache.set(key, entry)
75
+ return entry.value
76
+ }
77
+
78
+ function fallbackDisabled(scope: string) {
79
+ return fallback.get(scope) === true
80
+ }
81
+
82
+ function fallbackSet(scope: string) {
83
+ fallback.set(scope, true)
84
+ }
85
+
86
+ function quota(error: unknown) {
87
+ if (error instanceof DOMException) {
88
+ if (error.name === "QuotaExceededError") return true
89
+ if (error.name === "NS_ERROR_DOM_QUOTA_REACHED") return true
90
+ if (error.name === "QUOTA_EXCEEDED_ERR") return true
91
+ if (error.code === 22 || error.code === 1014) return true
92
+ return false
93
+ }
94
+
95
+ if (!error || typeof error !== "object") return false
96
+ const name = (error as { name?: string }).name
97
+ if (name === "QuotaExceededError" || name === "NS_ERROR_DOM_QUOTA_REACHED") return true
98
+ if (name && /quota/i.test(name)) return true
99
+
100
+ const code = (error as { code?: number }).code
101
+ if (code === 22 || code === 1014) return true
102
+
103
+ const message = (error as { message?: string }).message
104
+ if (typeof message !== "string") return false
105
+ if (/quota/i.test(message)) return true
106
+ return false
107
+ }
108
+
109
+ type Evict = { key: string; size: number }
110
+
111
+ function evict(storage: Storage, keep: string, value: string) {
112
+ const total = storage.length
113
+ const indexes = Array.from({ length: total }, (_, index) => index)
114
+ const items: Evict[] = []
115
+
116
+ for (const index of indexes) {
117
+ const name = storage.key(index)
118
+ if (!name) continue
119
+ if (!name.startsWith(LOCAL_PREFIX)) continue
120
+ if (name === keep) continue
121
+ const stored = storage.getItem(name)
122
+ items.push({ key: name, size: stored?.length ?? 0 })
123
+ }
124
+
125
+ items.sort((a, b) => b.size - a.size)
126
+
127
+ for (const item of items) {
128
+ storage.removeItem(item.key)
129
+ cacheDelete(item.key)
130
+
131
+ try {
132
+ storage.setItem(keep, value)
133
+ cacheSet(keep, value)
134
+ return true
135
+ } catch (error) {
136
+ if (!quota(error)) throw error
137
+ }
138
+ }
139
+
140
+ return false
141
+ }
142
+
143
+ function write(storage: Storage, key: string, value: string) {
144
+ try {
145
+ storage.setItem(key, value)
146
+ cacheSet(key, value)
147
+ return true
148
+ } catch (error) {
149
+ if (!quota(error)) throw error
150
+ }
151
+
152
+ try {
153
+ storage.removeItem(key)
154
+ cacheDelete(key)
155
+ storage.setItem(key, value)
156
+ cacheSet(key, value)
157
+ return true
158
+ } catch (error) {
159
+ if (!quota(error)) throw error
160
+ }
161
+
162
+ const ok = evict(storage, key, value)
163
+ return ok
164
+ }
165
+
166
+ function snapshot(value: unknown) {
167
+ return JSON.parse(JSON.stringify(value)) as unknown
168
+ }
169
+
170
+ function isRecord(value: unknown): value is Record<string, unknown> {
171
+ return typeof value === "object" && value !== null && !Array.isArray(value)
172
+ }
173
+
174
+ function merge(defaults: unknown, value: unknown): unknown {
175
+ if (value === undefined) return defaults
176
+ if (value === null) return value
177
+
178
+ if (Array.isArray(defaults)) {
179
+ if (Array.isArray(value)) return value
180
+ return defaults
181
+ }
182
+
183
+ if (isRecord(defaults)) {
184
+ if (!isRecord(value)) return defaults
185
+
186
+ const result: Record<string, unknown> = { ...defaults }
187
+ for (const key of Object.keys(value)) {
188
+ if (key in defaults) {
189
+ result[key] = merge((defaults as Record<string, unknown>)[key], (value as Record<string, unknown>)[key])
190
+ } else {
191
+ result[key] = (value as Record<string, unknown>)[key]
192
+ }
193
+ }
194
+ return result
195
+ }
196
+
197
+ return value
198
+ }
199
+
200
+ function parse(value: string) {
201
+ try {
202
+ return JSON.parse(value) as unknown
203
+ } catch {
204
+ return undefined
205
+ }
206
+ }
207
+
208
+ function normalize(defaults: unknown, raw: string, migrate?: (value: unknown) => unknown) {
209
+ const parsed = parse(raw)
210
+ if (parsed === undefined) return
211
+ const migrated = migrate ? migrate(parsed) : parsed
212
+ const merged = merge(defaults, migrated)
213
+ return JSON.stringify(merged)
214
+ }
215
+
216
+ function readCurrent(input: {
217
+ storage: SyncStorage
218
+ key: string
219
+ defaults: unknown
220
+ migrate?: (value: unknown) => unknown
221
+ }) {
222
+ const raw = input.storage.getItem(input.key)
223
+ if (raw === null) return
224
+ const next = normalize(input.defaults, raw, input.migrate)
225
+ if (next === undefined) {
226
+ input.storage.removeItem(input.key)
227
+ return null
228
+ }
229
+ if (raw !== next) input.storage.setItem(input.key, next)
230
+ return next
231
+ }
232
+
233
+ function migrateLegacy(input: {
234
+ current: SyncStorage
235
+ legacyStore?: SyncStorage
236
+ stores: SyncStorage[]
237
+ keys: string[]
238
+ key: string
239
+ defaults: unknown
240
+ migrate?: (value: unknown) => unknown
241
+ }) {
242
+ for (const store of input.stores) {
243
+ const raw = store.getItem(input.key)
244
+ if (raw === null) continue
245
+
246
+ const next = normalize(input.defaults, raw, input.migrate)
247
+ if (next === undefined) {
248
+ store.removeItem(input.key)
249
+ continue
250
+ }
251
+ input.current.setItem(input.key, next)
252
+ store.removeItem(input.key)
253
+ return next
254
+ }
255
+
256
+ if (!input.legacyStore) return null
257
+
258
+ for (const key of input.keys) {
259
+ const raw = input.legacyStore.getItem(key)
260
+ if (raw === null) continue
261
+
262
+ const next = normalize(input.defaults, raw, input.migrate)
263
+ if (next === undefined) {
264
+ input.legacyStore.removeItem(key)
265
+ continue
266
+ }
267
+ input.current.setItem(input.key, next)
268
+ input.legacyStore.removeItem(key)
269
+ return next
270
+ }
271
+
272
+ return null
273
+ }
274
+
275
+ async function readCurrentAsync(input: {
276
+ storage: AsyncStorage
277
+ key: string
278
+ defaults: unknown
279
+ migrate?: (value: unknown) => unknown
280
+ }) {
281
+ const raw = await input.storage.getItem(input.key)
282
+ if (raw === null) return
283
+ const next = normalize(input.defaults, raw, input.migrate)
284
+ if (next === undefined) {
285
+ await input.storage.removeItem(input.key).catch(() => undefined)
286
+ return null
287
+ }
288
+ if (raw !== next) await input.storage.setItem(input.key, next)
289
+ return next
290
+ }
291
+
292
+ async function removeAsync(storage: AsyncStorage, key: string) {
293
+ try {
294
+ await storage.removeItem(key)
295
+ } catch {}
296
+ }
297
+
298
+ async function migrateLegacyAsync(input: {
299
+ current: AsyncStorage
300
+ legacyStore?: AsyncStorage
301
+ stores: AsyncStorage[]
302
+ keys: string[]
303
+ key: string
304
+ defaults: unknown
305
+ migrate?: (value: unknown) => unknown
306
+ }) {
307
+ for (const store of input.stores) {
308
+ const raw = await store.getItem(input.key)
309
+ if (raw === null) continue
310
+
311
+ const next = normalize(input.defaults, raw, input.migrate)
312
+ if (next === undefined) {
313
+ await removeAsync(store, input.key)
314
+ continue
315
+ }
316
+ await input.current.setItem(input.key, next)
317
+ await store.removeItem(input.key)
318
+ return next
319
+ }
320
+
321
+ if (!input.legacyStore) return null
322
+
323
+ for (const key of input.keys) {
324
+ const raw = await input.legacyStore.getItem(key)
325
+ if (raw === null) continue
326
+
327
+ const next = normalize(input.defaults, raw, input.migrate)
328
+ if (next === undefined) {
329
+ await removeAsync(input.legacyStore, key)
330
+ continue
331
+ }
332
+ await input.current.setItem(input.key, next)
333
+ await input.legacyStore.removeItem(key)
334
+ return next
335
+ }
336
+
337
+ return null
338
+ }
339
+
340
+ function workspaceStorage(dir: string) {
341
+ const head = (dir.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-")
342
+ const sum = checksum(dir) ?? "0"
343
+ return `neurocode.workspace.${head}.${sum}.dat`
344
+ }
345
+
346
+ function draftStorage(draftID: string) {
347
+ const head = (draftID.slice(0, 12) || "draft").replace(/[^a-zA-Z0-9._-]/g, "-")
348
+ const sum = checksum(draftID) ?? "0"
349
+ return `neurocode.draft.${head}.${sum}.dat`
350
+ }
351
+
352
+ function windowStorage(windowID: string) {
353
+ const safe = (windowID || "browser").replace(/[^a-zA-Z0-9._-]/g, "-")
354
+ return `${WINDOW_STORAGE}.${safe}.dat`
355
+ }
356
+
357
+ function legacyWorkspaceStorage(dir: string) {
358
+ const storage = workspaceStorage(pathKey(dir))
359
+ const result = new Set<string>()
360
+ const raw = workspaceStorage(dir)
361
+ if (raw !== storage) result.add(raw)
362
+
363
+ const key = pathKey(dir)
364
+ const drive = key.length >= 3 && key[1] === ":" && key[2] === "/"
365
+ if (drive) {
366
+ const backslash = workspaceStorage(key.replaceAll("/", "\\"))
367
+ if (backslash !== storage) result.add(backslash)
368
+ }
369
+
370
+ if (result.size === 0) return
371
+ return [...result]
372
+ }
373
+
374
+ function serverWorkspaceTarget(scope: ServerScopeValue, dir: string, key: string, legacy?: string[]): PersistTarget {
375
+ if (scope !== ServerScope.local) return { storage: workspaceStorage(ScopedKey.from(scope, pathKey(dir))), key }
376
+ return { storage: workspaceStorage(pathKey(dir)), legacyStorageNames: legacyWorkspaceStorage(dir), key, legacy }
377
+ }
378
+
379
+ function localStorageWithPrefix(prefix: string): SyncStorage {
380
+ const base = `${prefix}:`
381
+ const scope = `prefix:${prefix}`
382
+ const item = (key: string) => base + key
383
+ return {
384
+ getItem: (key) => {
385
+ const name = item(key)
386
+ const cached = cacheGet(name)
387
+ if (fallbackDisabled(scope)) return cached ?? null
388
+
389
+ const stored = (() => {
390
+ try {
391
+ return localStorage.getItem(name)
392
+ } catch {
393
+ fallbackSet(scope)
394
+ return null
395
+ }
396
+ })()
397
+ if (stored === null) return cached ?? null
398
+ cacheSet(name, stored)
399
+ return stored
400
+ },
401
+ setItem: (key, value) => {
402
+ const name = item(key)
403
+ if (fallbackDisabled(scope)) return
404
+ try {
405
+ if (write(localStorage, name, value)) return
406
+ } catch {
407
+ fallbackSet(scope)
408
+ return
409
+ }
410
+ fallbackSet(scope)
411
+ },
412
+ removeItem: (key) => {
413
+ const name = item(key)
414
+ cacheDelete(name)
415
+ if (fallbackDisabled(scope)) return
416
+ try {
417
+ localStorage.removeItem(name)
418
+ } catch {
419
+ fallbackSet(scope)
420
+ }
421
+ },
422
+ }
423
+ }
424
+
425
+ function localStorageDirect(): SyncStorage {
426
+ const scope = "direct"
427
+ return {
428
+ getItem: (key) => {
429
+ const cached = cacheGet(key)
430
+ if (fallbackDisabled(scope)) return cached ?? null
431
+
432
+ const stored = (() => {
433
+ try {
434
+ return localStorage.getItem(key)
435
+ } catch {
436
+ fallbackSet(scope)
437
+ return null
438
+ }
439
+ })()
440
+ if (stored === null) return cached ?? null
441
+ cacheSet(key, stored)
442
+ return stored
443
+ },
444
+ setItem: (key, value) => {
445
+ if (fallbackDisabled(scope)) return
446
+ try {
447
+ if (write(localStorage, key, value)) return
448
+ } catch {
449
+ fallbackSet(scope)
450
+ return
451
+ }
452
+ fallbackSet(scope)
453
+ },
454
+ removeItem: (key) => {
455
+ cacheDelete(key)
456
+ if (fallbackDisabled(scope)) return
457
+ try {
458
+ localStorage.removeItem(key)
459
+ } catch {
460
+ fallbackSet(scope)
461
+ }
462
+ },
463
+ }
464
+ }
465
+
466
+ const DRAFT_PERSISTED_KEYS = ["prompt", "comments", "file-view", "layout"]
467
+
468
+ export function draftPersistedKeys() {
469
+ return DRAFT_PERSISTED_KEYS
470
+ }
471
+
472
+ export const PersistTesting = {
473
+ localStorageDirect,
474
+ localStorageWithPrefix,
475
+ migrateLegacy,
476
+ normalize,
477
+ resolveTarget,
478
+ windowStorage,
479
+ workspaceStorage,
480
+ }
481
+
482
+ export const Persist = {
483
+ global(key: string, legacy?: string[]): PersistTarget {
484
+ return { storage: GLOBAL_STORAGE, key, legacy }
485
+ },
486
+ window(key: string, legacy?: string[]): PersistTarget {
487
+ return { scope: "window", key, legacy }
488
+ },
489
+ draft(draftID: string, key: string, legacy?: string[]): PersistTarget {
490
+ return { storage: draftStorage(draftID), key: `draft:${key}`, legacy }
491
+ },
492
+ serverGlobal(scope: ServerScopeValue, key: string, legacy?: string[]): PersistTarget {
493
+ if (scope === ServerScope.local) return Persist.global(key, legacy)
494
+ return { storage: GLOBAL_STORAGE, key: ScopedKey.from(scope, key) }
495
+ },
496
+ workspace(dir: string, key: string, legacy?: string[]): PersistTarget {
497
+ return serverWorkspaceTarget(ServerScope.local, dir, `workspace:${key}`, legacy)
498
+ },
499
+ serverWorkspace(scope: ServerScopeValue, dir: string, key: string, legacy?: string[]): PersistTarget {
500
+ return serverWorkspaceTarget(scope, dir, `workspace:${key}`, legacy)
501
+ },
502
+ session(dir: string, session: string, key: string, legacy?: string[]): PersistTarget {
503
+ return serverWorkspaceTarget(ServerScope.local, dir, `session:${session}:${key}`, legacy)
504
+ },
505
+ serverSession(scope: ServerScopeValue, dir: string, session: string, key: string, legacy?: string[]): PersistTarget {
506
+ return serverWorkspaceTarget(scope, dir, `session:${session}:${key}`, legacy)
507
+ },
508
+ scoped(dir: string, session: string | undefined, key: string, legacy?: string[]): PersistTarget {
509
+ if (session) return Persist.session(dir, session, key, legacy)
510
+ return Persist.workspace(dir, key, legacy)
511
+ },
512
+ serverScoped(scope: ServerScopeValue, dir: string, session: string | undefined, key: string, legacy?: string[]) {
513
+ if (session) return Persist.serverSession(scope, dir, session, key, legacy)
514
+ return Persist.serverWorkspace(scope, dir, key, legacy)
515
+ },
516
+ }
517
+
518
+ function resolveTarget(target: PersistTarget, platform: Platform): PersistTarget {
519
+ if (target.scope !== "window") return target
520
+ if (platform.platform === "desktop" && !platform.windowID) return { ...target, storage: GLOBAL_STORAGE }
521
+ const windowID = platform.platform === "desktop" ? (platform.windowID ?? "browser") : "browser"
522
+ return {
523
+ ...target,
524
+ storage: windowStorage(windowID),
525
+ }
526
+ }
527
+
528
+ export function removePersisted(
529
+ target: { storage?: string; legacyStorageNames?: string[]; key: string },
530
+ platform?: Platform,
531
+ ) {
532
+ const isDesktop = platform?.platform === "desktop" && !!platform.storage
533
+
534
+ if (isDesktop) {
535
+ void platform.storage?.(target.storage)?.removeItem(target.key)
536
+ for (const storage of target.legacyStorageNames ?? []) {
537
+ void platform.storage?.(storage)?.removeItem(target.key)
538
+ }
539
+ return
540
+ }
541
+
542
+ if (!target.storage) {
543
+ localStorageDirect().removeItem(target.key)
544
+ return
545
+ }
546
+
547
+ localStorageWithPrefix(target.storage).removeItem(target.key)
548
+ for (const storage of target.legacyStorageNames ?? []) {
549
+ localStorageWithPrefix(storage).removeItem(target.key)
550
+ }
551
+ }
552
+
553
+ export function persisted<T>(
554
+ target: string | PersistTarget,
555
+ store: [Store<T>, SetStoreFunction<T>],
556
+ ): PersistedWithReady<T> {
557
+ const platform = usePlatform()
558
+ const config = resolveTarget(typeof target === "string" ? { key: target } : target, platform)
559
+
560
+ const defaults = snapshot(store[0])
561
+ const legacy = config.legacy ?? []
562
+
563
+ const isDesktop = platform.platform === "desktop" && !!platform.storage
564
+
565
+ const currentStorage = (() => {
566
+ if (isDesktop) return platform.storage?.(config.storage)
567
+ if (!config.storage) return localStorageDirect()
568
+ return localStorageWithPrefix(config.storage)
569
+ })()
570
+
571
+ const legacyStorage = (() => {
572
+ if (!isDesktop) return localStorageDirect()
573
+ if (!config.storage) return platform.storage?.()
574
+ return platform.storage?.(LEGACY_STORAGE)
575
+ })()
576
+
577
+ const legacyStorageNames = config.legacyStorageNames ?? []
578
+
579
+ const storage = (() => {
580
+ if (!isDesktop) {
581
+ const current = currentStorage as SyncStorage
582
+ const legacyStore = legacyStorage as SyncStorage
583
+ const legacyStores = legacyStorageNames.map(localStorageWithPrefix)
584
+
585
+ const api: SyncStorage = {
586
+ getItem: (key) => {
587
+ const value = readCurrent({ storage: current, key, defaults, migrate: config.migrate })
588
+ if (value !== undefined) return value
589
+ return migrateLegacy({
590
+ current,
591
+ legacyStore,
592
+ stores: legacyStores,
593
+ keys: legacy,
594
+ key,
595
+ defaults,
596
+ migrate: config.migrate,
597
+ })
598
+ },
599
+ setItem: (key, value) => {
600
+ current.setItem(key, value)
601
+ },
602
+ removeItem: (key) => {
603
+ current.removeItem(key)
604
+ },
605
+ }
606
+
607
+ return api
608
+ }
609
+
610
+ const current = currentStorage as AsyncStorage
611
+ const legacyStore = legacyStorage as AsyncStorage | undefined
612
+ const legacyStores = legacyStorageNames
613
+ .map((name) => platform.storage?.(name) as AsyncStorage | undefined)
614
+ .filter((x) => !!x)
615
+
616
+ const api: AsyncStorage = {
617
+ getItem: async (key) => {
618
+ const value = await readCurrentAsync({ storage: current, key, defaults, migrate: config.migrate })
619
+ if (value !== undefined) return value
620
+ return migrateLegacyAsync({
621
+ current,
622
+ legacyStore,
623
+ stores: legacyStores,
624
+ keys: legacy,
625
+ key,
626
+ defaults,
627
+ migrate: config.migrate,
628
+ })
629
+ },
630
+ setItem: async (key, value) => {
631
+ await current.setItem(key, value)
632
+ },
633
+ removeItem: async (key) => {
634
+ await current.removeItem(key)
635
+ },
636
+ }
637
+
638
+ return api
639
+ })()
640
+
641
+ const [state, setState, init] = makePersisted(store, { name: config.key, storage })
642
+
643
+ const isAsync = init instanceof Promise
644
+ const [ready] = createResource(
645
+ () => init,
646
+ async (initValue) => {
647
+ if (initValue instanceof Promise) await initValue
648
+ return true
649
+ },
650
+ { initialValue: !isAsync },
651
+ )
652
+
653
+ return [
654
+ state,
655
+ setState,
656
+ init,
657
+ Object.assign(() => (ready.loading ? false : ready.latest === true), {
658
+ promise: init instanceof Promise ? init : undefined,
659
+ }),
660
+ ]
661
+ }
@@ -0,0 +1,44 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import type { Part } from "@neurocode-ai/sdk/v2"
3
+ import { extractPromptFromParts } from "./prompt"
4
+
5
+ describe("extractPromptFromParts", () => {
6
+ test("restores multiple uploaded attachments", () => {
7
+ const parts = [
8
+ {
9
+ id: "text_1",
10
+ type: "text",
11
+ text: "check these",
12
+ sessionID: "ses_1",
13
+ messageID: "msg_1",
14
+ },
15
+ {
16
+ id: "file_1",
17
+ type: "file",
18
+ mime: "image/png",
19
+ url: "data:image/png;base64,AAA",
20
+ filename: "a.png",
21
+ sessionID: "ses_1",
22
+ messageID: "msg_1",
23
+ },
24
+ {
25
+ id: "file_2",
26
+ type: "file",
27
+ mime: "application/pdf",
28
+ url: "data:application/pdf;base64,BBB",
29
+ filename: "b.pdf",
30
+ sessionID: "ses_1",
31
+ messageID: "msg_1",
32
+ },
33
+ ] satisfies Part[]
34
+
35
+ const result = extractPromptFromParts(parts)
36
+
37
+ expect(result).toHaveLength(3)
38
+ expect(result[0]).toMatchObject({ type: "text", content: "check these" })
39
+ expect(result.slice(1)).toMatchObject([
40
+ { type: "image", filename: "a.png", mime: "image/png", dataUrl: "data:image/png;base64,AAA" },
41
+ { type: "image", filename: "b.pdf", mime: "application/pdf", dataUrl: "data:application/pdf;base64,BBB" },
42
+ ])
43
+ })
44
+ })