@vantran-se/openchamber-web 1.23.0 → 1.24.3
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/bin/cli.test.js +29 -6
- package/bin/lib/cli-http.js +5 -1
- package/bin/lib/commands-status.js +5 -0
- package/dist/assets/{CodeMirrorCommentWidgets-DFg2wnpo.js → CodeMirrorCommentWidgets-BjJHrzmU.js} +1 -1
- package/dist/assets/DiffView-COVQhWGd.js +4 -0
- package/dist/assets/FilesView-B1V5JxFX.js +13 -0
- package/dist/assets/GitView-CDOJy5Ay.js +6 -0
- package/dist/assets/GuestAttachDialog-D3S0Rjic.js +1 -0
- package/dist/assets/{JsonTreeView-BKdsjg5B.js → JsonTreeView-D0C9nU7K.js} +1 -1
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/LinearIssuesView-D7FKg1XJ.js +1 -0
- package/dist/assets/{MarkdownImageGallery-_5gEVr0X.js → MarkdownImageGallery-BrFjAVEf.js} +1 -1
- package/dist/assets/MarkdownRendererImpl-DWepTafh.js +12 -0
- package/dist/assets/{OnboardingScreen-DdP4_nuc.js → OnboardingScreen-Dyxxiqdt.js} +1 -1
- package/dist/assets/{PatchDiff-am3SAss2.js → PatchDiff-DnEhjXcC.js} +1 -1
- package/dist/assets/PierreDiffViewer-DyxZVVVR.js +210 -0
- package/dist/assets/{PlanView-BoJxdRZe.js → PlanView-LW2z_EAP.js} +4 -4
- package/dist/assets/PluginPane-B4tKno2h.js +3 -0
- package/dist/assets/SettingsView-DEaMulN9.js +29 -0
- package/dist/assets/{SettingsWindow-DVTdDSol.js → SettingsWindow-FLDJXOgQ.js} +1 -1
- package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +1 -0
- package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +15 -0
- package/dist/assets/{ToolPartDiffPreview-C7WLk5i9.js → ToolPartDiffPreview-CkR4msKP.js} +2 -2
- package/dist/assets/{VirtualizedFileDiff-DcdgkH-x.js → VirtualizedFileDiff-0pyr_mHY.js} +4 -4
- package/dist/assets/WalkthroughView-yNj4nOP5.js +4 -0
- package/dist/assets/appThemeRegistry-BuUKhbYQ.js +1 -0
- package/dist/assets/commitAndPush-DvJKa3Ks.js +1 -0
- package/dist/assets/{computeVirtualFileMetrics-C8byMnRh.js → computeVirtualFileMetrics-DxTBMuu-.js} +2 -2
- package/dist/assets/de-DgxgFyGG.js +10 -0
- package/dist/assets/es-BFsZj5F_.js +16 -0
- package/dist/assets/fr-jdZ5KKQi.js +14 -0
- package/dist/assets/{index-BB7ryz8y.js → index-CKHjPKbq.js} +1 -1
- package/dist/assets/index-Cbci4i1W.js +40 -0
- package/dist/assets/index-DqaMWNrC.css +1 -0
- package/dist/assets/{isDiffPlainText-45ujMPGb.js → isDiffPlainText-D7FkBMP6.js} +1 -1
- package/dist/assets/{isFilePlainText-Cc4KFSYi.js → isFilePlainText-CL2vsrIZ.js} +1 -1
- package/dist/assets/ja-C5kKrhO4.js +16 -0
- package/dist/assets/ko-DXDTT_0O.js +16 -0
- package/dist/assets/main-CZALtVek.js +896 -0
- package/dist/assets/main-CgXMBNvG.js +2 -0
- package/dist/assets/{markdown-shiki.worker-D5fBU-lw.js → markdown-shiki.worker-CahLvHIq.js} +5 -5
- package/dist/assets/markdownCore-Dh7WEsdM.js +5 -0
- package/dist/assets/miniChat-CwTmDixi.js +2 -0
- package/dist/assets/mobile-CLf2vCtO.js +2 -0
- package/dist/assets/patchFileDiff-Bsba8KFp.js +13 -0
- package/dist/assets/pl-D-7x4Y8Q.js +16 -0
- package/dist/assets/pt-BR-CSBZrHa8.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +2 -0
- package/dist/assets/renderMobileApp-V4hYd32H.js +25 -0
- package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +25 -0
- package/dist/assets/scheduledTasksApi-DzjbLd8P.js +1 -0
- package/dist/assets/shikiHighlight-8_3csmr6.js +1 -0
- package/dist/assets/surface-B6n7wJtt.js +1 -0
- package/dist/assets/textMateThemeFromAppTheme-CHo2fCgW.js +1 -0
- package/dist/assets/tr-Dr-oJ22L.js +7 -0
- package/dist/assets/uk-9Nx83UcI.js +16 -0
- package/dist/assets/useAppFontEffects-khZXpFL_.js +7333 -0
- package/dist/assets/useInlineCommentController-DkQ_gqpn.js +2 -0
- package/dist/assets/useRootScrollLock-DdvHf9Pt.js +2 -0
- package/dist/assets/{vendor-base-ui-DdV8vhHn.js → vendor-base-ui-CsXYnR5c.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DzlSIu_W.js → vendor-codemirror-language-data-CdzUmUyZ.js} +2 -2
- package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +1 -0
- package/dist/assets/vendor-katex-Cp579UoJ.css +1 -0
- package/dist/assets/{vendor-motion-dom-D_o3BTI3.js → vendor-motion-dom-DcCgeG69.js} +1 -1
- package/dist/assets/{vendor-remixicon-react-Dil0zKKT.js → vendor-remixicon-react-BDmGmFCA.js} +1 -1
- package/dist/assets/vendor-strip-json-comments-5dJlOkP1.js +4 -0
- package/dist/assets/{vendor-tanstack-react-virtual-g05Cx3z9.js → vendor-tanstack-react-virtual-ClYN8CzB.js} +1 -1
- package/dist/assets/{vendor-tanstack-virtual-core-Dubg10g5.js → vendor-tanstack-virtual-core-BewVQryB.js} +1 -1
- package/dist/assets/vendor-zod-CgAdXWZO.js +39 -0
- package/dist/assets/{web-components-CdGfR2XJ.js → web-components-B8IcVQAA.js} +2 -2
- package/dist/assets/zh-CN-Bxy_Hj3r.js +16 -0
- package/dist/assets/zh-TW-0BB4gpHM.js +16 -0
- package/dist/index.html +6 -4
- package/dist/mini-chat.html +6 -4
- package/dist/mobile.html +6 -4
- package/package.json +12 -9
- package/server/built-in-extensions/registry.json +4 -0
- package/server/index.d.ts +2 -0
- package/server/index.js +47 -23
- package/server/lib/agent-memory/runtime.js +7 -2
- package/server/lib/agent-memory/runtime.test.js +22 -0
- package/server/lib/agent-tool/DOCUMENTATION.md +17 -4
- package/server/lib/agent-tool/runtime.js +62 -15
- package/server/lib/agent-tool/runtime.test.js +88 -2
- package/server/lib/changelog/update-notes.js +2 -1
- package/server/lib/changelog/update-notes.test.js +19 -0
- package/server/lib/event-stream/DOCUMENTATION.md +26 -0
- package/server/lib/event-stream/delta-coalescer.js +0 -0
- package/server/lib/event-stream/delta-coalescer.test.js +292 -0
- package/server/lib/event-stream/global-hub.js +68 -13
- package/server/lib/event-stream/global-hub.test.js +182 -0
- package/server/lib/event-stream/global-ws-bridge.js +16 -18
- package/server/lib/event-stream/index.js +4 -0
- package/server/lib/event-stream/protocol.js +21 -7
- package/server/lib/event-stream/runtime.js +9 -6
- package/server/lib/event-stream/runtime.test.js +60 -0
- package/server/lib/fs/DOCUMENTATION.md +10 -1
- package/server/lib/fs/git-process.test.js +77 -0
- package/server/lib/fs/routes.js +58 -5
- package/server/lib/fs/routes.test.js +116 -0
- package/server/lib/fs/search.js +2 -1
- package/server/lib/git/DOCUMENTATION.md +30 -6
- package/server/lib/git/routes.js +37 -11
- package/server/lib/git/routes.test.js +78 -0
- package/server/lib/git/serial-refresh.js +93 -0
- package/server/lib/git/serial-refresh.test.js +112 -0
- package/server/lib/git/service.js +588 -74
- package/server/lib/git/service.test.js +492 -0
- package/server/lib/github/DOCUMENTATION.md +5 -0
- package/server/lib/github/pr-status.js +5 -5
- package/server/lib/github/pr-status.test.js +1 -1
- package/server/lib/github/routes.js +6 -6
- package/server/lib/guests/DOCUMENTATION.md +68 -0
- package/server/lib/guests/auth-store.js +184 -0
- package/server/lib/guests/auth-store.test.js +112 -0
- package/server/lib/guests/background.test.js +83 -0
- package/server/lib/guests/builtins.js +34 -0
- package/server/lib/guests/builtins.test.js +174 -0
- package/server/lib/guests/catalog.js +477 -0
- package/server/lib/guests/catalog.test.js +390 -0
- package/server/lib/guests/clone.js +297 -0
- package/server/lib/guests/extract-zip.js +136 -0
- package/server/lib/guests/extract-zip.test.js +109 -0
- package/server/lib/guests/files.js +308 -0
- package/server/lib/guests/files.test.js +262 -0
- package/server/lib/guests/grant-scope.js +81 -0
- package/server/lib/guests/grant-scope.test.js +75 -0
- package/server/lib/guests/host-session.js +73 -0
- package/server/lib/guests/host-session.test.js +30 -0
- package/server/lib/guests/html-styles.js +6 -0
- package/server/lib/guests/html-styles.test.js +45 -0
- package/server/lib/guests/html-tokens.js +73 -0
- package/server/lib/guests/html-tokens.test.js +53 -0
- package/server/lib/guests/install.js +406 -0
- package/server/lib/guests/install.test.js +338 -0
- package/server/lib/guests/oauth.js +501 -0
- package/server/lib/guests/oauth.test.js +481 -0
- package/server/lib/guests/persist.js +336 -0
- package/server/lib/guests/persist.test.js +288 -0
- package/server/lib/guests/request.js +112 -0
- package/server/lib/guests/request.test.js +206 -0
- package/server/lib/guests/routes.js +838 -0
- package/server/lib/guests/service.js +538 -0
- package/server/lib/guests/service.test.js +277 -0
- package/server/lib/guests/sockets.js +129 -0
- package/server/lib/guests/sockets.test.js +110 -0
- package/server/lib/guests/ssh-install.test.js +188 -0
- package/server/lib/guests/storage.js +68 -0
- package/server/lib/guests/storage.test.js +59 -0
- package/server/lib/guests/updates.js +294 -0
- package/server/lib/guests/updates.test.js +334 -0
- package/server/lib/guests/upload.js +50 -0
- package/server/lib/guests/upload.test.js +131 -0
- package/server/lib/linear/DOCUMENTATION.md +1 -1
- package/server/lib/message-queue/DOCUMENTATION.md +17 -1
- package/server/lib/message-queue/runtime.js +34 -1
- package/server/lib/message-queue/runtime.test.js +83 -6
- package/server/lib/notifications/DOCUMENTATION.md +1 -1
- package/server/lib/notifications/emitter-runtime.js +4 -3
- package/server/lib/notifications/routes.js +5 -0
- package/server/lib/openchamber-control/DOCUMENTATION.md +6 -0
- package/server/lib/openchamber-sessions/routes.js +62 -29
- package/server/lib/openchamber-sessions/routes.test.js +96 -0
- package/server/lib/opencode/DOCUMENTATION.md +73 -5
- package/server/lib/opencode/bootstrap-runtime.js +1 -0
- package/server/lib/opencode/core-routes.js +39 -1
- package/server/lib/opencode/env-runtime.js +5 -1
- package/server/lib/opencode/env-runtime.test.js +72 -0
- package/server/lib/opencode/feature-routes-runtime.js +30 -1
- package/server/lib/opencode/lifecycle-process.test.js +84 -0
- package/server/lib/opencode/lifecycle.js +58 -70
- package/server/lib/opencode/lifecycle.test.js +29 -0
- package/server/lib/opencode/managed-plugin-config.js +1 -1
- package/server/lib/opencode/openchamber-routes.js +72 -14
- package/server/lib/opencode/openchamber-routes.test.js +71 -3
- package/server/lib/opencode/proxy.js +33 -0
- package/server/lib/opencode/session-runtime.js +68 -0
- package/server/lib/opencode/session-runtime.test.js +43 -0
- package/server/lib/opencode/settings-helpers.js +11 -0
- package/server/lib/opencode/settings-helpers.test.js +24 -2
- package/server/lib/opencode/settings-normalization-runtime.js +2 -0
- package/server/lib/opencode/settings-normalization-runtime.test.js +13 -0
- package/server/lib/opencode/settings-registry.json +18 -0
- package/server/lib/opencode/settings-runtime.js +36 -9
- package/server/lib/opencode/settings-runtime.test.js +158 -2
- package/server/lib/opencode/shutdown-runtime.js +3 -0
- package/server/lib/opencode/shutdown-runtime.test.js +24 -0
- package/server/lib/opencode/theme-archive.js +53 -0
- package/server/lib/opencode/theme-catalog.js +171 -0
- package/server/lib/opencode/theme-catalog.test.js +125 -0
- package/server/lib/opencode/theme-import.test.js +144 -0
- package/server/lib/opencode/theme-runtime.js +100 -28
- package/server/lib/opencode/theme-runtime.test.js +15 -0
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +8 -0
- package/server/lib/permission-auto-accept/runtime.js +13 -0
- package/server/lib/permission-auto-accept/runtime.test.js +37 -1
- package/server/lib/project-context/DOCUMENTATION.md +13 -3
- package/server/lib/project-context/runtime.js +8 -2
- package/server/lib/project-context/runtime.test.js +54 -0
- package/server/lib/projects/DOCUMENTATION.md +2 -1
- package/server/lib/projects/project-config.js +40 -11
- package/server/lib/projects/project-config.test.js +97 -0
- package/server/lib/projects/project-id.js +26 -0
- package/server/lib/quota/DOCUMENTATION.md +0 -1
- package/server/lib/quota/providers/index.js +0 -7
- package/server/lib/realtime-proxy-terminal.test.js +150 -0
- package/server/lib/realtime-proxy.js +2 -0
- package/server/lib/relay/DOCUMENTATION.md +20 -0
- package/server/lib/relay/host-client.js +7 -0
- package/server/lib/relay/host-client.test.js +14 -0
- package/server/lib/routing/DOCUMENTATION.md +83 -0
- package/server/lib/routing/defaults.js +98 -0
- package/server/lib/routing/feature-flag.js +11 -0
- package/server/lib/routing/history.js +34 -0
- package/server/lib/routing/jev.js +98 -0
- package/server/lib/routing/routes.http.test.js +100 -0
- package/server/lib/routing/routes.js +81 -0
- package/server/lib/routing/runtime.js +217 -0
- package/server/lib/routing/runtime.test.js +188 -0
- package/server/lib/routing/store.js +249 -0
- package/server/lib/routing/store.test.js +96 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +9 -1
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +28 -0
- package/server/lib/scheduled-tasks/runtime.js +14 -4
- package/server/lib/scheduled-tasks/runtime.test.js +67 -0
- package/server/lib/security/request-security.js +9 -5
- package/server/lib/security/request-security.test.js +37 -0
- package/server/lib/session-assist/DOCUMENTATION.md +131 -66
- package/server/lib/session-assist/context.js +127 -0
- package/server/lib/session-assist/context.test.js +132 -0
- package/server/lib/session-assist/prompt.js +54 -0
- package/server/lib/session-assist/runtime.js +111 -257
- package/server/lib/session-assist/runtime.test.js +203 -0
- package/server/lib/small-model/DOCUMENTATION.md +14 -2
- package/server/lib/small-model/call.js +37 -6
- package/server/lib/small-model/call.test.js +182 -0
- package/server/lib/terminal/DOCUMENTATION.md +23 -2
- package/server/lib/terminal/runtime.js +27 -5
- package/server/lib/terminal/runtime.test.js +67 -0
- package/server/lib/terminal/shutdown.js +106 -0
- package/server/lib/terminal/shutdown.test.js +84 -0
- package/server/lib/tts/service.test.js +78 -0
- package/server/lib/ui-auth/DOCUMENTATION.md +8 -0
- package/server/lib/ui-auth/session-cookie.js +51 -0
- package/server/lib/ui-auth/session-cookie.test.js +46 -0
- package/server/lib/ui-auth/ui-auth.js +56 -17
- package/server/lib/ui-auth/ui-auth.test.js +96 -0
- package/server/lib/walkthrough/DOCUMENTATION.md +15 -5
- package/server/lib/walkthrough/pr-source.test.js +21 -0
- package/server/lib/walkthrough/pull-request.js +7 -4
- package/server/lib/walkthrough/pull-request.test.js +16 -0
- package/server/lib/walkthrough/routes.js +19 -0
- package/server/lib/walkthrough/routes.test.js +26 -0
- package/server/lib/walkthrough/sources.js +13 -2
- package/server/opencode-worktree-readiness.test.js +130 -0
- package/dist/assets/DiffView-3Dn4QHbu.js +0 -4
- package/dist/assets/DirtyBranchSwitchDialog-CwOerqVi.js +0 -1
- package/dist/assets/FilesView-BdTOpew9.js +0 -17
- package/dist/assets/GitView-Dqf9vHJh.js +0 -6
- package/dist/assets/LinearIssuesView-CmbeL7I6.js +0 -1
- package/dist/assets/MarkdownRendererImpl-wVZlx4dd.js +0 -12
- package/dist/assets/PierreDiffViewer-DCsydlKs.js +0 -161
- package/dist/assets/SettingsView-CcM8pF5O.js +0 -28
- package/dist/assets/ToolOutputDialog-BgfTbDZ4.js +0 -15
- package/dist/assets/WalkthroughView-BEVUDxuw.js +0 -4
- package/dist/assets/appThemeRegistry-BK0dHKa6.js +0 -1
- package/dist/assets/de-C8jV7J9y.js +0 -10
- package/dist/assets/es-BI6J1M5R.js +0 -16
- package/dist/assets/fr-D6oDFt7E.js +0 -14
- package/dist/assets/index-BqhiCmYS.js +0 -39
- package/dist/assets/index-FqSOBz0J.css +0 -1
- package/dist/assets/ja-C_4i5EQu.js +0 -16
- package/dist/assets/ko-BFbOTpbq.js +0 -16
- package/dist/assets/main-C9ETLKFc.js +0 -2
- package/dist/assets/main-CJmb2dF8.js +0 -930
- package/dist/assets/markdownCore-DVuZpnli.js +0 -5
- package/dist/assets/miniChat-B4Ytoq2n.js +0 -2
- package/dist/assets/mobile-BUT6-lo1.js +0 -2
- package/dist/assets/patchFileDiff-8j1apICU.js +0 -13
- package/dist/assets/pl-COFvT56l.js +0 -16
- package/dist/assets/pt-BR-DtVOO2Lj.js +0 -16
- package/dist/assets/registerCustomTheme-BBXUCRnG.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-B6vUG-vF.js +0 -2
- package/dist/assets/renderMobileApp-BNgHraq6.js +0 -25
- package/dist/assets/runtimeEndpointReset-B8LYpWvr.js +0 -17
- package/dist/assets/scheduledTasksApi-GC4epIYA.js +0 -1
- package/dist/assets/shikiHighlight-BJISeZsQ.js +0 -1
- package/dist/assets/surface-BvRzS58U.js +0 -1
- package/dist/assets/tr-B8Q0Go2l.js +0 -7
- package/dist/assets/uk-Dk4a-Fn-.js +0 -16
- package/dist/assets/useAppFontEffects-DpFNefUA.js +0 -7284
- package/dist/assets/useGitComparison-Dfxaqd0s.js +0 -1
- package/dist/assets/useInlineCommentController-CscsE4t-.js +0 -2
- package/dist/assets/useRootScrollLock-BaCvuRlT.js +0 -1
- package/dist/assets/vendor-framer-motion-livSKyyV.js +0 -1
- package/dist/assets/vendor-zod-BPAANqvH.js +0 -39
- package/dist/assets/zh-CN-B_BXxG1Q.js +0 -16
- package/dist/assets/zh-TW-DLrl1SPZ.js +0 -16
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +0 -55
- package/server/lib/mcp-reconnect/runtime.js +0 -122
- package/server/lib/mcp-reconnect/runtime.test.js +0 -140
- package/server/lib/quota/providers/crof.js +0 -96
- package/server/lib/quota/providers/crof.test.js +0 -98
- /package/dist/assets/{vendor-katex-k3Ppq1Ha.js → vendor-katex-CHD9BdDO.js} +0 -0
|
@@ -8,6 +8,7 @@ import { loadSourceSections, parseSource, sourceKey } from '../walkthrough/sourc
|
|
|
8
8
|
import { registerGitRoutes } from './routes.js';
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
+
unsupportedRepositoryRootReason,
|
|
11
12
|
checkoutBranch,
|
|
12
13
|
checkoutCommit,
|
|
13
14
|
cherryPick,
|
|
@@ -21,8 +22,10 @@ import {
|
|
|
21
22
|
getCommitFiles,
|
|
22
23
|
getLog,
|
|
23
24
|
getStatus,
|
|
25
|
+
getTrackingBranch,
|
|
24
26
|
getWorktrees,
|
|
25
27
|
isGitRepository,
|
|
28
|
+
observeWorktreeTopology,
|
|
26
29
|
populateWorktreeWithLockRecovery,
|
|
27
30
|
removeWorktree,
|
|
28
31
|
resolvePrimaryWorktreeRoot,
|
|
@@ -32,14 +35,18 @@ import {
|
|
|
32
35
|
revertCommit,
|
|
33
36
|
setLocalIdentity,
|
|
34
37
|
stageFiles,
|
|
38
|
+
subscribeWorktreeTopologyChanges,
|
|
35
39
|
unstageFiles,
|
|
36
40
|
applyHunk,
|
|
37
41
|
getDiff,
|
|
42
|
+
getPathDiff,
|
|
43
|
+
revertFile,
|
|
38
44
|
getUntrackedDiffs,
|
|
39
45
|
getFileDiff,
|
|
40
46
|
validateWorktreeCreate,
|
|
41
47
|
parseBranchCreationSource,
|
|
42
48
|
getRangeFiles,
|
|
49
|
+
push,
|
|
43
50
|
} from './service.js';
|
|
44
51
|
|
|
45
52
|
// ---------------------------------------------------------------------------
|
|
@@ -125,6 +132,23 @@ async function createTempRepo() {
|
|
|
125
132
|
// resolveBaseRefForLog
|
|
126
133
|
// ---------------------------------------------------------------------------
|
|
127
134
|
|
|
135
|
+
describe('unsupportedRepositoryRootReason', () => {
|
|
136
|
+
it('rejects a repository rooted at a filesystem root or the home directory', () => {
|
|
137
|
+
const home = path.join(os.tmpdir(), 'unsupported-root-home');
|
|
138
|
+
expect(unsupportedRepositoryRootReason('/', home)).toBe('filesystem-root');
|
|
139
|
+
expect(unsupportedRepositoryRootReason(path.parse(process.cwd()).root, home)).toBe('filesystem-root');
|
|
140
|
+
expect(unsupportedRepositoryRootReason(home, home)).toBe('home');
|
|
141
|
+
expect(unsupportedRepositoryRootReason(`${home}${path.sep}`, home)).toBe('home');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('accepts an ordinary project root, including one directly under home', () => {
|
|
145
|
+
const home = path.join(os.tmpdir(), 'unsupported-root-home');
|
|
146
|
+
expect(unsupportedRepositoryRootReason(path.join(home, 'project'), home)).toBeNull();
|
|
147
|
+
expect(unsupportedRepositoryRootReason(path.join(os.tmpdir(), 'repo'), home)).toBeNull();
|
|
148
|
+
expect(unsupportedRepositoryRootReason('', home)).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
128
152
|
describe('resolveBaseRefForLog', () => {
|
|
129
153
|
it('returns the local ref unchanged when it exists, even if origin also exists', async () => {
|
|
130
154
|
const checkRef = async (ref) => ref === 'main' || ref === 'refs/remotes/origin/main';
|
|
@@ -505,6 +529,131 @@ describe('symlink diffs', () => {
|
|
|
505
529
|
});
|
|
506
530
|
});
|
|
507
531
|
|
|
532
|
+
// ---------------------------------------------------------------------------
|
|
533
|
+
// Status paths that are not plain files (#3586)
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
describe.runIf(canRunGit())('diffs for status paths that are not plain files', () => {
|
|
537
|
+
const callDiffRoute = async (endpoint, query) => {
|
|
538
|
+
const routes = new Map();
|
|
539
|
+
registerGitRoutes({ get: (url, handler) => routes.set(url, handler), post() {}, put() {}, delete() {} });
|
|
540
|
+
let status = 200;
|
|
541
|
+
let body;
|
|
542
|
+
await routes.get(`/api/git/${endpoint}`)({ query }, {
|
|
543
|
+
status(value) { status = value; return this; },
|
|
544
|
+
json(value) { body = value; },
|
|
545
|
+
});
|
|
546
|
+
return { status, body };
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const createRepositoryWithSubmodule = () => {
|
|
550
|
+
const { repository } = createRepositoryWithRemote();
|
|
551
|
+
const library = createTempDir();
|
|
552
|
+
runGit(library, ['init', '-b', 'main']);
|
|
553
|
+
runGit(library, ['config', 'user.email', 'test@example.com']);
|
|
554
|
+
runGit(library, ['config', 'user.name', 'Test']);
|
|
555
|
+
fs.writeFileSync(path.join(library, 'lib.txt'), 'lib\n');
|
|
556
|
+
runGit(library, ['add', '.']);
|
|
557
|
+
runGit(library, ['commit', '-m', 'lib']);
|
|
558
|
+
runGit(repository, ['-c', 'protocol.file.allow=always', 'submodule', 'add', library, 'sub']);
|
|
559
|
+
runGit(repository, ['commit', '-m', 'add submodule']);
|
|
560
|
+
return { repository, recorded: runGit(repository, ['rev-parse', 'HEAD:sub']).trim() };
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
it('answers 404 with a code when a listed file is gone before its diff is requested', async () => {
|
|
564
|
+
const { repository } = createRepositoryWithRemote();
|
|
565
|
+
for (const endpoint of ['diff', 'file-diff']) {
|
|
566
|
+
const { status, body } = await callDiffRoute(endpoint, { directory: repository, path: 'removed.txt' });
|
|
567
|
+
expect(status).toBe(404);
|
|
568
|
+
expect(body).toEqual({ code: 'path_not_found', error: 'Path not found in working tree, index, or HEAD: removed.txt' });
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('answers 422 for a nested repository that status lists as a directory', async () => {
|
|
573
|
+
const { repository } = createRepositoryWithRemote();
|
|
574
|
+
const nested = path.join(repository, 'nested');
|
|
575
|
+
fs.mkdirSync(nested);
|
|
576
|
+
runGit(nested, ['init', '-b', 'main']);
|
|
577
|
+
fs.writeFileSync(path.join(nested, 'inner.txt'), 'inner\n');
|
|
578
|
+
expect((await getStatus(repository)).files).toContainEqual(expect.objectContaining({ path: 'nested/' }));
|
|
579
|
+
|
|
580
|
+
for (const endpoint of ['diff', 'file-diff']) {
|
|
581
|
+
const { status, body } = await callDiffRoute(endpoint, { directory: repository, path: 'nested/' });
|
|
582
|
+
expect(status).toBe(422);
|
|
583
|
+
expect(body.code).toBe('nested_repository');
|
|
584
|
+
}
|
|
585
|
+
await expect(revertFile(repository, 'nested/')).rejects.toMatchObject({ code: 'nested_repository' });
|
|
586
|
+
expect(fs.existsSync(path.join(nested, 'inner.txt'))).toBe(true);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('describes a submodule whose checked-out commit moved', async () => {
|
|
590
|
+
const { repository, recorded } = createRepositoryWithSubmodule();
|
|
591
|
+
const submodulePath = path.join(repository, 'sub');
|
|
592
|
+
runGit(submodulePath, ['config', 'user.email', 'test@example.com']);
|
|
593
|
+
runGit(submodulePath, ['config', 'user.name', 'Test']);
|
|
594
|
+
runGit(submodulePath, ['commit', '--allow-empty', '-m', 'moved']);
|
|
595
|
+
const moved = runGit(submodulePath, ['rev-parse', 'HEAD']).trim();
|
|
596
|
+
const submodule = { headCommit: recorded, indexCommit: recorded, worktreeCommit: moved, hasTrackedChanges: false, hasUntrackedFiles: false, hasConflict: false };
|
|
597
|
+
|
|
598
|
+
const patch = await callDiffRoute('diff', { directory: repository, path: 'sub' });
|
|
599
|
+
expect(patch.status).toBe(200);
|
|
600
|
+
expect(patch.body.diff).toContain(`+Subproject commit ${moved}`);
|
|
601
|
+
expect(patch.body.submodule).toEqual(submodule);
|
|
602
|
+
|
|
603
|
+
const split = await callDiffRoute('file-diff', { directory: repository, path: 'sub' });
|
|
604
|
+
expect(split.body).toEqual({
|
|
605
|
+
original: `Subproject commit ${recorded}\n`,
|
|
606
|
+
modified: `Subproject commit ${moved}\n`,
|
|
607
|
+
path: 'sub',
|
|
608
|
+
isBinary: false,
|
|
609
|
+
submodule,
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it('reports a submodule merge conflict instead of an unchanged commit', async () => {
|
|
614
|
+
const { repository } = createRepositoryWithRemote();
|
|
615
|
+
const library = createTempDir();
|
|
616
|
+
runGit(library, ['init', '-b', 'main']);
|
|
617
|
+
runGit(library, ['config', 'user.email', 'test@example.com']);
|
|
618
|
+
runGit(library, ['config', 'user.name', 'Test']);
|
|
619
|
+
runGit(library, ['commit', '--allow-empty', '-m', 'base']);
|
|
620
|
+
runGit(library, ['checkout', '-b', 'left']);
|
|
621
|
+
runGit(library, ['commit', '--allow-empty', '-m', 'left']);
|
|
622
|
+
runGit(library, ['checkout', '-b', 'right', 'main']);
|
|
623
|
+
runGit(library, ['commit', '--allow-empty', '-m', 'right']);
|
|
624
|
+
runGit(library, ['checkout', 'main']);
|
|
625
|
+
runGit(repository, ['-c', 'protocol.file.allow=always', 'submodule', 'add', library, 'sub']);
|
|
626
|
+
runGit(repository, ['commit', '-m', 'add submodule']);
|
|
627
|
+
const submodulePath = path.join(repository, 'sub');
|
|
628
|
+
for (const [branch, commit] of [['other', 'right'], ['next', 'left']]) {
|
|
629
|
+
if (branch === 'other') runGit(repository, ['checkout', '-b', 'other']);
|
|
630
|
+
else runGit(repository, ['checkout', 'next']);
|
|
631
|
+
runGit(submodulePath, ['checkout', commit]);
|
|
632
|
+
runGit(repository, ['add', 'sub']);
|
|
633
|
+
runGit(repository, ['commit', '-m', `move to ${commit}`]);
|
|
634
|
+
}
|
|
635
|
+
expect(() => runGit(repository, ['merge', 'other'])).toThrow();
|
|
636
|
+
|
|
637
|
+
const { submodule } = await getPathDiff(repository, { path: 'sub' });
|
|
638
|
+
expect(submodule).toMatchObject({
|
|
639
|
+
headCommit: runGit(repository, ['rev-parse', 'HEAD:sub']).trim(),
|
|
640
|
+
indexCommit: null,
|
|
641
|
+
hasConflict: true,
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('reports untracked files inside a submodule even though its patch is empty', async () => {
|
|
646
|
+
const { repository, recorded } = createRepositoryWithSubmodule();
|
|
647
|
+
fs.writeFileSync(path.join(repository, 'sub', 'scratch.txt'), 'scratch\n');
|
|
648
|
+
|
|
649
|
+
const result = await getPathDiff(repository, { path: 'sub' });
|
|
650
|
+
expect(result).toEqual({
|
|
651
|
+
diff: '',
|
|
652
|
+
submodule: { headCommit: recorded, indexCommit: recorded, worktreeCommit: recorded, hasTrackedChanges: false, hasUntrackedFiles: true, hasConflict: false },
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
|
|
508
657
|
// ---------------------------------------------------------------------------
|
|
509
658
|
// getStatus
|
|
510
659
|
// ---------------------------------------------------------------------------
|
|
@@ -601,6 +750,142 @@ describe('getStatus', () => {
|
|
|
601
750
|
process.chdir(previousCwd);
|
|
602
751
|
}
|
|
603
752
|
});
|
|
753
|
+
|
|
754
|
+
it('scopes diff stats by staged and working instead of combining a partially staged file', async () => {
|
|
755
|
+
if (!canRunGit()) return;
|
|
756
|
+
|
|
757
|
+
const repo = createTempDir();
|
|
758
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
759
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
760
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
761
|
+
const file = 'test.txt';
|
|
762
|
+
const filePath = path.join(repo, file);
|
|
763
|
+
fs.writeFileSync(filePath, 'one\ntwo\nthree\n');
|
|
764
|
+
runGit(repo, ['add', file]);
|
|
765
|
+
runGit(repo, ['commit', '-m', 'initial']);
|
|
766
|
+
|
|
767
|
+
// Stage one new line, then keep editing without staging another.
|
|
768
|
+
fs.writeFileSync(filePath, 'one\ntwo\nthree\nstaged\n');
|
|
769
|
+
runGit(repo, ['add', file]);
|
|
770
|
+
fs.writeFileSync(filePath, 'one\ntwo\nthree\nstaged\nworking\n');
|
|
771
|
+
|
|
772
|
+
const status = await getStatus(repo);
|
|
773
|
+
|
|
774
|
+
expect(status.diffStats.staged[file]).toEqual({ insertions: 1, deletions: 0 });
|
|
775
|
+
expect(status.diffStats.working[file]).toEqual({ insertions: 1, deletions: 0 });
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it('scopes untracked files to working stats and staged additions to staged stats', async () => {
|
|
779
|
+
if (!canRunGit()) return;
|
|
780
|
+
|
|
781
|
+
const repo = createTempDir();
|
|
782
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
783
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
784
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
785
|
+
fs.writeFileSync(path.join(repo, 'tracked.txt'), 'tracked\n');
|
|
786
|
+
runGit(repo, ['add', 'tracked.txt']);
|
|
787
|
+
runGit(repo, ['commit', '-m', 'initial']);
|
|
788
|
+
|
|
789
|
+
fs.writeFileSync(path.join(repo, 'untracked.txt'), 'a\nb\n');
|
|
790
|
+
fs.writeFileSync(path.join(repo, 'staged.txt'), 'c\nd\ne\n');
|
|
791
|
+
runGit(repo, ['add', 'staged.txt']);
|
|
792
|
+
|
|
793
|
+
const status = await getStatus(repo);
|
|
794
|
+
|
|
795
|
+
expect(status.diffStats.working['untracked.txt']).toEqual({ insertions: 2, deletions: 0 });
|
|
796
|
+
expect(status.diffStats.staged['staged.txt']).toEqual({ insertions: 3, deletions: 0 });
|
|
797
|
+
expect(status.diffStats.working['staged.txt']).toBeUndefined();
|
|
798
|
+
});
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
describe('push', () => {
|
|
802
|
+
it('publishes the current branch with an upstream and leaves other local branches alone', async () => {
|
|
803
|
+
if (!canRunGit()) return;
|
|
804
|
+
|
|
805
|
+
const remote = createTempDir();
|
|
806
|
+
const repo = createTempDir();
|
|
807
|
+
runGit(remote, ['init', '--bare']);
|
|
808
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
809
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
810
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
811
|
+
fs.writeFileSync(path.join(repo, 'README.md'), '# Test\n');
|
|
812
|
+
runGit(repo, ['add', 'README.md']);
|
|
813
|
+
runGit(repo, ['commit', '-m', 'Initial commit']);
|
|
814
|
+
runGit(repo, ['remote', 'add', 'fork', remote]);
|
|
815
|
+
runGit(repo, ['branch', 'unrelated']);
|
|
816
|
+
runGit(repo, ['checkout', '-b', 'feature']);
|
|
817
|
+
fs.writeFileSync(path.join(repo, 'feature.txt'), 'published\n');
|
|
818
|
+
runGit(repo, ['add', 'feature.txt']);
|
|
819
|
+
runGit(repo, ['commit', '-m', 'Add feature']);
|
|
820
|
+
|
|
821
|
+
const published = await push(repo, { remote: 'fork' });
|
|
822
|
+
expect(published.pushed).toEqual([{ local: 'refs/heads/feature', remote: 'fork' }]);
|
|
823
|
+
|
|
824
|
+
expect(runGit(repo, ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']).trim()).toBe('fork/feature');
|
|
825
|
+
expect(runGit(remote, ['rev-parse', 'refs/heads/feature']).trim()).toBe(runGit(repo, ['rev-parse', 'HEAD']).trim());
|
|
826
|
+
expect(() => runGit(remote, ['rev-parse', 'refs/heads/unrelated'])).toThrow();
|
|
827
|
+
expect(() => runGit(remote, ['rev-parse', 'refs/heads/main'])).toThrow();
|
|
828
|
+
|
|
829
|
+
expect((await push(repo)).pushed).toEqual([]);
|
|
830
|
+
fs.appendFileSync(path.join(repo, 'feature.txt'), 'next commit\n');
|
|
831
|
+
runGit(repo, ['commit', '-am', 'Update feature']);
|
|
832
|
+
expect((await push(repo)).pushed).toEqual([{ local: 'refs/heads/feature', remote: 'fork' }]);
|
|
833
|
+
expect(runGit(remote, ['rev-parse', 'feature']).trim()).toBe(runGit(repo, ['rev-parse', 'HEAD']).trim());
|
|
834
|
+
|
|
835
|
+
runGit(repo, ['reset', '--hard', 'HEAD~1']);
|
|
836
|
+
await expect(push(repo)).rejects.toThrow();
|
|
837
|
+
expect((await push(repo, { options: ['--force-with-lease'] })).pushed)
|
|
838
|
+
.toEqual([{ local: 'refs/heads/feature', remote: 'fork' }]);
|
|
839
|
+
expect(runGit(remote, ['rev-parse', 'feature']).trim()).toBe(runGit(repo, ['rev-parse', 'HEAD']).trim());
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it.each(['remote.pushDefault', 'branch.next.pushRemote'])('preserves the %s destination independently of the fetch remote', async (key) => {
|
|
843
|
+
const { repository, remote: upstream } = createRepositoryWithRemote({ remoteName: 'upstream', defaultBranch: 'next' });
|
|
844
|
+
const fork = createTempDir();
|
|
845
|
+
runGit(fork, ['init', '--bare']);
|
|
846
|
+
runGit(repository, ['remote', 'add', 'fork', fork]);
|
|
847
|
+
runGit(repository, ['branch', '--set-upstream-to=upstream/next']);
|
|
848
|
+
if (key === 'branch.next.pushRemote') runGit(repository, ['config', 'remote.pushDefault', 'upstream']);
|
|
849
|
+
runGit(repository, ['config', key, 'fork']);
|
|
850
|
+
const upstreamHead = runGit(upstream, ['rev-parse', 'next']).trim();
|
|
851
|
+
fs.appendFileSync(path.join(repository, 'README.md'), 'fork change\n');
|
|
852
|
+
runGit(repository, ['commit', '-am', 'Change for fork']);
|
|
853
|
+
|
|
854
|
+
expect((await push(repository)).pushed).toEqual([{ local: 'refs/heads/next', remote: 'fork' }]);
|
|
855
|
+
expect(runGit(fork, ['rev-parse', 'next']).trim()).toBe(runGit(repository, ['rev-parse', 'HEAD']).trim());
|
|
856
|
+
expect(runGit(upstream, ['rev-parse', 'next']).trim()).toBe(upstreamHead);
|
|
857
|
+
expect(readBranchConfig(repository, 'next', 'remote')).toBe('upstream');
|
|
858
|
+
expect((await push(repository)).pushed).toEqual([]);
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
it.each(['remote.pushDefault', 'branch.next.pushRemote'])('uses %s for first publication without an upstream', async (key) => {
|
|
862
|
+
const { repository, remote: origin } = createRepositoryWithRemote();
|
|
863
|
+
const fork = createTempDir();
|
|
864
|
+
runGit(fork, ['init', '--bare']);
|
|
865
|
+
runGit(repository, ['remote', 'add', 'fork', fork]);
|
|
866
|
+
runGit(repository, ['config', key, 'fork']);
|
|
867
|
+
runGit(repository, ['config', 'push.autoSetupRemote', 'false']);
|
|
868
|
+
|
|
869
|
+
expect((await push(repository)).pushed).toEqual([{ local: 'refs/heads/next', remote: 'fork' }]);
|
|
870
|
+
expect(readBranchConfig(repository, 'next', 'remote')).toBe('fork');
|
|
871
|
+
expect(() => runGit(origin, ['rev-parse', 'refs/heads/next'])).toThrow();
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
it('lets an explicit push remote override configured destinations', async () => {
|
|
875
|
+
const { repository, remote: origin } = createRepositoryWithRemote({ defaultBranch: 'next' });
|
|
876
|
+
const fork = createTempDir();
|
|
877
|
+
runGit(fork, ['init', '--bare']);
|
|
878
|
+
runGit(repository, ['remote', 'add', 'fork', fork]);
|
|
879
|
+
runGit(repository, ['branch', '--set-upstream-to=origin/next']);
|
|
880
|
+
runGit(repository, ['config', 'branch.next.pushRemote', 'fork']);
|
|
881
|
+
fs.appendFileSync(path.join(repository, 'README.md'), 'origin change\n');
|
|
882
|
+
runGit(repository, ['commit', '-am', 'Change for origin']);
|
|
883
|
+
|
|
884
|
+
expect((await push(repository, { remote: 'origin' })).pushed)
|
|
885
|
+
.toEqual([{ local: 'refs/heads/next', remote: 'origin' }]);
|
|
886
|
+
expect(runGit(origin, ['rev-parse', 'next']).trim()).toBe(runGit(repository, ['rev-parse', 'HEAD']).trim());
|
|
887
|
+
expect(() => runGit(fork, ['rev-parse', 'next'])).toThrow();
|
|
888
|
+
});
|
|
604
889
|
});
|
|
605
890
|
|
|
606
891
|
// ---------------------------------------------------------------------------
|
|
@@ -680,6 +965,78 @@ describe('getWorktrees', () => {
|
|
|
680
965
|
expect(Array.isArray(result)).toBe(true);
|
|
681
966
|
expect(warnSpy).not.toHaveBeenCalled();
|
|
682
967
|
});
|
|
968
|
+
it('notifies subscribers only when another git process changes the worktree set', async () => {
|
|
969
|
+
if (!canRunGit()) return;
|
|
970
|
+
|
|
971
|
+
const repo = createTempDir();
|
|
972
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
973
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
974
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
975
|
+
runGit(repo, ['commit', '--allow-empty', '-m', 'init']);
|
|
976
|
+
const worktreePath = path.join(createTempDir(), 'feature');
|
|
977
|
+
|
|
978
|
+
const events = [];
|
|
979
|
+
const unsubscribe = subscribeWorktreeTopologyChanges((event) => events.push(event));
|
|
980
|
+
try {
|
|
981
|
+
await observeWorktreeTopology(repo);
|
|
982
|
+
await observeWorktreeTopology(repo);
|
|
983
|
+
expect(events).toHaveLength(0);
|
|
984
|
+
|
|
985
|
+
runGit(repo, ['worktree', 'add', worktreePath, '-b', 'feature']);
|
|
986
|
+
await observeWorktreeTopology(worktreePath);
|
|
987
|
+
expect(events).toHaveLength(1);
|
|
988
|
+
expect(events[0].directories).toEqual(expect.arrayContaining([repo, worktreePath]));
|
|
989
|
+
|
|
990
|
+
await observeWorktreeTopology(repo);
|
|
991
|
+
expect(events).toHaveLength(1);
|
|
992
|
+
|
|
993
|
+
runGit(repo, ['worktree', 'remove', worktreePath]);
|
|
994
|
+
await observeWorktreeTopology(repo);
|
|
995
|
+
expect(events).toHaveLength(2);
|
|
996
|
+
} finally {
|
|
997
|
+
unsubscribe();
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
it('publishes worktrees this server creates and removes', async () => {
|
|
1002
|
+
if (!canRunGit()) return;
|
|
1003
|
+
|
|
1004
|
+
const previousXdgDataHome = process.env.XDG_DATA_HOME;
|
|
1005
|
+
process.env.XDG_DATA_HOME = createTempDir();
|
|
1006
|
+
const events = [];
|
|
1007
|
+
const unsubscribe = subscribeWorktreeTopologyChanges((event) => events.push(event));
|
|
1008
|
+
try {
|
|
1009
|
+
const repo = createTempDir();
|
|
1010
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
1011
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
1012
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
1013
|
+
runGit(repo, ['commit', '--allow-empty', '-m', 'init']);
|
|
1014
|
+
await observeWorktreeTopology(repo);
|
|
1015
|
+
|
|
1016
|
+
const created = await createWorktree(repo, {
|
|
1017
|
+
mode: 'new',
|
|
1018
|
+
worktreeName: 'published',
|
|
1019
|
+
branchName: 'openchamber/published',
|
|
1020
|
+
});
|
|
1021
|
+
expect(events).toHaveLength(1);
|
|
1022
|
+
expect(events[0].directories).toContain(repo);
|
|
1023
|
+
|
|
1024
|
+
// The publish refreshed the baseline, so the next observation is quiet.
|
|
1025
|
+
await observeWorktreeTopology(repo);
|
|
1026
|
+
expect(events).toHaveLength(1);
|
|
1027
|
+
|
|
1028
|
+
await removeWorktree(repo, { directory: created.path });
|
|
1029
|
+
expect(events).toHaveLength(2);
|
|
1030
|
+
} finally {
|
|
1031
|
+
unsubscribe();
|
|
1032
|
+
if (previousXdgDataHome === undefined) {
|
|
1033
|
+
delete process.env.XDG_DATA_HOME;
|
|
1034
|
+
} else {
|
|
1035
|
+
process.env.XDG_DATA_HOME = previousXdgDataHome;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
|
|
683
1040
|
it('flags a worktree whose directory was deleted outside git as prunable', async () => {
|
|
684
1041
|
const repo = createTempDir();
|
|
685
1042
|
runGit(repo, ['init', '-b', 'main']);
|
|
@@ -2187,3 +2544,138 @@ describe.runIf(canRunGit())('getRangeFiles', () => {
|
|
|
2187
2544
|
expect(copyEntry.path).toBe('copied destination.md');
|
|
2188
2545
|
});
|
|
2189
2546
|
});
|
|
2547
|
+
|
|
2548
|
+
// ---------------------------------------------------------------------------
|
|
2549
|
+
// getTrackingBranch
|
|
2550
|
+
// ---------------------------------------------------------------------------
|
|
2551
|
+
|
|
2552
|
+
describe('getTrackingBranch', () => {
|
|
2553
|
+
const createCommittedRepo = () => {
|
|
2554
|
+
const repo = createTempDir();
|
|
2555
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
2556
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
2557
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
2558
|
+
runGit(repo, ['commit', '--allow-empty', '-m', 'Initial commit']);
|
|
2559
|
+
return repo;
|
|
2560
|
+
};
|
|
2561
|
+
|
|
2562
|
+
it('reports the same upstream name as status, including a gone upstream', async () => {
|
|
2563
|
+
if (!canRunGit()) return;
|
|
2564
|
+
|
|
2565
|
+
const repo = createCommittedRepo();
|
|
2566
|
+
await expect(getTrackingBranch(repo)).resolves.toBeNull();
|
|
2567
|
+
|
|
2568
|
+
runGit(repo, ['remote', 'add', 'origin', 'https://example.invalid/repo.git']);
|
|
2569
|
+
runGit(repo, ['config', 'branch.main.remote', 'origin']);
|
|
2570
|
+
runGit(repo, ['config', 'branch.main.merge', 'refs/heads/main']);
|
|
2571
|
+
await expect(getTrackingBranch(repo)).resolves.toBe('origin/main');
|
|
2572
|
+
expect((await getStatus(repo)).tracking).toBe('origin/main');
|
|
2573
|
+
|
|
2574
|
+
runGit(repo, ['update-ref', 'refs/remotes/origin/main', 'HEAD']);
|
|
2575
|
+
await expect(getTrackingBranch(repo)).resolves.toBe('origin/main');
|
|
2576
|
+
});
|
|
2577
|
+
|
|
2578
|
+
it('is null for a detached HEAD and outside a repository', async () => {
|
|
2579
|
+
if (!canRunGit()) return;
|
|
2580
|
+
|
|
2581
|
+
const repo = createCommittedRepo();
|
|
2582
|
+
runGit(repo, ['checkout', '--detach']);
|
|
2583
|
+
await expect(getTrackingBranch(repo)).resolves.toBeNull();
|
|
2584
|
+
await expect(getTrackingBranch(createTempDir())).resolves.toBeNull();
|
|
2585
|
+
});
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
describe('getStatus concurrency', () => {
|
|
2589
|
+
it('answers overlapping reads of one repository and reflects changes made while a read ran', async () => {
|
|
2590
|
+
if (!canRunGit()) return;
|
|
2591
|
+
|
|
2592
|
+
const repo = createTempDir();
|
|
2593
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
2594
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
2595
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
2596
|
+
runGit(repo, ['commit', '--allow-empty', '-m', 'Initial commit']);
|
|
2597
|
+
|
|
2598
|
+
const first = getStatus(repo);
|
|
2599
|
+
fs.writeFileSync(path.join(repo, 'late.txt'), 'added after the first read was admitted\n');
|
|
2600
|
+
const second = getStatus(repo, { mode: 'light' });
|
|
2601
|
+
const third = getStatus(repo);
|
|
2602
|
+
|
|
2603
|
+
const [firstStatus, secondStatus, thirdStatus] = await Promise.all([first, second, third]);
|
|
2604
|
+
expect(firstStatus.current).toBe('main');
|
|
2605
|
+
expect(secondStatus.files.map((file) => file.path)).toContain('late.txt');
|
|
2606
|
+
expect(thirdStatus.files.map((file) => file.path)).toContain('late.txt');
|
|
2607
|
+
// The follow-up run served both later callers at the widest requested mode.
|
|
2608
|
+
expect(secondStatus.diffStats).toBeDefined();
|
|
2609
|
+
expect(thirdStatus.diffStats).toBeDefined();
|
|
2610
|
+
});
|
|
2611
|
+
});
|
|
2612
|
+
|
|
2613
|
+
describe('getStatus untracked directories', () => {
|
|
2614
|
+
const callDiffRoute = async (endpoint, query) => {
|
|
2615
|
+
const routes = new Map();
|
|
2616
|
+
registerGitRoutes({ get: (url, handler) => routes.set(url, handler), post() {}, put() {}, delete() {} });
|
|
2617
|
+
let status = 200;
|
|
2618
|
+
let body;
|
|
2619
|
+
await routes.get(`/api/git/${endpoint}`)({ query }, {
|
|
2620
|
+
status(value) { status = value; return this; },
|
|
2621
|
+
json(value) { body = value; },
|
|
2622
|
+
});
|
|
2623
|
+
return { status, body };
|
|
2624
|
+
};
|
|
2625
|
+
|
|
2626
|
+
const createCommittedRepo = () => {
|
|
2627
|
+
const repo = createTempDir();
|
|
2628
|
+
runGit(repo, ['init', '-b', 'main']);
|
|
2629
|
+
runGit(repo, ['config', 'user.email', 'test@example.com']);
|
|
2630
|
+
runGit(repo, ['config', 'user.name', 'Test User']);
|
|
2631
|
+
fs.writeFileSync(path.join(repo, 'README.md'), '# Test\n');
|
|
2632
|
+
runGit(repo, ['add', 'README.md']);
|
|
2633
|
+
runGit(repo, ['commit', '-m', 'Initial commit']);
|
|
2634
|
+
return repo;
|
|
2635
|
+
};
|
|
2636
|
+
|
|
2637
|
+
const writeFiles = (root, count) => {
|
|
2638
|
+
fs.mkdirSync(root, { recursive: true });
|
|
2639
|
+
for (let index = 0; index < count; index += 1) {
|
|
2640
|
+
fs.writeFileSync(path.join(root, `file-${String(index).padStart(5, '0')}.txt`), `${index}\n`);
|
|
2641
|
+
}
|
|
2642
|
+
};
|
|
2643
|
+
|
|
2644
|
+
it('lists the files of an ordinary new directory one by one', async () => {
|
|
2645
|
+
if (!canRunGit()) return;
|
|
2646
|
+
|
|
2647
|
+
const repo = createCommittedRepo();
|
|
2648
|
+
writeFiles(path.join(repo, 'feature', 'deep'), 3);
|
|
2649
|
+
fs.writeFileSync(path.join(repo, 'loose.txt'), 'loose\n');
|
|
2650
|
+
|
|
2651
|
+
const paths = (await getStatus(repo)).files.map((file) => file.path);
|
|
2652
|
+
expect(paths).toEqual([
|
|
2653
|
+
'feature/deep/file-00000.txt',
|
|
2654
|
+
'feature/deep/file-00001.txt',
|
|
2655
|
+
'feature/deep/file-00002.txt',
|
|
2656
|
+
'loose.txt',
|
|
2657
|
+
]);
|
|
2658
|
+
});
|
|
2659
|
+
|
|
2660
|
+
it('keeps a directory with more than a thousand new files as one entry the diff routes explain', async () => {
|
|
2661
|
+
if (!canRunGit()) return;
|
|
2662
|
+
|
|
2663
|
+
const repo = createCommittedRepo();
|
|
2664
|
+
writeFiles(path.join(repo, 'node_modules', 'pkg'), 1001);
|
|
2665
|
+
writeFiles(path.join(repo, 'small'), 2);
|
|
2666
|
+
|
|
2667
|
+
const status = await getStatus(repo);
|
|
2668
|
+
expect(status.files.map((file) => file.path)).toEqual([
|
|
2669
|
+
'node_modules/',
|
|
2670
|
+
'small/file-00000.txt',
|
|
2671
|
+
'small/file-00001.txt',
|
|
2672
|
+
]);
|
|
2673
|
+
expect(status.files[0]).toMatchObject({ index: '?', working_dir: '?' });
|
|
2674
|
+
|
|
2675
|
+
for (const endpoint of ['diff', 'file-diff']) {
|
|
2676
|
+
const { status: httpStatus, body } = await callDiffRoute(endpoint, { directory: repo, path: 'node_modules/' });
|
|
2677
|
+
expect(httpStatus).toBe(422);
|
|
2678
|
+
expect(body).toEqual({ code: 'untracked_directory', error: 'Path is a directory of untracked files: node_modules/' });
|
|
2679
|
+
}
|
|
2680
|
+
});
|
|
2681
|
+
});
|
|
@@ -63,6 +63,11 @@
|
|
|
63
63
|
|
|
64
64
|
## Consumers of PR data
|
|
65
65
|
|
|
66
|
+
PR list/search failures propagate as errors instead of successful empty lists.
|
|
67
|
+
Comparison pickers use these responses to offer retry rather than claiming the
|
|
68
|
+
repository has no PRs. A failed repository in a multi-repository listing fails
|
|
69
|
+
that page, so callers cannot mistake a partial page for a complete one.
|
|
70
|
+
|
|
66
71
|
- `packages/ui/src/components/session/SessionSidebar.tsx` reads all PR entries and maps them to `directory::branch`.
|
|
67
72
|
- `packages/ui/src/components/session/sidebar/SessionGroupSection.tsx` renders the compact badge, PR number, title, checks summary, and GitHub link.
|
|
68
73
|
- `packages/ui/src/components/views/git/PullRequestSection.tsx` uses the same shared entry for the full PR workflow.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { stat } from 'node:fs/promises';
|
|
2
|
-
import { getRemotes,
|
|
2
|
+
import { getRemotes, getTrackingBranch } from '../git/index.js';
|
|
3
3
|
import { resolveGitHubRepoFromDirectory } from './repo/index.js';
|
|
4
4
|
import { noteIfGitHubRateLimit } from './rate-limit.js';
|
|
5
5
|
|
|
@@ -644,13 +644,13 @@ export async function resolveGitHubPrStatus({ octokit, directory, branch, remote
|
|
|
644
644
|
const normalizedBranch = normalizeText(branch);
|
|
645
645
|
const normalizedRemoteName = normalizeText(remoteName) || 'origin';
|
|
646
646
|
|
|
647
|
-
const [
|
|
648
|
-
|
|
647
|
+
const [tracking, remotes] = await Promise.all([
|
|
648
|
+
getTrackingBranch(directory).catch(() => null),
|
|
649
649
|
getRemotes(directory).catch(() => []),
|
|
650
650
|
]);
|
|
651
651
|
|
|
652
|
-
const trackingRemoteName = parseTrackingRemoteName(
|
|
653
|
-
const trackingBranchName = parseTrackingBranchName(
|
|
652
|
+
const trackingRemoteName = parseTrackingRemoteName(tracking);
|
|
653
|
+
const trackingBranchName = parseTrackingBranchName(tracking);
|
|
654
654
|
const branchCandidates = [];
|
|
655
655
|
pushUnique(branchCandidates, normalizedBranch);
|
|
656
656
|
pushUnique(branchCandidates, trackingBranchName);
|
|
@@ -781,14 +781,14 @@ export function registerGitHubRoutes(app) {
|
|
|
781
781
|
// Determine the source remote for the head branch
|
|
782
782
|
// Priority: 1) explicit headRemote, 2) tracking branch remote, 3) 'origin' if targeting non-origin
|
|
783
783
|
let sourceRemote = headRemote;
|
|
784
|
-
const {
|
|
784
|
+
const { getTrackingBranch, getRemotes } = await import('../git/index.js');
|
|
785
785
|
|
|
786
786
|
// If no explicit headRemote, check the branch's tracking info
|
|
787
787
|
if (!sourceRemote) {
|
|
788
|
-
const
|
|
789
|
-
if (
|
|
788
|
+
const tracking = await getTrackingBranch(directory).catch(() => null);
|
|
789
|
+
if (tracking) {
|
|
790
790
|
// tracking is like "gsxdsm/fix/multi-remote-branch-creation" or "origin/main"
|
|
791
|
-
const trackingRemote =
|
|
791
|
+
const trackingRemote = tracking.split('/')[0];
|
|
792
792
|
if (trackingRemote) {
|
|
793
793
|
sourceRemote = trackingRemote;
|
|
794
794
|
}
|
|
@@ -1513,7 +1513,7 @@ export function registerGitHubRoutes(app) {
|
|
|
1513
1513
|
return res.json({ connected: true, repo, prs, page: effectivePage, hasMore });
|
|
1514
1514
|
} catch (error) {
|
|
1515
1515
|
console.error('Failed to search GitHub PRs:', error);
|
|
1516
|
-
|
|
1516
|
+
throw error;
|
|
1517
1517
|
}
|
|
1518
1518
|
}
|
|
1519
1519
|
|
|
@@ -1532,7 +1532,7 @@ export function registerGitHubRoutes(app) {
|
|
|
1532
1532
|
return { prs, hasMore };
|
|
1533
1533
|
} catch (error) {
|
|
1534
1534
|
console.warn(`Failed to list PRs for ${repoRef.owner}/${repoRef.repo}:`, error?.message || error);
|
|
1535
|
-
|
|
1535
|
+
throw error;
|
|
1536
1536
|
}
|
|
1537
1537
|
};
|
|
1538
1538
|
|