@vantran-se/openchamber-web 1.24.3 → 2.0.1
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-BjJHrzmU.js → CodeMirrorCommentWidgets-ntXIbhV2.js} +1 -1
- package/dist/assets/DiffView-CjxUovbM.js +4 -0
- package/dist/assets/FilesView-DFWD0RQR.js +17 -0
- package/dist/assets/{GitView-CDOJy5Ay.js → GitView-BWa7e5vw.js} +4 -4
- package/dist/assets/{GuestAttachDialog-D3S0Rjic.js → GuestAttachDialog-DdOuyZzp.js} +1 -1
- package/dist/assets/GuestSurfacePane-DDfzid3U.js +1 -0
- package/dist/assets/{JsonTreeView-D0C9nU7K.js → JsonTreeView-3sGwqexh.js} +1 -1
- package/dist/assets/LinearIssuesView-C6dpIdjw.js +1 -0
- package/dist/assets/MarkdownImageGallery-Q7YR_tl4.js +1 -0
- package/dist/assets/MarkdownRendererImpl-DxDV56bL.js +12 -0
- package/dist/assets/OnboardingScreen-rK4Stg__.js +1 -0
- package/dist/assets/{PatchDiff-DnEhjXcC.js → PatchDiff-DS-IMU1j.js} +1 -1
- package/dist/assets/{PierreDiffViewer-DyxZVVVR.js → PierreDiffViewer-DW23KWKZ.js} +1 -1
- package/dist/assets/PlanView-BCk_VobP.js +5 -0
- package/dist/assets/PluginPane-BPP1dbck.js +3 -0
- package/dist/assets/SettingsView-kMmrpn43.js +29 -0
- package/dist/assets/SettingsWindow-BpvD6IYQ.js +1 -0
- package/dist/assets/SubmoduleDiffSummary-BLttSS1X.js +1 -0
- package/dist/assets/ToolOutputDialog-CtqDqbyr.js +15 -0
- package/dist/assets/ToolPartDiffPreview-5FcKdyRy.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-0pyr_mHY.js → VirtualizedFileDiff-C763hNCR.js} +4 -4
- package/dist/assets/{WalkthroughView-yNj4nOP5.js → WalkthroughView-lap5btCF.js} +2 -2
- package/dist/assets/appThemeRegistry-u4zw1l0B.js +1 -0
- package/dist/assets/{commitAndPush-DvJKa3Ks.js → commitAndPush-CwGJfJG-.js} +1 -1
- package/dist/assets/{computeVirtualFileMetrics-DxTBMuu-.js → computeVirtualFileMetrics-DW5jjX4n.js} +1 -1
- package/dist/assets/de-CndEg67T.js +10 -0
- package/dist/assets/es-DglvPlaC.js +16 -0
- package/dist/assets/fr-DejPCpbS.js +14 -0
- package/dist/assets/index-C2r64QfD.js +1 -0
- package/dist/assets/index-CLcM8HKx.css +1 -0
- package/dist/assets/index-CvLgnuNI.js +41 -0
- package/dist/assets/{isDiffPlainText-D7FkBMP6.js → isDiffPlainText-D47a6hMP.js} +1 -1
- package/dist/assets/{isFilePlainText-CL2vsrIZ.js → isFilePlainText-B-mGgeKl.js} +1 -1
- package/dist/assets/ja-CVpQawCd.js +16 -0
- package/dist/assets/ko-Bw446h9j.js +16 -0
- package/dist/assets/main-Bz4WWXbr.js +2 -0
- package/dist/assets/main-oreqDsOV.js +896 -0
- package/dist/assets/{markdownCore-Dh7WEsdM.js → markdownCore-D2e5sp9y.js} +4 -4
- package/dist/assets/miniChat-D1v2gsBI.js +2 -0
- package/dist/assets/mobile-DBj3lCf2.js +2 -0
- package/dist/assets/{patchFileDiff-Bsba8KFp.js → patchFileDiff-BpeUOV91.js} +3 -3
- package/dist/assets/pl-BYNlPdhm.js +16 -0
- package/dist/assets/pt-BR-CL-Yfv9H.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-DVtfEB2z.js +2 -0
- package/dist/assets/renderMobileApp-DFp9-xbG.js +25 -0
- package/dist/assets/runtimeEndpointReset-CsIpUep1.js +27 -0
- package/dist/assets/scheduledTasksApi-B5OHVpPM.js +1 -0
- package/dist/assets/{shikiHighlight-8_3csmr6.js → shikiHighlight-DNm2Z_Jc.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-CHo2fCgW.js → textMateThemeFromAppTheme-DGF2kG7g.js} +1 -1
- package/dist/assets/tr-DzmbxLy-.js +7 -0
- package/dist/assets/uk-C7vQnwro.js +16 -0
- package/dist/assets/useAppFontEffects-Ci3FEaPf.js +7337 -0
- package/dist/assets/{useInlineCommentController-DkQ_gqpn.js → useInlineCommentController-CuUuakrt.js} +2 -2
- package/dist/assets/useRootScrollLock-CQIsyERq.js +2 -0
- package/dist/assets/{vendor-codemirror-language-data-CdzUmUyZ.js → vendor-codemirror-language-data-CQa7_1cr.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-B8IcVQAA.js → web-components-B6tKQyJN.js} +2 -2
- package/dist/assets/zh-CN-Cl0srS-z.js +16 -0
- package/dist/assets/zh-TW-DtII5bDb.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-COVQhWGd.js +0 -4
- package/dist/assets/FilesView-B1V5JxFX.js +0 -13
- package/dist/assets/LinearIssuesView-D7FKg1XJ.js +0 -1
- package/dist/assets/MarkdownImageGallery-BrFjAVEf.js +0 -1
- package/dist/assets/MarkdownRendererImpl-DWepTafh.js +0 -12
- package/dist/assets/OnboardingScreen-Dyxxiqdt.js +0 -1
- package/dist/assets/PlanView-LW2z_EAP.js +0 -5
- package/dist/assets/PluginPane-B4tKno2h.js +0 -3
- package/dist/assets/SettingsView-DEaMulN9.js +0 -29
- package/dist/assets/SettingsWindow-FLDJXOgQ.js +0 -1
- package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +0 -1
- package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +0 -15
- package/dist/assets/ToolPartDiffPreview-CkR4msKP.js +0 -8
- package/dist/assets/appThemeRegistry-BuUKhbYQ.js +0 -1
- package/dist/assets/de-DgxgFyGG.js +0 -10
- package/dist/assets/es-BFsZj5F_.js +0 -16
- package/dist/assets/fr-jdZ5KKQi.js +0 -14
- package/dist/assets/index-CKHjPKbq.js +0 -1
- package/dist/assets/index-Cbci4i1W.js +0 -40
- package/dist/assets/index-DqaMWNrC.css +0 -1
- package/dist/assets/ja-C5kKrhO4.js +0 -16
- package/dist/assets/ko-DXDTT_0O.js +0 -16
- package/dist/assets/main-CZALtVek.js +0 -896
- package/dist/assets/main-CgXMBNvG.js +0 -2
- package/dist/assets/miniChat-CwTmDixi.js +0 -2
- package/dist/assets/mobile-CLf2vCtO.js +0 -2
- package/dist/assets/pl-D-7x4Y8Q.js +0 -16
- package/dist/assets/pt-BR-CSBZrHa8.js +0 -16
- package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +0 -2
- package/dist/assets/renderMobileApp-V4hYd32H.js +0 -25
- package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +0 -25
- package/dist/assets/scheduledTasksApi-DzjbLd8P.js +0 -1
- package/dist/assets/tr-Dr-oJ22L.js +0 -7
- package/dist/assets/uk-9Nx83UcI.js +0 -16
- package/dist/assets/useAppFontEffects-khZXpFL_.js +0 -7333
- package/dist/assets/useRootScrollLock-DdvHf9Pt.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-Bxy_Hj3r.js +0 -16
- package/dist/assets/zh-TW-0BB4gpHM.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
|
@@ -1,203 +1,175 @@
|
|
|
1
|
-
import { createServer } from 'node:http';
|
|
2
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
2
|
import { createSessionAssistRuntime } from './runtime.js';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const chunks = [];
|
|
26
|
-
for await (const chunk of request) chunks.push(chunk);
|
|
27
|
-
state.patches.push(JSON.parse(Buffer.concat(chunks).toString()));
|
|
28
|
-
response.end(JSON.stringify(state.session));
|
|
29
|
-
} else if (url.pathname.endsWith('/message')) {
|
|
30
|
-
response.end(JSON.stringify(url.searchParams.get('limit') === '1' ? state.messages.slice(-1) : state.messages));
|
|
31
|
-
} else {
|
|
32
|
-
state.gets++;
|
|
33
|
-
response.statusCode = state.failFresh && state.gets > 1 ? 500 : 200;
|
|
34
|
-
response.end(JSON.stringify(state.session));
|
|
35
|
-
}
|
|
4
|
+
/**
|
|
5
|
+
* The recap and the suggestion live in OpenChamber's own session metadata
|
|
6
|
+
* store, because OpenCode 2.x accepts session metadata only at create time.
|
|
7
|
+
* `persistSessionAssist` is the seam. What is pinned here is the wiring: no
|
|
8
|
+
* store means no work and no cost, and injecting one turns generation back on.
|
|
9
|
+
*
|
|
10
|
+
* The previous suite drove the whole generation through a fake v1 OpenCode
|
|
11
|
+
* server. It is gone rather than rewritten: every shape it asserted on
|
|
12
|
+
* (`message.parts`, `info.parentID`, `info.summary`, `PATCH /session/{id}`)
|
|
13
|
+
* belongs to v1, so keeping it green would prove nothing about v2. The reader
|
|
14
|
+
* itself is covered by `context.test.js`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const runtimes = [];
|
|
18
|
+
|
|
19
|
+
const makeRuntime = (overrides = {}) => {
|
|
20
|
+
const buildOpenCodeUrl = vi.fn(() => 'http://127.0.0.1:1/');
|
|
21
|
+
const getOpenCodeAuthHeaders = vi.fn(() => ({}));
|
|
22
|
+
const getSmallModelService = vi.fn(async () => {
|
|
23
|
+
throw new Error('the small model must not be consulted while assist is parked');
|
|
36
24
|
});
|
|
37
|
-
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
38
|
-
const base = `http://127.0.0.1:${server.address().port}`;
|
|
39
|
-
state.base = base;
|
|
40
25
|
const runtime = createSessionAssistRuntime({
|
|
41
|
-
buildOpenCodeUrl
|
|
42
|
-
getOpenCodeAuthHeaders
|
|
43
|
-
|
|
26
|
+
buildOpenCodeUrl,
|
|
27
|
+
getOpenCodeAuthHeaders,
|
|
28
|
+
getSmallModelService,
|
|
29
|
+
getTargets: () => ({ recap: true, suggestion: true }),
|
|
44
30
|
quietMs: 1,
|
|
45
|
-
|
|
46
|
-
describeSmallModel: async () => ({ inputCharBudget: 64_000 }),
|
|
47
|
-
generateSmallModelText: async (args) => { state.calls.push(args); return generate(args, state); },
|
|
48
|
-
}),
|
|
31
|
+
...overrides,
|
|
49
32
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
33
|
+
runtimes.push(runtime);
|
|
34
|
+
return { runtime, buildOpenCodeUrl, getOpenCodeAuthHeaders, getSmallModelService };
|
|
35
|
+
};
|
|
54
36
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
37
|
+
const idle = (sessionId = 'ses_1') => ({
|
|
38
|
+
type: 'session.status',
|
|
39
|
+
properties: { sessionID: sessionId, status: { type: 'idle' } },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
while (runtimes.length > 0) runtimes.pop().stop();
|
|
61
44
|
vi.restoreAllMocks();
|
|
62
45
|
});
|
|
63
46
|
|
|
64
47
|
describe('session assist runtime', () => {
|
|
65
|
-
it('
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
expect(
|
|
73
|
-
expect(
|
|
74
|
-
expect(state.calls[0]).toMatchObject({ restrictToPreferredProvider: true, onOverflow: 'error', preferredProviderID: 'test-provider', preferredModelID: 'test-model', sessionID: 'session' });
|
|
75
|
-
expect(state.patches[0].metadata).toMatchObject({ external: 'keep', openchamber: {
|
|
76
|
-
note: 'keep', concurrent: 'new', assist: { recap: 'Зміни готові', suggestion: '', forMessageID: 'answer' },
|
|
77
|
-
} });
|
|
48
|
+
it('does no work and reaches no service while no assist store is injected', async () => {
|
|
49
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
50
|
+
const { runtime, buildOpenCodeUrl, getSmallModelService } = makeRuntime();
|
|
51
|
+
|
|
52
|
+
runtime.processPayload(idle());
|
|
53
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
54
|
+
|
|
55
|
+
expect(buildOpenCodeUrl).not.toHaveBeenCalled();
|
|
56
|
+
expect(getSmallModelService).not.toHaveBeenCalled();
|
|
78
57
|
});
|
|
79
58
|
|
|
80
|
-
it('
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
delete state.session.parentID;
|
|
91
|
-
state.session.revert = { messageID: 'user' };
|
|
92
|
-
status('idle');
|
|
93
|
-
await vi.waitFor(() => expect(state.gets).toBe(2));
|
|
94
|
-
delete state.session.revert;
|
|
95
|
-
state.session.time.archived = 1;
|
|
96
|
-
status('idle');
|
|
97
|
-
await vi.waitFor(() => expect(state.gets).toBe(3));
|
|
98
|
-
expect(state.calls).toHaveLength(0);
|
|
59
|
+
it('explains itself once, not on every idle session', async () => {
|
|
60
|
+
const log = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
61
|
+
const { runtime } = makeRuntime();
|
|
62
|
+
|
|
63
|
+
runtime.processPayload(idle('ses_1'));
|
|
64
|
+
runtime.processPayload(idle('ses_2'));
|
|
65
|
+
runtime.processPayload(idle('ses_3'));
|
|
66
|
+
|
|
67
|
+
const notices = log.mock.calls.filter(([line]) => String(line).includes('[session-assist] parked'));
|
|
68
|
+
expect(notices).toHaveLength(1);
|
|
99
69
|
});
|
|
100
70
|
|
|
101
|
-
it('
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
expect(
|
|
109
|
-
state.targets = { recap: false, suggestion: true };
|
|
110
|
-
status('idle');
|
|
111
|
-
await vi.waitFor(() => expect(state.calls).toHaveLength(2));
|
|
112
|
-
await pause();
|
|
113
|
-
expect(state.patches).toHaveLength(1);
|
|
71
|
+
it('ignores everything after stop', async () => {
|
|
72
|
+
const log = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
73
|
+
const { runtime } = makeRuntime();
|
|
74
|
+
|
|
75
|
+
runtime.stop();
|
|
76
|
+
runtime.processPayload(idle());
|
|
77
|
+
|
|
78
|
+
expect(log).not.toHaveBeenCalled();
|
|
114
79
|
});
|
|
115
80
|
|
|
116
|
-
it('
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
81
|
+
it('leaves an archived session alone: no context is loaded and no model is called', async () => {
|
|
82
|
+
const persistSessionAssist = vi.fn(async () => undefined);
|
|
83
|
+
const getSmallModelService = vi.fn(async () => {
|
|
84
|
+
throw new Error('the small model must not be consulted for an archived session');
|
|
85
|
+
});
|
|
86
|
+
const fetchMock = vi.fn(async (input) => {
|
|
87
|
+
const url = new URL(String(input));
|
|
88
|
+
const body = url.pathname === '/api/session/ses_1'
|
|
89
|
+
? { location: { directory: '/repo' }, data: { id: 'ses_1', location: { directory: '/repo' } } }
|
|
90
|
+
: { location: { directory: '/repo' }, data: { data: [], cursor: null } };
|
|
91
|
+
return new Response(JSON.stringify(body), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
92
|
+
});
|
|
93
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
94
|
+
const { runtime } = makeRuntime({
|
|
95
|
+
persistSessionAssist,
|
|
96
|
+
getSmallModelService,
|
|
97
|
+
buildOpenCodeUrl: (fetchPath) => `http://opencode.test${fetchPath}`,
|
|
98
|
+
isSessionArchived: async (sessionId) => sessionId === 'ses_1',
|
|
120
99
|
});
|
|
121
|
-
status('idle');
|
|
122
|
-
await vi.waitFor(() => expect(state.requests.some((r) => r.limit === '1')).toBe(true));
|
|
123
|
-
await pause();
|
|
124
|
-
expect(state.patches).toHaveLength(0);
|
|
125
|
-
});
|
|
126
100
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const { state, status } = await fixture();
|
|
130
|
-
state.failFresh = true;
|
|
131
|
-
status('idle');
|
|
132
|
-
await vi.waitFor(() => expect(warning).toHaveBeenCalled());
|
|
133
|
-
expect(state.gets).toBe(2);
|
|
134
|
-
expect(state.patches).toHaveLength(0);
|
|
135
|
-
});
|
|
101
|
+
runtime.processPayload(idle());
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
136
103
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
expect(state.calls[0].signal.aborted).toBe(true);
|
|
144
|
-
state.messages.push(message('next-user', 'user', 'Далі'), message('next-answer', 'assistant', 'Готово', { parentID: 'next-user' }));
|
|
145
|
-
status('idle');
|
|
146
|
-
await pause();
|
|
147
|
-
releases[0](output('Старий результат'));
|
|
148
|
-
await vi.waitFor(() => expect(releases).toHaveLength(2));
|
|
149
|
-
expect(state.patches).toHaveLength(0);
|
|
150
|
-
releases[1](output('Новий результат'));
|
|
151
|
-
await vi.waitFor(() => expect(state.patches).toHaveLength(1));
|
|
152
|
-
expect(state.patches[0].metadata.openchamber.assist).toMatchObject({ recap: 'Новий результат', forMessageID: 'next-answer' });
|
|
104
|
+
// The session record was read (that is where the parent/revert checks
|
|
105
|
+
// live), then the archive check stopped everything else.
|
|
106
|
+
expect(fetchMock.mock.calls.map(([input]) => new URL(String(input)).pathname)).toEqual(['/api/session/ses_1']);
|
|
107
|
+
expect(getSmallModelService).not.toHaveBeenCalled();
|
|
108
|
+
expect(persistSessionAssist).not.toHaveBeenCalled();
|
|
109
|
+
vi.unstubAllGlobals();
|
|
153
110
|
});
|
|
154
111
|
|
|
155
|
-
it('
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
112
|
+
it('generates and saves an assist for a turn that v2 closed with an idle marker', async () => {
|
|
113
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
114
|
+
const persistSessionAssist = vi.fn(async () => undefined);
|
|
115
|
+
const listLimits = [];
|
|
116
|
+
// Newest first, the way OpenCode serves it: the idle marker sits above
|
|
117
|
+
// the answer, and a model switch the user made afterwards above that.
|
|
118
|
+
const records = [
|
|
119
|
+
{ id: 'msg_switch', type: 'model-switched', model: { providerID: 'p', id: 'm' } },
|
|
120
|
+
{ id: 'msg_idle', type: 'idle', outcome: 'succeeded' },
|
|
121
|
+
{ id: 'msg_a', type: 'assistant', content: [{ type: 'text', text: 'All done.' }], finish: 'stop', time: { completed: 2 }, model: { providerID: 'p', id: 'm' } },
|
|
122
|
+
{ id: 'msg_u', type: 'user', text: 'Do the thing' },
|
|
123
|
+
];
|
|
124
|
+
const fetchMock = vi.fn(async (input) => {
|
|
125
|
+
const url = new URL(String(input));
|
|
126
|
+
let body;
|
|
127
|
+
// `session.get` answers `{ data }`, which the client unwraps; the message
|
|
128
|
+
// page is `{ data, cursor }` and reaches the reader as is.
|
|
129
|
+
if (url.pathname === '/api/session/ses_1') body = { data: { id: 'ses_1', location: { directory: '/repo' } } };
|
|
130
|
+
else if (url.pathname === '/api/session/ses_1/message') {
|
|
131
|
+
listLimits.push(Number(url.searchParams.get('limit')));
|
|
132
|
+
body = { data: records.slice(0, Number(url.searchParams.get('limit'))), cursor: {} };
|
|
133
|
+
} else throw new Error(`unexpected ${url.pathname}`);
|
|
134
|
+
return new Response(JSON.stringify(body), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
168
135
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
136
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
137
|
+
const { runtime } = makeRuntime({
|
|
138
|
+
persistSessionAssist,
|
|
139
|
+
buildOpenCodeUrl: (fetchPath) => `http://opencode.test${fetchPath}`,
|
|
140
|
+
isSessionArchived: async () => false,
|
|
141
|
+
getSmallModelService: async () => ({
|
|
142
|
+
describeSmallModel: async () => ({ inputCharBudget: 20_000 }),
|
|
143
|
+
generateSmallModelText: async () => ({ text: '{"recap":"Did the thing.","suggestion":"Verify it."}' }),
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
runtime.processPayload(idle());
|
|
148
|
+
for (let i = 0; i < 20 && persistSessionAssist.mock.calls.length === 0; i += 1) {
|
|
149
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
150
|
+
}
|
|
174
151
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const userUpdate = (created) => runtime.processPayload({ type: 'message.updated', properties: {
|
|
181
|
-
info: { id: 'user', sessionID: 'session', role: 'user', time: { created } },
|
|
182
|
-
} });
|
|
183
|
-
userUpdate(1);
|
|
184
|
-
expect(state.calls[0].signal.aborted).toBe(false);
|
|
185
|
-
userUpdate(Date.now());
|
|
186
|
-
expect(state.calls[0].signal.aborted).toBe(true);
|
|
187
|
-
release(output());
|
|
188
|
-
await pause();
|
|
189
|
-
expect(state.patches).toHaveLength(0);
|
|
152
|
+
expect(persistSessionAssist).toHaveBeenCalledTimes(1);
|
|
153
|
+
expect(persistSessionAssist.mock.calls[0][2]).toMatchObject({ recap: 'Did the thing.', suggestion: 'Verify it.', forMessageID: 'msg_a' });
|
|
154
|
+
// The pre-write re-check must see past the idle marker as well.
|
|
155
|
+
expect(listLimits.at(-1)).toBeGreaterThan(2);
|
|
156
|
+
vi.unstubAllGlobals();
|
|
190
157
|
});
|
|
191
158
|
|
|
192
|
-
it('
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
return output();
|
|
159
|
+
it('arms generation again as soon as a store is injected', async () => {
|
|
160
|
+
const persistSessionAssist = vi.fn(async () => undefined);
|
|
161
|
+
const getSmallModelService = vi.fn(async () => {
|
|
162
|
+
throw new Error('stop here: the transport is what this test observes');
|
|
197
163
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
164
|
+
const buildOpenCodeUrl = vi.fn(() => 'http://127.0.0.1:1/');
|
|
165
|
+
const { runtime } = makeRuntime({ persistSessionAssist, getSmallModelService, buildOpenCodeUrl });
|
|
166
|
+
|
|
167
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
168
|
+
runtime.processPayload(idle());
|
|
169
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
170
|
+
|
|
171
|
+
// The idle timer fired and the generation path ran, which is what the gate
|
|
172
|
+
// above suppresses.
|
|
173
|
+
expect(buildOpenCodeUrl).toHaveBeenCalled();
|
|
202
174
|
});
|
|
203
175
|
});
|
|
@@ -81,13 +81,19 @@ before touching the filesystem). Rationale: metadata rides every
|
|
|
81
81
|
a goal on an idle session emits no status transition.
|
|
82
82
|
3. On fire (`tick`), gated by the `sessionGoalEnabled` setting:
|
|
83
83
|
- fetch session (skip sub-agent sessions), require an `active` goal;
|
|
84
|
-
- authoritative live-activity check after the quiet window: re-read
|
|
85
|
-
session
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
- authoritative live-activity check after the quiet window: re-read
|
|
85
|
+
`/api/session/active`, bail if the parent resumed; then list the
|
|
86
|
+
parent's subagent sessions through `GET /api/session?parentID=` (cursor
|
|
87
|
+
paged) and bail while any of them is active. A status or children fetch
|
|
88
|
+
failure is unknown, not empty, so it skips the audit and retries after
|
|
89
|
+
another quiet window;
|
|
90
|
+
both reads live in `../opencode/session-activity.js`, shared with the
|
|
91
|
+
notification runtime;
|
|
92
|
+
- messages come from `/api/session/:id/message` as v2's flat records
|
|
93
|
+
(`type`, `content[]`, `model`, `finish`, `tokens`); `toLoopMessage`
|
|
94
|
+
projects them into the `{ info, parts }` view the rest of the tick reads,
|
|
95
|
+
and a completed `compaction` record plays v1's `summary: true` assistant
|
|
96
|
+
turn. Other plumbing roles are dropped from the view;
|
|
91
97
|
- quiescence check via the message tail (trailing user message or
|
|
92
98
|
unfinished assistant reply → bail; the next idle transition re-arms);
|
|
93
99
|
- token accounting as a SNAPSHOT of the latest completed assistant turn:
|
|
@@ -143,9 +149,10 @@ before touching the filesystem). Rationale: metadata rides every
|
|
|
143
149
|
loop blind to the turn cap;
|
|
144
150
|
- continue: persist accounting + `turnsUsed` first (a crash after the
|
|
145
151
|
write just waits for the next idle tick; the reverse could double-send),
|
|
146
|
-
re-check the tail, then `POST /session/:id/
|
|
147
|
-
continuation prompt
|
|
148
|
-
|
|
152
|
+
re-check the tail, then `POST /api/session/:id/prompt` with the
|
|
153
|
+
continuation prompt. v2 keeps the model and agent on the session, so the
|
|
154
|
+
prompt runs on what the session already uses; nothing is re-selected —
|
|
155
|
+
the goal spends the session's own subscription.
|
|
149
156
|
4. Settling (`complete`/`blocked`/`budgetLimited`) fires the injected
|
|
150
157
|
`emitGoalNotification` so the user hears about it even with the UI closed:
|
|
151
158
|
desktop + UI broadcast + the standard push fanout (web-push with full
|
|
@@ -190,10 +197,11 @@ sees only that final turn, so the report is its evidence.
|
|
|
190
197
|
|
|
191
198
|
Scheduled tasks can run as goals: `execution.goalEnabled` (+ optional
|
|
192
199
|
`execution.goalTokenBudget`) on a task makes the scheduled-tasks runtime
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
200
|
+
write the goal into OpenChamber's session metadata store through the
|
|
201
|
+
`persistSessionGoal` seam `server/index.js` hands it (objective = the expanded
|
|
202
|
+
task prompt; v2 `CommandInfo` carries no template, so a slash command's
|
|
203
|
+
objective is its raw invocation) and attach the goal-mode intro part to normal
|
|
204
|
+
prompts. The store write is what arms the loop (`notifyGoalChanged`).
|
|
197
205
|
|
|
198
206
|
## CLI-created goals
|
|
199
207
|
|
|
@@ -220,7 +228,7 @@ ordering used by create and scheduled goals.
|
|
|
220
228
|
- Web-server feature: VS Code (extension-only) renders goal state via
|
|
221
229
|
`session.updated` but does not run the loop.
|
|
222
230
|
- A goal on a session with no assistant reply yet starts after the first
|
|
223
|
-
user exchange completes (no
|
|
231
|
+
user exchange completes (there is no reply to audit before that).
|
|
224
232
|
- `tokensUsed` only counts completed assistant messages seen within the
|
|
225
233
|
40-message fetch window per tick; extremely long busy stretches between
|
|
226
234
|
idles undercount (acceptable: budget is a guardrail, not billing).
|
|
@@ -56,6 +56,7 @@ export const createSessionGoal = async ({
|
|
|
56
56
|
providerID,
|
|
57
57
|
modelID,
|
|
58
58
|
onWarning,
|
|
59
|
+
persistSessionGoal,
|
|
59
60
|
}) => {
|
|
60
61
|
const warn = (message, error) => {
|
|
61
62
|
if (typeof onWarning === 'function') {
|
|
@@ -98,17 +99,13 @@ export const createSessionGoal = async ({
|
|
|
98
99
|
createdAt: now,
|
|
99
100
|
updatedAt: now,
|
|
100
101
|
};
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
},
|
|
110
|
-
body: JSON.stringify({ metadata: { openchamber: { goal } } }),
|
|
111
|
-
});
|
|
112
|
-
if (!response.ok) throw new Error(`goal metadata patch failed (${response.status})`);
|
|
102
|
+
// OpenCode 2.x accepts session metadata only at create time, so the goal
|
|
103
|
+
// record lives in OpenChamber's own store next to the objective text.
|
|
104
|
+
void baseUrl;
|
|
105
|
+
void authHeaders;
|
|
106
|
+
if (typeof persistSessionGoal !== 'function') {
|
|
107
|
+
throw new Error('goal mode needs a session metadata store to save the goal in');
|
|
108
|
+
}
|
|
109
|
+
await persistSessionGoal(sessionID, directory, goal);
|
|
113
110
|
return goal;
|
|
114
111
|
};
|
|
@@ -20,8 +20,9 @@ describe('session goal creation', () => {
|
|
|
20
20
|
generateSmallModelTextMock.mockReset().mockResolvedValue({ text: '' });
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it('
|
|
24
|
-
const fetchMock = vi.fn(
|
|
23
|
+
it('saves the goal in OpenChamber\'s own store, never through OpenCode', async () => {
|
|
24
|
+
const fetchMock = vi.fn();
|
|
25
|
+
const persistSessionGoal = vi.fn(async () => undefined);
|
|
25
26
|
const originalFetch = globalThis.fetch;
|
|
26
27
|
globalThis.fetch = fetchMock;
|
|
27
28
|
try {
|
|
@@ -34,43 +35,41 @@ describe('session goal creation', () => {
|
|
|
34
35
|
tokenBudget: 200_000,
|
|
35
36
|
providerID: 'openai',
|
|
36
37
|
modelID: 'gpt-5.5',
|
|
38
|
+
persistSessionGoal,
|
|
37
39
|
});
|
|
38
40
|
|
|
39
41
|
expect(writeObjectiveMock).toHaveBeenCalledWith('ses_123', 'Finish and verify the migration');
|
|
40
|
-
expect(writeObjectiveMock.mock.invocationCallOrder[0]).toBeLessThan(fetchMock.mock.invocationCallOrder[0]);
|
|
41
42
|
expect(goal).toMatchObject({ objective: '', objectiveFile: true, status: 'active', tokenBudget: 200_000 });
|
|
42
|
-
expect(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
);
|
|
43
|
+
expect(persistSessionGoal).toHaveBeenCalledWith('ses_123', '/repo/app', goal);
|
|
44
|
+
// v2 has no session-metadata route; nothing may be sent to OpenCode.
|
|
45
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
46
46
|
} finally {
|
|
47
47
|
globalThis.fetch = originalFetch;
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
it('falls back to inline
|
|
51
|
+
it('falls back to an inline objective when the objective file cannot be written', async () => {
|
|
52
52
|
writeObjectiveMock.mockRejectedValueOnce(new Error('disk unavailable'));
|
|
53
|
-
const
|
|
54
|
-
const originalFetch = globalThis.fetch;
|
|
55
|
-
globalThis.fetch = fetchMock;
|
|
56
|
-
try {
|
|
57
|
-
await createSessionGoal({
|
|
58
|
-
baseUrl: 'http://opencode.test',
|
|
59
|
-
authHeaders: {},
|
|
60
|
-
sessionID: 'ses_123',
|
|
61
|
-
directory: '/repo/app',
|
|
62
|
-
objective: 'Finish the migration',
|
|
63
|
-
onWarning: vi.fn(),
|
|
64
|
-
});
|
|
53
|
+
const persistSessionGoal = vi.fn(async () => undefined);
|
|
65
54
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
55
|
+
await createSessionGoal({
|
|
56
|
+
sessionID: 'ses_123',
|
|
57
|
+
directory: '/repo/app',
|
|
58
|
+
objective: 'Finish the migration',
|
|
59
|
+
onWarning: vi.fn(),
|
|
60
|
+
persistSessionGoal,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const [, , goal] = persistSessionGoal.mock.calls[0];
|
|
64
|
+
expect(goal).toMatchObject({ objective: 'Finish the migration', objectiveFile: false });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('refuses to report a goal it has nowhere to save', async () => {
|
|
68
|
+
await expect(createSessionGoal({
|
|
69
|
+
sessionID: 'ses_123',
|
|
70
|
+
directory: '/repo/app',
|
|
71
|
+
objective: 'Finish the migration',
|
|
72
|
+
})).rejects.toThrow(/needs a session metadata store/);
|
|
74
73
|
});
|
|
75
74
|
|
|
76
75
|
it('builds the same goal intro with an optional budget', () => {
|