@ddlqhd/agent-sdk 0.1.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 +53 -0
- package/dist/chunk-5QMA2YBY.cjs +2880 -0
- package/dist/chunk-5QMA2YBY.cjs.map +1 -0
- package/dist/chunk-5Y56A64C.cjs +5 -0
- package/dist/chunk-5Y56A64C.cjs.map +1 -0
- package/dist/chunk-A3S3AGE3.js +3 -0
- package/dist/chunk-A3S3AGE3.js.map +1 -0
- package/dist/chunk-CNSGZVRN.cjs +152 -0
- package/dist/chunk-CNSGZVRN.cjs.map +1 -0
- package/dist/chunk-JF5AJQMU.cjs +2788 -0
- package/dist/chunk-JF5AJQMU.cjs.map +1 -0
- package/dist/chunk-NDSL7NPN.js +807 -0
- package/dist/chunk-NDSL7NPN.js.map +1 -0
- package/dist/chunk-OHXW2YM6.js +2708 -0
- package/dist/chunk-OHXW2YM6.js.map +1 -0
- package/dist/chunk-Q3SOMX26.js +2854 -0
- package/dist/chunk-Q3SOMX26.js.map +1 -0
- package/dist/chunk-WH3APNQ5.js +147 -0
- package/dist/chunk-WH3APNQ5.js.map +1 -0
- package/dist/chunk-X35MHWXE.cjs +817 -0
- package/dist/chunk-X35MHWXE.cjs.map +1 -0
- package/dist/cli/index.cjs +926 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +24 -0
- package/dist/cli/index.d.ts +24 -0
- package/dist/cli/index.js +916 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index-DPsZ1zat.d.ts +447 -0
- package/dist/index-RTPmFjMp.d.cts +447 -0
- package/dist/index.cjs +508 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +664 -0
- package/dist/index.d.ts +664 -0
- package/dist/index.js +204 -0
- package/dist/index.js.map +1 -0
- package/dist/models/index.cjs +62 -0
- package/dist/models/index.cjs.map +1 -0
- package/dist/models/index.d.cts +165 -0
- package/dist/models/index.d.ts +165 -0
- package/dist/models/index.js +5 -0
- package/dist/models/index.js.map +1 -0
- package/dist/tools/index.cjs +207 -0
- package/dist/tools/index.cjs.map +1 -0
- package/dist/tools/index.d.cts +108 -0
- package/dist/tools/index.d.ts +108 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types-C0aX_Qdp.d.cts +917 -0
- package/dist/types-C0aX_Qdp.d.ts +917 -0
- package/package.json +80 -0
|
@@ -0,0 +1,2880 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var chunkJF5AJQMU_cjs = require('./chunk-JF5AJQMU.cjs');
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var crypto = require('crypto');
|
|
8
|
+
var os = require('os');
|
|
9
|
+
var index_js = require('@modelcontextprotocol/sdk/client/index.js');
|
|
10
|
+
var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
11
|
+
var streamableHttp_js = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
|
|
12
|
+
var zod = require('zod');
|
|
13
|
+
var child_process = require('child_process');
|
|
14
|
+
var util = require('util');
|
|
15
|
+
|
|
16
|
+
var JsonlStorage = class {
|
|
17
|
+
basePath;
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
this.basePath = config.basePath || "./sessions";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 获取会话文件路径
|
|
23
|
+
*/
|
|
24
|
+
getFilePath(sessionId) {
|
|
25
|
+
const safeId = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
26
|
+
return path.join(this.basePath, `${safeId}.jsonl`);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 获取元数据文件路径
|
|
30
|
+
*/
|
|
31
|
+
getMetaFilePath(sessionId) {
|
|
32
|
+
const safeId = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
33
|
+
return path.join(this.basePath, `${safeId}.meta.json`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 确保目录存在
|
|
37
|
+
*/
|
|
38
|
+
async ensureDir() {
|
|
39
|
+
await fs.promises.mkdir(this.basePath, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 保存消息(追加模式)
|
|
43
|
+
*/
|
|
44
|
+
async save(sessionId, messages) {
|
|
45
|
+
await this.ensureDir();
|
|
46
|
+
const filePath = this.getFilePath(sessionId);
|
|
47
|
+
const metaPath = this.getMetaFilePath(sessionId);
|
|
48
|
+
let existingCount = 0;
|
|
49
|
+
try {
|
|
50
|
+
const content = await fs.promises.readFile(filePath, "utf-8");
|
|
51
|
+
existingCount = content.split("\n").filter(Boolean).length;
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
const newMessages = messages.slice(existingCount);
|
|
55
|
+
if (newMessages.length > 0) {
|
|
56
|
+
const lines = newMessages.map((msg) => {
|
|
57
|
+
const record = {
|
|
58
|
+
...msg,
|
|
59
|
+
timestamp: msg.timestamp || Date.now()
|
|
60
|
+
};
|
|
61
|
+
return JSON.stringify(record);
|
|
62
|
+
}).join("\n") + "\n";
|
|
63
|
+
await fs.promises.appendFile(filePath, lines, "utf-8");
|
|
64
|
+
}
|
|
65
|
+
const meta = {
|
|
66
|
+
id: sessionId,
|
|
67
|
+
createdAt: Date.now(),
|
|
68
|
+
updatedAt: Date.now(),
|
|
69
|
+
messageCount: messages.length
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
const existingMeta = JSON.parse(await fs.promises.readFile(metaPath, "utf-8"));
|
|
73
|
+
meta.createdAt = existingMeta.createdAt;
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
await fs.promises.writeFile(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 加载消息
|
|
80
|
+
*/
|
|
81
|
+
async load(sessionId) {
|
|
82
|
+
const filePath = this.getFilePath(sessionId);
|
|
83
|
+
try {
|
|
84
|
+
const content = await fs.promises.readFile(filePath, "utf-8");
|
|
85
|
+
const lines = content.split("\n").filter(Boolean);
|
|
86
|
+
return lines.map((line) => {
|
|
87
|
+
const parsed = JSON.parse(line);
|
|
88
|
+
const { timestamp, ...message } = parsed;
|
|
89
|
+
return message;
|
|
90
|
+
});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error.code === "ENOENT") {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 列出所有会话
|
|
100
|
+
*/
|
|
101
|
+
async list() {
|
|
102
|
+
await this.ensureDir();
|
|
103
|
+
try {
|
|
104
|
+
const files = await fs.promises.readdir(this.basePath);
|
|
105
|
+
const metaFiles = files.filter((f) => f.endsWith(".meta.json"));
|
|
106
|
+
const sessions = [];
|
|
107
|
+
for (const metaFile of metaFiles) {
|
|
108
|
+
try {
|
|
109
|
+
const metaPath = path.join(this.basePath, metaFile);
|
|
110
|
+
const meta = JSON.parse(await fs.promises.readFile(metaPath, "utf-8"));
|
|
111
|
+
sessions.push(meta);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
116
|
+
} catch {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 删除会话
|
|
122
|
+
*/
|
|
123
|
+
async delete(sessionId) {
|
|
124
|
+
const filePath = this.getFilePath(sessionId);
|
|
125
|
+
const metaPath = this.getMetaFilePath(sessionId);
|
|
126
|
+
await Promise.all([
|
|
127
|
+
fs.promises.unlink(filePath).catch(() => {
|
|
128
|
+
}),
|
|
129
|
+
fs.promises.unlink(metaPath).catch(() => {
|
|
130
|
+
})
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 检查会话是否存在
|
|
135
|
+
*/
|
|
136
|
+
async exists(sessionId) {
|
|
137
|
+
const filePath = this.getFilePath(sessionId);
|
|
138
|
+
try {
|
|
139
|
+
await fs.promises.access(filePath);
|
|
140
|
+
return true;
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 清空所有会话
|
|
147
|
+
*/
|
|
148
|
+
async clear() {
|
|
149
|
+
await this.ensureDir();
|
|
150
|
+
try {
|
|
151
|
+
const files = await fs.promises.readdir(this.basePath);
|
|
152
|
+
await Promise.all(
|
|
153
|
+
files.map((file) => fs.promises.unlink(path.join(this.basePath, file)).catch(() => {
|
|
154
|
+
}))
|
|
155
|
+
);
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 获取会话统计
|
|
161
|
+
*/
|
|
162
|
+
async getStats(sessionId) {
|
|
163
|
+
const filePath = this.getFilePath(sessionId);
|
|
164
|
+
const metaPath = this.getMetaFilePath(sessionId);
|
|
165
|
+
try {
|
|
166
|
+
const [metaContent, fileStat] = await Promise.all([
|
|
167
|
+
fs.promises.readFile(metaPath, "utf-8"),
|
|
168
|
+
fs.promises.stat(filePath)
|
|
169
|
+
]);
|
|
170
|
+
const meta = JSON.parse(metaContent);
|
|
171
|
+
return {
|
|
172
|
+
messageCount: meta.messageCount,
|
|
173
|
+
createdAt: meta.createdAt,
|
|
174
|
+
updatedAt: meta.updatedAt,
|
|
175
|
+
size: fileStat.size
|
|
176
|
+
};
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
function createJsonlStorage(config) {
|
|
183
|
+
return new JsonlStorage(config);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/storage/memory.ts
|
|
187
|
+
var MemoryStorage = class {
|
|
188
|
+
sessions = /* @__PURE__ */ new Map();
|
|
189
|
+
metadata = /* @__PURE__ */ new Map();
|
|
190
|
+
/**
|
|
191
|
+
* 保存消息
|
|
192
|
+
*/
|
|
193
|
+
async save(sessionId, messages) {
|
|
194
|
+
this.sessions.set(sessionId, [...messages]);
|
|
195
|
+
const existing = this.metadata.get(sessionId);
|
|
196
|
+
const now = Date.now();
|
|
197
|
+
this.metadata.set(sessionId, {
|
|
198
|
+
id: sessionId,
|
|
199
|
+
createdAt: existing?.createdAt || now,
|
|
200
|
+
updatedAt: now,
|
|
201
|
+
messageCount: messages.length
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 加载消息
|
|
206
|
+
*/
|
|
207
|
+
async load(sessionId) {
|
|
208
|
+
return this.sessions.get(sessionId) || [];
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 列出所有会话
|
|
212
|
+
*/
|
|
213
|
+
async list() {
|
|
214
|
+
return Array.from(this.metadata.values()).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* 删除会话
|
|
218
|
+
*/
|
|
219
|
+
async delete(sessionId) {
|
|
220
|
+
this.sessions.delete(sessionId);
|
|
221
|
+
this.metadata.delete(sessionId);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 检查会话是否存在
|
|
225
|
+
*/
|
|
226
|
+
async exists(sessionId) {
|
|
227
|
+
return this.sessions.has(sessionId);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 清空所有会话
|
|
231
|
+
*/
|
|
232
|
+
async clear() {
|
|
233
|
+
this.sessions.clear();
|
|
234
|
+
this.metadata.clear();
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* 获取会话数量
|
|
238
|
+
*/
|
|
239
|
+
get size() {
|
|
240
|
+
return this.sessions.size;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* 导出所有数据
|
|
244
|
+
*/
|
|
245
|
+
export() {
|
|
246
|
+
const result = {};
|
|
247
|
+
for (const [key, value] of this.sessions) {
|
|
248
|
+
result[key] = [...value];
|
|
249
|
+
}
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* 导入数据
|
|
254
|
+
*/
|
|
255
|
+
import(data) {
|
|
256
|
+
for (const [sessionId, messages] of Object.entries(data)) {
|
|
257
|
+
this.save(sessionId, messages);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
function createMemoryStorage() {
|
|
262
|
+
return new MemoryStorage();
|
|
263
|
+
}
|
|
264
|
+
var SessionManager = class {
|
|
265
|
+
storage;
|
|
266
|
+
currentSessionId = null;
|
|
267
|
+
constructor(config) {
|
|
268
|
+
this.storage = createStorage(config);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* 获取当前会话 ID
|
|
272
|
+
*/
|
|
273
|
+
get sessionId() {
|
|
274
|
+
return this.currentSessionId;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* 创建新会话
|
|
278
|
+
*/
|
|
279
|
+
createSession(sessionId) {
|
|
280
|
+
this.currentSessionId = sessionId || crypto.randomUUID();
|
|
281
|
+
return this.currentSessionId;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* 恢复会话
|
|
285
|
+
*/
|
|
286
|
+
async resumeSession(sessionId) {
|
|
287
|
+
const exists = await this.storage.exists(sessionId);
|
|
288
|
+
if (!exists) {
|
|
289
|
+
throw new Error(`Session "${sessionId}" not found`);
|
|
290
|
+
}
|
|
291
|
+
this.currentSessionId = sessionId;
|
|
292
|
+
return this.storage.load(sessionId);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* 保存消息到当前会话
|
|
296
|
+
*/
|
|
297
|
+
async saveMessages(messages) {
|
|
298
|
+
if (!this.currentSessionId) {
|
|
299
|
+
this.createSession();
|
|
300
|
+
}
|
|
301
|
+
await this.storage.save(this.currentSessionId, messages);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* 加载当前会话消息
|
|
305
|
+
*/
|
|
306
|
+
async loadMessages() {
|
|
307
|
+
if (!this.currentSessionId) {
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
return this.storage.load(this.currentSessionId);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* 追加消息
|
|
314
|
+
*/
|
|
315
|
+
async appendMessage(message) {
|
|
316
|
+
if (!this.currentSessionId) {
|
|
317
|
+
this.createSession();
|
|
318
|
+
}
|
|
319
|
+
const messages = await this.loadMessages();
|
|
320
|
+
messages.push(message);
|
|
321
|
+
await this.saveMessages(messages);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* 列出所有会话
|
|
325
|
+
*/
|
|
326
|
+
async listSessions() {
|
|
327
|
+
return this.storage.list();
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* 删除会话
|
|
331
|
+
*/
|
|
332
|
+
async deleteSession(sessionId) {
|
|
333
|
+
await this.storage.delete(sessionId);
|
|
334
|
+
if (this.currentSessionId === sessionId) {
|
|
335
|
+
this.currentSessionId = null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* 检查会话是否存在
|
|
340
|
+
*/
|
|
341
|
+
async sessionExists(sessionId) {
|
|
342
|
+
return this.storage.exists(sessionId);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* 获取会话信息
|
|
346
|
+
*/
|
|
347
|
+
async getSessionInfo(sessionId) {
|
|
348
|
+
const sessions = await this.storage.list();
|
|
349
|
+
return sessions.find((s) => s.id === sessionId) || null;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* 清空当前会话
|
|
353
|
+
*/
|
|
354
|
+
async clearCurrentSession() {
|
|
355
|
+
if (this.currentSessionId) {
|
|
356
|
+
await this.storage.delete(this.currentSessionId);
|
|
357
|
+
this.currentSessionId = null;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* 获取底层存储适配器
|
|
362
|
+
*/
|
|
363
|
+
getStorage() {
|
|
364
|
+
return this.storage;
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
function createSessionManager(config) {
|
|
368
|
+
return new SessionManager(config);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/storage/interface.ts
|
|
372
|
+
function createStorage(config) {
|
|
373
|
+
switch (config?.type) {
|
|
374
|
+
case "memory":
|
|
375
|
+
return new MemoryStorage();
|
|
376
|
+
case "jsonl":
|
|
377
|
+
default:
|
|
378
|
+
return new JsonlStorage({ basePath: config?.basePath });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function getSessionStoragePath(userBasePath) {
|
|
382
|
+
return path.join(userBasePath || os.homedir(), ".claude", "sessions");
|
|
383
|
+
}
|
|
384
|
+
async function getLatestSessionId(userBasePath) {
|
|
385
|
+
const sm = new SessionManager({
|
|
386
|
+
type: "jsonl",
|
|
387
|
+
basePath: getSessionStoragePath(userBasePath)
|
|
388
|
+
});
|
|
389
|
+
const list = await sm.listSessions();
|
|
390
|
+
return list[0]?.id;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/core/prompts.ts
|
|
394
|
+
var DEFAULT_SYSTEM_PROMPT = `You are an AI assistant powered by the Agent SDK. You can help users with various tasks by using your built-in tools and capabilities.
|
|
395
|
+
|
|
396
|
+
## Core Capabilities
|
|
397
|
+
|
|
398
|
+
### Tools
|
|
399
|
+
You have access to a set of tools that allow you to:
|
|
400
|
+
- **File Operations**: read, write, list, delete files and directories
|
|
401
|
+
- **Code Execution**: run shell commands, Python scripts, Node.js code
|
|
402
|
+
- **Web Access**: make HTTP requests, fetch webpages, download files
|
|
403
|
+
- **Custom Tools**: additional tools registered by the user or skills
|
|
404
|
+
|
|
405
|
+
When to use tools:
|
|
406
|
+
- Use tools when the task requires real-world actions (file I/O, computation, API calls)
|
|
407
|
+
- Prefer reading files before modifying them
|
|
408
|
+
- Use the simplest tool that gets the job done
|
|
409
|
+
- Run multiple independent tool calls in parallel when possible
|
|
410
|
+
|
|
411
|
+
**Prefer dedicated tools over Bash:** Do not use \`Bash\` to do work that has a first-class tool. This keeps actions reviewable and consistent.
|
|
412
|
+
- **Read** for file contents \u2014 not \`cat\`, \`head\`, \`tail\`, or \`sed\` to print files
|
|
413
|
+
- **Write** to create or overwrite files \u2014 not shell redirection or heredocs
|
|
414
|
+
- **Edit** for targeted file changes \u2014 not \`sed\`, \`awk\`, or ad-hoc scripts to patch files
|
|
415
|
+
- **Glob** to find paths by pattern \u2014 not \`find\` or \`ls\` for discovery
|
|
416
|
+
- **Grep** to search file contents \u2014 not \`grep\` or \`rg\` in the shell (built-in line-by-line regex search; correct integration)
|
|
417
|
+
- **WebFetch** / **WebSearch** when the task needs HTTP or web search (when configured)
|
|
418
|
+
|
|
419
|
+
Reserve **Bash** for real shell needs: \`git\`, package managers, build commands, compilers, and other operations that require a shell or are not covered above.
|
|
420
|
+
|
|
421
|
+
### Skills
|
|
422
|
+
Skills are instruction guides for specialized tasks. When activated, you receive the skill's full content including any referenced file paths.
|
|
423
|
+
|
|
424
|
+
{{SKILL_LIST}}
|
|
425
|
+
|
|
426
|
+
**Usage:**
|
|
427
|
+
- **Listing skills**: When the user asks about available skills (e.g., "what skills do you have", "\u4F60\u6709\u54EA\u4E9B\u6280\u80FD", "list your skills") \u2192 Simply describe the skills listed above. Do NOT activate any skill.
|
|
428
|
+
- **Activating skills**: When the user has a specific task that matches a skill's purpose \u2192 Call \`Skill\` with the skill name, then follow the returned instructions.
|
|
429
|
+
- After activation, use the provided Base Path to read any referenced files.
|
|
430
|
+
|
|
431
|
+
### Sessions
|
|
432
|
+
- Conversations are persisted in sessions
|
|
433
|
+
- Use session IDs to maintain context across multiple interactions
|
|
434
|
+
- Previous messages provide important context for current tasks
|
|
435
|
+
|
|
436
|
+
## Task Execution Principles
|
|
437
|
+
|
|
438
|
+
1. **Plan First for Complex Tasks**: For multi-step tasks, you MUST call \`TaskCreate\` BEFORE any other tool. Do NOT skip this step.
|
|
439
|
+
2. **Be Direct**: Go straight to the point. Try the simplest approach first.
|
|
440
|
+
3. **Be Concise**: If you can say it in one sentence, don't use three.
|
|
441
|
+
4. **Read Before Modify**: Always understand existing code before changing it.
|
|
442
|
+
5. **No Over-Engineering**: Only make changes directly requested or clearly necessary.
|
|
443
|
+
6. **Prefer Edit Over Create**: Modify existing files rather than creating new ones when appropriate.
|
|
444
|
+
7. **Handle Errors Gracefully**: Report errors clearly with actionable suggestions.
|
|
445
|
+
|
|
446
|
+
## Task Management with Todo List
|
|
447
|
+
|
|
448
|
+
**MANDATORY**: For multi-step tasks, call \`TaskCreate\` FIRST.
|
|
449
|
+
|
|
450
|
+
**Workflow:**
|
|
451
|
+
1. Receive complex task -> call \`TaskCreate\` immediately
|
|
452
|
+
2. Start first task (in_progress) -> complete -> mark completed
|
|
453
|
+
3. Move to next task -> repeat
|
|
454
|
+
4. Cancel tasks that become irrelevant
|
|
455
|
+
|
|
456
|
+
**Example:**
|
|
457
|
+
User: "Open Google, search X, summarize results, open first link, extract info"
|
|
458
|
+
-> Multi-step task detected -> call \`TaskCreate\` FIRST, then execute.
|
|
459
|
+
|
|
460
|
+
## Output Format
|
|
461
|
+
|
|
462
|
+
- Lead with the answer or action, not the reasoning
|
|
463
|
+
- Skip filler words and unnecessary preamble
|
|
464
|
+
- Use code blocks with language hints for code
|
|
465
|
+
- Structure longer responses with headers and lists
|
|
466
|
+
- Reference file paths with line numbers when relevant (e.g., \`src/index.ts:42\`)
|
|
467
|
+
|
|
468
|
+
## Security Guidelines
|
|
469
|
+
|
|
470
|
+
- Do not introduce security vulnerabilities (injection, XSS, etc.)
|
|
471
|
+
- Validate user inputs at boundaries
|
|
472
|
+
- Do not execute untrusted code without sandboxing
|
|
473
|
+
- Respect file system permissions and access controls
|
|
474
|
+
|
|
475
|
+
### High-risk actions (confirm with the user first)
|
|
476
|
+
|
|
477
|
+
There is no automatic approval UI: **ask in the conversation** (or use \`AskUserQuestion\`) before proceeding when an action is destructive, hard to reverse, or affects others. Examples:
|
|
478
|
+
- Deleting files or branches, dropping data, \`rm -rf\`, overwriting uncommitted work
|
|
479
|
+
- Hard-to-reverse git: force-push, \`reset --hard\`, rewriting published history, amending shared commits
|
|
480
|
+
- Actions visible outside this machine: pushing code, opening/closing/commenting on PRs or issues, sending messages, posting to external services, changing shared CI/CD or cloud permissions
|
|
481
|
+
- Broad dependency or infrastructure changes (e.g. major version bumps, lockfile rewrites) when impact is unclear
|
|
482
|
+
|
|
483
|
+
Default to explaining what you intend and getting explicit agreement unless the user already directed that exact action.
|
|
484
|
+
|
|
485
|
+
## Tool hooks
|
|
486
|
+
|
|
487
|
+
When hooks are configured (e.g. PreToolUse), a tool call may be **blocked** or its **inputs adjusted** before execution. If a tool fails with a message indicating a hook blocked or rejected the call, **do not** retry the identical tool call unchanged \u2014 read the reason, change your approach, or ask the user. Treat hook feedback as binding policy from the environment.
|
|
488
|
+
|
|
489
|
+
## Interaction Style
|
|
490
|
+
|
|
491
|
+
- Be helpful and proactive
|
|
492
|
+
- Ask clarifying questions when instructions are ambiguous
|
|
493
|
+
- Provide suggestions when you see opportunities for improvement
|
|
494
|
+
- Acknowledge limitations honestly
|
|
495
|
+
- Maintain a professional, friendly tone`;
|
|
496
|
+
var MemoryManager = class {
|
|
497
|
+
workspaceRoot;
|
|
498
|
+
userBasePath;
|
|
499
|
+
config;
|
|
500
|
+
constructor(workspaceRoot, config, userBasePath) {
|
|
501
|
+
this.workspaceRoot = workspaceRoot || process.cwd();
|
|
502
|
+
this.userBasePath = userBasePath || os.homedir();
|
|
503
|
+
this.config = config || {};
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Loads memory content from both user home ({userBasePath}/.claude/CLAUDE.md)
|
|
507
|
+
* and workspace root (./CLAUDE.md).
|
|
508
|
+
* @returns Combined memory content wrapped in system-minder tags
|
|
509
|
+
*/
|
|
510
|
+
loadMemory() {
|
|
511
|
+
const memories = [];
|
|
512
|
+
const userPath = path.join(this.userBasePath, ".claude", "CLAUDE.md");
|
|
513
|
+
if (fs.existsSync(userPath)) {
|
|
514
|
+
try {
|
|
515
|
+
const content = fs.readFileSync(userPath, "utf-8");
|
|
516
|
+
if (content.trim()) {
|
|
517
|
+
memories.push(`# User Memory
|
|
518
|
+
|
|
519
|
+
${content}`);
|
|
520
|
+
}
|
|
521
|
+
} catch (error) {
|
|
522
|
+
console.error(`Error reading user memory file: ${error}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const workspacePath = this.config.workspacePath || path.join(this.workspaceRoot, "CLAUDE.md");
|
|
526
|
+
if (fs.existsSync(workspacePath)) {
|
|
527
|
+
try {
|
|
528
|
+
const content = fs.readFileSync(workspacePath, "utf-8");
|
|
529
|
+
if (content.trim()) {
|
|
530
|
+
memories.push(`# Workspace Memory
|
|
531
|
+
|
|
532
|
+
${content}`);
|
|
533
|
+
}
|
|
534
|
+
} catch (error) {
|
|
535
|
+
console.error(`Error reading workspace memory file: ${error}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (memories.length === 0) {
|
|
539
|
+
return "";
|
|
540
|
+
}
|
|
541
|
+
const combinedContent = memories.join("\n\n");
|
|
542
|
+
return `<system-minder>
|
|
543
|
+
${combinedContent}
|
|
544
|
+
</system-minder>`;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Checks if memory files exist.
|
|
548
|
+
* @returns Object indicating existence of each memory file type
|
|
549
|
+
*/
|
|
550
|
+
checkMemoryFiles() {
|
|
551
|
+
const userPath = path.join(this.userBasePath, ".claude", "CLAUDE.md");
|
|
552
|
+
const workspacePath = this.config.workspacePath || path.join(this.workspaceRoot, "CLAUDE.md");
|
|
553
|
+
return {
|
|
554
|
+
userHome: fs.existsSync(userPath),
|
|
555
|
+
workspace: fs.existsSync(workspacePath)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
function isStdioConfig(config) {
|
|
560
|
+
return "command" in config;
|
|
561
|
+
}
|
|
562
|
+
var MCPClient = class {
|
|
563
|
+
client;
|
|
564
|
+
transport;
|
|
565
|
+
_name;
|
|
566
|
+
_connected = false;
|
|
567
|
+
_tools = [];
|
|
568
|
+
_serverInfo;
|
|
569
|
+
constructor(config) {
|
|
570
|
+
this._name = config.name;
|
|
571
|
+
this.client = new index_js.Client(
|
|
572
|
+
{ name: "agent-sdk-client", version: "0.1.0" },
|
|
573
|
+
{ capabilities: {} }
|
|
574
|
+
);
|
|
575
|
+
if (isStdioConfig(config)) {
|
|
576
|
+
this.transport = new stdio_js.StdioClientTransport({
|
|
577
|
+
command: config.command,
|
|
578
|
+
args: config.args,
|
|
579
|
+
env: config.env
|
|
580
|
+
});
|
|
581
|
+
} else {
|
|
582
|
+
this.transport = new streamableHttp_js.StreamableHTTPClientTransport(
|
|
583
|
+
new URL(config.url),
|
|
584
|
+
{ requestInit: { headers: config.headers } }
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async connect() {
|
|
589
|
+
if (this._connected) return;
|
|
590
|
+
await this.client.connect(this.transport);
|
|
591
|
+
this._connected = true;
|
|
592
|
+
const serverInfo = this.client.getServerVersion();
|
|
593
|
+
if (serverInfo) {
|
|
594
|
+
this._serverInfo = {
|
|
595
|
+
name: serverInfo.name,
|
|
596
|
+
version: serverInfo.version
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
await this.listTools();
|
|
600
|
+
}
|
|
601
|
+
async disconnect() {
|
|
602
|
+
if (!this._connected) return;
|
|
603
|
+
await this.client.close();
|
|
604
|
+
this._connected = false;
|
|
605
|
+
}
|
|
606
|
+
async listTools() {
|
|
607
|
+
const result = await this.client.listTools();
|
|
608
|
+
this._tools = result.tools.map((tool) => ({
|
|
609
|
+
name: tool.name,
|
|
610
|
+
description: tool.description,
|
|
611
|
+
inputSchema: tool.inputSchema
|
|
612
|
+
}));
|
|
613
|
+
return this._tools;
|
|
614
|
+
}
|
|
615
|
+
async callTool(name, args) {
|
|
616
|
+
try {
|
|
617
|
+
const result = await this.client.callTool({
|
|
618
|
+
name,
|
|
619
|
+
arguments: args
|
|
620
|
+
});
|
|
621
|
+
if ("toolResult" in result) {
|
|
622
|
+
return {
|
|
623
|
+
content: JSON.stringify(result.toolResult),
|
|
624
|
+
isError: false
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
const content = result.content.map((c) => {
|
|
628
|
+
if (c.type === "text") return c.text;
|
|
629
|
+
if (c.type === "image") return `[Image: ${c.mimeType}]`;
|
|
630
|
+
if (c.type === "resource") {
|
|
631
|
+
const res = c.resource;
|
|
632
|
+
if ("text" in res) return res.text;
|
|
633
|
+
if ("blob" in res) return `[Blob: ${res.mimeType}]`;
|
|
634
|
+
return "";
|
|
635
|
+
}
|
|
636
|
+
return JSON.stringify(c);
|
|
637
|
+
}).join("\n");
|
|
638
|
+
return {
|
|
639
|
+
content,
|
|
640
|
+
isError: result.isError ?? false
|
|
641
|
+
};
|
|
642
|
+
} catch (error) {
|
|
643
|
+
return {
|
|
644
|
+
content: `MCP tool error: ${error instanceof Error ? error.message : String(error)}`,
|
|
645
|
+
isError: true
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
async listResources() {
|
|
650
|
+
const result = await this.client.listResources();
|
|
651
|
+
return result.resources.map((r) => ({
|
|
652
|
+
uri: r.uri,
|
|
653
|
+
name: r.name,
|
|
654
|
+
description: r.description,
|
|
655
|
+
mimeType: r.mimeType
|
|
656
|
+
}));
|
|
657
|
+
}
|
|
658
|
+
async readResource(uri) {
|
|
659
|
+
const result = await this.client.readResource({ uri });
|
|
660
|
+
const content = result.contents[0];
|
|
661
|
+
if (!content) return "";
|
|
662
|
+
if ("text" in content) return content.text;
|
|
663
|
+
if ("blob" in content) return content.blob;
|
|
664
|
+
return "";
|
|
665
|
+
}
|
|
666
|
+
async listPrompts() {
|
|
667
|
+
const result = await this.client.listPrompts();
|
|
668
|
+
return result.prompts.map((p) => ({
|
|
669
|
+
name: p.name,
|
|
670
|
+
description: p.description,
|
|
671
|
+
arguments: p.arguments?.map((a) => ({
|
|
672
|
+
name: a.name,
|
|
673
|
+
description: a.description,
|
|
674
|
+
required: a.required
|
|
675
|
+
}))
|
|
676
|
+
}));
|
|
677
|
+
}
|
|
678
|
+
async getPrompt(name, args) {
|
|
679
|
+
const result = await this.client.getPrompt({
|
|
680
|
+
name,
|
|
681
|
+
arguments: args
|
|
682
|
+
});
|
|
683
|
+
return result.messages.map((m) => ({
|
|
684
|
+
role: m.role,
|
|
685
|
+
content: m.content.type === "text" ? m.content.text : JSON.stringify(m.content)
|
|
686
|
+
}));
|
|
687
|
+
}
|
|
688
|
+
toToolDefinitions() {
|
|
689
|
+
return this._tools.map((tool) => ({
|
|
690
|
+
name: `mcp_${this._name}__${tool.name}`,
|
|
691
|
+
description: tool.description || `MCP tool: ${tool.name}`,
|
|
692
|
+
parameters: this.convertSchema(tool.inputSchema),
|
|
693
|
+
handler: async (args) => this.callTool(tool.name, args),
|
|
694
|
+
category: "mcp"
|
|
695
|
+
// 标记为 MCP 类别,用于输出处理策略选择
|
|
696
|
+
}));
|
|
697
|
+
}
|
|
698
|
+
convertSchema(schema) {
|
|
699
|
+
if (!schema || !schema.properties) {
|
|
700
|
+
return zod.z.object({}).passthrough();
|
|
701
|
+
}
|
|
702
|
+
const shape = {};
|
|
703
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
704
|
+
const field = value;
|
|
705
|
+
let zodField;
|
|
706
|
+
switch (field.type) {
|
|
707
|
+
case "string":
|
|
708
|
+
zodField = zod.z.string();
|
|
709
|
+
break;
|
|
710
|
+
case "number":
|
|
711
|
+
case "integer":
|
|
712
|
+
zodField = zod.z.number();
|
|
713
|
+
break;
|
|
714
|
+
case "boolean":
|
|
715
|
+
zodField = zod.z.boolean();
|
|
716
|
+
break;
|
|
717
|
+
case "array":
|
|
718
|
+
zodField = zod.z.array(zod.z.any());
|
|
719
|
+
break;
|
|
720
|
+
case "object":
|
|
721
|
+
zodField = zod.z.object({}).passthrough();
|
|
722
|
+
break;
|
|
723
|
+
default:
|
|
724
|
+
zodField = zod.z.any();
|
|
725
|
+
}
|
|
726
|
+
if (field.description) {
|
|
727
|
+
zodField = zodField.describe(field.description);
|
|
728
|
+
}
|
|
729
|
+
if (!schema.required?.includes(key)) {
|
|
730
|
+
zodField = zodField.optional();
|
|
731
|
+
}
|
|
732
|
+
shape[key] = zodField;
|
|
733
|
+
}
|
|
734
|
+
return zod.z.object(shape);
|
|
735
|
+
}
|
|
736
|
+
get name() {
|
|
737
|
+
return this._name;
|
|
738
|
+
}
|
|
739
|
+
get connected() {
|
|
740
|
+
return this._connected;
|
|
741
|
+
}
|
|
742
|
+
get serverInfo() {
|
|
743
|
+
return this._serverInfo;
|
|
744
|
+
}
|
|
745
|
+
get tools() {
|
|
746
|
+
return this._tools;
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
function createMCPClient(config) {
|
|
750
|
+
return new MCPClient(config);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/mcp/adapter.ts
|
|
754
|
+
var MCPAdapter = class {
|
|
755
|
+
clients = /* @__PURE__ */ new Map();
|
|
756
|
+
toolMap = /* @__PURE__ */ new Map();
|
|
757
|
+
async addServer(config) {
|
|
758
|
+
if (this.clients.has(config.name)) {
|
|
759
|
+
throw new Error(`MCP server "${config.name}" already exists`);
|
|
760
|
+
}
|
|
761
|
+
const client = new MCPClient(config);
|
|
762
|
+
await client.connect();
|
|
763
|
+
this.clients.set(config.name, client);
|
|
764
|
+
for (const tool of client.tools) {
|
|
765
|
+
const fullName = `mcp_${config.name}__${tool.name}`;
|
|
766
|
+
this.toolMap.set(fullName, { client, toolName: tool.name });
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
async removeServer(name) {
|
|
770
|
+
const client = this.clients.get(name);
|
|
771
|
+
if (!client) return;
|
|
772
|
+
for (const [fullName, { client: c }] of this.toolMap.entries()) {
|
|
773
|
+
if (c === client) {
|
|
774
|
+
this.toolMap.delete(fullName);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
await client.disconnect();
|
|
778
|
+
this.clients.delete(name);
|
|
779
|
+
}
|
|
780
|
+
getToolDefinitions() {
|
|
781
|
+
const tools = [];
|
|
782
|
+
for (const client of this.clients.values()) {
|
|
783
|
+
tools.push(...client.toToolDefinitions());
|
|
784
|
+
}
|
|
785
|
+
return tools;
|
|
786
|
+
}
|
|
787
|
+
async executeTool(fullName, args) {
|
|
788
|
+
const mapping = this.toolMap.get(fullName);
|
|
789
|
+
if (!mapping) {
|
|
790
|
+
return {
|
|
791
|
+
content: `MCP tool "${fullName}" not found`,
|
|
792
|
+
isError: true
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
return mapping.client.callTool(mapping.toolName, args);
|
|
796
|
+
}
|
|
797
|
+
getClient(name) {
|
|
798
|
+
return this.clients.get(name);
|
|
799
|
+
}
|
|
800
|
+
getServerNames() {
|
|
801
|
+
return Array.from(this.clients.keys());
|
|
802
|
+
}
|
|
803
|
+
isConnected(name) {
|
|
804
|
+
const client = this.clients.get(name);
|
|
805
|
+
return client?.connected ?? false;
|
|
806
|
+
}
|
|
807
|
+
async disconnectAll() {
|
|
808
|
+
for (const client of this.clients.values()) {
|
|
809
|
+
await client.disconnect();
|
|
810
|
+
}
|
|
811
|
+
this.clients.clear();
|
|
812
|
+
this.toolMap.clear();
|
|
813
|
+
}
|
|
814
|
+
async listAllTools() {
|
|
815
|
+
const result = /* @__PURE__ */ new Map();
|
|
816
|
+
for (const [name, client] of this.clients) {
|
|
817
|
+
const tools = await client.listTools();
|
|
818
|
+
result.set(name, tools);
|
|
819
|
+
}
|
|
820
|
+
return result;
|
|
821
|
+
}
|
|
822
|
+
async listAllResources() {
|
|
823
|
+
const result = /* @__PURE__ */ new Map();
|
|
824
|
+
for (const [name, client] of this.clients) {
|
|
825
|
+
const resources = await client.listResources();
|
|
826
|
+
result.set(name, resources);
|
|
827
|
+
}
|
|
828
|
+
return result;
|
|
829
|
+
}
|
|
830
|
+
get size() {
|
|
831
|
+
return this.clients.size;
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
function createMCPAdapter() {
|
|
835
|
+
return new MCPAdapter();
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/skills/parser.ts
|
|
839
|
+
function parseSkillMd(content) {
|
|
840
|
+
const lines = content.split("\n");
|
|
841
|
+
let metadataEndIndex = -1;
|
|
842
|
+
let metadataStartIndex = -1;
|
|
843
|
+
for (let i = 0; i < lines.length; i++) {
|
|
844
|
+
const line = lines[i].trim();
|
|
845
|
+
if (line === "---") {
|
|
846
|
+
if (metadataStartIndex === -1) {
|
|
847
|
+
metadataStartIndex = i;
|
|
848
|
+
} else {
|
|
849
|
+
metadataEndIndex = i;
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
let metadata = {
|
|
855
|
+
name: "unknown",
|
|
856
|
+
description: ""
|
|
857
|
+
};
|
|
858
|
+
let bodyContent = content;
|
|
859
|
+
if (metadataStartIndex !== -1 && metadataEndIndex !== -1) {
|
|
860
|
+
const yamlContent = lines.slice(metadataStartIndex + 1, metadataEndIndex).join("\n");
|
|
861
|
+
metadata = parseSimpleYaml(yamlContent);
|
|
862
|
+
bodyContent = lines.slice(metadataEndIndex + 1).join("\n").trim();
|
|
863
|
+
}
|
|
864
|
+
if (metadata.name === "unknown") {
|
|
865
|
+
const titleMatch = bodyContent.match(/^#\s+(.+)$/m);
|
|
866
|
+
if (titleMatch) {
|
|
867
|
+
metadata.name = titleMatch[1].toLowerCase().replace(/\s+/g, "-");
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return {
|
|
871
|
+
metadata,
|
|
872
|
+
content: bodyContent
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function parseSimpleYaml(yaml) {
|
|
876
|
+
const metadata = {
|
|
877
|
+
name: "unknown",
|
|
878
|
+
description: ""
|
|
879
|
+
};
|
|
880
|
+
const lines = yaml.split("\n");
|
|
881
|
+
let currentKey = null;
|
|
882
|
+
let currentArray = [];
|
|
883
|
+
for (const line of lines) {
|
|
884
|
+
const trimmed = line.trim();
|
|
885
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
886
|
+
if (trimmed.startsWith("- ")) {
|
|
887
|
+
if (currentKey && currentArray) {
|
|
888
|
+
currentArray.push(trimmed.slice(2).replace(/^["']|["']$/g, ""));
|
|
889
|
+
}
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
if (currentKey && currentArray.length > 0) {
|
|
893
|
+
metadata[currentKey] = currentArray;
|
|
894
|
+
currentArray = [];
|
|
895
|
+
currentKey = null;
|
|
896
|
+
}
|
|
897
|
+
const match = trimmed.match(/^(\w+):\s*(.*)$/);
|
|
898
|
+
if (match) {
|
|
899
|
+
const [, key, value] = match;
|
|
900
|
+
if (value === "" || value === "[]") {
|
|
901
|
+
currentKey = key;
|
|
902
|
+
currentArray = [];
|
|
903
|
+
if (value === "[]") {
|
|
904
|
+
metadata[key] = [];
|
|
905
|
+
currentKey = null;
|
|
906
|
+
}
|
|
907
|
+
} else {
|
|
908
|
+
let parsedValue = value.replace(/^["']|["']$/g, "");
|
|
909
|
+
if (parsedValue === "true") {
|
|
910
|
+
parsedValue = true;
|
|
911
|
+
} else if (parsedValue === "false") {
|
|
912
|
+
parsedValue = false;
|
|
913
|
+
} else if (parsedValue.startsWith("[") && parsedValue.endsWith("]")) {
|
|
914
|
+
parsedValue = parsedValue.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""));
|
|
915
|
+
}
|
|
916
|
+
metadata[key] = parsedValue;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (currentKey && currentArray.length > 0) {
|
|
921
|
+
metadata[currentKey] = currentArray;
|
|
922
|
+
}
|
|
923
|
+
return metadata;
|
|
924
|
+
}
|
|
925
|
+
function inferMetadataFromPath(skillPath) {
|
|
926
|
+
const pathParts = skillPath.replace(/\\/g, "/").split("/");
|
|
927
|
+
const dirName = pathParts[pathParts.length - 1] || pathParts[pathParts.length - 2];
|
|
928
|
+
return {
|
|
929
|
+
name: dirName
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
var SkillLoader = class {
|
|
933
|
+
config;
|
|
934
|
+
constructor(config = {}) {
|
|
935
|
+
this.config = {
|
|
936
|
+
cwd: process.cwd(),
|
|
937
|
+
...config
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* 加载单个 Skill
|
|
942
|
+
*/
|
|
943
|
+
async load(skillPath) {
|
|
944
|
+
const resolvedPath = path.resolve(this.config.cwd, skillPath);
|
|
945
|
+
const stat = await this.getPathType(resolvedPath);
|
|
946
|
+
if (stat === "file") {
|
|
947
|
+
return this.loadFromFile(resolvedPath);
|
|
948
|
+
} else if (stat === "directory") {
|
|
949
|
+
return this.loadFromDirectory(resolvedPath);
|
|
950
|
+
}
|
|
951
|
+
throw new Error(`Skill path not found: ${resolvedPath}`);
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* 从文件加载 Skill
|
|
955
|
+
*/
|
|
956
|
+
async loadFromFile(filePath) {
|
|
957
|
+
const content = await fs.promises.readFile(filePath, "utf-8");
|
|
958
|
+
const parsed = parseSkillMd(content);
|
|
959
|
+
const metadata = parsed.metadata;
|
|
960
|
+
if (!metadata.name || metadata.name === "unknown") {
|
|
961
|
+
const inferred = inferMetadataFromPath(filePath);
|
|
962
|
+
if (inferred.name) {
|
|
963
|
+
parsed.metadata.name = inferred.name;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return {
|
|
967
|
+
metadata: parsed.metadata,
|
|
968
|
+
path: filePath,
|
|
969
|
+
instructions: parsed.content
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* 从目录加载 Skill
|
|
974
|
+
*/
|
|
975
|
+
async loadFromDirectory(dirPath) {
|
|
976
|
+
const skillMdPath = path.join(dirPath, "SKILL.md");
|
|
977
|
+
try {
|
|
978
|
+
await fs.promises.access(skillMdPath);
|
|
979
|
+
} catch {
|
|
980
|
+
throw new Error(`SKILL.md not found in ${dirPath}`);
|
|
981
|
+
}
|
|
982
|
+
const content = await fs.promises.readFile(skillMdPath, "utf-8");
|
|
983
|
+
const parsed = parseSkillMd(content);
|
|
984
|
+
const dirMetadata = parsed.metadata;
|
|
985
|
+
if (!dirMetadata.name || dirMetadata.name === "unknown") {
|
|
986
|
+
const inferred = inferMetadataFromPath(dirPath);
|
|
987
|
+
if (inferred.name) {
|
|
988
|
+
parsed.metadata.name = inferred.name;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
metadata: parsed.metadata,
|
|
993
|
+
path: dirPath,
|
|
994
|
+
instructions: parsed.content
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* 加载目录下的所有 Skills
|
|
999
|
+
*/
|
|
1000
|
+
async loadAll(dirPath) {
|
|
1001
|
+
const resolvedPath = path.resolve(this.config.cwd, dirPath);
|
|
1002
|
+
const skills = [];
|
|
1003
|
+
try {
|
|
1004
|
+
const entries = await fs.promises.readdir(resolvedPath, { withFileTypes: true });
|
|
1005
|
+
for (const entry of entries) {
|
|
1006
|
+
const entryPath = path.join(resolvedPath, entry.name);
|
|
1007
|
+
if (this.config.filter && !this.config.filter(entryPath)) {
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
try {
|
|
1011
|
+
if (entry.isDirectory()) {
|
|
1012
|
+
const hasSkillMd = await this.hasFile(entryPath, "SKILL.md");
|
|
1013
|
+
if (hasSkillMd) {
|
|
1014
|
+
const skill = await this.loadFromDirectory(entryPath);
|
|
1015
|
+
skills.push(skill);
|
|
1016
|
+
}
|
|
1017
|
+
} else if (entry.name.endsWith(".md") && entry.name !== "SKILL.md") {
|
|
1018
|
+
const skill = await this.loadFromFile(entryPath);
|
|
1019
|
+
skills.push(skill);
|
|
1020
|
+
}
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
console.warn(`Failed to load skill from ${entryPath}:`, error);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
} catch {
|
|
1026
|
+
}
|
|
1027
|
+
return skills;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* 检查文件是否存在
|
|
1031
|
+
*/
|
|
1032
|
+
async hasFile(dirPath, fileName) {
|
|
1033
|
+
try {
|
|
1034
|
+
await fs.promises.access(path.join(dirPath, fileName));
|
|
1035
|
+
return true;
|
|
1036
|
+
} catch {
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* 获取路径类型
|
|
1042
|
+
*/
|
|
1043
|
+
async getPathType(path) {
|
|
1044
|
+
try {
|
|
1045
|
+
const stat = await fs.promises.stat(path);
|
|
1046
|
+
if (stat.isFile()) return "file";
|
|
1047
|
+
if (stat.isDirectory()) return "directory";
|
|
1048
|
+
return null;
|
|
1049
|
+
} catch {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
function createSkillLoader(config) {
|
|
1055
|
+
return new SkillLoader(config);
|
|
1056
|
+
}
|
|
1057
|
+
var SkillRegistry = class {
|
|
1058
|
+
skills = /* @__PURE__ */ new Map();
|
|
1059
|
+
loader;
|
|
1060
|
+
workspaceRoot;
|
|
1061
|
+
userBasePath;
|
|
1062
|
+
skillConfig;
|
|
1063
|
+
constructor(config) {
|
|
1064
|
+
this.loader = new SkillLoader(config);
|
|
1065
|
+
this.workspaceRoot = config?.cwd || process.cwd();
|
|
1066
|
+
this.userBasePath = config?.userBasePath || os.homedir();
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* 注册 Skill
|
|
1070
|
+
*/
|
|
1071
|
+
register(skill) {
|
|
1072
|
+
if (this.skills.has(skill.metadata.name)) {
|
|
1073
|
+
throw new Error(`Skill "${skill.metadata.name}" is already registered`);
|
|
1074
|
+
}
|
|
1075
|
+
this.skills.set(skill.metadata.name, skill);
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* 加载并注册 Skill
|
|
1079
|
+
*/
|
|
1080
|
+
async load(path) {
|
|
1081
|
+
const skill = await this.loader.load(path);
|
|
1082
|
+
this.register(skill);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* 加载目录下的所有 Skills
|
|
1086
|
+
*/
|
|
1087
|
+
async loadAll(dirPath) {
|
|
1088
|
+
const skills = await this.loader.loadAll(dirPath);
|
|
1089
|
+
for (const skill of skills) {
|
|
1090
|
+
try {
|
|
1091
|
+
this.register(skill);
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
console.warn(`Failed to register skill "${skill.metadata.name}":`, error);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* 注销 Skill
|
|
1099
|
+
*/
|
|
1100
|
+
unregister(name) {
|
|
1101
|
+
return this.skills.delete(name);
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* 获取 Skill
|
|
1105
|
+
*/
|
|
1106
|
+
get(name) {
|
|
1107
|
+
return this.skills.get(name);
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* 获取所有 Skill
|
|
1111
|
+
*/
|
|
1112
|
+
getAll() {
|
|
1113
|
+
return Array.from(this.skills.values());
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* 获取 Skill 名称列表
|
|
1117
|
+
*/
|
|
1118
|
+
getNames() {
|
|
1119
|
+
return Array.from(this.skills.keys());
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* 检查 Skill 是否存在
|
|
1123
|
+
*/
|
|
1124
|
+
has(name) {
|
|
1125
|
+
return this.skills.has(name);
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* 搜索 Skill
|
|
1129
|
+
*/
|
|
1130
|
+
search(query) {
|
|
1131
|
+
const lowerQuery = query.toLowerCase();
|
|
1132
|
+
return this.getAll().filter(
|
|
1133
|
+
(skill) => skill.metadata.name.toLowerCase().includes(lowerQuery) || skill.metadata.description.toLowerCase().includes(lowerQuery) || skill.metadata.tags?.some((tag) => tag.toLowerCase().includes(lowerQuery))
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* 按标签过滤
|
|
1138
|
+
*/
|
|
1139
|
+
filterByTag(tag) {
|
|
1140
|
+
return this.getAll().filter(
|
|
1141
|
+
(skill) => skill.metadata.tags?.includes(tag)
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* 获取 Skill 数量
|
|
1146
|
+
*/
|
|
1147
|
+
get size() {
|
|
1148
|
+
return this.skills.size;
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* 清空所有 Skill
|
|
1152
|
+
*/
|
|
1153
|
+
clear() {
|
|
1154
|
+
this.skills.clear();
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* 导出 Skill 信息
|
|
1158
|
+
*/
|
|
1159
|
+
export() {
|
|
1160
|
+
return this.getAll().map((skill) => ({
|
|
1161
|
+
name: skill.metadata.name,
|
|
1162
|
+
description: skill.metadata.description,
|
|
1163
|
+
version: skill.metadata.version,
|
|
1164
|
+
path: skill.path
|
|
1165
|
+
}));
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* 获取所有 Skill 的元数据列表(用于 System Prompt)
|
|
1169
|
+
*/
|
|
1170
|
+
getMetadataList() {
|
|
1171
|
+
return this.getAll().map((skill) => ({
|
|
1172
|
+
name: skill.metadata.name,
|
|
1173
|
+
description: skill.metadata.description,
|
|
1174
|
+
argumentHint: skill.metadata.argumentHint
|
|
1175
|
+
}));
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* 获取用户可调用的 Skills
|
|
1179
|
+
*/
|
|
1180
|
+
getUserInvocableSkills() {
|
|
1181
|
+
return this.getAll().filter((skill) => skill.metadata.userInvocable !== false).map((skill) => ({
|
|
1182
|
+
name: skill.metadata.name,
|
|
1183
|
+
description: skill.metadata.description,
|
|
1184
|
+
argumentHint: skill.metadata.argumentHint
|
|
1185
|
+
}));
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* 获取模型可自动调用的 Skills(用于注入到 system prompt)
|
|
1189
|
+
*/
|
|
1190
|
+
getModelInvocableSkills() {
|
|
1191
|
+
return this.getAll().filter((skill) => skill.metadata.disableModelInvocation !== true).map((skill) => ({
|
|
1192
|
+
name: skill.metadata.name,
|
|
1193
|
+
description: skill.metadata.description
|
|
1194
|
+
}));
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* 获取格式化的 Skill 列表文本(用于 System Prompt)
|
|
1198
|
+
*/
|
|
1199
|
+
getFormattedList() {
|
|
1200
|
+
const userInvocable = this.getUserInvocableSkills();
|
|
1201
|
+
const modelInvocable = this.getModelInvocableSkills();
|
|
1202
|
+
if (userInvocable.length === 0 && modelInvocable.length === 0) {
|
|
1203
|
+
return "No skills are currently available.";
|
|
1204
|
+
}
|
|
1205
|
+
const sections = [];
|
|
1206
|
+
if (userInvocable.length > 0) {
|
|
1207
|
+
const userSkillsText = userInvocable.map((s) => {
|
|
1208
|
+
const hint = s.argumentHint ? ` ${s.argumentHint}` : "";
|
|
1209
|
+
return `- **/${s.name}**${hint}: ${s.description}`;
|
|
1210
|
+
}).join("\n");
|
|
1211
|
+
sections.push(`**User-invocable Skills** (type /skill-name to invoke):
|
|
1212
|
+
${userSkillsText}`);
|
|
1213
|
+
}
|
|
1214
|
+
if (modelInvocable.length > 0) {
|
|
1215
|
+
const modelSkillsText = modelInvocable.map((s) => `- **${s.name}**: ${s.description}`).join("\n");
|
|
1216
|
+
sections.push(`**Auto-loadable Skills** (Claude can invoke when relevant):
|
|
1217
|
+
${modelSkillsText}`);
|
|
1218
|
+
}
|
|
1219
|
+
return sections.join("\n\n") + `
|
|
1220
|
+
|
|
1221
|
+
**Note:** Only activate a skill when you need to perform its specific task. For questions about your capabilities, simply describe the available skills.`;
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* 根据名称获取 Skill 路径
|
|
1225
|
+
*/
|
|
1226
|
+
getSkillPath(name) {
|
|
1227
|
+
const skill = this.skills.get(name);
|
|
1228
|
+
return skill?.path;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* 加载 Skill 全量内容
|
|
1232
|
+
*/
|
|
1233
|
+
async loadFullContent(name) {
|
|
1234
|
+
const skill = this.skills.get(name);
|
|
1235
|
+
if (!skill) {
|
|
1236
|
+
throw new Error(`Skill "${name}" not found`);
|
|
1237
|
+
}
|
|
1238
|
+
if (skill.path) {
|
|
1239
|
+
try {
|
|
1240
|
+
const pathStat = await fs.promises.stat(skill.path);
|
|
1241
|
+
let skillMdPath;
|
|
1242
|
+
if (pathStat.isDirectory()) {
|
|
1243
|
+
skillMdPath = path.join(skill.path, "SKILL.md");
|
|
1244
|
+
} else {
|
|
1245
|
+
skillMdPath = skill.path;
|
|
1246
|
+
}
|
|
1247
|
+
const content = await fs.promises.readFile(skillMdPath, "utf-8");
|
|
1248
|
+
return content;
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
throw new Error(`Failed to read skill file: ${error instanceof Error ? error.message : String(error)}`);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (skill.instructions) {
|
|
1254
|
+
return skill.instructions;
|
|
1255
|
+
}
|
|
1256
|
+
throw new Error(`No content available for skill "${name}"`);
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* 获取默认 skill 路径
|
|
1260
|
+
*/
|
|
1261
|
+
getDefaultPaths() {
|
|
1262
|
+
const paths = [];
|
|
1263
|
+
const userPath = path.join(this.userBasePath, ".claude", "skills");
|
|
1264
|
+
if (fs.existsSync(userPath)) {
|
|
1265
|
+
paths.push(userPath);
|
|
1266
|
+
}
|
|
1267
|
+
const workspacePath = this.skillConfig?.workspacePath || path.join(this.workspaceRoot, ".claude", "skills");
|
|
1268
|
+
if (fs.existsSync(workspacePath)) {
|
|
1269
|
+
paths.push(workspacePath);
|
|
1270
|
+
}
|
|
1271
|
+
return paths;
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* 初始化加载所有 Skills
|
|
1275
|
+
* @param config Skill 配置
|
|
1276
|
+
* @param additionalPaths 额外的 skill 路径(来自 AgentConfig.skills)
|
|
1277
|
+
*/
|
|
1278
|
+
async initialize(config, additionalPaths) {
|
|
1279
|
+
this.skillConfig = config;
|
|
1280
|
+
if (config?.autoLoad !== false) {
|
|
1281
|
+
const defaultPaths = this.getDefaultPaths();
|
|
1282
|
+
for (const dirPath of defaultPaths) {
|
|
1283
|
+
try {
|
|
1284
|
+
const beforeCount = this.skills.size;
|
|
1285
|
+
await this.loadAll(dirPath);
|
|
1286
|
+
const loaded = this.skills.size - beforeCount;
|
|
1287
|
+
if (loaded > 0) {
|
|
1288
|
+
console.log(`Loaded ${loaded} skill(s) from: ${dirPath}`);
|
|
1289
|
+
}
|
|
1290
|
+
} catch (err) {
|
|
1291
|
+
console.error(`Failed to load skills from "${dirPath}":`, err);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
const allPaths = [...config?.additionalPaths || [], ...additionalPaths || []];
|
|
1296
|
+
for (const path of allPaths) {
|
|
1297
|
+
try {
|
|
1298
|
+
await this.load(path);
|
|
1299
|
+
} catch (err) {
|
|
1300
|
+
console.error(`Failed to load skill from "${path}":`, err);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (this.skills.size > 0) {
|
|
1304
|
+
console.log(`Skills initialized: ${this.getNames().join(", ")}`);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
function createSkillRegistry(config) {
|
|
1309
|
+
return new SkillRegistry(config);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// src/streaming/chunk-processor.ts
|
|
1313
|
+
var StreamChunkProcessor = class {
|
|
1314
|
+
currentToolCall = null;
|
|
1315
|
+
lastUsage;
|
|
1316
|
+
inTextBlock = false;
|
|
1317
|
+
emitTextBoundaries;
|
|
1318
|
+
constructor(options) {
|
|
1319
|
+
this.emitTextBoundaries = options?.emitTextBoundaries ?? true;
|
|
1320
|
+
}
|
|
1321
|
+
processChunk(chunk) {
|
|
1322
|
+
const events = [];
|
|
1323
|
+
const endTextBlockIfNeeded = () => {
|
|
1324
|
+
if (this.emitTextBoundaries && this.inTextBlock) {
|
|
1325
|
+
events.push({ type: "text_end" });
|
|
1326
|
+
this.inTextBlock = false;
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
switch (chunk.type) {
|
|
1330
|
+
case "text":
|
|
1331
|
+
if (chunk.content) {
|
|
1332
|
+
if (this.emitTextBoundaries && !this.inTextBlock) {
|
|
1333
|
+
events.push({ type: "text_start" });
|
|
1334
|
+
this.inTextBlock = true;
|
|
1335
|
+
}
|
|
1336
|
+
events.push({ type: "text_delta", content: chunk.content });
|
|
1337
|
+
}
|
|
1338
|
+
break;
|
|
1339
|
+
case "tool_call_start":
|
|
1340
|
+
endTextBlockIfNeeded();
|
|
1341
|
+
if (this.currentToolCall) {
|
|
1342
|
+
events.push(...this.finalizeStreamingToolCall());
|
|
1343
|
+
}
|
|
1344
|
+
if (chunk.toolCall) {
|
|
1345
|
+
this.currentToolCall = {
|
|
1346
|
+
id: chunk.toolCall.id,
|
|
1347
|
+
name: chunk.toolCall.name,
|
|
1348
|
+
arguments: ""
|
|
1349
|
+
};
|
|
1350
|
+
events.push({
|
|
1351
|
+
type: "tool_call_start",
|
|
1352
|
+
id: chunk.toolCall.id,
|
|
1353
|
+
name: chunk.toolCall.name
|
|
1354
|
+
});
|
|
1355
|
+
} else if (chunk.toolCallId && chunk.content) {
|
|
1356
|
+
this.currentToolCall = {
|
|
1357
|
+
id: chunk.toolCallId,
|
|
1358
|
+
name: chunk.content,
|
|
1359
|
+
arguments: ""
|
|
1360
|
+
};
|
|
1361
|
+
events.push({
|
|
1362
|
+
type: "tool_call_start",
|
|
1363
|
+
id: chunk.toolCallId,
|
|
1364
|
+
name: chunk.content
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
break;
|
|
1368
|
+
case "tool_call_delta":
|
|
1369
|
+
if (this.currentToolCall && chunk.toolCallId === this.currentToolCall.id && chunk.content) {
|
|
1370
|
+
this.currentToolCall.arguments += chunk.content;
|
|
1371
|
+
events.push({
|
|
1372
|
+
type: "tool_call_delta",
|
|
1373
|
+
id: this.currentToolCall.id,
|
|
1374
|
+
arguments: chunk.content
|
|
1375
|
+
});
|
|
1376
|
+
} else if (chunk.toolCallId && chunk.content) {
|
|
1377
|
+
events.push({
|
|
1378
|
+
type: "tool_call_delta",
|
|
1379
|
+
id: chunk.toolCallId,
|
|
1380
|
+
arguments: chunk.content
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
break;
|
|
1384
|
+
case "tool_call": {
|
|
1385
|
+
endTextBlockIfNeeded();
|
|
1386
|
+
if (!chunk.toolCall) break;
|
|
1387
|
+
const tc = chunk.toolCall;
|
|
1388
|
+
if (this.currentToolCall?.id === tc.id) {
|
|
1389
|
+
this.currentToolCall = null;
|
|
1390
|
+
} else if (this.currentToolCall) {
|
|
1391
|
+
events.push(...this.finalizeStreamingToolCall());
|
|
1392
|
+
}
|
|
1393
|
+
events.push({ type: "tool_call_end", id: tc.id });
|
|
1394
|
+
events.push({
|
|
1395
|
+
type: "tool_call",
|
|
1396
|
+
id: tc.id,
|
|
1397
|
+
name: tc.name,
|
|
1398
|
+
arguments: tc.arguments
|
|
1399
|
+
});
|
|
1400
|
+
break;
|
|
1401
|
+
}
|
|
1402
|
+
case "tool_call_end":
|
|
1403
|
+
endTextBlockIfNeeded();
|
|
1404
|
+
if (this.currentToolCall) {
|
|
1405
|
+
events.push(...this.finalizeStreamingToolCall());
|
|
1406
|
+
}
|
|
1407
|
+
break;
|
|
1408
|
+
case "thinking":
|
|
1409
|
+
endTextBlockIfNeeded();
|
|
1410
|
+
if (chunk.content !== void 0) {
|
|
1411
|
+
events.push({
|
|
1412
|
+
type: "thinking",
|
|
1413
|
+
content: chunk.content,
|
|
1414
|
+
signature: chunk.signature
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
break;
|
|
1418
|
+
case "error":
|
|
1419
|
+
endTextBlockIfNeeded();
|
|
1420
|
+
if (chunk.error) {
|
|
1421
|
+
events.push({
|
|
1422
|
+
type: "end",
|
|
1423
|
+
timestamp: Date.now(),
|
|
1424
|
+
reason: "error",
|
|
1425
|
+
error: chunk.error
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
break;
|
|
1429
|
+
case "metadata":
|
|
1430
|
+
if (chunk.metadata?.usage) {
|
|
1431
|
+
const usage = chunk.metadata.usage;
|
|
1432
|
+
this.lastUsage = usage;
|
|
1433
|
+
events.push({
|
|
1434
|
+
type: "model_usage",
|
|
1435
|
+
usage,
|
|
1436
|
+
...chunk.usagePhase !== void 0 ? { phase: chunk.usagePhase } : {}
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
break;
|
|
1440
|
+
}
|
|
1441
|
+
return events;
|
|
1442
|
+
}
|
|
1443
|
+
/** End open text block and finalize any in-progress streamed tool call. */
|
|
1444
|
+
flush() {
|
|
1445
|
+
const events = [];
|
|
1446
|
+
if (this.emitTextBoundaries && this.inTextBlock) {
|
|
1447
|
+
events.push({ type: "text_end" });
|
|
1448
|
+
this.inTextBlock = false;
|
|
1449
|
+
}
|
|
1450
|
+
if (this.currentToolCall) {
|
|
1451
|
+
events.push(...this.finalizeStreamingToolCall());
|
|
1452
|
+
}
|
|
1453
|
+
return events;
|
|
1454
|
+
}
|
|
1455
|
+
getUsage() {
|
|
1456
|
+
return this.lastUsage;
|
|
1457
|
+
}
|
|
1458
|
+
finalizeStreamingToolCall() {
|
|
1459
|
+
if (!this.currentToolCall) return [];
|
|
1460
|
+
const { id, name, arguments: argStr } = this.currentToolCall;
|
|
1461
|
+
const parsed = this.safeParseJSON(argStr);
|
|
1462
|
+
this.currentToolCall = null;
|
|
1463
|
+
return [
|
|
1464
|
+
{ type: "tool_call_end", id },
|
|
1465
|
+
{ type: "tool_call", id, name, arguments: parsed }
|
|
1466
|
+
];
|
|
1467
|
+
}
|
|
1468
|
+
safeParseJSON(str) {
|
|
1469
|
+
try {
|
|
1470
|
+
return JSON.parse(str);
|
|
1471
|
+
} catch {
|
|
1472
|
+
return str;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
var execAsync = util.promisify(child_process.exec);
|
|
1477
|
+
var SkillTemplateProcessor = class {
|
|
1478
|
+
context;
|
|
1479
|
+
constructor(context) {
|
|
1480
|
+
this.context = {
|
|
1481
|
+
cwd: process.cwd(),
|
|
1482
|
+
...context
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* 处理模板内容
|
|
1487
|
+
* @param content SKILL.md 内容
|
|
1488
|
+
* @param args 用户传入的参数字符串
|
|
1489
|
+
* @returns 处理后的内容
|
|
1490
|
+
*/
|
|
1491
|
+
async process(content, args) {
|
|
1492
|
+
let result = content;
|
|
1493
|
+
result = await this.processShellCommands(result);
|
|
1494
|
+
result = this.processVariables(result, args);
|
|
1495
|
+
return result;
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* 处理 shell 命令注入
|
|
1499
|
+
* 格式: !`command`
|
|
1500
|
+
* 命令在工作目录中执行,输出替换占位符
|
|
1501
|
+
*/
|
|
1502
|
+
async processShellCommands(content) {
|
|
1503
|
+
const shellCommandRegex = /!`([^`]+)`/g;
|
|
1504
|
+
const matches = [...content.matchAll(shellCommandRegex)];
|
|
1505
|
+
for (const match of matches) {
|
|
1506
|
+
const command = match[1];
|
|
1507
|
+
try {
|
|
1508
|
+
const { stdout } = await execAsync(command, {
|
|
1509
|
+
cwd: this.context.cwd,
|
|
1510
|
+
timeout: 3e4
|
|
1511
|
+
});
|
|
1512
|
+
content = content.replace(match[0], stdout.trim());
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1515
|
+
content = content.replace(match[0], `[Error executing: ${command}
|
|
1516
|
+
${errorMsg}]`);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return content;
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* 处理变量替换
|
|
1523
|
+
*/
|
|
1524
|
+
processVariables(content, args) {
|
|
1525
|
+
const argsArray = this.parseArguments(args);
|
|
1526
|
+
if (content.includes("$ARGUMENTS")) {
|
|
1527
|
+
content = content.replace(/\$ARGUMENTS/g, args);
|
|
1528
|
+
}
|
|
1529
|
+
content = content.replace(/\$ARGUMENTS\[(\d+)\]/g, (_, index) => {
|
|
1530
|
+
const i = parseInt(index, 10);
|
|
1531
|
+
return argsArray[i] || "";
|
|
1532
|
+
});
|
|
1533
|
+
content = content.replace(/\$(\d+)/g, (_, index) => {
|
|
1534
|
+
const i = parseInt(index, 10);
|
|
1535
|
+
return argsArray[i] || "";
|
|
1536
|
+
});
|
|
1537
|
+
if (this.context.sessionId) {
|
|
1538
|
+
content = content.replace(/\$\{CLAUDE_SESSION_ID\}/g, this.context.sessionId);
|
|
1539
|
+
}
|
|
1540
|
+
content = content.replace(/\$\{CLAUDE_SKILL_DIR\}/g, this.context.skillDir);
|
|
1541
|
+
return content;
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* 解析参数字符串为数组
|
|
1545
|
+
* 支持引号包裹的参数
|
|
1546
|
+
*/
|
|
1547
|
+
parseArguments(args) {
|
|
1548
|
+
if (!args.trim()) return [];
|
|
1549
|
+
const result = [];
|
|
1550
|
+
let current = "";
|
|
1551
|
+
let inQuotes = false;
|
|
1552
|
+
let quoteChar = "";
|
|
1553
|
+
for (let i = 0; i < args.length; i++) {
|
|
1554
|
+
const char = args[i];
|
|
1555
|
+
if (inQuotes) {
|
|
1556
|
+
if (char === quoteChar) {
|
|
1557
|
+
inQuotes = false;
|
|
1558
|
+
} else {
|
|
1559
|
+
current += char;
|
|
1560
|
+
}
|
|
1561
|
+
} else if (char === '"' || char === "'") {
|
|
1562
|
+
inQuotes = true;
|
|
1563
|
+
quoteChar = char;
|
|
1564
|
+
} else if (char === " ") {
|
|
1565
|
+
if (current) {
|
|
1566
|
+
result.push(current);
|
|
1567
|
+
current = "";
|
|
1568
|
+
}
|
|
1569
|
+
} else {
|
|
1570
|
+
current += char;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (current) {
|
|
1574
|
+
result.push(current);
|
|
1575
|
+
}
|
|
1576
|
+
return result;
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
function createSkillTemplateProcessor(context) {
|
|
1580
|
+
return new SkillTemplateProcessor(context);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// src/core/compressor.ts
|
|
1584
|
+
var SummarizationCompressor = class {
|
|
1585
|
+
constructor(model, options = {}) {
|
|
1586
|
+
this.model = model;
|
|
1587
|
+
this.options = options;
|
|
1588
|
+
}
|
|
1589
|
+
name = "summarization";
|
|
1590
|
+
async compress(messages, targetTokens) {
|
|
1591
|
+
const preserveRecent = this.options.preserveRecent ?? 6;
|
|
1592
|
+
const systemMessages = messages.filter((m) => m.role === "system");
|
|
1593
|
+
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
1594
|
+
if (nonSystemMessages.length <= preserveRecent) {
|
|
1595
|
+
return messages;
|
|
1596
|
+
}
|
|
1597
|
+
const recentMessages = nonSystemMessages.slice(-preserveRecent);
|
|
1598
|
+
const messagesToSummarize = nonSystemMessages.slice(0, -preserveRecent);
|
|
1599
|
+
const summaryPrompt = this.options.summaryPrompt ?? this.buildDefaultPrompt();
|
|
1600
|
+
const maxTokens = Math.min(
|
|
1601
|
+
this.options.maxSummaryTokens ?? 4e3,
|
|
1602
|
+
Math.floor(targetTokens * 0.3)
|
|
1603
|
+
);
|
|
1604
|
+
const summaryResponse = await this.model.complete({
|
|
1605
|
+
messages: [
|
|
1606
|
+
{ role: "system", content: summaryPrompt },
|
|
1607
|
+
...messagesToSummarize
|
|
1608
|
+
],
|
|
1609
|
+
maxTokens
|
|
1610
|
+
});
|
|
1611
|
+
return [
|
|
1612
|
+
...systemMessages,
|
|
1613
|
+
{
|
|
1614
|
+
role: "system",
|
|
1615
|
+
content: this.wrapSummary(summaryResponse.content)
|
|
1616
|
+
},
|
|
1617
|
+
...recentMessages
|
|
1618
|
+
];
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* 构建默认摘要提示 (借鉴 Opencode 模板)
|
|
1622
|
+
*/
|
|
1623
|
+
buildDefaultPrompt() {
|
|
1624
|
+
return `Provide a detailed prompt for continuing our conversation above.
|
|
1625
|
+
Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.
|
|
1626
|
+
The summary that you construct will be used so that another agent can read it and continue the work.
|
|
1627
|
+
|
|
1628
|
+
When constructing the summary, try to stick to this template:
|
|
1629
|
+
---
|
|
1630
|
+
## Goal
|
|
1631
|
+
|
|
1632
|
+
[What goal(s) is the user trying to accomplish?]
|
|
1633
|
+
|
|
1634
|
+
## Instructions
|
|
1635
|
+
|
|
1636
|
+
- [What important instructions did the user give you that are relevant]
|
|
1637
|
+
- [If there is a plan or spec, include information about it so next agent can continue using it]
|
|
1638
|
+
|
|
1639
|
+
## Discoveries
|
|
1640
|
+
|
|
1641
|
+
[What notable things were learned during this conversation that would be useful for the next agent to know when continuing the work]
|
|
1642
|
+
|
|
1643
|
+
## Accomplished
|
|
1644
|
+
|
|
1645
|
+
[What work has been completed, what work is still in progress, and what work is left?]
|
|
1646
|
+
|
|
1647
|
+
## Relevant files / directories
|
|
1648
|
+
|
|
1649
|
+
[Construct a structured list of relevant files that have been read, edited, or created that pertain to the task at hand. If all the files in a directory are relevant, include the path to the directory.]
|
|
1650
|
+
---`;
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* 包装摘要为 continuation 格式 (借鉴 Opencode)
|
|
1654
|
+
*/
|
|
1655
|
+
wrapSummary(summary) {
|
|
1656
|
+
return `This session is being continued from a previous conversation that ran out of context.
|
|
1657
|
+
The summary below covers the earlier portion of the conversation.
|
|
1658
|
+
|
|
1659
|
+
${summary}
|
|
1660
|
+
|
|
1661
|
+
Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.`;
|
|
1662
|
+
}
|
|
1663
|
+
};
|
|
1664
|
+
|
|
1665
|
+
// src/core/context-manager.ts
|
|
1666
|
+
var ContextManager = class {
|
|
1667
|
+
compressor;
|
|
1668
|
+
reserved;
|
|
1669
|
+
pruneEnabled;
|
|
1670
|
+
pruneMinimum;
|
|
1671
|
+
pruneProtect;
|
|
1672
|
+
compressCount = 0;
|
|
1673
|
+
_contextLength;
|
|
1674
|
+
_maxOutputTokens;
|
|
1675
|
+
constructor(model, options = {}) {
|
|
1676
|
+
const contextLength = options.contextLength ?? model.capabilities?.contextLength ?? 128e3;
|
|
1677
|
+
const maxOutputTokens = options.maxOutputTokens ?? model.capabilities?.maxOutputTokens ?? 32768;
|
|
1678
|
+
this._contextLength = contextLength;
|
|
1679
|
+
this._maxOutputTokens = maxOutputTokens;
|
|
1680
|
+
this.reserved = options.reserved ?? Math.min(2e4, maxOutputTokens);
|
|
1681
|
+
this.pruneEnabled = options.prune !== false;
|
|
1682
|
+
this.pruneMinimum = options.pruneMinimum ?? 2e4;
|
|
1683
|
+
this.pruneProtect = options.pruneProtect ?? 4e4;
|
|
1684
|
+
this.compressor = options.compressor ?? new SummarizationCompressor(model);
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* 计算可用空间
|
|
1688
|
+
*
|
|
1689
|
+
* usable = contextLength - maxOutputTokens - reserved
|
|
1690
|
+
*/
|
|
1691
|
+
get usable() {
|
|
1692
|
+
return this._contextLength - this._maxOutputTokens - this.reserved;
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* 判断是否需要压缩
|
|
1696
|
+
*
|
|
1697
|
+
* 使用当前上下文大小 (contextTokens) 判断,而非累计值
|
|
1698
|
+
*
|
|
1699
|
+
* @param usage 会话 token 使用量
|
|
1700
|
+
*/
|
|
1701
|
+
shouldCompress(usage) {
|
|
1702
|
+
return usage.contextTokens >= this.usable;
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* 执行压缩
|
|
1706
|
+
*/
|
|
1707
|
+
async compress(messages, targetTokens) {
|
|
1708
|
+
const startTime = Date.now();
|
|
1709
|
+
const originalCount = messages.length;
|
|
1710
|
+
const target = targetTokens ?? Math.floor(this.usable * 0.6);
|
|
1711
|
+
const compressedMessages = await this.compressor.compress(messages, target);
|
|
1712
|
+
this.compressCount++;
|
|
1713
|
+
return {
|
|
1714
|
+
messages: compressedMessages,
|
|
1715
|
+
stats: {
|
|
1716
|
+
originalMessageCount: originalCount,
|
|
1717
|
+
compressedMessageCount: compressedMessages.length,
|
|
1718
|
+
durationMs: Date.now() - startTime
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Prune: 清理旧的工具输出
|
|
1724
|
+
*
|
|
1725
|
+
* 借鉴 Opencode 的 prune 策略:
|
|
1726
|
+
* - 从后往前遍历消息
|
|
1727
|
+
* - 保留最近 PRUNE_PROTECT tokens 的工具输出
|
|
1728
|
+
* - 清理更早的工具输出
|
|
1729
|
+
*
|
|
1730
|
+
* @param messages 消息列表
|
|
1731
|
+
* @returns 处理后的消息列表
|
|
1732
|
+
*/
|
|
1733
|
+
prune(messages) {
|
|
1734
|
+
if (!this.pruneEnabled) return messages;
|
|
1735
|
+
let total = 0;
|
|
1736
|
+
const toPrune = [];
|
|
1737
|
+
let turns = 0;
|
|
1738
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1739
|
+
const msg = messages[i];
|
|
1740
|
+
if (msg.role === "user") turns++;
|
|
1741
|
+
if (turns < 2) continue;
|
|
1742
|
+
if (msg.role === "tool") {
|
|
1743
|
+
const estimate = this.estimateTokens(typeof msg.content === "string" ? msg.content : "");
|
|
1744
|
+
total += estimate;
|
|
1745
|
+
if (total > this.pruneProtect) {
|
|
1746
|
+
toPrune.push(i);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (toPrune.length > 0) {
|
|
1751
|
+
const prunedTokens = toPrune.reduce((sum, idx) => {
|
|
1752
|
+
const content = messages[idx].content;
|
|
1753
|
+
return sum + this.estimateTokens(typeof content === "string" ? content : "");
|
|
1754
|
+
}, 0);
|
|
1755
|
+
if (prunedTokens >= this.pruneMinimum) {
|
|
1756
|
+
return messages.map((msg, idx) => {
|
|
1757
|
+
if (toPrune.includes(idx) && msg.role === "tool") {
|
|
1758
|
+
return {
|
|
1759
|
+
...msg,
|
|
1760
|
+
content: "[Tool output pruned to save context]"
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
return msg;
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
return messages;
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* 获取上下文状态
|
|
1771
|
+
*/
|
|
1772
|
+
getStatus(usage) {
|
|
1773
|
+
return {
|
|
1774
|
+
used: usage.contextTokens,
|
|
1775
|
+
usable: this.usable,
|
|
1776
|
+
needsCompaction: usage.contextTokens >= this.usable,
|
|
1777
|
+
compressCount: this.compressCount
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* 重置 token 使用量 (压缩后调用)
|
|
1782
|
+
*/
|
|
1783
|
+
resetUsage() {
|
|
1784
|
+
return {
|
|
1785
|
+
contextTokens: 0,
|
|
1786
|
+
inputTokens: 0,
|
|
1787
|
+
outputTokens: 0,
|
|
1788
|
+
cacheReadTokens: 0,
|
|
1789
|
+
cacheWriteTokens: 0,
|
|
1790
|
+
totalTokens: 0
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Token 估算 (仅用于 prune,不做压缩判断)
|
|
1795
|
+
*
|
|
1796
|
+
* 借鉴 Opencode: CHARS_PER_TOKEN = 4
|
|
1797
|
+
*/
|
|
1798
|
+
estimateTokens(text) {
|
|
1799
|
+
return Math.max(0, Math.round((text || "").length / 4));
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
|
|
1803
|
+
// src/core/agent.ts
|
|
1804
|
+
function toMCPClientConfig(config) {
|
|
1805
|
+
if (config.transport === "http") {
|
|
1806
|
+
return {
|
|
1807
|
+
name: config.name,
|
|
1808
|
+
url: config.url,
|
|
1809
|
+
headers: config.headers
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
return {
|
|
1813
|
+
name: config.name,
|
|
1814
|
+
command: config.command,
|
|
1815
|
+
args: config.args,
|
|
1816
|
+
env: config.env
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
var Agent = class _Agent {
|
|
1820
|
+
config;
|
|
1821
|
+
toolRegistry;
|
|
1822
|
+
sessionManager;
|
|
1823
|
+
messages = [];
|
|
1824
|
+
mcpAdapter = null;
|
|
1825
|
+
skillRegistry;
|
|
1826
|
+
initPromise;
|
|
1827
|
+
contextManager = null;
|
|
1828
|
+
hookDiscoverPromise = null;
|
|
1829
|
+
agentDepth = 0;
|
|
1830
|
+
activeSubagentRuns = 0;
|
|
1831
|
+
// Token 使用量统计
|
|
1832
|
+
// contextTokens: 当前上下文大小 (用于压缩判断)
|
|
1833
|
+
// inputTokens/outputTokens: 累计消耗
|
|
1834
|
+
// totalTokens: 累计总消耗 (inputTokens + outputTokens)
|
|
1835
|
+
sessionUsage = _Agent.createEmptySessionUsage();
|
|
1836
|
+
constructor(config) {
|
|
1837
|
+
this.config = {
|
|
1838
|
+
maxIterations: 200,
|
|
1839
|
+
streaming: true,
|
|
1840
|
+
...config
|
|
1841
|
+
};
|
|
1842
|
+
this.skillRegistry = createSkillRegistry({
|
|
1843
|
+
cwd: config.cwd,
|
|
1844
|
+
userBasePath: config.userBasePath
|
|
1845
|
+
});
|
|
1846
|
+
this.toolRegistry = new chunkJF5AJQMU_cjs.ToolRegistry({
|
|
1847
|
+
executionPolicy: {
|
|
1848
|
+
disallowedTools: this.config.disallowedTools,
|
|
1849
|
+
allowedTools: this.config.allowedTools,
|
|
1850
|
+
canUseTool: this.config.canUseTool
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
this.registerInitialTools();
|
|
1854
|
+
const subagentEnabled = this.config.subagent?.enabled !== false;
|
|
1855
|
+
if (subagentEnabled) {
|
|
1856
|
+
if (this.toolRegistry.has("Agent")) {
|
|
1857
|
+
this.toolRegistry.unregister("Agent");
|
|
1858
|
+
}
|
|
1859
|
+
this.toolRegistry.register(chunkJF5AJQMU_cjs.createAgentTool({
|
|
1860
|
+
runner: (request, context) => this.runSubagent(request, context)
|
|
1861
|
+
}));
|
|
1862
|
+
} else if (this.toolRegistry.has("Agent")) {
|
|
1863
|
+
this.toolRegistry.unregister("Agent");
|
|
1864
|
+
}
|
|
1865
|
+
if (config.hookManager) {
|
|
1866
|
+
this.toolRegistry.setHookManager(config.hookManager);
|
|
1867
|
+
} else if (config.hookConfigDir !== void 0) {
|
|
1868
|
+
const hm = chunkJF5AJQMU_cjs.HookManager.create();
|
|
1869
|
+
this.toolRegistry.setHookManager(hm);
|
|
1870
|
+
this.hookDiscoverPromise = hm.discoverAndLoad(config.hookConfigDir);
|
|
1871
|
+
}
|
|
1872
|
+
this.sessionManager = new SessionManager({
|
|
1873
|
+
type: config.storage?.type || "jsonl",
|
|
1874
|
+
basePath: getSessionStoragePath(config.userBasePath)
|
|
1875
|
+
});
|
|
1876
|
+
if (config.contextManagement !== false) {
|
|
1877
|
+
const cmConfig = config.contextManagement === true ? {} : config.contextManagement ?? {};
|
|
1878
|
+
this.contextManager = new ContextManager(config.model, cmConfig);
|
|
1879
|
+
}
|
|
1880
|
+
this.initPromise = this.initializeAsync();
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* 注册内置 + 自定义工具,或仅 {@link AgentConfig.exclusiveTools}。
|
|
1884
|
+
*/
|
|
1885
|
+
registerInitialTools() {
|
|
1886
|
+
if (this.config.exclusiveTools !== void 0) {
|
|
1887
|
+
for (const tool of this.config.exclusiveTools) {
|
|
1888
|
+
if (this.toolRegistry.isDisallowed(tool.name)) {
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
this.toolRegistry.register(tool);
|
|
1892
|
+
}
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
const builtins = chunkJF5AJQMU_cjs.getAllBuiltinTools(this.skillRegistry, {
|
|
1896
|
+
resolve: this.config.askUserQuestion
|
|
1897
|
+
}).filter((t) => !this.toolRegistry.isDisallowed(t.name));
|
|
1898
|
+
this.toolRegistry.registerMany(builtins);
|
|
1899
|
+
for (const tool of this.config.tools ?? []) {
|
|
1900
|
+
if (this.toolRegistry.isDisallowed(tool.name)) {
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
if (this.toolRegistry.has(tool.name)) {
|
|
1904
|
+
this.toolRegistry.unregister(tool.name);
|
|
1905
|
+
}
|
|
1906
|
+
this.toolRegistry.register(tool);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* 异步初始化(skills 和 MCP)
|
|
1911
|
+
*/
|
|
1912
|
+
async initializeAsync() {
|
|
1913
|
+
try {
|
|
1914
|
+
if (this.hookDiscoverPromise) {
|
|
1915
|
+
await this.hookDiscoverPromise;
|
|
1916
|
+
}
|
|
1917
|
+
await this.skillRegistry.initialize(
|
|
1918
|
+
this.config.skillConfig,
|
|
1919
|
+
this.config.skills
|
|
1920
|
+
);
|
|
1921
|
+
if (this.config.mcpServers && this.config.mcpServers.length > 0) {
|
|
1922
|
+
this.mcpAdapter = new MCPAdapter();
|
|
1923
|
+
await this.initializeMCP(this.config.mcpServers);
|
|
1924
|
+
}
|
|
1925
|
+
} catch (err) {
|
|
1926
|
+
console.error("Failed to initialize:", err);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* 等待初始化完成
|
|
1931
|
+
* CLI 应在开始交互前调用此方法
|
|
1932
|
+
*/
|
|
1933
|
+
async waitForInit() {
|
|
1934
|
+
await this.initPromise;
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* 初始化 MCP 服务器
|
|
1938
|
+
*/
|
|
1939
|
+
async initializeMCP(servers) {
|
|
1940
|
+
if (!this.mcpAdapter) return;
|
|
1941
|
+
for (const serverConfig of servers) {
|
|
1942
|
+
try {
|
|
1943
|
+
await this.connectMCP(serverConfig);
|
|
1944
|
+
} catch (err) {
|
|
1945
|
+
console.error(`Failed to connect MCP server "${serverConfig.name}":`, err);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
annotateStreamEvent(event, iteration) {
|
|
1950
|
+
return {
|
|
1951
|
+
...event,
|
|
1952
|
+
streamEventId: crypto.randomUUID(),
|
|
1953
|
+
...iteration !== void 0 ? { iteration } : {},
|
|
1954
|
+
sessionId: this.sessionManager.sessionId ?? void 0
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
static createEmptySessionUsage() {
|
|
1958
|
+
return {
|
|
1959
|
+
contextTokens: 0,
|
|
1960
|
+
inputTokens: 0,
|
|
1961
|
+
outputTokens: 0,
|
|
1962
|
+
cacheReadTokens: 0,
|
|
1963
|
+
cacheWriteTokens: 0,
|
|
1964
|
+
totalTokens: 0
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
resetSessionState() {
|
|
1968
|
+
this.messages = [];
|
|
1969
|
+
this.sessionUsage = this.contextManager ? this.contextManager.resetUsage() : _Agent.createEmptySessionUsage();
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* 构建系统提示词
|
|
1973
|
+
* 处理默认提示词、替换模式、追加模式
|
|
1974
|
+
*/
|
|
1975
|
+
buildSystemPrompt(customPrompt) {
|
|
1976
|
+
const shouldIncludeEnv = typeof customPrompt === "object" ? customPrompt.includeEnvironment !== false : this.config.includeEnvironment !== false;
|
|
1977
|
+
let envSection = "";
|
|
1978
|
+
if (shouldIncludeEnv) {
|
|
1979
|
+
const cwd = this.config.cwd || process.cwd();
|
|
1980
|
+
const envInfo = chunkJF5AJQMU_cjs.getEnvironmentInfo(cwd);
|
|
1981
|
+
envSection = chunkJF5AJQMU_cjs.formatEnvironmentSection(envInfo);
|
|
1982
|
+
}
|
|
1983
|
+
if (!customPrompt) {
|
|
1984
|
+
let basePrompt = DEFAULT_SYSTEM_PROMPT;
|
|
1985
|
+
basePrompt = basePrompt.replace("{{SKILL_LIST}}", this.skillRegistry.getFormattedList());
|
|
1986
|
+
return basePrompt + envSection;
|
|
1987
|
+
}
|
|
1988
|
+
if (typeof customPrompt === "string") {
|
|
1989
|
+
let basePrompt = DEFAULT_SYSTEM_PROMPT;
|
|
1990
|
+
basePrompt = basePrompt.replace("{{SKILL_LIST}}", this.skillRegistry.getFormattedList());
|
|
1991
|
+
return `${basePrompt}${envSection}
|
|
1992
|
+
|
|
1993
|
+
${customPrompt}`;
|
|
1994
|
+
}
|
|
1995
|
+
const { content, mode = "append" } = customPrompt;
|
|
1996
|
+
if (mode === "replace") {
|
|
1997
|
+
return content + envSection;
|
|
1998
|
+
} else {
|
|
1999
|
+
let basePrompt = DEFAULT_SYSTEM_PROMPT;
|
|
2000
|
+
basePrompt = basePrompt.replace("{{SKILL_LIST}}", this.skillRegistry.getFormattedList());
|
|
2001
|
+
return `${basePrompt}${envSection}
|
|
2002
|
+
|
|
2003
|
+
${content}`;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* 流式执行
|
|
2008
|
+
*/
|
|
2009
|
+
async *stream(input, options) {
|
|
2010
|
+
const signal = options?.signal;
|
|
2011
|
+
if (options?.sessionId) {
|
|
2012
|
+
const isSwitchingSession = this.sessionManager.sessionId !== options.sessionId;
|
|
2013
|
+
if (isSwitchingSession) {
|
|
2014
|
+
this.resetSessionState();
|
|
2015
|
+
}
|
|
2016
|
+
try {
|
|
2017
|
+
this.messages = await this.sessionManager.resumeSession(options.sessionId);
|
|
2018
|
+
} catch {
|
|
2019
|
+
this.sessionManager.createSession(options.sessionId);
|
|
2020
|
+
}
|
|
2021
|
+
} else if (!this.sessionManager.sessionId) {
|
|
2022
|
+
this.resetSessionState();
|
|
2023
|
+
this.sessionManager.createSession();
|
|
2024
|
+
}
|
|
2025
|
+
if (this.messages.length === 0) {
|
|
2026
|
+
const systemPrompt = this.buildSystemPrompt(
|
|
2027
|
+
options?.systemPrompt || this.config.systemPrompt
|
|
2028
|
+
);
|
|
2029
|
+
this.messages.push({
|
|
2030
|
+
role: "system",
|
|
2031
|
+
content: systemPrompt
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
if (this.config.memory !== false) {
|
|
2035
|
+
const hasUserMessages = this.messages.some((m) => m.role === "user");
|
|
2036
|
+
if (!hasUserMessages) {
|
|
2037
|
+
const memoryManager = new MemoryManager(this.config.cwd, this.config.memoryConfig, this.config.userBasePath);
|
|
2038
|
+
const memoryContent = memoryManager.loadMemory();
|
|
2039
|
+
if (memoryContent) {
|
|
2040
|
+
this.messages.push({
|
|
2041
|
+
role: "system",
|
|
2042
|
+
content: memoryContent
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
let processedInput = input;
|
|
2048
|
+
const processed = await this.processInput(input);
|
|
2049
|
+
if (processed.invoked) {
|
|
2050
|
+
processedInput = processed.prompt;
|
|
2051
|
+
}
|
|
2052
|
+
this.messages.push({
|
|
2053
|
+
role: "user",
|
|
2054
|
+
content: processedInput
|
|
2055
|
+
});
|
|
2056
|
+
yield this.annotateStreamEvent({ type: "start", timestamp: Date.now() });
|
|
2057
|
+
try {
|
|
2058
|
+
const maxIterations = this.config.maxIterations || 10;
|
|
2059
|
+
let totalUsage = {
|
|
2060
|
+
promptTokens: 0,
|
|
2061
|
+
completionTokens: 0,
|
|
2062
|
+
totalTokens: 0
|
|
2063
|
+
};
|
|
2064
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
2065
|
+
if (signal?.aborted) {
|
|
2066
|
+
yield this.annotateStreamEvent(
|
|
2067
|
+
{
|
|
2068
|
+
type: "end",
|
|
2069
|
+
usage: totalUsage,
|
|
2070
|
+
timestamp: Date.now(),
|
|
2071
|
+
reason: "aborted"
|
|
2072
|
+
},
|
|
2073
|
+
iteration
|
|
2074
|
+
);
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
const contextEvents = await this.checkContextCompression();
|
|
2078
|
+
for (const event of contextEvents) {
|
|
2079
|
+
yield this.annotateStreamEvent(event, iteration);
|
|
2080
|
+
}
|
|
2081
|
+
const modelParams = {
|
|
2082
|
+
messages: this.messages,
|
|
2083
|
+
tools: this.toolRegistry.getAll(),
|
|
2084
|
+
temperature: this.config.temperature,
|
|
2085
|
+
maxTokens: this.config.maxTokens,
|
|
2086
|
+
signal,
|
|
2087
|
+
includeRawStreamEvents: options?.includeRawStreamEvents
|
|
2088
|
+
};
|
|
2089
|
+
const stream = this.config.model.stream(modelParams);
|
|
2090
|
+
let hasToolCalls = false;
|
|
2091
|
+
const toolCalls = [];
|
|
2092
|
+
let assistantContent = "";
|
|
2093
|
+
let thinkingContent = "";
|
|
2094
|
+
let thinkingSignature;
|
|
2095
|
+
const chunkProcessor = new StreamChunkProcessor({ emitTextBoundaries: true });
|
|
2096
|
+
const applyStreamOut = (out) => {
|
|
2097
|
+
if (out.type === "text_delta") {
|
|
2098
|
+
assistantContent += out.content;
|
|
2099
|
+
}
|
|
2100
|
+
if (out.type === "thinking") {
|
|
2101
|
+
thinkingContent += out.content;
|
|
2102
|
+
if (out.signature !== void 0 && !thinkingSignature) {
|
|
2103
|
+
thinkingSignature = out.signature;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
if (out.type === "tool_call") {
|
|
2107
|
+
hasToolCalls = true;
|
|
2108
|
+
toolCalls.push({
|
|
2109
|
+
id: out.id,
|
|
2110
|
+
name: out.name,
|
|
2111
|
+
arguments: out.arguments
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
if (out.type === "model_usage") {
|
|
2115
|
+
const usage = out.usage;
|
|
2116
|
+
if (usage.promptTokens > 0) {
|
|
2117
|
+
totalUsage.promptTokens = usage.promptTokens;
|
|
2118
|
+
this.sessionUsage.contextTokens = usage.promptTokens;
|
|
2119
|
+
this.sessionUsage.inputTokens += usage.promptTokens;
|
|
2120
|
+
}
|
|
2121
|
+
totalUsage.completionTokens += usage.completionTokens;
|
|
2122
|
+
totalUsage.totalTokens = totalUsage.promptTokens + totalUsage.completionTokens;
|
|
2123
|
+
this.sessionUsage.outputTokens += usage.completionTokens;
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
let fatalModelError = false;
|
|
2127
|
+
for await (const chunk of stream) {
|
|
2128
|
+
if (signal?.aborted) {
|
|
2129
|
+
for (const event of chunkProcessor.flush()) {
|
|
2130
|
+
const out = this.annotateStreamEvent(event, iteration);
|
|
2131
|
+
yield out;
|
|
2132
|
+
applyStreamOut(out);
|
|
2133
|
+
}
|
|
2134
|
+
if (assistantContent) {
|
|
2135
|
+
const assistantMessage2 = {
|
|
2136
|
+
role: "assistant",
|
|
2137
|
+
content: assistantContent
|
|
2138
|
+
};
|
|
2139
|
+
if (thinkingContent) {
|
|
2140
|
+
assistantMessage2.content = [
|
|
2141
|
+
{ type: "thinking", thinking: thinkingContent, signature: thinkingSignature || "" },
|
|
2142
|
+
{ type: "text", text: assistantContent }
|
|
2143
|
+
];
|
|
2144
|
+
}
|
|
2145
|
+
this.messages.push(assistantMessage2);
|
|
2146
|
+
}
|
|
2147
|
+
this.messages.push({
|
|
2148
|
+
role: "user",
|
|
2149
|
+
content: "[User interrupted the response]"
|
|
2150
|
+
});
|
|
2151
|
+
await this.sessionManager.saveMessages(this.messages);
|
|
2152
|
+
yield this.annotateStreamEvent(
|
|
2153
|
+
{
|
|
2154
|
+
type: "end",
|
|
2155
|
+
usage: totalUsage,
|
|
2156
|
+
timestamp: Date.now(),
|
|
2157
|
+
reason: "aborted",
|
|
2158
|
+
partialContent: assistantContent
|
|
2159
|
+
},
|
|
2160
|
+
iteration
|
|
2161
|
+
);
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
const events = chunkProcessor.processChunk(chunk);
|
|
2165
|
+
for (const event of events) {
|
|
2166
|
+
const out = this.annotateStreamEvent(event, iteration);
|
|
2167
|
+
yield out;
|
|
2168
|
+
applyStreamOut(out);
|
|
2169
|
+
if (out.type === "end" && out.reason === "error") {
|
|
2170
|
+
fatalModelError = true;
|
|
2171
|
+
break;
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
if (fatalModelError) {
|
|
2175
|
+
break;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
if (fatalModelError) {
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
for (const event of chunkProcessor.flush()) {
|
|
2182
|
+
const out = this.annotateStreamEvent(event, iteration);
|
|
2183
|
+
yield out;
|
|
2184
|
+
applyStreamOut(out);
|
|
2185
|
+
}
|
|
2186
|
+
const assistantMessage = {
|
|
2187
|
+
role: "assistant",
|
|
2188
|
+
content: assistantContent
|
|
2189
|
+
};
|
|
2190
|
+
if (thinkingContent) {
|
|
2191
|
+
const contentParts = [
|
|
2192
|
+
{
|
|
2193
|
+
type: "thinking",
|
|
2194
|
+
thinking: thinkingContent,
|
|
2195
|
+
signature: thinkingSignature
|
|
2196
|
+
}
|
|
2197
|
+
];
|
|
2198
|
+
if (assistantContent.trim()) {
|
|
2199
|
+
contentParts.push({ type: "text", text: assistantContent });
|
|
2200
|
+
}
|
|
2201
|
+
assistantMessage.content = contentParts;
|
|
2202
|
+
}
|
|
2203
|
+
if (toolCalls.length > 0) {
|
|
2204
|
+
assistantMessage.toolCalls = toolCalls;
|
|
2205
|
+
}
|
|
2206
|
+
this.messages.push(assistantMessage);
|
|
2207
|
+
if (!hasToolCalls) {
|
|
2208
|
+
break;
|
|
2209
|
+
}
|
|
2210
|
+
const toolResults = await this.executeTools(toolCalls);
|
|
2211
|
+
for (const result of toolResults) {
|
|
2212
|
+
if (result.isError && result.error) {
|
|
2213
|
+
yield this.annotateStreamEvent(
|
|
2214
|
+
{
|
|
2215
|
+
type: "tool_error",
|
|
2216
|
+
toolCallId: result.toolCallId,
|
|
2217
|
+
error: result.error
|
|
2218
|
+
},
|
|
2219
|
+
iteration
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
yield this.annotateStreamEvent(
|
|
2223
|
+
{
|
|
2224
|
+
type: "tool_result",
|
|
2225
|
+
toolCallId: result.toolCallId,
|
|
2226
|
+
result: result.content
|
|
2227
|
+
},
|
|
2228
|
+
iteration
|
|
2229
|
+
);
|
|
2230
|
+
this.messages.push({
|
|
2231
|
+
role: "tool",
|
|
2232
|
+
toolCallId: result.toolCallId,
|
|
2233
|
+
content: result.content
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
await this.sessionManager.saveMessages(this.messages);
|
|
2238
|
+
yield this.annotateStreamEvent({
|
|
2239
|
+
type: "session_summary",
|
|
2240
|
+
usage: totalUsage,
|
|
2241
|
+
iterations: Math.min(maxIterations, this.messages.length)
|
|
2242
|
+
});
|
|
2243
|
+
yield this.annotateStreamEvent({
|
|
2244
|
+
type: "end",
|
|
2245
|
+
timestamp: Date.now(),
|
|
2246
|
+
reason: "complete"
|
|
2247
|
+
});
|
|
2248
|
+
} catch (error) {
|
|
2249
|
+
if (error.name === "AbortError") {
|
|
2250
|
+
yield this.annotateStreamEvent({
|
|
2251
|
+
type: "end",
|
|
2252
|
+
timestamp: Date.now(),
|
|
2253
|
+
reason: "aborted"
|
|
2254
|
+
});
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
yield this.annotateStreamEvent({
|
|
2258
|
+
type: "end",
|
|
2259
|
+
timestamp: Date.now(),
|
|
2260
|
+
reason: "error",
|
|
2261
|
+
error
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* 非流式执行
|
|
2267
|
+
*/
|
|
2268
|
+
async run(input, options) {
|
|
2269
|
+
let content = "";
|
|
2270
|
+
const toolCalls = [];
|
|
2271
|
+
let usage;
|
|
2272
|
+
let iterations = 0;
|
|
2273
|
+
let streamError;
|
|
2274
|
+
for await (const event of this.stream(input, options)) {
|
|
2275
|
+
if (event.type === "text_delta") {
|
|
2276
|
+
content += event.content;
|
|
2277
|
+
}
|
|
2278
|
+
if (event.type === "tool_result") {
|
|
2279
|
+
const matchingCall = this.messages.filter((m) => m.role === "assistant" && m.toolCalls).flatMap((m) => m.toolCalls).find((tc) => tc.id === event.toolCallId);
|
|
2280
|
+
if (matchingCall) {
|
|
2281
|
+
toolCalls.push({
|
|
2282
|
+
name: matchingCall.name,
|
|
2283
|
+
arguments: matchingCall.arguments,
|
|
2284
|
+
result: event.result
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
if (event.type === "model_usage") {
|
|
2289
|
+
usage = event.usage;
|
|
2290
|
+
}
|
|
2291
|
+
if (event.type === "session_summary") {
|
|
2292
|
+
usage = event.usage;
|
|
2293
|
+
iterations = event.iterations;
|
|
2294
|
+
}
|
|
2295
|
+
if (event.type === "end") {
|
|
2296
|
+
if (event.usage !== void 0) {
|
|
2297
|
+
usage = event.usage;
|
|
2298
|
+
}
|
|
2299
|
+
if (event.reason === "error" && event.error) {
|
|
2300
|
+
streamError = event.error;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
if (streamError) {
|
|
2305
|
+
throw streamError;
|
|
2306
|
+
}
|
|
2307
|
+
return {
|
|
2308
|
+
content,
|
|
2309
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
2310
|
+
usage,
|
|
2311
|
+
sessionId: this.sessionManager.sessionId,
|
|
2312
|
+
iterations
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* 注册工具
|
|
2317
|
+
*/
|
|
2318
|
+
registerTool(tool) {
|
|
2319
|
+
this.toolRegistry.register(tool);
|
|
2320
|
+
}
|
|
2321
|
+
/**
|
|
2322
|
+
* 注册多个工具
|
|
2323
|
+
*/
|
|
2324
|
+
registerTools(tools) {
|
|
2325
|
+
for (const t of tools) {
|
|
2326
|
+
this.registerTool(t);
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* 获取工具注册中心
|
|
2331
|
+
*/
|
|
2332
|
+
getToolRegistry() {
|
|
2333
|
+
return this.toolRegistry;
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* 获取会话管理器
|
|
2337
|
+
*/
|
|
2338
|
+
getSessionManager() {
|
|
2339
|
+
return this.sessionManager;
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* 加载 Skill
|
|
2343
|
+
*/
|
|
2344
|
+
async loadSkill(path) {
|
|
2345
|
+
await this.skillRegistry.load(path);
|
|
2346
|
+
}
|
|
2347
|
+
/**
|
|
2348
|
+
* 获取 Skill 注册中心
|
|
2349
|
+
*/
|
|
2350
|
+
getSkillRegistry() {
|
|
2351
|
+
return this.skillRegistry;
|
|
2352
|
+
}
|
|
2353
|
+
/**
|
|
2354
|
+
* 处理用户输入,检测并处理 skill 调用
|
|
2355
|
+
* @param input 用户输入
|
|
2356
|
+
* @returns 处理结果
|
|
2357
|
+
*/
|
|
2358
|
+
async processInput(input) {
|
|
2359
|
+
const invocation = this.parseSkillInvocation(input);
|
|
2360
|
+
if (!invocation) {
|
|
2361
|
+
return { invoked: false, prompt: input };
|
|
2362
|
+
}
|
|
2363
|
+
const { name, args } = invocation;
|
|
2364
|
+
try {
|
|
2365
|
+
const prompt = await this.invokeSkill(name, args);
|
|
2366
|
+
return { invoked: true, skillName: name, prompt };
|
|
2367
|
+
} catch (error) {
|
|
2368
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2369
|
+
return {
|
|
2370
|
+
invoked: false,
|
|
2371
|
+
prompt: `Error invoking skill "${name}": ${errorMsg}
|
|
2372
|
+
|
|
2373
|
+
Original input: ${input}`
|
|
2374
|
+
};
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
/**
|
|
2378
|
+
* 调用 skill 并返回处理后的 prompt
|
|
2379
|
+
* @param name Skill 名称
|
|
2380
|
+
* @param args 参数字符串
|
|
2381
|
+
* @returns 处理后的 prompt
|
|
2382
|
+
*/
|
|
2383
|
+
async invokeSkill(name, args = "") {
|
|
2384
|
+
const skill = this.skillRegistry.get(name);
|
|
2385
|
+
if (!skill) {
|
|
2386
|
+
const available = this.skillRegistry.getNames();
|
|
2387
|
+
throw new Error(
|
|
2388
|
+
`Skill "${name}" not found. Available skills: ${available.join(", ") || "none"}`
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
2391
|
+
if (skill.metadata.userInvocable === false) {
|
|
2392
|
+
throw new Error(`Skill "${name}" is not user-invocable`);
|
|
2393
|
+
}
|
|
2394
|
+
const content = await this.skillRegistry.loadFullContent(name);
|
|
2395
|
+
const context = {
|
|
2396
|
+
skillDir: skill.path || "",
|
|
2397
|
+
sessionId: this.sessionManager.sessionId || void 0,
|
|
2398
|
+
cwd: this.config.cwd
|
|
2399
|
+
};
|
|
2400
|
+
const processor = createSkillTemplateProcessor(context);
|
|
2401
|
+
let processedContent = await processor.process(content, args);
|
|
2402
|
+
if (args && !content.includes("$ARGUMENTS") && !content.includes("$0")) {
|
|
2403
|
+
processedContent += `
|
|
2404
|
+
|
|
2405
|
+
ARGUMENTS: ${args}`;
|
|
2406
|
+
}
|
|
2407
|
+
return processedContent;
|
|
2408
|
+
}
|
|
2409
|
+
/**
|
|
2410
|
+
* 解析 skill 调用格式
|
|
2411
|
+
* 格式: /skill-name [args]
|
|
2412
|
+
* @param input 用户输入
|
|
2413
|
+
* @returns 解析结果或 null
|
|
2414
|
+
*/
|
|
2415
|
+
parseSkillInvocation(input) {
|
|
2416
|
+
const trimmed = input.trim();
|
|
2417
|
+
if (!trimmed.startsWith("/")) {
|
|
2418
|
+
return null;
|
|
2419
|
+
}
|
|
2420
|
+
const match = trimmed.match(/^\/([^\s\/]+)(?:\s+(.*))?$/);
|
|
2421
|
+
if (!match) {
|
|
2422
|
+
return null;
|
|
2423
|
+
}
|
|
2424
|
+
const name = match[1];
|
|
2425
|
+
const args = match[2] || "";
|
|
2426
|
+
if (!this.skillRegistry.has(name)) {
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
return { name, args };
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* 连接 MCP 服务器
|
|
2433
|
+
*/
|
|
2434
|
+
async connectMCP(config) {
|
|
2435
|
+
if (!this.mcpAdapter) {
|
|
2436
|
+
this.mcpAdapter = new MCPAdapter();
|
|
2437
|
+
}
|
|
2438
|
+
await this.mcpAdapter.addServer(toMCPClientConfig(config));
|
|
2439
|
+
const mcpTools = this.mcpAdapter.getToolDefinitions();
|
|
2440
|
+
for (const tool of mcpTools) {
|
|
2441
|
+
if (!tool.name.startsWith(`mcp_${config.name}__`)) {
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
if (this.toolRegistry.isDisallowed(tool.name)) {
|
|
2445
|
+
continue;
|
|
2446
|
+
}
|
|
2447
|
+
this.toolRegistry.register(tool);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
* 断开指定 MCP 服务器
|
|
2452
|
+
*/
|
|
2453
|
+
async disconnectMCP(name) {
|
|
2454
|
+
if (!this.mcpAdapter) return;
|
|
2455
|
+
const tools = this.toolRegistry.getAll();
|
|
2456
|
+
for (const tool of tools) {
|
|
2457
|
+
if (tool.name.startsWith(`mcp_${name}__`)) {
|
|
2458
|
+
this.toolRegistry.unregister(tool.name);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
await this.mcpAdapter.removeServer(name);
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* 断开所有 MCP 服务器
|
|
2465
|
+
*/
|
|
2466
|
+
async disconnectAllMCP() {
|
|
2467
|
+
if (!this.mcpAdapter) return;
|
|
2468
|
+
const tools = this.toolRegistry.getAll();
|
|
2469
|
+
for (const tool of tools) {
|
|
2470
|
+
if (tool.name.startsWith("mcp_") && tool.name.includes("__")) {
|
|
2471
|
+
this.toolRegistry.unregister(tool.name);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
await this.mcpAdapter.disconnectAll();
|
|
2475
|
+
this.mcpAdapter = null;
|
|
2476
|
+
}
|
|
2477
|
+
/**
|
|
2478
|
+
* 获取 MCP 适配器
|
|
2479
|
+
*/
|
|
2480
|
+
getMCPAdapter() {
|
|
2481
|
+
return this.mcpAdapter;
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* 销毁 Agent,清理资源
|
|
2485
|
+
*/
|
|
2486
|
+
async destroy() {
|
|
2487
|
+
await this.disconnectAllMCP();
|
|
2488
|
+
this.messages = [];
|
|
2489
|
+
}
|
|
2490
|
+
/**
|
|
2491
|
+
* 获取消息历史
|
|
2492
|
+
*/
|
|
2493
|
+
getMessages() {
|
|
2494
|
+
return [...this.messages];
|
|
2495
|
+
}
|
|
2496
|
+
/**
|
|
2497
|
+
* 清空消息历史
|
|
2498
|
+
*/
|
|
2499
|
+
clearMessages() {
|
|
2500
|
+
this.resetSessionState();
|
|
2501
|
+
}
|
|
2502
|
+
/**
|
|
2503
|
+
* 设置系统提示 (运行时替换)
|
|
2504
|
+
*/
|
|
2505
|
+
setSystemPrompt(prompt) {
|
|
2506
|
+
this.messages = this.messages.filter((m) => m.role !== "system");
|
|
2507
|
+
const systemPrompt = this.buildSystemPrompt(prompt);
|
|
2508
|
+
if (this.messages.length > 0) {
|
|
2509
|
+
this.messages.unshift({
|
|
2510
|
+
role: "system",
|
|
2511
|
+
content: systemPrompt
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
/**
|
|
2516
|
+
* 追加系统提示内容
|
|
2517
|
+
*/
|
|
2518
|
+
appendSystemPrompt(additionalContent) {
|
|
2519
|
+
const systemMessageIndex = this.messages.findIndex((m) => m.role === "system");
|
|
2520
|
+
if (systemMessageIndex >= 0) {
|
|
2521
|
+
this.messages[systemMessageIndex].content += `
|
|
2522
|
+
|
|
2523
|
+
${additionalContent}`;
|
|
2524
|
+
} else {
|
|
2525
|
+
const systemPrompt = this.buildSystemPrompt(additionalContent);
|
|
2526
|
+
this.messages.unshift({
|
|
2527
|
+
role: "system",
|
|
2528
|
+
content: systemPrompt
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* 获取当前系统提示内容
|
|
2534
|
+
*/
|
|
2535
|
+
getSystemPrompt() {
|
|
2536
|
+
const systemMessage = this.messages.find((m) => m.role === "system");
|
|
2537
|
+
if (!systemMessage) return void 0;
|
|
2538
|
+
return typeof systemMessage.content === "string" ? systemMessage.content : void 0;
|
|
2539
|
+
}
|
|
2540
|
+
/**
|
|
2541
|
+
* 手动触发上下文压缩
|
|
2542
|
+
*/
|
|
2543
|
+
async compressContext() {
|
|
2544
|
+
if (!this.contextManager) {
|
|
2545
|
+
throw new Error("Context management is disabled");
|
|
2546
|
+
}
|
|
2547
|
+
const result = await this.contextManager.compress(this.messages);
|
|
2548
|
+
this.messages = result.messages;
|
|
2549
|
+
this.sessionUsage = this.contextManager.resetUsage();
|
|
2550
|
+
await this.sessionManager.saveMessages(this.messages);
|
|
2551
|
+
return {
|
|
2552
|
+
messageCount: this.messages.length,
|
|
2553
|
+
stats: result.stats
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
/**
|
|
2557
|
+
* 获取上下文状态
|
|
2558
|
+
*/
|
|
2559
|
+
getContextStatus() {
|
|
2560
|
+
if (!this.contextManager) {
|
|
2561
|
+
return null;
|
|
2562
|
+
}
|
|
2563
|
+
return this.contextManager.getStatus(this.sessionUsage);
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* 获取会话累计 Token 使用量
|
|
2567
|
+
*/
|
|
2568
|
+
getSessionUsage() {
|
|
2569
|
+
return {
|
|
2570
|
+
...this.sessionUsage,
|
|
2571
|
+
totalTokens: this.sessionUsage.inputTokens + this.sessionUsage.outputTokens
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* 检查并执行上下文压缩
|
|
2576
|
+
* @returns 压缩事件数组(可能为空)
|
|
2577
|
+
*/
|
|
2578
|
+
async checkContextCompression() {
|
|
2579
|
+
if (!this.contextManager) {
|
|
2580
|
+
return [];
|
|
2581
|
+
}
|
|
2582
|
+
this.messages = this.contextManager.prune(this.messages);
|
|
2583
|
+
if (!this.contextManager.shouldCompress(this.sessionUsage)) {
|
|
2584
|
+
return [];
|
|
2585
|
+
}
|
|
2586
|
+
const result = await this.contextManager.compress(this.messages);
|
|
2587
|
+
this.messages = result.messages;
|
|
2588
|
+
this.sessionUsage = this.contextManager.resetUsage();
|
|
2589
|
+
return [
|
|
2590
|
+
{
|
|
2591
|
+
type: "context_compressed",
|
|
2592
|
+
stats: result.stats
|
|
2593
|
+
}
|
|
2594
|
+
];
|
|
2595
|
+
}
|
|
2596
|
+
getSubagentConfig() {
|
|
2597
|
+
return {
|
|
2598
|
+
enabled: this.config.subagent?.enabled !== false,
|
|
2599
|
+
maxDepth: this.config.subagent?.maxDepth ?? 1,
|
|
2600
|
+
maxParallel: this.config.subagent?.maxParallel ?? 5,
|
|
2601
|
+
timeoutMs: this.config.subagent?.timeoutMs ?? 12e4,
|
|
2602
|
+
allowDangerousTools: this.config.subagent?.allowDangerousTools ?? false,
|
|
2603
|
+
defaultAllowedTools: this.config.subagent?.defaultAllowedTools
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
resolveSubagentTools(request) {
|
|
2607
|
+
const subagentConfig = this.getSubagentConfig();
|
|
2608
|
+
const parentTools = this.toolRegistry.getAll();
|
|
2609
|
+
const byName = new Map(parentTools.map((tool) => [tool.name, tool]));
|
|
2610
|
+
const requestedNames = request.allowed_tools ?? subagentConfig.defaultAllowedTools;
|
|
2611
|
+
let selected = requestedNames ? requestedNames.map((name) => byName.get(name)).filter((tool) => tool !== void 0) : parentTools.filter((tool) => !tool.isDangerous);
|
|
2612
|
+
selected = selected.filter((tool) => tool.name !== "Agent");
|
|
2613
|
+
selected = selected.filter((tool) => tool.name !== "AskUserQuestion");
|
|
2614
|
+
if (!subagentConfig.allowDangerousTools) {
|
|
2615
|
+
const requestedDangerous = request.allowed_tools?.some((name) => byName.get(name)?.isDangerous);
|
|
2616
|
+
if (requestedDangerous) {
|
|
2617
|
+
return {
|
|
2618
|
+
error: "Subagent dangerous tools are disabled by configuration"
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
selected = selected.filter((tool) => !tool.isDangerous);
|
|
2622
|
+
}
|
|
2623
|
+
if (selected.length === 0) {
|
|
2624
|
+
return { error: "No tools available for subagent after filtering" };
|
|
2625
|
+
}
|
|
2626
|
+
return { tools: selected };
|
|
2627
|
+
}
|
|
2628
|
+
async runSubagent(request, context) {
|
|
2629
|
+
const subagentConfig = this.getSubagentConfig();
|
|
2630
|
+
const currentDepth = context?.agentDepth ?? this.agentDepth;
|
|
2631
|
+
if (!subagentConfig.enabled) {
|
|
2632
|
+
return { content: "Subagent is disabled by configuration", isError: true };
|
|
2633
|
+
}
|
|
2634
|
+
if (currentDepth >= subagentConfig.maxDepth) {
|
|
2635
|
+
return { content: "Subagent cannot spawn subagents", isError: true };
|
|
2636
|
+
}
|
|
2637
|
+
if (this.activeSubagentRuns >= subagentConfig.maxParallel) {
|
|
2638
|
+
return { content: "Subagent concurrency limit reached", isError: true };
|
|
2639
|
+
}
|
|
2640
|
+
const normalizedType = request.subagent_type ?? "general-purpose";
|
|
2641
|
+
const requestedTimeout = request.timeout_ms ?? subagentConfig.timeoutMs;
|
|
2642
|
+
const timeoutMs = Math.min(requestedTimeout, subagentConfig.timeoutMs);
|
|
2643
|
+
const maxIterations = Math.max(1, request.max_iterations ?? this.config.maxIterations ?? 50);
|
|
2644
|
+
const resolved = this.resolveSubagentTools(request);
|
|
2645
|
+
if (!resolved.tools) {
|
|
2646
|
+
return {
|
|
2647
|
+
content: resolved.error ?? "Unable to resolve subagent tools",
|
|
2648
|
+
isError: true
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
const childConfig = {
|
|
2652
|
+
...this.config,
|
|
2653
|
+
exclusiveTools: resolved.tools,
|
|
2654
|
+
tools: void 0,
|
|
2655
|
+
mcpServers: void 0,
|
|
2656
|
+
maxIterations,
|
|
2657
|
+
subagent: {
|
|
2658
|
+
...this.config.subagent,
|
|
2659
|
+
enabled: false
|
|
2660
|
+
}
|
|
2661
|
+
};
|
|
2662
|
+
const child = new _Agent(childConfig);
|
|
2663
|
+
child.agentDepth = currentDepth + 1;
|
|
2664
|
+
const startedAt = Date.now();
|
|
2665
|
+
this.activeSubagentRuns += 1;
|
|
2666
|
+
try {
|
|
2667
|
+
await child.waitForInit();
|
|
2668
|
+
const runPromise = child.run(request.prompt, {
|
|
2669
|
+
systemPrompt: request.system_prompt
|
|
2670
|
+
});
|
|
2671
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2672
|
+
const timer = setTimeout(() => {
|
|
2673
|
+
reject(new Error(`Subagent timed out after ${timeoutMs}ms`));
|
|
2674
|
+
}, timeoutMs);
|
|
2675
|
+
runPromise.finally(() => clearTimeout(timer)).catch(() => {
|
|
2676
|
+
});
|
|
2677
|
+
});
|
|
2678
|
+
const result = await Promise.race([runPromise, timeoutPromise]);
|
|
2679
|
+
return {
|
|
2680
|
+
content: result.content,
|
|
2681
|
+
metadata: {
|
|
2682
|
+
sessionId: result.sessionId,
|
|
2683
|
+
subagentType: normalizedType,
|
|
2684
|
+
durationMs: Date.now() - startedAt,
|
|
2685
|
+
usage: result.usage,
|
|
2686
|
+
toolNames: resolved.tools.map((tool) => tool.name),
|
|
2687
|
+
description: request.description
|
|
2688
|
+
}
|
|
2689
|
+
};
|
|
2690
|
+
} catch (error) {
|
|
2691
|
+
return {
|
|
2692
|
+
content: error instanceof Error ? error.message : String(error),
|
|
2693
|
+
isError: true,
|
|
2694
|
+
metadata: {
|
|
2695
|
+
subagentType: normalizedType,
|
|
2696
|
+
durationMs: Date.now() - startedAt,
|
|
2697
|
+
description: request.description,
|
|
2698
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2699
|
+
}
|
|
2700
|
+
};
|
|
2701
|
+
} finally {
|
|
2702
|
+
this.activeSubagentRuns -= 1;
|
|
2703
|
+
await child.destroy();
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
/**
|
|
2707
|
+
* 获取默认系统提示词
|
|
2708
|
+
*/
|
|
2709
|
+
static getDefaultSystemPrompt() {
|
|
2710
|
+
return DEFAULT_SYSTEM_PROMPT;
|
|
2711
|
+
}
|
|
2712
|
+
/**
|
|
2713
|
+
* 执行工具调用
|
|
2714
|
+
*/
|
|
2715
|
+
async executeTools(toolCalls) {
|
|
2716
|
+
const results = await Promise.all(
|
|
2717
|
+
toolCalls.map(async (tc) => {
|
|
2718
|
+
const result = await this.toolRegistry.execute(tc.name, tc.arguments, {
|
|
2719
|
+
toolCallId: tc.id,
|
|
2720
|
+
projectDir: this.config.cwd || process.cwd(),
|
|
2721
|
+
agentDepth: this.agentDepth
|
|
2722
|
+
});
|
|
2723
|
+
const isError = Boolean(result.isError);
|
|
2724
|
+
return {
|
|
2725
|
+
toolCallId: tc.id,
|
|
2726
|
+
content: isError ? `Error: ${result.content}` : result.content,
|
|
2727
|
+
isError,
|
|
2728
|
+
error: isError ? new Error(result.content) : void 0
|
|
2729
|
+
};
|
|
2730
|
+
})
|
|
2731
|
+
);
|
|
2732
|
+
return results;
|
|
2733
|
+
}
|
|
2734
|
+
};
|
|
2735
|
+
function createAgent(config) {
|
|
2736
|
+
return new Agent(config);
|
|
2737
|
+
}
|
|
2738
|
+
function expandEnvVars(value) {
|
|
2739
|
+
let result = value.replace(/\$\{([^}]+)\}/g, (_, varName) => {
|
|
2740
|
+
return process.env[varName] || "";
|
|
2741
|
+
});
|
|
2742
|
+
result = result.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, varName) => {
|
|
2743
|
+
return process.env[varName] || "";
|
|
2744
|
+
});
|
|
2745
|
+
return result;
|
|
2746
|
+
}
|
|
2747
|
+
function expandEnvVarsInObject(obj) {
|
|
2748
|
+
if (typeof obj === "string") {
|
|
2749
|
+
return expandEnvVars(obj);
|
|
2750
|
+
}
|
|
2751
|
+
if (Array.isArray(obj)) {
|
|
2752
|
+
return obj.map((item) => expandEnvVarsInObject(item));
|
|
2753
|
+
}
|
|
2754
|
+
if (obj && typeof obj === "object") {
|
|
2755
|
+
const result = {};
|
|
2756
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2757
|
+
result[key] = expandEnvVarsInObject(value);
|
|
2758
|
+
}
|
|
2759
|
+
return result;
|
|
2760
|
+
}
|
|
2761
|
+
return obj;
|
|
2762
|
+
}
|
|
2763
|
+
function transformConfig(config) {
|
|
2764
|
+
const servers = [];
|
|
2765
|
+
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
2766
|
+
const transport = serverConfig.url ? "http" : "stdio";
|
|
2767
|
+
const server = {
|
|
2768
|
+
name,
|
|
2769
|
+
transport,
|
|
2770
|
+
...transport === "stdio" ? {
|
|
2771
|
+
command: serverConfig.command,
|
|
2772
|
+
args: serverConfig.args,
|
|
2773
|
+
env: serverConfig.env
|
|
2774
|
+
} : {
|
|
2775
|
+
url: serverConfig.url,
|
|
2776
|
+
headers: serverConfig.headers
|
|
2777
|
+
}
|
|
2778
|
+
};
|
|
2779
|
+
servers.push(server);
|
|
2780
|
+
}
|
|
2781
|
+
return servers;
|
|
2782
|
+
}
|
|
2783
|
+
function findConfigFiles(startDir = process.cwd(), userBasePath) {
|
|
2784
|
+
const paths = [];
|
|
2785
|
+
const base = userBasePath || os.homedir();
|
|
2786
|
+
const userConfig = path.join(base, ".claude", "mcp_config.json");
|
|
2787
|
+
if (fs.existsSync(userConfig)) {
|
|
2788
|
+
paths.push(userConfig);
|
|
2789
|
+
}
|
|
2790
|
+
const workspaceConfig = path.join(startDir, ".claude", "mcp_config.json");
|
|
2791
|
+
if (fs.existsSync(workspaceConfig)) {
|
|
2792
|
+
paths.push(workspaceConfig);
|
|
2793
|
+
}
|
|
2794
|
+
return paths;
|
|
2795
|
+
}
|
|
2796
|
+
function loadSingleConfig(filePath) {
|
|
2797
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
2798
|
+
const rawConfig = JSON.parse(content);
|
|
2799
|
+
const expandedConfig = expandEnvVarsInObject(rawConfig);
|
|
2800
|
+
return transformConfig(expandedConfig);
|
|
2801
|
+
}
|
|
2802
|
+
function loadMCPConfig(configPath, startDir = process.cwd(), userBasePath) {
|
|
2803
|
+
if (configPath) {
|
|
2804
|
+
if (!fs.existsSync(configPath)) {
|
|
2805
|
+
return { servers: [] };
|
|
2806
|
+
}
|
|
2807
|
+
try {
|
|
2808
|
+
const servers = loadSingleConfig(configPath);
|
|
2809
|
+
return { servers, configPath };
|
|
2810
|
+
} catch (error) {
|
|
2811
|
+
console.error(`Failed to load MCP config from ${configPath}:`, error);
|
|
2812
|
+
return { servers: [] };
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
const configPaths = findConfigFiles(startDir, userBasePath);
|
|
2816
|
+
if (configPaths.length === 0) {
|
|
2817
|
+
return { servers: [] };
|
|
2818
|
+
}
|
|
2819
|
+
const mergedServers = /* @__PURE__ */ new Map();
|
|
2820
|
+
for (const path of configPaths) {
|
|
2821
|
+
try {
|
|
2822
|
+
const servers = loadSingleConfig(path);
|
|
2823
|
+
for (const server of servers) {
|
|
2824
|
+
mergedServers.set(server.name, server);
|
|
2825
|
+
}
|
|
2826
|
+
} catch (error) {
|
|
2827
|
+
console.error(`Failed to load MCP config from ${path}:`, error);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
return {
|
|
2831
|
+
servers: Array.from(mergedServers.values()),
|
|
2832
|
+
configPath: configPaths[configPaths.length - 1],
|
|
2833
|
+
// 主配置(工作目录)
|
|
2834
|
+
configPaths
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
function validateMCPConfig(config) {
|
|
2838
|
+
const errors = [];
|
|
2839
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
2840
|
+
errors.push("mcpServers must be an object");
|
|
2841
|
+
return errors;
|
|
2842
|
+
}
|
|
2843
|
+
for (const [name, server] of Object.entries(config.mcpServers)) {
|
|
2844
|
+
if (!server.command && !server.url) {
|
|
2845
|
+
errors.push(`Server "${name}": must have either "command" or "url"`);
|
|
2846
|
+
}
|
|
2847
|
+
if (server.command && server.url) {
|
|
2848
|
+
errors.push(`Server "${name}": cannot have both "command" and "url"`);
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
return errors;
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
exports.Agent = Agent;
|
|
2855
|
+
exports.DEFAULT_SYSTEM_PROMPT = DEFAULT_SYSTEM_PROMPT;
|
|
2856
|
+
exports.JsonlStorage = JsonlStorage;
|
|
2857
|
+
exports.MCPAdapter = MCPAdapter;
|
|
2858
|
+
exports.MCPClient = MCPClient;
|
|
2859
|
+
exports.MemoryManager = MemoryManager;
|
|
2860
|
+
exports.MemoryStorage = MemoryStorage;
|
|
2861
|
+
exports.SessionManager = SessionManager;
|
|
2862
|
+
exports.SkillLoader = SkillLoader;
|
|
2863
|
+
exports.SkillRegistry = SkillRegistry;
|
|
2864
|
+
exports.StreamChunkProcessor = StreamChunkProcessor;
|
|
2865
|
+
exports.createAgent = createAgent;
|
|
2866
|
+
exports.createJsonlStorage = createJsonlStorage;
|
|
2867
|
+
exports.createMCPAdapter = createMCPAdapter;
|
|
2868
|
+
exports.createMCPClient = createMCPClient;
|
|
2869
|
+
exports.createMemoryStorage = createMemoryStorage;
|
|
2870
|
+
exports.createSessionManager = createSessionManager;
|
|
2871
|
+
exports.createSkillLoader = createSkillLoader;
|
|
2872
|
+
exports.createSkillRegistry = createSkillRegistry;
|
|
2873
|
+
exports.createStorage = createStorage;
|
|
2874
|
+
exports.getLatestSessionId = getLatestSessionId;
|
|
2875
|
+
exports.getSessionStoragePath = getSessionStoragePath;
|
|
2876
|
+
exports.loadMCPConfig = loadMCPConfig;
|
|
2877
|
+
exports.parseSkillMd = parseSkillMd;
|
|
2878
|
+
exports.validateMCPConfig = validateMCPConfig;
|
|
2879
|
+
//# sourceMappingURL=chunk-5QMA2YBY.cjs.map
|
|
2880
|
+
//# sourceMappingURL=chunk-5QMA2YBY.cjs.map
|