ocsmarttools 0.1.4 → 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 CHANGED
@@ -2,6 +2,19 @@
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
+
5
18
  ## [0.1.4] - 2026-02-22
6
19
 
7
20
  ### Changed
package/README.md CHANGED
@@ -48,7 +48,13 @@ openclaw gateway restart
48
48
  1. Install + enable + restart.
49
49
  2. Done. The plugin auto-bootstraps and starts working in background.
50
50
  3. It also auto-manages an OCSmartTools routing block in `AGENTS.md` (unless disabled).
51
- 4. Optional check:
51
+ 4. Enable strict plugin-managed routing (optional):
52
+
53
+ ```text
54
+ /ocsmarttools strict on
55
+ ```
56
+
57
+ 5. Optional check:
52
58
 
53
59
  ```text
54
60
  /ocsmarttools status
@@ -71,6 +77,7 @@ Model note:
71
77
  | `/ocsmarttools stats reset` | Resets the stats window |
72
78
  | `/ocsmarttools setup [safe\|standard]` | Applies recommended defaults for the selected mode |
73
79
  | `/ocsmarttools mode <safe\|standard>` | Changes mode only |
80
+ | `/ocsmarttools strict <on\|off\|status>` | Enables/disables strict plugin-managed routing |
74
81
  | `/ocsmarttools sync` | Re-applies the auto-managed routing policy block in `AGENTS.md` |
75
82
  | `/ocsmarttools config` | Shows effective plugin config |
76
83
  | `/ocsmarttools config keys` | Lists editable config keys |
@@ -88,6 +95,7 @@ Model note:
88
95
  | `openclaw ocsmarttools stats reset` | Resets the stats window |
89
96
  | `openclaw ocsmarttools setup [safe\|standard]` | Applies recommended defaults for the selected mode |
90
97
  | `openclaw ocsmarttools mode <safe\|standard>` | Changes mode only |
98
+ | `openclaw ocsmarttools strict <on\|off>` | Enables/disables strict plugin-managed routing |
91
99
  | `openclaw ocsmarttools sync` | Re-applies the auto-managed routing policy block in `AGENTS.md` |
92
100
  | `openclaw ocsmarttools config` | Shows effective plugin config |
93
101
  | `openclaw ocsmarttools config keys` | Lists editable config keys |
@@ -103,6 +111,7 @@ Model note:
103
111
  /ocsmarttools config set storeLargeResults true
104
112
  /ocsmarttools config set toolSearch.useLiveRegistry true
105
113
  /ocsmarttools config set toolSearch.liveTimeoutMs 1500
114
+ /ocsmarttools config set strictRouting true
106
115
  /ocsmarttools config set autoInjectRoutingGuide true
107
116
  /ocsmarttools config set autoInjectRoutingGuide false
108
117
  /ocsmarttools config reset maxResultChars
@@ -116,6 +125,7 @@ Config path:
116
125
 
117
126
  - `standard` (default): zero-touch mode, no sandbox requirement, control-plane dispatch still blocked
118
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.
119
129
 
120
130
  Setup default:
121
131
  - `/ocsmarttools setup` and `openclaw ocsmarttools setup` default to `standard`.
@@ -153,6 +163,7 @@ If your instance uses strict `tools.allow`, include:
153
163
 
154
164
  - `ocsmarttools` does not bypass OpenClaw tool policy.
155
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`.
156
167
  - `tool_batch` is intentionally bounded (`maxSteps`, `maxForEach`).
157
168
  - Large-result handles are in-memory and expire by TTL.
158
169
  - `tool_result_get` works only while handle is still valid.
@@ -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.4",
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": {
@@ -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
  }
@@ -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,18 @@ 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" && parsed.value === true) {
283
- const sync = await syncRoutingGuide(api, next);
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 });
284
298
  if (sync.error) {
285
299
  return `Config updated: ${key}=${JSON.stringify(parsed.value)} (routing sync skipped: ${sync.error}).`;
286
300
  }
@@ -300,9 +314,14 @@ export async function resetConfig(api: OpenClawPluginApi, key?: string): Promise
300
314
  for (const cfgKey of sortedKeys()) {
301
315
  setValueAtPath(pluginCfg, cfgKey, DEFAULT_BY_KEY[cfgKey]);
302
316
  }
317
+ if (DEFAULT_SETTINGS.strictRouting) {
318
+ setValueAtPath(pluginCfg, "autoInjectRoutingGuide", true);
319
+ }
303
320
  await writeConfig(api, next);
