@karmaniverous/jeeves-meta-openclaw 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,19 @@
1
1
  # @karmaniverous/jeeves-meta-openclaw
2
2
 
3
- OpenClaw plugin for [jeeves-meta](../service/). A thin HTTP client that registers interactive tools and maintains dynamic TOOLS.md content.
3
+ OpenClaw plugin for [jeeves-meta](../service/). A thin HTTP client that registers interactive tools and uses [`@karmaniverous/jeeves`](https://github.com/karmaniverous/jeeves) core for managed TOOLS.md content writing and platform maintenance.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Four interactive tools** — `meta_list`, `meta_detail`, `meta_trigger`, `meta_preview`
8
- - **MetaServiceClient** — HTTP client delegating all operations to the running service
9
- - **TOOLS.md injection** — periodic refresh of entity stats and tool listing in the agent's system prompt
8
+ - **MetaServiceClient** — typed HTTP client delegating all operations to the running service
9
+ - **TOOLS.md injection** — periodic refresh of entity stats via `ComponentWriter` from `@karmaniverous/jeeves` (73-second prime interval)
10
10
  - **Dependency health** — shows warnings when watcher/gateway are degraded
11
11
  - **Consumer skill** — `SKILL.md` for agent integration
12
12
 
13
+ ## Plugin Lifecycle
14
+
15
+ ![Plugin Lifecycle](diagrams/assets/plugin-lifecycle.png)
16
+
13
17
  ## Install
14
18
 
15
19
  ```bash
@@ -24,10 +28,12 @@ npx @karmaniverous/jeeves-meta-openclaw install
24
28
 
25
29
  ## Configuration
26
30
 
27
- The plugin resolves the service URL in order:
28
- 1. Plugin config `serviceUrl` in `openclaw.json`
29
- 2. `JEEVES_META_URL` environment variable
30
- 3. Default: `http://127.0.0.1:1938`
31
+ The plugin resolves settings via a three-step fallback chain: plugin config → environment variable → default.
32
+
33
+ | Setting | Plugin Config Key | Env Var | Default |
34
+ |---------|-------------------|---------|---------|
35
+ | Service URL | `serviceUrl` | `JEEVES_META_URL` | `http://127.0.0.1:1938` |
36
+ | Config Root | `configRoot` | `JEEVES_CONFIG_ROOT` | `j:/config` |
31
37
 
32
38
  ```json
33
39
  {
@@ -36,7 +42,8 @@ The plugin resolves the service URL in order:
36
42
  "jeeves-meta-openclaw": {
37
43
  "enabled": true,
38
44
  "config": {
39
- "serviceUrl": "http://127.0.0.1:1938"
45
+ "serviceUrl": "http://127.0.0.1:1938",
46
+ "configRoot": "j:/config"
40
47
  }
41
48
  }
42
49
  }
@@ -44,6 +51,8 @@ The plugin resolves the service URL in order:
44
51
  }
45
52
  ```
46
53
 
54
+ The `configRoot` setting tells `@karmaniverous/jeeves` core where to find the platform config directory. Core derives `{configRoot}/jeeves-meta/` for component-specific configuration.
55
+
47
56
  ## Documentation
48
57
 
49
58
  - **[Plugin Setup](guides/plugin-setup.md)** — installation, config, lifecycle
@@ -54,4 +63,3 @@ The plugin resolves the service URL in order:
54
63
  ## License
55
64
 
56
65
  BSD-3-Clause
57
-
package/dist/cli.js CHANGED
@@ -21,6 +21,7 @@ import { fileURLToPath } from 'node:url';
21
21
  *
22
22
  * @module cli
23
23
  */
24
+ // Duplicated from helpers.ts — separate rollup bundle cannot share imports.
24
25
  const PLUGIN_ID = 'jeeves-meta-openclaw';
25
26
  /** Resolve the OpenClaw home directory. */
26
27
  function resolveOpenClawHome() {
package/dist/index.js CHANGED
@@ -1,33 +1,44 @@
1
- import { readFile, writeFile } from 'node:fs/promises';
2
- import { resolve } from 'node:path';
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { init, createAsyncContentCache, createComponentWriter } from '@karmaniverous/jeeves';
3
5
 
4
6
  /**
5
7
  * Shared types and utilities for the OpenClaw plugin.
6
8
  *
7
9
  * @module helpers
8
10
  */
9
- const PLUGIN_NAME = 'jeeves-meta-openclaw';
10
- const DEFAULT_SERVICE_URL = 'http://127.0.0.1:1938';
11
- /** Get plugin config. */
11
+ /** Plugin identifier. */
12
+ const PLUGIN_ID = 'jeeves-meta-openclaw';
13
+ /** Get plugin config object from the OpenClaw API. */
12
14
  function getPluginConfig(api) {
13
- return api.config?.plugins?.entries?.[PLUGIN_NAME]?.config;
15
+ return api.config?.plugins?.entries?.[PLUGIN_ID]?.config;
14
16
  }
15
17
  /**
16
- * Resolve the service URL.
18
+ * Resolve a plugin setting via the standard three-step fallback chain:
19
+ * plugin config → environment variable → default value.
17
20
  *
18
- * Resolution order:
19
- * 1. Plugin config `serviceUrl` setting
20
- * 2. `JEEVES_META_URL` environment variable
21
- * 3. Default: http://127.0.0.1:1938
21
+ * @param api - Plugin API.
22
+ * @param configKey - Key in the plugin config object.
23
+ * @param envVar - Environment variable name.
24
+ * @param fallback - Default value if neither source provides one.
22
25
  */
23
- function getServiceUrl(api) {
24
- const fromPlugin = getPluginConfig(api)?.serviceUrl;
26
+ function resolvePluginSetting(api, configKey, envVar, fallback) {
27
+ const fromPlugin = getPluginConfig(api)?.[configKey];
25
28
  if (typeof fromPlugin === 'string')
26
29
  return fromPlugin;
27
- const fromEnv = process.env['JEEVES_META_URL'];
30
+ const fromEnv = process.env[envVar];
28
31
  if (fromEnv)
29
32
  return fromEnv;
30
- return DEFAULT_SERVICE_URL;
33
+ return fallback;
34
+ }
35
+ /** Resolve the meta service URL. */
36
+ function getServiceUrl(api) {
37
+ return resolvePluginSetting(api, 'serviceUrl', 'JEEVES_META_URL', 'http://127.0.0.1:1938');
38
+ }
39
+ /** Resolve the platform config root. */
40
+ function getConfigRoot(api) {
41
+ return resolvePluginSetting(api, 'configRoot', 'JEEVES_CONFIG_ROOT', 'j:/config');
31
42
  }
32
43
  /** Format a successful tool result. */
33
44
  function ok(data) {
@@ -44,10 +55,166 @@ function fail(error) {
44
55
  };
45
56
  }
46
57
 
58
+ /**
59
+ * Tool name/description pairs — single source of truth for tool registration
60
+ * and placeholder content.
61
+ *
62
+ * @module toolMeta
63
+ */
64
+ /** Ordered list of meta_* tools. */
65
+ const META_TOOLS = [
66
+ {
67
+ name: 'meta_list',
68
+ description: 'List metas with summary stats and per-meta projection. Replaces meta_status + meta_entities.',
69
+ },
70
+ {
71
+ name: 'meta_detail',
72
+ description: 'Full detail for a single meta, with optional archive history.',
73
+ },
74
+ {
75
+ name: 'meta_trigger',
76
+ description: 'Manually trigger synthesis for a specific meta or the next-stalest candidate. Runs the full 3-step cycle (architect, builder, critic).',
77
+ },
78
+ {
79
+ name: 'meta_preview',
80
+ description: 'Dry-run: show what inputs would be gathered for the next synthesis cycle without running LLM.',
81
+ },
82
+ ];
83
+ /**
84
+ * Render the tools table for TOOLS.md injection.
85
+ *
86
+ * Produces a Markdown table with short descriptions suitable for
87
+ * both the placeholder and the live menu.
88
+ */
89
+ function renderToolsTable() {
90
+ const shortDesc = {
91
+ meta_list: 'List metas with summary stats and per-meta projection',
92
+ meta_detail: 'Full detail for a single meta with optional archive history',
93
+ meta_trigger: 'Manually trigger synthesis for a specific meta or next-stalest',
94
+ meta_preview: 'Dry-run: show what inputs would be gathered without running LLM',
95
+ };
96
+ const rows = META_TOOLS.map((t) => `| \`${t.name}\` | ${shortDesc[t.name] ?? t.description} |`);
97
+ return [
98
+ '### Tools',
99
+ '| Tool | Description |',
100
+ '|------|-------------|',
101
+ ...rows,
102
+ '',
103
+ 'Read the `jeeves-meta` skill for usage guidance, configuration, and troubleshooting.',
104
+ ].join('\n');
105
+ }
106
+
107
+ /**
108
+ * Generate the Meta menu content for TOOLS.md injection.
109
+ *
110
+ * Queries the jeeves-meta service for entity stats and produces
111
+ * a Markdown section suitable for agent system prompt injection.
112
+ *
113
+ * @module promptInjection
114
+ */
115
+ /**
116
+ * Generate the Meta menu Markdown for TOOLS.md.
117
+ *
118
+ * @param client - MetaServiceClient instance.
119
+ * @returns Markdown string for the Meta section.
120
+ */
121
+ async function generateMetaMenu(client) {
122
+ let status;
123
+ let metas;
124
+ try {
125
+ status = await client.status();
126
+ metas = await client.listMetas();
127
+ }
128
+ catch {
129
+ return [
130
+ '> **ACTION REQUIRED: jeeves-meta service is unreachable.**',
131
+ '> The service API is down or not configured.',
132
+ '>',
133
+ '> **Troubleshooting:**',
134
+ '> - Verify the service is installed: `npm list -g @karmaniverous/jeeves-meta`',
135
+ '> - Check if running: `curl http://localhost:1938/status`',
136
+ '> - Verify `serviceUrl` in plugin config if using a non-default port',
137
+ '>',
138
+ "> **Read the `jeeves-meta` skill's Bootstrapping section** for full setup guidance.",
139
+ ].join('\n');
140
+ }
141
+ if (metas.summary.total === 0) {
142
+ return [
143
+ '> **ACTION REQUIRED: No synthesis entities found.**',
144
+ '> The service is running but no `.meta/` directories were discovered.',
145
+ '>',
146
+ "> **Read the `jeeves-meta` skill's Bootstrapping section** for guidance",
147
+ '> on creating `.meta/` directories.',
148
+ ].join('\n');
149
+ }
150
+ const { summary } = metas;
151
+ const formatAge = (seconds) => {
152
+ if (!isFinite(seconds))
153
+ return 'never synthesized';
154
+ if (seconds < 3600)
155
+ return Math.round(seconds / 60).toString() + 'm';
156
+ if (seconds < 86400)
157
+ return Math.round(seconds / 3600).toString() + 'h';
158
+ return Math.round(seconds / 86400).toString() + 'd';
159
+ };
160
+ // Find stalest age
161
+ let stalestAge = 0;
162
+ for (const item of metas.metas) {
163
+ const s = item.stalenessSeconds !== null ? item.stalenessSeconds : Infinity;
164
+ if (s > stalestAge)
165
+ stalestAge = s;
166
+ }
167
+ const stalestDisplay = summary.stalestPath
168
+ ? summary.stalestPath + ' (' + formatAge(stalestAge) + ')'
169
+ : 'n/a';
170
+ const lastSynthDisplay = summary.lastSynthesizedAt
171
+ ? (summary.lastSynthesizedPath ?? '') +
172
+ ' (' +
173
+ summary.lastSynthesizedAt +
174
+ ')'
175
+ : 'n/a';
176
+ // Service status + dependency health
177
+ const depLines = [];
178
+ if (status.dependencies.watcher.status === 'indexing') {
179
+ depLines.push('> ⏳ **Watcher indexing**: Initial filesystem scan in progress. Synthesis will resume when complete.');
180
+ }
181
+ else if (status.dependencies.watcher.status !== 'ok' &&
182
+ status.dependencies.watcher.status !== 'indexing') {
183
+ depLines.push('> ⚠️ **Watcher**: ' + status.dependencies.watcher.status);
184
+ }
185
+ if (status.dependencies.watcher.rulesRegistered === false &&
186
+ status.dependencies.watcher.status === 'ok') {
187
+ depLines.push('> ⚠️ **Watcher rules not registered**: Meta files may not render properly in search/server.');
188
+ }
189
+ if (status.dependencies.gateway.status !== 'ok') {
190
+ depLines.push('> ⚠️ **Gateway**: ' + status.dependencies.gateway.status);
191
+ }
192
+ return [
193
+ 'The jeeves-meta synthesis engine manages ' +
194
+ summary.total.toString() +
195
+ ' meta entities.',
196
+ '',
197
+ '### Entity Summary',
198
+ '| Metric | Value |',
199
+ '|--------|-------|',
200
+ '| Total | ' + summary.total.toString() + ' |',
201
+ '| Stale | ' + summary.stale.toString() + ' |',
202
+ '| Errors | ' + summary.errors.toString() + ' |',
203
+ '| Never synthesized | ' + summary.neverSynthesized.toString() + ' |',
204
+ '| Stalest | ' + stalestDisplay + ' |',
205
+ '| Last synthesized | ' + lastSynthDisplay + ' |',
206
+ ...(depLines.length > 0 ? ['', '### Dependencies', ...depLines] : []),
207
+ '',
208
+ renderToolsTable(),
209
+ ].join('\n');
210
+ }
211
+
47
212
  /**
48
213
  * Thin HTTP client for the jeeves-meta service.
49
214
  *
50
215
  * Plugin delegates all operations to the running service via HTTP.
216
+ * Response types are defined here as the single source of truth —
217
+ * consumers should not redefine them.
51
218
  *
52
219
  * @module serviceClient
53
220
  */
@@ -134,19 +301,67 @@ class MetaServiceClient {
134
301
  }
135
302
  }
136
303
 
304
+ /**
305
+ * ServiceCommands and PluginCommands implementations for the JeevesComponent
306
+ * descriptor. Separated from register() for single-responsibility.
307
+ *
308
+ * @module serviceCommands
309
+ */
310
+ /**
311
+ * Create ServiceCommands that proxy to the meta HTTP service.
312
+ *
313
+ * @param client - MetaServiceClient instance.
314
+ */
315
+ function createServiceCommands(client) {
316
+ return {
317
+ async stop() {
318
+ // Meta service lifecycle is managed externally (NSSM).
319
+ },
320
+ async uninstall() {
321
+ // Service uninstall is handled by the service CLI.
322
+ },
323
+ async status() {
324
+ try {
325
+ const res = await client.status();
326
+ return {
327
+ running: res.status !== 'stopped',
328
+ version: res.version,
329
+ uptimeSeconds: res.uptime,
330
+ };
331
+ }
332
+ catch {
333
+ return { running: false };
334
+ }
335
+ },
336
+ };
337
+ }
338
+ /** Create PluginCommands (uninstall handled by CLI). */
339
+ function createPluginCommands() {
340
+ return {
341
+ async uninstall() {
342
+ // Plugin uninstall is handled by the CLI.
343
+ },
344
+ };
345
+ }
346
+
137
347
  /**
138
348
  * Meta tool registrations for OpenClaw.
139
349
  *
140
350
  * All tools delegate to the jeeves-meta HTTP service.
351
+ * Tool names and descriptions are sourced from {@link META_TOOLS}.
141
352
  *
142
353
  * @module tools
143
354
  */
355
+ /** Look up a tool's description by name. */
356
+ function desc(name) {
357
+ return META_TOOLS.find((t) => t.name === name)?.description ?? name;
358
+ }
144
359
  /** Register all meta_* tools. */
145
360
  function registerMetaTools(api, client) {
146
361
  // ─── meta_list ──────────────────────────────────────────────
147
362
  api.registerTool({
148
363
  name: 'meta_list',
149
- description: 'List metas with summary stats and per-meta projection. Replaces meta_status + meta_entities.',
364
+ description: desc('meta_list'),
150
365
  parameters: {
151
366
  type: 'object',
152
367
  properties: {
@@ -192,7 +407,7 @@ function registerMetaTools(api, client) {
192
407
  // ─── meta_detail ────────────────────────────────────────────
193
408
  api.registerTool({
194
409
  name: 'meta_detail',
195
- description: 'Full detail for a single meta, with optional archive history.',
410
+ description: desc('meta_detail'),
196
411
  parameters: {
197
412
  type: 'object',
198
413
  properties: {
@@ -228,7 +443,7 @@ function registerMetaTools(api, client) {
228
443
  // ─── meta_preview ────────────────────────────────────────────
229
444
  api.registerTool({
230
445
  name: 'meta_preview',
231
- description: 'Dry-run: show what inputs would be gathered for the next synthesis cycle without running LLM.',
446
+ description: desc('meta_preview'),
232
447
  parameters: {
233
448
  type: 'object',
234
449
  properties: {
@@ -251,7 +466,7 @@ function registerMetaTools(api, client) {
251
466
  // ─── meta_trigger ────────────────────────────────────────────
252
467
  api.registerTool({
253
468
  name: 'meta_trigger',
254
- description: 'Manually trigger synthesis for a specific meta or the next-stalest candidate. Runs the full 3-step cycle (architect, builder, critic).',
469
+ description: desc('meta_trigger'),
255
470
  parameters: {
256
471
  type: 'object',
257
472
  properties: {
@@ -274,259 +489,54 @@ function registerMetaTools(api, client) {
274
489
  }
275
490
 
276
491
  /**
277
- * Generate the Meta menu content for TOOLS.md injection.
278
- *
279
- * Queries the jeeves-meta service for entity stats and produces
280
- * a Markdown section suitable for agent system prompt injection.
281
- *
282
- * @module promptInjection
283
- */
284
- /**
285
- * Generate the Meta menu Markdown for TOOLS.md.
286
- *
287
- * @param client - MetaServiceClient instance.
288
- * @returns Markdown string for the Meta section.
289
- */
290
- async function generateMetaMenu(client) {
291
- let status;
292
- let metas;
293
- try {
294
- status = (await client.status());
295
- metas = (await client.listMetas());
296
- }
297
- catch {
298
- return [
299
- '> **ACTION REQUIRED: jeeves-meta service is unreachable.**',
300
- '> The service API is down or not configured.',
301
- '>',
302
- '> **Troubleshooting:**',
303
- '> - Verify the service is installed: `npm list -g @karmaniverous/jeeves-meta`',
304
- '> - Check if running: `curl http://localhost:1938/status`',
305
- '> - Verify `serviceUrl` in plugin config if using a non-default port',
306
- '>',
307
- "> **Read the `jeeves-meta` skill's Bootstrapping section** for full setup guidance.",
308
- ].join('\n');
309
- }
310
- if (metas.summary.total === 0) {
311
- return [
312
- '> **ACTION REQUIRED: No synthesis entities found.**',
313
- '> The service is running but no `.meta/` directories were discovered.',
314
- '>',
315
- "> **Read the `jeeves-meta` skill's Bootstrapping section** for guidance",
316
- '> on creating `.meta/` directories.',
317
- ].join('\n');
318
- }
319
- const { summary } = metas;
320
- const formatAge = (seconds) => {
321
- if (!isFinite(seconds))
322
- return 'never synthesized';
323
- if (seconds < 3600)
324
- return Math.round(seconds / 60).toString() + 'm';
325
- if (seconds < 86400)
326
- return Math.round(seconds / 3600).toString() + 'h';
327
- return Math.round(seconds / 86400).toString() + 'd';
328
- };
329
- // Find stalest age
330
- let stalestAge = 0;
331
- for (const item of metas.metas) {
332
- const s = item.stalenessSeconds !== null ? item.stalenessSeconds : Infinity;
333
- if (s > stalestAge)
334
- stalestAge = s;
335
- }
336
- const stalestDisplay = summary.stalestPath
337
- ? summary.stalestPath + ' (' + formatAge(stalestAge) + ')'
338
- : 'n/a';
339
- const lastSynthDisplay = summary.lastSynthesizedAt
340
- ? (summary.lastSynthesizedPath ?? '') +
341
- ' (' +
342
- summary.lastSynthesizedAt +
343
- ')'
344
- : 'n/a';
345
- // Service status + dependency health
346
- const depLines = [];
347
- if (status.dependencies.watcher.status !== 'ok') {
348
- depLines.push('> ⚠️ **Watcher**: ' + status.dependencies.watcher.status);
349
- }
350
- if (status.dependencies.gateway.status !== 'ok') {
351
- depLines.push('> ⚠️ **Gateway**: ' + status.dependencies.gateway.status);
352
- }
353
- return [
354
- 'The jeeves-meta synthesis engine manages ' +
355
- summary.total.toString() +
356
- ' meta entities.',
357
- '',
358
- '### Entity Summary',
359
- '| Metric | Value |',
360
- '|--------|-------|',
361
- '| Total | ' + summary.total.toString() + ' |',
362
- '| Stale | ' + summary.stale.toString() + ' |',
363
- '| Errors | ' + summary.errors.toString() + ' |',
364
- '| Never synthesized | ' + summary.neverSynthesized.toString() + ' |',
365
- '| Stalest | ' + stalestDisplay + ' |',
366
- '| Last synthesized | ' + lastSynthDisplay + ' |',
367
- ...(depLines.length > 0 ? ['', '### Dependencies', ...depLines] : []),
368
- '',
369
- '### Token Usage (cumulative)',
370
- '| Step | Tokens |',
371
- '|------|--------|',
372
- '| Architect | ' + summary.tokens.architect.toLocaleString() + ' |',
373
- '| Builder | ' + summary.tokens.builder.toLocaleString() + ' |',
374
- '| Critic | ' + summary.tokens.critic.toLocaleString() + ' |',
375
- '',
376
- '### Tools',
377
- '| Tool | Description |',
378
- '|------|-------------|',
379
- '| `meta_list` | List metas with summary stats and per-meta projection |',
380
- '| `meta_detail` | Full detail for a single meta with optional archive history |',
381
- '| `meta_trigger` | Manually trigger synthesis for a specific meta or next-stalest |',
382
- '| `meta_preview` | Dry-run: show what inputs would be gathered without running LLM |',
383
- '',
384
- 'Read the `jeeves-meta` skill for usage guidance, configuration, and troubleshooting.',
385
- ].join('\n');
386
- }
387
-
388
- /**
389
- * Periodic TOOLS.md disk writer for the Meta section.
390
- *
391
- * Upserts a `## Meta` section under the shared `# Jeeves Platform Tools`
392
- * header. The gateway reads TOOLS.md fresh from disk on each new session.
393
- *
394
- * @module toolsWriter
395
- */
396
- const REFRESH_INTERVAL_MS = 60_000;
397
- const INITIAL_DELAY_MS = 5_000;
398
- let intervalHandle = null;
399
- let lastWrittenMenu = '';
400
- /**
401
- * Resolve the workspace TOOLS.md path.
402
- * Uses api.resolvePath if available, otherwise falls back to CWD.
403
- */
404
- function resolveToolsPath(api) {
405
- const resolvePath = api
406
- .resolvePath;
407
- if (typeof resolvePath === 'function') {
408
- return resolvePath('TOOLS.md');
409
- }
410
- return resolve(process.cwd(), 'TOOLS.md');
411
- }
412
- /**
413
- * Upsert the Meta section in TOOLS.md content.
492
+ * OpenClaw plugin for jeeves-meta.
414
493
  *
415
- * Ordering convention: Watcher, Server, Meta.
416
- * - If `## Meta` exists, replace in place.
417
- * - Otherwise insert after `## Server` if present, after `## Watcher` if
418
- * Server is absent, or after the H1.
419
- */
420
- function upsertMetaSection(existing, metaMenu) {
421
- const section = '## Meta\n\n' + metaMenu;
422
- // Replace existing Meta section (match from ## Meta to next ## or # or EOF)
423
- // Remove ALL existing ## Meta sections (handles duplicates from prior bugs)
424
- const re = /^## Meta\n[\s\S]*?(?=\n## |\n# |$(?![\s\S]))/gm;
425
- const cleaned = existing.replace(re, '').replace(/\n{3,}/g, '\n\n');
426
- if (cleaned !== existing) {
427
- // Had at least one section — re-insert at correct position
428
- return upsertMetaSection(cleaned.trim() + '\n', metaMenu);
429
- }
430
- // No existing section. Insert in correct order.
431
- const platformH1 = '# Jeeves Platform Tools';
432
- // After ## Server if present
433
- const serverRe = /^## Server\n[\s\S]*?(?=\n## |\n# |$(?![\s\S]))/m;
434
- const serverMatch = serverRe.exec(existing);
435
- if (serverMatch) {
436
- const insertAt = serverMatch.index + serverMatch[0].length;
437
- return (existing.slice(0, insertAt) + '\n\n' + section + existing.slice(insertAt));
438
- }
439
- // After ## Watcher if present
440
- const watcherRe = /^## Watcher\n[\s\S]*?(?=\n## |\n# |$(?![\s\S]))/m;
441
- const watcherMatch = watcherRe.exec(existing);
442
- if (watcherMatch) {
443
- const insertAt = watcherMatch.index + watcherMatch[0].length;
444
- return (existing.slice(0, insertAt) + '\n\n' + section + existing.slice(insertAt));
445
- }
446
- // After H1 if present
447
- if (existing.includes(platformH1)) {
448
- const idx = existing.indexOf(platformH1) + platformH1.length;
449
- return (existing.slice(0, idx) + '\n\n' + section + '\n' + existing.slice(idx));
450
- }
451
- // Prepend platform header + meta section
452
- const trimmed = existing.trim();
453
- if (trimmed.length === 0) {
454
- return platformH1 + '\n\n' + section + '\n';
455
- }
456
- return platformH1 + '\n\n' + section + '\n\n' + trimmed + '\n';
457
- }
458
- /**
459
- * Fetch the current meta menu and write it to TOOLS.md if changed.
494
+ * Thin HTTP client all operations delegate to the jeeves-meta service.
495
+ * Uses `@karmaniverous/jeeves` core for TOOLS.md and platform content.
460
496
  *
461
- * @param api - Plugin API.
462
- * @param client - MetaServiceClient instance.
463
- * @returns True if the file was updated.
497
+ * @packageDocumentation
464
498
  */
465
- async function refreshToolsMd(api, client) {
466
- const menu = await generateMetaMenu(client);
467
- if (menu === lastWrittenMenu) {
468
- return false;
469
- }
470
- const toolsPath = resolveToolsPath(api);
471
- let current = '';
499
+ /** Plugin version derived from package.json. */
500
+ const PLUGIN_VERSION = (() => {
472
501
  try {
473
- current = await readFile(toolsPath, 'utf8');
502
+ const dir = dirname(fileURLToPath(import.meta.url));
503
+ const pkg = JSON.parse(readFileSync(resolve(dir, '..', 'package.json'), 'utf8'));
504
+ return pkg.version ?? 'unknown';
474
505
  }
475
506
  catch {
476
- // File doesn't exist yet
507
+ return 'unknown';
477
508
  }
478
- const updated = upsertMetaSection(current, menu);
479
- if (updated !== current) {
480
- await writeFile(toolsPath, updated, 'utf8');
481
- lastWrittenMenu = menu;
482
- return true;
483
- }
484
- lastWrittenMenu = menu;
485
- return false;
486
- }
509
+ })();
487
510
  /**
488
- * Start the periodic TOOLS.md writer.
489
- * Defers first write by 5s, then refreshes every 60s.
490
- *
491
- * @param api - Plugin API.
492
- * @param client - MetaServiceClient instance.
511
+ * Resolve the workspace path from the OpenClaw plugin API.
512
+ * Falls back to CWD if `api.resolvePath` is unavailable.
493
513
  */
494
- function startToolsWriter(api, client) {
495
- // Deferred initial write
496
- setTimeout(() => {
497
- refreshToolsMd(api, client).catch((err) => {
498
- console.error('[jeeves-meta] Failed to write TOOLS.md:', err);
499
- });
500
- }, INITIAL_DELAY_MS);
501
- // Periodic refresh
502
- if (intervalHandle) {
503
- clearInterval(intervalHandle);
504
- }
505
- intervalHandle = setInterval(() => {
506
- refreshToolsMd(api, client).catch((err) => {
507
- console.error('[jeeves-meta] Failed to refresh TOOLS.md:', err);
508
- });
509
- }, REFRESH_INTERVAL_MS);
510
- if (typeof intervalHandle === 'object' && 'unref' in intervalHandle) {
511
- intervalHandle.unref();
512
- }
514
+ function resolveWorkspacePath(api) {
515
+ return api.resolvePath ? api.resolvePath('.') : process.cwd();
513
516
  }
514
-
515
- /**
516
- * OpenClaw plugin for jeeves-meta.
517
- *
518
- * Thin HTTP client — all operations delegate to the jeeves-meta service.
519
- * The plugin registers tools and starts the periodic TOOLS.md writer.
520
- *
521
- * @packageDocumentation
522
- */
523
517
  /** Register all jeeves-meta tools with the OpenClaw plugin API. */
524
518
  function register(api) {
525
- const serviceUrl = getServiceUrl(api);
526
- const client = new MetaServiceClient({ serviceUrl });
519
+ const client = new MetaServiceClient({ serviceUrl: getServiceUrl(api) });
527
520
  registerMetaTools(api, client);
528
- // Start periodic TOOLS.md writer (fire-and-forget)
529
- startToolsWriter(api, client);
521
+ init({
522
+ workspacePath: resolveWorkspacePath(api),
523
+ configRoot: getConfigRoot(api),
524
+ });
525
+ const getContent = createAsyncContentCache({
526
+ fetch: async () => generateMetaMenu(client),
527
+ placeholder: 'The jeeves-meta synthesis engine is initializing...\n\n' +
528
+ renderToolsTable(),
529
+ });
530
+ const writer = createComponentWriter({
531
+ name: 'meta',
532
+ version: PLUGIN_VERSION,
533
+ sectionId: 'Meta',
534
+ refreshIntervalSeconds: 73,
535
+ generateToolsContent: getContent,
536
+ serviceCommands: createServiceCommands(client),
537
+ pluginCommands: createPluginCommands(),
538
+ });
539
+ writer.start();
530
540
  }
531
541
 
532
542
  export { register as default };
@@ -3,9 +3,17 @@
3
3
  ## Overview
4
4
 
5
5
  jeeves-meta is the Jeeves platform's knowledge synthesis engine. It discovers
6
- `.meta/` directories via watcher scan, gathers context from the Qdrant vector
7
- index, and uses a three-step LLM process (architect, builder, critic) to
8
- produce structured synthesis artifacts co-located with source content.
6
+ `.meta/` directories via the watcher's filesystem walk endpoint (`POST /walk`),
7
+ gathers context from co-located source files, and uses a three-step LLM process
8
+ (architect, builder, critic) to produce structured synthesis artifacts.
9
+
10
+ **Requires:** jeeves-watcher ≥ 0.10.0 (provides `POST /walk` and auto
11
+ rules-reindex on registration).
12
+
13
+ Discovery is filesystem-based (no Qdrant dependency). The service registers
14
+ virtual inference rules with the watcher for rendering and metadata tagging;
15
+ the watcher's `/status` response includes `rulesRegistered` to surface
16
+ registration health.
9
17
 
10
18
  ## Available Tools
11
19
 
@@ -12,6 +12,7 @@ export interface PluginApi {
12
12
  }>;
13
13
  };
14
14
  };
15
+ resolvePath?: (input: string) => string;
15
16
  registerTool(tool: {
16
17
  name: string;
17
18
  description: string;
@@ -30,14 +31,19 @@ export interface ToolResult {
30
31
  isError?: boolean;
31
32
  }
32
33
  /**
33
- * Resolve the service URL.
34
+ * Resolve a plugin setting via the standard three-step fallback chain:
35
+ * plugin config → environment variable → default value.
34
36
  *
35
- * Resolution order:
36
- * 1. Plugin config `serviceUrl` setting
37
- * 2. `JEEVES_META_URL` environment variable
38
- * 3. Default: http://127.0.0.1:1938
37
+ * @param api - Plugin API.
38
+ * @param configKey - Key in the plugin config object.
39
+ * @param envVar - Environment variable name.
40
+ * @param fallback - Default value if neither source provides one.
39
41
  */
42
+ export declare function resolvePluginSetting(api: PluginApi, configKey: string, envVar: string, fallback: string): string;
43
+ /** Resolve the meta service URL. */
40
44
  export declare function getServiceUrl(api: PluginApi): string;
45
+ /** Resolve the platform config root. */
46
+ export declare function getConfigRoot(api: PluginApi): string;
41
47
  /** Format a successful tool result. */
42
48
  export declare function ok(data: unknown): ToolResult;
43
49
  /** Format an error tool result. */
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests for helper utilities.
3
+ *
4
+ * @module helpers.test
5
+ */
6
+ export {};
@@ -2,7 +2,7 @@
2
2
  * OpenClaw plugin for jeeves-meta.
3
3
  *
4
4
  * Thin HTTP client — all operations delegate to the jeeves-meta service.
5
- * The plugin registers tools and starts the periodic TOOLS.md writer.
5
+ * Uses `@karmaniverous/jeeves` core for TOOLS.md and platform content.
6
6
  *
7
7
  * @packageDocumentation
8
8
  */
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests for TOOLS.md menu generation, including rulesRegistered warning.
3
+ *
4
+ * @module promptInjection.test
5
+ */
6
+ export {};
@@ -2,10 +2,58 @@
2
2
  * Thin HTTP client for the jeeves-meta service.
3
3
  *
4
4
  * Plugin delegates all operations to the running service via HTTP.
5
+ * Response types are defined here as the single source of truth —
6
+ * consumers should not redefine them.
5
7
  *
6
8
  * @module serviceClient
7
9
  */
8
- export interface MetaServiceConfig {
10
+ /** Service status response from GET /status. */
11
+ export interface StatusResponse {
12
+ /** Service uptime in seconds. */
13
+ uptime: number;
14
+ /** Current service status (idle, synthesizing, stopping, degraded). */
15
+ status: string;
16
+ /** Service version. */
17
+ version?: string;
18
+ /** Dependency health. */
19
+ dependencies: {
20
+ watcher: {
21
+ status: string;
22
+ rulesRegistered?: boolean;
23
+ indexing?: boolean;
24
+ };
25
+ gateway: {
26
+ status: string;
27
+ };
28
+ };
29
+ }
30
+ /** Summary block in the metas response. */
31
+ export interface MetasSummary {
32
+ total: number;
33
+ stale: number;
34
+ errors: number;
35
+ neverSynthesized: number;
36
+ stalestPath: string | null;
37
+ lastSynthesizedPath: string | null;
38
+ lastSynthesizedAt: string | null;
39
+ tokens: {
40
+ architect: number;
41
+ builder: number;
42
+ critic: number;
43
+ };
44
+ }
45
+ /** Per-meta item in the metas response. */
46
+ export interface MetasItem {
47
+ stalenessSeconds: number | null;
48
+ [key: string]: unknown;
49
+ }
50
+ /** Response from GET /metas. */
51
+ export interface MetasResponse {
52
+ summary: MetasSummary;
53
+ metas: MetasItem[];
54
+ }
55
+ /** Constructor config. */
56
+ interface MetaServiceConfig {
9
57
  /** Base URL of the jeeves-meta service (e.g. http://127.0.0.1:1938). */
10
58
  serviceUrl: string;
11
59
  }
@@ -17,7 +65,7 @@ export declare class MetaServiceClient {
17
65
  /** POST helper — returns parsed JSON. */
18
66
  private post;
19
67
  /** GET /status — service health + queue state. */
20
- status(): Promise<unknown>;
68
+ status(): Promise<StatusResponse>;
21
69
  /** GET /metas — list all meta entities with summary. */
22
70
  listMetas(params?: {
23
71
  pathPrefix?: string;
@@ -26,7 +74,7 @@ export declare class MetaServiceClient {
26
74
  neverSynthesized?: boolean;
27
75
  locked?: boolean;
28
76
  fields?: string[];
29
- }): Promise<unknown>;
77
+ }): Promise<MetasResponse>;
30
78
  /** GET /metas/:path — detail for a single meta. */
31
79
  detail(metaPath: string, options?: {
32
80
  includeArchive?: boolean | number;
@@ -43,3 +91,4 @@ export declare class MetaServiceClient {
43
91
  /** GET /config/validate — validate current config. */
44
92
  validate(): Promise<unknown>;
45
93
  }
94
+ export {};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * ServiceCommands and PluginCommands implementations for the JeevesComponent
3
+ * descriptor. Separated from register() for single-responsibility.
4
+ *
5
+ * @module serviceCommands
6
+ */
7
+ import type { PluginCommands, ServiceCommands } from '@karmaniverous/jeeves';
8
+ import type { MetaServiceClient } from './serviceClient.js';
9
+ /**
10
+ * Create ServiceCommands that proxy to the meta HTTP service.
11
+ *
12
+ * @param client - MetaServiceClient instance.
13
+ */
14
+ export declare function createServiceCommands(client: MetaServiceClient): ServiceCommands;
15
+ /** Create PluginCommands (uninstall handled by CLI). */
16
+ export declare function createPluginCommands(): PluginCommands;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests for ServiceCommands — status mapping and error handling.
3
+ *
4
+ * @module serviceCommands.test
5
+ */
6
+ export {};
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Tool name/description pairs — single source of truth for tool registration
3
+ * and placeholder content.
4
+ *
5
+ * @module toolMeta
6
+ */
7
+ /** Metadata for a single meta_* tool. */
8
+ interface ToolMeta {
9
+ /** Tool name as registered with OpenClaw. */
10
+ name: string;
11
+ /** Human-readable description. */
12
+ description: string;
13
+ }
14
+ /** Ordered list of meta_* tools. */
15
+ export declare const META_TOOLS: readonly ToolMeta[];
16
+ /**
17
+ * Render the tools table for TOOLS.md injection.
18
+ *
19
+ * Produces a Markdown table with short descriptions suitable for
20
+ * both the placeholder and the live menu.
21
+ */
22
+ export declare function renderToolsTable(): string;
23
+ export {};
@@ -2,6 +2,7 @@
2
2
  * Meta tool registrations for OpenClaw.
3
3
  *
4
4
  * All tools delegate to the jeeves-meta HTTP service.
5
+ * Tool names and descriptions are sourced from {@link META_TOOLS}.
5
6
  *
6
7
  * @module tools
7
8
  */
@@ -2,7 +2,7 @@
2
2
  "id": "jeeves-meta-openclaw",
3
3
  "name": "Jeeves Meta",
4
4
  "description": "Knowledge synthesis tools — trigger synthesis, view status, manage entities.",
5
- "version": "0.2.0",
5
+ "version": "0.4.0",
6
6
  "skills": [
7
7
  "dist/skills/jeeves-meta"
8
8
  ],
@@ -12,6 +12,10 @@
12
12
  "serviceUrl": {
13
13
  "type": "string",
14
14
  "description": "URL of the jeeves-meta HTTP service. Defaults to http://127.0.0.1:1938. Falls back to JEEVES_META_URL env var."
15
+ },
16
+ "configRoot": {
17
+ "type": "string",
18
+ "description": "Absolute path to the platform config root (e.g. j:/config). Used by @karmaniverous/jeeves core for config directory resolution."
15
19
  }
16
20
  }
17
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-meta-openclaw",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "OpenClaw plugin for jeeves-meta — synthesis tools and virtual rule registration",
6
6
  "license": "BSD-3-Clause",
@@ -52,18 +52,19 @@
52
52
  "output": "CHANGELOG.md",
53
53
  "unreleased": true,
54
54
  "commitLimit": false,
55
- "hideCredit": true
55
+ "hideCredit": true,
56
+ "tagPrefix": "openclaw/"
56
57
  },
57
58
  "devDependencies": {
58
- "@dotenvx/dotenvx": "^1.54.1",
59
+ "@dotenvx/dotenvx": "^1.55.1",
59
60
  "@rollup/plugin-typescript": "^12.3.0",
60
61
  "auto-changelog": "^2.5.0",
61
62
  "cross-env": "^10.1.0",
62
- "knip": "^5.85.0",
63
+ "knip": "^5.87.0",
63
64
  "release-it": "^19.2.4",
64
65
  "rollup": "^4.59.0",
65
66
  "tslib": "^2.8.1",
66
- "vitest": "^4.0.18"
67
+ "vitest": "^4.1.0"
67
68
  },
68
69
  "scripts": {
69
70
  "build": "npm run build:plugin && npm run build:skills",
@@ -112,5 +113,8 @@
112
113
  "npm": {
113
114
  "publish": true
114
115
  }
116
+ },
117
+ "dependencies": {
118
+ "@karmaniverous/jeeves": "^0.1.1"
115
119
  }
116
120
  }
@@ -1,27 +0,0 @@
1
- /**
2
- * Periodic TOOLS.md disk writer for the Meta section.
3
- *
4
- * Upserts a `## Meta` section under the shared `# Jeeves Platform Tools`
5
- * header. The gateway reads TOOLS.md fresh from disk on each new session.
6
- *
7
- * @module toolsWriter
8
- */
9
- import type { PluginApi } from './helpers.js';
10
- import type { MetaServiceClient } from './serviceClient.js';
11
- /**
12
- * Upsert the Meta section in TOOLS.md content.
13
- *
14
- * Ordering convention: Watcher, Server, Meta.
15
- * - If `## Meta` exists, replace in place.
16
- * - Otherwise insert after `## Server` if present, after `## Watcher` if
17
- * Server is absent, or after the H1.
18
- */
19
- export declare function upsertMetaSection(existing: string, metaMenu: string): string;
20
- /**
21
- * Start the periodic TOOLS.md writer.
22
- * Defers first write by 5s, then refreshes every 60s.
23
- *
24
- * @param api - Plugin API.
25
- * @param client - MetaServiceClient instance.
26
- */
27
- export declare function startToolsWriter(api: PluginApi, client: MetaServiceClient): void;
@@ -1,6 +0,0 @@
1
- /**
2
- * Tests for the TOOLS.md upsert logic.
3
- *
4
- * @module toolsWriter.test
5
- */
6
- export {};