@ai-setting/roy-agent-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -0
- package/dist/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
- package/package.json +91 -0
- package/src/bin/roy.ts +12 -0
- package/src/cli.ts +101 -0
- package/src/commands/act.ts +480 -0
- package/src/commands/commands-add.ts +110 -0
- package/src/commands/commands-dirs.ts +70 -0
- package/src/commands/commands-info.ts +90 -0
- package/src/commands/commands-list.ts +161 -0
- package/src/commands/commands-remove.ts +147 -0
- package/src/commands/commands.ts +55 -0
- package/src/commands/config/config-service.test.ts +449 -0
- package/src/commands/config/config-service.ts +312 -0
- package/src/commands/config/deep-merge.test.ts +168 -0
- package/src/commands/config/deep-merge.ts +63 -0
- package/src/commands/config/export.ts +97 -0
- package/src/commands/config/filter-history-e2e.test.ts +141 -0
- package/src/commands/config/import-preserve-refs.test.ts +212 -0
- package/src/commands/config/import.ts +119 -0
- package/src/commands/config/index.ts +35 -0
- package/src/commands/config/list.ts +281 -0
- package/src/commands/config/roy-config-e2e.test.ts +297 -0
- package/src/commands/config/types.ts +54 -0
- package/src/commands/debug/index.ts +38 -0
- package/src/commands/debug/log.test.ts +233 -0
- package/src/commands/debug/log.ts +123 -0
- package/src/commands/debug/span.test.ts +297 -0
- package/src/commands/debug/span.ts +211 -0
- package/src/commands/debug/trace.test.ts +254 -0
- package/src/commands/debug/trace.ts +140 -0
- package/src/commands/eventsource/add.ts +133 -0
- package/src/commands/eventsource/index.ts +48 -0
- package/src/commands/eventsource/list.ts +194 -0
- package/src/commands/eventsource/remove.ts +95 -0
- package/src/commands/eventsource/start.ts +103 -0
- package/src/commands/eventsource/status.ts +185 -0
- package/src/commands/eventsource/stop.ts +89 -0
- package/src/commands/index.ts +22 -0
- package/src/commands/input-handler.test.ts +76 -0
- package/src/commands/input-handler.ts +43 -0
- package/src/commands/interactive-esc.test.ts +254 -0
- package/src/commands/interactive.shutdown.test.ts +122 -0
- package/src/commands/interactive.test.ts +221 -0
- package/src/commands/interactive.ts +1015 -0
- package/src/commands/lsp/check.ts +92 -0
- package/src/commands/lsp/index.ts +32 -0
- package/src/commands/lsp/install.ts +126 -0
- package/src/commands/lsp/list.ts +64 -0
- package/src/commands/mcp/index.ts +27 -0
- package/src/commands/mcp/list.ts +116 -0
- package/src/commands/mcp/reload.ts +70 -0
- package/src/commands/mcp/tools.ts +121 -0
- package/src/commands/memory/extract-e2e.test.ts +388 -0
- package/src/commands/memory/index.ts +11 -0
- package/src/commands/memory/memory-simplified.test.ts +58 -0
- package/src/commands/memory/memory.ts +25 -0
- package/src/commands/memory/organize.ts +300 -0
- package/src/commands/memory/recall.test.ts +120 -0
- package/src/commands/memory/recall.ts +88 -0
- package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
- package/src/commands/memory/record-prompt-component.test.ts +343 -0
- package/src/commands/memory/record.test.ts +92 -0
- package/src/commands/memory/record.ts +332 -0
- package/src/commands/plugin.test.ts +292 -0
- package/src/commands/plugin.ts +267 -0
- package/src/commands/sessions/active.ts +96 -0
- package/src/commands/sessions/add-message.ts +96 -0
- package/src/commands/sessions/checkpoints.ts +154 -0
- package/src/commands/sessions/compact.test.ts +215 -0
- package/src/commands/sessions/compact.ts +269 -0
- package/src/commands/sessions/delete.ts +236 -0
- package/src/commands/sessions/get.ts +165 -0
- package/src/commands/sessions/grep.ts +233 -0
- package/src/commands/sessions/index.ts +95 -0
- package/src/commands/sessions/list.ts +210 -0
- package/src/commands/sessions/messages.test.ts +333 -0
- package/src/commands/sessions/messages.ts +248 -0
- package/src/commands/sessions/mock.ts +194 -0
- package/src/commands/sessions/new.ts +82 -0
- package/src/commands/sessions/rename.ts +98 -0
- package/src/commands/shared/event-handler.ts +213 -0
- package/src/commands/shared/event-message-formatter.ts +295 -0
- package/src/commands/shared/index.ts +11 -0
- package/src/commands/shared/query-executor.test.ts +434 -0
- package/src/commands/shared/query-executor.ts +324 -0
- package/src/commands/shared/repl-engine.test.ts +354 -0
- package/src/commands/shared/session-manager.test.ts +212 -0
- package/src/commands/shared/session-manager.ts +114 -0
- package/src/commands/skills/get.ts +90 -0
- package/src/commands/skills/index.ts +39 -0
- package/src/commands/skills/list.ts +129 -0
- package/src/commands/skills/reload.ts +59 -0
- package/src/commands/skills/search.ts +132 -0
- package/src/commands/skills/show-config.ts +93 -0
- package/src/commands/tasks/complete.ts +92 -0
- package/src/commands/tasks/create.ts +118 -0
- package/src/commands/tasks/delete.ts +86 -0
- package/src/commands/tasks/get.ts +116 -0
- package/src/commands/tasks/index.ts +53 -0
- package/src/commands/tasks/list.ts +140 -0
- package/src/commands/tasks/operations.ts +120 -0
- package/src/commands/tasks/update.ts +122 -0
- package/src/commands/tools/exec-tool.ts +128 -0
- package/src/commands/tools/get.ts +114 -0
- package/src/commands/tools/index.ts +35 -0
- package/src/commands/tools/list.ts +107 -0
- package/src/commands/tools/shared/index.ts +7 -0
- package/src/commands/tools/shared/schema-helper.ts +111 -0
- package/src/commands/workflow/commands/add.ts +315 -0
- package/src/commands/workflow/commands/get.ts +193 -0
- package/src/commands/workflow/commands/list.ts +137 -0
- package/src/commands/workflow/commands/nodes.ts +528 -0
- package/src/commands/workflow/commands/remove.ts +94 -0
- package/src/commands/workflow/commands/run.ts +398 -0
- package/src/commands/workflow/commands/status.ts +147 -0
- package/src/commands/workflow/commands/stop.ts +91 -0
- package/src/commands/workflow/commands/update.ts +130 -0
- package/src/commands/workflow/commands/validate.ts +139 -0
- package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
- package/src/commands/workflow/index.ts +65 -0
- package/src/commands/workflow/renderers.ts +358 -0
- package/src/commands/workflow/validators/index.ts +8 -0
- package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
- package/src/commands/workflow/validators/node-validator.ts +125 -0
- package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
- package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
- package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
- package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
- package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
- package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
- package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
- package/src/commands/workflow/validators/types.ts +78 -0
- package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
- package/src/commands/workflow/validators/workflow-validator.ts +320 -0
- package/src/index.ts +19 -0
- package/src/plugin/apply.ts +103 -0
- package/src/plugin/discover.ts +219 -0
- package/src/plugin/index.ts +45 -0
- package/src/plugin/registry.ts +272 -0
- package/src/plugin/types.ts +165 -0
- package/src/services/context-handler.service.test.ts +501 -0
- package/src/services/context-handler.service.ts +372 -0
- package/src/services/environment.service.commands-prompt.test.ts +167 -0
- package/src/services/environment.service.ts +656 -0
- package/src/services/output.service.test.ts +92 -0
- package/src/services/output.service.ts +122 -0
- package/src/services/quiet-mode.service.test.ts +114 -0
- package/src/services/quiet-mode.service.ts +81 -0
- package/src/services/stream-output.service.test.ts +214 -0
- package/src/services/stream-output.service.ts +323 -0
- package/src/util/which.test.ts +101 -0
- package/src/util/which.ts +55 -0
|
@@ -0,0 +1,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
|
+
};
|