@karmaniverous/jeeves-watcher-openclaw 0.4.1 → 0.5.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/index.js CHANGED
@@ -3,11 +3,21 @@
3
3
  * Shared types and utility functions for the OpenClaw plugin tool registrations.
4
4
  */
5
5
  const DEFAULT_API_URL = 'http://127.0.0.1:1936';
6
+ const DEFAULT_CACHE_TTL_MS = 30000;
7
+ /** Extract plugin config from the API object */
8
+ function getPluginConfig(api) {
9
+ return api.config?.plugins?.entries?.['jeeves-watcher-openclaw']?.config;
10
+ }
6
11
  /** Resolve the watcher API base URL from plugin config. */
7
12
  function getApiUrl(api) {
8
- const url = api.config?.plugins?.entries?.['jeeves-watcher-openclaw']?.config?.apiUrl;
13
+ const url = getPluginConfig(api)?.apiUrl;
9
14
  return typeof url === 'string' ? url : DEFAULT_API_URL;
10
15
  }
16
+ /** Resolve the cache TTL for plugin hooks from config. */
17
+ function getCacheTtlMs(api) {
18
+ const ttl = getPluginConfig(api)?.cacheTtlMs;
19
+ return typeof ttl === 'number' ? ttl : DEFAULT_CACHE_TTL_MS;
20
+ }
11
21
  /** Format a successful tool result. */
