@karmaniverous/jeeves-watcher-openclaw 0.1.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 +46 -0
- package/dist/index.js +267 -0
- package/dist/rollup.config.d.ts +7 -0
- package/dist/scripts/build-skills.d.ts +5 -0
- package/dist/skills/jeeves-watcher/SKILL.md +428 -0
- package/dist/skills/jeeves-watcher-admin/SKILL.md +200 -0
- package/dist/src/helpers.d.ts +38 -0
- package/dist/src/index.d.ts +7 -0
- package/openclaw.plugin.json +24 -0
- package/package.json +105 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# @karmaniverous/jeeves-watcher-openclaw
|
|
2
|
+
|
|
3
|
+
[OpenClaw](https://openclaw.ai) plugin for [jeeves-watcher](https://www.npmjs.com/package/@karmaniverous/jeeves-watcher) — semantic search and metadata enrichment tools for your AI agent.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
A running [jeeves-watcher](https://www.npmjs.com/package/@karmaniverous/jeeves-watcher) service with its REST API accessible.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
openclaw plugins install @karmaniverous/jeeves-watcher-openclaw
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Configuration
|
|
16
|
+
|
|
17
|
+
Set the `apiUrl` in the plugin configuration to point at your jeeves-watcher service:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"apiUrl": "http://localhost:3000"
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Tools
|
|
26
|
+
|
|
27
|
+
| Tool | Description |
|
|
28
|
+
|------|-------------|
|
|
29
|
+
| `watcher_status` | Service health, uptime, and collection stats |
|
|
30
|
+
| `watcher_search` | Semantic search across indexed documents |
|
|
31
|
+
| `watcher_enrich` | Enrich document metadata via rules engine |
|
|
32
|
+
| `watcher_query` | Raw Qdrant query with filters |
|
|
33
|
+
| `watcher_validate` | Validate a watcher configuration |
|
|
34
|
+
| `watcher_config_apply` | Apply a new configuration |
|
|
35
|
+
| `watcher_reindex` | Trigger a full reindex |
|
|
36
|
+
| `watcher_issues` | List indexing issues and errors |
|
|
37
|
+
|
|
38
|
+
## Documentation
|
|
39
|
+
|
|
40
|
+
Full docs for the jeeves-watcher service and this plugin:
|
|
41
|
+
|
|
42
|
+
**[docs.karmanivero.us/jeeves-watcher](https://docs.karmanivero.us/jeeves-watcher)**
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
BSD-3-Clause
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugin/helpers
|
|
3
|
+
* Shared types and utility functions for the OpenClaw plugin tool registrations.
|
|
4
|
+
*/
|
|
5
|
+
const DEFAULT_API_URL = 'http://127.0.0.1:3458';
|
|
6
|
+
/** Resolve the watcher API base URL from plugin config. */
|
|
7
|
+
function getApiUrl(api) {
|
|
8
|
+
const url = api.config?.plugins?.entries?.['jeeves-watcher']?.config?.apiUrl;
|
|
9
|
+
return typeof url === 'string' ? url : DEFAULT_API_URL;
|
|
10
|
+
}
|
|
11
|
+
/** Format a successful tool result. */
|
|
12
|
+
function ok(data) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Format an error tool result. */
|
|
18
|
+
function fail(error) {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
22
|
+
isError: true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Fetch JSON from a URL, throwing on non-OK responses. */
|
|
26
|
+
async function fetchJson(url, init) {
|
|
27
|
+
const res = await fetch(url, init);
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
throw new Error(`HTTP ${String(res.status)}: ${await res.text()}`);
|
|
30
|
+
}
|
|
31
|
+
return res.json();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @module plugin
|
|
36
|
+
* OpenClaw plugin entry point. Registers all jeeves-watcher tools.
|
|
37
|
+
*/
|
|
38
|
+
/** Register all jeeves-watcher tools with the OpenClaw plugin API. */
|
|
39
|
+
function register(api) {
|
|
40
|
+
const baseUrl = getApiUrl(api);
|
|
41
|
+
api.registerTool({
|
|
42
|
+
name: 'watcher_status',
|
|
43
|
+
description: 'Get jeeves-watcher service health, uptime, and collection statistics.',
|
|
44
|
+
parameters: { type: 'object', properties: {} },
|
|
45
|
+
execute: async () => {
|
|
46
|
+
try {
|
|
47
|
+
return ok(await fetchJson(`${baseUrl}/status`));
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return fail(error);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
}, { optional: true });
|
|
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).',
|
|
65
|
+
},
|
|
66
|
+
offset: {
|
|
67
|
+
type: 'number',
|
|
68
|
+
description: 'Number of results to skip for pagination.',
|
|
69
|
+
},
|
|
70
|
+
filter: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
description: 'Qdrant filter object.',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
execute: async (_id, params) => {
|
|
77
|
+
try {
|
|
78
|
+
const body = { query: params.query };
|
|
79
|
+
if (params.limit !== undefined)
|
|
80
|
+
body.limit = params.limit;
|
|
81
|
+
if (params.offset !== undefined)
|
|
82
|
+
body.offset = params.offset;
|
|
83
|
+
if (params.filter !== undefined)
|
|
84
|
+
body.filter = params.filter;
|
|
85
|
+
return ok(await fetchJson(`${baseUrl}/search`, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: { 'Content-Type': 'application/json' },
|
|
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.',
|
|
106
|
+
},
|
|
107
|
+
metadata: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
description: 'Key-value metadata to set on the document.',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
execute: async (_id, params) => {
|
|
114
|
+
try {
|
|
115
|
+
return ok(await fetchJson(`${baseUrl}/metadata`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: { 'Content-Type': 'application/json' },
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
path: params.path,
|
|
120
|
+
metadata: params.metadata,
|
|
121
|
+
}),
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return fail(error);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
}, { optional: true });
|
|
129
|
+
api.registerTool({
|
|
130
|
+
name: 'watcher_query',
|
|
131
|
+
description: 'Query the merged virtual document via JSONPath.',
|
|
132
|
+
parameters: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
required: ['path'],
|
|
135
|
+
properties: {
|
|
136
|
+
path: {
|
|
137
|
+
type: 'string',
|
|
138
|
+
description: 'JSONPath expression.',
|
|
139
|
+
},
|
|
140
|
+
resolve: {
|
|
141
|
+
type: 'array',
|
|
142
|
+
items: { type: 'string', enum: ['files', 'globals'] },
|
|
143
|
+
description: 'Resolution scopes to include (e.g., ["files"], ["globals"], or both).',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
execute: async (_id, params) => {
|
|
148
|
+
try {
|
|
149
|
+
const body = { path: params.path };
|
|
150
|
+
if (params.resolve !== undefined)
|
|
151
|
+
body.resolve = params.resolve;
|
|
152
|
+
return ok(await fetchJson(`${baseUrl}/config/query`, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: { 'Content-Type': 'application/json' },
|
|
155
|
+
body: JSON.stringify(body),
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
return fail(error);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
}, { optional: true });
|
|
163
|
+
api.registerTool({
|
|
164
|
+
name: 'watcher_validate',
|
|
165
|
+
description: 'Validate a candidate config (or current config if omitted). Optionally test file paths against the config to preview rule matching and metadata output.',
|
|
166
|
+
parameters: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
config: {
|
|
170
|
+
type: 'object',
|
|
171
|
+
description: 'Candidate config (partial or full). Omit to validate current config.',
|
|
172
|
+
},
|
|
173
|
+
testPaths: {
|
|
174
|
+
type: 'array',
|
|
175
|
+
items: { type: 'string' },
|
|
176
|
+
description: 'File paths to test against the config for dry-run preview.',
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
execute: async (_id, params) => {
|
|
181
|
+
try {
|
|
182
|
+
const body = {};
|
|
183
|
+
if (params.config !== undefined)
|
|
184
|
+
body.config = params.config;
|
|
185
|
+
if (params.testPaths !== undefined)
|
|
186
|
+
body.testPaths = params.testPaths;
|
|
187
|
+
return ok(await fetchJson(`${baseUrl}/config/validate`, {
|
|
188
|
+
method: 'POST',
|
|
189
|
+
headers: { 'Content-Type': 'application/json' },
|
|
190
|
+
body: JSON.stringify(body),
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
return fail(error);
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
}, { optional: true });
|
|
198
|
+
api.registerTool({
|
|
199
|
+
name: 'watcher_config_apply',
|
|
200
|
+
description: 'Apply a full or partial config. Validates, writes to disk, and triggers configured reindex behavior.',
|
|
201
|
+
parameters: {
|
|
202
|
+
type: 'object',
|
|
203
|
+
required: ['config'],
|
|
204
|
+
properties: {
|
|
205
|
+
config: {
|
|
206
|
+
type: 'object',
|
|
207
|
+
description: 'Full or partial config to apply.',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
execute: async (_id, params) => {
|
|
212
|
+
try {
|
|
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 });
|
|
224
|
+
api.registerTool({
|
|
225
|
+
name: 'watcher_reindex',
|
|
226
|
+
description: 'Trigger a reindex of the watched files.',
|
|
227
|
+
parameters: {
|
|
228
|
+
type: 'object',
|
|
229
|
+
properties: {
|
|
230
|
+
scope: {
|
|
231
|
+
type: 'string',
|
|
232
|
+
enum: ['rules', 'full'],
|
|
233
|
+
description: 'Reindex scope: "rules" (default) re-applies inference rules; "full" re-embeds everything.',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
execute: async (_id, params) => {
|
|
238
|
+
try {
|
|
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 });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export { register as default };
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jeeves-watcher
|
|
3
|
+
description: >
|
|
4
|
+
Semantic search across a structured document archive. Use when you need to
|
|
5
|
+
recall prior context, find documents, answer questions that require searching
|
|
6
|
+
across domains (email, Slack, Jira, codebase, meetings, projects), or enrich
|
|
7
|
+
document metadata.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# jeeves-watcher — Search & Discovery
|
|
11
|
+
|
|
12
|
+
## Theory of Operation
|
|
13
|
+
|
|
14
|
+
You have access to a **semantic archive** of your human's working world: email, Slack messages, Jira tickets, code repositories, meeting notes, project documents, and more. Everything is indexed, chunked, embedded, and searchable. This is your long-term memory for anything beyond the current conversation.
|
|
15
|
+
|
|
16
|
+
**When to reach for the watcher:**
|
|
17
|
+
|
|
18
|
+
- **Someone asks about something that happened.** A meeting, a decision, a conversation, a ticket. You weren't there, but the archive was. Search it.
|
|
19
|
+
- **You need context you don't have.** Before asking the human "what's the status of X?", search for X. The answer is probably already indexed.
|
|
20
|
+
- **You're working on a project and need background.** Architecture decisions, prior discussions, related tickets, relevant code. Search by topic, filter by domain.
|
|
21
|
+
- **You need to verify something.** Don't guess from stale memory. Search for the current state.
|
|
22
|
+
- **You want to connect dots across domains.** The same topic might appear in a Jira ticket, a Slack thread, an email, and a code commit. A single semantic search surfaces all of them.
|
|
23
|
+
|
|
24
|
+
**When NOT to use it:**
|
|
25
|
+
|
|
26
|
+
- You already have the information in the current conversation
|
|
27
|
+
- The question is about general knowledge, not the human's specific context
|
|
28
|
+
- The watcher is unreachable (fall back to filesystem browsing)
|
|
29
|
+
|
|
30
|
+
**How it works, conceptually:**
|
|
31
|
+
|
|
32
|
+
The watcher monitors directories on the filesystem. When files change, it extracts text, applies **inference rules** (config-driven pattern matching) to derive structured metadata, and embeds everything into a vector store. Each inference rule defines a record type: what files it matches, what metadata schema applies, how to extract fields.
|
|
33
|
+
|
|
34
|
+
You don't need to know the rules in advance. The config is introspectable at runtime. Orient once per session, then search with confidence.
|
|
35
|
+
|
|
36
|
+
**Key mental model:** Think of it as a search engine scoped to your human's data, with structured metadata on every result. Plain semantic search works. Adding metadata filters makes it precise.
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
1. **Orient yourself** (once per session) — learn the deployment's organizational strategy and available record types
|
|
41
|
+
2. **Search** — semantic search with optional metadata filters
|
|
42
|
+
3. **Read source** — retrieve full file content for complete context
|
|
43
|
+
|
|
44
|
+
## Tools
|
|
45
|
+
|
|
46
|
+
### `watcher_search`
|
|
47
|
+
Semantic search over indexed documents.
|
|
48
|
+
- `query` (string, required) — natural language search query
|
|
49
|
+
- `limit` (number, optional) — max results, default 10
|
|
50
|
+
- `offset` (number, optional) — skip N results for pagination
|
|
51
|
+
- `filter` (object, optional) — Qdrant filter for metadata filtering
|
|
52
|
+
|
|
53
|
+
### `watcher_enrich`
|
|
54
|
+
Set or update metadata on a document.
|
|
55
|
+
- `path` (string, required) — file path of the document
|
|
56
|
+
- `metadata` (object, required) — key-value metadata to merge
|
|
57
|
+
|
|
58
|
+
### `watcher_status`
|
|
59
|
+
Service health check. Returns uptime, collection stats, reindex status.
|
|
60
|
+
|
|
61
|
+
### `watcher_query`
|
|
62
|
+
Query the merged virtual document via JSONPath.
|
|
63
|
+
- `path` (string, required) — JSONPath expression
|
|
64
|
+
- `resolve` (string[], optional) — `["files"]`, `["globals"]`, or `["files","globals"]`
|
|
65
|
+
|
|
66
|
+
## Qdrant Filter Syntax
|
|
67
|
+
|
|
68
|
+
Filters use Qdrant's native JSON filter format, passed as the `filter` parameter to `watcher_search`.
|
|
69
|
+
|
|
70
|
+
### Basic Patterns
|
|
71
|
+
|
|
72
|
+
**Match exact value:**
|
|
73
|
+
```json
|
|
74
|
+
{ "must": [{ "key": "domain", "match": { "value": "email" } }] }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Match text (full-text search within field):**
|
|
78
|
+
```json
|
|
79
|
+
{ "must": [{ "key": "chunk_text", "match": { "text": "authentication" } }] }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Combine conditions (AND):**
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"must": [
|
|
86
|
+
{ "key": "domain", "match": { "value": "jira" } },
|
|
87
|
+
{ "key": "status", "match": { "value": "In Progress" } }
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Exclude (NOT):**
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"must_not": [{ "key": "domain", "match": { "value": "repos" } }]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Any of (OR):**
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"should": [
|
|
103
|
+
{ "key": "domain", "match": { "value": "email" } },
|
|
104
|
+
{ "key": "domain", "match": { "value": "slack" } }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Nested (combine AND + NOT):**
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"must": [{ "key": "domain", "match": { "value": "jira" } }],
|
|
113
|
+
"must_not": [{ "key": "status", "match": { "value": "Done" } }]
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Key Differences
|
|
118
|
+
- `match.value` — exact match (case-sensitive, for keyword fields like `domain`, `status`)
|
|
119
|
+
- `match.text` — full-text match (for text fields like `chunk_text`)
|
|
120
|
+
|
|
121
|
+
## Search Result Shape
|
|
122
|
+
|
|
123
|
+
Each result from `watcher_search` contains:
|
|
124
|
+
|
|
125
|
+
| Field | Type | Description |
|
|
126
|
+
|-------|------|-------------|
|
|
127
|
+
| `id` | string | Qdrant point ID |
|
|
128
|
+
| `score` | number | Similarity score (0-1, higher = more relevant) |
|
|
129
|
+
| `payload.file_path` | string | Source file path |
|
|
130
|
+
| `payload.chunk_text` | string | The matched text chunk |
|
|
131
|
+
| `payload.chunk_index` | number | Chunk position within the file |
|
|
132
|
+
| `payload.total_chunks` | number | Total chunks for this file |
|
|
133
|
+
| `payload.content_hash` | string | Hash of the full document content |
|
|
134
|
+
| `payload.matched_rules` | string[] | Names of inference rules that matched |
|
|
135
|
+
|
|
136
|
+
Additional metadata fields depend on the deployment's inference rules (e.g., `domain`, `status`, `author`). Use `watcher_query` to discover available fields.
|
|
137
|
+
|
|
138
|
+
## JSONPath Patterns for Schema Discovery
|
|
139
|
+
|
|
140
|
+
Use `watcher_query` to explore the merged virtual document. Common patterns:
|
|
141
|
+
|
|
142
|
+
### Orientation
|
|
143
|
+
```
|
|
144
|
+
$.inferenceRules[*].['name','description'] — List all rules with descriptions
|
|
145
|
+
$.search.scoreThresholds — Score interpretation thresholds
|
|
146
|
+
$.slots — Named filter patterns (e.g., memory)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Schema Discovery
|
|
150
|
+
```
|
|
151
|
+
$.inferenceRules[?(@.name=='jira-issue')] — Full rule details
|
|
152
|
+
$.inferenceRules[?(@.name=='jira-issue')].values — Distinct values for a rule
|
|
153
|
+
$.inferenceRules[?(@.name=='jira-issue')].values.status — Values for a specific field
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Helper Enumeration
|
|
157
|
+
```
|
|
158
|
+
$.mapHelpers — All JsonMap helper namespaces
|
|
159
|
+
$.mapHelpers.slack.exports — Exports from the 'slack' helper
|
|
160
|
+
$.templateHelpers — All Handlebars helper namespaces
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Issues
|
|
164
|
+
```
|
|
165
|
+
$.issues — All runtime embedding failures
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Full Config Introspection
|
|
169
|
+
```
|
|
170
|
+
$.schemas — Global named schemas
|
|
171
|
+
$.maps — Named JsonMap transforms
|
|
172
|
+
$.templates — Named Handlebars templates
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Orientation Pattern (Once Per Session)
|
|
178
|
+
|
|
179
|
+
Query the deployment's organizational context and available record types. This information is stable within a session; query once and rely on results for the remainder.
|
|
180
|
+
|
|
181
|
+
**Efficient pattern (two calls):**
|
|
182
|
+
|
|
183
|
+
1. **Top-level context:**
|
|
184
|
+
```
|
|
185
|
+
watcher_query: path="$.['description','search']"
|
|
186
|
+
```
|
|
187
|
+
Returns:
|
|
188
|
+
- `description` — organizational strategy (e.g., how domains are structured, what partitioning means)
|
|
189
|
+
- `search.scoreThresholds` — score interpretation boundaries (strong, relevant, noise)
|
|
190
|
+
|
|
191
|
+
2. **Available record types:**
|
|
192
|
+
```
|
|
193
|
+
watcher_query: path="$.inferenceRules[*].['name','description']"
|
|
194
|
+
```
|
|
195
|
+
Returns list of inference rules with their names and descriptions.
|
|
196
|
+
|
|
197
|
+
**Example result:**
|
|
198
|
+
```json
|
|
199
|
+
[
|
|
200
|
+
{ "name": "email-archive", "description": "Email archive messages" },
|
|
201
|
+
{ "name": "slack-message", "description": "Slack channel messages with channel and author metadata" },
|
|
202
|
+
{ "name": "jira-issue", "description": "Jira issue metadata extracted from issue JSON exports" }
|
|
203
|
+
]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The top-level `description` explains this deployment's organizational strategy. Each rule's `description` explains what that specific record type represents. Both levels are useful: one orients, the other enumerates.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## `resolve` Usage Guidance
|
|
211
|
+
|
|
212
|
+
The `resolve` parameter controls which reference layers are expanded in `watcher_query`:
|
|
213
|
+
|
|
214
|
+
- **No `resolve` (default):** Raw config structure with references intact (lightweight)
|
|
215
|
+
- **`resolve: ["files"]`:** Resolve file path references to their contents (e.g., `"schemas/base.json"` → the JSON Schema object)
|
|
216
|
+
- **`resolve: ["globals"]`:** Resolve named schema references (e.g., `"base"` in a rule's schema array → the global schema object)
|
|
217
|
+
- **`resolve: ["files","globals"]`:** Fully inlined, everything expanded
|
|
218
|
+
|
|
219
|
+
**When to use:**
|
|
220
|
+
- **Orientation:** No resolve (just names and descriptions, lightweight)
|
|
221
|
+
- **Query planning:** `resolve: ["files","globals"]` (need complete merged schemas for filter construction)
|
|
222
|
+
- **Browsing global schemas:** `resolve: ["files"]` (see schema contents but keep named references visible for DRY structure understanding)
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Query Planning (Per Search Task)
|
|
227
|
+
|
|
228
|
+
Identify relevant rule(s) from the orientation model, then retrieve their schemas:
|
|
229
|
+
|
|
230
|
+
**Retrieve complete schema for a rule:**
|
|
231
|
+
```
|
|
232
|
+
watcher_query: path="$.inferenceRules[?(@.name=='jira-issue')].schema"
|
|
233
|
+
resolve=["files","globals"]
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Returns the fully merged schema with properties, types, `set` provenance, `uiHint`, `enum`, etc.
|
|
237
|
+
|
|
238
|
+
**For select/multiselect fields without `enum` in schema:**
|
|
239
|
+
```
|
|
240
|
+
watcher_query: path="$.inferenceRules[?(@.name=='jira-issue')].values.status"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Retrieves valid filter values from the runtime values index (distinct values accumulated during embedding).
|
|
244
|
+
|
|
245
|
+
**When search results span multiple rules** (indicated by `matched_rules` on results): query each unique rule's schema separately and merge mentally. Most result sets share the same rule combination, so this is typically one or two queries, not one per result.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## uiHint → Qdrant Filter Mapping
|
|
250
|
+
|
|
251
|
+
Use `uiHint` to determine filter construction strategy. **This table is explicit, not intuited:**
|
|
252
|
+
|
|
253
|
+
| `uiHint` | Qdrant filter | Notes |
|
|
254
|
+
|----------|--------------|-------|
|
|
255
|
+
| `text` | `{ "key": "<field>", "match": { "text": "<value>" } }` | Substring/keyword match |
|
|
256
|
+
| `select` | `{ "key": "<field>", "match": { "value": "<enum_value>" } }` | Exact match; use `enum` values from schema or runtime values index |
|
|
257
|
+
| `multiselect` | `{ "key": "<field>", "match": { "value": "<enum_value>" } }` | Any-element match on array field; use `enum` or runtime values index |
|
|
258
|
+
| `date` | `{ "key": "<field>", "range": { "gte": <unix_ts>, "lt": <unix_ts> } }` | Either bound optional for open-ended ranges (e.g., "after January" → `gte` only) |
|
|
259
|
+
| `number` | `{ "key": "<field>", "range": { "gte": <n>, "lte": <n> } }` | Either bound optional for open-ended ranges |
|
|
260
|
+
| `check` | `{ "key": "<field>", "match": { "value": true } }` | Boolean match |
|
|
261
|
+
| *(absent)* | Do not use in filters | Internal bookkeeping field, not intended for search |
|
|
262
|
+
|
|
263
|
+
**Fallback:** If a `select`/`multiselect` field has neither `enum` in schema nor values in the index, treat it as `text` (substring match instead of exact match).
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Qdrant Filter Combinators
|
|
268
|
+
|
|
269
|
+
Compose individual field conditions into complex queries using three combinators:
|
|
270
|
+
|
|
271
|
+
| Combinator | Semantics | Use case |
|
|
272
|
+
|-----------|-----------|----------|
|
|
273
|
+
| `must` | AND — all conditions required | Intersecting constraints (domain + date range + assignee) |
|
|
274
|
+
| `should` | OR — at least one must match | Alternative values, fuzzy criteria ("assigned to X or Y") |
|
|
275
|
+
| `must_not` | Exclusion — any match triggers exclude | Filtering out noise (exclude Done, exclude codebase domain) |
|
|
276
|
+
|
|
277
|
+
**Combinators nest arbitrarily for complex boolean logic:**
|
|
278
|
+
```json
|
|
279
|
+
{
|
|
280
|
+
"must": [
|
|
281
|
+
{ "key": "domain", "match": { "value": "jira" } },
|
|
282
|
+
{ "key": "created", "range": { "gte": 1735689600 } }
|
|
283
|
+
],
|
|
284
|
+
"should": [
|
|
285
|
+
{ "key": "assignee", "match": { "value": "Jason Williscroft" } },
|
|
286
|
+
{ "key": "assignee", "match": { "value": null } }
|
|
287
|
+
],
|
|
288
|
+
"must_not": [
|
|
289
|
+
{ "key": "status", "match": { "value": "Done" } }
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
A consuming UI will necessarily compose simple single-field filters. The assistant can compose deeply complex queries combining multiple fields, nested boolean logic, and open-ended ranges to precisely target what it needs.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Search Execution
|
|
299
|
+
|
|
300
|
+
**Plain semantic search is valid and often sufficient.** Not every query needs metadata filters. When the user's question is broad or exploratory, a natural language query with no filter object is the right starting point. Add filters to narrow, not as a default.
|
|
301
|
+
|
|
302
|
+
**Result limit guidance:**
|
|
303
|
+
- Default: 10 results
|
|
304
|
+
- Broad discovery / exploratory: 20–30, apply score threshold cutoff from config
|
|
305
|
+
- Targeted retrieval with tight filters: 5
|
|
306
|
+
- Cross-domain sweep: 15–20, no domain filter, use score to separate signal from noise
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Search Result Shape
|
|
311
|
+
|
|
312
|
+
**Qdrant output (stable across all configs):**
|
|
313
|
+
```json
|
|
314
|
+
{
|
|
315
|
+
"id": "<point_id>",
|
|
316
|
+
"score": 0.82,
|
|
317
|
+
"payload": {
|
|
318
|
+
"file_path": "j:/domains/jira/VCN/issue/WEB-123.json",
|
|
319
|
+
"chunk_index": 0,
|
|
320
|
+
"total_chunks": 1,
|
|
321
|
+
"chunk_text": "...",
|
|
322
|
+
"content_hash": "...",
|
|
323
|
+
"matched_rules": ["jira-issue", "json-subject"],
|
|
324
|
+
...config-defined metadata fields...
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**System fields present on every result** (watcher-managed, not config-defined):
|
|
330
|
+
- `file_path` — source file path
|
|
331
|
+
- `chunk_index` / `total_chunks` — chunk position within document
|
|
332
|
+
- `chunk_text` — the embedded text content
|
|
333
|
+
- `content_hash` — content fingerprint for deduplication
|
|
334
|
+
- `matched_rules` — inference rules that produced this point's metadata
|
|
335
|
+
|
|
336
|
+
**All other payload fields are config-defined** (via inference rule schemas).
|
|
337
|
+
|
|
338
|
+
Refer to Qdrant documentation for the complete search response envelope.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Post-Processing Guidance
|
|
343
|
+
|
|
344
|
+
### Score Interpretation
|
|
345
|
+
Use `scoreThresholds` from config (queried during orientation). Values are deployment-specific, constrained to [-1, 1]:
|
|
346
|
+
- `strong` — minimum score for a strong match
|
|
347
|
+
- `relevant` — minimum score for relevance
|
|
348
|
+
- `noise` — maximum score below which results are noise
|
|
349
|
+
|
|
350
|
+
### Chunk Grouping
|
|
351
|
+
Multiple results with the same `file_path` are chunks of one document. Read the full file for complete context.
|
|
352
|
+
|
|
353
|
+
### Schema Lookup
|
|
354
|
+
Use `matched_rules` on results to look up applicable schemas for metadata interpretation:
|
|
355
|
+
```
|
|
356
|
+
watcher_query: path="$.inferenceRules[?(@.name=='jira-issue')].schema"
|
|
357
|
+
resolve=["files","globals"]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Full Context
|
|
361
|
+
Search gives you chunks; use `read` with `file_path` for the complete document.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Path Testing
|
|
366
|
+
|
|
367
|
+
When uncertain whether a file is indexed, use the path test endpoint:
|
|
368
|
+
```
|
|
369
|
+
watcher_query: path="$.inferenceRules[?(@.name=='<rule>')].match"
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Or check if a specific path would match:
|
|
373
|
+
- Returns matching rule names and watch scope status
|
|
374
|
+
- Empty `rules` array means no inference rules match
|
|
375
|
+
- `watched: false` means the path falls outside watch paths or is excluded by ignore patterns
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Diagnostics
|
|
380
|
+
|
|
381
|
+
Check the issues endpoint for failed embeddings:
|
|
382
|
+
```
|
|
383
|
+
watcher_query: path="$.issues"
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Issues are self-healing:** resolved on successful re-process. The issues file always represents the current set of unresolved problems: a live todo list.
|
|
387
|
+
|
|
388
|
+
**Issue types:**
|
|
389
|
+
- `type_collision` — multiple rules declare the same property with incompatible types (includes `property`, `rules[]`, `types[]`)
|
|
390
|
+
- `interpolation_error` — `set` template path doesn't resolve (includes `property`, `rule`)
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Enrichment
|
|
395
|
+
|
|
396
|
+
Use `watcher_enrich` to tag documents after analysis (e.g., `reviewed: true`, project labels).
|
|
397
|
+
|
|
398
|
+
**Metadata is validated against the file's matched rule schemas.** Validation errors return structured messages:
|
|
399
|
+
```json
|
|
400
|
+
{
|
|
401
|
+
"error": "Validation failed",
|
|
402
|
+
"details": [
|
|
403
|
+
{
|
|
404
|
+
"property": "priority",
|
|
405
|
+
"expected": "string",
|
|
406
|
+
"received": "number",
|
|
407
|
+
"rule": "jira-issue",
|
|
408
|
+
"message": "Property 'priority' is declared as string in jira-issue schema, received number"
|
|
409
|
+
}
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Error Handling
|
|
417
|
+
|
|
418
|
+
If the watcher is unreachable:
|
|
419
|
+
- Inform the user that semantic search is temporarily unavailable
|
|
420
|
+
- Fall back to direct `read` for known file paths
|
|
421
|
+
- Do not retry silently in a loop
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## References
|
|
426
|
+
|
|
427
|
+
- [JSONPath Plus documentation](https://www.npmjs.com/package/jsonpath-plus) for JSONPath syntax
|
|
428
|
+
- [Qdrant filtering documentation](https://qdrant.tech/documentation/concepts/filtering/) for advanced query patterns and search response format
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jeeves-watcher-admin
|
|
3
|
+
description: >
|
|
4
|
+
Instance management for a jeeves-watcher deployment. Use when you need to
|
|
5
|
+
author or validate config, trigger reindexing, diagnose embedding failures,
|
|
6
|
+
or manage helper registrations.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# jeeves-watcher — Instance Administration
|
|
10
|
+
|
|
11
|
+
## Tools
|
|
12
|
+
|
|
13
|
+
### `watcher_validate`
|
|
14
|
+
Validate config and optionally test file paths.
|
|
15
|
+
- `config` (object, optional) — candidate config (partial or full). Omit to validate current config.
|
|
16
|
+
- `testPaths` (string[], optional) — file paths to test against the config
|
|
17
|
+
|
|
18
|
+
Partial configs merge with current config by rule name. If `config` is omitted, tests against the running config.
|
|
19
|
+
|
|
20
|
+
### `watcher_config_apply`
|
|
21
|
+
Apply config changes atomically.
|
|
22
|
+
- `config` (object, required) — full or partial config to apply
|
|
23
|
+
|
|
24
|
+
Validates, writes to disk, and triggers configured reindex behavior. Returns validation errors if invalid.
|
|
25
|
+
|
|
26
|
+
### `watcher_reindex`
|
|
27
|
+
Trigger a reindex.
|
|
28
|
+
- `scope` (string, optional) — `"rules"` (default) or `"full"`
|
|
29
|
+
|
|
30
|
+
Rules scope re-applies inference rules without re-embedding (lightweight). Full scope re-processes all files.
|
|
31
|
+
|
|
32
|
+
### `watcher_issues`
|
|
33
|
+
Get runtime embedding failures. Returns `{ filePath: IssueRecord }` showing files that failed and why.
|
|
34
|
+
|
|
35
|
+
### `watcher_query`
|
|
36
|
+
Query config and runtime state via JSONPath (same tool as consumer skill).
|
|
37
|
+
|
|
38
|
+
### `watcher_status`
|
|
39
|
+
Service health check including reindex progress.
|
|
40
|
+
|
|
41
|
+
## Qdrant Filter Syntax
|
|
42
|
+
|
|
43
|
+
Filters use Qdrant's native JSON filter format, passed as the `filter` parameter to `watcher_search`.
|
|
44
|
+
|
|
45
|
+
### Basic Patterns
|
|
46
|
+
|
|
47
|
+
**Match exact value:**
|
|
48
|
+
```json
|
|
49
|
+
{ "must": [{ "key": "domain", "match": { "value": "email" } }] }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Match text (full-text search within field):**
|
|
53
|
+
```json
|
|
54
|
+
{ "must": [{ "key": "chunk_text", "match": { "text": "authentication" } }] }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Combine conditions (AND):**
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"must": [
|
|
61
|
+
{ "key": "domain", "match": { "value": "jira" } },
|
|
62
|
+
{ "key": "status", "match": { "value": "In Progress" } }
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Exclude (NOT):**
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"must_not": [{ "key": "domain", "match": { "value": "repos" } }]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Any of (OR):**
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"should": [
|
|
78
|
+
{ "key": "domain", "match": { "value": "email" } },
|
|
79
|
+
{ "key": "domain", "match": { "value": "slack" } }
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Nested (combine AND + NOT):**
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"must": [{ "key": "domain", "match": { "value": "jira" } }],
|
|
88
|
+
"must_not": [{ "key": "status", "match": { "value": "Done" } }]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Key Differences
|
|
93
|
+
- `match.value` — exact match (case-sensitive, for keyword fields like `domain`, `status`)
|
|
94
|
+
- `match.text` — full-text match (for text fields like `chunk_text`)
|
|
95
|
+
|
|
96
|
+
## Search Result Shape
|
|
97
|
+
|
|
98
|
+
Each result from `watcher_search` contains:
|
|
99
|
+
|
|
100
|
+
| Field | Type | Description |
|
|
101
|
+
|-------|------|-------------|
|
|
102
|
+
| `id` | string | Qdrant point ID |
|
|
103
|
+
| `score` | number | Similarity score (0-1, higher = more relevant) |
|
|
104
|
+
| `payload.file_path` | string | Source file path |
|
|
105
|
+
| `payload.chunk_text` | string | The matched text chunk |
|
|
106
|
+
| `payload.chunk_index` | number | Chunk position within the file |
|
|
107
|
+
| `payload.total_chunks` | number | Total chunks for this file |
|
|
108
|
+
| `payload.content_hash` | string | Hash of the full document content |
|
|
109
|
+
| `payload.matched_rules` | string[] | Names of inference rules that matched |
|
|
110
|
+
|
|
111
|
+
Additional metadata fields depend on the deployment's inference rules (e.g., `domain`, `status`, `author`). Use `watcher_query` to discover available fields.
|
|
112
|
+
|
|
113
|
+
## JSONPath Patterns for Schema Discovery
|
|
114
|
+
|
|
115
|
+
Use `watcher_query` to explore the merged virtual document. Common patterns:
|
|
116
|
+
|
|
117
|
+
### Orientation
|
|
118
|
+
```
|
|
119
|
+
$.inferenceRules[*].['name','description'] — List all rules with descriptions
|
|
120
|
+
$.search.scoreThresholds — Score interpretation thresholds
|
|
121
|
+
$.slots — Named filter patterns (e.g., memory)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Schema Discovery
|
|
125
|
+
```
|
|
126
|
+
$.inferenceRules[?(@.name=='jira-issue')] — Full rule details
|
|
127
|
+
$.inferenceRules[?(@.name=='jira-issue')].values — Distinct values for a rule
|
|
128
|
+
$.inferenceRules[?(@.name=='jira-issue')].values.status — Values for a specific field
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Helper Enumeration
|
|
132
|
+
```
|
|
133
|
+
$.mapHelpers — All JsonMap helper namespaces
|
|
134
|
+
$.mapHelpers.slack.exports — Exports from the 'slack' helper
|
|
135
|
+
$.templateHelpers — All Handlebars helper namespaces
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Issues
|
|
139
|
+
```
|
|
140
|
+
$.issues — All runtime embedding failures
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Full Config Introspection
|
|
144
|
+
```
|
|
145
|
+
$.schemas — Global named schemas
|
|
146
|
+
$.maps — Named JsonMap transforms
|
|
147
|
+
$.templates — Named Handlebars templates
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Config Authoring
|
|
151
|
+
|
|
152
|
+
### Rule Structure
|
|
153
|
+
Each inference rule has:
|
|
154
|
+
- `name` (required) — unique identifier
|
|
155
|
+
- `description` (optional) — human-readable purpose
|
|
156
|
+
- `match` — JSON Schema with picomatch glob for path matching
|
|
157
|
+
- `set` — metadata fields to set on match
|
|
158
|
+
- `map` (optional) — named JsonMap transform
|
|
159
|
+
- `template` (optional) — named Handlebars template
|
|
160
|
+
|
|
161
|
+
### Config Workflow
|
|
162
|
+
1. Edit config (or build partial config object)
|
|
163
|
+
2. Validate: `watcher_validate` with optional `testPaths` for dry-run preview
|
|
164
|
+
3. Apply: `watcher_config_apply` — validates, writes, triggers reindex
|
|
165
|
+
4. Monitor: `watcher_issues` for runtime embedding failures
|
|
166
|
+
|
|
167
|
+
### When to Reindex
|
|
168
|
+
- **Rules scope** (`"rules"`): Changed rule matching patterns, set expressions, schema mappings. No re-embedding needed.
|
|
169
|
+
- **Full scope** (`"full"`): Changed embedding config, added watch paths, broad schema restructuring. Re-embeds everything.
|
|
170
|
+
|
|
171
|
+
## Diagnostics
|
|
172
|
+
|
|
173
|
+
### Escalation Path
|
|
174
|
+
1. `watcher_status` — is the service healthy? Is a reindex running?
|
|
175
|
+
2. `watcher_issues` — what files are failing and why?
|
|
176
|
+
3. `watcher_query` with `$.issues` — same data via JSONPath
|
|
177
|
+
4. Check logs at the configured log path
|
|
178
|
+
|
|
179
|
+
### Error Categories
|
|
180
|
+
- `type_collision` — metadata field type mismatch during extraction
|
|
181
|
+
- `interpolation` — template/set expression failed to resolve
|
|
182
|
+
- `read_failure` — file couldn't be read (permissions, encoding)
|
|
183
|
+
- `embedding` — embedding API error
|
|
184
|
+
|
|
185
|
+
## Helper Management
|
|
186
|
+
|
|
187
|
+
Helpers use namespace prefixing: config key becomes prefix. A helper named `slack` exports `slack_extractParticipants`.
|
|
188
|
+
|
|
189
|
+
Enumerate loaded helpers:
|
|
190
|
+
```
|
|
191
|
+
$.mapHelpers — JsonMap helper namespaces with exports
|
|
192
|
+
$.templateHelpers — Handlebars helper namespaces with exports
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## CLI Fallbacks
|
|
196
|
+
|
|
197
|
+
If the watcher API is down:
|
|
198
|
+
- `jeeves-watcher status` — check if the service is running
|
|
199
|
+
- `jeeves-watcher validate` — validate config from CLI
|
|
200
|
+
- Restart via NSSM (Windows) or systemctl (Linux)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugin/helpers
|
|
3
|
+
* Shared types and utility functions for the OpenClaw plugin tool registrations.
|
|
4
|
+
*/
|
|
5
|
+
/** Minimal OpenClaw plugin API surface used for tool registration. */
|
|
6
|
+
export interface PluginApi {
|
|
7
|
+
config?: {
|
|
8
|
+
plugins?: {
|
|
9
|
+
entries?: Record<string, {
|
|
10
|
+
config?: Record<string, unknown>;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
registerTool(tool: {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
parameters: Record<string, unknown>;
|
|
18
|
+
execute: (id: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
19
|
+
}, options?: {
|
|
20
|
+
optional?: boolean;
|
|
21
|
+
}): void;
|
|
22
|
+
}
|
|
23
|
+
/** Result shape returned by each tool execution. */
|
|
24
|
+
export interface ToolResult {
|
|
25
|
+
content: Array<{
|
|
26
|
+
type: string;
|
|
27
|
+
text: string;
|
|
28
|
+
}>;
|
|
29
|
+
isError?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Resolve the watcher API base URL from plugin config. */
|
|
32
|
+
export declare function getApiUrl(api: PluginApi): string;
|
|
33
|
+
/** Format a successful tool result. */
|
|
34
|
+
export declare function ok(data: unknown): ToolResult;
|
|
35
|
+
/** Format an error tool result. */
|
|
36
|
+
export declare function fail(error: unknown): ToolResult;
|
|
37
|
+
/** Fetch JSON from a URL, throwing on non-OK responses. */
|
|
38
|
+
export declare function fetchJson(url: string, init?: RequestInit): Promise<unknown>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugin
|
|
3
|
+
* OpenClaw plugin entry point. Registers all jeeves-watcher tools.
|
|
4
|
+
*/
|
|
5
|
+
import type { PluginApi } from './helpers.js';
|
|
6
|
+
/** Register all jeeves-watcher tools with the OpenClaw plugin API. */
|
|
7
|
+
export default function register(api: PluginApi): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "jeeves-watcher",
|
|
3
|
+
"name": "Jeeves Watcher",
|
|
4
|
+
"description": "Semantic search and metadata enrichment via a jeeves-watcher instance.",
|
|
5
|
+
"version": "0.5.0",
|
|
6
|
+
"skills": ["dist/skills/jeeves-watcher", "dist/skills/jeeves-watcher-admin"],
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {
|
|
11
|
+
"apiUrl": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "jeeves-watcher API base URL",
|
|
14
|
+
"default": "http://127.0.0.1:3458"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"uiHints": {
|
|
19
|
+
"apiUrl": {
|
|
20
|
+
"label": "Watcher API URL",
|
|
21
|
+
"placeholder": "http://127.0.0.1:3458"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@karmaniverous/jeeves-watcher-openclaw",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"author": "Jason Williscroft",
|
|
5
|
+
"description": "OpenClaw plugin for jeeves-watcher — semantic search and metadata enrichment tools",
|
|
6
|
+
"license": "BSD-3-Clause",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"openclaw.plugin.json",
|
|
21
|
+
"dist/skills"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/karmaniverous/jeeves-watcher.git",
|
|
29
|
+
"directory": "packages/openclaw"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/karmaniverous/jeeves-watcher/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/karmaniverous/jeeves-watcher#readme",
|
|
35
|
+
"keywords": [
|
|
36
|
+
"openclaw",
|
|
37
|
+
"plugin",
|
|
38
|
+
"jeeves-watcher",
|
|
39
|
+
"semantic-search"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20"
|
|
43
|
+
},
|
|
44
|
+
"openclaw": {
|
|
45
|
+
"extensions": [
|
|
46
|
+
"./dist/index.js"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"auto-changelog": {
|
|
50
|
+
"output": "CHANGELOG.md",
|
|
51
|
+
"unreleased": true,
|
|
52
|
+
"commitLimit": false,
|
|
53
|
+
"hideCredit": true
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@dotenvx/dotenvx": "^1.52.0",
|
|
57
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
58
|
+
"auto-changelog": "^2.5.0",
|
|
59
|
+
"cross-env": "^10.1.0",
|
|
60
|
+
"handlebars": "^4.7.8",
|
|
61
|
+
"release-it": "^19.2.4",
|
|
62
|
+
"rollup": "^4.59.0",
|
|
63
|
+
"tslib": "^2.8.1"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build:plugin": "rimraf dist && cross-env NO_COLOR=1 rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript",
|
|
67
|
+
"build:skills": "tsx scripts/build-skills.ts",
|
|
68
|
+
"build": "npm run build:plugin && npm run build:skills",
|
|
69
|
+
"changelog": "auto-changelog",
|
|
70
|
+
"lint": "eslint .",
|
|
71
|
+
"lint:fix": "eslint --fix .",
|
|
72
|
+
"release": "dotenvx run -f .env.local -- release-it",
|
|
73
|
+
"release:pre": "dotenvx run -f .env.local -- release-it --no-git.requireBranch --github.prerelease --preRelease",
|
|
74
|
+
"typecheck": "tsc"
|
|
75
|
+
},
|
|
76
|
+
"release-it": {
|
|
77
|
+
"git": {
|
|
78
|
+
"changelog": "npx auto-changelog --unreleased-only --stdout --template https://raw.githubusercontent.com/release-it/release-it/main/templates/changelog-compact.hbs",
|
|
79
|
+
"commitMessage": "chore: release @karmaniverous/jeeves-watcher-openclaw v${version}",
|
|
80
|
+
"requireBranch": "main"
|
|
81
|
+
},
|
|
82
|
+
"github": {
|
|
83
|
+
"release": true
|
|
84
|
+
},
|
|
85
|
+
"hooks": {
|
|
86
|
+
"after:init": [
|
|
87
|
+
"npm run lint",
|
|
88
|
+
"npm run typecheck",
|
|
89
|
+
"npm run build"
|
|
90
|
+
],
|
|
91
|
+
"before:npm:release": [
|
|
92
|
+
"npx auto-changelog -p",
|
|
93
|
+
"git add -A"
|
|
94
|
+
],
|
|
95
|
+
"after:release": [
|
|
96
|
+
"git switch -c release/openclaw/${version}",
|
|
97
|
+
"git push -u origin release/openclaw/${version}",
|
|
98
|
+
"git switch ${branchName}"
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
"npm": {
|
|
102
|
+
"publish": true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|