mozi-bot 1.0.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 (259) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +467 -0
  3. package/dist/agents/agent.d.ts +118 -0
  4. package/dist/agents/agent.d.ts.map +1 -0
  5. package/dist/agents/agent.js +686 -0
  6. package/dist/agents/agent.js.map +1 -0
  7. package/dist/agents/compaction.d.ts +78 -0
  8. package/dist/agents/compaction.d.ts.map +1 -0
  9. package/dist/agents/compaction.js +350 -0
  10. package/dist/agents/compaction.js.map +1 -0
  11. package/dist/agents/failover-error.d.ts +42 -0
  12. package/dist/agents/failover-error.d.ts.map +1 -0
  13. package/dist/agents/failover-error.js +171 -0
  14. package/dist/agents/failover-error.js.map +1 -0
  15. package/dist/agents/index.d.ts +10 -0
  16. package/dist/agents/index.d.ts.map +1 -0
  17. package/dist/agents/index.js +10 -0
  18. package/dist/agents/index.js.map +1 -0
  19. package/dist/agents/model-fallback.d.ts +54 -0
  20. package/dist/agents/model-fallback.d.ts.map +1 -0
  21. package/dist/agents/model-fallback.js +227 -0
  22. package/dist/agents/model-fallback.js.map +1 -0
  23. package/dist/agents/session-store.d.ts +53 -0
  24. package/dist/agents/session-store.d.ts.map +1 -0
  25. package/dist/agents/session-store.js +217 -0
  26. package/dist/agents/session-store.js.map +1 -0
  27. package/dist/agents/system-prompt.d.ts +29 -0
  28. package/dist/agents/system-prompt.d.ts.map +1 -0
  29. package/dist/agents/system-prompt.js +299 -0
  30. package/dist/agents/system-prompt.js.map +1 -0
  31. package/dist/channels/common/base.d.ts +40 -0
  32. package/dist/channels/common/base.d.ts.map +1 -0
  33. package/dist/channels/common/base.js +23 -0
  34. package/dist/channels/common/base.js.map +1 -0
  35. package/dist/channels/common/index.d.ts +21 -0
  36. package/dist/channels/common/index.d.ts.map +1 -0
  37. package/dist/channels/common/index.js +66 -0
  38. package/dist/channels/common/index.js.map +1 -0
  39. package/dist/channels/dingtalk/api.d.ts +30 -0
  40. package/dist/channels/dingtalk/api.d.ts.map +1 -0
  41. package/dist/channels/dingtalk/api.js +149 -0
  42. package/dist/channels/dingtalk/api.js.map +1 -0
  43. package/dist/channels/dingtalk/events.d.ts +80 -0
  44. package/dist/channels/dingtalk/events.d.ts.map +1 -0
  45. package/dist/channels/dingtalk/events.js +92 -0
  46. package/dist/channels/dingtalk/events.js.map +1 -0
  47. package/dist/channels/dingtalk/index.d.ts +51 -0
  48. package/dist/channels/dingtalk/index.d.ts.map +1 -0
  49. package/dist/channels/dingtalk/index.js +231 -0
  50. package/dist/channels/dingtalk/index.js.map +1 -0
  51. package/dist/channels/dingtalk/stream.d.ts +31 -0
  52. package/dist/channels/dingtalk/stream.d.ts.map +1 -0
  53. package/dist/channels/dingtalk/stream.js +116 -0
  54. package/dist/channels/dingtalk/stream.js.map +1 -0
  55. package/dist/channels/feishu/api.d.ts +33 -0
  56. package/dist/channels/feishu/api.d.ts.map +1 -0
  57. package/dist/channels/feishu/api.js +120 -0
  58. package/dist/channels/feishu/api.js.map +1 -0
  59. package/dist/channels/feishu/events.d.ts +87 -0
  60. package/dist/channels/feishu/events.d.ts.map +1 -0
  61. package/dist/channels/feishu/events.js +163 -0
  62. package/dist/channels/feishu/events.js.map +1 -0
  63. package/dist/channels/feishu/index.d.ts +46 -0
  64. package/dist/channels/feishu/index.d.ts.map +1 -0
  65. package/dist/channels/feishu/index.js +207 -0
  66. package/dist/channels/feishu/index.js.map +1 -0
  67. package/dist/channels/feishu/websocket.d.ts +32 -0
  68. package/dist/channels/feishu/websocket.d.ts.map +1 -0
  69. package/dist/channels/feishu/websocket.js +121 -0
  70. package/dist/channels/feishu/websocket.js.map +1 -0
  71. package/dist/channels/index.d.ts +9 -0
  72. package/dist/channels/index.d.ts.map +1 -0
  73. package/dist/channels/index.js +9 -0
  74. package/dist/channels/index.js.map +1 -0
  75. package/dist/cli/index.d.ts +6 -0
  76. package/dist/cli/index.d.ts.map +1 -0
  77. package/dist/cli/index.js +468 -0
  78. package/dist/cli/index.js.map +1 -0
  79. package/dist/commands/index.d.ts +54 -0
  80. package/dist/commands/index.d.ts.map +1 -0
  81. package/dist/commands/index.js +171 -0
  82. package/dist/commands/index.js.map +1 -0
  83. package/dist/config/index.d.ts +201 -0
  84. package/dist/config/index.d.ts.map +1 -0
  85. package/dist/config/index.js +261 -0
  86. package/dist/config/index.js.map +1 -0
  87. package/dist/gateway/index.d.ts +5 -0
  88. package/dist/gateway/index.d.ts.map +1 -0
  89. package/dist/gateway/index.js +5 -0
  90. package/dist/gateway/index.js.map +1 -0
  91. package/dist/gateway/server.d.ts +42 -0
  92. package/dist/gateway/server.d.ts.map +1 -0
  93. package/dist/gateway/server.js +235 -0
  94. package/dist/gateway/server.js.map +1 -0
  95. package/dist/hooks/index.d.ts +168 -0
  96. package/dist/hooks/index.d.ts.map +1 -0
  97. package/dist/hooks/index.js +148 -0
  98. package/dist/hooks/index.js.map +1 -0
  99. package/dist/index.d.ts +18 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +29 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/memory/index.d.ts +94 -0
  104. package/dist/memory/index.d.ts.map +1 -0
  105. package/dist/memory/index.js +282 -0
  106. package/dist/memory/index.js.map +1 -0
  107. package/dist/plugins/index.d.ts +57 -0
  108. package/dist/plugins/index.d.ts.map +1 -0
  109. package/dist/plugins/index.js +103 -0
  110. package/dist/plugins/index.js.map +1 -0
  111. package/dist/providers/anthropic-compatible.d.ts +48 -0
  112. package/dist/providers/anthropic-compatible.d.ts.map +1 -0
  113. package/dist/providers/anthropic-compatible.js +380 -0
  114. package/dist/providers/anthropic-compatible.js.map +1 -0
  115. package/dist/providers/base.d.ts +26 -0
  116. package/dist/providers/base.d.ts.map +1 -0
  117. package/dist/providers/base.js +58 -0
  118. package/dist/providers/base.js.map +1 -0
  119. package/dist/providers/custom-openai.d.ts +66 -0
  120. package/dist/providers/custom-openai.d.ts.map +1 -0
  121. package/dist/providers/custom-openai.js +255 -0
  122. package/dist/providers/custom-openai.js.map +1 -0
  123. package/dist/providers/dashscope.d.ts +15 -0
  124. package/dist/providers/dashscope.d.ts.map +1 -0
  125. package/dist/providers/dashscope.js +237 -0
  126. package/dist/providers/dashscope.js.map +1 -0
  127. package/dist/providers/deepseek.d.ts +10 -0
  128. package/dist/providers/deepseek.d.ts.map +1 -0
  129. package/dist/providers/deepseek.js +57 -0
  130. package/dist/providers/deepseek.js.map +1 -0
  131. package/dist/providers/index.d.ts +33 -0
  132. package/dist/providers/index.d.ts.map +1 -0
  133. package/dist/providers/index.js +142 -0
  134. package/dist/providers/index.js.map +1 -0
  135. package/dist/providers/kimi.d.ts +10 -0
  136. package/dist/providers/kimi.d.ts.map +1 -0
  137. package/dist/providers/kimi.js +83 -0
  138. package/dist/providers/kimi.js.map +1 -0
  139. package/dist/providers/minimax.d.ts +20 -0
  140. package/dist/providers/minimax.d.ts.map +1 -0
  141. package/dist/providers/minimax.js +252 -0
  142. package/dist/providers/minimax.js.map +1 -0
  143. package/dist/providers/modelscope.d.ts +15 -0
  144. package/dist/providers/modelscope.d.ts.map +1 -0
  145. package/dist/providers/modelscope.js +237 -0
  146. package/dist/providers/modelscope.js.map +1 -0
  147. package/dist/providers/openai-compatible.d.ts +57 -0
  148. package/dist/providers/openai-compatible.d.ts.map +1 -0
  149. package/dist/providers/openai-compatible.js +193 -0
  150. package/dist/providers/openai-compatible.js.map +1 -0
  151. package/dist/providers/stepfun.d.ts +10 -0
  152. package/dist/providers/stepfun.d.ts.map +1 -0
  153. package/dist/providers/stepfun.js +125 -0
  154. package/dist/providers/stepfun.js.map +1 -0
  155. package/dist/providers/zhipu.d.ts +15 -0
  156. package/dist/providers/zhipu.d.ts.map +1 -0
  157. package/dist/providers/zhipu.js +247 -0
  158. package/dist/providers/zhipu.js.map +1 -0
  159. package/dist/sessions/index.d.ts +6 -0
  160. package/dist/sessions/index.d.ts.map +1 -0
  161. package/dist/sessions/index.js +6 -0
  162. package/dist/sessions/index.js.map +1 -0
  163. package/dist/sessions/store.d.ts +45 -0
  164. package/dist/sessions/store.d.ts.map +1 -0
  165. package/dist/sessions/store.js +268 -0
  166. package/dist/sessions/store.js.map +1 -0
  167. package/dist/sessions/types.d.ts +110 -0
  168. package/dist/sessions/types.d.ts.map +1 -0
  169. package/dist/sessions/types.js +6 -0
  170. package/dist/sessions/types.js.map +1 -0
  171. package/dist/tools/builtin/apply-patch.d.ts +8 -0
  172. package/dist/tools/builtin/apply-patch.d.ts.map +1 -0
  173. package/dist/tools/builtin/apply-patch.js +272 -0
  174. package/dist/tools/builtin/apply-patch.js.map +1 -0
  175. package/dist/tools/builtin/bash.d.ts +25 -0
  176. package/dist/tools/builtin/bash.d.ts.map +1 -0
  177. package/dist/tools/builtin/bash.js +212 -0
  178. package/dist/tools/builtin/bash.js.map +1 -0
  179. package/dist/tools/builtin/browser.d.ts +12 -0
  180. package/dist/tools/builtin/browser.d.ts.map +1 -0
  181. package/dist/tools/builtin/browser.js +693 -0
  182. package/dist/tools/builtin/browser.js.map +1 -0
  183. package/dist/tools/builtin/filesystem.d.ts +29 -0
  184. package/dist/tools/builtin/filesystem.d.ts.map +1 -0
  185. package/dist/tools/builtin/filesystem.js +484 -0
  186. package/dist/tools/builtin/filesystem.js.map +1 -0
  187. package/dist/tools/builtin/image.d.ts +13 -0
  188. package/dist/tools/builtin/image.d.ts.map +1 -0
  189. package/dist/tools/builtin/image.js +126 -0
  190. package/dist/tools/builtin/image.js.map +1 -0
  191. package/dist/tools/builtin/index.d.ts +30 -0
  192. package/dist/tools/builtin/index.d.ts.map +1 -0
  193. package/dist/tools/builtin/index.js +55 -0
  194. package/dist/tools/builtin/index.js.map +1 -0
  195. package/dist/tools/builtin/process-registry.d.ts +71 -0
  196. package/dist/tools/builtin/process-registry.d.ts.map +1 -0
  197. package/dist/tools/builtin/process-registry.js +172 -0
  198. package/dist/tools/builtin/process-registry.js.map +1 -0
  199. package/dist/tools/builtin/process-tool.d.ts +8 -0
  200. package/dist/tools/builtin/process-tool.d.ts.map +1 -0
  201. package/dist/tools/builtin/process-tool.js +279 -0
  202. package/dist/tools/builtin/process-tool.js.map +1 -0
  203. package/dist/tools/builtin/subagent.d.ts +33 -0
  204. package/dist/tools/builtin/subagent.d.ts.map +1 -0
  205. package/dist/tools/builtin/subagent.js +185 -0
  206. package/dist/tools/builtin/subagent.js.map +1 -0
  207. package/dist/tools/builtin/system.d.ts +11 -0
  208. package/dist/tools/builtin/system.d.ts.map +1 -0
  209. package/dist/tools/builtin/system.js +144 -0
  210. package/dist/tools/builtin/system.js.map +1 -0
  211. package/dist/tools/builtin/web.d.ts +9 -0
  212. package/dist/tools/builtin/web.d.ts.map +1 -0
  213. package/dist/tools/builtin/web.js +87 -0
  214. package/dist/tools/builtin/web.js.map +1 -0
  215. package/dist/tools/common.d.ts +45 -0
  216. package/dist/tools/common.d.ts.map +1 -0
  217. package/dist/tools/common.js +129 -0
  218. package/dist/tools/common.js.map +1 -0
  219. package/dist/tools/index.d.ts +8 -0
  220. package/dist/tools/index.d.ts.map +1 -0
  221. package/dist/tools/index.js +8 -0
  222. package/dist/tools/index.js.map +1 -0
  223. package/dist/tools/registry.d.ts +33 -0
  224. package/dist/tools/registry.d.ts.map +1 -0
  225. package/dist/tools/registry.js +174 -0
  226. package/dist/tools/registry.js.map +1 -0
  227. package/dist/tools/types.d.ts +58 -0
  228. package/dist/tools/types.d.ts.map +1 -0
  229. package/dist/tools/types.js +11 -0
  230. package/dist/tools/types.js.map +1 -0
  231. package/dist/types/index.d.ts +280 -0
  232. package/dist/types/index.d.ts.map +1 -0
  233. package/dist/types/index.js +34 -0
  234. package/dist/types/index.js.map +1 -0
  235. package/dist/utils/index.d.ts +35 -0
  236. package/dist/utils/index.d.ts.map +1 -0
  237. package/dist/utils/index.js +114 -0
  238. package/dist/utils/index.js.map +1 -0
  239. package/dist/utils/logger.d.ts +24 -0
  240. package/dist/utils/logger.d.ts.map +1 -0
  241. package/dist/utils/logger.js +94 -0
  242. package/dist/utils/logger.js.map +1 -0
  243. package/dist/web/index.d.ts +7 -0
  244. package/dist/web/index.d.ts.map +1 -0
  245. package/dist/web/index.js +7 -0
  246. package/dist/web/index.js.map +1 -0
  247. package/dist/web/static.d.ts +12 -0
  248. package/dist/web/static.d.ts.map +1 -0
  249. package/dist/web/static.js +1260 -0
  250. package/dist/web/static.js.map +1 -0
  251. package/dist/web/types.d.ts +95 -0
  252. package/dist/web/types.d.ts.map +1 -0
  253. package/dist/web/types.js +5 -0
  254. package/dist/web/types.js.map +1 -0
  255. package/dist/web/websocket.d.ts +58 -0
  256. package/dist/web/websocket.d.ts.map +1 -0
  257. package/dist/web/websocket.js +371 -0
  258. package/dist/web/websocket.js.map +1 -0
  259. package/package.json +89 -0
