shadow-claw 1.26.0 → 1.27.1
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/.agents/skills/main/skill-creator/SKILL.md +39 -0
- package/.agents/skills/main/skill-creator/references/declarative-pipelines.md +44 -0
- package/.agents/skills/main/skill-creator/references/declarative-tools.md +35 -0
- package/.agents/skills/main/skill-creator/references/frontmatter-spec.md +50 -0
- package/.well-known/agent-skills/index.json +14 -0
- package/README.md +5 -3
- package/bin/build/build.mjs +148 -15
- package/bin/build/build.test.mjs +128 -0
- package/bin/cli.mjs +35 -0
- package/bin/commands/mcp.mjs +277 -55
- package/bin/commands/mcp.test.mjs +275 -1
- package/bin/commands/skills-index.mjs +447 -0
- package/bin/commands/skills-index.test.mjs +243 -0
- package/bin/site-config/utils/custom-elements/get-approved-custom-element-scripts.test.mjs +2 -0
- package/bin/site-config/utils/custom-elements/sanitize-embedded-custom-element-scripts.test.mjs +10 -1
- package/dist/lib/components/index.js +25 -25
- package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
- package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/settings/shadow-claw-git.js +1 -1
- package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
- package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
- package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
- package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
- package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
- package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw-a2ui.js +1 -1
- package/dist/lib/components/shadow-claw-accounts.js +1 -1
- package/dist/lib/components/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/shadow-claw-channels.js +1 -1
- package/dist/lib/components/shadow-claw-chat.js +1 -1
- package/dist/lib/components/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/shadow-claw-conversations.js +1 -1
- package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
- package/dist/lib/components/shadow-claw-files.js +1 -1
- package/dist/lib/components/shadow-claw-git.js +1 -1
- package/dist/lib/components/shadow-claw-integrations.js +1 -1
- package/dist/lib/components/shadow-claw-llm.js +1 -1
- package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
- package/dist/lib/components/shadow-claw-networking.js +1 -1
- package/dist/lib/components/shadow-claw-page-header.js +1 -1
- package/dist/lib/components/shadow-claw-pages.js +1 -1
- package/dist/lib/components/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/shadow-claw-settings.js +1 -1
- package/dist/lib/components/shadow-claw-storage.js +1 -1
- package/dist/lib/components/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/shadow-claw-tasks.js +1 -1
- package/dist/lib/components/shadow-claw-terminal.js +1 -1
- package/dist/lib/components/shadow-claw-tools.js +1 -1
- package/dist/lib/components/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw.js +1 -1
- package/dist/lib/{configurePeerJs-CrlGan0m.js → configurePeerJs-oODzltaC.js} +2 -2
- package/dist/lib/{configurePeerJs-CrlGan0m.js.map → configurePeerJs-oODzltaC.js.map} +1 -1
- package/dist/lib/core/control-plane-client.d.ts +1 -0
- package/dist/lib/{downloadGroupFile-DboUjmp2.js → downloadGroupFile-DmcBQU1n.js} +2 -2
- package/dist/lib/{downloadGroupFile-DboUjmp2.js.map → downloadGroupFile-DmcBQU1n.js.map} +1 -1
- package/dist/lib/{file-viewer-BrK2Wxgh.js → file-viewer-C3YKnq_-.js} +4 -3
- package/dist/lib/{file-viewer-BrK2Wxgh.js.map → file-viewer-C3YKnq_-.js.map} +1 -1
- package/dist/lib/index.js +26 -26
- package/dist/lib/{initControlPlane-CsO4UR6_.js → initControlPlane-C2zURVIS.js} +86 -13
- package/dist/lib/initControlPlane-C2zURVIS.js.map +1 -0
- package/dist/lib/{markdown-eeSTFnMA.js → markdown-C6EHl9zs.js} +3 -3
- package/dist/lib/{markdown-eeSTFnMA.js.map → markdown-C6EHl9zs.js.map} +1 -1
- package/dist/lib/{orchestrator-CJvoC2Uu.js → orchestrator-CACLa_Ro.js} +7 -6
- package/dist/lib/{orchestrator-CJvoC2Uu.js.map → orchestrator-CACLa_Ro.js.map} +1 -1
- package/dist/lib/{orchestrator-DCfomsn9.js → orchestrator-KRjx0eba.js} +6 -534
- package/dist/lib/{orchestrator-DCfomsn9.js.map → orchestrator-KRjx0eba.js.map} +1 -1
- package/dist/lib/parsePath-DwC_YZjw.js +16 -0
- package/dist/lib/parsePath-DwC_YZjw.js.map +1 -0
- package/dist/lib/{peerjs-Dp02bXcG.js → peerjs-DFWOBcXf.js} +4 -4
- package/dist/lib/{peerjs-Dp02bXcG.js.map → peerjs-DFWOBcXf.js.map} +1 -1
- package/dist/lib/{readGroupFileBytes-Bve-e5e_.js → readGroupFileBytes-C8oOe8Iu.js} +3 -16
- package/dist/lib/readGroupFileBytes-C8oOe8Iu.js.map +1 -0
- package/dist/lib/security/custom-element-security.d.ts +8 -0
- package/dist/lib/server/control-plane-types.d.ts +1 -1
- package/dist/lib/{shadow-claw-D8xINM3c.js → shadow-claw-6UHdiy4a.js} +5 -5
- package/dist/lib/{shadow-claw-D8xINM3c.js.map → shadow-claw-6UHdiy4a.js.map} +1 -1
- package/dist/lib/{shadow-claw-a2ui-7rJqG4Ke.js → shadow-claw-a2ui-CpZUTzlh.js} +5 -4
- package/dist/lib/{shadow-claw-a2ui-7rJqG4Ke.js.map → shadow-claw-a2ui-CpZUTzlh.js.map} +1 -1
- package/dist/lib/{shadow-claw-accounts-CdruwN8A.js → shadow-claw-accounts-03oJ__XC.js} +2 -2
- package/dist/lib/{shadow-claw-accounts-CdruwN8A.js.map → shadow-claw-accounts-03oJ__XC.js.map} +1 -1
- package/dist/lib/{shadow-claw-channel-config-Bj0-Nk3t.js → shadow-claw-channel-config-DmfwJc6G.js} +3 -3
- package/dist/lib/{shadow-claw-channel-config-Bj0-Nk3t.js.map → shadow-claw-channel-config-DmfwJc6G.js.map} +1 -1
- package/dist/lib/{shadow-claw-channels-C_jI4KhP.js → shadow-claw-channels-1N6Jl-By.js} +3 -3
- package/dist/lib/{shadow-claw-channels-C_jI4KhP.js.map → shadow-claw-channels-1N6Jl-By.js.map} +1 -1
- package/dist/lib/{shadow-claw-chat-B16MspoP.js → shadow-claw-chat-BMoqQJRK.js} +10 -10
- package/dist/lib/{shadow-claw-chat-B16MspoP.js.map → shadow-claw-chat-BMoqQJRK.js.map} +1 -1
- package/dist/lib/{shadow-claw-control-plane-x8Ldid_h.js → shadow-claw-control-plane-UG0o7pCz.js} +6 -6
- package/dist/lib/{shadow-claw-control-plane-x8Ldid_h.js.map → shadow-claw-control-plane-UG0o7pCz.js.map} +1 -1
- package/dist/lib/{shadow-claw-conversations-Ct1kAYdB.js → shadow-claw-conversations-BwXep3iu.js} +4 -3
- package/dist/lib/{shadow-claw-conversations-Ct1kAYdB.js.map → shadow-claw-conversations-BwXep3iu.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-BfDHHgpA.js → shadow-claw-file-viewer-DUxzVxDT.js} +14 -10
- package/dist/lib/shadow-claw-file-viewer-DUxzVxDT.js.map +1 -0
- package/dist/lib/{shadow-claw-files-EYhOPYIH.js → shadow-claw-files-DIREEnT2.js} +7 -7
- package/dist/lib/{shadow-claw-files-EYhOPYIH.js.map → shadow-claw-files-DIREEnT2.js.map} +1 -1
- package/dist/lib/{shadow-claw-git-tcSpT-P0.js → shadow-claw-git-Bg1SWgsF.js} +4 -4
- package/dist/lib/{shadow-claw-git-tcSpT-P0.js.map → shadow-claw-git-Bg1SWgsF.js.map} +1 -1
- package/dist/lib/{shadow-claw-integrations-NbcCx_ZI.js → shadow-claw-integrations-Ds-CLPhj.js} +2 -2
- package/dist/lib/{shadow-claw-integrations-NbcCx_ZI.js.map → shadow-claw-integrations-Ds-CLPhj.js.map} +1 -1
- package/dist/lib/{shadow-claw-llm-BazJdD5f.js → shadow-claw-llm-CazBrVpM.js} +3 -3
- package/dist/lib/{shadow-claw-llm-BazJdD5f.js.map → shadow-claw-llm-CazBrVpM.js.map} +1 -1
- package/dist/lib/{shadow-claw-mcp-remote-CYmhpg7a.js → shadow-claw-mcp-remote-CT5Q-54E.js} +2 -2
- package/dist/lib/{shadow-claw-mcp-remote-CYmhpg7a.js.map → shadow-claw-mcp-remote-CT5Q-54E.js.map} +1 -1
- package/dist/lib/{shadow-claw-networking-DImPxe0H.js → shadow-claw-networking-Df85tCbO.js} +2 -2
- package/dist/lib/{shadow-claw-networking-DImPxe0H.js.map → shadow-claw-networking-Df85tCbO.js.map} +1 -1
- package/dist/lib/{shadow-claw-page-header-CcHUfFUx.js → shadow-claw-page-header-Cti2fzf-.js} +2 -2
- package/dist/lib/{shadow-claw-page-header-CcHUfFUx.js.map → shadow-claw-page-header-Cti2fzf-.js.map} +1 -1
- package/dist/lib/{shadow-claw-pages-D54jF1Qq.js → shadow-claw-pages-BgPyIEZO.js} +15 -11
- package/dist/lib/shadow-claw-pages-BgPyIEZO.js.map +1 -0
- package/dist/lib/{shadow-claw-peerjs-B9-T8Nlx.js → shadow-claw-peerjs-xkBXILed.js} +3 -3
- package/dist/lib/{shadow-claw-peerjs-B9-T8Nlx.js.map → shadow-claw-peerjs-xkBXILed.js.map} +1 -1
- package/dist/lib/{shadow-claw-settings-Bt1VAe-1.js → shadow-claw-settings-CN89xRVU.js} +4 -4
- package/dist/lib/{shadow-claw-settings-Bt1VAe-1.js.map → shadow-claw-settings-CN89xRVU.js.map} +1 -1
- package/dist/lib/{shadow-claw-storage-CFOjGf1d.js → shadow-claw-storage-VnhcObCe.js} +3 -3
- package/dist/lib/{shadow-claw-storage-CFOjGf1d.js.map → shadow-claw-storage-VnhcObCe.js.map} +1 -1
- package/dist/lib/{shadow-claw-task-server-BiKa2GOm.js → shadow-claw-task-server-jgEC2hHB.js} +2 -2
- package/dist/lib/{shadow-claw-task-server-BiKa2GOm.js.map → shadow-claw-task-server-jgEC2hHB.js.map} +1 -1
- package/dist/lib/{shadow-claw-tasks-DDHd8W4d.js → shadow-claw-tasks-i_evIufy.js} +6 -6
- package/dist/lib/{shadow-claw-tasks-DDHd8W4d.js.map → shadow-claw-tasks-i_evIufy.js.map} +1 -1
- package/dist/lib/{shadow-claw-terminal-kB6_tWKz.js → shadow-claw-terminal-CwScROlu.js} +2 -2
- package/dist/lib/{shadow-claw-terminal-kB6_tWKz.js.map → shadow-claw-terminal-CwScROlu.js.map} +1 -1
- package/dist/lib/{shadow-claw-tools-DxXIm0ku.js → shadow-claw-tools-DD3fyXP4.js} +5 -4
- package/dist/lib/{shadow-claw-tools-DxXIm0ku.js.map → shadow-claw-tools-DD3fyXP4.js.map} +1 -1
- package/dist/lib/{shadow-claw-webvm-Dc8oo7ao.js → shadow-claw-webvm-DbJq4I3-.js} +2 -2
- package/dist/lib/{shadow-claw-webvm-Dc8oo7ao.js.map → shadow-claw-webvm-DbJq4I3-.js.map} +1 -1
- package/dist/lib/{syncWebMcpRegistration-jyRGbC9w.js → syncWebMcpRegistration-DNLnQ-jz.js} +3 -2
- package/dist/lib/{syncWebMcpRegistration-jyRGbC9w.js.map → syncWebMcpRegistration-DNLnQ-jz.js.map} +1 -1
- package/dist/lib/tools-DwwB9RBa.js +546 -0
- package/dist/lib/tools-DwwB9RBa.js.map +1 -0
- package/dist/lib/{trusted-types-CkLDpLiZ.js → trusted-types-Dx0RZlrd.js} +19 -8
- package/dist/lib/trusted-types-Dx0RZlrd.js.map +1 -0
- package/dist/lib/utils/index.js +1 -1
- package/dist/lib/utils/utils.js +1 -1
- package/dist/public/.well-known/agent-skills/index.json +14 -0
- package/dist/public/AGENTS.md +5 -3
- package/dist/public/README.md +5 -3
- package/dist/public/agent.worker.js +1 -1
- package/dist/public/{configurePeerJs-COcwE0E7.js → configurePeerJs-BqgeBjP7.js} +1 -1
- package/dist/public/custom-element-security-FU04Cq05.js +1 -0
- package/dist/public/dist-B9m1QM9v.js +58 -0
- package/dist/public/docs/README.md +31 -31
- package/dist/public/docs/example/article/index.html +1 -1
- package/dist/public/docs/publishing/index.html +1 -1
- package/dist/public/docs/skill-creator/index.html +1 -1
- package/dist/public/docs/subsystems/a2ui.md +11 -1
- package/dist/public/docs/subsystems/cli.md +32 -0
- package/dist/public/docs/subsystems/control-plane.md +8 -2
- package/dist/public/docs/subsystems/custom-element-security.md +19 -9
- package/dist/public/docs/subsystems/mcp-server.md +39 -15
- package/dist/public/docs/subsystems/skills.md +30 -0
- package/dist/public/docs/subsystems/webmcp.md +9 -0
- package/dist/public/downloadGroupFile-D8KRJGGL.js +1 -0
- package/dist/public/e2e/README.md +7 -4
- package/dist/public/{e2e-bridge-CHARpbpW.js → e2e-bridge-Ceqr5kGc.js} +1 -1
- package/dist/public/effect-BJCrpFdp.js +1 -0
- package/dist/public/file-viewer-C-6Jc_tb.js +1 -0
- package/dist/public/index.html +1 -1
- package/dist/public/index.js +1 -1
- package/dist/public/initControlPlane-BN9cWyO0.js +1 -0
- package/dist/public/main/index.html +1 -1
- package/dist/public/main/memory/index.html +1 -1
- package/dist/public/{markdown-1I3O6Mmd.js → markdown-C3M4OW_A.js} +1 -1
- package/dist/public/{orchestrator-Cl95w2vb.js → orchestrator-CMbCTjBf.js} +7 -7
- package/dist/public/{orchestrator-D-xlXG92.js → orchestrator-DHjdLtqa.js} +663 -663
- package/dist/public/peerjs-DUCKH-m9.js +1 -0
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-CaoKmIIB.js → shadow-claw-EAt2ml5Y.js} +3 -3
- package/dist/public/{shadow-claw-accounts-CJvYC4li.js → shadow-claw-accounts-xUOLBsQZ.js} +1 -1
- package/dist/public/{shadow-claw-channels-3K62sU5f.js → shadow-claw-channels-XsfuWFF8.js} +4 -4
- package/dist/public/{shadow-claw-chat-Cu2keU2I.js → shadow-claw-chat-CbSaOZ4q.js} +7 -7
- package/dist/public/{shadow-claw-control-plane-D1OwlQI_.js → shadow-claw-control-plane-DHvN3Ukg.js} +2 -2
- package/dist/public/{shadow-claw-conversations-DDtg3BEZ.js → shadow-claw-conversations-BWs5VVfs.js} +2 -2
- package/dist/public/{shadow-claw-file-viewer-BR__1z9S.js → shadow-claw-file-viewer-BWsoW5QS.js} +5 -4
- package/dist/public/{shadow-claw-files-BJSyZ-Bl.js → shadow-claw-files-CG7MqWiV.js} +8 -8
- package/dist/public/{shadow-claw-git-MAjhXRLc.js → shadow-claw-git-DBRPZNav.js} +6 -6
- package/dist/public/{shadow-claw-integrations-82UoFuDR.js → shadow-claw-integrations-BBVnO0Fc.js} +8 -8
- package/dist/public/{shadow-claw-llm-R8j0lwPP.js → shadow-claw-llm-DMvLgKXo.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-BzZoz4LX.js → shadow-claw-mcp-remote-Bkx0gsmF.js} +2 -2
- package/dist/public/{shadow-claw-networking-DLEd9cNm.js → shadow-claw-networking-Dd8Rw31D.js} +2 -2
- package/dist/public/{shadow-claw-notifications-1vwR7TbT.js → shadow-claw-notifications-Dbevj-lq.js} +2 -2
- package/dist/public/{shadow-claw-page-header-DMKbkv3s.js → shadow-claw-page-header-BccSHItx.js} +1 -1
- package/dist/public/{shadow-claw-pages-CjyeIV__.js → shadow-claw-pages-3_lQAv0t.js} +4 -3
- package/dist/public/{shadow-claw-settings-BfbT4dBU.js → shadow-claw-settings-t8TIlgrM.js} +5 -5
- package/dist/public/{shadow-claw-storage-C-mWQQML.js → shadow-claw-storage-CfEXoSIE.js} +2 -2
- package/dist/public/{shadow-claw-task-server-D3lHp0Nc.js → shadow-claw-task-server-DxQSsgRZ.js} +2 -2
- package/dist/public/{shadow-claw-tasks-DVVWEm-c.js → shadow-claw-tasks-DNpicQwz.js} +14 -14
- package/dist/public/{shadow-claw-terminal-BwuTRp-O.js → shadow-claw-terminal-DpY6zU_r.js} +1 -1
- package/dist/public/{shadow-claw-toast-ClCwgzFG.js → shadow-claw-toast-BkrQIP-3.js} +1 -1
- package/dist/public/{shadow-claw-tools-zFLk2nbV.js → shadow-claw-tools-WSwWohnk.js} +2 -2
- package/dist/public/{shadow-claw-webvm-qR9X7fg7.js → shadow-claw-webvm-CP6oFjg5.js} +2 -2
- package/dist/public/syncWebMcpRegistration-DOn9gLnD.js +1 -0
- package/dist/public/toast-BXgUfbyh.js +1 -0
- package/dist/public/tools-DvckJZG2.js +1 -0
- package/dist/server.js +188 -188
- package/package.json +3 -1
- package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.stories.ts +1380 -93
- package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.ts +11 -6
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +8 -1
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +13 -5
- package/src/components/shadow-claw-pages/utils/buildHtmlPageSrcdoc.ts +13 -5
- package/src/core/control-plane-client.ts +15 -0
- package/src/core/utils/initControlPlane.test.ts +187 -0
- package/src/core/utils/initControlPlane.ts +194 -20
- package/src/security/custom-element-security.test.ts +51 -0
- package/src/security/custom-element-security.ts +33 -15
- package/src/server/control-plane-types.ts +1 -0
- package/src/server/control-plane.test.ts +69 -0
- package/src/server/control-plane.ts +45 -3
- package/src/server/mcp/tools/built-in-tools.ts +80 -5
- package/src/server/mcp/tools/client-tool-relay.ts +183 -26
- package/src/server/mcp/tools/mcp-tools.test.ts +140 -0
- package/src/server/middleware/static-files.test.ts +142 -0
- package/src/server/middleware/static-files.ts +66 -8
- package/src/testing/jest-setup.ts +14 -0
- package/src/worker/utils/handleMessage.ts +4 -1
- package/dist/lib/initControlPlane-CsO4UR6_.js.map +0 -1
- package/dist/lib/readGroupFileBytes-Bve-e5e_.js.map +0 -1
- package/dist/lib/shadow-claw-file-viewer-BfDHHgpA.js.map +0 -1
- package/dist/lib/shadow-claw-pages-D54jF1Qq.js.map +0 -1
- package/dist/lib/trusted-types-CkLDpLiZ.js.map +0 -1
- package/dist/public/custom-element-security-NCqQ5lmC.js +0 -1
- package/dist/public/downloadGroupFile-G0BZQgQg.js +0 -1
- package/dist/public/effect-rHYr4nfu.js +0 -1
- package/dist/public/file-viewer-8PdCURDD.js +0 -1
- package/dist/public/initControlPlane-CcGXax0A.js +0 -1
- package/dist/public/peerjs-BZdxWQFv.js +0 -1
- package/dist/public/syncWebMcpRegistration-DD42YsYV.js +0 -1
- package/dist/public/toast-HAq8XWFj.js +0 -58
|
@@ -1,7 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
describe,
|
|
3
|
+
it,
|
|
4
|
+
expect,
|
|
5
|
+
jest,
|
|
6
|
+
beforeEach,
|
|
7
|
+
afterEach,
|
|
8
|
+
} from "@jest/globals";
|
|
2
9
|
import { createCliMcpEngine } from "./mcp.mjs";
|
|
3
10
|
|
|
4
11
|
describe("CLI MCP Engine (createCliMcpEngine)", () => {
|
|
12
|
+
let errorSpy;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
errorSpy = jest.spyOn(console, "error").mockImplementation(() => {});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
errorSpy?.mockRestore();
|
|
20
|
+
});
|
|
21
|
+
|
|
5
22
|
it("processes server/discover request", async () => {
|
|
6
23
|
const mockClient = {
|
|
7
24
|
listClients: jest
|
|
@@ -97,4 +114,261 @@ describe("CLI MCP Engine (createCliMcpEngine)", () => {
|
|
|
97
114
|
groupId: undefined,
|
|
98
115
|
});
|
|
99
116
|
});
|
|
117
|
+
|
|
118
|
+
it("processes tools/call for relayed client tool (e.g. list_files)", async () => {
|
|
119
|
+
const mockClient = {
|
|
120
|
+
listClients: jest
|
|
121
|
+
.fn()
|
|
122
|
+
.mockResolvedValue([{ clientId: "c1", deviceLabel: "Browser Test" }]),
|
|
123
|
+
sendCommand: jest
|
|
124
|
+
.fn()
|
|
125
|
+
.mockImplementation(async (clientId, action, payload) => {
|
|
126
|
+
if (action === "invoke-tool" && payload.toolName === "list_files") {
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
data: {
|
|
130
|
+
result: ".agents/ MEMORY.md index.html -/",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return { success: false, error: "Not handled" };
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const engine = createCliMcpEngine({ client: mockClient });
|
|
139
|
+
const res = await engine.handleMessage({
|
|
140
|
+
jsonrpc: "2.0",
|
|
141
|
+
id: 4,
|
|
142
|
+
method: "tools/call",
|
|
143
|
+
params: {
|
|
144
|
+
name: "list_files",
|
|
145
|
+
arguments: {},
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(res).toBeDefined();
|
|
150
|
+
expect(res.id).toBe(4);
|
|
151
|
+
expect(res.result.resultType).toBe("complete");
|
|
152
|
+
expect(res.result.isError).toBe(false);
|
|
153
|
+
expect(res.result.content[0].text).toBe(".agents/ MEMORY.md index.html -/");
|
|
154
|
+
expect(mockClient.sendCommand).toHaveBeenCalledWith("c1", "invoke-tool", {
|
|
155
|
+
toolName: "list_files",
|
|
156
|
+
input: {},
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("handles tools/call relayed client tool failure with informative error content", async () => {
|
|
161
|
+
const mockClient = {
|
|
162
|
+
listClients: jest
|
|
163
|
+
.fn()
|
|
164
|
+
.mockResolvedValue([{ clientId: "c1", deviceLabel: "Browser Test" }]),
|
|
165
|
+
sendCommand: jest.fn().mockImplementation(async () => {
|
|
166
|
+
return {
|
|
167
|
+
success: false,
|
|
168
|
+
error: "Permission denied reading directory",
|
|
169
|
+
};
|
|
170
|
+
}),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const engine = createCliMcpEngine({ client: mockClient });
|
|
174
|
+
const res = await engine.handleMessage({
|
|
175
|
+
jsonrpc: "2.0",
|
|
176
|
+
id: 5,
|
|
177
|
+
method: "tools/call",
|
|
178
|
+
params: {
|
|
179
|
+
name: "list_files",
|
|
180
|
+
arguments: { path: "restricted" },
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(res).toBeDefined();
|
|
185
|
+
expect(res.id).toBe(5);
|
|
186
|
+
expect(res.result.resultType).toBe("complete");
|
|
187
|
+
expect(res.result.isError).toBe(true);
|
|
188
|
+
expect(res.result.content[0].text).toContain(
|
|
189
|
+
"Permission denied reading directory",
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("handles multi-client discovery and explicit clientId targeting", async () => {
|
|
194
|
+
const mockClient = {
|
|
195
|
+
listClients: jest.fn().mockResolvedValue([
|
|
196
|
+
{ clientId: "client-alpha", deviceLabel: "Linux Desktop" },
|
|
197
|
+
{ clientId: "client-beta", deviceLabel: "Android Phone" },
|
|
198
|
+
]),
|
|
199
|
+
sendCommand: jest
|
|
200
|
+
.fn()
|
|
201
|
+
.mockImplementation(async (clientId, action, payload) => {
|
|
202
|
+
if (action === "list-tools") {
|
|
203
|
+
const tools = [
|
|
204
|
+
{
|
|
205
|
+
name: "list_files",
|
|
206
|
+
description: "List workspace files.",
|
|
207
|
+
inputSchema: {
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: { path: { type: "string" } },
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
if (clientId === "client-alpha") {
|
|
214
|
+
tools.push({
|
|
215
|
+
name: "ask_user",
|
|
216
|
+
description: "Ask user for confirmation.",
|
|
217
|
+
inputSchema: {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: { question: { type: "string" } },
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
success: true,
|
|
225
|
+
data: { tools },
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (action === "invoke-tool") {
|
|
229
|
+
return {
|
|
230
|
+
success: true,
|
|
231
|
+
data: {
|
|
232
|
+
result: `Files from ${clientId}`,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return { success: false };
|
|
237
|
+
}),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const engine = createCliMcpEngine({ client: mockClient });
|
|
241
|
+
|
|
242
|
+
// 1. tools/list should augment schema with clientId enum and default client info
|
|
243
|
+
const listRes = await engine.handleMessage({
|
|
244
|
+
jsonrpc: "2.0",
|
|
245
|
+
id: 10,
|
|
246
|
+
method: "tools/list",
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const listFilesTool = listRes.result.tools.find(
|
|
250
|
+
(t) => t.name === "list_files",
|
|
251
|
+
);
|
|
252
|
+
expect(listFilesTool).toBeDefined();
|
|
253
|
+
expect(listFilesTool.description).toContain("[Default: Linux Desktop");
|
|
254
|
+
expect(listFilesTool.inputSchema.properties.clientId).toBeDefined();
|
|
255
|
+
expect(listFilesTool.inputSchema.properties.clientId.enum).toEqual([
|
|
256
|
+
"client-alpha",
|
|
257
|
+
"client-beta",
|
|
258
|
+
]);
|
|
259
|
+
|
|
260
|
+
// ask_user should ONLY be available on client-alpha
|
|
261
|
+
const askUserTool = listRes.result.tools.find((t) => t.name === "ask_user");
|
|
262
|
+
expect(askUserTool).toBeDefined();
|
|
263
|
+
expect(askUserTool.description).toContain("[Client: Linux Desktop");
|
|
264
|
+
expect(askUserTool.inputSchema.properties.clientId.enum).toEqual([
|
|
265
|
+
"client-alpha",
|
|
266
|
+
]);
|
|
267
|
+
|
|
268
|
+
// 2. tools/call targeting client-beta explicitly
|
|
269
|
+
const callResBeta = await engine.handleMessage({
|
|
270
|
+
jsonrpc: "2.0",
|
|
271
|
+
id: 11,
|
|
272
|
+
method: "tools/call",
|
|
273
|
+
params: {
|
|
274
|
+
name: "list_files",
|
|
275
|
+
arguments: { clientId: "client-beta" },
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
expect(mockClient.sendCommand).toHaveBeenCalledWith(
|
|
280
|
+
"client-beta",
|
|
281
|
+
"invoke-tool",
|
|
282
|
+
{
|
|
283
|
+
toolName: "list_files",
|
|
284
|
+
input: {},
|
|
285
|
+
},
|
|
286
|
+
);
|
|
287
|
+
expect(callResBeta.result.content[0].text).toBe("Files from client-beta");
|
|
288
|
+
|
|
289
|
+
// 3. Switch active default client to client-beta
|
|
290
|
+
const setActiveRes = await engine.handleMessage({
|
|
291
|
+
jsonrpc: "2.0",
|
|
292
|
+
id: 12,
|
|
293
|
+
method: "tools/call",
|
|
294
|
+
params: {
|
|
295
|
+
name: "shadowclaw_set_active_client",
|
|
296
|
+
arguments: { clientId: "client-beta" },
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(setActiveRes.result.content[0].text).toContain(
|
|
301
|
+
"Active default client set to: client-beta",
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// 4. tools/call without clientId now defaults to client-beta for list_files
|
|
305
|
+
const callResDefault = await engine.handleMessage({
|
|
306
|
+
jsonrpc: "2.0",
|
|
307
|
+
id: 13,
|
|
308
|
+
method: "tools/call",
|
|
309
|
+
params: {
|
|
310
|
+
name: "list_files",
|
|
311
|
+
arguments: {},
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
expect(callResDefault.result.content[0].text).toBe(
|
|
316
|
+
"Files from client-beta",
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// 5. Attempting to call ask_user on client-beta should be REJECTED because client-beta does not support it
|
|
320
|
+
const unsupportedRes = await engine.handleMessage({
|
|
321
|
+
jsonrpc: "2.0",
|
|
322
|
+
id: 14,
|
|
323
|
+
method: "tools/call",
|
|
324
|
+
params: {
|
|
325
|
+
name: "ask_user",
|
|
326
|
+
arguments: { clientId: "client-beta", question: "Prompt client beta?" },
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(unsupportedRes.result.isError).toBe(true);
|
|
331
|
+
expect(unsupportedRes.result.content[0].text).toContain(
|
|
332
|
+
"Tool 'ask_user' is not enabled or available on client 'client-beta'",
|
|
333
|
+
);
|
|
334
|
+
expect(unsupportedRes.result.content[0].text).toContain(
|
|
335
|
+
"Available on: client-alpha",
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// 6. Calling ask_user without clientId should automatically default to client-alpha
|
|
339
|
+
// (the only supporting client), even though active default is client-beta!
|
|
340
|
+
const askUserRes = await engine.handleMessage({
|
|
341
|
+
jsonrpc: "2.0",
|
|
342
|
+
id: 15,
|
|
343
|
+
method: "tools/call",
|
|
344
|
+
params: {
|
|
345
|
+
name: "ask_user",
|
|
346
|
+
arguments: { question: "Are you ready?" },
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect(mockClient.sendCommand).toHaveBeenCalledWith(
|
|
351
|
+
"client-alpha",
|
|
352
|
+
"invoke-tool",
|
|
353
|
+
{
|
|
354
|
+
toolName: "ask_user",
|
|
355
|
+
input: { question: "Are you ready?" },
|
|
356
|
+
},
|
|
357
|
+
300000,
|
|
358
|
+
);
|
|
359
|
+
expect(askUserRes.result.content[0].text).toBe("Files from client-alpha");
|
|
360
|
+
|
|
361
|
+
// 7. tools/call for ask_user with inputResponses returns fulfilled response
|
|
362
|
+
const fulfilledRes = await engine.handleMessage({
|
|
363
|
+
jsonrpc: "2.0",
|
|
364
|
+
id: 16,
|
|
365
|
+
method: "tools/call",
|
|
366
|
+
params: {
|
|
367
|
+
name: "ask_user",
|
|
368
|
+
arguments: { question: "Are you ready?" },
|
|
369
|
+
inputResponses: { response: "Yes, ready to go!" },
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
expect(fulfilledRes.result.content[0].text).toBe("Yes, ready to go!");
|
|
373
|
+
});
|
|
100
374
|
});
|