@aiworker/web 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/README.md +4 -4
  2. package/bin/cli.js +14 -4
  3. package/bin/cli.test.js +165 -73
  4. package/bin/lib/cli-api-target.js +2 -2
  5. package/bin/lib/cli-args.js +119 -119
  6. package/bin/lib/cli-lifecycle.js +9 -9
  7. package/bin/lib/cli-network.js +3 -3
  8. package/bin/lib/cli-paths.js +3 -3
  9. package/bin/lib/cli-ports.js +2 -2
  10. package/bin/lib/cli-process.js +4 -4
  11. package/bin/lib/cli-startup.js +64 -15
  12. package/bin/lib/cli-tunnel-profiles.js +1 -1
  13. package/bin/lib/cli-tunnel-utils.js +2 -2
  14. package/bin/lib/commands-connect-url.js +19 -19
  15. package/bin/lib/commands-lifecycle.js +27 -27
  16. package/bin/lib/commands-logs.js +5 -5
  17. package/bin/lib/commands-models.js +1 -1
  18. package/bin/lib/commands-projects.js +1 -1
  19. package/bin/lib/commands-schedule.js +1 -1
  20. package/bin/lib/commands-serve.js +29 -29
  21. package/bin/lib/commands-session.js +1 -1
  22. package/bin/lib/commands-startup.js +45 -10
  23. package/bin/lib/commands-status.js +1 -1
  24. package/bin/lib/commands-tunnel.js +40 -40
  25. package/bin/lib/commands-update.js +1 -1
  26. package/bin/lib/commands-update.test.js +6 -6
  27. package/dist/assets/{CodeMirrorCommentWidgets-B3u7UIcE.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
  28. package/dist/assets/DiffView-DXjVsEOw.js +4 -0
  29. package/dist/assets/DirtyBranchSwitchDialog-aig6zJjg.js +1 -0
  30. package/dist/assets/FilesView-CJdG0-wk.js +17 -0
  31. package/dist/assets/GitView-BXsS3jG6.js +6 -0
  32. package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-D_hpXBnF.js} +1 -1
  33. package/dist/assets/LinearIssuesView-BoUiLQG1.js +1 -0
  34. package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
  35. package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
  36. package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
  37. package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-8p6nLJaU.js} +1 -1
  38. package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
  39. package/dist/assets/PlanView-CJTmOF6I.js +5 -0
  40. package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
  41. package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-Cpb4hM6q.js} +1 -1
  42. package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
  43. package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
  44. package/dist/assets/ToolPartDiffPreview-DtZLzrSK.js +8 -0
  45. package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
  46. package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
  47. package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
  48. package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
  49. package/dist/assets/de-DCrsQ5zS.js +10 -0
  50. package/dist/assets/es-Dwv4YfH6.js +16 -0
  51. package/dist/assets/fr-BCWg4Cnx.js +14 -0
  52. package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
  53. package/dist/assets/index-B68h5S5B.css +1 -0
  54. package/dist/assets/index-DVwMarHI.js +39 -0
  55. package/dist/assets/{index-CODwDsU_.js → index-jXSK5RrB.js} +1 -1
  56. package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-B_x_rVon.js} +1 -1
  57. package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-DW_xMer1.js} +1 -1
  58. package/dist/assets/ja-Bruw3RYC.js +16 -0
  59. package/dist/assets/ko-idhXGF8j.js +16 -0
  60. package/dist/assets/main-BkyaQhJX.js +930 -0
  61. package/dist/assets/{main-C5DVOxc6.js → main-CzdPuL0s.js} +2 -2
  62. package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
  63. package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-CCkqKOLp.js} +2 -2
  64. package/dist/assets/{mobile-CcaxB0n4.js → mobile-mvxTo055.js} +2 -2
  65. package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
  66. package/dist/assets/pl-CGZWUqZJ.js +16 -0
  67. package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
  68. package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
  69. package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
  70. package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
  71. package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
  72. package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-CRl0SUC9.js} +1 -1
  73. package/dist/assets/surface-oMvhsog3.js +1 -0
  74. package/dist/assets/tr-BK_KiO2V.js +7 -0
  75. package/dist/assets/uk-CG74xV8N.js +16 -0
  76. package/dist/assets/useAppFontEffects-CPKeOpUo.js +7284 -0
  77. package/dist/assets/useGitComparison-DH220WmO.js +1 -0
  78. package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
  79. package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
  80. package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
  81. package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
  82. package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
  83. package/dist/assets/{web-components-Bhg83tjf.js → web-components-z7hfnS8n.js} +1 -1
  84. package/dist/assets/zh-CN-T4AXofZI.js +16 -0
  85. package/dist/assets/zh-TW-Dte40sxp.js +16 -0
  86. package/dist/index.html +21 -59
  87. package/dist/mini-chat.html +7 -7
  88. package/dist/mobile.html +5 -5
  89. package/package.json +3 -4
  90. package/server/index.d.ts +15 -0
  91. package/server/index.js +158 -89
  92. package/server/lib/agent-memory/actions.js +2 -2
  93. package/server/lib/agent-memory/feature-flag.js +1 -1
  94. package/server/lib/agent-memory/feature-flag.test.js +8 -8
  95. package/server/lib/agent-memory/routes.js +2 -2
  96. package/server/lib/agent-tool/runtime.js +37 -54
  97. package/server/lib/agent-tool/runtime.test.js +59 -59
  98. package/server/lib/browser-control/broker.js +3 -3
  99. package/server/lib/browser-control/broker.test.js +1 -1
  100. package/server/lib/changelog/update-notes.js +82 -0
  101. package/server/lib/changelog/update-notes.test.js +59 -0
  102. package/server/lib/client-auth/remote-clients.test.js +5 -5
  103. package/server/lib/context-obligatory/runtime.js +7 -7
  104. package/server/lib/context-obligatory/runtime.test.js +5 -5
  105. package/server/lib/data-dir-migration.js +40 -0
  106. package/server/lib/data-dir-migration.test.js +59 -0
  107. package/server/lib/dev-servers/parse.js +2 -2
  108. package/server/lib/dev-servers/parse.test.js +2 -2
  109. package/server/lib/dev-servers/routes.js +1 -1
  110. package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
  111. package/server/lib/dev-tunnel/client.js +1 -1
  112. package/server/lib/dev-tunnel/runtime.js +5 -5
  113. package/server/lib/dev-tunnel/tunnel.test.js +21 -1
  114. package/server/lib/dictation/local/model-catalog.js +1 -1
  115. package/server/lib/dictation/local/worker-process.js +2 -2
  116. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  117. package/server/lib/event-stream/global-ws-bridge.js +1 -1
  118. package/server/lib/event-stream/protocol.test.js +4 -4
  119. package/server/lib/event-stream/runtime.test.js +18 -18
  120. package/server/lib/fs/DOCUMENTATION.md +5 -0
  121. package/server/lib/fs/routes.js +68 -26
  122. package/server/lib/fs/routes.test.js +208 -22
  123. package/server/lib/git/DOCUMENTATION.md +32 -6
  124. package/server/lib/git/identity-storage.js +1 -1
  125. package/server/lib/git/issue-2746-longpaths.test.js +4 -4
  126. package/server/lib/git/routes.js +41 -5
  127. package/server/lib/git/service.js +300 -224
  128. package/server/lib/git/service.test.js +596 -28
  129. package/server/lib/github/auth.js +8 -8
  130. package/server/lib/github/pr-status.js +1 -1
  131. package/server/lib/github/routes.js +2 -2
  132. package/server/lib/inherited-env.js +17 -10
  133. package/server/lib/inherited-env.test.js +10 -11
  134. package/server/lib/linear/auth.js +8 -8
  135. package/server/lib/linear/auth.test.js +24 -24
  136. package/server/lib/linear/issues.test.js +6 -6
  137. package/server/lib/linear/mapping.test.js +4 -4
  138. package/server/lib/linear/oauth.js +2 -2
  139. package/server/lib/linear/oauth.test.js +14 -14
  140. package/server/lib/linear/routes.js +5 -5
  141. package/server/lib/linear/routes.test.js +16 -16
  142. package/server/lib/linear/status-runtime.test.js +9 -9
  143. package/server/lib/linear/status.js +1 -1
  144. package/server/lib/linear/status.test.js +15 -15
  145. package/server/lib/linear/teams.test.js +4 -4
  146. package/server/lib/magic-prompts/routes.js +2 -2
  147. package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
  148. package/server/lib/mcp-reconnect/runtime.js +122 -0
  149. package/server/lib/mcp-reconnect/runtime.test.js +140 -0
  150. package/server/lib/message-queue/DOCUMENTATION.md +161 -0
  151. package/server/lib/message-queue/runtime.js +870 -0
  152. package/server/lib/message-queue/runtime.test.js +482 -0
  153. package/server/lib/network-defaults.js +23 -0
  154. package/server/lib/network-defaults.test.js +52 -0
  155. package/server/lib/notifications/apns-runtime.js +17 -17
  156. package/server/lib/notifications/apns-runtime.test.js +11 -11
  157. package/server/lib/notifications/emitter-runtime.js +1 -1
  158. package/server/lib/notifications/emitter-runtime.test.js +1 -1
  159. package/server/lib/notifications/push-runtime.js +7 -7
  160. package/server/lib/notifications/routes.js +1 -1
  161. package/server/lib/notifications/runtime.js +1 -1
  162. package/server/lib/openchamber-control/action-resolution.test.js +10 -10
  163. package/server/lib/openchamber-control/actions.js +24 -24
  164. package/server/lib/openchamber-control/error.js +4 -4
  165. package/server/lib/openchamber-control/routes.js +2 -2
  166. package/server/lib/openchamber-control/routes.test.js +5 -5
  167. package/server/lib/openchamber-control/screenshots.js +1 -1
  168. package/server/lib/openchamber-control/screenshots.test.js +1 -1
  169. package/server/lib/openchamber-control/service.js +50 -50
  170. package/server/lib/openchamber-control/service.test.js +5 -5
  171. package/server/lib/openchamber-sessions/routes.js +124 -22
  172. package/server/lib/openchamber-sessions/routes.test.js +99 -11
  173. package/server/lib/opencode/DOCUMENTATION.md +30 -6
  174. package/server/lib/opencode/auth.js +1 -1
  175. package/server/lib/opencode/bootstrap-runtime.js +8 -6
  176. package/server/lib/opencode/cli-options.js +8 -8
  177. package/server/lib/opencode/config-paths.test.js +116 -0
  178. package/server/lib/opencode/core-routes.js +8 -7
  179. package/server/lib/opencode/core-routes.test.js +11 -11
  180. package/server/lib/opencode/env-config.js +5 -5
  181. package/server/lib/opencode/env-config.test.js +7 -7
  182. package/server/lib/opencode/env-runtime.js +10 -10
  183. package/server/lib/opencode/env-runtime.test.js +18 -18
  184. package/server/lib/opencode/feature-routes-runtime.js +19 -12
  185. package/server/lib/opencode/hmr-state-runtime.js +2 -2
  186. package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
  187. package/server/lib/opencode/input-history-scope.js +14 -0
  188. package/server/lib/opencode/lifecycle.js +8 -8
  189. package/server/lib/opencode/lifecycle.test.js +4 -4
  190. package/server/lib/opencode/managed-plugin-config.js +35 -0
  191. package/server/lib/opencode/managed-process-registry.js +3 -3
  192. package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
  193. package/server/lib/opencode/openchamber-routes.js +79 -19
  194. package/server/lib/opencode/openchamber-routes.test.js +139 -12
  195. package/server/lib/opencode/plugins.js +2 -2
  196. package/server/lib/opencode/plugins.test.js +1 -1
  197. package/server/lib/opencode/project-icon-routes.js +2 -2
  198. package/server/lib/opencode/project-icon-routes.test.js +1 -1
  199. package/server/lib/opencode/providers.js +44 -2
  200. package/server/lib/opencode/providers.test.js +131 -4
  201. package/server/lib/opencode/proxy.js +2 -2
  202. package/server/lib/opencode/pwa-manifest-routes.js +1 -1
  203. package/server/lib/opencode/restart-session-recovery.test.js +1 -1
  204. package/server/lib/opencode/routes-upgrade.test.js +4 -4
  205. package/server/lib/opencode/routes.js +19 -17
  206. package/server/lib/opencode/server-startup-runtime.js +6 -6
  207. package/server/lib/opencode/session-runtime.js +3 -3
  208. package/server/lib/opencode/session-runtime.test.js +6 -6
  209. package/server/lib/opencode/settings-files.js +214 -0
  210. package/server/lib/opencode/settings-helpers.js +116 -44
  211. package/server/lib/opencode/settings-helpers.test.js +354 -12
  212. package/server/lib/opencode/settings-registry.json +747 -0
  213. package/server/lib/opencode/settings-runtime.js +141 -20
  214. package/server/lib/opencode/settings-runtime.test.js +191 -4
  215. package/server/lib/opencode/shared.js +5 -2
  216. package/server/lib/opencode/shared.test.js +7 -7
  217. package/server/lib/opencode/shutdown-runtime.js +2 -0
  218. package/server/lib/opencode/skill-routes.js +2 -1
  219. package/server/lib/opencode/snippets.js +1 -2
  220. package/server/lib/opencode/startup-performance.js +1 -1
  221. package/server/lib/opencode/startup-performance.test.js +6 -6
  222. package/server/lib/opencode/static-routes-runtime.js +8 -8
  223. package/server/lib/opencode/static-routes-runtime.test.js +7 -7
  224. package/server/lib/opencode/upgrade-capability.js +2 -2
  225. package/server/lib/opencode/upgrade-capability.test.js +3 -3
  226. package/server/lib/package-manager.js +17 -42
  227. package/server/lib/package-manager.test.js +19 -19
  228. package/server/lib/permission-auto-accept/runtime.js +1 -1
  229. package/server/lib/project-context/DOCUMENTATION.md +25 -1
  230. package/server/lib/project-context/routes.http.test.js +18 -0
  231. package/server/lib/project-context/routes.js +16 -1
  232. package/server/lib/project-context/runtime.js +248 -23
  233. package/server/lib/project-context/runtime.test.js +106 -3
  234. package/server/lib/projects/DOCUMENTATION.md +76 -0
  235. package/server/lib/projects/project-config.js +144 -11
  236. package/server/lib/projects/project-id.js +17 -0
  237. package/server/lib/projects/project-setup.js +409 -0
  238. package/server/lib/projects/project-setup.test.js +557 -0
  239. package/server/lib/projects/routes.js +59 -0
  240. package/server/lib/quota/DOCUMENTATION.md +40 -2
  241. package/server/lib/quota/credentials/providers.js +4 -0
  242. package/server/lib/quota/credentials/store.js +4 -4
  243. package/server/lib/quota/credentials/store.test.js +8 -8
  244. package/server/lib/quota/index.js +1 -0
  245. package/server/lib/quota/providers/cline-pass.js +125 -0
  246. package/server/lib/quota/providers/cline-pass.test.js +103 -0
  247. package/server/lib/quota/providers/exe-dev.js +75 -0
  248. package/server/lib/quota/providers/exe-dev.test.js +47 -0
  249. package/server/lib/quota/providers/hyper.js +118 -0
  250. package/server/lib/quota/providers/hyper.test.js +126 -0
  251. package/server/lib/quota/providers/index.js +22 -0
  252. package/server/lib/quota/providers/neuralwatt.js +3 -6
  253. package/server/lib/quota/providers/neuralwatt.test.js +7 -7
  254. package/server/lib/quota/providers/ollama-cloud.js +32 -1
  255. package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
  256. package/server/lib/quota/providers/opencode-go.js +3 -2
  257. package/server/lib/quota/providers/opencode-go.test.js +35 -6
  258. package/server/lib/quota/providers/openrouter.js +119 -24
  259. package/server/lib/quota/providers/openrouter.test.js +330 -0
  260. package/server/lib/quota/providers/xai.js +1 -1
  261. package/server/lib/quota/routes.js +2 -0
  262. package/server/lib/quota/utils/auth.js +1 -1
  263. package/server/lib/relay/DOCUMENTATION.md +56 -1
  264. package/server/lib/relay/cross-compat.test.js +26 -0
  265. package/server/lib/relay/downstream-scheduler.js +154 -0
  266. package/server/lib/relay/downstream-scheduler.test.js +162 -0
  267. package/server/lib/relay/e2ee.js +10 -8
  268. package/server/lib/relay/flow-control.test.js +177 -0
  269. package/server/lib/relay/host-client.js +38 -9
  270. package/server/lib/relay/host-client.test.js +9 -1
  271. package/server/lib/relay/host-lock.js +1 -1
  272. package/server/lib/relay/service.js +5 -5
  273. package/server/lib/relay/tunnel-codec.js +9 -0
  274. package/server/lib/relay/tunnel-host.js +69 -18
  275. package/server/lib/relay/tunnel-host.test.js +9 -1
  276. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
  277. package/server/lib/scheduled-tasks/routes.js +5 -5
  278. package/server/lib/scheduled-tasks/service.js +25 -25
  279. package/server/lib/scheduled-tasks/service.test.js +1 -1
  280. package/server/lib/security/bind-host.js +4 -4
  281. package/server/lib/security/bind-host.test.js +1 -1
  282. package/server/lib/security/request-security.js +11 -5
  283. package/server/lib/security/request-security.test.js +14 -14
  284. package/server/lib/session-assist/runtime.js +15 -18
  285. package/server/lib/session-folders/routes.js +2 -2
  286. package/server/lib/session-folders/routes.test.js +8 -8
  287. package/server/lib/session-goal/DOCUMENTATION.md +20 -7
  288. package/server/lib/session-goal/create.js +4 -2
  289. package/server/lib/session-goal/create.test.js +1 -1
  290. package/server/lib/session-goal/objectives.js +4 -4
  291. package/server/lib/session-goal/routes.js +1 -1
  292. package/server/lib/session-goal/runtime.js +91 -30
  293. package/server/lib/session-goal/runtime.test.js +428 -2
  294. package/server/lib/session-knowledge/runtime.js +11 -11
  295. package/server/lib/session-knowledge/runtime.test.js +5 -5
  296. package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
  297. package/server/lib/skills-catalog/cache.test.js +2 -2
  298. package/server/lib/skills-catalog/disk-cache.js +5 -5
  299. package/server/lib/skills-catalog/github-meta.test.js +2 -2
  300. package/server/lib/skills-catalog/install.js +3 -2
  301. package/server/lib/small-model/DOCUMENTATION.md +7 -2
  302. package/server/lib/small-model/call.js +18 -7
  303. package/server/lib/small-model/call.test.js +58 -6
  304. package/server/lib/small-model/index.js +12 -16
  305. package/server/lib/small-model/index.test.js +1 -1
  306. package/server/lib/small-model/resolve.js +1 -1
  307. package/server/lib/small-model/resolve.test.js +1 -1
  308. package/server/lib/small-model/routes.js +2 -1
  309. package/server/lib/small-model/runtime-providers.js +15 -7
  310. package/server/lib/small-model/runtime-providers.test.js +7 -1
  311. package/server/lib/system-prompt/runtime.js +3 -22
  312. package/server/lib/system-prompt/runtime.test.js +3 -3
  313. package/server/lib/terminal/DOCUMENTATION.md +14 -10
  314. package/server/lib/terminal/runtime.js +190 -31
  315. package/server/lib/terminal/runtime.test.js +572 -38
  316. package/server/lib/terminal/shells.js +20 -3
  317. package/server/lib/terminal/shells.test.js +33 -2
  318. package/server/lib/tts/base-url.js +3 -3
  319. package/server/lib/tts/routes.test.js +20 -20
  320. package/server/lib/ui-auth/ui-auth.js +12 -9
  321. package/server/lib/ui-auth/ui-auth.test.js +19 -3
  322. package/server/lib/ui-auth/ui-passkeys.js +7 -7
  323. package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
  324. package/server/lib/walkthrough/index.js +1 -0
  325. package/server/lib/walkthrough/jobs.test.js +1 -1
  326. package/server/lib/walkthrough/language.test.js +1 -1
  327. package/server/lib/walkthrough/model-settings.js +11 -15
  328. package/server/lib/walkthrough/model-settings.test.js +1 -1
  329. package/server/lib/walkthrough/prompt.js +3 -1
  330. package/server/lib/walkthrough/pull-request.test.js +5 -5
  331. package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
  332. package/server/lib/walkthrough/routes.test.js +26 -5
  333. package/server/lib/walkthrough/sources.js +23 -2
  334. package/server/lib/walkthrough/store.js +3 -3
  335. package/server/lib/walkthrough/store.test.js +1 -1
  336. package/server/opencode-proxy.test.js +2 -2
  337. package/server/proxy-headers.js +1 -1
  338. package/server/sse-routes.test.js +4 -4
  339. package/README.md.bak +0 -250
  340. package/bin/lib/DOCUMENTATION.md.bak +0 -163
  341. package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
  342. package/dist/assets/DiffView-DluiRSla.js +0 -4
  343. package/dist/assets/FilesView-Bdq8ppva.js +0 -17
  344. package/dist/assets/GitView-Znn7VJcP.js +0 -6
  345. package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
  346. package/dist/assets/MarkdownImageGallery-ClOvuZxC.js +0 -1
  347. package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
  348. package/dist/assets/OnboardingScreen-D2DG7K6b.js +0 -1
  349. package/dist/assets/PierreDiffViewer-B7m3f0d7.js +0 -157
  350. package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
  351. package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
  352. package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
  353. package/dist/assets/WalkthroughView-egQgxel3.js +0 -4
  354. package/dist/assets/contextPanelEmbeddedChat-C3fjDbdg.js +0 -1
  355. package/dist/assets/de-C1P6X5Rs.js +0 -9
  356. package/dist/assets/es-CDgaF1_t.js +0 -15
  357. package/dist/assets/fr-Bgxqvvz_.js +0 -13
  358. package/dist/assets/index-C57jT0Sa.css +0 -1
  359. package/dist/assets/index-DW_E3rnL.js +0 -7
  360. package/dist/assets/ja-BDRrEcNP.js +0 -15
  361. package/dist/assets/ko-Ba8sQV79.js +0 -15
  362. package/dist/assets/main-Bv5Pkngh.js +0 -928
  363. package/dist/assets/markdownCore-CZwzz5gw.js +0 -3
  364. package/dist/assets/patchFileDiff-yNfWXN4E.js +0 -10
  365. package/dist/assets/pl-Dioz29BI.js +0 -15
  366. package/dist/assets/pt-BR-DIs6ElwV.js +0 -15
  367. package/dist/assets/renderElectronMiniChatApp-BcKLHXKe.js +0 -2
  368. package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
  369. package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
  370. package/dist/assets/scheduledTasksApi-DLW0mkQK.js +0 -1
  371. package/dist/assets/tr-DFnMlPxY.js +0 -6
  372. package/dist/assets/uk-ZrCNjM7Y.js +0 -15
  373. package/dist/assets/useAppFontEffects-CjrntdL0.js +0 -7310
  374. package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
  375. package/dist/assets/useRootScrollLock-BHYPkrLv.js +0 -1
  376. package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
  377. package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
  378. package/dist/assets/zh-CN-iv_AV4EC.js +0 -15
  379. package/dist/assets/zh-TW-CzEUk38j.js +0 -15
  380. package/server/index.d.ts.bak +0 -39
  381. package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
  382. package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
  383. package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
  384. package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
  385. package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
  386. package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
  387. package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
  388. package/server/lib/git/DOCUMENTATION.md.bak +0 -193
  389. package/server/lib/github/DOCUMENTATION.md.bak +0 -183
  390. package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
  391. package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
  392. package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
  393. package/server/lib/notifications/APNS.md.bak +0 -135
  394. package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
  395. package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
  396. package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
  397. package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
  398. package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
  399. package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
  400. package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
  401. package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
  402. package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
  403. package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
  404. package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
  405. package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
  406. package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
  407. package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
  408. package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
  409. package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
  410. package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
  411. package/server/lib/text/DOCUMENTATION.md.bak +0 -35
  412. package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
  413. package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
  414. package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
  415. package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
