@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,248 @@
1
+ /**
2
+ * @fileoverview Sessions Messages Command
3
+ *
4
+ * 命令:roy sessions messages <session-id>
5
+ */
6
+
7
+ import { CommandModule } from "yargs";
8
+ import { EnvironmentService } from "../../services/environment.service";
9
+ import { OutputService } from "../../services/output.service";
10
+ import chalk from "chalk";
11
+ import type { SessionComponent, Session, SessionMessage } from "@ai-setting/roy-agent-core";
12
+
13
+ interface MessagesOptions {
14
+ sessionId: string;
15
+ offset: number;
16
+ limit: number;
17
+ reverse?: boolean;
18
+ json?: boolean;
19
+ config?: string;
20
+ }
21
+
22
+ export const MessagesCommand: CommandModule<object, object> = {
23
+ command: "messages <session-id>",
24
+ aliases: ["msgs"],
25
+ describe: "查看会话消息",
26
+
27
+ builder: (yargs) =>
28
+ yargs
29
+ .positional("session-id", {
30
+ describe: "会话 ID",
31
+ type: "string",
32
+ demandOption: true,
33
+ })
34
+ .option("limit", { alias: "n", type: "number", demandOption: true, describe: "返回消息数量" })
35
+ .option("offset", { alias: "o", type: "number", demandOption: true, describe: "起始偏移量" })
36
+ .option("reverse", { alias: "r", type: "boolean", default: false })
37
+ .option("json", { alias: "j", type: "boolean", default: false }),
38
+
39
+ async handler(args) {
40
+ const a = args as any;
41
+ const output = new OutputService();
42
+ const envService = new EnvironmentService(output);
43
+
44
+ try {
45
+ await envService.create({ configPath: a.config });
46
+ const env = envService.getEnvironment();
47
+ if (!env) {
48
+ output.error("Failed to create environment");
49
+ process.exit(1);
50
+ }
51
+ const sessionComponent = env.getComponent("session") as SessionComponent | undefined;
52
+
53
+ if (!sessionComponent) {
54
+ output.error("SessionComponent not available");
55
+ process.exit(1);
56
+ }
57
+
58
+ const session: Session | undefined = await sessionComponent.get(a.sessionId);
59
+ if (!session) {
60
+ output.error(`Session not found: ${a.sessionId}`);
61
+ process.exit(1);
62
+ }
63
+
64
+ // 并行获取消息和总数
65
+ const [messages, totalMessages] = await Promise.all([
66
+ sessionComponent.getMessages(a.sessionId, {
67
+ offset: a.offset,
68
+ limit: a.limit,
69
+ }),
70
+ sessionComponent.getMessageCount(a.sessionId),
71
+ ]);
72
+
73
+ if (a.reverse) {
74
+ messages.reverse();
75
+ }
76
+
77
+ if (a.json) {
78
+ output.json({
79
+ sessionId: a.sessionId,
80
+ messages: messages.map((m: SessionMessage) => ({
81
+ id: m.id,
82
+ role: m.role,
83
+ content: m.content,
84
+ parts: m.parts, // 包含结构化 parts 信息
85
+ timestamp: new Date(m.timestamp).toISOString(),
86
+ isCheckpoint: m.metadata?.isCheckpoint,
87
+ })),
88
+ total: totalMessages,
89
+ pagination: {
90
+ offset: a.offset,
91
+ limit: a.limit,
92
+ hasMore: a.offset + a.limit < totalMessages,
93
+ },
94
+ });
95
+ } else {
96
+ // 检查点消息样式
97
+ const checkpointStyle = (content: string) => {
98
+ const lines = content.split("\n");
99
+ return lines.slice(0, 10).join("\n") + (lines.length > 10 ? "\n..." : "");
100
+ };
101
+
102
+ const roleColors: Record<string, (text: string) => string> = {
103
+ user: chalk.blue,
104
+ assistant: chalk.green,
105
+ system: chalk.gray,
106
+ tool: chalk.yellow,
107
+ };
108
+
109
+ /**
110
+ * 格式化消息内容,支持 parts 结构化展示
111
+ */
112
+ const formatMessageContent = (m: SessionMessage): string[] => {
113
+ const lines: string[] = [];
114
+
115
+ // 处理 parts(结构化内容)
116
+ if (m.parts && m.parts.length > 0) {
117
+ for (const part of m.parts as any[]) {
118
+ if (part.type === "text") {
119
+ const text = part.content?.length > 300
120
+ ? part.content.slice(0, 297) + "..."
121
+ : (part.content || "");
122
+ lines.push(...text.split("\n").map((l: string) => l || " "));
123
+ } else if (part.type === "tool-call") {
124
+ lines.push(chalk.cyan(`[Tool Call] ${part.toolName}`));
125
+ const argsStr = typeof part.arguments === 'string'
126
+ ? part.arguments
127
+ : JSON.stringify(part.arguments ?? null, null, 2);
128
+ if (argsStr && argsStr.length > 200) {
129
+ lines.push(...argsStr.slice(0, 197).split("\n").map((l: string) => chalk.gray(l)));
130
+ lines.push(chalk.gray("..."));
131
+ } else if (argsStr) {
132
+ lines.push(...argsStr.split("\n").map((l: string) => chalk.gray(l)));
133
+ }
134
+ } else if (part.type === "tool-result") {
135
+ lines.push(chalk.yellow(`[Tool Result] ${part.toolName}`));
136
+ const output = part.output?.length > 200
137
+ ? part.output.slice(0, 197) + "..."
138
+ : (part.output || "No output");
139
+ lines.push(...output.split("\n").map((l: string) => chalk.gray(l)));
140
+ } else if (part.type === "reasoning") {
141
+ lines.push(chalk.magenta(`[Reasoning]`));
142
+ lines.push(...(part.content || "").split("\n").slice(0, 5).map((l: string) => chalk.gray(l)));
143
+ } else if (part.type === "checkpoint") {
144
+ lines.push(chalk.cyan(`[Checkpoint]`));
145
+ }
146
+ // ==================== Workflow Node Types ====================
147
+ else if (part.type === "workflow-node-call") {
148
+ lines.push(chalk.blue(`[Node Call] ${part.nodeType}: ${part.nodeId}`));
149
+ if (part.input && Object.keys(part.input).length > 0) {
150
+ const inputSummary = Object.entries(part.input)
151
+ .slice(0, 3)
152
+ .map(([k, v]) => `${k}: ${JSON.stringify(v).slice(0, 50)}`)
153
+ .join(", ");
154
+ lines.push(...inputSummary.split("\n").map((l: string) => chalk.gray(` ${l}`)));
155
+ }
156
+ if (part.agentSessionId) {
157
+ lines.push(chalk.gray(` agentSessionId: ${part.agentSessionId}`));
158
+ }
159
+ } else if (part.type === "workflow-node-interrupt") {
160
+ lines.push(chalk.yellow(`[Node Interrupt] ${part.nodeType}: ${part.nodeId}`));
161
+ lines.push(chalk.bold(` Query: ${part.query}`));
162
+ if (part.options && part.options.length > 0) {
163
+ lines.push(chalk.gray(` Options: ${part.options.join(", ")}`));
164
+ }
165
+ if (part.agentSessionId) {
166
+ lines.push(chalk.gray(` agentSessionId: ${part.agentSessionId}`));
167
+ }
168
+ } else if (part.type === "workflow-node-result") {
169
+ const status = part.error ? "❌" : "✅";
170
+ lines.push(chalk.green(`${status} [Node Result] ${part.nodeType}: ${part.nodeId}`));
171
+ if (part.output) {
172
+ const outputStr = typeof part.output === 'string'
173
+ ? part.output
174
+ : JSON.stringify(part.output).slice(0, 200);
175
+ lines.push(...outputStr.split("\n").slice(0, 5).map((l: string) => chalk.gray(` ${l}`)));
176
+ }
177
+ if (part.error) {
178
+ lines.push(chalk.red(` Error: ${part.error}`));
179
+ }
180
+ if (part.durationMs !== undefined) {
181
+ lines.push(chalk.gray(` Duration: ${part.durationMs}ms`));
182
+ }
183
+ } else if (part.type === "workflow-node-resume") {
184
+ lines.push(chalk.green(`[Node Resume] Response: ${part.response}`));
185
+ } else if (part.type === "workflow-node-start") {
186
+ lines.push(chalk.blue(`[Node Start] ${part.nodeId}`));
187
+ }
188
+ }
189
+ } else if (m.content) {
190
+ // 纯文本内容(兼容旧格式)
191
+ const content = m.content.length > 200
192
+ ? m.content.slice(0, 197) + "..."
193
+ : m.content;
194
+ lines.push(...content.split("\n").map(l => l || " "));
195
+ }
196
+
197
+ return lines.length > 0 ? lines : ["(empty)"];
198
+ };
199
+
200
+ output.log(chalk.bold(`┌─ Messages (${a.sessionId}) ────────────────────────┐`));
201
+
202
+ messages.forEach((m: SessionMessage, i: number) => {
203
+ const roleColor = roleColors[m.role] || chalk.white;
204
+ const time = new Date(m.timestamp).toLocaleTimeString("zh-CN", {
205
+ hour: "2-digit",
206
+ minute: "2-digit",
207
+ });
208
+
209
+ if (m.metadata?.isCheckpoint) {
210
+ // Checkpoint 消息特殊样式
211
+ output.log(chalk.cyan("├─ #" + (a.offset + i + 1) + " checkpoint") + " " + chalk.gray(time));
212
+ output.log("│");
213
+ output.log("│ " + checkpointStyle(m.content).split("\n").join("\n│ "));
214
+ output.log("│");
215
+ } else {
216
+ output.log(chalk.cyan("├─ #" + (a.offset + i + 1) + " ") + roleColor(m.role.padEnd(10)) + " " + chalk.gray(time));
217
+
218
+ // 使用格式化的内容(支持 parts)
219
+ const contentLines = formatMessageContent(m);
220
+ for (const line of contentLines.slice(0, 20)) { // 最多显示20行
221
+ output.log("│ " + line);
222
+ }
223
+ if (contentLines.length > 20) {
224
+ output.log("│ " + chalk.gray("... (truncated)"));
225
+ }
226
+ output.log("│");
227
+ }
228
+ });
229
+
230
+ output.log("└" + "─".repeat(51) + "┘");
231
+
232
+ // 显示分页信息和总数
233
+ const hasMore = a.offset + a.limit < totalMessages;
234
+ const showingEnd = Math.min(a.offset + a.limit, totalMessages);
235
+
236
+ output.log(chalk.gray(
237
+ `Showing ${a.offset + 1}-${showingEnd} of ${totalMessages} messages` +
238
+ (hasMore ? " (more available)" : "")
239
+ ));
240
+ }
241
+ } catch (error) {
242
+ output.error(`Failed to get messages: ${error instanceof Error ? error.message : String(error)}`);
243
+ process.exit(1);
244
+ } finally {
245
+ await envService.dispose();
246
+ }
247
+ },
248
+ };
@@ -0,0 +1,194 @@
1
+ /**
2
+ * @fileoverview Sessions Mock Command
3
+ *
4
+ * 命令:roy sessions mock <session-id>
5
+ *
6
+ * 插入模拟的交互消息(模拟一个完整的 agent 交互流程)
7
+ */
8
+
9
+ import { CommandModule } from "yargs";
10
+ import { EnvironmentService } from "../../services/environment.service";
11
+ import { OutputService } from "../../services/output.service";
12
+ import chalk from "chalk";
13
+ import type { SessionComponent, Session } from "@ai-setting/roy-agent-core";
14
+
15
+ interface MockOptions {
16
+ sessionId: string;
17
+ count?: number;
18
+ json?: boolean;
19
+ config?: string;
20
+ }
21
+
22
+ // 模拟的消息序列(一个完整的 agent 交互)
23
+ const MOCK_SEQUENCES = [
24
+ // 序列 1: 文件操作
25
+ {
26
+ name: "文件操作流程",
27
+ messages: [
28
+ { role: "user", content: "帮我查看一下当前目录有哪些文件" },
29
+ { role: "assistant", content: "", toolCalls: [{ name: "glob", args: { pattern: "**/*" } }] },
30
+ { role: "tool", content: "找到 5 个文件:\n- README.md\n- package.json\n- src/index.ts\n- src/app.ts\n- tsconfig.json" },
31
+ { role: "assistant", content: "当前目录包含以下文件:\n1. README.md - 项目说明\n2. package.json - 项目配置\n3. src/index.ts - 入口文件\n4. src/app.ts - 应用文件\n5. tsconfig.json - TypeScript 配置" },
32
+ ],
33
+ },
34
+ // 序列 2: 搜索代码
35
+ {
36
+ name: "代码搜索流程",
37
+ messages: [
38
+ { role: "user", content: "搜索一下代码中包含 'TODO' 的地方" },
39
+ { role: "assistant", content: "", toolCalls: [{ name: "grep", args: { pattern: "TODO" } }] },
40
+ { role: "tool", content: "找到 3 处 TODO:\n1. src/index.ts:15 - TODO: 实现错误处理\n2. src/app.ts:42 - TODO: 优化性能\n3. src/utils.ts:8 - TODO: 添加单元测试" },
41
+ { role: "assistant", content: "代码中发现 3 处 TODO 标记:\n\n| 文件 | 行号 | 内容 |\n|------|------|------|\n| src/index.ts | 15 | 实现错误处理 |\n| src/app.ts | 42 | 优化性能 |\n| src/utils.ts | 8 | 添加单元测试 |\n\n需要我帮你处理这些 TODO 吗?" },
42
+ ],
43
+ },
44
+ // 序列 3: 写文件
45
+ {
46
+ name: "写文件流程",
47
+ messages: [
48
+ { role: "user", content: "帮我创建一个新的配置文件 config.yaml" },
49
+ { role: "assistant", content: "", toolCalls: [{ name: "writeFile", args: { path: "config.yaml", content: "# 配置文件\nname: my-app\nversion: 1.0.0\n\ndatabase:\n host: localhost\n port: 5432" } }] },
50
+ { role: "tool", content: "文件 config.yaml 已创建成功" },
51
+ { role: "assistant", content: "已创建配置文件 `config.yaml`,包含:\n- 基本配置(name, version)\n- 数据库配置(host, port)\n\n你可以根据需要修改这些配置。" },
52
+ ],
53
+ },
54
+ // 序列 4: 读取文件
55
+ {
56
+ name: "读取文件流程",
57
+ messages: [
58
+ { role: "user", content: "看看 package.json 的内容" },
59
+ { role: "assistant", content: "", toolCalls: [{ name: "readFile", args: { path: "package.json" } }] },
60
+ { role: "tool", content: '{\n "name": "my-app",\n "version": "1.0.0",\n "scripts": {\n "dev": "bun run src/index.ts",\n "build": "bun build"\n }\n}' },
61
+ { role: "assistant", content: "package.json 内容如下:\n\n```json\n{\n \"name\": \"my-app\",\n \"version\": \"1.0.0\",\n \"scripts\": {\n \"dev\": \"bun run src/index.ts\",\n \"build\": \"bun build\"\n }\n}\n```\n\n这是一个标准的 Node.js 项目配置。" },
62
+ ],
63
+ },
64
+ ];
65
+
66
+ export const MockCommand: CommandModule<object, object> = {
67
+ command: "mock <session-id>",
68
+ describe: "插入模拟消息到会话(用于测试 checkpoint)",
69
+
70
+ builder: (yargs) =>
71
+ yargs
72
+ .positional("session-id", {
73
+ describe: "会话 ID",
74
+ type: "string",
75
+ demandOption: true,
76
+ })
77
+ .option("count", {
78
+ alias: "n",
79
+ type: "number",
80
+ default: 1,
81
+ description: "插入几组模拟消息(1-4)",
82
+ })
83
+ .option("json", { alias: "j", type: "boolean", default: false }),
84
+
85
+ async handler(args) {
86
+ const a = args as any;
87
+ const output = new OutputService();
88
+ const envService = new EnvironmentService(output);
89
+
90
+ try {
91
+ await envService.create({ configPath: a.config });
92
+ const env = envService.getEnvironment();
93
+ if (!env) {
94
+ output.error("Failed to create environment");
95
+ process.exit(1);
96
+ }
97
+ const sessionComponent = env.getComponent("session") as SessionComponent | undefined;
98
+
99
+ if (!sessionComponent) {
100
+ output.error("SessionComponent not available");
101
+ process.exit(1);
102
+ }
103
+
104
+ // 检查会话是否存在
105
+ const session: Session | undefined = await sessionComponent.get(a.sessionId);
106
+ if (!session) {
107
+ output.error(`Session not found: ${a.sessionId}`);
108
+ process.exit(1);
109
+ }
110
+
111
+ const count = Math.min(Math.max(a.count || 1, 1), MOCK_SEQUENCES.length);
112
+ const addedMessages: string[] = [];
113
+
114
+ if (!a.json) {
115
+ output.info(`Inserting ${count} mock sequence(s) into session...`);
116
+ output.log("");
117
+ }
118
+
119
+ // 添加模拟消息
120
+ for (let seqIdx = 0; seqIdx < count; seqIdx++) {
121
+ const sequence = MOCK_SEQUENCES[seqIdx];
122
+
123
+ if (!a.json) {
124
+ output.log(chalk.bold(`\n📝 Sequence ${seqIdx + 1}: ${sequence.name}`));
125
+ }
126
+
127
+ for (const msg of sequence.messages) {
128
+ // 处理 tool call 消息
129
+ if (msg.role === "assistant" && msg.toolCalls) {
130
+ // 添加 assistant 消息(带 tool call)
131
+ const toolCallText = msg.toolCalls
132
+ .map((tc) => `🔧 Calling tool: ${tc.name}\n\`\`\`\n${JSON.stringify(tc.args, null, 2)}\n\`\`\``)
133
+ .join("\n\n");
134
+
135
+ const assistantMsgId = await sessionComponent.addMessage(a.sessionId!, {
136
+ role: "assistant",
137
+ content: toolCallText,
138
+ });
139
+ if (assistantMsgId) addedMessages.push(assistantMsgId);
140
+
141
+ if (!a.json) {
142
+ output.log(chalk.cyan(` [${addedMessages.length}] assistant: 🔧 Tool call`));
143
+ }
144
+
145
+ // 添加 tool result
146
+ const toolMsgId = await sessionComponent.addMessage(a.sessionId!, {
147
+ role: "tool",
148
+ content: msg.content,
149
+ });
150
+ if (toolMsgId) addedMessages.push(toolMsgId);
151
+
152
+ if (!a.json) {
153
+ output.log(chalk.yellow(` [${addedMessages.length}] tool: ${msg.content.substring(0, 50)}...`));
154
+ }
155
+ } else {
156
+ // 普通消息
157
+ const msgId = await sessionComponent.addMessage(a.sessionId!, {
158
+ role: msg.role as any,
159
+ content: msg.content,
160
+ });
161
+ if (msgId) addedMessages.push(msgId);
162
+
163
+ if (!a.json) {
164
+ const roleColor = msg.role === "user" ? chalk.green : msg.role === "assistant" ? chalk.cyan : chalk.gray;
165
+ output.log(roleColor(` [${addedMessages.length}] ${msg.role}: ${msg.content.substring(0, 50)}...`));
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ // 显示结果
172
+ if (a.json) {
173
+ output.json({
174
+ success: true,
175
+ sessionId: a.sessionId,
176
+ sequencesAdded: count,
177
+ messagesAdded: addedMessages.length,
178
+ messageIds: addedMessages,
179
+ });
180
+ } else {
181
+ output.log("\n" + chalk.bold.green("✅ Mock data inserted successfully!"));
182
+ output.log(` Session: ${a.sessionId}`);
183
+ output.log(` Sequences: ${count}`);
184
+ output.log(` Messages: ${addedMessages.length}`);
185
+ output.log("");
186
+ output.log(chalk.gray(" Now run: "));
187
+ output.log(chalk.gray(` roy sessions compact ${a.sessionId}`));
188
+ output.log(chalk.gray(" to trigger checkpoint generation"));
189
+ }
190
+ } finally {
191
+ await envService.dispose();
192
+ }
193
+ },
194
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @fileoverview Sessions New Command
3
+ *
4
+ * 命令:roy sessions new
5
+ */
6
+
7
+ import { CommandModule } from "yargs";
8
+ import { EnvironmentService } from "../../services/environment.service";
9
+ import { OutputService } from "../../services/output.service";
10
+ import chalk from "chalk";
11
+ import type { SessionComponent, Session } from "@ai-setting/roy-agent-core";
12
+
13
+ interface NewOptions {
14
+ title?: string;
15
+ directory?: string;
16
+ active?: boolean;
17
+ json?: boolean;
18
+ config?: string;
19
+ }
20
+
21
+ export const NewCommand: CommandModule<object, NewOptions> = {
22
+ command: "new [options]",
23
+ aliases: ["create"],
24
+ describe: "创建新会话",
25
+
26
+ builder: (yargs) =>
27
+ yargs
28
+ .option("title", { alias: "t", type: "string" })
29
+ .option("directory", { alias: "d", type: "string" })
30
+ .option("active", { alias: "s", type: "boolean", default: false })
31
+ .option("json", { type: "boolean", default: false }),
32
+
33
+ async handler(args) {
34
+ const output = new OutputService();
35
+ const envService = new EnvironmentService(output);
36
+
37
+ try {
38
+ await envService.create({ configPath: args.config });
39
+ const env = envService.getEnvironment();
40
+ if (!env) {
41
+ output.error("Failed to create environment");
42
+ process.exit(1);
43
+ }
44
+ const sessionComponent = env.getComponent("session") as SessionComponent | undefined;
45
+
46
+ if (!sessionComponent) {
47
+ output.error("SessionComponent not available");
48
+ process.exit(1);
49
+ }
50
+
51
+ const session: Session = await sessionComponent.create({
52
+ title: args.title,
53
+ directory: args.directory || process.cwd(),
54
+ });
55
+
56
+ if (args.active) {
57
+ await sessionComponent.setActiveSession(session.id);
58
+ }
59
+
60
+ if (args.json) {
61
+ output.json({
62
+ success: true,
63
+ session: {
64
+ id: session.id,
65
+ title: session.title,
66
+ directory: session.directory,
67
+ createdAt: new Date(session.createdAt).toISOString(),
68
+ },
69
+ });
70
+ } else {
71
+ output.success(chalk.green("✓") + " Session created: " + session.id);
72
+ output.info("Title: " + session.title);
73
+ output.info("Directory: " + session.directory);
74
+ }
75
+ } catch (error) {
76
+ output.error(`Failed to create session: ${error instanceof Error ? error.message : String(error)}`);
77
+ process.exit(1);
78
+ } finally {
79
+ await envService.dispose();
80
+ }
81
+ },
82
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @fileoverview Sessions Rename Command
3
+ *
4
+ * 命令:roy sessions rename <session-id> <new-title>
5
+ */
6
+
7
+ import { CommandModule } from "yargs";
8
+ import { EnvironmentService } from "../../services/environment.service";
9
+ import { OutputService } from "../../services/output.service";
10
+ import chalk from "chalk";
11
+ import type { SessionComponent, Session } from "@ai-setting/roy-agent-core";
12
+
13
+ interface RenameOptions {
14
+ sessionId: string;
15
+ title: string;
16
+ json?: boolean;
17
+ config?: string;
18
+ }
19
+
20
+ export const RenameCommand: CommandModule<object, object> = {
21
+ command: "rename <session-id> <title>",
22
+ aliases: ["mv"],
23
+ describe: "更新会话标题",
24
+
25
+ builder: (yargs) =>
26
+ yargs
27
+ .positional("session-id", {
28
+ describe: "会话 ID",
29
+ type: "string",
30
+ demandOption: true,
31
+ })
32
+ .positional("title", {
33
+ describe: "新标题",
34
+ type: "string",
35
+ demandOption: true,
36
+ })
37
+ .option("json", {
38
+ describe: "JSON 格式输出",
39
+ type: "boolean",
40
+ default: false,
41
+ }),
42
+
43
+ async handler(args) {
44
+ const a = args as any;
45
+ const output = new OutputService();
46
+ const envService = new EnvironmentService(output);
47
+
48
+ try {
49
+ await envService.create({ configPath: a.config });
50
+ const env = envService.getEnvironment();
51
+ if (!env) {
52
+ output.error("Failed to create environment");
53
+ process.exit(1);
54
+ }
55
+ const sessionComponent = env.getComponent("session") as SessionComponent | undefined;
56
+
57
+ if (!sessionComponent) {
58
+ output.error("SessionComponent not available");
59
+ process.exit(1);
60
+ }
61
+
62
+ // 获取会话
63
+ const session: Session | undefined = await sessionComponent.get(a.sessionId);
64
+ if (!session) {
65
+ output.error(`Session not found: ${a.sessionId}`);
66
+ process.exit(1);
67
+ }
68
+
69
+ // 更新标题
70
+ const success = await sessionComponent.update(a.sessionId, {
71
+ title: a.title,
72
+ });
73
+
74
+ if (!success) {
75
+ output.error("Failed to rename session");
76
+ process.exit(1);
77
+ }
78
+
79
+ if (a.json) {
80
+ output.json({
81
+ success: true,
82
+ session: {
83
+ id: a.sessionId,
84
+ title: a.title,
85
+ },
86
+ });
87
+ } else {
88
+ output.success(`Session renamed: ${chalk.green(a.title)}`);
89
+ output.info(`ID: ${a.sessionId}`);
90
+ }
91
+ } catch (error) {
92
+ output.error(`Failed to rename session: ${error instanceof Error ? error.message : String(error)}`);
93
+ process.exit(1);
94
+ } finally {
95
+ await envService.dispose();
96
+ }
97
+ },
98
+ };