@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
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { copyFile, mkdir, mkdtemp, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
3
7
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
8
|
|
|
5
9
|
import { mintOutsideFileGrant, registerFsRoutes } from './routes.js';
|
|
@@ -116,7 +120,7 @@ const registerExec = ({ spawn }) => {
|
|
|
116
120
|
resolveProjectDirectory: async () => ({ directory: '/repo' }),
|
|
117
121
|
buildAugmentedPath: () => '/usr/bin',
|
|
118
122
|
resolveGitBinaryForSpawn: () => 'git',
|
|
119
|
-
|
|
123
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
120
124
|
});
|
|
121
125
|
return getRoute('POST', '/api/fs/exec');
|
|
122
126
|
};
|
|
@@ -136,7 +140,7 @@ const registerWrite = (fsPromises) => {
|
|
|
136
140
|
resolveProjectDirectory: async () => ({ directory: '/repo' }),
|
|
137
141
|
buildAugmentedPath: () => '/usr/bin',
|
|
138
142
|
resolveGitBinaryForSpawn: () => 'git',
|
|
139
|
-
|
|
143
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
140
144
|
});
|
|
141
145
|
return getRoute('POST', '/api/fs/write');
|
|
142
146
|
};
|
|
@@ -160,7 +164,7 @@ const registerUpload = (fsPromises) => {
|
|
|
160
164
|
resolveProjectDirectory: async () => ({ directory: '/repo' }),
|
|
161
165
|
buildAugmentedPath: () => '/usr/bin',
|
|
162
166
|
resolveGitBinaryForSpawn: () => 'git',
|
|
163
|
-
|
|
167
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
164
168
|
});
|
|
165
169
|
return getRoute('POST', '/api/fs/upload');
|
|
166
170
|
};
|
|
@@ -180,7 +184,7 @@ const registerRead = (fsPromises, resolveProjectDirectory = async () => ({ direc
|
|
|
180
184
|
resolveProjectDirectory,
|
|
181
185
|
buildAugmentedPath: () => '/usr/bin',
|
|
182
186
|
resolveGitBinaryForSpawn: () => 'git',
|
|
183
|
-
|
|
187
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
184
188
|
});
|
|
185
189
|
return getRoute('GET', '/api/fs/read');
|
|
186
190
|
};
|
|
@@ -200,7 +204,7 @@ const registerRaw = (fsPromises) => {
|
|
|
200
204
|
resolveProjectDirectory: async () => ({ directory: '/repo' }),
|
|
201
205
|
buildAugmentedPath: () => '/usr/bin',
|
|
202
206
|
resolveGitBinaryForSpawn: () => 'git',
|
|
203
|
-
|
|
207
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
204
208
|
});
|
|
205
209
|
return getRoute('GET', '/api/fs/raw');
|
|
206
210
|
};
|
|
@@ -220,7 +224,7 @@ const registerMkdir = (fsPromises) => {
|
|
|
220
224
|
resolveProjectDirectory: async () => ({ directory: '/repo' }),
|
|
221
225
|
buildAugmentedPath: () => '/usr/bin',
|
|
222
226
|
resolveGitBinaryForSpawn: () => 'git',
|
|
223
|
-
|
|
227
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
224
228
|
});
|
|
225
229
|
return getRoute('POST', '/api/fs/mkdir');
|
|
226
230
|
};
|
|
@@ -241,7 +245,7 @@ const registerReveal = ({ fsPromises, spawn, platform = 'linux' }) => {
|
|
|
241
245
|
resolveProjectDirectory: async () => ({ directory: '/repo' }),
|
|
242
246
|
buildAugmentedPath: () => '/usr/bin',
|
|
243
247
|
resolveGitBinaryForSpawn: () => 'git',
|
|
244
|
-
|
|
248
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
245
249
|
});
|
|
246
250
|
return getRoute('POST', '/api/fs/reveal');
|
|
247
251
|
};
|
|
@@ -482,8 +486,8 @@ describe('fs upload', () => {
|
|
|
482
486
|
});
|
|
483
487
|
|
|
484
488
|
it('cleans up a partial temp file when the configured streaming limit is exceeded', async () => {
|
|
485
|
-
const previous = process.env.
|
|
486
|
-
process.env.
|
|
489
|
+
const previous = process.env.AIWORKER_FS_UPLOAD_MAX_BYTES;
|
|
490
|
+
process.env.AIWORKER_FS_UPLOAD_MAX_BYTES = '5';
|
|
487
491
|
const write = vi.fn(async (_buffer, _offset, length) => ({ bytesWritten: length }));
|
|
488
492
|
const fsPromises = {
|
|
489
493
|
open: vi.fn(async () => ({ write, close: vi.fn(async () => undefined) })),
|
|
@@ -504,14 +508,14 @@ describe('fs upload', () => {
|
|
|
504
508
|
expect(fsPromises.link).not.toHaveBeenCalled();
|
|
505
509
|
expect(fsPromises.unlink).toHaveBeenCalledWith(expect.stringMatching(/^\/repo\/file\.bin\.upload-/));
|
|
506
510
|
} finally {
|
|
507
|
-
if (previous === undefined) delete process.env.
|
|
508
|
-
else process.env.
|
|
511
|
+
if (previous === undefined) delete process.env.AIWORKER_FS_UPLOAD_MAX_BYTES;
|
|
512
|
+
else process.env.AIWORKER_FS_UPLOAD_MAX_BYTES = previous;
|
|
509
513
|
}
|
|
510
514
|
});
|
|
511
515
|
|
|
512
516
|
it('rejects a declared oversized upload before opening a temp file', async () => {
|
|
513
|
-
const previous = process.env.
|
|
514
|
-
process.env.
|
|
517
|
+
const previous = process.env.AIWORKER_FS_UPLOAD_MAX_BYTES;
|
|
518
|
+
process.env.AIWORKER_FS_UPLOAD_MAX_BYTES = '5';
|
|
515
519
|
const fsPromises = {
|
|
516
520
|
open: vi.fn(async () => ({ write: vi.fn(), close: vi.fn() })),
|
|
517
521
|
};
|
|
@@ -523,8 +527,8 @@ describe('fs upload', () => {
|
|
|
523
527
|
expect(res.body).toEqual({ error: 'File exceeds maximum size of 5 bytes' });
|
|
524
528
|
expect(fsPromises.open).not.toHaveBeenCalled();
|
|
525
529
|
} finally {
|
|
526
|
-
if (previous === undefined) delete process.env.
|
|
527
|
-
else process.env.
|
|
530
|
+
if (previous === undefined) delete process.env.AIWORKER_FS_UPLOAD_MAX_BYTES;
|
|
531
|
+
else process.env.AIWORKER_FS_UPLOAD_MAX_BYTES = previous;
|
|
528
532
|
}
|
|
529
533
|
});
|
|
530
534
|
|
|
@@ -843,11 +847,11 @@ describe('fs reveal', () => {
|
|
|
843
847
|
|
|
844
848
|
describe('fs exec git-read cache', () => {
|
|
845
849
|
beforeEach(() => {
|
|
846
|
-
delete process.env.
|
|
850
|
+
delete process.env.AIWORKER_GIT_READ_CACHE_TTL_MS;
|
|
847
851
|
});
|
|
848
852
|
|
|
849
853
|
afterEach(() => {
|
|
850
|
-
delete process.env.
|
|
854
|
+
delete process.env.AIWORKER_GIT_READ_CACHE_TTL_MS;
|
|
851
855
|
});
|
|
852
856
|
|
|
853
857
|
it('rejects background command execution', async () => {
|
|
@@ -956,7 +960,7 @@ describe('fs exec git-read cache', () => {
|
|
|
956
960
|
});
|
|
957
961
|
|
|
958
962
|
it('disables caching when TTL is 0', async () => {
|
|
959
|
-
process.env.
|
|
963
|
+
process.env.AIWORKER_GIT_READ_CACHE_TTL_MS = '0';
|
|
960
964
|
const command = 'git rev-parse --absolute-git-dir';
|
|
961
965
|
const { spawn, calls } = createSpawn({ stdoutByCommand: { [command]: '/repo/.git\n' } });
|
|
962
966
|
const handler = registerExec({ spawn });
|
|
@@ -1062,7 +1066,7 @@ describe('fs list symlink path space (issue 2627)', () => {
|
|
|
1062
1066
|
resolveProjectDirectory: async () => ({ directory: '/workspace' }),
|
|
1063
1067
|
buildAugmentedPath: () => '/usr/bin',
|
|
1064
1068
|
resolveGitBinaryForSpawn: () => 'git',
|
|
1065
|
-
|
|
1069
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
1066
1070
|
});
|
|
1067
1071
|
return getRoute('GET', '/api/fs/list');
|
|
1068
1072
|
};
|
|
@@ -1162,7 +1166,7 @@ describe('fs git-dirs', () => {
|
|
|
1162
1166
|
resolveProjectDirectory: async () => ({ directory: '/workspace' }),
|
|
1163
1167
|
buildAugmentedPath: () => '/usr/bin',
|
|
1164
1168
|
resolveGitBinaryForSpawn: () => 'git',
|
|
1165
|
-
|
|
1169
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
1166
1170
|
});
|
|
1167
1171
|
return { handler: getRoute('GET', '/api/fs/git-dirs'), readdir };
|
|
1168
1172
|
};
|
|
@@ -1385,7 +1389,11 @@ describe('fs stat directory scope (issue 3019)', () => {
|
|
|
1385
1389
|
path: path.posix,
|
|
1386
1390
|
fsPromises: {
|
|
1387
1391
|
realpath: async (targetPath) => targetPath,
|
|
1388
|
-
stat: async () => (
|
|
1392
|
+
stat: async (targetPath) => (
|
|
1393
|
+
targetPath === '/repo-b'
|
|
1394
|
+
? { isDirectory: () => true, mtimeMs: 123 }
|
|
1395
|
+
: { isFile: () => true, size: 12, mtimeMs: 456 }
|
|
1396
|
+
),
|
|
1389
1397
|
},
|
|
1390
1398
|
spawn: vi.fn(),
|
|
1391
1399
|
crypto: { randomUUID: () => 'job-0' },
|
|
@@ -1393,7 +1401,7 @@ describe('fs stat directory scope (issue 3019)', () => {
|
|
|
1393
1401
|
resolveProjectDirectory: projectDirectoryRuntime.resolveProjectDirectory,
|
|
1394
1402
|
buildAugmentedPath: () => '/usr/bin',
|
|
1395
1403
|
resolveGitBinaryForSpawn: () => 'git',
|
|
1396
|
-
|
|
1404
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
1397
1405
|
});
|
|
1398
1406
|
return getRoute('GET', '/api/fs/stat');
|
|
1399
1407
|
};
|
|
@@ -1428,4 +1436,182 @@ describe('fs stat directory scope (issue 3019)', () => {
|
|
|
1428
1436
|
expect(res.statusCode).toBe(200);
|
|
1429
1437
|
expect(res.body.isFile).toBe(true);
|
|
1430
1438
|
});
|
|
1439
|
+
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
describe('fs stat directory error handling', () => {
|
|
1443
|
+
it('loads in Node without workspace node_modules, as packaged desktop does', async () => {
|
|
1444
|
+
const directory = await mkdtemp(path.join(tmpdir(), 'openchamber-fs-import-'));
|
|
1445
|
+
try {
|
|
1446
|
+
await mkdir(path.join(directory, 'fs'));
|
|
1447
|
+
await copyFile(new URL('./routes.js', import.meta.url), path.join(directory, 'fs/routes.mjs'));
|
|
1448
|
+
await copyFile(new URL('../path-realpath-cache.js', import.meta.url), path.join(directory, 'path-realpath-cache.js'));
|
|
1449
|
+
expect(() => execFileSync('node', [
|
|
1450
|
+
'--input-type=module',
|
|
1451
|
+
'--eval',
|
|
1452
|
+
'await import(process.argv[1])',
|
|
1453
|
+
pathToFileURL(path.join(directory, 'fs/routes.mjs')).href,
|
|
1454
|
+
], { cwd: directory, stdio: 'pipe' })).not.toThrow();
|
|
1455
|
+
} finally {
|
|
1456
|
+
await rm(directory, { recursive: true, force: true });
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
it('returns directory-missing reasons and permission errors for directory stat', async () => {
|
|
1461
|
+
const { app, getRoute } = createRouteRegistry();
|
|
1462
|
+
const enoent = Object.assign(new Error('missing'), { code: 'ENOENT' });
|
|
1463
|
+
const enotdir = Object.assign(new Error('not a directory'), { code: 'ENOTDIR' });
|
|
1464
|
+
const eacces = Object.assign(new Error('denied'), { code: 'EACCES' });
|
|
1465
|
+
const stat = vi.fn(async (targetPath) => {
|
|
1466
|
+
if (targetPath === '/repo-b') throw enoent;
|
|
1467
|
+
if (targetPath === '/repo-b/file.txt/child') throw enotdir;
|
|
1468
|
+
if (targetPath === '/repo-b/protected') throw eacces;
|
|
1469
|
+
if (targetPath === '/repo-b/file.txt') return { isDirectory: () => false };
|
|
1470
|
+
if (targetPath === '/repo-b/failure') throw new Error('unavailable');
|
|
1471
|
+
return { isDirectory: () => true, mtimeMs: 1 };
|
|
1472
|
+
});
|
|
1473
|
+
const readdir = vi.fn(async () => []);
|
|
1474
|
+
const callStat = async (handler, { headers = {}, query }) => {
|
|
1475
|
+
const res = createMockResponse();
|
|
1476
|
+
const req = {
|
|
1477
|
+
url: `/api/fs/directory-stat?${new URLSearchParams(query)}`,
|
|
1478
|
+
query,
|
|
1479
|
+
get: (name) => headers[name.toLowerCase()] ?? undefined,
|
|
1480
|
+
};
|
|
1481
|
+
await handler(req, res);
|
|
1482
|
+
return res;
|
|
1483
|
+
};
|
|
1484
|
+
registerFsRoutes(app, {
|
|
1485
|
+
os: { homedir: () => '/home/user' },
|
|
1486
|
+
path: path.posix,
|
|
1487
|
+
fsPromises: {
|
|
1488
|
+
realpath: async (targetPath) => targetPath,
|
|
1489
|
+
stat,
|
|
1490
|
+
readdir,
|
|
1491
|
+
},
|
|
1492
|
+
spawn: vi.fn(),
|
|
1493
|
+
crypto: { randomUUID: () => 'job-0' },
|
|
1494
|
+
normalizeDirectoryPath: (p) => p,
|
|
1495
|
+
resolveProjectDirectory: async () => ({ directory: '/repo' }),
|
|
1496
|
+
buildAugmentedPath: () => '/usr/bin',
|
|
1497
|
+
resolveGitBinaryForSpawn: () => 'git',
|
|
1498
|
+
aiworkerUserConfigRoot: '/home/user/.config',
|
|
1499
|
+
});
|
|
1500
|
+
const handler = getRoute('GET', '/api/fs/directory-stat');
|
|
1501
|
+
|
|
1502
|
+
const available = await callStat(handler, { query: { path: '/other-project' } });
|
|
1503
|
+
expect(available.statusCode).toBe(200);
|
|
1504
|
+
expect(available.body).toEqual({ isDirectory: true });
|
|
1505
|
+
expect(available.getHeader('Cache-Control')).toBe('no-store');
|
|
1506
|
+
expect(stat).toHaveBeenCalledTimes(1);
|
|
1507
|
+
|
|
1508
|
+
const invalid = await callStat(handler, { query: { path: ' ' } });
|
|
1509
|
+
expect(invalid.statusCode).toBe(400);
|
|
1510
|
+
expect(stat).toHaveBeenCalledTimes(1);
|
|
1511
|
+
|
|
1512
|
+
for (const query of ['path=/repo&path=/other', 'path[]=/repo', '']) {
|
|
1513
|
+
const malformed = createMockResponse();
|
|
1514
|
+
await handler({ url: `/api/fs/directory-stat?${query}` }, malformed);
|
|
1515
|
+
expect(malformed.statusCode).toBe(400);
|
|
1516
|
+
}
|
|
1517
|
+
expect(stat).toHaveBeenCalledTimes(1);
|
|
1518
|
+
|
|
1519
|
+
const missing = await callStat(handler, { headers: { 'x-opencode-directory': '/repo-b' }, query: { path: '/repo-b', directory: 'true' } });
|
|
1520
|
+
expect(missing.statusCode).toBe(404);
|
|
1521
|
+
expect(missing.body).toEqual({ error: 'Directory not found', reason: 'not-found' });
|
|
1522
|
+
|
|
1523
|
+
const notDir = await callStat(handler, { headers: { 'x-opencode-directory': '/repo-b' }, query: { path: '/repo-b/file.txt/child', directory: 'true' } });
|
|
1524
|
+
expect(notDir.statusCode).toBe(400);
|
|
1525
|
+
expect(notDir.body).toEqual({ error: 'Specified path is not a directory', reason: 'not-directory' });
|
|
1526
|
+
|
|
1527
|
+
const denied = await callStat(handler, { headers: { 'x-opencode-directory': '/repo-b' }, query: { path: '/repo-b/protected', directory: 'true' } });
|
|
1528
|
+
expect(denied.statusCode).toBe(403);
|
|
1529
|
+
expect(denied.body).toEqual({ error: 'Access to directory denied', reason: 'os-permission' });
|
|
1530
|
+
|
|
1531
|
+
const file = await callStat(handler, { query: { path: '/repo-b/file.txt' } });
|
|
1532
|
+
expect(file.statusCode).toBe(400);
|
|
1533
|
+
expect(file.body.reason).toBe('not-directory');
|
|
1534
|
+
|
|
1535
|
+
const failure = await callStat(handler, { query: { path: '/repo-b/failure' } });
|
|
1536
|
+
expect(failure.statusCode).toBe(500);
|
|
1537
|
+
expect(failure.body).toEqual({ error: 'Failed to stat directory' });
|
|
1538
|
+
expect(readdir).not.toHaveBeenCalled();
|
|
1539
|
+
});
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1542
|
+
describe('fs managed chats root', () => {
|
|
1543
|
+
const registerWithChatsRoot = ({ managedChatsRoot, fsPromises = {} } = {}) => {
|
|
1544
|
+
const { app, getRoute } = createRouteRegistry();
|
|
1545
|
+
registerFsRoutes(app, {
|
|
1546
|
+
os: { homedir: () => '/home/user' },
|
|
1547
|
+
path: path.posix,
|
|
1548
|
+
fsPromises: {
|
|
1549
|
+
realpath: async (targetPath) => targetPath,
|
|
1550
|
+
mkdir: async () => undefined,
|
|
1551
|
+
...fsPromises,
|
|
1552
|
+
},
|
|
1553
|
+
spawn: vi.fn(),
|
|
1554
|
+
crypto: { randomUUID: () => 'job-0' },
|
|
1555
|
+
normalizeDirectoryPath: (p) => p,
|
|
1556
|
+
resolveProjectDirectory: async () => ({ directory: '/repo' }),
|
|
1557
|
+
buildAugmentedPath: () => '/usr/bin',
|
|
1558
|
+
resolveGitBinaryForSpawn: () => 'git',
|
|
1559
|
+
aiworkerUserConfigRoot: '/home/user/.config/openchamber',
|
|
1560
|
+
managedChatsRoot,
|
|
1561
|
+
});
|
|
1562
|
+
return {
|
|
1563
|
+
home: getRoute('GET', '/api/fs/home'),
|
|
1564
|
+
mkdir: getRoute('POST', '/api/fs/mkdir'),
|
|
1565
|
+
};
|
|
1566
|
+
};
|
|
1567
|
+
|
|
1568
|
+
it('exposes the default chats root next to the home directory', async () => {
|
|
1569
|
+
const { home } = registerWithChatsRoot();
|
|
1570
|
+
|
|
1571
|
+
const res = createMockResponse();
|
|
1572
|
+
await home(undefined, res);
|
|
1573
|
+
|
|
1574
|
+
expect(res.statusCode).toBe(200);
|
|
1575
|
+
expect(res.body.home).toBe('/home/user');
|
|
1576
|
+
expect(res.body.chatsRoot).toBe('/home/user/.config/openchamber/chats');
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
it('exposes a relocated chats root when AIWORKER_CHATS_DIR is configured upstream', async () => {
|
|
1580
|
+
const { home } = registerWithChatsRoot({ managedChatsRoot: '/srv/openchamber-chats' });
|
|
1581
|
+
|
|
1582
|
+
const res = createMockResponse();
|
|
1583
|
+
await home(undefined, res);
|
|
1584
|
+
|
|
1585
|
+
expect(res.statusCode).toBe(200);
|
|
1586
|
+
expect(res.body.chatsRoot).toBe('/srv/openchamber-chats');
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
it('allows mkdir inside the relocated chats root outside the active workspace', async () => {
|
|
1590
|
+
const mkdirCalls = [];
|
|
1591
|
+
const { mkdir } = registerWithChatsRoot({
|
|
1592
|
+
managedChatsRoot: '/srv/openchamber-chats',
|
|
1593
|
+
fsPromises: {
|
|
1594
|
+
mkdir: async (targetPath) => {
|
|
1595
|
+
mkdirCalls.push(targetPath);
|
|
1596
|
+
},
|
|
1597
|
+
},
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
const res = createMockResponse();
|
|
1601
|
+
await mkdir({ body: { path: '/srv/openchamber-chats/2026-08-25/session-a' } }, res);
|
|
1602
|
+
|
|
1603
|
+
expect(res.statusCode).toBe(200);
|
|
1604
|
+
expect(res.body.success).toBe(true);
|
|
1605
|
+
expect(mkdirCalls).toEqual(['/srv/openchamber-chats/2026-08-25/session-a']);
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
it('still rejects mkdir outside the workspace and all managed roots', async () => {
|
|
1609
|
+
const { mkdir } = registerWithChatsRoot({ managedChatsRoot: '/srv/openchamber-chats' });
|
|
1610
|
+
|
|
1611
|
+
const res = createMockResponse();
|
|
1612
|
+
await mkdir({ body: { path: '/etc/passwd-holder' } }, res);
|
|
1613
|
+
|
|
1614
|
+
expect(res.statusCode).toBe(400);
|
|
1615
|
+
expect(res.body).toEqual({ error: 'Path is outside of active workspace' });
|
|
1616
|
+
});
|
|
1431
1617
|
});
|
|
@@ -25,9 +25,11 @@ The following functions are exported and used by the web server:
|
|
|
25
25
|
|
|
26
26
|
### Status and Diff Operations
|
|
27
27
|
- `getStatus(directory)`: Get comprehensive Git status including current branch, tracking, ahead/behind, file changes, diff stats, merge/rebase state.
|
|
28
|
-
- `getDiff(directory, { path, staged, contextLines })`: Get diff output for files or entire working tree. Untracked symbolic links are represented as link entries without following their targets.
|
|
29
|
-
- `getRangeDiff(directory, { base, head, path, contextLines })`:
|
|
30
|
-
- `getRangeFiles(directory, { base, head })`:
|
|
28
|
+
- `getDiff(directory, { path, staged, contextLines })`: Get diff output for files or entire working tree with full Git blob identities. Untracked symbolic links are represented as link entries without following their targets.
|
|
29
|
+
- `getRangeDiff(directory, { base, head, path, contextLines, includeWorkingTree })`: Compare the merge base of the exact selected refs with `head`. With `includeWorkingTree: true`, compare with the checked-out branch's current files instead, including committed, staged, unstaged, and untracked work in one net diff. This mode rejects a head that is not the checked-out branch. Exposed as `GET /api/git/range-diff`; omit `path` for the whole comparison.
|
|
30
|
+
- `getRangeFiles(directory, { base, head, includeWorkingTree })`: List changed paths using the same comparison as `getRangeDiff`. A successful empty list means the final files match the merge base, even if staging and working-tree changes cancel each other out.
|
|
31
|
+
- Both range operations honor refs literally. A local `main` is never replaced with `origin/main`, and an unavailable ref fails rather than choosing a different remote. The UI picker sends qualified refs to distinguish local and remote branches with matching display names.
|
|
32
|
+
- Working-tree comparisons use the real index read-only. When untracked paths exist, a temporary copy of the index receives intent-to-add entries so Git computes additions, deletions, recreations, and renames together. Current contents come from the working tree, symlinks remain links, ignored files stay excluded, and temporary files are removed on success or failure.
|
|
31
33
|
- `getFileDiff(directory, { path, staged })`: Get original and modified file contents for a single file (handles images as data URLs and symbolic links as their link-target text).
|
|
32
34
|
- `listUntrackedPaths(directory)`: List individual untracked file paths honoring ignore rules. Much cheaper than `getStatus` when that is all a caller needs. Deliberately not `--directory`: collapsed directory entries end in a slash and are rejected by the per-file diff helpers, so a caller would silently lose every file inside a new directory.
|
|
33
35
|
- `getUntrackedDiffs(directory, filePaths, { concurrency, contextLines })`: Diffs for untracked files against an empty tree. Resolves the repository context once instead of per file (`getDiff` re-resolves every call, costing an extra `rev-parse` each time) and bounds how many diff processes run at once. Returns one entry per input path in order; unreadable paths yield `''` rather than failing the batch.
|
|
@@ -35,10 +37,12 @@ The following functions are exported and used by the web server:
|
|
|
35
37
|
- `revertFile(directory, filePath, options)`: Revert a file. Default scope `all` discards staged and working-tree changes; scope `working` discards only unstaged/working-tree changes.
|
|
36
38
|
- `stageFile(directory, filePath)`: Add one file path to the index.
|
|
37
39
|
- `unstageFile(directory, filePath)`: Remove one file path from the index while preserving working-tree content.
|
|
38
|
-
- `applyHunk(directory, filePath, options)`: Apply a single-hunk patch via `git apply`. `options.action` is `stage` (`git apply --cached`), `unstage` (`git apply --cached --reverse`), or `discard` (`git apply --reverse` in the working tree).
|
|
40
|
+
- `applyHunk(directory, filePath, options)`: Apply a single-hunk patch via `git apply`. `options.action` is `stage` (`git apply --cached`), `unstage` (`git apply --cached --reverse`), or `discard` (`git apply --reverse` in the working tree). Inside the index mutation queue, the server verifies that the complete patch exactly matches one current three-context-line hunk for that file and scope, then runs `--check` before applying. Applicability alone cannot prove an unstaged change: old staged or committed hunks can reverse cleanly too. Stale, historical and multi-file patches fail with a refresh error. Temporary patch files are removed on success and failure; hunk content retains CRLF bytes.
|
|
39
41
|
|
|
40
42
|
### Branch Operations
|
|
43
|
+
- `getBranchBase(directory, branch)`: Read a named creation source from reflog. After a rebase, the creation source is no longer a current parent record, so return `null` and let the user choose a base. Explicit per-runtime, directory, and branch choices in the shared UI outrank detection.
|
|
41
44
|
- `getBranches(directory)`: Get list of local and remote branches (filtered to active remote branches).
|
|
45
|
+
- `getUnpushedBranchCounts(directory, branchNames)`: Count commits ahead of each locally known upstream for up to five supplied local branches. This reads local refs only and omits branches without an upstream.
|
|
42
46
|
- `createBranch(directory, branchName, options)`: Create and checkout a new branch.
|
|
43
47
|
- `checkoutBranch(directory, branchName)`: Checkout an existing branch. A remote-tracking name (`origin/main`, or the `remotes/`-prefixed form) resolves to the local branch of that name, created with `--track` when it does not exist yet, because the branch selector offers remote branches as places to work rather than commits to inspect — a literal checkout of the remote ref would detach HEAD. A local branch whose own name looks like a remote ref wins over that resolution, and anything unresolvable is checked out as requested. The returned `branch` is the branch that was actually checked out, which callers should report instead of the requested name.
|
|
44
48
|
- `deleteBranch(directory, branch, options)`: Delete a branch (supports force flag).
|
|
@@ -48,7 +52,7 @@ The following functions are exported and used by the web server:
|
|
|
48
52
|
### Worktree Operations
|
|
49
53
|
- `getWorktrees(directory)`: List all git worktrees for a repository.
|
|
50
54
|
- `validateWorktreeCreate(directory, input)`: Validate worktree creation parameters (mode, branchName, startRef, upstream config).
|
|
51
|
-
- `createWorktree(directory, input)`: Create a new worktree (supports 'new' and 'existing' modes, upstream setup). After populating the worktree, the repository's `post-checkout` hook runs once with git's standard arguments (null ref as previous HEAD, the checked-out HEAD, and flag `1`) from the worktree directory, mirroring `git worktree add` without `--no-checkout`; a missing or non-executable hook is skipped and a failing hook is logged as a warning, never failing worktree creation or the session bootstrap.
|
|
55
|
+
- `createWorktree(directory, input)`: Create a new worktree (supports 'new' and 'existing' modes, upstream setup). When the current tracked branch has no unpublished commits, the UI supplies its remote-tracking ref and this operation fetches that branch once before creating the worktree. A failed fetch falls back to the local branch and reports `sourceFetchFailed`; other remote start refs still require an existing local ref when their fetch fails. After populating the worktree, the repository's `post-checkout` hook runs once with git's standard arguments (null ref as previous HEAD, the checked-out HEAD, and flag `1`) from the worktree directory, mirroring `git worktree add` without `--no-checkout`; a missing or non-executable hook is skipped and a failing hook is logged as a warning, never failing worktree creation or the session bootstrap.
|
|
52
56
|
- `removeWorktree(directory, input)`: Remove a worktree (optionally delete local branch).
|
|
53
57
|
- `isLinkedWorktree(directory)`: Check if directory is a linked worktree (not primary).
|
|
54
58
|
|
|
@@ -70,7 +74,8 @@ bootstrap, tracking is left unset rather than writing `branch.*.remote` /
|
|
|
70
74
|
|
|
71
75
|
### Log Operations
|
|
72
76
|
- `getLog(directory, options)`: Get commit history with stats (supports maxCount, from, to, file filters).
|
|
73
|
-
- `getCommitFiles(directory, commitHash)`: Get file changes for a specific commit.
|
|
77
|
+
- `getCommitFiles(directory, commitHash)`: Get file changes for a specific commit relative to its first parent, or the empty tree for a root commit. NUL-delimited paths preserve whitespace; renamed files return their destination in `path` and source in `previousPath`.
|
|
78
|
+
- `getCommitDiff(directory, { hash, path, previousPath, contextLines })`: Get the same commit's patch, with optional file filtering and context depth. `previousPath` keeps a rename's old and new paths in the per-file patch. Reads committed objects only, never the working tree. Exposed as `GET /api/git/commit-diff`; an unavailable hash fails rather than returning an empty diff.
|
|
74
79
|
- `getCommitFileDiff(directory, hash, filePath, isBinary)`: Get before/after content for a specific file in a commit. Returns `{ original, modified, isBinary }`. Runs `git show <hash>^:<path>` and `git show <hash>:<path>` in parallel; returns empty strings on failure (added/deleted/root-commit edge cases).
|
|
75
80
|
|
|
76
81
|
### Merge and Rebase Operations
|
|
@@ -129,8 +134,28 @@ The following functions are internal helpers used by exported functions:
|
|
|
129
134
|
|
|
130
135
|
### Runtime availability of range diffs
|
|
131
136
|
- `GET /api/git/range-diff` is served by the AiWorker web server, so it is available to web, desktop, and mobile clients. The shared `GitAPI.getGitRangeDiff` is therefore optional: web supplies the HTTP implementation, and VS Code does not implement it because the extension host serves Git through its own bridge rather than these routes. Features built on range diffs (currently the AI diff walkthrough) are not offered in VS Code.
|
|
137
|
+
- Commit comparison uses the same server boundary through optional `GitAPI.getGitCommitDiff`. Desktop Changes, mobile Changes, and the existing walkthrough surface share branch/commit comparison semantics. Mobile Changes uses the same selectors and `useGitComparison` file-list owner, with a read-only list-to-detail flow. VS Code keeps its existing modes because its Git bridge does not provide these comparison operations. The HTTP operations are available to web, Electron, hosted mobile, and Capacitor clients.
|
|
132
138
|
|
|
133
139
|
### Staged and unstaged change handling
|
|
140
|
+
- Desktop Changes floats a compact action capsule after each hunk's last changed row,
|
|
141
|
+
including single-hunk files. Whole-file controls remain in the Git panel.
|
|
142
|
+
`getPatchHunkAnchors` uses the canonical patch's final changed row and side, so a hunk
|
|
143
|
+
ending in deletions is anchored after those deletions rather than above them.
|
|
144
|
+
Zero-height Pierre annotation slots anchor the capsule over following context
|
|
145
|
+
without a separate band. At EOF the capsule lifts inside the code column;
|
|
146
|
+
a one-line code column has a minimum hit-target height. React controls mount only
|
|
147
|
+
for currently rendered slots and only after their rendered diff and anchor
|
|
148
|
+
identities match the current props. Comment annotations remain independent.
|
|
149
|
+
- The canonical three-line-context action patch stays separate from the full-file
|
|
150
|
+
display patch. Their bytes must be identical, or their file headers and full
|
|
151
|
+
blob identities must match, including when reusing a cached action patch.
|
|
152
|
+
Mismatch leaves actions unavailable until Retry obtains a matching pair.
|
|
153
|
+
Successful hunk mutations invalidate
|
|
154
|
+
every mounted view of that path through `sessionEvents.requestGitRefresh`.
|
|
155
|
+
Actions remain unavailable until the refresh succeeds. Last turn, Branch and
|
|
156
|
+
Commit snapshots never expose hunk mutations. Mobile uses its separate Changes
|
|
157
|
+
surface and VS Code does not mount these controls.
|
|
158
|
+
- Untracked patches from `getDiff` and `getUntrackedDiffs` use `git diff --no-index` with separate stdout, stderr, and process exit status. Exit codes 0 and 1 return stdout only, so line-ending warnings never become patch text or request failures. Other exits and process failures reject the single-file request; the batch keeps an empty entry for the failed path and preserves the other results.
|
|
134
159
|
- `status.files` exposes both `index` and `working_dir` codes. Shared UI uses these as separate scopes: staged rows are derived from non-empty `index` statuses, while unstaged rows are derived from `working_dir` statuses and untracked files.
|
|
135
160
|
- A file with both staged and unstaged changes can appear in both UI sections. Staged rows request diffs with `staged: true`; unstaged rows request normal working-tree diffs.
|
|
136
161
|
- The shared Git panel exposes explicit staging actions. Unstaged rows use `stageFile`, staged rows use `unstageFile`, and commits operate on the current staged index.
|
|
@@ -142,6 +167,7 @@ The following functions are internal helpers used by exported functions:
|
|
|
142
167
|
- `path`: Absolute path to worktree directory.
|
|
143
168
|
- `directoryCreated`: Present when create returned after the target directory exists while background Git/bootstrap work continues.
|
|
144
169
|
- `bootstrapStatus`: Background setup state. The legacy `status` remains `pending`, `ready`, or `failed`, while `phase` reports `directory-created`, `git-ready`, or `setup-ready`. Fast create starts at `pending`/`directory-created`; population and upstream Git completion advances to `pending`/`git-ready` before setup/start scripts; completed setup is `ready`/`setup-ready`. A missing in-memory state falls back to `ready`/`setup-ready`; clients continue to accept legacy status responses that omit `phase`.
|
|
170
|
+
- `sourceFetchFailed`: Present when the automatic source-branch fetch failed and creation fell back to the tracked local branch.
|
|
145
171
|
- Fast-create background failures remove OpenCode sandbox metadata for directories that never became Git worktrees, and remove the pre-created directory only if it is still empty. User-created files are never recursively deleted by this cleanup.
|
|
146
172
|
- Worktree removal waits for any active create/bootstrap task for that directory before deleting it, preventing a background Git or setup task from restoring removed state or racing filesystem cleanup.
|
|
147
173
|
- Worktree bootstrap retries transient `index.lock` conflicts. If the lock remains byte-for-byte and metadata-identical across the retry window, it is treated as stale, removed, and population continues automatically; changing locks are left untouched and reported as failures.
|
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
|
|
5
|
-
const STORAGE_DIR = path.join(os.homedir(), '.config', '
|
|
5
|
+
const STORAGE_DIR = path.join(os.homedir(), '.config', 'aiworker');
|
|
6
6
|
const STORAGE_FILE = path.join(STORAGE_DIR, 'git-identities.json');
|
|
7
7
|
|
|
8
8
|
function ensureStorageDir() {
|
|
@@ -12,11 +12,11 @@ import {
|
|
|
12
12
|
} from './service.js';
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
|
-
// Regression for https://
|
|
15
|
+
// Regression for https://roweb.cn/roweb/openchamber/issues/2746
|
|
16
16
|
//
|
|
17
17
|
// "[Bug] new worktree, Filename too long"
|
|
18
18
|
//
|
|
19
|
-
//
|
|
19
|
+
// AiWorker places worktrees under:
|
|
20
20
|
// <XDG_DATA_HOME>/opencode/worktree/<40-char root commit hash>/<worktree name>
|
|
21
21
|
// and populates them with `git reset --hard`. On Windows, that deep prefix plus
|
|
22
22
|
// a deeply nested repo file (e.g. yudao ~173 chars) exceeds MAX_PATH (260) and
|
|
@@ -95,7 +95,7 @@ describe('issue #2746 - worktree long path support', () => {
|
|
|
95
95
|
const created = await createWorktree(repo, {
|
|
96
96
|
mode: 'new',
|
|
97
97
|
worktreeName: 'issue-2746',
|
|
98
|
-
branchName: '
|
|
98
|
+
branchName: 'aiworker/issue-2746',
|
|
99
99
|
});
|
|
100
100
|
expect(created.directoryCreated).toBe(true);
|
|
101
101
|
|
|
@@ -162,7 +162,7 @@ describe('issue #2746 - worktree long path support', () => {
|
|
|
162
162
|
const created = await createWorktree(repo, {
|
|
163
163
|
mode: 'new',
|
|
164
164
|
worktreeName: 'issue-2746-namemax',
|
|
165
|
-
branchName: '
|
|
165
|
+
branchName: 'aiworker/issue-2746-namemax',
|
|
166
166
|
});
|
|
167
167
|
expect(created.directoryCreated).toBe(true);
|
|
168
168
|
|
package/server/lib/git/routes.js
CHANGED
|
@@ -7,13 +7,13 @@ export function registerGitRoutes(app) {
|
|
|
7
7
|
return gitLibraries;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
const resolveDirectoryQuery = (value) => {
|
|
10
|
+
const resolveDirectoryQuery = (value, preserveWhitespace = false) => {
|
|
11
11
|
const raw = Array.isArray(value) ? value[0] : value;
|
|
12
12
|
if (typeof raw !== 'string') {
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
|
-
const
|
|
16
|
-
return
|
|
15
|
+
const normalized = preserveWhitespace ? raw : raw.trim();
|
|
16
|
+
return normalized || null;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const extractGitErrorText = (error) => {
|
|
@@ -417,6 +417,7 @@ export function registerGitRoutes(app) {
|
|
|
417
417
|
const diff = await getRangeDiff(directory, {
|
|
418
418
|
base,
|
|
419
419
|
head,
|
|
420
|
+
includeWorkingTree: req.query.includeWorkingTree === 'true',
|
|
420
421
|
path: pathParam,
|
|
421
422
|
contextLines: Number.isFinite(context) ? context : 3,
|
|
422
423
|
});
|
|
@@ -463,7 +464,7 @@ export function registerGitRoutes(app) {
|
|
|
463
464
|
return res.status(400).json({ error: 'base and head parameters are required' });
|
|
464
465
|
}
|
|
465
466
|
|
|
466
|
-
const files = await getRangeFiles(directory, { base, head });
|
|
467
|
+
const files = await getRangeFiles(directory, { base, head, includeWorkingTree: req.query.includeWorkingTree === 'true' });
|
|
467
468
|
res.json({ files });
|
|
468
469
|
} catch (error) {
|
|
469
470
|
console.error('Failed to get git range files:', error);
|
|
@@ -873,6 +874,22 @@ export function registerGitRoutes(app) {
|
|
|
873
874
|
}
|
|
874
875
|
});
|
|
875
876
|
|
|
877
|
+
app.post('/api/git/branch-push-status', async (req, res) => {
|
|
878
|
+
const { getUnpushedBranchCounts } = await getGitLibraries();
|
|
879
|
+
try {
|
|
880
|
+
const directory = req.query.directory;
|
|
881
|
+
const branches = req.body?.branches;
|
|
882
|
+
if (!directory) return res.status(400).json({ error: 'directory parameter is required' });
|
|
883
|
+
if (!Array.isArray(branches) || branches.some((branch) => typeof branch !== 'string')) {
|
|
884
|
+
return res.status(400).json({ error: 'branches must be an array of branch names' });
|
|
885
|
+
}
|
|
886
|
+
res.json(await getUnpushedBranchCounts(directory, branches));
|
|
887
|
+
} catch (error) {
|
|
888
|
+
console.error('Failed to get branch push status:', error);
|
|
889
|
+
res.status(500).json({ error: error.message || 'Failed to get branch push status' });
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
|
|
876
893
|
app.post('/api/git/branches', async (req, res) => {
|
|
877
894
|
const { createBranch } = await getGitLibraries();
|
|
878
895
|
try {
|
|
@@ -1074,7 +1091,7 @@ export function registerGitRoutes(app) {
|
|
|
1074
1091
|
// Worktrees are an optional feature. Avoid repeated 500s (and repeated client retries)
|
|
1075
1092
|
// when the directory isn't a git repo or uses shell shorthand like "~/".
|
|
1076
1093
|
console.warn('Failed to get worktrees, returning empty list:', error?.message || error);
|
|
1077
|
-
res.setHeader('X-
|
|
1094
|
+
res.setHeader('X-AiWorker-Warning', 'git worktrees unavailable');
|
|
1078
1095
|
res.json([]);
|
|
1079
1096
|
}
|
|
1080
1097
|
});
|
|
@@ -1284,6 +1301,25 @@ export function registerGitRoutes(app) {
|
|
|
1284
1301
|
}
|
|
1285
1302
|
});
|
|
1286
1303
|
|
|
1304
|
+
app.get('/api/git/commit-diff', async (req, res) => {
|
|
1305
|
+
const { getCommitDiff } = await getGitLibraries();
|
|
1306
|
+
try {
|
|
1307
|
+
const directory = resolveDirectoryQuery(req.query.directory);
|
|
1308
|
+
const hash = resolveDirectoryQuery(req.query.hash);
|
|
1309
|
+
if (!directory || !hash) return res.status(400).json({ error: 'directory and hash are required' });
|
|
1310
|
+
const context = Number(req.query.context ?? 3);
|
|
1311
|
+
const diff = await getCommitDiff(directory, {
|
|
1312
|
+
hash,
|
|
1313
|
+
path: resolveDirectoryQuery(req.query.path, true) ?? undefined,
|
|
1314
|
+
previousPath: resolveDirectoryQuery(req.query.previousPath, true) ?? undefined,
|
|
1315
|
+
contextLines: Number.isFinite(context) ? context : 3,
|
|
1316
|
+
});
|
|
1317
|
+
res.json({ diff });
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
res.status(500).json({ error: error.message || 'Failed to get commit diff' });
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1287
1323
|
app.get('/api/git/commit-file-diff', async (req, res) => {
|
|
1288
1324
|
const { getCommitFileDiff } = await getGitLibraries();
|
|
1289
1325
|
try {
|