@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
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { INJECTED_ENV_KEY, assignInjectedEnv, injectedEnvKeys } from './injected-env.js';
|
|
4
|
+
|
|
5
|
+
describe('assignInjectedEnv', () => {
|
|
6
|
+
it('sets the values and records their names', () => {
|
|
7
|
+
const env = { PATH: '/usr/bin' };
|
|
8
|
+
|
|
9
|
+
expect(assignInjectedEnv(env, { OPENCHAMBER_RUNTIME: 'desktop', OPENCHAMBER_DESKTOP_NOTIFY: 'true' })).toBe(env);
|
|
10
|
+
|
|
11
|
+
expect(env).toEqual({
|
|
12
|
+
PATH: '/usr/bin',
|
|
13
|
+
OPENCHAMBER_RUNTIME: 'desktop',
|
|
14
|
+
OPENCHAMBER_DESKTOP_NOTIFY: 'true',
|
|
15
|
+
[INJECTED_ENV_KEY]: 'OPENCHAMBER_RUNTIME,OPENCHAMBER_DESKTOP_NOTIFY',
|
|
16
|
+
});
|
|
17
|
+
expect(injectedEnvKeys(env)).toEqual(new Set(['OPENCHAMBER_RUNTIME', 'OPENCHAMBER_DESKTOP_NOTIFY', INJECTED_ENV_KEY]));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('leaves a value the shell already had as the user\'s', () => {
|
|
21
|
+
const env = { OPENCODE_PASSWORD: 'theirs', OPENCHAMBER_SKIP_API_COMPRESSION: 'false' };
|
|
22
|
+
|
|
23
|
+
assignInjectedEnv(env, {
|
|
24
|
+
OPENCODE_PASSWORD: 'theirs',
|
|
25
|
+
OPENCODE_SERVER_PASSWORD: 'theirs',
|
|
26
|
+
OPENCHAMBER_SKIP_API_COMPRESSION: env.OPENCHAMBER_SKIP_API_COMPRESSION || 'true',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(env.OPENCHAMBER_SKIP_API_COMPRESSION).toBe('false');
|
|
30
|
+
expect(injectedEnvKeys(env)).toEqual(new Set(['OPENCODE_SERVER_PASSWORD', INJECTED_ENV_KEY]));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('adds to a record inherited from the parent process', () => {
|
|
34
|
+
const env = assignInjectedEnv({}, { OPENCHAMBER_RUNTIME: 'desktop' });
|
|
35
|
+
const child = assignInjectedEnv({ ...env }, { OPENCODE_CONFIG: '/tmp/opencode.json', OPENCHAMBER_RUNTIME: 'desktop' });
|
|
36
|
+
|
|
37
|
+
expect(child[INJECTED_ENV_KEY]).toBe('OPENCHAMBER_RUNTIME,OPENCODE_CONFIG');
|
|
38
|
+
expect(env[INJECTED_ENV_KEY]).toBe('OPENCHAMBER_RUNTIME');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('skips undefined values and writes no record when nothing changed', () => {
|
|
42
|
+
const env = { OPENCHAMBER_HOST: '0.0.0.0' };
|
|
43
|
+
|
|
44
|
+
assignInjectedEnv(env, { OPENCHAMBER_UI_PASSWORD: undefined, OPENCHAMBER_HOST: '0.0.0.0' });
|
|
45
|
+
|
|
46
|
+
expect(env).toEqual({ OPENCHAMBER_HOST: '0.0.0.0' });
|
|
47
|
+
expect(injectedEnvKeys(env).size).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('injectedEnvKeys', () => {
|
|
52
|
+
it('ignores a malformed record', () => {
|
|
53
|
+
expect(injectedEnvKeys({ [INJECTED_ENV_KEY]: '' }).size).toBe(0);
|
|
54
|
+
expect(injectedEnvKeys({ [INJECTED_ENV_KEY]: ',,' })).toEqual(new Set([INJECTED_ENV_KEY]));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -13,7 +13,7 @@ VS Code omits Linear (`RuntimeAPIs.linear` is optional). Hide Linear UI when the
|
|
|
13
13
|
- `packages/web/server/lib/linear/auth.js`: auth file, client id, scopes, redirect URI.
|
|
14
14
|
- `packages/web/server/lib/linear/oauth.js`: authorization-code + PKCE S256, public callback broker handoff, refresh, revoke.
|
|
15
15
|
- `packages/web/server/lib/linear/client.js`: GraphQL helper, viewer/organization lookup, and access-token refresh. GraphQL errors prefer `extensions.userPresentableMessage` / validation constraints over the generic `Argument Validation Error` label. User-facing Linear errors set `LinearApiError.userError`. Requests send `public-file-urls-expire-in: 3600` so file URLs in issue descriptions and comments are temporarily readable in the panel.
|
|
16
|
-
- `packages/web/server/lib/linear/issues.js`: list/search/get issues, team workflow states, `issueUpdate`, and `commentCreate`. Parses identifiers and Linear URLs. `issueUpdate` resolves identifiers to UUIDs first because Linear's mutation does not accept `ENG-12`. List/get include `state.id`, `priority` (0–4), and labels (`id`, `name`, sanitized hex `color`) so the panel can show them and update status.
|
|
16
|
+
- `packages/web/server/lib/linear/issues.js`: list/search/get issues, team workflow states, `issueUpdate`, and `commentCreate`. Parses identifiers and Linear URLs. `issueUpdate` resolves identifiers to UUIDs first because Linear's mutation does not accept `ENG-12`. List/get include `state.id`, `priority` (0–4), and labels (`id`, `name`, sanitized hex `color`) so the panel can show them and update status. Every issue also carries `parent` (the issue's own fields without labels) and `subIssueProgress` (`{ total, done, more }`, done counts completed, canceled and duplicate states, null without sub-issues). Linear has no sub-issue count and every connection multiplies a query's complexity by its page size (one query may not exceed 10,000 points), so a list asks only the first 20 sub-issue states per issue; `more` says there are others. Get and update add `subIssues` (first 100, related-issue fields, in Linear's `subIssueSortOrder`) and `subIssuesMore`.
|
|
17
17
|
- `packages/web/server/lib/linear/teams.js`: list Linear teams for mapping UI.
|
|
18
18
|
- `packages/web/server/lib/linear/mapping.js`: persist default and per-team OpenChamber project paths. Separate from the auth file so disconnect does not wipe maps.
|
|
19
19
|
- `packages/web/server/lib/linear/status.js`: persist per-session started/completed/failure flags and post the matching Linear comment with an open-session URL. Posts nothing unless the user opted in and the session origin is public; `isPublicSessionOrigin` rejects loopback, private LAN, carrier-grade NAT, link-local and single-label hosts. The dedupe file keeps the newest 500 sessions.
|
|
@@ -78,12 +78,11 @@ A worktree started from the panel or picker is created in that mapped project. N
|
|
|
78
78
|
|
|
79
79
|
- `RuntimeAPIs.linear` is optional. Hide Linear settings, the chat picker, and the panel when it is missing (VS Code).
|
|
80
80
|
- Store: `packages/ui/src/stores/useLinearAuthStore.ts`. App start refreshes it from `App.tsx` and `MobileApp.tsx`, not `VSCodeApp`.
|
|
81
|
-
- Settings: first-party section on the Integrations page. Connect opens the authorization URL and polls status until the workspace list or current `authorizedAt` changes, so Add workspace is not treated as done just because a workspace was already connected. When connected, map a default project and optional per-team projects for the current workspace. Other stored workspaces appear in a list with Switch to. Disconnect removes the current workspace only. The
|
|
82
|
-
-
|
|
83
|
-
- Chat: composer attach menu "Link Linear Issue" attaches body and comments as `linear-issue` context on the next send. Exclusive with a linked GitHub issue or PR. The attached issue is stored on session metadata (`kind: 'linear'`) so work status can show it. Clicking that work-status row opens the
|
|
81
|
+
- Settings: first-party section on the Integrations page. Connect opens the authorization URL and polls status until the workspace list or current `authorizedAt` changes, so Add workspace is not treated as done just because a workspace was already connected. When connected, map a default project and optional per-team projects for the current workspace. Other stored workspaces appear in a list with Switch to. Disconnect removes the current workspace only. The board can also switch the current workspace when more than one is stored.
|
|
82
|
+
- Board: Linear issues list on the issues and PRs board (`packages/ui/src/components/sourceBoard`, desktop/web), next to the repository's issues and PRs. It filters by team, state, whose issues (`assignee=me` or `created`) and priority together, changes an issue's state from its state pill (`issueUpdate`), switches the workspace from the team menu, and starts a session or worktree with the issue attached to the message box. The team's mapped project is where the session starts unless the user picks another. VS Code and the phone shell have their own surfaces (the phone shell opens the board from the sessions menu).
|
|
83
|
+
- Chat: composer attach menu "Link Linear Issue" attaches body and comments as `linear-issue` context on the next send. Exclusive with a linked GitHub issue or PR. The attached issue is stored on session metadata (`kind: 'linear'`) so work status can show it. Clicking that work-status row opens the board searched for the issue when Linear is connected on desktop/web; otherwise the Linear URL. Managed Chats do not offer start-from-issue; those sessions have no project directory.
|
|
84
84
|
- Worktree: New Worktree can start from a Linear issue. It uses the currently active project and does not consult team-to-project mapping. It shares the reference picker with GitHub (single choice); one issue, PR or Linear issue names the branch.
|
|
85
|
-
- Status comments: off until the user turns them on in Settings -> Integrations -> Linear (`LinearSessionComments.tsx`). When on,
|
|
86
|
-
- Magic prompts: `linear.issue.review.visible` / `.instructions`, used only by Start session from the panel and the chat. An issue attached in the composer or from New Worktree carries no instructions.
|
|
85
|
+
- Status comments: off until the user turns them on in Settings -> Integrations -> Linear (`LinearSessionComments.tsx`). When on, a session whose first message carries the issue posts `started` once it is sent. The event hub posts `completed` on the first idle after that, and `failure` on `session.error` except `MessageAbortedError`. Failed comments must not fail session create. Comment bodies are English (they live on Linear) and are one markdown link named `OpenChamber session started` (or completed/failed). Web uses `/?session=<id>` on the current origin; desktop reports the loopback origin its own server listens on, not `openchamber-ui://`. A Linear comment is read by the whole team, so the server posts nothing when that origin is not publicly reachable rather than publishing a link only its author could open. Opening `/?session=` selects that session after the global session list can resolve its directory.
|
|
87
86
|
|
|
88
87
|
## Notes for contributors
|
|
89
88
|
|
|
@@ -25,7 +25,7 @@ function readListStatus(value) {
|
|
|
25
25
|
|
|
26
26
|
function readListAssignee(value) {
|
|
27
27
|
const assignee = readTrimmedString(value);
|
|
28
|
-
if (assignee === 'me' || assignee === 'any') {
|
|
28
|
+
if (assignee === 'me' || assignee === 'created' || assignee === 'any') {
|
|
29
29
|
return assignee;
|
|
30
30
|
}
|
|
31
31
|
return 'any';
|
|
@@ -59,6 +59,9 @@ function buildIssueListFilter({ status, assignee, teamId, priority } = {}) {
|
|
|
59
59
|
if (resolvedAssignee === 'me') {
|
|
60
60
|
filter.assignee = { isMe: { eq: true } };
|
|
61
61
|
}
|
|
62
|
+
if (resolvedAssignee === 'created') {
|
|
63
|
+
filter.creator = { isMe: { eq: true } };
|
|
64
|
+
}
|
|
62
65
|
if (team) {
|
|
63
66
|
filter.team = { id: { eq: team } };
|
|
64
67
|
}
|
|
@@ -68,7 +71,9 @@ function buildIssueListFilter({ status, assignee, teamId, priority } = {}) {
|
|
|
68
71
|
return Object.keys(filter).length > 0 ? filter : undefined;
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
// A related issue (a parent, a sub-issue) carries what its row and its own
|
|
75
|
+
// preview header need; labels stay out, they multiply the query's cost.
|
|
76
|
+
const RELATED_ISSUE_FIELDS = `
|
|
72
77
|
id
|
|
73
78
|
identifier
|
|
74
79
|
title
|
|
@@ -78,7 +83,26 @@ const ISSUE_SUMMARY_FIELDS = `
|
|
|
78
83
|
state { id name type }
|
|
79
84
|
assignee { name displayName avatarUrl }
|
|
80
85
|
team { id key name }
|
|
86
|
+
`;
|
|
87
|
+
// Linear has no sub-issue count, and every connection multiplies the query's
|
|
88
|
+
// complexity by its page size (a query may not exceed 10,000 points). A list
|
|
89
|
+
// page asks this many sub-issue states per issue; more reads as "N+".
|
|
90
|
+
const SUB_ISSUE_COUNT_LIMIT = 20;
|
|
91
|
+
const SUB_ISSUE_PAGE = 100;
|
|
92
|
+
const ISSUE_SUMMARY_FIELDS = `
|
|
93
|
+
${RELATED_ISSUE_FIELDS}
|
|
81
94
|
labels { nodes { id name color } }
|
|
95
|
+
parent { ${RELATED_ISSUE_FIELDS} }
|
|
96
|
+
children(first: ${SUB_ISSUE_COUNT_LIMIT}) {
|
|
97
|
+
nodes { state { type } }
|
|
98
|
+
pageInfo { hasNextPage }
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
const SUB_ISSUES_FIELDS = `
|
|
102
|
+
subIssues: children(first: ${SUB_ISSUE_PAGE}) {
|
|
103
|
+
nodes { ${RELATED_ISSUE_FIELDS} subIssueSortOrder }
|
|
104
|
+
pageInfo { hasNextPage }
|
|
105
|
+
}
|
|
82
106
|
`;
|
|
83
107
|
const LIST_QUERY = `
|
|
84
108
|
query ListLinearIssues($first: Int!, $after: String, $filter: IssueFilter) {
|
|
@@ -100,6 +124,7 @@ const GET_QUERY = `
|
|
|
100
124
|
query GetLinearIssue($id: String!) {
|
|
101
125
|
issue(id: $id) {
|
|
102
126
|
${ISSUE_SUMMARY_FIELDS}
|
|
127
|
+
${SUB_ISSUES_FIELDS}
|
|
103
128
|
description
|
|
104
129
|
comments(first: 50) {
|
|
105
130
|
nodes {
|
|
@@ -135,6 +160,7 @@ const ISSUE_UPDATE = `
|
|
|
135
160
|
success
|
|
136
161
|
issue {
|
|
137
162
|
${ISSUE_SUMMARY_FIELDS}
|
|
163
|
+
${SUB_ISSUES_FIELDS}
|
|
138
164
|
description
|
|
139
165
|
comments(first: 50) {
|
|
140
166
|
nodes {
|
|
@@ -266,7 +292,7 @@ function readLabels(value) {
|
|
|
266
292
|
return nodes.map(readLabel).filter(Boolean);
|
|
267
293
|
}
|
|
268
294
|
|
|
269
|
-
function
|
|
295
|
+
function readRelatedIssue(node) {
|
|
270
296
|
if (!isPlainObject(node)) return null;
|
|
271
297
|
const id = readTrimmedString(node.id);
|
|
272
298
|
const identifier = readTrimmedString(node.identifier);
|
|
@@ -282,11 +308,41 @@ function readIssueSummary(node) {
|
|
|
282
308
|
assignee: readAssignee(node.assignee),
|
|
283
309
|
team: readTeam(node.team),
|
|
284
310
|
priority: readPriority(node.priority),
|
|
285
|
-
labels: readLabels(node.labels),
|
|
286
311
|
updatedAt: readTrimmedString(node.updatedAt) || null,
|
|
287
312
|
};
|
|
288
313
|
}
|
|
289
314
|
|
|
315
|
+
const FINISHED_STATE_TYPES = new Set(['completed', 'canceled', 'duplicate']);
|
|
316
|
+
|
|
317
|
+
/** How many sub-issues an issue has and how many are finished; null without any. */
|
|
318
|
+
function readSubIssueProgress(connection) {
|
|
319
|
+
const nodes = isPlainObject(connection) && Array.isArray(connection.nodes) ? connection.nodes : [];
|
|
320
|
+
if (nodes.length === 0) return null;
|
|
321
|
+
const done = nodes.filter((node) => FINISHED_STATE_TYPES.has(readState(node?.state)?.type)).length;
|
|
322
|
+
return { total: nodes.length, done, more: readPageInfo(connection).hasMore };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function readIssueSummary(node) {
|
|
326
|
+
const summary = readRelatedIssue(node);
|
|
327
|
+
if (!summary) return null;
|
|
328
|
+
return {
|
|
329
|
+
...summary,
|
|
330
|
+
labels: readLabels(node.labels),
|
|
331
|
+
parent: readRelatedIssue(node.parent),
|
|
332
|
+
subIssueProgress: readSubIssueProgress(node.children),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Linear's own order: the one the sub-issues are dragged into in Linear.
|
|
337
|
+
function readSubIssues(connection) {
|
|
338
|
+
const nodes = isPlainObject(connection) && Array.isArray(connection.nodes) ? connection.nodes : [];
|
|
339
|
+
return nodes
|
|
340
|
+
.map((node) => ({ issue: readRelatedIssue(node), order: readFiniteNumber(node?.subIssueSortOrder) ?? 0 }))
|
|
341
|
+
.filter((entry) => entry.issue)
|
|
342
|
+
.sort((left, right) => left.order - right.order)
|
|
343
|
+
.map((entry) => entry.issue);
|
|
344
|
+
}
|
|
345
|
+
|
|
290
346
|
function readComment(node) {
|
|
291
347
|
if (!isPlainObject(node)) return null;
|
|
292
348
|
const id = readTrimmedString(node.id);
|
|
@@ -318,6 +374,8 @@ function readIssue(node) {
|
|
|
318
374
|
...summary,
|
|
319
375
|
description: isString(node.description) ? node.description : null,
|
|
320
376
|
comments,
|
|
377
|
+
subIssues: readSubIssues(node.subIssues),
|
|
378
|
+
subIssuesMore: readPageInfo(node.subIssues).hasMore,
|
|
321
379
|
};
|
|
322
380
|
}
|
|
323
381
|
|
|
@@ -157,6 +157,8 @@ describe('Linear issue list/get', () => {
|
|
|
157
157
|
priority: 1,
|
|
158
158
|
labels: [{ id: 'label-bug', name: 'Bug', color: '#eb5757' }],
|
|
159
159
|
updatedAt: '2026-09-30T10:00:00.000Z',
|
|
160
|
+
parent: null,
|
|
161
|
+
subIssueProgress: null,
|
|
160
162
|
}],
|
|
161
163
|
cursor: 'cursor-2',
|
|
162
164
|
hasMore: true,
|
|
@@ -263,6 +265,18 @@ describe('Linear issue list/get', () => {
|
|
|
263
265
|
expect(result.issues).toHaveLength(1);
|
|
264
266
|
});
|
|
265
267
|
|
|
268
|
+
it('lists the issues I created', async () => {
|
|
269
|
+
vi.stubGlobal('fetch', vi.fn(async (_url, options) => {
|
|
270
|
+
expect(JSON.parse(options.body).variables.filter).toEqual({
|
|
271
|
+
state: { type: { nin: ['completed', 'canceled', 'duplicate'] } },
|
|
272
|
+
creator: { isMe: { eq: true } },
|
|
273
|
+
});
|
|
274
|
+
return jsonResponse({ data: { issues: { nodes: [issueNode], pageInfo: { hasNextPage: false, endCursor: null } } } });
|
|
275
|
+
}));
|
|
276
|
+
const result = await listLinearIssues({ assignee: 'created' });
|
|
277
|
+
expect(result.issues).toHaveLength(1);
|
|
278
|
+
});
|
|
279
|
+
|
|
266
280
|
it('filters each panel status to a Linear state type or name', async () => {
|
|
267
281
|
const filters = [];
|
|
268
282
|
vi.stubGlobal('fetch', vi.fn(async (_url, options) => {
|
|
@@ -374,6 +388,60 @@ describe('Linear issue list/get', () => {
|
|
|
374
388
|
}]);
|
|
375
389
|
});
|
|
376
390
|
|
|
391
|
+
it('reads the parent and how many sub-issues a listed issue has finished', async () => {
|
|
392
|
+
const child = (type) => ({ state: { type } });
|
|
393
|
+
vi.stubGlobal('fetch', vi.fn(async (_url, options) => {
|
|
394
|
+
const body = JSON.parse(options.body);
|
|
395
|
+
expect(body.query).toContain('children(first: 20)');
|
|
396
|
+
return jsonResponse({
|
|
397
|
+
data: {
|
|
398
|
+
issues: {
|
|
399
|
+
nodes: [
|
|
400
|
+
{
|
|
401
|
+
...issueNode,
|
|
402
|
+
parent: { id: 'issue-uuid-0', identifier: 'ENG-1', title: 'Login revamp', url: 'https://linear.app/openchamber/issue/ENG-1', state: { type: 'started' } },
|
|
403
|
+
children: { nodes: [child('completed'), child('canceled'), child('started')], pageInfo: { hasNextPage: true } },
|
|
404
|
+
},
|
|
405
|
+
{ ...issueNode, id: 'issue-uuid-2', identifier: 'ENG-13', parent: null, children: { nodes: [], pageInfo: { hasNextPage: false } } },
|
|
406
|
+
],
|
|
407
|
+
pageInfo: { hasNextPage: false, endCursor: null },
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
}));
|
|
412
|
+
|
|
413
|
+
const result = await listLinearIssues();
|
|
414
|
+
expect(result.issues?.[0]?.parent).toMatchObject({ id: 'issue-uuid-0', identifier: 'ENG-1', title: 'Login revamp' });
|
|
415
|
+
expect(result.issues?.[0]?.parent?.labels).toBeUndefined();
|
|
416
|
+
expect(result.issues?.[0]?.subIssueProgress).toEqual({ total: 3, done: 2, more: true });
|
|
417
|
+
expect(result.issues?.[1]?.parent).toBeNull();
|
|
418
|
+
expect(result.issues?.[1]?.subIssueProgress).toBeNull();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('loads one issue with its sub-issues in Linear order', async () => {
|
|
422
|
+
const sub = (identifier, order) => ({
|
|
423
|
+
id: `uuid-${identifier}`,
|
|
424
|
+
identifier,
|
|
425
|
+
title: `Part ${identifier}`,
|
|
426
|
+
url: `https://linear.app/openchamber/issue/${identifier}`,
|
|
427
|
+
state: { id: 's', name: 'Todo', type: 'unstarted' },
|
|
428
|
+
subIssueSortOrder: order,
|
|
429
|
+
});
|
|
430
|
+
vi.stubGlobal('fetch', vi.fn(async () => jsonResponse({
|
|
431
|
+
data: {
|
|
432
|
+
issue: {
|
|
433
|
+
...issueNode,
|
|
434
|
+
subIssues: { nodes: [sub('ENG-21', 2), sub('ENG-20', -1), { id: '', identifier: 'X' }], pageInfo: { hasNextPage: false } },
|
|
435
|
+
comments: { nodes: [] },
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
})));
|
|
439
|
+
|
|
440
|
+
const result = await getLinearIssue('ENG-12');
|
|
441
|
+
expect(result.issue?.subIssues?.map((issue) => issue.identifier)).toEqual(['ENG-20', 'ENG-21']);
|
|
442
|
+
expect(result.issue?.subIssuesMore).toBe(false);
|
|
443
|
+
});
|
|
444
|
+
|
|
377
445
|
it('creates a comment on the resolved issue UUID', async () => {
|
|
378
446
|
vi.stubGlobal('fetch', vi.fn(async (_url, options) => {
|
|
379
447
|
const body = JSON.parse(options.body);
|
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
} from './auth.js';
|
|
9
9
|
import { isPlainObject, isString, readFiniteNumber, readTrimmedString } from './parse.js';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const LINEAR_AUTHORIZE_URL = 'https://linear.app/oauth/authorize';
|
|
12
|
+
const LINEAR_TOKEN_URL = 'https://api.linear.app/oauth/token';
|
|
13
|
+
const LINEAR_REVOKE_URL = 'https://api.linear.app/oauth/revoke';
|
|
14
|
+
const PENDING_AUTHORIZATION_TTL_MS = 10 * 60_000;
|
|
15
15
|
|
|
16
16
|
const pendingByState = new Map();
|
|
17
17
|
const brokerPollsByState = new Map();
|
|
@@ -24,7 +24,7 @@ export class LinearOAuthError extends Error {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
function createPkcePair() {
|
|
28
28
|
const verifier = crypto.randomBytes(32).toString('base64url');
|
|
29
29
|
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
|
|
30
30
|
return { verifier, challenge };
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
return Object.prototype.toString.call(value) === '[object String]';
|
|
3
|
-
}
|
|
1
|
+
import { isString } from '../shared/guards.js';
|
|
4
2
|
|
|
5
|
-
export
|
|
6
|
-
if (value == null || Array.isArray(value)) {
|
|
7
|
-
return false;
|
|
8
|
-
}
|
|
9
|
-
return Object.getPrototypeOf(value) === Object.prototype;
|
|
10
|
-
}
|
|
3
|
+
export { isString, isPlainObject } from '../shared/guards.js';
|
|
11
4
|
|
|
12
5
|
export function readTrimmedString(value) {
|
|
13
6
|
return isString(value) && value.trim() ? value.trim() : '';
|
|
@@ -71,7 +71,29 @@ never replaces editable text or delivered parts. Older items without a summary
|
|
|
71
71
|
derive one from the attached comment metadata or the first non-instruction
|
|
72
72
|
context text. This optional field needs no queue-file migration.
|
|
73
73
|
|
|
74
|
-
##
|
|
74
|
+
## Scheduled items
|
|
75
|
+
|
|
76
|
+
Scheduled tasks use the same items with an optional `scheduledTask` field
|
|
77
|
+
containing `projectId` and `taskId`. Reuse does not change the target's goal or
|
|
78
|
+
auto-accept policy.
|
|
79
|
+
Only the server can enqueue this provenance. Enqueue deduplicates the project
|
|
80
|
+
and task across all session queues. A scheduled item can omit its model to
|
|
81
|
+
keep the target session selection. Normal composer items still require their
|
|
82
|
+
captured model. The UI retains both kinds in the same projection.
|
|
83
|
+
|
|
84
|
+
Scheduled send guards and terminal result callbacks apply only to scheduled
|
|
85
|
+
items. A definitive before-send target or task refusal with 404 or 409 removes
|
|
86
|
+
that item and lets the next item proceed. This includes removed or retargeted
|
|
87
|
+
tasks and moved targets. Other validation errors and all send failures use the
|
|
88
|
+
existing queue retry policy, including model-switch 404 or 409 responses.
|
|
89
|
+
Successful send reports `sent` without waiting for an assistant reply. Removing,
|
|
90
|
+
clearing or evicting a waiting scheduled item reports `cancelled`. Scheduled
|
|
91
|
+
items can be reordered and removed, but cannot be taken for editing or manual
|
|
92
|
+
send. Bulk take returns only ordinary items and leaves scheduled items queued.
|
|
93
|
+
Disable, delete and retarget use `cancelScheduledTask`. Existing composer
|
|
94
|
+
persistence, retries, directory query and delivery gates remain unchanged.
|
|
95
|
+
|
|
96
|
+
## Queue file
|
|
75
97
|
|
|
76
98
|
`<data-dir>/message-queue.json` (`OPENCHAMBER_DATA_DIR` or
|
|
77
99
|
`~/.config/openchamber`): `{ version, revision, sessions: { [sessionId]:
|
|
@@ -133,7 +133,11 @@ export const parseQueuedItemInput = (value) => {
|
|
|
133
133
|
throw new TypeError('item needs text, attachments, or context');
|
|
134
134
|
}
|
|
135
135
|
const sendConfig = parseSendConfig(raw.sendConfig);
|
|
136
|
-
|
|
136
|
+
const provenance = asRecord(raw.scheduledTask);
|
|
137
|
+
const projectId = asNonEmptyString(provenance?.projectId);
|
|
138
|
+
const taskId = asNonEmptyString(provenance?.taskId);
|
|
139
|
+
if (provenance && (!projectId || !taskId)) throw new TypeError('invalid scheduledTask provenance');
|
|
140
|
+
if (!sendConfig && !provenance) throw new TypeError('item sendConfig with providerID and modelID is required');
|
|
137
141
|
const item = { content, text };
|
|
138
142
|
const agentMention = asNonEmptyString(raw.agentMention);
|
|
139
143
|
if (agentMention) item.agentMention = agentMention;
|
|
@@ -141,7 +145,12 @@ export const parseQueuedItemInput = (value) => {
|
|
|
141
145
|
item.context = context;
|
|
142
146
|
const contextPreview = asNonEmptyString(raw.contextPreview).slice(0, 103);
|
|
143
147
|
if (contextPreview) item.contextPreview = contextPreview;
|
|
144
|
-
item.sendConfig = sendConfig;
|
|
148
|
+
item.sendConfig = sendConfig ?? {};
|
|
149
|
+
if (provenance) {
|
|
150
|
+
item.scheduledTask = { projectId, taskId };
|
|
151
|
+
const agent = asNonEmptyString(raw.sendConfig?.agent);
|
|
152
|
+
if (agent) item.sendConfig.agent = agent;
|
|
153
|
+
}
|
|
145
154
|
return item;
|
|
146
155
|
};
|
|
147
156
|
|
|
@@ -177,6 +186,7 @@ const toPublicItem = (item) => {
|
|
|
177
186
|
+ (contextPreview.length > firstLine.length || firstLine.length > 100 ? '...' : '');
|
|
178
187
|
}
|
|
179
188
|
publicItem.sendConfig = { ...item.sendConfig };
|
|
189
|
+
if (item.scheduledTask) publicItem.scheduledTask = { ...item.scheduledTask };
|
|
180
190
|
return publicItem;
|
|
181
191
|
};
|
|
182
192
|
|
|
@@ -228,6 +238,9 @@ export function createMessageQueueRuntime({
|
|
|
228
238
|
sessionKnowledgeRuntime = null,
|
|
229
239
|
broadcastGlobalUiEvent,
|
|
230
240
|
onPromptSent,
|
|
241
|
+
beforeScheduledTaskSend,
|
|
242
|
+
validateScheduledTaskTarget,
|
|
243
|
+
onScheduledTaskResult,
|
|
231
244
|
// Resolves the `openchamber/auto` sentinel into a real model and agent right
|
|
232
245
|
// before the send; absent means the queue never sees the sentinel.
|
|
233
246
|
resolveAutoSelection = null,
|
|
@@ -373,7 +386,16 @@ export function createMessageQueueRuntime({
|
|
|
373
386
|
return { revision, session: sessionSnapshot(sessionId) };
|
|
374
387
|
};
|
|
375
388
|
|
|
376
|
-
const
|
|
389
|
+
const reportScheduledResult = (item, status, error) => {
|
|
390
|
+
if (!item.scheduledTask) return;
|
|
391
|
+
Promise.resolve().then(() => onScheduledTaskResult?.(item.scheduledTask, status, error))
|
|
392
|
+
.catch((failure) => console.warn('[message-queue] scheduled task state write failed:', failure?.message ?? failure));
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const setQueueItems = (sessionId, directory, items, status = 'cancelled', error) => {
|
|
396
|
+
for (const item of queues.get(sessionId)?.items ?? []) {
|
|
397
|
+
if (item.scheduledTask && !items.some((entry) => entry.id === item.id)) reportScheduledResult(item, status, error);
|
|
398
|
+
}
|
|
377
399
|
directories.set(sessionId, directory);
|
|
378
400
|
if (items.length === 0) {
|
|
379
401
|
queues.delete(sessionId);
|
|
@@ -494,13 +516,13 @@ export function createMessageQueueRuntime({
|
|
|
494
516
|
|
|
495
517
|
// Jev routing, when the queued send named the Auto sentinel. A failure
|
|
496
518
|
// inside resolves to the fallback model; only a missing fallback throws.
|
|
497
|
-
const routed = await resolveAutoSelection?.({
|
|
519
|
+
const routed = providerID && modelID ? await resolveAutoSelection?.({
|
|
498
520
|
sessionId,
|
|
499
521
|
directory,
|
|
500
522
|
model,
|
|
501
523
|
agent,
|
|
502
524
|
requestText: item.text,
|
|
503
|
-
});
|
|
525
|
+
}) : null;
|
|
504
526
|
if (routed) {
|
|
505
527
|
model = routed.model;
|
|
506
528
|
agent = routed.agent ?? agent;
|
|
@@ -508,7 +530,7 @@ export function createMessageQueueRuntime({
|
|
|
508
530
|
|
|
509
531
|
// v2 selects model and agent on the session, not per prompt: the choice is
|
|
510
532
|
// switched once and then persists.
|
|
511
|
-
await openCodeFetch(`/api/session/${encodeURIComponent(sessionId)}/model`, {
|
|
533
|
+
if (!item.scheduledTask || (providerID && modelID)) await openCodeFetch(`/api/session/${encodeURIComponent(sessionId)}/model`, {
|
|
512
534
|
directory,
|
|
513
535
|
method: 'POST',
|
|
514
536
|
body: { model },
|
|
@@ -627,6 +649,19 @@ export function createMessageQueueRuntime({
|
|
|
627
649
|
|
|
628
650
|
const idle = await isSessionIdle(sessionId, queue.directory);
|
|
629
651
|
if (idle === null) {
|
|
652
|
+
if (head.scheduledTask && validateScheduledTaskTarget) {
|
|
653
|
+
try {
|
|
654
|
+
await validateScheduledTaskTarget(head.scheduledTask.projectId, sessionId);
|
|
655
|
+
} catch (error) {
|
|
656
|
+
if (error?.statusCode === 404 || error?.statusCode === 409) {
|
|
657
|
+
const after = queues.get(sessionId);
|
|
658
|
+
if (after) setQueueItems(sessionId, after.directory, after.items.filter((entry) => entry.id !== head.id), 'failed', error.message);
|
|
659
|
+
commit(sessionId);
|
|
660
|
+
armDispatch(sessionId);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
630
665
|
armDispatch(sessionId, retryDelayMs(1));
|
|
631
666
|
return;
|
|
632
667
|
}
|
|
@@ -651,9 +686,23 @@ export function createMessageQueueRuntime({
|
|
|
651
686
|
sending.set(sessionId, item.id);
|
|
652
687
|
broadcast(sessionId);
|
|
653
688
|
try {
|
|
689
|
+
if (item.scheduledTask) {
|
|
690
|
+
try {
|
|
691
|
+
await beforeScheduledTaskSend?.(sessionId, current.directory, item);
|
|
692
|
+
} catch (error) {
|
|
693
|
+
if (error?.statusCode !== 404 && error?.statusCode !== 409) throw error;
|
|
694
|
+
sending.delete(sessionId);
|
|
695
|
+
const after = queues.get(sessionId);
|
|
696
|
+
if (after) setQueueItems(sessionId, after.directory, after.items.filter((entry) => entry.id !== item.id), 'failed', error.message);
|
|
697
|
+
failures.delete(sessionId);
|
|
698
|
+
commit(sessionId);
|
|
699
|
+
armDispatch(sessionId);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
654
703
|
await sendItem(sessionId, current.directory, item);
|
|
655
704
|
const after = queues.get(sessionId);
|
|
656
|
-
if (after) setQueueItems(sessionId, after.directory, after.items.filter((entry) => entry.id !== item.id));
|
|
705
|
+
if (after) setQueueItems(sessionId, after.directory, after.items.filter((entry) => entry.id !== item.id), 'sent');
|
|
657
706
|
failures.delete(sessionId);
|
|
658
707
|
sending.delete(sessionId);
|
|
659
708
|
commit(sessionId);
|
|
@@ -693,6 +742,16 @@ export function createMessageQueueRuntime({
|
|
|
693
742
|
if (!directory) throw new TypeError('directory is required');
|
|
694
743
|
const parsed = parseQueuedItemInput(itemInput);
|
|
695
744
|
await load();
|
|
745
|
+
if (parsed.scheduledTask) {
|
|
746
|
+
for (const queue of queues.values()) {
|
|
747
|
+
if (queue.items.some((entry) => entry.scheduledTask?.projectId === parsed.scheduledTask.projectId && entry.scheduledTask.taskId === parsed.scheduledTask.taskId)) {
|
|
748
|
+
throw httpError('task is already queued', 409);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if ((queues.get(sessionId)?.items.length ?? 0) >= MAX_ITEMS_PER_SESSION || (!queues.has(sessionId) && queues.size >= MAX_SESSIONS)) {
|
|
752
|
+
throw httpError('message queue is full', 409);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
696
755
|
const item = {
|
|
697
756
|
id: `queued-${now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
698
757
|
createdAt: now(),
|
|
@@ -700,7 +759,7 @@ export function createMessageQueueRuntime({
|
|
|
700
759
|
};
|
|
701
760
|
const existing = queues.get(sessionId);
|
|
702
761
|
const items = [...(existing?.items ?? []), item].slice(-MAX_ITEMS_PER_SESSION);
|
|
703
|
-
|
|
762
|
+
setQueueItems(sessionId, directory, items);
|
|
704
763
|
directories.set(sessionId, directory);
|
|
705
764
|
if (queues.size > MAX_SESSIONS) {
|
|
706
765
|
const oldest = Array.from(queues.entries())
|
|
@@ -708,7 +767,7 @@ export function createMessageQueueRuntime({
|
|
|
708
767
|
.sort((left, right) => (left[1].items[0]?.createdAt ?? 0) - (right[1].items[0]?.createdAt ?? 0))
|
|
709
768
|
.slice(0, queues.size - MAX_SESSIONS);
|
|
710
769
|
for (const [staleId] of oldest) {
|
|
711
|
-
|
|
770
|
+
setQueueItems(staleId, directories.get(staleId), []);
|
|
712
771
|
clearTimer(staleId);
|
|
713
772
|
broadcast(staleId);
|
|
714
773
|
directories.delete(staleId);
|
|
@@ -741,6 +800,7 @@ export function createMessageQueueRuntime({
|
|
|
741
800
|
const queue = queues.get(sessionId);
|
|
742
801
|
const item = queue?.items.find((entry) => entry.id === itemId);
|
|
743
802
|
if (!queue || !item) throw httpError('queued message not found', 404);
|
|
803
|
+
if (item.scheduledTask) throw httpError('scheduled messages cannot be taken', 409);
|
|
744
804
|
setQueueItems(sessionId, queue.directory, queue.items.filter((entry) => entry.id !== itemId));
|
|
745
805
|
return { ...commit(sessionId), item };
|
|
746
806
|
};
|
|
@@ -752,9 +812,9 @@ export function createMessageQueueRuntime({
|
|
|
752
812
|
const queue = queues.get(sessionId);
|
|
753
813
|
if (!queue) return { revision, session: sessionSnapshot(sessionId), items: [] };
|
|
754
814
|
const sendingId = sending.get(sessionId) ?? null;
|
|
755
|
-
const items = queue.items.filter((item) => item.id !== sendingId);
|
|
815
|
+
const items = queue.items.filter((item) => item.id !== sendingId && !item.scheduledTask);
|
|
756
816
|
if (items.length === 0) return { revision, session: sessionSnapshot(sessionId), items: [] };
|
|
757
|
-
setQueueItems(sessionId, queue.directory, queue.items.filter((item) => item.id === sendingId));
|
|
817
|
+
setQueueItems(sessionId, queue.directory, queue.items.filter((item) => item.id === sendingId || item.scheduledTask));
|
|
758
818
|
return { ...commit(sessionId), items };
|
|
759
819
|
};
|
|
760
820
|
|
|
@@ -810,7 +870,7 @@ export function createMessageQueueRuntime({
|
|
|
810
870
|
const deletedSessionId = extractDeletedSessionId(payload);
|
|
811
871
|
if (deletedSessionId) {
|
|
812
872
|
if (!queues.has(deletedSessionId)) return;
|
|
813
|
-
|
|
873
|
+
setQueueItems(deletedSessionId, directories.get(deletedSessionId), []);
|
|
814
874
|
clearTimer(deletedSessionId);
|
|
815
875
|
failures.delete(deletedSessionId);
|
|
816
876
|
commit(deletedSessionId);
|
|
@@ -870,11 +930,23 @@ export function createMessageQueueRuntime({
|
|
|
870
930
|
timers.clear();
|
|
871
931
|
};
|
|
872
932
|
|
|
933
|
+
const cancelScheduledTask = async (projectId, taskId) => {
|
|
934
|
+
await load();
|
|
935
|
+
for (const [sessionId, queue] of queues) {
|
|
936
|
+
const items = queue.items.filter((item) => item.scheduledTask?.projectId !== projectId || item.scheduledTask.taskId !== taskId || sending.get(sessionId) === item.id);
|
|
937
|
+
if (items.length === queue.items.length) continue;
|
|
938
|
+
setQueueItems(sessionId, queue.directory, items);
|
|
939
|
+
commit(sessionId);
|
|
940
|
+
armDispatch(sessionId);
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
873
944
|
return {
|
|
874
945
|
load,
|
|
875
946
|
snapshot,
|
|
876
947
|
sessionSnapshot,
|
|
877
948
|
enqueue,
|
|
949
|
+
cancelScheduledTask,
|
|
878
950
|
remove,
|
|
879
951
|
take,
|
|
880
952
|
takeAll,
|
|
@@ -906,6 +978,7 @@ export function registerMessageQueueRoutes(app, runtime) {
|
|
|
906
978
|
|
|
907
979
|
app.post('/api/message-queue/sessions/:sessionId/items', async (req, res) => {
|
|
908
980
|
try {
|
|
981
|
+
if (req.body?.item?.scheduledTask) throw new TypeError('scheduledTask is server-owned');
|
|
909
982
|
res.json(await runtime.enqueue(req.params.sessionId, req.body?.directory, req.body?.item));
|
|
910
983
|
} catch (error) {
|
|
911
984
|
respondError(res, error, 'Failed to queue message');
|