@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.
- package/README.md +4 -4
- package/bin/cli.js +14 -4
- package/bin/cli.test.js +165 -73
- package/bin/lib/cli-api-target.js +2 -2
- package/bin/lib/cli-args.js +119 -119
- package/bin/lib/cli-lifecycle.js +9 -9
- package/bin/lib/cli-network.js +3 -3
- package/bin/lib/cli-paths.js +3 -3
- package/bin/lib/cli-ports.js +2 -2
- package/bin/lib/cli-process.js +4 -4
- package/bin/lib/cli-startup.js +64 -15
- package/bin/lib/cli-tunnel-profiles.js +1 -1
- package/bin/lib/cli-tunnel-utils.js +2 -2
- package/bin/lib/commands-connect-url.js +19 -19
- package/bin/lib/commands-lifecycle.js +27 -27
- package/bin/lib/commands-logs.js +5 -5
- package/bin/lib/commands-models.js +1 -1
- package/bin/lib/commands-projects.js +1 -1
- package/bin/lib/commands-schedule.js +1 -1
- package/bin/lib/commands-serve.js +29 -29
- package/bin/lib/commands-session.js +1 -1
- package/bin/lib/commands-startup.js +45 -10
- package/bin/lib/commands-status.js +1 -1
- package/bin/lib/commands-tunnel.js +40 -40
- package/bin/lib/commands-update.js +1 -1
- package/bin/lib/commands-update.test.js +6 -6
- package/dist/assets/{CodeMirrorCommentWidgets-B3u7UIcE.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
- package/dist/assets/DiffView-DXjVsEOw.js +4 -0
- package/dist/assets/DirtyBranchSwitchDialog-aig6zJjg.js +1 -0
- package/dist/assets/FilesView-CJdG0-wk.js +17 -0
- package/dist/assets/GitView-BXsS3jG6.js +6 -0
- package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-D_hpXBnF.js} +1 -1
- package/dist/assets/LinearIssuesView-BoUiLQG1.js +1 -0
- package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
- package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
- package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
- package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-8p6nLJaU.js} +1 -1
- package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
- package/dist/assets/PlanView-CJTmOF6I.js +5 -0
- package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
- package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-Cpb4hM6q.js} +1 -1
- package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
- package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
- package/dist/assets/ToolPartDiffPreview-DtZLzrSK.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
- package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
- package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
- package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
- package/dist/assets/de-DCrsQ5zS.js +10 -0
- package/dist/assets/es-Dwv4YfH6.js +16 -0
- package/dist/assets/fr-BCWg4Cnx.js +14 -0
- package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
- package/dist/assets/index-B68h5S5B.css +1 -0
- package/dist/assets/index-DVwMarHI.js +39 -0
- package/dist/assets/{index-CODwDsU_.js → index-jXSK5RrB.js} +1 -1
- package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-B_x_rVon.js} +1 -1
- package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-DW_xMer1.js} +1 -1
- package/dist/assets/ja-Bruw3RYC.js +16 -0
- package/dist/assets/ko-idhXGF8j.js +16 -0
- package/dist/assets/main-BkyaQhJX.js +930 -0
- package/dist/assets/{main-C5DVOxc6.js → main-CzdPuL0s.js} +2 -2
- package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
- package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-CCkqKOLp.js} +2 -2
- package/dist/assets/{mobile-CcaxB0n4.js → mobile-mvxTo055.js} +2 -2
- package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
- package/dist/assets/pl-CGZWUqZJ.js +16 -0
- package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
- package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
- package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
- package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
- package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-CRl0SUC9.js} +1 -1
- package/dist/assets/surface-oMvhsog3.js +1 -0
- package/dist/assets/tr-BK_KiO2V.js +7 -0
- package/dist/assets/uk-CG74xV8N.js +16 -0
- package/dist/assets/useAppFontEffects-CPKeOpUo.js +7284 -0
- package/dist/assets/useGitComparison-DH220WmO.js +1 -0
- package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
- package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
- package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
- package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
- package/dist/assets/{web-components-Bhg83tjf.js → web-components-z7hfnS8n.js} +1 -1
- package/dist/assets/zh-CN-T4AXofZI.js +16 -0
- package/dist/assets/zh-TW-Dte40sxp.js +16 -0
- package/dist/index.html +21 -59
- package/dist/mini-chat.html +7 -7
- package/dist/mobile.html +5 -5
- package/package.json +3 -4
- package/server/index.d.ts +15 -0
- package/server/index.js +158 -89
- package/server/lib/agent-memory/actions.js +2 -2
- package/server/lib/agent-memory/feature-flag.js +1 -1
- package/server/lib/agent-memory/feature-flag.test.js +8 -8
- package/server/lib/agent-memory/routes.js +2 -2
- package/server/lib/agent-tool/runtime.js +37 -54
- package/server/lib/agent-tool/runtime.test.js +59 -59
- package/server/lib/browser-control/broker.js +3 -3
- package/server/lib/browser-control/broker.test.js +1 -1
- package/server/lib/changelog/update-notes.js +82 -0
- package/server/lib/changelog/update-notes.test.js +59 -0
- package/server/lib/client-auth/remote-clients.test.js +5 -5
- package/server/lib/context-obligatory/runtime.js +7 -7
- package/server/lib/context-obligatory/runtime.test.js +5 -5
- package/server/lib/data-dir-migration.js +40 -0
- package/server/lib/data-dir-migration.test.js +59 -0
- package/server/lib/dev-servers/parse.js +2 -2
- package/server/lib/dev-servers/parse.test.js +2 -2
- package/server/lib/dev-servers/routes.js +1 -1
- package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
- package/server/lib/dev-tunnel/client.js +1 -1
- package/server/lib/dev-tunnel/runtime.js +5 -5
- package/server/lib/dev-tunnel/tunnel.test.js +21 -1
- package/server/lib/dictation/local/model-catalog.js +1 -1
- package/server/lib/dictation/local/worker-process.js +2 -2
- package/server/lib/event-stream/directory-ws-bridge.js +1 -1
- package/server/lib/event-stream/global-ws-bridge.js +1 -1
- package/server/lib/event-stream/protocol.test.js +4 -4
- package/server/lib/event-stream/runtime.test.js +18 -18
- package/server/lib/fs/DOCUMENTATION.md +5 -0
- package/server/lib/fs/routes.js +68 -26
- package/server/lib/fs/routes.test.js +208 -22
- package/server/lib/git/DOCUMENTATION.md +32 -6
- package/server/lib/git/identity-storage.js +1 -1
- package/server/lib/git/issue-2746-longpaths.test.js +4 -4
- package/server/lib/git/routes.js +41 -5
- package/server/lib/git/service.js +300 -224
- package/server/lib/git/service.test.js +596 -28
- package/server/lib/github/auth.js +8 -8
- package/server/lib/github/pr-status.js +1 -1
- package/server/lib/github/routes.js +2 -2
- package/server/lib/inherited-env.js +17 -10
- package/server/lib/inherited-env.test.js +10 -11
- package/server/lib/linear/auth.js +8 -8
- package/server/lib/linear/auth.test.js +24 -24
- package/server/lib/linear/issues.test.js +6 -6
- package/server/lib/linear/mapping.test.js +4 -4
- package/server/lib/linear/oauth.js +2 -2
- package/server/lib/linear/oauth.test.js +14 -14
- package/server/lib/linear/routes.js +5 -5
- package/server/lib/linear/routes.test.js +16 -16
- package/server/lib/linear/status-runtime.test.js +9 -9
- package/server/lib/linear/status.js +1 -1
- package/server/lib/linear/status.test.js +15 -15
- package/server/lib/linear/teams.test.js +4 -4
- package/server/lib/magic-prompts/routes.js +2 -2
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
- package/server/lib/mcp-reconnect/runtime.js +122 -0
- package/server/lib/mcp-reconnect/runtime.test.js +140 -0
- package/server/lib/message-queue/DOCUMENTATION.md +161 -0
- package/server/lib/message-queue/runtime.js +870 -0
- package/server/lib/message-queue/runtime.test.js +482 -0
- package/server/lib/network-defaults.js +23 -0
- package/server/lib/network-defaults.test.js +52 -0
- package/server/lib/notifications/apns-runtime.js +17 -17
- package/server/lib/notifications/apns-runtime.test.js +11 -11
- package/server/lib/notifications/emitter-runtime.js +1 -1
- package/server/lib/notifications/emitter-runtime.test.js +1 -1
- package/server/lib/notifications/push-runtime.js +7 -7
- package/server/lib/notifications/routes.js +1 -1
- package/server/lib/notifications/runtime.js +1 -1
- package/server/lib/openchamber-control/action-resolution.test.js +10 -10
- package/server/lib/openchamber-control/actions.js +24 -24
- package/server/lib/openchamber-control/error.js +4 -4
- package/server/lib/openchamber-control/routes.js +2 -2
- package/server/lib/openchamber-control/routes.test.js +5 -5
- package/server/lib/openchamber-control/screenshots.js +1 -1
- package/server/lib/openchamber-control/screenshots.test.js +1 -1
- package/server/lib/openchamber-control/service.js +50 -50
- package/server/lib/openchamber-control/service.test.js +5 -5
- package/server/lib/openchamber-sessions/routes.js +124 -22
- package/server/lib/openchamber-sessions/routes.test.js +99 -11
- package/server/lib/opencode/DOCUMENTATION.md +30 -6
- package/server/lib/opencode/auth.js +1 -1
- package/server/lib/opencode/bootstrap-runtime.js +8 -6
- package/server/lib/opencode/cli-options.js +8 -8
- package/server/lib/opencode/config-paths.test.js +116 -0
- package/server/lib/opencode/core-routes.js +8 -7
- package/server/lib/opencode/core-routes.test.js +11 -11
- package/server/lib/opencode/env-config.js +5 -5
- package/server/lib/opencode/env-config.test.js +7 -7
- package/server/lib/opencode/env-runtime.js +10 -10
- package/server/lib/opencode/env-runtime.test.js +18 -18
- package/server/lib/opencode/feature-routes-runtime.js +19 -12
- package/server/lib/opencode/hmr-state-runtime.js +2 -2
- package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
- package/server/lib/opencode/input-history-scope.js +14 -0
- package/server/lib/opencode/lifecycle.js +8 -8
- package/server/lib/opencode/lifecycle.test.js +4 -4
- package/server/lib/opencode/managed-plugin-config.js +35 -0
- package/server/lib/opencode/managed-process-registry.js +3 -3
- package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
- package/server/lib/opencode/openchamber-routes.js +79 -19
- package/server/lib/opencode/openchamber-routes.test.js +139 -12
- package/server/lib/opencode/plugins.js +2 -2
- package/server/lib/opencode/plugins.test.js +1 -1
- package/server/lib/opencode/project-icon-routes.js +2 -2
- package/server/lib/opencode/project-icon-routes.test.js +1 -1
- package/server/lib/opencode/providers.js +44 -2
- package/server/lib/opencode/providers.test.js +131 -4
- package/server/lib/opencode/proxy.js +2 -2
- package/server/lib/opencode/pwa-manifest-routes.js +1 -1
- package/server/lib/opencode/restart-session-recovery.test.js +1 -1
- package/server/lib/opencode/routes-upgrade.test.js +4 -4
- package/server/lib/opencode/routes.js +19 -17
- package/server/lib/opencode/server-startup-runtime.js +6 -6
- package/server/lib/opencode/session-runtime.js +3 -3
- package/server/lib/opencode/session-runtime.test.js +6 -6
- package/server/lib/opencode/settings-files.js +214 -0
- package/server/lib/opencode/settings-helpers.js +116 -44
- package/server/lib/opencode/settings-helpers.test.js +354 -12
- package/server/lib/opencode/settings-registry.json +747 -0
- package/server/lib/opencode/settings-runtime.js +141 -20
- package/server/lib/opencode/settings-runtime.test.js +191 -4
- package/server/lib/opencode/shared.js +5 -2
- package/server/lib/opencode/shared.test.js +7 -7
- package/server/lib/opencode/shutdown-runtime.js +2 -0
- package/server/lib/opencode/skill-routes.js +2 -1
- package/server/lib/opencode/snippets.js +1 -2
- package/server/lib/opencode/startup-performance.js +1 -1
- package/server/lib/opencode/startup-performance.test.js +6 -6
- package/server/lib/opencode/static-routes-runtime.js +8 -8
- package/server/lib/opencode/static-routes-runtime.test.js +7 -7
- package/server/lib/opencode/upgrade-capability.js +2 -2
- package/server/lib/opencode/upgrade-capability.test.js +3 -3
- package/server/lib/package-manager.js +17 -42
- package/server/lib/package-manager.test.js +19 -19
- package/server/lib/permission-auto-accept/runtime.js +1 -1
- package/server/lib/project-context/DOCUMENTATION.md +25 -1
- package/server/lib/project-context/routes.http.test.js +18 -0
- package/server/lib/project-context/routes.js +16 -1
- package/server/lib/project-context/runtime.js +248 -23
- package/server/lib/project-context/runtime.test.js +106 -3
- package/server/lib/projects/DOCUMENTATION.md +76 -0
- package/server/lib/projects/project-config.js +144 -11
- package/server/lib/projects/project-id.js +17 -0
- package/server/lib/projects/project-setup.js +409 -0
- package/server/lib/projects/project-setup.test.js +557 -0
- package/server/lib/projects/routes.js +59 -0
- package/server/lib/quota/DOCUMENTATION.md +40 -2
- package/server/lib/quota/credentials/providers.js +4 -0
- package/server/lib/quota/credentials/store.js +4 -4
- package/server/lib/quota/credentials/store.test.js +8 -8
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/cline-pass.js +125 -0
- package/server/lib/quota/providers/cline-pass.test.js +103 -0
- package/server/lib/quota/providers/exe-dev.js +75 -0
- package/server/lib/quota/providers/exe-dev.test.js +47 -0
- package/server/lib/quota/providers/hyper.js +118 -0
- package/server/lib/quota/providers/hyper.test.js +126 -0
- package/server/lib/quota/providers/index.js +22 -0
- package/server/lib/quota/providers/neuralwatt.js +3 -6
- package/server/lib/quota/providers/neuralwatt.test.js +7 -7
- package/server/lib/quota/providers/ollama-cloud.js +32 -1
- package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
- package/server/lib/quota/providers/opencode-go.js +3 -2
- package/server/lib/quota/providers/opencode-go.test.js +35 -6
- package/server/lib/quota/providers/openrouter.js +119 -24
- package/server/lib/quota/providers/openrouter.test.js +330 -0
- package/server/lib/quota/providers/xai.js +1 -1
- package/server/lib/quota/routes.js +2 -0
- package/server/lib/quota/utils/auth.js +1 -1
- package/server/lib/relay/DOCUMENTATION.md +56 -1
- package/server/lib/relay/cross-compat.test.js +26 -0
- package/server/lib/relay/downstream-scheduler.js +154 -0
- package/server/lib/relay/downstream-scheduler.test.js +162 -0
- package/server/lib/relay/e2ee.js +10 -8
- package/server/lib/relay/flow-control.test.js +177 -0
- package/server/lib/relay/host-client.js +38 -9
- package/server/lib/relay/host-client.test.js +9 -1
- package/server/lib/relay/host-lock.js +1 -1
- package/server/lib/relay/service.js +5 -5
- package/server/lib/relay/tunnel-codec.js +9 -0
- package/server/lib/relay/tunnel-host.js +69 -18
- package/server/lib/relay/tunnel-host.test.js +9 -1
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
- package/server/lib/scheduled-tasks/routes.js +5 -5
- package/server/lib/scheduled-tasks/service.js +25 -25
- package/server/lib/scheduled-tasks/service.test.js +1 -1
- package/server/lib/security/bind-host.js +4 -4
- package/server/lib/security/bind-host.test.js +1 -1
- package/server/lib/security/request-security.js +11 -5
- package/server/lib/security/request-security.test.js +14 -14
- package/server/lib/session-assist/runtime.js +15 -18
- package/server/lib/session-folders/routes.js +2 -2
- package/server/lib/session-folders/routes.test.js +8 -8
- package/server/lib/session-goal/DOCUMENTATION.md +20 -7
- package/server/lib/session-goal/create.js +4 -2
- package/server/lib/session-goal/create.test.js +1 -1
- package/server/lib/session-goal/objectives.js +4 -4
- package/server/lib/session-goal/routes.js +1 -1
- package/server/lib/session-goal/runtime.js +91 -30
- package/server/lib/session-goal/runtime.test.js +428 -2
- package/server/lib/session-knowledge/runtime.js +11 -11
- package/server/lib/session-knowledge/runtime.test.js +5 -5
- package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
- package/server/lib/skills-catalog/cache.test.js +2 -2
- package/server/lib/skills-catalog/disk-cache.js +5 -5
- package/server/lib/skills-catalog/github-meta.test.js +2 -2
- package/server/lib/skills-catalog/install.js +3 -2
- package/server/lib/small-model/DOCUMENTATION.md +7 -2
- package/server/lib/small-model/call.js +18 -7
- package/server/lib/small-model/call.test.js +58 -6
- package/server/lib/small-model/index.js +12 -16
- package/server/lib/small-model/index.test.js +1 -1
- package/server/lib/small-model/resolve.js +1 -1
- package/server/lib/small-model/resolve.test.js +1 -1
- package/server/lib/small-model/routes.js +2 -1
- package/server/lib/small-model/runtime-providers.js +15 -7
- package/server/lib/small-model/runtime-providers.test.js +7 -1
- package/server/lib/system-prompt/runtime.js +3 -22
- package/server/lib/system-prompt/runtime.test.js +3 -3
- package/server/lib/terminal/DOCUMENTATION.md +14 -10
- package/server/lib/terminal/runtime.js +190 -31
- package/server/lib/terminal/runtime.test.js +572 -38
- package/server/lib/terminal/shells.js +20 -3
- package/server/lib/terminal/shells.test.js +33 -2
- package/server/lib/tts/base-url.js +3 -3
- package/server/lib/tts/routes.test.js +20 -20
- package/server/lib/ui-auth/ui-auth.js +12 -9
- package/server/lib/ui-auth/ui-auth.test.js +19 -3
- package/server/lib/ui-auth/ui-passkeys.js +7 -7
- package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
- package/server/lib/walkthrough/index.js +1 -0
- package/server/lib/walkthrough/jobs.test.js +1 -1
- package/server/lib/walkthrough/language.test.js +1 -1
- package/server/lib/walkthrough/model-settings.js +11 -15
- package/server/lib/walkthrough/model-settings.test.js +1 -1
- package/server/lib/walkthrough/prompt.js +3 -1
- package/server/lib/walkthrough/pull-request.test.js +5 -5
- package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
- package/server/lib/walkthrough/routes.test.js +26 -5
- package/server/lib/walkthrough/sources.js +23 -2
- package/server/lib/walkthrough/store.js +3 -3
- package/server/lib/walkthrough/store.test.js +1 -1
- package/server/opencode-proxy.test.js +2 -2
- package/server/proxy-headers.js +1 -1
- package/server/sse-routes.test.js +4 -4
- package/README.md.bak +0 -250
- package/bin/lib/DOCUMENTATION.md.bak +0 -163
- package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
- package/dist/assets/DiffView-DluiRSla.js +0 -4
- package/dist/assets/FilesView-Bdq8ppva.js +0 -17
- package/dist/assets/GitView-Znn7VJcP.js +0 -6
- package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
- package/dist/assets/MarkdownImageGallery-ClOvuZxC.js +0 -1
- package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
- package/dist/assets/OnboardingScreen-D2DG7K6b.js +0 -1
- package/dist/assets/PierreDiffViewer-B7m3f0d7.js +0 -157
- package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
- package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
- package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
- package/dist/assets/WalkthroughView-egQgxel3.js +0 -4
- package/dist/assets/contextPanelEmbeddedChat-C3fjDbdg.js +0 -1
- package/dist/assets/de-C1P6X5Rs.js +0 -9
- package/dist/assets/es-CDgaF1_t.js +0 -15
- package/dist/assets/fr-Bgxqvvz_.js +0 -13
- package/dist/assets/index-C57jT0Sa.css +0 -1
- package/dist/assets/index-DW_E3rnL.js +0 -7
- package/dist/assets/ja-BDRrEcNP.js +0 -15
- package/dist/assets/ko-Ba8sQV79.js +0 -15
- package/dist/assets/main-Bv5Pkngh.js +0 -928
- package/dist/assets/markdownCore-CZwzz5gw.js +0 -3
- package/dist/assets/patchFileDiff-yNfWXN4E.js +0 -10
- package/dist/assets/pl-Dioz29BI.js +0 -15
- package/dist/assets/pt-BR-DIs6ElwV.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-BcKLHXKe.js +0 -2
- package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
- package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
- package/dist/assets/scheduledTasksApi-DLW0mkQK.js +0 -1
- package/dist/assets/tr-DFnMlPxY.js +0 -6
- package/dist/assets/uk-ZrCNjM7Y.js +0 -15
- package/dist/assets/useAppFontEffects-CjrntdL0.js +0 -7310
- package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
- package/dist/assets/useRootScrollLock-BHYPkrLv.js +0 -1
- package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
- package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
- package/dist/assets/zh-CN-iv_AV4EC.js +0 -15
- package/dist/assets/zh-TW-CzEUk38j.js +0 -15
- package/server/index.d.ts.bak +0 -39
- package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
- package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
- package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
- package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
- package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
- package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
- package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
- package/server/lib/git/DOCUMENTATION.md.bak +0 -193
- package/server/lib/github/DOCUMENTATION.md.bak +0 -183
- package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
- package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
- package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
- package/server/lib/notifications/APNS.md.bak +0 -135
- package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
- package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
- package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
- package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
- package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
- package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
- package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
- package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
- package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
- package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
- package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
- package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
- package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
- package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
- package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
- package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
- package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
- package/server/lib/text/DOCUMENTATION.md.bak +0 -35
- package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
- package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
- package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
- package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# Project Context
|
|
2
|
-
|
|
3
|
-
Server-owned storage for the Project Notes surface: free-form notes, todos, and
|
|
4
|
-
plan markdown files.
|
|
5
|
-
|
|
6
|
-
The managed Chats root (`~/.config/openchamber/chats`) is also one context owner. Every dated per-session directory beneath it resolves to that root, so Notes, Todo, Plans, pinned knowledge, and project memory are shared across ordinary chats without registering Chats as a user project.
|
|
7
|
-
|
|
8
|
-
## Ownership
|
|
9
|
-
|
|
10
|
-
| Path | Owner | Contents |
|
|
11
|
-
|---|---|---|
|
|
12
|
-
| `<projectsDir>/<projectId>.json` | shared UI (`packages/ui/src/lib/openchamberConfig.ts`), plus server-owned `version` / `scheduledTasks` | worktree setup, draft starters, project actions |
|
|
13
|
-
| `<projectsDir>/<projectId>/context.json` | **this module, exclusively** | notes, todos, plan manifest |
|
|
14
|
-
| `<projectsDir>/<projectId>/plans/*.md` | **this module, exclusively** | plan bodies |
|
|
15
|
-
|
|
16
|
-
The split is the point. Both files were previously one, written by the client
|
|
17
|
-
with a whole-file read-modify-write. Adding a server writer to that file would
|
|
18
|
-
have made unrelated features (project actions, draft starters) clobber notes
|
|
19
|
-
across processes, with no lock able to span both sides. Separate files remove
|
|
20
|
-
the shared resource instead of trying to coordinate access to it.
|
|
21
|
-
|
|
22
|
-
Nothing outside this module may write `context.json` or the `plans` directory.
|
|
23
|
-
|
|
24
|
-
## Storage format
|
|
25
|
-
|
|
26
|
-
```json
|
|
27
|
-
{
|
|
28
|
-
"version": 2,
|
|
29
|
-
"notes": [{
|
|
30
|
-
"id": "", "body": "", "createdAt": 0, "updatedAt": 0,
|
|
31
|
-
"source": "manual | selection | agent",
|
|
32
|
-
"origin": { "sessionId": "", "messageId": "" }
|
|
33
|
-
}],
|
|
34
|
-
"todos": [{ "id": "", "text": "", "completed": false, "createdAt": 0 }],
|
|
35
|
-
"plans": [{ "id": "", "file": "1700000000-title.md", "title": "", "createdAt": 0 }]
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Notes are entries, not one blob. Version 1 stored a single string; it converts
|
|
40
|
-
to a single `manual` note on read (an empty string converts to no notes at
|
|
41
|
-
all). The conversion lives in the read path rather than a separate migration
|
|
42
|
-
pass so that every reader — including one racing a writer — sees one shape.
|
|
43
|
-
Legacy `pinned` fields may remain in existing files but are ignored; attachment
|
|
44
|
-
ownership lives in each session's metadata.
|
|
45
|
-
|
|
46
|
-
`source` records where a note came from, and `origin` links it back to the
|
|
47
|
-
message it was distilled from, so a note taken off a chat selection can be
|
|
48
|
-
traced to its conversation.
|
|
49
|
-
|
|
50
|
-
Notes and todos are written through separate routes. That split is what stops a
|
|
51
|
-
todo toggle from persisting half-typed notes alongside it, and stops an
|
|
52
|
-
agent-authored note from clobbering a concurrent todo change.
|
|
53
|
-
|
|
54
|
-
Plan links store a **base name**, never a path. The file always lives in
|
|
55
|
-
`<projectId>/plans/`, so moving the project storage directory cannot invalidate
|
|
56
|
-
a reference and a caller can never address a file outside it. `title` is
|
|
57
|
-
denormalized into the manifest so listing plans costs one read rather than one
|
|
58
|
-
read per plan; `readPlan` returns the title parsed from the file, which wins if
|
|
59
|
-
the two ever disagree.
|
|
60
|
-
|
|
61
|
-
## Routes
|
|
62
|
-
|
|
63
|
-
| Method | Route | Notes |
|
|
64
|
-
|---|---|---|
|
|
65
|
-
| GET | `/api/project-context/:projectId` | full context; missing file is `200` empty |
|
|
66
|
-
| PUT | `/api/project-context/:projectId/todos` | replaces the whole list; returns committed context |
|
|
67
|
-
| POST | `/api/project-context/:projectId/notes` | `201`; takes `{body, source?, origin?}` |
|
|
68
|
-
| PATCH | `/api/project-context/:projectId/notes/:noteId` | patches `body`; legacy `pinned` input is ignored by session knowledge; `404` when unknown |
|
|
69
|
-
| DELETE | `/api/project-context/:projectId/notes/:noteId` | `404` when unknown |
|
|
70
|
-
| PATCH | `/api/project-context/:projectId/plans/:planId` | legacy project pin state only; session attachment uses session knowledge; `404` when unknown |
|
|
71
|
-
| GET | `/api/project-context/:projectId/plans/:planId` | `404` when the link or its markdown is gone |
|
|
72
|
-
| POST | `/api/project-context/:projectId/plans` | `201`; takes `{title, body}`, never a path |
|
|
73
|
-
| PUT | `/api/project-context/:projectId/plans/:planId` | takes the whole `{raw}` document; `404` when the link or its markdown is gone |
|
|
74
|
-
| DELETE | `/api/project-context/:projectId/plans/:planId` | `404` when unknown |
|
|
75
|
-
|
|
76
|
-
**Body parsing is attached per route.** This server has no global JSON parser:
|
|
77
|
-
`core-routes` parses only an allowlist of `/api` path prefixes so the generic
|
|
78
|
-
OpenCode proxy keeps an unread request stream, and every other `/api` request
|
|
79
|
-
passes through untouched. A write route that forgets `express.json()` therefore
|
|
80
|
-
sees `req.body` as `undefined` and rejects every request as a malformed body —
|
|
81
|
-
which is exactly how this shipped once. `routes.http.test.js` mounts the routes
|
|
82
|
-
on a bare express app so that failure mode fails the suite instead of the user.
|
|
83
|
-
|
|
84
|
-
`projectId` is validated against `/^[a-zA-Z0-9._:-]+$/`, which rejects
|
|
85
|
-
separators and traversal. Validation failures are `400`; malformed stored data
|
|
86
|
-
and I/O failures are `500`.
|
|
87
|
-
|
|
88
|
-
## Invariants
|
|
89
|
-
|
|
90
|
-
- **Missing is not malformed.** A missing `context.json` is authoritative empty
|
|
91
|
-
data. Unparseable JSON is a failure that propagates as `500`, so the client
|
|
92
|
-
preserves what it already has instead of rendering an empty panel over intact
|
|
93
|
-
data on disk.
|
|
94
|
-
- **Writes are serialized per project** through an in-process lock, and land via
|
|
95
|
-
write-to-temp + rename so a crash cannot leave a half-written file.
|
|
96
|
-
- **`readContext` never takes the lock.** Every mutator calls it while already
|
|
97
|
-
holding the lock, so locking there would deadlock. The legacy migration it can
|
|
98
|
-
trigger is safe unlocked: both writes are atomic renames of identical content.
|
|
99
|
-
- **Plan create writes markdown before the manifest entry**; delete removes the
|
|
100
|
-
manifest entry before the file. Either partial failure leaves an unreferenced
|
|
101
|
-
markdown file, which is inert. The reverse order would leave a manifest entry
|
|
102
|
-
that renders as a plan and fails to open.
|
|
103
|
-
- **Plan update takes the raw document, not title + body.** The editor owns
|
|
104
|
-
the file verbatim; reassembling it from parsed parts would rewrite the
|
|
105
|
-
heading and reformat what the user typed. The manifest title is re-derived
|
|
106
|
-
from the saved content, and the file name never changes with the title — it
|
|
107
|
-
is the stable identity behind the link.
|
|
108
|
-
- **Plan update refuses to recreate a deleted file.** If the markdown vanished
|
|
109
|
-
underneath an open editor the link is already dead; writing would resurrect
|
|
110
|
-
content the user believes was discarded, so it returns `404` instead.
|
|
111
|
-
- **A note patch touches only the fields it names.** Pinning sends `pinned`
|
|
112
|
-
alone, so it cannot roll back an edit that landed between the two requests,
|
|
113
|
-
and editing does not reset a pin. Editing bumps `updatedAt`; pinning does not,
|
|
114
|
-
because a pin is not a change to what the note says.
|
|
115
|
-
- **A note body can be clamped but never blanked.** An empty body is rejected
|
|
116
|
-
rather than stored, since a note with nothing in it is indistinguishable from
|
|
117
|
-
a delete the user did not ask for.
|
|
118
|
-
- **Notes are capped at 200 per project.** Past that, creation fails loudly
|
|
119
|
-
instead of silently evicting the oldest entry.
|
|
120
|
-
- **Per-entry sanitization never fails the whole read.** A malformed todo or
|
|
121
|
-
plan link is dropped; the rest of the context still loads.
|
|
122
|
-
|
|
123
|
-
## Legacy migration
|
|
124
|
-
|
|
125
|
-
`projectNotes`, `projectTodos`, and `projectPlanFiles` originally lived in
|
|
126
|
-
`<projectId>.json`. On the first read with no `context.json`, those three keys
|
|
127
|
-
are moved out and deleted from the client-owned file; every other key is
|
|
128
|
-
preserved untouched.
|
|
129
|
-
|
|
130
|
-
Plan links carried absolute paths. Migration converts each to a base name. A
|
|
131
|
-
file already in the plans directory is used in place; one referenced from
|
|
132
|
-
elsewhere — a stale path left by an earlier project id — is copied in rather
|
|
133
|
-
than dropped. A link whose markdown cannot be found at all is discarded, since
|
|
134
|
-
it could not have been opened either way.
|
|
135
|
-
|
|
136
|
-
The legacy keys are removed only after `context.json` is durably written, so any
|
|
137
|
-
failure simply leaves the migration to run again on the next read. Repeat and
|
|
138
|
-
concurrent reads converge on identical content.
|
|
139
|
-
|
|
140
|
-
## Cross-module contract
|
|
141
|
-
|
|
142
|
-
`packages/web/server/lib/opencode/settings-runtime.js` merges project storage
|
|
143
|
-
when a project id changes. Its `mergeProjectContextFiles` step must run before
|
|
144
|
-
`moveDirectoryContents`, because that mover only renames into a free
|
|
145
|
-
destination and would otherwise discard the old `context.json` whenever the
|
|
146
|
-
destination already had one.
|
|
147
|
-
|
|
148
|
-
`mergeProjectContextFiles` merges every list by identity and deliberately does
|
|
149
|
-
not convert a version 1 string note: this module owns that conversion, and
|
|
150
|
-
doing it in two places would mean two definitions of the same migration.
|
|
151
|
-
|
|
152
|
-
`mergeProjectConfigData` still merges the legacy `projectNotes` /
|
|
153
|
-
`projectTodos` / `projectPlanFiles` keys. That is deliberate: a project whose
|
|
154
|
-
context has not been migrated yet keeps its data in `<projectId>.json`, and the
|
|
155
|
-
migration picks it up from the merged destination afterwards.
|
|
156
|
-
|
|
157
|
-
## Tests
|
|
158
|
-
|
|
159
|
-
- `runtime.test.js` — storage, sanitization, migration, locking, plan lifecycle.
|
|
160
|
-
- `routes.test.js` — status-code mapping, payload validation, failure surfacing.
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
# Quota Module Documentation
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
This module fetches quota and usage signals for supported providers in the web server runtime.
|
|
5
|
-
|
|
6
|
-
## Entrypoints and structure
|
|
7
|
-
- `packages/web/server/lib/quota/index.js`: public entrypoint imported by `packages/web/server/index.js`.
|
|
8
|
-
- `packages/web/server/lib/quota/routes.js`: Express route registration for quota endpoints.
|
|
9
|
-
- `packages/web/server/lib/quota/providers/index.js`: provider registry, configured-provider list, and provider dispatcher.
|
|
10
|
-
- `packages/web/server/lib/quota/providers/google/`: Google-specific auth, API, and transform modules.
|
|
11
|
-
- `packages/web/server/lib/quota/providers/claude/`: Claude credential discovery, usage transforms, and rate-limit handling.
|
|
12
|
-
- `packages/web/server/lib/quota/utils/`: shared auth, transform, and formatting helpers.
|
|
13
|
-
|
|
14
|
-
## Supported provider IDs (dispatcher)
|
|
15
|
-
|
|
16
|
-
These provider IDs are currently dispatchable via `fetchQuotaForProvider(providerId)` in `packages/web/server/lib/quota/providers/index.js`.
|
|
17
|
-
|
|
18
|
-
| Provider ID | Display name | Module | Auth aliases/keys |
|
|
19
|
-
| --- | --- | --- | --- |
|
|
20
|
-
| `claude` | Claude | `providers/claude/` | Claude Code Keychain entry, Claude Code credentials file, OpenCode `auth.json` (`anthropic`, `claude`), `CLAUDE_CODE_OAUTH_TOKEN` |
|
|
21
|
-
| `codex` | Codex | `providers/codex.js` | `openai`, `codex`, `chatgpt` |
|
|
22
|
-
| `command-code` | Command Code | `providers/command-code.js` | `command-code` OAuth/API credential in OpenCode `auth.json`, or `COMMAND_CODE_API_KEY` |
|
|
23
|
-
| `cursor` | Cursor | `providers/cursor.js` | Environment/token files, AiWorker-managed credentials, or explicit one-time Cursor import |
|
|
24
|
-
| `crof` | CrofAI | `providers/crof.js` | `crof` (API key under `key` or `token`) |
|
|
25
|
-
| `deepseek` | DeepSeek | `providers/deepseek.js` | `deepseek` (API key under `key` or `token`) |
|
|
26
|
-
| `google` | Google | `providers/google/index.js` | `google`, `google.oauth`, Antigravity accounts file |
|
|
27
|
-
| `github-copilot` | GitHub Copilot | `providers/copilot.js` | `github-copilot`, `copilot` |
|
|
28
|
-
| `github-copilot-addon` | GitHub Copilot Add-on | `providers/copilot.js` | `github-copilot`, `copilot` |
|
|
29
|
-
| `kimi-for-coding` | Kimi for Coding | `providers/kimi.js` | `kimi-for-coding`, `kimi` |
|
|
30
|
-
| `nano-gpt` | NanoGPT | `providers/nanogpt.js` | `nano-gpt`, `nanogpt`, `nano_gpt` |
|
|
31
|
-
| `openrouter` | OpenRouter | `providers/openrouter.js` | `openrouter` |
|
|
32
|
-
| `zai-coding-plan` | z.ai | `providers/zai.js` | `zai-coding-plan`, `zai`, `z.ai` |
|
|
33
|
-
| `zhipuai-coding-plan` | Zhipu AI Coding Plan | `providers/zhipuai-coding-plan.js` | `zhipuai-coding-plan`, `zhipuai`, `zhipu` |
|
|
34
|
-
| `minimax-coding-plan` | MiniMax Coding Plan (minimax.io) | `providers/minimax-coding-plan.js` / `providers/minimax-shared.js` | `minimax-coding-plan` |
|
|
35
|
-
| `minimax-cn-coding-plan` | MiniMax Coding Plan (minimaxi.com) | `providers/minimax-cn-coding-plan.js` / `providers/minimax-shared.js` | `minimax-cn-coding-plan` |
|
|
36
|
-
| `ollama-cloud` | Ollama Cloud | `providers/ollama-cloud.js` | Manual cookie stored under `~/.config/openchamber/quota/` |
|
|
37
|
-
| `wafer` | Wafer.ai | `providers/wafer.js` | `wafer`, `wafer-ai`, `wafer_ai`, `wafer.ai` |
|
|
38
|
-
| `opencode-go` | OpenCode Go | `providers/opencode-go.js` | `opencode-go` API key from OpenCode `auth.json` |
|
|
39
|
-
| `neuralwatt` | NeuralWatt | `providers/neuralwatt.js` | `neuralwatt` (API key under `key` or `token`) |
|
|
40
|
-
| `xai` | xAI | `providers/xai.js` | `xai` OAuth entry in OpenCode `auth.json` |
|
|
41
|
-
|
|
42
|
-
## Internal-only provider module
|
|
43
|
-
- `providers/openai.js` exists for logic parity/reuse but is intentionally not registered for dispatcher ID routing.
|
|
44
|
-
|
|
45
|
-
## Response contract
|
|
46
|
-
All providers should return results via shared helpers to preserve API shape:
|
|
47
|
-
- Required fields: `providerId`, `providerName`, `ok`, `configured`, `usage`, `fetchedAt`
|
|
48
|
-
- Optional field: `error`
|
|
49
|
-
- Unsupported provider requests should return `ok: false`, `configured: false`, `error: Unsupported provider`
|
|
50
|
-
|
|
51
|
-
Provider modules must export `providerId`, `providerName`, `aliases`, `isConfigured(auth?)`, and `fetchQuota()`.
|
|
52
|
-
`fetchQuota()` should return a quota result with `usage.windows` keyed by window name (for example `5h`, `7d`, `daily`) and optional provider-specific `usage.models` data.
|
|
53
|
-
|
|
54
|
-
Ollama Cloud and Cursor credentials are explicitly managed through Settings. OpenCode Go usage uses `GET https://opencode.ai/zen/go/v1/usage` with the `opencode-go` API key from OpenCode `auth.json` as a bearer token. The server validates managed credentials before atomic `0600` writes and never returns secrets through its API. AiWorker never scans browser cookie stores or automatically reads Cursor storage; Cursor import is an explicit one-time user action and never modifies Cursor's database.
|
|
55
|
-
|
|
56
|
-
Command Code usage resolves account scope through `GET /alpha/whoami`, then reads server-backed credit balances and five-hour/weekly limits from `GET /alpha/billing/credits?orgId=...`. Personal accounts return `org: null` and use `/alpha/billing/credits` without an `orgId`; organization accounts include their organization id. Web/Electron and VS Code read the standard `command-code` OpenCode auth entry (including OAuth `access`) or `COMMAND_CODE_API_KEY`; credentials remain in the owning runtime and are never returned to shared UI.
|
|
57
|
-
|
|
58
|
-
On the first OpenCode Go usage refresh after upgrading, AiWorker deletes the obsolete `quota/opencode-go.json` credential file without reading its cookie value.
|
|
59
|
-
|
|
60
|
-
## Claude credential and limit semantics
|
|
61
|
-
|
|
62
|
-
Claude quota reports the subscription limits Claude Code itself is bound by, read from `GET https://api.anthropic.com/api/oauth/usage`.
|
|
63
|
-
|
|
64
|
-
- **Credential sources**, in priority order: the macOS Keychain entry `Claude Code-credentials`, then `${CLAUDE_CONFIG_DIR:-~/.claude}/.credentials.json` (the Linux/WSL location), then the OpenCode `auth.json` entry, then `CLAUDE_CODE_OAUTH_TOKEN`. The Keychain wins on macOS because the credentials file there is a leftover Claude Code no longer updates.
|
|
65
|
-
- **All sources are read-only.** AiWorker never writes to Claude Code's credential store and never refreshes the OAuth token, because Anthropic does not support two live refresh tokens for one `client_id` — refreshing here would sign the user out of Claude Code. Credentials are read fresh per request so a Claude Code refresh is picked up immediately; an expired token yields an explicit "open Claude Code to sign in again" error rather than a bare 401.
|
|
66
|
-
- **Limits come from the `limits` array**, keyed by `kind`: `session` maps to the `5h` window, `weekly_all` to `7d`, and `weekly_scoped` to a per-model `7d` window named by `scope.model.display_name`. The legacy `five_hour`/`seven_day` fields are only a fallback; `seven_day_sonnet`/`seven_day_opus` are no longer populated by Anthropic. Unrecognized limit kinds and Anthropic's rotating internal code names (`nimbus_quill`, `tangelo`, ...) are ignored rather than guessed at.
|
|
67
|
-
- **Extra usage** is reported as the `extra_usage` window from `spend`, only while `spend.enabled` is true, with a money `valueLabel`.
|
|
68
|
-
- **Rate limiting**: Anthropic returns 429 aggressively. The last successful usage payload is cached in memory and reserved during a cooldown (`Retry-After`, else five minutes, capped at one hour). The cache is keyed by a hash of the access and refresh tokens, so switching accounts drops it instead of showing the previous account's numbers.
|
|
69
|
-
- **Runtime parity**: Web/Electron and VS Code preserve the last successful Claude values during the same bounded 429 cooldown. Quota dispatchers also coalesce concurrent refreshes for the same provider in each runtime, while requests for different providers remain parallel.
|
|
70
|
-
|
|
71
|
-
## Add a new provider (quick steps)
|
|
72
|
-
1. Choose module shape based on complexity:
|
|
73
|
-
- Simple providers: create `packages/web/server/lib/quota/providers/<provider>.js`.
|
|
74
|
-
- Complex providers (multi-source auth, multiple API calls, non-trivial transforms): create `packages/web/server/lib/quota/providers/<provider>/` with split modules like Google (`index.js`, `auth.js`, `api.js`, `transforms.js`).
|
|
75
|
-
2. Export `providerId`, `providerName`, `aliases`, `isConfigured`, and `fetchQuota`.
|
|
76
|
-
3. Use shared helpers from `packages/web/server/lib/quota/utils/index.js` (`buildResult`, `toUsageWindow`, auth/conversion helpers) to keep payload shape consistent.
|
|
77
|
-
4. Register the provider in `packages/web/server/lib/quota/providers/index.js`.
|
|
78
|
-
5. If needed for direct use, export a named fetcher from `packages/web/server/lib/quota/providers/index.js` and `packages/web/server/lib/quota/index.js`.
|
|
79
|
-
6. Update this file with the new provider ID, module path, and alias/auth details.
|
|
80
|
-
7. Validate with `bun run type-check`, `bun run lint`, and `bun run build`.
|
|
81
|
-
|
|
82
|
-
## MiniMax M3 / Token Plan migration
|
|
83
|
-
|
|
84
|
-
In 2025/2026 MiniMax rebranded "Coding Plan" to "Token Plan" alongside the M3 model release. The API underwent breaking changes:
|
|
85
|
-
|
|
86
|
-
- **Endpoint fallback**: The provider tries `/v1/token_plan/remains` (M3) first, falling back to legacy `/v1/api/openplatform/coding_plan/remains`.
|
|
87
|
-
- **Field semantics**: On the `token_plan/remains` endpoint, `current_interval_usage_count` returns **remaining** quota (not consumed). The provider computes `used = total - remaining` for this endpoint. The legacy `coding_plan/remains` endpoint retains the old semantics (`usage_count = consumed`).
|
|
88
|
-
- **Percentage-based plans**: Legacy Coding Plan accounts return `current_interval_total_count: 0` but include `current_interval_remaining_percent`. The provider prefers this field when count fields are absent.
|
|
89
|
-
- **model_remains array**: Now contains entries for multiple model categories (chat, speech, video, image). The provider selects the chat-model entry by matching `MiniMax-M*`, then `general`/`chat`/`text` by name, then any entry with a remaining percent.
|
|
90
|
-
- **Window status**: The `current_interval_status` and `current_weekly_status` fields indicate whether a window is active. Status `3` means the window is not applicable for the current plan tier (e.g. legacy plans without weekly limits). The provider omits inactive windows.
|
|
91
|
-
|
|
92
|
-
## Kimi for Coding field semantics
|
|
93
|
-
|
|
94
|
-
`GET https://api.kimi.com/coding/v1/usages` is inconsistent about which field carries consumption:
|
|
95
|
-
- The weekly `usage` block returns `used` (consumed) with no `remaining` field.
|
|
96
|
-
- Each `limits[].detail` rate-limit block returns `remaining` (available) with no `used` field.
|
|
97
|
-
|
|
98
|
-
The provider computes `usedPercent` from whichever of `used`/`remaining` is present (`used` takes precedence when both exist) rather than assuming one field name. Both `packages/web/server/lib/quota/providers/kimi.js` and `packages/vscode/src/quotaProviders.ts` (`fetchKimiQuota`) must stay in sync — the VS Code extension duplicates this parsing logic rather than importing it.
|
|
99
|
-
|
|
100
|
-
## GitHub Copilot quota semantics
|
|
101
|
-
|
|
102
|
-
GitHub Copilot usage exposes only the `premium_interactions` snapshot as the
|
|
103
|
-
`premium_interactions` window. Shared UI labels that window **AI Credits** and treats it as
|
|
104
|
-
the provider's primary usage marker. Legacy chat-request quota and unlimited
|
|
105
|
-
completion quota are intentionally omitted. Keep
|
|
106
|
-
`packages/web/server/lib/quota/providers/copilot.js` and
|
|
107
|
-
`packages/vscode/src/quotaProviders.ts` in sync.
|
|
108
|
-
|
|
109
|
-
The `/copilot_internal/user` endpoint is undocumented; its quota semantics mirror
|
|
110
|
-
what `microsoft/vscode-copilot-chat` consumes (`CopilotUserQuotaInfo`). Each
|
|
111
|
-
snapshot carries `entitlement`, `remaining`, `unlimited`, and
|
|
112
|
-
`percent_remaining`. Providers must honor these rules:
|
|
113
|
-
|
|
114
|
-
- `unlimited: true` renders a percent-less window with an "Unlimited" value label.
|
|
115
|
-
- Percent math requires a positive `entitlement`; entitlements of `0`, `-1`, or null are unusable.
|
|
116
|
-
- When entitlement/remaining are unusable, fall back to `100 - percent_remaining`.
|
|
117
|
-
- Snapshots other than `premium_interactions` (legacy annual plans) yield zero windows.
|
|
118
|
-
|
|
119
|
-
## Notes for contributors
|
|
120
|
-
- Keep provider IDs stable; clients use them directly.
|
|
121
|
-
- Avoid adding alias-based dispatch in `fetchQuotaForProvider`; dispatch currently expects exact provider IDs.
|
|
122
|
-
- Keep Google behavior changes isolated and review `providers/google/*` together.
|
|
123
|
-
- Z.ai Coding Plan exposes separate 5-hour and weekly token/credit limit entries plus a monthly `TIME_LIMIT` for MCP tools. The API renamed the limit type from `TOKENS_LIMIT` to `CREDIT_LIMIT` (same `unit`/`number` window semantics); `CREDIT_LIMIT` entries additionally carry `usage` (total), `currentValue` (consumed), and `remaining`, surfaced as a credit `valueLabel`, and the payload's `data.level` becomes `planLabel`. Web and VS Code must preserve these windows and stay in sync.
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# Relay Module Documentation
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
The private relay lets an AiWorker client (mobile app, browser, or another desktop) reach a user's AiWorker instance through AiWorker-hosted infrastructure when the instance is not directly reachable (behind NAT, no public URL, no tunnel). The instance dials **outbound** to the relay; nothing needs to be exposed inbound.
|
|
6
|
-
|
|
7
|
-
Traffic is **end-to-end encrypted between the two endpoints** (client and host instance). The relay infrastructure forwards opaque ciphertext and cannot read application traffic — it is an untrusted transport, not a trusted middlebox.
|
|
8
|
-
|
|
9
|
-
This module (`packages/web/server/lib/relay/`) is the **host side**: it runs inside the AiWorker web server (so it works for Electron desktop, headless server, and CLI installs alike). The **client side** lives in `packages/ui/src/lib/relay/`. The **relay service itself** is a separate Cloudflare Worker in the `openchamber-website` repo and only brokers connections.
|
|
10
|
-
|
|
11
|
-
## The three layers
|
|
12
|
-
|
|
13
|
-
Traffic is modeled as three stacked layers. The relay understands only Layer 1; Layers 2–3 exist solely between the client and the host.
|
|
14
|
-
|
|
15
|
-
1. **Relay routing (Layer 1)** — outbound WebSocket connections to the relay, connection brokering, and host authentication to the relay. The relay routes each client to the correct host and forwards frames verbatim.
|
|
16
|
-
2. **End-to-end encryption (Layer 2)** — an authenticated encrypted channel established directly between client and host, keyed so the relay cannot participate. Built on standard WebCrypto primitives (ECDH key agreement + AEAD framing). The host's encryption public key is distributed to the client out-of-band via the pairing payload and is the client's trust anchor.
|
|
17
|
-
3. **Tunnel multiplexing (Layer 3)** — because an AiWorker client speaks many concurrent HTTP requests, an event stream (SSE), and WebSockets to one origin, the encrypted channel carries a small multiplexing protocol. It frames HTTP request/response (including streamed bodies) and WebSocket sub-streams so the whole app works over one encrypted connection.
|
|
18
|
-
|
|
19
|
-
## Entrypoints and structure
|
|
20
|
-
|
|
21
|
-
Host side (`packages/web/server/lib/relay/`):
|
|
22
|
-
- `service.js` — thin entrypoint: relay config (enabled flag + relay URL), the management routes (`GET/POST /api/openchamber/relay/{status,enable,disable}`), a `getPairingCandidate()` accessor (the relay transport candidate folded into pairing-v2 links when enabled, consumed by the pairing-session route in `core-routes.js`), and lifecycle wiring. Started from `packages/web/server/index.js` only when the user has explicitly enabled the relay. The relay endpoint defaults to the AiWorker-hosted relay but can be pinned to a self-hosted relay via the `AIWORKER_RELAY_URL` env var (must be `ws://`/`wss://`); when set it overrides the stored setting for the host connection, the pairing candidate, and status, so paired clients inherit the endpoint automatically.
|
|
23
|
-
- `identity.js` — the host's stable identity: the long-lived signing keypair (shared with the push relay, defines the routing id) plus a long-lived encryption keypair (the E2EE trust anchor). Reused across restarts; never rotated implicitly.
|
|
24
|
-
- `signing-key.js` — storage/derivation of the signing keypair and the routing id, shared with the notifications runtime.
|
|
25
|
-
- `host-client.js` — the long-lived connection manager: one outbound control connection to the relay, a per-client data connection for each connected device, reconnect/backoff, and the E2EE responder handshake per connection.
|
|
26
|
-
- `host-lock.js` — the per-machine host claim. Every local instance sharing the data dir shares the relay identity (same serverId), so concurrent relay hosts evict each other at the relay worker (`4001: Control replaced`) and paired devices land on whichever local process won last. The claim file (`<data-dir>/relay-host.lock`, `{ pid }`) makes this deterministic: `service.js` only starts the host when no LIVE process holds the claim (stale claims from dead pids are ignored), goes to `standby` otherwise, and a 30s watcher both takes over when the claimant dies and stands down when another process claims. A standby watcher waits a 2-minute grace after the claim frees before taking over, so a cleanly restarting host (app update/relaunch) — which reclaims at boot with no wait — always wins the restart window over a bystander instance. Explicit user intent — creating a pairing link or hitting `/relay/enable` — force-claims; the previous holder's watcher sees the takeover and backs off instead of fighting. Instances created with `allowPassiveHost: false` (dev servers via `AIWORKER_RELAY_HOST=off`, the Electron dev shell via `AIWORKER_ELECTRON_DEV`; `AIWORKER_RELAY_HOST=on` overrides) never start the host passively at all — boot, demand reconcile, and watcher takeover leave them in `standby`; only explicit enable/pairing hosts there. The claim is cooperative (the relay worker still enforces the single host slot); it only decides which process keeps retrying.
|
|
27
|
-
- `tunnel-host.js` — the per-connection dispatcher: decrypts tunnel frames and forwards HTTP/SSE/WS to the local server over loopback, then streams responses back. Enforces a path allowlist and never injects credentials.
|
|
28
|
-
- `e2ee.js`, `tunnel-codec.js` — host-side (JS) mirrors of the shared crypto and framing (see "Two implementations" below).
|
|
29
|
-
|
|
30
|
-
Client side (`packages/ui/src/lib/relay/`):
|
|
31
|
-
- `protocol.ts` — the shared contract: constants, frame types, message shapes. The normative source both implementations follow.
|
|
32
|
-
- `crypto.ts`, `handshake.ts` — the E2EE primitives and handshake state machines (initiator + responder).
|
|
33
|
-
- `tunnel-codec.ts` — Layer 3 frame codec, fragmentation, and outbound frame batching.
|
|
34
|
-
- `tunnel-client.ts` — the client tunnel: exposes a `fetch()`-compatible and a WebSocket-compatible surface backed by the encrypted tunnel.
|
|
35
|
-
- `tunnel-payloads.ts`, `runtime-tunnel.ts`, `runtime-socket.ts` — payload helpers, the active-tunnel singleton, and the shared "open a runtime WebSocket the right way" helper.
|
|
36
|
-
|
|
37
|
-
Relay is not a separate link format: it is one transport candidate inside the unified **pairing v2** payload (`packages/ui/src/lib/connectionPayload.ts`). A relay candidate is `{ type: 'relay', relayUrl, serverId, hostEncPubJwk }` — no embedded token; the client redeems the one-time pairing secret over the tunnel like any other candidate.
|
|
38
|
-
|
|
39
|
-
## What travels the tunnel
|
|
40
|
-
|
|
41
|
-
Everything a client normally sends to the single AiWorker origin:
|
|
42
|
-
- **HTTP** — REST endpoints and proxied OpenCode SDK calls under `/api/*`, plus `/auth/*` and `/health`.
|
|
43
|
-
- **SSE** — long-lived streamed responses (the event stream and notifications). These are just HTTP responses whose body streams; the tunnel needs no special SSE handling.
|
|
44
|
-
- **WebSocket** — the endpoints that use a real socket (the global event stream on platforms that support WS, terminal I/O, dictation).
|
|
45
|
-
|
|
46
|
-
The host dispatcher restricts tunneled traffic to explicit path allowlists (one for HTTP, one for WS).
|
|
47
|
-
|
|
48
|
-
Request bodies crossing the tunnel are buffered on the host and forwarded to loopback only once the client's `StreamEnd` frame arrives (bodies above ~512 KB stream live instead). A body whose frames were lost in transit therefore never reaches the loopback server as an empty/truncated chunked body — the host aborts the stream and the client sees an ambiguous transport failure it can retry, instead of the loopback server's bare `400` ("Failed to send message (400)" from the mobile app).
|
|
49
|
-
|
|
50
|
-
## Authentication model
|
|
51
|
-
|
|
52
|
-
- The tunnel is **transport only**. The AiWorker server still authenticates every tunneled request exactly as it authenticates a direct remote client. The relay path grants reachability, not authorization.
|
|
53
|
-
- Clients carry their normal credential. HTTP and SSE requests authenticate with the client's bearer token (a header). **WebSocket upgrades cannot send headers**, so they authenticate with a short-lived URL-scoped token minted beforehand and passed as a query parameter. This asymmetry is important when adding new WebSocket features (see the skill).
|
|
54
|
-
- The host authenticates itself to the relay with a signed handshake using its long-lived signing key.
|
|
55
|
-
- Enabling the relay is explicit opt-in and disabled by default; disabling it severs all relay reachability immediately.
|
|
56
|
-
|
|
57
|
-
## End-to-end flow (overview)
|
|
58
|
-
|
|
59
|
-
1. **Pairing.** The host issues a pairing-v2 link (QR / deep link) carrying a one-time secret and a list of transport candidates. When the relay is enabled, one candidate is the relay transport (its endpoint, routing id, and encryption public key — the E2EE trust anchor). The client redeems the secret over the first reachable candidate; over the relay candidate it opens the E2EE tunnel first, then redeems through it, and stores the connection.
|
|
60
|
-
2. **Presence.** When the relay is enabled, the host opens one outbound control connection and waits.
|
|
61
|
-
3. **Connect.** The client connects for a given routing id; the relay notifies the host over the control connection; the host opens a matching per-client data connection.
|
|
62
|
-
4. **Handshake.** Over that connection pair, client and host run the E2EE handshake and derive a shared encrypted channel the relay cannot read.
|
|
63
|
-
5. **Traffic.** All normal app traffic is multiplexed and encrypted through that channel. On the host, decrypted requests are dispatched to the local server over loopback with the actual loopback origin, so normal origin checks still apply without trusting client-supplied origin metadata; responses stream back encrypted. Reconnects re-establish a fresh channel and the app's existing retry machinery recovers.
|
|
64
|
-
|
|
65
|
-
## Candidate refresh (staying off the relay when direct works)
|
|
66
|
-
|
|
67
|
-
Pairing-payload transport candidates are a snapshot: when DHCP hands the host
|
|
68
|
-
machine a new LAN address, a device's saved direct candidate goes stale and the
|
|
69
|
-
device silently degrades to relay-only. To recover, an already-paired client can
|
|
70
|
-
call `GET /api/client-auth/connection/candidates` (UI session or client bearer;
|
|
71
|
-
registered with the auth/access routes) over any live transport — including
|
|
72
|
-
through the tunnel — to learn the server's **current** LAN URLs plus the relay
|
|
73
|
-
candidate, and update its saved candidate set (mobile: `mobileConnections.ts`;
|
|
74
|
-
desktop: `desktopRelayRestore.ts`).
|
|
75
|
-
|
|
76
|
-
Identity gating: the response carries the stable `serverId` (base64url SHA-256 of
|
|
77
|
-
the public signing JWK — the same identity the relay routes by, exposed by the
|
|
78
|
-
relay service's `getServerId()` and echoed unauthenticated on `/health` and
|
|
79
|
-
`/api/version`). Clients ignore a refresh whose `serverId` does not match their
|
|
80
|
-
pinned relay identity, and verify `/health`'s `serverId` on a learned address
|
|
81
|
-
**before** sending their bearer token to it — a re-assigned LAN address may now
|
|
82
|
-
belong to a different machine.
|
|
83
|
-
|
|
84
|
-
## Two implementations, kept in sync
|
|
85
|
-
|
|
86
|
-
The E2EE and framing logic exists twice: TypeScript in `packages/ui/src/lib/relay/` (shared by the client and the normative reference) and a JavaScript mirror in this module (the host, which is plain JS ESM). They **must stay byte-compatible** — a client encrypted by one must decrypt on the other. A cross-compatibility test (`cross-compat.test.js`) imports the TS modules directly and exercises a full TS-client ↔ JS-host exchange. Any change to the wire format, frame codec, handshake, or batching must update both sides and keep that test green.
|
|
87
|
-
|
|
88
|
-
## Runtime integration (client)
|
|
89
|
-
|
|
90
|
-
Relay mode plugs into the existing client transport layer rather than a parallel path: `runtime-switch` activates the tunnel singleton, `runtime-fetch` routes runtime requests through it, `runtime-url`/`runtime-socket` yield tunnel-backed URLs and sockets, and `runtime-auth` mints the URL-scoped token through the tunnel. Direct-URL connections and the Electron realtime-proxy path are unaffected.
|
|
91
|
-
|
|
92
|
-
## Design invariants (do not regress)
|
|
93
|
-
|
|
94
|
-
- The relay never sees plaintext application traffic; it sees only routing metadata (routing id, connection identifiers, timestamps, coarse counts).
|
|
95
|
-
- Pairing secrets travel in URL fragments only, never in query strings, never logged.
|
|
96
|
-
- The host dispatcher never injects credentials; the server authenticates each tunneled request.
|
|
97
|
-
- The tunnel is transparent to the app: adding relay support to a feature should not require the feature to know the relay exists — it goes through the shared runtime transport helpers.
|
|
98
|
-
- The two implementations stay byte-compatible and the wire format is versioned/negotiated so mixed client/host app versions degrade gracefully rather than break.
|
|
99
|
-
|
|
100
|
-
For the operational rules that keep future changes (new WebSocket endpoints, transport refactors, terminal/voice porting) from breaking this, load the `relay-transport` skill.
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
# Scheduled Tasks module
|
|
2
|
-
|
|
3
|
-
Server-owned scheduled task runtime and routes for AiWorker-only automation.
|
|
4
|
-
|
|
5
|
-
## Scope
|
|
6
|
-
|
|
7
|
-
- Per-project scheduled task persistence is owned by `packages/web/server/lib/projects/project-config.js`.
|
|
8
|
-
- Markdown loop discovery/parsing is owned by `packages/web/server/lib/scheduled-tasks/loops.js`.
|
|
9
|
-
- Runtime orchestration and execution is owned by `packages/web/server/lib/scheduled-tasks/runtime.js`.
|
|
10
|
-
- This module is AiWorker feature logic; it is intentionally separate from OpenCode proxy/runtime internals.
|
|
11
|
-
|
|
12
|
-
## Cross-instance occurrence claiming
|
|
13
|
-
|
|
14
|
-
Multiple AiWorker server processes can share the same on-disk project config
|
|
15
|
-
(for example CLI `serve` on port 3000 and the Electron desktop server on port
|
|
16
|
-
57123). Each process keeps its own timers, so without coordination a daily (or
|
|
17
|
-
weekly / cron / once) slot would dispatch twice.
|
|
18
|
-
|
|
19
|
-
Before a **scheduled** run creates a session, the runtime claims the occurrence
|
|
20
|
-
in shared project config under the project write lock:
|
|
21
|
-
|
|
22
|
-
- Writes `state.lastScheduledFor` to the armed `nextRunAt` timestamp and advances
|
|
23
|
-
`state.nextRunAt` to the following occurrence.
|
|
24
|
-
- A second instance that loses the claim skips session creation and reschedules
|
|
25
|
-
from the winner's persisted `nextRunAt`.
|
|
26
|
-
- Project config writes also take a cross-process `.json.lock` file so the
|
|
27
|
-
read-modify-write is serialized across processes, not only within one process.
|
|
28
|
-
- The sharing processes may run different AiWorker versions. Normalization
|
|
29
|
-
keeps only the fields a build knows, so every writer persists tasks it did
|
|
30
|
-
not change verbatim from disk and swaps only `state` onto a task whose state
|
|
31
|
-
it updated; a task goes out normalized only when it was deliberately
|
|
32
|
-
replaced (upsert, loop adoption). An older server touching the file after a
|
|
33
|
-
run therefore cannot strip fields a newer build added, such as a task's goal
|
|
34
|
-
or auto-accept settings.
|
|
35
|
-
- Lock timeout / filesystem errors on claim, manual-start, or completion state
|
|
36
|
-
writes always release the in-process running slot (via `finally`) and best-effort
|
|
37
|
-
re-arm the **next future** occurrence; they must not leave the task permanently
|
|
38
|
-
"running" or reject unhandled from the queue pump.
|
|
39
|
-
- Project write locks release the in-process promise chain even when
|
|
40
|
-
`acquireProjectFileLock` times out, so a later write for the same project can
|
|
41
|
-
proceed after the on-disk lock is cleared (a hung chain would permanently wedge
|
|
42
|
-
every mutating API and strand `runTask` before its `finally`).
|
|
43
|
-
- Re-arm helpers only schedule a persisted `nextRunAt` when it is still in the
|
|
44
|
-
future. A past slot (common for `once` after claim, which cannot advance
|
|
45
|
-
`nextRunAt`) falls back to `computeNextRunAt` — which returns null for a
|
|
46
|
-
consumed/past once occurrence — so a losing instance stops instead of
|
|
47
|
-
spinning delay-0 timers against the project lock.
|
|
48
|
-
- On claim lock/fs failure, best-effort persist `lastStatus: error` + `lastError`
|
|
49
|
-
when nobody else claimed the occurrence, so a past `once` task is not left
|
|
50
|
-
enabled-but-inert with only a warn log. Recurring schedules still re-arm the
|
|
51
|
-
next slot.
|
|
52
|
-
- On completion-write failure after a session already ran, in-memory status is
|
|
53
|
-
set to a terminal value and a single persist retry is attempted so
|
|
54
|
-
`lastStatus` does not stay `running`. Manual `runNow` still returns the
|
|
55
|
-
`sessionID` as a successful dispatch (`ok` follows run status, with
|
|
56
|
-
`persistError` set) rather than a hard 500; the run API and Scheduled Tasks
|
|
57
|
-
UI surface `persistError` as a warning toast.
|
|
58
|
-
- The claim predicate rejects a duplicate solely via `lastScheduledFor` within
|
|
59
|
-
slack of this occurrence. It does not consult advanced on-disk `nextRunAt`
|
|
60
|
-
(that field is routinely overwritten by a second instance syncing inside
|
|
61
|
-
`TASK_DUE_SLACK_MS`, including on later days when `lastScheduledFor` is already
|
|
62
|
-
set from a prior claim).
|
|
63
|
-
- Claiming always writes `nextRunAt` (including `undefined`) so a past once-slot
|
|
64
|
-
is cleared when there is no following occurrence.
|
|
65
|
-
|
|
66
|
-
Manual `runNow` does not claim a schedule occurrence.
|
|
67
|
-
|
|
68
|
-
## Files
|
|
69
|
-
|
|
70
|
-
- `packages/web/server/lib/scheduled-tasks/runtime.js`
|
|
71
|
-
- Next-run computation (daily/weekly/cron compatibility)
|
|
72
|
-
- Timer scheduling and queueing
|
|
73
|
-
- Concurrency controls
|
|
74
|
-
- Session create + prompt_async execution
|
|
75
|
-
- Emits AiWorker task-run events
|
|
76
|
-
|
|
77
|
-
- `packages/web/server/lib/scheduled-tasks/loops.js`
|
|
78
|
-
- Discovery of `.agents/loops/*.md` (project scope, ancestors up to the worktree root) and `~/.agents/loops/*.md` (user scope)
|
|
79
|
-
- Frontmatter parsing into scheduled-task definitions
|
|
80
|
-
- `syncProject` reconciles discovered loops with the persisted task list on every project sync (startup, task list load, task save/delete)
|
|
81
|
-
|
|
82
|
-
- `packages/web/server/lib/scheduled-tasks/routes.js`
|
|
83
|
-
- Scheduled task CRUD endpoints
|
|
84
|
-
- Listing tasks reconciles loop files first, so opening the Scheduled Tasks UI discovers file additions, edits, and removals without a server restart
|
|
85
|
-
- Loop-file endpoints toggle `enabled` in frontmatter or delete the authoritative markdown file, then reconcile the project
|
|
86
|
-
- Manual run endpoint
|
|
87
|
-
- AiWorker events SSE stream endpoint
|
|
88
|
-
|
|
89
|
-
## Loop file format
|
|
90
|
-
|
|
91
|
-
Portable, git-commit-able scheduled-task definitions:
|
|
92
|
-
|
|
93
|
-
```markdown
|
|
94
|
-
---
|
|
95
|
-
name: daily-digest
|
|
96
|
-
schedule: "0 9 * * *"
|
|
97
|
-
enabled: true
|
|
98
|
-
model: anthropic/claude-sonnet-4-5
|
|
99
|
-
agent: plan
|
|
100
|
-
timezone: Europe/Kyiv
|
|
101
|
-
---
|
|
102
|
-
Summarize repository changes since yesterday.
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Field mapping (model: `packages/ui/src/lib/scheduledTasksApi.ts`):
|
|
106
|
-
|
|
107
|
-
| Frontmatter | Task field |
|
|
108
|
-
|---|---|
|
|
109
|
-
| `name` | `name` (required, max 80 characters — longer names are rejected as malformed) |
|
|
110
|
-
| `schedule` | `schedule.kind: "cron"` + `schedule.cron` (required, cron-only in the portable format) |
|
|
111
|
-
| `enabled` | `enabled` (default `false` — a loop only runs when the file explicitly enables it; add `enabled: true` to activate) |
|
|
112
|
-
| `model` | split on the first `/` into `execution.providerID` / `execution.modelID` (required) |
|
|
113
|
-
| `agent` | `execution.agent` (optional) |
|
|
114
|
-
| `timezone` | `schedule.timezone` (optional, IANA; defaults to the server zone) |
|
|
115
|
-
| body | `execution.prompt` (required) |
|
|
116
|
-
|
|
117
|
-
`thinking_level` and `goalEnabled`/`goalTokenBudget` are not part of the portable
|
|
118
|
-
format (UI/JSON-only today); `daily`/`weekly`/`once` schedules remain UI/JSON-only.
|
|
119
|
-
Runtime state (`lastRunAt`, `nextRunAt`, `lastStatus`, `lastError`, `lastSessionId`,
|
|
120
|
-
`lastDurationMs`) is never written to the markdown file — it continues to live in
|
|
121
|
-
the project config state store.
|
|
122
|
-
|
|
123
|
-
## Loop reconciliation rules
|
|
124
|
-
|
|
125
|
-
`projectConfigRuntime.reconcileLoopTasks(projectID, loops)` runs inside the
|
|
126
|
-
project write lock on every `syncProject` when the project path is known:
|
|
127
|
-
|
|
128
|
-
- **Identity.** For loop-owned tasks (carrying the `loopFile` marker) identity
|
|
129
|
-
is the loop file path: a loop takes its task over regardless of the task's
|
|
130
|
-
current name, so renaming the loop (the `name` field, or a UI rename) renames
|
|
131
|
-
the task in place instead of leaving a stale duplicate behind. A loop whose
|
|
132
|
-
name matches a JSON task (no `loopFile`) takes that task over instead: its
|
|
133
|
-
schedule/execution/enabled are overwritten from the file while the task's
|
|
134
|
-
`id` and runtime `state` are preserved (markdown wins on conflict).
|
|
135
|
-
- **UI-only fields survive adoption.** Execution fields the file format does
|
|
136
|
-
not define (`goalEnabled`, `goalTokenBudget`, `permissionAutoAccept`,
|
|
137
|
-
`variant`) are preserved from the task when a loop adopts it; only fields the
|
|
138
|
-
file defines are re-applied.
|
|
139
|
-
- **Deletion.** A task carrying the `loopFile` marker whose loop file is no
|
|
140
|
-
longer discovered (removed or renamed) is unscheduled (removed from the
|
|
141
|
-
config). The marker is persisted in the config file, so removal is detected
|
|
142
|
-
across restarts. JSON-configured tasks without the marker are never removed.
|
|
143
|
-
A task whose loop file still exists but is currently unparseable is KEPT with
|
|
144
|
-
its last good definition — a transiently malformed file (mid-edit, bad merge)
|
|
145
|
-
never deletes a task or its runtime state.
|
|
146
|
-
- **Creation.** Loops without a matching task are created under a deterministic
|
|
147
|
-
`loop:<scope>:<name>` id so runtime state survives restarts. At most one task
|
|
148
|
-
is driven per loop file; orphan duplicates of the same file are unscheduled.
|
|
149
|
-
- **Scope precedence.** Project-scope loops shadow user-scope loops with the
|
|
150
|
-
same name; among project files the nearest ancestor wins.
|
|
151
|
-
- **Malformed files** (missing `name`/`schedule`/`model`/body, invalid cron,
|
|
152
|
-
unreadable) are reported to the scheduler as `definition: null` entries and
|
|
153
|
-
warned about; they never block valid loops in the same or other scopes.
|
|
154
|
-
- **Loop-file mutations.** The loop file remains authoritative. The scheduled-
|
|
155
|
-
tasks UI opens it in the built-in file editor, updates its `enabled`
|
|
156
|
-
frontmatter through the loop-file endpoint, and deletes the file through the
|
|
157
|
-
loop-file endpoint after confirmation. Each mutation reconciles the project.
|
|
158
|
-
The general task deletion API still rejects loop-sourced tasks while their
|
|
159
|
-
file exists; once the file is gone, deleting an orphan task is allowed.
|
|
160
|
-
|
|
161
|
-
## Public exports (runtime.js)
|
|
162
|
-
|
|
163
|
-
- `createScheduledTasksRuntime(dependencies)`
|
|
164
|
-
- Returned API:
|
|
165
|
-
- `start()`
|
|
166
|
-
- `stop()`
|
|
167
|
-
- `syncAllProjects()`
|
|
168
|
-
- `syncProject(projectId)`
|
|
169
|
-
- `runNow(projectId, taskId)`
|
|
170
|
-
|
|
171
|
-
## Public exports (routes.js)
|
|
172
|
-
|
|
173
|
-
- `registerScheduledTaskRoutes(app, dependencies)`
|
|
174
|
-
- Registers:
|
|
175
|
-
- `GET /api/projects/:projectId/scheduled-tasks`
|
|
176
|
-
- `PUT /api/projects/:projectId/scheduled-tasks`
|
|
177
|
-
- `DELETE /api/projects/:projectId/scheduled-tasks/:taskId`
|
|
178
|
-
- `PATCH /api/projects/:projectId/scheduled-tasks/:taskId/loop-file`
|
|
179
|
-
- `DELETE /api/projects/:projectId/scheduled-tasks/:taskId/loop-file`
|
|
180
|
-
- `POST /api/projects/:projectId/scheduled-tasks/:taskId/run`
|
|
181
|
-
- `GET /api/openchamber/scheduled-tasks/status`
|
|
182
|
-
- `GET /api/openchamber/events`
|