@aiworker/web 1.22.0 → 1.22.2

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 (354) hide show
  1. package/README.md +4 -4
  2. package/bin/cli.js +7 -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-CSICpWQD.js} +1 -1
  28. package/dist/assets/{DiffView-DluiRSla.js → DiffView-hDBKesA_.js} +3 -3
  29. package/dist/assets/DirtyBranchSwitchDialog-C22HdWj4.js +1 -0
  30. package/dist/assets/FilesView-CdWzeOp9.js +17 -0
  31. package/dist/assets/GitView-VQQkYuUw.js +6 -0
  32. package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-6lrhwD3q.js} +1 -1
  33. package/dist/assets/LinearIssuesView-Ba6Q9569.js +1 -0
  34. package/dist/assets/{MarkdownImageGallery-ClOvuZxC.js → MarkdownImageGallery-Czv_mhvS.js} +1 -1
  35. package/dist/assets/MarkdownRendererImpl-BrMcswiy.js +12 -0
  36. package/dist/assets/{OnboardingScreen-D2DG7K6b.js → OnboardingScreen-ChxQoAkx.js} +1 -1
  37. package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-BzcZPlOq.js} +1 -1
  38. package/dist/assets/{PierreDiffViewer-B7m3f0d7.js → PierreDiffViewer-BchBtZlp.js} +2 -2
  39. package/dist/assets/PlanView-DyDt_nZ_.js +5 -0
  40. package/dist/assets/SettingsView-BKf5xBsP.js +28 -0
  41. package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-DDQYaKWQ.js} +1 -1
  42. package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-iYF8Y2bz.js} +6 -6
  43. package/dist/assets/ToolPartDiffPreview-CdjbOVMM.js +8 -0
  44. package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-fFFm2QgH.js} +1 -1
  45. package/dist/assets/{WalkthroughView-egQgxel3.js → WalkthroughView-DOeCsMju.js} +2 -2
  46. package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-CzgLum_K.js} +2 -2
  47. package/dist/assets/{contextPanelEmbeddedChat-C3fjDbdg.js → contextPanelEmbeddedChat-C0ZLAepL.js} +1 -1
  48. package/dist/assets/{de-C1P6X5Rs.js → de-B5JaA3SA.js} +6 -5
  49. package/dist/assets/{es-CDgaF1_t.js → es-EPIH88ii.js} +6 -5
  50. package/dist/assets/{fr-Bgxqvvz_.js → fr-CAMfxUdZ.js} +6 -5
  51. package/dist/assets/{index-CODwDsU_.js → index-Bl3LSbjn.js} +1 -1
  52. package/dist/assets/index-DF5kMP-i.js +39 -0
  53. package/dist/assets/index-Dl3xbAB_.css +1 -0
  54. package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-Dx-dB_8l.js} +1 -1
  55. package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-CWFPwmnU.js} +1 -1
  56. package/dist/assets/{ja-BDRrEcNP.js → ja-CifOE1VN.js} +7 -6
  57. package/dist/assets/{ko-Ba8sQV79.js → ko-BBDyAyYQ.js} +7 -6
  58. package/dist/assets/{main-C5DVOxc6.js → main-DnBAoktx.js} +2 -2
  59. package/dist/assets/main-MhSaOZoB.js +930 -0
  60. package/dist/assets/{markdownCore-CZwzz5gw.js → markdownCore-BrBwOOMn.js} +2 -2
  61. package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-BKyvLseN.js} +2 -2
  62. package/dist/assets/{mobile-CcaxB0n4.js → mobile-DKjeFgMN.js} +2 -2
  63. package/dist/assets/{patchFileDiff-yNfWXN4E.js → patchFileDiff-o8ms1me8.js} +1 -1
  64. package/dist/assets/pl-DgEHYfFM.js +16 -0
  65. package/dist/assets/{pt-BR-DIs6ElwV.js → pt-BR-DuWHojUw.js} +6 -5
  66. package/dist/assets/{renderElectronMiniChatApp-BcKLHXKe.js → renderElectronMiniChatApp-CDgBwwCS.js} +2 -2
  67. package/dist/assets/renderMobileApp-DJIKIK1r.js +25 -0
  68. package/dist/assets/runtimeEndpointReset-CYJ9O_xX.js +17 -0
  69. package/dist/assets/{scheduledTasksApi-DLW0mkQK.js → scheduledTasksApi-D8yamh1b.js} +1 -1
  70. package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-TEQCmzW8.js} +1 -1
  71. package/dist/assets/tr-D5m2yIWL.js +7 -0
  72. package/dist/assets/{uk-ZrCNjM7Y.js → uk-D_q60oog.js} +6 -5
  73. package/dist/assets/{useAppFontEffects-CjrntdL0.js → useAppFontEffects-D7sNpuL2.js} +161 -190
  74. package/dist/assets/useInlineCommentController-Brz_cLcQ.js +2 -0
  75. package/dist/assets/{useRootScrollLock-BHYPkrLv.js → useRootScrollLock-lXEnWlaU.js} +1 -1
  76. package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Dp3KMRQG.js} +2 -2
  77. package/dist/assets/vendor-zod-CK21ihK-.js +39 -0
  78. package/dist/assets/{web-components-Bhg83tjf.js → web-components-_mNEMjFY.js} +1 -1
  79. package/dist/assets/{zh-CN-iv_AV4EC.js → zh-CN-D8QvsVeg.js} +6 -5
  80. package/dist/assets/{zh-TW-CzEUk38j.js → zh-TW-B9t7rQQO.js} +6 -5
  81. package/dist/index.html +22 -22
  82. package/dist/mini-chat.html +7 -7
  83. package/dist/mobile.html +5 -5
  84. package/package.json +3 -3
  85. package/server/index.js +118 -85
  86. package/server/lib/agent-memory/actions.js +2 -2
  87. package/server/lib/agent-memory/feature-flag.js +1 -1
  88. package/server/lib/agent-memory/feature-flag.test.js +8 -8
  89. package/server/lib/agent-memory/routes.js +2 -2
  90. package/server/lib/agent-tool/runtime.js +37 -54
  91. package/server/lib/agent-tool/runtime.test.js +59 -59
  92. package/server/lib/browser-control/broker.js +3 -3
  93. package/server/lib/browser-control/broker.test.js +1 -1
  94. package/server/lib/changelog/update-notes.js +82 -0
  95. package/server/lib/changelog/update-notes.test.js +59 -0
  96. package/server/lib/client-auth/remote-clients.test.js +5 -5
  97. package/server/lib/context-obligatory/runtime.js +7 -7
  98. package/server/lib/context-obligatory/runtime.test.js +5 -5
  99. package/server/lib/dev-servers/parse.js +2 -2
  100. package/server/lib/dev-servers/parse.test.js +2 -2
  101. package/server/lib/dev-servers/routes.js +1 -1
  102. package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
  103. package/server/lib/dev-tunnel/client.js +1 -1
  104. package/server/lib/dev-tunnel/runtime.js +5 -5
  105. package/server/lib/dev-tunnel/tunnel.test.js +21 -1
  106. package/server/lib/dictation/local/model-catalog.js +1 -1
  107. package/server/lib/dictation/local/worker-process.js +2 -2
  108. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  109. package/server/lib/event-stream/global-ws-bridge.js +1 -1
  110. package/server/lib/event-stream/protocol.test.js +4 -4
  111. package/server/lib/event-stream/runtime.test.js +6 -6
  112. package/server/lib/fs/DOCUMENTATION.md +2 -0
  113. package/server/lib/fs/routes.js +37 -26
  114. package/server/lib/fs/routes.test.js +98 -21
  115. package/server/lib/git/DOCUMENTATION.md +3 -1
  116. package/server/lib/git/identity-storage.js +1 -1
  117. package/server/lib/git/issue-2746-longpaths.test.js +4 -4
  118. package/server/lib/git/routes.js +17 -1
  119. package/server/lib/git/service.js +116 -26
  120. package/server/lib/git/service.test.js +174 -3
  121. package/server/lib/github/auth.js +8 -8
  122. package/server/lib/github/pr-status.js +1 -1
  123. package/server/lib/github/routes.js +2 -2
  124. package/server/lib/inherited-env.js +1 -1
  125. package/server/lib/inherited-env.test.js +2 -2
  126. package/server/lib/linear/auth.js +8 -8
  127. package/server/lib/linear/auth.test.js +24 -24
  128. package/server/lib/linear/issues.test.js +6 -6
  129. package/server/lib/linear/mapping.test.js +4 -4
  130. package/server/lib/linear/oauth.js +2 -2
  131. package/server/lib/linear/oauth.test.js +14 -14
  132. package/server/lib/linear/routes.js +5 -5
  133. package/server/lib/linear/routes.test.js +16 -16
  134. package/server/lib/linear/status-runtime.test.js +9 -9
  135. package/server/lib/linear/status.js +1 -1
  136. package/server/lib/linear/status.test.js +15 -15
  137. package/server/lib/linear/teams.test.js +4 -4
  138. package/server/lib/magic-prompts/routes.js +2 -2
  139. package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
  140. package/server/lib/mcp-reconnect/runtime.js +122 -0
  141. package/server/lib/mcp-reconnect/runtime.test.js +140 -0
  142. package/server/lib/message-queue/DOCUMENTATION.md +158 -0
  143. package/server/lib/message-queue/runtime.js +870 -0
  144. package/server/lib/message-queue/runtime.test.js +482 -0
  145. package/server/lib/notifications/apns-runtime.js +17 -17
  146. package/server/lib/notifications/apns-runtime.test.js +11 -11
  147. package/server/lib/notifications/emitter-runtime.js +1 -1
  148. package/server/lib/notifications/emitter-runtime.test.js +1 -1
  149. package/server/lib/notifications/push-runtime.js +7 -7
  150. package/server/lib/notifications/routes.js +1 -1
  151. package/server/lib/notifications/runtime.js +1 -1
  152. package/server/lib/openchamber-control/action-resolution.test.js +10 -10
  153. package/server/lib/openchamber-control/actions.js +24 -24
  154. package/server/lib/openchamber-control/error.js +4 -4
  155. package/server/lib/openchamber-control/routes.js +2 -2
  156. package/server/lib/openchamber-control/routes.test.js +5 -5
  157. package/server/lib/openchamber-control/screenshots.js +1 -1
  158. package/server/lib/openchamber-control/screenshots.test.js +1 -1
  159. package/server/lib/openchamber-control/service.js +50 -50
  160. package/server/lib/openchamber-control/service.test.js +5 -5
  161. package/server/lib/openchamber-sessions/routes.js +124 -22
  162. package/server/lib/openchamber-sessions/routes.test.js +99 -11
  163. package/server/lib/opencode/DOCUMENTATION.md +13 -5
  164. package/server/lib/opencode/auth.js +1 -1
  165. package/server/lib/opencode/bootstrap-runtime.js +6 -6
  166. package/server/lib/opencode/cli-options.js +8 -8
  167. package/server/lib/opencode/config-paths.test.js +116 -0
  168. package/server/lib/opencode/core-routes.js +8 -7
  169. package/server/lib/opencode/core-routes.test.js +11 -11
  170. package/server/lib/opencode/env-config.js +5 -5
  171. package/server/lib/opencode/env-config.test.js +7 -7
  172. package/server/lib/opencode/env-runtime.js +10 -10
  173. package/server/lib/opencode/env-runtime.test.js +18 -18
  174. package/server/lib/opencode/feature-routes-runtime.js +17 -12
  175. package/server/lib/opencode/hmr-state-runtime.js +2 -2
  176. package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
  177. package/server/lib/opencode/input-history-scope.js +14 -0
  178. package/server/lib/opencode/lifecycle.js +8 -8
  179. package/server/lib/opencode/lifecycle.test.js +4 -4
  180. package/server/lib/opencode/managed-plugin-config.js +35 -0
  181. package/server/lib/opencode/managed-process-registry.js +3 -3
  182. package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
  183. package/server/lib/opencode/openchamber-routes.js +15 -15
  184. package/server/lib/opencode/openchamber-routes.test.js +11 -11
  185. package/server/lib/opencode/plugins.js +2 -2
  186. package/server/lib/opencode/plugins.test.js +1 -1
  187. package/server/lib/opencode/project-icon-routes.js +2 -2
  188. package/server/lib/opencode/project-icon-routes.test.js +1 -1
  189. package/server/lib/opencode/providers.js +44 -2
  190. package/server/lib/opencode/providers.test.js +131 -4
  191. package/server/lib/opencode/proxy.js +2 -2
  192. package/server/lib/opencode/pwa-manifest-routes.js +1 -1
  193. package/server/lib/opencode/restart-session-recovery.test.js +1 -1
  194. package/server/lib/opencode/routes-upgrade.test.js +4 -4
  195. package/server/lib/opencode/routes.js +14 -14
  196. package/server/lib/opencode/server-startup-runtime.js +6 -6
  197. package/server/lib/opencode/session-runtime.js +3 -3
  198. package/server/lib/opencode/session-runtime.test.js +6 -6
  199. package/server/lib/opencode/settings-helpers.js +31 -3
  200. package/server/lib/opencode/settings-helpers.test.js +189 -12
  201. package/server/lib/opencode/settings-runtime.js +2 -2
  202. package/server/lib/opencode/settings-runtime.test.js +29 -0
  203. package/server/lib/opencode/shared.js +5 -2
  204. package/server/lib/opencode/shared.test.js +7 -7
  205. package/server/lib/opencode/shutdown-runtime.js +2 -0
  206. package/server/lib/opencode/skill-routes.js +2 -1
  207. package/server/lib/opencode/snippets.js +1 -2
  208. package/server/lib/opencode/startup-performance.js +1 -1
  209. package/server/lib/opencode/startup-performance.test.js +6 -6
  210. package/server/lib/opencode/static-routes-runtime.js +8 -8
  211. package/server/lib/opencode/static-routes-runtime.test.js +7 -7
  212. package/server/lib/opencode/upgrade-capability.js +2 -2
  213. package/server/lib/opencode/upgrade-capability.test.js +3 -3
  214. package/server/lib/package-manager.js +17 -42
  215. package/server/lib/package-manager.test.js +19 -19
  216. package/server/lib/permission-auto-accept/runtime.js +1 -1
  217. package/server/lib/project-context/routes.js +1 -1
  218. package/server/lib/projects/project-config.js +2 -2
  219. package/server/lib/quota/DOCUMENTATION.md +9 -1
  220. package/server/lib/quota/credentials/providers.js +4 -0
  221. package/server/lib/quota/credentials/store.js +4 -4
  222. package/server/lib/quota/credentials/store.test.js +8 -8
  223. package/server/lib/quota/index.js +1 -0
  224. package/server/lib/quota/providers/exe-dev.js +75 -0
  225. package/server/lib/quota/providers/exe-dev.test.js +47 -0
  226. package/server/lib/quota/providers/hyper.js +118 -0
  227. package/server/lib/quota/providers/hyper.test.js +126 -0
  228. package/server/lib/quota/providers/index.js +15 -0
  229. package/server/lib/quota/providers/ollama-cloud.js +1 -1
  230. package/server/lib/quota/providers/opencode-go.js +2 -1
  231. package/server/lib/quota/providers/opencode-go.test.js +9 -5
  232. package/server/lib/quota/providers/xai.js +1 -1
  233. package/server/lib/quota/routes.js +2 -0
  234. package/server/lib/quota/utils/auth.js +1 -1
  235. package/server/lib/relay/DOCUMENTATION.md +1 -1
  236. package/server/lib/relay/host-client.js +1 -1
  237. package/server/lib/relay/host-client.test.js +9 -1
  238. package/server/lib/relay/host-lock.js +1 -1
  239. package/server/lib/relay/service.js +5 -5
  240. package/server/lib/relay/tunnel-host.js +3 -1
  241. package/server/lib/relay/tunnel-host.test.js +9 -1
  242. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
  243. package/server/lib/scheduled-tasks/routes.js +5 -5
  244. package/server/lib/scheduled-tasks/service.js +25 -25
  245. package/server/lib/scheduled-tasks/service.test.js +1 -1
  246. package/server/lib/security/bind-host.js +4 -4
  247. package/server/lib/security/bind-host.test.js +1 -1
  248. package/server/lib/security/request-security.js +11 -5
  249. package/server/lib/security/request-security.test.js +14 -14
  250. package/server/lib/session-assist/runtime.js +10 -9
  251. package/server/lib/session-folders/routes.js +2 -2
  252. package/server/lib/session-folders/routes.test.js +8 -8
  253. package/server/lib/session-goal/DOCUMENTATION.md +20 -7
  254. package/server/lib/session-goal/create.js +4 -2
  255. package/server/lib/session-goal/create.test.js +1 -1
  256. package/server/lib/session-goal/objectives.js +4 -4
  257. package/server/lib/session-goal/routes.js +1 -1
  258. package/server/lib/session-goal/runtime.js +88 -22
  259. package/server/lib/session-goal/runtime.test.js +428 -2
  260. package/server/lib/session-knowledge/runtime.js +11 -11
  261. package/server/lib/session-knowledge/runtime.test.js +5 -5
  262. package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
  263. package/server/lib/skills-catalog/cache.test.js +2 -2
  264. package/server/lib/skills-catalog/disk-cache.js +5 -5
  265. package/server/lib/skills-catalog/github-meta.test.js +2 -2
  266. package/server/lib/skills-catalog/install.js +3 -2
  267. package/server/lib/small-model/DOCUMENTATION.md +4 -0
  268. package/server/lib/small-model/call.js +9 -3
  269. package/server/lib/small-model/call.test.js +29 -6
  270. package/server/lib/small-model/index.js +8 -7
  271. package/server/lib/small-model/index.test.js +1 -1
  272. package/server/lib/small-model/resolve.js +1 -1
  273. package/server/lib/small-model/resolve.test.js +1 -1
  274. package/server/lib/small-model/routes.js +2 -1
  275. package/server/lib/system-prompt/runtime.js +3 -22
  276. package/server/lib/system-prompt/runtime.test.js +3 -3
  277. package/server/lib/terminal/DOCUMENTATION.md +11 -7
  278. package/server/lib/terminal/runtime.js +180 -26
  279. package/server/lib/terminal/runtime.test.js +554 -27
  280. package/server/lib/terminal/shells.js +20 -3
  281. package/server/lib/terminal/shells.test.js +33 -2
  282. package/server/lib/tts/base-url.js +3 -3
  283. package/server/lib/tts/routes.test.js +20 -20
  284. package/server/lib/ui-auth/ui-auth.js +12 -9
  285. package/server/lib/ui-auth/ui-auth.test.js +19 -3
  286. package/server/lib/ui-auth/ui-passkeys.js +7 -7
  287. package/server/lib/walkthrough/index.js +1 -0
  288. package/server/lib/walkthrough/jobs.test.js +1 -1
  289. package/server/lib/walkthrough/language.test.js +1 -1
  290. package/server/lib/walkthrough/model-settings.js +3 -3
  291. package/server/lib/walkthrough/model-settings.test.js +1 -1
  292. package/server/lib/walkthrough/pull-request.test.js +5 -5
  293. package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
  294. package/server/lib/walkthrough/routes.test.js +26 -5
  295. package/server/lib/walkthrough/store.js +3 -3
  296. package/server/lib/walkthrough/store.test.js +1 -1
  297. package/server/opencode-proxy.test.js +2 -2
  298. package/server/proxy-headers.js +1 -1
  299. package/server/sse-routes.test.js +4 -4
  300. package/README.md.bak +0 -250
  301. package/bin/lib/DOCUMENTATION.md.bak +0 -163
  302. package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
  303. package/dist/assets/FilesView-Bdq8ppva.js +0 -17
  304. package/dist/assets/GitView-Znn7VJcP.js +0 -6
  305. package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
  306. package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
  307. package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
  308. package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
  309. package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
  310. package/dist/assets/index-C57jT0Sa.css +0 -1
  311. package/dist/assets/index-DW_E3rnL.js +0 -7
  312. package/dist/assets/main-Bv5Pkngh.js +0 -928
  313. package/dist/assets/pl-Dioz29BI.js +0 -15
  314. package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
  315. package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
  316. package/dist/assets/tr-DFnMlPxY.js +0 -6
  317. package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
  318. package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
  319. package/server/index.d.ts.bak +0 -39
  320. package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
  321. package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
  322. package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
  323. package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
  324. package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
  325. package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
  326. package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
  327. package/server/lib/git/DOCUMENTATION.md.bak +0 -193
  328. package/server/lib/github/DOCUMENTATION.md.bak +0 -183
  329. package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
  330. package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
  331. package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
  332. package/server/lib/notifications/APNS.md.bak +0 -135
  333. package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
  334. package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
  335. package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
  336. package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
  337. package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
  338. package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
  339. package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
  340. package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
  341. package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
  342. package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
  343. package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
  344. package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
  345. package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
  346. package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
  347. package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
  348. package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
  349. package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
  350. package/server/lib/text/DOCUMENTATION.md.bak +0 -35
  351. package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
  352. package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
  353. package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
  354. package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
