@karmaniverous/jeeves-meta-openclaw 0.1.1 → 0.1.3
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 +293 -382
- package/dist/src/promptInjection.d.ts +3 -2
- package/dist/src/rules.d.ts +2 -1
- package/dist/src/toolsWriter.d.ts +2 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HttpWatcherClient,
|
|
1
|
+
import { HttpWatcherClient, listMetas, normalizePath, findNode, computeEffectiveStaleness, selectCandidate, paginatedScan, filterInScope, computeStructureHash, readLatestArchive, hasSteerChanged, isArchitectTriggered, actualStaleness, loadSynthConfig } from '@karmaniverous/jeeves-meta';
|
|
2
2
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
|
|
@@ -55,168 +55,175 @@ function fail(error) {
|
|
|
55
55
|
* @module rules
|
|
56
56
|
*/
|
|
57
57
|
const SOURCE = 'jeeves-meta';
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
schema: [
|
|
73
|
-
'base',
|
|
74
|
-
{
|
|
58
|
+
/**
|
|
59
|
+
* Build virtual rule definitions using configured domain tags.
|
|
60
|
+
*
|
|
61
|
+
* @param config - Synth config with metaProperty/metaArchiveProperty.
|
|
62
|
+
* @returns Array of inference rule specs.
|
|
63
|
+
*/
|
|
64
|
+
function buildSynthRules(config) {
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
name: 'synth-meta-live',
|
|
68
|
+
description: 'Live jeeves-meta .meta/meta.json files',
|
|
69
|
+
match: {
|
|
75
70
|
properties: {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
synth_emphasis: { type: 'number', set: '{{json._emphasis}}' },
|
|
81
|
-
synth_synthesis_count: {
|
|
82
|
-
type: 'integer',
|
|
83
|
-
set: '{{json._synthesisCount}}',
|
|
84
|
-
},
|
|
85
|
-
synth_structure_hash: {
|
|
86
|
-
type: 'string',
|
|
87
|
-
set: '{{json._structureHash}}',
|
|
88
|
-
},
|
|
89
|
-
synth_architect_tokens: {
|
|
90
|
-
type: 'integer',
|
|
91
|
-
set: '{{json._architectTokens}}',
|
|
92
|
-
},
|
|
93
|
-
synth_builder_tokens: {
|
|
94
|
-
type: 'integer',
|
|
95
|
-
set: '{{json._builderTokens}}',
|
|
96
|
-
},
|
|
97
|
-
synth_critic_tokens: {
|
|
98
|
-
type: 'integer',
|
|
99
|
-
set: '{{json._criticTokens}}',
|
|
100
|
-
},
|
|
101
|
-
synth_error_step: {
|
|
102
|
-
type: 'string',
|
|
103
|
-
set: '{{json._error.step}}',
|
|
104
|
-
},
|
|
105
|
-
generated_at_unix: {
|
|
106
|
-
type: 'integer',
|
|
107
|
-
set: '{{toUnix json._generatedAt}}',
|
|
108
|
-
description: 'Synthesis timestamp as Unix seconds for range queries',
|
|
109
|
-
},
|
|
110
|
-
has_error: {
|
|
111
|
-
type: 'boolean',
|
|
112
|
-
set: '{{#if json._error}}true{{else}}false{{/if}}',
|
|
71
|
+
file: {
|
|
72
|
+
properties: {
|
|
73
|
+
path: { type: 'string', glob: '**/.meta/meta.json' },
|
|
74
|
+
},
|
|
113
75
|
},
|
|
114
76
|
},
|
|
115
77
|
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
frontmatter: [
|
|
119
|
-
'synth_id',
|
|
120
|
-
'synth_steer',
|
|
121
|
-
'generated_at_unix',
|
|
122
|
-
'synth_depth',
|
|
123
|
-
'synth_emphasis',
|
|
124
|
-
'synth_architect_tokens',
|
|
125
|
-
'synth_builder_tokens',
|
|
126
|
-
'synth_critic_tokens',
|
|
127
|
-
],
|
|
128
|
-
body: [
|
|
78
|
+
schema: [
|
|
79
|
+
'base',
|
|
129
80
|
{
|
|
130
|
-
path: 'json._content',
|
|
131
|
-
heading: 1,
|
|
132
|
-
label: 'Synthesis',
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
},
|
|
136
|
-
renderAs: 'md',
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
name: 'synth-meta-archive',
|
|
140
|
-
description: 'Archived jeeves-meta .meta/archive snapshots',
|
|
141
|
-
match: {
|
|
142
|
-
properties: {
|
|
143
|
-
file: {
|
|
144
81
|
properties: {
|
|
145
|
-
|
|
82
|
+
domains: { set: config.metaProperty.domains },
|
|
83
|
+
synth_id: { type: 'string', set: '{{json._id}}' },
|
|
84
|
+
synth_steer: { type: 'string', set: '{{json._steer}}' },
|
|
85
|
+
synth_depth: { type: 'number', set: '{{json._depth}}' },
|
|
86
|
+
synth_emphasis: { type: 'number', set: '{{json._emphasis}}' },
|
|
87
|
+
synth_synthesis_count: {
|
|
88
|
+
type: 'integer',
|
|
89
|
+
set: '{{json._synthesisCount}}',
|
|
90
|
+
},
|
|
91
|
+
synth_structure_hash: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
set: '{{json._structureHash}}',
|
|
94
|
+
},
|
|
95
|
+
synth_architect_tokens: {
|
|
96
|
+
type: 'integer',
|
|
97
|
+
set: '{{json._architectTokens}}',
|
|
98
|
+
},
|
|
99
|
+
synth_builder_tokens: {
|
|
100
|
+
type: 'integer',
|
|
101
|
+
set: '{{json._builderTokens}}',
|
|
102
|
+
},
|
|
103
|
+
synth_critic_tokens: {
|
|
104
|
+
type: 'integer',
|
|
105
|
+
set: '{{json._criticTokens}}',
|
|
106
|
+
},
|
|
107
|
+
synth_error_step: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
set: '{{json._error.step}}',
|
|
110
|
+
},
|
|
111
|
+
generated_at_unix: {
|
|
112
|
+
type: 'integer',
|
|
113
|
+
set: '{{toUnix json._generatedAt}}',
|
|
114
|
+
description: 'Synthesis timestamp as Unix seconds for range queries',
|
|
115
|
+
},
|
|
116
|
+
has_error: {
|
|
117
|
+
type: 'boolean',
|
|
118
|
+
set: '{{#if json._error}}true{{else}}false{{/if}}',
|
|
119
|
+
},
|
|
146
120
|
},
|
|
147
121
|
},
|
|
122
|
+
],
|
|
123
|
+
render: {
|
|
124
|
+
frontmatter: [
|
|
125
|
+
'synth_id',
|
|
126
|
+
'synth_steer',
|
|
127
|
+
'generated_at_unix',
|
|
128
|
+
'synth_depth',
|
|
129
|
+
'synth_emphasis',
|
|
130
|
+
'synth_architect_tokens',
|
|
131
|
+
'synth_builder_tokens',
|
|
132
|
+
'synth_critic_tokens',
|
|
133
|
+
],
|
|
134
|
+
body: [
|
|
135
|
+
{
|
|
136
|
+
path: 'json._content',
|
|
137
|
+
heading: 1,
|
|
138
|
+
label: 'Synthesis',
|
|
139
|
+
},
|
|
140
|
+
],
|
|
148
141
|
},
|
|
142
|
+
renderAs: 'md',
|
|
149
143
|
},
|
|
150
|
-
|
|
151
|
-
'
|
|
152
|
-
|
|
144
|
+
{
|
|
145
|
+
name: 'synth-meta-archive',
|
|
146
|
+
description: 'Archived jeeves-meta .meta/archive snapshots',
|
|
147
|
+
match: {
|
|
153
148
|
properties: {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
149
|
+
file: {
|
|
150
|
+
properties: {
|
|
151
|
+
path: { type: 'string', glob: '**/.meta/archive/*.json' },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
158
154
|
},
|
|
159
155
|
},
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
frontmatter: ['synth_id', 'archived', 'archived_at'],
|
|
163
|
-
body: [
|
|
156
|
+
schema: [
|
|
157
|
+
'base',
|
|
164
158
|
{
|
|
165
|
-
path: 'json._content',
|
|
166
|
-
heading: 1,
|
|
167
|
-
label: 'Synthesis (archived)',
|
|
168
|
-
},
|
|
169
|
-
],
|
|
170
|
-
},
|
|
171
|
-
renderAs: 'md',
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
name: 'synth-config',
|
|
175
|
-
description: 'jeeves-meta configuration file',
|
|
176
|
-
match: {
|
|
177
|
-
properties: {
|
|
178
|
-
file: {
|
|
179
159
|
properties: {
|
|
180
|
-
|
|
160
|
+
domains: { set: config.metaArchiveProperty.domains },
|
|
161
|
+
synth_id: { type: 'string', set: '{{json._id}}' },
|
|
162
|
+
archived: { type: 'boolean', set: 'true' },
|
|
163
|
+
archived_at: { type: 'string', set: '{{json._archivedAt}}' },
|
|
181
164
|
},
|
|
182
165
|
},
|
|
166
|
+
],
|
|
167
|
+
render: {
|
|
168
|
+
frontmatter: ['synth_id', 'archived', 'archived_at'],
|
|
169
|
+
body: [
|
|
170
|
+
{
|
|
171
|
+
path: 'json._content',
|
|
172
|
+
heading: 1,
|
|
173
|
+
label: 'Synthesis (archived)',
|
|
174
|
+
},
|
|
175
|
+
],
|
|
183
176
|
},
|
|
177
|
+
renderAs: 'md',
|
|
184
178
|
},
|
|
185
|
-
|
|
186
|
-
'
|
|
187
|
-
|
|
179
|
+
{
|
|
180
|
+
name: 'synth-config',
|
|
181
|
+
description: 'jeeves-meta configuration file',
|
|
182
|
+
match: {
|
|
188
183
|
properties: {
|
|
189
|
-
|
|
184
|
+
file: {
|
|
185
|
+
properties: {
|
|
186
|
+
path: { type: 'string', glob: '**/jeeves-meta.config.json' },
|
|
187
|
+
},
|
|
188
|
+
},
|
|
190
189
|
},
|
|
191
190
|
},
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
frontmatter: [
|
|
195
|
-
'watchPaths',
|
|
196
|
-
'watcherUrl',
|
|
197
|
-
'gatewayUrl',
|
|
198
|
-
'architectEvery',
|
|
199
|
-
'depthWeight',
|
|
200
|
-
'maxArchive',
|
|
201
|
-
'maxLines',
|
|
202
|
-
'batchSize',
|
|
203
|
-
],
|
|
204
|
-
body: [
|
|
191
|
+
schema: [
|
|
192
|
+
'base',
|
|
205
193
|
{
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
path: 'json.defaultCritic',
|
|
212
|
-
heading: 2,
|
|
213
|
-
label: 'Default Critic Prompt',
|
|
194
|
+
properties: {
|
|
195
|
+
domains: { set: ['synth-config'] },
|
|
196
|
+
},
|
|
214
197
|
},
|
|
215
198
|
],
|
|
199
|
+
render: {
|
|
200
|
+
frontmatter: [
|
|
201
|
+
'watchPaths',
|
|
202
|
+
'watcherUrl',
|
|
203
|
+
'gatewayUrl',
|
|
204
|
+
'architectEvery',
|
|
205
|
+
'depthWeight',
|
|
206
|
+
'maxArchive',
|
|
207
|
+
'maxLines',
|
|
208
|
+
'batchSize',
|
|
209
|
+
],
|
|
210
|
+
body: [
|
|
211
|
+
{
|
|
212
|
+
path: 'json.defaultArchitect',
|
|
213
|
+
heading: 2,
|
|
214
|
+
label: 'Default Architect Prompt',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
path: 'json.defaultCritic',
|
|
218
|
+
heading: 2,
|
|
219
|
+
label: 'Default Critic Prompt',
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
renderAs: 'md',
|
|
216
224
|
},
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
];
|
|
225
|
+
];
|
|
226
|
+
}
|
|
220
227
|
/**
|
|
221
228
|
* Register jeeves-meta virtual rules with the watcher.
|
|
222
229
|
*
|
|
@@ -225,9 +232,9 @@ const SYNTH_RULES = [
|
|
|
225
232
|
*
|
|
226
233
|
* @param watcherUrl - Base URL for the watcher service.
|
|
227
234
|
*/
|
|
228
|
-
async function registerSynthRules(watcherUrl) {
|
|
235
|
+
async function registerSynthRules(watcherUrl, config) {
|
|
229
236
|
const client = new HttpWatcherClient({ baseUrl: watcherUrl });
|
|
230
|
-
await client.registerRules(SOURCE,
|
|
237
|
+
await client.registerRules(SOURCE, buildSynthRules(config));
|
|
231
238
|
}
|
|
232
239
|
|
|
233
240
|
/**
|
|
@@ -248,8 +255,8 @@ function registerSynthTools(api) {
|
|
|
248
255
|
};
|
|
249
256
|
/** Derive watcherUrl from loaded config. */
|
|
250
257
|
const getWatcherUrl = () => getConfig().watcherUrl;
|
|
251
|
-
/**
|
|
252
|
-
const
|
|
258
|
+
/** Create a watcher client. */
|
|
259
|
+
const getWatcher = () => new HttpWatcherClient({ baseUrl: getWatcherUrl() });
|
|
253
260
|
// ─── synth_list ──────────────────────────────────────────────
|
|
254
261
|
api.registerTool({
|
|
255
262
|
name: 'synth_list',
|
|
@@ -281,26 +288,31 @@ function registerSynthTools(api) {
|
|
|
281
288
|
execute: async (_id, params) => {
|
|
282
289
|
try {
|
|
283
290
|
const pathPrefix = params.pathPrefix;
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
291
|
+
const config = getConfig();
|
|
292
|
+
const result = await listMetas(config, getWatcher());
|
|
293
|
+
// Apply path prefix filter
|
|
294
|
+
let entries = result.entries;
|
|
295
|
+
if (pathPrefix) {
|
|
296
|
+
entries = entries.filter((e) => e.path.includes(pathPrefix));
|
|
297
|
+
}
|
|
298
|
+
// Apply structured filter
|
|
299
|
+
const filter = params.filter;
|
|
300
|
+
if (filter) {
|
|
301
|
+
entries = entries.filter((e) => {
|
|
302
|
+
if (filter.hasError !== undefined && e.hasError !== filter.hasError)
|
|
303
|
+
return false;
|
|
304
|
+
if (filter.neverSynthesized !== undefined &&
|
|
305
|
+
(e.stalenessSeconds === Infinity) !== filter.neverSynthesized)
|
|
306
|
+
return false;
|
|
307
|
+
if (filter.locked !== undefined && e.locked !== filter.locked)
|
|
308
|
+
return false;
|
|
309
|
+
if (typeof filter.staleHours === 'number' &&
|
|
310
|
+
e.stalenessSeconds < filter.staleHours * 3600)
|
|
311
|
+
return false;
|
|
312
|
+
return true;
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
// Recompute summary for filtered entries
|
|
304
316
|
let staleCount = 0;
|
|
305
317
|
let errorCount = 0;
|
|
306
318
|
let lockedCount = 0;
|
|
@@ -312,113 +324,69 @@ function registerSynthTools(api) {
|
|
|
312
324
|
let lastSynthAt = null;
|
|
313
325
|
let stalestPath = null;
|
|
314
326
|
let stalestEffective = -1;
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
const filePath = sf.file_path;
|
|
318
|
-
const depth = typeof sf['synth_depth'] === 'number' ? sf['synth_depth'] : 0;
|
|
319
|
-
const emphasis = typeof sf['synth_emphasis'] === 'number' ? sf['synth_emphasis'] : 1;
|
|
320
|
-
const hasError = sf['has_error'] === true || sf['has_error'] === 'true';
|
|
321
|
-
const archTokens = typeof sf['synth_architect_tokens'] === 'number'
|
|
322
|
-
? sf['synth_architect_tokens']
|
|
323
|
-
: 0;
|
|
324
|
-
const buildTokens = typeof sf['synth_builder_tokens'] === 'number'
|
|
325
|
-
? sf['synth_builder_tokens']
|
|
326
|
-
: 0;
|
|
327
|
-
const critTokens = typeof sf['synth_critic_tokens'] === 'number'
|
|
328
|
-
? sf['synth_critic_tokens']
|
|
329
|
-
: 0;
|
|
330
|
-
const genAtUnix = typeof sf['generated_at_unix'] === 'number'
|
|
331
|
-
? sf['generated_at_unix']
|
|
332
|
-
: 0;
|
|
333
|
-
const locked = isLocked(normalizePath(filePath));
|
|
334
|
-
const neverSynth = genAtUnix === 0;
|
|
335
|
-
// Compute staleness from generated_at_unix
|
|
336
|
-
let stalenessSeconds;
|
|
337
|
-
if (neverSynth) {
|
|
338
|
-
stalenessSeconds = Infinity;
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
stalenessSeconds = Math.floor(Date.now() / 1000) - genAtUnix;
|
|
342
|
-
if (stalenessSeconds < 0)
|
|
343
|
-
stalenessSeconds = 0;
|
|
344
|
-
}
|
|
345
|
-
// Apply structured filter
|
|
346
|
-
const filter = params.filter;
|
|
347
|
-
if (filter) {
|
|
348
|
-
if (filter.hasError !== undefined && hasError !== filter.hasError)
|
|
349
|
-
continue;
|
|
350
|
-
if (filter.neverSynthesized !== undefined &&
|
|
351
|
-
neverSynth !== filter.neverSynthesized)
|
|
352
|
-
continue;
|
|
353
|
-
if (filter.locked !== undefined && locked !== filter.locked)
|
|
354
|
-
continue;
|
|
355
|
-
if (typeof filter.staleHours === 'number' &&
|
|
356
|
-
stalenessSeconds < filter.staleHours * 3600)
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
if (stalenessSeconds > 0)
|
|
327
|
+
for (const e of entries) {
|
|
328
|
+
if (e.stalenessSeconds > 0)
|
|
360
329
|
staleCount++;
|
|
361
|
-
if (hasError)
|
|
330
|
+
if (e.hasError)
|
|
362
331
|
errorCount++;
|
|
363
|
-
if (locked)
|
|
332
|
+
if (e.locked)
|
|
364
333
|
lockedCount++;
|
|
365
|
-
if (
|
|
334
|
+
if (e.stalenessSeconds === Infinity)
|
|
366
335
|
neverSynthesizedCount++;
|
|
367
|
-
if (
|
|
368
|
-
totalArchTokens +=
|
|
369
|
-
if (
|
|
370
|
-
totalBuilderTokens +=
|
|
371
|
-
if (
|
|
372
|
-
totalCriticTokens +=
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
lastSynthAt =
|
|
376
|
-
lastSynthPath =
|
|
336
|
+
if (e.architectTokens)
|
|
337
|
+
totalArchTokens += e.architectTokens;
|
|
338
|
+
if (e.builderTokens)
|
|
339
|
+
totalBuilderTokens += e.builderTokens;
|
|
340
|
+
if (e.criticTokens)
|
|
341
|
+
totalCriticTokens += e.criticTokens;
|
|
342
|
+
if (e.lastSynthesized &&
|
|
343
|
+
(!lastSynthAt || e.lastSynthesized > lastSynthAt)) {
|
|
344
|
+
lastSynthAt = e.lastSynthesized;
|
|
345
|
+
lastSynthPath = e.path;
|
|
377
346
|
}
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
const effectiveStaleness = (stalenessSeconds === Infinity
|
|
347
|
+
const depthFactor = Math.pow(1 + config.depthWeight, e.depth);
|
|
348
|
+
const effectiveStaleness = (e.stalenessSeconds === Infinity
|
|
381
349
|
? Number.MAX_SAFE_INTEGER
|
|
382
|
-
: stalenessSeconds) *
|
|
350
|
+
: e.stalenessSeconds) *
|
|
383
351
|
depthFactor *
|
|
384
|
-
emphasis;
|
|
352
|
+
e.emphasis;
|
|
385
353
|
if (effectiveStaleness > stalestEffective) {
|
|
386
354
|
stalestEffective = effectiveStaleness;
|
|
387
|
-
stalestPath =
|
|
355
|
+
stalestPath = e.path;
|
|
388
356
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
357
|
+
}
|
|
358
|
+
// Project fields
|
|
359
|
+
const fields = params.fields;
|
|
360
|
+
const items = entries.map((e) => {
|
|
361
|
+
const stalenessDisplay = e.stalenessSeconds === Infinity
|
|
362
|
+
? 'never-synthesized'
|
|
363
|
+
: Math.round(e.stalenessSeconds);
|
|
364
|
+
const display = {
|
|
365
|
+
path: e.path,
|
|
366
|
+
depth: e.depth,
|
|
367
|
+
emphasis: e.emphasis,
|
|
368
|
+
stalenessSeconds: stalenessDisplay,
|
|
369
|
+
lastSynthesized: e.lastSynthesized,
|
|
370
|
+
hasError: e.hasError,
|
|
371
|
+
locked: e.locked,
|
|
372
|
+
architectTokens: e.architectTokens,
|
|
373
|
+
builderTokens: e.builderTokens,
|
|
374
|
+
criticTokens: e.criticTokens,
|
|
375
|
+
children: e.children,
|
|
406
376
|
};
|
|
407
377
|
if (fields) {
|
|
408
378
|
const projected = {};
|
|
409
379
|
for (const f of fields) {
|
|
410
|
-
if (f in
|
|
411
|
-
projected[f] =
|
|
380
|
+
if (f in display)
|
|
381
|
+
projected[f] = display[f];
|
|
412
382
|
}
|
|
413
|
-
|
|
383
|
+
return projected;
|
|
414
384
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
}
|
|
385
|
+
return display;
|
|
386
|
+
});
|
|
419
387
|
return ok({
|
|
420
388
|
summary: {
|
|
421
|
-
total:
|
|
389
|
+
total: entries.length,
|
|
422
390
|
stale: staleCount,
|
|
423
391
|
errors: errorCount,
|
|
424
392
|
locked: lockedCount,
|
|
@@ -432,7 +400,11 @@ function registerSynthTools(api) {
|
|
|
432
400
|
lastSynthesizedPath: lastSynthPath,
|
|
433
401
|
lastSynthesizedAt: lastSynthAt,
|
|
434
402
|
},
|
|
435
|
-
items:
|
|
403
|
+
items: items.sort((a, b) => {
|
|
404
|
+
const ap = typeof a.path === 'string' ? a.path : '';
|
|
405
|
+
const bp = typeof b.path === 'string' ? b.path : '';
|
|
406
|
+
return ap.localeCompare(bp);
|
|
407
|
+
}),
|
|
436
408
|
});
|
|
437
409
|
}
|
|
438
410
|
catch (error) {
|
|
@@ -475,13 +447,14 @@ function registerSynthTools(api) {
|
|
|
475
447
|
'_feedback',
|
|
476
448
|
]);
|
|
477
449
|
const fields = params.fields;
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
const targetNode = findNode(tree, targetPath);
|
|
450
|
+
const result = await listMetas(getConfig(), getWatcher());
|
|
451
|
+
const targetNode = findNode(result.tree, targetPath);
|
|
481
452
|
if (!targetNode) {
|
|
482
453
|
return fail('Meta path not found: ' + targetPath);
|
|
483
454
|
}
|
|
484
|
-
const
|
|
455
|
+
const { readFileSync } = await import('node:fs');
|
|
456
|
+
const { join } = await import('node:path');
|
|
457
|
+
const meta = JSON.parse(readFileSync(join(targetNode.metaPath, 'meta.json'), 'utf8'));
|
|
485
458
|
// Apply field projection
|
|
486
459
|
const projectMeta = (m) => {
|
|
487
460
|
if (fields) {
|
|
@@ -490,7 +463,6 @@ function registerSynthTools(api) {
|
|
|
490
463
|
result[f] = m[f];
|
|
491
464
|
return result;
|
|
492
465
|
}
|
|
493
|
-
// Default: exclude big text blobs
|
|
494
466
|
const result = {};
|
|
495
467
|
for (const [k, v] of Object.entries(m)) {
|
|
496
468
|
if (!defaultExclude.has(k))
|
|
@@ -510,7 +482,6 @@ function registerSynthTools(api) {
|
|
|
510
482
|
const limit = typeof includeArchive === 'number'
|
|
511
483
|
? includeArchive
|
|
512
484
|
: archiveFiles.length;
|
|
513
|
-
// Most recent first (files are sorted by timestamp)
|
|
514
485
|
const selected = archiveFiles.slice(-limit).reverse();
|
|
515
486
|
const archives = selected.map((af) => {
|
|
516
487
|
const raw = readFileSync(join(targetNode.metaPath, 'archive', af), 'utf8');
|
|
@@ -542,27 +513,27 @@ function registerSynthTools(api) {
|
|
|
542
513
|
execute: async (_id, params) => {
|
|
543
514
|
try {
|
|
544
515
|
const targetPath = params.path;
|
|
545
|
-
const
|
|
546
|
-
const
|
|
516
|
+
const config = getConfig();
|
|
517
|
+
const watcher = getWatcher();
|
|
518
|
+
const result = await listMetas(config, watcher);
|
|
547
519
|
let targetNode;
|
|
548
520
|
if (targetPath) {
|
|
549
521
|
const normalized = normalizePath(targetPath);
|
|
550
|
-
targetNode = findNode(tree, normalized);
|
|
522
|
+
targetNode = findNode(result.tree, normalized);
|
|
551
523
|
if (!targetNode) {
|
|
552
524
|
return fail('Meta path not found: ' + targetPath);
|
|
553
525
|
}
|
|
554
526
|
}
|
|
555
527
|
else {
|
|
556
|
-
// Select stalest
|
|
557
|
-
const candidates =
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
const weighted = computeEffectiveStaleness(candidates, getConfig().depthWeight);
|
|
528
|
+
// Select stalest candidate
|
|
529
|
+
const candidates = result.entries
|
|
530
|
+
.filter((e) => e.stalenessSeconds > 0)
|
|
531
|
+
.map((e) => ({
|
|
532
|
+
node: e.node,
|
|
533
|
+
meta: e.meta,
|
|
534
|
+
actualStaleness: e.stalenessSeconds,
|
|
535
|
+
}));
|
|
536
|
+
const weighted = computeEffectiveStaleness(candidates, config.depthWeight);
|
|
566
537
|
const winner = selectCandidate(weighted);
|
|
567
538
|
if (!winner) {
|
|
568
539
|
return ok({
|
|
@@ -571,23 +542,20 @@ function registerSynthTools(api) {
|
|
|
571
542
|
}
|
|
572
543
|
targetNode = winner.node;
|
|
573
544
|
}
|
|
574
|
-
const
|
|
575
|
-
const
|
|
576
|
-
|
|
545
|
+
const { readFileSync: readMeta } = await import('node:fs');
|
|
546
|
+
const { join: joinMeta } = await import('node:path');
|
|
547
|
+
const meta = JSON.parse(readMeta(joinMeta(targetNode.metaPath, 'meta.json'), 'utf8'));
|
|
548
|
+
// Scope files
|
|
577
549
|
const allScanFiles = await paginatedScan(watcher, {
|
|
578
550
|
pathPrefix: targetNode.ownerPath,
|
|
579
551
|
});
|
|
580
552
|
const allFiles = allScanFiles.map((f) => f.file_path);
|
|
581
553
|
const scopeFiles = filterInScope(targetNode, allFiles);
|
|
582
|
-
// Structure hash on scope-filtered files (matches orchestrator)
|
|
583
554
|
const structureHash = computeStructureHash(scopeFiles);
|
|
584
555
|
const structureChanged = structureHash !== meta._structureHash;
|
|
585
|
-
// Steer change
|
|
586
556
|
const latestArchive = readLatestArchive(targetNode.metaPath);
|
|
587
557
|
const steerChanged = hasSteerChanged(meta._steer, latestArchive?._steer, Boolean(latestArchive));
|
|
588
|
-
|
|
589
|
-
const architectTriggered = isArchitectTriggered(meta, structureChanged, steerChanged, getConfig().architectEvery);
|
|
590
|
-
// Delta files
|
|
558
|
+
const architectTriggered = isArchitectTriggered(meta, structureChanged, steerChanged, config.architectEvery);
|
|
591
559
|
let deltaFiles = [];
|
|
592
560
|
if (meta._generatedAt) {
|
|
593
561
|
const modifiedAfter = Math.floor(new Date(meta._generatedAt).getTime() / 1000);
|
|
@@ -622,7 +590,7 @@ function registerSynthTools(api) {
|
|
|
622
590
|
...(!meta._builder ? ['no cached builder (first run)'] : []),
|
|
623
591
|
...(structureChanged ? ['structure changed'] : []),
|
|
624
592
|
...(steerChanged ? ['steer changed'] : []),
|
|
625
|
-
...((meta._synthesisCount ?? 0) >=
|
|
593
|
+
...((meta._synthesisCount ?? 0) >= config.architectEvery
|
|
626
594
|
? ['periodic refresh (architectEvery)']
|
|
627
595
|
: []),
|
|
628
596
|
],
|
|
@@ -654,22 +622,14 @@ function registerSynthTools(api) {
|
|
|
654
622
|
try {
|
|
655
623
|
const { orchestrate } = await import('@karmaniverous/jeeves-meta');
|
|
656
624
|
const { GatewayExecutor } = await import('@karmaniverous/jeeves-meta');
|
|
657
|
-
// Load config from canonical config file
|
|
658
625
|
const config = getConfig();
|
|
659
626
|
const executor = new GatewayExecutor({
|
|
660
627
|
gatewayUrl: config.gatewayUrl,
|
|
661
628
|
apiKey: config.gatewayApiKey,
|
|
662
629
|
});
|
|
663
|
-
const watcher =
|
|
664
|
-
// If path specified, temporarily override watchPaths to target it
|
|
630
|
+
const watcher = getWatcher();
|
|
665
631
|
const targetPath = params.path;
|
|
666
|
-
const
|
|
667
|
-
? {
|
|
668
|
-
...config,
|
|
669
|
-
watchPaths: [targetPath.replace(/[/\\]\.meta[/\\]?$/, '')],
|
|
670
|
-
}
|
|
671
|
-
: config;
|
|
672
|
-
const results = await orchestrate(effectiveConfig, executor, watcher);
|
|
632
|
+
const results = await orchestrate(config, executor, watcher, targetPath);
|
|
673
633
|
const synthesized = results.filter((r) => r.synthesized);
|
|
674
634
|
if (synthesized.length === 0) {
|
|
675
635
|
return ok({
|
|
@@ -712,46 +672,28 @@ function registerSynthTools(api) {
|
|
|
712
672
|
* 2. No entities found - ACTION REQUIRED with setup guidance
|
|
713
673
|
* 3. Healthy - entity stats + tool listing + skill reference
|
|
714
674
|
*
|
|
715
|
-
* @param
|
|
675
|
+
* @param config - Full synth config (for listMetas and watcherUrl).
|
|
716
676
|
* @returns Markdown string for the Meta section.
|
|
717
677
|
*/
|
|
718
|
-
async function generateMetaMenu(
|
|
719
|
-
let
|
|
678
|
+
async function generateMetaMenu(config) {
|
|
679
|
+
let result;
|
|
720
680
|
try {
|
|
721
|
-
const watcher = new HttpWatcherClient({ baseUrl: watcherUrl });
|
|
722
|
-
|
|
723
|
-
filter: {
|
|
724
|
-
must: [
|
|
725
|
-
{
|
|
726
|
-
key: 'domains',
|
|
727
|
-
match: { value: 'synth-meta' },
|
|
728
|
-
},
|
|
729
|
-
],
|
|
730
|
-
},
|
|
731
|
-
fields: [
|
|
732
|
-
'synth_depth',
|
|
733
|
-
'synth_emphasis',
|
|
734
|
-
'synth_synthesis_count',
|
|
735
|
-
'synth_architect_tokens',
|
|
736
|
-
'synth_builder_tokens',
|
|
737
|
-
'synth_critic_tokens',
|
|
738
|
-
'synth_error_step',
|
|
739
|
-
'generated_at_unix',
|
|
740
|
-
'has_error',
|
|
741
|
-
],
|
|
742
|
-
});
|
|
681
|
+
const watcher = new HttpWatcherClient({ baseUrl: config.watcherUrl });
|
|
682
|
+
result = await listMetas(config, watcher);
|
|
743
683
|
}
|
|
744
684
|
catch {
|
|
745
685
|
return [
|
|
746
686
|
'> **ACTION REQUIRED: jeeves-watcher is unreachable.**',
|
|
747
687
|
'> The jeeves-meta synthesis engine requires a running jeeves-watcher service.',
|
|
748
|
-
|
|
688
|
+
'> The watcher API at ' +
|
|
689
|
+
config.watcherUrl +
|
|
690
|
+
' is down or not configured.',
|
|
749
691
|
'>',
|
|
750
692
|
"> **Read the `jeeves-meta` skill's Bootstrap section immediately**",
|
|
751
693
|
'> for setup instructions. Do not attempt synthesis until watcher is available.',
|
|
752
694
|
].join('\n');
|
|
753
695
|
}
|
|
754
|
-
if (
|
|
696
|
+
if (result.entries.length === 0) {
|
|
755
697
|
return [
|
|
756
698
|
'> **ACTION REQUIRED: No synthesis entities found.**',
|
|
757
699
|
'> The watcher is running but no `.meta/` directories were discovered',
|
|
@@ -761,55 +703,7 @@ async function generateMetaMenu(watcherUrl) {
|
|
|
761
703
|
'> on creating `.meta/` directories and configuring watch paths.',
|
|
762
704
|
].join('\n');
|
|
763
705
|
}
|
|
764
|
-
|
|
765
|
-
const now = Math.floor(Date.now() / 1000);
|
|
766
|
-
let staleCount = 0;
|
|
767
|
-
let errorCount = 0;
|
|
768
|
-
let neverSynthesized = 0;
|
|
769
|
-
let totalArchTokens = 0;
|
|
770
|
-
let totalBuilderTokens = 0;
|
|
771
|
-
let totalCriticTokens = 0;
|
|
772
|
-
let stalestPath = '';
|
|
773
|
-
let stalestAge = 0;
|
|
774
|
-
let lastSynthPath = '';
|
|
775
|
-
let lastSynthUnix = 0;
|
|
776
|
-
for (const e of entities) {
|
|
777
|
-
const generatedAt = e['generated_at_unix'];
|
|
778
|
-
const hasError = e['has_error'];
|
|
779
|
-
const archTokens = e['synth_architect_tokens'];
|
|
780
|
-
const builderTokens = e['synth_builder_tokens'];
|
|
781
|
-
const criticTokens = e['synth_critic_tokens'];
|
|
782
|
-
if (!generatedAt) {
|
|
783
|
-
neverSynthesized++;
|
|
784
|
-
staleCount++;
|
|
785
|
-
if (!isFinite(stalestAge)) ;
|
|
786
|
-
else {
|
|
787
|
-
stalestAge = Infinity;
|
|
788
|
-
stalestPath = e.file_path;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
const age = now - generatedAt;
|
|
793
|
-
if (age > 0)
|
|
794
|
-
staleCount++;
|
|
795
|
-
if (age > stalestAge && isFinite(age)) {
|
|
796
|
-
stalestAge = age;
|
|
797
|
-
stalestPath = e.file_path;
|
|
798
|
-
}
|
|
799
|
-
if (generatedAt > lastSynthUnix) {
|
|
800
|
-
lastSynthUnix = generatedAt;
|
|
801
|
-
lastSynthPath = e.file_path;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
if (hasError)
|
|
805
|
-
errorCount++;
|
|
806
|
-
if (archTokens)
|
|
807
|
-
totalArchTokens += archTokens;
|
|
808
|
-
if (builderTokens)
|
|
809
|
-
totalBuilderTokens += builderTokens;
|
|
810
|
-
if (criticTokens)
|
|
811
|
-
totalCriticTokens += criticTokens;
|
|
812
|
-
}
|
|
706
|
+
const { summary, entries } = result;
|
|
813
707
|
const formatAge = (seconds) => {
|
|
814
708
|
if (!isFinite(seconds))
|
|
815
709
|
return 'never synthesized';
|
|
@@ -819,25 +713,42 @@ async function generateMetaMenu(watcherUrl) {
|
|
|
819
713
|
return Math.round(seconds / 3600).toString() + 'h';
|
|
820
714
|
return Math.round(seconds / 86400).toString() + 'd';
|
|
821
715
|
};
|
|
716
|
+
// Find stalest age for display
|
|
717
|
+
let stalestAge = 0;
|
|
718
|
+
for (const e of entries) {
|
|
719
|
+
if (e.stalenessSeconds > stalestAge)
|
|
720
|
+
stalestAge = e.stalenessSeconds;
|
|
721
|
+
}
|
|
722
|
+
const stalestDisplay = summary.stalestPath
|
|
723
|
+
? summary.stalestPath + ' (' + formatAge(stalestAge) + ')'
|
|
724
|
+
: 'n/a';
|
|
725
|
+
const lastSynthDisplay = summary.lastSynthesizedAt
|
|
726
|
+
? (summary.lastSynthesizedPath ?? '') +
|
|
727
|
+
' (' +
|
|
728
|
+
summary.lastSynthesizedAt +
|
|
729
|
+
')'
|
|
730
|
+
: 'n/a';
|
|
822
731
|
const lines = [
|
|
823
|
-
|
|
732
|
+
'The jeeves-meta synthesis engine manages ' +
|
|
733
|
+
entries.length.toString() +
|
|
734
|
+
' meta entities.',
|
|
824
735
|
'',
|
|
825
736
|
'### Entity Summary',
|
|
826
737
|
'| Metric | Value |',
|
|
827
738
|
'|--------|-------|',
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
739
|
+
'| Total | ' + summary.total.toString() + ' |',
|
|
740
|
+
'| Stale | ' + summary.stale.toString() + ' |',
|
|
741
|
+
'| Errors | ' + summary.errors.toString() + ' |',
|
|
742
|
+
'| Never synthesized | ' + summary.neverSynthesized.toString() + ' |',
|
|
743
|
+
'| Stalest | ' + stalestDisplay + ' |',
|
|
744
|
+
'| Last synthesized | ' + lastSynthDisplay + ' |',
|
|
834
745
|
'',
|
|
835
746
|
'### Token Usage (cumulative)',
|
|
836
747
|
'| Step | Tokens |',
|
|
837
748
|
'|------|--------|',
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
749
|
+
'| Architect | ' + summary.tokens.architect.toLocaleString() + ' |',
|
|
750
|
+
'| Builder | ' + summary.tokens.builder.toLocaleString() + ' |',
|
|
751
|
+
'| Critic | ' + summary.tokens.critic.toLocaleString() + ' |',
|
|
841
752
|
'',
|
|
842
753
|
'### Tools',
|
|
843
754
|
'| Tool | Description |',
|
|
@@ -926,8 +837,8 @@ function upsertMetaSection(existing, metaMenu) {
|
|
|
926
837
|
* @param watcherUrl - Watcher API base URL.
|
|
927
838
|
* @returns True if the file was updated.
|
|
928
839
|
*/
|
|
929
|
-
async function refreshToolsMd(api,
|
|
930
|
-
const menu = await generateMetaMenu(
|
|
840
|
+
async function refreshToolsMd(api, config) {
|
|
841
|
+
const menu = await generateMetaMenu(config);
|
|
931
842
|
if (menu === lastWrittenMenu) {
|
|
932
843
|
return false;
|
|
933
844
|
}
|
|
@@ -955,10 +866,10 @@ async function refreshToolsMd(api, watcherUrl) {
|
|
|
955
866
|
* @param api - Plugin API.
|
|
956
867
|
* @param watcherUrl - Watcher API base URL.
|
|
957
868
|
*/
|
|
958
|
-
function startToolsWriter(api,
|
|
869
|
+
function startToolsWriter(api, config) {
|
|
959
870
|
// Deferred initial write
|
|
960
871
|
setTimeout(() => {
|
|
961
|
-
refreshToolsMd(api,
|
|
872
|
+
refreshToolsMd(api, config).catch((err) => {
|
|
962
873
|
console.error('[jeeves-meta] Failed to write TOOLS.md:', err);
|
|
963
874
|
});
|
|
964
875
|
}, INITIAL_DELAY_MS);
|
|
@@ -967,7 +878,7 @@ function startToolsWriter(api, watcherUrl) {
|
|
|
967
878
|
clearInterval(intervalHandle);
|
|
968
879
|
}
|
|
969
880
|
intervalHandle = setInterval(() => {
|
|
970
|
-
refreshToolsMd(api,
|
|
881
|
+
refreshToolsMd(api, config).catch((err) => {
|
|
971
882
|
console.error('[jeeves-meta] Failed to refresh TOOLS.md:', err);
|
|
972
883
|
});
|
|
973
884
|
}, REFRESH_INTERVAL_MS);
|
|
@@ -990,12 +901,12 @@ function register(api) {
|
|
|
990
901
|
// Load config for rule registration and tools writer
|
|
991
902
|
const config = loadSynthConfig(getConfigPath(api));
|
|
992
903
|
// Register virtual rules with watcher (fire-and-forget at startup)
|
|
993
|
-
registerSynthRules(config.watcherUrl).catch((err) => {
|
|
904
|
+
registerSynthRules(config.watcherUrl, config).catch((err) => {
|
|
994
905
|
const message = err instanceof Error ? err.message : String(err);
|
|
995
906
|
console.error('[jeeves-meta] Failed to register virtual rules:', message);
|
|
996
907
|
});
|
|
997
908
|
// Start periodic TOOLS.md writer
|
|
998
|
-
startToolsWriter(api, config
|
|
909
|
+
startToolsWriter(api, config);
|
|
999
910
|
}
|
|
1000
911
|
|
|
1001
912
|
export { register as default };
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module promptInjection
|
|
8
8
|
*/
|
|
9
|
+
import { type SynthConfig } from '@karmaniverous/jeeves-meta';
|
|
9
10
|
/**
|
|
10
11
|
* Generate the Meta menu Markdown for TOOLS.md.
|
|
11
12
|
*
|
|
@@ -14,7 +15,7 @@
|
|
|
14
15
|
* 2. No entities found - ACTION REQUIRED with setup guidance
|
|
15
16
|
* 3. Healthy - entity stats + tool listing + skill reference
|
|
16
17
|
*
|
|
17
|
-
* @param
|
|
18
|
+
* @param config - Full synth config (for listMetas and watcherUrl).
|
|
18
19
|
* @returns Markdown string for the Meta section.
|
|
19
20
|
*/
|
|
20
|
-
export declare function generateMetaMenu(
|
|
21
|
+
export declare function generateMetaMenu(config: SynthConfig): Promise<string>;
|
package/dist/src/rules.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module rules
|
|
10
10
|
*/
|
|
11
|
+
import type { SynthConfig } from '@karmaniverous/jeeves-meta';
|
|
11
12
|
/**
|
|
12
13
|
* Register jeeves-meta virtual rules with the watcher.
|
|
13
14
|
*
|
|
@@ -16,4 +17,4 @@
|
|
|
16
17
|
*
|
|
17
18
|
* @param watcherUrl - Base URL for the watcher service.
|
|
18
19
|
*/
|
|
19
|
-
export declare function registerSynthRules(watcherUrl: string): Promise<void>;
|
|
20
|
+
export declare function registerSynthRules(watcherUrl: string, config: SynthConfig): Promise<void>;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module toolsWriter
|
|
8
8
|
*/
|
|
9
|
+
import { type SynthConfig } from '@karmaniverous/jeeves-meta';
|
|
9
10
|
import type { PluginApi } from './helpers.js';
|
|
10
11
|
/**
|
|
11
12
|
* Upsert the Meta section in TOOLS.md content.
|
|
@@ -23,4 +24,4 @@ export declare function upsertMetaSection(existing: string, metaMenu: string): s
|
|
|
23
24
|
* @param api - Plugin API.
|
|
24
25
|
* @param watcherUrl - Watcher API base URL.
|
|
25
26
|
*/
|
|
26
|
-
export declare function startToolsWriter(api: PluginApi,
|
|
27
|
+
export declare function startToolsWriter(api: PluginApi, config: SynthConfig): void;
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED