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.
- package/.well-known/mcp/server-card.json +2 -2
- package/.well-known/mcp/servers.json +1 -1
- package/.well-known/mcp.json +2 -2
- package/dist/cli/cli.js +2 -2
- package/dist/lib/components/index.js +2 -2
- package/dist/lib/components/shadow-claw-file-viewer/shadow-claw-file-viewer.d.ts +1 -1
- package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
- package/dist/lib/components/shadow-claw.js +1 -1
- package/dist/lib/core/orchestrator/orchestrator-state.d.ts +2 -1
- package/dist/lib/core/orchestrator/orchestrator.d.ts +2 -1
- package/dist/lib/core/orchestrator/utils/operations/task.d.ts +1 -0
- package/dist/lib/index.js +2 -2
- package/dist/lib/{orchestrator-Bq9ft3uG.js → orchestrator-CMN43L0F.js} +9 -13
- package/dist/lib/orchestrator-CMN43L0F.js.map +1 -0
- package/dist/lib/orchestrator-D9f5fDCb.js.map +1 -1
- package/dist/lib/{shadow-claw-MumteAJy.js → shadow-claw-4x3LqlII.js} +2 -2
- package/dist/lib/{shadow-claw-MumteAJy.js.map → shadow-claw-4x3LqlII.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-CMOnFmcP.js → shadow-claw-file-viewer-CuDhkVcL.js} +21 -21
- package/dist/lib/{shadow-claw-file-viewer-CMOnFmcP.js.map → shadow-claw-file-viewer-CuDhkVcL.js.map} +1 -1
- package/dist/lib/syncWebMcpRegistration-Bbph0XZl.js.map +1 -1
- package/dist/public/.well-known/mcp/server-card.json +2 -2
- package/dist/public/.well-known/mcp/servers.json +1 -1
- package/dist/public/.well-known/mcp.json +2 -2
- 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/publishing/index.html +2 -2
- package/dist/public/docs/skill-creator/index.html +2 -2
- package/dist/public/index.html +2 -2
- package/dist/public/index.js +1 -1
- package/dist/public/main/index.html +2 -2
- package/dist/public/main/memory/index.html +2 -2
- package/dist/public/{orchestrator-BLkS_jpm.js → orchestrator-Dc_Q73La.js} +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-DmrxnX3Q.js → shadow-claw-Dabb2AEp.js} +2 -2
- package/dist/public/{shadow-claw-file-viewer-a_uRA30F.js → shadow-claw-file-viewer-CDs1QGXk.js} +1 -1
- package/dist/server.js +1 -1
- package/package.json +1 -1
- package/src/cli/cli.ts +2 -2
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +31 -31
- package/src/core/effect.test.ts +19 -0
- package/src/core/orchestrator/orchestrator-state.ts +2 -1
- package/src/core/orchestrator/orchestrator.test.ts +686 -5
- package/src/core/orchestrator/orchestrator.ts +3 -10
- package/src/core/orchestrator/utils/compactContext.test.ts +423 -121
- package/src/core/orchestrator/utils/configurePeerJs.test.ts +27 -0
- package/src/core/orchestrator/utils/deliverResponse.test.ts +127 -0
- package/src/core/orchestrator/utils/dispatchSubagentInvoke.test.ts +23 -0
- package/src/core/orchestrator/utils/enqueue.test.ts +520 -86
- package/src/core/orchestrator/utils/handleWorkerMessage.test.ts +664 -144
- package/src/core/orchestrator/utils/initTasks.node.test.ts +147 -0
- package/src/core/orchestrator/utils/initTasks.test.ts +926 -191
- package/src/core/orchestrator/utils/initTasks.ts +5 -4
- package/src/core/orchestrator/utils/invokeAgent.test.ts +563 -110
- package/src/core/orchestrator/utils/invokeAgent.ts +8 -10
- package/src/core/orchestrator/utils/loadChannelConfigurations.test.ts +50 -0
- package/src/core/orchestrator/utils/operations/channel.test.ts +377 -170
- package/src/core/orchestrator/utils/operations/provider.test.ts +663 -85
- package/src/core/orchestrator/utils/operations/room.test.ts +74 -11
- package/src/core/orchestrator/utils/operations/task.test.ts +452 -56
- package/src/core/orchestrator/utils/operations/task.ts +1 -1
- package/src/core/orchestrator/utils/operations/vm.test.ts +66 -7
- package/src/core/orchestrator/utils/parseDirectToolCommand.test.ts +6 -0
- package/src/core/orchestrator/utils/settings.test.ts +248 -81
- package/src/core/orchestrator/utils/setupPushTaskListener.test.ts +58 -0
- package/src/core/orchestrator/utils/syncWebMcpRegistration.test.ts +341 -65
- package/src/core/orchestrator/utils/syncWebMcpRegistration.ts +4 -2
- package/src/server/routes/mcp.ts +2 -2
- package/src/stores/file-viewer.test.ts +11 -0
- package/src/stores/theme.test.ts +41 -2
- package/src/stores/toast.test.ts +25 -0
- package/src/worker/tools/bash/native-bash-executor.test.ts +31 -1
- package/src/worker/tools/git/utils/extractConflictPaths.test.ts +24 -0
- package/src/worker/tools/git/utils/parseConflictRegions.test.ts +46 -0
- package/src/worker/tools/git/utils/resolveCorsProxy.test.ts +26 -5
- package/src/worker/tools/git/utils/truncateSnippet.test.ts +16 -0
- package/src/worker/tools/node-transformers-executor.test.ts +105 -6
- package/src/worker/tools/remote-mcp/remote-mcp.test.ts +172 -47
- package/src/worker/tools/remote-mcp/utils/formatListToolsOutput.test.ts +19 -0
- package/src/worker/tools/remote-mcp/utils/isMcpReauthError.test.ts +45 -0
- package/src/worker/tools/remote-mcp/utils/resolveMcpReauth.test.ts +48 -0
- package/src/worker/tools/rooms/list-room-members.test.ts +24 -10
- package/src/worker/tools/tasks/list-tasks.test.ts +39 -8
- package/src/worker/tools/tasks/update-task.test.ts +78 -14
- package/src/worker/tools/ui/javascript.test.ts +70 -17
- package/src/worker/tools/ui/list-tool-profiles.test.ts +31 -9
- 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:
|
|
19
|
+
post: mockPost,
|
|
5
20
|
}));
|
|
6
21
|
|
|
7
22
|
jest.unstable_mockModule("./tasks-utils.js", () => ({
|
|
8
|
-
getGroupTasks:
|
|
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
|
-
|
|
34
|
+
mockGetGroupTasks.mockResolvedValue([{ id: "task-2" }]);
|
|
22
35
|
const result = await executeUpdateTask(
|
|
23
|
-
{} as
|
|
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(
|
|
41
|
+
expect(mockPost).not.toHaveBeenCalled();
|
|
29
42
|
});
|
|
30
43
|
|
|
31
|
-
it("updates
|
|
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
|
-
|
|
54
|
+
mockGetGroupTasks.mockResolvedValue([mockTask]);
|
|
42
55
|
|
|
43
56
|
const result = await executeUpdateTask(
|
|
44
|
-
{} as
|
|
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(
|
|
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:
|
|
24
|
+
sandboxedEval: mockSandboxedEval,
|
|
5
25
|
}));
|
|
6
26
|
|
|
7
27
|
jest.unstable_mockModule("../utils/getAllowFullInternetAccess.js", () => ({
|
|
8
|
-
getAllowFullInternetAccess:
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
(
|
|
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
|
});
|