js-agent-core 1.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 (168) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +29 -0
  3. package/dist/core/AgentDashboard.d.ts +33 -0
  4. package/dist/core/AgentDashboard.d.ts.map +1 -0
  5. package/dist/core/AgentDashboard.js +477 -0
  6. package/dist/core/AgentDashboard.js.map +1 -0
  7. package/dist/core/AutoVectorStore.d.ts +23 -0
  8. package/dist/core/AutoVectorStore.d.ts.map +1 -0
  9. package/dist/core/AutoVectorStore.js +55 -0
  10. package/dist/core/AutoVectorStore.js.map +1 -0
  11. package/dist/core/BaseAgent.d.ts +70 -0
  12. package/dist/core/BaseAgent.d.ts.map +1 -0
  13. package/dist/core/BaseAgent.js +583 -0
  14. package/dist/core/BaseAgent.js.map +1 -0
  15. package/dist/core/EventEmitter.d.ts +8 -0
  16. package/dist/core/EventEmitter.d.ts.map +1 -0
  17. package/dist/core/EventEmitter.js +32 -0
  18. package/dist/core/EventEmitter.js.map +1 -0
  19. package/dist/core/LocalEmbedder.d.ts +25 -0
  20. package/dist/core/LocalEmbedder.d.ts.map +1 -0
  21. package/dist/core/LocalEmbedder.js +62 -0
  22. package/dist/core/LocalEmbedder.js.map +1 -0
  23. package/dist/core/LongTermMemory.d.ts +30 -0
  24. package/dist/core/LongTermMemory.d.ts.map +1 -0
  25. package/dist/core/LongTermMemory.js +123 -0
  26. package/dist/core/LongTermMemory.js.map +1 -0
  27. package/dist/core/MemoryVectorStore.d.ts +17 -0
  28. package/dist/core/MemoryVectorStore.d.ts.map +1 -0
  29. package/dist/core/MemoryVectorStore.js +44 -0
  30. package/dist/core/MemoryVectorStore.js.map +1 -0
  31. package/dist/core/OpenAIProvider.d.ts +21 -0
  32. package/dist/core/OpenAIProvider.d.ts.map +1 -0
  33. package/dist/core/OpenAIProvider.js +254 -0
  34. package/dist/core/OpenAIProvider.js.map +1 -0
  35. package/dist/core/SimpleMemory.d.ts +12 -0
  36. package/dist/core/SimpleMemory.d.ts.map +1 -0
  37. package/dist/core/SimpleMemory.js +27 -0
  38. package/dist/core/SimpleMemory.js.map +1 -0
  39. package/dist/core/StructuredPlanner.d.ts +13 -0
  40. package/dist/core/StructuredPlanner.d.ts.map +1 -0
  41. package/dist/core/StructuredPlanner.js +156 -0
  42. package/dist/core/StructuredPlanner.js.map +1 -0
  43. package/dist/core/ToolRegistry.d.ts +18 -0
  44. package/dist/core/ToolRegistry.d.ts.map +1 -0
  45. package/dist/core/ToolRegistry.js +74 -0
  46. package/dist/core/ToolRegistry.js.map +1 -0
  47. package/dist/core/index.d.ts +14 -0
  48. package/dist/core/index.d.ts.map +1 -0
  49. package/dist/core/index.js +14 -0
  50. package/dist/core/index.js.map +1 -0
  51. package/dist/core/logging/CompositeLogger.d.ts +8 -0
  52. package/dist/core/logging/CompositeLogger.d.ts.map +1 -0
  53. package/dist/core/logging/CompositeLogger.js +23 -0
  54. package/dist/core/logging/CompositeLogger.js.map +1 -0
  55. package/dist/core/logging/NodeFsLogger.d.ts +12 -0
  56. package/dist/core/logging/NodeFsLogger.d.ts.map +1 -0
  57. package/dist/core/logging/NodeFsLogger.js +46 -0
  58. package/dist/core/logging/NodeFsLogger.js.map +1 -0
  59. package/dist/core/logging/WebIndexedDbLogger.d.ts +15 -0
  60. package/dist/core/logging/WebIndexedDbLogger.d.ts.map +1 -0
  61. package/dist/core/logging/WebIndexedDbLogger.js +65 -0
  62. package/dist/core/logging/WebIndexedDbLogger.js.map +1 -0
  63. package/dist/core/logging/index.d.ts +11 -0
  64. package/dist/core/logging/index.d.ts.map +1 -0
  65. package/dist/core/logging/index.js +33 -0
  66. package/dist/core/logging/index.js.map +1 -0
  67. package/dist/core/persistence/NodeFsSkillStore.d.ts +17 -0
  68. package/dist/core/persistence/NodeFsSkillStore.d.ts.map +1 -0
  69. package/dist/core/persistence/NodeFsSkillStore.js +124 -0
  70. package/dist/core/persistence/NodeFsSkillStore.js.map +1 -0
  71. package/dist/core/persistence/NodeFsVectorStore.d.ts +25 -0
  72. package/dist/core/persistence/NodeFsVectorStore.d.ts.map +1 -0
  73. package/dist/core/persistence/NodeFsVectorStore.js +74 -0
  74. package/dist/core/persistence/NodeFsVectorStore.js.map +1 -0
  75. package/dist/core/persistence/SkillLoader.d.ts +26 -0
  76. package/dist/core/persistence/SkillLoader.d.ts.map +1 -0
  77. package/dist/core/persistence/SkillLoader.js +144 -0
  78. package/dist/core/persistence/SkillLoader.js.map +1 -0
  79. package/dist/core/persistence/WebIndexedDbSkillStore.d.ts +21 -0
  80. package/dist/core/persistence/WebIndexedDbSkillStore.d.ts.map +1 -0
  81. package/dist/core/persistence/WebIndexedDbSkillStore.js +119 -0
  82. package/dist/core/persistence/WebIndexedDbSkillStore.js.map +1 -0
  83. package/dist/core/persistence/WebIndexedDbVectorStore.d.ts +30 -0
  84. package/dist/core/persistence/WebIndexedDbVectorStore.d.ts.map +1 -0
  85. package/dist/core/persistence/WebIndexedDbVectorStore.js +87 -0
  86. package/dist/core/persistence/WebIndexedDbVectorStore.js.map +1 -0
  87. package/dist/core/persistence/index.d.ts +6 -0
  88. package/dist/core/persistence/index.d.ts.map +1 -0
  89. package/dist/core/persistence/index.js +21 -0
  90. package/dist/core/persistence/index.js.map +1 -0
  91. package/dist/index.d.ts +3 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +3 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/interfaces/IAgentEvent.d.ts +27 -0
  96. package/dist/interfaces/IAgentEvent.d.ts.map +1 -0
  97. package/dist/interfaces/IAgentEvent.js +2 -0
  98. package/dist/interfaces/IAgentEvent.js.map +1 -0
  99. package/dist/interfaces/IAgentState.d.ts +22 -0
  100. package/dist/interfaces/IAgentState.d.ts.map +1 -0
  101. package/dist/interfaces/IAgentState.js +2 -0
  102. package/dist/interfaces/IAgentState.js.map +1 -0
  103. package/dist/interfaces/ILLMProvider.d.ts +43 -0
  104. package/dist/interfaces/ILLMProvider.d.ts.map +1 -0
  105. package/dist/interfaces/ILLMProvider.js +2 -0
  106. package/dist/interfaces/ILLMProvider.js.map +1 -0
  107. package/dist/interfaces/ILogger.d.ts +15 -0
  108. package/dist/interfaces/ILogger.d.ts.map +1 -0
  109. package/dist/interfaces/ILogger.js +2 -0
  110. package/dist/interfaces/ILogger.js.map +1 -0
  111. package/dist/interfaces/IMemory.d.ts +32 -0
  112. package/dist/interfaces/IMemory.d.ts.map +1 -0
  113. package/dist/interfaces/IMemory.js +2 -0
  114. package/dist/interfaces/IMemory.js.map +1 -0
  115. package/dist/interfaces/IPlanner.d.ts +20 -0
  116. package/dist/interfaces/IPlanner.d.ts.map +1 -0
  117. package/dist/interfaces/IPlanner.js +2 -0
  118. package/dist/interfaces/IPlanner.js.map +1 -0
  119. package/dist/interfaces/ISkill.d.ts +9 -0
  120. package/dist/interfaces/ISkill.d.ts.map +1 -0
  121. package/dist/interfaces/ISkill.js +2 -0
  122. package/dist/interfaces/ISkill.js.map +1 -0
  123. package/dist/interfaces/ISkillStore.d.ts +53 -0
  124. package/dist/interfaces/ISkillStore.d.ts.map +1 -0
  125. package/dist/interfaces/ISkillStore.js +2 -0
  126. package/dist/interfaces/ISkillStore.js.map +1 -0
  127. package/dist/interfaces/ITool.d.ts +32 -0
  128. package/dist/interfaces/ITool.d.ts.map +1 -0
  129. package/dist/interfaces/ITool.js +2 -0
  130. package/dist/interfaces/ITool.js.map +1 -0
  131. package/dist/interfaces/index.d.ts +10 -0
  132. package/dist/interfaces/index.d.ts.map +1 -0
  133. package/dist/interfaces/index.js +10 -0
  134. package/dist/interfaces/index.js.map +1 -0
  135. package/package.json +47 -0
  136. package/src/core/AgentDashboard.ts +533 -0
  137. package/src/core/AutoVectorStore.ts +60 -0
  138. package/src/core/BaseAgent.ts +676 -0
  139. package/src/core/EventEmitter.ts +35 -0
  140. package/src/core/LocalEmbedder.ts +68 -0
  141. package/src/core/LongTermMemory.ts +146 -0
  142. package/src/core/MemoryVectorStore.ts +54 -0
  143. package/src/core/OpenAIProvider.ts +274 -0
  144. package/src/core/SimpleMemory.ts +31 -0
  145. package/src/core/StructuredPlanner.ts +165 -0
  146. package/src/core/ToolRegistry.ts +89 -0
  147. package/src/core/index.ts +16 -0
  148. package/src/core/logging/CompositeLogger.ts +26 -0
  149. package/src/core/logging/NodeFsLogger.ts +53 -0
  150. package/src/core/logging/WebIndexedDbLogger.ts +76 -0
  151. package/src/core/logging/index.ts +35 -0
  152. package/src/core/persistence/NodeFsSkillStore.ts +138 -0
  153. package/src/core/persistence/NodeFsVectorStore.ts +86 -0
  154. package/src/core/persistence/SkillLoader.ts +153 -0
  155. package/src/core/persistence/WebIndexedDbSkillStore.ts +139 -0
  156. package/src/core/persistence/WebIndexedDbVectorStore.ts +106 -0
  157. package/src/core/persistence/index.ts +22 -0
  158. package/src/index.ts +2 -0
  159. package/src/interfaces/IAgentEvent.ts +46 -0
  160. package/src/interfaces/IAgentState.ts +22 -0
  161. package/src/interfaces/ILLMProvider.ts +47 -0
  162. package/src/interfaces/ILogger.ts +16 -0
  163. package/src/interfaces/IMemory.ts +29 -0
  164. package/src/interfaces/IPlanner.ts +22 -0
  165. package/src/interfaces/ISkill.ts +9 -0
  166. package/src/interfaces/ISkillStore.ts +60 -0
  167. package/src/interfaces/ITool.ts +38 -0
  168. package/src/interfaces/index.ts +10 -0
