@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 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 [--memory]
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, options = {}) {
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(memoryMode) {
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', { memory: memoryMode })) {
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(memoryFlag);
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 [--memory] Install plugin');
296
- console.log(' npx @karmaniverous/jeeves-watcher-openclaw uninstall Remove plugin');
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 (email, Slack, Jira, codebase, meetings, projects), enrich
7
- document metadata, manage watcher config, or diagnose indexing issues.
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: 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.
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 a project and need background.** Architecture decisions, prior discussions, related tickets, relevant code. Search by topic, filter by domain.
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 a Jira ticket, a Slack thread, an email, and a code commit. A single semantic search surfaces all of them.
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 search tools with different scopes:
77
+ You have two complementary tools with different scopes:
76
78
 
77
- - **`memory_search`** — curated notes (MEMORY.md + memory/*.md). High signal, small scope. This is your long-term memory: decisions, rules, people, project context. Always check here first.
78
- - **`watcher_search`** — the full indexed archive (160K+ points across all domains: Slack, email, Jira, code, meetings, tweets, projects). Broad scope, raw record.
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 Noodle" → memory says "male, he/him" → that's thin → escalate to `watcher_search("Noodle")` → tweets reveal #NoodleThePoodle with photos. Report the full picture.
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
- **The principle:** Memory is your curated highlights. The 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.
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
- **How it works, conceptually:**
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
- 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.
121
+ ## Installation
100
122
 
101
- You don't need to know the rules in advance. The config is introspectable at runtime. Orient once per session, then search with confidence.
123
+ ```
124
+ npx @karmaniverous/jeeves-watcher-openclaw install
125
+ ```
102
126
 
103
- **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.
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": "Jason Williscroft" } },
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 [--memory]
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', options?: PatchConfigOptions): string[];
17
+ export declare function patchConfig(config: Record<string, unknown>, mode: 'add' | 'remove'): string[];
@@ -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. */
@@ -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.3.13",
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:3458"
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:3458"
23
+ "placeholder": "http://127.0.0.1:1936"
24
24
  }
25
25
  }
26
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-watcher-openclaw",
3
- "version": "0.3.13",
3
+ "version": "0.4.0",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "OpenClaw plugin for jeeves-watcher — semantic search and metadata enrichment tools",
6
6
  "license": "BSD-3-Clause",
@@ -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 {};