@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.
- package/README.md +263 -0
- package/SUMMARY_USAGE.md +359 -0
- package/TOOLS_INTEGRATION_SUMMARY.md +206 -0
- package/dist/agents/error-analyzer.d.ts +62 -0
- package/dist/agents/error-analyzer.d.ts.map +1 -0
- package/dist/agents/error-analyzer.js +168 -0
- package/dist/agents/error-analyzer.js.map +1 -0
- package/dist/agents/heal-agent.d.ts +30 -0
- package/dist/agents/heal-agent.d.ts.map +1 -0
- package/dist/agents/heal-agent.js +76 -0
- package/dist/agents/heal-agent.js.map +1 -0
- package/dist/agents/healer.d.ts +73 -0
- package/dist/agents/healer.d.ts.map +1 -0
- package/dist/agents/healer.js +538 -0
- package/dist/agents/healer.js.map +1 -0
- package/dist/agents/langgraph-agent.d.ts +44 -0
- package/dist/agents/langgraph-agent.d.ts.map +1 -0
- package/dist/agents/langgraph-agent.js +328 -0
- package/dist/agents/langgraph-agent.js.map +1 -0
- package/dist/agents/react-agent.d.ts +52 -0
- package/dist/agents/react-agent.d.ts.map +1 -0
- package/dist/agents/react-agent.js +262 -0
- package/dist/agents/react-agent.js.map +1 -0
- package/dist/agents/tools/form.d.ts +22 -0
- package/dist/agents/tools/form.d.ts.map +1 -0
- package/dist/agents/tools/form.js +134 -0
- package/dist/agents/tools/form.js.map +1 -0
- package/dist/agents/tools/index.d.ts +13 -0
- package/dist/agents/tools/index.d.ts.map +1 -0
- package/dist/agents/tools/index.js +33 -0
- package/dist/agents/tools/index.js.map +1 -0
- package/dist/agents/tools/navigate.d.ts +22 -0
- package/dist/agents/tools/navigate.d.ts.map +1 -0
- package/dist/agents/tools/navigate.js +74 -0
- package/dist/agents/tools/navigate.js.map +1 -0
- package/dist/agents/tools/snapshot.d.ts +22 -0
- package/dist/agents/tools/snapshot.d.ts.map +1 -0
- package/dist/agents/tools/snapshot.js +110 -0
- package/dist/agents/tools/snapshot.js.map +1 -0
- package/dist/agents/tools/verify.d.ts +34 -0
- package/dist/agents/tools/verify.d.ts.map +1 -0
- package/dist/agents/tools/verify.js +169 -0
- package/dist/agents/tools/verify.js.map +1 -0
- package/dist/agents/tools/wait.d.ts +22 -0
- package/dist/agents/tools/wait.d.ts.map +1 -0
- package/dist/agents/tools/wait.js +104 -0
- package/dist/agents/tools/wait.js.map +1 -0
- package/dist/agents/types.d.ts +51 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +6 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/core/ai-heal.d.ts +89 -0
- package/dist/core/ai-heal.d.ts.map +1 -0
- package/dist/core/ai-heal.js +468 -0
- package/dist/core/ai-heal.js.map +1 -0
- package/dist/core/execution-engine.d.ts +16 -0
- package/dist/core/execution-engine.d.ts.map +1 -0
- package/dist/core/execution-engine.js +44 -0
- package/dist/core/execution-engine.js.map +1 -0
- package/dist/core/runner.d.ts +195 -0
- package/dist/core/runner.d.ts.map +1 -0
- package/dist/core/runner.js +658 -0
- package/dist/core/runner.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/types/external.d.ts +6 -0
- package/dist/types/external.d.ts.map +1 -0
- package/dist/types/external.js +7 -0
- package/dist/types/external.js.map +1 -0
- package/dist/types/index.d.ts +153 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +26 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/object-registry.d.ts +48 -0
- package/dist/utils/object-registry.d.ts.map +1 -0
- package/dist/utils/object-registry.js +133 -0
- package/dist/utils/object-registry.js.map +1 -0
- package/package.json +37 -0
- package/playwright.config.ts +38 -0
- package/src/agents/heal-agent.ts +85 -0
- package/src/agents/healer.ts +619 -0
- package/src/agents/tools/EXAMPLES.md +347 -0
- package/src/agents/tools/README.md +207 -0
- package/src/agents/tools/form.ts +138 -0
- package/src/agents/tools/index.ts +29 -0
- package/src/agents/tools/navigate.ts +69 -0
- package/src/agents/tools/snapshot.ts +109 -0
- package/src/agents/tools/verify.ts +168 -0
- package/src/agents/tools/wait.ts +103 -0
- package/src/agents/types.ts +79 -0
- package/src/core/runner.ts +756 -0
- package/src/index.ts +29 -0
- package/src/types/external.ts +7 -0
- package/src/types/index.ts +200 -0
- package/tests/agent/test-heal-agent.spec.ts +81 -0
- package/tests/tools/README.md +227 -0
- package/tests/tools/TEST_SUMMARY.md +214 -0
- package/tests/tools/quick-test.ts +88 -0
- package/tests/tools/tools.test.ts +491 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 导航工具 - Navigate
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { DynamicTool } from '@langchain/core/tools';
|
|
6
|
+
import type { Page } from 'playwright';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 创建导航工具
|
|
10
|
+
*/
|
|
11
|
+
export function createNavigateTool(page: Page): DynamicTool {
|
|
12
|
+
return new DynamicTool({
|
|
13
|
+
name: 'browser_navigate',
|
|
14
|
+
description: 'Navigate to a URL. Input should be the URL as a string.',
|
|
15
|
+
func: async (input: string) => {
|
|
16
|
+
try {
|
|
17
|
+
await page.goto(input, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
18
|
+
return `Navigated to ${input}`;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new Error(`Failed to navigate to ${input}: ${(error as Error).message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 创建后退工具
|
|
28
|
+
*/
|
|
29
|
+
export function createGoBackTool(page: Page): DynamicTool {
|
|
30
|
+
return new DynamicTool({
|
|
31
|
+
name: 'browser_navigate_back',
|
|
32
|
+
description: 'Go back to the previous page. No input needed.',
|
|
33
|
+
func: async () => {
|
|
34
|
+
try {
|
|
35
|
+
await page.goBack();
|
|
36
|
+
return 'Navigated back';
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error(`Failed to go back: ${(error as Error).message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 创建前进工具
|
|
46
|
+
*/
|
|
47
|
+
export function createGoForwardTool(page: Page): DynamicTool {
|
|
48
|
+
return new DynamicTool({
|
|
49
|
+
name: 'browser_navigate_forward',
|
|
50
|
+
description: 'Go forward to the next page. No input needed.',
|
|
51
|
+
func: async () => {
|
|
52
|
+
try {
|
|
53
|
+
await page.goForward();
|
|
54
|
+
return 'Navigated forward';
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new Error(`Failed to go forward: ${(error as Error).message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 导航工具集
|
|
64
|
+
*/
|
|
65
|
+
export const navigateTools = (page: Page): DynamicTool[] => [
|
|
66
|
+
createNavigateTool(page),
|
|
67
|
+
createGoBackTool(page),
|
|
68
|
+
createGoForwardTool(page),
|
|
69
|
+
];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 快照和交互工具 - Snapshot & Click
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { DynamicTool } from '@langchain/core/tools';
|
|
6
|
+
import type { Page } from 'playwright';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 创建页面快照工具
|
|
10
|
+
*/
|
|
11
|
+
export function createSnapshotTool(page: Page): DynamicTool {
|
|
12
|
+
return new DynamicTool({
|
|
13
|
+
name: 'browser_snapshot',
|
|
14
|
+
description: 'Get an accessibility snapshot of the current page. This is better than a screenshot for understanding page structure. No input needed.',
|
|
15
|
+
func: async () => {
|
|
16
|
+
try {
|
|
17
|
+
// 使用 aria 快照作为替代
|
|
18
|
+
const snapshot = await page.locator('body').ariaSnapshot();
|
|
19
|
+
return `Page accessibility snapshot:\n${snapshot}`;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
throw new Error(`Failed to get snapshot: ${(error as Error).message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 创建点击工具
|
|
29
|
+
*/
|
|
30
|
+
export function createClickTool(page: Page): DynamicTool {
|
|
31
|
+
return new DynamicTool({
|
|
32
|
+
name: 'browser_click',
|
|
33
|
+
description: 'Click on an element. Input should be a CSS selector or text that identifies the element. You can also use multiple selectors separated by commas.',
|
|
34
|
+
func: async (input: string) => {
|
|
35
|
+
try {
|
|
36
|
+
// 尝试多个选择器
|
|
37
|
+
const selectors = input.split(',').map(s => s.trim());
|
|
38
|
+
|
|
39
|
+
for (const selector of selectors) {
|
|
40
|
+
try {
|
|
41
|
+
// 先尝试作为文本选择器
|
|
42
|
+
const locator = page.getByText(selector).first();
|
|
43
|
+
const count = await locator.count();
|
|
44
|
+
if (count > 0) {
|
|
45
|
+
await locator.click({ timeout: 5000 });
|
|
46
|
+
return `Clicked on element with text: "${selector}"`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 再尝试作为 CSS 选择器
|
|
50
|
+
await page.click(selector, { timeout: 5000 });
|
|
51
|
+
return `Clicked on element: "${selector}"`;
|
|
52
|
+
} catch {
|
|
53
|
+
// 继续尝试下一个选择器
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
throw new Error(`No element found matching: ${input}`);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error(`Failed to click on "${input}": ${(error as Error).message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 创建悬停工具
|
|
68
|
+
*/
|
|
69
|
+
export function createHoverTool(page: Page): DynamicTool {
|
|
70
|
+
return new DynamicTool({
|
|
71
|
+
name: 'browser_hover',
|
|
72
|
+
description: 'Hover over an element. Input should be a CSS selector or text that identifies the element.',
|
|
73
|
+
func: async (input: string) => {
|
|
74
|
+
try {
|
|
75
|
+
// 尝试多个选择器
|
|
76
|
+
const selectors = input.split(',').map(s => s.trim());
|
|
77
|
+
|
|
78
|
+
for (const selector of selectors) {
|
|
79
|
+
try {
|
|
80
|
+
const locator = page.getByText(selector).first();
|
|
81
|
+
const count = await locator.count();
|
|
82
|
+
if (count > 0) {
|
|
83
|
+
await locator.hover({ timeout: 5000 });
|
|
84
|
+
return `Hovered over element with text: "${selector}"`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await page.hover(selector, { timeout: 5000 });
|
|
88
|
+
return `Hovered over element: "${selector}"`;
|
|
89
|
+
} catch {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
throw new Error(`No element found matching: ${input}`);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new Error(`Failed to hover over "${input}": ${(error as Error).message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 快照和交互工具集
|
|
104
|
+
*/
|
|
105
|
+
export const snapshotTools = (page: Page): DynamicTool[] => [
|
|
106
|
+
createSnapshotTool(page),
|
|
107
|
+
createClickTool(page),
|
|
108
|
+
createHoverTool(page),
|
|
109
|
+
];
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 验证工具 - Verify
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { DynamicTool } from '@langchain/core/tools';
|
|
6
|
+
import type { Page } from 'playwright';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 创建获取URL工具
|
|
10
|
+
*/
|
|
11
|
+
export function createGetURLTool(page: Page): DynamicTool {
|
|
12
|
+
return new DynamicTool({
|
|
13
|
+
name: 'browser_get_url',
|
|
14
|
+
description: 'Get the current page URL. No input needed.',
|
|
15
|
+
func: async () => {
|
|
16
|
+
try {
|
|
17
|
+
const url = page.url();
|
|
18
|
+
return `Current URL: ${url}`;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new Error(`Failed to get URL: ${(error as Error).message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 创建获取页面内容工具
|
|
28
|
+
*/
|
|
29
|
+
export function createGetContentTool(page: Page): DynamicTool {
|
|
30
|
+
return new DynamicTool({
|
|
31
|
+
name: 'browser_get_content',
|
|
32
|
+
description: 'Get the page content (HTML). No input needed.',
|
|
33
|
+
func: async () => {
|
|
34
|
+
try {
|
|
35
|
+
const content = await page.content();
|
|
36
|
+
// 返回前1000个字符以避免过长
|
|
37
|
+
const preview = content.length > 1000 ? content.substring(0, 1000) + '...' : content;
|
|
38
|
+
return `Page content:\n${preview}`;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(`Failed to get content: ${(error as Error).message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 创建获取文本工具
|
|
48
|
+
*/
|
|
49
|
+
export function createGetTextTool(page: Page): DynamicTool {
|
|
50
|
+
return new DynamicTool({
|
|
51
|
+
name: 'browser_get_text',
|
|
52
|
+
description: 'Get text content from an element. Input should be a CSS selector or text that identifies the element.',
|
|
53
|
+
func: async (input: string) => {
|
|
54
|
+
try {
|
|
55
|
+
// 尝试多个选择器
|
|
56
|
+
const selectors = input.split(',').map(s => s.trim());
|
|
57
|
+
|
|
58
|
+
for (const selector of selectors) {
|
|
59
|
+
try {
|
|
60
|
+
// 先尝试作为文本选择器
|
|
61
|
+
const locator = page.getByText(selector).first();
|
|
62
|
+
const count = await locator.count();
|
|
63
|
+
if (count > 0) {
|
|
64
|
+
const text = await locator.textContent();
|
|
65
|
+
return `Text from element with text "${selector}": ${text}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 再尝试作为 CSS 选择器
|
|
69
|
+
const element = page.locator(selector).first();
|
|
70
|
+
const text = await element.textContent();
|
|
71
|
+
return `Text from element "${selector}": ${text}`;
|
|
72
|
+
} catch {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
throw new Error(`No element found matching: ${input}`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new Error(`Failed to get text: ${(error as Error).message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 创建截图工具
|
|
87
|
+
*/
|
|
88
|
+
export function createScreenshotTool(page: Page): DynamicTool {
|
|
89
|
+
return new DynamicTool({
|
|
90
|
+
name: 'browser_screenshot',
|
|
91
|
+
description: 'Take a screenshot of the current page. No input needed.',
|
|
92
|
+
func: async () => {
|
|
93
|
+
try {
|
|
94
|
+
const timestamp = Date.now();
|
|
95
|
+
const filename = `screenshot-${timestamp}.png`;
|
|
96
|
+
await page.screenshot({ path: filename });
|
|
97
|
+
return `Screenshot saved to ${filename}`;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
throw new Error(`Failed to take screenshot: ${(error as Error).message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 创建验证文本可见工具
|
|
107
|
+
*/
|
|
108
|
+
export function createVerifyTextVisibleTool(page: Page): DynamicTool {
|
|
109
|
+
return new DynamicTool({
|
|
110
|
+
name: 'browser_verify_text',
|
|
111
|
+
description: 'Verify that text is visible on the page. Input should be the text to verify.',
|
|
112
|
+
func: async (input: string) => {
|
|
113
|
+
try {
|
|
114
|
+
const locator = page.getByText(input).filter({ visible: true });
|
|
115
|
+
const count = await locator.count();
|
|
116
|
+
|
|
117
|
+
if (count === 0) {
|
|
118
|
+
throw new Error(`Text "${input}" not found on page`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return `Text "${input}" is visible on the page`;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw new Error(`Failed to verify text: ${(error as Error).message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 创建验证元素可见工具
|
|
131
|
+
*/
|
|
132
|
+
export function createVerifyElementVisibleTool(page: Page): DynamicTool {
|
|
133
|
+
return new DynamicTool({
|
|
134
|
+
name: 'browser_verify_element',
|
|
135
|
+
description: 'Verify that an element is visible on the page. Input should be a CSS selector.',
|
|
136
|
+
func: async (input: string) => {
|
|
137
|
+
try {
|
|
138
|
+
const locator = page.locator(input).first();
|
|
139
|
+
const count = await locator.count();
|
|
140
|
+
|
|
141
|
+
if (count === 0) {
|
|
142
|
+
throw new Error(`Element "${input}" not found on page`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const isVisible = await locator.isVisible();
|
|
146
|
+
if (!isVisible) {
|
|
147
|
+
throw new Error(`Element "${input}" is not visible`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return `Element "${input}" is visible on the page`;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
throw new Error(`Failed to verify element: ${(error as Error).message}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 验证工具集
|
|
160
|
+
*/
|
|
161
|
+
export const verifyTools = (page: Page): DynamicTool[] => [
|
|
162
|
+
createGetURLTool(page),
|
|
163
|
+
createGetContentTool(page),
|
|
164
|
+
createGetTextTool(page),
|
|
165
|
+
createScreenshotTool(page),
|
|
166
|
+
createVerifyTextVisibleTool(page),
|
|
167
|
+
createVerifyElementVisibleTool(page),
|
|
168
|
+
];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 等待工具 - Wait
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { DynamicTool } from '@langchain/core/tools';
|
|
6
|
+
import type { Page } from 'playwright';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 创建等待时间工具
|
|
10
|
+
*/
|
|
11
|
+
export function createWaitForTimeTool(page: Page): DynamicTool {
|
|
12
|
+
return new DynamicTool({
|
|
13
|
+
name: 'browser_wait_for_time',
|
|
14
|
+
description: 'Wait for a specified amount of time. Input should be the time in milliseconds as a number.',
|
|
15
|
+
func: async (input: string) => {
|
|
16
|
+
try {
|
|
17
|
+
const time = parseInt(input, 10);
|
|
18
|
+
if (isNaN(time)) {
|
|
19
|
+
throw new Error('Input must be a number representing milliseconds');
|
|
20
|
+
}
|
|
21
|
+
await page.waitForTimeout(time);
|
|
22
|
+
return `Waited for ${time}ms`;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw new Error(`Failed to wait: ${(error as Error).message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 创建等待元素工具
|
|
32
|
+
*/
|
|
33
|
+
export function createWaitForSelectorTool(page: Page): DynamicTool {
|
|
34
|
+
return new DynamicTool({
|
|
35
|
+
name: 'browser_wait_for_selector',
|
|
36
|
+
description: 'Wait for an element to appear. Input should be a CSS selector or text that identifies the element. You can optionally specify timeout in milliseconds like: "selector, timeout=5000".',
|
|
37
|
+
func: async (input: string) => {
|
|
38
|
+
try {
|
|
39
|
+
// 解析输入(支持可选的 timeout 参数)
|
|
40
|
+
let selector = input;
|
|
41
|
+
let timeout = 30000;
|
|
42
|
+
|
|
43
|
+
const timeoutMatch = input.match(/timeout=(\d+)/);
|
|
44
|
+
if (timeoutMatch) {
|
|
45
|
+
timeout = parseInt(timeoutMatch[1], 10);
|
|
46
|
+
selector = input.replace(/,?\s*timeout=\d+/, '').trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 尝试多个选择器
|
|
50
|
+
const selectors = selector.split(',').map(s => s.trim());
|
|
51
|
+
|
|
52
|
+
for (const sel of selectors) {
|
|
53
|
+
try {
|
|
54
|
+
// 先尝试作为文本选择器
|
|
55
|
+
const locator = page.getByText(sel);
|
|
56
|
+
const count = await locator.count();
|
|
57
|
+
if (count > 0) {
|
|
58
|
+
await locator.waitFor({ state: 'visible', timeout });
|
|
59
|
+
return `Element with text "${sel}" became visible`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 再尝试作为 CSS 选择器
|
|
63
|
+
await page.waitForSelector(sel, { state: 'visible', timeout });
|
|
64
|
+
return `Element "${sel}" became visible`;
|
|
65
|
+
} catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
throw new Error(`No element found matching: ${selector}`);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new Error(`Failed to wait for selector: ${(error as Error).message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 创建等待URL工具
|
|
80
|
+
*/
|
|
81
|
+
export function createWaitForURLTool(page: Page): DynamicTool {
|
|
82
|
+
return new DynamicTool({
|
|
83
|
+
name: 'browser_wait_for_url',
|
|
84
|
+
description: 'Wait for the URL to match a pattern. Input should be a URL pattern (string, glob, or regex).',
|
|
85
|
+
func: async (input: string) => {
|
|
86
|
+
try {
|
|
87
|
+
await page.waitForURL(input, { timeout: 30000 });
|
|
88
|
+
return `URL matched: ${input}`;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
throw new Error(`Failed to wait for URL: ${(error as Error).message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 等待工具集
|
|
98
|
+
*/
|
|
99
|
+
export const waitTools = (page: Page): DynamicTool[] => [
|
|
100
|
+
createWaitForTimeTool(page),
|
|
101
|
+
createWaitForSelectorTool(page),
|
|
102
|
+
createWaitForURLTool(page),
|
|
103
|
+
];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自愈 Agent 类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Page, BrowserContext } from 'playwright';
|
|
6
|
+
import type { ModelConfig } from '../types/external';
|
|
7
|
+
import type { ReActStep } from '../types';
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Agent 状态
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 自愈 Agent 状态
|
|
15
|
+
*/
|
|
16
|
+
export interface HealAgentState {
|
|
17
|
+
// 输入
|
|
18
|
+
objective: string; // 步骤描述,如 "点击按钮"
|
|
19
|
+
error: string; // 错误信息
|
|
20
|
+
stackTrace?: string; // 错误堆栈
|
|
21
|
+
originalCode: string; // 原始代码(用于参考)
|
|
22
|
+
|
|
23
|
+
// 执行上下文
|
|
24
|
+
context: {
|
|
25
|
+
page: any; // Playwright Page 对象
|
|
26
|
+
context?: any; // Playwright BrowserContext 对象
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// 变量绑定
|
|
30
|
+
variables?: Record<string, any>; // 用户提供的变量列表,Agent 需要回填变量值
|
|
31
|
+
|
|
32
|
+
// 执行配置
|
|
33
|
+
maxSteps?: number; // 最大执行步数
|
|
34
|
+
debugMode?: boolean; // 调试模式
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Agent 配置
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Agent 配置
|
|
43
|
+
*/
|
|
44
|
+
export interface HealAgentConfig {
|
|
45
|
+
model?: ModelConfig; // 模型配置(用于 LLM Agent)
|
|
46
|
+
maxSteps?: number; // 最大执行步数
|
|
47
|
+
debugMode?: boolean; // 调试模式
|
|
48
|
+
setVariable?: (name: string, value: any) => void; // 变量设置回调
|
|
49
|
+
getAllVariables?: () => Record<string, any>; // 获取所有变量回调
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Agent 结果
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Agent 执行结果
|
|
58
|
+
*/
|
|
59
|
+
export interface HealAgentResult {
|
|
60
|
+
success: boolean; // 是否成功完成目标
|
|
61
|
+
reasoning?: string; // Agent 的推理过程
|
|
62
|
+
errorMessage?: string; // 如果失败,错误原因
|
|
63
|
+
steps?: number; // 执行了多少步
|
|
64
|
+
updatedVariables?: Record<string, any>; // Agent 更新的变量值
|
|
65
|
+
reactSteps?: ReActStep[]; // ReAct 循环的详细步骤记录
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// MCP 工具
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* MCP 工具定义
|
|
74
|
+
*/
|
|
75
|
+
export interface MCPTool {
|
|
76
|
+
name: string;
|
|
77
|
+
description: string;
|
|
78
|
+
inputSchema: Record<string, unknown>;
|
|
79
|
+
}
|