@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.
Files changed (158) hide show
  1. package/README.md +126 -0
  2. package/dist/bin/roy.js +127297 -0
  3. package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
  4. package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
  5. package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
  6. package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
  7. package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
  8. package/package.json +91 -0
  9. package/src/bin/roy.ts +12 -0
  10. package/src/cli.ts +101 -0
  11. package/src/commands/act.ts +480 -0
  12. package/src/commands/commands-add.ts +110 -0
  13. package/src/commands/commands-dirs.ts +70 -0
  14. package/src/commands/commands-info.ts +90 -0
  15. package/src/commands/commands-list.ts +161 -0
  16. package/src/commands/commands-remove.ts +147 -0
  17. package/src/commands/commands.ts +55 -0
  18. package/src/commands/config/config-service.test.ts +449 -0
  19. package/src/commands/config/config-service.ts +312 -0
  20. package/src/commands/config/deep-merge.test.ts +168 -0
  21. package/src/commands/config/deep-merge.ts +63 -0
  22. package/src/commands/config/export.ts +97 -0
  23. package/src/commands/config/filter-history-e2e.test.ts +141 -0
  24. package/src/commands/config/import-preserve-refs.test.ts +212 -0
  25. package/src/commands/config/import.ts +119 -0
  26. package/src/commands/config/index.ts +35 -0
  27. package/src/commands/config/list.ts +281 -0
  28. package/src/commands/config/roy-config-e2e.test.ts +297 -0
  29. package/src/commands/config/types.ts +54 -0
  30. package/src/commands/debug/index.ts +38 -0
  31. package/src/commands/debug/log.test.ts +233 -0
  32. package/src/commands/debug/log.ts +123 -0
  33. package/src/commands/debug/span.test.ts +297 -0
  34. package/src/commands/debug/span.ts +211 -0
  35. package/src/commands/debug/trace.test.ts +254 -0
  36. package/src/commands/debug/trace.ts +140 -0
  37. package/src/commands/eventsource/add.ts +133 -0
  38. package/src/commands/eventsource/index.ts +48 -0
  39. package/src/commands/eventsource/list.ts +194 -0
  40. package/src/commands/eventsource/remove.ts +95 -0
  41. package/src/commands/eventsource/start.ts +103 -0
  42. package/src/commands/eventsource/status.ts +185 -0
  43. package/src/commands/eventsource/stop.ts +89 -0
  44. package/src/commands/index.ts +22 -0
  45. package/src/commands/input-handler.test.ts +76 -0
  46. package/src/commands/input-handler.ts +43 -0
  47. package/src/commands/interactive-esc.test.ts +254 -0
  48. package/src/commands/interactive.shutdown.test.ts +122 -0
  49. package/src/commands/interactive.test.ts +221 -0
  50. package/src/commands/interactive.ts +1015 -0
  51. package/src/commands/lsp/check.ts +92 -0
  52. package/src/commands/lsp/index.ts +32 -0
  53. package/src/commands/lsp/install.ts +126 -0
  54. package/src/commands/lsp/list.ts +64 -0
  55. package/src/commands/mcp/index.ts +27 -0
  56. package/src/commands/mcp/list.ts +116 -0
  57. package/src/commands/mcp/reload.ts +70 -0
  58. package/src/commands/mcp/tools.ts +121 -0
  59. package/src/commands/memory/extract-e2e.test.ts +388 -0
  60. package/src/commands/memory/index.ts +11 -0
  61. package/src/commands/memory/memory-simplified.test.ts +58 -0
  62. package/src/commands/memory/memory.ts +25 -0
  63. package/src/commands/memory/organize.ts +300 -0
  64. package/src/commands/memory/recall.test.ts +120 -0
  65. package/src/commands/memory/recall.ts +88 -0
  66. package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
  67. package/src/commands/memory/record-prompt-component.test.ts +343 -0
  68. package/src/commands/memory/record.test.ts +92 -0
  69. package/src/commands/memory/record.ts +332 -0
  70. package/src/commands/plugin.test.ts +292 -0
  71. package/src/commands/plugin.ts +267 -0
  72. package/src/commands/sessions/active.ts +96 -0
  73. package/src/commands/sessions/add-message.ts +96 -0
  74. package/src/commands/sessions/checkpoints.ts +154 -0
  75. package/src/commands/sessions/compact.test.ts +215 -0
  76. package/src/commands/sessions/compact.ts +269 -0
  77. package/src/commands/sessions/delete.ts +236 -0
  78. package/src/commands/sessions/get.ts +165 -0
  79. package/src/commands/sessions/grep.ts +233 -0
  80. package/src/commands/sessions/index.ts +95 -0
  81. package/src/commands/sessions/list.ts +210 -0
  82. package/src/commands/sessions/messages.test.ts +333 -0
  83. package/src/commands/sessions/messages.ts +248 -0
  84. package/src/commands/sessions/mock.ts +194 -0
  85. package/src/commands/sessions/new.ts +82 -0
  86. package/src/commands/sessions/rename.ts +98 -0
  87. package/src/commands/shared/event-handler.ts +213 -0
  88. package/src/commands/shared/event-message-formatter.ts +295 -0
  89. package/src/commands/shared/index.ts +11 -0
  90. package/src/commands/shared/query-executor.test.ts +434 -0
  91. package/src/commands/shared/query-executor.ts +324 -0
  92. package/src/commands/shared/repl-engine.test.ts +354 -0
  93. package/src/commands/shared/session-manager.test.ts +212 -0
  94. package/src/commands/shared/session-manager.ts +114 -0
  95. package/src/commands/skills/get.ts +90 -0
  96. package/src/commands/skills/index.ts +39 -0
  97. package/src/commands/skills/list.ts +129 -0
  98. package/src/commands/skills/reload.ts +59 -0
  99. package/src/commands/skills/search.ts +132 -0
  100. package/src/commands/skills/show-config.ts +93 -0
  101. package/src/commands/tasks/complete.ts +92 -0
  102. package/src/commands/tasks/create.ts +118 -0
  103. package/src/commands/tasks/delete.ts +86 -0
  104. package/src/commands/tasks/get.ts +116 -0
  105. package/src/commands/tasks/index.ts +53 -0
  106. package/src/commands/tasks/list.ts +140 -0
  107. package/src/commands/tasks/operations.ts +120 -0
  108. package/src/commands/tasks/update.ts +122 -0
  109. package/src/commands/tools/exec-tool.ts +128 -0
  110. package/src/commands/tools/get.ts +114 -0
  111. package/src/commands/tools/index.ts +35 -0
  112. package/src/commands/tools/list.ts +107 -0
  113. package/src/commands/tools/shared/index.ts +7 -0
  114. package/src/commands/tools/shared/schema-helper.ts +111 -0
  115. package/src/commands/workflow/commands/add.ts +315 -0
  116. package/src/commands/workflow/commands/get.ts +193 -0
  117. package/src/commands/workflow/commands/list.ts +137 -0
  118. package/src/commands/workflow/commands/nodes.ts +528 -0
  119. package/src/commands/workflow/commands/remove.ts +94 -0
  120. package/src/commands/workflow/commands/run.ts +398 -0
  121. package/src/commands/workflow/commands/status.ts +147 -0
  122. package/src/commands/workflow/commands/stop.ts +91 -0
  123. package/src/commands/workflow/commands/update.ts +130 -0
  124. package/src/commands/workflow/commands/validate.ts +139 -0
  125. package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
  126. package/src/commands/workflow/index.ts +65 -0
  127. package/src/commands/workflow/renderers.ts +358 -0
  128. package/src/commands/workflow/validators/index.ts +8 -0
  129. package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
  130. package/src/commands/workflow/validators/node-validator.ts +125 -0
  131. package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
  132. package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
  133. package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
  134. package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
  135. package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
  136. package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
  137. package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
  138. package/src/commands/workflow/validators/types.ts +78 -0
  139. package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
  140. package/src/commands/workflow/validators/workflow-validator.ts +320 -0
  141. package/src/index.ts +19 -0
  142. package/src/plugin/apply.ts +103 -0
  143. package/src/plugin/discover.ts +219 -0
  144. package/src/plugin/index.ts +45 -0
  145. package/src/plugin/registry.ts +272 -0
  146. package/src/plugin/types.ts +165 -0
  147. package/src/services/context-handler.service.test.ts +501 -0
  148. package/src/services/context-handler.service.ts +372 -0
  149. package/src/services/environment.service.commands-prompt.test.ts +167 -0
  150. package/src/services/environment.service.ts +656 -0
  151. package/src/services/output.service.test.ts +92 -0
  152. package/src/services/output.service.ts +122 -0
  153. package/src/services/quiet-mode.service.test.ts +114 -0
  154. package/src/services/quiet-mode.service.ts +81 -0
  155. package/src/services/stream-output.service.test.ts +214 -0
  156. package/src/services/stream-output.service.ts +323 -0
  157. package/src/util/which.test.ts +101 -0
  158. package/src/util/which.ts +55 -0
