@aiworker/web 1.22.0 → 1.23.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 (415) hide show
  1. package/README.md +4 -4
  2. package/bin/cli.js +14 -4
  3. package/bin/cli.test.js +165 -73
  4. package/bin/lib/cli-api-target.js +2 -2
  5. package/bin/lib/cli-args.js +119 -119
  6. package/bin/lib/cli-lifecycle.js +9 -9
  7. package/bin/lib/cli-network.js +3 -3
  8. package/bin/lib/cli-paths.js +3 -3
  9. package/bin/lib/cli-ports.js +2 -2
  10. package/bin/lib/cli-process.js +4 -4
  11. package/bin/lib/cli-startup.js +64 -15
  12. package/bin/lib/cli-tunnel-profiles.js +1 -1
  13. package/bin/lib/cli-tunnel-utils.js +2 -2
  14. package/bin/lib/commands-connect-url.js +19 -19
  15. package/bin/lib/commands-lifecycle.js +27 -27
  16. package/bin/lib/commands-logs.js +5 -5
  17. package/bin/lib/commands-models.js +1 -1
  18. package/bin/lib/commands-projects.js +1 -1
  19. package/bin/lib/commands-schedule.js +1 -1
  20. package/bin/lib/commands-serve.js +29 -29
  21. package/bin/lib/commands-session.js +1 -1
  22. package/bin/lib/commands-startup.js +45 -10
  23. package/bin/lib/commands-status.js +1 -1
  24. package/bin/lib/commands-tunnel.js +40 -40
  25. package/bin/lib/commands-update.js +1 -1
  26. package/bin/lib/commands-update.test.js +6 -6
  27. package/dist/assets/{CodeMirrorCommentWidgets-B3u7UIcE.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
  28. package/dist/assets/DiffView-DXjVsEOw.js +4 -0
  29. package/dist/assets/DirtyBranchSwitchDialog-aig6zJjg.js +1 -0
  30. package/dist/assets/FilesView-CJdG0-wk.js +17 -0
  31. package/dist/assets/GitView-BXsS3jG6.js +6 -0
  32. package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-D_hpXBnF.js} +1 -1
  33. package/dist/assets/LinearIssuesView-BoUiLQG1.js +1 -0
  34. package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
  35. package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
  36. package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
  37. package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-8p6nLJaU.js} +1 -1
  38. package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
  39. package/dist/assets/PlanView-CJTmOF6I.js +5 -0
  40. package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
  41. package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-Cpb4hM6q.js} +1 -1
  42. package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
  43. package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
  44. package/dist/assets/ToolPartDiffPreview-DtZLzrSK.js +8 -0
  45. package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
  46. package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
  47. package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
  48. package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
  49. package/dist/assets/de-DCrsQ5zS.js +10 -0
  50. package/dist/assets/es-Dwv4YfH6.js +16 -0
  51. package/dist/assets/fr-BCWg4Cnx.js +14 -0
  52. package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
  53. package/dist/assets/index-B68h5S5B.css +1 -0
  54. package/dist/assets/index-DVwMarHI.js +39 -0
  55. package/dist/assets/{index-CODwDsU_.js → index-jXSK5RrB.js} +1 -1
  56. package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-B_x_rVon.js} +1 -1
  57. package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-DW_xMer1.js} +1 -1
  58. package/dist/assets/ja-Bruw3RYC.js +16 -0
  59. package/dist/assets/ko-idhXGF8j.js +16 -0
  60. package/dist/assets/main-BkyaQhJX.js +930 -0
  61. package/dist/assets/{main-C5DVOxc6.js → main-CzdPuL0s.js} +2 -2
  62. package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
  63. package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-CCkqKOLp.js} +2 -2
  64. package/dist/assets/{mobile-CcaxB0n4.js → mobile-mvxTo055.js} +2 -2
  65. package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
  66. package/dist/assets/pl-CGZWUqZJ.js +16 -0
  67. package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
  68. package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
  69. package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
  70. package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
  71. package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
  72. package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-CRl0SUC9.js} +1 -1
  73. package/dist/assets/surface-oMvhsog3.js +1 -0
  74. package/dist/assets/tr-BK_KiO2V.js +7 -0
  75. package/dist/assets/uk-CG74xV8N.js +16 -0
  76. package/dist/assets/useAppFontEffects-CPKeOpUo.js +7284 -0
  77. package/dist/assets/useGitComparison-DH220WmO.js +1 -0
  78. package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
  79. package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
  80. package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
  81. package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
  82. package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
  83. package/dist/assets/{web-components-Bhg83tjf.js → web-components-z7hfnS8n.js} +1 -1
  84. package/dist/assets/zh-CN-T4AXofZI.js +16 -0
  85. package/dist/assets/zh-TW-Dte40sxp.js +16 -0
  86. package/dist/index.html +21 -59
  87. package/dist/mini-chat.html +7 -7
  88. package/dist/mobile.html +5 -5
  89. package/package.json +3 -4
  90. package/server/index.d.ts +15 -0
  91. package/server/index.js +158 -89
  92. package/server/lib/agent-memory/actions.js +2 -2
  93. package/server/lib/agent-memory/feature-flag.js +1 -1
  94. package/server/lib/agent-memory/feature-flag.test.js +8 -8
  95. package/server/lib/agent-memory/routes.js +2 -2
  96. package/server/lib/agent-tool/runtime.js +37 -54
  97. package/server/lib/agent-tool/runtime.test.js +59 -59
  98. package/server/lib/browser-control/broker.js +3 -3
  99. package/server/lib/browser-control/broker.test.js +1 -1
  100. package/server/lib/changelog/update-notes.js +82 -0
  101. package/server/lib/changelog/update-notes.test.js +59 -0
  102. package/server/lib/client-auth/remote-clients.test.js +5 -5
  103. package/server/lib/context-obligatory/runtime.js +7 -7
  104. package/server/lib/context-obligatory/runtime.test.js +5 -5
  105. package/server/lib/data-dir-migration.js +40 -0
  106. package/server/lib/data-dir-migration.test.js +59 -0
  107. package/server/lib/dev-servers/parse.js +2 -2
  108. package/server/lib/dev-servers/parse.test.js +2 -2
  109. package/server/lib/dev-servers/routes.js +1 -1
  110. package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
  111. package/server/lib/dev-tunnel/client.js +1 -1
  112. package/server/lib/dev-tunnel/runtime.js +5 -5
  113. package/server/lib/dev-tunnel/tunnel.test.js +21 -1
  114. package/server/lib/dictation/local/model-catalog.js +1 -1
  115. package/server/lib/dictation/local/worker-process.js +2 -2
  116. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  117. package/server/lib/event-stream/global-ws-bridge.js +1 -1
  118. package/server/lib/event-stream/protocol.test.js +4 -4
  119. package/server/lib/event-stream/runtime.test.js +18 -18
  120. package/server/lib/fs/DOCUMENTATION.md +5 -0
  121. package/server/lib/fs/routes.js +68 -26
  122. package/server/lib/fs/routes.test.js +208 -22
  123. package/server/lib/git/DOCUMENTATION.md +32 -6
  124. package/server/lib/git/identity-storage.js +1 -1
  125. package/server/lib/git/issue-2746-longpaths.test.js +4 -4
  126. package/server/lib/git/routes.js +41 -5
  127. package/server/lib/git/service.js +300 -224
  128. package/server/lib/git/service.test.js +596 -28
  129. package/server/lib/github/auth.js +8 -8
  130. package/server/lib/github/pr-status.js +1 -1
  131. package/server/lib/github/routes.js +2 -2
  132. package/server/lib/inherited-env.js +17 -10
  133. package/server/lib/inherited-env.test.js +10 -11
  134. package/server/lib/linear/auth.js +8 -8
  135. package/server/lib/linear/auth.test.js +24 -24
  136. package/server/lib/linear/issues.test.js +6 -6
  137. package/server/lib/linear/mapping.test.js +4 -4
  138. package/server/lib/linear/oauth.js +2 -2
  139. package/server/lib/linear/oauth.test.js +14 -14
  140. package/server/lib/linear/routes.js +5 -5
  141. package/server/lib/linear/routes.test.js +16 -16
  142. package/server/lib/linear/status-runtime.test.js +9 -9
  143. package/server/lib/linear/status.js +1 -1
  144. package/server/lib/linear/status.test.js +15 -15
  145. package/server/lib/linear/teams.test.js +4 -4
  146. package/server/lib/magic-prompts/routes.js +2 -2
  147. package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
  148. package/server/lib/mcp-reconnect/runtime.js +122 -0
  149. package/server/lib/mcp-reconnect/runtime.test.js +140 -0
  150. package/server/lib/message-queue/DOCUMENTATION.md +161 -0
  151. package/server/lib/message-queue/runtime.js +870 -0
  152. package/server/lib/message-queue/runtime.test.js +482 -0
  153. package/server/lib/network-defaults.js +23 -0
  154. package/server/lib/network-defaults.test.js +52 -0
  155. package/server/lib/notifications/apns-runtime.js +17 -17
  156. package/server/lib/notifications/apns-runtime.test.js +11 -11
  157. package/server/lib/notifications/emitter-runtime.js +1 -1
  158. package/server/lib/notifications/emitter-runtime.test.js +1 -1
  159. package/server/lib/notifications/push-runtime.js +7 -7
  160. package/server/lib/notifications/routes.js +1 -1
  161. package/server/lib/notifications/runtime.js +1 -1
  162. package/server/lib/openchamber-control/action-resolution.test.js +10 -10
  163. package/server/lib/openchamber-control/actions.js +24 -24
  164. package/server/lib/openchamber-control/error.js +4 -4
  165. package/server/lib/openchamber-control/routes.js +2 -2
  166. package/server/lib/openchamber-control/routes.test.js +5 -5
  167. package/server/lib/openchamber-control/screenshots.js +1 -1
  168. package/server/lib/openchamber-control/screenshots.test.js +1 -1
  169. package/server/lib/openchamber-control/service.js +50 -50
  170. package/server/lib/openchamber-control/service.test.js +5 -5
  171. package/server/lib/openchamber-sessions/routes.js +124 -22
  172. package/server/lib/openchamber-sessions/routes.test.js +99 -11
  173. package/server/lib/opencode/DOCUMENTATION.md +30 -6
  174. package/server/lib/opencode/auth.js +1 -1
  175. package/server/lib/opencode/bootstrap-runtime.js +8 -6
  176. package/server/lib/opencode/cli-options.js +8 -8
  177. package/server/lib/opencode/config-paths.test.js +116 -0
  178. package/server/lib/opencode/core-routes.js +8 -7
  179. package/server/lib/opencode/core-routes.test.js +11 -11
  180. package/server/lib/opencode/env-config.js +5 -5
  181. package/server/lib/opencode/env-config.test.js +7 -7
  182. package/server/lib/opencode/env-runtime.js +10 -10
  183. package/server/lib/opencode/env-runtime.test.js +18 -18
  184. package/server/lib/opencode/feature-routes-runtime.js +19 -12
  185. package/server/lib/opencode/hmr-state-runtime.js +2 -2
  186. package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
  187. package/server/lib/opencode/input-history-scope.js +14 -0
  188. package/server/lib/opencode/lifecycle.js +8 -8
  189. package/server/lib/opencode/lifecycle.test.js +4 -4
  190. package/server/lib/opencode/managed-plugin-config.js +35 -0
  191. package/server/lib/opencode/managed-process-registry.js +3 -3
  192. package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
  193. package/server/lib/opencode/openchamber-routes.js +79 -19
  194. package/server/lib/opencode/openchamber-routes.test.js +139 -12
  195. package/server/lib/opencode/plugins.js +2 -2
  196. package/server/lib/opencode/plugins.test.js +1 -1
  197. package/server/lib/opencode/project-icon-routes.js +2 -2
  198. package/server/lib/opencode/project-icon-routes.test.js +1 -1
  199. package/server/lib/opencode/providers.js +44 -2
  200. package/server/lib/opencode/providers.test.js +131 -4
  201. package/server/lib/opencode/proxy.js +2 -2
  202. package/server/lib/opencode/pwa-manifest-routes.js +1 -1
  203. package/server/lib/opencode/restart-session-recovery.test.js +1 -1
  204. package/server/lib/opencode/routes-upgrade.test.js +4 -4
  205. package/server/lib/opencode/routes.js +19 -17
  206. package/server/lib/opencode/server-startup-runtime.js +6 -6
  207. package/server/lib/opencode/session-runtime.js +3 -3
  208. package/server/lib/opencode/session-runtime.test.js +6 -6
  209. package/server/lib/opencode/settings-files.js +214 -0
  210. package/server/lib/opencode/settings-helpers.js +116 -44
  211. package/server/lib/opencode/settings-helpers.test.js +354 -12
  212. package/server/lib/opencode/settings-registry.json +747 -0
  213. package/server/lib/opencode/settings-runtime.js +141 -20
  214. package/server/lib/opencode/settings-runtime.test.js +191 -4
  215. package/server/lib/opencode/shared.js +5 -2
  216. package/server/lib/opencode/shared.test.js +7 -7
  217. package/server/lib/opencode/shutdown-runtime.js +2 -0
  218. package/server/lib/opencode/skill-routes.js +2 -1
  219. package/server/lib/opencode/snippets.js +1 -2
  220. package/server/lib/opencode/startup-performance.js +1 -1
  221. package/server/lib/opencode/startup-performance.test.js +6 -6
  222. package/server/lib/opencode/static-routes-runtime.js +8 -8
  223. package/server/lib/opencode/static-routes-runtime.test.js +7 -7
  224. package/server/lib/opencode/upgrade-capability.js +2 -2
  225. package/server/lib/opencode/upgrade-capability.test.js +3 -3
  226. package/server/lib/package-manager.js +17 -42
  227. package/server/lib/package-manager.test.js +19 -19
  228. package/server/lib/permission-auto-accept/runtime.js +1 -1
  229. package/server/lib/project-context/DOCUMENTATION.md +25 -1
  230. package/server/lib/project-context/routes.http.test.js +18 -0
  231. package/server/lib/project-context/routes.js +16 -1
  232. package/server/lib/project-context/runtime.js +248 -23
  233. package/server/lib/project-context/runtime.test.js +106 -3
  234. package/server/lib/projects/DOCUMENTATION.md +76 -0
  235. package/server/lib/projects/project-config.js +144 -11
  236. package/server/lib/projects/project-id.js +17 -0
  237. package/server/lib/projects/project-setup.js +409 -0
  238. package/server/lib/projects/project-setup.test.js +557 -0
  239. package/server/lib/projects/routes.js +59 -0
  240. package/server/lib/quota/DOCUMENTATION.md +40 -2
  241. package/server/lib/quota/credentials/providers.js +4 -0
  242. package/server/lib/quota/credentials/store.js +4 -4
  243. package/server/lib/quota/credentials/store.test.js +8 -8
  244. package/server/lib/quota/index.js +1 -0
  245. package/server/lib/quota/providers/cline-pass.js +125 -0
  246. package/server/lib/quota/providers/cline-pass.test.js +103 -0
  247. package/server/lib/quota/providers/exe-dev.js +75 -0
  248. package/server/lib/quota/providers/exe-dev.test.js +47 -0
  249. package/server/lib/quota/providers/hyper.js +118 -0
  250. package/server/lib/quota/providers/hyper.test.js +126 -0
  251. package/server/lib/quota/providers/index.js +22 -0
  252. package/server/lib/quota/providers/neuralwatt.js +3 -6
  253. package/server/lib/quota/providers/neuralwatt.test.js +7 -7
  254. package/server/lib/quota/providers/ollama-cloud.js +32 -1
  255. package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
  256. package/server/lib/quota/providers/opencode-go.js +3 -2
  257. package/server/lib/quota/providers/opencode-go.test.js +35 -6
  258. package/server/lib/quota/providers/openrouter.js +119 -24
  259. package/server/lib/quota/providers/openrouter.test.js +330 -0
  260. package/server/lib/quota/providers/xai.js +1 -1
  261. package/server/lib/quota/routes.js +2 -0
  262. package/server/lib/quota/utils/auth.js +1 -1
  263. package/server/lib/relay/DOCUMENTATION.md +56 -1
  264. package/server/lib/relay/cross-compat.test.js +26 -0
  265. package/server/lib/relay/downstream-scheduler.js +154 -0
  266. package/server/lib/relay/downstream-scheduler.test.js +162 -0
  267. package/server/lib/relay/e2ee.js +10 -8
  268. package/server/lib/relay/flow-control.test.js +177 -0
  269. package/server/lib/relay/host-client.js +38 -9
  270. package/server/lib/relay/host-client.test.js +9 -1
  271. package/server/lib/relay/host-lock.js +1 -1
  272. package/server/lib/relay/service.js +5 -5
  273. package/server/lib/relay/tunnel-codec.js +9 -0
  274. package/server/lib/relay/tunnel-host.js +69 -18
  275. package/server/lib/relay/tunnel-host.test.js +9 -1
  276. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
  277. package/server/lib/scheduled-tasks/routes.js +5 -5
  278. package/server/lib/scheduled-tasks/service.js +25 -25
  279. package/server/lib/scheduled-tasks/service.test.js +1 -1
  280. package/server/lib/security/bind-host.js +4 -4
  281. package/server/lib/security/bind-host.test.js +1 -1
  282. package/server/lib/security/request-security.js +11 -5
  283. package/server/lib/security/request-security.test.js +14 -14
  284. package/server/lib/session-assist/runtime.js +15 -18
  285. package/server/lib/session-folders/routes.js +2 -2
  286. package/server/lib/session-folders/routes.test.js +8 -8
  287. package/server/lib/session-goal/DOCUMENTATION.md +20 -7
  288. package/server/lib/session-goal/create.js +4 -2
  289. package/server/lib/session-goal/create.test.js +1 -1
  290. package/server/lib/session-goal/objectives.js +4 -4
  291. package/server/lib/session-goal/routes.js +1 -1
  292. package/server/lib/session-goal/runtime.js +91 -30
  293. package/server/lib/session-goal/runtime.test.js +428 -2
  294. package/server/lib/session-knowledge/runtime.js +11 -11
  295. package/server/lib/session-knowledge/runtime.test.js +5 -5
  296. package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
  297. package/server/lib/skills-catalog/cache.test.js +2 -2
  298. package/server/lib/skills-catalog/disk-cache.js +5 -5
  299. package/server/lib/skills-catalog/github-meta.test.js +2 -2
  300. package/server/lib/skills-catalog/install.js +3 -2
  301. package/server/lib/small-model/DOCUMENTATION.md +7 -2
  302. package/server/lib/small-model/call.js +18 -7
  303. package/server/lib/small-model/call.test.js +58 -6
  304. package/server/lib/small-model/index.js +12 -16
  305. package/server/lib/small-model/index.test.js +1 -1
  306. package/server/lib/small-model/resolve.js +1 -1
  307. package/server/lib/small-model/resolve.test.js +1 -1
  308. package/server/lib/small-model/routes.js +2 -1
  309. package/server/lib/small-model/runtime-providers.js +15 -7
  310. package/server/lib/small-model/runtime-providers.test.js +7 -1
  311. package/server/lib/system-prompt/runtime.js +3 -22
  312. package/server/lib/system-prompt/runtime.test.js +3 -3
  313. package/server/lib/terminal/DOCUMENTATION.md +14 -10
  314. package/server/lib/terminal/runtime.js +190 -31
  315. package/server/lib/terminal/runtime.test.js +572 -38
  316. package/server/lib/terminal/shells.js +20 -3
  317. package/server/lib/terminal/shells.test.js +33 -2
  318. package/server/lib/tts/base-url.js +3 -3
  319. package/server/lib/tts/routes.test.js +20 -20
  320. package/server/lib/ui-auth/ui-auth.js +12 -9
  321. package/server/lib/ui-auth/ui-auth.test.js +19 -3
  322. package/server/lib/ui-auth/ui-passkeys.js +7 -7
  323. package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
  324. package/server/lib/walkthrough/index.js +1 -0
  325. package/server/lib/walkthrough/jobs.test.js +1 -1
  326. package/server/lib/walkthrough/language.test.js +1 -1
  327. package/server/lib/walkthrough/model-settings.js +11 -15
  328. package/server/lib/walkthrough/model-settings.test.js +1 -1
  329. package/server/lib/walkthrough/prompt.js +3 -1
  330. package/server/lib/walkthrough/pull-request.test.js +5 -5
  331. package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
  332. package/server/lib/walkthrough/routes.test.js +26 -5
  333. package/server/lib/walkthrough/sources.js +23 -2
  334. package/server/lib/walkthrough/store.js +3 -3
  335. package/server/lib/walkthrough/store.test.js +1 -1
  336. package/server/opencode-proxy.test.js +2 -2
  337. package/server/proxy-headers.js +1 -1
  338. package/server/sse-routes.test.js +4 -4
  339. package/README.md.bak +0 -250
  340. package/bin/lib/DOCUMENTATION.md.bak +0 -163
  341. package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
  342. package/dist/assets/DiffView-DluiRSla.js +0 -4
  343. package/dist/assets/FilesView-Bdq8ppva.js +0 -17
  344. package/dist/assets/GitView-Znn7VJcP.js +0 -6
  345. package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
  346. package/dist/assets/MarkdownImageGallery-ClOvuZxC.js +0 -1
  347. package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
  348. package/dist/assets/OnboardingScreen-D2DG7K6b.js +0 -1
  349. package/dist/assets/PierreDiffViewer-B7m3f0d7.js +0 -157
  350. package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
  351. package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
  352. package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
  353. package/dist/assets/WalkthroughView-egQgxel3.js +0 -4
  354. package/dist/assets/contextPanelEmbeddedChat-C3fjDbdg.js +0 -1
  355. package/dist/assets/de-C1P6X5Rs.js +0 -9
  356. package/dist/assets/es-CDgaF1_t.js +0 -15
  357. package/dist/assets/fr-Bgxqvvz_.js +0 -13
  358. package/dist/assets/index-C57jT0Sa.css +0 -1
  359. package/dist/assets/index-DW_E3rnL.js +0 -7
  360. package/dist/assets/ja-BDRrEcNP.js +0 -15
  361. package/dist/assets/ko-Ba8sQV79.js +0 -15
  362. package/dist/assets/main-Bv5Pkngh.js +0 -928
  363. package/dist/assets/markdownCore-CZwzz5gw.js +0 -3
  364. package/dist/assets/patchFileDiff-yNfWXN4E.js +0 -10
  365. package/dist/assets/pl-Dioz29BI.js +0 -15
  366. package/dist/assets/pt-BR-DIs6ElwV.js +0 -15
  367. package/dist/assets/renderElectronMiniChatApp-BcKLHXKe.js +0 -2
  368. package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
  369. package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
  370. package/dist/assets/scheduledTasksApi-DLW0mkQK.js +0 -1
  371. package/dist/assets/tr-DFnMlPxY.js +0 -6
  372. package/dist/assets/uk-ZrCNjM7Y.js +0 -15
  373. package/dist/assets/useAppFontEffects-CjrntdL0.js +0 -7310
  374. package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
  375. package/dist/assets/useRootScrollLock-BHYPkrLv.js +0 -1
  376. package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
  377. package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
  378. package/dist/assets/zh-CN-iv_AV4EC.js +0 -15
  379. package/dist/assets/zh-TW-CzEUk38j.js +0 -15
  380. package/server/index.d.ts.bak +0 -39
  381. package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
  382. package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
  383. package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
  384. package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
  385. package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
  386. package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
  387. package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
  388. package/server/lib/git/DOCUMENTATION.md.bak +0 -193
  389. package/server/lib/github/DOCUMENTATION.md.bak +0 -183
  390. package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
  391. package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
  392. package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
  393. package/server/lib/notifications/APNS.md.bak +0 -135
  394. package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
  395. package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
  396. package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
  397. package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
  398. package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
  399. package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
  400. package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
  401. package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
  402. package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
  403. package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
  404. package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
  405. package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
  406. package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
  407. package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
  408. package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
  409. package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
  410. package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
  411. package/server/lib/text/DOCUMENTATION.md.bak +0 -35
  412. package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
  413. package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
  414. package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
  415. package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
