@karmaniverous/jeeves-watcher-openclaw 0.3.13 → 0.4.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/dist/cli.js +7 -86
- package/dist/index.js +0 -283
- package/dist/skills/jeeves-watcher/SKILL.md +47 -72
- 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,17 +92,44 @@ 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:
|
|
94
102
|
|
|
95
|
-
**
|
|
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).
|
|
96
118
|
|
|
97
|
-
|
|
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.
|
|
98
120
|
|
|
99
|
-
|
|
121
|
+
## Installation
|
|
100
122
|
|
|
101
|
-
|
|
123
|
+
```
|
|
124
|
+
npx @karmaniverous/jeeves-watcher-openclaw install
|
|
125
|
+
```
|
|
102
126
|
|
|
103
|
-
|
|
127
|
+
This copies the plugin to OpenClaw's extensions directory and patches `openclaw.json` to register it. Restart the gateway to load the plugin.
|
|
128
|
+
|
|
129
|
+
To remove:
|
|
130
|
+
```
|
|
131
|
+
npx @karmaniverous/jeeves-watcher-openclaw uninstall
|
|
132
|
+
```
|
|
104
133
|
|
|
105
134
|
## Quick Start
|
|
106
135
|
|
|
@@ -123,64 +152,8 @@ The first time the watcher plugin loads in a new deployment, orient yourself pro
|
|
|
123
152
|
|
|
124
153
|
**Key principle:** The agent drives discovery. The user shouldn't have to explain their archive to you — the archive explains itself through its config.
|
|
125
154
|
|
|
126
|
-
## Install Modes
|
|
127
|
-
|
|
128
|
-
The plugin supports two install modes, controlled by the `--memory` flag during installation:
|
|
129
|
-
|
|
130
|
-
**Standard mode** (`npx @karmaniverous/jeeves-watcher-openclaw install`):
|
|
131
|
-
- Registers `watcher_*` tools only (search, enrich, status, query, validate, config_apply, reindex, issues)
|
|
132
|
-
- OpenClaw's built-in `memory-core` remains the memory provider (re-enabled if previously disabled)
|
|
133
|
-
- Use when the watcher is a supplementary search tool alongside native memory
|
|
134
|
-
|
|
135
|
-
**Memory mode** (`npx @karmaniverous/jeeves-watcher-openclaw install --memory`):
|
|
136
|
-
- Registers all tools: `watcher_*` plus `memory_search` and `memory_get`
|
|
137
|
-
- Claims the OpenClaw memory slot — the watcher becomes the memory provider
|
|
138
|
-
- Disables `memory-core` (`plugins.entries.memory-core.enabled: false`) to prevent core memory tools from shadowing the plugin's tools
|
|
139
|
-
- MEMORY.md and memory/*.md files are indexed and searched via the watcher's vector store
|
|
140
|
-
- Use when the watcher should be the single source of truth for both archive search and agent memory
|
|
141
|
-
|
|
142
|
-
**Why `memory-core` must be disabled:** OpenClaw's built-in `memory-core` plugin registers its own `memory_search` and `memory_get` tools. If both `memory-core` and this plugin are enabled, the core tools shadow the plugin's tools (same name, core registers first). Disabling `memory-core` ensures the plugin's Qdrant-backed implementations are the ones that execute.
|
|
143
|
-
|
|
144
|
-
**How to tell which mode you're in:** Check the response from `memory_search` or `memory_get`. In memory mode (watcher-backed), responses include `"provider": "jeeves-watcher"`. In standard mode (memory-core), responses are bare arrays/strings with no `provider` field.
|
|
145
|
-
|
|
146
|
-
## Proactive Posture
|
|
147
|
-
|
|
148
|
-
**On your first `memory_search` or `memory_get` call each session, check the response for the `provider` field.**
|
|
149
|
-
|
|
150
|
-
- **If the response is `{ provider: "jeeves-watcher", ... }`:** You're in memory mode. The watcher is serving memory. No action needed.
|
|
151
|
-
- **If the response has any other shape** (bare array, bare string, different provider, no `provider` field): The watcher is NOT serving memory. Another provider is active. Check whether an upgrade is warranted.
|
|
152
|
-
|
|
153
|
-
**When to suggest upgrading to memory mode:**
|
|
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:
|
|
155
|
-
|
|
156
|
-
> "I notice memory queries are running on flat file search, but the watcher has [N] points with semantic embeddings. I can upgrade to memory mode now — this runs `npx @karmaniverous/jeeves-watcher-openclaw install --memory` and restarts the gateway. Shall I go ahead?"
|
|
157
|
-
|
|
158
|
-
If the user agrees, execute the install and gateway restart. Don't hand them a CLI command when you can do it yourself.
|
|
159
|
-
|
|
160
|
-
**Don't nag.** Suggest once per session at most. If the user declines, drop it.
|
|
161
|
-
|
|
162
155
|
## Tools
|
|
163
156
|
|
|
164
|
-
### `memory_search`
|
|
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
|
|
169
|
-
|
|
170
|
-
**Response (memory mode):** `{ provider: "jeeves-watcher", results: [{ path, from, to, snippet, score }] }` where `from`/`to` are 1-indexed line numbers.
|
|
171
|
-
**Response (standard mode / memory-core):** `[{ path, from, to, snippet, score }]` — bare array, no `provider` field.
|
|
172
|
-
|
|
173
|
-
### `memory_get`
|
|
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
|
|
178
|
-
|
|
179
|
-
**Response (memory mode):** `{ provider: "jeeves-watcher", content: "file content..." }`
|
|
180
|
-
**Response (standard mode / memory-core):** `"file content..."` — bare string, no `provider` field.
|
|
181
|
-
|
|
182
|
-
Path validation: only files within the workspace's MEMORY.md and memory/**/*.md are accessible.
|
|
183
|
-
|
|
184
157
|
### `watcher_search`
|
|
185
158
|
Semantic search over indexed documents.
|
|
186
159
|
- `query` (string, required) — natural language search query
|
|
@@ -227,6 +200,8 @@ Get runtime embedding failures. Returns `{ filePath: IssueRecord }` showing file
|
|
|
227
200
|
|
|
228
201
|
Filters use Qdrant's native JSON filter format, passed as the `filter` parameter to `watcher_search`.
|
|
229
202
|
|
|
203
|
+
> **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.
|
|
204
|
+
|
|
230
205
|
### Basic Patterns
|
|
231
206
|
|
|
232
207
|
**Match exact value:**
|
|
@@ -442,7 +417,7 @@ Compose individual field conditions into complex queries using three combinators
|
|
|
442
417
|
{ "key": "created", "range": { "gte": 1735689600 } }
|
|
443
418
|
],
|
|
444
419
|
"should": [
|
|
445
|
-
{ "key": "assignee", "match": { "value": "
|
|
420
|
+
{ "key": "assignee", "match": { "value": "Jane Doe" } },
|
|
446
421
|
{ "key": "assignee", "match": { "value": null } }
|
|
447
422
|
],
|
|
448
423
|
"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.0",
|
|
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 {};
|