@@ -0,0 +1,281 @@
1
+ /**
2
+ * @fileoverview config list 子命令
3
+ *
4
+ * 原 show-config 功能的重构版本
5
+ */
6
+
7
+ import { CommandModule } from "yargs";
8
+ import chalk from "chalk";
9
+ import { EnvironmentService } from "../../services/environment.service";
10
+ import { OutputService } from "../../services/output.service";
11
+ import { ConfigService } from "./config-service";
12
+ import { SUPPORTED_COMPONENTS, COMPONENT_DESCRIPTIONS, resolveComponentName } from "./types";
13
+
14
+ export interface ConfigListOptions {
15
+ component?: string;
16
+ json?: boolean;
17
+ keys?: boolean;
18
+ sources?: boolean;
19
+ config?: string;
20
+ }
21
+
22
+ /**
23
+ * show-config 命令使用示例
24
+ */
25
+ const USAGE_COMMANDS = [
26
+ { command: "roy config list", description: "显示帮助信息" },
27
+ { command: "roy config list tool", description: "查看 tool component 配置" },
28
+ { command: "roy config list session", description: "查看 session component 配置" },
29
+ { command: "roy config list all", description: "查看所有 components 概览" },
30
+ { command: "roy config list tool --json", description: "JSON 格式输出" },
31
+ { command: "roy config list tool --keys", description: "只显示配置键" },
32
+ ];
33
+
34
+ /**
35
+ * ConfigListCommand - config list 子命令
36
+ */
37
+ export const ConfigListCommand: CommandModule<object, ConfigListOptions> = {
38
+ command: "list [component]",
39
+ describe: "查看组件配置信息",
40
+
41
+ builder: (yargs) =>
42
+ yargs
43
+ .positional("component", {
44
+ type: "string",
45
+ describe: "Component 名称",
46
+ })
47
+ .option("json", {
48
+ type: "boolean",
49
+ describe: "JSON 格式输出",
50
+ alias: "j",
51
+ })
52
+ .option("keys", {
53
+ type: "boolean",
54
+ describe: "只显示配置键",
55
+ alias: "k",
56
+ })
57
+ .option("sources", {
58
+ type: "boolean",
59
+ describe: "只显示配置源",
60
+ alias: "s",
61
+ })
62
+ .option("config", {
63
+ type: "string",
64
+ describe: "配置文件路径",
65
+ alias: "c",
66
+ })
67
+ .example("roy config list", "显示帮助信息")
68
+ .example("roy config list tool", "查看 tool component 配置")
69
+ .example("roy config list session", "查看 session component 配置")
70
+ .example("roy config list all", "查看所有 components 概览")
71
+ .example("roy config list tool --json", "JSON 格式输出")
72
+ .example("roy config list tool --keys", "只显示配置键"),
73
+
74
+ async handler(args) {
75
+ const output = new OutputService();
76
+ const envService = new EnvironmentService(output);
77
+
78
+ try {
79
+ await envService.create({ configPath: args.config });
80
+ const env = envService.getEnvironment();
81
+ if (!env) {
82
+ output.error("Failed to get environment");
83
+ process.exit(1);
84
+ }
85
+ const configComponent = env.getComponent("config") as any;
86
+ if (!configComponent) {
87
+ output.error("ConfigComponent not available");
88
+ process.exit(1);
89
+ }
90
+ const configService = new ConfigService(configComponent, output);
91
+
92
+ // 无参数时显示帮助
93
+ if (!args.component) {
94
+ showHelp(output);
95
+ return;
96
+ }
97
+
98
+ // all: 显示所有 components
99
+ if (args.component === "all") {
100
+ await showAllComponents(output, configService);
101
+ return;
102
+ }
103
+
104
+ // 解析 component 名称(处理别名)
105
+ const componentName = resolveComponentName(args.component);
106
+
107
+ // 验证 component
108
+ if (!SUPPORTED_COMPONENTS.includes(componentName as any)) {
109
+ output.error(`Unknown component: ${args.component}`);
110
+ output.log("");
111
+ output.log("Supported components:");
112
+ for (const comp of SUPPORTED_COMPONENTS) {
113
+ output.log(` ${chalk.cyan(comp.padEnd(15))} ${COMPONENT_DESCRIPTIONS[comp] || ""}`);
114
+ }
115
+ process.exit(1);
116
+ }
117
+
118
+ // 显示特定 component
119
+ await showComponentConfig(componentName, output, configService, {
120
+ json: args.json,
121
+ keys: args.keys,
122
+ sources: args.sources,
123
+ });
124
+ } catch (error) {
125
+ output.error(`Failed to show config: ${error}`);
126
+ process.exit(1);
127
+ } finally {
128
+ await envService.dispose();
129
+ }
130
+ },
131
+ };
132
+
133
+ /**
134
+ * 显示帮助信息
135
+ */
136
+ function showHelp(output: OutputService) {
137
+ output.log(chalk.bold.cyan("# roy config list"));
138
+ output.log("");
139
+ output.log("查看 roy-agent 组件配置信息");
140
+ output.log("");
141
+ output.log(chalk.bold("Usage:"));
142
+ output.log(` ${chalk.cyan("roy config list [component] [options]")}`);
143
+ output.log("");
144
+ output.log(chalk.bold("Components:"));
145
+ for (const comp of SUPPORTED_COMPONENTS) {
146
+ output.log(
147
+ ` ${chalk.cyan(comp.padEnd(15))} ${chalk.gray(COMPONENT_DESCRIPTIONS[comp] || "")}`
148
+ );
149
+ }
150
+ output.log(` ${chalk.cyan("all".padEnd(15))} 显示所有 components 概览`);
151
+ output.log("");
152
+ output.log(chalk.bold("Options:"));
153
+ output.log(` ${chalk.cyan("-j, --json")} JSON 格式输出`);
154
+ output.log(` ${chalk.cyan("-k, --keys")} 只显示配置键`);
155
+ output.log(` ${chalk.cyan("-s, --sources")} 只显示配置源`);
156
+ output.log("");
157
+ output.log(chalk.bold("Examples:"));
158
+ for (const { command, description } of USAGE_COMMANDS) {
159
+ output.log(` ${chalk.cyan(command.padEnd(40))} ${chalk.gray(description)}`);
160
+ }
161
+ }
162
+
163
+ /**
164
+ * 显示所有 components 概览
165
+ */
166
+ async function showAllComponents(
167
+ output: OutputService,
168
+ configService: ConfigService
169
+ ) {
170
+ const components: Array<{ name: string; config: Record<string, unknown> }> = [];
171
+
172
+ for (const compName of SUPPORTED_COMPONENTS) {
173
+ const config = configService.getComponentConfig(compName);
174
+ components.push({ name: compName, config });
175
+ }
176
+
177
+ output.log(chalk.bold.cyan("# All Components Overview"));
178
+ output.log("");
179
+ output.log(
180
+ ` ${chalk.cyan("Component".padEnd(15))} ${chalk.cyan("Keys".padEnd(10))} ${chalk.gray("Description")}`
181
+ );
182
+ output.log(` ${chalk.gray("─".repeat(60))}`);
183
+
184
+ for (const comp of components) {
185
+ const keyCount = Object.keys(comp.config).length;
186
+ output.log(
187
+ ` ${chalk.cyan(comp.name.padEnd(15))} ${keyCount.toString().padEnd(10)} ${chalk.gray(COMPONENT_DESCRIPTIONS[comp.name] || "")}`
188
+ );
189
+ }
190
+ }
191
+
192
+ /**
193
+ * 显示特定 component 配置
194
+ */
195
+ async function showComponentConfig(
196
+ componentName: string,
197
+ output: OutputService,
198
+ configService: ConfigService,
199
+ options: { json?: boolean; keys?: boolean; sources?: boolean }
200
+ ) {
201
+ const config = configService.getComponentConfig(componentName);
202
+ const filePath = configService.getComponentFilePath(componentName);
203
+
204
+ if (options.json) {
205
+ output.json({
206
+ name: componentName,
207
+ filePath,
208
+ config,
209
+ });
210
+ return;
211
+ }
212
+
213
+ output.log(chalk.bold.cyan(`# ${componentName} Component Configuration`));
214
+ output.log("");
215
+
216
+ // File Path
217
+ if (filePath) {
218
+ output.log(chalk.bold("File:"));
219
+ output.log(` ${chalk.cyan(filePath)}`);
220
+ output.log("");
221
+ }
222
+
223
+ // Config Keys
224
+ if (!options.sources) {
225
+ output.log(chalk.bold("Configuration:"));
226
+ if (Object.keys(config).length === 0) {
227
+ output.log(` ${chalk.gray("(无配置)")}`);
228
+ } else {
229
+ const flatConfig = flattenConfig(config);
230
+ for (const [key, value] of flatConfig) {
231
+ const displayValue = formatValue(value);
232
+ output.log(` ${chalk.cyan(key + ":")} ${displayValue}`);
233
+ }
234
+ }
235
+ output.log("");
236
+ }
237
+ }
238
+
239
+ /**
240
+ * 扁平化配置对象
241
+ */
242
+ function flattenConfig(
243
+ obj: unknown,
244
+ prefix = ""
245
+ ): Array<[string, unknown]> {
246
+ const result: Array<[string, unknown]> = [];
247
+
248
+ if (typeof obj !== "object" || obj === null) {
249
+ if (prefix) {
250
+ result.push([prefix, obj]);
251
+ }
252
+ return result;
253
+ }
254
+
255
+ for (const [key, value] of Object.entries(obj)) {
256
+ const fullKey = prefix ? `${prefix}.${key}` : key;
257
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
258
+ result.push(...flattenConfig(value, fullKey));
259
+ } else {
260
+ result.push([fullKey, value]);
261
+ }
262
+ }
263
+
264
+ return result;
265
+ }
266
+
267
+ /**
268
+ * 格式化值显示
269
+ */
270
+ function formatValue(value: unknown): string {
271
+ if (value === undefined) {
272
+ return chalk.gray("undefined");
273
+ }
274
+ if (value === null) {
275
+ return chalk.gray("null");
276
+ }
277
+ if (typeof value === "object") {
278
+ return chalk.gray(JSON.stringify(value));
279
+ }
280
+ return String(value);
281
+ }
@@ -0,0 +1,297 @@
1
+ /**
2
+ * @fileoverview roy config 命令端到端测试
3
+ *
4
+ * 验证 roy config export 能正确导出 config.jsonc 中各 component 的配置(包括默认值)
5
+ *
6
+ * TDD 流程:
7
+ * - RED: 测试应该失败(因为当前实现有问题)
8
+ * - GREEN: 实现最小代码让测试通过
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
12
+ import * as fsSync from "fs";
13
+ import * as path from "path";
14
+ import * as os from "os";
15
+ import { EnvironmentService } from "../../services/environment.service";
16
+ import { OutputService } from "../../services/output.service";
17
+ import { ConfigService } from "./config-service";
18
+
19
+ describe("roy config export integration", () => {
20
+ let tempDir: string;
21
+ let configFilePath: string;
22
+
23
+ beforeEach(() => {
24
+ // 创建临时目录
25
+ tempDir = fsSync.mkdtempSync(path.join(os.tmpdir(), "roy-config-e2e-"));
26
+ configFilePath = path.join(tempDir, "config.jsonc");
27
+ });
28
+
29
+ afterEach(() => {
30
+ // 清理临时目录
31
+ if (fsSync.existsSync(tempDir)) {
32
+ fsSync.rmSync(tempDir, { recursive: true });
33
+ }
34
+ vi.restoreAllMocks();
35
+ });
36
+
37
+ describe("ConfigService.getComponentConfig should read from file source", () => {
38
+ it("should export llm config from file source", async () => {
39
+ // 创建 config.jsonc 文件,包含 llm 配置
40
+ const configContent = `{
41
+ // LLM 配置
42
+ "llm": {
43
+ "defaultModel": "gpt-4o",
44
+ "defaultProvider": "openai",
45
+ "temperature": 0.7,
46
+ "providers": {
47
+ "openai": {
48
+ "apiKey": "sk-test-key",
49
+ "baseURL": "https://api.openai.com/v1"
50
+ }
51
+ }
52
+ }
53
+ }`;
54
+ fsSync.writeFileSync(configFilePath, configContent, "utf-8");
55
+
56
+ // 使用 EnvironmentService 创建环境
57
+ const output = new OutputService();
58
+ const envService = new EnvironmentService(output);
59
+
60
+ try {
61
+ await envService.create({
62
+ configPath: configFilePath,
63
+ sessionStorage: { type: "memory" } // 使用内存存储,避免污染正式数据库
64
+ });
65
+ const env = envService.getEnvironment();
66
+ expect(env).not.toBeNull();
67
+
68
+ const configComponent = env!.getComponent("config");
69
+ expect(configComponent).toBeDefined();
70
+
71
+ const configService = new ConfigService(configComponent as any, output);
72
+
73
+ // 导出 llm 配置
74
+ const llmConfig = configService.getComponentConfig("llm");
75
+
76
+ // 验证配置已正确读取
77
+ expect(llmConfig.defaultModel).toBe("gpt-4o");
78
+ expect(llmConfig.defaultProvider).toBe("openai");
79
+ expect(llmConfig.temperature).toBe(0.7);
80
+ expect(llmConfig.providers).toBeDefined();
81
+ expect(llmConfig.providers.openai.apiKey).toBe("sk-test-key");
82
+ } finally {
83
+ await envService.dispose();
84
+ }
85
+ });
86
+
87
+ it("should export agent config from file source", async () => {
88
+ // 创建 config.jsonc 文件,包含 agent 配置
89
+ const configContent = `{
90
+ // Agent 配置
91
+ "agent": {
92
+ "maxIterations": 100,
93
+ "maxErrorRetries": 3,
94
+ "doomLoopThreshold": 5
95
+ }
96
+ }`;
97
+ fsSync.writeFileSync(configFilePath, configContent, "utf-8");
98
+
99
+ const output = new OutputService();
100
+ const envService = new EnvironmentService(output);
101
+
102
+ try {
103
+ await envService.create({
104
+ configPath: configFilePath,
105
+ sessionStorage: { type: "memory" } // 使用内存存储,避免污染正式数据库
106
+ });
107
+ const env = envService.getEnvironment();
108
+
109
+ const configComponent = env!.getComponent("config");
110
+ const configService = new ConfigService(configComponent as any, output);
111
+
112
+ // 导出 agent 配置
113
+ const agentConfig = configService.getComponentConfig("agent");
114
+
115
+ // 验证配置已正确读取
116
+ expect(agentConfig.maxIterations).toBe(100);
117
+ expect(agentConfig.maxErrorRetries).toBe(3);
118
+ expect(agentConfig.doomLoopThreshold).toBe(5);
119
+ } finally {
120
+ await envService.dispose();
121
+ }
122
+ });
123
+
124
+ it("should export multiple components from the same config file", async () => {
125
+ // 创建 config.jsonc 文件,包含多个 component 配置
126
+ const configContent = `{
127
+ // LLM 配置
128
+ "llm": {
129
+ "defaultModel": "claude-3-sonnet",
130
+ "defaultProvider": "anthropic"
131
+ },
132
+ // Agent 配置
133
+ "agent": {
134
+ "maxIterations": 50
135
+ },
136
+ // Tool 配置
137
+ "tool": {
138
+ "timeout": 30000
139
+ }
140
+ }`;
141
+ fsSync.writeFileSync(configFilePath, configContent, "utf-8");
142
+
143
+ const output = new OutputService();
144
+ const envService = new EnvironmentService(output);
145
+
146
+ try {
147
+ await envService.create({
148
+ configPath: configFilePath,
149
+ sessionStorage: { type: "memory" } // 使用内存存储,避免污染正式数据库
150
+ });
151
+ const env = envService.getEnvironment();
152
+
153
+ const configComponent = env!.getComponent("config");
154
+ const configService = new ConfigService(configComponent as any, output);
155
+
156
+ // 导出各个 component 配置
157
+ const llmConfig = configService.getComponentConfig("llm");
158
+ const agentConfig = configService.getComponentConfig("agent");
159
+ const toolConfig = configService.getComponentConfig("tool");
160
+
161
+ // 验证配置
162
+ expect(llmConfig.defaultModel).toBe("claude-3-sonnet");
163
+ expect(llmConfig.defaultProvider).toBe("anthropic");
164
+ expect(agentConfig.maxIterations).toBe(50);
165
+ expect(toolConfig.timeout).toBe(30000);
166
+ } finally {
167
+ await envService.dispose();
168
+ }
169
+ });
170
+
171
+ it("should export default values when config file only has partial settings", async () => {
172
+ // 创建 config.jsonc 文件,只包含部分配置
173
+ const configContent = `{
174
+ // 只有 llm 配置
175
+ "llm": {
176
+ "defaultModel": "gpt-4o"
177
+ }
178
+ }`;
179
+ fsSync.writeFileSync(configFilePath, configContent, "utf-8");
180
+
181
+ const output = new OutputService();
182
+ const envService = new EnvironmentService(output);
183
+
184
+ try {
185
+ await envService.create({
186
+ configPath: configFilePath,
187
+ sessionStorage: { type: "memory" } // 使用内存存储,避免污染正式数据库
188
+ });
189
+ const env = envService.getEnvironment();
190
+
191
+ const configComponent = env!.getComponent("config");
192
+ const configService = new ConfigService(configComponent as any, output);
193
+
194
+ // 获取 agent 配置(未在文件中设置)
195
+ const agentConfig = configService.getComponentConfig("agent");
196
+
197
+ // 应该有默认值(即使没有在文件中设置)
198
+ // 注意:这取决于各 component 是否有注册默认值
199
+ expect(agentConfig).toBeDefined();
200
+ // maxIterations 应该有默认值(从 component 注册的 defaults)
201
+ // 如果当前实现没有默认值,至少应该是空对象而不是 undefined
202
+ expect(typeof agentConfig).toBe("object");
203
+ } finally {
204
+ await envService.dispose();
205
+ }
206
+ });
207
+
208
+ it("should handle JSONC format with comments", async () => {
209
+ // 创建带注释的 JSONC 文件
210
+ const configContent = `{
211
+ // LLM 配置
212
+ "llm": {
213
+ // 模型设置
214
+ "defaultModel": "gpt-4-turbo", // inline comment
215
+ "temperature": 0.5
216
+ },
217
+ // Agent 配置
218
+ "agent": {
219
+ "maxIterations": 200
220
+ }
221
+ }`;
222
+ fsSync.writeFileSync(configFilePath, configContent, "utf-8");
223
+
224
+ const output = new OutputService();
225
+ const envService = new EnvironmentService(output);
226
+
227
+ try {
228
+ await envService.create({
229
+ configPath: configFilePath,
230
+ sessionStorage: { type: "memory" } // 使用内存存储,避免污染正式数据库
231
+ });
232
+ const env = envService.getEnvironment();
233
+
234
+ const configComponent = env!.getComponent("config");
235
+ const configService = new ConfigService(configComponent as any, output);
236
+
237
+ // 验证 JSONC 注释被正确解析
238
+ const llmConfig = configService.getComponentConfig("llm");
239
+ expect(llmConfig.defaultModel).toBe("gpt-4-turbo");
240
+ expect(llmConfig.temperature).toBe(0.5);
241
+
242
+ const agentConfig = configService.getComponentConfig("agent");
243
+ expect(agentConfig.maxIterations).toBe(200);
244
+ } finally {
245
+ await envService.dispose();
246
+ }
247
+ });
248
+
249
+ it("should export to file correctly with all components", async () => {
250
+ // 创建包含完整配置的 config.jsonc
251
+ const configContent = `{
252
+ "llm": {
253
+ "defaultModel": "gpt-4o",
254
+ "defaultProvider": "openai"
255
+ },
256
+ "agent": {
257
+ "maxIterations": 100
258
+ }
259
+ }`;
260
+ fsSync.writeFileSync(configFilePath, configContent, "utf-8");
261
+
262
+ const output = new OutputService();
263
+ const envService = new EnvironmentService(output);
264
+
265
+ const exportFilePath = path.join(tempDir, "exported-config.json");
266
+
267
+ try {
268
+ await envService.create({
269
+ configPath: configFilePath,
270
+ sessionStorage: { type: "memory" } // 使用内存存储,避免污染正式数据库
271
+ });
272
+ const env = envService.getEnvironment();
273
+
274
+ const configComponent = env!.getComponent("config");
275
+ const configService = new ConfigService(configComponent as any, output);
276
+
277
+ // 导出 llm 配置到文件
278
+ await configService.exportToFile("llm", { filePath: exportFilePath });
279
+
280
+ // 读取导出的文件
281
+ const exportedContent = fsSync.readFileSync(exportFilePath, "utf-8");
282
+ const exportedData = JSON.parse(exportedContent);
283
+
284
+ // 验证导出内容
285
+ expect(exportedData.llm).toBeDefined();
286
+ expect(exportedData.llm.defaultModel).toBe("gpt-4o");
287
+ expect(exportedData.llm.defaultProvider).toBe("openai");
288
+ } finally {
289
+ await envService.dispose();
290
+ // 清理导出文件
291
+ if (fsSync.existsSync(exportFilePath)) {
292
+ fsSync.unlinkSync(exportFilePath);
293
+ }
294
+ }
295
+ });
296
+ });
297
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @fileoverview config 子命令类型定义
3
+ */
4
+
5
+ /**
6
+ * 所有支持的 Components 列表
7
+ */
8
+ export const SUPPORTED_COMPONENTS = [
9
+ 'tool',
10
+ 'task',
11
+ 'memory',
12
+ 'session',
13
+ 'skill',
14
+ 'commands',
15
+ 'eventsource',
16
+ 'mcp',
17
+ 'agent',
18
+ 'log-trace',
19
+ 'lsp',
20
+ ] as const;
21
+
22
+ export type SupportedComponent = typeof SUPPORTED_COMPONENTS[number];
23
+
24
+ /**
25
+ * Component 别名映射(用户输入 -> 内部名称)
26
+ */
27
+ export const COMPONENT_ALIASES: Record<string, string> = {
28
+ session: 'session',
29
+ sessions: 'session',
30
+ };
31
+
32
+ /**
33
+ * 获取组件的内部名称(处理别名)
34
+ */
35
+ export function resolveComponentName(name: string): string {
36
+ return COMPONENT_ALIASES[name] ?? name;
37
+ }
38
+
39
+ /**
40
+ * Component 描述映射
41
+ */
42
+ export const COMPONENT_DESCRIPTIONS: Record<string, string> = {
43
+ tool: 'Tool Component - 工具管理',
44
+ task: 'Task Component - 任务管理',
45
+ memory: 'Memory Component - 记忆管理',
46
+ session: 'Session Component - 会话管理',
47
+ skill: 'Skill Component - 技能管理',
48
+ commands: 'Commands Component - 命令管理',
49
+ eventsource: 'EventSource Component - 事件源管理',
50
+ mcp: 'MCP Component - MCP 服务器管理',
51
+ agent: 'Agent Component - Agent 配置和循环控制',
52
+ 'log-trace': 'LogTrace Component - 日志和追踪管理',
53
+ lsp: 'LSP Component - 语言服务器协议管理',
54
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @fileoverview Debug Commands Entry
3
+ *
4
+ * Command: roy debug [log|trace|span]
5
+ *
6
+ * 提供调试能力:
7
+ * - log: 查看日志文件(支持 --tail -n)
8
+ * - trace: 以 tree 方式查看 trace 的 spans
9
+ * - span: 通过 spanId 查询详细的 span 信息
10
+ */
11
+
12
+ import type { CommandModule } from "yargs";
13
+ import { LogCommand } from "./log";
14
+ import { TraceCommand } from "./trace";
15
+ import { SpanCommand } from "./span";
16
+
17
+ /**
18
+ * Debug Command
19
+ */
20
+ export const DebugCommand: CommandModule = {
21
+ command: "debug",
22
+ describe: "调试工具 - 日志查看、trace 分析、span 查询",
23
+ builder: (yargs) =>
24
+ yargs
25
+ .command(LogCommand)
26
+ .command(TraceCommand)
27
+ .command(SpanCommand)
28
+ .demandCommand()
29
+ .help(),
30
+ handler: () => {
31
+ // Default handler - will show help
32
+ },
33
+ };
34
+
35
+ // Export subcommands for testing
36
+ export { LogCommand } from "./log";
37
+ export { TraceCommand } from "./trace";
38
+ export { SpanCommand } from "./span";