@remnic/core 9.6.29 → 9.6.30
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/access-admin-ops-surface.js +2 -1
- package/dist/access-boundary.js +3 -2
- package/dist/access-cli.js +16 -15
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.js +7 -6
- package/dist/access-identity-continuity-surface.js +2 -1
- package/dist/access-lcm-surface.js +2 -1
- package/dist/access-mcp.js +6 -5
- package/dist/access-observe-write-surface.js +2 -1
- package/dist/access-operations-batch.js +4 -3
- package/dist/access-operations.js +5 -4
- package/dist/access-recall-surface.js +2 -1
- package/dist/access-service.js +2 -1
- package/dist/{chunk-5WJ4LBFR.js → chunk-3VSTTB74.js} +7 -7
- package/dist/{chunk-4463LUIE.js → chunk-77UPM7P3.js} +22 -11
- package/dist/chunk-77UPM7P3.js.map +1 -0
- package/dist/{chunk-FV5FVO2J.js → chunk-7O2JL5RY.js} +9 -5
- package/dist/chunk-7O2JL5RY.js.map +1 -0
- package/dist/{chunk-LZTFCAKE.js → chunk-BFU7H32D.js} +28 -15
- package/dist/chunk-BFU7H32D.js.map +1 -0
- package/dist/chunk-C37ZRXX7.js +36 -0
- package/dist/chunk-C37ZRXX7.js.map +1 -0
- package/dist/{chunk-SGYTQKC2.js → chunk-C6TKSI35.js} +3 -3
- package/dist/{chunk-C2SEH754.js → chunk-DAAJUA6Q.js} +4 -4
- package/dist/{chunk-EAREADWV.js → chunk-GQURC4B7.js} +2 -2
- package/dist/{chunk-4SBW47WK.js → chunk-IRMLYBMU.js} +28 -15
- package/dist/chunk-IRMLYBMU.js.map +1 -0
- package/dist/{chunk-XA4PWKBW.js → chunk-MXAGJHTA.js} +3 -3
- package/dist/{chunk-5BHIGBHQ.js → chunk-OXVKR5OU.js} +6 -3
- package/dist/{chunk-5BHIGBHQ.js.map → chunk-OXVKR5OU.js.map} +1 -1
- package/dist/{chunk-IF7THCGJ.js → chunk-PUQZIS6A.js} +3 -3
- package/dist/{chunk-PYPOFEMK.js → chunk-R26WO46Z.js} +1 -1
- package/dist/chunk-R26WO46Z.js.map +1 -0
- package/dist/chunk-VDC6RZI4.js +1 -0
- package/dist/{chunk-3RROR5KV.js → chunk-VJRJTRYF.js} +4 -4
- package/dist/{chunk-L6W77GWW.js → chunk-XGSOXFD4.js} +25 -13
- package/dist/chunk-XGSOXFD4.js.map +1 -0
- package/dist/cli.js +8 -7
- package/dist/event-order-recall.js +3 -2
- package/dist/evidence-pack.d.ts +2 -0
- package/dist/evidence-pack.js +1 -1
- package/dist/explicit-cue-recall.d.ts +3 -0
- package/dist/explicit-cue-recall.js +3 -2
- package/dist/focused-list-recall.js +3 -2
- package/dist/index.js +16 -15
- package/dist/lcm/index.d.ts +22 -0
- package/dist/lcm/index.js +9 -1
- package/dist/orchestrator.js +16 -15
- package/dist/response-guidance-recall.js +3 -2
- package/dist/targeted-fact-recall.js +3 -2
- package/package.json +2 -2
- package/src/access-lcm-surface.ts +20 -5
- package/src/access-recall-surface.ts +2 -1
- package/src/event-order-recall.ts +5 -3
- package/src/evidence-pack.ts +2 -0
- package/src/explicit-cue-recall.ts +67 -66
- package/src/focused-list-recall.ts +25 -16
- package/src/lcm/evidence-identity.ts +59 -0
- package/src/lcm/index.ts +7 -0
- package/src/lcm-retrieval-lineage.test.ts +236 -0
- package/src/response-guidance-recall.ts +22 -14
- package/src/targeted-fact-recall.ts +25 -16
- package/dist/chunk-4463LUIE.js.map +0 -1
- package/dist/chunk-4SBW47WK.js.map +0 -1
- package/dist/chunk-FV5FVO2J.js.map +0 -1
- package/dist/chunk-HHLLAQGZ.js +0 -1
- package/dist/chunk-L6W77GWW.js.map +0 -1
- package/dist/chunk-LZTFCAKE.js.map +0 -1
- package/dist/chunk-PYPOFEMK.js.map +0 -1
- /package/dist/{chunk-5WJ4LBFR.js.map → chunk-3VSTTB74.js.map} +0 -0
- /package/dist/{chunk-SGYTQKC2.js.map → chunk-C6TKSI35.js.map} +0 -0
- /package/dist/{chunk-C2SEH754.js.map → chunk-DAAJUA6Q.js.map} +0 -0
- /package/dist/{chunk-EAREADWV.js.map → chunk-GQURC4B7.js.map} +0 -0
- /package/dist/{chunk-XA4PWKBW.js.map → chunk-MXAGJHTA.js.map} +0 -0
- /package/dist/{chunk-IF7THCGJ.js.map → chunk-PUQZIS6A.js.map} +0 -0
- /package/dist/{chunk-HHLLAQGZ.js.map → chunk-VDC6RZI4.js.map} +0 -0
- /package/dist/{chunk-3RROR5KV.js.map → chunk-VJRJTRYF.js.map} +0 -0
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
resolveLcmReadSessionIds,
|
|
5
5
|
} from "./lcm-fallback-read.js";
|
|
6
6
|
import { unifiedDedupeAndRank } from "./recall-pipeline-stages.js";
|
|
7
|
+
import {
|
|
8
|
+
lcmArchiveRowId,
|
|
9
|
+
lcmEvidenceIdentity,
|
|
10
|
+
} from "./lcm/evidence-identity.js";
|
|
7
11
|
|
|
8
12
|
export interface ExplicitCueRecallEngine {
|
|
9
13
|
expandContext(
|
|
@@ -11,7 +15,15 @@ export interface ExplicitCueRecallEngine {
|
|
|
11
15
|
fromTurn: number,
|
|
12
16
|
toTurn: number,
|
|
13
17
|
maxTokens: number,
|
|
14
|
-
): Promise<
|
|
18
|
+
): Promise<
|
|
19
|
+
Array<{
|
|
20
|
+
id?: number;
|
|
21
|
+
session_id?: string;
|
|
22
|
+
turn_index: number;
|
|
23
|
+
role: string;
|
|
24
|
+
content: string;
|
|
25
|
+
}>
|
|
26
|
+
>;
|
|
15
27
|
getStats?(sessionId?: string): Promise<{
|
|
16
28
|
totalMessages: number;
|
|
17
29
|
maxTurnIndex?: number;
|
|
@@ -22,6 +34,7 @@ export interface ExplicitCueRecallEngine {
|
|
|
22
34
|
sessionId?: string,
|
|
23
35
|
): Promise<
|
|
24
36
|
Array<{
|
|
37
|
+
id?: number;
|
|
25
38
|
turn_index: number;
|
|
26
39
|
role: string;
|
|
27
40
|
content: string;
|
|
@@ -62,6 +75,16 @@ export type ExplicitTurnReference = {
|
|
|
62
75
|
includeDirectTurn: boolean;
|
|
63
76
|
};
|
|
64
77
|
|
|
78
|
+
interface ExplicitCueEvidenceItem {
|
|
79
|
+
id: string;
|
|
80
|
+
archiveRowId?: number;
|
|
81
|
+
sessionId: string;
|
|
82
|
+
turnIndex: number;
|
|
83
|
+
role: string;
|
|
84
|
+
content: string;
|
|
85
|
+
score?: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
65
88
|
const DEFAULT_MAX_CHARS = 2_400;
|
|
66
89
|
const DEFAULT_MAX_ITEM_CHARS = 1_200;
|
|
67
90
|
const DEFAULT_MAX_REFERENCES = 24;
|
|
@@ -326,23 +349,17 @@ export async function buildExplicitCueRecallSection(
|
|
|
326
349
|
return "";
|
|
327
350
|
}
|
|
328
351
|
|
|
329
|
-
const evidenceItems:
|
|
330
|
-
id: string;
|
|
331
|
-
sessionId: string;
|
|
332
|
-
turnIndex: number;
|
|
333
|
-
role: string;
|
|
334
|
-
content: string;
|
|
335
|
-
score?: number;
|
|
336
|
-
}> = [];
|
|
352
|
+
const evidenceItems: ExplicitCueEvidenceItem[] = [];
|
|
337
353
|
const seenTurns = new Set<string>();
|
|
338
354
|
|
|
339
355
|
// #1505 codex P2: gather cue evidence across the ordered LCM read key set
|
|
340
356
|
// (primary overlay → project/root fallbacks) into ONE shared accumulator
|
|
341
357
|
// (`evidenceItems` / `seenTurns`), so a branch-scoped session's project/root
|
|
342
358
|
// fallback cues are RECOVERED instead of being skipped by the old
|
|
343
|
-
// first-non-empty short-circuit. `seenTurns`
|
|
344
|
-
//
|
|
345
|
-
// `
|
|
359
|
+
// first-non-empty short-circuit. `seenTurns` is the historical local name;
|
|
360
|
+
// its keys use archive-row identity when available and fall back to
|
|
361
|
+
// `session_id`+`turn_index` for legacy engines. The budget is applied exactly
|
|
362
|
+
// once in the `buildEvidencePack` call below. `gatherAcrossReadSessions` isolates a
|
|
346
363
|
// per-key read failure (a corrupt/locked fallback index must not discard the
|
|
347
364
|
// other keys' cues); single-key recall runs each collector directly, so a
|
|
348
365
|
// failure propagates exactly as before.
|
|
@@ -510,13 +527,7 @@ async function collectTurnReferenceEvidence(options: {
|
|
|
510
527
|
sessionId?: string;
|
|
511
528
|
query: string;
|
|
512
529
|
maxReferences: number;
|
|
513
|
-
evidenceItems:
|
|
514
|
-
id: string;
|
|
515
|
-
sessionId: string;
|
|
516
|
-
turnIndex: number;
|
|
517
|
-
role: string;
|
|
518
|
-
content: string;
|
|
519
|
-
}>;
|
|
530
|
+
evidenceItems: ExplicitCueEvidenceItem[];
|
|
520
531
|
seenTurns: Set<string>;
|
|
521
532
|
}): Promise<void> {
|
|
522
533
|
if (!options.sessionId) {
|
|
@@ -575,13 +586,7 @@ async function collectFocusedTranscriptCueEvidence(options: {
|
|
|
575
586
|
engine: ExplicitCueRecallEngine;
|
|
576
587
|
sessionId?: string;
|
|
577
588
|
query: string;
|
|
578
|
-
evidenceItems:
|
|
579
|
-
id: string;
|
|
580
|
-
sessionId: string;
|
|
581
|
-
turnIndex: number;
|
|
582
|
-
role: string;
|
|
583
|
-
content: string;
|
|
584
|
-
}>;
|
|
589
|
+
evidenceItems: ExplicitCueEvidenceItem[];
|
|
585
590
|
seenTurns: Set<string>;
|
|
586
591
|
}): Promise<void> {
|
|
587
592
|
if (!options.sessionId || !options.engine.getStats) {
|
|
@@ -611,8 +616,8 @@ async function collectFocusedTranscriptCueEvidence(options: {
|
|
|
611
616
|
continue;
|
|
612
617
|
}
|
|
613
618
|
appendEvidenceItem(options.evidenceItems, options.seenTurns, {
|
|
614
|
-
|
|
615
|
-
sessionId: options.sessionId,
|
|
619
|
+
...lcmEvidenceIdentity(message, options.sessionId),
|
|
620
|
+
sessionId: message.session_id ?? options.sessionId,
|
|
616
621
|
turnIndex: message.turn_index,
|
|
617
622
|
role: message.role,
|
|
618
623
|
content: message.content,
|
|
@@ -772,13 +777,7 @@ async function collectContentLabelReferenceEvidence(options: {
|
|
|
772
777
|
sessionId?: string;
|
|
773
778
|
query: string;
|
|
774
779
|
references: ExplicitTurnReference[];
|
|
775
|
-
evidenceItems:
|
|
776
|
-
id: string;
|
|
777
|
-
sessionId: string;
|
|
778
|
-
turnIndex: number;
|
|
779
|
-
role: string;
|
|
780
|
-
content: string;
|
|
781
|
-
}>;
|
|
780
|
+
evidenceItems: ExplicitCueEvidenceItem[];
|
|
782
781
|
seenTurns: Set<string>;
|
|
783
782
|
}): Promise<Set<number>> {
|
|
784
783
|
const resolved = new Set<number>();
|
|
@@ -818,7 +817,7 @@ async function collectContentLabelReferenceEvidence(options: {
|
|
|
818
817
|
}
|
|
819
818
|
if (expanded.length === 0) {
|
|
820
819
|
appendEvidenceItem(options.evidenceItems, options.seenTurns, {
|
|
821
|
-
|
|
820
|
+
...lcmEvidenceIdentity(hit, hit.session_id),
|
|
822
821
|
sessionId: hit.session_id,
|
|
823
822
|
turnIndex: hit.turn_index,
|
|
824
823
|
role: hit.role,
|
|
@@ -845,6 +844,7 @@ async function searchReferenceContentLabels(
|
|
|
845
844
|
sessionId?: string,
|
|
846
845
|
): Promise<
|
|
847
846
|
Array<{
|
|
847
|
+
id?: number;
|
|
848
848
|
turn_index: number;
|
|
849
849
|
role: string;
|
|
850
850
|
content: string;
|
|
@@ -855,6 +855,7 @@ async function searchReferenceContentLabels(
|
|
|
855
855
|
const hits = new Map<
|
|
856
856
|
string,
|
|
857
857
|
{
|
|
858
|
+
id?: number;
|
|
858
859
|
turn_index: number;
|
|
859
860
|
role: string;
|
|
860
861
|
content: string;
|
|
@@ -878,7 +879,8 @@ async function searchReferenceContentLabels(
|
|
|
878
879
|
) {
|
|
879
880
|
continue;
|
|
880
881
|
}
|
|
881
|
-
hits.set(`${result.session_id
|
|
882
|
+
hits.set(`${lcmEvidenceIdentity(result, result.session_id).id}:${labelKind}`, {
|
|
883
|
+
...(typeof result.id === "number" ? { id: result.id } : {}),
|
|
882
884
|
turn_index: result.turn_index,
|
|
883
885
|
role: result.role,
|
|
884
886
|
content: result.content,
|
|
@@ -1054,14 +1056,7 @@ async function collectLexicalCueEvidence(options: {
|
|
|
1054
1056
|
includeBenchmarkAnchorCues?: boolean;
|
|
1055
1057
|
includeContentLexicalCues?: boolean;
|
|
1056
1058
|
includeStructuredPlanCues?: boolean;
|
|
1057
|
-
evidenceItems:
|
|
1058
|
-
id: string;
|
|
1059
|
-
sessionId: string;
|
|
1060
|
-
turnIndex: number;
|
|
1061
|
-
role: string;
|
|
1062
|
-
content: string;
|
|
1063
|
-
score?: number;
|
|
1064
|
-
}>;
|
|
1059
|
+
evidenceItems: ExplicitCueEvidenceItem[];
|
|
1065
1060
|
seenTurns: Set<string>;
|
|
1066
1061
|
}): Promise<void> {
|
|
1067
1062
|
const cues = prioritizeLexicalCueSearchCues(
|
|
@@ -1096,7 +1091,7 @@ async function collectLexicalCueEvidence(options: {
|
|
|
1096
1091
|
);
|
|
1097
1092
|
if (expanded.length === 0) {
|
|
1098
1093
|
appendEvidenceItem(options.evidenceItems, options.seenTurns, {
|
|
1099
|
-
|
|
1094
|
+
...lcmEvidenceIdentity(result, result.session_id),
|
|
1100
1095
|
sessionId: result.session_id,
|
|
1101
1096
|
turnIndex: result.turn_index,
|
|
1102
1097
|
role: result.role,
|
|
@@ -1107,7 +1102,7 @@ async function collectLexicalCueEvidence(options: {
|
|
|
1107
1102
|
}
|
|
1108
1103
|
if (exactHitFirst) {
|
|
1109
1104
|
appendEvidenceItem(options.evidenceItems, options.seenTurns, {
|
|
1110
|
-
|
|
1105
|
+
...lcmEvidenceIdentity(result, result.session_id),
|
|
1111
1106
|
sessionId: result.session_id,
|
|
1112
1107
|
turnIndex: result.turn_index,
|
|
1113
1108
|
role: result.role,
|
|
@@ -1131,6 +1126,8 @@ interface LabeledTrajectoryStep {
|
|
|
1131
1126
|
observation?: string;
|
|
1132
1127
|
actionTurnIndex?: number;
|
|
1133
1128
|
observationTurnIndex?: number;
|
|
1129
|
+
actionArchiveRowId?: number;
|
|
1130
|
+
observationArchiveRowId?: number;
|
|
1134
1131
|
}
|
|
1135
1132
|
|
|
1136
1133
|
interface TrajectoryBounds {
|
|
@@ -1187,7 +1184,12 @@ function hasTrajectoryAnalysisIntent(query: string): boolean {
|
|
|
1187
1184
|
}
|
|
1188
1185
|
|
|
1189
1186
|
function parseLabeledTrajectory(
|
|
1190
|
-
messages: Array<{
|
|
1187
|
+
messages: Array<{
|
|
1188
|
+
id?: number;
|
|
1189
|
+
turn_index: number;
|
|
1190
|
+
role: string;
|
|
1191
|
+
content: string;
|
|
1192
|
+
}>,
|
|
1191
1193
|
): LabeledTrajectoryStep[] {
|
|
1192
1194
|
const byStep = new Map<number, LabeledTrajectoryStep>();
|
|
1193
1195
|
|
|
@@ -1197,6 +1199,8 @@ function parseLabeledTrajectory(
|
|
|
1197
1199
|
const step = getOrCreateTrajectoryStep(byStep, action.step);
|
|
1198
1200
|
step.action = action.value;
|
|
1199
1201
|
step.actionTurnIndex = message.turn_index;
|
|
1202
|
+
const archiveRowId = lcmArchiveRowId(message);
|
|
1203
|
+
if (archiveRowId !== undefined) step.actionArchiveRowId = archiveRowId;
|
|
1200
1204
|
continue;
|
|
1201
1205
|
}
|
|
1202
1206
|
|
|
@@ -1205,6 +1209,10 @@ function parseLabeledTrajectory(
|
|
|
1205
1209
|
const step = getOrCreateTrajectoryStep(byStep, observation.step);
|
|
1206
1210
|
step.observation = observation.value;
|
|
1207
1211
|
step.observationTurnIndex = message.turn_index;
|
|
1212
|
+
const archiveRowId = lcmArchiveRowId(message);
|
|
1213
|
+
if (archiveRowId !== undefined) {
|
|
1214
|
+
step.observationArchiveRowId = archiveRowId;
|
|
1215
|
+
}
|
|
1208
1216
|
}
|
|
1209
1217
|
}
|
|
1210
1218
|
|
|
@@ -4538,21 +4546,21 @@ function normalizeTrajectoryQuery(value: string): string {
|
|
|
4538
4546
|
}
|
|
4539
4547
|
|
|
4540
4548
|
function appendExpandedEvidence(
|
|
4541
|
-
evidenceItems:
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4549
|
+
evidenceItems: ExplicitCueEvidenceItem[],
|
|
4550
|
+
seenTurns: Set<string>,
|
|
4551
|
+
sessionId: string,
|
|
4552
|
+
expanded: Array<{
|
|
4553
|
+
id?: number;
|
|
4554
|
+
session_id?: string;
|
|
4555
|
+
turn_index: number;
|
|
4545
4556
|
role: string;
|
|
4546
4557
|
content: string;
|
|
4547
4558
|
}>,
|
|
4548
|
-
seenTurns: Set<string>,
|
|
4549
|
-
sessionId: string,
|
|
4550
|
-
expanded: Array<{ turn_index: number; role: string; content: string }>,
|
|
4551
4559
|
): void {
|
|
4552
4560
|
for (const message of expanded) {
|
|
4553
4561
|
appendEvidenceItem(evidenceItems, seenTurns, {
|
|
4554
|
-
|
|
4555
|
-
sessionId,
|
|
4562
|
+
...lcmEvidenceIdentity(message, sessionId),
|
|
4563
|
+
sessionId: message.session_id ?? sessionId,
|
|
4556
4564
|
turnIndex: message.turn_index,
|
|
4557
4565
|
role: message.role,
|
|
4558
4566
|
content: message.content,
|
|
@@ -4689,14 +4697,7 @@ async function collectNamedMeetingFactEvidence(options: {
|
|
|
4689
4697
|
sessionId?: string;
|
|
4690
4698
|
query: string;
|
|
4691
4699
|
maxReferences: number;
|
|
4692
|
-
evidenceItems:
|
|
4693
|
-
id: string;
|
|
4694
|
-
sessionId: string;
|
|
4695
|
-
turnIndex: number;
|
|
4696
|
-
role: string;
|
|
4697
|
-
content: string;
|
|
4698
|
-
score?: number;
|
|
4699
|
-
}>;
|
|
4700
|
+
evidenceItems: ExplicitCueEvidenceItem[];
|
|
4700
4701
|
seenTurns: Set<string>;
|
|
4701
4702
|
}): Promise<void> {
|
|
4702
4703
|
const cues = collectMeetingFactCues(options.query)
|
|
@@ -4712,7 +4713,7 @@ async function collectNamedMeetingFactEvidence(options: {
|
|
|
4712
4713
|
for (const result of results) {
|
|
4713
4714
|
if (!isNamedMeetingFactEvidence(result.content, options.query)) continue;
|
|
4714
4715
|
appendEvidenceItem(options.evidenceItems, options.seenTurns, {
|
|
4715
|
-
|
|
4716
|
+
...lcmEvidenceIdentity(result, result.session_id),
|
|
4716
4717
|
sessionId: result.session_id,
|
|
4717
4718
|
turnIndex: result.turn_index,
|
|
4718
4719
|
role: result.role,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
gatherAcrossReadSessions,
|
|
9
9
|
resolveLcmReadSessionIds,
|
|
10
10
|
} from "./lcm-fallback-read.js";
|
|
11
|
+
import { isSameLcmRow, lcmEvidenceIdentity } from "./lcm/evidence-identity.js";
|
|
11
12
|
|
|
12
13
|
export interface FocusedListRecallOptions {
|
|
13
14
|
engine: ExplicitCueRecallEngine | null | undefined;
|
|
@@ -134,27 +135,35 @@ async function collectFocusedListItems(
|
|
|
134
135
|
result.turn_index + searchWindowAfter,
|
|
135
136
|
searchWindowTokens,
|
|
136
137
|
);
|
|
138
|
+
const searchIdentity = lcmEvidenceIdentity(result, result.session_id);
|
|
137
139
|
const searchHit: EvidencePackItem = {
|
|
138
|
-
|
|
140
|
+
...searchIdentity,
|
|
139
141
|
sessionId: result.session_id,
|
|
140
142
|
turnIndex: result.turn_index,
|
|
141
143
|
role: result.role,
|
|
142
144
|
content: result.content,
|
|
143
145
|
...(typeof result.score === "number" ? { score: result.score } : {}),
|
|
144
146
|
};
|
|
145
|
-
const candidates: EvidencePackItem[] = expanded.map((message) =>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
147
|
+
const candidates: EvidencePackItem[] = expanded.map((message) => {
|
|
148
|
+
const matchesSearchHit = isSameLcmRow(
|
|
149
|
+
message,
|
|
150
|
+
result.session_id,
|
|
151
|
+
result,
|
|
152
|
+
result.session_id,
|
|
153
|
+
);
|
|
154
|
+
return {
|
|
155
|
+
...lcmEvidenceIdentity(message, result.session_id),
|
|
156
|
+
sessionId: message.session_id ?? result.session_id,
|
|
157
|
+
turnIndex: message.turn_index,
|
|
158
|
+
role: message.role,
|
|
159
|
+
content: message.content,
|
|
160
|
+
...(matchesSearchHit && typeof result.score === "number"
|
|
161
|
+
? { score: result.score }
|
|
162
|
+
: {}),
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
const hitIndex = expanded.findIndex((message) =>
|
|
166
|
+
isSameLcmRow(message, result.session_id, result, result.session_id)
|
|
158
167
|
);
|
|
159
168
|
if (hitIndex >= 0) {
|
|
160
169
|
candidates[hitIndex] = searchHit;
|
|
@@ -221,8 +230,8 @@ async function collectFocusedListScanItems(
|
|
|
221
230
|
);
|
|
222
231
|
for (const message of messages) {
|
|
223
232
|
const candidate = {
|
|
224
|
-
|
|
225
|
-
sessionId: options.sessionId,
|
|
233
|
+
...lcmEvidenceIdentity(message, options.sessionId),
|
|
234
|
+
sessionId: message.session_id ?? options.sessionId,
|
|
226
235
|
turnIndex: message.turn_index,
|
|
227
236
|
role: message.role,
|
|
228
237
|
content: message.content,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface LcmRowIdentity {
|
|
2
|
+
id?: number;
|
|
3
|
+
session_id?: string;
|
|
4
|
+
turn_index: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface LcmEvidenceIdentity {
|
|
8
|
+
id: string;
|
|
9
|
+
archiveRowId?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function lcmArchiveRowId(row: Pick<LcmRowIdentity, "id">): number | undefined {
|
|
13
|
+
const value = row.id;
|
|
14
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0
|
|
15
|
+
? value
|
|
16
|
+
: undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveSessionId(row: LcmRowIdentity, fallbackSessionId: string): string {
|
|
20
|
+
return row.session_id ?? fallbackSessionId;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build the non-rendered evidence identity for an LCM archive row.
|
|
25
|
+
*
|
|
26
|
+
* Older adapters and test doubles may omit the SQLite row id. Those callers
|
|
27
|
+
* retain the historical session+turn identity until they adopt row lineage.
|
|
28
|
+
*/
|
|
29
|
+
export function lcmEvidenceIdentity(
|
|
30
|
+
row: LcmRowIdentity,
|
|
31
|
+
fallbackSessionId: string,
|
|
32
|
+
): LcmEvidenceIdentity {
|
|
33
|
+
const sessionId = resolveSessionId(row, fallbackSessionId);
|
|
34
|
+
const archiveRowId = lcmArchiveRowId(row);
|
|
35
|
+
if (archiveRowId === undefined) {
|
|
36
|
+
return { id: `${sessionId}:${row.turn_index}` };
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
id: JSON.stringify(["lcm-row", sessionId, archiveRowId]),
|
|
40
|
+
archiveRowId,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Match a search hit to its expanded archive row without conflating siblings. */
|
|
45
|
+
export function isSameLcmRow(
|
|
46
|
+
left: LcmRowIdentity,
|
|
47
|
+
leftFallbackSessionId: string,
|
|
48
|
+
right: LcmRowIdentity,
|
|
49
|
+
rightFallbackSessionId: string,
|
|
50
|
+
): boolean {
|
|
51
|
+
const leftRowId = lcmArchiveRowId(left);
|
|
52
|
+
const rightRowId = lcmArchiveRowId(right);
|
|
53
|
+
const leftSessionId = resolveSessionId(left, leftFallbackSessionId);
|
|
54
|
+
const rightSessionId = resolveSessionId(right, rightFallbackSessionId);
|
|
55
|
+
if (leftRowId !== undefined && rightRowId !== undefined) {
|
|
56
|
+
return leftRowId === rightRowId && leftSessionId === rightSessionId;
|
|
57
|
+
}
|
|
58
|
+
return leftSessionId === rightSessionId && left.turn_index === right.turn_index;
|
|
59
|
+
}
|
package/src/lcm/index.ts
CHANGED
|
@@ -4,6 +4,13 @@ export {
|
|
|
4
4
|
type LcmEngineConfig,
|
|
5
5
|
type LcmExpandedMessage,
|
|
6
6
|
} from "./engine.js";
|
|
7
|
+
export {
|
|
8
|
+
isSameLcmRow,
|
|
9
|
+
lcmArchiveRowId,
|
|
10
|
+
lcmEvidenceIdentity,
|
|
11
|
+
type LcmEvidenceIdentity,
|
|
12
|
+
type LcmRowIdentity,
|
|
13
|
+
} from "./evidence-identity.js";
|
|
7
14
|
export { LcmArchive, estimateTokens } from "./archive.js";
|
|
8
15
|
export { LcmDag, type SummaryNode } from "./dag.js";
|
|
9
16
|
export { LcmSummarizer, type SummarizeFn } from "./summarizer.js";
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { AccessLcmSurface, type AccessLcmSurfaceDeps } from "./access-lcm-surface.js";
|
|
5
|
+
import {
|
|
6
|
+
AccessRecallSurface,
|
|
7
|
+
type AccessRecallSurfaceDeps,
|
|
8
|
+
} from "./access-recall-surface.js";
|
|
9
|
+
import { buildEventOrderRecallSection } from "./event-order-recall.js";
|
|
10
|
+
import { buildExplicitCueRecallSection, type ExplicitCueRecallEngine } from "./explicit-cue-recall.js";
|
|
11
|
+
import { buildFocusedListRecallSection } from "./focused-list-recall.js";
|
|
12
|
+
import {
|
|
13
|
+
isSameLcmRow,
|
|
14
|
+
lcmEvidenceIdentity,
|
|
15
|
+
} from "./lcm/evidence-identity.js";
|
|
16
|
+
import { buildResponseGuidanceRecallSection } from "./response-guidance-recall.js";
|
|
17
|
+
import { buildTargetedFactRecallSection } from "./targeted-fact-recall.js";
|
|
18
|
+
|
|
19
|
+
interface TestRow {
|
|
20
|
+
id?: number;
|
|
21
|
+
session_id: string;
|
|
22
|
+
turn_index: number;
|
|
23
|
+
role: string;
|
|
24
|
+
content: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class LineageEngine implements ExplicitCueRecallEngine {
|
|
28
|
+
constructor(
|
|
29
|
+
private readonly rows: TestRow[],
|
|
30
|
+
private readonly searchRowId?: number,
|
|
31
|
+
private readonly searchContent?: string,
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
async expandContext(
|
|
35
|
+
sessionId: string,
|
|
36
|
+
fromTurn: number,
|
|
37
|
+
toTurn: number,
|
|
38
|
+
_maxTokens: number,
|
|
39
|
+
): Promise<TestRow[]> {
|
|
40
|
+
return this.rows.filter((row) =>
|
|
41
|
+
row.session_id === sessionId &&
|
|
42
|
+
row.turn_index >= fromTurn &&
|
|
43
|
+
row.turn_index <= toTurn
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async searchContextFull(): Promise<
|
|
48
|
+
Array<TestRow & { score: number }>
|
|
49
|
+
> {
|
|
50
|
+
const row = this.rows.find((candidate) => candidate.id === this.searchRowId);
|
|
51
|
+
return row
|
|
52
|
+
? [{
|
|
53
|
+
...row,
|
|
54
|
+
content: this.searchContent ?? row.content,
|
|
55
|
+
score: 99,
|
|
56
|
+
}]
|
|
57
|
+
: [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async getStats(sessionId?: string): Promise<{
|
|
61
|
+
totalMessages: number;
|
|
62
|
+
maxTurnIndex?: number;
|
|
63
|
+
}> {
|
|
64
|
+
const rows = sessionId
|
|
65
|
+
? this.rows.filter((row) => row.session_id === sessionId)
|
|
66
|
+
: this.rows;
|
|
67
|
+
return {
|
|
68
|
+
totalMessages: rows.length,
|
|
69
|
+
maxTurnIndex: rows.length > 0
|
|
70
|
+
? Math.max(...rows.map((row) => row.turn_index))
|
|
71
|
+
: undefined,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
test("LCM evidence identity distinguishes sibling rows and preserves the legacy fallback", () => {
|
|
77
|
+
const first = { id: 41, session_id: "session", turn_index: 7 };
|
|
78
|
+
const second = { id: 42, session_id: "session", turn_index: 7 };
|
|
79
|
+
assert.notEqual(
|
|
80
|
+
lcmEvidenceIdentity(first, "session").id,
|
|
81
|
+
lcmEvidenceIdentity(second, "session").id,
|
|
82
|
+
);
|
|
83
|
+
assert.equal(isSameLcmRow(first, "session", second, "session"), false);
|
|
84
|
+
assert.equal(
|
|
85
|
+
lcmEvidenceIdentity({ turn_index: 7 }, "session").id,
|
|
86
|
+
"session:7",
|
|
87
|
+
);
|
|
88
|
+
assert.equal(
|
|
89
|
+
isSameLcmRow(
|
|
90
|
+
{ turn_index: 7 },
|
|
91
|
+
"session",
|
|
92
|
+
{ turn_index: 7 },
|
|
93
|
+
"session",
|
|
94
|
+
),
|
|
95
|
+
true,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("explicit-cue recall keeps distinct archive rows that share a turn", async () => {
|
|
100
|
+
const engine = new LineageEngine([
|
|
101
|
+
{ id: 101, session_id: "s", turn_index: 7, role: "user", content: "First row at the referenced turn." },
|
|
102
|
+
{ id: 102, session_id: "s", turn_index: 7, role: "assistant", content: "Second row at the referenced turn." },
|
|
103
|
+
]);
|
|
104
|
+
const recalled = await buildExplicitCueRecallSection({
|
|
105
|
+
engine,
|
|
106
|
+
sessionId: "s",
|
|
107
|
+
query: "Review turn 7",
|
|
108
|
+
maxChars: 4_000,
|
|
109
|
+
});
|
|
110
|
+
assert.match(recalled, /First row at the referenced turn/);
|
|
111
|
+
assert.match(recalled, /Second row at the referenced turn/);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("targeted recall replaces and scores only the matching same-turn row", async () => {
|
|
115
|
+
const engine = new LineageEngine([
|
|
116
|
+
{ id: 101, session_id: "s", turn_index: 7, role: "user", content: "I earn approximately $100,000 CAD annually as a senior engineer." },
|
|
117
|
+
{ id: 102, session_id: "s", turn_index: 7, role: "user", content: "My recent raise to $110,000 CAD as a senior engineer at Saint Pierre Manufacturing Ltd is now effective." },
|
|
118
|
+
], 102, "The matching row records my recent raise to $110,000 CAD as a senior engineer at Saint Pierre Manufacturing Ltd.");
|
|
119
|
+
const recalled = await buildTargetedFactRecallSection({
|
|
120
|
+
engine,
|
|
121
|
+
sessionId: "s",
|
|
122
|
+
query: "What is my annual salary as a senior engineer at Saint Pierre Manufacturing Ltd?",
|
|
123
|
+
maxChars: 6_000,
|
|
124
|
+
});
|
|
125
|
+
assert.match(recalled, /earn approximately \$100,000 CAD annually/);
|
|
126
|
+
assert.match(recalled, /matching row records my recent raise to \$110,000 CAD/);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("focused-list recall keeps distinct same-turn calculations while matching the search row", async () => {
|
|
130
|
+
const engine = new LineageEngine([
|
|
131
|
+
{ id: 101, session_id: "s", turn_index: 7, role: "user", content: "Can you help me calculate P(both heads) = 1/2 x 1/2 = 1/4 so I can make sure I get it right?" },
|
|
132
|
+
{ id: 102, session_id: "s", turn_index: 7, role: "user", content: "I am trying to verify if P(rolling a number greater than 4) = 2/6 = 1/3 is correct." },
|
|
133
|
+
], 102, "I confirmed P(rolling a number greater than 4) = 2/6 = 1/3.");
|
|
134
|
+
const recalled = await buildFocusedListRecallSection({
|
|
135
|
+
engine,
|
|
136
|
+
sessionId: "s",
|
|
137
|
+
query: "In my questions about tossing coins and rolling dice, how many different probability calculations did I try to confirm?",
|
|
138
|
+
maxChars: 6_000,
|
|
139
|
+
});
|
|
140
|
+
assert.match(recalled, /P\(both heads\)/);
|
|
141
|
+
assert.match(recalled, /P\(rolling a number greater than 4\)/);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("response guidance keeps distinct same-turn rows while preserving its existing filters", async () => {
|
|
145
|
+
const engine = new LineageEngine([
|
|
146
|
+
{ id: 101, session_id: "s", turn_index: 7, role: "user", content: "I prefer a structured daily routine with a written priority list each morning." },
|
|
147
|
+
{ id: 102, session_id: "s", turn_index: 7, role: "user", content: "My structured daily routine schedules focused work blocks and buffer time between meetings." },
|
|
148
|
+
], 102, "My structured daily routine schedules focused work blocks and buffer time between meetings.");
|
|
149
|
+
const recalled = await buildResponseGuidanceRecallSection({
|
|
150
|
+
engine,
|
|
151
|
+
sessionId: "s",
|
|
152
|
+
query: "How should I organize my day to stay on track?",
|
|
153
|
+
maxChars: 6_000,
|
|
154
|
+
});
|
|
155
|
+
assert.match(recalled, /written priority list each morning/);
|
|
156
|
+
assert.match(recalled, /focused work blocks/);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("event-order recall keeps distinct same-turn chronological rows", async () => {
|
|
160
|
+
const engine = new LineageEngine([
|
|
161
|
+
{ id: 101, session_id: "s", turn_index: 7, role: "user", content: "Patrick suggested a March workshop on workflow optimization." },
|
|
162
|
+
{ id: 102, session_id: "s", turn_index: 7, role: "user", content: "Patrick and I planned an interview tips meeting for tomorrow." },
|
|
163
|
+
]);
|
|
164
|
+
const recalled = await buildEventOrderRecallSection({
|
|
165
|
+
engine,
|
|
166
|
+
sessionId: "s",
|
|
167
|
+
query: "Walk me through my interactions with Patrick in order.",
|
|
168
|
+
maxChars: 6_000,
|
|
169
|
+
});
|
|
170
|
+
assert.match(recalled, /workshop suggestion/);
|
|
171
|
+
assert.match(recalled, /interview tips meeting/);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
function accessSearchDeps(rows: TestRow[]): AccessLcmSurfaceDeps {
|
|
175
|
+
const orchestrator = {
|
|
176
|
+
config: {
|
|
177
|
+
defaultNamespace: "default",
|
|
178
|
+
namespaces: false,
|
|
179
|
+
},
|
|
180
|
+
lcmEngine: {
|
|
181
|
+
enabled: true,
|
|
182
|
+
searchContextFull: async () => rows,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
orchestrator,
|
|
187
|
+
lcmSessionIdsForNamespaces: () => ["primary", "fallback"],
|
|
188
|
+
resolveImplicitLcmReadFallbackNamespace: () => "default",
|
|
189
|
+
resolveLcmReadNamespace: () => "default",
|
|
190
|
+
resolveLcmReadSessionIds: () => ["primary", "fallback"],
|
|
191
|
+
resolveLcmReadSessionKey: () => "primary",
|
|
192
|
+
resolveReadableNamespace: () => "default",
|
|
193
|
+
resolveRequestPrincipal: () => "default",
|
|
194
|
+
resolveScopeProfileLcmReadNamespaces: () => null,
|
|
195
|
+
} as unknown as AccessLcmSurfaceDeps;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
test("access LCM search dedupes repeated rows but retains same-turn siblings", async () => {
|
|
199
|
+
const rows: TestRow[] = [
|
|
200
|
+
{ id: 101, session_id: "s", turn_index: 7, role: "user", content: "first sibling" },
|
|
201
|
+
{ id: 102, session_id: "s", turn_index: 7, role: "assistant", content: "second sibling" },
|
|
202
|
+
];
|
|
203
|
+
const response = await new AccessLcmSurface(accessSearchDeps(rows)).lcmSearch({
|
|
204
|
+
query: "sibling",
|
|
205
|
+
sessionKey: "s",
|
|
206
|
+
});
|
|
207
|
+
assert.deepEqual(response.results.map((row) => row.content), [
|
|
208
|
+
"first sibling",
|
|
209
|
+
"second sibling",
|
|
210
|
+
]);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("raw excerpts dedupe repeated rows but retain same-turn siblings", async () => {
|
|
214
|
+
const rows: TestRow[] = [
|
|
215
|
+
{ id: 101, session_id: "s", turn_index: 7, role: "user", content: "first sibling" },
|
|
216
|
+
{ id: 102, session_id: "s", turn_index: 7, role: "assistant", content: "second sibling" },
|
|
217
|
+
];
|
|
218
|
+
const deps = {
|
|
219
|
+
orchestrator: {
|
|
220
|
+
config: { defaultNamespace: "default" },
|
|
221
|
+
lcmEngine: {
|
|
222
|
+
enabled: true,
|
|
223
|
+
searchContextFull: async () => rows,
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
} as unknown as AccessRecallSurfaceDeps;
|
|
227
|
+
const excerpts = await new AccessRecallSurface(deps).fetchRawExcerpts("raw", {
|
|
228
|
+
query: "sibling",
|
|
229
|
+
sessionKey: "s",
|
|
230
|
+
lcmSessionIds: ["primary", "fallback"],
|
|
231
|
+
});
|
|
232
|
+
assert.deepEqual(excerpts?.map((row) => row.content), [
|
|
233
|
+
"first sibling",
|
|
234
|
+
"second sibling",
|
|
235
|
+
]);
|
|
236
|
+
});
|