shadow-claw 1.52.0 → 1.52.1

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 (112) hide show
  1. package/.well-known/mcp/server-card.json +1 -1
  2. package/.well-known/mcp.json +1 -1
  3. package/README.md +9 -9
  4. package/dist/cli/build/build.js +1 -1
  5. package/dist/cli/{chunk-DYq-orHK.js → chunk-BgTv9w0K.js} +1 -1
  6. package/dist/cli/{chunk-C6jii2Fl.js → chunk-d5hIhj8t.js} +1 -1
  7. package/dist/cli/cli.js +1 -1
  8. package/dist/lib/components/index.js +1 -1
  9. package/dist/lib/components/shadow-claw-pages.js +1 -1
  10. package/dist/lib/index.js +1 -1
  11. package/dist/lib/{shadow-claw-pages-kA2qa-4B.js → shadow-claw-pages-CjHS50HN.js} +2 -2
  12. package/dist/lib/{shadow-claw-pages-kA2qa-4B.js.map → shadow-claw-pages-CjHS50HN.js.map} +1 -1
  13. package/dist/public/.well-known/mcp/server-card.json +1 -1
  14. package/dist/public/.well-known/mcp.json +1 -1
  15. package/dist/public/AGENTS.md +1 -1
  16. package/dist/public/README.md +9 -9
  17. package/dist/public/docs/README.md +1 -1
  18. package/dist/public/docs/architecture/orchestrator.md +2 -2
  19. package/dist/public/docs/architecture/overview.md +3 -3
  20. package/dist/public/docs/architecture/worker-protocol.md +29 -29
  21. package/dist/public/docs/example/article/index.html +1 -1
  22. package/dist/public/docs/example/article.html +1 -1
  23. package/dist/public/docs/publishing/index.html +1 -1
  24. package/dist/public/docs/publishing.html +1 -1
  25. package/dist/public/docs/skill-creator/index.html +1 -1
  26. package/dist/public/docs/skill-creator.html +1 -1
  27. package/dist/public/docs/subsystems/local-models.md +1 -1
  28. package/dist/public/docs/subsystems/providers.md +11 -4
  29. package/dist/public/e2e/README.md +2 -2
  30. package/dist/public/index.html +1 -1
  31. package/dist/public/index.js +1 -1
  32. package/dist/public/llms.txt +1 -1
  33. package/dist/public/main/index.html +1 -1
  34. package/dist/public/main/memory/index.html +1 -1
  35. package/dist/public/main/memory.html +1 -1
  36. package/dist/public/main.html +1 -1
  37. package/dist/public/service-worker.js +1 -1
  38. package/dist/public/{shadow-claw-Ba2ZFE9m.js → shadow-claw-T9oPpqxT.js} +1 -1
  39. package/dist/public/{shadow-claw-pages-Cy6T0G9y.js → shadow-claw-pages-IW_LNEL_.js} +1 -1
  40. package/package.json +1 -1
  41. package/src/cli/build/build.test.ts +95 -0
  42. package/src/cli/build/build.ts +5 -1
  43. package/src/cli/commands/agent.test.ts +134 -0
  44. package/src/cli/commands/backup.test.ts +85 -0
  45. package/src/cli/commands/clients.test.ts +32 -0
  46. package/src/cli/commands/mcp.test.ts +260 -1
  47. package/src/cli/commands/send-file.test.ts +73 -0
  48. package/src/cli/commands/send.test.ts +58 -0
  49. package/src/cli/commands/tasks.test.ts +78 -0
  50. package/src/cli/commands/webrtc-listen.test.ts +74 -0
  51. package/src/cli/tools/assert-version-bump.test.ts +30 -1
  52. package/src/cli/tools/file-search-replace.test.ts +33 -1
  53. package/src/cli/tools/patch-service-worker-trusted-types.test.ts +24 -1
  54. package/src/cli/tools/touch-nojekyll.test.ts +12 -0
  55. package/src/cli/tools/version.test.ts +109 -5
  56. package/src/cli/utils/agent-core.test.ts +40 -0
  57. package/src/cli/utils/control-client.test.ts +239 -2
  58. package/src/cli/utils/load-workspace-config.test.ts +63 -0
  59. package/src/cli/utils/local-models.test.ts +98 -0
  60. package/src/cli/utils/progress-bar.test.ts +41 -2
  61. package/src/cli/utils/resolve-cache-dir.test.ts +61 -0
  62. package/src/cli/utils/resolve-project-root.test.ts +17 -0
  63. package/src/cli/utils/stdin.test.ts +38 -0
  64. package/src/cli/utils/stdio.test.ts +51 -1
  65. package/src/cli/utils/suppress-warnings.test.ts +9 -0
  66. package/src/cli/utils/webrtc-control-client.test.ts +569 -0
  67. package/src/common/utils/frontmatter.test.ts +117 -0
  68. package/src/components/common/help/providers.test.ts +67 -0
  69. package/src/components/common/shadow-claw-provider-model-picker/shadow-claw-provider-model-picker.test.ts +121 -0
  70. package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +131 -0
  71. package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +130 -0
  72. package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +12 -0
  73. package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +328 -0
  74. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +530 -8
  75. package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +459 -0
  76. package/src/components/settings/shadow-claw-networking/shadow-claw-networking.test.ts +88 -0
  77. package/src/components/settings/shadow-claw-notifications/shadow-claw-notifications.test.ts +92 -0
  78. package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +127 -0
  79. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +173 -0
  80. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +62 -0
  81. package/src/components/settings/shadow-claw-webvm/shadow-claw-webvm.test.ts +104 -0
  82. package/src/components/shadow-claw/shadow-claw.test.ts +112 -0
  83. package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +109 -0
  84. package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +361 -0
  85. package/src/components/shadow-claw-dialog/shadow-claw-dialog.test.ts +76 -0
  86. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +307 -0
  87. package/src/components/shadow-claw-files/shadow-claw-files.test.ts +398 -1
  88. package/src/components/shadow-claw-pages/shadow-claw-pages.test.ts +85 -0
  89. package/src/components/shadow-claw-pages/shadow-claw-pages.ts +2 -1
  90. package/src/components/shadow-claw-pdf-viewer/shadow-claw-pdf-viewer.test.ts +229 -0
  91. package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +233 -0
  92. package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +223 -1
  93. package/src/components/shadow-claw-toast/shadow-claw-toast.test.ts +79 -0
  94. package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +229 -1
  95. package/src/server/logger.test.ts +57 -0
  96. package/src/server/peer.test.ts +47 -0
  97. package/src/server/routes/gemini.test.ts +244 -0
  98. package/src/server/server-peer.test.ts +245 -11
  99. package/src/server/services/llamafile-manager.test.ts +668 -0
  100. package/src/share-target/pending-shares.test.ts +90 -0
  101. package/src/shell/fs.test.ts +125 -0
  102. package/src/shell/shell.test.ts +8 -0
  103. package/src/subsystems/git/credentials.test.ts +260 -0
  104. package/src/subsystems/git/git.test.ts +64 -0
  105. package/src/subsystems/providers/executeNativeAiTask.test.ts +78 -0
  106. package/src/subsystems/providers/utils/parseLocalModelToolCall.test.ts +57 -0
  107. package/src/worker/tools/builtin-ai/builtin-ai.test.ts +88 -0
  108. package/src/worker/tools/email/email.test.ts +358 -0
  109. package/src/worker/tools/email/utils/resolveIntegrationEmailAuth.test.ts +120 -0
  110. package/src/worker/tools/email/utils/resolveUniqueAttachmentPath.test.ts +80 -0
  111. package/src/worker/tools/fetch-file/fetch-file.test.ts +111 -0
  112. package/src/worker/tools/fetch-url/fetch-url.test.ts +295 -0
