@aiworker/web 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/README.md +4 -4
  2. package/bin/cli.js +14 -4
  3. package/bin/cli.test.js +165 -73
  4. package/bin/lib/cli-api-target.js +2 -2
  5. package/bin/lib/cli-args.js +119 -119
  6. package/bin/lib/cli-lifecycle.js +9 -9
  7. package/bin/lib/cli-network.js +3 -3
  8. package/bin/lib/cli-paths.js +3 -3
  9. package/bin/lib/cli-ports.js +2 -2
  10. package/bin/lib/cli-process.js +4 -4
  11. package/bin/lib/cli-startup.js +64 -15
  12. package/bin/lib/cli-tunnel-profiles.js +1 -1
  13. package/bin/lib/cli-tunnel-utils.js +2 -2
  14. package/bin/lib/commands-connect-url.js +19 -19
  15. package/bin/lib/commands-lifecycle.js +27 -27
  16. package/bin/lib/commands-logs.js +5 -5
  17. package/bin/lib/commands-models.js +1 -1
  18. package/bin/lib/commands-projects.js +1 -1
  19. package/bin/lib/commands-schedule.js +1 -1
  20. package/bin/lib/commands-serve.js +29 -29
  21. package/bin/lib/commands-session.js +1 -1
  22. package/bin/lib/commands-startup.js +45 -10
  23. package/bin/lib/commands-status.js +1 -1
  24. package/bin/lib/commands-tunnel.js +40 -40
  25. package/bin/lib/commands-update.js +1 -1
  26. package/bin/lib/commands-update.test.js +6 -6
  27. package/dist/assets/{CodeMirrorCommentWidgets-B3u7UIcE.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
  28. package/dist/assets/DiffView-DXjVsEOw.js +4 -0
  29. package/dist/assets/DirtyBranchSwitchDialog-aig6zJjg.js +1 -0
  30. package/dist/assets/FilesView-CJdG0-wk.js +17 -0
  31. package/dist/assets/GitView-BXsS3jG6.js +6 -0
  32. package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-D_hpXBnF.js} +1 -1
  33. package/dist/assets/LinearIssuesView-BoUiLQG1.js +1 -0
  34. package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
  35. package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
  36. package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
  37. package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-8p6nLJaU.js} +1 -1
  38. package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
  39. package/dist/assets/PlanView-CJTmOF6I.js +5 -0
  40. package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
  41. package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-Cpb4hM6q.js} +1 -1
  42. package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
  43. package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
  44. package/dist/assets/ToolPartDiffPreview-DtZLzrSK.js +8 -0
  45. package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
  46. package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
  47. package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
  48. package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
  49. package/dist/assets/de-DCrsQ5zS.js +10 -0
  50. package/dist/assets/es-Dwv4YfH6.js +16 -0
  51. package/dist/assets/fr-BCWg4Cnx.js +14 -0
  52. package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
  53. package/dist/assets/index-B68h5S5B.css +1 -0
  54. package/dist/assets/index-DVwMarHI.js +39 -0
  55. package/dist/assets/{index-CODwDsU_.js → index-jXSK5RrB.js} +1 -1
  56. package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-B_x_rVon.js} +1 -1
  57. package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-DW_xMer1.js} +1 -1
  58. package/dist/assets/ja-Bruw3RYC.js +16 -0
  59. package/dist/assets/ko-idhXGF8j.js +16 -0
  60. package/dist/assets/main-BkyaQhJX.js +930 -0
  61. package/dist/assets/{main-C5DVOxc6.js → main-CzdPuL0s.js} +2 -2
  62. package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
  63. package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-CCkqKOLp.js} +2 -2
  64. package/dist/assets/{mobile-CcaxB0n4.js → mobile-mvxTo055.js} +2 -2
  65. package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
  66. package/dist/assets/pl-CGZWUqZJ.js +16 -0
  67. package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
  68. package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
  69. package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
  70. package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
  71. package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
  72. package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-CRl0SUC9.js} +1 -1
  73. package/dist/assets/surface-oMvhsog3.js +1 -0
  74. package/dist/assets/tr-BK_KiO2V.js +7 -0
  75. package/dist/assets/uk-CG74xV8N.js +16 -0
  76. package/dist/assets/useAppFontEffects-CPKeOpUo.js +7284 -0
  77. package/dist/assets/useGitComparison-DH220WmO.js +1 -0
  78. package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
  79. package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
  80. package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
  81. package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
  82. package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
  83. package/dist/assets/{web-components-Bhg83tjf.js → web-components-z7hfnS8n.js} +1 -1
  84. package/dist/assets/zh-CN-T4AXofZI.js +16 -0
  85. package/dist/assets/zh-TW-Dte40sxp.js +16 -0
  86. package/dist/index.html +21 -59
  87. package/dist/mini-chat.html +7 -7
  88. package/dist/mobile.html +5 -5
  89. package/package.json +3 -4
  90. package/server/index.d.ts +15 -0
  91. package/server/index.js +158 -89
  92. package/server/lib/agent-memory/actions.js +2 -2
  93. package/server/lib/agent-memory/feature-flag.js +1 -1
  94. package/server/lib/agent-memory/feature-flag.test.js +8 -8
  95. package/server/lib/agent-memory/routes.js +2 -2
  96. package/server/lib/agent-tool/runtime.js +37 -54
  97. package/server/lib/agent-tool/runtime.test.js +59 -59
  98. package/server/lib/browser-control/broker.js +3 -3
  99. package/server/lib/browser-control/broker.test.js +1 -1
  100. package/server/lib/changelog/update-notes.js +82 -0
  101. package/server/lib/changelog/update-notes.test.js +59 -0
  102. package/server/lib/client-auth/remote-clients.test.js +5 -5
  103. package/server/lib/context-obligatory/runtime.js +7 -7
  104. package/server/lib/context-obligatory/runtime.test.js +5 -5
  105. package/server/lib/data-dir-migration.js +40 -0
  106. package/server/lib/data-dir-migration.test.js +59 -0
  107. package/server/lib/dev-servers/parse.js +2 -2
  108. package/server/lib/dev-servers/parse.test.js +2 -2
  109. package/server/lib/dev-servers/routes.js +1 -1
  110. package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
  111. package/server/lib/dev-tunnel/client.js +1 -1
  112. package/server/lib/dev-tunnel/runtime.js +5 -5
  113. package/server/lib/dev-tunnel/tunnel.test.js +21 -1
  114. package/server/lib/dictation/local/model-catalog.js +1 -1
  115. package/server/lib/dictation/local/worker-process.js +2 -2
  116. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  117. package/server/lib/event-stream/global-ws-bridge.js +1 -1
  118. package/server/lib/event-stream/protocol.test.js +4 -4
  119. package/server/lib/event-stream/runtime.test.js +18 -18
  120. package/server/lib/fs/DOCUMENTATION.md +5 -0
  121. package/server/lib/fs/routes.js +68 -26
  122. package/server/lib/fs/routes.test.js +208 -22
  123. package/server/lib/git/DOCUMENTATION.md +32 -6
  124. package/server/lib/git/identity-storage.js +1 -1
  125. package/server/lib/git/issue-2746-longpaths.test.js +4 -4
  126. package/server/lib/git/routes.js +41 -5
  127. package/server/lib/git/service.js +300 -224
  128. package/server/lib/git/service.test.js +596 -28
  129. package/server/lib/github/auth.js +8 -8
  130. package/server/lib/github/pr-status.js +1 -1
  131. package/server/lib/github/routes.js +2 -2
  132. package/server/lib/inherited-env.js +17 -10
  133. package/server/lib/inherited-env.test.js +10 -11
  134. package/server/lib/linear/auth.js +8 -8
  135. package/server/lib/linear/auth.test.js +24 -24
  136. package/server/lib/linear/issues.test.js +6 -6
  137. package/server/lib/linear/mapping.test.js +4 -4
  138. package/server/lib/linear/oauth.js +2 -2
  139. package/server/lib/linear/oauth.test.js +14 -14
  140. package/server/lib/linear/routes.js +5 -5
  141. package/server/lib/linear/routes.test.js +16 -16
  142. package/server/lib/linear/status-runtime.test.js +9 -9
  143. package/server/lib/linear/status.js +1 -1
  144. package/server/lib/linear/status.test.js +15 -15
  145. package/server/lib/linear/teams.test.js +4 -4
  146. package/server/lib/magic-prompts/routes.js +2 -2
  147. package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
  148. package/server/lib/mcp-reconnect/runtime.js +122 -0
  149. package/server/lib/mcp-reconnect/runtime.test.js +140 -0
  150. package/server/lib/message-queue/DOCUMENTATION.md +161 -0
  151. package/server/lib/message-queue/runtime.js +870 -0
  152. package/server/lib/message-queue/runtime.test.js +482 -0
  153. package/server/lib/network-defaults.js +23 -0
  154. package/server/lib/network-defaults.test.js +52 -0
  155. package/server/lib/notifications/apns-runtime.js +17 -17
  156. package/server/lib/notifications/apns-runtime.test.js +11 -11
  157. package/server/lib/notifications/emitter-runtime.js +1 -1
  158. package/server/lib/notifications/emitter-runtime.test.js +1 -1
  159. package/server/lib/notifications/push-runtime.js +7 -7
  160. package/server/lib/notifications/routes.js +1 -1
  161. package/server/lib/notifications/runtime.js +1 -1
  162. package/server/lib/openchamber-control/action-resolution.test.js +10 -10
  163. package/server/lib/openchamber-control/actions.js +24 -24
  164. package/server/lib/openchamber-control/error.js +4 -4
  165. package/server/lib/openchamber-control/routes.js +2 -2
  166. package/server/lib/openchamber-control/routes.test.js +5 -5
  167. package/server/lib/openchamber-control/screenshots.js +1 -1
  168. package/server/lib/openchamber-control/screenshots.test.js +1 -1
  169. package/server/lib/openchamber-control/service.js +50 -50
  170. package/server/lib/openchamber-control/service.test.js +5 -5
  171. package/server/lib/openchamber-sessions/routes.js +124 -22
  172. package/server/lib/openchamber-sessions/routes.test.js +99 -11
  173. package/server/lib/opencode/DOCUMENTATION.md +30 -6
  174. package/server/lib/opencode/auth.js +1 -1
  175. package/server/lib/opencode/bootstrap-runtime.js +8 -6
  176. package/server/lib/opencode/cli-options.js +8 -8
  177. package/server/lib/opencode/config-paths.test.js +116 -0
  178. package/server/lib/opencode/core-routes.js +8 -7
  179. package/server/lib/opencode/core-routes.test.js +11 -11
  180. package/server/lib/opencode/env-config.js +5 -5
  181. package/server/lib/opencode/env-config.test.js +7 -7
  182. package/server/lib/opencode/env-runtime.js +10 -10
  183. package/server/lib/opencode/env-runtime.test.js +18 -18
  184. package/server/lib/opencode/feature-routes-runtime.js +19 -12
  185. package/server/lib/opencode/hmr-state-runtime.js +2 -2
  186. package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
  187. package/server/lib/opencode/input-history-scope.js +14 -0
  188. package/server/lib/opencode/lifecycle.js +8 -8
  189. package/server/lib/opencode/lifecycle.test.js +4 -4
  190. package/server/lib/opencode/managed-plugin-config.js +35 -0
  191. package/server/lib/opencode/managed-process-registry.js +3 -3
  192. package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
  193. package/server/lib/opencode/openchamber-routes.js +79 -19
  194. package/server/lib/opencode/openchamber-routes.test.js +139 -12
  195. package/server/lib/opencode/plugins.js +2 -2
  196. package/server/lib/opencode/plugins.test.js +1 -1
  197. package/server/lib/opencode/project-icon-routes.js +2 -2
  198. package/server/lib/opencode/project-icon-routes.test.js +1 -1
  199. package/server/lib/opencode/providers.js +44 -2
  200. package/server/lib/opencode/providers.test.js +131 -4
  201. package/server/lib/opencode/proxy.js +2 -2
  202. package/server/lib/opencode/pwa-manifest-routes.js +1 -1
  203. package/server/lib/opencode/restart-session-recovery.test.js +1 -1
  204. package/server/lib/opencode/routes-upgrade.test.js +4 -4
  205. package/server/lib/opencode/routes.js +19 -17
  206. package/server/lib/opencode/server-startup-runtime.js +6 -6
  207. package/server/lib/opencode/session-runtime.js +3 -3
  208. package/server/lib/opencode/session-runtime.test.js +6 -6
  209. package/server/lib/opencode/settings-files.js +214 -0
  210. package/server/lib/opencode/settings-helpers.js +116 -44
  211. package/server/lib/opencode/settings-helpers.test.js +354 -12
  212. package/server/lib/opencode/settings-registry.json +747 -0
  213. package/server/lib/opencode/settings-runtime.js +141 -20
  214. package/server/lib/opencode/settings-runtime.test.js +191 -4
  215. package/server/lib/opencode/shared.js +5 -2
  216. package/server/lib/opencode/shared.test.js +7 -7
  217. package/server/lib/opencode/shutdown-runtime.js +2 -0
  218. package/server/lib/opencode/skill-routes.js +2 -1
  219. package/server/lib/opencode/snippets.js +1 -2
  220. package/server/lib/opencode/startup-performance.js +1 -1
  221. package/server/lib/opencode/startup-performance.test.js +6 -6
  222. package/server/lib/opencode/static-routes-runtime.js +8 -8
  223. package/server/lib/opencode/static-routes-runtime.test.js +7 -7
  224. package/server/lib/opencode/upgrade-capability.js +2 -2
  225. package/server/lib/opencode/upgrade-capability.test.js +3 -3
  226. package/server/lib/package-manager.js +17 -42
  227. package/server/lib/package-manager.test.js +19 -19
  228. package/server/lib/permission-auto-accept/runtime.js +1 -1
  229. package/server/lib/project-context/DOCUMENTATION.md +25 -1
  230. package/server/lib/project-context/routes.http.test.js +18 -0
  231. package/server/lib/project-context/routes.js +16 -1
  232. package/server/lib/project-context/runtime.js +248 -23
  233. package/server/lib/project-context/runtime.test.js +106 -3
  234. package/server/lib/projects/DOCUMENTATION.md +76 -0
  235. package/server/lib/projects/project-config.js +144 -11
  236. package/server/lib/projects/project-id.js +17 -0
  237. package/server/lib/projects/project-setup.js +409 -0
  238. package/server/lib/projects/project-setup.test.js +557 -0
  239. package/server/lib/projects/routes.js +59 -0
  240. package/server/lib/quota/DOCUMENTATION.md +40 -2
  241. package/server/lib/quota/credentials/providers.js +4 -0
  242. package/server/lib/quota/credentials/store.js +4 -4
  243. package/server/lib/quota/credentials/store.test.js +8 -8
  244. package/server/lib/quota/index.js +1 -0
  245. package/server/lib/quota/providers/cline-pass.js +125 -0
  246. package/server/lib/quota/providers/cline-pass.test.js +103 -0
  247. package/server/lib/quota/providers/exe-dev.js +75 -0
  248. package/server/lib/quota/providers/exe-dev.test.js +47 -0
  249. package/server/lib/quota/providers/hyper.js +118 -0
  250. package/server/lib/quota/providers/hyper.test.js +126 -0
  251. package/server/lib/quota/providers/index.js +22 -0
  252. package/server/lib/quota/providers/neuralwatt.js +3 -6
  253. package/server/lib/quota/providers/neuralwatt.test.js +7 -7
  254. package/server/lib/quota/providers/ollama-cloud.js +32 -1
  255. package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
  256. package/server/lib/quota/providers/opencode-go.js +3 -2
  257. package/server/lib/quota/providers/opencode-go.test.js +35 -6
  258. package/server/lib/quota/providers/openrouter.js +119 -24
  259. package/server/lib/quota/providers/openrouter.test.js +330 -0
  260. package/server/lib/quota/providers/xai.js +1 -1
  261. package/server/lib/quota/routes.js +2 -0
  262. package/server/lib/quota/utils/auth.js +1 -1
  263. package/server/lib/relay/DOCUMENTATION.md +56 -1
  264. package/server/lib/relay/cross-compat.test.js +26 -0
  265. package/server/lib/relay/downstream-scheduler.js +154 -0
  266. package/server/lib/relay/downstream-scheduler.test.js +162 -0
  267. package/server/lib/relay/e2ee.js +10 -8
  268. package/server/lib/relay/flow-control.test.js +177 -0
  269. package/server/lib/relay/host-client.js +38 -9
  270. package/server/lib/relay/host-client.test.js +9 -1
  271. package/server/lib/relay/host-lock.js +1 -1
  272. package/server/lib/relay/service.js +5 -5
  273. package/server/lib/relay/tunnel-codec.js +9 -0
  274. package/server/lib/relay/tunnel-host.js +69 -18
  275. package/server/lib/relay/tunnel-host.test.js +9 -1
  276. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
  277. package/server/lib/scheduled-tasks/routes.js +5 -5
  278. package/server/lib/scheduled-tasks/service.js +25 -25
  279. package/server/lib/scheduled-tasks/service.test.js +1 -1
  280. package/server/lib/security/bind-host.js +4 -4
  281. package/server/lib/security/bind-host.test.js +1 -1
  282. package/server/lib/security/request-security.js +11 -5
  283. package/server/lib/security/request-security.test.js +14 -14
  284. package/server/lib/session-assist/runtime.js +15 -18
  285. package/server/lib/session-folders/routes.js +2 -2
  286. package/server/lib/session-folders/routes.test.js +8 -8
  287. package/server/lib/session-goal/DOCUMENTATION.md +20 -7
  288. package/server/lib/session-goal/create.js +4 -2
  289. package/server/lib/session-goal/create.test.js +1 -1
  290. package/server/lib/session-goal/objectives.js +4 -4
  291. package/server/lib/session-goal/routes.js +1 -1
  292. package/server/lib/session-goal/runtime.js +91 -30
  293. package/server/lib/session-goal/runtime.test.js +428 -2
  294. package/server/lib/session-knowledge/runtime.js +11 -11
  295. package/server/lib/session-knowledge/runtime.test.js +5 -5
  296. package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
  297. package/server/lib/skills-catalog/cache.test.js +2 -2
  298. package/server/lib/skills-catalog/disk-cache.js +5 -5
  299. package/server/lib/skills-catalog/github-meta.test.js +2 -2
  300. package/server/lib/skills-catalog/install.js +3 -2
  301. package/server/lib/small-model/DOCUMENTATION.md +7 -2
  302. package/server/lib/small-model/call.js +18 -7
  303. package/server/lib/small-model/call.test.js +58 -6
  304. package/server/lib/small-model/index.js +12 -16
  305. package/server/lib/small-model/index.test.js +1 -1
  306. package/server/lib/small-model/resolve.js +1 -1
  307. package/server/lib/small-model/resolve.test.js +1 -1
  308. package/server/lib/small-model/routes.js +2 -1
  309. package/server/lib/small-model/runtime-providers.js +15 -7
  310. package/server/lib/small-model/runtime-providers.test.js +7 -1
  311. package/server/lib/system-prompt/runtime.js +3 -22
  312. package/server/lib/system-prompt/runtime.test.js +3 -3
  313. package/server/lib/terminal/DOCUMENTATION.md +14 -10
  314. package/server/lib/terminal/runtime.js +190 -31
  315. package/server/lib/terminal/runtime.test.js +572 -38
  316. package/server/lib/terminal/shells.js +20 -3
  317. package/server/lib/terminal/shells.test.js +33 -2
  318. package/server/lib/tts/base-url.js +3 -3
  319. package/server/lib/tts/routes.test.js +20 -20
  320. package/server/lib/ui-auth/ui-auth.js +12 -9
  321. package/server/lib/ui-auth/ui-auth.test.js +19 -3
  322. package/server/lib/ui-auth/ui-passkeys.js +7 -7
  323. package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
  324. package/server/lib/walkthrough/index.js +1 -0
  325. package/server/lib/walkthrough/jobs.test.js +1 -1
  326. package/server/lib/walkthrough/language.test.js +1 -1
  327. package/server/lib/walkthrough/model-settings.js +11 -15
  328. package/server/lib/walkthrough/model-settings.test.js +1 -1
  329. package/server/lib/walkthrough/prompt.js +3 -1
  330. package/server/lib/walkthrough/pull-request.test.js +5 -5
  331. package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
  332. package/server/lib/walkthrough/routes.test.js +26 -5
  333. package/server/lib/walkthrough/sources.js +23 -2
  334. package/server/lib/walkthrough/store.js +3 -3
  335. package/server/lib/walkthrough/store.test.js +1 -1
  336. package/server/opencode-proxy.test.js +2 -2
  337. package/server/proxy-headers.js +1 -1
  338. package/server/sse-routes.test.js +4 -4
  339. package/README.md.bak +0 -250
  340. package/bin/lib/DOCUMENTATION.md.bak +0 -163
  341. package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
  342. package/dist/assets/DiffView-DluiRSla.js +0 -4
  343. package/dist/assets/FilesView-Bdq8ppva.js +0 -17
  344. package/dist/assets/GitView-Znn7VJcP.js +0 -6
  345. package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
  346. package/dist/assets/MarkdownImageGallery-ClOvuZxC.js +0 -1
  347. package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
  348. package/dist/assets/OnboardingScreen-D2DG7K6b.js +0 -1
  349. package/dist/assets/PierreDiffViewer-B7m3f0d7.js +0 -157
  350. package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
  351. package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
  352. package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
  353. package/dist/assets/WalkthroughView-egQgxel3.js +0 -4
  354. package/dist/assets/contextPanelEmbeddedChat-C3fjDbdg.js +0 -1
  355. package/dist/assets/de-C1P6X5Rs.js +0 -9
  356. package/dist/assets/es-CDgaF1_t.js +0 -15
  357. package/dist/assets/fr-Bgxqvvz_.js +0 -13
  358. package/dist/assets/index-C57jT0Sa.css +0 -1
  359. package/dist/assets/index-DW_E3rnL.js +0 -7
  360. package/dist/assets/ja-BDRrEcNP.js +0 -15
  361. package/dist/assets/ko-Ba8sQV79.js +0 -15
  362. package/dist/assets/main-Bv5Pkngh.js +0 -928
  363. package/dist/assets/markdownCore-CZwzz5gw.js +0 -3
  364. package/dist/assets/patchFileDiff-yNfWXN4E.js +0 -10
  365. package/dist/assets/pl-Dioz29BI.js +0 -15
  366. package/dist/assets/pt-BR-DIs6ElwV.js +0 -15
  367. package/dist/assets/renderElectronMiniChatApp-BcKLHXKe.js +0 -2
  368. package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
  369. package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
  370. package/dist/assets/scheduledTasksApi-DLW0mkQK.js +0 -1
  371. package/dist/assets/tr-DFnMlPxY.js +0 -6
  372. package/dist/assets/uk-ZrCNjM7Y.js +0 -15
  373. package/dist/assets/useAppFontEffects-CjrntdL0.js +0 -7310
  374. package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
  375. package/dist/assets/useRootScrollLock-BHYPkrLv.js +0 -1
  376. package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
  377. package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
  378. package/dist/assets/zh-CN-iv_AV4EC.js +0 -15
  379. package/dist/assets/zh-TW-CzEUk38j.js +0 -15
  380. package/server/index.d.ts.bak +0 -39
  381. package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
  382. package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
  383. package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
  384. package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
  385. package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
  386. package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
  387. package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
  388. package/server/lib/git/DOCUMENTATION.md.bak +0 -193
  389. package/server/lib/github/DOCUMENTATION.md.bak +0 -183
  390. package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
  391. package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
  392. package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
  393. package/server/lib/notifications/APNS.md.bak +0 -135
  394. package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
  395. package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
  396. package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
  397. package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
  398. package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
  399. package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
  400. package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
  401. package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
  402. package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
  403. package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
  404. package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
  405. package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
  406. package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
  407. package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
  408. package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
  409. package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
  410. package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
  411. package/server/lib/text/DOCUMENTATION.md.bak +0 -35
  412. package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
  413. package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
  414. package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
  415. package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