12
22
  function ok(data) {
13
23
  return {
@@ -18,7 +28,7 @@ function ok(data) {
18
28
  function fail(error) {
19
29
  const message = error instanceof Error ? error.message : String(error);
20
30
  return {
21
- content: [{ type: 'text', text: `Error: ${message}` }],
31
+ content: [{ type: 'text', text: 'Error: ' + message }],
22
32
  isError: true,
23
33
  };
24
34
  }
@@ -35,7 +45,7 @@ function connectionFail(error, baseUrl) {
35
45
  {
36
46
  type: 'text',
37
47
  text: [
38
- `Watcher service not reachable at ${baseUrl}.`,
48
+ 'Watcher service not reachable at ' + baseUrl + '.',
39
49
  'Either start the watcher service, or if it runs on a different port,',
40
50
  'set plugins.entries.jeeves-watcher-openclaw.config.apiUrl in openclaw.json.',
41
51
  ].join('\n'),
@@ -47,22 +57,162 @@ function connectionFail(error, baseUrl) {
47
57
  return fail(error);
48
58
  }
49
59
  /** Fetch JSON from a URL, throwing on non-OK responses. */
50
- async function fetchJson(url, init) {
60
+ async function fetchJson$1(url, init) {
51
61
  const res = await fetch(url, init);
52
62
  if (!res.ok) {
53
- throw new Error(`HTTP ${String(res.status)}: ${await res.text()}`);
63
+ throw new Error('HTTP ' + String(res.status) + ': ' + (await res.text()));
54
64
  }
55
65
  return res.json();
56
66
  }
57
67
  /** POST JSON to a URL and return parsed response. */
58
68
  async function postJson(url, body) {
59
- return fetchJson(url, {
69
+ return fetchJson$1(url, {
60
70
  method: 'POST',
61
71
  headers: { 'Content-Type': 'application/json' },
62
72
  body: JSON.stringify(body),
63
73
  });
64
74
  }
65
75
 
76
+ let menuCache = null;
77
+ function isAgentBootstrapEventContext(value) {
78
+ if (!value || typeof value !== 'object') {
79
+ return false;
80
+ }
81
+ const v = value;
82
+ return Array.isArray(v.bootstrapFiles);
83
+ }
84
+ async function fetchJson(url, init) {
85
+ const res = await fetch(url, init);
86
+ if (!res.ok) {
87
+ throw new Error(`HTTP ${String(res.status)}: ${await res.text()}`);
88
+ }
89
+ return res.json();
90
+ }
91
+ /**
92
+ * Fetches data from the watcher API and generates a Markdown menu string.
93
+ * The string is platform-agnostic and safe to inject into TOOLS.md.
94
+ */
95
+ async function generateWatcherMenu(apiUrl) {
96
+ let pointCount = 0;
97
+ const activeRules = [];
98
+ const watchPaths = [];
99
+ try {
100
+ const [statusRes, rulesRes, pathsRes] = await Promise.all([
101
+ fetchJson(`${apiUrl}/status`),
102
+ fetchJson(`${apiUrl}/config/query`, {
103
+ method: 'POST',
104
+ headers: { 'Content-Type': 'application/json' },
105
+ body: JSON.stringify({ path: '$.inferenceRules[*]' }),
106
+ }),
107
+ fetchJson(`${apiUrl}/config/query`, {
108
+ method: 'POST',
109
+ headers: { 'Content-Type': 'application/json' },
110
+ body: JSON.stringify({ path: '$.watch.paths[*]' }),
111
+ }),
112
+ ]);
113
+ pointCount = statusRes.collection?.pointCount ?? 0;
114
+ if (Array.isArray(rulesRes.result)) {
115
+ for (const rule of rulesRes.result) {
116
+ if (rule.name && rule.description) {
117
+ activeRules.push({ name: rule.name, description: rule.description });
118
+ }
119
+ }
120
+ }
121
+ if (Array.isArray(pathsRes.result)) {
122
+ for (const p of pathsRes.result) {
123
+ if (typeof p === 'string') {
124
+ watchPaths.push(p);
125
+ }
126
+ }
127
+ }
128
+ }
129
+ catch {
130
+ return '*Watcher service is currently unreachable.*';
131
+ }
132
+ const lines = [
133
+ `This environment includes a semantic search index (\`watcher_search\`) covering ${pointCount.toLocaleString()} document chunks.`,
134
+ '**Escalation Rule:** Use `memory_search` for personal operational notes, decisions, and rules. Escalate to `watcher_search` when memory is thin, or when searching the broader archive (tickets, docs, code). ALWAYS use `watcher_search` BEFORE filesystem commands (exec, grep) when looking for information that matches the indexed categories below.',
135
+ '',
136
+ '### Score Interpretation:',
137
+ '* **Strong:** >= 0.75',
138
+ '* **Relevant:** >= 0.50',
139
+ '* **Noise:** < 0.25',
140
+ '',
141
+ "### What's on the menu:",
142
+ ];
143
+ if (activeRules.length) {
144
+ for (const rule of activeRules) {
145
+ lines.push(`* **${rule.name}**: ${rule.description}`);
146
+ }
147
+ }
148
+ else {
149
+ lines.push('* (No inference rules configured)');
150
+ }
151
+ lines.push('', '### Indexed paths:');
152
+ if (watchPaths.length) {
153
+ for (const p of watchPaths) {
154
+ lines.push(`* \`${p}\``);
155
+ }
156
+ }
157
+ else {
158
+ lines.push('* (No watch paths configured)');
159
+ }
160
+ return lines.join('\n');
161
+ }
162
+ async function getCachedWatcherMenu(apiUrl, ttlMs) {
163
+ const now = Date.now();
164
+ if (menuCache && menuCache.expiresAt > now) {
165
+ return menuCache.value;
166
+ }
167
+ const menu = await generateWatcherMenu(apiUrl);
168
+ menuCache = { value: menu, expiresAt: now + ttlMs };
169
+ return menu;
170
+ }
171
+ function ensurePlatformToolsSection(toolsMd) {
172
+ if (toolsMd.includes('# Jeeves Platform Tools')) {
173
+ return toolsMd;
174
+ }
175
+ return `# Jeeves Platform Tools\n\n${toolsMd}`;
176
+ }
177
+ function upsertWatcherSection(toolsMd, watcherMenu) {
178
+ const section = `## Watcher\n\n${watcherMenu}\n`;
179
+ // If we already injected a Watcher section, replace it.
180
+ const re = /^## Watcher\n[\s\S]*?(?=^##\s|$)/m;
181
+ if (re.test(toolsMd)) {
182
+ return toolsMd.replace(re, section);
183
+ }
184
+ // Otherwise insert immediately after the H1 if present, else append.
185
+ const h1 = '# Jeeves Platform Tools';
186
+ const idx = toolsMd.indexOf(h1);
187
+ if (idx !== -1) {
188
+ const afterH1 = idx + h1.length;
189
+ return (toolsMd.slice(0, afterH1) + `\n\n${section}` + toolsMd.slice(afterH1));
190
+ }
191
+ return `${toolsMd}\n\n${section}`;
192
+ }
193
+ /**
194
+ * Hook handler for agent:bootstrap.
195
+ * Injects/updates the Watcher Menu into the TOOLS.md payload.
196
+ */
197
+ async function handleAgentBootstrap(event, api) {
198
+ const context = event?.context;
199
+ if (!isAgentBootstrapEventContext(context)) {
200
+ return;
201
+ }
202
+ const apiUrl = getApiUrl(api);
203
+ const cacheTtlMs = getCacheTtlMs(api);
204
+ const watcherMenu = await getCachedWatcherMenu(apiUrl, cacheTtlMs);
205
+ let toolsFile = context.bootstrapFiles.find((f) => f.name === 'TOOLS.md');
206
+ if (!toolsFile) {
207
+ toolsFile = { name: 'TOOLS.md', content: '', missing: false };
208
+ context.bootstrapFiles.push(toolsFile);
209
+ }
210
+ const current = toolsFile.content ?? '';
211
+ const withH1 = ensurePlatformToolsSection(current);
212
+ const updated = upsertWatcherSection(withH1, watcherMenu);
213
+ toolsFile.content = updated;
214
+ }
215
+
66
216
  /**
67
217
  * @module plugin/watcherTools
68
218
  * Watcher tool registrations (watcher_* tools) for the OpenClaw plugin.
@@ -79,7 +229,7 @@ function registerApiTool(api, baseUrl, config) {
79
229
  const url = `${baseUrl}${endpoint}`;
80
230
  const data = body !== undefined
81
231
  ? await postJson(url, body)
82
- : await fetchJson(url);
232
+ : await fetchJson$1(url);
83
233
  return ok(data);
84
234
  }
85
235
  catch (error) {
@@ -249,6 +399,13 @@ function registerWatcherTools(api, baseUrl) {
249
399
  function register(api) {
250
400
  const baseUrl = getApiUrl(api);
251
401
  registerWatcherTools(api, baseUrl);
402
+ // Register the agent:bootstrap hook if the host OpenClaw version supports it
403
+ const registerHook = api.registerInternalHook;
404
+ if (typeof registerHook === 'function') {
405
+ registerHook('agent:bootstrap', async (event) => {
406
+ await handleAgentBootstrap(event, api);
407
+ });
408
+ }
252
409
  }
253
410
 
254
411
  export { register as default };
@@ -56,8 +56,6 @@ curl -X POST http://127.0.0.1:<PORT>/config/query \
56
56
 
57
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
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.
60
-
61
59
  **When to reach for the watcher:**
62
60
 
63
61
  - **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.
@@ -72,52 +70,8 @@ You have access to a **semantic archive** of your human's working world. Documen
72
70
  - The question is about general knowledge, not the human's specific context
73
71
  - The watcher is unreachable (fall back to filesystem browsing)
74
72
 
75
- ## Memory → Archive Escalation
76
-
77
- You have two complementary tools with different scopes:
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.
81
-
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.
83
-
84
- **Triggers for escalation:**
85
- - Memory returns 0 results for a named entity (person, project, tool, pet)
86
- - Memory returns results but they lack the detail the question needs
87
- - The question is about something that *happened* (a conversation, a meeting, a decision) rather than something you *noted*
88
- - The human seems surprised you don't know something
89
-
90
- **Don't escalate when:**
91
- - Memory gave you a clear, sufficient answer
92
- - The question is about your own operational rules or preferences (that's purely memory)
93
- - You've already searched the archive this turn
94
-
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
73
  **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
74
 
99
- ## Embedding Alignment
100
-
101
- Memory-core and the watcher both use embeddings, but may use different models by default. For best cross-tool consistency, offer to configure memory-core to use `gemini-embedding-001` (the same model the watcher uses) via the gateway config:
102
-
103
- 1. **Verify Google API key** — check that the gateway has a Google API key configured (needed for Gemini embeddings).
104
- 2. **Apply config** — use `gateway config.patch` to set the memory-core embedding provider:
105
- ```json
106
- {
107
- "agents": {
108
- "defaults": {
109
- "memorySearch": {
110
- "provider": "gemini",
111
- "model": "gemini-embedding-001"
112
- }
113
- }
114
- }
115
- }
116
- ```
117
- 3. **Restart gateway** to pick up the new embedding provider. Memory-core will re-embed all memory files on next sync (dimension change triggers automatic vector table recreation).
118
-
119
- This gives memory-core the same 3072-dimensional Gemini embeddings the watcher uses, ensuring semantic similarity scores are comparable across memory and archive searches.
120
-
121
75
  ## Plugin Installation
122
76
 
123
77
  ```
@@ -135,9 +89,8 @@ npx @karmaniverous/jeeves-watcher-openclaw uninstall
135
89
 
136
90
  If the watcher service is already running and healthy:
137
91
 
138
- 1. **Orient yourself** (once per session) — use `watcher_query` to learn the deployment's organizational strategy and available record types (see Orientation Pattern below)
139
- 2. **Search** — use `watcher_search` with a natural language query and optional metadata filters
140
- 3. **Read source** — use `read` (standard file read) with `file_path` from search results for full document content
92
+ 1. **Search** — use `watcher_search` with a natural language query and optional metadata filters
93
+ 2. **Read source** — use `read` (standard file read) with `file_path` from search results for full document content
141
94
 
142
95
  ## Bootstrap (First-Time Setup)
143
96
 
@@ -165,14 +118,27 @@ If not running, install it. **Prefer native installation** (especially on cloud
165
118
 
166
119
  **Linux (recommended for servers):**
167
120
  ```bash
168
- # Download latest release
169
- curl -L https://github.com/qdrant/qdrant/releases/latest/download/qdrant-x86_64-unknown-linux-musl.tar.gz -o qdrant.tar.gz
170
- tar xzf qdrant.tar.gz
171
-
172
- # Run (foreground for testing)
173
- ./qdrant
121
+ # Download and install binary
122
+ curl -L https://github.com/qdrant/qdrant/releases/latest/download/qdrant-x86_64-unknown-linux-musl.tar.gz -o /tmp/qdrant.tar.gz
123
+ sudo tar xzf /tmp/qdrant.tar.gz -C /usr/local/bin/
124
+
125
+ # Create qdrant user and directories
126
+ sudo useradd -r -s /bin/false qdrant
127
+ sudo mkdir -p /var/lib/qdrant/storage /var/lib/qdrant/snapshots /etc/qdrant
128
+ sudo chown -R qdrant:qdrant /var/lib/qdrant
129
+
130
+ # Create config
131
+ sudo tee /etc/qdrant/config.yaml > /dev/null <<EOF
132
+ storage:
133
+ storage_path: /var/lib/qdrant/storage
134
+ snapshots_path: /var/lib/qdrant/snapshots
135
+ service:
136
+ host: 0.0.0.0
137
+ http_port: 6333
138
+ grpc_port: 6334
139
+ EOF
174
140
 
175
- # For production: create a systemd service
141
+ # Create systemd service
176
142
  sudo tee /etc/systemd/system/qdrant.service > /dev/null <<EOF
177
143
  [Unit]
178
144
  Description=Qdrant Vector Database
@@ -181,12 +147,14 @@ After=network.target
181
147
  [Service]
182
148
  Type=simple
183
149
  ExecStart=/usr/local/bin/qdrant --config-path /etc/qdrant/config.yaml
150
+ WorkingDirectory=/var/lib/qdrant
184
151
  Restart=always
185
152
  User=qdrant
186
153
 
187
154
  [Install]
188
155
  WantedBy=multi-user.target
189
156
  EOF
157
+ sudo systemctl daemon-reload
190
158
  sudo systemctl enable --now qdrant
191
159
  ```
192
160
 
@@ -310,6 +278,7 @@ After=network.target qdrant.service
310
278
  [Service]
311
279
  Type=simple
312
280
  ExecStart=$(which jeeves-watcher) start -c <config-path>
281
+ WorkingDirectory=%h
313
282
  Restart=always
314
283
  Environment=GOOGLE_API_KEY=<key>
315
284
  User=$USER
@@ -351,10 +320,6 @@ Once health is confirmed and initial indexing has started:
351
320
  2. Query `$.inferenceRules[*].['name','description']` for available record types.
352
321
  3. Report to the user: how many points indexed so far, which domains are available, estimated time to complete initial indexing (based on file count and embedding rate).
353
322
 
354
- ### Step 9: Align Memory-Core Embeddings
355
-
356
- After the watcher is healthy, offer to align OpenClaw's memory-core with the same embedding model for consistent vector quality across both systems. See the **Embedding Alignment** section above for the procedure.
357
-
358
323
  ### On Subsequent Sessions
359
324
 
360
325
  On sessions after bootstrap is complete:
@@ -413,8 +378,6 @@ Get runtime embedding failures. Returns `{ filePath: IssueRecord }` showing file
413
378
 
414
379
  Filters use Qdrant's native JSON filter format, passed as the `filter` parameter to `watcher_search`.
415
380
 
416
- > **Note:** The field names and values used in these examples (e.g., `domain`, `status`, `assignee`) are illustrative. Actual fields depend on the deployment's inference rules. Use the Orientation Pattern and Query Planning sections to discover what's available before constructing filters.
417
-
418
381
  ### Basic Patterns
419
382
 
420
383
  **Match exact value:**
@@ -483,94 +446,6 @@ Each result from `watcher_search` contains:
483
446
 
484
447
  Additional metadata fields depend on the deployment's inference rules (e.g., `domain`, `status`, `author`). Use `watcher_query` to discover available fields.
485
448
 
486
- ## Orientation Pattern (Once Per Session)
487
-
488
- Query the deployment's organizational context and available record types. This information is stable within a session; query once and rely on results for the remainder.
489
-
490
- **Efficient pattern (two calls):**
491
-
492
- 1. **Top-level context:**
493
- ```
494
- watcher_query: path="$.['description','search']"
495
- ```
496
- Returns:
497
- - `description` — organizational strategy (e.g., how domains are structured, what partitioning means)
498
- - `search.scoreThresholds` — score interpretation boundaries (strong, relevant, noise)
499
-
500
- 2. **Available record types:**
501
- ```
502
- watcher_query: path="$.inferenceRules[*].['name','description']"
503
- ```
504
- Returns list of inference rules with their names and descriptions.
505
-
506
- **Example result:**
507
- ```json
508
- [
509
- { "name": "email-archive", "description": "Email archive messages" },
510
- { "name": "slack-message", "description": "Slack channel messages with channel and author metadata" },
511
- { "name": "jira-issue", "description": "Jira issue metadata extracted from issue JSON exports" }
512
- ]
513
- ```
514
-
515
- The top-level `description` explains this deployment's organizational strategy. Each rule's `description` explains what that specific record type represents. Both levels are useful: one orients, the other enumerates.
516
-
517
- ---
518
-
519
- ## `resolve` Usage Guidance
520
-
521
- The `resolve` parameter controls which reference layers are expanded in `watcher_query`:
522
-
523
- - **No `resolve` (default):** Raw config structure with references intact (lightweight)
524
- - **`resolve: ["files"]`:** Resolve file path references to their contents (e.g., `"schemas/base.json"` → the JSON Schema object)
525
- - **`resolve: ["globals"]`:** Resolve named schema references (e.g., `"base"` in a rule's schema array → the global schema object)
526
- - **`resolve: ["files","globals"]`:** Fully inlined, everything expanded
527
-
528
- **When to use:**
529
- - **Orientation:** No resolve (just names and descriptions, lightweight)
530
- - **Query planning:** `resolve: ["files","globals"]` (need complete merged schemas for filter construction)
531
- - **Browsing global schemas:** `resolve: ["files"]` (see schema contents but keep named references visible for DRY structure understanding)
532
-
533
- ---
534
-
535
- ## JSONPath Patterns for Schema Discovery
536
-
537
- Use `watcher_query` to explore the merged virtual document. Common patterns:
538
-
539
- ### Orientation
540
- ```
541
- $.inferenceRules[*].['name','description'] — List all rules with descriptions
542
- $.search.scoreThresholds — Score interpretation thresholds
543
- $.slots — Named filter patterns (e.g., memory)
544
- ```
545
-
546
- ### Schema Discovery
547
- ```
548
- $.inferenceRules[?(@.name=='jira-issue')] — Full rule details
549
- $.inferenceRules[?(@.name=='jira-issue')].values — Distinct values for a rule
550
- $.inferenceRules[?(@.name=='jira-issue')].values.status — Values for a specific field
551
- ```
552
-
553
- ### Helper Enumeration
554
- ```
555
- $.mapHelpers — All JsonMap helper namespaces
556
- $.mapHelpers.slack.exports — Exports from the 'slack' helper
557
- $.templateHelpers — All Handlebars helper namespaces
558
- ```
559
-
560
- ### Issues
561
- ```
562
- $.issues — All runtime embedding failures
563
- ```
564
-
565
- ### Full Config Introspection
566
- ```
567
- $.schemas — Global named schemas
568
- $.maps — Named JsonMap transforms
569
- $.templates — Named Handlebars templates
570
- ```
571
-
572
- ---
573
-
574
449
  ## Query Planning (Per Search Task)
575
450
 
576
451
  Identify relevant rule(s) from the orientation model, then retrieve their schemas:
@@ -603,7 +478,7 @@ Use `uiHint` to determine filter construction strategy. **This table is explicit
603
478
  | `text` | `{ "key": "<field>", "match": { "text": "<value>" } }` | Substring/keyword match |
604
479
  | `select` | `{ "key": "<field>", "match": { "value": "<enum_value>" } }` | Exact match; use `enum` values from schema or runtime values index |
605
480
  | `multiselect` | `{ "key": "<field>", "match": { "value": "<enum_value>" } }` | Any-element match on array field; use `enum` or runtime values index |
606
- | `date` | `{ "key": "<field>", "range": { "gte": <unix_ts>, "lt": <unix_ts> } }` | Either bound optional for open-ended ranges (e.g., "after January" → `gte` only) |
481
+ | `date` | `{ "key": "<field>", "range": { "gte": <unix_ts>, "lt": <unix_ts> } }` | Range filter against integer fields holding Unix timestamps (seconds). Source dates should be normalized in config via `{{toUnix ...}}` in `set` expressions. | for open-ended ranges (e.g., "after January" → `gte` only) |
607
482
  | `number` | `{ "key": "<field>", "range": { "gte": <n>, "lte": <n> } }` | Either bound optional for open-ended ranges |
608
483
  | `check` | `{ "key": "<field>", "match": { "value": true } }` | Boolean match |
609
484
  | *(absent)* | Do not use in filters | Internal bookkeeping field, not intended for search |
@@ -790,3 +665,7 @@ If tools are unavailable (plugin not loaded in this session):
790
665
 
791
666
  - [JSONPath Plus documentation](https://www.npmjs.com/package/jsonpath-plus) for JSONPath syntax
792
667
  - [Qdrant filtering documentation](https://qdrant.tech/documentation/concepts/filtering/) for advanced query patterns and search response format
668
+
669
+
670
+
671
+
@@ -19,6 +19,11 @@ export interface PluginApi {
19
19
  }, options?: {
20
20
  optional?: boolean;
21
21
  }): void;
22
+ /**
23
+ * Optional internal hook registration (available on newer OpenClaw builds).
24
+ * We keep this optional to preserve compatibility.
25
+ */
26
+ registerInternalHook?: (event: string, handler: (event: unknown) => Promise<void> | void) => void;
22
27
  }
23
28
  /** Result shape returned by each tool execution. */
24
29
  export interface ToolResult {
@@ -30,6 +35,8 @@ export interface ToolResult {
30
35
  }
31
36
  /** Resolve the watcher API base URL from plugin config. */
32
37
  export declare function getApiUrl(api: PluginApi): string;
38
+ /** Resolve the cache TTL for plugin hooks from config. */
39
+ export declare function getCacheTtlMs(api: PluginApi): number;
33
40
  /** Format a successful tool result. */
34
41
  export declare function ok(data: unknown): ToolResult;
35
42
  /** Format an error tool result. */
@@ -0,0 +1,6 @@
1
+ import { type PluginApi } from './helpers.js';
2
+ /**
3
+ * Hook handler for agent:bootstrap.
4
+ * Injects/updates the Watcher Menu into the TOOLS.md payload.
5
+ */
6
+ export declare function handleAgentBootstrap(event: unknown, api: PluginApi): Promise<void>;
@@ -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.4.1",
5
+ "version": "0.5.0",
6
6
  "skills": [
7
7
  "dist/skills/jeeves-watcher"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-watcher-openclaw",
3
- "version": "0.4.1",
3
+ "version": "0.5.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",