@devflow-tools/memory-engine 0.17.0 → 0.17.2
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/consolidator.d.ts.map +1 -1
- package/dist/consolidator.js +24 -15
- package/dist/consolidator.js.map +1 -1
- package/dist/durable-memory-text.d.ts +8 -0
- package/dist/durable-memory-text.d.ts.map +1 -0
- package/dist/durable-memory-text.js +35 -0
- package/dist/durable-memory-text.js.map +1 -0
- package/dist/event-grouper.d.ts +1 -0
- package/dist/event-grouper.d.ts.map +1 -1
- package/dist/event-grouper.js +18 -13
- package/dist/event-grouper.js.map +1 -1
- package/dist/explicit-memory-enricher.d.ts.map +1 -1
- package/dist/explicit-memory-enricher.js +2 -1
- package/dist/explicit-memory-enricher.js.map +1 -1
- package/dist/hybrid-search.d.ts +23 -0
- package/dist/hybrid-search.d.ts.map +1 -1
- package/dist/hybrid-search.js +171 -17
- package/dist/hybrid-search.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/memory-capsule.d.ts +3 -0
- package/dist/memory-capsule.d.ts.map +1 -1
- package/dist/memory-capsule.js +27 -0
- package/dist/memory-capsule.js.map +1 -1
- package/dist/memory-engine.d.ts +1 -0
- package/dist/memory-engine.d.ts.map +1 -1
- package/dist/memory-engine.js +83 -37
- package/dist/memory-engine.js.map +1 -1
- package/dist/memory-gate.d.ts +6 -0
- package/dist/memory-gate.d.ts.map +1 -1
- package/dist/memory-gate.js +293 -30
- package/dist/memory-gate.js.map +1 -1
- package/dist/memory-maintenance.d.ts +3 -1
- package/dist/memory-maintenance.d.ts.map +1 -1
- package/dist/memory-maintenance.js +46 -1
- package/dist/memory-maintenance.js.map +1 -1
- package/dist/memory-relevance.d.ts +7 -1
- package/dist/memory-relevance.d.ts.map +1 -1
- package/dist/memory-relevance.js +54 -2
- package/dist/memory-relevance.js.map +1 -1
- package/dist/memory-store.d.ts +1 -0
- package/dist/memory-store.d.ts.map +1 -1
- package/dist/memory-store.js +57 -18
- package/dist/memory-store.js.map +1 -1
- package/dist/migrations/v11-to-v12.d.ts +15 -1
- package/dist/migrations/v11-to-v12.d.ts.map +1 -1
- package/dist/migrations/v11-to-v12.js +215 -15
- package/dist/migrations/v11-to-v12.js.map +1 -1
- package/dist/semantic-memory-compiler.d.ts +29 -0
- package/dist/semantic-memory-compiler.d.ts.map +1 -0
- package/dist/semantic-memory-compiler.js +149 -0
- package/dist/semantic-memory-compiler.js.map +1 -0
- package/dist/session-summarizer.d.ts +2 -0
- package/dist/session-summarizer.d.ts.map +1 -1
- package/dist/session-summarizer.js.map +1 -1
- package/package.json +4 -3
package/dist/memory-gate.js
CHANGED
|
@@ -12,7 +12,8 @@ import { join } from 'node:path';
|
|
|
12
12
|
import { isObservationType, observationDedupKey, } from './observation-types.js';
|
|
13
13
|
import { evaluateObservationQuality } from './observation-quality.js';
|
|
14
14
|
import { mergeSemanticEntities, normalizeEntityKey, normalizeSemanticMemory, } from './semantic-memory.js';
|
|
15
|
-
import { validateMemoryCapsules } from './memory-capsule.js';
|
|
15
|
+
import { validateEvidenceSpans, validateMemoryCapsules } from './memory-capsule.js';
|
|
16
|
+
import { emitSemanticControlLog } from '@devflow-tools/telemetry';
|
|
16
17
|
const BATCH_CONCEPT_STOP_WORDS = new Set([
|
|
17
18
|
'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
18
19
|
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'should',
|
|
@@ -180,10 +181,12 @@ export class MemoryGate {
|
|
|
180
181
|
if (this.engine && signals.length > 0) {
|
|
181
182
|
try {
|
|
182
183
|
this.engine.recordSignals(signals);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
emitSemanticControlLog({
|
|
185
|
+
event: 'memory.write.completed',
|
|
186
|
+
identity: { projectRoot: this.projectRoot, sessionId: event.sessionId },
|
|
187
|
+
level: 'debug',
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
data: { stage: 'signals', count: signals.length, types: signals.map(signal => signal.type) },
|
|
187
190
|
});
|
|
188
191
|
}
|
|
189
192
|
catch { /* non-critical */ }
|
|
@@ -263,9 +266,9 @@ export class MemoryGate {
|
|
|
263
266
|
.filter(event => sessionId === undefined || event.session_id === sessionId);
|
|
264
267
|
if (events.length === 0)
|
|
265
268
|
return null;
|
|
266
|
-
const groups = groupEventsBySession(events);
|
|
269
|
+
const groups = groupEventsBySession(events, { projectRoot: this.projectRoot });
|
|
267
270
|
const preserveSessionIds = this.getActiveSessionIds(groups);
|
|
268
|
-
const { selected, discarded } = triageGroupsWithDisposition(groups, 8, { preserveSessionIds });
|
|
271
|
+
const { selected, discarded } = triageGroupsWithDisposition(groups, 8, { preserveSessionIds, projectRoot: this.projectRoot });
|
|
269
272
|
const discardedEventIds = discarded.flatMap(group => group.events).map(event => event.id);
|
|
270
273
|
if (discardedEventIds.length > 0) {
|
|
271
274
|
const discardedSessionIds = [...new Set(discarded.flatMap(group => group.events).map(event => event.session_id).filter(Boolean))];
|
|
@@ -452,10 +455,12 @@ export class MemoryGate {
|
|
|
452
455
|
leases.commitLease(batchId);
|
|
453
456
|
});
|
|
454
457
|
transaction();
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
458
|
+
emitSemanticControlLog({
|
|
459
|
+
event: 'memory.write.completed',
|
|
460
|
+
identity: { projectRoot: this.projectRoot, sessionId: lease.sessionIds[0] },
|
|
461
|
+
level: 'info',
|
|
462
|
+
timestamp: Date.now(),
|
|
463
|
+
data: { stage: 'distill_batch', batchId, observationCount: validObs.length, eventsMarked: lease.eventIds.length },
|
|
459
464
|
});
|
|
460
465
|
return validObs.length;
|
|
461
466
|
}
|
|
@@ -503,6 +508,7 @@ export class MemoryGate {
|
|
|
503
508
|
observations: validObs,
|
|
504
509
|
sourceEvents: [event],
|
|
505
510
|
defaultSourceEventIds: [turnEventId],
|
|
511
|
+
evidenceText: readCanonicalPrompt(event.payload, event.user_message) || undefined,
|
|
506
512
|
});
|
|
507
513
|
if (capsules.rejected.length > 0 || capsules.accepted.length !== validObs.length) {
|
|
508
514
|
throw new Error(`memory_commit_turn failed semantic capsule validation: ${JSON.stringify(capsules.rejected)}`);
|
|
@@ -579,6 +585,156 @@ export class MemoryGate {
|
|
|
579
585
|
})();
|
|
580
586
|
return saved;
|
|
581
587
|
}
|
|
588
|
+
async refineExplicitTurnObservations(turnEventId, sessionId, observations) {
|
|
589
|
+
if (!this.engine)
|
|
590
|
+
await this.forceWarmUp();
|
|
591
|
+
const engine = this.requireEngine();
|
|
592
|
+
const event = engine.store.getEvent(turnEventId);
|
|
593
|
+
if (!event || event.session_id !== sessionId) {
|
|
594
|
+
throw new Error(`Memory turn event ${turnEventId} was not found for session ${sessionId}`);
|
|
595
|
+
}
|
|
596
|
+
const sourceText = readCanonicalPrompt(event.payload, event.user_message);
|
|
597
|
+
if (!sourceText)
|
|
598
|
+
throw new Error(`Memory turn event ${turnEventId} has no canonical user prompt`);
|
|
599
|
+
for (const observation of observations) {
|
|
600
|
+
validateEvidenceSpans(sourceText, observation.evidenceSpans ?? []);
|
|
601
|
+
}
|
|
602
|
+
const durableCandidates = observations.filter(observation => observation.disposition !== 'turn_constraint');
|
|
603
|
+
const turnConstraints = observations.filter(observation => observation.disposition === 'turn_constraint');
|
|
604
|
+
const validObs = durableCandidates.length > 0 ? validateObservationBatch(durableCandidates) : [];
|
|
605
|
+
if (!validObs)
|
|
606
|
+
throw new Error('Explicit memory refinement contains invalid durable candidates');
|
|
607
|
+
const capsules = validateMemoryCapsules({
|
|
608
|
+
projectRoot: this.projectRoot,
|
|
609
|
+
observations: validObs,
|
|
610
|
+
sourceEvents: [event],
|
|
611
|
+
defaultSourceEventIds: [turnEventId],
|
|
612
|
+
evidenceText: sourceText,
|
|
613
|
+
requireEvidenceSpans: true,
|
|
614
|
+
});
|
|
615
|
+
if (capsules.rejected.length > 0 || capsules.accepted.length !== validObs.length) {
|
|
616
|
+
throw new Error(`Explicit memory refinement failed evidence validation: ${JSON.stringify(capsules.rejected)}`);
|
|
617
|
+
}
|
|
618
|
+
const existingIds = new Set(engine.store.listObservations().map(observation => observation.id));
|
|
619
|
+
const turnId = eventTurnId(event, turnEventId);
|
|
620
|
+
const memories = [];
|
|
621
|
+
const retiredMemoryIds = [];
|
|
622
|
+
const operations = [];
|
|
623
|
+
this.getStoreDb().transaction(() => {
|
|
624
|
+
for (const acceptedCapsule of capsules.accepted) {
|
|
625
|
+
const observation = acceptedCapsule.observation;
|
|
626
|
+
const semantic = normalizeSemanticMemory({
|
|
627
|
+
project: this.projectRoot,
|
|
628
|
+
type: observation.type,
|
|
629
|
+
title: observation.title,
|
|
630
|
+
narrative: observation.narrative ?? '',
|
|
631
|
+
concepts: observation.concepts,
|
|
632
|
+
subject: observation.subject,
|
|
633
|
+
predicate: observation.predicate,
|
|
634
|
+
value: observation.value,
|
|
635
|
+
polarity: observation.polarity,
|
|
636
|
+
entities: observation.entities,
|
|
637
|
+
temporal: observation.temporal,
|
|
638
|
+
});
|
|
639
|
+
const write = engine.store.acceptSemanticObservation({
|
|
640
|
+
sessionId,
|
|
641
|
+
project: this.projectRoot,
|
|
642
|
+
type: observation.type,
|
|
643
|
+
title: observation.title,
|
|
644
|
+
narrative: observation.narrative,
|
|
645
|
+
facts: [...new Set([...(observation.facts ?? []), ...(observation.evidenceSpans ?? [])])],
|
|
646
|
+
concepts: mergeSemanticEntities(normalizeBatchConcepts(observation), [semantic.subject, semantic.predicate, semantic.value], semantic.entities.map(normalizeEntityKey)),
|
|
647
|
+
importance: Math.max(8, observation.importance),
|
|
648
|
+
confidence: Math.max(0.9, observation.confidence),
|
|
649
|
+
source: 'explicit_intent',
|
|
650
|
+
sourceEventIds: [turnEventId],
|
|
651
|
+
qualityStatus: 'accepted',
|
|
652
|
+
evidenceStatus: 'verified',
|
|
653
|
+
evidenceDetails: (observation.evidenceSpans ?? []).map(span => ({
|
|
654
|
+
kind: 'host_evidence_span',
|
|
655
|
+
source: turnEventId,
|
|
656
|
+
summary: span,
|
|
657
|
+
})),
|
|
658
|
+
semantic,
|
|
659
|
+
capsule: acceptedCapsule.capsule,
|
|
660
|
+
projection: acceptedCapsule.projection,
|
|
661
|
+
preferIncomingNarrative: true,
|
|
662
|
+
});
|
|
663
|
+
const row = write.row;
|
|
664
|
+
operations.push(write.action.toLocaleLowerCase('en-US'));
|
|
665
|
+
memories.push({
|
|
666
|
+
id: row.id,
|
|
667
|
+
type: row.type,
|
|
668
|
+
title: row.title,
|
|
669
|
+
content: row.narrative,
|
|
670
|
+
source: row.source,
|
|
671
|
+
scope: row.project,
|
|
672
|
+
confidence: row.confidence,
|
|
673
|
+
importance: row.importance,
|
|
674
|
+
score: 1,
|
|
675
|
+
createdAt: row.timestamp,
|
|
676
|
+
indexStatus: row.embedding ? 'indexed' : 'pending',
|
|
677
|
+
});
|
|
678
|
+
engine.store.recordPendingTurnObservation({
|
|
679
|
+
turnEventId,
|
|
680
|
+
turnId,
|
|
681
|
+
sessionId,
|
|
682
|
+
project: this.projectRoot,
|
|
683
|
+
observationId: row.id,
|
|
684
|
+
createdByTurn: !existingIds.has(row.id),
|
|
685
|
+
createdAt: event.created_at,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
const constraintSpans = turnConstraints.flatMap(candidate => candidate.evidenceSpans ?? []);
|
|
689
|
+
const baseline = engine.store.listObservations({ project: this.projectRoot })
|
|
690
|
+
.filter(observation => observation.source === 'explicit_intent')
|
|
691
|
+
.filter(observation => parseLearningStringArray(observation.source_event_ids).includes(turnEventId));
|
|
692
|
+
const refinedIds = new Set(memories.map(memory => memory.id));
|
|
693
|
+
for (const observation of baseline) {
|
|
694
|
+
if (refinedIds.has(observation.id) || observation.resolution_state === 'superseded')
|
|
695
|
+
continue;
|
|
696
|
+
const replacementIndex = durableCandidates.findIndex(candidate => ((candidate.evidenceSpans ?? []).some(span => sameGroundedClause(observation.narrative, span))));
|
|
697
|
+
const replacement = replacementIndex >= 0
|
|
698
|
+
? memories[replacementIndex]
|
|
699
|
+
: durableCandidates.length === 1 ? memories[0] : undefined;
|
|
700
|
+
if (!replacement || replacement.id === observation.id)
|
|
701
|
+
continue;
|
|
702
|
+
engine.store.setObservationResolution(observation.id, 'superseded', replacement.id);
|
|
703
|
+
engine.store.addSemanticObservationRelation(replacement.id, observation.id, 'supersedes');
|
|
704
|
+
retiredMemoryIds.push(observation.id);
|
|
705
|
+
operations.push('supersede');
|
|
706
|
+
}
|
|
707
|
+
for (const observation of baseline) {
|
|
708
|
+
if (observation.resolution_state === 'superseded' || retiredMemoryIds.includes(observation.id))
|
|
709
|
+
continue;
|
|
710
|
+
if (!constraintSpans.some(span => sameGroundedClause(observation.narrative, span)))
|
|
711
|
+
continue;
|
|
712
|
+
if (engine.store.quarantineObservation(observation.id, 'host_classified_turn_constraint', {
|
|
713
|
+
turnId,
|
|
714
|
+
eventId: turnEventId,
|
|
715
|
+
evidenceSpans: constraintSpans,
|
|
716
|
+
})) {
|
|
717
|
+
retiredMemoryIds.push(observation.id);
|
|
718
|
+
operations.push('retire');
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
})();
|
|
722
|
+
emitSemanticControlLog({
|
|
723
|
+
event: 'memory.semantic.refined',
|
|
724
|
+
identity: { projectRoot: this.projectRoot, sessionId, turnId },
|
|
725
|
+
level: 'info',
|
|
726
|
+
timestamp: Date.now(),
|
|
727
|
+
data: {
|
|
728
|
+
candidateCount: observations.length,
|
|
729
|
+
durableCount: durableCandidates.length,
|
|
730
|
+
turnConstraintCount: turnConstraints.length,
|
|
731
|
+
memoryIds: memories.map(memory => memory.id),
|
|
732
|
+
retiredMemoryIds,
|
|
733
|
+
operations,
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
return { memories, retiredMemoryIds, operations };
|
|
737
|
+
}
|
|
582
738
|
async skipTurnEvent(turnEventId, sessionId, reason) {
|
|
583
739
|
if (!this.engine)
|
|
584
740
|
await this.forceWarmUp();
|
|
@@ -639,7 +795,13 @@ export class MemoryGate {
|
|
|
639
795
|
* @deprecated Use saveDistilledObservationsByBatch(batchId, observations) instead.
|
|
640
796
|
*/
|
|
641
797
|
async saveDistilledObservations(observations, sessionId) {
|
|
642
|
-
|
|
798
|
+
emitSemanticControlLog({
|
|
799
|
+
event: 'memory.write.degraded',
|
|
800
|
+
identity: { projectRoot: this.projectRoot, sessionId },
|
|
801
|
+
level: 'warn',
|
|
802
|
+
timestamp: Date.now(),
|
|
803
|
+
data: { stage: 'legacy_distill', reason: 'deprecated_session_write', fallback: 'legacy_compatibility_path' },
|
|
804
|
+
});
|
|
643
805
|
if (!this.engine)
|
|
644
806
|
return 0;
|
|
645
807
|
const validObs = validateObservationBatch(observations);
|
|
@@ -684,10 +846,12 @@ export class MemoryGate {
|
|
|
684
846
|
engine.store.markEventsProcessed(unprocessedIds);
|
|
685
847
|
});
|
|
686
848
|
transaction();
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
849
|
+
emitSemanticControlLog({
|
|
850
|
+
event: 'memory.write.completed',
|
|
851
|
+
identity: { projectRoot: this.projectRoot, sessionId },
|
|
852
|
+
level: 'info',
|
|
853
|
+
timestamp: Date.now(),
|
|
854
|
+
data: { stage: 'legacy_distill', observationCount: validObs.length, eventsMarked: unprocessedIds.length },
|
|
691
855
|
});
|
|
692
856
|
return validObs.length;
|
|
693
857
|
}
|
|
@@ -716,11 +880,12 @@ export class MemoryGate {
|
|
|
716
880
|
const events = this.engine.store.getUnprocessedEvents();
|
|
717
881
|
if (events.length === 0)
|
|
718
882
|
return 0;
|
|
719
|
-
const groups = groupEventsBySession(events, { log: false });
|
|
883
|
+
const groups = groupEventsBySession(events, { log: false, projectRoot: this.projectRoot });
|
|
720
884
|
const preserveSessionIds = this.getActiveSessionIds(groups);
|
|
721
885
|
const { discarded } = triageGroupsWithDisposition(groups, Number.MAX_SAFE_INTEGER, {
|
|
722
886
|
log: false,
|
|
723
887
|
preserveSessionIds,
|
|
888
|
+
projectRoot: this.projectRoot,
|
|
724
889
|
});
|
|
725
890
|
const ids = discarded.flatMap(group => group.events).map(event => event.id);
|
|
726
891
|
return this.engine.store.discardEvents(ids, 'low_value_auto_triage', `auto-triage:${Date.now()}`);
|
|
@@ -753,7 +918,10 @@ export class MemoryGate {
|
|
|
753
918
|
this.projectValid = true;
|
|
754
919
|
}
|
|
755
920
|
this.state = 'warming';
|
|
756
|
-
|
|
921
|
+
emitSemanticControlLog({
|
|
922
|
+
event: 'memory.engine.transitioned', identity: { projectRoot: this.projectRoot }, level: 'info', timestamp: Date.now(),
|
|
923
|
+
data: { from: 'cold', to: 'warming' },
|
|
924
|
+
});
|
|
757
925
|
this.engine = new MemoryEngine(this.projectRoot, this.embedder);
|
|
758
926
|
this.setLeaseStore(new LeaseStore(this.getStoreDb()));
|
|
759
927
|
// Clean up zombie sessions (>24h active)
|
|
@@ -761,7 +929,13 @@ export class MemoryGate {
|
|
|
761
929
|
const zombies = this.engine.store.getZombieSessions(24);
|
|
762
930
|
for (const z of zombies) {
|
|
763
931
|
this.engine.store.closeSession(z.id);
|
|
764
|
-
|
|
932
|
+
emitSemanticControlLog({
|
|
933
|
+
event: 'memory.engine.transitioned',
|
|
934
|
+
identity: { projectRoot: this.projectRoot, sessionId: z.id },
|
|
935
|
+
level: 'warn',
|
|
936
|
+
timestamp: Date.now(),
|
|
937
|
+
data: { from: 'active', to: 'closed', reason: 'zombie_session', ageHours: Math.round((Date.now() - z.created_at) / 3600000) },
|
|
938
|
+
});
|
|
765
939
|
}
|
|
766
940
|
}
|
|
767
941
|
catch { /* non-critical */ }
|
|
@@ -771,7 +945,10 @@ export class MemoryGate {
|
|
|
771
945
|
}
|
|
772
946
|
catch { /* non-critical */ }
|
|
773
947
|
this.state = 'hot';
|
|
774
|
-
|
|
948
|
+
emitSemanticControlLog({
|
|
949
|
+
event: 'memory.engine.transitioned', identity: { projectRoot: this.projectRoot }, level: 'info', timestamp: Date.now(),
|
|
950
|
+
data: { from: 'warming', to: 'hot' },
|
|
951
|
+
});
|
|
775
952
|
}
|
|
776
953
|
// --- IMemoryEngine implementation (delegates to engine when hot) ---
|
|
777
954
|
requireEngine() {
|
|
@@ -854,7 +1031,28 @@ export class MemoryGate {
|
|
|
854
1031
|
async saveExplicitMemoryIntent(content, sessionId, sourceEventId) {
|
|
855
1032
|
if (!this.engine)
|
|
856
1033
|
await this.forceWarmUp();
|
|
857
|
-
|
|
1034
|
+
try {
|
|
1035
|
+
const result = await this.requireEngine().saveExplicitMemoryIntent(content, sessionId, sourceEventId);
|
|
1036
|
+
emitSemanticControlLog({
|
|
1037
|
+
event: 'memory.write.completed',
|
|
1038
|
+
identity: { projectRoot: this.projectRoot, sessionId },
|
|
1039
|
+
level: 'info',
|
|
1040
|
+
timestamp: Date.now(),
|
|
1041
|
+
data: {
|
|
1042
|
+
stage: 'explicit_memory', memoryId: result.id, relatedMemoryIds: result.relatedMemoryIds ?? [],
|
|
1043
|
+
semanticStatus: result.semanticDecision?.status,
|
|
1044
|
+
operations: result.semanticDecision?.operations ?? [],
|
|
1045
|
+
},
|
|
1046
|
+
});
|
|
1047
|
+
return result;
|
|
1048
|
+
}
|
|
1049
|
+
catch (error) {
|
|
1050
|
+
emitSemanticControlLog({
|
|
1051
|
+
event: 'memory.write.degraded', identity: { projectRoot: this.projectRoot, sessionId }, level: 'error', timestamp: Date.now(),
|
|
1052
|
+
data: { stage: 'explicit_memory', reason: memoryErrorReason(error) },
|
|
1053
|
+
});
|
|
1054
|
+
throw error;
|
|
1055
|
+
}
|
|
858
1056
|
}
|
|
859
1057
|
async enrichExplicitMemoryObservation(observationId) {
|
|
860
1058
|
if (!this.engine)
|
|
@@ -924,15 +1122,28 @@ export class MemoryGate {
|
|
|
924
1122
|
const rows = engine.store.listObservationsMissingEmbedding(batchSize, lastId);
|
|
925
1123
|
if (rows.length === 0)
|
|
926
1124
|
break;
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
|
|
1125
|
+
const texts = rows.map(row => `${row.title}\n${row.narrative}`);
|
|
1126
|
+
let indexed = [];
|
|
1127
|
+
try {
|
|
1128
|
+
const embeddings = await this.embedder.embedBatch(texts);
|
|
1129
|
+
if (embeddings.length !== rows.length) {
|
|
1130
|
+
throw new Error(`Observation vector count mismatch: ${rows.length} observations, ${embeddings.length} vectors`);
|
|
1131
|
+
}
|
|
1132
|
+
indexed = rows.map((row, index) => ({ id: row.id, embedding: embeddings[index] }));
|
|
1133
|
+
}
|
|
1134
|
+
catch {
|
|
1135
|
+
for (let index = 0; index < rows.length; index++) {
|
|
1136
|
+
try {
|
|
1137
|
+
indexed.push({ id: rows[index].id, embedding: await this.embedder.embed(texts[index]) });
|
|
1138
|
+
}
|
|
1139
|
+
catch {
|
|
1140
|
+
// Keep this observation pending; deterministic retrieval remains available.
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
930
1143
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
})));
|
|
935
|
-
processed += rows.length;
|
|
1144
|
+
if (indexed.length > 0)
|
|
1145
|
+
engine.store.insertObservationEmbeddings(indexed);
|
|
1146
|
+
processed += indexed.length;
|
|
936
1147
|
lastId = rows.at(-1).id;
|
|
937
1148
|
const coverage = engine.store.countObservationVectors();
|
|
938
1149
|
options.onProgress?.({ processed, remaining: coverage.missing, lastId });
|
|
@@ -964,7 +1175,38 @@ export class MemoryGate {
|
|
|
964
1175
|
async search(query, options) {
|
|
965
1176
|
if (!this.engine)
|
|
966
1177
|
await this.forceWarmUp();
|
|
967
|
-
|
|
1178
|
+
const startedAt = Date.now();
|
|
1179
|
+
try {
|
|
1180
|
+
const results = await this.requireEngine().search(query, options);
|
|
1181
|
+
emitSemanticControlLog({
|
|
1182
|
+
event: 'memory.search.completed',
|
|
1183
|
+
identity: {
|
|
1184
|
+
projectRoot: this.projectRoot,
|
|
1185
|
+
sessionId: options?.sessionId,
|
|
1186
|
+
executionId: options?.executionId,
|
|
1187
|
+
requestId: options?.requestId,
|
|
1188
|
+
},
|
|
1189
|
+
level: 'info',
|
|
1190
|
+
timestamp: Date.now(),
|
|
1191
|
+
data: {
|
|
1192
|
+
status: results.length > 0 ? 'selected' : 'empty', selected: results.length,
|
|
1193
|
+
verified: results.filter(result => result.entry.status === 'approved').length,
|
|
1194
|
+
durationMs: Date.now() - startedAt,
|
|
1195
|
+
vectorReady: results.filter(result => (result.scoreDetails?.semanticScore ?? 0) > 0).length,
|
|
1196
|
+
},
|
|
1197
|
+
});
|
|
1198
|
+
return results;
|
|
1199
|
+
}
|
|
1200
|
+
catch (error) {
|
|
1201
|
+
emitSemanticControlLog({
|
|
1202
|
+
event: 'memory.search.completed',
|
|
1203
|
+
identity: { projectRoot: this.projectRoot, sessionId: options?.sessionId, executionId: options?.executionId, requestId: options?.requestId },
|
|
1204
|
+
level: 'error',
|
|
1205
|
+
timestamp: Date.now(),
|
|
1206
|
+
data: { status: 'failed', selected: 0, durationMs: Date.now() - startedAt, reason: memoryErrorReason(error) },
|
|
1207
|
+
});
|
|
1208
|
+
throw error;
|
|
1209
|
+
}
|
|
968
1210
|
}
|
|
969
1211
|
async listMemories(filter) {
|
|
970
1212
|
if (!this.engine)
|
|
@@ -1070,6 +1312,24 @@ function eventTurnId(event, fallbackEventId) {
|
|
|
1070
1312
|
}
|
|
1071
1313
|
return `turn:${fallbackEventId}`;
|
|
1072
1314
|
}
|
|
1315
|
+
function readCanonicalPrompt(payloadJson, userMessage) {
|
|
1316
|
+
if (payloadJson) {
|
|
1317
|
+
try {
|
|
1318
|
+
const payload = JSON.parse(payloadJson);
|
|
1319
|
+
if (typeof payload.prompt === 'string' && payload.prompt.trim())
|
|
1320
|
+
return payload.prompt.trim();
|
|
1321
|
+
}
|
|
1322
|
+
catch { }
|
|
1323
|
+
}
|
|
1324
|
+
return userMessage?.trim() ?? '';
|
|
1325
|
+
}
|
|
1326
|
+
function sameGroundedClause(narrative, evidenceSpan) {
|
|
1327
|
+
const left = narrative.normalize('NFKC').trim().toLocaleLowerCase('en-US');
|
|
1328
|
+
const right = evidenceSpan.normalize('NFKC').trim().toLocaleLowerCase('en-US');
|
|
1329
|
+
if (left.length < 4 || right.length < 4)
|
|
1330
|
+
return false;
|
|
1331
|
+
return left === right || (right.includes(left) && left.length / right.length >= 0.7);
|
|
1332
|
+
}
|
|
1073
1333
|
// --- L1 Rule Scorer (backward compat) ---
|
|
1074
1334
|
function scoreL1Rules(event) {
|
|
1075
1335
|
let score = 0;
|
|
@@ -1131,5 +1391,8 @@ function scoreDialogSignals(message) {
|
|
|
1131
1391
|
}
|
|
1132
1392
|
return maxScore;
|
|
1133
1393
|
}
|
|
1394
|
+
function memoryErrorReason(error) {
|
|
1395
|
+
return (error instanceof Error ? error.message : String(error)).trim().slice(0, 500) || 'memory_operation_failed';
|
|
1396
|
+
}
|
|
1134
1397
|
export { scoreL1Rules, scoreDialogSignals };
|
|
1135
1398
|
//# sourceMappingURL=memory-gate.js.map
|