ningzhi-agent-cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CLAUDE.md +197 -0
  2. package/README.md +35 -0
  3. package/dist/agent/agent.d.ts +24 -0
  4. package/dist/agent/agent.js +432 -0
  5. package/dist/agent/cli.d.ts +1 -0
  6. package/dist/agent/cli.js +209 -0
  7. package/dist/agent/colors.d.ts +19 -0
  8. package/dist/agent/colors.js +92 -0
  9. package/dist/agent/commands.d.ts +7 -0
  10. package/dist/agent/commands.js +73 -0
  11. package/dist/agent/config.d.ts +38 -0
  12. package/dist/agent/config.js +119 -0
  13. package/dist/agent/context.d.ts +17 -0
  14. package/dist/agent/context.js +146 -0
  15. package/dist/agent/db.d.ts +25 -0
  16. package/dist/agent/db.js +185 -0
  17. package/dist/agent/hooks/engine.d.ts +27 -0
  18. package/dist/agent/hooks/engine.js +122 -0
  19. package/dist/agent/hooks/index.d.ts +1 -0
  20. package/dist/agent/hooks/index.js +11 -0
  21. package/dist/agent/mcp/client.d.ts +31 -0
  22. package/dist/agent/mcp/client.js +138 -0
  23. package/dist/agent/mcp/index.d.ts +4 -0
  24. package/dist/agent/mcp/index.js +27 -0
  25. package/dist/agent/mcp/wrapper.d.ts +7 -0
  26. package/dist/agent/mcp/wrapper.js +26 -0
  27. package/dist/agent/model.d.ts +5 -0
  28. package/dist/agent/model.js +35 -0
  29. package/dist/agent/permission/dangerous-path.json +115 -0
  30. package/dist/agent/permission/exec.d.ts +14 -0
  31. package/dist/agent/permission/exec.js +35 -0
  32. package/dist/agent/permission/is-dangerous-path.d.ts +8 -0
  33. package/dist/agent/permission/is-dangerous-path.js +96 -0
  34. package/dist/agent/permission/is-safe-domains.d.ts +1 -0
  35. package/dist/agent/permission/is-safe-domains.js +145 -0
  36. package/dist/agent/permission/network.d.ts +8 -0
  37. package/dist/agent/permission/network.js +14 -0
  38. package/dist/agent/permission/read.d.ts +9 -0
  39. package/dist/agent/permission/read.js +17 -0
  40. package/dist/agent/permission/util.d.ts +11 -0
  41. package/dist/agent/permission/util.js +347 -0
  42. package/dist/agent/permission/write.d.ts +11 -0
  43. package/dist/agent/permission/write.js +21 -0
  44. package/dist/agent/prompt.d.ts +1 -0
  45. package/dist/agent/prompt.js +52 -0
  46. package/dist/agent/skills/oppose/SKILL.md +100 -0
  47. package/dist/agent/skills/planner/SKILL.md +90 -0
  48. package/dist/agent/skills.d.ts +8 -0
  49. package/dist/agent/skills.js +103 -0
  50. package/dist/agent/tools/agent_tool.d.ts +3 -0
  51. package/dist/agent/tools/agent_tool.js +44 -0
  52. package/dist/agent/tools/exec_tool.d.ts +3 -0
  53. package/dist/agent/tools/exec_tool.js +33 -0
  54. package/dist/agent/tools/load_skill_tool.d.ts +3 -0
  55. package/dist/agent/tools/load_skill_tool.js +16 -0
  56. package/dist/agent/tools/memory_create_tool.d.ts +6 -0
  57. package/dist/agent/tools/memory_create_tool.js +39 -0
  58. package/dist/agent/tools/memory_delete_tool.d.ts +3 -0
  59. package/dist/agent/tools/memory_delete_tool.js +28 -0
  60. package/dist/agent/tools/memory_retrieve_tool.d.ts +4 -0
  61. package/dist/agent/tools/memory_retrieve_tool.js +26 -0
  62. package/dist/agent/tools/profile_update_tool.d.ts +3 -0
  63. package/dist/agent/tools/profile_update_tool.js +51 -0
  64. package/dist/agent/tools/read_file_tool.d.ts +3 -0
  65. package/dist/agent/tools/read_file_tool.js +16 -0
  66. package/dist/agent/tools/run_js_tool.d.ts +3 -0
  67. package/dist/agent/tools/run_js_tool.js +47 -0
  68. package/dist/agent/tools/run_py_tool.d.ts +3 -0
  69. package/dist/agent/tools/run_py_tool.js +37 -0
  70. package/dist/agent/tools/web_fetch_tool.d.ts +3 -0
  71. package/dist/agent/tools/web_fetch_tool.js +28 -0
  72. package/dist/agent/tools/web_search_tool.d.ts +3 -0
  73. package/dist/agent/tools/web_search_tool.js +38 -0
  74. package/dist/agent/tools/write_file_tool.d.ts +4 -0
  75. package/dist/agent/tools/write_file_tool.js +15 -0
  76. package/dist/agent/tools.d.ts +14 -0
  77. package/dist/agent/tools.js +218 -0
  78. package/dist/agent/utils.d.ts +11 -0
  79. package/dist/agent/utils.js +43 -0
  80. package/dist/index.d.ts +2 -0
  81. package/dist/index.js +81 -0
  82. package/dist/install.d.ts +1 -0
  83. package/dist/install.js +150 -0
  84. package/package.json +56 -0