@@ -0,0 +1,75 @@
1
+ import { readManagedCredential } from '../credentials/providers.js';
2
+ import { asObject, buildResult, formatMoney, toNumber, toTimestamp, toUsageWindow } from '../utils/index.js';
3
+
4
+ export const providerId = 'exe-dev';
5
+ export const providerName = 'exe.dev';
6
+ export const aliases = ['exe-dev'];
7
+ const EXEC_URL = 'https://exe.dev/exec';
8
+ const USAGE_COMMAND = 'billing credits usage --group=day --json';
9
+
10
+ export const parseExeDevUsage = (payload) => {
11
+ const data = asObject(payload);
12
+ if (!data) return null;
13
+
14
+ const totalCost = toNumber(data.total_cost_usd);
15
+ const monthlyAllowance = toNumber(data.monthly_allowance_usd);
16
+ const resetAt = toTimestamp(data.period_end);
17
+ if (totalCost === null || monthlyAllowance === null || monthlyAllowance < 0 || resetAt === null) return null;
18
+
19
+ const usedPercent = monthlyAllowance > 0
20
+ ? Math.min(100, Math.max(0, (totalCost / monthlyAllowance) * 100))
21
+ : null;
22
+ const spent = formatMoney(totalCost);
23
+ const allowance = formatMoney(monthlyAllowance);
24
+ if (spent === null || allowance === null) return null;
25
+
26
+ return {
27
+ monthly: toUsageWindow({
28
+ usedPercent,
29
+ windowSeconds: null,
30
+ resetAt,
31
+ valueLabel: `$${spent} / $${allowance}`,
32
+ }),
33
+ };
34
+ };
35
+
36
+ export const fetchExeDevUsage = async (credential, fetchImpl = fetch) => {
37
+ const response = await fetchImpl(EXEC_URL, {
38
+ method: 'POST',
39
+ headers: {
40
+ Accept: 'application/json',
41
+ Authorization: `Bearer ${credential.usageToken}`,
42
+ 'Content-Type': 'text/plain',
43
+ 'User-Agent': 'AiWorker quota provider',
44
+ },
45
+ body: USAGE_COMMAND,
46
+ signal: AbortSignal.timeout(15_000),
47
+ });
48
+ if (response.status === 401 || response.status === 403) throw new Error('exe.dev authentication failed');
49
+ if (!response.ok) throw new Error(`exe.dev usage API returned HTTP ${response.status}`);
50
+ const windows = parseExeDevUsage(await response.json().catch(() => null));
51
+ if (!windows) throw new Error('exe.dev usage data could not be parsed');
52
+ return windows;
53
+ };
54
+
55
+ export const isConfigured = () => Boolean(readManagedCredential(providerId));
56
+
57
+ export const fetchQuota = async () => {
58
+ const credential = readManagedCredential(providerId);
59
+ if (!credential) {
60
+ return buildResult({ providerId, providerName, ok: false, configured: false, error: 'Not configured' });
61
+ }
62
+
63
+ try {
64
+ const windows = await fetchExeDevUsage(credential);
65
+ return buildResult({ providerId, providerName, ok: true, configured: true, usage: { windows } });
66
+ } catch (error) {
67
+ return buildResult({
68
+ providerId,
69
+ providerName,
70
+ ok: false,
71
+ configured: true,
72
+ error: error instanceof Error ? error.message : 'Request failed',
73
+ });
74
+ }
75
+ };
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { fetchExeDevUsage, parseExeDevUsage } from './exe-dev.js';
3
+
4
+ const payload = {
5
+ allowance_spend_usd: 0.11,
6
+ extra_spend_usd: 0,
7
+ group: 'day',
8
+ month: '2026-09',
9
+ monthly_allowance_usd: 20,
10
+ period_end: '2026-10-01T00:00:00Z',
11
+ period_start: '2026-09-01T00:00:00Z',
12
+ total_cost_usd: 0.11,
13
+ total_requests: 17,
14
+ };
15
+
16
+ describe('exe.dev quota provider', () => {
17
+ it('parses monthly credit usage', () => {
18
+ const windows = parseExeDevUsage(payload);
19
+ expect(windows?.monthly.usedPercent).toBeCloseTo(0.55);
20
+ expect(windows?.monthly.valueLabel).toBe('$0.11 / $20.00');
21
+ expect(windows?.monthly.resetAt).toBe(Date.parse('2026-10-01T00:00:00Z'));
22
+ });
23
+
24
+ it('sends the scoped billing command without exposing the token elsewhere', async () => {
25
+ const requests = [];
26
+ const windows = await fetchExeDevUsage({ usageToken: 'test-token' }, async (url, init) => {
27
+ requests.push({ url, init });
28
+ return Response.json(payload);
29
+ });
30
+ expect(windows.monthly.valueLabel).toBe('$0.11 / $20.00');
31
+ expect(requests).toHaveLength(1);
32
+ expect(requests[0].url).toBe('https://exe.dev/exec');
33
+ expect(requests[0].init.method).toBe('POST');
34
+ expect(requests[0].init.body).toBe('billing credits usage --group=day --json');
35
+ expect(requests[0].init.headers.Authorization).toBe('Bearer test-token');
36
+ });
37
+
38
+ it('rejects malformed successful responses', async () => {
39
+ await expect(fetchExeDevUsage({ usageToken: 'test-token' }, async () => Response.json({})))
40
+ .rejects.toThrow('could not be parsed');
41
+ });
42
+
43
+ it('reports authentication failure without including the token', async () => {
44
+ await expect(fetchExeDevUsage({ usageToken: 'test-token' }, async () => new Response('', { status: 401 })))
45
+ .rejects.toThrow('exe.dev authentication failed');
46
+ });
47
+ });
@@ -0,0 +1,118 @@
1
+ import { readAuthFile } from '../../opencode/auth.js';
2
+ import {
3
+ getAuthEntry,
4
+ normalizeAuthEntry,
5
+ buildResult,
6
+ toUsageWindow,
7
+ toNumber,
8
+ formatMoney,
9
+ asObject,
10
+ asNonEmptyString
11
+ } from '../utils/index.js';
12
+
13
+ export const providerId = 'hyper';
14
+ export const providerName = 'Charm Hyper';
15
+ export const aliases = ['hyper'];
16
+ const HYPER_QUOTA_URL = 'https://hyper.charm.land/v1/credits';
17
+ const CREDIT_TO_USD = 0.05;
18
+
19
+ const getApiKey = (auth) => {
20
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
21
+ return asNonEmptyString(entry?.key) ?? asNonEmptyString(entry?.token);
22
+ };
23
+
24
+ export const isConfigured = (auth = readAuthFile()) => Boolean(getApiKey(auth));
25
+
26
+ export const fetchQuota = async ({ readAuth = readAuthFile, fetchImpl = fetch } = {}) => {
27
+ const apiKey = getApiKey(readAuth());
28
+
29
+ if (!apiKey) {
30
+ return buildResult({
31
+ providerId,
32
+ providerName,
33
+ ok: false,
34
+ configured: false,
35
+ error: 'Not configured'
36
+ });
37
+ }
38
+
39
+ const timeoutSignal = AbortSignal.timeout(15_000);
40
+
41
+ try {
42
+ const response = await fetchImpl(HYPER_QUOTA_URL, {
43
+ method: 'GET',
44
+ headers: {
45
+ Authorization: `Bearer ${apiKey}`,
46
+ 'Accept-Encoding': 'identity'
47
+ },
48
+ signal: timeoutSignal
49
+ });
50
+
51
+ if (!response.ok) {
52
+ return buildResult({
53
+ providerId,
54
+ providerName,
55
+ ok: false,
56
+ configured: true,
57
+ error: response.status === 401 || response.status === 403
58
+ ? 'Session expired — please re-authenticate with Charm Hyper'
59
+ : `API error: ${response.status}`
60
+ });
61
+ }
62
+
63
+ const payload = asObject(await response.json());
64
+ const rawBalance = payload?.balance;
65
+ const balance = toNumber(asNonEmptyString(rawBalance)
66
+ ?? (Number.isFinite(rawBalance) ? rawBalance : null));
67
+
68
+ if (balance === null) {
69
+ return buildResult({
70
+ providerId,
71
+ providerName,
72
+ ok: false,
73
+ configured: true,
74
+ error: 'No quota data in response'
75
+ });
76
+ }
77
+
78
+ const creditsLabel = Number.isInteger(balance) ? String(balance) : formatMoney(balance);
79
+ const windows = {
80
+ credits_balance: toUsageWindow({
81
+ usedPercent: null,
82
+ windowSeconds: null,
83
+ resetAt: null,
84
+ valueLabel: `$${formatMoney(balance * CREDIT_TO_USD)}`
85
+ }),
86
+ credits: toUsageWindow({
87
+ usedPercent: null,
88
+ windowSeconds: null,
89
+ resetAt: null,
90
+ valueLabel: creditsLabel
91
+ })
92
+ };
93
+
94
+ return buildResult({
95
+ providerId,
96
+ providerName,
97
+ ok: true,
98
+ configured: true,
99
+ usage: { windows }
100
+ });
101
+ } catch (error) {
102
+ const isTimeout = error instanceof DOMException && (
103
+ error.name === 'TimeoutError' || (error.name === 'AbortError' && timeoutSignal.aborted)
104
+ );
105
+ const isParseError = error instanceof SyntaxError;
106
+ return buildResult({
107
+ providerId,
108
+ providerName,
109
+ ok: false,
110
+ configured: true,
111
+ error: isTimeout
112
+ ? 'Request timed out'
113
+ : isParseError
114
+ ? 'Invalid response from provider'
115
+ : (error instanceof Error ? error.message : 'Request failed')
116
+ });
117
+ }
118
+ };
@@ -0,0 +1,126 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { fetchQuota, isConfigured } from './hyper.js';
3
+
4
+ const readAuth = () => ({ hyper: { key: 'test-token' } });
5
+
6
+ // https://hyper.charm.land/docs/api/credits.html documents the balance payload.
7
+ // https://hyper.charm.land/faq defines one Hypercredit as $0.05.
8
+ describe('Charm Hyper quota provider', () => {
9
+ it.each([
10
+ [100, '100', '$5.00'],
11
+ ['50', '50', '$2.50'],
12
+ [25.5, '25.50', '$1.28'],
13
+ [0, '0', '$0.00'],
14
+ ['0', '0', '$0.00'],
15
+ ])('formats balance %s without an untranslated unit', async (balance, credits, dollars) => {
16
+ const result = await fetchQuota({
17
+ readAuth,
18
+ fetchImpl: async () => Response.json({ balance }),
19
+ });
20
+
21
+ expect(result.ok).toBe(true);
22
+ expect(result.providerId).toBe('hyper');
23
+ expect(result.configured).toBe(true);
24
+ expect(result.usage.windows.credits.valueLabel).toBe(credits);
25
+ expect(result.usage.windows.credits_balance.valueLabel).toBe(dollars);
26
+ for (const window of Object.values(result.usage.windows)) {
27
+ expect(window.usedPercent).toBeNull();
28
+ expect(window.remainingPercent).toBeNull();
29
+ expect(window.windowSeconds).toBeNull();
30
+ expect(window.resetAt).toBeNull();
31
+ expect(window.resetAfterSeconds).toBeNull();
32
+ }
33
+ });
34
+
35
+ it.each([
36
+ {}, null, [], { balance: '' }, { balance: ' \t ' }, { balance: 'NaN' },
37
+ { balance: 'Infinity' }, { balance: null }, { balance: true }, { balance: [] },
38
+ { balance: {} },
39
+ ])('rejects invalid payload %j instead of showing zero', async (payload) => {
40
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => Response.json(payload) });
41
+
42
+ expect(result.ok).toBe(false);
43
+ expect(result.configured).toBe(true);
44
+ expect(result.error).toBe('No quota data in response');
45
+ expect(result.usage).toBeNull();
46
+ });
47
+
48
+ it.each([
49
+ { hyper: { key: 'test-token' } },
50
+ { hyper: { token: 'test-token' } },
51
+ { hyper: 'test-token' },
52
+ { hyper: { key: ' ', token: 'test-token' } },
53
+ { hyper: { key: 42, token: 'test-token' } },
54
+ ])('uses a validated credential for the documented request', async (auth) => {
55
+ expect(isConfigured(auth)).toBe(true);
56
+ let requests = 0;
57
+ const result = await fetchQuota({
58
+ readAuth: () => auth,
59
+ fetchImpl: async (url, options) => {
60
+ requests += 1;
61
+ expect(url).toBe('https://hyper.charm.land/v1/credits');
62
+ expect(options.method).toBe('GET');
63
+ expect(new Headers(options.headers).get('Authorization')).toBe('Bearer test-token');
64
+ expect(options.signal).toBeInstanceOf(AbortSignal);
65
+ return Response.json({ balance: 100 });
66
+ },
67
+ });
68
+
69
+ expect(requests).toBe(1);
70
+ expect(result.ok).toBe(true);
71
+ expect(JSON.stringify(result)).not.toContain('test-token');
72
+ });
73
+
74
+ it.each([{}, { hyper: { key: '' } }, { hyper: { key: ' ' } }, { hyper: { key: 42 } }])(
75
+ 'does not request usage without a valid credential',
76
+ async (auth) => {
77
+ expect(isConfigured(auth)).toBe(false);
78
+ let requests = 0;
79
+ const result = await fetchQuota({
80
+ readAuth: () => auth,
81
+ fetchImpl: async () => {
82
+ requests += 1;
83
+ return Response.json({ balance: 100 });
84
+ },
85
+ });
86
+
87
+ expect(requests).toBe(0);
88
+ expect(result.ok).toBe(false);
89
+ expect(result.configured).toBe(false);
90
+ expect(result.error).toBe('Not configured');
91
+ },
92
+ );
93
+
94
+ it.each([
95
+ [401, 'Session expired — please re-authenticate with Charm Hyper'],
96
+ [403, 'Session expired — please re-authenticate with Charm Hyper'],
97
+ [429, 'API error: 429'],
98
+ [500, 'API error: 500'],
99
+ ])('reports HTTP %s as a failure', async (status, error) => {
100
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => new Response(null, { status }) });
101
+
102
+ expect(result.ok).toBe(false);
103
+ expect(result.configured).toBe(true);
104
+ expect(result.error).toBe(error);
105
+ expect(result.usage).toBeNull();
106
+ });
107
+
108
+ it('reports invalid JSON as a parse failure', async () => {
109
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => new Response('{') });
110
+ expect(result.error).toBe('Invalid response from provider');
111
+ expect(result.ok).toBe(false);
112
+ expect(result.configured).toBe(true);
113
+ expect(result.usage).toBeNull();
114
+ });
115
+
116
+ it.each([
117
+ [new DOMException('Timed out', 'TimeoutError'), 'Request timed out'],
118
+ [new Error('Network unavailable'), 'Network unavailable'],
119
+ ])('reports request failure', async (failure, message) => {
120
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => { throw failure; } });
121
+ expect(result.error).toBe(message);
122
+ expect(result.ok).toBe(false);
123
+ expect(result.configured).toBe(true);
124
+ expect(result.usage).toBeNull();
125
+ });
126
+ });
@@ -13,7 +13,9 @@ import * as copilot from './copilot.js';
13
13
  import * as crof from './crof.js';
14
14
  import * as cursor from './cursor.js';
15
15
  import * as deepseek from './deepseek.js';
16
+ import * as exeDev from './exe-dev.js';
16
17
  import * as google from './google/index.js';
18
+ import * as hyper from './hyper.js';
17
19
  import * as kimi from './kimi.js';
18
20
  import * as nanogpt from './nanogpt.js';
19
21
  import * as openai from './openai.js';
@@ -59,12 +61,24 @@ const registry = {
59
61
  isConfigured: deepseek.isConfigured,
60
62
  fetchQuota: deepseek.fetchQuota
61
63
  },
64
+ 'exe-dev': {
65
+ providerId: exeDev.providerId,
66
+ providerName: exeDev.providerName,
67
+ isConfigured: exeDev.isConfigured,
68
+ fetchQuota: exeDev.fetchQuota
69
+ },
62
70
  google: {
63
71
  providerId: google.providerId,
64
72
  providerName: google.providerName,
65
73
  isConfigured: google.isConfigured,
66
74
  fetchQuota: google.fetchGoogleQuota
67
75
  },
76
+ hyper: {
77
+ providerId: hyper.providerId,
78
+ providerName: hyper.providerName,
79
+ isConfigured: hyper.isConfigured,
80
+ fetchQuota: hyper.fetchQuota
81
+ },
68
82
  'zai-coding-plan': {
69
83
  providerId: zai.providerId,
70
84
  providerName: zai.providerName,
@@ -213,6 +227,7 @@ export const fetchGoogleQuota = google.fetchGoogleQuota;
213
227
  export const fetchCodexQuota = codex.fetchQuota;
214
228
  export const fetchCursorQuota = cursor.fetchQuota;
215
229
  export const fetchDeepseekQuota = deepseek.fetchQuota;
230
+ export const fetchHyperQuota = hyper.fetchQuota;
216
231
  export const fetchCopilotQuota = copilot.fetchQuota;
217
232
  export const fetchCopilotAddonQuota = copilot.fetchQuotaAddon;
218
233
  export const fetchKimiQuota = kimi.fetchQuota;
@@ -45,7 +45,7 @@ export const isConfigured = () => {
45
45
  export const fetchOllamaCloudUsage = async (credential, fetchImpl = fetch) => {
46
46
  const response = await fetchImpl('https://ollama.com/settings', {
47
47
  method: 'GET',
48
- headers: { Cookie: credential.cookie, 'User-Agent': 'OpenChamber quota provider' },
48
+ headers: { Cookie: credential.cookie, 'User-Agent': 'AiWorker quota provider' },
49
49
  redirect: 'manual',
50
50
  signal: AbortSignal.timeout(15_000),
51
51
  });
@@ -37,7 +37,8 @@ export const fetchOpenCodeGoUsage = async (apiKey, fetchImpl = fetch) => {
37
37
  headers: {
38
38
  Accept: 'application/json',
39
39
  Authorization: `Bearer ${apiKey}`,
40
- 'User-Agent': 'OpenChamber quota provider',
40
+ 'x-opencode-session': 'openchamber-usage',
41
+ 'User-Agent': 'AiWorker quota provider',
41
42
  },
42
43
  signal: AbortSignal.timeout(15_000),
43
44
  });
@@ -3,9 +3,9 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
5
5
 
6
- const previousDataDirectory = process.env.OPENCHAMBER_DATA_DIR;
6
+ const previousDataDirectory = process.env.AIWORKER_DATA_DIR;
7
7
  const temporaryDataDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-opencode-go-'));
8
- process.env.OPENCHAMBER_DATA_DIR = temporaryDataDirectory;
8
+ process.env.AIWORKER_DATA_DIR = temporaryDataDirectory;
9
9
 
10
10
  vi.mock('../../opencode/auth.js', () => ({
11
11
  readAuthFile: () => ({ 'opencode-go': { key: 'test-key' } }),
@@ -18,8 +18,8 @@ afterEach(() => {
18
18
  });
19
19
 
20
20
  afterAll(() => {
21
- if (previousDataDirectory === undefined) delete process.env.OPENCHAMBER_DATA_DIR;
22
- else process.env.OPENCHAMBER_DATA_DIR = previousDataDirectory;
21
+ if (previousDataDirectory === undefined) delete process.env.AIWORKER_DATA_DIR;
22
+ else process.env.AIWORKER_DATA_DIR = previousDataDirectory;
23
23
  fs.rmSync(temporaryDataDirectory, { recursive: true, force: true });
24
24
  });
25
25
 
@@ -43,7 +43,11 @@ describe('OpenCode Go quota provider', () => {
43
43
  return new Response(JSON.stringify({ usage: { rolling: { percent: 25, resetsAt: '2026-08-12T12:00:00.000Z' } } }));
44
44
  });
45
45
  expect(request.url).toBe('https://opencode.ai/zen/go/v1/usage');
46
- expect(request.options.headers).toMatchObject({ Accept: 'application/json', Authorization: 'Bearer secret' });
46
+ expect(request.options.headers).toMatchObject({
47
+ Accept: 'application/json',
48
+ Authorization: 'Bearer secret',
49
+ 'x-opencode-session': 'openchamber-usage',
50
+ });
47
51
  expect(request.options.headers.Cookie).toBeUndefined();
48
52
  expect(usage['5h'].usedPercent).toBe(25);
49
53
  });
@@ -323,7 +323,7 @@ const fetchUsage = async (accessToken) => {
323
323
  'Content-Type': 'application/grpc-web+proto',
324
324
  'x-grpc-web': '1',
325
325
  'x-user-agent': 'connect-es/2.1.1',
326
- 'User-Agent': 'OpenChamber'
326
+ 'User-Agent': 'AiWorker'
327
327
  },
328
328
  body: EMPTY_GRPC_WEB_BODY,
329
329
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
@@ -2,8 +2,10 @@ import express from 'express';
2
2
  import { deleteManagedCredential, getManagedCredentialStatus, normalizers, readManagedCredential, writeManagedCredential } from './credentials/providers.js';
3
3
  import { fetchOllamaCloudUsage } from './providers/ollama-cloud.js';
4
4
  import { importCursorCredential, validateCursorCredential } from './providers/cursor.js';
5
+ import { fetchExeDevUsage } from './providers/exe-dev.js';
5
6
 
6
7
  const validators = {
8
+ 'exe-dev': fetchExeDevUsage,
7
9
  'ollama-cloud': fetchOllamaCloudUsage,
8
10
  cursor: validateCursorCredential,
9
11
  };
@@ -1,8 +1,8 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
+ import { OPENCODE_CONFIG_DIR } from '../../opencode/shared.js';
4
5
 
5
- const OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode');
6
6
  const OPENCODE_DATA_DIR = path.join(os.homedir(), '.local', 'share', 'opencode');
7
7
 
8
8
  export const ANTIGRAVITY_ACCOUNTS_PATHS = [
@@ -41,7 +41,7 @@ Relay is not a separate link format: it is one transport candidate inside the un
41
41
  Everything a client normally sends to the single AiWorker origin:
42
42
  - **HTTP** — REST endpoints and proxied OpenCode SDK calls under `/api/*`, plus `/auth/*` and `/health`.
43
43
  - **SSE** — long-lived streamed responses (the event stream and notifications). These are just HTTP responses whose body streams; the tunnel needs no special SSE handling.
44
- - **WebSocket** — the endpoints that use a real socket (the global event stream on platforms that support WS, terminal I/O, dictation).
44
+ - **WebSocket** — the endpoints that use a real socket (the global event stream on platforms that support WS, terminal I/O, dictation, and desktop dev-server previews).
45
45
 
46
46
  The host dispatcher restricts tunneled traffic to explicit path allowlists (one for HTTP, one for WS).
47
47
 
@@ -33,7 +33,7 @@ const DEFAULT_BATCH_WINDOW_MS = 150;
33
33
  // the 150 ms default. Only applies on directions where batching was negotiated.
34
34
  const resolveBatchWindowMs = (option) => {
35
35
  if (Number.isFinite(option) && option >= 0) return option;
36
- const envValue = Number.parseInt(process.env.OPENCHAMBER_RELAY_BATCH_WINDOW_MS ?? '', 10);
36
+ const envValue = Number.parseInt(process.env.AIWORKER_RELAY_BATCH_WINDOW_MS ?? '', 10);
37
37
  if (Number.isFinite(envValue) && envValue >= 0) return envValue;
38
38
  return DEFAULT_BATCH_WINDOW_MS;
39
39
  };
@@ -98,6 +98,9 @@ const startFakeRelay = () => {
98
98
  wsUrl: `ws://127.0.0.1:${port}`,
99
99
  state,
100
100
  stop: () => new Promise((r) => {
101
+ // A socket a failed test left open would hold server.close() until
102
+ // the hook timeout; drop them so a failure is reported once.
103
+ for (const client of wss.clients) client.terminate();
101
104
  wss.close();
102
105
  server.close(() => r());
103
106
  }),
@@ -162,7 +165,6 @@ const runScriptedClient = async ({ relayUrl, serverId, hostEncPubJwk }) => {
162
165
  url.searchParams.set('role', 'client');
163
166
  url.searchParams.set('serverId', serverId);
164
167
  url.searchParams.set('connectionId', connectionId);
165
- const ws = new WebSocket(url.toString());
166
168
 
167
169
  const hostPub = await globalThis.crypto.subtle.importKey(
168
170
  'jwk',
@@ -182,6 +184,12 @@ const runScriptedClient = async ({ relayUrl, serverId, hostEncPubJwk }) => {
182
184
  resolveDone = resolve;
183
185
  });
184
186
 
187
+ // Dialed only now, with the key material ready and the listeners attached
188
+ // in the same tick. Dialing before the WebCrypto awaits above let a loopback
189
+ // socket open while the key generation was still queued on the threadpool,
190
+ // and an `open` event with no listener means no hello, no ready, and a
191
+ // client that waits forever. Loaded CI runners hit exactly that.
192
+ const ws = new WebSocket(url.toString());
185
193
  ws.on('open', async () => {
186
194
  ws.send(JSON.stringify({
187
195
  t: 'hello',
@@ -1,4 +1,4 @@
1
- // Per-machine relay-host claim. Every OpenChamber instance on a machine shares
1
+ // Per-machine relay-host claim. Every AiWorker instance on a machine shares
2
2
  // the same data dir and therefore the same relay signing key / serverId, so if
3
3
  // two processes run a relay host at once they fight over the single host slot
4
4
  // at the relay worker (each new connection closes the previous one with
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // Config lives in the server settings file as `settings.privateRelay =
5
5
  // { enabled, relayUrl }` (same storage precedent as tunnels/notifications).
6
- // Routes are registered with the other OpenChamber feature routes, before the
6
+ // Routes are registered with the other AiWorker feature routes, before the
7
7
  // generic OpenCode proxy, and are covered by the same global UI auth gate.
8
8
  //
9
9
  // Cross-runtime parity note: relay host mode intentionally targets the web
@@ -16,7 +16,7 @@ import express from 'express';
16
16
  import { createRelayIdentityRuntime } from './identity.js';
17
17
  import { startRelayHost } from './host-client.js';
18
18
 
19
- export const DEFAULT_RELAY_URL = 'wss://relay.openchamber.dev/ws';
19
+ export const DEFAULT_RELAY_URL = 'wss://relay.aiworker.dev/ws';
20
20
 
21
21
  const isValidRelayUrl = (value) => {
22
22
  if (typeof value !== 'string') return false;
@@ -40,7 +40,7 @@ const normalizeRelayUrl = (value) => {
40
40
  // stored setting entirely, so the host connection, the pairing offer, and the
41
41
  // status all point at it — clients then inherit it from the offer automatically.
42
42
  const envRelayUrlOverride = () => {
43
- const raw = process.env.OPENCHAMBER_RELAY_URL;
43
+ const raw = process.env.AIWORKER_RELAY_URL;
44
44
  if (typeof raw !== 'string' || !raw.trim() || !isValidRelayUrl(raw)) return null;
45
45
  return raw.trim();
46
46
  };
@@ -100,7 +100,7 @@ export const createRelayService = ({
100
100
  return {
101
101
  enabled: stored?.enabled === true,
102
102
  relayUrl: override ?? normalizeRelayUrl(stored?.relayUrl),
103
- // True when the endpoint is pinned by OPENCHAMBER_RELAY_URL (a self-hosted
103
+ // True when the endpoint is pinned by AIWORKER_RELAY_URL (a self-hosted
104
104
  // relay); the stored setting is ignored while it is set.
105
105
  relayUrlLocked: override !== null,
106
106
  };
@@ -122,7 +122,7 @@ export const createRelayService = ({
122
122
 
123
123
  const standbyStatus = (holderPid) => ({
124
124
  state: 'standby',
125
- lastError: `relay host is owned by another local OpenChamber process (pid ${holderPid})`,
125
+ lastError: `relay host is owned by another local AiWorker process (pid ${holderPid})`,
126
126
  connectedClients: 0,
127
127
  });
128
128
 
@@ -33,7 +33,9 @@ const ALLOWED_WS_PATHS = new Set([
33
33
  '/api/event/ws',
34
34
  '/api/terminal/ws',
35
35
  '/api/dictation/ws',
36
+ '/api/dev-tunnel',
36
37
  ]);
38
+ export const isAllowedRelayWebSocketPath = (pathname) => ALLOWED_WS_PATHS.has(pathname);
37
39
 
38
40
  // Hop-by-hop headers stripped from tunneled requests; `host` is set by fetch
39
41
  // to the loopback origin. content-length is dropped too because the body is
@@ -425,7 +427,7 @@ export const createTunnelHost = ({ connectionId, getLocalPort, sendFrame, getBuf
425
427
  void sendAbort(streamId, error?.message ?? 'malformed ws open');
426
428
  return;
427
429
  }
428
- if (!ALLOWED_WS_PATHS.has(open.path)) {
430
+ if (!isAllowedRelayWebSocketPath(open.path)) {
429
431
  void sendAbort(streamId, 'Path is not allowed through the relay');
430
432
  return;
431
433
  }
@@ -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.