304
- if (DEFAULT_SETTINGS.autoInjectRoutingGuide) {
305
- const sync = await syncRoutingGuide(api, next);
321
+ if (resolveSettings(api, next).autoInjectRoutingGuide) {
322
+ const sync = await syncRoutingGuide(api, next, {
323
+ strictRouting: resolveSettings(api, next).strictRouting,
324
+ });
306
325
  if (sync.error) {
307
326
  return `Config reset to plugin defaults (routing sync skipped: ${sync.error}).`;
308
327
  }
@@ -315,9 +334,17 @@ export async function resetConfig(api: OpenClawPluginApi, key?: string): Promise
315
334
  }
316
335
 
317
336
  setValueAtPath(pluginCfg, key, DEFAULT_BY_KEY[key]);
337
+ if (key === "strictRouting" && DEFAULT_BY_KEY[key] === true) {
338
+ setValueAtPath(pluginCfg, "autoInjectRoutingGuide", true);
339
+ }
318
340
  await writeConfig(api, next);
319
- if (key === "autoInjectRoutingGuide" && DEFAULT_BY_KEY[key] === true) {
320
- const sync = await syncRoutingGuide(api, next);
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 });
321
348
  if (sync.error) {
322
349
  return `Config key reset: ${key}=${JSON.stringify(DEFAULT_BY_KEY[key])} (routing sync skipped: ${sync.error}).`;
323
350
  }
@@ -345,6 +372,7 @@ export async function applySetup(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
345
372
 
346
373
  pluginCfg.enabled = true;
347
374
  pluginCfg.mode = mode;
375
+ pluginCfg.strictRouting = false;
348
376
  pluginCfg.autoInjectRoutingGuide = true;
349
377
  pluginCfg.maxSteps = DEFAULT_SETTINGS.maxSteps;
350
378
  pluginCfg.maxForEach = DEFAULT_SETTINGS.maxForEach;
@@ -370,7 +398,7 @@ export async function applySetup(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
370
398
  );
371
399
 
372
400
  await writeConfig(api, next);
373
- const sync = await syncRoutingGuide(api, next);
401
+ const sync = await syncRoutingGuide(api, next, { strictRouting: false });
374
402
  const syncLine = sync.error
375
403
  ? `- routing guide sync skipped: ${sync.error}`
376
404
  : `- routing guide ${sync.changed ? "synced" : "already up to date"} (${sync.filePath ?? "AGENTS.md"})`;
@@ -401,13 +429,35 @@ export async function updateMode(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
401
429
  return `Mode updated to ${mode}.`;
402
430
  }
403
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
+
404
454
  export async function syncManagedRouting(api: OpenClawPluginApi): Promise<string> {
405
455
  const loaded = api.runtime.config.loadConfig();
406
456
  const settings = resolveSettings(api, loaded);
407
457
  if (!settings.autoInjectRoutingGuide) {
408
458
  return "Routing guide sync skipped: autoInjectRoutingGuide=false.";
409
459
  }
410
- const result = await syncRoutingGuide(api, loaded);
460
+ const result = await syncRoutingGuide(api, loaded, { strictRouting: settings.strictRouting });
411
461
  if (result.error) {
412
462
  return `Routing guide sync skipped: ${result.error}`;
413
463
  }
@@ -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,12 +114,21 @@ 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);
131
+ const sync = await syncRoutingGuide(api, next, { strictRouting });
122
132
  if (sync.error) {
123
133
  notes.push(`routing guide skipped: ${sync.error}`);
124
134
  } else if (sync.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
- autoInjectRoutingGuide: asBool(
79
- pluginCfg.autoInjectRoutingGuide,
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),
@@ -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
- const ROUTING_BLOCK = `${BLOCK_START}
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,23 +94,11 @@ function resolveWorkspaceDir(cfg: OpenClawConfig): string {
41
94
  return path.join(os.homedir(), ".openclaw", "workspace");
42
95
  }
43
96
 
44
- function upsertRoutingBlock(raw: string): string {
45
- const source = raw.trim()
46
- ? raw
47
- : "# AGENTS.md - Workspace Directives\n\nAdd local operating preferences below.\n";
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;
63
104
  error?: string;
@@ -77,8 +118,13 @@ export async function syncRoutingGuide(api: OpenClawPluginApi, cfg: OpenClawConf
77
118
  }
78
119
  }
79
120
 
80
- const next = upsertRoutingBlock(current);
81
- if (next === current) {
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) {
82
128
  return { changed: false, filePath };
83
129
  }
84
130