shadow-claw 1.40.0 → 1.40.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 (86) hide show
  1. package/.well-known/mcp/server-card.json +2 -2
  2. package/.well-known/mcp/servers.json +1 -1
  3. package/.well-known/mcp.json +2 -2
  4. package/dist/cli/cli.js +2 -2
  5. package/dist/lib/components/index.js +2 -2
  6. package/dist/lib/components/shadow-claw-file-viewer/shadow-claw-file-viewer.d.ts +1 -1
  7. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  8. package/dist/lib/components/shadow-claw.js +1 -1
  9. package/dist/lib/core/orchestrator/orchestrator-state.d.ts +2 -1
  10. package/dist/lib/core/orchestrator/orchestrator.d.ts +2 -1
  11. package/dist/lib/core/orchestrator/utils/operations/task.d.ts +1 -0
  12. package/dist/lib/index.js +2 -2
  13. package/dist/lib/{orchestrator-Bq9ft3uG.js → orchestrator-CMN43L0F.js} +9 -13
  14. package/dist/lib/orchestrator-CMN43L0F.js.map +1 -0
  15. package/dist/lib/orchestrator-D9f5fDCb.js.map +1 -1
  16. package/dist/lib/{shadow-claw-MumteAJy.js → shadow-claw-4x3LqlII.js} +2 -2
  17. package/dist/lib/{shadow-claw-MumteAJy.js.map → shadow-claw-4x3LqlII.js.map} +1 -1
  18. package/dist/lib/{shadow-claw-file-viewer-CMOnFmcP.js → shadow-claw-file-viewer-CuDhkVcL.js} +21 -21
  19. package/dist/lib/{shadow-claw-file-viewer-CMOnFmcP.js.map → shadow-claw-file-viewer-CuDhkVcL.js.map} +1 -1
  20. package/dist/lib/syncWebMcpRegistration-Bbph0XZl.js.map +1 -1
  21. package/dist/public/.well-known/mcp/server-card.json +2 -2
  22. package/dist/public/.well-known/mcp/servers.json +1 -1
  23. package/dist/public/.well-known/mcp.json +2 -2
  24. package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
  25. package/dist/public/docs/example/article/index.html +2 -2
  26. package/dist/public/docs/publishing/index.html +2 -2
  27. package/dist/public/docs/skill-creator/index.html +2 -2
  28. package/dist/public/index.html +2 -2
  29. package/dist/public/index.js +1 -1
  30. package/dist/public/main/index.html +2 -2
  31. package/dist/public/main/memory/index.html +2 -2
  32. package/dist/public/{orchestrator-BLkS_jpm.js → orchestrator-Dc_Q73La.js} +1 -1
  33. package/dist/public/service-worker.js +1 -1
  34. package/dist/public/{shadow-claw-DmrxnX3Q.js → shadow-claw-Dabb2AEp.js} +2 -2
  35. package/dist/public/{shadow-claw-file-viewer-a_uRA30F.js → shadow-claw-file-viewer-CDs1QGXk.js} +1 -1
  36. package/dist/server.js +1 -1
  37. package/package.json +1 -1
  38. package/src/cli/cli.ts +2 -2
  39. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +31 -31
  40. package/src/core/effect.test.ts +19 -0
  41. package/src/core/orchestrator/orchestrator-state.ts +2 -1
  42. package/src/core/orchestrator/orchestrator.test.ts +686 -5
  43. package/src/core/orchestrator/orchestrator.ts +3 -10
  44. package/src/core/orchestrator/utils/compactContext.test.ts +423 -121
  45. package/src/core/orchestrator/utils/configurePeerJs.test.ts +27 -0
  46. package/src/core/orchestrator/utils/deliverResponse.test.ts +127 -0
  47. package/src/core/orchestrator/utils/dispatchSubagentInvoke.test.ts +23 -0
  48. package/src/core/orchestrator/utils/enqueue.test.ts +520 -86
  49. package/src/core/orchestrator/utils/handleWorkerMessage.test.ts +664 -144
  50. package/src/core/orchestrator/utils/initTasks.node.test.ts +147 -0
  51. package/src/core/orchestrator/utils/initTasks.test.ts +926 -191
  52. package/src/core/orchestrator/utils/initTasks.ts +5 -4
  53. package/src/core/orchestrator/utils/invokeAgent.test.ts +563 -110
  54. package/src/core/orchestrator/utils/invokeAgent.ts +8 -10
  55. package/src/core/orchestrator/utils/loadChannelConfigurations.test.ts +50 -0
  56. package/src/core/orchestrator/utils/operations/channel.test.ts +377 -170
  57. package/src/core/orchestrator/utils/operations/provider.test.ts +663 -85
  58. package/src/core/orchestrator/utils/operations/room.test.ts +74 -11
  59. package/src/core/orchestrator/utils/operations/task.test.ts +452 -56
  60. package/src/core/orchestrator/utils/operations/task.ts +1 -1
  61. package/src/core/orchestrator/utils/operations/vm.test.ts +66 -7
  62. package/src/core/orchestrator/utils/parseDirectToolCommand.test.ts +6 -0
  63. package/src/core/orchestrator/utils/settings.test.ts +248 -81
  64. package/src/core/orchestrator/utils/setupPushTaskListener.test.ts +58 -0
  65. package/src/core/orchestrator/utils/syncWebMcpRegistration.test.ts +341 -65
  66. package/src/core/orchestrator/utils/syncWebMcpRegistration.ts +4 -2
  67. package/src/server/routes/mcp.ts +2 -2
  68. package/src/stores/file-viewer.test.ts +11 -0
  69. package/src/stores/theme.test.ts +41 -2
  70. package/src/stores/toast.test.ts +25 -0
  71. package/src/worker/tools/bash/native-bash-executor.test.ts +31 -1
  72. package/src/worker/tools/git/utils/extractConflictPaths.test.ts +24 -0
  73. package/src/worker/tools/git/utils/parseConflictRegions.test.ts +46 -0
  74. package/src/worker/tools/git/utils/resolveCorsProxy.test.ts +26 -5
  75. package/src/worker/tools/git/utils/truncateSnippet.test.ts +16 -0
  76. package/src/worker/tools/node-transformers-executor.test.ts +105 -6
  77. package/src/worker/tools/remote-mcp/remote-mcp.test.ts +172 -47
  78. package/src/worker/tools/remote-mcp/utils/formatListToolsOutput.test.ts +19 -0
  79. package/src/worker/tools/remote-mcp/utils/isMcpReauthError.test.ts +45 -0
  80. package/src/worker/tools/remote-mcp/utils/resolveMcpReauth.test.ts +48 -0
  81. package/src/worker/tools/rooms/list-room-members.test.ts +24 -10
  82. package/src/worker/tools/tasks/list-tasks.test.ts +39 -8
  83. package/src/worker/tools/tasks/update-task.test.ts +78 -14
  84. package/src/worker/tools/ui/javascript.test.ts +70 -17
  85. package/src/worker/tools/ui/list-tool-profiles.test.ts +31 -9
  86. package/dist/lib/orchestrator-Bq9ft3uG.js.map +0 -1
