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
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
DEFAULT_PROMPT_API_FALLBACK_MODEL,
|
|
7
7
|
DEFAULT_PROVIDER,
|
|
8
8
|
} from "../../config/config.js";
|
|
9
|
+
import { decodePathSegment } from "../../core/app-routes.js";
|
|
9
10
|
|
|
10
11
|
import { shouldInstallE2eBridge } from "../../testing/e2e-bridge.js";
|
|
11
12
|
|
|
@@ -1160,7 +1161,10 @@ export class ShadowClawChat extends ShadowClawElement {
|
|
|
1160
1161
|
return null;
|
|
1161
1162
|
}
|
|
1162
1163
|
|
|
1163
|
-
const parts = normalized
|
|
1164
|
+
const parts = normalized
|
|
1165
|
+
.split("/")
|
|
1166
|
+
.filter(Boolean)
|
|
1167
|
+
.map((part) => decodePathSegment(part));
|
|
1164
1168
|
if (parts.some((part) => part === "..")) {
|
|
1165
1169
|
return null;
|
|
1166
1170
|
}
|
|
@@ -1445,6 +1445,31 @@ describe("shadow-claw-file-viewer", () => {
|
|
|
1445
1445
|
);
|
|
1446
1446
|
});
|
|
1447
1447
|
|
|
1448
|
+
it("rewrites relative asset hrefs with spaces in markdown preview HTML", () => {
|
|
1449
|
+
const component = new ShadowClawFileViewer();
|
|
1450
|
+
const inputHtml =
|
|
1451
|
+
'<p><a href="./My%20File%20With%20Spaces%20In%20The%20Name.md">file</a></p>';
|
|
1452
|
+
const rewritten = component.rewriteWorkspacePreviewHtml(
|
|
1453
|
+
inputHtml,
|
|
1454
|
+
"index.md",
|
|
1455
|
+
);
|
|
1456
|
+
expect(rewritten).toContain(
|
|
1457
|
+
'href="/files/test-group/My%20File%20With%20Spaces%20In%20The%20Name.md"',
|
|
1458
|
+
);
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
it("rewrites relative asset hrefs in subfolders with spaces in markdown preview HTML", () => {
|
|
1462
|
+
const component = new ShadowClawFileViewer();
|
|
1463
|
+
const inputHtml = '<p><a href="sub%20folder/My%20File.md">file</a></p>';
|
|
1464
|
+
const rewritten = component.rewriteWorkspacePreviewHtml(
|
|
1465
|
+
inputHtml,
|
|
1466
|
+
"index.md",
|
|
1467
|
+
);
|
|
1468
|
+
expect(rewritten).toContain(
|
|
1469
|
+
'href="/files/test-group/sub%20folder/My%20File.md"',
|
|
1470
|
+
);
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1448
1473
|
it("rewrites relative asset hrefs when subpath base path is active", () => {
|
|
1449
1474
|
document.querySelectorAll("base").forEach((el) => el.remove());
|
|
1450
1475
|
const baseEl = document.createElement("base");
|
|
@@ -1530,6 +1555,59 @@ describe("shadow-claw-file-viewer", () => {
|
|
|
1530
1555
|
).toBe("weather/README.md");
|
|
1531
1556
|
});
|
|
1532
1557
|
|
|
1558
|
+
it("resolves files and subfolders with spaces (both literal and percent-encoded)", () => {
|
|
1559
|
+
const component = new ShadowClawFileViewer();
|
|
1560
|
+
|
|
1561
|
+
expect(
|
|
1562
|
+
component.resolveWorkspaceLinkPath(
|
|
1563
|
+
"My File With Spaces In The Name.md",
|
|
1564
|
+
"README.md",
|
|
1565
|
+
),
|
|
1566
|
+
).toBe("My File With Spaces In The Name.md");
|
|
1567
|
+
|
|
1568
|
+
expect(
|
|
1569
|
+
component.resolveWorkspaceLinkPath(
|
|
1570
|
+
"./My File With Spaces In The Name.md",
|
|
1571
|
+
"README.md",
|
|
1572
|
+
),
|
|
1573
|
+
).toBe("My File With Spaces In The Name.md");
|
|
1574
|
+
|
|
1575
|
+
expect(
|
|
1576
|
+
component.resolveWorkspaceLinkPath(
|
|
1577
|
+
"./My%20File%20With%20Spaces%20In%20The%20Name.md",
|
|
1578
|
+
"README.md",
|
|
1579
|
+
),
|
|
1580
|
+
).toBe("My File With Spaces In The Name.md");
|
|
1581
|
+
|
|
1582
|
+
expect(
|
|
1583
|
+
component.resolveWorkspaceLinkPath(
|
|
1584
|
+
"sub folder/My File With Spaces In The Name.md",
|
|
1585
|
+
"README.md",
|
|
1586
|
+
),
|
|
1587
|
+
).toBe("sub folder/My File With Spaces In The Name.md");
|
|
1588
|
+
|
|
1589
|
+
expect(
|
|
1590
|
+
component.resolveWorkspaceLinkPath(
|
|
1591
|
+
"sub%20folder/My%20File%20With%20Spaces%20In%20The%20Name.md",
|
|
1592
|
+
"README.md",
|
|
1593
|
+
),
|
|
1594
|
+
).toBe("sub folder/My File With Spaces In The Name.md");
|
|
1595
|
+
|
|
1596
|
+
expect(
|
|
1597
|
+
component.resolveWorkspaceLinkPath(
|
|
1598
|
+
"./child note.md",
|
|
1599
|
+
"my sub folder/README.md",
|
|
1600
|
+
),
|
|
1601
|
+
).toBe("my sub folder/child note.md");
|
|
1602
|
+
|
|
1603
|
+
expect(
|
|
1604
|
+
component.resolveWorkspaceLinkPath(
|
|
1605
|
+
"../other folder/doc.md",
|
|
1606
|
+
"my sub folder/README.md",
|
|
1607
|
+
),
|
|
1608
|
+
).toBe("other folder/doc.md");
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1533
1611
|
it("rejects external and root-escape links", () => {
|
|
1534
1612
|
const component = new ShadowClawFileViewer();
|
|
1535
1613
|
|
|
@@ -6,6 +6,7 @@ import { splitFrontmatter } from "../../common/utils/frontmatter.mjs";
|
|
|
6
6
|
import { CONFIG_KEYS } from "../../config/config.js";
|
|
7
7
|
import {
|
|
8
8
|
applyBasePath,
|
|
9
|
+
decodePathSegment,
|
|
9
10
|
getFileRouteDirPath,
|
|
10
11
|
getWorkspaceRouteRequestPath,
|
|
11
12
|
isPossibleAppRoute,
|
|
@@ -1068,7 +1069,10 @@ export class ShadowClawFileViewer extends ShadowClawElement {
|
|
|
1068
1069
|
|
|
1069
1070
|
if (!isAbsolute) {
|
|
1070
1071
|
const baseNormalized = basePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1071
|
-
const baseParts = baseNormalized
|
|
1072
|
+
const baseParts = baseNormalized
|
|
1073
|
+
.split("/")
|
|
1074
|
+
.filter(Boolean)
|
|
1075
|
+
.map((segment) => decodePathSegment(segment));
|
|
1072
1076
|
baseParts.pop();
|
|
1073
1077
|
stack.push(...baseParts);
|
|
1074
1078
|
}
|
|
@@ -1088,7 +1092,7 @@ export class ShadowClawFileViewer extends ShadowClawElement {
|
|
|
1088
1092
|
continue;
|
|
1089
1093
|
}
|
|
1090
1094
|
|
|
1091
|
-
stack.push(part);
|
|
1095
|
+
stack.push(decodePathSegment(part));
|
|
1092
1096
|
}
|
|
1093
1097
|
|
|
1094
1098
|
return stack.length > 0 ? stack.join("/") : null;
|
|
@@ -76,4 +76,60 @@ describe("resolveWorkspaceLinkPath", () => {
|
|
|
76
76
|
);
|
|
77
77
|
expect(result).toBeNull();
|
|
78
78
|
});
|
|
79
|
+
|
|
80
|
+
it("resolves files and subfolders with spaces (both literal and percent-encoded)", () => {
|
|
81
|
+
expect(
|
|
82
|
+
resolveWorkspaceLinkPath(
|
|
83
|
+
"My File With Spaces In The Name.md",
|
|
84
|
+
"index.md",
|
|
85
|
+
groupId,
|
|
86
|
+
origin,
|
|
87
|
+
),
|
|
88
|
+
).toBe("My File With Spaces In The Name.md");
|
|
89
|
+
|
|
90
|
+
expect(
|
|
91
|
+
resolveWorkspaceLinkPath(
|
|
92
|
+
"./My File With Spaces In The Name.md",
|
|
93
|
+
"index.md",
|
|
94
|
+
groupId,
|
|
95
|
+
origin,
|
|
96
|
+
),
|
|
97
|
+
).toBe("My File With Spaces In The Name.md");
|
|
98
|
+
|
|
99
|
+
expect(
|
|
100
|
+
resolveWorkspaceLinkPath(
|
|
101
|
+
"./My%20File%20With%20Spaces%20In%20The%20Name.md",
|
|
102
|
+
"index.md",
|
|
103
|
+
groupId,
|
|
104
|
+
origin,
|
|
105
|
+
),
|
|
106
|
+
).toBe("My File With Spaces In The Name.md");
|
|
107
|
+
|
|
108
|
+
expect(
|
|
109
|
+
resolveWorkspaceLinkPath(
|
|
110
|
+
"sub folder/My File With Spaces In The Name.md",
|
|
111
|
+
"index.md",
|
|
112
|
+
groupId,
|
|
113
|
+
origin,
|
|
114
|
+
),
|
|
115
|
+
).toBe("sub folder/My File With Spaces In The Name.md");
|
|
116
|
+
|
|
117
|
+
expect(
|
|
118
|
+
resolveWorkspaceLinkPath(
|
|
119
|
+
"sub%20folder/My%20File%20With%20Spaces%20In%20The%20Name.md",
|
|
120
|
+
"index.md",
|
|
121
|
+
groupId,
|
|
122
|
+
origin,
|
|
123
|
+
),
|
|
124
|
+
).toBe("sub folder/My File With Spaces In The Name.md");
|
|
125
|
+
|
|
126
|
+
expect(
|
|
127
|
+
resolveWorkspaceLinkPath(
|
|
128
|
+
"./child.md",
|
|
129
|
+
"sub folder/index.md",
|
|
130
|
+
groupId,
|
|
131
|
+
origin,
|
|
132
|
+
),
|
|
133
|
+
).toBe("sub folder/child.md");
|
|
134
|
+
});
|
|
79
135
|
});
|
|
@@ -137,6 +137,28 @@ describe("shadow-claw-tasks", () => {
|
|
|
137
137
|
).toBe("weather/archive/index.md");
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
+
it("resolves workspace links with spaces and percent-encoded spaces", () => {
|
|
141
|
+
const component = new ShadowClawTasks();
|
|
142
|
+
|
|
143
|
+
expect(
|
|
144
|
+
component.resolveWorkspaceLinkPath(
|
|
145
|
+
"My File With Spaces In The Name.md",
|
|
146
|
+
),
|
|
147
|
+
).toBe("My File With Spaces In The Name.md");
|
|
148
|
+
|
|
149
|
+
expect(
|
|
150
|
+
component.resolveWorkspaceLinkPath(
|
|
151
|
+
"./My%20File%20With%20Spaces%20In%20The%20Name.md",
|
|
152
|
+
),
|
|
153
|
+
).toBe("My File With Spaces In The Name.md");
|
|
154
|
+
|
|
155
|
+
expect(
|
|
156
|
+
component.resolveWorkspaceLinkPath(
|
|
157
|
+
"sub%20folder/My%20File%20With%20Spaces.md",
|
|
158
|
+
),
|
|
159
|
+
).toBe("sub folder/My File With Spaces.md");
|
|
160
|
+
});
|
|
161
|
+
|
|
140
162
|
it("rejects external links and parent traversal", () => {
|
|
141
163
|
const component = new ShadowClawTasks();
|
|
142
164
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { renderMarkdown } from "../../content/markdown.js";
|
|
2
2
|
import { CONFIG_KEYS } from "../../config/config.js";
|
|
3
3
|
import { effect } from "../../core/effect.js";
|
|
4
|
+
import { decodePathSegment } from "../../core/app-routes.js";
|
|
4
5
|
|
|
5
6
|
import { getDb, ShadowClawDatabase } from "../../db/db.js";
|
|
6
7
|
import { getConfig } from "../../db/getConfig.js";
|
|
@@ -629,7 +630,10 @@ export class ShadowClawTasks extends ShadowClawElement {
|
|
|
629
630
|
return null;
|
|
630
631
|
}
|
|
631
632
|
|
|
632
|
-
const parts = normalized
|
|
633
|
+
const parts = normalized
|
|
634
|
+
.split("/")
|
|
635
|
+
.filter(Boolean)
|
|
636
|
+
.map((part) => decodePathSegment(part));
|
|
633
637
|
if (parts.some((part) => part === "..")) {
|
|
634
638
|
return null;
|
|
635
639
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { normalizeMarkdownLinks } from "./markdown-links.js";
|
|
2
|
+
|
|
3
|
+
describe("normalizeMarkdownLinks", () => {
|
|
4
|
+
it("wraps relative link destinations with spaces in angle brackets", () => {
|
|
5
|
+
const input =
|
|
6
|
+
"[My File With Spaces In The Name.md](./My File With Spaces In The Name.md)";
|
|
7
|
+
const expected =
|
|
8
|
+
"[My File With Spaces In The Name.md](<./My File With Spaces In The Name.md>)";
|
|
9
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("wraps bare filename destinations with spaces in angle brackets", () => {
|
|
13
|
+
const input =
|
|
14
|
+
"[My File With Spaces In The Name.md](My File With Spaces In The Name.md)";
|
|
15
|
+
const expected =
|
|
16
|
+
"[My File With Spaces In The Name.md](<My File With Spaces In The Name.md>)";
|
|
17
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("wraps subfolder paths with spaces in angle brackets", () => {
|
|
21
|
+
const input =
|
|
22
|
+
"[My File](folder with spaces/My File With Spaces In The Name.md)";
|
|
23
|
+
const expected =
|
|
24
|
+
"[My File](<folder with spaces/My File With Spaces In The Name.md>)";
|
|
25
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("wraps relative parent paths with spaces in angle brackets", () => {
|
|
29
|
+
const input =
|
|
30
|
+
"[My File](../parent folder/sub folder/My File With Spaces In The Name.md)";
|
|
31
|
+
const expected =
|
|
32
|
+
"[My File](<../parent folder/sub folder/My File With Spaces In The Name.md>)";
|
|
33
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("wraps HTTP/HTTPS URLs with spaces in angle brackets", () => {
|
|
37
|
+
const input =
|
|
38
|
+
"[My File With Spaces In The Name.md](https://karlherrick.com/What About This)";
|
|
39
|
+
const expected =
|
|
40
|
+
"[My File With Spaces In The Name.md](<https://karlherrick.com/What About This>)";
|
|
41
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("wraps image destinations with spaces in angle brackets", () => {
|
|
45
|
+
const input = "";
|
|
46
|
+
const expected = "";
|
|
47
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("supports link titles when destination contains spaces", () => {
|
|
51
|
+
const input = '[My File](./My File With Spaces.md "File Title")';
|
|
52
|
+
const expected = '[My File](<./My File With Spaces.md> "File Title")';
|
|
53
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("supports single-quoted link titles when destination contains spaces", () => {
|
|
57
|
+
const input = "[My File](./My File With Spaces.md 'File Title')";
|
|
58
|
+
const expected = "[My File](<./My File With Spaces.md> 'File Title')";
|
|
59
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("does not modify links that are already wrapped in angle brackets", () => {
|
|
63
|
+
const input = "[My File](<./My File With Spaces.md>)";
|
|
64
|
+
expect(normalizeMarkdownLinks(input)).toBe(input);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("does not modify links that have no spaces", () => {
|
|
68
|
+
const input = "[My File](./MyFile.md)";
|
|
69
|
+
expect(normalizeMarkdownLinks(input)).toBe(input);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("preserves fenced code blocks without modifying link-like text inside them", () => {
|
|
73
|
+
const input = [
|
|
74
|
+
"Here is some code:",
|
|
75
|
+
"```markdown",
|
|
76
|
+
"[My File With Spaces In The Name.md](./My File With Spaces In The Name.md)",
|
|
77
|
+
"```",
|
|
78
|
+
"And a real link outside:",
|
|
79
|
+
"[My Real File](./Real File.md)",
|
|
80
|
+
].join("\n");
|
|
81
|
+
|
|
82
|
+
const expected = [
|
|
83
|
+
"Here is some code:",
|
|
84
|
+
"```markdown",
|
|
85
|
+
"[My File With Spaces In The Name.md](./My File With Spaces In The Name.md)",
|
|
86
|
+
"```",
|
|
87
|
+
"And a real link outside:",
|
|
88
|
+
"[My Real File](<./Real File.md>)",
|
|
89
|
+
].join("\n");
|
|
90
|
+
|
|
91
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("preserves inline code spans without modifying link-like text inside them", () => {
|
|
95
|
+
const input =
|
|
96
|
+
"Inline: `[My File](./My File.md)` and link: [My Link](./My Link.md)";
|
|
97
|
+
const expected =
|
|
98
|
+
"Inline: `[My File](./My File.md)` and link: [My Link](<./My Link.md>)";
|
|
99
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("handles multiple links on the same line", () => {
|
|
103
|
+
const input =
|
|
104
|
+
"See [File 1](File 1.md) and [File 2](File 2.md) for details.";
|
|
105
|
+
const expected =
|
|
106
|
+
"See [File 1](<File 1.md>) and [File 2](<File 2.md>) for details.";
|
|
107
|
+
expect(normalizeMarkdownLinks(input)).toBe(expected);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShadowClaw — Markdown link destination normalizer
|
|
3
|
+
*
|
|
4
|
+
* CommonMark / Marked specifications require link destinations containing spaces
|
|
5
|
+
* to be enclosed in angle brackets `<...>` (e.g. `[text](<path with spaces.md>)`).
|
|
6
|
+
*
|
|
7
|
+
* This utility allows users to write natural Markdown links and image tags with spaces
|
|
8
|
+
* in filenames and URLs without manually typing `%20` or `<...>`:
|
|
9
|
+
* [Doc](./My File With Spaces.md) -> [Doc](<./My File With Spaces.md>)
|
|
10
|
+
* [Doc](folder/My File With Spaces.md) -> [Doc](<folder/My File With Spaces.md>)
|
|
11
|
+
* [Link](https://example.com/My Page) -> [Link](<https://example.com/My Page>)
|
|
12
|
+
*  -> 
|
|
13
|
+
*
|
|
14
|
+
* Code blocks and inline code spans are strictly preserved.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function normalizeDestination(rawInsideParens: string): string {
|
|
18
|
+
const trimmed = rawInsideParens.trim();
|
|
19
|
+
if (!trimmed) {
|
|
20
|
+
return rawInsideParens;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check for an optional quoted title at the end: "title" or 'title'
|
|
24
|
+
const titleMatch = trimmed.match(/\s+("[^"]*"|'[^']*')\s*$/);
|
|
25
|
+
let dest: string;
|
|
26
|
+
let titlePart = "";
|
|
27
|
+
|
|
28
|
+
if (titleMatch && typeof titleMatch.index === "number") {
|
|
29
|
+
dest = trimmed.slice(0, titleMatch.index).trim();
|
|
30
|
+
titlePart = ` ${titleMatch[1]}`;
|
|
31
|
+
} else {
|
|
32
|
+
dest = trimmed;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If already wrapped in angle brackets or has no spaces, do not modify
|
|
36
|
+
if ((dest.startsWith("<") && dest.endsWith(">")) || !/[\s]/.test(dest)) {
|
|
37
|
+
return rawInsideParens;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return `<${dest}>${titlePart}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Normalizes link and image destinations containing spaces by enclosing them in CommonMark `<...>`.
|
|
45
|
+
*/
|
|
46
|
+
export function normalizeMarkdownLinks(markdown: string): string {
|
|
47
|
+
if (typeof markdown !== "string" || !markdown.includes("[")) {
|
|
48
|
+
return markdown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const length = markdown.length;
|
|
52
|
+
let result = "";
|
|
53
|
+
let i = 0;
|
|
54
|
+
|
|
55
|
+
while (i < length) {
|
|
56
|
+
// 1. Check for fenced code blocks at start of line: ``` or ~~~
|
|
57
|
+
const isStartOfLine = i === 0 || markdown[i - 1] === "\n";
|
|
58
|
+
if (isStartOfLine && (markdown[i] === "`" || markdown[i] === "~")) {
|
|
59
|
+
const char = markdown[i];
|
|
60
|
+
let fenceLen = 1;
|
|
61
|
+
while (i + fenceLen < length && markdown[i + fenceLen] === char) {
|
|
62
|
+
fenceLen++;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (fenceLen >= 3) {
|
|
66
|
+
const fenceStr = char.repeat(fenceLen);
|
|
67
|
+
const endOfOpeningLine = markdown.indexOf("\n", i + fenceLen);
|
|
68
|
+
const searchStart =
|
|
69
|
+
endOfOpeningLine === -1 ? length : endOfOpeningLine + 1;
|
|
70
|
+
const closeIdx = markdown.indexOf(`\n${fenceStr}`, searchStart - 1);
|
|
71
|
+
|
|
72
|
+
if (closeIdx !== -1) {
|
|
73
|
+
const endOfClosingLine = markdown.indexOf(
|
|
74
|
+
"\n",
|
|
75
|
+
closeIdx + 1 + fenceLen,
|
|
76
|
+
);
|
|
77
|
+
const endIdx =
|
|
78
|
+
endOfClosingLine === -1 ? length : endOfClosingLine + 1;
|
|
79
|
+
result += markdown.slice(i, endIdx);
|
|
80
|
+
i = endIdx;
|
|
81
|
+
continue;
|
|
82
|
+
} else {
|
|
83
|
+
// Unclosed fence runs to the end
|
|
84
|
+
result += markdown.slice(i);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 2. Check for inline code spans: ` or `` etc.
|
|
91
|
+
if (markdown[i] === "`") {
|
|
92
|
+
let backtickCount = 1;
|
|
93
|
+
while (
|
|
94
|
+
i + backtickCount < length &&
|
|
95
|
+
markdown[i + backtickCount] === "`"
|
|
96
|
+
) {
|
|
97
|
+
backtickCount++;
|
|
98
|
+
}
|
|
99
|
+
const backticks = "`".repeat(backtickCount);
|
|
100
|
+
const closeIdx = markdown.indexOf(backticks, i + backtickCount);
|
|
101
|
+
|
|
102
|
+
if (closeIdx !== -1) {
|
|
103
|
+
const endIdx = closeIdx + backtickCount;
|
|
104
|
+
result += markdown.slice(i, endIdx);
|
|
105
|
+
i = endIdx;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 3. Check for link or image:  or [...](...)
|
|
111
|
+
const isImage =
|
|
112
|
+
markdown[i] === "!" && i + 1 < length && markdown[i + 1] === "[";
|
|
113
|
+
const isLink = markdown[i] === "[";
|
|
114
|
+
|
|
115
|
+
if (isImage || isLink) {
|
|
116
|
+
const labelStart = isImage ? i + 2 : i + 1;
|
|
117
|
+
let depth = 1;
|
|
118
|
+
let j = labelStart;
|
|
119
|
+
|
|
120
|
+
while (j < length && depth > 0) {
|
|
121
|
+
if (markdown[j] === "\\") {
|
|
122
|
+
j += 2; // Skip escaped character
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (markdown[j] === "[") {
|
|
126
|
+
depth++;
|
|
127
|
+
} else if (markdown[j] === "]") {
|
|
128
|
+
depth--;
|
|
129
|
+
}
|
|
130
|
+
if (depth > 0) {
|
|
131
|
+
j++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// If we found the matching ']' and the next non-whitespace or immediate char is '('
|
|
136
|
+
if (depth === 0 && j < length && markdown[j] === "]") {
|
|
137
|
+
const labelEnd = j;
|
|
138
|
+
const nextCharIdx = j + 1;
|
|
139
|
+
|
|
140
|
+
if (nextCharIdx < length && markdown[nextCharIdx] === "(") {
|
|
141
|
+
// Find matching ')'
|
|
142
|
+
let parenDepth = 1;
|
|
143
|
+
let k = nextCharIdx + 1;
|
|
144
|
+
|
|
145
|
+
while (k < length && parenDepth > 0) {
|
|
146
|
+
if (markdown[k] === "\\") {
|
|
147
|
+
k += 2;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (markdown[k] === "(") {
|
|
151
|
+
parenDepth++;
|
|
152
|
+
} else if (markdown[k] === ")") {
|
|
153
|
+
parenDepth--;
|
|
154
|
+
}
|
|
155
|
+
if (parenDepth > 0) {
|
|
156
|
+
k++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (parenDepth === 0 && k < length && markdown[k] === ")") {
|
|
161
|
+
const rawInsideLabel = markdown.slice(labelStart, labelEnd);
|
|
162
|
+
const rawInsideParens = markdown.slice(nextCharIdx + 1, k);
|
|
163
|
+
|
|
164
|
+
// Recursively normalize label in case there are nested links/images
|
|
165
|
+
const normalizedLabel = rawInsideLabel.includes("[")
|
|
166
|
+
? normalizeMarkdownLinks(rawInsideLabel)
|
|
167
|
+
: rawInsideLabel;
|
|
168
|
+
|
|
169
|
+
const normalizedInsideParens =
|
|
170
|
+
normalizeDestination(rawInsideParens);
|
|
171
|
+
const prefix = isImage ? "`;
|
|
174
|
+
i = k + 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Default: append current character
|
|
182
|
+
result += markdown[i];
|
|
183
|
+
i++;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
@@ -115,4 +115,37 @@ describe("renderMarkdown", () => {
|
|
|
115
115
|
expect(html).not.toContain("<iframe");
|
|
116
116
|
expect(html).not.toContain("phishing-site.example.com");
|
|
117
117
|
});
|
|
118
|
+
|
|
119
|
+
it("renders markdown links with spaces in relative filenames and URLs", async () => {
|
|
120
|
+
const markdown = [
|
|
121
|
+
"[My File With Spaces In The Name.md](./My File With Spaces In The Name.md)",
|
|
122
|
+
"[My File With Spaces In The Name.md](My File With Spaces In The Name.md)",
|
|
123
|
+
"[Subfolder Doc](sub folder/My File With Spaces In The Name.md)",
|
|
124
|
+
"[External](https://karlherrick.com/What About This)",
|
|
125
|
+
].join("\n\n");
|
|
126
|
+
|
|
127
|
+
const html = await renderMarkdown(markdown);
|
|
128
|
+
|
|
129
|
+
expect(html).toContain(
|
|
130
|
+
'<a href="./My%20File%20With%20Spaces%20In%20The%20Name.md">My File With Spaces In The Name.md</a>',
|
|
131
|
+
);
|
|
132
|
+
expect(html).toContain(
|
|
133
|
+
'<a href="My%20File%20With%20Spaces%20In%20The%20Name.md">My File With Spaces In The Name.md</a>',
|
|
134
|
+
);
|
|
135
|
+
expect(html).toContain(
|
|
136
|
+
'<a href="sub%20folder/My%20File%20With%20Spaces%20In%20The%20Name.md">Subfolder Doc</a>',
|
|
137
|
+
);
|
|
138
|
+
expect(html).toContain(
|
|
139
|
+
'<a href="https://karlherrick.com/What%20About%20This">External</a>',
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("renders markdown images with spaces in relative filenames", async () => {
|
|
144
|
+
const markdown = "";
|
|
145
|
+
const html = await renderMarkdown(markdown);
|
|
146
|
+
|
|
147
|
+
expect(html).toContain(
|
|
148
|
+
'<img src="./sub%20folder/My%20Image%20With%20Spaces.png" alt="My Image">',
|
|
149
|
+
);
|
|
150
|
+
});
|
|
118
151
|
});
|
package/src/content/markdown.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import hljs from "highlight.js";
|
|
10
10
|
import { marked } from "marked";
|
|
11
11
|
|
|
12
|
+
import { normalizeMarkdownLinks } from "./markdown-links.js";
|
|
13
|
+
|
|
12
14
|
import {
|
|
13
15
|
createFrontmatterDetailsElement,
|
|
14
16
|
PostFrontmatter,
|
|
@@ -164,9 +166,12 @@ export async function renderMarkdown(
|
|
|
164
166
|
const parsed: { data: PostFrontmatter; content: string } =
|
|
165
167
|
splitFrontmatter(src);
|
|
166
168
|
|
|
169
|
+
// Normalize links and images with spaces in URLs
|
|
170
|
+
const normalizedContent = normalizeMarkdownLinks(parsed.content);
|
|
171
|
+
|
|
167
172
|
// Parse markdown to HTML
|
|
168
173
|
const headingCounts = new Map<string, number>();
|
|
169
|
-
const html = await marked.parse(
|
|
174
|
+
const html = await marked.parse(normalizedContent, {
|
|
170
175
|
gfm: true,
|
|
171
176
|
breaks: options?.breaks ?? false,
|
|
172
177
|
headingCounts,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyBasePath,
|
|
3
3
|
buildRoutePath,
|
|
4
|
+
decodePathSegment,
|
|
4
5
|
getAppBasePath,
|
|
5
6
|
getDeploymentNamespace,
|
|
6
7
|
getFileRouteDirPath,
|
|
@@ -146,6 +147,70 @@ describe("app-routes", () => {
|
|
|
146
147
|
});
|
|
147
148
|
});
|
|
148
149
|
|
|
150
|
+
it("decodes percent-encoded segments in getWorkspaceRouteRequestPath", () => {
|
|
151
|
+
expect(
|
|
152
|
+
getWorkspaceRouteRequestPath(
|
|
153
|
+
"/files/main/My%20File%20With%20Spaces%20In%20The%20Name.md",
|
|
154
|
+
),
|
|
155
|
+
).toEqual({
|
|
156
|
+
groupId: "br:main",
|
|
157
|
+
path: "My File With Spaces In The Name.md",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(
|
|
161
|
+
getWorkspaceRouteRequestPath(
|
|
162
|
+
"/files/main/sub%20folder/My%20File%20With%20Spaces%20In%20The%20Name.md",
|
|
163
|
+
),
|
|
164
|
+
).toEqual({
|
|
165
|
+
groupId: "br:main",
|
|
166
|
+
path: "sub folder/My File With Spaces In The Name.md",
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("decodePathSegment", () => {
|
|
171
|
+
it("decodes percent-encoded strings", () => {
|
|
172
|
+
expect(decodePathSegment("My%20File.md")).toBe("My File.md");
|
|
173
|
+
expect(decodePathSegment("folder%20name")).toBe("folder name");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("leaves unencoded strings untouched", () => {
|
|
177
|
+
expect(decodePathSegment("simple.md")).toBe("simple.md");
|
|
178
|
+
expect(decodePathSegment("already has spaces.md")).toBe(
|
|
179
|
+
"already has spaces.md",
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("safely handles malformed percent encoding without throwing", () => {
|
|
184
|
+
expect(decodePathSegment("100%real")).toBe("100%real");
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("parses route URLs with spaces in filenames and subfolders", () => {
|
|
189
|
+
const route = parseRouteFromUrl(
|
|
190
|
+
new URL(
|
|
191
|
+
"http://localhost:8888/files/main/My%20File%20With%20Spaces%20In%20The%20Name.md",
|
|
192
|
+
),
|
|
193
|
+
);
|
|
194
|
+
expect(route).toEqual({
|
|
195
|
+
page: "files",
|
|
196
|
+
groupId: "br:main",
|
|
197
|
+
path: "My File With Spaces In The Name.md",
|
|
198
|
+
anchor: undefined,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const nestedRoute = parseRouteFromUrl(
|
|
202
|
+
new URL(
|
|
203
|
+
"http://localhost:8888/files/main/sub%20folder/My%20File%20With%20Spaces%20In%20The%20Name.md",
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
expect(nestedRoute).toEqual({
|
|
207
|
+
page: "files",
|
|
208
|
+
groupId: "br:main",
|
|
209
|
+
path: "sub folder/My File With Spaces In The Name.md",
|
|
210
|
+
anchor: undefined,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
149
214
|
describe("applyBasePath", () => {
|
|
150
215
|
// Reset cached base path between tests
|
|
151
216
|
beforeEach(() => {
|