@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
@@ -18,7 +18,7 @@ const goal = {
18
18
  const session = {
19
19
  id: SESSION_ID,
20
20
  directory: DIRECTORY,
21
- metadata: { openchamber: { goal } },
21
+ metadata: { aiworker: { goal } },
22
22
  };
23
23
 
24
24
  const jsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
@@ -46,6 +46,78 @@ const startIdleTick = async (fetchImpl) => {
46
46
  return { runtime, getSmallModelService };
47
47
  };
48
48
 
49
+ const assistantMessage = (id, infoOverrides = {}) => ({
50
+ info: {
51
+ id,
52
+ sessionID: SESSION_ID,
53
+ role: 'assistant',
54
+ providerID: 'provider',
55
+ modelID: 'model',
56
+ time: { created: 2, completed: 2 },
57
+ tokens: { input: 1, output: 1, cache: { read: 0 } },
58
+ ...infoOverrides,
59
+ },
60
+ parts: [{ type: 'text', text: 'The agent made progress.' }],
61
+ });
62
+
63
+ const createRuntimeHarness = ({ messages, messageFactory, goalOverrides = {}, maxAutoTurns = 20 }) => {
64
+ const requests = [];
65
+ let messageFetchCount = 0;
66
+ const activeSession = {
67
+ ...session,
68
+ metadata: { aiworker: { goal: { ...goal, ...goalOverrides } } },
69
+ };
70
+ const service = {
71
+ generateSmallModelText: vi.fn(async () => ({
72
+ text: '{"verdict":"continue","note":"More work remains"}',
73
+ providerID: 'provider',
74
+ modelID: 'model',
75
+ })),
76
+ };
77
+ const fetchImpl = vi.fn(async (input, init = {}) => {
78
+ const pathname = requestPath(input);
79
+ requests.push({ pathname, method: init.method ?? 'GET', body: init.body });
80
+ if (pathname === `/session/${SESSION_ID}` && init.method === 'PATCH') {
81
+ activeSession.metadata = JSON.parse(init.body).metadata;
82
+ return jsonResponse(activeSession);
83
+ }
84
+ if (pathname === `/session/${SESSION_ID}`) return jsonResponse(activeSession);
85
+ if (pathname === '/session/status') return jsonResponse({});
86
+ if (pathname === `/session/${SESSION_ID}/children`) return jsonResponse([]);
87
+ if (pathname === `/session/${SESSION_ID}/message`) {
88
+ const nextMessages = messageFactory ? messageFactory(messageFetchCount) : messages;
89
+ messageFetchCount += 1;
90
+ return jsonResponse(nextMessages);
91
+ }
92
+ if (pathname === `/session/${SESSION_ID}/prompt_async`) return jsonResponse({ ok: true });
93
+ throw new Error(`Unexpected request: ${pathname}`);
94
+ });
95
+ vi.stubGlobal('fetch', fetchImpl);
96
+ const runtime = createSessionGoalRuntime({
97
+ buildOpenCodeUrl: (pathname) => `http://opencode.test${pathname}`,
98
+ getOpenCodeAuthHeaders: () => ({}),
99
+ getSmallModelService: async () => service,
100
+ isEnabled: () => true,
101
+ idleQuietMs: 10,
102
+ maxAutoTurns,
103
+ });
104
+ return { runtime, requests, service, activeSession };
105
+ };
106
+
107
+ const runIdleTick = async (runtime) => {
108
+ runtime.processPayload({
109
+ type: 'session.status',
110
+ properties: { sessionID: SESSION_ID, status: { type: 'idle' }, directory: DIRECTORY },
111
+ });
112
+ await vi.runOnlyPendingTimersAsync();
113
+ };
114
+
115
+ const lastPatchedGoal = (requests) => {
116
+ const patches = requests.filter((request) => request.pathname === `/session/${SESSION_ID}` && request.method === 'PATCH');
117
+ expect(patches.length).toBeGreaterThan(0);
118
+ return JSON.parse(patches.at(-1).body).metadata.aiworker.goal;
119
+ };
120
+
49
121
  describe('session goal live activity gate', () => {
50
122
  beforeEach(() => {
51
123
  vi.useFakeTimers();
@@ -170,7 +242,7 @@ describe('session goal live activity gate', () => {
170
242
  expect(service.generateSmallModelText).toHaveBeenCalledOnce();
171
243
  const patch = requests.find((request) => request.pathname === `/session/${SESSION_ID}` && request.method === 'PATCH');
172
244
  expect(patch).toBeDefined();
173
- const writtenGoal = JSON.parse(patch.body).metadata.openchamber.goal;
245
+ const writtenGoal = JSON.parse(patch.body).metadata.aiworker.goal;
174
246
  expect(writtenGoal).toMatchObject({
175
247
  status: 'complete',
176
248
  evaluationProviderID: 'provider',
@@ -178,4 +250,358 @@ describe('session goal live activity gate', () => {
178
250
  });
179
251
  runtime.stop();
180
252
  });
253
+
254
+ it('skips audit and sends continuation prompt when assistant message finishes with length stop', async () => {
255
+ const requests = [];
256
+ const fetchImpl = vi.fn(async (input, init = {}) => {
257
+ const pathname = requestPath(input);
258
+ requests.push({ pathname, method: init.method ?? 'GET', body: init.body });
259
+ if (pathname === `/session/${SESSION_ID}` && init.method === 'PATCH') return jsonResponse(session);
260
+ if (pathname === `/session/${SESSION_ID}`) return jsonResponse(session);
261
+ if (pathname === '/session/status') return jsonResponse({});
262
+ if (pathname === `/session/${SESSION_ID}/children`) return jsonResponse([]);
263
+ if (pathname === `/session/${SESSION_ID}/prompt_async`) return jsonResponse({ ok: true });
264
+ if (pathname === `/session/${SESSION_ID}/message`) {
265
+ return jsonResponse([{
266
+ info: {
267
+ id: 'msg_assistant_len',
268
+ sessionID: SESSION_ID,
269
+ role: 'assistant',
270
+ providerID: 'provider',
271
+ modelID: 'model',
272
+ finish: 'length',
273
+ time: { completed: 2 },
274
+ tokens: { input: 100, output: 4096, reasoning: 4096, cache: { read: 0 } },
275
+ },
276
+ parts: [{ type: 'reasoning', text: 'Drafting extensive implementation...' }],
277
+ }]);
278
+ }
279
+ throw new Error(`Unexpected request: ${pathname}`);
280
+ });
281
+ const service = {
282
+ generateSmallModelText: vi.fn(),
283
+ };
284
+ vi.stubGlobal('fetch', fetchImpl);
285
+ const runtime = createSessionGoalRuntime({
286
+ buildOpenCodeUrl: (pathname) => `http://opencode.test${pathname}`,
287
+ getOpenCodeAuthHeaders: () => ({}),
288
+ getSmallModelService: async () => service,
289
+ isEnabled: () => true,
290
+ idleQuietMs: 10,
291
+ });
292
+
293
+ runtime.processPayload({
294
+ type: 'session.status',
295
+ properties: { sessionID: SESSION_ID, status: { type: 'idle' }, directory: DIRECTORY },
296
+ });
297
+ await vi.runOnlyPendingTimersAsync();
298
+
299
+ // Audit must be skipped on length-truncated turn
300
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
301
+
302
+ // Goal accounting is persisted and turnsUsed incremented
303
+ const patch = requests.find((request) => request.pathname === `/session/${SESSION_ID}` && request.method === 'PATCH');
304
+ expect(patch).toBeDefined();
305
+ const writtenGoal = JSON.parse(patch.body).metadata.aiworker.goal;
306
+ expect(writtenGoal).toMatchObject({
307
+ status: 'active',
308
+ turnsUsed: 2,
309
+ });
310
+
311
+ // Continuation prompt is dispatched
312
+ const promptAsync = requests.find((request) => request.pathname === `/session/${SESSION_ID}/prompt_async` && request.method === 'POST');
313
+ expect(promptAsync).toBeDefined();
314
+ const promptBody = JSON.parse(promptAsync.body);
315
+ expect(promptBody.parts[0].text).toContain('Continue working toward the active session goal.');
316
+ runtime.stop();
317
+ });
318
+
319
+ it('skips audit and sends continuation prompt when assistant message carries MessageOutputLengthError', async () => {
320
+ const requests = [];
321
+ const fetchImpl = vi.fn(async (input, init = {}) => {
322
+ const pathname = requestPath(input);
323
+ requests.push({ pathname, method: init.method ?? 'GET', body: init.body });
324
+ if (pathname === `/session/${SESSION_ID}` && init.method === 'PATCH') return jsonResponse(session);
325
+ if (pathname === `/session/${SESSION_ID}`) return jsonResponse(session);
326
+ if (pathname === '/session/status') return jsonResponse({});
327
+ if (pathname === `/session/${SESSION_ID}/children`) return jsonResponse([]);
328
+ if (pathname === `/session/${SESSION_ID}/prompt_async`) return jsonResponse({ ok: true });
329
+ if (pathname === `/session/${SESSION_ID}/message`) {
330
+ return jsonResponse([{
331
+ info: {
332
+ id: 'msg_assistant_len_err',
333
+ sessionID: SESSION_ID,
334
+ role: 'assistant',
335
+ providerID: 'provider',
336
+ modelID: 'model',
337
+ error: { name: 'MessageOutputLengthError', message: 'Maximum token limit reached' },
338
+ time: { completed: 2 },
339
+ tokens: { input: 100, output: 4096, reasoning: 4096, cache: { read: 0 } },
340
+ },
341
+ parts: [{ type: 'reasoning', text: 'Drafting extensive implementation...' }],
342
+ }]);
343
+ }
344
+ throw new Error(`Unexpected request: ${pathname}`);
345
+ });
346
+ const service = {
347
+ generateSmallModelText: vi.fn(),
348
+ };
349
+ vi.stubGlobal('fetch', fetchImpl);
350
+ const runtime = createSessionGoalRuntime({
351
+ buildOpenCodeUrl: (pathname) => `http://opencode.test${pathname}`,
352
+ getOpenCodeAuthHeaders: () => ({}),
353
+ getSmallModelService: async () => service,
354
+ isEnabled: () => true,
355
+ idleQuietMs: 10,
356
+ });
357
+
358
+ runtime.processPayload({
359
+ type: 'session.status',
360
+ properties: { sessionID: SESSION_ID, status: { type: 'idle' }, directory: DIRECTORY },
361
+ });
362
+ await vi.runOnlyPendingTimersAsync();
363
+
364
+ // Audit must be skipped on MessageOutputLengthError
365
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
366
+
367
+ // Goal remains active and turnsUsed incremented
368
+ const patch = requests.find((request) => request.pathname === `/session/${SESSION_ID}` && request.method === 'PATCH');
369
+ expect(patch).toBeDefined();
370
+ const writtenGoal = JSON.parse(patch.body).metadata.aiworker.goal;
371
+ expect(writtenGoal).toMatchObject({
372
+ status: 'active',
373
+ turnsUsed: 2,
374
+ });
375
+
376
+ // Continuation prompt is dispatched
377
+ const promptAsync = requests.find((request) => request.pathname === `/session/${SESSION_ID}/prompt_async` && request.method === 'POST');
378
+ expect(promptAsync).toBeDefined();
379
+ runtime.stop();
380
+ });
381
+
382
+ it.each([
383
+ { name: 'APIError', error: { name: 'APIError', message: 'provider failed' } },
384
+ { name: 'StructuredOutputError', error: { name: 'StructuredOutputError', message: 'invalid output' } },
385
+ { name: 'unnamed errors', error: {} },
386
+ ])('blocks a length finish when the assistant also has a non-length $name', async ({ error }) => {
387
+ const { runtime, requests, service } = createRuntimeHarness({
388
+ messages: [assistantMessage('msg_assistant_error', { finish: 'length', error })],
389
+ });
390
+
391
+ await runIdleTick(runtime);
392
+
393
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
394
+ expect(requests.some((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toBe(false);
395
+ expect(lastPatchedGoal(requests)).toMatchObject({
396
+ status: 'blocked',
397
+ statusReason: error.name || 'assistant turn failed',
398
+ });
399
+ runtime.stop();
400
+ });
401
+
402
+ it('blocks after two consecutive length-truncated agent turns without persisting a counter, regardless of message IDs', async () => {
403
+ const firstLength = assistantMessage('z', { finish: 'length', time: { created: 10, completed: 11 } });
404
+ const summary = assistantMessage('summary', { summary: true, time: { created: 15, completed: 16 } });
405
+ const secondLength = assistantMessage('a', { finish: 'length', time: { created: 20, completed: 21 } });
406
+ const { runtime, requests, service } = createRuntimeHarness({
407
+ messageFactory: (fetchCount) => fetchCount < 2 ? [firstLength] : [firstLength, summary, secondLength],
408
+ });
409
+
410
+ await runIdleTick(runtime);
411
+ runtime.processPayload({
412
+ type: 'session.status',
413
+ properties: { sessionID: SESSION_ID, status: { type: 'idle' }, directory: DIRECTORY },
414
+ });
415
+ await vi.runOnlyPendingTimersAsync();
416
+
417
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
418
+ expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(1);
419
+ expect(lastPatchedGoal(requests)).toMatchObject({
420
+ status: 'blocked',
421
+ statusReason: 'repeated output truncation',
422
+ });
423
+ runtime.stop();
424
+ });
425
+
426
+ it('allows one explicit Resume after repeated truncation, then blocks another cutoff', async () => {
427
+ const first = assistantMessage('first', { finish: 'length', time: { created: 10, completed: 11 } });
428
+ const second = assistantMessage('second', { finish: 'length', time: { created: 20, completed: 21 } });
429
+ const messages = [first, second];
430
+ const { runtime, requests, activeSession } = createRuntimeHarness({ messages });
431
+ try {
432
+ await runIdleTick(runtime);
433
+ expect(lastPatchedGoal(requests).status).toBe('blocked');
434
+ expect(requests.filter((request) => request.pathname.endsWith('/prompt_async'))).toHaveLength(0);
435
+ Object.assign(activeSession.metadata.aiworker.goal, { status: 'active', statusReason: 'resumed', turnsUsed: 0 });
436
+ await runIdleTick(runtime);
437
+ expect(lastPatchedGoal(requests)).toMatchObject({ status: 'active', statusReason: '', turnsUsed: 1 });
438
+ expect(requests.filter((request) => request.pathname.endsWith('/prompt_async'))).toHaveLength(1);
439
+ messages.push(assistantMessage('third', { finish: 'length', time: { created: 30, completed: 31 } }));
440
+ await runIdleTick(runtime);
441
+ expect(lastPatchedGoal(requests)).toMatchObject({ status: 'blocked', statusReason: 'repeated output truncation' });
442
+ expect(requests.filter((request) => request.pathname.endsWith('/prompt_async'))).toHaveLength(1);
443
+ } finally {
444
+ runtime.stop();
445
+ }
446
+ });
447
+
448
+ it.each([
449
+ { tokenBudget: 1, error: undefined, expectedStatus: 'budgetLimited' },
450
+ { tokenBudget: null, error: { name: 'APIError' }, expectedStatus: 'blocked' },
451
+ ])('keeps $expectedStatus protection on explicit Resume', async ({ tokenBudget, error, expectedStatus }) => {
452
+ const { runtime, requests } = createRuntimeHarness({
453
+ goalOverrides: { statusReason: 'resumed', tokenBudget },
454
+ messages: [assistantMessage('resumed', { finish: 'length', error })],
455
+ });
456
+ try {
457
+ await runIdleTick(runtime);
458
+ expect(lastPatchedGoal(requests).status).toBe(expectedStatus);
459
+ expect(requests.filter((request) => request.pathname.endsWith('/prompt_async'))).toHaveLength(0);
460
+ } finally {
461
+ runtime.stop();
462
+ }
463
+ });
464
+
465
+ it('continues after a truncated agent turn followed by a length-finished summary', async () => {
466
+ const firstLength = assistantMessage('agent-length', { finish: 'length', time: { created: 10, completed: 11 } });
467
+ const summary = assistantMessage('summary', {
468
+ summary: true,
469
+ finish: 'length',
470
+ time: { created: 15, completed: 16 },
471
+ });
472
+ const { runtime, requests, service } = createRuntimeHarness({
473
+ messageFactory: (fetchCount) => fetchCount < 2 ? [firstLength] : [firstLength, summary],
474
+ });
475
+
476
+ await runIdleTick(runtime);
477
+ await runIdleTick(runtime);
478
+
479
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
480
+ expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(2);
481
+ expect(lastPatchedGoal(requests)).toMatchObject({ status: 'active' });
482
+ runtime.stop();
483
+ });
484
+
485
+ it('does not infer a repeated streak when a completed assistant timestamp is missing', async () => {
486
+ const firstLength = assistantMessage('z', { finish: 'length', time: { completed: 11 } });
487
+ const secondLength = assistantMessage('a', { finish: 'length', time: { created: 20, completed: 21 } });
488
+ const { runtime, requests, service } = createRuntimeHarness({
489
+ messageFactory: (fetchCount) => fetchCount < 2 ? [firstLength] : [firstLength, secondLength],
490
+ });
491
+
492
+ await runIdleTick(runtime);
493
+ await runIdleTick(runtime);
494
+
495
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
496
+ expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(2);
497
+ expect(lastPatchedGoal(requests)).toMatchObject({ status: 'active' });
498
+ runtime.stop();
499
+ });
500
+
501
+ it('ignores a missing timestamp on an older assistant when known post-goal turns establish a streak', async () => {
502
+ const olderLength = assistantMessage('legacy', { finish: 'length', time: { completed: 5 } });
503
+ const firstLength = assistantMessage('z', { finish: 'length', time: { created: 10, completed: 11 } });
504
+ const secondLength = assistantMessage('a', { finish: 'length', time: { created: 20, completed: 21 } });
505
+ const { runtime, requests, service } = createRuntimeHarness({
506
+ goalOverrides: { createdAt: 6 },
507
+ messageFactory: (fetchCount) => fetchCount < 2 ? [firstLength] : [olderLength, firstLength, secondLength],
508
+ });
509
+
510
+ await runIdleTick(runtime);
511
+ await runIdleTick(runtime);
512
+
513
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
514
+ expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(1);
515
+ expect(lastPatchedGoal(requests)).toMatchObject({
516
+ status: 'blocked',
517
+ statusReason: 'repeated output truncation',
518
+ });
519
+ runtime.stop();
520
+ });
521
+
522
+ it('does not count a previous truncated turn created at the goal boundary', async () => {
523
+ const firstLength = assistantMessage('before-boundary', { finish: 'length', time: { created: 10, completed: 11 } });
524
+ const secondLength = assistantMessage('after-boundary', { finish: 'length', time: { created: 20, completed: 21 } });
525
+ const { runtime, requests, service } = createRuntimeHarness({
526
+ goalOverrides: { createdAt: 10 },
527
+ messageFactory: (fetchCount) => fetchCount < 2 ? [firstLength] : [firstLength, secondLength],
528
+ });
529
+
530
+ await runIdleTick(runtime);
531
+ await runIdleTick(runtime);
532
+
533
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
534
+ expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(2);
535
+ expect(lastPatchedGoal(requests)).toMatchObject({ status: 'active' });
536
+ runtime.stop();
537
+ });
538
+
539
+ it('allows length recovery after an ordinary assistant turn breaks the streak', async () => {
540
+ const firstLength = assistantMessage('msg_length_before_normal', { finish: 'length' });
541
+ const normal = assistantMessage('msg_normal');
542
+ const secondLength = assistantMessage('msg_length_after_normal', { finish: 'length' });
543
+ const { runtime, requests, service } = createRuntimeHarness({
544
+ messageFactory: (fetchCount) => fetchCount < 2
545
+ ? [firstLength]
546
+ : [firstLength, normal, secondLength],
547
+ });
548
+
549
+ await runIdleTick(runtime);
550
+ await runIdleTick(runtime);
551
+
552
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
553
+ expect(requests.filter((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toHaveLength(2);
554
+ expect(lastPatchedGoal(requests)).toMatchObject({ status: 'active' });
555
+ runtime.stop();
556
+ });
557
+
558
+ it('keeps MessageAbortedError pause behavior instead of continuing', async () => {
559
+ const { runtime, requests, service } = createRuntimeHarness({
560
+ messages: [assistantMessage('msg_aborted', { error: { name: 'MessageAbortedError' } })],
561
+ });
562
+
563
+ await runIdleTick(runtime);
564
+
565
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
566
+ expect(requests.some((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toBe(false);
567
+ expect(lastPatchedGoal(requests)).toMatchObject({
568
+ status: 'paused',
569
+ statusReason: 'paused after abort',
570
+ });
571
+ runtime.stop();
572
+ });
573
+
574
+ it('checks the token budget before allowing length recovery', async () => {
575
+ const { runtime, requests, service } = createRuntimeHarness({
576
+ goalOverrides: { tokenBudget: 5 },
577
+ messages: [assistantMessage('msg_budget_length', {
578
+ finish: 'length',
579
+ tokens: { input: 3, output: 3, cache: { read: 0 } },
580
+ })],
581
+ });
582
+
583
+ await runIdleTick(runtime);
584
+
585
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
586
+ expect(requests.some((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toBe(false);
587
+ expect(lastPatchedGoal(requests)).toMatchObject({ status: 'budgetLimited' });
588
+ runtime.stop();
589
+ });
590
+
591
+ it('checks the auto-continuation cap before allowing length recovery', async () => {
592
+ const { runtime, requests, service } = createRuntimeHarness({
593
+ maxAutoTurns: 1,
594
+ messages: [assistantMessage('msg_cap_length', { finish: 'length' })],
595
+ });
596
+
597
+ await runIdleTick(runtime);
598
+
599
+ expect(service.generateSmallModelText).not.toHaveBeenCalled();
600
+ expect(requests.some((request) => request.pathname === `/session/${SESSION_ID}/prompt_async`)).toBe(false);
601
+ expect(lastPatchedGoal(requests)).toMatchObject({
602
+ status: 'blocked',
603
+ statusReason: 'auto-continuation limit reached',
604
+ });
605
+ runtime.stop();
606
+ });
181
607
  });
@@ -60,7 +60,7 @@ const buildMemoryBlock = ({ global, project }) => {
60
60
  return [
61
61
  'You have stored memory from earlier sessions. Only the titles are listed below.',
62
62
  'A title is an abbreviation, not the memory. Read the entry with the'
63
- + ' openchamber_memory tool before you act on it: titles routinely leave out'
63
+ + ' aiworker_memory tool before you act on it: titles routinely leave out'
64
64
  + ' the conditions, exceptions and reasons that decide how the memory'
65
65
  + ' applies, and a title that looks self-explanatory is the most likely to'
66
66
  + ' be hiding them. Read every title that could bear on the task at hand;'
@@ -122,8 +122,8 @@ export const createSessionKnowledgeRuntime = (dependencies) => {
122
122
  */
123
123
  const readPins = (session) => {
124
124
  const metadata = isRecord(session?.metadata) ? session.metadata : {};
125
- const openchamber = isRecord(metadata.openchamber) ? metadata.openchamber : {};
126
- const pins = isRecord(openchamber[PINS_METADATA_KEY]) ? openchamber[PINS_METADATA_KEY] : {};
125
+ const aiworker = isRecord(metadata.aiworker) ? metadata.aiworker : {};
126
+ const pins = isRecord(aiworker[PINS_METADATA_KEY]) ? aiworker[PINS_METADATA_KEY] : {};
127
127
  const strings = (value) => Array.isArray(value)
128
128
  ? [...new Set(value.filter((entry) => typeof entry === 'string' && entry.trim()).map((entry) => entry.trim()))]
129
129
  : [];
@@ -229,8 +229,8 @@ export const createSessionKnowledgeRuntime = (dependencies) => {
229
229
 
230
230
  const readDeliveredSignature = (session) => {
231
231
  const metadata = isRecord(session?.metadata) ? session.metadata : {};
232
- const openchamber = isRecord(metadata.openchamber) ? metadata.openchamber : {};
233
- const delivered = openchamber[KNOWLEDGE_METADATA_KEY];
232
+ const aiworker = isRecord(metadata.aiworker) ? metadata.aiworker : {};
233
+ const delivered = aiworker[KNOWLEDGE_METADATA_KEY];
234
234
  return typeof delivered === 'string' ? delivered : '';
235
235
  };
236
236
 
@@ -267,7 +267,7 @@ export const createSessionKnowledgeRuntime = (dependencies) => {
267
267
  const setPin = async (sessionId, directory, kind, id, pinned) => {
268
268
  const fresh = await readSession(sessionId, directory);
269
269
  const metadata = isRecord(fresh?.metadata) ? fresh.metadata : {};
270
- const openchamber = isRecord(metadata.openchamber) ? metadata.openchamber : {};
270
+ const aiworker = isRecord(metadata.aiworker) ? metadata.aiworker : {};
271
271
  const pins = readPins(fresh);
272
272
  const key = kind === 'note' ? 'notes' : 'plans';
273
273
  const next = new Set(pins[key]);
@@ -279,8 +279,8 @@ export const createSessionKnowledgeRuntime = (dependencies) => {
279
279
  body: {
280
280
  metadata: {
281
281
  ...metadata,
282
- openchamber: {
283
- ...openchamber,
282
+ aiworker: {
283
+ ...aiworker,
284
284
  [PINS_METADATA_KEY]: { ...pins, [key]: [...next] },
285
285
  [KNOWLEDGE_METADATA_KEY]: '',
286
286
  },
@@ -296,20 +296,20 @@ export const createSessionKnowledgeRuntime = (dependencies) => {
296
296
  * agent had context it never received.
297
297
  *
298
298
  * Merged onto a fresh read, because the session's metadata holds other
299
- * OpenChamber state — pinned messages among it — and a blind write would
299
+ * AiWorker state — pinned messages among it — and a blind write would
300
300
  * drop whatever changed in between.
301
301
  */
302
302
  const recordDelivered = async (sessionId, directory, signature) => {
303
303
  const fresh = await readSession(sessionId, directory);
304
304
  const metadata = isRecord(fresh?.metadata) ? fresh.metadata : {};
305
- const openchamber = isRecord(metadata.openchamber) ? metadata.openchamber : {};
305
+ const aiworker = isRecord(metadata.aiworker) ? metadata.aiworker : {};
306
306
  await openCodeFetch(`/session/${encodeURIComponent(sessionId)}`, {
307
307
  directory,
308
308
  method: 'PATCH',
309
309
  body: {
310
310
  metadata: {
311
311
  ...metadata,
312
- openchamber: { ...openchamber, [KNOWLEDGE_METADATA_KEY]: signature },
312
+ aiworker: { ...aiworker, [KNOWLEDGE_METADATA_KEY]: signature },
313
313
  },
314
314
  },
315
315
  });
@@ -193,10 +193,10 @@ describe('reading what a session was told', () => {
193
193
  const runtime = createRuntime();
194
194
 
195
195
  expect(runtime.readPins({
196
- metadata: { openchamber: { project_context_pins: { notes: ['n1'], plans: [] } } },
196
+ metadata: { aiworker: { project_context_pins: { notes: ['n1'], plans: [] } } },
197
197
  })).toEqual({ notes: ['n1'], plans: [] });
198
198
  expect(runtime.readPins({
199
- metadata: { openchamber: { project_context_pins: { notes: [], plans: ['p1'] } } },
199
+ metadata: { aiworker: { project_context_pins: { notes: [], plans: ['p1'] } } },
200
200
  })).toEqual({ notes: [], plans: ['p1'] });
201
201
  expect(runtime.readPins({})).toEqual({ notes: [], plans: [] });
202
202
  });
@@ -208,7 +208,7 @@ describe('reading what a session was told', () => {
208
208
  requests.push({ path, options });
209
209
  if (options.method === 'PATCH') return {};
210
210
  return {
211
- metadata: { openchamber: { project_context_pins: { notes: [], plans: [] }, knowledge_context_delivered: 'old' } },
211
+ metadata: { aiworker: { project_context_pins: { notes: [], plans: [] }, knowledge_context_delivered: 'old' } },
212
212
  };
213
213
  },
214
214
  });
@@ -216,7 +216,7 @@ describe('reading what a session was told', () => {
216
216
  await runtime.setPin('ses_a', DIRECTORY, 'note', 'n1', true);
217
217
 
218
218
  expect(requests.map((request) => request.path)).toEqual(['/session/ses_a', '/session/ses_a']);
219
- expect(requests[1].options.body.metadata.openchamber).toEqual({
219
+ expect(requests[1].options.body.metadata.aiworker).toEqual({
220
220
  project_context_pins: { notes: ['n1'], plans: [] },
221
221
  knowledge_context_delivered: '',
222
222
  });
@@ -226,7 +226,7 @@ describe('reading what a session was told', () => {
226
226
  const runtime = createRuntime();
227
227
 
228
228
  expect(runtime.readDeliveredSignature({
229
- metadata: { openchamber: { knowledge_context_delivered: 'sig' } },
229
+ metadata: { aiworker: { knowledge_context_delivered: 'sig' } },
230
230
  })).toBe('sig');
231
231
  });
232
232
 
@@ -58,7 +58,7 @@ The following functions are internal helpers used by exported functions:
58
58
  - `safeRm(dir)`: Safely remove directory recursively (ignores errors).
59
59
  - `ensureDir(dirPath)`: Ensure directory exists with recursive creation.
60
60
  - `copyDirectoryNoSymlinks(srcDir, dstDir)`: Copy directory contents without symlinks, with path traversal protection.
61
- - `normalizeUserSkillDir(userSkillDir)`: Normalize user skill directory path (handles legacy `~/.config/opencode/skill` `~/.config/opencode/skills` migration).
61
+ - `normalizeUserSkillDir(userSkillDir)`: Normalize the user skill directory path (handles the legacy `skill` directory in the XDG config location, or `~/.config/opencode/skill` when XDG is unset, by selecting the plural `skills` directory when appropriate).
62
62
 
63
63
  ### Git Clone Helpers (`install.js`, `scan.js`)
64
64
  - `cloneRepo({ cloneUrl, identity, tempDir })`: Clone git repository with preferred partial clone (`--filter=blob:none`) and fallback. Uses non-interactive mode.
@@ -8,11 +8,11 @@ let tempDataDir;
8
8
 
9
9
  beforeEach(() => {
10
10
  tempDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skills-cache-test-'));
11
- process.env.OPENCHAMBER_DATA_DIR = tempDataDir;
11
+ process.env.AIWORKER_DATA_DIR = tempDataDir;
12
12
  });
13
13
 
14
14
  afterEach(() => {
15
- delete process.env.OPENCHAMBER_DATA_DIR;
15
+ delete process.env.AIWORKER_DATA_DIR;
16
16
  clearCache();
17
17
  vi.restoreAllMocks();
18
18
  fs.rmSync(tempDataDir, { recursive: true, force: true });
@@ -2,9 +2,9 @@ import fs from 'fs';
2
2
  import os from 'os';
3
3
  import path from 'path';
4
4
 
5
- const resolveDataDir = () => (process.env.OPENCHAMBER_DATA_DIR
6
- ? path.resolve(process.env.OPENCHAMBER_DATA_DIR)
7
- : path.join(os.homedir(), '.config', 'openchamber'));
5
+ const resolveDataDir = () => (process.env.AIWORKER_DATA_DIR
6
+ ? path.resolve(process.env.AIWORKER_DATA_DIR)
7
+ : path.join(os.homedir(), '.config', 'aiworker'));
8
8
 
9
9
  const readJsonFile = (filePath) => {
10
10
  try {
@@ -17,7 +17,7 @@ const readJsonFile = (filePath) => {
17
17
  };
18
18
 
19
19
  /**
20
- * Read a persisted cache object from the OpenChamber data directory.
20
+ * Read a persisted cache object from the AiWorker data directory.
21
21
  * Returns null when the file is missing, unreadable, or malformed.
22
22
  */
23
23
  export const readDiskCache = (fileName) => {
@@ -29,7 +29,7 @@ export const readDiskCache = (fileName) => {
29
29
  };
30
30
 
31
31
  /**
32
- * Persist a cache object to the OpenChamber data directory with an atomic
32
+ * Persist a cache object to the AiWorker data directory with an atomic
33
33
  * temp-file rename. Failures are ignored: the in-memory cache stays
34
34
  * authoritative and the next successful write retries persistence.
35
35
  */
@@ -10,11 +10,11 @@ let tempDataDir;
10
10
 
11
11
  beforeEach(() => {
12
12
  tempDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'github-meta-test-'));
13
- process.env.OPENCHAMBER_DATA_DIR = tempDataDir;
13
+ process.env.AIWORKER_DATA_DIR = tempDataDir;
14
14
  });
15
15
 
16
16
  afterEach(() => {
17
- delete process.env.OPENCHAMBER_DATA_DIR;
17
+ delete process.env.AIWORKER_DATA_DIR;
18
18
  globalThis.fetch = originalFetch;
19
19
  clearGitHubMetaCache();
20
20
  vi.restoreAllMocks();
@@ -4,13 +4,14 @@ import path from 'path';
4
4
 
5
5
  import { assertGitAvailable, looksLikeAuthError, runGit } from './git.js';
6
6
  import { parseSkillRepoSource } from './source.js';
7
+ import { OPENCODE_CONFIG_DIR } from '../opencode/shared.js';
7
8
 
8
9
  const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
9
10
 
10
11
  function normalizeUserSkillDir(userSkillDir) {
11
12
  if (!userSkillDir) return null;
12
- const legacySkillDir = path.join(os.homedir(), '.config', 'opencode', 'skill');
13
- const pluralSkillDir = path.join(os.homedir(), '.config', 'opencode', 'skills');
13
+ const legacySkillDir = path.join(OPENCODE_CONFIG_DIR, 'skill');
14
+ const pluralSkillDir = path.join(OPENCODE_CONFIG_DIR, 'skills');
14
15
  if (userSkillDir === legacySkillDir) {
15
16
  if (fs.existsSync(legacySkillDir) && !fs.existsSync(pluralSkillDir)) return legacySkillDir;
16
17
  return pluralSkillDir;