@vantran-se/openchamber-web 1.24.4 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -8
- package/bin/lib/commands-connect-url.js +3 -8
- package/bin/lib/commands-tunnel.js +3 -0
- package/dist/assets/{CodeMirrorCommentWidgets-B4a0JkUz.js → CodeMirrorCommentWidgets-B82Oye3V.js} +1 -1
- package/dist/assets/DiffView-DJLmWn6Z.js +4 -0
- package/dist/assets/FilesView-DjksmZRo.js +17 -0
- package/dist/assets/{GitView-Byu2DY7s.js → GitView-DiUsUIgr.js} +4 -4
- package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-CYwWYLr3.js} +1 -1
- package/dist/assets/GuestSurfacePane-CmctyRI4.js +1 -0
- package/dist/assets/{JsonTreeView-VW_7czo3.js → JsonTreeView-DOHHcbgJ.js} +1 -1
- package/dist/assets/LinearIssuesView-DaqnylcV.js +1 -0
- package/dist/assets/MarkdownImageGallery-HJNvWt7L.js +1 -0
- package/dist/assets/MarkdownRendererImpl-nDZRGg2I.js +12 -0
- package/dist/assets/OnboardingScreen-C2B5Bsof.js +1 -0
- package/dist/assets/{PatchDiff-DCjGhlYV.js → PatchDiff-C4FLBjpt.js} +1 -1
- package/dist/assets/{PierreDiffViewer-CEDZNkU2.js → PierreDiffViewer-C_1S9c9n.js} +1 -1
- package/dist/assets/PlanView-_eZvb-G5.js +5 -0
- package/dist/assets/PluginPane-sM8fcZIV.js +3 -0
- package/dist/assets/SettingsView-BRfr46hb.js +29 -0
- package/dist/assets/SettingsWindow-CcUDwQHg.js +1 -0
- package/dist/assets/SubmoduleDiffSummary-CrN1ZGwl.js +1 -0
- package/dist/assets/ToolOutputDialog-Cij4jI09.js +15 -0
- package/dist/assets/ToolPartDiffPreview-BXzwQ-JY.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-NU0FHFhT.js → VirtualizedFileDiff-DSWTLJNH.js} +4 -4
- package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-CpUUgY8H.js} +2 -2
- package/dist/assets/appThemeRegistry-B5tHTkR8.js +1 -0
- package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-D7D8qV8p.js} +1 -1
- package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.js → computeVirtualFileMetrics-cq9uQ30i.js} +1 -1
- package/dist/assets/de-CUO1-KqF.js +10 -0
- package/dist/assets/es-BlbC6sGJ.js +16 -0
- package/dist/assets/fr-D2nwe_0W.js +14 -0
- package/dist/assets/index-BQOoYIOu.js +1 -0
- package/dist/assets/index-CLcM8HKx.css +1 -0
- package/dist/assets/index-CvLgnuNI.js +41 -0
- package/dist/assets/{isDiffPlainText-CO4e5J_S.js → isDiffPlainText-D0obJ2p8.js} +1 -1
- package/dist/assets/{isFilePlainText-ue6kCou7.js → isFilePlainText-DB3_rjyW.js} +1 -1
- package/dist/assets/ja-CA7vnDsZ.js +16 -0
- package/dist/assets/ko-Chy6a9l-.js +16 -0
- package/dist/assets/main-B9JFK3vF.js +896 -0
- package/dist/assets/main-Bhiqnrhd.js +2 -0
- package/dist/assets/{markdownCore-CK48LG0z.js → markdownCore-tfqOFaBd.js} +4 -4
- package/dist/assets/miniChat-B0UOL5zA.js +2 -0
- package/dist/assets/mobile-BJppFIEo.js +2 -0
- package/dist/assets/{patchFileDiff-D7QVlovD.js → patchFileDiff-Db6KuwCP.js} +3 -3
- package/dist/assets/pl-CTR5A_1P.js +16 -0
- package/dist/assets/pt-BR-BE1O1KEx.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-Yz5sfoFh.js +2 -0
- package/dist/assets/renderMobileApp-QQ-yB8aY.js +25 -0
- package/dist/assets/runtimeEndpointReset-DKDdzRzm.js +27 -0
- package/dist/assets/scheduledTasksApi-C5sCuNqf.js +1 -0
- package/dist/assets/{shikiHighlight-BPu3JiQs.js → shikiHighlight-xURoo5t_.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.js → textMateThemeFromAppTheme-BZLGuYTU.js} +1 -1
- package/dist/assets/tr-Bh5vK6cW.js +7 -0
- package/dist/assets/uk-CLDkZFy_.js +16 -0
- package/dist/assets/useAppFontEffects-BxrtCnfR.js +7337 -0
- package/dist/assets/{useInlineCommentController-CgS0fnw_.js → useInlineCommentController-CeXuR-SR.js} +2 -2
- package/dist/assets/useRootScrollLock-DgWxezc1.js +2 -0
- package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.js → vendor-codemirror-language-data-DOuaQUZ0.js} +2 -2
- package/dist/assets/vendor-cron-parser-CjvYVigk.js +2 -0
- package/dist/assets/vendor-opencode-client-_BAHQo4R.js +12 -0
- package/dist/assets/{vendor-remixicon-react-BDmGmFCA.js → vendor-remixicon-react-D6dZ0UwF.js} +1 -1
- package/dist/assets/{vendor-zod-CgAdXWZO.js → vendor-zod-Gea9uBlY.js} +1 -1
- package/dist/assets/{web-components-DxX8bZ1o.js → web-components-63H1h21j.js} +2 -2
- package/dist/assets/zh-CN-DODgcmYj.js +16 -0
- package/dist/assets/zh-TW-ByNfIwLA.js +16 -0
- package/dist/index.html +13 -7
- package/dist/mini-chat.html +5 -5
- package/dist/mobile.html +5 -5
- package/package.json +6 -5
- package/server/index.d.ts +1 -0
- package/server/index.js +246 -91
- package/server/lib/agent-tool/DOCUMENTATION.md +32 -15
- package/server/lib/agent-tool/runtime.js +135 -54
- package/server/lib/agent-tool/runtime.test.js +155 -87
- package/server/lib/browser-control/DOCUMENTATION.md +37 -2
- package/server/lib/browser-control/provider.js +217 -0
- package/server/lib/browser-control/provider.test.js +251 -0
- package/server/lib/context-obligatory/runtime.js +61 -50
- package/server/lib/context-obligatory/runtime.test.js +62 -162
- package/server/lib/event-stream/DOCUMENTATION.md +12 -1
- package/server/lib/event-stream/delta-coalescer.js +0 -0
- package/server/lib/event-stream/delta-coalescer.test.js +68 -57
- package/server/lib/event-stream/directory-ws-bridge.js +1 -1
- package/server/lib/event-stream/global-hub.js +10 -1
- package/server/lib/event-stream/global-hub.test.js +27 -26
- package/server/lib/event-stream/index.js +6 -0
- package/server/lib/event-stream/protocol.js +26 -19
- package/server/lib/event-stream/protocol.test.js +18 -7
- package/server/lib/event-stream/rebind.test.js +5 -5
- package/server/lib/event-stream/runtime.test.js +17 -15
- package/server/lib/event-stream/translate-v2.js +313 -0
- package/server/lib/event-stream/translate-v2.test.js +209 -0
- package/server/lib/event-stream/upstream-reader.test.js +16 -16
- package/server/lib/fs/DOCUMENTATION.md +4 -2
- package/server/lib/fs/byte-range.js +35 -0
- package/server/lib/fs/byte-range.test.js +31 -0
- package/server/lib/fs/routes.js +47 -42
- package/server/lib/fs/routes.test.js +123 -18
- package/server/lib/git/service.js +12 -0
- package/server/lib/github/DOCUMENTATION.md +1 -1
- package/server/lib/github/pr-status.js +22 -2
- package/server/lib/github/pr-status.test.js +27 -14
- package/server/lib/guests/DOCUMENTATION.md +13 -0
- package/server/lib/guests/catalog.js +12 -0
- package/server/lib/guests/catalog.test.js +33 -0
- package/server/lib/guests/routes.js +10 -1
- package/server/lib/guests/service.js +192 -14
- package/server/lib/guests/service.test.js +201 -1
- package/server/lib/guests/surface.js +630 -0
- package/server/lib/guests/surface.test.js +486 -0
- package/server/lib/markdown-image-grants/routes.js +18 -5
- package/server/lib/markdown-image-grants/routes.test.js +9 -6
- package/server/lib/message-queue/DOCUMENTATION.md +33 -22
- package/server/lib/message-queue/runtime.js +155 -100
- package/server/lib/message-queue/runtime.test.js +168 -82
- package/server/lib/notifications/DOCUMENTATION.md +28 -0
- package/server/lib/notifications/control-stream.http.test.js +68 -0
- package/server/lib/notifications/emitter-runtime.js +15 -0
- package/server/lib/notifications/emitter-runtime.test.js +32 -0
- package/server/lib/notifications/push-runtime.js +5 -1
- package/server/lib/notifications/push-runtime.test.js +2 -2
- package/server/lib/notifications/routes.js +1 -1
- package/server/lib/notifications/runtime.js +115 -26
- package/server/lib/notifications/template-runtime.js +13 -15
- package/server/lib/notifications/template-runtime.test.js +16 -9
- package/server/lib/notifications/trigger-runtime.test.js +187 -0
- package/server/lib/openchamber-control/DOCUMENTATION.md +10 -0
- package/server/lib/openchamber-control/actions.js +3 -2
- package/server/lib/openchamber-control/error.js +1 -1
- package/server/lib/openchamber-control/file-open.js +59 -0
- package/server/lib/openchamber-control/file-open.test.js +57 -0
- package/server/lib/openchamber-control/routes.test.js +12 -0
- package/server/lib/openchamber-control/service.js +102 -49
- package/server/lib/openchamber-control/service.test.js +84 -44
- package/server/lib/openchamber-sessions/archive-store.js +224 -0
- package/server/lib/openchamber-sessions/archive-store.test.js +187 -0
- package/server/lib/openchamber-sessions/opencode-client.js +22 -0
- package/server/lib/openchamber-sessions/routes.js +398 -328
- package/server/lib/openchamber-sessions/routes.test.js +777 -740
- package/server/lib/openchamber-sessions/session-metadata-store.js +285 -0
- package/server/lib/openchamber-sessions/session-metadata-store.test.js +259 -0
- package/server/lib/opencode/DOCUMENTATION.md +449 -29
- package/server/lib/opencode/agents.js +233 -327
- package/server/lib/opencode/auth.js +41 -44
- package/server/lib/opencode/auth.test.js +92 -0
- package/server/lib/opencode/cli-upgrade.d.ts +4 -0
- package/server/lib/opencode/cli-upgrade.js +18 -0
- package/server/lib/opencode/cli-upgrade.test.js +36 -0
- package/server/lib/opencode/commands.js +164 -145
- package/server/lib/opencode/compatibility.d.ts +16 -0
- package/server/lib/opencode/compatibility.js +88 -0
- package/server/lib/opencode/compatibility.test.js +62 -0
- package/server/lib/opencode/config-entity-routes.js +58 -29
- package/server/lib/opencode/config-entity-v2.test.js +290 -0
- package/server/lib/opencode/config-mutation-response.js +17 -7
- package/server/lib/opencode/config-mutation-response.test.js +4 -7
- package/server/lib/opencode/config-paths.test.js +2 -2
- package/server/lib/opencode/config-v2.d.ts +183 -0
- package/server/lib/opencode/config-v2.js +801 -0
- package/server/lib/opencode/config-v2.test.js +466 -0
- package/server/lib/opencode/credential-db.js +144 -0
- package/server/lib/opencode/credential-db.test.js +104 -0
- package/server/lib/opencode/env-runtime.js +48 -9
- package/server/lib/opencode/env-runtime.test.js +45 -0
- package/server/lib/opencode/feature-routes-runtime.js +15 -5
- package/server/lib/opencode/lifecycle-process.test.js +7 -4
- package/server/lib/opencode/lifecycle.js +85 -30
- package/server/lib/opencode/lifecycle.test.js +80 -8
- package/server/lib/opencode/login-shell-env.js +27 -0
- package/server/lib/opencode/managed-config-file.js +119 -0
- package/server/lib/opencode/managed-config-file.test.js +152 -0
- package/server/lib/opencode/managed-plugin-config.js +33 -13
- package/server/lib/opencode/mcp.js +43 -149
- package/server/lib/opencode/network-runtime.js +6 -7
- package/server/lib/opencode/openchamber-routes.test.js +2 -5
- package/server/lib/opencode/plugin-routes.js +3 -3
- package/server/lib/opencode/plugin-routes.test.js +6 -24
- package/server/lib/opencode/plugins.js +91 -40
- package/server/lib/opencode/plugins.test.js +28 -15
- package/server/lib/opencode/providers.js +63 -83
- package/server/lib/opencode/providers.test.js +128 -59
- package/server/lib/opencode/proxy-agent-wiring.test.js +4 -2
- package/server/lib/opencode/proxy.js +179 -129
- package/server/lib/opencode/pwa-manifest-routes.js +4 -2
- package/server/lib/opencode/pwa-manifest-routes.test.js +3 -2
- package/server/lib/opencode/response-envelope.js +14 -0
- package/server/lib/opencode/response-envelope.test.js +23 -0
- package/server/lib/opencode/routes-upgrade.test.js +73 -116
- package/server/lib/opencode/routes.js +121 -428
- package/server/lib/opencode/server-utils-runtime.js +10 -4
- package/server/lib/opencode/session-activity.js +78 -0
- package/server/lib/opencode/session-runtime.js +21 -10
- package/server/lib/opencode/session-runtime.test.js +9 -9
- package/server/lib/opencode/settings-helpers.js +9 -9
- package/server/lib/opencode/settings-helpers.test.js +7 -12
- package/server/lib/opencode/settings-registry.json +6 -8
- package/server/lib/opencode/settings-runtime.js +18 -1
- package/server/lib/opencode/settings-runtime.test.js +31 -2
- package/server/lib/opencode/shared.js +54 -22
- package/server/lib/opencode/shared.test.js +8 -4
- package/server/lib/opencode/shutdown-runtime.js +54 -7
- package/server/lib/opencode/shutdown-runtime.test.js +224 -30
- package/server/lib/opencode/skill-routes.js +23 -24
- package/server/lib/opencode/skills.js +7 -5
- package/server/lib/opencode/tunnel-wiring-runtime.js +2 -1
- package/server/lib/opencode/upgrade-capability.test.js +1 -1
- package/server/lib/opencode/v1-migration-topup.js +288 -0
- package/server/lib/opencode/v1-migration-topup.test.js +266 -0
- package/server/lib/opencode/v2-install.d.ts +8 -0
- package/server/lib/opencode/v2-install.js +158 -0
- package/server/lib/opencode/v2-install.test.js +124 -0
- package/server/lib/opencode/watcher.js +10 -3
- package/server/lib/opencode/watcher.test.js +17 -21
- package/server/lib/opencode/websearch-config.js +66 -0
- package/server/lib/package-manager.js +55 -68
- package/server/lib/package-manager.test.js +241 -75
- package/server/lib/permission-auto-accept/runtime.js +38 -22
- package/server/lib/permission-auto-accept/runtime.test.js +39 -17
- package/server/lib/quota/DOCUMENTATION.md +4 -0
- package/server/lib/quota/providers/xai.js +4 -4
- package/server/lib/relay/tunnel-host.js +4 -1
- package/server/lib/relay/tunnel-host.test.js +7 -0
- package/server/lib/routing/DOCUMENTATION.md +52 -21
- package/server/lib/routing/defaults.js +23 -6
- package/server/lib/routing/jev.js +17 -7
- package/server/lib/routing/routes.http.test.js +68 -33
- package/server/lib/routing/routes.js +65 -22
- package/server/lib/routing/runtime.js +145 -48
- package/server/lib/routing/runtime.test.js +79 -55
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +4 -0
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +51 -46
- package/server/lib/scheduled-tasks/runtime.js +81 -74
- package/server/lib/scheduled-tasks/runtime.test.js +53 -1
- package/server/lib/session-assist/DOCUMENTATION.md +8 -3
- package/server/lib/session-assist/context.js +122 -46
- package/server/lib/session-assist/context.test.js +117 -49
- package/server/lib/session-assist/runtime.js +52 -22
- package/server/lib/session-assist/runtime.test.js +145 -173
- package/server/lib/session-goal/DOCUMENTATION.md +23 -15
- package/server/lib/session-goal/create.js +9 -12
- package/server/lib/session-goal/create.test.js +27 -28
- package/server/lib/session-goal/runtime.js +164 -77
- package/server/lib/session-goal/runtime.test.js +271 -519
- package/server/lib/session-knowledge/DOCUMENTATION.md +5 -0
- package/server/lib/session-knowledge/runtime.js +59 -41
- package/server/lib/session-knowledge/runtime.test.js +90 -20
- package/server/lib/small-model/DOCUMENTATION.md +154 -208
- package/server/lib/small-model/client.js +113 -0
- package/server/lib/small-model/index.js +296 -158
- package/server/lib/small-model/index.test.js +423 -256
- package/server/lib/small-model/routes.js +1 -1
- package/server/lib/spaces/DOCUMENTATION.md +807 -0
- package/server/lib/spaces/code-in-bait.js +185 -0
- package/server/lib/spaces/code-in.js +641 -0
- package/server/lib/spaces/code-in.test.js +1048 -0
- package/server/lib/spaces/errors.js +8 -0
- package/server/lib/spaces/exec-http.js +94 -0
- package/server/lib/spaces/gatekeeper-channel.js +202 -0
- package/server/lib/spaces/gatekeeper-channel.test.js +215 -0
- package/server/lib/spaces/gatekeeper-program.cjs +914 -0
- package/server/lib/spaces/gatekeeper-program.test.js +1391 -0
- package/server/lib/spaces/hardening.js +457 -0
- package/server/lib/spaces/hardening.test.js +368 -0
- package/server/lib/spaces/host-git.js +127 -0
- package/server/lib/spaces/host-git.test.js +137 -0
- package/server/lib/spaces/labels.js +234 -0
- package/server/lib/spaces/labels.test.js +219 -0
- package/server/lib/spaces/layout.js +184 -0
- package/server/lib/spaces/layout.test.js +30 -0
- package/server/lib/spaces/manager.js +82 -0
- package/server/lib/spaces/manager.test.js +114 -0
- package/server/lib/spaces/places/code-in.docker.live.test.js +216 -0
- package/server/lib/spaces/places/contract-suite.js +156 -0
- package/server/lib/spaces/places/contract.docker.live.test.js +7 -0
- package/server/lib/spaces/places/contract.test.js +6 -0
- package/server/lib/spaces/places/docker-engine.js +126 -0
- package/server/lib/spaces/places/docker-live-support.js +202 -0
- package/server/lib/spaces/places/docker-tools.js +186 -0
- package/server/lib/spaces/places/docker.js +587 -0
- package/server/lib/spaces/places/docker.test.js +1610 -0
- package/server/lib/spaces/places/escape-suite.js +1018 -0
- package/server/lib/spaces/places/escape.docker.live.test.js +7 -0
- package/server/lib/spaces/places/fake-docker.js +375 -0
- package/server/lib/spaces/places/memory-place.js +103 -0
- package/server/lib/spaces/places/packed.docker.live.test.js +77 -0
- package/server/lib/spaces/places/registry.js +43 -0
- package/server/lib/spaces/places/registry.test.js +50 -0
- package/server/lib/spaces/places/server.docker.live.test.js +225 -0
- package/server/lib/spaces/run-command.js +261 -0
- package/server/lib/spaces/run-command.test.js +396 -0
- package/server/lib/spaces/space-server.js +190 -0
- package/server/lib/spaces/space-server.test.js +335 -0
- package/server/lib/spaces/tools-filler.js +82 -0
- package/server/lib/spaces/tools-filler.test.js +148 -0
- package/server/lib/spaces/tools-pack.js +49 -0
- package/server/lib/spaces/tools-pack.test.js +57 -0
- package/server/lib/spaces/tools.js +138 -0
- package/server/lib/spaces/tools.test.js +153 -0
- package/server/lib/tts/openai-sdk.js +11 -0
- package/server/lib/tts/service.js +7 -5
- package/server/lib/tts/stt.js +3 -2
- package/server/lib/tunnels/DOCUMENTATION.md +2 -0
- package/server/lib/tunnels/routes.js +7 -0
- package/server/lib/tunnels/routes.test.js +55 -0
- package/server/lib/ui-auth/ui-auth.js +38 -2
- package/server/lib/ui-auth/ui-auth.test.js +42 -0
- package/server/lib/ui-auth/ui-passkeys.js +12 -10
- package/server/lib/walkthrough/DOCUMENTATION.md +10 -1
- package/server/lib/walkthrough/pull-request.js +61 -8
- package/server/lib/walkthrough/pull-request.test.js +54 -1
- package/server/lib/walkthrough/routes.js +20 -0
- package/server/lib/walkthrough/routes.test.js +19 -0
- package/server/opencode-proxy.test.js +190 -166
- package/server/opencode-worktree-readiness.test.js +5 -4
- package/dist/assets/DiffView-B2CD9wC8.js +0 -4
- package/dist/assets/FilesView-C4vMFclc.js +0 -13
- package/dist/assets/LinearIssuesView-EzGNCGrd.js +0 -1
- package/dist/assets/MarkdownImageGallery-B2YUDFaB.js +0 -1
- package/dist/assets/MarkdownRendererImpl-B4ikIzR3.js +0 -12
- package/dist/assets/OnboardingScreen-CQHBSTEi.js +0 -1
- package/dist/assets/PlanView-D3x-pTkZ.js +0 -5
- package/dist/assets/PluginPane-DEcgDQ9h.js +0 -3
- package/dist/assets/SettingsView-CSinuH-1.js +0 -29
- package/dist/assets/SettingsWindow-B5CHoJFZ.js +0 -1
- package/dist/assets/SubmoduleDiffSummary-oxeACCWs.js +0 -1
- package/dist/assets/ToolOutputDialog-tLNWmfcW.js +0 -15
- package/dist/assets/ToolPartDiffPreview-CflzmK-x.js +0 -8
- package/dist/assets/appThemeRegistry-DLicw4dH.js +0 -1
- package/dist/assets/de-Bro6_zyl.js +0 -10
- package/dist/assets/es-Bb9XQZvd.js +0 -16
- package/dist/assets/fr-Dgfzn-IL.js +0 -14
- package/dist/assets/index-CtQNmqUL.js +0 -1
- package/dist/assets/index-DqaMWNrC.css +0 -1
- package/dist/assets/index-Dqc6USl3.js +0 -40
- package/dist/assets/ja-BYsakGFh.js +0 -16
- package/dist/assets/ko-BSaQz9aN.js +0 -16
- package/dist/assets/main-B62_pjL5.js +0 -2
- package/dist/assets/main-BEdcOI2P.js +0 -896
- package/dist/assets/miniChat-LT8Rqofr.js +0 -2
- package/dist/assets/mobile-CqarEynh.js +0 -2
- package/dist/assets/pl-OIviPdoj.js +0 -16
- package/dist/assets/pt-BR-Cltg-E6s.js +0 -16
- package/dist/assets/renderElectronMiniChatApp-COyLTtZx.js +0 -2
- package/dist/assets/renderMobileApp-BC1MHg6g.js +0 -25
- package/dist/assets/runtimeEndpointReset-DdrazOVH.js +0 -25
- package/dist/assets/scheduledTasksApi-B2SWMGEU.js +0 -1
- package/dist/assets/tr-CQI-KPre.js +0 -7
- package/dist/assets/uk-CqW7R4DA.js +0 -16
- package/dist/assets/useAppFontEffects-ne84JBVL.js +0 -7333
- package/dist/assets/useRootScrollLock-Cpf1qoGp.js +0 -2
- package/dist/assets/vendor-cron-parser-CUcP7OfX.js +0 -2
- package/dist/assets/vendor-opencode-sdk-BJ73u-bH.js +0 -7
- package/dist/assets/zh-CN-CVZG_o_J.js +0 -16
- package/dist/assets/zh-TW-D5ZxzCZp.js +0 -16
- package/server/lib/opencode/mcp-oauth-callback.test.js +0 -111
- package/server/lib/routing/feature-flag.js +0 -11
- package/server/lib/small-model/call.js +0 -851
- package/server/lib/small-model/call.test.js +0 -1292
- package/server/lib/small-model/catalog.js +0 -13
- package/server/lib/small-model/resolve.js +0 -131
- package/server/lib/small-model/resolve.test.js +0 -197
- package/server/lib/small-model/runtime-providers.js +0 -164
- package/server/lib/small-model/runtime-providers.test.js +0 -118
- package/server/lib/system-prompt/DOCUMENTATION.md +0 -36
- package/server/lib/system-prompt/runtime.js +0 -49
- package/server/lib/system-prompt/runtime.test.js +0 -76
- package/server/lib/walkthrough/reproduce-2607.test.js +0 -148
|
@@ -90,10 +90,18 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
90
90
|
|
|
91
91
|
const rememberSession = (info, directoryHint) => {
|
|
92
92
|
if (!info || typeof info.id !== 'string' || !info.id) return;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
// v2 session updates are partial (a rename carries only the title), so a
|
|
94
|
+
// field the update does not name keeps what an earlier record said.
|
|
95
|
+
const previous = sessions.get(info.id);
|
|
96
|
+
const parentID = typeof info.parentID === 'string' && info.parentID ? info.parentID : previous?.parentID ?? null;
|
|
97
|
+
// v2 keeps the directory on `location`; translated events already flatten it.
|
|
98
|
+
const directory = typeof info.directory === 'string' && info.directory
|
|
99
|
+
? info.directory
|
|
100
|
+
: (typeof info.location?.directory === 'string' && info.location.directory
|
|
101
|
+
? info.location.directory
|
|
102
|
+
: previous?.directory ?? directoryHint);
|
|
103
|
+
if (previous) sessions.delete(info.id);
|
|
104
|
+
sessions.set(info.id, { parentID, directory });
|
|
97
105
|
if (sessions.size > SESSION_CACHE_LIMIT) {
|
|
98
106
|
sessions.delete(sessions.keys().next().value);
|
|
99
107
|
}
|
|
@@ -101,12 +109,14 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
101
109
|
|
|
102
110
|
const request = async (path, { directory, method = 'GET', body } = {}) => {
|
|
103
111
|
const url = new URL(buildOpenCodeUrl(path, ''));
|
|
104
|
-
if (directory) url.searchParams.set('directory', directory);
|
|
105
112
|
const response = await fetchImpl(url, {
|
|
106
113
|
method,
|
|
107
114
|
headers: {
|
|
108
115
|
Accept: 'application/json',
|
|
109
116
|
...(body ? { 'Content-Type': 'application/json' } : {}),
|
|
117
|
+
// OpenCode 2.x scopes a request to a directory through this header;
|
|
118
|
+
// the pending-permission list and services behind it are per location.
|
|
119
|
+
...(directory ? { 'x-opencode-directory': encodeURIComponent(directory) } : {}),
|
|
110
120
|
...getOpenCodeAuthHeaders(),
|
|
111
121
|
},
|
|
112
122
|
...(body ? { body: JSON.stringify(body) } : {}),
|
|
@@ -123,7 +133,7 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
123
133
|
const getSession = async (sessionId, directory) => {
|
|
124
134
|
const cached = sessions.get(sessionId);
|
|
125
135
|
if (cached) return cached;
|
|
126
|
-
const info = await request(`/session/${encodeURIComponent(sessionId)}`, { directory });
|
|
136
|
+
const info = await request(`/api/session/${encodeURIComponent(sessionId)}`, { directory });
|
|
127
137
|
rememberSession(info?.data ?? info, directory);
|
|
128
138
|
return sessions.get(sessionId) ?? null;
|
|
129
139
|
};
|
|
@@ -156,10 +166,12 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
156
166
|
const verdict = await evaluatePermission(permission, directory);
|
|
157
167
|
if (verdict?.action === 'hold') return true;
|
|
158
168
|
}
|
|
159
|
-
|
|
169
|
+
// v2 scopes a permission reply under its session.
|
|
170
|
+
await request(`/api/session/${encodeURIComponent(permission.sessionID)}/permission/${encodeURIComponent(permission.id)}/reply`, {
|
|
160
171
|
directory,
|
|
161
172
|
method: 'POST',
|
|
162
|
-
body
|
|
173
|
+
// OpenCode 2.0.8 renamed the reply body field `reply` to `decision`.
|
|
174
|
+
body: { decision: 'once' },
|
|
163
175
|
});
|
|
164
176
|
return true;
|
|
165
177
|
};
|
|
@@ -198,7 +210,7 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
198
210
|
for (const directory of scopes) {
|
|
199
211
|
let payload;
|
|
200
212
|
try {
|
|
201
|
-
payload = await request('/permission', { directory });
|
|
213
|
+
payload = await request('/api/permission/request', { directory });
|
|
202
214
|
} catch {
|
|
203
215
|
continue;
|
|
204
216
|
}
|
|
@@ -217,20 +229,24 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
217
229
|
}
|
|
218
230
|
|
|
219
231
|
const processEvent = (event) => {
|
|
220
|
-
const raw = event?.payload;
|
|
221
|
-
const payload = raw?.payload && typeof raw.payload === 'object' ? raw.payload : raw;
|
|
222
232
|
const directory = typeof event?.directory === 'string' && event.directory !== 'global' ? event.directory : undefined;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
233
|
+
for (const payload of event?.translated?.() ?? []) {
|
|
234
|
+
if (payload.type === 'session.created' || payload.type === 'session.updated') {
|
|
235
|
+
rememberSession(payload.properties?.info, directory ?? payload.properties?.directory);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
// A v2 permission request is `{ id, sessionID, action, resources, ... }`;
|
|
239
|
+
// only the id and session id are used to reply.
|
|
240
|
+
if (payload.type === 'permission.asked') {
|
|
241
|
+
void processPermission(payload.properties, directory ?? payload.properties?.directory);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
// The routing safety net holds a request instead of replying; once the
|
|
245
|
+
// user answers it, stop tracking it.
|
|
246
|
+
if (payload.type === 'permission.replied') {
|
|
247
|
+
const permissionId = payload.properties?.requestID;
|
|
248
|
+
if (typeof permissionId === 'string' && permissionId) onPermissionReplied?.(permissionId);
|
|
249
|
+
}
|
|
234
250
|
}
|
|
235
251
|
};
|
|
236
252
|
|
|
@@ -23,11 +23,17 @@ const createRuntime = ({ stored, fetchImpl, retryDelaysMs = [0], evaluatePermiss
|
|
|
23
23
|
return {
|
|
24
24
|
runtime,
|
|
25
25
|
getSettings: () => settings,
|
|
26
|
-
|
|
26
|
+
// The hub hands server-side subscribers already-translated events.
|
|
27
|
+
emit: (payload, directory = '/project') => eventHandler({ payload, directory, translated: () => [payload] }),
|
|
27
28
|
connect: () => statusHandler({ type: 'connect' }),
|
|
28
29
|
};
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
const directoryHeader = (init) => {
|
|
33
|
+
const value = init?.headers?.['x-opencode-directory'];
|
|
34
|
+
return typeof value === 'string' ? decodeURIComponent(value) : null;
|
|
35
|
+
};
|
|
36
|
+
|
|
31
37
|
const flush = async () => {
|
|
32
38
|
for (let index = 0; index < 20; index += 1) await Promise.resolve();
|
|
33
39
|
};
|
|
@@ -63,11 +69,24 @@ describe('permission auto-accept runtime', () => {
|
|
|
63
69
|
await expect(runtime.isSessionAutoAccepting('grandchild', '/project')).resolves.toBe(true);
|
|
64
70
|
});
|
|
65
71
|
|
|
72
|
+
it('keeps a subagent\'s lineage when a later partial update names only its title', async () => {
|
|
73
|
+
const fetchImpl = vi.fn(async () => Response.json({}));
|
|
74
|
+
const { runtime, emit } = createRuntime({ fetchImpl });
|
|
75
|
+
await runtime.setSessionPolicy('root', true);
|
|
76
|
+
emit({ type: 'session.created', properties: { info: { id: 'child', parentID: 'root', directory: '/project' } } });
|
|
77
|
+
// v2 renames arrive as partial session records without parentID.
|
|
78
|
+
emit({ type: 'session.updated', properties: { info: { id: 'child', title: 'Subagent' } } });
|
|
79
|
+
emit({ type: 'permission.asked', properties: { id: 'p1', sessionID: 'child', permission: 'bash', metadata: {} } });
|
|
80
|
+
await flush();
|
|
81
|
+
const replies = fetchImpl.mock.calls.map(([url]) => new URL(url).pathname).filter((path) => path.endsWith('/reply'));
|
|
82
|
+
expect(replies).toEqual(['/api/session/child/permission/p1/reply']);
|
|
83
|
+
});
|
|
84
|
+
|
|
66
85
|
it('fetches missing subagent lineage before replying', async () => {
|
|
67
86
|
const fetchImpl = vi.fn(async (url, init = {}) => {
|
|
68
87
|
const path = new URL(url).pathname;
|
|
69
|
-
if (path === '/permission') return new Response('[]');
|
|
70
|
-
if (path === '/session/child') return Response.json({ id: 'child', parentID: 'root', directory: '/project' });
|
|
88
|
+
if (path === '/api/permission/request') return new Response('[]');
|
|
89
|
+
if (path === '/api/session/child') return Response.json({ id: 'child', parentID: 'root', directory: '/project' });
|
|
71
90
|
if (init.method === 'POST') return Response.json({});
|
|
72
91
|
return new Response('', { status: 404 });
|
|
73
92
|
});
|
|
@@ -76,15 +95,16 @@ describe('permission auto-accept runtime', () => {
|
|
|
76
95
|
fetchImpl,
|
|
77
96
|
});
|
|
78
97
|
await expect(runtime.processPermission({ id: 'perm', sessionID: 'child' }, '/project')).resolves.toBe(true);
|
|
79
|
-
|
|
98
|
+
// v2 scopes a permission reply under its session.
|
|
99
|
+
expect(fetchImpl.mock.calls.some(([url, init]) => new URL(url).pathname === '/api/session/child/permission/perm/reply' && init.method === 'POST')).toBe(true);
|
|
80
100
|
});
|
|
81
101
|
|
|
82
102
|
it('retries a transient reply failure and deduplicates concurrent events', async () => {
|
|
83
103
|
let replyAttempts = 0;
|
|
84
104
|
const fetchImpl = vi.fn(async (url, init = {}) => {
|
|
85
105
|
const path = new URL(url).pathname;
|
|
86
|
-
if (path === '/permission') return new Response('[]');
|
|
87
|
-
if (path === '/permission/perm/reply' && init.method === 'POST') {
|
|
106
|
+
if (path === '/api/permission/request') return new Response('[]');
|
|
107
|
+
if (path === '/api/session/root/permission/perm/reply' && init.method === 'POST') {
|
|
88
108
|
replyAttempts += 1;
|
|
89
109
|
return replyAttempts === 1 ? new Response('', { status: 503 }) : Response.json({});
|
|
90
110
|
}
|
|
@@ -105,8 +125,8 @@ describe('permission auto-accept runtime', () => {
|
|
|
105
125
|
it('reconciles pending permissions after reconnect', async () => {
|
|
106
126
|
const fetchImpl = vi.fn(async (url, init = {}) => {
|
|
107
127
|
const path = new URL(url).pathname;
|
|
108
|
-
if (path === '/permission') return Response.json([{ id: 'pending', sessionID: 'root' }]);
|
|
109
|
-
if (path === '/permission/pending/reply' && init.method === 'POST') return Response.json({});
|
|
128
|
+
if (path === '/api/permission/request') return Response.json([{ id: 'pending', sessionID: 'root' }]);
|
|
129
|
+
if (path === '/api/session/root/permission/pending/reply' && init.method === 'POST') return Response.json({});
|
|
110
130
|
return Response.json({ id: 'root' });
|
|
111
131
|
});
|
|
112
132
|
const { connect } = createRuntime({
|
|
@@ -115,23 +135,23 @@ describe('permission auto-accept runtime', () => {
|
|
|
115
135
|
});
|
|
116
136
|
connect();
|
|
117
137
|
await flush();
|
|
118
|
-
expect(fetchImpl.mock.calls.some(([url]) => new URL(url).pathname === '/permission/pending/reply')).toBe(true);
|
|
138
|
+
expect(fetchImpl.mock.calls.some(([url]) => new URL(url).pathname === '/api/session/root/permission/pending/reply')).toBe(true);
|
|
119
139
|
});
|
|
120
140
|
|
|
121
141
|
it('accepts existing pending permissions when a session policy is enabled', async () => {
|
|
122
142
|
const fetchImpl = vi.fn(async (url, init = {}) => {
|
|
123
143
|
const parsed = new URL(url);
|
|
124
144
|
const path = parsed.pathname;
|
|
125
|
-
if (path === '/permission') {
|
|
126
|
-
return
|
|
145
|
+
if (path === '/api/permission/request') {
|
|
146
|
+
return directoryHeader(init) === '/project'
|
|
127
147
|
? Response.json([
|
|
128
148
|
{ id: 'root-pending', sessionID: 'root' },
|
|
129
149
|
{ id: 'other-pending', sessionID: 'other' },
|
|
130
150
|
])
|
|
131
151
|
: Response.json([]);
|
|
132
152
|
}
|
|
133
|
-
if (path === '/permission/root-pending/reply' && init.method === 'POST') return Response.json({});
|
|
134
|
-
if (path === '/session/other') return Response.json({ id: 'other' });
|
|
153
|
+
if (path === '/api/session/root/permission/root-pending/reply' && init.method === 'POST') return Response.json({});
|
|
154
|
+
if (path === '/api/session/other') return Response.json({ id: 'other' });
|
|
135
155
|
return new Response('', { status: 404 });
|
|
136
156
|
});
|
|
137
157
|
const { runtime } = createRuntime({ fetchImpl });
|
|
@@ -141,8 +161,9 @@ describe('permission auto-accept runtime', () => {
|
|
|
141
161
|
const replyPaths = fetchImpl.mock.calls
|
|
142
162
|
.filter(([, init]) => init?.method === 'POST')
|
|
143
163
|
.map(([url]) => new URL(url).pathname);
|
|
144
|
-
expect(replyPaths).toEqual(['/permission/root-pending/reply']);
|
|
145
|
-
|
|
164
|
+
expect(replyPaths).toEqual(['/api/session/root/permission/root-pending/reply']);
|
|
165
|
+
// OpenCode 2.x scopes the pending list by header, not by query.
|
|
166
|
+
expect(fetchImpl.mock.calls.some(([, init]) => directoryHeader(init) === '/project')).toBe(true);
|
|
146
167
|
expect(await runtime.load()).toEqual({ sessions: { root: true }, revision: 1 });
|
|
147
168
|
});
|
|
148
169
|
|
|
@@ -163,8 +184,9 @@ describe('permission auto-accept runtime', () => {
|
|
|
163
184
|
emit({ type: 'permission.asked', properties: { id: 'safe', sessionID: 'root', permission: 'bash', metadata: {} } });
|
|
164
185
|
await flush();
|
|
165
186
|
|
|
166
|
-
const replies = fetchImpl.mock.calls.
|
|
167
|
-
expect(replies).toEqual(['http://opencode.test/permission/safe/reply
|
|
187
|
+
const replies = fetchImpl.mock.calls.filter(([url]) => String(url).includes('/reply'));
|
|
188
|
+
expect(replies.map(([url]) => String(url))).toEqual(['http://opencode.test/api/session/root/permission/safe/reply']);
|
|
189
|
+
expect(directoryHeader(replies[0]?.[1])).toBe('/project');
|
|
168
190
|
expect(evaluatePermission).toHaveBeenCalledTimes(2);
|
|
169
191
|
|
|
170
192
|
emit({ type: 'permission.replied', properties: { sessionID: 'root', requestID: 'held', reply: 'once' } });
|
|
@@ -20,6 +20,10 @@ extension applies the same policy in its own process at activation.
|
|
|
20
20
|
|
|
21
21
|
These provider IDs are currently dispatchable via `fetchQuotaForProvider(providerId)` in `packages/web/server/lib/quota/providers/index.js`.
|
|
22
22
|
|
|
23
|
+
Where this table says "OpenCode `auth.json`", the credential is read through
|
|
24
|
+
`../opencode/auth.js`, which answers from OpenCode 2.x's own credential
|
|
25
|
+
database first and the legacy file second (see the opencode module docs).
|
|
26
|
+
|
|
23
27
|
| Provider ID | Display name | Module | Auth aliases/keys |
|
|
24
28
|
| --- | --- | --- | --- |
|
|
25
29
|
| `claude` | Claude | `providers/claude/` | Claude Code Keychain entry, Claude Code credentials file, OpenCode `auth.json` (`anthropic`, `claude`), `CLAUDE_CODE_OAUTH_TOKEN` |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readAuthFile
|
|
1
|
+
import { readAuthFile } from '../../opencode/auth.js';
|
|
2
2
|
import { buildResult, toUsageWindow } from '../utils/index.js';
|
|
3
3
|
|
|
4
4
|
export const providerId = 'xai';
|
|
@@ -104,9 +104,9 @@ const refreshXaiOauth = async (entry) => {
|
|
|
104
104
|
expires
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
auth.
|
|
109
|
-
|
|
107
|
+
// The refreshed token is kept in memory for this process only: OpenCode
|
|
108
|
+
// 2.x owns `auth.json` (it imports it once and never reads it again), so
|
|
109
|
+
// writing it back would drift from the credential OpenCode actually uses.
|
|
110
110
|
return refreshed;
|
|
111
111
|
})().finally(() => {
|
|
112
112
|
refreshPromise = null;
|
|
@@ -34,7 +34,10 @@ const ALLOWED_WS_PATHS = new Set([
|
|
|
34
34
|
'/api/dictation/ws',
|
|
35
35
|
'/api/dev-tunnel',
|
|
36
36
|
]);
|
|
37
|
-
|
|
37
|
+
// Extension surfaces carry the extension id in the path, so they are matched
|
|
38
|
+
// by shape rather than listed.
|
|
39
|
+
const GUEST_SURFACE_WS_PATH = /^\/api\/guests\/[a-z][a-z0-9-]*\/surface\/ws$/;
|
|
40
|
+
export const isAllowedRelayWebSocketPath = (pathname) => ALLOWED_WS_PATHS.has(pathname) || GUEST_SURFACE_WS_PATH.test(pathname);
|
|
38
41
|
|
|
39
42
|
// Hop-by-hop headers stripped from tunneled requests; `host` is set by fetch
|
|
40
43
|
// to the loopback origin. content-length is dropped too because the body is
|
|
@@ -152,4 +152,11 @@ describe('relay host WebSocket allowlist', () => {
|
|
|
152
152
|
expect(isAllowedRelayWebSocketPath('/api/dev-tunnel/')).toBe(false);
|
|
153
153
|
expect(isAllowedRelayWebSocketPath('/api/database/ws')).toBe(false);
|
|
154
154
|
});
|
|
155
|
+
|
|
156
|
+
test('allows an extension surface socket by path shape only', () => {
|
|
157
|
+
expect(isAllowedRelayWebSocketPath('/api/guests/server-chrome/surface/ws')).toBe(true);
|
|
158
|
+
expect(isAllowedRelayWebSocketPath('/api/guests/Server/surface/ws')).toBe(false);
|
|
159
|
+
expect(isAllowedRelayWebSocketPath('/api/guests/server-chrome/surface/ws/x')).toBe(false);
|
|
160
|
+
expect(isAllowedRelayWebSocketPath('/api/guests/server-chrome/service/request')).toBe(false);
|
|
161
|
+
});
|
|
155
162
|
});
|
|
@@ -9,39 +9,53 @@ it with that category's model, thinking variant and agent. With the safety net
|
|
|
9
9
|
on, the same call decides whether an auto-accepted permission should stay on
|
|
10
10
|
screen for the user instead.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
Routing needs the OpenChamber server, so it is present wherever that server is
|
|
13
|
+
and absent everywhere else: VS Code talks to OpenCode directly and never offers
|
|
14
|
+
Auto. There is no env gate — the feature shipped dark behind
|
|
15
|
+
`OPENCHAMBER_ROUTING_ENABLE` until Jev stopped needing a key of the user's own
|
|
16
|
+
(see "Where a Jev request goes"), which was the reason to hide it.
|
|
15
17
|
|
|
16
18
|
## Files
|
|
17
19
|
|
|
18
|
-
- `
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
- `defaults.js` — the Auto sentinel, both Jev endpoints, built-in categories,
|
|
21
|
+
the question wording for routing and for the safety net, history excerpt
|
|
22
|
+
limits.
|
|
21
23
|
- `store.js` — `routing.json` (only deviations from the built-ins) and
|
|
22
24
|
`routing-auth.json` (the Jev key alone, mode 0600) in the OpenChamber data
|
|
23
25
|
dir. `resolveEffectiveConfig` merges built-ins with stored overrides;
|
|
24
26
|
`toStoredConfig` is its inverse. A missing file is the defaults, a malformed
|
|
25
27
|
one throws.
|
|
26
|
-
- `jev.js` — request builders, answer parsing, the HTTP call with a timeout
|
|
28
|
+
- `jev.js` — request builders, answer parsing, the HTTP call with a timeout,
|
|
29
|
+
and `jevEndpoint`, which picks where one request goes.
|
|
27
30
|
- `history.js` — the last three settled turns through session assist's
|
|
28
31
|
`loadAssistContext` (text parts only, attached quotes included, no files or
|
|
29
32
|
tool payloads), each user message cut to its head and each answer to head
|
|
30
33
|
plus tail. The new request is never cut.
|
|
31
|
-
- `runtime.js` — `createRoutingRuntime`: `describe`, `
|
|
34
|
+
- `runtime.js` — `createRoutingRuntime`: `describe`, `noteModelSelection`,
|
|
35
|
+
`isAutoSession`, `resolveAutoSelection`, `applySessionSelection`, `routeSend`,
|
|
32
36
|
`evaluatePermission`, config and token writes, event broadcasts.
|
|
33
37
|
- `routes.js` — `/api/routing` (GET, PUT), `/api/routing/token` (PUT, DELETE)
|
|
34
38
|
and `registerRoutingPromptRewrite`.
|
|
35
39
|
|
|
36
40
|
## Invariants
|
|
37
41
|
|
|
38
|
-
- The sentinel never reaches OpenCode.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
and
|
|
44
|
-
|
|
42
|
+
- The sentinel never reaches OpenCode. OpenCode 2.x holds the model and agent
|
|
43
|
+
on the session and a prompt body carries only the user's text, so Auto is
|
|
44
|
+
per-session state: `POST /api/session` drops the sentinel from the create
|
|
45
|
+
body (the session starts on OpenCode's default; the first send switches it),
|
|
46
|
+
`POST /api/session/:id/model` with the sentinel is
|
|
47
|
+
swallowed and marks the session (`noteModelSelection`), and every
|
|
48
|
+
`POST /api/session/:id/{prompt,command}` in a marked session is routed
|
|
49
|
+
(`routeSend`) and switches the session onto the answer before the send is
|
|
50
|
+
forwarded. Both sit ahead of the generic proxy, which replays a parsed body.
|
|
51
|
+
The message queue calls `resolveAutoSelection` itself and applies the answer
|
|
52
|
+
with the model/agent switches it already makes. Without a fallback model the
|
|
53
|
+
runtime throws 400 rather than forwarding. The OpenChamber session service
|
|
54
|
+
(`openchamber-sessions/routes.js`) talks to OpenCode through the SDK and can
|
|
55
|
+
pick Auto up from Session Defaults, so it calls `resolveAutoSelection` itself
|
|
56
|
+
before switching the session.
|
|
57
|
+
- The mark lives in process memory. A server restart between the model switch
|
|
58
|
+
and the next send drops it (see the TODO in `runtime.js`).
|
|
45
59
|
- Every failure keeps the user's own behaviour. A Jev error, timeout, unknown
|
|
46
60
|
category or low confidence routes to the fallback model; the decision carries
|
|
47
61
|
the reason. A safety-net failure accepts the permission exactly as auto-accept
|
|
@@ -49,13 +63,29 @@ Code has no OpenChamber server and never offers Auto.
|
|
|
49
63
|
- A category without a model uses the fallback model *and* variant; a variant
|
|
50
64
|
only travels with the model it was chosen for. A category agent replaces the
|
|
51
65
|
composer's agent; an empty one keeps it.
|
|
52
|
-
- Auto is offered (`autoReady`)
|
|
53
|
-
|
|
66
|
+
- Auto is offered (`autoReady`) with `enabled`, a fallback model and at least
|
|
67
|
+
two enabled categories. A key is not among them: see the endpoint rule below.
|
|
54
68
|
- Held permission decisions are cached for 15 minutes per request id so
|
|
55
69
|
reconnect reconciliation in `permission-auto-accept` does not re-ask Jev;
|
|
56
70
|
`permission.replied` forgets them.
|
|
57
|
-
- The rewrite
|
|
58
|
-
|
|
71
|
+
- The send rewrite reads a body only in a session already marked as routed,
|
|
72
|
+
which the URL alone answers; every other send, and anything that is not JSON,
|
|
73
|
+
reaches the proxy as the stream it arrived as.
|
|
74
|
+
|
|
75
|
+
## Where a Jev request goes
|
|
76
|
+
|
|
77
|
+
A saved TypeSafe key wins — the user pasted it and it carries their own quota —
|
|
78
|
+
and the request goes to `api.typesafe.ai/v1/systemone` as `jev-latest`. Without
|
|
79
|
+
a key the same body goes to `opencode.ai/zen/v1/systemone` as `jev-1.13-free`,
|
|
80
|
+
which OpenCode Zen answers with no credential at all, tagged
|
|
81
|
+
`x-opencode-client: openchamber`. Dax approved this use in Slack on 2026-09-22
|
|
82
|
+
on terms the code and the copy keep together: zen can identify and throttle our
|
|
83
|
+
calls through that header, and Settings → Routing tells the user in plain words
|
|
84
|
+
that the free model is a limited-time OpenCode promotion that will later need a
|
|
85
|
+
key. Zen rejects the `jev-latest` alias, so the versioned free id is sent.
|
|
86
|
+
|
|
87
|
+
Never make the free tier the only path: the zen docs call it "available for a
|
|
88
|
+
limited time", and the key field is what users fall back to when it ends.
|
|
59
89
|
|
|
60
90
|
## Events
|
|
61
91
|
|
|
@@ -79,5 +109,6 @@ debounced saves and manages the key.
|
|
|
79
109
|
`store.test.js` (defaults, deviation round-trip, deleted built-ins, malformed
|
|
80
110
|
file, token file mode), `runtime.test.js` (request text, excerpts, decisions,
|
|
81
111
|
rewrite and fallback paths, safety net hold/skip/off), `routes.http.test.js`
|
|
82
|
-
(
|
|
83
|
-
auto-accept tests cover
|
|
112
|
+
(sentinel dropped from a create and swallowed on the model switch, routed send
|
|
113
|
+
ahead of a stand-in proxy, the routes). The queue and auto-accept tests cover
|
|
114
|
+
their hooks.
|
|
@@ -14,16 +14,33 @@ import { z } from 'zod';
|
|
|
14
14
|
const AUTO_PROVIDER_ID = 'openchamber';
|
|
15
15
|
const AUTO_MODEL_ID = 'auto';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
// v2 names a model `{ providerID, id }` (`Model.Ref`); OpenChamber's own stored
|
|
18
|
+
// config and the message queue's send config name it `{ providerID, modelID }`.
|
|
19
|
+
const autoModelSchema = z.object({ providerID: z.literal(AUTO_PROVIDER_ID), id: z.literal(AUTO_MODEL_ID) });
|
|
20
|
+
const autoStoredModelSchema = z.object({ providerID: z.literal(AUTO_PROVIDER_ID), modelID: z.literal(AUTO_MODEL_ID) });
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
export const isAutoModel = (model) => autoModelSchema.safeParse(model).success
|
|
23
|
+
|| autoStoredModelSchema.safeParse(model).success;
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
export const
|
|
25
|
+
/** The sentinel itself, in the v2 `Model.Ref` shape. */
|
|
26
|
+
export const AUTO_MODEL_REF = { providerID: AUTO_PROVIDER_ID, id: AUTO_MODEL_ID };
|
|
27
|
+
|
|
28
|
+
/** The user's own TypeSafe key: their quota, their account, the `jev-latest` alias. */
|
|
29
|
+
export const JEV_API_URL = 'https://api.typesafe.ai/v1/systemone';
|
|
25
30
|
export const JEV_MODEL = 'jev-latest';
|
|
26
31
|
|
|
32
|
+
/**
|
|
33
|
+
* OpenCode Zen serves the same System One endpoint and answers without any
|
|
34
|
+
* credential while `jev-1.13-free` is free. Dax approved OpenChamber using it
|
|
35
|
+
* (Slack, 2026-09-22) on terms this module keeps: the UI tells the user it is a
|
|
36
|
+
* limited-time free model that will later need a Zen key, and every call names
|
|
37
|
+
* OpenChamber so zen can see or throttle us. Zen rejects the `jev-latest`
|
|
38
|
+
* alias, so the versioned free id is sent instead.
|
|
39
|
+
*/
|
|
40
|
+
export const ZEN_JEV_API_URL = 'https://opencode.ai/zen/v1/systemone';
|
|
41
|
+
export const ZEN_JEV_MODEL = 'jev-1.13-free';
|
|
42
|
+
export const ZEN_CLIENT_ID = 'openchamber';
|
|
43
|
+
|
|
27
44
|
/** Per-attempt timeout; the lab measured 250–700 ms warm, ~1 s on a cold TLS handshake. */
|
|
28
45
|
export const JEV_TIMEOUT_MS = 4000;
|
|
29
46
|
|
|
@@ -4,20 +4,30 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
JEV_API_PATH,
|
|
7
|
+
JEV_API_URL,
|
|
9
8
|
JEV_MODEL,
|
|
10
9
|
JEV_TIMEOUT_MS,
|
|
11
10
|
ROUTING_INSTRUCTIONS,
|
|
12
11
|
SAFETY_INSTRUCTIONS,
|
|
13
12
|
SAFETY_KINDS,
|
|
13
|
+
ZEN_CLIENT_ID,
|
|
14
|
+
ZEN_JEV_API_URL,
|
|
15
|
+
ZEN_JEV_MODEL,
|
|
14
16
|
} from './defaults.js';
|
|
15
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Where one request goes. A saved TypeSafe key wins: the user chose it and it
|
|
20
|
+
* carries their own quota. Without one, the same questions go to the free Jev
|
|
21
|
+
* model OpenCode Zen serves without a credential, identified as OpenChamber.
|
|
22
|
+
*/
|
|
23
|
+
export const jevEndpoint = (token) => (token
|
|
24
|
+
? { url: JEV_API_URL, model: JEV_MODEL, headers: { authorization: `Bearer ${token}` }, source: 'typesafe' }
|
|
25
|
+
: { url: ZEN_JEV_API_URL, model: ZEN_JEV_MODEL, headers: { 'x-opencode-client': ZEN_CLIENT_ID }, source: 'zen-free' });
|
|
26
|
+
|
|
16
27
|
export const buildRoutingRequest = ({ categories, history, request }) => {
|
|
17
28
|
const criteria = {};
|
|
18
29
|
for (const category of categories) criteria[category.id] = category.description;
|
|
19
30
|
return {
|
|
20
|
-
model: JEV_MODEL,
|
|
21
31
|
state: { history, request },
|
|
22
32
|
questions: { category: { type: 'choice', instructions: ROUTING_INSTRUCTIONS, criteria } },
|
|
23
33
|
};
|
|
@@ -25,7 +35,6 @@ export const buildRoutingRequest = ({ categories, history, request }) => {
|
|
|
25
35
|
|
|
26
36
|
/** `permission` is what OpenCode reported: the tool kind, its patterns and its metadata. */
|
|
27
37
|
export const buildPermissionRequest = (permission) => ({
|
|
28
|
-
model: JEV_MODEL,
|
|
29
38
|
state: {
|
|
30
39
|
permission: {
|
|
31
40
|
type: permission.permission,
|
|
@@ -75,10 +84,11 @@ export const createJevClient = ({ fetchImpl = fetch, timeoutMs = JEV_TIMEOUT_MS
|
|
|
75
84
|
const timer = setTimeout(() => abort.abort(), timeoutMs);
|
|
76
85
|
const started = Date.now();
|
|
77
86
|
try {
|
|
78
|
-
const
|
|
87
|
+
const endpoint = jevEndpoint(token);
|
|
88
|
+
const response = await fetchImpl(endpoint.url, {
|
|
79
89
|
method: 'POST',
|
|
80
|
-
headers: {
|
|
81
|
-
body: JSON.stringify(request),
|
|
90
|
+
headers: { ...endpoint.headers, 'content-type': 'application/json' },
|
|
91
|
+
body: JSON.stringify({ ...request, model: endpoint.model }),
|
|
82
92
|
signal: abort.signal,
|
|
83
93
|
});
|
|
84
94
|
const text = await response.text();
|