@@ -0,0 +1,693 @@
1
+ /**
2
+ * 内置工具 - 浏览器控制
3
+ *
4
+ * 基于 Playwright 实现的浏览器自动化工具
5
+ * 支持页面导航、截图、内容提取、元素交互等功能
6
+ *
7
+ * 参考 moltbot 实现,增加了元素引用(ref)系统,支持更丰富的交互操作
8
+ */
9
+ import { Type } from "@sinclair/typebox";
10
+ import { jsonResult, errorResult, readStringParam, readNumberParam, readBooleanParam } from "../common.js";
11
+ let browserSession = null;
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ let playwrightModule = null;
14
+ /** 延迟加载 Playwright */
15
+ async function getPlaywright() {
16
+ if (!playwrightModule) {
17
+ try {
18
+ // @ts-ignore - 动态导入可选依赖
19
+ playwrightModule = await import("playwright-core");
20
+ }
21
+ catch {
22
+ throw new Error("Playwright not installed. Run: npm install playwright-core");
23
+ }
24
+ }
25
+ return playwrightModule;
26
+ }
27
+ /** 获取或创建浏览器会话 */
28
+ async function getBrowserSession() {
29
+ if (browserSession) {
30
+ return browserSession;
31
+ }
32
+ throw new Error("Browser not started. Use 'start' action first.");
33
+ }
34
+ /** 通过 ref 获取元素定位器 */
35
+ function getRefLocator(page, ref, session) {
36
+ // 标准化 ref 格式
37
+ const normalized = ref.startsWith("@")
38
+ ? ref.slice(1)
39
+ : ref.startsWith("ref=")
40
+ ? ref.slice(4)
41
+ : ref;
42
+ // 处理 e{数字} 格式的引用
43
+ if (/^e\d+$/i.test(normalized)) {
44
+ const info = session.refs.get(normalized.toLowerCase());
45
+ if (!info) {
46
+ throw new Error(`Unknown ref "${normalized}". Run a new snapshot first.`);
47
+ }
48
+ // 通过角色和名称定位
49
+ const locator = info.name
50
+ ? page.getByRole(info.role, { name: info.name, exact: true })
51
+ : page.getByRole(info.role);
52
+ return info.nth !== undefined ? locator.nth(info.nth) : locator;
53
+ }
54
+ // 尝试作为 CSS 选择器
55
+ return page.locator(ref);
56
+ }
57
+ /** 解析 aria 快照,生成元素引用 */
58
+ function parseAriaSnapshot(snapshot) {
59
+ const refs = new Map();
60
+ let refCounter = 1;
61
+ // 匹配 aria 快照中的角色和名称
62
+ // 格式类似: - button "Submit" 或 - textbox "Email"
63
+ const lines = snapshot.split("\n");
64
+ const roleCounters = new Map();
65
+ for (const line of lines) {
66
+ // 匹配: - role "name" 或 - role (无名称)
67
+ const match = line.match(/^\s*-\s+(\w+)(?:\s+"([^"]*)")?/);
68
+ if (match) {
69
+ const role = match[1];
70
+ const name = match[2];
71
+ // 只为交互元素生成 ref
72
+ const interactiveRoles = [
73
+ "button", "link", "textbox", "checkbox", "radio", "combobox",
74
+ "listbox", "option", "menuitem", "tab", "switch", "slider",
75
+ "searchbox", "spinbutton", "menuitemcheckbox", "menuitemradio",
76
+ "treeitem", "gridcell", "row", "cell"
77
+ ];
78
+ if (interactiveRoles.includes(role)) {
79
+ // 计算 nth 索引
80
+ const key = `${role}:${name || ""}`;
81
+ if (!roleCounters.has(role)) {
82
+ roleCounters.set(role, new Map());
83
+ }
84
+ const roleMap = roleCounters.get(role);
85
+ const count = roleMap.get(key) || 0;
86
+ roleMap.set(key, count + 1);
87
+ const refKey = `e${refCounter}`;
88
+ refs.set(refKey, {
89
+ role,
90
+ name: name || undefined,
91
+ nth: count > 0 ? count : undefined,
92
+ });
93
+ refCounter++;
94
+ }
95
+ }
96
+ }
97
+ return refs;
98
+ }
99
+ /** 生成带 ref 标记的快照文本 */
100
+ function generateRefSnapshot(refs) {
101
+ const lines = [];
102
+ for (const [ref, info] of refs) {
103
+ const nameStr = info.name ? ` "${info.name}"` : "";
104
+ const nthStr = info.nth !== undefined ? ` [${info.nth}]` : "";
105
+ lines.push(`[${ref}] ${info.role}${nameStr}${nthStr}`);
106
+ }
107
+ return lines.join("\n");
108
+ }
109
+ /** 浏览器控制工具 */
110
+ export function createBrowserTool() {
111
+ return {
112
+ name: "browser",
113
+ label: "Browser Control",
114
+ description: `Control a browser for web automation tasks.
115
+
116
+ Actions:
117
+ - start: Launch browser (headless by default, set headless=false to see the browser)
118
+ - stop: Close browser
119
+ - navigate: Go to a URL
120
+ - screenshot: Take a screenshot (supports element screenshot with ref or selector)
121
+ - snapshot: Get page accessibility snapshot with element refs (e1, e2, etc.)
122
+ - click: Click an element (use ref like "e1" or CSS selector)
123
+ - type: Type text into an element (supports slowly mode and submit)
124
+ - hover: Hover over an element
125
+ - drag: Drag from one element to another
126
+ - press: Press a keyboard key
127
+ - select: Select option(s) from a dropdown
128
+ - scroll: Scroll the page or to an element
129
+ - evaluate: Execute JavaScript
130
+ - wait: Wait for a condition
131
+ - fill: Fill multiple form fields at once
132
+
133
+ Element Reference System:
134
+ - After calling 'snapshot', you'll get refs like e1, e2, e3 for interactive elements
135
+ - Use these refs in click, type, hover, drag, select actions
136
+ - Refs are more reliable than CSS selectors for AI-driven automation`,
137
+ parameters: Type.Object({
138
+ action: Type.String({
139
+ description: "Action: start, stop, navigate, screenshot, snapshot, click, type, hover, drag, press, select, scroll, evaluate, wait, fill",
140
+ }),
141
+ // Navigation
142
+ url: Type.Optional(Type.String({ description: "URL for navigate action" })),
143
+ // Element targeting
144
+ ref: Type.Optional(Type.String({ description: "Element ref (e.g., 'e1', 'e2') from snapshot" })),
145
+ selector: Type.Optional(Type.String({ description: "CSS selector (fallback if ref not available)" })),
146
+ // Click options
147
+ doubleClick: Type.Optional(Type.Boolean({ description: "Double click instead of single click" })),
148
+ button: Type.Optional(Type.String({ description: "Mouse button: left, right, middle" })),
149
+ modifiers: Type.Optional(Type.Array(Type.String(), { description: "Modifier keys: Alt, Control, Meta, Shift" })),
150
+ // Type options
151
+ text: Type.Optional(Type.String({ description: "Text for type/press action" })),
152
+ slowly: Type.Optional(Type.Boolean({ description: "Type slowly with delay between chars" })),
153
+ submit: Type.Optional(Type.Boolean({ description: "Press Enter after typing" })),
154
+ // Press options
155
+ key: Type.Optional(Type.String({ description: "Key to press (e.g., 'Enter', 'Tab', 'ArrowDown')" })),
156
+ // Drag options
157
+ startRef: Type.Optional(Type.String({ description: "Start element ref for drag" })),
158
+ endRef: Type.Optional(Type.String({ description: "End element ref for drag" })),
159
+ // Select options
160
+ values: Type.Optional(Type.Array(Type.String(), { description: "Values to select in dropdown" })),
161
+ // Fill options
162
+ fields: Type.Optional(Type.Array(Type.Object({
163
+ ref: Type.String({ description: "Element ref" }),
164
+ type: Type.String({ description: "Field type: text, checkbox, radio" }),
165
+ value: Type.Union([Type.String(), Type.Boolean(), Type.Number()], { description: "Value to fill" }),
166
+ }), { description: "Form fields to fill" })),
167
+ // Screenshot options
168
+ fullPage: Type.Optional(Type.Boolean({ description: "Take full page screenshot" })),
169
+ // Scroll options
170
+ direction: Type.Optional(Type.String({ description: "Scroll direction: up, down, left, right" })),
171
+ amount: Type.Optional(Type.Number({ description: "Scroll amount in pixels" })),
172
+ // Wait options
173
+ waitFor: Type.Optional(Type.String({ description: "Wait condition: selector, text, textGone, timeout, load, network, url" })),
174
+ value: Type.Optional(Type.String({ description: "Value for wait condition" })),
175
+ // Evaluate options
176
+ code: Type.Optional(Type.String({ description: "JavaScript code for evaluate action" })),
177
+ // General options
178
+ headless: Type.Optional(Type.Boolean({ description: "Run browser headless (default: true)" })),
179
+ timeout: Type.Optional(Type.Number({ description: "Timeout in milliseconds (default: 30000)" })),
180
+ }),
181
+ execute: async (_toolCallId, args) => {
182
+ const params = args;
183
+ const action = readStringParam(params, "action", { required: true });
184
+ const timeout = readNumberParam(params, "timeout", { min: 1000, max: 120000 }) ?? 30000;
185
+ try {
186
+ switch (action) {
187
+ case "start":
188
+ return await startBrowser(params);
189
+ case "stop":
190
+ return await stopBrowser();
191
+ case "navigate":
192
+ return await navigateTo(params, timeout);
193
+ case "screenshot":
194
+ return await takeScreenshot(params, timeout);
195
+ case "snapshot":
196
+ return await getSnapshot(params, timeout);
197
+ case "click":
198
+ return await clickElement(params, timeout);
199
+ case "type":
200
+ return await typeText(params, timeout);
201
+ case "hover":
202
+ return await hoverElement(params, timeout);
203
+ case "drag":
204
+ return await dragElement(params, timeout);
205
+ case "press":
206
+ return await pressKey(params);
207
+ case "select":
208
+ return await selectOption(params, timeout);
209
+ case "scroll":
210
+ return await scrollPage(params, timeout);
211
+ case "evaluate":
212
+ return await evaluateScript(params, timeout);
213
+ case "wait":
214
+ return await waitFor(params, timeout);
215
+ case "fill":
216
+ return await fillForm(params, timeout);
217
+ default:
218
+ return errorResult(`Unknown action: ${action}`);
219
+ }
220
+ }
221
+ catch (error) {
222
+ return errorResult(error instanceof Error ? error.message : String(error));
223
+ }
224
+ },
225
+ };
226
+ }
227
+ /** 启动浏览器 */
228
+ async function startBrowser(params) {
229
+ if (browserSession) {
230
+ return jsonResult({ status: "already_running", message: "Browser is already running" });
231
+ }
232
+ const headless = readBooleanParam(params, "headless") ?? true;
233
+ const playwright = await getPlaywright();
234
+ try {
235
+ const browser = await playwright.chromium.launch({
236
+ headless,
237
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
238
+ });
239
+ const context = await browser.newContext({
240
+ viewport: { width: 1280, height: 720 },
241
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
242
+ });
243
+ const page = await context.newPage();
244
+ browserSession = {
245
+ browser,
246
+ context,
247
+ page,
248
+ refs: new Map(),
249
+ refsMode: "role",
250
+ };
251
+ return jsonResult({
252
+ status: "started",
253
+ headless,
254
+ viewport: { width: 1280, height: 720 },
255
+ hint: "Use 'snapshot' action to get element refs for interaction",
256
+ });
257
+ }
258
+ catch (error) {
259
+ return errorResult(`Failed to start browser: ${error instanceof Error ? error.message : String(error)}`);
260
+ }
261
+ }
262
+ /** 停止浏览器 */
263
+ async function stopBrowser() {
264
+ if (!browserSession) {
265
+ return jsonResult({ status: "not_running", message: "Browser is not running" });
266
+ }
267
+ try {
268
+ await browserSession.browser.close();
269
+ browserSession = null;
270
+ return jsonResult({ status: "stopped" });
271
+ }
272
+ catch (error) {
273
+ browserSession = null;
274
+ return errorResult(`Error closing browser: ${error instanceof Error ? error.message : String(error)}`);
275
+ }
276
+ }
277
+ /** 导航到 URL */
278
+ async function navigateTo(params, timeout) {
279
+ const session = await getBrowserSession();
280
+ const url = readStringParam(params, "url", { required: true });
281
+ try {
282
+ new URL(url); // 验证 URL
283
+ }
284
+ catch {
285
+ return errorResult(`Invalid URL: ${url}`);
286
+ }
287
+ const page = session.page;
288
+ await page.goto(url, { timeout, waitUntil: "domcontentloaded" });
289
+ // 清除旧的 refs
290
+ session.refs.clear();
291
+ return jsonResult({
292
+ status: "navigated",
293
+ url: page.url(),
294
+ title: await page.title(),
295
+ hint: "Use 'snapshot' action to get element refs for interaction",
296
+ });
297
+ }
298
+ /** 截图 */
299
+ async function takeScreenshot(params, timeout) {
300
+ const session = await getBrowserSession();
301
+ const fullPage = readBooleanParam(params, "fullPage") ?? false;
302
+ const ref = readStringParam(params, "ref");
303
+ const selector = readStringParam(params, "selector");
304
+ const page = session.page;
305
+ let buffer;
306
+ if (ref) {
307
+ // 截取特定 ref 元素
308
+ const locator = getRefLocator(page, ref, session);
309
+ buffer = await locator.screenshot({ type: "png", timeout });
310
+ }
311
+ else if (selector) {
312
+ // 截取 CSS 选择器匹配的元素
313
+ const locator = page.locator(selector).first();
314
+ buffer = await locator.screenshot({ type: "png", timeout });
315
+ }
316
+ else {
317
+ // 全页面或视口截图
318
+ buffer = await page.screenshot({
319
+ fullPage,
320
+ type: "png",
321
+ });
322
+ }
323
+ const base64 = buffer.toString("base64");
324
+ return jsonResult({
325
+ status: "screenshot_taken",
326
+ fullPage,
327
+ size: buffer.length,
328
+ element: ref || selector || undefined,
329
+ base64: base64.slice(0, 100) + "...",
330
+ dataUrl: `data:image/png;base64,${base64}`,
331
+ });
332
+ }
333
+ /** 获取页面快照,包含元素引用 */
334
+ async function getSnapshot(params, timeout) {
335
+ const session = await getBrowserSession();
336
+ const page = session.page;
337
+ const title = await page.title();
338
+ const url = page.url();
339
+ // 尝试使用 Playwright 的 ariaSnapshot API
340
+ let ariaSnapshot = "";
341
+ try {
342
+ ariaSnapshot = await page.locator("body").ariaSnapshot();
343
+ }
344
+ catch {
345
+ // 如果不支持,使用传统方式
346
+ }
347
+ // 解析 aria 快照,生成元素引用
348
+ if (ariaSnapshot) {
349
+ const refs = parseAriaSnapshot(ariaSnapshot);
350
+ session.refs = refs;
351
+ session.refsMode = "role";
352
+ // 生成带 ref 的快照
353
+ const refSnapshot = generateRefSnapshot(refs);
354
+ return jsonResult({
355
+ status: "snapshot",
356
+ url,
357
+ title,
358
+ elementsCount: refs.size,
359
+ elements: refSnapshot,
360
+ ariaSnapshot: ariaSnapshot.length > 8000 ? ariaSnapshot.slice(0, 8000) + "\n...[truncated]" : ariaSnapshot,
361
+ hint: "Use refs like 'e1', 'e2' in click, type, hover actions",
362
+ });
363
+ }
364
+ // 传统方式:提取页面内容
365
+ const content = await page.evaluate(`
366
+ (() => {
367
+ const scripts = document.querySelectorAll("script, style, noscript");
368
+ scripts.forEach(el => el.remove());
369
+ return document.body?.innerText ?? "";
370
+ })()
371
+ `);
372
+ // 获取可交互元素
373
+ const interactiveElements = await page.evaluate(`
374
+ (() => {
375
+ const elements = [];
376
+ const selectors = 'a, button, input, textarea, select, [role="button"], [role="link"], [role="textbox"], [role="checkbox"], [role="radio"], [onclick]';
377
+ const els = document.querySelectorAll(selectors);
378
+ let index = 1;
379
+ els.forEach(el => {
380
+ if (index > 50) return;
381
+ const rect = el.getBoundingClientRect();
382
+ if (rect.width === 0 || rect.height === 0) return;
383
+ elements.push({
384
+ ref: 'e' + index,
385
+ tag: el.tagName.toLowerCase(),
386
+ type: el.type || el.getAttribute('role') || '',
387
+ text: (el.innerText || el.value || el.placeholder || el.getAttribute('aria-label') || '').slice(0, 50),
388
+ name: el.name || '',
389
+ id: el.id || '',
390
+ });
391
+ index++;
392
+ });
393
+ return elements;
394
+ })()
395
+ `);
396
+ // 更新 refs
397
+ session.refs.clear();
398
+ for (const el of interactiveElements) {
399
+ session.refs.set(el.ref, {
400
+ role: el.tag === "a" ? "link" : el.tag === "button" ? "button" : el.type || el.tag,
401
+ name: el.text || el.name || el.id || undefined,
402
+ });
403
+ }
404
+ const contentStr = String(content || "");
405
+ const truncatedContent = contentStr.length > 5000 ? contentStr.slice(0, 5000) + "...[truncated]" : contentStr;
406
+ return jsonResult({
407
+ status: "snapshot",
408
+ url,
409
+ title,
410
+ contentLength: contentStr.length,
411
+ content: truncatedContent,
412
+ elements: interactiveElements,
413
+ elementsCount: interactiveElements.length,
414
+ hint: "Use refs like 'e1', 'e2' in click, type, hover actions",
415
+ });
416
+ }
417
+ /** 点击元素 */
418
+ async function clickElement(params, timeout) {
419
+ const session = await getBrowserSession();
420
+ const ref = readStringParam(params, "ref");
421
+ const selector = readStringParam(params, "selector");
422
+ const doubleClick = readBooleanParam(params, "doubleClick") ?? false;
423
+ const button = readStringParam(params, "button");
424
+ const modifiers = params.modifiers;
425
+ if (!ref && !selector) {
426
+ return errorResult("Either 'ref' or 'selector' is required");
427
+ }
428
+ const page = session.page;
429
+ const locator = ref ? getRefLocator(page, ref, session) : page.locator(selector);
430
+ const clickOptions = {
431
+ timeout,
432
+ button: button || "left",
433
+ };
434
+ if (modifiers?.length) {
435
+ clickOptions.modifiers = modifiers;
436
+ }
437
+ if (doubleClick) {
438
+ await locator.dblclick(clickOptions);
439
+ }
440
+ else {
441
+ await locator.click(clickOptions);
442
+ }
443
+ return jsonResult({
444
+ status: "clicked",
445
+ element: ref || selector,
446
+ doubleClick,
447
+ button: button || "left",
448
+ });
449
+ }
450
+ /** 输入文本 */
451
+ async function typeText(params, timeout) {
452
+ const session = await getBrowserSession();
453
+ const ref = readStringParam(params, "ref");
454
+ const selector = readStringParam(params, "selector");
455
+ const text = readStringParam(params, "text", { required: true });
456
+ const slowly = readBooleanParam(params, "slowly") ?? false;
457
+ const submit = readBooleanParam(params, "submit") ?? false;
458
+ if (!ref && !selector) {
459
+ return errorResult("Either 'ref' or 'selector' is required");
460
+ }
461
+ const page = session.page;
462
+ const locator = ref ? getRefLocator(page, ref, session) : page.locator(selector);
463
+ if (slowly) {
464
+ // 先点击聚焦,然后逐字输入
465
+ await locator.click({ timeout });
466
+ await locator.type(text, { timeout, delay: 75 });
467
+ }
468
+ else {
469
+ // 直接填充
470
+ await locator.fill(text, { timeout });
471
+ }
472
+ if (submit) {
473
+ await locator.press("Enter", { timeout });
474
+ }
475
+ return jsonResult({
476
+ status: "typed",
477
+ element: ref || selector,
478
+ text: text.slice(0, 50) + (text.length > 50 ? "..." : ""),
479
+ slowly,
480
+ submitted: submit,
481
+ });
482
+ }
483
+ /** 悬停元素 */
484
+ async function hoverElement(params, timeout) {
485
+ const session = await getBrowserSession();
486
+ const ref = readStringParam(params, "ref");
487
+ const selector = readStringParam(params, "selector");
488
+ if (!ref && !selector) {
489
+ return errorResult("Either 'ref' or 'selector' is required");
490
+ }
491
+ const page = session.page;
492
+ const locator = ref ? getRefLocator(page, ref, session) : page.locator(selector);
493
+ await locator.hover({ timeout });
494
+ return jsonResult({
495
+ status: "hovered",
496
+ element: ref || selector,
497
+ });
498
+ }
499
+ /** 拖拽元素 */
500
+ async function dragElement(params, timeout) {
501
+ const session = await getBrowserSession();
502
+ const startRef = readStringParam(params, "startRef", { required: true });
503
+ const endRef = readStringParam(params, "endRef", { required: true });
504
+ const page = session.page;
505
+ const startLocator = getRefLocator(page, startRef, session);
506
+ const endLocator = getRefLocator(page, endRef, session);
507
+ await startLocator.dragTo(endLocator, { timeout });
508
+ return jsonResult({
509
+ status: "dragged",
510
+ from: startRef,
511
+ to: endRef,
512
+ });
513
+ }
514
+ /** 按键 */
515
+ async function pressKey(params) {
516
+ const session = await getBrowserSession();
517
+ const key = readStringParam(params, "key", { required: true });
518
+ const page = session.page;
519
+ await page.keyboard.press(key);
520
+ return jsonResult({
521
+ status: "pressed",
522
+ key,
523
+ });
524
+ }
525
+ /** 选择下拉选项 */
526
+ async function selectOption(params, timeout) {
527
+ const session = await getBrowserSession();
528
+ const ref = readStringParam(params, "ref");
529
+ const selector = readStringParam(params, "selector");
530
+ const values = params.values;
531
+ if (!ref && !selector) {
532
+ return errorResult("Either 'ref' or 'selector' is required");
533
+ }
534
+ if (!values?.length) {
535
+ return errorResult("'values' array is required");
536
+ }
537
+ const page = session.page;
538
+ const locator = ref ? getRefLocator(page, ref, session) : page.locator(selector);
539
+ await locator.selectOption(values, { timeout });
540
+ return jsonResult({
541
+ status: "selected",
542
+ element: ref || selector,
543
+ values,
544
+ });
545
+ }
546
+ /** 滚动页面 */
547
+ async function scrollPage(params, timeout) {
548
+ const session = await getBrowserSession();
549
+ const ref = readStringParam(params, "ref");
550
+ const direction = readStringParam(params, "direction") ?? "down";
551
+ const amount = readNumberParam(params, "amount", { min: 100, max: 10000 }) ?? 500;
552
+ const page = session.page;
553
+ // 如果指定了 ref,滚动到该元素
554
+ if (ref) {
555
+ const locator = getRefLocator(page, ref, session);
556
+ await locator.scrollIntoViewIfNeeded({ timeout });
557
+ return jsonResult({
558
+ status: "scrolled",
559
+ element: ref,
560
+ });
561
+ }
562
+ // 页面滚动
563
+ let deltaX = 0;
564
+ let deltaY = 0;
565
+ switch (direction) {
566
+ case "up":
567
+ deltaY = -amount;
568
+ break;
569
+ case "down":
570
+ deltaY = amount;
571
+ break;
572
+ case "left":
573
+ deltaX = -amount;
574
+ break;
575
+ case "right":
576
+ deltaX = amount;
577
+ break;
578
+ }
579
+ await page.evaluate(`window.scrollBy(${deltaX}, ${deltaY})`);
580
+ return jsonResult({
581
+ status: "scrolled",
582
+ direction,
583
+ amount,
584
+ });
585
+ }
586
+ /** 执行 JavaScript */
587
+ async function evaluateScript(params, timeout) {
588
+ const session = await getBrowserSession();
589
+ const code = readStringParam(params, "code", { required: true });
590
+ const ref = readStringParam(params, "ref");
591
+ const page = session.page;
592
+ let result;
593
+ if (ref) {
594
+ // 在特定元素上执行
595
+ const locator = getRefLocator(page, ref, session);
596
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
597
+ result = await locator.evaluate((el, code) => {
598
+ // eslint-disable-next-line no-eval
599
+ return eval(code);
600
+ }, code);
601
+ }
602
+ else {
603
+ result = await page.evaluate(code);
604
+ }
605
+ return jsonResult({
606
+ status: "evaluated",
607
+ element: ref || undefined,
608
+ result: JSON.stringify(result, null, 2).slice(0, 2000),
609
+ });
610
+ }
611
+ /** 等待条件 */
612
+ async function waitFor(params, timeout) {
613
+ const session = await getBrowserSession();
614
+ const waitForCondition = readStringParam(params, "waitFor") ?? "timeout";
615
+ const value = readStringParam(params, "value");
616
+ const page = session.page;
617
+ switch (waitForCondition) {
618
+ case "selector":
619
+ if (!value)
620
+ return errorResult("Selector value required");
621
+ await page.waitForSelector(value, { timeout });
622
+ return jsonResult({ status: "waited", condition: "selector", selector: value });
623
+ case "text": {
624
+ if (!value)
625
+ return errorResult("Text value required");
626
+ await page.getByText(value).first().waitFor({ state: "visible", timeout });
627
+ return jsonResult({ status: "waited", condition: "text", text: value });
628
+ }
629
+ case "textGone": {
630
+ if (!value)
631
+ return errorResult("Text value required");
632
+ await page.getByText(value).first().waitFor({ state: "hidden", timeout });
633
+ return jsonResult({ status: "waited", condition: "textGone", text: value });
634
+ }
635
+ case "timeout": {
636
+ const ms = readNumberParam(params, "amount", { min: 100, max: 30000 }) ?? 1000;
637
+ await page.waitForTimeout(ms);
638
+ return jsonResult({ status: "waited", condition: "timeout", ms });
639
+ }
640
+ case "load":
641
+ await page.waitForLoadState("load", { timeout });
642
+ return jsonResult({ status: "waited", condition: "load" });
643
+ case "network":
644
+ await page.waitForLoadState("networkidle", { timeout });
645
+ return jsonResult({ status: "waited", condition: "networkidle" });
646
+ case "url":
647
+ if (!value)
648
+ return errorResult("URL pattern required");
649
+ await page.waitForURL(value, { timeout });
650
+ return jsonResult({ status: "waited", condition: "url", url: value });
651
+ default:
652
+ return errorResult(`Unknown wait condition: ${waitForCondition}`);
653
+ }
654
+ }
655
+ /** 批量填充表单 */
656
+ async function fillForm(params, timeout) {
657
+ const session = await getBrowserSession();
658
+ const fields = params.fields;
659
+ if (!fields?.length) {
660
+ return errorResult("'fields' array is required");
661
+ }
662
+ const page = session.page;
663
+ const results = [];
664
+ for (const field of fields) {
665
+ const ref = field.ref?.trim();
666
+ const type = field.type?.trim();
667
+ const rawValue = field.value;
668
+ if (!ref || !type) {
669
+ results.push({ ref: ref || "unknown", status: "skipped" });
670
+ continue;
671
+ }
672
+ const locator = getRefLocator(page, ref, session);
673
+ try {
674
+ if (type === "checkbox" || type === "radio") {
675
+ const checked = rawValue === true || rawValue === 1 || rawValue === "1" || rawValue === "true";
676
+ await locator.setChecked(checked, { timeout });
677
+ }
678
+ else {
679
+ const value = typeof rawValue === "string" ? rawValue : String(rawValue);
680
+ await locator.fill(value, { timeout });
681
+ }
682
+ results.push({ ref, status: "filled" });
683
+ }
684
+ catch (error) {
685
+ results.push({ ref, status: `error: ${error instanceof Error ? error.message : String(error)}` });
686
+ }
687
+ }
688
+ return jsonResult({
689
+ status: "form_filled",
690
+ fields: results,
691
+ });
692
+ }
693
+ //# sourceMappingURL=browser.js.map