@karmaniverous/jeeves-watcher-openclaw 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cli.js +81 -64
- package/dist/index.js +387 -188
- package/dist/skills/jeeves-watcher/SKILL.md +21 -2
- package/dist/src/cli.d.ts +2 -1
- package/dist/src/cli.test.d.ts +1 -0
- package/dist/src/helpers.d.ts +19 -0
- package/dist/src/helpers.test.d.ts +1 -0
- package/dist/src/index.test.d.ts +1 -0
- package/dist/src/memoryTools.d.ts +17 -0
- package/dist/src/memoryTools.test.d.ts +1 -0
- package/dist/src/watcherTools.d.ts +7 -0
- package/dist/src/watcherTools.test.d.ts +1 -0
- package/openclaw.plugin.json +2 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ Set the `apiUrl` in the plugin configuration to point at your jeeves-watcher ser
|
|
|
38
38
|
|
|
39
39
|
```json
|
|
40
40
|
{
|
|
41
|
-
"apiUrl": "http://
|
|
41
|
+
"apiUrl": "http://127.0.0.1:3458"
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -54,6 +54,8 @@ Set the `apiUrl` in the plugin configuration to point at your jeeves-watcher ser
|
|
|
54
54
|
| `watcher_config_apply` | Apply a new configuration |
|
|
55
55
|
| `watcher_reindex` | Trigger a full reindex |
|
|
56
56
|
| `watcher_issues` | List indexing issues and errors |
|
|
57
|
+
| `memory_search` | Semantically search memory files (MEMORY.md and memory/*.md) |
|
|
58
|
+
| `memory_get` | Read content from memory files with optional line range |
|
|
57
59
|
|
|
58
60
|
## Documentation
|
|
59
61
|
|
package/dist/cli.js
CHANGED
|
@@ -22,15 +22,12 @@ import { fileURLToPath } from 'url';
|
|
|
22
22
|
const PLUGIN_ID = 'jeeves-watcher-openclaw';
|
|
23
23
|
/** Resolve the OpenClaw home directory. */
|
|
24
24
|
function resolveOpenClawHome() {
|
|
25
|
-
// 1. OPENCLAW_CONFIG points directly to the config file
|
|
26
25
|
if (process.env.OPENCLAW_CONFIG) {
|
|
27
26
|
return dirname(resolve(process.env.OPENCLAW_CONFIG));
|
|
28
27
|
}
|
|
29
|
-
// 2. OPENCLAW_HOME points to the .openclaw directory
|
|
30
28
|
if (process.env.OPENCLAW_HOME) {
|
|
31
29
|
return resolve(process.env.OPENCLAW_HOME);
|
|
32
30
|
}
|
|
33
|
-
// 3. Default location
|
|
34
31
|
return join(homedir(), '.openclaw');
|
|
35
32
|
}
|
|
36
33
|
/** Resolve the config file path. */
|
|
@@ -43,8 +40,6 @@ function resolveConfigPath(home) {
|
|
|
43
40
|
/** Get the package root (where this CLI lives). */
|
|
44
41
|
function getPackageRoot() {
|
|
45
42
|
const thisFile = fileURLToPath(import.meta.url);
|
|
46
|
-
// In dist: dist/cli.js → package root is ..
|
|
47
|
-
// In src: src/cli.ts → package root is ..
|
|
48
43
|
return resolve(dirname(thisFile), '..');
|
|
49
44
|
}
|
|
50
45
|
/** Read and parse JSON, returning null on failure. */
|
|
@@ -60,6 +55,79 @@ function readJson(path) {
|
|
|
60
55
|
function writeJson(path, data) {
|
|
61
56
|
writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
|
|
62
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Patch an allowlist array: add or remove the plugin ID.
|
|
60
|
+
* Returns a log message if a change was made, or undefined.
|
|
61
|
+
*/
|
|
62
|
+
function patchAllowList(parent, key, label, mode) {
|
|
63
|
+
if (!Array.isArray(parent[key]) || parent[key].length === 0)
|
|
64
|
+
return undefined;
|
|
65
|
+
const list = parent[key];
|
|
66
|
+
if (mode === 'add') {
|
|
67
|
+
if (!list.includes(PLUGIN_ID)) {
|
|
68
|
+
list.push(PLUGIN_ID);
|
|
69
|
+
return `Added "${PLUGIN_ID}" to ${label}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const filtered = list.filter((id) => id !== PLUGIN_ID);
|
|
74
|
+
if (filtered.length !== list.length) {
|
|
75
|
+
parent[key] = filtered;
|
|
76
|
+
return `Removed "${PLUGIN_ID}" from ${label}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
/** Patch OpenClaw config for install or uninstall. Returns log messages. */
|
|
82
|
+
function patchConfig(config, mode) {
|
|
83
|
+
const messages = [];
|
|
84
|
+
// Ensure plugins section
|
|
85
|
+
if (!config.plugins || typeof config.plugins !== 'object') {
|
|
86
|
+
config.plugins = {};
|
|
87
|
+
}
|
|
88
|
+
const plugins = config.plugins;
|
|
89
|
+
// plugins.allow
|
|
90
|
+
const pluginAllow = patchAllowList(plugins, 'allow', 'plugins.allow', mode);
|
|
91
|
+
if (pluginAllow)
|
|
92
|
+
messages.push(pluginAllow);
|
|
93
|
+
// plugins.entries
|
|
94
|
+
if (!plugins.entries || typeof plugins.entries !== 'object') {
|
|
95
|
+
plugins.entries = {};
|
|
96
|
+
}
|
|
97
|
+
const entries = plugins.entries;
|
|
98
|
+
if (mode === 'add') {
|
|
99
|
+
if (!entries[PLUGIN_ID]) {
|
|
100
|
+
entries[PLUGIN_ID] = { enabled: true };
|
|
101
|
+
messages.push(`Added "${PLUGIN_ID}" to plugins.entries`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (PLUGIN_ID in entries) {
|
|
105
|
+
Reflect.deleteProperty(entries, PLUGIN_ID);
|
|
106
|
+
messages.push(`Removed "${PLUGIN_ID}" from plugins.entries`);
|
|
107
|
+
}
|
|
108
|
+
// plugins.slots — claim or release the memory slot
|
|
109
|
+
if (!plugins.slots || typeof plugins.slots !== 'object') {
|
|
110
|
+
plugins.slots = {};
|
|
111
|
+
}
|
|
112
|
+
const slots = plugins.slots;
|
|
113
|
+
if (mode === 'add') {
|
|
114
|
+
const prev = slots.memory;
|
|
115
|
+
slots.memory = PLUGIN_ID;
|
|
116
|
+
if (prev !== PLUGIN_ID) {
|
|
117
|
+
messages.push(`Set plugins.slots.memory to "${PLUGIN_ID}"${prev ? ` (was "${prev}")` : ''}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (slots.memory === PLUGIN_ID) {
|
|
121
|
+
slots.memory = 'memory-core';
|
|
122
|
+
messages.push(`Reverted plugins.slots.memory to "memory-core"`);
|
|
123
|
+
}
|
|
124
|
+
// tools.allow
|
|
125
|
+
const tools = (config.tools ?? {});
|
|
126
|
+
const toolAllow = patchAllowList(tools, 'allow', 'tools.allow', mode);
|
|
127
|
+
if (toolAllow)
|
|
128
|
+
messages.push(toolAllow);
|
|
129
|
+
return messages;
|
|
130
|
+
}
|
|
63
131
|
/** Install the plugin into OpenClaw's extensions directory. */
|
|
64
132
|
function install() {
|
|
65
133
|
const home = resolveOpenClawHome();
|
|
@@ -71,19 +139,16 @@ function install() {
|
|
|
71
139
|
console.log(`Extensions dir: ${extDir}`);
|
|
72
140
|
console.log(`Package root: ${pkgRoot}`);
|
|
73
141
|
console.log();
|
|
74
|
-
// Validate OpenClaw home exists
|
|
75
142
|
if (!existsSync(home)) {
|
|
76
143
|
console.error(`Error: OpenClaw home directory not found at ${home}`);
|
|
77
144
|
console.error('Set OPENCLAW_HOME or OPENCLAW_CONFIG if using a non-default installation.');
|
|
78
145
|
process.exit(1);
|
|
79
146
|
}
|
|
80
|
-
// Validate config exists
|
|
81
147
|
if (!existsSync(configPath)) {
|
|
82
148
|
console.error(`Error: OpenClaw config not found at ${configPath}`);
|
|
83
149
|
console.error('Set OPENCLAW_CONFIG if using a non-default config location.');
|
|
84
150
|
process.exit(1);
|
|
85
151
|
}
|
|
86
|
-
// Validate package root has openclaw.plugin.json
|
|
87
152
|
const pluginManifestPath = join(pkgRoot, 'openclaw.plugin.json');
|
|
88
153
|
if (!existsSync(pluginManifestPath)) {
|
|
89
154
|
console.error(`Error: openclaw.plugin.json not found at ${pluginManifestPath}`);
|
|
@@ -95,9 +160,7 @@ function install() {
|
|
|
95
160
|
rmSync(extDir, { recursive: true, force: true });
|
|
96
161
|
}
|
|
97
162
|
mkdirSync(extDir, { recursive: true });
|
|
98
|
-
|
|
99
|
-
const filesToCopy = ['dist', 'openclaw.plugin.json', 'package.json'];
|
|
100
|
-
for (const file of filesToCopy) {
|
|
163
|
+
for (const file of ['dist', 'openclaw.plugin.json', 'package.json']) {
|
|
101
164
|
const src = join(pkgRoot, file);
|
|
102
165
|
const dest = join(extDir, file);
|
|
103
166
|
if (existsSync(src)) {
|
|
@@ -105,13 +168,12 @@ function install() {
|
|
|
105
168
|
console.log(` ✓ ${file}`);
|
|
106
169
|
}
|
|
107
170
|
}
|
|
108
|
-
// Copy node_modules if present (for runtime dependencies)
|
|
109
171
|
const nodeModulesSrc = join(pkgRoot, 'node_modules');
|
|
110
172
|
if (existsSync(nodeModulesSrc)) {
|
|
111
173
|
cpSync(nodeModulesSrc, join(extDir, 'node_modules'), { recursive: true });
|
|
112
174
|
console.log(' ✓ node_modules');
|
|
113
175
|
}
|
|
114
|
-
// Patch
|
|
176
|
+
// Patch config
|
|
115
177
|
console.log();
|
|
116
178
|
console.log('Patching OpenClaw config...');
|
|
117
179
|
const config = readJson(configPath);
|
|
@@ -119,36 +181,8 @@ function install() {
|
|
|
119
181
|
console.error(`Error: Could not parse ${configPath}`);
|
|
120
182
|
process.exit(1);
|
|
121
183
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
config.plugins = {};
|
|
125
|
-
}
|
|
126
|
-
const plugins = config.plugins;
|
|
127
|
-
// If plugins.allow exists and is populated, add ourselves to it
|
|
128
|
-
if (Array.isArray(plugins.allow) && plugins.allow.length > 0) {
|
|
129
|
-
const allow = plugins.allow;
|
|
130
|
-
if (!allow.includes(PLUGIN_ID)) {
|
|
131
|
-
allow.push(PLUGIN_ID);
|
|
132
|
-
console.log(` ✓ Added "${PLUGIN_ID}" to plugins.allow`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// Add to plugins.entries
|
|
136
|
-
if (!plugins.entries || typeof plugins.entries !== 'object') {
|
|
137
|
-
plugins.entries = {};
|
|
138
|
-
}
|
|
139
|
-
const entries = plugins.entries;
|
|
140
|
-
if (!entries[PLUGIN_ID]) {
|
|
141
|
-
entries[PLUGIN_ID] = { enabled: true };
|
|
142
|
-
console.log(` ✓ Added "${PLUGIN_ID}" to plugins.entries`);
|
|
143
|
-
}
|
|
144
|
-
// If tools.allow exists and is populated, add ourselves to it
|
|
145
|
-
const tools = (config.tools ?? {});
|
|
146
|
-
if (Array.isArray(tools.allow) && tools.allow.length > 0) {
|
|
147
|
-
const toolsAllow = tools.allow;
|
|
148
|
-
if (!toolsAllow.includes(PLUGIN_ID)) {
|
|
149
|
-
toolsAllow.push(PLUGIN_ID);
|
|
150
|
-
console.log(` ✓ Added "${PLUGIN_ID}" to tools.allow`);
|
|
151
|
-
}
|
|
184
|
+
for (const msg of patchConfig(config, 'add')) {
|
|
185
|
+
console.log(` ✓ ${msg}`);
|
|
152
186
|
}
|
|
153
187
|
writeJson(configPath, config);
|
|
154
188
|
console.log();
|
|
@@ -164,7 +198,6 @@ function uninstall() {
|
|
|
164
198
|
console.log(`Config: ${configPath}`);
|
|
165
199
|
console.log(`Extensions dir: ${extDir}`);
|
|
166
200
|
console.log();
|
|
167
|
-
// Remove extensions directory
|
|
168
201
|
if (existsSync(extDir)) {
|
|
169
202
|
rmSync(extDir, { recursive: true, force: true });
|
|
170
203
|
console.log(`✓ Removed ${extDir}`);
|
|
@@ -172,30 +205,12 @@ function uninstall() {
|
|
|
172
205
|
else {
|
|
173
206
|
console.log(` (extensions directory not found, skipping)`);
|
|
174
207
|
}
|
|
175
|
-
// Patch OpenClaw config
|
|
176
208
|
if (existsSync(configPath)) {
|
|
177
209
|
console.log('Patching OpenClaw config...');
|
|
178
210
|
const config = readJson(configPath);
|
|
179
211
|
if (config) {
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
if (Array.isArray(plugins.allow) && plugins.allow.length > 0) {
|
|
183
|
-
plugins.allow = plugins.allow.filter((id) => id !== PLUGIN_ID);
|
|
184
|
-
console.log(` ✓ Removed "${PLUGIN_ID}" from plugins.allow`);
|
|
185
|
-
}
|
|
186
|
-
// Remove from plugins.entries
|
|
187
|
-
if (plugins.entries && typeof plugins.entries === 'object') {
|
|
188
|
-
const entries = plugins.entries;
|
|
189
|
-
if (PLUGIN_ID in entries) {
|
|
190
|
-
Reflect.deleteProperty(entries, PLUGIN_ID);
|
|
191
|
-
console.log(` ✓ Removed "${PLUGIN_ID}" from plugins.entries`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Remove from tools.allow if it exists and is populated
|
|
195
|
-
const tools = (config.tools ?? {});
|
|
196
|
-
if (Array.isArray(tools.allow) && tools.allow.length > 0) {
|
|
197
|
-
tools.allow = tools.allow.filter((id) => id !== PLUGIN_ID);
|
|
198
|
-
console.log(` ✓ Removed "${PLUGIN_ID}" from tools.allow`);
|
|
212
|
+
for (const msg of patchConfig(config, 'remove')) {
|
|
213
|
+
console.log(` ✓ ${msg}`);
|
|
199
214
|
}
|
|
200
215
|
writeJson(configPath, config);
|
|
201
216
|
}
|
|
@@ -234,3 +249,5 @@ switch (command) {
|
|
|
234
249
|
}
|
|
235
250
|
break;
|
|
236
251
|
}
|
|
252
|
+
|
|
253
|
+
export { patchConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* @module plugin/helpers
|
|
3
7
|
* Shared types and utility functions for the OpenClaw plugin tool registrations.
|
|
4
8
|
*/
|
|
5
9
|
const DEFAULT_API_URL = 'http://127.0.0.1:3458';
|
|
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
|
+
let result = p.replace(/\\/g, '/');
|
|
15
|
+
if (/^[A-Z]:/.test(result)) {
|
|
16
|
+
result = result[0].toLowerCase() + result.slice(1);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the workspace path from gateway config.
|
|
22
|
+
* Priority: agent-specific > defaults > fallback (~/.openclaw/workspace).
|
|
23
|
+
*/
|
|
24
|
+
function getWorkspacePath(api) {
|
|
25
|
+
const agentWorkspace = api.config?.agents?.entries?.['main']?.workspace ??
|
|
26
|
+
api.config?.agents?.defaults?.workspace;
|
|
27
|
+
return agentWorkspace ?? join(homedir(), '.openclaw', 'workspace');
|
|
28
|
+
}
|
|
6
29
|
/** Resolve the watcher API base URL from plugin config. */
|
|
7
30
|
function getApiUrl(api) {
|
|
8
31
|
const url = api.config?.plugins?.entries?.['jeeves-watcher']?.config?.apiUrl;
|
|
@@ -54,238 +77,414 @@ async function fetchJson(url, init) {
|
|
|
54
77
|
}
|
|
55
78
|
return res.json();
|
|
56
79
|
}
|
|
80
|
+
/** POST JSON to a URL and return parsed response. */
|
|
81
|
+
async function postJson(url, body) {
|
|
82
|
+
return fetchJson(url, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: { 'Content-Type': 'application/json' },
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
57
88
|
|
|
58
89
|
/**
|
|
59
|
-
* @module plugin
|
|
60
|
-
*
|
|
90
|
+
* @module plugin/memoryTools
|
|
91
|
+
* memory_search and memory_get tool implementations with lazy init.
|
|
92
|
+
*
|
|
93
|
+
* Lazy init registers virtual inference rules with the watcher on first
|
|
94
|
+
* memory_search call. Re-attempts on failure. memory_get reads files
|
|
95
|
+
* directly from the filesystem with path validation.
|
|
61
96
|
*/
|
|
62
|
-
/**
|
|
63
|
-
function
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
api.registerTool({
|
|
79
|
-
name: 'watcher_search',
|
|
80
|
-
description: 'Semantic search over indexed documents. Supports Qdrant filters.',
|
|
81
|
-
parameters: {
|
|
82
|
-
type: 'object',
|
|
83
|
-
required: ['query'],
|
|
84
|
-
properties: {
|
|
85
|
-
query: { type: 'string', description: 'Search query text.' },
|
|
86
|
-
limit: {
|
|
87
|
-
type: 'number',
|
|
88
|
-
description: 'Max results (default 10).',
|
|
97
|
+
/** Build virtual inference rules for a workspace path. */
|
|
98
|
+
function buildVirtualRules(workspace) {
|
|
99
|
+
const ws = normalizePath(workspace);
|
|
100
|
+
return [
|
|
101
|
+
{
|
|
102
|
+
name: 'openclaw-memory-longterm',
|
|
103
|
+
description: 'OpenClaw long-term memory file',
|
|
104
|
+
match: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
file: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
path: { type: 'string', pattern: `^${ws}/MEMORY\\.md$` },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
89
113
|
},
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
},
|
|
94
|
-
filter: {
|
|
114
|
+
},
|
|
115
|
+
schema: [
|
|
116
|
+
{
|
|
95
117
|
type: 'object',
|
|
96
|
-
|
|
118
|
+
properties: {
|
|
119
|
+
domain: { type: 'string', set: 'memory' },
|
|
120
|
+
kind: { type: 'string', set: 'long-term' },
|
|
121
|
+
},
|
|
97
122
|
},
|
|
98
|
-
|
|
99
|
-
},
|
|
100
|
-
execute: async (_id, params) => {
|
|
101
|
-
try {
|
|
102
|
-
const body = { query: params.query };
|
|
103
|
-
if (params.limit !== undefined)
|
|
104
|
-
body.limit = params.limit;
|
|
105
|
-
if (params.offset !== undefined)
|
|
106
|
-
body.offset = params.offset;
|
|
107
|
-
if (params.filter !== undefined)
|
|
108
|
-
body.filter = params.filter;
|
|
109
|
-
return ok(await fetchJson(`${baseUrl}/search`, {
|
|
110
|
-
method: 'POST',
|
|
111
|
-
headers: { 'Content-Type': 'application/json' },
|
|
112
|
-
body: JSON.stringify(body),
|
|
113
|
-
}));
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
return connectionFail(error, baseUrl);
|
|
117
|
-
}
|
|
123
|
+
],
|
|
118
124
|
},
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
{
|
|
126
|
+
name: 'openclaw-memory-daily',
|
|
127
|
+
description: 'OpenClaw daily memory logs',
|
|
128
|
+
match: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: {
|
|
131
|
+
file: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {
|
|
134
|
+
path: { type: 'string', pattern: `^${ws}/memory/.*\\.md$` },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
130
137
|
},
|
|
131
|
-
|
|
138
|
+
},
|
|
139
|
+
schema: [
|
|
140
|
+
{
|
|
132
141
|
type: 'object',
|
|
133
|
-
|
|
142
|
+
properties: {
|
|
143
|
+
domain: { type: 'string', set: 'memory' },
|
|
144
|
+
kind: { type: 'string', set: 'daily-log' },
|
|
145
|
+
},
|
|
134
146
|
},
|
|
135
|
-
|
|
147
|
+
],
|
|
136
148
|
},
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
/** Validate a path is within the memory scope. */
|
|
152
|
+
function isAllowedMemoryPath(filePath, workspace) {
|
|
153
|
+
const norm = normalizePath(filePath).toLowerCase();
|
|
154
|
+
const ws = normalizePath(workspace).toLowerCase();
|
|
155
|
+
// Exact match: {workspace}/MEMORY.md
|
|
156
|
+
if (norm === `${ws}/memory.md`)
|
|
157
|
+
return true;
|
|
158
|
+
// Prefix match: {workspace}/memory/**/*.md
|
|
159
|
+
if (norm.startsWith(`${ws}/memory/`) && norm.endsWith('.md'))
|
|
160
|
+
return true;
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Create memory tool registrations for the plugin.
|
|
165
|
+
* Returns register functions for memory_search and memory_get.
|
|
166
|
+
*/
|
|
167
|
+
function createMemoryTools(api, baseUrl) {
|
|
168
|
+
const workspace = getWorkspacePath(api);
|
|
169
|
+
const state = {
|
|
170
|
+
initialized: false,
|
|
171
|
+
workspace,
|
|
172
|
+
baseUrl,
|
|
173
|
+
};
|
|
174
|
+
/** Lazy init: register virtual rules with watcher. */
|
|
175
|
+
async function ensureInit() {
|
|
176
|
+
if (state.initialized)
|
|
177
|
+
return;
|
|
178
|
+
// Check watcher is reachable
|
|
179
|
+
await fetchJson(`${state.baseUrl}/status`);
|
|
180
|
+
// Clear any stale rules
|
|
181
|
+
await fetchJson(`${state.baseUrl}/rules/unregister`, {
|
|
182
|
+
method: 'DELETE',
|
|
183
|
+
headers: { 'Content-Type': 'application/json' },
|
|
184
|
+
body: JSON.stringify({ source: PLUGIN_SOURCE }),
|
|
185
|
+
});
|
|
186
|
+
// Register virtual rules
|
|
187
|
+
await postJson(`${state.baseUrl}/rules/register`, {
|
|
188
|
+
source: PLUGIN_SOURCE,
|
|
189
|
+
rules: buildVirtualRules(state.workspace),
|
|
190
|
+
});
|
|
191
|
+
state.initialized = true;
|
|
192
|
+
}
|
|
193
|
+
const memorySearch = async (_id, params) => {
|
|
194
|
+
try {
|
|
195
|
+
await ensureInit();
|
|
196
|
+
const body = {
|
|
197
|
+
query: params.query,
|
|
198
|
+
filter: {
|
|
199
|
+
must: [{ key: 'domain', match: { value: 'memory' } }],
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
if (params.maxResults !== undefined)
|
|
203
|
+
body.limit = params.maxResults;
|
|
204
|
+
const raw = await postJson(`${state.baseUrl}/search`, body);
|
|
205
|
+
// Map results to system-prompt-compatible format
|
|
206
|
+
const results = raw.map((r) => {
|
|
207
|
+
const payload = r.payload;
|
|
208
|
+
const mapped = {
|
|
209
|
+
path: payload.file_path,
|
|
210
|
+
snippet: payload.chunk_text,
|
|
211
|
+
score: r.score,
|
|
212
|
+
};
|
|
213
|
+
if (payload.line_start != null)
|
|
214
|
+
mapped.from = payload.line_start;
|
|
215
|
+
if (payload.line_end != null)
|
|
216
|
+
mapped.to = payload.line_end;
|
|
217
|
+
return mapped;
|
|
218
|
+
});
|
|
219
|
+
const minScore = typeof params.minScore === 'number' ? params.minScore : 0;
|
|
220
|
+
const filtered = results.filter((r) => typeof r.score === 'number' && r.score >= minScore);
|
|
221
|
+
return ok(filtered);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
state.initialized = false;
|
|
225
|
+
return connectionFail(error, state.baseUrl);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const memoryGet = async (_id, params) => {
|
|
229
|
+
try {
|
|
230
|
+
const filePath = String(params.path);
|
|
231
|
+
// Re-derive workspace on every call for immediate pickup of moves
|
|
232
|
+
const currentWorkspace = getWorkspacePath(api);
|
|
233
|
+
if (!isAllowedMemoryPath(filePath, currentWorkspace)) {
|
|
234
|
+
return fail(`Path not within memory scope. Allowed: ${currentWorkspace}/MEMORY.md and ${currentWorkspace}/memory/**/*.md`);
|
|
235
|
+
}
|
|
236
|
+
const content = await readFile(filePath, 'utf-8');
|
|
237
|
+
if (params.from !== undefined) {
|
|
238
|
+
const from = Number(params.from);
|
|
239
|
+
const lines = content.split('\n');
|
|
240
|
+
const startIdx = Math.max(0, from - 1); // 1-indexed to 0-indexed
|
|
241
|
+
const count = params.lines !== undefined
|
|
242
|
+
? Number(params.lines)
|
|
243
|
+
: lines.length - startIdx;
|
|
244
|
+
const sliced = lines.slice(startIdx, startIdx + count);
|
|
245
|
+
return ok(sliced.join('\n'));
|
|
246
|
+
}
|
|
247
|
+
return ok(content);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
return fail(error);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
return { memorySearch, memoryGet };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @module plugin/watcherTools
|
|
258
|
+
* Watcher tool registrations (watcher_* tools) for the OpenClaw plugin.
|
|
259
|
+
*/
|
|
260
|
+
/** Register a single API tool with standard try/catch + ok/connectionFail. */
|
|
261
|
+
function registerApiTool(api, baseUrl, config) {
|
|
262
|
+
api.registerTool({
|
|
263
|
+
name: config.name,
|
|
264
|
+
description: config.description,
|
|
265
|
+
parameters: config.parameters,
|
|
137
266
|
execute: async (_id, params) => {
|
|
138
267
|
try {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}),
|
|
146
|
-
}));
|
|
268
|
+
const [endpoint, body] = config.buildRequest(params);
|
|
269
|
+
const url = `${baseUrl}${endpoint}`;
|
|
270
|
+
const data = body !== undefined
|
|
271
|
+
? await postJson(url, body)
|
|
272
|
+
: await fetchJson(url);
|
|
273
|
+
return ok(data);
|
|
147
274
|
}
|
|
148
275
|
catch (error) {
|
|
149
276
|
return connectionFail(error, baseUrl);
|
|
150
277
|
}
|
|
151
278
|
},
|
|
152
279
|
}, { optional: true });
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
280
|
+
}
|
|
281
|
+
/** Pick defined keys from params into a body object. */
|
|
282
|
+
function pickDefined(params, keys) {
|
|
283
|
+
const body = {};
|
|
284
|
+
for (const key of keys) {
|
|
285
|
+
if (params[key] !== undefined)
|
|
286
|
+
body[key] = params[key];
|
|
287
|
+
}
|
|
288
|
+
return body;
|
|
289
|
+
}
|
|
290
|
+
/** Register all 8 watcher_* tools with the OpenClaw plugin API. */
|
|
291
|
+
function registerWatcherTools(api, baseUrl) {
|
|
292
|
+
const tools = [
|
|
293
|
+
{
|
|
294
|
+
name: 'watcher_status',
|
|
295
|
+
description: 'Get jeeves-watcher service health, uptime, and collection statistics.',
|
|
296
|
+
parameters: { type: 'object', properties: {} },
|
|
297
|
+
buildRequest: () => ['/status'],
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: 'watcher_search',
|
|
301
|
+
description: 'Semantic search over indexed documents. Supports Qdrant filters.',
|
|
302
|
+
parameters: {
|
|
303
|
+
type: 'object',
|
|
304
|
+
required: ['query'],
|
|
305
|
+
properties: {
|
|
306
|
+
query: { type: 'string', description: 'Search query text.' },
|
|
307
|
+
limit: { type: 'number', description: 'Max results (default 10).' },
|
|
308
|
+
offset: {
|
|
309
|
+
type: 'number',
|
|
310
|
+
description: 'Number of results to skip for pagination.',
|
|
311
|
+
},
|
|
312
|
+
filter: { type: 'object', description: 'Qdrant filter object.' },
|
|
163
313
|
},
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
314
|
+
},
|
|
315
|
+
buildRequest: (params) => {
|
|
316
|
+
const body = pickDefined(params, [
|
|
317
|
+
'query',
|
|
318
|
+
'limit',
|
|
319
|
+
'offset',
|
|
320
|
+
'filter',
|
|
321
|
+
]);
|
|
322
|
+
return ['/search', body];
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: 'watcher_enrich',
|
|
327
|
+
description: 'Set or update metadata on a document by file path.',
|
|
328
|
+
parameters: {
|
|
329
|
+
type: 'object',
|
|
330
|
+
required: ['path', 'metadata'],
|
|
331
|
+
properties: {
|
|
332
|
+
path: {
|
|
333
|
+
type: 'string',
|
|
334
|
+
description: 'Relative file path of the document.',
|
|
335
|
+
},
|
|
336
|
+
metadata: {
|
|
337
|
+
type: 'object',
|
|
338
|
+
description: 'Key-value metadata to set on the document.',
|
|
339
|
+
},
|
|
168
340
|
},
|
|
169
341
|
},
|
|
342
|
+
buildRequest: (params) => [
|
|
343
|
+
'/metadata',
|
|
344
|
+
{ path: params.path, metadata: params.metadata },
|
|
345
|
+
],
|
|
170
346
|
},
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
347
|
+
{
|
|
348
|
+
name: 'watcher_query',
|
|
349
|
+
description: 'Query the merged virtual document via JSONPath.',
|
|
350
|
+
parameters: {
|
|
351
|
+
type: 'object',
|
|
352
|
+
required: ['path'],
|
|
353
|
+
properties: {
|
|
354
|
+
path: { type: 'string', description: 'JSONPath expression.' },
|
|
355
|
+
resolve: {
|
|
356
|
+
type: 'array',
|
|
357
|
+
items: { type: 'string', enum: ['files', 'globals'] },
|
|
358
|
+
description: 'Resolution scopes to include (e.g., ["files"], ["globals"], or both).',
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
buildRequest: (params) => {
|
|
363
|
+
const body = pickDefined(params, ['path', 'resolve']);
|
|
364
|
+
return ['/config/query', body];
|
|
365
|
+
},
|
|
185
366
|
},
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
367
|
+
{
|
|
368
|
+
name: 'watcher_validate',
|
|
369
|
+
description: 'Validate a candidate config (or current config if omitted). Optionally test file paths against the config to preview rule matching and metadata output.',
|
|
370
|
+
parameters: {
|
|
371
|
+
type: 'object',
|
|
372
|
+
properties: {
|
|
373
|
+
config: {
|
|
374
|
+
type: 'object',
|
|
375
|
+
description: 'Candidate config (partial or full). Omit to validate current config.',
|
|
376
|
+
},
|
|
377
|
+
testPaths: {
|
|
378
|
+
type: 'array',
|
|
379
|
+
items: { type: 'string' },
|
|
380
|
+
description: 'File paths to test against the config for dry-run preview.',
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
buildRequest: (params) => {
|
|
385
|
+
const body = pickDefined(params, ['config', 'testPaths']);
|
|
386
|
+
return ['/config/validate', body];
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: 'watcher_config_apply',
|
|
391
|
+
description: 'Apply a full or partial config. Validates, writes to disk, and triggers configured reindex behavior.',
|
|
392
|
+
parameters: {
|
|
393
|
+
type: 'object',
|
|
394
|
+
required: ['config'],
|
|
395
|
+
properties: {
|
|
396
|
+
config: {
|
|
397
|
+
type: 'object',
|
|
398
|
+
description: 'Full or partial config to apply.',
|
|
399
|
+
},
|
|
196
400
|
},
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
401
|
+
},
|
|
402
|
+
buildRequest: (params) => ['/config/apply', { config: params.config }],
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: 'watcher_reindex',
|
|
406
|
+
description: 'Trigger a reindex of the watched files.',
|
|
407
|
+
parameters: {
|
|
408
|
+
type: 'object',
|
|
409
|
+
properties: {
|
|
410
|
+
scope: {
|
|
411
|
+
type: 'string',
|
|
412
|
+
enum: ['rules', 'full'],
|
|
413
|
+
description: 'Reindex scope: "rules" (default) re-applies inference rules; "full" re-embeds everything.',
|
|
414
|
+
},
|
|
201
415
|
},
|
|
202
416
|
},
|
|
417
|
+
buildRequest: (params) => [
|
|
418
|
+
'/config-reindex',
|
|
419
|
+
{ scope: params.scope ?? 'rules' },
|
|
420
|
+
],
|
|
203
421
|
},
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (params.testPaths !== undefined)
|
|
210
|
-
body.testPaths = params.testPaths;
|
|
211
|
-
return ok(await fetchJson(`${baseUrl}/config/validate`, {
|
|
212
|
-
method: 'POST',
|
|
213
|
-
headers: { 'Content-Type': 'application/json' },
|
|
214
|
-
body: JSON.stringify(body),
|
|
215
|
-
}));
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
return connectionFail(error, baseUrl);
|
|
219
|
-
}
|
|
422
|
+
{
|
|
423
|
+
name: 'watcher_issues',
|
|
424
|
+
description: 'Get runtime embedding failures. Shows files that failed processing and why.',
|
|
425
|
+
parameters: { type: 'object', properties: {} },
|
|
426
|
+
buildRequest: () => ['/issues'],
|
|
220
427
|
},
|
|
221
|
-
|
|
428
|
+
];
|
|
429
|
+
for (const tool of tools) {
|
|
430
|
+
registerApiTool(api, baseUrl, tool);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* @module plugin
|
|
436
|
+
* OpenClaw plugin entry point. Registers all jeeves-watcher tools.
|
|
437
|
+
*/
|
|
438
|
+
/** Register all jeeves-watcher tools with the OpenClaw plugin API. */
|
|
439
|
+
function register(api) {
|
|
440
|
+
const baseUrl = getApiUrl(api);
|
|
441
|
+
// Register 8 watcher_* tools
|
|
442
|
+
registerWatcherTools(api, baseUrl);
|
|
443
|
+
// Register memory slot tools (memory_search + memory_get)
|
|
444
|
+
const { memorySearch, memoryGet } = createMemoryTools(api, baseUrl);
|
|
222
445
|
api.registerTool({
|
|
223
|
-
name: '
|
|
224
|
-
description: '
|
|
446
|
+
name: 'memory_search',
|
|
447
|
+
description: 'Semantically search MEMORY.md and memory/*.md files. Returns top snippets with path and line numbers.',
|
|
225
448
|
parameters: {
|
|
226
449
|
type: 'object',
|
|
227
|
-
required: ['
|
|
450
|
+
required: ['query'],
|
|
228
451
|
properties: {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
452
|
+
query: { type: 'string', description: 'Search query text.' },
|
|
453
|
+
maxResults: {
|
|
454
|
+
type: 'number',
|
|
455
|
+
description: 'Maximum results to return.',
|
|
456
|
+
},
|
|
457
|
+
minScore: {
|
|
458
|
+
type: 'number',
|
|
459
|
+
description: 'Minimum similarity score threshold.',
|
|
232
460
|
},
|
|
233
461
|
},
|
|
234
462
|
},
|
|
235
|
-
execute:
|
|
236
|
-
|
|
237
|
-
return ok(await fetchJson(`${baseUrl}/config/apply`, {
|
|
238
|
-
method: 'POST',
|
|
239
|
-
headers: { 'Content-Type': 'application/json' },
|
|
240
|
-
body: JSON.stringify({ config: params.config }),
|
|
241
|
-
}));
|
|
242
|
-
}
|
|
243
|
-
catch (error) {
|
|
244
|
-
return connectionFail(error, baseUrl);
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
}, { optional: true });
|
|
463
|
+
execute: memorySearch,
|
|
464
|
+
});
|
|
248
465
|
api.registerTool({
|
|
249
|
-
name: '
|
|
250
|
-
description: '
|
|
466
|
+
name: 'memory_get',
|
|
467
|
+
description: 'Read content from MEMORY.md or memory/*.md files with optional line range.',
|
|
251
468
|
parameters: {
|
|
252
469
|
type: 'object',
|
|
470
|
+
required: ['path'],
|
|
253
471
|
properties: {
|
|
254
|
-
|
|
472
|
+
path: {
|
|
255
473
|
type: 'string',
|
|
256
|
-
|
|
257
|
-
|
|
474
|
+
description: 'Path to the memory file to read.',
|
|
475
|
+
},
|
|
476
|
+
from: {
|
|
477
|
+
type: 'number',
|
|
478
|
+
description: 'Line number to start reading from (1-indexed).',
|
|
479
|
+
},
|
|
480
|
+
lines: {
|
|
481
|
+
type: 'number',
|
|
482
|
+
description: 'Number of lines to read.',
|
|
258
483
|
},
|
|
259
484
|
},
|
|
260
485
|
},
|
|
261
|
-
execute:
|
|
262
|
-
|
|
263
|
-
return ok(await fetchJson(`${baseUrl}/config-reindex`, {
|
|
264
|
-
method: 'POST',
|
|
265
|
-
headers: { 'Content-Type': 'application/json' },
|
|
266
|
-
body: JSON.stringify({
|
|
267
|
-
scope: params.scope ?? 'rules',
|
|
268
|
-
}),
|
|
269
|
-
}));
|
|
270
|
-
}
|
|
271
|
-
catch (error) {
|
|
272
|
-
return connectionFail(error, baseUrl);
|
|
273
|
-
}
|
|
274
|
-
},
|
|
275
|
-
}, { optional: true });
|
|
276
|
-
api.registerTool({
|
|
277
|
-
name: 'watcher_issues',
|
|
278
|
-
description: 'Get runtime embedding failures. Shows files that failed processing and why.',
|
|
279
|
-
parameters: { type: 'object', properties: {} },
|
|
280
|
-
execute: async () => {
|
|
281
|
-
try {
|
|
282
|
-
return ok(await fetchJson(`${baseUrl}/issues`));
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
return connectionFail(error, baseUrl);
|
|
286
|
-
}
|
|
287
|
-
},
|
|
288
|
-
}, { optional: true });
|
|
486
|
+
execute: memoryGet,
|
|
487
|
+
});
|
|
289
488
|
}
|
|
290
489
|
|
|
291
490
|
export { register as default };
|
|
@@ -46,6 +46,9 @@ curl -X POST http://127.0.0.1:<PORT>/config/query \
|
|
|
46
46
|
| `/config-reindex` | POST | Trigger reindex |
|
|
47
47
|
| `/metadata` | POST | Enrich document metadata |
|
|
48
48
|
| `/issues` | GET | Runtime embedding failures |
|
|
49
|
+
| `/rules/register` | POST | Register virtual inference rules |
|
|
50
|
+
| `/rules/unregister` | DELETE | Remove virtual rules by source |
|
|
51
|
+
| `/points/delete` | POST | Delete points matching a Qdrant filter |
|
|
49
52
|
|
|
50
53
|
**If the watcher is unreachable:** Check the service status (`nssm status jeeves-watcher` on Windows), check the configured port in the watcher config file, and check logs for startup errors.
|
|
51
54
|
|
|
@@ -83,6 +86,22 @@ You don't need to know the rules in advance. The config is introspectable at run
|
|
|
83
86
|
|
|
84
87
|
## Tools
|
|
85
88
|
|
|
89
|
+
### `memory_search`
|
|
90
|
+
Semantically search MEMORY.md and memory/*.md files. Powered by the watcher's vector store with Gemini 3072-dim embeddings.
|
|
91
|
+
- `query` (string, required) — search query text
|
|
92
|
+
- `maxResults` (number, optional) — maximum results to return
|
|
93
|
+
- `minScore` (number, optional) — minimum similarity score threshold
|
|
94
|
+
|
|
95
|
+
Returns: `[{ path, from, to, snippet, score }]` where `from`/`to` are 1-indexed line numbers.
|
|
96
|
+
|
|
97
|
+
### `memory_get`
|
|
98
|
+
Read content from MEMORY.md or memory/*.md files with optional line range.
|
|
99
|
+
- `path` (string, required) — path to the memory file
|
|
100
|
+
- `from` (number, optional) — line number to start reading from (1-indexed)
|
|
101
|
+
- `lines` (number, optional) — number of lines to read
|
|
102
|
+
|
|
103
|
+
Path validation: only files within the workspace's MEMORY.md and memory/**/*.md are accessible.
|
|
104
|
+
|
|
86
105
|
### `watcher_search`
|
|
87
106
|
Semantic search over indexed documents.
|
|
88
107
|
- `query` (string, required) — natural language search query
|
|
@@ -411,9 +430,9 @@ Or check if a specific path would match:
|
|
|
411
430
|
### Rule Structure
|
|
412
431
|
Each inference rule has:
|
|
413
432
|
- `name` (required) — unique identifier
|
|
414
|
-
- `description` (
|
|
433
|
+
- `description` (required) — human-readable purpose
|
|
415
434
|
- `match` — JSON Schema with picomatch glob for path matching
|
|
416
|
-
- `
|
|
435
|
+
- `schema` — array of named schema references and/or inline schema objects with `set` templates
|
|
417
436
|
- `map` (optional) — named JsonMap transform
|
|
418
437
|
- `template` (optional) — named Handlebars template
|
|
419
438
|
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -13,4 +13,5 @@
|
|
|
13
13
|
* - OPENCLAW_HOME env var (path to .openclaw directory)
|
|
14
14
|
* - Default: ~/.openclaw/openclaw.json
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
/** Patch OpenClaw config for install or uninstall. Returns log messages. */
|
|
17
|
+
export declare function patchConfig(config: Record<string, unknown>, mode: 'add' | 'remove'): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/src/helpers.d.ts
CHANGED
|
@@ -10,6 +10,14 @@ 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
|
+
};
|
|
13
21
|
};
|
|
14
22
|
registerTool(tool: {
|
|
15
23
|
name: string;
|
|
@@ -28,6 +36,15 @@ export interface ToolResult {
|
|
|
28
36
|
}>;
|
|
29
37
|
isError?: boolean;
|
|
30
38
|
}
|
|
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;
|
|
31
48
|
/** Resolve the watcher API base URL from plugin config. */
|
|
32
49
|
export declare function getApiUrl(api: PluginApi): string;
|
|
33
50
|
/** Format a successful tool result. */
|
|
@@ -38,3 +55,5 @@ export declare function fail(error: unknown): ToolResult;
|
|
|
38
55
|
export declare function connectionFail(error: unknown, baseUrl: string): ToolResult;
|
|
39
56
|
/** Fetch JSON from a URL, throwing on non-OK responses. */
|
|
40
57
|
export declare function fetchJson(url: string, init?: RequestInit): Promise<unknown>;
|
|
58
|
+
/** POST JSON to a URL and return parsed response. */
|
|
59
|
+
export declare function postJson(url: string, body: unknown): Promise<unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
/**
|
|
11
|
+
* Create memory tool registrations for the plugin.
|
|
12
|
+
* Returns register functions for memory_search and memory_get.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createMemoryTools(api: PluginApi, baseUrl: string): {
|
|
15
|
+
memorySearch: (_id: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
16
|
+
memoryGet: (_id: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugin/watcherTools
|
|
3
|
+
* Watcher tool registrations (watcher_* tools) for the OpenClaw plugin.
|
|
4
|
+
*/
|
|
5
|
+
import { type PluginApi } from './helpers.js';
|
|
6
|
+
/** Register all 8 watcher_* tools with the OpenClaw plugin API. */
|
|
7
|
+
export declare function registerWatcherTools(api: PluginApi, baseUrl: string): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
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.3.1",
|
|
6
|
+
"kind": "memory",
|
|
6
7
|
"skills": [
|
|
7
8
|
"dist/skills/jeeves-watcher"
|
|
8
9
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karmaniverous/jeeves-watcher-openclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
@@ -59,9 +59,11 @@
|
|
|
59
59
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
60
60
|
"auto-changelog": "^2.5.0",
|
|
61
61
|
"cross-env": "^10.1.0",
|
|
62
|
+
"knip": "^5.85.0",
|
|
62
63
|
"release-it": "^19.2.4",
|
|
63
64
|
"rollup": "^4.59.0",
|
|
64
|
-
"tslib": "^2.8.1"
|
|
65
|
+
"tslib": "^2.8.1",
|
|
66
|
+
"vitest": "^4.0.18"
|
|
65
67
|
},
|
|
66
68
|
"scripts": {
|
|
67
69
|
"build:plugin": "rimraf dist && cross-env NO_COLOR=1 rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript",
|
|
@@ -72,6 +74,8 @@
|
|
|
72
74
|
"lint:fix": "eslint --fix .",
|
|
73
75
|
"release": "dotenvx run -f .env.local -- release-it",
|
|
74
76
|
"release:pre": "dotenvx run -f .env.local -- release-it --no-git.requireBranch --github.prerelease --preRelease",
|
|
77
|
+
"knip": "knip",
|
|
78
|
+
"test": "vitest run",
|
|
75
79
|
"typecheck": "tsc"
|
|
76
80
|
},
|
|
77
81
|
"release-it": {
|
|
@@ -91,6 +95,7 @@
|
|
|
91
95
|
"after:init": [
|
|
92
96
|
"npm run lint",
|
|
93
97
|
"npm run typecheck",
|
|
98
|
+
"npm run test",
|
|
94
99
|
"npm run build"
|
|
95
100
|
],
|
|
96
101
|
"before:npm:release": [
|