ocsmarttools 0.1.9 → 0.1.11
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/CHANGELOG.md +27 -0
- package/README.md +19 -2
- package/openclaw.plugin.json +14 -0
- package/package.json +1 -1
- package/src/commands/operations.ts +23 -2
- package/src/lib/bootstrap.ts +36 -0
- package/src/lib/plugin-config.ts +51 -1
- package/src/lib/routing-guide.ts +17 -0
- package/src/lib/tool-catalog.ts +177 -23
- package/src/lib/web-fetch-fallback.ts +181 -0
- package/src/tools/tool-batch.ts +45 -2
- package/src/tools/tool-dispatch.ts +44 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `ocsmarttools` are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.1.11] - 2026-02-22
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Defaulted `strictRouting` to `true` for new installs/bootstrap paths.
|
|
9
|
+
- Added blocked-site fallback path for `web_fetch` failures:
|
|
10
|
+
- detect anti-bot/rate-limit/access-denied failures
|
|
11
|
+
- attempt browser fallback (`start` -> `open`/`navigate` -> `snapshot`)
|
|
12
|
+
- return native-handoff guidance for browser/skill workflows if fallback fails
|
|
13
|
+
- Added new config keys:
|
|
14
|
+
- `webFetchFallback.enabled`
|
|
15
|
+
- `webFetchFallback.browserTarget`
|
|
16
|
+
- `webFetchFallback.snapshotFormat`
|
|
17
|
+
- `webFetchFallback.snapshotLimit`
|
|
18
|
+
- Updated routing policy to explicitly allow browser/skill-guided recovery on blocked pages.
|
|
19
|
+
|
|
20
|
+
## [0.1.10] - 2026-02-22
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Expanded auto-routing trigger intents in managed `AGENTS.md` policy to cover:
|
|
24
|
+
- research/evidence
|
|
25
|
+
- reporting/writing
|
|
26
|
+
- coding/engineering
|
|
27
|
+
- data/extraction
|
|
28
|
+
- risk/compliance
|
|
29
|
+
- Expanded built-in `tool_search` keyword coverage across runtime, filesystem, web, browser, and session tools for broader intent matching.
|
|
30
|
+
- Improved `tool_search` scoring tokenization so punctuation-heavy terms (for example `CI/CD`) match more reliably.
|
|
31
|
+
|
|
5
32
|
## [0.1.9] - 2026-02-22
|
|
6
33
|
|
|
7
34
|
### Changed
|
package/README.md
CHANGED
|
@@ -15,7 +15,9 @@ OCSmartTools helps reduce latency and token cost by routing multi-step work thro
|
|
|
15
15
|
- `tool_result_get`
|
|
16
16
|
- Built-in metrics for success/failure/timeout, latency, and estimated savings
|
|
17
17
|
- Skill-compatible (including browser/Playwright-style workflows)
|
|
18
|
-
- Auto-managed routing guidance for research/report tasks (no per-prompt guardrails needed)
|
|
18
|
+
- Auto-managed routing guidance for research/report/coding/data tasks (no per-prompt guardrails needed)
|
|
19
|
+
- Strict routing is default-on for new installs
|
|
20
|
+
- Auto fallback for blocked pages: `web_fetch` -> browser snapshot path
|
|
19
21
|
|
|
20
22
|
## Install
|
|
21
23
|
|
|
@@ -96,11 +98,26 @@ Example:
|
|
|
96
98
|
- `safe`: requires sandbox and blocks control-plane dispatch
|
|
97
99
|
- `strictRouting=true`: enforces plugin-managed routing guidance and keeps routing block synced
|
|
98
100
|
|
|
101
|
+
Web fetch fallback defaults:
|
|
102
|
+
- `webFetchFallback.enabled=true`
|
|
103
|
+
- `webFetchFallback.browserTarget=auto`
|
|
104
|
+
- `webFetchFallback.snapshotFormat=ai`
|
|
105
|
+
- `webFetchFallback.snapshotLimit=12000`
|
|
106
|
+
|
|
107
|
+
## Auto Trigger Intents
|
|
108
|
+
|
|
109
|
+
When tools are needed, routing is auto-biased toward `tool_dispatch` / `tool_batch` for these intent families:
|
|
110
|
+
- research/evidence (sources, benchmarks, case studies, latest/news)
|
|
111
|
+
- reporting/writing (reports, briefs, proposals, plans, emails, docs/specs)
|
|
112
|
+
- coding/engineering (build, debug, fix, test, refactor, deploy, migration)
|
|
113
|
+
- data/extraction (scrape/fetch/parse/aggregate/rank/CSV/JSON/ETL)
|
|
114
|
+
- risk/compliance (security/privacy/legal/audit requests)
|
|
115
|
+
|
|
99
116
|
## Skills and Blocked Sites
|
|
100
117
|
|
|
101
118
|
Skills in OpenClaw are instruction layers, not separate execution engines. OCSmartTools can still dispatch/batch the underlying tools used by skills.
|
|
102
119
|
|
|
103
|
-
For websites that block plain fetch,
|
|
120
|
+
For websites that block plain fetch, OCSmartTools now tries browser fallback automatically. If that also fails, it returns a native-handoff recommendation so OpenClaw can continue with browser tools or skill-guided Playwright/stealth workflows.
|
|
104
121
|
|
|
105
122
|
## Safety Notes
|
|
106
123
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -19,6 +19,16 @@
|
|
|
19
19
|
"resultSampleItems": { "type": "integer", "minimum": 1, "maximum": 50 },
|
|
20
20
|
"requireSandbox": { "type": "boolean" },
|
|
21
21
|
"denyControlPlane": { "type": "boolean" },
|
|
22
|
+
"webFetchFallback": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"properties": {
|
|
26
|
+
"enabled": { "type": "boolean" },
|
|
27
|
+
"browserTarget": { "type": "string", "enum": ["auto", "sandbox", "host", "node"] },
|
|
28
|
+
"snapshotFormat": { "type": "string", "enum": ["ai", "aria"] },
|
|
29
|
+
"snapshotLimit": { "type": "integer", "minimum": 1000, "maximum": 50000 }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
22
32
|
"toolSearch": {
|
|
23
33
|
"type": "object",
|
|
24
34
|
"additionalProperties": false,
|
|
@@ -44,6 +54,10 @@
|
|
|
44
54
|
"resultSampleItems": { "label": "Result Sample Items", "advanced": true },
|
|
45
55
|
"requireSandbox": { "label": "Require Sandbox", "advanced": true },
|
|
46
56
|
"denyControlPlane": { "label": "Deny Control Plane", "advanced": true },
|
|
57
|
+
"webFetchFallback.enabled": { "label": "Enable Web Fetch Fallback" },
|
|
58
|
+
"webFetchFallback.browserTarget": { "label": "Fallback Browser Target", "advanced": true },
|
|
59
|
+
"webFetchFallback.snapshotFormat": { "label": "Fallback Snapshot Format", "advanced": true },
|
|
60
|
+
"webFetchFallback.snapshotLimit": { "label": "Fallback Snapshot Limit", "advanced": true },
|
|
47
61
|
"toolSearch.enabled": { "label": "Enable Tool Search" },
|
|
48
62
|
"toolSearch.defaultLimit": { "label": "Tool Search Default Limit", "advanced": true },
|
|
49
63
|
"toolSearch.useLiveRegistry": { "label": "Use Live Tool Registry", "advanced": true },
|
package/package.json
CHANGED
|
@@ -32,6 +32,10 @@ const CONFIG_SPECS: Record<string, ConfigSpec> = {
|
|
|
32
32
|
resultSampleItems: { kind: "integer", min: 1, max: 50 },
|
|
33
33
|
requireSandbox: { kind: "boolean" },
|
|
34
34
|
denyControlPlane: { kind: "boolean" },
|
|
35
|
+
"webFetchFallback.enabled": { kind: "boolean" },
|
|
36
|
+
"webFetchFallback.browserTarget": { kind: "enum", values: ["auto", "sandbox", "host", "node"] },
|
|
37
|
+
"webFetchFallback.snapshotFormat": { kind: "enum", values: ["ai", "aria"] },
|
|
38
|
+
"webFetchFallback.snapshotLimit": { kind: "integer", min: 1000, max: 50000 },
|
|
35
39
|
"toolSearch.enabled": { kind: "boolean" },
|
|
36
40
|
"toolSearch.defaultLimit": { kind: "integer", min: 1, max: 50 },
|
|
37
41
|
"toolSearch.useLiveRegistry": { kind: "boolean" },
|
|
@@ -52,6 +56,10 @@ const DEFAULT_BY_KEY: Record<string, boolean | number | string> = {
|
|
|
52
56
|
resultSampleItems: DEFAULT_SETTINGS.resultSampleItems,
|
|
53
57
|
requireSandbox: DEFAULT_SETTINGS.requireSandbox,
|
|
54
58
|
denyControlPlane: DEFAULT_SETTINGS.denyControlPlane,
|
|
59
|
+
"webFetchFallback.enabled": DEFAULT_SETTINGS.webFetchFallback.enabled,
|
|
60
|
+
"webFetchFallback.browserTarget": DEFAULT_SETTINGS.webFetchFallback.browserTarget,
|
|
61
|
+
"webFetchFallback.snapshotFormat": DEFAULT_SETTINGS.webFetchFallback.snapshotFormat,
|
|
62
|
+
"webFetchFallback.snapshotLimit": DEFAULT_SETTINGS.webFetchFallback.snapshotLimit,
|
|
55
63
|
"toolSearch.enabled": DEFAULT_SETTINGS.toolSearch.enabled,
|
|
56
64
|
"toolSearch.defaultLimit": DEFAULT_SETTINGS.toolSearch.defaultLimit,
|
|
57
65
|
"toolSearch.useLiveRegistry": DEFAULT_SETTINGS.toolSearch.useLiveRegistry,
|
|
@@ -183,6 +191,8 @@ export function renderHelp(): string {
|
|
|
183
191
|
"",
|
|
184
192
|
"Quick examples:",
|
|
185
193
|
"- /ocsmarttools status",
|
|
194
|
+
"- /ocsmarttools config set webFetchFallback.enabled true",
|
|
195
|
+
"- /ocsmarttools config set webFetchFallback.browserTarget auto",
|
|
186
196
|
"- /ocsmarttools config set maxResultChars 120000",
|
|
187
197
|
"- /ocsmarttools strict on",
|
|
188
198
|
"- /ocsmarttools stats",
|
|
@@ -253,6 +263,10 @@ export function renderConfig(api: OpenClawPluginApi): string {
|
|
|
253
263
|
`- resultSampleItems: ${s.resultSampleItems}`,
|
|
254
264
|
`- requireSandbox: ${s.requireSandbox}`,
|
|
255
265
|
`- denyControlPlane: ${s.denyControlPlane}`,
|
|
266
|
+
`- webFetchFallback.enabled: ${s.webFetchFallback.enabled}`,
|
|
267
|
+
`- webFetchFallback.browserTarget: ${s.webFetchFallback.browserTarget}`,
|
|
268
|
+
`- webFetchFallback.snapshotFormat: ${s.webFetchFallback.snapshotFormat}`,
|
|
269
|
+
`- webFetchFallback.snapshotLimit: ${s.webFetchFallback.snapshotLimit}`,
|
|
256
270
|
`- toolSearch.enabled: ${s.toolSearch.enabled}`,
|
|
257
271
|
`- toolSearch.defaultLimit: ${s.toolSearch.defaultLimit}`,
|
|
258
272
|
`- toolSearch.useLiveRegistry: ${s.toolSearch.useLiveRegistry}`,
|
|
@@ -375,7 +389,7 @@ export async function applySetup(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
|
|
|
375
389
|
|
|
376
390
|
pluginCfg.enabled = true;
|
|
377
391
|
pluginCfg.mode = mode;
|
|
378
|
-
pluginCfg.strictRouting =
|
|
392
|
+
pluginCfg.strictRouting = true;
|
|
379
393
|
pluginCfg.autoInjectRoutingGuide = true;
|
|
380
394
|
pluginCfg.maxSteps = DEFAULT_SETTINGS.maxSteps;
|
|
381
395
|
pluginCfg.maxForEach = DEFAULT_SETTINGS.maxForEach;
|
|
@@ -386,6 +400,12 @@ export async function applySetup(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
|
|
|
386
400
|
pluginCfg.resultSampleItems = DEFAULT_SETTINGS.resultSampleItems;
|
|
387
401
|
pluginCfg.requireSandbox = mode === "safe";
|
|
388
402
|
pluginCfg.denyControlPlane = true;
|
|
403
|
+
pluginCfg.webFetchFallback = {
|
|
404
|
+
enabled: DEFAULT_SETTINGS.webFetchFallback.enabled,
|
|
405
|
+
browserTarget: DEFAULT_SETTINGS.webFetchFallback.browserTarget,
|
|
406
|
+
snapshotFormat: DEFAULT_SETTINGS.webFetchFallback.snapshotFormat,
|
|
407
|
+
snapshotLimit: DEFAULT_SETTINGS.webFetchFallback.snapshotLimit,
|
|
408
|
+
};
|
|
389
409
|
pluginCfg.toolSearch = {
|
|
390
410
|
enabled: true,
|
|
391
411
|
defaultLimit: DEFAULT_SETTINGS.toolSearch.defaultLimit,
|
|
@@ -401,7 +421,7 @@ export async function applySetup(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
|
|
|
401
421
|
);
|
|
402
422
|
|
|
403
423
|
await writeConfig(api, next);
|
|
404
|
-
const sync = await syncRoutingGuide(api, next, { strictRouting:
|
|
424
|
+
const sync = await syncRoutingGuide(api, next, { strictRouting: true });
|
|
405
425
|
const syncLine = sync.error
|
|
406
426
|
? `- routing guide sync skipped: ${sync.error}`
|
|
407
427
|
: `- routing guide ${sync.changed ? "synced" : "already up to date"} (${sync.filePath ?? "AGENTS.md"})`;
|
|
@@ -409,6 +429,7 @@ export async function applySetup(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
|
|
|
409
429
|
return [
|
|
410
430
|
"OCSmartTools setup applied.",
|
|
411
431
|
`- mode: ${mode}`,
|
|
432
|
+
"- strictRouting: true",
|
|
412
433
|
"- autoInjectRoutingGuide: true",
|
|
413
434
|
syncLine,
|
|
414
435
|
`- ensured tools.allow includes: ${ADVTOOLS_TOOL_NAMES.join(", ")}`,
|
package/src/lib/bootstrap.ts
CHANGED
|
@@ -57,6 +57,42 @@ export async function autoBootstrap(api: OpenClawPluginApi): Promise<{ changed:
|
|
|
57
57
|
setDefault("resultSampleItems", DEFAULT_SETTINGS.resultSampleItems);
|
|
58
58
|
setDefault("requireSandbox", DEFAULT_SETTINGS.requireSandbox);
|
|
59
59
|
setDefault("denyControlPlane", DEFAULT_SETTINGS.denyControlPlane);
|
|
60
|
+
if (pluginCfg.webFetchFallback === undefined) {
|
|
61
|
+
pluginCfg.webFetchFallback = {
|
|
62
|
+
enabled: DEFAULT_SETTINGS.webFetchFallback.enabled,
|
|
63
|
+
browserTarget: DEFAULT_SETTINGS.webFetchFallback.browserTarget,
|
|
64
|
+
snapshotFormat: DEFAULT_SETTINGS.webFetchFallback.snapshotFormat,
|
|
65
|
+
snapshotLimit: DEFAULT_SETTINGS.webFetchFallback.snapshotLimit,
|
|
66
|
+
};
|
|
67
|
+
changed = true;
|
|
68
|
+
notes.push("set default webFetchFallback");
|
|
69
|
+
} else {
|
|
70
|
+
const wf = asObj(pluginCfg.webFetchFallback);
|
|
71
|
+
if (pluginCfg.webFetchFallback !== wf) {
|
|
72
|
+
pluginCfg.webFetchFallback = wf;
|
|
73
|
+
changed = true;
|
|
74
|
+
}
|
|
75
|
+
if (wf.enabled === undefined) {
|
|
76
|
+
wf.enabled = DEFAULT_SETTINGS.webFetchFallback.enabled;
|
|
77
|
+
changed = true;
|
|
78
|
+
notes.push("set default webFetchFallback.enabled");
|
|
79
|
+
}
|
|
80
|
+
if (wf.browserTarget === undefined) {
|
|
81
|
+
wf.browserTarget = DEFAULT_SETTINGS.webFetchFallback.browserTarget;
|
|
82
|
+
changed = true;
|
|
83
|
+
notes.push("set default webFetchFallback.browserTarget");
|
|
84
|
+
}
|
|
85
|
+
if (wf.snapshotFormat === undefined) {
|
|
86
|
+
wf.snapshotFormat = DEFAULT_SETTINGS.webFetchFallback.snapshotFormat;
|
|
87
|
+
changed = true;
|
|
88
|
+
notes.push("set default webFetchFallback.snapshotFormat");
|
|
89
|
+
}
|
|
90
|
+
if (wf.snapshotLimit === undefined) {
|
|
91
|
+
wf.snapshotLimit = DEFAULT_SETTINGS.webFetchFallback.snapshotLimit;
|
|
92
|
+
changed = true;
|
|
93
|
+
notes.push("set default webFetchFallback.snapshotLimit");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
60
96
|
if (pluginCfg.toolSearch === undefined) {
|
|
61
97
|
pluginCfg.toolSearch = {
|
|
62
98
|
enabled: DEFAULT_SETTINGS.toolSearch.enabled,
|
package/src/lib/plugin-config.ts
CHANGED
|
@@ -16,6 +16,12 @@ export type AdvToolsSettings = {
|
|
|
16
16
|
resultSampleItems: number;
|
|
17
17
|
requireSandbox: boolean;
|
|
18
18
|
denyControlPlane: boolean;
|
|
19
|
+
webFetchFallback: {
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
browserTarget: "auto" | "sandbox" | "host" | "node";
|
|
22
|
+
snapshotFormat: "ai" | "aria";
|
|
23
|
+
snapshotLimit: number;
|
|
24
|
+
};
|
|
19
25
|
toolSearch: {
|
|
20
26
|
enabled: boolean;
|
|
21
27
|
defaultLimit: number;
|
|
@@ -27,7 +33,7 @@ export type AdvToolsSettings = {
|
|
|
27
33
|
export const DEFAULT_SETTINGS: AdvToolsSettings = {
|
|
28
34
|
enabled: true,
|
|
29
35
|
mode: "standard",
|
|
30
|
-
strictRouting:
|
|
36
|
+
strictRouting: true,
|
|
31
37
|
autoInjectRoutingGuide: true,
|
|
32
38
|
maxSteps: 25,
|
|
33
39
|
maxForEach: 20,
|
|
@@ -38,6 +44,12 @@ export const DEFAULT_SETTINGS: AdvToolsSettings = {
|
|
|
38
44
|
resultSampleItems: 8,
|
|
39
45
|
requireSandbox: false,
|
|
40
46
|
denyControlPlane: true,
|
|
47
|
+
webFetchFallback: {
|
|
48
|
+
enabled: true,
|
|
49
|
+
browserTarget: "auto",
|
|
50
|
+
snapshotFormat: "ai",
|
|
51
|
+
snapshotLimit: 12000,
|
|
52
|
+
},
|
|
41
53
|
toolSearch: {
|
|
42
54
|
enabled: true,
|
|
43
55
|
defaultLimit: 8,
|
|
@@ -67,12 +79,33 @@ function asMode(value: unknown, fallback: AdvToolsMode): AdvToolsMode {
|
|
|
67
79
|
return value === "safe" || value === "standard" ? value : fallback;
|
|
68
80
|
}
|
|
69
81
|
|
|
82
|
+
function asBrowserTarget(
|
|
83
|
+
value: unknown,
|
|
84
|
+
fallback: AdvToolsSettings["webFetchFallback"]["browserTarget"],
|
|
85
|
+
): AdvToolsSettings["webFetchFallback"]["browserTarget"] {
|
|
86
|
+
if (value === "auto" || value === "sandbox" || value === "host" || value === "node") {
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
return fallback;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function asSnapshotFormat(
|
|
93
|
+
value: unknown,
|
|
94
|
+
fallback: AdvToolsSettings["webFetchFallback"]["snapshotFormat"],
|
|
95
|
+
): AdvToolsSettings["webFetchFallback"]["snapshotFormat"] {
|
|
96
|
+
if (value === "ai" || value === "aria") {
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
return fallback;
|
|
100
|
+
}
|
|
101
|
+
|
|
70
102
|
export function resolveSettings(api: OpenClawPluginApi, cfg: OpenClawConfig = api.config): AdvToolsSettings {
|
|
71
103
|
const plugins = asObj((cfg as Record<string, unknown>).plugins);
|
|
72
104
|
const entries = asObj(plugins.entries);
|
|
73
105
|
const entry = asObj(entries[api.id]);
|
|
74
106
|
const pluginCfg = asObj(entry.config ?? api.pluginConfig);
|
|
75
107
|
const ts = asObj(pluginCfg.toolSearch);
|
|
108
|
+
const wf = asObj(pluginCfg.webFetchFallback);
|
|
76
109
|
|
|
77
110
|
const strictRouting = asBool(pluginCfg.strictRouting, DEFAULT_SETTINGS.strictRouting);
|
|
78
111
|
const autoInjectRoutingGuide = strictRouting
|
|
@@ -103,6 +136,23 @@ export function resolveSettings(api: OpenClawPluginApi, cfg: OpenClawConfig = ap
|
|
|
103
136
|
),
|
|
104
137
|
requireSandbox: asBool(pluginCfg.requireSandbox, DEFAULT_SETTINGS.requireSandbox),
|
|
105
138
|
denyControlPlane: asBool(pluginCfg.denyControlPlane, DEFAULT_SETTINGS.denyControlPlane),
|
|
139
|
+
webFetchFallback: {
|
|
140
|
+
enabled: asBool(wf.enabled, DEFAULT_SETTINGS.webFetchFallback.enabled),
|
|
141
|
+
browserTarget: asBrowserTarget(
|
|
142
|
+
wf.browserTarget,
|
|
143
|
+
DEFAULT_SETTINGS.webFetchFallback.browserTarget,
|
|
144
|
+
),
|
|
145
|
+
snapshotFormat: asSnapshotFormat(
|
|
146
|
+
wf.snapshotFormat,
|
|
147
|
+
DEFAULT_SETTINGS.webFetchFallback.snapshotFormat,
|
|
148
|
+
),
|
|
149
|
+
snapshotLimit: asInt(
|
|
150
|
+
wf.snapshotLimit,
|
|
151
|
+
DEFAULT_SETTINGS.webFetchFallback.snapshotLimit,
|
|
152
|
+
1000,
|
|
153
|
+
50000,
|
|
154
|
+
),
|
|
155
|
+
},
|
|
106
156
|
toolSearch: {
|
|
107
157
|
enabled: asBool(ts.enabled, DEFAULT_SETTINGS.toolSearch.enabled),
|
|
108
158
|
defaultLimit: asInt(ts.defaultLimit, DEFAULT_SETTINGS.toolSearch.defaultLimit, 1, 50),
|
package/src/lib/routing-guide.ts
CHANGED
|
@@ -6,6 +6,15 @@ import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
|
6
6
|
const BLOCK_START = "<!-- OCSMARTTOOLS_ROUTING_START -->";
|
|
7
7
|
const BLOCK_END = "<!-- OCSMARTTOOLS_ROUTING_END -->";
|
|
8
8
|
|
|
9
|
+
const AUTO_TRIGGER_LINES = [
|
|
10
|
+
"Auto-route trigger intents (use routing tools by default when tools are needed):",
|
|
11
|
+
"- Research and evidence: research, investigate, analyze, benchmark, compare, evaluate, market intelligence, trends, latest, news, sources, citations, references, case study, due diligence.",
|
|
12
|
+
"- Reporting and writing: report, summary, brief, memo, presentation, plan, proposal, recommendation, email, RFC, PRD, specification, documentation, SOP.",
|
|
13
|
+
"- Coding and engineering: code, implement, build, website, app, script, debug, fix, bug, stack trace, test, lint, refactor, optimize, compile, deploy, CI/CD, migration, release notes.",
|
|
14
|
+
"- Data and extraction: scrape, crawl, fetch, parse, transform, aggregate, rank, classify, table, CSV, JSON, dataset, ETL.",
|
|
15
|
+
"- Risk and compliance: risk register, security review, privacy, legal, policy, SOC 2, GDPR, HIPAA, audit.",
|
|
16
|
+
];
|
|
17
|
+
|
|
9
18
|
function buildRoutingBlock(strictRouting: boolean): string {
|
|
10
19
|
const body = strictRouting
|
|
11
20
|
? [
|
|
@@ -22,8 +31,12 @@ function buildRoutingBlock(strictRouting: boolean): string {
|
|
|
22
31
|
"6. Avoid direct native tool calls unless routing tools are unavailable due to runtime/tool-policy constraints.",
|
|
23
32
|
"7. Do not present citations/sources unless they came from successful tool calls in the current run.",
|
|
24
33
|
"8. If required retrieval fails, return a clear partial/failure report instead of fabricated evidence.",
|
|
34
|
+
"9. Treat trigger intents below as routing-required unless the request is clearly a no-tool task.",
|
|
35
|
+
"10. If `web_fetch` is blocked (anti-bot/captcha), prefer browser fallback; if fallback also fails, allow native browser/skill-guided flows (for example Playwright stealth skills).",
|
|
25
36
|
"",
|
|
26
37
|
"Common large/noisy tools: `web_fetch`, `read` (large files), `exec`, `process`, `browser`, `nodes`.",
|
|
38
|
+
"",
|
|
39
|
+
...AUTO_TRIGGER_LINES,
|
|
27
40
|
].join("\n")
|
|
28
41
|
: [
|
|
29
42
|
"## OCSmartTools Routing Policy (Auto-Managed)",
|
|
@@ -38,8 +51,12 @@ function buildRoutingBlock(strictRouting: boolean): string {
|
|
|
38
51
|
"6. Use direct native tool calls only for simple one-shot small-output actions.",
|
|
39
52
|
"7. Do not include citations/sources that were not retrieved by tools in the current run.",
|
|
40
53
|
"8. If retrieval fails, explicitly report the gap.",
|
|
54
|
+
"9. Treat trigger intents below as strong routing signals unless the task is clearly no-tool.",
|
|
55
|
+
"10. For blocked `web_fetch` pages, allow browser/skill-guided fallback paths.",
|
|
41
56
|
"",
|
|
42
57
|
"Common large/noisy tools: `web_fetch`, `read` (large files), `exec`, `process`, `browser`, `nodes`.",
|
|
58
|
+
"",
|
|
59
|
+
...AUTO_TRIGGER_LINES,
|
|
43
60
|
].join("\n");
|
|
44
61
|
|
|
45
62
|
return `${BLOCK_START}
|
package/src/lib/tool-catalog.ts
CHANGED
|
@@ -9,34 +9,184 @@ export type ToolEntry = {
|
|
|
9
9
|
source: "builtin" | "policy" | "live";
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
const RESEARCH_KEYWORDS = [
|
|
13
|
+
"research",
|
|
14
|
+
"investigate",
|
|
15
|
+
"analyze",
|
|
16
|
+
"analysis",
|
|
17
|
+
"benchmark",
|
|
18
|
+
"compare",
|
|
19
|
+
"evaluate",
|
|
20
|
+
"market intelligence",
|
|
21
|
+
"trend",
|
|
22
|
+
"latest",
|
|
23
|
+
"news",
|
|
24
|
+
"evidence",
|
|
25
|
+
"sources",
|
|
26
|
+
"citations",
|
|
27
|
+
"references",
|
|
28
|
+
"case study",
|
|
29
|
+
"due diligence",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const REPORT_KEYWORDS = [
|
|
33
|
+
"report",
|
|
34
|
+
"summary",
|
|
35
|
+
"brief",
|
|
36
|
+
"memo",
|
|
37
|
+
"recommendation",
|
|
38
|
+
"proposal",
|
|
39
|
+
"plan",
|
|
40
|
+
"writeup",
|
|
41
|
+
"presentation",
|
|
42
|
+
"email",
|
|
43
|
+
"documentation",
|
|
44
|
+
"spec",
|
|
45
|
+
"rfc",
|
|
46
|
+
"prd",
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const CODING_KEYWORDS = [
|
|
50
|
+
"code",
|
|
51
|
+
"implement",
|
|
52
|
+
"build",
|
|
53
|
+
"website",
|
|
54
|
+
"app",
|
|
55
|
+
"script",
|
|
56
|
+
"debug",
|
|
57
|
+
"fix",
|
|
58
|
+
"bug",
|
|
59
|
+
"stack trace",
|
|
60
|
+
"test",
|
|
61
|
+
"lint",
|
|
62
|
+
"refactor",
|
|
63
|
+
"optimize",
|
|
64
|
+
"compile",
|
|
65
|
+
"deploy",
|
|
66
|
+
"ci",
|
|
67
|
+
"cd",
|
|
68
|
+
"migration",
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const DATA_KEYWORDS = [
|
|
72
|
+
"extract",
|
|
73
|
+
"scrape",
|
|
74
|
+
"crawl",
|
|
75
|
+
"parse",
|
|
76
|
+
"transform",
|
|
77
|
+
"aggregate",
|
|
78
|
+
"rank",
|
|
79
|
+
"classify",
|
|
80
|
+
"csv",
|
|
81
|
+
"json",
|
|
82
|
+
"dataset",
|
|
83
|
+
"etl",
|
|
84
|
+
"table",
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const BROWSER_KEYWORDS = [
|
|
88
|
+
"playwright",
|
|
89
|
+
"stealth",
|
|
90
|
+
"anti-bot",
|
|
91
|
+
"captcha",
|
|
92
|
+
"dynamic site",
|
|
93
|
+
"javascript page",
|
|
94
|
+
"login wall",
|
|
95
|
+
"rendered page",
|
|
96
|
+
"automation",
|
|
97
|
+
];
|
|
98
|
+
|
|
12
99
|
const BUILTIN: ToolEntry[] = [
|
|
13
100
|
{
|
|
14
101
|
name: "exec",
|
|
15
102
|
group: "runtime",
|
|
16
103
|
description: "Run shell commands.",
|
|
17
104
|
paramsHint: "{ command }",
|
|
18
|
-
keywords: [
|
|
105
|
+
keywords: [
|
|
106
|
+
"shell",
|
|
107
|
+
"terminal",
|
|
108
|
+
"command line",
|
|
109
|
+
"python",
|
|
110
|
+
"node",
|
|
111
|
+
"npm",
|
|
112
|
+
"git",
|
|
113
|
+
"build",
|
|
114
|
+
"run tests",
|
|
115
|
+
"start server",
|
|
116
|
+
...CODING_KEYWORDS,
|
|
117
|
+
...DATA_KEYWORDS,
|
|
118
|
+
...BROWSER_KEYWORDS,
|
|
119
|
+
],
|
|
120
|
+
source: "builtin",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "bash",
|
|
124
|
+
group: "runtime",
|
|
125
|
+
description: "Alias for exec in many contexts.",
|
|
126
|
+
paramsHint: "{ command }",
|
|
127
|
+
keywords: ["shell", "terminal", "script", ...CODING_KEYWORDS],
|
|
128
|
+
source: "builtin",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "process",
|
|
132
|
+
group: "runtime",
|
|
133
|
+
description: "Manage background command sessions.",
|
|
134
|
+
paramsHint: "{ action, sessionId? }",
|
|
135
|
+
keywords: ["long running", "background job", "watch logs", "daemon", "worker", "server process", "job status"],
|
|
136
|
+
source: "builtin",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "read",
|
|
140
|
+
group: "fs",
|
|
141
|
+
description: "Read files.",
|
|
142
|
+
paramsHint: "{ path }",
|
|
143
|
+
keywords: ["open file", "inspect file", "view logs", "read config", "read docs", "audit code", ...REPORT_KEYWORDS],
|
|
144
|
+
source: "builtin",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "write",
|
|
148
|
+
group: "fs",
|
|
149
|
+
description: "Write files.",
|
|
150
|
+
paramsHint: "{ path, content }",
|
|
151
|
+
keywords: ["create file", "save output", "generate report", "draft", "write docs", "export", ...REPORT_KEYWORDS],
|
|
152
|
+
source: "builtin",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "edit",
|
|
156
|
+
group: "fs",
|
|
157
|
+
description: "Patch file text by replacement.",
|
|
158
|
+
paramsHint: "{ path, oldText, newText }",
|
|
159
|
+
keywords: ["modify file", "update code", "refactor file", "fix snippet", ...CODING_KEYWORDS],
|
|
160
|
+
source: "builtin",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "apply_patch",
|
|
164
|
+
group: "fs",
|
|
165
|
+
description: "Apply unified-style file patch.",
|
|
166
|
+
paramsHint: "{ input }",
|
|
167
|
+
keywords: ["multi-file patch", "diff", "code patch", "batch edits", "surgical edit", ...CODING_KEYWORDS],
|
|
168
|
+
source: "builtin",
|
|
169
|
+
},
|
|
170
|
+
{ name: "sessions_list", group: "sessions", description: "List sessions.", paramsHint: "{ ... }", keywords: ["session", "agents", "workers"], source: "builtin" },
|
|
171
|
+
{ name: "sessions_history", group: "sessions", description: "Read session history.", paramsHint: "{ sessionKey? }", keywords: ["conversation history", "chat log", "audit trail"], source: "builtin" },
|
|
172
|
+
{ name: "sessions_send", group: "sessions", description: "Send to another session.", paramsHint: "{ ... }", keywords: ["delegate", "handoff", "subtask"], source: "builtin" },
|
|
173
|
+
{ name: "sessions_spawn", group: "sessions", description: "Spawn subagent session.", paramsHint: "{ prompt, ... }", keywords: ["spawn agent", "parallel agent", "subagent", "delegate"], source: "builtin" },
|
|
174
|
+
{ name: "session_status", group: "sessions", description: "Show current session status.", paramsHint: "{}", keywords: ["status", "health", "progress"], source: "builtin" },
|
|
175
|
+
{
|
|
176
|
+
name: "memory_search",
|
|
177
|
+
group: "memory",
|
|
178
|
+
description: "Search memory snippets.",
|
|
179
|
+
paramsHint: "{ query }",
|
|
180
|
+
keywords: ["recall", "memory", "previous notes", "past decisions", "context lookup"],
|
|
19
181
|
source: "builtin",
|
|
20
182
|
},
|
|
21
|
-
{ name: "
|
|
22
|
-
{ name: "process", group: "runtime", description: "Manage background command sessions.", paramsHint: "{ action, sessionId? }", source: "builtin" },
|
|
23
|
-
{ name: "read", group: "fs", description: "Read files.", paramsHint: "{ path }", source: "builtin" },
|
|
24
|
-
{ name: "write", group: "fs", description: "Write files.", paramsHint: "{ path, content }", source: "builtin" },
|
|
25
|
-
{ name: "edit", group: "fs", description: "Patch file text by replacement.", paramsHint: "{ path, oldText, newText }", source: "builtin" },
|
|
26
|
-
{ name: "apply_patch", group: "fs", description: "Apply unified-style file patch.", paramsHint: "{ input }", source: "builtin" },
|
|
27
|
-
{ name: "sessions_list", group: "sessions", description: "List sessions.", paramsHint: "{ ... }", source: "builtin" },
|
|
28
|
-
{ name: "sessions_history", group: "sessions", description: "Read session history.", paramsHint: "{ sessionKey? }", source: "builtin" },
|
|
29
|
-
{ name: "sessions_send", group: "sessions", description: "Send to another session.", paramsHint: "{ ... }", source: "builtin" },
|
|
30
|
-
{ name: "sessions_spawn", group: "sessions", description: "Spawn subagent session.", paramsHint: "{ prompt, ... }", source: "builtin" },
|
|
31
|
-
{ name: "session_status", group: "sessions", description: "Show current session status.", paramsHint: "{}", source: "builtin" },
|
|
32
|
-
{ name: "memory_search", group: "memory", description: "Search memory snippets.", paramsHint: "{ query }", source: "builtin" },
|
|
33
|
-
{ name: "memory_get", group: "memory", description: "Read memory entries.", paramsHint: "{ key }", source: "builtin" },
|
|
183
|
+
{ name: "memory_get", group: "memory", description: "Read memory entries.", paramsHint: "{ key }", keywords: ["memory key", "stored context", "knowledge"], source: "builtin" },
|
|
34
184
|
{
|
|
35
185
|
name: "web_search",
|
|
36
186
|
group: "web",
|
|
37
187
|
description: "Search the web.",
|
|
38
188
|
paramsHint: "{ query }",
|
|
39
|
-
keywords: ["
|
|
189
|
+
keywords: ["discover", "find", "lookup", ...RESEARCH_KEYWORDS, ...REPORT_KEYWORDS],
|
|
40
190
|
source: "builtin",
|
|
41
191
|
},
|
|
42
192
|
{
|
|
@@ -44,7 +194,7 @@ const BUILTIN: ToolEntry[] = [
|
|
|
44
194
|
group: "web",
|
|
45
195
|
description: "Fetch and extract web page content.",
|
|
46
196
|
paramsHint: "{ url }",
|
|
47
|
-
keywords: ["read page", "extract", "html"],
|
|
197
|
+
keywords: ["read page", "fetch url", "extract", "html", "article text", "content extraction", ...DATA_KEYWORDS, ...RESEARCH_KEYWORDS],
|
|
48
198
|
source: "builtin",
|
|
49
199
|
},
|
|
50
200
|
{
|
|
@@ -52,14 +202,14 @@ const BUILTIN: ToolEntry[] = [
|
|
|
52
202
|
group: "ui",
|
|
53
203
|
description: "Browser automation.",
|
|
54
204
|
paramsHint: "{ action, ... }",
|
|
55
|
-
keywords: [
|
|
205
|
+
keywords: [...BROWSER_KEYWORDS, "web navigation", "click", "form fill", "screenshot", "scrape dynamic"],
|
|
56
206
|
source: "builtin",
|
|
57
207
|
},
|
|
58
|
-
{ name: "canvas", group: "ui", description: "Canvas/artifact rendering actions.", paramsHint: "{ action, ... }", source: "builtin" },
|
|
59
|
-
{ name: "cron", group: "automation", description: "Manage scheduled jobs.", paramsHint: "{ action, ... }", source: "builtin" },
|
|
60
|
-
{ name: "gateway", group: "automation", description: "Gateway control-plane actions.", paramsHint: "{ action, ... }", source: "builtin" },
|
|
61
|
-
{ name: "message", group: "messaging", description: "Message actions across channels.", paramsHint: "{ action, ... }", source: "builtin" },
|
|
62
|
-
{ name: "nodes", group: "nodes", description: "Invoke node-side capabilities.", paramsHint: "{ action, ... }", source: "builtin" },
|
|
208
|
+
{ name: "canvas", group: "ui", description: "Canvas/artifact rendering actions.", paramsHint: "{ action, ... }", keywords: ["diagram", "visual", "chart", "artifact"], source: "builtin" },
|
|
209
|
+
{ name: "cron", group: "automation", description: "Manage scheduled jobs.", paramsHint: "{ action, ... }", keywords: ["schedule", "recurring", "automation", "job timer"], source: "builtin" },
|
|
210
|
+
{ name: "gateway", group: "automation", description: "Gateway control-plane actions.", paramsHint: "{ action, ... }", keywords: ["gateway control", "restart", "config patch", "service"], source: "builtin" },
|
|
211
|
+
{ name: "message", group: "messaging", description: "Message actions across channels.", paramsHint: "{ action, ... }", keywords: ["notify", "chat", "post update", "send message"], source: "builtin" },
|
|
212
|
+
{ name: "nodes", group: "nodes", description: "Invoke node-side capabilities.", paramsHint: "{ action, ... }", keywords: ["device", "camera", "screen", "location", "node action"], source: "builtin" },
|
|
63
213
|
];
|
|
64
214
|
|
|
65
215
|
const GROUP_NAMES = new Set([
|
|
@@ -328,6 +478,10 @@ function score(query: string, entry: ToolEntry): number {
|
|
|
328
478
|
const d = entry.description.toLowerCase();
|
|
329
479
|
const g = entry.group.toLowerCase();
|
|
330
480
|
const keywords = (entry.keywords ?? []).map((v) => v.toLowerCase());
|
|
481
|
+
const tokens = q
|
|
482
|
+
.split(/[^a-z0-9]+/)
|
|
483
|
+
.map((t) => t.trim())
|
|
484
|
+
.filter(Boolean);
|
|
331
485
|
|
|
332
486
|
let s = 0;
|
|
333
487
|
if (n === q) s += 100;
|
|
@@ -337,7 +491,7 @@ function score(query: string, entry: ToolEntry): number {
|
|
|
337
491
|
if (d.includes(q)) s += 10;
|
|
338
492
|
if (keywords.includes(q)) s += 25;
|
|
339
493
|
|
|
340
|
-
for (const token of
|
|
494
|
+
for (const token of tokens) {
|
|
341
495
|
if (n.includes(token)) s += 12;
|
|
342
496
|
if (d.includes(token)) s += 4;
|
|
343
497
|
if (g.includes(token)) s += 4;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { AdvToolsSettings } from "./plugin-config.js";
|
|
3
|
+
import type { InvokeResult } from "./invoke.js";
|
|
4
|
+
import { invokeToolViaGateway } from "./invoke.js";
|
|
5
|
+
|
|
6
|
+
const BLOCKED_STATUSES = new Set([401, 403, 406, 409, 410, 412, 418, 425, 429, 451, 503]);
|
|
7
|
+
const BLOCKED_HINTS = [
|
|
8
|
+
"access denied",
|
|
9
|
+
"forbidden",
|
|
10
|
+
"blocked",
|
|
11
|
+
"bot",
|
|
12
|
+
"captcha",
|
|
13
|
+
"cloudflare",
|
|
14
|
+
"challenge",
|
|
15
|
+
"javascript required",
|
|
16
|
+
"enable javascript",
|
|
17
|
+
"rate limit",
|
|
18
|
+
"too many requests",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function asObj(value: unknown): Record<string, unknown> {
|
|
22
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
23
|
+
? (value as Record<string, unknown>)
|
|
24
|
+
: {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toLowerBlob(value: unknown): string {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.stringify(value).toLowerCase();
|
|
30
|
+
} catch {
|
|
31
|
+
return String(value ?? "").toLowerCase();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isLikelyBlockedFailure(result: InvokeResult): boolean {
|
|
36
|
+
if (BLOCKED_STATUSES.has(result.status)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const msg = `${result.error?.message ?? ""}\n${toLowerBlob(result.error?.details ?? "")}`.toLowerCase();
|
|
40
|
+
return BLOCKED_HINTS.some((hint) => msg.includes(hint));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function withBrowserTarget(
|
|
44
|
+
args: Record<string, unknown>,
|
|
45
|
+
target: AdvToolsSettings["webFetchFallback"]["browserTarget"],
|
|
46
|
+
): Record<string, unknown> {
|
|
47
|
+
if (target === "auto") {
|
|
48
|
+
return args;
|
|
49
|
+
}
|
|
50
|
+
return { ...args, target };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type BrowserFallbackAttempt = {
|
|
54
|
+
attempted: true;
|
|
55
|
+
ok: boolean;
|
|
56
|
+
status: number;
|
|
57
|
+
latencyMs: number;
|
|
58
|
+
error?: InvokeResult["error"];
|
|
59
|
+
result?: unknown;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type WebFetchFallbackResult =
|
|
63
|
+
| { attempted: false }
|
|
64
|
+
| ({
|
|
65
|
+
strategy: "web_fetch_browser_snapshot_v1";
|
|
66
|
+
primaryStatus: number;
|
|
67
|
+
url: string;
|
|
68
|
+
nativeHandoffRecommended: boolean;
|
|
69
|
+
nativeHandoffReason: string;
|
|
70
|
+
} & BrowserFallbackAttempt);
|
|
71
|
+
|
|
72
|
+
type AttemptParams = {
|
|
73
|
+
cfg: OpenClawConfig;
|
|
74
|
+
settings: AdvToolsSettings;
|
|
75
|
+
toolName: string;
|
|
76
|
+
args: Record<string, unknown>;
|
|
77
|
+
primary: InvokeResult;
|
|
78
|
+
sessionKey?: string;
|
|
79
|
+
channel?: string;
|
|
80
|
+
accountId?: string;
|
|
81
|
+
timeoutMs?: number;
|
|
82
|
+
signal?: AbortSignal;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export async function maybeAttemptWebFetchFallback(params: AttemptParams): Promise<WebFetchFallbackResult> {
|
|
86
|
+
const { settings, toolName, args, primary } = params;
|
|
87
|
+
if (!settings.webFetchFallback.enabled || toolName !== "web_fetch" || primary.ok) {
|
|
88
|
+
return { attempted: false };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const url = typeof args.url === "string" ? args.url.trim() : "";
|
|
92
|
+
if (!url || !isLikelyBlockedFailure(primary)) {
|
|
93
|
+
return { attempted: false };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const startedAt = Date.now();
|
|
97
|
+
const browserTarget = settings.webFetchFallback.browserTarget;
|
|
98
|
+
const invokeCommon = {
|
|
99
|
+
sessionKey: params.sessionKey,
|
|
100
|
+
channel: params.channel,
|
|
101
|
+
accountId: params.accountId,
|
|
102
|
+
timeoutMs: params.timeoutMs,
|
|
103
|
+
signal: params.signal,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Best-effort start; failures are tolerated because browser may already be running.
|
|
107
|
+
await invokeToolViaGateway(params.cfg, {
|
|
108
|
+
tool: "browser",
|
|
109
|
+
action: "start",
|
|
110
|
+
args: withBrowserTarget({}, browserTarget),
|
|
111
|
+
...invokeCommon,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const open = await invokeToolViaGateway(params.cfg, {
|
|
115
|
+
tool: "browser",
|
|
116
|
+
action: "open",
|
|
117
|
+
args: withBrowserTarget({ url }, browserTarget),
|
|
118
|
+
...invokeCommon,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (!open.ok) {
|
|
122
|
+
// Retry via navigate in case the browser is up but open action is not supported by runtime/profile.
|
|
123
|
+
await invokeToolViaGateway(params.cfg, {
|
|
124
|
+
tool: "browser",
|
|
125
|
+
action: "navigate",
|
|
126
|
+
args: withBrowserTarget({ url }, browserTarget),
|
|
127
|
+
...invokeCommon,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const snapshot = await invokeToolViaGateway(params.cfg, {
|
|
132
|
+
tool: "browser",
|
|
133
|
+
action: "snapshot",
|
|
134
|
+
args: withBrowserTarget(
|
|
135
|
+
{
|
|
136
|
+
format: settings.webFetchFallback.snapshotFormat,
|
|
137
|
+
limit: settings.webFetchFallback.snapshotLimit,
|
|
138
|
+
},
|
|
139
|
+
browserTarget,
|
|
140
|
+
),
|
|
141
|
+
...invokeCommon,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!snapshot.ok) {
|
|
145
|
+
return {
|
|
146
|
+
attempted: true,
|
|
147
|
+
ok: false,
|
|
148
|
+
status: snapshot.status,
|
|
149
|
+
latencyMs: Date.now() - startedAt,
|
|
150
|
+
strategy: "web_fetch_browser_snapshot_v1",
|
|
151
|
+
primaryStatus: primary.status,
|
|
152
|
+
url,
|
|
153
|
+
error: snapshot.error,
|
|
154
|
+
nativeHandoffRecommended: true,
|
|
155
|
+
nativeHandoffReason:
|
|
156
|
+
"Browser fallback failed. Allow native browser calls or skill-guided Playwright/stealth workflow.",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const snapshotObj = asObj(snapshot.result);
|
|
161
|
+
return {
|
|
162
|
+
attempted: true,
|
|
163
|
+
ok: true,
|
|
164
|
+
status: snapshot.status,
|
|
165
|
+
latencyMs: Date.now() - startedAt,
|
|
166
|
+
strategy: "web_fetch_browser_snapshot_v1",
|
|
167
|
+
primaryStatus: primary.status,
|
|
168
|
+
url,
|
|
169
|
+
nativeHandoffRecommended: false,
|
|
170
|
+
nativeHandoffReason: "",
|
|
171
|
+
result: {
|
|
172
|
+
url,
|
|
173
|
+
fallback: {
|
|
174
|
+
strategy: "web_fetch_blocked_to_browser_snapshot",
|
|
175
|
+
primaryStatus: primary.status,
|
|
176
|
+
primaryError: primary.error,
|
|
177
|
+
},
|
|
178
|
+
browserSnapshot: snapshotObj,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
package/src/tools/tool-batch.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { AnyAgentTool, OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
2
|
import { jsonResult } from "openclaw/plugin-sdk";
|
|
3
3
|
import { resolveSettings } from "../lib/plugin-config.js";
|
|
4
4
|
import { invokeToolViaGateway } from "../lib/invoke.js";
|
|
@@ -6,6 +6,7 @@ import type { ResultStore } from "../lib/result-store.js";
|
|
|
6
6
|
import { shapeToolResult } from "../lib/result-shaper.js";
|
|
7
7
|
import { resolveValueTemplates } from "../lib/refs.js";
|
|
8
8
|
import type { MetricsStore } from "../lib/metrics-store.js";
|
|
9
|
+
import { maybeAttemptWebFetchFallback } from "../lib/web-fetch-fallback.js";
|
|
9
10
|
|
|
10
11
|
const CONTROL_PLANE = new Set(["gateway", "cron"]);
|
|
11
12
|
|
|
@@ -82,7 +83,7 @@ function parseSteps(raw: unknown): BatchStep[] {
|
|
|
82
83
|
|
|
83
84
|
async function runCall(params: {
|
|
84
85
|
api: OpenClawPluginApi;
|
|
85
|
-
config:
|
|
86
|
+
config: OpenClawConfig;
|
|
86
87
|
settings: ReturnType<typeof resolveSettings>;
|
|
87
88
|
step: BatchStepCall;
|
|
88
89
|
context: Record<string, unknown>;
|
|
@@ -159,6 +160,48 @@ async function runCall(params: {
|
|
|
159
160
|
outcome: invoke.error?.type === "timeout" ? "timeout" : "failure",
|
|
160
161
|
latencyMs: invoke.latencyMs,
|
|
161
162
|
});
|
|
163
|
+
|
|
164
|
+
const fallback = await maybeAttemptWebFetchFallback({
|
|
165
|
+
cfg: config,
|
|
166
|
+
settings,
|
|
167
|
+
toolName,
|
|
168
|
+
args,
|
|
169
|
+
primary: invoke,
|
|
170
|
+
sessionKey,
|
|
171
|
+
channel,
|
|
172
|
+
accountId,
|
|
173
|
+
timeoutMs,
|
|
174
|
+
signal: params.signal,
|
|
175
|
+
});
|
|
176
|
+
if (fallback.attempted) {
|
|
177
|
+
params.metrics?.record({
|
|
178
|
+
tool: "browser",
|
|
179
|
+
outcome: fallback.ok ? "success" : "failure",
|
|
180
|
+
latencyMs: fallback.latencyMs,
|
|
181
|
+
});
|
|
182
|
+
if (fallback.ok) {
|
|
183
|
+
const shaped = shapeToolResult({
|
|
184
|
+
toolName: "browser",
|
|
185
|
+
value: fallback.result,
|
|
186
|
+
settings,
|
|
187
|
+
store: params.store,
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
ok: true,
|
|
191
|
+
status: fallback.status,
|
|
192
|
+
tool: toolName,
|
|
193
|
+
fallbackUsed: "browser",
|
|
194
|
+
result: shaped,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
status: invoke.status,
|
|
200
|
+
tool: toolName,
|
|
201
|
+
error: invoke.error,
|
|
202
|
+
fallback,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
162
205
|
return {
|
|
163
206
|
ok: false,
|
|
164
207
|
status: invoke.status,
|
|
@@ -5,6 +5,7 @@ import { invokeToolViaGateway } from "../lib/invoke.js";
|
|
|
5
5
|
import type { ResultStore } from "../lib/result-store.js";
|
|
6
6
|
import { shapeToolResult } from "../lib/result-shaper.js";
|
|
7
7
|
import type { MetricsStore } from "../lib/metrics-store.js";
|
|
8
|
+
import { maybeAttemptWebFetchFallback } from "../lib/web-fetch-fallback.js";
|
|
8
9
|
|
|
9
10
|
const CONTROL_PLANE = new Set(["gateway", "cron"]);
|
|
10
11
|
const INTERNAL_LOOP_TOOLS = new Set(["tool_dispatch", "tool_batch"]);
|
|
@@ -122,11 +123,50 @@ export function createToolDispatchTool(
|
|
|
122
123
|
outcome: result.error?.type === "timeout" ? "timeout" : "failure",
|
|
123
124
|
latencyMs: result.latencyMs,
|
|
124
125
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
|
|
127
|
+
const fallback = await maybeAttemptWebFetchFallback({
|
|
128
|
+
cfg: loaded,
|
|
129
|
+
settings,
|
|
130
|
+
toolName,
|
|
131
|
+
args: safeArgs as Record<string, unknown>,
|
|
132
|
+
primary: result,
|
|
133
|
+
sessionKey,
|
|
134
|
+
channel,
|
|
135
|
+
accountId,
|
|
136
|
+
timeoutMs: requestedTimeoutMs,
|
|
137
|
+
signal: signal as AbortSignal | undefined,
|
|
129
138
|
});
|
|
139
|
+
|
|
140
|
+
if (fallback.attempted) {
|
|
141
|
+
options?.metrics?.record({
|
|
142
|
+
tool: "browser",
|
|
143
|
+
outcome: fallback.ok ? "success" : "failure",
|
|
144
|
+
latencyMs: fallback.latencyMs,
|
|
145
|
+
});
|
|
146
|
+
if (fallback.ok) {
|
|
147
|
+
const shaped = shapeToolResult({
|
|
148
|
+
toolName: "browser",
|
|
149
|
+
value: fallback.result,
|
|
150
|
+
settings,
|
|
151
|
+
store: options?.store,
|
|
152
|
+
});
|
|
153
|
+
return jsonResult({
|
|
154
|
+
ok: true,
|
|
155
|
+
status: fallback.status,
|
|
156
|
+
tool: toolName,
|
|
157
|
+
fallbackUsed: "browser",
|
|
158
|
+
result: shaped,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return jsonResult({
|
|
162
|
+
ok: false,
|
|
163
|
+
status: result.status,
|
|
164
|
+
error: result.error,
|
|
165
|
+
fallback,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return jsonResult({ ok: false, status: result.status, error: result.error });
|
|
130
170
|
}
|
|
131
171
|
|
|
132
172
|
const shaped = shapeToolResult({
|