@karmaniverous/jeeves-watcher-openclaw 0.5.3 → 0.5.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/dist/index.js +97 -62
- package/dist/src/promptInjection.d.ts +5 -0
- package/dist/src/toolsWriter.d.ts +20 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* @module plugin/helpers
|
|
3
6
|
* Shared types and utility functions for the OpenClaw plugin tool registrations.
|
|
4
7
|
*/
|
|
5
8
|
const DEFAULT_API_URL = 'http://127.0.0.1:1936';
|
|
6
|
-
const DEFAULT_CACHE_TTL_MS = 30000;
|
|
7
9
|
/** Extract plugin config from the API object */
|
|
8
10
|
function getPluginConfig(api) {
|
|
9
11
|
return api.config?.plugins?.entries?.['jeeves-watcher-openclaw']?.config;
|
|
@@ -13,11 +15,6 @@ function getApiUrl(api) {
|
|
|
13
15
|
const url = getPluginConfig(api)?.apiUrl;
|
|
14
16
|
return typeof url === 'string' ? url : DEFAULT_API_URL;
|
|
15
17
|
}
|
|
16
|
-
/** Resolve the cache TTL for plugin hooks from config. */
|
|
17
|
-
function getCacheTtlMs(api) {
|
|
18
|
-
const ttl = getPluginConfig(api)?.cacheTtlMs;
|
|
19
|
-
return typeof ttl === 'number' ? ttl : DEFAULT_CACHE_TTL_MS;
|
|
20
|
-
}
|
|
21
18
|
/** Format a successful tool result. */
|
|
22
19
|
function ok(data) {
|
|
23
20
|
return {
|
|
@@ -73,14 +70,6 @@ async function postJson(url, body) {
|
|
|
73
70
|
});
|
|
74
71
|
}
|
|
75
72
|
|
|
76
|
-
let menuCache = null;
|
|
77
|
-
function isAgentBootstrapEventContext(value) {
|
|
78
|
-
if (!value || typeof value !== 'object') {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
const v = value;
|
|
82
|
-
return Array.isArray(v.bootstrapFiles);
|
|
83
|
-
}
|
|
84
73
|
async function fetchJson(url, init) {
|
|
85
74
|
const res = await fetch(url, init);
|
|
86
75
|
if (!res.ok) {
|
|
@@ -177,58 +166,106 @@ async function generateWatcherMenu(apiUrl) {
|
|
|
177
166
|
}
|
|
178
167
|
return lines.join('\n');
|
|
179
168
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @module plugin/toolsWriter
|
|
172
|
+
* Writes the Watcher menu section directly to TOOLS.md on disk.
|
|
173
|
+
* Replaces the agent:bootstrap hook approach which was unreliable due to
|
|
174
|
+
* OpenClaw's clearInternalHooks() wiping plugin-registered hooks on startup.
|
|
175
|
+
*/
|
|
176
|
+
const REFRESH_INTERVAL_MS = 60_000;
|
|
177
|
+
let intervalHandle = null;
|
|
178
|
+
let lastWrittenMenu = '';
|
|
179
|
+
/**
|
|
180
|
+
* Resolve the workspace TOOLS.md path.
|
|
181
|
+
* Uses api.resolvePath if available, otherwise falls back to CWD.
|
|
182
|
+
*/
|
|
183
|
+
function resolveToolsPath(api) {
|
|
184
|
+
const resolvePath = api
|
|
185
|
+
.resolvePath;
|
|
186
|
+
if (typeof resolvePath === 'function') {
|
|
187
|
+
return resolvePath('TOOLS.md');
|
|
184
188
|
}
|
|
185
|
-
|
|
186
|
-
menuCache = { value: menu, expiresAt: now + ttlMs };
|
|
187
|
-
return menu;
|
|
189
|
+
return resolve(process.cwd(), 'TOOLS.md');
|
|
188
190
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Upsert the watcher section in TOOLS.md content.
|
|
193
|
+
*
|
|
194
|
+
* Strategy:
|
|
195
|
+
* - If a `## Watcher` section already exists, replace it in place.
|
|
196
|
+
* - Otherwise, prepend `# Jeeves Platform Tools\n\n## Watcher\n\n...`
|
|
197
|
+
* before any existing content.
|
|
198
|
+
*/
|
|
199
|
+
function upsertWatcherContent(existing, watcherMenu) {
|
|
200
|
+
const section = `## Watcher\n\n${watcherMenu}`;
|
|
201
|
+
// Replace existing watcher section (match from ## Watcher to next ## or # or EOF)
|
|
202
|
+
const re = /^## Watcher\n[\s\S]*?(?=\n## |\n# |$(?![\s\S]))/m;
|
|
203
|
+
if (re.test(existing)) {
|
|
204
|
+
return existing.replace(re, section);
|
|
205
|
+
}
|
|
206
|
+
// No existing section. Prepend under a platform tools H1.
|
|
207
|
+
const platformH1 = '# Jeeves Platform Tools';
|
|
208
|
+
if (existing.includes(platformH1)) {
|
|
209
|
+
// Insert after the H1
|
|
210
|
+
const idx = existing.indexOf(platformH1) + platformH1.length;
|
|
211
|
+
return existing.slice(0, idx) + `\n\n${section}\n` + existing.slice(idx);
|
|
192
212
|
}
|
|
193
|
-
|
|
213
|
+
// Prepend platform header + watcher section before existing content
|
|
214
|
+
const trimmed = existing.trim();
|
|
215
|
+
if (trimmed.length === 0) {
|
|
216
|
+
return `${platformH1}\n\n${section}\n`;
|
|
217
|
+
}
|
|
218
|
+
return `${platformH1}\n\n${section}\n\n${trimmed}\n`;
|
|
194
219
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Fetch the current watcher menu and write it to TOOLS.md if changed.
|
|
222
|
+
* Returns true if the file was updated.
|
|
223
|
+
*/
|
|
224
|
+
async function refreshToolsMd(api) {
|
|
225
|
+
const apiUrl = getApiUrl(api);
|
|
226
|
+
const menu = await generateWatcherMenu(apiUrl);
|
|
227
|
+
if (menu === lastWrittenMenu) {
|
|
228
|
+
return false;
|
|
201
229
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const afterH1 = idx + h1.length;
|
|
207
|
-
return (toolsMd.slice(0, afterH1) + `\n\n${section}` + toolsMd.slice(afterH1));
|
|
230
|
+
const toolsPath = resolveToolsPath(api);
|
|
231
|
+
let current = '';
|
|
232
|
+
try {
|
|
233
|
+
current = await readFile(toolsPath, 'utf8');
|
|
208
234
|
}
|
|
209
|
-
|
|
235
|
+
catch {
|
|
236
|
+
// File doesn't exist yet — we'll create it
|
|
237
|
+
}
|
|
238
|
+
const updated = upsertWatcherContent(current, menu);
|
|
239
|
+
if (updated !== current) {
|
|
240
|
+
await writeFile(toolsPath, updated, 'utf8');
|
|
241
|
+
lastWrittenMenu = menu;
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
lastWrittenMenu = menu;
|
|
245
|
+
return false;
|
|
210
246
|
}
|
|
211
247
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
248
|
+
* Start the periodic TOOLS.md writer.
|
|
249
|
+
* Writes immediately on startup, then refreshes every REFRESH_INTERVAL_MS.
|
|
214
250
|
*/
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
251
|
+
function startToolsWriter(api) {
|
|
252
|
+
// Initial write (fire and forget — errors logged but not thrown)
|
|
253
|
+
refreshToolsMd(api).catch((err) => {
|
|
254
|
+
console.error('[jeeves-watcher] Failed to write TOOLS.md:', err);
|
|
255
|
+
});
|
|
256
|
+
// Periodic refresh
|
|
257
|
+
if (intervalHandle) {
|
|
258
|
+
clearInterval(intervalHandle);
|
|
219
259
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
260
|
+
intervalHandle = setInterval(() => {
|
|
261
|
+
refreshToolsMd(api).catch((err) => {
|
|
262
|
+
console.error('[jeeves-watcher] Failed to refresh TOOLS.md:', err);
|
|
263
|
+
});
|
|
264
|
+
}, REFRESH_INTERVAL_MS);
|
|
265
|
+
// Don't keep the process alive just for this interval
|
|
266
|
+
if (typeof intervalHandle === 'object' && 'unref' in intervalHandle) {
|
|
267
|
+
intervalHandle.unref();
|
|
227
268
|
}
|
|
228
|
-
const current = toolsFile.content ?? '';
|
|
229
|
-
const withH1 = ensurePlatformToolsSection(current);
|
|
230
|
-
const updated = upsertWatcherSection(withH1, watcherMenu);
|
|
231
|
-
toolsFile.content = updated;
|
|
232
269
|
}
|
|
233
270
|
|
|
234
271
|
/**
|
|
@@ -417,13 +454,11 @@ function registerWatcherTools(api, baseUrl) {
|
|
|
417
454
|
function register(api) {
|
|
418
455
|
const baseUrl = getApiUrl(api);
|
|
419
456
|
registerWatcherTools(api, baseUrl);
|
|
420
|
-
//
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}, { name: 'jeeves-watcher-openclaw' });
|
|
426
|
-
}
|
|
457
|
+
// Write the watcher menu to TOOLS.md on disk, refreshing periodically.
|
|
458
|
+
// This replaces the agent:bootstrap hook approach which was unreliable
|
|
459
|
+
// because OpenClaw's clearInternalHooks() wipes plugin-registered hooks
|
|
460
|
+
// during the async startup sequence.
|
|
461
|
+
startToolsWriter(api);
|
|
427
462
|
}
|
|
428
463
|
|
|
429
464
|
export { register as default };
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { type PluginApi } from './helpers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fetches data from the watcher API and generates a Markdown menu string.
|
|
4
|
+
* The string is platform-agnostic and safe to inject into TOOLS.md.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateWatcherMenu(apiUrl: string): Promise<string>;
|
|
2
7
|
/**
|
|
3
8
|
* Hook handler for agent:bootstrap.
|
|
4
9
|
* Injects/updates the Watcher Menu into the TOOLS.md payload.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugin/toolsWriter
|
|
3
|
+
* Writes the Watcher menu section directly to TOOLS.md on disk.
|
|
4
|
+
* Replaces the agent:bootstrap hook approach which was unreliable due to
|
|
5
|
+
* OpenClaw's clearInternalHooks() wiping plugin-registered hooks on startup.
|
|
6
|
+
*/
|
|
7
|
+
import { type PluginApi } from './helpers.js';
|
|
8
|
+
/**
|
|
9
|
+
* Start the periodic TOOLS.md writer.
|
|
10
|
+
* Writes immediately on startup, then refreshes every REFRESH_INTERVAL_MS.
|
|
11
|
+
*/
|
|
12
|
+
export declare function startToolsWriter(api: PluginApi): void;
|
|
13
|
+
/**
|
|
14
|
+
* Force an immediate refresh (e.g., after watcher_config_apply).
|
|
15
|
+
*/
|
|
16
|
+
export declare function forceRefreshToolsMd(api: PluginApi): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Stop the periodic writer (for cleanup).
|
|
19
|
+
*/
|
|
20
|
+
export declare function stopToolsWriter(): void;
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "jeeves-watcher-openclaw",
|
|
3
3
|
"name": "Jeeves Watcher",
|
|
4
4
|
"description": "Semantic search, metadata enrichment, and instance administration for a jeeves-watcher deployment.",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.5",
|
|
6
6
|
"skills": [
|
|
7
7
|
"dist/skills/jeeves-watcher"
|
|
8
8
|
],
|
package/package.json
CHANGED