shadow-claw 1.26.0 → 1.27.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/.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 +146 -15
- package/bin/build/build.test.mjs +57 -0
- package/bin/cli.mjs +26 -0
- package/bin/commands/mcp.mjs +277 -55
- package/bin/commands/mcp.test.mjs +275 -1
- package/bin/commands/skills-index.mjs +328 -0
- package/bin/commands/skills-index.test.mjs +161 -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 +29 -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 +1 -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
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
DEFAULT_IFRAME_SANDBOX_POLICY,
|
|
8
8
|
getAllowedCustomElementHostPatterns,
|
|
9
9
|
getAllowedCustomElements,
|
|
10
|
+
getApprovedCustomElementScriptDescriptors,
|
|
10
11
|
getApprovedCustomElementScripts,
|
|
11
12
|
getCustomElementPurifyConfig,
|
|
12
13
|
getIframeCsp,
|
|
@@ -398,6 +399,12 @@ describe("custom-element-security", () => {
|
|
|
398
399
|
expect(getApprovedCustomElementScripts()).toEqual([
|
|
399
400
|
"https://kherrick.github.io/block-garden/block-garden-bundle-min.mjs",
|
|
400
401
|
]);
|
|
402
|
+
expect(getApprovedCustomElementScriptDescriptors()).toEqual([
|
|
403
|
+
{
|
|
404
|
+
src: "https://kherrick.github.io/block-garden/block-garden-bundle-min.mjs",
|
|
405
|
+
hasInit: false,
|
|
406
|
+
},
|
|
407
|
+
]);
|
|
401
408
|
|
|
402
409
|
const csp = getIframeCsp("test-nonce");
|
|
403
410
|
expect(csp).toContain("script-src 'nonce-test-nonce'");
|
|
@@ -409,5 +416,49 @@ describe("custom-element-security", () => {
|
|
|
409
416
|
setIframeSandboxPolicy(DEFAULT_IFRAME_SANDBOX_POLICY);
|
|
410
417
|
}
|
|
411
418
|
});
|
|
419
|
+
|
|
420
|
+
it("parses object script descriptors with src and hasInit", () => {
|
|
421
|
+
const siteConfigScript = document.createElement("script");
|
|
422
|
+
siteConfigScript.id = "shadow-claw-site-config";
|
|
423
|
+
siteConfigScript.type = "application/json";
|
|
424
|
+
siteConfigScript.textContent = JSON.stringify({
|
|
425
|
+
customElements: {
|
|
426
|
+
allowedDomains: ["kherrick.github.io"],
|
|
427
|
+
scripts: [
|
|
428
|
+
{ src: ".agents/scripts/main/pwgen-adapter.js", hasInit: true },
|
|
429
|
+
{ src: "https://kherrick.github.io/x-pwgen.js", hasInit: false },
|
|
430
|
+
"local-element.js",
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
document.head.appendChild(siteConfigScript);
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
installCustomElementsRegistryGuard();
|
|
438
|
+
|
|
439
|
+
expect(getApprovedCustomElementScripts()).toEqual([
|
|
440
|
+
".agents/scripts/main/pwgen-adapter.js",
|
|
441
|
+
"https://kherrick.github.io/x-pwgen.js",
|
|
442
|
+
"local-element.js",
|
|
443
|
+
]);
|
|
444
|
+
expect(getApprovedCustomElementScriptDescriptors()).toEqual([
|
|
445
|
+
{
|
|
446
|
+
src: ".agents/scripts/main/pwgen-adapter.js",
|
|
447
|
+
hasInit: true,
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
src: "https://kherrick.github.io/x-pwgen.js",
|
|
451
|
+
hasInit: false,
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
src: "local-element.js",
|
|
455
|
+
hasInit: false,
|
|
456
|
+
},
|
|
457
|
+
]);
|
|
458
|
+
} finally {
|
|
459
|
+
uninstallCustomElementsRegistryGuard();
|
|
460
|
+
siteConfigScript.remove();
|
|
461
|
+
}
|
|
462
|
+
});
|
|
412
463
|
});
|
|
413
464
|
});
|
|
@@ -150,10 +150,10 @@ export function isSafeCustomElementSource(urlStr: string): boolean {
|
|
|
150
150
|
|
|
151
151
|
try {
|
|
152
152
|
const base =
|
|
153
|
-
typeof
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
?
|
|
153
|
+
typeof globalThis !== "undefined" &&
|
|
154
|
+
globalThis.location?.origin &&
|
|
155
|
+
globalThis.location.origin !== "null"
|
|
156
|
+
? globalThis.location.origin
|
|
157
157
|
: "http://localhost";
|
|
158
158
|
const url = new URL(trimmed, base);
|
|
159
159
|
|
|
@@ -163,10 +163,10 @@ export function isSafeCustomElementSource(urlStr: string): boolean {
|
|
|
163
163
|
|
|
164
164
|
// Same-origin is trusted
|
|
165
165
|
if (
|
|
166
|
-
typeof
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
url.origin ===
|
|
166
|
+
typeof globalThis !== "undefined" &&
|
|
167
|
+
globalThis.location?.origin &&
|
|
168
|
+
globalThis.location.origin !== "null" &&
|
|
169
|
+
url.origin === globalThis.location.origin
|
|
170
170
|
) {
|
|
171
171
|
return true;
|
|
172
172
|
}
|
|
@@ -240,8 +240,8 @@ export function getIframeCsp(nonce: string): string {
|
|
|
240
240
|
try {
|
|
241
241
|
const parsed = new URL(
|
|
242
242
|
scriptUrl,
|
|
243
|
-
typeof
|
|
244
|
-
?
|
|
243
|
+
typeof globalThis !== "undefined" && globalThis.location?.origin
|
|
244
|
+
? globalThis.location.origin
|
|
245
245
|
: "http://localhost",
|
|
246
246
|
);
|
|
247
247
|
allowedScriptHosts.add(parsed.origin);
|
|
@@ -372,10 +372,15 @@ export function initCustomElementSecurityFromEmbeddedConfig(): void {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
export interface CustomElementScriptDescriptor {
|
|
376
|
+
src: string;
|
|
377
|
+
hasInit: boolean;
|
|
378
|
+
}
|
|
379
|
+
|
|
375
380
|
/**
|
|
376
|
-
* Retrieve approved custom element script
|
|
381
|
+
* Retrieve approved custom element script descriptors from the embedded site config.
|
|
377
382
|
*/
|
|
378
|
-
export function
|
|
383
|
+
export function getApprovedCustomElementScriptDescriptors(): CustomElementScriptDescriptor[] {
|
|
379
384
|
if (typeof document === "undefined") {
|
|
380
385
|
return [];
|
|
381
386
|
}
|
|
@@ -395,10 +400,16 @@ export function getApprovedCustomElementScripts(): string[] {
|
|
|
395
400
|
|
|
396
401
|
if (Array.isArray(scripts)) {
|
|
397
402
|
return scripts
|
|
398
|
-
.map((s: any) =>
|
|
403
|
+
.map((s: any) => {
|
|
404
|
+
const src = typeof s === "string" ? s : s?.src;
|
|
405
|
+
const hasInit =
|
|
406
|
+
typeof s === "object" && s !== null ? Boolean(s.hasInit) : false;
|
|
407
|
+
return { src, hasInit };
|
|
408
|
+
})
|
|
399
409
|
.filter(
|
|
400
|
-
(
|
|
401
|
-
typeof
|
|
410
|
+
(entry): entry is CustomElementScriptDescriptor =>
|
|
411
|
+
typeof entry.src === "string" &&
|
|
412
|
+
isSafeCustomElementSource(entry.src),
|
|
402
413
|
);
|
|
403
414
|
}
|
|
404
415
|
} catch {}
|
|
@@ -406,6 +417,13 @@ export function getApprovedCustomElementScripts(): string[] {
|
|
|
406
417
|
return [];
|
|
407
418
|
}
|
|
408
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Retrieve approved custom element script URLs from the embedded site config.
|
|
422
|
+
*/
|
|
423
|
+
export function getApprovedCustomElementScripts(): string[] {
|
|
424
|
+
return getApprovedCustomElementScriptDescriptors().map((d) => d.src);
|
|
425
|
+
}
|
|
426
|
+
|
|
409
427
|
/**
|
|
410
428
|
* Load allowed custom elements and host patterns from IndexedDB.
|
|
411
429
|
*/
|
|
@@ -224,6 +224,75 @@ describe("control-plane", () => {
|
|
|
224
224
|
|
|
225
225
|
ws.close();
|
|
226
226
|
});
|
|
227
|
+
|
|
228
|
+
it("removes client when WebSocket closes or sends client:unregister", async () => {
|
|
229
|
+
const ws = new WebSocket(
|
|
230
|
+
`ws://127.0.0.1:${port}/ws/control?token=${token}`,
|
|
231
|
+
);
|
|
232
|
+
await new Promise<void>((resolve) => ws.on("open", () => resolve()));
|
|
233
|
+
|
|
234
|
+
ws.send(
|
|
235
|
+
JSON.stringify({
|
|
236
|
+
id: "reg-unreg",
|
|
237
|
+
type: "client:register",
|
|
238
|
+
payload: {
|
|
239
|
+
clientId: "test-client-unreg",
|
|
240
|
+
deviceLabel: "Test Device",
|
|
241
|
+
capabilities: ["opfs"],
|
|
242
|
+
version: "1.0.0",
|
|
243
|
+
},
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Wait for registration
|
|
248
|
+
await new Promise((resolve) => ws.once("message", resolve));
|
|
249
|
+
|
|
250
|
+
// 1. Should be listed as connected
|
|
251
|
+
let clients = controlPlane.getConnectedClients();
|
|
252
|
+
expect(clients.some((c) => c.clientId === "test-client-unreg")).toBe(
|
|
253
|
+
true,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const resConnected = await makeHttpRequest({
|
|
257
|
+
port,
|
|
258
|
+
path: "/api/control/clients",
|
|
259
|
+
headers: { "x-control-token": token },
|
|
260
|
+
});
|
|
261
|
+
expect(
|
|
262
|
+
resConnected.data.clients.some(
|
|
263
|
+
(c: any) => c.clientId === "test-client-unreg",
|
|
264
|
+
),
|
|
265
|
+
).toBe(true);
|
|
266
|
+
|
|
267
|
+
// 2. Send client:unregister
|
|
268
|
+
ws.send(
|
|
269
|
+
JSON.stringify({
|
|
270
|
+
id: "unreg-msg",
|
|
271
|
+
type: "client:unregister",
|
|
272
|
+
payload: { clientId: "test-client-unreg" },
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
276
|
+
|
|
277
|
+
// 3. Should no longer be listed in getConnectedClients or /api/control/clients
|
|
278
|
+
clients = controlPlane.getConnectedClients();
|
|
279
|
+
expect(clients.some((c) => c.clientId === "test-client-unreg")).toBe(
|
|
280
|
+
false,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const resAfterUnreg = await makeHttpRequest({
|
|
284
|
+
port,
|
|
285
|
+
path: "/api/control/clients",
|
|
286
|
+
headers: { "x-control-token": token },
|
|
287
|
+
});
|
|
288
|
+
expect(
|
|
289
|
+
resAfterUnreg.data.clients.some(
|
|
290
|
+
(c: any) => c.clientId === "test-client-unreg",
|
|
291
|
+
),
|
|
292
|
+
).toBe(false);
|
|
293
|
+
|
|
294
|
+
ws.close();
|
|
295
|
+
});
|
|
227
296
|
});
|
|
228
297
|
|
|
229
298
|
describe("Command execution", () => {
|
|
@@ -15,6 +15,7 @@ import { ulid } from "../utils/ulid.js";
|
|
|
15
15
|
import {
|
|
16
16
|
registerClient,
|
|
17
17
|
updateClientHeartbeat,
|
|
18
|
+
unregisterClient,
|
|
18
19
|
getAllClients,
|
|
19
20
|
pruneStaleClients,
|
|
20
21
|
getOrCreateControlToken,
|
|
@@ -60,6 +61,8 @@ export interface ControlPlane {
|
|
|
60
61
|
) => Promise<CommandResultPayload[]>;
|
|
61
62
|
isClientConnected: (clientId: string) => boolean;
|
|
62
63
|
getConnectedClients: () => ClientInfo[];
|
|
64
|
+
getActiveClientId?: () => string;
|
|
65
|
+
setActiveClientId?: (id: string) => void;
|
|
63
66
|
getToken: () => string;
|
|
64
67
|
close: () => void;
|
|
65
68
|
}
|
|
@@ -296,6 +299,22 @@ export function createControlPlane(options: ControlPlaneOptions): ControlPlane {
|
|
|
296
299
|
updateClientHeartbeat(hb.clientId);
|
|
297
300
|
log(`Heartbeat from client: ${hb.clientId}`);
|
|
298
301
|
}
|
|
302
|
+
} else if (type === "client:unregister") {
|
|
303
|
+
const unreg = payload as { clientId?: string };
|
|
304
|
+
const unregId =
|
|
305
|
+
(unreg?.clientId &&
|
|
306
|
+
typeof unreg.clientId === "string" &&
|
|
307
|
+
unreg.clientId.trim()) ||
|
|
308
|
+
(ws ? socketToClientId.get(ws) : undefined);
|
|
309
|
+
if (unregId) {
|
|
310
|
+
unregisterClient(unregId);
|
|
311
|
+
if (ws) {
|
|
312
|
+
activeSockets.delete(unregId);
|
|
313
|
+
socketToClientId.delete(ws);
|
|
314
|
+
}
|
|
315
|
+
sseClientsByClientId.delete(unregId);
|
|
316
|
+
log(`Client explicitly unregistered: ${unregId}`);
|
|
317
|
+
}
|
|
299
318
|
} else if (type === "command:result") {
|
|
300
319
|
const res = payload as CommandResultPayload;
|
|
301
320
|
if (res?.commandId && pendingCommands.has(res.commandId)) {
|
|
@@ -327,6 +346,9 @@ export function createControlPlane(options: ControlPlaneOptions): ControlPlane {
|
|
|
327
346
|
activeSockets.delete(clientId);
|
|
328
347
|
socketToClientId.delete(ws);
|
|
329
348
|
log(`Client disconnected: ${clientId}`);
|
|
349
|
+
if (!isClientConnected(clientId)) {
|
|
350
|
+
unregisterClient(clientId);
|
|
351
|
+
}
|
|
330
352
|
}
|
|
331
353
|
});
|
|
332
354
|
|
|
@@ -381,12 +403,13 @@ export function createControlPlane(options: ControlPlaneOptions): ControlPlane {
|
|
|
381
403
|
});
|
|
382
404
|
});
|
|
383
405
|
|
|
384
|
-
// List all registered clients
|
|
406
|
+
// List connected clients (or all registered clients if ?all=true)
|
|
385
407
|
app.get(
|
|
386
408
|
"/api/control/clients",
|
|
387
409
|
adminAuthMiddleware,
|
|
388
|
-
(
|
|
389
|
-
const
|
|
410
|
+
(req: Request, res: Response) => {
|
|
411
|
+
const showAll = req.query.all === "true" || req.query.all === "1";
|
|
412
|
+
const clients = showAll ? getAllClients() : getConnectedClients();
|
|
390
413
|
res.json({ clients });
|
|
391
414
|
},
|
|
392
415
|
);
|
|
@@ -468,6 +491,9 @@ export function createControlPlane(options: ControlPlaneOptions): ControlPlane {
|
|
|
468
491
|
if (clientId && sseClientsByClientId.get(clientId) === res) {
|
|
469
492
|
sseClientsByClientId.delete(clientId);
|
|
470
493
|
log(`SSE client disconnected: ${clientId}`);
|
|
494
|
+
if (!isClientConnected(clientId)) {
|
|
495
|
+
unregisterClient(clientId);
|
|
496
|
+
}
|
|
471
497
|
}
|
|
472
498
|
});
|
|
473
499
|
},
|
|
@@ -574,11 +600,25 @@ export function createControlPlane(options: ControlPlaneOptions): ControlPlane {
|
|
|
574
600
|
return false;
|
|
575
601
|
}
|
|
576
602
|
|
|
603
|
+
let activeClientId: string = "";
|
|
604
|
+
|
|
577
605
|
function getConnectedClients(): ClientInfo[] {
|
|
578
606
|
const all = getAllClients();
|
|
579
607
|
return all.filter((c) => isClientConnected(c.clientId));
|
|
580
608
|
}
|
|
581
609
|
|
|
610
|
+
function getActiveClientId(): string {
|
|
611
|
+
if (activeClientId && isClientConnected(activeClientId)) {
|
|
612
|
+
return activeClientId;
|
|
613
|
+
}
|
|
614
|
+
const connected = getConnectedClients();
|
|
615
|
+
return connected.length > 0 ? connected[0].clientId : "";
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function setActiveClientId(id: string): void {
|
|
619
|
+
activeClientId = id;
|
|
620
|
+
}
|
|
621
|
+
|
|
582
622
|
function close(): void {
|
|
583
623
|
clearInterval(prunerInterval);
|
|
584
624
|
httpServer.removeListener("upgrade", upgradeHandler);
|
|
@@ -615,6 +655,8 @@ export function createControlPlane(options: ControlPlaneOptions): ControlPlane {
|
|
|
615
655
|
broadcastCommand,
|
|
616
656
|
isClientConnected,
|
|
617
657
|
getConnectedClients,
|
|
658
|
+
getActiveClientId,
|
|
659
|
+
setActiveClientId,
|
|
618
660
|
getToken: () => token,
|
|
619
661
|
close,
|
|
620
662
|
};
|
|
@@ -105,6 +105,22 @@ export const SHADOWCLAW_BUILTIN_TOOLS: McpTool[] = [
|
|
|
105
105
|
},
|
|
106
106
|
},
|
|
107
107
|
},
|
|
108
|
+
{
|
|
109
|
+
name: "shadowclaw_set_active_client",
|
|
110
|
+
description:
|
|
111
|
+
"Set the active default connected client used when no clientId is explicitly provided in tool calls.",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: {
|
|
115
|
+
clientId: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description:
|
|
118
|
+
"Target client ID (or index '0', '1', prefix, or device label) to make active.",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
required: ["clientId"],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
108
124
|
{
|
|
109
125
|
name: "shadowclaw_server_status",
|
|
110
126
|
description:
|
|
@@ -120,10 +136,6 @@ export function resolveTargetClientId(
|
|
|
120
136
|
controlPlane: any,
|
|
121
137
|
requestedId?: unknown,
|
|
122
138
|
): string {
|
|
123
|
-
if (typeof requestedId === "string" && requestedId.trim()) {
|
|
124
|
-
return requestedId.trim();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
139
|
try {
|
|
128
140
|
const clients =
|
|
129
141
|
typeof controlPlane.getConnectedClients === "function"
|
|
@@ -133,11 +145,43 @@ export function resolveTargetClientId(
|
|
|
133
145
|
: [];
|
|
134
146
|
|
|
135
147
|
if (Array.isArray(clients) && clients.length > 0) {
|
|
148
|
+
if (typeof requestedId === "string" && requestedId.trim()) {
|
|
149
|
+
const trimmed = requestedId.trim();
|
|
150
|
+
const exact = clients.find(
|
|
151
|
+
(c: any) => (c.clientId || c.id) === trimmed,
|
|
152
|
+
);
|
|
153
|
+
if (exact) return exact.clientId || exact.id;
|
|
154
|
+
|
|
155
|
+
const idx = parseInt(trimmed, 10);
|
|
156
|
+
if (!isNaN(idx) && idx >= 0 && idx < clients.length) {
|
|
157
|
+
return clients[idx].clientId || clients[idx].id;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const prefix = clients.find(
|
|
161
|
+
(c: any) =>
|
|
162
|
+
(c.clientId || c.id || "").startsWith(trimmed) ||
|
|
163
|
+
(c.clientId || "").replace(/^client-/, "").startsWith(trimmed),
|
|
164
|
+
);
|
|
165
|
+
if (prefix) return prefix.clientId || prefix.id;
|
|
166
|
+
|
|
167
|
+
const byLabel = clients.find((c: any) =>
|
|
168
|
+
c.deviceLabel?.toLowerCase().includes(trimmed.toLowerCase()),
|
|
169
|
+
);
|
|
170
|
+
if (byLabel) return byLabel.clientId || byLabel.id;
|
|
171
|
+
|
|
172
|
+
return trimmed;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (typeof controlPlane.getActiveClientId === "function") {
|
|
176
|
+
const active = controlPlane.getActiveClientId();
|
|
177
|
+
if (active) return active;
|
|
178
|
+
}
|
|
179
|
+
|
|
136
180
|
return clients[0].clientId || clients[0].id || "";
|
|
137
181
|
}
|
|
138
182
|
} catch (_) {}
|
|
139
183
|
|
|
140
|
-
return "";
|
|
184
|
+
return typeof requestedId === "string" ? requestedId.trim() : "";
|
|
141
185
|
}
|
|
142
186
|
|
|
143
187
|
export function registerBuiltInTools(
|
|
@@ -356,6 +400,37 @@ export function registerBuiltInTools(
|
|
|
356
400
|
};
|
|
357
401
|
}
|
|
358
402
|
|
|
403
|
+
case "shadowclaw_set_active_client": {
|
|
404
|
+
const targetId = resolveTargetClientId(controlPlane, args.clientId);
|
|
405
|
+
if (!targetId) {
|
|
406
|
+
return {
|
|
407
|
+
isError: true,
|
|
408
|
+
content: [
|
|
409
|
+
{
|
|
410
|
+
type: "text",
|
|
411
|
+
text: `Error: Client '${args.clientId}' not found among connected clients.`,
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (typeof controlPlane.setActiveClientId === "function") {
|
|
418
|
+
controlPlane.setActiveClientId(targetId);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
content: [
|
|
423
|
+
{
|
|
424
|
+
type: "text",
|
|
425
|
+
text: `Active default client set to: ${targetId}`,
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
_meta: {
|
|
429
|
+
activeClientId: targetId,
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
359
434
|
case "shadowclaw_server_status": {
|
|
360
435
|
const clients =
|
|
361
436
|
typeof controlPlane.getConnectedClients === "function"
|