@@ -0,0 +1,82 @@
1
+ // Release notes for the update dialog.
2
+ //
3
+ // The repo publishes `changelog/index.json` on `main`: one object per release,
4
+ // newest first, with the app notes grouped as New / Improvements / Fixes /
5
+ // Misc. This module fetches it and renders the releases between the installed
6
+ // version (exclusive) and the offered one (inclusive) as the Markdown the
7
+ // dialog already understands: a `## [x.y.z] - YYYY-MM-DD` header per release,
8
+ // the release title in bold, the intro, then the groups.
9
+ //
10
+ // Any failure (network, 404, unexpected shape) yields null: the update is
11
+ // still offered, only without notes.
12
+
13
+ export const CHANGELOG_INDEX_URL = 'https://raw.githubusercontent.com/openchamber/openchamber/main/changelog/index.json';
14
+
15
+ const GROUPS = [
16
+ ['new', 'New'],
17
+ ['improvements', 'Improvements'],
18
+ ['fixes', 'Fixes'],
19
+ ['misc', 'Misc'],
20
+ ];
21
+
22
+ const VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
23
+ const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
24
+
25
+ // The file crosses a network boundary with no schema library on this side, so
26
+ // each field is coerced into its domain shape here; a release without a valid
27
+ // version and date is dropped.
28
+ const text = (value) => (value === null || value === undefined ? null : String(value).trim() || null);
29
+ const bulletList = (value) => (Array.isArray(value) ? value.map((item) => String(item)) : []);
30
+
31
+ const parseRelease = (entry) => {
32
+ if (entry === null || entry === undefined) return null;
33
+ const version = text(entry.version);
34
+ const date = text(entry.date);
35
+ if (!version || !VERSION_PATTERN.test(version) || !date || !DATE_PATTERN.test(date)) return null;
36
+ const app = entry.app ?? {};
37
+ return {
38
+ version,
39
+ date,
40
+ title: text(entry.title),
41
+ intro: text(entry.intro),
42
+ groups: GROUPS.map(([key, heading]) => ({ heading, bullets: bulletList(app[key]) })),
43
+ };
44
+ };
45
+
46
+ const renderRelease = (release) => {
47
+ const lines = [`## [${release.version}] - ${release.date}`, ''];
48
+ if (release.title) lines.push(`**${release.title}**`, '');
49
+ if (release.intro) lines.push(release.intro, '');
50
+ for (const { heading, bullets } of release.groups) {
51
+ if (bullets.length === 0) continue;
52
+ lines.push(`### ${heading}`, '', ...bullets.map((bullet) => `- ${bullet}`), '');
53
+ }
54
+ return lines.join('\n').trim();
55
+ };
56
+
57
+ /**
58
+ * Markdown for the releases in (fromVersion, toVersion], newest first, or
59
+ * null when the index holds none of them.
60
+ */
61
+ export function renderUpdateNotes(index, fromVersion, toVersion, compareVersions) {
62
+ if (!Array.isArray(index)) return null;
63
+ const relevant = index
64
+ .map(parseRelease)
65
+ .filter((release) => release !== null)
66
+ .filter((release) => compareVersions(release.version, fromVersion) > 0 && compareVersions(release.version, toVersion) <= 0)
67
+ .sort((a, b) => compareVersions(b.version, a.version));
68
+ if (relevant.length === 0) return null;
69
+ return relevant.map(renderRelease).join('\n\n');
70
+ }
71
+
72
+ /** Fetch the index and render the notes for one update; null on any failure. */
73
+ export async function fetchUpdateNotes(fromVersion, toVersion, compareVersions, options = {}) {
74
+ const fetchImpl = options.fetch ?? fetch;
75
+ try {
76
+ const response = await fetchImpl(CHANGELOG_INDEX_URL, { signal: AbortSignal.timeout(options.timeoutMs ?? 10_000) });
77
+ if (!response.ok) return null;
78
+ return renderUpdateNotes(await response.json(), fromVersion, toVersion, compareVersions);
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { fetchUpdateNotes, renderUpdateNotes } from './update-notes.js';
3
+
4
+ const compare = (a, b) => {
5
+ const pa = a.split('.').map(Number);
6
+ const pb = b.split('.').map(Number);
7
+ for (let i = 0; i < 3; i += 1) if (pa[i] !== pb[i]) return pa[i] - pb[i];
8
+ return 0;
9
+ };
10
+
11
+ const index = [
12
+ { version: '1.2.3', date: '2026-03-03', title: 'Comments everywhere', intro: 'A short intro.', app: { new: ['**Comments:** on code.'], improvements: [], fixes: ['Chat: no freeze.'], misc: [] }, vscode: null },
13
+ { version: '1.2.2', date: '2026-03-02', title: null, intro: null, app: { new: [], improvements: ['Faster.'], fixes: [], misc: [] }, vscode: null },
14
+ { version: '1.2.1', date: '2026-03-01', title: 'Old', intro: null, app: { new: ['Older.'], improvements: [], fixes: [], misc: [] }, vscode: null },
15
+ ];
16
+
17
+ describe('renderUpdateNotes', () => {
18
+ it('renders the releases after the installed version up to the offered one, newest first', () => {
19
+ expect(renderUpdateNotes(index, '1.2.1', '1.2.3', compare)).toBe(`## [1.2.3] - 2026-03-03
20
+
21
+ **Comments everywhere**
22
+
23
+ A short intro.
24
+
25
+ ### New
26
+
27
+ - **Comments:** on code.
28
+
29
+ ### Fixes
30
+
31
+ - Chat: no freeze.
32
+
33
+ ## [1.2.2] - 2026-03-02
34
+
35
+ ### Improvements
36
+
37
+ - Faster.`);
38
+ });
39
+
40
+ it('returns null when nothing lies in the range or the payload is not an index', () => {
41
+ expect(renderUpdateNotes(index, '1.2.3', '1.2.3', compare)).toBe(null);
42
+ expect(renderUpdateNotes({ entries: [] }, '1.0.0', '9.9.9', compare)).toBe(null);
43
+ expect(renderUpdateNotes([{ version: 'nope' }, { version: '1.2.2', date: '2026' }], '1.0.0', '9.9.9', compare)).toBe(null);
44
+ });
45
+ });
46
+
47
+ describe('fetchUpdateNotes', () => {
48
+ it('turns a failed request or a thrown fetch into null', async () => {
49
+ const notFound = async () => ({ ok: false });
50
+ expect(await fetchUpdateNotes('1.0.0', '2.0.0', compare, { fetch: notFound })).toBe(null);
51
+ const throwing = async () => { throw new Error('offline'); };
52
+ expect(await fetchUpdateNotes('1.0.0', '2.0.0', compare, { fetch: throwing })).toBe(null);
53
+ });
54
+
55
+ it('renders a successful response', async () => {
56
+ const ok = async () => ({ ok: true, json: async () => index });
57
+ expect(await fetchUpdateNotes('1.2.2', '1.2.3', compare, { fetch: ok })).toMatch(/^## \[1\.2\.3\] - 2026-03-03\n\n\*\*Comments everywhere\*\*/);
58
+ });
59
+ });
@@ -86,15 +86,15 @@ describe('remote client auth runtime', () => {
86
86
  it('keeps the replaced record label on a dedupe re-mint without an explicit label', async () => {
87
87
  const { dir, runtime } = await createRuntime();
88
88
  try {
89
- await runtime.createClient({ label: 'Iryna iPhone', dedupeKey: 'mobile:device-1', fallbackLabel: 'OpenChamber Mobile' });
90
- const remint = await runtime.createClient({ dedupeKey: 'mobile:device-1', fallbackLabel: 'OpenChamber Mobile' });
89
+ await runtime.createClient({ label: 'Iryna iPhone', dedupeKey: 'mobile:device-1', fallbackLabel: 'AiWorker Mobile' });
90
+ const remint = await runtime.createClient({ dedupeKey: 'mobile:device-1', fallbackLabel: 'AiWorker Mobile' });
91
91
  expect(remint.client.label).toBe('Iryna iPhone');
92
92
 
93
- const renamed = await runtime.createClient({ label: 'Work phone', dedupeKey: 'mobile:device-1', fallbackLabel: 'OpenChamber Mobile' });
93
+ const renamed = await runtime.createClient({ label: 'Work phone', dedupeKey: 'mobile:device-1', fallbackLabel: 'AiWorker Mobile' });
94
94
  expect(renamed.client.label).toBe('Work phone');
95
95
 
96
- const fresh = await runtime.createClient({ dedupeKey: 'mobile:device-2', fallbackLabel: 'OpenChamber Mobile' });
97
- expect(fresh.client.label).toBe('OpenChamber Mobile');
96
+ const fresh = await runtime.createClient({ dedupeKey: 'mobile:device-2', fallbackLabel: 'AiWorker Mobile' });
97
+ expect(fresh.client.label).toBe('AiWorker Mobile');
98
98
  } finally {
99
99
  await fs.rm(dir, { recursive: true, force: true });
100
100
  }
@@ -5,15 +5,15 @@ const isRecord = (value) => Boolean(value && typeof value === 'object' && !Array
5
5
 
6
6
  const readContextState = (session) => {
7
7
  const metadata = isRecord(session?.metadata) ? session.metadata : {};
8
- const openchamber = isRecord(metadata.openchamber) ? metadata.openchamber : {};
9
- const messages = Array.isArray(openchamber.context_obligatory_messages)
10
- ? openchamber.context_obligatory_messages.filter((item) =>
8
+ const aiworker = isRecord(metadata.aiworker) ? metadata.aiworker : {};
9
+ const messages = Array.isArray(aiworker.context_obligatory_messages)
10
+ ? aiworker.context_obligatory_messages.filter((item) =>
11
11
  isRecord(item)
12
12
  && typeof item.id === 'string'
13
13
  && typeof item.createdAt === 'number'
14
14
  && (item.role === 'user' || item.role === 'assistant'))
15
15
  : [];
16
- return { metadata, openchamber, messages };
16
+ return { metadata, aiworker, messages };
17
17
  };
18
18
 
19
19
  const buildContextPrompt = (entries) => {
@@ -89,7 +89,7 @@ export const createContextObligatoryRuntime = ({
89
89
  const summary = recent.toReversed().find((message) =>
90
90
  message?.info?.role === 'assistant' && message.info.summary === true)?.info;
91
91
  if (!summary?.id || !summary?.time?.completed) return;
92
- if (state.openchamber.context_obligatory_last_compaction_message_id === summary.id) return;
92
+ if (state.aiworker.context_obligatory_last_compaction_message_id === summary.id) return;
93
93
 
94
94
  const fetched = await Promise.allSettled(state.messages.map(async (pinned) => {
95
95
  const message = await openCodeFetch(
@@ -138,8 +138,8 @@ export const createContextObligatoryRuntime = ({
138
138
  body: {
139
139
  metadata: {
140
140
  ...freshState.metadata,
141
- openchamber: {
142
- ...freshState.openchamber,
141
+ aiworker: {
142
+ ...freshState.aiworker,
143
143
  context_obligatory_last_compaction_message_id: summary.id,
144
144
  // Recorded together with the cursor: the session now carries this
145
145
  // knowledge again, so the next send must not repeat it.
@@ -23,7 +23,7 @@ describe('context obligatory runtime', () => {
23
23
  sessionReads += 1;
24
24
  return json({
25
25
  id: 'ses_1',
26
- metadata: { openchamber: { context_obligatory_messages: [
26
+ metadata: { aiworker: { context_obligatory_messages: [
27
27
  { id: 'msg_2', createdAt: 20, role: 'assistant' },
28
28
  { id: 'msg_1', createdAt: 10, role: 'user' },
29
29
  ] } },
@@ -58,7 +58,7 @@ describe('context obligatory runtime', () => {
58
58
  expect(payload.parts[0].text).toContain('Only if no tasks or next steps remain');
59
59
  expect(payload.parts[0].text).toContain('no more than one short paragraph');
60
60
  const patch = requests.find((request) => request.method === 'PATCH');
61
- expect(JSON.parse(patch.body).metadata.openchamber.context_obligatory_last_compaction_message_id).toBe('msg_summary');
61
+ expect(JSON.parse(patch.body).metadata.aiworker.context_obligatory_last_compaction_message_id).toBe('msg_summary');
62
62
  expect(sessionReads).toBe(2);
63
63
  runtime.stop();
64
64
  });
@@ -75,7 +75,7 @@ describe('context obligatory runtime', () => {
75
75
  if (url.pathname === '/session/ses_1' && init.method === 'PATCH') return json({});
76
76
  if (url.pathname === '/session/ses_1') return json({
77
77
  id: 'ses_1',
78
- metadata: { openchamber: { knowledge_context_delivered: 'sig-before-compaction' } },
78
+ metadata: { aiworker: { knowledge_context_delivered: 'sig-before-compaction' } },
79
79
  });
80
80
  if (url.pathname === '/session/ses_1/message') return json([
81
81
  { info: { id: 'msg_agent', role: 'assistant', providerID: 'provider', modelID: 'model', agent: 'build' } },
@@ -112,7 +112,7 @@ describe('context obligatory runtime', () => {
112
112
  expect(JSON.parse(prompt.body).parts[0].text).toContain('Remember this.');
113
113
  const patch = requests.find((request) => request.method === 'PATCH');
114
114
  // Recorded with the cursor, so the next ordinary send does not repeat it.
115
- expect(JSON.parse(patch.body).metadata.openchamber.knowledge_context_delivered).toBe('sig-1');
115
+ expect(JSON.parse(patch.body).metadata.aiworker.knowledge_context_delivered).toBe('sig-1');
116
116
  });
117
117
 
118
118
  it('sends pinned messages and project knowledge as one message', async () => {
@@ -123,7 +123,7 @@ describe('context obligatory runtime', () => {
123
123
  if (url.pathname === '/session/ses_1' && init.method === 'PATCH') return json({});
124
124
  if (url.pathname === '/session/ses_1') return json({
125
125
  id: 'ses_1',
126
- metadata: { openchamber: { context_obligatory_messages: [{ id: 'msg_1', createdAt: 10, role: 'user' }] } },
126
+ metadata: { aiworker: { context_obligatory_messages: [{ id: 'msg_1', createdAt: 10, role: 'user' }] } },
127
127
  });
128
128
  if (url.pathname === '/session/ses_1/message') return json([
129
129
  { info: { id: 'msg_agent', role: 'assistant', providerID: 'provider', modelID: 'model', agent: 'build' } },
@@ -0,0 +1,40 @@
1
+ // One-time copy of the user-authored folders into a custom data directory.
2
+ //
3
+ // `AIWORKER_DATA_DIR` is documented as "the AiWorker data directory",
4
+ // but for a long time only the flat files (settings, auth, push tokens)
5
+ // followed it while `projects/`, `themes/`, and `speech-models/` stayed under
6
+ // `~/.config/openchamber`. Now every folder hangs off the data directory, so an
7
+ // instance that already ran with a custom directory finds those folders in
8
+ // the old place. This copies each one over once: only when the new location
9
+ // does not exist yet and the old one does. It copies rather than moves
10
+ // because a second instance on the same machine (a custom directory next to
11
+ // the default one) must not strip the default instance of its projects.
12
+
13
+ const USER_DIR_ENTRIES = Object.freeze(['projects', 'themes', 'speech-models']);
14
+
15
+ const exists = async (fsPromises, target) => fsPromises.access(target).then(() => true, () => false);
16
+
17
+ /**
18
+ * Copy the user folders from `legacyRoot` into `dataDir`. Returns the entries
19
+ * copied. A no-op when the two roots are the same directory. A folder whose
20
+ * copy fails is reported through `warn` and its partial copy removed; the
21
+ * server keeps starting, reading the (empty) new location.
22
+ */
23
+ export const migrateLegacyUserDirs = async ({ fsPromises, path, dataDir, legacyRoot, warn = () => {}, entries = USER_DIR_ENTRIES }) => {
24
+ if (path.resolve(dataDir) === path.resolve(legacyRoot)) return [];
25
+ const moved = [];
26
+ for (const entry of entries) {
27
+ const from = path.join(legacyRoot, entry);
28
+ const to = path.join(dataDir, entry);
29
+ if (await exists(fsPromises, to) || !(await exists(fsPromises, from))) continue;
30
+ try {
31
+ await fsPromises.mkdir(dataDir, { recursive: true });
32
+ await fsPromises.cp(from, to, { recursive: true, errorOnExist: true, force: false });
33
+ moved.push(entry);
34
+ } catch (error) {
35
+ await fsPromises.rm(to, { recursive: true, force: true }).catch(() => {});
36
+ warn(`Failed to copy ${from} to ${to}: ${error instanceof Error ? error.message : String(error)}`);
37
+ }
38
+ }
39
+ return moved;
40
+ };
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import fsPromises from 'fs/promises';
5
+
6
+ import { migrateLegacyUserDirs } from './data-dir-migration.js';
7
+
8
+ const setup = async () => {
9
+ const root = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'oc-data-dir-'));
10
+ const legacyRoot = path.join(root, 'legacy');
11
+ const dataDir = path.join(root, 'custom');
12
+ await fsPromises.mkdir(path.join(legacyRoot, 'projects'), { recursive: true });
13
+ await fsPromises.writeFile(path.join(legacyRoot, 'projects', 'p.json'), '{"a":1}');
14
+ await fsPromises.mkdir(path.join(legacyRoot, 'themes'), { recursive: true });
15
+ return { root, legacyRoot, dataDir, cleanup: () => fsPromises.rm(root, { recursive: true, force: true }) };
16
+ };
17
+
18
+ describe('migrateLegacyUserDirs', () => {
19
+ it('copies the user folders once into a custom data dir and leaves the originals', async () => {
20
+ const { legacyRoot, dataDir, cleanup } = await setup();
21
+ try {
22
+ const warnings = [];
23
+ const moved = await migrateLegacyUserDirs({ fsPromises, path, dataDir, legacyRoot, warn: (message) => warnings.push(message) });
24
+ expect(moved).toEqual(['projects', 'themes']);
25
+ expect(warnings).toEqual([]);
26
+ expect(await fsPromises.readFile(path.join(dataDir, 'projects', 'p.json'), 'utf8')).toBe('{"a":1}');
27
+ // The default instance keeps its own copy: a second instance must not strip it.
28
+ expect(await fsPromises.readFile(path.join(legacyRoot, 'projects', 'p.json'), 'utf8')).toBe('{"a":1}');
29
+ // A second start copies nothing more.
30
+ expect(await migrateLegacyUserDirs({ fsPromises, path, dataDir, legacyRoot })).toEqual([]);
31
+ } finally {
32
+ await cleanup();
33
+ }
34
+ });
35
+
36
+ it('never merges into a folder that already exists in the data dir', async () => {
37
+ const { legacyRoot, dataDir, cleanup } = await setup();
38
+ try {
39
+ await fsPromises.mkdir(path.join(dataDir, 'projects'), { recursive: true });
40
+ await fsPromises.writeFile(path.join(dataDir, 'projects', 'q.json'), '{}');
41
+ const moved = await migrateLegacyUserDirs({ fsPromises, path, dataDir, legacyRoot });
42
+ expect(moved).toEqual(['themes']);
43
+ expect(await fsPromises.readdir(path.join(dataDir, 'projects'))).toEqual(['q.json']);
44
+ expect(await fsPromises.readdir(path.join(legacyRoot, 'projects'))).toEqual(['p.json']);
45
+ } finally {
46
+ await cleanup();
47
+ }
48
+ });
49
+
50
+ it('is a no-op when the data dir is the default root', async () => {
51
+ const { legacyRoot, cleanup } = await setup();
52
+ try {
53
+ expect(await migrateLegacyUserDirs({ fsPromises, path, dataDir: legacyRoot, legacyRoot })).toEqual([]);
54
+ expect(await fsPromises.readdir(path.join(legacyRoot, 'projects'))).toEqual(['p.json']);
55
+ } finally {
56
+ await cleanup();
57
+ }
58
+ });
59
+ });
@@ -143,7 +143,7 @@ export const parseNetstatListeners = (output) => {
143
143
  /**
144
144
  * Narrows raw listeners to the ones worth offering as a preview target.
145
145
  *
146
- * `ownPorts` removes OpenChamber's own listeners — offering the user a preview
146
+ * `ownPorts` removes AiWorker's own listeners — offering the user a preview
147
147
  * of the app they are already looking at is pure noise.
148
148
  */
149
149
  export const selectDevServerCandidates = (listeners, { ownPorts = [], ownPids = [] } = {}) => {
@@ -169,7 +169,7 @@ const PROC_LOOPBACK_ADDRESSES = new Set(['0100007F', '00000000000000000000000001
169
169
  * Parses `/proc/net/tcp` and `/proc/net/tcp6`.
170
170
  *
171
171
  * The fallback for hosts without `lsof`, which is most containers — and a
172
- * deployed OpenChamber is exactly where a dev server needs discovering. Reads a
172
+ * deployed AiWorker is exactly where a dev server needs discovering. Reads a
173
173
  * kernel file rather than shelling out, so it cannot be defeated by a missing
174
174
  * binary or a stripped PATH.
175
175
  *
@@ -100,11 +100,11 @@ describe('candidate selection', () => {
100
100
  const listeners = [
101
101
  { port: 5173, pid: 10, command: 'node' },
102
102
  { port: 5432, pid: 11, command: 'postgres' },
103
- { port: 4096, pid: 12, command: 'openchamber' },
103
+ { port: 4096, pid: 12, command: 'aiworker' },
104
104
  { port: 3000, pid: 13, command: 'node' },
105
105
  ];
106
106
 
107
- test('drops OpenChamber own ports so the app never offers itself', () => {
107
+ test('drops AiWorker own ports so the app never offers itself', () => {
108
108
  const ports = selectDevServerCandidates(listeners, { ownPorts: [4096] }).map((entry) => entry.port);
109
109
  expect(ports).toEqual([5173, 3000]);
110
110
  });
@@ -50,7 +50,7 @@ const runCommand = (spawn, command, args, timeoutMs) => new Promise((resolve) =>
50
50
 
51
51
  /**
52
52
  * Reads the kernel's socket tables. Containers routinely ship without `lsof`,
53
- * and a deployed OpenChamber is precisely where discovery has to work, so this
53
+ * and a deployed AiWorker is precisely where discovery has to work, so this
54
54
  * is tried whenever the command is unavailable.
55
55
  */
56
56
  const readProcListeners = async (readFile) => {
@@ -20,7 +20,13 @@ and what made it fragile per framework.
20
20
  - `client.js` is the local end: it binds a loopback listener on the user's
21
21
  machine and pipes each accepted connection through one WebSocket. It lives in
22
22
  this package because it needs a WebSocket client the package already depends
23
- on; the desktop shell drives it over IPC.
23
+ on; the desktop shell drives it over IPC for directly reachable HTTP(S)
24
+ runtimes.
25
+ - Relay-only runtimes use `packages/electron/relay-dev-tunnel.mjs` for the local
26
+ listener. Each accepted connection gets an Electron `MessagePort`; the trusted
27
+ renderer carries its bytes through the active E2EE relay. This keeps relay
28
+ credentials and encryption in their existing renderer owner instead of
29
+ duplicating them in Electron main.
24
30
  - Port discovery is not owned here. `runtime.js` is given the reachable set by
25
31
  the same dev-server discovery the user's own list is built from.
26
32
  - The browser panel decides when to tunnel; this module never chooses a target.
@@ -40,9 +46,12 @@ and what made it fragile per framework.
40
46
  usual origin allowlist applies unchanged. That check is a CSRF defence: a
41
47
  hostile page can make a browser open a WebSocket carrying ambient cookies,
42
48
  and the origin is what exposes it.
43
- - With no `Origin` the request must carry client-token auth. A browser cannot
44
- reach this path the WebSocket API always sends an origin and never lets a
45
- page set an `Authorization` header so this case is the desktop shell.
49
+ - With no `Origin` the request must carry client-token auth or a short-lived
50
+ URL token. The bearer case is the desktop main process. The URL-token case
51
+ is the trusted renderer carrying the socket through the E2EE relay.
52
+ - Through the E2EE relay, the trusted renderer mints a short-lived URL token
53
+ and includes it in the virtual WebSocket URL. The relay host and URL-token
54
+ allowlists accept exactly `/api/dev-tunnel`, not subpaths.
46
55
  - Concurrency is capped per host, not per page, because one page load opens
47
56
  many sockets.
48
57
  - A connection that cannot be established fails the socket rather than holding
@@ -2,7 +2,7 @@
2
2
  * Local end of the dev-server tunnel.
3
3
  *
4
4
  * Binds a loopback listener on this machine and pipes every connection to a
5
- * dev server on the OpenChamber host. The point of binding a real local port —
5
+ * dev server on the AiWorker host. The point of binding a real local port —
6
6
  * rather than serving the remote page under a path on some other origin — is
7
7
  * that the page then has its own origin at the root of its own host. Absolute
8
8
  * URLs resolve, cookies scope correctly, HMR sockets connect, and nothing has
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Raw byte tunnel to a dev server running on the OpenChamber host.
2
+ * Raw byte tunnel to a dev server running on the AiWorker host.
3
3
  *
4
4
  * This is what lets a desktop client preview a dev server that lives on another
5
5
  * machine without rewriting anything. The client binds its own local port and
@@ -20,9 +20,9 @@
20
20
  *
21
21
  * - With an `Origin` header, the request came from a browser context and the
22
22
  * usual origin check applies unchanged.
23
- * - With no `Origin`, the request must carry client-token auth. A browser
24
- * cannot reach this path: the WebSocket API always sends an origin and never
25
- * lets a page set an `Authorization` header.
23
+ * - With no `Origin`, the request must carry client-token auth or a short-lived
24
+ * URL token. The URL-token case is used only by the trusted renderer through
25
+ * the E2EE relay; the UI-auth allowlist limits it to this exact path.
26
26
  */
27
27
  import net from 'node:net';
28
28
  import { WebSocketServer } from 'ws';
@@ -133,7 +133,7 @@ export function createDevTunnelRuntime({
133
133
  void (async () => {
134
134
  try {
135
135
  if (uiAuthController?.enabled) {
136
- const auth = await uiAuthController.resolveAuthContext(req, null, { allowUrlToken: false });
136
+ const auth = await uiAuthController.resolveAuthContext(req, null, { allowUrlToken: true });
137
137
  if (!auth) {
138
138
  rejectWebSocketUpgrade(socket, 401, 'UI authentication required');
139
139
  return;
@@ -1,12 +1,13 @@
1
1
  import { afterEach, describe, expect, test } from 'bun:test';
2
2
  import http from 'node:http';
3
3
  import net from 'node:net';
4
+ import WebSocket from 'ws';
4
5
 
5
6
  import { createDevTunnelClient } from './client.js';
6
7
  import { createDevTunnelRuntime, isDevTunnelPath } from './runtime.js';
7
8
 
8
9
  /**
9
- * These exercise the real socket path end to end: a dev server, an OpenChamber
10
+ * These exercise the real socket path end to end: a dev server, an AiWorker
10
11
  * host tunnelling to it, and a client binding a local port. Anything less would
11
12
  * not prove the thing that matters — that a page loads over the tunnel exactly
12
13
  * as it does locally.
@@ -228,6 +229,12 @@ describe('dev tunnel authentication', () => {
228
229
  enabled: true,
229
230
  resolveAuthContext: async () => ({ type: 'session' }),
230
231
  };
232
+ const urlTokenAuth = {
233
+ enabled: true,
234
+ resolveAuthContext: async (req, _res, options) => (
235
+ options?.allowUrlToken === true && req.url.includes('oc_url_token=good') ? { type: 'client', token: 'url:authenticated' } : null
236
+ ),
237
+ };
231
238
 
232
239
  test('accepts a bearer-authenticated client that sends no origin', async () => {
233
240
  const devPort = await startDevServer((_req, res) => res.end('ok'));
@@ -243,6 +250,19 @@ describe('dev tunnel authentication', () => {
243
250
  expect((await httpGet(localPort, '/')).body).toBe('ok');
244
251
  });
245
252
 
253
+ test('accepts a URL-token client carried by the E2EE relay', async () => {
254
+ const devPort = await startDevServer((_req, res) => res.end('relay-ok'));
255
+ const host = await startHost({ allowedPorts: [devPort], auth: urlTokenAuth });
256
+
257
+ const body = await new Promise((resolve, reject) => {
258
+ const socket = new WebSocket(`ws://127.0.0.1:${host.port}/api/dev-tunnel?port=${devPort}&oc_url_token=good`);
259
+ socket.on('open', () => socket.send('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'));
260
+ socket.on('message', (data) => resolve(Buffer.from(data).toString()));
261
+ socket.on('error', reject);
262
+ });
263
+ expect(body).toContain('relay-ok');
264
+ });
265
+
246
266
  test('rejects a client with no credentials', async () => {
247
267
  const devPort = await startDevServer((_req, res) => res.end('ok'));
248
268
  const host = await startHost({ allowedPorts: [devPort], auth: clientAuth });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Catalog of local sherpa-onnx STT models available for dictation.
3
3
  * Models are downloaded on demand from the k2-fsa GitHub releases and
4
- * extracted under the OpenChamber speech-models directory.
4
+ * extracted under the AiWorker speech-models directory.
5
5
  *
6
6
  * `type` selects the recognizer construction path in the worker:
7
7
  * - 'nemo_transducer': encoder/decoder/joiner transducer (Parakeet)
@@ -2,7 +2,7 @@
2
2
  * Dictation local-speech worker process.
3
3
  *
4
4
  * Hosts the sherpa-onnx native inference (Parakeet STT) in a separate process
5
- * so ONNX decoding never blocks the main OpenChamber server. Communicates
5
+ * so ONNX decoding never blocks the main AiWorker server. Communicates
6
6
  * with the parent over child_process IPC (advanced serialization, so Buffers
7
7
  * survive the trip as Uint8Array).
8
8
  *
@@ -24,7 +24,7 @@ import { getLocalSttModelDir, getLocalSttModelSpec } from './model-catalog.js';
24
24
  import { pcm16ToWav } from '../audio.js';
25
25
  import path from 'path';
26
26
 
27
- process.title = 'OpenChamber Dictation';
27
+ process.title = 'AiWorker Dictation';
28
28
 
29
29
  const engines = new Map();
30
30
  const ttsEngines = new Map();
@@ -56,7 +56,7 @@ export function acceptDirectoryMessageStreamWsConnection({
56
56
  return;
57
57
  }
58
58
 
59
- sendMessageStreamWsEvent(socket, { type: 'openchamber:heartbeat', timestamp: Date.now() }, { directory: 'global' });
59
+ sendMessageStreamWsEvent(socket, { type: 'aiworker:heartbeat', timestamp: Date.now() }, { directory: 'global' });
60
60
  }, heartbeatIntervalMs);
61
61
 
62
62
  socket.on('close', () => {
@@ -177,7 +177,7 @@ export function createGlobalMessageStreamWsBridge({
177
177
  return;
178
178
  }
179
179
 
180
- sendMessageStreamWsEvent(socket, { type: 'openchamber:heartbeat', timestamp: Date.now() }, { directory: 'global' });
180
+ sendMessageStreamWsEvent(socket, { type: 'aiworker:heartbeat', timestamp: Date.now() }, { directory: 'global' });
181
181
  }, heartbeatIntervalMs);
182
182
 
183
183
  socket.on('close', () => {
@@ -32,14 +32,14 @@ describe('event stream protocol helpers', () => {
32
32
 
33
33
  it('derives directory from payload properties when not wrapped', () => {
34
34
  const envelope = parseSseEventEnvelope(
35
- 'data: {"type":"openchamber:notification","properties":{"directory":"/tmp/project"}}\n'
35
+ 'data: {"type":"aiworker:notification","properties":{"directory":"/tmp/project"}}\n'
36
36
  );
37
37
 
38
38
  expect(envelope).toEqual({
39
39
  eventId: null,
40
40
  directory: '/tmp/project',
41
41
  payload: {
42
- type: 'openchamber:notification',
42
+ type: 'aiworker:notification',
43
43
  properties: { directory: '/tmp/project' },
44
44
  },
45
45
  });
@@ -167,14 +167,14 @@ describe('event stream protocol helpers', () => {
167
167
 
168
168
  const sent = sendMessageStreamWsEvent(
169
169
  socket,
170
- { type: 'openchamber:heartbeat', timestamp: 1 },
170
+ { type: 'aiworker:heartbeat', timestamp: 1 },
171
171
  { eventId: 'evt-2', directory: '/tmp/project' }
172
172
  );
173
173
 
174
174
  expect(sent).toBe(true);
175
175
  expect(JSON.parse(rawPayload)).toEqual({
176
176
  type: 'event',
177
- payload: { type: 'openchamber:heartbeat', timestamp: 1 },
177
+ payload: { type: 'aiworker:heartbeat', timestamp: 1 },
178
178
  eventId: 'evt-2',
179
179
  directory: '/tmp/project',
180
180
  });