@anweat/dsh-browser 0.1.15 → 0.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +272 -75
- package/assets/skills/dsh-browser/SKILL.md +120 -0
- package/assets/skills/dsh-browser/references/develop.md +48 -0
- package/assets/skills/dsh-browser/references/observe.md +91 -0
- package/assets/skills/dsh-browser/references/opencli.md +25 -0
- package/assets/skills/dsh-browser/references/recipes.md +149 -0
- package/assets/skills/dsh-browser/references/userscripts.md +44 -0
- package/cordis.patch.yml +4 -0
- package/lib/actions/act.d.ts +3 -0
- package/lib/actions/act.js +147 -0
- package/lib/actions/act.js.map +1 -0
- package/lib/actions/automation.d.ts +3 -0
- package/lib/actions/automation.js +356 -0
- package/lib/actions/automation.js.map +1 -0
- package/lib/actions/crawl.d.ts +3 -0
- package/lib/actions/crawl.js +28 -0
- package/lib/actions/crawl.js.map +1 -0
- package/lib/actions/errors.d.ts +53 -0
- package/lib/actions/errors.js +178 -0
- package/lib/actions/errors.js.map +1 -0
- package/lib/actions/index-view.d.ts +42 -0
- package/lib/actions/index-view.js +268 -0
- package/lib/actions/index-view.js.map +1 -0
- package/lib/actions/inspect.d.ts +3 -0
- package/lib/actions/inspect.js +35 -0
- package/lib/actions/inspect.js.map +1 -0
- package/lib/actions/observe.d.ts +3 -0
- package/lib/actions/observe.js +121 -0
- package/lib/actions/observe.js.map +1 -0
- package/lib/actions/opencli.d.ts +3 -0
- package/lib/actions/opencli.js +51 -0
- package/lib/actions/opencli.js.map +1 -0
- package/lib/actions/registry.d.ts +35 -0
- package/lib/actions/registry.js +89 -0
- package/lib/actions/registry.js.map +1 -0
- package/lib/actions/run.d.ts +34 -0
- package/lib/actions/run.js +226 -0
- package/lib/actions/run.js.map +1 -0
- package/lib/actions/runtime.d.ts +3 -0
- package/lib/actions/runtime.js +33 -0
- package/lib/actions/runtime.js.map +1 -0
- package/lib/actions/schema.d.ts +31 -0
- package/lib/actions/schema.js +309 -0
- package/lib/actions/schema.js.map +1 -0
- package/lib/actions/script.d.ts +3 -0
- package/lib/actions/script.js +97 -0
- package/lib/actions/script.js.map +1 -0
- package/lib/actions/shared.d.ts +31 -0
- package/lib/actions/shared.js +45 -0
- package/lib/actions/shared.js.map +1 -0
- package/lib/actions/surface.d.ts +22 -0
- package/lib/actions/surface.js +18 -0
- package/lib/actions/surface.js.map +1 -0
- package/lib/actions/target.d.ts +3 -0
- package/lib/actions/target.js +66 -0
- package/lib/actions/target.js.map +1 -0
- package/lib/actions/types.d.ts +181 -0
- package/lib/actions/types.js +48 -0
- package/lib/actions/types.js.map +1 -0
- package/lib/activation-rules.d.ts +29 -0
- package/lib/activation-rules.js +33 -0
- package/lib/activation-rules.js.map +1 -0
- package/lib/approval-policy.d.ts +40 -2
- package/lib/approval-policy.js +184 -97
- package/lib/approval-policy.js.map +1 -1
- package/lib/automation-assets-rpc.d.ts +8 -2
- package/lib/automation-assets-rpc.js +67 -6
- package/lib/automation-assets-rpc.js.map +1 -1
- package/lib/automation-assets.d.ts +152 -4
- package/lib/automation-assets.js +255 -15
- package/lib/automation-assets.js.map +1 -1
- package/lib/automation-convert.d.ts +23 -0
- package/lib/automation-convert.js +110 -0
- package/lib/automation-convert.js.map +1 -0
- package/lib/automation-development.d.ts +26 -0
- package/lib/automation-development.js +47 -0
- package/lib/automation-development.js.map +1 -1
- package/lib/automation-execution.d.ts +57 -1
- package/lib/automation-execution.js +167 -11
- package/lib/automation-execution.js.map +1 -1
- package/lib/automation-journal.d.ts +92 -0
- package/lib/automation-journal.js +449 -0
- package/lib/automation-journal.js.map +1 -0
- package/lib/automation-v2.d.ts +138 -0
- package/lib/automation-v2.js +631 -0
- package/lib/automation-v2.js.map +1 -0
- package/lib/automation.d.ts +82 -5
- package/lib/automation.js +239 -82
- package/lib/automation.js.map +1 -1
- package/lib/browser-service.d.ts +304 -32
- package/lib/browser-service.js +631 -182
- package/lib/browser-service.js.map +1 -1
- package/lib/client/SettingsCard.js +95 -57
- package/lib/client/SettingsCard.js.map +1 -1
- package/lib/client/asset-editor.d.ts +55 -0
- package/lib/client/asset-editor.js +101 -0
- package/lib/client/asset-editor.js.map +1 -0
- package/lib/client/automation-assets-client.d.ts +99 -2
- package/lib/client/automation-assets-client.js +243 -10
- package/lib/client/automation-assets-client.js.map +1 -1
- package/lib/client/form.d.ts +48 -4
- package/lib/client/form.js +142 -29
- package/lib/client/form.js.map +1 -1
- package/lib/client/index.d.ts +25 -2
- package/lib/client/index.js +5 -1
- package/lib/client/index.js.map +1 -1
- package/lib/client/locales.d.ts +85 -6
- package/lib/client/locales.js +64 -12
- package/lib/client/locales.js.map +1 -1
- package/lib/client/prompts-form.d.ts +67 -0
- package/lib/client/prompts-form.js +140 -0
- package/lib/client/prompts-form.js.map +1 -0
- package/lib/client/prompts-status-client.d.ts +34 -0
- package/lib/client/prompts-status-client.js +53 -0
- package/lib/client/prompts-status-client.js.map +1 -0
- package/lib/client.js +1914 -329
- package/lib/client.js.map +1 -1
- package/lib/config.d.ts +50 -1
- package/lib/config.js +31 -1
- package/lib/config.js.map +1 -1
- package/lib/dialogs.d.ts +37 -0
- package/lib/dialogs.js +73 -0
- package/lib/dialogs.js.map +1 -0
- package/lib/freedom.d.ts +23 -7
- package/lib/freedom.js +48 -59
- package/lib/freedom.js.map +1 -1
- package/lib/index.d.ts +4 -4
- package/lib/index.js +36 -10
- package/lib/index.js.map +1 -1
- package/lib/journal.d.ts +109 -0
- package/lib/journal.js +238 -0
- package/lib/journal.js.map +1 -0
- package/lib/locator.d.ts +82 -0
- package/lib/locator.js +190 -0
- package/lib/locator.js.map +1 -0
- package/lib/observe-script.d.ts +27 -0
- package/lib/observe-script.js +565 -0
- package/lib/observe-script.js.map +1 -0
- package/lib/observe.d.ts +179 -0
- package/lib/observe.js +368 -0
- package/lib/observe.js.map +1 -0
- package/lib/policy-keys.d.ts +30 -0
- package/lib/policy-keys.js +34 -0
- package/lib/policy-keys.js.map +1 -0
- package/lib/prompt-limits.d.ts +30 -0
- package/lib/prompt-limits.js +31 -0
- package/lib/prompt-limits.js.map +1 -0
- package/lib/prompts.d.ts +131 -0
- package/lib/prompts.js +295 -0
- package/lib/prompts.js.map +1 -0
- package/lib/skill.d.ts +100 -0
- package/lib/skill.js +181 -0
- package/lib/skill.js.map +1 -0
- package/lib/tool-defs.d.ts +72 -0
- package/lib/tool-defs.js +67 -0
- package/lib/tool-defs.js.map +1 -0
- package/lib/tools.d.ts +19 -4
- package/lib/tools.js +124 -922
- package/lib/tools.js.map +1 -1
- package/package.json +35 -30
- package/scripts/dump-prompts.mjs +29 -0
- package/scripts/measure-tool-surface.mjs +190 -0
package/lib/tools.js
CHANGED
|
@@ -1,956 +1,158 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Model-facing
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Model-facing tool surfaces for dsh-browser.
|
|
3
|
+
*
|
|
4
|
+
* Every capability is an action in the registry (`src/actions/`). This module
|
|
5
|
+
* only projects that registry into tools:
|
|
6
|
+
*
|
|
7
|
+
* - `indexed` (default): two small tools, `browser_index` for progressive
|
|
8
|
+
* disclosure and `browser_call` to run an action. Constant, tiny context cost.
|
|
9
|
+
* - `flat`: one tool per usable action, named `browser_<group>_<action>`.
|
|
10
|
+
* Every action is described up front; useful for comparison and debugging.
|
|
11
|
+
*
|
|
12
|
+
* Both surfaces dispatch through the same {@link runAction}, so validation,
|
|
13
|
+
* the result envelope, and the error codes are identical.
|
|
5
14
|
* @module dsh-browser/tools
|
|
6
15
|
*/
|
|
7
16
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
|
-
import {
|
|
17
|
+
import { sessionKeyFor } from "./browser-service.js";
|
|
18
|
+
import { configuredBrowserActions } from "./freedom.js";
|
|
9
19
|
import { AutomationDevelopmentService } from "./automation-development.js";
|
|
10
|
-
import {
|
|
20
|
+
import { ACTIONS, CALL_TOOL, INDEX_TOOL, flatToolName, traitsFor } from "./actions/registry.js";
|
|
21
|
+
import { expandParams } from "./actions/schema.js";
|
|
22
|
+
import { renderIndex } from "./actions/index-view.js";
|
|
23
|
+
import { runAction } from "./actions/run.js";
|
|
24
|
+
import { COMPLIANCE_NOTICE } from "./actions/shared.js";
|
|
25
|
+
import { CALL_PARAMETERS, INDEX_PARAMETERS, callDescription, indexDescription } from "./tool-defs.js";
|
|
11
26
|
function sessionId(exec) {
|
|
12
27
|
const value = exec?.agent?.session?.id;
|
|
13
28
|
return typeof value === 'string' && value ? value : 'unknown-session';
|
|
14
29
|
}
|
|
15
|
-
|
|
16
|
-
const raw = JSON.stringify(value);
|
|
17
|
-
return { action, ...asset ? { assetId: asset.id, status: asset.status } : {}, resultJson: raw.slice(0, 100_000), truncated: raw.length > 100_000 };
|
|
18
|
-
}
|
|
19
|
-
function renderState(v) {
|
|
20
|
-
const parts = [];
|
|
21
|
-
if (v.title)
|
|
22
|
-
parts.push('Title: ' + v.title);
|
|
23
|
-
parts.push(v.url);
|
|
24
|
-
parts.push(v.text);
|
|
25
|
-
if (v.screenshotPath)
|
|
26
|
-
parts.push('Screenshot: ' + v.screenshotPath);
|
|
27
|
-
return [{ type: 'text', text: parts.join('\n\n') }];
|
|
28
|
-
}
|
|
29
|
-
function renderScriptResult(value) {
|
|
30
|
-
const v = value;
|
|
31
|
-
return [{ type: 'text', text: [
|
|
32
|
-
`Script: ${v.name} (${v.sha256.slice(0, 12)})`,
|
|
33
|
-
`URL: ${v.url}`,
|
|
34
|
-
`Capabilities: ${v.capabilities.join(', ')}`,
|
|
35
|
-
v.resultJson + (v.truncated ? '\n(result truncated)' : ''),
|
|
36
|
-
].join('\n') }];
|
|
37
|
-
}
|
|
38
|
-
const SCRIPT_RESULT_SCHEMA = {
|
|
30
|
+
const ENVELOPE_SCHEMA = {
|
|
39
31
|
type: 'object',
|
|
40
32
|
additionalProperties: false,
|
|
41
33
|
properties: {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
timeoutMs: { type: 'number' },
|
|
61
|
-
waitMs: { type: 'number' },
|
|
62
|
-
deltaY: { type: 'number' },
|
|
63
|
-
checked: { type: 'boolean' },
|
|
64
|
-
mode: { type: 'string', enum: ['text', 'html', 'links', 'attribute'] },
|
|
65
|
-
attribute: { type: 'string' },
|
|
66
|
-
limit: { type: 'number' },
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
const COMPLIANCE_NOTICE = 'The caller/operator must use this capability according to the target site rules and applicable requirements; dsh-browser only executes the requested browser operation and does not determine whether a particular use is permitted.';
|
|
70
|
-
const FRAME_SCHEMA = {
|
|
71
|
-
type: 'object',
|
|
72
|
-
additionalProperties: false,
|
|
73
|
-
properties: {
|
|
74
|
-
selector: { type: 'string', description: 'CSS selector for an iframe. Use exactly one frame field.' },
|
|
75
|
-
name: { type: 'string', description: 'Frame name. Use exactly one frame field.' },
|
|
76
|
-
url: { type: 'string', description: 'Playwright frame URL/glob. Use exactly one frame field.' },
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
const LOCATOR_SCHEMA = {
|
|
80
|
-
type: 'object',
|
|
81
|
-
additionalProperties: false,
|
|
82
|
-
properties: {
|
|
83
|
-
selector: { type: 'string', description: 'CSS selector. Use exactly one of selector, role, text, or label.' },
|
|
84
|
-
role: { type: 'string', description: 'Accessible role used by Playwright getByRole.' },
|
|
85
|
-
name: { type: 'string', description: 'Optional accessible name; valid only with role.' },
|
|
86
|
-
text: { type: 'string', description: 'Visible text used by Playwright getByText.' },
|
|
87
|
-
label: { type: 'string', description: 'Form label used by Playwright getByLabel.' },
|
|
88
|
-
exact: { type: 'boolean', description: 'Require an exact semantic match. Defaults to false.' },
|
|
89
|
-
frame: FRAME_SCHEMA,
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
const INTERACTIVE_OUTPUT_SCHEMA = {
|
|
93
|
-
type: 'object',
|
|
94
|
-
additionalProperties: false,
|
|
95
|
-
properties: {
|
|
96
|
-
url: { type: 'string', required: true },
|
|
97
|
-
title: { type: 'string' },
|
|
98
|
-
text: { type: 'string', required: true },
|
|
99
|
-
screenshotPath: { type: 'string' },
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
function toolTarget(args, required = true) {
|
|
103
|
-
if (typeof args.selector === 'string' && args.locator === undefined)
|
|
104
|
-
return args.selector;
|
|
105
|
-
if (args.selector === undefined && args.locator && typeof args.locator === 'object')
|
|
106
|
-
return args.locator;
|
|
107
|
-
if (!required && args.selector === undefined && args.locator === undefined)
|
|
108
|
-
return undefined;
|
|
109
|
-
throw new Error('provide exactly one of selector or locator');
|
|
110
|
-
}
|
|
111
|
-
export function registerTools(ctx, config, service, assets) {
|
|
112
|
-
const exposedTools = new Set(configuredBrowserTools(config.automationMode, config.automationAssets, config.enabled));
|
|
113
|
-
const development = assets ? new AutomationDevelopmentService(assets, config.automationAssets) : undefined;
|
|
114
|
-
const register = (tool) => {
|
|
115
|
-
if (exposedTools.has(String(tool.name)))
|
|
116
|
-
ctx.tools.register(tool);
|
|
117
|
-
};
|
|
118
|
-
register(defineTool({
|
|
119
|
-
name: 'browser_automation_search',
|
|
120
|
-
description: '[Experimental] Search reusable browser automations by explicit task keywords and optional domain. Returns only compact metadata, never recipe steps or userscript source.',
|
|
121
|
-
parameters: {
|
|
122
|
-
query: { type: 'string', required: true, description: 'Short task description.' },
|
|
123
|
-
domain: { type: 'string', description: 'Optional target hostname.' },
|
|
124
|
-
status: { type: 'string', enum: ['active', 'draft', 'archived', 'all'], description: 'Asset lifecycle scope. Defaults to active.' },
|
|
125
|
-
kind: { type: 'string', enum: ['recipe', 'userscript'], description: 'Optional asset kind.' },
|
|
126
|
-
},
|
|
127
|
-
output: {
|
|
128
|
-
schema: {
|
|
129
|
-
type: 'array', items: { type: 'object', additionalProperties: false, properties: {
|
|
130
|
-
id: { type: 'string', required: true }, kind: { type: 'string', required: true }, status: { type: 'string', required: true },
|
|
131
|
-
name: { type: 'string', required: true }, description: { type: 'string', required: true },
|
|
132
|
-
domains: { type: 'array', required: true, items: { type: 'string' } }, tags: { type: 'array', required: true, items: { type: 'string' } },
|
|
133
|
-
inputNames: { type: 'array', required: true, items: { type: 'string' } }, revision: { type: 'number', required: true },
|
|
134
|
-
testStatus: { type: 'string', required: true }, successCount: { type: 'number', required: true }, failureCount: { type: 'number', required: true },
|
|
135
|
-
updatedAt: { type: 'string', required: true }, lastRunAt: { type: 'string' },
|
|
136
|
-
} },
|
|
137
|
-
},
|
|
138
|
-
render: (_args, value) => [{ type: 'text', text: value.length ? value.map(item => `${item.id} — ${item.name} [${item.kind}] domains=${item.domains.join(',') || '*'} inputs=${item.inputNames.join(',') || '-'}`).join('\n') : 'No active reusable automation matched.' }],
|
|
139
|
-
},
|
|
140
|
-
isConcurrencySafe: () => true,
|
|
141
|
-
async execute(args) { return assets?.search(args.query, args.domain, args.status, args.kind) ?? []; },
|
|
142
|
-
}));
|
|
143
|
-
register(defineTool({
|
|
144
|
-
name: 'browser_automation_develop',
|
|
145
|
-
description: '[Experimental] Explicitly inspect, save, validate, or replay one reusable automation draft. Use search first. Full recipe/source is returned only for action=get with an exact id. This tool never activates assets. ' + COMPLIANCE_NOTICE,
|
|
146
|
-
parameters: {
|
|
147
|
-
action: { type: 'string', required: true, enum: ['get', 'save', 'validate', 'test'] },
|
|
148
|
-
id: { type: 'string', description: 'Exact asset id for get, update, or validate.' },
|
|
149
|
-
kind: { type: 'string', enum: ['recipe', 'userscript'], description: 'Required for save.' },
|
|
150
|
-
name: { type: 'string', description: 'Required for save.' },
|
|
151
|
-
description: { type: 'string' },
|
|
152
|
-
domains: { type: 'array', items: { type: 'string' } },
|
|
153
|
-
tags: { type: 'array', items: { type: 'string' }, description: 'Explicit retrieval keywords, capped at 20.' },
|
|
154
|
-
inputNames: { type: 'array', items: { type: 'string' }, description: 'Declared UserScript __DSH_INPUTS__ keys. Recipe placeholders are inferred.' },
|
|
155
|
-
recipe: { type: 'array', items: RECIPE_STEP_SCHEMA, description: 'One to 25 declarative Playwright steps.' },
|
|
156
|
-
source: { type: 'string', description: 'Complete UserScript with @match and @grant none; capped by validator.' },
|
|
157
|
-
url: { type: 'string', description: 'Required for test; must match the draft domain and UserScript @match.' },
|
|
158
|
-
inputs: { type: 'object', additionalProperties: true, description: 'Declared runtime inputs for test.' },
|
|
159
|
-
authProfile: { type: 'string' }, rulePack: { type: 'string' },
|
|
160
|
-
},
|
|
161
|
-
output: {
|
|
162
|
-
schema: { type: 'object', additionalProperties: false, properties: {
|
|
163
|
-
action: { type: 'string', required: true }, assetId: { type: 'string' }, status: { type: 'string' },
|
|
164
|
-
resultJson: { type: 'string', required: true }, truncated: { type: 'boolean', required: true },
|
|
165
|
-
} },
|
|
166
|
-
render: (_args, value) => [{ type: 'text', text: `Automation development ${value.action}${value.assetId ? ` ${value.assetId} [${value.status}]` : ''}\n${value.resultJson}${value.truncated ? '\n(result truncated)' : ''}` }],
|
|
167
|
-
},
|
|
168
|
-
timeoutMs: 30_000,
|
|
169
|
-
isConcurrencySafe: () => false,
|
|
170
|
-
async execute(args, exec) {
|
|
171
|
-
if (!assets || !development)
|
|
172
|
-
throw new Error('model automation development is disabled');
|
|
173
|
-
if (args.action === 'get') {
|
|
174
|
-
if (!args.id)
|
|
175
|
-
throw new Error('automation development get requires id');
|
|
176
|
-
const asset = development.get(args.id);
|
|
177
|
-
return developmentResult('get', asset, asset);
|
|
178
|
-
}
|
|
179
|
-
if (args.action === 'validate') {
|
|
180
|
-
if (!args.id)
|
|
181
|
-
throw new Error('automation development validate requires id');
|
|
182
|
-
const asset = development.validate(args.id);
|
|
183
|
-
return developmentResult('validate', { id: asset.id, kind: asset.kind, status: asset.status, testStatus: asset.testStatus, testMessage: asset.testMessage }, asset);
|
|
184
|
-
}
|
|
185
|
-
if (args.action === 'test') {
|
|
186
|
-
if (!args.id || !args.url)
|
|
187
|
-
throw new Error('automation development test requires id and url');
|
|
188
|
-
const result = await executeAutomationAsset(service, assets, args.id, args.url, args.inputs, 'draft', { signal: exec.signal, ...args.authProfile ? { authProfile: args.authProfile } : {}, ...args.rulePack ? { rulePack: args.rulePack } : {} });
|
|
189
|
-
const raw = JSON.stringify(result.value);
|
|
190
|
-
return developmentResult('test', { id: result.asset.id, testStatus: result.asset.testStatus, testMessage: result.asset.testMessage, resultJson: raw.slice(0, 50_000), truncated: raw.length > 50_000 }, result.asset);
|
|
191
|
-
}
|
|
192
|
-
if (args.action !== 'save' || !args.kind || !args.name)
|
|
193
|
-
throw new Error('automation development save requires kind and name');
|
|
194
|
-
const asset = development.save({
|
|
195
|
-
...args.id ? { id: args.id } : {}, kind: args.kind, name: args.name,
|
|
196
|
-
...args.description !== undefined ? { description: args.description } : {},
|
|
197
|
-
...args.domains ? { domains: args.domains } : {}, ...args.tags ? { tags: args.tags } : {},
|
|
198
|
-
...args.inputNames ? { inputNames: args.inputNames } : {}, ...args.recipe ? { recipe: args.recipe } : {},
|
|
199
|
-
...args.source !== undefined ? { source: args.source } : {},
|
|
200
|
-
}, sessionId(exec));
|
|
201
|
-
const compact = { id: asset.id, kind: asset.kind, status: asset.status, name: asset.name, domains: asset.domains, tags: asset.tags, inputNames: asset.inputNames, revision: asset.revision, testStatus: asset.testStatus };
|
|
202
|
-
return developmentResult('save', compact, asset);
|
|
203
|
-
},
|
|
204
|
-
}));
|
|
205
|
-
register(defineTool({
|
|
206
|
-
name: 'browser_automation_run',
|
|
207
|
-
description: '[Experimental] Run one manually activated reusable automation by id. Search first. Source and recipe internals remain Host-side; provide declared inputs and a target HTTP(S) URL. ' + COMPLIANCE_NOTICE,
|
|
208
|
-
parameters: {
|
|
209
|
-
id: { type: 'string', required: true }, url: { type: 'string', required: true },
|
|
210
|
-
inputs: { type: 'object', additionalProperties: true, description: 'Declared string inputs used by named recipe placeholders.' },
|
|
211
|
-
authProfile: { type: 'string' }, rulePack: { type: 'string' },
|
|
212
|
-
},
|
|
213
|
-
output: {
|
|
214
|
-
schema: { type: 'object', additionalProperties: false, properties: {
|
|
215
|
-
assetId: { type: 'string', required: true }, kind: { type: 'string', required: true }, resultJson: { type: 'string', required: true }, truncated: { type: 'boolean', required: true },
|
|
216
|
-
} },
|
|
217
|
-
render: (_args, value) => [{ type: 'text', text: `Automation ${value.assetId} [${value.kind}]\n${value.resultJson}${value.truncated ? '\n(result truncated)' : ''}` }],
|
|
218
|
-
},
|
|
219
|
-
timeoutMs: 120_000,
|
|
220
|
-
isConcurrencySafe: () => false,
|
|
221
|
-
async execute(args, exec) {
|
|
222
|
-
if (!assets)
|
|
223
|
-
throw new Error('automation assets are unavailable');
|
|
224
|
-
const result = await executeAutomationAsset(service, assets, args.id, args.url, args.inputs, 'active', { signal: exec.signal, ...args.authProfile ? { authProfile: args.authProfile } : {}, ...args.rulePack ? { rulePack: args.rulePack } : {} });
|
|
225
|
-
const raw = JSON.stringify(result.value);
|
|
226
|
-
return { assetId: result.asset.id, kind: result.asset.kind, resultJson: raw.slice(0, 100_000), truncated: raw.length > 100_000 };
|
|
227
|
-
},
|
|
228
|
-
}));
|
|
229
|
-
register(defineTool({
|
|
230
|
-
name: 'browser_open',
|
|
231
|
-
description: 'Open a URL in the persistent browser page and return the rendered title, readable text, and a full-page screenshot path. Optional bounded capture stores console messages and failed/4xx/5xx requests in memory without bodies or headers. ' + COMPLIANCE_NOTICE,
|
|
232
|
-
parameters: {
|
|
233
|
-
url: { type: 'string', required: true, description: 'The HTTP(S) URL to open.' },
|
|
234
|
-
waitMs: { type: 'number', description: 'Extra settle time in ms after load.' },
|
|
235
|
-
authProfile: { type: 'string', description: 'Named, domain-scoped auth profile from dsh-browser config.' },
|
|
236
|
-
rulePack: { type: 'string', description: 'Named, domain-scoped enhancement rule pack.' },
|
|
237
|
-
capture: { type: 'array', items: { type: 'string', enum: ['console', 'network'] }, description: 'Optional in-memory capture channels for this navigation. Records are capped and reset on the next open.' },
|
|
238
|
-
},
|
|
239
|
-
output: {
|
|
240
|
-
schema: {
|
|
241
|
-
type: 'object',
|
|
242
|
-
additionalProperties: false,
|
|
243
|
-
properties: {
|
|
244
|
-
url: { type: 'string', required: true },
|
|
245
|
-
title: { type: 'string' },
|
|
246
|
-
text: { type: 'string', required: true },
|
|
247
|
-
screenshotPath: { type: 'string' },
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
render: (_args, value) => renderState(value),
|
|
251
|
-
},
|
|
252
|
-
timeoutMs: 60_000,
|
|
253
|
-
isConcurrencySafe: () => false,
|
|
254
|
-
async execute(args) {
|
|
255
|
-
return service.open(args.url, { ...args.waitMs !== undefined ? { waitMs: args.waitMs } : {}, ...args.authProfile ? { authProfile: args.authProfile } : {}, ...args.rulePack ? { rulePack: args.rulePack } : {}, ...args.capture ? { capture: args.capture } : {} });
|
|
256
|
-
},
|
|
257
|
-
}));
|
|
258
|
-
register(defineTool({
|
|
259
|
-
name: 'browser_click',
|
|
260
|
-
description: 'Click a CSS selector or structured Playwright locator on the current browser page, then return the updated page state. ' + COMPLIANCE_NOTICE,
|
|
261
|
-
parameters: {
|
|
262
|
-
selector: { type: 'string', description: 'CSS selector of the element to click. Omit when locator is provided.' },
|
|
263
|
-
locator: LOCATOR_SCHEMA,
|
|
264
|
-
},
|
|
265
|
-
output: {
|
|
266
|
-
schema: {
|
|
267
|
-
type: 'object',
|
|
268
|
-
additionalProperties: false,
|
|
269
|
-
properties: {
|
|
270
|
-
url: { type: 'string', required: true },
|
|
271
|
-
title: { type: 'string' },
|
|
272
|
-
text: { type: 'string', required: true },
|
|
273
|
-
screenshotPath: { type: 'string' },
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
render: (_args, value) => renderState(value),
|
|
277
|
-
},
|
|
278
|
-
timeoutMs: 30_000,
|
|
279
|
-
isConcurrencySafe: () => false,
|
|
280
|
-
async execute(args) {
|
|
281
|
-
return service.click(toolTarget(args));
|
|
282
|
-
},
|
|
283
|
-
}));
|
|
284
|
-
register(defineTool({
|
|
285
|
-
name: 'browser_type',
|
|
286
|
-
description: 'Type text into an input/textarea selected by CSS or a structured Playwright locator, then return the page state. ' + COMPLIANCE_NOTICE,
|
|
287
|
-
parameters: {
|
|
288
|
-
selector: { type: 'string', description: 'CSS selector of the input/textarea. Omit when locator is provided.' },
|
|
289
|
-
locator: LOCATOR_SCHEMA,
|
|
290
|
-
text: { type: 'string', required: true, description: 'Text to type.' },
|
|
291
|
-
},
|
|
292
|
-
output: {
|
|
293
|
-
schema: {
|
|
294
|
-
type: 'object',
|
|
295
|
-
additionalProperties: false,
|
|
296
|
-
properties: {
|
|
297
|
-
url: { type: 'string', required: true },
|
|
298
|
-
title: { type: 'string' },
|
|
299
|
-
text: { type: 'string', required: true },
|
|
300
|
-
screenshotPath: { type: 'string' },
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
render: (_args, value) => renderState(value),
|
|
304
|
-
},
|
|
305
|
-
timeoutMs: 30_000,
|
|
306
|
-
isConcurrencySafe: () => false,
|
|
307
|
-
async execute(args) {
|
|
308
|
-
return service.type(toolTarget(args), args.text);
|
|
309
|
-
},
|
|
310
|
-
}));
|
|
311
|
-
register(defineTool({
|
|
312
|
-
name: 'browser_wait',
|
|
313
|
-
description: 'Wait for one locator state, URL pattern, network idle, or a bounded amount of time on the active page. ' + COMPLIANCE_NOTICE,
|
|
314
|
-
parameters: {
|
|
315
|
-
selector: { type: 'string', description: 'CSS selector to wait for. Omit when locator or another wait mode is provided.' },
|
|
316
|
-
locator: LOCATOR_SCHEMA,
|
|
317
|
-
state: { type: 'string', enum: ['visible', 'hidden', 'attached', 'detached'], description: 'Locator state. Defaults to visible.' },
|
|
318
|
-
urlPattern: { type: 'string', description: 'Playwright URL glob to wait for.' },
|
|
319
|
-
networkIdle: { type: 'boolean', description: 'Set true to wait for networkidle.' },
|
|
320
|
-
timeMs: { type: 'number', description: 'Fixed delay from 0 to 10,000 ms.' },
|
|
321
|
-
timeoutMs: { type: 'number', description: 'Condition timeout from 0 to 30,000 ms. Default 15,000.' },
|
|
322
|
-
},
|
|
323
|
-
output: { schema: INTERACTIVE_OUTPUT_SCHEMA, render: (_args, value) => renderState(value) },
|
|
324
|
-
timeoutMs: 35_000,
|
|
325
|
-
isConcurrencySafe: () => false,
|
|
326
|
-
async execute(args) {
|
|
327
|
-
const target = toolTarget(args, false);
|
|
328
|
-
return service.wait(target, {
|
|
329
|
-
...args.state ? { state: args.state } : {}, ...args.urlPattern ? { urlPattern: args.urlPattern } : {},
|
|
330
|
-
...args.networkIdle !== undefined ? { networkIdle: args.networkIdle } : {}, ...args.timeMs !== undefined ? { timeMs: args.timeMs } : {},
|
|
331
|
-
...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {},
|
|
332
|
-
});
|
|
333
|
-
},
|
|
334
|
-
}));
|
|
335
|
-
register(defineTool({
|
|
336
|
-
name: 'browser_press',
|
|
337
|
-
description: 'Press a key globally or on a CSS/semantic locator in the active page. ' + COMPLIANCE_NOTICE,
|
|
338
|
-
parameters: {
|
|
339
|
-
selector: { type: 'string', description: 'Optional CSS target. Omit for a global keyboard press or when locator is provided.' },
|
|
340
|
-
locator: LOCATOR_SCHEMA,
|
|
341
|
-
key: { type: 'string', required: true, description: 'Playwright key such as Enter, ArrowDown, or Control+Enter.' },
|
|
342
|
-
timeoutMs: { type: 'number', description: 'Target timeout from 1 to 30,000 ms.' },
|
|
343
|
-
},
|
|
344
|
-
output: { schema: INTERACTIVE_OUTPUT_SCHEMA, render: (_args, value) => renderState(value) },
|
|
345
|
-
timeoutMs: 30_000,
|
|
346
|
-
isConcurrencySafe: () => false,
|
|
347
|
-
async execute(args) { return service.press(toolTarget(args, false), args.key, { ...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {} }); },
|
|
348
|
-
}));
|
|
349
|
-
register(defineTool({
|
|
350
|
-
name: 'browser_select',
|
|
351
|
-
description: 'Select one or more values in a dropdown selected by CSS or a semantic locator. ' + COMPLIANCE_NOTICE,
|
|
352
|
-
parameters: {
|
|
353
|
-
selector: { type: 'string', description: 'CSS selector. Omit when locator is provided.' }, locator: LOCATOR_SCHEMA,
|
|
354
|
-
values: { type: 'array', required: true, items: { type: 'string' }, description: 'One to 20 option values.' },
|
|
355
|
-
timeoutMs: { type: 'number', description: 'Target timeout from 1 to 30,000 ms.' },
|
|
356
|
-
},
|
|
357
|
-
output: { schema: INTERACTIVE_OUTPUT_SCHEMA, render: (_args, value) => renderState(value) },
|
|
358
|
-
timeoutMs: 30_000,
|
|
359
|
-
isConcurrencySafe: () => false,
|
|
360
|
-
async execute(args) { return service.select(toolTarget(args), args.values, { ...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {} }); },
|
|
361
|
-
}));
|
|
362
|
-
register(defineTool({
|
|
363
|
-
name: 'browser_check',
|
|
364
|
-
description: 'Check or uncheck a checkbox or radio control selected by CSS or a semantic locator. ' + COMPLIANCE_NOTICE,
|
|
365
|
-
parameters: {
|
|
366
|
-
selector: { type: 'string', description: 'CSS selector. Omit when locator is provided.' }, locator: LOCATOR_SCHEMA,
|
|
367
|
-
checked: { type: 'boolean', description: 'True to check, false to uncheck. Defaults to true.' },
|
|
368
|
-
timeoutMs: { type: 'number', description: 'Target timeout from 1 to 30,000 ms.' },
|
|
369
|
-
},
|
|
370
|
-
output: { schema: INTERACTIVE_OUTPUT_SCHEMA, render: (_args, value) => renderState(value) },
|
|
371
|
-
timeoutMs: 30_000,
|
|
372
|
-
isConcurrencySafe: () => false,
|
|
373
|
-
async execute(args) { return service.check(toolTarget(args), args.checked ?? true, { ...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {} }); },
|
|
374
|
-
}));
|
|
375
|
-
register(defineTool({
|
|
376
|
-
name: 'browser_hover',
|
|
377
|
-
description: 'Hover a CSS selector or semantic locator on the current browser page and return the updated state. ' + COMPLIANCE_NOTICE,
|
|
378
|
-
parameters: {
|
|
379
|
-
selector: { type: 'string', description: 'CSS selector of the element to hover. Omit when locator is provided.' },
|
|
380
|
-
locator: LOCATOR_SCHEMA,
|
|
381
|
-
waitMs: { type: 'number', description: 'Settle time after hovering, from 0 to the tool timeout. Default 300 ms.' },
|
|
382
|
-
},
|
|
383
|
-
output: {
|
|
384
|
-
schema: {
|
|
385
|
-
type: 'object', additionalProperties: false, properties: {
|
|
386
|
-
url: { type: 'string', required: true }, title: { type: 'string' }, text: { type: 'string', required: true }, screenshotPath: { type: 'string' },
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
render: (_args, value) => renderState(value),
|
|
390
|
-
},
|
|
391
|
-
timeoutMs: 30_000,
|
|
392
|
-
isConcurrencySafe: () => false,
|
|
393
|
-
async execute(args) { return service.hover(toolTarget(args), { ...args.waitMs !== undefined ? { waitMs: args.waitMs } : {} }); },
|
|
394
|
-
}));
|
|
395
|
-
register(defineTool({
|
|
396
|
-
name: 'browser_set_files',
|
|
397
|
-
description: 'Set one or more existing local files on a file-input CSS selector in the current page. Paths must be absolute regular files; the tool reads them for upload but does not modify them. Approval discloses the requested paths unless automationMode=unrestricted. ' + COMPLIANCE_NOTICE,
|
|
398
|
-
parameters: {
|
|
399
|
-
selector: { type: 'string', description: 'CSS selector of an input[type=file] element. Omit when locator is provided.' },
|
|
400
|
-
locator: LOCATOR_SCHEMA,
|
|
401
|
-
files: { type: 'array', required: true, items: { type: 'string' }, description: 'One to 20 absolute local file paths, with at most 512 MiB total size.' },
|
|
402
|
-
},
|
|
403
|
-
output: {
|
|
404
|
-
schema: {
|
|
405
|
-
type: 'object', additionalProperties: false, properties: {
|
|
406
|
-
url: { type: 'string', required: true }, title: { type: 'string' }, text: { type: 'string', required: true }, screenshotPath: { type: 'string' },
|
|
407
|
-
files: { type: 'array', required: true, items: { type: 'string' } },
|
|
408
|
-
},
|
|
409
|
-
},
|
|
410
|
-
render: (_args, value) => {
|
|
411
|
-
const result = value;
|
|
412
|
-
return [{ type: 'text', text: renderState(result)[0].text + '\n\nSelected files: ' + result.files.join(', ') }];
|
|
413
|
-
},
|
|
414
|
-
},
|
|
415
|
-
timeoutMs: 30_000,
|
|
416
|
-
isConcurrencySafe: () => false,
|
|
417
|
-
async execute(args) { return service.setFiles(toolTarget(args), args.files); },
|
|
418
|
-
}));
|
|
419
|
-
register(defineTool({
|
|
420
|
-
name: 'browser_evaluate',
|
|
421
|
-
description: 'Evaluate one bounded JavaScript expression in the current page and return capped JSON. It runs with the page origin and login state, so it can read or mutate the DOM, access non-HttpOnly cookies/storage, and issue requests allowed by the browser. It has no Node.js or direct host-filesystem access, and downloads are not persisted by this tool. ' + COMPLIANCE_NOTICE,
|
|
422
|
-
parameters: {
|
|
423
|
-
expression: { type: 'string', required: true, description: 'A JavaScript expression up to 20,000 characters. The resolved value must be JSON-serializable.' },
|
|
424
|
-
timeoutMs: { type: 'number', description: 'Execution timeout from 1,000 to 30,000 ms. Default 15,000; Chromium page execution is terminated while the page remains open.' },
|
|
425
|
-
},
|
|
426
|
-
output: {
|
|
427
|
-
schema: {
|
|
428
|
-
type: 'object', additionalProperties: false, properties: {
|
|
429
|
-
url: { type: 'string', required: true }, resultJson: { type: 'string', required: true }, truncated: { type: 'boolean', required: true },
|
|
430
|
-
capabilities: { type: 'array', required: true, items: { type: 'string' } }, warnings: { type: 'array', required: true, items: { type: 'string' } },
|
|
431
|
-
},
|
|
432
|
-
},
|
|
433
|
-
render: (_args, value) => {
|
|
434
|
-
const result = value;
|
|
435
|
-
return [{ type: 'text', text: [`URL: ${result.url}`, `Capabilities: ${result.capabilities.join(', ')}`, result.resultJson + (result.truncated ? '\n(result truncated)' : ''), ...result.warnings.map(warning => 'Warning: ' + warning)].join('\n') }];
|
|
436
|
-
},
|
|
437
|
-
},
|
|
438
|
-
timeoutMs: 35_000,
|
|
439
|
-
isConcurrencySafe: () => false,
|
|
440
|
-
async execute(args) { return service.evaluate(args.expression, { ...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {} }); },
|
|
441
|
-
}));
|
|
442
|
-
register(defineTool({
|
|
443
|
-
name: 'browser_console',
|
|
444
|
-
description: 'Return bounded, redacted console records captured since the latest browser_open with capture=[console]. Records stay in memory and omit console argument objects. ' + COMPLIANCE_NOTICE,
|
|
445
|
-
parameters: {
|
|
446
|
-
level: { type: 'string', enum: ['debug', 'log', 'info', 'warning', 'error'], description: 'Minimum severity. Defaults to debug.' },
|
|
447
|
-
limit: { type: 'number', description: 'Return the newest 1 to 200 records. Default 100.' },
|
|
448
|
-
clear: { type: 'boolean', description: 'Clear captured records after reading.' },
|
|
449
|
-
},
|
|
450
|
-
output: { schema: { type: 'object', additionalProperties: false, properties: {
|
|
451
|
-
enabled: { type: 'boolean', required: true }, records: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: {
|
|
452
|
-
type: { type: 'string', required: true }, text: { type: 'string', required: true }, url: { type: 'string' }, timestamp: { type: 'string', required: true },
|
|
453
|
-
} } },
|
|
454
|
-
} }, render: (_args, value) => [{ type: 'text', text: value.enabled ? (value.records.map(record => `[${record.type}] ${record.text}${record.url ? ` (${record.url})` : ''}`).join('\n') || 'No captured console messages.') : 'Console capture is disabled; reopen with capture=[console].' }] },
|
|
455
|
-
timeoutMs: 10_000,
|
|
456
|
-
isConcurrencySafe: () => true,
|
|
457
|
-
async execute(args) { return service.consoleMessages(args); },
|
|
458
|
-
}));
|
|
459
|
-
register(defineTool({
|
|
460
|
-
name: 'browser_requests',
|
|
461
|
-
description: 'Return bounded, redacted failed and HTTP 4xx/5xx requests captured since the latest browser_open with capture=[network]. Request/response bodies and headers are never recorded. ' + COMPLIANCE_NOTICE,
|
|
462
|
-
parameters: {
|
|
463
|
-
limit: { type: 'number', description: 'Return the newest 1 to 200 records. Default 100.' },
|
|
464
|
-
clear: { type: 'boolean', description: 'Clear captured records after reading.' },
|
|
465
|
-
},
|
|
466
|
-
output: { schema: { type: 'object', additionalProperties: false, properties: {
|
|
467
|
-
enabled: { type: 'boolean', required: true }, records: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: {
|
|
468
|
-
method: { type: 'string', required: true }, url: { type: 'string', required: true }, status: { type: 'number' }, failure: { type: 'string' }, timestamp: { type: 'string', required: true },
|
|
469
|
-
} } },
|
|
470
|
-
} }, render: (_args, value) => [{ type: 'text', text: value.enabled ? (value.records.map(record => `${record.method} ${record.status ?? 'FAILED'} ${record.url}${record.failure ? ` — ${record.failure}` : ''}`).join('\n') || 'No failed or HTTP 4xx/5xx requests captured.') : 'Network capture is disabled; reopen with capture=[network].' }] },
|
|
471
|
-
timeoutMs: 10_000,
|
|
472
|
-
isConcurrencySafe: () => true,
|
|
473
|
-
async execute(args) { return service.networkRequests(args); },
|
|
474
|
-
}));
|
|
475
|
-
register(defineTool({
|
|
476
|
-
name: 'browser_scroll',
|
|
477
|
-
description: 'Scroll the current browser page vertically by deltaY pixels (positive = down) to trigger lazy loading, then return the page state. ' + COMPLIANCE_NOTICE,
|
|
478
|
-
parameters: {
|
|
479
|
-
deltaY: { type: 'number', description: 'Pixels to scroll; positive scrolls down. Default 2000.' },
|
|
480
|
-
},
|
|
481
|
-
output: {
|
|
482
|
-
schema: {
|
|
483
|
-
type: 'object',
|
|
484
|
-
additionalProperties: false,
|
|
485
|
-
properties: {
|
|
486
|
-
url: { type: 'string', required: true },
|
|
487
|
-
title: { type: 'string' },
|
|
488
|
-
text: { type: 'string', required: true },
|
|
489
|
-
screenshotPath: { type: 'string' },
|
|
490
|
-
},
|
|
491
|
-
},
|
|
492
|
-
render: (_args, value) => renderState(value),
|
|
493
|
-
},
|
|
494
|
-
timeoutMs: 20_000,
|
|
495
|
-
isConcurrencySafe: () => false,
|
|
496
|
-
async execute(args) {
|
|
497
|
-
return service.scroll(args.deltaY ?? 2000);
|
|
498
|
-
},
|
|
499
|
-
}));
|
|
500
|
-
register(defineTool({
|
|
501
|
-
name: 'browser_read',
|
|
502
|
-
description: 'Read the current browser page state (URL, title, readable text) without taking a screenshot.',
|
|
503
|
-
parameters: {},
|
|
504
|
-
output: {
|
|
505
|
-
schema: {
|
|
506
|
-
type: 'object',
|
|
507
|
-
additionalProperties: false,
|
|
508
|
-
properties: {
|
|
509
|
-
url: { type: 'string', required: true },
|
|
510
|
-
title: { type: 'string' },
|
|
511
|
-
text: { type: 'string', required: true },
|
|
512
|
-
screenshotPath: { type: 'string' },
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
render: (_args, value) => renderState(value),
|
|
516
|
-
},
|
|
517
|
-
timeoutMs: 20_000,
|
|
518
|
-
isConcurrencySafe: () => false,
|
|
519
|
-
async execute() {
|
|
520
|
-
return service.read();
|
|
521
|
-
},
|
|
522
|
-
}));
|
|
523
|
-
register(defineTool({
|
|
524
|
-
name: 'browser_screenshot',
|
|
525
|
-
description: 'Capture the page, a bounded region, or a CSS/semantic locator. Files always land inside the configured snapshotDir under a plain caller-supplied filename or an autogenerated name. ' + COMPLIANCE_NOTICE,
|
|
526
|
-
parameters: {
|
|
527
|
-
selector: { type: 'string', description: 'Optional CSS selector. Omit when locator is provided.' },
|
|
528
|
-
locator: LOCATOR_SCHEMA,
|
|
529
|
-
clip: { type: 'object', additionalProperties: false, properties: {
|
|
530
|
-
x: { type: 'number', required: true }, y: { type: 'number', required: true }, width: { type: 'number', required: true }, height: { type: 'number', required: true },
|
|
531
|
-
} },
|
|
532
|
-
fullPage: { type: 'boolean', description: 'Capture the full scrollable page. Defaults to true for page screenshots; incompatible with selector/locator.' },
|
|
533
|
-
format: { type: 'string', enum: ['png', 'jpeg'], description: 'Image format. Defaults from filename or png.' },
|
|
534
|
-
quality: { type: 'number', description: 'JPEG quality from 0 to 100.' },
|
|
535
|
-
filename: { type: 'string', description: 'Plain filename only, ending in .png, .jpg, or .jpeg. Directory traversal and absolute paths are rejected.' },
|
|
536
|
-
},
|
|
537
|
-
output: {
|
|
538
|
-
schema: {
|
|
539
|
-
type: 'object',
|
|
540
|
-
additionalProperties: false,
|
|
541
|
-
properties: {
|
|
542
|
-
path: { type: 'string', required: true },
|
|
543
|
-
},
|
|
544
|
-
},
|
|
545
|
-
render: (_args, value) => [{ type: 'text', text: 'Screenshot: ' + value.path }],
|
|
546
|
-
},
|
|
547
|
-
timeoutMs: 30_000,
|
|
548
|
-
isConcurrencySafe: () => false,
|
|
549
|
-
async execute(args) {
|
|
550
|
-
const target = toolTarget(args, false);
|
|
551
|
-
return service.screenshot({
|
|
552
|
-
...target ? { target } : {},
|
|
553
|
-
...args.clip ? { clip: args.clip } : {}, ...args.fullPage !== undefined ? { fullPage: args.fullPage } : {},
|
|
554
|
-
...args.format ? { format: args.format } : {}, ...args.quality !== undefined ? { quality: args.quality } : {},
|
|
555
|
-
...args.filename ? { filename: args.filename } : {},
|
|
556
|
-
});
|
|
557
|
-
},
|
|
558
|
-
}));
|
|
559
|
-
register(defineTool({
|
|
560
|
-
name: 'browser_close',
|
|
561
|
-
description: 'Close the current browser page (and its context). The next browser_open starts a fresh page.',
|
|
562
|
-
parameters: {},
|
|
563
|
-
output: {
|
|
564
|
-
schema: { type: 'object', additionalProperties: false, properties: { closed: { type: 'boolean', required: true } } },
|
|
565
|
-
render: () => [{ type: 'text', text: 'Browser page closed.' }],
|
|
566
|
-
},
|
|
567
|
-
timeoutMs: 15_000,
|
|
568
|
-
async execute() {
|
|
569
|
-
await service.closePage();
|
|
570
|
-
return { closed: true };
|
|
571
|
-
},
|
|
572
|
-
}));
|
|
573
|
-
register(defineTool({
|
|
574
|
-
name: 'browser_status',
|
|
575
|
-
description: 'Report the browser runtime status: enabled, channel, headless, whether chromium is installed, whether the bundled OpenCLI is enabled, and the active page URL.',
|
|
576
|
-
parameters: {},
|
|
577
|
-
output: {
|
|
578
|
-
schema: {
|
|
579
|
-
type: 'object',
|
|
580
|
-
additionalProperties: false,
|
|
581
|
-
properties: {
|
|
582
|
-
enabled: { type: 'boolean', required: true },
|
|
583
|
-
channel: { type: 'string', required: true },
|
|
584
|
-
browserRuntime: { type: 'string', required: true, enum: ['playwright', 'patchright'] },
|
|
585
|
-
runtimeWarnings: { type: 'array', required: true, items: { type: 'string' } },
|
|
586
|
-
headless: { type: 'boolean', required: true },
|
|
587
|
-
opencliEnabled: { type: 'boolean', required: true },
|
|
588
|
-
opencliInstalled: { type: 'boolean', required: true },
|
|
589
|
-
opencliEntryPath: { type: 'string' },
|
|
590
|
-
automationMode: { type: 'string', required: true, enum: ['read-only', 'standard', 'autonomous', 'unrestricted'] },
|
|
591
|
-
exposedTools: { type: 'array', required: true, items: { type: 'string' } },
|
|
592
|
-
directInteractionPolicy: { type: 'string', required: true, enum: ['deny', 'ask', 'allow'] },
|
|
593
|
-
mutatingRecipePolicy: { type: 'string', required: true, enum: ['deny', 'ask', 'allow'] },
|
|
594
|
-
externalUserscriptPolicy: { type: 'string', required: true, enum: ['deny', 'ask', 'allow'] },
|
|
595
|
-
pageEvaluatePolicy: { type: 'string', required: true, enum: ['deny', 'ask', 'allow'] },
|
|
596
|
-
fileUploadPolicy: { type: 'string', required: true, enum: ['deny', 'ask', 'allow'] },
|
|
597
|
-
opencliRunPolicy: { type: 'string', required: true, enum: ['deny', 'ask', 'allow'] },
|
|
598
|
-
chromiumInstalled: { type: 'boolean', required: true },
|
|
599
|
-
chromiumExecutablePath: { type: 'string' },
|
|
600
|
-
usagePolicy: { type: 'object', required: true, additionalProperties: true, properties: {} },
|
|
601
|
-
usageGovernor: { type: 'object', required: true, additionalProperties: true, properties: {} },
|
|
602
|
-
activeUrl: { type: 'string' },
|
|
603
|
-
activeAuthProfile: { type: 'string' },
|
|
604
|
-
authProfiles: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: { id: { type: 'string', required: true }, allowedDomains: { type: 'array', required: true, items: { type: 'string' } }, persistState: { type: 'boolean', required: true } } } },
|
|
605
|
-
rulePacks: { type: 'array', required: true, items: { type: 'string' } },
|
|
606
|
-
builtinScripts: { type: 'array', required: true, items: { type: 'string' } },
|
|
607
|
-
externalUserscriptsRequireApproval: { type: 'boolean', required: true },
|
|
608
|
-
mutatingRecipesRequireApproval: { type: 'boolean', required: true },
|
|
609
|
-
},
|
|
610
|
-
},
|
|
611
|
-
render: (_args, value) => {
|
|
612
|
-
const v = value;
|
|
613
|
-
return [{ type: 'text', text: [
|
|
614
|
-
'browser: ' + (v.enabled ? 'enabled' : 'disabled'),
|
|
615
|
-
'runtime: ' + v.browserRuntime + ' / ' + v.channel + (v.headless ? ' (headless)' : ' (headed)'),
|
|
616
|
-
'automation mode: ' + v.automationMode + ' (' + v.exposedTools.length + ' tools exposed)',
|
|
617
|
-
'direct interactions: ' + v.directInteractionPolicy,
|
|
618
|
-
'mutating recipes: ' + v.mutatingRecipePolicy,
|
|
619
|
-
'external userscripts: ' + v.externalUserscriptPolicy,
|
|
620
|
-
'page evaluate: ' + v.pageEvaluatePolicy,
|
|
621
|
-
'local file upload: ' + v.fileUploadPolicy,
|
|
622
|
-
'general opencli: ' + v.opencliRunPolicy,
|
|
623
|
-
'runtime chromium installed: ' + v.chromiumInstalled,
|
|
624
|
-
...(v.chromiumExecutablePath ? ['chromium executable: ' + v.chromiumExecutablePath] : []),
|
|
625
|
-
`usage buffer: concurrency=${v.usagePolicy.maxConcurrency}, burst=${v.usagePolicy.burst}/${v.usagePolicy.minDelayMs}ms, crawl=${v.usagePolicy.maxPagesPerRun} pages depth ${v.usagePolicy.maxDepth}`,
|
|
626
|
-
`usage activity: runs=${v.usageGovernor.totalRuns}, queued=${v.usageGovernor.queued}, waited=${v.usageGovernor.totalWaitMs}ms, backoffs=${v.usageGovernor.backoffEvents}`,
|
|
627
|
-
'opencli: ' + (v.opencliEnabled ? 'enabled' : 'disabled') + ' / ' + (v.opencliInstalled ? 'installed' : 'missing'),
|
|
628
|
-
...(v.opencliEntryPath ? ['opencli entry: ' + v.opencliEntryPath] : []),
|
|
629
|
-
'auth profiles: ' + (v.authProfiles.map(p => p.id + '[' + p.allowedDomains.join(',') + ']' + (p.persistState ? '(writeback)' : '')).join('; ') || '-'),
|
|
630
|
-
'rule packs: ' + (v.rulePacks.join(', ') || '-'),
|
|
631
|
-
'built-in scripts: ' + v.builtinScripts.join(', '),
|
|
632
|
-
...v.runtimeWarnings.map(warning => 'warning: ' + warning),
|
|
633
|
-
...(v.activeAuthProfile ? ['active auth profile: ' + v.activeAuthProfile] : []),
|
|
634
|
-
...(v.activeUrl ? ['active page: ' + v.activeUrl] : []),
|
|
635
|
-
].join('\n') }];
|
|
636
|
-
},
|
|
637
|
-
},
|
|
638
|
-
timeoutMs: 15_000,
|
|
639
|
-
isConcurrencySafe: () => true,
|
|
640
|
-
async execute() {
|
|
641
|
-
return service.status();
|
|
642
|
-
},
|
|
643
|
-
}));
|
|
644
|
-
register(defineTool({
|
|
645
|
-
name: 'browser_install',
|
|
646
|
-
description: 'Install the bundled Playwright chromium browser (downloads to the Playwright cache). Run this once if browser_status reports chromium not installed.',
|
|
647
|
-
parameters: {},
|
|
648
|
-
output: {
|
|
649
|
-
schema: {
|
|
650
|
-
type: 'object',
|
|
651
|
-
additionalProperties: false,
|
|
652
|
-
properties: {
|
|
653
|
-
code: { type: 'number', required: true },
|
|
654
|
-
stdout: { type: 'string' },
|
|
655
|
-
stderr: { type: 'string' },
|
|
656
|
-
timedOut: { type: 'boolean', required: true },
|
|
657
|
-
},
|
|
658
|
-
},
|
|
659
|
-
render: (_args, value) => {
|
|
660
|
-
const v = value;
|
|
661
|
-
return [{ type: 'text', text: 'install exit=' + v.code + (v.timedOut ? ' (timeout)' : '') + '\n' + ((v.stderr || v.stdout) ?? '').slice(0, 2000) }];
|
|
662
|
-
},
|
|
663
|
-
},
|
|
664
|
-
timeoutMs: 600_000,
|
|
665
|
-
isConcurrencySafe: () => false,
|
|
666
|
-
async execute() {
|
|
667
|
-
const r = await service.installChromium();
|
|
668
|
-
return { code: r.code, stdout: r.stdout, stderr: r.stderr, timedOut: r.timedOut };
|
|
669
|
-
},
|
|
670
|
-
}));
|
|
671
|
-
register(defineTool({
|
|
672
|
-
name: 'browser_script_catalog',
|
|
673
|
-
description: 'List trusted built-in read-only browser scripts. Prefer these over external JavaScript for article extraction, links, JSON-LD, and form structure.',
|
|
674
|
-
parameters: {},
|
|
675
|
-
output: {
|
|
676
|
-
schema: {
|
|
677
|
-
type: 'array',
|
|
678
|
-
items: {
|
|
679
|
-
type: 'object',
|
|
680
|
-
additionalProperties: false,
|
|
34
|
+
ok: { type: 'boolean', required: true },
|
|
35
|
+
action: { type: 'string', required: true },
|
|
36
|
+
seq: { type: 'number' },
|
|
37
|
+
executionStatus: { type: 'string', required: true, enum: ['completed', 'failed', 'cancelled', 'outcome_unknown'] },
|
|
38
|
+
result: { type: 'object', additionalProperties: true },
|
|
39
|
+
error: {
|
|
40
|
+
type: 'object', additionalProperties: false,
|
|
41
|
+
properties: {
|
|
42
|
+
code: { type: 'string', required: true },
|
|
43
|
+
message: { type: 'string', required: true },
|
|
44
|
+
hint: { type: 'string' },
|
|
45
|
+
schema: { type: 'string' },
|
|
46
|
+
current: {
|
|
47
|
+
type: 'object', additionalProperties: false,
|
|
48
|
+
properties: { targetId: { type: 'string', required: true }, generation: { type: 'number', required: true } },
|
|
49
|
+
},
|
|
50
|
+
candidates: {
|
|
51
|
+
type: 'object', additionalProperties: false,
|
|
681
52
|
properties: {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
53
|
+
total: { type: 'number', required: true },
|
|
54
|
+
items: {
|
|
55
|
+
type: 'array',
|
|
56
|
+
items: {
|
|
57
|
+
type: 'object', additionalProperties: false,
|
|
58
|
+
properties: {
|
|
59
|
+
index: { type: 'number', required: true },
|
|
60
|
+
role: { type: 'string', required: true },
|
|
61
|
+
name: { type: 'string', required: true },
|
|
62
|
+
text: { type: 'string', required: true },
|
|
63
|
+
visible: { type: 'boolean', required: true },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
686
67
|
},
|
|
687
68
|
},
|
|
688
69
|
},
|
|
689
|
-
render: (_args, value) => [{ type: 'text', text: value.map(item => `${item.id} — ${item.description} (${item.sha256.slice(0, 12)})`).join('\n') }],
|
|
690
|
-
},
|
|
691
|
-
isConcurrencySafe: () => true,
|
|
692
|
-
async execute() {
|
|
693
|
-
return service.scriptCatalog();
|
|
694
70
|
},
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
description: 'Validate externally supplied Tampermonkey/UserScript-style JavaScript without executing it. Parses @match/@grant, reports SHA-256 and capabilities. Only @grant none is supported.',
|
|
699
|
-
parameters: {
|
|
700
|
-
source: { type: 'string', required: true, description: 'Complete userscript source including the metadata block. Never embed credentials.' },
|
|
701
|
-
url: { type: 'string', description: 'Optional target URL to verify against @match and @exclude-match.' },
|
|
71
|
+
truncation: {
|
|
72
|
+
type: 'object', additionalProperties: false,
|
|
73
|
+
properties: { omitted: { type: 'number', required: true }, reason: { type: 'string', required: true } },
|
|
702
74
|
},
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const renderEnvelope = (_args, value) => [{ type: 'text', text: JSON.stringify(value) }];
|
|
78
|
+
export function registerTools(ctx, config, service, assets, runtime = {}) {
|
|
79
|
+
if (!config.enabled)
|
|
80
|
+
return;
|
|
81
|
+
const development = assets ? new AutomationDevelopmentService(assets, config.automationAssets) : undefined;
|
|
82
|
+
const runEnv = { mode: config.automationMode, options: config.automationAssets, enabled: config.enabled };
|
|
83
|
+
const register = (tool) => { ctx.tools.register(tool); };
|
|
84
|
+
// Read at call time wherever the surface allows it, so a config edit applies to the next call.
|
|
85
|
+
// Tool descriptions are the exception: the host fixes them when the tool is registered.
|
|
86
|
+
const promptsNow = () => config.prompts?.current();
|
|
87
|
+
const actionContext = (exec) => ({
|
|
88
|
+
service, config, ...assets ? { assets } : {}, ...development ? { development } : {},
|
|
89
|
+
session: sessionKeyFor(exec?.agent),
|
|
90
|
+
sessionId: sessionId(exec),
|
|
91
|
+
agent: exec?.agent,
|
|
92
|
+
signal: exec?.signal ?? new AbortController().signal,
|
|
93
|
+
});
|
|
94
|
+
if (config.toolSurface === 'flat') {
|
|
95
|
+
const exposed = new Set(configuredBrowserActions(config.automationMode, config.automationAssets, config.enabled));
|
|
96
|
+
for (const action of ACTIONS.filter(entry => exposed.has(entry.name)))
|
|
97
|
+
register(flatTool(action, actionContext, runEnv, promptsNow()));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
register(defineTool({
|
|
101
|
+
name: INDEX_TOOL,
|
|
102
|
+
description: indexDescription(promptsNow()?.tools.browser_index),
|
|
103
|
+
parameters: { ...INDEX_PARAMETERS },
|
|
104
|
+
output: {
|
|
105
|
+
schema: { type: 'object', additionalProperties: false, properties: { level: { type: 'string', required: true }, text: { type: 'string', required: true } } },
|
|
106
|
+
render: (_args, value) => [{ type: 'text', text: value.text }],
|
|
727
107
|
},
|
|
108
|
+
timeoutMs: 15_000,
|
|
728
109
|
isConcurrencySafe: () => true,
|
|
729
110
|
async execute(args) {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
matches: result.metadata.matches,
|
|
737
|
-
grants: result.metadata.grants,
|
|
738
|
-
capabilities: result.capabilities,
|
|
739
|
-
errors: result.errors,
|
|
740
|
-
warnings: result.warnings,
|
|
111
|
+
runtime.refreshSkill?.();
|
|
112
|
+
const prompts = promptsNow();
|
|
113
|
+
const env = {
|
|
114
|
+
mode: config.automationMode, options: config.automationAssets, enabled: config.enabled,
|
|
115
|
+
skillAvailable: runtime.skillAvailable?.() ?? false,
|
|
116
|
+
...prompts ? { prompts } : {},
|
|
741
117
|
};
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
url: { type: 'string', required: true },
|
|
749
|
-
scriptId: { type: 'string', required: true, enum: ['article-clean', 'links', 'jsonld', 'forms'] },
|
|
750
|
-
authProfile: { type: 'string' },
|
|
751
|
-
rulePack: { type: 'string' },
|
|
752
|
-
timeoutMs: { type: 'number' },
|
|
753
|
-
},
|
|
754
|
-
output: { schema: SCRIPT_RESULT_SCHEMA, render: (_args, value) => renderScriptResult(value) },
|
|
755
|
-
timeoutMs: 60_000,
|
|
756
|
-
isConcurrencySafe: () => false,
|
|
757
|
-
async execute(args, exec) {
|
|
758
|
-
return service.runBuiltinScript(args.url, args.scriptId, {
|
|
759
|
-
signal: exec.signal,
|
|
760
|
-
...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {},
|
|
761
|
-
...args.authProfile ? { authProfile: args.authProfile } : {},
|
|
762
|
-
...args.rulePack ? { rulePack: args.rulePack } : {},
|
|
763
|
-
});
|
|
764
|
-
},
|
|
765
|
-
}));
|
|
766
|
-
register(defineTool({
|
|
767
|
-
name: 'browser_userscript_run',
|
|
768
|
-
description: 'Run an externally supplied Tampermonkey/UserScript-style script in a fresh Playwright context. Approval follows automationMode (skipped only in unrestricted); target @match, source/result caps, and no-GM_* validation always apply. ' + COMPLIANCE_NOTICE,
|
|
769
|
-
parameters: {
|
|
770
|
-
url: { type: 'string', required: true },
|
|
771
|
-
source: { type: 'string', required: true, description: 'Complete userscript source. Validate first. Never embed credentials or tokens.' },
|
|
772
|
-
authProfile: { type: 'string' },
|
|
773
|
-
rulePack: { type: 'string' },
|
|
774
|
-
timeoutMs: { type: 'number' },
|
|
775
|
-
},
|
|
776
|
-
output: { schema: SCRIPT_RESULT_SCHEMA, render: (_args, value) => renderScriptResult(value) },
|
|
777
|
-
timeoutMs: 60_000,
|
|
778
|
-
isConcurrencySafe: () => false,
|
|
779
|
-
async execute(args, exec) {
|
|
780
|
-
return service.runUserscript(args.url, args.source, {
|
|
781
|
-
signal: exec.signal,
|
|
782
|
-
...args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {},
|
|
783
|
-
...args.authProfile ? { authProfile: args.authProfile } : {},
|
|
784
|
-
...args.rulePack ? { rulePack: args.rulePack } : {},
|
|
785
|
-
});
|
|
786
|
-
},
|
|
787
|
-
}));
|
|
788
|
-
register(defineTool({
|
|
789
|
-
name: 'browser_recipe_run',
|
|
790
|
-
description: 'Run a bounded Playwright recipe (max 25 named steps). Read-only steps run directly; mutating steps are denied in read-only, approved once in standard, and direct in autonomous/unrestricted. ' + COMPLIANCE_NOTICE,
|
|
791
|
-
parameters: {
|
|
792
|
-
url: { type: 'string', description: 'Open this URL first; omit only when browser_open already established an active page.' },
|
|
793
|
-
authProfile: { type: 'string' },
|
|
794
|
-
rulePack: { type: 'string' },
|
|
795
|
-
waitMs: { type: 'number' },
|
|
796
|
-
steps: { type: 'array', required: true, items: RECIPE_STEP_SCHEMA },
|
|
797
|
-
},
|
|
798
|
-
output: {
|
|
799
|
-
schema: {
|
|
800
|
-
type: 'object',
|
|
801
|
-
additionalProperties: false,
|
|
802
|
-
properties: {
|
|
803
|
-
url: { type: 'string', required: true },
|
|
804
|
-
title: { type: 'string', required: true },
|
|
805
|
-
text: { type: 'string', required: true },
|
|
806
|
-
screenshotPath: { type: 'string' },
|
|
807
|
-
steps: {
|
|
808
|
-
type: 'array', required: true, items: {
|
|
809
|
-
type: 'object', additionalProperties: false, properties: {
|
|
810
|
-
step: { type: 'number', required: true },
|
|
811
|
-
action: { type: 'string', required: true },
|
|
812
|
-
ok: { type: 'boolean', required: true },
|
|
813
|
-
value: { type: 'string' },
|
|
814
|
-
},
|
|
815
|
-
},
|
|
816
|
-
},
|
|
817
|
-
},
|
|
818
|
-
},
|
|
819
|
-
render: (_args, value) => [{ type: 'text', text: [
|
|
820
|
-
`${value.title}\n${value.url}`,
|
|
821
|
-
...value.steps.map(step => `${step.step}. ${step.action}: ${step.ok ? 'ok' : 'failed'}${step.value ? '\n' + step.value : ''}`),
|
|
822
|
-
value.text,
|
|
823
|
-
].join('\n\n') }],
|
|
824
|
-
},
|
|
825
|
-
timeoutMs: 120_000,
|
|
826
|
-
isConcurrencySafe: () => false,
|
|
827
|
-
async execute(args, exec) {
|
|
828
|
-
const steps = args.steps;
|
|
829
|
-
try {
|
|
830
|
-
const result = await service.recipe(steps, {
|
|
831
|
-
signal: exec.signal,
|
|
832
|
-
...args.url ? { url: args.url } : {},
|
|
833
|
-
...args.waitMs !== undefined ? { waitMs: args.waitMs } : {},
|
|
834
|
-
...args.authProfile ? { authProfile: args.authProfile } : {},
|
|
835
|
-
...args.rulePack ? { rulePack: args.rulePack } : {},
|
|
836
|
-
});
|
|
837
|
-
assets?.recordRecipe(result.url, steps, sessionId(exec), true);
|
|
838
|
-
return result;
|
|
839
|
-
}
|
|
840
|
-
catch (error) {
|
|
841
|
-
if (args.url)
|
|
842
|
-
assets?.recordRecipe(args.url, steps, sessionId(exec), false);
|
|
843
|
-
throw error;
|
|
118
|
+
if (!args.group && !args.action && !args.query) {
|
|
119
|
+
try {
|
|
120
|
+
const status = await service.status();
|
|
121
|
+
env.runtime = { chromiumInstalled: status.chromiumInstalled, opencliInstalled: status.opencliInstalled, opencliEnabled: status.opencliEnabled };
|
|
122
|
+
}
|
|
123
|
+
catch { /* runtime facts are best-effort */ }
|
|
844
124
|
}
|
|
125
|
+
return renderIndex(args, env);
|
|
845
126
|
},
|
|
846
127
|
}));
|
|
847
128
|
register(defineTool({
|
|
848
|
-
name:
|
|
849
|
-
description:
|
|
850
|
-
parameters: {
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
sameOrigin: { type: 'boolean', description: 'Only follow links on starting origins. Default true.' },
|
|
855
|
-
maxCharsPerPage: { type: 'number', description: 'Readable text cap per page, 1000-50000.' },
|
|
856
|
-
},
|
|
857
|
-
output: {
|
|
858
|
-
schema: {
|
|
859
|
-
type: 'object', additionalProperties: false, properties: {
|
|
860
|
-
pages: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: {
|
|
861
|
-
url: { type: 'string', required: true }, title: { type: 'string', required: true }, text: { type: 'string', required: true }, depth: { type: 'number', required: true }, status: { type: 'number', required: true },
|
|
862
|
-
} } },
|
|
863
|
-
errors: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: {
|
|
864
|
-
url: { type: 'string', required: true }, depth: { type: 'number', required: true }, error: { type: 'string', required: true }, status: { type: 'number' },
|
|
865
|
-
} } },
|
|
866
|
-
stats: { type: 'object', required: true, additionalProperties: false, properties: {
|
|
867
|
-
pagesVisited: { type: 'number', required: true }, queued: { type: 'number', required: true }, elapsedMs: { type: 'number', required: true }, waitMs: { type: 'number', required: true }, backoffEvents: { type: 'number', required: true },
|
|
868
|
-
} },
|
|
869
|
-
warnings: { type: 'array', required: true, items: { type: 'string' } },
|
|
870
|
-
},
|
|
871
|
-
},
|
|
872
|
-
render: (_args, value) => [{ type: 'text', text: [
|
|
873
|
-
`crawl: ${value.stats.pagesVisited} visited, ${value.pages.length} pages, ${value.errors.length} errors, ${value.stats.waitMs}ms buffered`,
|
|
874
|
-
...value.warnings.map(warning => 'warning: ' + warning),
|
|
875
|
-
...value.pages.map(page => `\n[depth ${page.depth}, HTTP ${page.status}] ${page.title}\n${page.url}\n${page.text}`),
|
|
876
|
-
...value.errors.map(error => `\nERROR depth ${error.depth} ${error.url}: ${error.error}`),
|
|
877
|
-
].join('\n') }],
|
|
878
|
-
},
|
|
879
|
-
timeoutMs: 300_000,
|
|
129
|
+
name: CALL_TOOL,
|
|
130
|
+
description: callDescription(promptsNow()?.tools.browser_call),
|
|
131
|
+
parameters: { ...CALL_PARAMETERS },
|
|
132
|
+
output: { schema: ENVELOPE_SCHEMA, render: renderEnvelope },
|
|
133
|
+
timeoutMs: 600_000,
|
|
134
|
+
// Calls are serialized per agent step: interactions share one page, and the Host classifier cannot read the action.
|
|
880
135
|
isConcurrencySafe: () => false,
|
|
881
136
|
async execute(args, exec) {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
...args.maxPages !== undefined ? { maxPages: args.maxPages } : {},
|
|
885
|
-
...args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {},
|
|
886
|
-
...args.sameOrigin !== undefined ? { sameOrigin: args.sameOrigin } : {},
|
|
887
|
-
...args.maxCharsPerPage !== undefined ? { maxCharsPerPage: args.maxCharsPerPage } : {},
|
|
888
|
-
});
|
|
889
|
-
},
|
|
890
|
-
}));
|
|
891
|
-
register(defineTool({
|
|
892
|
-
name: 'browser_opencli_status',
|
|
893
|
-
description: 'Run bundled OpenCLI doctor and return the real daemon, extension, profile, and Browser Bridge connectivity status.',
|
|
894
|
-
parameters: {},
|
|
895
|
-
output: {
|
|
896
|
-
schema: {
|
|
897
|
-
type: 'object', additionalProperties: false, properties: {
|
|
898
|
-
code: { type: 'number', required: true }, stdout: { type: 'string', required: true }, stderr: { type: 'string', required: true }, timedOut: { type: 'boolean', required: true },
|
|
899
|
-
},
|
|
900
|
-
},
|
|
901
|
-
render: (_args, value) => [{ type: 'text', text: (value.stdout || value.stderr).slice(0, 20_000) }],
|
|
902
|
-
},
|
|
903
|
-
timeoutMs: 45_000,
|
|
904
|
-
isConcurrencySafe: () => true,
|
|
905
|
-
async execute(_args, exec) {
|
|
906
|
-
return service.opencliDoctor(exec.signal);
|
|
907
|
-
},
|
|
908
|
-
}));
|
|
909
|
-
register(defineTool({
|
|
910
|
-
name: 'browser_opencli_catalog',
|
|
911
|
-
description: 'Discover bundled OpenCLI adapters through a filtered, capped catalog. Use before browser_opencli_run to find exact command names, access class, strategy, and arguments without exposing the full catalog.',
|
|
912
|
-
parameters: {
|
|
913
|
-
query: { type: 'string' },
|
|
914
|
-
site: { type: 'string' },
|
|
915
|
-
access: { type: 'string', enum: ['read', 'write'] },
|
|
916
|
-
strategy: { type: 'string' },
|
|
917
|
-
limit: { type: 'number' },
|
|
918
|
-
},
|
|
919
|
-
output: {
|
|
920
|
-
schema: { type: 'array', items: { type: 'object', additionalProperties: false, properties: {
|
|
921
|
-
command: { type: 'string', required: true }, site: { type: 'string', required: true }, name: { type: 'string', required: true }, description: { type: 'string', required: true }, access: { type: 'string', required: true }, strategy: { type: 'string', required: true }, argsJson: { type: 'string', required: true }, example: { type: 'string' }, domain: { type: 'string' },
|
|
922
|
-
} } },
|
|
923
|
-
render: (_args, value) => [{ type: 'text', text: value.map(item => `${item.command} [${item.access}/${item.strategy}]\n${item.description}\nargs=${item.argsJson}${item.example ? '\nexample: ' + item.example : ''}`).join('\n\n') }],
|
|
924
|
-
},
|
|
925
|
-
timeoutMs: 75_000,
|
|
926
|
-
isConcurrencySafe: () => true,
|
|
927
|
-
async execute(args, exec) {
|
|
928
|
-
const items = await service.opencliCatalog(args, exec.signal);
|
|
929
|
-
return items.map(({ args: itemArgs, ...item }) => ({ ...item, argsJson: JSON.stringify(itemArgs) }));
|
|
137
|
+
runtime.refreshSkill?.();
|
|
138
|
+
return runAction(args.action, args.args, actionContext(exec), runEnv);
|
|
930
139
|
},
|
|
931
140
|
}));
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
},
|
|
945
|
-
},
|
|
946
|
-
render: (_args, value) => [{ type: 'text', text: `opencli exit=${value.code}${value.timedOut ? ' (timeout)' : ''}\n${(value.stdout || value.stderr).slice(0, 100_000)}` }],
|
|
947
|
-
},
|
|
948
|
-
timeoutMs: 180_000,
|
|
949
|
-
isConcurrencySafe: () => false,
|
|
141
|
+
}
|
|
142
|
+
/** One flat tool for one action: the same registry entry, projected as a native tool. */
|
|
143
|
+
function flatTool(action, actionContext, runEnv, prompts) {
|
|
144
|
+
const summary = prompts?.actions[action.name]?.summary ?? action.summary;
|
|
145
|
+
const notes = prompts?.actions[action.name]?.notes ?? action.notes;
|
|
146
|
+
return defineTool({
|
|
147
|
+
name: flatToolName(action),
|
|
148
|
+
description: summary + (notes ? ' ' + notes : '') + (action.group === 'runtime' || action.group === 'inspect' ? '' : ' ' + COMPLIANCE_NOTICE),
|
|
149
|
+
parameters: expandParams(action.params),
|
|
150
|
+
output: { schema: ENVELOPE_SCHEMA, render: renderEnvelope },
|
|
151
|
+
timeoutMs: action.timeoutMs + 5_000,
|
|
152
|
+
isConcurrencySafe: (args) => traitsFor(action, args).concurrencySafe,
|
|
950
153
|
async execute(args, exec) {
|
|
951
|
-
|
|
952
|
-
return service.opencli(argv, { timeoutMs: Math.min(Math.max(args.timeoutMs ?? 60_000, 1_000), 120_000), signal: exec.signal });
|
|
154
|
+
return runAction(action.name, args, actionContext(exec), runEnv);
|
|
953
155
|
},
|
|
954
|
-
})
|
|
156
|
+
});
|
|
955
157
|
}
|
|
956
158
|
//# sourceMappingURL=tools.js.map
|