shadow-claw 1.23.3 → 1.24.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 (286) hide show
  1. package/README.md +42 -5
  2. package/bin/assert-version-bump.mjs +60 -38
  3. package/bin/assert-version-bump.test.mjs +74 -11
  4. package/bin/cli.mjs +166 -0
  5. package/bin/cli.test.mjs +60 -0
  6. package/bin/commands/backup.mjs +70 -0
  7. package/bin/commands/clients.mjs +41 -0
  8. package/bin/commands/peer-id.mjs +76 -0
  9. package/bin/commands/peer-id.test.mjs +104 -0
  10. package/bin/commands/send.mjs +51 -0
  11. package/bin/commands/tasks.mjs +59 -0
  12. package/bin/commands/webrtc-listen.mjs +254 -0
  13. package/bin/prerender-dsd-shell/prerender-dsd-shell.test.mjs +46 -0
  14. package/bin/rolldown.config.test.mjs +401 -8
  15. package/bin/utils/control-client.mjs +243 -0
  16. package/bin/utils/control-client.test.mjs +83 -0
  17. package/bin/utils/ulid.mjs +45 -0
  18. package/bin/utils/webrtc-control-client.mjs +699 -0
  19. package/bin/utils/webrtc-control-client.test.mjs +318 -0
  20. package/dist/electron/main.cjs +1 -1
  21. package/dist/public/AGENTS.md +5 -3
  22. package/dist/public/README.md +42 -5
  23. package/dist/public/agent.worker.js +6 -6
  24. package/dist/public/{app-routes-B0ePT49v.js → app-routes-Bs-riIvr.js} +1 -1
  25. package/dist/public/backup-controller-C_s9in2t.js +1 -0
  26. package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +1 -0
  27. package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +1 -0
  28. package/dist/public/components/settings/shadow-claw-storage/shadow-claw-storage.html +1 -1
  29. package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +1 -1
  30. package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
  31. package/dist/public/components/shadow-claw-files/shadow-claw-files.css +1 -1
  32. package/dist/public/components/shadow-claw-files/shadow-claw-files.html +1 -1
  33. package/dist/public/components/shadow-claw-settings/shadow-claw-settings.html +1 -1
  34. package/dist/public/config-CrHchneq.js +1 -0
  35. package/dist/public/{configurePeerJs-B7kNU8AQ.js → configurePeerJs-CxpZeDJJ.js} +1 -1
  36. package/dist/public/{connections-xd_66YVU.js → connections-DvbgdkaB.js} +1 -1
  37. package/dist/public/{custom-element-security-CZ92anB8.js → custom-element-security-NCqQ5lmC.js} +1 -1
  38. package/dist/public/docs/README.md +3 -1
  39. package/dist/public/docs/architecture/overview.md +12 -12
  40. package/dist/public/docs/architecture/storage.md +5 -2
  41. package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
  42. package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +6 -3
  43. package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
  44. package/dist/public/docs/example/article/index.html +2 -2
  45. package/dist/public/docs/guides/configuring-messaging-channels.md +41 -6
  46. package/dist/public/docs/guides/server-development-configuration.md +24 -0
  47. package/dist/public/docs/publishing/index.html +19 -76
  48. package/dist/public/docs/skill-creator/index.html +2 -2
  49. package/dist/public/docs/subsystems/backup.md +56 -0
  50. package/dist/public/docs/subsystems/cli.md +124 -0
  51. package/dist/public/docs/subsystems/control-plane.md +158 -0
  52. package/dist/public/docs/subsystems/notifications.md +54 -2
  53. package/dist/public/docs/subsystems/reactive-ui.md +1 -0
  54. package/dist/public/downloadGroupFile-By0GR6eV.js +1 -0
  55. package/dist/public/e2e/README.md +5 -1
  56. package/dist/public/{e2e-bridge-DkS5xFGX.js → e2e-bridge-IGWsRl4i.js} +1 -1
  57. package/dist/public/file-viewer-D0eflgt6.js +1 -0
  58. package/dist/public/files/main/~/docs/publishing-to-github-pages.md +17 -82
  59. package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
  60. package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
  61. package/dist/public/{git-BkjrsRXX.js → git-7Vq_SJSp.js} +2 -2
  62. package/dist/public/{iframe-sanitizer-L6Jyu__Z.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
  63. package/dist/public/index.html +2 -2
  64. package/dist/public/index.js +1 -1
  65. package/dist/public/{initChatSplitResize-BslPXAXS.js → initChatSplitResize-CzV2E7Ul.js} +1 -1
  66. package/dist/public/initControlPlane-C9jpnWev.js +1 -0
  67. package/dist/public/llms.txt +1 -1
  68. package/dist/public/main/index.html +2 -2
  69. package/dist/public/main/memory/index.html +2 -2
  70. package/dist/public/{markdown-6MBrIo-S.js → markdown-BnpKxSxl.js} +1 -1
  71. package/dist/public/mcp-reconnect-9emsqGRe.js +1 -0
  72. package/dist/public/{memoryStorage-CjGseKI1.js → memoryStorage-C0KvLNUp.js} +1 -1
  73. package/dist/public/{orchestrator-B9KmIj8Q.js → orchestrator-3h9xxHUm.js} +676 -676
  74. package/dist/public/orchestrator-nJj59RqD.js +15 -0
  75. package/dist/public/pages/main/~/docs/publishing-to-github-pages.md +17 -82
  76. package/dist/public/{peerjs-Dv8JNm7v.js → peerjs-BP4laMXj.js} +1 -1
  77. package/dist/public/{push-client-Dqq06wbB.js → push-client-CrjEXK3U.js} +1 -1
  78. package/dist/public/service-worker/fetch-proxy.js +1 -1
  79. package/dist/public/service-worker/push-handler.js +1 -1
  80. package/dist/public/service-worker.js +1 -1
  81. package/dist/public/{shadow-claw-Bd1rfuVC.js → shadow-claw-CbwETBWf.js} +2 -2
  82. package/dist/public/{shadow-claw-accounts-DL2l3oM0.js → shadow-claw-accounts-B4sBTQEj.js} +3 -3
  83. package/dist/public/{shadow-claw-channels-BGXDsQ38.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
  84. package/dist/public/{shadow-claw-chat-NXg6zz_l.js → shadow-claw-chat-Crv0DTCY.js} +6 -6
  85. package/dist/public/shadow-claw-control-plane-CX1IGbh9.js +199 -0
  86. package/dist/public/{shadow-claw-conversations-C8eqGpP9.js → shadow-claw-conversations-_9qAaRXk.js} +1 -1
  87. package/dist/public/shadow-claw-dialog-BrOwAOdk.js +1 -0
  88. package/dist/public/shadow-claw-file-viewer-Blf6yGVD.js +577 -0
  89. package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
  90. package/dist/public/{shadow-claw-git-C0ntz7aX.js → shadow-claw-git-v8S0JjuR.js} +3 -3
  91. package/dist/public/{shadow-claw-integrations-DCXV2r69.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
  92. package/dist/public/{shadow-claw-llm-CudgFQsS.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
  93. package/dist/public/{shadow-claw-mcp-remote-B8RkpX28.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
  94. package/dist/public/{shadow-claw-networking-BLiRsrsK.js → shadow-claw-networking-qHbP95z9.js} +1 -1
  95. package/dist/public/{shadow-claw-notifications-CQhQcjtL.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
  96. package/dist/public/{shadow-claw-page-header-NeRD0tKU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
  97. package/dist/public/{shadow-claw-pages-DCHnFh6r.js → shadow-claw-pages-DFZHhzFZ.js} +3 -3
  98. package/dist/public/{shadow-claw-settings-VAfjKFQg.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
  99. package/dist/public/shadow-claw-storage-BFynbP0j.js +219 -0
  100. package/dist/public/{shadow-claw-task-server-Dk33N_1G.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
  101. package/dist/public/{shadow-claw-tasks-xA1VfmWN.js → shadow-claw-tasks-Caxc9atL.js} +4 -4
  102. package/dist/public/{shadow-claw-terminal-Du2r0ciq.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
  103. package/dist/public/{shadow-claw-tools-CZj4rq7l.js → shadow-claw-tools-BfJx3A9W.js} +2 -2
  104. package/dist/public/{shadow-claw-webvm-JYUkWICG.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
  105. package/dist/public/static-main/~/docs/publishing-to-github-pages.md +17 -82
  106. package/dist/public/static-main-manifest.json +1 -1
  107. package/dist/public/{syncWebMcpRegistration-DfpvASnh.js → syncWebMcpRegistration-B_M2fCjH.js} +1 -1
  108. package/dist/server.js +286 -189
  109. package/package.json +2 -1
  110. package/src/__mocks__/jszip.cjs +15 -5
  111. package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +35 -0
  112. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +117 -0
  113. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +81 -0
  114. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +127 -0
  115. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +292 -0
  116. package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +72 -0
  117. package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +20 -30
  118. package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +116 -0
  119. package/src/components/settings/shadow-claw-llm/model-ranking.test.ts +143 -51
  120. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +307 -4
  121. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.ts +8 -8
  122. package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +31 -0
  123. package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +186 -0
  124. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.html +17 -0
  125. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +120 -0
  126. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.ts +56 -0
  127. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +23 -2
  128. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
  129. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +19 -0
  130. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +2 -1
  131. package/src/components/shadow-claw/utils/initChatSplitResize.test.ts +180 -13
  132. package/src/components/shadow-claw/utils/setupEffects.test.ts +39 -0
  133. package/src/components/shadow-claw-a2ui/catalog/basic/shared.test.ts +117 -0
  134. package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +1159 -1
  135. package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +1523 -19
  136. package/src/components/shadow-claw-chat/utils/escapeHtml.test.ts +13 -0
  137. package/src/components/shadow-claw-chat/utils/getPeerChatDisplayStatus.test.ts +20 -0
  138. package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +494 -0
  139. package/src/components/shadow-claw-dialog/shadow-claw-dialog.ts +13 -2
  140. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +378 -0
  141. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +11 -9
  142. package/src/components/shadow-claw-files/shadow-claw-files.css +41 -17
  143. package/src/components/shadow-claw-files/shadow-claw-files.html +35 -0
  144. package/src/components/shadow-claw-files/shadow-claw-files.test.ts +627 -1
  145. package/src/components/shadow-claw-files/shadow-claw-files.ts +170 -6
  146. package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +31 -0
  147. package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.ts +2 -1
  148. package/src/components/shadow-claw-pages/utils/rewriteWorkspacePreviewHtml.test.ts +15 -0
  149. package/src/components/shadow-claw-pdf-viewer/utils/installGetOrInsertComputedPolyfill.test.ts +65 -0
  150. package/src/components/shadow-claw-settings/shadow-claw-settings.html +7 -0
  151. package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +221 -0
  152. package/src/components/shadow-claw-settings/shadow-claw-settings.ts +2 -0
  153. package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +407 -0
  154. package/src/components/shadow-claw-terminal/utils/consumeEscapeSequence.test.ts +58 -0
  155. package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +80 -0
  156. package/src/config/config.test.ts +9 -0
  157. package/src/config/config.ts +15 -0
  158. package/src/core/app-routes.test.ts +56 -0
  159. package/src/core/app-routes.ts +8 -1
  160. package/src/core/backup-controller.test.ts +101 -0
  161. package/src/core/backup-controller.ts +137 -0
  162. package/src/core/control-plane-client.test.ts +408 -0
  163. package/src/core/control-plane-client.ts +425 -0
  164. package/src/core/orchestrator/orchestrator.ts +4 -0
  165. package/src/core/orchestrator/utils/compactContext.test.ts +78 -0
  166. package/src/core/orchestrator/utils/operations/channel.test.ts +254 -9
  167. package/src/core/orchestrator/utils/operations/provider.test.ts +326 -34
  168. package/src/core/orchestrator/utils/operations/vm.test.ts +114 -2
  169. package/src/core/orchestrator/utils/syncProxyConfigToServiceWorker.test.ts +60 -0
  170. package/src/core/utils/initControlPlane.test.ts +276 -0
  171. package/src/core/utils/initControlPlane.ts +437 -0
  172. package/src/core/utils/initializeApp.ts +18 -0
  173. package/src/db/getSession.test.ts +13 -1
  174. package/src/db/openDatabase.test.ts +10 -1
  175. package/src/db/openDatabase.ts +3 -1
  176. package/src/server/app.test.ts +12 -0
  177. package/src/server/app.ts +18 -8
  178. package/src/server/client-registry.test.ts +250 -0
  179. package/src/server/client-registry.ts +477 -0
  180. package/src/server/config.ts +15 -0
  181. package/src/server/control-plane-types.ts +120 -0
  182. package/src/server/control-plane.test.ts +472 -0
  183. package/src/server/control-plane.ts +581 -0
  184. package/src/server/routes/backup.test.ts +263 -0
  185. package/src/server/routes/backup.ts +262 -0
  186. package/src/server/routes/integrations-email.test.ts +532 -0
  187. package/src/server/routes/llamafile.test.ts +172 -48
  188. package/src/server/routes/mesh-llm.test.ts +192 -0
  189. package/src/server/routes/ollama.test.ts +49 -0
  190. package/src/server/server-peer.test.ts +86 -0
  191. package/src/server/server-peer.ts +330 -0
  192. package/src/server/server.ts +35 -0
  193. package/src/server/services/llamafile-manager.test.ts +230 -6
  194. package/src/server/services/transformers-runtime.test.ts +186 -18
  195. package/src/server/webrtc.test.ts +21 -0
  196. package/src/server/webrtc.ts +72 -0
  197. package/src/service-worker/fetch-proxy.test.ts +64 -1
  198. package/src/service-worker/push-handler.ts +39 -0
  199. package/src/shell/vm.test.ts +744 -12
  200. package/src/storage/ensureMainGroupIndex.test.ts +153 -0
  201. package/src/storage/getGroupFile.test.ts +66 -0
  202. package/src/storage/memoryStorage.test.ts +82 -2
  203. package/src/storage/migrateLegacyOpfs.test.ts +327 -0
  204. package/src/storage/migrateLegacyOpfs.ts +117 -0
  205. package/src/storage/writeGroupFileBytes.test.ts +143 -0
  206. package/src/stores/chat-ui.test.ts +20 -0
  207. package/src/stores/orchestrator.test.ts +134 -0
  208. package/src/stores/toast.test.ts +50 -0
  209. package/src/stores/tools.test.ts +147 -10
  210. package/src/subsystems/channels/imessage.test.ts +59 -0
  211. package/src/subsystems/channels/peerjs.test.ts +500 -62
  212. package/src/subsystems/channels/peerjs.ts +71 -4
  213. package/src/subsystems/channels/telegram.test.ts +177 -0
  214. package/src/subsystems/email/catalog.test.ts +1 -0
  215. package/src/subsystems/email/connections.test.ts +39 -1
  216. package/src/subsystems/git/credentials.test.ts +160 -825
  217. package/src/subsystems/git/git.test.ts +463 -0
  218. package/src/subsystems/notifications/__mocks__/node-sqlite.cjs +12 -1
  219. package/src/subsystems/notifications/push-routes.test.ts +33 -0
  220. package/src/subsystems/notifications/push-routes.ts +23 -0
  221. package/src/subsystems/notifications/push-store.ts +1 -1
  222. package/src/subsystems/providers/builtin-ai-tasks.test.ts +74 -0
  223. package/src/subsystems/providers/litert-lm-provider.test.ts +212 -51
  224. package/src/subsystems/providers/model-registry.test.ts +115 -0
  225. package/src/subsystems/providers/providers.test.ts +94 -1
  226. package/src/subsystems/providers/transformers-js-provider.test.ts +279 -0
  227. package/src/subsystems/providers/utils/createModelCacheFetch.test.ts +2 -2
  228. package/src/subsystems/providers/utils/downloadModelToCache.test.ts +2 -2
  229. package/src/subsystems/providers/utils/loadModelStream.test.ts +2 -2
  230. package/src/subsystems/skills/activateSkill.test.ts +101 -0
  231. package/src/subsystems/skills/discoverSkills.test.ts +251 -0
  232. package/src/subsystems/tools/cron.test.ts +64 -0
  233. package/src/subsystems/tools/declarative.test.ts +255 -89
  234. package/src/testing/assets/v86.9pfs/libv86.mjs +63 -0
  235. package/src/testing/assets/v86.ext2/libv86.mjs +3 -0
  236. package/src/testing/assets_missing_v86/assets/v86.9pfs/libv86.mjs +1 -0
  237. package/src/testing/jest-setup.ts +328 -40
  238. package/src/testing/mock-v86.mjs +49 -0
  239. package/src/ui/a2ui/types.ts +4 -3
  240. package/src/ui/a2ui/utils/normaliseComponentsToMap.test.ts +102 -0
  241. package/src/ui/a2ui/utils/resolveChildIds.test.ts +63 -0
  242. package/src/ui/a2ui/utils/resolveDynamicNumber.test.ts +13 -1
  243. package/src/worker/tools/bash/utils/waitForVMReady.test.ts +40 -0
  244. package/src/worker/tools/email/email.test.ts +193 -8
  245. package/src/worker/tools/email/utils/asEmailAttachmentInputs.test.ts +47 -0
  246. package/src/worker/tools/git/git.test.ts +214 -3
  247. package/src/worker/tools/rooms/invite-to-room.test.ts +54 -0
  248. package/src/worker/tools/rooms/leave-room.test.ts +40 -0
  249. package/src/worker/tools/rooms/list-room-members.test.ts +75 -0
  250. package/src/worker/tools/tasks/tasks-utils.test.ts +22 -0
  251. package/src/worker/tools/ui/ask-user.test.ts +50 -0
  252. package/src/worker/tools/ui/clear-chat.test.ts +19 -0
  253. package/src/worker/tools/ui/get-current-time.test.ts +19 -0
  254. package/src/worker/tools/ui/javascript.test.ts +116 -0
  255. package/src/worker/tools/ui/list-components.test.ts +10 -0
  256. package/src/worker/tools/ui/list-tool-profiles.test.ts +42 -0
  257. package/src/worker/tools/ui/manage-tools.test.ts +34 -0
  258. package/src/worker/tools/ui/render-component.test.ts +346 -0
  259. package/src/worker/tools/ui/send-notification.test.ts +40 -0
  260. package/src/worker/tools/ui/utils/getAllowFullInternetAccess.test.ts +31 -0
  261. package/src/worker/tools/ui/utils/parseBooleanConfig.test.ts +25 -0
  262. package/src/worker/tools/workspace/attach-file.test.ts +84 -0
  263. package/src/worker/tools/workspace/copy-file.test.ts +69 -0
  264. package/src/worker/tools/workspace/create-directory.test.ts +40 -0
  265. package/src/worker/tools/workspace/delete-file.test.ts +73 -0
  266. package/src/worker/tools/workspace/diff-files.test.ts +94 -0
  267. package/src/worker/tools/workspace/list-files.test.ts +36 -0
  268. package/src/worker/tools/workspace/move-file.test.ts +66 -0
  269. package/src/worker/tools/workspace/open-file.test.ts +57 -0
  270. package/src/worker/tools/workspace/patch-file.test.ts +81 -0
  271. package/src/worker/tools/workspace/read-file.test.ts +116 -0
  272. package/src/worker/tools/workspace/send-file.test.ts +82 -0
  273. package/src/worker/tools/workspace/update-memory.test.ts +29 -0
  274. package/src/worker/tools/workspace/write-file.test.ts +28 -0
  275. package/src/worker/utils/rate-limit.test.ts +199 -0
  276. package/src/worker/utils/tool-state.test.ts +29 -0
  277. package/src/worker/utils/transformers-js-utils.test.ts +59 -0
  278. package/dist/public/config-AL77QAx4.js +0 -1
  279. package/dist/public/downloadGroupFile-BABv_1mJ.js +0 -1
  280. package/dist/public/file-viewer-CCSABMsE.js +0 -1
  281. package/dist/public/mcp-reconnect-DoicazQd.js +0 -1
  282. package/dist/public/orchestrator-4su7iyWj.js +0 -15
  283. package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
  284. package/dist/public/shadow-claw-file-viewer-9Qkwc30T.js +0 -577
  285. package/dist/public/shadow-claw-files-aDyq6Hv7.js +0 -781
  286. package/dist/public/shadow-claw-storage-Cf31np81.js +0 -202
@@ -98,6 +98,10 @@ const mockSetMaxTokens = jest.fn(async () => {});
98
98
  const mockSetRateLimitCallsPerMinute = jest.fn(async () => {});
99
99
  const mockSetRateLimitAutoAdapt = jest.fn(async () => {});
100
100
  const mockSetReasoningEffort = jest.fn(async () => {});
101
+ const mockSetMaxIterations = jest.fn(async () => {});
102
+ const mockSetLlamafileSettings = jest.fn(async () => {});
103
+ const mockSetContextCompressionEnabled = jest.fn(async () => {});
104
+ const mockSetStreamingEnabled = jest.fn(async () => {});
101
105
 
102
106
  jest.unstable_mockModule(
103
107
  "../../../core/orchestrator/utils/operations/provider.js",
@@ -128,7 +132,7 @@ jest.unstable_mockModule(
128
132
  getProviderRuntimeHeaders: jest.fn().mockReturnValue({}),
129
133
  setProvider: mockSetProvider,
130
134
  setBedrockSettings: mockSetBedrockSettings,
131
- setLlamafileSettings: jest.fn(async () => {}),
135
+ setLlamafileSettings: mockSetLlamafileSettings,
132
136
  setMeshLlmSettings: jest.fn(async () => {}),
133
137
  setModel: jest.fn(async () => {}),
134
138
  setAssistantName: jest.fn(async () => {}),
@@ -151,11 +155,11 @@ jest.unstable_mockModule(
151
155
  setGitProxyUrl: jest.fn(async () => {}),
152
156
  setProxyUrl: jest.fn(async () => {}),
153
157
  setVmInternetAccess: jest.fn(async () => {}),
154
- setStreamingEnabled: jest.fn(async () => {}),
158
+ setStreamingEnabled: mockSetStreamingEnabled,
155
159
  setUseProxy: jest.fn(async () => {}),
156
160
  setVMBashFullInternetAccess: jest.fn(async () => {}),
157
- setContextCompressionEnabled: jest.fn(async () => {}),
158
- setMaxIterations: jest.fn(async () => {}),
161
+ setContextCompressionEnabled: mockSetContextCompressionEnabled,
162
+ setMaxIterations: mockSetMaxIterations,
159
163
  }),
160
164
  );
161
165
 
@@ -196,6 +200,7 @@ const { setConfig } = await import("../../../db/setConfig.js");
196
200
 
197
201
  function createOrchestratorStub(overrides = {}) {
198
202
  const base = {
203
+ setApiKey: jest.fn<any>().mockResolvedValue(undefined),
199
204
  setProvider: jest.fn<any>().mockResolvedValue(undefined),
200
205
  setModel: jest.fn<any>().mockResolvedValue(undefined),
201
206
  setAssistantName: jest.fn<any>().mockResolvedValue(undefined),
@@ -819,4 +824,302 @@ describe("shadow-claw-llm", () => {
819
824
 
820
825
  document.body.removeChild(el);
821
826
  });
827
+
828
+ it("saves subagent max parallel, workspace mode, and reasoning effort", async () => {
829
+ const el = new ShadowClawLlm() as any;
830
+ el.db = {};
831
+ el.orchestrator = createOrchestratorStub();
832
+ document.body.appendChild(el);
833
+ await el.render();
834
+
835
+ const maxParallelInput = el.shadowRoot?.querySelector(
836
+ '[data-setting="subagent-max-parallel-input"]',
837
+ ) as HTMLInputElement | null;
838
+ if (maxParallelInput) {
839
+ maxParallelInput.value = "8";
840
+ }
841
+ await el.saveSubagentMaxParallel();
842
+ expect(setConfig).toHaveBeenCalledWith(
843
+ expect.anything(),
844
+ "subagent_max_parallel",
845
+ "8",
846
+ );
847
+
848
+ const wsModeSelect = el.shadowRoot?.querySelector(
849
+ '[data-setting="subagent-workspace-mode-select"]',
850
+ ) as HTMLSelectElement | null;
851
+ if (wsModeSelect) {
852
+ wsModeSelect.value = "isolated";
853
+ }
854
+ await el.saveSubagentWorkspaceMode();
855
+ expect(setConfig).toHaveBeenCalledWith(
856
+ expect.anything(),
857
+ "subagent_workspace_mode",
858
+ "isolated",
859
+ );
860
+
861
+ const reasoningSelect = el.shadowRoot?.querySelector(
862
+ '[data-setting="reasoning-effort-select"]',
863
+ ) as HTMLSelectElement | null;
864
+ if (reasoningSelect) {
865
+ reasoningSelect.value = "high";
866
+ }
867
+ await el.saveReasoningEffort();
868
+ expect(mockSetReasoningEffort).toHaveBeenCalledWith(
869
+ expect.anything(),
870
+ expect.anything(),
871
+ "high",
872
+ );
873
+
874
+ document.body.removeChild(el);
875
+ });
876
+
877
+ it("saves max iterations and context compression settings", async () => {
878
+ const el = new ShadowClawLlm() as any;
879
+ el.db = {};
880
+ const orch = createOrchestratorStub();
881
+ (orchestratorStore as any).orchestrator = orch;
882
+ document.body.appendChild(el);
883
+ await el.render();
884
+
885
+ const maxIterInput = el.shadowRoot?.querySelector(
886
+ '[data-setting="max-iterations-input"]',
887
+ ) as HTMLInputElement | null;
888
+ if (maxIterInput) {
889
+ maxIterInput.value = "100";
890
+ }
891
+ await el.saveMaxIterations();
892
+ expect(mockSetMaxIterations).toHaveBeenCalledWith(
893
+ expect.anything(),
894
+ expect.anything(),
895
+ 100,
896
+ );
897
+
898
+ await el.onContextCompressionToggle(true);
899
+ expect(mockSetContextCompressionEnabled).toHaveBeenCalledWith(
900
+ expect.anything(),
901
+ expect.anything(),
902
+ true,
903
+ );
904
+
905
+ document.body.removeChild(el);
906
+ });
907
+
908
+ it("saves rate limit settings and applies recommended max tokens", async () => {
909
+ const el = new ShadowClawLlm() as any;
910
+ el.db = {};
911
+ const orch = createOrchestratorStub();
912
+ (orchestratorStore as any).orchestrator = orch;
913
+ document.body.appendChild(el);
914
+ await el.render();
915
+
916
+ const rateInput = el.shadowRoot?.querySelector(
917
+ '[data-setting="rate-limit-calls-per-minute-input"]',
918
+ ) as HTMLInputElement | null;
919
+ const adaptInput = el.shadowRoot?.querySelector(
920
+ '[data-setting="rate-limit-auto-adapt-toggle"]',
921
+ ) as HTMLInputElement | null;
922
+
923
+ if (rateInput) rateInput.value = "30";
924
+ if (adaptInput) adaptInput.checked = true;
925
+
926
+ await el.saveRateLimitSettings();
927
+ expect(mockSetRateLimitCallsPerMinute).toHaveBeenCalledWith(
928
+ expect.anything(),
929
+ expect.anything(),
930
+ 30,
931
+ );
932
+ expect(mockSetRateLimitAutoAdapt).toHaveBeenCalledWith(
933
+ expect.anything(),
934
+ expect.anything(),
935
+ true,
936
+ );
937
+
938
+ const maxTokensInput = el.shadowRoot?.querySelector(
939
+ '[data-setting="max-tokens-input"]',
940
+ ) as HTMLInputElement | null;
941
+ await el.applyRecommendedMaxTokens();
942
+ expect(maxTokensInput?.value).toBeTruthy();
943
+
944
+ document.body.removeChild(el);
945
+ });
946
+
947
+ it("saves bedrock and llamafile settings", async () => {
948
+ const el = new ShadowClawLlm() as any;
949
+ el.db = {};
950
+ const orch = createOrchestratorStub();
951
+ (orchestratorStore as any).orchestrator = orch;
952
+ document.body.appendChild(el);
953
+ await el.render();
954
+
955
+ const regionInput = el.shadowRoot?.querySelector(
956
+ '[data-setting="bedrock-region-input"]',
957
+ ) as HTMLInputElement | null;
958
+ const profileInput = el.shadowRoot?.querySelector(
959
+ '[data-setting="bedrock-profile-input"]',
960
+ ) as HTMLInputElement | null;
961
+
962
+ if (regionInput) regionInput.value = "us-west-2";
963
+ if (profileInput) profileInput.value = "default";
964
+
965
+ await el.saveBedrockSettings();
966
+ expect(mockSetBedrockSettings).toHaveBeenCalledWith(
967
+ expect.anything(),
968
+ expect.anything(),
969
+ expect.objectContaining({ region: "us-west-2", profile: "default" }),
970
+ );
971
+
972
+ const modeInput = el.shadowRoot?.querySelector(
973
+ '[data-setting="llamafile-mode-select"]',
974
+ ) as HTMLSelectElement | null;
975
+ const hostInput = el.shadowRoot?.querySelector(
976
+ '[data-setting="llamafile-host-input"]',
977
+ ) as HTMLInputElement | null;
978
+ const portInput = el.shadowRoot?.querySelector(
979
+ '[data-setting="llamafile-port-input"]',
980
+ ) as HTMLInputElement | null;
981
+ const offlineInput = el.shadowRoot?.querySelector(
982
+ '[data-setting="llamafile-offline-toggle"]',
983
+ ) as HTMLInputElement | null;
984
+
985
+ if (modeInput) modeInput.value = "server";
986
+ if (hostInput) hostInput.value = "http://127.0.0.1";
987
+ if (portInput) portInput.value = "8080";
988
+ if (offlineInput) offlineInput.checked = true;
989
+
990
+ await el.saveLlamafileSettings();
991
+ expect(mockSetLlamafileSettings).toHaveBeenCalledWith(
992
+ expect.anything(),
993
+ expect.anything(),
994
+ expect.objectContaining({
995
+ mode: "server",
996
+ host: "http://127.0.0.1",
997
+ port: 8080,
998
+ offline: true,
999
+ }),
1000
+ );
1001
+
1002
+ document.body.removeChild(el);
1003
+ });
1004
+
1005
+ it("handles saveReasoningEffort, saveSubagentMaxParallel, and onStreamingToggle", async () => {
1006
+ const orch = createOrchestratorStub();
1007
+ (orchestratorStore as any).orchestrator = orch;
1008
+
1009
+ const el = new ShadowClawLlm() as any;
1010
+ document.body.appendChild(el);
1011
+ await el.render();
1012
+
1013
+ // 1. saveReasoningEffort
1014
+ const select = el.shadowRoot?.querySelector(
1015
+ '[data-setting="reasoning-effort-select"]',
1016
+ ) as HTMLSelectElement | null;
1017
+ if (select) select.value = "high";
1018
+ await el.saveReasoningEffort();
1019
+ expect(mockSetReasoningEffort).toHaveBeenCalledWith(
1020
+ orch,
1021
+ expect.anything(),
1022
+ "high",
1023
+ );
1024
+
1025
+ // 2. saveSubagentMaxParallel
1026
+ const parallelInput = el.shadowRoot?.querySelector(
1027
+ '[data-setting="subagent-max-parallel-input"]',
1028
+ ) as HTMLInputElement | null;
1029
+ if (parallelInput) parallelInput.value = "5";
1030
+ await el.saveSubagentMaxParallel();
1031
+ expect(setConfig).toHaveBeenCalledWith(
1032
+ expect.anything(),
1033
+ "subagent_max_parallel",
1034
+ "5",
1035
+ );
1036
+
1037
+ // 3. onStreamingToggle
1038
+ await el.onStreamingToggle(true);
1039
+ expect(mockSetStreamingEnabled).toHaveBeenCalled();
1040
+
1041
+ // 4. saveMaxIterations
1042
+ const maxIterInput = el.shadowRoot?.querySelector(
1043
+ '[data-setting="max-iterations-input"]',
1044
+ ) as HTMLInputElement | null;
1045
+ if (maxIterInput) maxIterInput.value = "42";
1046
+ await el.saveMaxIterations();
1047
+ expect(mockSetMaxIterations).toHaveBeenCalledWith(
1048
+ orch,
1049
+ expect.anything(),
1050
+ 42,
1051
+ );
1052
+
1053
+ // 5. saveMaxTokens
1054
+ const maxTokensInput = el.shadowRoot?.querySelector(
1055
+ '[data-setting="max-tokens-input"]',
1056
+ ) as HTMLInputElement | null;
1057
+ if (maxTokensInput) maxTokensInput.value = "2048";
1058
+ await el.saveMaxTokens();
1059
+ expect(mockSetMaxTokens).toHaveBeenCalledWith(
1060
+ orch,
1061
+ expect.anything(),
1062
+ 2048,
1063
+ );
1064
+
1065
+ // 6. saveSubagentWorkspaceMode
1066
+ const wsModeSelect = el.shadowRoot?.querySelector(
1067
+ '[data-setting="subagent-workspace-mode-select"]',
1068
+ ) as HTMLSelectElement | null;
1069
+ if (wsModeSelect) wsModeSelect.value = "isolated";
1070
+ await el.saveSubagentWorkspaceMode();
1071
+ expect(setConfig).toHaveBeenCalledWith(
1072
+ expect.anything(),
1073
+ "subagent_workspace_mode",
1074
+ "isolated",
1075
+ );
1076
+
1077
+ // 7. saveApiKey
1078
+ const keyInput = el.shadowRoot?.querySelector(
1079
+ '[data-setting="api-key-input"]',
1080
+ ) as HTMLInputElement | null;
1081
+ const providerSelect = el.shadowRoot?.querySelector(
1082
+ '[data-setting="provider-select"]',
1083
+ ) as HTMLSelectElement | null;
1084
+ if (keyInput) keyInput.value = "sk-test-key-123";
1085
+ if (providerSelect) providerSelect.value = "provider-a";
1086
+ await el.saveApiKey();
1087
+ expect(orch.setApiKey).toHaveBeenCalledWith(
1088
+ expect.anything(),
1089
+ "sk-test-key-123",
1090
+ );
1091
+
1092
+ // 8. saveBuiltinAiSettings
1093
+ await el.saveBuiltinAiSettings();
1094
+ expect(setConfig).toHaveBeenCalled();
1095
+
1096
+ // 9. saveTransformersJsSettings
1097
+ await el.saveTransformersJsSettings();
1098
+ expect(setConfig).toHaveBeenCalled();
1099
+
1100
+ // 10. showLlamafileHelpDialog
1101
+ el.requestAppDialog = jest.fn<any>().mockResolvedValue(undefined);
1102
+ await el.showLlamafileHelpDialog("connection refused");
1103
+ expect(el.requestAppDialog).toHaveBeenCalled();
1104
+
1105
+ // 11. saveMeshLlmSettings
1106
+ const hostInput = el.shadowRoot?.querySelector(
1107
+ '[data-setting="mesh-llm-host-input"]',
1108
+ ) as HTMLInputElement | null;
1109
+ if (hostInput) hostInput.value = "http://localhost:11434";
1110
+ await el.saveMeshLlmSettings();
1111
+
1112
+ // 12. updateModelSelector dynamic models
1113
+ (global.fetch as any) = jest.fn<any>().mockResolvedValue({
1114
+ ok: true,
1115
+ status: 200,
1116
+ json: async () => ({
1117
+ models: [{ id: "dynamic-model-1", name: "Dynamic Model 1" }],
1118
+ }),
1119
+ });
1120
+ orch.getProvider.mockReturnValue("bedrock_proxy");
1121
+ el.updateModelSelector();
1122
+
1123
+ document.body.removeChild(el);
1124
+ });
822
1125
  });
@@ -318,16 +318,16 @@ export class ShadowClawLlm extends ShadowClawElement {
318
318
  }
319
319
 
320
320
  const modeInput = root.querySelector(
321
- '[data-setting="llamafile-mode"]',
321
+ '[data-setting="llamafile-mode"], [data-setting="llamafile-mode-select"]',
322
322
  ) as HTMLSelectElement | null;
323
323
  const hostInput = root.querySelector(
324
- '[data-setting="llamafile-host"]',
324
+ '[data-setting="llamafile-host"], [data-setting="llamafile-host-input"]',
325
325
  ) as HTMLInputElement | null;
326
326
  const portInput = root.querySelector(
327
- '[data-setting="llamafile-port"]',
327
+ '[data-setting="llamafile-port"], [data-setting="llamafile-port-input"]',
328
328
  ) as HTMLInputElement | null;
329
329
  const offlineInput = root.querySelector(
330
- '[data-setting="llamafile-offline"]',
330
+ '[data-setting="llamafile-offline"], [data-setting="llamafile-offline-toggle"]',
331
331
  ) as HTMLInputElement | null;
332
332
 
333
333
  if (modeInput) {
@@ -1694,16 +1694,16 @@ export class ShadowClawLlm extends ShadowClawElement {
1694
1694
  }
1695
1695
 
1696
1696
  const modeInput = root.querySelector(
1697
- '[data-setting="llamafile-mode"]',
1697
+ '[data-setting="llamafile-mode"], [data-setting="llamafile-mode-select"]',
1698
1698
  ) as HTMLSelectElement | null;
1699
1699
  const hostInput = root.querySelector(
1700
- '[data-setting="llamafile-host"]',
1700
+ '[data-setting="llamafile-host"], [data-setting="llamafile-host-input"]',
1701
1701
  ) as HTMLInputElement | null;
1702
1702
  const portInput = root.querySelector(
1703
- '[data-setting="llamafile-port"]',
1703
+ '[data-setting="llamafile-port"], [data-setting="llamafile-port-input"]',
1704
1704
  ) as HTMLInputElement | null;
1705
1705
  const offlineInput = root.querySelector(
1706
- '[data-setting="llamafile-offline"]',
1706
+ '[data-setting="llamafile-offline"], [data-setting="llamafile-offline-toggle"]',
1707
1707
  ) as HTMLInputElement | null;
1708
1708
 
1709
1709
  if (!modeInput || !hostInput || !portInput || !offlineInput) {
@@ -291,4 +291,35 @@ describe("shadow-claw-mcp-remote", () => {
291
291
 
292
292
  document.body.removeChild(el);
293
293
  });
294
+
295
+ it("handles deleteConnection, reconnectOAuth, and testConnection", async () => {
296
+ mockDeleteConnection.mockResolvedValue(true);
297
+ mockReconnectMcpOAuth.mockResolvedValue({ success: true });
298
+ mockTestConnection.mockResolvedValue({
299
+ success: true,
300
+ toolCount: 2,
301
+ toolNames: ["search", "fetch"],
302
+ steps: [{ step: "Ping", status: "ok" }],
303
+ });
304
+
305
+ const el = new ShadowClawMcpRemote() as any;
306
+ document.body.appendChild(el);
307
+ await el.connectedCallback();
308
+ el.db = {} as any;
309
+ el.connections = [{ id: "conn-1", label: "My Connection" }];
310
+
311
+ // 1. Delete connection
312
+ await el.deleteConnection("conn-1");
313
+ expect(mockDeleteConnection).toHaveBeenCalledWith(el.db, "conn-1");
314
+
315
+ // 2. Reconnect OAuth
316
+ await el.reconnectOAuth("conn-1");
317
+ expect(mockReconnectMcpOAuth).toHaveBeenCalledWith(el.db, "conn-1");
318
+
319
+ // 3. Test connection
320
+ await el.testConnection("conn-1");
321
+ expect(mockTestConnection).toHaveBeenCalledWith(el.db, "conn-1");
322
+
323
+ document.body.removeChild(el);
324
+ });
294
325
  });
@@ -198,4 +198,190 @@ describe("shadow-claw-peerjs", () => {
198
198
  "http://localhost/?peer=test",
199
199
  );
200
200
  });
201
+
202
+ it("should show room QR code", async () => {
203
+ const el = new ShadowClawPeerJs() as any;
204
+ document.body.appendChild(el);
205
+ await el.connectedCallback();
206
+
207
+ const room = { roomId: "room-1", name: "Room One" };
208
+ await el.showRoomQr(room);
209
+
210
+ const group = el.shadowRoot?.querySelector(
211
+ '[data-info="room-qr-group"]',
212
+ ) as HTMLElement;
213
+ const label = el.shadowRoot?.querySelector('[data-info="room-qr-label"]');
214
+ expect(group.hidden).toBe(false);
215
+ expect(label?.textContent).toContain("Room One");
216
+
217
+ document.body.removeChild(el);
218
+ });
219
+
220
+ it("should add and remove trusted peer rows", async () => {
221
+ const el = new ShadowClawPeerJs() as any;
222
+ document.body.appendChild(el);
223
+ await el.connectedCallback();
224
+
225
+ const initialRows =
226
+ el.shadowRoot?.querySelectorAll(".peerjs-trusted-peer-row").length || 0;
227
+ el._appendTrustedPeerRow("peer-xyz", "Peer XYZ");
228
+
229
+ const newRows = el.shadowRoot?.querySelectorAll(".peerjs-trusted-peer-row");
230
+ expect(newRows.length).toBe(initialRows + 1);
231
+
232
+ // Remove row
233
+ const removeBtn = newRows[newRows.length - 1].querySelector(
234
+ ".save-btn--danger",
235
+ ) as HTMLButtonElement;
236
+ removeBtn?.click();
237
+
238
+ expect(
239
+ el.shadowRoot?.querySelectorAll(".peerjs-trusted-peer-row").length,
240
+ ).toBe(initialRows);
241
+
242
+ document.body.removeChild(el);
243
+ });
244
+
245
+ it("should copy room URL and generate trusted peer options with aliases", async () => {
246
+ const el = new ShadowClawPeerJs() as any;
247
+ document.body.appendChild(el);
248
+ await el.connectedCallback();
249
+
250
+ const mockClipboard = {
251
+ writeText: jest.fn<any>().mockResolvedValue(undefined),
252
+ };
253
+ Object.assign(navigator, { clipboard: mockClipboard });
254
+
255
+ const options = el._trustedPeerOptions();
256
+ expect(options.length).toBeGreaterThan(0);
257
+
258
+ document.body.removeChild(el);
259
+ });
260
+
261
+ it("handles copyPeerId and copyPeerUrl", async () => {
262
+ const el = new ShadowClawPeerJs() as any;
263
+ document.body.appendChild(el);
264
+ await el.connectedCallback();
265
+
266
+ const mockClipboard = {
267
+ writeText: jest.fn<any>().mockResolvedValue(undefined),
268
+ };
269
+ Object.assign(navigator, { clipboard: mockClipboard });
270
+
271
+ // 1. Copy Peer ID
272
+ const peerIdInput = el.shadowRoot?.querySelector(
273
+ '[data-setting="peerjs-my-peer-id-input"]',
274
+ ) as HTMLInputElement;
275
+ if (peerIdInput) peerIdInput.value = "my-test-peer";
276
+ await el.copyPeerId();
277
+ expect(mockClipboard.writeText).toHaveBeenCalledWith("my-test-peer");
278
+
279
+ // 2. Copy Peer URL
280
+ el._currentPeerUrl = "http://localhost/?peer=my-test-peer";
281
+ await el.copyPeerUrl();
282
+ expect(mockClipboard.writeText).toHaveBeenCalledWith(
283
+ "http://localhost/?peer=my-test-peer",
284
+ );
285
+
286
+ document.body.removeChild(el);
287
+ });
288
+
289
+ it("handles savePeerJsConfig", async () => {
290
+ const el = new ShadowClawPeerJs() as any;
291
+ document.body.appendChild(el);
292
+ await el.connectedCallback();
293
+
294
+ const hostInput = el.shadowRoot?.querySelector(
295
+ '[data-setting="peerjs-server-host-input"]',
296
+ ) as HTMLInputElement;
297
+ const portInput = el.shadowRoot?.querySelector(
298
+ '[data-setting="peerjs-server-port-input"]',
299
+ ) as HTMLInputElement;
300
+
301
+ if (hostInput) hostInput.value = "peer.example.com";
302
+ if (portInput) portInput.value = "9001";
303
+
304
+ await el.savePeerJsConfig();
305
+ expect(mockConfigurePeerJs).toHaveBeenCalledWith(
306
+ expect.anything(),
307
+ expect.anything(),
308
+ "test-peer-id",
309
+ expect.any(Array),
310
+ "peer.example.com",
311
+ 9001,
312
+ "/myapp",
313
+ false,
314
+ );
315
+
316
+ document.body.removeChild(el);
317
+ });
318
+
319
+ it("handles copyRoomUrl, showRoomQr, updateConnectionStatus, and updateQrCode", async () => {
320
+ const el = new ShadowClawPeerJs() as any;
321
+ document.body.appendChild(el);
322
+ await el.connectedCallback();
323
+
324
+ const mockClipboard = {
325
+ writeText: jest.fn<any>().mockResolvedValue(undefined),
326
+ };
327
+ Object.assign(navigator, { clipboard: mockClipboard });
328
+
329
+ // 1. copyRoomUrl
330
+ el._currentRoomUrl = "http://localhost/?room=test-room";
331
+ await el.copyRoomUrl();
332
+ expect(mockClipboard.writeText).toHaveBeenCalledWith(
333
+ "http://localhost/?room=test-room",
334
+ );
335
+
336
+ // 2. showRoomQr
337
+ const room = {
338
+ roomId: "room-1",
339
+ name: "Test Room",
340
+ members: [],
341
+ createdAt: 0,
342
+ updatedAt: 0,
343
+ };
344
+ await el.showRoomQr(room);
345
+ expect(el._currentRoomUrl).toContain("room-1");
346
+
347
+ // 4. renderRooms
348
+ const { listRooms } =
349
+ await import("../../../core/orchestrator/utils/operations/room.js");
350
+ const testRooms = [
351
+ {
352
+ roomId: "room-host-1",
353
+ name: "My Host Room",
354
+ hostPeerId: "test-peer-id",
355
+ members: [
356
+ { peerId: "test-peer-id", alias: "Host Me", kind: "human" },
357
+ { peerId: "peer-remote-1", alias: "Agent Remote", kind: "agent" },
358
+ ],
359
+ createdAt: Date.now(),
360
+ updatedAt: Date.now(),
361
+ },
362
+ {
363
+ roomId: "room-guest-2",
364
+ name: "Guest Room",
365
+ hostPeerId: "peer-other",
366
+ members: [{ peerId: "peer-other", alias: "Other", kind: "human" }],
367
+ createdAt: Date.now(),
368
+ updatedAt: Date.now(),
369
+ },
370
+ ];
371
+ (listRooms as jest.Mock<any>).mockReturnValue(testRooms);
372
+
373
+ let roomsContainer = el.shadowRoot?.querySelector(
374
+ '[data-info="rooms-list"]',
375
+ );
376
+ if (!roomsContainer) {
377
+ roomsContainer = document.createElement("div");
378
+ roomsContainer.setAttribute("data-info", "rooms-list");
379
+ el.shadowRoot?.appendChild(roomsContainer);
380
+ }
381
+
382
+ el.renderRooms();
383
+ expect(roomsContainer.querySelectorAll(".peerjs-room-card").length).toBe(2);
384
+
385
+ document.body.removeChild(el);
386
+ });
201
387
  });
@@ -54,5 +54,22 @@
54
54
  files directly accessible on your disk.
55
55
  </div>
56
56
  </div>
57
+
58
+ <div class="form-group">
59
+ <div class="form-toggle">
60
+ <input
61
+ data-setting="files-upload-append-ulid-toggle"
62
+ id="files-upload-append-ulid-toggle"
63
+ type="checkbox"
64
+ />
65
+ <label class="form-label" for="files-upload-append-ulid-toggle">
66
+ Append unique ID (ULID) to uploaded file names
67
+ </label>
68
+ </div>
69
+ <div class="form-helper">
70
+ When enabled, uploads automatically prefix file names with a ULID (e.g.
71
+ 01J...-filename.ext). Default: disabled.
72
+ </div>
73
+ </div>
57
74
  </div>
58
75
  </template>