@karmaniverous/jeeves-meta-openclaw 0.1.2 → 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 +134 -242
- package/dist/src/promptInjection.d.ts +3 -2
- 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
|
|
|
@@ -255,6 +255,8 @@ function registerSynthTools(api) {
|
|
|
255
255
|
};
|
|
256
256
|
/** Derive watcherUrl from loaded config. */
|
|
257
257
|
const getWatcherUrl = () => getConfig().watcherUrl;
|
|
258
|
+
/** Create a watcher client. */
|
|
259
|
+
const getWatcher = () => new HttpWatcherClient({ baseUrl: getWatcherUrl() });
|
|
258
260
|
// ─── synth_list ──────────────────────────────────────────────
|
|
259
261
|
api.registerTool({
|
|
260
262
|
name: 'synth_list',
|
|
@@ -286,34 +288,31 @@ function registerSynthTools(api) {
|
|
|
286
288
|
execute: async (_id, params) => {
|
|
287
289
|
try {
|
|
288
290
|
const pathPrefix = params.pathPrefix;
|
|
289
|
-
const watcher = new HttpWatcherClient({ baseUrl: getWatcherUrl() });
|
|
290
291
|
const config = getConfig();
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
});
|
|
316
|
-
const entities = [];
|
|
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
|
|
317
316
|
let staleCount = 0;
|
|
318
317
|
let errorCount = 0;
|
|
319
318
|
let lockedCount = 0;
|
|
@@ -325,112 +324,69 @@ function registerSynthTools(api) {
|
|
|
325
324
|
let lastSynthAt = null;
|
|
326
325
|
let stalestPath = null;
|
|
327
326
|
let stalestEffective = -1;
|
|
328
|
-
for (const
|
|
329
|
-
|
|
330
|
-
const depth = typeof sf['synth_depth'] === 'number' ? sf['synth_depth'] : 0;
|
|
331
|
-
const emphasis = typeof sf['synth_emphasis'] === 'number' ? sf['synth_emphasis'] : 1;
|
|
332
|
-
const hasError = sf['has_error'] === true || sf['has_error'] === 'true';
|
|
333
|
-
const archTokens = typeof sf['synth_architect_tokens'] === 'number'
|
|
334
|
-
? sf['synth_architect_tokens']
|
|
335
|
-
: 0;
|
|
336
|
-
const buildTokens = typeof sf['synth_builder_tokens'] === 'number'
|
|
337
|
-
? sf['synth_builder_tokens']
|
|
338
|
-
: 0;
|
|
339
|
-
const critTokens = typeof sf['synth_critic_tokens'] === 'number'
|
|
340
|
-
? sf['synth_critic_tokens']
|
|
341
|
-
: 0;
|
|
342
|
-
const genAtUnix = typeof sf['generated_at_unix'] === 'number'
|
|
343
|
-
? sf['generated_at_unix']
|
|
344
|
-
: 0;
|
|
345
|
-
const locked = isLocked(normalizePath(filePath));
|
|
346
|
-
const neverSynth = genAtUnix === 0;
|
|
347
|
-
// Compute staleness from generated_at_unix
|
|
348
|
-
let stalenessSeconds;
|
|
349
|
-
if (neverSynth) {
|
|
350
|
-
stalenessSeconds = Infinity;
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
stalenessSeconds = Math.floor(Date.now() / 1000) - genAtUnix;
|
|
354
|
-
if (stalenessSeconds < 0)
|
|
355
|
-
stalenessSeconds = 0;
|
|
356
|
-
}
|
|
357
|
-
// Apply structured filter
|
|
358
|
-
const filter = params.filter;
|
|
359
|
-
if (filter) {
|
|
360
|
-
if (filter.hasError !== undefined && hasError !== filter.hasError)
|
|
361
|
-
continue;
|
|
362
|
-
if (filter.neverSynthesized !== undefined &&
|
|
363
|
-
neverSynth !== filter.neverSynthesized)
|
|
364
|
-
continue;
|
|
365
|
-
if (filter.locked !== undefined && locked !== filter.locked)
|
|
366
|
-
continue;
|
|
367
|
-
if (typeof filter.staleHours === 'number' &&
|
|
368
|
-
stalenessSeconds < filter.staleHours * 3600)
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
if (stalenessSeconds > 0)
|
|
327
|
+
for (const e of entries) {
|
|
328
|
+
if (e.stalenessSeconds > 0)
|
|
372
329
|
staleCount++;
|
|
373
|
-
if (hasError)
|
|
330
|
+
if (e.hasError)
|
|
374
331
|
errorCount++;
|
|
375
|
-
if (locked)
|
|
332
|
+
if (e.locked)
|
|
376
333
|
lockedCount++;
|
|
377
|
-
if (
|
|
334
|
+
if (e.stalenessSeconds === Infinity)
|
|
378
335
|
neverSynthesizedCount++;
|
|
379
|
-
if (
|
|
380
|
-
totalArchTokens +=
|
|
381
|
-
if (
|
|
382
|
-
totalBuilderTokens +=
|
|
383
|
-
if (
|
|
384
|
-
totalCriticTokens +=
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
lastSynthAt =
|
|
388
|
-
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;
|
|
389
346
|
}
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
const effectiveStaleness = (stalenessSeconds === Infinity
|
|
347
|
+
const depthFactor = Math.pow(1 + config.depthWeight, e.depth);
|
|
348
|
+
const effectiveStaleness = (e.stalenessSeconds === Infinity
|
|
393
349
|
? Number.MAX_SAFE_INTEGER
|
|
394
|
-
: stalenessSeconds) *
|
|
350
|
+
: e.stalenessSeconds) *
|
|
395
351
|
depthFactor *
|
|
396
|
-
emphasis;
|
|
352
|
+
e.emphasis;
|
|
397
353
|
if (effectiveStaleness > stalestEffective) {
|
|
398
354
|
stalestEffective = effectiveStaleness;
|
|
399
|
-
stalestPath =
|
|
355
|
+
stalestPath = e.path;
|
|
400
356
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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,
|
|
418
376
|
};
|
|
419
377
|
if (fields) {
|
|
420
378
|
const projected = {};
|
|
421
379
|
for (const f of fields) {
|
|
422
|
-
if (f in
|
|
423
|
-
projected[f] =
|
|
380
|
+
if (f in display)
|
|
381
|
+
projected[f] = display[f];
|
|
424
382
|
}
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
entities.push(raw);
|
|
383
|
+
return projected;
|
|
429
384
|
}
|
|
430
|
-
|
|
385
|
+
return display;
|
|
386
|
+
});
|
|
431
387
|
return ok({
|
|
432
388
|
summary: {
|
|
433
|
-
total:
|
|
389
|
+
total: entries.length,
|
|
434
390
|
stale: staleCount,
|
|
435
391
|
errors: errorCount,
|
|
436
392
|
locked: lockedCount,
|
|
@@ -444,7 +400,11 @@ function registerSynthTools(api) {
|
|
|
444
400
|
lastSynthesizedPath: lastSynthPath,
|
|
445
401
|
lastSynthesizedAt: lastSynthAt,
|
|
446
402
|
},
|
|
447
|
-
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
|
+
}),
|
|
448
408
|
});
|
|
449
409
|
}
|
|
450
410
|
catch (error) {
|
|
@@ -487,10 +447,8 @@ function registerSynthTools(api) {
|
|
|
487
447
|
'_feedback',
|
|
488
448
|
]);
|
|
489
449
|
const fields = params.fields;
|
|
490
|
-
const
|
|
491
|
-
const
|
|
492
|
-
const tree = buildOwnershipTree(metaPaths);
|
|
493
|
-
const targetNode = findNode(tree, targetPath);
|
|
450
|
+
const result = await listMetas(getConfig(), getWatcher());
|
|
451
|
+
const targetNode = findNode(result.tree, targetPath);
|
|
494
452
|
if (!targetNode) {
|
|
495
453
|
return fail('Meta path not found: ' + targetPath);
|
|
496
454
|
}
|
|
@@ -505,7 +463,6 @@ function registerSynthTools(api) {
|
|
|
505
463
|
result[f] = m[f];
|
|
506
464
|
return result;
|
|
507
465
|
}
|
|
508
|
-
// Default: exclude big text blobs
|
|
509
466
|
const result = {};
|
|
510
467
|
for (const [k, v] of Object.entries(m)) {
|
|
511
468
|
if (!defaultExclude.has(k))
|
|
@@ -525,7 +482,6 @@ function registerSynthTools(api) {
|
|
|
525
482
|
const limit = typeof includeArchive === 'number'
|
|
526
483
|
? includeArchive
|
|
527
484
|
: archiveFiles.length;
|
|
528
|
-
// Most recent first (files are sorted by timestamp)
|
|
529
485
|
const selected = archiveFiles.slice(-limit).reverse();
|
|
530
486
|
const archives = selected.map((af) => {
|
|
531
487
|
const raw = readFileSync(join(targetNode.metaPath, 'archive', af), 'utf8');
|
|
@@ -557,35 +513,27 @@ function registerSynthTools(api) {
|
|
|
557
513
|
execute: async (_id, params) => {
|
|
558
514
|
try {
|
|
559
515
|
const targetPath = params.path;
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
const
|
|
516
|
+
const config = getConfig();
|
|
517
|
+
const watcher = getWatcher();
|
|
518
|
+
const result = await listMetas(config, watcher);
|
|
563
519
|
let targetNode;
|
|
564
520
|
if (targetPath) {
|
|
565
521
|
const normalized = normalizePath(targetPath);
|
|
566
|
-
targetNode = findNode(tree, normalized);
|
|
522
|
+
targetNode = findNode(result.tree, normalized);
|
|
567
523
|
if (!targetNode) {
|
|
568
524
|
return fail('Meta path not found: ' + targetPath);
|
|
569
525
|
}
|
|
570
526
|
}
|
|
571
527
|
else {
|
|
572
|
-
// Select stalest
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
candidates.push({ node, meta, actualStaleness: staleness });
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
catch {
|
|
585
|
-
// skip unreadable
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
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);
|
|
589
537
|
const winner = selectCandidate(weighted);
|
|
590
538
|
if (!winner) {
|
|
591
539
|
return ok({
|
|
@@ -597,21 +545,17 @@ function registerSynthTools(api) {
|
|
|
597
545
|
const { readFileSync: readMeta } = await import('node:fs');
|
|
598
546
|
const { join: joinMeta } = await import('node:path');
|
|
599
547
|
const meta = JSON.parse(readMeta(joinMeta(targetNode.metaPath, 'meta.json'), 'utf8'));
|
|
600
|
-
// Scope files
|
|
548
|
+
// Scope files
|
|
601
549
|
const allScanFiles = await paginatedScan(watcher, {
|
|
602
550
|
pathPrefix: targetNode.ownerPath,
|
|
603
551
|
});
|
|
604
552
|
const allFiles = allScanFiles.map((f) => f.file_path);
|
|
605
553
|
const scopeFiles = filterInScope(targetNode, allFiles);
|
|
606
|
-
// Structure hash on scope-filtered files (matches orchestrator)
|
|
607
554
|
const structureHash = computeStructureHash(scopeFiles);
|
|
608
555
|
const structureChanged = structureHash !== meta._structureHash;
|
|
609
|
-
// Steer change
|
|
610
556
|
const latestArchive = readLatestArchive(targetNode.metaPath);
|
|
611
557
|
const steerChanged = hasSteerChanged(meta._steer, latestArchive?._steer, Boolean(latestArchive));
|
|
612
|
-
|
|
613
|
-
const architectTriggered = isArchitectTriggered(meta, structureChanged, steerChanged, getConfig().architectEvery);
|
|
614
|
-
// Delta files
|
|
558
|
+
const architectTriggered = isArchitectTriggered(meta, structureChanged, steerChanged, config.architectEvery);
|
|
615
559
|
let deltaFiles = [];
|
|
616
560
|
if (meta._generatedAt) {
|
|
617
561
|
const modifiedAfter = Math.floor(new Date(meta._generatedAt).getTime() / 1000);
|
|
@@ -646,7 +590,7 @@ function registerSynthTools(api) {
|
|
|
646
590
|
...(!meta._builder ? ['no cached builder (first run)'] : []),
|
|
647
591
|
...(structureChanged ? ['structure changed'] : []),
|
|
648
592
|
...(steerChanged ? ['steer changed'] : []),
|
|
649
|
-
...((meta._synthesisCount ?? 0) >=
|
|
593
|
+
...((meta._synthesisCount ?? 0) >= config.architectEvery
|
|
650
594
|
? ['periodic refresh (architectEvery)']
|
|
651
595
|
: []),
|
|
652
596
|
],
|
|
@@ -678,13 +622,12 @@ function registerSynthTools(api) {
|
|
|
678
622
|
try {
|
|
679
623
|
const { orchestrate } = await import('@karmaniverous/jeeves-meta');
|
|
680
624
|
const { GatewayExecutor } = await import('@karmaniverous/jeeves-meta');
|
|
681
|
-
// Load config from canonical config file
|
|
682
625
|
const config = getConfig();
|
|
683
626
|
const executor = new GatewayExecutor({
|
|
684
627
|
gatewayUrl: config.gatewayUrl,
|
|
685
628
|
apiKey: config.gatewayApiKey,
|
|
686
629
|
});
|
|
687
|
-
const watcher =
|
|
630
|
+
const watcher = getWatcher();
|
|
688
631
|
const targetPath = params.path;
|
|
689
632
|
const results = await orchestrate(config, executor, watcher, targetPath);
|
|
690
633
|
const synthesized = results.filter((r) => r.synthesized);
|
|
@@ -729,48 +672,28 @@ function registerSynthTools(api) {
|
|
|
729
672
|
* 2. No entities found - ACTION REQUIRED with setup guidance
|
|
730
673
|
* 3. Healthy - entity stats + tool listing + skill reference
|
|
731
674
|
*
|
|
732
|
-
* @param
|
|
675
|
+
* @param config - Full synth config (for listMetas and watcherUrl).
|
|
733
676
|
* @returns Markdown string for the Meta section.
|
|
734
677
|
*/
|
|
735
|
-
async function generateMetaMenu(
|
|
736
|
-
let
|
|
678
|
+
async function generateMetaMenu(config) {
|
|
679
|
+
let result;
|
|
737
680
|
try {
|
|
738
|
-
const watcher = new HttpWatcherClient({ baseUrl: watcherUrl });
|
|
739
|
-
|
|
740
|
-
filter: metaFilter,
|
|
741
|
-
fields: [
|
|
742
|
-
'synth_depth',
|
|
743
|
-
'synth_emphasis',
|
|
744
|
-
'synth_synthesis_count',
|
|
745
|
-
'synth_architect_tokens',
|
|
746
|
-
'synth_builder_tokens',
|
|
747
|
-
'synth_critic_tokens',
|
|
748
|
-
'synth_error_step',
|
|
749
|
-
'generated_at_unix',
|
|
750
|
-
'has_error',
|
|
751
|
-
],
|
|
752
|
-
});
|
|
681
|
+
const watcher = new HttpWatcherClient({ baseUrl: config.watcherUrl });
|
|
682
|
+
result = await listMetas(config, watcher);
|
|
753
683
|
}
|
|
754
684
|
catch {
|
|
755
685
|
return [
|
|
756
686
|
'> **ACTION REQUIRED: jeeves-watcher is unreachable.**',
|
|
757
687
|
'> The jeeves-meta synthesis engine requires a running jeeves-watcher service.',
|
|
758
|
-
|
|
688
|
+
'> The watcher API at ' +
|
|
689
|
+
config.watcherUrl +
|
|
690
|
+
' is down or not configured.',
|
|
759
691
|
'>',
|
|
760
692
|
"> **Read the `jeeves-meta` skill's Bootstrap section immediately**",
|
|
761
693
|
'> for setup instructions. Do not attempt synthesis until watcher is available.',
|
|
762
694
|
].join('\n');
|
|
763
695
|
}
|
|
764
|
-
|
|
765
|
-
const seenPaths = new Set();
|
|
766
|
-
entities = entities.filter((e) => {
|
|
767
|
-
const fp = e.file_path;
|
|
768
|
-
if (seenPaths.has(fp))
|
|
769
|
-
return false;
|
|
770
|
-
seenPaths.add(fp);
|
|
771
|
-
return true;
|
|
772
|
-
});
|
|
773
|
-
if (entities.length === 0) {
|
|
696
|
+
if (result.entries.length === 0) {
|
|
774
697
|
return [
|
|
775
698
|
'> **ACTION REQUIRED: No synthesis entities found.**',
|
|
776
699
|
'> The watcher is running but no `.meta/` directories were discovered',
|
|
@@ -780,55 +703,7 @@ async function generateMetaMenu(watcherUrl, metaFilter) {
|
|
|
780
703
|
'> on creating `.meta/` directories and configuring watch paths.',
|
|
781
704
|
].join('\n');
|
|
782
705
|
}
|
|
783
|
-
|
|
784
|
-
const now = Math.floor(Date.now() / 1000);
|
|
785
|
-
let staleCount = 0;
|
|
786
|
-
let errorCount = 0;
|
|
787
|
-
let neverSynthesized = 0;
|
|
788
|
-
let totalArchTokens = 0;
|
|
789
|
-
let totalBuilderTokens = 0;
|
|
790
|
-
let totalCriticTokens = 0;
|
|
791
|
-
let stalestPath = '';
|
|
792
|
-
let stalestAge = 0;
|
|
793
|
-
let lastSynthPath = '';
|
|
794
|
-
let lastSynthUnix = 0;
|
|
795
|
-
for (const e of entities) {
|
|
796
|
-
const generatedAt = e['generated_at_unix'];
|
|
797
|
-
const hasError = e['has_error'];
|
|
798
|
-
const archTokens = e['synth_architect_tokens'];
|
|
799
|
-
const builderTokens = e['synth_builder_tokens'];
|
|
800
|
-
const criticTokens = e['synth_critic_tokens'];
|
|
801
|
-
if (!generatedAt) {
|
|
802
|
-
neverSynthesized++;
|
|
803
|
-
staleCount++;
|
|
804
|
-
if (!isFinite(stalestAge)) ;
|
|
805
|
-
else {
|
|
806
|
-
stalestAge = Infinity;
|
|
807
|
-
stalestPath = e.file_path;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
else {
|
|
811
|
-
const age = now - generatedAt;
|
|
812
|
-
if (age > 0)
|
|
813
|
-
staleCount++;
|
|
814
|
-
if (age > stalestAge && isFinite(age)) {
|
|
815
|
-
stalestAge = age;
|
|
816
|
-
stalestPath = e.file_path;
|
|
817
|
-
}
|
|
818
|
-
if (generatedAt > lastSynthUnix) {
|
|
819
|
-
lastSynthUnix = generatedAt;
|
|
820
|
-
lastSynthPath = e.file_path;
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
if (hasError)
|
|
824
|
-
errorCount++;
|
|
825
|
-
if (archTokens)
|
|
826
|
-
totalArchTokens += archTokens;
|
|
827
|
-
if (builderTokens)
|
|
828
|
-
totalBuilderTokens += builderTokens;
|
|
829
|
-
if (criticTokens)
|
|
830
|
-
totalCriticTokens += criticTokens;
|
|
831
|
-
}
|
|
706
|
+
const { summary, entries } = result;
|
|
832
707
|
const formatAge = (seconds) => {
|
|
833
708
|
if (!isFinite(seconds))
|
|
834
709
|
return 'never synthesized';
|
|
@@ -838,25 +713,42 @@ async function generateMetaMenu(watcherUrl, metaFilter) {
|
|
|
838
713
|
return Math.round(seconds / 3600).toString() + 'h';
|
|
839
714
|
return Math.round(seconds / 86400).toString() + 'd';
|
|
840
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';
|
|
841
731
|
const lines = [
|
|
842
|
-
|
|
732
|
+
'The jeeves-meta synthesis engine manages ' +
|
|
733
|
+
entries.length.toString() +
|
|
734
|
+
' meta entities.',
|
|
843
735
|
'',
|
|
844
736
|
'### Entity Summary',
|
|
845
737
|
'| Metric | Value |',
|
|
846
738
|
'|--------|-------|',
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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 + ' |',
|
|
853
745
|
'',
|
|
854
746
|
'### Token Usage (cumulative)',
|
|
855
747
|
'| Step | Tokens |',
|
|
856
748
|
'|------|--------|',
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
749
|
+
'| Architect | ' + summary.tokens.architect.toLocaleString() + ' |',
|
|
750
|
+
'| Builder | ' + summary.tokens.builder.toLocaleString() + ' |',
|
|
751
|
+
'| Critic | ' + summary.tokens.critic.toLocaleString() + ' |',
|
|
860
752
|
'',
|
|
861
753
|
'### Tools',
|
|
862
754
|
'| Tool | Description |',
|
|
@@ -946,7 +838,7 @@ function upsertMetaSection(existing, metaMenu) {
|
|
|
946
838
|
* @returns True if the file was updated.
|
|
947
839
|
*/
|
|
948
840
|
async function refreshToolsMd(api, config) {
|
|
949
|
-
const menu = await generateMetaMenu(config
|
|
841
|
+
const menu = await generateMetaMenu(config);
|
|
950
842
|
if (menu === lastWrittenMenu) {
|
|
951
843
|
return false;
|
|
952
844
|
}
|
|
@@ -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/openclaw.plugin.json
CHANGED
package/package.json
CHANGED