@@ -1,13 +1,73 @@
1
1
  import { jest } from "@jest/globals";
2
- import { ServerPeer } from "./server-peer.js";
2
+ import { EventEmitter } from "node:events";
3
+
4
+ class MockConn extends EventEmitter {
5
+ public peer: string;
6
+ public open = false;
7
+ public sent: any[] = [];
8
+
9
+ constructor(peer: string) {
10
+ super();
11
+ this.peer = peer;
12
+ }
13
+
14
+ send(data: any) {
15
+ this.sent.push(data);
16
+ }
17
+
18
+ close() {
19
+ this.open = false;
20
+ this.emit("close");
21
+ }
22
+ }
23
+
24
+ class MockPeer extends EventEmitter {
25
+ public id: string;
26
+ public options: any;
27
+ public connections: MockConn[] = [];
28
+
29
+ constructor(id: string, options: any) {
30
+ super();
31
+ this.id = id;
32
+ this.options = options;
33
+ }
34
+
35
+ connect(remotePeerId: string) {
36
+ const conn = new MockConn(remotePeerId);
37
+ this.connections.push(conn);
38
+ return conn;
39
+ }
40
+
41
+ destroy() {
42
+ this.emit("close");
43
+ }
44
+ }
45
+
46
+ let mockPeerInstance: MockPeer | null = null;
47
+ const mockPeerConstructor = jest.fn((id: string, opts: any) => {
48
+ mockPeerInstance = new MockPeer(id, opts);
49
+ return mockPeerInstance;
50
+ });
51
+
52
+ jest.unstable_mockModule("./webrtc.js", () => ({
53
+ getPeerClass: jest.fn(async () => mockPeerConstructor),
54
+ }));
55
+
56
+ const { ServerPeer } = await import("./server-peer.js");
3
57
 
