@ai-setting/roy-agent-cli 1.0.0
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/README.md +126 -0
- package/dist/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
- package/package.json +91 -0
- package/src/bin/roy.ts +12 -0
- package/src/cli.ts +101 -0
- package/src/commands/act.ts +480 -0
- package/src/commands/commands-add.ts +110 -0
- package/src/commands/commands-dirs.ts +70 -0
- package/src/commands/commands-info.ts +90 -0
- package/src/commands/commands-list.ts +161 -0
- package/src/commands/commands-remove.ts +147 -0
- package/src/commands/commands.ts +55 -0
- package/src/commands/config/config-service.test.ts +449 -0
- package/src/commands/config/config-service.ts +312 -0
- package/src/commands/config/deep-merge.test.ts +168 -0
- package/src/commands/config/deep-merge.ts +63 -0
- package/src/commands/config/export.ts +97 -0
- package/src/commands/config/filter-history-e2e.test.ts +141 -0
- package/src/commands/config/import-preserve-refs.test.ts +212 -0
- package/src/commands/config/import.ts +119 -0
- package/src/commands/config/index.ts +35 -0
- package/src/commands/config/list.ts +281 -0
- package/src/commands/config/roy-config-e2e.test.ts +297 -0
- package/src/commands/config/types.ts +54 -0
- package/src/commands/debug/index.ts +38 -0
- package/src/commands/debug/log.test.ts +233 -0
- package/src/commands/debug/log.ts +123 -0
- package/src/commands/debug/span.test.ts +297 -0
- package/src/commands/debug/span.ts +211 -0
- package/src/commands/debug/trace.test.ts +254 -0
- package/src/commands/debug/trace.ts +140 -0
- package/src/commands/eventsource/add.ts +133 -0
- package/src/commands/eventsource/index.ts +48 -0
- package/src/commands/eventsource/list.ts +194 -0
- package/src/commands/eventsource/remove.ts +95 -0
- package/src/commands/eventsource/start.ts +103 -0
- package/src/commands/eventsource/status.ts +185 -0
- package/src/commands/eventsource/stop.ts +89 -0
- package/src/commands/index.ts +22 -0
- package/src/commands/input-handler.test.ts +76 -0
- package/src/commands/input-handler.ts +43 -0
- package/src/commands/interactive-esc.test.ts +254 -0
- package/src/commands/interactive.shutdown.test.ts +122 -0
- package/src/commands/interactive.test.ts +221 -0
- package/src/commands/interactive.ts +1015 -0
- package/src/commands/lsp/check.ts +92 -0
- package/src/commands/lsp/index.ts +32 -0
- package/src/commands/lsp/install.ts +126 -0
- package/src/commands/lsp/list.ts +64 -0
- package/src/commands/mcp/index.ts +27 -0
- package/src/commands/mcp/list.ts +116 -0
- package/src/commands/mcp/reload.ts +70 -0
- package/src/commands/mcp/tools.ts +121 -0
- package/src/commands/memory/extract-e2e.test.ts +388 -0
- package/src/commands/memory/index.ts +11 -0
- package/src/commands/memory/memory-simplified.test.ts +58 -0
- package/src/commands/memory/memory.ts +25 -0
- package/src/commands/memory/organize.ts +300 -0
- package/src/commands/memory/recall.test.ts +120 -0
- package/src/commands/memory/recall.ts +88 -0
- package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
- package/src/commands/memory/record-prompt-component.test.ts +343 -0
- package/src/commands/memory/record.test.ts +92 -0
- package/src/commands/memory/record.ts +332 -0
- package/src/commands/plugin.test.ts +292 -0
- package/src/commands/plugin.ts +267 -0
- package/src/commands/sessions/active.ts +96 -0
- package/src/commands/sessions/add-message.ts +96 -0
- package/src/commands/sessions/checkpoints.ts +154 -0
- package/src/commands/sessions/compact.test.ts +215 -0
- package/src/commands/sessions/compact.ts +269 -0
- package/src/commands/sessions/delete.ts +236 -0
- package/src/commands/sessions/get.ts +165 -0
- package/src/commands/sessions/grep.ts +233 -0
- package/src/commands/sessions/index.ts +95 -0
- package/src/commands/sessions/list.ts +210 -0
- package/src/commands/sessions/messages.test.ts +333 -0
- package/src/commands/sessions/messages.ts +248 -0
- package/src/commands/sessions/mock.ts +194 -0
- package/src/commands/sessions/new.ts +82 -0
- package/src/commands/sessions/rename.ts +98 -0
- package/src/commands/shared/event-handler.ts +213 -0
- package/src/commands/shared/event-message-formatter.ts +295 -0
- package/src/commands/shared/index.ts +11 -0
- package/src/commands/shared/query-executor.test.ts +434 -0
- package/src/commands/shared/query-executor.ts +324 -0
- package/src/commands/shared/repl-engine.test.ts +354 -0
- package/src/commands/shared/session-manager.test.ts +212 -0
- package/src/commands/shared/session-manager.ts +114 -0
- package/src/commands/skills/get.ts +90 -0
- package/src/commands/skills/index.ts +39 -0
- package/src/commands/skills/list.ts +129 -0
- package/src/commands/skills/reload.ts +59 -0
- package/src/commands/skills/search.ts +132 -0
- package/src/commands/skills/show-config.ts +93 -0
- package/src/commands/tasks/complete.ts +92 -0
- package/src/commands/tasks/create.ts +118 -0
- package/src/commands/tasks/delete.ts +86 -0
- package/src/commands/tasks/get.ts +116 -0
- package/src/commands/tasks/index.ts +53 -0
- package/src/commands/tasks/list.ts +140 -0
- package/src/commands/tasks/operations.ts +120 -0
- package/src/commands/tasks/update.ts +122 -0
- package/src/commands/tools/exec-tool.ts +128 -0
- package/src/commands/tools/get.ts +114 -0
- package/src/commands/tools/index.ts +35 -0
- package/src/commands/tools/list.ts +107 -0
- package/src/commands/tools/shared/index.ts +7 -0
- package/src/commands/tools/shared/schema-helper.ts +111 -0
- package/src/commands/workflow/commands/add.ts +315 -0
- package/src/commands/workflow/commands/get.ts +193 -0
- package/src/commands/workflow/commands/list.ts +137 -0
- package/src/commands/workflow/commands/nodes.ts +528 -0
- package/src/commands/workflow/commands/remove.ts +94 -0
- package/src/commands/workflow/commands/run.ts +398 -0
- package/src/commands/workflow/commands/status.ts +147 -0
- package/src/commands/workflow/commands/stop.ts +91 -0
- package/src/commands/workflow/commands/update.ts +130 -0
- package/src/commands/workflow/commands/validate.ts +139 -0
- package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
- package/src/commands/workflow/index.ts +65 -0
- package/src/commands/workflow/renderers.ts +358 -0
- package/src/commands/workflow/validators/index.ts +8 -0
- package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
- package/src/commands/workflow/validators/node-validator.ts +125 -0
- package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
- package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
- package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
- package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
- package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
- package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
- package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
- package/src/commands/workflow/validators/types.ts +78 -0
- package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
- package/src/commands/workflow/validators/workflow-validator.ts +320 -0
- package/src/index.ts +19 -0
- package/src/plugin/apply.ts +103 -0
- package/src/plugin/discover.ts +219 -0
- package/src/plugin/index.ts +45 -0
- package/src/plugin/registry.ts +272 -0
- package/src/plugin/types.ts +165 -0
- package/src/services/context-handler.service.test.ts +501 -0
- package/src/services/context-handler.service.ts +372 -0
- package/src/services/environment.service.commands-prompt.test.ts +167 -0
- package/src/services/environment.service.ts +656 -0
- package/src/services/output.service.test.ts +92 -0
- package/src/services/output.service.ts +122 -0
- package/src/services/quiet-mode.service.test.ts +114 -0
- package/src/services/quiet-mode.service.ts +81 -0
- package/src/services/stream-output.service.test.ts +214 -0
- package/src/services/stream-output.service.ts +323 -0
- package/src/util/which.test.ts +101 -0
- package/src/util/which.ts +55 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview ConfigService 测试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
6
|
+
import * as fsSync from "fs";
|
|
7
|
+
import * as fsPromises from "fs/promises";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
import { ConfigService } from "./config-service";
|
|
11
|
+
|
|
12
|
+
// Mock OutputService
|
|
13
|
+
const mockOutput = {
|
|
14
|
+
log: vi.fn(),
|
|
15
|
+
info: vi.fn(),
|
|
16
|
+
error: vi.fn(),
|
|
17
|
+
warn: vi.fn(),
|
|
18
|
+
debug: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Mock ConfigComponent
|
|
22
|
+
function createMockConfigComponent(memoryData: Record<string, unknown> = {}) {
|
|
23
|
+
const memorySource = {
|
|
24
|
+
name: "memory" as const,
|
|
25
|
+
priority: 0,
|
|
26
|
+
read: (key: string) => memoryData[key],
|
|
27
|
+
list: () =>
|
|
28
|
+
Object.entries(memoryData).map(([key, value]) => ({ key, value })),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
getSources: () => [memorySource],
|
|
33
|
+
get: (key: string) => memoryData[key],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("ConfigService", () => {
|
|
38
|
+
let tempDir: string;
|
|
39
|
+
let tempFilePath: string;
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
tempDir = fsSync.mkdtempSync(path.join(os.tmpdir(), "config-test-"));
|
|
43
|
+
tempFilePath = path.join(tempDir, "config.json");
|
|
44
|
+
vi.clearAllMocks();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
// 清理临时目录
|
|
49
|
+
if (fsSync.existsSync(tempDir)) {
|
|
50
|
+
fsSync.rmSync(tempDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("getComponentConfig", () => {
|
|
55
|
+
it("should return empty config when no memory source", () => {
|
|
56
|
+
const mockConfig = {
|
|
57
|
+
getSources: () => [],
|
|
58
|
+
};
|
|
59
|
+
const service = new ConfigService(
|
|
60
|
+
mockConfig as any,
|
|
61
|
+
mockOutput as any
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const config = service.getComponentConfig("agent");
|
|
65
|
+
expect(config).toEqual({});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should extract component config with prefix", () => {
|
|
69
|
+
const mockConfig = createMockConfigComponent({
|
|
70
|
+
"agent.maxIterations": 100,
|
|
71
|
+
"agent.maxErrorRetries": 3,
|
|
72
|
+
"agent.doomLoopThreshold": 5,
|
|
73
|
+
"llm.provider": "openai",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const service = new ConfigService(
|
|
77
|
+
mockConfig as any,
|
|
78
|
+
mockOutput as any
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const config = service.getComponentConfig("agent");
|
|
82
|
+
expect(config).toEqual({
|
|
83
|
+
maxIterations: 100,
|
|
84
|
+
maxErrorRetries: 3,
|
|
85
|
+
doomLoopThreshold: 5,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should preserve nested structure", () => {
|
|
90
|
+
const mockConfig = createMockConfigComponent({
|
|
91
|
+
"agent.defaultAgent.maxIterations": 100,
|
|
92
|
+
"agent.defaultAgent.toolTimeout": 30000,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const service = new ConfigService(
|
|
96
|
+
mockConfig as any,
|
|
97
|
+
mockOutput as any
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const config = service.getComponentConfig("agent");
|
|
101
|
+
expect(config).toEqual({
|
|
102
|
+
defaultAgent: {
|
|
103
|
+
maxIterations: 100,
|
|
104
|
+
toolTimeout: 30000,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("getComponentFilePath", () => {
|
|
111
|
+
it("should return file path from file source", () => {
|
|
112
|
+
const mockConfig = {
|
|
113
|
+
getSources: () => [
|
|
114
|
+
{
|
|
115
|
+
name: "file",
|
|
116
|
+
filePath: "/path/to/config.json",
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
const service = new ConfigService(
|
|
121
|
+
mockConfig as any,
|
|
122
|
+
mockOutput as any
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const filePath = service.getComponentFilePath("agent");
|
|
126
|
+
expect(filePath).toBe("/path/to/config.json");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should return undefined when no file source", () => {
|
|
130
|
+
const mockConfig = {
|
|
131
|
+
getSources: () => [
|
|
132
|
+
{
|
|
133
|
+
name: "memory",
|
|
134
|
+
priority: 0,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
const service = new ConfigService(
|
|
139
|
+
mockConfig as any,
|
|
140
|
+
mockOutput as any
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const filePath = service.getComponentFilePath("agent");
|
|
144
|
+
expect(filePath).toBeUndefined();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("exportToFile", () => {
|
|
149
|
+
it("should export config to file", async () => {
|
|
150
|
+
const mockConfig = createMockConfigComponent({
|
|
151
|
+
"agent.maxIterations": 100,
|
|
152
|
+
});
|
|
153
|
+
const service = new ConfigService(
|
|
154
|
+
mockConfig as any,
|
|
155
|
+
mockOutput as any
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
await service.exportToFile("agent", {
|
|
159
|
+
filePath: tempFilePath,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const content = fsSync.readFileSync(tempFilePath, "utf-8");
|
|
163
|
+
const data = JSON.parse(content);
|
|
164
|
+
expect(data).toEqual({
|
|
165
|
+
agent: {
|
|
166
|
+
maxIterations: 100,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should return content when no file path", async () => {
|
|
172
|
+
const mockConfig = createMockConfigComponent({
|
|
173
|
+
"agent.maxIterations": 100,
|
|
174
|
+
});
|
|
175
|
+
const service = new ConfigService(
|
|
176
|
+
mockConfig as any,
|
|
177
|
+
mockOutput as any
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const content = await service.exportToFile("agent", {});
|
|
181
|
+
|
|
182
|
+
expect(content).toContain("agent");
|
|
183
|
+
expect(content).toContain("maxIterations");
|
|
184
|
+
expect(content).toContain("100");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should create parent directories if not exist", async () => {
|
|
188
|
+
const mockConfig = createMockConfigComponent({
|
|
189
|
+
"agent.maxIterations": 100,
|
|
190
|
+
});
|
|
191
|
+
const service = new ConfigService(
|
|
192
|
+
mockConfig as any,
|
|
193
|
+
mockOutput as any
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const nestedPath = path.join(tempDir, "nested", "dir", "config.json");
|
|
197
|
+
await service.exportToFile("agent", { filePath: nestedPath });
|
|
198
|
+
|
|
199
|
+
expect(fsSync.existsSync(nestedPath)).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("importFromFile", () => {
|
|
204
|
+
it("should import config from file and merge", async () => {
|
|
205
|
+
// 创建源文件
|
|
206
|
+
const sourceFile = path.join(tempDir, "source.json");
|
|
207
|
+
fsSync.writeFileSync(
|
|
208
|
+
sourceFile,
|
|
209
|
+
JSON.stringify({
|
|
210
|
+
agent: {
|
|
211
|
+
maxIterations: 200,
|
|
212
|
+
doomLoopThreshold: 5,
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// 创建目标文件(已有部分配置)
|
|
218
|
+
fsSync.writeFileSync(
|
|
219
|
+
tempFilePath,
|
|
220
|
+
JSON.stringify({
|
|
221
|
+
agent: {
|
|
222
|
+
maxIterations: 100,
|
|
223
|
+
maxErrorRetries: 3,
|
|
224
|
+
},
|
|
225
|
+
llm: {
|
|
226
|
+
provider: "openai",
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const mockConfig = {
|
|
232
|
+
getSources: () => [
|
|
233
|
+
{
|
|
234
|
+
name: "file",
|
|
235
|
+
filePath: tempFilePath,
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
};
|
|
239
|
+
const service = new ConfigService(
|
|
240
|
+
mockConfig as any,
|
|
241
|
+
mockOutput as any
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const result = await service.importFromFile("agent", {
|
|
245
|
+
sourceFile,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(result.success).toBe(true);
|
|
249
|
+
expect(result.merged).toBe(true);
|
|
250
|
+
expect(result.filePath).toBe(tempFilePath);
|
|
251
|
+
expect(result.changes.length).toBeGreaterThan(0);
|
|
252
|
+
|
|
253
|
+
// 验证文件内容
|
|
254
|
+
const content = fsSync.readFileSync(tempFilePath, "utf-8");
|
|
255
|
+
const data = JSON.parse(content);
|
|
256
|
+
expect(data.agent.maxIterations).toBe(200); // 被覆盖
|
|
257
|
+
expect(data.agent.maxErrorRetries).toBe(3); // 保留
|
|
258
|
+
expect(data.agent.doomLoopThreshold).toBe(5); // 新增
|
|
259
|
+
expect(data.llm.provider).toBe("openai"); // 保留
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should support dry-run mode", async () => {
|
|
263
|
+
const sourceFile = path.join(tempDir, "source.json");
|
|
264
|
+
fsSync.writeFileSync(
|
|
265
|
+
sourceFile,
|
|
266
|
+
JSON.stringify({
|
|
267
|
+
agent: { maxIterations: 200 },
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
fsSync.writeFileSync(
|
|
272
|
+
tempFilePath,
|
|
273
|
+
JSON.stringify({
|
|
274
|
+
agent: { maxIterations: 100 },
|
|
275
|
+
})
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const mockConfig = {
|
|
279
|
+
getSources: () => [
|
|
280
|
+
{
|
|
281
|
+
name: "file",
|
|
282
|
+
filePath: tempFilePath,
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
const service = new ConfigService(
|
|
287
|
+
mockConfig as any,
|
|
288
|
+
mockOutput as any
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const result = await service.importFromFile("agent", {
|
|
292
|
+
sourceFile,
|
|
293
|
+
dryRun: true,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(result.success).toBe(true);
|
|
297
|
+
expect(result.filePath).toBe(tempFilePath);
|
|
298
|
+
|
|
299
|
+
// 文件内容不应被修改
|
|
300
|
+
const content = fsSync.readFileSync(tempFilePath, "utf-8");
|
|
301
|
+
const data = JSON.parse(content);
|
|
302
|
+
expect(data.agent.maxIterations).toBe(100); // 未被修改
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("should create new file if target does not exist", async () => {
|
|
306
|
+
const sourceFile = path.join(tempDir, "source.json");
|
|
307
|
+
fsSync.writeFileSync(
|
|
308
|
+
sourceFile,
|
|
309
|
+
JSON.stringify({
|
|
310
|
+
agent: { maxIterations: 200 },
|
|
311
|
+
})
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const mockConfig = {
|
|
315
|
+
getSources: () => [
|
|
316
|
+
{
|
|
317
|
+
name: "file",
|
|
318
|
+
filePath: tempFilePath,
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
};
|
|
322
|
+
const service = new ConfigService(
|
|
323
|
+
mockConfig as any,
|
|
324
|
+
mockOutput as any
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const result = await service.importFromFile("agent", {
|
|
328
|
+
sourceFile,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
expect(result.success).toBe(true);
|
|
332
|
+
expect(fsSync.existsSync(tempFilePath)).toBe(true);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("should return error when source file not found", async () => {
|
|
336
|
+
const mockConfig = {
|
|
337
|
+
getSources: () => [
|
|
338
|
+
{
|
|
339
|
+
name: "file",
|
|
340
|
+
filePath: tempFilePath,
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
};
|
|
344
|
+
const service = new ConfigService(
|
|
345
|
+
mockConfig as any,
|
|
346
|
+
mockOutput as any
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const result = await service.importFromFile("agent", {
|
|
350
|
+
sourceFile: "/non/existent/file.json",
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
expect(result.success).toBe(false);
|
|
354
|
+
expect(result.message).toContain("无法读取源文件");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("should return error when component config not found in source", async () => {
|
|
358
|
+
const sourceFile = path.join(tempDir, "source.json");
|
|
359
|
+
fsSync.writeFileSync(
|
|
360
|
+
sourceFile,
|
|
361
|
+
JSON.stringify({
|
|
362
|
+
llm: { provider: "openai" },
|
|
363
|
+
})
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const mockConfig = {
|
|
367
|
+
getSources: () => [
|
|
368
|
+
{
|
|
369
|
+
name: "file",
|
|
370
|
+
filePath: tempFilePath,
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
};
|
|
374
|
+
const service = new ConfigService(
|
|
375
|
+
mockConfig as any,
|
|
376
|
+
mockOutput as any
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const result = await service.importFromFile("agent", {
|
|
380
|
+
sourceFile,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
expect(result.success).toBe(false);
|
|
384
|
+
expect(result.message).toContain("未找到 agent 配置");
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("should preserve other components when importing JSONC file with comments", async () => {
|
|
388
|
+
// 创建源文件
|
|
389
|
+
const sourceFile = path.join(tempDir, "source.json");
|
|
390
|
+
fsSync.writeFileSync(
|
|
391
|
+
sourceFile,
|
|
392
|
+
JSON.stringify({
|
|
393
|
+
agent: {
|
|
394
|
+
maxIterations: 200,
|
|
395
|
+
doomLoopThreshold: 5,
|
|
396
|
+
},
|
|
397
|
+
})
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// 创建目标文件(JSONC 格式,包含注释和其他组件配置)
|
|
401
|
+
const targetFilePath = path.join(tempDir, "target.jsonc");
|
|
402
|
+
fsSync.writeFileSync(
|
|
403
|
+
targetFilePath,
|
|
404
|
+
`{
|
|
405
|
+
// LLM 配置
|
|
406
|
+
"llm": {
|
|
407
|
+
"provider": "openai",
|
|
408
|
+
"model": "gpt-4"
|
|
409
|
+
},
|
|
410
|
+
"agent": {
|
|
411
|
+
"maxIterations": 100,
|
|
412
|
+
"maxErrorRetries": 3
|
|
413
|
+
}
|
|
414
|
+
}`
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const mockConfig = {
|
|
418
|
+
getSources: () => [
|
|
419
|
+
{
|
|
420
|
+
name: "file",
|
|
421
|
+
filePath: targetFilePath,
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
};
|
|
425
|
+
const service = new ConfigService(
|
|
426
|
+
mockConfig as any,
|
|
427
|
+
mockOutput as any
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
const result = await service.importFromFile("agent", {
|
|
431
|
+
sourceFile,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
expect(result.success).toBe(true);
|
|
435
|
+
expect(result.merged).toBe(true);
|
|
436
|
+
|
|
437
|
+
// 验证文件内容 - llm 配置应该被保留
|
|
438
|
+
const content = fsSync.readFileSync(targetFilePath, "utf-8");
|
|
439
|
+
const data = JSON.parse(content);
|
|
440
|
+
expect(data.llm).toBeDefined();
|
|
441
|
+
expect(data.llm.provider).toBe("openai");
|
|
442
|
+
expect(data.llm.model).toBe("gpt-4");
|
|
443
|
+
// agent 配置应该被合并
|
|
444
|
+
expect(data.agent.maxIterations).toBe(200); // 被覆盖
|
|
445
|
+
expect(data.agent.maxErrorRetries).toBe(3); // 保留
|
|
446
|
+
expect(data.agent.doomLoopThreshold).toBe(5); // 新增
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
});
|