@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,155 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { WorktreeCreationError, createWorktreeCreation } from './worktree-creation.js';
|
|
3
|
+
|
|
4
|
+
const sourceRequest = {
|
|
5
|
+
context: {
|
|
6
|
+
provider: 'github', instance: 'github.com', directory: '/repo', repositoryId: 'repo_one',
|
|
7
|
+
accountId: 'account_one', bindingRevision: 2, primaryRemote: 'origin',
|
|
8
|
+
},
|
|
9
|
+
project: { id: 'acme/app', owner: 'acme', name: 'app' }, number: 42,
|
|
10
|
+
expectedHeadSha: 'a'.repeat(40), requestedRemoteName: 'pr-alice',
|
|
11
|
+
};
|
|
12
|
+
const resolvedSource = {
|
|
13
|
+
context: sourceRequest.context,
|
|
14
|
+
targetProject: sourceRequest.project,
|
|
15
|
+
sourceProject: { id: 'Alice/app', owner: 'Alice', name: 'app' },
|
|
16
|
+
number: 42, headRef: 'refs/heads/feature', headSha: 'a'.repeat(40),
|
|
17
|
+
requestedRemoteName: 'pr-alice', endpoint: 'https://github.com/Alice/app.git',
|
|
18
|
+
classification: 'contributor-fork',
|
|
19
|
+
};
|
|
20
|
+
const validAccount = {
|
|
21
|
+
credentialId: 'account_one', credentialRevision: 3, providerUserId: 'github.com#42', status: 'valid',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const setup = (overrides = {}) => {
|
|
25
|
+
const libraries = {
|
|
26
|
+
createWorktree: vi.fn(async () => ({ path: '/worktrees/feature', branch: 'feature' })),
|
|
27
|
+
validateWorktreeCreate: vi.fn(async () => ({ ok: false, errors: [{ code: 'contributor_transfer_unavailable', message: 'transfer first' }] })),
|
|
28
|
+
getRepositoryRemoteUrls: vi.fn(async () => []),
|
|
29
|
+
...overrides.libraries,
|
|
30
|
+
};
|
|
31
|
+
const dependencies = {
|
|
32
|
+
getGitLibraries: async () => libraries,
|
|
33
|
+
networkOperations: {
|
|
34
|
+
hydrateBoundCheckout: vi.fn(),
|
|
35
|
+
transferContributorHead: vi.fn(async () => ({ state: 'succeeded' })),
|
|
36
|
+
},
|
|
37
|
+
getSourceControlBinding: vi.fn(async () => ({ revision: 0, repository: { repositoryId: 'repo_one' }, binding: null })),
|
|
38
|
+
contributorProvenance: { compareAndSwap: vi.fn() },
|
|
39
|
+
resolveChangeRequestSource: vi.fn(async () => resolvedSource),
|
|
40
|
+
createHttpsCredentialReference: vi.fn(() => 'credential_reference'),
|
|
41
|
+
resolveSourceControlAccount: vi.fn(async () => validAccount),
|
|
42
|
+
worktreeBootstrapStore: { read: vi.fn(), write: vi.fn() },
|
|
43
|
+
...overrides.dependencies,
|
|
44
|
+
};
|
|
45
|
+
return { libraries, dependencies, creation: createWorktreeCreation(dependencies) };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const request = { mode: 'existing', branchName: 'feature', worktreeName: 'feature', changeRequestSource: sourceRequest };
|
|
49
|
+
|
|
50
|
+
describe('worktree creation from a change request', () => {
|
|
51
|
+
it('creates a contributor worktree from the server-resolved head only', async () => {
|
|
52
|
+
const { libraries, dependencies, creation } = setup();
|
|
53
|
+
|
|
54
|
+
await expect(creation.create('/repo', request)).resolves.toEqual({ path: '/worktrees/feature', branch: 'feature' });
|
|
55
|
+
|
|
56
|
+
expect(dependencies.resolveChangeRequestSource).toHaveBeenCalledExactlyOnceWith(sourceRequest);
|
|
57
|
+
expect(dependencies.networkOperations.transferContributorHead).toHaveBeenCalledExactlyOnceWith({
|
|
58
|
+
directory: '/repo', sourceRequest, source: { ...resolvedSource, requestedRemoteName: 'pr-alice' },
|
|
59
|
+
destinationRef: 'refs/remotes/pr-alice/feature', credentialId: 'credential_reference',
|
|
60
|
+
});
|
|
61
|
+
const [, input, options] = libraries.createWorktree.mock.calls[0];
|
|
62
|
+
expect(input).toMatchObject({
|
|
63
|
+
mode: 'existing', branchName: 'feature', worktreeName: 'feature',
|
|
64
|
+
contributorFork: true, changeRequestTransfer: true, contributorTransferComplete: true,
|
|
65
|
+
ensureRemoteName: 'pr-alice', ensureRemoteUrl: 'https://github.com/Alice/app.git',
|
|
66
|
+
existingBranch: 'remotes/pr-alice/feature', expectedRevision: 'a'.repeat(40), setUpstream: false,
|
|
67
|
+
});
|
|
68
|
+
expect(input.changeRequestSource).toBeUndefined();
|
|
69
|
+
expect(options).toMatchObject({
|
|
70
|
+
contributorProvenance: dependencies.contributorProvenance,
|
|
71
|
+
contributorSource: { ...resolvedSource, requestedRemoteName: 'pr-alice' },
|
|
72
|
+
bootstrapStore: dependencies.worktreeBootstrapStore,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('refuses contributor authority supplied by the caller before reading anything', async () => {
|
|
77
|
+
const { libraries, dependencies, creation } = setup();
|
|
78
|
+
|
|
79
|
+
await expect(creation.create('/repo', { ...request, ensureRemoteUrl: 'https://attacker.example/app.git' }))
|
|
80
|
+
.rejects.toMatchObject({ status: 400, code: 'INVALID_CONTRIBUTOR_WORKTREE' });
|
|
81
|
+
expect(dependencies.resolveChangeRequestSource).not.toHaveBeenCalled();
|
|
82
|
+
expect(libraries.createWorktree).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('refuses a remote name that already points elsewhere, before any transfer', async () => {
|
|
86
|
+
const { libraries, dependencies, creation } = setup({ libraries: {
|
|
87
|
+
validateWorktreeCreate: vi.fn(async () => ({
|
|
88
|
+
ok: false,
|
|
89
|
+
errors: [{ code: 'remote_name_collision', message: 'Remote pr-alice already exists with a different endpoint' }],
|
|
90
|
+
})),
|
|
91
|
+
} });
|
|
92
|
+
|
|
93
|
+
const error = await creation.create('/repo', request).catch((caught) => caught);
|
|
94
|
+
expect(error).toBeInstanceOf(WorktreeCreationError);
|
|
95
|
+
expect(error).toMatchObject({
|
|
96
|
+
status: 409, code: 'CONTRIBUTOR_REMOTE_COLLISION', remoteName: 'pr-alice',
|
|
97
|
+
message: 'Remote pr-alice already exists with a different endpoint',
|
|
98
|
+
});
|
|
99
|
+
expect(dependencies.networkOperations.transferContributorHead).not.toHaveBeenCalled();
|
|
100
|
+
expect(libraries.createWorktree).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('refuses when the account that would fetch the head is not the context\'s exact one', async () => {
|
|
104
|
+
const { libraries, dependencies, creation } = setup({ dependencies: {
|
|
105
|
+
resolveSourceControlAccount: vi.fn(async () => ({ ...validAccount, credentialId: 'other_account' })),
|
|
106
|
+
} });
|
|
107
|
+
|
|
108
|
+
await expect(creation.create('/repo', request)).rejects.toMatchObject({ status: 409, code: 'AUTHENTICATION_REQUIRED' });
|
|
109
|
+
expect(dependencies.networkOperations.transferContributorHead).not.toHaveBeenCalled();
|
|
110
|
+
expect(libraries.createWorktree).not.toHaveBeenCalled();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('reports a failed transfer and creates nothing', async () => {
|
|
114
|
+
const { libraries, creation } = setup({ dependencies: {
|
|
115
|
+
networkOperations: {
|
|
116
|
+
hydrateBoundCheckout: vi.fn(),
|
|
117
|
+
transferContributorHead: vi.fn(async () => ({ state: 'failed', error: { code: 'TIMEOUT', message: 'Git network operation timed out' } })),
|
|
118
|
+
},
|
|
119
|
+
} });
|
|
120
|
+
|
|
121
|
+
await expect(creation.create('/repo', request))
|
|
122
|
+
.rejects.toMatchObject({ status: 409, code: 'TIMEOUT', message: 'Git network operation timed out' });
|
|
123
|
+
expect(libraries.createWorktree).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('passes a provider refusal through and creates nothing', async () => {
|
|
127
|
+
const stale = Object.assign(new Error('Change request head changed'), { status: 409, code: 'SOURCE_CONTROL_CHANGE_REQUEST_STALE' });
|
|
128
|
+
const { libraries, dependencies, creation } = setup({ dependencies: {
|
|
129
|
+
resolveChangeRequestSource: vi.fn(async () => { throw stale; }),
|
|
130
|
+
} });
|
|
131
|
+
|
|
132
|
+
await expect(creation.create('/repo', request)).rejects.toBe(stale);
|
|
133
|
+
expect(dependencies.networkOperations.transferContributorHead).not.toHaveBeenCalled();
|
|
134
|
+
expect(libraries.createWorktree).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('refuses where contributor transfer is not served, with no plain worktree instead', async () => {
|
|
138
|
+
const { libraries, creation } = setup({ dependencies: { resolveChangeRequestSource: undefined } });
|
|
139
|
+
|
|
140
|
+
await expect(creation.create('/repo', request)).rejects.toMatchObject({ status: 501, code: 'RUNTIME_UNSUPPORTED' });
|
|
141
|
+
expect(libraries.createWorktree).not.toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('turns a remote collision found while creating into the route\'s collision answer', async () => {
|
|
145
|
+
const collision = Object.assign(new Error('Remote pr-alice already exists with a different endpoint'), {
|
|
146
|
+
code: 'CONTRIBUTOR_REMOTE_COLLISION', status: 409, remoteName: 'pr-alice',
|
|
147
|
+
});
|
|
148
|
+
const { creation } = setup({ libraries: { createWorktree: vi.fn(async () => { throw collision; }) } });
|
|
149
|
+
|
|
150
|
+
await expect(creation.create('/repo', request)).rejects.toMatchObject({
|
|
151
|
+
status: 409, code: 'CONTRIBUTOR_REMOTE_COLLISION', remoteName: 'pr-alice',
|
|
152
|
+
message: 'Contributor remote name is already used by a different endpoint',
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
- `getOctokitOrNull()`: current Octokit or `null`.
|
|
46
46
|
- `getOctokitForAccountId(accountId)`: exact persisted or verified CLI Octokit context with credential revision and provider-user identity. It never falls back to another account. A `github.com#cli:` id resolves only while the user has switched to the gh account (`isGhCliActive()`); the gh token is otherwise read only to list the gh account in Settings.
|
|
47
|
+
- `getGitHubCliCredential(accountId, revision?)`: the same gh account as a credential record shaped like a persisted one (`credentialId`, `credentialRevision: 1`, `providerUserId`, `accessToken`, `status: 'valid'`), under the same consent rule plus a check that gh is still signed in as that user. The server's source-control account resolver falls back to it after `getGitHubAuthByAccountId`, so Git transport (fork PR worktrees, managed clones, the credential helper) accepts the gh account that reads already use.
|
|
47
48
|
|
|
48
49
|
### Repo
|
|
49
50
|
|
|
@@ -70,6 +71,9 @@
|
|
|
70
71
|
- Canonical reads use the exact account and primary remote returned by validation. A GitHub `401` invalidates only that account. `403` and network failures remain read errors and do not change account state.
|
|
71
72
|
- Route, PR discovery, and repository metadata caches use an opaque digest of both account ID and credential. Canonical status cache keys also include the trusted repository ID and binding revision, so path replacement and rebinding cannot reuse a warm response. Exact accounts and repository bindings are resolved before cache reads, and bound repository validation always precedes fork metadata cache reads.
|
|
72
73
|
- Canonical PR create, update, merge, and ready routes validate the complete mutation context before credential, Git, cache, or provider access. They use only the validated account and trusted primary remote, require the target and source to belong to the provider-reported fork network, and return durable mutation receipts. Retired `/api/github/pr/*` mutations never enter this flow.
|
|
74
|
+
- Comments and reviews take the same path: `/pr/comment` (a pull request's conversation, through the issues comments API), `/pr/review` (`approve` or `request-changes` for the target's `headSha`, the commit the user read, so a push since refuses it as a changed target; requesting changes needs text, which GitHub posts as the review body) and `/issues/comment` (refuses a number GitHub reports as a pull request). Record kinds are `change-request-comment`, `change-request-review` and `issue-comment`. A comment or review interrupted after dispatch stays `outcome-unknown`: nothing but its private text would tell a lost write from one never sent, and that text is not persisted. A `403`/`404`/`422` refusal answers with GitHub's own reason (`SOURCE_CONTROL_MUTATION_REJECTED`), such as a review of the user's own pull request; it is not stored.
|
|
75
|
+
- `/pr/state` and `/issues/state` close or reopen (`state: 'open' | 'closed'`; record kinds `change-request-state`, `issue-state`). The issue route refuses a pull request number. Restart reconciliation reads the item: the asked state is success, the other open/closed state a definite not-applied failure. A merged pull request reads `closed` and GitHub refuses to reopen it with its own reason.
|
|
76
|
+
- `/pr/labels`, `/issues/labels` and `/pr/reviewers` set the whole set, deduplicated and sorted so a repeat digests the same (record kinds `change-request-labels`, `issue-labels`, `change-request-reviewers`). Labels go through `issues.setLabels`; reviewers are compared with the requested ones and added, then removed, in two calls: asking again for the same set repairs a failure between them. Reconciliation succeeds only when the current set matches; anything else stays `outcome-unknown`, since another edit may have moved it. `GET /references/labels` and `/references/reviewers` (repository labels, assignable users, one page of 100 each) check the repository against the bound network like `/references/detail`. The detail's `pull.reviewers` lists requested users; team requests are left out. `pull.checks` summarizes the head commit's check contexts with `summarizeCheckContexts`, the same counting as the list statuses, and is null for a closed or merged PR.
|
|
73
77
|
- Canonical mutations resolve the exact local credential and its revision before inspecting durable state. The exact credential ID and revision remain in authorization and digest input, while the route derives the provider-user ID from that resolved credential and passes it separately as receipt actor `providerAccountId` and audit identity. A client-supplied provider-user ID is never accepted as authority and cannot affect either value. Terminal records replay their compact result or stored error without provider repository preflight, but only after exact credential resolution. Running records lazily read only the provider state needed for reconciliation and never repeat a write. New keys retain the full strict preflight before claim and the single provider write. Changed credential, revision, input, or binding authority conflicts. Provider `4xx` rejections are definite failures; `5xx` and statusless transport failures after dispatch remain `outcome-unknown`.
|
|
74
78
|
- Cross-process execution exclusion, lock errors, and stopped-writer orphan recovery follow the [source-control lock contract](../source-control/DOCUMENTATION.md#cross-process-locks-and-manual-recovery). A live or crashed owner's execution lock blocks reconciliation; restarting alone does not remove it.
|
|
75
79
|
- Durable mutation records keep the exact credential account actor used for replay authorization, binding authority, server-resolved project and PR coordinates, an input digest, and compact public results only. Receipt and audit actor metadata use the separately verified provider-user ID. PR title/body text and raw provider payloads are not persisted or logged.
|
|
@@ -113,14 +117,15 @@ that page, so callers cannot mistake a partial page for a complete one.
|
|
|
113
117
|
- It reads local git status and remotes first.
|
|
114
118
|
- It ranks remotes in this order: explicit remote, tracking remote, `origin`, `upstream`, then the rest.
|
|
115
119
|
- It resolves those remotes into GitHub repos.
|
|
120
|
+
- The ranked-first remote is the branch's source unless the worktree was checked out from a contributor's fork PR. Such a worktree deliberately has no upstream, so `/pr/status` reads its contributor provenance (`readContributorProvenance`, supplied by the server runtime) and, when the provenance's source ref is this branch, passes the fork remote as `sourceRemoteName`. That remote, not the primary one, is then the only source, so the open PR from the fork is found. Unreadable provenance resolves as for any other branch.
|
|
116
121
|
- It expands each repo through `parent` and `source` so PRs in upstream repos can still be found.
|
|
117
122
|
- It skips PR lookup when the current branch matches that repo's default branch.
|
|
118
123
|
- It first searches for **open** PRs by likely source owner plus exact head branch.
|
|
119
124
|
- If that fails, it falls back to broader GitHub search for open PRs on the branch name.
|
|
120
125
|
- An **open PR from any candidate repo always wins** over a closed/merged one, so a merged fork PR can never hide an open upstream PR for the same head.
|
|
121
|
-
- Only when no target has an open PR does it return the branch's newest closed/merged PR, as history
|
|
122
|
-
- History is looked up
|
|
123
|
-
- The history answer is remembered per repo
|
|
126
|
+
- Only when no target has an open PR does it return the branch's newest closed/merged PR, as history. Its head commit must be an ancestor of the checkout's `HEAD`, or have a patch-equivalent commit there after a rebase. Missing old objects cannot prove that association. History is matched by branch name, and names get reused: a fresh worktree cut from the default branch under a name that was merged before must not inherit the old PR.
|
|
127
|
+
- History is looked up only for a non-default branch's own name and the ranked-first remote's fork network: its repository plus its `parent` and `source` targets. This restores merged fork PRs hosted by an upstream repository without querying unrelated contributor remotes, while terminal history never attaches to a target's default branch. A contributor fork PR worktree also asks its ranked-first remote, where that PR merges. The primary network has at most three targets, so history remains bounded instead of fanning out across every configured remote.
|
|
128
|
+
- The history answer is remembered per repo, branch, and ordered primary source-owner set so discovery polls do not re-query it: a found closed/merged record for `6h`, and "no history yet" for `10m`. Scoping by ordered source owners prevents cache misses and result confusion when searching the same fork branch from different direct-upstream or fork-origin remotes. A found record only changes if a second PR appears on the same head, and while that one is open the open-PR path wins without ever reading this cache.
|
|
124
129
|
- Creating, merging, or closing a PR invalidates both the shared repo pull list and that remembered history.
|
|
125
130
|
- The route skips the checks summary and the merge-permission lookup for a closed/merged PR: neither is actionable, and both cost extra GitHub calls.
|
|
126
131
|
- `403` and `404` during repo lookups are treated as expected gaps, not hard errors.
|
|
@@ -136,11 +141,11 @@ that page, so callers cannot mistake a partial page for a complete one.
|
|
|
136
141
|
|
|
137
142
|
## Reference picker lists
|
|
138
143
|
|
|
139
|
-
- `GET /api/source-control/github/references?<read context>&kind=issue|pull&
|
|
140
|
-
- One GraphQL `search` document per page,
|
|
144
|
+
- `GET /api/source-control/github/references?<read context>&kind=issue|pull&state=&people=&query=&cursor=` takes the same bound read context as every canonical read and answers one page (30) of the project's issues or PRs across its repo network, read with that context's account (origin plus fork parent/source): `{ connected, repo, items, cursor, hasMore, total }`. `repo: null` means the project has no GitHub remote.
|
|
145
|
+
- One GraphQL `search` document per page, newest activity first. `state` (`open`, the default, `closed`, `merged`, `all`) and `people` (`any`, the default, `assigned` → `assignee:@me`, `created` → `author:@me`, `reviewRequested` → `review-requested:@me`) combine; an issue list reads `merged` as closed and ignores review requests. User text is appended as is, so GitHub qualifiers work; state and sort are added only when the text sets none.
|
|
141
146
|
- A pasted `#123`, `123` or github.com issue/PR link skips search and reads that number with `issueOrPullRequest` in every network repo (a link: only its own repo), whatever the tab's kind.
|
|
142
147
|
- Each item carries what a row and the preview show: state (issues `open` / `completed` / `not_planned`, PRs `open` / `closed` / `merged`), author, labels, comment count, the body cut at 20 000 characters (`bodyTruncated`), and for a PR draft, head/base and head repo (with `cloneUrl` for fork worktrees).
|
|
143
|
-
- `GET /api/source-control/github/references/detail?<read context>&owner=&repo=&number=` answers what the preview adds for one item: the newest 50 comments, oldest first, plus for a PR its reviews (a review's text or verdict, and each line comment with `path`/`line`), size and review decision. Its checks come from `references/status`, the same answer that colours it. The repo must be in the project's network. These fields stay off the page query: on a 30-PR page size, review decision and checks together ran past GitHub's 10 s search timeout (measured 2026-10-01), while one item answers in about a second.
|
|
148
|
+
- `GET /api/source-control/github/references/detail?<read context>&owner=&repo=&number=` answers what the preview adds for one item: the newest 50 comments, oldest first, plus for a PR its reviews (a review's text or verdict, and each line comment with `path`/`line`), its newest 50 commits with `commitTotal`, size and review decision. Its checks come from `references/status`, the same answer that colours it. The repo must be in the project's network. These fields stay off the page query: on a 30-PR page size, review decision and checks together ran past GitHub's 10 s search timeout (measured 2026-10-01), while one item answers in about a second.
|
|
144
149
|
- `GET /api/source-control/github/references/status?<read context>&pulls=owner/repo#n,...` answers what colours the PRs a picker page shows, at most 30: `{ connected, statuses: [{ owner, repo, number, checks, mergeable, mergeableState }] }` from `fetchPrSummaries`, the sidebar's summaries, so the picker and the sidebar colour a PR the same way. Every repo must be in the project's network. A PR GitHub could not resolve is left out. Mergeability is most of its cost (30 PRs: about 2 s with checks only, 4 to 7 s with mergeability, measured 2026-10-06), so it is a request after the page, not part of it.
|
|
145
150
|
- Failures are errors, never an empty page; GraphQL `RATE_LIMITED` records the shared cooldown and answers `503`. The client cache lives in `packages/ui/src/components/references/`.
|
|
146
151
|
- Attaching still reads full context through the canonical `issues/get` + `issues/comments` and `pulls/context`.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPlainObject, isString } from '../shared/guards.js';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import fsSync from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -18,10 +19,6 @@ const DEFAULT_GITHUB_CLIENT_ID = 'Ov23lizomPOC3eFYo56r';
|
|
|
18
19
|
const DEFAULT_GITHUB_SCOPES = 'repo read:org workflow read:user user:email';
|
|
19
20
|
export const GH_CLI_ACCOUNT_ID = 'gh-cli';
|
|
20
21
|
|
|
21
|
-
const isString = (value) => Object.prototype.toString.call(value) === '[object String]';
|
|
22
|
-
const isPlainObject = (value) => value === Object(value)
|
|
23
|
-
&& !Array.isArray(value)
|
|
24
|
-
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
25
22
|
const exactKeys = (value, required, optional = []) => {
|
|
26
23
|
const allowed = new Set([...required, ...optional]);
|
|
27
24
|
return required.every((key) => Object.hasOwn(value, key))
|
|
@@ -177,6 +174,7 @@ function publicCredential(credential, state) {
|
|
|
177
174
|
};
|
|
178
175
|
}
|
|
179
176
|
|
|
177
|
+
/** @public Exposed to integration tests that dynamically import the auth module. */
|
|
180
178
|
export function createGitHubAuthStore({ filePath, fsImpl = fs, lockWaitMs = 2_000 } = {}) {
|
|
181
179
|
if (!validText(filePath)) throw new TypeError('GitHub auth file path is required');
|
|
182
180
|
let transactions = Promise.resolve();
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isString } from '../shared/guards.js';
|
|
2
|
+
|
|
1
3
|
const DEVICE_CODE_URL = 'https://github.com/login/device/code';
|
|
2
4
|
const ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token';
|
|
3
5
|
const DEVICE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
|
|
@@ -11,8 +13,6 @@ const encodeForm = (params) => {
|
|
|
11
13
|
return body.toString();
|
|
12
14
|
};
|
|
13
15
|
|
|
14
|
-
const isString = (value) => Object.prototype.toString.call(value) === '[object String]';
|
|
15
|
-
|
|
16
16
|
async function postForm(url, params, { fetch: fetchImpl = fetch, timeoutMs = 10_000 } = {}) {
|
|
17
17
|
const response = await fetchImpl(url, {
|
|
18
18
|
method: 'POST',
|
|
@@ -108,6 +108,37 @@ export async function getOctokitOrNull() {
|
|
|
108
108
|
return createOctokit(token, accountId);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* The gh CLI account as a credential record shaped like a persisted one, for
|
|
113
|
+
* Git transport. Same consent rule as getOctokitForAccountId: only while the
|
|
114
|
+
* user has switched to the gh account, and only when gh is still signed in
|
|
115
|
+
* as that same GitHub user. The CLI credential has no revisions beyond 1.
|
|
116
|
+
*/
|
|
117
|
+
export async function getGitHubCliCredential(accountId, revision) {
|
|
118
|
+
if (!accountId.startsWith('github.com#cli:') || !isGhCliActive() || isGhCliDisabled()) return null;
|
|
119
|
+
if (revision !== undefined && revision !== 1) return null;
|
|
120
|
+
const token = getGhCliToken();
|
|
121
|
+
if (!token) return null;
|
|
122
|
+
let response;
|
|
123
|
+
try {
|
|
124
|
+
response = await createOctokit(token, accountId).rest.users.getAuthenticated();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error?.status === 401) return null;
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
if (githubCliAccountId(response.data.id) !== accountId) return null;
|
|
130
|
+
return {
|
|
131
|
+
credentialId: accountId,
|
|
132
|
+
accountId,
|
|
133
|
+
credentialRevision: 1,
|
|
134
|
+
providerUserId: githubAccountId(response.data.id),
|
|
135
|
+
accessToken: token,
|
|
136
|
+
source: 'cli',
|
|
137
|
+
status: 'valid',
|
|
138
|
+
user: response.data,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
111
142
|
export async function getOctokitForAccountId(accountId, options = {}) {
|
|
112
143
|
const auth = await getGitHubAuthByAccountId(accountId);
|
|
113
144
|
if (auth) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { stat } from 'node:fs/promises';
|
|
2
|
-
import { getRemotes, getTrackingBranch, isAncestorOfHead } from '../git/index.js';
|
|
2
|
+
import { getRemotes, getTrackingBranch, isAncestorOfHead, isPatchEquivalentOfHead } from '../git/index.js';
|
|
3
3
|
import { resolveGitHubRepoFromDirectory } from './repo/index.js';
|
|
4
4
|
import { noteIfGitHubRateLimit } from './rate-limit.js';
|
|
5
5
|
import { getOctokitCacheIdentity } from './octokit.js';
|
|
@@ -596,14 +596,25 @@ const isTerminalPr = (pr) => Boolean(pr) && (pr.state === 'closed' || Boolean(pr
|
|
|
596
596
|
// fresh worktree called `feature` cut from the default branch would inherit
|
|
597
597
|
// the merged PR of last month's `feature`. The PR only belongs to this checkout
|
|
598
598
|
// when the commit it was merged or closed at is part of the checkout's history.
|
|
599
|
-
//
|
|
600
|
-
|
|
599
|
+
// A rebase can preserve its patch while replacing its commit. Both checks are
|
|
600
|
+
// injectable so focused tests need no git repository.
|
|
601
|
+
const isHistoricalPrOfCheckout = async (directory, pr, {
|
|
602
|
+
isAncestor = isAncestorOfHead,
|
|
603
|
+
isPatchEquivalent = isPatchEquivalentOfHead,
|
|
604
|
+
} = {}) => {
|
|
601
605
|
const headSha = normalizeText(pr?.head?.sha);
|
|
602
606
|
if (!headSha) {
|
|
603
607
|
return false;
|
|
604
608
|
}
|
|
605
609
|
try {
|
|
606
|
-
|
|
610
|
+
if (await isAncestor(directory, headSha)) {
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
} catch {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
try {
|
|
617
|
+
return await isPatchEquivalent(directory, headSha);
|
|
607
618
|
} catch {
|
|
608
619
|
return false;
|
|
609
620
|
}
|
|
@@ -620,10 +631,10 @@ export { isHistoricalPrOfCheckout };
|
|
|
620
631
|
* the same head. The caller must prefer an open PR from ANY target over a
|
|
621
632
|
* historical one — otherwise a merged fork PR hides an open upstream PR.
|
|
622
633
|
*
|
|
623
|
-
* `includeHistory` is off by default and
|
|
624
|
-
* targets. Live status is worth searching
|
|
625
|
-
*
|
|
626
|
-
*
|
|
634
|
+
* `includeHistory` is off by default and is only enabled for primary-source
|
|
635
|
+
* targets. Live status is worth searching every configured remote; history is
|
|
636
|
+
* limited to the branch's primary fork network so unrelated remotes cannot
|
|
637
|
+
* multiply serial GitHub calls until the route hits its resolve timeout.
|
|
627
638
|
*/
|
|
628
639
|
const findBranchPrCandidates = async ({ octokit, target, branch, sourceCandidates, force = false, coverage = null, includeHistory = false }) => {
|
|
629
640
|
const matcher = buildSourceMatcher(sourceCandidates);
|
|
@@ -659,7 +670,7 @@ const findBranchPrCandidates = async ({ octokit, target, branch, sourceCandidate
|
|
|
659
670
|
return { open: null, historical: null };
|
|
660
671
|
}
|
|
661
672
|
|
|
662
|
-
const historicalKey = scopedCacheKey(octokit, `${normalizeRepoKey(target.repo?.owner, target.repo?.repo)}::${branch}`);
|
|
673
|
+
const historicalKey = scopedCacheKey(octokit, `${normalizeRepoKey(target.repo?.owner, target.repo?.repo)}::${branch}::${sourceOwners.map(normalizeLower).filter(Boolean).join(',')}`);
|
|
663
674
|
if (historicalKey && includeHistory && !force && openListWasComplete) {
|
|
664
675
|
const cached = _historicalPrCache.get(historicalKey);
|
|
665
676
|
if (isHistoricalPrCacheFresh(cached)) {
|
|
@@ -707,7 +718,7 @@ const findBranchPrCandidates = async ({ octokit, target, branch, sourceCandidate
|
|
|
707
718
|
// Exported for focused unit tests of open-versus-historical branch matching.
|
|
708
719
|
export { findBranchPrCandidates };
|
|
709
720
|
|
|
710
|
-
export async function resolveGitHubPrStatus({ octokit, directory, branch, remoteName, force = false }) {
|
|
721
|
+
export async function resolveGitHubPrStatus({ octokit, directory, branch, remoteName, sourceRemoteName = null, force = false }) {
|
|
711
722
|
// A deleted worktree can still have a session in the sidebar that keeps
|
|
712
723
|
// requesting its PR status. Bail before touching git or GitHub for a
|
|
713
724
|
// directory that no longer exists — otherwise every poll spends a git call
|
|
@@ -755,10 +766,15 @@ export async function resolveGitHubPrStatus({ octokit, directory, branch, remote
|
|
|
755
766
|
// forks — are places to look for an open PR, but their `owner:branch`
|
|
756
767
|
// heads are unrelated branches that merely share a name; treating them as
|
|
757
768
|
// sources made a fork's closed `main` PR show up on the local main.
|
|
769
|
+
// A branch checked out from a contributor's fork PR comes from that fork's
|
|
770
|
+
// remote, which the caller knows from the worktree's provenance.
|
|
758
771
|
const primaryRemoteName = resolvedTargets[0]?.remoteName ?? null;
|
|
759
|
-
const
|
|
760
|
-
(target) => target.remoteName ===
|
|
761
|
-
|
|
772
|
+
const contributorSources = sourceRemoteName
|
|
773
|
+
? resolvedTargets.filter((target) => target.remoteName === sourceRemoteName)
|
|
774
|
+
: [];
|
|
775
|
+
const sourceCandidates = contributorSources.length > 0
|
|
776
|
+
? contributorSources
|
|
777
|
+
: resolvedTargets.filter((target) => target.remoteName === primaryRemoteName);
|
|
762
778
|
// When every consulted repo list was complete, a no-PR result is
|
|
763
779
|
// authoritative and the expensive Search API fallback is pointless.
|
|
764
780
|
const coverage = { authoritative: true };
|
|
@@ -786,11 +802,12 @@ export async function resolveGitHubPrStatus({ octokit, directory, branch, remote
|
|
|
786
802
|
continue;
|
|
787
803
|
}
|
|
788
804
|
|
|
789
|
-
// History is only asked
|
|
790
|
-
//
|
|
791
|
-
//
|
|
792
|
-
//
|
|
793
|
-
|
|
805
|
+
// History is only asked for the branch's own name and its primary remote
|
|
806
|
+
// network. That includes a fork's parent/source, where a fork-owned PR
|
|
807
|
+
// may have been merged, while excluding unrelated contributor remotes.
|
|
808
|
+
// A contributor fork checkout's PR merges into the primary remote, which
|
|
809
|
+
// is not among its source candidates.
|
|
810
|
+
const isPrimaryAssociation = (target === resolvedTargets[0] || sourceCandidates.includes(target)) && candidateBranch === branchCandidates[0] && candidateBranch !== defaultBranch;
|
|
794
811
|
|
|
795
812
|
const { open, historical } = await findBranchPrCandidates({
|
|
796
813
|
octokit,
|