@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,14 +1,14 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { createOpencodeClient } from '@opencode-ai/sdk/v2';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { AiWorkerControlError, asControlError } from './error.js';
|
|
4
|
+
import { AIWORKER_ALL_ACTIONS } from './actions.js';
|
|
5
5
|
import { writeScreenshot } from './screenshots.js';
|
|
6
6
|
|
|
7
7
|
const DEFAULT_WAIT_TIMEOUT_SECONDS = 600;
|
|
8
8
|
const MAX_WAIT_TIMEOUT_SECONDS = 86_400;
|
|
9
9
|
const WAIT_POLL_INTERVAL_MS = 500;
|
|
10
10
|
// One service, both capabilities: which tool asked is the caller's concern.
|
|
11
|
-
const CONTROL_ACTIONS = new Set(
|
|
11
|
+
const CONTROL_ACTIONS = new Set(AIWORKER_ALL_ACTIONS);
|
|
12
12
|
const SCHEDULE_TASK_ID_ACTIONS = new Set([
|
|
13
13
|
'schedule.run',
|
|
14
14
|
'schedule.delete',
|
|
@@ -25,7 +25,7 @@ const positiveInteger = (value, fallback, field) => {
|
|
|
25
25
|
if (value === undefined || value === null) return fallback;
|
|
26
26
|
const number = Number(value);
|
|
27
27
|
if (!Number.isSafeInteger(number) || number < 1) {
|
|
28
|
-
throw new
|
|
28
|
+
throw new AiWorkerControlError(`${field} must be a positive integer`, 400);
|
|
29
29
|
}
|
|
30
30
|
return number;
|
|
31
31
|
};
|
|
@@ -33,7 +33,7 @@ const positiveInteger = (value, fallback, field) => {
|
|
|
33
33
|
const normalizeWaitTimeoutMs = (value) => {
|
|
34
34
|
const seconds = value === undefined || value === null ? DEFAULT_WAIT_TIMEOUT_SECONDS : Number(value);
|
|
35
35
|
if (!Number.isSafeInteger(seconds) || seconds < 1 || seconds > MAX_WAIT_TIMEOUT_SECONDS) {
|
|
36
|
-
throw new
|
|
36
|
+
throw new AiWorkerControlError(`timeout must be from 1 to ${MAX_WAIT_TIMEOUT_SECONDS} seconds`, 400);
|
|
37
37
|
}
|
|
38
38
|
return seconds * 1000;
|
|
39
39
|
};
|
|
@@ -64,20 +64,20 @@ const extractTextMessages = (messages, role = 'all') => {
|
|
|
64
64
|
|
|
65
65
|
const parseModel = (value) => {
|
|
66
66
|
const model = asNonEmptyString(value);
|
|
67
|
-
if (!model) throw new
|
|
67
|
+
if (!model) throw new AiWorkerControlError('model is required', 400);
|
|
68
68
|
const slashIndex = model.indexOf('/');
|
|
69
69
|
if (slashIndex <= 0 || slashIndex === model.length - 1) {
|
|
70
|
-
throw new
|
|
70
|
+
throw new AiWorkerControlError('model must be in provider/model format', 400);
|
|
71
71
|
}
|
|
72
72
|
return { providerID: model.slice(0, slashIndex), modelID: model.slice(slashIndex + 1) };
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
const parseWeekdays = (value) => {
|
|
76
76
|
const raw = asNonEmptyString(value);
|
|
77
|
-
if (!raw) throw new
|
|
77
|
+
if (!raw) throw new AiWorkerControlError('weekly is required', 400);
|
|
78
78
|
const weekdays = raw.split(',').map((entry) => Number.parseInt(entry.trim(), 10));
|
|
79
79
|
if (weekdays.some((entry) => !Number.isInteger(entry) || entry < 0 || entry > 6)) {
|
|
80
|
-
throw new
|
|
80
|
+
throw new AiWorkerControlError('weekly must contain weekdays from 0 to 6', 400);
|
|
81
81
|
}
|
|
82
82
|
return Array.from(new Set(weekdays)).sort((a, b) => a - b);
|
|
83
83
|
};
|
|
@@ -89,18 +89,18 @@ const buildSchedule = (input) => {
|
|
|
89
89
|
const cron = asNonEmptyString(input.cron);
|
|
90
90
|
const selectors = [daily, weekly, once, cron].filter(Boolean);
|
|
91
91
|
if (selectors.length !== 1) {
|
|
92
|
-
throw new
|
|
92
|
+
throw new AiWorkerControlError('Provide exactly one of daily, weekly, once, or cron', 400);
|
|
93
93
|
}
|
|
94
94
|
const timezone = asNonEmptyString(input.timezone);
|
|
95
95
|
if (daily) return { kind: 'daily', times: [daily], ...(timezone ? { timezone } : {}) };
|
|
96
96
|
if (weekly) {
|
|
97
97
|
const time = asNonEmptyString(input.time);
|
|
98
|
-
if (!time) throw new
|
|
98
|
+
if (!time) throw new AiWorkerControlError('time is required with weekly', 400);
|
|
99
99
|
return { kind: 'weekly', weekdays: parseWeekdays(weekly), times: [time], ...(timezone ? { timezone } : {}) };
|
|
100
100
|
}
|
|
101
101
|
if (once) {
|
|
102
102
|
const time = asNonEmptyString(input.time);
|
|
103
|
-
if (!time) throw new
|
|
103
|
+
if (!time) throw new AiWorkerControlError('time is required with once', 400);
|
|
104
104
|
return { kind: 'once', date: once, time, ...(timezone ? { timezone } : {}) };
|
|
105
105
|
}
|
|
106
106
|
return { kind: 'cron', cron, ...(timezone ? { timezone } : {}) };
|
|
@@ -109,15 +109,15 @@ const buildSchedule = (input) => {
|
|
|
109
109
|
const buildScheduledTask = (input) => {
|
|
110
110
|
const name = asNonEmptyString(input.name);
|
|
111
111
|
const prompt = asNonEmptyString(input.prompt);
|
|
112
|
-
if (!name) throw new
|
|
113
|
-
if (!prompt) throw new
|
|
112
|
+
if (!name) throw new AiWorkerControlError('name is required', 400);
|
|
113
|
+
if (!prompt) throw new AiWorkerControlError('prompt is required', 400);
|
|
114
114
|
const model = parseModel(input.model);
|
|
115
115
|
const goalTokenBudget = input.goalTokenBudget;
|
|
116
116
|
if (goalTokenBudget !== undefined && input.goal !== true) {
|
|
117
|
-
throw new
|
|
117
|
+
throw new AiWorkerControlError('goalTokenBudget requires goal', 400);
|
|
118
118
|
}
|
|
119
119
|
if (goalTokenBudget !== undefined && (!Number.isSafeInteger(goalTokenBudget) || goalTokenBudget < 1000 || goalTokenBudget > 100_000_000)) {
|
|
120
|
-
throw new
|
|
120
|
+
throw new AiWorkerControlError('goalTokenBudget must be from 1000 to 100000000', 400);
|
|
121
121
|
}
|
|
122
122
|
return {
|
|
123
123
|
name,
|
|
@@ -134,7 +134,7 @@ const buildScheduledTask = (input) => {
|
|
|
134
134
|
};
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
-
export const
|
|
137
|
+
export const createAiWorkerControlService = (dependencies) => {
|
|
138
138
|
const {
|
|
139
139
|
readSettingsFromDiskMigrated,
|
|
140
140
|
sanitizeProjects,
|
|
@@ -152,11 +152,11 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
152
152
|
|
|
153
153
|
const wait = (duration, signal) => {
|
|
154
154
|
if (!signal) return sleep(duration);
|
|
155
|
-
if (signal.aborted) return Promise.reject(new
|
|
155
|
+
if (signal.aborted) return Promise.reject(new AiWorkerControlError('AiWorker action was cancelled', 499));
|
|
156
156
|
return new Promise((resolve, reject) => {
|
|
157
157
|
const onAbort = () => {
|
|
158
158
|
signal.removeEventListener('abort', onAbort);
|
|
159
|
-
reject(new
|
|
159
|
+
reject(new AiWorkerControlError('AiWorker action was cancelled', 499));
|
|
160
160
|
};
|
|
161
161
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
162
162
|
sleep(duration).then(() => {
|
|
@@ -201,7 +201,7 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
201
201
|
const response = await client.session.status({ directory });
|
|
202
202
|
const statuses = response?.data;
|
|
203
203
|
if (!statuses || typeof statuses !== 'object' || Array.isArray(statuses)) {
|
|
204
|
-
throw new
|
|
204
|
+
throw new AiWorkerControlError('Invalid session status response', 500);
|
|
205
205
|
}
|
|
206
206
|
return statuses[sessionID] || { type: 'idle' };
|
|
207
207
|
};
|
|
@@ -223,7 +223,7 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
223
223
|
const deadline = now() + timeoutMs;
|
|
224
224
|
let observedActivity = false;
|
|
225
225
|
while (true) {
|
|
226
|
-
if (signal?.aborted) throw new
|
|
226
|
+
if (signal?.aborted) throw new AiWorkerControlError('AiWorker action was cancelled', 499);
|
|
227
227
|
const status = await sessionStatus(client, sessionID, directory);
|
|
228
228
|
if (status.type === 'busy' || status.type === 'retry') {
|
|
229
229
|
observedActivity = true;
|
|
@@ -238,7 +238,7 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
238
238
|
}
|
|
239
239
|
const remaining = deadline - now();
|
|
240
240
|
if (remaining <= 0) {
|
|
241
|
-
throw new
|
|
241
|
+
throw new AiWorkerControlError(`Session did not become idle within ${Math.ceil(timeoutMs / 1000)} seconds`, 500);
|
|
242
242
|
}
|
|
243
243
|
await wait(Math.min(WAIT_POLL_INTERVAL_MS, remaining), signal);
|
|
244
244
|
}
|
|
@@ -262,8 +262,8 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
262
262
|
};
|
|
263
263
|
|
|
264
264
|
const executeSessionAction = async (action, input, contextDirectory, signal) => {
|
|
265
|
-
if (input.timeout !== undefined && input.wait !== true) throw new
|
|
266
|
-
if (input.lastAssistant === true && input.wait !== true) throw new
|
|
265
|
+
if (input.timeout !== undefined && input.wait !== true) throw new AiWorkerControlError('timeout requires wait', 400);
|
|
266
|
+
if (input.lastAssistant === true && input.wait !== true) throw new AiWorkerControlError('lastAssistant requires wait', 400);
|
|
267
267
|
const sessionID = asNonEmptyString(input.sessionId);
|
|
268
268
|
let directory = asNonEmptyString(input.directory) || (!input.projectId ? asNonEmptyString(contextDirectory) : null);
|
|
269
269
|
if (sessionID && action !== 'session.create' && !asNonEmptyString(input.directory) && !input.projectId) {
|
|
@@ -293,7 +293,7 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
293
293
|
if (action === 'session.create') {
|
|
294
294
|
result = await sessionService.create(payload);
|
|
295
295
|
} else {
|
|
296
|
-
if (!sessionID) throw new
|
|
296
|
+
if (!sessionID) throw new AiWorkerControlError('sessionId is required', 400);
|
|
297
297
|
if (action === 'session.send') {
|
|
298
298
|
result = await sessionService.send(sessionID, payload);
|
|
299
299
|
} else {
|
|
@@ -335,11 +335,11 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
335
335
|
const readViewport = (required) => {
|
|
336
336
|
const viewport = asNonEmptyString(input.viewport);
|
|
337
337
|
if (!viewport) {
|
|
338
|
-
if (required) throw new
|
|
338
|
+
if (required) throw new AiWorkerControlError('viewport is required for browser.resize', 400);
|
|
339
339
|
return;
|
|
340
340
|
}
|
|
341
341
|
if (!['mobile', 'tablet', 'desktop', 'fill'].includes(viewport)) {
|
|
342
|
-
throw new
|
|
342
|
+
throw new AiWorkerControlError('viewport must be mobile, tablet, desktop, or fill', 400);
|
|
343
343
|
}
|
|
344
344
|
parameters.viewport = viewport;
|
|
345
345
|
};
|
|
@@ -354,15 +354,15 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
354
354
|
if (action === 'browser.open') {
|
|
355
355
|
readViewport(false);
|
|
356
356
|
const url = asNonEmptyString(input.url);
|
|
357
|
-
if (!url) throw new
|
|
357
|
+
if (!url) throw new AiWorkerControlError('url is required for browser.open', 400);
|
|
358
358
|
let parsed;
|
|
359
359
|
try {
|
|
360
360
|
parsed = new URL(url);
|
|
361
361
|
} catch {
|
|
362
|
-
throw new
|
|
362
|
+
throw new AiWorkerControlError('url must be an absolute http(s) URL', 400);
|
|
363
363
|
}
|
|
364
364
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
365
|
-
throw new
|
|
365
|
+
throw new AiWorkerControlError('url must use http or https', 400);
|
|
366
366
|
}
|
|
367
367
|
parameters.url = parsed.toString();
|
|
368
368
|
}
|
|
@@ -372,7 +372,7 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
372
372
|
const selector = asNonEmptyString(input.selector);
|
|
373
373
|
const text = asNonEmptyString(input.text);
|
|
374
374
|
if (!selector && !text) {
|
|
375
|
-
throw new
|
|
375
|
+
throw new AiWorkerControlError('browser.click requires selector or text', 400);
|
|
376
376
|
}
|
|
377
377
|
if (selector) parameters.selector = selector;
|
|
378
378
|
if (text) parameters.text = text;
|
|
@@ -385,15 +385,15 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
385
385
|
|
|
386
386
|
if (action === 'browser.inspect') {
|
|
387
387
|
const selector = asNonEmptyString(input.selector);
|
|
388
|
-
if (!selector) throw new
|
|
388
|
+
if (!selector) throw new AiWorkerControlError('selector is required for browser.inspect', 400);
|
|
389
389
|
parameters.selector = selector;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
if (action === 'browser.type') {
|
|
393
393
|
const selector = asNonEmptyString(input.selector);
|
|
394
|
-
if (!selector) throw new
|
|
394
|
+
if (!selector) throw new AiWorkerControlError('selector is required for browser.type', 400);
|
|
395
395
|
if (typeof input.value !== 'string') {
|
|
396
|
-
throw new
|
|
396
|
+
throw new AiWorkerControlError('value is required for browser.type', 400);
|
|
397
397
|
}
|
|
398
398
|
parameters.selector = selector;
|
|
399
399
|
parameters.value = input.value;
|
|
@@ -404,10 +404,10 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
404
404
|
const selector = asNonEmptyString(input.selector);
|
|
405
405
|
const direction = asNonEmptyString(input.direction);
|
|
406
406
|
if (!selector && !direction) {
|
|
407
|
-
throw new
|
|
407
|
+
throw new AiWorkerControlError('browser.scroll requires direction or selector', 400);
|
|
408
408
|
}
|
|
409
409
|
if (direction && !['up', 'down', 'top', 'bottom'].includes(direction)) {
|
|
410
|
-
throw new
|
|
410
|
+
throw new AiWorkerControlError('direction must be up, down, top, or bottom', 400);
|
|
411
411
|
}
|
|
412
412
|
if (selector) parameters.selector = selector;
|
|
413
413
|
if (direction) parameters.direction = direction;
|
|
@@ -425,7 +425,7 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
425
425
|
if (action === 'browser.capture') {
|
|
426
426
|
const directory = asNonEmptyString(input.directory) || asNonEmptyString(contextDirectory);
|
|
427
427
|
if (!directory) {
|
|
428
|
-
throw new
|
|
428
|
+
throw new AiWorkerControlError('directory is required to save a screenshot', 400);
|
|
429
429
|
}
|
|
430
430
|
const capture = result && typeof result === 'object' ? result : {};
|
|
431
431
|
const saved = await writeScreenshot({
|
|
@@ -457,17 +457,17 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
457
457
|
const execute = async (action, input = {}, contextDirectory, options = {}) => {
|
|
458
458
|
try {
|
|
459
459
|
if (!CONTROL_ACTIONS.has(action)) {
|
|
460
|
-
throw new
|
|
460
|
+
throw new AiWorkerControlError(`Unsupported AiWorker action: ${action || 'missing'}`, 400);
|
|
461
461
|
}
|
|
462
462
|
if (action.startsWith('memory.')) {
|
|
463
463
|
if (!agentMemoryActions) {
|
|
464
|
-
throw new
|
|
464
|
+
throw new AiWorkerControlError('Agent memory is not available on this server', 503);
|
|
465
465
|
}
|
|
466
466
|
return agentMemoryActions.execute(action, input, contextDirectory);
|
|
467
467
|
}
|
|
468
468
|
if (action.startsWith('browser.')) {
|
|
469
469
|
if (!browserControl) {
|
|
470
|
-
throw new
|
|
470
|
+
throw new AiWorkerControlError('The in-app browser is not available on this server', 503);
|
|
471
471
|
}
|
|
472
472
|
return browserAction(action, input, options.signal, contextDirectory);
|
|
473
473
|
}
|
|
@@ -477,7 +477,7 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
477
477
|
if (action.startsWith('schedule.')) {
|
|
478
478
|
const taskID = asNonEmptyString(input.taskId);
|
|
479
479
|
if (SCHEDULE_TASK_ID_ACTIONS.has(action) && !taskID) {
|
|
480
|
-
throw new
|
|
480
|
+
throw new AiWorkerControlError('taskId is required', 400);
|
|
481
481
|
}
|
|
482
482
|
const explicitProjectID = asNonEmptyString(input.projectId);
|
|
483
483
|
const explicitDirectory = asNonEmptyString(input.directory);
|
|
@@ -501,7 +501,7 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
501
501
|
return { deleted: true, tasks: await scheduledTaskService.remove(projectID, taskID) };
|
|
502
502
|
case 'schedule.toggle': {
|
|
503
503
|
if (typeof input.disabled !== 'boolean') {
|
|
504
|
-
throw new
|
|
504
|
+
throw new AiWorkerControlError('disabled is required for schedule.toggle', 400);
|
|
505
505
|
}
|
|
506
506
|
const enabled = input.disabled === false;
|
|
507
507
|
return { task: await scheduledTaskService.setEnabled(projectID, taskID, enabled), enabled };
|
|
@@ -536,18 +536,18 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
536
536
|
}
|
|
537
537
|
return { sessions, limit, directory, archived: input.all === true ? 'included' : 'excluded' };
|
|
538
538
|
}
|
|
539
|
-
if (!sessionID) throw new
|
|
540
|
-
if (!directory) throw new
|
|
539
|
+
if (!sessionID) throw new AiWorkerControlError('sessionId is required', 400);
|
|
540
|
+
if (!directory) throw new AiWorkerControlError('directory is required', 400);
|
|
541
541
|
if (action === 'session.status') {
|
|
542
542
|
return { sessionId: sessionID, directory, sessionStatus: await sessionStatus(client, sessionID, directory) };
|
|
543
543
|
}
|
|
544
544
|
if (action === 'session.messages') {
|
|
545
|
-
if (input.timeout !== undefined && input.wait !== true) throw new
|
|
545
|
+
if (input.timeout !== undefined && input.wait !== true) throw new AiWorkerControlError('timeout requires wait', 400);
|
|
546
546
|
const role = input.lastAssistant === true ? 'assistant' : (asNonEmptyString(input.role) || 'all');
|
|
547
|
-
if (!['all', 'user', 'assistant'].includes(role)) throw new
|
|
547
|
+
if (!['all', 'user', 'assistant'].includes(role)) throw new AiWorkerControlError('role must be all, user, or assistant', 400);
|
|
548
548
|
const last = input.last === true || input.lastAssistant === true;
|
|
549
|
-
if (input.all === true && (last || input.limit !== undefined)) throw new
|
|
550
|
-
if (last && input.limit !== undefined) throw new
|
|
549
|
+
if (input.all === true && (last || input.limit !== undefined)) throw new AiWorkerControlError('all cannot be combined with last or limit', 400);
|
|
550
|
+
if (last && input.limit !== undefined) throw new AiWorkerControlError('last cannot be combined with limit', 400);
|
|
551
551
|
const currentStatus = input.wait === true
|
|
552
552
|
? await waitForIdle({ client, sessionID, directory, timeoutMs: normalizeWaitTimeoutMs(input.timeout), requireActivity: false, startedAt: now(), signal: options.signal })
|
|
553
553
|
: await sessionStatus(client, sessionID, directory);
|
|
@@ -555,9 +555,9 @@ export const createOpenChamberControlService = (dependencies) => {
|
|
|
555
555
|
return { sessionId: sessionID, directory, role, sessionStatus: currentStatus, messages: await sessionMessages(client, sessionID, directory, role, limit) };
|
|
556
556
|
}
|
|
557
557
|
}
|
|
558
|
-
throw new
|
|
558
|
+
throw new AiWorkerControlError(`Unsupported AiWorker action: ${action || 'missing'}`, 400);
|
|
559
559
|
} catch (error) {
|
|
560
|
-
throw asControlError(error, `Failed to execute ${action || '
|
|
560
|
+
throw asControlError(error, `Failed to execute ${action || 'AiWorker action'}`);
|
|
561
561
|
}
|
|
562
562
|
};
|
|
563
563
|
|
|
@@ -4,7 +4,7 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import fs from 'node:fs/promises';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { createAiWorkerControlService } from './service.js';
|
|
8
8
|
|
|
9
9
|
const createService = (overrides = {}) => {
|
|
10
10
|
const client = {
|
|
@@ -28,7 +28,7 @@ const createService = (overrides = {}) => {
|
|
|
28
28
|
remove: vi.fn(),
|
|
29
29
|
setEnabled: vi.fn(),
|
|
30
30
|
};
|
|
31
|
-
const service =
|
|
31
|
+
const service = createAiWorkerControlService({
|
|
32
32
|
readSettingsFromDiskMigrated: vi.fn(async () => ({
|
|
33
33
|
projects: [{ id: 'project-1', path: '/repo', label: 'Repo' }],
|
|
34
34
|
defaultModel: 'provider/model',
|
|
@@ -47,7 +47,7 @@ const createService = (overrides = {}) => {
|
|
|
47
47
|
return { service, client, sessionService, scheduledTaskService };
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
describe('
|
|
50
|
+
describe('AiWorker control service', () => {
|
|
51
51
|
it('serves project and model projections without an HTTP or CLI round trip', async () => {
|
|
52
52
|
const { service } = createService();
|
|
53
53
|
await expect(service.execute('projects.list')).resolves.toEqual({
|
|
@@ -262,7 +262,7 @@ describe('OpenChamber control service', () => {
|
|
|
262
262
|
|
|
263
263
|
it('rejects actions outside the fixed contract', async () => {
|
|
264
264
|
const { service } = createService();
|
|
265
|
-
await expect(service.execute('session.delete')).rejects.toThrow('Unsupported
|
|
265
|
+
await expect(service.execute('session.delete')).rejects.toThrow('Unsupported AiWorker action');
|
|
266
266
|
});
|
|
267
267
|
});
|
|
268
268
|
|
|
@@ -289,7 +289,7 @@ describe('browser capture', () => {
|
|
|
289
289
|
|
|
290
290
|
const result = await service.execute('browser.capture', { label: 'After fix' }, directory);
|
|
291
291
|
|
|
292
|
-
expect(result.path.startsWith('.
|
|
292
|
+
expect(result.path.startsWith('.aiworker/screenshots/after-fix-')).toBe(true);
|
|
293
293
|
expect(result.path.endsWith('.png')).toBe(true);
|
|
294
294
|
expect(result.url).toBe('http://localhost:3000/');
|
|
295
295
|
expect(result.viewport).toEqual({ mode: 'mobile', width: 390, height: 844 });
|
|
@@ -4,7 +4,7 @@ import { createWorktree, getWorktreeBootstrapStatus } from '../git/index.js';
|
|
|
4
4
|
import { expandSnippets } from '../opencode/snippets.js';
|
|
5
5
|
import { expandCommandGoalObjective, parseScheduledCommandPrompt } from '../scheduled-tasks/runtime.js';
|
|
6
6
|
import { buildGoalIntroText, createSessionGoal } from '../session-goal/create.js';
|
|
7
|
-
import {
|
|
7
|
+
import { AiWorkerControlError, asControlError } from '../openchamber-control/error.js';
|
|
8
8
|
|
|
9
9
|
const asNonEmptyString = (value) => {
|
|
10
10
|
if (typeof value !== 'string') return null;
|
|
@@ -253,6 +253,40 @@ const latestCompletedAssistantMessageID = async ({ client, sessionID, directory
|
|
|
253
253
|
return asNonEmptyString(latest?.id);
|
|
254
254
|
};
|
|
255
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Upper bound on one archive batch.
|
|
258
|
+
*
|
|
259
|
+
* The batch is applied one session at a time against OpenCode, so an unbounded
|
|
260
|
+
* list would hold a request open for as long as the list is large. Callers with
|
|
261
|
+
* more sessions than this send several batches and keep their own partial
|
|
262
|
+
* results.
|
|
263
|
+
*/
|
|
264
|
+
const MAX_ARCHIVE_BATCH = 500;
|
|
265
|
+
|
|
266
|
+
const parseArchiveRequest = (payload) => {
|
|
267
|
+
const rawIds = payload?.ids;
|
|
268
|
+
if (!Array.isArray(rawIds) || rawIds.length === 0) {
|
|
269
|
+
return { ok: false, error: 'ids must be a non-empty array of session ids' };
|
|
270
|
+
}
|
|
271
|
+
if (rawIds.length > MAX_ARCHIVE_BATCH) {
|
|
272
|
+
return { ok: false, error: `ids must contain at most ${MAX_ARCHIVE_BATCH} session ids` };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const ids = [];
|
|
276
|
+
for (const value of rawIds) {
|
|
277
|
+
const id = asNonEmptyString(value);
|
|
278
|
+
if (!id) return { ok: false, error: 'ids must contain non-empty session ids' };
|
|
279
|
+
ids.push(id);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const archivedAt = payload?.archivedAt;
|
|
283
|
+
if (archivedAt !== undefined && (!Number.isSafeInteger(archivedAt) || archivedAt <= 0)) {
|
|
284
|
+
return { ok: false, error: 'archivedAt must be a positive integer timestamp' };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { ok: true, ids, archivedAt: archivedAt ?? Date.now() };
|
|
288
|
+
};
|
|
289
|
+
|
|
256
290
|
const resolveRequestedDirectory = async ({ payload, readSettingsFromDiskMigrated, sanitizeProjects, validateDirectoryPath }) => {
|
|
257
291
|
const projectID = asNonEmptyString(payload?.projectId) || asNonEmptyString(payload?.projectID);
|
|
258
292
|
if (projectID) {
|
|
@@ -292,12 +326,12 @@ const waitForWorktreeBootstrapReady = async ({ directory }) => {
|
|
|
292
326
|
for (;;) {
|
|
293
327
|
const status = await getWorktreeBootstrapStatus(directory);
|
|
294
328
|
if (status?.status === 'failed') {
|
|
295
|
-
throw new
|
|
329
|
+
throw new AiWorkerControlError(`Worktree bootstrap failed: ${status.error || 'unknown error'}`, 500);
|
|
296
330
|
}
|
|
297
331
|
const phase = status?.phase;
|
|
298
332
|
if (status?.status === 'ready' || phase === 'git-ready' || phase === 'setup-ready') return;
|
|
299
333
|
if (Date.now() >= deadline) {
|
|
300
|
-
throw new
|
|
334
|
+
throw new AiWorkerControlError('Timed out waiting for the worktree bootstrap', 500);
|
|
301
335
|
}
|
|
302
336
|
await new Promise((resolve) => setTimeout(resolve, WORKTREE_BOOTSTRAP_POLL_MS));
|
|
303
337
|
}
|
|
@@ -350,7 +384,7 @@ const resolveWorktreeInput = (payload) => {
|
|
|
350
384
|
};
|
|
351
385
|
};
|
|
352
386
|
|
|
353
|
-
export const
|
|
387
|
+
export const createAiWorkerSessionService = (dependencies) => {
|
|
354
388
|
const {
|
|
355
389
|
readSettingsFromDiskMigrated,
|
|
356
390
|
sanitizeProjects,
|
|
@@ -404,23 +438,23 @@ export const createOpenChamberSessionService = (dependencies) => {
|
|
|
404
438
|
if (requestedAgent && agents.length > 0) {
|
|
405
439
|
const agent = agents.find((entry) => entry?.name === requestedAgent) || null;
|
|
406
440
|
if (!agent) {
|
|
407
|
-
throw new
|
|
441
|
+
throw new AiWorkerControlError(`Unknown agent '${requestedAgent}' for ${directory}`, 400);
|
|
408
442
|
}
|
|
409
443
|
if (!isPrimaryAgentMode(agent.mode)) {
|
|
410
|
-
throw new
|
|
444
|
+
throw new AiWorkerControlError(`Agent '${requestedAgent}' is a subagent and cannot receive a prompt directly`, 400);
|
|
411
445
|
}
|
|
412
446
|
}
|
|
413
447
|
|
|
414
448
|
if (requestedModel && providers.length > 0) {
|
|
415
449
|
if (!hasProviderModel(providers, requestedModel.providerID, requestedModel.modelID)) {
|
|
416
|
-
throw new
|
|
450
|
+
throw new AiWorkerControlError(
|
|
417
451
|
`Unknown model '${requestedModel.providerID}/${requestedModel.modelID}' for ${directory}`,
|
|
418
452
|
400,
|
|
419
453
|
);
|
|
420
454
|
}
|
|
421
455
|
if (requestedVariant
|
|
422
456
|
&& !resolveVariant(providers, requestedModel.providerID, requestedModel.modelID, requestedVariant)) {
|
|
423
|
-
throw new
|
|
457
|
+
throw new AiWorkerControlError(
|
|
424
458
|
`Unknown variant '${requestedVariant}' for model '${requestedModel.providerID}/${requestedModel.modelID}'`,
|
|
425
459
|
400,
|
|
426
460
|
);
|
|
@@ -499,7 +533,7 @@ export const createOpenChamberSessionService = (dependencies) => {
|
|
|
499
533
|
tokenBudget: goalInput.tokenBudget,
|
|
500
534
|
providerID: model.providerID,
|
|
501
535
|
modelID: model.modelID,
|
|
502
|
-
onWarning: (message, error) => console.warn(`[
|
|
536
|
+
onWarning: (message, error) => console.warn(`[AiWorkerSessions] ${message}:`, error?.message || error),
|
|
503
537
|
});
|
|
504
538
|
}
|
|
505
539
|
|
|
@@ -579,12 +613,70 @@ export const createOpenChamberSessionService = (dependencies) => {
|
|
|
579
613
|
return { model, agent, variant, promptDispatched: true, dispatchedAsCommand: Boolean(resolvedCommand) };
|
|
580
614
|
};
|
|
581
615
|
|
|
616
|
+
/**
|
|
617
|
+
* Archive a batch of sessions in one request.
|
|
618
|
+
*
|
|
619
|
+
* The UI archives every session linked to a worktree before removing it.
|
|
620
|
+
* Doing that from the browser costs one request per session plus a store
|
|
621
|
+
* reconciliation between each of them, which is what made deleting a
|
|
622
|
+
* worktree with many sessions take tens of seconds. Here the batch stays on
|
|
623
|
+
* the server, next to OpenCode, and the client reconciles once.
|
|
624
|
+
*
|
|
625
|
+
* Sessions are updated one at a time on purpose: they are archived against a
|
|
626
|
+
* single OpenCode instance, and a fan-out of concurrent writes would trade a
|
|
627
|
+
* UI stall for server event-loop starvation. One failed session never stops
|
|
628
|
+
* the batch — it is reported in `failedIds` while the rest still archive, so
|
|
629
|
+
* callers keep the partial-failure behaviour they already show.
|
|
630
|
+
*/
|
|
631
|
+
const archive = async (payload = {}) => {
|
|
632
|
+
const parsed = parseArchiveRequest(payload);
|
|
633
|
+
if (!parsed.ok) {
|
|
634
|
+
throw new AiWorkerControlError(parsed.error, 400);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const resolvedDirectory = await resolveRequestedDirectory({
|
|
638
|
+
payload,
|
|
639
|
+
readSettingsFromDiskMigrated,
|
|
640
|
+
sanitizeProjects,
|
|
641
|
+
validateDirectoryPath,
|
|
642
|
+
});
|
|
643
|
+
if (!resolvedDirectory.ok) {
|
|
644
|
+
throw new AiWorkerControlError(resolvedDirectory.error, resolvedDirectory.status || 400);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (typeof waitForOpenCodeReady === 'function') await waitForOpenCodeReady(10_000, 250);
|
|
648
|
+
|
|
649
|
+
const directory = resolvedDirectory.directory;
|
|
650
|
+
const baseUrl = buildOpenCodeUrl('/', '').replace(/\/$/, '');
|
|
651
|
+
const client = createOpencodeClient({ baseUrl, headers: getOpenCodeAuthHeaders() });
|
|
652
|
+
|
|
653
|
+
const archived = [];
|
|
654
|
+
const failedIds = [];
|
|
655
|
+
for (const sessionID of parsed.ids) {
|
|
656
|
+
try {
|
|
657
|
+
const response = await client.session.update({
|
|
658
|
+
sessionID,
|
|
659
|
+
directory,
|
|
660
|
+
time: { archived: parsed.archivedAt },
|
|
661
|
+
});
|
|
662
|
+
const session = response?.data;
|
|
663
|
+
if (session?.id) archived.push(session);
|
|
664
|
+
else failedIds.push(sessionID);
|
|
665
|
+
} catch (error) {
|
|
666
|
+
console.warn('[AiWorkerSessions] failed to archive session', sessionID, error);
|
|
667
|
+
failedIds.push(sessionID);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return { directory, archived, failedIds };
|
|
672
|
+
};
|
|
673
|
+
|
|
582
674
|
const create = async (payload = {}) => {
|
|
583
675
|
const title = asNonEmptyString(payload.title);
|
|
584
676
|
const prompt = asNonEmptyString(payload.prompt);
|
|
585
677
|
const goalInput = resolveGoalInput(payload, prompt);
|
|
586
678
|
if (!goalInput.ok) {
|
|
587
|
-
throw new
|
|
679
|
+
throw new AiWorkerControlError(goalInput.error, 400);
|
|
588
680
|
}
|
|
589
681
|
const model = resolveRequestedModel(payload);
|
|
590
682
|
const agent = asNonEmptyString(payload.agent);
|
|
@@ -597,14 +689,14 @@ export const createOpenChamberSessionService = (dependencies) => {
|
|
|
597
689
|
validateDirectoryPath,
|
|
598
690
|
});
|
|
599
691
|
if (!resolvedDirectory.ok) {
|
|
600
|
-
throw new
|
|
692
|
+
throw new AiWorkerControlError(resolvedDirectory.error, resolvedDirectory.status || 400);
|
|
601
693
|
}
|
|
602
694
|
|
|
603
695
|
const worktreeInput = resolveWorktreeInput(payload);
|
|
604
696
|
let worktree = null;
|
|
605
697
|
let sessionDirectory = resolvedDirectory.directory;
|
|
606
698
|
if (payload?.worktree && !worktreeInput) {
|
|
607
|
-
throw new
|
|
699
|
+
throw new AiWorkerControlError('worktree.name is required when worktree is provided', 400);
|
|
608
700
|
}
|
|
609
701
|
|
|
610
702
|
if (typeof waitForOpenCodeReady === 'function') await waitForOpenCodeReady(10_000, 250);
|
|
@@ -692,10 +784,10 @@ export const createOpenChamberSessionService = (dependencies) => {
|
|
|
692
784
|
const runExisting = async (action, sourceSessionId, payload = {}) => {
|
|
693
785
|
const sourceSessionID = asNonEmptyString(sourceSessionId);
|
|
694
786
|
const prompt = asNonEmptyString(payload.prompt);
|
|
695
|
-
if (!sourceSessionID) throw new
|
|
696
|
-
if (!prompt) throw new
|
|
787
|
+
if (!sourceSessionID) throw new AiWorkerControlError('sessionId is required', 400);
|
|
788
|
+
if (!prompt) throw new AiWorkerControlError('prompt is required', 400);
|
|
697
789
|
const goalInput = resolveGoalInput(payload, prompt);
|
|
698
|
-
if (!goalInput.ok) throw new
|
|
790
|
+
if (!goalInput.ok) throw new AiWorkerControlError(goalInput.error, 400);
|
|
699
791
|
const requestedModel = resolveRequestedModel(payload);
|
|
700
792
|
|
|
701
793
|
let targetSessionID = sourceSessionID;
|
|
@@ -709,7 +801,7 @@ export const createOpenChamberSessionService = (dependencies) => {
|
|
|
709
801
|
validateDirectoryPath,
|
|
710
802
|
});
|
|
711
803
|
if (!resolvedDirectory.ok) {
|
|
712
|
-
throw new
|
|
804
|
+
throw new AiWorkerControlError(resolvedDirectory.error, resolvedDirectory.status || 400);
|
|
713
805
|
}
|
|
714
806
|
directory = resolvedDirectory.directory;
|
|
715
807
|
if (typeof waitForOpenCodeReady === 'function') await waitForOpenCodeReady(10_000, 250);
|
|
@@ -794,7 +886,7 @@ export const createOpenChamberSessionService = (dependencies) => {
|
|
|
794
886
|
const statusCode = Number(error?.statusCode) || 500;
|
|
795
887
|
const forkCreated = action === 'fork' && targetSessionID !== sourceSessionID;
|
|
796
888
|
const goalConfigured = error?.goalConfigured === true;
|
|
797
|
-
throw new
|
|
889
|
+
throw new AiWorkerControlError(
|
|
798
890
|
error instanceof Error ? error.message : `Failed to ${action} session`,
|
|
799
891
|
statusCode,
|
|
800
892
|
{
|
|
@@ -813,6 +905,7 @@ export const createOpenChamberSessionService = (dependencies) => {
|
|
|
813
905
|
|
|
814
906
|
return {
|
|
815
907
|
create,
|
|
908
|
+
archive,
|
|
816
909
|
send: (sessionID, payload) => runExisting('send', sessionID, payload),
|
|
817
910
|
fork: (sessionID, payload) => runExisting('fork', sessionID, payload),
|
|
818
911
|
};
|
|
@@ -831,18 +924,27 @@ const sendServiceError = (res, error, fallback) => {
|
|
|
831
924
|
});
|
|
832
925
|
};
|
|
833
926
|
|
|
834
|
-
export const
|
|
835
|
-
const service = dependencies.sessionService ||
|
|
927
|
+
export const registerAiWorkerSessionRoutes = (app, dependencies) => {
|
|
928
|
+
const service = dependencies.sessionService || createAiWorkerSessionService(dependencies);
|
|
836
929
|
|
|
837
930
|
app.post('/api/openchamber/sessions', express.json({ limit: '1mb' }), async (req, res) => {
|
|
838
931
|
try {
|
|
839
932
|
return res.json(await service.create(req.body && typeof req.body === 'object' ? req.body : {}));
|
|
840
933
|
} catch (error) {
|
|
841
|
-
console.error('[
|
|
934
|
+
console.error('[AiWorkerSessions] failed to create session:', error);
|
|
842
935
|
return sendServiceError(res, error, 'Failed to create session');
|
|
843
936
|
}
|
|
844
937
|
});
|
|
845
938
|
|
|
939
|
+
app.post('/api/openchamber/sessions/archive', express.json({ limit: '1mb' }), async (req, res) => {
|
|
940
|
+
try {
|
|
941
|
+
return res.json(await service.archive(req.body && typeof req.body === 'object' ? req.body : {}));
|
|
942
|
+
} catch (error) {
|
|
943
|
+
console.error('[AiWorkerSessions] failed to archive sessions:', error);
|
|
944
|
+
return sendServiceError(res, error, 'Failed to archive sessions');
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
|
|
846
948
|
app.post(
|
|
847
949
|
'/api/openchamber/sessions/:sessionId/send',
|
|
848
950
|
express.json({ limit: '1mb' }),
|
|
@@ -850,7 +952,7 @@ export const registerOpenChamberSessionRoutes = (app, dependencies) => {
|
|
|
850
952
|
try {
|
|
851
953
|
return res.json(await service.send(req.params.sessionId, req.body));
|
|
852
954
|
} catch (error) {
|
|
853
|
-
console.error('[
|
|
955
|
+
console.error('[AiWorkerSessions] failed to send session:', error);
|
|
854
956
|
return sendServiceError(res, error, 'Failed to send session');
|
|
855
957
|
}
|
|
856
958
|
},
|
|
@@ -862,7 +964,7 @@ export const registerOpenChamberSessionRoutes = (app, dependencies) => {
|
|
|
862
964
|
try {
|
|
863
965
|
return res.json(await service.fork(req.params.sessionId, req.body));
|
|
864
966
|
} catch (error) {
|
|
865
|
-
console.error('[
|
|
967
|
+
console.error('[AiWorkerSessions] failed to fork session:', error);
|
|
866
968
|
return sendServiceError(res, error, 'Failed to fork session');
|
|
867
969
|
}
|
|
868
970
|
},
|