@karmaniverous/jeeves-watcher-openclaw 0.5.7 → 0.6.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 +1 -0
- package/dist/index.js +73 -14
- package/dist/skills/jeeves-watcher/SKILL.md +80 -1
- package/dist/src/watcherTools.d.ts +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ On startup, the plugin writes a `## Watcher` section to `TOOLS.md` in the agent'
|
|
|
57
57
|
| `watcher_validate` | Validate a watcher configuration |
|
|
58
58
|
| `watcher_config_apply` | Apply a new configuration |
|
|
59
59
|
| `watcher_reindex` | Trigger a full reindex |
|
|
60
|
+
| `watcher_scan` | Filter-only point query with cursor pagination |
|
|
60
61
|
| `watcher_issues` | List indexing issues and errors |
|
|
61
62
|
|
|
62
63
|
## Documentation
|
package/dist/index.js
CHANGED
|
@@ -54,7 +54,7 @@ function connectionFail(error, baseUrl) {
|
|
|
54
54
|
return fail(error);
|
|
55
55
|
}
|
|
56
56
|
/** Fetch JSON from a URL, throwing on non-OK responses. */
|
|
57
|
-
async function fetchJson
|
|
57
|
+
async function fetchJson(url, init) {
|
|
58
58
|
const res = await fetch(url, init);
|
|
59
59
|
if (!res.ok) {
|
|
60
60
|
throw new Error('HTTP ' + String(res.status) + ': ' + (await res.text()));
|
|
@@ -63,20 +63,13 @@ async function fetchJson$1(url, init) {
|
|
|
63
63
|
}
|
|
64
64
|
/** POST JSON to a URL and return parsed response. */
|
|
65
65
|
async function postJson(url, body) {
|
|
66
|
-
return fetchJson
|
|
66
|
+
return fetchJson(url, {
|
|
67
67
|
method: 'POST',
|
|
68
68
|
headers: { 'Content-Type': 'application/json' },
|
|
69
69
|
body: JSON.stringify(body),
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
async function fetchJson(url, init) {
|
|
74
|
-
const res = await fetch(url, init);
|
|
75
|
-
if (!res.ok) {
|
|
76
|
-
throw new Error(`HTTP ${String(res.status)}: ${await res.text()}`);
|
|
77
|
-
}
|
|
78
|
-
return res.json();
|
|
79
|
-
}
|
|
80
73
|
/**
|
|
81
74
|
* Fetches data from the watcher API and generates a Markdown menu string.
|
|
82
75
|
* The string is platform-agnostic and safe to inject into TOOLS.md.
|
|
@@ -87,7 +80,7 @@ async function generateWatcherMenu(apiUrl) {
|
|
|
87
80
|
const watchPaths = [];
|
|
88
81
|
const ignoredPaths = [];
|
|
89
82
|
try {
|
|
90
|
-
const [statusRes, rulesRes, pathsRes, ignoredRes] = await Promise.all([
|
|
83
|
+
const [statusRes, rulesRes, pathsRes, ignoredRes] = (await Promise.all([
|
|
91
84
|
fetchJson(`${apiUrl}/status`),
|
|
92
85
|
fetchJson(`${apiUrl}/config/query`, {
|
|
93
86
|
method: 'POST',
|
|
@@ -104,7 +97,7 @@ async function generateWatcherMenu(apiUrl) {
|
|
|
104
97
|
headers: { 'Content-Type': 'application/json' },
|
|
105
98
|
body: JSON.stringify({ path: '$.watch.ignored[*]' }),
|
|
106
99
|
}),
|
|
107
|
-
]);
|
|
100
|
+
]));
|
|
108
101
|
pointCount = statusRes.collection?.pointCount ?? 0;
|
|
109
102
|
if (Array.isArray(rulesRes.result)) {
|
|
110
103
|
for (const rule of rulesRes.result) {
|
|
@@ -128,11 +121,36 @@ async function generateWatcherMenu(apiUrl) {
|
|
|
128
121
|
}
|
|
129
122
|
}
|
|
130
123
|
catch {
|
|
131
|
-
|
|
124
|
+
let qdrantStatus = '*Unknown*';
|
|
125
|
+
try {
|
|
126
|
+
// Assuming Qdrant runs locally on the default port
|
|
127
|
+
const res = await fetch('http://127.0.0.1:6333/healthz', {
|
|
128
|
+
signal: AbortSignal.timeout(1000),
|
|
129
|
+
});
|
|
130
|
+
qdrantStatus = res.ok ? 'Running' : 'Error';
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
qdrantStatus = 'Down / Unreachable';
|
|
134
|
+
}
|
|
135
|
+
return `> **ACTION REQUIRED: Watcher service is unreachable.**
|
|
136
|
+
> The jeeves-watcher service API at ${apiUrl} is down or not installed.
|
|
137
|
+
> **Diagnostic Info:**
|
|
138
|
+
> - Watcher API (${apiUrl}): Unreachable
|
|
139
|
+
> - Qdrant (http://127.0.0.1:6333): ${qdrantStatus}
|
|
140
|
+
>
|
|
141
|
+
> **DO NOT** ignore this. Read the \`jeeves-watcher\` skill's **Bootstrap** section immediately and walk the user through the setup process. Use the diagnostic info above to determine if Qdrant needs installing/starting, or if only the watcher service needs attention.`;
|
|
142
|
+
}
|
|
143
|
+
if (pointCount === 0) {
|
|
144
|
+
return `> **ACTION REQUIRED: Watcher service is empty.**
|
|
145
|
+
> The jeeves-watcher service is running at ${apiUrl} but has no indexed data.
|
|
146
|
+
> The service has either just been installed (no configuration/data), or indexing hasn't run.
|
|
147
|
+
>
|
|
148
|
+
> **DO NOT** ignore this. Read the \`jeeves-watcher\` skill's **Bootstrap** section immediately and walk the user through the setup process to configure and reindex the workspace.`;
|
|
132
149
|
}
|
|
133
150
|
const lines = [
|
|
134
151
|
`This environment includes a semantic search index (\`watcher_search\`) covering ${pointCount.toLocaleString()} document chunks.`,
|
|
135
152
|
'**Escalation Rule:** Use `memory_search` for personal operational notes, decisions, and rules. Escalate to `watcher_search` when memory is thin, or when searching the broader archive (tickets, docs, code). ALWAYS use `watcher_search` BEFORE filesystem commands (exec, grep) when looking for information that matches the indexed categories below.',
|
|
153
|
+
'**Scan-first rule:** When a task involves structural queries (file enumeration, staleness checks, domain listing, counts), use `watcher_scan` instead of `watcher_search`. Scan does NOT use embeddings and does NOT accept a query string.',
|
|
136
154
|
'**Search-first rule:** When a task involves finding, reading, or modifying files in indexed paths, run `watcher_search` FIRST — even if you already know the file path. Search surfaces related files you may not have considered and catches stale artifacts. Direct filesystem access is for acting on search results, not bypassing them.',
|
|
137
155
|
'',
|
|
138
156
|
'### Score Interpretation:',
|
|
@@ -285,7 +303,7 @@ function registerApiTool(api, baseUrl, config) {
|
|
|
285
303
|
const url = `${baseUrl}${endpoint}`;
|
|
286
304
|
const data = body !== undefined
|
|
287
305
|
? await postJson(url, body)
|
|
288
|
-
: await fetchJson
|
|
306
|
+
: await fetchJson(url);
|
|
289
307
|
return ok(data);
|
|
290
308
|
}
|
|
291
309
|
catch (error) {
|
|
@@ -303,7 +321,7 @@ function pickDefined(params, keys) {
|
|
|
303
321
|
}
|
|
304
322
|
return body;
|
|
305
323
|
}
|
|
306
|
-
/** Register all
|
|
324
|
+
/** Register all 9 watcher_* tools with the OpenClaw plugin API. */
|
|
307
325
|
function registerWatcherTools(api, baseUrl) {
|
|
308
326
|
const tools = [
|
|
309
327
|
{
|
|
@@ -435,6 +453,47 @@ function registerWatcherTools(api, baseUrl) {
|
|
|
435
453
|
{ scope: params.scope ?? 'rules' },
|
|
436
454
|
],
|
|
437
455
|
},
|
|
456
|
+
{
|
|
457
|
+
name: 'watcher_scan',
|
|
458
|
+
description: 'Filter-only point query without vector search. Returns metadata for points matching a Qdrant filter. Use for structural queries: file enumeration, staleness checks, delta computation. Use watcher_search for semantic/similarity queries.',
|
|
459
|
+
parameters: {
|
|
460
|
+
type: 'object',
|
|
461
|
+
required: ['filter'],
|
|
462
|
+
properties: {
|
|
463
|
+
filter: {
|
|
464
|
+
type: 'object',
|
|
465
|
+
description: 'Qdrant filter object (required).',
|
|
466
|
+
},
|
|
467
|
+
limit: {
|
|
468
|
+
type: 'number',
|
|
469
|
+
description: 'Page size (default 100, max 1000).',
|
|
470
|
+
},
|
|
471
|
+
cursor: {
|
|
472
|
+
type: 'string',
|
|
473
|
+
description: 'Opaque cursor from previous response for pagination.',
|
|
474
|
+
},
|
|
475
|
+
fields: {
|
|
476
|
+
type: 'array',
|
|
477
|
+
items: { type: 'string' },
|
|
478
|
+
description: 'Payload fields to return (projection).',
|
|
479
|
+
},
|
|
480
|
+
countOnly: {
|
|
481
|
+
type: 'boolean',
|
|
482
|
+
description: 'If true, return { count } instead of points.',
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
buildRequest: (params) => {
|
|
487
|
+
const body = pickDefined(params, [
|
|
488
|
+
'filter',
|
|
489
|
+
'limit',
|
|
490
|
+
'cursor',
|
|
491
|
+
'fields',
|
|
492
|
+
'countOnly',
|
|
493
|
+
]);
|
|
494
|
+
return ['/scan', body];
|
|
495
|
+
},
|
|
496
|
+
},
|
|
438
497
|
{
|
|
439
498
|
name: 'watcher_issues',
|
|
440
499
|
description: 'Get runtime embedding failures. Shows files that failed processing and why.',
|
|
@@ -45,6 +45,7 @@ curl -X POST http://127.0.0.1:<PORT>/config/query \
|
|
|
45
45
|
| `/config/apply` | POST | Apply config changes |
|
|
46
46
|
| `/config-reindex` | POST | Trigger reindex |
|
|
47
47
|
| `/metadata` | POST | Enrich document metadata |
|
|
48
|
+
| `/scan` | POST | Filter-only point query (no embeddings) |
|
|
48
49
|
| `/issues` | GET | Runtime embedding failures |
|
|
49
50
|
| `/rules/register` | POST | Register virtual inference rules |
|
|
50
51
|
| `/rules/unregister` | DELETE | Remove virtual rules by source |
|
|
@@ -78,7 +79,11 @@ You have access to a **semantic archive** of your human's working world. Documen
|
|
|
78
79
|
npx @karmaniverous/jeeves-watcher-openclaw install
|
|
79
80
|
```
|
|
80
81
|
|
|
81
|
-
This copies the plugin to OpenClaw's extensions directory and patches `openclaw.json` to register it.
|
|
82
|
+
This copies the plugin to OpenClaw's extensions directory and patches `openclaw.json` to register it.
|
|
83
|
+
|
|
84
|
+
**Important:** Add `"jeeves-watcher-openclaw"` to the `tools.allow` array in `openclaw.json` so the agent can use the plugin's tools.
|
|
85
|
+
|
|
86
|
+
Restart the gateway to load the plugin.
|
|
82
87
|
|
|
83
88
|
To remove:
|
|
84
89
|
```
|
|
@@ -371,9 +376,83 @@ Trigger a reindex.
|
|
|
371
376
|
|
|
372
377
|
Rules scope re-applies inference rules without re-embedding (lightweight). Full scope re-processes all files.
|
|
373
378
|
|
|
379
|
+
|
|
380
|
+
### `watcher_scan`
|
|
381
|
+
Filter-only point query without vector search. Use for structural queries where the question has no semantic dimension.
|
|
382
|
+
- `filter` (object, required) — Qdrant filter object. Required to prevent accidental full-collection scans.
|
|
383
|
+
- `limit` (number, optional) — page size, default 100, max 1000
|
|
384
|
+
- `cursor` (string, optional) — opaque cursor from previous response for pagination
|
|
385
|
+
- `fields` (string[], optional) — payload fields to return (projection)
|
|
386
|
+
- `countOnly` (boolean, optional) — if true, return `{ count }` instead of points
|
|
387
|
+
|
|
388
|
+
**Response (normal):**
|
|
389
|
+
```json
|
|
390
|
+
{
|
|
391
|
+
"points": [{ "id": "uuid", "payload": { ... } }],
|
|
392
|
+
"cursor": "opaque-string-or-null"
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Response (countOnly):**
|
|
397
|
+
```json
|
|
398
|
+
{ "count": 1234 }
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**Key differences from `watcher_search`:**
|
|
402
|
+
- No `query` parameter — does NOT use embeddings
|
|
403
|
+
- No `score` field — results are unranked filter matches
|
|
404
|
+
- Cursor-based pagination (not offset-based)
|
|
405
|
+
- Zero cost per call beyond Qdrant's filtered scroll
|
|
406
|
+
|
|
407
|
+
**Pagination pattern:**
|
|
408
|
+
```
|
|
409
|
+
let cursor = undefined;
|
|
410
|
+
do {
|
|
411
|
+
const result = await watcher_scan({ filter, limit: 100, cursor });
|
|
412
|
+
// process result.points
|
|
413
|
+
cursor = result.cursor;
|
|
414
|
+
} while (cursor);
|
|
415
|
+
```
|
|
416
|
+
|
|
374
417
|
### `watcher_issues`
|
|
375
418
|
Get runtime embedding failures. Returns `{ filePath: IssueRecord }` showing files that failed and why.
|
|
376
419
|
|
|
420
|
+
## Query Planning: Scan vs Search
|
|
421
|
+
|
|
422
|
+
**Decision rule:** If the query has no semantic/natural-language dimension, use `watcher_scan`. If you need meaning-based similarity, use `watcher_search`.
|
|
423
|
+
|
|
424
|
+
| Use `watcher_scan` | Use `watcher_search` |
|
|
425
|
+
|---------------------|----------------------|
|
|
426
|
+
| "List all files in domain X" | "Find documents about authentication" |
|
|
427
|
+
| "Files modified after timestamp T" | "What discusses rate limiting?" |
|
|
428
|
+
| "Enumerate paths under prefix P" | "Prior conversations about deployment" |
|
|
429
|
+
| "Count files matching a condition" | "Related tickets to this issue" |
|
|
430
|
+
| "Staleness detection / delta computation" | "What happened in last week's meetings?" |
|
|
431
|
+
|
|
432
|
+
**Scan-specific filter examples:**
|
|
433
|
+
|
|
434
|
+
**Domain enumeration:**
|
|
435
|
+
```json
|
|
436
|
+
{ "must": [{ "key": "domains", "match": { "value": "email" } }] }
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Modified after timestamp:**
|
|
440
|
+
```json
|
|
441
|
+
{ "must": [{ "key": "modified_at", "range": { "gte": 1772800000 } }] }
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Path prefix matching:**
|
|
445
|
+
```json
|
|
446
|
+
{ "must": [{ "key": "file_path", "match": { "text": "j:/domains/jira" } }] }
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**Count files in a domain (no point data transferred):**
|
|
450
|
+
```
|
|
451
|
+
watcher_scan: filter={"must":[{"key":"domains","match":{"value":"github"}}]}, countOnly=true
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
377
456
|
## Qdrant Filter Syntax
|
|
378
457
|
|
|
379
458
|
Filters use Qdrant's native JSON filter format, passed as the `filter` parameter to `watcher_search`.
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* Watcher tool registrations (watcher_* tools) for the OpenClaw plugin.
|
|
4
4
|
*/
|
|
5
5
|
import { type PluginApi } from './helpers.js';
|
|
6
|
-
/** Register all
|
|
6
|
+
/** Register all 9 watcher_* tools with the OpenClaw plugin API. */
|
|
7
7
|
export declare function registerWatcherTools(api: PluginApi, baseUrl: string): 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
|
+
"version": "0.6.0",
|
|
6
6
|
"skills": [
|
|
7
7
|
"dist/skills/jeeves-watcher"
|
|
8
8
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karmaniverous/jeeves-watcher-openclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"author": "Jason Williscroft",
|
|
5
5
|
"description": "OpenClaw plugin for jeeves-watcher — semantic search and metadata enrichment tools",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"hideCredit": true
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@dotenvx/dotenvx": "^1.
|
|
58
|
+
"@dotenvx/dotenvx": "^1.54.1",
|
|
59
59
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
60
60
|
"auto-changelog": "^2.5.0",
|
|
61
61
|
"cross-env": "^10.1.0",
|