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,7 +1,13 @@
|
|
|
1
|
-
import { jest } from "@jest/globals";
|
|
1
|
+
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
2
2
|
import { DEFAULT_GROUP_ID } from "../../../../config/config.js";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const mockDefaultSetConfig = jest.fn() as any;
|
|
5
|
+
|
|
6
|
+
jest.unstable_mockModule("../../../../db/setConfig.js", () => ({
|
|
7
|
+
setConfig: mockDefaultSetConfig,
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const {
|
|
5
11
|
closeTerminalSession,
|
|
6
12
|
openTerminalSession,
|
|
7
13
|
sendTerminalInput,
|
|
@@ -11,18 +17,23 @@ import {
|
|
|
11
17
|
setVMBootMode,
|
|
12
18
|
setVMBootHost,
|
|
13
19
|
setVMNetworkRelayURL,
|
|
14
|
-
}
|
|
20
|
+
} = await import("./vm.js");
|
|
15
21
|
|
|
16
22
|
import type { OrchestratorState } from "../../orchestrator-state.js";
|
|
17
23
|
|
|
18
|
-
function makeState() {
|
|
24
|
+
function makeState(withWorker = true) {
|
|
19
25
|
return {
|
|
20
|
-
agentWorker: { postMessage: jest.fn() } as any,
|
|
26
|
+
agentWorker: withWorker ? ({ postMessage: jest.fn() } as any) : undefined,
|
|
21
27
|
vmBootMode: "auto",
|
|
22
28
|
} as unknown as OrchestratorState;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
describe("vm operations", () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
jest.clearAllMocks();
|
|
34
|
+
mockDefaultSetConfig.mockResolvedValue(undefined);
|
|
35
|
+
});
|
|
36
|
+
|
|
26
37
|
it("openTerminalSession sends open message with default and custom groupId", () => {
|
|
27
38
|
const state = makeState();
|
|
28
39
|
openTerminalSession(state, "group1");
|
|
@@ -62,22 +73,34 @@ describe("vm operations", () => {
|
|
|
62
73
|
});
|
|
63
74
|
});
|
|
64
75
|
|
|
65
|
-
it("syncTerminalWorkspace sends sync message", () => {
|
|
76
|
+
it("syncTerminalWorkspace sends sync message with default and custom groupId", () => {
|
|
66
77
|
const state = makeState();
|
|
67
78
|
syncTerminalWorkspace(state, "group-sync");
|
|
68
79
|
expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
|
|
69
80
|
payload: { groupId: "group-sync" },
|
|
70
81
|
type: "vm-workspace-sync",
|
|
71
82
|
});
|
|
83
|
+
|
|
84
|
+
syncTerminalWorkspace(state);
|
|
85
|
+
expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
|
|
86
|
+
payload: { groupId: DEFAULT_GROUP_ID },
|
|
87
|
+
type: "vm-workspace-sync",
|
|
88
|
+
});
|
|
72
89
|
});
|
|
73
90
|
|
|
74
|
-
it("flushTerminalWorkspace sends flush message", () => {
|
|
91
|
+
it("flushTerminalWorkspace sends flush message with default and custom groupId", () => {
|
|
75
92
|
const state = makeState();
|
|
76
93
|
flushTerminalWorkspace(state, "group-flush");
|
|
77
94
|
expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
|
|
78
95
|
payload: { groupId: "group-flush" },
|
|
79
96
|
type: "vm-workspace-flush",
|
|
80
97
|
});
|
|
98
|
+
|
|
99
|
+
flushTerminalWorkspace(state);
|
|
100
|
+
expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
|
|
101
|
+
payload: { groupId: DEFAULT_GROUP_ID },
|
|
102
|
+
type: "vm-workspace-flush",
|
|
103
|
+
});
|
|
81
104
|
});
|
|
82
105
|
|
|
83
106
|
it("answerUserPrompt sends ask-user-response message", () => {
|
|
@@ -110,6 +133,26 @@ describe("vm operations", () => {
|
|
|
110
133
|
);
|
|
111
134
|
expect(state.vmBootMode).toBe("disabled");
|
|
112
135
|
expect(mockSetConfig).toHaveBeenCalledWith({}, "vm_boot_mode", "disabled");
|
|
136
|
+
|
|
137
|
+
// Test other valid modes: ext2, auto, disabled
|
|
138
|
+
await setVMBootMode(state, {} as any, "ext2", mockSetConfig as any);
|
|
139
|
+
expect(state.vmBootMode).toBe("ext2");
|
|
140
|
+
|
|
141
|
+
await setVMBootMode(state, {} as any, "auto", mockSetConfig as any);
|
|
142
|
+
expect(state.vmBootMode).toBe("auto");
|
|
143
|
+
|
|
144
|
+
await setVMBootMode(state, {} as any, "disabled", mockSetConfig as any);
|
|
145
|
+
expect(state.vmBootMode).toBe("disabled");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("setVMBootMode uses default setConfig and handles absent agentWorker", async () => {
|
|
149
|
+
const state = makeState(false);
|
|
150
|
+
await setVMBootMode(state, {} as any, "auto");
|
|
151
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
152
|
+
{},
|
|
153
|
+
"vm_boot_mode",
|
|
154
|
+
"auto",
|
|
155
|
+
);
|
|
113
156
|
});
|
|
114
157
|
|
|
115
158
|
it("setVMBootHost sets boot host, updates config and notifies worker", async () => {
|
|
@@ -133,6 +176,12 @@ describe("vm operations", () => {
|
|
|
133
176
|
});
|
|
134
177
|
});
|
|
135
178
|
|
|
179
|
+
it("setVMBootHost handles non-string host, default setConfig, and absent agentWorker", async () => {
|
|
180
|
+
const state = makeState(false);
|
|
181
|
+
await setVMBootHost(state, {} as any, null as any);
|
|
182
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith({}, "vm_boot_host", "");
|
|
183
|
+
});
|
|
184
|
+
|
|
136
185
|
it("setVMNetworkRelayURL sets relay URL, updates config and notifies worker", async () => {
|
|
137
186
|
const state = makeState();
|
|
138
187
|
const mockSetConfig = jest.fn().mockResolvedValue(undefined as never);
|
|
@@ -153,4 +202,14 @@ describe("vm operations", () => {
|
|
|
153
202
|
type: "set-vm-mode",
|
|
154
203
|
});
|
|
155
204
|
});
|
|
205
|
+
|
|
206
|
+
it("setVMNetworkRelayURL handles non-string relay URL, default setConfig, and absent agentWorker", async () => {
|
|
207
|
+
const state = makeState(false);
|
|
208
|
+
await setVMNetworkRelayURL(state, {} as any, undefined as any);
|
|
209
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
210
|
+
{},
|
|
211
|
+
"vm_network_relay_url",
|
|
212
|
+
"",
|
|
213
|
+
);
|
|
214
|
+
});
|
|
156
215
|
});
|
|
@@ -116,5 +116,11 @@ describe("parseDirectToolCommand", () => {
|
|
|
116
116
|
it("should return null if JSON parses to a primitive", () => {
|
|
117
117
|
const msg = createMsg('/test_tool "hello"');
|
|
118
118
|
expect(parseDirectToolCommand(policy, assistantName, msg)).toBeNull();
|
|
119
|
+
|
|
120
|
+
const msgNumber = createMsg("/test_tool 123");
|
|
121
|
+
expect(parseDirectToolCommand(policy, assistantName, msgNumber)).toBeNull();
|
|
122
|
+
|
|
123
|
+
const msgNull = createMsg("/test_tool null");
|
|
124
|
+
expect(parseDirectToolCommand(policy, assistantName, msgNull)).toBeNull();
|
|
119
125
|
});
|
|
120
126
|
});
|
|
@@ -1,8 +1,27 @@
|
|
|
1
|
-
import { jest } from "@jest/globals";
|
|
1
|
+
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
CONFIG_KEYS,
|
|
4
|
+
DEFAULT_TASK_SERVER_URL,
|
|
5
|
+
} from "../../../config/config.js";
|
|
6
|
+
import type { ShadowClawDatabase } from "../../../db/db.js";
|
|
7
|
+
import type { OrchestratorState } from "../orchestrator-state.js";
|
|
2
8
|
|
|
3
|
-
|
|
9
|
+
const mockDefaultSetConfig =
|
|
10
|
+
jest.fn<
|
|
11
|
+
(_db: ShadowClawDatabase, _key: string, _value: string) => Promise<void>
|
|
12
|
+
>();
|
|
13
|
+
const mockSyncProxyConfigToServiceWorker =
|
|
14
|
+
jest.fn<(_state: OrchestratorState) => void>();
|
|
4
15
|
|
|
5
|
-
|
|
16
|
+
jest.unstable_mockModule("../../../db/setConfig.js", () => ({
|
|
17
|
+
setConfig: mockDefaultSetConfig,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
jest.unstable_mockModule("./syncProxyConfigToServiceWorker.js", () => ({
|
|
21
|
+
syncProxyConfigToServiceWorker: mockSyncProxyConfigToServiceWorker,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const {
|
|
6
25
|
setContextCompressionEnabled,
|
|
7
26
|
setGitProxyUrl,
|
|
8
27
|
setMaxIterations,
|
|
@@ -12,28 +31,26 @@ import {
|
|
|
12
31
|
setRateLimitCallsPerMinute,
|
|
13
32
|
setReasoningEffort,
|
|
14
33
|
setStreamingEnabled,
|
|
34
|
+
setTaskServerEnabled,
|
|
15
35
|
setTaskServerUrl,
|
|
16
36
|
setUseProxy,
|
|
17
37
|
setVMBashFullInternetAccess,
|
|
18
38
|
setVMBashTimeout,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
import type { OrchestratorState } from "../orchestrator-state.js";
|
|
39
|
+
} = await import("./settings.js");
|
|
22
40
|
|
|
23
|
-
|
|
24
|
-
function mockSetConfig() {
|
|
41
|
+
function mockCustomSetConfig() {
|
|
25
42
|
const calls: Array<{ key: string; value: string }> = [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
calls,
|
|
29
|
-
mockFn: jest.fn(async (_db: any, key: string, value: string) => {
|
|
43
|
+
const mockFn = jest.fn(
|
|
44
|
+
async (_db: ShadowClawDatabase, key: string, value: string) => {
|
|
30
45
|
calls.push({ key, value });
|
|
31
|
-
}
|
|
32
|
-
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
return { calls, mockFn };
|
|
33
49
|
}
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
function makeState(
|
|
52
|
+
overrides: Partial<OrchestratorState> = {},
|
|
53
|
+
): OrchestratorState {
|
|
37
54
|
return {
|
|
38
55
|
contextCompressionEnabled: false,
|
|
39
56
|
gitProxyUrl: "/git-proxy",
|
|
@@ -45,7 +62,8 @@ function makeState(overrides: Partial<OrchestratorState> = {}) {
|
|
|
45
62
|
rateLimitCallsPerMinute: 0,
|
|
46
63
|
reasoningEffort: "none",
|
|
47
64
|
streamingEnabled: true,
|
|
48
|
-
|
|
65
|
+
taskServerEnabled: false,
|
|
66
|
+
taskServerUrl: DEFAULT_TASK_SERVER_URL,
|
|
49
67
|
useProxy: false,
|
|
50
68
|
vmBashFullInternetAccess: false,
|
|
51
69
|
...overrides,
|
|
@@ -53,13 +71,19 @@ function makeState(overrides: Partial<OrchestratorState> = {}) {
|
|
|
53
71
|
}
|
|
54
72
|
|
|
55
73
|
describe("settings (functional utilities)", () => {
|
|
74
|
+
const mockDb = {} as ShadowClawDatabase;
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
jest.clearAllMocks();
|
|
78
|
+
mockDefaultSetConfig.mockResolvedValue(undefined);
|
|
79
|
+
});
|
|
80
|
+
|
|
56
81
|
describe("setContextCompressionEnabled", () => {
|
|
57
|
-
it("normalizes truthy values and persists to db", async () => {
|
|
82
|
+
it("normalizes truthy values and persists to db with custom setConfig", async () => {
|
|
58
83
|
const state = makeState();
|
|
59
|
-
const { calls, mockFn } =
|
|
60
|
-
const db = {} as any;
|
|
84
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
61
85
|
|
|
62
|
-
await setContextCompressionEnabled(state,
|
|
86
|
+
await setContextCompressionEnabled(state, mockDb, true, mockFn);
|
|
63
87
|
|
|
64
88
|
expect(state.contextCompressionEnabled).toBe(true);
|
|
65
89
|
expect(calls).toEqual([
|
|
@@ -67,44 +91,51 @@ describe("settings (functional utilities)", () => {
|
|
|
67
91
|
]);
|
|
68
92
|
});
|
|
69
93
|
|
|
70
|
-
it("normalizes falsy values", async () => {
|
|
94
|
+
it("normalizes falsy values and persists with default setConfig", async () => {
|
|
71
95
|
const state = makeState({ contextCompressionEnabled: true });
|
|
72
|
-
const { mockFn } = mockSetConfig();
|
|
73
96
|
|
|
74
|
-
await setContextCompressionEnabled(state,
|
|
97
|
+
await setContextCompressionEnabled(state, mockDb, false);
|
|
75
98
|
|
|
76
99
|
expect(state.contextCompressionEnabled).toBe(false);
|
|
100
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
101
|
+
mockDb,
|
|
102
|
+
CONFIG_KEYS.CONTEXT_COMPRESSION_ENABLED,
|
|
103
|
+
"false",
|
|
104
|
+
);
|
|
77
105
|
});
|
|
78
106
|
});
|
|
79
107
|
|
|
80
108
|
describe("setGitProxyUrl", () => {
|
|
81
109
|
it("defaults to /git-proxy when empty", async () => {
|
|
82
110
|
const state = makeState();
|
|
83
|
-
const { calls, mockFn } =
|
|
111
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
84
112
|
|
|
85
|
-
await setGitProxyUrl(state,
|
|
113
|
+
await setGitProxyUrl(state, mockDb, "", mockFn);
|
|
86
114
|
|
|
87
115
|
expect(state.gitProxyUrl).toBe("/git-proxy");
|
|
88
116
|
expect(calls[0].value).toBe("/git-proxy");
|
|
89
117
|
});
|
|
90
118
|
|
|
91
|
-
it("stores the provided url", async () => {
|
|
119
|
+
it("stores the provided url using default setConfig", async () => {
|
|
92
120
|
const state = makeState();
|
|
93
|
-
const { calls, mockFn } = mockSetConfig();
|
|
94
121
|
|
|
95
|
-
await setGitProxyUrl(state,
|
|
122
|
+
await setGitProxyUrl(state, mockDb, "/custom-git");
|
|
96
123
|
|
|
97
124
|
expect(state.gitProxyUrl).toBe("/custom-git");
|
|
98
|
-
expect(
|
|
125
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
126
|
+
mockDb,
|
|
127
|
+
CONFIG_KEYS.GIT_PROXY_URL,
|
|
128
|
+
"/custom-git",
|
|
129
|
+
);
|
|
99
130
|
});
|
|
100
131
|
});
|
|
101
132
|
|
|
102
133
|
describe("setMaxIterations", () => {
|
|
103
|
-
it("stores value on state and in db", async () => {
|
|
134
|
+
it("stores value on state and in db with custom setConfig", async () => {
|
|
104
135
|
const state = makeState();
|
|
105
|
-
const { calls, mockFn } =
|
|
136
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
106
137
|
|
|
107
|
-
await setMaxIterations(state,
|
|
138
|
+
await setMaxIterations(state, mockDb, 50, mockFn);
|
|
108
139
|
|
|
109
140
|
expect(state.maxIterations).toBe(50);
|
|
110
141
|
expect(calls[0]).toEqual({
|
|
@@ -112,16 +143,28 @@ describe("settings (functional utilities)", () => {
|
|
|
112
143
|
value: "50",
|
|
113
144
|
});
|
|
114
145
|
});
|
|
146
|
+
|
|
147
|
+
it("stores value using default setConfig", async () => {
|
|
148
|
+
const state = makeState();
|
|
149
|
+
|
|
150
|
+
await setMaxIterations(state, mockDb, 10);
|
|
151
|
+
|
|
152
|
+
expect(state.maxIterations).toBe(10);
|
|
153
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
154
|
+
mockDb,
|
|
155
|
+
CONFIG_KEYS.MAX_ITERATIONS,
|
|
156
|
+
"10",
|
|
157
|
+
);
|
|
158
|
+
});
|
|
115
159
|
});
|
|
116
160
|
|
|
117
161
|
describe("setMaxTokens", () => {
|
|
118
162
|
it("stores large values without clamping to model maximum", async () => {
|
|
119
163
|
const state = makeState({ model: "test-model" });
|
|
120
|
-
const { calls, mockFn } =
|
|
164
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
121
165
|
|
|
122
|
-
await setMaxTokens(state,
|
|
166
|
+
await setMaxTokens(state, mockDb, 999999, mockFn);
|
|
123
167
|
|
|
124
|
-
// No upper clamp — user can set any value they want
|
|
125
168
|
expect(state.maxTokens).toBe(999999);
|
|
126
169
|
expect(calls[0]).toEqual({
|
|
127
170
|
key: CONFIG_KEYS.MAX_TOKENS,
|
|
@@ -129,65 +172,101 @@ describe("settings (functional utilities)", () => {
|
|
|
129
172
|
});
|
|
130
173
|
});
|
|
131
174
|
|
|
132
|
-
it("clamps below 1 to 1", async () => {
|
|
175
|
+
it("clamps below 1 to 1 and uses default setConfig", async () => {
|
|
133
176
|
const state = makeState();
|
|
134
|
-
const { mockFn } = mockSetConfig();
|
|
135
177
|
|
|
136
|
-
await setMaxTokens(state,
|
|
178
|
+
await setMaxTokens(state, mockDb, -10);
|
|
137
179
|
|
|
138
180
|
expect(state.maxTokens).toBe(1);
|
|
181
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
182
|
+
mockDb,
|
|
183
|
+
CONFIG_KEYS.MAX_TOKENS,
|
|
184
|
+
"1",
|
|
185
|
+
);
|
|
139
186
|
});
|
|
140
187
|
});
|
|
141
188
|
|
|
142
189
|
describe("setProxyUrl", () => {
|
|
143
190
|
it("defaults to /proxy when empty", async () => {
|
|
144
191
|
const state = makeState();
|
|
145
|
-
const { calls, mockFn } =
|
|
192
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
146
193
|
|
|
147
|
-
await setProxyUrl(state,
|
|
194
|
+
await setProxyUrl(state, mockDb, "", mockFn);
|
|
148
195
|
|
|
149
196
|
expect(state.proxyUrl).toBe("/proxy");
|
|
150
197
|
expect(calls[0].value).toBe("/proxy");
|
|
198
|
+
expect(mockSyncProxyConfigToServiceWorker).toHaveBeenCalledWith(state);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("stores the custom url and uses default setConfig", async () => {
|
|
202
|
+
const state = makeState();
|
|
203
|
+
|
|
204
|
+
await setProxyUrl(state, mockDb, "http://localhost:8080/proxy");
|
|
205
|
+
|
|
206
|
+
expect(state.proxyUrl).toBe("http://localhost:8080/proxy");
|
|
207
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
208
|
+
mockDb,
|
|
209
|
+
CONFIG_KEYS.PROXY_URL,
|
|
210
|
+
"http://localhost:8080/proxy",
|
|
211
|
+
);
|
|
212
|
+
expect(mockSyncProxyConfigToServiceWorker).toHaveBeenCalledWith(state);
|
|
151
213
|
});
|
|
152
214
|
});
|
|
153
215
|
|
|
154
216
|
describe("setRateLimitAutoAdapt", () => {
|
|
155
|
-
it("normalizes and persists boolean", async () => {
|
|
217
|
+
it("normalizes and persists boolean with custom setConfig", async () => {
|
|
156
218
|
const state = makeState({ rateLimitAutoAdapt: true });
|
|
157
|
-
const { calls, mockFn } =
|
|
219
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
158
220
|
|
|
159
|
-
await setRateLimitAutoAdapt(state,
|
|
221
|
+
await setRateLimitAutoAdapt(state, mockDb, false, mockFn);
|
|
160
222
|
|
|
161
223
|
expect(state.rateLimitAutoAdapt).toBe(false);
|
|
162
224
|
expect(calls[0].value).toBe("false");
|
|
163
225
|
});
|
|
226
|
+
|
|
227
|
+
it("normalizes and persists boolean with default setConfig", async () => {
|
|
228
|
+
const state = makeState({ rateLimitAutoAdapt: false });
|
|
229
|
+
|
|
230
|
+
await setRateLimitAutoAdapt(state, mockDb, true);
|
|
231
|
+
|
|
232
|
+
expect(state.rateLimitAutoAdapt).toBe(true);
|
|
233
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
234
|
+
mockDb,
|
|
235
|
+
CONFIG_KEYS.RATE_LIMIT_AUTO_ADAPT,
|
|
236
|
+
"true",
|
|
237
|
+
);
|
|
238
|
+
});
|
|
164
239
|
});
|
|
165
240
|
|
|
166
241
|
describe("setRateLimitCallsPerMinute", () => {
|
|
167
242
|
it("normalizes to a non-negative integer", async () => {
|
|
168
243
|
const state = makeState();
|
|
169
|
-
const { calls, mockFn } =
|
|
244
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
170
245
|
|
|
171
|
-
await setRateLimitCallsPerMinute(state,
|
|
246
|
+
await setRateLimitCallsPerMinute(state, mockDb, 3.7, mockFn);
|
|
172
247
|
|
|
173
248
|
expect(state.rateLimitCallsPerMinute).toBe(3);
|
|
174
249
|
expect(calls[0].value).toBe("3");
|
|
175
250
|
});
|
|
176
251
|
|
|
177
|
-
it("normalizes NaN to 0", async () => {
|
|
252
|
+
it("normalizes NaN to 0 and uses default setConfig", async () => {
|
|
178
253
|
const state = makeState();
|
|
179
|
-
const { mockFn } = mockSetConfig();
|
|
180
254
|
|
|
181
|
-
await setRateLimitCallsPerMinute(state,
|
|
255
|
+
await setRateLimitCallsPerMinute(state, mockDb, NaN);
|
|
182
256
|
|
|
183
257
|
expect(state.rateLimitCallsPerMinute).toBe(0);
|
|
258
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
259
|
+
mockDb,
|
|
260
|
+
CONFIG_KEYS.RATE_LIMIT_CALLS_PER_MINUTE,
|
|
261
|
+
"0",
|
|
262
|
+
);
|
|
184
263
|
});
|
|
185
264
|
|
|
186
265
|
it("normalizes negative to 0", async () => {
|
|
187
266
|
const state = makeState();
|
|
188
|
-
const { mockFn } =
|
|
267
|
+
const { mockFn } = mockCustomSetConfig();
|
|
189
268
|
|
|
190
|
-
await setRateLimitCallsPerMinute(state,
|
|
269
|
+
await setRateLimitCallsPerMinute(state, mockDb, -5, mockFn);
|
|
191
270
|
|
|
192
271
|
expect(state.rateLimitCallsPerMinute).toBe(0);
|
|
193
272
|
});
|
|
@@ -196,103 +275,191 @@ describe("settings (functional utilities)", () => {
|
|
|
196
275
|
describe("setReasoningEffort", () => {
|
|
197
276
|
it("normalizes and lowercases effort string", async () => {
|
|
198
277
|
const state = makeState();
|
|
199
|
-
const { calls, mockFn } =
|
|
278
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
200
279
|
|
|
201
|
-
await setReasoningEffort(state,
|
|
280
|
+
await setReasoningEffort(state, mockDb, " HIGH ", mockFn);
|
|
202
281
|
|
|
203
282
|
expect(state.reasoningEffort).toBe("high");
|
|
204
283
|
expect(calls[0].value).toBe("high");
|
|
205
284
|
});
|
|
206
285
|
|
|
207
|
-
it("defaults to 'none' for empty input", async () => {
|
|
286
|
+
it("defaults to 'none' for empty input with default setConfig", async () => {
|
|
208
287
|
const state = makeState();
|
|
209
|
-
const { mockFn } = mockSetConfig();
|
|
210
288
|
|
|
211
|
-
await setReasoningEffort(state,
|
|
289
|
+
await setReasoningEffort(state, mockDb, "");
|
|
212
290
|
|
|
213
291
|
expect(state.reasoningEffort).toBe("none");
|
|
292
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
293
|
+
mockDb,
|
|
294
|
+
CONFIG_KEYS.REASONING_EFFORT,
|
|
295
|
+
"none",
|
|
296
|
+
);
|
|
214
297
|
});
|
|
215
298
|
|
|
216
299
|
it("defaults to 'none' for non-string input", async () => {
|
|
217
300
|
const state = makeState();
|
|
218
|
-
const { mockFn } =
|
|
301
|
+
const { mockFn } = mockCustomSetConfig();
|
|
219
302
|
|
|
220
|
-
await setReasoningEffort(state,
|
|
303
|
+
await setReasoningEffort(state, mockDb, 42 as unknown as string, mockFn);
|
|
221
304
|
|
|
222
305
|
expect(state.reasoningEffort).toBe("none");
|
|
223
306
|
});
|
|
224
307
|
});
|
|
225
308
|
|
|
226
309
|
describe("setStreamingEnabled", () => {
|
|
227
|
-
it("normalizes truthy and persists", async () => {
|
|
310
|
+
it("normalizes truthy and persists with custom setConfig", async () => {
|
|
228
311
|
const state = makeState({ streamingEnabled: false });
|
|
229
|
-
const { calls, mockFn } =
|
|
312
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
230
313
|
|
|
231
|
-
await setStreamingEnabled(state,
|
|
314
|
+
await setStreamingEnabled(state, mockDb, true, mockFn);
|
|
232
315
|
|
|
233
316
|
expect(state.streamingEnabled).toBe(true);
|
|
234
317
|
expect(calls[0].value).toBe("true");
|
|
235
318
|
});
|
|
319
|
+
|
|
320
|
+
it("normalizes falsy and persists with default setConfig", async () => {
|
|
321
|
+
const state = makeState({ streamingEnabled: true });
|
|
322
|
+
|
|
323
|
+
await setStreamingEnabled(state, mockDb, false);
|
|
324
|
+
|
|
325
|
+
expect(state.streamingEnabled).toBe(false);
|
|
326
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
327
|
+
mockDb,
|
|
328
|
+
CONFIG_KEYS.STREAMING_ENABLED,
|
|
329
|
+
"false",
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe("setTaskServerEnabled", () => {
|
|
335
|
+
it("normalizes truthy values and persists with custom setConfig", async () => {
|
|
336
|
+
const state = makeState({ taskServerEnabled: false });
|
|
337
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
338
|
+
|
|
339
|
+
await setTaskServerEnabled(state, mockDb, true, mockFn);
|
|
340
|
+
|
|
341
|
+
expect(state.taskServerEnabled).toBe(true);
|
|
342
|
+
expect(calls).toEqual([
|
|
343
|
+
{ key: CONFIG_KEYS.TASK_SERVER_ENABLED, value: "true" },
|
|
344
|
+
]);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("normalizes falsy values and persists with default setConfig", async () => {
|
|
348
|
+
const state = makeState({ taskServerEnabled: true });
|
|
349
|
+
|
|
350
|
+
await setTaskServerEnabled(state, mockDb, false);
|
|
351
|
+
|
|
352
|
+
expect(state.taskServerEnabled).toBe(false);
|
|
353
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
354
|
+
mockDb,
|
|
355
|
+
CONFIG_KEYS.TASK_SERVER_ENABLED,
|
|
356
|
+
"false",
|
|
357
|
+
);
|
|
358
|
+
});
|
|
236
359
|
});
|
|
237
360
|
|
|
238
361
|
describe("setTaskServerUrl", () => {
|
|
239
|
-
it("defaults to config default when empty", async () => {
|
|
362
|
+
it("defaults to config default when empty with custom setConfig", async () => {
|
|
240
363
|
const state = makeState();
|
|
241
|
-
const { calls, mockFn } =
|
|
364
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
242
365
|
|
|
243
|
-
await setTaskServerUrl(state,
|
|
366
|
+
await setTaskServerUrl(state, mockDb, "", mockFn);
|
|
244
367
|
|
|
245
|
-
expect(state.taskServerUrl).toBe(
|
|
246
|
-
expect(calls[0].value).toBe(
|
|
368
|
+
expect(state.taskServerUrl).toBe(DEFAULT_TASK_SERVER_URL);
|
|
369
|
+
expect(calls[0].value).toBe(DEFAULT_TASK_SERVER_URL);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("persists provided url with default setConfig", async () => {
|
|
373
|
+
const state = makeState();
|
|
374
|
+
|
|
375
|
+
await setTaskServerUrl(state, mockDb, "http://tasks.lan:9999");
|
|
376
|
+
|
|
377
|
+
expect(state.taskServerUrl).toBe("http://tasks.lan:9999");
|
|
378
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
379
|
+
mockDb,
|
|
380
|
+
CONFIG_KEYS.TASK_SERVER_URL,
|
|
381
|
+
"http://tasks.lan:9999",
|
|
382
|
+
);
|
|
247
383
|
});
|
|
248
384
|
});
|
|
249
385
|
|
|
250
386
|
describe("setUseProxy", () => {
|
|
251
|
-
it("normalizes and persists boolean", async () => {
|
|
387
|
+
it("normalizes and persists boolean with custom setConfig", async () => {
|
|
252
388
|
const state = makeState();
|
|
253
|
-
const { calls, mockFn } =
|
|
389
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
254
390
|
|
|
255
|
-
await setUseProxy(state,
|
|
391
|
+
await setUseProxy(state, mockDb, true, mockFn);
|
|
256
392
|
|
|
257
393
|
expect(state.useProxy).toBe(true);
|
|
258
394
|
expect(calls[0].value).toBe("true");
|
|
395
|
+
expect(mockSyncProxyConfigToServiceWorker).toHaveBeenCalledWith(state);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("normalizes and persists false with default setConfig", async () => {
|
|
399
|
+
const state = makeState({ useProxy: true });
|
|
400
|
+
|
|
401
|
+
await setUseProxy(state, mockDb, false);
|
|
402
|
+
|
|
403
|
+
expect(state.useProxy).toBe(false);
|
|
404
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
405
|
+
mockDb,
|
|
406
|
+
CONFIG_KEYS.USE_PROXY,
|
|
407
|
+
"false",
|
|
408
|
+
);
|
|
409
|
+
expect(mockSyncProxyConfigToServiceWorker).toHaveBeenCalledWith(state);
|
|
259
410
|
});
|
|
260
411
|
});
|
|
261
412
|
|
|
262
413
|
describe("setVMBashFullInternetAccess", () => {
|
|
263
|
-
it("normalizes and persists boolean", async () => {
|
|
414
|
+
it("normalizes and persists boolean with custom setConfig", async () => {
|
|
264
415
|
const state = makeState();
|
|
265
|
-
const { calls, mockFn } =
|
|
416
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
266
417
|
|
|
267
|
-
await setVMBashFullInternetAccess(state,
|
|
418
|
+
await setVMBashFullInternetAccess(state, mockDb, true, mockFn);
|
|
268
419
|
|
|
269
420
|
expect(state.vmBashFullInternetAccess).toBe(true);
|
|
270
421
|
expect(calls[0].value).toBe("true");
|
|
271
422
|
});
|
|
423
|
+
|
|
424
|
+
it("normalizes and persists false with default setConfig", async () => {
|
|
425
|
+
const state = makeState({ vmBashFullInternetAccess: true });
|
|
426
|
+
|
|
427
|
+
await setVMBashFullInternetAccess(state, mockDb, false);
|
|
428
|
+
|
|
429
|
+
expect(state.vmBashFullInternetAccess).toBe(false);
|
|
430
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
431
|
+
mockDb,
|
|
432
|
+
CONFIG_KEYS.VM_BASH_FULL_INTERNET_ACCESS,
|
|
433
|
+
"false",
|
|
434
|
+
);
|
|
435
|
+
});
|
|
272
436
|
});
|
|
273
437
|
|
|
274
438
|
describe("setVMBashTimeout", () => {
|
|
275
|
-
it("clamps to [1, 1800]", async () => {
|
|
276
|
-
const { calls, mockFn } =
|
|
439
|
+
it("clamps to [1, 1800] with custom setConfig", async () => {
|
|
440
|
+
const { calls, mockFn } = mockCustomSetConfig();
|
|
277
441
|
const state = makeState();
|
|
278
442
|
|
|
279
|
-
await setVMBashTimeout(state,
|
|
443
|
+
await setVMBashTimeout(state, mockDb, 0, mockFn);
|
|
280
444
|
expect(calls[0].value).toBe("1");
|
|
281
445
|
|
|
282
|
-
await setVMBashTimeout(state,
|
|
446
|
+
await setVMBashTimeout(state, mockDb, 99999, mockFn);
|
|
283
447
|
expect(calls[1].value).toBe("1800");
|
|
284
448
|
|
|
285
|
-
await setVMBashTimeout(state,
|
|
449
|
+
await setVMBashTimeout(state, mockDb, 120, mockFn);
|
|
286
450
|
expect(calls[2].value).toBe("120");
|
|
287
451
|
});
|
|
288
452
|
|
|
289
|
-
it("floors fractional seconds", async () => {
|
|
453
|
+
it("floors fractional seconds and persists with default setConfig", async () => {
|
|
290
454
|
const state = makeState();
|
|
291
|
-
const { calls, mockFn } = mockSetConfig();
|
|
292
455
|
|
|
293
|
-
await setVMBashTimeout(state,
|
|
456
|
+
await setVMBashTimeout(state, mockDb, 45.9);
|
|
294
457
|
|
|
295
|
-
expect(
|
|
458
|
+
expect(mockDefaultSetConfig).toHaveBeenCalledWith(
|
|
459
|
+
mockDb,
|
|
460
|
+
CONFIG_KEYS.VM_BASH_TIMEOUT_SEC,
|
|
461
|
+
"45",
|
|
462
|
+
);
|
|
296
463
|
});
|
|
297
464
|
});
|
|
298
465
|
});
|