@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,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Sessions Delete Command
|
|
3
|
+
*
|
|
4
|
+
* 命令:roy sessions delete <session-id...>
|
|
5
|
+
*
|
|
6
|
+
* 支持:
|
|
7
|
+
* - 单个删除:roy sessions delete <session-id>
|
|
8
|
+
* - 批量删除:roy sessions delete <session-id-1> <session-id-2> ...
|
|
9
|
+
* - 全量删除:roy sessions delete --all
|
|
10
|
+
* - 按时间删除:roy sessions delete --older-than <days>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { CommandModule } from "yargs";
|
|
14
|
+
import { EnvironmentService } from "../../services/environment.service";
|
|
15
|
+
import { OutputService } from "../../services/output.service";
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import type { SessionComponent, Session } from "@ai-setting/roy-agent-core";
|
|
18
|
+
|
|
19
|
+
interface DeleteOptions {
|
|
20
|
+
sessionIds: string[];
|
|
21
|
+
all?: boolean;
|
|
22
|
+
olderThan?: number;
|
|
23
|
+
keepActive?: boolean;
|
|
24
|
+
force?: boolean;
|
|
25
|
+
dryRun?: boolean;
|
|
26
|
+
config?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const DeleteCommand: CommandModule<object, object> = {
|
|
30
|
+
command: "delete [session-ids...]",
|
|
31
|
+
aliases: ["rm"],
|
|
32
|
+
describe: "删除会话(支持批量删除和全量删除)",
|
|
33
|
+
|
|
34
|
+
builder: (yargs) =>
|
|
35
|
+
yargs
|
|
36
|
+
.positional("session-ids", {
|
|
37
|
+
describe: "会话 ID(可指定多个)",
|
|
38
|
+
type: "string",
|
|
39
|
+
demandOption: false,
|
|
40
|
+
})
|
|
41
|
+
.option("all", {
|
|
42
|
+
alias: "a",
|
|
43
|
+
type: "boolean",
|
|
44
|
+
default: false,
|
|
45
|
+
description: "删除所有会话",
|
|
46
|
+
})
|
|
47
|
+
.option("older-than", {
|
|
48
|
+
alias: "o",
|
|
49
|
+
type: "number",
|
|
50
|
+
description: "删除指定天数之前的会话",
|
|
51
|
+
})
|
|
52
|
+
.option("keep-active", {
|
|
53
|
+
alias: "k",
|
|
54
|
+
type: "boolean",
|
|
55
|
+
default: false,
|
|
56
|
+
description: "全量删除时保留当前活动会话",
|
|
57
|
+
})
|
|
58
|
+
.option("force", {
|
|
59
|
+
alias: "f",
|
|
60
|
+
type: "boolean",
|
|
61
|
+
default: false,
|
|
62
|
+
description: "跳过确认直接删除",
|
|
63
|
+
})
|
|
64
|
+
.option("dry-run", {
|
|
65
|
+
alias: "d",
|
|
66
|
+
type: "boolean",
|
|
67
|
+
default: false,
|
|
68
|
+
description: "预览模式,只显示将要删除的会话",
|
|
69
|
+
})
|
|
70
|
+
.check((argv) => {
|
|
71
|
+
// 确保至少指定了一种删除方式
|
|
72
|
+
const sessionIds = argv.sessionIds as string[] | undefined;
|
|
73
|
+
const hasIds = sessionIds && sessionIds.length > 0;
|
|
74
|
+
const hasAll = argv.all === true;
|
|
75
|
+
const hasOlderThan = argv.olderThan !== undefined;
|
|
76
|
+
|
|
77
|
+
if (!hasIds && !hasAll && !hasOlderThan) {
|
|
78
|
+
throw new Error("请指定要删除的会话 ID,或使用 --all 或 --older-than 选项");
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}),
|
|
82
|
+
|
|
83
|
+
async handler(args) {
|
|
84
|
+
const a = args as any;
|
|
85
|
+
const output = new OutputService();
|
|
86
|
+
const envService = new EnvironmentService(output);
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await envService.create({ configPath: a.config });
|
|
90
|
+
const env = envService.getEnvironment();
|
|
91
|
+
if (!env) {
|
|
92
|
+
output.error("Failed to create environment");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
const sessionComponent = env.getComponent("session") as SessionComponent | undefined;
|
|
96
|
+
|
|
97
|
+
if (!sessionComponent) {
|
|
98
|
+
output.error("SessionComponent not available");
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const activeSessionId = sessionComponent.getActiveSessionId();
|
|
103
|
+
let sessionsToDelete: Session[] = [];
|
|
104
|
+
|
|
105
|
+
// 确定要删除的会话
|
|
106
|
+
if (a.sessionIds && a.sessionIds.length > 0) {
|
|
107
|
+
// 批量删除指定的会话
|
|
108
|
+
for (const id of a.sessionIds) {
|
|
109
|
+
const session = await sessionComponent.get(id);
|
|
110
|
+
if (session) {
|
|
111
|
+
sessionsToDelete.push(session);
|
|
112
|
+
} else {
|
|
113
|
+
output.warn(`Session not found: ${id}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} else if (a.all) {
|
|
117
|
+
// 全量删除
|
|
118
|
+
const allSessions = await sessionComponent.list({ limit: 100000 });
|
|
119
|
+
sessionsToDelete = allSessions.filter((s: Session) => {
|
|
120
|
+
if (a.keepActive && s.id === activeSessionId) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
});
|
|
125
|
+
if (a.keepActive && sessionsToDelete.length > 0) {
|
|
126
|
+
output.log(chalk.yellow(`Keeping active session: ${activeSessionId}`));
|
|
127
|
+
}
|
|
128
|
+
} else if (a.olderThan !== undefined) {
|
|
129
|
+
// 按时间删除
|
|
130
|
+
const cutoffDate = new Date();
|
|
131
|
+
cutoffDate.setDate(cutoffDate.getDate() - a.olderThan);
|
|
132
|
+
const cutoffTimestamp = cutoffDate.getTime();
|
|
133
|
+
|
|
134
|
+
const allSessions = await sessionComponent.list({ limit: 100000 });
|
|
135
|
+
sessionsToDelete = allSessions.filter((s: Session) => {
|
|
136
|
+
const updatedAt = new Date(s.updatedAt).getTime();
|
|
137
|
+
if (updatedAt < cutoffTimestamp) {
|
|
138
|
+
if (a.keepActive && s.id === activeSessionId) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
});
|
|
145
|
+
output.log(chalk.gray(`Deleting sessions not updated since ${cutoffDate.toISOString().split("T")[0]} (${a.olderThan} days ago)`));
|
|
146
|
+
} else {
|
|
147
|
+
// 这个分支理论上不会到达,因为 yargs check 已经验证了
|
|
148
|
+
output.log("No sessions to delete");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (sessionsToDelete.length === 0) {
|
|
153
|
+
output.log("No sessions to delete");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 预览模式
|
|
158
|
+
if (a.dryRun) {
|
|
159
|
+
output.log(chalk.yellow(`[DRY RUN] Would delete ${sessionsToDelete.length} session(s):\n`));
|
|
160
|
+
sessionsToDelete.forEach((s: Session, i: number) => {
|
|
161
|
+
const marker = s.id === activeSessionId ? chalk.green("▶") : " ";
|
|
162
|
+
output.log(` ${marker} ${s.id}`);
|
|
163
|
+
output.log(` Title: ${s.title}`);
|
|
164
|
+
output.log(` Updated: ${new Date(s.updatedAt).toLocaleString()}`);
|
|
165
|
+
if (i < sessionsToDelete.length - 1) output.log("");
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 确认删除
|
|
171
|
+
if (!a.force) {
|
|
172
|
+
output.log(chalk.red(`\n⚠️ About to delete ${sessionsToDelete.length} session(s):\n`));
|
|
173
|
+
sessionsToDelete.forEach((s: Session, i: number) => {
|
|
174
|
+
const marker = s.id === activeSessionId ? chalk.green("▶") : " ";
|
|
175
|
+
output.log(` ${marker} ${s.id} - ${s.title}`);
|
|
176
|
+
});
|
|
177
|
+
output.log("");
|
|
178
|
+
|
|
179
|
+
const answer = await new Promise<string>((resolve) => {
|
|
180
|
+
const readline = require("readline");
|
|
181
|
+
const rl = readline.createInterface({
|
|
182
|
+
input: process.stdin,
|
|
183
|
+
output: process.stdout,
|
|
184
|
+
});
|
|
185
|
+
rl.question(chalk.red("Type 'yes' to confirm deletion: "), (res: string) => {
|
|
186
|
+
rl.close();
|
|
187
|
+
resolve(res);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (answer.toLowerCase() !== "yes") {
|
|
192
|
+
output.log("Deletion cancelled");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 执行删除
|
|
198
|
+
let successCount = 0;
|
|
199
|
+
let failCount = 0;
|
|
200
|
+
const errors: string[] = [];
|
|
201
|
+
|
|
202
|
+
for (const session of sessionsToDelete) {
|
|
203
|
+
try {
|
|
204
|
+
const success = await sessionComponent.delete(session.id);
|
|
205
|
+
if (success) {
|
|
206
|
+
successCount++;
|
|
207
|
+
} else {
|
|
208
|
+
failCount++;
|
|
209
|
+
errors.push(`Failed to delete: ${session.id}`);
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
failCount++;
|
|
213
|
+
errors.push(`${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 输出结果
|
|
218
|
+
if (successCount > 0) {
|
|
219
|
+
output.success(chalk.green(`✓`) + ` Deleted ${successCount} session(s)`);
|
|
220
|
+
}
|
|
221
|
+
if (failCount > 0) {
|
|
222
|
+
output.error(`✗ Failed to delete ${failCount} session(s)`);
|
|
223
|
+
errors.forEach((e) => output.error(` - ${e}`));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (successCount === 0 && failCount === 0) {
|
|
227
|
+
output.log("No sessions were deleted");
|
|
228
|
+
}
|
|
229
|
+
} catch (error) {
|
|
230
|
+
output.error(`Failed to delete session: ${error instanceof Error ? error.message : String(error)}`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
} finally {
|
|
233
|
+
await envService.dispose();
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Sessions Get Command
|
|
3
|
+
*
|
|
4
|
+
* 命令:roy sessions get <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
|
+
// Inline type for WorkflowSessionMetadata
|
|
14
|
+
interface WorkflowSessionMetadata {
|
|
15
|
+
type: 'workflow';
|
|
16
|
+
workflowId?: string;
|
|
17
|
+
workflowName: string;
|
|
18
|
+
status: 'running' | 'paused' | 'completed' | 'failed';
|
|
19
|
+
agentSessions?: AgentSessionRef[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface AgentSessionRef {
|
|
23
|
+
nodeId: string;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
status: 'active' | 'paused' | 'completed';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface GetOptions {
|
|
29
|
+
sessionId: string;
|
|
30
|
+
messages?: boolean;
|
|
31
|
+
checkpoints?: boolean;
|
|
32
|
+
json?: boolean;
|
|
33
|
+
config?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const GetCommand: CommandModule<object, object> = {
|
|
37
|
+
command: "get <session-id>",
|
|
38
|
+
aliases: ["show"],
|
|
39
|
+
describe: "获取会话详情",
|
|
40
|
+
|
|
41
|
+
builder: (yargs) =>
|
|
42
|
+
yargs
|
|
43
|
+
.positional("session-id", {
|
|
44
|
+
describe: "会话 ID",
|
|
45
|
+
type: "string",
|
|
46
|
+
demandOption: true,
|
|
47
|
+
})
|
|
48
|
+
.option("messages", { type: "boolean", default: false })
|
|
49
|
+
.option("checkpoints", { alias: "c", type: "boolean", default: false })
|
|
50
|
+
.option("json", { alias: "j", type: "boolean", default: false }),
|
|
51
|
+
|
|
52
|
+
async handler(args) {
|
|
53
|
+
const a = args as any;
|
|
54
|
+
const output = new OutputService();
|
|
55
|
+
const envService = new EnvironmentService(output);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await envService.create({ configPath: a.config });
|
|
59
|
+
const env = envService.getEnvironment();
|
|
60
|
+
if (!env) {
|
|
61
|
+
output.error("Failed to create environment");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const sessionComponent = env.getComponent("session") as SessionComponent | undefined;
|
|
65
|
+
|
|
66
|
+
if (!sessionComponent) {
|
|
67
|
+
output.error("SessionComponent not available");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const session = await sessionComponent.get(a.sessionId);
|
|
72
|
+
if (!session) {
|
|
73
|
+
output.error(`Session not found: ${a.sessionId}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const activeSessionId = sessionComponent.getActiveSessionId();
|
|
78
|
+
const checkpoints: SessionCheckpoint[] = a.checkpoints
|
|
79
|
+
? await sessionComponent.getCheckpoints(a.sessionId)
|
|
80
|
+
: [];
|
|
81
|
+
|
|
82
|
+
if (a.json) {
|
|
83
|
+
output.json({
|
|
84
|
+
id: session.id,
|
|
85
|
+
title: session.title,
|
|
86
|
+
directory: session.directory,
|
|
87
|
+
parentID: session.parentID,
|
|
88
|
+
messageCount: session.messageCount,
|
|
89
|
+
isActive: session.id === activeSessionId,
|
|
90
|
+
checkpoints: checkpoints.map((cp: SessionCheckpoint) => ({
|
|
91
|
+
id: cp.id,
|
|
92
|
+
title: cp.title,
|
|
93
|
+
messageIndex: cp.messageIndex,
|
|
94
|
+
type: cp.type,
|
|
95
|
+
createdAt: new Date(cp.createdAt).toISOString(),
|
|
96
|
+
processKeyPoints: cp.processKeyPoints,
|
|
97
|
+
currentState: cp.currentState,
|
|
98
|
+
})),
|
|
99
|
+
metadata: session.metadata,
|
|
100
|
+
createdAt: new Date(session.createdAt).toISOString(),
|
|
101
|
+
updatedAt: new Date(session.updatedAt).toISOString(),
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
// 详细信息输出
|
|
105
|
+
const lines = [
|
|
106
|
+
chalk.bold("┌─ Session Details ─────────────────────────────────┐"),
|
|
107
|
+
`│ ID: ${session.id}`,
|
|
108
|
+
`│ Title: ${session.title}`,
|
|
109
|
+
`│ Directory: ${session.directory}`,
|
|
110
|
+
`│ Messages: ${session.messageCount}`,
|
|
111
|
+
`│ Active: ${session.id === activeSessionId ? chalk.green("✓") : chalk.gray("-")}`,
|
|
112
|
+
`│ Created: ${new Date(session.createdAt).toLocaleString("zh-CN")}`,
|
|
113
|
+
`│ Updated: ${new Date(session.updatedAt).toLocaleString("zh-CN")}`,
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
// Check if this is a workflow session
|
|
117
|
+
const workflowMeta = session.metadata?.type === 'workflow'
|
|
118
|
+
? session.metadata as unknown as WorkflowSessionMetadata
|
|
119
|
+
: null;
|
|
120
|
+
|
|
121
|
+
if (workflowMeta) {
|
|
122
|
+
lines.push("├─ Workflow Info ─────────────────────────────────────┤");
|
|
123
|
+
lines.push(`│ Type: ${chalk.cyan("workflow")}`);
|
|
124
|
+
lines.push(`│ Workflow: ${workflowMeta.workflowName}`);
|
|
125
|
+
if (workflowMeta.workflowId) {
|
|
126
|
+
lines.push(`│ Workflow ID: ${workflowMeta.workflowId}`);
|
|
127
|
+
}
|
|
128
|
+
const statusColor = workflowMeta.status === 'running' ? chalk.green
|
|
129
|
+
: workflowMeta.status === 'paused' ? chalk.yellow
|
|
130
|
+
: workflowMeta.status === 'completed' ? chalk.gray
|
|
131
|
+
: chalk.red;
|
|
132
|
+
lines.push(`│ Status: ${statusColor(workflowMeta.status)}`);
|
|
133
|
+
|
|
134
|
+
// Display agent sub-sessions if any
|
|
135
|
+
if (workflowMeta.agentSessions && workflowMeta.agentSessions.length > 0) {
|
|
136
|
+
lines.push("├─ Agent Sub-sessions ─────────────────────────────────┤");
|
|
137
|
+
workflowMeta.agentSessions.forEach((agent: AgentSessionRef) => {
|
|
138
|
+
const agentStatusColor = agent.status === 'active' ? chalk.green
|
|
139
|
+
: agent.status === 'paused' ? chalk.yellow
|
|
140
|
+
: chalk.gray;
|
|
141
|
+
lines.push(`│ ${agent.nodeId}: ${agent.sessionId} (${agentStatusColor(agent.status)})`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (checkpoints.length > 0) {
|
|
147
|
+
lines.push("├─ Checkpoints ──────────────────────────────────────┤");
|
|
148
|
+
checkpoints.slice(-5).reverse().forEach((cp: SessionCheckpoint, i: number) => {
|
|
149
|
+
const marker = i === 0 ? "✓ Latest" : " #" + (checkpoints.length - i);
|
|
150
|
+
lines.push(`│ ${marker} ${cp.title}`);
|
|
151
|
+
lines.push(`│ ${new Date(cp.createdAt).toLocaleString("zh-CN")}`);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
lines.push("└───────────────────────────────────────────────────┘");
|
|
156
|
+
output.log(lines.join("\n"));
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
output.error(`Failed to get session: ${error instanceof Error ? error.message : String(error)}`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
} finally {
|
|
162
|
+
await envService.dispose();
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Sessions Grep Command
|
|
3
|
+
*
|
|
4
|
+
* 命令:roy sessions grep <query>
|
|
5
|
+
*
|
|
6
|
+
* 在所有会话消息中进行全文搜索,支持:
|
|
7
|
+
* - 多关键字搜索 (AND/OR/NOT)
|
|
8
|
+
* - 按会话过滤
|
|
9
|
+
* - 按时间范围过滤
|
|
10
|
+
* - 结果以 session 为单位聚合展示
|
|
11
|
+
* - --quiet 安静模式
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { CommandModule } from "yargs";
|
|
15
|
+
import { EnvironmentService } from "../../services/environment.service";
|
|
16
|
+
import { OutputService } from "../../services/output.service";
|
|
17
|
+
import chalk from "chalk";
|
|
18
|
+
import { type SessionComponent, type SearchMessagesOptions } from "@ai-setting/roy-agent-core";
|
|
19
|
+
import { CliQuietModeService } from "../../services/quiet-mode.service";
|
|
20
|
+
|
|
21
|
+
export const GrepCommand: CommandModule<object, object> = {
|
|
22
|
+
command: "grep <query...>",
|
|
23
|
+
aliases: ["search"],
|
|
24
|
+
describe: "搜索会话消息(全文搜索)",
|
|
25
|
+
|
|
26
|
+
builder: (yargs) =>
|
|
27
|
+
yargs
|
|
28
|
+
.positional("query", {
|
|
29
|
+
describe: "搜索关键词(支持 AND/OR/NOT)",
|
|
30
|
+
type: "string",
|
|
31
|
+
demandOption: true,
|
|
32
|
+
})
|
|
33
|
+
.option("session", {
|
|
34
|
+
alias: "s",
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "限制搜索特定会话 ID",
|
|
37
|
+
})
|
|
38
|
+
.option("limit", {
|
|
39
|
+
alias: "n",
|
|
40
|
+
type: "number",
|
|
41
|
+
default: 50,
|
|
42
|
+
description: "最大返回结果数",
|
|
43
|
+
})
|
|
44
|
+
.option("before", {
|
|
45
|
+
alias: "b",
|
|
46
|
+
type: "number",
|
|
47
|
+
description: "搜索此时间之前的消息(Unix timestamp 或天数)",
|
|
48
|
+
})
|
|
49
|
+
.option("after", {
|
|
50
|
+
alias: "a",
|
|
51
|
+
type: "number",
|
|
52
|
+
description: "搜索此时间之后的消息(Unix timestamp 或天数)",
|
|
53
|
+
})
|
|
54
|
+
.option("json", {
|
|
55
|
+
alias: "j",
|
|
56
|
+
type: "boolean",
|
|
57
|
+
default: false,
|
|
58
|
+
description: "JSON 格式输出",
|
|
59
|
+
})
|
|
60
|
+
.option("quiet", {
|
|
61
|
+
alias: "q",
|
|
62
|
+
type: "boolean",
|
|
63
|
+
default: false,
|
|
64
|
+
description: "简洁输出",
|
|
65
|
+
}),
|
|
66
|
+
|
|
67
|
+
async handler(args) {
|
|
68
|
+
// 强制类型转换以处理 yargs 的类型推断问题
|
|
69
|
+
const a = args as any;
|
|
70
|
+
|
|
71
|
+
// 在任何日志输出之前设置 quiet 模式
|
|
72
|
+
const isQuiet = a.quiet === true;
|
|
73
|
+
if (isQuiet) {
|
|
74
|
+
CliQuietModeService.getInstance().setQuiet(true);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const output = new OutputService();
|
|
78
|
+
output.configure({ quiet: isQuiet });
|
|
79
|
+
const envService = new EnvironmentService(output);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
await envService.create({
|
|
83
|
+
configPath: a.config,
|
|
84
|
+
});
|
|
85
|
+
const env = envService.getEnvironment();
|
|
86
|
+
if (!env) {
|
|
87
|
+
output.error("Failed to create environment");
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const sessionComponent = env.getComponent("session") as SessionComponent;
|
|
91
|
+
|
|
92
|
+
if (!sessionComponent) {
|
|
93
|
+
output.error("SessionComponent not available");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 转换相对时间为绝对时间戳
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
let beforeTime: number | undefined;
|
|
100
|
+
let afterTime: number | undefined;
|
|
101
|
+
|
|
102
|
+
if (a.before !== undefined) {
|
|
103
|
+
// 如果是小于 1e12,视为天数
|
|
104
|
+
beforeTime = a.before < 1e12
|
|
105
|
+
? now - a.before * 24 * 60 * 60 * 1000
|
|
106
|
+
: a.before;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (a.after !== undefined) {
|
|
110
|
+
afterTime = a.after < 1e12
|
|
111
|
+
? now - a.after * 24 * 60 * 60 * 1000
|
|
112
|
+
: a.after;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// query 是 string[],合并为字符串
|
|
116
|
+
const query = Array.isArray(a.query) ? a.query.join(" ") : a.query ?? "";
|
|
117
|
+
|
|
118
|
+
const searchOptions: SearchMessagesOptions = {
|
|
119
|
+
query,
|
|
120
|
+
sessionId: a.session,
|
|
121
|
+
limit: a.limit,
|
|
122
|
+
beforeTime,
|
|
123
|
+
afterTime,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// 执行搜索
|
|
127
|
+
const results = await sessionComponent.searchMessages(searchOptions);
|
|
128
|
+
|
|
129
|
+
if (results.length === 0) {
|
|
130
|
+
if (!a.quiet) {
|
|
131
|
+
output.log(chalk.yellow(`No results found for: ${query}`));
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (a.json) {
|
|
137
|
+
// JSON 格式输出
|
|
138
|
+
output.json({
|
|
139
|
+
query,
|
|
140
|
+
totalMatches: results.reduce((sum, r) => sum + r.matches.length, 0),
|
|
141
|
+
sessionCount: results.length,
|
|
142
|
+
results: results.map((r) => ({
|
|
143
|
+
sessionId: r.sessionId,
|
|
144
|
+
sessionTitle: r.sessionTitle,
|
|
145
|
+
messageCount: r.messageCount,
|
|
146
|
+
matches: r.matches.map((m) => ({
|
|
147
|
+
messageId: m.messageId,
|
|
148
|
+
role: m.role,
|
|
149
|
+
content: m.content,
|
|
150
|
+
timestamp: new Date(m.timestamp).toISOString(),
|
|
151
|
+
contextBefore: m.contextBefore,
|
|
152
|
+
contextAfter: m.contextAfter,
|
|
153
|
+
})),
|
|
154
|
+
})),
|
|
155
|
+
});
|
|
156
|
+
} else if (a.quiet) {
|
|
157
|
+
// 简洁输出:只显示匹配的消息
|
|
158
|
+
for (const result of results) {
|
|
159
|
+
for (const match of result.matches) {
|
|
160
|
+
const time = new Date(match.timestamp).toLocaleString("zh-CN");
|
|
161
|
+
output.log(`${chalk.gray(result.sessionId)} ${chalk.blue(result.sessionTitle)} ${chalk.gray(time)}`);
|
|
162
|
+
output.log(` ${match.content}`);
|
|
163
|
+
output.log("");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
// 格式化输出:以 session 为单位聚合
|
|
168
|
+
let totalMatches = 0;
|
|
169
|
+
|
|
170
|
+
for (let ri = 0; ri < results.length; ri++) {
|
|
171
|
+
const result = results[ri];
|
|
172
|
+
totalMatches += result.matches.length;
|
|
173
|
+
|
|
174
|
+
// Session 头部
|
|
175
|
+
const time = new Date(result.updatedAt).toLocaleString("zh-CN");
|
|
176
|
+
const titleStr = `─ ${result.sessionTitle} (${result.sessionId})`;
|
|
177
|
+
output.log(chalk.bold(`\n┌${titleStr}${"─".repeat(Math.max(0, 50 - titleStr.length))}┐`));
|
|
178
|
+
output.log(`│ ${chalk.gray(`Updated: ${time} | ${result.messageCount} messages | ${result.matches.length} matches`)}${" ".repeat(Math.max(0, 50 - 60))}│`);
|
|
179
|
+
|
|
180
|
+
// 每条匹配的消息
|
|
181
|
+
for (let i = 0; i < result.matches.length; i++) {
|
|
182
|
+
const match = result.matches[i];
|
|
183
|
+
const roleColor = match.role === "user" ? chalk.blue
|
|
184
|
+
: match.role === "assistant" ? chalk.green
|
|
185
|
+
: chalk.gray;
|
|
186
|
+
const msgTime = new Date(match.timestamp).toLocaleTimeString("zh-CN", {
|
|
187
|
+
hour: "2-digit",
|
|
188
|
+
minute: "2-digit",
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
output.log(`├─ #${i + 1} ${roleColor(match.role.padEnd(10))} ${chalk.gray(msgTime)}`);
|
|
192
|
+
output.log("│");
|
|
193
|
+
|
|
194
|
+
// 消息内容(限制行数,过滤空行)
|
|
195
|
+
const lines = match.content
|
|
196
|
+
.split("\n")
|
|
197
|
+
.map(line => line.trim()) // 去除每行首尾空白
|
|
198
|
+
.filter(line => line.length > 0); // 过滤空行
|
|
199
|
+
|
|
200
|
+
const maxLines = 5;
|
|
201
|
+
const displayLines = lines.slice(0, maxLines);
|
|
202
|
+
|
|
203
|
+
if (displayLines.length === 0) {
|
|
204
|
+
output.log("│ " + chalk.gray("(empty content)"));
|
|
205
|
+
} else {
|
|
206
|
+
for (const line of displayLines) {
|
|
207
|
+
const truncated = line.length > 76 ? line.slice(0, 73) + "..." : line;
|
|
208
|
+
output.log(`│ ${chalk.gray(truncated)}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (lines.length > maxLines) {
|
|
213
|
+
output.log("│ " + chalk.gray(`... (${lines.length - maxLines} more lines)`));
|
|
214
|
+
}
|
|
215
|
+
output.log("│");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Session 底部
|
|
219
|
+
output.log(`└${"─".repeat(52)}┘`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 统计信息
|
|
223
|
+
output.log(`\n${chalk.green("✓")} Found ${chalk.bold(totalMatches.toString())} matches in ${chalk.bold(results.length.toString())} sessions`);
|
|
224
|
+
output.log(chalk.gray(`Query: "${query}"`));
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
output.error(`Search failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
} finally {
|
|
230
|
+
await envService.dispose();
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
};
|