@aiworker/web 1.22.0 → 1.23.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 +4 -4
- package/bin/cli.js +14 -4
- package/bin/cli.test.js +165 -73
- package/bin/lib/cli-api-target.js +2 -2
- package/bin/lib/cli-args.js +119 -119
- package/bin/lib/cli-lifecycle.js +9 -9
- package/bin/lib/cli-network.js +3 -3
- package/bin/lib/cli-paths.js +3 -3
- package/bin/lib/cli-ports.js +2 -2
- package/bin/lib/cli-process.js +4 -4
- package/bin/lib/cli-startup.js +64 -15
- package/bin/lib/cli-tunnel-profiles.js +1 -1
- package/bin/lib/cli-tunnel-utils.js +2 -2
- package/bin/lib/commands-connect-url.js +19 -19
- package/bin/lib/commands-lifecycle.js +27 -27
- package/bin/lib/commands-logs.js +5 -5
- package/bin/lib/commands-models.js +1 -1
- package/bin/lib/commands-projects.js +1 -1
- package/bin/lib/commands-schedule.js +1 -1
- package/bin/lib/commands-serve.js +29 -29
- package/bin/lib/commands-session.js +1 -1
- package/bin/lib/commands-startup.js +45 -10
- package/bin/lib/commands-status.js +1 -1
- package/bin/lib/commands-tunnel.js +40 -40
- package/bin/lib/commands-update.js +1 -1
- package/bin/lib/commands-update.test.js +6 -6
- package/dist/assets/{CodeMirrorCommentWidgets-B3u7UIcE.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
- package/dist/assets/DiffView-DXjVsEOw.js +4 -0
- package/dist/assets/DirtyBranchSwitchDialog-aig6zJjg.js +1 -0
- package/dist/assets/FilesView-CJdG0-wk.js +17 -0
- package/dist/assets/GitView-BXsS3jG6.js +6 -0
- package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-D_hpXBnF.js} +1 -1
- package/dist/assets/LinearIssuesView-BoUiLQG1.js +1 -0
- package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
- package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
- package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
- package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-8p6nLJaU.js} +1 -1
- package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
- package/dist/assets/PlanView-CJTmOF6I.js +5 -0
- package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
- package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-Cpb4hM6q.js} +1 -1
- package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
- package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
- package/dist/assets/ToolPartDiffPreview-DtZLzrSK.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
- package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
- package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
- package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
- package/dist/assets/de-DCrsQ5zS.js +10 -0
- package/dist/assets/es-Dwv4YfH6.js +16 -0
- package/dist/assets/fr-BCWg4Cnx.js +14 -0
- package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
- package/dist/assets/index-B68h5S5B.css +1 -0
- package/dist/assets/index-DVwMarHI.js +39 -0
- package/dist/assets/{index-CODwDsU_.js → index-jXSK5RrB.js} +1 -1
- package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-B_x_rVon.js} +1 -1
- package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-DW_xMer1.js} +1 -1
- package/dist/assets/ja-Bruw3RYC.js +16 -0
- package/dist/assets/ko-idhXGF8j.js +16 -0
- package/dist/assets/main-BkyaQhJX.js +930 -0
- package/dist/assets/{main-C5DVOxc6.js → main-CzdPuL0s.js} +2 -2
- package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
- package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-CCkqKOLp.js} +2 -2
- package/dist/assets/{mobile-CcaxB0n4.js → mobile-mvxTo055.js} +2 -2
- package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
- package/dist/assets/pl-CGZWUqZJ.js +16 -0
- package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
- package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
- package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
- package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
- package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-CRl0SUC9.js} +1 -1
- package/dist/assets/surface-oMvhsog3.js +1 -0
- package/dist/assets/tr-BK_KiO2V.js +7 -0
- package/dist/assets/uk-CG74xV8N.js +16 -0
- package/dist/assets/useAppFontEffects-CPKeOpUo.js +7284 -0
- package/dist/assets/useGitComparison-DH220WmO.js +1 -0
- package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
- package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
- package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
- package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
- package/dist/assets/{web-components-Bhg83tjf.js → web-components-z7hfnS8n.js} +1 -1
- package/dist/assets/zh-CN-T4AXofZI.js +16 -0
- package/dist/assets/zh-TW-Dte40sxp.js +16 -0
- package/dist/index.html +21 -59
- package/dist/mini-chat.html +7 -7
- package/dist/mobile.html +5 -5
- package/package.json +3 -4
- package/server/index.d.ts +15 -0
- package/server/index.js +158 -89
- package/server/lib/agent-memory/actions.js +2 -2
- package/server/lib/agent-memory/feature-flag.js +1 -1
- package/server/lib/agent-memory/feature-flag.test.js +8 -8
- package/server/lib/agent-memory/routes.js +2 -2
- package/server/lib/agent-tool/runtime.js +37 -54
- package/server/lib/agent-tool/runtime.test.js +59 -59
- package/server/lib/browser-control/broker.js +3 -3
- package/server/lib/browser-control/broker.test.js +1 -1
- package/server/lib/changelog/update-notes.js +82 -0
- package/server/lib/changelog/update-notes.test.js +59 -0
- package/server/lib/client-auth/remote-clients.test.js +5 -5
- package/server/lib/context-obligatory/runtime.js +7 -7
- package/server/lib/context-obligatory/runtime.test.js +5 -5
- package/server/lib/data-dir-migration.js +40 -0
- package/server/lib/data-dir-migration.test.js +59 -0
- package/server/lib/dev-servers/parse.js +2 -2
- package/server/lib/dev-servers/parse.test.js +2 -2
- package/server/lib/dev-servers/routes.js +1 -1
- package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
- package/server/lib/dev-tunnel/client.js +1 -1
- package/server/lib/dev-tunnel/runtime.js +5 -5
- package/server/lib/dev-tunnel/tunnel.test.js +21 -1
- package/server/lib/dictation/local/model-catalog.js +1 -1
- package/server/lib/dictation/local/worker-process.js +2 -2
- package/server/lib/event-stream/directory-ws-bridge.js +1 -1
- package/server/lib/event-stream/global-ws-bridge.js +1 -1
- package/server/lib/event-stream/protocol.test.js +4 -4
- package/server/lib/event-stream/runtime.test.js +18 -18
- package/server/lib/fs/DOCUMENTATION.md +5 -0
- package/server/lib/fs/routes.js +68 -26
- package/server/lib/fs/routes.test.js +208 -22
- package/server/lib/git/DOCUMENTATION.md +32 -6
- package/server/lib/git/identity-storage.js +1 -1
- package/server/lib/git/issue-2746-longpaths.test.js +4 -4
- package/server/lib/git/routes.js +41 -5
- package/server/lib/git/service.js +300 -224
- package/server/lib/git/service.test.js +596 -28
- package/server/lib/github/auth.js +8 -8
- package/server/lib/github/pr-status.js +1 -1
- package/server/lib/github/routes.js +2 -2
- package/server/lib/inherited-env.js +17 -10
- package/server/lib/inherited-env.test.js +10 -11
- package/server/lib/linear/auth.js +8 -8
- package/server/lib/linear/auth.test.js +24 -24
- package/server/lib/linear/issues.test.js +6 -6
- package/server/lib/linear/mapping.test.js +4 -4
- package/server/lib/linear/oauth.js +2 -2
- package/server/lib/linear/oauth.test.js +14 -14
- package/server/lib/linear/routes.js +5 -5
- package/server/lib/linear/routes.test.js +16 -16
- package/server/lib/linear/status-runtime.test.js +9 -9
- package/server/lib/linear/status.js +1 -1
- package/server/lib/linear/status.test.js +15 -15
- package/server/lib/linear/teams.test.js +4 -4
- package/server/lib/magic-prompts/routes.js +2 -2
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
- package/server/lib/mcp-reconnect/runtime.js +122 -0
- package/server/lib/mcp-reconnect/runtime.test.js +140 -0
- package/server/lib/message-queue/DOCUMENTATION.md +161 -0
- package/server/lib/message-queue/runtime.js +870 -0
- package/server/lib/message-queue/runtime.test.js +482 -0
- package/server/lib/network-defaults.js +23 -0
- package/server/lib/network-defaults.test.js +52 -0
- package/server/lib/notifications/apns-runtime.js +17 -17
- package/server/lib/notifications/apns-runtime.test.js +11 -11
- package/server/lib/notifications/emitter-runtime.js +1 -1
- package/server/lib/notifications/emitter-runtime.test.js +1 -1
- package/server/lib/notifications/push-runtime.js +7 -7
- package/server/lib/notifications/routes.js +1 -1
- package/server/lib/notifications/runtime.js +1 -1
- package/server/lib/openchamber-control/action-resolution.test.js +10 -10
- package/server/lib/openchamber-control/actions.js +24 -24
- package/server/lib/openchamber-control/error.js +4 -4
- package/server/lib/openchamber-control/routes.js +2 -2
- package/server/lib/openchamber-control/routes.test.js +5 -5
- package/server/lib/openchamber-control/screenshots.js +1 -1
- package/server/lib/openchamber-control/screenshots.test.js +1 -1
- package/server/lib/openchamber-control/service.js +50 -50
- package/server/lib/openchamber-control/service.test.js +5 -5
- package/server/lib/openchamber-sessions/routes.js +124 -22
- package/server/lib/openchamber-sessions/routes.test.js +99 -11
- package/server/lib/opencode/DOCUMENTATION.md +30 -6
- package/server/lib/opencode/auth.js +1 -1
- package/server/lib/opencode/bootstrap-runtime.js +8 -6
- package/server/lib/opencode/cli-options.js +8 -8
- package/server/lib/opencode/config-paths.test.js +116 -0
- package/server/lib/opencode/core-routes.js +8 -7
- package/server/lib/opencode/core-routes.test.js +11 -11
- package/server/lib/opencode/env-config.js +5 -5
- package/server/lib/opencode/env-config.test.js +7 -7
- package/server/lib/opencode/env-runtime.js +10 -10
- package/server/lib/opencode/env-runtime.test.js +18 -18
- package/server/lib/opencode/feature-routes-runtime.js +19 -12
- package/server/lib/opencode/hmr-state-runtime.js +2 -2
- package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
- package/server/lib/opencode/input-history-scope.js +14 -0
- package/server/lib/opencode/lifecycle.js +8 -8
- package/server/lib/opencode/lifecycle.test.js +4 -4
- package/server/lib/opencode/managed-plugin-config.js +35 -0
- package/server/lib/opencode/managed-process-registry.js +3 -3
- package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
- package/server/lib/opencode/openchamber-routes.js +79 -19
- package/server/lib/opencode/openchamber-routes.test.js +139 -12
- package/server/lib/opencode/plugins.js +2 -2
- package/server/lib/opencode/plugins.test.js +1 -1
- package/server/lib/opencode/project-icon-routes.js +2 -2
- package/server/lib/opencode/project-icon-routes.test.js +1 -1
- package/server/lib/opencode/providers.js +44 -2
- package/server/lib/opencode/providers.test.js +131 -4
- package/server/lib/opencode/proxy.js +2 -2
- package/server/lib/opencode/pwa-manifest-routes.js +1 -1
- package/server/lib/opencode/restart-session-recovery.test.js +1 -1
- package/server/lib/opencode/routes-upgrade.test.js +4 -4
- package/server/lib/opencode/routes.js +19 -17
- package/server/lib/opencode/server-startup-runtime.js +6 -6
- package/server/lib/opencode/session-runtime.js +3 -3
- package/server/lib/opencode/session-runtime.test.js +6 -6
- package/server/lib/opencode/settings-files.js +214 -0
- package/server/lib/opencode/settings-helpers.js +116 -44
- package/server/lib/opencode/settings-helpers.test.js +354 -12
- package/server/lib/opencode/settings-registry.json +747 -0
- package/server/lib/opencode/settings-runtime.js +141 -20
- package/server/lib/opencode/settings-runtime.test.js +191 -4
- package/server/lib/opencode/shared.js +5 -2
- package/server/lib/opencode/shared.test.js +7 -7
- package/server/lib/opencode/shutdown-runtime.js +2 -0
- package/server/lib/opencode/skill-routes.js +2 -1
- package/server/lib/opencode/snippets.js +1 -2
- package/server/lib/opencode/startup-performance.js +1 -1
- package/server/lib/opencode/startup-performance.test.js +6 -6
- package/server/lib/opencode/static-routes-runtime.js +8 -8
- package/server/lib/opencode/static-routes-runtime.test.js +7 -7
- package/server/lib/opencode/upgrade-capability.js +2 -2
- package/server/lib/opencode/upgrade-capability.test.js +3 -3
- package/server/lib/package-manager.js +17 -42
- package/server/lib/package-manager.test.js +19 -19
- package/server/lib/permission-auto-accept/runtime.js +1 -1
- package/server/lib/project-context/DOCUMENTATION.md +25 -1
- package/server/lib/project-context/routes.http.test.js +18 -0
- package/server/lib/project-context/routes.js +16 -1
- package/server/lib/project-context/runtime.js +248 -23
- package/server/lib/project-context/runtime.test.js +106 -3
- package/server/lib/projects/DOCUMENTATION.md +76 -0
- package/server/lib/projects/project-config.js +144 -11
- package/server/lib/projects/project-id.js +17 -0
- package/server/lib/projects/project-setup.js +409 -0
- package/server/lib/projects/project-setup.test.js +557 -0
- package/server/lib/projects/routes.js +59 -0
- package/server/lib/quota/DOCUMENTATION.md +40 -2
- package/server/lib/quota/credentials/providers.js +4 -0
- package/server/lib/quota/credentials/store.js +4 -4
- package/server/lib/quota/credentials/store.test.js +8 -8
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/cline-pass.js +125 -0
- package/server/lib/quota/providers/cline-pass.test.js +103 -0
- package/server/lib/quota/providers/exe-dev.js +75 -0
- package/server/lib/quota/providers/exe-dev.test.js +47 -0
- package/server/lib/quota/providers/hyper.js +118 -0
- package/server/lib/quota/providers/hyper.test.js +126 -0
- package/server/lib/quota/providers/index.js +22 -0
- package/server/lib/quota/providers/neuralwatt.js +3 -6
- package/server/lib/quota/providers/neuralwatt.test.js +7 -7
- package/server/lib/quota/providers/ollama-cloud.js +32 -1
- package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
- package/server/lib/quota/providers/opencode-go.js +3 -2
- package/server/lib/quota/providers/opencode-go.test.js +35 -6
- package/server/lib/quota/providers/openrouter.js +119 -24
- package/server/lib/quota/providers/openrouter.test.js +330 -0
- package/server/lib/quota/providers/xai.js +1 -1
- package/server/lib/quota/routes.js +2 -0
- package/server/lib/quota/utils/auth.js +1 -1
- package/server/lib/relay/DOCUMENTATION.md +56 -1
- package/server/lib/relay/cross-compat.test.js +26 -0
- package/server/lib/relay/downstream-scheduler.js +154 -0
- package/server/lib/relay/downstream-scheduler.test.js +162 -0
- package/server/lib/relay/e2ee.js +10 -8
- package/server/lib/relay/flow-control.test.js +177 -0
- package/server/lib/relay/host-client.js +38 -9
- package/server/lib/relay/host-client.test.js +9 -1
- package/server/lib/relay/host-lock.js +1 -1
- package/server/lib/relay/service.js +5 -5
- package/server/lib/relay/tunnel-codec.js +9 -0
- package/server/lib/relay/tunnel-host.js +69 -18
- package/server/lib/relay/tunnel-host.test.js +9 -1
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
- package/server/lib/scheduled-tasks/routes.js +5 -5
- package/server/lib/scheduled-tasks/service.js +25 -25
- package/server/lib/scheduled-tasks/service.test.js +1 -1
- package/server/lib/security/bind-host.js +4 -4
- package/server/lib/security/bind-host.test.js +1 -1
- package/server/lib/security/request-security.js +11 -5
- package/server/lib/security/request-security.test.js +14 -14
- package/server/lib/session-assist/runtime.js +15 -18
- package/server/lib/session-folders/routes.js +2 -2
- package/server/lib/session-folders/routes.test.js +8 -8
- package/server/lib/session-goal/DOCUMENTATION.md +20 -7
- package/server/lib/session-goal/create.js +4 -2
- package/server/lib/session-goal/create.test.js +1 -1
- package/server/lib/session-goal/objectives.js +4 -4
- package/server/lib/session-goal/routes.js +1 -1
- package/server/lib/session-goal/runtime.js +91 -30
- package/server/lib/session-goal/runtime.test.js +428 -2
- package/server/lib/session-knowledge/runtime.js +11 -11
- package/server/lib/session-knowledge/runtime.test.js +5 -5
- package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
- package/server/lib/skills-catalog/cache.test.js +2 -2
- package/server/lib/skills-catalog/disk-cache.js +5 -5
- package/server/lib/skills-catalog/github-meta.test.js +2 -2
- package/server/lib/skills-catalog/install.js +3 -2
- package/server/lib/small-model/DOCUMENTATION.md +7 -2
- package/server/lib/small-model/call.js +18 -7
- package/server/lib/small-model/call.test.js +58 -6
- package/server/lib/small-model/index.js +12 -16
- package/server/lib/small-model/index.test.js +1 -1
- package/server/lib/small-model/resolve.js +1 -1
- package/server/lib/small-model/resolve.test.js +1 -1
- package/server/lib/small-model/routes.js +2 -1
- package/server/lib/small-model/runtime-providers.js +15 -7
- package/server/lib/small-model/runtime-providers.test.js +7 -1
- package/server/lib/system-prompt/runtime.js +3 -22
- package/server/lib/system-prompt/runtime.test.js +3 -3
- package/server/lib/terminal/DOCUMENTATION.md +14 -10
- package/server/lib/terminal/runtime.js +190 -31
- package/server/lib/terminal/runtime.test.js +572 -38
- package/server/lib/terminal/shells.js +20 -3
- package/server/lib/terminal/shells.test.js +33 -2
- package/server/lib/tts/base-url.js +3 -3
- package/server/lib/tts/routes.test.js +20 -20
- package/server/lib/ui-auth/ui-auth.js +12 -9
- package/server/lib/ui-auth/ui-auth.test.js +19 -3
- package/server/lib/ui-auth/ui-passkeys.js +7 -7
- package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
- package/server/lib/walkthrough/index.js +1 -0
- package/server/lib/walkthrough/jobs.test.js +1 -1
- package/server/lib/walkthrough/language.test.js +1 -1
- package/server/lib/walkthrough/model-settings.js +11 -15
- package/server/lib/walkthrough/model-settings.test.js +1 -1
- package/server/lib/walkthrough/prompt.js +3 -1
- package/server/lib/walkthrough/pull-request.test.js +5 -5
- package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
- package/server/lib/walkthrough/routes.test.js +26 -5
- package/server/lib/walkthrough/sources.js +23 -2
- package/server/lib/walkthrough/store.js +3 -3
- package/server/lib/walkthrough/store.test.js +1 -1
- package/server/opencode-proxy.test.js +2 -2
- package/server/proxy-headers.js +1 -1
- package/server/sse-routes.test.js +4 -4
- package/README.md.bak +0 -250
- package/bin/lib/DOCUMENTATION.md.bak +0 -163
- package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
- package/dist/assets/DiffView-DluiRSla.js +0 -4
- package/dist/assets/FilesView-Bdq8ppva.js +0 -17
- package/dist/assets/GitView-Znn7VJcP.js +0 -6
- package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
- package/dist/assets/MarkdownImageGallery-ClOvuZxC.js +0 -1
- package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
- package/dist/assets/OnboardingScreen-D2DG7K6b.js +0 -1
- package/dist/assets/PierreDiffViewer-B7m3f0d7.js +0 -157
- package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
- package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
- package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
- package/dist/assets/WalkthroughView-egQgxel3.js +0 -4
- package/dist/assets/contextPanelEmbeddedChat-C3fjDbdg.js +0 -1
- package/dist/assets/de-C1P6X5Rs.js +0 -9
- package/dist/assets/es-CDgaF1_t.js +0 -15
- package/dist/assets/fr-Bgxqvvz_.js +0 -13
- package/dist/assets/index-C57jT0Sa.css +0 -1
- package/dist/assets/index-DW_E3rnL.js +0 -7
- package/dist/assets/ja-BDRrEcNP.js +0 -15
- package/dist/assets/ko-Ba8sQV79.js +0 -15
- package/dist/assets/main-Bv5Pkngh.js +0 -928
- package/dist/assets/markdownCore-CZwzz5gw.js +0 -3
- package/dist/assets/patchFileDiff-yNfWXN4E.js +0 -10
- package/dist/assets/pl-Dioz29BI.js +0 -15
- package/dist/assets/pt-BR-DIs6ElwV.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-BcKLHXKe.js +0 -2
- package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
- package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
- package/dist/assets/scheduledTasksApi-DLW0mkQK.js +0 -1
- package/dist/assets/tr-DFnMlPxY.js +0 -6
- package/dist/assets/uk-ZrCNjM7Y.js +0 -15
- package/dist/assets/useAppFontEffects-CjrntdL0.js +0 -7310
- package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
- package/dist/assets/useRootScrollLock-BHYPkrLv.js +0 -1
- package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
- package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
- package/dist/assets/zh-CN-iv_AV4EC.js +0 -15
- package/dist/assets/zh-TW-CzEUk38j.js +0 -15
- package/server/index.d.ts.bak +0 -39
- package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
- package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
- package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
- package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
- package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
- package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
- package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
- package/server/lib/git/DOCUMENTATION.md.bak +0 -193
- package/server/lib/github/DOCUMENTATION.md.bak +0 -183
- package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
- package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
- package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
- package/server/lib/notifications/APNS.md.bak +0 -135
- package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
- package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
- package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
- package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
- package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
- package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
- package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
- package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
- package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
- package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
- package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
- package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
- package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
- package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
- package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
- package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
- package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
- package/server/lib/text/DOCUMENTATION.md.bak +0 -35
- package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
- package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
- package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
- package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
|
@@ -200,7 +200,7 @@ const resolveGitBinary = () => {
|
|
|
200
200
|
return resolvedGitBinary;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
const explicit = [process.env.GIT_BINARY, process.env.
|
|
203
|
+
const explicit = [process.env.GIT_BINARY, process.env.AIWORKER_GIT_BINARY]
|
|
204
204
|
.map((value) => (typeof value === 'string' ? value.trim() : ''))
|
|
205
205
|
.filter(Boolean);
|
|
206
206
|
for (const candidate of explicit) {
|
|
@@ -352,19 +352,20 @@ const buildGitEnv = async () => {
|
|
|
352
352
|
return env;
|
|
353
353
|
};
|
|
354
354
|
|
|
355
|
-
const createGit = async (directory, { allowUnsafeSshCommand = false } = {}) => {
|
|
355
|
+
const createGit = async (directory, { allowUnsafeSshCommand = false, allowUnsafeCredentialHelper = false } = {}) => {
|
|
356
356
|
const env = await buildGitEnv();
|
|
357
357
|
const spawnOptions = { windowsHide: true };
|
|
358
358
|
const binary = getGitBinary();
|
|
359
359
|
const hasCustomBinary = typeof binary === 'string' && binary.trim() && binary !== 'git' && binary !== 'git.exe';
|
|
360
|
-
const unsafe = hasCustomBinary || allowUnsafeSshCommand
|
|
360
|
+
const unsafe = hasCustomBinary || allowUnsafeSshCommand || allowUnsafeCredentialHelper
|
|
361
361
|
? {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
362
|
+
...(hasCustomBinary && { allowUnsafeCustomBinary: true }),
|
|
363
|
+
...(allowUnsafeSshCommand && { allowUnsafeSshCommand: true }),
|
|
364
|
+
...(allowUnsafeCredentialHelper && { allowUnsafeCredentialHelper: true }),
|
|
365
|
+
}
|
|
365
366
|
: undefined;
|
|
366
367
|
// Always pin simple-git to an explicit working directory. Omitting baseDir
|
|
367
|
-
// makes simple-git use process.cwd(), which breaks when the
|
|
368
|
+
// makes simple-git use process.cwd(), which breaks when the AiWorker
|
|
368
369
|
// server was launched from a neutral directory (e.g. $HOME) and the opened
|
|
369
370
|
// project lives elsewhere — session/project discovery then sees spurious
|
|
370
371
|
// "not a git repository" errors and can abort enumeration.
|
|
@@ -684,6 +685,15 @@ const parseWorktreePorcelain = (raw) => {
|
|
|
684
685
|
const branchRef = line.substring('branch '.length).trim();
|
|
685
686
|
current.branchRef = branchRef;
|
|
686
687
|
current.branch = cleanBranchName(branchRef);
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// git marks a worktree whose directory is gone (deleted outside git) as
|
|
692
|
+
// prunable; it stays registered until `git worktree prune`. The sidebar
|
|
693
|
+
// needs that distinction: the directory is missing, but the sessions that
|
|
694
|
+
// lived there are not.
|
|
695
|
+
if (line === 'prunable' || line.startsWith('prunable ')) {
|
|
696
|
+
current.prunable = true;
|
|
687
697
|
}
|
|
688
698
|
}
|
|
689
699
|
|
|
@@ -932,7 +942,7 @@ const runGitCommand = async (cwd, args) => {
|
|
|
932
942
|
} catch (error) {
|
|
933
943
|
return {
|
|
934
944
|
success: false,
|
|
935
|
-
exitCode:
|
|
945
|
+
exitCode: Number.isInteger(error?.code) ? error.code : null,
|
|
936
946
|
stdout: String(error?.stdout || ''),
|
|
937
947
|
stderr: String(error?.stderr || ''),
|
|
938
948
|
message: parseGitErrorText(error),
|
|
@@ -991,7 +1001,7 @@ const getFileIdentity = async (filePath) => {
|
|
|
991
1001
|
}
|
|
992
1002
|
};
|
|
993
1003
|
|
|
994
|
-
//
|
|
1004
|
+
// AiWorker places managed worktrees under a deep data-dir path
|
|
995
1005
|
// (`<XDG_DATA_HOME>/opencode/worktree/<40-char project id>/<name>/`). On
|
|
996
1006
|
// Windows that prefix plus a deeply nested repo file routinely exceeds
|
|
997
1007
|
// MAX_PATH (260). Git can check those paths out when core.longpaths is
|
|
@@ -1009,7 +1019,7 @@ const formatWorktreePopulateError = (message) => {
|
|
|
1009
1019
|
return [
|
|
1010
1020
|
text,
|
|
1011
1021
|
'The worktree checkout path exceeds this system\'s path-length limit.',
|
|
1012
|
-
'
|
|
1022
|
+
'AiWorker enables Git `core.longpaths` for worktree population; if this still fails on Windows, enable OS long paths (LongPathsEnabled) or open the repository from a shorter absolute path.',
|
|
1013
1023
|
].join('\n');
|
|
1014
1024
|
};
|
|
1015
1025
|
|
|
@@ -1019,7 +1029,7 @@ export const ensureWorktreeLongpaths = async (directory) => {
|
|
|
1019
1029
|
return;
|
|
1020
1030
|
}
|
|
1021
1031
|
// Local config is shared across linked worktrees via the common git dir, so
|
|
1022
|
-
// subsequent
|
|
1032
|
+
// subsequent AiWorker and CLI git operations in this repo also get long
|
|
1023
1033
|
// path support. Failures here are non-fatal: populate still passes
|
|
1024
1034
|
// `-c core.longpaths=true` on reset.
|
|
1025
1035
|
await runGitCommand(directory, ['config', 'core.longpaths', 'true']);
|
|
@@ -1309,7 +1319,7 @@ export async function computeIntegratePlan(input = {}) {
|
|
|
1309
1319
|
}
|
|
1310
1320
|
|
|
1311
1321
|
const createIntegrateTempWorktree = async (repoRoot, targetBranch) => {
|
|
1312
|
-
const tmpParent = path.join(os.homedir(), '.config', '
|
|
1322
|
+
const tmpParent = path.join(os.homedir(), '.config', 'aiworker', 'tmp');
|
|
1313
1323
|
await fsp.mkdir(tmpParent, { recursive: true });
|
|
1314
1324
|
const tmpDir = await fsp.mkdtemp(path.join(tmpParent, 'oc-integrate-'));
|
|
1315
1325
|
try {
|
|
@@ -1622,7 +1632,7 @@ const resolveCandidateDirectory = async (worktreeRoot, preferredName, explicitBr
|
|
|
1622
1632
|
return { name, directory, branch: explicitBranchName };
|
|
1623
1633
|
}
|
|
1624
1634
|
|
|
1625
|
-
const branch = `
|
|
1635
|
+
const branch = `aiworker/${name}`;
|
|
1626
1636
|
const branchRef = `refs/heads/${branch}`;
|
|
1627
1637
|
const branchExists = await runGitCommand(primaryWorktree, ['show-ref', '--verify', '--quiet', branchRef]);
|
|
1628
1638
|
if (branchExists.success) {
|
|
@@ -1742,7 +1752,7 @@ const loadProjectStartCommand = async (projectID) => {
|
|
|
1742
1752
|
|
|
1743
1753
|
// OpenCode owns its own project/sandbox registry. It records a worktree as a
|
|
1744
1754
|
// sandbox itself when an instance boots for that directory, and filters entries
|
|
1745
|
-
// whose directory no longer exists when reading them back.
|
|
1755
|
+
// whose directory no longer exists when reading them back. AiWorker used to
|
|
1746
1756
|
// write that state directly into OpenCode's storage JSON and SQLite database,
|
|
1747
1757
|
// behind the back of the running process: the row changed but the server was
|
|
1748
1758
|
// never told, so a worktree created while OpenCode was running stayed unknown
|
|
@@ -2137,7 +2147,7 @@ export async function hasLocalIdentity(directory) {
|
|
|
2137
2147
|
}
|
|
2138
2148
|
|
|
2139
2149
|
export async function setLocalIdentity(directory, profile) {
|
|
2140
|
-
const git = await createGit(directory, { allowUnsafeSshCommand: true });
|
|
2150
|
+
const git = await createGit(directory, { allowUnsafeSshCommand: true, allowUnsafeCredentialHelper: true });
|
|
2141
2151
|
|
|
2142
2152
|
try {
|
|
2143
2153
|
|
|
@@ -2455,11 +2465,26 @@ export async function getStatus(directory, options = {}) {
|
|
|
2455
2465
|
}
|
|
2456
2466
|
}
|
|
2457
2467
|
|
|
2468
|
+
const getNoIndexDiff = async (repoRoot, repoPath, contextLines) => {
|
|
2469
|
+
const args = ['diff', '--no-color', '--full-index'];
|
|
2470
|
+
if (Number.isFinite(contextLines)) {
|
|
2471
|
+
args.push(`-U${Math.max(0, contextLines)}`);
|
|
2472
|
+
}
|
|
2473
|
+
args.push('--no-index', '--', '/dev/null', repoPath);
|
|
2474
|
+
const result = await runGitCommand(repoRoot, args);
|
|
2475
|
+
// Exit 1 means differences, even when Git also writes warnings to stderr.
|
|
2476
|
+
// Spawn and buffer errors have no numeric exit code and must still fail.
|
|
2477
|
+
if (result.exitCode === 0 || result.exitCode === 1) {
|
|
2478
|
+
return result.stdout;
|
|
2479
|
+
}
|
|
2480
|
+
throw new Error(result.stderr || result.message || 'Failed to get untracked Git diff');
|
|
2481
|
+
};
|
|
2482
|
+
|
|
2458
2483
|
export async function getDiff(directory, { path: filePath, staged = false, contextLines = 3 } = {}) {
|
|
2459
2484
|
const { directoryPath, directoryGit, repoRoot, git } = await createRepositoryGitContext(directory);
|
|
2460
2485
|
|
|
2461
2486
|
try {
|
|
2462
|
-
const args = ['diff', '--no-color'];
|
|
2487
|
+
const args = ['diff', '--no-color', '--full-index'];
|
|
2463
2488
|
const fileContext = filePath ? await resolveGitFileContext(directoryPath, directoryGit, filePath, repoRoot) : null;
|
|
2464
2489
|
|
|
2465
2490
|
if (typeof contextLines === 'number' && !Number.isNaN(contextLines)) {
|
|
@@ -2505,21 +2530,7 @@ export async function getDiff(directory, { path: filePath, staged = false, conte
|
|
|
2505
2530
|
].join('\n');
|
|
2506
2531
|
}
|
|
2507
2532
|
|
|
2508
|
-
|
|
2509
|
-
if (typeof contextLines === 'number' && !Number.isNaN(contextLines)) {
|
|
2510
|
-
noIndexArgs.push(`-U${Math.max(0, contextLines)}`);
|
|
2511
|
-
}
|
|
2512
|
-
noIndexArgs.push('--no-index', '--', '/dev/null', fileContext.repoPath);
|
|
2513
|
-
try {
|
|
2514
|
-
const noIndexDiff = await git.raw(noIndexArgs);
|
|
2515
|
-
return noIndexDiff;
|
|
2516
|
-
} catch (noIndexError) {
|
|
2517
|
-
// git diff --no-index returns exit code 1 when differences exist (not a real error)
|
|
2518
|
-
if (noIndexError.exitCode === 1 && noIndexError.message) {
|
|
2519
|
-
return noIndexError.message;
|
|
2520
|
-
}
|
|
2521
|
-
throw noIndexError;
|
|
2522
|
-
}
|
|
2533
|
+
return await getNoIndexDiff(repoRoot, fileContext.repoPath, contextLines);
|
|
2523
2534
|
}
|
|
2524
2535
|
} catch (error) {
|
|
2525
2536
|
console.error('Failed to get Git diff:', error);
|
|
@@ -2568,7 +2579,7 @@ export async function getUntrackedDiffs(directory, filePaths = [], { concurrency
|
|
|
2568
2579
|
const paths = (Array.isArray(filePaths) ? filePaths : []).filter((value) => typeof value === 'string' && value);
|
|
2569
2580
|
if (paths.length === 0) return [];
|
|
2570
2581
|
|
|
2571
|
-
const { directoryPath, directoryGit, repoRoot
|
|
2582
|
+
const { directoryPath, directoryGit, repoRoot } = await createRepositoryGitContext(directory);
|
|
2572
2583
|
const results = new Array(paths.length).fill('');
|
|
2573
2584
|
let cursor = 0;
|
|
2574
2585
|
|
|
@@ -2577,18 +2588,7 @@ export async function getUntrackedDiffs(directory, filePaths = [], { concurrency
|
|
|
2577
2588
|
const index = cursor++;
|
|
2578
2589
|
try {
|
|
2579
2590
|
const fileContext = await resolveGitFileContext(directoryPath, directoryGit, paths[index], repoRoot);
|
|
2580
|
-
|
|
2581
|
-
if (typeof contextLines === 'number' && !Number.isNaN(contextLines)) {
|
|
2582
|
-
args.push(`-U${Math.max(0, contextLines)}`);
|
|
2583
|
-
}
|
|
2584
|
-
args.push('--no-index', '--', '/dev/null', fileContext.repoPath);
|
|
2585
|
-
try {
|
|
2586
|
-
results[index] = await git.raw(args);
|
|
2587
|
-
} catch (error) {
|
|
2588
|
-
// `git diff --no-index` exits 1 whenever there are differences, which
|
|
2589
|
-
// for a new file is always.
|
|
2590
|
-
results[index] = error?.exitCode === 1 && error?.message ? error.message : '';
|
|
2591
|
-
}
|
|
2591
|
+
results[index] = await getNoIndexDiff(repoRoot, fileContext.repoPath, contextLines);
|
|
2592
2592
|
} catch {
|
|
2593
2593
|
results[index] = '';
|
|
2594
2594
|
}
|
|
@@ -2618,7 +2618,51 @@ async function assertRangeRefsResolve(git, refs) {
|
|
|
2618
2618
|
}
|
|
2619
2619
|
}
|
|
2620
2620
|
|
|
2621
|
-
|
|
2621
|
+
// A private index lets git include untracked paths in the same tree comparison
|
|
2622
|
+
// as tracked files, including a staged deletion recreated at the same path.
|
|
2623
|
+
// Intent-to-add records only their existence; diff reads current file contents.
|
|
2624
|
+
async function runWorkingTreeRangeDiff(context, baseRef, headRef, args, paths = []) {
|
|
2625
|
+
const { git, repoRoot } = context;
|
|
2626
|
+
const readHead = async () => {
|
|
2627
|
+
const commit = (await git.raw(['rev-parse', '--verify', 'HEAD'])).trim();
|
|
2628
|
+
const ref = (await git.raw(['symbolic-ref', '--quiet', 'HEAD'])).trim();
|
|
2629
|
+
return `${commit}\n${ref}`;
|
|
2630
|
+
};
|
|
2631
|
+
const startingHead = await readHead();
|
|
2632
|
+
const [headCommit, currentRef] = startingHead.split('\n');
|
|
2633
|
+
const requestedRef = (await git.raw(['rev-parse', '--verify', '--symbolic-full-name', '--end-of-options', headRef])).trim();
|
|
2634
|
+
if (requestedRef !== currentRef) {
|
|
2635
|
+
throw new Error('Working-tree comparisons require the checked-out branch. Refresh and try again.');
|
|
2636
|
+
}
|
|
2637
|
+
const mergeBase = (await git.raw(['merge-base', baseRef, headCommit])).trim();
|
|
2638
|
+
const readDiff = async (comparisonGit) => {
|
|
2639
|
+
const diff = await comparisonGit.raw([...args, mergeBase, '--', ...paths]);
|
|
2640
|
+
if (await readHead() !== startingHead) {
|
|
2641
|
+
throw new Error('The checked-out branch changed during comparison. Refresh and try again.');
|
|
2642
|
+
}
|
|
2643
|
+
return diff;
|
|
2644
|
+
};
|
|
2645
|
+
const untracked = await git.raw(['ls-files', '--others', '--exclude-standard', '-z', '--', ...paths]);
|
|
2646
|
+
if (!untracked) return readDiff(git);
|
|
2647
|
+
|
|
2648
|
+
const temporaryDirectory = await fsp.mkdtemp(path.join(os.tmpdir(), 'openchamber-branch-diff-'));
|
|
2649
|
+
try {
|
|
2650
|
+
const indexPath = (await git.raw(['rev-parse', '--git-path', 'index'])).trim();
|
|
2651
|
+
const temporaryIndex = path.join(temporaryDirectory, 'index');
|
|
2652
|
+
await fsp.copyFile(path.resolve(repoRoot, indexPath), temporaryIndex);
|
|
2653
|
+
const pathspecFile = path.join(temporaryDirectory, 'paths');
|
|
2654
|
+
await fsp.writeFile(pathspecFile, untracked);
|
|
2655
|
+
const comparisonGit = await createGit(repoRoot);
|
|
2656
|
+
comparisonGit.env('GIT_INDEX_FILE', temporaryIndex);
|
|
2657
|
+
comparisonGit.env('GIT_LITERAL_PATHSPECS', '1');
|
|
2658
|
+
await comparisonGit.raw(['add', '--intent-to-add', '--pathspec-from-file=' + pathspecFile, '--pathspec-file-nul']);
|
|
2659
|
+
return await readDiff(comparisonGit);
|
|
2660
|
+
} finally {
|
|
2661
|
+
await fsp.rm(temporaryDirectory, { recursive: true, force: true });
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
export async function getRangeDiff(directory, { base, head, path: filePath, contextLines = 3, includeWorkingTree = false } = {}) {
|
|
2622
2666
|
const { directoryPath, directoryGit, repoRoot, git } = await createRepositoryGitContext(directory);
|
|
2623
2667
|
const baseRef = typeof base === 'string' ? base.trim() : '';
|
|
2624
2668
|
const headRef = typeof head === 'string' ? head.trim() : '';
|
|
@@ -2626,51 +2670,39 @@ export async function getRangeDiff(directory, { base, head, path: filePath, cont
|
|
|
2626
2670
|
throw new Error('base and head are required');
|
|
2627
2671
|
}
|
|
2628
2672
|
|
|
2629
|
-
|
|
2630
|
-
// when local base branch is stale (common when user stays on feature branch).
|
|
2631
|
-
let resolvedBase = baseRef;
|
|
2632
|
-
const originCandidate = `refs/remotes/origin/${baseRef}`;
|
|
2633
|
-
try {
|
|
2634
|
-
const verified = await git.raw(['rev-parse', '--verify', originCandidate]);
|
|
2635
|
-
if (verified && verified.trim()) {
|
|
2636
|
-
resolvedBase = `origin/${baseRef}`;
|
|
2637
|
-
}
|
|
2638
|
-
} catch {
|
|
2639
|
-
// ignore
|
|
2640
|
-
}
|
|
2641
|
-
|
|
2642
|
-
// Not every repository has an `origin`. When the base names a branch that
|
|
2643
|
-
// exists only on another remote, a bare name does not resolve — git looks in
|
|
2644
|
-
// refs/heads, not across remotes — and the diff fails with "ambiguous
|
|
2645
|
-
// argument". Fall back to whichever remote actually carries it.
|
|
2646
|
-
if (resolvedBase === baseRef && !/[*?[\]^~:\\]/.test(baseRef)) {
|
|
2647
|
-
const resolvesLocally = await git
|
|
2648
|
-
.raw(['rev-parse', '--verify', `refs/heads/${baseRef}`])
|
|
2649
|
-
.then((value) => Boolean(String(value || '').trim()))
|
|
2650
|
-
.catch(() => false);
|
|
2651
|
-
|
|
2652
|
-
if (!resolvesLocally) {
|
|
2653
|
-
const remoteMatch = await git
|
|
2654
|
-
.raw(['for-each-ref', '--count=1', '--format=%(refname:short)', `refs/remotes/*/${baseRef}`])
|
|
2655
|
-
.then((value) => String(value || '').trim())
|
|
2656
|
-
.catch(() => '');
|
|
2657
|
-
if (remoteMatch) {
|
|
2658
|
-
resolvedBase = remoteMatch;
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
|
|
2663
|
-
await assertRangeRefsResolve(git, [resolvedBase, headRef]);
|
|
2673
|
+
await assertRangeRefsResolve(git, [baseRef, headRef]);
|
|
2664
2674
|
|
|
2665
2675
|
const args = ['diff', '--no-color'];
|
|
2666
2676
|
if (typeof contextLines === 'number' && !Number.isNaN(contextLines)) {
|
|
2667
2677
|
args.push(`-U${Math.max(0, contextLines)}`);
|
|
2668
2678
|
}
|
|
2669
|
-
|
|
2679
|
+
const paths = [];
|
|
2670
2680
|
if (filePath) {
|
|
2671
|
-
|
|
2672
|
-
|
|
2681
|
+
try {
|
|
2682
|
+
const fileContext = await resolveGitFileContext(directoryPath, directoryGit, filePath, repoRoot);
|
|
2683
|
+
paths.push(fileContext.repoPath);
|
|
2684
|
+
} catch (error) {
|
|
2685
|
+
if (error.message !== 'Invalid file path') throw error;
|
|
2686
|
+
// A committed deletion is absent from HEAD, the index, and the working
|
|
2687
|
+
// tree. It is still a valid range path when it exists at the merge base.
|
|
2688
|
+
const mergeBase = (await git.raw(['merge-base', baseRef, headRef])).trim();
|
|
2689
|
+
for (const root of new Set([repoRoot, directoryPath])) {
|
|
2690
|
+
const target = path.resolve(root, filePath);
|
|
2691
|
+
if (!isInsideOrSameDirectory(repoRoot, target)) continue;
|
|
2692
|
+
const repoPath = toGitPath(path.relative(repoRoot, target));
|
|
2693
|
+
const exists = await git.raw(['cat-file', '-e', `${mergeBase}:${repoPath}`]).then(() => true).catch(() => false);
|
|
2694
|
+
if (exists) {
|
|
2695
|
+
paths.push(repoPath);
|
|
2696
|
+
break;
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
if (paths.length === 0) throw error;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
if (includeWorkingTree) {
|
|
2703
|
+
return runWorkingTreeRangeDiff({ git, repoRoot }, baseRef, headRef, args, paths);
|
|
2673
2704
|
}
|
|
2705
|
+
args.push(`${baseRef}...${headRef}`, '--', ...paths);
|
|
2674
2706
|
const diff = await git.raw(args);
|
|
2675
2707
|
return diff;
|
|
2676
2708
|
}
|
|
@@ -2692,6 +2724,9 @@ export function parseBranchCreationSource(reflogText) {
|
|
|
2692
2724
|
.split('\n')
|
|
2693
2725
|
.map((line) => line.trim())
|
|
2694
2726
|
.filter(Boolean);
|
|
2727
|
+
// Rebase records its destination as a commit, not a parent branch. The
|
|
2728
|
+
// creation ref is no longer evidence of the current base after restacking.
|
|
2729
|
+
if (lines.some((line) => /^rebase(?:\s|\()/.test(line))) return null;
|
|
2695
2730
|
// Reflog lists newest entries first; the creation entry is the oldest one.
|
|
2696
2731
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
2697
2732
|
const match = lines[index].match(BRANCH_CREATION_SOURCE_RE);
|
|
@@ -2743,31 +2778,23 @@ export async function getBranchBase(directory, branch) {
|
|
|
2743
2778
|
return { base: source };
|
|
2744
2779
|
}
|
|
2745
2780
|
|
|
2746
|
-
export async function getRangeFiles(directory, { base, head } = {}) {
|
|
2747
|
-
const { git } = await createRepositoryGitContext(directory);
|
|
2781
|
+
export async function getRangeFiles(directory, { base, head, includeWorkingTree = false } = {}) {
|
|
2782
|
+
const { git, repoRoot } = await createRepositoryGitContext(directory);
|
|
2748
2783
|
const baseRef = typeof base === 'string' ? base.trim() : '';
|
|
2749
2784
|
const headRef = typeof head === 'string' ? head.trim() : '';
|
|
2750
2785
|
if (!baseRef || !headRef) {
|
|
2751
2786
|
throw new Error('base and head are required');
|
|
2752
2787
|
}
|
|
2753
2788
|
|
|
2754
|
-
|
|
2755
|
-
const originCandidate = `refs/remotes/origin/${baseRef}`;
|
|
2756
|
-
try {
|
|
2757
|
-
const verified = await git.raw(['rev-parse', '--verify', originCandidate]);
|
|
2758
|
-
if (verified && verified.trim()) {
|
|
2759
|
-
resolvedBase = `origin/${baseRef}`;
|
|
2760
|
-
}
|
|
2761
|
-
} catch {
|
|
2762
|
-
// ignore
|
|
2763
|
-
}
|
|
2764
|
-
|
|
2765
|
-
await assertRangeRefsResolve(git, [resolvedBase, headRef]);
|
|
2789
|
+
await assertRangeRefsResolve(git, [baseRef, headRef]);
|
|
2766
2790
|
|
|
2767
2791
|
// `-C` (copy detection among changed files only, so cheap) makes copies
|
|
2768
2792
|
// surface as C entries instead of plain additions; rename detection is on
|
|
2769
2793
|
// by default.
|
|
2770
|
-
const
|
|
2794
|
+
const args = ['diff', '--name-status', '-z', '-C'];
|
|
2795
|
+
const raw = includeWorkingTree
|
|
2796
|
+
? await runWorkingTreeRangeDiff({ git, repoRoot }, baseRef, headRef, args)
|
|
2797
|
+
: await git.raw([...args, `${baseRef}...${headRef}`, '--']);
|
|
2771
2798
|
// -z format: STATUS\0PATH\0[ORIG\0] repeated. For rename/copy entries
|
|
2772
2799
|
// (`R100`, `C75`) the first path token is the ORIGINAL path and the second
|
|
2773
2800
|
// is the DESTINATION — the diff (and the UI) must address the destination.
|
|
@@ -2777,7 +2804,7 @@ export async function getRangeFiles(directory, { base, head } = {}) {
|
|
|
2777
2804
|
const status = (tokens[index] || '').trim();
|
|
2778
2805
|
if (!status) continue;
|
|
2779
2806
|
const isRenameOrCopy = status.startsWith('R') || status.startsWith('C');
|
|
2780
|
-
const path = isRenameOrCopy ? (tokens[index + 2] || '')
|
|
2807
|
+
const path = isRenameOrCopy ? (tokens[index + 2] || '') : (tokens[index + 1] || '');
|
|
2781
2808
|
index += isRenameOrCopy ? 2 : 1;
|
|
2782
2809
|
if (path) {
|
|
2783
2810
|
files.push({ path, status: status.charAt(0) });
|
|
@@ -2823,27 +2850,6 @@ const parseIsBinaryFromNumstat = (raw) => {
|
|
|
2823
2850
|
return added === '-' || deleted === '-';
|
|
2824
2851
|
};
|
|
2825
2852
|
|
|
2826
|
-
const extractGitStatusPath = (status, pathPart) => {
|
|
2827
|
-
if ((status === 'R' || status === 'C') && pathPart.includes('\t')) {
|
|
2828
|
-
return pathPart.split('\t').pop() || pathPart;
|
|
2829
|
-
}
|
|
2830
|
-
return pathPart;
|
|
2831
|
-
};
|
|
2832
|
-
|
|
2833
|
-
const extractGitNumstatDestinationPath = (filePath) => {
|
|
2834
|
-
if (!filePath.includes(' => ')) {
|
|
2835
|
-
return filePath;
|
|
2836
|
-
}
|
|
2837
|
-
|
|
2838
|
-
const braceMatch = filePath.match(/^(.*)\{([^{}]*)\s=>\s([^{}]*)\}(.*)$/);
|
|
2839
|
-
if (braceMatch) {
|
|
2840
|
-
const [, prefix, , destination, suffix] = braceMatch;
|
|
2841
|
-
return `${prefix}${destination}${suffix}`.replace(/\/+/g, '/');
|
|
2842
|
-
}
|
|
2843
|
-
|
|
2844
|
-
return filePath.split(' => ').pop()?.trim() || filePath;
|
|
2845
|
-
};
|
|
2846
|
-
|
|
2847
2853
|
const looksBinaryBySniff = async (absolutePath) => {
|
|
2848
2854
|
try {
|
|
2849
2855
|
const handle = await fsp.open(absolutePath, 'r');
|
|
@@ -3093,11 +3099,13 @@ const normalizePatchTargetPath = (value) => {
|
|
|
3093
3099
|
};
|
|
3094
3100
|
|
|
3095
3101
|
const extractPatchTargetPath = (patch) => {
|
|
3096
|
-
const
|
|
3102
|
+
const firstHunk = patch.search(/^@@\s/m);
|
|
3103
|
+
const header = firstHunk < 0 ? patch : patch.slice(0, firstHunk);
|
|
3104
|
+
const matches = [...header.matchAll(/^(?:-{3}|\+{3})\s+.+$/gm)];
|
|
3097
3105
|
const realTargets = matches
|
|
3098
3106
|
.map((match) => normalizePatchTargetPath(parsePatchPathToken(match[0])))
|
|
3099
3107
|
.filter(Boolean);
|
|
3100
|
-
return realTargets
|
|
3108
|
+
return realTargets.at(-1) || null;
|
|
3101
3109
|
};
|
|
3102
3110
|
|
|
3103
3111
|
const writeTempPatchFile = async (patch) => {
|
|
@@ -3125,9 +3133,21 @@ export async function applyHunk(directory, filePath, options = {}) {
|
|
|
3125
3133
|
const fileContext = await resolveGitFileContext(directoryPath, directoryGit, filePath, repoRoot);
|
|
3126
3134
|
validateRepositoryFilePaths(repoRoot, [fileContext.repoPath]);
|
|
3127
3135
|
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3136
|
+
// Applicability alone is insufficient: a previously staged or committed
|
|
3137
|
+
// hunk may still reverse cleanly against the working tree. Accept only a
|
|
3138
|
+
// canonical hunk from this file's current working/index diff.
|
|
3139
|
+
const current = await getDiff(directory, { path: filePath, staged: action === 'unstage', contextLines: 3 });
|
|
3140
|
+
const starts = [...current.matchAll(/^@@\s/gm)].map((match) => match.index);
|
|
3141
|
+
const header = current.slice(0, starts[0] ?? 0);
|
|
3142
|
+
const isCurrentHunk = starts.some((start, index) => (
|
|
3143
|
+
header + current.slice(start, starts[index + 1] ?? current.length) === patch
|
|
3144
|
+
));
|
|
3145
|
+
if (!isCurrentHunk) {
|
|
3146
|
+
const targetPath = extractPatchTargetPath(patch);
|
|
3147
|
+
if (targetPath && targetPath !== fileContext.repoPath && targetPath !== filePath) {
|
|
3148
|
+
throw new Error('patch target path does not match the requested file');
|
|
3149
|
+
}
|
|
3150
|
+
throw new Error('Hunk no longer applies — refresh and try again.');
|
|
3131
3151
|
}
|
|
3132
3152
|
|
|
3133
3153
|
const flags = HUNK_ACTION_FLAGS[action];
|
|
@@ -3248,7 +3268,7 @@ export async function stashPush(directory, options = {}) {
|
|
|
3248
3268
|
const { git } = await createRepositoryGitContext(directory);
|
|
3249
3269
|
const message = typeof options.message === 'string' && options.message.trim()
|
|
3250
3270
|
? options.message.trim()
|
|
3251
|
-
: `
|
|
3271
|
+
: `AiWorker stash ${new Date().toISOString()}`;
|
|
3252
3272
|
const output = await git.raw(['stash', 'push', '--include-untracked', '-m', message]);
|
|
3253
3273
|
return {
|
|
3254
3274
|
success: true,
|
|
@@ -3687,6 +3707,35 @@ export async function getBranches(directory) {
|
|
|
3687
3707
|
}
|
|
3688
3708
|
}
|
|
3689
3709
|
|
|
3710
|
+
/**
|
|
3711
|
+
* Counts locally unpushed commits for a small caller-supplied set of local
|
|
3712
|
+
* branches. This deliberately reads only local refs: the branch picker calls
|
|
3713
|
+
* it when opened, never polls, and never fetches a remote behind the user's
|
|
3714
|
+
* back. Unknown, remote, and upstream-less branches are omitted.
|
|
3715
|
+
*/
|
|
3716
|
+
export async function getUnpushedBranchCounts(directory, branchNames) {
|
|
3717
|
+
const { git } = await createRepositoryGitContext(directory);
|
|
3718
|
+
const requested = [...new Set(Array.isArray(branchNames) ? branchNames : [])]
|
|
3719
|
+
.filter((name) => typeof name === 'string' && name.length > 0)
|
|
3720
|
+
.slice(0, 5);
|
|
3721
|
+
if (requested.length === 0) return { counts: {} };
|
|
3722
|
+
|
|
3723
|
+
const local = new Set((await git.branchLocal()).all);
|
|
3724
|
+
const counts = {};
|
|
3725
|
+
await Promise.all(requested.map(async (branch) => {
|
|
3726
|
+
if (!local.has(branch)) return;
|
|
3727
|
+
const upstream = await git.raw(['rev-parse', '--abbrev-ref', '--symbolic-full-name', `${branch}@{upstream}`])
|
|
3728
|
+
.then((value) => value.trim())
|
|
3729
|
+
.catch(() => '');
|
|
3730
|
+
if (!upstream) return;
|
|
3731
|
+
const count = await git.raw(['rev-list', '--count', `${upstream}..${branch}`])
|
|
3732
|
+
.then((value) => Number.parseInt(value.trim(), 10))
|
|
3733
|
+
.catch(() => 0);
|
|
3734
|
+
if (Number.isFinite(count) && count > 0) counts[branch] = count;
|
|
3735
|
+
}));
|
|
3736
|
+
return { counts };
|
|
3737
|
+
}
|
|
3738
|
+
|
|
3690
3739
|
async function getRemoteDefaultBranches(git) {
|
|
3691
3740
|
let defaults = {};
|
|
3692
3741
|
|
|
@@ -4007,6 +4056,7 @@ export async function getWorktrees(directory) {
|
|
|
4007
4056
|
name: path.basename(entry.worktree || ''),
|
|
4008
4057
|
branch: entry.branch || '',
|
|
4009
4058
|
path: entry.worktree,
|
|
4059
|
+
prunable: entry.prunable === true,
|
|
4010
4060
|
}));
|
|
4011
4061
|
} catch (error) {
|
|
4012
4062
|
// Worktrees are an optional feature. When the caller passes a directory
|
|
@@ -4266,6 +4316,7 @@ async function attachGitWorktreeToCandidate(context, candidate, input = {}) {
|
|
|
4266
4316
|
|
|
4267
4317
|
const parsedRemoteStartRef = await resolveRemoteBranchRef(context.primaryWorktree, startRef);
|
|
4268
4318
|
if (parsedRemoteStartRef) {
|
|
4319
|
+
worktreeAddArgs.splice(2, 0, '--no-track');
|
|
4269
4320
|
inferredUpstream = {
|
|
4270
4321
|
remote: parsedRemoteStartRef.remote,
|
|
4271
4322
|
branch: parsedRemoteStartRef.branch,
|
|
@@ -4273,24 +4324,17 @@ async function attachGitWorktreeToCandidate(context, candidate, input = {}) {
|
|
|
4273
4324
|
}
|
|
4274
4325
|
}
|
|
4275
4326
|
|
|
4276
|
-
if (ensureRemoteName && ensureRemoteUrl) {
|
|
4327
|
+
if (mode === 'existing' && ensureRemoteName && ensureRemoteUrl) {
|
|
4277
4328
|
await ensureRemoteWithUrl(context.primaryWorktree, ensureRemoteName, ensureRemoteUrl);
|
|
4278
4329
|
}
|
|
4279
4330
|
|
|
4280
|
-
if (mode === 'new') {
|
|
4281
|
-
const parsedRemoteStartRef = await resolveRemoteBranchRef(context.primaryWorktree, startRef);
|
|
4282
|
-
if (parsedRemoteStartRef) {
|
|
4283
|
-
await fetchRemoteBranchRef(context.primaryWorktree, parsedRemoteStartRef.remote, parsedRemoteStartRef.branch);
|
|
4284
|
-
}
|
|
4285
|
-
}
|
|
4286
|
-
|
|
4287
4331
|
await runGitCommandOrThrow(context.primaryWorktree, worktreeAddArgs, 'Failed to create git worktree');
|
|
4288
4332
|
|
|
4289
4333
|
const upstreamRemote = shouldSetUpstream
|
|
4290
|
-
? String(
|
|
4334
|
+
? String(input?.upstreamRemote || inferredUpstream?.remote || '').trim()
|
|
4291
4335
|
: '';
|
|
4292
4336
|
const upstreamBranch = shouldSetUpstream
|
|
4293
|
-
? String(
|
|
4337
|
+
? String(input?.upstreamBranch || inferredUpstream?.branch || '').trim()
|
|
4294
4338
|
: '';
|
|
4295
4339
|
|
|
4296
4340
|
const bootstrapStatus = setWorktreeBootstrapState(
|
|
@@ -4325,6 +4369,50 @@ async function attachGitWorktreeToCandidate(context, candidate, input = {}) {
|
|
|
4325
4369
|
};
|
|
4326
4370
|
}
|
|
4327
4371
|
|
|
4372
|
+
const prepareWorktreeCreateSource = async (context, input = {}) => {
|
|
4373
|
+
if (input?.mode === 'existing') {
|
|
4374
|
+
return { input, sourceFetchFailed: false };
|
|
4375
|
+
}
|
|
4376
|
+
|
|
4377
|
+
const startRef = normalizeStartRef(input?.startRef);
|
|
4378
|
+
const remoteStartRef = await resolveRemoteBranchRef(context.primaryWorktree, startRef);
|
|
4379
|
+
if (!remoteStartRef) {
|
|
4380
|
+
return { input, sourceFetchFailed: false };
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
const status = await getStatus(context.primaryWorktree, { mode: 'light' }).catch(() => null);
|
|
4384
|
+
const trackingRef = status?.tracking
|
|
4385
|
+
? await resolveRemoteBranchRef(context.primaryWorktree, status.tracking)
|
|
4386
|
+
: null;
|
|
4387
|
+
const canFallbackToLocal = Boolean(
|
|
4388
|
+
status?.current
|
|
4389
|
+
&& status.ahead === 0
|
|
4390
|
+
&& trackingRef?.fullRef === remoteStartRef.fullRef
|
|
4391
|
+
);
|
|
4392
|
+
|
|
4393
|
+
try {
|
|
4394
|
+
await fetchRemoteBranchRef(context.primaryWorktree, remoteStartRef.remote, remoteStartRef.branch);
|
|
4395
|
+
return { input, sourceFetchFailed: false };
|
|
4396
|
+
} catch (error) {
|
|
4397
|
+
if (canFallbackToLocal) {
|
|
4398
|
+
return {
|
|
4399
|
+
input: { ...input, startRef: status.current },
|
|
4400
|
+
sourceFetchFailed: true,
|
|
4401
|
+
};
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
const refExists = await runGitCommand(
|
|
4405
|
+
context.primaryWorktree,
|
|
4406
|
+
['show-ref', '--verify', '--quiet', remoteStartRef.fullRef]
|
|
4407
|
+
);
|
|
4408
|
+
if (!refExists.success) {
|
|
4409
|
+
throw error;
|
|
4410
|
+
}
|
|
4411
|
+
console.warn(`Worktree create: failed to refresh ${remoteStartRef.remote}/${remoteStartRef.branch}, proceeding with the existing remote-tracking ref`);
|
|
4412
|
+
return { input, sourceFetchFailed: false };
|
|
4413
|
+
}
|
|
4414
|
+
};
|
|
4415
|
+
|
|
4328
4416
|
export async function createWorktree(directory, input = {}) {
|
|
4329
4417
|
const mode = input?.mode === 'existing' ? 'existing' : 'new';
|
|
4330
4418
|
const context = await resolveWorktreeProjectContext(directory);
|
|
@@ -4333,10 +4421,18 @@ export async function createWorktree(directory, input = {}) {
|
|
|
4333
4421
|
await assertWorktreeCreatePreflight(directory, input);
|
|
4334
4422
|
}
|
|
4335
4423
|
|
|
4424
|
+
const ensureRemoteName = String(input?.ensureRemoteName || '').trim();
|
|
4425
|
+
const ensureRemoteUrl = String(input?.ensureRemoteUrl || '').trim();
|
|
4426
|
+
if (ensureRemoteName && ensureRemoteUrl) {
|
|
4427
|
+
await ensureRemoteWithUrl(context.primaryWorktree, ensureRemoteName, ensureRemoteUrl);
|
|
4428
|
+
}
|
|
4429
|
+
const prepared = await prepareWorktreeCreateSource(context, input);
|
|
4430
|
+
const preparedInput = prepared.input;
|
|
4431
|
+
|
|
4336
4432
|
await fsp.mkdir(context.worktreeRoot, { recursive: true });
|
|
4337
4433
|
|
|
4338
|
-
const preferredName = String(
|
|
4339
|
-
const preferredBranchName = cleanBranchName(String(
|
|
4434
|
+
const preferredName = String(preparedInput?.worktreeName || preparedInput?.name || '').trim();
|
|
4435
|
+
const preferredBranchName = cleanBranchName(String(preparedInput?.branchName || '').trim());
|
|
4340
4436
|
|
|
4341
4437
|
const candidate = await resolveCandidateDirectory(
|
|
4342
4438
|
context.worktreeRoot,
|
|
@@ -4345,7 +4441,7 @@ export async function createWorktree(directory, input = {}) {
|
|
|
4345
4441
|
context.primaryWorktree
|
|
4346
4442
|
);
|
|
4347
4443
|
|
|
4348
|
-
if (
|
|
4444
|
+
if (preparedInput?.returnAfterDirectoryCreated === true) {
|
|
4349
4445
|
await fsp.mkdir(candidate.directory, { recursive: false });
|
|
4350
4446
|
|
|
4351
4447
|
const bootstrapStatus = setWorktreeBootstrapState(
|
|
@@ -4354,10 +4450,10 @@ export async function createWorktree(directory, input = {}) {
|
|
|
4354
4450
|
WORKTREE_BOOTSTRAP_PHASE_DIRECTORY_CREATED
|
|
4355
4451
|
);
|
|
4356
4452
|
const localBranch = mode === 'existing'
|
|
4357
|
-
? cleanBranchName(String(
|
|
4453
|
+
? cleanBranchName(String(preparedInput?.branchName || preparedInput?.existingBranch || candidate.branch || '').trim())
|
|
4358
4454
|
: candidate.branch;
|
|
4359
4455
|
|
|
4360
|
-
const task = attachGitWorktreeToCandidate(context, candidate,
|
|
4456
|
+
const task = attachGitWorktreeToCandidate(context, candidate, preparedInput).catch(async (error) => {
|
|
4361
4457
|
setWorktreeBootstrapState(
|
|
4362
4458
|
candidate.directory,
|
|
4363
4459
|
WORKTREE_BOOTSTRAP_FAILED,
|
|
@@ -4369,7 +4465,7 @@ export async function createWorktree(directory, input = {}) {
|
|
|
4369
4465
|
});
|
|
4370
4466
|
trackWorktreeBootstrapTask(candidate.directory, task);
|
|
4371
4467
|
|
|
4372
|
-
|
|
4468
|
+
const result = {
|
|
4373
4469
|
head: '',
|
|
4374
4470
|
name: candidate.name,
|
|
4375
4471
|
branch: localBranch,
|
|
@@ -4377,9 +4473,14 @@ export async function createWorktree(directory, input = {}) {
|
|
|
4377
4473
|
directoryCreated: true,
|
|
4378
4474
|
bootstrapStatus,
|
|
4379
4475
|
};
|
|
4476
|
+
if (prepared.sourceFetchFailed) {
|
|
4477
|
+
result.sourceFetchFailed = true;
|
|
4478
|
+
}
|
|
4479
|
+
return result;
|
|
4380
4480
|
}
|
|
4381
4481
|
|
|
4382
|
-
|
|
4482
|
+
const result = await attachGitWorktreeToCandidate(context, candidate, preparedInput);
|
|
4483
|
+
return prepared.sourceFetchFailed ? { ...result, sourceFetchFailed: true } : result;
|
|
4383
4484
|
}
|
|
4384
4485
|
|
|
4385
4486
|
export async function getWorktreeBootstrapStatus(directory) {
|
|
@@ -4588,12 +4689,11 @@ export async function getLog(directory, options = {}) {
|
|
|
4588
4689
|
};
|
|
4589
4690
|
const resolvedFrom = await resolveBaseRefForLog(options.from, checkRef);
|
|
4590
4691
|
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
file: filePath
|
|
4596
|
-
});
|
|
4692
|
+
// simple-git's `to` alone means HEAD..to, which is empty for the current
|
|
4693
|
+
// branch. A single requested ref means its reachable history instead.
|
|
4694
|
+
const baseLog = options.to && !resolvedFrom
|
|
4695
|
+
? await git.log([`--max-count=${maxCount}`, options.to, ...(filePath ? ['--', filePath] : [])])
|
|
4696
|
+
: await git.log({ maxCount, from: resolvedFrom, to: options.to, file: filePath });
|
|
4597
4697
|
|
|
4598
4698
|
const logArgs = [
|
|
4599
4699
|
'log',
|
|
@@ -4837,85 +4937,61 @@ export async function canonicalizeWorktreeState(directory) {
|
|
|
4837
4937
|
};
|
|
4838
4938
|
}
|
|
4839
4939
|
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
const numstatRaw = await git.raw([
|
|
4846
|
-
'show',
|
|
4847
|
-
'--numstat',
|
|
4848
|
-
'--format=',
|
|
4849
|
-
commitHash
|
|
4850
|
-
]);
|
|
4851
|
-
|
|
4852
|
-
const files = [];
|
|
4853
|
-
const lines = numstatRaw.trim().split('\n').filter(Boolean);
|
|
4854
|
-
|
|
4855
|
-
for (const line of lines) {
|
|
4856
|
-
const parts = line.split('\t');
|
|
4857
|
-
if (parts.length < 3) continue;
|
|
4858
|
-
|
|
4859
|
-
const [insertionsRaw, deletionsRaw, ...pathParts] = parts;
|
|
4860
|
-
const filePath = pathParts.join('\t');
|
|
4861
|
-
if (!filePath) continue;
|
|
4862
|
-
|
|
4863
|
-
const insertions = insertionsRaw === '-' ? 0 : parseInt(insertionsRaw, 10) || 0;
|
|
4864
|
-
const deletions = deletionsRaw === '-' ? 0 : parseInt(deletionsRaw, 10) || 0;
|
|
4865
|
-
const isBinary = insertionsRaw === '-' && deletionsRaw === '-';
|
|
4866
|
-
|
|
4867
|
-
let changeType = 'M';
|
|
4868
|
-
let displayPath = filePath;
|
|
4869
|
-
|
|
4870
|
-
if (filePath.includes(' => ')) {
|
|
4871
|
-
changeType = 'R';
|
|
4872
|
-
|
|
4873
|
-
const match = filePath.match(/(?:\{[^}]*\s=>\s[^}]*\}|.*\s=>\s.*)/);
|
|
4874
|
-
if (match) {
|
|
4875
|
-
displayPath = filePath;
|
|
4876
|
-
}
|
|
4877
|
-
}
|
|
4878
|
-
|
|
4879
|
-
files.push({
|
|
4880
|
-
path: displayPath,
|
|
4881
|
-
insertions,
|
|
4882
|
-
deletions,
|
|
4883
|
-
isBinary,
|
|
4884
|
-
changeType
|
|
4885
|
-
});
|
|
4886
|
-
}
|
|
4887
|
-
|
|
4888
|
-
const nameStatusRaw = await git.raw([
|
|
4889
|
-
'show',
|
|
4890
|
-
'--name-status',
|
|
4891
|
-
'--format=',
|
|
4892
|
-
commitHash
|
|
4893
|
-
]).catch(() => '');
|
|
4894
|
-
|
|
4895
|
-
const statusMap = new Map();
|
|
4896
|
-
const statusLines = nameStatusRaw.trim().split('\n').filter(Boolean);
|
|
4897
|
-
for (const line of statusLines) {
|
|
4898
|
-
const match = line.match(/^([AMDRC])\d*\t(.+)$/);
|
|
4899
|
-
if (match) {
|
|
4900
|
-
const [, status, pathPart] = match;
|
|
4901
|
-
statusMap.set(extractGitStatusPath(status, pathPart), status);
|
|
4902
|
-
}
|
|
4903
|
-
}
|
|
4940
|
+
async function resolveCommitHash(git, hash) {
|
|
4941
|
+
if (!/^[0-9a-f]{7,64}$/i.test(hash)) throw new Error('A commit hash is required');
|
|
4942
|
+
return (await git.raw(['rev-parse', '--verify', '--end-of-options', `${hash}^{commit}`])).trim();
|
|
4943
|
+
}
|
|
4904
4944
|
|
|
4905
|
-
|
|
4906
|
-
const basePath = extractGitNumstatDestinationPath(file.path);
|
|
4945
|
+
const commitShowArgs = (hash) => ['show', '--format=', '--root', '--diff-merges=first-parent', '--find-renames', hash];
|
|
4907
4946
|
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4947
|
+
export async function getCommitDiff(directory, { hash, path: filePath, previousPath, contextLines = 3 } = {}) {
|
|
4948
|
+
const { git } = await createRepositoryGitContext(directory);
|
|
4949
|
+
const commit = await resolveCommitHash(git, hash);
|
|
4950
|
+
const paths = [filePath, previousPath].filter(Boolean).map((value) => `:(literal)${value}`);
|
|
4951
|
+
return git.raw([
|
|
4952
|
+
...commitShowArgs(commit), '--no-color', '--no-ext-diff', `-U${Math.max(0, contextLines)}`,
|
|
4953
|
+
'--', ...paths,
|
|
4954
|
+
]);
|
|
4955
|
+
}
|
|
4913
4956
|
|
|
4914
|
-
|
|
4915
|
-
}
|
|
4916
|
-
|
|
4917
|
-
|
|
4957
|
+
export async function getCommitFiles(directory, commitHash) {
|
|
4958
|
+
const { git } = await createRepositoryGitContext(directory);
|
|
4959
|
+
const hash = await resolveCommitHash(git, commitHash);
|
|
4960
|
+
const [numstat, nameStatus] = await Promise.all([
|
|
4961
|
+
git.raw([...commitShowArgs(hash), '--numstat', '-z', '--']),
|
|
4962
|
+
git.raw([...commitShowArgs(hash), '--name-status', '-z', '--']),
|
|
4963
|
+
]);
|
|
4964
|
+
const stats = new Map();
|
|
4965
|
+
const tokens = numstat.split('\0');
|
|
4966
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
4967
|
+
const match = /^(\d+|-)\t(\d+|-)\t([\s\S]*)$/.exec(tokens[index]);
|
|
4968
|
+
if (!match) continue;
|
|
4969
|
+
let destination = match[3];
|
|
4970
|
+
if (!destination) {
|
|
4971
|
+
destination = tokens[index + 2];
|
|
4972
|
+
index += 2;
|
|
4973
|
+
}
|
|
4974
|
+
stats.set(destination, {
|
|
4975
|
+
insertions: Number.parseInt(match[1], 10) || 0,
|
|
4976
|
+
deletions: Number.parseInt(match[2], 10) || 0,
|
|
4977
|
+
isBinary: match[1] === '-',
|
|
4978
|
+
});
|
|
4918
4979
|
}
|
|
4980
|
+
const files = [];
|
|
4981
|
+
const names = nameStatus.split('\0');
|
|
4982
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
4983
|
+
const changeType = names[index].charAt(0);
|
|
4984
|
+
if (!changeType) continue;
|
|
4985
|
+
const renamed = changeType === 'R' || changeType === 'C';
|
|
4986
|
+
const previousPath = renamed ? names[++index] : undefined;
|
|
4987
|
+
const filePath = names[++index];
|
|
4988
|
+
const fileStats = stats.get(filePath);
|
|
4989
|
+
if (!filePath || !fileStats) throw new Error('Incomplete commit file statistics');
|
|
4990
|
+
const entry = { path: filePath, ...fileStats, changeType };
|
|
4991
|
+
if (previousPath) entry.previousPath = previousPath;
|
|
4992
|
+
files.push(entry);
|
|
4993
|
+
}
|
|
4994
|
+
return { files };
|
|
4919
4995
|
}
|
|
4920
4996
|
|
|
4921
4997
|
export async function renameBranch(directory, oldName, newName) {
|