v0.1.2: 全项目代码审查修复 + 子代理编排器 + 上下文压缩 + 并行工具执行
后端修复: Ollama adapter 移除死代码/修复超时硬编码/iteration硬编码/pullModel无超时/流异常断开补发DONE; SSE解析器支持非字符串arguments; Sandbox fail-closed安全加固; IPC移除未使用变量 新增功能: TaskOrchestrator子任务编排器; DelegateTaskTool委派工具; Agent Loop并行工具执行; 上下文自动压缩; 记忆检索注入System Prompt 前端修复: useAgentStream text_delta thought累积bug; 工具调用状态正确流转; 修复重复stateChange事件; TraceStep.thought正确填充
This commit is contained in:
@@ -1,19 +1,18 @@
|
||||
/**
|
||||
* browser — 浏览器交互工具集(9 个函数)
|
||||
* web_browser — 统一浏览器交互工具
|
||||
*
|
||||
* 基于单例 BrowserWindowManager 实现网页加载、截图、JS 执行、
|
||||
* 内容提取、点击、输入、滚动、等待、关闭等操作。
|
||||
* 将 9 个浏览器操作合并为单个工具,通过 `action` 参数路由:
|
||||
* 1. open — 打开 URL
|
||||
* 2. screenshot — 截图(视口/元素/全页三模式)
|
||||
* 3. evaluate — 执行 JavaScript
|
||||
* 4. extract — 提取页面文本与链接
|
||||
* 5. click — 点击元素
|
||||
* 6. type — 输入文本
|
||||
* 7. scroll — 滚动页面
|
||||
* 8. wait — 等待条件
|
||||
* 9. close — 关闭窗口
|
||||
*
|
||||
* 工具列表:
|
||||
* 1. browser_open — 打开 URL
|
||||
* 2. browser_screenshot — 截图(视口/元素/全页三模式)
|
||||
* 3. browser_evaluate — 执行 JavaScript
|
||||
* 4. browser_extract — 提取页面文本与链接
|
||||
* 5. browser_click — 点击元素
|
||||
* 6. browser_type — 输入文本
|
||||
* 7. browser_scroll — 滚动页面
|
||||
* 8. browser_wait — 等待条件
|
||||
* 9. browser_close — 关闭窗口
|
||||
* 基于单例 BrowserWindowManager 实现。
|
||||
*
|
||||
* @see docs/Agent网络工具通用设计-v2.md — 第 4 章 browser 浏览器设计
|
||||
*/
|
||||
@@ -35,354 +34,241 @@ function getManager(): BrowserWindowManager {
|
||||
return managerInstance;
|
||||
}
|
||||
|
||||
/** 获取共享浏览器管理器单例(供 web_fetch 等工具复用) */
|
||||
export function getBrowserManager(): BrowserWindowManager {
|
||||
return getManager();
|
||||
}
|
||||
|
||||
/** 应用退出时清理(由 main.ts 调用) */
|
||||
export function cleanupBrowser(): void {
|
||||
BrowserWindowManager.cleanup(managerInstance);
|
||||
managerInstance = null;
|
||||
}
|
||||
|
||||
// ===== 1. browser_open =====
|
||||
// ===== WebBrowserTool =====
|
||||
|
||||
export class BrowserOpenTool implements IMetonaTool {
|
||||
export class WebBrowserTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'browser_open',
|
||||
description: 'Open a URL in a hidden browser window. Loads the page and optionally waits for a selector to appear. Returns the page title and URL.',
|
||||
name: 'web_browser',
|
||||
description: [
|
||||
'Control a hidden browser window to navigate, inspect, and interact with web pages.',
|
||||
'Use the "action" parameter to specify the operation:',
|
||||
' open — Load a URL; optionally wait for a CSS selector before returning. Returns page title and URL.',
|
||||
' screenshot — Capture the viewport (default), full page, or a specific element by CSS selector. Returns base64 PNG.',
|
||||
' evaluate — Execute JavaScript in the page context. Returns the result of the last expression.',
|
||||
' extract — Extract clean text and up to 50 links from the page or a specific element.',
|
||||
' click — Click an element by CSS selector. Scrolls into view first. Can optionally wait for the selector.',
|
||||
' type — Type text into an input element. Optionally clear first and submit the form.',
|
||||
' scroll — Scroll the page: down/up (500px), top/bottom, or scroll to an element by selector.',
|
||||
' wait — Wait for a CSS selector to appear (with timeout) or a fixed duration.',
|
||||
' close — Close the browser window and release resources.',
|
||||
].join('\n'),
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'Target URL (http/https only)' },
|
||||
wait_selector: { type: 'string', description: 'CSS selector to wait for before returning (optional)' },
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['open', 'screenshot', 'evaluate', 'extract', 'click', 'type', 'scroll', 'wait', 'close'],
|
||||
description: 'Browser operation to perform',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: '[open] Target URL (http/https only)',
|
||||
},
|
||||
wait_selector: {
|
||||
type: 'string',
|
||||
description: '[open] CSS selector to wait for before returning (optional)',
|
||||
},
|
||||
script: {
|
||||
type: 'string',
|
||||
description: '[evaluate] JavaScript code to execute (must return a value)',
|
||||
},
|
||||
selector: {
|
||||
type: 'string',
|
||||
description: '[screenshot|extract|click|type|scroll] CSS selector of the target element',
|
||||
},
|
||||
full_page: {
|
||||
type: 'boolean',
|
||||
description: '[screenshot] Capture entire scrollable page (default false)',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
description: '[type] Text to type into the element',
|
||||
},
|
||||
clear: {
|
||||
type: 'boolean',
|
||||
description: '[type] Clear the field before typing (default true)',
|
||||
},
|
||||
submit: {
|
||||
type: 'boolean',
|
||||
description: '[type] Submit the form after typing (default false)',
|
||||
},
|
||||
direction: {
|
||||
type: 'string',
|
||||
enum: ['down', 'up', 'top', 'bottom'],
|
||||
description: '[scroll] Scroll direction (default down). Ignored if selector is provided.',
|
||||
},
|
||||
wait: {
|
||||
type: 'boolean',
|
||||
description: '[click] Wait for selector to appear before clicking (default false)',
|
||||
},
|
||||
time_ms: {
|
||||
type: 'number',
|
||||
description: '[wait] Fixed wait time in milliseconds (default 1000). Used as timeout when selector is also provided.',
|
||||
},
|
||||
},
|
||||
required: ['url'],
|
||||
required: ['action'],
|
||||
},
|
||||
category: MetonaToolCategory.NETWORK,
|
||||
riskLevel: MetonaRiskLevel.MEDIUM,
|
||||
riskLevel: MetonaRiskLevel.HIGH,
|
||||
requiresPermission: true,
|
||||
timeoutMs: 60_000,
|
||||
};
|
||||
|
||||
async execute(args: Record<string, unknown>, _context: ToolExecutionContext): Promise<unknown> {
|
||||
const url = args.url as string;
|
||||
if (!url || !/^https?:\/\//i.test(url)) {
|
||||
return { success: false, error: 'URL must start with http:// or https://' };
|
||||
const action = args.action as string;
|
||||
if (!action) {
|
||||
return { success: false, error: 'Missing required parameter: action' };
|
||||
}
|
||||
|
||||
const waitSelector = args.wait_selector as string | undefined;
|
||||
logTool('browser_open', `Opening: ${url}`);
|
||||
logTool('web_browser', `action=${action}`);
|
||||
|
||||
try {
|
||||
const result = await getManager().open({ url, waitSelector });
|
||||
return { success: true, ...result };
|
||||
} catch (err) {
|
||||
return { success: false, error: (err as Error).message };
|
||||
switch (action) {
|
||||
// ===== open =====
|
||||
case 'open': {
|
||||
const url = args.url as string;
|
||||
if (!url || !/^https?:\/\//i.test(url)) {
|
||||
return { success: false, error: 'URL must start with http:// or https://' };
|
||||
}
|
||||
const waitSelector = args.wait_selector as string | undefined;
|
||||
try {
|
||||
const result = await getManager().open({ url, waitSelector });
|
||||
return { success: true, action, ...result };
|
||||
} catch (err) {
|
||||
return { success: false, action, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== screenshot =====
|
||||
case 'screenshot': {
|
||||
const fullPage = (args.full_page as boolean) ?? false;
|
||||
const selector = args.selector as string | undefined;
|
||||
try {
|
||||
const result = await getManager().screenshot({ fullPage, selector });
|
||||
return {
|
||||
success: true,
|
||||
action,
|
||||
image: result.data,
|
||||
width: result.width,
|
||||
height: result.height,
|
||||
mime_type: 'image/png',
|
||||
};
|
||||
} catch (err) {
|
||||
return { success: false, action, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== evaluate =====
|
||||
case 'evaluate': {
|
||||
const script = args.script as string;
|
||||
if (!script) {
|
||||
return { success: false, action, error: 'No script provided' };
|
||||
}
|
||||
try {
|
||||
const result = await getManager().evaluate(script);
|
||||
return { success: true, action, result };
|
||||
} catch (err) {
|
||||
return { success: false, action, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== extract =====
|
||||
case 'extract': {
|
||||
const selector = args.selector as string | undefined;
|
||||
try {
|
||||
const result = await getManager().extract(selector);
|
||||
return {
|
||||
success: true,
|
||||
action,
|
||||
text: result.text,
|
||||
links: result.links,
|
||||
link_count: result.links.length,
|
||||
};
|
||||
} catch (err) {
|
||||
return { success: false, action, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== click =====
|
||||
case 'click': {
|
||||
const selector = args.selector as string;
|
||||
const wait = (args.wait as boolean) ?? false;
|
||||
if (!selector) {
|
||||
return { success: false, action, error: 'No selector provided' };
|
||||
}
|
||||
try {
|
||||
await getManager().click(selector, wait);
|
||||
return { success: true, action, selector, clicked: true };
|
||||
} catch (err) {
|
||||
return { success: false, action, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== type =====
|
||||
case 'type': {
|
||||
const selector = args.selector as string;
|
||||
const text = args.text as string;
|
||||
const clear = (args.clear as boolean) ?? true;
|
||||
const submit = (args.submit as boolean) ?? false;
|
||||
if (!selector) {
|
||||
return { success: false, action, error: 'No selector provided' };
|
||||
}
|
||||
if (text === undefined || text === null) {
|
||||
return { success: false, action, error: 'No text provided' };
|
||||
}
|
||||
try {
|
||||
await getManager().type(selector, text, { clear, submit });
|
||||
return { success: true, action, selector, typed: text.length };
|
||||
} catch (err) {
|
||||
return { success: false, action, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== scroll =====
|
||||
case 'scroll': {
|
||||
const direction = (args.direction as string) ?? 'down';
|
||||
const selector = args.selector as string | undefined;
|
||||
try {
|
||||
await getManager().scroll({
|
||||
direction: direction as 'down' | 'up' | 'top' | 'bottom',
|
||||
selector,
|
||||
});
|
||||
return { success: true, action, direction, selector };
|
||||
} catch (err) {
|
||||
return { success: false, action, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== wait =====
|
||||
case 'wait': {
|
||||
const selector = args.selector as string | undefined;
|
||||
const timeMs = (args.time_ms as number) ?? 1_000;
|
||||
try {
|
||||
await getManager().wait({ selector, timeMs });
|
||||
return { success: true, action, waited_for: selector ?? `${timeMs}ms` };
|
||||
} catch (err) {
|
||||
return { success: false, action, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== close =====
|
||||
case 'close': {
|
||||
getManager().close();
|
||||
return { success: true, action, closed: true };
|
||||
}
|
||||
|
||||
default:
|
||||
return { success: false, error: `Unknown action: ${action}` };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 2. browser_screenshot =====
|
||||
|
||||
export class BrowserScreenshotTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'browser_screenshot',
|
||||
description: 'Capture a screenshot of the current browser window. Three modes: viewport (default), full page, or specific element by CSS selector. Returns base64-encoded PNG image.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
full_page: { type: 'boolean', description: 'Capture entire scrollable page (default false)' },
|
||||
selector: { type: 'string', description: 'CSS selector to capture a specific element (overrides full_page)' },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
category: MetonaToolCategory.NETWORK,
|
||||
riskLevel: MetonaRiskLevel.LOW,
|
||||
requiresPermission: false,
|
||||
timeoutMs: 30_000,
|
||||
};
|
||||
|
||||
async execute(args: Record<string, unknown>, _context: ToolExecutionContext): Promise<unknown> {
|
||||
const fullPage = (args.full_page as boolean) ?? false;
|
||||
const selector = args.selector as string | undefined;
|
||||
|
||||
logTool('browser_screenshot', `full_page=${fullPage}, selector=${selector ?? 'none'}`);
|
||||
|
||||
try {
|
||||
const result = await getManager().screenshot({ fullPage, selector });
|
||||
return {
|
||||
success: true,
|
||||
image: result.data,
|
||||
width: result.width,
|
||||
height: result.height,
|
||||
mime_type: 'image/png',
|
||||
};
|
||||
} catch (err) {
|
||||
return { success: false, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 3. browser_evaluate =====
|
||||
|
||||
export class BrowserEvaluateTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'browser_evaluate',
|
||||
description: 'Execute JavaScript code in the current browser page context. Returns the result of the last expression. Use for data extraction, DOM queries, or triggering page actions.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
script: { type: 'string', description: 'JavaScript code to execute (must return a value)' },
|
||||
},
|
||||
required: ['script'],
|
||||
},
|
||||
category: MetonaToolCategory.NETWORK,
|
||||
riskLevel: MetonaRiskLevel.HIGH,
|
||||
requiresPermission: true,
|
||||
timeoutMs: 30_000,
|
||||
};
|
||||
|
||||
async execute(args: Record<string, unknown>, _context: ToolExecutionContext): Promise<unknown> {
|
||||
const script = args.script as string;
|
||||
if (!script) {
|
||||
return { success: false, error: 'No script provided' };
|
||||
}
|
||||
|
||||
logTool('browser_evaluate', `Executing ${script.length} chars of JS`);
|
||||
|
||||
try {
|
||||
const result = await getManager().evaluate(script);
|
||||
return { success: true, result };
|
||||
} catch (err) {
|
||||
return { success: false, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 4. browser_extract =====
|
||||
|
||||
export class BrowserExtractTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'browser_extract',
|
||||
description: 'Extract text content and links from the current browser page. Optionally target a specific element by CSS selector. Strips scripts, styles, and navigation elements. Returns clean text and up to 50 links.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
selector: { type: 'string', description: 'CSS selector to extract from (default: entire body)' },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
category: MetonaToolCategory.NETWORK,
|
||||
riskLevel: MetonaRiskLevel.LOW,
|
||||
requiresPermission: false,
|
||||
timeoutMs: 30_000,
|
||||
};
|
||||
|
||||
async execute(args: Record<string, unknown>, _context: ToolExecutionContext): Promise<unknown> {
|
||||
const selector = args.selector as string | undefined;
|
||||
|
||||
logTool('browser_extract', `selector=${selector ?? 'body'}`);
|
||||
|
||||
try {
|
||||
const result = await getManager().extract(selector);
|
||||
return {
|
||||
success: true,
|
||||
text: result.text,
|
||||
links: result.links,
|
||||
link_count: result.links.length,
|
||||
};
|
||||
} catch (err) {
|
||||
return { success: false, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 5. browser_click =====
|
||||
|
||||
export class BrowserClickTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'browser_click',
|
||||
description: 'Click an element on the page by CSS selector. Scrolls the element into view before clicking. Optionally wait for the selector to appear first.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
selector: { type: 'string', description: 'CSS selector of the element to click' },
|
||||
wait: { type: 'boolean', description: 'Wait for selector to appear before clicking (default false)' },
|
||||
},
|
||||
required: ['selector'],
|
||||
},
|
||||
category: MetonaToolCategory.NETWORK,
|
||||
riskLevel: MetonaRiskLevel.MEDIUM,
|
||||
requiresPermission: true,
|
||||
timeoutMs: 30_000,
|
||||
};
|
||||
|
||||
async execute(args: Record<string, unknown>, _context: ToolExecutionContext): Promise<unknown> {
|
||||
const selector = args.selector as string;
|
||||
const wait = (args.wait as boolean) ?? false;
|
||||
|
||||
if (!selector) {
|
||||
return { success: false, error: 'No selector provided' };
|
||||
}
|
||||
|
||||
logTool('browser_click', `selector=${selector}, wait=${wait}`);
|
||||
|
||||
try {
|
||||
await getManager().click(selector, wait);
|
||||
return { success: true, selector, clicked: true };
|
||||
} catch (err) {
|
||||
return { success: false, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 6. browser_type =====
|
||||
|
||||
export class BrowserTypeTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'browser_type',
|
||||
description: 'Type text into an input element by CSS selector. Focuses the element, optionally clears it first, and dispatches input/change events for React/Vue compatibility. Can optionally submit the form.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
selector: { type: 'string', description: 'CSS selector of the input element' },
|
||||
text: { type: 'string', description: 'Text to type into the element' },
|
||||
clear: { type: 'boolean', description: 'Clear the field before typing (default true)' },
|
||||
submit: { type: 'boolean', description: 'Submit the form after typing (default false)' },
|
||||
},
|
||||
required: ['selector', 'text'],
|
||||
},
|
||||
category: MetonaToolCategory.NETWORK,
|
||||
riskLevel: MetonaRiskLevel.MEDIUM,
|
||||
requiresPermission: true,
|
||||
timeoutMs: 30_000,
|
||||
};
|
||||
|
||||
async execute(args: Record<string, unknown>, _context: ToolExecutionContext): Promise<unknown> {
|
||||
const selector = args.selector as string;
|
||||
const text = args.text as string;
|
||||
const clear = (args.clear as boolean) ?? true;
|
||||
const submit = (args.submit as boolean) ?? false;
|
||||
|
||||
if (!selector) {
|
||||
return { success: false, error: 'No selector provided' };
|
||||
}
|
||||
if (text === undefined || text === null) {
|
||||
return { success: false, error: 'No text provided' };
|
||||
}
|
||||
|
||||
logTool('browser_type', `selector=${selector}, len=${text.length}, submit=${submit}`);
|
||||
|
||||
try {
|
||||
await getManager().type(selector, text, { clear, submit });
|
||||
return { success: true, selector, typed: text.length };
|
||||
} catch (err) {
|
||||
return { success: false, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 7. browser_scroll =====
|
||||
|
||||
export class BrowserScrollTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'browser_scroll',
|
||||
description: 'Scroll the browser page. Modes: down/up (500px increment), top/bottom (absolute), or scroll to a specific element by CSS selector.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
direction: { type: 'string', description: 'Scroll direction: down, up, top, bottom (default down)' },
|
||||
selector: { type: 'string', description: 'CSS selector to scroll to (overrides direction)' },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
category: MetonaToolCategory.NETWORK,
|
||||
riskLevel: MetonaRiskLevel.LOW,
|
||||
requiresPermission: false,
|
||||
timeoutMs: 15_000,
|
||||
};
|
||||
|
||||
async execute(args: Record<string, unknown>, _context: ToolExecutionContext): Promise<unknown> {
|
||||
const direction = (args.direction as string) ?? 'down';
|
||||
const selector = args.selector as string | undefined;
|
||||
|
||||
logTool('browser_scroll', `direction=${direction}, selector=${selector ?? 'none'}`);
|
||||
|
||||
try {
|
||||
await getManager().scroll({ direction: direction as 'down' | 'up' | 'top' | 'bottom', selector });
|
||||
return { success: true, direction, selector };
|
||||
} catch (err) {
|
||||
return { success: false, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 8. browser_wait =====
|
||||
|
||||
export class BrowserWaitTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'browser_wait',
|
||||
description: 'Wait for a condition on the browser page. Either wait for a CSS selector to appear (with timeout) or wait for a fixed duration.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
selector: { type: 'string', description: 'CSS selector to wait for (mutually exclusive with time_ms)' },
|
||||
time_ms: { type: 'number', description: 'Fixed wait time in milliseconds (default 1000)' },
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
category: MetonaToolCategory.NETWORK,
|
||||
riskLevel: MetonaRiskLevel.LOW,
|
||||
requiresPermission: false,
|
||||
timeoutMs: 30_000,
|
||||
};
|
||||
|
||||
async execute(args: Record<string, unknown>, _context: ToolExecutionContext): Promise<unknown> {
|
||||
const selector = args.selector as string | undefined;
|
||||
const timeMs = (args.time_ms as number) ?? 1_000;
|
||||
|
||||
logTool('browser_wait', `selector=${selector ?? 'none'}, time_ms=${timeMs}`);
|
||||
|
||||
try {
|
||||
await getManager().wait({ selector, timeMs });
|
||||
return { success: true, waited_for: selector ?? `${timeMs}ms` };
|
||||
} catch (err) {
|
||||
return { success: false, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 9. browser_close =====
|
||||
|
||||
export class BrowserCloseTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'browser_close',
|
||||
description: 'Close the current browser window and release resources. Call this when browser interaction is complete to free memory.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
},
|
||||
category: MetonaToolCategory.NETWORK,
|
||||
riskLevel: MetonaRiskLevel.LOW,
|
||||
requiresPermission: false,
|
||||
timeoutMs: 10_000,
|
||||
};
|
||||
|
||||
async execute(_args: Record<string, unknown>, _context: ToolExecutionContext): Promise<unknown> {
|
||||
logTool('browser_close', 'Closing browser window');
|
||||
getManager().close();
|
||||
return { success: true, closed: true };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 导出所有 Browser 工具 =====
|
||||
|
||||
export const browserTools: IMetonaTool[] = [
|
||||
new BrowserOpenTool(),
|
||||
new BrowserScreenshotTool(),
|
||||
new BrowserEvaluateTool(),
|
||||
new BrowserExtractTool(),
|
||||
new BrowserClickTool(),
|
||||
new BrowserTypeTool(),
|
||||
new BrowserScrollTool(),
|
||||
new BrowserWaitTool(),
|
||||
new BrowserCloseTool(),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user