ocsmarttools 0.1.3 → 0.1.5
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 +22 -0
- package/README.md +16 -1
- package/openclaw.plugin.json +2 -0
- package/package.json +2 -2
- package/src/commands/chat.ts +16 -0
- package/src/commands/cli.ts +19 -0
- package/src/commands/operations.ts +74 -8
- package/src/lib/bootstrap.ts +14 -2
- package/src/lib/plugin-config.ts +9 -4
- package/src/lib/routing-guide.ts +82 -23
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `ocsmarttools` are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.1.5] - 2026-02-22
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Plugin-native strict routing mode (`strictRouting`) for fully self-managed routing behavior.
|
|
9
|
+
- New strict commands in chat and CLI:
|
|
10
|
+
- `/ocsmarttools strict <on|off|status>`
|
|
11
|
+
- `openclaw ocsmarttools strict <on|off>`
|
|
12
|
+
- Strict/non-strict auto-managed routing policy variants in `AGENTS.md`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- `strictRouting=true` now forces `autoInjectRoutingGuide=true`.
|
|
16
|
+
- Routing sync now applies the strict policy variant automatically when strict mode is enabled.
|
|
17
|
+
|
|
18
|
+
## [0.1.4] - 2026-02-22
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Broadened peer compatibility range to `openclaw >=2026.2.19` for easier installs across existing instances.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Routing policy sync is now fail-safe on restricted/read-only environments (never breaks plugin startup).
|
|
25
|
+
- Setup/config flows now return clear messages when routing sync is skipped due to filesystem permissions/path issues.
|
|
26
|
+
|
|
5
27
|
## [0.1.3] - 2026-02-22
|
|
6
28
|
|
|
7
29
|
### Added
|
package/README.md
CHANGED
|
@@ -23,6 +23,10 @@ flowchart LR
|
|
|
23
23
|
|
|
24
24
|
## Install
|
|
25
25
|
|
|
26
|
+
Compatibility:
|
|
27
|
+
- OpenClaw: `>=2026.2.19`
|
|
28
|
+
- Works with existing installed instances (no core patch required)
|
|
29
|
+
|
|
26
30
|
### npm
|
|
27
31
|
|
|
28
32
|
```bash
|
|
@@ -44,7 +48,13 @@ openclaw gateway restart
|
|
|
44
48
|
1. Install + enable + restart.
|
|
45
49
|
2. Done. The plugin auto-bootstraps and starts working in background.
|
|
46
50
|
3. It also auto-manages an OCSmartTools routing block in `AGENTS.md` (unless disabled).
|
|
47
|
-
4.
|
|
51
|
+
4. Enable strict plugin-managed routing (optional):
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
/ocsmarttools strict on
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
5. Optional check:
|
|
48
58
|
|
|
49
59
|
```text
|
|
50
60
|
/ocsmarttools status
|
|
@@ -67,6 +77,7 @@ Model note:
|
|
|
67
77
|
| `/ocsmarttools stats reset` | Resets the stats window |
|
|
68
78
|
| `/ocsmarttools setup [safe\|standard]` | Applies recommended defaults for the selected mode |
|
|
69
79
|
| `/ocsmarttools mode <safe\|standard>` | Changes mode only |
|
|
80
|
+
| `/ocsmarttools strict <on\|off\|status>` | Enables/disables strict plugin-managed routing |
|
|
70
81
|
| `/ocsmarttools sync` | Re-applies the auto-managed routing policy block in `AGENTS.md` |
|
|
71
82
|
| `/ocsmarttools config` | Shows effective plugin config |
|
|
72
83
|
| `/ocsmarttools config keys` | Lists editable config keys |
|
|
@@ -84,6 +95,7 @@ Model note:
|
|
|
84
95
|
| `openclaw ocsmarttools stats reset` | Resets the stats window |
|
|
85
96
|
| `openclaw ocsmarttools setup [safe\|standard]` | Applies recommended defaults for the selected mode |
|
|
86
97
|
| `openclaw ocsmarttools mode <safe\|standard>` | Changes mode only |
|
|
98
|
+
| `openclaw ocsmarttools strict <on\|off>` | Enables/disables strict plugin-managed routing |
|
|
87
99
|
| `openclaw ocsmarttools sync` | Re-applies the auto-managed routing policy block in `AGENTS.md` |
|
|
88
100
|
| `openclaw ocsmarttools config` | Shows effective plugin config |
|
|
89
101
|
| `openclaw ocsmarttools config keys` | Lists editable config keys |
|
|
@@ -99,6 +111,7 @@ Model note:
|
|
|
99
111
|
/ocsmarttools config set storeLargeResults true
|
|
100
112
|
/ocsmarttools config set toolSearch.useLiveRegistry true
|
|
101
113
|
/ocsmarttools config set toolSearch.liveTimeoutMs 1500
|
|
114
|
+
/ocsmarttools config set strictRouting true
|
|
102
115
|
/ocsmarttools config set autoInjectRoutingGuide true
|
|
103
116
|
/ocsmarttools config set autoInjectRoutingGuide false
|
|
104
117
|
/ocsmarttools config reset maxResultChars
|
|
@@ -112,6 +125,7 @@ Config path:
|
|
|
112
125
|
|
|
113
126
|
- `standard` (default): zero-touch mode, no sandbox requirement, control-plane dispatch still blocked
|
|
114
127
|
- `safe`: requires sandboxed execution and blocks control-plane dispatch (`gateway`, `cron`)
|
|
128
|
+
- `strictRouting=true`: strict plugin-managed routing policy is auto-synced with no manual workspace edits.
|
|
115
129
|
|
|
116
130
|
Setup default:
|
|
117
131
|
- `/ocsmarttools setup` and `openclaw ocsmarttools setup` default to `standard`.
|
|
@@ -149,6 +163,7 @@ If your instance uses strict `tools.allow`, include:
|
|
|
149
163
|
|
|
150
164
|
- `ocsmarttools` does not bypass OpenClaw tool policy.
|
|
151
165
|
- Routing policy is auto-injected into `AGENTS.md` with managed markers; it can be re-synced via `/ocsmarttools sync`.
|
|
166
|
+
- `strictRouting=true` forces `autoInjectRoutingGuide=true`.
|
|
152
167
|
- `tool_batch` is intentionally bounded (`maxSteps`, `maxForEach`).
|
|
153
168
|
- Large-result handles are in-memory and expire by TTL.
|
|
154
169
|
- `tool_result_get` works only while handle is still valid.
|
package/openclaw.plugin.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"properties": {
|
|
9
9
|
"enabled": { "type": "boolean" },
|
|
10
10
|
"mode": { "type": "string", "enum": ["safe", "standard"] },
|
|
11
|
+
"strictRouting": { "type": "boolean" },
|
|
11
12
|
"autoInjectRoutingGuide": { "type": "boolean" },
|
|
12
13
|
"maxSteps": { "type": "integer", "minimum": 1, "maximum": 200 },
|
|
13
14
|
"maxForEach": { "type": "integer", "minimum": 1, "maximum": 200 },
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
},
|
|
33
34
|
"uiHints": {
|
|
34
35
|
"mode": { "label": "Mode" },
|
|
36
|
+
"strictRouting": { "label": "Strict Routing" },
|
|
35
37
|
"autoInjectRoutingGuide": { "label": "Auto Inject Routing Guide" },
|
|
36
38
|
"maxSteps": { "label": "Max Steps", "advanced": true },
|
|
37
39
|
"maxForEach": { "label": "Max ForEach", "advanced": true },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ocsmarttools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Provider-agnostic advanced tool orchestration plugin for OpenClaw with search, dispatch, and batching",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"openclaw": ">=2026.2.
|
|
27
|
+
"openclaw": ">=2026.2.19"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"typescript": "^5.8.0",
|
package/src/commands/chat.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
resetConfig,
|
|
14
14
|
setConfigKey,
|
|
15
15
|
syncManagedRouting,
|
|
16
|
+
updateStrictRouting,
|
|
16
17
|
updateMode,
|
|
17
18
|
} from "./operations.js";
|
|
18
19
|
import type { AdvToolsMode } from "../lib/plugin-config.js";
|
|
@@ -73,6 +74,20 @@ export function registerChatCommands(api: OpenClawPluginApi, metrics: MetricsSto
|
|
|
73
74
|
return { text: await updateMode(api, mode) };
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
if (cmd === "strict") {
|
|
78
|
+
const arg = (parts[1] ?? "status").toLowerCase();
|
|
79
|
+
if (arg === "status") {
|
|
80
|
+
return { text: renderStatus(api) };
|
|
81
|
+
}
|
|
82
|
+
if (arg === "on" || arg === "true" || arg === "enable") {
|
|
83
|
+
return { text: await updateStrictRouting(api, true) };
|
|
84
|
+
}
|
|
85
|
+
if (arg === "off" || arg === "false" || arg === "disable") {
|
|
86
|
+
return { text: await updateStrictRouting(api, false) };
|
|
87
|
+
}
|
|
88
|
+
return { text: "Usage: /ocsmarttools strict <on|off|status>" };
|
|
89
|
+
}
|
|
90
|
+
|
|
76
91
|
if (cmd === "sync") {
|
|
77
92
|
return { text: await syncManagedRouting(api) };
|
|
78
93
|
}
|
|
@@ -112,6 +127,7 @@ export function registerChatCommands(api: OpenClawPluginApi, metrics: MetricsSto
|
|
|
112
127
|
"/ocsmarttools config set <key> <value>",
|
|
113
128
|
"/ocsmarttools config reset [key]",
|
|
114
129
|
"/ocsmarttools sync",
|
|
130
|
+
"/ocsmarttools strict <on|off|status>",
|
|
115
131
|
].join("\n"),
|
|
116
132
|
};
|
|
117
133
|
}
|
package/src/commands/cli.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
resetConfig,
|
|
12
12
|
setConfigKey,
|
|
13
13
|
syncManagedRouting,
|
|
14
|
+
updateStrictRouting,
|
|
14
15
|
updateMode,
|
|
15
16
|
} from "./operations.js";
|
|
16
17
|
import type { AdvToolsMode } from "../lib/plugin-config.js";
|
|
@@ -98,6 +99,24 @@ export function registerCliCommands(api: OpenClawPluginApi, metrics: MetricsStor
|
|
|
98
99
|
console.log(text);
|
|
99
100
|
});
|
|
100
101
|
|
|
102
|
+
adv
|
|
103
|
+
.command("strict <state>")
|
|
104
|
+
.description("Set strict routing on/off (state: on|off)")
|
|
105
|
+
.action(async (stateRaw: string) => {
|
|
106
|
+
const state = stateRaw.trim().toLowerCase();
|
|
107
|
+
if (["on", "true", "enable"].includes(state)) {
|
|
108
|
+
// eslint-disable-next-line no-console
|
|
109
|
+
console.log(await updateStrictRouting(api, true));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (["off", "false", "disable"].includes(state)) {
|
|
113
|
+
// eslint-disable-next-line no-console
|
|
114
|
+
console.log(await updateStrictRouting(api, false));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
throw new Error("strict state must be one of: on, off");
|
|
118
|
+
});
|
|
119
|
+
|
|
101
120
|
adv
|
|
102
121
|
.command("config")
|
|
103
122
|
.description("Show effective plugin config")
|
|
@@ -21,6 +21,7 @@ type ConfigSpec =
|
|
|
21
21
|
const CONFIG_SPECS: Record<string, ConfigSpec> = {
|
|
22
22
|
enabled: { kind: "boolean" },
|
|
23
23
|
mode: { kind: "enum", values: ["safe", "standard"] },
|
|
24
|
+
strictRouting: { kind: "boolean" },
|
|
24
25
|
autoInjectRoutingGuide: { kind: "boolean" },
|
|
25
26
|
maxSteps: { kind: "integer", min: 1, max: 200 },
|
|
26
27
|
maxForEach: { kind: "integer", min: 1, max: 200 },
|
|
@@ -40,6 +41,7 @@ const CONFIG_SPECS: Record<string, ConfigSpec> = {
|
|
|
40
41
|
const DEFAULT_BY_KEY: Record<string, boolean | number | string> = {
|
|
41
42
|
enabled: DEFAULT_SETTINGS.enabled,
|
|
42
43
|
mode: DEFAULT_SETTINGS.mode,
|
|
44
|
+
strictRouting: DEFAULT_SETTINGS.strictRouting,
|
|
43
45
|
autoInjectRoutingGuide: DEFAULT_SETTINGS.autoInjectRoutingGuide,
|
|
44
46
|
maxSteps: DEFAULT_SETTINGS.maxSteps,
|
|
45
47
|
maxForEach: DEFAULT_SETTINGS.maxForEach,
|
|
@@ -143,6 +145,7 @@ export function renderStatus(api: OpenClawPluginApi): string {
|
|
|
143
145
|
"OCSmartTools Status",
|
|
144
146
|
`- plugin: ${api.id}`,
|
|
145
147
|
`- mode: ${s.mode}`,
|
|
148
|
+
`- strictRouting: ${s.strictRouting}`,
|
|
146
149
|
`- autoInjectRoutingGuide: ${s.autoInjectRoutingGuide}`,
|
|
147
150
|
`- tool_search enabled: ${s.toolSearch.enabled}`,
|
|
148
151
|
`- maxSteps: ${s.maxSteps}`,
|
|
@@ -169,6 +172,7 @@ export function renderHelp(): string {
|
|
|
169
172
|
"- /ocsmarttools stats reset: Reset metrics window",
|
|
170
173
|
"- /ocsmarttools setup [safe|standard]: Apply recommended defaults (default: standard)",
|
|
171
174
|
"- /ocsmarttools mode <safe|standard>: Switch only the operating mode",
|
|
175
|
+
"- /ocsmarttools strict [on|off|status]: Toggle strict routing behavior",
|
|
172
176
|
"- /ocsmarttools sync: Re-apply auto-managed routing policy to AGENTS.md",
|
|
173
177
|
"- /ocsmarttools config: Show effective plugin config",
|
|
174
178
|
"- /ocsmarttools config keys: List editable config keys",
|
|
@@ -177,6 +181,7 @@ export function renderHelp(): string {
|
|
|
177
181
|
"",
|
|
178
182
|
"Examples:",
|
|
179
183
|
"- /ocsmarttools config set maxResultChars 120000",
|
|
184
|
+
"- /ocsmarttools strict on",
|
|
180
185
|
"- /ocsmarttools config set storeLargeResults true",
|
|
181
186
|
"- /ocsmarttools config reset maxResultChars",
|
|
182
187
|
].join("\n");
|
|
@@ -234,6 +239,7 @@ export function renderConfig(api: OpenClawPluginApi): string {
|
|
|
234
239
|
"OCSmartTools Config",
|
|
235
240
|
`- enabled: ${s.enabled}`,
|
|
236
241
|
`- mode: ${s.mode}`,
|
|
242
|
+
`- strictRouting: ${s.strictRouting}`,
|
|
237
243
|
`- autoInjectRoutingGuide: ${s.autoInjectRoutingGuide}`,
|
|
238
244
|
`- maxSteps: ${s.maxSteps}`,
|
|
239
245
|
`- maxForEach: ${s.maxForEach}`,
|
|
@@ -277,10 +283,21 @@ export async function setConfigKey(
|
|
|
277
283
|
entryObj.config = pluginCfg;
|
|
278
284
|
|
|
279
285
|
setValueAtPath(pluginCfg, key, parsed.value);
|
|
286
|
+
if (key === "strictRouting" && parsed.value === true) {
|
|
287
|
+
setValueAtPath(pluginCfg, "autoInjectRoutingGuide", true);
|
|
288
|
+
}
|
|
280
289
|
await writeConfig(api, next);
|
|
281
290
|
|
|
282
|
-
if (key === "autoInjectRoutingGuide"
|
|
283
|
-
|
|
291
|
+
if (key === "autoInjectRoutingGuide" || key === "strictRouting") {
|
|
292
|
+
const strictRouting = resolveSettings(api, next).strictRouting;
|
|
293
|
+
const shouldSync = resolveSettings(api, next).autoInjectRoutingGuide;
|
|
294
|
+
if (!shouldSync) {
|
|
295
|
+
return `Config updated: ${key}=${JSON.stringify(parsed.value)}.`;
|
|
296
|
+
}
|
|
297
|
+
const sync = await syncRoutingGuide(api, next, { strictRouting });
|
|
298
|
+
if (sync.error) {
|
|
299
|
+
return `Config updated: ${key}=${JSON.stringify(parsed.value)} (routing sync skipped: ${sync.error}).`;
|
|
300
|
+
}
|
|
284
301
|
}
|
|
285
302
|
|
|
286
303
|
return `Config updated: ${key}=${JSON.stringify(parsed.value)}.`;
|
|
@@ -297,9 +314,17 @@ export async function resetConfig(api: OpenClawPluginApi, key?: string): Promise
|
|
|
297
314
|
for (const cfgKey of sortedKeys()) {
|
|
298
315
|
setValueAtPath(pluginCfg, cfgKey, DEFAULT_BY_KEY[cfgKey]);
|
|
299
316
|
}
|
|
317
|
+
if (DEFAULT_SETTINGS.strictRouting) {
|
|
318
|
+
setValueAtPath(pluginCfg, "autoInjectRoutingGuide", true);
|
|
319
|
+
}
|
|
300
320
|
await writeConfig(api, next);
|
|
301
|
-
if (
|
|
302
|
-
await syncRoutingGuide(api, next
|
|
321
|
+
if (resolveSettings(api, next).autoInjectRoutingGuide) {
|
|
322
|
+
const sync = await syncRoutingGuide(api, next, {
|
|
323
|
+
strictRouting: resolveSettings(api, next).strictRouting,
|
|
324
|
+
});
|
|
325
|
+
if (sync.error) {
|
|
326
|
+
return `Config reset to plugin defaults (routing sync skipped: ${sync.error}).`;
|
|
327
|
+
}
|
|
303
328
|
}
|
|
304
329
|
return "Config reset to plugin defaults.";
|
|
305
330
|
}
|
|
@@ -309,9 +334,20 @@ export async function resetConfig(api: OpenClawPluginApi, key?: string): Promise
|
|
|
309
334
|
}
|
|
310
335
|
|
|
311
336
|
setValueAtPath(pluginCfg, key, DEFAULT_BY_KEY[key]);
|
|
337
|
+
if (key === "strictRouting" && DEFAULT_BY_KEY[key] === true) {
|
|
338
|
+
setValueAtPath(pluginCfg, "autoInjectRoutingGuide", true);
|
|
339
|
+
}
|
|
312
340
|
await writeConfig(api, next);
|
|
313
|
-
if (key === "autoInjectRoutingGuide"
|
|
314
|
-
|
|
341
|
+
if (key === "autoInjectRoutingGuide" || key === "strictRouting") {
|
|
342
|
+
const strictRouting = resolveSettings(api, next).strictRouting;
|
|
343
|
+
const shouldSync = resolveSettings(api, next).autoInjectRoutingGuide;
|
|
344
|
+
if (!shouldSync) {
|
|
345
|
+
return `Config key reset: ${key}=${JSON.stringify(DEFAULT_BY_KEY[key])}.`;
|
|
346
|
+
}
|
|
347
|
+
const sync = await syncRoutingGuide(api, next, { strictRouting });
|
|
348
|
+
if (sync.error) {
|
|
349
|
+
return `Config key reset: ${key}=${JSON.stringify(DEFAULT_BY_KEY[key])} (routing sync skipped: ${sync.error}).`;
|
|
350
|
+
}
|
|
315
351
|
}
|
|
316
352
|
return `Config key reset: ${key}=${JSON.stringify(DEFAULT_BY_KEY[key])}.`;
|
|
317
353
|
}
|
|
@@ -336,6 +372,7 @@ export async function applySetup(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
|
|
|
336
372
|
|
|
337
373
|
pluginCfg.enabled = true;
|
|
338
374
|
pluginCfg.mode = mode;
|
|
375
|
+
pluginCfg.strictRouting = false;
|
|
339
376
|
pluginCfg.autoInjectRoutingGuide = true;
|
|
340
377
|
pluginCfg.maxSteps = DEFAULT_SETTINGS.maxSteps;
|
|
341
378
|
pluginCfg.maxForEach = DEFAULT_SETTINGS.maxForEach;
|
|
@@ -361,12 +398,16 @@ export async function applySetup(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
|
|
|
361
398
|
);
|
|
362
399
|
|
|
363
400
|
await writeConfig(api, next);
|
|
364
|
-
await syncRoutingGuide(api, next);
|
|
401
|
+
const sync = await syncRoutingGuide(api, next, { strictRouting: false });
|
|
402
|
+
const syncLine = sync.error
|
|
403
|
+
? `- routing guide sync skipped: ${sync.error}`
|
|
404
|
+
: `- routing guide ${sync.changed ? "synced" : "already up to date"} (${sync.filePath ?? "AGENTS.md"})`;
|
|
365
405
|
|
|
366
406
|
return [
|
|
367
407
|
"OCSmartTools setup applied.",
|
|
368
408
|
`- mode: ${mode}`,
|
|
369
409
|
"- autoInjectRoutingGuide: true",
|
|
410
|
+
syncLine,
|
|
370
411
|
`- ensured tools.allow includes: ${ADVTOOLS_TOOL_NAMES.join(", ")}`,
|
|
371
412
|
"- config written via runtime config writer",
|
|
372
413
|
"If your gateway does not hot-apply this change, run: openclaw gateway restart",
|
|
@@ -388,13 +429,38 @@ export async function updateMode(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
|
|
|
388
429
|
return `Mode updated to ${mode}.`;
|
|
389
430
|
}
|
|
390
431
|
|
|
432
|
+
export async function updateStrictRouting(api: OpenClawPluginApi, enabled: boolean): Promise<string> {
|
|
433
|
+
const next = deepCloneConfig(api.runtime.config.loadConfig());
|
|
434
|
+
const { entryObj } = ensurePluginEntry(next, api.id);
|
|
435
|
+
entryObj.enabled = true;
|
|
436
|
+
|
|
437
|
+
const pluginCfg = asObj(entryObj.config);
|
|
438
|
+
entryObj.config = pluginCfg;
|
|
439
|
+
pluginCfg.strictRouting = enabled;
|
|
440
|
+
if (enabled) {
|
|
441
|
+
pluginCfg.autoInjectRoutingGuide = true;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
await writeConfig(api, next);
|
|
445
|
+
if (resolveSettings(api, next).autoInjectRoutingGuide) {
|
|
446
|
+
const sync = await syncRoutingGuide(api, next, { strictRouting: enabled });
|
|
447
|
+
if (sync.error) {
|
|
448
|
+
return `Strict routing ${enabled ? "enabled" : "disabled"} (routing sync skipped: ${sync.error}).`;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return `Strict routing ${enabled ? "enabled" : "disabled"}.`;
|
|
452
|
+
}
|
|
453
|
+
|
|
391
454
|
export async function syncManagedRouting(api: OpenClawPluginApi): Promise<string> {
|
|
392
455
|
const loaded = api.runtime.config.loadConfig();
|
|
393
456
|
const settings = resolveSettings(api, loaded);
|
|
394
457
|
if (!settings.autoInjectRoutingGuide) {
|
|
395
458
|
return "Routing guide sync skipped: autoInjectRoutingGuide=false.";
|
|
396
459
|
}
|
|
397
|
-
const result = await syncRoutingGuide(api, loaded);
|
|
460
|
+
const result = await syncRoutingGuide(api, loaded, { strictRouting: settings.strictRouting });
|
|
461
|
+
if (result.error) {
|
|
462
|
+
return `Routing guide sync skipped: ${result.error}`;
|
|
463
|
+
}
|
|
398
464
|
if (!result.changed) {
|
|
399
465
|
return `Routing guide already up to date (${result.filePath ?? "AGENTS.md"}).`;
|
|
400
466
|
}
|
package/src/lib/bootstrap.ts
CHANGED
|
@@ -46,6 +46,7 @@ export async function autoBootstrap(api: OpenClawPluginApi): Promise<{ changed:
|
|
|
46
46
|
|
|
47
47
|
setDefault("enabled", true);
|
|
48
48
|
setDefault("mode", DEFAULT_SETTINGS.mode);
|
|
49
|
+
setDefault("strictRouting", DEFAULT_SETTINGS.strictRouting);
|
|
49
50
|
setDefault("autoInjectRoutingGuide", DEFAULT_SETTINGS.autoInjectRoutingGuide);
|
|
50
51
|
setDefault("maxSteps", DEFAULT_SETTINGS.maxSteps);
|
|
51
52
|
setDefault("maxForEach", DEFAULT_SETTINGS.maxForEach);
|
|
@@ -113,13 +114,24 @@ export async function autoBootstrap(api: OpenClawPluginApi): Promise<{ changed:
|
|
|
113
114
|
await writeConfig(api, next);
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
const strictRouting =
|
|
118
|
+
typeof pluginCfg.strictRouting === "boolean" ? pluginCfg.strictRouting : DEFAULT_SETTINGS.strictRouting;
|
|
119
|
+
if (strictRouting && pluginCfg.autoInjectRoutingGuide !== true) {
|
|
120
|
+
pluginCfg.autoInjectRoutingGuide = true;
|
|
121
|
+
changed = true;
|
|
122
|
+
notes.push("forced autoInjectRoutingGuide=true for strictRouting");
|
|
123
|
+
await writeConfig(api, next);
|
|
124
|
+
}
|
|
125
|
+
|
|
116
126
|
const routingGuideEnabled =
|
|
117
127
|
typeof pluginCfg.autoInjectRoutingGuide === "boolean"
|
|
118
128
|
? pluginCfg.autoInjectRoutingGuide
|
|
119
129
|
: DEFAULT_SETTINGS.autoInjectRoutingGuide;
|
|
120
130
|
if (routingGuideEnabled) {
|
|
121
|
-
const sync = await syncRoutingGuide(api, next);
|
|
122
|
-
if (sync.
|
|
131
|
+
const sync = await syncRoutingGuide(api, next, { strictRouting });
|
|
132
|
+
if (sync.error) {
|
|
133
|
+
notes.push(`routing guide skipped: ${sync.error}`);
|
|
134
|
+
} else if (sync.changed) {
|
|
123
135
|
notes.push(`synced routing guide (${sync.filePath ?? "AGENTS.md"})`);
|
|
124
136
|
}
|
|
125
137
|
}
|
package/src/lib/plugin-config.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type AdvToolsMode = "safe" | "standard";
|
|
|
5
5
|
export type AdvToolsSettings = {
|
|
6
6
|
enabled: boolean;
|
|
7
7
|
mode: AdvToolsMode;
|
|
8
|
+
strictRouting: boolean;
|
|
8
9
|
autoInjectRoutingGuide: boolean;
|
|
9
10
|
maxSteps: number;
|
|
10
11
|
maxForEach: number;
|
|
@@ -26,6 +27,7 @@ export type AdvToolsSettings = {
|
|
|
26
27
|
export const DEFAULT_SETTINGS: AdvToolsSettings = {
|
|
27
28
|
enabled: true,
|
|
28
29
|
mode: "standard",
|
|
30
|
+
strictRouting: false,
|
|
29
31
|
autoInjectRoutingGuide: true,
|
|
30
32
|
maxSteps: 25,
|
|
31
33
|
maxForEach: 20,
|
|
@@ -72,13 +74,16 @@ export function resolveSettings(api: OpenClawPluginApi, cfg: OpenClawConfig = ap
|
|
|
72
74
|
const pluginCfg = asObj(entry.config ?? api.pluginConfig);
|
|
73
75
|
const ts = asObj(pluginCfg.toolSearch);
|
|
74
76
|
|
|
77
|
+
const strictRouting = asBool(pluginCfg.strictRouting, DEFAULT_SETTINGS.strictRouting);
|
|
78
|
+
const autoInjectRoutingGuide = strictRouting
|
|
79
|
+
? true
|
|
80
|
+
: asBool(pluginCfg.autoInjectRoutingGuide, DEFAULT_SETTINGS.autoInjectRoutingGuide);
|
|
81
|
+
|
|
75
82
|
return {
|
|
76
83
|
enabled: asBool(pluginCfg.enabled, DEFAULT_SETTINGS.enabled),
|
|
77
84
|
mode: asMode(pluginCfg.mode, DEFAULT_SETTINGS.mode),
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
DEFAULT_SETTINGS.autoInjectRoutingGuide,
|
|
81
|
-
),
|
|
85
|
+
strictRouting,
|
|
86
|
+
autoInjectRoutingGuide,
|
|
82
87
|
maxSteps: asInt(pluginCfg.maxSteps, DEFAULT_SETTINGS.maxSteps, 1, 200),
|
|
83
88
|
maxForEach: asInt(pluginCfg.maxForEach, DEFAULT_SETTINGS.maxForEach, 1, 200),
|
|
84
89
|
maxResultChars: asInt(pluginCfg.maxResultChars, DEFAULT_SETTINGS.maxResultChars, 500, 500000),
|
package/src/lib/routing-guide.ts
CHANGED
|
@@ -6,7 +6,60 @@ 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
|
-
|
|
9
|
+
function buildRoutingBlock(strictRouting: boolean): string {
|
|
10
|
+
const body = strictRouting
|
|
11
|
+
? [
|
|
12
|
+
"## OCSmartTools Routing Policy (Auto-Managed, Strict)",
|
|
13
|
+
"",
|
|
14
|
+
"Default objective: preserve answer quality while reducing token and latency cost.",
|
|
15
|
+
"",
|
|
16
|
+
"Mandatory rules in strict mode:",
|
|
17
|
+
"1. Route tool usage through `tool_dispatch` or `tool_batch`.",
|
|
18
|
+
"2. Use `tool_batch` for 2+ related calls, iterative steps, or map-style tasks.",
|
|
19
|
+
"3. Use `tool_search` only when tool choice is unclear.",
|
|
20
|
+
"4. Use `tool_result_get` only when a stored handle needs more detail.",
|
|
21
|
+
"5. Avoid direct native tool calls unless routing tools are unavailable due to runtime/tool-policy constraints.",
|
|
22
|
+
"",
|
|
23
|
+
"Common large/noisy tools: `web_fetch`, `read` (large files), `exec`, `process`, `browser`, `nodes`.",
|
|
24
|
+
].join("\n")
|
|
25
|
+
: [
|
|
26
|
+
"## OCSmartTools Routing Policy (Auto-Managed)",
|
|
27
|
+
"",
|
|
28
|
+
"Default objective: preserve answer quality while reducing token and latency cost.",
|
|
29
|
+
"",
|
|
30
|
+
"1. If tool usage is needed and result size is uncertain, use `tool_dispatch`.",
|
|
31
|
+
"2. If the task needs 2+ related tool calls, use `tool_batch`.",
|
|
32
|
+
"3. Use `tool_search` only when tool choice is unclear.",
|
|
33
|
+
"4. Prefer compact/tool-shaped outputs; use `tool_result_get` only when more detail is required.",
|
|
34
|
+
"5. Use direct native tool calls only for simple one-shot small-output actions.",
|
|
35
|
+
"",
|
|
36
|
+
"Common large/noisy tools: `web_fetch`, `read` (large files), `exec`, `process`, `browser`, `nodes`.",
|
|
37
|
+
].join("\n");
|
|
38
|
+
|
|
39
|
+
return `${BLOCK_START}
|
|
40
|
+
${body}
|
|
41
|
+
${BLOCK_END}
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const DEFAULT_BASE_CONTENT = "# AGENTS.md - Workspace Directives\n\nAdd local operating preferences below.\n";
|
|
46
|
+
|
|
47
|
+
function upsertRoutingBlock(raw: string, strictRouting: boolean): string {
|
|
48
|
+
const routingBlock = buildRoutingBlock(strictRouting);
|
|
49
|
+
const source = raw.trim() ? raw : DEFAULT_BASE_CONTENT;
|
|
50
|
+
|
|
51
|
+
const start = source.indexOf(BLOCK_START);
|
|
52
|
+
const end = source.indexOf(BLOCK_END);
|
|
53
|
+
if (start >= 0 && end > start) {
|
|
54
|
+
const before = source.slice(0, start).replace(/\s*$/, "");
|
|
55
|
+
const after = source.slice(end + BLOCK_END.length).replace(/^\s*/, "");
|
|
56
|
+
return `${before}\n\n${routingBlock}\n${after}`.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
57
|
+
}
|
|
58
|
+
const joined = `${source.replace(/\s*$/, "")}\n\n${routingBlock}\n`;
|
|
59
|
+
return joined.replace(/\n{3,}/g, "\n\n");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const LEGACY_ROUTING_BLOCK = `${BLOCK_START}
|
|
10
63
|
## OCSmartTools Routing Policy (Auto-Managed)
|
|
11
64
|
|
|
12
65
|
Default objective: preserve answer quality while reducing token and latency cost.
|
|
@@ -41,25 +94,14 @@ function resolveWorkspaceDir(cfg: OpenClawConfig): string {
|
|
|
41
94
|
return path.join(os.homedir(), ".openclaw", "workspace");
|
|
42
95
|
}
|
|
43
96
|
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const start = source.indexOf(BLOCK_START);
|
|
50
|
-
const end = source.indexOf(BLOCK_END);
|
|
51
|
-
if (start >= 0 && end > start) {
|
|
52
|
-
const before = source.slice(0, start).replace(/\s*$/, "");
|
|
53
|
-
const after = source.slice(end + BLOCK_END.length).replace(/^\s*/, "");
|
|
54
|
-
return `${before}\n\n${ROUTING_BLOCK}\n${after}`.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
55
|
-
}
|
|
56
|
-
const joined = `${source.replace(/\s*$/, "")}\n\n${ROUTING_BLOCK}\n`;
|
|
57
|
-
return joined.replace(/\n{3,}/g, "\n\n");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function syncRoutingGuide(api: OpenClawPluginApi, cfg: OpenClawConfig): Promise<{
|
|
97
|
+
export async function syncRoutingGuide(
|
|
98
|
+
api: OpenClawPluginApi,
|
|
99
|
+
cfg: OpenClawConfig,
|
|
100
|
+
options?: { strictRouting?: boolean },
|
|
101
|
+
): Promise<{
|
|
61
102
|
changed: boolean;
|
|
62
103
|
filePath?: string;
|
|
104
|
+
error?: string;
|
|
63
105
|
}> {
|
|
64
106
|
const workspaceDir = resolveWorkspaceDir(cfg);
|
|
65
107
|
const filePath = path.join(workspaceDir, "AGENTS.md");
|
|
@@ -68,17 +110,34 @@ export async function syncRoutingGuide(api: OpenClawPluginApi, cfg: OpenClawConf
|
|
|
68
110
|
current = await fs.readFile(filePath, "utf8");
|
|
69
111
|
} catch (error) {
|
|
70
112
|
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
71
|
-
|
|
113
|
+
return {
|
|
114
|
+
changed: false,
|
|
115
|
+
filePath,
|
|
116
|
+
error: `read failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
117
|
+
};
|
|
72
118
|
}
|
|
73
119
|
}
|
|
74
120
|
|
|
75
|
-
const
|
|
76
|
-
|
|
121
|
+
const strictRouting = options?.strictRouting === true;
|
|
122
|
+
// If a legacy block exists, treat it as managed content and replace it with the latest variant.
|
|
123
|
+
const normalizedCurrent = current.includes(LEGACY_ROUTING_BLOCK)
|
|
124
|
+
? current.replace(LEGACY_ROUTING_BLOCK, buildRoutingBlock(false))
|
|
125
|
+
: current;
|
|
126
|
+
const next = upsertRoutingBlock(normalizedCurrent, strictRouting);
|
|
127
|
+
if (next === current || next === normalizedCurrent) {
|
|
77
128
|
return { changed: false, filePath };
|
|
78
129
|
}
|
|
79
130
|
|
|
80
|
-
|
|
81
|
-
|
|
131
|
+
try {
|
|
132
|
+
await fs.mkdir(workspaceDir, { recursive: true });
|
|
133
|
+
await fs.writeFile(filePath, next, "utf8");
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return {
|
|
136
|
+
changed: false,
|
|
137
|
+
filePath,
|
|
138
|
+
error: `write failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
82
141
|
api.logger.info(`[ocsmarttools] routing policy synced: ${filePath}`);
|
|
83
142
|
return { changed: true, filePath };
|
|
84
143
|
}
|