4
58
  describe("ServerPeer", () => {
59
+ beforeEach(() => {
60
+ mockPeerInstance = null;
61
+ jest.clearAllMocks();
62
+ });
63
+
5
64
  it("initializes ServerPeer with options and handlers", () => {
6
65
  const mockHandler = jest.fn();
7
66
  const peer = new ServerPeer({
8
67
  peerId: "test-server-peer",
9
68
  host: "127.0.0.1",
10
69
  port: 8888,
70
+ verbose: true,
11
71
  handlers: {
12
72
  "custom-action": mockHandler,
13
73
  },
@@ -18,6 +78,49 @@ describe("ServerPeer", () => {
18
78
  expect(peer.getConnectedPeers()).toEqual([]);
19
79
  });
20
80
 
81
+ it("starts successfully and handles peer lifecycle", async () => {
82
+ const peer = new ServerPeer({
83
+ peerId: "my-peer",
84
+ verbose: true,
85
+ });
86
+
87
+ const startPromise = peer.start();
88
+ await new Promise((r) => setTimeout(r, 10));
89
+ expect(mockPeerInstance).toBeDefined();
90
+
91
+ mockPeerInstance!.emit("open", "my-peer");
92
+ const id = await startPromise;
93
+ expect(id).toBe("my-peer");
94
+ expect(peer.isOpen).toBe(true);
95
+
96
+ // Incoming connection from remote peer
97
+ const incomingConn = new MockConn("remote-client");
98
+ mockPeerInstance!.emit("connection", incomingConn);
99
+
100
+ incomingConn.open = true;
101
+ incomingConn.emit("open");
102
+ expect(peer.isPeerConnected("remote-client")).toBe(true);
103
+ expect(peer.getConnectedPeers()).toEqual(["remote-client"]);
104
+
105
+ // Connection error and close
106
+ incomingConn.emit("error", new Error("DataChannel drop"));
107
+ incomingConn.emit("close");
108
+ expect(peer.isPeerConnected("remote-client")).toBe(false);
109
+
110
+ // Peer close
111
+ mockPeerInstance!.emit("close");
112
+ expect(peer.isOpen).toBe(false);
113
+ });
114
+
115
+ it("handles start error before open", async () => {
116
+ const peer = new ServerPeer({ peerId: "err-peer" });
117
+ const startPromise = peer.start();
118
+ await new Promise((r) => setTimeout(r, 10));
119
+
120
+ mockPeerInstance!.emit("error", new Error("Signaling unreachable"));
121
+ await expect(startPromise).rejects.toThrow("Signaling unreachable");
122
+ });
123
+
21
124
  it("handles incoming command:execute and command:result payloads", async () => {
22
125
  const mockHandler = jest.fn(async (args: any) => ({
23
126
  ok: true,
@@ -36,16 +139,20 @@ describe("ServerPeer", () => {
36
139
  send: (data: any) => sentMessages.push(data),
37
140
  };
38
141
 
39
- // Simulate incoming command:execute
40
- await (peer as any)._handleData("remote-client-peer", mockConn, {
41
- id: "req-1",
42
- type: "command:execute",
43
- payload: {
44
- commandId: "cmd-1",
45
- action: "echo-test",
46
- args: { text: "hello peer" },
47
- },
48
- });
142
+ // Simulate string rawData with JSON parsing
143
+ await (peer as any)._handleData(
144
+ "remote-client-peer",
145
+ mockConn,
146
+ JSON.stringify({
147
+ id: "req-1",
148
+ type: "command:execute",
149
+ payload: {
150
+ commandId: "cmd-1",
151
+ action: "echo-test",
152
+ args: { text: "hello peer" },
153
+ },
154
+ }),
155
+ );
49
156
 
50
157
  expect(mockHandler).toHaveBeenCalledWith({ text: "hello peer" });
51
158
  expect(sentMessages).toHaveLength(1);
@@ -57,6 +164,38 @@ describe("ServerPeer", () => {
57
164
  });
58
165
  });
59
166
 
167
+ it("handles handler exception during command:execute", async () => {
168
+ const mockHandler = jest.fn(async () => {
169
+ throw new Error("Action failure");
170
+ });
171
+ const peer = new ServerPeer({
172
+ peerId: "test-server-peer",
173
+ handlers: {
174
+ "failing-action": mockHandler,
175
+ },
176
+ });
177
+
178
+ const sentMessages: any[] = [];
179
+ const mockConn = {
180
+ peer: "remote-client-peer",
181
+ send: (data: any) => sentMessages.push(data),
182
+ };
183
+
184
+ await (peer as any)._handleData("remote-client-peer", mockConn, {
185
+ id: "req-fail",
186
+ type: "command:execute",
187
+ payload: {
188
+ commandId: "cmd-fail",
189
+ action: "failing-action",
190
+ args: {},
191
+ },
192
+ });
193
+
194
+ expect(sentMessages).toHaveLength(1);
195
+ expect(sentMessages[0].payload.success).toBe(false);
196
+ expect(sentMessages[0].payload.error).toBe("Action failure");
197
+ });
198
+
60
199
  it("handles unknown action error", async () => {
61
200
  const peer = new ServerPeer({
62
201
  peerId: "test-server-peer",
@@ -83,4 +222,99 @@ describe("ServerPeer", () => {
83
222
  expect(sentMessages[0].payload.success).toBe(false);
84
223
  expect(sentMessages[0].payload.error).toContain("Unknown action");
85
224
  });
225
+
226
+ it("ignores invalid or non-object rawData", async () => {
227
+ const peer = new ServerPeer();
228
+ const mockConn = { send: jest.fn() };
229
+
230
+ await (peer as any)._handleData("remote", mockConn, "invalid-json{");
231
+ await (peer as any)._handleData("remote", mockConn, null);
232
+ await (peer as any)._handleData("remote", mockConn, 123);
233
+ expect(mockConn.send).not.toHaveBeenCalled();
234
+ });
235
+
236
+ it("sends command to connected peer and resolves on result", async () => {
237
+ const peer = new ServerPeer({ peerId: "server-sender" });
238
+ const startPromise = peer.start();
239
+ await new Promise((r) => setTimeout(r, 10));
240
+ mockPeerInstance!.emit("open", "server-sender");
241
+ await startPromise;
242
+
243
+ const mockConn = new MockConn("target-peer");
244
+ mockConn.open = true;
245
+ (peer as any)._connections.set("target-peer", mockConn);
246
+
247
+ const sendPromise = peer.sendCommand("target-peer", "ping", { val: 1 });
248
+ expect(mockConn.sent).toHaveLength(1);
249
+ const sentMsg = mockConn.sent[0];
250
+ const cmdId = sentMsg.payload.commandId;
251
+
252
+ // Simulate incoming command:result from remote peer
253
+ await (peer as any)._handleData("target-peer", mockConn, {
254
+ type: "command:result",
255
+ payload: {
256
+ commandId: cmdId,
257
+ success: true,
258
+ data: "pong",
259
+ },
260
+ });
261
+
262
+ const result = await sendPromise;
263
+ expect(result.success).toBe(true);
264
+ expect(result.data).toBe("pong");
265
+ });
266
+
267
+ it("throws when sendCommand is called before peer is started", async () => {
268
+ const peer = new ServerPeer();
269
+ await expect(peer.sendCommand("target", "action")).rejects.toThrow(
270
+ "Server peer is not running",
271
+ );
272
+ });
273
+
274
+ it("connects to remote peer if not already connected when sending command", async () => {
275
+ const peer = new ServerPeer({ peerId: "server-sender" });
276
+ const startPromise = peer.start();
277
+ await new Promise((r) => setTimeout(r, 10));
278
+ mockPeerInstance!.emit("open", "server-sender");
279
+ await startPromise;
280
+
281
+ const sendPromise = peer.sendCommand("new-remote", "fetch-data");
282
+ const createdConn = mockPeerInstance!.connections[0];
283
+ expect(createdConn).toBeDefined();
284
+
285
+ createdConn.open = true;
286
+ createdConn.emit("open");
287
+ await new Promise((r) => setTimeout(r, 10));
288
+
289
+ const sentMsg = createdConn.sent[0];
290
+ await (peer as any)._handleData("new-remote", createdConn, {
291
+ type: "command:result",
292
+ payload: {
293
+ commandId: sentMsg.payload.commandId,
294
+ success: true,
295
+ },
296
+ });
297
+
298
+ const res = await sendPromise;
299
+ expect(res.success).toBe(true);
300
+ });
301
+
302
+ it("closes peer and terminates pending commands and connections", async () => {
303
+ const peer = new ServerPeer({ peerId: "server-close" });
304
+ const startPromise = peer.start();
305
+ await new Promise((r) => setTimeout(r, 10));
306
+ mockPeerInstance!.emit("open", "server-close");
307
+ await startPromise;
308
+
309
+ const mockConn = new MockConn("peer-1");
310
+ mockConn.open = true;
311
+ (peer as any)._connections.set("peer-1", mockConn);
312
+
313
+ const pendingPromise = peer.sendCommand("peer-1", "long-job", {}, 10_000);
314
+ peer.close();
315
+
316
+ await expect(pendingPromise).rejects.toThrow("Server peer closed");
317
+ expect(peer.isOpen).toBe(false);
318
+ expect(peer.getConnectedPeers()).toHaveLength(0);
319
+ });
86
320
  });