@@ -0,0 +1,482 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+ import { createMessageQueueRuntime, parseQueuedItemInput } from './runtime.js';
6
+
7
+ const SESSION = 'ses_queue_test_1';
8
+ const DIRECTORY = '/repo';
9
+
10
+ const item = (overrides = {}) => ({
11
+ content: 'follow up',
12
+ text: 'follow up',
13
+ attachments: [],
14
+ sendConfig: { providerID: 'anthropic', modelID: 'claude', agent: 'build' },
15
+ ...overrides,
16
+ });
17
+
18
+ const tempDirs = [];
19
+ const makeDataDir = () => {
20
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-message-queue-'));
21
+ tempDirs.push(dir);
22
+ return dir;
23
+ };
24
+
25
+ afterEach(() => {
26
+ vi.useRealTimers();
27
+ for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
28
+ });
29
+
30
+ /**
31
+ * A fake OpenCode: status map, message tail, command list, and a log of every
32
+ * prompt/command it received.
33
+ */
34
+ const createOpenCode = () => {
35
+ const state = {
36
+ statuses: {},
37
+ tail: [],
38
+ commands: [],
39
+ sent: [],
40
+ failNext: null,
41
+ };
42
+ const fetchImpl = vi.fn(async (url, init = {}) => {
43
+ const { pathname } = new URL(url);
44
+ const method = init.method ?? 'GET';
45
+ if (state.failNext && state.failNext.test(pathname)) {
46
+ state.failNext = null;
47
+ return new Response('boom', { status: 500 });
48
+ }
49
+ if (pathname === '/session/status') return Response.json(state.statuses);
50
+ if (pathname.endsWith('/message')) return Response.json(state.tail);
51
+ if (pathname === '/command') return Response.json(state.commands);
52
+ if (method === 'POST' && (pathname.endsWith('/prompt_async') || pathname.endsWith('/command'))) {
53
+ state.sent.push({ path: pathname, body: JSON.parse(init.body) });
54
+ return new Response(null, { status: 204 });
55
+ }
56
+ return new Response('not found', { status: 404 });
57
+ });
58
+ return { state, fetchImpl };
59
+ };
60
+
61
+ const createRuntime = ({ dataDir = makeDataDir(), openCode = createOpenCode(), knowledge = null, retryDelayMs } = {}) => {
62
+ let eventHandler = () => {};
63
+ let statusHandler = () => {};
64
+ const broadcasts = [];
65
+ const promptSent = [];
66
+ const options = {
67
+ globalEventHub: {
68
+ subscribeEvent(handler) { eventHandler = handler; return () => {}; },
69
+ subscribeStatus(handler) { statusHandler = handler; return () => {}; },
70
+ },
71
+ buildOpenCodeUrl: (fetchPath) => `http://opencode.test${fetchPath}`,
72
+ getOpenCodeAuthHeaders: () => ({}),
73
+ sessionKnowledgeRuntime: knowledge,
74
+ broadcastGlobalUiEvent: (event) => broadcasts.push(event),
75
+ onPromptSent: (sessionId) => promptSent.push(sessionId),
76
+ dataDir,
77
+ fetchImpl: openCode.fetchImpl,
78
+ dispatchQuietMs: 0,
79
+ abortHoldMs: 50,
80
+ };
81
+ if (retryDelayMs) options.retryDelayMs = retryDelayMs;
82
+ const runtime = createMessageQueueRuntime(options);
83
+ return {
84
+ runtime,
85
+ openCode,
86
+ dataDir,
87
+ broadcasts,
88
+ promptSent,
89
+ emit: (payload, directory = DIRECTORY) => eventHandler({ payload, directory }),
90
+ connect: () => statusHandler({ type: 'connect' }),
91
+ };
92
+ };
93
+
94
+ const settle = async (ms = 30) => {
95
+ await new Promise((resolve) => setTimeout(resolve, ms));
96
+ };
97
+
98
+ describe('parseQueuedItemInput', () => {
99
+ it('rejects an item the server could not deliver later', () => {
100
+ expect(() => parseQueuedItemInput({ content: 'x' })).toThrow(TypeError);
101
+ expect(() => parseQueuedItemInput(item({ content: '', text: '' }))).toThrow(TypeError);
102
+ expect(() => parseQueuedItemInput(item({ attachments: [{ filename: 'a.png' }] }))).toThrow(TypeError);
103
+ });
104
+
105
+ it('keeps delivery fields and trims blank edges of the content', () => {
106
+ const parsed = parseQueuedItemInput(item({ content: '\n\nhello\n', text: 'hello', agentMention: 'reviewer' }));
107
+ expect(parsed).toEqual({
108
+ content: 'hello',
109
+ text: 'hello',
110
+ agentMention: 'reviewer',
111
+ attachments: [],
112
+ context: [],
113
+ sendConfig: { providerID: 'anthropic', modelID: 'claude', agent: 'build' },
114
+ });
115
+ });
116
+
117
+ it('keeps captured context and rejects a malformed part', () => {
118
+ const context = [
119
+ { kind: 'context', text: 'Comment on `a.ts`', metadata: { aiworkerContext: { kind: 'code-comment' } }, instructions: '' },
120
+ { kind: 'instruction', text: 'use the skill' },
121
+ { kind: 'synthetic', text: 'conflict payload' },
122
+ ];
123
+ expect(parseQueuedItemInput(item({ context })).context).toEqual([
124
+ { kind: 'context', text: 'Comment on `a.ts`', metadata: { aiworkerContext: { kind: 'code-comment' } } },
125
+ { kind: 'instruction', text: 'use the skill' },
126
+ { kind: 'synthetic', text: 'conflict payload' },
127
+ ]);
128
+ expect(() => parseQueuedItemInput(item({ context: [{ kind: 'context', text: 'no metadata' }] }))).toThrow(TypeError);
129
+ expect(() => parseQueuedItemInput(item({ context: [{ kind: 'other', text: 'x' }] }))).toThrow(TypeError);
130
+ });
131
+
132
+ it('accepts an item that is only context', () => {
133
+ const parsed = parseQueuedItemInput(item({ content: '', text: '', context: [{ kind: 'synthetic', text: 'just context' }] }));
134
+ expect(parsed.text).toBe('');
135
+ expect(parsed.context).toHaveLength(1);
136
+ });
137
+ });
138
+
139
+ describe('message queue runtime', () => {
140
+ it('delivers the head of the queue when the session goes idle, in order', async () => {
141
+ const { runtime, openCode, emit, promptSent, broadcasts } = createRuntime();
142
+ runtime.start();
143
+ openCode.state.statuses = { [SESSION]: { type: 'busy' } };
144
+
145
+ await runtime.enqueue(SESSION, DIRECTORY, item({ content: 'first', text: 'first' }));
146
+ await runtime.enqueue(SESSION, DIRECTORY, item({ content: 'second', text: 'second' }));
147
+ await settle();
148
+ expect(openCode.state.sent).toHaveLength(0);
149
+
150
+ openCode.state.statuses = {};
151
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
152
+ await settle();
153
+
154
+ expect(openCode.state.sent).toHaveLength(1);
155
+ expect(openCode.state.sent[0].path).toBe(`/session/${SESSION}/prompt_async`);
156
+ expect(openCode.state.sent[0].body).toEqual({
157
+ model: { providerID: 'anthropic', modelID: 'claude' },
158
+ agent: 'build',
159
+ parts: [{ type: 'text', text: 'first' }],
160
+ });
161
+ expect(promptSent).toEqual([SESSION]);
162
+ expect(runtime.sessionSnapshot(SESSION).items.map((entry) => entry.content)).toEqual(['second']);
163
+ // Clients learned about the in-flight item and then the removal.
164
+ expect(broadcasts.at(-1)).toMatchObject({
165
+ type: 'aiworker:message-queue.updated',
166
+ properties: { session: { sessionId: SESSION, sendingId: null } },
167
+ });
168
+
169
+ // The next turn: busy, then idle again — the second message goes out.
170
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'busy' } } });
171
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
172
+ await settle();
173
+ expect(openCode.state.sent).toHaveLength(2);
174
+ expect(runtime.sessionSnapshot(SESSION).items).toEqual([]);
175
+ });
176
+
177
+ it('does not send into a running turn even when the status event says idle', async () => {
178
+ const { runtime, openCode, emit } = createRuntime();
179
+ runtime.start();
180
+ openCode.state.tail = [{ info: { role: 'assistant', time: { created: 1 } } }];
181
+ await runtime.enqueue(SESSION, DIRECTORY, item());
182
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
183
+ await settle();
184
+ expect(openCode.state.sent).toHaveLength(0);
185
+
186
+ // The reply completes: that alone drains the queue (a missed idle event
187
+ // must not strand it).
188
+ openCode.state.tail = [{ info: { role: 'assistant', time: { created: 1, completed: 2 } } }];
189
+ emit({ type: 'message.updated', properties: { info: { role: 'assistant', sessionID: SESSION, time: { created: 1, completed: 2 } } } });
190
+ await settle();
191
+ expect(openCode.state.sent).toHaveLength(1);
192
+ });
193
+
194
+ it('treats an unreachable OpenCode as unknown, not idle', async () => {
195
+ const { runtime, openCode, emit } = createRuntime({ retryDelayMs: () => 10 });
196
+ runtime.start();
197
+ await runtime.enqueue(SESSION, DIRECTORY, item());
198
+ openCode.state.failNext = /\/session\/status$/;
199
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
200
+ await settle(5);
201
+ expect(openCode.state.sent).toHaveLength(0);
202
+ // Retried after the status fetch recovers.
203
+ await settle(40);
204
+ expect(openCode.state.sent).toHaveLength(1);
205
+ });
206
+
207
+ it('keeps a failed item and retries with backoff', async () => {
208
+ const { runtime, openCode, emit, broadcasts } = createRuntime({ retryDelayMs: () => 20 });
209
+ runtime.start();
210
+ await runtime.enqueue(SESSION, DIRECTORY, item());
211
+ openCode.state.failNext = /prompt_async$/;
212
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
213
+ await settle(10);
214
+ expect(openCode.state.sent).toHaveLength(0);
215
+ expect(runtime.sessionSnapshot(SESSION).items).toHaveLength(1);
216
+ expect(runtime.sessionSnapshot(SESSION).sendingId).toBeNull();
217
+ expect(broadcasts.at(-1).properties.session.sendingId).toBeNull();
218
+ await settle(40);
219
+ expect(openCode.state.sent).toHaveLength(1);
220
+ expect(runtime.sessionSnapshot(SESSION).items).toHaveLength(0);
221
+ });
222
+
223
+ it('holds delivery briefly after a user abort', async () => {
224
+ const { runtime, openCode, emit } = createRuntime();
225
+ runtime.start();
226
+ await runtime.enqueue(SESSION, DIRECTORY, item());
227
+ emit({ type: 'message.updated', properties: { info: { role: 'assistant', sessionID: SESSION, error: { name: 'MessageAbortedError' } } } });
228
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
229
+ await settle(10);
230
+ expect(openCode.state.sent).toHaveLength(0);
231
+ await settle(80);
232
+ expect(openCode.state.sent).toHaveLength(1);
233
+ });
234
+
235
+ it('honors a hold until it is released', async () => {
236
+ const { runtime, openCode, emit } = createRuntime();
237
+ runtime.start();
238
+ await runtime.enqueue(SESSION, DIRECTORY, item());
239
+ runtime.setHold(SESSION, true, 60_000);
240
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
241
+ await settle();
242
+ expect(openCode.state.sent).toHaveLength(0);
243
+
244
+ runtime.setHold(SESSION, false);
245
+ await settle();
246
+ expect(openCode.state.sent).toHaveLength(1);
247
+ });
248
+
249
+ it('survives a restart and delivers once OpenCode reconnects', async () => {
250
+ const dataDir = makeDataDir();
251
+ const first = createRuntime({ dataDir });
252
+ first.runtime.start();
253
+ first.openCode.state.statuses = { [SESSION]: { type: 'busy' } };
254
+ await first.runtime.enqueue(SESSION, DIRECTORY, item({ content: 'persisted', text: 'persisted' }));
255
+ await first.runtime.flush();
256
+ first.runtime.stop();
257
+
258
+ const second = createRuntime({ dataDir });
259
+ second.runtime.start();
260
+ await second.runtime.load();
261
+ expect(second.runtime.sessionSnapshot(SESSION).items.map((entry) => entry.content)).toEqual(['persisted']);
262
+ second.connect();
263
+ await settle();
264
+ expect(second.openCode.state.sent).toHaveLength(1);
265
+ expect(second.openCode.state.sent[0].body.parts).toEqual([{ type: 'text', text: 'persisted' }]);
266
+ });
267
+
268
+ it('moves an unreadable queue file aside instead of treating it as empty', async () => {
269
+ const dataDir = makeDataDir();
270
+ fs.writeFileSync(path.join(dataDir, 'message-queue.json'), '{ not json');
271
+ const { runtime } = createRuntime({ dataDir });
272
+ await runtime.load();
273
+ expect(runtime.snapshot().sessions).toEqual([]);
274
+ expect(fs.readdirSync(dataDir).some((name) => name.startsWith('message-queue.json.corrupt-'))).toBe(true);
275
+ });
276
+
277
+ it('refuses to remove or take the item currently being sent', async () => {
278
+ const { runtime, openCode, emit } = createRuntime();
279
+ runtime.start();
280
+ let release;
281
+ // status map, message tail, then the prompt itself (held open until released)
282
+ openCode.fetchImpl.mockImplementationOnce(async () => Response.json({}))
283
+ .mockImplementationOnce(async () => Response.json([]))
284
+ .mockImplementationOnce(() => new Promise((resolve) => { release = () => resolve(new Response(null, { status: 204 })); }));
285
+ const { itemId } = await runtime.enqueue(SESSION, DIRECTORY, item());
286
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
287
+ await settle();
288
+ expect(runtime.sessionSnapshot(SESSION).sendingId).toBe(itemId);
289
+
290
+ await expect(runtime.remove(SESSION, itemId)).rejects.toMatchObject({ status: 409 });
291
+ await expect(runtime.take(SESSION, itemId)).rejects.toMatchObject({ status: 409 });
292
+ const taken = await runtime.takeAll(SESSION);
293
+ expect(taken.items).toEqual([]);
294
+ expect(runtime.sessionSnapshot(SESSION).items).toHaveLength(1);
295
+
296
+ release();
297
+ await settle();
298
+ expect(runtime.sessionSnapshot(SESSION).items).toHaveLength(0);
299
+ });
300
+
301
+ it('take hands back the full payload and leaves the rest queued', async () => {
302
+ const { runtime } = createRuntime();
303
+ runtime.start();
304
+ const attachment = { id: 'a1', filename: 'shot.png', mimeType: 'image/png', size: 3, source: 'local', dataUrl: 'data:image/png;base64,AAA=' };
305
+ const first = await runtime.enqueue(SESSION, DIRECTORY, item({ content: 'with image', attachments: [attachment] }));
306
+ await runtime.enqueue(SESSION, DIRECTORY, item({ content: 'plain' }));
307
+
308
+ expect(runtime.sessionSnapshot(SESSION).items[0].attachments[0]).not.toHaveProperty('dataUrl');
309
+ const taken = await runtime.take(SESSION, first.itemId);
310
+ expect(taken.item.attachments[0].dataUrl).toBe(attachment.dataUrl);
311
+ expect(runtime.sessionSnapshot(SESSION).items.map((entry) => entry.content)).toEqual(['plain']);
312
+
313
+ const all = await runtime.takeAll(SESSION);
314
+ expect(all.items.map((entry) => entry.content)).toEqual(['plain']);
315
+ expect(runtime.snapshot().sessions).toEqual([]);
316
+ });
317
+
318
+ it('names the directory in the broadcast that empties a queue', async () => {
319
+ // The UI keys its projection by directory; without it the client cannot
320
+ // tell which queue just delivered its last message and keeps showing it.
321
+ const { runtime, emit, broadcasts, openCode } = createRuntime();
322
+ runtime.start();
323
+ await runtime.enqueue(SESSION, DIRECTORY, item());
324
+ openCode.state.statuses = {};
325
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
326
+ await settle();
327
+
328
+ expect(openCode.state.sent).toHaveLength(1);
329
+ expect(runtime.snapshot().sessions).toEqual([]);
330
+ expect(broadcasts.at(-1).properties.session).toEqual({ sessionId: SESSION, directory: DIRECTORY, items: [], sendingId: null });
331
+ });
332
+
333
+ it('reorders only with a complete permutation', async () => {
334
+ const { runtime } = createRuntime();
335
+ runtime.start();
336
+ const a = await runtime.enqueue(SESSION, DIRECTORY, item({ content: 'a' }));
337
+ const b = await runtime.enqueue(SESSION, DIRECTORY, item({ content: 'b' }));
338
+ await expect(runtime.reorder(SESSION, [b.itemId])).rejects.toThrow(TypeError);
339
+ await runtime.reorder(SESSION, [b.itemId, a.itemId]);
340
+ expect(runtime.sessionSnapshot(SESSION).items.map((entry) => entry.content)).toEqual(['b', 'a']);
341
+ });
342
+
343
+ it('drops the queue of a deleted session', async () => {
344
+ const { runtime, emit, broadcasts } = createRuntime();
345
+ runtime.start();
346
+ await runtime.enqueue(SESSION, DIRECTORY, item());
347
+ emit({ type: 'session.deleted', properties: { info: { id: SESSION } } });
348
+ expect(runtime.snapshot().sessions).toEqual([]);
349
+ expect(broadcasts.at(-1).properties.session).toMatchObject({ sessionId: SESSION, items: [] });
350
+ });
351
+
352
+ it('dispatches a queued slash command through the command endpoint', async () => {
353
+ const { runtime, openCode, emit } = createRuntime();
354
+ runtime.start();
355
+ openCode.state.commands = [{ name: 'review' }];
356
+ await runtime.enqueue(SESSION, DIRECTORY, item({ content: '/review src', text: '/review src', sendConfig: { providerID: 'p', modelID: 'm', agent: 'build', variant: 'max' } }));
357
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
358
+ await settle();
359
+ expect(openCode.state.sent).toHaveLength(1);
360
+ expect(openCode.state.sent[0].path).toBe(`/session/${SESSION}/command`);
361
+ expect(openCode.state.sent[0].body).toEqual({ command: 'review', arguments: 'src', model: 'p/m', agent: 'build', variant: 'max' });
362
+ });
363
+
364
+ it('delivers captured context as synthetic parts, instructions first, before project knowledge', async () => {
365
+ const knowledge = {
366
+ resolvePendingForSession: async () => ({ text: 'pinned notes', signature: 'sig-1' }),
367
+ recordDelivered: async () => {},
368
+ };
369
+ const { runtime, openCode, emit } = createRuntime({ knowledge });
370
+ runtime.start();
371
+ const metadata = { aiworkerContext: { kind: 'github-pr', number: 7, title: 'PR', url: 'https://x/pr/7' } };
372
+ await runtime.enqueue(SESSION, DIRECTORY, item({
373
+ agentMention: 'reviewer',
374
+ attachments: [{ id: 'a', filename: 'f.txt', mimeType: 'text/plain', size: 1, source: 'local', dataUrl: 'data:text/plain,hi' }],
375
+ context: [
376
+ { kind: 'context', text: 'the diff', metadata, instructions: 'how to read it' },
377
+ { kind: 'synthetic', text: 'conflict payload' },
378
+ { kind: 'instruction', text: 'use the skill' },
379
+ ],
380
+ }));
381
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
382
+ await settle();
383
+ expect(openCode.state.sent[0].body.parts).toEqual([
384
+ { type: 'text', text: 'follow up' },
385
+ { type: 'file', mime: 'text/plain', filename: 'f.txt', url: 'data:text/plain,hi' },
386
+ { type: 'text', text: 'how to read it', synthetic: true },
387
+ { type: 'text', text: 'the diff', synthetic: true, metadata },
388
+ { type: 'text', text: 'conflict payload', synthetic: true },
389
+ { type: 'text', text: 'use the skill', synthetic: true },
390
+ { type: 'text', text: 'pinned notes', synthetic: true },
391
+ { type: 'agent', name: 'reviewer' },
392
+ ]);
393
+ });
394
+
395
+ it('keeps files on the command route, which is all that route accepts', async () => {
396
+ const { runtime, openCode, emit } = createRuntime();
397
+ runtime.start();
398
+ openCode.state.commands = [{ name: 'review' }];
399
+ await runtime.enqueue(SESSION, DIRECTORY, item({
400
+ content: '/review',
401
+ text: '/review',
402
+ attachments: [{ id: 'a', filename: 'f.txt', mimeType: 'text/plain', size: 1, source: 'local', dataUrl: 'data:text/plain,hi' }],
403
+ }));
404
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
405
+ await settle();
406
+ expect(openCode.state.sent[0].path).toBe(`/session/${SESSION}/command`);
407
+ expect(openCode.state.sent[0].body.parts).toEqual([{ type: 'file', mime: 'text/plain', filename: 'f.txt', url: 'data:text/plain,hi' }]);
408
+ });
409
+
410
+ it('sends a command queued with context as its expanded prompt, context included', async () => {
411
+ // The command route rejects text parts, so a command with captured
412
+ // context takes the prompt route with the template expanded, exactly as
413
+ // the composer does.
414
+ const { runtime, openCode, emit } = createRuntime();
415
+ runtime.start();
416
+ openCode.state.commands = [{ name: 'review', source: 'command', template: 'Review $1 with focus on $2' }];
417
+ const metadata = { aiworkerContext: { kind: 'chat-quote', quote: 'q', text: 'why?' } };
418
+ await runtime.enqueue(SESSION, DIRECTORY, item({
419
+ content: '/review src "error handling"',
420
+ text: '/review src "error handling"',
421
+ context: [{ kind: 'context', text: 'quoted', metadata }],
422
+ }));
423
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
424
+ await settle();
425
+ expect(openCode.state.sent[0].path).toBe(`/session/${SESSION}/prompt_async`);
426
+ expect(openCode.state.sent[0].body.parts).toEqual([
427
+ { type: 'text', text: 'Review src with focus on error handling' },
428
+ { type: 'text', text: 'quoted', synthetic: true, metadata },
429
+ ]);
430
+ });
431
+
432
+ it('sends a skill queued with context as an explicit invocation, context included', async () => {
433
+ const { runtime, openCode, emit } = createRuntime();
434
+ runtime.start();
435
+ openCode.state.commands = [{ name: 'grill', source: 'skill', template: 'skill body' }];
436
+ await runtime.enqueue(SESSION, DIRECTORY, item({
437
+ content: '/grill auth',
438
+ text: '/grill auth',
439
+ context: [{ kind: 'synthetic', text: 'focus on tests' }],
440
+ }));
441
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
442
+ await settle();
443
+ expect(openCode.state.sent[0].path).toBe(`/session/${SESSION}/prompt_async`);
444
+ expect(openCode.state.sent[0].body.parts).toEqual([
445
+ { type: 'text', text: '/grill auth' },
446
+ { type: 'text', text: 'focus on tests', synthetic: true },
447
+ { type: 'text', text: 'The user explicitly invoked the grill skill. Use the corresponding skill tool to handle this request.', synthetic: true },
448
+ ]);
449
+ });
450
+
451
+ it('keeps captured context out of snapshots and broadcasts, and hands it back on take', async () => {
452
+ const { runtime, broadcasts } = createRuntime();
453
+ runtime.start();
454
+ const context = [{ kind: 'synthetic', text: 'a large diff' }];
455
+ const { itemId } = await runtime.enqueue(SESSION, DIRECTORY, item({ context }));
456
+ expect(runtime.sessionSnapshot(SESSION).items[0]).not.toHaveProperty('context');
457
+ expect(runtime.sessionSnapshot(SESSION).items[0].text).toBe('follow up');
458
+ expect(broadcasts.at(-1).properties.session.items[0]).not.toHaveProperty('context');
459
+ const taken = await runtime.take(SESSION, itemId);
460
+ expect(taken.item.context).toEqual(context);
461
+ });
462
+
463
+ it('attaches pending project knowledge and records its delivery', async () => {
464
+ const recorded = [];
465
+ const knowledge = {
466
+ resolvePendingForSession: async () => ({ text: 'pinned notes', signature: 'sig-1' }),
467
+ recordDelivered: async (sessionId, directory, signature) => { recorded.push({ sessionId, directory, signature }); },
468
+ };
469
+ const { runtime, openCode, emit } = createRuntime({ knowledge });
470
+ runtime.start();
471
+ await runtime.enqueue(SESSION, DIRECTORY, item({ agentMention: 'reviewer', attachments: [{ id: 'a', filename: 'f.txt', mimeType: 'text/plain', size: 1, source: 'local', dataUrl: 'data:text/plain,hi' }] }));
472
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
473
+ await settle();
474
+ expect(openCode.state.sent[0].body.parts).toEqual([
475
+ { type: 'text', text: 'follow up' },
476
+ { type: 'file', mime: 'text/plain', filename: 'f.txt', url: 'data:text/plain,hi' },
477
+ { type: 'text', text: 'pinned notes', synthetic: true },
478
+ { type: 'agent', name: 'reviewer' },
479
+ ]);
480
+ expect(recorded).toEqual([{ sessionId: SESSION, directory: DIRECTORY, signature: 'sig-1' }]);
481
+ });
482
+ });
@@ -0,0 +1,23 @@
1
+ import net from 'node:net';
2
+
3
+ // Node caps each happy-eyeballs connect attempt at 250ms by default
4
+ // (autoSelectFamilyAttemptTimeout). TCP handshakes to provider endpoints that are
5
+ // geographically distant routinely take 300-1500ms, so fetch() from a Node process
6
+ // aborts every attempt (ETIMEDOUT) and surfaces "fetch failed" even though the host
7
+ // is reachable — e.g. the z.ai quota endpoint from an IPv4-only egress (#3399).
8
+ //
9
+ // Every Node process entrypoint that performs provider fetches raises the
10
+ // per-attempt cap. Family autoselection itself stays enabled, so the IPv6→IPv4
11
+ // fallback and ::1/localhost servers keep working; disabling it instead breaks
12
+ // local MCP servers. Runtimes without the setter (Bun's fetch path, older Node)
13
+ // are a no-op.
14
+ export const CONNECT_ATTEMPT_TIMEOUT_MS = 5_000;
15
+
16
+ export const applyConnectAttemptTimeout = (netModule = net) => {
17
+ try {
18
+ netModule.setDefaultAutoSelectFamilyAttemptTimeout(CONNECT_ATTEMPT_TIMEOUT_MS);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ };
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import net from 'node:net';
3
+ import { spawnSync } from 'node:child_process';
4
+
5
+ import { applyConnectAttemptTimeout, CONNECT_ATTEMPT_TIMEOUT_MS } from './network-defaults.js';
6
+
7
+ describe('applyConnectAttemptTimeout', () => {
8
+ it('initializes the server entrypoint in a fresh Node process', () => {
9
+ const serverUrl = new URL('../index.js', import.meta.url).href;
10
+ const result = spawnSync('node', ['--input-type=module', '--eval', `
11
+ import net from 'node:net';
12
+ import assert from 'node:assert/strict';
13
+ const family = net.getDefaultAutoSelectFamily();
14
+ net.setDefaultAutoSelectFamilyAttemptTimeout(250);
15
+ await import(${JSON.stringify(serverUrl)});
16
+ assert.equal(net.getDefaultAutoSelectFamilyAttemptTimeout(), 5000);
17
+ assert.equal(net.getDefaultAutoSelectFamily(), family);
18
+ process.exit(0);
19
+ `], { encoding: 'utf8', timeout: 15_000, windowsHide: true });
20
+ expect(result.status, result.stderr).toBe(0);
21
+ });
22
+
23
+ it('raises the per-attempt connect timeout on runtimes that expose the setter', () => {
24
+ const previous = net.getDefaultAutoSelectFamilyAttemptTimeout();
25
+ try {
26
+ expect(applyConnectAttemptTimeout()).toBe(true);
27
+ expect(net.getDefaultAutoSelectFamilyAttemptTimeout()).toBe(CONNECT_ATTEMPT_TIMEOUT_MS);
28
+ } finally {
29
+ net.setDefaultAutoSelectFamilyAttemptTimeout(previous);
30
+ }
31
+ });
32
+
33
+ it('is a no-op on runtimes without the setter', () => {
34
+ expect(applyConnectAttemptTimeout({})).toBe(false);
35
+ });
36
+
37
+ it('survives a throwing setter', () => {
38
+ const setDefaultAutoSelectFamilyAttemptTimeout = vi.fn(() => {
39
+ throw new Error('not supported');
40
+ });
41
+ expect(applyConnectAttemptTimeout({ setDefaultAutoSelectFamilyAttemptTimeout })).toBe(false);
42
+ });
43
+
44
+ it('leaves family autoselection itself untouched', () => {
45
+ const setDefaultAutoSelectFamily = vi.fn();
46
+ const setDefaultAutoSelectFamilyAttemptTimeout = vi.fn();
47
+ applyConnectAttemptTimeout({ setDefaultAutoSelectFamily, setDefaultAutoSelectFamilyAttemptTimeout });
48
+ expect(setDefaultAutoSelectFamilyAttemptTimeout).toHaveBeenCalledTimes(1);
49
+ expect(setDefaultAutoSelectFamilyAttemptTimeout).toHaveBeenCalledWith(CONNECT_ATTEMPT_TIMEOUT_MS);
50
+ expect(setDefaultAutoSelectFamily).not.toHaveBeenCalled();
51
+ });
52
+ });
@@ -5,7 +5,7 @@
5
5
  // - Relay (default): POST tokens + generic text to the central Cloudflare relay, which
6
6
  // holds the single project APNs key and signs+sends — so users configure nothing.
7
7
  // - Direct (fallback): sign an ES256 JWT with Node crypto and send over HTTP/2 ourselves,
8
- // for self-hosters who set OPENCHAMBER_APNS_* and OPENCHAMBER_PUSH_RELAY_DISABLED=true.
8
+ // for self-hosters who set AIWORKER_APNS_* and AIWORKER_PUSH_RELAY_DISABLED=true.
9
9
  // Wired into the same trigger fanout as web push (see runtime.js); the relay carries only
10
10
  // generic, model-based text (no session content) — see APNS.md.
11
11
 
@@ -19,8 +19,8 @@ const APNS_HOST_PRODUCTION = 'https://api.push.apple.com';
19
19
  const APNS_HOST_SANDBOX = 'https://api.sandbox.push.apple.com';
20
20
  // APNs rejects auth tokens older than 1h; refresh well inside that window.
21
21
  const JWT_TTL_MS = 50 * 60 * 1000;
22
- const DEFAULT_BUNDLE_ID = 'com.openchamber.app';
23
- const DEFAULT_RELAY_URL = 'https://api.openchamber.dev/v1/push/send';
22
+ const DEFAULT_BUNDLE_ID = 'com.aiworker.app';
23
+ const DEFAULT_RELAY_URL = 'https://api.aiworker.dev/v1/push/send';
24
24
  const MAX_TOKENS_PER_SESSION = 10;
25
25
  // APNs reasons that mean the token is permanently invalid → drop it.
26
26
  const DEAD_TOKEN_REASONS = new Set(['BadDeviceToken', 'Unregistered', 'DeviceTokenNotForTopic']);
@@ -227,18 +227,18 @@ export const createApnsRuntime = (deps) => {
227
227
  // ---------------------------------------------------------------------------
228
228
 
229
229
  const resolveApnsConfig = async () => {
230
- let keyId = trimmedEnv('OPENCHAMBER_APNS_KEY_ID');
231
- let teamId = trimmedEnv('OPENCHAMBER_APNS_TEAM_ID');
232
- let bundleId = trimmedEnv('OPENCHAMBER_APNS_BUNDLE_ID');
233
- let environment = (trimmedEnv('OPENCHAMBER_APNS_ENVIRONMENT') || '').toLowerCase();
234
- let p8 = normalizePem(process.env.OPENCHAMBER_APNS_P8 || '');
230
+ let keyId = trimmedEnv('AIWORKER_APNS_KEY_ID');
231
+ let teamId = trimmedEnv('AIWORKER_APNS_TEAM_ID');
232
+ let bundleId = trimmedEnv('AIWORKER_APNS_BUNDLE_ID');
233
+ let environment = (trimmedEnv('AIWORKER_APNS_ENVIRONMENT') || '').toLowerCase();
234
+ let p8 = normalizePem(process.env.AIWORKER_APNS_P8 || '');
235
235
 
236
- const p8Path = trimmedEnv('OPENCHAMBER_APNS_P8_PATH');
236
+ const p8Path = trimmedEnv('AIWORKER_APNS_P8_PATH');
237
237
  if (!p8 && p8Path) {
238
238
  try {
239
239
  p8 = (await fsPromises.readFile(p8Path, 'utf8')).trim();
240
240
  } catch (error) {
241
- console.warn('[APNs] Failed to read OPENCHAMBER_APNS_P8_PATH:', error?.message ?? error);
241
+ console.warn('[APNs] Failed to read AIWORKER_APNS_P8_PATH:', error?.message ?? error);
242
242
  }
243
243
  }
244
244
 
@@ -377,15 +377,15 @@ export const createApnsRuntime = (deps) => {
377
377
  // Relay mode (default): the single APNs key lives in the central Cloudflare relay, not on
378
378
  // each user's server — so users configure nothing. The server just POSTs device tokens +
379
379
  // generic text; the relay signs + sends and reports which tokens to drop. Direct mode (below)
380
- // is the fallback for self-hosters who set OPENCHAMBER_APNS_* and disable the relay.
380
+ // is the fallback for self-hosters who set AIWORKER_APNS_* and disable the relay.
381
381
  const resolveRelayConfig = () => {
382
- if (trimmedEnv('OPENCHAMBER_PUSH_RELAY_DISABLED') === 'true') return null;
383
- const url = trimmedEnv('OPENCHAMBER_PUSH_RELAY_URL') || DEFAULT_RELAY_URL;
384
- const override = (trimmedEnv('OPENCHAMBER_APNS_ENVIRONMENT') || '').toLowerCase();
382
+ if (trimmedEnv('AIWORKER_PUSH_RELAY_DISABLED') === 'true') return null;
383
+ const url = trimmedEnv('AIWORKER_PUSH_RELAY_URL') || DEFAULT_RELAY_URL;
384
+ const override = (trimmedEnv('AIWORKER_APNS_ENVIRONMENT') || '').toLowerCase();
385
385
  return {
386
386
  url,
387
387
  registerUrl: url.replace(/\/send$/, '/register-token'),
388
- // Explicit OPENCHAMBER_APNS_ENVIRONMENT forces every send to that environment; when
388
+ // Explicit AIWORKER_APNS_ENVIRONMENT forces every send to that environment; when
389
389
  // unset (null), each token is delivered to the environment it registered with.
390
390
  environment: override === 'sandbox' ? 'sandbox' : override === 'production' ? 'production' : null,
391
391
  };
@@ -393,7 +393,7 @@ export const createApnsRuntime = (deps) => {
393
393
 
394
394
  const sendViaRelay = async (deviceTokens, payload, relay, environment) => {
395
395
  const tokens = deviceTokens.slice(0, 100);
396
- const title = typeof payload?.title === 'string' && payload.title.length > 0 ? payload.title : 'OpenChamber';
396
+ const title = typeof payload?.title === 'string' && payload.title.length > 0 ? payload.title : 'AiWorker';
397
397
  const { privateKey, publicJwk } = await getOrCreateRelayKeypair();
398
398
  const ts = Date.now();
399
399
  // Sign over the same canonical form the relay verifies: ts.sortedTokens.title.
@@ -438,7 +438,7 @@ export const createApnsRuntime = (deps) => {
438
438
  if (!warnedUnconfigured) {
439
439
  warnedUnconfigured = true;
440
440
  console.warn(
441
- '[APNs] Relay disabled and no direct config; set OPENCHAMBER_APNS_KEY_ID / OPENCHAMBER_APNS_TEAM_ID / OPENCHAMBER_APNS_P8 for direct send.',
441
+ '[APNs] Relay disabled and no direct config; set AIWORKER_APNS_KEY_ID / AIWORKER_APNS_TEAM_ID / AIWORKER_APNS_P8 for direct send.',
442
442
  );
443
443
  }
444
444
  return;