@vantran-se/openchamber-web 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -24
- package/bin/cli.js +1 -3
- package/bin/cli.test.js +40 -16
- package/bin/lib/DOCUMENTATION.md +6 -5
- package/bin/lib/cli-args.js +6 -1
- package/bin/lib/cli-args.test.js +34 -0
- package/bin/lib/cli-lifecycle.js +0 -1
- package/bin/lib/cli-network.js +8 -2
- package/bin/lib/cli-process.js +1 -1
- package/bin/lib/cli-settings-accessors.test.js +21 -13
- package/bin/lib/cli-startup.js +17 -11
- package/bin/lib/cli-startup.test.js +92 -2
- package/bin/lib/commands-connect-url.js +8 -1
- package/bin/lib/commands-schedule.js +8 -2
- package/bin/lib/commands-serve.js +15 -21
- package/bin/lib/commands-update.js +12 -24
- package/bin/lib/commands-update.test.js +14 -46
- package/dist/assets/DiffView-Cv9MSXl1.js +4 -0
- package/dist/assets/FilePreviewCommentMenu-D4kLseHj.js +4 -0
- package/dist/assets/FilesView-BVoD8I2x.js +14 -0
- package/dist/assets/{GitView-DzuzhIPx.js → GitView-Bzb8p-ew.js} +4 -4
- package/dist/assets/GuestSurfacePane-Crdbvsxo.js +1 -0
- package/dist/assets/InProgressOperationBanner-CwMjNQgk.js +2 -0
- package/dist/assets/{JsonTreeView-B_EPRjzy.js → JsonTreeView-DRKHZfsn.js} +1 -1
- package/dist/assets/MarkdownImageGallery-DZtqT1Zn.js +1 -0
- package/dist/assets/MarkdownRendererImpl-CqG7HCNd.js +10 -0
- package/dist/assets/OnboardingScreen-DMIxwYK5.js +1 -0
- package/dist/assets/{PatchDiff-6yh1csza.js → PatchDiff-CUcCg1jk.js} +1 -1
- package/dist/assets/PierreDiffViewer-CnnR-dnD.js +228 -0
- package/dist/assets/PlanView-RCBadcY7.js +5 -0
- package/dist/assets/SettingsView-CjBjZFRO.js +30 -0
- package/dist/assets/SettingsWindow-DyM7QuhI.js +1 -0
- package/dist/assets/SubmoduleDiffSummary-BWIfgMED.js +1 -0
- package/dist/assets/ToolOutputDialog-Q0P3FuAi.js +15 -0
- package/dist/assets/ToolPartDiffPreview-p7qR9FpU.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-BTJVRkyo.js → VirtualizedFileDiff-CFpjL8Ns.js} +3 -3
- package/dist/assets/{Virtualizer-xCEYz34p.js → Virtualizer-C0IsclZn.js} +1 -1
- package/dist/assets/WalkthroughView-DI31zD02.js +4 -0
- package/dist/assets/appThemeRegistry-B9gkFq9i.js +1 -0
- package/dist/assets/{computeVirtualFileMetrics-CmPe29DM.js → computeVirtualFileMetrics-C2WM6JC2.js} +2 -2
- package/dist/assets/de-YKfk5MmC.js +14 -0
- package/dist/assets/es-_gwT0kr-.js +20 -0
- package/dist/assets/fileTreeStatus-D438yi8M.js +1 -0
- package/dist/assets/fr-DqzvXmIm.js +18 -0
- package/dist/assets/index-BLaHKdtx.js +1 -0
- package/dist/assets/index-BkMXRrVQ.css +1 -0
- package/dist/assets/index-DxNa7xYB.js +65 -0
- package/dist/assets/initializeSharedPreferences-Bck5EAWP.js +1 -0
- package/dist/assets/{isDiffPlainText-CQmDCnEv.js → isDiffPlainText-Dgbw8VKk.js} +1 -1
- package/dist/assets/{isFilePlainText-CYBUzW4I.js → isFilePlainText-_ZS5UYRj.js} +1 -1
- package/dist/assets/ja-B-GFYhQE.js +20 -0
- package/dist/assets/ko-mzksfqDC.js +20 -0
- package/dist/assets/main-C5Xx8dn_.js +2 -0
- package/dist/assets/main-CfBSQM1H.js +990 -0
- package/dist/assets/markdownCore-BmWwW1ll.js +7 -0
- package/dist/assets/miniChat-mnKwy4vQ.js +2 -0
- package/dist/assets/mobile-DeEQd0wZ.js +2 -0
- package/dist/assets/nl-CWQr6NSq.js +20 -0
- package/dist/assets/{parseDiffFromFile-Cy0PNxun.js → parseDiffFromFile-BUgn-T9p.js} +2 -2
- package/dist/assets/patchFileDiff-Dt8LhgBh.js +13 -0
- package/dist/assets/pl-UmRcDUX2.js +20 -0
- package/dist/assets/pt-BR-BSLybvVF.js +20 -0
- package/dist/assets/relocateRestoredSession-B5E9xb3T.js +1 -0
- package/dist/assets/renderElectronMiniChatApp-C0a9A5_J.js +2 -0
- package/dist/assets/renderMobileApp-BNncg3Pk.js +25 -0
- package/dist/assets/runtimeEndpointReset-CZ5aafpl.js +62 -0
- package/dist/assets/{selectionMarkdown-TkYk5h9l.js → selectionMarkdown-CUN3wUld.js} +9 -9
- package/dist/assets/{sessionOwnership-Bs1xbRfB.js → sessionOwnership-BSuxCEhb.js} +1 -1
- package/dist/assets/shikiHighlight-DHDx-6eM.js +1 -0
- package/dist/assets/space-apply-DM_2pRW7.js +1 -0
- package/dist/assets/space-close-Bpomt8z0.js +1 -0
- package/dist/assets/{surface-B6n7wJtt.js → surface-C-jQaGnL.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-CDlKffWz.js → textMateThemeFromAppTheme-DlsTphbY.js} +1 -1
- package/dist/assets/tr-gaTrX_HK.js +11 -0
- package/dist/assets/uk-CnJF6ykl.js +20 -0
- package/dist/assets/{useAppFontEffects-DCqCVGlT.js → useAppFontEffects-DGlvOqP_.js} +194 -233
- package/dist/assets/useInlineCommentController-kMN4Wg_D.js +2 -0
- package/dist/assets/{useRootScrollLock-BowwS8kq.js → useRootScrollLock-DMPpvpNP.js} +2 -2
- package/dist/assets/{vendor-base-ui-DX96QhTP.js → vendor-base-ui-Det7U81h.js} +1 -1
- package/dist/assets/{vendor-codemirror-autocomplete-DW1FQz9_.js → vendor-codemirror-autocomplete-BSHQHbKu.js} +1 -1
- package/dist/assets/{vendor-codemirror-commands-DpmhalwN.js → vendor-codemirror-commands-C4G_dPWe.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-angular-BqExbN1M.js → vendor-codemirror-lang-angular-B6LbBGHB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-cpp-DZA36HfS.js → vendor-codemirror-lang-cpp-BWWXBx7W.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-css-B07blnnZ.js → vendor-codemirror-lang-css-Cayfb29q.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-go-BM8K9D1-.js → vendor-codemirror-lang-go-BoKlCNKw.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-html-DaH9adfE.js → vendor-codemirror-lang-html-BQWerHsJ.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-java-CsdC0viz.js → vendor-codemirror-lang-java-DWX7Fq_K.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-javascript-CpGYXGuo.js → vendor-codemirror-lang-javascript-DbpEcZM0.js} +2 -2
- package/dist/assets/{vendor-codemirror-lang-jinja-BBeGzXw5.js → vendor-codemirror-lang-jinja-D1JIJRA7.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-json-58T70Hqs.js → vendor-codemirror-lang-json-BzgckzmB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-less-BI-xCA59.js → vendor-codemirror-lang-less-B9EpDXFi.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-liquid-zEcMa-zy.js → vendor-codemirror-lang-liquid-9U7kRbSf.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-markdown-BLKnlLP4.js → vendor-codemirror-lang-markdown-D2TEBIvZ.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-php-9f35M83-.js → vendor-codemirror-lang-php-BT6sDVzE.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-python-jAMtYiA9.js +3 -0
- package/dist/assets/{vendor-codemirror-lang-rust-MNDL_jZQ.js → vendor-codemirror-lang-rust-CVO56EiL.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-sass-p9epsSNm.js → vendor-codemirror-lang-sass-DZlOoUWu.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-sql-Bqzh0EXX.js → vendor-codemirror-lang-sql-fZ6t1DPw.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-vue-BnNUWV-p.js → vendor-codemirror-lang-vue-DxbqrLiE.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-wast-C2a-oLmk.js → vendor-codemirror-lang-wast-DyU1Gt-I.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-xml-C9a_K5_p.js +1 -0
- package/dist/assets/{vendor-codemirror-lang-yaml-C0sjswB0.js → vendor-codemirror-lang-yaml-Cptq8l0e.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-ByBn5zIW.js → vendor-codemirror-language-DufXoQvE.js} +2 -2
- package/dist/assets/vendor-codemirror-language-data-CtwSWKtX.js +2 -0
- package/dist/assets/{vendor-codemirror-search-ib-sHD5W.js → vendor-codemirror-search-CvXM_pZg.js} +1 -1
- package/dist/assets/{vendor-codemirror-state-G3UuR-be.js → vendor-codemirror-state-Dek9sMtw.js} +1 -1
- package/dist/assets/{vendor-codemirror-view-Bniqp7p5.js → vendor-codemirror-view-1e0FQ6Wn.js} +1 -1
- package/dist/assets/vendor-framer-motion-DxDVhOLc.js +9 -0
- package/dist/assets/vendor-katex-CuQvj9-L.js +257 -0
- package/dist/assets/vendor-katex-DdZNUVNG.css +1 -0
- package/dist/assets/{vendor-motion-dom-DcCgeG69.js → vendor-motion-dom-HXPOMxb7.js} +1 -1
- package/dist/assets/{vendor-opencode-client-DHzhmLwV.js → vendor-opencode-client-BmFcUkbQ.js} +1 -1
- package/dist/assets/{vendor-replit-codemirror-vim-DkzqxN2H.js → vendor-replit-codemirror-vim-BlJRn-cW.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-virtual-H_JmXOFV.js → vendor-tanstack-react-virtual-CvuLsk7D.js} +1 -1
- package/dist/assets/{vendor-tanstack-virtual-core-Dxc8hSTB.js → vendor-tanstack-virtual-core-DPaNcJcT.js} +1 -1
- package/dist/assets/zh-CN-t2vigSB1.js +20 -0
- package/dist/assets/zh-TW-jAgqBokB.js +20 -0
- package/dist/index.html +6 -6
- package/dist/mini-chat.html +6 -6
- package/dist/mobile.html +6 -6
- package/package.json +8 -5
- package/server/index.js +80 -36
- package/server/lib/agent-memory/actions.js +1 -6
- package/server/lib/agent-memory/routes.js +1 -2
- package/server/lib/agent-memory/runtime.js +1 -8
- package/server/lib/agent-tool/DOCUMENTATION.md +7 -3
- package/server/lib/agent-tool/runtime.js +3 -6
- package/server/lib/agent-tool/runtime.test.js +0 -2
- package/server/lib/browser-control/DOCUMENTATION.md +6 -0
- package/server/lib/changelog/update-notes.js +1 -1
- package/server/lib/context-obligatory/runtime.js +1 -2
- package/server/lib/dev-tunnel/DOCUMENTATION.md +8 -0
- package/server/lib/dev-tunnel/client.js +21 -10
- package/server/lib/dev-tunnel/tunnel.test.js +34 -0
- package/server/lib/dispatch-results/runtime.js +1 -1
- package/server/lib/enterprise-mode.d.ts +1 -0
- package/server/lib/enterprise-mode.js +13 -2
- package/server/lib/enterprise-mode.test.js +21 -0
- package/server/lib/event-stream/DOCUMENTATION.md +2 -2
- package/server/lib/event-stream/index.js +1 -3
- package/server/lib/event-stream/translate-v2.js +1 -9
- package/server/lib/event-stream/translate-v2.test.js +0 -11
- package/server/lib/fs/DOCUMENTATION.md +5 -2
- package/server/lib/fs/routes.js +62 -15
- package/server/lib/fs/routes.test.js +278 -48
- package/server/lib/fs/workspace-file-names.js +104 -0
- package/server/lib/fs/workspace-file-names.test.js +62 -0
- package/server/lib/git/DOCUMENTATION.md +17 -5
- package/server/lib/git/contributor-provenance-storage.js +9 -6
- package/server/lib/git/contributor-provenance-storage.test.js +29 -0
- package/server/lib/git/credential-broker.js +1 -1
- package/server/lib/git/credential-resolver.js +1 -4
- package/server/lib/git/credentials.js +1 -0
- package/server/lib/git/discovery-endpoint.js +1 -1
- package/server/lib/git/identity-provisioning.js +2 -1
- package/server/lib/git/identity-storage.js +3 -4
- package/server/lib/git/lfs-discovery.js +14 -14
- package/server/lib/git/lfs-discovery.test.js +16 -6
- package/server/lib/git/network-operation-plan.js +50 -62
- package/server/lib/git/network-operation-plan.test.js +103 -1
- package/server/lib/git/network-operation-registry.js +4 -6
- package/server/lib/git/network-operation-storage.js +4 -6
- package/server/lib/git/network-operations.js +12 -4
- package/server/lib/git/network-operations.test.js +19 -6
- package/server/lib/git/redaction.js +2 -1
- package/server/lib/git/repository-credential-runtime.js +5 -3
- package/server/lib/git/repository-credential-runtime.test.js +22 -0
- package/server/lib/git/routes.js +31 -137
- package/server/lib/git/routes.test.js +2 -2
- package/server/lib/git/service.js +143 -27
- package/server/lib/git/service.test.js +215 -1
- package/server/lib/git/ssh-credential-storage.js +1 -4
- package/server/lib/git/submodule-discovery.js +1 -1
- package/server/lib/git/worktree-bootstrap-storage.js +3 -4
- package/server/lib/git/worktree-bootstrap-storage.test.js +15 -0
- package/server/lib/git/worktree-creation.js +194 -0
- package/server/lib/git/worktree-creation.test.js +155 -0
- package/server/lib/github/DOCUMENTATION.md +11 -6
- package/server/lib/github/auth.js +2 -4
- package/server/lib/github/device-flow.js +2 -2
- package/server/lib/github/gh-cli-credential.js +1 -0
- package/server/lib/github/octokit.js +31 -0
- package/server/lib/github/pr-status.js +35 -18
- package/server/lib/github/pr-status.test.js +271 -3
- package/server/lib/github/reference-search.js +102 -13
- package/server/lib/github/reference-search.test.js +48 -7
- package/server/lib/github/routes.auth.test.js +18 -0
- package/server/lib/github/routes.js +360 -6
- package/server/lib/github/routes.mutations.test.js +239 -0
- package/server/lib/github/routes.test.js +134 -2
- package/server/lib/gitlab/DOCUMENTATION.md +5 -1
- package/server/lib/gitlab/auth-storage.test.js +1 -0
- package/server/lib/gitlab/mappers.js +30 -1
- package/server/lib/gitlab/resources.js +230 -6
- package/server/lib/gitlab/resources.test.js +179 -0
- package/server/lib/gitlab/routes.js +200 -6
- package/server/lib/gitlab/routes.test.js +186 -2
- package/server/lib/gitlab/validation.js +1 -5
- package/server/lib/guests/DOCUMENTATION.md +1 -1
- package/server/lib/guests/oauth.js +2 -2
- package/server/lib/guests/service.js +1 -1
- package/server/lib/guests/sockets.js +1 -1
- package/server/lib/injected-env.d.ts +8 -0
- package/server/lib/injected-env.js +50 -0
- package/server/lib/injected-env.test.js +56 -0
- package/server/lib/linear/DOCUMENTATION.md +5 -6
- package/server/lib/linear/issues.js +62 -4
- package/server/lib/linear/issues.test.js +68 -0
- package/server/lib/linear/oauth.js +5 -5
- package/server/lib/linear/parse.js +2 -9
- package/server/lib/message-queue/DOCUMENTATION.md +23 -1
- package/server/lib/message-queue/runtime.js +85 -12
- package/server/lib/message-queue/runtime.test.js +172 -2
- package/server/lib/notifications/apns-runtime.js +5 -6
- package/server/lib/notifications/apns-runtime.test.js +20 -4
- package/server/lib/openchamber-control/DOCUMENTATION.md +19 -1
- package/server/lib/openchamber-control/actions.js +2 -1
- package/server/lib/openchamber-control/service.js +119 -20
- package/server/lib/openchamber-control/service.test.js +166 -0
- package/server/lib/openchamber-sessions/archive-store.js +1 -6
- package/server/lib/openchamber-sessions/routes.js +47 -12
- package/server/lib/openchamber-sessions/routes.test.js +65 -2
- package/server/lib/openchamber-sessions/session-metadata-store.js +1 -6
- package/server/lib/opencode/DOCUMENTATION.md +22 -8
- package/server/lib/opencode/agents.js +62 -0
- package/server/lib/opencode/auth-state-runtime.js +3 -1
- package/server/lib/opencode/auth.d.ts +1 -1
- package/server/lib/opencode/auth.js +8 -0
- package/server/lib/opencode/auth.test.js +31 -0
- package/server/lib/opencode/config-entity-routes.js +16 -0
- package/server/lib/opencode/config-entity-v2.test.js +16 -1
- package/server/lib/opencode/config-paths.test.js +2 -2
- package/server/lib/opencode/config-v2.js +2 -4
- package/server/lib/opencode/feature-routes-runtime.js +89 -49
- package/server/lib/opencode/lifecycle.js +41 -21
- package/server/lib/opencode/lifecycle.test.js +50 -2
- package/server/lib/opencode/model-discovery.js +1 -1
- package/server/lib/opencode/model-discovery.test.js +17 -0
- package/server/lib/opencode/models-metadata.js +0 -2
- package/server/lib/opencode/openchamber-routes.test.js +3 -3
- package/server/lib/opencode/plugins.js +6 -9
- package/server/lib/opencode/provider-source-route.test.js +101 -3
- package/server/lib/opencode/providers-disabled.test.js +59 -0
- package/server/lib/opencode/providers.js +34 -0
- package/server/lib/opencode/proxy.js +11 -3
- package/server/lib/opencode/routes.js +40 -2
- package/server/lib/opencode/settings-helpers.js +27 -1
- package/server/lib/opencode/settings-helpers.test.js +19 -7
- package/server/lib/opencode/settings-registry.json +27 -16
- package/server/lib/opencode/settings-runtime.test.js +18 -0
- package/server/lib/opencode/shared-service-runtime.js +23 -104
- package/server/lib/opencode/shared.js +77 -46
- package/server/lib/opencode/shared.test.js +12 -12
- package/server/lib/opencode/shutdown-runtime.test.js +2 -3
- package/server/lib/opencode/skill-routes.js +123 -38
- package/server/lib/opencode/skill-routes.test.js +201 -0
- package/server/lib/opencode/skills.js +81 -34
- package/server/lib/opencode/skills.test.js +176 -21
- package/server/lib/opencode/startup-pipeline-runtime.js +6 -3
- package/server/lib/opencode/startup-pipeline-runtime.test.js +2 -1
- package/server/lib/opencode/watcher.test.js +0 -54
- package/server/lib/outbound-proxy.js +52 -0
- package/server/lib/outbound-proxy.test.js +55 -0
- package/server/lib/package-manager.js +23 -17
- package/server/lib/package-manager.test.js +5 -18
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +1 -1
- package/server/lib/permission-auto-accept/runtime.js +19 -47
- package/server/lib/permission-auto-accept/runtime.test.js +2 -2
- package/server/lib/project-context/DOCUMENTATION.md +7 -1
- package/server/lib/project-context/routes.js +1 -2
- package/server/lib/project-context/runtime.js +12 -14
- package/server/lib/project-context/runtime.test.js +78 -2
- package/server/lib/projects/project-config.js +30 -15
- package/server/lib/projects/project-config.test.js +30 -0
- package/server/lib/projects/project-setup.js +1 -2
- package/server/lib/projects/routes.js +1 -2
- package/server/lib/quota/DOCUMENTATION.md +35 -7
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/claude/index.js +1 -1
- package/server/lib/quota/providers/cline-pass.js +1 -1
- package/server/lib/quota/providers/exe-dev.js +1 -1
- package/server/lib/quota/providers/google/index.js +1 -1
- package/server/lib/quota/providers/hyper.js +1 -1
- package/server/lib/quota/providers/kimi.js +116 -36
- package/server/lib/quota/providers/kimi.test.js +128 -3
- package/server/lib/quota/providers/opencode-go.js +144 -21
- package/server/lib/quota/providers/opencode-go.test.js +229 -6
- package/server/lib/quota/providers/openrouter.js +1 -1
- package/server/lib/quota/providers/zenmux.js +1 -1
- package/server/lib/quota/utils/auth.js +1 -0
- package/server/lib/quota/utils/transformers.js +2 -6
- package/server/lib/relay/DOCUMENTATION.md +18 -3
- package/server/lib/relay/e2ee.js +7 -7
- package/server/lib/relay/host-client.js +84 -13
- package/server/lib/relay/host-client.test.js +201 -1
- package/server/lib/relay/identity.js +6 -29
- package/server/lib/relay/identity.test.js +18 -28
- package/server/lib/relay/key-store.js +141 -0
- package/server/lib/relay/key-store.test.js +138 -0
- package/server/lib/relay/service.js +8 -8
- package/server/lib/relay/service.test.js +10 -5
- package/server/lib/relay/signing-key.js +13 -36
- package/server/lib/relay/tunnel-codec.js +10 -10
- package/server/lib/route-module-exports.test.js +22 -0
- package/server/lib/routing/DOCUMENTATION.md +23 -3
- package/server/lib/routing/cache.js +87 -0
- package/server/lib/routing/defaults.js +11 -0
- package/server/lib/routing/history.js +12 -4
- package/server/lib/routing/runtime.js +39 -3
- package/server/lib/routing/runtime.test.js +81 -2
- package/server/lib/routing/store.js +12 -1
- package/server/lib/routing/store.test.js +13 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +59 -1
- package/server/lib/scheduled-tasks/existing-session.js +119 -0
- package/server/lib/scheduled-tasks/existing-session.test.js +350 -0
- package/server/lib/scheduled-tasks/loops.js +1 -8
- package/server/lib/scheduled-tasks/routes.js +8 -8
- package/server/lib/scheduled-tasks/runtime.js +103 -13
- package/server/lib/scheduled-tasks/runtime.test.js +48 -0
- package/server/lib/scheduled-tasks/service.js +33 -11
- package/server/lib/scheduled-tasks/service.test.js +128 -2
- package/server/lib/scheduled-tasks/session-defaults.js +27 -0
- package/server/lib/scheduled-tasks/session-defaults.test.js +19 -0
- package/server/lib/session-assist/context.js +1 -0
- package/server/lib/session-folders/routes.js +2 -2
- package/server/lib/session-goal/objectives.js +1 -1
- package/server/lib/session-knowledge/routes.js +2 -2
- package/server/lib/session-knowledge/runtime.js +1 -2
- package/server/lib/session-work/DOCUMENTATION.md +39 -14
- package/server/lib/session-work/questions.js +31 -2
- package/server/lib/session-work/runtime.js +22 -13
- package/server/lib/session-work/runtime.test.js +43 -26
- package/server/lib/session-work/state.js +10 -4
- package/server/lib/session-work/state.test.js +8 -1
- package/server/lib/shared/DOCUMENTATION.md +89 -0
- package/server/lib/shared/guards.js +53 -0
- package/server/lib/shared/guards.test.js +112 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +10 -8
- package/server/lib/skills-catalog/cache.js +3 -2
- package/server/lib/skills-catalog/curated-sources.js +5 -1
- package/server/lib/skills-catalog/git.js +107 -20
- package/server/lib/skills-catalog/git.test.js +78 -0
- package/server/lib/skills-catalog/install.js +21 -12
- package/server/lib/skills-catalog/scan.js +25 -15
- package/server/lib/skills-catalog/source.js +26 -1
- package/server/lib/skills-catalog/source.test.js +27 -0
- package/server/lib/source-control/DOCUMENTATION.md +2 -1
- package/server/lib/source-control/audit-storage.js +7 -6
- package/server/lib/source-control/audit-storage.test.js +11 -0
- package/server/lib/source-control/binding-service.js +2 -5
- package/server/lib/source-control/change-request-worktree.js +90 -0
- package/server/lib/source-control/change-request-worktree.test.js +121 -0
- package/server/lib/source-control/mutation-executor.js +1 -1
- package/server/lib/source-control/mutation-storage.js +11 -5
- package/server/lib/source-control/provider-instance.js +1 -2
- package/server/lib/source-control/repository-identity.js +1 -1
- package/server/lib/source-control/routes.js +14 -2
- package/server/lib/spaces/DOCUMENTATION.md +42 -17
- package/server/lib/spaces/command-stream.test.js +15 -0
- package/server/lib/spaces/dispatcher.js +7 -0
- package/server/lib/spaces/dispatcher.test.js +17 -0
- package/server/lib/spaces/hardening.js +2 -2
- package/server/lib/spaces/hardening.test.js +1 -1
- package/server/lib/spaces/host.js +2 -0
- package/server/lib/spaces/journey.js +66 -5
- package/server/lib/spaces/journey.test.js +95 -4
- package/server/lib/spaces/layout.js +1 -1
- package/server/lib/spaces/places/docker-disk.js +38 -14
- package/server/lib/spaces/places/docker-disk.test.js +80 -1
- package/server/lib/spaces/places/docker.js +35 -11
- package/server/lib/spaces/places/fake-docker.js +22 -3
- package/server/lib/spaces/places/memory-place.js +3 -0
- package/server/lib/spaces/places/packed.docker.live.test.js +1 -1
- package/server/lib/spaces/routes.js +5 -0
- package/server/lib/spaces/routes.test.js +4 -2
- package/server/lib/spaces/run-command.js +9 -2
- package/server/lib/spaces/tools.js +4 -5
- package/server/lib/spaces/tools.test.js +2 -2
- package/server/lib/terminal/runtime.js +18 -6
- package/server/lib/terminal/runtime.test.js +66 -0
- package/server/lib/tts/index.js +3 -0
- package/server/lib/tts/service.js +4 -2
- package/server/lib/walkthrough/DOCUMENTATION.md +3 -1
- package/server/lib/walkthrough/index.js +2 -3
- package/server/lib/walkthrough/pr-source.test.js +22 -0
- package/server/lib/walkthrough/sources.js +6 -1
- package/server/opencode-proxy.test.js +127 -0
- package/bin/lib/commands-startup.test.js +0 -15
- package/dist/assets/CodeMirrorCommentWidgets-CVDSjcrP.js +0 -1
- package/dist/assets/DiffView-Ct7vL3fD.js +0 -4
- package/dist/assets/FilesView-DT0uEYgT.js +0 -17
- package/dist/assets/GuestSurfacePane-DVvk1VPu.js +0 -1
- package/dist/assets/LinearIssuesView-C10MwA9h.js +0 -1
- package/dist/assets/MarkdownImageGallery-BbN_K_3d.js +0 -1
- package/dist/assets/MarkdownRendererImpl-BLt2Tkk9.js +0 -10
- package/dist/assets/OnboardingScreen-Bgp5A3WB.js +0 -1
- package/dist/assets/PierreDiffViewer-CWJLj-S-.js +0 -210
- package/dist/assets/PlanView-DsRWnsOI.js +0 -5
- package/dist/assets/PublishDialog-CCeKloRu.js +0 -2
- package/dist/assets/SettingsView-BuPPsNPp.js +0 -30
- package/dist/assets/SettingsWindow-CM5fL8Ot.js +0 -1
- package/dist/assets/SubmoduleDiffSummary-CfsMcfiQ.js +0 -1
- package/dist/assets/ToolOutputDialog-mZrfSofz.js +0 -15
- package/dist/assets/ToolPartDiffPreview-BUGCRYEL.js +0 -8
- package/dist/assets/WalkthroughView-ClBo7PPd.js +0 -4
- package/dist/assets/appThemeRegistry-CxzMxR-H.js +0 -1
- package/dist/assets/de-CBla4hjd.js +0 -14
- package/dist/assets/es-BmnZtxTD.js +0 -20
- package/dist/assets/fileTreeStatus-BSHFIGEg.js +0 -1
- package/dist/assets/fr-OVKIBUZO.js +0 -18
- package/dist/assets/index-CKCFKmjF.js +0 -1
- package/dist/assets/index-Dbx4V4GN.css +0 -1
- package/dist/assets/index-vQ1Pg1cr.js +0 -61
- package/dist/assets/ja-6v-yr-gE.js +0 -20
- package/dist/assets/ko-I_ZLvgQ9.js +0 -20
- package/dist/assets/main-CmO4dRtR.js +0 -2
- package/dist/assets/main-b9vqhlYM.js +0 -999
- package/dist/assets/markdownCore-BPEZRQfP.js +0 -7
- package/dist/assets/miniChat-B5Oi7B-6.js +0 -2
- package/dist/assets/mobile-CVtShif9.js +0 -2
- package/dist/assets/nl-DcvIJf46.js +0 -20
- package/dist/assets/patchFileDiff-CW3wCH1O.js +0 -13
- package/dist/assets/pl-JxCUl0sd.js +0 -20
- package/dist/assets/pt-BR-L89mjja2.js +0 -20
- package/dist/assets/relocateRestoredSession-DYh_9oBa.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-BD-1JHSg.js +0 -2
- package/dist/assets/renderMobileApp-7s5InXt1.js +0 -25
- package/dist/assets/runtimeEndpointReset-DqLJC6L1.js +0 -53
- package/dist/assets/shikiHighlight-CXgZnvHh.js +0 -1
- package/dist/assets/space-apply-1trGVIl8.js +0 -1
- package/dist/assets/space-close-y7j-NuBv.js +0 -1
- package/dist/assets/tr-ka5J1flt.js +0 -11
- package/dist/assets/uk-PyGe6Q_u.js +0 -20
- package/dist/assets/useInlineCommentController-CrjSGCpi.js +0 -2
- package/dist/assets/vendor-codemirror-lang-python-B7ClrMIT.js +0 -3
- package/dist/assets/vendor-codemirror-lang-xml-C8tGnS64.js +0 -1
- package/dist/assets/vendor-codemirror-language-data-q2bKO1EM.js +0 -2
- package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +0 -1
- package/dist/assets/vendor-katex-CHD9BdDO.js +0 -257
- package/dist/assets/vendor-katex-Cp579UoJ.css +0 -1
- package/dist/assets/zh-CN-B0P2tybJ.js +0 -20
- package/dist/assets/zh-TW-Bs9C5cN8.js +0 -20
|
@@ -350,6 +350,54 @@ describe('scheduled-tasks runtime prompt dispatch', () => {
|
|
|
350
350
|
expect(dispatch[2].body).toMatchObject({ text: 'Review open issues' });
|
|
351
351
|
expect(dispatch[2].body.resume).toBeUndefined();
|
|
352
352
|
});
|
|
353
|
+
|
|
354
|
+
it('points a run that fails after creating its session at that session', async () => {
|
|
355
|
+
vi.stubGlobal('fetch', vi.fn(async (input, init = {}) => {
|
|
356
|
+
const { pathname } = new URL(String(input));
|
|
357
|
+
if (init.method === 'POST' && pathname.endsWith('/prompt')) {
|
|
358
|
+
return new Response(JSON.stringify({ error: 'provider down' }), { status: 500 });
|
|
359
|
+
}
|
|
360
|
+
const data = pathname === '/api/session' ? { id: 'ses_failed' } : pathname === '/api/command' ? [] : {};
|
|
361
|
+
return new Response(JSON.stringify({ location: { directory: '/repo' }, data }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
362
|
+
}));
|
|
363
|
+
const task = {
|
|
364
|
+
id: 'task-1',
|
|
365
|
+
name: 'Nightly',
|
|
366
|
+
enabled: true,
|
|
367
|
+
schedule: { kind: 'daily', times: ['03:00'], timezone: 'UTC' },
|
|
368
|
+
execution: { prompt: 'Review open issues', providerID: 'openai', modelID: 'gpt-5' },
|
|
369
|
+
state: { createdAt: 1, updatedAt: 1, lastSessionId: 'ses_previous' },
|
|
370
|
+
};
|
|
371
|
+
const patches = [];
|
|
372
|
+
const runtime = createScheduledTasksRuntime({
|
|
373
|
+
projectConfigRuntime: {
|
|
374
|
+
listScheduledTasks: async () => [task],
|
|
375
|
+
reconcileLoopTasks: async () => [task],
|
|
376
|
+
updateScheduledTaskState: async (_projectID, _taskID, patch) => {
|
|
377
|
+
patches.push(patch);
|
|
378
|
+
return { task, updated: true };
|
|
379
|
+
},
|
|
380
|
+
updateScheduledTaskStateIf: async () => ({ task, updated: true }),
|
|
381
|
+
},
|
|
382
|
+
listProjects: async () => [{ id: 'proj', path: '/repo' }],
|
|
383
|
+
buildOpenCodeUrl: () => 'http://127.0.0.1:1/',
|
|
384
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
385
|
+
waitForOpenCodeReady: async () => {},
|
|
386
|
+
logger: { info: () => {}, warn: () => {}, error: () => {} },
|
|
387
|
+
});
|
|
388
|
+
await runtime.start();
|
|
389
|
+
const result = await runtime.runNow('proj', 'task-1');
|
|
390
|
+
runtime.stop();
|
|
391
|
+
|
|
392
|
+
expect(result.ok).toBe(false);
|
|
393
|
+
// The start clears the previous run's session, creation records this one,
|
|
394
|
+
// and the failure keeps it.
|
|
395
|
+
const startIndex = patches.findIndex((patch) => patch.lastStatus === 'running');
|
|
396
|
+
expect(Object.hasOwn(patches[startIndex], 'lastSessionId')).toBe(true);
|
|
397
|
+
expect(patches[startIndex].lastSessionId).toBeUndefined();
|
|
398
|
+
expect(patches[startIndex + 1]).toEqual({ lastSessionId: 'ses_failed' });
|
|
399
|
+
expect(patches.at(-1)).toMatchObject({ lastStatus: 'error', lastSessionId: 'ses_failed' });
|
|
400
|
+
});
|
|
353
401
|
});
|
|
354
402
|
|
|
355
403
|
describe('scheduled-tasks runtime chats scope', () => {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { asNonEmptyString } from '../shared/guards.js';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { OpenChamberControlError } from '../openchamber-control/error.js';
|
|
4
|
-
import { loopFingerprint, parseLoopDefinition, setLoopFileEnabled } from './loops.js';
|
|
5
|
+
import { discoverLoopFiles, loopFingerprint, parseLoopDefinition, setLoopFileEnabled } from './loops.js';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
7
|
+
// Same scope rule the scheduler applies: a file under the project's own
|
|
8
|
+
// `.agents/loops` is a repository loop, even where it is also the user folder.
|
|
9
|
+
const isRepositoryLoop = (projectPath, loopFile) => discoverLoopFiles(projectPath)
|
|
10
|
+
.some((entry) => entry.scope === 'project' && entry.filePath === loopFile);
|
|
11
11
|
|
|
12
12
|
export const createScheduledTaskService = (dependencies) => {
|
|
13
13
|
const {
|
|
@@ -111,6 +111,10 @@ export const createScheduledTaskService = (dependencies) => {
|
|
|
111
111
|
throw new OpenChamberControlError('task payload is required', 400);
|
|
112
112
|
}
|
|
113
113
|
let upserted;
|
|
114
|
+
const previous = taskInput.id ? (await projectConfigRuntime.listScheduledTasks(id)).find((task) => task.id === taskInput.id) : null;
|
|
115
|
+
if (taskInput.targetSessionId && (taskInput.enabled !== false || previous?.targetSessionId !== taskInput.targetSessionId)) {
|
|
116
|
+
await scheduledTasksRuntime.validateTarget(id, taskInput.targetSessionId);
|
|
117
|
+
}
|
|
114
118
|
try {
|
|
115
119
|
upserted = await projectConfigRuntime.upsertScheduledTask(id, taskInput);
|
|
116
120
|
} catch (error) {
|
|
@@ -118,6 +122,9 @@ export const createScheduledTaskService = (dependencies) => {
|
|
|
118
122
|
const invalid = message.toLowerCase().includes('required') || message.toLowerCase().includes('invalid');
|
|
119
123
|
throw new OpenChamberControlError(message, invalid ? 400 : 500);
|
|
120
124
|
}
|
|
125
|
+
if (previous?.targetSessionId && ((previous.enabled && upserted.task.enabled === false) || previous.targetSessionId !== upserted.task.targetSessionId)) {
|
|
126
|
+
await scheduledTasksRuntime.cancelWaitingPrompt(id, taskInput.id);
|
|
127
|
+
}
|
|
121
128
|
await scheduledTasksRuntime.syncProject(id);
|
|
122
129
|
const tasks = await projectConfigRuntime.listScheduledTasks(id);
|
|
123
130
|
return {
|
|
@@ -146,6 +153,7 @@ export const createScheduledTaskService = (dependencies) => {
|
|
|
146
153
|
}
|
|
147
154
|
const result = await projectConfigRuntime.deleteScheduledTask(id, normalizedTaskID);
|
|
148
155
|
if (!result.deleted) throw new OpenChamberControlError('Task not found', 404);
|
|
156
|
+
await scheduledTasksRuntime.cancelWaitingPrompt?.(id, normalizedTaskID);
|
|
149
157
|
await scheduledTasksRuntime.syncProject(id);
|
|
150
158
|
return projectConfigRuntime.listScheduledTasks(id);
|
|
151
159
|
};
|
|
@@ -156,11 +164,11 @@ export const createScheduledTaskService = (dependencies) => {
|
|
|
156
164
|
if (!normalizedTaskID) throw new OpenChamberControlError('taskId is required', 400);
|
|
157
165
|
const result = await scheduledTasksRuntime.runNow(id, normalizedTaskID);
|
|
158
166
|
if (result.running || result.queued) {
|
|
159
|
-
throw new OpenChamberControlError(result.error || 'Task already running', 409);
|
|
167
|
+
throw new OpenChamberControlError(result.error || 'Task already running', 409, { busy: result.queued ? 'queued' : 'running' });
|
|
160
168
|
}
|
|
161
169
|
if (result.skipped) throw new OpenChamberControlError('Task not found or disabled', 404);
|
|
162
170
|
if (!result.ok) {
|
|
163
|
-
throw new OpenChamberControlError(result.error || 'Task run failed', 500, { task: result.task });
|
|
171
|
+
throw new OpenChamberControlError(result.error || 'Task run failed', result.statusCode || 500, { task: result.task });
|
|
164
172
|
}
|
|
165
173
|
const response = {
|
|
166
174
|
task: result.task,
|
|
@@ -173,12 +181,26 @@ export const createScheduledTaskService = (dependencies) => {
|
|
|
173
181
|
return response;
|
|
174
182
|
};
|
|
175
183
|
|
|
184
|
+
// The agent tool and the CLI. A loop task follows its file, so it changes
|
|
185
|
+
// the way the Scheduled tasks checkbox does. A repository loop is the
|
|
186
|
+
// exception for enabling: that is the user's own decision on this machine,
|
|
187
|
+
// made in Scheduled tasks, never by an agent or a script on their behalf.
|
|
176
188
|
const setEnabled = async (projectID, taskID, enabled) => {
|
|
177
|
-
const
|
|
189
|
+
const project = await findProjectByID(projectID);
|
|
190
|
+
const tasks = await scheduledTasksRuntime.syncProject(project.id);
|
|
178
191
|
const task = tasks.find((entry) => entry?.id === taskID);
|
|
179
192
|
if (!task) throw new OpenChamberControlError('Task not found', 404);
|
|
180
|
-
|
|
181
|
-
|
|
193
|
+
if (!task.loopFile) {
|
|
194
|
+
const result = await upsert(project.id, { ...task, enabled });
|
|
195
|
+
return result.task;
|
|
196
|
+
}
|
|
197
|
+
// Nothing to change: the loop file, which may be tracked in the
|
|
198
|
+
// repository, and the approval stay untouched.
|
|
199
|
+
if (enabled === task.enabled) return task;
|
|
200
|
+
if (enabled && isRepositoryLoop(project.path, task.loopFile)) {
|
|
201
|
+
throw new OpenChamberControlError(`"${task.name}" comes from this repository. Enable it in Scheduled tasks.`, 409);
|
|
202
|
+
}
|
|
203
|
+
return setLoopEnabled(project.id, task.id, enabled);
|
|
182
204
|
};
|
|
183
205
|
|
|
184
206
|
const status = async () => {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { mkdtemp, readFile, rm, writeFile } from 'fs/promises';
|
|
4
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'fs/promises';
|
|
5
5
|
import { createScheduledTaskService } from './service.js';
|
|
6
6
|
import { registerScheduledTaskRoutes } from './routes.js';
|
|
7
7
|
import { CHATS_SCOPE_PUBLIC_ID, createChatsScope } from './chats-scope.js';
|
|
8
|
+
import { createScheduledTasksRuntime } from './runtime.js';
|
|
9
|
+
import { createProjectConfigRuntime } from '../projects/project-config.js';
|
|
8
10
|
|
|
9
11
|
const createService = (overrides = {}) => {
|
|
10
12
|
const projectConfigRuntime = {
|
|
@@ -349,3 +351,127 @@ Run the digest.
|
|
|
349
351
|
}
|
|
350
352
|
});
|
|
351
353
|
});
|
|
354
|
+
|
|
355
|
+
// The agent tool and the CLI enable and disable through setEnabled; these run
|
|
356
|
+
// it against the real project config and scheduler, as the server does.
|
|
357
|
+
describe('scheduled-task service setEnabled on loop tasks', () => {
|
|
358
|
+
const loopFileContent = (name, enabled, prompt = `Run ${name}.`) => `---
|
|
359
|
+
name: ${name}
|
|
360
|
+
schedule: "0 9 * * *"
|
|
361
|
+
enabled: ${enabled}
|
|
362
|
+
model: openai/gpt-5
|
|
363
|
+
---
|
|
364
|
+
${prompt}
|
|
365
|
+
`;
|
|
366
|
+
|
|
367
|
+
let tempRoot;
|
|
368
|
+
let repoPath;
|
|
369
|
+
let repoLoop;
|
|
370
|
+
let userLoop;
|
|
371
|
+
let service;
|
|
372
|
+
let projectConfigRuntime;
|
|
373
|
+
const savedHome = { HOME: process.env.HOME, USERPROFILE: process.env.USERPROFILE };
|
|
374
|
+
|
|
375
|
+
beforeEach(async () => {
|
|
376
|
+
tempRoot = await mkdtemp(path.join(os.tmpdir(), 'oc-loop-set-enabled-'));
|
|
377
|
+
// User-scope loops live under the home directory.
|
|
378
|
+
const home = path.join(tempRoot, 'home');
|
|
379
|
+
process.env.HOME = home;
|
|
380
|
+
process.env.USERPROFILE = home;
|
|
381
|
+
repoPath = path.join(tempRoot, 'repo');
|
|
382
|
+
await mkdir(path.join(repoPath, '.agents', 'loops'), { recursive: true });
|
|
383
|
+
await mkdir(path.join(home, '.agents', 'loops'), { recursive: true });
|
|
384
|
+
repoLoop = path.join(repoPath, '.agents', 'loops', 'repo.md');
|
|
385
|
+
userLoop = path.join(home, '.agents', 'loops', 'mine.md');
|
|
386
|
+
await writeFile(repoLoop, loopFileContent('repo-loop', true), 'utf8');
|
|
387
|
+
await writeFile(userLoop, loopFileContent('my-loop', false), 'utf8');
|
|
388
|
+
|
|
389
|
+
projectConfigRuntime = createProjectConfigRuntime({
|
|
390
|
+
fsPromises: await import('fs/promises'),
|
|
391
|
+
path,
|
|
392
|
+
projectsDirPath: path.join(tempRoot, 'config'),
|
|
393
|
+
createTaskID: () => 'task-fixed-id',
|
|
394
|
+
});
|
|
395
|
+
const listProjects = async () => [{ id: 'proj', path: repoPath }];
|
|
396
|
+
const scheduledTasksRuntime = createScheduledTasksRuntime({
|
|
397
|
+
buildOpenCodeUrl: () => 'http://localhost',
|
|
398
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
399
|
+
waitForOpenCodeReady: async () => {},
|
|
400
|
+
projectConfigRuntime,
|
|
401
|
+
listProjects,
|
|
402
|
+
});
|
|
403
|
+
service = createScheduledTaskService({
|
|
404
|
+
readSettingsFromDiskMigrated: async () => ({ projects: await listProjects() }),
|
|
405
|
+
sanitizeProjects: (projects) => projects,
|
|
406
|
+
projectConfigRuntime,
|
|
407
|
+
scheduledTasksRuntime,
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
afterEach(async () => {
|
|
412
|
+
for (const [key, value] of Object.entries(savedHome)) {
|
|
413
|
+
if (value === undefined) delete process.env[key];
|
|
414
|
+
else process.env[key] = value;
|
|
415
|
+
}
|
|
416
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const stored = async (taskID) => (await projectConfigRuntime.listScheduledTasks('proj')).find((task) => task.id === taskID);
|
|
420
|
+
const listed = async (taskID) => (await service.list('proj')).find((task) => task.id === taskID);
|
|
421
|
+
|
|
422
|
+
it('refuses to enable a repository loop and changes nothing', async () => {
|
|
423
|
+
expect(await listed('loop:project:repo-loop')).toMatchObject({ enabled: false, loopApproval: 'required' });
|
|
424
|
+
|
|
425
|
+
await expect(service.setEnabled('proj', 'loop:project:repo-loop', true)).rejects.toMatchObject({
|
|
426
|
+
statusCode: 409,
|
|
427
|
+
message: '"repo-loop" comes from this repository. Enable it in Scheduled tasks.',
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
expect((await stored('loop:project:repo-loop')).enabled).toBe(false);
|
|
431
|
+
expect(await readFile(repoLoop, 'utf8')).toBe(loopFileContent('repo-loop', true));
|
|
432
|
+
expect(await listed('loop:project:repo-loop')).toMatchObject({ enabled: false, loopApproval: 'required' });
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('leaves a held-back repository loop file alone when asked to disable it', async () => {
|
|
436
|
+
await expect(service.setEnabled('proj', 'loop:project:repo-loop', false)).resolves.toMatchObject({ enabled: false });
|
|
437
|
+
|
|
438
|
+
expect(await readFile(repoLoop, 'utf8')).toBe(loopFileContent('repo-loop', true));
|
|
439
|
+
expect(await listed('loop:project:repo-loop')).toMatchObject({ enabled: false, loopApproval: 'required' });
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('disables a repository loop the user enabled, and it stays disabled', async () => {
|
|
443
|
+
await service.setLoopEnabled('proj', 'loop:project:repo-loop', true);
|
|
444
|
+
const enabled = await listed('loop:project:repo-loop');
|
|
445
|
+
expect(enabled.enabled).toBe(true);
|
|
446
|
+
expect(enabled.loopApproval).toBeUndefined();
|
|
447
|
+
|
|
448
|
+
await expect(service.setEnabled('proj', 'loop:project:repo-loop', false)).resolves.toMatchObject({ enabled: false });
|
|
449
|
+
|
|
450
|
+
const disabled = await listed('loop:project:repo-loop');
|
|
451
|
+
expect(disabled.enabled).toBe(false);
|
|
452
|
+
// Paused by the user: nothing to explain on the card.
|
|
453
|
+
expect(disabled.loopApproval).toBeUndefined();
|
|
454
|
+
expect(await readFile(repoLoop, 'utf8')).toContain('enabled: false');
|
|
455
|
+
// And the agent cannot turn it back on.
|
|
456
|
+
await expect(service.setEnabled('proj', 'loop:project:repo-loop', true)).rejects.toMatchObject({ statusCode: 409 });
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('says a repository loop changed since the user enabled it', async () => {
|
|
460
|
+
await service.setLoopEnabled('proj', 'loop:project:repo-loop', true);
|
|
461
|
+
await writeFile(repoLoop, loopFileContent('repo-loop', true, 'Run something else.'), 'utf8');
|
|
462
|
+
|
|
463
|
+
expect(await listed('loop:project:repo-loop')).toMatchObject({ enabled: false, loopApproval: 'outdated' });
|
|
464
|
+
expect((await stored('loop:project:repo-loop')).loopApproval).toBeUndefined();
|
|
465
|
+
await expect(service.setEnabled('proj', 'loop:project:repo-loop', true)).rejects.toMatchObject({ statusCode: 409 });
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('enables and disables a loop from the user folder', async () => {
|
|
469
|
+
await expect(service.setEnabled('proj', 'loop:user:my-loop', true)).resolves.toMatchObject({ enabled: true });
|
|
470
|
+
expect((await stored('loop:user:my-loop')).enabled).toBe(true);
|
|
471
|
+
expect(await readFile(userLoop, 'utf8')).toContain('enabled: true');
|
|
472
|
+
|
|
473
|
+
await expect(service.setEnabled('proj', 'loop:user:my-loop', false)).resolves.toMatchObject({ enabled: false });
|
|
474
|
+
expect((await stored('loop:user:my-loop')).enabled).toBe(false);
|
|
475
|
+
expect(await readFile(userLoop, 'utf8')).toContain('enabled: false');
|
|
476
|
+
});
|
|
477
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model, thinking level and agent a new session starts with, as Settings
|
|
3
|
+
* and the project define them: the project's own default first, then the
|
|
4
|
+
* global one. A thinking level travels with the model it was set next to.
|
|
5
|
+
* Anything unset is null, and OpenCode then applies its own default.
|
|
6
|
+
*/
|
|
7
|
+
const splitModel = (value) => {
|
|
8
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
9
|
+
const slash = text.indexOf('/');
|
|
10
|
+
if (slash <= 0 || slash === text.length - 1) return null;
|
|
11
|
+
return { providerID: text.slice(0, slash), modelID: text.slice(slash + 1) };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const text = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
|
|
15
|
+
|
|
16
|
+
export const resolveSessionDefaults = ({ settings, project }) => {
|
|
17
|
+
const projectModel = splitModel(project?.defaultModel);
|
|
18
|
+
const globalModel = splitModel(settings?.defaultModel);
|
|
19
|
+
const model = projectModel ?? globalModel;
|
|
20
|
+
const variant = projectModel ? text(project?.defaultVariant) : globalModel ? text(settings?.defaultVariant) : null;
|
|
21
|
+
return {
|
|
22
|
+
providerID: model?.providerID ?? null,
|
|
23
|
+
modelID: model?.modelID ?? null,
|
|
24
|
+
variant,
|
|
25
|
+
agent: text(project?.defaultAgent) ?? text(settings?.defaultAgent),
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { resolveSessionDefaults } from './session-defaults.js';
|
|
4
|
+
|
|
5
|
+
describe('resolveSessionDefaults', () => {
|
|
6
|
+
it('prefers the project default, then the global one, keeping each variant with its model', () => {
|
|
7
|
+
const settings = { defaultModel: 'anthropic/claude-sonnet-5', defaultVariant: 'high', defaultAgent: 'build' };
|
|
8
|
+
expect(resolveSessionDefaults({ settings, project: { defaultModel: 'openai/gpt-6', defaultAgent: 'plan' } }))
|
|
9
|
+
.toEqual({ providerID: 'openai', modelID: 'gpt-6', variant: null, agent: 'plan' });
|
|
10
|
+
expect(resolveSessionDefaults({ settings, project: {} }))
|
|
11
|
+
.toEqual({ providerID: 'anthropic', modelID: 'claude-sonnet-5', variant: 'high', agent: 'build' });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('leaves everything to OpenCode when nothing is set', () => {
|
|
15
|
+
expect(resolveSessionDefaults({ settings: {}, project: null }))
|
|
16
|
+
.toEqual({ providerID: null, modelID: null, variant: null, agent: null });
|
|
17
|
+
expect(resolveSessionDefaults({ settings: { defaultModel: 'no-slash' }, project: null }).modelID).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -111,6 +111,7 @@ function readMessage(message) {
|
|
|
111
111
|
id: message.id,
|
|
112
112
|
role,
|
|
113
113
|
created: Number.isFinite(message.time?.created) ? message.time.created : null,
|
|
114
|
+
completed: Number.isFinite(message.time?.completed) ? message.time.completed : null,
|
|
114
115
|
transparent: isTransparent(message),
|
|
115
116
|
// v2 has no `parentID` on a message: a turn is the run of messages between
|
|
116
117
|
// one user message and the assistant reply that follows it, which is what
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { isRecord as isObjectRecord } from '../shared/guards.js';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
4
4
|
|
|
5
5
|
const hasValidFolderShape = (folder) => (
|
|
6
6
|
isObjectRecord(folder)
|
|
@@ -24,7 +24,7 @@ const goalsDir = () => path.join(
|
|
|
24
24
|
|
|
25
25
|
const objectiveFilePath = (sessionId) => path.join(goalsDir(), `${sessionId}.md`);
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
const isValidObjectiveKey = (sessionId) =>
|
|
28
28
|
typeof sessionId === 'string' && SESSION_ID_PATTERN.test(sessionId);
|
|
29
29
|
|
|
30
30
|
const clampContent = (content) => String(content ?? '').trim().slice(0, GOAL_OBJECTIVE_CHAR_LIMIT);
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
import express from 'express';
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
import { isRecord } from '../shared/guards.js';
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const parseJsonBody = express.json({ limit: '1mb' });
|
|
19
19
|
|
|
20
20
|
const asNonEmptyString = (value) => (
|
|
21
21
|
typeof value === 'string' && value.trim().length > 0 ? value.trim() : ''
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* it survives compaction: the tab goes on believing the agent still has context
|
|
15
15
|
* that has just been summarised away.
|
|
16
16
|
*/
|
|
17
|
+
import { isRecord } from '../shared/guards.js';
|
|
17
18
|
|
|
18
19
|
const KNOWLEDGE_METADATA_KEY = 'knowledge_context_delivered';
|
|
19
20
|
const PINS_METADATA_KEY = 'project_context_pins';
|
|
@@ -21,8 +22,6 @@ const PINS_METADATA_KEY = 'project_context_pins';
|
|
|
21
22
|
/** Total budget for the assembled block; anything past it is cut, loudly. */
|
|
22
23
|
const KNOWLEDGE_MAX_LENGTH = 8000;
|
|
23
24
|
|
|
24
|
-
const isRecord = (value) => Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
25
|
-
|
|
26
25
|
const truncate = (value, budget) => (
|
|
27
26
|
value.length <= budget ? value : `${value.slice(0, Math.max(0, budget - 1))}…`
|
|
28
27
|
);
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
Keeps sessions where real work is going on in one block at the top of the
|
|
6
6
|
sidebar until the user marks them done. Jev moves a session in when work
|
|
7
|
-
starts in it; the user alone closes it.
|
|
8
|
-
|
|
7
|
+
starts in it; the user alone closes it. While a session is in work, Jev also
|
|
8
|
+
says after a turn whether the work looks done. Independently of work, the
|
|
9
|
+
same call says whether the turn handed over changes worth a look (the
|
|
10
|
+
composer then offers an AI review or a walkthrough). The same turn-end Jev call tells session assist whether the
|
|
11
|
+
Small Model is worth waking.
|
|
9
12
|
|
|
10
13
|
## Files
|
|
11
14
|
|
|
@@ -25,6 +28,10 @@ session assist whether the Small Model is worth waking.
|
|
|
25
28
|
- `doneAt` once the user closed it
|
|
26
29
|
- `suggestDoneAt` while Jev thinks the last turn closed the work out
|
|
27
30
|
|
|
31
|
+
`metadata.openchamber.reviewOffer`: `{ at }` while Jev thinks the last turn
|
|
32
|
+
handed over changes the user could look over. It lives outside `work` so it
|
|
33
|
+
needs neither the In work block nor a session in work.
|
|
34
|
+
|
|
28
35
|
The UI parser is `packages/ui/src/lib/sessionWorkMetadata.ts`; the user's own
|
|
29
36
|
Track / Done go through the generic metadata route
|
|
30
37
|
(`setSessionWorkState` in `packages/ui/src/sync/session-actions.ts`), which
|
|
@@ -32,7 +39,6 @@ also exists in VS Code.
|
|
|
32
39
|
|
|
33
40
|
## Invariants
|
|
34
41
|
|
|
35
|
-
- Jev only evaluates sessions adopted by OpenChamber (`metadata.openchamber.adopted === true`). Sessions seen only through the shared local OpenCode service remain TUI-owned; viewing one does not send its content to Jev. Creating a session or sending into it from an OpenChamber surface persists adoption first.
|
|
36
42
|
- Jev only opens. Nothing here ever writes `done`.
|
|
37
43
|
- Jev reopens a closed session only for a request sent after the user closed
|
|
38
44
|
it (`openByJevPatch` compares the request's `time.created` with `doneAt`).
|
|
@@ -40,13 +46,14 @@ also exists in VS Code.
|
|
|
40
46
|
store's per-session write queue, so a user's Done written a moment earlier
|
|
41
47
|
is seen and kept.
|
|
42
48
|
- A turn-end check is bound to its turn: every turn start and new user
|
|
43
|
-
message advances a per-session counter, and
|
|
49
|
+
message advances a per-session counter, and a hint is written only if
|
|
44
50
|
the counter is unchanged when the write runs (decided inside the store's
|
|
45
51
|
queue). A late Jev answer about a turn the user already moved past writes
|
|
46
52
|
nothing.
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
- A turn writes at most one hint: looks done wins over the review offer.
|
|
54
|
+
- A hint is current while its time `>= session.time.idle` (the assist's
|
|
55
|
+
freshness rule). This runtime also deletes a hint it wrote when the next
|
|
56
|
+
turn starts; the rule covers hints written by another process.
|
|
50
57
|
- Only top-level, unarchived project sessions that are not review sessions
|
|
51
58
|
(`openchamber.kind === 'review'`) are asked about. Managed Chats (under the
|
|
52
59
|
`chatRoots` the server passes in) are plain conversations and never in work.
|
|
@@ -66,8 +73,10 @@ also exists in VS Code.
|
|
|
66
73
|
message id is asked about once.
|
|
67
74
|
- A turn ended: session assist calls `evaluateTurnEnd` before it arms its quiet
|
|
68
75
|
window. One call carries only the questions that apply: the open questions
|
|
69
|
-
while not in work, `wrap_up` while in work, `recap` / `next_step` for the
|
|
70
|
-
|
|
76
|
+
while not in work, `wrap_up` while in work, `review_ready` while the review offer is on, `recap` / `next_step` for the assist fields
|
|
77
|
+
the user has on. No applicable question, no call.
|
|
78
|
+
- Jev reads the request's first 1500 characters, the answer's first and last
|
|
79
|
+
500, and three earlier turns cut as routing cuts them.
|
|
71
80
|
|
|
72
81
|
## Measurement
|
|
73
82
|
|
|
@@ -82,27 +91,43 @@ across 106 work sessions, 22 of them followed by more edits, and caught 53 of
|
|
|
82
91
|
81 shipping steps. At 0.85 the same wording hinted 70 times (18) and missed a
|
|
83
92
|
closing turn whose answer asked the user for a last check (0.82); the earlier
|
|
84
93
|
request-only wording at 0.85 hinted 60 times (16) and caught 33.
|
|
94
|
+
Growing the answer excerpt from 300+300 to 500+500 left `wrap_up` where it
|
|
95
|
+
was (80 hints, 22 followed by edits; 53 of 77 shipping steps).
|
|
96
|
+
`review_ready >= 0.7` was set on the 156 sessions and checked on the 190,
|
|
97
|
+
against whether the turn ran an edit tool (Jev sees no tools): with looks
|
|
98
|
+
done winning it hinted after 271 of 362 edit turns and after 14 of 823 turns
|
|
99
|
+
without edits. The lab is `julia-lab/jev-work` (`run-review.mjs`,
|
|
100
|
+
`score-held.mjs`) under `~/projects/openchamber-extensions`.
|
|
85
101
|
`recap` stayed high (0.86–0.96) after a closing "thanks"/"commit" that followed
|
|
86
102
|
real work, because it reads the same three turns the recap does. Do not change
|
|
87
103
|
the wording or thresholds without re-running this measurement.
|
|
88
104
|
|
|
89
105
|
## Settings
|
|
90
106
|
|
|
91
|
-
`sessionWorkEnabled
|
|
92
|
-
`sessionWorkAutoOpen
|
|
93
|
-
and inert without a classification provider
|
|
107
|
+
- `sessionWorkEnabled`: the block and the actions, default on.
|
|
108
|
+
- `sessionWorkAutoOpen`: Jev opens sessions, default on; not offered in VS
|
|
109
|
+
Code, and inert without a classification provider.
|
|
110
|
+
- `sessionReviewOfferEnabled`: the review offer, default off (Settings → Chat →
|
|
111
|
+
Session Assistance); not offered in VS Code, inert without a classification
|
|
112
|
+
provider, independent of the other two.
|
|
113
|
+
- `sessionWorkKeepInGroup`: a tracked session also stays under its project
|
|
114
|
+
group and folders, default off; Recent, Timeline, and Chats still place it
|
|
115
|
+
once.
|
|
116
|
+
|
|
117
|
+
All four are read at every use.
|
|
94
118
|
|
|
95
119
|
## Runtime parity
|
|
96
120
|
|
|
97
121
|
- Web, desktop, hosted mobile, Capacitor: all of it; the server owns Jev.
|
|
98
|
-
- VS Code: no OpenChamber server, so no Jev and no
|
|
122
|
+
- VS Code: no OpenChamber server, so no Jev, no done hint and no review offer. The block and
|
|
99
123
|
Track / Done work through the extension's metadata bridge.
|
|
100
124
|
|
|
101
125
|
## Tests
|
|
102
126
|
|
|
103
127
|
`state.test.js` (patches, reopen rule, hint lifecycle), `runtime.test.js`
|
|
104
128
|
(send-time open, no repeat asks, ineligible sessions, the combined turn-end
|
|
105
|
-
call,
|
|
129
|
+
call, the review offer with and without In work and looks done winning over
|
|
130
|
+
it, a late answer after the next turn started, never closing, failure as
|
|
106
131
|
unknown), plus the gate cases in
|
|
107
132
|
`../session-assist/runtime.test.js`, `loadSettledTurns` in
|
|
108
133
|
`../session-assist/context.test.js`, and `updateSessionMetadata` in
|
|
@@ -36,10 +36,31 @@ const TOWARD_CHANGE = {
|
|
|
36
36
|
* held-out 70 hints in 106 work sessions (18 followed by more edits) against
|
|
37
37
|
* 60 (16), and 47 of 81 shipping steps hinted against 33. The threshold then
|
|
38
38
|
* went from 0.85 to 0.8: 80 hints (22), 53 of 81 shipping steps, so a closing
|
|
39
|
-
* turn whose answer hands the user a last check still gets the hint.
|
|
39
|
+
* turn whose answer hands the user a last check still gets the hint. The
|
|
40
|
+
* answer excerpt then grew from 300+300 to 500+500 characters for
|
|
41
|
+
* `review_ready`; re-run with the production request cut, wrap-up held: 80
|
|
42
|
+
* hints (22) against 81 (24), 53 of 77 shipping steps either way.
|
|
40
43
|
*/
|
|
41
44
|
const WRAP_UP = 'Does this turn close out the work in this conversation: in `request` the user confirms it works or is good, thanks the agent, or asks to commit, push, merge, sync, or release it, without asking for any further change, and `answer` reports that step done?';
|
|
42
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Asked with `wrap_up`: the turn handed over changes the user could look over
|
|
48
|
+
* (the composer then offers an AI review or a walkthrough). Measured against
|
|
49
|
+
* whether the turn ran an edit tool, which Jev never sees: on 190 held-out
|
|
50
|
+
* sessions at 0.7, with a looks-done hint taking precedence, it hinted after
|
|
51
|
+
* 271 of 362 edit turns and after 14 of 823 turns without edits. Two other
|
|
52
|
+
* wordings (handed over as done; no criteria) hinted 4-7x more on turns
|
|
53
|
+
* without edits. Most false hints are cleanups outside the code (deleted
|
|
54
|
+
* builds); most misses say "did not change the app" after scratch edits.
|
|
55
|
+
*/
|
|
56
|
+
const REVIEW_READY = {
|
|
57
|
+
question: 'Did the agent change the project in this turn, so there are new changes the user could look over now?',
|
|
58
|
+
criteria: {
|
|
59
|
+
true: '`answer` reports that the agent itself edited the project in this turn: implemented, fixed, added, removed, refactored, or rewrote code, UI, docs, tests, or configuration.',
|
|
60
|
+
false: 'The agent only explained, answered a question, investigated, reviewed code or a PR, proposed or planned a change without making it, drafted a message, ran commands to show output, or committed, pushed, or merged changes made earlier; or it failed before changing anything.',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
43
64
|
const RECAP = {
|
|
44
65
|
question: 'Is there substantive work or a finding in this conversation worth a one-line reminder later: something the agent changed, fixed, found out, or a decision that was reached?',
|
|
45
66
|
criteria: {
|
|
@@ -59,6 +80,7 @@ const NEXT_STEP = {
|
|
|
59
80
|
/** Open when either passes (measured together). */
|
|
60
81
|
const OPEN_THRESHOLDS = { change: 0.85, towardChange: 0.9 };
|
|
61
82
|
const WRAP_UP_THRESHOLD = 0.8;
|
|
83
|
+
const REVIEW_READY_THRESHOLD = 0.7;
|
|
62
84
|
/** The recap is skipped only when Jev is nearly sure there is nothing to remind. */
|
|
63
85
|
const RECAP_SKIP_BELOW = 0.3;
|
|
64
86
|
const NEXT_STEP_THRESHOLD = 0.5;
|
|
@@ -81,7 +103,8 @@ export const buildSendRequest = ({ history, request }) => ({
|
|
|
81
103
|
/**
|
|
82
104
|
* The request Jev reads when a turn ended, or null when nothing is asked.
|
|
83
105
|
* `ask` names the groups: `open` (the session is not in work), `wrapUp` (it
|
|
84
|
-
* is), `recap` and `nextStep` (the
|
|
106
|
+
* is), `reviewReady` (the review offer is on), `recap` and `nextStep` (the
|
|
107
|
+
* assist fields the user has on).
|
|
85
108
|
*/
|
|
86
109
|
export const buildTurnEndRequest = ({ history, request, answer, ask }) => {
|
|
87
110
|
const questions = {};
|
|
@@ -90,6 +113,7 @@ export const buildTurnEndRequest = ({ history, request, answer, ask }) => {
|
|
|
90
113
|
questions.toward_change = noul(TURN_END_CONTEXT, TOWARD_CHANGE);
|
|
91
114
|
}
|
|
92
115
|
if (ask.wrapUp) questions.wrap_up = noul(TURN_END_CONTEXT, { question: WRAP_UP });
|
|
116
|
+
if (ask.reviewReady) questions.review_ready = noul(TURN_END_CONTEXT, REVIEW_READY);
|
|
93
117
|
if (ask.recap) questions.recap = noul(TURN_END_CONTEXT, RECAP);
|
|
94
118
|
if (ask.nextStep) questions.next_step = noul(TURN_END_CONTEXT, NEXT_STEP);
|
|
95
119
|
if (Object.keys(questions).length === 0) return null;
|
|
@@ -112,6 +136,11 @@ export const decideWrapUp = (answers) => {
|
|
|
112
136
|
return score !== null && score >= WRAP_UP_THRESHOLD;
|
|
113
137
|
};
|
|
114
138
|
|
|
139
|
+
export const decideReviewReady = (answers) => {
|
|
140
|
+
const score = answerOf(answers, 'review_ready');
|
|
141
|
+
return score !== null && score >= REVIEW_READY_THRESHOLD;
|
|
142
|
+
};
|
|
143
|
+
|
|
115
144
|
/**
|
|
116
145
|
* Which assist fields are worth the Small Model. A field Jev did not answer
|
|
117
146
|
* stays allowed: an unanswered question must not silence the assist.
|