ocsmarttools 0.1.2 → 0.1.3

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.3] - 2026-02-22
6
+
7
+ ### Added
8
+ - Plugin-native auto-routing guide sync for `AGENTS.md` with managed markers, enabled by default (`autoInjectRoutingGuide`).
9
+ - New chat and CLI sync commands (`/ocsmarttools sync`, `openclaw ocsmarttools sync`) to re-apply routing guidance instantly.
10
+
11
+ ### Changed
12
+ - `setup` now re-syncs the managed routing guide automatically.
13
+ - Added `autoInjectRoutingGuide` to config schema, status, config output, and editable keys.
14
+
15
+ ### Fixed
16
+ - Removed CLI registration conflict by making `stats reset` a subcommand under `stats`.
17
+
5
18
  ## [0.1.2] - 2026-02-22
6
19
 
7
20
  ### Added
package/README.md CHANGED
@@ -43,7 +43,8 @@ openclaw gateway restart
43
43
 
44
44
  1. Install + enable + restart.
45
45
  2. Done. The plugin auto-bootstraps and starts working in background.
46
- 3. Optional check:
46
+ 3. It also auto-manages an OCSmartTools routing block in `AGENTS.md` (unless disabled).
47
+ 4. Optional check:
47
48
 
48
49
  ```text
49
50
  /ocsmarttools status
@@ -66,6 +67,7 @@ Model note:
66
67
  | `/ocsmarttools stats reset` | Resets the stats window |
67
68
  | `/ocsmarttools setup [safe\|standard]` | Applies recommended defaults for the selected mode |
68
69
  | `/ocsmarttools mode <safe\|standard>` | Changes mode only |
70
+ | `/ocsmarttools sync` | Re-applies the auto-managed routing policy block in `AGENTS.md` |
69
71
  | `/ocsmarttools config` | Shows effective plugin config |
70
72
  | `/ocsmarttools config keys` | Lists editable config keys |
71
73
  | `/ocsmarttools config set <key> <value>` | Updates one config key with validation |
@@ -82,6 +84,7 @@ Model note:
82
84
  | `openclaw ocsmarttools stats reset` | Resets the stats window |
83
85
  | `openclaw ocsmarttools setup [safe\|standard]` | Applies recommended defaults for the selected mode |
84
86
  | `openclaw ocsmarttools mode <safe\|standard>` | Changes mode only |
87
+ | `openclaw ocsmarttools sync` | Re-applies the auto-managed routing policy block in `AGENTS.md` |
85
88
  | `openclaw ocsmarttools config` | Shows effective plugin config |
86
89
  | `openclaw ocsmarttools config keys` | Lists editable config keys |
87
90
  | `openclaw ocsmarttools config set <key> <value>` | Updates one config key with validation |
@@ -96,6 +99,8 @@ Model note:
96
99
  /ocsmarttools config set storeLargeResults true
97
100
  /ocsmarttools config set toolSearch.useLiveRegistry true
98
101
  /ocsmarttools config set toolSearch.liveTimeoutMs 1500
102
+ /ocsmarttools config set autoInjectRoutingGuide true
103
+ /ocsmarttools config set autoInjectRoutingGuide false
99
104
  /ocsmarttools config reset maxResultChars
100
105
  /ocsmarttools stats
101
106
  ```
@@ -143,6 +148,7 @@ If your instance uses strict `tools.allow`, include:
143
148
  ## Safety and Limits
144
149
 
145
150
  - `ocsmarttools` does not bypass OpenClaw tool policy.
151
+ - Routing policy is auto-injected into `AGENTS.md` with managed markers; it can be re-synced via `/ocsmarttools sync`.
146
152
  - `tool_batch` is intentionally bounded (`maxSteps`, `maxForEach`).
147
153
  - Large-result handles are in-memory and expire by TTL.