@@ -0,0 +1,126 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { fetchQuota, isConfigured } from './hyper.js';
3
+
4
+ const readAuth = () => ({ hyper: { key: 'test-token' } });
5
+
6
+ // https://hyper.charm.land/docs/api/credits.html documents the balance payload.
7
+ // https://hyper.charm.land/faq defines one Hypercredit as $0.05.
8
+ describe('Charm Hyper quota provider', () => {
9
+ it.each([
10
+ [100, '100', '$5.00'],
11
+ ['50', '50', '$2.50'],
12
+ [25.5, '25.50', '$1.28'],
13
+ [0, '0', '$0.00'],
14
+ ['0', '0', '$0.00'],
15
+ ])('formats balance %s without an untranslated unit', async (balance, credits, dollars) => {
16
+ const result = await fetchQuota({
17
+ readAuth,
18
+ fetchImpl: async () => Response.json({ balance }),
19
+ });
20
+
21
+ expect(result.ok).toBe(true);
22
+ expect(result.providerId).toBe('hyper');
23
+ expect(result.configured).toBe(true);
24
+ expect(result.usage.windows.credits.valueLabel).toBe(credits);
25
+ expect(result.usage.windows.credits_balance.valueLabel).toBe(dollars);
26
+ for (const window of Object.values(result.usage.windows)) {
27
+ expect(window.usedPercent).toBeNull();
28
+ expect(window.remainingPercent).toBeNull();
29
+ expect(window.windowSeconds).toBeNull();
30
+ expect(window.resetAt).toBeNull();
31
+ expect(window.resetAfterSeconds).toBeNull();
32
+ }
33
+ });
34
+
35
+ it.each([
36
+ {}, null, [], { balance: '' }, { balance: ' \t ' }, { balance: 'NaN' },
37
+ { balance: 'Infinity' }, { balance: null }, { balance: true }, { balance: [] },
38
+ { balance: {} },
39
+ ])('rejects invalid payload %j instead of showing zero', async (payload) => {
40
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => Response.json(payload) });
41
+
42
+ expect(result.ok).toBe(false);
43
+ expect(result.configured).toBe(true);
44
+ expect(result.error).toBe('No quota data in response');
45
+ expect(result.usage).toBeNull();
46
+ });
47
+
48
+ it.each([
49
+ { hyper: { key: 'test-token' } },
50
+ { hyper: { token: 'test-token' } },
51
+ { hyper: 'test-token' },
52
+ { hyper: { key: ' ', token: 'test-token' } },
53
+ { hyper: { key: 42, token: 'test-token' } },
54
+ ])('uses a validated credential for the documented request', async (auth) => {
55
+ expect(isConfigured(auth)).toBe(true);
56
+ let requests = 0;
57
+ const result = await fetchQuota({
58
+ readAuth: () => auth,
59
+ fetchImpl: async (url, options) => {
60
+ requests += 1;
61
+ expect(url).toBe('https://hyper.charm.land/v1/credits');
62
+ expect(options.method).toBe('GET');
63
+ expect(new Headers(options.headers).get('Authorization')).toBe('Bearer test-token');
64
+ expect(options.signal).toBeInstanceOf(AbortSignal);
65
+ return Response.json({ balance: 100 });
66
+ },
67
+ });
68
+
69
+ expect(requests).toBe(1);
70
+ expect(result.ok).toBe(true);
71
+ expect(JSON.stringify(result)).not.toContain('test-token');
72
+ });
73
+
74
+ it.each([{}, { hyper: { key: '' } }, { hyper: { key: ' ' } }, { hyper: { key: 42 } }])(
75
+ 'does not request usage without a valid credential',
76
+ async (auth) => {
77
+ expect(isConfigured(auth)).toBe(false);
78
+ let requests = 0;
79
+ const result = await fetchQuota({
80
+ readAuth: () => auth,
81
+ fetchImpl: async () => {
82
+ requests += 1;
83
+ return Response.json({ balance: 100 });
84
+ },
85
+ });
86
+
87
+ expect(requests).toBe(0);
88
+ expect(result.ok).toBe(false);
89
+ expect(result.configured).toBe(false);
90
+ expect(result.error).toBe('Not configured');
91
+ },
92
+ );
93
+
94
+ it.each([
95
+ [401, 'Session expired — please re-authenticate with Charm Hyper'],
96
+ [403, 'Session expired — please re-authenticate with Charm Hyper'],
97
+ [429, 'API error: 429'],
98
+ [500, 'API error: 500'],
99
+ ])('reports HTTP %s as a failure', async (status, error) => {
100
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => new Response(null, { status }) });
101
+
102
+ expect(result.ok).toBe(false);
103
+ expect(result.configured).toBe(true);
104
+ expect(result.error).toBe(error);
105
+ expect(result.usage).toBeNull();
106
+ });
107
+
108
+ it('reports invalid JSON as a parse failure', async () => {
109
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => new Response('{') });
110
+ expect(result.error).toBe('Invalid response from provider');
111
+ expect(result.ok).toBe(false);
112
+ expect(result.configured).toBe(true);
113
+ expect(result.usage).toBeNull();
114
+ });
115
+
116
+ it.each([
117
+ [new DOMException('Timed out', 'TimeoutError'), 'Request timed out'],
118
+ [new Error('Network unavailable'), 'Network unavailable'],
119
+ ])('reports request failure', async (failure, message) => {
120
+ const result = await fetchQuota({ readAuth, fetchImpl: async () => { throw failure; } });
121
+ expect(result.error).toBe(message);
122
+ expect(result.ok).toBe(false);
123
+ expect(result.configured).toBe(true);
124
+ expect(result.usage).toBeNull();
125
+ });
126
+ });
@@ -8,12 +8,15 @@
8
8
  import { buildResult } from '../utils/index.js';
