@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,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview SessionManager Test
|
|
3
|
+
*
|
|
4
|
+
* TDD: 测试 SessionManager 的核心功能
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, test, expect, vi, beforeEach, afterEach } from "bun:test";
|
|
8
|
+
import { SessionManager } from "./session-manager";
|
|
9
|
+
import { OutputService } from "../../services/output.service";
|
|
10
|
+
|
|
11
|
+
// Mock types
|
|
12
|
+
interface MockSession {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface MockSessionComponent {
|
|
18
|
+
getActiveSession: ReturnType<typeof vi.fn>;
|
|
19
|
+
get: ReturnType<typeof vi.fn>;
|
|
20
|
+
setActiveSession: ReturnType<typeof vi.fn>;
|
|
21
|
+
create: ReturnType<typeof vi.fn>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("SessionManager", () => {
|
|
25
|
+
let sessionManager: SessionManager;
|
|
26
|
+
let mockSessionComponent: MockSessionComponent;
|
|
27
|
+
let mockOutput: OutputService;
|
|
28
|
+
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
|
29
|
+
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
// 创建 mock SessionComponent
|
|
33
|
+
mockSessionComponent = {
|
|
34
|
+
getActiveSession: vi.fn(),
|
|
35
|
+
get: vi.fn(),
|
|
36
|
+
setActiveSession: vi.fn(),
|
|
37
|
+
create: vi.fn(),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
mockOutput = new OutputService();
|
|
41
|
+
mockOutput.configure({ quiet: true });
|
|
42
|
+
|
|
43
|
+
// Spy on console methods
|
|
44
|
+
consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
45
|
+
consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
46
|
+
|
|
47
|
+
sessionManager = new SessionManager({
|
|
48
|
+
sessionComponent: mockSessionComponent as any,
|
|
49
|
+
output: mockOutput,
|
|
50
|
+
quiet: true,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
vi.restoreAllMocks();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("init()", () => {
|
|
59
|
+
test("should create new session when no sessionId provided", async () => {
|
|
60
|
+
// Given: no active session and no sessionId
|
|
61
|
+
// First call to getActiveSession returns null (no active session)
|
|
62
|
+
// First call to get() returns the newly created session
|
|
63
|
+
mockSessionComponent.getActiveSession.mockResolvedValue(null);
|
|
64
|
+
mockSessionComponent.create.mockResolvedValue({
|
|
65
|
+
id: "sess_new123",
|
|
66
|
+
title: "New Session",
|
|
67
|
+
});
|
|
68
|
+
mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
|
|
69
|
+
mockSessionComponent.get.mockResolvedValueOnce({
|
|
70
|
+
id: "sess_new123",
|
|
71
|
+
title: "New Session",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// When
|
|
75
|
+
const result = await sessionManager.init();
|
|
76
|
+
|
|
77
|
+
// Then
|
|
78
|
+
expect(result.isNewSession).toBe(true);
|
|
79
|
+
expect(result.sessionId).toBe("sess_new123");
|
|
80
|
+
expect(mockSessionComponent.create).toHaveBeenCalledWith({
|
|
81
|
+
title: expect.stringContaining("Session -"),
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("should return existing session when sessionId exists", async () => {
|
|
86
|
+
// Given: existing session
|
|
87
|
+
const existingSession: MockSession = {
|
|
88
|
+
id: "sess_existing",
|
|
89
|
+
title: "Existing Session",
|
|
90
|
+
};
|
|
91
|
+
mockSessionComponent.get.mockResolvedValue(existingSession);
|
|
92
|
+
mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
|
|
93
|
+
|
|
94
|
+
// When
|
|
95
|
+
const result = await sessionManager.init("sess_existing");
|
|
96
|
+
|
|
97
|
+
// Then
|
|
98
|
+
expect(result.isNewSession).toBe(false);
|
|
99
|
+
expect(result.sessionId).toBe("sess_existing");
|
|
100
|
+
expect(result.sessionTitle).toBe("Existing Session");
|
|
101
|
+
expect(mockSessionComponent.create).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("should create new session when specified sessionId does not exist", async () => {
|
|
105
|
+
// Given: sessionId provided but doesn't exist
|
|
106
|
+
// First call to get() returns null (session not found)
|
|
107
|
+
// Second call to get() returns the newly created session
|
|
108
|
+
mockSessionComponent.get
|
|
109
|
+
.mockResolvedValueOnce(null) // first call - session not found
|
|
110
|
+
.mockResolvedValueOnce({ id: "sess_new456", title: "New Session" }); // second call - new session
|
|
111
|
+
mockSessionComponent.create.mockResolvedValue({
|
|
112
|
+
id: "sess_new456",
|
|
113
|
+
title: "New Session",
|
|
114
|
+
});
|
|
115
|
+
mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
|
|
116
|
+
|
|
117
|
+
// When
|
|
118
|
+
const result = await sessionManager.init("sess_nonexistent");
|
|
119
|
+
|
|
120
|
+
// Then
|
|
121
|
+
expect(result.isNewSession).toBe(true);
|
|
122
|
+
expect(result.sessionId).toBe("sess_new456");
|
|
123
|
+
expect(mockSessionComponent.create).toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("should continue last session when isContinue is true", async () => {
|
|
127
|
+
// Given: active session exists
|
|
128
|
+
// First call to getActiveSession returns the active session
|
|
129
|
+
// First call to get() returns the active session
|
|
130
|
+
const activeSession: MockSession = {
|
|
131
|
+
id: "sess_active",
|
|
132
|
+
title: "Active Session",
|
|
133
|
+
};
|
|
134
|
+
mockSessionComponent.getActiveSession.mockResolvedValue(activeSession);
|
|
135
|
+
mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
|
|
136
|
+
mockSessionComponent.get.mockResolvedValueOnce(activeSession);
|
|
137
|
+
|
|
138
|
+
// When
|
|
139
|
+
const result = await sessionManager.init(undefined, true);
|
|
140
|
+
|
|
141
|
+
// Then
|
|
142
|
+
expect(result.sessionId).toBe("sess_active");
|
|
143
|
+
expect(mockSessionComponent.getActiveSession).toHaveBeenCalled();
|
|
144
|
+
expect(mockSessionComponent.create).not.toHaveBeenCalled();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("should prefer explicit sessionId over continue flag", async () => {
|
|
148
|
+
// Given: both sessionId and isContinue provided
|
|
149
|
+
const existingSession: MockSession = {
|
|
150
|
+
id: "sess_explicit",
|
|
151
|
+
title: "Explicit Session",
|
|
152
|
+
};
|
|
153
|
+
mockSessionComponent.get.mockResolvedValue(existingSession);
|
|
154
|
+
mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
|
|
155
|
+
|
|
156
|
+
// When: sessionId takes precedence
|
|
157
|
+
const result = await sessionManager.init("sess_explicit", true);
|
|
158
|
+
|
|
159
|
+
// Then
|
|
160
|
+
expect(result.sessionId).toBe("sess_explicit");
|
|
161
|
+
expect(mockSessionComponent.getActiveSession).not.toHaveBeenCalled();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("switchSession()", () => {
|
|
166
|
+
test("should switch to specified session", async () => {
|
|
167
|
+
// Given
|
|
168
|
+
const targetSession: MockSession = {
|
|
169
|
+
id: "sess_target",
|
|
170
|
+
title: "Target Session",
|
|
171
|
+
};
|
|
172
|
+
mockSessionComponent.get.mockResolvedValue(targetSession);
|
|
173
|
+
mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
|
|
174
|
+
|
|
175
|
+
// When
|
|
176
|
+
const result = await sessionManager.switchSession("sess_target");
|
|
177
|
+
|
|
178
|
+
// Then
|
|
179
|
+
expect(result.sessionId).toBe("sess_target");
|
|
180
|
+
expect(result.sessionTitle).toBe("Target Session");
|
|
181
|
+
expect(mockSessionComponent.setActiveSession).toHaveBeenCalledWith("sess_target");
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("getCurrentSession()", () => {
|
|
186
|
+
test("should return current active session", async () => {
|
|
187
|
+
// Given
|
|
188
|
+
const activeSession: MockSession = {
|
|
189
|
+
id: "sess_current",
|
|
190
|
+
title: "Current Session",
|
|
191
|
+
};
|
|
192
|
+
mockSessionComponent.getActiveSession.mockResolvedValue(activeSession);
|
|
193
|
+
|
|
194
|
+
// When
|
|
195
|
+
const result = await sessionManager.getCurrentSession();
|
|
196
|
+
|
|
197
|
+
// Then
|
|
198
|
+
expect(result).toEqual(activeSession);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("should return null when no active session", async () => {
|
|
202
|
+
// Given
|
|
203
|
+
mockSessionComponent.getActiveSession.mockResolvedValue(null);
|
|
204
|
+
|
|
205
|
+
// When
|
|
206
|
+
const result = await sessionManager.getCurrentSession();
|
|
207
|
+
|
|
208
|
+
// Then
|
|
209
|
+
expect(result).toBeNull();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Session Manager - Session 管理共享逻辑
|
|
3
|
+
*
|
|
4
|
+
* 从 act.ts 抽取,供 interactive 复用
|
|
5
|
+
*
|
|
6
|
+
* 功能:
|
|
7
|
+
* - 获取/创建 Session
|
|
8
|
+
* - 设置活跃会话
|
|
9
|
+
* - 切换会话
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { OutputService } from "../../services/output.service";
|
|
13
|
+
import type { Session, SessionComponent } from "@ai-setting/roy-agent-core";
|
|
14
|
+
|
|
15
|
+
export interface SessionInitResult {
|
|
16
|
+
sessionId: string;
|
|
17
|
+
isNewSession: boolean;
|
|
18
|
+
sessionTitle: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SessionManagerOptions {
|
|
22
|
+
sessionComponent: SessionComponent;
|
|
23
|
+
output: OutputService;
|
|
24
|
+
quiet: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* SessionManager - 会话管理
|
|
29
|
+
*/
|
|
30
|
+
export class SessionManager {
|
|
31
|
+
private sessionComponent: SessionComponent;
|
|
32
|
+
private output: OutputService;
|
|
33
|
+
private quiet: boolean;
|
|
34
|
+
|
|
35
|
+
constructor(options: SessionManagerOptions) {
|
|
36
|
+
this.sessionComponent = options.sessionComponent;
|
|
37
|
+
this.output = options.output;
|
|
38
|
+
this.quiet = options.quiet;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 初始化会话
|
|
43
|
+
*
|
|
44
|
+
* 优先级:
|
|
45
|
+
* 1. 指定 sessionId -> 验证并使用
|
|
46
|
+
* 2. --continue -> 获取上次会话
|
|
47
|
+
* 3. 无 -> 创建新会话
|
|
48
|
+
*/
|
|
49
|
+
async init(sessionId?: string, isContinue?: boolean): Promise<SessionInitResult> {
|
|
50
|
+
let targetSessionId = sessionId;
|
|
51
|
+
let isNewSession = false;
|
|
52
|
+
|
|
53
|
+
// --continue 模式
|
|
54
|
+
if (isContinue && !targetSessionId) {
|
|
55
|
+
const activeSession = await this.sessionComponent.getActiveSession();
|
|
56
|
+
if (activeSession) {
|
|
57
|
+
targetSessionId = activeSession.id;
|
|
58
|
+
if (!this.quiet) {
|
|
59
|
+
this.output.info(`继续会话: ${activeSession.title} (${targetSessionId})`);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
this.output.warn("没有找到上次会话,将创建新会话");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 指定 session 验证
|
|
67
|
+
if (targetSessionId) {
|
|
68
|
+
await this.sessionComponent.setActiveSession(targetSessionId);
|
|
69
|
+
const session = await this.sessionComponent.get(targetSessionId);
|
|
70
|
+
if (session) {
|
|
71
|
+
if (!this.quiet) {
|
|
72
|
+
this.output.info(`会话: ${session.title} (${targetSessionId})`);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
this.output.warn(`会话不存在: ${targetSessionId},将创建新会话`);
|
|
76
|
+
targetSessionId = undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 创建新会话
|
|
81
|
+
if (!targetSessionId) {
|
|
82
|
+
const newSession = await this.sessionComponent.create({
|
|
83
|
+
title: `Session - ${new Date().toLocaleString("zh-CN")}`,
|
|
84
|
+
});
|
|
85
|
+
targetSessionId = newSession.id;
|
|
86
|
+
isNewSession = true;
|
|
87
|
+
await this.sessionComponent.setActiveSession(targetSessionId);
|
|
88
|
+
if (!this.quiet) {
|
|
89
|
+
this.output.info(`创建新会话: ${newSession.title} (${targetSessionId})`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const session = await this.sessionComponent.get(targetSessionId!);
|
|
94
|
+
return {
|
|
95
|
+
sessionId: targetSessionId!,
|
|
96
|
+
isNewSession,
|
|
97
|
+
sessionTitle: session?.title || "Unknown",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 切换会话
|
|
103
|
+
*/
|
|
104
|
+
async switchSession(sessionId: string): Promise<SessionInitResult> {
|
|
105
|
+
return this.init(sessionId, false);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 获取当前会话信息
|
|
110
|
+
*/
|
|
111
|
+
async getCurrentSession(): Promise<Session | undefined> {
|
|
112
|
+
return this.sessionComponent.getActiveSession();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Skills Get Command
|
|
3
|
+
*
|
|
4
|
+
* 命令:roy skills get <name>
|
|
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 { SkillComponent, SkillEntry } from "@ai-setting/roy-agent-core";
|
|
12
|
+
|
|
13
|
+
interface GetOptions {
|
|
14
|
+
name: string;
|
|
15
|
+
json?: boolean;
|
|
16
|
+
config?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const GetCommand: CommandModule<object, GetOptions> = {
|
|
20
|
+
command: "get <name>",
|
|
21
|
+
describe: "获取指定技能内容",
|
|
22
|
+
|
|
23
|
+
builder: (yargs) =>
|
|
24
|
+
yargs
|
|
25
|
+
.positional("name", {
|
|
26
|
+
type: "string",
|
|
27
|
+
demandOption: true,
|
|
28
|
+
description: "技能名称",
|
|
29
|
+
})
|
|
30
|
+
.option("json", {
|
|
31
|
+
alias: "j",
|
|
32
|
+
type: "boolean",
|
|
33
|
+
default: false,
|
|
34
|
+
description: "JSON 输出",
|
|
35
|
+
}),
|
|
36
|
+
|
|
37
|
+
async handler(args) {
|
|
38
|
+
const output = new OutputService();
|
|
39
|
+
const envService = new EnvironmentService(output);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
await envService.create({ configPath: args.config });
|
|
43
|
+
const env = envService.getEnvironment();
|
|
44
|
+
if (!env) {
|
|
45
|
+
output.error("Failed to create environment");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
const skillComponent = env.getComponent("skill") as SkillComponent | undefined;
|
|
49
|
+
|
|
50
|
+
if (!skillComponent) {
|
|
51
|
+
output.error("SkillComponent not available");
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const skill = skillComponent.getSkill(args.name);
|
|
56
|
+
|
|
57
|
+
if (!skill) {
|
|
58
|
+
output.error(`Skill not found: ${args.name}`);
|
|
59
|
+
output.log("Use 'roy skills list' to see available skills.");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (args.json) {
|
|
64
|
+
output.json({
|
|
65
|
+
name: skill.name,
|
|
66
|
+
description: skill.description,
|
|
67
|
+
source: skill.source,
|
|
68
|
+
filePath: skill.filePath,
|
|
69
|
+
content: skill.content,
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
// 输出技能信息头
|
|
73
|
+
output.log(chalk.bold.cyan(`# ${skill.name}`));
|
|
74
|
+
output.log("");
|
|
75
|
+
output.log(`${chalk.gray("Description:")} ${skill.description}`);
|
|
76
|
+
output.log(`${chalk.gray("Source:")} ${skill.source}`);
|
|
77
|
+
output.log(`${chalk.gray("File:")} ${skill.filePath}`);
|
|
78
|
+
output.log("");
|
|
79
|
+
output.log(chalk.bold("--- Content ---"));
|
|
80
|
+
output.log("");
|
|
81
|
+
output.log(skill.content);
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
output.error(`Failed to get skill: ${error}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
} finally {
|
|
87
|
+
await envService.dispose();
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Skills Command Entry
|
|
3
|
+
*
|
|
4
|
+
* 命令入口:roy skills [action]
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CommandModule } from "yargs";
|
|
8
|
+
import { ListCommand } from "./list";
|
|
9
|
+
import { GetCommand } from "./get";
|
|
10
|
+
import { SearchCommand } from "./search";
|
|
11
|
+
import { ReloadCommand } from "./reload";
|
|
12
|
+
import { ShowConfigCommand } from "./show-config";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Skills Command
|
|
16
|
+
*
|
|
17
|
+
* 技能管理命令,提供以下子命令:
|
|
18
|
+
* - list: 列出所有技能
|
|
19
|
+
* - get: 获取指定技能内容
|
|
20
|
+
* - search: 搜索技能
|
|
21
|
+
* - reload: 重新加载技能
|
|
22
|
+
* - show-config: 显示配置信息
|
|
23
|
+
*/
|
|
24
|
+
export const SkillsCommand: CommandModule = {
|
|
25
|
+
command: "skills",
|
|
26
|
+
describe: "技能管理 - 列出、获取、搜索、重新加载技能",
|
|
27
|
+
builder: (yargs) =>
|
|
28
|
+
yargs
|
|
29
|
+
.command(ListCommand)
|
|
30
|
+
.command(GetCommand)
|
|
31
|
+
.command(SearchCommand)
|
|
32
|
+
.command(ReloadCommand)
|
|
33
|
+
.command(ShowConfigCommand)
|
|
34
|
+
.demandCommand()
|
|
35
|
+
.help(),
|
|
36
|
+
handler: () => {
|
|
37
|
+
console.log("Use 'roy skills --help' for usage information");
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Skills List Command
|
|
3
|
+
*
|
|
4
|
+
* 命令:roy skills list
|
|
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 { SkillComponent, SkillListItem } from "@ai-setting/roy-agent-core";
|
|
12
|
+
|
|
13
|
+
interface ListOptions {
|
|
14
|
+
source?: "built-in" | "user" | "project" | "all";
|
|
15
|
+
json?: boolean;
|
|
16
|
+
quiet?: boolean;
|
|
17
|
+
config?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const ListCommand: CommandModule<object, object> = {
|
|
21
|
+
command: "list",
|
|
22
|
+
aliases: ["ls"],
|
|
23
|
+
describe: "列出所有可用技能",
|
|
24
|
+
|
|
25
|
+
builder: (yargs) =>
|
|
26
|
+
yargs
|
|
27
|
+
.option("source", {
|
|
28
|
+
alias: "s",
|
|
29
|
+
type: "string",
|
|
30
|
+
choices: ["built-in", "user", "project", "all"],
|
|
31
|
+
default: "all",
|
|
32
|
+
description: "按来源筛选",
|
|
33
|
+
})
|
|
34
|
+
.option("json", {
|
|
35
|
+
alias: "j",
|
|
36
|
+
type: "boolean",
|
|
37
|
+
default: false,
|
|
38
|
+
description: "JSON 输出",
|
|
39
|
+
})
|
|
40
|
+
.option("quiet", {
|
|
41
|
+
alias: "q",
|
|
42
|
+
type: "boolean",
|
|
43
|
+
default: false,
|
|
44
|
+
description: "简洁输出",
|
|
45
|
+
}),
|
|
46
|
+
|
|
47
|
+
async handler(args) {
|
|
48
|
+
const a = args as any;
|
|
49
|
+
const output = new OutputService();
|
|
50
|
+
const envService = new EnvironmentService(output);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
await envService.create({ configPath: a.config });
|
|
54
|
+
const env = envService.getEnvironment();
|
|
55
|
+
if (!env) {
|
|
56
|
+
output.error("Failed to create environment");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
const skillComponent = env.getComponent("skill") as SkillComponent | undefined;
|
|
60
|
+
|
|
61
|
+
if (!skillComponent) {
|
|
62
|
+
output.error("SkillComponent not available");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const skills = skillComponent.getSkillList() as SkillListItem[];
|
|
67
|
+
|
|
68
|
+
// 按来源筛选
|
|
69
|
+
const filtered = a.source && a.source !== "all"
|
|
70
|
+
? skills.filter((s: SkillListItem) => s.source === a.source)
|
|
71
|
+
: skills;
|
|
72
|
+
|
|
73
|
+
if (a.json) {
|
|
74
|
+
output.json({
|
|
75
|
+
skills: filtered.map((s: SkillListItem) => ({
|
|
76
|
+
name: s.name,
|
|
77
|
+
description: s.description,
|
|
78
|
+
source: s.source,
|
|
79
|
+
})),
|
|
80
|
+
count: filtered.length,
|
|
81
|
+
});
|
|
82
|
+
} else if (a.quiet) {
|
|
83
|
+
filtered.forEach((s: SkillListItem) => output.log(s.name));
|
|
84
|
+
} else {
|
|
85
|
+
// 表格输出
|
|
86
|
+
const header = [
|
|
87
|
+
chalk.bold("Name"),
|
|
88
|
+
chalk.bold("Description"),
|
|
89
|
+
chalk.bold("Source"),
|
|
90
|
+
].join(" | ");
|
|
91
|
+
|
|
92
|
+
const sourceColor = (source: string) => {
|
|
93
|
+
switch (source) {
|
|
94
|
+
case "project": return chalk.green;
|
|
95
|
+
case "user": return chalk.blue;
|
|
96
|
+
case "built-in": return chalk.gray;
|
|
97
|
+
default: return chalk.white;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const rows = filtered.map((s: SkillListItem) => {
|
|
102
|
+
const desc = s.description.length > 50
|
|
103
|
+
? s.description.slice(0, 47) + "..."
|
|
104
|
+
: s.description;
|
|
105
|
+
return [
|
|
106
|
+
chalk.cyan(s.name),
|
|
107
|
+
desc,
|
|
108
|
+
sourceColor(s.source)(`[${s.source}]`),
|
|
109
|
+
].join(" | ");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
output.log([
|
|
113
|
+
`┌─ Skills ${"─".repeat(55)}┐`,
|
|
114
|
+
`│${header}│`,
|
|
115
|
+
"├" + "─".repeat(header.length + 2) + "┤",
|
|
116
|
+
...rows.map(r => `│${r}│`),
|
|
117
|
+
"└" + "─".repeat(header.length + 2) + "┘",
|
|
118
|
+
"",
|
|
119
|
+
chalk.gray(`Total: ${filtered.length} skills`),
|
|
120
|
+
].join("\n"));
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
output.error(`Failed to list skills: ${error}`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
} finally {
|
|
126
|
+
await envService.dispose();
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Skills Reload Command
|
|
3
|
+
*
|
|
4
|
+
* 命令:roy skills reload
|
|
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 { SkillComponent } from "@ai-setting/roy-agent-core";
|
|
12
|
+
|
|
13
|
+
interface ReloadOptions {
|
|
14
|
+
config?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ReloadCommand: CommandModule<object, ReloadOptions> = {
|
|
18
|
+
command: "reload",
|
|
19
|
+
describe: "重新扫描并更新 SkillTool",
|
|
20
|
+
|
|
21
|
+
builder: (yargs) =>
|
|
22
|
+
yargs
|
|
23
|
+
.option("config", {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "配置文件路径",
|
|
26
|
+
}),
|
|
27
|
+
|
|
28
|
+
async handler(args) {
|
|
29
|
+
const output = new OutputService();
|
|
30
|
+
const envService = new EnvironmentService(output);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await envService.create({ configPath: args.config });
|
|
34
|
+
const env = envService.getEnvironment();
|
|
35
|
+
if (!env) {
|
|
36
|
+
output.error("Failed to create environment");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const skillComponent = env.getComponent("skill") as SkillComponent | undefined;
|
|
40
|
+
|
|
41
|
+
if (!skillComponent) {
|
|
42
|
+
output.error("SkillComponent not available");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
output.log(chalk.blue("Reloading skills..."));
|
|
47
|
+
|
|
48
|
+
await skillComponent.reload();
|
|
49
|
+
|
|
50
|
+
const skills = skillComponent.getSkillList();
|
|
51
|
+
output.log(chalk.green(`Successfully reloaded ${skills.length} skills`));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
output.error(`Failed to reload skills: ${error}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
} finally {
|
|
56
|
+
await envService.dispose();
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|