@@ -0,0 +1,676 @@
1
+ import { IAgentEvent, IEventEmitter, ISkill, ITool, ILLMProvider, LLMMessage, IMemory, LLMResponse, IPlanner, TaskList, IAgentState, ISkillStore, SkillFile, SkillMetadata, ILogger, LogLevel, LogContext } from '../interfaces/index.js';
2
+ import { SimpleEventEmitter } from './EventEmitter.js';
3
+ import { ToolRegistry } from './ToolRegistry.js';
4
+ import { SimpleMemory } from './SimpleMemory.js';
5
+ import { LongTermMemory } from './LongTermMemory.js';
6
+ import { AutoVectorStore } from './AutoVectorStore.js';
7
+ import { LocalEmbedder } from './LocalEmbedder.js';
8
+ import { StructuredPlanner } from './StructuredPlanner.js';
9
+ import { SkillLoader } from './persistence/SkillLoader.js';
10
+ import { LoggerFactory } from './logging/index.js';
11
+ import { zodToJsonSchema } from 'zod-to-json-schema';
12
+ // Removed uuid import to avoid new dependencies
13
+
14
+ export interface AgentOptions {
15
+ provider: ILLMProvider;
16
+ model: string;
17
+ systemPrompt?: string;
18
+ memory?: IMemory;
19
+ planner?: IPlanner;
20
+ maxIterations?: number;
21
+ enableDynamicSkills?: boolean;
22
+ routerModel?: string;
23
+ skillStore?: ISkillStore;
24
+ stream?: boolean;
25
+ logger?: ILogger;
26
+ agentId?: string;
27
+ }
28
+
29
+ export class BaseAgent {
30
+ private eventEmitter: IEventEmitter;
31
+ private toolRegistry: ToolRegistry;
32
+ private provider: ILLMProvider;
33
+ private memory: IMemory;
34
+ private planner: IPlanner;
35
+ private model: string;
36
+ private routerModel?: string;
37
+ private maxIterations: number;
38
+ private enableDynamicSkills: boolean;
39
+ private skillStore?: ISkillStore;
40
+ private stream: boolean;
41
+ private logger?: ILogger;
42
+ private agentId: string;
43
+ private currentTraceId?: string;
44
+ private skills: ISkill[] = [];
45
+ private registeredSkills: ISkill[] = [];
46
+ private activeSkills: Set<string> = new Set();
47
+ private currentTaskList?: TaskList;
48
+
49
+ constructor(options: AgentOptions) {
50
+ this.eventEmitter = new SimpleEventEmitter();
51
+ this.toolRegistry = new ToolRegistry();
52
+ this.provider = options.provider;
53
+ this.model = options.model;
54
+ this.routerModel = options.routerModel;
55
+ this.logger = options.logger;
56
+ this.agentId = options.agentId || `agent_${Date.now()}`;
57
+
58
+ // Default to LongTermMemory with AutoVectorStore and LocalEmbedder
59
+ // This allows for environment-aware persistence and lazy-loading of models
60
+ this.memory = options.memory || new LongTermMemory({
61
+ provider: this.provider,
62
+ vectorStore: new AutoVectorStore(),
63
+ embedder: new LocalEmbedder()
64
+ });
65
+
66
+ this.stream = options.stream ?? true;
67
+ this.planner = options.planner || new StructuredPlanner(this.provider, this.model, this.stream);
68
+ this.maxIterations = options.maxIterations || 10;
69
+ this.enableDynamicSkills = options.enableDynamicSkills || false;
70
+ this.skillStore = options.skillStore;
71
+
72
+ if (options.systemPrompt) {
73
+ this.addToMemory({ role: 'system', content: options.systemPrompt });
74
+ }
75
+
76
+ // 异步初始化默认 Logger
77
+ this.initDefaultLogger();
78
+ }
79
+
80
+ private async initDefaultLogger() {
81
+ if (!this.logger) {
82
+ this.logger = await LoggerFactory.createDefaultLogger() || undefined;
83
+ }
84
+ }
85
+
86
+ registerSkill(skill: ISkill): void {
87
+ if (!this.registeredSkills.some(s => s.name === skill.name)) {
88
+ this.registeredSkills.push(skill);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * 持久化保存一个新技能并自动注册
94
+ */
95
+ async saveSkill(metadata: SkillMetadata, files: SkillFile[]): Promise<void> {
96
+ if (!this.skillStore) {
97
+ throw new Error('Skill store not configured for this agent.');
98
+ }
99
+
100
+ // 1. 保存到存储器
101
+ await this.skillStore.saveSkill(metadata, files);
102
+
103
+ // 2. 加载并注册到内存
104
+ const skill = await SkillLoader.load(metadata, files, (this.skillStore as any).baseDir);
105
+ this.registerSkill(skill);
106
+ }
107
+
108
+ /**
109
+ * 从存储中加载所有已保存的技能
110
+ */
111
+ async loadStoredSkills(): Promise<void> {
112
+ if (!this.skillStore) return;
113
+
114
+ const metadataList = await this.skillStore.listSkills();
115
+ for (const meta of metadataList) {
116
+ const files = await this.skillStore.getSkillFiles(meta.name);
117
+ try {
118
+ const skill = await SkillLoader.load(meta, files, (this.skillStore as any).baseDir);
119
+ this.registerSkill(skill);
120
+ } catch (e) {
121
+ console.error(`Failed to load stored skill ${meta.name}:`, e);
122
+ }
123
+ }
124
+ }
125
+
126
+ async use(skill: ISkill): Promise<void> {
127
+ if (this.activeSkills.has(skill.name)) {
128
+ return;
129
+ }
130
+
131
+ this.skills.push(skill);
132
+ this.activeSkills.add(skill.name);
133
+ this.toolRegistry.registerSkill(skill);
134
+
135
+ if (skill.onInit) {
136
+ await skill.onInit(this);
137
+ }
138
+
139
+ if (skill.systemPrompt) {
140
+ const prompt = await skill.systemPrompt(this);
141
+ await this.addToMemory({ role: 'system', content: prompt });
142
+ }
143
+ }
144
+
145
+ registerTool(tool: ITool): void {
146
+ this.toolRegistry.registerTool(tool);
147
+ }
148
+
149
+ on<T = any>(event: IAgentEvent['type'], listener: (payload: T) => void): void {
150
+ this.eventEmitter.on(event, listener);
151
+ }
152
+
153
+ async getHistory(): Promise<LLMMessage[]> {
154
+ return this.memory.getHistory();
155
+ }
156
+
157
+ async getMemoryUsage(): Promise<{ used: number; total: number }> {
158
+ const history = await this.memory.getHistory();
159
+ const totalChars = history.reduce((acc, m) => acc + (m.content?.length || 0), 0);
160
+ const estimatedTokens = Math.ceil(totalChars / 4);
161
+ return { used: estimatedTokens, total: 128000 };
162
+ }
163
+
164
+ async exportState(): Promise<IAgentState> {
165
+ return {
166
+ history: await this.memory.getHistory(),
167
+ taskList: this.currentTaskList,
168
+ skills: {
169
+ active: Array.from(this.activeSkills),
170
+ registered: this.registeredSkills.map(s => s.name)
171
+ },
172
+ tools: this.toolRegistry.getAllTools().map(t => t.name),
173
+ config: {
174
+ model: this.model,
175
+ routerModel: this.routerModel,
176
+ enableDynamicSkills: this.enableDynamicSkills,
177
+ maxIterations: this.maxIterations
178
+ },
179
+ metadata: {
180
+ version: '1.0.0',
181
+ exportedAt: new Date().toISOString()
182
+ }
183
+ };
184
+ }
185
+
186
+ /**
187
+ * 恢复 Agent 状态 (从快照中恢复)
188
+ * @param state 导出的状态快照
189
+ * @param availableSkills 可用的技能定义列表 (因为函数逻辑无法序列化,需手动提供)
190
+ * @param availableTools 可用的工具定义列表 (可选)
191
+ */
192
+ async restoreState(state: IAgentState, availableSkills: ISkill[] = [], availableTools: ITool[] = []): Promise<void> {
193
+ // 1. 恢复配置 (Config)
194
+ this.model = state.config.model;
195
+ this.routerModel = state.config.routerModel;
196
+ this.enableDynamicSkills = state.config.enableDynamicSkills;
197
+ this.maxIterations = state.config.maxIterations;
198
+ this.currentTaskList = state.taskList;
199
+
200
+ // 2. 恢复记忆 (Memory) - 先清空再添加
201
+ if (this.memory.clear) {
202
+ await this.memory.clear();
203
+ }
204
+ const history = state.history;
205
+ for (const msg of history) {
206
+ await this.addToMemory(msg);
207
+ }
208
+
209
+ // 3. 恢复已注册的技能 (Registered Skills)
210
+ for (const skillName of state.skills.registered) {
211
+ const skill = availableSkills.find(s => s.name === skillName);
212
+ if (skill) {
213
+ this.registerSkill(skill);
214
+ }
215
+ }
216
+
217
+ // 4. 恢复已激活的技能 (Active Skills)
218
+ for (const skillName of state.skills.active) {
219
+ const skill = availableSkills.find(s => s.name === skillName);
220
+ if (skill) {
221
+ // 使用 use 会注入提示词和工具,由于 memory 已经恢复,这里需要确保幂等
222
+ await this.use(skill);
223
+ }
224
+ }
225
+
226
+ // 5. 恢复手动注册的工具 (Tools)
227
+ if (state.tools && state.tools.length > 0) {
228
+ for (const toolName of state.tools) {
229
+ // 如果工具不在已激活技能中,则尝试手动注册
230
+ const tool = availableTools.find(t => t.name === toolName);
231
+ if (tool) {
232
+ this.registerTool(tool);
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ async run(input: string, options?: { traceId?: string }): Promise<string> {
239
+ this.currentTraceId = options?.traceId || `tr-${Math.random().toString(36).substring(2, 11)}`;
240
+ const traceId = this.currentTraceId;
241
+ this.emit('start', input);
242
+ this.log('info', 'Agent run started', { input });
243
+
244
+ // Step 0: Dynamic Skill Selection (Optional)
245
+ if (this.enableDynamicSkills && this.registeredSkills.length > 0) {
246
+ this.emit('think', 'Selecting relevant skills...');
247
+ this.log('debug', 'Selecting dynamic skills');
248
+ await this.selectAndActivateSkills(input);
249
+ }
250
+
251
+ // Get context from memory if available
252
+ let context = '';
253
+ if (this.memory.getContext) {
254
+ context = await this.memory.getContext(input);
255
+ this.log('debug', 'Context retrieved from memory', { contextLength: context.length });
256
+ }
257
+
258
+ const tools = this.toolRegistry.getAllTools();
259
+ const toolsJson = tools.map(tool => ({
260
+ type: 'function',
261
+ function: {
262
+ name: tool.name,
263
+ description: tool.description,
264
+ parameters: zodToJsonSchema(tool.parameters),
265
+ },
266
+ }));
267
+
268
+ // Add mark_task_status system tool dynamically
269
+ toolsJson.push({
270
+ type: 'function',
271
+ function: {
272
+ name: 'mark_task_status',
273
+ description: 'Mark a task as completed or failed based on its criteria. MUST be called when a task criteria is met or determined impossible.',
274
+ parameters: {
275
+ type: 'object',
276
+ properties: {
277
+ task_id: { type: 'string', description: 'The ID of the task to mark' },
278
+ status: { type: 'string', enum: ['completed', 'failed'] },
279
+ result_or_reason: { type: 'string', description: 'Final result summary or failure reason' }
280
+ },
281
+ required: ['task_id', 'status', 'result_or_reason']
282
+ }
283
+ }
284
+ } as any);
285
+
286
+ // Step 1: Planning
287
+ this.emit('think', 'Planning tasks...');
288
+ this.log('info', 'Starting task planning', { toolsCount: tools.length });
289
+ let currentPlanningContent = '';
290
+ this.currentTaskList = await this.planner.plan(input, tools, context, (chunk) => {
291
+ currentPlanningContent += chunk;
292
+
293
+ // Optimization: Only emit content inside <think> tags during planning
294
+ // to avoid showing raw JSON to the user
295
+ const thinkMatch = currentPlanningContent.match(/<think>([\s\S]*?)$/);
296
+ if (thinkMatch) {
297
+ let thought = thinkMatch[1];
298
+ // If there's a closing tag, only show content before it
299
+ if (thought.includes('</think>')) {
300
+ thought = thought.split('</think>')[0];
301
+ }
302
+ this.emit('think', thought);
303
+ }
304
+ });
305
+ this.log('debug', 'Planning completed', { rawResponse: currentPlanningContent });
306
+ this.emit('task.plan', this.currentTaskList);
307
+
308
+ // Optimization: If planner provides a direct response, return it immediately
309
+ if (this.currentTaskList.isCompleted && this.currentTaskList.response) {
310
+ this.log('info', 'Planner provided direct response', { response: this.currentTaskList.response });
311
+ this.emit('think', this.currentTaskList.response);
312
+ await this.addToMemory({ role: 'user', content: input });
313
+ await this.addToMemory({ role: 'assistant', content: this.currentTaskList.response });
314
+ this.emit('agent.done', this.currentTaskList.response);
315
+ return this.currentTaskList.response;
316
+ }
317
+
318
+ this.emit('think', `Plan generated with ${this.currentTaskList.tasks.length} tasks.`);
319
+ this.log('info', 'Tasks to execute', { tasks: this.currentTaskList.tasks });
320
+
321
+ await this.addToMemory({ role: 'user', content: input });
322
+
323
+ let iterations = 0;
324
+ while (iterations < this.maxIterations) {
325
+ iterations++;
326
+ this.log('info', `Starting iteration ${iterations}`);
327
+
328
+ const messages = await this.memory.getHistory();
329
+
330
+ // Inject context from LongTermMemory as a system message if it exists
331
+ if (context) {
332
+ messages.push({
333
+ role: 'system',
334
+ content: context
335
+ });
336
+ }
337
+
338
+ // 动态注入当前任务列表,不持久化到 memory history
339
+ if (this.currentTaskList) {
340
+ // Find ready tasks
341
+ const readyTasks = this.currentTaskList.tasks.filter(t =>
342
+ (t.status === 'pending' || t.status === 'in_progress') &&
343
+ t.dependsOn.every(depId => this.currentTaskList!.tasks.find(dep => dep.id === depId)?.status === 'completed')
344
+ );
345
+
346
+ for (const t of readyTasks) {
347
+ if (t.status === 'pending') {
348
+ t.status = 'in_progress';
349
+ this.emit('task.update', t);
350
+ }
351
+ }
352
+
353
+ const taskContext = readyTasks.map(t => `- Task ID: ${t.id}\n Goal: ${t.goal}\n Criteria (Definition of Done): ${t.criteria}`).join('\n\n');
354
+
355
+ messages.push({
356
+ role: 'system',
357
+ content: `Current Task List:\n${JSON.stringify(this.currentTaskList, null, 2)}\n\nYou are currently executing the following active task(s):\n${taskContext}\n\nYou MUST use the 'mark_task_status' tool to explicitly mark a task as 'completed' or 'failed' once its criteria is met or it is determined impossible. Do not guess the status, explicitly call the tool.`
358
+ });
359
+ }
360
+
361
+ // Attempt generation with streaming support
362
+ let response: LLMResponse;
363
+ this.log('debug', 'Calling LLM provider', { traceId, iterations, messagesCount: messages.length });
364
+
365
+ if (this.provider.stream && this.stream) {
366
+ let fullContent = '';
367
+
368
+ response = await this.provider.stream(messages, {
369
+ model: this.model,
370
+ tools: toolsJson.length > 0 ? toolsJson : undefined,
371
+ stream: true,
372
+ intent: 'execute',
373
+ }, (chunk) => {
374
+ if (chunk.content) {
375
+ fullContent += chunk.content;
376
+ this.emit('think', fullContent); // Emit partial content
377
+ }
378
+ if (chunk.toolCalls) {
379
+ this.emit('tool.call.delta', chunk.toolCalls);
380
+ }
381
+ });
382
+ this.log('debug', 'LLM stream finished', { fullContent, toolCallsCount: response.toolCalls?.length });
383
+ } else {
384
+ response = await this.provider.generate(messages, {
385
+ model: this.model,
386
+ tools: toolsJson.length > 0 ? toolsJson : undefined,
387
+ stream: false,
388
+ intent: 'execute',
389
+ });
390
+ this.log('debug', 'LLM generation finished', { response });
391
+ }
392
+
393
+ if (response.content) {
394
+ this.emit('think', response.content);
395
+ await this.addToMemory({
396
+ role: 'assistant',
397
+ content: response.content,
398
+ toolCalls: response.toolCalls
399
+ });
400
+ }
401
+
402
+ // Check if goal reached or need more planning
403
+ if (!response.toolCalls || response.toolCalls.length === 0) {
404
+ this.log('info', 'Agent execution completed successfully', { finalResponse: response.content });
405
+ this.emit('agent.done', response.content || '');
406
+ return response.content || '';
407
+ }
408
+
409
+ if (response.toolCalls && response.toolCalls.length > 0) {
410
+ this.log('info', `Executing ${response.toolCalls.length} tools in parallel`, { toolCalls: response.toolCalls });
411
+ // Collect results and errors for all calls in parallel
412
+ const toolExecutionPromises = response.toolCalls.map(async (toolCall) => {
413
+ if (toolCall.function.name === 'mark_task_status') {
414
+ try {
415
+ const args = JSON.parse(toolCall.function.arguments);
416
+ const task = this.currentTaskList!.tasks.find(t => t.id === args.task_id);
417
+ if (task) {
418
+ task.status = args.status;
419
+ if (args.status === 'completed') {
420
+ task.result = args.result_or_reason;
421
+ } else {
422
+ task.error = args.result_or_reason;
423
+ }
424
+ this.emit('task.update', task);
425
+
426
+ const resultMsg = `Task ${args.task_id} successfully marked as ${args.status}.`;
427
+ this.log('info', resultMsg, { task_id: args.task_id, status: args.status });
428
+ await this.addToMemory({
429
+ role: 'tool',
430
+ tool_call_id: toolCall.id,
431
+ name: toolCall.function.name,
432
+ content: resultMsg,
433
+ });
434
+ return { toolName: toolCall.function.name, success: true, data: resultMsg };
435
+ } else {
436
+ const errorMsg = `Task with ID ${args.task_id} not found.`;
437
+ this.log('warn', errorMsg, { task_id: args.task_id });
438
+ await this.addToMemory({
439
+ role: 'tool',
440
+ tool_call_id: toolCall.id,
441
+ name: toolCall.function.name,
442
+ content: errorMsg,
443
+ });
444
+ return { toolName: toolCall.function.name, success: false, error: errorMsg };
445
+ }
446
+ } catch (err: any) {
447
+ const errorMsg = `Failed to parse mark_task_status arguments: ${err.message}`;
448
+ await this.addToMemory({
449
+ role: 'tool',
450
+ tool_call_id: toolCall.id,
451
+ name: toolCall.function.name,
452
+ content: errorMsg,
453
+ });
454
+ return { toolName: toolCall.function.name, success: false, error: errorMsg };
455
+ }
456
+ }
457
+
458
+ const tool = this.toolRegistry.getTool(toolCall.function.name);
459
+ if (!tool) {
460
+ const error = `Tool '${toolCall.function.name}' not found.`;
461
+ this.log('error', error, { toolName: toolCall.function.name });
462
+ await this.addToMemory({
463
+ role: 'tool',
464
+ tool_call_id: toolCall.id,
465
+ name: toolCall.function.name,
466
+ content: error,
467
+ });
468
+ return { toolName: toolCall.function.name, error };
469
+ }
470
+
471
+ // 人机协作审批机制 (Human-in-the-loop)
472
+ if (tool.requiresApproval) {
473
+ this.emit('think', `Waiting for user approval to execute tool: ${tool.name}`);
474
+ this.log('info', 'Waiting for tool approval', { toolName: tool.name, arguments: toolCall.function.arguments });
475
+
476
+ try {
477
+ const approvalResult = await new Promise<any>((resolve, reject) => {
478
+ // 设置超时机制 (例如 5 分钟),防止 Agent 永久挂起
479
+ const timeoutId = setTimeout(() => {
480
+ reject(new Error(`Approval timeout for tool ${tool.name}`));
481
+ }, 5 * 60 * 1000);
482
+
483
+ this.emit('tool.require_approval', {
484
+ toolCallId: toolCall.id,
485
+ toolName: tool.name,
486
+ arguments: toolCall.function.arguments,
487
+ resolve: (result: any) => {
488
+ clearTimeout(timeoutId);
489
+ resolve(result);
490
+ },
491
+ reject: (error: Error) => {
492
+ clearTimeout(timeoutId);
493
+ reject(error);
494
+ }
495
+ });
496
+ });
497
+
498
+ if (!approvalResult.approved) {
499
+ const errorMsg = `User rejected execution. Reason: ${approvalResult.feedback || 'No reason provided'}`;
500
+ this.log('warn', 'Tool execution rejected by user', { toolName: tool.name, feedback: approvalResult.feedback });
501
+
502
+ await this.addToMemory({
503
+ role: 'tool',
504
+ tool_call_id: toolCall.id,
505
+ name: tool.name,
506
+ content: errorMsg,
507
+ });
508
+
509
+ return { toolName: tool.name, error: errorMsg };
510
+ }
511
+
512
+ // 进阶:如果用户修改了参数,则覆盖原参数
513
+ if (approvalResult.modifiedArgs) {
514
+ this.log('info', 'Tool arguments modified by user', { toolName: tool.name, oldArgs: toolCall.function.arguments, newArgs: approvalResult.modifiedArgs });
515
+ toolCall.function.arguments = typeof approvalResult.modifiedArgs === 'string'
516
+ ? approvalResult.modifiedArgs
517
+ : JSON.stringify(approvalResult.modifiedArgs);
518
+ }
519
+
520
+ } catch (approvalError: any) {
521
+ const errorMsg = `Approval process failed: ${approvalError.message}`;
522
+ this.log('error', errorMsg, { toolName: tool.name, stack: approvalError.stack });
523
+ await this.addToMemory({
524
+ role: 'tool',
525
+ tool_call_id: toolCall.id,
526
+ name: tool.name,
527
+ content: errorMsg,
528
+ });
529
+ return { toolName: tool.name, error: errorMsg };
530
+ }
531
+ }
532
+
533
+ this.log('info', `Calling tool: ${tool.name}`, { arguments: toolCall.function.arguments });
534
+ this.emit('tool.call', {
535
+ name: tool.name,
536
+ arguments: toolCall.function.arguments,
537
+ });
538
+
539
+ try {
540
+ const startTime = Date.now();
541
+ // 使用 ToolRegistry 的统一校验与执行逻辑
542
+ const result = await this.toolRegistry.validateAndExecute(
543
+ toolCall.function.name,
544
+ toolCall.function.arguments,
545
+ undefined,
546
+ (percent, message) => {
547
+ this.emit('tool.progress', { name: tool.name, percent, message });
548
+ }
549
+ );
550
+ const latency = Date.now() - startTime;
551
+
552
+ this.log('info', `Tool ${tool.name} executed`, { latency, result });
553
+ this.emit('tool.result', result);
554
+
555
+ await this.addToMemory({
556
+ role: 'tool',
557
+ tool_call_id: toolCall.id,
558
+ name: tool.name,
559
+ content: result.error ? result.error : JSON.stringify(result.data),
560
+ });
561
+ return { toolName: tool.name, ...result };
562
+ } catch (error: any) {
563
+ const errorMsg = `Error executing tool ${tool.name}: ${error.message}`;
564
+ this.log('error', errorMsg, { toolName: tool.name, stack: error.stack });
565
+ this.emit('agent.error', errorMsg);
566
+
567
+ await this.addToMemory({
568
+ role: 'tool',
569
+ tool_call_id: toolCall.id,
570
+ name: tool.name,
571
+ content: errorMsg,
572
+ });
573
+ return { toolName: tool.name, error: error.message };
574
+ }
575
+ });
576
+
577
+ const executionResults = await Promise.all(toolExecutionPromises);
578
+
579
+ // Check if goal reached or need more planning if ANY tool failed
580
+ const failedResults = executionResults.filter(r => r && r.error);
581
+ if (failedResults.length > 0) {
582
+ const errorContext = failedResults
583
+ .map(r => `Tool ${r!.toolName} failed: ${r!.error}`)
584
+ .join('\n');
585
+
586
+ this.log('warn', 'Some tools failed, triggering replan', { failedTools: failedResults.map(r => r!.toolName) });
587
+ this.emit('think', `Re-planning due to errors: ${failedResults.map(r => r!.toolName).join(', ')}...`);
588
+ let currentReplanContent = '';
589
+ this.currentTaskList = await this.planner.replan(input, tools, this.currentTaskList!, errorContext, (chunk) => {
590
+ currentReplanContent += chunk;
591
+ this.emit('think', currentReplanContent);
592
+ });
593
+ this.log('debug', 'Re-planning completed', { rawResponse: currentReplanContent });
594
+ this.emit('task.plan', this.currentTaskList);
595
+ }
596
+ }
597
+ }
598
+
599
+
600
+ const errorMsg = `Agent exceeded max iterations (${this.maxIterations})`;
601
+ this.log('error', errorMsg, { traceId, iterations });
602
+ this.emit('agent.error', errorMsg);
603
+ throw new Error(errorMsg);
604
+ }
605
+
606
+ private emit(type: IAgentEvent['type'], payload: any) {
607
+ const event: IAgentEvent = {
608
+ id: `evt-${Math.random().toString(36).substring(2, 11)}`,
609
+ type,
610
+ payload,
611
+ timestamp: Date.now(),
612
+ agentId: this.agentId,
613
+ traceId: this.currentTraceId,
614
+ };
615
+ this.eventEmitter.emit(type, event);
616
+ }
617
+
618
+ private log(level: LogLevel, message: string, context?: LogContext) {
619
+ if (this.logger) {
620
+ this.logger.log(level, message, {
621
+ agentId: this.agentId,
622
+ traceId: this.currentTraceId,
623
+ ...context
624
+ });
625
+ }
626
+ }
627
+
628
+ private async addToMemory(message: LLMMessage): Promise<void> {
629
+ await this.memory.add(message);
630
+ this.emit('memory.add', message);
631
+ this.log('debug', 'Message added to memory', {
632
+ role: message.role,
633
+ contentPreview: message.content?.substring(0, 100),
634
+ toolCalls: message.toolCalls?.length
635
+ });
636
+
637
+ // Estimate memory usage (simplified for demo)
638
+ const history = await this.memory.getHistory();
639
+ const totalChars = history.reduce((acc, m) => acc + (m.content?.length || 0), 0);
640
+ const estimatedTokens = Math.ceil(totalChars / 4);
641
+ this.emit('memory.usage', { used: estimatedTokens, total: 128000 });
642
+ }
643
+
644
+ private async selectAndActivateSkills(input: string): Promise<void> {
645
+ const skillList = this.registeredSkills
646
+ .map(s => `- ${s.name}: ${s.description || 'No description'}`)
647
+ .join('\n');
648
+
649
+ const prompt = `Based on the user's request: "${input}", select the most relevant skills from the following list to help solve the task.
650
+ Return only the names of the selected skills as a JSON array of strings. If no skills are needed, return an empty array [].
651
+
652
+ Available Skills:
653
+ ${skillList}`;
654
+
655
+ const response = await this.provider.generate([
656
+ { role: 'system', content: 'You are a skill router. Your goal is to select necessary skills for a given task.' },
657
+ { role: 'user', content: prompt }
658
+ ], { model: this.routerModel || this.model });
659
+
660
+ try {
661
+ // Simple extraction of JSON array
662
+ const match = response.content?.match(/\[.*\]/s);
663
+ if (match) {
664
+ const selectedNames: string[] = JSON.parse(match[0]);
665
+ for (const name of selectedNames) {
666
+ const skill = this.registeredSkills.find(s => s.name === name);
667
+ if (skill) {
668
+ await this.use(skill);
669
+ }
670
+ }
671
+ }
672
+ } catch (e: any) {
673
+ this.emit('agent.error', `Failed to parse skill routing response: ${e.message}`);
674
+ }
675
+ }
676
+ }