@@ -1,15 +1,28 @@
1
- import { jest } from "@jest/globals";
1
+ import { beforeEach, describe, expect, it, jest } from "@jest/globals";
2
+ import type { ShadowClawDatabase } from "../../../db/types.js";
3
+
4
+ interface TaskRecord {
5
+ id: string;
6
+ schedule?: string;
7
+ type?: string;
8
+ prompt?: string;
9
+ tools?: string[];
10
+ enabled?: boolean;
11
+ name?: string;
12
+ }
13
+
14
+ const mockPost =
15
+ jest.fn<(msg: { type: string; payload: { task: TaskRecord } }) => void>();
16
+ const mockGetGroupTasks = jest.fn<() => Promise<TaskRecord[]>>();
2
17
 
3
18
  jest.unstable_mockModule("../../utils/post.js", () => ({
4
- post: jest.fn(),
19
+ post: mockPost,
5
20
  }));
6
21
 
7
22
  jest.unstable_mockModule("./tasks-utils.js", () => ({
8
- getGroupTasks: jest.fn(),
23
+ getGroupTasks: mockGetGroupTasks,
9
24
  }));
10
25
 
11
- const { post } = await import("../../utils/post.js");
12
- const { getGroupTasks } = await import("./tasks-utils.js");
13
26
  const { executeUpdateTask } = await import("./update-task.js");
