@karmaniverous/jeeves-watcher-openclaw 0.3.7 → 0.3.9

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,9 +8,13 @@ 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 [--memory]
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
+ *
14
18
  * Bypasses OpenClaw's `plugins install` command, which has a known
15
19
  * spawn EINVAL bug on Windows (https://github.com/openclaw/openclaw/issues/9224).
16
20
  *
@@ -79,7 +83,7 @@ function patchAllowList(parent, key, label, mode) {
79
83
  return undefined;
80
84
  }
81
85
  /** Patch OpenClaw config for install or uninstall. Returns log messages. */
82
- function patchConfig(config, mode) {
86
+ function patchConfig(config, mode, options = {}) {
83
87
  const messages = [];
84
88
  // Ensure plugins section
85
89
  if (!config.plugins || typeof config.plugins !== 'object') {
@@ -110,13 +114,20 @@ function patchConfig(config, mode) {
110
114
  plugins.slots = {};
111
115
  }
112
116
  const slots = plugins.slots;
113
- if (mode === 'add') {
117
+ if (mode === 'add' && options.memory) {
114
118
  const prev = slots.memory;
115
119
  slots.memory = PLUGIN_ID;
116
120
  if (prev !== PLUGIN_ID) {
117
121
  messages.push(`Set plugins.slots.memory to "${PLUGIN_ID}"${prev ? ` (was "${prev}")` : ''}`);
118
122
  }
119
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
+ }
120
131
  else if (slots.memory === PLUGIN_ID) {
121
132
  slots.memory = 'memory-core';
122
133
  messages.push(`Reverted plugins.slots.memory to "memory-core"`);
@@ -129,7 +140,7 @@ function patchConfig(config, mode) {
129
140
  return messages;
130
141
  }
131
142
  /** Install the plugin into OpenClaw's extensions directory. */
132
- function install() {
143
+ function install(memoryMode) {
133
144
  const home = resolveOpenClawHome();
134
145
  const configPath = resolveConfigPath(home);
135
146
  const extDir = join(home, 'extensions', PLUGIN_ID);
@@ -138,6 +149,7 @@ function install() {
138
149
  console.log(`Config: ${configPath}`);
139
150
  console.log(`Extensions dir: ${extDir}`);
140
151
  console.log(`Package root: ${pkgRoot}`);
152
+ console.log(`Memory mode: ${memoryMode ? 'yes (claiming memory slot)' : 'no (watcher tools only)'}`);
141
153
  console.log();
142
154
  if (!existsSync(home)) {
143
155
  console.error(`Error: OpenClaw home directory not found at ${home}`);
@@ -173,6 +185,20 @@ function install() {
173
185
  cpSync(nodeModulesSrc, join(extDir, 'node_modules'), { recursive: true });
174
186
  console.log(' ✓ node_modules');
175
187
  }
188
+ // Patch manifest based on memory mode
189
+ const installedManifestPath = join(extDir, 'openclaw.plugin.json');
190
+ const manifest = readJson(installedManifestPath);
191
+ if (manifest) {
192
+ if (memoryMode) {
193
+ manifest.kind = 'memory';
194
+ console.log(' ✓ Set kind: "memory" in manifest');
195
+ }
196
+ else {
197
+ delete manifest.kind;
198
+ console.log(' ✓ Removed kind from manifest (non-memory mode)');
199
+ }
200
+ writeJson(installedManifestPath, manifest);
201
+ }
176
202
  // Patch config
177
203
  console.log();
178
204
  console.log('Patching OpenClaw config...');
@@ -181,13 +207,19 @@ function install() {
181
207
  console.error(`Error: Could not parse ${configPath}`);
182
208
  process.exit(1);
183
209
  }
184
- for (const msg of patchConfig(config, 'add')) {
210
+ for (const msg of patchConfig(config, 'add', { memory: memoryMode })) {
185
211
  console.log(` ✓ ${msg}`);
186
212
  }
187
213
  writeJson(configPath, config);
188
214
  console.log();
189
215
  console.log('✅ Plugin installed successfully.');
190
216
  console.log(' Restart the OpenClaw gateway to load the plugin.');
217
+ if (memoryMode) {
218
+ console.log(' Memory slot claimed — memory_search and memory_get tools will be available.');
219
+ }
220
+ else {
221
+ console.log(' Watcher tools available. Run with --memory after bootstrapping to enable memory features.');
222
+ }
191
223
  }
192
224
  /** Uninstall the plugin from OpenClaw's extensions directory. */
193
225
  function uninstall() {
@@ -221,9 +253,10 @@ function uninstall() {
221
253
  }
222
254
  // Main
223
255
  const command = process.argv[2];
256
+ const memoryFlag = process.argv.includes('--memory');
224
257
  switch (command) {
225
258
  case 'install':
226
- install();
259
+ install(memoryFlag);
227
260
  break;
228
261
  case 'uninstall':
229
262
  uninstall();
@@ -232,8 +265,11 @@ switch (command) {
232
265
  console.log(`@karmaniverous/jeeves-watcher-openclaw — OpenClaw plugin installer`);
233
266
  console.log();
234
267
  console.log('Usage:');
235
- console.log(' npx @karmaniverous/jeeves-watcher-openclaw install Install plugin');
236
- console.log(' npx @karmaniverous/jeeves-watcher-openclaw uninstall Remove plugin');
268
+ console.log(' npx @karmaniverous/jeeves-watcher-openclaw install [--memory] Install plugin');
269
+ console.log(' npx @karmaniverous/jeeves-watcher-openclaw uninstall Remove plugin');
270
+ console.log();
271
+ console.log('Options:');
272
+ console.log(' --memory Claim memory slot (replaces memory-core with Qdrant-backed search)');
237
273
  console.log();
238
274
  console.log('Environment variables:');
239
275
  console.log(' OPENCLAW_CONFIG Path to openclaw.json (overrides all)');
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ function normalizePath(p) {
15
15
  }
16
16
  /**
17
17
  * Resolve the workspace path from gateway config.
18
- * Priority: agent-specific > defaults > fallback (~/.openclaw/workspace).
18
+ * Priority: agent-specific \> defaults \> fallback (~/.openclaw/workspace).
19
19
  */
20
20
  function getWorkspacePath(api) {
21
21
  const agentWorkspace = api.config?.agents?.entries?.['main']?.workspace ??
@@ -27,6 +27,26 @@ function getApiUrl(api) {
27
27
  const url = api.config?.plugins?.entries?.['jeeves-watcher-openclaw']?.config?.apiUrl;
28
28
  return typeof url === 'string' ? url : DEFAULT_API_URL;
29
29
  }
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
+ }
30
50
  /** Format a successful tool result. */
31
51
  function ok(data) {
32
52
  return {
@@ -90,12 +110,42 @@ async function postJson(url, body) {
90
110
  * memory_search call. Re-attempts on failure. memory_get reads files
91
111
  * directly from the filesystem with path validation.
92
112
  */
93
- /** Build virtual inference rules for a workspace path. */
94
- function buildVirtualRules(workspace) {
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) {
95
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
+ };
96
146
  return [
97
147
  {
98
- name: 'openclaw-memory-longterm',
148
+ name: RULE_LONGTERM,
99
149
  description: 'OpenClaw long-term memory file',
100
150
  match: {
101
151
  properties: {
@@ -106,22 +156,10 @@ function buildVirtualRules(workspace) {
106
156
  },
107
157
  },
108
158
  },
109
- schema: [
110
- {
111
- type: 'object',
112
- properties: {
113
- domains: {
114
- type: 'array',
115
- items: { type: 'string' },
116
- set: ['memory'],
117
- },
118
- kind: { type: 'string', set: 'long-term' },
119
- },
120
- },
121
- ],
159
+ schema: [longtermInternal, ...(userSchemas[RULE_LONGTERM] ?? [])],
122
160
  },
123
161
  {
124
- name: 'openclaw-memory-daily',
162
+ name: RULE_DAILY,
125
163
  description: 'OpenClaw daily memory logs',
126
164
  match: {
127
165
  properties: {
@@ -132,19 +170,7 @@ function buildVirtualRules(workspace) {
132
170
  },
133
171
  },
134
172
  },
135
- schema: [
136
- {
137
- type: 'object',
138
- properties: {
139
- domains: {
140
- type: 'array',
141
- items: { type: 'string' },
142
- set: ['memory'],
143
- },
144
- kind: { type: 'string', set: 'daily-log' },
145
- },
146
- },
147
- ],
173
+ schema: [dailyInternal, ...(userSchemas[RULE_DAILY] ?? [])],
148
174
  },
149
175
  ];
150
176
  }
@@ -166,6 +192,7 @@ function isAllowedMemoryPath(filePath, workspace) {
166
192
  */
167
193
  function createMemoryTools(api, baseUrl) {
168
194
  const workspace = getWorkspacePath(api);
195
+ const userSchemas = getPluginSchemas(api);
169
196
  const state = {
170
197
  initialized: false,
171
198
  lastWatcherUptime: 0,
@@ -188,7 +215,7 @@ function createMemoryTools(api, baseUrl) {
188
215
  body: JSON.stringify({ source: PLUGIN_SOURCE }),
189
216
  });
190
217
  // Register virtual rules
191
- const virtualRules = buildVirtualRules(state.workspace);
218
+ const virtualRules = buildVirtualRules(state.workspace, userSchemas);
192
219
  await postJson(`${state.baseUrl}/rules/register`, {
193
220
  source: PLUGIN_SOURCE,
194
221
  rules: virtualRules,
@@ -216,7 +243,7 @@ function createMemoryTools(api, baseUrl) {
216
243
  const body = {
217
244
  query: params.query,
218
245
  filter: {
219
- must: [{ key: 'domains', match: { value: 'memory' } }],
246
+ must: [{ key: PROP_SOURCE, match: { value: SOURCE_MEMORY } }],
220
247
  },
221
248
  };
222
249
  if (params.maxResults !== undefined)
@@ -84,6 +84,52 @@ You don't need to know the rules in advance. The config is introspectable at run
84
84
  2. **Search** — use `watcher_search` with a natural language query and optional metadata filters
85
85
  3. **Read source** — use `read` (standard file read) with `file_path` from search results for full document content
86
86
 
87
+ ## Bootstrap (First Session)
88
+
89
+ The first time the watcher plugin loads in a new deployment, orient yourself proactively. Don't wait for the user to ask a question — understand what you have access to.
90
+
91
+ **Automatic bootstrap sequence:**
92
+
93
+ 1. **Health check** — call `watcher_status`. Confirm the service is running, note point count and collection dimensions.
94
+ 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.
95
+ 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.
96
+ 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.
97
+
98
+ **On subsequent sessions:** Repeat steps 1-3 silently. Only report if something changed (service down, point count dropped significantly, new domains appeared).
99
+
100
+ **Key principle:** The agent drives discovery. The user shouldn't have to explain their archive to you — the archive explains itself through its config.
101
+
102
+ ## Install Modes
103
+
104
+ The plugin supports two install modes, controlled by the `--memory` flag during installation:
105
+
106
+ **Standard mode** (`npx @karmaniverous/jeeves-watcher-openclaw install`):
107
+ - Registers `watcher_*` tools only (search, enrich, status, query, validate, config_apply, reindex, issues)
108
+ - OpenClaw's built-in `memory-core` remains the memory provider
109
+ - Use when the watcher is a supplementary search tool alongside native memory
110
+
111
+ **Memory mode** (`npx @karmaniverous/jeeves-watcher-openclaw install --memory`):
112
+ - Registers all tools: `watcher_*` plus `memory_search` and `memory_get`
113
+ - Claims the OpenClaw memory slot — the watcher becomes the memory provider
114
+ - MEMORY.md and memory/*.md files are indexed and searched via the watcher's vector store
115
+ - Use when the watcher should be the single source of truth for both archive search and agent memory
116
+
117
+ **How to tell which mode you're in:** If `memory_search` and `memory_get` are in your available tools, you're in memory mode. If you only see `watcher_*` tools, you're in standard mode.
118
+
119
+ ## Proactive Posture
120
+
121
+ **If you're in standard mode and the deployment would benefit from memory mode, say so.** The watcher's vector store with 3072-dimension Gemini embeddings provides significantly better semantic memory than flat file search. If you notice:
122
+
123
+ - The user has a substantial archive (>10K points)
124
+ - Memory searches are returning poor results via native memory
125
+ - The user is asking questions that span multiple memory files
126
+
127
+ ...then suggest the upgrade:
128
+
129
+ > "The watcher is running with a large archive but I'm using basic file memory. Upgrading to memory mode would give semantic search over your memory files too. To upgrade: `npx @karmaniverous/jeeves-watcher-openclaw install --memory` and restart the gateway."
130
+
131
+ **Don't nag.** Suggest once per session at most, and only when there's a concrete benefit. If the user declines or doesn't respond, drop it.
132
+
87
133
  ## Tools
88
134
 
89
135
  ### `memory_search`
package/dist/src/cli.d.ts CHANGED
@@ -2,9 +2,13 @@
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 [--memory]
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
+ *
8
12
  * Bypasses OpenClaw's `plugins install` command, which has a known
9
13
  * spawn EINVAL bug on Windows (https://github.com/openclaw/openclaw/issues/9224).
10
14
  *
@@ -13,5 +17,10 @@
13
17
  * - OPENCLAW_HOME env var (path to .openclaw directory)
14
18
  * - Default: ~/.openclaw/openclaw.json
15
19
  */
20
+ /** Options for patchConfig. */
21
+ export interface PatchConfigOptions {
22
+ /** Whether to claim the memory slot (--memory flag). */
23
+ memory?: boolean;
24
+ }
16
25
  /** Patch OpenClaw config for install or uninstall. Returns log messages. */
17
- export declare function patchConfig(config: Record<string, unknown>, mode: 'add' | 'remove'): string[];
26
+ export declare function patchConfig(config: Record<string, unknown>, mode: 'add' | 'remove', options?: PatchConfigOptions): string[];
@@ -42,11 +42,22 @@ export declare const PLUGIN_SOURCE = "jeeves-watcher-openclaw";
42
42
  export declare function normalizePath(p: string): string;
43
43
  /**
44
44
  * Resolve the workspace path from gateway config.
45
- * Priority: agent-specific > defaults > fallback (~/.openclaw/workspace).
45
+ * Priority: agent-specific \> defaults \> fallback (~/.openclaw/workspace).
46
46
  */
47
47
  export declare function getWorkspacePath(api: PluginApi): string;
48
48
  /** Resolve the watcher API base URL from plugin config. */
49
49
  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>>;
50
61
  /** Format a successful tool result. */
51
62
  export declare function ok(data: unknown): ToolResult;
52
63
  /** Format an error tool result. */
@@ -7,6 +7,14 @@
7
7
  * directly from the filesystem with path validation.
8
8
  */
9
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";
10
18
  /**
11
19
  * Create memory tool registrations for the plugin.
12
20
  * Returns register functions for memory_search and memory_get.
@@ -2,8 +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.7",
6
- "kind": "memory",
5
+ "version": "0.3.9",
7
6
  "skills": [
8
7
  "dist/skills/jeeves-watcher"
9
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-watcher-openclaw",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
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",