@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
|
@@ -324,6 +324,20 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
324
324
|
oauthFlowRegistry = createOAuthFlowRegistry();
|
|
325
325
|
return oauthFlowRegistry;
|
|
326
326
|
};
|
|
327
|
+
// A worktree checked out from a contributor's fork PR deliberately has no
|
|
328
|
+
// upstream, so the branch alone looks like it came from the primary remote.
|
|
329
|
+
// Its provenance names the fork remote the branch really came from. Without
|
|
330
|
+
// provenance (or when it cannot be read) status resolves as for any branch.
|
|
331
|
+
const readContributorSourceRemote = async (directory, branch) => {
|
|
332
|
+
if (!(options.readContributorProvenance instanceof Function)) return null;
|
|
333
|
+
try {
|
|
334
|
+
const { provenance } = await options.readContributorProvenance(directory);
|
|
335
|
+
return provenance?.kind === 'contributor-fork' && provenance.provider === 'github'
|
|
336
|
+
&& provenance.sourceRef === `refs/heads/${branch}` ? provenance.remoteName : null;
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
327
341
|
const sendOAuthFlowError = (res, error) => {
|
|
328
342
|
if (error?.code === 'SOURCE_CONTROL_OAUTH_FLOW_BUSY') {
|
|
329
343
|
return res.status(409).json({ error: 'OAuth flow is busy', code: error.code });
|
|
@@ -441,6 +455,25 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
441
455
|
error.status = status;
|
|
442
456
|
return error;
|
|
443
457
|
};
|
|
458
|
+
// GitHub's own limit for a comment body.
|
|
459
|
+
const COMMENT_MAX_LENGTH = 65536;
|
|
460
|
+
const commentText = (value, required) => {
|
|
461
|
+
if (value === undefined && !required) return undefined;
|
|
462
|
+
if (!isProviderString(value) || !value.trim()) throw canonicalMutationError('body is required');
|
|
463
|
+
if (value.length > COMMENT_MAX_LENGTH) throw canonicalMutationError('body is too long');
|
|
464
|
+
return value;
|
|
465
|
+
};
|
|
466
|
+
const ISSUE_KINDS = new Set(['issue-comment', 'issue-state', 'issue-labels']);
|
|
467
|
+
// Labels or reviewers as one whole set, so a repeat asks for the same result.
|
|
468
|
+
const nameList = (value, name, limit) => {
|
|
469
|
+
if (!Array.isArray(value) || value.length > limit
|
|
470
|
+
|| value.some((entry) => !isProviderString(entry) || !entry.trim() || entry.length > 256)) {
|
|
471
|
+
throw canonicalMutationError(`${name} are invalid`);
|
|
472
|
+
}
|
|
473
|
+
return [...new Set(value)].sort();
|
|
474
|
+
};
|
|
475
|
+
const sameNames = (left, right) => left.length === right.length && [...left].sort().every((entry, index) => entry === right[index]);
|
|
476
|
+
const mutationRecordKind = (kind) => (ISSUE_KINDS.has(kind) ? kind : `change-request-${kind}`);
|
|
444
477
|
const requiredMutationText = (value, name) => {
|
|
445
478
|
if (!isProviderString(value) || !value.trim()) throw canonicalMutationError(`${name} is required`);
|
|
446
479
|
return value.trim();
|
|
@@ -517,7 +550,28 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
517
550
|
const { number, ...result } = receipt.result;
|
|
518
551
|
return { ...receipt, target: { ...receipt.target, number }, result };
|
|
519
552
|
};
|
|
520
|
-
const reconcileExistingMutation = async (octokit, kind, target) => {
|
|
553
|
+
const reconcileExistingMutation = async (octokit, kind, target, operation) => {
|
|
554
|
+
// A comment or a review leaves nothing that tells a lost write from one
|
|
555
|
+
// never sent short of guessing from its text, so the outcome stays unknown.
|
|
556
|
+
if (kind === 'comment' || kind === 'review' || kind === 'issue-comment') return { state: 'outcome-unknown' };
|
|
557
|
+
if (kind === 'labels' || kind === 'issue-labels') {
|
|
558
|
+
const current = await octokit.rest.issues.listLabelsOnIssue({
|
|
559
|
+
owner: target.project.owner, repo: target.project.name, issue_number: target.number, per_page: 100,
|
|
560
|
+
});
|
|
561
|
+
const names = requireProviderArray(current?.data, 'GitHub returned an invalid label list').map((label) => label?.name);
|
|
562
|
+
// Another edit since may have changed them either way: only a match is certain.
|
|
563
|
+
return names.every(isProviderString) && sameNames(names, operation.labels) ? { state: 'succeeded', result: {} } : { state: 'outcome-unknown' };
|
|
564
|
+
}
|
|
565
|
+
if (kind === 'reviewers') {
|
|
566
|
+
const current = await requestedReviewers(octokit, target.project, target.number);
|
|
567
|
+
return sameNames(current, operation.reviewers) ? { state: 'succeeded', result: {} } : { state: 'outcome-unknown' };
|
|
568
|
+
}
|
|
569
|
+
if (kind === 'issue-state') {
|
|
570
|
+
const current = await octokit.rest.issues.get({ owner: target.project.owner, repo: target.project.name, issue_number: target.number });
|
|
571
|
+
const state = current?.data?.state;
|
|
572
|
+
if (state !== 'open' && state !== 'closed') return { state: 'outcome-unknown' };
|
|
573
|
+
return state === operation.state ? { state: 'succeeded', result: { state } } : definiteFailure('SOURCE_CONTROL_MUTATION_NOT_APPLIED');
|
|
574
|
+
}
|
|
521
575
|
const current = await octokit.rest.pulls.get({
|
|
522
576
|
owner: target.project.owner,
|
|
523
577
|
repo: target.project.name,
|
|
@@ -531,6 +585,10 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
531
585
|
if (currentPr.state === 'open' && currentPr.merged === false) {
|
|
532
586
|
return definiteFailure('SOURCE_CONTROL_MUTATION_NOT_APPLIED');
|
|
533
587
|
}
|
|
588
|
+
} else if (kind === 'state') {
|
|
589
|
+
// A merged pull request reads `closed` too, and can no longer be reopened.
|
|
590
|
+
if (currentPr.state === operation.state) return { state: 'succeeded', result: { state: currentPr.state } };
|
|
591
|
+
if (currentPr.state === 'open' || currentPr.state === 'closed') return definiteFailure('SOURCE_CONTROL_MUTATION_NOT_APPLIED');
|
|
534
592
|
} else if (kind === 'ready') {
|
|
535
593
|
if (currentPr.draft === false) return { state: 'succeeded', result: { ready: true } };
|
|
536
594
|
if (currentPr.draft === true && currentPr.state === 'open') {
|
|
@@ -548,6 +606,73 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
548
606
|
}
|
|
549
607
|
return 'outcome-unknown';
|
|
550
608
|
};
|
|
609
|
+
// GitHub's own reason for refusing a write (a locked conversation, a review
|
|
610
|
+
// of your own pull request) is what the user needs to read; the generic
|
|
611
|
+
// mutation error would hide it.
|
|
612
|
+
const providerRejection = (error) => {
|
|
613
|
+
const status = error?.response?.status ?? error?.status;
|
|
614
|
+
if (![403, 404, 422].includes(status)) return error;
|
|
615
|
+
const data = error?.response?.data;
|
|
616
|
+
const first = Array.isArray(data?.errors) ? data.errors[0] : undefined;
|
|
617
|
+
const detail = [first, first?.message, data?.message].find((value) => isProviderString(value) && value.trim());
|
|
618
|
+
return canonicalMutationError(detail ?? 'GitHub refused the change', 'SOURCE_CONTROL_MUTATION_REJECTED', status);
|
|
619
|
+
};
|
|
620
|
+
const postComment = async (octokit, project, number, body) => {
|
|
621
|
+
let response;
|
|
622
|
+
try {
|
|
623
|
+
response = await octokit.rest.issues.createComment({ owner: project.owner, repo: project.name, issue_number: number, body });
|
|
624
|
+
} catch (error) {
|
|
625
|
+
throw providerRejection(error);
|
|
626
|
+
}
|
|
627
|
+
if (!isPlainObject(response?.data) || !Number.isSafeInteger(response.data.id)) {
|
|
628
|
+
throw invalidProviderPayload('GitHub returned an invalid comment');
|
|
629
|
+
}
|
|
630
|
+
return {};
|
|
631
|
+
};
|
|
632
|
+
const setLabels = async (octokit, project, number, labels) => {
|
|
633
|
+
let response;
|
|
634
|
+
try {
|
|
635
|
+
response = await octokit.rest.issues.setLabels({ owner: project.owner, repo: project.name, issue_number: number, labels });
|
|
636
|
+
} catch (error) {
|
|
637
|
+
throw providerRejection(error);
|
|
638
|
+
}
|
|
639
|
+
requireProviderArray(response?.data, 'GitHub returned an invalid label list');
|
|
640
|
+
return {};
|
|
641
|
+
};
|
|
642
|
+
const requestedReviewers = async (octokit, project, number) => {
|
|
643
|
+
const response = await octokit.rest.pulls.listRequestedReviewers({ owner: project.owner, repo: project.name, pull_number: number });
|
|
644
|
+
const users = response?.data?.users;
|
|
645
|
+
if (!Array.isArray(users) || users.some((user) => !isProviderString(user?.login))) {
|
|
646
|
+
throw invalidProviderPayload('GitHub returned an invalid reviewer list');
|
|
647
|
+
}
|
|
648
|
+
return users.map((user) => user.login);
|
|
649
|
+
};
|
|
650
|
+
// GitHub adds and removes review requests separately. Asking again for the
|
|
651
|
+
// same set is safe, so a failure between the two is fixed by trying again.
|
|
652
|
+
const setReviewers = async (octokit, project, number, reviewers) => {
|
|
653
|
+
const current = await requestedReviewers(octokit, project, number);
|
|
654
|
+
const add = reviewers.filter((login) => !current.includes(login));
|
|
655
|
+
const remove = current.filter((login) => !reviewers.includes(login));
|
|
656
|
+
const pull = { owner: project.owner, repo: project.name, pull_number: number };
|
|
657
|
+
try {
|
|
658
|
+
if (add.length) await octokit.rest.pulls.requestReviewers({ ...pull, reviewers: add });
|
|
659
|
+
if (remove.length) await octokit.rest.pulls.removeRequestedReviewers({ ...pull, reviewers: remove });
|
|
660
|
+
} catch (error) {
|
|
661
|
+
throw providerRejection(error);
|
|
662
|
+
}
|
|
663
|
+
return {};
|
|
664
|
+
};
|
|
665
|
+
// Closing or reopening an issue or a pull request; GitHub refuses to reopen a merged one.
|
|
666
|
+
const setState = async (update, state) => {
|
|
667
|
+
let response;
|
|
668
|
+
try {
|
|
669
|
+
response = await update();
|
|
670
|
+
} catch (error) {
|
|
671
|
+
throw providerRejection(error);
|
|
672
|
+
}
|
|
673
|
+
if (response?.data?.state !== state) throw invalidProviderPayload('GitHub returned an unexpected state');
|
|
674
|
+
return { state };
|
|
675
|
+
};
|
|
551
676
|
const sendCanonicalMutationError = async (res, error) => {
|
|
552
677
|
if (error?.status === 401) await invalidateRequestAccount(error);
|
|
553
678
|
const status = Number.isSafeInteger(error?.status) && error.status >= 400 && error.status <= 599
|
|
@@ -617,12 +742,45 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
617
742
|
return { method, digest: (target) => digestMutationInput({ kind, actor, credential, target, method }) };
|
|
618
743
|
}
|
|
619
744
|
if (kind === 'ready') return { digest: (target) => digestMutationInput({ kind, actor, credential, target }) };
|
|
745
|
+
if (kind === 'labels' || kind === 'issue-labels') {
|
|
746
|
+
const labels = nameList(req.body?.labels, 'labels', 100);
|
|
747
|
+
return { labels, digest: (target) => digestMutationInput({ kind, actor, credential, target, labels }) };
|
|
748
|
+
}
|
|
749
|
+
if (kind === 'reviewers') {
|
|
750
|
+
const reviewers = nameList(req.body?.reviewers, 'reviewers', 50);
|
|
751
|
+
return { reviewers, digest: (target) => digestMutationInput({ kind, actor, credential, target, reviewers }) };
|
|
752
|
+
}
|
|
753
|
+
if (kind === 'state' || kind === 'issue-state') {
|
|
754
|
+
const state = req.body?.state;
|
|
755
|
+
if (state !== 'open' && state !== 'closed') throw canonicalMutationError('state is invalid');
|
|
756
|
+
return { state, digest: (target) => digestMutationInput({ kind, actor, credential, target, state }) };
|
|
757
|
+
}
|
|
758
|
+
if (kind === 'comment' || kind === 'issue-comment') {
|
|
759
|
+
const body = commentText(req.body?.body, true);
|
|
760
|
+
return { body, digest: (target) => digestMutationInput({ kind, actor, credential, target, body }) };
|
|
761
|
+
}
|
|
762
|
+
if (kind === 'review') {
|
|
763
|
+
const verdict = req.body?.verdict;
|
|
764
|
+
if (verdict !== 'approve' && verdict !== 'request-changes') throw canonicalMutationError('review verdict is invalid');
|
|
765
|
+
// A verdict names the commit it is for; without one it would land on whatever was pushed last.
|
|
766
|
+
requiredMutationText(context.target.headSha, 'target headSha');
|
|
767
|
+
// GitHub asks for the reason when changes are requested.
|
|
768
|
+
const body = commentText(req.body?.body, verdict === 'request-changes');
|
|
769
|
+
return {
|
|
770
|
+
verdict, body,
|
|
771
|
+
digest(target) {
|
|
772
|
+
const input = { kind, actor, credential, target, verdict };
|
|
773
|
+
if (body !== undefined) input.body = body;
|
|
774
|
+
return digestMutationInput(input);
|
|
775
|
+
},
|
|
776
|
+
};
|
|
777
|
+
}
|
|
620
778
|
throw canonicalMutationError('Mutation kind is invalid');
|
|
621
779
|
};
|
|
622
780
|
const storedMutationAuthorityMatches = (record, kind, actor, context) => {
|
|
623
781
|
const target = record?.target;
|
|
624
782
|
const expected = context.target;
|
|
625
|
-
return record?.kind ===
|
|
783
|
+
return record?.kind === mutationRecordKind(kind)
|
|
626
784
|
&& record.actor?.provider === actor.provider
|
|
627
785
|
&& record.actor?.instance === actor.instance
|
|
628
786
|
&& record.actor?.accountId === actor.accountId
|
|
@@ -702,7 +860,7 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
702
860
|
if (!sourceProject) return { state: 'outcome-unknown' };
|
|
703
861
|
return reconcileCreateMutation(octokit, expectedProject, sourceProject, operation.head, operation.base);
|
|
704
862
|
}
|
|
705
|
-
return reconcileExistingMutation(octokit, kind, target);
|
|
863
|
+
return reconcileExistingMutation(octokit, kind, target, operation);
|
|
706
864
|
};
|
|
707
865
|
const execution = await options.mutationExecutor.execute({
|
|
708
866
|
record: {
|
|
@@ -771,6 +929,34 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
771
929
|
return { number: created.number };
|
|
772
930
|
};
|
|
773
931
|
reconcile = () => reconcileCreateMutation(octokit, expectedProject, sourceProject, head, base);
|
|
932
|
+
} else if (ISSUE_KINDS.has(kind)) {
|
|
933
|
+
const expected = context.target;
|
|
934
|
+
if (!Number.isSafeInteger(expected.number) || expected.number < 1
|
|
935
|
+
|| expected.head !== undefined || expected.base !== undefined || expected.headSha !== undefined) {
|
|
936
|
+
throw canonicalMutationError('Issue target is invalid');
|
|
937
|
+
}
|
|
938
|
+
const response = await octokit.rest.issues.get({
|
|
939
|
+
owner: expectedProject.owner, repo: expectedProject.name, issue_number: expected.number,
|
|
940
|
+
});
|
|
941
|
+
const issue = response?.data;
|
|
942
|
+
if (!isPlainObject(issue) || issue.number !== expected.number) throw invalidProviderPayload('GitHub returned an invalid issue');
|
|
943
|
+
// GitHub lists pull requests among issues; this write is for issues only.
|
|
944
|
+
if (issue.pull_request) {
|
|
945
|
+
throw canonicalMutationError('GitHub issue is a pull request', 'SOURCE_CONTROL_MUTATION_TARGET_INVALID', 409);
|
|
946
|
+
}
|
|
947
|
+
target = {
|
|
948
|
+
repositoryId: context.repositoryId,
|
|
949
|
+
bindingRevision: context.bindingRevision,
|
|
950
|
+
primaryRemote: context.primaryRemote,
|
|
951
|
+
project: { id: `${expectedProject.owner}/${expectedProject.name}`, owner: expectedProject.owner, name: expectedProject.name },
|
|
952
|
+
number: issue.number,
|
|
953
|
+
};
|
|
954
|
+
if (kind === 'issue-comment') perform = () => postComment(octokit, expectedProject, target.number, operation.body);
|
|
955
|
+
else if (kind === 'issue-labels') perform = () => setLabels(octokit, expectedProject, target.number, operation.labels);
|
|
956
|
+
else perform = () => setState(() => octokit.rest.issues.update({
|
|
957
|
+
owner: expectedProject.owner, repo: expectedProject.name, issue_number: target.number, state: operation.state,
|
|
958
|
+
}), operation.state);
|
|
959
|
+
reconcile = () => reconcileExistingMutation(octokit, kind, target, operation);
|
|
774
960
|
} else {
|
|
775
961
|
if (!Number.isSafeInteger(context.target.number) || context.target.number < 1) {
|
|
776
962
|
throw canonicalMutationError('target number is required');
|
|
@@ -815,6 +1001,45 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
815
1001
|
return { merged: data.merged };
|
|
816
1002
|
};
|
|
817
1003
|
reconcile = () => reconcileExistingMutation(octokit, kind, target);
|
|
1004
|
+
} else if (kind === 'labels') {
|
|
1005
|
+
perform = () => setLabels(octokit, expectedProject, target.number, operation.labels);
|
|
1006
|
+
reconcile = () => reconcileExistingMutation(octokit, kind, target, operation);
|
|
1007
|
+
} else if (kind === 'reviewers') {
|
|
1008
|
+
perform = () => setReviewers(octokit, expectedProject, target.number, operation.reviewers);
|
|
1009
|
+
reconcile = () => reconcileExistingMutation(octokit, kind, target, operation);
|
|
1010
|
+
} else if (kind === 'state') {
|
|
1011
|
+
perform = () => setState(() => octokit.rest.pulls.update({
|
|
1012
|
+
owner: expectedProject.owner, repo: expectedProject.name, pull_number: target.number, state: operation.state,
|
|
1013
|
+
}), operation.state);
|
|
1014
|
+
reconcile = () => reconcileExistingMutation(octokit, kind, target, operation);
|
|
1015
|
+
} else if (kind === 'comment') {
|
|
1016
|
+
perform = () => postComment(octokit, expectedProject, target.number, operation.body);
|
|
1017
|
+
reconcile = () => reconcileExistingMutation(octokit, kind, target);
|
|
1018
|
+
} else if (kind === 'review') {
|
|
1019
|
+
const { verdict, body } = operation;
|
|
1020
|
+
perform = async () => {
|
|
1021
|
+
const reviewInput = {
|
|
1022
|
+
owner: expectedProject.owner,
|
|
1023
|
+
repo: expectedProject.name,
|
|
1024
|
+
pull_number: target.number,
|
|
1025
|
+
// The verdict is for the commit the user read.
|
|
1026
|
+
commit_id: target.headSha,
|
|
1027
|
+
event: verdict === 'approve' ? 'APPROVE' : 'REQUEST_CHANGES',
|
|
1028
|
+
};
|
|
1029
|
+
if (body !== undefined) reviewInput.body = body;
|
|
1030
|
+
let response;
|
|
1031
|
+
try {
|
|
1032
|
+
response = await octokit.rest.pulls.createReview(reviewInput);
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
throw providerRejection(error);
|
|
1035
|
+
}
|
|
1036
|
+
if (response?.data?.state !== (verdict === 'approve' ? 'APPROVED' : 'CHANGES_REQUESTED')) {
|
|
1037
|
+
throw invalidProviderPayload('GitHub returned an invalid review');
|
|
1038
|
+
}
|
|
1039
|
+
// GitHub posts the verdict and its text as one review.
|
|
1040
|
+
return { commented: body !== undefined };
|
|
1041
|
+
};
|
|
1042
|
+
reconcile = () => reconcileExistingMutation(octokit, kind, target);
|
|
818
1043
|
} else {
|
|
819
1044
|
const alreadyReady = pr.draft === false;
|
|
820
1045
|
const nodeId = optionalMutationText(pr.node_id, 'pull request node id', true);
|
|
@@ -840,7 +1065,7 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
840
1065
|
record: {
|
|
841
1066
|
key: context.idempotencyKey,
|
|
842
1067
|
inputDigest: operation.digest(target),
|
|
843
|
-
kind:
|
|
1068
|
+
kind: mutationRecordKind(kind),
|
|
844
1069
|
actor,
|
|
845
1070
|
target,
|
|
846
1071
|
},
|
|
@@ -1359,12 +1584,14 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
1359
1584
|
|
|
1360
1585
|
const resolveGitHubPrStatus = options.resolveGitHubPrStatus
|
|
1361
1586
|
?? (await import('./pr-status.js')).resolveGitHubPrStatus;
|
|
1587
|
+
const sourceRemoteName = await readContributorSourceRemote(directory, branch);
|
|
1362
1588
|
const resolvedStatus = await withTimeout(
|
|
1363
1589
|
resolveGitHubPrStatus({
|
|
1364
1590
|
octokit,
|
|
1365
1591
|
directory,
|
|
1366
1592
|
branch,
|
|
1367
1593
|
remoteName: remote,
|
|
1594
|
+
sourceRemoteName,
|
|
1368
1595
|
force,
|
|
1369
1596
|
}),
|
|
1370
1597
|
PR_STATUS_RESOLVE_TIMEOUT_MS,
|
|
@@ -1537,6 +1764,22 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
1537
1764
|
|
|
1538
1765
|
app.post(canonicalGitHubRoutePath('/pr/ready'), (req, res) => runCanonicalMutation('ready', req, res));
|
|
1539
1766
|
|
|
1767
|
+
app.post(canonicalGitHubRoutePath('/pr/comment'), (req, res) => runCanonicalMutation('comment', req, res));
|
|
1768
|
+
|
|
1769
|
+
app.post(canonicalGitHubRoutePath('/pr/review'), (req, res) => runCanonicalMutation('review', req, res));
|
|
1770
|
+
|
|
1771
|
+
app.post(canonicalGitHubRoutePath('/issues/comment'), (req, res) => runCanonicalMutation('issue-comment', req, res));
|
|
1772
|
+
|
|
1773
|
+
app.post(canonicalGitHubRoutePath('/pr/state'), (req, res) => runCanonicalMutation('state', req, res));
|
|
1774
|
+
|
|
1775
|
+
app.post(canonicalGitHubRoutePath('/issues/state'), (req, res) => runCanonicalMutation('issue-state', req, res));
|
|
1776
|
+
|
|
1777
|
+
app.post(canonicalGitHubRoutePath('/pr/labels'), (req, res) => runCanonicalMutation('labels', req, res));
|
|
1778
|
+
|
|
1779
|
+
app.post(canonicalGitHubRoutePath('/issues/labels'), (req, res) => runCanonicalMutation('issue-labels', req, res));
|
|
1780
|
+
|
|
1781
|
+
app.post(canonicalGitHubRoutePath('/pr/reviewers'), (req, res) => runCanonicalMutation('reviewers', req, res));
|
|
1782
|
+
|
|
1540
1783
|
app.get(canonicalGitHubRoutePath('/repo/upstream'), async (req, res) => {
|
|
1541
1784
|
try {
|
|
1542
1785
|
const directory = readQueryString(req, 'directory');
|
|
@@ -1701,7 +1944,7 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
1701
1944
|
// repository network. Failures are errors, never an empty page: the picker
|
|
1702
1945
|
// keeps what it showed and offers a retry.
|
|
1703
1946
|
app.get(canonicalGitHubRoutePath('/references'), async (req, res) => {
|
|
1704
|
-
const {
|
|
1947
|
+
const { readReferencePeople, readReferenceKind, readReferenceState, searchGitHubReferences } = await import('./reference-search.js');
|
|
1705
1948
|
const directory = readQueryString(req, 'directory');
|
|
1706
1949
|
const kind = readReferenceKind(req.query?.kind);
|
|
1707
1950
|
if (!directory || !kind) {
|
|
@@ -1735,7 +1978,8 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
1735
1978
|
octokit,
|
|
1736
1979
|
repos,
|
|
1737
1980
|
kind,
|
|
1738
|
-
|
|
1981
|
+
state: readReferenceState(req.query?.state),
|
|
1982
|
+
people: readReferencePeople(req.query?.people),
|
|
1739
1983
|
text: readQueryString(req, 'query'),
|
|
1740
1984
|
cursor: readQueryString(req, 'cursor') || null,
|
|
1741
1985
|
});
|
|
@@ -1825,6 +2069,60 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
1825
2069
|
}
|
|
1826
2070
|
});
|
|
1827
2071
|
|
|
2072
|
+
// The repository's labels and the people who can be asked to review in it,
|
|
2073
|
+
// for the board's pickers. The repository must be in the bound project's
|
|
2074
|
+
// network, as for the detail above; one page of 100 is what the pickers show.
|
|
2075
|
+
const readRepositoryChoices = (suffix, read) => app.get(canonicalGitHubRoutePath(suffix), async (req, res) => {
|
|
2076
|
+
const directory = readQueryString(req, 'directory');
|
|
2077
|
+
const owner = readQueryString(req, 'owner');
|
|
2078
|
+
const repoName = readQueryString(req, 'repo');
|
|
2079
|
+
if (!directory || !owner || !repoName) {
|
|
2080
|
+
return res.status(400).json({ error: 'directory, owner and repo are required' });
|
|
2081
|
+
}
|
|
2082
|
+
try {
|
|
2083
|
+
const trustedContext = await validateReadContext(req, directory);
|
|
2084
|
+
if (!trustedContext) return res.status(501).json({ error: 'Bound source control read is unavailable' });
|
|
2085
|
+
const octokit = (await getOctokitForRead(req, trustedContext))?.octokit;
|
|
2086
|
+
if (!octokit) return res.json({ connected: false });
|
|
2087
|
+
const repo = await resolveRepoForRequest(octokit, directory, { owner, repo: repoName }, trustedContext.primaryRemote, {
|
|
2088
|
+
resolveGitHubRepoFromDirectory: options.resolveGitHubRepoFromDirectory,
|
|
2089
|
+
resolveRepoNetwork: options.resolveRepoNetwork,
|
|
2090
|
+
strictNetworkErrors: true,
|
|
2091
|
+
strictMetadataErrors: true,
|
|
2092
|
+
requireResolvedRepo: true,
|
|
2093
|
+
});
|
|
2094
|
+
if (!repo) return res.status(400).json({ error: 'Repository is not part of this project' });
|
|
2095
|
+
return res.json({ connected: true, items: await read(octokit, repo) });
|
|
2096
|
+
} catch (error) {
|
|
2097
|
+
if (isReadContextError(error)) return res.status(error.status ?? 400).json(readContextErrorBody(error));
|
|
2098
|
+
const accountError = sendExactAccountError(res, error);
|
|
2099
|
+
if (accountError) return accountError;
|
|
2100
|
+
if (error?.status === 401) {
|
|
2101
|
+
await invalidateRequestAccount(error);
|
|
2102
|
+
return res.json({ connected: false });
|
|
2103
|
+
}
|
|
2104
|
+
return res.status(500).json({ error: error.message || 'Failed to read the repository' });
|
|
2105
|
+
}
|
|
2106
|
+
});
|
|
2107
|
+
|
|
2108
|
+
readRepositoryChoices('/references/labels', async (octokit, repo) => {
|
|
2109
|
+
const response = await octokit.rest.issues.listLabelsForRepo({ owner: repo.owner, repo: repo.repo, per_page: 100 });
|
|
2110
|
+
return requireProviderArray(response?.data, 'GitHub returned an invalid label list').flatMap((label) => {
|
|
2111
|
+
if (!isProviderString(label?.name) || !label.name) return [];
|
|
2112
|
+
return [isProviderString(label.color) && label.color ? { name: label.name, color: label.color } : { name: label.name }];
|
|
2113
|
+
});
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
readRepositoryChoices('/references/reviewers', async (octokit, repo) => {
|
|
2117
|
+
const response = await octokit.rest.issues.listAssignees({ owner: repo.owner, repo: repo.repo, per_page: 100 });
|
|
2118
|
+
return requireProviderArray(response?.data, 'GitHub returned an invalid user list').flatMap((user) => {
|
|
2119
|
+
if (!isProviderString(user?.login) || !user.login) return [];
|
|
2120
|
+
return [isProviderString(user.avatar_url) && user.avatar_url
|
|
2121
|
+
? { id: user.login, login: user.login, avatarUrl: user.avatar_url }
|
|
2122
|
+
: { id: user.login, login: user.login }];
|
|
2123
|
+
});
|
|
2124
|
+
});
|
|
2125
|
+
|
|
1828
2126
|
// What colours the PRs a picker page shows: checks and mergeability, from
|
|
1829
2127
|
// the same summaries the sidebar reads. Every PR must be in the bound
|
|
1830
2128
|
// repository's network. A PR GitHub could not resolve is left out, which
|
|
@@ -2889,6 +3187,62 @@ export function registerGitHubRoutes(app, options = {}) {
|
|
|
2889
3187
|
});
|
|
2890
3188
|
|
|
2891
3189
|
return Object.freeze({
|
|
3190
|
+
/**
|
|
3191
|
+
* The account GitHub reads use when none is named: the gh login while the
|
|
3192
|
+
* user has switched to it, else OpenChamber's active account. Null when
|
|
3193
|
+
* neither is there.
|
|
3194
|
+
*/
|
|
3195
|
+
readCurrentAccountId: async () => {
|
|
3196
|
+
const { getGitHubAuth, githubCliAccountId, isGhCliActive, isGhCliDisabled, createOctokit } = await getGitHubLibraries();
|
|
3197
|
+
if (isGhCliActive() && !isGhCliDisabled()) {
|
|
3198
|
+
const { getGhCliToken } = await import('./gh-cli-credential.js');
|
|
3199
|
+
const token = getGhCliToken();
|
|
3200
|
+
if (token) {
|
|
3201
|
+
// Only a rejected token means "no account"; a network or server
|
|
3202
|
+
// failure must not read as one, or another host's account answers.
|
|
3203
|
+
try {
|
|
3204
|
+
return githubCliAccountId((await createOctokit(token).rest.users.getAuthenticated()).data.id);
|
|
3205
|
+
} catch (error) {
|
|
3206
|
+
if (error?.status === 401) return null;
|
|
3207
|
+
throw error;
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
return (await getGitHubAuth())?.accountId ?? null;
|
|
3212
|
+
},
|
|
3213
|
+
/** A pull request's head on the context's primary remote, by number, for a worktree made from it. */
|
|
3214
|
+
readChangeRequestHead: async ({ context, number }) => {
|
|
3215
|
+
const trusted = await options.validateReadContext(context);
|
|
3216
|
+
const account = await getOctokitForRequest({}, trusted.accountId);
|
|
3217
|
+
const repo = await resolveRepoForRequest(account.octokit, trusted.directory, null, trusted.primaryRemote, {
|
|
3218
|
+
resolveGitHubRepoFromDirectory: options.resolveGitHubRepoFromDirectory,
|
|
3219
|
+
resolveRepoNetwork: options.resolveRepoNetwork,
|
|
3220
|
+
strictNetworkErrors: true,
|
|
3221
|
+
strictMetadataErrors: true,
|
|
3222
|
+
requireResolvedRepo: true,
|
|
3223
|
+
});
|
|
3224
|
+
let response;
|
|
3225
|
+
try {
|
|
3226
|
+
response = await account.octokit.rest.pulls.get({ owner: repo.owner, repo: repo.repo, pull_number: number });
|
|
3227
|
+
} catch (error) {
|
|
3228
|
+
if (error?.status === 404) {
|
|
3229
|
+
throw canonicalMutationError(`Pull request #${number} was not found in ${repo.owner}/${repo.repo}`, 'SOURCE_CONTROL_CHANGE_REQUEST_NOT_FOUND', 404);
|
|
3230
|
+
}
|
|
3231
|
+
throw error;
|
|
3232
|
+
}
|
|
3233
|
+
const payload = requireGitHubPullRequest(response?.data, 'GitHub returned an invalid pull request');
|
|
3234
|
+
if (!payload.head?.repo) {
|
|
3235
|
+
throw canonicalMutationError(`Pull request #${number} has no branch to check out: its fork was deleted`, 'SOURCE_CONTROL_CHANGE_REQUEST_HEAD_GONE', 409);
|
|
3236
|
+
}
|
|
3237
|
+
const headSha = isProviderString(payload.head.sha) ? payload.head.sha.toLowerCase() : '';
|
|
3238
|
+
if (!/^[0-9a-f]{40}$/.test(headSha)) throw invalidProviderPayload('GitHub returned an invalid pull request head');
|
|
3239
|
+
return {
|
|
3240
|
+
project: { id: `${repo.owner}/${repo.repo}`, owner: repo.owner, name: repo.repo },
|
|
3241
|
+
headSha,
|
|
3242
|
+
headBranch: payload.head.ref,
|
|
3243
|
+
headOwner: payload.head.repo.owner.login,
|
|
3244
|
+
};
|
|
3245
|
+
},
|
|
2892
3246
|
resolveChangeRequestSource: async ({ context, project, number, expectedHeadSha, requestedRemoteName }) => {
|
|
2893
3247
|
const trusted = await options.validateReadContext(context);
|
|
2894
3248
|
const account = await getOctokitForRequest({}, trusted.accountId);
|