14
27
 
15
28
  describe("executeUpdateTask", () => {
@@ -18,18 +31,18 @@ describe("executeUpdateTask", () => {
18
31
  });
19
32
 
20
33
  it("returns error if task not found", async () => {
21
- (getGroupTasks as jest.Mock<any>).mockResolvedValue([{ id: "task-2" }]);
34
+ mockGetGroupTasks.mockResolvedValue([{ id: "task-2" }]);
22
35
  const result = await executeUpdateTask(
23
- {} as any,
36
+ {} as ShadowClawDatabase,
24
37
  { id: "task-1" },
25
38
  "group-1",
26
39
  );
27
40
  expect(result).toBe("Error: Task with ID task-1 not found.");
28
- expect(post).not.toHaveBeenCalled();
41
+ expect(mockPost).not.toHaveBeenCalled();
29
42
  });
30
43
 
31
- it("updates task successfully", async () => {
32
- const mockTask = {
44
+ it("updates all provided fields successfully", async () => {
45
+ const mockTask: TaskRecord = {
33
46
  id: "task-1",
34
47
  schedule: "* * * * *",
35
48
  type: "prompt",
@@ -38,10 +51,10 @@ describe("executeUpdateTask", () => {
38
51
  enabled: false,
39
52
  name: "old name",
40
53
  };
41
- (getGroupTasks as jest.Mock<any>).mockResolvedValue([mockTask]);
54
+ mockGetGroupTasks.mockResolvedValue([mockTask]);
42
55
 
43
56
  const result = await executeUpdateTask(
44
- {} as any,
57
+ {} as ShadowClawDatabase,
45
58
  {
46
59
  id: "task-1",
47
60
  schedule: "0 0 * * *",
@@ -49,7 +62,7 @@ describe("executeUpdateTask", () => {
49
62
  prompt: "new prompt",
50
63
  tools: ["tool1"],
51
64
  enabled: true,
52
- name: "new name",
65
+ name: " new name ",
53
66
  },
54
67
  "group-1",
55
68
  );
@@ -61,10 +74,61 @@ describe("executeUpdateTask", () => {
61
74
  expect(mockTask.enabled).toBe(true);
62
75
  expect(mockTask.name).toBe("new name");
63
76
 
64
- expect(post).toHaveBeenCalledWith({
77
+ expect(mockPost).toHaveBeenCalledWith({
65
78
  type: "update-task",
66
79
  payload: { task: mockTask },
67
80
  });
68
81
  expect(result).toBe("Task task-1 updated successfully.");
69
82
  });
83
+
84
+ it("preserves unchanged fields when optional fields are omitted or type is prompt/invalid", async () => {
85
+ const mockTask: TaskRecord = {
86
+ id: "task-1",
87
+ schedule: "original-schedule",
88
+ type: "tools",
89
+ prompt: "original-prompt",
90
+ tools: ["t0"],
91
+ enabled: true,
92
+ name: "original-name",
93
+ };
94
+ mockGetGroupTasks.mockResolvedValue([mockTask]);
95
+
96
+ // Update with type "prompt", tools not an array, non-string name
97
+ const result = await executeUpdateTask(
98
+ {} as ShadowClawDatabase,
99
+ {
100
+ id: "task-1",
101
+ type: "prompt",
102
+ name: null,
103
+ },
104
+ "group-1",
105
+ );
106
+
107
+ expect(mockTask.schedule).toBe("original-schedule");
108
+ expect(mockTask.type).toBe("prompt");
109
+ expect(mockTask.prompt).toBe("original-prompt");
110
+ expect(mockTask.tools).toEqual(["t0"]);
111
+ expect(mockTask.enabled).toBe(true);
112
+ expect(mockTask.name).toBeUndefined();
113
+ expect(result).toBe("Task task-1 updated successfully.");
114
+ });
115
+
116
+ it("ignores unknown type values", async () => {
117
+ const mockTask: TaskRecord = {
118
+ id: "task-1",
119
+ type: "tools",
120
+ };
121
+ mockGetGroupTasks.mockResolvedValue([mockTask]);
122
+
123
+ await executeUpdateTask(
124
+ {} as ShadowClawDatabase,
125
+ {
126
+ id: "task-1",
127
+ type: "unknown-type",
128
+ },
129
+ "group-1",
130
+ );
131
+
132
+ expect(mockTask.type).toBe("tools");
133
+ });
70
134
  });
@@ -1,32 +1,51 @@
1
- import { jest } from "@jest/globals";
1
+ import { beforeEach, describe, expect, it, jest } from "@jest/globals";
2
+ import type { ShadowClawDatabase } from "../../../db/types.js";
3
+
4
+ interface SandboxedEvalResult {
5
+ ok: boolean;
6
+ value?: unknown;
7
+ error?: string;
8
+ }
9
+
10
+ const mockSandboxedEval =
11
+ jest.fn<
12
+ (
13
+ code?: string,
14
+ context?: unknown,
15
+ allowInternet?: boolean,
16
+ data?: unknown,
17
+ ) => Promise<SandboxedEvalResult | null>
18
+ >();
19
+ const mockGetAllowFullInternetAccess = jest
20
+ .fn<() => Promise<boolean>>()
21
+ .mockResolvedValue(false);
2
22
 
3
23
  jest.unstable_mockModule("../../utils/sandboxedEval.js", () => ({
4
- sandboxedEval: jest.fn(),
24
+ sandboxedEval: mockSandboxedEval,
5
25
  }));
6
26
 
7
27
  jest.unstable_mockModule("../utils/getAllowFullInternetAccess.js", () => ({
8
- getAllowFullInternetAccess: jest.fn<any>().mockResolvedValue(false),
28
+ getAllowFullInternetAccess: mockGetAllowFullInternetAccess,
9
29
  }));
10
30
 
11
31
  const { executeJavascript } = await import("./javascript.js");
12
- const { sandboxedEval } = await import("../../utils/sandboxedEval.js");
13
32
 
14
33
  describe("executeJavascript", () => {
15
- const mockDb: any = {};
34
+ const mockDb = {} as ShadowClawDatabase;
16
35
 
17
36
  beforeEach(() => {
18
37
  jest.clearAllMocks();
19
38
  });
20
39
 
21
40
  it("evaluates simple expression returning number", async () => {
22
- (sandboxedEval as jest.Mock<any>).mockResolvedValue({
41
+ mockSandboxedEval.mockResolvedValue({
23
42
  ok: true,
24
43
  value: 42,
25
44
  });
26
45
 
27
46
  const result = await executeJavascript(mockDb, { code: "1 + 41" });
28
47
  expect(result).toBe("42");
29
- expect(sandboxedEval).toHaveBeenCalledWith(
48
+ expect(mockSandboxedEval).toHaveBeenCalledWith(
30
49
  "return (1 + 41);",
31
50
  undefined,
32
51
  false,
@@ -35,7 +54,7 @@ describe("executeJavascript", () => {
35
54
  });
36
55
 
37
56
  it("handles explicit return statements", async () => {
38
- (sandboxedEval as jest.Mock<any>).mockResolvedValue({
57
+ mockSandboxedEval.mockResolvedValue({
39
58
  ok: true,
40
59
  value: "hello",
41
60
  });
@@ -44,7 +63,7 @@ describe("executeJavascript", () => {
44
63
  code: "const x = 'hello'; return x;",
45
64
  });
46
65
  expect(result).toBe("hello");
47
- expect(sandboxedEval).toHaveBeenCalledWith(
66
+ expect(mockSandboxedEval).toHaveBeenCalledWith(
48
67
  "const x = 'hello'; return x;",
49
68
  undefined,
50
69
  false,
@@ -53,7 +72,7 @@ describe("executeJavascript", () => {
53
72
  });
54
73
 
55
74
  it("handles object return values by formatting as JSON", async () => {
56
- (sandboxedEval as jest.Mock<any>).mockResolvedValue({
75
+ mockSandboxedEval.mockResolvedValue({
57
76
  ok: true,
58
77
  value: { name: "Alice", age: 30 },
59
78
  });
@@ -64,8 +83,21 @@ describe("executeJavascript", () => {
64
83
  expect(JSON.parse(result)).toEqual({ name: "Alice", age: 30 });
65
84
  });
66
85
 
86
+ it("falls back to String representation when object serialization throws", async () => {
87
+ const circular: Record<string, unknown> = {};
88
+ circular.self = circular;
89
+
90
+ mockSandboxedEval.mockResolvedValue({
91
+ ok: true,
92
+ value: circular,
93
+ });
94
+
95
+ const result = await executeJavascript(mockDb, { code: "circular" });
96
+ expect(result).toBe("[object Object]");
97
+ });
98
+
67
99
  it("returns null representation for null values", async () => {
68
- (sandboxedEval as jest.Mock<any>).mockResolvedValue({
100
+ mockSandboxedEval.mockResolvedValue({
69
101
  ok: true,
70
102
  value: null,
71
103
  });
@@ -74,8 +106,8 @@ describe("executeJavascript", () => {
74
106
  expect(result).toBe("null");
75
107
  });
76
108
 
77
- it("provides hint when evaluation returns undefined", async () => {
78
- (sandboxedEval as jest.Mock<any>).mockResolvedValue({
109
+ it("provides hint when evaluation returns undefined or __UNDEFINED__", async () => {
110
+ mockSandboxedEval.mockResolvedValue({
79
111
  ok: true,
80
112
  value: undefined,
81
113
  });
@@ -83,20 +115,41 @@ describe("executeJavascript", () => {
83
115
  const result = await executeJavascript(mockDb, { code: "const a = 1;" });
84
116
  expect(result).toContain("(no return value)");
85
117
  expect(result).toContain("Hint: Your code did not return a value");
118
+
119
+ mockSandboxedEval.mockResolvedValue({
120
+ ok: true,
121
+ value: "__UNDEFINED__",
122
+ });
123
+
124
+ const resultUndefStr = await executeJavascript(mockDb, {
125
+ code: "undefined",
126
+ });
127
+ expect(resultUndefStr).toContain("(no return value)");
86
128
  });
87
129
 
88
- it("reports JavaScript errors", async () => {
89
- (sandboxedEval as jest.Mock<any>).mockResolvedValue({
130
+ it("reports JavaScript errors and falls back to Unknown error when error is missing", async () => {
131
+ mockSandboxedEval.mockResolvedValue({
90
132
  ok: false,
91
133
  error: "ReferenceError: foo is not defined",
92
134
  });
93
135
 
94
136
  const result = await executeJavascript(mockDb, { code: "foo" });
95
137
  expect(result).toBe("JavaScript error: ReferenceError: foo is not defined");
138
+
139
+ mockSandboxedEval.mockResolvedValue({
140
+ ok: false,
141
+ });
142
+
143
+ const resultUnknown = await executeJavascript(mockDb, { code: "fail" });
144
+ expect(resultUnknown).toBe("JavaScript error: Unknown error");
145
+
146
+ mockSandboxedEval.mockResolvedValue(null);
147
+ const resultNull = await executeJavascript(mockDb, { code: "fail" });
148
+ expect(resultNull).toBe("JavaScript error: Unknown error");
96
149
  });
97
150
 
98
151
  it("falls back to raw code when wrapping in return produces syntax error", async () => {
99
- (sandboxedEval as jest.Mock<any>)
152
+ mockSandboxedEval
100
153
  .mockResolvedValueOnce({
101
154
  ok: false,
102
155
  error: "SyntaxError: Unexpected token 'for'",
@@ -110,7 +163,7 @@ describe("executeJavascript", () => {
110
163
  code: "for (let i = 0; i < 5; i++) { /* do work */ }",
111
164
  });
112
165
 
113
- expect(sandboxedEval).toHaveBeenCalledTimes(2);
166
+ expect(mockSandboxedEval).toHaveBeenCalledTimes(2);
114
167
  expect(result).toContain("(no return value)");
115
168
  });
116
169
  });
@@ -1,28 +1,30 @@
1
- import { jest } from "@jest/globals";
1
+ import { beforeEach, describe, expect, it, jest } from "@jest/globals";
2
+ import type { ShadowClawDatabase } from "../../../db/types.js";
3
+
4
+ const mockGetConfig = jest.fn<() => Promise<unknown>>();
2
5
 
3
6
  jest.unstable_mockModule("../../../db/getConfig.js", () => ({
4
- getConfig: jest.fn(),
7
+ getConfig: mockGetConfig,
5
8
  }));
6
9
 
7
10
  const { executeListToolProfiles } = await import("./list-tool-profiles.js");
8
- const { getConfig } = await import("../../../db/getConfig.js");
9
11
 
10
12
  describe("executeListToolProfiles", () => {
11
- const mockDb: any = {};
13
+ const mockDb = {} as ShadowClawDatabase;
12
14
 
13
15
  beforeEach(() => {
14
16
  jest.clearAllMocks();
15
17
  });
16
18
 
17
19
  it("lists default profile when no custom profiles in DB", async () => {
18
- (getConfig as jest.Mock<any>).mockResolvedValue(null);
20
+ mockGetConfig.mockResolvedValue(null);
19
21
 
20
22
  const result = await executeListToolProfiles(mockDb);
21
23
  expect(result).toContain("[Profile ID: __builtin_default]");
22
24
  expect(result).toContain("Tools:");
23
25
  });
24
26
 
25
- it("includes custom profiles when stored in DB as JSON string or array", async () => {
27
+ it("includes custom profiles when stored in DB as JSON string", async () => {
26
28
  const customProfiles = [
27
29
  {
28
30
  id: "coding",
@@ -30,13 +32,33 @@ describe("executeListToolProfiles", () => {
30
32
  enabledToolNames: ["bash", "read_file", "write_file"],
31
33
  },
32
34
  ];
33
- (getConfig as jest.Mock<any>).mockResolvedValue(
34
- JSON.stringify(customProfiles),
35
- );
35
+ mockGetConfig.mockResolvedValue(JSON.stringify(customProfiles));
36
36
 
37
37
  const result = await executeListToolProfiles(mockDb);
38
38
  expect(result).toContain("[Profile ID: __builtin_default]");
39
39
  expect(result).toContain("[Profile ID: coding] Coding Profile");
40
40
  expect(result).toContain("Tools: bash, read_file, write_file");
41
41
  });
42
+
43
+ it("handles custom profiles when stored directly as an array in DB", async () => {
44
+ const customProfiles = [
45
+ {
46
+ id: "research",
47
+ name: "Research Profile",
48
+ enabledToolNames: ["web_search"],
49
+ },
50
+ ];
51
+ mockGetConfig.mockResolvedValue(customProfiles);
52
+
53
+ const result = await executeListToolProfiles(mockDb);
54
+ expect(result).toContain("[Profile ID: research] Research Profile");
55
+ expect(result).toContain("Tools: web_search");
56
+ });
57
+
58
+ it("handles corrupted JSON gracefully", async () => {
59
+ mockGetConfig.mockResolvedValue("{ not valid json");
60
+
61
+ const result = await executeListToolProfiles(mockDb);
62
+ expect(result).toContain("[Profile ID: __builtin_default]");
63
+ });
42
64
  });