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.
Files changed (277) hide show
  1. package/.agents/skills/main/skill-creator/SKILL.md +3 -2
  2. package/.agents/skills/main/skill-creator/references/frontmatter-spec.md +5 -3
  3. package/.well-known/agent-skills/index.json +1 -1
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/.well-known/mcp.json +1 -1
  6. package/dist/lib/{app-routes-CqBOeA5q.js → app-routes-BAEMLu9g.js} +3 -2
  7. package/dist/lib/app-routes-BAEMLu9g.js.map +1 -0
  8. package/dist/lib/{bundler-8JNIKwOs.js → bundler-CsL7hHMq.js} +4 -9
  9. package/dist/lib/{bundler-8JNIKwOs.js.map → bundler-CsL7hHMq.js.map} +1 -1
  10. package/dist/lib/components/index.js +26 -26
  11. package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
  12. package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
  13. package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
  14. package/dist/lib/components/settings/shadow-claw-git.js +1 -1
  15. package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
  16. package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
  17. package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
  18. package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
  19. package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
  20. package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
  21. package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
  22. package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
  23. package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
  24. package/dist/lib/components/shadow-claw-a2ui.js +1 -1
  25. package/dist/lib/components/shadow-claw-accounts.js +1 -1
  26. package/dist/lib/components/shadow-claw-channel-config.js +1 -1
  27. package/dist/lib/components/shadow-claw-channels.js +1 -1
  28. package/dist/lib/components/shadow-claw-chat.js +1 -1
  29. package/dist/lib/components/shadow-claw-control-plane.js +1 -1
  30. package/dist/lib/components/shadow-claw-conversations.js +1 -1
  31. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  32. package/dist/lib/components/shadow-claw-files.js +1 -1
  33. package/dist/lib/components/shadow-claw-git.js +1 -1
  34. package/dist/lib/components/shadow-claw-integrations.js +1 -1
  35. package/dist/lib/components/shadow-claw-llm.js +1 -1
  36. package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
  37. package/dist/lib/components/shadow-claw-networking.js +1 -1
  38. package/dist/lib/components/shadow-claw-notifications.js +1 -1
  39. package/dist/lib/components/shadow-claw-page-header.js +1 -1
  40. package/dist/lib/components/shadow-claw-pages.js +1 -1
  41. package/dist/lib/components/shadow-claw-peerjs.js +1 -1
  42. package/dist/lib/components/shadow-claw-settings.js +1 -1
  43. package/dist/lib/components/shadow-claw-storage.js +1 -1
  44. package/dist/lib/components/shadow-claw-task-server.js +1 -1
  45. package/dist/lib/components/shadow-claw-tasks.js +1 -1
  46. package/dist/lib/components/shadow-claw-terminal.js +1 -1
  47. package/dist/lib/components/shadow-claw-tools.js +1 -1
  48. package/dist/lib/components/shadow-claw-webvm.js +1 -1
  49. package/dist/lib/components/shadow-claw.js +1 -1
  50. package/dist/lib/{config--CFrw3J3.js → config-DjZ9QWc4.js} +2 -2
  51. package/dist/lib/{config--CFrw3J3.js.map → config-DjZ9QWc4.js.map} +1 -1
  52. package/dist/lib/{configurePeerJs-DG4hliKp.js → configurePeerJs-H0WaoT0w.js} +3 -3
  53. package/dist/lib/{configurePeerJs-DG4hliKp.js.map → configurePeerJs-H0WaoT0w.js.map} +1 -1
  54. package/dist/lib/{connections-DwjzwVz0.js → connections-BURqMIUv.js} +2 -2
  55. package/dist/lib/{connections-DwjzwVz0.js.map → connections-BURqMIUv.js.map} +1 -1
  56. package/dist/lib/content/markdown-links.d.ts +19 -0
  57. package/dist/lib/core/app-routes.d.ts +1 -0
  58. package/dist/lib/{downloadGroupFile-DmcBQU1n.js → downloadGroupFile-B8J4up2a.js} +2 -2
  59. package/dist/lib/{downloadGroupFile-DmcBQU1n.js.map → downloadGroupFile-B8J4up2a.js.map} +1 -1
  60. package/dist/lib/{file-viewer-C3YKnq_-.js → file-viewer-CwvhxAAG.js} +5 -5
  61. package/dist/lib/{file-viewer-C3YKnq_-.js.map → file-viewer-CwvhxAAG.js.map} +1 -1
  62. package/dist/lib/{getGroupDir-m0f-ekgX.js → getGroupDir-QrN2aTo8.js} +4 -4
  63. package/dist/lib/{getGroupDir-m0f-ekgX.js.map → getGroupDir-QrN2aTo8.js.map} +1 -1
  64. package/dist/lib/{git-C-Q6-IVe.js → git-DhZoe1SE.js} +3 -3
  65. package/dist/lib/{git-C-Q6-IVe.js.map → git-DhZoe1SE.js.map} +1 -1
  66. package/dist/lib/{iframe-sanitizer-CUqHuWQ-.js → iframe-sanitizer-Bujgq14T.js} +2 -2
  67. package/dist/lib/{iframe-sanitizer-CUqHuWQ-.js.map → iframe-sanitizer-Bujgq14T.js.map} +1 -1
  68. package/dist/lib/index.js +27 -27
  69. package/dist/lib/{initChatSplitResize-yyBTU6h9.js → initChatSplitResize-BjiWoqC_.js} +2 -2
  70. package/dist/lib/{initChatSplitResize-yyBTU6h9.js.map → initChatSplitResize-BjiWoqC_.js.map} +1 -1
  71. package/dist/lib/{initControlPlane-DnpAzphg.js → initControlPlane-C9JxQCDh.js} +16 -16
  72. package/dist/lib/{initControlPlane-DnpAzphg.js.map → initControlPlane-C9JxQCDh.js.map} +1 -1
  73. package/dist/lib/{markdown-DgdSt2GD.js → markdown-DYHmNjC4.js} +126 -5
  74. package/dist/lib/markdown-DYHmNjC4.js.map +1 -0
  75. package/dist/lib/{mcp-reconnect-C86MR3z8.js → mcp-reconnect-BxJ77WSb.js} +2 -2
  76. package/dist/lib/{mcp-reconnect-C86MR3z8.js.map → mcp-reconnect-BxJ77WSb.js.map} +1 -1
  77. package/dist/lib/{memoryStorage-UR8E8K-H.js → memoryStorage-D3FBwO9T.js} +2 -2
  78. package/dist/lib/{memoryStorage-UR8E8K-H.js.map → memoryStorage-D3FBwO9T.js.map} +1 -1
  79. package/dist/lib/{orchestrator-ClXbJ4Mb.js → orchestrator-C6dc-2OQ.js} +130 -27
  80. package/dist/lib/{orchestrator-ClXbJ4Mb.js.map → orchestrator-C6dc-2OQ.js.map} +1 -1
  81. package/dist/lib/{orchestrator-CjEau_RG.js → orchestrator-DVkyd8K9.js} +27 -17
  82. package/dist/lib/orchestrator-DVkyd8K9.js.map +1 -0
  83. package/dist/lib/{peerjs-BdfpltIe.js → peerjs-BUS9O04I.js} +274 -60
  84. package/dist/lib/peerjs-BUS9O04I.js.map +1 -0
  85. package/dist/lib/{push-client-DjwKknNg.js → push-client-4JsQGmQa.js} +3 -3
  86. package/dist/lib/{push-client-DjwKknNg.js.map → push-client-4JsQGmQa.js.map} +1 -1
  87. package/dist/lib/{readGroupFileBytes-C8oOe8Iu.js → readGroupFileBytes-CSA7u-nJ.js} +2 -2
  88. package/dist/lib/{readGroupFileBytes-C8oOe8Iu.js.map → readGroupFileBytes-CSA7u-nJ.js.map} +1 -1
  89. package/dist/lib/{shadow-claw-DcPGKenn.js → shadow-claw-BMoF8Auf.js} +11 -11
  90. package/dist/lib/{shadow-claw-DcPGKenn.js.map → shadow-claw-BMoF8Auf.js.map} +1 -1
  91. package/dist/lib/{shadow-claw-a2ui-CpZUTzlh.js → shadow-claw-a2ui-2I5nffIn.js} +2 -2
  92. package/dist/lib/{shadow-claw-a2ui-CpZUTzlh.js.map → shadow-claw-a2ui-2I5nffIn.js.map} +1 -1
  93. package/dist/lib/{shadow-claw-accounts-03oJ__XC.js → shadow-claw-accounts-BCjlgvzy.js} +3 -3
  94. package/dist/lib/{shadow-claw-accounts-03oJ__XC.js.map → shadow-claw-accounts-BCjlgvzy.js.map} +1 -1
  95. package/dist/lib/{shadow-claw-channel-config-DEL-aZ7N.js → shadow-claw-channel-config-CxQAmlDE.js} +3 -3
  96. package/dist/lib/{shadow-claw-channel-config-DEL-aZ7N.js.map → shadow-claw-channel-config-CxQAmlDE.js.map} +1 -1
  97. package/dist/lib/{shadow-claw-channels-BgaJiPdE.js → shadow-claw-channels-CuYMUVAG.js} +3 -3
  98. package/dist/lib/{shadow-claw-channels-BgaJiPdE.js.map → shadow-claw-channels-CuYMUVAG.js.map} +1 -1
  99. package/dist/lib/{shadow-claw-chat-mXPQZbwT.js → shadow-claw-chat-RFtPmmyl.js} +13 -12
  100. package/dist/lib/shadow-claw-chat-RFtPmmyl.js.map +1 -0
  101. package/dist/lib/{shadow-claw-control-plane-DO6foOLV.js → shadow-claw-control-plane-siqmRzR1.js} +7 -7
  102. package/dist/lib/{shadow-claw-control-plane-DO6foOLV.js.map → shadow-claw-control-plane-siqmRzR1.js.map} +1 -1
  103. package/dist/lib/{shadow-claw-conversations-BLwHTwdW.js → shadow-claw-conversations-DygK5y1e.js} +7 -7
  104. package/dist/lib/{shadow-claw-conversations-BLwHTwdW.js.map → shadow-claw-conversations-DygK5y1e.js.map} +1 -1
  105. package/dist/lib/{shadow-claw-file-viewer-BSbZvSl-.js → shadow-claw-file-viewer-DGMA7d7H.js} +11 -11
  106. package/dist/lib/shadow-claw-file-viewer-DGMA7d7H.js.map +1 -0
  107. package/dist/lib/{shadow-claw-files-BLVr9Pua.js → shadow-claw-files-CgD1B9J7.js} +8 -8
  108. package/dist/lib/{shadow-claw-files-BLVr9Pua.js.map → shadow-claw-files-CgD1B9J7.js.map} +1 -1
  109. package/dist/lib/{shadow-claw-git-CHC1H86J.js → shadow-claw-git-Bw1jGRrf.js} +6 -6
  110. package/dist/lib/{shadow-claw-git-CHC1H86J.js.map → shadow-claw-git-Bw1jGRrf.js.map} +1 -1
  111. package/dist/lib/{shadow-claw-integrations-Ds-CLPhj.js → shadow-claw-integrations-ChaXtvsg.js} +4 -4
  112. package/dist/lib/{shadow-claw-integrations-Ds-CLPhj.js.map → shadow-claw-integrations-ChaXtvsg.js.map} +1 -1
  113. package/dist/lib/{shadow-claw-llm-CKXfxd3D.js → shadow-claw-llm-CDM4Akjc.js} +6 -6
  114. package/dist/lib/{shadow-claw-llm-CKXfxd3D.js.map → shadow-claw-llm-CDM4Akjc.js.map} +1 -1
  115. package/dist/lib/{shadow-claw-mcp-remote-CT5Q-54E.js → shadow-claw-mcp-remote-BLxs_Z4I.js} +4 -4
  116. package/dist/lib/{shadow-claw-mcp-remote-CT5Q-54E.js.map → shadow-claw-mcp-remote-BLxs_Z4I.js.map} +1 -1
  117. package/dist/lib/{shadow-claw-networking-dpYvfchh.js → shadow-claw-networking-BYt-kMSI.js} +2 -2
  118. package/dist/lib/{shadow-claw-networking-dpYvfchh.js.map → shadow-claw-networking-BYt-kMSI.js.map} +1 -1
  119. package/dist/lib/{shadow-claw-notifications-_YeAVPga.js → shadow-claw-notifications-Dpe4EtZI.js} +3 -3
  120. package/dist/lib/{shadow-claw-notifications-_YeAVPga.js.map → shadow-claw-notifications-Dpe4EtZI.js.map} +1 -1
  121. package/dist/lib/{shadow-claw-page-header-Cti2fzf-.js → shadow-claw-page-header-P4EvWyUr.js} +2 -2
  122. package/dist/lib/{shadow-claw-page-header-Cti2fzf-.js.map → shadow-claw-page-header-P4EvWyUr.js.map} +1 -1
  123. package/dist/lib/{shadow-claw-pages-DVzZriZ7.js → shadow-claw-pages-DWd_c4d6.js} +10 -10
  124. package/dist/lib/{shadow-claw-pages-DVzZriZ7.js.map → shadow-claw-pages-DWd_c4d6.js.map} +1 -1
  125. package/dist/lib/{shadow-claw-peerjs-BvixjeBz.js → shadow-claw-peerjs-D928817m.js} +3 -3
  126. package/dist/lib/{shadow-claw-peerjs-BvixjeBz.js.map → shadow-claw-peerjs-D928817m.js.map} +1 -1
  127. package/dist/lib/{shadow-claw-settings-DV9-gD4Q.js → shadow-claw-settings-C8OPrSqs.js} +8 -8
  128. package/dist/lib/{shadow-claw-settings-DV9-gD4Q.js.map → shadow-claw-settings-C8OPrSqs.js.map} +1 -1
  129. package/dist/lib/{shadow-claw-storage-Bv__kXG6.js → shadow-claw-storage-CZ2uZJNw.js} +5 -5
  130. package/dist/lib/{shadow-claw-storage-Bv__kXG6.js.map → shadow-claw-storage-CZ2uZJNw.js.map} +1 -1
  131. package/dist/lib/{shadow-claw-task-server-DNOe8PWK.js → shadow-claw-task-server-CLT018BX.js} +2 -2
  132. package/dist/lib/{shadow-claw-task-server-DNOe8PWK.js.map → shadow-claw-task-server-CLT018BX.js.map} +1 -1
  133. package/dist/lib/{shadow-claw-tasks-qmcDT4PU.js → shadow-claw-tasks-D5h-lGmq.js} +9 -8
  134. package/dist/lib/shadow-claw-tasks-D5h-lGmq.js.map +1 -0
  135. package/dist/lib/{shadow-claw-terminal-0QUWP8of.js → shadow-claw-terminal-Cy1O6KVM.js} +2 -2
  136. package/dist/lib/{shadow-claw-terminal-0QUWP8of.js.map → shadow-claw-terminal-Cy1O6KVM.js.map} +1 -1
  137. package/dist/lib/{shadow-claw-tools-Bs_bmI6-.js → shadow-claw-tools-CVD06bCR.js} +9 -9
  138. package/dist/lib/{shadow-claw-tools-Bs_bmI6-.js.map → shadow-claw-tools-CVD06bCR.js.map} +1 -1
  139. package/dist/lib/{shadow-claw-webvm-DYaoL7QY.js → shadow-claw-webvm-DoGl5ZSJ.js} +3 -3
  140. package/dist/lib/{shadow-claw-webvm-DYaoL7QY.js.map → shadow-claw-webvm-DoGl5ZSJ.js.map} +1 -1
  141. package/dist/lib/subsystems/channels/peerjs.d.ts +37 -4
  142. package/dist/lib/subsystems/channels/room.d.ts +4 -0
  143. package/dist/lib/{syncWebMcpRegistration-BhbqI6R2.js → syncWebMcpRegistration-BeYkQEvj.js} +5 -5
  144. package/dist/lib/{syncWebMcpRegistration-BhbqI6R2.js.map → syncWebMcpRegistration-BeYkQEvj.js.map} +1 -1
  145. package/dist/lib/tools-C3Kft940.js +1 -0
  146. package/dist/lib/{tools-DBaMRiEu.js → tools-DKzPWc0q.js} +2 -2
  147. package/dist/lib/{tools-DBaMRiEu.js.map → tools-DKzPWc0q.js.map} +1 -1
  148. package/dist/lib/{tools-DwwB9RBa.js → tools-DqcJe8LS.js} +5 -5
  149. package/dist/lib/{tools-DwwB9RBa.js.map → tools-DqcJe8LS.js.map} +1 -1
  150. package/dist/lib/{trusted-types-Dx0RZlrd.js → trusted-types-Oz7xXJAY.js} +4 -4
  151. package/dist/lib/{trusted-types-Dx0RZlrd.js.map → trusted-types-Oz7xXJAY.js.map} +1 -1
  152. package/dist/lib/utils/index.js +1 -1
  153. package/dist/lib/utils/namespacedStorage.js +1 -1
  154. package/dist/lib/utils/utils.js +1 -1
  155. package/dist/lib/{webmcp-j_1_T3wf.js → webmcp-BC8_WafM.js} +4 -4
  156. package/dist/lib/{webmcp-j_1_T3wf.js.map → webmcp-BC8_WafM.js.map} +1 -1
  157. package/dist/lib/worker/utils/system-prompt.d.ts +4 -1
  158. package/dist/public/.agents/skills/main/skill-creator/SKILL.md +3 -2
  159. package/dist/public/.agents/skills/main/skill-creator/references/frontmatter-spec.md +5 -3
  160. package/dist/public/.well-known/agent-skills/index.json +1 -1
  161. package/dist/public/.well-known/mcp/server-card.json +1 -1
  162. package/dist/public/.well-known/mcp.json +1 -1
  163. package/dist/public/agent.worker.js +18 -17
  164. package/dist/public/app-routes-BMQ4HFaM.js +1 -0
  165. package/dist/public/{bundler-Ca-Xwln8.js → bundler-0if-QelV.js} +4 -4
  166. package/dist/public/{config-CrHchneq.js → config-Bnwx4EfS.js} +1 -1
  167. package/dist/public/{configurePeerJs-B470pdeU.js → configurePeerJs-DkyGvwk_.js} +1 -1
  168. package/dist/public/{connections-DvbgdkaB.js → connections-6575KDGE.js} +1 -1
  169. package/dist/public/{custom-element-security-FU04Cq05.js → custom-element-security-BQJQ_oKz.js} +1 -1
  170. package/dist/public/docs/example/article/index.html +2 -2
  171. package/dist/public/docs/publishing/index.html +2 -2
  172. package/dist/public/docs/skill-creator/index.html +2 -2
  173. package/dist/public/docs/subsystems/skills.md +3 -1
  174. package/dist/public/{downloadGroupFile-D8KRJGGL.js → downloadGroupFile-Wo1Sxdhr.js} +1 -1
  175. package/dist/public/{e2e-bridge-CSW0S5Yz.js → e2e-bridge-CvLCUU2O.js} +1 -1
  176. package/dist/public/{file-viewer-Cs-riNvy.js → file-viewer-CIjqAvNF.js} +1 -1
  177. package/dist/public/{getGroupDir-P1h9wl6S.js → getGroupDir-BwprrdrQ.js} +1 -1
  178. package/dist/public/{git-7Vq_SJSp.js → git-BSnY2D-A.js} +2 -2
  179. package/dist/public/{iframe-sanitizer-y2Z1pPwY.js → iframe-sanitizer-viROQkRn.js} +1 -1
  180. package/dist/public/index.html +2 -2
  181. package/dist/public/index.js +1 -1
  182. package/dist/public/{initChatSplitResize-CzV2E7Ul.js → initChatSplitResize-BA18wLQQ.js} +1 -1
  183. package/dist/public/{initControlPlane-BqCY0NQy.js → initControlPlane-L96uJkRA.js} +1 -1
  184. package/dist/public/main/index.html +2 -2
  185. package/dist/public/main/memory/index.html +2 -2
  186. package/dist/public/{markdown-Cw7vCtTg.js → markdown-DGjZAm_w.js} +17 -14
  187. package/dist/public/{mcp-reconnect-9emsqGRe.js → mcp-reconnect-CBHuaYdF.js} +1 -1
  188. package/dist/public/{memoryStorage-C0KvLNUp.js → memoryStorage-C1llaYus.js} +1 -1
  189. package/dist/public/orchestrator-C0VOoTiK.js +15 -0
  190. package/dist/public/{orchestrator-DpkkeD6T.js → orchestrator-DMWi--Dc.js} +20 -19
  191. package/dist/public/peerjs-Bf6ZxfbA.js +1 -0
  192. package/dist/public/{push-client-CHjSbrWs.js → push-client-BD4YalBa.js} +1 -1
  193. package/dist/public/service-worker.js +1 -1
  194. package/dist/public/{shadow-claw-BBPV_9X8.js → shadow-claw-DRy-D4Qi.js} +2 -2
  195. package/dist/public/{shadow-claw-accounts-xUOLBsQZ.js → shadow-claw-accounts-DnfmA8cb.js} +1 -1
  196. package/dist/public/{shadow-claw-channels-BJvrKQjg.js → shadow-claw-channels-8UkycZAa.js} +1 -1
  197. package/dist/public/{shadow-claw-chat-BG2EaRaw.js → shadow-claw-chat-Cf5LUiul.js} +9 -9
  198. package/dist/public/{shadow-claw-control-plane-BBa7WbTg.js → shadow-claw-control-plane-UsBF-Nvd.js} +2 -2
  199. package/dist/public/{shadow-claw-conversations-CFaWFmkm.js → shadow-claw-conversations-Aoe61Y21.js} +1 -1
  200. package/dist/public/{shadow-claw-file-viewer-Z-GIKaE9.js → shadow-claw-file-viewer-D28dSSEn.js} +6 -6
  201. package/dist/public/{shadow-claw-files-BBx4iMsk.js → shadow-claw-files-CaDUlPEU.js} +1 -1
  202. package/dist/public/{shadow-claw-git-vTOKatwd.js → shadow-claw-git-DYNcZMWA.js} +2 -2
  203. package/dist/public/{shadow-claw-integrations-BBVnO0Fc.js → shadow-claw-integrations-BJf_hQVv.js} +1 -1
  204. package/dist/public/{shadow-claw-llm-Ce1Ax6-x.js → shadow-claw-llm-BqK2SIRf.js} +2 -2
  205. package/dist/public/{shadow-claw-mcp-remote-Bkx0gsmF.js → shadow-claw-mcp-remote-B_dDG990.js} +1 -1
  206. package/dist/public/{shadow-claw-networking-q4XIzdB5.js → shadow-claw-networking-D8LxOofv.js} +1 -1
  207. package/dist/public/{shadow-claw-notifications-D2cz0gmm.js → shadow-claw-notifications-DpVyiNG_.js} +1 -1
  208. package/dist/public/{shadow-claw-page-header-BccSHItx.js → shadow-claw-page-header-DdC7bJuW.js} +1 -1
  209. package/dist/public/{shadow-claw-pages-3335lmSC.js → shadow-claw-pages-CYMl4J-6.js} +3 -3
  210. package/dist/public/{shadow-claw-settings-4ptYuJwO.js → shadow-claw-settings-Da_8Oy3O.js} +3 -3
  211. package/dist/public/{shadow-claw-storage-bIrPKFhF.js → shadow-claw-storage-DXIm86_-.js} +1 -1
  212. package/dist/public/{shadow-claw-task-server-5yqVBxy1.js → shadow-claw-task-server-BCceBD0G.js} +1 -1
  213. package/dist/public/{shadow-claw-tasks-DcqYHPLZ.js → shadow-claw-tasks-BW1QZ5L3.js} +19 -19
  214. package/dist/public/{shadow-claw-terminal-fARaDONr.js → shadow-claw-terminal-vqfsxhcK.js} +1 -1
  215. package/dist/public/{shadow-claw-tools-paEAhcJr.js → shadow-claw-tools-l0Jsa7UJ.js} +1 -1
  216. package/dist/public/{shadow-claw-webvm-CbzH_vyX.js → shadow-claw-webvm-CDPOWZm7.js} +1 -1
  217. package/dist/public/skills/main/skill-creator/SKILL.md +3 -2
  218. package/dist/public/skills/main/skill-creator/references/frontmatter-spec.md +5 -3
  219. package/dist/public/static-main-manifest.json +2 -2
  220. package/dist/public/{syncWebMcpRegistration-CWSYaHex.js → syncWebMcpRegistration-5b5FkP9_.js} +1 -1
  221. package/dist/public/tools-B-PzsLc2.js +1 -0
  222. package/dist/public/{tools-C8x3nmo4.js → tools-CcgG5XuA.js} +1 -1
  223. package/dist/public/{tools-DvckJZG2.js → tools-VSOY66vs.js} +1 -1
  224. package/dist/public/{webmcp-DAjmliCx.js → webmcp-DBONjQWd.js} +1 -1
  225. package/dist/server.js +2 -2
  226. package/package.json +2 -2
  227. package/src/__mocks__/marked.cjs +14 -0
  228. package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +17 -0
  229. package/src/components/shadow-claw-chat/shadow-claw-chat.ts +5 -1
  230. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +78 -0
  231. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +6 -2
  232. package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +56 -0
  233. package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +22 -0
  234. package/src/components/shadow-claw-tasks/shadow-claw-tasks.ts +5 -1
  235. package/src/content/markdown-links.test.ts +109 -0
  236. package/src/content/markdown-links.ts +187 -0
  237. package/src/content/markdown.test.ts +33 -0
  238. package/src/content/markdown.ts +6 -1
  239. package/src/core/app-routes.test.ts +65 -0
  240. package/src/core/app-routes.ts +1 -1
  241. package/src/core/orchestrator/orchestrator.ts +3 -0
  242. package/src/core/orchestrator/utils/compactContext.ts +2 -0
  243. package/src/core/orchestrator/utils/invokeAgent.ts +1 -0
  244. package/src/subsystems/accounts/service-accounts.test.ts +0 -1
  245. package/src/subsystems/channels/peerjs.test.ts +479 -1
  246. package/src/subsystems/channels/peerjs.ts +420 -75
  247. package/src/subsystems/channels/room.test.ts +35 -0
  248. package/src/subsystems/channels/room.ts +15 -0
  249. package/src/subsystems/skills/activateSkill.test.ts +38 -5
  250. package/src/subsystems/skills/activateSkill.ts +25 -3
  251. package/src/subsystems/skills/parseSkill.test.ts +71 -0
  252. package/src/subsystems/skills/parseSkill.ts +185 -25
  253. package/src/subsystems/tools/files.ts +4 -4
  254. package/src/worker/tools/spawn-subagent/spawn-subagent.test.ts +22 -23
  255. package/src/worker/tools/workspace/send-file.test.ts +24 -1
  256. package/src/worker/tools/workspace/send-file.ts +6 -3
  257. package/src/worker/utils/handleCompact.test.ts +3 -4
  258. package/src/worker/utils/handleInvoke.test.ts +13 -11
  259. package/src/worker/utils/handleInvoke.ts +3 -0
  260. package/src/worker/utils/handleMessage.test.ts +0 -1
  261. package/src/worker/utils/post.test.ts +3 -4
  262. package/src/worker/utils/sandboxedEval.test.ts +1 -2
  263. package/src/worker/utils/system-prompt.test.ts +59 -0
  264. package/src/worker/utils/system-prompt.ts +21 -1
  265. package/src/worker/utils/withRetry.test.ts +3 -16
  266. package/dist/lib/app-routes-CqBOeA5q.js.map +0 -1
  267. package/dist/lib/markdown-DgdSt2GD.js.map +0 -1
  268. package/dist/lib/orchestrator-CjEau_RG.js.map +0 -1
  269. package/dist/lib/peerjs-BdfpltIe.js.map +0 -1
  270. package/dist/lib/shadow-claw-chat-mXPQZbwT.js.map +0 -1
  271. package/dist/lib/shadow-claw-file-viewer-BSbZvSl-.js.map +0 -1
  272. package/dist/lib/shadow-claw-tasks-qmcDT4PU.js.map +0 -1
  273. package/dist/lib/tools-D4Pp-nUw.js +0 -1
  274. package/dist/public/app-routes-Bs-riIvr.js +0 -1
  275. package/dist/public/orchestrator-BWkHHO9Y.js +0 -15
  276. package/dist/public/peerjs-tf2EmWjB.js +0 -1
  277. 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
