@vantran-se/openchamber-web 1.24.4 → 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-B4a0JkUz.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-Byu2DY7s.js → GitView-BWa7e5vw.js} +4 -4
- package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-DdOuyZzp.js} +1 -1
- package/dist/assets/GuestSurfacePane-DDfzid3U.js +1 -0
- package/dist/assets/{JsonTreeView-VW_7czo3.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-DCjGhlYV.js → PatchDiff-DS-IMU1j.js} +1 -1
- package/dist/assets/{PierreDiffViewer-CEDZNkU2.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-NU0FHFhT.js → VirtualizedFileDiff-C763hNCR.js} +4 -4
- package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-lap5btCF.js} +2 -2
- package/dist/assets/appThemeRegistry-u4zw1l0B.js +1 -0
- package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-CwGJfJG-.js} +1 -1
- package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.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-CO4e5J_S.js → isDiffPlainText-D47a6hMP.js} +1 -1
- package/dist/assets/{isFilePlainText-ue6kCou7.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-CK48LG0z.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-D7QVlovD.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-BPu3JiQs.js → shikiHighlight-DNm2Z_Jc.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.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-CgS0fnw_.js → useInlineCommentController-CuUuakrt.js} +2 -2
- package/dist/assets/useRootScrollLock-CQIsyERq.js +2 -0
- package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.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-DxX8bZ1o.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-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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OpenCode } from '@opencode/client';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
3
|
import { CronExpressionParser } from 'cron-parser';
|
|
4
4
|
import { expandSnippets } from '../opencode/snippets.js';
|
|
@@ -255,12 +255,27 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
255
255
|
emitTaskRunEvent,
|
|
256
256
|
setSessionAutoAccept,
|
|
257
257
|
sessionKnowledgeRuntime = null,
|
|
258
|
+
// The goal record lives in OpenChamber's metadata store (OpenCode 2.x takes
|
|
259
|
+
// session metadata only at create time); without it goal mode cannot run.
|
|
260
|
+
persistSessionGoal = null,
|
|
258
261
|
logger = console,
|
|
259
262
|
maxGlobalConcurrency = DEFAULT_GLOBAL_CONCURRENCY,
|
|
260
263
|
maxProjectConcurrency = DEFAULT_PROJECT_CONCURRENCY,
|
|
261
264
|
maxRunDurationMs = DEFAULT_MAX_RUN_MS,
|
|
262
265
|
} = deps;
|
|
263
266
|
|
|
267
|
+
// Every OpenCode route lives under /api in v2 and the client appends it, so
|
|
268
|
+
// the client only wants the origin. Directory scoping is a request header.
|
|
269
|
+
const openCodeOrigin = () => new URL(buildOpenCodeUrl('/api/info', '')).origin;
|
|
270
|
+
const createScopedClient = (directory) => OpenCode.make({
|
|
271
|
+
baseUrl: openCodeOrigin(),
|
|
272
|
+
headers: {
|
|
273
|
+
...getOpenCodeAuthHeaders(),
|
|
274
|
+
...(directory ? { 'x-opencode-directory': encodeURIComponent(directory) } : {}),
|
|
275
|
+
},
|
|
276
|
+
fetch,
|
|
277
|
+
});
|
|
278
|
+
|
|
264
279
|
let started = false;
|
|
265
280
|
const tasksByProject = new Map();
|
|
266
281
|
const projectPathByID = new Map();
|
|
@@ -461,61 +476,45 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
461
476
|
return projectRunning < maxProjectConcurrency;
|
|
462
477
|
};
|
|
463
478
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
...(task.execution.variant ? { variant: task.execution.variant } : {}),
|
|
471
|
-
parts: [
|
|
472
|
-
// Standing project context first, so the prompt reads against it. A
|
|
473
|
-
// scheduled run has no UI to attach this, which is why it is asked for
|
|
474
|
-
// here rather than assembled by whoever is sending.
|
|
475
|
-
...(knowledgeText ? [{ type: 'text', text: knowledgeText, synthetic: true }] : []),
|
|
476
|
-
{
|
|
477
|
-
type: 'text',
|
|
478
|
-
text: expandSnippets(task.execution.prompt, projectPath),
|
|
479
|
-
},
|
|
480
|
-
...(task.execution.goalEnabled
|
|
481
|
-
? [{ type: 'text', text: buildGoalIntroText(task.execution.goalTokenBudget), synthetic: true }]
|
|
482
|
-
: []),
|
|
483
|
-
],
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
const runPromptAsync = async ({ baseUrl, authHeaders, sessionID, projectPath, task }) => {
|
|
487
|
-
// Never allowed to fail the run: a task that executes without its
|
|
488
|
-
// background is a lesser loss than a task that does not execute.
|
|
489
|
-
const knowledge = sessionKnowledgeRuntime
|
|
490
|
-
? await sessionKnowledgeRuntime.resolvePendingForSession(sessionID, projectPath)
|
|
491
|
-
.catch(() => ({ text: '', signature: '' }))
|
|
492
|
-
: { text: '', signature: '' };
|
|
493
|
-
|
|
494
|
-
const promptUrl = new URL(`${baseUrl}/session/${encodeURIComponent(sessionID)}/prompt_async`);
|
|
495
|
-
promptUrl.searchParams.set('directory', projectPath);
|
|
496
|
-
const response = await fetch(promptUrl.toString(), {
|
|
497
|
-
method: 'POST',
|
|
498
|
-
headers: {
|
|
499
|
-
...authHeaders,
|
|
500
|
-
'content-type': 'application/json',
|
|
501
|
-
accept: 'application/json',
|
|
502
|
-
},
|
|
503
|
-
body: JSON.stringify(buildPromptAsyncPayload(task, projectPath, knowledge.text)),
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
if (!response.ok) {
|
|
507
|
-
const body = await response.text().catch(() => '');
|
|
508
|
-
throw new Error(`prompt_async failed (${response.status})${body ? `: ${body}` : ''}`);
|
|
509
|
-
}
|
|
479
|
+
// Never allowed to fail the run: a task that executes without its
|
|
480
|
+
// background is a lesser loss than a task that does not execute.
|
|
481
|
+
const resolveKnowledge = (sessionID, projectPath) => (sessionKnowledgeRuntime
|
|
482
|
+
? sessionKnowledgeRuntime.resolvePendingForSession(sessionID, projectPath)
|
|
483
|
+
.catch(() => ({ text: '', signature: '' }))
|
|
484
|
+
: Promise.resolve({ text: '', signature: '' }));
|
|
510
485
|
|
|
511
|
-
|
|
512
|
-
|
|
486
|
+
// Recorded only after the send is accepted, so a failed dispatch carries
|
|
487
|
+
// the context again on the next run.
|
|
488
|
+
const recordKnowledge = async (sessionID, projectPath, knowledge) => {
|
|
513
489
|
if (knowledge.text && sessionKnowledgeRuntime) {
|
|
514
490
|
await sessionKnowledgeRuntime.recordDelivered(sessionID, projectPath, knowledge.signature)
|
|
515
491
|
.catch(() => undefined);
|
|
516
492
|
}
|
|
517
493
|
};
|
|
518
494
|
|
|
495
|
+
const runPrompt = async ({ client, sessionID, projectPath, task }) => {
|
|
496
|
+
const knowledge = await resolveKnowledge(sessionID, projectPath);
|
|
497
|
+
|
|
498
|
+
// A v2 prompt carries a single authored text. Standing project context and
|
|
499
|
+
// the goal briefing therefore travel as synthetic messages sent first, so
|
|
500
|
+
// the model still reads the prompt against them exactly as before. A
|
|
501
|
+
// synthetic message schedules execution unless `resume: false`: without
|
|
502
|
+
// it the model would start on the briefing alone, before the task arrived.
|
|
503
|
+
if (knowledge.text) {
|
|
504
|
+
await client.session.synthetic({ sessionID, text: knowledge.text, resume: false });
|
|
505
|
+
}
|
|
506
|
+
if (task.execution.goalEnabled) {
|
|
507
|
+
await client.session.synthetic({ sessionID, text: buildGoalIntroText(task.execution.goalTokenBudget), resume: false });
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
await client.session.prompt({
|
|
511
|
+
sessionID,
|
|
512
|
+
text: expandSnippets(task.execution.prompt, projectPath),
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
await recordKnowledge(sessionID, projectPath, knowledge);
|
|
516
|
+
};
|
|
517
|
+
|
|
519
518
|
const resolveScheduledCommand = async ({ client, projectPath, task }) => {
|
|
520
519
|
const parsed = parseScheduledCommandPrompt(task?.execution?.prompt);
|
|
521
520
|
if (!parsed) {
|
|
@@ -524,27 +523,35 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
524
523
|
|
|
525
524
|
let commands = [];
|
|
526
525
|
try {
|
|
527
|
-
const response = await client.command.list({ directory: projectPath });
|
|
526
|
+
const response = await client.command.list({ location: { directory: projectPath } });
|
|
528
527
|
commands = Array.isArray(response?.data) ? response.data : [];
|
|
529
528
|
} catch {
|
|
530
529
|
return null;
|
|
531
530
|
}
|
|
532
531
|
|
|
532
|
+
// v2 CommandInfo carries no template, so a goal objective distilled from
|
|
533
|
+
// the command body is no longer available; the goal falls back to the
|
|
534
|
+
// prompt text, which expandCommandGoalObjective already handles.
|
|
533
535
|
const command = commands.find((candidate) => candidate?.name === parsed.command);
|
|
534
536
|
return command ? { ...parsed, template: command.template } : null;
|
|
535
537
|
};
|
|
536
538
|
|
|
537
|
-
const runScheduledCommand = async ({ client,
|
|
539
|
+
const runScheduledCommand = async ({ client, sessionID, projectPath, command }) => {
|
|
540
|
+
// The command route takes no extra parts, so standing context goes in
|
|
541
|
+
// first as a synthetic message that does not start execution.
|
|
542
|
+
const knowledge = await resolveKnowledge(sessionID, projectPath);
|
|
543
|
+
if (knowledge.text) {
|
|
544
|
+
await client.session.synthetic({ sessionID, text: knowledge.text, resume: false });
|
|
545
|
+
}
|
|
546
|
+
// Agent, model and variant are session properties in v2 and were already
|
|
547
|
+
// set when the run created the session; the command body only carries text.
|
|
538
548
|
await client.session.command({
|
|
539
549
|
sessionID,
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
...(task.execution.agent ? { agent: task.execution.agent } : {}),
|
|
544
|
-
model: `${task.execution.providerID}/${task.execution.modelID}`,
|
|
545
|
-
...(task.execution.variant ? { variant: task.execution.variant } : {}),
|
|
550
|
+
// OpenCode 2.0.8 renamed the command body field `command` to `name`.
|
|
551
|
+
name: command.command,
|
|
552
|
+
text: command.arguments,
|
|
546
553
|
});
|
|
547
|
-
|
|
554
|
+
await recordKnowledge(sessionID, projectPath, knowledge);
|
|
548
555
|
};
|
|
549
556
|
|
|
550
557
|
const runTaskWithWatchdog = async (projectID, task, reason) => {
|
|
@@ -559,18 +566,23 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
559
566
|
await waitForOpenCodeReady(10_000, 250);
|
|
560
567
|
}
|
|
561
568
|
|
|
562
|
-
const baseUrl =
|
|
569
|
+
const baseUrl = openCodeOrigin();
|
|
563
570
|
const authHeaders = getOpenCodeAuthHeaders();
|
|
564
|
-
const client =
|
|
565
|
-
baseUrl,
|
|
566
|
-
headers: authHeaders,
|
|
567
|
-
});
|
|
571
|
+
const client = createScopedClient(projectPath);
|
|
568
572
|
|
|
569
|
-
|
|
570
|
-
|
|
573
|
+
// Agent, model and variant belong to the session in v2: a scheduled run
|
|
574
|
+
// fixes them here instead of repeating them on every prompt.
|
|
575
|
+
const session = await client.session.create({
|
|
571
576
|
title,
|
|
577
|
+
location: { directory: projectPath },
|
|
578
|
+
model: {
|
|
579
|
+
providerID: task.execution.providerID,
|
|
580
|
+
id: task.execution.modelID,
|
|
581
|
+
...(task.execution.variant ? { variant: task.execution.variant } : {}),
|
|
582
|
+
},
|
|
583
|
+
...(task.execution.agent ? { agent: task.execution.agent } : {}),
|
|
572
584
|
});
|
|
573
|
-
const sessionID =
|
|
585
|
+
const sessionID = session?.id;
|
|
574
586
|
if (!sessionID) {
|
|
575
587
|
throw new Error('failed to create session');
|
|
576
588
|
}
|
|
@@ -606,6 +618,7 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
606
618
|
await createSessionGoal({
|
|
607
619
|
baseUrl,
|
|
608
620
|
authHeaders,
|
|
621
|
+
persistSessionGoal,
|
|
609
622
|
sessionID,
|
|
610
623
|
directory: projectPath,
|
|
611
624
|
objective: commandObjective ?? expandSnippets(task.execution.prompt, projectPath),
|
|
@@ -617,15 +630,9 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
617
630
|
}
|
|
618
631
|
|
|
619
632
|
if (scheduledCommand) {
|
|
620
|
-
await runScheduledCommand({ client,
|
|
633
|
+
await runScheduledCommand({ client, sessionID, projectPath, command: scheduledCommand });
|
|
621
634
|
} else {
|
|
622
|
-
await
|
|
623
|
-
baseUrl,
|
|
624
|
-
authHeaders,
|
|
625
|
-
sessionID,
|
|
626
|
-
projectPath,
|
|
627
|
-
task,
|
|
628
|
-
});
|
|
635
|
+
await runPrompt({ client, sessionID, projectPath, task });
|
|
629
636
|
}
|
|
630
637
|
|
|
631
638
|
const finishedAt = Date.now();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { mkdtemp, rm, mkdir, writeFile } from 'fs/promises';
|
|
@@ -273,3 +273,55 @@ Run daily.
|
|
|
273
273
|
}
|
|
274
274
|
});
|
|
275
275
|
});
|
|
276
|
+
|
|
277
|
+
describe('scheduled-tasks runtime prompt dispatch', () => {
|
|
278
|
+
afterEach(() => {
|
|
279
|
+
vi.unstubAllGlobals();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('parks the briefing with resume: false so execution starts on the task prompt', async () => {
|
|
283
|
+
const posts = [];
|
|
284
|
+
vi.stubGlobal('fetch', vi.fn(async (input, init = {}) => {
|
|
285
|
+
const { pathname } = new URL(String(input));
|
|
286
|
+
if (init.method === 'POST') posts.push({ pathname, body: JSON.parse(init.body) });
|
|
287
|
+
const data = pathname === '/api/session' ? { id: 'ses_run' } : pathname === '/api/command' ? [] : {};
|
|
288
|
+
return new Response(JSON.stringify({ location: { directory: '/repo' }, data }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
289
|
+
}));
|
|
290
|
+
const task = {
|
|
291
|
+
id: 'task-1',
|
|
292
|
+
name: 'Nightly',
|
|
293
|
+
enabled: true,
|
|
294
|
+
schedule: { kind: 'daily', times: ['03:00'], timezone: 'UTC' },
|
|
295
|
+
execution: { prompt: 'Review open issues', providerID: 'openai', modelID: 'gpt-5', goalEnabled: true, goalTokenBudget: 50_000 },
|
|
296
|
+
state: { createdAt: 1, updatedAt: 1 },
|
|
297
|
+
};
|
|
298
|
+
const runtime = createScheduledTasksRuntime({
|
|
299
|
+
projectConfigRuntime: {
|
|
300
|
+
listScheduledTasks: async () => [task],
|
|
301
|
+
reconcileLoopTasks: async () => [task],
|
|
302
|
+
updateScheduledTaskState: async () => ({ task, updated: true }),
|
|
303
|
+
updateScheduledTaskStateIf: async () => ({ task, updated: true }),
|
|
304
|
+
},
|
|
305
|
+
listProjects: async () => [{ id: 'proj', path: '/repo' }],
|
|
306
|
+
buildOpenCodeUrl: () => 'http://127.0.0.1:1/',
|
|
307
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
308
|
+
waitForOpenCodeReady: async () => {},
|
|
309
|
+
persistSessionGoal: async () => undefined,
|
|
310
|
+
sessionKnowledgeRuntime: {
|
|
311
|
+
resolvePendingForSession: async () => ({ text: 'Project background', signature: 'sig' }),
|
|
312
|
+
recordDelivered: async () => undefined,
|
|
313
|
+
},
|
|
314
|
+
logger: { info: () => {}, warn: () => {}, error: () => {} },
|
|
315
|
+
});
|
|
316
|
+
await runtime.start();
|
|
317
|
+
await runtime.runNow('proj', 'task-1');
|
|
318
|
+
runtime.stop();
|
|
319
|
+
|
|
320
|
+
const dispatch = posts.filter((post) => post.pathname.startsWith('/api/session/ses_run/'));
|
|
321
|
+
expect(dispatch.map((post) => post.pathname.split('/').at(-1))).toEqual(['synthetic', 'synthetic', 'prompt']);
|
|
322
|
+
expect(dispatch[0].body).toMatchObject({ text: 'Project background', resume: false });
|
|
323
|
+
expect(dispatch[1].body.resume).toBe(false);
|
|
324
|
+
expect(dispatch[2].body).toMatchObject({ text: 'Review open issues' });
|
|
325
|
+
expect(dispatch[2].body.resume).toBeUndefined();
|
|
326
|
+
});
|
|
327
|
+
});
|
|
@@ -28,9 +28,14 @@ bringing an entire old task back into the prompt. This was compared against
|
|
|
28
28
|
one, five, ten, and full-history contexts on long maintainer sessions. There
|
|
29
29
|
is no full-history cache and no assumed provider prefix-cache behavior.
|
|
30
30
|
|
|
31
|
-
The latest record must be a completed, successful, non-summary
|
|
32
|
-
with visible text.
|
|
33
|
-
|
|
31
|
+
The latest content record must be a completed, successful, non-summary
|
|
32
|
+
assistant answer with visible text. OpenCode closes every turn with an `idle`
|
|
33
|
+
record and appends agent/model/location switches as records of their own;
|
|
34
|
+
`newestContentId` looks past those, both here and in the re-check before the
|
|
35
|
+
write, so an ordinary v2 transcript still ends in its answer. An `idle` whose
|
|
36
|
+
outcome is `failed` or `interrupted` is not skipped: it disqualifies the turn.
|
|
37
|
+
Child, archived, and reverted sessions are skipped. A new prompt clears the
|
|
38
|
+
revert boundary before its next idle event.
|
|
34
39
|
|
|
35
40
|
Human turns follow chronological message intervals. OpenCode can insert
|
|
36
41
|
synthetic continuation users during compaction, so a final answer's `parentID`
|
|
@@ -23,8 +23,8 @@ export function excerpt(text, limit) {
|
|
|
23
23
|
return text.slice(0, head) + marker + (tail > 0 ? text.slice(-tail) : '');
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function attachedText(
|
|
27
|
-
const context =
|
|
26
|
+
function attachedText(message) {
|
|
27
|
+
const context = message.metadata?.openchamberContext;
|
|
28
28
|
if (QUOTE_FIELDS.has(context?.kind)) {
|
|
29
29
|
const source = (context[QUOTE_FIELDS.get(context.kind)] ?? '').trim();
|
|
30
30
|
const authored = (context.text ?? '').trim();
|
|
@@ -33,8 +33,8 @@ function attachedText(part) {
|
|
|
33
33
|
const quote = excerpt(source, 4_000).split('\n').map((line) => `> ${line}`).join('\n');
|
|
34
34
|
return { text: `Attached ${context.kind}${label ? ` (${label}${location})` : ''}:\n${quote}\n\nUser comment:\n${excerpt(authored, USER_CHAR_LIMIT)}`, authored };
|
|
35
35
|
}
|
|
36
|
-
if (LINK_KINDS.has(context?.kind)) return { text: excerpt(
|
|
37
|
-
const comment =
|
|
36
|
+
if (LINK_KINDS.has(context?.kind)) return { text: excerpt(message.text ?? '', USER_CHAR_LIMIT), authored: '' };
|
|
37
|
+
const comment = message.metadata?.opencodeComment;
|
|
38
38
|
if (comment) {
|
|
39
39
|
const authored = comment.comment.trim();
|
|
40
40
|
const source = (comment.preview ?? '').trim();
|
|
@@ -44,51 +44,119 @@ function attachedText(part) {
|
|
|
44
44
|
return null;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// Records OpenCode appends around a turn that carry no conversation content:
|
|
48
|
+
// the `idle` marker that closes every turn and the agent/model/location
|
|
49
|
+
// switches. They are invisible to turn boundaries and to "what is the newest
|
|
50
|
+
// message". An `idle` whose outcome is not `succeeded` is not transparent: it
|
|
51
|
+
// is the evidence that the turn failed or was interrupted.
|
|
52
|
+
const TRANSPARENT_TYPES = new Set(['agent-switched', 'model-switched', 'location-switched']);
|
|
53
|
+
|
|
54
|
+
function isTransparent(record) {
|
|
55
|
+
if (record?.type === 'idle') return record.outcome === 'succeeded';
|
|
56
|
+
return TRANSPARENT_TYPES.has(record?.type);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The id of the newest record that is conversation content, given a page in
|
|
61
|
+
* v2's newest-first order; null when the page holds only service records.
|
|
62
|
+
* Both the assist reader and the pre-write re-check use it so they agree on
|
|
63
|
+
* what "the last message" is.
|
|
64
|
+
*/
|
|
65
|
+
export function newestContentId(records) {
|
|
66
|
+
for (const record of Array.isArray(records) ? records : []) {
|
|
67
|
+
if (!record?.id || isTransparent(record)) continue;
|
|
68
|
+
return record.id;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* v2 message records are flat: an assistant message carries `content[]`, a user
|
|
75
|
+
* message a single `text` plus its attachments, and context items that used to
|
|
76
|
+
* ride along as `synthetic` text parts are their own `synthetic` messages now.
|
|
77
|
+
* Tool payloads never enter this view.
|
|
78
|
+
*/
|
|
79
|
+
function readMessage(message) {
|
|
80
|
+
const role = message.type;
|
|
49
81
|
const blocks = [];
|
|
50
82
|
const authored = [];
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const attached =
|
|
83
|
+
|
|
84
|
+
if (role === 'user') {
|
|
85
|
+
const attached = attachedText(message);
|
|
54
86
|
if (attached) {
|
|
55
87
|
blocks.push(attached.text);
|
|
56
88
|
authored.push(attached.authored);
|
|
57
|
-
} else if (
|
|
58
|
-
blocks.push(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
} else if (typeof message.text === 'string') {
|
|
90
|
+
blocks.push(message.text);
|
|
91
|
+
// Legacy terminal selections are source material, not a language sample.
|
|
92
|
+
authored.push(message.text.replace(/\n*<terminal_context>\n[\s\S]*?\n<\/terminal_context>\s*$/, ''));
|
|
93
|
+
}
|
|
94
|
+
} else if (role === 'assistant') {
|
|
95
|
+
for (const part of Array.isArray(message.content) ? message.content : []) {
|
|
96
|
+
if (part?.type === 'text' && typeof part.text === 'string') blocks.push(part.text);
|
|
97
|
+
}
|
|
98
|
+
} else if (role === 'synthetic') {
|
|
99
|
+
// Context the user attached to the message that follows: in v1 it was a
|
|
100
|
+
// `synthetic` text part inside the user message, in v2 it is its own turn.
|
|
101
|
+
const attached = attachedText(message);
|
|
102
|
+
if (attached) {
|
|
103
|
+
blocks.push(attached.text);
|
|
104
|
+
authored.push(attached.authored);
|
|
105
|
+
} else if (typeof message.text === 'string') {
|
|
106
|
+
blocks.push(message.text);
|
|
63
107
|
}
|
|
64
108
|
}
|
|
109
|
+
|
|
65
110
|
return {
|
|
66
|
-
id:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
111
|
+
id: message.id,
|
|
112
|
+
role,
|
|
113
|
+
transparent: isTransparent(message),
|
|
114
|
+
// v2 has no `parentID` on a message: a turn is the run of messages between
|
|
115
|
+
// one user message and the assistant reply that follows it, which is what
|
|
116
|
+
// `collectTurns` walks.
|
|
117
|
+
providerID: message.model?.providerID,
|
|
118
|
+
modelID: message.model?.id,
|
|
119
|
+
complete: role === 'assistant'
|
|
120
|
+
&& message.finish === 'stop'
|
|
121
|
+
&& Boolean(message.time?.completed)
|
|
122
|
+
&& !message.error,
|
|
123
|
+
text: excerpt(blocks.join('\n\n').trim(), role === 'user' ? USER_CHAR_LIMIT : ANSWER_CHAR_LIMIT),
|
|
71
124
|
authored: excerpt(authored.filter(Boolean).join('\n\n').trim(), USER_CHAR_LIMIT),
|
|
72
125
|
};
|
|
73
126
|
}
|
|
74
127
|
|
|
128
|
+
/**
|
|
129
|
+
* A turn is one user message and the assistant reply that closes it. v2 dropped
|
|
130
|
+
* `parentID`, so the boundary is positional: everything between two user
|
|
131
|
+
* messages belongs to the turn the earlier one opened.
|
|
132
|
+
*
|
|
133
|
+
* Synthetic messages are the context the user attached to the message they are
|
|
134
|
+
* about to send — v1 carried them as parts of that user message — so they are
|
|
135
|
+
* folded into the next user message instead of opening a turn of their own.
|
|
136
|
+
*/
|
|
75
137
|
function collectTurns(messages) {
|
|
76
138
|
const turns = [];
|
|
77
139
|
let active = null;
|
|
78
|
-
let
|
|
140
|
+
let attached = [];
|
|
79
141
|
for (const message of messages) {
|
|
80
|
-
if (message.
|
|
81
|
-
|
|
82
|
-
|
|
142
|
+
if (message.transparent) continue;
|
|
143
|
+
if (message.role === 'synthetic') {
|
|
144
|
+
if (message.text) attached.push(message);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (message.role === 'user') {
|
|
148
|
+
const text = [...attached.map((entry) => entry.text), message.text].filter(Boolean).join('\n\n');
|
|
149
|
+
const authored = [...attached.map((entry) => entry.authored), message.authored].filter(Boolean).join('\n\n');
|
|
150
|
+
attached = [];
|
|
151
|
+
if (!text) continue;
|
|
152
|
+
active = { user: { ...message, text, authored }, assistant: null, complete: false };
|
|
83
153
|
turns.push(active);
|
|
84
|
-
|
|
85
|
-
parents.add(message.id);
|
|
86
|
-
} else if (active && message.role === 'assistant' && parents.has(message.parentID) && !message.summary) {
|
|
87
|
-
// Compaction's synthetic continuation users belong to the same human
|
|
88
|
-
// turn. Parent IDs alone would lose its eventual final answer.
|
|
89
|
-
if (message.text) active.assistant = message;
|
|
90
|
-
active.complete = message.complete && Boolean(message.text);
|
|
154
|
+
continue;
|
|
91
155
|
}
|
|
156
|
+
attached = [];
|
|
157
|
+
if (!active || message.role !== 'assistant') continue;
|
|
158
|
+
if (message.text) active.assistant = message;
|
|
159
|
+
active.complete = message.complete && Boolean(message.text);
|
|
92
160
|
}
|
|
93
161
|
return turns.slice(-TURN_LIMIT);
|
|
94
162
|
}
|
|
@@ -96,32 +164,40 @@ function collectTurns(messages) {
|
|
|
96
164
|
/** Failure is thrown; null means no eligible final answer within bounded history. */
|
|
97
165
|
export async function loadAssistContext({ readPage, signal }) {
|
|
98
166
|
let messages = [];
|
|
99
|
-
let
|
|
167
|
+
let cursor;
|
|
100
168
|
const cursors = new Set();
|
|
101
169
|
const ids = new Set();
|
|
102
170
|
for (let pageNumber = 0; pageNumber < MAX_PAGES; pageNumber++) {
|
|
103
171
|
signal.throwIfAborted();
|
|
104
|
-
|
|
172
|
+
// v2 pages messages newest first and returns `{ data, cursor }`.
|
|
173
|
+
const page = await readPage({ limit: PAGE_SIZE, cursor });
|
|
105
174
|
signal.throwIfAborted();
|
|
106
|
-
if (!Array.isArray(page
|
|
175
|
+
if (!Array.isArray(page?.data)) throw new Error('Session message page is unavailable');
|
|
107
176
|
const older = [];
|
|
108
177
|
for (const record of page.data) {
|
|
109
|
-
if (ids.has(record.
|
|
110
|
-
ids.add(record.
|
|
178
|
+
if (!record?.id || ids.has(record.id)) continue;
|
|
179
|
+
ids.add(record.id);
|
|
111
180
|
older.push(readMessage(record));
|
|
112
181
|
}
|
|
182
|
+
older.reverse();
|
|
113
183
|
messages = older.concat(messages);
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
if (!
|
|
120
|
-
|
|
184
|
+
const next = typeof page.cursor?.next === 'string' ? page.cursor.next : null;
|
|
185
|
+
// A successful turn ends with an `idle` marker after the answer; look past
|
|
186
|
+
// it and its siblings to the newest content record.
|
|
187
|
+
const last = messages.findLast((message) => !message.transparent);
|
|
188
|
+
if (!last) {
|
|
189
|
+
if (!next || pageNumber === MAX_PAGES - 1) return null;
|
|
190
|
+
} else {
|
|
191
|
+
if (!last.complete || !last.text) return null;
|
|
192
|
+
const turns = collectTurns(messages);
|
|
193
|
+
if (turns.length === TURN_LIMIT || !next || pageNumber === MAX_PAGES - 1) {
|
|
194
|
+
if (!turns.at(-1)?.complete || turns.at(-1).assistant.id !== last.id) return null;
|
|
195
|
+
return { turns, last };
|
|
196
|
+
}
|
|
121
197
|
}
|
|
122
|
-
if (cursors.has(
|
|
123
|
-
cursors.add(
|
|
124
|
-
|
|
198
|
+
if (cursors.has(next)) throw new Error('Session message pagination made no progress');
|
|
199
|
+
cursors.add(next);
|
|
200
|
+
cursor = next;
|
|
125
201
|
}
|
|
126
202
|
return null;
|
|
127
203
|
}
|