@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
@@ -15,7 +15,6 @@ import {
15
15
  createFragmentAssembler,
16
16
  decodeJsonPayload,
17
17
  decodeTunnelFrame,
18
- encodeFragmentedMessage,
19
18
  encodeJsonPayload,
20
19
  encodeTunnelFrame,
21
20
  } from './tunnel-codec.js';
@@ -33,7 +32,9 @@ const ALLOWED_WS_PATHS = new Set([
33
32
  '/api/event/ws',
34
33
  '/api/terminal/ws',
35
34
  '/api/dictation/ws',
35
+ '/api/dev-tunnel',
36
36
  ]);
37
+ export const isAllowedRelayWebSocketPath = (pathname) => ALLOWED_WS_PATHS.has(pathname);
37
38
 
38
39
  // Hop-by-hop headers stripped from tunneled requests; `host` is set by fetch
39
40
  // to the loopback origin. content-length is dropped too because the body is
@@ -72,6 +73,11 @@ const BODY_BUFFER_MAX_BYTES = 512 * 1024;
72
73
  // completes, so a stalled tunnel converts into an ambiguous transport failure
73
74
  // (which the client already retries) instead of a hung loopback request.
74
75
  const BODY_DELIVERY_TIMEOUT_MS = 15_000;
76
+ const MAX_PENDING_WS_BYTES = 16 * 1024 * 1024;
77
+ const MAX_PENDING_WS_MESSAGES = 1024;
78
+ // Node ws supports read backpressure. Bun's ws shim does not implement pause;
79
+ // there the bounded queue fails the affected substream explicitly on overflow.
80
+ const CAN_PAUSE_WS_READS = !process.versions.bun;
75
81
 
76
82
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
77
83
 
@@ -97,13 +103,17 @@ const isWsClosePayload = (parsed) => Boolean(parsed && typeof parsed === 'object
97
103
  * sendFrame: (plaintextFrame: Uint8Array) => void | Promise<void>,
98
104
  * getBufferedAmount: () => number,
99
105
  * bodyDeliveryTimeoutMs?: number,
106
+ * responseChunkBytes?: number,
107
+ * cancelPendingFrames?: (streamId: number) => void,
100
108
  * }} deps
101
109
  */