- transferProgressSignal.set({
212
+ updateTransferProgress({
155
213
  count: received,
156
214
  total,
157
215
  direction: "receive",
158
216
  });
159
217
 
160
218
  if (received >= total) {
161
- setTimeout(() => transferProgressSignal.set(null), 1500);
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 the raw binary buffer so PeerJS chunks it natively.
478
- // We start tracking progress before the send call so the UI
479
- // shows immediately rather than after the first chunk.
480
- const trackPromise = this._trackSendProgress(conn, bytes.length);
481
- conn.send(bytes.buffer);
482
- await trackPromise;
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
- * Track outbound send progress by polling RTCDataChannel.bufferedAmount.
1475
- * Resolves when all queued data has been flushed to the network.
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 _trackSendProgress(
1787
+ private async _waitForBufferDrain(
1478
1788
  conn: DataConnection,
1479
- totalBytes: number,
1789
+ threshold: number = FILE_BUFFER_DRAIN_THRESHOLD,
1480
1790
  ): Promise<void> {
1481
- return new Promise<void>((resolve) => {
1482
- const rawDc = (conn as any)._dc as RTCDataChannel | undefined;
1483
- if (!rawDc || totalBytes === 0) {
1484
- this.transferProgressSignal.set(null);
1485
- resolve();
1486
-
1487
- return;
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
- const buffered = rawDc.bufferedAmount;
1498
- const sent = Math.max(0, totalBytes - buffered);
1499
- this.transferProgressSignal.set({
1500
- count: sent,
1501
- total: totalBytes,
1502
- direction: "send",
1503
- });
1803
+ if (!rawDc || rawDc.bufferedAmount <= threshold) {
1804
+ clearInterval(poll);
1805
+ resolve();
1806
+ }
1807
+ }, 20);
1504
1808
 
1505
- if (buffered === 0) {
1809
+ if (typeof rawDc.addEventListener === "function") {
1810
+ const onLow = () => {
1506
1811
  clearInterval(poll);
1507
- this.transferProgressSignal.set({
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
- }, 80);
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
  }