@chen-rmag/ai-runner 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.
Files changed (102) hide show
  1. package/README.md +263 -0
  2. package/SUMMARY_USAGE.md +359 -0
  3. package/TOOLS_INTEGRATION_SUMMARY.md +206 -0
  4. package/dist/agents/error-analyzer.d.ts +62 -0
  5. package/dist/agents/error-analyzer.d.ts.map +1 -0
  6. package/dist/agents/error-analyzer.js +168 -0
  7. package/dist/agents/error-analyzer.js.map +1 -0
  8. package/dist/agents/heal-agent.d.ts +30 -0
  9. package/dist/agents/heal-agent.d.ts.map +1 -0
  10. package/dist/agents/heal-agent.js +76 -0
  11. package/dist/agents/heal-agent.js.map +1 -0
  12. package/dist/agents/healer.d.ts +73 -0
  13. package/dist/agents/healer.d.ts.map +1 -0
  14. package/dist/agents/healer.js +538 -0
  15. package/dist/agents/healer.js.map +1 -0
  16. package/dist/agents/langgraph-agent.d.ts +44 -0
  17. package/dist/agents/langgraph-agent.d.ts.map +1 -0
  18. package/dist/agents/langgraph-agent.js +328 -0
  19. package/dist/agents/langgraph-agent.js.map +1 -0
  20. package/dist/agents/react-agent.d.ts +52 -0
  21. package/dist/agents/react-agent.d.ts.map +1 -0
  22. package/dist/agents/react-agent.js +262 -0
  23. package/dist/agents/react-agent.js.map +1 -0
  24. package/dist/agents/tools/form.d.ts +22 -0
  25. package/dist/agents/tools/form.d.ts.map +1 -0
  26. package/dist/agents/tools/form.js +134 -0
  27. package/dist/agents/tools/form.js.map +1 -0
  28. package/dist/agents/tools/index.d.ts +13 -0
  29. package/dist/agents/tools/index.d.ts.map +1 -0
  30. package/dist/agents/tools/index.js +33 -0
  31. package/dist/agents/tools/index.js.map +1 -0
  32. package/dist/agents/tools/navigate.d.ts +22 -0
  33. package/dist/agents/tools/navigate.d.ts.map +1 -0
  34. package/dist/agents/tools/navigate.js +74 -0
  35. package/dist/agents/tools/navigate.js.map +1 -0
  36. package/dist/agents/tools/snapshot.d.ts +22 -0
  37. package/dist/agents/tools/snapshot.d.ts.map +1 -0
  38. package/dist/agents/tools/snapshot.js +110 -0
  39. package/dist/agents/tools/snapshot.js.map +1 -0
  40. package/dist/agents/tools/verify.d.ts +34 -0
  41. package/dist/agents/tools/verify.d.ts.map +1 -0
  42. package/dist/agents/tools/verify.js +169 -0
  43. package/dist/agents/tools/verify.js.map +1 -0
  44. package/dist/agents/tools/wait.d.ts +22 -0
  45. package/dist/agents/tools/wait.d.ts.map +1 -0
  46. package/dist/agents/tools/wait.js +104 -0
  47. package/dist/agents/tools/wait.js.map +1 -0
  48. package/dist/agents/types.d.ts +51 -0
  49. package/dist/agents/types.d.ts.map +1 -0
  50. package/dist/agents/types.js +6 -0
  51. package/dist/agents/types.js.map +1 -0
  52. package/dist/core/ai-heal.d.ts +89 -0
  53. package/dist/core/ai-heal.d.ts.map +1 -0
  54. package/dist/core/ai-heal.js +468 -0
  55. package/dist/core/ai-heal.js.map +1 -0
  56. package/dist/core/execution-engine.d.ts +16 -0
  57. package/dist/core/execution-engine.d.ts.map +1 -0
  58. package/dist/core/execution-engine.js +44 -0
  59. package/dist/core/execution-engine.js.map +1 -0
  60. package/dist/core/runner.d.ts +195 -0
  61. package/dist/core/runner.d.ts.map +1 -0
  62. package/dist/core/runner.js +658 -0
  63. package/dist/core/runner.js.map +1 -0
  64. package/dist/index.d.ts +8 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +11 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/types/external.d.ts +6 -0
  69. package/dist/types/external.d.ts.map +1 -0
  70. package/dist/types/external.js +7 -0
  71. package/dist/types/external.js.map +1 -0
  72. package/dist/types/index.d.ts +153 -0
  73. package/dist/types/index.d.ts.map +1 -0
  74. package/dist/types/index.js +26 -0
  75. package/dist/types/index.js.map +1 -0
  76. package/dist/utils/object-registry.d.ts +48 -0
  77. package/dist/utils/object-registry.d.ts.map +1 -0
  78. package/dist/utils/object-registry.js +133 -0
  79. package/dist/utils/object-registry.js.map +1 -0
  80. package/package.json +37 -0
  81. package/playwright.config.ts +38 -0
  82. package/src/agents/heal-agent.ts +85 -0
  83. package/src/agents/healer.ts +619 -0
  84. package/src/agents/tools/EXAMPLES.md +347 -0
  85. package/src/agents/tools/README.md +207 -0
  86. package/src/agents/tools/form.ts +138 -0
  87. package/src/agents/tools/index.ts +29 -0
  88. package/src/agents/tools/navigate.ts +69 -0
  89. package/src/agents/tools/snapshot.ts +109 -0
  90. package/src/agents/tools/verify.ts +168 -0
  91. package/src/agents/tools/wait.ts +103 -0
  92. package/src/agents/types.ts +79 -0
  93. package/src/core/runner.ts +756 -0
  94. package/src/index.ts +29 -0
  95. package/src/types/external.ts +7 -0
  96. package/src/types/index.ts +200 -0
  97. package/tests/agent/test-heal-agent.spec.ts +81 -0
  98. package/tests/tools/README.md +227 -0
  99. package/tests/tools/TEST_SUMMARY.md +214 -0
  100. package/tests/tools/quick-test.ts +88 -0
  101. package/tests/tools/tools.test.ts +491 -0
  102. package/tsconfig.json +22 -0
