shadow-claw 1.23.4 → 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/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 +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-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-settings/shadow-claw-settings.html +1 -1
- package/dist/public/config-CrHchneq.js +1 -0
- package/dist/public/{configurePeerJs-B4Ym9VMj.js → configurePeerJs-CxpZeDJJ.js} +1 -1
- package/dist/public/{connections-BEn6JYdu.js → connections-DvbgdkaB.js} +1 -1
- package/dist/public/{custom-element-security-DdYlUozq.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/decisions/bundled-typescript-architecture.md +1 -1
- package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +2 -2
- package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
- package/dist/public/docs/example/article/index.html +1 -1
- 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 +1 -1
- package/dist/public/docs/skill-creator/index.html +1 -1
- 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-BQ4KzUt5.js → e2e-bridge-IGWsRl4i.js} +1 -1
- package/dist/public/file-viewer-D0eflgt6.js +1 -0
- package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
- package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
- package/dist/public/{git-DEUvQTUA.js → git-7Vq_SJSp.js} +2 -2
- package/dist/public/{iframe-sanitizer-0NS4XLOd.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
- package/dist/public/index.html +1 -1
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-llGkSJXg.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 +1 -1
- package/dist/public/main/memory/index.html +1 -1
- package/dist/public/{markdown-CyYR4App.js → markdown-BnpKxSxl.js} +1 -1
- package/dist/public/{mcp-reconnect-CSx4aMwy.js → mcp-reconnect-9emsqGRe.js} +1 -1
- package/dist/public/{memoryStorage-Dwg8NhAZ.js → memoryStorage-C0KvLNUp.js} +1 -1
- package/dist/public/{orchestrator-DRfEudXk.js → orchestrator-3h9xxHUm.js} +676 -676
- package/dist/public/orchestrator-nJj59RqD.js +15 -0
- package/dist/public/{peerjs-CurBuKvk.js → peerjs-BP4laMXj.js} +1 -1
- package/dist/public/{push-client-Dii-vPEh.js → push-client-CrjEXK3U.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-5IHaDDMJ.js → shadow-claw-CbwETBWf.js} +2 -2
- package/dist/public/{shadow-claw-accounts-CZvz4iQz.js → shadow-claw-accounts-B4sBTQEj.js} +1 -1
- package/dist/public/{shadow-claw-channels-BKXFyRS3.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
- package/dist/public/{shadow-claw-chat-CCLuhWdI.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-DV-87UUh.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-Dk2dbAUd.js → shadow-claw-file-viewer-Blf6yGVD.js} +1 -1
- package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
- package/dist/public/{shadow-claw-git-DRA7_qck.js → shadow-claw-git-v8S0JjuR.js} +2 -2
- package/dist/public/{shadow-claw-integrations-1Xu5snuV.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
- package/dist/public/{shadow-claw-llm-6Aci1BDP.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-BUjAeVXC.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
- package/dist/public/{shadow-claw-networking-FQe4nCp3.js → shadow-claw-networking-qHbP95z9.js} +1 -1
- package/dist/public/{shadow-claw-notifications-DV2wibL3.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
- package/dist/public/{shadow-claw-page-header-B63S4HTU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
- package/dist/public/{shadow-claw-pages-DE3jWj5K.js → shadow-claw-pages-DFZHhzFZ.js} +1 -1
- package/dist/public/{shadow-claw-settings-S6DAKfYx.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
- package/dist/public/{shadow-claw-storage-DaacA3_K.js → shadow-claw-storage-BFynbP0j.js} +2 -2
- package/dist/public/{shadow-claw-task-server-IjKX042p.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
- package/dist/public/{shadow-claw-tasks-Cy0JYmGU.js → shadow-claw-tasks-Caxc9atL.js} +1 -1
- package/dist/public/{shadow-claw-terminal-znAt7HyP.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
- package/dist/public/{shadow-claw-tools-Cutv9Si5.js → shadow-claw-tools-BfJx3A9W.js} +1 -1
- package/dist/public/{shadow-claw-webvm-BH8mPDEu.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
- package/dist/public/{syncWebMcpRegistration-BqJxaMGR.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.test.ts +76 -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 +273 -0
- package/src/components/shadow-claw-files/shadow-claw-files.test.ts +408 -1
- 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.ts +10 -0
- 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/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/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-BYAuxx3j.js +0 -1
- package/dist/public/downloadGroupFile-Drdvg1Ev.js +0 -1
- package/dist/public/file-viewer-CcZYw-PS.js +0 -1
- package/dist/public/orchestrator-o9Fj67BN.js +0 -15
- package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
- package/dist/public/shadow-claw-files-BpfpJIuP.js +0 -840
|
@@ -17,25 +17,35 @@ describe("TransformersRuntimeService", () => {
|
|
|
17
17
|
jest.resetModules();
|
|
18
18
|
jest.useFakeTimers();
|
|
19
19
|
|
|
20
|
-
mkdirMock = jest.fn();
|
|
20
|
+
mkdirMock = jest.fn().mockResolvedValue(undefined as never);
|
|
21
21
|
readFileMock = jest.fn();
|
|
22
|
-
writeFileMock = jest.fn();
|
|
22
|
+
writeFileMock = jest.fn().mockResolvedValue(undefined as never);
|
|
23
23
|
statMock = jest.fn();
|
|
24
|
-
unlinkMock = jest.fn();
|
|
25
|
-
readdirMock = jest
|
|
24
|
+
unlinkMock = jest.fn().mockResolvedValue(undefined as never);
|
|
25
|
+
readdirMock = jest
|
|
26
|
+
.fn()
|
|
27
|
+
.mockResolvedValue(["model1.onnx", "model2.onnx"] as never);
|
|
26
28
|
|
|
27
|
-
processorMock = Object.assign(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
processorMock = Object.assign(
|
|
30
|
+
jest.fn().mockImplementation(() => ({
|
|
31
|
+
input_ids: { dims: [1, 5] },
|
|
32
|
+
})),
|
|
33
|
+
{
|
|
34
|
+
apply_chat_template: jest.fn(() => "prompt"),
|
|
35
|
+
tokenizer: {
|
|
36
|
+
apply_chat_template: jest.fn(() => "prompt"),
|
|
37
|
+
},
|
|
38
|
+
batch_decode: jest.fn(() => ["Generated response"]),
|
|
39
|
+
dispose: jest.fn(),
|
|
40
|
+
},
|
|
41
|
+
);
|
|
32
42
|
|
|
33
43
|
modelMock = {
|
|
34
44
|
generate: jest.fn(async ({ streamer }: any) => {
|
|
35
45
|
streamer?.callback_function?.("Hello");
|
|
36
46
|
|
|
37
47
|
return {
|
|
38
|
-
|
|
48
|
+
slice: jest.fn(() => [1, 2, 3]),
|
|
39
49
|
};
|
|
40
50
|
}),
|
|
41
51
|
dispose: jest.fn(),
|
|
@@ -52,10 +62,16 @@ describe("TransformersRuntimeService", () => {
|
|
|
52
62
|
Gemma4ForConditionalGeneration: {
|
|
53
63
|
from_pretrained: jest.fn(async () => modelMock),
|
|
54
64
|
},
|
|
65
|
+
AutoModelForCausalLM: {
|
|
66
|
+
from_pretrained: jest.fn(async () => modelMock),
|
|
67
|
+
},
|
|
68
|
+
AutoModelForImageTextToText: {
|
|
69
|
+
from_pretrained: jest.fn(async () => modelMock),
|
|
70
|
+
},
|
|
55
71
|
TextStreamer: class {
|
|
56
72
|
callback_function: any;
|
|
57
73
|
constructor(_tokenizer: any, opts: any) {
|
|
58
|
-
this.callback_function = opts
|
|
74
|
+
this.callback_function = opts?.callback_function;
|
|
59
75
|
}
|
|
60
76
|
},
|
|
61
77
|
};
|
|
@@ -86,7 +102,7 @@ describe("TransformersRuntimeService", () => {
|
|
|
86
102
|
}
|
|
87
103
|
|
|
88
104
|
describe("runChatCompletion", () => {
|
|
89
|
-
it("loads model and generates text", async () => {
|
|
105
|
+
it("loads Gemma 4 model and generates text", async () => {
|
|
90
106
|
const service = await getService();
|
|
91
107
|
const onToken = jest.fn();
|
|
92
108
|
|
|
@@ -98,7 +114,6 @@ describe("TransformersRuntimeService", () => {
|
|
|
98
114
|
onToken,
|
|
99
115
|
});
|
|
100
116
|
|
|
101
|
-
// Advance timers to handle internal awaitOperation timeout poll
|
|
102
117
|
await jest.advanceTimersByTimeAsync(0);
|
|
103
118
|
|
|
104
119
|
const result = await promise;
|
|
@@ -109,10 +124,44 @@ describe("TransformersRuntimeService", () => {
|
|
|
109
124
|
).toHaveBeenCalled();
|
|
110
125
|
});
|
|
111
126
|
|
|
127
|
+
it("loads standard non-Gemma model (Qwen 3)", async () => {
|
|
128
|
+
const service = await getService();
|
|
129
|
+
const onToken = jest.fn();
|
|
130
|
+
|
|
131
|
+
const promise = service.runChatCompletion({
|
|
132
|
+
modelId: "onnx-community/Qwen3-0.6B-ONNX",
|
|
133
|
+
messages: [{ role: "user", content: "hi" }],
|
|
134
|
+
maxCompletionTokens: 10,
|
|
135
|
+
verbose: false,
|
|
136
|
+
onToken,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await jest.advanceTimersByTimeAsync(0);
|
|
140
|
+
|
|
141
|
+
const result = await promise;
|
|
142
|
+
expect(result.text).toBe("Hello");
|
|
143
|
+
expect(
|
|
144
|
+
transformersMock.AutoModelForImageTextToText.from_pretrained,
|
|
145
|
+
).toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("throws error for unsupported model ID", async () => {
|
|
149
|
+
const service = await getService();
|
|
150
|
+
|
|
151
|
+
await expect(
|
|
152
|
+
service.runChatCompletion({
|
|
153
|
+
modelId: "unsupported/model",
|
|
154
|
+
messages: [{ role: "user", content: "hi" }],
|
|
155
|
+
maxCompletionTokens: 10,
|
|
156
|
+
verbose: false,
|
|
157
|
+
}),
|
|
158
|
+
).rejects.toThrow(/not supported by Transformers.js/);
|
|
159
|
+
});
|
|
160
|
+
|
|
112
161
|
it("times out if model loading takes too long", async () => {
|
|
113
162
|
process.env.TRANSFORMERS_JS_REQUEST_TIMEOUT_MS = "100";
|
|
114
163
|
transformersMock.Gemma4ForConditionalGeneration.from_pretrained.mockImplementation(
|
|
115
|
-
() => new Promise(() => {}),
|
|
164
|
+
() => new Promise(() => {}),
|
|
116
165
|
);
|
|
117
166
|
|
|
118
167
|
const service = await getService();
|
|
@@ -130,14 +179,122 @@ describe("TransformersRuntimeService", () => {
|
|
|
130
179
|
await wrappedPromise;
|
|
131
180
|
delete process.env.TRANSFORMERS_JS_REQUEST_TIMEOUT_MS;
|
|
132
181
|
});
|
|
182
|
+
|
|
183
|
+
it("handles batch_decode fallback when streamer produces empty string", async () => {
|
|
184
|
+
modelMock.generate.mockImplementationOnce(async () => ({
|
|
185
|
+
slice: jest.fn(() => [1, 2, 3]),
|
|
186
|
+
}));
|
|
187
|
+
|
|
188
|
+
const service = await getService();
|
|
189
|
+
const promise = service.runChatCompletion({
|
|
190
|
+
modelId: "onnx-community/gemma-4-E2B-it-ONNX",
|
|
191
|
+
messages: [{ role: "user", content: "hi" }],
|
|
192
|
+
maxCompletionTokens: 10,
|
|
193
|
+
verbose: false,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await jest.advanceTimersByTimeAsync(0);
|
|
197
|
+
|
|
198
|
+
const result = await promise;
|
|
199
|
+
expect(result.text).toBe("Generated response");
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("fetchDynamicModels", () => {
|
|
204
|
+
it("returns models from cached models.json if present", async () => {
|
|
205
|
+
readFileMock.mockResolvedValueOnce(
|
|
206
|
+
JSON.stringify({
|
|
207
|
+
models: [
|
|
208
|
+
{
|
|
209
|
+
id: "onnx-community/custom-model",
|
|
210
|
+
name: "Custom (ONNX)",
|
|
211
|
+
context_length: 32000,
|
|
212
|
+
max_completion_tokens: 8192,
|
|
213
|
+
supports_tools: true,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const service = await getService();
|
|
220
|
+
const models = await service.fetchDynamicModels();
|
|
221
|
+
expect(models.length).toBe(1);
|
|
222
|
+
expect(models[0].id).toBe("onnx-community/custom-model");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("fetches from discovery URL when cache file is missing", async () => {
|
|
226
|
+
readFileMock
|
|
227
|
+
.mockRejectedValueOnce(new Error("ENOENT")) // cache file missing
|
|
228
|
+
.mockResolvedValueOnce(
|
|
229
|
+
JSON.stringify([
|
|
230
|
+
{ id: "onnx-community/gemma-4-26B-it-ONNX" },
|
|
231
|
+
{ id: "onnx-community/gemma-4-E4B-it-ONNX" },
|
|
232
|
+
]),
|
|
233
|
+
); // discovery part file content
|
|
234
|
+
|
|
235
|
+
statMock.mockRejectedValue(new Error("ENOENT"));
|
|
236
|
+
|
|
237
|
+
(globalThis as any).fetch = (jest.fn() as any).mockResolvedValue({
|
|
238
|
+
ok: true,
|
|
239
|
+
status: 200,
|
|
240
|
+
body: {
|
|
241
|
+
getReader: () => ({
|
|
242
|
+
read: (jest.fn() as any)
|
|
243
|
+
.mockResolvedValueOnce({
|
|
244
|
+
done: false,
|
|
245
|
+
value: new Uint8Array([123]),
|
|
246
|
+
})
|
|
247
|
+
.mockResolvedValueOnce({ done: true, value: undefined }),
|
|
248
|
+
releaseLock: jest.fn(),
|
|
249
|
+
}),
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const service = await getService();
|
|
254
|
+
const models = await service.fetchDynamicModels();
|
|
255
|
+
expect(models.length).toBeGreaterThan(0);
|
|
256
|
+
expect(models.some((m) => m.id.includes("gemma-4"))).toBe(true);
|
|
257
|
+
});
|
|
133
258
|
});
|
|
134
259
|
|
|
135
|
-
describe("
|
|
260
|
+
describe("getDownloadStatus & getDiskCacheStatus & prewarmModel", () => {
|
|
261
|
+
it("returns download status and allows testing status injection", async () => {
|
|
262
|
+
const service = await getService();
|
|
263
|
+
expect(service.getDownloadStatus().status).toBe("idle");
|
|
264
|
+
|
|
265
|
+
service.__setDownloadStatusForTests({ status: "running", progress: 0.5 });
|
|
266
|
+
expect(service.getDownloadStatus().status).toBe("running");
|
|
267
|
+
expect(service.getDownloadStatus().progress).toBe(0.5);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("returns disk cache status", async () => {
|
|
271
|
+
statMock.mockResolvedValue({ isFile: () => true });
|
|
272
|
+
readdirMock.mockResolvedValue(["entry1", "entry2"]);
|
|
273
|
+
|
|
274
|
+
const service = await getService();
|
|
275
|
+
const status = await service.getDiskCacheStatus();
|
|
276
|
+
expect(status.modelsCatalogExists).toBe(true);
|
|
277
|
+
expect(status.runtimeCacheDirExists).toBe(true);
|
|
278
|
+
expect(status.runtimeCacheEntryCount).toBe(2);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("prewarms model and returns metadata", async () => {
|
|
282
|
+
const service = await getService();
|
|
283
|
+
const info = await service.prewarmModel({
|
|
284
|
+
modelId: "onnx-community/gemma-4-E2B-it-ONNX",
|
|
285
|
+
verbose: false,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(info.modelId).toBe("onnx-community/gemma-4-E2B-it-ONNX");
|
|
289
|
+
expect(info.loader).toBeTruthy();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe("disposeRuntime and reset", () => {
|
|
136
294
|
it("disposes model and processor", async () => {
|
|
137
295
|
const service = await getService();
|
|
138
296
|
const modelId = "onnx-community/gemma-4-E2B-it-ONNX";
|
|
139
297
|
|
|
140
|
-
// Load it first
|
|
141
298
|
await service.runChatCompletion({
|
|
142
299
|
modelId,
|
|
143
300
|
messages: [{ role: "user", content: "hi" }],
|
|
@@ -150,6 +307,19 @@ describe("TransformersRuntimeService", () => {
|
|
|
150
307
|
expect(modelMock.dispose).toHaveBeenCalled();
|
|
151
308
|
expect(processorMock.dispose).toHaveBeenCalled();
|
|
152
309
|
});
|
|
310
|
+
|
|
311
|
+
it("resets runtime for tests", async () => {
|
|
312
|
+
const service = await getService();
|
|
313
|
+
service.__setRuntimeForTests("onnx-community/gemma-4-E2B-it-ONNX", {
|
|
314
|
+
model: modelMock,
|
|
315
|
+
processor: processorMock,
|
|
316
|
+
TextStreamer: class {},
|
|
317
|
+
modelLoaderName: "test",
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
await service.__resetRuntimeForTests();
|
|
321
|
+
expect(modelMock.dispose).toHaveBeenCalled();
|
|
322
|
+
});
|
|
153
323
|
});
|
|
154
324
|
|
|
155
325
|
describe("idle cleanup", () => {
|
|
@@ -164,10 +334,8 @@ describe("TransformersRuntimeService", () => {
|
|
|
164
334
|
verbose: false,
|
|
165
335
|
});
|
|
166
336
|
|
|
167
|
-
// Should still be alive
|
|
168
337
|
expect(modelMock.dispose).not.toHaveBeenCalled();
|
|
169
338
|
|
|
170
|
-
// Wait for idle timeout (10s)
|
|
171
339
|
await jest.advanceTimersByTimeAsync(11000);
|
|
172
340
|
|
|
173
341
|
expect(modelMock.dispose).toHaveBeenCalled();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureServerWebRtc,
|
|
3
|
+
isWebRtcAvailable,
|
|
4
|
+
getPeerClass,
|
|
5
|
+
} from "./webrtc.js";
|
|
6
|
+
|
|
7
|
+
describe("server webrtc", () => {
|
|
8
|
+
it("polyfs WebRTC APIs on globalThis in Node environment", async () => {
|
|
9
|
+
const available = await ensureServerWebRtc();
|
|
10
|
+
expect(available).toBe(true);
|
|
11
|
+
expect(isWebRtcAvailable()).toBe(true);
|
|
12
|
+
expect((globalThis as any).RTCPeerConnection).toBeDefined();
|
|
13
|
+
expect((globalThis as any).RTCSessionDescription).toBeDefined();
|
|
14
|
+
expect((globalThis as any).RTCIceCandidate).toBeDefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("loads PeerJS constructor configured for Node", async () => {
|
|
18
|
+
const PeerClass = await getPeerClass();
|
|
19
|
+
expect(typeof PeerClass).toBe("function");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShadowClaw — Server-Side WebRTC Initialization & PeerJS Loader
|
|
3
|
+
*
|
|
4
|
+
* Polyfills WebRTC (RTCPeerConnection, RTCSessionDescription, RTCIceCandidate)
|
|
5
|
+
* in Node.js environments using node-datachannel, allowing Node servers and CLI
|
|
6
|
+
* tools to establish direct WebRTC RTCDataChannel connections.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
let isAvailable: boolean | null = null;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Ensure WebRTC APIs are present in globalThis for Node.js environments.
|
|
13
|
+
* Returns true if WebRTC is available (either natively or via polyfill).
|
|
14
|
+
*/
|
|
15
|
+
export async function ensureServerWebRtc(): Promise<boolean> {
|
|
16
|
+
if (
|
|
17
|
+
typeof globalThis !== "undefined" &&
|
|
18
|
+
typeof (globalThis as any).RTCPeerConnection !== "undefined"
|
|
19
|
+
) {
|
|
20
|
+
isAvailable = true;
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (isAvailable !== null) {
|
|
25
|
+
return isAvailable;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const wrtc = await import("node-datachannel/polyfill");
|
|
30
|
+
if (wrtc && wrtc.RTCPeerConnection) {
|
|
31
|
+
Object.assign(globalThis, {
|
|
32
|
+
RTCPeerConnection: wrtc.RTCPeerConnection,
|
|
33
|
+
RTCSessionDescription: wrtc.RTCSessionDescription,
|
|
34
|
+
RTCIceCandidate: wrtc.RTCIceCandidate,
|
|
35
|
+
});
|
|
36
|
+
isAvailable = true;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.warn(
|
|
41
|
+
"[webrtc] Failed to load node-datachannel WebRTC polyfill:",
|
|
42
|
+
err instanceof Error ? err.message : String(err),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
isAvailable = false;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check whether WebRTC is available in the current environment.
|
|
52
|
+
*/
|
|
53
|
+
export function isWebRtcAvailable(): boolean {
|
|
54
|
+
if (
|
|
55
|
+
typeof globalThis !== "undefined" &&
|
|
56
|
+
typeof (globalThis as any).RTCPeerConnection !== "undefined"
|
|
57
|
+
) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return isAvailable === true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Dynamically loads and returns the PeerJS Peer constructor configured for Node.
|
|
65
|
+
*/
|
|
66
|
+
export async function getPeerClass(): Promise<any> {
|
|
67
|
+
await ensureServerWebRtc();
|
|
68
|
+
const mod = await import("peerjs");
|
|
69
|
+
return (
|
|
70
|
+
(mod as any).default?.Peer || (mod as any).default || (mod as any).Peer
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
4
4
|
|
|
5
|
-
describe("service-worker fetch proxy
|
|
5
|
+
describe("service-worker fetch proxy", () => {
|
|
6
6
|
let fetchListener: ((event: any) => void) | null = null;
|
|
7
|
+
let messageListener: ((event: any) => void) | null = null;
|
|
7
8
|
let networkFetch: jest.Mock;
|
|
8
9
|
|
|
9
10
|
const imageBytes = new Uint8Array([255, 216, 255, 217]);
|
|
@@ -82,6 +83,7 @@ describe("service-worker fetch proxy workspace routes", () => {
|
|
|
82
83
|
beforeEach(async () => {
|
|
83
84
|
jest.resetModules();
|
|
84
85
|
fetchListener = null;
|
|
86
|
+
messageListener = null;
|
|
85
87
|
|
|
86
88
|
Object.defineProperty(globalThis, "Response", {
|
|
87
89
|
configurable: true,
|
|
@@ -168,6 +170,8 @@ describe("service-worker fetch proxy workspace routes", () => {
|
|
|
168
170
|
(type: string, handler: (event: any) => void) => {
|
|
169
171
|
if (type === "fetch") {
|
|
170
172
|
fetchListener = handler;
|
|
173
|
+
} else if (type === "message") {
|
|
174
|
+
messageListener = handler;
|
|
171
175
|
}
|
|
172
176
|
},
|
|
173
177
|
),
|
|
@@ -292,4 +296,63 @@ describe("service-worker fetch proxy workspace routes", () => {
|
|
|
292
296
|
expect(response).toBeNull();
|
|
293
297
|
expect(networkFetch).not.toHaveBeenCalled();
|
|
294
298
|
});
|
|
299
|
+
|
|
300
|
+
it("handles proxy configuration messages and proxies cross-origin requests", async () => {
|
|
301
|
+
expect(messageListener).toBeTruthy();
|
|
302
|
+
|
|
303
|
+
// Enable proxy via message
|
|
304
|
+
messageListener!({
|
|
305
|
+
data: {
|
|
306
|
+
type: "set-proxy-config",
|
|
307
|
+
payload: {
|
|
308
|
+
useProxy: true,
|
|
309
|
+
proxyUrl: "/proxy",
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
} as any);
|
|
313
|
+
|
|
314
|
+
const crossOriginPostRequest = {
|
|
315
|
+
url: "https://api.external.com/v1/resource",
|
|
316
|
+
method: "POST",
|
|
317
|
+
mode: "cors",
|
|
318
|
+
destination: "fetch",
|
|
319
|
+
headers: new TestHeaders({ Authorization: "Bearer token" }),
|
|
320
|
+
clone: () => ({
|
|
321
|
+
text: async () => JSON.stringify({ key: "val" }),
|
|
322
|
+
}),
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const response = await dispatchFetch(crossOriginPostRequest);
|
|
326
|
+
expect(response.status).toBe(200);
|
|
327
|
+
expect(networkFetch).toHaveBeenCalledWith(
|
|
328
|
+
`${globalThis.location.origin}/proxy/https://api.external.com/v1/resource`,
|
|
329
|
+
expect.objectContaining({
|
|
330
|
+
method: "POST",
|
|
331
|
+
headers: { "Content-Type": "application/json" },
|
|
332
|
+
}),
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("handles legacy set-use-proxy message and direct fetch when proxy disabled", async () => {
|
|
337
|
+
messageListener!({
|
|
338
|
+
data: {
|
|
339
|
+
type: "set-use-proxy",
|
|
340
|
+
payload: {
|
|
341
|
+
useProxy: false,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
} as any);
|
|
345
|
+
|
|
346
|
+
const crossOriginGetRequest = {
|
|
347
|
+
url: "https://api.external.com/v1/data",
|
|
348
|
+
method: "GET",
|
|
349
|
+
mode: "cors",
|
|
350
|
+
destination: "fetch",
|
|
351
|
+
headers: new TestHeaders(),
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const response = await dispatchFetch(crossOriginGetRequest);
|
|
355
|
+
expect(response.status).toBe(200);
|
|
356
|
+
expect(networkFetch).toHaveBeenCalledWith(crossOriginGetRequest);
|
|
357
|
+
});
|
|
295
358
|
});
|
|
@@ -12,6 +12,9 @@ interface PushData {
|
|
|
12
12
|
type?: string;
|
|
13
13
|
taskId?: string;
|
|
14
14
|
groupId?: string;
|
|
15
|
+
clientId?: string;
|
|
16
|
+
action?: string;
|
|
17
|
+
args?: Record<string, unknown>;
|
|
15
18
|
prompt?: string;
|
|
16
19
|
taskType?: string;
|
|
17
20
|
tools?: unknown[];
|
|
@@ -29,6 +32,42 @@ self.addEventListener("push", (event: PushEvent) => {
|
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
// Remote command trigger — relay to client windows and notify
|
|
36
|
+
if (data.type === "remote-command") {
|
|
37
|
+
event.waitUntil(
|
|
38
|
+
self.clients
|
|
39
|
+
.matchAll({ type: "window", includeUncontrolled: true })
|
|
40
|
+
.then((clientList) => {
|
|
41
|
+
for (const client of clientList) {
|
|
42
|
+
client.postMessage({
|
|
43
|
+
type: "remote-command-trigger",
|
|
44
|
+
clientId: data.clientId,
|
|
45
|
+
action: data.action,
|
|
46
|
+
args: data.args,
|
|
47
|
+
prompt: data.prompt,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return self.registration.showNotification(
|
|
52
|
+
"ShadowClaw — Remote Command",
|
|
53
|
+
{
|
|
54
|
+
body: data.prompt || `Remote command received: ${data.action}`,
|
|
55
|
+
icon: "/assets/icons/512.png",
|
|
56
|
+
badge: "/assets/icons/192.png",
|
|
57
|
+
data: {
|
|
58
|
+
type: "remote-command",
|
|
59
|
+
clientId: data.clientId,
|
|
60
|
+
action: data.action,
|
|
61
|
+
args: data.args,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
32
71
|
// Scheduled task trigger — relay to any open client windows, then show notification
|
|
33
72
|
if (data.type === "scheduled-task") {
|
|
34
73
|
event.waitUntil(
|