@shadowforge0/aquifer-memory 1.8.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -0
- package/README.md +49 -22
- package/README_CN.md +24 -22
- package/README_TW.md +20 -22
- package/aquifer.config.example.json +2 -1
- package/consumers/cli.js +535 -2
- package/consumers/codex.js +1 -1
- package/consumers/mcp.js +3 -0
- package/consumers/openclaw-ext/index.js +64 -6
- package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
- package/consumers/openclaw-ext/package.json +1 -1
- package/consumers/openclaw-install.js +326 -0
- package/consumers/openclaw-plugin.js +39 -1
- package/consumers/shared/config.js +2 -0
- package/core/aquifer.js +180 -33
- package/core/backends/local.js +109 -0
- package/core/doctor.js +924 -0
- package/core/finalization-inspector.js +164 -0
- package/core/memory-explain.js +624 -0
- package/core/memory-recall.js +49 -23
- package/core/memory-records.js +16 -5
- package/core/memory-review.js +891 -0
- package/core/memory-serving.js +61 -4
- package/core/operator-observability.js +249 -0
- package/core/postgres-migrations.js +13 -0
- package/core/session-finalization.js +76 -1
- package/core/storage.js +124 -8
- package/docs/getting-started.md +34 -1
- package/docs/setup.md +102 -22
- package/package.json +5 -4
- package/schema/019-v1-memory-review-resolutions.sql +53 -0
- package/scripts/codex-checkpoint-commands.js +28 -0
- package/scripts/codex-checkpoint-runtime.js +109 -0
- package/scripts/codex-recovery.js +16 -4
package/consumers/cli.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* aquifer backfill [options] Enrich pending sessions
|
|
12
12
|
* aquifer stats [options] Show database statistics
|
|
13
13
|
* aquifer backend-info [--json] Show selected backend capabilities
|
|
14
|
+
* aquifer install-openclaw Install/update OpenClaw MCP + extension wiring
|
|
15
|
+
* aquifer finalization ... Inspect read-only finalization ledger
|
|
14
16
|
* aquifer export [options] Export sessions
|
|
15
17
|
* aquifer operator ... Run operator-safe consolidation jobs
|
|
16
18
|
* aquifer mcp Start MCP server
|
|
@@ -21,6 +23,7 @@ const { createAquiferFromConfig } = require('./shared/factory');
|
|
|
21
23
|
const { loadConfig } = require('./shared/config');
|
|
22
24
|
const { formatRecallResults } = require('./shared/recall-format');
|
|
23
25
|
const { backendCapabilities } = require('../core/backends/capabilities');
|
|
26
|
+
const { summarizeFinalizationListRow } = require('../core/finalization-inspector');
|
|
24
27
|
|
|
25
28
|
function formatDate(value, fallback) {
|
|
26
29
|
if (!value) return fallback;
|
|
@@ -183,6 +186,9 @@ function parseArgs(argv) {
|
|
|
183
186
|
'policy-version', 'worker-id', 'apply-token', 'claim-lease-seconds', 'snapshot-as-of',
|
|
184
187
|
'scope-id', 'scope-key', 'scope-keys', 'scope-kind', 'context-key', 'topic-key', 'authority', 'input',
|
|
185
188
|
'synthesis-summary', 'synthesis-summary-file', 'min-finalizations', 'checkpoint-key',
|
|
189
|
+
'openclaw-home', 'id', 'transcript-hash', 'phase', 'host', 'run-id', 'kind', 'query',
|
|
190
|
+
'memory-type', 'visibility', 'as-of', 'resolution', 'reason', 'actor-kind', 'actor-id',
|
|
191
|
+
'defer-until', 'expected-latest-issue-feedback-id', 'expected-latest-issue-feedback-at',
|
|
186
192
|
]);
|
|
187
193
|
for (let i = 0; i < argv.length; i++) {
|
|
188
194
|
if (argv[i] === '--') { args._.push(...argv.slice(i + 1)); break; }
|
|
@@ -412,6 +418,9 @@ async function cmdStats(aquifer, args) {
|
|
|
412
418
|
console.log(`Serving mode: ${stats.serving?.mode || 'legacy'}`);
|
|
413
419
|
console.log(`Active scope: ${stats.serving?.activeScopePath?.join(' > ') || stats.serving?.activeScopeKey || 'none'}`);
|
|
414
420
|
console.log(`Sessions: ${stats.sessionTotal} (${Object.entries(stats.sessions).map(([k, v]) => `${k}: ${v}`).join(', ')})`);
|
|
421
|
+
if (stats.pendingSessions?.available) {
|
|
422
|
+
console.log(`Actionable pending/failed: ${stats.pendingSessions.total}`);
|
|
423
|
+
}
|
|
415
424
|
console.log(`Summaries: ${stats.summaries}`);
|
|
416
425
|
console.log(`Turn embeddings: ${stats.turnEmbeddings}`);
|
|
417
426
|
console.log(`Entities: ${stats.entities}`);
|
|
@@ -473,6 +482,149 @@ async function cmdBackendInfo(args) {
|
|
|
473
482
|
}
|
|
474
483
|
}
|
|
475
484
|
|
|
485
|
+
function formatDoctor(result = {}) {
|
|
486
|
+
const checks = Array.isArray(result.checks) ? result.checks : [];
|
|
487
|
+
if (checks.length === 0) return `Doctor: ${result.status || 'unknown'}`;
|
|
488
|
+
return checks.map(check => {
|
|
489
|
+
const next = check.nextAction ? ` next=${check.nextAction}` : '';
|
|
490
|
+
return `${check.status || 'unknown'} ${check.id || 'check'}: ${check.summary || ''}${next}`;
|
|
491
|
+
}).join('\n');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async function cmdDoctor(aquifer, args) {
|
|
495
|
+
const result = await aquifer.doctor.run({
|
|
496
|
+
host: args.flags.host || undefined,
|
|
497
|
+
openclawHome: args.flags['openclaw-home'] || undefined,
|
|
498
|
+
limit: parsePositiveInt(args.flags.limit, 20),
|
|
499
|
+
});
|
|
500
|
+
if (args.flags.json) {
|
|
501
|
+
console.log(JSON.stringify(result, null, 2));
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
console.log(formatDoctor(result));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function formatTimestamp(value) {
|
|
508
|
+
if (!value) return '?';
|
|
509
|
+
const parsed = new Date(value);
|
|
510
|
+
return isNaN(parsed.getTime()) ? String(value) : parsed.toISOString();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function shortHash(value) {
|
|
514
|
+
if (!value) return '?';
|
|
515
|
+
return String(value).slice(0, 12);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function formatFinalizationList(rows = []) {
|
|
519
|
+
if (!rows || rows.length === 0) return 'No finalization rows found.';
|
|
520
|
+
return rows.map(row => {
|
|
521
|
+
const item = summarizeFinalizationListRow(row);
|
|
522
|
+
const session = `${item.source}/${item.agentId}/${item.sessionId}`;
|
|
523
|
+
const phase = item.phase ? ` phase=${item.phase}` : '';
|
|
524
|
+
const mode = item.mode ? ` mode=${item.mode}` : '';
|
|
525
|
+
const hash = item.transcriptHashPrefix ? ` hash=${item.transcriptHashPrefix}` : '';
|
|
526
|
+
return `#${item.id} ${item.status} ${session}${phase}${mode}${hash} updated=${formatTimestamp(item.updatedAt)}`;
|
|
527
|
+
}).join('\n');
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function formatFinalizationInspect(report = {}) {
|
|
531
|
+
const item = report.finalization || {};
|
|
532
|
+
const candidateSummary = report.candidateSummary || {};
|
|
533
|
+
const lineage = report.lineage || {};
|
|
534
|
+
const envelope = report.candidateEnvelope || {};
|
|
535
|
+
const memoryResult = report.memoryResult || {};
|
|
536
|
+
const lines = [
|
|
537
|
+
`Finalization #${item.id || '?'} ${item.status || 'unknown'}`,
|
|
538
|
+
`Session: ${item.source || '?'}/${item.agentId || '?'}/${item.sessionId || '?'} phase=${item.phase || '?'} mode=${item.mode || '?'} host=${item.host || '?'}`,
|
|
539
|
+
`Transcript hash: ${item.transcriptHashPrefix || shortHash(item.transcriptHash)}`,
|
|
540
|
+
`Updated: ${formatTimestamp(item.updatedAt)} finalized=${formatTimestamp(item.finalizedAt)}`,
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
if (item.finalizerModel) lines.push(`Finalizer: ${item.finalizerModel}`);
|
|
544
|
+
if (item.scopeKind || item.scopeKey || item.contextKey || item.topicKey) {
|
|
545
|
+
lines.push(`Scope: kind=${item.scopeKind || '?'} key=${item.scopeKey || '?'} context=${item.contextKey || '?'} topic=${item.topicKey || '?'}`);
|
|
546
|
+
}
|
|
547
|
+
if (item.error) lines.push(`Error: ${item.error}`);
|
|
548
|
+
|
|
549
|
+
lines.push(`Memory result: candidates=${memoryResult.candidates || 0} promoted=${memoryResult.promoted || 0} quarantined=${memoryResult.quarantined || 0} skipped=${memoryResult.skipped || 0}`);
|
|
550
|
+
lines.push(`Candidate envelope: version=${envelope.version || '?'} hash=${shortHash(envelope.hash)} candidates=${envelope.candidateCount || 0}`);
|
|
551
|
+
lines.push(`Candidate actions: total=${candidateSummary.total || 0} promote=${candidateSummary.promote || 0} quarantine=${candidateSummary.quarantine || 0} skip=${candidateSummary.skip || 0} supersede=${candidateSummary.supersede || 0} error=${candidateSummary.error || 0}`);
|
|
552
|
+
lines.push(`Lineage: memories=${(lineage.memoryRecordIds || []).join(',') || 'none'} facts=${(lineage.factAssertionIds || []).join(',') || 'none'} evidenceRefs=${lineage.evidenceRefCount || 0} evidenceItems=${lineage.evidenceItemCount || 0}`);
|
|
553
|
+
|
|
554
|
+
if (report.review?.humanReviewText) {
|
|
555
|
+
lines.push('\nHuman review:\n' + report.review.humanReviewText);
|
|
556
|
+
}
|
|
557
|
+
if (report.review?.sessionStartText) {
|
|
558
|
+
lines.push('\nSessionStart:\n' + report.review.sessionStartText);
|
|
559
|
+
}
|
|
560
|
+
if (Array.isArray(report.candidates) && report.candidates.length > 0) {
|
|
561
|
+
lines.push('\nCandidates:');
|
|
562
|
+
for (const candidate of report.candidates) {
|
|
563
|
+
lines.push(` [${candidate.index}] ${candidate.action}${candidate.reason ? `/${candidate.reason}` : ''} ${candidate.memoryType || '?'} ${candidate.canonicalKey || '?'}${candidate.memoryRecordId ? ` memory=${candidate.memoryRecordId}` : ''}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return lines.join('\n');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async function cmdFinalization(aquifer, args) {
|
|
570
|
+
const verb = args._[1] || 'list';
|
|
571
|
+
|
|
572
|
+
if (verb === 'list') {
|
|
573
|
+
const rows = await aquifer.finalization.list({
|
|
574
|
+
status: args.flags.status || undefined,
|
|
575
|
+
agentId: args.flags['agent-id'] || undefined,
|
|
576
|
+
source: args.flags.source || undefined,
|
|
577
|
+
host: args.flags.host || undefined,
|
|
578
|
+
sessionId: args.flags['session-id'] || undefined,
|
|
579
|
+
transcriptHash: args.flags['transcript-hash'] || undefined,
|
|
580
|
+
phase: args.flags.phase || undefined,
|
|
581
|
+
mode: args.flags.mode || undefined,
|
|
582
|
+
limit: parsePositiveInt(args.flags.limit, 50),
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
if (args.flags.json) {
|
|
586
|
+
console.log(JSON.stringify({
|
|
587
|
+
readOnly: true,
|
|
588
|
+
finalizations: rows.map(summarizeFinalizationListRow),
|
|
589
|
+
}, null, 2));
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
console.log(formatFinalizationList(rows));
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (verb === 'inspect') {
|
|
597
|
+
const id = args.flags.id || args._[2] || undefined;
|
|
598
|
+
const sessionId = args.flags['session-id'];
|
|
599
|
+
if (id && (sessionId || args.flags['agent-id'] || args.flags.source || args.flags['transcript-hash'])) {
|
|
600
|
+
console.error('Usage: --id is mutually exclusive with --session-id/--agent-id/--source/--transcript-hash.');
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
if (!id && (!sessionId || !args.flags['agent-id'] || !args.flags.source)) {
|
|
604
|
+
console.error('Usage: aquifer finalization inspect (--id ID | --session-id ID --agent-id ID --source SOURCE [--transcript-hash HASH]) [--json]');
|
|
605
|
+
process.exit(1);
|
|
606
|
+
}
|
|
607
|
+
const report = await aquifer.finalization.inspect({
|
|
608
|
+
id,
|
|
609
|
+
sessionId: sessionId || undefined,
|
|
610
|
+
agentId: args.flags['agent-id'] || undefined,
|
|
611
|
+
source: args.flags.source || undefined,
|
|
612
|
+
transcriptHash: args.flags['transcript-hash'] || undefined,
|
|
613
|
+
phase: args.flags.phase || undefined,
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
if (args.flags.json) {
|
|
617
|
+
console.log(JSON.stringify(report, null, 2));
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
console.log(formatFinalizationInspect(report));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
console.error('Usage: aquifer finalization <list|inspect> [options]');
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
|
|
476
628
|
async function cmdLocalQuickstart(aquifer) {
|
|
477
629
|
const cfg = aquifer.getConfig();
|
|
478
630
|
console.log('Aquifer quickstart — verifying local starter backend.\n');
|
|
@@ -644,10 +796,319 @@ async function cmdBootstrap(aquifer, args) {
|
|
|
644
796
|
}
|
|
645
797
|
}
|
|
646
798
|
|
|
799
|
+
function formatExplain(result = {}) {
|
|
800
|
+
const selected = Array.isArray(result.selected) ? result.selected : [];
|
|
801
|
+
const excluded = Array.isArray(result.excluded) ? result.excluded : [];
|
|
802
|
+
const lines = [
|
|
803
|
+
`Explain ${result.lane || 'memory'}: selected=${selected.length} excluded=${excluded.length}`,
|
|
804
|
+
];
|
|
805
|
+
if (selected.length > 0) {
|
|
806
|
+
lines.push('Selected:');
|
|
807
|
+
for (const row of selected) {
|
|
808
|
+
lines.push(` ${row.memoryId || row.id || '?'} ${row.canonicalKey || '?'} reason=${row.reason || 'selected'}`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (excluded.length > 0) {
|
|
812
|
+
lines.push('Excluded:');
|
|
813
|
+
for (const row of excluded) {
|
|
814
|
+
lines.push(` ${row.memoryId || row.id || '?'} ${row.canonicalKey || '?'} reason=${row.reason || 'unknown'}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (result.budget) {
|
|
818
|
+
lines.push(`Budget: limit=${result.budget.limit || '?'} maxChars=${result.budget.maxChars || '?'} overflow=${result.budget.overflow === true}`);
|
|
819
|
+
}
|
|
820
|
+
return lines.join('\n');
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function formatFeedbackCounts(counts = {}) {
|
|
824
|
+
const entries = Object.entries(counts).filter(([, count]) => count > 0);
|
|
825
|
+
if (entries.length === 0) return 'none';
|
|
826
|
+
return entries.map(([type, count]) => `${type}=${count}`).join(' ');
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function formatReviewResolution(resolution = null) {
|
|
830
|
+
if (!resolution || !resolution.resolution) return 'none';
|
|
831
|
+
const at = resolution.resolvedAt ? ` at=${formatTimestamp(resolution.resolvedAt)}` : '';
|
|
832
|
+
const defer = resolution.deferUntil ? ` deferUntil=${formatTimestamp(resolution.deferUntil)}` : '';
|
|
833
|
+
return `${resolution.resolution}${at}${defer}`;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function formatReviewQueue(result = {}) {
|
|
837
|
+
if (result.available === false) {
|
|
838
|
+
return result.error || 'Memory review queue is unavailable.';
|
|
839
|
+
}
|
|
840
|
+
const rows = Array.isArray(result.items) ? result.items : (Array.isArray(result.reviewQueue) ? result.reviewQueue : []);
|
|
841
|
+
if (rows.length === 0) return 'No memory review items found.';
|
|
842
|
+
return rows.map(row => {
|
|
843
|
+
const scope = row.scope?.key ? ` scope=${row.scope.key}` : '';
|
|
844
|
+
const latest = row.latestFeedbackAt ? ` latest=${formatTimestamp(row.latestFeedbackAt)}` : '';
|
|
845
|
+
const resolution = row.resolution ? ` resolution=${formatReviewResolution(row.resolution)}` : '';
|
|
846
|
+
const counts = formatFeedbackCounts(row.feedbackCounts);
|
|
847
|
+
return `#${row.memoryId} severity=${row.severityScore} issues=${row.issueCount}/${row.feedbackCount} ${row.status || '?'}${scope} ${row.canonicalKey || '?'} feedback=${counts}${latest}${resolution}`;
|
|
848
|
+
}).join('\n');
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function formatReviewInspect(result = {}) {
|
|
852
|
+
const memory = result.memory || {};
|
|
853
|
+
const review = result.review || {};
|
|
854
|
+
const visibility = memory.visibility || {};
|
|
855
|
+
const lines = [
|
|
856
|
+
`Memory #${memory.memoryId || '?'} ${memory.status || '?'} ${memory.canonicalKey || '?'}`,
|
|
857
|
+
`Scope: ${memory.scope?.kind || '?'}/${memory.scope?.key || '?'}`,
|
|
858
|
+
`Visibility: bootstrap=${visibility.bootstrap === true} recall=${visibility.recall === true}`,
|
|
859
|
+
`Feedback: issues=${review.issueCount || 0}/${review.feedbackCount || 0} ${formatFeedbackCounts(review.feedbackCounts)}`,
|
|
860
|
+
`Latest resolution: ${formatReviewResolution(review.latestResolution)}`,
|
|
861
|
+
];
|
|
862
|
+
const events = Array.isArray(review.recentFeedback) ? review.recentFeedback : [];
|
|
863
|
+
if (events.length > 0) {
|
|
864
|
+
lines.push('Recent feedback:');
|
|
865
|
+
for (const event of events) {
|
|
866
|
+
const actor = event.actorId ? `${event.actorKind || 'actor'}:${event.actorId}` : (event.actorKind || 'actor');
|
|
867
|
+
lines.push(` #${event.id} ${event.feedbackType} ${actor} at=${formatTimestamp(event.createdAt)}`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return lines.join('\n');
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function formatReviewResolve(result = {}) {
|
|
874
|
+
const resolution = result.resolution || {};
|
|
875
|
+
const memory = result.memory || {};
|
|
876
|
+
const issueCount = result.review?.issueFeedbackCount || 0;
|
|
877
|
+
return [
|
|
878
|
+
`Review resolution recorded: ${resolution.resolution || '?'} for memory #${memory.memoryId || resolution.memoryId || '?'}`,
|
|
879
|
+
`Canonical key: ${memory.canonicalKey || resolution.canonicalKey || '?'}`,
|
|
880
|
+
`Issue feedback covered: ${issueCount} ${formatFeedbackCounts(result.review?.issueFeedbackCounts)}`,
|
|
881
|
+
`Memory mutated: ${result.memoryMutated === true}`,
|
|
882
|
+
].join('\n');
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
async function cmdReview(aquifer, args) {
|
|
886
|
+
const verb = args._[1] || 'queue';
|
|
887
|
+
if (verb === 'queue') {
|
|
888
|
+
const activeScopePath = parseScopePath(args.flags['active-scope-path']);
|
|
889
|
+
const activeScopeKey = args.flags['active-scope-key']
|
|
890
|
+
|| args.flags['scope-key']
|
|
891
|
+
|| (activeScopePath ? activeScopePath[activeScopePath.length - 1] : undefined);
|
|
892
|
+
const result = await aquifer.review.queue({
|
|
893
|
+
feedbackType: args.flags['feedback-type'] || undefined,
|
|
894
|
+
memoryType: args.flags['memory-type'] || undefined,
|
|
895
|
+
visibility: args.flags.visibility || undefined,
|
|
896
|
+
activeScopeKey,
|
|
897
|
+
activeScopePath,
|
|
898
|
+
scopeKey: args.flags['scope-key'] || activeScopeKey,
|
|
899
|
+
canonicalKey: args.flags['canonical-key'] || undefined,
|
|
900
|
+
dateFrom: args.flags['date-from'] || undefined,
|
|
901
|
+
dateTo: args.flags['date-to'] || undefined,
|
|
902
|
+
asOf: args.flags['as-of'] || args.flags['snapshot-as-of'] || undefined,
|
|
903
|
+
includeResolved: args.flags['include-resolved'] === true,
|
|
904
|
+
limit: parsePositiveInt(args.flags.limit, 20),
|
|
905
|
+
});
|
|
906
|
+
if (args.flags.json) {
|
|
907
|
+
console.log(JSON.stringify(result, null, 2));
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
console.log(formatReviewQueue(result));
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (verb === 'inspect') {
|
|
915
|
+
const activeScopePath = parseScopePath(args.flags['active-scope-path']);
|
|
916
|
+
const activeScopeKey = args.flags['active-scope-key']
|
|
917
|
+
|| args.flags['scope-key']
|
|
918
|
+
|| (activeScopePath ? activeScopePath[activeScopePath.length - 1] : undefined);
|
|
919
|
+
const memoryId = args.flags['memory-id'] || args.flags.id || args._[2] || undefined;
|
|
920
|
+
const canonicalKey = args.flags['canonical-key'] || undefined;
|
|
921
|
+
if (memoryId && canonicalKey) {
|
|
922
|
+
console.error('Usage: --memory-id/--id is mutually exclusive with --canonical-key.');
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
925
|
+
if (!memoryId && !canonicalKey) {
|
|
926
|
+
console.error('Usage: aquifer review inspect (--memory-id ID | --canonical-key KEY) [--json]');
|
|
927
|
+
process.exit(1);
|
|
928
|
+
}
|
|
929
|
+
const result = await aquifer.review.inspect({
|
|
930
|
+
memoryId,
|
|
931
|
+
canonicalKey,
|
|
932
|
+
activeScopeKey,
|
|
933
|
+
activeScopePath,
|
|
934
|
+
scopeKey: args.flags['scope-key'] || activeScopeKey,
|
|
935
|
+
visibility: args.flags.visibility || undefined,
|
|
936
|
+
feedbackType: args.flags['feedback-type'] || undefined,
|
|
937
|
+
dateFrom: args.flags['date-from'] || undefined,
|
|
938
|
+
dateTo: args.flags['date-to'] || undefined,
|
|
939
|
+
asOf: args.flags['as-of'] || args.flags['snapshot-as-of'] || undefined,
|
|
940
|
+
limit: parsePositiveInt(args.flags.limit, 20),
|
|
941
|
+
});
|
|
942
|
+
if (args.flags.json) {
|
|
943
|
+
console.log(JSON.stringify(result, null, 2));
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
console.log(formatReviewInspect(result));
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (verb === 'resolve') {
|
|
951
|
+
const activeScopePath = parseScopePath(args.flags['active-scope-path']);
|
|
952
|
+
const activeScopeKey = args.flags['active-scope-key']
|
|
953
|
+
|| args.flags['scope-key']
|
|
954
|
+
|| (activeScopePath ? activeScopePath[activeScopePath.length - 1] : undefined);
|
|
955
|
+
const memoryId = args.flags['memory-id'] || args.flags.id || undefined;
|
|
956
|
+
const canonicalKey = args.flags['canonical-key'] || undefined;
|
|
957
|
+
if (memoryId && canonicalKey) {
|
|
958
|
+
console.error('Usage: --memory-id/--id is mutually exclusive with --canonical-key.');
|
|
959
|
+
process.exit(1);
|
|
960
|
+
}
|
|
961
|
+
if (!memoryId && !canonicalKey) {
|
|
962
|
+
console.error('Usage: aquifer review resolve (--memory-id ID | --canonical-key KEY) --resolution resolved|ignored|deferred [--json]');
|
|
963
|
+
process.exit(1);
|
|
964
|
+
}
|
|
965
|
+
if (!args.flags['expected-latest-issue-feedback-id'] && !args.flags['expected-latest-issue-feedback-at']) {
|
|
966
|
+
console.error('Usage: aquifer review resolve requires --expected-latest-issue-feedback-id or --expected-latest-issue-feedback-at.');
|
|
967
|
+
process.exit(1);
|
|
968
|
+
}
|
|
969
|
+
const result = await aquifer.review.resolve({
|
|
970
|
+
memoryId,
|
|
971
|
+
canonicalKey,
|
|
972
|
+
resolution: args.flags.resolution || undefined,
|
|
973
|
+
reason: args.flags.reason || args.flags.note || undefined,
|
|
974
|
+
actorKind: args.flags['actor-kind'] || undefined,
|
|
975
|
+
actorId: args.flags['actor-id'] || args.flags['agent-id'] || undefined,
|
|
976
|
+
deferUntil: args.flags['defer-until'] || undefined,
|
|
977
|
+
expectedLatestIssueFeedbackId: args.flags['expected-latest-issue-feedback-id'] || undefined,
|
|
978
|
+
expectedLatestIssueFeedbackAt: args.flags['expected-latest-issue-feedback-at'] || undefined,
|
|
979
|
+
activeScopeKey,
|
|
980
|
+
activeScopePath,
|
|
981
|
+
scopeKey: args.flags['scope-key'] || activeScopeKey,
|
|
982
|
+
visibility: args.flags.visibility || undefined,
|
|
983
|
+
asOf: args.flags['as-of'] || args.flags['snapshot-as-of'] || undefined,
|
|
984
|
+
});
|
|
985
|
+
if (args.flags.json) {
|
|
986
|
+
console.log(JSON.stringify(result, null, 2));
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
console.log(formatReviewResolve(result));
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
console.error('Usage: aquifer review <queue|inspect|resolve> [options]');
|
|
994
|
+
process.exit(1);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
async function cmdExplain(aquifer, args) {
|
|
998
|
+
const lane = args._[1] || 'bootstrap';
|
|
999
|
+
if (lane === 'bootstrap') {
|
|
1000
|
+
const result = await aquifer.memory.explainBootstrap({
|
|
1001
|
+
activeScopeKey: args.flags['active-scope-key'] || undefined,
|
|
1002
|
+
activeScopePath: parseScopePath(args.flags['active-scope-path']),
|
|
1003
|
+
limit: parsePositiveInt(args.flags.limit, 5),
|
|
1004
|
+
maxChars: parsePositiveInt(args.flags['max-chars'], 4000),
|
|
1005
|
+
});
|
|
1006
|
+
if (args.flags.json) {
|
|
1007
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
console.log(formatExplain(result));
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (lane === 'memory') {
|
|
1014
|
+
const query = args.flags.query || args._.slice(2).join(' ');
|
|
1015
|
+
if (!query) {
|
|
1016
|
+
console.error('Usage: aquifer explain memory --query TEXT --active-scope-key KEY [--limit N] [--json]');
|
|
1017
|
+
process.exit(1);
|
|
1018
|
+
}
|
|
1019
|
+
const result = await aquifer.memory.explainCurrent(query, {
|
|
1020
|
+
activeScopeKey: args.flags['active-scope-key'] || undefined,
|
|
1021
|
+
activeScopePath: parseScopePath(args.flags['active-scope-path']),
|
|
1022
|
+
limit: parsePositiveInt(args.flags.limit, 5),
|
|
1023
|
+
});
|
|
1024
|
+
if (args.flags.json) {
|
|
1025
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
console.log(formatExplain(result));
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
console.error('Usage: aquifer explain <bootstrap|memory> [options]');
|
|
1032
|
+
process.exit(1);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function formatOperatorStatus(result = {}) {
|
|
1036
|
+
const compaction = result.compaction || {};
|
|
1037
|
+
const checkpoint = result.checkpoint || {};
|
|
1038
|
+
const lines = [
|
|
1039
|
+
`Compaction: ${compaction.available === false ? 'unavailable' : 'available'} statuses=${JSON.stringify(compaction.statusCounts || {})}`,
|
|
1040
|
+
`Checkpoint: ${checkpoint.available === false ? 'unavailable' : 'available'} statuses=${JSON.stringify(checkpoint.statusCounts || {})}`,
|
|
1041
|
+
];
|
|
1042
|
+
if (Array.isArray(compaction.staleClaims) && compaction.staleClaims.length > 0) {
|
|
1043
|
+
lines.push(`Stale compaction claims: ${compaction.staleClaims.map(run => `#${run.id}/${run.cadence}`).join(', ')}`);
|
|
1044
|
+
}
|
|
1045
|
+
if (Array.isArray(compaction.latest) && compaction.latest.length > 0) {
|
|
1046
|
+
lines.push('Latest compaction: ' + compaction.latest.map(run => `#${run.id} ${run.status} ${run.cadence}`).join(', '));
|
|
1047
|
+
}
|
|
1048
|
+
if (Array.isArray(checkpoint.latest) && checkpoint.latest.length > 0) {
|
|
1049
|
+
lines.push('Latest checkpoint: ' + checkpoint.latest.map(run => `#${run.id} ${run.status} sources=${run.sourceCount}`).join(', '));
|
|
1050
|
+
}
|
|
1051
|
+
return lines.join('\n');
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function formatOperatorInspect(result = {}) {
|
|
1055
|
+
const run = result.run || {};
|
|
1056
|
+
const lines = [
|
|
1057
|
+
`${run.kind || 'operator'} run #${run.id || '?'} status=${run.status || '?'}`,
|
|
1058
|
+
];
|
|
1059
|
+
if (run.kind === 'compaction') {
|
|
1060
|
+
lines.push(`Window: ${run.cadence || '?'} ${run.periodStart || '?'} -> ${run.periodEnd || '?'}`);
|
|
1061
|
+
lines.push(`Plan: candidates=${run.candidateCount || 0} statusUpdates=${run.statusUpdateCount || 0}`);
|
|
1062
|
+
if (run.leaseExpiresAt) lines.push(`Claim: worker=${run.workerId || '?'} leaseExpiresAt=${run.leaseExpiresAt}`);
|
|
1063
|
+
} else if (run.kind === 'checkpoint') {
|
|
1064
|
+
lines.push(`Checkpoint: scope=${run.scopeId || '?'} key=${run.checkpointKey || '?'}`);
|
|
1065
|
+
lines.push(`Range: ${run.fromFinalizationIdExclusive || 0} -> ${run.toFinalizationIdInclusive || '?'}`);
|
|
1066
|
+
lines.push(`Sources: ${run.sourceCount || 0}`);
|
|
1067
|
+
}
|
|
1068
|
+
if (run.error) lines.push(`Error: ${run.error}`);
|
|
1069
|
+
if (Array.isArray(result.sources) && result.sources.length > 0) {
|
|
1070
|
+
lines.push('Sources:');
|
|
1071
|
+
for (const source of result.sources) {
|
|
1072
|
+
lines.push(` [${source.sourceIndex}] finalization=${source.finalizationId} ${source.source || '?'}/${source.agentId || '?'}/${source.sessionId || '?'}`);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return lines.join('\n');
|
|
1076
|
+
}
|
|
1077
|
+
|
|
647
1078
|
async function cmdOperator(aquifer, args) {
|
|
648
1079
|
const operatorVerb = args._[1] || 'compaction';
|
|
649
1080
|
const cadenceVerbs = new Set(['manual', 'daily', 'weekly', 'monthly']);
|
|
650
1081
|
|
|
1082
|
+
if (operatorVerb === 'status') {
|
|
1083
|
+
const result = await aquifer.operator.status({
|
|
1084
|
+
limit: parsePositiveInt(args.flags.limit, 10),
|
|
1085
|
+
});
|
|
1086
|
+
if (args.flags.json) {
|
|
1087
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
console.log(formatOperatorStatus(result));
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
if (operatorVerb === 'inspect') {
|
|
1095
|
+
const runId = args.flags['run-id'] || args.flags.id || args._[2];
|
|
1096
|
+
if (!runId) {
|
|
1097
|
+
console.error('Usage: aquifer operator inspect --run-id ID [--kind compaction|checkpoint] [--json]');
|
|
1098
|
+
process.exit(1);
|
|
1099
|
+
}
|
|
1100
|
+
const result = await aquifer.operator.inspect({
|
|
1101
|
+
runId,
|
|
1102
|
+
kind: args.flags.kind || 'compaction',
|
|
1103
|
+
});
|
|
1104
|
+
if (args.flags.json) {
|
|
1105
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
console.log(formatOperatorInspect(result));
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
651
1112
|
if (operatorVerb === 'checkpoint') {
|
|
652
1113
|
const synthesisSummary = readSynthesisSummaryFromFlags(args.flags);
|
|
653
1114
|
const result = await aquifer.checkpoints.runProducer({
|
|
@@ -830,13 +1291,18 @@ Commands:
|
|
|
830
1291
|
quickstart Verify end-to-end setup (migrate → commit → enrich → recall)
|
|
831
1292
|
migrate Run database migrations
|
|
832
1293
|
backend-info Show selected backend capabilities without connecting to a database
|
|
1294
|
+
install-openclaw Install/update OpenClaw MCP + extension wiring
|
|
1295
|
+
doctor Run read-only health diagnostics
|
|
833
1296
|
recall <query> Search sessions (requires embed config)
|
|
834
1297
|
evidence-recall <query> Search legacy session/evidence plane explicitly
|
|
835
1298
|
feedback Record trust feedback on a session
|
|
836
1299
|
memory-feedback Record curated memory feedback
|
|
837
1300
|
feedback-stats Show trust feedback statistics and coverage
|
|
1301
|
+
review ... Inspect or resolve memory feedback review queue
|
|
838
1302
|
backfill Enrich pending sessions
|
|
839
1303
|
operator ... Run operator-safe consolidation jobs
|
|
1304
|
+
finalization ... Inspect read-only finalization ledger
|
|
1305
|
+
explain ... Explain current-memory serving decisions
|
|
840
1306
|
compact Plan or apply curated memory compaction
|
|
841
1307
|
stats Show database statistics
|
|
842
1308
|
export Export sessions as JSONL
|
|
@@ -853,11 +1319,32 @@ Options:
|
|
|
853
1319
|
--date-to YYYY-MM-DD End date
|
|
854
1320
|
--entities A,B,C Entity names (comma-separated, recall)
|
|
855
1321
|
--entity-mode any|all Entity match mode (recall, default: any)
|
|
1322
|
+
--id ID Finalization row id
|
|
1323
|
+
--host HOST Finalization host filter
|
|
1324
|
+
--status STATUS Finalization status filter
|
|
1325
|
+
--phase PHASE Finalization phase filter
|
|
1326
|
+
--transcript-hash HASH Finalization transcript hash filter
|
|
1327
|
+
--run-id ID Operator run id
|
|
1328
|
+
--kind KIND Operator inspect kind: compaction|checkpoint
|
|
1329
|
+
--query TEXT Explain memory query
|
|
856
1330
|
--session-id ID Session ID (feedback)
|
|
857
|
-
--memory-id ID Curated memory record ID (memory-feedback)
|
|
858
|
-
--canonical-key KEY Active curated memory canonical key (memory-feedback)
|
|
1331
|
+
--memory-id ID Curated memory record ID (memory-feedback, review inspect/resolve)
|
|
1332
|
+
--canonical-key KEY Active curated memory canonical key (memory-feedback, review queue/inspect/resolve)
|
|
859
1333
|
--verdict helpful|unhelpful Feedback verdict (feedback)
|
|
860
1334
|
--feedback-type TYPE Curated memory feedback type
|
|
1335
|
+
--memory-type TYPE Curated memory type filter (review queue)
|
|
1336
|
+
--visibility MODE Review visibility filter: either|both|bootstrap|recall
|
|
1337
|
+
--as-of ISO Review queue memory validity instant
|
|
1338
|
+
--include-resolved Include currently resolved review items in review queue
|
|
1339
|
+
--resolution ACTION Review resolution: resolved|ignored|deferred
|
|
1340
|
+
--reason TEXT Operator reason for review resolution
|
|
1341
|
+
--actor-kind KIND Review resolution actor kind: user|agent|system|curator
|
|
1342
|
+
--actor-id ID Review resolution actor id
|
|
1343
|
+
--defer-until ISO Re-open deferred review item after this instant
|
|
1344
|
+
--expected-latest-issue-feedback-id ID
|
|
1345
|
+
Reject review resolution if newer issue feedback exists
|
|
1346
|
+
--expected-latest-issue-feedback-at ISO
|
|
1347
|
+
Timestamp guard for review resolution snapshots
|
|
861
1348
|
--note TEXT Feedback note (feedback)
|
|
862
1349
|
--explain Show score breakdown per result (recall)
|
|
863
1350
|
--allow-unsafe-debug Allow broad evidence-recall without audit boundary
|
|
@@ -865,6 +1352,11 @@ Options:
|
|
|
865
1352
|
--dry-run Preview only (backfill)
|
|
866
1353
|
--output PATH Output file (export)
|
|
867
1354
|
--config PATH Config file path
|
|
1355
|
+
--openclaw-home PATH OpenClaw home path (install-openclaw)
|
|
1356
|
+
--skip-extension Do not wire the OpenClaw extension/plugin (install-openclaw)
|
|
1357
|
+
--skip-mcp Do not update mcp.servers.aquifer (install-openclaw)
|
|
1358
|
+
--force Move aside existing extension path if needed (install-openclaw)
|
|
1359
|
+
--link-current-package Development only: wire the current checkout instead of OpenClaw node_modules
|
|
868
1360
|
--lookback-days N How far back in days (bootstrap, default: 14)
|
|
869
1361
|
--max-chars N Max output characters (bootstrap, default: 4000)
|
|
870
1362
|
--active-scope-key KEY Active curated memory scope key
|
|
@@ -890,6 +1382,17 @@ Options:
|
|
|
890
1382
|
--min-messages N Min user messages to ingest (ingest-opencode, default: 3)
|
|
891
1383
|
|
|
892
1384
|
Operator examples:
|
|
1385
|
+
aquifer doctor --json
|
|
1386
|
+
aquifer finalization list --status finalized --json
|
|
1387
|
+
aquifer finalization inspect --id 42 --json
|
|
1388
|
+
aquifer finalization inspect --session-id SESSION --agent-id main --source codex --transcript-hash HASH
|
|
1389
|
+
aquifer explain bootstrap --active-scope-key project:aquifer --json
|
|
1390
|
+
aquifer explain memory --query "serving contract" --active-scope-key project:aquifer --json
|
|
1391
|
+
aquifer review queue --scope-key project:aquifer --feedback-type incorrect --json
|
|
1392
|
+
aquifer review inspect --memory-id 42 --json
|
|
1393
|
+
aquifer review resolve --memory-id 42 --resolution resolved --reason "verified current" --json
|
|
1394
|
+
aquifer operator status --json
|
|
1395
|
+
aquifer operator inspect --run-id 42 --kind compaction --json
|
|
893
1396
|
aquifer operator compaction daily --json
|
|
894
1397
|
aquifer operator compaction daily --include-synthesis-prompt --json
|
|
895
1398
|
aquifer operator compaction manual --period-start 2026-04-27T00:00:00Z --period-end 2026-04-28T00:00:00Z --apply
|
|
@@ -898,7 +1401,9 @@ Operator examples:
|
|
|
898
1401
|
aquifer operator checkpoint --scope-id 7 --synthesis-summary-file /tmp/checkpoint-summary.json --apply --finalize --json
|
|
899
1402
|
AQUIFER_BACKEND=local aquifer backend-info --json
|
|
900
1403
|
aquifer codex-recovery checkpoint-heartbeat --hook-stdin --scope-key project:aquifer
|
|
1404
|
+
aquifer codex-recovery checkpoint-spool-status --json --limit 10
|
|
901
1405
|
aquifer codex-recovery checkpoint-heartbeat-hook --scope-key project:aquifer --hooks-path "$CODEX_HOME/hooks.json" --json
|
|
1406
|
+
aquifer install-openclaw --openclaw-home "$OPENCLAW_HOME"
|
|
902
1407
|
aquifer operator archive-distill --input /tmp/archive-snapshot.json --json`);
|
|
903
1408
|
process.exit(0);
|
|
904
1409
|
}
|
|
@@ -939,6 +1444,11 @@ Operator examples:
|
|
|
939
1444
|
return;
|
|
940
1445
|
}
|
|
941
1446
|
|
|
1447
|
+
if (command === 'install-openclaw') {
|
|
1448
|
+
await require('./openclaw-install').cmdInstallOpenClaw(args);
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
942
1452
|
// All other commands need an Aquifer instance
|
|
943
1453
|
const configOverrides = {};
|
|
944
1454
|
if (args.flags.config) {
|
|
@@ -986,6 +1496,9 @@ Operator examples:
|
|
|
986
1496
|
case 'migrate':
|
|
987
1497
|
await cmdMigrate(aquifer, args);
|
|
988
1498
|
break;
|
|
1499
|
+
case 'doctor':
|
|
1500
|
+
await cmdDoctor(aquifer, args);
|
|
1501
|
+
break;
|
|
989
1502
|
case 'recall':
|
|
990
1503
|
await cmdRecall(aquifer, args);
|
|
991
1504
|
break;
|
|
@@ -1001,12 +1514,21 @@ Operator examples:
|
|
|
1001
1514
|
case 'feedback-stats':
|
|
1002
1515
|
await cmdFeedbackStats(aquifer, args);
|
|
1003
1516
|
break;
|
|
1517
|
+
case 'review':
|
|
1518
|
+
await cmdReview(aquifer, args);
|
|
1519
|
+
break;
|
|
1004
1520
|
case 'backfill':
|
|
1005
1521
|
await cmdBackfill(aquifer, args);
|
|
1006
1522
|
break;
|
|
1007
1523
|
case 'operator':
|
|
1008
1524
|
await cmdOperator(aquifer, args);
|
|
1009
1525
|
break;
|
|
1526
|
+
case 'finalization':
|
|
1527
|
+
await cmdFinalization(aquifer, args);
|
|
1528
|
+
break;
|
|
1529
|
+
case 'explain':
|
|
1530
|
+
await cmdExplain(aquifer, args);
|
|
1531
|
+
break;
|
|
1010
1532
|
case 'compact':
|
|
1011
1533
|
await cmdCompact(aquifer, args);
|
|
1012
1534
|
break;
|
|
@@ -1039,8 +1561,19 @@ module.exports = {
|
|
|
1039
1561
|
selectedBackendInfo,
|
|
1040
1562
|
cmdBackendInfo,
|
|
1041
1563
|
cmdMigrate,
|
|
1564
|
+
cmdDoctor,
|
|
1042
1565
|
cmdLocalQuickstart,
|
|
1043
1566
|
cmdOperator,
|
|
1567
|
+
cmdFinalization,
|
|
1568
|
+
cmdExplain,
|
|
1569
|
+
cmdReview,
|
|
1570
|
+
formatDoctor,
|
|
1571
|
+
formatFinalizationList,
|
|
1572
|
+
formatFinalizationInspect,
|
|
1573
|
+
formatExplain,
|
|
1574
|
+
formatReviewQueue,
|
|
1575
|
+
formatReviewInspect,
|
|
1576
|
+
cmdInstallOpenClaw: require('./openclaw-install').cmdInstallOpenClaw,
|
|
1044
1577
|
readSynthesisSummaryFromFlags,
|
|
1045
1578
|
};
|
|
1046
1579
|
|
package/consumers/codex.js
CHANGED
|
@@ -40,7 +40,7 @@ const DEFAULT_MAX_AFTERBURNS = 1;
|
|
|
40
40
|
const DEFAULT_MIN_IMPORT_USER_MESSAGES = 3;
|
|
41
41
|
const MAX_RETRY_COUNT = 3;
|
|
42
42
|
const SAFE_SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/;
|
|
43
|
-
const DEFAULT_RECOVERY_MAX_BYTES = 1024 * 1024;
|
|
43
|
+
const DEFAULT_RECOVERY_MAX_BYTES = 20 * 1024 * 1024;
|
|
44
44
|
const DEFAULT_RECOVERY_MAX_MESSAGES = 80;
|
|
45
45
|
const DEFAULT_RECOVERY_MAX_CHARS = 24000;
|
|
46
46
|
const DEFAULT_RECOVERY_MAX_PROMPT_TOKENS = 9000;
|
package/consumers/mcp.js
CHANGED
|
@@ -389,6 +389,9 @@ async function main() {
|
|
|
389
389
|
for (const [status, count] of Object.entries(stats.sessions)) {
|
|
390
390
|
lines.push(` ${status}: ${count}`);
|
|
391
391
|
}
|
|
392
|
+
if (stats.pendingSessions?.available) {
|
|
393
|
+
lines.push(`Actionable pending/failed: ${stats.pendingSessions.total}`);
|
|
394
|
+
}
|
|
392
395
|
lines.push(`Summaries: ${stats.summaries}`);
|
|
393
396
|
lines.push(`Turn embeddings: ${stats.turnEmbeddings}`);
|
|
394
397
|
lines.push(`Entities: ${stats.entities}`);
|