@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,215 @@
1
+ /**
2
+ * @fileoverview Compact Command 测试
3
+ *
4
+ * TDD 规范:验证 compact 命令的 hint 自动生成逻辑
5
+ *
6
+ * 测试场景:
7
+ * 1. 当未指定 --scenario 参数时,自动调用 generateCompactHint
8
+ * 2. 当指定 --scenario 参数时,直接使用传入的 scenario
9
+ * 3. 输出正确的 hint 信息
10
+ */
11
+
12
+ import { describe, expect, test, jest, beforeEach } from "bun:test";
13
+ import type { CompactResult, SessionCheckpoint } from "@ai-setting/roy-agent-core";
14
+
15
+ // Mock Session 类型
16
+ interface MockSession {
17
+ id: string;
18
+ title: string;
19
+ createdAt: number;
20
+ updatedAt: number;
21
+ messageCount: number;
22
+ metadata: Record<string, unknown>;
23
+ }
24
+
25
+ const createMockCheckpoint = (overrides?: Partial<SessionCheckpoint>): SessionCheckpoint => ({
26
+ id: "cp_123",
27
+ messageIndex: 0,
28
+ title: "Test Checkpoint",
29
+ summary: "Test summary",
30
+ processKeyPoints: ["Point 1", "Point 2"],
31
+ currentState: "Current state",
32
+ nextSteps: ["Next step 1"],
33
+ userIntents: [],
34
+ messageCountBefore: 10,
35
+ createdAt: Date.now(),
36
+ type: "compact",
37
+ ...overrides,
38
+ });
39
+
40
+ const createMockCompactResult = (overrides?: Partial<CompactResult>): CompactResult => ({
41
+ checkpoint: createMockCheckpoint(),
42
+ deletedMessageCount: 10,
43
+ remainingMessageCount: 2,
44
+ checkpointCount: 1,
45
+ ...overrides,
46
+ });
47
+
48
+ // 直接从 summary-agent 导入函数进行测试
49
+ import { parseCompactHintResponse } from "../../../../core/src/env/agent/summary-agent";
50
+
51
+ describe("CompactCommand hint 处理逻辑", () => {
52
+
53
+ describe("两阶段 hint 生成逻辑", () => {
54
+
55
+ test("未指定 scenario 时应调用 generateCompactHint", async () => {
56
+ // Arrange: 模拟 mock
57
+ const mockGenerateCompactHint = jest.fn<() => Promise<string>>()
58
+ .mockResolvedValue("Feature development. User is implementing auth module. Focus on: auth flow decisions.");
59
+
60
+ // Simulate the two-phase logic
61
+ let scenarioHint = ""; // a.scenario is undefined
62
+
63
+ if (!scenarioHint) {
64
+ // 模拟 CLI 两阶段逻辑
65
+ scenarioHint = await mockGenerateCompactHint();
66
+ }
67
+
68
+ // Assert: generateCompactHint should be called
69
+ expect(mockGenerateCompactHint).toHaveBeenCalled();
70
+ expect(scenarioHint).toBe("Feature development. User is implementing auth module. Focus on: auth flow decisions.");
71
+ });
72
+
73
+ test("指定 scenario 时不应调用 generateCompactHint", async () => {
74
+ // Arrange: 模拟 mock
75
+ const mockGenerateCompactHint = jest.fn<() => Promise<string>>()
76
+ .mockResolvedValue("Should not be called");
77
+
78
+ // Simulate the two-phase logic with provided scenario
79
+ const providedScenario = "Bug investigation. User is fixing login bug.";
80
+ let scenarioHint = providedScenario; // a.scenario is provided
81
+
82
+ if (!scenarioHint) {
83
+ scenarioHint = await mockGenerateCompactHint();
84
+ }
85
+
86
+ // Assert: generateCompactHint should NOT be called
87
+ expect(mockGenerateCompactHint).not.toHaveBeenCalled();
88
+ expect(scenarioHint).toBe(providedScenario);
89
+ });
90
+
91
+ test("generateCompactHint 失败时应继续执行", async () => {
92
+ // Arrange: 模拟会失败的 mock
93
+ const mockGenerateCompactHint = jest.fn<() => Promise<string>>()
94
+ .mockRejectedValue(new Error("LLM failed"));
95
+
96
+ // Simulate the two-phase logic with fallback
97
+ let scenarioHint = ""; // a.scenario is undefined
98
+
99
+ if (!scenarioHint) {
100
+ try {
101
+ scenarioHint = await mockGenerateCompactHint();
102
+ } catch (hintError) {
103
+ // 失败时继续使用空提示
104
+ scenarioHint = "";
105
+ }
106
+ }
107
+
108
+ // Assert: should continue with empty hint
109
+ expect(mockGenerateCompactHint).toHaveBeenCalled();
110
+ expect(scenarioHint).toBe("");
111
+ });
112
+
113
+ });
114
+
115
+ describe("compact 调用参数验证", () => {
116
+
117
+ test("应将生成的 scenarioHint 传递给 compact", async () => {
118
+ // Arrange
119
+ const generatedHint = "Feature development. User is implementing auth module.";
120
+ const mockCompact = jest.fn<() => Promise<CompactResult>>().mockResolvedValue(
121
+ createMockCompactResult()
122
+ );
123
+
124
+ // Act: 模拟 compact 调用
125
+ await mockCompact("session_1", {
126
+ scenarioHint: generatedHint,
127
+ });
128
+
129
+ // Assert
130
+ expect(mockCompact).toHaveBeenCalledWith("session_1", {
131
+ scenarioHint: generatedHint,
132
+ });
133
+ });
134
+
135
+ test("当 scenarioHint 为空时也应传递空字符串", async () => {
136
+ // Arrange
137
+ const mockCompact = jest.fn<() => Promise<CompactResult>>().mockResolvedValue(
138
+ createMockCompactResult()
139
+ );
140
+
141
+ // Act: 模拟 compact 调用(无 scenario)
142
+ await mockCompact("session_1", {
143
+ scenarioHint: "",
144
+ });
145
+
146
+ // Assert
147
+ expect(mockCompact).toHaveBeenCalledWith("session_1", {
148
+ scenarioHint: "",
149
+ });
150
+ });
151
+
152
+ });
153
+
154
+ describe("parseCompactHintResponse 行为验证", () => {
155
+
156
+ test("应直接返回 LLM 输出", () => {
157
+ const llmOutput = "Feature development. User is implementing auth module. Focus on: auth flow decisions.";
158
+ const result = parseCompactHintResponse(llmOutput);
159
+
160
+ expect(result).toBe(llmOutput);
161
+ });
162
+
163
+ test("应处理空内容", () => {
164
+ const result = parseCompactHintResponse("");
165
+ expect(result).toBe("[Hint extraction failed]");
166
+ });
167
+
168
+ test("应限制长度", () => {
169
+ const longText = "a".repeat(600);
170
+ const result = parseCompactHintResponse(longText);
171
+
172
+ expect(result.length).toBe(503); // 500 + "..."
173
+ expect(result.endsWith("...")).toBe(true);
174
+ });
175
+
176
+ test("应处理带首尾空白的输入", () => {
177
+ const input = " Feature development. User is implementing auth module. ";
178
+ const result = parseCompactHintResponse(input);
179
+
180
+ expect(result).toBe("Feature development. User is implementing auth module.");
181
+ });
182
+
183
+ });
184
+
185
+ describe("Hint 输出格式验证", () => {
186
+
187
+ test("Hint 应该包含工作类型关键词", () => {
188
+ const hints = [
189
+ "Feature development. User is implementing auth module.",
190
+ "Bug investigation. User is fixing login bug.",
191
+ "Refactoring. User is migrating to new architecture.",
192
+ ];
193
+
194
+ hints.forEach(hint => {
195
+ const result = parseCompactHintResponse(hint);
196
+ expect(result).toMatch(/Feature development|Bug investigation|Refactoring/i);
197
+ });
198
+ });
199
+
200
+ test("Hint 应该包含 Focus on 关键词", () => {
201
+ const hints = [
202
+ "Feature development. Focus on: auth flow decisions.",
203
+ "Bug investigation. Focus on: root cause analysis.",
204
+ "Refactoring. Focus on: pattern decisions.",
205
+ ];
206
+
207
+ hints.forEach(hint => {
208
+ const result = parseCompactHintResponse(hint);
209
+ expect(result).toMatch(/Focus on:/i);
210
+ });
211
+ });
212
+
213
+ });
214
+
215
+ });
@@ -0,0 +1,269 @@
1
+ /**
2
+ * @fileoverview Sessions Compact Command
3
+ *
4
+ * 命令:roy sessions compact <session-id>
5
+ *
6
+ * 压缩会话,生成 checkpoint
7
+ *
8
+ * 特性:
9
+ * 1. 如果未指定 --scenario 参数,自动调用 generateCompactHint 生成场景化提示
10
+ * 2. 输出生成的 hint prompt(用于告知用户生成了什么指导)
11
+ * 3. 输出最终的 checkpoint
12
+ * 4. 复用自动触发 compact 的两阶段逻辑
13
+ */
14
+
15
+ import { CommandModule } from "yargs";
16
+ import { EnvironmentService } from "../../services/environment.service";
17
+ import { OutputService } from "../../services/output.service";
18
+ import chalk from "chalk";
19
+ import type {
20
+ SessionComponent,
21
+ Session,
22
+ LLMComponent,
23
+ PromptComponent,
24
+ } from "@ai-setting/roy-agent-core";
25
+
26
+ interface CompactOptions {
27
+ sessionId: string;
28
+ summary?: string;
29
+ scenario?: string;
30
+ processKeyPoints?: string;
31
+ currentState?: string;
32
+ nextSteps?: string;
33
+ dryRun?: boolean;
34
+ json?: boolean;
35
+ config?: string;
36
+ }
37
+
38
+ export const CompactCommand: CommandModule<object, object> = {
39
+ command: "compact <session-id>",
40
+ aliases: ["compress", "checkpoint"],
41
+ describe: "压缩会话,生成 checkpoint [new]",
42
+
43
+ builder: (yargs) =>
44
+ yargs
45
+ .positional("session-id", {
46
+ describe: "会话 ID",
47
+ type: "string",
48
+ demandOption: true,
49
+ })
50
+ .option("summary", { alias: "s", type: "string", description: "摘要提示" })
51
+ .option("scenario", {
52
+ alias: "S",
53
+ type: "string",
54
+ description: "场景提示(可选)。不指定时自动生成。例如:'Task execution in progress: implementing feature X, current working directory is /path/to/project'"
55
+ })
56
+ .option("process-key-points", { type: "string", description: "过程要点(逗号分隔)" })
57
+ .option("current-state", { type: "string", description: "当前状态" })
58
+ .option("next-steps", { type: "string", description: "后续待跟进(逗号分隔)" })
59
+ .option("dry-run", { alias: "d", type: "boolean", default: false, description: "预览压缩效果" })
60
+ .option("json", { alias: "j", type: "boolean", default: false }),
61
+
62
+ async handler(args) {
63
+ const a = args as any;
64
+ const output = new OutputService();
65
+ const envService = new EnvironmentService(output);
66
+
67
+ try {
68
+ await envService.create({ configPath: a.config });
69
+ const env = envService.getEnvironment();
70
+ if (!env) {
71
+ output.error("Failed to create environment");
72
+ process.exit(1);
73
+ }
74
+ const sessionComponent = env.getComponent("session") as SessionComponent | undefined;
75
+
76
+ if (!sessionComponent) {
77
+ output.error("SessionComponent not available");
78
+ process.exit(1);
79
+ }
80
+
81
+ // 检查会话是否存在
82
+ const session: Session | undefined = await sessionComponent.get(a.sessionId);
83
+ if (!session) {
84
+ output.error(`Session not found: ${a.sessionId}`);
85
+ process.exit(1);
86
+ }
87
+
88
+ // 初始化 SummaryAgent 所需的组件
89
+ const llmComponent = env.getComponent("llm") as LLMComponent | undefined;
90
+ const promptComponent = env.getComponent("prompt") as PromptComponent | undefined;
91
+
92
+ if (llmComponent && promptComponent) {
93
+ sessionComponent.setSummaryComponents(promptComponent, llmComponent);
94
+ } else {
95
+ output.error("LLMComponent or PromptComponent not available. Cannot generate checkpoint.");
96
+ process.exit(1);
97
+ }
98
+
99
+ // Dry run - 预览
100
+ if (a.dryRun) {
101
+ const preview = await sessionComponent.previewCompact(a.sessionId);
102
+
103
+ if (a.json) {
104
+ output.json({
105
+ sessionId: a.sessionId,
106
+ preview: {
107
+ messageCountToCompact: preview.messageCountToCompact,
108
+ estimatedCheckpointTokens: preview.estimatedCheckpointTokens,
109
+ wouldTrigger: preview.wouldTrigger,
110
+ },
111
+ dryRun: true,
112
+ });
113
+ } else {
114
+ output.log(chalk.bold("┌─ Compact Preview ─────────────────────────────────┐"));
115
+ output.log(`│ Session: ${session.title}`);
116
+ output.log(`│ Messages to compact: ${chalk.yellow(preview.messageCountToCompact)}`);
117
+ output.log(`│ Estimated checkpoint tokens: ${preview.estimatedCheckpointTokens}`);
118
+ output.log(`│ Would trigger: ${preview.wouldTrigger ? chalk.green("Yes") : chalk.gray("No")}`);
119
+ output.log("│");
120
+ output.log("│ " + chalk.gray("(Dry run - no changes made)"));
121
+ output.log("└" + "─".repeat(51) + "┘");
122
+ }
123
+ return;
124
+ }
125
+
126
+ // 解析逗号分隔的选项
127
+ const processKeyPoints = a.processKeyPoints?.split(",").map((s: string) => s.trim()).filter(Boolean);
128
+ const nextSteps = a.nextSteps?.split(",").map((s: string) => s.trim()).filter(Boolean);
129
+
130
+ // ================================================================
131
+ // 两阶段逻辑:如果未指定 scenario,自动生成 hint
132
+ // ================================================================
133
+ let scenarioHint = a.scenario || "";
134
+
135
+ if (!scenarioHint) {
136
+ if (!a.json) {
137
+ output.info("Generating scenario hint...");
138
+ }
139
+ try {
140
+ scenarioHint = await sessionComponent.generateCompactHint(a.sessionId);
141
+ if (!a.json) {
142
+ output.log(chalk.bold("├─ Auto-generated Scenario Hint ──────────────────────────┤"));
143
+ output.log("│ " + chalk.cyan(scenarioHint.substring(0, 70)));
144
+ if (scenarioHint.length > 70) {
145
+ output.log(chalk.gray("│ ") + scenarioHint.substring(70, 140));
146
+ }
147
+ }
148
+ } catch (hintError) {
149
+ if (!a.json) {
150
+ output.warn("Failed to generate scenario hint, continuing with empty hint");
151
+ }
152
+ scenarioHint = "";
153
+ }
154
+ } else {
155
+ if (!a.json) {
156
+ output.info(`Using provided scenario: ${a.scenario}`);
157
+ }
158
+ }
159
+
160
+ // ================================================================
161
+ // 执行 compact
162
+ // ================================================================
163
+ if (!a.json) {
164
+ output.info("Generating checkpoint...");
165
+ }
166
+
167
+ const result = await sessionComponent.compact(a.sessionId, {
168
+ summary: a.summary,
169
+ scenarioHint,
170
+ processKeyPoints,
171
+ currentState: a.currentState,
172
+ nextSteps,
173
+ });
174
+
175
+ if (a.json) {
176
+ output.json({
177
+ success: true,
178
+ hint: {
179
+ autoGenerated: !a.scenario,
180
+ value: scenarioHint || null,
181
+ },
182
+ checkpoint: {
183
+ id: result.checkpoint.id,
184
+ title: result.checkpoint.title,
185
+ processKeyPoints: result.checkpoint.processKeyPoints,
186
+ currentState: result.checkpoint.currentState,
187
+ nextSteps: result.checkpoint.nextSteps,
188
+ userIntents: result.checkpoint.userIntents,
189
+ createdAt: new Date(result.checkpoint.createdAt).toISOString(),
190
+ },
191
+ stats: {
192
+ messagesCompacted: result.deletedMessageCount,
193
+ remainingMessages: result.remainingMessageCount,
194
+ totalCheckpoints: result.checkpointCount,
195
+ },
196
+ });
197
+ } else {
198
+ // 成功输出
199
+ output.log(chalk.bold.green("┌─ Checkpoint Created ────────────────────────────────┐"));
200
+ output.log(`│ ID: ${result.checkpoint.id}`);
201
+ output.log(`│ Title: ${result.checkpoint.title}`);
202
+ output.log(`│ Type: compact`);
203
+ output.log(`│ Created: ${new Date(result.checkpoint.createdAt).toLocaleString("zh-CN")}`);
204
+
205
+ // Scenario Hint 部分
206
+ output.log(chalk.bold("├─ Scenario Hint ────────────────────────────────────┤"));
207
+ if (scenarioHint) {
208
+ const hintLines = scenarioHint.split("\n");
209
+ hintLines.forEach((line: string, i: number) => {
210
+ if (i === 0) {
211
+ output.log(chalk.cyan("│ " + line.substring(0, 48)));
212
+ if (line.length > 48) {
213
+ for (let j = 48; j < line.length; j += 48) {
214
+ output.log(chalk.gray("│ ") + line.substring(j, j + 48));
215
+ }
216
+ }
217
+ } else {
218
+ output.log(chalk.gray("│ ") + line);
219
+ }
220
+ });
221
+ } else {
222
+ output.log(chalk.gray("│ (no hint)"));
223
+ }
224
+
225
+ output.log("├─ Process Key Points ─────────────────────────────────┤");
226
+ result.checkpoint.processKeyPoints.forEach((p: string, i: number) => {
227
+ output.log(`│ ${i + 1}. ${p}`);
228
+ });
229
+ output.log("├─ Current State ─────────────────────────────────────┤");
230
+ output.log("│ " + result.checkpoint.currentState);
231
+ if (result.checkpoint.nextSteps?.length) {
232
+ output.log("├─ Next Steps ───────────────────────────────────────┤");
233
+ result.checkpoint.nextSteps.forEach((s: string, i: number) => {
234
+ output.log(`│ ${i + 1}. ${s}`);
235
+ });
236
+ }
237
+ if (result.checkpoint.userIntents?.length) {
238
+ output.log("├─ User Intents ─────────────────────────────────────┤");
239
+ result.checkpoint.userIntents.forEach((intent: string, i: number) => {
240
+ output.log(`│ ${i + 1}. ${intent}`);
241
+ });
242
+ }
243
+ if (result.checkpoint.recentMessages?.length) {
244
+ output.log("├─ Recent Messages (preserved for context) ──────────┤");
245
+ result.checkpoint.recentMessages.forEach((msg) => {
246
+ const prefix = msg.role === 'user' ? '👤' : '🤖';
247
+ const content = msg.content.length > 60
248
+ ? msg.content.substring(0, 60) + '...'
249
+ : msg.content;
250
+ output.log(chalk.gray(`│ ${prefix} [${msg.role}] ${content}`));
251
+ });
252
+ }
253
+ output.log("├─ Stats ─────────────────────────────────────────────┤");
254
+ output.log(`│ Messages compacted: ${chalk.yellow(result.deletedMessageCount)}`);
255
+ output.log(`│ Remaining messages: ${result.remainingMessageCount}`);
256
+ output.log(`│ Total checkpoints: ${result.checkpointCount}`);
257
+ output.log("└" + "─".repeat(51) + "┘");
258
+ }
259
+ } catch (error) {
260
+ output.error(`Failed to compact session: ${error instanceof Error ? error.message : String(error)}`);
261
+ if (process.env.DEBUG) {
262
+ console.error(error);
263
+ }
264
+ process.exit(1);
265
+ } finally {
266
+ await envService.dispose();
267
+ }
268
+ },
269
+ };