102
- export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBufferedAmount, bodyDeliveryTimeoutMs = BODY_DELIVERY_TIMEOUT_MS }) => {
110
+ export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBufferedAmount, bodyDeliveryTimeoutMs = BODY_DELIVERY_TIMEOUT_MS, responseChunkBytes = MAX_TUNNEL_PAYLOAD_BYTES, cancelPendingFrames = () => {} }) => {
103
111
  /** @type {Map<number, { kind: 'http', abort: AbortController, body: { enqueue(payload: Uint8Array): void, close(): void, error(error: Error): void } | null, noBody: boolean } | { kind: 'ws', socket: WebSocket, opened: boolean }>} */
104
112
  const streams = new Map();
105
113
  const assembler = createFragmentAssembler();
106
114
  let closed = false;
115
+ let pendingWsBytes = 0;
116
+ let pendingWsMessages = 0;
107
117
 
108
118
  const send = async (frame) => {
109
119
  if (closed) return;
@@ -126,6 +136,7 @@ export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBuf
126
136
  const stream = streams.get(streamId);
127
137
  if (!stream) return;
128
138
  dropStream(streamId);
139
+ cancelPendingFrames(streamId);
129
140
  if (stream.kind === 'http') {
130
141
  try {
131
142
  stream.body?.error(new Error(String(reason ?? 'aborted')));
@@ -203,17 +214,27 @@ export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBuf
203
214
  if (STRIPPED_RESPONSE_HEADERS.has(name)) continue;
204
215
  responseHeaders[name] = value;
205
216
  }
206
- await sendJson(TunnelFrameType.HttpResponse, streamId, { status: response.status, headers: responseHeaders });
217
+ if (closed || stream.abort.signal.aborted) {
218
+ await response.body?.cancel();
219
+ return;
220
+ }
207
221
 
208
222
  try {
223
+ await sendJson(TunnelFrameType.HttpResponse, streamId, { status: response.status, headers: responseHeaders });
209
224
  if (response.body) {
210
225
  for await (const chunk of response.body) {
211
226
  if (closed || stream.abort.signal.aborted) return;
212
227
  const bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
213
- for (const piece of chunkPayload(bytes, MAX_TUNNEL_PAYLOAD_BYTES)) {
228
+ const pieces = chunkPayload(bytes, responseChunkBytes);
229
+ // Offer at most one plaintext-frame budget at a time. The scheduler
230
+ // can batch small slices and interleave streams without buffering the
231
+ // entire source or encrypting hundreds of tiny messages separately.
232
+ const groupSize = Math.max(1, Math.floor(MAX_TUNNEL_PAYLOAD_BYTES / responseChunkBytes));
233
+ for (let offset = 0; offset < pieces.length; offset += groupSize) {
214
234
  await waitForBackpressure(stream.abort.signal);
215
235
  if (closed || stream.abort.signal.aborted) return;
216
- await send(encodeTunnelFrame(TunnelFrameType.HttpBody, streamId, piece));
236
+ await Promise.all(pieces.slice(offset, offset + groupSize).map(piece =>
237
+ send(encodeTunnelFrame(TunnelFrameType.HttpBody, streamId, piece))));
217
238
  }
218
239
  }
219
240
  }
@@ -425,7 +446,7 @@ export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBuf
425
446
  void sendAbort(streamId, error?.message ?? 'malformed ws open');
426
447
  return;
427
448
  }
428
- if (!ALLOWED_WS_PATHS.has(open.path)) {
449
+ if (!isAllowedRelayWebSocketPath(open.path)) {
429
450
  void sendAbort(streamId, 'Path is not allowed through the relay');
430
451
  return;
431
452
  }
@@ -446,6 +467,7 @@ export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBuf
446
467
  try {
447
468
  socket = new WebSocket(url, open.protocols, {
448
469
  headers: dialHeaders,
470
+ maxPayload: MAX_PENDING_WS_BYTES,
449
471
  });
450
472
  } catch (error) {
451
473
  void sendAbort(streamId, error?.message ?? 'ws dial failed');
@@ -453,6 +475,8 @@ export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBuf
453
475
  }
454
476
  const stream = { kind: 'ws', socket, opened: false };
455
477
  streams.set(streamId, stream);
478
+ let outputChain = Promise.resolve();
479
+ let socketPendingMessages = 0;
456
480
 
457
481
  socket.on('open', () => {
458
482
  if (streams.get(streamId) !== stream) return;
@@ -463,23 +487,47 @@ export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBuf
463
487
  if (streams.get(streamId) !== stream || closed) return;
464
488
  const bytes = Buffer.isBuffer(data) ? new Uint8Array(data) : new Uint8Array(Buffer.concat(data));
465
489
  const frameType = isBinary ? TunnelFrameType.WsBinary : TunnelFrameType.WsText;
466
- void (async () => {
467
- for (const frame of encodeFragmentedMessage(frameType, streamId, bytes)) {
490
+ if (pendingWsBytes + bytes.length > MAX_PENDING_WS_BYTES || pendingWsMessages >= MAX_PENDING_WS_MESSAGES) {
491
+ abortLocalStream(streamId, 'upstream WebSocket exceeded downstream queue limit');
492
+ void sendAbort(streamId, 'upstream WebSocket exceeded downstream queue limit');
493
+ return;
494
+ }
495
+ pendingWsBytes += bytes.length;
496
+ pendingWsMessages += 1;
497
+ socketPendingMessages += 1;
498
+ if (CAN_PAUSE_WS_READS) socket.pause();
499
+ outputChain = outputChain.then(async () => {
500
+ // Serialize entire messages, including their fragments. A later close
501
+ // must also wait here or it can overtake the final output.
502
+ const chunks = chunkPayload(bytes, responseChunkBytes);
503
+ const groupSize = Math.max(1, Math.floor(MAX_TUNNEL_PAYLOAD_BYTES / responseChunkBytes));
504
+ for (let offset = 0; offset < chunks.length; offset += groupSize) {
468
505
  await waitForBackpressure(null);
469
506
  if (streams.get(streamId) !== stream || closed) return;
470
- await send(frame);
507
+ await Promise.all(chunks.slice(offset, offset + groupSize).map((chunk, index) =>
508
+ send(encodeTunnelFrame(frameType, streamId, chunk, offset + index < chunks.length - 1))));
471
509
  }
472
- })();
510
+ }).catch(() => {
511
+ abortLocalStream(streamId, 'upstream WebSocket forwarding failed');
512
+ void sendAbort(streamId, 'upstream WebSocket forwarding failed');
513
+ }).finally(() => {
514
+ pendingWsBytes -= bytes.length;
515
+ pendingWsMessages -= 1;
516
+ socketPendingMessages -= 1;
517
+ if (CAN_PAUSE_WS_READS && socketPendingMessages === 0 && streams.get(streamId) === stream && socket.readyState === WebSocket.OPEN) socket.resume();
518
+ });
473
519
  });
474
520
  socket.on('close', (code, reasonBuffer) => {
475
- if (streams.get(streamId) !== stream) return;
476
- dropStream(streamId);
477
- const reason = reasonBuffer ? reasonBuffer.toString('utf8') : '';
478
- if (stream.opened) {
479
- void sendJson(TunnelFrameType.WsClose, streamId, { code: code || 1000, reason });
480
- } else {
481
- void sendAbort(streamId, reason || `upstream ws closed (${code || 'no code'})`);
482
- }
521
+ void outputChain.then(async () => {
522
+ if (streams.get(streamId) !== stream) return;
523
+ dropStream(streamId);
524
+ const reason = reasonBuffer ? reasonBuffer.toString('utf8') : '';
525
+ if (stream.opened) {
526
+ await sendJson(TunnelFrameType.WsClose, streamId, { code: code || 1000, reason });
527
+ } else {
528
+ await sendAbort(streamId, reason || `upstream ws closed (${code || 'no code'})`);
529
+ }
530
+ });
483
531
  });
484
532
  socket.on('error', (error) => {
485
533
  if (streams.get(streamId) !== stream) return;
@@ -510,6 +558,7 @@ export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBuf
510
558
  const stream = streams.get(streamId);
511
559
  if (!stream || stream.kind !== 'ws') return;
512
560
  dropStream(streamId);
561
+ cancelPendingFrames(streamId);
513
562
  let close = { code: 1000, reason: '' };
514
563
  try {
515
564
  close = decodeJsonPayload(payload, isWsClosePayload);
@@ -518,6 +567,8 @@ export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBuf
518
567
  }
519
568
  const code = Number.isInteger(close.code) && close.code >= 1000 && close.code <= 4999 ? close.code : 1000;
520
569
  try {
570
+ // A paused receiver still needs to read the peer's close handshake.
571
+ if (CAN_PAUSE_WS_READS) stream.socket.resume();
521
572
  stream.socket.close(code, typeof close.reason === 'string' ? close.reason : '');
522
573
  } catch {
523
574
  stream.socket.terminate();
@@ -1,7 +1,7 @@
1
1
  import { describe, test, expect } from 'bun:test';
2
2
  import http from 'node:http';
3
3
 
4
- import { createTunnelHost } from './tunnel-host.js';
4
+ import { createTunnelHost, isAllowedRelayWebSocketPath } from './tunnel-host.js';
5
5
  import { decodeTunnelFrame, encodeTunnelFrame, encodeJsonPayload, TunnelFrameType } from './tunnel-codec.js';
6
6
 
7
7
  const startLoopback = () =>
@@ -145,3 +145,11 @@ describe('tunnel-host HTTP body forwarding', () => {
145
145
  await loopback.stop();
146
146
  });
147
147
  });
148
+
149
+ describe('relay host WebSocket allowlist', () => {
150
+ test('allows only the exact dev-server tunnel path', () => {
151
+ expect(isAllowedRelayWebSocketPath('/api/dev-tunnel')).toBe(true);
152
+ expect(isAllowedRelayWebSocketPath('/api/dev-tunnel/')).toBe(false);
153
+ expect(isAllowedRelayWebSocketPath('/api/database/ws')).toBe(false);
154
+ });
155
+ });
@@ -1,10 +1,10 @@
1
1
  import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
2
2
 
3
3
  /**
4
- * Regression for https://github.com/openchamber/openchamber/issues/2710
4
+ * Regression for https://roweb.cn/roweb/openchamber/issues/2710
5
5
  * "Scheduled daily task executes twice at the configured time"
6
6
  *
7
- * Root cause: each OpenChamber server process keeps its own timers. Two
7
+ * Root cause: each AiWorker server process keeps its own timers. Two
8
8
  * instances that share the same on-disk project config (CLI serve on port 3000
9
9
  * + Electron on 57123, or a startup login service + desktop) each arm a timer
10
10
  * for the same occurrence and both dispatch.
@@ -15,7 +15,7 @@ export const registerScheduledTaskRoutes = (app, dependencies) => {
15
15
  sanitizeProjects,
16
16
  projectConfigRuntime,
17
17
  scheduledTasksRuntime,
18
- getOpenChamberEventClients,
18
+ getAiWorkerEventClients,
19
19
  writeSseEvent,
20
20
  scheduledTaskService = createScheduledTaskService(dependencies),
21
21
  } = dependencies;
@@ -149,14 +149,14 @@ export const registerScheduledTaskRoutes = (app, dependencies) => {
149
149
  // not of this server: a desktop shell and a browser tab can be connected to
150
150
  // the same server at once. Recording it on the connection keeps the answer
151
151
  // current without any enable/disable setting to go stale.
152
- res.openchamberBrowserCapable = req.query?.browser === '1';
152
+ res.aiworkerBrowserCapable = req.query?.browser === '1';
153
153
 
154
- const clients = getOpenChamberEventClients();
154
+ const clients = getAiWorkerEventClients();
155
155
  clients.add(res);
156
156
 
157
157
  try {
158
158
  writeSseEvent(res, {
159
- type: 'openchamber:event-stream-ready',
159
+ type: 'aiworker:event-stream-ready',
160
160
  properties: {
161
161
  connectedAt: Date.now(),
162
162
  },
@@ -167,7 +167,7 @@ export const registerScheduledTaskRoutes = (app, dependencies) => {
167
167
  const heartbeat = setInterval(() => {
168
168
  try {
169
169
  writeSseEvent(res, {
170
- type: 'openchamber:heartbeat',
170
+ type: 'aiworker:heartbeat',
171
171
  properties: {
172
172
  timestamp: Date.now(),
173
173
  },
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { OpenChamberControlError } from '../openchamber-control/error.js';
3
+ import { AiWorkerControlError } from '../openchamber-control/error.js';
4
4
  import { setLoopFileEnabled } from './loops.js';
5
5
 
6
6
  const asNonEmptyString = (value) => {
@@ -24,10 +24,10 @@ export const createScheduledTaskService = (dependencies) => {
24
24
 
25
25
  const findProjectByID = async (projectID) => {
26
26
  const normalized = asNonEmptyString(projectID);
27
- if (!normalized) throw new OpenChamberControlError('projectId is required', 400);
27
+ if (!normalized) throw new AiWorkerControlError('projectId is required', 400);
28
28
  const projects = await listProjects();
29
29
  const project = projects.find((entry) => entry.id === normalized) || null;
30
- if (!project) throw new OpenChamberControlError('Project not found', 404);
30
+ if (!project) throw new AiWorkerControlError('Project not found', 404);
31
31
  return project;
32
32
  };
33
33
 
@@ -35,17 +35,17 @@ export const createScheduledTaskService = (dependencies) => {
35
35
  const requestedProjectID = asNonEmptyString(projectId);
36
36
  const requestedDirectory = asNonEmptyString(directory);
37
37
  if (requestedProjectID && requestedDirectory) {
38
- throw new OpenChamberControlError('Provide only one of projectId or directory', 400);
38
+ throw new AiWorkerControlError('Provide only one of projectId or directory', 400);
39
39
  }
40
40
  if (requestedProjectID) {
41
41
  await findProjectByID(requestedProjectID);
42
42
  return requestedProjectID;
43
43
  }
44
- if (!requestedDirectory) throw new OpenChamberControlError('projectId or directory is required', 400);
44
+ if (!requestedDirectory) throw new AiWorkerControlError('projectId or directory is required', 400);
45
45
  const resolvedDirectory = path.resolve(requestedDirectory);
46
46
  const projects = await listProjects();
47
47
  const project = projects.find((entry) => path.resolve(entry.path) === resolvedDirectory);
48
- if (!project) throw new OpenChamberControlError(`Project not found for directory: ${resolvedDirectory}`, 404);
48
+ if (!project) throw new AiWorkerControlError(`Project not found for directory: ${resolvedDirectory}`, 404);
49
49
  return project.id;
50
50
  };
51
51
 
@@ -57,28 +57,28 @@ export const createScheduledTaskService = (dependencies) => {
57
57
  const findLoopTask = async (projectID, taskID) => {
58
58
  await findProjectByID(projectID);
59
59
  const normalizedTaskID = asNonEmptyString(taskID);
60
- if (!normalizedTaskID) throw new OpenChamberControlError('taskId is required', 400);
60
+ if (!normalizedTaskID) throw new AiWorkerControlError('taskId is required', 400);
61
61
  const tasks = await scheduledTasksRuntime.syncProject(projectID);
62
62
  const task = tasks.find((entry) => entry?.id === normalizedTaskID) || null;
63
- if (!task) throw new OpenChamberControlError('Task not found', 404);
64
- if (!task.loopFile) throw new OpenChamberControlError('Task is not managed by a loop file', 400);
65
- if (!fs.existsSync(task.loopFile)) throw new OpenChamberControlError('Loop file not found', 404);
63
+ if (!task) throw new AiWorkerControlError('Task not found', 404);
64
+ if (!task.loopFile) throw new AiWorkerControlError('Task is not managed by a loop file', 400);
65
+ if (!fs.existsSync(task.loopFile)) throw new AiWorkerControlError('Loop file not found', 404);
66
66
  return task;
67
67
  };
68
68
 
69
69
  const setLoopEnabled = async (projectID, taskID, enabled) => {
70
70
  if (typeof enabled !== 'boolean') {
71
- throw new OpenChamberControlError('enabled must be a boolean', 400);
71
+ throw new AiWorkerControlError('enabled must be a boolean', 400);
72
72
  }
73
73
  const task = await findLoopTask(projectID, taskID);
74
74
  try {
75
75
  if (!setLoopFileEnabled(task.loopFile, enabled)) {
76
- throw new OpenChamberControlError('Loop file must be valid before changing its enabled state', 400);
76
+ throw new AiWorkerControlError('Loop file must be valid before changing its enabled state', 400);
77
77
  }
78
78
  } catch (error) {
79
- if (error instanceof OpenChamberControlError) throw error;
79
+ if (error instanceof AiWorkerControlError) throw error;
80
80
  const message = error instanceof Error ? error.message : 'Failed to update loop file';
81
- throw new OpenChamberControlError(message, 500);
81
+ throw new AiWorkerControlError(message, 500);
82
82
  }
83
83
  const tasks = await scheduledTasksRuntime.syncProject(projectID);
84
84
  return tasks.find((entry) => entry.id === taskID) || null;
@@ -90,7 +90,7 @@ export const createScheduledTaskService = (dependencies) => {
90
90
  fs.unlinkSync(task.loopFile);
91
91
  } catch (error) {
92
92
  const message = error instanceof Error ? error.message : 'Failed to delete loop file';
93
- throw new OpenChamberControlError(message, 500);
93
+ throw new AiWorkerControlError(message, 500);
94
94
  }
95
95
  return scheduledTasksRuntime.syncProject(projectID);
96
96
  };
@@ -98,7 +98,7 @@ export const createScheduledTaskService = (dependencies) => {
98
98
  const upsert = async (projectID, taskInput) => {
99
99
  await findProjectByID(projectID);
100
100
  if (!taskInput || typeof taskInput !== 'object') {
101
- throw new OpenChamberControlError('task payload is required', 400);
101
+ throw new AiWorkerControlError('task payload is required', 400);
102
102
  }
103
103
  let upserted;
104
104
  try {
@@ -106,7 +106,7 @@ export const createScheduledTaskService = (dependencies) => {
106
106
  } catch (error) {
107
107
  const message = error instanceof Error ? error.message : 'Failed to save scheduled task';
108
108
  const invalid = message.toLowerCase().includes('required') || message.toLowerCase().includes('invalid');
109
- throw new OpenChamberControlError(message, invalid ? 400 : 500);
109
+ throw new AiWorkerControlError(message, invalid ? 400 : 500);
110
110
  }
111
111
  await scheduledTasksRuntime.syncProject(projectID);
112
112
  const tasks = await projectConfigRuntime.listScheduledTasks(projectID);
@@ -120,7 +120,7 @@ export const createScheduledTaskService = (dependencies) => {
120
120
  const remove = async (projectID, taskID) => {
121
121
  await findProjectByID(projectID);
122
122
  const normalizedTaskID = asNonEmptyString(taskID);
123
- if (!normalizedTaskID) throw new OpenChamberControlError('taskId is required', 400);
123
+ if (!normalizedTaskID) throw new AiWorkerControlError('taskId is required', 400);
124
124
  const current = await projectConfigRuntime.listScheduledTasks(projectID);
125
125
  const existing = current.find((task) => task.id === normalizedTaskID) || null;
126
126
  if (existing?.loopFile && fs.existsSync(existing.loopFile)) {
@@ -129,13 +129,13 @@ export const createScheduledTaskService = (dependencies) => {
129
129
  // file exists. The file itself is the removal surface. Once the file is
130
130
  // gone (the task is an orphan that the next sync would remove anyway),
131
131
  // deleting the row is safe and allowed.
132
- throw new OpenChamberControlError(
132
+ throw new AiWorkerControlError(
133
133
  'Loop task is managed by its .agents/loops markdown file; delete the file to remove the task',
134
134
  400,
135
135
  );
136
136
  }
137
137
  const result = await projectConfigRuntime.deleteScheduledTask(projectID, normalizedTaskID);
138
- if (!result.deleted) throw new OpenChamberControlError('Task not found', 404);
138
+ if (!result.deleted) throw new AiWorkerControlError('Task not found', 404);
139
139
  await scheduledTasksRuntime.syncProject(projectID);
140
140
  return projectConfigRuntime.listScheduledTasks(projectID);
141
141
  };
@@ -143,14 +143,14 @@ export const createScheduledTaskService = (dependencies) => {
143
143
  const run = async (projectID, taskID) => {
144
144
  await findProjectByID(projectID);
145
145
  const normalizedTaskID = asNonEmptyString(taskID);
146
- if (!normalizedTaskID) throw new OpenChamberControlError('taskId is required', 400);
146
+ if (!normalizedTaskID) throw new AiWorkerControlError('taskId is required', 400);
147
147
  const result = await scheduledTasksRuntime.runNow(projectID, normalizedTaskID);
148
148
  if (result.running || result.queued) {
149
- throw new OpenChamberControlError(result.error || 'Task already running', 409);
149
+ throw new AiWorkerControlError(result.error || 'Task already running', 409);
150
150
  }
151
- if (result.skipped) throw new OpenChamberControlError('Task not found or disabled', 404);
151
+ if (result.skipped) throw new AiWorkerControlError('Task not found or disabled', 404);
152
152
  if (!result.ok) {
153
- throw new OpenChamberControlError(result.error || 'Task run failed', 500, { task: result.task });
153
+ throw new AiWorkerControlError(result.error || 'Task run failed', 500, { task: result.task });
154
154
  }
155
155
  return {
156
156
  task: result.task,
@@ -164,7 +164,7 @@ export const createScheduledTaskService = (dependencies) => {
164
164
  const setEnabled = async (projectID, taskID, enabled) => {
165
165
  const tasks = await list(projectID);
166
166
  const task = tasks.find((entry) => entry?.id === taskID);
167
- if (!task) throw new OpenChamberControlError('Task not found', 404);
167
+ if (!task) throw new AiWorkerControlError('Task not found', 404);
168
168
  const result = await upsert(projectID, { ...task, enabled });
169
169
  return result.task;
170
170
  };
@@ -166,7 +166,7 @@ describe('scheduled-task loop-file routes', () => {
166
166
  sanitizeProjects: vi.fn(),
167
167
  projectConfigRuntime: {},
168
168
  scheduledTasksRuntime: {},
169
- getOpenChamberEventClients: () => new Set(),
169
+ getAiWorkerEventClients: () => new Set(),
170
170
  writeSseEvent: vi.fn(),
171
171
  });
172
172
  return handlers;
@@ -32,9 +32,9 @@ export const isLoopbackBindHost = (host) => {
32
32
  export const isNetworkExposedBindHost = (host) => !isLoopbackBindHost(host);
33
33
 
34
34
  export const isUnsafeUnauthenticatedLanAllowed = (env = process.env) =>
35
- env?.OPENCHAMBER_ALLOW_UNAUTHENTICATED_LAN === 'true';
35
+ env?.AIWORKER_ALLOW_UNAUTHENTICATED_LAN === 'true';
36
36
 
37
37
  export const getUnauthenticatedLanErrorMessage = (host) =>
38
- `OpenChamber refuses to bind to ${host || 'a network-exposed host'} without UI authentication. `
39
- + 'Set --ui-password or OPENCHAMBER_UI_PASSWORD before exposing it over LAN, '
40
- + 'or set OPENCHAMBER_ALLOW_UNAUTHENTICATED_LAN=true to accept the risk.';
38
+ `AiWorker refuses to bind to ${host || 'a network-exposed host'} without UI authentication. `
39
+ + 'Set --ui-password or AIWORKER_UI_PASSWORD before exposing it over LAN, '
40
+ + 'or set AIWORKER_ALLOW_UNAUTHENTICATED_LAN=true to accept the risk.';
@@ -25,7 +25,7 @@ describe('bind host exposure classification', () => {
25
25
  '::ffff:192.168.1.10',
26
26
  'fe80::1',
27
27
  'fc00::1',
28
- 'openchamber.local',
28
+ 'aiworker.local',
29
29
  'example.com',
30
30
  '',
31
31
  ]) {
@@ -103,7 +103,7 @@ export const createRequestSecurityRuntime = (deps) => {
103
103
  return { origins, hosts };
104
104
  };
105
105
 
106
- const isRequestOriginAllowed = async (req) => {
106
+ const isRequestOriginAllowed = (req) => {
107
107
  const originHeader = typeof req.headers.origin === 'string' ? req.headers.origin.trim() : '';
108
108
  if (!originHeader) {
109
109
  return false;
@@ -120,15 +120,21 @@ export const createRequestSecurityRuntime = (deps) => {
120
120
  return false;
121
121
  }
122
122
 
123
- const candidates = await getRequestOriginCandidates(req);
124
- if (candidates.origins.has(origin.origin)) return true;
123
+ const forwardedHostHeader = req.headers['x-forwarded-host'];
124
+ const forwardedHost = (Array.isArray(forwardedHostHeader) ? forwardedHostHeader[0] : forwardedHostHeader || '')
125
+ .split(',')[0].trim().toLowerCase();
126
+ const hostHeader = req.headers.host;
127
+ const host = forwardedHost || (Array.isArray(hostHeader) ? hostHeader[0] : hostHeader || '').trim().toLowerCase();
128
+ if (host && host === origin.host.toLowerCase()) return true;
125
129
 
126
- // TLS commonly ends at a cloud edge before an HTTP hop to OpenChamber.
130
+ // TLS commonly ends at a cloud edge before an HTTP hop to AiWorker.
127
131
  // In that setup the browser's Origin is https while a generic reverse
128
132
  // proxy reports the upstream request as http. The external host remains
129
133
  // authoritative, so compare it directly instead of requiring the proxy to
130
134
  // preserve the browser-facing protocol.
131
- return candidates.hosts.has(origin.host.toLowerCase());
135
+ return getRequestOriginCandidates(req).then((candidates) => (
136
+ candidates.origins.has(origin.origin) || candidates.hosts.has(origin.host.toLowerCase())
137
+ ));
132
138
  };
133
139
 
134
140
  return {
@@ -9,55 +9,55 @@ describe('request security runtime', () => {
9
9
  test('allows packaged client origins for remote client transports', async () => {
10
10
  const runtime = createRuntime();
11
11
 
12
- await expect(runtime.isRequestOriginAllowed({
12
+ expect(await runtime.isRequestOriginAllowed({
13
13
  headers: {
14
14
  origin: 'openchamber-ui://app',
15
15
  host: '192.168.1.130:1202',
16
16
  },
17
17
  socket: {},
18
- })).resolves.toBe(true);
18
+ })).toBe(true);
19
19
 
20
- await expect(runtime.isRequestOriginAllowed({
20
+ expect(await runtime.isRequestOriginAllowed({
21
21
  headers: {
22
22
  origin: 'capacitor://localhost',
23
23
  host: '192.168.1.130:1202',
24
24
  },
25
25
  socket: {},
26
- })).resolves.toBe(true);
26
+ })).toBe(true);
27
27
 
28
28
  // Android Capacitor WebView (androidScheme 'https') reports this origin.
29
- await expect(runtime.isRequestOriginAllowed({
29
+ expect(await runtime.isRequestOriginAllowed({
30
30
  headers: {
31
31
  origin: 'https://localhost',
32
32
  host: '192.168.1.130:1202',
33
33
  },
34
34
  socket: {},
35
- })).resolves.toBe(true);
35
+ })).toBe(true);
36
36
  });
37
37
 
38
38
  test('rejects unknown origins', async () => {
39
39
  const runtime = createRuntime();
40
40
 
41
- await expect(runtime.isRequestOriginAllowed({
41
+ expect(await runtime.isRequestOriginAllowed({
42
42
  headers: {
43
43
  origin: 'https://evil.example.com',
44
44
  host: '192.168.1.130:1202',
45
45
  },
46
46
  socket: {},
47
- })).resolves.toBe(false);
47
+ })).toBe(false);
48
48
  });
49
49
 
50
50
  test('allows the external host when TLS terminates before an HTTP proxy hop', async () => {
51
51
  const runtime = createRuntime();
52
52
 
53
- await expect(runtime.isRequestOriginAllowed({
53
+ expect(await runtime.isRequestOriginAllowed({
54
54
  headers: {
55
55
  origin: 'https://devchamber.example.com',
56
56
  host: 'devchamber.example.com',
57
57
  'x-forwarded-proto': 'http',
58
58
  },
59
59
  socket: {},
60
- })).resolves.toBe(true);
60
+ })).toBe(true);
61
61
  });
62
62
 
63
63
  test('uses the forwarded external host without trusting a different origin', async () => {
@@ -71,13 +71,13 @@ describe('request security runtime', () => {
71
71
  socket: {},
72
72
  };
73
73
 
74
- await expect(runtime.isRequestOriginAllowed({
74
+ expect(await runtime.isRequestOriginAllowed({
75
75
  ...request,
76
76
  headers: { ...request.headers, origin: 'https://devchamber.example.com' },
77
- })).resolves.toBe(true);
78
- await expect(runtime.isRequestOriginAllowed({
77
+ })).toBe(true);
78
+ expect(await runtime.isRequestOriginAllowed({
79
79
  ...request,
80
80
  headers: { ...request.headers, origin: 'https://evil.example.com' },
81
- })).resolves.toBe(false);
81
+ })).toBe(false);
82
82
  });
83
83
  });
@@ -1,7 +1,7 @@
1
1
  // Session assist: after a session goes idle and stays quiet, generate a short
2
2
  // recap of the agent's last reply plus one suggested user follow-up with the
3
3
  // small model, and store both on the session's metadata
4
- // (metadata.openchamber.assist). Clients decide visibility from
4
+ // (metadata.aiworker.assist). Clients decide visibility from
5
5
  // assist.forMessageID — a new message makes the payload stale everywhere
6
6
  // without any extra writes.
7
7
  //
@@ -11,11 +11,12 @@
11
11
  import fs from 'fs';
12
12
  import os from 'os';
13
13
  import path from 'path';
14
+ import { readMergedSettingsSync } from '../opencode/settings-files.js';
14
15
 
15
- const OPENCHAMBER_SETTINGS_FILE = path.join(
16
- process.env.OPENCHAMBER_DATA_DIR
17
- ? path.resolve(process.env.OPENCHAMBER_DATA_DIR)
18
- : path.join(os.homedir(), '.config', 'openchamber'),
16
+ const AIWORKER_SETTINGS_FILE = path.join(
17
+ process.env.AIWORKER_DATA_DIR
18
+ ? path.resolve(process.env.AIWORKER_DATA_DIR)
19
+ : path.join(os.homedir(), '.config', 'aiworker'),
19
20
  'settings.json',
20
21
  );
21
22
 
@@ -23,16 +24,11 @@ const OPENCHAMBER_SETTINGS_FILE = path.join(
23
24
  // off, no small-model calls and no metadata writes happen at all. Existing
24
25
  // payloads stay untouched — clients keep showing them and dismissal still works.
25
26
  const getSessionAssistTargets = () => {
26
- try {
27
- const raw = fs.readFileSync(OPENCHAMBER_SETTINGS_FILE, 'utf8');
28
- const settings = JSON.parse(raw);
29
- return {
30
- recap: settings?.sessionRecapEnabled !== false,
31
- suggestion: settings?.sessionSuggestionEnabled !== false,
32
- };
33
- } catch {
34
- return { recap: true, suggestion: true };
35
- }
27
+ const settings = readMergedSettingsSync({ fs, path, settingsFilePath: AIWORKER_SETTINGS_FILE });
28
+ return {
29
+ recap: settings.sessionRecapEnabled !== false,
30
+ suggestion: settings.sessionSuggestionEnabled !== false,
31
+ };
36
32
  };
37
33
 
38
34
  const IDLE_QUIET_MS = 60_000;
@@ -257,6 +253,7 @@ export const createSessionAssistRuntime = ({
257
253
  prompt: `The latest exchange in the conversation:\n\n${transcript}\n\nWrite ${requestedFields} in the SAME language as this sample from the conversation: "${languageSample}"`,
258
254
  system: buildAssistSystemPrompt(targets),
259
255
  directory,
256
+ sessionID: sessionId,
260
257
  preferredProviderID: typeof lastAssistantInfo.providerID === 'string' ? lastAssistantInfo.providerID : undefined,
261
258
  preferredModelID: typeof lastAssistantInfo.modelID === 'string' ? lastAssistantInfo.modelID : undefined,
262
259
  });
@@ -316,8 +313,8 @@ export const createSessionAssistRuntime = ({
316
313
  const currentMetadata = freshSession?.metadata && typeof freshSession.metadata === 'object'
317
314
  ? freshSession.metadata
318
315
  : (session.metadata && typeof session.metadata === 'object' ? session.metadata : {});
319
- const currentNamespace = currentMetadata.openchamber && typeof currentMetadata.openchamber === 'object'
320
- ? currentMetadata.openchamber
316
+ const currentNamespace = currentMetadata.aiworker && typeof currentMetadata.aiworker === 'object'
317
+ ? currentMetadata.aiworker
321
318
  : {};
322
319
 
323
320
  console.log(`[session-assist] generated for ${sessionId} via ${generated.providerID}/${generated.modelID}`);
@@ -327,7 +324,7 @@ export const createSessionAssistRuntime = ({
327
324
  body: {
328
325
  metadata: {
329
326
  ...currentMetadata,
330
- openchamber: {
327
+ aiworker: {
331
328
  ...currentNamespace,
332
329
  assist: {
333
330
  recap,