@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,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
+ });
@@ -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;
@@ -6,7 +6,7 @@ import { createApnsRuntime } from './apns-runtime.js';
6
6
  // A real P-256 key so the ES256 signing path (direct mode) runs for real.
7
7
  const { privateKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' });
8
8
  const P8 = privateKey.export({ type: 'pkcs8', format: 'pem' }).toString();
9
- const APNS_CONFIG = { keyId: 'KEY123', teamId: 'TEAM123', p8: P8, bundleId: 'com.openchamber.app', environment: 'sandbox' };
9
+ const APNS_CONFIG = { keyId: 'KEY123', teamId: 'TEAM123', p8: P8, bundleId: 'com.aiworker.app', environment: 'sandbox' };
10
10
 
11
11
  // In-memory fs so add-then-read reflects within a test.
12
12
  const createMemoryFs = () => {
@@ -67,9 +67,9 @@ const isSend = ([url]) => String(url) === 'https://relay.test/v1/push/send';
67
67
 
68
68
  afterEach(() => {
69
69
  vi.unstubAllGlobals();
70
- delete process.env.OPENCHAMBER_PUSH_RELAY_URL;
71
- delete process.env.OPENCHAMBER_PUSH_RELAY_DISABLED;
72
- delete process.env.OPENCHAMBER_APNS_ENVIRONMENT;
70
+ delete process.env.AIWORKER_PUSH_RELAY_URL;
71
+ delete process.env.AIWORKER_PUSH_RELAY_DISABLED;
72
+ delete process.env.AIWORKER_APNS_ENVIRONMENT;
73
73
  });
74
74
 
75
75
  describe('apns runtime relay mode (default)', () => {
@@ -85,7 +85,7 @@ describe('apns runtime relay mode (default)', () => {
85
85
  }),
86
86
  );
87
87
  vi.stubGlobal('fetch', fetchMock);
88
- process.env.OPENCHAMBER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
88
+ process.env.AIWORKER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
89
89
 
90
90
  const runtime = createApnsRuntime(makeDeps());
91
91
  await runtime.addOrUpdateApnsToken('s1', 'tokenA');
@@ -132,7 +132,7 @@ describe('apns runtime relay mode (default)', () => {
132
132
  it('reuses one persisted keypair (same serverId) across register + send', async () => {
133
133
  const fetchMock = vi.fn(async () => jsonResponse({ ok: true, results: [] }));
134
134
  vi.stubGlobal('fetch', fetchMock);
135
- process.env.OPENCHAMBER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
135
+ process.env.AIWORKER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
136
136
 
137
137
  const deps = makeDeps();
138
138
  const runtime = createApnsRuntime(deps);
@@ -149,8 +149,8 @@ describe('apns runtime relay mode (default)', () => {
149
149
  it('honors an explicit sandbox environment override for every token', async () => {
150
150
  const fetchMock = vi.fn(async () => jsonResponse({ ok: true, results: [] }));
151
151
  vi.stubGlobal('fetch', fetchMock);
152
- process.env.OPENCHAMBER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
153
- process.env.OPENCHAMBER_APNS_ENVIRONMENT = 'sandbox';
152
+ process.env.AIWORKER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
153
+ process.env.AIWORKER_APNS_ENVIRONMENT = 'sandbox';
154
154
 
155
155
  const runtime = createApnsRuntime(makeDeps());
156
156
  await runtime.addOrUpdateApnsToken('s1', 'tokenA', undefined, 'ios', 'production');
@@ -163,7 +163,7 @@ describe('apns runtime relay mode (default)', () => {
163
163
  it('routes each token to its registered environment (dev build sandbox, release production)', async () => {
164
164
  const fetchMock = vi.fn(async () => jsonResponse({ ok: true, results: [] }));
165
165
  vi.stubGlobal('fetch', fetchMock);
166
- process.env.OPENCHAMBER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
166
+ process.env.AIWORKER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
167
167
 
168
168
  const runtime = createApnsRuntime(makeDeps());
169
169
  await runtime.addOrUpdateApnsToken('s1', 'tokenXcode', undefined, 'ios', 'sandbox');
@@ -200,7 +200,7 @@ describe('apns runtime direct fallback (relay disabled)', () => {
200
200
  });
201
201
 
202
202
  it('sends each token to the APNs host of its registered environment', async () => {
203
- process.env.OPENCHAMBER_PUSH_RELAY_DISABLED = 'true';
203
+ process.env.AIWORKER_PUSH_RELAY_DISABLED = 'true';
204
204
  const { environment: _environment, ...configWithoutEnvironment } = APNS_CONFIG;
205
205
  const hosts = [];
206
206
  const http2 = {
@@ -241,7 +241,7 @@ describe('apns runtime direct fallback (relay disabled)', () => {
241
241
  });
242
242
 
243
243
  it('signs an ES256 JWT and sends over http2 when relay is disabled', async () => {
244
- process.env.OPENCHAMBER_PUSH_RELAY_DISABLED = 'true';
244
+ process.env.AIWORKER_PUSH_RELAY_DISABLED = 'true';
245
245
  const targeted = [];
246
246
  const http2 = {
247
247
  connect: () => ({
@@ -66,7 +66,7 @@ export const createNotificationEmitterRuntime = (dependencies) => {
66
66
  const desktopNotificationDelivered = options.desktopNotificationDelivered === true;
67
67
 
68
68
  const syntheticPayload = {
69
- type: 'openchamber:notification',
69
+ type: 'aiworker:notification',
70
70
  properties: {
71
71
  ...payload,
72
72
  // Tell local desktop UI whether a native channel already accepted this
@@ -36,7 +36,7 @@ describe('notification emitter runtime', () => {
36
36
  runtime.broadcastUiNotification({ title: 'Ready' }, { desktopNotificationDelivered: true });
37
37
 
38
38
  expect(broadcastGlobalUiEvent).toHaveBeenCalledWith({
39
- type: 'openchamber:notification',
39
+ type: 'aiworker:notification',
40
40
  properties: {
41
41
  title: 'Ready',
42
42
  desktopNotificationDelivered: true,