@karmaniverous/jeeves-watcher-openclaw 0.1.2 → 0.3.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 +3 -1
- package/dist/cli.js +65 -64
- package/dist/index.js +412 -189
- package/dist/scripts/build-skills.d.ts +1 -1
- package/dist/skills/jeeves-watcher/SKILL.md +182 -85
- package/dist/src/cli.d.ts +2 -1
- package/dist/src/cli.test.d.ts +1 -0
- package/dist/src/helpers.d.ts +21 -0
- package/dist/src/helpers.test.d.ts +1 -0
- package/dist/src/index.test.d.ts +1 -0
- package/dist/src/memoryTools.d.ts +17 -0
- package/dist/src/memoryTools.test.d.ts +1 -0
- package/dist/src/watcherTools.d.ts +7 -0
- package/dist/src/watcherTools.test.d.ts +1 -0
- package/openclaw.plugin.json +4 -4
- package/package.json +7 -3
- package/dist/skills/jeeves-watcher-admin/SKILL.md +0 -200
package/dist/index.js
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* @module plugin/helpers
|
|
3
7
|
* Shared types and utility functions for the OpenClaw plugin tool registrations.
|
|
4
8
|
*/
|
|
5
9
|
const DEFAULT_API_URL = 'http://127.0.0.1:3458';
|
|
10
|
+
/** Source identifier for virtual rule registration. */
|
|
11
|
+
const PLUGIN_SOURCE = 'jeeves-watcher-openclaw';
|
|
12
|
+
/** Normalize a path to forward slashes and lowercase drive letter on Windows. */
|
|
13
|
+
function normalizePath(p) {
|
|
14
|
+
let result = p.replace(/\\/g, '/');
|
|
15
|
+
if (/^[A-Z]:/.test(result)) {
|
|
16
|
+
result = result[0].toLowerCase() + result.slice(1);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the workspace path from gateway config.
|
|
22
|
+
* Priority: agent-specific > defaults > fallback (~/.openclaw/workspace).
|
|
23
|
+
*/
|
|
24
|
+
function getWorkspacePath(api) {
|
|
25
|
+
const agentWorkspace = api.config?.agents?.entries?.['main']?.workspace ??
|
|
26
|
+
api.config?.agents?.defaults?.workspace;
|
|
27
|
+
return agentWorkspace ?? join(homedir(), '.openclaw', 'workspace');
|
|
28
|
+
}
|
|
6
29
|
/** Resolve the watcher API base URL from plugin config. */
|
|
7
30
|
function getApiUrl(api) {
|
|
8
31
|
const url = api.config?.plugins?.entries?.['jeeves-watcher']?.config?.apiUrl;
|
|
@@ -22,6 +45,30 @@ function fail(error) {
|
|
|
22
45
|
isError: true,
|
|
23
46
|
};
|
|
24
47
|
}
|
|
48
|
+
/** Format a connection error with actionable guidance. */
|
|
49
|
+
function connectionFail(error, baseUrl) {
|
|
50
|
+
const cause = error instanceof Error ? error.cause : undefined;
|
|
51
|
+
const code = cause && typeof cause === 'object' && 'code' in cause
|
|
52
|
+
? String(cause.code)
|
|
53
|
+
: '';
|
|
54
|
+
const isConnectionError = code === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'ETIMEDOUT';
|
|
55
|
+
if (isConnectionError) {
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: 'text',
|
|
60
|
+
text: [
|
|
61
|
+
`Watcher service not reachable at ${baseUrl}.`,
|
|
62
|
+
'Either start the watcher service, or if it runs on a different port,',
|
|
63
|
+
'set plugins.entries.jeeves-watcher.config.apiUrl in openclaw.json.',
|
|
64
|
+
].join('\n'),
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
isError: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return fail(error);
|
|
71
|
+
}
|
|
25
72
|
/** Fetch JSON from a URL, throwing on non-OK responses. */
|
|
26
73
|
async function fetchJson(url, init) {
|
|
27
74
|
const res = await fetch(url, init);
|
|
@@ -30,238 +77,414 @@ async function fetchJson(url, init) {
|
|
|
30
77
|
}
|
|
31
78
|
return res.json();
|
|
32
79
|
}
|
|
80
|
+
/** POST JSON to a URL and return parsed response. */
|
|
81
|
+
async function postJson(url, body) {
|
|
82
|
+
return fetchJson(url, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: { 'Content-Type': 'application/json' },
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
33
88
|
|
|
34
89
|
/**
|
|
35
|
-
* @module plugin
|
|
36
|
-
*
|
|
90
|
+
* @module plugin/memoryTools
|
|
91
|
+
* memory_search and memory_get tool implementations with lazy init.
|
|
92
|
+
*
|
|
93
|
+
* Lazy init registers virtual inference rules with the watcher on first
|
|
94
|
+
* memory_search call. Re-attempts on failure. memory_get reads files
|
|
95
|
+
* directly from the filesystem with path validation.
|
|
37
96
|
*/
|
|
38
|
-
/**
|
|
39
|
-
function
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
api.registerTool({
|
|
55
|
-
name: 'watcher_search',
|
|
56
|
-
description: 'Semantic search over indexed documents. Supports Qdrant filters.',
|
|
57
|
-
parameters: {
|
|
58
|
-
type: 'object',
|
|
59
|
-
required: ['query'],
|
|
60
|
-
properties: {
|
|
61
|
-
query: { type: 'string', description: 'Search query text.' },
|
|
62
|
-
limit: {
|
|
63
|
-
type: 'number',
|
|
64
|
-
description: 'Max results (default 10).',
|
|
97
|
+
/** Build virtual inference rules for a workspace path. */
|
|
98
|
+
function buildVirtualRules(workspace) {
|
|
99
|
+
const ws = normalizePath(workspace);
|
|
100
|
+
return [
|
|
101
|
+
{
|
|
102
|
+
name: 'openclaw-memory-longterm',
|
|
103
|
+
description: 'OpenClaw long-term memory file',
|
|
104
|
+
match: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
file: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
path: { type: 'string', pattern: `^${ws}/MEMORY\\.md$` },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
65
113
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
},
|
|
70
|
-
filter: {
|
|
114
|
+
},
|
|
115
|
+
schema: [
|
|
116
|
+
{
|
|
71
117
|
type: 'object',
|
|
72
|
-
|
|
118
|
+
properties: {
|
|
119
|
+
domain: { type: 'string', set: 'memory' },
|
|
120
|
+
kind: { type: 'string', set: 'long-term' },
|
|
121
|
+
},
|
|
73
122
|
},
|
|
74
|
-
|
|
123
|
+
],
|
|
75
124
|
},
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
body: JSON.stringify(body),
|
|
89
|
-
}));
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
return fail(error);
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
}, { optional: true });
|
|
96
|
-
api.registerTool({
|
|
97
|
-
name: 'watcher_enrich',
|
|
98
|
-
description: 'Set or update metadata on a document by file path.',
|
|
99
|
-
parameters: {
|
|
100
|
-
type: 'object',
|
|
101
|
-
required: ['path', 'metadata'],
|
|
102
|
-
properties: {
|
|
103
|
-
path: {
|
|
104
|
-
type: 'string',
|
|
105
|
-
description: 'Relative file path of the document.',
|
|
125
|
+
{
|
|
126
|
+
name: 'openclaw-memory-daily',
|
|
127
|
+
description: 'OpenClaw daily memory logs',
|
|
128
|
+
match: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: {
|
|
131
|
+
file: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {
|
|
134
|
+
path: { type: 'string', pattern: `^${ws}/memory/.*\\.md$` },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
106
137
|
},
|
|
107
|
-
|
|
138
|
+
},
|
|
139
|
+
schema: [
|
|
140
|
+
{
|
|
108
141
|
type: 'object',
|
|
109
|
-
|
|
142
|
+
properties: {
|
|
143
|
+
domain: { type: 'string', set: 'memory' },
|
|
144
|
+
kind: { type: 'string', set: 'daily-log' },
|
|
145
|
+
},
|
|
110
146
|
},
|
|
111
|
-
|
|
147
|
+
],
|
|
112
148
|
},
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
/** Validate a path is within the memory scope. */
|
|
152
|
+
function isAllowedMemoryPath(filePath, workspace) {
|
|
153
|
+
const norm = normalizePath(filePath).toLowerCase();
|
|
154
|
+
const ws = normalizePath(workspace).toLowerCase();
|
|
155
|
+
// Exact match: {workspace}/MEMORY.md
|
|
156
|
+
if (norm === `${ws}/memory.md`)
|
|
157
|
+
return true;
|
|
158
|
+
// Prefix match: {workspace}/memory/**/*.md
|
|
159
|
+
if (norm.startsWith(`${ws}/memory/`) && norm.endsWith('.md'))
|
|
160
|
+
return true;
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Create memory tool registrations for the plugin.
|
|
165
|
+
* Returns register functions for memory_search and memory_get.
|
|
166
|
+
*/
|
|
167
|
+
function createMemoryTools(api, baseUrl) {
|
|
168
|
+
const workspace = getWorkspacePath(api);
|
|
169
|
+
const state = {
|
|
170
|
+
initialized: false,
|
|
171
|
+
workspace,
|
|
172
|
+
baseUrl,
|
|
173
|
+
};
|
|
174
|
+
/** Lazy init: register virtual rules with watcher. */
|
|
175
|
+
async function ensureInit() {
|
|
176
|
+
if (state.initialized)
|
|
177
|
+
return;
|
|
178
|
+
// Check watcher is reachable
|
|
179
|
+
await fetchJson(`${state.baseUrl}/status`);
|
|
180
|
+
// Clear any stale rules
|
|
181
|
+
await fetchJson(`${state.baseUrl}/rules/unregister`, {
|
|
182
|
+
method: 'DELETE',
|
|
183
|
+
headers: { 'Content-Type': 'application/json' },
|
|
184
|
+
body: JSON.stringify({ source: PLUGIN_SOURCE }),
|
|
185
|
+
});
|
|
186
|
+
// Register virtual rules
|
|
187
|
+
await postJson(`${state.baseUrl}/rules/register`, {
|
|
188
|
+
source: PLUGIN_SOURCE,
|
|
189
|
+
rules: buildVirtualRules(state.workspace),
|
|
190
|
+
});
|
|
191
|
+
state.initialized = true;
|
|
192
|
+
}
|
|
193
|
+
const memorySearch = async (_id, params) => {
|
|
194
|
+
try {
|
|
195
|
+
await ensureInit();
|
|
196
|
+
const body = {
|
|
197
|
+
query: params.query,
|
|
198
|
+
filter: {
|
|
199
|
+
must: [{ key: 'domain', match: { value: 'memory' } }],
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
if (params.maxResults !== undefined)
|
|
203
|
+
body.limit = params.maxResults;
|
|
204
|
+
const raw = await postJson(`${state.baseUrl}/search`, body);
|
|
205
|
+
// Map results to system-prompt-compatible format
|
|
206
|
+
const results = raw.map((r) => {
|
|
207
|
+
const payload = r.payload;
|
|
208
|
+
const mapped = {
|
|
209
|
+
path: payload.file_path,
|
|
210
|
+
snippet: payload.chunk_text,
|
|
211
|
+
score: r.score,
|
|
212
|
+
};
|
|
213
|
+
if (payload.line_start != null)
|
|
214
|
+
mapped.from = payload.line_start;
|
|
215
|
+
if (payload.line_end != null)
|
|
216
|
+
mapped.to = payload.line_end;
|
|
217
|
+
return mapped;
|
|
218
|
+
});
|
|
219
|
+
const minScore = typeof params.minScore === 'number' ? params.minScore : 0;
|
|
220
|
+
const filtered = results.filter((r) => typeof r.score === 'number' && r.score >= minScore);
|
|
221
|
+
return ok(filtered);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
state.initialized = false;
|
|
225
|
+
return connectionFail(error, state.baseUrl);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const memoryGet = async (_id, params) => {
|
|
229
|
+
try {
|
|
230
|
+
const filePath = String(params.path);
|
|
231
|
+
// Re-derive workspace on every call for immediate pickup of moves
|
|
232
|
+
const currentWorkspace = getWorkspacePath(api);
|
|
233
|
+
if (!isAllowedMemoryPath(filePath, currentWorkspace)) {
|
|
234
|
+
return fail(`Path not within memory scope. Allowed: ${currentWorkspace}/MEMORY.md and ${currentWorkspace}/memory/**/*.md`);
|
|
235
|
+
}
|
|
236
|
+
const content = await readFile(filePath, 'utf-8');
|
|
237
|
+
if (params.from !== undefined) {
|
|
238
|
+
const from = Number(params.from);
|
|
239
|
+
const lines = content.split('\n');
|
|
240
|
+
const startIdx = Math.max(0, from - 1); // 1-indexed to 0-indexed
|
|
241
|
+
const count = params.lines !== undefined
|
|
242
|
+
? Number(params.lines)
|
|
243
|
+
: lines.length - startIdx;
|
|
244
|
+
const sliced = lines.slice(startIdx, startIdx + count);
|
|
245
|
+
return ok(sliced.join('\n'));
|
|
246
|
+
}
|
|
247
|
+
return ok(content);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
return fail(error);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
return { memorySearch, memoryGet };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @module plugin/watcherTools
|
|
258
|
+
* Watcher tool registrations (watcher_* tools) for the OpenClaw plugin.
|
|
259
|
+
*/
|
|
260
|
+
/** Register a single API tool with standard try/catch + ok/connectionFail. */
|
|
261
|
+
function registerApiTool(api, baseUrl, config) {
|
|
262
|
+
api.registerTool({
|
|
263
|
+
name: config.name,
|
|
264
|
+
description: config.description,
|
|
265
|
+
parameters: config.parameters,
|
|
113
266
|
execute: async (_id, params) => {
|
|
114
267
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}),
|
|
122
|
-
}));
|
|
268
|
+
const [endpoint, body] = config.buildRequest(params);
|
|
269
|
+
const url = `${baseUrl}${endpoint}`;
|
|
270
|
+
const data = body !== undefined
|
|
271
|
+
? await postJson(url, body)
|
|
272
|
+
: await fetchJson(url);
|
|
273
|
+
return ok(data);
|
|
123
274
|
}
|
|
124
275
|
catch (error) {
|
|
125
|
-
return
|
|
276
|
+
return connectionFail(error, baseUrl);
|
|
126
277
|
}
|
|
127
278
|
},
|
|
128
279
|
}, { optional: true });
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
280
|
+
}
|
|
281
|
+
/** Pick defined keys from params into a body object. */
|
|
282
|
+
function pickDefined(params, keys) {
|
|
283
|
+
const body = {};
|
|
284
|
+
for (const key of keys) {
|
|
285
|
+
if (params[key] !== undefined)
|
|
286
|
+
body[key] = params[key];
|
|
287
|
+
}
|
|
288
|
+
return body;
|
|
289
|
+
}
|
|
290
|
+
/** Register all 8 watcher_* tools with the OpenClaw plugin API. */
|
|
291
|
+
function registerWatcherTools(api, baseUrl) {
|
|
292
|
+
const tools = [
|
|
293
|
+
{
|
|
294
|
+
name: 'watcher_status',
|
|
295
|
+
description: 'Get jeeves-watcher service health, uptime, and collection statistics.',
|
|
296
|
+
parameters: { type: 'object', properties: {} },
|
|
297
|
+
buildRequest: () => ['/status'],
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: 'watcher_search',
|
|
301
|
+
description: 'Semantic search over indexed documents. Supports Qdrant filters.',
|
|
302
|
+
parameters: {
|
|
303
|
+
type: 'object',
|
|
304
|
+
required: ['query'],
|
|
305
|
+
properties: {
|
|
306
|
+
query: { type: 'string', description: 'Search query text.' },
|
|
307
|
+
limit: { type: 'number', description: 'Max results (default 10).' },
|
|
308
|
+
offset: {
|
|
309
|
+
type: 'number',
|
|
310
|
+
description: 'Number of results to skip for pagination.',
|
|
311
|
+
},
|
|
312
|
+
filter: { type: 'object', description: 'Qdrant filter object.' },
|
|
139
313
|
},
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
314
|
+
},
|
|
315
|
+
buildRequest: (params) => {
|
|
316
|
+
const body = pickDefined(params, [
|
|
317
|
+
'query',
|
|
318
|
+
'limit',
|
|
319
|
+
'offset',
|
|
320
|
+
'filter',
|
|
321
|
+
]);
|
|
322
|
+
return ['/search', body];
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: 'watcher_enrich',
|
|
327
|
+
description: 'Set or update metadata on a document by file path.',
|
|
328
|
+
parameters: {
|
|
329
|
+
type: 'object',
|
|
330
|
+
required: ['path', 'metadata'],
|
|
331
|
+
properties: {
|
|
332
|
+
path: {
|
|
333
|
+
type: 'string',
|
|
334
|
+
description: 'Relative file path of the document.',
|
|
335
|
+
},
|
|
336
|
+
metadata: {
|
|
337
|
+
type: 'object',
|
|
338
|
+
description: 'Key-value metadata to set on the document.',
|
|
339
|
+
},
|
|
144
340
|
},
|
|
145
341
|
},
|
|
342
|
+
buildRequest: (params) => [
|
|
343
|
+
'/metadata',
|
|
344
|
+
{ path: params.path, metadata: params.metadata },
|
|
345
|
+
],
|
|
146
346
|
},
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
347
|
+
{
|
|
348
|
+
name: 'watcher_query',
|
|
349
|
+
description: 'Query the merged virtual document via JSONPath.',
|
|
350
|
+
parameters: {
|
|
351
|
+
type: 'object',
|
|
352
|
+
required: ['path'],
|
|
353
|
+
properties: {
|
|
354
|
+
path: { type: 'string', description: 'JSONPath expression.' },
|
|
355
|
+
resolve: {
|
|
356
|
+
type: 'array',
|
|
357
|
+
items: { type: 'string', enum: ['files', 'globals'] },
|
|
358
|
+
description: 'Resolution scopes to include (e.g., ["files"], ["globals"], or both).',
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
buildRequest: (params) => {
|
|
363
|
+
const body = pickDefined(params, ['path', 'resolve']);
|
|
364
|
+
return ['/config/query', body];
|
|
365
|
+
},
|
|
161
366
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
367
|
+
{
|
|
368
|
+
name: 'watcher_validate',
|
|
369
|
+
description: 'Validate a candidate config (or current config if omitted). Optionally test file paths against the config to preview rule matching and metadata output.',
|
|
370
|
+
parameters: {
|
|
371
|
+
type: 'object',
|
|
372
|
+
properties: {
|
|
373
|
+
config: {
|
|
374
|
+
type: 'object',
|
|
375
|
+
description: 'Candidate config (partial or full). Omit to validate current config.',
|
|
376
|
+
},
|
|
377
|
+
testPaths: {
|
|
378
|
+
type: 'array',
|
|
379
|
+
items: { type: 'string' },
|
|
380
|
+
description: 'File paths to test against the config for dry-run preview.',
|
|
381
|
+
},
|
|
172
382
|
},
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
383
|
+
},
|
|
384
|
+
buildRequest: (params) => {
|
|
385
|
+
const body = pickDefined(params, ['config', 'testPaths']);
|
|
386
|
+
return ['/config/validate', body];
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: 'watcher_config_apply',
|
|
391
|
+
description: 'Apply a full or partial config. Validates, writes to disk, and triggers configured reindex behavior.',
|
|
392
|
+
parameters: {
|
|
393
|
+
type: 'object',
|
|
394
|
+
required: ['config'],
|
|
395
|
+
properties: {
|
|
396
|
+
config: {
|
|
397
|
+
type: 'object',
|
|
398
|
+
description: 'Full or partial config to apply.',
|
|
399
|
+
},
|
|
177
400
|
},
|
|
178
401
|
},
|
|
402
|
+
buildRequest: (params) => ['/config/apply', { config: params.config }],
|
|
179
403
|
},
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
404
|
+
{
|
|
405
|
+
name: 'watcher_reindex',
|
|
406
|
+
description: 'Trigger a reindex of the watched files.',
|
|
407
|
+
parameters: {
|
|
408
|
+
type: 'object',
|
|
409
|
+
properties: {
|
|
410
|
+
scope: {
|
|
411
|
+
type: 'string',
|
|
412
|
+
enum: ['rules', 'full'],
|
|
413
|
+
description: 'Reindex scope: "rules" (default) re-applies inference rules; "full" re-embeds everything.',
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
buildRequest: (params) => [
|
|
418
|
+
'/config-reindex',
|
|
419
|
+
{ scope: params.scope ?? 'rules' },
|
|
420
|
+
],
|
|
196
421
|
},
|
|
197
|
-
|
|
422
|
+
{
|
|
423
|
+
name: 'watcher_issues',
|
|
424
|
+
description: 'Get runtime embedding failures. Shows files that failed processing and why.',
|
|
425
|
+
parameters: { type: 'object', properties: {} },
|
|
426
|
+
buildRequest: () => ['/issues'],
|
|
427
|
+
},
|
|
428
|
+
];
|
|
429
|
+
for (const tool of tools) {
|
|
430
|
+
registerApiTool(api, baseUrl, tool);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* @module plugin
|
|
436
|
+
* OpenClaw plugin entry point. Registers all jeeves-watcher tools.
|
|
437
|
+
*/
|
|
438
|
+
/** Register all jeeves-watcher tools with the OpenClaw plugin API. */
|
|
439
|
+
function register(api) {
|
|
440
|
+
const baseUrl = getApiUrl(api);
|
|
441
|
+
// Register 8 watcher_* tools
|
|
442
|
+
registerWatcherTools(api, baseUrl);
|
|
443
|
+
// Register memory slot tools (memory_search + memory_get)
|
|
444
|
+
const { memorySearch, memoryGet } = createMemoryTools(api, baseUrl);
|
|
198
445
|
api.registerTool({
|
|
199
|
-
name: '
|
|
200
|
-
description: '
|
|
446
|
+
name: 'memory_search',
|
|
447
|
+
description: 'Semantically search MEMORY.md and memory/*.md files. Returns top snippets with path and line numbers.',
|
|
201
448
|
parameters: {
|
|
202
449
|
type: 'object',
|
|
203
|
-
required: ['
|
|
450
|
+
required: ['query'],
|
|
204
451
|
properties: {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
452
|
+
query: { type: 'string', description: 'Search query text.' },
|
|
453
|
+
maxResults: {
|
|
454
|
+
type: 'number',
|
|
455
|
+
description: 'Maximum results to return.',
|
|
456
|
+
},
|
|
457
|
+
minScore: {
|
|
458
|
+
type: 'number',
|
|
459
|
+
description: 'Minimum similarity score threshold.',
|
|
208
460
|
},
|
|
209
461
|
},
|
|
210
462
|
},
|
|
211
|
-
execute:
|
|
212
|
-
|
|
213
|
-
return ok(await fetchJson(`${baseUrl}/config/apply`, {
|
|
214
|
-
method: 'POST',
|
|
215
|
-
headers: { 'Content-Type': 'application/json' },
|
|
216
|
-
body: JSON.stringify({ config: params.config }),
|
|
217
|
-
}));
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
return fail(error);
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
}, { optional: true });
|
|
463
|
+
execute: memorySearch,
|
|
464
|
+
});
|
|
224
465
|
api.registerTool({
|
|
225
|
-
name: '
|
|
226
|
-
description: '
|
|
466
|
+
name: 'memory_get',
|
|
467
|
+
description: 'Read content from MEMORY.md or memory/*.md files with optional line range.',
|
|
227
468
|
parameters: {
|
|
228
469
|
type: 'object',
|
|
470
|
+
required: ['path'],
|
|
229
471
|
properties: {
|
|
230
|
-
|
|
472
|
+
path: {
|
|
231
473
|
type: 'string',
|
|
232
|
-
|
|
233
|
-
|
|
474
|
+
description: 'Path to the memory file to read.',
|
|
475
|
+
},
|
|
476
|
+
from: {
|
|
477
|
+
type: 'number',
|
|
478
|
+
description: 'Line number to start reading from (1-indexed).',
|
|
479
|
+
},
|
|
480
|
+
lines: {
|
|
481
|
+
type: 'number',
|
|
482
|
+
description: 'Number of lines to read.',
|
|
234
483
|
},
|
|
235
484
|
},
|
|
236
485
|
},
|
|
237
|
-
execute:
|
|
238
|
-
|
|
239
|
-
return ok(await fetchJson(`${baseUrl}/config-reindex`, {
|
|
240
|
-
method: 'POST',
|
|
241
|
-
headers: { 'Content-Type': 'application/json' },
|
|
242
|
-
body: JSON.stringify({
|
|
243
|
-
scope: params.scope ?? 'rules',
|
|
244
|
-
}),
|
|
245
|
-
}));
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
return fail(error);
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
}, { optional: true });
|
|
252
|
-
api.registerTool({
|
|
253
|
-
name: 'watcher_issues',
|
|
254
|
-
description: 'Get runtime embedding failures. Shows files that failed processing and why.',
|
|
255
|
-
parameters: { type: 'object', properties: {} },
|
|
256
|
-
execute: async () => {
|
|
257
|
-
try {
|
|
258
|
-
return ok(await fetchJson(`${baseUrl}/issues`));
|
|
259
|
-
}
|
|
260
|
-
catch (error) {
|
|
261
|
-
return fail(error);
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
}, { optional: true });
|
|
486
|
+
execute: memoryGet,
|
|
487
|
+
});
|
|
265
488
|
}
|
|
266
489
|
|
|
267
490
|
export { register as default };
|