shadow-claw 1.29.0 → 1.30.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 +3 -2
- package/.agents/skills/main/skill-creator/references/frontmatter-spec.md +5 -3
- package/.well-known/agent-skills/index.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/.well-known/mcp.json +1 -1
- package/dist/lib/{app-routes-CqBOeA5q.js → app-routes-BAEMLu9g.js} +3 -2
- package/dist/lib/app-routes-BAEMLu9g.js.map +1 -0
- package/dist/lib/{bundler-8JNIKwOs.js → bundler-CsL7hHMq.js} +4 -9
- package/dist/lib/{bundler-8JNIKwOs.js.map → bundler-CsL7hHMq.js.map} +1 -1
- package/dist/lib/components/index.js +26 -26
- 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-notifications.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-notifications.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/{config--CFrw3J3.js → config-DjZ9QWc4.js} +2 -2
- package/dist/lib/{config--CFrw3J3.js.map → config-DjZ9QWc4.js.map} +1 -1
- package/dist/lib/{configurePeerJs-DG4hliKp.js → configurePeerJs-H0WaoT0w.js} +3 -3
- package/dist/lib/{configurePeerJs-DG4hliKp.js.map → configurePeerJs-H0WaoT0w.js.map} +1 -1
- package/dist/lib/{connections-DwjzwVz0.js → connections-BURqMIUv.js} +2 -2
- package/dist/lib/{connections-DwjzwVz0.js.map → connections-BURqMIUv.js.map} +1 -1
- package/dist/lib/content/markdown-links.d.ts +19 -0
- package/dist/lib/core/app-routes.d.ts +1 -0
- package/dist/lib/{downloadGroupFile-DmcBQU1n.js → downloadGroupFile-B8J4up2a.js} +2 -2
- package/dist/lib/{downloadGroupFile-DmcBQU1n.js.map → downloadGroupFile-B8J4up2a.js.map} +1 -1
- package/dist/lib/{file-viewer-C3YKnq_-.js → file-viewer-CwvhxAAG.js} +5 -5
- package/dist/lib/{file-viewer-C3YKnq_-.js.map → file-viewer-CwvhxAAG.js.map} +1 -1
- package/dist/lib/{getGroupDir-m0f-ekgX.js → getGroupDir-QrN2aTo8.js} +4 -4
- package/dist/lib/{getGroupDir-m0f-ekgX.js.map → getGroupDir-QrN2aTo8.js.map} +1 -1
- package/dist/lib/{git-C-Q6-IVe.js → git-DhZoe1SE.js} +3 -3
- package/dist/lib/{git-C-Q6-IVe.js.map → git-DhZoe1SE.js.map} +1 -1
- package/dist/lib/{iframe-sanitizer-CUqHuWQ-.js → iframe-sanitizer-Bujgq14T.js} +2 -2
- package/dist/lib/{iframe-sanitizer-CUqHuWQ-.js.map → iframe-sanitizer-Bujgq14T.js.map} +1 -1
- package/dist/lib/index.js +27 -27
- package/dist/lib/{initChatSplitResize-yyBTU6h9.js → initChatSplitResize-BjiWoqC_.js} +2 -2
- package/dist/lib/{initChatSplitResize-yyBTU6h9.js.map → initChatSplitResize-BjiWoqC_.js.map} +1 -1
- package/dist/lib/{initControlPlane-DnpAzphg.js → initControlPlane-C9JxQCDh.js} +16 -16
- package/dist/lib/{initControlPlane-DnpAzphg.js.map → initControlPlane-C9JxQCDh.js.map} +1 -1
- package/dist/lib/{markdown-DgdSt2GD.js → markdown-DYHmNjC4.js} +126 -5
- package/dist/lib/markdown-DYHmNjC4.js.map +1 -0
- package/dist/lib/{mcp-reconnect-C86MR3z8.js → mcp-reconnect-BxJ77WSb.js} +2 -2
- package/dist/lib/{mcp-reconnect-C86MR3z8.js.map → mcp-reconnect-BxJ77WSb.js.map} +1 -1
- package/dist/lib/{memoryStorage-UR8E8K-H.js → memoryStorage-D3FBwO9T.js} +2 -2
- package/dist/lib/{memoryStorage-UR8E8K-H.js.map → memoryStorage-D3FBwO9T.js.map} +1 -1
- package/dist/lib/{orchestrator-ClXbJ4Mb.js → orchestrator-C6dc-2OQ.js} +130 -27
- package/dist/lib/{orchestrator-ClXbJ4Mb.js.map → orchestrator-C6dc-2OQ.js.map} +1 -1
- package/dist/lib/{orchestrator-CjEau_RG.js → orchestrator-DVkyd8K9.js} +27 -17
- package/dist/lib/orchestrator-DVkyd8K9.js.map +1 -0
- package/dist/lib/{peerjs-BdfpltIe.js → peerjs-BUS9O04I.js} +274 -60
- package/dist/lib/peerjs-BUS9O04I.js.map +1 -0
- package/dist/lib/{push-client-DjwKknNg.js → push-client-4JsQGmQa.js} +3 -3
- package/dist/lib/{push-client-DjwKknNg.js.map → push-client-4JsQGmQa.js.map} +1 -1
- package/dist/lib/{readGroupFileBytes-C8oOe8Iu.js → readGroupFileBytes-CSA7u-nJ.js} +2 -2
- package/dist/lib/{readGroupFileBytes-C8oOe8Iu.js.map → readGroupFileBytes-CSA7u-nJ.js.map} +1 -1
- package/dist/lib/{shadow-claw-DcPGKenn.js → shadow-claw-BMoF8Auf.js} +11 -11
- package/dist/lib/{shadow-claw-DcPGKenn.js.map → shadow-claw-BMoF8Auf.js.map} +1 -1
- package/dist/lib/{shadow-claw-a2ui-CpZUTzlh.js → shadow-claw-a2ui-2I5nffIn.js} +2 -2
- package/dist/lib/{shadow-claw-a2ui-CpZUTzlh.js.map → shadow-claw-a2ui-2I5nffIn.js.map} +1 -1
- package/dist/lib/{shadow-claw-accounts-03oJ__XC.js → shadow-claw-accounts-BCjlgvzy.js} +3 -3
- package/dist/lib/{shadow-claw-accounts-03oJ__XC.js.map → shadow-claw-accounts-BCjlgvzy.js.map} +1 -1
- package/dist/lib/{shadow-claw-channel-config-DEL-aZ7N.js → shadow-claw-channel-config-CxQAmlDE.js} +3 -3
- package/dist/lib/{shadow-claw-channel-config-DEL-aZ7N.js.map → shadow-claw-channel-config-CxQAmlDE.js.map} +1 -1
- package/dist/lib/{shadow-claw-channels-BgaJiPdE.js → shadow-claw-channels-CuYMUVAG.js} +3 -3
- package/dist/lib/{shadow-claw-channels-BgaJiPdE.js.map → shadow-claw-channels-CuYMUVAG.js.map} +1 -1
- package/dist/lib/{shadow-claw-chat-mXPQZbwT.js → shadow-claw-chat-RFtPmmyl.js} +13 -12
- package/dist/lib/shadow-claw-chat-RFtPmmyl.js.map +1 -0
- package/dist/lib/{shadow-claw-control-plane-DO6foOLV.js → shadow-claw-control-plane-siqmRzR1.js} +7 -7
- package/dist/lib/{shadow-claw-control-plane-DO6foOLV.js.map → shadow-claw-control-plane-siqmRzR1.js.map} +1 -1
- package/dist/lib/{shadow-claw-conversations-BLwHTwdW.js → shadow-claw-conversations-DygK5y1e.js} +7 -7
- package/dist/lib/{shadow-claw-conversations-BLwHTwdW.js.map → shadow-claw-conversations-DygK5y1e.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-BSbZvSl-.js → shadow-claw-file-viewer-DGMA7d7H.js} +11 -11
- package/dist/lib/shadow-claw-file-viewer-DGMA7d7H.js.map +1 -0
- package/dist/lib/{shadow-claw-files-BLVr9Pua.js → shadow-claw-files-CgD1B9J7.js} +8 -8
- package/dist/lib/{shadow-claw-files-BLVr9Pua.js.map → shadow-claw-files-CgD1B9J7.js.map} +1 -1
- package/dist/lib/{shadow-claw-git-CHC1H86J.js → shadow-claw-git-Bw1jGRrf.js} +6 -6
- package/dist/lib/{shadow-claw-git-CHC1H86J.js.map → shadow-claw-git-Bw1jGRrf.js.map} +1 -1
- package/dist/lib/{shadow-claw-integrations-Ds-CLPhj.js → shadow-claw-integrations-ChaXtvsg.js} +4 -4
- package/dist/lib/{shadow-claw-integrations-Ds-CLPhj.js.map → shadow-claw-integrations-ChaXtvsg.js.map} +1 -1
- package/dist/lib/{shadow-claw-llm-CKXfxd3D.js → shadow-claw-llm-CDM4Akjc.js} +6 -6
- package/dist/lib/{shadow-claw-llm-CKXfxd3D.js.map → shadow-claw-llm-CDM4Akjc.js.map} +1 -1
- package/dist/lib/{shadow-claw-mcp-remote-CT5Q-54E.js → shadow-claw-mcp-remote-BLxs_Z4I.js} +4 -4
- package/dist/lib/{shadow-claw-mcp-remote-CT5Q-54E.js.map → shadow-claw-mcp-remote-BLxs_Z4I.js.map} +1 -1
- package/dist/lib/{shadow-claw-networking-dpYvfchh.js → shadow-claw-networking-BYt-kMSI.js} +2 -2
- package/dist/lib/{shadow-claw-networking-dpYvfchh.js.map → shadow-claw-networking-BYt-kMSI.js.map} +1 -1
- package/dist/lib/{shadow-claw-notifications-_YeAVPga.js → shadow-claw-notifications-Dpe4EtZI.js} +3 -3
- package/dist/lib/{shadow-claw-notifications-_YeAVPga.js.map → shadow-claw-notifications-Dpe4EtZI.js.map} +1 -1
- package/dist/lib/{shadow-claw-page-header-Cti2fzf-.js → shadow-claw-page-header-P4EvWyUr.js} +2 -2
- package/dist/lib/{shadow-claw-page-header-Cti2fzf-.js.map → shadow-claw-page-header-P4EvWyUr.js.map} +1 -1
- package/dist/lib/{shadow-claw-pages-DVzZriZ7.js → shadow-claw-pages-DWd_c4d6.js} +10 -10
- package/dist/lib/{shadow-claw-pages-DVzZriZ7.js.map → shadow-claw-pages-DWd_c4d6.js.map} +1 -1
- package/dist/lib/{shadow-claw-peerjs-BvixjeBz.js → shadow-claw-peerjs-D928817m.js} +3 -3
- package/dist/lib/{shadow-claw-peerjs-BvixjeBz.js.map → shadow-claw-peerjs-D928817m.js.map} +1 -1
- package/dist/lib/{shadow-claw-settings-DV9-gD4Q.js → shadow-claw-settings-C8OPrSqs.js} +8 -8
- package/dist/lib/{shadow-claw-settings-DV9-gD4Q.js.map → shadow-claw-settings-C8OPrSqs.js.map} +1 -1
- package/dist/lib/{shadow-claw-storage-Bv__kXG6.js → shadow-claw-storage-CZ2uZJNw.js} +5 -5
- package/dist/lib/{shadow-claw-storage-Bv__kXG6.js.map → shadow-claw-storage-CZ2uZJNw.js.map} +1 -1
- package/dist/lib/{shadow-claw-task-server-DNOe8PWK.js → shadow-claw-task-server-CLT018BX.js} +2 -2
- package/dist/lib/{shadow-claw-task-server-DNOe8PWK.js.map → shadow-claw-task-server-CLT018BX.js.map} +1 -1
- package/dist/lib/{shadow-claw-tasks-qmcDT4PU.js → shadow-claw-tasks-D5h-lGmq.js} +9 -8
- package/dist/lib/shadow-claw-tasks-D5h-lGmq.js.map +1 -0
- package/dist/lib/{shadow-claw-terminal-0QUWP8of.js → shadow-claw-terminal-Cy1O6KVM.js} +2 -2
- package/dist/lib/{shadow-claw-terminal-0QUWP8of.js.map → shadow-claw-terminal-Cy1O6KVM.js.map} +1 -1
- package/dist/lib/{shadow-claw-tools-Bs_bmI6-.js → shadow-claw-tools-CVD06bCR.js} +9 -9
- package/dist/lib/{shadow-claw-tools-Bs_bmI6-.js.map → shadow-claw-tools-CVD06bCR.js.map} +1 -1
- package/dist/lib/{shadow-claw-webvm-DYaoL7QY.js → shadow-claw-webvm-DoGl5ZSJ.js} +3 -3
- package/dist/lib/{shadow-claw-webvm-DYaoL7QY.js.map → shadow-claw-webvm-DoGl5ZSJ.js.map} +1 -1
- package/dist/lib/subsystems/channels/peerjs.d.ts +37 -4
- package/dist/lib/subsystems/channels/room.d.ts +4 -0
- package/dist/lib/{syncWebMcpRegistration-BhbqI6R2.js → syncWebMcpRegistration-BeYkQEvj.js} +5 -5
- package/dist/lib/{syncWebMcpRegistration-BhbqI6R2.js.map → syncWebMcpRegistration-BeYkQEvj.js.map} +1 -1
- package/dist/lib/tools-C3Kft940.js +1 -0
- package/dist/lib/{tools-DBaMRiEu.js → tools-DKzPWc0q.js} +2 -2
- package/dist/lib/{tools-DBaMRiEu.js.map → tools-DKzPWc0q.js.map} +1 -1
- package/dist/lib/{tools-DwwB9RBa.js → tools-DqcJe8LS.js} +5 -5
- package/dist/lib/{tools-DwwB9RBa.js.map → tools-DqcJe8LS.js.map} +1 -1
- package/dist/lib/{trusted-types-Dx0RZlrd.js → trusted-types-Oz7xXJAY.js} +4 -4
- package/dist/lib/{trusted-types-Dx0RZlrd.js.map → trusted-types-Oz7xXJAY.js.map} +1 -1
- package/dist/lib/utils/index.js +1 -1
- package/dist/lib/utils/namespacedStorage.js +1 -1
- package/dist/lib/utils/utils.js +1 -1
- package/dist/lib/{webmcp-j_1_T3wf.js → webmcp-BC8_WafM.js} +4 -4
- package/dist/lib/{webmcp-j_1_T3wf.js.map → webmcp-BC8_WafM.js.map} +1 -1
- package/dist/lib/worker/utils/system-prompt.d.ts +4 -1
- package/dist/public/.agents/skills/main/skill-creator/SKILL.md +3 -2
- package/dist/public/.agents/skills/main/skill-creator/references/frontmatter-spec.md +5 -3
- package/dist/public/.well-known/agent-skills/index.json +1 -1
- package/dist/public/.well-known/mcp/server-card.json +1 -1
- package/dist/public/.well-known/mcp.json +1 -1
- package/dist/public/agent.worker.js +18 -17
- package/dist/public/app-routes-BMQ4HFaM.js +1 -0
- package/dist/public/{bundler-Ca-Xwln8.js → bundler-0if-QelV.js} +4 -4
- package/dist/public/{config-CrHchneq.js → config-Bnwx4EfS.js} +1 -1
- package/dist/public/{configurePeerJs-B470pdeU.js → configurePeerJs-DkyGvwk_.js} +1 -1
- package/dist/public/{connections-DvbgdkaB.js → connections-6575KDGE.js} +1 -1
- package/dist/public/{custom-element-security-FU04Cq05.js → custom-element-security-BQJQ_oKz.js} +1 -1
- package/dist/public/docs/example/article/index.html +2 -2
- package/dist/public/docs/publishing/index.html +2 -2
- package/dist/public/docs/skill-creator/index.html +2 -2
- package/dist/public/docs/subsystems/skills.md +3 -1
- package/dist/public/{downloadGroupFile-D8KRJGGL.js → downloadGroupFile-Wo1Sxdhr.js} +1 -1
- package/dist/public/{e2e-bridge-CSW0S5Yz.js → e2e-bridge-CvLCUU2O.js} +1 -1
- package/dist/public/{file-viewer-Cs-riNvy.js → file-viewer-CIjqAvNF.js} +1 -1
- package/dist/public/{getGroupDir-P1h9wl6S.js → getGroupDir-BwprrdrQ.js} +1 -1
- package/dist/public/{git-7Vq_SJSp.js → git-BSnY2D-A.js} +2 -2
- package/dist/public/{iframe-sanitizer-y2Z1pPwY.js → iframe-sanitizer-viROQkRn.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-CzV2E7Ul.js → initChatSplitResize-BA18wLQQ.js} +1 -1
- package/dist/public/{initControlPlane-BqCY0NQy.js → initControlPlane-L96uJkRA.js} +1 -1
- package/dist/public/main/index.html +2 -2
- package/dist/public/main/memory/index.html +2 -2
- package/dist/public/{markdown-Cw7vCtTg.js → markdown-DGjZAm_w.js} +17 -14
- package/dist/public/{mcp-reconnect-9emsqGRe.js → mcp-reconnect-CBHuaYdF.js} +1 -1
- package/dist/public/{memoryStorage-C0KvLNUp.js → memoryStorage-C1llaYus.js} +1 -1
- package/dist/public/orchestrator-C0VOoTiK.js +15 -0
- package/dist/public/{orchestrator-DpkkeD6T.js → orchestrator-DMWi--Dc.js} +20 -19
- package/dist/public/peerjs-Bf6ZxfbA.js +1 -0
- package/dist/public/{push-client-CHjSbrWs.js → push-client-BD4YalBa.js} +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-BBPV_9X8.js → shadow-claw-DRy-D4Qi.js} +2 -2
- package/dist/public/{shadow-claw-accounts-xUOLBsQZ.js → shadow-claw-accounts-DnfmA8cb.js} +1 -1
- package/dist/public/{shadow-claw-channels-BJvrKQjg.js → shadow-claw-channels-8UkycZAa.js} +1 -1
- package/dist/public/{shadow-claw-chat-BG2EaRaw.js → shadow-claw-chat-Cf5LUiul.js} +9 -9
- package/dist/public/{shadow-claw-control-plane-BBa7WbTg.js → shadow-claw-control-plane-UsBF-Nvd.js} +2 -2
- package/dist/public/{shadow-claw-conversations-CFaWFmkm.js → shadow-claw-conversations-Aoe61Y21.js} +1 -1
- package/dist/public/{shadow-claw-file-viewer-Z-GIKaE9.js → shadow-claw-file-viewer-D28dSSEn.js} +6 -6
- package/dist/public/{shadow-claw-files-BBx4iMsk.js → shadow-claw-files-CaDUlPEU.js} +1 -1
- package/dist/public/{shadow-claw-git-vTOKatwd.js → shadow-claw-git-DYNcZMWA.js} +2 -2
- package/dist/public/{shadow-claw-integrations-BBVnO0Fc.js → shadow-claw-integrations-BJf_hQVv.js} +1 -1
- package/dist/public/{shadow-claw-llm-Ce1Ax6-x.js → shadow-claw-llm-BqK2SIRf.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-Bkx0gsmF.js → shadow-claw-mcp-remote-B_dDG990.js} +1 -1
- package/dist/public/{shadow-claw-networking-q4XIzdB5.js → shadow-claw-networking-D8LxOofv.js} +1 -1
- package/dist/public/{shadow-claw-notifications-D2cz0gmm.js → shadow-claw-notifications-DpVyiNG_.js} +1 -1
- package/dist/public/{shadow-claw-page-header-BccSHItx.js → shadow-claw-page-header-DdC7bJuW.js} +1 -1
- package/dist/public/{shadow-claw-pages-3335lmSC.js → shadow-claw-pages-CYMl4J-6.js} +3 -3
- package/dist/public/{shadow-claw-settings-4ptYuJwO.js → shadow-claw-settings-Da_8Oy3O.js} +3 -3
- package/dist/public/{shadow-claw-storage-bIrPKFhF.js → shadow-claw-storage-DXIm86_-.js} +1 -1
- package/dist/public/{shadow-claw-task-server-5yqVBxy1.js → shadow-claw-task-server-BCceBD0G.js} +1 -1
- package/dist/public/{shadow-claw-tasks-DcqYHPLZ.js → shadow-claw-tasks-BW1QZ5L3.js} +19 -19
- package/dist/public/{shadow-claw-terminal-fARaDONr.js → shadow-claw-terminal-vqfsxhcK.js} +1 -1
- package/dist/public/{shadow-claw-tools-paEAhcJr.js → shadow-claw-tools-l0Jsa7UJ.js} +1 -1
- package/dist/public/{shadow-claw-webvm-CbzH_vyX.js → shadow-claw-webvm-CDPOWZm7.js} +1 -1
- package/dist/public/skills/main/skill-creator/SKILL.md +3 -2
- package/dist/public/skills/main/skill-creator/references/frontmatter-spec.md +5 -3
- package/dist/public/static-main-manifest.json +2 -2
- package/dist/public/{syncWebMcpRegistration-CWSYaHex.js → syncWebMcpRegistration-5b5FkP9_.js} +1 -1
- package/dist/public/tools-B-PzsLc2.js +1 -0
- package/dist/public/{tools-C8x3nmo4.js → tools-CcgG5XuA.js} +1 -1
- package/dist/public/{tools-DvckJZG2.js → tools-VSOY66vs.js} +1 -1
- package/dist/public/{webmcp-DAjmliCx.js → webmcp-DBONjQWd.js} +1 -1
- package/dist/server.js +2 -2
- package/package.json +2 -2
- package/src/__mocks__/marked.cjs +14 -0
- package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +17 -0
- package/src/components/shadow-claw-chat/shadow-claw-chat.ts +5 -1
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +78 -0
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +6 -2
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +56 -0
- package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +22 -0
- package/src/components/shadow-claw-tasks/shadow-claw-tasks.ts +5 -1
- package/src/content/markdown-links.test.ts +109 -0
- package/src/content/markdown-links.ts +187 -0
- package/src/content/markdown.test.ts +33 -0
- package/src/content/markdown.ts +6 -1
- package/src/core/app-routes.test.ts +65 -0
- package/src/core/app-routes.ts +1 -1
- package/src/core/orchestrator/orchestrator.ts +3 -0
- package/src/core/orchestrator/utils/compactContext.ts +2 -0
- package/src/core/orchestrator/utils/invokeAgent.ts +1 -0
- package/src/subsystems/accounts/service-accounts.test.ts +0 -1
- package/src/subsystems/channels/peerjs.test.ts +479 -1
- package/src/subsystems/channels/peerjs.ts +420 -75
- package/src/subsystems/channels/room.test.ts +35 -0
- package/src/subsystems/channels/room.ts +15 -0
- package/src/subsystems/skills/activateSkill.test.ts +38 -5
- package/src/subsystems/skills/activateSkill.ts +25 -3
- package/src/subsystems/skills/parseSkill.test.ts +71 -0
- package/src/subsystems/skills/parseSkill.ts +185 -25
- package/src/subsystems/tools/files.ts +4 -4
- package/src/worker/tools/spawn-subagent/spawn-subagent.test.ts +22 -23
- package/src/worker/tools/workspace/send-file.test.ts +24 -1
- package/src/worker/tools/workspace/send-file.ts +6 -3
- package/src/worker/utils/handleCompact.test.ts +3 -4
- package/src/worker/utils/handleInvoke.test.ts +13 -11
- package/src/worker/utils/handleInvoke.ts +3 -0
- package/src/worker/utils/handleMessage.test.ts +0 -1
- package/src/worker/utils/post.test.ts +3 -4
- package/src/worker/utils/sandboxedEval.test.ts +1 -2
- package/src/worker/utils/system-prompt.test.ts +59 -0
- package/src/worker/utils/system-prompt.ts +21 -1
- package/src/worker/utils/withRetry.test.ts +3 -16
- package/dist/lib/app-routes-CqBOeA5q.js.map +0 -1
- package/dist/lib/markdown-DgdSt2GD.js.map +0 -1
- package/dist/lib/orchestrator-CjEau_RG.js.map +0 -1
- package/dist/lib/peerjs-BdfpltIe.js.map +0 -1
- package/dist/lib/shadow-claw-chat-mXPQZbwT.js.map +0 -1
- package/dist/lib/shadow-claw-file-viewer-BSbZvSl-.js.map +0 -1
- package/dist/lib/shadow-claw-tasks-qmcDT4PU.js.map +0 -1
- package/dist/lib/tools-D4Pp-nUw.js +0 -1
- package/dist/public/app-routes-Bs-riIvr.js +0 -1
- package/dist/public/orchestrator-BWkHHO9Y.js +0 -15
- package/dist/public/peerjs-tf2EmWjB.js +0 -1
- package/dist/public/tools-COduyYJ0.js +0 -1
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
Channel,
|
|
21
21
|
ChannelMessageCallback,
|
|
22
22
|
ChannelTypingCallback,
|
|
23
|
+
RoomMeta,
|
|
23
24
|
} from "./types.js";
|
|
24
25
|
|
|
25
26
|
// A2A Protocol imports
|
|
@@ -64,6 +65,55 @@ export const transferProgressSignal = new Signal.State<{
|
|
|
64
65
|
direction: "send" | "receive";
|
|
65
66
|
} | null>(null);
|
|
66
67
|
|
|
68
|
+
let transferProgressClearTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Update transfer progress signal and cancel any pending delayed clear timer.
|
|
72
|
+
*/
|
|
73
|
+
export function updateTransferProgress(
|
|
74
|
+
progress: {
|
|
75
|
+
count: number;
|
|
76
|
+
total: number;
|
|
77
|
+
direction: "send" | "receive";
|
|
78
|
+
} | null,
|
|
79
|
+
): void {
|
|
80
|
+
if (transferProgressClearTimeout !== null) {
|
|
81
|
+
clearTimeout(transferProgressClearTimeout);
|
|
82
|
+
transferProgressClearTimeout = null;
|
|
83
|
+
}
|
|
84
|
+
transferProgressSignal.set(progress);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Clear transfer progress after a delay, ensuring any existing timer is reset.
|
|
89
|
+
*/
|
|
90
|
+
export function clearTransferProgressDelayed(delayMs = 1500): void {
|
|
91
|
+
if (transferProgressClearTimeout !== null) {
|
|
92
|
+
clearTimeout(transferProgressClearTimeout);
|
|
93
|
+
}
|
|
94
|
+
transferProgressClearTimeout = setTimeout(() => {
|
|
95
|
+
transferProgressSignal.set(null);
|
|
96
|
+
transferProgressClearTimeout = null;
|
|
97
|
+
}, delayMs);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Maximum chunk size in bytes for application-level file transfers (15 KB to stay safely under PeerJS 16,300 MTU after msgpack framing) */
|
|
101
|
+
export const FILE_CHUNK_SIZE = 15360;
|
|
102
|
+
|
|
103
|
+
/** Threshold for WebRTC DataChannel bufferedAmount backpressure (64 KB) */
|
|
104
|
+
export const FILE_BUFFER_DRAIN_THRESHOLD = 65536;
|
|
105
|
+
|
|
106
|
+
interface InboundFileTransfer {
|
|
107
|
+
transferId: string;
|
|
108
|
+
name: string;
|
|
109
|
+
mimeType: string;
|
|
110
|
+
size: number;
|
|
111
|
+
totalChunks: number;
|
|
112
|
+
receivedChunks: Map<number, Uint8Array>;
|
|
113
|
+
receivedBytes: number;
|
|
114
|
+
targetGroupId?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
67
117
|
export interface PeerJsServerConfig {
|
|
68
118
|
host?: string;
|
|
69
119
|
port?: number;
|
|
@@ -142,23 +192,31 @@ export class PeerJsChannel implements Channel {
|
|
|
142
192
|
| null = null;
|
|
143
193
|
/** Per-connection task managers keyed by remote peer ID */
|
|
144
194
|
private _taskManagers = new Map<string, PeerTaskManager>();
|
|
195
|
+
/** Inbound multi-chunk file transfers keyed by transferId */
|
|
196
|
+
private _inboundTransfers = new Map<string, InboundFileTransfer>();
|
|
145
197
|
|
|
146
198
|
constructor() {
|
|
147
199
|
if (typeof globalThis.addEventListener === "function") {
|
|
148
200
|
globalThis.addEventListener("peerjs-dc-handle-chunk", (e: any) => {
|
|
149
201
|
if (e?.detail?.chunkInfo) {
|
|
202
|
+
// If an application-level multi-chunk file transfer is in progress,
|
|
203
|
+
// ignore underlying PeerJS binarypack frame chunks to prevent fighting/flickering.
|
|
204
|
+
if (this._inboundTransfers.size > 0) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
150
208
|
// PeerJS dispatches `count` BEFORE incrementing it (0-indexed pre-increment),
|
|
151
209
|
// so the actual number of received chunks is count + 1.
|
|
152
210
|
const { count: rawCount, total } = e.detail.chunkInfo;
|
|
153
211
|
const received = rawCount + 1;
|
|
154
|
-
|
|
212
|
+
updateTransferProgress({
|
|
155
213
|
count: received,
|
|
156
214
|
total,
|
|
157
215
|
direction: "receive",
|
|
158
216
|
});
|
|
159
217
|
|
|
160
218
|
if (received >= total) {
|
|
161
|
-
|
|
219
|
+
clearTransferProgressDelayed(1500);
|
|
162
220
|
}
|
|
163
221
|
}
|
|
164
222
|
});
|
|
@@ -317,6 +375,113 @@ export class PeerJsChannel implements Channel {
|
|
|
317
375
|
this._roomNotificationHandler = handler;
|
|
318
376
|
}
|
|
319
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Stream file attachments to all other members in a room over WebRTC DataConnections,
|
|
380
|
+
* tagged with the room groupId.
|
|
381
|
+
*/
|
|
382
|
+
async sendAttachmentsToRoom(
|
|
383
|
+
room: RoomMeta,
|
|
384
|
+
groupId: string,
|
|
385
|
+
attachments: MessageAttachment[],
|
|
386
|
+
): Promise<void> {
|
|
387
|
+
const me = this.myPeerId;
|
|
388
|
+
const fileAttachments = (attachments || []).filter(
|
|
389
|
+
(a): a is MessageAttachment & { path: string } => !!a.path,
|
|
390
|
+
);
|
|
391
|
+
if (fileAttachments.length === 0) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
const db = await getDb();
|
|
397
|
+
for (const att of fileAttachments) {
|
|
398
|
+
let bytes: Uint8Array;
|
|
399
|
+
try {
|
|
400
|
+
bytes = await readGroupFileBytes(db, groupId, att.path);
|
|
401
|
+
} catch (err) {
|
|
402
|
+
console.warn(
|
|
403
|
+
`PeerJsChannel: failed to read file ${att.path} for room:`,
|
|
404
|
+
err,
|
|
405
|
+
);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const canonicalName = att.path;
|
|
410
|
+
const mimeType = att.mimeType || "application/octet-stream";
|
|
411
|
+
const totalChunks = Math.max(
|
|
412
|
+
1,
|
|
413
|
+
Math.ceil(bytes.length / FILE_CHUNK_SIZE),
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
for (const member of room.members) {
|
|
417
|
+
if (member.peerId === me) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const conn = this._getOrOpenConnection(member.peerId);
|
|
422
|
+
if (!conn) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
await this._waitForOpen(conn);
|
|
428
|
+
const transferId = ulid();
|
|
429
|
+
|
|
430
|
+
conn.send({
|
|
431
|
+
type: "__file_header",
|
|
432
|
+
id: transferId,
|
|
433
|
+
name: canonicalName,
|
|
434
|
+
mimeType,
|
|
435
|
+
size: bytes.length,
|
|
436
|
+
totalChunks,
|
|
437
|
+
chunkSize: FILE_CHUNK_SIZE,
|
|
438
|
+
groupId,
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
442
|
+
const start = i * FILE_CHUNK_SIZE;
|
|
443
|
+
const end = Math.min(start + FILE_CHUNK_SIZE, bytes.length);
|
|
444
|
+
const chunk = bytes.subarray(start, end);
|
|
445
|
+
const chunkBuf = chunk.buffer.slice(
|
|
446
|
+
chunk.byteOffset,
|
|
447
|
+
chunk.byteOffset + chunk.byteLength,
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
conn.send({
|
|
451
|
+
type: "__file_chunk",
|
|
452
|
+
id: transferId,
|
|
453
|
+
name: canonicalName,
|
|
454
|
+
index: i,
|
|
455
|
+
totalChunks,
|
|
456
|
+
data: chunkBuf,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
updateTransferProgress({
|
|
460
|
+
count: i + 1,
|
|
461
|
+
total: totalChunks,
|
|
462
|
+
direction: "send",
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
await this._waitForBufferDrain(conn);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
clearTransferProgressDelayed(1500);
|
|
469
|
+
} catch (err) {
|
|
470
|
+
console.warn(
|
|
471
|
+
`PeerJsChannel: failed to stream file ${att.path} to room member ${member.peerId}:`,
|
|
472
|
+
err,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
} catch (err) {
|
|
478
|
+
console.error(
|
|
479
|
+
"PeerJsChannel: failed to access DB for room file transfer",
|
|
480
|
+
err,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
320
485
|
setTyping(groupId: string, typing: boolean): void {
|
|
321
486
|
const remotePeerId = groupId.replace(/^peer:/, "");
|
|
322
487
|
const conn = this.connections.get(remotePeerId);
|
|
@@ -466,20 +631,56 @@ export class PeerJsChannel implements Channel {
|
|
|
466
631
|
const mimeType =
|
|
467
632
|
(att as any).mimeType || "application/octet-stream";
|
|
468
633
|
|
|
634
|
+
const transferId = ulid();
|
|
635
|
+
const totalChunks = Math.max(
|
|
636
|
+
1,
|
|
637
|
+
Math.ceil(bytes.length / FILE_CHUNK_SIZE),
|
|
638
|
+
);
|
|
639
|
+
|
|
469
640
|
// Send the header
|
|
470
641
|
conn.send({
|
|
471
642
|
type: "__file_header",
|
|
643
|
+
id: transferId,
|
|
472
644
|
name: canonicalName,
|
|
473
645
|
mimeType,
|
|
474
646
|
size: bytes.length,
|
|
647
|
+
totalChunks,
|
|
648
|
+
chunkSize: FILE_CHUNK_SIZE,
|
|
649
|
+
groupId,
|
|
475
650
|
});
|
|
476
651
|
|
|
477
|
-
// Send
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
652
|
+
// Send each chunk frame sequentially with flow control
|
|
653
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
654
|
+
const start = i * FILE_CHUNK_SIZE;
|
|
655
|
+
const end = Math.min(start + FILE_CHUNK_SIZE, bytes.length);
|
|
656
|
+
const chunk = bytes.subarray(start, end);
|
|
657
|
+
|
|
658
|
+
// Copy into a clean ArrayBuffer slice for transfer
|
|
659
|
+
const chunkBuf = chunk.buffer.slice(
|
|
660
|
+
chunk.byteOffset,
|
|
661
|
+
chunk.byteOffset + chunk.byteLength,
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
conn.send({
|
|
665
|
+
type: "__file_chunk",
|
|
666
|
+
id: transferId,
|
|
667
|
+
name: canonicalName,
|
|
668
|
+
index: i,
|
|
669
|
+
totalChunks,
|
|
670
|
+
data: chunkBuf,
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
updateTransferProgress({
|
|
674
|
+
count: i + 1,
|
|
675
|
+
total: totalChunks,
|
|
676
|
+
direction: "send",
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
// Apply backpressure if the DataChannel buffer is filling up
|
|
680
|
+
await this._waitForBufferDrain(conn);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
clearTransferProgressDelayed(1500);
|
|
483
684
|
|
|
484
685
|
// Prepare the metadata part for the A2A envelope
|
|
485
686
|
fileParts.push({
|
|
@@ -1045,41 +1246,8 @@ export class PeerJsChannel implements Channel {
|
|
|
1045
1246
|
if (data instanceof ArrayBuffer || data instanceof Uint8Array) {
|
|
1046
1247
|
if (this._pendingInboundFile) {
|
|
1047
1248
|
const canonicalName = this._pendingInboundFile.name;
|
|
1048
|
-
const groupId = `peer:${remotePeerId}`;
|
|
1049
1249
|
const bytes = new Uint8Array(data as ArrayBuffer);
|
|
1050
|
-
|
|
1051
|
-
getDb().then(async (db) => {
|
|
1052
|
-
let finalName = canonicalName;
|
|
1053
|
-
let counter = 1;
|
|
1054
|
-
|
|
1055
|
-
while (await groupFileExists(db, groupId, finalName)) {
|
|
1056
|
-
const lastDotIndex = canonicalName.lastIndexOf(".");
|
|
1057
|
-
const hasExt =
|
|
1058
|
-
lastDotIndex > 0 && lastDotIndex < canonicalName.length - 1;
|
|
1059
|
-
|
|
1060
|
-
if (hasExt) {
|
|
1061
|
-
const base = canonicalName.substring(0, lastDotIndex);
|
|
1062
|
-
const ext = canonicalName.substring(lastDotIndex);
|
|
1063
|
-
finalName = `${base} (${counter})${ext}`;
|
|
1064
|
-
} else {
|
|
1065
|
-
finalName = `${canonicalName} (${counter})`;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
counter++;
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
if (finalName !== canonicalName) {
|
|
1072
|
-
this._inboundRemap.set(canonicalName, finalName);
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
writeGroupFileBytes(db, groupId, finalName, bytes).catch((err) => {
|
|
1076
|
-
console.error(
|
|
1077
|
-
"PeerJsChannel: failed to write inbound file bytes",
|
|
1078
|
-
err,
|
|
1079
|
-
);
|
|
1080
|
-
});
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1250
|
+
this._saveInboundFile(remotePeerId, canonicalName, bytes);
|
|
1083
1251
|
this._pendingInboundFile = null;
|
|
1084
1252
|
}
|
|
1085
1253
|
|
|
@@ -1093,12 +1261,94 @@ export class PeerJsChannel implements Channel {
|
|
|
1093
1261
|
const msg = data as Record<string, unknown>;
|
|
1094
1262
|
|
|
1095
1263
|
if (msg.type === "__file_header") {
|
|
1264
|
+
const transferId = (msg.id as string) || (msg.name as string);
|
|
1265
|
+
const totalChunks = (msg.totalChunks as number) || 1;
|
|
1266
|
+
const existing = this._inboundTransfers.get(transferId);
|
|
1267
|
+
|
|
1268
|
+
this._inboundTransfers.set(transferId, {
|
|
1269
|
+
transferId,
|
|
1270
|
+
name: msg.name as string,
|
|
1271
|
+
mimeType: (msg.mimeType as string) || "application/octet-stream",
|
|
1272
|
+
size: (msg.size as number) || 0,
|
|
1273
|
+
totalChunks,
|
|
1274
|
+
receivedChunks: existing?.receivedChunks || new Map(),
|
|
1275
|
+
receivedBytes: existing?.receivedBytes || 0,
|
|
1276
|
+
targetGroupId:
|
|
1277
|
+
typeof msg.groupId === "string" ? msg.groupId : undefined,
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1096
1280
|
this._pendingInboundFile = {
|
|
1097
1281
|
name: msg.name as string,
|
|
1098
1282
|
mimeType: msg.mimeType as string,
|
|
1099
1283
|
size: msg.size as number,
|
|
1100
1284
|
};
|
|
1101
1285
|
|
|
1286
|
+
updateTransferProgress({
|
|
1287
|
+
count: 0,
|
|
1288
|
+
total: totalChunks,
|
|
1289
|
+
direction: "receive",
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (msg.type === "__file_chunk") {
|
|
1296
|
+
const transferId = (msg.id as string) || (msg.name as string);
|
|
1297
|
+
let transfer = this._inboundTransfers.get(transferId);
|
|
1298
|
+
if (!transfer) {
|
|
1299
|
+
const totalChunks = (msg.totalChunks as number) || 1;
|
|
1300
|
+
transfer = {
|
|
1301
|
+
transferId,
|
|
1302
|
+
name: msg.name as string,
|
|
1303
|
+
mimeType: "application/octet-stream",
|
|
1304
|
+
size: 0,
|
|
1305
|
+
totalChunks,
|
|
1306
|
+
receivedChunks: new Map(),
|
|
1307
|
+
receivedBytes: 0,
|
|
1308
|
+
};
|
|
1309
|
+
this._inboundTransfers.set(transferId, transfer);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
const index = typeof msg.index === "number" ? msg.index : 0;
|
|
1313
|
+
const chunkData =
|
|
1314
|
+
msg.data instanceof ArrayBuffer
|
|
1315
|
+
? new Uint8Array(msg.data)
|
|
1316
|
+
: msg.data instanceof Uint8Array
|
|
1317
|
+
? msg.data
|
|
1318
|
+
: new Uint8Array((msg.data as any) || []);
|
|
1319
|
+
|
|
1320
|
+
if (!transfer.receivedChunks.has(index)) {
|
|
1321
|
+
transfer.receivedChunks.set(index, chunkData);
|
|
1322
|
+
transfer.receivedBytes += chunkData.byteLength;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
updateTransferProgress({
|
|
1326
|
+
count: transfer.receivedChunks.size,
|
|
1327
|
+
total: transfer.totalChunks,
|
|
1328
|
+
direction: "receive",
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
if (transfer.receivedChunks.size >= transfer.totalChunks) {
|
|
1332
|
+
const completeBytes = new Uint8Array(
|
|
1333
|
+
transfer.size || transfer.receivedBytes,
|
|
1334
|
+
);
|
|
1335
|
+
let offset = 0;
|
|
1336
|
+
for (let i = 0; i < transfer.totalChunks; i++) {
|
|
1337
|
+
const part = transfer.receivedChunks.get(i);
|
|
1338
|
+
if (part) {
|
|
1339
|
+
completeBytes.set(part, offset);
|
|
1340
|
+
offset += part.byteLength;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
const targetGroupId = transfer.targetGroupId || `peer:${remotePeerId}`;
|
|
1345
|
+
this._saveInboundFile(targetGroupId, transfer.name, completeBytes);
|
|
1346
|
+
this._inboundTransfers.delete(transferId);
|
|
1347
|
+
this._pendingInboundFile = null;
|
|
1348
|
+
|
|
1349
|
+
clearTransferProgressDelayed(1500);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1102
1352
|
return;
|
|
1103
1353
|
}
|
|
1104
1354
|
|
|
@@ -1220,6 +1470,9 @@ export class PeerJsChannel implements Channel {
|
|
|
1220
1470
|
return;
|
|
1221
1471
|
}
|
|
1222
1472
|
|
|
1473
|
+
// Attach chunk progress interceptor to track inbound file transfers
|
|
1474
|
+
this._attachChunkProgressTracker(conn);
|
|
1475
|
+
|
|
1223
1476
|
// Register the connection
|
|
1224
1477
|
this.connections.set(remotePeerId, conn);
|
|
1225
1478
|
this._updateConnectedPeersSignal();
|
|
@@ -1273,6 +1526,63 @@ export class PeerJsChannel implements Channel {
|
|
|
1273
1526
|
});
|
|
1274
1527
|
}
|
|
1275
1528
|
|
|
1529
|
+
/**
|
|
1530
|
+
* Intercept incoming BinaryPack chunk handling on DataConnection to track
|
|
1531
|
+
* file reception progress without requiring a custom PeerJS fork.
|
|
1532
|
+
*/
|
|
1533
|
+
private _attachChunkProgressTracker(conn: DataConnection): void {
|
|
1534
|
+
const connAny = conn as any;
|
|
1535
|
+
if (connAny._chunkProgressPatched) {
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
connAny._chunkProgressPatched = true;
|
|
1539
|
+
|
|
1540
|
+
const self = this;
|
|
1541
|
+
const origHandleChunk = connAny._handleChunk;
|
|
1542
|
+
if (typeof origHandleChunk === "function") {
|
|
1543
|
+
connAny._handleChunk = function (data: any) {
|
|
1544
|
+
if (data && typeof data === "object" && "__peerData" in data) {
|
|
1545
|
+
// If an application-level multi-chunk file transfer is in progress, ignore
|
|
1546
|
+
// raw PeerJS binarypack frame chunks to prevent fighting/flickering.
|
|
1547
|
+
if (self._inboundTransfers.size === 0) {
|
|
1548
|
+
const id = data.__peerData;
|
|
1549
|
+
const currentCount = this._chunkedData?.[id]?.count ?? 0;
|
|
1550
|
+
const total = data.total;
|
|
1551
|
+
|
|
1552
|
+
if (
|
|
1553
|
+
typeof globalThis.dispatchEvent === "function" &&
|
|
1554
|
+
typeof CustomEvent === "function"
|
|
1555
|
+
) {
|
|
1556
|
+
globalThis.dispatchEvent(
|
|
1557
|
+
new CustomEvent("peerjs-dc-handle-chunk", {
|
|
1558
|
+
detail: {
|
|
1559
|
+
chunkInfo: {
|
|
1560
|
+
count: currentCount,
|
|
1561
|
+
total,
|
|
1562
|
+
},
|
|
1563
|
+
},
|
|
1564
|
+
}),
|
|
1565
|
+
);
|
|
1566
|
+
} else {
|
|
1567
|
+
const received = currentCount + 1;
|
|
1568
|
+
updateTransferProgress({
|
|
1569
|
+
count: received,
|
|
1570
|
+
total,
|
|
1571
|
+
direction: "receive",
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
if (received >= total) {
|
|
1575
|
+
clearTransferProgressDelayed(1500);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
return origHandleChunk.call(this, data);
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1276
1586
|
/**
|
|
1277
1587
|
* Handle an inbound task status update notification.
|
|
1278
1588
|
*/
|
|
@@ -1471,48 +1781,83 @@ export class PeerJsChannel implements Channel {
|
|
|
1471
1781
|
}
|
|
1472
1782
|
|
|
1473
1783
|
/**
|
|
1474
|
-
*
|
|
1475
|
-
*
|
|
1784
|
+
* Pause execution if the underlying WebRTC DataChannel send buffer
|
|
1785
|
+
* exceeds the drain threshold, preventing buffer bloat during large transfers.
|
|
1476
1786
|
*/
|
|
1477
|
-
private
|
|
1787
|
+
private async _waitForBufferDrain(
|
|
1478
1788
|
conn: DataConnection,
|
|
1479
|
-
|
|
1789
|
+
threshold: number = FILE_BUFFER_DRAIN_THRESHOLD,
|
|
1480
1790
|
): Promise<void> {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
this.transferProgressSignal.set({
|
|
1491
|
-
count: 0,
|
|
1492
|
-
total: totalBytes,
|
|
1493
|
-
direction: "send",
|
|
1494
|
-
});
|
|
1791
|
+
const rawDc =
|
|
1792
|
+
((conn as any).dataChannel as RTCDataChannel | undefined) ||
|
|
1793
|
+
((conn as any)._dc as RTCDataChannel | undefined);
|
|
1794
|
+
if (!rawDc || typeof rawDc.bufferedAmount !== "number") {
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
if (rawDc.bufferedAmount <= threshold) {
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1495
1800
|
|
|
1801
|
+
await new Promise<void>((resolve) => {
|
|
1496
1802
|
const poll = setInterval(() => {
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
direction: "send",
|
|
1503
|
-
});
|
|
1803
|
+
if (!rawDc || rawDc.bufferedAmount <= threshold) {
|
|
1804
|
+
clearInterval(poll);
|
|
1805
|
+
resolve();
|
|
1806
|
+
}
|
|
1807
|
+
}, 20);
|
|
1504
1808
|
|
|
1505
|
-
|
|
1809
|
+
if (typeof rawDc.addEventListener === "function") {
|
|
1810
|
+
const onLow = () => {
|
|
1506
1811
|
clearInterval(poll);
|
|
1507
|
-
|
|
1508
|
-
count: totalBytes,
|
|
1509
|
-
total: totalBytes,
|
|
1510
|
-
direction: "send",
|
|
1511
|
-
});
|
|
1512
|
-
setTimeout(() => this.transferProgressSignal.set(null), 1500);
|
|
1812
|
+
rawDc.removeEventListener("bufferedamountlow", onLow);
|
|
1513
1813
|
resolve();
|
|
1814
|
+
};
|
|
1815
|
+
try {
|
|
1816
|
+
rawDc.bufferedAmountLowThreshold = threshold;
|
|
1817
|
+
rawDc.addEventListener("bufferedamountlow", onLow, { once: true });
|
|
1818
|
+
} catch {
|
|
1819
|
+
// Fall back to polling if bufferedAmountLowThreshold throws
|
|
1514
1820
|
}
|
|
1515
|
-
}
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* Persist complete received file bytes into group storage, handling
|
|
1827
|
+
* duplicate filename incrementing and path remapping.
|
|
1828
|
+
*/
|
|
1829
|
+
private _saveInboundFile(
|
|
1830
|
+
groupId: string,
|
|
1831
|
+
canonicalName: string,
|
|
1832
|
+
bytes: Uint8Array,
|
|
1833
|
+
): void {
|
|
1834
|
+
getDb().then(async (db) => {
|
|
1835
|
+
let finalName = canonicalName;
|
|
1836
|
+
let counter = 1;
|
|
1837
|
+
|
|
1838
|
+
while (await groupFileExists(db, groupId, finalName)) {
|
|
1839
|
+
const lastDotIndex = canonicalName.lastIndexOf(".");
|
|
1840
|
+
const hasExt =
|
|
1841
|
+
lastDotIndex > 0 && lastDotIndex < canonicalName.length - 1;
|
|
1842
|
+
|
|
1843
|
+
if (hasExt) {
|
|
1844
|
+
const base = canonicalName.substring(0, lastDotIndex);
|
|
1845
|
+
const ext = canonicalName.substring(lastDotIndex);
|
|
1846
|
+
finalName = `${base} (${counter})${ext}`;
|
|
1847
|
+
} else {
|
|
1848
|
+
finalName = `${canonicalName} (${counter})`;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
counter++;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
if (finalName !== canonicalName) {
|
|
1855
|
+
this._inboundRemap.set(canonicalName, finalName);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
writeGroupFileBytes(db, groupId, finalName, bytes).catch((err) => {
|
|
1859
|
+
console.error("PeerJsChannel: failed to write inbound file bytes", err);
|
|
1860
|
+
});
|
|
1516
1861
|
});
|
|
1517
1862
|
}
|
|
1518
1863
|
|
|
@@ -53,4 +53,39 @@ describe("RoomChannel", () => {
|
|
|
53
53
|
|
|
54
54
|
expect(received).toEqual([msg]);
|
|
55
55
|
});
|
|
56
|
+
|
|
57
|
+
it("streams attachments to room members via peerjs before broadcasting", async () => {
|
|
58
|
+
const channel = new RoomChannel();
|
|
59
|
+
const manager = {
|
|
60
|
+
...makeManager(),
|
|
61
|
+
get: jest.fn().mockReturnValue({
|
|
62
|
+
roomId: "abc",
|
|
63
|
+
name: "Test Room",
|
|
64
|
+
hostPeerId: "host1",
|
|
65
|
+
members: [{ peerId: "host1" }, { peerId: "peer2" }],
|
|
66
|
+
createdAt: 1,
|
|
67
|
+
}),
|
|
68
|
+
};
|
|
69
|
+
const mockPeerJs = {
|
|
70
|
+
sendAttachmentsToRoom: jest.fn<any>().mockResolvedValue(undefined),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
channel.setManager(manager as any);
|
|
74
|
+
channel.setPeerJs(mockPeerJs as any);
|
|
75
|
+
|
|
76
|
+
const attachments = [{ fileName: "doc.pdf", path: "doc.pdf" }];
|
|
77
|
+
await channel.send("room:abc", "here is the file", attachments);
|
|
78
|
+
|
|
79
|
+
expect(manager.get).toHaveBeenCalledWith("abc");
|
|
80
|
+
expect(mockPeerJs.sendAttachmentsToRoom).toHaveBeenCalledWith(
|
|
81
|
+
expect.objectContaining({ roomId: "abc" }),
|
|
82
|
+
"room:abc",
|
|
83
|
+
attachments,
|
|
84
|
+
);
|
|
85
|
+
expect(manager.broadcast).toHaveBeenCalledWith(
|
|
86
|
+
"abc",
|
|
87
|
+
"here is the file",
|
|
88
|
+
attachments,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
56
91
|
});
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
InboundMessage,
|
|
22
22
|
} from "./types.js";
|
|
23
23
|
|
|
24
|
+
import type { PeerJsChannel } from "./peerjs.js";
|
|
24
25
|
import type { RoomManager } from "./room-manager.js";
|
|
25
26
|
|
|
26
27
|
export class RoomChannel implements Channel {
|
|
@@ -31,6 +32,7 @@ export class RoomChannel implements Channel {
|
|
|
31
32
|
typingCallback: ChannelTypingCallback | null = null;
|
|
32
33
|
|
|
33
34
|
private _manager: RoomManager | null = null;
|
|
35
|
+
private _peerjs: PeerJsChannel | null = null;
|
|
34
36
|
|
|
35
37
|
/** Deliver an inbound room message (called by the {@link RoomManager}). */
|
|
36
38
|
deliverInbound(msg: InboundMessage): void {
|
|
@@ -50,6 +52,11 @@ export class RoomChannel implements Channel {
|
|
|
50
52
|
this._manager = manager;
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
/** Wire the PeerJS channel used for WebRTC file streaming. */
|
|
56
|
+
setPeerJs(peerjs: PeerJsChannel): void {
|
|
57
|
+
this._peerjs = peerjs;
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
setTyping(_groupId: string, _typing: boolean): void {
|
|
54
61
|
// Typing presence within rooms is not propagated in this version.
|
|
55
62
|
}
|
|
@@ -72,6 +79,14 @@ export class RoomChannel implements Channel {
|
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
const roomId = roomIdFromGroupId(groupId);
|
|
82
|
+
|
|
83
|
+
if (attachments && attachments.length > 0 && this._peerjs) {
|
|
84
|
+
const room = this._manager.get(roomId);
|
|
85
|
+
if (room) {
|
|
86
|
+
await this._peerjs.sendAttachmentsToRoom(room, groupId, attachments);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
75
90
|
this._manager.broadcast(roomId, text, attachments);
|
|
76
91
|
}
|
|
77
92
|
}
|