@jungjaehoon/mama-core 1.7.0 → 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/db/migrations/038-create-vnext-operator-contracts.sql +57 -0
- package/db/migrations/039-add-connector-event-operator-ingest-seq.sql +96 -0
- package/db/migrations/040-create-operator-memory-commit-intents.sql +19 -0
- package/db/migrations/041-enforce-operator-memory-commit-claim-invariant.sql +74 -0
- package/db/migrations/042-embedding-prefix-scheme.sql +46 -0
- package/dist/db-adapter/node-sqlite-adapter.d.ts +14 -1
- package/dist/db-adapter/node-sqlite-adapter.js +323 -6
- package/dist/db-manager.d.ts +9 -10
- package/dist/db-manager.js +59 -4
- package/dist/embedding-client.d.ts +3 -1
- package/dist/embedding-client.js +3 -2
- package/dist/embedding-server/index.d.ts +2 -0
- package/dist/embedding-server/index.js +10 -2
- package/dist/embeddings.d.ts +5 -3
- package/dist/embeddings.js +16 -10
- package/dist/index.d.ts +3 -2
- package/dist/index.js +7 -4
- package/dist/mama-api.js +1 -1
- package/dist/memory/api.d.ts +7 -1
- package/dist/memory/api.js +236 -31
- package/dist/memory/provenance.d.ts +1 -0
- package/dist/memory/provenance.js +1 -0
- package/dist/memory-inject.js +1 -1
- package/dist/provenance/source-ref.d.ts +29 -0
- package/dist/provenance/source-ref.js +204 -0
- package/package.json +2 -1
package/dist/memory/api.js
CHANGED
|
@@ -4,10 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getChannelSummary = exports.upsertChannelSummary = exports.LEXICAL_STOPWORDS = void 0;
|
|
7
|
+
exports.buildDecisionId = buildDecisionId;
|
|
7
8
|
exports.getLexicalQueryTokens = getLexicalQueryTokens;
|
|
8
9
|
exports.loadEdgesForIds = loadEdgesForIds;
|
|
9
10
|
exports.saveMemory = saveMemory;
|
|
10
11
|
exports.saveMemoryWithTrustedProvenance = saveMemoryWithTrustedProvenance;
|
|
12
|
+
exports.promoteMemoryStatus = promoteMemoryStatus;
|
|
11
13
|
exports.buildProfile = buildProfile;
|
|
12
14
|
exports.recallMemory = recallMemory;
|
|
13
15
|
exports.ingestMemory = ingestMemory;
|
|
@@ -40,8 +42,18 @@ const search_quality_js_1 = require("../search/search-quality.js");
|
|
|
40
42
|
const event_store_js_1 = require("./event-store.js");
|
|
41
43
|
const provenance_js_1 = require("./provenance.js");
|
|
42
44
|
function buildDecisionId(topic) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
// Non-ASCII topics (Korean etc.) used to collapse into bare underscore runs
|
|
46
|
+
// ("decision_______<ts>") - unreadable and near-colliding. Collapse the runs
|
|
47
|
+
// and fall back to a stable topic hash when nothing ASCII survives; the
|
|
48
|
+
// human-readable topic itself is stored verbatim in the topic column.
|
|
49
|
+
const safeTopic = topic
|
|
50
|
+
.replace(/[^a-z0-9_]+/gi, '_')
|
|
51
|
+
.replace(/_+/g, '_')
|
|
52
|
+
.replace(/^_+|_+$/g, '')
|
|
53
|
+
.toLowerCase();
|
|
54
|
+
// sha256 (not sha1) purely to keep SAST scanners quiet - this is an id slug, not crypto.
|
|
55
|
+
const slug = safeTopic || `t${node_crypto_1.default.createHash('sha256').update(topic).digest('hex').slice(0, 8)}`;
|
|
56
|
+
return `decision_${slug}_${Date.now()}_${node_crypto_1.default.randomUUID().slice(0, 8)}`;
|
|
45
57
|
}
|
|
46
58
|
function buildSaveEventReason(toolName, gatewayCallId) {
|
|
47
59
|
const parts = ['saved'];
|
|
@@ -442,11 +454,16 @@ async function saveMemoryInternal(input, options) {
|
|
|
442
454
|
const id = buildDecisionId(input.topic);
|
|
443
455
|
const now = Date.now();
|
|
444
456
|
const provenance = (0, provenance_js_1.normalizeMemoryWriteProvenance)(options);
|
|
457
|
+
const targetStatus = input.status ?? 'active';
|
|
458
|
+
const shouldApplyEvolution = targetStatus === 'active';
|
|
445
459
|
// Find evolution candidates: exact topic match first, then semantic search fallback
|
|
446
460
|
const primaryScope = input.scopes.length > 0 ? input.scopes[0] : null;
|
|
447
461
|
const excludeIds = new Set(input.excludeIds ?? []);
|
|
448
462
|
let existingCandidates;
|
|
449
|
-
if (
|
|
463
|
+
if (!shouldApplyEvolution) {
|
|
464
|
+
existingCandidates = [];
|
|
465
|
+
}
|
|
466
|
+
else if (primaryScope) {
|
|
450
467
|
const scopeId = await (0, db_manager_js_1.ensureMemoryScope)(primaryScope.kind, primaryScope.id);
|
|
451
468
|
existingCandidates = adapter
|
|
452
469
|
.prepare(`
|
|
@@ -475,11 +492,14 @@ async function saveMemoryInternal(input, options) {
|
|
|
475
492
|
.all(input.topic).filter((c) => !excludeIds.has(c.id));
|
|
476
493
|
}
|
|
477
494
|
// Semantic fallback: if no exact topic match, find similar memories via vector search
|
|
478
|
-
if (existingCandidates.length === 0) {
|
|
495
|
+
if (shouldApplyEvolution && existingCandidates.length === 0) {
|
|
479
496
|
try {
|
|
480
497
|
const queryText = `${input.topic} ${input.summary}`;
|
|
481
|
-
const embedding = await (0, embeddings_js_1.generateEmbedding)(queryText);
|
|
482
|
-
|
|
498
|
+
const embedding = await (0, embeddings_js_1.generateEmbedding)(queryText, 'query');
|
|
499
|
+
// Exclude superseded history so a dense supersede chain cannot fill all 3
|
|
500
|
+
// candidate slots and hide the prior ACTIVE decision (which would break the
|
|
501
|
+
// evolution link and create an active duplicate).
|
|
502
|
+
const semanticResults = await (0, db_manager_js_1.vectorSearch)(embedding, 3, 0.82, undefined, Array.from(EXCLUDED_STATUSES));
|
|
483
503
|
// Scope-filter semantic candidates when a primary scope is available
|
|
484
504
|
let scopeFiltered = semanticResults;
|
|
485
505
|
if (primaryScope) {
|
|
@@ -509,13 +529,15 @@ async function saveMemoryInternal(input, options) {
|
|
|
509
529
|
// Semantic search unavailable — proceed with empty candidates
|
|
510
530
|
}
|
|
511
531
|
}
|
|
512
|
-
const evolution =
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
532
|
+
const evolution = shouldApplyEvolution
|
|
533
|
+
? (0, evolution_engine_js_1.resolveMemoryEvolution)({
|
|
534
|
+
incoming: { topic: input.topic, summary: input.summary, kind: input.kind },
|
|
535
|
+
existing: existingCandidates.map((c) => ({
|
|
536
|
+
...c,
|
|
537
|
+
kind: (c.kind || 'fact'),
|
|
538
|
+
})),
|
|
539
|
+
})
|
|
540
|
+
: { edges: [] };
|
|
519
541
|
const supersedesTarget = evolution.edges.find((edge) => edge.type === 'supersedes')?.to_id ?? null;
|
|
520
542
|
const entityObservationIds = Array.from(new Set(input.entityObservationIds ?? []));
|
|
521
543
|
const eventDateTime = typeof input.eventDateTime === 'number'
|
|
@@ -556,7 +578,7 @@ async function saveMemoryInternal(input, options) {
|
|
|
556
578
|
SET kind = ?, status = ?, summary = ?, is_static = ?, trust_context = ?, updated_at = ?
|
|
557
579
|
WHERE id = ?
|
|
558
580
|
`)
|
|
559
|
-
.run(input.kind,
|
|
581
|
+
.run(input.kind, targetStatus, input.summary, input.kind === 'preference' || input.kind === 'constraint' ? 1 : 0, JSON.stringify({ source: input.source }), now, id);
|
|
560
582
|
for (const { scopeId, isPrimary } of resolvedScopeIds) {
|
|
561
583
|
adapter
|
|
562
584
|
.prepare(`
|
|
@@ -609,22 +631,41 @@ async function saveMemoryInternal(input, options) {
|
|
|
609
631
|
rollbackError.memoryId = id;
|
|
610
632
|
throw rollbackError;
|
|
611
633
|
}
|
|
612
|
-
//
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
634
|
+
// Sync the adapter's status cache for every row whose status changed in the
|
|
635
|
+
// post-insert transaction: the saved row itself (status is written AFTER
|
|
636
|
+
// insertEmbedding populated the cache) and any rows just superseded. This makes
|
|
637
|
+
// the vectorSearch pre-filter correct within this session, not only after reload.
|
|
638
|
+
if (adapter.refreshDecisionStatusCache) {
|
|
639
|
+
const changedIds = [
|
|
640
|
+
id,
|
|
641
|
+
...evolution.edges.filter((e) => e.type === 'supersedes').map((e) => e.to_id),
|
|
642
|
+
];
|
|
643
|
+
const ridStmt = adapter.prepare('SELECT rowid FROM decisions WHERE id = ?');
|
|
644
|
+
for (const changedId of changedIds) {
|
|
645
|
+
const ridRow = ridStmt.get(changedId);
|
|
646
|
+
if (ridRow) {
|
|
647
|
+
adapter.refreshDecisionStatusCache(ridRow.rowid);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
625
650
|
}
|
|
626
|
-
|
|
627
|
-
//
|
|
651
|
+
if (options?.projectTruth !== false) {
|
|
652
|
+
// Project to memory_truth table (best-effort; failure should not break save)
|
|
653
|
+
try {
|
|
654
|
+
await (0, truth_store_js_1.projectMemoryTruth)({
|
|
655
|
+
memory_id: id,
|
|
656
|
+
topic: input.topic,
|
|
657
|
+
truth_status: targetStatus,
|
|
658
|
+
effective_summary: input.summary,
|
|
659
|
+
effective_details: input.details,
|
|
660
|
+
trust_score: input.confidence ?? 0.5,
|
|
661
|
+
scope_refs: input.scopes,
|
|
662
|
+
supporting_event_ids: [],
|
|
663
|
+
superseded_by: undefined,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
catch {
|
|
667
|
+
// Truth projection is best-effort; do not fail the save
|
|
668
|
+
}
|
|
628
669
|
}
|
|
629
670
|
return {
|
|
630
671
|
success: true,
|
|
@@ -640,6 +681,156 @@ async function saveMemory(input) {
|
|
|
640
681
|
async function saveMemoryWithTrustedProvenance(input, options) {
|
|
641
682
|
return saveMemoryInternal((0, provenance_js_1.sanitizePublicSaveMemoryInput)(input), options);
|
|
642
683
|
}
|
|
684
|
+
async function promoteMemoryStatus(input) {
|
|
685
|
+
await (0, db_manager_js_1.initDB)();
|
|
686
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
687
|
+
const memoryId = input.memoryId;
|
|
688
|
+
const now = input.nowMs ?? Date.now();
|
|
689
|
+
const targetStatus = input.status;
|
|
690
|
+
const row = adapter
|
|
691
|
+
.prepare(`
|
|
692
|
+
SELECT id, topic, decision, reasoning, confidence, kind, summary, supersedes
|
|
693
|
+
FROM decisions
|
|
694
|
+
WHERE id = ?
|
|
695
|
+
`)
|
|
696
|
+
.get(memoryId);
|
|
697
|
+
if (!row) {
|
|
698
|
+
throw new Error(`Cannot promote missing memory ${memoryId}`);
|
|
699
|
+
}
|
|
700
|
+
const topic = String(row.topic);
|
|
701
|
+
const summary = String(row.summary ?? row.decision ?? '');
|
|
702
|
+
const details = String(row.reasoning ?? row.decision ?? '');
|
|
703
|
+
const kind = (row.kind ?? 'fact') || 'fact';
|
|
704
|
+
const scopes = batchLoadScopes(adapter, [memoryId]).get(memoryId) ?? [];
|
|
705
|
+
let evolution = { edges: [] };
|
|
706
|
+
if (targetStatus === 'active') {
|
|
707
|
+
const primaryScope = scopes[0] ?? null;
|
|
708
|
+
let existingCandidates;
|
|
709
|
+
if (primaryScope) {
|
|
710
|
+
const scopeId = await (0, db_manager_js_1.ensureMemoryScope)(primaryScope.kind, primaryScope.id);
|
|
711
|
+
existingCandidates = adapter
|
|
712
|
+
.prepare(`
|
|
713
|
+
SELECT d.id, d.topic, d.summary, d.kind
|
|
714
|
+
FROM decisions d
|
|
715
|
+
JOIN memory_scope_bindings msb ON msb.memory_id = d.id
|
|
716
|
+
WHERE d.topic = ? AND msb.scope_id = ? AND d.id <> ?
|
|
717
|
+
AND (d.status = 'active' OR d.status IS NULL)
|
|
718
|
+
AND d.superseded_by IS NULL
|
|
719
|
+
ORDER BY d.created_at DESC
|
|
720
|
+
LIMIT 5
|
|
721
|
+
`)
|
|
722
|
+
.all(topic, scopeId, memoryId);
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
existingCandidates = adapter
|
|
726
|
+
.prepare(`
|
|
727
|
+
SELECT id, topic, summary, kind
|
|
728
|
+
FROM decisions
|
|
729
|
+
WHERE topic = ? AND id <> ?
|
|
730
|
+
AND (status = 'active' OR status IS NULL)
|
|
731
|
+
AND superseded_by IS NULL
|
|
732
|
+
ORDER BY created_at DESC
|
|
733
|
+
LIMIT 5
|
|
734
|
+
`)
|
|
735
|
+
.all(topic, memoryId);
|
|
736
|
+
}
|
|
737
|
+
if (existingCandidates.length === 0) {
|
|
738
|
+
try {
|
|
739
|
+
const queryText = `${topic} ${summary}`;
|
|
740
|
+
const embedding = await (0, embeddings_js_1.generateEmbedding)(queryText, 'query');
|
|
741
|
+
// Same exclusion as saveMemoryInternal's fallback: superseded history must
|
|
742
|
+
// not crowd out the prior ACTIVE decision from the 3 candidate slots.
|
|
743
|
+
const semanticResults = await (0, db_manager_js_1.vectorSearch)(embedding, 3, 0.82, undefined, Array.from(EXCLUDED_STATUSES));
|
|
744
|
+
let scopeFiltered = semanticResults;
|
|
745
|
+
if (primaryScope) {
|
|
746
|
+
const semIds = semanticResults.map((result) => String(result.id));
|
|
747
|
+
const semScopeMap = batchLoadScopes(adapter, semIds);
|
|
748
|
+
const scopeKey = `${primaryScope.kind}:${primaryScope.id}`;
|
|
749
|
+
scopeFiltered = semanticResults.filter((result) => {
|
|
750
|
+
const resultScopes = semScopeMap.get(String(result.id)) ?? [];
|
|
751
|
+
return (resultScopes.length === 0 ||
|
|
752
|
+
resultScopes.some((scope) => `${scope.kind}:${scope.id}` === scopeKey));
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
existingCandidates = scopeFiltered
|
|
756
|
+
.filter((result) => String(result.id) !== memoryId)
|
|
757
|
+
.filter((result) => {
|
|
758
|
+
const status = String(result.status || '');
|
|
759
|
+
return !status || status === 'active' || status === '';
|
|
760
|
+
})
|
|
761
|
+
.map((result) => ({
|
|
762
|
+
id: String(result.id),
|
|
763
|
+
topic: String(result.topic || ''),
|
|
764
|
+
summary: String(result.decision || ''),
|
|
765
|
+
kind: 'fact',
|
|
766
|
+
_semanticMatch: true,
|
|
767
|
+
}));
|
|
768
|
+
}
|
|
769
|
+
catch {
|
|
770
|
+
// Semantic search unavailable — proceed with exact-match candidates only.
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
evolution = (0, evolution_engine_js_1.resolveMemoryEvolution)({
|
|
774
|
+
incoming: { topic, summary, kind },
|
|
775
|
+
existing: existingCandidates.map((candidate) => ({
|
|
776
|
+
...candidate,
|
|
777
|
+
kind: (candidate.kind || 'fact'),
|
|
778
|
+
})),
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
const existingSupersedesTarget = typeof row.supersedes === 'string' && row.supersedes.length > 0 ? row.supersedes : null;
|
|
782
|
+
const supersedesTarget = evolution.edges.find((edge) => edge.type === 'supersedes')?.to_id ??
|
|
783
|
+
(targetStatus === 'active' ? existingSupersedesTarget : null);
|
|
784
|
+
adapter.transaction(() => {
|
|
785
|
+
const updated = adapter
|
|
786
|
+
.prepare('UPDATE decisions SET status = ?, supersedes = ?, updated_at = ? WHERE id = ?')
|
|
787
|
+
.run(targetStatus, supersedesTarget, now, memoryId);
|
|
788
|
+
if (updated.changes !== 1) {
|
|
789
|
+
throw new Error(`Cannot promote missing memory ${memoryId}`);
|
|
790
|
+
}
|
|
791
|
+
adapter
|
|
792
|
+
.prepare(`
|
|
793
|
+
INSERT OR REPLACE INTO memory_truth (
|
|
794
|
+
memory_id, topic, truth_status, effective_summary, effective_details, trust_score,
|
|
795
|
+
scope_refs, supporting_event_ids, superseded_by, contradicted_by, created_at, updated_at
|
|
796
|
+
)
|
|
797
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, COALESCE((SELECT supporting_event_ids FROM memory_truth WHERE memory_id = ?), '[]'), NULL, NULL, COALESCE((SELECT created_at FROM memory_truth WHERE memory_id = ?), ?), ?)
|
|
798
|
+
`)
|
|
799
|
+
.run(memoryId, topic, targetStatus, summary, details, row.confidence ?? 0.5, JSON.stringify(scopes), memoryId, memoryId, now, now);
|
|
800
|
+
for (const edge of evolution.edges) {
|
|
801
|
+
if (edge.type === 'supersedes') {
|
|
802
|
+
adapter
|
|
803
|
+
.prepare(`UPDATE decisions SET superseded_by = ?, status = 'superseded', updated_at = ? WHERE id = ?`)
|
|
804
|
+
.run(memoryId, now, edge.to_id);
|
|
805
|
+
adapter
|
|
806
|
+
.prepare(`UPDATE memory_truth SET truth_status = 'superseded', superseded_by = ?, updated_at = ? WHERE memory_id = ?`)
|
|
807
|
+
.run(memoryId, now, edge.to_id);
|
|
808
|
+
}
|
|
809
|
+
adapter
|
|
810
|
+
.prepare(`
|
|
811
|
+
INSERT OR REPLACE INTO decision_edges (from_id, to_id, relationship, reason, weight, created_at)
|
|
812
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
813
|
+
`)
|
|
814
|
+
.run(memoryId, edge.to_id, edge.type, edge.reason ?? null, 1.0, now);
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
// Sync the adapter's status cache for every row whose status changed above.
|
|
818
|
+
// Without this, a row promoted OUT of an excluded status (e.g. stale->active)
|
|
819
|
+
// stays invisible to the vectorSearch pre-filter until the next full reload.
|
|
820
|
+
if (adapter.refreshDecisionStatusCache) {
|
|
821
|
+
const changedIds = [
|
|
822
|
+
memoryId,
|
|
823
|
+
...evolution.edges.filter((e) => e.type === 'supersedes').map((e) => e.to_id),
|
|
824
|
+
];
|
|
825
|
+
const ridStmt = adapter.prepare('SELECT rowid FROM decisions WHERE id = ?');
|
|
826
|
+
for (const id of changedIds) {
|
|
827
|
+
const ridRow = ridStmt.get(id);
|
|
828
|
+
if (ridRow) {
|
|
829
|
+
adapter.refreshDecisionStatusCache(ridRow.rowid);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
643
834
|
async function buildProfile(scopes) {
|
|
644
835
|
const records = await loadScopedMemories(scopes);
|
|
645
836
|
return (0, profile_builder_js_1.classifyProfileEntries)(records);
|
|
@@ -704,11 +895,14 @@ async function recallMemory(query, options = {}) {
|
|
|
704
895
|
let primaryQueryEmbedding = null;
|
|
705
896
|
try {
|
|
706
897
|
for (const sq of subQueries) {
|
|
707
|
-
const queryEmbedding = await (0, embeddings_js_1.generateEmbedding)(sq);
|
|
898
|
+
const queryEmbedding = await (0, embeddings_js_1.generateEmbedding)(sq, 'query');
|
|
708
899
|
if (sq === query && primaryQueryEmbedding === null) {
|
|
709
900
|
primaryQueryEmbedding = queryEmbedding;
|
|
710
901
|
}
|
|
711
|
-
const vectorResults = await (0, db_manager_js_1.vectorSearch)(queryEmbedding, vectorLimit, searchOptions.threshold, searchOptions.topicPrefix
|
|
902
|
+
const vectorResults = await (0, db_manager_js_1.vectorSearch)(queryEmbedding, vectorLimit, searchOptions.threshold, searchOptions.topicPrefix,
|
|
903
|
+
// Keep superseded history out of the candidate top-K at search time; the
|
|
904
|
+
// post-filter below stays the authority (and includeHistory restores it).
|
|
905
|
+
options.includeHistory ? undefined : Array.from(EXCLUDED_STATUSES));
|
|
712
906
|
let filtered = vectorResults;
|
|
713
907
|
let vectorScopeMap = new Map();
|
|
714
908
|
if (options.scopes && options.scopes.length > 0) {
|
|
@@ -1318,6 +1512,17 @@ async function recallMemory(query, options = {}) {
|
|
|
1318
1512
|
];
|
|
1319
1513
|
});
|
|
1320
1514
|
fusedHits = fusedHits.filter((hit) => hit.source_type !== 'decision' || acceptedPrimaryIds.has(hit.source_id));
|
|
1515
|
+
// Honor options.limit on the final memories (matched is RRF-rank-sorted, canonical
|
|
1516
|
+
// dual-write appends last). Without this cap the full fusion set (hundreds of records)
|
|
1517
|
+
// flowed into bundle.memories AND the per-record enrichment SQL loops below.
|
|
1518
|
+
if (matched.length > requestedLimit) {
|
|
1519
|
+
matched = matched.slice(0, requestedLimit);
|
|
1520
|
+
// Keep bundle.fused_hits consistent with the capped memories: drop decision hits
|
|
1521
|
+
// whose record no longer appears in `matched` (wiki hits are added below and are
|
|
1522
|
+
// not keyed to memories).
|
|
1523
|
+
const cappedIds = new Set(matched.map((record) => record.id));
|
|
1524
|
+
fusedHits = fusedHits.filter((hit) => hit.source_type !== 'decision' || cappedIds.has(hit.source_id));
|
|
1525
|
+
}
|
|
1321
1526
|
const acceptedWikiFusedHits = Array.from(wikiScores.values())
|
|
1322
1527
|
.sort((a, b) => b.score - a.score)
|
|
1323
1528
|
.flatMap((entry) => {
|
|
@@ -5,6 +5,7 @@ export interface TrustedProvenanceCapability {
|
|
|
5
5
|
export interface TrustedMemoryWriteOptions {
|
|
6
6
|
provenance: MemoryWriteProvenance;
|
|
7
7
|
capability: TrustedProvenanceCapability;
|
|
8
|
+
projectTruth?: boolean;
|
|
8
9
|
}
|
|
9
10
|
export interface NormalizedMemoryProvenance {
|
|
10
11
|
actor: MemoryEventRecord['actor'];
|
|
@@ -92,6 +92,7 @@ function appendProvenanceSourceRefs(options, refs) {
|
|
|
92
92
|
assertTrustedProvenanceCapability(options.capability);
|
|
93
93
|
return {
|
|
94
94
|
capability: options.capability,
|
|
95
|
+
projectTruth: options.projectTruth,
|
|
95
96
|
provenance: {
|
|
96
97
|
...options.provenance,
|
|
97
98
|
source_refs: [...(options.provenance.source_refs ?? []), ...refs],
|
package/dist/memory-inject.js
CHANGED
|
@@ -77,7 +77,7 @@ async function injectDecisionContext(userMessage) {
|
|
|
77
77
|
async function performMemoryInjection(userMessage, startTime) {
|
|
78
78
|
// 1. Generate query embedding
|
|
79
79
|
const { generateEmbedding } = await import('./embeddings.js');
|
|
80
|
-
const queryEmbedding = await generateEmbedding(userMessage);
|
|
80
|
+
const queryEmbedding = await generateEmbedding(userMessage, 'query');
|
|
81
81
|
const embeddingLatency = Date.now() - startTime;
|
|
82
82
|
(0, debug_logger_js_1.info)(`[MAMA] Embedding generation: ${embeddingLatency}ms`);
|
|
83
83
|
// 2. Adaptive threshold (shorter queries need higher confidence)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ContextRef } from '../context-compile/types.js';
|
|
2
|
+
export declare const STRICT_SOURCE_REF_KINDS: readonly ["memory", "raw", "entity", "case", "decision", "os_task", "agent_situation_packet", "report_slot", "context_packet", "model_run", "tool_trace", "wiki_page", "report"];
|
|
3
|
+
export type StrictSourceRefKind = (typeof STRICT_SOURCE_REF_KINDS)[number];
|
|
4
|
+
export declare const LEGACY_SOURCE_REF_KINDS: readonly ["message", "conversation", "raw_memory"];
|
|
5
|
+
export type LegacySourceRefKind = (typeof LEGACY_SOURCE_REF_KINDS)[number];
|
|
6
|
+
export type SourceRef = {
|
|
7
|
+
kind: Exclude<StrictSourceRefKind, 'raw'>;
|
|
8
|
+
id: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'raw';
|
|
11
|
+
id: string;
|
|
12
|
+
connector: string;
|
|
13
|
+
source_id?: string;
|
|
14
|
+
channel_id?: string | null;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'legacy';
|
|
17
|
+
legacy_kind: LegacySourceRefKind;
|
|
18
|
+
id: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function fromContextRef(ref: ContextRef): SourceRef;
|
|
21
|
+
export declare function toContextRef(ref: SourceRef): ContextRef | null;
|
|
22
|
+
export declare function fromVerifyArtifact(artifact: {
|
|
23
|
+
type: string;
|
|
24
|
+
id: string;
|
|
25
|
+
}): SourceRef;
|
|
26
|
+
export declare function parseSourceRefString(value: string): SourceRef;
|
|
27
|
+
export declare function serializeSourceRef(ref: SourceRef): string;
|
|
28
|
+
export declare function assertNonEmptySourceRefs(refs: readonly SourceRef[]): void;
|
|
29
|
+
//# sourceMappingURL=source-ref.d.ts.map
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LEGACY_SOURCE_REF_KINDS = exports.STRICT_SOURCE_REF_KINDS = void 0;
|
|
4
|
+
exports.fromContextRef = fromContextRef;
|
|
5
|
+
exports.toContextRef = toContextRef;
|
|
6
|
+
exports.fromVerifyArtifact = fromVerifyArtifact;
|
|
7
|
+
exports.parseSourceRefString = parseSourceRefString;
|
|
8
|
+
exports.serializeSourceRef = serializeSourceRef;
|
|
9
|
+
exports.assertNonEmptySourceRefs = assertNonEmptySourceRefs;
|
|
10
|
+
const types_js_1 = require("../context-compile/types.js");
|
|
11
|
+
exports.STRICT_SOURCE_REF_KINDS = [
|
|
12
|
+
'memory',
|
|
13
|
+
'raw',
|
|
14
|
+
'entity',
|
|
15
|
+
'case',
|
|
16
|
+
'decision',
|
|
17
|
+
'os_task',
|
|
18
|
+
'agent_situation_packet',
|
|
19
|
+
'report_slot',
|
|
20
|
+
'context_packet',
|
|
21
|
+
'model_run',
|
|
22
|
+
'tool_trace',
|
|
23
|
+
'wiki_page',
|
|
24
|
+
'report',
|
|
25
|
+
];
|
|
26
|
+
exports.LEGACY_SOURCE_REF_KINDS = ['message', 'conversation', 'raw_memory'];
|
|
27
|
+
const STRICT_SOURCE_REF_KIND_SET = new Set(exports.STRICT_SOURCE_REF_KINDS);
|
|
28
|
+
const CONTEXT_REF_KIND_SET = new Set(types_js_1.CONTEXT_REF_KINDS);
|
|
29
|
+
const LEGACY_SOURCE_REF_KIND_SET = new Set(exports.LEGACY_SOURCE_REF_KINDS);
|
|
30
|
+
const VERIFY_ARTIFACT_KIND_SET = new Set([
|
|
31
|
+
'decision',
|
|
32
|
+
'os_task',
|
|
33
|
+
'agent_situation_packet',
|
|
34
|
+
'report_slot',
|
|
35
|
+
'context_packet',
|
|
36
|
+
'model_run',
|
|
37
|
+
'tool_trace',
|
|
38
|
+
]);
|
|
39
|
+
function requiredString(value, field) {
|
|
40
|
+
if (typeof value !== 'string') {
|
|
41
|
+
throw new Error(`Source ref ${field} must be a string`);
|
|
42
|
+
}
|
|
43
|
+
const trimmed = value.trim();
|
|
44
|
+
if (trimmed.length === 0) {
|
|
45
|
+
throw new Error(`Source ref ${field} must not be empty`);
|
|
46
|
+
}
|
|
47
|
+
return trimmed;
|
|
48
|
+
}
|
|
49
|
+
function optionalString(value, field) {
|
|
50
|
+
if (value === undefined) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return requiredString(value, field);
|
|
54
|
+
}
|
|
55
|
+
function optionalNullableString(value, field) {
|
|
56
|
+
if (value === null) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return optionalString(value, field);
|
|
60
|
+
}
|
|
61
|
+
function assertStrictSourceRefKind(kind) {
|
|
62
|
+
if (!STRICT_SOURCE_REF_KIND_SET.has(kind)) {
|
|
63
|
+
throw new Error(`Unsupported source ref kind: ${kind}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function assertLegacySourceRefKind(kind) {
|
|
67
|
+
if (!LEGACY_SOURCE_REF_KIND_SET.has(kind)) {
|
|
68
|
+
throw new Error(`Unsupported legacy source ref kind: ${kind}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function assertContextSourceRefKind(kind) {
|
|
72
|
+
if (kind === 'raw' || !CONTEXT_REF_KIND_SET.has(kind)) {
|
|
73
|
+
throw new Error(`Unsupported context source ref kind: ${kind}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function fromContextRef(ref) {
|
|
77
|
+
if (ref === null || ref === undefined) {
|
|
78
|
+
throw new Error('Context ref must not be null or undefined');
|
|
79
|
+
}
|
|
80
|
+
const input = ref;
|
|
81
|
+
const kind = requiredString(input.kind, 'kind');
|
|
82
|
+
if (kind === 'raw') {
|
|
83
|
+
const normalized = {
|
|
84
|
+
kind: 'raw',
|
|
85
|
+
id: requiredString(input.raw_id, 'raw_id'),
|
|
86
|
+
connector: requiredString(input.connector, 'connector'),
|
|
87
|
+
};
|
|
88
|
+
const sourceId = optionalString(input.source_id, 'source_id');
|
|
89
|
+
const channelId = optionalNullableString(input.channel_id, 'channel_id');
|
|
90
|
+
if (sourceId !== undefined) {
|
|
91
|
+
normalized.source_id = sourceId;
|
|
92
|
+
}
|
|
93
|
+
if (channelId !== undefined) {
|
|
94
|
+
normalized.channel_id = channelId;
|
|
95
|
+
}
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
assertContextSourceRefKind(kind);
|
|
99
|
+
return {
|
|
100
|
+
kind,
|
|
101
|
+
id: requiredString(input.id, 'id'),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function toContextRef(ref) {
|
|
105
|
+
if (ref === null || ref === undefined) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
switch (ref.kind) {
|
|
109
|
+
case 'memory':
|
|
110
|
+
case 'entity':
|
|
111
|
+
case 'case':
|
|
112
|
+
return {
|
|
113
|
+
kind: ref.kind,
|
|
114
|
+
id: requiredString(ref.id, 'id'),
|
|
115
|
+
};
|
|
116
|
+
case 'raw': {
|
|
117
|
+
const normalized = {
|
|
118
|
+
kind: 'raw',
|
|
119
|
+
raw_id: requiredString(ref.id, 'id'),
|
|
120
|
+
connector: requiredString(ref.connector, 'connector'),
|
|
121
|
+
};
|
|
122
|
+
const sourceId = optionalString(ref.source_id, 'source_id');
|
|
123
|
+
const channelId = optionalNullableString(ref.channel_id, 'channel_id');
|
|
124
|
+
if (sourceId !== undefined) {
|
|
125
|
+
normalized.source_id = sourceId;
|
|
126
|
+
}
|
|
127
|
+
if (channelId !== undefined) {
|
|
128
|
+
normalized.channel_id = channelId;
|
|
129
|
+
}
|
|
130
|
+
return normalized;
|
|
131
|
+
}
|
|
132
|
+
default:
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function fromVerifyArtifact(artifact) {
|
|
137
|
+
if (artifact === null || artifact === undefined) {
|
|
138
|
+
throw new Error('Artifact must not be null or undefined');
|
|
139
|
+
}
|
|
140
|
+
const kind = requiredString(artifact.type, 'type');
|
|
141
|
+
if (!VERIFY_ARTIFACT_KIND_SET.has(kind)) {
|
|
142
|
+
throw new Error(`Unsupported verify artifact source ref kind: ${kind}`);
|
|
143
|
+
}
|
|
144
|
+
assertStrictSourceRefKind(kind);
|
|
145
|
+
if (kind === 'raw') {
|
|
146
|
+
throw new Error('Verify artifact source refs cannot use raw kind');
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
kind,
|
|
150
|
+
id: requiredString(artifact.id, 'id'),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function parseSourceRefString(value) {
|
|
154
|
+
const rawValue = requiredString(value, 'value');
|
|
155
|
+
const parts = rawValue.split(':');
|
|
156
|
+
const kind = requiredString(parts[0], 'kind');
|
|
157
|
+
if (kind === 'raw') {
|
|
158
|
+
const connector = requiredString(parts[1], 'connector');
|
|
159
|
+
const id = requiredString(parts.slice(2).join(':'), 'id');
|
|
160
|
+
return { kind: 'raw', connector, id };
|
|
161
|
+
}
|
|
162
|
+
if (LEGACY_SOURCE_REF_KIND_SET.has(kind)) {
|
|
163
|
+
assertLegacySourceRefKind(kind);
|
|
164
|
+
return {
|
|
165
|
+
kind: 'legacy',
|
|
166
|
+
legacy_kind: kind,
|
|
167
|
+
id: requiredString(parts.slice(1).join(':'), 'id'),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
assertStrictSourceRefKind(kind);
|
|
171
|
+
if (kind === 'raw') {
|
|
172
|
+
throw new Error('Raw source refs must include connector and id');
|
|
173
|
+
}
|
|
174
|
+
const sourceKind = kind;
|
|
175
|
+
return {
|
|
176
|
+
kind: sourceKind,
|
|
177
|
+
id: requiredString(parts.slice(1).join(':'), 'id'),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function serializeSourceRef(ref) {
|
|
181
|
+
if (ref === null || ref === undefined) {
|
|
182
|
+
throw new Error('Source ref must not be null or undefined');
|
|
183
|
+
}
|
|
184
|
+
switch (ref.kind) {
|
|
185
|
+
case 'raw':
|
|
186
|
+
return `raw:${requiredString(ref.connector, 'connector')}:${requiredString(ref.id, 'id')}`;
|
|
187
|
+
case 'legacy':
|
|
188
|
+
assertLegacySourceRefKind(ref.legacy_kind);
|
|
189
|
+
return `${ref.legacy_kind}:${requiredString(ref.id, 'id')}`;
|
|
190
|
+
default: {
|
|
191
|
+
assertStrictSourceRefKind(ref.kind);
|
|
192
|
+
return `${ref.kind}:${requiredString(ref.id, 'id')}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function assertNonEmptySourceRefs(refs) {
|
|
197
|
+
if (!Array.isArray(refs) || refs.length === 0) {
|
|
198
|
+
throw new Error('Source refs must not be empty');
|
|
199
|
+
}
|
|
200
|
+
for (const ref of refs) {
|
|
201
|
+
serializeSourceRef(ref);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=source-ref.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungjaehoon/mama-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "MAMA Core - Shared modules for Memory-Augmented MCP Assistant",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"./agent-situation": "./dist/agent-situation/index.js",
|
|
51
51
|
"./agent-graph": "./dist/agent-graph/index.js",
|
|
52
52
|
"./context-compile": "./dist/context-compile/index.js",
|
|
53
|
+
"./provenance/source-ref": "./dist/provenance/source-ref.js",
|
|
53
54
|
"./edges/types": "./dist/edges/types.js",
|
|
54
55
|
"./edges/store": "./dist/edges/store.js",
|
|
55
56
|
"./edges/ref-validation": "./dist/edges/ref-validation.js"
|