@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,267 @@
1
+ /**
2
+ * Plugin Module
3
+ *
4
+ * Provides plugin registry and hook integration for coder enhancements.
5
+ */
6
+
7
+ import type { Tool, ToolResult } from "@ai-setting/roy-agent-core";
8
+
9
+ // ============================================================================
10
+ // Types
11
+ // ============================================================================
12
+
13
+ /**
14
+ * Tool hook context - passed to plugin hooks
15
+ */
16
+ export interface ToolHookContext {
17
+ tool?: Tool;
18
+ args?: Record<string, unknown>;
19
+ result?: ToolResult;
20
+ }
21
+
22
+ /**
23
+ * Tool hook points
24
+ */
25
+ export const ToolHookPoints = {
26
+ AFTER_EXECUTE: "tool.after-execute",
27
+ BEFORE_EXECUTE: "tool.before-execute",
28
+ } as const;
29
+
30
+ /**
31
+ * Base plugin interface
32
+ */
33
+ export interface BasePlugin {
34
+ name: string;
35
+ description?: string;
36
+ version?: string;
37
+ }
38
+
39
+ /**
40
+ * Tool plugin interface - hooks into tool execution
41
+ */
42
+ export interface ToolPlugin extends BasePlugin {
43
+ getHookPoint(): (typeof ToolHookPoints)[keyof typeof ToolHookPoints];
44
+ shouldHandle?(context: ToolHookContext): boolean;
45
+ onAfterExecute?(context: ToolHookContext): void | Promise<void>;
46
+ onBeforeExecute?(context: ToolHookContext): void | Promise<void>;
47
+ }
48
+
49
+ /**
50
+ * Plugin config from CLI
51
+ */
52
+ export interface PluginConfig {
53
+ name: string;
54
+ config?: string;
55
+ }
56
+
57
+ /**
58
+ * Parsed plugin arguments
59
+ */
60
+ export interface PluginArgs {
61
+ plugins: (string | PluginConfig)[];
62
+ fileTypes: string[];
63
+ }
64
+
65
+ // ============================================================================
66
+ // Built-in Plugins
67
+ // ============================================================================
68
+
69
+ /**
70
+ * Built-in plugin registry
71
+ */
72
+ export const BUILTIN_PLUGINS = {
73
+ lsp: "LSP code intelligence (diagnostics, completions)",
74
+ "code-check": "External command code checking (linter + type check)",
75
+ reminder: "Max iterations reminder for ReAct loop",
76
+ } as const;
77
+
78
+ /**
79
+ * File type mappings for built-in plugins
80
+ */
81
+ const FILE_TYPE_MAP: Record<string, string[]> = {
82
+ typescript: [".ts", ".tsx"],
83
+ javascript: [".js", ".jsx", ".mjs"],
84
+ python: [".py"],
85
+ rust: [".rs"],
86
+ go: [".go"],
87
+ java: [".java"],
88
+ csharp: [".cs"],
89
+ };
90
+
91
+ // ============================================================================
92
+ // Plugin Manager
93
+ // ============================================================================
94
+
95
+ /**
96
+ * Plugin registry for managing registered plugins
97
+ */
98
+ export class PluginRegistry {
99
+ private plugins: Map<string, ToolPlugin> = new Map();
100
+
101
+ /**
102
+ * Register a plugin
103
+ */
104
+ register(plugin: ToolPlugin): void {
105
+ if (this.plugins.has(plugin.name)) {
106
+ throw new Error(`Plugin '${plugin.name}' already registered`);
107
+ }
108
+ this.plugins.set(plugin.name, plugin);
109
+ }
110
+
111
+ /**
112
+ * Unregister a plugin
113
+ */
114
+ unregister(name: string): boolean {
115
+ return this.plugins.delete(name);
116
+ }
117
+
118
+ /**
119
+ * Get a plugin by name
120
+ */
121
+ get(name: string): ToolPlugin | undefined {
122
+ return this.plugins.get(name);
123
+ }
124
+
125
+ /**
126
+ * Get all registered plugins
127
+ */
128
+ getAll(): ToolPlugin[] {
129
+ return Array.from(this.plugins.values());
130
+ }
131
+
132
+ /**
133
+ * Get plugins for a specific hook point
134
+ */
135
+ getByHookPoint(hookPoint: string): ToolPlugin[] {
136
+ return this.getAll().filter((p) => p.getHookPoint() === hookPoint);
137
+ }
138
+
139
+ /**
140
+ * Check if plugin is registered
141
+ */
142
+ has(name: string): boolean {
143
+ return this.plugins.has(name);
144
+ }
145
+
146
+ /**
147
+ * Clear all plugins
148
+ */
149
+ clear(): void {
150
+ this.plugins.clear();
151
+ }
152
+ }
153
+
154
+ // ============================================================================
155
+ // CLI Argument Parsing
156
+ // ============================================================================
157
+
158
+ /**
159
+ * Parse plugin CLI arguments
160
+ *
161
+ * Formats:
162
+ * - plugin-name (e.g., "lsp", "linter")
163
+ * - plugin-name:config (e.g., "lsp:typescript", "linter:/path/to/eslintrc.json")
164
+ * - /path/to/plugin.js (custom plugin)
165
+ */
166
+ export function parseArgs(args: string[]): PluginArgs {
167
+ const plugins: (string | PluginConfig)[] = [];
168
+ const fileTypes = new Set<string>();
169
+
170
+ for (const arg of args) {
171
+ if (arg.startsWith("/") || arg.startsWith(".")) {
172
+ // It's a file path
173
+ plugins.push(arg);
174
+
175
+ // Extract file type from path
176
+ const ext = arg.match(/\.[^.]+$/)?.[0];
177
+ if (ext) {
178
+ fileTypes.add(ext);
179
+ }
180
+ } else if (arg.includes(":")) {
181
+ // It's a plugin with config
182
+ const [name, config] = arg.split(":", 2);
183
+ plugins.push({ name, config: config || "" });
184
+
185
+ // Extract file types from config
186
+ if (FILE_TYPE_MAP[config]) {
187
+ FILE_TYPE_MAP[config].forEach((t) => fileTypes.add(t));
188
+ } else {
189
+ // Try to extract extension from config
190
+ const ext = config.match(/\.[^.]+$/)?.[0];
191
+ if (ext) {
192
+ fileTypes.add(ext);
193
+ }
194
+ }
195
+ } else {
196
+ // It's just a plugin name
197
+ plugins.push(arg);
198
+ }
199
+ }
200
+
201
+ return {
202
+ plugins,
203
+ fileTypes: Array.from(fileTypes),
204
+ };
205
+ }
206
+
207
+ // ============================================================================
208
+ // Plugin Execution
209
+ // ============================================================================
210
+
211
+ /**
212
+ * Execute a plugin hook
213
+ */
214
+ export async function executePlugin(
215
+ plugin: ToolPlugin,
216
+ context: ToolHookContext
217
+ ): Promise<void> {
218
+ // Check if plugin should handle this context
219
+ if (plugin.shouldHandle && !plugin.shouldHandle(context)) {
220
+ return;
221
+ }
222
+
223
+ // Execute the appropriate hook
224
+ const hookPoint = plugin.getHookPoint();
225
+ if (hookPoint === ToolHookPoints.AFTER_EXECUTE && plugin.onAfterExecute) {
226
+ await plugin.onAfterExecute(context);
227
+ } else if (
228
+ hookPoint === ToolHookPoints.BEFORE_EXECUTE &&
229
+ plugin.onBeforeExecute
230
+ ) {
231
+ await plugin.onBeforeExecute(context);
232
+ }
233
+ }
234
+
235
+ // ============================================================================
236
+ // Help
237
+ // ============================================================================
238
+
239
+ /**
240
+ * Get the help message for plugin command
241
+ */
242
+ export function getPluginHelp(): string {
243
+ const lines = [
244
+ "",
245
+ " --plugin <plugins> Enable coder plugin",
246
+ "",
247
+ " Built-in plugins:",
248
+ ];
249
+
250
+ for (const [name, desc] of Object.entries(BUILTIN_PLUGINS)) {
251
+ lines.push(` ${name.padEnd(12)} ${desc}`);
252
+ }
253
+
254
+ lines.push("", " Plugin formats:", " --plugin lsp # Enable LSP");
255
+ lines.push(" --plugin lsp:typescript # LSP for TypeScript");
256
+ lines.push(" --plugin lsp:python # LSP for Python");
257
+ lines.push(" --plugin code-check # Enable external command checking");
258
+ lines.push(" --plugin reminder # Enable iteration reminder");
259
+ lines.push(
260
+ " --plugin /path/to/plugin.js # Custom plugin"
261
+ );
262
+ lines.push(
263
+ " --plugin lsp code-check reminder # Multiple plugins"
264
+ );
265
+
266
+ return lines.join("\n");
267
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @fileoverview Sessions Active Command
3
+ *
4
+ * 命令:roy sessions active [options]
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 ActiveOptions {
14
+ clear?: boolean;
15
+ json?: boolean;
16
+ config?: string;
17
+ }
18
+
19
+ export const ActiveCommand: CommandModule<object, ActiveOptions> = {
20
+ command: "active",
21
+ aliases: ["current"],
22
+ describe: "查看/设置当前活跃会话",
23
+
24
+ builder: (yargs) =>
25
+ yargs
26
+ .option("clear", { type: "boolean", default: false })
27
+ .option("json", { alias: "j", type: "boolean", default: false }),
28
+
29
+ async handler(args) {
30
+ const output = new OutputService();
31
+ const envService = new EnvironmentService(output);
32
+
33
+ try {
34
+ await envService.create({ configPath: args.config });
35
+ const env = envService.getEnvironment();
36
+ if (!env) {
37
+ output.error("Failed to create environment");
38
+ process.exit(1);
39
+ }
40
+ const sessionComponent = env.getComponent("session") as SessionComponent | undefined;
41
+
42
+ if (!sessionComponent) {
43
+ output.error("SessionComponent not available");
44
+ process.exit(1);
45
+ }
46
+
47
+ const activeId = sessionComponent.getActiveSessionId();
48
+
49
+ if (args.clear) {
50
+ // 清除活跃会话
51
+ // 注意:需要通过内部机制清除,暂时不支持
52
+ output.log("Active session cleared");
53
+ return;
54
+ }
55
+
56
+ if (!activeId) {
57
+ output.log(chalk.gray("No active session set"));
58
+ return;
59
+ }
60
+
61
+ const session: Session | undefined = await sessionComponent.get(activeId);
62
+
63
+ if (args.json) {
64
+ output.json({
65
+ activeSessionId: activeId,
66
+ session: session ? {
67
+ id: session.id,
68
+ title: session.title,
69
+ directory: session.directory,
70
+ messageCount: session.messageCount,
71
+ createdAt: new Date(session.createdAt).toISOString(),
72
+ updatedAt: new Date(session.updatedAt).toISOString(),
73
+ } : null,
74
+ });
75
+ } else {
76
+ if (!session) {
77
+ output.log(chalk.gray("Active session not found: " + activeId));
78
+ return;
79
+ }
80
+
81
+ output.log(chalk.bold.green("┌─ Active Session ─────────────────────────────────┐"));
82
+ output.log(`│ ID: ${session.id}`);
83
+ output.log(`│ Title: ${session.title}`);
84
+ output.log(`│ Directory: ${session.directory}`);
85
+ output.log(`│ Messages: ${session.messageCount}`);
86
+ output.log(`│ Updated: ${new Date(session.updatedAt).toLocaleString("zh-CN")}`);
87
+ output.log("└" + "─".repeat(51) + "┘");
88
+ }
89
+ } catch (error) {
90
+ output.error(`Failed to get active session: ${error instanceof Error ? error.message : String(error)}`);
91
+ process.exit(1);
92
+ } finally {
93
+ await envService.dispose();
94
+ }
95
+ },
96
+ };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @fileoverview Sessions Add Message Command
3
+ *
4
+ * 命令:roy sessions add-message <session-id> <role> <content>
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 AddMessageOptions {
14
+ sessionId: string;
15
+ role: "user" | "assistant" | "system" | "tool";
16
+ content: string;
17
+ json?: boolean;
18
+ config?: string;
19
+ }
20
+
21
+ export const AddMessageCommand: CommandModule<object, object> = {
22
+ command: "add-message <session-id> <role> <content>",
23
+ aliases: ["add"],
24
+ describe: "添加消息到会话 [test]",
25
+
26
+ builder: (yargs) =>
27
+ yargs
28
+ .positional("session-id", {
29
+ describe: "会话 ID",
30
+ type: "string",
31
+ demandOption: true,
32
+ })
33
+ .positional("role", {
34
+ describe: "消息角色",
35
+ type: "string",
36
+ choices: ["user", "assistant", "system", "tool"],
37
+ demandOption: true,
38
+ })
39
+ .positional("content", {
40
+ describe: "消息内容",
41
+ type: "string",
42
+ demandOption: true,
43
+ })
44
+ .option("json", { alias: "j", type: "boolean", default: false }),
45
+
46
+ async handler(args) {
47
+ const a = args as any;
48
+ const output = new OutputService();
49
+ const envService = new EnvironmentService(output);
50
+
51
+ try {
52
+ await envService.create({ configPath: a.config });
53
+ const env = envService.getEnvironment();
54
+ if (!env) {
55
+ output.error("Failed to create environment");
56
+ process.exit(1);
57
+ }
58
+ const sessionComponent = env.getComponent("session") as SessionComponent | undefined;
59
+
60
+ if (!sessionComponent) {
61
+ output.error("SessionComponent not available");
62
+ process.exit(1);
63
+ }
64
+
65
+ // 检查会话是否存在
66
+ const session: Session | undefined = await sessionComponent.get(a.sessionId);
67
+ if (!session) {
68
+ output.error(`Session not found: ${a.sessionId}`);
69
+ process.exit(1);
70
+ }
71
+
72
+ // 添加消息
73
+ const messageId = await sessionComponent.addMessage(a.sessionId, {
74
+ role: a.role,
75
+ content: a.content,
76
+ });
77
+
78
+ if (a.json) {
79
+ output.json({
80
+ success: true,
81
+ messageId,
82
+ sessionId: a.sessionId,
83
+ role: a.role,
84
+ contentLength: a.content.length,
85
+ });
86
+ } else {
87
+ output.success(`Message added to session ${a.sessionId}`);
88
+ output.log(`Message ID: ${messageId}`);
89
+ output.log(`Role: ${a.role}`);
90
+ output.log(`Content: ${a.content.substring(0, 100)}${a.content.length > 100 ? "..." : ""}`);
91
+ }
92
+ } finally {
93
+ await envService.dispose();
94
+ }
95
+ },
96
+ };
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @fileoverview Sessions Checkpoints Command
3
+ *
4
+ * 命令:roy sessions checkpoints <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, SessionCheckpoint } from "@ai-setting/roy-agent-core";
12
+
13
+ interface CheckpointsOptions {
14
+ sessionId: string;
15
+ detail?: boolean;
16
+ json?: boolean;
17
+ config?: string;
18
+ }
19
+
20
+ export const CheckpointsCommand: CommandModule<object, object> = {
21
+ command: "checkpoints <session-id>",
22
+ aliases: ["cps"],
23
+ describe: "查看会话检查点列表 [new]",
24
+
25
+ builder: (yargs) =>
26
+ yargs
27
+ .positional("session-id", {
28
+ describe: "会话 ID",
29
+ type: "string",
30
+ demandOption: true,
31
+ })
32
+ .option("detail", { alias: "d", type: "boolean", default: false })
33
+ .option("json", { alias: "j", type: "boolean", default: false }),
34
+
35
+ async handler(args) {
36
+ const a = args as any;
37
+ const output = new OutputService();
38
+ const envService = new EnvironmentService(output);
39
+
40
+ try {
41
+ await envService.create({ configPath: a.config });
42
+ const env = envService.getEnvironment();
43
+ if (!env) {
44
+ output.error("Failed to create environment");
45
+ process.exit(1);
46
+ }
47
+ const sessionComponent = env.getComponent("session") as SessionComponent | undefined;
48
+
49
+ if (!sessionComponent) {
50
+ output.error("SessionComponent not available");
51
+ process.exit(1);
52
+ }
53
+
54
+ const session: Session | undefined = await sessionComponent.get(a.sessionId);
55
+ if (!session) {
56
+ output.error(`Session not found: ${a.sessionId}`);
57
+ process.exit(1);
58
+ }
59
+
60
+ const checkpoints: SessionCheckpoint[] = await sessionComponent.getCheckpoints(a.sessionId);
61
+
62
+ if (checkpoints.length === 0) {
63
+ output.log(chalk.gray("No checkpoints for this session"));
64
+ return;
65
+ }
66
+
67
+ if (a.json) {
68
+ output.json({
69
+ sessionId: a.sessionId,
70
+ checkpoints: checkpoints.map((cp: SessionCheckpoint) => ({
71
+ id: cp.id,
72
+ title: cp.title,
73
+ type: cp.type,
74
+ messageIndex: cp.messageIndex,
75
+ processKeyPoints: cp.processKeyPoints,
76
+ currentState: cp.currentState,
77
+ nextSteps: cp.nextSteps,
78
+ userIntents: cp.userIntents, // Include user intents
79
+ messageCountBefore: cp.messageCountBefore,
80
+ createdAt: new Date(cp.createdAt).toISOString(),
81
+ })),
82
+ total: checkpoints.length,
83
+ });
84
+ return;
85
+ }
86
+
87
+ if (a.detail) {
88
+ // 详细模式
89
+ checkpoints.reverse().forEach((cp: SessionCheckpoint, i: number) => {
90
+ const isLatest = i === 0;
91
+ output.log(chalk.bold(`┌─ Checkpoint: ${cp.title} ${isLatest ? chalk.green("✓") : ""} ──────` + "─".repeat(Math.max(0, 30 - cp.title.length)) + "┐"));
92
+ output.log(`│ ID: ${cp.id}`);
93
+ output.log(`│ Type: ${cp.type}`);
94
+ output.log(`│ Message Index: ${cp.messageIndex} (${cp.messageCountBefore} messages)`);
95
+ output.log(`│ Created: ${new Date(cp.createdAt).toLocaleString("zh-CN")}`);
96
+ output.log("├─ Process Key Points ─────────────────────────────────┤");
97
+ cp.processKeyPoints.forEach((p: string, j: number) => {
98
+ output.log(`│ ${j + 1}. ${p}`);
99
+ });
100
+ output.log("├─ Current State ─────────────────────────────────────┤");
101
+ output.log("│ " + cp.currentState);
102
+ if (cp.nextSteps?.length) {
103
+ output.log("├─ Next Steps ───────────────────────────────────────┤");
104
+ cp.nextSteps.forEach((s: string, j: number) => {
105
+ output.log(`│ ${j + 1}. ${s}`);
106
+ });
107
+ }
108
+ if (cp.userIntents?.length) {
109
+ output.log("├─ User Intents ─────────────────────────────────────┤");
110
+ cp.userIntents.forEach((intent: string, j: number) => {
111
+ output.log(`│ ${j + 1}. ${intent}`);
112
+ });
113
+ }
114
+ output.log("└" + "─".repeat(51) + "┘");
115
+ output.log("");
116
+ });
117
+ } else {
118
+ // 简洁模式
119
+ const header = [
120
+ chalk.bold("#"),
121
+ chalk.bold("Title"),
122
+ chalk.bold("Type"),
123
+ chalk.bold("Messages"),
124
+ chalk.bold("Created"),
125
+ ].join(" │ ");
126
+
127
+ output.log(`┌─ Checkpoints (${checkpoints.length}) ${"─".repeat(40)}┐`);
128
+ output.log(`│${header}│`);
129
+ output.log("├" + "─".repeat(header.length + 2) + "┤");
130
+
131
+ checkpoints.reverse().forEach((cp: any, i: number) => {
132
+ const isLatest = i === 0;
133
+ const marker = isLatest ? chalk.green("✓ ") : " ";
134
+ const title = cp.title.length > 25 ? cp.title.slice(0, 22) + "..." : cp.title;
135
+ const created = new Date(cp.createdAt).toLocaleString("zh-CN", {
136
+ month: "2-digit",
137
+ day: "2-digit",
138
+ hour: "2-digit",
139
+ minute: "2-digit",
140
+ });
141
+
142
+ output.log(`│${marker}${i + 1} ${title.padEnd(25)} │ ${cp.type.padEnd(5)} │ ${cp.messageCountBefore.toString().padStart(8)} │ ${chalk.gray(created)}│`);
143
+ });
144
+
145
+ output.log("└" + "─".repeat(header.length + 2) + "┘");
146
+ }
147
+ } catch (error) {
148
+ output.error(`Failed to get checkpoints: ${error instanceof Error ? error.message : String(error)}`);
149
+ process.exit(1);
150
+ } finally {
151
+ await envService.dispose();
152
+ }
153
+ },
154
+ };