148
154
  - `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
+ "autoInjectRoutingGuide": { "type": "boolean" },
11
12
  "maxSteps": { "type": "integer", "minimum": 1, "maximum": 200 },
12
13
  "maxForEach": { "type": "integer", "minimum": 1, "maximum": 200 },
13
14
  "maxResultChars": { "type": "integer", "minimum": 500, "maximum": 500000 },
@@ -31,6 +32,7 @@
31
32
  },
32
33
  "uiHints": {
33
34
  "mode": { "label": "Mode" },
35
+ "autoInjectRoutingGuide": { "label": "Auto Inject Routing Guide" },
34
36
  "maxSteps": { "label": "Max Steps", "advanced": true },
35
37
  "maxForEach": { "label": "Max ForEach", "advanced": true },
36
38
  "maxResultChars": { "label": "Max Result Chars", "advanced": true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ocsmarttools",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Provider-agnostic advanced tool orchestration plugin for OpenClaw with search, dispatch, and batching",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -12,6 +12,7 @@ import {
12
12
  resetStats,
13
13
  resetConfig,
14
14
  setConfigKey,
15
+ syncManagedRouting,
15
16
  updateMode,
16
17
  } from "./operations.js";
17
18
  import type { AdvToolsMode } from "../lib/plugin-config.js";
@@ -72,6 +73,10 @@ export function registerChatCommands(api: OpenClawPluginApi, metrics: MetricsSto
72
73
  return { text: await updateMode(api, mode) };
73
74
  }
74
75
 
76
+ if (cmd === "sync") {
77
+ return { text: await syncManagedRouting(api) };
78
+ }
79
+
75
80
  if (cmd === "config") {
76
81
  const action = (parts[1] ?? "get").toLowerCase();
77
82
  if (action === "get") {
@@ -106,6 +111,7 @@ export function registerChatCommands(api: OpenClawPluginApi, metrics: MetricsSto
106
111
  "/ocsmarttools config keys",
107
112
  "/ocsmarttools config set <key> <value>",
108
113
  "/ocsmarttools config reset [key]",
114
+ "/ocsmarttools sync",
109
115
  ].join("\n"),
110
116
  };
111
117
  }
@@ -10,6 +10,7 @@ import {
10
10
  resetStats,
11
11
  resetConfig,
12
12
  setConfigKey,
13
+ syncManagedRouting,
13
14
  updateMode,
14
15
  } from "./operations.js";
15
16
  import type { AdvToolsMode } from "../lib/plugin-config.js";
@@ -52,16 +53,13 @@ export function registerCliCommands(api: OpenClawPluginApi, metrics: MetricsStor
52
53
  console.log(renderStatus(api));
53
54
  });
54
55
 
55
- adv
56
- .command("stats")
57
- .description("Show usage/savings metrics")
58
- .action(() => {
59
- // eslint-disable-next-line no-console
60
- console.log(renderStats(metrics));
61
- });
62
-
63
- adv
64
- .command("stats reset")
56
+ const statsCmd = adv.command("stats").description("Show usage/savings metrics");
57
+ statsCmd.action(() => {
58
+ // eslint-disable-next-line no-console
59
+ console.log(renderStats(metrics));
60
+ });
61
+ statsCmd
62
+ .command("reset")
65
63
  .description("Reset usage/savings metrics window")
66
64
  .action(() => {
67
65
  // eslint-disable-next-line no-console
@@ -91,6 +89,15 @@ export function registerCliCommands(api: OpenClawPluginApi, metrics: MetricsStor
91
89
  console.log(text);
92
90
  });
93
91
 
92
+ adv
93
+ .command("sync")
94
+ .description("Re-apply auto-managed routing policy to AGENTS.md")
95
+ .action(async () => {
96
+ const text = await syncManagedRouting(api);
97
+ // eslint-disable-next-line no-console
98
+ console.log(text);
99
+ });
100
+
94
101
  adv
95
102
  .command("config")
96
103
  .description("Show effective plugin config")
@@ -9,6 +9,7 @@ import {
9
9
  writeConfig,
10
10
  } from "../lib/plugin-config.js";
11
11
  import type { MetricsStore } from "../lib/metrics-store.js";
12
+ import { syncRoutingGuide } from "../lib/routing-guide.js";
12
13
 
13
14
  const ADVTOOLS_TOOL_NAMES = ["tool_search", "tool_dispatch", "tool_batch", "tool_result_get"];
14
15
  type ConfigKind = "boolean" | "integer" | "enum";
@@ -20,6 +21,7 @@ type ConfigSpec =
20
21
  const CONFIG_SPECS: Record<string, ConfigSpec> = {
21
22
  enabled: { kind: "boolean" },
22
23
  mode: { kind: "enum", values: ["safe", "standard"] },
24
+ autoInjectRoutingGuide: { kind: "boolean" },
23
25
  maxSteps: { kind: "integer", min: 1, max: 200 },
24
26
  maxForEach: { kind: "integer", min: 1, max: 200 },
25
27
  maxResultChars: { kind: "integer", min: 500, max: 500000 },
@@ -38,6 +40,7 @@ const CONFIG_SPECS: Record<string, ConfigSpec> = {
38
40
  const DEFAULT_BY_KEY: Record<string, boolean | number | string> = {
39
41
  enabled: DEFAULT_SETTINGS.enabled,
40
42
  mode: DEFAULT_SETTINGS.mode,
43
+ autoInjectRoutingGuide: DEFAULT_SETTINGS.autoInjectRoutingGuide,
41
44
  maxSteps: DEFAULT_SETTINGS.maxSteps,
42
45
  maxForEach: DEFAULT_SETTINGS.maxForEach,
43
46
  maxResultChars: DEFAULT_SETTINGS.maxResultChars,
@@ -140,6 +143,7 @@ export function renderStatus(api: OpenClawPluginApi): string {
140
143
  "OCSmartTools Status",
141
144
  `- plugin: ${api.id}`,
142
145
  `- mode: ${s.mode}`,
146
+ `- autoInjectRoutingGuide: ${s.autoInjectRoutingGuide}`,
143
147
  `- tool_search enabled: ${s.toolSearch.enabled}`,
144
148
  `- maxSteps: ${s.maxSteps}`,
145
149
  `- maxForEach: ${s.maxForEach}`,
@@ -165,6 +169,7 @@ export function renderHelp(): string {
165
169
  "- /ocsmarttools stats reset: Reset metrics window",
166
170
  "- /ocsmarttools setup [safe|standard]: Apply recommended defaults (default: standard)",
167
171
  "- /ocsmarttools mode <safe|standard>: Switch only the operating mode",
172
+ "- /ocsmarttools sync: Re-apply auto-managed routing policy to AGENTS.md",
168
173
  "- /ocsmarttools config: Show effective plugin config",
169
174
  "- /ocsmarttools config keys: List editable config keys",
170
175
  "- /ocsmarttools config set <key> <value>: Update one config key",
@@ -229,6 +234,7 @@ export function renderConfig(api: OpenClawPluginApi): string {
229
234
  "OCSmartTools Config",
230
235
  `- enabled: ${s.enabled}`,
231
236
  `- mode: ${s.mode}`,
237
+ `- autoInjectRoutingGuide: ${s.autoInjectRoutingGuide}`,
232
238
  `- maxSteps: ${s.maxSteps}`,
233
239
  `- maxForEach: ${s.maxForEach}`,
234
240
  `- maxResultChars: ${s.maxResultChars}`,
@@ -273,6 +279,10 @@ export async function setConfigKey(
273
279
  setValueAtPath(pluginCfg, key, parsed.value);
274
280
  await writeConfig(api, next);
275
281
 
282
+ if (key === "autoInjectRoutingGuide" && parsed.value === true) {
283
+ await syncRoutingGuide(api, next);
284
+ }
285
+
276
286
  return `Config updated: ${key}=${JSON.stringify(parsed.value)}.`;
277
287
  }
278
288
 
@@ -288,6 +298,9 @@ export async function resetConfig(api: OpenClawPluginApi, key?: string): Promise
288
298
  setValueAtPath(pluginCfg, cfgKey, DEFAULT_BY_KEY[cfgKey]);
289
299
  }
290
300
  await writeConfig(api, next);
301
+ if (DEFAULT_SETTINGS.autoInjectRoutingGuide) {
302
+ await syncRoutingGuide(api, next);
303
+ }
291
304
  return "Config reset to plugin defaults.";
292
305
  }
293
306
 
@@ -297,6 +310,9 @@ export async function resetConfig(api: OpenClawPluginApi, key?: string): Promise
297
310
 
298
311
  setValueAtPath(pluginCfg, key, DEFAULT_BY_KEY[key]);
299
312
  await writeConfig(api, next);
313
+ if (key === "autoInjectRoutingGuide" && DEFAULT_BY_KEY[key] === true) {
314
+ await syncRoutingGuide(api, next);
315
+ }
300
316
  return `Config key reset: ${key}=${JSON.stringify(DEFAULT_BY_KEY[key])}.`;
301
317
  }
302
318
 
@@ -320,6 +336,7 @@ export async function applySetup(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
320
336
 
321
337
  pluginCfg.enabled = true;
322
338
  pluginCfg.mode = mode;
339
+ pluginCfg.autoInjectRoutingGuide = true;
323
340
  pluginCfg.maxSteps = DEFAULT_SETTINGS.maxSteps;
324
341
  pluginCfg.maxForEach = DEFAULT_SETTINGS.maxForEach;
325
342
  pluginCfg.maxResultChars = DEFAULT_SETTINGS.maxResultChars;
@@ -344,10 +361,12 @@ export async function applySetup(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
344
361
  );
345
362
 
346
363
  await writeConfig(api, next);
364
+ await syncRoutingGuide(api, next);
347
365
 
348
366
  return [
349
367
  "OCSmartTools setup applied.",
350
368
  `- mode: ${mode}`,
369
+ "- autoInjectRoutingGuide: true",
351
370
  `- ensured tools.allow includes: ${ADVTOOLS_TOOL_NAMES.join(", ")}`,
352
371
  "- config written via runtime config writer",
353
372
  "If your gateway does not hot-apply this change, run: openclaw gateway restart",
@@ -368,3 +387,16 @@ export async function updateMode(api: OpenClawPluginApi, mode: AdvToolsMode): Pr
368
387
  await writeConfig(api, next);
369
388
  return `Mode updated to ${mode}.`;
370
389
  }
390
+
391
+ export async function syncManagedRouting(api: OpenClawPluginApi): Promise<string> {
392
+ const loaded = api.runtime.config.loadConfig();
393
+ const settings = resolveSettings(api, loaded);
394
+ if (!settings.autoInjectRoutingGuide) {
395
+ return "Routing guide sync skipped: autoInjectRoutingGuide=false.";
396
+ }
397
+ const result = await syncRoutingGuide(api, loaded);
398
+ if (!result.changed) {
399
+ return `Routing guide already up to date (${result.filePath ?? "AGENTS.md"}).`;
400
+ }
401
+ return `Routing guide synced (${result.filePath ?? "AGENTS.md"}).`;
402
+ }
@@ -6,6 +6,7 @@ import {
6
6
  mergeUniqueStrings,
7
7
  writeConfig,
8
8
  } from "./plugin-config.js";
9
+ import { syncRoutingGuide } from "./routing-guide.js";
9
10
 
10
11
  const TOOL_NAMES = ["tool_search", "tool_dispatch", "tool_batch", "tool_result_get"];
11
12
 
@@ -45,6 +46,7 @@ export async function autoBootstrap(api: OpenClawPluginApi): Promise<{ changed:
45
46
 
46
47
  setDefault("enabled", true);
47
48
  setDefault("mode", DEFAULT_SETTINGS.mode);
49
+ setDefault("autoInjectRoutingGuide", DEFAULT_SETTINGS.autoInjectRoutingGuide);
48
50
  setDefault("maxSteps", DEFAULT_SETTINGS.maxSteps);
49
51
  setDefault("maxForEach", DEFAULT_SETTINGS.maxForEach);
50
52
  setDefault("maxResultChars", DEFAULT_SETTINGS.maxResultChars);
@@ -110,5 +112,17 @@ export async function autoBootstrap(api: OpenClawPluginApi): Promise<{ changed:
110
112
  if (changed) {
111
113
  await writeConfig(api, next);
112
114
  }
115
+
116
+ const routingGuideEnabled =
117
+ typeof pluginCfg.autoInjectRoutingGuide === "boolean"
118
+ ? pluginCfg.autoInjectRoutingGuide
119
+ : DEFAULT_SETTINGS.autoInjectRoutingGuide;
120
+ if (routingGuideEnabled) {
121
+ const sync = await syncRoutingGuide(api, next);
122
+ if (sync.changed) {
123
+ notes.push(`synced routing guide (${sync.filePath ?? "AGENTS.md"})`);
124
+ }
125
+ }
126
+
113
127
  return { changed, notes };
114
128
  }
@@ -5,6 +5,7 @@ export type AdvToolsMode = "safe" | "standard";
5
5
  export type AdvToolsSettings = {
6
6
  enabled: boolean;
7
7
  mode: AdvToolsMode;
8
+ autoInjectRoutingGuide: boolean;
8
9
  maxSteps: number;
9
10
  maxForEach: number;
10
11
  maxResultChars: number;
@@ -25,6 +26,7 @@ export type AdvToolsSettings = {
25
26
  export const DEFAULT_SETTINGS: AdvToolsSettings = {
26
27
  enabled: true,
27
28
  mode: "standard",
29
+ autoInjectRoutingGuide: true,
28
30
  maxSteps: 25,
29
31
  maxForEach: 20,
30
32
  maxResultChars: 40000,
@@ -73,6 +75,10 @@ export function resolveSettings(api: OpenClawPluginApi, cfg: OpenClawConfig = ap
73
75
  return {
74
76
  enabled: asBool(pluginCfg.enabled, DEFAULT_SETTINGS.enabled),
75
77
  mode: asMode(pluginCfg.mode, DEFAULT_SETTINGS.mode),
78
+ autoInjectRoutingGuide: asBool(
79
+ pluginCfg.autoInjectRoutingGuide,
80
+ DEFAULT_SETTINGS.autoInjectRoutingGuide,
81
+ ),
76
82
  maxSteps: asInt(pluginCfg.maxSteps, DEFAULT_SETTINGS.maxSteps, 1, 200),
77
83
  maxForEach: asInt(pluginCfg.maxForEach, DEFAULT_SETTINGS.maxForEach, 1, 200),
78
84
  maxResultChars: asInt(pluginCfg.maxResultChars, DEFAULT_SETTINGS.maxResultChars, 500, 500000),
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk";
5
+
6
+ const BLOCK_START = "<!-- OCSMARTTOOLS_ROUTING_START -->";
7
+ const BLOCK_END = "<!-- OCSMARTTOOLS_ROUTING_END -->";
8
+
9
+ const ROUTING_BLOCK = `${BLOCK_START}
10
+ ## OCSmartTools Routing Policy (Auto-Managed)
11
+
12
+ Default objective: preserve answer quality while reducing token and latency cost.
13
+
14
+ 1. If tool usage is needed and result size is uncertain, use \`tool_dispatch\`.
15
+ 2. If the task needs 2+ related tool calls, use \`tool_batch\`.
16
+ 3. Use \`tool_search\` only when tool choice is unclear.
17
+ 4. Prefer compact/tool-shaped outputs; use \`tool_result_get\` only when more detail is required.
18
+ 5. Use direct native tool calls only for simple one-shot small-output actions.
19
+
20
+ Common large/noisy tools: \`web_fetch\`, \`read\` (large files), \`exec\`, \`process\`, \`browser\`, \`nodes\`.
21
+ ${BLOCK_END}
22
+ `;
23
+
24
+ function asObj(value: unknown): Record<string, unknown> {
25
+ return value && typeof value === "object" && !Array.isArray(value)
26
+ ? (value as Record<string, unknown>)
27
+ : {};
28
+ }
29
+
30
+ function resolveWorkspaceDir(cfg: OpenClawConfig): string {
31
+ const root = cfg as Record<string, unknown>;
32
+ const agents = asObj(root.agents);
33
+ const defaults = asObj(agents.defaults);
34
+ const configured = defaults.workspace;
35
+ if (typeof configured === "string" && configured.trim()) {
36
+ return configured.trim();
37
+ }
38
+ if (typeof process.env.OPENCLAW_WORKSPACE === "string" && process.env.OPENCLAW_WORKSPACE.trim()) {
39
+ return process.env.OPENCLAW_WORKSPACE.trim();
40
+ }
41
+ return path.join(os.homedir(), ".openclaw", "workspace");
42
+ }
43
+
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<{
61
+ changed: boolean;
62
+ filePath?: string;
63
+ }> {
64
+ const workspaceDir = resolveWorkspaceDir(cfg);
65
+ const filePath = path.join(workspaceDir, "AGENTS.md");
66
+ let current = "";
67
+ try {
68
+ current = await fs.readFile(filePath, "utf8");
69
+ } catch (error) {
70
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ const next = upsertRoutingBlock(current);
76
+ if (next === current) {
77
+ return { changed: false, filePath };
78
+ }
79
+
80
+ await fs.mkdir(workspaceDir, { recursive: true });
81
+ await fs.writeFile(filePath, next, "utf8");
82
+ api.logger.info(`[ocsmarttools] routing policy synced: ${filePath}`);
83
+ return { changed: true, filePath };
84
+ }