@karmaniverous/jeeves-watcher-openclaw 0.3.13 → 0.4.1
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/cli.js +7 -86
- package/dist/index.js +0 -283
- package/dist/skills/jeeves-watcher/SKILL.md +253 -65
- package/dist/src/cli.d.ts +2 -11
- package/dist/src/helpers.d.ts +0 -28
- package/openclaw.plugin.json +3 -3
- package/package.json +1 -1
- package/dist/src/memoryTools.d.ts +0 -25
- package/dist/src/memoryTools.test.d.ts +0 -1
package/dist/cli.js
CHANGED
|
@@ -8,13 +8,9 @@ import { fileURLToPath } from 'url';
|
|
|
8
8
|
* CLI for installing/uninstalling the jeeves-watcher OpenClaw plugin.
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
11
|
-
* npx \@karmaniverous/jeeves-watcher-openclaw install
|
|
11
|
+
* npx \@karmaniverous/jeeves-watcher-openclaw install
|
|
12
12
|
* npx \@karmaniverous/jeeves-watcher-openclaw uninstall
|
|
13
13
|
*
|
|
14
|
-
* The --memory flag claims the OpenClaw memory slot, replacing memory-core
|
|
15
|
-
* with the plugin's Qdrant-backed memory_search and memory_get tools.
|
|
16
|
-
* Without --memory, only watcher admin tools are registered.
|
|
17
|
-
*
|
|
18
14
|
* Bypasses OpenClaw's `plugins install` command, which has a known
|
|
19
15
|
* spawn EINVAL bug on Windows (https://github.com/openclaw/openclaw/issues/9224).
|
|
20
16
|
*
|
|
@@ -83,7 +79,7 @@ function patchAllowList(parent, key, label, mode) {
|
|
|
83
79
|
return undefined;
|
|
84
80
|
}
|
|
85
81
|
/** Patch OpenClaw config for install or uninstall. Returns log messages. */
|
|
86
|
-
function patchConfig(config, mode
|
|
82
|
+
function patchConfig(config, mode) {
|
|
87
83
|
const messages = [];
|
|
88
84
|
// Ensure plugins section
|
|
89
85
|
if (!config.plugins || typeof config.plugins !== 'object') {
|
|
@@ -109,56 +105,6 @@ function patchConfig(config, mode, options = {}) {
|
|
|
109
105
|
Reflect.deleteProperty(entries, PLUGIN_ID);
|
|
110
106
|
messages.push(`Removed "${PLUGIN_ID}" from plugins.entries`);
|
|
111
107
|
}
|
|
112
|
-
// plugins.slots — claim or release the memory slot
|
|
113
|
-
if (!plugins.slots || typeof plugins.slots !== 'object') {
|
|
114
|
-
plugins.slots = {};
|
|
115
|
-
}
|
|
116
|
-
const slots = plugins.slots;
|
|
117
|
-
if (mode === 'add' && options.memory) {
|
|
118
|
-
const prev = slots.memory;
|
|
119
|
-
slots.memory = PLUGIN_ID;
|
|
120
|
-
if (prev !== PLUGIN_ID) {
|
|
121
|
-
messages.push(`Set plugins.slots.memory to "${PLUGIN_ID}"${prev ? ` (was "${prev}")` : ''}`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
else if (mode === 'add' && !options.memory) {
|
|
125
|
-
// Non-memory install: revert slot if we held it
|
|
126
|
-
if (slots.memory === PLUGIN_ID) {
|
|
127
|
-
slots.memory = 'memory-core';
|
|
128
|
-
messages.push(`Reverted plugins.slots.memory to "memory-core" (non-memory install)`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
else if (slots.memory === PLUGIN_ID) {
|
|
132
|
-
slots.memory = 'memory-core';
|
|
133
|
-
messages.push(`Reverted plugins.slots.memory to "memory-core"`);
|
|
134
|
-
}
|
|
135
|
-
// memory-core.enabled — disable when claiming memory slot to prevent
|
|
136
|
-
// core memory tools from shadowing the plugin's tools
|
|
137
|
-
const memoryCoreEntry = entries['memory-core'];
|
|
138
|
-
if (mode === 'add' && options.memory) {
|
|
139
|
-
if (!memoryCoreEntry) {
|
|
140
|
-
entries['memory-core'] = { enabled: false };
|
|
141
|
-
messages.push('Set memory-core.enabled to false');
|
|
142
|
-
}
|
|
143
|
-
else if (memoryCoreEntry.enabled !== false) {
|
|
144
|
-
memoryCoreEntry.enabled = false;
|
|
145
|
-
messages.push('Set memory-core.enabled to false');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
else if (mode === 'add' && !options.memory) {
|
|
149
|
-
// Non-memory install: re-enable memory-core if we disabled it
|
|
150
|
-
if (memoryCoreEntry && memoryCoreEntry.enabled === false) {
|
|
151
|
-
memoryCoreEntry.enabled = true;
|
|
152
|
-
messages.push('Re-enabled memory-core (non-memory install)');
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
else if (mode === 'remove') {
|
|
156
|
-
// Uninstall: re-enable memory-core if we disabled it
|
|
157
|
-
if (memoryCoreEntry && memoryCoreEntry.enabled === false) {
|
|
158
|
-
memoryCoreEntry.enabled = true;
|
|
159
|
-
messages.push('Re-enabled memory-core');
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
108
|
// tools.allow
|
|
163
109
|
const tools = (config.tools ?? {});
|
|
164
110
|
const toolAllow = patchAllowList(tools, 'allow', 'tools.allow', mode);
|
|
@@ -167,7 +113,7 @@ function patchConfig(config, mode, options = {}) {
|
|
|
167
113
|
return messages;
|
|
168
114
|
}
|
|
169
115
|
/** Install the plugin into OpenClaw's extensions directory. */
|
|
170
|
-
function install(
|
|
116
|
+
function install() {
|
|
171
117
|
const home = resolveOpenClawHome();
|
|
172
118
|
const configPath = resolveConfigPath(home);
|
|
173
119
|
const extDir = join(home, 'extensions', PLUGIN_ID);
|
|
@@ -176,7 +122,6 @@ function install(memoryMode) {
|
|
|
176
122
|
console.log(`Config: ${configPath}`);
|
|
177
123
|
console.log(`Extensions dir: ${extDir}`);
|
|
178
124
|
console.log(`Package root: ${pkgRoot}`);
|
|
179
|
-
console.log(`Memory mode: ${memoryMode ? 'yes (claiming memory slot)' : 'no (watcher tools only)'}`);
|
|
180
125
|
console.log();
|
|
181
126
|
if (!existsSync(home)) {
|
|
182
127
|
console.error(`Error: OpenClaw home directory not found at ${home}`);
|
|
@@ -212,20 +157,6 @@ function install(memoryMode) {
|
|
|
212
157
|
cpSync(nodeModulesSrc, join(extDir, 'node_modules'), { recursive: true });
|
|
213
158
|
console.log(' ✓ node_modules');
|
|
214
159
|
}
|
|
215
|
-
// Patch manifest based on memory mode
|
|
216
|
-
const installedManifestPath = join(extDir, 'openclaw.plugin.json');
|
|
217
|
-
const manifest = readJson(installedManifestPath);
|
|
218
|
-
if (manifest) {
|
|
219
|
-
if (memoryMode) {
|
|
220
|
-
manifest.kind = 'memory';
|
|
221
|
-
console.log(' ✓ Set kind: "memory" in manifest');
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
delete manifest.kind;
|
|
225
|
-
console.log(' ✓ Removed kind from manifest (non-memory mode)');
|
|
226
|
-
}
|
|
227
|
-
writeJson(installedManifestPath, manifest);
|
|
228
|
-
}
|
|
229
160
|
// Patch config
|
|
230
161
|
console.log();
|
|
231
162
|
console.log('Patching OpenClaw config...');
|
|
@@ -234,19 +165,13 @@ function install(memoryMode) {
|
|
|
234
165
|
console.error(`Error: Could not parse ${configPath}`);
|
|
235
166
|
process.exit(1);
|
|
236
167
|
}
|
|
237
|
-
for (const msg of patchConfig(config, 'add'
|
|
168
|
+
for (const msg of patchConfig(config, 'add')) {
|
|
238
169
|
console.log(` ✓ ${msg}`);
|
|
239
170
|
}
|
|
240
171
|
writeJson(configPath, config);
|
|
241
172
|
console.log();
|
|
242
173
|
console.log('✅ Plugin installed successfully.');
|
|
243
174
|
console.log(' Restart the OpenClaw gateway to load the plugin.');
|
|
244
|
-
if (memoryMode) {
|
|
245
|
-
console.log(' Memory slot claimed — memory_search and memory_get tools will be available.');
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
console.log(' Watcher tools available. Run with --memory after bootstrapping to enable memory features.');
|
|
249
|
-
}
|
|
250
175
|
}
|
|
251
176
|
/** Uninstall the plugin from OpenClaw's extensions directory. */
|
|
252
177
|
function uninstall() {
|
|
@@ -280,10 +205,9 @@ function uninstall() {
|
|
|
280
205
|
}
|
|
281
206
|
// Main
|
|
282
207
|
const command = process.argv[2];
|
|
283
|
-
const memoryFlag = process.argv.includes('--memory');
|
|
284
208
|
switch (command) {
|
|
285
209
|
case 'install':
|
|
286
|
-
install(
|
|
210
|
+
install();
|
|
287
211
|
break;
|
|
288
212
|
case 'uninstall':
|
|
289
213
|
uninstall();
|
|
@@ -292,11 +216,8 @@ switch (command) {
|
|
|
292
216
|
console.log(`@karmaniverous/jeeves-watcher-openclaw — OpenClaw plugin installer`);
|
|
293
217
|
console.log();
|
|
294
218
|
console.log('Usage:');
|
|
295
|
-
console.log(' npx @karmaniverous/jeeves-watcher-openclaw install
|
|
296
|
-
console.log(' npx @karmaniverous/jeeves-watcher-openclaw uninstall
|
|
297
|
-
console.log();
|
|
298
|
-
console.log('Options:');
|
|
299
|
-
console.log(' --memory Claim memory slot (replaces memory-core with Qdrant-backed search)');
|
|
219
|
+
console.log(' npx @karmaniverous/jeeves-watcher-openclaw install Install plugin');
|
|
220
|
+
console.log(' npx @karmaniverous/jeeves-watcher-openclaw uninstall Remove plugin');
|
|
300
221
|
console.log();
|
|
301
222
|
console.log('Environment variables:');
|
|
302
223
|
console.log(' OPENCLAW_CONFIG Path to openclaw.json (overrides all)');
|
package/dist/index.js
CHANGED
|
@@ -1,52 +1,13 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
2
|
* @module plugin/helpers
|
|
7
3
|
* Shared types and utility functions for the OpenClaw plugin tool registrations.
|
|
8
4
|
*/
|
|
9
5
|
const DEFAULT_API_URL = 'http://127.0.0.1:1936';
|
|
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
|
-
return p.replace(/\\/g, '/');
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Resolve the workspace path from gateway config.
|
|
18
|
-
* Priority: agent-specific \> defaults \> fallback (~/.openclaw/workspace).
|
|
19
|
-
*/
|
|
20
|
-
function getWorkspacePath(api) {
|
|
21
|
-
const agentWorkspace = api.config?.agents?.entries?.['main']?.workspace ??
|
|
22
|
-
api.config?.agents?.defaults?.workspace;
|
|
23
|
-
return agentWorkspace ?? join(homedir(), '.openclaw', 'workspace');
|
|
24
|
-
}
|
|
25
6
|
/** Resolve the watcher API base URL from plugin config. */
|
|
26
7
|
function getApiUrl(api) {
|
|
27
8
|
const url = api.config?.plugins?.entries?.['jeeves-watcher-openclaw']?.config?.apiUrl;
|
|
28
9
|
return typeof url === 'string' ? url : DEFAULT_API_URL;
|
|
29
10
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Resolve user-supplied schemas from plugin config.
|
|
32
|
-
* Returns a map of rule name → schema array (always normalized to array).
|
|
33
|
-
*/
|
|
34
|
-
function getPluginSchemas(api) {
|
|
35
|
-
const config = api.config?.plugins?.entries?.['jeeves-watcher-openclaw']?.config;
|
|
36
|
-
const raw = config?.schemas;
|
|
37
|
-
if (!raw || typeof raw !== 'object')
|
|
38
|
-
return {};
|
|
39
|
-
const result = {};
|
|
40
|
-
for (const [name, value] of Object.entries(raw)) {
|
|
41
|
-
if (Array.isArray(value)) {
|
|
42
|
-
result[name] = value;
|
|
43
|
-
}
|
|
44
|
-
else if (typeof value === 'object' || typeof value === 'string') {
|
|
45
|
-
result[name] = [value];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
11
|
/** Format a successful tool result. */
|
|
51
12
|
function ok(data) {
|
|
52
13
|
return {
|
|
@@ -102,204 +63,6 @@ async function postJson(url, body) {
|
|
|
102
63
|
});
|
|
103
64
|
}
|
|
104
65
|
|
|
105
|
-
/**
|
|
106
|
-
* @module plugin/memoryTools
|
|
107
|
-
* memory_search and memory_get tool implementations with lazy init.
|
|
108
|
-
*
|
|
109
|
-
* Lazy init registers virtual inference rules with the watcher on first
|
|
110
|
-
* memory_search call. Re-attempts on failure. memory_get reads files
|
|
111
|
-
* directly from the filesystem with path validation.
|
|
112
|
-
*/
|
|
113
|
-
/** Private property prefix — namespaces plugin metadata to avoid collisions. */
|
|
114
|
-
const PROP_PREFIX = '_jeeves_watcher_openclaw_';
|
|
115
|
-
/** Private property keys used by the plugin for filtering. */
|
|
116
|
-
const PROP_SOURCE = `${PROP_PREFIX}source_`;
|
|
117
|
-
const PROP_KIND = `${PROP_PREFIX}kind_`;
|
|
118
|
-
/** Memory source value for filter queries. */
|
|
119
|
-
const SOURCE_MEMORY = 'memory';
|
|
120
|
-
/** Virtual rule names (exported for testing and config reference). */
|
|
121
|
-
const RULE_LONGTERM = 'openclaw-memory-longterm';
|
|
122
|
-
const RULE_DAILY = 'openclaw-memory-daily';
|
|
123
|
-
/**
|
|
124
|
-
* Build virtual inference rules for a workspace path.
|
|
125
|
-
*
|
|
126
|
-
* Each rule's schema is composed of:
|
|
127
|
-
* 1. Plugin-internal schema (private namespaced properties, no uiHint)
|
|
128
|
-
* 2. User-supplied schemas from plugin config (optional, owner-controlled)
|
|
129
|
-
*/
|
|
130
|
-
function buildVirtualRules(workspace, userSchemas) {
|
|
131
|
-
const ws = normalizePath(workspace);
|
|
132
|
-
const longtermInternal = {
|
|
133
|
-
type: 'object',
|
|
134
|
-
properties: {
|
|
135
|
-
[PROP_SOURCE]: { type: 'string', set: SOURCE_MEMORY },
|
|
136
|
-
[PROP_KIND]: { type: 'string', set: 'long-term' },
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
const dailyInternal = {
|
|
140
|
-
type: 'object',
|
|
141
|
-
properties: {
|
|
142
|
-
[PROP_SOURCE]: { type: 'string', set: SOURCE_MEMORY },
|
|
143
|
-
[PROP_KIND]: { type: 'string', set: 'daily-log' },
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
return [
|
|
147
|
-
{
|
|
148
|
-
name: RULE_LONGTERM,
|
|
149
|
-
description: 'OpenClaw long-term memory file',
|
|
150
|
-
match: {
|
|
151
|
-
properties: {
|
|
152
|
-
file: {
|
|
153
|
-
properties: {
|
|
154
|
-
path: { glob: `${ws}/MEMORY.md` },
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
schema: [longtermInternal, ...(userSchemas[RULE_LONGTERM] ?? [])],
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
name: RULE_DAILY,
|
|
163
|
-
description: 'OpenClaw daily memory logs',
|
|
164
|
-
match: {
|
|
165
|
-
properties: {
|
|
166
|
-
file: {
|
|
167
|
-
properties: {
|
|
168
|
-
path: { glob: `${ws}/memory/**/*.md` },
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
schema: [dailyInternal, ...(userSchemas[RULE_DAILY] ?? [])],
|
|
174
|
-
},
|
|
175
|
-
];
|
|
176
|
-
}
|
|
177
|
-
/** Validate a path is within the memory scope. */
|
|
178
|
-
function isAllowedMemoryPath(filePath, workspace) {
|
|
179
|
-
const norm = normalizePath(filePath).toLowerCase();
|
|
180
|
-
const ws = normalizePath(workspace).toLowerCase();
|
|
181
|
-
// Exact match: {workspace}/MEMORY.md
|
|
182
|
-
if (norm === `${ws}/memory.md`)
|
|
183
|
-
return true;
|
|
184
|
-
// Prefix match: {workspace}/memory/**/*.md
|
|
185
|
-
if (norm.startsWith(`${ws}/memory/`) && norm.endsWith('.md'))
|
|
186
|
-
return true;
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Create memory tool registrations for the plugin.
|
|
191
|
-
* Returns register functions for memory_search and memory_get.
|
|
192
|
-
*/
|
|
193
|
-
function createMemoryTools(api, baseUrl) {
|
|
194
|
-
const workspace = getWorkspacePath(api);
|
|
195
|
-
const userSchemas = getPluginSchemas(api);
|
|
196
|
-
const state = {
|
|
197
|
-
initialized: false,
|
|
198
|
-
lastWatcherUptime: 0,
|
|
199
|
-
workspace,
|
|
200
|
-
baseUrl,
|
|
201
|
-
};
|
|
202
|
-
/** Lazy init: register virtual rules with watcher. */
|
|
203
|
-
async function ensureInit() {
|
|
204
|
-
// Check watcher is reachable and detect restarts
|
|
205
|
-
const status = (await fetchJson(`${state.baseUrl}/status`));
|
|
206
|
-
const uptime = status.uptime ?? 0;
|
|
207
|
-
// If watcher restarted (uptime decreased), re-register virtual rules
|
|
208
|
-
if (state.initialized && uptime >= state.lastWatcherUptime)
|
|
209
|
-
return;
|
|
210
|
-
state.lastWatcherUptime = uptime;
|
|
211
|
-
// Clear any stale rules
|
|
212
|
-
await fetchJson(`${state.baseUrl}/rules/unregister`, {
|
|
213
|
-
method: 'DELETE',
|
|
214
|
-
headers: { 'Content-Type': 'application/json' },
|
|
215
|
-
body: JSON.stringify({ source: PLUGIN_SOURCE }),
|
|
216
|
-
});
|
|
217
|
-
// Register virtual rules
|
|
218
|
-
const virtualRules = buildVirtualRules(state.workspace, userSchemas);
|
|
219
|
-
await postJson(`${state.baseUrl}/rules/register`, {
|
|
220
|
-
source: PLUGIN_SOURCE,
|
|
221
|
-
rules: virtualRules,
|
|
222
|
-
});
|
|
223
|
-
// Re-apply rules to already-indexed files matching virtual rule globs
|
|
224
|
-
const globs = virtualRules
|
|
225
|
-
.map((r) => {
|
|
226
|
-
const path = r.match.properties.file.properties.path;
|
|
227
|
-
return typeof path.glob === 'string' ? path.glob : undefined;
|
|
228
|
-
})
|
|
229
|
-
.filter((g) => g !== undefined);
|
|
230
|
-
if (globs.length > 0) {
|
|
231
|
-
try {
|
|
232
|
-
await postJson(`${state.baseUrl}/rules/reapply`, { globs });
|
|
233
|
-
}
|
|
234
|
-
catch {
|
|
235
|
-
// Non-fatal: files will get correct rules on next change
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
state.initialized = true;
|
|
239
|
-
}
|
|
240
|
-
const memorySearch = async (_id, params) => {
|
|
241
|
-
try {
|
|
242
|
-
await ensureInit();
|
|
243
|
-
const body = {
|
|
244
|
-
query: params.query,
|
|
245
|
-
filter: {
|
|
246
|
-
must: [{ key: PROP_SOURCE, match: { value: SOURCE_MEMORY } }],
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
if (params.maxResults !== undefined)
|
|
250
|
-
body.limit = params.maxResults;
|
|
251
|
-
const raw = await postJson(`${state.baseUrl}/search`, body);
|
|
252
|
-
// Map results to system-prompt-compatible format
|
|
253
|
-
const results = raw.map((r) => {
|
|
254
|
-
const payload = r.payload;
|
|
255
|
-
const mapped = {
|
|
256
|
-
path: payload.file_path,
|
|
257
|
-
snippet: payload.chunk_text,
|
|
258
|
-
score: r.score,
|
|
259
|
-
};
|
|
260
|
-
if (payload.line_start != null)
|
|
261
|
-
mapped.from = payload.line_start;
|
|
262
|
-
if (payload.line_end != null)
|
|
263
|
-
mapped.to = payload.line_end;
|
|
264
|
-
return mapped;
|
|
265
|
-
});
|
|
266
|
-
const minScore = typeof params.minScore === 'number' ? params.minScore : 0;
|
|
267
|
-
const filtered = results.filter((r) => typeof r.score === 'number' && r.score >= minScore);
|
|
268
|
-
return ok({ provider: 'jeeves-watcher', results: filtered });
|
|
269
|
-
}
|
|
270
|
-
catch (error) {
|
|
271
|
-
state.initialized = false;
|
|
272
|
-
return connectionFail(error, state.baseUrl);
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
const memoryGet = async (_id, params) => {
|
|
276
|
-
try {
|
|
277
|
-
const filePath = String(params.path);
|
|
278
|
-
// Re-derive workspace on every call for immediate pickup of moves
|
|
279
|
-
const currentWorkspace = getWorkspacePath(api);
|
|
280
|
-
if (!isAllowedMemoryPath(filePath, currentWorkspace)) {
|
|
281
|
-
return fail(`Path not within memory scope. Allowed: ${currentWorkspace}/MEMORY.md and ${currentWorkspace}/memory/**/*.md`);
|
|
282
|
-
}
|
|
283
|
-
const content = await readFile(filePath, 'utf-8');
|
|
284
|
-
if (params.from !== undefined) {
|
|
285
|
-
const from = Number(params.from);
|
|
286
|
-
const lines = content.split('\n');
|
|
287
|
-
const startIdx = Math.max(0, from - 1); // 1-indexed to 0-indexed
|
|
288
|
-
const count = params.lines !== undefined
|
|
289
|
-
? Number(params.lines)
|
|
290
|
-
: lines.length - startIdx;
|
|
291
|
-
const sliced = lines.slice(startIdx, startIdx + count);
|
|
292
|
-
return ok({ provider: 'jeeves-watcher', content: sliced.join('\n') });
|
|
293
|
-
}
|
|
294
|
-
return ok({ provider: 'jeeves-watcher', content });
|
|
295
|
-
}
|
|
296
|
-
catch (error) {
|
|
297
|
-
return fail(error);
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
return { memorySearch, memoryGet };
|
|
301
|
-
}
|
|
302
|
-
|
|
303
66
|
/**
|
|
304
67
|
* @module plugin/watcherTools
|
|
305
68
|
* Watcher tool registrations (watcher_* tools) for the OpenClaw plugin.
|
|
@@ -485,53 +248,7 @@ function registerWatcherTools(api, baseUrl) {
|
|
|
485
248
|
/** Register all jeeves-watcher tools with the OpenClaw plugin API. */
|
|
486
249
|
function register(api) {
|
|
487
250
|
const baseUrl = getApiUrl(api);
|
|
488
|
-
// Register 8 watcher_* tools
|
|
489
251
|
registerWatcherTools(api, baseUrl);
|
|
490
|
-
// Register memory slot tools (memory_search + memory_get)
|
|
491
|
-
const { memorySearch, memoryGet } = createMemoryTools(api, baseUrl);
|
|
492
|
-
api.registerTool({
|
|
493
|
-
name: 'memory_search',
|
|
494
|
-
description: 'Semantically search MEMORY.md and memory/*.md files. Returns top snippets with path and line numbers.',
|
|
495
|
-
parameters: {
|
|
496
|
-
type: 'object',
|
|
497
|
-
required: ['query'],
|
|
498
|
-
properties: {
|
|
499
|
-
query: { type: 'string', description: 'Search query text.' },
|
|
500
|
-
maxResults: {
|
|
501
|
-
type: 'number',
|
|
502
|
-
description: 'Maximum results to return.',
|
|
503
|
-
},
|
|
504
|
-
minScore: {
|
|
505
|
-
type: 'number',
|
|
506
|
-
description: 'Minimum similarity score threshold.',
|
|
507
|
-
},
|
|
508
|
-
},
|
|
509
|
-
},
|
|
510
|
-
execute: memorySearch,
|
|
511
|
-
});
|
|
512
|
-
api.registerTool({
|
|
513
|
-
name: 'memory_get',
|
|
514
|
-
description: 'Read content from MEMORY.md or memory/*.md files with optional line range.',
|
|
515
|
-
parameters: {
|
|
516
|
-
type: 'object',
|
|
517
|
-
required: ['path'],
|
|
518
|
-
properties: {
|
|
519
|
-
path: {
|
|
520
|
-
type: 'string',
|
|
521
|
-
description: 'Path to the memory file to read.',
|
|
522
|
-
},
|
|
523
|
-
from: {
|
|
524
|
-
type: 'number',
|
|
525
|
-
description: 'Line number to start reading from (1-indexed).',
|
|
526
|
-
},
|
|
527
|
-
lines: {
|
|
528
|
-
type: 'number',
|
|
529
|
-
description: 'Number of lines to read.',
|
|
530
|
-
},
|
|
531
|
-
},
|
|
532
|
-
},
|
|
533
|
-
execute: memoryGet,
|
|
534
|
-
});
|
|
535
252
|
}
|
|
536
253
|
|
|
537
254
|
export { register as default };
|
|
@@ -3,8 +3,8 @@ name: jeeves-watcher
|
|
|
3
3
|
description: >
|
|
4
4
|
Semantic search across a structured document archive. Use when you need to
|
|
5
5
|
recall prior context, find documents, answer questions that require searching
|
|
6
|
-
across domains
|
|
7
|
-
|
|
6
|
+
across indexed domains, enrich document metadata, manage watcher config, or
|
|
7
|
+
diagnose indexing issues.
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# jeeves-watcher — Search, Discovery & Administration
|
|
@@ -54,15 +54,17 @@ curl -X POST http://127.0.0.1:<PORT>/config/query \
|
|
|
54
54
|
|
|
55
55
|
## Theory of Operation
|
|
56
56
|
|
|
57
|
-
You have access to a **semantic archive** of your human's working world
|
|
57
|
+
You have access to a **semantic archive** of your human's working world. Documents, messages, tickets, notes, code, and other artifacts are indexed, chunked, embedded, and searchable. This is your long-term recall for anything beyond the current conversation.
|
|
58
|
+
|
|
59
|
+
**Every deployment is different.** The archive's structure, domains, metadata fields, and record types are all defined by the deployment's configuration. Do not assume any particular domains exist (e.g., "email", "slack", "jira"). Always discover what's available using the Orientation Pattern below. Examples in this skill use common domain names for illustration only.
|
|
58
60
|
|
|
59
61
|
**When to reach for the watcher:**
|
|
60
62
|
|
|
61
|
-
- **Someone asks about something that happened.** A meeting, a decision, a conversation, a ticket. You weren't there, but the archive was. Search it.
|
|
63
|
+
- **Someone asks about something that happened.** A meeting, a decision, a conversation, a ticket, a message. You weren't there, but the archive was. Search it.
|
|
62
64
|
- **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.
|
|
63
|
-
- **You're working on
|
|
65
|
+
- **You're working on something and need background.** Prior discussions, related records, relevant documents. Search by topic, filter by domain.
|
|
64
66
|
- **You need to verify something.** Don't guess from stale memory. Search for the current state.
|
|
65
|
-
- **You want to connect dots across domains.** The same topic might appear in
|
|
67
|
+
- **You want to connect dots across domains.** The same topic might appear in multiple domains. A single semantic search surfaces all of them.
|
|
66
68
|
|
|
67
69
|
**When NOT to use it:**
|
|
68
70
|
|
|
@@ -72,10 +74,10 @@ You have access to a **semantic archive** of your human's working world: email,
|
|
|
72
74
|
|
|
73
75
|
## Memory → Archive Escalation
|
|
74
76
|
|
|
75
|
-
You have two
|
|
77
|
+
You have two complementary tools with different scopes:
|
|
76
78
|
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
+
- **memory-core** (`memory_search` / `memory_get`) — OpenClaw's built-in memory provider. Manages curated notes in MEMORY.md and memory/*.md. High signal, small scope. This is your long-term memory: decisions, rules, people, project context. Always check here first.
|
|
80
|
+
- **watcher** (`watcher_search`) — the full indexed archive across all configured domains. Broad scope, raw record.
|
|
79
81
|
|
|
80
82
|
**The escalation rule:** When `memory_search` returns thin, zero, or low-confidence results for something your human clearly expects you to know about — a person, a project, an event, a thing — don't stop there. Follow up with `watcher_search` across the full index.
|
|
81
83
|
|
|
@@ -90,96 +92,280 @@ You have two search tools with different scopes:
|
|
|
90
92
|
- The question is about your own operational rules or preferences (that's purely memory)
|
|
91
93
|
- You've already searched the archive this turn
|
|
92
94
|
|
|
93
|
-
**Example:** "Tell me about
|
|
95
|
+
**Example:** "Tell me about Project X" → memory says "started in January" → that's thin → escalate to `watcher_search("Project X")` → tickets, messages, and docs reveal the full history. Report the full picture.
|
|
96
|
+
|
|
97
|
+
**The principle:** Memory-core is your curated highlights. The watcher archive is your perfect recall. Use memory first for speed and signal, but never let its narrow scope be the ceiling of what you can remember.
|
|
98
|
+
|
|
99
|
+
## Embedding Alignment
|
|
100
|
+
|
|
101
|
+
Memory-core and the watcher both use embeddings, but may use different models by default. For best cross-tool consistency, offer to configure memory-core to use `gemini-embedding-001` (the same model the watcher uses) via the gateway config:
|
|
102
|
+
|
|
103
|
+
1. **Verify Google API key** — check that the gateway has a Google API key configured (needed for Gemini embeddings).
|
|
104
|
+
2. **Apply config** — use `gateway config.patch` to set the memory-core embedding provider:
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"agents": {
|
|
108
|
+
"defaults": {
|
|
109
|
+
"memorySearch": {
|
|
110
|
+
"provider": "gemini",
|
|
111
|
+
"model": "gemini-embedding-001"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
3. **Restart gateway** to pick up the new embedding provider. Memory-core will re-embed all memory files on next sync (dimension change triggers automatic vector table recreation).
|
|
118
|
+
|
|
119
|
+
This gives memory-core the same 3072-dimensional Gemini embeddings the watcher uses, ensuring semantic similarity scores are comparable across memory and archive searches.
|
|
94
120
|
|
|
95
|
-
|
|
121
|
+
## Plugin Installation
|
|
96
122
|
|
|
97
|
-
|
|
123
|
+
```
|
|
124
|
+
npx @karmaniverous/jeeves-watcher-openclaw install
|
|
125
|
+
```
|
|
98
126
|
|
|
99
|
-
|
|
127
|
+
This copies the plugin to OpenClaw's extensions directory and patches `openclaw.json` to register it. Restart the gateway to load the plugin.
|
|
100
128
|
|
|
101
|
-
|
|
129
|
+
To remove:
|
|
130
|
+
```
|
|
131
|
+
npx @karmaniverous/jeeves-watcher-openclaw uninstall
|
|
132
|
+
```
|
|
102
133
|
|
|
103
|
-
|
|
134
|
+
## Quick Start (Existing Deployment)
|
|
104
135
|
|
|
105
|
-
|
|
136
|
+
If the watcher service is already running and healthy:
|
|
106
137
|
|
|
107
138
|
1. **Orient yourself** (once per session) — use `watcher_query` to learn the deployment's organizational strategy and available record types (see Orientation Pattern below)
|
|
108
139
|
2. **Search** — use `watcher_search` with a natural language query and optional metadata filters
|
|
109
140
|
3. **Read source** — use `read` (standard file read) with `file_path` from search results for full document content
|
|
110
141
|
|
|
111
|
-
## Bootstrap (First
|
|
142
|
+
## Bootstrap (First-Time Setup)
|
|
112
143
|
|
|
113
|
-
|
|
144
|
+
When the plugin loads and the watcher service is NOT yet set up, drive the entire setup proactively. The user should be able to install the plugin with nothing else in place and the bootstrap process gets them to a working system.
|
|
114
145
|
|
|
115
|
-
**
|
|
146
|
+
**The agent drives this process.** Don't hand the user CLI commands and wait. Check each prerequisite, explain what's needed, execute what you can, and prompt the user only for decisions that require human judgment.
|
|
116
147
|
|
|
117
|
-
|
|
118
|
-
2. **Discover the deployment** — run the Orientation Pattern (see below): query `$.['description','search']` for organizational strategy and score thresholds, then `$.inferenceRules[*].['name','description']` for available record types.
|
|
119
|
-
3. **Cache context** — store the orientation results mentally for the session. You now know what domains exist, what record types are searchable, and how to interpret scores.
|
|
120
|
-
4. **Report readiness** — briefly tell the user what you found: how many points, which domains, any issues. One or two sentences, not a wall of text.
|
|
148
|
+
### Step 1: Check Node.js
|
|
121
149
|
|
|
122
|
-
|
|
150
|
+
Verify Node.js is installed and version ≥ 20:
|
|
151
|
+
```bash
|
|
152
|
+
node --version
|
|
153
|
+
```
|
|
123
154
|
|
|
124
|
-
|
|
155
|
+
If missing or too old, guide the user to install Node.js 20+ from https://nodejs.org or via their package manager.
|
|
125
156
|
|
|
126
|
-
|
|
157
|
+
### Step 2: Install Qdrant
|
|
127
158
|
|
|
128
|
-
|
|
159
|
+
Check if Qdrant is already running:
|
|
160
|
+
```bash
|
|
161
|
+
curl -s http://localhost:6333/healthz
|
|
162
|
+
```
|
|
129
163
|
|
|
130
|
-
**
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
164
|
+
If not running, install it. **Prefer native installation** (especially on cloud instances where Docker may not be available):
|
|
165
|
+
|
|
166
|
+
**Linux (recommended for servers):**
|
|
167
|
+
```bash
|
|
168
|
+
# Download latest release
|
|
169
|
+
curl -L https://github.com/qdrant/qdrant/releases/latest/download/qdrant-x86_64-unknown-linux-musl.tar.gz -o qdrant.tar.gz
|
|
170
|
+
tar xzf qdrant.tar.gz
|
|
171
|
+
|
|
172
|
+
# Run (foreground for testing)
|
|
173
|
+
./qdrant
|
|
174
|
+
|
|
175
|
+
# For production: create a systemd service
|
|
176
|
+
sudo tee /etc/systemd/system/qdrant.service > /dev/null <<EOF
|
|
177
|
+
[Unit]
|
|
178
|
+
Description=Qdrant Vector Database
|
|
179
|
+
After=network.target
|
|
180
|
+
|
|
181
|
+
[Service]
|
|
182
|
+
Type=simple
|
|
183
|
+
ExecStart=/usr/local/bin/qdrant --config-path /etc/qdrant/config.yaml
|
|
184
|
+
Restart=always
|
|
185
|
+
User=qdrant
|
|
186
|
+
|
|
187
|
+
[Install]
|
|
188
|
+
WantedBy=multi-user.target
|
|
189
|
+
EOF
|
|
190
|
+
sudo systemctl enable --now qdrant
|
|
191
|
+
```
|
|
134
192
|
|
|
135
|
-
**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
193
|
+
**Windows:**
|
|
194
|
+
```powershell
|
|
195
|
+
# Download from GitHub releases page
|
|
196
|
+
# https://github.com/qdrant/qdrant/releases
|
|
197
|
+
# Extract and run, or register as NSSM service:
|
|
198
|
+
nssm install Qdrant <path-to-qdrant.exe>
|
|
199
|
+
nssm start Qdrant
|
|
200
|
+
```
|
|
141
201
|
|
|
142
|
-
**
|
|
202
|
+
**Docker (fallback, if available):**
|
|
203
|
+
```bash
|
|
204
|
+
docker run -d -p 6333:6333 -v qdrant_data:/qdrant/storage qdrant/qdrant
|
|
205
|
+
```
|
|
143
206
|
|
|
144
|
-
|
|
207
|
+
After installation, verify:
|
|
208
|
+
```bash
|
|
209
|
+
curl -s http://localhost:6333/healthz
|
|
210
|
+
```
|
|
145
211
|
|
|
146
|
-
|
|
212
|
+
### Step 3: Install Watcher Service
|
|
147
213
|
|
|
148
|
-
|
|
214
|
+
Install the watcher CLI globally:
|
|
215
|
+
```bash
|
|
216
|
+
npm install -g @karmaniverous/jeeves-watcher
|
|
217
|
+
```
|
|
149
218
|
|
|
150
|
-
|
|
151
|
-
|
|
219
|
+
Verify:
|
|
220
|
+
```bash
|
|
221
|
+
jeeves-watcher --version
|
|
222
|
+
```
|
|
152
223
|
|
|
153
|
-
|
|
154
|
-
Call `watcher_status` to check the archive size. If the watcher has >10K points, the deployment has a substantial archive and would benefit from semantic memory. Offer to run the upgrade yourself:
|
|
224
|
+
### Step 4: Set Up Embedding Provider
|
|
155
225
|
|
|
156
|
-
|
|
226
|
+
The watcher uses Google Gemini for embeddings by default (`gemini-embedding-001`, 3072 dimensions).
|
|
157
227
|
|
|
158
|
-
|
|
228
|
+
Check for an existing API key:
|
|
229
|
+
```bash
|
|
230
|
+
echo $GOOGLE_API_KEY # Linux/Mac
|
|
231
|
+
echo %GOOGLE_API_KEY% # Windows cmd
|
|
232
|
+
$env:GOOGLE_API_KEY # PowerShell
|
|
233
|
+
```
|
|
159
234
|
|
|
160
|
-
|
|
235
|
+
If not set, guide the user:
|
|
236
|
+
1. Go to https://aistudio.google.com/apikey
|
|
237
|
+
2. Create an API key (free tier supports 1,000 embedding requests/minute)
|
|
238
|
+
3. Set it as a persistent environment variable:
|
|
239
|
+
- **Linux:** Add `export GOOGLE_API_KEY=<key>` to `~/.bashrc` or `~/.profile`
|
|
240
|
+
- **Windows:** `setx GOOGLE_API_KEY "<key>"` (new shell sessions only) or set via System Properties → Environment Variables
|
|
241
|
+
- **macOS:** Add to `~/.zshrc` or use `launchctl setenv`
|
|
242
|
+
|
|
243
|
+
Verify the key works by testing a Gemini API call:
|
|
244
|
+
```bash
|
|
245
|
+
curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:embedContent?key=$GOOGLE_API_KEY" \
|
|
246
|
+
-H "Content-Type: application/json" \
|
|
247
|
+
-d '{"model":"models/gemini-embedding-001","content":{"parts":[{"text":"test"}]}}'
|
|
248
|
+
```
|
|
161
249
|
|
|
162
|
-
|
|
250
|
+
A successful response contains an `embedding.values` array.
|
|
163
251
|
|
|
164
|
-
###
|
|
165
|
-
Semantically search MEMORY.md and memory/*.md files. Powered by the watcher's vector store with Gemini 3072-dim embeddings.
|
|
166
|
-
- `query` (string, required) — search query text
|
|
167
|
-
- `maxResults` (number, optional) — maximum results to return
|
|
168
|
-
- `minScore` (number, optional) — minimum similarity score threshold
|
|
252
|
+
### Step 5: Author Initial Config
|
|
169
253
|
|
|
170
|
-
|
|
171
|
-
|
|
254
|
+
Ask the user these questions:
|
|
255
|
+
- **What directories should the watcher index?** (e.g., `~/documents`, `~/projects`, a workspace path)
|
|
256
|
+
- **What types of files matter?** (helps determine file extensions for watch globs)
|
|
257
|
+
- **Are there directories to exclude?** (node_modules, .git, build outputs, etc.)
|
|
172
258
|
|
|
173
|
-
|
|
174
|
-
Read content from MEMORY.md or memory/*.md files with optional line range.
|
|
175
|
-
- `path` (string, required) — path to the memory file
|
|
176
|
-
- `from` (number, optional) — line number to start reading from (1-indexed)
|
|
177
|
-
- `lines` (number, optional) — number of lines to read
|
|
259
|
+
Then generate a starter config file. Example minimal config:
|
|
178
260
|
|
|
179
|
-
|
|
180
|
-
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"description": "Personal knowledge base indexing",
|
|
264
|
+
"api": { "port": 1936 },
|
|
265
|
+
"watch": {
|
|
266
|
+
"paths": [
|
|
267
|
+
"/home/user/documents/**/*.{md,txt,json,pdf,html,docx}"
|
|
268
|
+
],
|
|
269
|
+
"ignored": ["**/node_modules/**", "**/.git/**", "**/dist/**"]
|
|
270
|
+
},
|
|
271
|
+
"embedding": {
|
|
272
|
+
"provider": "gemini",
|
|
273
|
+
"model": "gemini-embedding-001",
|
|
274
|
+
"dimensions": 3072,
|
|
275
|
+
"apiKey": "${GOOGLE_API_KEY}",
|
|
276
|
+
"chunkSize": 1000,
|
|
277
|
+
"chunkOverlap": 200,
|
|
278
|
+
"rateLimitPerMinute": 1000,
|
|
279
|
+
"concurrency": 5
|
|
280
|
+
},
|
|
281
|
+
"vectorStore": {
|
|
282
|
+
"url": "http://localhost:6333",
|
|
283
|
+
"collection": "jeeves_archive"
|
|
284
|
+
},
|
|
285
|
+
"search": {
|
|
286
|
+
"scoreThresholds": { "strong": 0.75, "relevant": 0.5, "noise": 0.25 },
|
|
287
|
+
"hybrid": { "enabled": true }
|
|
288
|
+
},
|
|
289
|
+
"logging": { "level": "info" },
|
|
290
|
+
"inferenceRules": []
|
|
291
|
+
}
|
|
292
|
+
```
|
|
181
293
|
|
|
182
|
-
|
|
294
|
+
Write the config to a sensible location (e.g., `~/.config/jeeves-watcher.config.json` on Linux, or alongside the user's workspace). Validate with:
|
|
295
|
+
```bash
|
|
296
|
+
jeeves-watcher validate -c <config-path>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Step 6: Register and Start as a Service
|
|
300
|
+
|
|
301
|
+
**The watcher should run as a persistent service, not a foreground process.**
|
|
302
|
+
|
|
303
|
+
**Linux (systemd):**
|
|
304
|
+
```bash
|
|
305
|
+
sudo tee /etc/systemd/system/jeeves-watcher.service > /dev/null <<EOF
|
|
306
|
+
[Unit]
|
|
307
|
+
Description=Jeeves Watcher - Filesystem Indexing Service
|
|
308
|
+
After=network.target qdrant.service
|
|
309
|
+
|
|
310
|
+
[Service]
|
|
311
|
+
Type=simple
|
|
312
|
+
ExecStart=$(which jeeves-watcher) start -c <config-path>
|
|
313
|
+
Restart=always
|
|
314
|
+
Environment=GOOGLE_API_KEY=<key>
|
|
315
|
+
User=$USER
|
|
316
|
+
|
|
317
|
+
[Install]
|
|
318
|
+
WantedBy=multi-user.target
|
|
319
|
+
EOF
|
|
320
|
+
sudo systemctl enable --now jeeves-watcher
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Windows (NSSM):**
|
|
324
|
+
```powershell
|
|
325
|
+
jeeves-watcher service install
|
|
326
|
+
# Or manually:
|
|
327
|
+
nssm install jeeves-watcher "$(which jeeves-watcher)" start -c <config-path>
|
|
328
|
+
nssm set jeeves-watcher AppEnvironmentExtra GOOGLE_API_KEY=<key>
|
|
329
|
+
nssm start jeeves-watcher
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Verify the service started:
|
|
333
|
+
```bash
|
|
334
|
+
curl -s http://127.0.0.1:1936/status
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Step 7: Verify Health
|
|
338
|
+
|
|
339
|
+
Call `watcher_status` (or `curl http://127.0.0.1:1936/status`). Confirm:
|
|
340
|
+
- Service is running
|
|
341
|
+
- Qdrant collection exists with expected dimensions (3072)
|
|
342
|
+
- Point count is increasing (initial indexing in progress)
|
|
343
|
+
|
|
344
|
+
If the point count is 0 after a minute, check `watcher_issues` for embedding failures.
|
|
345
|
+
|
|
346
|
+
### Step 8: Orientation
|
|
347
|
+
|
|
348
|
+
Once health is confirmed and initial indexing has started:
|
|
349
|
+
|
|
350
|
+
1. Query `$.['description','search']` for the deployment's organizational strategy and score thresholds.
|
|
351
|
+
2. Query `$.inferenceRules[*].['name','description']` for available record types.
|
|
352
|
+
3. Report to the user: how many points indexed so far, which domains are available, estimated time to complete initial indexing (based on file count and embedding rate).
|
|
353
|
+
|
|
354
|
+
### Step 9: Align Memory-Core Embeddings
|
|
355
|
+
|
|
356
|
+
After the watcher is healthy, offer to align OpenClaw's memory-core with the same embedding model for consistent vector quality across both systems. See the **Embedding Alignment** section above for the procedure.
|
|
357
|
+
|
|
358
|
+
### On Subsequent Sessions
|
|
359
|
+
|
|
360
|
+
On sessions after bootstrap is complete:
|
|
361
|
+
|
|
362
|
+
1. Call `watcher_status` silently.
|
|
363
|
+
2. Run the orientation queries silently.
|
|
364
|
+
3. Only report if something changed (service down, point count dropped significantly, new domains appeared).
|
|
365
|
+
|
|
366
|
+
**Key principle:** The agent drives discovery. The user shouldn't have to explain their archive to you — the archive explains itself through its config.
|
|
367
|
+
|
|
368
|
+
## Tools
|
|
183
369
|
|
|
184
370
|
### `watcher_search`
|
|
185
371
|
Semantic search over indexed documents.
|
|
@@ -227,6 +413,8 @@ Get runtime embedding failures. Returns `{ filePath: IssueRecord }` showing file
|
|
|
227
413
|
|
|
228
414
|
Filters use Qdrant's native JSON filter format, passed as the `filter` parameter to `watcher_search`.
|
|
229
415
|
|
|
416
|
+
> **Note:** The field names and values used in these examples (e.g., `domain`, `status`, `assignee`) are illustrative. Actual fields depend on the deployment's inference rules. Use the Orientation Pattern and Query Planning sections to discover what's available before constructing filters.
|
|
417
|
+
|
|
230
418
|
### Basic Patterns
|
|
231
419
|
|
|
232
420
|
**Match exact value:**
|
|
@@ -442,7 +630,7 @@ Compose individual field conditions into complex queries using three combinators
|
|
|
442
630
|
{ "key": "created", "range": { "gte": 1735689600 } }
|
|
443
631
|
],
|
|
444
632
|
"should": [
|
|
445
|
-
{ "key": "assignee", "match": { "value": "
|
|
633
|
+
{ "key": "assignee", "match": { "value": "Jane Doe" } },
|
|
446
634
|
{ "key": "assignee", "match": { "value": null } }
|
|
447
635
|
],
|
|
448
636
|
"must_not": [
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
* CLI for installing/uninstalling the jeeves-watcher OpenClaw plugin.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* npx \@karmaniverous/jeeves-watcher-openclaw install
|
|
5
|
+
* npx \@karmaniverous/jeeves-watcher-openclaw install
|
|
6
6
|
* npx \@karmaniverous/jeeves-watcher-openclaw uninstall
|
|
7
7
|
*
|
|
8
|
-
* The --memory flag claims the OpenClaw memory slot, replacing memory-core
|
|
9
|
-
* with the plugin's Qdrant-backed memory_search and memory_get tools.
|
|
10
|
-
* Without --memory, only watcher admin tools are registered.
|
|
11
|
-
*
|
|
12
8
|
* Bypasses OpenClaw's `plugins install` command, which has a known
|
|
13
9
|
* spawn EINVAL bug on Windows (https://github.com/openclaw/openclaw/issues/9224).
|
|
14
10
|
*
|
|
@@ -17,10 +13,5 @@
|
|
|
17
13
|
* - OPENCLAW_HOME env var (path to .openclaw directory)
|
|
18
14
|
* - Default: ~/.openclaw/openclaw.json
|
|
19
15
|
*/
|
|
20
|
-
/** Options for patchConfig. */
|
|
21
|
-
export interface PatchConfigOptions {
|
|
22
|
-
/** Whether to claim the memory slot (--memory flag). */
|
|
23
|
-
memory?: boolean;
|
|
24
|
-
}
|
|
25
16
|
/** Patch OpenClaw config for install or uninstall. Returns log messages. */
|
|
26
|
-
export declare function patchConfig(config: Record<string, unknown>, mode: 'add' | 'remove'
|
|
17
|
+
export declare function patchConfig(config: Record<string, unknown>, mode: 'add' | 'remove'): string[];
|
package/dist/src/helpers.d.ts
CHANGED
|
@@ -10,14 +10,6 @@ export interface PluginApi {
|
|
|
10
10
|
config?: Record<string, unknown>;
|
|
11
11
|
}>;
|
|
12
12
|
};
|
|
13
|
-
agents?: {
|
|
14
|
-
entries?: Record<string, {
|
|
15
|
-
workspace?: string;
|
|
16
|
-
}>;
|
|
17
|
-
defaults?: {
|
|
18
|
-
workspace?: string;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
13
|
};
|
|
22
14
|
registerTool(tool: {
|
|
23
15
|
name: string;
|
|
@@ -36,28 +28,8 @@ export interface ToolResult {
|
|
|
36
28
|
}>;
|
|
37
29
|
isError?: boolean;
|
|
38
30
|
}
|
|
39
|
-
/** Source identifier for virtual rule registration. */
|
|
40
|
-
export declare const PLUGIN_SOURCE = "jeeves-watcher-openclaw";
|
|
41
|
-
/** Normalize a path to forward slashes and lowercase drive letter on Windows. */
|
|
42
|
-
export declare function normalizePath(p: string): string;
|
|
43
|
-
/**
|
|
44
|
-
* Resolve the workspace path from gateway config.
|
|
45
|
-
* Priority: agent-specific \> defaults \> fallback (~/.openclaw/workspace).
|
|
46
|
-
*/
|
|
47
|
-
export declare function getWorkspacePath(api: PluginApi): string;
|
|
48
31
|
/** Resolve the watcher API base URL from plugin config. */
|
|
49
32
|
export declare function getApiUrl(api: PluginApi): string;
|
|
50
|
-
/**
|
|
51
|
-
* Schema value type — matches watcher inference rule schema conventions.
|
|
52
|
-
* Can be an inline JSON Schema object, a file reference string,
|
|
53
|
-
* a named schema reference, or a composable array of these.
|
|
54
|
-
*/
|
|
55
|
-
export type SchemaValue = Record<string, unknown> | string | Array<Record<string, unknown> | string>;
|
|
56
|
-
/**
|
|
57
|
-
* Resolve user-supplied schemas from plugin config.
|
|
58
|
-
* Returns a map of rule name → schema array (always normalized to array).
|
|
59
|
-
*/
|
|
60
|
-
export declare function getPluginSchemas(api: PluginApi): Record<string, Array<Record<string, unknown> | string>>;
|
|
61
33
|
/** Format a successful tool result. */
|
|
62
34
|
export declare function ok(data: unknown): ToolResult;
|
|
63
35
|
/** Format an error tool result. */
|
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.4.1",
|
|
6
6
|
"skills": [
|
|
7
7
|
"dist/skills/jeeves-watcher"
|
|
8
8
|
],
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"apiUrl": {
|
|
14
14
|
"type": "string",
|
|
15
15
|
"description": "jeeves-watcher API base URL",
|
|
16
|
-
"default": "http://127.0.0.1:
|
|
16
|
+
"default": "http://127.0.0.1:1936"
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"uiHints": {
|
|
21
21
|
"apiUrl": {
|
|
22
22
|
"label": "Watcher API URL",
|
|
23
|
-
"placeholder": "http://127.0.0.1:
|
|
23
|
+
"placeholder": "http://127.0.0.1:1936"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
package/package.json
CHANGED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module plugin/memoryTools
|
|
3
|
-
* memory_search and memory_get tool implementations with lazy init.
|
|
4
|
-
*
|
|
5
|
-
* Lazy init registers virtual inference rules with the watcher on first
|
|
6
|
-
* memory_search call. Re-attempts on failure. memory_get reads files
|
|
7
|
-
* directly from the filesystem with path validation.
|
|
8
|
-
*/
|
|
9
|
-
import { type PluginApi, type ToolResult } from './helpers.js';
|
|
10
|
-
/** Private property keys used by the plugin for filtering. */
|
|
11
|
-
export declare const PROP_SOURCE = "_jeeves_watcher_openclaw_source_";
|
|
12
|
-
export declare const PROP_KIND = "_jeeves_watcher_openclaw_kind_";
|
|
13
|
-
/** Memory source value for filter queries. */
|
|
14
|
-
export declare const SOURCE_MEMORY = "memory";
|
|
15
|
-
/** Virtual rule names (exported for testing and config reference). */
|
|
16
|
-
export declare const RULE_LONGTERM = "openclaw-memory-longterm";
|
|
17
|
-
export declare const RULE_DAILY = "openclaw-memory-daily";
|
|
18
|
-
/**
|
|
19
|
-
* Create memory tool registrations for the plugin.
|
|
20
|
-
* Returns register functions for memory_search and memory_get.
|
|
21
|
-
*/
|
|
22
|
-
export declare function createMemoryTools(api: PluginApi, baseUrl: string): {
|
|
23
|
-
memorySearch: (_id: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
24
|
-
memoryGet: (_id: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
25
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|