@@ -0,0 +1,432 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.initAgent = initAgent;
37
+ exports.runAgentStream = runAgentStream;
38
+ exports.runSubAgent = runSubAgent;
39
+ exports.compressContext = compressContext;
40
+ const langgraph_checkpoint_sqlite_1 = require("@langchain/langgraph-checkpoint-sqlite");
41
+ const messages_1 = require("@langchain/core/messages");
42
+ const langgraph_1 = require("@langchain/langgraph");
43
+ const langgraph_2 = require("@langchain/langgraph");
44
+ const dotenv = __importStar(require("dotenv"));
45
+ const fs = __importStar(require("node:fs"));
46
+ const db_1 = require("./db");
47
+ const tools_1 = require("./tools");
48
+ const context_1 = require("./context");
49
+ const colors_1 = require("./colors");
50
+ const prompt_1 = require("./prompt");
51
+ const read_1 = require("./permission/read");
52
+ const write_1 = require("./permission/write");
53
+ const exec_1 = require("./permission/exec");
54
+ const network_1 = require("./permission/network");
55
+ const hooks_1 = require("./hooks");
56
+ dotenv.config();
57
+ const model_1 = require("./model");
58
+ // ── 模型 ──────────────────────────────────────────────────
59
+ const model = (0, model_1.createModel)({ streaming: true });
60
+ // ── State Schema ──────────────────────────────────────────
61
+ const StateAnnotation = langgraph_1.Annotation.Root({
62
+ // 完整对话消息列表(含 HumanMessage / AIMessage / ToolMessage 等)
63
+ messages: (0, langgraph_1.Annotation)({
64
+ // reducer:合并状态更新时使用 langgraph 内置的消息合并器(按 id 去重、追加新消息、支持删除/替换)
65
+ reducer: langgraph_2.messagesStateReducer,
66
+ default: () => [],
67
+ }),
68
+ contextSummary: (0, langgraph_1.Annotation)({
69
+ reducer: (_prev, next) => next, // 保持最新摘要
70
+ default: () => null,
71
+ }),
72
+ compressionCount: (0, langgraph_1.Annotation)({
73
+ reducer: (_prev, next) => next, // 保持最新压缩次数
74
+ default: () => 0,
75
+ }),
76
+ lastCompressedIndex: (0, langgraph_1.Annotation)({
77
+ reducer: (_prev, next) => next, // 保持最新压缩索引
78
+ default: () => 0,
79
+ }),
80
+ });
81
+ // ── Graph Nodes ───────────────────────────────────────────
82
+ function shouldContinue(state) {
83
+ const lastMessage = state.messages[state.messages.length - 1];
84
+ if (messages_1.AIMessage.isInstance(lastMessage) && lastMessage.tool_calls?.length) {
85
+ return "tools";
86
+ }
87
+ return langgraph_1.END;
88
+ }
89
+ /**
90
+ * 简化工具调用消息,只保留最近 3 个
91
+ * @param messages - 原始消息数组
92
+ * @returns 简化后的消息数组
93
+ */
94
+ function simplifyToolMessages(messages) {
95
+ const toolIndices = [];
96
+ // 找到所有工具调用消息的索引
97
+ for (let i = 0; i < messages.length; i++) {
98
+ if (messages[i].type === "tool") {
99
+ toolIndices.push(i);
100
+ }
101
+ }
102
+ // 只保留最近 3 个工具调用消息
103
+ const recentToolIndices = new Set(toolIndices.slice(-3));
104
+ // 遍历所有消息,简化工具调用消息
105
+ return messages.map((msg, i) => {
106
+ if (msg.type !== "tool")
107
+ return msg;
108
+ if (recentToolIndices.has(i))
109
+ return msg;
110
+ const toolMsg = msg;
111
+ if (toolMsg.name === "read_file")
112
+ return msg;
113
+ // 简化工具调用消息,只保留工具名
114
+ return new messages_1.ToolMessage({
115
+ content: `[Previous: used ${toolMsg.name}]`,
116
+ tool_call_id: toolMsg.tool_call_id,
117
+ name: toolMsg.name,
118
+ });
119
+ });
120
+ }
121
+ // ── 记忆 ──────────────────────────────────────────────────
122
+ // recursive 作用: 1. 目录已存在时静默跳过(最重要)不报错。 2. 支持多级路径自动补全父目录:
123
+ // fs.mkdirSync("a/b/c", { recursive: true }); // 一口气把 a、a/b、a/b/c 全建出来
124
+ fs.mkdirSync(".dbData", { recursive: true });
125
+ const checkpointer = langgraph_checkpoint_sqlite_1.SqliteSaver.fromConnString(db_1.DB_PATH);
126
+ // ── Agent Graph 工厂 ──────────────────────────────────────
127
+ function createAgentGraph(toolList) {
128
+ const modelWithTheseTools = model.bindTools(toolList);
129
+ async function modelRequest(state, config) {
130
+ let modelMessages = state.messages ?? [];
131
+ // 如果有历史摘要,添加到系统提示词
132
+ if (state.contextSummary && state.lastCompressedIndex > 0) {
133
+ const summaryMsg = new messages_1.SystemMessage(`历史对话摘要:\n\n${state.contextSummary}`);
134
+ // 从压缩索引开始添加新消息
135
+ modelMessages = [
136
+ summaryMsg,
137
+ ...modelMessages.slice(state.lastCompressedIndex),
138
+ ];
139
+ }
140
+ // 简化工具调用消息,只保留工具名
141
+ modelMessages = simplifyToolMessages(modelMessages);
142
+ // 上下文压缩的最后一道防线
143
+ // 只保留最近 500 条消息, 极端场景,一般达不到,是为了防止上下文爆炸,不过一般不加这个逻辑也可以正常工作
144
+ modelMessages = modelMessages.slice(-500);
145
+ // 构建模型输入,包含系统提示词和简化后的消息
146
+ const messages = [new messages_1.SystemMessage(prompt_1.systemPrompt), ...modelMessages];
147
+ // 调用模型
148
+ const response = await modelWithTheseTools.invoke(messages, config);
149
+ // 返回模型响应, response 是一个 AIMessage 对象
150
+ return { messages: [response] };
151
+ }
152
+ async function toolNode(state, config) {
153
+ const messages = state.messages;
154
+ // 找到所有工具调用消息的 id
155
+ const toolMessageIds = new Set(messages
156
+ .filter((msg) => msg.type === "tool")
157
+ .map((msg) => msg.tool_call_id));
158
+ // 找到最后一个 AI 消息
159
+ let aiMessage;
160
+ for (let i = messages.length - 1; i >= 0; i--) {
161
+ if (messages_1.AIMessage.isInstance(messages[i])) {
162
+ aiMessage = messages[i];
163
+ break;
164
+ }
165
+ }
166
+ // 如果没有 AI 消息,或者不是 AI 消息,抛出错误
167
+ if (!aiMessage || !messages_1.AIMessage.isInstance(aiMessage)) {
168
+ throw new Error("ToolNode only accepts AIMessages as input.");
169
+ }
170
+ // 过滤出 未处理的工具调用, 就是 id 为 null 或不在 toolMessageIds 中的
171
+ // 如果 ai message 某个工具调用的 id 找不到对应的 ToolMessage,说明它还没被执行过,
172
+ // 正常是走 !toolMessageIds.has(call.id) 这个逻辑
173
+ const toolCalls = aiMessage.tool_calls?.filter((call) => call.id == null || !toolMessageIds.has(call.id)) ?? [];
174
+ if (toolCalls.length === 0) {
175
+ return { messages: [] };
176
+ }
177
+ // 过滤出允许执行的工具调用
178
+ const allowCalls = [];
179
+ const blockMessages = [];
180
+ const confirmCalls = [];
181
+ // 遍历所有工具调用,根据权限等级判断是否允许执行
182
+ for (const call of toolCalls) {
183
+ const tool = toolList.find((t) => t.name === call.name);
184
+ const level = tool?.permission_level;
185
+ let decision;
186
+ if (level === "read") {
187
+ decision = (0, read_1.checkReadPermission)(call);
188
+ }
189
+ else if (level === "write") {
190
+ decision = (0, write_1.checkWritePermission)(call);
191
+ }
192
+ else if (level === "exec") {
193
+ decision = (0, exec_1.checkExecPermission)(call);
194
+ }
195
+ else if (level === "network") {
196
+ decision = (0, network_1.checkNetworkPermission)(call);
197
+ }
198
+ else if (level === "mcp") {
199
+ decision = { action: "confirm" };
200
+ }
201
+ else {
202
+ decision = { action: "allow" };
203
+ }
204
+ if (decision.action === "allow") {
205
+ allowCalls.push(call);
206
+ }
207
+ else if (decision.action === "block") {
208
+ blockMessages.push(new messages_1.ToolMessage({
209
+ content: decision.reason,
210
+ tool_call_id: call.id ?? "",
211
+ name: call.name,
212
+ }));
213
+ }
214
+ else {
215
+ confirmCalls.push(call);
216
+ }
217
+ }
218
+ // 执行允许执行的工具调用
219
+ async function executeCalls(calls) {
220
+ return Promise.all(calls.map(async (call) => {
221
+ const tool = toolList.find((t) => t.name === call.name);
222
+ console.log((0, colors_1.formatToolLog)(call.name, JSON.stringify(call.args)));
223
+ try {
224
+ const threadId = config?.configurable?.thread_id || "";
225
+ // PreToolUse hook
226
+ const preResult = await (0, hooks_1.runPreToolUseHooks)({
227
+ toolName: call.name,
228
+ toolArgs: call.args,
229
+ toolCallId: call.id ?? "",
230
+ threadId,
231
+ });
232
+ if (preResult.action === "block") {
233
+ return new messages_1.ToolMessage({
234
+ content: preResult.reason,
235
+ tool_call_id: call.id ?? "",
236
+ name: call.name,
237
+ });
238
+ }
239
+ if (!tool)
240
+ throw new Error(`Tool "${call.name}" not found.`);
241
+ const output = await tool.invoke({ ...call, type: "tool_call" }, config);
242
+ let content = typeof output === "string" ? output : JSON.stringify(output);
243
+ // PostToolUse hook
244
+ const postResult = await (0, hooks_1.runPostToolUseHooks)({
245
+ toolName: call.name,
246
+ toolArgs: call.args,
247
+ toolOutput: content,
248
+ toolCallId: call.id ?? "",
249
+ threadId,
250
+ });
251
+ if (postResult.action === "block") {
252
+ content = postResult.reason;
253
+ }
254
+ else if (postResult.action === "inject") {
255
+ content = `[Hook injection]\n${postResult.message}\n\n${content}`;
256
+ }
257
+ if (preResult.action === "inject") {
258
+ content = `[Hook injection]\n${preResult.message}\n\n${content}`;
259
+ }
260
+ const finalContent = await (0, tools_1.maybePersistedOutput)(content, call.id ?? "");
261
+ return new messages_1.ToolMessage({
262
+ content: finalContent,
263
+ tool_call_id: call.id ?? "",
264
+ name: call.name,
265
+ });
266
+ }
267
+ catch (e) {
268
+ return new messages_1.ToolMessage({
269
+ content: `Error: ${e.message}\n Please fix your mistakes.`,
270
+ tool_call_id: call.id ?? "",
271
+ name: call.name,
272
+ });
273
+ }
274
+ }));
275
+ }
276
+ const allowOutputs = await executeCalls(allowCalls);
277
+ if (confirmCalls.length === 0) {
278
+ return { messages: [...allowOutputs, ...blockMessages] };
279
+ }
280
+ const result = (0, langgraph_1.interrupt)({ toolCalls: confirmCalls });
281
+ if (result !== "approved") {
282
+ const deniedMessages = confirmCalls.map((call) => new messages_1.ToolMessage({
283
+ content: "Tool execution was denied by the user.",
284
+ tool_call_id: call.id ?? "",
285
+ name: call.name,
286
+ }));
287
+ return {
288
+ messages: [...allowOutputs, ...blockMessages, ...deniedMessages],
289
+ };
290
+ }
291
+ const confirmOutputs = await executeCalls(confirmCalls);
292
+ return { messages: [...allowOutputs, ...blockMessages, ...confirmOutputs] };
293
+ }
294
+ // 定义工作流
295
+ const workflow = new langgraph_1.StateGraph(StateAnnotation)
296
+ .addNode("model_request", modelRequest)
297
+ .addNode("tools", toolNode)
298
+ .addEdge(langgraph_1.START, "model_request")
299
+ .addConditionalEdges("model_request", shouldContinue, {
300
+ tools: "tools",
301
+ [langgraph_1.END]: langgraph_1.END,
302
+ })
303
+ .addEdge("tools", "model_request");
304
+ // 编译工作流
305
+ return workflow.compile({
306
+ checkpointer,
307
+ });
308
+ }
309
+ // ── Agent 创建 ────────────────────────────────────────────
310
+ let agent = null;
311
+ let subAgent = null;
312
+ async function initAgent() {
313
+ await (0, tools_1.initTools)();
314
+ agent = createAgentGraph(tools_1.tools);
315
+ subAgent = createAgentGraph(tools_1.tools.filter((t) => t.name !== "agent_tool"));
316
+ }
317
+ function getAgent() {
318
+ if (!agent)
319
+ throw new Error("Agent not initialized. Call initAgent() first.");
320
+ return agent;
321
+ }
322
+ function getSubAgent() {
323
+ if (!subAgent)
324
+ throw new Error("Agent not initialized. Call initAgent() first.");
325
+ return subAgent;
326
+ }
327
+ // ── 核心运行逻辑 ──────────────────────────────────────────
328
+ async function _runAgent(compiledAgent, userMessage, onToken, onToolConfirmation, threadId, signal) {
329
+ const config = { configurable: { thread_id: threadId } };
330
+ let fullResponse = "";
331
+ let usageMetadata;
332
+ let input = { messages: [new messages_1.HumanMessage(userMessage)] };
333
+ while (true) {
334
+ const stream = await compiledAgent.stream(input, {
335
+ ...config,
336
+ streamMode: "messages",
337
+ signal,
338
+ });
339
+ // for await...of 遍历的是 异步可迭代对象(AsyncIterable)
340
+ // 每次 next() 返回的是 Promise,需要 await 才能拿到下一项。 LangGraph 的 agent.stream(...) 返回一个 AsyncGenerator。它的特点是:
341
+ // 不是一次性把结果给你,而是 LLM 每生成一个 token(或一小批 token),就 yield 一次。 必须等网络/模型把这块数据推送过来才能继续,这正是 await 存在的意义。
342
+ for await (const chunk of stream) {
343
+ if (signal?.aborted) {
344
+ throw new Error("abort");
345
+ }
346
+ const message = chunk[0];
347
+ const metadata = chunk[1];
348
+ // streamMode: "messages" 下,工具调用等非模型节点产生的消息也会出现在流里,
349
+ // 通过 metadata.langgraph_node 过滤,只保留模型节点(model_request)输出的 token
350
+ if (metadata?.langgraph_node !== "model_request")
351
+ continue;
352
+ // 从 message 中提取 token 使用信息
353
+ const msgUsage = message.usage_metadata;
354
+ if (msgUsage) {
355
+ usageMetadata = msgUsage;
356
+ }
357
+ // AIMessageChunk 的 content 在 message.content 属性上,不在 kwargs.content
358
+ const content = message.content ?? message.kwargs?.content ?? "";
359
+ const toolCallChunks = message.tool_call_chunks ?? [];
360
+ // 过滤掉工具调用消息,只保留模型节点(model_request)输出的 token
361
+ if (!content || toolCallChunks.length > 0)
362
+ continue;
363
+ // 回调 token 给调用方
364
+ onToken(content);
365
+ fullResponse += content;
366
+ }
367
+ // 流消费完后,取出当前会话状态,检查是否卡在 interrupt 上
368
+ // (toolNode 里的 interrupt({ toolCalls }) 会让图执行暂停,等待外部 resume)
369
+ const state = await compiledAgent.getState(config);
370
+ // 找到处于中断状态的任务(其 interrupts 数组非空)
371
+ const interruptedTask = state.tasks?.find((t) => t.interrupts?.length > 0);
372
+ // 没有中断任务,说明本轮已正常走到 END,退出 while 循环
373
+ if (!interruptedTask)
374
+ break;
375
+ // =====》 有需要用户确认的工具调用,就会有中断任务 《===== 工具节点中断,来到 model_request 节点
376
+ // 把待确认的工具调用清单回调给调用方,由用户决定是否执行
377
+ const confirmed = await onToolConfirmation(interruptedTask.interrupts[0].value.toolCalls);
378
+ // 用 Command({ resume }) 把用户的决定送回给正在等待的 interrupt() =====》 发送给 toolNode
379
+ // 从 toolNode 里 interrupt() 调用的那一行继续往下执行
380
+ // 对应 toolNode 中 `const result = interrupt({ toolCalls })` 的返回值
381
+ input = new langgraph_1.Command({ resume: confirmed ? "approved" : "denied" });
382
+ }
383
+ // 返回完整回复及 token 使用信息
384
+ return { response: fullResponse, usageMetadata };
385
+ }
386
+ /**
387
+ * 以流式方式运行 agent,将 token 逐个回调给调用方
388
+ * @param {string} userMessage - 当前用户输入(历史已由 checkpointer 自动续接)
389
+ * @param {Function} onToken - 每个 token 到来时的回调 (token: string) => void
390
+ * @param {Function} onToolConfirmation - tool 调用前的确认回调,返回 true 则执行,false 则拒绝
391
+ * @param {string} threadId - 会话 ID,相同 ID 自动续上历史记录
392
+ * @returns {Promise<{ response: string; usageMetadata?: UsageMetadata }>} 完整的 AI 回复文本及 token 使用信息
393
+ */
394
+ async function runAgentStream(userMessage, onToken, onToolConfirmation, threadId = "default-session", signal) {
395
+ return _runAgent(getAgent(), userMessage, onToken, onToolConfirmation, threadId, signal);
396
+ }
397
+ /**
398
+ * 启动一个 subagent 执行独立任务,完成后返回结果
399
+ * @param {string} prompt - 给 subagent 的任务提示
400
+ * @returns {Promise<string>} subagent 的最终回复
401
+ */
402
+ async function runSubAgent(prompt) {
403
+ const threadId = `subagent-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
404
+ const result = await _runAgent(getSubAgent(), prompt.trim(), () => { }, // 不需要流式输出
405
+ async () => true, // subagent 自动确认 tools
406
+ threadId, undefined);
407
+ return result.response;
408
+ }
409
+ // 压缩上下文
410
+ async function compressContext(threadId) {
411
+ const config = { configurable: { thread_id: threadId } };
412
+ const currentState = await getAgent().getState(config);
413
+ const messages = currentState.values.messages || [];
414
+ const existingSummary = currentState.values.contextSummary || null;
415
+ const lastIndex = currentState.values.lastCompressedIndex || 0;
416
+ const count = currentState.values.compressionCount || 0;
417
+ const recentKeep = 6;
418
+ const safeIndex = (0, context_1.findSafeCompressionIndex)(messages, recentKeep);
419
+ if (safeIndex <= lastIndex) {
420
+ return { didCompress: false, count };
421
+ }
422
+ // 从 lastCompressedIndex 开始,压缩到 safeIndex
423
+ // safeIndex 是最近保留的索引,压缩到 safeIndex 之前的消息
424
+ const toCompress = messages.slice(lastIndex, safeIndex);
425
+ const newSummary = await (0, context_1.compressMessages)(toCompress, existingSummary);
426
+ await getAgent().updateState(config, {
427
+ contextSummary: newSummary,
428
+ lastCompressedIndex: safeIndex,
429
+ compressionCount: count + 1,
430
+ });
431
+ return { didCompress: true, count: count + 1 };
432
+ }
@@ -0,0 +1 @@
1
+ export declare function interactiveChat(): Promise<void>;
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.interactiveChat = interactiveChat;
37
+ const readline = __importStar(require("readline")); // readline 用于处理命令行输入输出,提供交互式界面
38
+ const node_fs_1 = require("node:fs");
39
+ const node_path_1 = require("node:path");
40
+ const agent_1 = require("./agent");
41
+ const context_1 = require("./context");
42
+ const colors_1 = require("./colors");
43
+ const hooks_1 = require("./hooks");
44
+ const commands_1 = require("./commands");
45
+ const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "../../package.json"), "utf-8"));
46
+ function createInterface() {
47
+ return readline.createInterface({
48
+ input: process.stdin,
49
+ output: process.stdout,
50
+ });
51
+ }
52
+ // 打印启动 Banner:ASCII 艺术标题 + 信息盒 + 使用说明
53
+ async function printBanner() {
54
+ // 动态导入 figlet(ASCII 字体)和 boxen(边框盒子),避免首屏加载开销
55
+ const figlet = (await Promise.resolve().then(() => __importStar(require("figlet")))).default; // figlet 用于生成 ASCII 字体
56
+ const { default: boxen } = await Promise.resolve().then(() => __importStar(require("boxen"))); // boxen 用于创建带边框的文本框
57
+ // 用 Slant 字体渲染包名,并以 banner 色输出
58
+ console.log(colors_1.color.banner(figlet.textSync(pkg.name, { font: "Slant" })));
59
+ // 信息盒内容:每行「字段名: 值」,字段名用灰色弱化
60
+ const info = [
61
+ `${colors_1.color.gray("Description")}: ${pkg.description}`,
62
+ `${colors_1.color.gray("Version")}: ${pkg.version}`,
63
+ `${colors_1.color.gray("Author")}: ${pkg.author}`,
64
+ `${colors_1.color.gray("Docs")}: ${pkg.docs}`,
65
+ ].join("\n");
66
+ // boxen 把 info 包成圆角边框盒子,padding=1 留内边距,灰色边框
67
+ console.log(boxen(info, {
68
+ padding: 1,
69
+ borderStyle: "round",
70
+ borderColor: "gray",
71
+ }));
72
+ // 使用说明:按键与退出命令
73
+ console.log("Usage:");
74
+ console.log(" ESC - Cancel AI request");
75
+ console.log(" exit - Exit the chat");
76
+ for (const [name, cmd] of commands_1.commands) {
77
+ // padEnd 如果不足8个字符,用空格填充到8个字符
78
+ console.log(` /${name.padEnd(8)} - ${cmd.description}`);
79
+ }
80
+ console.log();
81
+ }
82
+ function prompt(question) {
83
+ return new Promise((resolve) => {
84
+ const rl = createInterface(); // 创建 readline 接口,用于处理用户输入
85
+ rl.question(question, (answer) => {
86
+ rl.close();
87
+ resolve(answer);
88
+ });
89
+ });
90
+ }
91
+ async function chat(userInput) {
92
+ const rl = createInterface();
93
+ process.stdout.write("\n" + colors_1.color.aiPrefix());
94
+ const controller = new AbortController();
95
+ // 监听 ESC 键,中断 AI 请求
96
+ const escListener = (_str, key) => {
97
+ if (key.name === "escape" || key.name === "esc") {
98
+ process.stdout.write("\n\n" + colors_1.color.cancelled("[Cancelled]") + "\n");
99
+ controller.abort();
100
+ rl.close();
101
+ }
102
+ };
103
+ // 让 process.stdin 开始发出 "keypress" 事件(默认不发)
104
+ // 这样才能在 AI 请求过程中 实时捕获 ESC 键而不必等待用户按回车
105
+ readline.emitKeypressEvents(process.stdin);
106
+ // 监听 process.stdin 的 "keypress" 事件,捕获 ESC 键
107
+ process.stdin.on("keypress", escListener);
108
+ let usageMetadata;
109
+ try {
110
+ const result = await (0, agent_1.runAgentStream)(userInput, (token) => {
111
+ process.stdout.write(token);
112
+ }, async (toolCalls) => {
113
+ for (const call of toolCalls) {
114
+ console.log((0, colors_1.formatToolLog)(call.name, JSON.stringify(call.args)));
115
+ }
116
+ const answer = await new Promise((resolve) => {
117
+ rl.question("确认执行以上工具? (y/n): ", resolve);
118
+ });
119
+ return (answer.trim().toLowerCase() === "y" ||
120
+ answer.trim().toLowerCase() === "yes");
121
+ }, commands_1.threadId, controller.signal);
122
+ usageMetadata = result.usageMetadata;
123
+ }
124
+ catch (err) {
125
+ if (err.message !== "abort") {
126
+ throw err;
127
+ }
128
+ }
129
+ finally {
130
+ process.stdin.removeListener("keypress", escListener);
131
+ // 关闭接口,"交出对 stdin 的控制权",移除 readline 挂在 stdin 上的内部监听
132
+ rl.close();
133
+ }
134
+ // 在一轮对话 结束后打印 token 使用情况
135
+ if (usageMetadata) {
136
+ const limit = (0, context_1.getModelContextLimit)();
137
+ const percentage = (usageMetadata.total_tokens / limit) * 100;
138
+ const percentageStr = percentage.toFixed(1);
139
+ const tokenText = `\n\nTokens: ${usageMetadata.total_tokens.toLocaleString()} / ${limit.toLocaleString()} (${percentageStr}%)`;
140
+ if (percentage >= 80) {
141
+ process.stdout.write("\n" +
142
+ colors_1.color.error(tokenText) +
143
+ "\n" +
144
+ colors_1.color.error("警告:Context window 接近大模型接口上限,即将压缩 Context,可能会丢失信息") +
145
+ "\n" +
146
+ colors_1.color.error("建议输入 /new 命令开启新会话"));
147
+ try {
148
+ const result = await (0, agent_1.compressContext)(commands_1.threadId);
149
+ if (result.didCompress) {
150
+ process.stdout.write("\n" +
151
+ colors_1.color.error(`Context 已压缩(第 ${result.count} 次),已保留最近 6 条消息`) +
152
+ "\n");
153
+ if (result.count >= 3) {
154
+ process.stdout.write(colors_1.color.error("强烈建议输入 /new 命令开启新会话,以避免信息丢失") +
155
+ "\n");
156
+ }
157
+ }
158
+ }
159
+ catch {
160
+ // 压缩失败不影响主流程
161
+ }
162
+ }
163
+ else {
164
+ process.stdout.write("\n" + colors_1.color.tokenInfo(tokenText));
165
+ }
166
+ }
167
+ process.stdout.write("\n\n");
168
+ // 在本轮对话的 readline 接口关闭后,把`process.stdin` 恢复回“流动模式”,
169
+ // 确保下一轮`prompt()` 能正常读到用户输入
170
+ // `rl.close()` 移除了所有消费者之后,stdin 可能退回暂停模式。
171
+ // 如果不恢复,下一轮主循环里`prompt()` 新建的接口有可能出现“提示符显示了但打字没反应”的情况
172
+ rl.resume();
173
+ }
174
+ async function interactiveChat() {
175
+ await printBanner();
176
+ await (0, hooks_1.runSessionStartHooks)(commands_1.threadId);
177
+ while (true) {
178
+ const userInput = await prompt(colors_1.color.userPrefix());
179
+ if (!userInput.trim())
180
+ continue;
181
+ if (userInput.toLowerCase() === "exit") {
182
+ console.log(colors_1.color.goodbye("再见!"));
183
+ break;
184
+ }
185
+ // 以 "/" 开头的输入按内置命令处理(如 /skills),否则交给 AI
186
+ if (userInput.startsWith("/")) {
187
+ // 按空白(空格/Tab)拆分,如 "/new abc" -> ["/new", "abc"]
188
+ const parts = userInput.trim().split(/\s+/);
189
+ // 去掉首字符 "/" 得到命令名,例如:"​/new" -> "new"
190
+ const cmdName = parts[0].slice(1);
191
+ // 其余部分作为命令参数传给处理器
192
+ const args = parts.slice(1);
193
+ const cmd = commands_1.commands.get(cmdName);
194
+ if (cmd) {
195
+ await cmd.execute(args);
196
+ }
197
+ else {
198
+ console.log(colors_1.color.error(`Unknown command: ${cmdName}`));
199
+ }
200
+ continue; // 命令已处理完,回到循环等待下一次输入
201
+ }
202
+ try {
203
+ await chat(userInput);
204
+ }
205
+ catch (err) {
206
+ console.error(colors_1.color.error(`请求出错: ${err.message}`));
207
+ }
208
+ }
209
+ }
@@ -0,0 +1,19 @@
1
+ export declare function initColors(): Promise<void>;
2
+ export declare const color: {
3
+ banner: (text: string) => string;
4
+ userPrefix: () => string;
5
+ aiPrefix: () => string;
6
+ goodbye: (text: string) => string;
7
+ cancelled: (text: string) => string;
8
+ error: (text: string) => string;
9
+ toolTag: () => string;
10
+ toolName: (name: string) => string;
11
+ toolAction: () => string;
12
+ toolArg: (arg: string) => string;
13
+ gray: (text: string) => string;
14
+ tokenInfo: (text: string) => string;
15
+ warn: (text: string) => string;
16
+ info: (text: string) => string;
17
+ good: (text: string) => string;
18
+ };
19
+ export declare function formatToolLog(name: string, detail?: string): string;