cogmem 3.6.5 → 3.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/MEMORY_ATLAS.md +59 -12
- package/README.md +21 -13
- package/RELEASE_CHECKLIST.md +5 -4
- package/dist/agent/AgentMemoryBackend.d.ts +24 -3
- package/dist/agent/AgentMemoryBackend.js +285 -55
- package/dist/agent/AgentRecallQueryCompiler.d.ts +1 -1
- package/dist/agent/AgentRecallQueryCompiler.js +13 -1
- package/dist/agent/MemoryUsageReceipt.js +8 -10
- package/dist/agent/SessionWorkingState.js +3 -2
- package/dist/agent/UntrustedMemorySerializer.d.ts +2 -0
- package/dist/agent/UntrustedMemorySerializer.js +14 -0
- package/dist/atlas/ActionFrameExtractor.js +2 -0
- package/dist/atlas/EpisodeTitleGenerator.d.ts +31 -0
- package/dist/atlas/EpisodeTitleGenerator.js +191 -0
- package/dist/atlas/FacetQueryPlanner.d.ts +33 -0
- package/dist/atlas/FacetQueryPlanner.js +229 -0
- package/dist/atlas/GraphCurator.d.ts +30 -0
- package/dist/atlas/GraphCurator.js +501 -0
- package/dist/atlas/MemoryAtlasIndexer.d.ts +15 -0
- package/dist/atlas/MemoryAtlasIndexer.js +55 -6
- package/dist/atlas/MemoryAtlasQueryCompiler.js +10 -7
- package/dist/atlas/MemoryAtlasService.d.ts +4 -1
- package/dist/atlas/MemoryAtlasService.js +174 -11
- package/dist/atlas/MemoryAtlasTypes.d.ts +75 -11
- package/dist/bin/memory.js +18 -5
- package/dist/factory.d.ts +18 -0
- package/dist/factory.js +41 -2
- package/dist/host/openclaw/AutoMemoryPluginInstaller.js +135 -39
- package/dist/mcp/server.js +1 -1
- package/dist/recall/RecallExplanation.d.ts +1 -1
- package/dist/store/MemoryAtlasStore.d.ts +12 -1
- package/dist/store/MemoryAtlasStore.js +353 -15
- package/dist/utils/ActionKindRegistry.d.ts +10 -0
- package/dist/utils/ActionKindRegistry.js +18 -0
- package/dist/utils/EntityCueExtractor.d.ts +13 -0
- package/dist/utils/EntityCueExtractor.js +81 -0
- package/examples/hermes-backend/AGENTS.md +3 -3
- package/examples/hermes-backend/README.md +1 -1
- package/examples/hermes-backend/SKILL.md +14 -5
- package/examples/hermes-backend/references/operations.md +12 -9
- package/examples/openclaw-backend/AGENTS.md +3 -2
- package/examples/openclaw-backend/README.md +3 -2
- package/examples/openclaw-backend/SKILL.md +31 -9
- package/examples/openclaw-backend/references/operations.md +29 -11
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { memoryEventCharRange, memoryEventLabel, memoryEventSourceRange, normalizeSourceContextWindow, } from '../recall/SourceContextMetadata.js';
|
|
2
2
|
import { isOperationalNoiseText, isRecallableMemoryEvidence } from '../recall/RecallGovernance.js';
|
|
3
3
|
import { compileAgentRecallQuery, } from './AgentRecallQueryCompiler.js';
|
|
4
|
+
import { extractEntityCues } from '../utils/EntityCueExtractor.js';
|
|
5
|
+
import { inferActionKinds } from '../utils/ActionKindRegistry.js';
|
|
4
6
|
export class KernelAgentMemoryBackend {
|
|
5
7
|
kernel;
|
|
6
8
|
constructor(kernel) {
|
|
@@ -248,13 +250,13 @@ export class KernelAgentMemoryBackend {
|
|
|
248
250
|
intent: query.intent,
|
|
249
251
|
anchorText: query.anchorText,
|
|
250
252
|
});
|
|
251
|
-
if (
|
|
253
|
+
if (queryPlan.intent === 'previous_session_summary') {
|
|
252
254
|
return this.recallPreviousSession(query, queryPlan);
|
|
253
255
|
}
|
|
254
|
-
if (
|
|
256
|
+
if (queryPlan.intent === 'forensic_quote') {
|
|
255
257
|
return this.recallForensicQuote(query, queryPlan);
|
|
256
258
|
}
|
|
257
|
-
if (queryPlan.intent === 'historical_discussion') {
|
|
259
|
+
if (queryPlan.intent === 'historical_discussion' || queryPlan.intent === 'action_history') {
|
|
258
260
|
return this.recallHistoricalDiscussion(query, queryPlan);
|
|
259
261
|
}
|
|
260
262
|
const limit = query.limit ?? 5;
|
|
@@ -431,8 +433,7 @@ export class KernelAgentMemoryBackend {
|
|
|
431
433
|
? this.getSessionEvents(previousSessionId, query, Math.max(limit * 3, 24))
|
|
432
434
|
: [];
|
|
433
435
|
const items = events
|
|
434
|
-
.filter((event) => this.
|
|
435
|
-
.filter((event) => this.isAllowedRawEventCollection(event, query.collection))
|
|
436
|
+
.filter((event) => this.isRawEventInRecallScope(event, query, queryPlan.intent))
|
|
436
437
|
.filter((event) => !this.isOperationalNoiseRawEvent(event))
|
|
437
438
|
.filter((event) => this.hasReadableEventText(event))
|
|
438
439
|
.slice(0, limit)
|
|
@@ -457,26 +458,29 @@ export class KernelAgentMemoryBackend {
|
|
|
457
458
|
}
|
|
458
459
|
recallForensicQuote(query, queryPlan) {
|
|
459
460
|
const limit = query.limit ?? 5;
|
|
460
|
-
const anchorItems = this.recallForensicAnchor(query, limit);
|
|
461
|
+
const anchorItems = this.recallForensicAnchor(query, queryPlan, limit);
|
|
461
462
|
const rawEvents = anchorItems.length > 0 && (queryPlan.anchorUsed || !!query.anchorEventId)
|
|
462
463
|
? []
|
|
463
|
-
:
|
|
464
|
+
: [
|
|
465
|
+
...this.rawEventsForLocalDateCue(query, Math.max(limit * 4, 20)),
|
|
466
|
+
...this.searchRawEventsByQueryPlan(queryPlan, query, Math.max(limit * 4, 20)),
|
|
467
|
+
];
|
|
468
|
+
const facetQuoteItems = anchorItems.length === 0 ? this.facetGraphQuoteItemsForQuery(query, queryPlan, limit) : [];
|
|
464
469
|
const items = [
|
|
465
470
|
...anchorItems,
|
|
466
471
|
...rawEvents
|
|
467
472
|
.filter((event) => this.isAgentRawEvent(event, query.agentId))
|
|
468
|
-
.filter((event) => this.
|
|
469
|
-
.filter((event) => this.isAllowedRawEventCollection(event, query.collection))
|
|
470
|
-
.filter((event) => !this.isOperationalNoiseRawEvent(event))
|
|
473
|
+
.filter((event) => this.isRawEventInRecallScope(event, query, queryPlan.intent))
|
|
471
474
|
.filter((event) => this.isQuoteSourceEvent(event))
|
|
472
475
|
.filter((event) => this.hasReadableEventText(event))
|
|
473
|
-
.sort((a, b) => this.quoteEventPriority(a) - this.quoteEventPriority(b))
|
|
476
|
+
.sort((a, b) => this.quoteEventPriority(a, queryPlan) - this.quoteEventPriority(b, queryPlan))
|
|
474
477
|
.slice(0, limit)
|
|
475
478
|
.map((event) => this.toAgentRawRecallItem(event, {
|
|
476
479
|
sourceType: 'raw_ledger',
|
|
477
480
|
whyMatched: 'forensic_quote_raw_event',
|
|
478
481
|
canAnswerExactQuote: true,
|
|
479
482
|
})),
|
|
483
|
+
...facetQuoteItems,
|
|
480
484
|
].filter((item, index, array) => array.findIndex((candidate) => candidate.id === item.id) === index)
|
|
481
485
|
.slice(0, limit);
|
|
482
486
|
return {
|
|
@@ -498,42 +502,117 @@ export class KernelAgentMemoryBackend {
|
|
|
498
502
|
const allowsGraph = laneAllowed(query.retrievalPolicy, 'graph');
|
|
499
503
|
const allowsCompiled = laneAllowed(query.retrievalPolicy, 'compiled');
|
|
500
504
|
const allowsRawSource = laneAllowed(query.retrievalPolicy, 'raw_source');
|
|
505
|
+
const facetResult = allowsGraph ? this.facetGraphItemsForQuery(query, limit) : { items: [], cards: [], relatedButNotSelected: [], relaxationTrace: [] };
|
|
506
|
+
const facetItems = allowsRawSource ? facetResult.items : [];
|
|
501
507
|
const graphItems = allowsGraph ? this.memoryBindingGraphItemsForQuery(query, queryPlan, limit) : [];
|
|
502
508
|
const rawItems = allowsRawSource ? this.rawLedgerFallbackItemsForQuery(queryPlan, query, Math.max(limit * 2, 10)) : [];
|
|
503
|
-
const retrievalLimit = Math.max(limit * 4, 24);
|
|
504
509
|
const compiledItems = allowsCompiled
|
|
505
|
-
? this.
|
|
506
|
-
projectId: query.projectId,
|
|
507
|
-
limit: retrievalLimit,
|
|
508
|
-
startTime: query.startTime,
|
|
509
|
-
endTime: query.endTime,
|
|
510
|
-
}).rawEvidence, query.agentId, query.collection, query.excludeSessionId)
|
|
511
|
-
.slice(0, limit)
|
|
512
|
-
.map((neuron) => this.toAgentRecallItem(neuron))
|
|
510
|
+
? this.compiledItemsForHistoricalQuery(queryPlan, query, limit)
|
|
513
511
|
: [];
|
|
514
|
-
const
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
512
|
+
const relevantCompiledItems = this.filterCompiledItemsByQueryCues(compiledItems, queryPlan);
|
|
513
|
+
const items = this.mergeHistoricalRecallItems(facetItems, rawItems, graphItems, relevantCompiledItems, limit);
|
|
514
|
+
const selectedLane = facetItems.length > 0
|
|
515
|
+
? 'facet_graph_raw_ledger'
|
|
516
|
+
: rawItems.length > 0
|
|
517
|
+
? 'raw_ledger'
|
|
518
|
+
: graphItems.length > 0
|
|
519
|
+
? 'graph'
|
|
520
|
+
: relevantCompiledItems.length > 0
|
|
521
|
+
? 'compiled'
|
|
522
|
+
: 'none';
|
|
522
523
|
return {
|
|
523
|
-
recallMode:
|
|
524
|
+
recallMode: facetItems.length > 0
|
|
525
|
+
? (rawItems.length > 0 ? 'atlas_raw_grounded_recall' : 'atlas_facet_recall')
|
|
526
|
+
: rawItems.length > 0 ? 'raw_ledger_fallback' : 'brain_recall_fallback',
|
|
524
527
|
items,
|
|
525
|
-
fallbackUsed:
|
|
528
|
+
fallbackUsed: facetItems.length === 0,
|
|
526
529
|
queryPlan,
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
530
|
+
atlasCards: facetResult.cards,
|
|
531
|
+
relatedButNotSelected: facetResult.relatedButNotSelected,
|
|
532
|
+
relaxationTrace: facetResult.relaxationTrace,
|
|
533
|
+
decisionTrace: recallDecisionTrace(selectedLane, queryPlan.intent === 'action_history' ? 'action_history' : 'historical_discussion', {
|
|
534
|
+
graph: graphItems.length + facetItems.length,
|
|
535
|
+
navigation: relevantCompiledItems.length,
|
|
536
|
+
scopedNavigation: relevantCompiledItems.length,
|
|
531
537
|
brainFallback: 0,
|
|
532
|
-
rawLedger: rawItems.length,
|
|
538
|
+
rawLedger: rawItems.length + facetItems.length,
|
|
533
539
|
}, items.length),
|
|
534
540
|
};
|
|
535
541
|
}
|
|
536
|
-
|
|
542
|
+
facetGraphQuoteItemsForQuery(query, queryPlan, limit) {
|
|
543
|
+
try {
|
|
544
|
+
const atlas = this.kernel.graphExplore(query.query, {
|
|
545
|
+
projectId: query.projectId,
|
|
546
|
+
limit: Math.max(limit * 2, 6),
|
|
547
|
+
includeEvidence: true,
|
|
548
|
+
evidenceLimit: 2,
|
|
549
|
+
refresh: true,
|
|
550
|
+
staleOk: true,
|
|
551
|
+
});
|
|
552
|
+
const cards = (atlas.cards ?? []).slice(0, Math.max(limit * 2, 6));
|
|
553
|
+
const events = cards
|
|
554
|
+
.map((card) => card.sourceLocator?.eventId ?? card.evidenceEventIds[0])
|
|
555
|
+
.filter((eventId) => Boolean(eventId))
|
|
556
|
+
.map((eventId) => this.kernel.getEventContext(eventId, { before: 0, after: 0 })?.event)
|
|
557
|
+
.filter((event) => Boolean(event))
|
|
558
|
+
.filter((event) => this.isRawEventInRecallScope(event, query, queryPlan.intent))
|
|
559
|
+
.filter((event) => this.isQuoteSourceEvent(event))
|
|
560
|
+
.filter((event) => this.hasReadableEventText(event));
|
|
561
|
+
return this.dedupeRawEventsByTurnPreferUser(events)
|
|
562
|
+
.slice(0, limit)
|
|
563
|
+
.map((event) => this.toAgentRawRecallItem(event, {
|
|
564
|
+
sourceType: 'raw_ledger',
|
|
565
|
+
whyMatched: 'forensic_quote_atlas_source_locator',
|
|
566
|
+
canAnswerExactQuote: true,
|
|
567
|
+
}));
|
|
568
|
+
}
|
|
569
|
+
catch {
|
|
570
|
+
return [];
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
facetGraphItemsForQuery(query, limit) {
|
|
574
|
+
try {
|
|
575
|
+
const atlas = this.kernel.graphExplore(query.query, {
|
|
576
|
+
projectId: query.projectId,
|
|
577
|
+
limit,
|
|
578
|
+
includeEvidence: true,
|
|
579
|
+
evidenceLimit: 2,
|
|
580
|
+
refresh: true,
|
|
581
|
+
staleOk: true,
|
|
582
|
+
});
|
|
583
|
+
const cards = (atlas.cards ?? []).slice(0, limit);
|
|
584
|
+
const items = cards.map((card) => this.toAgentRecallItemFromAtlasCard(card, query)).filter((item) => Boolean(item));
|
|
585
|
+
const relatedButNotSelected = cards.flatMap((card) => card.relatedButNotSelected ?? []).slice(0, 8);
|
|
586
|
+
return { items, cards, relatedButNotSelected, relaxationTrace: atlas.relaxationTrace ?? [] };
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
return { items: [], cards: [], relatedButNotSelected: [], relaxationTrace: [] };
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
toAgentRecallItemFromAtlasCard(card, query) {
|
|
593
|
+
const eventId = card.sourceLocator?.eventId ?? card.evidenceEventIds[0];
|
|
594
|
+
const sourceContext = eventId ? this.toAgentSourceContext(eventId) : undefined;
|
|
595
|
+
const anchorEvent = sourceContext?.event;
|
|
596
|
+
return {
|
|
597
|
+
id: `facet:${card.canonicalId}`,
|
|
598
|
+
text: [card.displayTitle, card.oneLineSummary].filter(Boolean).join(': '),
|
|
599
|
+
projectId: query.projectId,
|
|
600
|
+
topicPath: card.parentTopics[0],
|
|
601
|
+
canonicalId: card.canonicalId,
|
|
602
|
+
displayTitle: card.displayTitle,
|
|
603
|
+
matchedFacets: card.matchedFacets,
|
|
604
|
+
matchedPaths: card.matchedPaths,
|
|
605
|
+
tags: ['facet_graph', card.eventKind, card.issueType].filter((tag) => Boolean(tag)),
|
|
606
|
+
source: 'memory_atlas',
|
|
607
|
+
sourceType: 'raw_ledger',
|
|
608
|
+
sourceAnchor: anchorEvent ? this.toAgentSourceAnchorFromContextEvent(anchorEvent) : eventId ? { eventId } : undefined,
|
|
609
|
+
sourceContext,
|
|
610
|
+
confidence: Math.min(1, 0.7 + card.matchedFacets.length * 0.08),
|
|
611
|
+
whyMatched: card.whyMatched,
|
|
612
|
+
canAnswerExactQuote: Boolean(sourceContext),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
recallForensicAnchor(query, queryPlan, limit) {
|
|
537
616
|
if (!query.anchorEventId)
|
|
538
617
|
return [];
|
|
539
618
|
const context = this.kernel.getEventContext(query.anchorEventId, { before: 4, after: 4 });
|
|
@@ -542,8 +621,7 @@ export class KernelAgentMemoryBackend {
|
|
|
542
621
|
const candidates = [context.event, ...context.before.slice().reverse(), ...context.after];
|
|
543
622
|
return candidates
|
|
544
623
|
.filter((event) => this.isAgentRawEvent(event, query.agentId))
|
|
545
|
-
.filter((event) => this.
|
|
546
|
-
.filter((event) => this.isAllowedRawEventCollection(event, query.collection))
|
|
624
|
+
.filter((event) => this.isRawEventInRecallScope(event, query, queryPlan.intent))
|
|
547
625
|
.filter((event) => !this.isOperationalNoiseRawEvent(event))
|
|
548
626
|
.filter((event) => this.isQuoteSourceEvent(event))
|
|
549
627
|
.filter((event) => this.hasReadableEventText(event))
|
|
@@ -551,7 +629,7 @@ export class KernelAgentMemoryBackend {
|
|
|
551
629
|
const anchorDelta = (a.eventId === query.anchorEventId ? 0 : 1) - (b.eventId === query.anchorEventId ? 0 : 1);
|
|
552
630
|
if (anchorDelta !== 0)
|
|
553
631
|
return anchorDelta;
|
|
554
|
-
return this.quoteEventPriority(a) - this.quoteEventPriority(b);
|
|
632
|
+
return this.quoteEventPriority(a, queryPlan) - this.quoteEventPriority(b, queryPlan);
|
|
555
633
|
})
|
|
556
634
|
.slice(0, limit)
|
|
557
635
|
.map((event) => this.toAgentRawRecallItem(event, {
|
|
@@ -584,15 +662,39 @@ export class KernelAgentMemoryBackend {
|
|
|
584
662
|
}
|
|
585
663
|
return out;
|
|
586
664
|
}
|
|
665
|
+
rawEventsForLocalDateCue(query, limit) {
|
|
666
|
+
const localDate = localDateCue(query.query, query);
|
|
667
|
+
if (!localDate)
|
|
668
|
+
return [];
|
|
669
|
+
const [year, month, day] = localDate.split('-').map(Number);
|
|
670
|
+
const byLocalDate = this.kernel.eventStore.queryEvents(1, 1000, {
|
|
671
|
+
projectId: query.projectId ? [query.projectId] : undefined,
|
|
672
|
+
workspaceId: query.workspaceId ? [query.workspaceId] : undefined,
|
|
673
|
+
}).records.filter((event) => event.localDate === localDate).slice(0, limit);
|
|
674
|
+
if (byLocalDate.length)
|
|
675
|
+
return byLocalDate;
|
|
676
|
+
const startTime = Date.UTC(year, month - 1, day);
|
|
677
|
+
const endTime = Date.UTC(year, month - 1, day + 1);
|
|
678
|
+
const byTime = this.kernel.eventStore.queryEvents(1, Math.max(1, Math.min(limit, 200)), {
|
|
679
|
+
projectId: query.projectId ? [query.projectId] : undefined,
|
|
680
|
+
workspaceId: query.workspaceId ? [query.workspaceId] : undefined,
|
|
681
|
+
startTime,
|
|
682
|
+
endTime,
|
|
683
|
+
}).records;
|
|
684
|
+
if (byTime.length)
|
|
685
|
+
return byTime;
|
|
686
|
+
return this.kernel.eventStore.queryEvents(1, 1000, {
|
|
687
|
+
projectId: query.projectId ? [query.projectId] : undefined,
|
|
688
|
+
workspaceId: query.workspaceId ? [query.workspaceId] : undefined,
|
|
689
|
+
}).records.filter((event) => event.localDate === localDate).slice(0, limit);
|
|
690
|
+
}
|
|
587
691
|
rawLedgerFallbackItemsForQuery(queryPlan, query, limit) {
|
|
588
692
|
const searchedEvents = this.searchRawEventsByQueryPlan(queryPlan, query, Math.max(limit * 2, 10));
|
|
589
693
|
const rawEvents = queryPlan.intent === 'forensic_quote'
|
|
590
694
|
? this.dedupeRawEventsByTurnPreferUser(searchedEvents)
|
|
591
695
|
: this.dedupeRawEventsByTurnPreferCue(searchedEvents, queryPlan);
|
|
592
696
|
return rawEvents
|
|
593
|
-
.filter((event) => this.
|
|
594
|
-
.filter((event) => this.isAllowedSession(event, query))
|
|
595
|
-
.filter((event) => this.isAllowedRawEventCollection(event, query.collection))
|
|
697
|
+
.filter((event) => this.isRawEventInRecallScope(event, query, queryPlan.intent))
|
|
596
698
|
.filter((event) => !this.isOperationalNoiseRawEvent(event))
|
|
597
699
|
.slice(0, limit)
|
|
598
700
|
.map((event) => this.toAgentRawRecallItem(event, {
|
|
@@ -614,11 +716,7 @@ export class KernelAgentMemoryBackend {
|
|
|
614
716
|
const event = this.kernel.getEventContext(anchor.eventId, { before: 0, after: 0 })?.event;
|
|
615
717
|
if (!event)
|
|
616
718
|
continue;
|
|
617
|
-
if (!this.
|
|
618
|
-
continue;
|
|
619
|
-
if (!this.isAllowedSession(event, query))
|
|
620
|
-
continue;
|
|
621
|
-
if (!this.isAllowedRawEventCollection(event, query.collection))
|
|
719
|
+
if (!this.isRawEventInRecallScope(event, query, queryPlan.intent))
|
|
622
720
|
continue;
|
|
623
721
|
if (this.isOperationalNoiseRawEvent(event))
|
|
624
722
|
continue;
|
|
@@ -687,6 +785,28 @@ export class KernelAgentMemoryBackend {
|
|
|
687
785
|
.filter((term) => term.length >= 2 && !/^(hermes|openclaw|cogmem)$/i.test(term));
|
|
688
786
|
return uniqueNonEmpty(terms);
|
|
689
787
|
}
|
|
788
|
+
queryHasStructuredCue(queryPlan) {
|
|
789
|
+
return /hermes|openclaw|cogmem|启动|安装|配置|重启|停止|操作|处理|执行|start|launch|install|configure|restart|stop|run/i.test(queryPlan.originalQuery)
|
|
790
|
+
|| queryPlan.intent === 'action_history';
|
|
791
|
+
}
|
|
792
|
+
structuredCueTerms(queryPlan) {
|
|
793
|
+
const terms = [
|
|
794
|
+
...queryPlan.keywords,
|
|
795
|
+
...queryPlan.semanticCuePhrases.flatMap((phrase) => phrase.split(/\s+/)),
|
|
796
|
+
]
|
|
797
|
+
.map((term) => term.trim())
|
|
798
|
+
.filter((term) => term.length >= 2)
|
|
799
|
+
.filter((term) => !/^(之前|什么|做过|做了|让你|我的|原话|精确|完整|the|what|did|you|to)$/i.test(term));
|
|
800
|
+
if (/启动|start|launch/i.test(queryPlan.originalQuery))
|
|
801
|
+
terms.push('启动', 'start', 'launch');
|
|
802
|
+
if (/安装|install|setup/i.test(queryPlan.originalQuery))
|
|
803
|
+
terms.push('安装', 'install', 'setup');
|
|
804
|
+
if (/配置|修改|config/i.test(queryPlan.originalQuery))
|
|
805
|
+
terms.push('配置', '修改', 'config');
|
|
806
|
+
if (/操作|处理|执行|run|ran/i.test(queryPlan.originalQuery))
|
|
807
|
+
terms.push('操作', '执行', 'run');
|
|
808
|
+
return uniqueNonEmpty(terms);
|
|
809
|
+
}
|
|
690
810
|
itemSearchableText(item) {
|
|
691
811
|
return [
|
|
692
812
|
item.text,
|
|
@@ -698,15 +818,75 @@ export class KernelAgentMemoryBackend {
|
|
|
698
818
|
const out = [];
|
|
699
819
|
const seen = new Set();
|
|
700
820
|
for (const item of [...primary, ...secondary]) {
|
|
701
|
-
|
|
821
|
+
const keys = [item.canonicalId, item.sourceAnchor?.eventId, item.id].filter((value) => Boolean(value));
|
|
822
|
+
if (keys.some((key) => seen.has(key)))
|
|
702
823
|
continue;
|
|
703
|
-
|
|
824
|
+
for (const key of keys)
|
|
825
|
+
seen.add(key);
|
|
704
826
|
out.push(item);
|
|
705
827
|
if (out.length >= limit)
|
|
706
828
|
break;
|
|
707
829
|
}
|
|
708
830
|
return out;
|
|
709
831
|
}
|
|
832
|
+
mergeHistoricalRecallItems(facetItems, rawItems, graphItems, compiledItems, limit) {
|
|
833
|
+
const merged = this.mergeRecallItems(facetItems, this.mergeRecallItems(rawItems, this.mergeRecallItems(graphItems, compiledItems, limit), limit), limit);
|
|
834
|
+
if (merged.some((item) => item.sourceType === 'imported_summary'))
|
|
835
|
+
return merged;
|
|
836
|
+
const importedSupport = compiledItems.find((item) => item.sourceType === 'imported_summary');
|
|
837
|
+
if (!importedSupport)
|
|
838
|
+
return merged;
|
|
839
|
+
const withoutDuplicate = merged.filter((item) => item.id !== importedSupport.id && item.sourceAnchor?.eventId !== importedSupport.sourceAnchor?.eventId);
|
|
840
|
+
if (withoutDuplicate.length < limit)
|
|
841
|
+
return [...withoutDuplicate, importedSupport];
|
|
842
|
+
return [...withoutDuplicate.slice(0, Math.max(0, limit - 1)), importedSupport];
|
|
843
|
+
}
|
|
844
|
+
compiledItemsForHistoricalQuery(queryPlan, query, limit) {
|
|
845
|
+
const retrievalLimit = Math.max(limit * 4, 24);
|
|
846
|
+
const out = [];
|
|
847
|
+
const seen = new Set();
|
|
848
|
+
for (const searchText of uniqueNonEmpty([queryPlan.primarySearchText, ...queryPlan.searchTexts, queryPlan.originalQuery])) {
|
|
849
|
+
const rawEvidence = this.kernel.navigateMemory(searchText, {
|
|
850
|
+
projectId: query.projectId,
|
|
851
|
+
limit: retrievalLimit,
|
|
852
|
+
startTime: query.startTime,
|
|
853
|
+
endTime: query.endTime,
|
|
854
|
+
}).rawEvidence;
|
|
855
|
+
const items = this.filterAgentEvidence(rawEvidence, query.agentId, query.collection, query.excludeSessionId)
|
|
856
|
+
.map((neuron) => this.toAgentRecallItem(neuron));
|
|
857
|
+
for (const item of items) {
|
|
858
|
+
const key = item.id || item.sourceAnchor?.eventId;
|
|
859
|
+
if (key && seen.has(key))
|
|
860
|
+
continue;
|
|
861
|
+
if (key)
|
|
862
|
+
seen.add(key);
|
|
863
|
+
out.push(item);
|
|
864
|
+
if (out.length >= limit)
|
|
865
|
+
return out;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return out;
|
|
869
|
+
}
|
|
870
|
+
filterCompiledItemsByQueryCues(items, queryPlan) {
|
|
871
|
+
if (!this.queryHasStructuredCue(queryPlan))
|
|
872
|
+
return items;
|
|
873
|
+
const cues = this.structuredCueTerms(queryPlan);
|
|
874
|
+
if (!cues.length)
|
|
875
|
+
return items;
|
|
876
|
+
return items.filter((item) => {
|
|
877
|
+
const haystack = this.itemSearchableText(item).toLowerCase();
|
|
878
|
+
if (queryPlan.intent === 'action_history') {
|
|
879
|
+
const entityTerms = extractEntityCues(queryPlan.originalQuery).map((entity) => entity.label.toLowerCase());
|
|
880
|
+
const entityMatched = entityTerms.length === 0 || entityTerms.some((term) => haystack.includes(term));
|
|
881
|
+
const queryKinds = inferActionKinds(queryPlan.originalQuery);
|
|
882
|
+
const allowedKinds = queryKinds.length ? queryKinds : ['started', 'installed', 'configured', 'restarted', 'stopped', 'operated', 'implemented'];
|
|
883
|
+
const itemKinds = inferActionKinds(haystack);
|
|
884
|
+
const actionMatched = itemKinds.some((kind) => allowedKinds.includes(kind));
|
|
885
|
+
return entityMatched && actionMatched;
|
|
886
|
+
}
|
|
887
|
+
return cues.some((cue) => haystack.includes(cue.toLowerCase()));
|
|
888
|
+
});
|
|
889
|
+
}
|
|
710
890
|
dedupeRawEventsByTurnPreferUser(events) {
|
|
711
891
|
const byTurn = new Map();
|
|
712
892
|
for (const event of events) {
|
|
@@ -727,6 +907,8 @@ export class KernelAgentMemoryBackend {
|
|
|
727
907
|
expandRawSearchTexts(queryPlan) {
|
|
728
908
|
const hostNeutralKeywords = queryPlan.keywords.filter((keyword) => !/^(hermes|openclaw|cogmem)$/i.test(keyword));
|
|
729
909
|
return uniqueNonEmpty([
|
|
910
|
+
queryPlan.originalQuery,
|
|
911
|
+
queryPlan.keywords.join(' '),
|
|
730
912
|
...queryPlan.searchTexts,
|
|
731
913
|
hostNeutralKeywords.join(' '),
|
|
732
914
|
...hostNeutralKeywords.filter((keyword) => keyword.length >= 2),
|
|
@@ -873,6 +1055,21 @@ export class KernelAgentMemoryBackend {
|
|
|
873
1055
|
return true;
|
|
874
1056
|
return false;
|
|
875
1057
|
}
|
|
1058
|
+
isRawEventInRecallScope(event, query, effectiveIntent) {
|
|
1059
|
+
if (query.projectId && event.projectId !== query.projectId)
|
|
1060
|
+
return false;
|
|
1061
|
+
if (query.workspaceId && event.workspaceId !== query.workspaceId)
|
|
1062
|
+
return false;
|
|
1063
|
+
if (query.threadId && event.threadId !== query.threadId)
|
|
1064
|
+
return false;
|
|
1065
|
+
if (!this.isAgentRawEvent(event, query.agentId))
|
|
1066
|
+
return false;
|
|
1067
|
+
if (!this.isAllowedSession(event, query, effectiveIntent))
|
|
1068
|
+
return false;
|
|
1069
|
+
if (!this.isAllowedRawEventCollection(event, query.collection))
|
|
1070
|
+
return false;
|
|
1071
|
+
return true;
|
|
1072
|
+
}
|
|
876
1073
|
isOperationalNoiseRawEvent(event) {
|
|
877
1074
|
const payload = event.payload;
|
|
878
1075
|
const tags = Array.isArray(payload.metadata?.tags) ? payload.metadata.tags : [];
|
|
@@ -884,10 +1081,10 @@ export class KernelAgentMemoryBackend {
|
|
|
884
1081
|
}
|
|
885
1082
|
return isOperationalNoiseText(typeof payload.text === 'string' ? payload.text : JSON.stringify(event.payload));
|
|
886
1083
|
}
|
|
887
|
-
isAllowedSession(event, query) {
|
|
1084
|
+
isAllowedSession(event, query, effectiveIntent = query.intent) {
|
|
888
1085
|
if (query.excludeSessionId && event.sessionId === query.excludeSessionId)
|
|
889
1086
|
return false;
|
|
890
|
-
if (query.sessionId &&
|
|
1087
|
+
if (query.sessionId && effectiveIntent && effectiveIntent !== 'memory_recall' && event.sessionId === query.sessionId)
|
|
891
1088
|
return false;
|
|
892
1089
|
return true;
|
|
893
1090
|
}
|
|
@@ -897,12 +1094,13 @@ export class KernelAgentMemoryBackend {
|
|
|
897
1094
|
|| typeof payload.output === 'string'
|
|
898
1095
|
|| typeof payload.title === 'string';
|
|
899
1096
|
}
|
|
900
|
-
quoteEventPriority(event) {
|
|
1097
|
+
quoteEventPriority(event, queryPlan) {
|
|
1098
|
+
const cuePenalty = queryPlan && this.rawEventCueScore(event, queryPlan) > 0 ? 0 : queryPlan ? 10 : 0;
|
|
901
1099
|
if (event.role === 'user')
|
|
902
|
-
return
|
|
1100
|
+
return cuePenalty;
|
|
903
1101
|
if (event.role === 'assistant')
|
|
904
|
-
return 1;
|
|
905
|
-
return 2;
|
|
1102
|
+
return cuePenalty + 1;
|
|
1103
|
+
return cuePenalty + 2;
|
|
906
1104
|
}
|
|
907
1105
|
isQuoteSourceEvent(event) {
|
|
908
1106
|
return event.role === 'user' || (!event.role && event.rawEventType === 'message');
|
|
@@ -1317,6 +1515,38 @@ function uniqueNonEmpty(values) {
|
|
|
1317
1515
|
function laneAllowed(policy, lane) {
|
|
1318
1516
|
return !policy || policy.allowedLanes.includes(lane);
|
|
1319
1517
|
}
|
|
1518
|
+
function localDateCue(query, options = {}) {
|
|
1519
|
+
const currentYear = localYear(options);
|
|
1520
|
+
const iso = query.match(/(20\d{2})[-年\/.](\d{1,2})[-月\/.](\d{1,2})日?/);
|
|
1521
|
+
if (iso)
|
|
1522
|
+
return `${iso[1]}-${padDatePart(Number(iso[2]))}-${padDatePart(Number(iso[3]))}`;
|
|
1523
|
+
const cn = query.match(/(?:(20\d{2})年)?(\d{1,2})月(\d{1,2})(?:号|日)?/);
|
|
1524
|
+
if (cn)
|
|
1525
|
+
return `${cn[1] || currentYear}-${padDatePart(Number(cn[2]))}-${padDatePart(Number(cn[3]))}`;
|
|
1526
|
+
return undefined;
|
|
1527
|
+
}
|
|
1528
|
+
function localYear(options) {
|
|
1529
|
+
const explicit = options.localDateNow?.match(/^(20\d{2})-\d{2}-\d{2}$/u)?.[1];
|
|
1530
|
+
if (explicit)
|
|
1531
|
+
return Number(explicit);
|
|
1532
|
+
const now = options.now ?? Date.now();
|
|
1533
|
+
try {
|
|
1534
|
+
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
1535
|
+
timeZone: options.timeZone || 'Asia/Tokyo',
|
|
1536
|
+
year: 'numeric',
|
|
1537
|
+
month: '2-digit',
|
|
1538
|
+
day: '2-digit',
|
|
1539
|
+
}).formatToParts(new Date(now));
|
|
1540
|
+
const year = parts.find((part) => part.type === 'year')?.value;
|
|
1541
|
+
if (year)
|
|
1542
|
+
return Number(year);
|
|
1543
|
+
}
|
|
1544
|
+
catch { /* fall back below */ }
|
|
1545
|
+
return new Date(now).getUTCFullYear();
|
|
1546
|
+
}
|
|
1547
|
+
function padDatePart(value) {
|
|
1548
|
+
return String(value).padStart(2, '0');
|
|
1549
|
+
}
|
|
1320
1550
|
function cliArg(value) {
|
|
1321
1551
|
return /^[A-Za-z0-9._:/=@+-]+$/u.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1322
1552
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type AgentRecallIntent = 'memory_recall' | 'previous_session_summary' | 'forensic_quote' | 'historical_discussion';
|
|
1
|
+
export type AgentRecallIntent = 'memory_recall' | 'previous_session_summary' | 'forensic_quote' | 'historical_discussion' | 'action_history';
|
|
2
2
|
export interface AgentRecallQueryCompileInput {
|
|
3
3
|
query: string;
|
|
4
4
|
intent?: AgentRecallIntent;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { extractEntityCues, inferOperationalActionCue } from '../utils/EntityCueExtractor.js';
|
|
1
2
|
const PROTECTED_PHRASES = [
|
|
2
3
|
'CogMem Memory Context',
|
|
3
4
|
'Memory Context',
|
|
4
5
|
'OpenClaw',
|
|
5
|
-
'
|
|
6
|
+
'启动',
|
|
6
7
|
'cogmem',
|
|
7
8
|
'Obsidian',
|
|
8
9
|
'记忆内核',
|
|
@@ -25,6 +26,7 @@ const PROTECTED_PHRASES = [
|
|
|
25
26
|
'报错',
|
|
26
27
|
'错误',
|
|
27
28
|
'工具',
|
|
29
|
+
'操作',
|
|
28
30
|
];
|
|
29
31
|
const QUERY_FILLERS = [
|
|
30
32
|
'我现在不是问你泛泛解释',
|
|
@@ -99,6 +101,9 @@ export function inferAgentRecallIntent(query) {
|
|
|
99
101
|
if (/原话|怎么说的|完整对话|上一句|下一句|exact quote|verbatim/.test(text)) {
|
|
100
102
|
return 'forensic_quote';
|
|
101
103
|
}
|
|
104
|
+
if (/(让你.{0,20}(对|给|把)?.{0,20}(做过|做了|启动|安装|配置|修改|重启|停止|操作|处理|执行)|对.{0,30}(做过什么|做了什么|哪些操作)|what did (i ask you to do|you do) to|operations? on)/i.test(text)) {
|
|
105
|
+
return 'action_history';
|
|
106
|
+
}
|
|
102
107
|
if (/记得.{0,12}(聊过|讨论过|说过)|还记得|之前.{0,12}(聊过|讨论过|说过)|以前.{0,12}(聊过|讨论过|说过)|(过去|几个月前|半年前|上个月|前几天|昨天|上次|上个).{0,20}(聊过|讨论过|说过)|当时.{0,12}(聊|说|问)|那次.{0,12}(聊|说|问)|有没有.{0,12}(记录|聊过|讨论过)|have we discussed|did we talk about|previously discussed/.test(text)) {
|
|
103
108
|
return 'historical_discussion';
|
|
104
109
|
}
|
|
@@ -202,6 +207,13 @@ function buildSemanticCuePhrases(keywords, query, anchorText) {
|
|
|
202
207
|
out.push('产品コード');
|
|
203
208
|
out.push('数量');
|
|
204
209
|
}
|
|
210
|
+
const action = inferOperationalActionCue(text);
|
|
211
|
+
for (const entity of extractEntityCues(text, 4)) {
|
|
212
|
+
out.push(entity.label);
|
|
213
|
+
out.push(`${entity.label} 操作`);
|
|
214
|
+
if (action)
|
|
215
|
+
out.push(`${action.label} ${entity.label}`);
|
|
216
|
+
}
|
|
205
217
|
return uniqueNonEmpty(out);
|
|
206
218
|
}
|
|
207
219
|
function extractTemporalHints(query) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { serializeUntrustedMemory } from './UntrustedMemorySerializer.js';
|
|
2
3
|
export function createMemoryUsageReceipt(input) {
|
|
3
4
|
const createdAt = input.createdAt ?? Date.now();
|
|
4
5
|
const turnId = input.turnId || `${input.sessionId}:${createdAt}`;
|
|
@@ -29,14 +30,14 @@ export function createMemoryUsageReceipt(input) {
|
|
|
29
30
|
}
|
|
30
31
|
export function formatMemoryUsageBridge(receipt, maxChars = 1200) {
|
|
31
32
|
const lines = [
|
|
32
|
-
`<COGMEM_TURN_BRIDGE turn_id="${
|
|
33
|
+
`<COGMEM_TURN_BRIDGE turn_id="${serializeUntrustedMemory(receipt.turnId, 180)}" source="cogmem" compact="true" ttl_turns="${receipt.ttlTurns}" compile_allowed="false">`,
|
|
33
34
|
'Previous assistant answer used Cogmem memory.',
|
|
34
35
|
'',
|
|
35
36
|
'Used memory themes:',
|
|
36
37
|
...listLines(receipt.usedThemes),
|
|
37
38
|
'',
|
|
38
39
|
'Working conclusion produced in that turn:',
|
|
39
|
-
`- ${receipt.workingConclusion || 'No compact conclusion recorded.'}`,
|
|
40
|
+
`- ${serializeUntrustedMemory(receipt.workingConclusion || 'No compact conclusion recorded.', 260)}`,
|
|
40
41
|
'',
|
|
41
42
|
'Source anchors:',
|
|
42
43
|
...listLines(receipt.sourceAnchors.map(formatSourceAnchor)),
|
|
@@ -96,19 +97,16 @@ function firstSentence(text, limit) {
|
|
|
96
97
|
return sentence.length > limit ? `${sentence.slice(0, limit)}...` : sentence;
|
|
97
98
|
}
|
|
98
99
|
function listLines(values) {
|
|
99
|
-
return values.length > 0 ? values.map((value) => `- ${value}`) : ['- none'];
|
|
100
|
+
return values.length > 0 ? values.map((value) => `- ${serializeUntrustedMemory(value, 260)}`) : ['- none'];
|
|
100
101
|
}
|
|
101
102
|
function formatSourceAnchor(anchor) {
|
|
102
103
|
return [
|
|
103
|
-
anchor.memoryId ? `memory:${anchor.memoryId}` : '',
|
|
104
|
-
anchor.eventId ? `event:${anchor.eventId}` : '',
|
|
105
|
-
anchor.sessionId ? `session:${anchor.sessionId}` : '',
|
|
106
|
-
anchor.role ? `role:${anchor.role}` : '',
|
|
104
|
+
anchor.memoryId ? `memory:${serializeUntrustedMemory(anchor.memoryId, 160)}` : '',
|
|
105
|
+
anchor.eventId ? `event:${serializeUntrustedMemory(anchor.eventId, 160)}` : '',
|
|
106
|
+
anchor.sessionId ? `session:${serializeUntrustedMemory(anchor.sessionId, 160)}` : '',
|
|
107
|
+
anchor.role ? `role:${serializeUntrustedMemory(anchor.role, 80)}` : '',
|
|
107
108
|
].filter(Boolean).join('; ');
|
|
108
109
|
}
|
|
109
|
-
function escapeAttribute(value) {
|
|
110
|
-
return String(value).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
111
|
-
}
|
|
112
110
|
function clampBlock(text, closingTag, maxChars) {
|
|
113
111
|
if (text.length <= maxChars)
|
|
114
112
|
return text;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serializeUntrustedMemory } from './UntrustedMemorySerializer.js';
|
|
1
2
|
export function updateSessionWorkingState(previous, input) {
|
|
2
3
|
const maxChars = Math.max(240, Math.min(4000, Math.floor(input.maxChars ?? previous?.maxChars ?? 1800)));
|
|
3
4
|
const topic = inferTopic(input.userText) || previous?.currentTopic;
|
|
@@ -19,7 +20,7 @@ export function formatSessionWorkingState(state) {
|
|
|
19
20
|
const lines = [
|
|
20
21
|
`<COGMEM_SESSION_STATE scope="current_session" compact="true" persistence="session_only" compile_allowed="false">`,
|
|
21
22
|
'Current working topic:',
|
|
22
|
-
`- ${state.currentTopic || 'unspecified'}`,
|
|
23
|
+
`- ${serializeUntrustedMemory(state.currentTopic || 'unspecified', 180)}`,
|
|
23
24
|
'',
|
|
24
25
|
'Current design direction:',
|
|
25
26
|
...listLines(state.designDirection),
|
|
@@ -72,7 +73,7 @@ function compactSentence(text, limit) {
|
|
|
72
73
|
return sentence.length > limit ? `${sentence.slice(0, limit)}...` : sentence;
|
|
73
74
|
}
|
|
74
75
|
function listLines(values) {
|
|
75
|
-
return values.length > 0 ? values.map((value) => `- ${value}`) : ['- none'];
|
|
76
|
+
return values.length > 0 ? values.map((value) => `- ${serializeUntrustedMemory(value, 260)}`) : ['- none'];
|
|
76
77
|
}
|
|
77
78
|
function appendBounded(existing, additions, limit) {
|
|
78
79
|
const out = [];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { stripCogmemRecallBlocks } from './ContextHygiene.js';
|
|
2
|
+
export function serializeUntrustedMemory(input, limit = 500) {
|
|
3
|
+
const clean = stripCogmemRecallBlocks(String(input ?? '')).text
|
|
4
|
+
.replace(/<\/?COGMEM_[A-Z0-9_:-]+[^>]*>/gi, '')
|
|
5
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, ' ')
|
|
6
|
+
.replace(/\s+/g, ' ')
|
|
7
|
+
.trim()
|
|
8
|
+
.replace(/&/g, '&')
|
|
9
|
+
.replace(/</g, '<')
|
|
10
|
+
.replace(/>/g, '>')
|
|
11
|
+
.replace(/"/g, '"')
|
|
12
|
+
.replace(/'/g, ''');
|
|
13
|
+
return clean.slice(0, Math.max(0, limit));
|
|
14
|
+
}
|
|
@@ -27,6 +27,8 @@ export class ActionFrameExtractor {
|
|
|
27
27
|
if (!event.projectId || (event.role !== 'user' && event.role !== 'tool'))
|
|
28
28
|
continue;
|
|
29
29
|
const text = eventTextForMemory(event);
|
|
30
|
+
if (!text.trim())
|
|
31
|
+
continue;
|
|
30
32
|
const markers = actionMarkers(text);
|
|
31
33
|
if (!markers.length)
|
|
32
34
|
continue;
|