@@ -0,0 +1,619 @@
1
+ /**
2
+ * Healer - 测试自愈代理
3
+ * 使用 LangChain 的 createAgent API 实现 ReAct 循环
4
+ *
5
+ * 参考:https://www.npmjs.com/package/langchain
6
+ */
7
+
8
+ import { createAgent } from 'langchain';
9
+ import { ChatAnthropic } from '@langchain/anthropic';
10
+ import { DynamicTool } from '@langchain/core/tools';
11
+ import type { HealAgentState, HealAgentResult } from './types';
12
+ import type { ModelConfig } from '../types/external';
13
+ import type { ReActStep } from '../types';
14
+ import { getAllTools } from './tools';
15
+
16
+ // ============================================================================
17
+ // Playwright 工具集(使用标准化工具 + 自定义工具)
18
+ // ============================================================================
19
+
20
+ /**
21
+ * 创建 Playwright 工具集
22
+ * 使用标准化的 Playwright MCP 转换工具,并添加 Healer 特有的变量管理工具
23
+ */
24
+ function createPlaywrightTools(page: any, setVariable: (name: string, value: any) => void, getAllVariables?: () => Record<string, any>): DynamicTool[] {
25
+ // 获取所有标准化工具
26
+ const standardTools = getAllTools(page);
27
+
28
+ // 添加 set_variable 工具
29
+ const setVariableTool = new DynamicTool({
30
+ name: 'set_variable',
31
+ description: 'Set the value of a variable. Input format: variableName,value (comma separated). For example: username,john or url,https://example.com. This tool is used to save data retrieved from the page to variables for later use.',
32
+ func: async (input: string) => {
33
+ const parts = input.split(',');
34
+ const varName = parts[0].trim();
35
+ const varValue = parts.slice(1).join(',').trim();
36
+
37
+ setVariable(varName, varValue);
38
+ return `Variable set: ${varName} = ${varValue}`;
39
+ }
40
+ });
41
+
42
+ // 添加 get_variable 工具 - 查看所有变量或获取特定变量
43
+ const getVariableTool = new DynamicTool({
44
+ name: 'get_variable',
45
+ description: 'Get all variables or get a specific variable value. To get all variables, use: get_variable or get_variable/all. To get a specific variable, use: get_variable,variableName. This helps you understand what data has been stored.',
46
+ func: async (input: string) => {
47
+ if (!input || input.trim() === '' || input.trim() === 'all') {
48
+ // 获取所有变量
49
+ const allVars = getAllVariables ? getAllVariables() : {};
50
+ const varList = Object.keys(allVars).map(key => ` - ${key}: ${JSON.stringify(allVars[key])}`).join('\n');
51
+ return `Current variables:\n${varList || ' (no variables set)'}`;
52
+ } else {
53
+ // 获取特定变量
54
+ const varName = input.trim();
55
+ const allVars = getAllVariables ? getAllVariables() : {};
56
+ const value = allVars[varName];
57
+ return `Variable ${varName}: ${JSON.stringify(value)}`;
58
+ }
59
+ }
60
+ });
61
+
62
+ return [...standardTools, setVariableTool, getVariableTool];
63
+ }
64
+
65
+ // ============================================================================
66
+ // Healer 类
67
+ // ============================================================================
68
+
69
+ export class Healer {
70
+ private modelConfig: ModelConfig;
71
+ private maxSteps: number;
72
+ private debugMode: boolean;
73
+ private reactSteps: ReActStep[] = []; // 记录 ReAct 循环的详细步骤
74
+ private setVariableCallback?: (name: string, value: any) => void; // 外部传入的 setVariable 回调
75
+ private getAllVariablesCallback?: () => Record<string, any>; // 外部传入的 getAllVariables 回调
76
+
77
+ constructor(modelConfig: ModelConfig, config: { maxSteps?: number; debugMode?: boolean; setVariable?: (name: string, value: any) => void; getAllVariables?: () => Record<string, any> } = {}) {
78
+ this.modelConfig = modelConfig;
79
+ this.maxSteps = config.maxSteps ?? 20;
80
+ this.debugMode = config.debugMode ?? false;
81
+ this.setVariableCallback = config.setVariable;
82
+ this.getAllVariablesCallback = config.getAllVariables;
83
+ }
84
+
85
+ /**
86
+ * 默认的 setVariable 方法(当没有提供回调时使用)
87
+ */
88
+ private defaultSetVariable(name: string, value: any): void {
89
+ if (this.debugMode) {
90
+ console.log(`[Healer] ⚠️ 没有设置 setVariable 回调,变量 ${name} = ${value} 不会被保存`);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * 默认的 getAllVariables 方法(当没有提供回调时使用)
96
+ */
97
+ private defaultGetAllVariables(): Record<string, any> {
98
+ if (this.debugMode) {
99
+ console.log(`[Healer] ⚠️ 没有设置 getAllVariables 回调,返回空对象`);
100
+ }
101
+ return {};
102
+ }
103
+
104
+ /**
105
+ * 执行自愈(使用 LangChain createAgent API)
106
+ */
107
+ async execute(state: HealAgentState): Promise<HealAgentResult> {
108
+ console.log('\n========================================');
109
+ console.log('[Healer] 开始执行(使用 langchain createAgent)');
110
+ console.log('========================================');
111
+ console.log(`[Healer] 模型: ${this.modelConfig.provider}/${this.modelConfig.modelName}`);
112
+ console.log(`[Healer] 目标: ${state.objective}`);
113
+ console.log(`[Healer] 错误: ${state.error}`);
114
+
115
+ const page = state.context?.page;
116
+
117
+ if (!page) {
118
+ throw new Error('缺少 page 对象');
119
+ }
120
+
121
+ try {
122
+ // 重置步骤记录
123
+ this.reactSteps = [];
124
+
125
+ // 创建 LLM 实例
126
+ const llm = this.createLLM();
127
+
128
+ // 步数计数器(用于在工具返回值中提醒)
129
+ let stepCount = 0;
130
+ const stepCounter = { count: 0 };
131
+
132
+ // 创建 Playwright 工具(包装日志输出和步数提醒)
133
+ const tools = this.createPlaywrightToolsWithLogging(
134
+ page,
135
+ this.setVariableCallback || this.defaultSetVariable.bind(this),
136
+ this.getAllVariablesCallback || this.defaultGetAllVariables.bind(this),
137
+ stepCounter,
138
+ this.maxSteps
139
+ );
140
+
141
+ // 使用 createAgent API
142
+ const agent = createAgent({
143
+ model: llm,
144
+ tools: tools as any, // Cast to any to bypass duplicate @langchain/core type incompatibility
145
+ systemPrompt: this.buildSystemPrompt(state)
146
+
147
+ } as any); // Cast entire config to bypass type instantiation issues
148
+
149
+ console.log(`[Healer] 🚀 开始 ReAct 循环...`);
150
+
151
+ // 使用 stream 以便实时输出每一步
152
+ const inputStream = {
153
+ messages: [{
154
+ role: 'user' as const,
155
+ content: this.buildUserMessage(state)
156
+ }]
157
+ };
158
+
159
+ let finalResult: any = null;
160
+ let lastEvent: any = null;
161
+
162
+ // 流式执行并实时输出
163
+ const stream = await agent.stream(inputStream, {
164
+ recursionLimit: 25,
165
+ streamMode: 'updates',
166
+ });
167
+
168
+ for await (const event of stream) {
169
+ stepCount++;
170
+ stepCounter.count = stepCount;
171
+ lastEvent = event; // 保存最后一个事件
172
+
173
+ if (this.debugMode) {
174
+ console.log(`\n[Healer] 📊 步骤 ${stepCount}:`);
175
+ console.log(`[Healer] 🔍 事件类型:`, Object.keys(event));
176
+ console.log(`[Healer] 🔍 事件结构:`, JSON.stringify(event, null, 2).substring(0, 500));
177
+ }
178
+
179
+ // 在 updates 模式下,messages 在不同位置
180
+ // 1. AI 思考和工具调用:event.model_request.messages
181
+ // 2. 工具执行结果:event.tools.messages
182
+ const messages = event.model_request?.messages || event.messages;
183
+ const toolMessages = event.tools?.messages;
184
+
185
+ if (this.debugMode) {
186
+ console.log(`[Healer] 🔍 检查 messages:`, messages ? `找到 ${messages.length} 条消息` : '未找到');
187
+ console.log(`[Healer] 🔍 检查 tools.messages:`, toolMessages ? `找到 ${toolMessages.length} 条工具消息` : '未找到');
188
+ }
189
+
190
+ // 处理 AI 思考和工具调用
191
+ if (messages && Array.isArray(messages)) {
192
+ for (const msg of messages) {
193
+ if (msg.type === 'ai') {
194
+ // AI 消息:content 是 string(思考过程),tool_calls 是数组(工具调用)
195
+ if (msg.content && typeof msg.content === 'string') {
196
+ // Thought 步骤
197
+ console.log(`[Healer] 💭 AI思考: ${msg.content}`);
198
+ this.recordThoughtStep(msg.content, stepCount);
199
+ }
200
+
201
+ // 处理工具调用
202
+ const aiMsg = msg as any;
203
+ if (aiMsg.tool_calls && Array.isArray(aiMsg.tool_calls)) {
204
+ for (const toolCall of aiMsg.tool_calls) {
205
+ // Action 步骤
206
+ const name = toolCall.name || toolCall.id;
207
+ const args = toolCall.args || {};
208
+ console.log(`[Healer] 🔧 工具调用: ${name}`);
209
+ console.log(`[Healer] 📝 参数:`, JSON.stringify(args, null, 2));
210
+ this.recordActionStep(name, args, stepCount);
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ // 处理工具执行结果
218
+ if (toolMessages && Array.isArray(toolMessages)) {
219
+ for (const msg of toolMessages) {
220
+ if (msg.type === 'tool') {
221
+ // Observation 步骤
222
+ const output = msg.content?.toString()?.substring(0, 200) || '(空)';
223
+ const success = true; // 如果能收到消息说明执行成功了
224
+ console.log(`[Healer] ✅ 工具结果 [${msg.name}]:`, output);
225
+ this.recordObservationStep(output, success, stepCount);
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ // 使用最后一个事件作为最终结果(不要重新调用 invoke)
232
+ finalResult = lastEvent || { messages: [] };
233
+
234
+ // 调试:输出 finalResult 结构
235
+ if (this.debugMode) {
236
+ console.log('\n[Healer] 🔍 finalResult 结构:', JSON.stringify(finalResult, null, 2).substring(0, 1000));
237
+ }
238
+
239
+ // 提取结果
240
+ const success = this.isSuccess(finalResult);
241
+ const reasoning = this.extractReasoning(finalResult);
242
+ const steps = stepCount; // 直接使用记录的步数
243
+
244
+ console.log(`\n[Healer] 执行${success ? '成功 ✅' : '失败 ❌'}`);
245
+ console.log(`[Healer] 总步数: ${steps || stepCount}`);
246
+ console.log(`[Healer] 📝 记录了 ${this.reactSteps.length} 个 ReAct 步骤`);
247
+ console.log('========================================\n');
248
+
249
+ return {
250
+ success,
251
+ reasoning,
252
+ steps: steps || stepCount,
253
+ updatedVariables: {}, // 变量已通过回调函数设置,这里返回空对象
254
+ reactSteps: this.reactSteps // 返回详细的 ReAct 步骤记录
255
+ };
256
+
257
+ } catch (error) {
258
+ console.error(`[Healer] ❌ 执行失败: ${error}`);
259
+
260
+ return {
261
+ success: false,
262
+ errorMessage: (error as Error).message,
263
+ reasoning: `Healer 执行失败: ${(error as Error).message}`,
264
+ steps: 0,
265
+ updatedVariables: {},
266
+ reactSteps: this.reactSteps
267
+ };
268
+ }
269
+ }
270
+
271
+ /**
272
+ * 记录 Thought 步骤
273
+ */
274
+ private recordThoughtStep(thought: string, stepNumber: number): void {
275
+ this.reactSteps.push({
276
+ stepNumber,
277
+ stepType: 'thought',
278
+ timestamp: Date.now(),
279
+ thought
280
+ });
281
+ if (this.debugMode) {
282
+ console.log(`[Healer] 📝 已记录 Thought 步骤,当前共 ${this.reactSteps.length} 个步骤`);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * 记录 Action 步骤
288
+ */
289
+ private recordActionStep(toolName: string, toolInput: any, stepNumber: number): void {
290
+ this.reactSteps.push({
291
+ stepNumber,
292
+ stepType: 'action',
293
+ timestamp: Date.now(),
294
+ toolCall: {
295
+ name: toolName,
296
+ input: toolInput
297
+ }
298
+ });
299
+ if (this.debugMode) {
300
+ console.log(`[Healer] 📝 已记录 Action 步骤,当前共 ${this.reactSteps.length} 个步骤`);
301
+ }
302
+ }
303
+
304
+ /**
305
+ * 记录 Observation 步骤
306
+ */
307
+ private recordObservationStep(output: string, success: boolean, stepNumber: number): void {
308
+ this.reactSteps.push({
309
+ stepNumber,
310
+ stepType: 'observation',
311
+ timestamp: Date.now(),
312
+ toolResult: {
313
+ output,
314
+ success
315
+ }
316
+ });
317
+ if (this.debugMode) {
318
+ console.log(`[Healer] 📝 已记录 Observation 步骤,当前共 ${this.reactSteps.length} 个步骤`);
319
+ }
320
+ }
321
+
322
+ /**
323
+ * 创建带日志输出的 Playwright 工具
324
+ */
325
+ private createPlaywrightToolsWithLogging(
326
+ page: any,
327
+ setVariable: (name: string, value: any) => void,
328
+ getAllVariables: () => Record<string, any>,
329
+ stepCounter: { count: number },
330
+ maxSteps: number
331
+ ): DynamicTool[] {
332
+ const baseTools = createPlaywrightTools(page, setVariable, getAllVariables);
333
+
334
+ // 包装每个工具以添加日志输出和步数提醒
335
+ return baseTools.map(tool => {
336
+ const originalFunc = tool.func;
337
+ tool.func = async (input: string) => {
338
+ if (this.debugMode) {
339
+ console.log(`\n[Healer] 🔧 工具调用: ${tool.name}`);
340
+ console.log(`[Healer] 📝 输入: ${input}`);
341
+ }
342
+
343
+ const startTime = Date.now();
344
+ const result = await originalFunc.call(tool, input);
345
+ const duration = Date.now() - startTime;
346
+
347
+ if (this.debugMode) {
348
+ console.log(`[Healer] ✅ 输出: ${result}`);
349
+ console.log(`[Healer] ⏱️ 耗时: ${duration}ms`);
350
+ }
351
+
352
+ // 附加步数信息到结果中
353
+ const currentStep = stepCounter.count;
354
+ const remainingSteps = Math.max(0, maxSteps - currentStep);
355
+
356
+ let stepWarning = '';
357
+ if (remainingSteps <= 0) {
358
+ stepWarning = `\n\n🚨🚨🚨 【紧急警告】:你已经达到最大步数限制 (${maxSteps} 次)!必须立即结束,不能再调用任何工具!🚨🚨🚨`;
359
+ } else if (remainingSteps <= 3) {
360
+ stepWarning = `\n\n⚠️⚠️⚠️ 【重要提醒】:你只剩下 ${remainingSteps} 次工具调用机会!请立即完成当前任务,不要再调用新工具!⚠️⚠️⚠️`;
361
+ } else if (currentStep >= 10) {
362
+ stepWarning = `\n\n📊 【步数提醒】:已使用 ${currentStep}/${maxSteps} 次,还剩 ${remainingSteps} 次,请加快速度。`;
363
+ }
364
+
365
+ return result + stepWarning;
366
+ };
367
+ return tool;
368
+ });
369
+ }
370
+
371
+ /**
372
+ * 创建 LLM 实例
373
+ */
374
+ private createLLM(): any {
375
+ switch (this.modelConfig.provider) {
376
+ case 'anthropic':
377
+ return new ChatAnthropic({
378
+ apiKey: this.modelConfig.apiKey,
379
+ model: this.modelConfig.modelName,
380
+ temperature: this.modelConfig.temperature ?? 0,
381
+ maxTokens: this.modelConfig.maxTokens ?? 4096
382
+ });
383
+
384
+ case 'openai':
385
+ // 需要安装 @langchain/openai
386
+ const { ChatOpenAI } = require('@langchain/openai');
387
+ return new ChatOpenAI({
388
+ apiKey: this.modelConfig.apiKey,
389
+ modelName: this.modelConfig.modelName,
390
+ temperature: this.modelConfig.temperature ?? 0,
391
+ maxTokens: this.modelConfig.maxTokens ?? 4096,
392
+ configuration: {
393
+ baseURL: this.modelConfig.baseURL
394
+ }
395
+ });
396
+
397
+ default:
398
+ throw new Error(`不支持的模型提供商: ${this.modelConfig.provider}`);
399
+ }
400
+ }
401
+
402
+ /**
403
+ * 构建系统提示
404
+ */
405
+ private buildSystemPrompt(state: HealAgentState): string {
406
+ // 获取当前可用变量列表
407
+ const allVars = this.getAllVariablesCallback ? this.getAllVariablesCallback() : {};
408
+ const variableList = Object.keys(allVars).length > 0
409
+ ? Object.keys(allVars).map(v => ` - ${v}: ${JSON.stringify(allVars[v])}`).join('\n')
410
+ : ' (no variables set yet)';
411
+
412
+ return `
413
+ You are a Playwright test self-healing expert, skilled at analyzing and fixing errors in browser automation tests.
414
+
415
+ ## Available Tools
416
+ ### Navigation & Page Control
417
+ - browser_navigate(url): Navigate to a URL
418
+ - browser_navigate_back(): Navigate back
419
+ - browser_navigate_forward(): Navigate forward
420
+
421
+ ### Element Interaction
422
+ - browser_click(selector): Click on an element (supports multiple selectors, comma-separated)
423
+ - browser_fill(json): Fill form fields. Format: {"selector": "css-or-text", "value": "value"}
424
+ - browser_set_checked(json): Check/uncheck checkboxes. Format: {"selector": "css-or-text", "checked": true|false}
425
+ - browser_select_option(json): Select dropdown option. Format: {"selector": "css-or-text", "value": "option-value"}
426
+ - browser_hover(selector): Hover over an element
427
+
428
+ ### Waiting & Synchronization
429
+ - browser_wait_for_time(ms): Wait for specified time in milliseconds
430
+ - browser_wait_for_selector(selector): Wait for element to appear (supports optional timeout: "selector, timeout=5000")
431
+ - browser_wait_for_url(pattern): Wait for URL to match pattern
432
+
433
+ ### Verification & Inspection
434
+ - browser_snapshot(): Get accessibility snapshot of the page
435
+ - browser_get_url(): Get current page URL
436
+ - browser_get_content(): Get page HTML content
437
+ - browser_get_text(selector): Get text content of an element
438
+ - browser_screenshot(): Take a screenshot and save it
439
+ - browser_verify_text(text): Verify if text is visible on page
440
+ - browser_verify_element(selector): Verify if element exists on page
441
+
442
+ ### Variable Management (IMPORTANT!)
443
+ - set_variable(name,value): Save a variable value for later use. Format: variableName,value
444
+ - Example: set_variable(url,https://example.com) or set_variable(username,john)
445
+ - get_variable: Get all variables or get a specific variable. Use this to understand what data is already stored.
446
+ - To get all variables: get_variable or get_variable/all
447
+ - To get specific variable: get_variable,variableName
448
+
449
+ ## Current Available Variables
450
+ ${variableList}
451
+
452
+ ## Variable Handling Best Practices
453
+ 1. **Use existing variables**: Before setting new variables, check what's already available using get_variable
454
+ 2. **Maintain variable assignments**: If the original code sets variables (like url = page.url()), you MUST reproduce this behavior
455
+ 3. **Semantic understanding**: Analyze the original code to understand what each variable represents (e.g., 'url' typically means current page URL)
456
+ 4. **Consistency**: Use the same variable names and meanings as in the original code
457
+
458
+ ## Workflow
459
+ 1. **Check existing variables**: Use get_variable to see what's already stored
460
+ 2. **Analyze Error**: Understand the error cause and current state
461
+ 3. **Analyze original code**: Look for variable assignments that need to be reproduced
462
+ 4. **Select Tool**: Choose appropriate tool based on the error
463
+ 5. **Execute & Set Variables**: Call tools and use set_variable to maintain variable assignments
464
+ 6. **Evaluate**: Determine if successful, decide next step
465
+ 7. **Loop**: Until success or max steps reached
466
+
467
+ ## Step Limit (CRITICAL!)
468
+ - **Maximum step limit: ${this.maxSteps} steps**
469
+ - **When system shows ≤ 3 steps remaining, stop calling new tools immediately**
470
+ - **When system shows 0 steps remaining, must end immediately, no more tool calls**
471
+ - **Prioritize direct approaches, avoid unnecessary debugging (like frequent screenshots)**
472
+ - **Every tool call consumes a step, use them wisely**
473
+
474
+ ## Strategy Tips
475
+ - Timeout errors: Use browser_wait_for_selector first, then retry
476
+ - Element not found: Try multiple selectors or use browser_screenshot to check state
477
+ - Selector errors: Try more generic selectors (button, a, input)
478
+ - Network delays: Use browser_wait_for_time to wait
479
+ - Uncertain state: Use browser_screenshot or browser_get_url to inspect
480
+ - **Variable handling**: If original code involves variable assignment, use browser_get_text to get the value, then set_variable(name,value) to save it
481
+ - **Efficiency first**: Complete tasks in minimum steps, every step must have clear purpose
482
+
483
+ Please use the tools to complete the self-healing task.
484
+ `.trim();
485
+ }
486
+
487
+ /**
488
+ * 构建用户消息
489
+ */
490
+ private buildUserMessage(state: HealAgentState): string {
491
+ return `
492
+ Please help me fix the following test error:
493
+
494
+ ## Current Task
495
+ ${state.objective}
496
+
497
+ ## Error Message
498
+ ${state.error}
499
+
500
+ ## Original Code
501
+ \`\`\`typescript
502
+ ${state.originalCode}
503
+ \`\`\`
504
+
505
+ ## Important Instructions
506
+ 1. Check existing variables using get_variable before setting new ones
507
+ 2. Analyze the original code to understand what variables are being set (e.g., heal.set(), variable assignments)
508
+ 3. Reproduce those variable assignments using set_variable after completing the task
509
+ 4. Use semantic understanding to determine what each variable represents
510
+
511
+ Please use the provided tools to analyze the problem and attempt to fix it.
512
+ `.trim();
513
+ }
514
+
515
+ /**
516
+ * 判断是否成功
517
+ * TODO: 后续优化成功判断逻辑,支持更多场景
518
+ */
519
+ private isSuccess(result: any): boolean {
520
+ if (!result) {
521
+ return false;
522
+ }
523
+
524
+ // 兼容不同的数据结构
525
+ let messages: any[] | undefined;
526
+
527
+ if (result.model_request && result.model_request.messages) {
528
+ // 结构: { model_request: { messages: [...] } }
529
+ messages = result.model_request.messages;
530
+ } else if (result.messages) {
531
+ // 结构: { messages: [...] }
532
+ messages = result.messages;
533
+ }
534
+
535
+ if (!messages || !Array.isArray(messages)) {
536
+ return false;
537
+ }
538
+
539
+ const lastMessage = messages[messages.length - 1];
540
+ const content = lastMessage.content?.toString() || '';
541
+
542
+ // 检查是否包含成功关键词
543
+ return content.includes('成功') ||
544
+ content.toLowerCase().includes('success') ||
545
+ !content.includes('失败') && !content.toLowerCase().includes('error');
546
+ }
547
+
548
+ /**
549
+ * 提取推理过程
550
+ * TODO: 后续优化推理过程提取,支持更详细的分析
551
+ */
552
+ private extractReasoning(result: any): string {
553
+ if (!result) {
554
+ return '无法提取推理过程';
555
+ }
556
+
557
+ const reasoningParts: string[] = [];
558
+
559
+ // 兼容不同的数据结构
560
+ let messages: any[] | undefined;
561
+
562
+ if (result.model_request && result.model_request.messages) {
563
+ // 结构: { model_request: { messages: [...] } }
564
+ messages = result.model_request.messages;
565
+ } else if (result.messages) {
566
+ // 结构: { messages: [...] }
567
+ messages = result.messages;
568
+ }
569
+
570
+ // 处理工具执行结果(event.tools.messages 结构)
571
+ const toolMessages = result.tools?.messages;
572
+ if (toolMessages && Array.isArray(toolMessages)) {
573
+ for (const msg of toolMessages) {
574
+ if (msg.type === 'tool') {
575
+ const output = msg.content?.toString()?.substring(0, 300) || '';
576
+ reasoningParts.push(`工具结果 [${msg.name}]: ${output}`);
577
+ }
578
+ }
579
+ }
580
+
581
+ if (messages && Array.isArray(messages)) {
582
+ // 收集所有消息内容
583
+ const messageTexts = messages.map((msg: any) => {
584
+ const parts: string[] = [];
585
+
586
+ // AI 消息:content 是 string,tool_calls 是数组
587
+ if (msg.type === 'ai') {
588
+ if (msg.content && typeof msg.content === 'string') {
589
+ parts.push(`AI思考: ${msg.content}`);
590
+ }
591
+
592
+ const aiMsg = msg as any;
593
+ if (aiMsg.tool_calls && Array.isArray(aiMsg.tool_calls)) {
594
+ for (const toolCall of aiMsg.tool_calls) {
595
+ const name = toolCall.name || toolCall.id;
596
+ const args = JSON.stringify(toolCall.args || {});
597
+ parts.push(`工具调用: ${name}`);
598
+ parts.push(`参数: ${args}`);
599
+ }
600
+ }
601
+ } else if (msg.type === 'tool') {
602
+ // 工具执行结果(兼容旧结构)
603
+ const output = msg.content?.toString()?.substring(0, 300) || '';
604
+ parts.push(`工具结果: ${output}`);
605
+ }
606
+
607
+ return parts.join('\n');
608
+ });
609
+
610
+ reasoningParts.push(...messageTexts);
611
+ }
612
+
613
+ if (reasoningParts.length === 0) {
614
+ return '无法提取推理过程';
615
+ }
616
+
617
+ return reasoningParts.filter(Boolean).join('\n\n---\n\n');
618
+ }
619
+ }