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.
- package/.well-known/mcp/server-card.json +1 -1
- package/.well-known/mcp.json +1 -1
- package/README.md +9 -9
- package/dist/cli/build/build.js +1 -1
- package/dist/cli/{chunk-DYq-orHK.js → chunk-BgTv9w0K.js} +1 -1
- package/dist/cli/{chunk-C6jii2Fl.js → chunk-d5hIhj8t.js} +1 -1
- package/dist/cli/cli.js +1 -1
- package/dist/lib/components/index.js +1 -1
- package/dist/lib/components/shadow-claw-pages.js +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/{shadow-claw-pages-kA2qa-4B.js → shadow-claw-pages-CjHS50HN.js} +2 -2
- package/dist/lib/{shadow-claw-pages-kA2qa-4B.js.map → shadow-claw-pages-CjHS50HN.js.map} +1 -1
- package/dist/public/.well-known/mcp/server-card.json +1 -1
- package/dist/public/.well-known/mcp.json +1 -1
- package/dist/public/AGENTS.md +1 -1
- package/dist/public/README.md +9 -9
- package/dist/public/docs/README.md +1 -1
- package/dist/public/docs/architecture/orchestrator.md +2 -2
- package/dist/public/docs/architecture/overview.md +3 -3
- package/dist/public/docs/architecture/worker-protocol.md +29 -29
- package/dist/public/docs/example/article/index.html +1 -1
- package/dist/public/docs/example/article.html +1 -1
- package/dist/public/docs/publishing/index.html +1 -1
- package/dist/public/docs/publishing.html +1 -1
- package/dist/public/docs/skill-creator/index.html +1 -1
- package/dist/public/docs/skill-creator.html +1 -1
- package/dist/public/docs/subsystems/local-models.md +1 -1
- package/dist/public/docs/subsystems/providers.md +11 -4
- package/dist/public/e2e/README.md +2 -2
- package/dist/public/index.html +1 -1
- package/dist/public/index.js +1 -1
- 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/main/memory.html +1 -1
- package/dist/public/main.html +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-Ba2ZFE9m.js → shadow-claw-T9oPpqxT.js} +1 -1
- package/dist/public/{shadow-claw-pages-Cy6T0G9y.js → shadow-claw-pages-IW_LNEL_.js} +1 -1
- package/package.json +1 -1
- package/src/cli/build/build.test.ts +95 -0
- package/src/cli/build/build.ts +5 -1
- package/src/cli/commands/agent.test.ts +134 -0
- package/src/cli/commands/backup.test.ts +85 -0
- package/src/cli/commands/clients.test.ts +32 -0
- package/src/cli/commands/mcp.test.ts +260 -1
- package/src/cli/commands/send-file.test.ts +73 -0
- package/src/cli/commands/send.test.ts +58 -0
- package/src/cli/commands/tasks.test.ts +78 -0
- package/src/cli/commands/webrtc-listen.test.ts +74 -0
- package/src/cli/tools/assert-version-bump.test.ts +30 -1
- package/src/cli/tools/file-search-replace.test.ts +33 -1
- package/src/cli/tools/patch-service-worker-trusted-types.test.ts +24 -1
- package/src/cli/tools/touch-nojekyll.test.ts +12 -0
- package/src/cli/tools/version.test.ts +109 -5
- package/src/cli/utils/agent-core.test.ts +40 -0
- package/src/cli/utils/control-client.test.ts +239 -2
- package/src/cli/utils/load-workspace-config.test.ts +63 -0
- package/src/cli/utils/local-models.test.ts +98 -0
- package/src/cli/utils/progress-bar.test.ts +41 -2
- package/src/cli/utils/resolve-cache-dir.test.ts +61 -0
- package/src/cli/utils/resolve-project-root.test.ts +17 -0
- package/src/cli/utils/stdin.test.ts +38 -0
- package/src/cli/utils/stdio.test.ts +51 -1
- package/src/cli/utils/suppress-warnings.test.ts +9 -0
- package/src/cli/utils/webrtc-control-client.test.ts +569 -0
- package/src/common/utils/frontmatter.test.ts +117 -0
- package/src/components/common/help/providers.test.ts +67 -0
- package/src/components/common/shadow-claw-provider-model-picker/shadow-claw-provider-model-picker.test.ts +121 -0
- package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +131 -0
- package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +130 -0
- package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +12 -0
- package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +328 -0
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +530 -8
- package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +459 -0
- package/src/components/settings/shadow-claw-networking/shadow-claw-networking.test.ts +88 -0
- package/src/components/settings/shadow-claw-notifications/shadow-claw-notifications.test.ts +92 -0
- package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +127 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +173 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +62 -0
- package/src/components/settings/shadow-claw-webvm/shadow-claw-webvm.test.ts +104 -0
- package/src/components/shadow-claw/shadow-claw.test.ts +112 -0
- package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +109 -0
- package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +361 -0
- package/src/components/shadow-claw-dialog/shadow-claw-dialog.test.ts +76 -0
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +307 -0
- package/src/components/shadow-claw-files/shadow-claw-files.test.ts +398 -1
- package/src/components/shadow-claw-pages/shadow-claw-pages.test.ts +85 -0
- package/src/components/shadow-claw-pages/shadow-claw-pages.ts +2 -1
- package/src/components/shadow-claw-pdf-viewer/shadow-claw-pdf-viewer.test.ts +229 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +233 -0
- package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +223 -1
- package/src/components/shadow-claw-toast/shadow-claw-toast.test.ts +79 -0
- package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +229 -1
- package/src/server/logger.test.ts +57 -0
- package/src/server/peer.test.ts +47 -0
- package/src/server/routes/gemini.test.ts +244 -0
- package/src/server/server-peer.test.ts +245 -11
- package/src/server/services/llamafile-manager.test.ts +668 -0
- package/src/share-target/pending-shares.test.ts +90 -0
- package/src/shell/fs.test.ts +125 -0
- package/src/shell/shell.test.ts +8 -0
- package/src/subsystems/git/credentials.test.ts +260 -0
- package/src/subsystems/git/git.test.ts +64 -0
- package/src/subsystems/providers/executeNativeAiTask.test.ts +78 -0
- package/src/subsystems/providers/utils/parseLocalModelToolCall.test.ts +57 -0
- package/src/worker/tools/builtin-ai/builtin-ai.test.ts +88 -0
- package/src/worker/tools/email/email.test.ts +358 -0
- package/src/worker/tools/email/utils/resolveIntegrationEmailAuth.test.ts +120 -0
- package/src/worker/tools/email/utils/resolveUniqueAttachmentPath.test.ts +80 -0
- package/src/worker/tools/fetch-file/fetch-file.test.ts +111 -0
- 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 {
|
|
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
|
|
40
|
-
await (peer as any)._handleData(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
});
|