9
9
 
10
10
  import * as claude from './claude/index.js';
11
+ import * as clinePass from './cline-pass.js';
11
12
  import * as codex from './codex.js';
12
13
  import * as copilot from './copilot.js';
13
14
  import * as crof from './crof.js';
14
15
  import * as cursor from './cursor.js';
15
16
  import * as deepseek from './deepseek.js';
17
+ import * as exeDev from './exe-dev.js';
16
18
  import * as google from './google/index.js';
19
+ import * as hyper from './hyper.js';
17
20
  import * as kimi from './kimi.js';
18
21
  import * as nanogpt from './nanogpt.js';
19
22
  import * as openai from './openai.js';
@@ -35,6 +38,12 @@ const registry = {
35
38
  isConfigured: claude.isConfigured,
36
39
  fetchQuota: claude.fetchQuota
37
40
  },
41
+ 'cline-pass': {
42
+ providerId: clinePass.providerId,
43
+ providerName: clinePass.providerName,
44
+ isConfigured: clinePass.isConfigured,
45
+ fetchQuota: clinePass.fetchQuota
46
+ },
38
47
  codex: {
39
48
  providerId: codex.providerId,
40
49
  providerName: codex.providerName,
@@ -59,12 +68,24 @@ const registry = {
59
68
  isConfigured: deepseek.isConfigured,
60
69
  fetchQuota: deepseek.fetchQuota
61
70
  },
71
+ 'exe-dev': {
72
+ providerId: exeDev.providerId,
73
+ providerName: exeDev.providerName,
74
+ isConfigured: exeDev.isConfigured,
75
+ fetchQuota: exeDev.fetchQuota
76
+ },
62
77
  google: {
63
78
  providerId: google.providerId,
64
79
  providerName: google.providerName,
65
80
  isConfigured: google.isConfigured,
66
81
  fetchQuota: google.fetchGoogleQuota
67
82
  },
83
+ hyper: {
84
+ providerId: hyper.providerId,
85
+ providerName: hyper.providerName,
86
+ isConfigured: hyper.isConfigured,
87
+ fetchQuota: hyper.fetchQuota
88
+ },
68
89
  'zai-coding-plan': {
69
90
  providerId: zai.providerId,
70
91
  providerName: zai.providerName,
@@ -213,6 +234,7 @@ export const fetchGoogleQuota = google.fetchGoogleQuota;
213
234
  export const fetchCodexQuota = codex.fetchQuota;
214
235
  export const fetchCursorQuota = cursor.fetchQuota;
215
236
  export const fetchDeepseekQuota = deepseek.fetchQuota;
237
+ export const fetchHyperQuota = hyper.fetchQuota;
216
238
  export const fetchCopilotQuota = copilot.fetchQuota;
217
239
  export const fetchCopilotAddonQuota = copilot.fetchQuotaAddon;
218
240
  export const fetchKimiQuota = kimi.fetchQuota;
@@ -74,7 +74,6 @@ export const fetchQuota = async () => {
74
74
  const subscription = payload?.subscription ?? null;
75
75
  const inOverage = Boolean(subscription?.in_overage);
76
76
  const allowance = payload?.key?.allowance ?? null;
77
- const keyName = payload?.key?.name ?? null;
78
77
  const creditsRemaining = toNumber(payload?.balance?.credits_remaining_usd);
79
78
 
80
79
  const windows = {};
@@ -116,19 +115,17 @@ export const fetchQuota = async () => {
116
115
  : (spent !== null && effectiveLimit !== null && effectiveLimit > 0
117
116
  ? Math.max(0, Math.min(100, (spent / effectiveLimit) * 100))
118
117
  : null);
119
- // Window title is the localized period label (daily/weekly/monthly); key
120
- // name is attached via valueLabel for identification (wafer precedent).
118
+ // Window title is the localized period label (daily/weekly/monthly); the
119
+ // usage value stays a percent so the UI's display-mode toggle applies.
121
120
  const periodKey = (period === 'daily' || period === 'weekly' || period === 'monthly' || period === 'month')
122
121
  ? (period === 'month' ? 'monthly' : period)
123
122
  : 'billing_cycle';
124
- const labelName = asNonEmptyString(keyName);
125
123
  const resetAt = toTimestamp(allowance.reset_at);
126
124
  const windowSeconds = period ? periodToWindowSeconds(period) : null;
127
125
  windows[periodKey] = toUsageWindow({
128
126
  usedPercent,
129
127
  windowSeconds,
130
- resetAt,
131
- ...(labelName ? { valueLabel: labelName } : {})
128
+ resetAt
132
129
  });
133
130
  } else if (creditsRemaining !== null) {
134
131
  windows.credits_balance = toUsageWindow({
@@ -89,7 +89,7 @@ describe('NeuralWatt quota provider', () => {
89
89
  expect(result.usage.windows.credits_balance.valueLabel).toBe('$32.68');
90
90
  });
91
91
 
92
- it('surfaces subscription and allowance windows (allowance keyed by period, key name in valueLabel)', async () => {
92
+ it('surfaces subscription and allowance windows (allowance keyed by period, percent value)', async () => {
93
93
  const payload = {
94
94
  ...DOCUMENTED_SUBSCRIPTION_PAYLOAD,
95
95
  balance: { credits_remaining_usd: 200 },
@@ -107,11 +107,11 @@ describe('NeuralWatt quota provider', () => {
107
107
  expect(subWindow.usedPercent).toBeCloseTo((13.9023 / 20.0) * 100, 4);
108
108
 
109
109
  // Allowance window is keyed by the localized period label ("monthly");
110
- // key name flows through valueLabel for identification.
110
+ // the usage value stays a percent — no key-name valueLabel.
111
111
  const allowWindow = result.usage.windows.monthly;
112
112
  expect(allowWindow).toBeDefined();
113
113
  expect(allowWindow.usedPercent).toBe(25);
114
- expect(allowWindow.valueLabel).toBe('Prod');
114
+ expect(allowWindow.valueLabel).toBeUndefined();
115
115
  expect(allowWindow.resetAt).toBe(Date.parse('2026-08-01T00:00:00Z'));
116
116
 
117
117
  // credits_balance suppressed because allowance is present
@@ -137,7 +137,7 @@ describe('NeuralWatt quota provider', () => {
137
137
  expect(window.usedPercent).toBeCloseTo((25 / 55) * 100, 4);
138
138
  expect(window.windowSeconds).toBe(30 * 86400);
139
139
  expect(window.resetAt).toBe(Date.parse('2026-08-01T00:00:00Z'));
140
- expect(window.valueLabel).toBe('prod-key');
140
+ expect(window.valueLabel).toBeUndefined();
141
141
  expect(result.usage.windows.credits_balance).toBeUndefined();
142
142
  });
143
143
 
@@ -176,7 +176,7 @@ describe('NeuralWatt quota provider', () => {
176
176
  expect(window).toBeDefined();
177
177
  expect(window.windowSeconds).toBe(604800);
178
178
  expect(window.resetAt).toBe(Date.parse('2026-07-04T00:00:00Z'));
179
- expect(window.valueLabel).toBe('Prod');
179
+ expect(window.valueLabel).toBeUndefined();
180
180
  });
181
181
 
182
182
  it('uses daily as the allowance key when period is daily', async () => {
@@ -216,7 +216,7 @@ describe('NeuralWatt quota provider', () => {
216
216
  expect(window.usedPercent).toBe(25);
217
217
  });
218
218
 
219
- it('marks blocked allowance as 100% with valueLabel set', async () => {
219
+ it('marks blocked allowance as 100% with percent value', async () => {
220
220
  const payload = {
221
221
  balance: { credits_remaining_usd: 30 },
222
222
  subscription: null,
@@ -231,7 +231,7 @@ describe('NeuralWatt quota provider', () => {
231
231
 
232
232
  const window = result.usage.windows.monthly;
233
233
  expect(window.usedPercent).toBe(100);
234
- expect(window.valueLabel).toBe('sample');
234
+ expect(window.valueLabel).toBeUndefined();
235
235
  });
236
236
 
237
237
  it('falls back to credits_balance when neither subscription nor allowance exists', async () => {
@@ -35,6 +35,37 @@ export const parseOllamaSettingsHtml = (html) => {
35
35
  valueLabel: `${used ?? 0} / ${total ?? 0}`
36
36
  });
37
37
  }
38
+ // Cost-based plans render "Monthly usage" with a dollar amount instead of
39
+ // session/weekly/premium windows; support both page shapes.
40
+ const monthlyMatch = html.match(/Monthly\s+usage[\s\S]{0,200}?\$([0-9][0-9,.]*)\s+of\s+\$([0-9][0-9,.]*)/i);
41
+ if (monthlyMatch) {
42
+ const used = toNumber(monthlyMatch[1].replace(/,/g, ''));
43
+ const total = toNumber(monthlyMatch[2].replace(/,/g, ''));
44
+ const usedPercent = total && used !== null ? Math.min(100, (used / total) * 100) : null;
45
+ windows.monthly = toUsageWindow({
46
+ usedPercent,
47
+ windowSeconds: null,
48
+ resetAt: null,
49
+ valueLabel: `$${monthlyMatch[1]} / $${monthlyMatch[2]}`
50
+ });
51
+ }
52
+ // "Extra usage" credits block (visible when credits/auto-reload is enabled):
53
+ // a balance, not a percent. Anchor on "Balance remaining" — nearby "Add $5"
54
+ // and auto-reload copy also contain dollar amounts. Surfaced with the
55
+ // credits_balance key and OpenAI-style plain money label (the UI renders
56
+ // it as "Credits Balance"); a $0 balance is omitted rather than shown.
57
+ const balanceMatch = html.match(/Balance\s+remaining[\s\S]{0,200}?\$([0-9][0-9,.]*)/i);
58
+ if (balanceMatch) {
59
+ const balance = toNumber(balanceMatch[1].replace(/,/g, ''));
60
+ if (balance !== 0) {
61
+ windows.credits_balance = toUsageWindow({
62
+ usedPercent: null,
63
+ windowSeconds: null,
64
+ resetAt: null,
65
+ valueLabel: `$${balanceMatch[1]}`
66
+ });
67
+ }
68
+ }
38
69
  return windows;
39
70
  };
40
71
 
@@ -45,7 +76,7 @@ export const isConfigured = () => {
45
76
  export const fetchOllamaCloudUsage = async (credential, fetchImpl = fetch) => {
46
77
  const response = await fetchImpl('https://ollama.com/settings', {
47
78
  method: 'GET',
48
- headers: { Cookie: credential.cookie, 'User-Agent': 'OpenChamber quota provider' },
79
+ headers: { Cookie: credential.cookie, 'User-Agent': 'AiWorker quota provider' },
49
80
  redirect: 'manual',
50
81
  signal: AbortSignal.timeout(15_000),
51
82
  });
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'bun:test';
2
- import { fetchOllamaCloudUsage } from './ollama-cloud.js';
2
+ import { parseOllamaSettingsHtml, fetchOllamaCloudUsage } from './ollama-cloud.js';
3
3
 
4
4
  describe('Ollama Cloud quota provider', () => {
5
5
  it('rejects redirects without forwarding credentials', async () => {
@@ -9,4 +9,36 @@ describe('Ollama Cloud quota provider', () => {
9
9
  it('rejects successful pages without usage data', async () => {
10
10
  await expect(fetchOllamaCloudUsage({ cookie: 'session=secret' }, async () => new Response('<html></html>'))).rejects.toThrow('could not be parsed');
11
11
  });
12
+
13
+ it('parses session/weekly/premium windows', () => {
14
+ const windows = parseOllamaSettingsHtml('<p>Session usage: 42%</p><p>Weekly usage: 7%</p><p>Premium 120 / 1000</p>');
15
+ expect(windows.session.usedPercent).toBe(42);
16
+ expect(windows.weekly.usedPercent).toBe(7);
17
+ expect(windows.premium.usedPercent).toBe(12);
18
+ expect(windows.premium.valueLabel).toBe('120 / 1000');
19
+ });
20
+
21
+ it('parses the cost-based monthly usage shape', () => {
22
+ const html = '<span class="text-sm">Monthly usage</span>\n <span class="text-sm "\n >$4.39 of $60 used</span\n >';
23
+ const windows = parseOllamaSettingsHtml(html);
24
+ expect(windows.monthly.usedPercent).toBeCloseTo(7.3, 1);
25
+ expect(windows.monthly.valueLabel).toBe('$4.39 / $60');
26
+ expect(windows.monthly.remainingPercent).toBeCloseTo(92.7, 1);
27
+ });
28
+
29
+ it('parses the extra-usage credits balance', () => {
30
+ const html = '<div><span>Balance remaining</span><span>$12.50</span></div><button>Add $5</button>';
31
+ const windows = parseOllamaSettingsHtml(html);
32
+ expect(windows.credits_balance.usedPercent).toBeNull();
33
+ expect(windows.credits_balance.valueLabel).toBe('$12.50');
34
+ });
35
+
36
+ it('omits the credits balance when it is zero', () => {
37
+ const html = '<div><span>Balance remaining</span><span>$0.00</span></div>';
38
+ expect(parseOllamaSettingsHtml(html).credits_balance).toBeUndefined();
39
+ });
40
+
41
+ it('ignores nearby dollar amounts that are not the balance', () => {
42
+ expect(parseOllamaSettingsHtml('<p>Add $5 to your balance when it hits $0</p>').credits_balance).toBeUndefined();
43
+ });
12
44
  });
@@ -25,7 +25,7 @@ export const parseOpenCodeGoUsage = (payload) => {
25
25
  if (typeof resetAt !== 'string' || !Number.isFinite(new Date(resetAt).getTime())) continue;
26
26
  windows[key] = toUsageWindow({
27
27
  usedPercent: Math.min(100, Math.max(0, usedPercent)),
28
- resetAt,
28
+ resetAt: new Date(resetAt).getTime(),
29
29
  windowSeconds: null,
30
30
  });
31
31
  }
@@ -37,7 +37,8 @@ export const fetchOpenCodeGoUsage = async (apiKey, fetchImpl = fetch) => {
37
37
  headers: {
38
38
  Accept: 'application/json',
39
39
  Authorization: `Bearer ${apiKey}`,
40
- 'User-Agent': 'OpenChamber quota provider',
40
+ 'x-opencode-session': 'openchamber-usage',
41
+ 'User-Agent': 'AiWorker quota provider',
41
42
  },
42
43
  signal: AbortSignal.timeout(15_000),
43
44
  });
@@ -3,9 +3,9 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
5
5
 
6
- const previousDataDirectory = process.env.OPENCHAMBER_DATA_DIR;
6
+ const previousDataDirectory = process.env.AIWORKER_DATA_DIR;
7
7
  const temporaryDataDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-opencode-go-'));
8
- process.env.OPENCHAMBER_DATA_DIR = temporaryDataDirectory;
8
+ process.env.AIWORKER_DATA_DIR = temporaryDataDirectory;
9
9
 
10
10
  vi.mock('../../opencode/auth.js', () => ({
11
11
  readAuthFile: () => ({ 'opencode-go': { key: 'test-key' } }),
@@ -18,8 +18,8 @@ afterEach(() => {
18
18
  });
19
19
 
20
20
  afterAll(() => {
21
- if (previousDataDirectory === undefined) delete process.env.OPENCHAMBER_DATA_DIR;
22
- else process.env.OPENCHAMBER_DATA_DIR = previousDataDirectory;
21
+ if (previousDataDirectory === undefined) delete process.env.AIWORKER_DATA_DIR;
22
+ else process.env.AIWORKER_DATA_DIR = previousDataDirectory;
23
23
  fs.rmSync(temporaryDataDirectory, { recursive: true, force: true });
24
24
  });
25
25
 
@@ -27,11 +27,36 @@ describe('OpenCode Go quota provider', () => {
27
27
  it('parses partial API usage windows', () => {
28
28
  const windows = parseOpenCodeGoUsage({ usage: { rolling: { percent: 25, resetsAt: '2026-08-12T12:00:00.000Z' }, weekly: { percent: 40, resetsAt: '2026-08-19T12:00:00.000Z' } } });
29
29
  expect(windows['5h'].usedPercent).toBe(25);
30
- expect(windows['5h'].resetAt).toBe('2026-08-12T12:00:00.000Z');
30
+ expect(windows['5h'].resetAt).toBe(Date.parse('2026-08-12T12:00:00.000Z'));
31
31
  expect(windows.weekly.usedPercent).toBe(40);
32
32
  expect(windows.monthly).toBeUndefined();
33
33
  });
34
34
 
35
+ it('returns numeric reset timestamps for all usage windows', async () => {
36
+ const resetsAt = '2026-10-01T00:00:00.000Z';
37
+ const windows = await fetchOpenCodeGoUsage('test-key', async () => new Response(JSON.stringify({
38
+ usage: {
39
+ rolling: { percent: 25, resetsAt },
40
+ weekly: { percent: 40, resetsAt },
41
+ monthly: { percent: 60, resetsAt },
42
+ },
43
+ })));
44
+ expect(Object.keys(windows)).toEqual(['5h', 'weekly', 'monthly']);
45
+ for (const window of Object.values(windows)) {
46
+ expect(window.resetAt).toBe(Date.parse(resetsAt));
47
+ }
48
+ });
49
+
50
+ it('preserves valid windows when another reset date is invalid', () => {
51
+ const windows = parseOpenCodeGoUsage({ usage: {
52
+ rolling: { percent: 25, resetsAt: 'invalid' },
53
+ weekly: { percent: 40, resetsAt: '2026-08-19T12:00:00.000Z' },
54
+ monthly: { percent: 60, resetsAt: null },
55
+ } });
56
+ expect(Object.keys(windows)).toEqual(['weekly']);
57
+ expect(windows.weekly.resetAt).toBe(Date.parse('2026-08-19T12:00:00.000Z'));
58
+ });
59
+
35
60
  it('does not expose credentials in authentication errors', async () => {
36
61
  await expect(fetchOpenCodeGoUsage('secret', async () => new Response('', { status: 403 }))).rejects.toThrow('authentication failed');
37
62
  });
@@ -43,7 +68,11 @@ describe('OpenCode Go quota provider', () => {
43
68
  return new Response(JSON.stringify({ usage: { rolling: { percent: 25, resetsAt: '2026-08-12T12:00:00.000Z' } } }));
44
69
  });
45
70
  expect(request.url).toBe('https://opencode.ai/zen/go/v1/usage');
46
- expect(request.options.headers).toMatchObject({ Accept: 'application/json', Authorization: 'Bearer secret' });
71
+ expect(request.options.headers).toMatchObject({
72
+ Accept: 'application/json',
73
+ Authorization: 'Bearer secret',
74
+ 'x-opencode-session': 'openchamber-usage',
75
+ });
47
76
  expect(request.options.headers.Cookie).toBeUndefined();
48
77
  expect(usage['5h'].usedPercent).toBe(25);
49
78
  });
@@ -5,12 +5,30 @@ import {
5
5
  buildResult,
6
6
  toUsageWindow,
7
7
  toNumber,
8
+ asObject,
8
9
  formatMoney
9
10
  } from '../utils/index.js';
10
11
 
11
12
  export const providerId = 'openrouter';
12
13
  export const providerName = 'OpenRouter';
13
- const aliases = ['openrouter'];
14
+ export const aliases = ['openrouter'];
15
+ const OPENROUTER_QUOTA_URL = 'https://openrouter.ai/api/v1/key';
16
+ const PERIOD_SECONDS = { daily: 86400, weekly: 604800, monthly: 30 * 86400 };
17
+
18
+ export const resolveResetAt = (limitReset, nowMs) => {
19
+ const now = new Date(nowMs);
20
+ const y = now.getUTCFullYear();
21
+ const m = now.getUTCMonth();
22
+ const d = now.getUTCDate();
23
+
24
+ if (limitReset === 'daily') return Date.UTC(y, m, d + 1);
25
+ if (limitReset === 'weekly') {
26
+ const dow = now.getUTCDay();
27
+ return Date.UTC(y, m, d + ((8 - dow) % 7 || 7));
28
+ }
29
+ if (limitReset === 'monthly') return Date.UTC(y, m + 1, 1);
30
+ return null;
31
+ };
14
32
 
15
33
  export const isConfigured = () => {
16
34
  const auth = readAuthFile();
@@ -33,13 +51,16 @@ export const fetchQuota = async () => {
33
51
  });
34
52
  }
35
53
 
54
+ const timeoutSignal = AbortSignal.timeout(15_000);
55
+
36
56
  try {
37
- const response = await fetch('https://openrouter.ai/api/v1/credits', {
57
+ const response = await fetch(OPENROUTER_QUOTA_URL, {
38
58
  method: 'GET',
39
59
  headers: {
40
60
  Authorization: `Bearer ${apiKey}`,
41
- 'Content-Type': 'application/json'
42
- }
61
+ 'Accept-Encoding': 'identity'
62
+ },
63
+ signal: timeoutSignal
43
64
  });
44
65
 
45
66
  if (!response.ok) {
@@ -48,45 +69,119 @@ export const fetchQuota = async () => {
48
69
  providerName,
49
70
  ok: false,
50
71
  configured: true,
51
- error: `API error: ${response.status}`
72
+ error: response.status === 401 || response.status === 403
73
+ ? 'Session expired — please re-authenticate with OpenRouter'
74
+ : `API error: ${response.status}`
52
75
  });
53
76
  }
54
77
 
55
78
  const payload = await response.json();
56
- const credits = payload?.data ?? {};
57
- const totalCredits = toNumber(credits.total_credits);
58
- const totalUsage = toNumber(credits.total_usage);
59
- const remaining = totalCredits !== null && totalUsage !== null
60
- ? Math.max(0, totalCredits - totalUsage)
61
- : null;
62
- let valueLabel = null;
63
- if (remaining !== null && totalUsage !== null) {
64
- valueLabel = `$${formatMoney(remaining)} left · $${formatMoney(totalUsage)} spent`;
79
+ const data = asObject(payload?.data);
80
+
81
+ if (data === null) {
82
+ return buildResult({
83
+ providerId,
84
+ providerName,
85
+ ok: false,
86
+ configured: true,
87
+ error: 'No quota data in response'
88
+ });
89
+ }
90
+
91
+ if (data.is_management_key === true) {
92
+ return buildResult({
93
+ providerId,
94
+ providerName,
95
+ ok: false,
96
+ configured: true,
97
+ error: 'Management key configured — quota needs an inference API key'
98
+ });
65
99
  }
66
100
 
67
- const windows = {
68
- credits: toUsageWindow({
69
- usedPercent: null,
70
- windowSeconds: null,
71
- resetAt: null,
72
- valueLabel
73
- })
74
- };
101
+ const limit = toNumber(data.limit);
102
+ const limitRemaining = toNumber(data.limit_remaining);
103
+ if (limit !== null && limitRemaining === null) {
104
+ return buildResult({
105
+ providerId,
106
+ providerName,
107
+ ok: false,
108
+ configured: true,
109
+ error: 'No quota data in response'
110
+ });
111
+ }
112
+
113
+ const usageMonthly = toNumber(data.usage_monthly);
114
+ if (limit === null && usageMonthly === null) {
115
+ return buildResult({
116
+ providerId,
117
+ providerName,
118
+ ok: false,
119
+ configured: true,
120
+ error: 'No quota data in response'
121
+ });
122
+ }
123
+
124
+ const nowMs = Date.now();
125
+ let windowKey;
126
+ let windowSeconds;
127
+ let resetAt;
128
+ let usedPercent;
129
+ let valueLabel;
130
+
131
+ if (limit === null) {
132
+ windowKey = 'monthly';
133
+ windowSeconds = PERIOD_SECONDS.monthly;
134
+ resetAt = resolveResetAt('monthly', nowMs);
135
+ usedPercent = null;
136
+ valueLabel = `$${formatMoney(usageMonthly)} spent`;
137
+ } else {
138
+ const used = Math.max(0, limit - limitRemaining);
139
+ const percent = limit > 0 ? (used / limit) * 100 : null;
140
+ usedPercent = percent === null ? null : Math.min(100, percent);
141
+ valueLabel = `$${formatMoney(used)} / $${formatMoney(limit)}`;
142
+
143
+ if (Object.hasOwn(PERIOD_SECONDS, data.limit_reset)) {
144
+ windowKey = data.limit_reset;
145
+ windowSeconds = PERIOD_SECONDS[data.limit_reset];
146
+ resetAt = resolveResetAt(data.limit_reset, nowMs);
147
+ } else {
148
+ windowKey = 'credits';
149
+ windowSeconds = null;
150
+ resetAt = null;
151
+ }
152
+ }
75
153
 
76
154
  return buildResult({
77
155
  providerId,
78
156
  providerName,
79
157
  ok: true,
80
158
  configured: true,
81
- usage: { windows }
159
+ usage: {
160
+ windows: {
161
+ [windowKey]: toUsageWindow({
162
+ usedPercent,
163
+ windowSeconds,
164
+ resetAt,
165
+ valueLabel
166
+ })
167
+ }
168
+ }
82
169
  });
83
170
  } catch (error) {
171
+ const isTimeout = error instanceof DOMException && (
172
+ error.name === 'TimeoutError' || (error.name === 'AbortError' && timeoutSignal.aborted)
173
+ );
174
+ const isParseError = error instanceof SyntaxError;
84
175
  return buildResult({
85
176
  providerId,
86
177
  providerName,
87
178
  ok: false,
88
179
  configured: true,
89
- error: error instanceof Error ? error.message : 'Request failed'
180
+ error: isTimeout
181
+ ? 'Request timed out'
182
+ : isParseError
183
+ ? 'Invalid response from provider'
184
+ : (error instanceof Error ? error.message : 'Request failed')
90
185
  });
91
186
  }
92
187
  };