@vantran-se/openchamber-web 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -24
- package/bin/cli.js +1 -3
- package/bin/cli.test.js +40 -16
- package/bin/lib/DOCUMENTATION.md +6 -5
- package/bin/lib/cli-args.js +6 -1
- package/bin/lib/cli-args.test.js +34 -0
- package/bin/lib/cli-lifecycle.js +0 -1
- package/bin/lib/cli-network.js +8 -2
- package/bin/lib/cli-process.js +1 -1
- package/bin/lib/cli-settings-accessors.test.js +21 -13
- package/bin/lib/cli-startup.js +17 -11
- package/bin/lib/cli-startup.test.js +92 -2
- package/bin/lib/commands-connect-url.js +8 -1
- package/bin/lib/commands-schedule.js +8 -2
- package/bin/lib/commands-serve.js +15 -21
- package/bin/lib/commands-update.js +12 -24
- package/bin/lib/commands-update.test.js +14 -46
- package/dist/assets/DiffView-Cv9MSXl1.js +4 -0
- package/dist/assets/FilePreviewCommentMenu-D4kLseHj.js +4 -0
- package/dist/assets/FilesView-BVoD8I2x.js +14 -0
- package/dist/assets/{GitView-DzuzhIPx.js → GitView-Bzb8p-ew.js} +4 -4
- package/dist/assets/GuestSurfacePane-Crdbvsxo.js +1 -0
- package/dist/assets/InProgressOperationBanner-CwMjNQgk.js +2 -0
- package/dist/assets/{JsonTreeView-B_EPRjzy.js → JsonTreeView-DRKHZfsn.js} +1 -1
- package/dist/assets/MarkdownImageGallery-DZtqT1Zn.js +1 -0
- package/dist/assets/MarkdownRendererImpl-CqG7HCNd.js +10 -0
- package/dist/assets/OnboardingScreen-DMIxwYK5.js +1 -0
- package/dist/assets/{PatchDiff-6yh1csza.js → PatchDiff-CUcCg1jk.js} +1 -1
- package/dist/assets/PierreDiffViewer-CnnR-dnD.js +228 -0
- package/dist/assets/PlanView-RCBadcY7.js +5 -0
- package/dist/assets/SettingsView-CjBjZFRO.js +30 -0
- package/dist/assets/SettingsWindow-DyM7QuhI.js +1 -0
- package/dist/assets/SubmoduleDiffSummary-BWIfgMED.js +1 -0
- package/dist/assets/ToolOutputDialog-Q0P3FuAi.js +15 -0
- package/dist/assets/ToolPartDiffPreview-p7qR9FpU.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-BTJVRkyo.js → VirtualizedFileDiff-CFpjL8Ns.js} +3 -3
- package/dist/assets/{Virtualizer-xCEYz34p.js → Virtualizer-C0IsclZn.js} +1 -1
- package/dist/assets/WalkthroughView-DI31zD02.js +4 -0
- package/dist/assets/appThemeRegistry-B9gkFq9i.js +1 -0
- package/dist/assets/{computeVirtualFileMetrics-CmPe29DM.js → computeVirtualFileMetrics-C2WM6JC2.js} +2 -2
- package/dist/assets/de-YKfk5MmC.js +14 -0
- package/dist/assets/es-_gwT0kr-.js +20 -0
- package/dist/assets/fileTreeStatus-D438yi8M.js +1 -0
- package/dist/assets/fr-DqzvXmIm.js +18 -0
- package/dist/assets/index-BLaHKdtx.js +1 -0
- package/dist/assets/index-BkMXRrVQ.css +1 -0
- package/dist/assets/index-DxNa7xYB.js +65 -0
- package/dist/assets/initializeSharedPreferences-Bck5EAWP.js +1 -0
- package/dist/assets/{isDiffPlainText-CQmDCnEv.js → isDiffPlainText-Dgbw8VKk.js} +1 -1
- package/dist/assets/{isFilePlainText-CYBUzW4I.js → isFilePlainText-_ZS5UYRj.js} +1 -1
- package/dist/assets/ja-B-GFYhQE.js +20 -0
- package/dist/assets/ko-mzksfqDC.js +20 -0
- package/dist/assets/main-C5Xx8dn_.js +2 -0
- package/dist/assets/main-CfBSQM1H.js +990 -0
- package/dist/assets/markdownCore-BmWwW1ll.js +7 -0
- package/dist/assets/miniChat-mnKwy4vQ.js +2 -0
- package/dist/assets/mobile-DeEQd0wZ.js +2 -0
- package/dist/assets/nl-CWQr6NSq.js +20 -0
- package/dist/assets/{parseDiffFromFile-Cy0PNxun.js → parseDiffFromFile-BUgn-T9p.js} +2 -2
- package/dist/assets/patchFileDiff-Dt8LhgBh.js +13 -0
- package/dist/assets/pl-UmRcDUX2.js +20 -0
- package/dist/assets/pt-BR-BSLybvVF.js +20 -0
- package/dist/assets/relocateRestoredSession-B5E9xb3T.js +1 -0
- package/dist/assets/renderElectronMiniChatApp-C0a9A5_J.js +2 -0
- package/dist/assets/renderMobileApp-BNncg3Pk.js +25 -0
- package/dist/assets/runtimeEndpointReset-CZ5aafpl.js +62 -0
- package/dist/assets/{selectionMarkdown-TkYk5h9l.js → selectionMarkdown-CUN3wUld.js} +9 -9
- package/dist/assets/{sessionOwnership-Bs1xbRfB.js → sessionOwnership-BSuxCEhb.js} +1 -1
- package/dist/assets/shikiHighlight-DHDx-6eM.js +1 -0
- package/dist/assets/space-apply-DM_2pRW7.js +1 -0
- package/dist/assets/space-close-Bpomt8z0.js +1 -0
- package/dist/assets/{surface-B6n7wJtt.js → surface-C-jQaGnL.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-CDlKffWz.js → textMateThemeFromAppTheme-DlsTphbY.js} +1 -1
- package/dist/assets/tr-gaTrX_HK.js +11 -0
- package/dist/assets/uk-CnJF6ykl.js +20 -0
- package/dist/assets/{useAppFontEffects-DCqCVGlT.js → useAppFontEffects-DGlvOqP_.js} +194 -233
- package/dist/assets/useInlineCommentController-kMN4Wg_D.js +2 -0
- package/dist/assets/{useRootScrollLock-BowwS8kq.js → useRootScrollLock-DMPpvpNP.js} +2 -2
- package/dist/assets/{vendor-base-ui-DX96QhTP.js → vendor-base-ui-Det7U81h.js} +1 -1
- package/dist/assets/{vendor-codemirror-autocomplete-DW1FQz9_.js → vendor-codemirror-autocomplete-BSHQHbKu.js} +1 -1
- package/dist/assets/{vendor-codemirror-commands-DpmhalwN.js → vendor-codemirror-commands-C4G_dPWe.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-angular-BqExbN1M.js → vendor-codemirror-lang-angular-B6LbBGHB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-cpp-DZA36HfS.js → vendor-codemirror-lang-cpp-BWWXBx7W.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-css-B07blnnZ.js → vendor-codemirror-lang-css-Cayfb29q.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-go-BM8K9D1-.js → vendor-codemirror-lang-go-BoKlCNKw.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-html-DaH9adfE.js → vendor-codemirror-lang-html-BQWerHsJ.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-java-CsdC0viz.js → vendor-codemirror-lang-java-DWX7Fq_K.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-javascript-CpGYXGuo.js → vendor-codemirror-lang-javascript-DbpEcZM0.js} +2 -2
- package/dist/assets/{vendor-codemirror-lang-jinja-BBeGzXw5.js → vendor-codemirror-lang-jinja-D1JIJRA7.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-json-58T70Hqs.js → vendor-codemirror-lang-json-BzgckzmB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-less-BI-xCA59.js → vendor-codemirror-lang-less-B9EpDXFi.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-liquid-zEcMa-zy.js → vendor-codemirror-lang-liquid-9U7kRbSf.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-markdown-BLKnlLP4.js → vendor-codemirror-lang-markdown-D2TEBIvZ.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-php-9f35M83-.js → vendor-codemirror-lang-php-BT6sDVzE.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-python-jAMtYiA9.js +3 -0
- package/dist/assets/{vendor-codemirror-lang-rust-MNDL_jZQ.js → vendor-codemirror-lang-rust-CVO56EiL.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-sass-p9epsSNm.js → vendor-codemirror-lang-sass-DZlOoUWu.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-sql-Bqzh0EXX.js → vendor-codemirror-lang-sql-fZ6t1DPw.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-vue-BnNUWV-p.js → vendor-codemirror-lang-vue-DxbqrLiE.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-wast-C2a-oLmk.js → vendor-codemirror-lang-wast-DyU1Gt-I.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-xml-C9a_K5_p.js +1 -0
- package/dist/assets/{vendor-codemirror-lang-yaml-C0sjswB0.js → vendor-codemirror-lang-yaml-Cptq8l0e.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-ByBn5zIW.js → vendor-codemirror-language-DufXoQvE.js} +2 -2
- package/dist/assets/vendor-codemirror-language-data-CtwSWKtX.js +2 -0
- package/dist/assets/{vendor-codemirror-search-ib-sHD5W.js → vendor-codemirror-search-CvXM_pZg.js} +1 -1
- package/dist/assets/{vendor-codemirror-state-G3UuR-be.js → vendor-codemirror-state-Dek9sMtw.js} +1 -1
- package/dist/assets/{vendor-codemirror-view-Bniqp7p5.js → vendor-codemirror-view-1e0FQ6Wn.js} +1 -1
- package/dist/assets/vendor-framer-motion-DxDVhOLc.js +9 -0
- package/dist/assets/vendor-katex-CuQvj9-L.js +257 -0
- package/dist/assets/vendor-katex-DdZNUVNG.css +1 -0
- package/dist/assets/{vendor-motion-dom-DcCgeG69.js → vendor-motion-dom-HXPOMxb7.js} +1 -1
- package/dist/assets/{vendor-opencode-client-DHzhmLwV.js → vendor-opencode-client-BmFcUkbQ.js} +1 -1
- package/dist/assets/{vendor-replit-codemirror-vim-DkzqxN2H.js → vendor-replit-codemirror-vim-BlJRn-cW.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-virtual-H_JmXOFV.js → vendor-tanstack-react-virtual-CvuLsk7D.js} +1 -1
- package/dist/assets/{vendor-tanstack-virtual-core-Dxc8hSTB.js → vendor-tanstack-virtual-core-DPaNcJcT.js} +1 -1
- package/dist/assets/zh-CN-t2vigSB1.js +20 -0
- package/dist/assets/zh-TW-jAgqBokB.js +20 -0
- package/dist/index.html +6 -6
- package/dist/mini-chat.html +6 -6
- package/dist/mobile.html +6 -6
- package/package.json +8 -5
- package/server/index.js +80 -36
- package/server/lib/agent-memory/actions.js +1 -6
- package/server/lib/agent-memory/routes.js +1 -2
- package/server/lib/agent-memory/runtime.js +1 -8
- package/server/lib/agent-tool/DOCUMENTATION.md +7 -3
- package/server/lib/agent-tool/runtime.js +3 -6
- package/server/lib/agent-tool/runtime.test.js +0 -2
- package/server/lib/browser-control/DOCUMENTATION.md +6 -0
- package/server/lib/changelog/update-notes.js +1 -1
- package/server/lib/context-obligatory/runtime.js +1 -2
- package/server/lib/dev-tunnel/DOCUMENTATION.md +8 -0
- package/server/lib/dev-tunnel/client.js +21 -10
- package/server/lib/dev-tunnel/tunnel.test.js +34 -0
- package/server/lib/dispatch-results/runtime.js +1 -1
- package/server/lib/enterprise-mode.d.ts +1 -0
- package/server/lib/enterprise-mode.js +13 -2
- package/server/lib/enterprise-mode.test.js +21 -0
- package/server/lib/event-stream/DOCUMENTATION.md +2 -2
- package/server/lib/event-stream/index.js +1 -3
- package/server/lib/event-stream/translate-v2.js +1 -9
- package/server/lib/event-stream/translate-v2.test.js +0 -11
- package/server/lib/fs/DOCUMENTATION.md +5 -2
- package/server/lib/fs/routes.js +62 -15
- package/server/lib/fs/routes.test.js +278 -48
- package/server/lib/fs/workspace-file-names.js +104 -0
- package/server/lib/fs/workspace-file-names.test.js +62 -0
- package/server/lib/git/DOCUMENTATION.md +17 -5
- package/server/lib/git/contributor-provenance-storage.js +9 -6
- package/server/lib/git/contributor-provenance-storage.test.js +29 -0
- package/server/lib/git/credential-broker.js +1 -1
- package/server/lib/git/credential-resolver.js +1 -4
- package/server/lib/git/credentials.js +1 -0
- package/server/lib/git/discovery-endpoint.js +1 -1
- package/server/lib/git/identity-provisioning.js +2 -1
- package/server/lib/git/identity-storage.js +3 -4
- package/server/lib/git/lfs-discovery.js +14 -14
- package/server/lib/git/lfs-discovery.test.js +16 -6
- package/server/lib/git/network-operation-plan.js +50 -62
- package/server/lib/git/network-operation-plan.test.js +103 -1
- package/server/lib/git/network-operation-registry.js +4 -6
- package/server/lib/git/network-operation-storage.js +4 -6
- package/server/lib/git/network-operations.js +12 -4
- package/server/lib/git/network-operations.test.js +19 -6
- package/server/lib/git/redaction.js +2 -1
- package/server/lib/git/repository-credential-runtime.js +5 -3
- package/server/lib/git/repository-credential-runtime.test.js +22 -0
- package/server/lib/git/routes.js +31 -137
- package/server/lib/git/routes.test.js +2 -2
- package/server/lib/git/service.js +143 -27
- package/server/lib/git/service.test.js +215 -1
- package/server/lib/git/ssh-credential-storage.js +1 -4
- package/server/lib/git/submodule-discovery.js +1 -1
- package/server/lib/git/worktree-bootstrap-storage.js +3 -4
- package/server/lib/git/worktree-bootstrap-storage.test.js +15 -0
- package/server/lib/git/worktree-creation.js +194 -0
- package/server/lib/git/worktree-creation.test.js +155 -0
- package/server/lib/github/DOCUMENTATION.md +11 -6
- package/server/lib/github/auth.js +2 -4
- package/server/lib/github/device-flow.js +2 -2
- package/server/lib/github/gh-cli-credential.js +1 -0
- package/server/lib/github/octokit.js +31 -0
- package/server/lib/github/pr-status.js +35 -18
- package/server/lib/github/pr-status.test.js +271 -3
- package/server/lib/github/reference-search.js +102 -13
- package/server/lib/github/reference-search.test.js +48 -7
- package/server/lib/github/routes.auth.test.js +18 -0
- package/server/lib/github/routes.js +360 -6
- package/server/lib/github/routes.mutations.test.js +239 -0
- package/server/lib/github/routes.test.js +134 -2
- package/server/lib/gitlab/DOCUMENTATION.md +5 -1
- package/server/lib/gitlab/auth-storage.test.js +1 -0
- package/server/lib/gitlab/mappers.js +30 -1
- package/server/lib/gitlab/resources.js +230 -6
- package/server/lib/gitlab/resources.test.js +179 -0
- package/server/lib/gitlab/routes.js +200 -6
- package/server/lib/gitlab/routes.test.js +186 -2
- package/server/lib/gitlab/validation.js +1 -5
- package/server/lib/guests/DOCUMENTATION.md +1 -1
- package/server/lib/guests/oauth.js +2 -2
- package/server/lib/guests/service.js +1 -1
- package/server/lib/guests/sockets.js +1 -1
- package/server/lib/injected-env.d.ts +8 -0
- package/server/lib/injected-env.js +50 -0
- package/server/lib/injected-env.test.js +56 -0
- package/server/lib/linear/DOCUMENTATION.md +5 -6
- package/server/lib/linear/issues.js +62 -4
- package/server/lib/linear/issues.test.js +68 -0
- package/server/lib/linear/oauth.js +5 -5
- package/server/lib/linear/parse.js +2 -9
- package/server/lib/message-queue/DOCUMENTATION.md +23 -1
- package/server/lib/message-queue/runtime.js +85 -12
- package/server/lib/message-queue/runtime.test.js +172 -2
- package/server/lib/notifications/apns-runtime.js +5 -6
- package/server/lib/notifications/apns-runtime.test.js +20 -4
- package/server/lib/openchamber-control/DOCUMENTATION.md +19 -1
- package/server/lib/openchamber-control/actions.js +2 -1
- package/server/lib/openchamber-control/service.js +119 -20
- package/server/lib/openchamber-control/service.test.js +166 -0
- package/server/lib/openchamber-sessions/archive-store.js +1 -6
- package/server/lib/openchamber-sessions/routes.js +47 -12
- package/server/lib/openchamber-sessions/routes.test.js +65 -2
- package/server/lib/openchamber-sessions/session-metadata-store.js +1 -6
- package/server/lib/opencode/DOCUMENTATION.md +22 -8
- package/server/lib/opencode/agents.js +62 -0
- package/server/lib/opencode/auth-state-runtime.js +3 -1
- package/server/lib/opencode/auth.d.ts +1 -1
- package/server/lib/opencode/auth.js +8 -0
- package/server/lib/opencode/auth.test.js +31 -0
- package/server/lib/opencode/config-entity-routes.js +16 -0
- package/server/lib/opencode/config-entity-v2.test.js +16 -1
- package/server/lib/opencode/config-paths.test.js +2 -2
- package/server/lib/opencode/config-v2.js +2 -4
- package/server/lib/opencode/feature-routes-runtime.js +89 -49
- package/server/lib/opencode/lifecycle.js +41 -21
- package/server/lib/opencode/lifecycle.test.js +50 -2
- package/server/lib/opencode/model-discovery.js +1 -1
- package/server/lib/opencode/model-discovery.test.js +17 -0
- package/server/lib/opencode/models-metadata.js +0 -2
- package/server/lib/opencode/openchamber-routes.test.js +3 -3
- package/server/lib/opencode/plugins.js +6 -9
- package/server/lib/opencode/provider-source-route.test.js +101 -3
- package/server/lib/opencode/providers-disabled.test.js +59 -0
- package/server/lib/opencode/providers.js +34 -0
- package/server/lib/opencode/proxy.js +11 -3
- package/server/lib/opencode/routes.js +40 -2
- package/server/lib/opencode/settings-helpers.js +27 -1
- package/server/lib/opencode/settings-helpers.test.js +19 -7
- package/server/lib/opencode/settings-registry.json +27 -16
- package/server/lib/opencode/settings-runtime.test.js +18 -0
- package/server/lib/opencode/shared-service-runtime.js +23 -104
- package/server/lib/opencode/shared.js +77 -46
- package/server/lib/opencode/shared.test.js +12 -12
- package/server/lib/opencode/shutdown-runtime.test.js +2 -3
- package/server/lib/opencode/skill-routes.js +123 -38
- package/server/lib/opencode/skill-routes.test.js +201 -0
- package/server/lib/opencode/skills.js +81 -34
- package/server/lib/opencode/skills.test.js +176 -21
- package/server/lib/opencode/startup-pipeline-runtime.js +6 -3
- package/server/lib/opencode/startup-pipeline-runtime.test.js +2 -1
- package/server/lib/opencode/watcher.test.js +0 -54
- package/server/lib/outbound-proxy.js +52 -0
- package/server/lib/outbound-proxy.test.js +55 -0
- package/server/lib/package-manager.js +23 -17
- package/server/lib/package-manager.test.js +5 -18
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +1 -1
- package/server/lib/permission-auto-accept/runtime.js +19 -47
- package/server/lib/permission-auto-accept/runtime.test.js +2 -2
- package/server/lib/project-context/DOCUMENTATION.md +7 -1
- package/server/lib/project-context/routes.js +1 -2
- package/server/lib/project-context/runtime.js +12 -14
- package/server/lib/project-context/runtime.test.js +78 -2
- package/server/lib/projects/project-config.js +30 -15
- package/server/lib/projects/project-config.test.js +30 -0
- package/server/lib/projects/project-setup.js +1 -2
- package/server/lib/projects/routes.js +1 -2
- package/server/lib/quota/DOCUMENTATION.md +35 -7
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/claude/index.js +1 -1
- package/server/lib/quota/providers/cline-pass.js +1 -1
- package/server/lib/quota/providers/exe-dev.js +1 -1
- package/server/lib/quota/providers/google/index.js +1 -1
- package/server/lib/quota/providers/hyper.js +1 -1
- package/server/lib/quota/providers/kimi.js +116 -36
- package/server/lib/quota/providers/kimi.test.js +128 -3
- package/server/lib/quota/providers/opencode-go.js +144 -21
- package/server/lib/quota/providers/opencode-go.test.js +229 -6
- package/server/lib/quota/providers/openrouter.js +1 -1
- package/server/lib/quota/providers/zenmux.js +1 -1
- package/server/lib/quota/utils/auth.js +1 -0
- package/server/lib/quota/utils/transformers.js +2 -6
- package/server/lib/relay/DOCUMENTATION.md +18 -3
- package/server/lib/relay/e2ee.js +7 -7
- package/server/lib/relay/host-client.js +84 -13
- package/server/lib/relay/host-client.test.js +201 -1
- package/server/lib/relay/identity.js +6 -29
- package/server/lib/relay/identity.test.js +18 -28
- package/server/lib/relay/key-store.js +141 -0
- package/server/lib/relay/key-store.test.js +138 -0
- package/server/lib/relay/service.js +8 -8
- package/server/lib/relay/service.test.js +10 -5
- package/server/lib/relay/signing-key.js +13 -36
- package/server/lib/relay/tunnel-codec.js +10 -10
- package/server/lib/route-module-exports.test.js +22 -0
- package/server/lib/routing/DOCUMENTATION.md +23 -3
- package/server/lib/routing/cache.js +87 -0
- package/server/lib/routing/defaults.js +11 -0
- package/server/lib/routing/history.js +12 -4
- package/server/lib/routing/runtime.js +39 -3
- package/server/lib/routing/runtime.test.js +81 -2
- package/server/lib/routing/store.js +12 -1
- package/server/lib/routing/store.test.js +13 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +59 -1
- package/server/lib/scheduled-tasks/existing-session.js +119 -0
- package/server/lib/scheduled-tasks/existing-session.test.js +350 -0
- package/server/lib/scheduled-tasks/loops.js +1 -8
- package/server/lib/scheduled-tasks/routes.js +8 -8
- package/server/lib/scheduled-tasks/runtime.js +103 -13
- package/server/lib/scheduled-tasks/runtime.test.js +48 -0
- package/server/lib/scheduled-tasks/service.js +33 -11
- package/server/lib/scheduled-tasks/service.test.js +128 -2
- package/server/lib/scheduled-tasks/session-defaults.js +27 -0
- package/server/lib/scheduled-tasks/session-defaults.test.js +19 -0
- package/server/lib/session-assist/context.js +1 -0
- package/server/lib/session-folders/routes.js +2 -2
- package/server/lib/session-goal/objectives.js +1 -1
- package/server/lib/session-knowledge/routes.js +2 -2
- package/server/lib/session-knowledge/runtime.js +1 -2
- package/server/lib/session-work/DOCUMENTATION.md +39 -14
- package/server/lib/session-work/questions.js +31 -2
- package/server/lib/session-work/runtime.js +22 -13
- package/server/lib/session-work/runtime.test.js +43 -26
- package/server/lib/session-work/state.js +10 -4
- package/server/lib/session-work/state.test.js +8 -1
- package/server/lib/shared/DOCUMENTATION.md +89 -0
- package/server/lib/shared/guards.js +53 -0
- package/server/lib/shared/guards.test.js +112 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +10 -8
- package/server/lib/skills-catalog/cache.js +3 -2
- package/server/lib/skills-catalog/curated-sources.js +5 -1
- package/server/lib/skills-catalog/git.js +107 -20
- package/server/lib/skills-catalog/git.test.js +78 -0
- package/server/lib/skills-catalog/install.js +21 -12
- package/server/lib/skills-catalog/scan.js +25 -15
- package/server/lib/skills-catalog/source.js +26 -1
- package/server/lib/skills-catalog/source.test.js +27 -0
- package/server/lib/source-control/DOCUMENTATION.md +2 -1
- package/server/lib/source-control/audit-storage.js +7 -6
- package/server/lib/source-control/audit-storage.test.js +11 -0
- package/server/lib/source-control/binding-service.js +2 -5
- package/server/lib/source-control/change-request-worktree.js +90 -0
- package/server/lib/source-control/change-request-worktree.test.js +121 -0
- package/server/lib/source-control/mutation-executor.js +1 -1
- package/server/lib/source-control/mutation-storage.js +11 -5
- package/server/lib/source-control/provider-instance.js +1 -2
- package/server/lib/source-control/repository-identity.js +1 -1
- package/server/lib/source-control/routes.js +14 -2
- package/server/lib/spaces/DOCUMENTATION.md +42 -17
- package/server/lib/spaces/command-stream.test.js +15 -0
- package/server/lib/spaces/dispatcher.js +7 -0
- package/server/lib/spaces/dispatcher.test.js +17 -0
- package/server/lib/spaces/hardening.js +2 -2
- package/server/lib/spaces/hardening.test.js +1 -1
- package/server/lib/spaces/host.js +2 -0
- package/server/lib/spaces/journey.js +66 -5
- package/server/lib/spaces/journey.test.js +95 -4
- package/server/lib/spaces/layout.js +1 -1
- package/server/lib/spaces/places/docker-disk.js +38 -14
- package/server/lib/spaces/places/docker-disk.test.js +80 -1
- package/server/lib/spaces/places/docker.js +35 -11
- package/server/lib/spaces/places/fake-docker.js +22 -3
- package/server/lib/spaces/places/memory-place.js +3 -0
- package/server/lib/spaces/places/packed.docker.live.test.js +1 -1
- package/server/lib/spaces/routes.js +5 -0
- package/server/lib/spaces/routes.test.js +4 -2
- package/server/lib/spaces/run-command.js +9 -2
- package/server/lib/spaces/tools.js +4 -5
- package/server/lib/spaces/tools.test.js +2 -2
- package/server/lib/terminal/runtime.js +18 -6
- package/server/lib/terminal/runtime.test.js +66 -0
- package/server/lib/tts/index.js +3 -0
- package/server/lib/tts/service.js +4 -2
- package/server/lib/walkthrough/DOCUMENTATION.md +3 -1
- package/server/lib/walkthrough/index.js +2 -3
- package/server/lib/walkthrough/pr-source.test.js +22 -0
- package/server/lib/walkthrough/sources.js +6 -1
- package/server/opencode-proxy.test.js +127 -0
- package/bin/lib/commands-startup.test.js +0 -15
- package/dist/assets/CodeMirrorCommentWidgets-CVDSjcrP.js +0 -1
- package/dist/assets/DiffView-Ct7vL3fD.js +0 -4
- package/dist/assets/FilesView-DT0uEYgT.js +0 -17
- package/dist/assets/GuestSurfacePane-DVvk1VPu.js +0 -1
- package/dist/assets/LinearIssuesView-C10MwA9h.js +0 -1
- package/dist/assets/MarkdownImageGallery-BbN_K_3d.js +0 -1
- package/dist/assets/MarkdownRendererImpl-BLt2Tkk9.js +0 -10
- package/dist/assets/OnboardingScreen-Bgp5A3WB.js +0 -1
- package/dist/assets/PierreDiffViewer-CWJLj-S-.js +0 -210
- package/dist/assets/PlanView-DsRWnsOI.js +0 -5
- package/dist/assets/PublishDialog-CCeKloRu.js +0 -2
- package/dist/assets/SettingsView-BuPPsNPp.js +0 -30
- package/dist/assets/SettingsWindow-CM5fL8Ot.js +0 -1
- package/dist/assets/SubmoduleDiffSummary-CfsMcfiQ.js +0 -1
- package/dist/assets/ToolOutputDialog-mZrfSofz.js +0 -15
- package/dist/assets/ToolPartDiffPreview-BUGCRYEL.js +0 -8
- package/dist/assets/WalkthroughView-ClBo7PPd.js +0 -4
- package/dist/assets/appThemeRegistry-CxzMxR-H.js +0 -1
- package/dist/assets/de-CBla4hjd.js +0 -14
- package/dist/assets/es-BmnZtxTD.js +0 -20
- package/dist/assets/fileTreeStatus-BSHFIGEg.js +0 -1
- package/dist/assets/fr-OVKIBUZO.js +0 -18
- package/dist/assets/index-CKCFKmjF.js +0 -1
- package/dist/assets/index-Dbx4V4GN.css +0 -1
- package/dist/assets/index-vQ1Pg1cr.js +0 -61
- package/dist/assets/ja-6v-yr-gE.js +0 -20
- package/dist/assets/ko-I_ZLvgQ9.js +0 -20
- package/dist/assets/main-CmO4dRtR.js +0 -2
- package/dist/assets/main-b9vqhlYM.js +0 -999
- package/dist/assets/markdownCore-BPEZRQfP.js +0 -7
- package/dist/assets/miniChat-B5Oi7B-6.js +0 -2
- package/dist/assets/mobile-CVtShif9.js +0 -2
- package/dist/assets/nl-DcvIJf46.js +0 -20
- package/dist/assets/patchFileDiff-CW3wCH1O.js +0 -13
- package/dist/assets/pl-JxCUl0sd.js +0 -20
- package/dist/assets/pt-BR-L89mjja2.js +0 -20
- package/dist/assets/relocateRestoredSession-DYh_9oBa.js +0 -1
- package/dist/assets/renderElectronMiniChatApp-BD-1JHSg.js +0 -2
- package/dist/assets/renderMobileApp-7s5InXt1.js +0 -25
- package/dist/assets/runtimeEndpointReset-DqLJC6L1.js +0 -53
- package/dist/assets/shikiHighlight-CXgZnvHh.js +0 -1
- package/dist/assets/space-apply-1trGVIl8.js +0 -1
- package/dist/assets/space-close-y7j-NuBv.js +0 -1
- package/dist/assets/tr-ka5J1flt.js +0 -11
- package/dist/assets/uk-PyGe6Q_u.js +0 -20
- package/dist/assets/useInlineCommentController-CrjSGCpi.js +0 -2
- package/dist/assets/vendor-codemirror-lang-python-B7ClrMIT.js +0 -3
- package/dist/assets/vendor-codemirror-lang-xml-C8tGnS64.js +0 -1
- package/dist/assets/vendor-codemirror-language-data-q2bKO1EM.js +0 -2
- package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +0 -1
- package/dist/assets/vendor-katex-CHD9BdDO.js +0 -257
- package/dist/assets/vendor-katex-Cp579UoJ.css +0 -1
- package/dist/assets/zh-CN-B0P2tybJ.js +0 -20
- package/dist/assets/zh-TW-Bs9C5cN8.js +0 -20
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
SKILL_SCOPE,
|
|
8
8
|
ensureDirs,
|
|
9
9
|
parseMdFile,
|
|
10
|
+
parseMdFileAsync,
|
|
10
11
|
writeMdFile,
|
|
11
12
|
readConfigLayers,
|
|
12
13
|
readConfig,
|
|
@@ -92,8 +93,8 @@ function getProjectAgentsSkillPath(workingDirectory, skillName) {
|
|
|
92
93
|
return path.join(getProjectAgentsSkillDir(workingDirectory, skillName), 'SKILL.md');
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
function getSkillScope(skillName, workingDirectory) {
|
|
96
|
-
const discovered = discoverSkills(workingDirectory).find((skill) => skill.name === skillName);
|
|
96
|
+
async function getSkillScope(skillName, workingDirectory) {
|
|
97
|
+
const discovered = (await discoverSkills(workingDirectory)).find((skill) => skill.name === skillName);
|
|
97
98
|
if (discovered?.path) {
|
|
98
99
|
return { scope: discovered.scope || null, path: discovered.path, source: discovered.source || null };
|
|
99
100
|
}
|
|
@@ -128,8 +129,8 @@ function getSkillScope(skillName, workingDirectory) {
|
|
|
128
129
|
return { scope: null, path: null, source: null };
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
function getSkillWritePath(skillName, workingDirectory, requestedScope) {
|
|
132
|
-
const existing = getSkillScope(skillName, workingDirectory);
|
|
132
|
+
async function getSkillWritePath(skillName, workingDirectory, requestedScope) {
|
|
133
|
+
const existing = await getSkillScope(skillName, workingDirectory);
|
|
133
134
|
if (existing.path) {
|
|
134
135
|
return existing;
|
|
135
136
|
}
|
|
@@ -150,15 +151,34 @@ function getSkillWritePath(skillName, workingDirectory, requestedScope) {
|
|
|
150
151
|
};
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
function discoverSkills(workingDirectory) {
|
|
154
|
+
async function discoverSkills(workingDirectory) {
|
|
154
155
|
const skills = new Map();
|
|
155
156
|
|
|
157
|
+
// A root can come up twice in one discovery: a home directory opened as a
|
|
158
|
+
// project lists `~/.claude/skills` and `~/.agents/skills` once as user
|
|
159
|
+
// skills and again as project skills. Each root is walked and each SKILL.md
|
|
160
|
+
// read once; the second pass still re-adds them in the same order, so the
|
|
161
|
+
// project scope wins as before.
|
|
162
|
+
const walkedRoots = new Map();
|
|
163
|
+
const parsedFiles = new Map();
|
|
164
|
+
const walkRoot = (root) => {
|
|
165
|
+
const key = path.resolve(root);
|
|
166
|
+
if (!walkedRoots.has(key)) walkedRoots.set(key, walkSkillMdFiles(root));
|
|
167
|
+
return walkedRoots.get(key);
|
|
168
|
+
};
|
|
169
|
+
const addSkills = async (root, scope, source) => {
|
|
170
|
+
for (const skillMdPath of await walkRoot(root)) {
|
|
171
|
+
if (!parsedFiles.has(skillMdPath)) {
|
|
172
|
+
parsedFiles.set(skillMdPath, await parseMdFileAsync(skillMdPath).catch(() => null));
|
|
173
|
+
}
|
|
174
|
+
addSkillFromMdFile(skills, skillMdPath, parsedFiles.get(skillMdPath), scope, source);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
156
178
|
for (const externalRootName of ['.claude', '.agents']) {
|
|
157
179
|
const homeRoot = path.join(os.homedir(), externalRootName, 'skills');
|
|
158
180
|
const source = externalRootName === '.agents' ? 'agents' : 'claude';
|
|
159
|
-
|
|
160
|
-
addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.USER, source);
|
|
161
|
-
}
|
|
181
|
+
await addSkills(homeRoot, SKILL_SCOPE.USER, source);
|
|
162
182
|
}
|
|
163
183
|
|
|
164
184
|
if (workingDirectory) {
|
|
@@ -168,9 +188,7 @@ function discoverSkills(workingDirectory) {
|
|
|
168
188
|
for (const externalRootName of ['.claude', '.agents']) {
|
|
169
189
|
const source = externalRootName === '.agents' ? 'agents' : 'claude';
|
|
170
190
|
const externalSkillsRoot = path.join(ancestor, externalRootName, 'skills');
|
|
171
|
-
|
|
172
|
-
addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.PROJECT, source);
|
|
173
|
-
}
|
|
191
|
+
await addSkills(externalSkillsRoot, SKILL_SCOPE.PROJECT, source);
|
|
174
192
|
}
|
|
175
193
|
}
|
|
176
194
|
}
|
|
@@ -183,13 +201,11 @@ function discoverSkills(workingDirectory) {
|
|
|
183
201
|
for (const dir of configDirectories) {
|
|
184
202
|
for (const subDir of ['skill', 'skills']) {
|
|
185
203
|
const root = path.join(dir, subDir);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
addSkillFromMdFile(skills, skillMdPath, scope, 'opencode');
|
|
192
|
-
}
|
|
204
|
+
const isUserConfigDir = dir === OPENCODE_CONFIG_DIR
|
|
205
|
+
|| dir === homeOpencodeDir
|
|
206
|
+
|| (customConfigDir && dir === customConfigDir);
|
|
207
|
+
const scope = isUserConfigDir ? SKILL_SCOPE.USER : SKILL_SCOPE.PROJECT;
|
|
208
|
+
await addSkills(root, scope, 'opencode');
|
|
193
209
|
}
|
|
194
210
|
}
|
|
195
211
|
|
|
@@ -214,9 +230,7 @@ function discoverSkills(workingDirectory) {
|
|
|
214
230
|
const resolved = path.isAbsolute(expanded)
|
|
215
231
|
? path.resolve(expanded)
|
|
216
232
|
: path.resolve(workingDirectory || process.cwd(), expanded);
|
|
217
|
-
|
|
218
|
-
addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.PROJECT, 'opencode');
|
|
219
|
-
}
|
|
233
|
+
await addSkills(resolved, SKILL_SCOPE.PROJECT, 'opencode');
|
|
220
234
|
}
|
|
221
235
|
|
|
222
236
|
const cacheCandidates = [];
|
|
@@ -232,9 +246,7 @@ function discoverSkills(workingDirectory) {
|
|
|
232
246
|
for (const entry of entries) {
|
|
233
247
|
if (!entry.isDirectory()) continue;
|
|
234
248
|
const skillRoot = path.join(cacheRoot, entry.name);
|
|
235
|
-
|
|
236
|
-
addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.USER, 'opencode');
|
|
237
|
-
}
|
|
249
|
+
await addSkills(skillRoot, SKILL_SCOPE.USER, 'opencode');
|
|
238
250
|
}
|
|
239
251
|
}
|
|
240
252
|
|
|
@@ -308,7 +320,10 @@ function applyModelInvocation(frontmatter, disabled) {
|
|
|
308
320
|
}
|
|
309
321
|
}
|
|
310
322
|
|
|
311
|
-
|
|
323
|
+
// `includeSupportingFiles: false` leaves `md.supportingFiles` out instead of
|
|
324
|
+
// walking the skill folder: the skills list has no use for it, and a folder
|
|
325
|
+
// with a bundled `.venv` or `node_modules` costs seconds to walk.
|
|
326
|
+
async function getSkillSources(skillName, workingDirectory, discoveredSkill = null, { includeSupportingFiles = true } = {}) {
|
|
312
327
|
const isReadableFile = (filePath) => {
|
|
313
328
|
if (!filePath) return false;
|
|
314
329
|
try {
|
|
@@ -339,7 +354,7 @@ function getSkillSources(skillName, workingDirectory, discoveredSkill = null) {
|
|
|
339
354
|
|
|
340
355
|
const matchedDiscovered = discoveredSkill && discoveredSkill.name === skillName
|
|
341
356
|
? discoveredSkill
|
|
342
|
-
: discoverSkills(workingDirectory).find((skill) => skill.name === skillName);
|
|
357
|
+
: (await discoverSkills(workingDirectory)).find((skill) => skill.name === skillName);
|
|
343
358
|
const discoveredDescription =
|
|
344
359
|
matchedDiscovered && typeof matchedDiscovered.description === 'string'
|
|
345
360
|
? matchedDiscovered.description
|
|
@@ -410,7 +425,6 @@ function getSkillSources(skillName, workingDirectory, discoveredSkill = null) {
|
|
|
410
425
|
scope: mdScope,
|
|
411
426
|
source: mdSource,
|
|
412
427
|
fields: isBuiltInDiscovered ? ['description', 'instructions'] : [],
|
|
413
|
-
supportingFiles: [],
|
|
414
428
|
name: matchedDiscovered?.name || skillName,
|
|
415
429
|
description: discoveredDescription,
|
|
416
430
|
instructions: isBuiltInDiscovered ? discoveredContent : '',
|
|
@@ -444,7 +458,7 @@ function getSkillSources(skillName, workingDirectory, discoveredSkill = null) {
|
|
|
444
458
|
};
|
|
445
459
|
|
|
446
460
|
if (mdExists && mdDir) {
|
|
447
|
-
const { frontmatter, body } =
|
|
461
|
+
const { frontmatter, body } = await parseMdFileAsync(mdPath);
|
|
448
462
|
sources.md.fields = Object.keys(frontmatter);
|
|
449
463
|
sources.md.description = frontmatter.description || '';
|
|
450
464
|
sources.md.name = frontmatter.name || skillName;
|
|
@@ -455,7 +469,9 @@ function getSkillSources(skillName, workingDirectory, discoveredSkill = null) {
|
|
|
455
469
|
} else {
|
|
456
470
|
sources.md.instructions = '';
|
|
457
471
|
}
|
|
458
|
-
|
|
472
|
+
}
|
|
473
|
+
if (includeSupportingFiles) {
|
|
474
|
+
sources.md.supportingFiles = mdExists && mdDir ? await listSkillSupportingFiles(mdDir) : [];
|
|
459
475
|
}
|
|
460
476
|
|
|
461
477
|
return sources;
|
|
@@ -474,11 +490,25 @@ function assertValidSkillName(skillName) {
|
|
|
474
490
|
}
|
|
475
491
|
}
|
|
476
492
|
|
|
493
|
+
// Create, edit and rename check the disk, wait on discovery, then write. Run
|
|
494
|
+
// one at a time per process, the way they did while discovery was
|
|
495
|
+
// synchronous, so a check cannot go stale before its own write.
|
|
496
|
+
let skillWriteQueue = Promise.resolve();
|
|
497
|
+
function serializeSkillWrite(write) {
|
|
498
|
+
const run = skillWriteQueue.then(write, write);
|
|
499
|
+
skillWriteQueue = run.catch(() => {});
|
|
500
|
+
return run;
|
|
501
|
+
}
|
|
502
|
+
|
|
477
503
|
function createSkill(skillName, config, workingDirectory, scope) {
|
|
504
|
+
return serializeSkillWrite(() => createSkillNow(skillName, config, workingDirectory, scope));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function createSkillNow(skillName, config, workingDirectory, scope) {
|
|
478
508
|
ensureDirs();
|
|
479
509
|
assertValidSkillName(skillName);
|
|
480
510
|
|
|
481
|
-
const existing = getSkillScope(skillName, workingDirectory);
|
|
511
|
+
const existing = await getSkillScope(skillName, workingDirectory);
|
|
482
512
|
if (existing.path) {
|
|
483
513
|
throw new Error(`Skill ${skillName} already exists at ${existing.path}`);
|
|
484
514
|
}
|
|
@@ -534,7 +564,16 @@ function createSkill(skillName, config, workingDirectory, scope) {
|
|
|
534
564
|
applyModelInvocation(frontmatter, true);
|
|
535
565
|
}
|
|
536
566
|
|
|
537
|
-
|
|
567
|
+
// `wx` fails if SKILL.md appeared since the check above, from another
|
|
568
|
+
// process or a client racing this one, instead of overwriting it.
|
|
569
|
+
try {
|
|
570
|
+
writeMdFile(targetPath, frontmatter, instructions || '', { flag: 'wx' });
|
|
571
|
+
} catch (error) {
|
|
572
|
+
if (error?.code === 'EEXIST') {
|
|
573
|
+
throw new Error(`Skill ${skillName} already exists at ${targetPath}`);
|
|
574
|
+
}
|
|
575
|
+
throw error;
|
|
576
|
+
}
|
|
538
577
|
|
|
539
578
|
if (supportingFiles && Array.isArray(supportingFiles)) {
|
|
540
579
|
for (const file of supportingFiles) {
|
|
@@ -548,6 +587,10 @@ function createSkill(skillName, config, workingDirectory, scope) {
|
|
|
548
587
|
}
|
|
549
588
|
|
|
550
589
|
function updateSkill(skillName, updates, workingDirectory, targetPath = null) {
|
|
590
|
+
return serializeSkillWrite(() => updateSkillNow(skillName, updates, workingDirectory, targetPath));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async function updateSkillNow(skillName, updates, workingDirectory, targetPath = null) {
|
|
551
594
|
ensureDirs();
|
|
552
595
|
|
|
553
596
|
const requestedPath = typeof targetPath === 'string' && targetPath.trim()
|
|
@@ -555,7 +598,7 @@ function updateSkill(skillName, updates, workingDirectory, targetPath = null) {
|
|
|
555
598
|
: null;
|
|
556
599
|
const existing = requestedPath && fs.existsSync(requestedPath)
|
|
557
600
|
? { scope: null, path: requestedPath, source: null }
|
|
558
|
-
: getSkillScope(skillName, workingDirectory);
|
|
601
|
+
: await getSkillScope(skillName, workingDirectory);
|
|
559
602
|
if (!existing.path) {
|
|
560
603
|
throw new Error(`Skill "${skillName}" not found`);
|
|
561
604
|
}
|
|
@@ -724,6 +767,10 @@ function isManagedSkillPath(skillMdPath, workingDirectory) {
|
|
|
724
767
|
}
|
|
725
768
|
|
|
726
769
|
function renameSkill(oldName, newName, workingDirectory) {
|
|
770
|
+
return serializeSkillWrite(() => renameSkillNow(oldName, newName, workingDirectory));
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async function renameSkillNow(oldName, newName, workingDirectory) {
|
|
727
774
|
ensureDirs();
|
|
728
775
|
assertValidSkillName(newName);
|
|
729
776
|
|
|
@@ -731,7 +778,7 @@ function renameSkill(oldName, newName, workingDirectory) {
|
|
|
731
778
|
return;
|
|
732
779
|
}
|
|
733
780
|
|
|
734
|
-
const existing = getSkillScope(oldName, workingDirectory);
|
|
781
|
+
const existing = await getSkillScope(oldName, workingDirectory);
|
|
735
782
|
if (!existing.path) {
|
|
736
783
|
throw new Error(`Skill "${oldName}" not found`);
|
|
737
784
|
}
|
|
@@ -753,7 +800,7 @@ function renameSkill(oldName, newName, workingDirectory) {
|
|
|
753
800
|
throw new Error(`Skill "${oldName}" does not match ${existing.path}`);
|
|
754
801
|
}
|
|
755
802
|
|
|
756
|
-
const conflict = getSkillScope(newName, workingDirectory);
|
|
803
|
+
const conflict = await getSkillScope(newName, workingDirectory);
|
|
757
804
|
if (conflict.path) {
|
|
758
805
|
throw new Error(`Skill ${newName} already exists at ${conflict.path}`);
|
|
759
806
|
}
|
|
@@ -4,7 +4,7 @@ import fsPromises from 'fs/promises';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import yaml from 'yaml';
|
|
7
|
-
import { discoverSkills, getSkillSources, mergeDiscoveredSkills, renameSkill, updateSkill } from './skills.js';
|
|
7
|
+
import { createSkill, discoverSkills, getSkillSources, mergeDiscoveredSkills, renameSkill, updateSkill } from './skills.js';
|
|
8
8
|
|
|
9
9
|
describe('skills', () => {
|
|
10
10
|
it('merges locally discovered skills missing from OpenCode live discovery', () => {
|
|
@@ -48,7 +48,7 @@ describe('skills', () => {
|
|
|
48
48
|
'utf8',
|
|
49
49
|
);
|
|
50
50
|
|
|
51
|
-
const discovered = discoverSkills(tempRoot);
|
|
51
|
+
const discovered = await discoverSkills(tempRoot);
|
|
52
52
|
const match = discovered.find((skill) => skill.name === 'repo-local-skill');
|
|
53
53
|
|
|
54
54
|
expect(match).toEqual({
|
|
@@ -63,8 +63,163 @@ describe('skills', () => {
|
|
|
63
63
|
}
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
describe('discovery over a tree with nested skills and links', () => {
|
|
67
|
+
// Expected values were recorded from the synchronous discovery on main
|
|
68
|
+
// before it became async; they must not move.
|
|
69
|
+
const buildTree = async () => {
|
|
70
|
+
const root = await fsPromises.realpath(await fsPromises.mkdtemp(path.join(os.tmpdir(), 'oc-skill-equiv-')));
|
|
71
|
+
const write = async (relative, frontmatter) => {
|
|
72
|
+
const file = path.join(root, relative);
|
|
73
|
+
await fsPromises.mkdir(path.dirname(file), { recursive: true });
|
|
74
|
+
await fsPromises.writeFile(file, `---\n${frontmatter}\n---\nBody\n`, 'utf8');
|
|
75
|
+
};
|
|
76
|
+
const skill = (relative, name) => write(relative, `name: ${name}\ndescription: ${name} description`);
|
|
77
|
+
// A junction needs no elevation on Windows; the type is ignored on POSIX.
|
|
78
|
+
const link = async (target, relative) => {
|
|
79
|
+
await fsPromises.mkdir(path.dirname(path.join(root, relative)), { recursive: true });
|
|
80
|
+
await fsPromises.symlink(path.join(root, target), path.join(root, relative), process.platform === 'win32' ? 'junction' : 'dir');
|
|
81
|
+
};
|
|
82
|
+
await skill('home/.agents/skills/alpha/SKILL.md', 'alpha');
|
|
83
|
+
await skill('home/.agents/skills/alpha/nested/inner/SKILL.md', 'alpha-inner');
|
|
84
|
+
await skill('home/.agents/skills/group/beta/SKILL.md', 'beta');
|
|
85
|
+
await write('home/.agents/skills/broken/SKILL.md', 'description: no name');
|
|
86
|
+
await skill('outside/gamma/SKILL.md', 'gamma');
|
|
87
|
+
await link('outside/gamma', 'home/.agents/skills/linked-gamma');
|
|
88
|
+
await link('home/.agents/skills', 'home/.agents/skills/loop/back');
|
|
89
|
+
await link('home/.agents/skills/alpha', 'home/.claude/skills/alpha');
|
|
90
|
+
await skill('home/.claude/skills/delta/SKILL.md', 'delta');
|
|
91
|
+
await skill('home/.claude/skills/delta/.venv/lib/pkg/SKILL.md', 'venv-skill');
|
|
92
|
+
await fsPromises.mkdir(path.join(root, 'home/work/project/.git'), { recursive: true });
|
|
93
|
+
await skill('home/work/project/.agents/skills/proj/SKILL.md', 'proj');
|
|
94
|
+
await skill('home/work/project/.opencode/skills/oc/SKILL.md', 'oc');
|
|
95
|
+
await skill('home/work/project/.claude/skills/delta/SKILL.md', 'delta');
|
|
96
|
+
return root;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const withHome = async (home, run) => {
|
|
100
|
+
const previous = { HOME: process.env.HOME, USERPROFILE: process.env.USERPROFILE };
|
|
101
|
+
process.env.HOME = home;
|
|
102
|
+
process.env.USERPROFILE = home;
|
|
103
|
+
try {
|
|
104
|
+
return await run();
|
|
105
|
+
} finally {
|
|
106
|
+
for (const [key, value] of Object.entries(previous)) {
|
|
107
|
+
if (value === undefined) delete process.env[key];
|
|
108
|
+
else process.env[key] = value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Skills of the machine running the tests (global config, caches) are
|
|
114
|
+
// outside the tree and left out.
|
|
115
|
+
const discoveredIn = async (root, directory) => (await discoverSkills(directory))
|
|
116
|
+
.filter((skill) => skill.path.startsWith(root))
|
|
117
|
+
.map((skill) => [skill.name, path.relative(root, skill.path).split(path.sep).join('/'), skill.scope, skill.source])
|
|
118
|
+
.sort((a, b) => a[0].localeCompare(b[0]));
|
|
119
|
+
|
|
120
|
+
const userSkills = (scope) => [
|
|
121
|
+
['alpha', 'home/.agents/skills/alpha/SKILL.md', scope, 'agents'],
|
|
122
|
+
['alpha-inner', 'home/.agents/skills/alpha/nested/inner/SKILL.md', scope, 'agents'],
|
|
123
|
+
['beta', 'home/.agents/skills/group/beta/SKILL.md', scope, 'agents'],
|
|
124
|
+
['delta', 'home/.claude/skills/delta/SKILL.md', scope, 'claude'],
|
|
125
|
+
['gamma', 'home/.agents/skills/linked-gamma/SKILL.md', scope, 'agents'],
|
|
126
|
+
['venv-skill', 'home/.claude/skills/delta/.venv/lib/pkg/SKILL.md', scope, 'claude'],
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
it('finds the same skills as before for a project, a home-directory project and no project', async () => {
|
|
130
|
+
const root = await buildTree();
|
|
131
|
+
const home = path.join(root, 'home');
|
|
132
|
+
try {
|
|
133
|
+
await withHome(home, async () => {
|
|
134
|
+
expect(await discoveredIn(root, path.join(home, 'work', 'project'))).toEqual([
|
|
135
|
+
['alpha', 'home/.agents/skills/alpha/SKILL.md', 'user', 'agents'],
|
|
136
|
+
['alpha-inner', 'home/.agents/skills/alpha/nested/inner/SKILL.md', 'user', 'agents'],
|
|
137
|
+
['beta', 'home/.agents/skills/group/beta/SKILL.md', 'user', 'agents'],
|
|
138
|
+
['delta', 'home/work/project/.claude/skills/delta/SKILL.md', 'project', 'claude'],
|
|
139
|
+
['gamma', 'home/.agents/skills/linked-gamma/SKILL.md', 'user', 'agents'],
|
|
140
|
+
['oc', 'home/work/project/.opencode/skills/oc/SKILL.md', 'project', 'opencode'],
|
|
141
|
+
['proj', 'home/work/project/.agents/skills/proj/SKILL.md', 'project', 'agents'],
|
|
142
|
+
['venv-skill', 'home/.claude/skills/delta/.venv/lib/pkg/SKILL.md', 'user', 'claude'],
|
|
143
|
+
]);
|
|
144
|
+
// The home directory's own skill roots are read once but still
|
|
145
|
+
// land as project skills, the way the second pass always left them.
|
|
146
|
+
expect(await discoveredIn(root, home)).toEqual(userSkills('project'));
|
|
147
|
+
expect(await discoveredIn(root, null)).toEqual(userSkills('user'));
|
|
148
|
+
});
|
|
149
|
+
} finally {
|
|
150
|
+
await fsPromises.rm(root, { recursive: true, force: true });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('lets other work run while it reads the tree', async () => {
|
|
155
|
+
const root = await buildTree();
|
|
156
|
+
const events = [];
|
|
157
|
+
try {
|
|
158
|
+
await withHome(path.join(root, 'home'), async () => {
|
|
159
|
+
const discovery = discoverSkills(path.join(root, 'home', 'work', 'project')).then(() => events.push('discovered'));
|
|
160
|
+
setImmediate(() => events.push('other work'));
|
|
161
|
+
await discovery;
|
|
162
|
+
});
|
|
163
|
+
expect(events).toEqual(['other work', 'discovered']);
|
|
164
|
+
} finally {
|
|
165
|
+
await fsPromises.rm(root, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('writes that race each other', () => {
|
|
171
|
+
const makeProject = async () => {
|
|
172
|
+
const tempRoot = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'oc-skills-race-'));
|
|
173
|
+
const projectRoot = path.join(tempRoot, 'project');
|
|
174
|
+
await fsPromises.mkdir(path.join(projectRoot, '.git'), { recursive: true });
|
|
175
|
+
return { tempRoot, projectRoot };
|
|
176
|
+
};
|
|
177
|
+
const skillPath = (projectRoot, name) => path.join(projectRoot, '.opencode', 'skills', name, 'SKILL.md');
|
|
178
|
+
// `.agents` project skills: the existence check only finds them through
|
|
179
|
+
// discovery, so two creates both pass it unless the write itself refuses.
|
|
180
|
+
const agentsSkillPath = (projectRoot, name) => path.join(projectRoot, '.agents', 'skills', name, 'SKILL.md');
|
|
181
|
+
|
|
182
|
+
it('lets exactly one of two simultaneous creates of the same name succeed', async () => {
|
|
183
|
+
const { tempRoot, projectRoot } = await makeProject();
|
|
184
|
+
try {
|
|
185
|
+
const results = await Promise.allSettled([
|
|
186
|
+
createSkill('race-skill', { description: 'First', source: 'agents' }, projectRoot, 'project'),
|
|
187
|
+
createSkill('race-skill', { description: 'Second', source: 'agents' }, projectRoot, 'project'),
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
expect(results.map((result) => result.status).sort()).toEqual(['fulfilled', 'rejected']);
|
|
191
|
+
expect(results.find((result) => result.status === 'rejected').reason.message).toMatch(/already exists/);
|
|
192
|
+
const written = await fsPromises.readFile(agentsSkillPath(projectRoot, 'race-skill'), 'utf8');
|
|
193
|
+
expect(written).toContain(`description: ${results[0].status === 'fulfilled' ? 'First' : 'Second'}`);
|
|
194
|
+
} finally {
|
|
195
|
+
await fsPromises.rm(tempRoot, { recursive: true, force: true });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('does not write an edit into the folder of a skill renamed meanwhile', async () => {
|
|
200
|
+
const { tempRoot, projectRoot } = await makeProject();
|
|
201
|
+
const oldDir = path.dirname(skillPath(projectRoot, 'moving-skill'));
|
|
202
|
+
try {
|
|
203
|
+
await createSkill('moving-skill', { description: 'Before' }, projectRoot, 'project');
|
|
204
|
+
|
|
205
|
+
const [renamed, edited] = await Promise.allSettled([
|
|
206
|
+
renameSkill('moving-skill', 'moved-skill', projectRoot),
|
|
207
|
+
updateSkill('moving-skill', { description: 'Edited' }, projectRoot),
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
expect(renamed.status).toBe('fulfilled');
|
|
211
|
+
expect(edited.status).toBe('rejected');
|
|
212
|
+
expect(edited.reason.message).toMatch(/not found/);
|
|
213
|
+
expect(fs.existsSync(oldDir)).toBe(false);
|
|
214
|
+
expect(await fsPromises.readFile(skillPath(projectRoot, 'moved-skill'), 'utf8')).toContain('description: Before');
|
|
215
|
+
} finally {
|
|
216
|
+
await fsPromises.rm(tempRoot, { recursive: true, force: true });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('resolves built-in OpenCode skill content without parsing virtual locations as files', async () => {
|
|
222
|
+
const sources = await getSkillSources(
|
|
68
223
|
'customize-opencode',
|
|
69
224
|
'/tmp/openchamber-skills-test-missing-project',
|
|
70
225
|
{
|
|
@@ -87,9 +242,9 @@ describe('skills', () => {
|
|
|
87
242
|
expect(sources.md.fields).toEqual(['description', 'instructions']);
|
|
88
243
|
});
|
|
89
244
|
|
|
90
|
-
it('clears file metadata when a discovered skill path is unreadable', () => {
|
|
245
|
+
it('clears file metadata when a discovered skill path is unreadable', async () => {
|
|
91
246
|
const missingPath = path.join(os.tmpdir(), 'openchamber-skills-test-missing-file', 'SKILL.md');
|
|
92
|
-
const sources = getSkillSources(
|
|
247
|
+
const sources = await getSkillSources(
|
|
93
248
|
'missing-agent-skill',
|
|
94
249
|
'/tmp/openchamber-skills-test-missing-project',
|
|
95
250
|
{
|
|
@@ -131,7 +286,7 @@ describe('skills', () => {
|
|
|
131
286
|
'utf8',
|
|
132
287
|
);
|
|
133
288
|
|
|
134
|
-
const sources = getSkillSources('example-skill', tempRoot, {
|
|
289
|
+
const sources = await getSkillSources('example-skill', tempRoot, {
|
|
135
290
|
name: 'example-skill',
|
|
136
291
|
path: skillPath,
|
|
137
292
|
scope: 'user',
|
|
@@ -167,24 +322,24 @@ describe('skills', () => {
|
|
|
167
322
|
['---', 'name: manual-skill', 'description: Manual', 'metadata:', ' team: core', '---', '', 'Body.', ''].join('\n'),
|
|
168
323
|
'utf8',
|
|
169
324
|
);
|
|
170
|
-
expect(getSkillSources('manual-skill', tempRoot, discovered).md.disableModelInvocation).toBe(false);
|
|
325
|
+
expect((await getSkillSources('manual-skill', tempRoot, discovered)).md.disableModelInvocation).toBe(false);
|
|
171
326
|
|
|
172
|
-
updateSkill('manual-skill', { disableModelInvocation: true }, tempRoot, skillPath);
|
|
327
|
+
await updateSkill('manual-skill', { disableModelInvocation: true }, tempRoot, skillPath);
|
|
173
328
|
expect(await readFrontmatter()).toEqual({
|
|
174
329
|
name: 'manual-skill',
|
|
175
330
|
description: 'Manual',
|
|
176
331
|
metadata: { team: 'core', 'opencode/autoinvoke': false },
|
|
177
332
|
'disable-model-invocation': true,
|
|
178
333
|
});
|
|
179
|
-
expect(getSkillSources('manual-skill', tempRoot, discovered).md.disableModelInvocation).toBe(true);
|
|
334
|
+
expect((await getSkillSources('manual-skill', tempRoot, discovered)).md.disableModelInvocation).toBe(true);
|
|
180
335
|
|
|
181
|
-
updateSkill('manual-skill', { disableModelInvocation: false }, tempRoot, skillPath);
|
|
336
|
+
await updateSkill('manual-skill', { disableModelInvocation: false }, tempRoot, skillPath);
|
|
182
337
|
expect(await readFrontmatter()).toEqual({
|
|
183
338
|
name: 'manual-skill',
|
|
184
339
|
description: 'Manual',
|
|
185
340
|
metadata: { team: 'core' },
|
|
186
341
|
});
|
|
187
|
-
expect(getSkillSources('manual-skill', tempRoot, discovered).md.disableModelInvocation).toBe(false);
|
|
342
|
+
expect((await getSkillSources('manual-skill', tempRoot, discovered)).md.disableModelInvocation).toBe(false);
|
|
188
343
|
} finally {
|
|
189
344
|
await fsPromises.rm(tempRoot, { recursive: true, force: true });
|
|
190
345
|
}
|
|
@@ -201,7 +356,7 @@ describe('skills', () => {
|
|
|
201
356
|
['---', 'name: read-skill', 'description: Read', ...frontmatterLines, '---', '', 'Body.', ''].join('\n'),
|
|
202
357
|
'utf8',
|
|
203
358
|
);
|
|
204
|
-
return getSkillSources('read-skill', tempRoot, discovered).md.disableModelInvocation;
|
|
359
|
+
return (await getSkillSources('read-skill', tempRoot, discovered)).md.disableModelInvocation;
|
|
205
360
|
};
|
|
206
361
|
|
|
207
362
|
try {
|
|
@@ -249,7 +404,7 @@ describe('skills', () => {
|
|
|
249
404
|
);
|
|
250
405
|
await fsPromises.writeFile(supportPath, 'supporting file contents\n', 'utf8');
|
|
251
406
|
|
|
252
|
-
renameSkill('original-skill', 'renamed-skill', projectRoot);
|
|
407
|
+
await renameSkill('original-skill', 'renamed-skill', projectRoot);
|
|
253
408
|
|
|
254
409
|
const renamedDir = path.join(projectRoot, '.opencode', 'skills', 'renamed-skill');
|
|
255
410
|
const renamedPath = path.join(renamedDir, 'SKILL.md');
|
|
@@ -259,7 +414,7 @@ describe('skills', () => {
|
|
|
259
414
|
expect(fs.existsSync(renamedPath)).toBe(true);
|
|
260
415
|
expect(fs.existsSync(renamedSupportPath)).toBe(true);
|
|
261
416
|
|
|
262
|
-
const sources = getSkillSources('renamed-skill', projectRoot, {
|
|
417
|
+
const sources = await getSkillSources('renamed-skill', projectRoot, {
|
|
263
418
|
name: 'renamed-skill',
|
|
264
419
|
path: renamedPath,
|
|
265
420
|
scope: 'project',
|
|
@@ -305,7 +460,7 @@ describe('skills', () => {
|
|
|
305
460
|
);
|
|
306
461
|
await fsPromises.chmod(skillPath, 0o444);
|
|
307
462
|
|
|
308
|
-
expect(
|
|
463
|
+
await expect(renameSkill('rollback-skill', 'rollback-skill-renamed', projectRoot)).rejects.toThrow();
|
|
309
464
|
|
|
310
465
|
expect(fs.existsSync(skillDir)).toBe(true);
|
|
311
466
|
expect(fs.existsSync(path.join(projectRoot, '.opencode', 'skills', 'rollback-skill-renamed'))).toBe(false);
|
|
@@ -390,11 +545,11 @@ describe('skills', () => {
|
|
|
390
545
|
'utf8',
|
|
391
546
|
);
|
|
392
547
|
|
|
393
|
-
expect(
|
|
394
|
-
expect(
|
|
395
|
-
expect(
|
|
396
|
-
expect(
|
|
397
|
-
expect(
|
|
548
|
+
await expect(renameSkill('managed-skill', 'Invalid_Name', projectRoot)).rejects.toThrow(/Invalid skill name/);
|
|
549
|
+
await expect(renameSkill('missing-skill', 'new-skill', projectRoot)).rejects.toThrow(/not found/);
|
|
550
|
+
await expect(renameSkill('managed-skill', 'taken-name', projectRoot)).rejects.toThrow(/already exists/);
|
|
551
|
+
await expect(renameSkill('folder-name', 'renamed-mismatch', projectRoot)).rejects.toThrow(/does not match/);
|
|
552
|
+
await expect(renameSkill('cache-skill', 'cache-skill-renamed', projectRoot)).rejects.toThrow(/managed skill directories/);
|
|
398
553
|
|
|
399
554
|
expect(fs.existsSync(managedDir)).toBe(true);
|
|
400
555
|
expect(fs.existsSync(cacheDir)).toBe(true);
|
|
@@ -33,8 +33,11 @@ export const createStartupPipelineRuntime = (dependencies) => {
|
|
|
33
33
|
terminalHeartbeatIntervalMs,
|
|
34
34
|
terminalRebindWindowMs,
|
|
35
35
|
terminalMaxRebindsPerWindow,
|
|
36
|
+
environmentRuntime = null,
|
|
36
37
|
setupProxy,
|
|
38
|
+
scheduleOpenCodeApiDetection,
|
|
37
39
|
bootstrapOpenCodeAtStartup,
|
|
40
|
+
onListenerReady = null,
|
|
38
41
|
staticRoutesRuntime,
|
|
39
42
|
process,
|
|
40
43
|
crypto,
|
|
@@ -53,7 +56,6 @@ export const createStartupPipelineRuntime = (dependencies) => {
|
|
|
53
56
|
port,
|
|
54
57
|
startupTunnelRequest,
|
|
55
58
|
onTunnelReady,
|
|
56
|
-
onListenerReady,
|
|
57
59
|
tunnelRuntimeContext,
|
|
58
60
|
attachSignals,
|
|
59
61
|
apiOnly,
|
|
@@ -75,6 +77,7 @@ export const createStartupPipelineRuntime = (dependencies) => {
|
|
|
75
77
|
TERMINAL_INPUT_WS_HEARTBEAT_INTERVAL_MS: terminalHeartbeatIntervalMs,
|
|
76
78
|
TERMINAL_INPUT_WS_REBIND_WINDOW_MS: terminalRebindWindowMs,
|
|
77
79
|
TERMINAL_INPUT_WS_MAX_REBINDS_PER_WINDOW: terminalMaxRebindsPerWindow,
|
|
80
|
+
environmentRuntime,
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
const dictationRuntime = createDictationRuntime({
|
|
@@ -134,7 +137,8 @@ export const createStartupPipelineRuntime = (dependencies) => {
|
|
|
134
137
|
onTunnelReady,
|
|
135
138
|
afterListening: async ({ activePort }) => {
|
|
136
139
|
tunnelRuntimeContext.setActivePort(activePort);
|
|
137
|
-
|
|
140
|
+
await onListenerReady?.({ activePort });
|
|
141
|
+
scheduleOpenCodeApiDetection();
|
|
138
142
|
await bootstrapOpenCodeAtStartup();
|
|
139
143
|
},
|
|
140
144
|
});
|
|
@@ -142,7 +146,6 @@ export const createStartupPipelineRuntime = (dependencies) => {
|
|
|
142
146
|
durationMs: performance.now() - pipelineStartedAt,
|
|
143
147
|
});
|
|
144
148
|
|
|
145
|
-
|
|
146
149
|
serverStartupRuntime.attachProcessHandlers({ attachSignals });
|
|
147
150
|
|
|
148
151
|
return {
|
|
@@ -31,6 +31,7 @@ describe('startup pipeline runtime', () => {
|
|
|
31
31
|
tunnelRuntimeContext: {
|
|
32
32
|
setActivePort: (port) => order.push(`port:${port}`),
|
|
33
33
|
},
|
|
34
|
+
scheduleOpenCodeApiDetection: vi.fn(),
|
|
34
35
|
bootstrapOpenCodeAtStartup: async () => {
|
|
35
36
|
order.push('bootstrap');
|
|
36
37
|
await bootstrapGate;
|
|
@@ -41,7 +42,7 @@ describe('startup pipeline runtime', () => {
|
|
|
41
42
|
attachSignals: false,
|
|
42
43
|
});
|
|
43
44
|
|
|
44
|
-
await Promise
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
45
46
|
expect(order).toEqual(['listen', 'port:3901', 'bootstrap']);
|
|
46
47
|
releaseBootstrap();
|
|
47
48
|
await run;
|