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.
- package/README.md +42 -5
- package/bin/assert-version-bump.mjs +60 -38
- package/bin/assert-version-bump.test.mjs +74 -11
- package/bin/cli.mjs +166 -0
- package/bin/cli.test.mjs +60 -0
- package/bin/commands/backup.mjs +70 -0
- package/bin/commands/clients.mjs +41 -0
- package/bin/commands/peer-id.mjs +76 -0
- package/bin/commands/peer-id.test.mjs +104 -0
- package/bin/commands/send.mjs +51 -0
- package/bin/commands/tasks.mjs +59 -0
- package/bin/commands/webrtc-listen.mjs +254 -0
- package/bin/prerender-dsd-shell/prerender-dsd-shell.test.mjs +46 -0
- package/bin/rolldown.config.test.mjs +401 -8
- package/bin/utils/control-client.mjs +243 -0
- package/bin/utils/control-client.test.mjs +83 -0
- package/bin/utils/ulid.mjs +45 -0
- package/bin/utils/webrtc-control-client.mjs +699 -0
- package/bin/utils/webrtc-control-client.test.mjs +318 -0
- package/dist/electron/main.cjs +1 -1
- package/dist/public/AGENTS.md +5 -3
- package/dist/public/README.md +42 -5
- package/dist/public/agent.worker.js +6 -6
- package/dist/public/{app-routes-B0ePT49v.js → app-routes-Bs-riIvr.js} +1 -1
- package/dist/public/backup-controller-C_s9in2t.js +1 -0
- package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +1 -0
- package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +1 -0
- package/dist/public/components/settings/shadow-claw-storage/shadow-claw-storage.html +1 -1
- package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +1 -1
- package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.css +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.html +1 -1
- package/dist/public/components/shadow-claw-settings/shadow-claw-settings.html +1 -1
- package/dist/public/config-CrHchneq.js +1 -0
- package/dist/public/{configurePeerJs-B7kNU8AQ.js → configurePeerJs-CxpZeDJJ.js} +1 -1
- package/dist/public/{connections-xd_66YVU.js → connections-DvbgdkaB.js} +1 -1
- package/dist/public/{custom-element-security-CZ92anB8.js → custom-element-security-NCqQ5lmC.js} +1 -1
- package/dist/public/docs/README.md +3 -1
- package/dist/public/docs/architecture/overview.md +12 -12
- package/dist/public/docs/architecture/storage.md +5 -2
- package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
- package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +6 -3
- package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
- package/dist/public/docs/example/article/index.html +2 -2
- package/dist/public/docs/guides/configuring-messaging-channels.md +41 -6
- package/dist/public/docs/guides/server-development-configuration.md +24 -0
- package/dist/public/docs/publishing/index.html +19 -76
- package/dist/public/docs/skill-creator/index.html +2 -2
- package/dist/public/docs/subsystems/backup.md +56 -0
- package/dist/public/docs/subsystems/cli.md +124 -0
- package/dist/public/docs/subsystems/control-plane.md +158 -0
- package/dist/public/docs/subsystems/notifications.md +54 -2
- package/dist/public/docs/subsystems/reactive-ui.md +1 -0
- package/dist/public/downloadGroupFile-By0GR6eV.js +1 -0
- package/dist/public/e2e/README.md +5 -1
- package/dist/public/{e2e-bridge-DkS5xFGX.js → e2e-bridge-IGWsRl4i.js} +1 -1
- package/dist/public/file-viewer-D0eflgt6.js +1 -0
- package/dist/public/files/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
- package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
- package/dist/public/{git-BkjrsRXX.js → git-7Vq_SJSp.js} +2 -2
- package/dist/public/{iframe-sanitizer-L6Jyu__Z.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-BslPXAXS.js → initChatSplitResize-CzV2E7Ul.js} +1 -1
- package/dist/public/initControlPlane-C9jpnWev.js +1 -0
- package/dist/public/llms.txt +1 -1
- package/dist/public/main/index.html +2 -2
- package/dist/public/main/memory/index.html +2 -2
- package/dist/public/{markdown-6MBrIo-S.js → markdown-BnpKxSxl.js} +1 -1
- package/dist/public/mcp-reconnect-9emsqGRe.js +1 -0
- package/dist/public/{memoryStorage-CjGseKI1.js → memoryStorage-C0KvLNUp.js} +1 -1
- package/dist/public/{orchestrator-B9KmIj8Q.js → orchestrator-3h9xxHUm.js} +676 -676
- package/dist/public/orchestrator-nJj59RqD.js +15 -0
- package/dist/public/pages/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/{peerjs-Dv8JNm7v.js → peerjs-BP4laMXj.js} +1 -1
- package/dist/public/{push-client-Dqq06wbB.js → push-client-CrjEXK3U.js} +1 -1
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker/push-handler.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-Bd1rfuVC.js → shadow-claw-CbwETBWf.js} +2 -2
- package/dist/public/{shadow-claw-accounts-DL2l3oM0.js → shadow-claw-accounts-B4sBTQEj.js} +3 -3
- package/dist/public/{shadow-claw-channels-BGXDsQ38.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
- package/dist/public/{shadow-claw-chat-NXg6zz_l.js → shadow-claw-chat-Crv0DTCY.js} +6 -6
- package/dist/public/shadow-claw-control-plane-CX1IGbh9.js +199 -0
- package/dist/public/{shadow-claw-conversations-C8eqGpP9.js → shadow-claw-conversations-_9qAaRXk.js} +1 -1
- package/dist/public/shadow-claw-dialog-BrOwAOdk.js +1 -0
- package/dist/public/shadow-claw-file-viewer-Blf6yGVD.js +577 -0
- package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
- package/dist/public/{shadow-claw-git-C0ntz7aX.js → shadow-claw-git-v8S0JjuR.js} +3 -3
- package/dist/public/{shadow-claw-integrations-DCXV2r69.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
- package/dist/public/{shadow-claw-llm-CudgFQsS.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-B8RkpX28.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
- package/dist/public/{shadow-claw-networking-BLiRsrsK.js → shadow-claw-networking-qHbP95z9.js} +1 -1
- package/dist/public/{shadow-claw-notifications-CQhQcjtL.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
- package/dist/public/{shadow-claw-page-header-NeRD0tKU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
- package/dist/public/{shadow-claw-pages-DCHnFh6r.js → shadow-claw-pages-DFZHhzFZ.js} +3 -3
- package/dist/public/{shadow-claw-settings-VAfjKFQg.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
- package/dist/public/shadow-claw-storage-BFynbP0j.js +219 -0
- package/dist/public/{shadow-claw-task-server-Dk33N_1G.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
- package/dist/public/{shadow-claw-tasks-xA1VfmWN.js → shadow-claw-tasks-Caxc9atL.js} +4 -4
- package/dist/public/{shadow-claw-terminal-Du2r0ciq.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
- package/dist/public/{shadow-claw-tools-CZj4rq7l.js → shadow-claw-tools-BfJx3A9W.js} +2 -2
- package/dist/public/{shadow-claw-webvm-JYUkWICG.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
- package/dist/public/static-main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/static-main-manifest.json +1 -1
- package/dist/public/{syncWebMcpRegistration-DfpvASnh.js → syncWebMcpRegistration-B_M2fCjH.js} +1 -1
- package/dist/server.js +286 -189
- package/package.json +2 -1
- package/src/__mocks__/jszip.cjs +15 -5
- package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +35 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +117 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +81 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +127 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +292 -0
- package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +72 -0
- package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +20 -30
- package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +116 -0
- package/src/components/settings/shadow-claw-llm/model-ranking.test.ts +143 -51
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +307 -4
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.ts +8 -8
- package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +31 -0
- package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +186 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.html +17 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +120 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.ts +56 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +23 -2
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +19 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +2 -1
- package/src/components/shadow-claw/utils/initChatSplitResize.test.ts +180 -13
- package/src/components/shadow-claw/utils/setupEffects.test.ts +39 -0
- package/src/components/shadow-claw-a2ui/catalog/basic/shared.test.ts +117 -0
- package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +1159 -1
- package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +1523 -19
- package/src/components/shadow-claw-chat/utils/escapeHtml.test.ts +13 -0
- package/src/components/shadow-claw-chat/utils/getPeerChatDisplayStatus.test.ts +20 -0
- package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +494 -0
- package/src/components/shadow-claw-dialog/shadow-claw-dialog.ts +13 -2
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +378 -0
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +11 -9
- package/src/components/shadow-claw-files/shadow-claw-files.css +41 -17
- package/src/components/shadow-claw-files/shadow-claw-files.html +35 -0
- package/src/components/shadow-claw-files/shadow-claw-files.test.ts +627 -1
- package/src/components/shadow-claw-files/shadow-claw-files.ts +170 -6
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +31 -0
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.ts +2 -1
- package/src/components/shadow-claw-pages/utils/rewriteWorkspacePreviewHtml.test.ts +15 -0
- package/src/components/shadow-claw-pdf-viewer/utils/installGetOrInsertComputedPolyfill.test.ts +65 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.html +7 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +221 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.ts +2 -0
- package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +407 -0
- package/src/components/shadow-claw-terminal/utils/consumeEscapeSequence.test.ts +58 -0
- package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +80 -0
- package/src/config/config.test.ts +9 -0
- package/src/config/config.ts +15 -0
- package/src/core/app-routes.test.ts +56 -0
- package/src/core/app-routes.ts +8 -1
- package/src/core/backup-controller.test.ts +101 -0
- package/src/core/backup-controller.ts +137 -0
- package/src/core/control-plane-client.test.ts +408 -0
- package/src/core/control-plane-client.ts +425 -0
- package/src/core/orchestrator/orchestrator.ts +4 -0
- package/src/core/orchestrator/utils/compactContext.test.ts +78 -0
- package/src/core/orchestrator/utils/operations/channel.test.ts +254 -9
- package/src/core/orchestrator/utils/operations/provider.test.ts +326 -34
- package/src/core/orchestrator/utils/operations/vm.test.ts +114 -2
- package/src/core/orchestrator/utils/syncProxyConfigToServiceWorker.test.ts +60 -0
- package/src/core/utils/initControlPlane.test.ts +276 -0
- package/src/core/utils/initControlPlane.ts +437 -0
- package/src/core/utils/initializeApp.ts +18 -0
- package/src/db/getSession.test.ts +13 -1
- package/src/db/openDatabase.test.ts +10 -1
- package/src/db/openDatabase.ts +3 -1
- package/src/server/app.test.ts +12 -0
- package/src/server/app.ts +18 -8
- package/src/server/client-registry.test.ts +250 -0
- package/src/server/client-registry.ts +477 -0
- package/src/server/config.ts +15 -0
- package/src/server/control-plane-types.ts +120 -0
- package/src/server/control-plane.test.ts +472 -0
- package/src/server/control-plane.ts +581 -0
- package/src/server/routes/backup.test.ts +263 -0
- package/src/server/routes/backup.ts +262 -0
- package/src/server/routes/integrations-email.test.ts +532 -0
- package/src/server/routes/llamafile.test.ts +172 -48
- package/src/server/routes/mesh-llm.test.ts +192 -0
- package/src/server/routes/ollama.test.ts +49 -0
- package/src/server/server-peer.test.ts +86 -0
- package/src/server/server-peer.ts +330 -0
- package/src/server/server.ts +35 -0
- package/src/server/services/llamafile-manager.test.ts +230 -6
- package/src/server/services/transformers-runtime.test.ts +186 -18
- package/src/server/webrtc.test.ts +21 -0
- package/src/server/webrtc.ts +72 -0
- package/src/service-worker/fetch-proxy.test.ts +64 -1
- package/src/service-worker/push-handler.ts +39 -0
- package/src/shell/vm.test.ts +744 -12
- package/src/storage/ensureMainGroupIndex.test.ts +153 -0
- package/src/storage/getGroupFile.test.ts +66 -0
- package/src/storage/memoryStorage.test.ts +82 -2
- package/src/storage/migrateLegacyOpfs.test.ts +327 -0
- package/src/storage/migrateLegacyOpfs.ts +117 -0
- package/src/storage/writeGroupFileBytes.test.ts +143 -0
- package/src/stores/chat-ui.test.ts +20 -0
- package/src/stores/orchestrator.test.ts +134 -0
- package/src/stores/toast.test.ts +50 -0
- package/src/stores/tools.test.ts +147 -10
- package/src/subsystems/channels/imessage.test.ts +59 -0
- package/src/subsystems/channels/peerjs.test.ts +500 -62
- package/src/subsystems/channels/peerjs.ts +71 -4
- package/src/subsystems/channels/telegram.test.ts +177 -0
- package/src/subsystems/email/catalog.test.ts +1 -0
- package/src/subsystems/email/connections.test.ts +39 -1
- package/src/subsystems/git/credentials.test.ts +160 -825
- package/src/subsystems/git/git.test.ts +463 -0
- package/src/subsystems/notifications/__mocks__/node-sqlite.cjs +12 -1
- package/src/subsystems/notifications/push-routes.test.ts +33 -0
- package/src/subsystems/notifications/push-routes.ts +23 -0
- package/src/subsystems/notifications/push-store.ts +1 -1
- package/src/subsystems/providers/builtin-ai-tasks.test.ts +74 -0
- package/src/subsystems/providers/litert-lm-provider.test.ts +212 -51
- package/src/subsystems/providers/model-registry.test.ts +115 -0
- package/src/subsystems/providers/providers.test.ts +94 -1
- package/src/subsystems/providers/transformers-js-provider.test.ts +279 -0
- package/src/subsystems/providers/utils/createModelCacheFetch.test.ts +2 -2
- package/src/subsystems/providers/utils/downloadModelToCache.test.ts +2 -2
- package/src/subsystems/providers/utils/loadModelStream.test.ts +2 -2
- package/src/subsystems/skills/activateSkill.test.ts +101 -0
- package/src/subsystems/skills/discoverSkills.test.ts +251 -0
- package/src/subsystems/tools/cron.test.ts +64 -0
- package/src/subsystems/tools/declarative.test.ts +255 -89
- package/src/testing/assets/v86.9pfs/libv86.mjs +63 -0
- package/src/testing/assets/v86.ext2/libv86.mjs +3 -0
- package/src/testing/assets_missing_v86/assets/v86.9pfs/libv86.mjs +1 -0
- package/src/testing/jest-setup.ts +328 -40
- package/src/testing/mock-v86.mjs +49 -0
- package/src/ui/a2ui/types.ts +4 -3
- package/src/ui/a2ui/utils/normaliseComponentsToMap.test.ts +102 -0
- package/src/ui/a2ui/utils/resolveChildIds.test.ts +63 -0
- package/src/ui/a2ui/utils/resolveDynamicNumber.test.ts +13 -1
- package/src/worker/tools/bash/utils/waitForVMReady.test.ts +40 -0
- package/src/worker/tools/email/email.test.ts +193 -8
- package/src/worker/tools/email/utils/asEmailAttachmentInputs.test.ts +47 -0
- package/src/worker/tools/git/git.test.ts +214 -3
- package/src/worker/tools/rooms/invite-to-room.test.ts +54 -0
- package/src/worker/tools/rooms/leave-room.test.ts +40 -0
- package/src/worker/tools/rooms/list-room-members.test.ts +75 -0
- package/src/worker/tools/tasks/tasks-utils.test.ts +22 -0
- package/src/worker/tools/ui/ask-user.test.ts +50 -0
- package/src/worker/tools/ui/clear-chat.test.ts +19 -0
- package/src/worker/tools/ui/get-current-time.test.ts +19 -0
- package/src/worker/tools/ui/javascript.test.ts +116 -0
- package/src/worker/tools/ui/list-components.test.ts +10 -0
- package/src/worker/tools/ui/list-tool-profiles.test.ts +42 -0
- package/src/worker/tools/ui/manage-tools.test.ts +34 -0
- package/src/worker/tools/ui/render-component.test.ts +346 -0
- package/src/worker/tools/ui/send-notification.test.ts +40 -0
- package/src/worker/tools/ui/utils/getAllowFullInternetAccess.test.ts +31 -0
- package/src/worker/tools/ui/utils/parseBooleanConfig.test.ts +25 -0
- package/src/worker/tools/workspace/attach-file.test.ts +84 -0
- package/src/worker/tools/workspace/copy-file.test.ts +69 -0
- package/src/worker/tools/workspace/create-directory.test.ts +40 -0
- package/src/worker/tools/workspace/delete-file.test.ts +73 -0
- package/src/worker/tools/workspace/diff-files.test.ts +94 -0
- package/src/worker/tools/workspace/list-files.test.ts +36 -0
- package/src/worker/tools/workspace/move-file.test.ts +66 -0
- package/src/worker/tools/workspace/open-file.test.ts +57 -0
- package/src/worker/tools/workspace/patch-file.test.ts +81 -0
- package/src/worker/tools/workspace/read-file.test.ts +116 -0
- package/src/worker/tools/workspace/send-file.test.ts +82 -0
- package/src/worker/tools/workspace/update-memory.test.ts +29 -0
- package/src/worker/tools/workspace/write-file.test.ts +28 -0
- package/src/worker/utils/rate-limit.test.ts +199 -0
- package/src/worker/utils/tool-state.test.ts +29 -0
- package/src/worker/utils/transformers-js-utils.test.ts +59 -0
- package/dist/public/config-AL77QAx4.js +0 -1
- package/dist/public/downloadGroupFile-BABv_1mJ.js +0 -1
- package/dist/public/file-viewer-CCSABMsE.js +0 -1
- package/dist/public/mcp-reconnect-DoicazQd.js +0 -1
- package/dist/public/orchestrator-4su7iyWj.js +0 -15
- package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
- package/dist/public/shadow-claw-file-viewer-9Qkwc30T.js +0 -577
- package/dist/public/shadow-claw-files-aDyq6Hv7.js +0 -781
- package/dist/public/shadow-claw-storage-Cf31np81.js +0 -202
|
@@ -4,7 +4,6 @@ import { jest } from "@jest/globals";
|
|
|
4
4
|
// Minimal PeerJS mocks
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
|
|
7
|
-
// type DataHandler = (data: unknown) => void;
|
|
8
7
|
type EventHandler = (...args: any[]) => void;
|
|
9
8
|
|
|
10
9
|
class MockDataConnection {
|
|
@@ -44,13 +43,11 @@ class MockPeer {
|
|
|
44
43
|
|
|
45
44
|
constructor(id: string, _opts?: unknown) {
|
|
46
45
|
this.id = id;
|
|
47
|
-
// Simulate async "open" event so start() gets our id
|
|
48
46
|
setTimeout(() => this.emit("open", id), 0);
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
connect(remotePeerId: string): MockDataConnection {
|
|
52
50
|
const conn = new MockDataConnection(remotePeerId);
|
|
53
|
-
// Simulate open
|
|
54
51
|
setTimeout(() => conn.emit("open"), 0);
|
|
55
52
|
|
|
56
53
|
return conn;
|
|
@@ -74,7 +71,6 @@ class MockPeer {
|
|
|
74
71
|
}
|
|
75
72
|
}
|
|
76
73
|
|
|
77
|
-
// Mock the peerjs module
|
|
78
74
|
let lastPeerInstance: MockPeer | null = null;
|
|
79
75
|
|
|
80
76
|
jest.unstable_mockModule("peerjs", () => ({
|
|
@@ -90,7 +86,7 @@ jest.unstable_mockModule("../../db/db.js", () => ({
|
|
|
90
86
|
}));
|
|
91
87
|
|
|
92
88
|
jest.unstable_mockModule("../../storage/readGroupFileBytes.js", () => ({
|
|
93
|
-
readGroupFileBytes: jest.fn(async () => new Uint8Array()),
|
|
89
|
+
readGroupFileBytes: jest.fn(async () => new Uint8Array([1, 2, 3])),
|
|
94
90
|
}));
|
|
95
91
|
|
|
96
92
|
jest.unstable_mockModule("../../storage/writeGroupFileBytes.js", () => ({
|
|
@@ -103,26 +99,16 @@ jest.unstable_mockModule("../../utils/utils.js", () => ({
|
|
|
103
99
|
|
|
104
100
|
const { PeerJsChannel } = await import("./peerjs.js");
|
|
105
101
|
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
|
-
// Helpers
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
|
|
110
102
|
function flushMicrotasks(): Promise<void> {
|
|
111
103
|
return new Promise((r) => setTimeout(r, 10));
|
|
112
104
|
}
|
|
113
105
|
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
// Tests
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
|
|
118
106
|
describe("PeerJsChannel", () => {
|
|
119
107
|
beforeEach(() => {
|
|
120
108
|
lastPeerInstance = null;
|
|
121
109
|
jest.clearAllMocks();
|
|
122
110
|
});
|
|
123
111
|
|
|
124
|
-
// ----- configure -----
|
|
125
|
-
|
|
126
112
|
describe("configure()", () => {
|
|
127
113
|
it("stores myPeerId and trustedPeerIds", () => {
|
|
128
114
|
const ch = new PeerJsChannel();
|
|
@@ -145,8 +131,6 @@ describe("PeerJsChannel", () => {
|
|
|
145
131
|
});
|
|
146
132
|
});
|
|
147
133
|
|
|
148
|
-
// ----- start / stop -----
|
|
149
|
-
|
|
150
134
|
describe("start() / stop()", () => {
|
|
151
135
|
it("creates a Peer on start", async () => {
|
|
152
136
|
const ch = new PeerJsChannel();
|
|
@@ -170,7 +154,7 @@ describe("PeerJsChannel", () => {
|
|
|
170
154
|
it("does not start without configure being called first", async () => {
|
|
171
155
|
const { Peer } = await import("peerjs");
|
|
172
156
|
const ch = new PeerJsChannel();
|
|
173
|
-
ch.start();
|
|
157
|
+
ch.start();
|
|
174
158
|
await new Promise((r) => setTimeout(r, 0));
|
|
175
159
|
expect(Peer).not.toHaveBeenCalled();
|
|
176
160
|
});
|
|
@@ -186,17 +170,8 @@ describe("PeerJsChannel", () => {
|
|
|
186
170
|
});
|
|
187
171
|
});
|
|
188
172
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
describe("onMessage()", () => {
|
|
192
|
-
it("stores the message callback", () => {
|
|
193
|
-
const ch = new PeerJsChannel();
|
|
194
|
-
const cb = jest.fn();
|
|
195
|
-
ch.onMessage(cb);
|
|
196
|
-
expect(ch.messageCallback).toBe(cb);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("fires callback on inbound chat message", async () => {
|
|
173
|
+
describe("onMessage() and typing callbacks", () => {
|
|
174
|
+
it("stores the message callback and fires on chat message", async () => {
|
|
200
175
|
const ch = new PeerJsChannel();
|
|
201
176
|
const cb = jest.fn();
|
|
202
177
|
ch.onMessage(cb);
|
|
@@ -205,12 +180,10 @@ describe("PeerJsChannel", () => {
|
|
|
205
180
|
|
|
206
181
|
await flushMicrotasks();
|
|
207
182
|
|
|
208
|
-
// Simulate incoming connection
|
|
209
183
|
const incomingConn = new MockDataConnection("remote-peer");
|
|
210
184
|
lastPeerInstance!.emit("connection", incomingConn);
|
|
211
185
|
incomingConn.emit("open");
|
|
212
186
|
|
|
213
|
-
// Simulate inbound chat data
|
|
214
187
|
incomingConn.emit("data", { type: "chat", text: "hello" });
|
|
215
188
|
|
|
216
189
|
expect(cb).toHaveBeenCalledWith(
|
|
@@ -223,10 +196,10 @@ describe("PeerJsChannel", () => {
|
|
|
223
196
|
);
|
|
224
197
|
});
|
|
225
198
|
|
|
226
|
-
it("
|
|
199
|
+
it("fires onTyping callback when typing event received", async () => {
|
|
227
200
|
const ch = new PeerJsChannel();
|
|
228
|
-
const
|
|
229
|
-
ch.
|
|
201
|
+
const typingCb = jest.fn();
|
|
202
|
+
ch.onTyping(typingCb);
|
|
230
203
|
ch.configure("my-id", []);
|
|
231
204
|
ch.start();
|
|
232
205
|
|
|
@@ -235,9 +208,10 @@ describe("PeerJsChannel", () => {
|
|
|
235
208
|
const incomingConn = new MockDataConnection("remote-peer");
|
|
236
209
|
lastPeerInstance!.emit("connection", incomingConn);
|
|
237
210
|
incomingConn.emit("open");
|
|
211
|
+
|
|
238
212
|
incomingConn.emit("data", { type: "typing", typing: true });
|
|
239
213
|
|
|
240
|
-
expect(
|
|
214
|
+
expect(typingCb).toHaveBeenCalledWith("peer:remote-peer", true);
|
|
241
215
|
});
|
|
242
216
|
|
|
243
217
|
it("rejects messages from untrusted peers when trustedPeerIds is set", async () => {
|
|
@@ -277,9 +251,7 @@ describe("PeerJsChannel", () => {
|
|
|
277
251
|
});
|
|
278
252
|
});
|
|
279
253
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
describe("send()", () => {
|
|
254
|
+
describe("send() and setTyping()", () => {
|
|
283
255
|
it("sends a chat message over an open connection", async () => {
|
|
284
256
|
const ch = new PeerJsChannel();
|
|
285
257
|
ch.configure("my-id", []);
|
|
@@ -287,14 +259,12 @@ describe("PeerJsChannel", () => {
|
|
|
287
259
|
|
|
288
260
|
await flushMicrotasks();
|
|
289
261
|
|
|
290
|
-
// Simulate remote peer connecting to us first (so we have a conn)
|
|
291
262
|
const incomingConn = new MockDataConnection("remote-peer");
|
|
292
263
|
lastPeerInstance!.emit("connection", incomingConn);
|
|
293
264
|
incomingConn.emit("open");
|
|
294
265
|
|
|
295
266
|
await ch.send("peer:remote-peer", "hello world");
|
|
296
267
|
|
|
297
|
-
// Find the SendMessage call (skip GetAgentCard)
|
|
298
268
|
const calls = (incomingConn.send as jest.Mock).mock.calls;
|
|
299
269
|
const sendMsgCall = calls.find(
|
|
300
270
|
(c: any[]) => c[0]?.method === "SendMessage",
|
|
@@ -309,39 +279,38 @@ describe("PeerJsChannel", () => {
|
|
|
309
279
|
]);
|
|
310
280
|
});
|
|
311
281
|
|
|
312
|
-
it("
|
|
282
|
+
it("sends a chat message with attachments", async () => {
|
|
313
283
|
const ch = new PeerJsChannel();
|
|
314
284
|
ch.configure("my-id", []);
|
|
315
285
|
ch.start();
|
|
316
286
|
|
|
317
287
|
await flushMicrotasks();
|
|
318
288
|
|
|
319
|
-
const incomingConn = new MockDataConnection("
|
|
289
|
+
const incomingConn = new MockDataConnection("remote-peer");
|
|
320
290
|
lastPeerInstance!.emit("connection", incomingConn);
|
|
321
291
|
incomingConn.emit("open");
|
|
322
292
|
|
|
323
|
-
await ch.send("peer:
|
|
293
|
+
await ch.send("peer:remote-peer", "check this file", [
|
|
294
|
+
{
|
|
295
|
+
path: "images/test.png",
|
|
296
|
+
mimeType: "image/png",
|
|
297
|
+
size: 3,
|
|
298
|
+
} as any,
|
|
299
|
+
]);
|
|
324
300
|
|
|
325
|
-
// Find the SendMessage call (skip GetAgentCard)
|
|
326
301
|
const calls = (incomingConn.send as jest.Mock).mock.calls;
|
|
327
302
|
const sendMsgCall = calls.find(
|
|
328
303
|
(c: any[]) => c[0]?.method === "SendMessage",
|
|
329
304
|
);
|
|
330
305
|
expect(sendMsgCall).toBeDefined();
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
{ kind: "text", text: "test" },
|
|
337
|
-
]);
|
|
306
|
+
expect(
|
|
307
|
+
(sendMsgCall![0] as any).params.message.parts.some(
|
|
308
|
+
(p: any) => p.kind === "file",
|
|
309
|
+
),
|
|
310
|
+
).toBe(true);
|
|
338
311
|
});
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// ----- setTyping -----
|
|
342
312
|
|
|
343
|
-
|
|
344
|
-
it("sends a typing message when typing=true", async () => {
|
|
313
|
+
it("sends typing notifications", async () => {
|
|
345
314
|
const ch = new PeerJsChannel();
|
|
346
315
|
ch.configure("my-id", []);
|
|
347
316
|
ch.start();
|
|
@@ -353,13 +322,19 @@ describe("PeerJsChannel", () => {
|
|
|
353
322
|
incomingConn.emit("open");
|
|
354
323
|
|
|
355
324
|
ch.setTyping("peer:remote-peer", true);
|
|
356
|
-
|
|
357
325
|
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
358
326
|
expect.objectContaining({ type: "typing", typing: true }),
|
|
359
327
|
);
|
|
328
|
+
|
|
329
|
+
ch.setTyping("peer:remote-peer", false);
|
|
330
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
331
|
+
expect.objectContaining({ type: "typing", typing: false }),
|
|
332
|
+
);
|
|
360
333
|
});
|
|
334
|
+
});
|
|
361
335
|
|
|
362
|
-
|
|
336
|
+
describe("A2A JSON-RPC request handling", () => {
|
|
337
|
+
it("responds to GetAgentCard request", async () => {
|
|
363
338
|
const ch = new PeerJsChannel();
|
|
364
339
|
ch.configure("my-id", []);
|
|
365
340
|
ch.start();
|
|
@@ -370,15 +345,51 @@ describe("PeerJsChannel", () => {
|
|
|
370
345
|
lastPeerInstance!.emit("connection", incomingConn);
|
|
371
346
|
incomingConn.emit("open");
|
|
372
347
|
|
|
373
|
-
|
|
348
|
+
incomingConn.emit("data", {
|
|
349
|
+
jsonrpc: "2.0",
|
|
350
|
+
id: "req-1",
|
|
351
|
+
method: "GetAgentCard",
|
|
352
|
+
});
|
|
374
353
|
|
|
375
354
|
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
376
|
-
expect.objectContaining({
|
|
355
|
+
expect.objectContaining({
|
|
356
|
+
jsonrpc: "2.0",
|
|
357
|
+
id: "req-1",
|
|
358
|
+
result: expect.objectContaining({
|
|
359
|
+
name: "my-id",
|
|
360
|
+
}),
|
|
361
|
+
}),
|
|
377
362
|
);
|
|
378
363
|
});
|
|
379
|
-
});
|
|
380
364
|
|
|
381
|
-
|
|
365
|
+
it("responds to unknown method with METHOD_NOT_FOUND", async () => {
|
|
366
|
+
const ch = new PeerJsChannel();
|
|
367
|
+
ch.configure("my-id", []);
|
|
368
|
+
ch.start();
|
|
369
|
+
|
|
370
|
+
await flushMicrotasks();
|
|
371
|
+
|
|
372
|
+
const incomingConn = new MockDataConnection("remote-peer");
|
|
373
|
+
lastPeerInstance!.emit("connection", incomingConn);
|
|
374
|
+
incomingConn.emit("open");
|
|
375
|
+
|
|
376
|
+
incomingConn.emit("data", {
|
|
377
|
+
jsonrpc: "2.0",
|
|
378
|
+
id: "req-unknown",
|
|
379
|
+
method: "NonExistentMethod",
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
383
|
+
expect.objectContaining({
|
|
384
|
+
jsonrpc: "2.0",
|
|
385
|
+
id: "req-unknown",
|
|
386
|
+
error: expect.objectContaining({
|
|
387
|
+
code: -32601,
|
|
388
|
+
}),
|
|
389
|
+
}),
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
382
393
|
|
|
383
394
|
describe("isConfigured()", () => {
|
|
384
395
|
it("returns false before configure", () => {
|
|
@@ -392,4 +403,431 @@ describe("PeerJsChannel", () => {
|
|
|
392
403
|
expect(ch.isConfigured()).toBe(true);
|
|
393
404
|
});
|
|
394
405
|
});
|
|
406
|
+
|
|
407
|
+
describe("A2UI & room notification methods", () => {
|
|
408
|
+
it("sends A2UI surface envelopes and A2UI actions", async () => {
|
|
409
|
+
const ch = new PeerJsChannel();
|
|
410
|
+
ch.configure("my-id", []);
|
|
411
|
+
ch.start();
|
|
412
|
+
|
|
413
|
+
await flushMicrotasks();
|
|
414
|
+
|
|
415
|
+
const incomingConn = new MockDataConnection("remote-peer");
|
|
416
|
+
lastPeerInstance!.emit("connection", incomingConn);
|
|
417
|
+
incomingConn.emit("open");
|
|
418
|
+
|
|
419
|
+
// 1. sendA2UI
|
|
420
|
+
await ch.sendA2UI("peer:remote-peer", {
|
|
421
|
+
surfaces: { main: { type: "text", content: "Hello A2UI" } },
|
|
422
|
+
} as any);
|
|
423
|
+
|
|
424
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
425
|
+
expect.objectContaining({
|
|
426
|
+
jsonrpc: "2.0",
|
|
427
|
+
method: "message/send",
|
|
428
|
+
params: expect.objectContaining({
|
|
429
|
+
message: expect.objectContaining({
|
|
430
|
+
role: "agent",
|
|
431
|
+
parts: [
|
|
432
|
+
expect.objectContaining({
|
|
433
|
+
kind: "a2ui",
|
|
434
|
+
}),
|
|
435
|
+
],
|
|
436
|
+
}),
|
|
437
|
+
}),
|
|
438
|
+
}),
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
// 2. sendA2UIAction
|
|
442
|
+
await ch.sendA2UIAction("peer:remote-peer", {
|
|
443
|
+
surfaceId: "main",
|
|
444
|
+
action: "submit",
|
|
445
|
+
payload: { text: "Clicked" },
|
|
446
|
+
} as any);
|
|
447
|
+
|
|
448
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
449
|
+
expect.objectContaining({
|
|
450
|
+
jsonrpc: "2.0",
|
|
451
|
+
method: "message/send",
|
|
452
|
+
params: expect.objectContaining({
|
|
453
|
+
message: expect.objectContaining({
|
|
454
|
+
role: "user",
|
|
455
|
+
parts: [
|
|
456
|
+
expect.objectContaining({
|
|
457
|
+
kind: "a2ui-action",
|
|
458
|
+
}),
|
|
459
|
+
],
|
|
460
|
+
}),
|
|
461
|
+
}),
|
|
462
|
+
}),
|
|
463
|
+
);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("sends room notifications and dispatches to handler", async () => {
|
|
467
|
+
const ch = new PeerJsChannel();
|
|
468
|
+
ch.configure("my-id", []);
|
|
469
|
+
ch.start();
|
|
470
|
+
|
|
471
|
+
await flushMicrotasks();
|
|
472
|
+
|
|
473
|
+
const incomingConn = new MockDataConnection("remote-peer");
|
|
474
|
+
lastPeerInstance!.emit("connection", incomingConn);
|
|
475
|
+
incomingConn.emit("open");
|
|
476
|
+
|
|
477
|
+
// 1. sendRoomNotification
|
|
478
|
+
const sent = ch.sendRoomNotification("remote-peer", {
|
|
479
|
+
jsonrpc: "2.0",
|
|
480
|
+
method: "room/join",
|
|
481
|
+
params: { roomId: "room-123" },
|
|
482
|
+
});
|
|
483
|
+
expect(sent).toBe(true);
|
|
484
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
485
|
+
expect.objectContaining({
|
|
486
|
+
method: "room/join",
|
|
487
|
+
}),
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
// 2. setRoomNotificationHandler & receive notification
|
|
491
|
+
const roomHandler = jest.fn();
|
|
492
|
+
ch.setRoomNotificationHandler(roomHandler);
|
|
493
|
+
|
|
494
|
+
incomingConn.emit("data", {
|
|
495
|
+
jsonrpc: "2.0",
|
|
496
|
+
method: "room/message",
|
|
497
|
+
params: { text: "Hello Room" },
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
expect(roomHandler).toHaveBeenCalledWith("remote-peer", "room/message", {
|
|
501
|
+
text: "Hello Room",
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("manages task lifecycle and notifies onTaskComplete", async () => {
|
|
506
|
+
const ch = new PeerJsChannel();
|
|
507
|
+
ch.configure("my-id", []);
|
|
508
|
+
ch.start();
|
|
509
|
+
|
|
510
|
+
await flushMicrotasks();
|
|
511
|
+
|
|
512
|
+
const incomingConn = new MockDataConnection("remote-peer");
|
|
513
|
+
lastPeerInstance!.emit("connection", incomingConn);
|
|
514
|
+
incomingConn.emit("open");
|
|
515
|
+
|
|
516
|
+
const onComplete = jest.fn();
|
|
517
|
+
ch.onTaskComplete(onComplete);
|
|
518
|
+
|
|
519
|
+
// Verify task manager creation
|
|
520
|
+
const tm = ch.getTaskManager("remote-peer");
|
|
521
|
+
expect(tm).toBeDefined();
|
|
522
|
+
|
|
523
|
+
// Emit terminal task status update from remote peer
|
|
524
|
+
incomingConn.emit("data", {
|
|
525
|
+
jsonrpc: "2.0",
|
|
526
|
+
method: "tasks/statusUpdate",
|
|
527
|
+
params: {
|
|
528
|
+
taskId: "t-1",
|
|
529
|
+
contextId: "c-1",
|
|
530
|
+
status: {
|
|
531
|
+
state: "TASK_STATE_COMPLETED",
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
expect(onComplete).toHaveBeenCalledWith("peer:remote-peer");
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("tracks peer connection status with isPeerConnected", async () => {
|
|
540
|
+
const ch = new PeerJsChannel();
|
|
541
|
+
ch.configure("my-id", []);
|
|
542
|
+
ch.start();
|
|
543
|
+
|
|
544
|
+
await flushMicrotasks();
|
|
545
|
+
|
|
546
|
+
expect(ch.isPeerConnected("remote-peer")).toBe(false);
|
|
547
|
+
|
|
548
|
+
const incomingConn = new MockDataConnection("remote-peer");
|
|
549
|
+
lastPeerInstance!.emit("connection", incomingConn);
|
|
550
|
+
incomingConn.emit("open");
|
|
551
|
+
|
|
552
|
+
expect(ch.isPeerConnected("remote-peer")).toBe(true);
|
|
553
|
+
|
|
554
|
+
ch.stop();
|
|
555
|
+
expect(ch.isPeerConnected("remote-peer")).toBe(false);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it("processes inbound A2A envelopes with file attachments and remapping", async () => {
|
|
559
|
+
const ch = new PeerJsChannel();
|
|
560
|
+
ch.configure("my-id", []);
|
|
561
|
+
ch.start();
|
|
562
|
+
|
|
563
|
+
await flushMicrotasks();
|
|
564
|
+
|
|
565
|
+
const incomingConn = new MockDataConnection("remote-peer");
|
|
566
|
+
lastPeerInstance!.emit("connection", incomingConn);
|
|
567
|
+
incomingConn.emit("open");
|
|
568
|
+
|
|
569
|
+
const messageCallback = jest.fn();
|
|
570
|
+
ch.onMessage(messageCallback);
|
|
571
|
+
|
|
572
|
+
// Inbound A2A envelope with text and file part
|
|
573
|
+
incomingConn.emit("data", {
|
|
574
|
+
jsonrpc: "2.0",
|
|
575
|
+
method: "message/send",
|
|
576
|
+
params: {
|
|
577
|
+
message: {
|
|
578
|
+
role: "user",
|
|
579
|
+
parts: [
|
|
580
|
+
{ kind: "text", text: "Here is the report [doc](doc.pdf)" },
|
|
581
|
+
{
|
|
582
|
+
kind: "file",
|
|
583
|
+
name: "doc.pdf",
|
|
584
|
+
mimeType: "application/pdf",
|
|
585
|
+
size: 1024,
|
|
586
|
+
},
|
|
587
|
+
],
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
expect(messageCallback).toHaveBeenCalledWith(
|
|
593
|
+
expect.objectContaining({
|
|
594
|
+
groupId: "peer:remote-peer",
|
|
595
|
+
sender: "remote-peer",
|
|
596
|
+
content: "Here is the report [doc](doc.pdf)",
|
|
597
|
+
attachments: [
|
|
598
|
+
expect.objectContaining({
|
|
599
|
+
fileName: "doc.pdf",
|
|
600
|
+
mimeType: "application/pdf",
|
|
601
|
+
size: 1024,
|
|
602
|
+
}),
|
|
603
|
+
],
|
|
604
|
+
}),
|
|
605
|
+
);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it("completes active task when present and returns false when absent", async () => {
|
|
609
|
+
const ch = new PeerJsChannel();
|
|
610
|
+
ch.configure("my-id", []);
|
|
611
|
+
expect(ch.completeActiveTask("peer:remote-peer")).toBe(false);
|
|
612
|
+
|
|
613
|
+
const tm = ch.getTaskManager("remote-peer");
|
|
614
|
+
expect(ch.completeActiveTask("peer:remote-peer")).toBe(false);
|
|
615
|
+
|
|
616
|
+
// Create a task
|
|
617
|
+
tm.handleSendMessage({
|
|
618
|
+
message: {
|
|
619
|
+
messageId: "msg-123",
|
|
620
|
+
role: "user" as any,
|
|
621
|
+
parts: [{ text: "do task" }],
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
expect(ch.completeActiveTask("peer:remote-peer")).toBe(true);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it("handles connectPeer, setTyping, and room notifications", async () => {
|
|
629
|
+
const ch = new PeerJsChannel();
|
|
630
|
+
ch.configure("my-id", []);
|
|
631
|
+
ch.start();
|
|
632
|
+
|
|
633
|
+
await flushMicrotasks();
|
|
634
|
+
|
|
635
|
+
ch.connectPeer("remote-peer-2");
|
|
636
|
+
expect(ch.isPeerConnected("remote-peer-2")).toBe(true);
|
|
637
|
+
|
|
638
|
+
ch.setTyping("peer:remote-peer-2", true);
|
|
639
|
+
const conn = (ch as any).connections.get("remote-peer-2");
|
|
640
|
+
expect(conn.send).toHaveBeenCalledWith({ type: "typing", typing: true });
|
|
641
|
+
|
|
642
|
+
// sendRoomNotification with open connection
|
|
643
|
+
const sent = ch.sendRoomNotification("remote-peer-2", {
|
|
644
|
+
jsonrpc: "2.0",
|
|
645
|
+
method: "room/custom",
|
|
646
|
+
params: { ok: true },
|
|
647
|
+
});
|
|
648
|
+
expect(sent).toBe(true);
|
|
649
|
+
|
|
650
|
+
// sendRoomNotification when connection opens later
|
|
651
|
+
const freshConn = new MockDataConnection("remote-peer-3");
|
|
652
|
+
freshConn.open = false;
|
|
653
|
+
(lastPeerInstance as any).connect = jest.fn(() => freshConn);
|
|
654
|
+
|
|
655
|
+
const queued = ch.sendRoomNotification("remote-peer-3", {
|
|
656
|
+
jsonrpc: "2.0",
|
|
657
|
+
method: "room/queued",
|
|
658
|
+
params: {},
|
|
659
|
+
});
|
|
660
|
+
expect(queued).toBe(true);
|
|
661
|
+
|
|
662
|
+
freshConn.open = true;
|
|
663
|
+
freshConn.emit("open");
|
|
664
|
+
await flushMicrotasks();
|
|
665
|
+
expect(freshConn.send).toHaveBeenCalledWith({
|
|
666
|
+
jsonrpc: "2.0",
|
|
667
|
+
method: "room/queued",
|
|
668
|
+
params: {},
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("tracks peerjs-dc-handle-chunk event on globalThis", async () => {
|
|
673
|
+
new PeerJsChannel();
|
|
674
|
+
const customEvent = new CustomEvent("peerjs-dc-handle-chunk", {
|
|
675
|
+
detail: {
|
|
676
|
+
chunkInfo: { count: 1, total: 3 },
|
|
677
|
+
},
|
|
678
|
+
});
|
|
679
|
+
globalThis.dispatchEvent(customEvent);
|
|
680
|
+
|
|
681
|
+
const { transferProgressSignal } = await import("./peerjs.js");
|
|
682
|
+
expect(transferProgressSignal.get()).toEqual({
|
|
683
|
+
count: 2,
|
|
684
|
+
total: 3,
|
|
685
|
+
direction: "receive",
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it("sends file attachments and markdown links over DataConnection", async () => {
|
|
690
|
+
const ch = new PeerJsChannel();
|
|
691
|
+
ch.configure("my-id", []);
|
|
692
|
+
ch.start();
|
|
693
|
+
|
|
694
|
+
await flushMicrotasks();
|
|
695
|
+
|
|
696
|
+
const incomingConn = new MockDataConnection("remote-peer-files");
|
|
697
|
+
lastPeerInstance!.emit("connection", incomingConn);
|
|
698
|
+
incomingConn.emit("open");
|
|
699
|
+
|
|
700
|
+
await ch.send("peer:remote-peer-files", "See [notes](notes.txt)", [
|
|
701
|
+
{ path: "image.png", mimeType: "image/png" } as any,
|
|
702
|
+
]);
|
|
703
|
+
|
|
704
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
705
|
+
expect.objectContaining({
|
|
706
|
+
type: "__file_header",
|
|
707
|
+
name: "image.png",
|
|
708
|
+
mimeType: "image/png",
|
|
709
|
+
}),
|
|
710
|
+
);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it("sends A2UI and A2UIAction envelopes", async () => {
|
|
714
|
+
const ch = new PeerJsChannel();
|
|
715
|
+
ch.configure("my-id", []);
|
|
716
|
+
ch.start();
|
|
717
|
+
|
|
718
|
+
await flushMicrotasks();
|
|
719
|
+
|
|
720
|
+
const incomingConn = new MockDataConnection("remote-peer-a2ui");
|
|
721
|
+
lastPeerInstance!.emit("connection", incomingConn);
|
|
722
|
+
incomingConn.emit("open");
|
|
723
|
+
|
|
724
|
+
// 1. sendA2UI
|
|
725
|
+
await ch.sendA2UI("peer:remote-peer-a2ui", {
|
|
726
|
+
surfaceId: "s1",
|
|
727
|
+
components: [],
|
|
728
|
+
} as any);
|
|
729
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
730
|
+
expect.objectContaining({
|
|
731
|
+
method: "message/send",
|
|
732
|
+
}),
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
// 2. sendA2UIAction
|
|
736
|
+
await ch.sendA2UIAction("peer:remote-peer-a2ui", {
|
|
737
|
+
actionId: "a1",
|
|
738
|
+
surfaceId: "s1",
|
|
739
|
+
componentId: "c1",
|
|
740
|
+
} as any);
|
|
741
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
742
|
+
expect.objectContaining({
|
|
743
|
+
method: "message/send",
|
|
744
|
+
}),
|
|
745
|
+
);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it("handles inbound A2A JSON-RPC requests, errors, and responses", async () => {
|
|
749
|
+
const ch = new PeerJsChannel();
|
|
750
|
+
ch.configure("my-id", []);
|
|
751
|
+
ch.start();
|
|
752
|
+
|
|
753
|
+
await flushMicrotasks();
|
|
754
|
+
|
|
755
|
+
const incomingConn = new MockDataConnection("remote-peer-rpc");
|
|
756
|
+
lastPeerInstance!.emit("connection", incomingConn);
|
|
757
|
+
incomingConn.emit("open");
|
|
758
|
+
|
|
759
|
+
// 1. Unknown method
|
|
760
|
+
incomingConn.emit("data", {
|
|
761
|
+
jsonrpc: "2.0",
|
|
762
|
+
id: "req-1",
|
|
763
|
+
method: "unknown/method",
|
|
764
|
+
params: {},
|
|
765
|
+
});
|
|
766
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
767
|
+
expect.objectContaining({
|
|
768
|
+
id: "req-1",
|
|
769
|
+
error: expect.objectContaining({ code: -32601 }),
|
|
770
|
+
}),
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
// 2. GetTask missing params
|
|
774
|
+
incomingConn.emit("data", {
|
|
775
|
+
jsonrpc: "2.0",
|
|
776
|
+
id: "req-2",
|
|
777
|
+
method: "GetTask",
|
|
778
|
+
params: {},
|
|
779
|
+
});
|
|
780
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
781
|
+
expect.objectContaining({
|
|
782
|
+
id: "req-2",
|
|
783
|
+
error: expect.objectContaining({ code: -32602 }),
|
|
784
|
+
}),
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
// 3. CancelTask missing params
|
|
788
|
+
incomingConn.emit("data", {
|
|
789
|
+
jsonrpc: "2.0",
|
|
790
|
+
id: "req-3",
|
|
791
|
+
method: "CancelTask",
|
|
792
|
+
params: {},
|
|
793
|
+
});
|
|
794
|
+
expect(incomingConn.send).toHaveBeenCalledWith(
|
|
795
|
+
expect.objectContaining({
|
|
796
|
+
id: "req-3",
|
|
797
|
+
error: expect.objectContaining({ code: -32602 }),
|
|
798
|
+
}),
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
// 4. Pending request resolution
|
|
802
|
+
const resolveFn = jest.fn();
|
|
803
|
+
(ch as any)._pendingRequests.set("req-test", {
|
|
804
|
+
resolve: resolveFn,
|
|
805
|
+
reject: jest.fn(),
|
|
806
|
+
timer: setTimeout(() => {}, 10000),
|
|
807
|
+
});
|
|
808
|
+
incomingConn.emit("data", {
|
|
809
|
+
jsonrpc: "2.0",
|
|
810
|
+
id: "req-test",
|
|
811
|
+
result: { ok: true },
|
|
812
|
+
});
|
|
813
|
+
expect(resolveFn).toHaveBeenCalledWith(
|
|
814
|
+
expect.objectContaining({ result: { ok: true } }),
|
|
815
|
+
);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
it("supports wildcard and prefix trusted peer matching", async () => {
|
|
819
|
+
const ch = new PeerJsChannel();
|
|
820
|
+
ch.trustedPeerIds = new Set(["cli-*", "server-node"]);
|
|
821
|
+
|
|
822
|
+
expect((ch as any)._isTrustedPeer("cli-01m1cke479mr186s4hbp4a3c3h")).toBe(
|
|
823
|
+
true,
|
|
824
|
+
);
|
|
825
|
+
expect((ch as any)._isTrustedPeer("server-node")).toBe(true);
|
|
826
|
+
expect((ch as any)._isTrustedPeer("random-untrusted-peer")).toBe(false);
|
|
827
|
+
|
|
828
|
+
ch.trustedPeerIds = new Set(["cli"]);
|
|
829
|
+
expect((ch as any)._isTrustedPeer("cli-12345")).toBe(true);
|
|
830
|
+
expect((ch as any)._isTrustedPeer("other-12345")).toBe(false);
|
|
831
|
+
});
|
|
832
|
+
});
|
|
395
833
|
});
|