@agenr/openclaw-plugin 3.3.0 → 2026.6.3
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/build-before-turn-artifact-NPUHVWFE.js +71 -0
- package/dist/build-recall-artifact-F3LS3PZX.js +62 -0
- package/dist/chunk-5AXMFBHR.js +14 -0
- package/dist/chunk-5AYIXQRF.js +4452 -0
- package/dist/chunk-5TIP2EPP.js +6944 -0
- package/dist/chunk-GAERET5Q.js +2070 -0
- package/dist/chunk-GF3PX3VM.js +41 -0
- package/dist/chunk-GKZQ5AG5.js +44 -0
- package/dist/chunk-IBPS64W3.js +1069 -0
- package/dist/chunk-MC3C2XM5.js +148 -0
- package/dist/chunk-NSLTJBUC.js +270 -0
- package/dist/chunk-OJSIZDZD.js +9 -0
- package/dist/chunk-OWGQWQUP.js +45 -0
- package/dist/chunk-SIY3JA7T.js +3062 -0
- package/dist/chunk-SOQW7356.js +2416 -0
- package/dist/chunk-U74RE3L7.js +3233 -0
- package/dist/chunk-VBPYU7GO.js +597 -0
- package/dist/chunk-VTHBPXDQ.js +1750 -0
- package/dist/chunk-XFJ4S4G2.js +1679 -0
- package/dist/chunk-Y5NB3FTH.js +106 -0
- package/dist/chunk-ZX55JBV2.js +4451 -0
- package/dist/index.js +1855 -19846
- package/dist/lifecycle-checkpoint-IAC5FCQU.js +154 -0
- package/dist/scan-6JKPOQHD.js +6 -0
- package/dist/service-EKFACEN6.js +15 -0
- package/dist/service-RHNB5AEQ.js +861 -0
- package/dist/sink-AUAAWC5O.js +8 -0
- package/openclaw.plugin.json +2 -11
- package/package.json +1 -1
|
@@ -0,0 +1,1679 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DURABLE_SELECT_COLUMNS,
|
|
3
|
+
buildActiveDurableClause,
|
|
4
|
+
buildValidAsOfClause,
|
|
5
|
+
getDurables,
|
|
6
|
+
mapDurableRow,
|
|
7
|
+
parseJsonStringArray,
|
|
8
|
+
projectClaimCentricRecallEntry,
|
|
9
|
+
readOptionalString,
|
|
10
|
+
readRequiredString,
|
|
11
|
+
runProcedureRecall,
|
|
12
|
+
truncate
|
|
13
|
+
} from "./chunk-5TIP2EPP.js";
|
|
14
|
+
import {
|
|
15
|
+
recall
|
|
16
|
+
} from "./chunk-GAERET5Q.js";
|
|
17
|
+
import {
|
|
18
|
+
MEMORY_DIRECTIVE_CLAIM_KEY_PREFIX,
|
|
19
|
+
isDirectiveDurable,
|
|
20
|
+
isProactiveDirectiveDurable,
|
|
21
|
+
parseDirectiveMetadata
|
|
22
|
+
} from "./chunk-VTHBPXDQ.js";
|
|
23
|
+
import {
|
|
24
|
+
isCurrentlyValidMemory,
|
|
25
|
+
isWithinValidityWindow,
|
|
26
|
+
resolveKeyedDurableLifecycleStatus
|
|
27
|
+
} from "./chunk-VBPYU7GO.js";
|
|
28
|
+
|
|
29
|
+
// src/core/directives/abstain.ts
|
|
30
|
+
var ABSTAIN_VERB_PATTERN = "(?:mention|mentioning|bring up|bringing up|discuss|discussing|talk about|talking about|reference|referencing|raise|raising|recommend|recommending|suggest|suggesting|surface|surfacing|remind me about|remind me of)";
|
|
31
|
+
var ABSTAIN_LEAD_PATTERN = "(?:do not|don't|do n't|never|please do not|please don't|stop|avoid|no longer)";
|
|
32
|
+
var ABSTAIN_PHRASE_PATTERN = new RegExp(`\\b${ABSTAIN_LEAD_PATTERN}\\s+${ABSTAIN_VERB_PATTERN}\\s+(.+?)(?:[.!?;]|$)`, "giu");
|
|
33
|
+
var TRAILING_QUALIFIER_PATTERN = /\b(?:again|anymore|any more|ever again|ever|at all|please|with me|to me)\s*$/giu;
|
|
34
|
+
var LEADING_DETERMINER_PATTERN = /^(?:the|a|an|any|my|our|that|this|these|those)\s+/iu;
|
|
35
|
+
var MIN_BLOCKED_TERM_LENGTH = 2;
|
|
36
|
+
function parseAbstainDirective(entry) {
|
|
37
|
+
const metadata = parseDirectiveMetadata(entry);
|
|
38
|
+
if (!metadata || metadata.polarity !== "abstain") {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const claimKey = entry.claim_key?.trim() ?? `${MEMORY_DIRECTIVE_CLAIM_KEY_PREFIX}${entry.id}`;
|
|
42
|
+
const blockedTerms = extractBlockedTerms(entry, claimKey);
|
|
43
|
+
if (blockedTerms.length === 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
id: entry.id,
|
|
48
|
+
claimKey,
|
|
49
|
+
blockedTerms
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function collectAbstainDirectives(entries) {
|
|
53
|
+
const directives = [];
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const directive = parseAbstainDirective(entry);
|
|
56
|
+
if (directive) {
|
|
57
|
+
directives.push(directive);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return directives;
|
|
61
|
+
}
|
|
62
|
+
function findAbstainViolation(entry, directives) {
|
|
63
|
+
if (directives.length === 0) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const haystack = normalizeForMatch(`${entry.subject} ${entry.content}`);
|
|
67
|
+
if (haystack.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
for (const directive of directives) {
|
|
71
|
+
if (directive.id === entry.id) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
for (const blockedTerm of directive.blockedTerms) {
|
|
75
|
+
if (mentionsBlockedTerm(haystack, blockedTerm)) {
|
|
76
|
+
return {
|
|
77
|
+
directiveId: directive.id,
|
|
78
|
+
blockedTerm
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function normalizeTextForPhraseMatch(value) {
|
|
86
|
+
return normalizeForMatch(value);
|
|
87
|
+
}
|
|
88
|
+
function textMentionsPhrase(normalizedHaystack, normalizedPhrase) {
|
|
89
|
+
return mentionsBlockedTerm(normalizedHaystack, normalizedPhrase);
|
|
90
|
+
}
|
|
91
|
+
function textMatchesTopicTrigger(normalizedHaystack, trigger) {
|
|
92
|
+
if (!trigger.startsWith("topic:")) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const topic = trigger.slice("topic:".length);
|
|
96
|
+
return textMentionsPhrase(normalizedHaystack, topic);
|
|
97
|
+
}
|
|
98
|
+
function extractBlockedTerms(entry, claimKey) {
|
|
99
|
+
const terms = /* @__PURE__ */ new Set();
|
|
100
|
+
for (const source of [entry.content, entry.subject]) {
|
|
101
|
+
for (const phrase of matchAbstainPhrases(source)) {
|
|
102
|
+
const cleaned = cleanBlockedTerm(phrase);
|
|
103
|
+
if (cleaned.length >= MIN_BLOCKED_TERM_LENGTH) {
|
|
104
|
+
terms.add(cleaned);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (terms.size === 0) {
|
|
109
|
+
const fallback = cleanBlockedTerm(deriveTermFromClaimKey(claimKey));
|
|
110
|
+
if (fallback.length >= MIN_BLOCKED_TERM_LENGTH) {
|
|
111
|
+
terms.add(fallback);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return Array.from(terms);
|
|
115
|
+
}
|
|
116
|
+
function matchAbstainPhrases(source) {
|
|
117
|
+
const phrases = [];
|
|
118
|
+
const pattern = new RegExp(ABSTAIN_PHRASE_PATTERN.source, ABSTAIN_PHRASE_PATTERN.flags);
|
|
119
|
+
let match = pattern.exec(source);
|
|
120
|
+
while (match !== null) {
|
|
121
|
+
const captured = match[1];
|
|
122
|
+
if (captured) {
|
|
123
|
+
phrases.push(captured);
|
|
124
|
+
}
|
|
125
|
+
match = pattern.exec(source);
|
|
126
|
+
}
|
|
127
|
+
return phrases;
|
|
128
|
+
}
|
|
129
|
+
function deriveTermFromClaimKey(claimKey) {
|
|
130
|
+
const suffix = claimKey.slice(MEMORY_DIRECTIVE_CLAIM_KEY_PREFIX.length);
|
|
131
|
+
return suffix.replace(/^(?:do[_-]?not[_-]?mention[_-]?|avoid[_-]?|no[_-]?)/iu, "").replace(/[_-]+/gu, " ").trim();
|
|
132
|
+
}
|
|
133
|
+
function cleanBlockedTerm(phrase) {
|
|
134
|
+
const collapsed = phrase.replace(/\s+/gu, " ").trim();
|
|
135
|
+
const withoutLead = collapsed.replace(LEADING_DETERMINER_PATTERN, "");
|
|
136
|
+
const withoutTrailing = withoutLead.replace(TRAILING_QUALIFIER_PATTERN, "").trim();
|
|
137
|
+
return normalizeForMatch(withoutTrailing);
|
|
138
|
+
}
|
|
139
|
+
function mentionsBlockedTerm(haystack, blockedTerm) {
|
|
140
|
+
if (blockedTerm.length < MIN_BLOCKED_TERM_LENGTH) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const escaped = escapeRegExp(blockedTerm).replace(/\\?\s+/gu, "\\s+");
|
|
144
|
+
const pattern = new RegExp(`(?:^|[^\\p{L}\\p{N}])${escaped}(?:[^\\p{L}\\p{N}]|$)`, "iu");
|
|
145
|
+
return pattern.test(haystack);
|
|
146
|
+
}
|
|
147
|
+
function normalizeForMatch(value) {
|
|
148
|
+
return value.replace(/\s+/gu, " ").trim().normalize("NFKC").toLocaleLowerCase();
|
|
149
|
+
}
|
|
150
|
+
function escapeRegExp(value) {
|
|
151
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/app/directives/abstain-filter.ts
|
|
155
|
+
var ABSTAIN_DIRECTIVE_LOOKUP_FAILED_NOTICE = "Memory-directive lookup failed; blocked-topic suppression was skipped this pass and only directive rows were withheld.";
|
|
156
|
+
async function applyAbstainDirectives(items, listActiveAbstainDirectives2) {
|
|
157
|
+
if (items.length === 0) {
|
|
158
|
+
return { kept: [...items], suppressed: [], lookupFailed: false };
|
|
159
|
+
}
|
|
160
|
+
const suppressed = [];
|
|
161
|
+
const nonDirectiveItems = [];
|
|
162
|
+
for (const item of items) {
|
|
163
|
+
if (isDirectiveDurable(item.entry) && !isAllowedDirectiveInjectionItem(item)) {
|
|
164
|
+
suppressed.push({ entryId: item.entry.id, reason: "directive_self" });
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
nonDirectiveItems.push(item);
|
|
168
|
+
}
|
|
169
|
+
if (!listActiveAbstainDirectives2 || nonDirectiveItems.length === 0) {
|
|
170
|
+
return { kept: nonDirectiveItems, suppressed, lookupFailed: false };
|
|
171
|
+
}
|
|
172
|
+
let directiveRows;
|
|
173
|
+
try {
|
|
174
|
+
directiveRows = await listActiveAbstainDirectives2();
|
|
175
|
+
} catch {
|
|
176
|
+
return { kept: nonDirectiveItems, suppressed, lookupFailed: true };
|
|
177
|
+
}
|
|
178
|
+
const directives = collectAbstainDirectives(directiveRows);
|
|
179
|
+
if (directives.length === 0) {
|
|
180
|
+
return { kept: nonDirectiveItems, suppressed, lookupFailed: false };
|
|
181
|
+
}
|
|
182
|
+
const kept = [];
|
|
183
|
+
for (const item of nonDirectiveItems) {
|
|
184
|
+
const violation = findAbstainViolation(item.entry, directives);
|
|
185
|
+
if (violation) {
|
|
186
|
+
suppressed.push({
|
|
187
|
+
entryId: item.entry.id,
|
|
188
|
+
reason: "directive_topic",
|
|
189
|
+
directiveId: violation.directiveId,
|
|
190
|
+
blockedTerm: violation.blockedTerm
|
|
191
|
+
});
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
kept.push(item);
|
|
195
|
+
}
|
|
196
|
+
return { kept, suppressed, lookupFailed: false };
|
|
197
|
+
}
|
|
198
|
+
function buildAbstentionNotice(suppression) {
|
|
199
|
+
if (suppression.reason === "directive_self") {
|
|
200
|
+
return `Skipped injecting memory directive ${suppression.entryId}; directives are not surfaced as memory.`;
|
|
201
|
+
}
|
|
202
|
+
const directive = suppression.directiveId ?? "unknown";
|
|
203
|
+
const term = suppression.blockedTerm ?? "a blocked topic";
|
|
204
|
+
return `Suppressed durable ${suppression.entryId} because memory directive ${directive} blocks "${term}".`;
|
|
205
|
+
}
|
|
206
|
+
async function applyAbstainDirectivesForInjection(items, listActiveAbstainDirectives2, diagnostics) {
|
|
207
|
+
const result = await applyAbstainDirectives(items, listActiveAbstainDirectives2);
|
|
208
|
+
if (result.lookupFailed) {
|
|
209
|
+
diagnostics.notices.push(ABSTAIN_DIRECTIVE_LOOKUP_FAILED_NOTICE);
|
|
210
|
+
}
|
|
211
|
+
if (result.suppressed.length > 0) {
|
|
212
|
+
diagnostics.directiveAbstentions = result.suppressed;
|
|
213
|
+
for (const suppression of result.suppressed) {
|
|
214
|
+
diagnostics.notices.push(buildAbstentionNotice(suppression));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return result.kept;
|
|
218
|
+
}
|
|
219
|
+
function isAllowedDirectiveInjectionItem(item) {
|
|
220
|
+
return item.sourceKind === "directive" && isProactiveDirectiveDurable(item.entry);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/app/before-turn/format-provenance.ts
|
|
224
|
+
function formatProjectedProvenance(provenance) {
|
|
225
|
+
const parts = [
|
|
226
|
+
provenance.supersededById ? `superseded_by=${provenance.supersededById}` : void 0,
|
|
227
|
+
provenance.supersessionKind ? `kind=${provenance.supersessionKind}` : void 0,
|
|
228
|
+
provenance.supersessionReason ? `reason=${provenance.supersessionReason}` : void 0,
|
|
229
|
+
provenance.supportSourceKind ? `support=${provenance.supportSourceKind}` : void 0,
|
|
230
|
+
provenance.supportMode ? `support_mode=${provenance.supportMode}` : void 0,
|
|
231
|
+
provenance.supportObservedAt ? `observed=${provenance.supportObservedAt}` : void 0,
|
|
232
|
+
provenance.supportLocator ? `locator=${provenance.supportLocator}` : void 0
|
|
233
|
+
].filter((value) => value !== void 0);
|
|
234
|
+
return parts.length > 0 ? parts.join(" | ") : void 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/app/before-turn/select-patch-items.ts
|
|
238
|
+
function selectDurablePatchItems(items, policy, diagnostics) {
|
|
239
|
+
if (policy.maxDurableEntries <= 0 || items.length === 0) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
const boundedItems = items.slice(0, policy.maxDurableEntries);
|
|
243
|
+
const expandedLimit = Math.max(policy.maxDurableEntries, policy.maxHighConfidenceDurableEntries);
|
|
244
|
+
if (expandedLimit <= policy.maxDurableEntries || items.length <= policy.maxDurableEntries) {
|
|
245
|
+
return boundedItems;
|
|
246
|
+
}
|
|
247
|
+
const expansionCandidates = items.slice(0, expandedLimit);
|
|
248
|
+
const canExpand = expansionCandidates.length > policy.maxDurableEntries && expansionCandidates.every((item) => item.score >= policy.highConfidenceRecallThreshold);
|
|
249
|
+
if (canExpand) {
|
|
250
|
+
diagnostics.notices.push(`Before-turn durable recall expanded to ${expansionCandidates.length} high-confidence items.`);
|
|
251
|
+
return expansionCandidates;
|
|
252
|
+
}
|
|
253
|
+
diagnostics.notices.push(
|
|
254
|
+
`Before-turn durable recall kept the top ${boundedItems.length} item${boundedItems.length === 1 ? "" : "s"} because additional candidates were not high confidence.`
|
|
255
|
+
);
|
|
256
|
+
return boundedItems;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/app/before-turn/topic-directives.ts
|
|
260
|
+
var TOPIC_PROACTIVE_DIRECTIVE_LOOKUP_FAILED_NOTICE = "Topic proactive directive lookup failed; topic directive surfacing was skipped this pass.";
|
|
261
|
+
async function injectTopicProactiveDirectives(currentTurnText, recalledItems, policy, deps, diagnostics) {
|
|
262
|
+
if (!deps.listActiveTopicProactiveDirectives) {
|
|
263
|
+
return recalledItems;
|
|
264
|
+
}
|
|
265
|
+
let directiveRows;
|
|
266
|
+
try {
|
|
267
|
+
directiveRows = await deps.listActiveTopicProactiveDirectives();
|
|
268
|
+
} catch {
|
|
269
|
+
diagnostics.notices.push(TOPIC_PROACTIVE_DIRECTIVE_LOOKUP_FAILED_NOTICE);
|
|
270
|
+
return recalledItems;
|
|
271
|
+
}
|
|
272
|
+
const now = deps.now ?? /* @__PURE__ */ new Date();
|
|
273
|
+
const nowMs = now.getTime();
|
|
274
|
+
const activeDirectives = filterCurrentEntries(directiveRows, nowMs);
|
|
275
|
+
diagnostics.topicProactiveDirectiveCandidateCount = activeDirectives.length;
|
|
276
|
+
const normalizedTurn = normalizeTextForPhraseMatch(currentTurnText);
|
|
277
|
+
const matchedDirectives = activeDirectives.filter((entry) => {
|
|
278
|
+
const metadata = parseDirectiveMetadata(entry);
|
|
279
|
+
return metadata?.polarity === "proactive" && metadata.trigger.startsWith("topic:") && textMatchesTopicTrigger(normalizedTurn, metadata.trigger);
|
|
280
|
+
});
|
|
281
|
+
diagnostics.topicProactiveDirectiveMatchedCount = matchedDirectives.length;
|
|
282
|
+
if (matchedDirectives.length === 0) {
|
|
283
|
+
return recalledItems;
|
|
284
|
+
}
|
|
285
|
+
const directiveItems = matchedDirectives.map((entry) => buildTopicDirectivePatchItem(entry, deps, policy.highConfidenceRecallThreshold));
|
|
286
|
+
return mergeTopicDirectivePatchItems(directiveItems, recalledItems, policy, diagnostics);
|
|
287
|
+
}
|
|
288
|
+
function filterCurrentEntries(entries, nowMs) {
|
|
289
|
+
return entries.filter((entry) => isWithinValidityWindow(entry.valid_from, entry.valid_to, nowMs));
|
|
290
|
+
}
|
|
291
|
+
function buildTopicDirectivePatchItem(entry, deps, score) {
|
|
292
|
+
const metadata = parseDirectiveMetadata(entry);
|
|
293
|
+
const projected = projectClaimCentricRecallEntry(buildSyntheticRecallOutput(entry, score), {
|
|
294
|
+
slotPolicyConfig: deps.slotPolicyConfig
|
|
295
|
+
});
|
|
296
|
+
const provenanceSummary = formatProjectedProvenance(projected.provenance);
|
|
297
|
+
return {
|
|
298
|
+
rank: 0,
|
|
299
|
+
entry,
|
|
300
|
+
sourceKind: "directive",
|
|
301
|
+
score,
|
|
302
|
+
whySurfaced: {
|
|
303
|
+
summary: `proactive memory directive; trigger ${metadata?.trigger ?? "topic"}`,
|
|
304
|
+
reasons: ["proactive memory directive", `trigger ${metadata?.trigger ?? "topic"}`, `importance ${entry.importance}`]
|
|
305
|
+
},
|
|
306
|
+
memoryState: projected.memoryState,
|
|
307
|
+
claimStatus: projected.claimStatus,
|
|
308
|
+
freshnessLabel: projected.freshness.label,
|
|
309
|
+
...provenanceSummary ? { provenanceSummary } : {}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function buildSyntheticRecallOutput(entry, score) {
|
|
313
|
+
return {
|
|
314
|
+
entry,
|
|
315
|
+
score,
|
|
316
|
+
scores: {
|
|
317
|
+
relevance: score,
|
|
318
|
+
rrf: score,
|
|
319
|
+
vector: 0,
|
|
320
|
+
lexical: 0,
|
|
321
|
+
recency: 1,
|
|
322
|
+
importance: entry.importance / 10,
|
|
323
|
+
historicalLineage: 0,
|
|
324
|
+
neighborhoodBoost: 0,
|
|
325
|
+
claimKeyTrustPenalty: 0,
|
|
326
|
+
claimKeyRedundancyPenalty: 0
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function mergeTopicDirectivePatchItems(directiveItems, recalledItems, policy, diagnostics) {
|
|
331
|
+
const seenEntryIds = /* @__PURE__ */ new Set();
|
|
332
|
+
const merged = [];
|
|
333
|
+
for (const item of [...directiveItems, ...recalledItems]) {
|
|
334
|
+
if (seenEntryIds.has(item.entry.id)) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
seenEntryIds.add(item.entry.id);
|
|
338
|
+
merged.push(item);
|
|
339
|
+
}
|
|
340
|
+
return selectDurablePatchItems(merged, policy, diagnostics);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/app/before-turn/service.ts
|
|
344
|
+
var DEFAULT_MAX_DURABLE_ENTRIES = 1;
|
|
345
|
+
var DEFAULT_MAX_HIGH_CONFIDENCE_DURABLE_ENTRIES = 2;
|
|
346
|
+
var DEFAULT_MAX_RECENT_TURNS = 2;
|
|
347
|
+
var DEFAULT_MAX_QUERY_CHARS = 450;
|
|
348
|
+
var DEFAULT_MAX_PROCEDURE_CANDIDATES = 3;
|
|
349
|
+
var DEFAULT_RECALL_THRESHOLD = 0.6;
|
|
350
|
+
var DEFAULT_HIGH_CONFIDENCE_RECALL_THRESHOLD = 0.97;
|
|
351
|
+
var DEFAULT_PROCEDURE_THRESHOLD = 0.72;
|
|
352
|
+
var DEFAULT_SKIP_TRIVIAL_TURNS = true;
|
|
353
|
+
var DEFAULT_REQUIRE_TURN_SIGNAL = true;
|
|
354
|
+
var SHORT_TURN_MAX_WORDS = 4;
|
|
355
|
+
var SHORT_TURN_MAX_CHARS = 24;
|
|
356
|
+
var SOCIAL_TURN_RE = /^(?:hi|hello|hey|hey there|hello there|thanks|thank you|ok|okay|cool|sounds good|got it|yep|yes|no|nice|great|awesome|perfect|ping)(?:[.!?]+)?$/iu;
|
|
357
|
+
var TASK_SIGNAL_RE = /\b(?:do|make|create|draft|write|send|schedule|book|reserve|organize|arrange|prepare|plan|choose|decide|contact|call|email|message|buy|order|find|search|check|compare|review|explain|summarize|investigate|research|use|apply|remember|recall|assist|help|should|need to|help me)\b/iu;
|
|
358
|
+
var FACTUAL_SIGNAL_RE = /\b(?:what(?:'s|\s+is|\s+was|\s+were)?|which|where|who|when|how much|how many|what time|what day|did we|do we|previous|prior|earlier|before|last|again|decision|preference|fact|rule|policy|status|availability|location|address|phone|email|price|cost|budget|deadline|date|time|name|called|uses|used|change(?:d)?|order|reservation|appointment|account|contact)\b/iu;
|
|
359
|
+
var PROCEDURAL_SIGNAL_RE = /\b(?:how do i|how should i|how can i|how to|what should i do|what do i need to do|steps|procedure|process|workflow|runbook|playbook|guide|guidance|instructions|checklist|recipe|template|walk me through|step by step|best way to|planning|arrange|prepare|book|reserve|schedule|rollout|migration|incident response)\b/iu;
|
|
360
|
+
var CONTEXT_REFERENCE_RE = /\b(?:it|its|that|this|they|them|their|those|these|he|him|his|she|her|hers|other one|other ones)\b/iu;
|
|
361
|
+
var HARD_CONTEXT_PREFIX_RE = /^(?:and\b|also\b|what about\b|how about\b|same\b|same as\b)/iu;
|
|
362
|
+
var SOFT_CONTEXT_FALLBACK_RE = /\b(?:next|follow up|follow-up|continue|continuation)\b/iu;
|
|
363
|
+
var CONTEXT_QUESTION_PREFIX_RE = /^(?:when|where|why|should|does|is|are|what should)\b/iu;
|
|
364
|
+
var MAX_CONTEXT_ANCHOR_CHARS = 120;
|
|
365
|
+
var DIRECTNESS_STABLE_GAP = 0.08;
|
|
366
|
+
var DIRECTNESS_SUBJECT_ENTITY_MATCH_BONUS = 0.16;
|
|
367
|
+
var DIRECTNESS_SUBJECT_IDENTITY_WRAPPER_BONUS = 0.12;
|
|
368
|
+
var DIRECTNESS_DEFINITIONAL_CONTENT_BONUS = 0.22;
|
|
369
|
+
var DIRECTNESS_CLAIM_KEY_ENTITY_MATCH_BONUS = 0.18;
|
|
370
|
+
var DIRECTNESS_ADJACENT_RELATIONSHIP_PENALTY = 0.18;
|
|
371
|
+
var DIRECTNESS_LIST_LORE_PENALTY = 0.08;
|
|
372
|
+
var ENTITY_DIRECTNESS_MAX_WORDS = 5;
|
|
373
|
+
var ENTITY_DIRECTNESS_RECALL_CANDIDATE_LIMIT = 5;
|
|
374
|
+
var DIRECTNESS_IDENTITY_WRAPPERS = /* @__PURE__ */ new Set(["identity", "profile", "bio", "biography", "definition", "overview", "summary"]);
|
|
375
|
+
var DIRECTNESS_RELATIONSHIP_KEYWORDS = /* @__PURE__ */ new Set([
|
|
376
|
+
"cousin",
|
|
377
|
+
"cousins",
|
|
378
|
+
"family",
|
|
379
|
+
"brother",
|
|
380
|
+
"brothers",
|
|
381
|
+
"sister",
|
|
382
|
+
"sisters",
|
|
383
|
+
"mother",
|
|
384
|
+
"father",
|
|
385
|
+
"parent",
|
|
386
|
+
"parents",
|
|
387
|
+
"friend",
|
|
388
|
+
"friends",
|
|
389
|
+
"relationship",
|
|
390
|
+
"relationships",
|
|
391
|
+
"owner",
|
|
392
|
+
"owners"
|
|
393
|
+
]);
|
|
394
|
+
var DIRECTNESS_LIST_LORE_KEYWORDS = /* @__PURE__ */ new Set(["list", "notes", "timeline", "history", "background", "facts", "lore"]);
|
|
395
|
+
async function runBeforeTurn(input, deps) {
|
|
396
|
+
const policy = normalizePolicy(input.policy);
|
|
397
|
+
const currentTurnText = normalizeOptionalString(input.currentTurnText);
|
|
398
|
+
const recentTurns = normalizeRecentTurns(input.recentTurns, policy.maxRecentTurns, currentTurnText);
|
|
399
|
+
const diagnostics = {
|
|
400
|
+
queryVariants: [],
|
|
401
|
+
recentTurnCount: recentTurns.length,
|
|
402
|
+
turnSignalLabels: [],
|
|
403
|
+
durableRecallUsed: false,
|
|
404
|
+
durableRecallCandidateCount: 0,
|
|
405
|
+
procedureRecallUsed: false,
|
|
406
|
+
procedureCandidateCount: 0,
|
|
407
|
+
abstained: false,
|
|
408
|
+
abstentionReasons: [],
|
|
409
|
+
notices: []
|
|
410
|
+
};
|
|
411
|
+
if (!currentTurnText) {
|
|
412
|
+
diagnostics.abstained = true;
|
|
413
|
+
diagnostics.abstentionReasons.push("Current turn text was empty after normalization.");
|
|
414
|
+
return {
|
|
415
|
+
durableMemory: [],
|
|
416
|
+
diagnostics
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const turnSignal = inspectTurnSignal(currentTurnText);
|
|
420
|
+
diagnostics.turnSignalLabels = turnSignal.signalLabels;
|
|
421
|
+
if (turnSignal.suppressedTurnCategory) {
|
|
422
|
+
diagnostics.suppressedTurnCategory = turnSignal.suppressedTurnCategory;
|
|
423
|
+
}
|
|
424
|
+
if (policy.skipTrivialTurns && turnSignal.suppressedTurnCategory && turnSignal.suppressedTurnCategory !== "low_signal") {
|
|
425
|
+
diagnostics.abstained = true;
|
|
426
|
+
diagnostics.abstentionReasons.push(turnSignal.reason);
|
|
427
|
+
return {
|
|
428
|
+
durableMemory: [],
|
|
429
|
+
diagnostics
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (policy.requireTurnSignal && turnSignal.signalLabels.length === 0) {
|
|
433
|
+
diagnostics.abstained = true;
|
|
434
|
+
diagnostics.abstentionReasons.push(turnSignal.reason);
|
|
435
|
+
return {
|
|
436
|
+
durableMemory: [],
|
|
437
|
+
diagnostics
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const durableQueryPlan = buildDurableRecallQueryPlan(currentTurnText, recentTurns, policy.maxQueryChars);
|
|
441
|
+
const procedureQuery = buildProcedureQuery(currentTurnText, recentTurns, policy.maxQueryChars);
|
|
442
|
+
if (!durableQueryPlan) {
|
|
443
|
+
diagnostics.abstained = true;
|
|
444
|
+
diagnostics.abstentionReasons.push("No usable before-turn query could be derived from the turn context.");
|
|
445
|
+
return {
|
|
446
|
+
durableMemory: [],
|
|
447
|
+
diagnostics
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
const [recalledDurableMemory, procedure] = await Promise.all([
|
|
451
|
+
policy.enableDurableRecall ? runDurableRecallSelection(currentTurnText, durableQueryPlan, input.sessionKey, policy, deps, diagnostics) : Promise.resolve([]),
|
|
452
|
+
policy.enableProcedureSuggestion && procedureQuery ? runProcedureSelection(procedureQuery, policy, deps, diagnostics) : Promise.resolve(void 0)
|
|
453
|
+
]);
|
|
454
|
+
const durableMemoryWithTopicDirectives = await injectTopicProactiveDirectives(currentTurnText, recalledDurableMemory, policy, deps, diagnostics);
|
|
455
|
+
const durableMemory = await applyAbstainDirectivesForInjection(durableMemoryWithTopicDirectives, deps.listActiveAbstainDirectives, diagnostics);
|
|
456
|
+
if (!policy.enableDurableRecall) {
|
|
457
|
+
diagnostics.abstentionReasons.push("Durable recall disabled by before-turn policy.");
|
|
458
|
+
} else if (durableMemory.length === 0) {
|
|
459
|
+
diagnostics.abstentionReasons.push("No durable memory entries cleared the before-turn threshold.");
|
|
460
|
+
}
|
|
461
|
+
if (!policy.enableProcedureSuggestion) {
|
|
462
|
+
diagnostics.abstentionReasons.push("Procedure suggestion disabled by before-turn policy.");
|
|
463
|
+
} else if (!procedure) {
|
|
464
|
+
diagnostics.abstentionReasons.push("No canonical procedure suggestion cleared the before-turn threshold.");
|
|
465
|
+
}
|
|
466
|
+
diagnostics.abstained = durableMemory.length === 0 && !procedure;
|
|
467
|
+
return {
|
|
468
|
+
durableMemory: assignRanks(durableMemory),
|
|
469
|
+
...procedure ? { procedure } : {},
|
|
470
|
+
diagnostics
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
async function runDurableRecallSelection(currentTurnText, queryPlan, sessionKey, policy, deps, diagnostics) {
|
|
474
|
+
diagnostics.durableRecallUsed = true;
|
|
475
|
+
const attemptedVariants = [];
|
|
476
|
+
const primaryResult = await runDurableRecallAttempt(currentTurnText, queryPlan.primary.query, sessionKey, policy, deps, diagnostics);
|
|
477
|
+
attemptedVariants.push({
|
|
478
|
+
kind: queryPlan.primary.kind,
|
|
479
|
+
query: queryPlan.primary.query,
|
|
480
|
+
candidateCount: primaryResult.candidateCount,
|
|
481
|
+
selected: primaryResult.items.length > 0
|
|
482
|
+
});
|
|
483
|
+
if (queryPlan.fallback === void 0 || primaryResult.items.length > 0 && !shouldRetryWeakPrimaryWithContext(primaryResult, policy)) {
|
|
484
|
+
diagnostics.query = queryPlan.primary.query;
|
|
485
|
+
diagnostics.queryPolicy = queryPlan.policy;
|
|
486
|
+
diagnostics.queryVariants = attemptedVariants;
|
|
487
|
+
diagnostics.durableRecallTrace = primaryResult.durableRecallTrace;
|
|
488
|
+
diagnostics.durableRecallCandidateCount = primaryResult.candidateCount;
|
|
489
|
+
diagnostics.directness = primaryResult.directness;
|
|
490
|
+
if (primaryResult.notices.length > 0) {
|
|
491
|
+
diagnostics.notices.push(...primaryResult.notices);
|
|
492
|
+
}
|
|
493
|
+
if (primaryResult.directness?.decision === "abstained") {
|
|
494
|
+
diagnostics.abstentionReasons.push(primaryResult.directness.reason);
|
|
495
|
+
}
|
|
496
|
+
return primaryResult.items;
|
|
497
|
+
}
|
|
498
|
+
const fallbackPlan = queryPlan.fallback;
|
|
499
|
+
const primaryItems = primaryResult.items;
|
|
500
|
+
const fallbackResult = await runDurableRecallAttempt(currentTurnText, fallbackPlan.query, sessionKey, policy, deps, diagnostics);
|
|
501
|
+
const shouldUseFallback = fallbackResult.items.length > 0;
|
|
502
|
+
attemptedVariants[0] = {
|
|
503
|
+
...attemptedVariants[0],
|
|
504
|
+
selected: primaryItems.length > 0 && !shouldUseFallback
|
|
505
|
+
};
|
|
506
|
+
attemptedVariants.push({
|
|
507
|
+
kind: fallbackPlan.kind,
|
|
508
|
+
query: fallbackPlan.query,
|
|
509
|
+
candidateCount: fallbackResult.candidateCount,
|
|
510
|
+
selected: shouldUseFallback
|
|
511
|
+
});
|
|
512
|
+
const selectedResult = shouldUseFallback ? fallbackResult : primaryResult;
|
|
513
|
+
const selectedQuery = shouldUseFallback ? fallbackPlan.query : queryPlan.primary.query;
|
|
514
|
+
const selectedPolicy = shouldUseFallback ? "contextual_fallback" : queryPlan.policy;
|
|
515
|
+
diagnostics.query = selectedQuery;
|
|
516
|
+
diagnostics.queryPolicy = selectedPolicy;
|
|
517
|
+
diagnostics.queryVariants = attemptedVariants;
|
|
518
|
+
diagnostics.durableRecallTrace = selectedResult.durableRecallTrace;
|
|
519
|
+
diagnostics.durableRecallCandidateCount = selectedResult.candidateCount;
|
|
520
|
+
diagnostics.directness = selectedResult.directness;
|
|
521
|
+
if (primaryResult.notices.length > 0) {
|
|
522
|
+
diagnostics.notices.push(...primaryResult.notices);
|
|
523
|
+
}
|
|
524
|
+
if (fallbackResult.notices.length > 0) {
|
|
525
|
+
diagnostics.notices.push(...fallbackResult.notices);
|
|
526
|
+
}
|
|
527
|
+
if (selectedResult.directness?.decision === "abstained") {
|
|
528
|
+
diagnostics.abstentionReasons.push(selectedResult.directness.reason);
|
|
529
|
+
}
|
|
530
|
+
return selectedResult.items;
|
|
531
|
+
}
|
|
532
|
+
function shouldRetryWeakPrimaryWithContext(primaryResult, policy) {
|
|
533
|
+
const topScore = primaryResult.items[0]?.score;
|
|
534
|
+
return typeof topScore === "number" && topScore < policy.highConfidenceRecallThreshold;
|
|
535
|
+
}
|
|
536
|
+
async function runDurableRecallAttempt(currentTurnText, query, sessionKey, policy, deps, diagnostics) {
|
|
537
|
+
const directnessQuery = detectEntityDefinitionTurn(currentTurnText);
|
|
538
|
+
const durableRecallLimit = directnessQuery ? Math.max(policy.maxDurableEntries, policy.maxHighConfidenceDurableEntries, ENTITY_DIRECTNESS_RECALL_CANDIDATE_LIMIT) : Math.max(policy.maxDurableEntries, policy.maxHighConfidenceDurableEntries);
|
|
539
|
+
let durableRecallTrace;
|
|
540
|
+
try {
|
|
541
|
+
const recalled = await recall(
|
|
542
|
+
{
|
|
543
|
+
text: query,
|
|
544
|
+
limit: durableRecallLimit,
|
|
545
|
+
threshold: policy.recallThreshold,
|
|
546
|
+
sessionKey
|
|
547
|
+
},
|
|
548
|
+
deps.recall,
|
|
549
|
+
{
|
|
550
|
+
trace: {
|
|
551
|
+
reportSummary(summary) {
|
|
552
|
+
durableRecallTrace = summary;
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
slotPolicyConfig: deps.slotPolicyConfig,
|
|
556
|
+
...deps.now ? { now: deps.now } : {}
|
|
557
|
+
}
|
|
558
|
+
);
|
|
559
|
+
const notices = durableRecallTrace?.degraded.notices.length ? [...durableRecallTrace.degraded.notices] : [];
|
|
560
|
+
const directnessSelection = applyDirectnessSelection(
|
|
561
|
+
currentTurnText,
|
|
562
|
+
recalled.map((item) => buildDurablePatchItem(item, deps))
|
|
563
|
+
);
|
|
564
|
+
return {
|
|
565
|
+
items: selectDurablePatchItems(directnessSelection.items, policy, diagnostics),
|
|
566
|
+
candidateCount: recalled.length,
|
|
567
|
+
durableRecallTrace,
|
|
568
|
+
directness: directnessSelection.diagnostics,
|
|
569
|
+
notices
|
|
570
|
+
};
|
|
571
|
+
} catch (error) {
|
|
572
|
+
return {
|
|
573
|
+
items: [],
|
|
574
|
+
candidateCount: 0,
|
|
575
|
+
durableRecallTrace,
|
|
576
|
+
notices: [`Before-turn durable recall failed: ${formatErrorMessage(error)}`]
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async function runProcedureSelection(query, policy, deps, diagnostics) {
|
|
581
|
+
diagnostics.procedureRecallUsed = true;
|
|
582
|
+
try {
|
|
583
|
+
const result = await runProcedureRecall(
|
|
584
|
+
{
|
|
585
|
+
text: query,
|
|
586
|
+
limit: policy.maxProcedureCandidates,
|
|
587
|
+
threshold: policy.procedureThreshold
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
db: deps.procedures,
|
|
591
|
+
...deps.embedQuery ? { embedQuery: deps.embedQuery } : {}
|
|
592
|
+
}
|
|
593
|
+
);
|
|
594
|
+
diagnostics.procedureCandidateCount = result.candidates.length;
|
|
595
|
+
if (result.notices.length > 0) {
|
|
596
|
+
diagnostics.notices.push(...result.notices);
|
|
597
|
+
}
|
|
598
|
+
const canonicalProcedure = result.canonicalProcedure;
|
|
599
|
+
if (!canonicalProcedure) {
|
|
600
|
+
return void 0;
|
|
601
|
+
}
|
|
602
|
+
const leader = result.candidates.find((candidate) => candidate.procedure.id === canonicalProcedure.id);
|
|
603
|
+
if (!leader) {
|
|
604
|
+
diagnostics.notices.push("Procedure recall returned a canonical procedure without a matching ranked candidate.");
|
|
605
|
+
return void 0;
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
procedure: canonicalProcedure,
|
|
609
|
+
score: leader.score,
|
|
610
|
+
scores: {
|
|
611
|
+
relevance: leader.scores.relevance,
|
|
612
|
+
lexical: leader.scores.lexical,
|
|
613
|
+
vector: leader.scores.vector
|
|
614
|
+
},
|
|
615
|
+
whySurfaced: {
|
|
616
|
+
summary: `canonical procedure match; score ${leader.score.toFixed(2)}`,
|
|
617
|
+
reasons: [
|
|
618
|
+
"canonical procedure match",
|
|
619
|
+
`score ${leader.score.toFixed(2)}`,
|
|
620
|
+
`lexical ${leader.scores.lexical.toFixed(2)}`,
|
|
621
|
+
`vector ${leader.scores.vector.toFixed(2)}`
|
|
622
|
+
]
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
} catch (error) {
|
|
626
|
+
diagnostics.notices.push(`Before-turn procedure recall failed: ${formatErrorMessage(error)}`);
|
|
627
|
+
return void 0;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function buildDurablePatchItem(recalled, deps) {
|
|
631
|
+
const projected = projectClaimCentricRecallEntry(recalled, {
|
|
632
|
+
slotPolicyConfig: deps.slotPolicyConfig
|
|
633
|
+
});
|
|
634
|
+
return {
|
|
635
|
+
rank: 0,
|
|
636
|
+
entry: recalled.entry,
|
|
637
|
+
sourceKind: "turn_recall",
|
|
638
|
+
score: recalled.score,
|
|
639
|
+
whySurfaced: projected.whySurfaced,
|
|
640
|
+
memoryState: projected.memoryState,
|
|
641
|
+
claimStatus: projected.claimStatus,
|
|
642
|
+
freshnessLabel: projected.freshness.label,
|
|
643
|
+
...formatProjectedProvenance(projected.provenance) ? { provenanceSummary: formatProjectedProvenance(projected.provenance) } : {}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function buildDurableRecallQueryPlan(currentTurnText, recentTurns, maxChars) {
|
|
647
|
+
const currentOnlyQuery = buildCurrentTurnOnlyQuery(currentTurnText, maxChars);
|
|
648
|
+
if (!currentOnlyQuery) {
|
|
649
|
+
return void 0;
|
|
650
|
+
}
|
|
651
|
+
const contextualQuery = buildContextualAnchorQuery(currentOnlyQuery, recentTurns, maxChars);
|
|
652
|
+
if (!contextualQuery) {
|
|
653
|
+
return {
|
|
654
|
+
policy: "current_only",
|
|
655
|
+
primary: {
|
|
656
|
+
kind: "current_only",
|
|
657
|
+
query: currentOnlyQuery
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
if (requiresContextualQuery(currentTurnText)) {
|
|
662
|
+
return {
|
|
663
|
+
policy: "contextual_required",
|
|
664
|
+
primary: {
|
|
665
|
+
kind: "contextual_anchor",
|
|
666
|
+
query: contextualQuery
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
if (shouldAllowContextualFallback(currentTurnText, recentTurns)) {
|
|
671
|
+
return {
|
|
672
|
+
policy: "current_only",
|
|
673
|
+
primary: {
|
|
674
|
+
kind: "current_only",
|
|
675
|
+
query: currentOnlyQuery
|
|
676
|
+
},
|
|
677
|
+
fallback: {
|
|
678
|
+
kind: "contextual_anchor",
|
|
679
|
+
query: contextualQuery
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
policy: "current_only",
|
|
685
|
+
primary: {
|
|
686
|
+
kind: "current_only",
|
|
687
|
+
query: currentOnlyQuery
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function buildCurrentTurnOnlyQuery(currentTurnText, maxChars) {
|
|
692
|
+
if (maxChars <= 0) {
|
|
693
|
+
return void 0;
|
|
694
|
+
}
|
|
695
|
+
const query = truncate2(normalizeWhitespace(currentTurnText), maxChars);
|
|
696
|
+
return query.length > 0 ? query : void 0;
|
|
697
|
+
}
|
|
698
|
+
function buildContextualAnchorQuery(currentTurnQuery, recentTurns, maxChars) {
|
|
699
|
+
const anchor = buildCompactContextAnchor(recentTurns);
|
|
700
|
+
if (!anchor || maxChars <= currentTurnQuery.length) {
|
|
701
|
+
return void 0;
|
|
702
|
+
}
|
|
703
|
+
const prefix = "Topic: ";
|
|
704
|
+
const separator = "\n";
|
|
705
|
+
const remaining = maxChars - currentTurnQuery.length - separator.length - prefix.length;
|
|
706
|
+
if (remaining <= 0) {
|
|
707
|
+
return void 0;
|
|
708
|
+
}
|
|
709
|
+
return `${currentTurnQuery}${separator}${prefix}${truncate2(anchor, remaining)}`;
|
|
710
|
+
}
|
|
711
|
+
function buildCompactContextAnchor(recentTurns) {
|
|
712
|
+
const recentTurn = recentTurns[recentTurns.length - 1];
|
|
713
|
+
if (!recentTurn) {
|
|
714
|
+
return void 0;
|
|
715
|
+
}
|
|
716
|
+
const normalized = normalizeWhitespace(recentTurn.text);
|
|
717
|
+
return normalized.length > 0 ? truncate2(normalized, MAX_CONTEXT_ANCHOR_CHARS) : void 0;
|
|
718
|
+
}
|
|
719
|
+
function requiresContextualQuery(currentTurnText) {
|
|
720
|
+
const normalizedTurn = normalizeWhitespace(currentTurnText);
|
|
721
|
+
const lowerTurn = normalizedTurn.toLowerCase();
|
|
722
|
+
const wordCount = normalizedTurn.split(/\s+/u).filter((token) => token.length > 0).length;
|
|
723
|
+
const hasContextReference = CONTEXT_REFERENCE_RE.test(lowerTurn);
|
|
724
|
+
if (HARD_CONTEXT_PREFIX_RE.test(lowerTurn) && (hasContextReference || lowerTurn.includes("other one"))) {
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
if (CONTEXT_QUESTION_PREFIX_RE.test(lowerTurn) && hasContextReference && wordCount <= 8) {
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
return hasContextReference && wordCount <= 6;
|
|
731
|
+
}
|
|
732
|
+
function shouldAllowContextualFallback(currentTurnText, recentTurns) {
|
|
733
|
+
if (recentTurns.length === 0 || requiresContextualQuery(currentTurnText)) {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
return SOFT_CONTEXT_FALLBACK_RE.test(normalizeWhitespace(currentTurnText).toLowerCase());
|
|
737
|
+
}
|
|
738
|
+
function buildProcedureQuery(currentTurnText, recentTurns, maxChars) {
|
|
739
|
+
const normalizedCurrentTurn = normalizeWhitespace(currentTurnText);
|
|
740
|
+
if (normalizedCurrentTurn.length > 0) {
|
|
741
|
+
return truncate2(normalizedCurrentTurn, maxChars);
|
|
742
|
+
}
|
|
743
|
+
const recentUserTurn = [...recentTurns].reverse().find((turn) => turn.role === "user");
|
|
744
|
+
return recentUserTurn ? truncate2(normalizeWhitespace(recentUserTurn.text), maxChars) : void 0;
|
|
745
|
+
}
|
|
746
|
+
function applyDirectnessSelection(currentTurnText, items) {
|
|
747
|
+
if (items.length === 0) {
|
|
748
|
+
return { items };
|
|
749
|
+
}
|
|
750
|
+
const queryMatch = detectEntityDefinitionTurn(currentTurnText);
|
|
751
|
+
if (!queryMatch) {
|
|
752
|
+
return { items };
|
|
753
|
+
}
|
|
754
|
+
const scoredCandidates = items.map((item, index) => scoreDirectnessCandidate(queryMatch, item, index + 1));
|
|
755
|
+
const rerankedCandidates = [...scoredCandidates].sort(compareDirectnessCandidates);
|
|
756
|
+
const winner = rerankedCandidates[0];
|
|
757
|
+
const runnerUp = rerankedCandidates[1];
|
|
758
|
+
const winnerGap = runnerUp ? winner.adjustedScore - runnerUp.adjustedScore : void 0;
|
|
759
|
+
const winnerHasPositiveIdentitySignal = hasPositiveIdentitySignal(winner);
|
|
760
|
+
const runnerUpHasPositiveIdentitySignal = runnerUp ? hasPositiveIdentitySignal(runnerUp) : false;
|
|
761
|
+
const winnerHasOnlyAdjacentSignals = winner.signals.includes("adjacent_relationship") && !winner.signals.includes("definitional_content") && !winner.signals.includes("subject_entity_match") && !winner.signals.includes("subject_identity_wrapper");
|
|
762
|
+
const requiresStrictStableGap = runnerUpHasPositiveIdentitySignal;
|
|
763
|
+
const winnerGapTooSmall = requiresStrictStableGap && runnerUp !== void 0 && winnerGap !== void 0 && winnerGap < DIRECTNESS_STABLE_GAP;
|
|
764
|
+
if (!winnerHasPositiveIdentitySignal || winnerHasOnlyAdjacentSignals || winnerGapTooSmall) {
|
|
765
|
+
const reason2 = !winnerHasPositiveIdentitySignal || winnerHasOnlyAdjacentSignals ? `Before-turn directness check abstained for "${queryMatch.entity}" because the top candidate looked adjacent rather than definitional.` : `Before-turn directness check abstained for "${queryMatch.entity}" because the top candidates remained too close after reranking.`;
|
|
766
|
+
return {
|
|
767
|
+
items: [],
|
|
768
|
+
diagnostics: buildDirectnessDiagnostics(queryMatch, "abstained", reason2, rerankedCandidates, winnerGap)
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
const decision = winner.baseRank === 1 ? "kept" : "reranked";
|
|
772
|
+
const reason = decision === "kept" ? `Before-turn directness check kept ${winner.item.entry.id} because it stayed the clearest definitional match for "${queryMatch.entity}".` : `Before-turn directness check reranked ${winner.item.entry.id} ahead of an adjacent match for "${queryMatch.entity}".`;
|
|
773
|
+
return {
|
|
774
|
+
items: [winner.item],
|
|
775
|
+
diagnostics: buildDirectnessDiagnostics(queryMatch, decision, reason, rerankedCandidates, winnerGap)
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function detectEntityDefinitionTurn(currentTurnText) {
|
|
779
|
+
const normalizedTurn = normalizeWhitespace(currentTurnText);
|
|
780
|
+
const patterns = [/^(?:who|what)\s+is\s+(.+?)(?:\s+again)?[?!.,]*$/iu, /^who'?s\s+(.+?)(?:\s+again)?[?!.,]*$/iu, /^tell\s+me\s+about\s+(.+?)[?!.,]*$/iu];
|
|
781
|
+
for (const pattern of patterns) {
|
|
782
|
+
const match = pattern.exec(normalizedTurn);
|
|
783
|
+
const candidateEntity = normalizeDirectnessEntity(match?.[1]);
|
|
784
|
+
if (candidateEntity) {
|
|
785
|
+
return {
|
|
786
|
+
kind: "entity_definition",
|
|
787
|
+
entity: candidateEntity,
|
|
788
|
+
normalizedEntity: normalizeDirectnessText(candidateEntity)
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return void 0;
|
|
793
|
+
}
|
|
794
|
+
function normalizeDirectnessEntity(entityText) {
|
|
795
|
+
const cleaned = entityText ? normalizeWhitespace(entityText).replace(/^[("'`]+/u, "").replace(/[)"'`?!.,]+$/u, "").replace(/^(?:the|a|an)\s+/iu, "").trim() : "";
|
|
796
|
+
if (cleaned.length === 0) {
|
|
797
|
+
return void 0;
|
|
798
|
+
}
|
|
799
|
+
const wordCount = cleaned.split(/\s+/u).filter((token) => token.length > 0).length;
|
|
800
|
+
const normalized = normalizeDirectnessText(cleaned);
|
|
801
|
+
if (wordCount === 0 || wordCount > ENTITY_DIRECTNESS_MAX_WORDS || CONTEXT_REFERENCE_RE.test(normalized) || normalized.includes("'s") || containsKeyword(normalized, DIRECTNESS_RELATIONSHIP_KEYWORDS)) {
|
|
802
|
+
return void 0;
|
|
803
|
+
}
|
|
804
|
+
return cleaned;
|
|
805
|
+
}
|
|
806
|
+
function scoreDirectnessCandidate(queryMatch, item, baseRank) {
|
|
807
|
+
const subject = normalizeDirectnessText(item.entry.subject);
|
|
808
|
+
const content = normalizeDirectnessText(item.entry.content);
|
|
809
|
+
const signals = [];
|
|
810
|
+
let directnessDelta = 0;
|
|
811
|
+
if (subject === queryMatch.normalizedEntity) {
|
|
812
|
+
signals.push("subject_entity_match");
|
|
813
|
+
directnessDelta += DIRECTNESS_SUBJECT_ENTITY_MATCH_BONUS;
|
|
814
|
+
} else if (isIdentityWrapperSubject(subject, queryMatch.normalizedEntity)) {
|
|
815
|
+
signals.push("subject_identity_wrapper");
|
|
816
|
+
directnessDelta += DIRECTNESS_SUBJECT_IDENTITY_WRAPPER_BONUS;
|
|
817
|
+
}
|
|
818
|
+
if (hasDefinitionalContent(content, queryMatch.normalizedEntity)) {
|
|
819
|
+
signals.push("definitional_content");
|
|
820
|
+
directnessDelta += DIRECTNESS_DEFINITIONAL_CONTENT_BONUS;
|
|
821
|
+
}
|
|
822
|
+
if (hasEntityClaimKey(item.entry.claim_key, queryMatch.normalizedEntity)) {
|
|
823
|
+
signals.push("claim_key_entity_match");
|
|
824
|
+
directnessDelta += DIRECTNESS_CLAIM_KEY_ENTITY_MATCH_BONUS;
|
|
825
|
+
}
|
|
826
|
+
if (looksLikeAdjacentRelationship(subject, content, queryMatch.normalizedEntity)) {
|
|
827
|
+
signals.push("adjacent_relationship");
|
|
828
|
+
directnessDelta -= DIRECTNESS_ADJACENT_RELATIONSHIP_PENALTY;
|
|
829
|
+
}
|
|
830
|
+
if (looksLikeListLore(subject, content)) {
|
|
831
|
+
signals.push("list_lore");
|
|
832
|
+
directnessDelta -= DIRECTNESS_LIST_LORE_PENALTY;
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
item,
|
|
836
|
+
baseRank,
|
|
837
|
+
baseScore: item.score,
|
|
838
|
+
directnessDelta,
|
|
839
|
+
adjustedScore: item.score + directnessDelta,
|
|
840
|
+
signals
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function buildDirectnessDiagnostics(queryMatch, decision, reason, candidates, winnerGap) {
|
|
844
|
+
const winner = decision === "abstained" ? void 0 : candidates[0];
|
|
845
|
+
const runnerUp = candidates[1];
|
|
846
|
+
return {
|
|
847
|
+
queryKind: queryMatch.kind,
|
|
848
|
+
entity: queryMatch.entity,
|
|
849
|
+
decision,
|
|
850
|
+
winnerEntryId: winner?.item.entry.id,
|
|
851
|
+
runnerUpEntryId: runnerUp?.item.entry.id,
|
|
852
|
+
...winnerGap !== void 0 ? { winnerGap: roundToThreeDecimals(winnerGap) } : {},
|
|
853
|
+
reason,
|
|
854
|
+
candidates: candidates.map((candidate) => ({
|
|
855
|
+
entryId: candidate.item.entry.id,
|
|
856
|
+
baseRank: candidate.baseRank,
|
|
857
|
+
baseScore: roundToThreeDecimals(candidate.baseScore),
|
|
858
|
+
directnessDelta: roundToThreeDecimals(candidate.directnessDelta),
|
|
859
|
+
adjustedScore: roundToThreeDecimals(candidate.adjustedScore),
|
|
860
|
+
signals: candidate.signals
|
|
861
|
+
}))
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function hasPositiveIdentitySignal(candidate) {
|
|
865
|
+
return candidate.signals.includes("definitional_content") || candidate.signals.includes("subject_entity_match") || candidate.signals.includes("subject_identity_wrapper");
|
|
866
|
+
}
|
|
867
|
+
function isIdentityWrapperSubject(subject, entity) {
|
|
868
|
+
for (const wrapper of DIRECTNESS_IDENTITY_WRAPPERS) {
|
|
869
|
+
if (subject === `${entity} ${wrapper}` || subject === `${wrapper} ${entity}`) {
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
function hasDefinitionalContent(content, entity) {
|
|
876
|
+
const escapedEntity = escapeRegExp2(entity);
|
|
877
|
+
if (startsWithBareRelationshipPredicate(content, escapedEntity)) {
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
const anchoredPatterns = [new RegExp(`^${escapedEntity}\\s+(?:is|was|means)\\b`, "u"), new RegExp(`^${escapedEntity}\\s+refers\\s+to\\b`, "u")];
|
|
881
|
+
if (anchoredPatterns.some((pattern) => pattern.test(content))) {
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
return hasEmbeddedDefinitionalContent(content, escapedEntity);
|
|
885
|
+
}
|
|
886
|
+
function hasEmbeddedDefinitionalContent(content, escapedEntity) {
|
|
887
|
+
const embeddedLead = `(?:^|[.!?;:]\\s+)${escapedEntity}\\s+(?:is|was)\\s+`;
|
|
888
|
+
const fullNameLead = `(?:^|[.!?;:]\\s+)${escapedEntity}(?:\\s+[\\p{L}\\p{N}]+){1,2}\\s+(?:is|was)\\s+`;
|
|
889
|
+
if (startsWithBareRelationshipPredicate(content, escapedEntity)) {
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
const patterns = [
|
|
893
|
+
new RegExp(`${embeddedLead}(?:a|an|the)\\b`, "u"),
|
|
894
|
+
new RegExp(`${embeddedLead}[\\p{L}\\p{N}]+(?:['\u2019]s)\\b`, "u"),
|
|
895
|
+
// Allow short-name queries like "who is John?" to match a leading
|
|
896
|
+
// full-name clause such as "John Doe is married to Beverly".
|
|
897
|
+
new RegExp(`${fullNameLead}(?:a|an|the)\\b`, "u"),
|
|
898
|
+
new RegExp(`${fullNameLead}[\\p{L}\\p{N}]+\\b`, "u")
|
|
899
|
+
];
|
|
900
|
+
return patterns.some((pattern) => pattern.test(content));
|
|
901
|
+
}
|
|
902
|
+
function startsWithBareRelationshipPredicate(content, escapedEntity) {
|
|
903
|
+
const relationshipAlternation = Array.from(DIRECTNESS_RELATIONSHIP_KEYWORDS).join("|");
|
|
904
|
+
const embeddedLead = `(?:^|[.!?;:]\\s+)${escapedEntity}\\s+(?:is|was)\\s+`;
|
|
905
|
+
return new RegExp(`${embeddedLead}(?:${relationshipAlternation})\\b`, "u").test(content);
|
|
906
|
+
}
|
|
907
|
+
function looksLikeAdjacentRelationship(subject, content, entity) {
|
|
908
|
+
if (!subject.includes(entity) && !content.includes(entity)) {
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
return containsKeyword(subject, DIRECTNESS_RELATIONSHIP_KEYWORDS) || containsKeyword(content, DIRECTNESS_RELATIONSHIP_KEYWORDS);
|
|
912
|
+
}
|
|
913
|
+
function looksLikeListLore(subject, content) {
|
|
914
|
+
return containsKeyword(subject, DIRECTNESS_LIST_LORE_KEYWORDS) || containsKeyword(content, DIRECTNESS_LIST_LORE_KEYWORDS);
|
|
915
|
+
}
|
|
916
|
+
function compareDirectnessCandidates(left, right) {
|
|
917
|
+
if (right.adjustedScore !== left.adjustedScore) {
|
|
918
|
+
return right.adjustedScore - left.adjustedScore;
|
|
919
|
+
}
|
|
920
|
+
if (right.baseScore !== left.baseScore) {
|
|
921
|
+
return right.baseScore - left.baseScore;
|
|
922
|
+
}
|
|
923
|
+
return left.baseRank - right.baseRank;
|
|
924
|
+
}
|
|
925
|
+
function normalizeDirectnessText(value) {
|
|
926
|
+
return normalizeWhitespace(value).toLocaleLowerCase();
|
|
927
|
+
}
|
|
928
|
+
function hasEntityClaimKey(claimKey, entity) {
|
|
929
|
+
const head = claimKey?.split("/", 1)[0]?.replace(/[-_]+/g, " ");
|
|
930
|
+
return normalizeDirectnessText(head ?? "") === entity;
|
|
931
|
+
}
|
|
932
|
+
function containsKeyword(text, keywords) {
|
|
933
|
+
const tokens = new Set(text.match(/[\p{L}\p{N}]+/gu) ?? []);
|
|
934
|
+
for (const keyword of keywords) {
|
|
935
|
+
if (tokens.has(keyword)) {
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
function escapeRegExp2(value) {
|
|
942
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
943
|
+
}
|
|
944
|
+
function roundToThreeDecimals(value) {
|
|
945
|
+
return Math.round(value * 1e3) / 1e3;
|
|
946
|
+
}
|
|
947
|
+
function inspectTurnSignal(currentTurnText) {
|
|
948
|
+
const normalizedTurn = normalizeWhitespace(currentTurnText);
|
|
949
|
+
const lowerTurn = normalizedTurn.toLowerCase();
|
|
950
|
+
const signalLabels = collectTurnSignalLabels(lowerTurn);
|
|
951
|
+
const wordCount = normalizedTurn.split(/\s+/u).filter((token) => token.length > 0).length;
|
|
952
|
+
const isShortTurn = wordCount <= SHORT_TURN_MAX_WORDS || normalizedTurn.length <= SHORT_TURN_MAX_CHARS;
|
|
953
|
+
if (signalLabels.length === 0 && (SOCIAL_TURN_RE.test(lowerTurn) || isShortTurn)) {
|
|
954
|
+
return {
|
|
955
|
+
signalLabels,
|
|
956
|
+
suppressedTurnCategory: "short_social",
|
|
957
|
+
reason: "Current turn was short or social without clear factual, procedural, or task intent."
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
if (signalLabels.length === 0) {
|
|
961
|
+
return {
|
|
962
|
+
signalLabels,
|
|
963
|
+
suppressedTurnCategory: "low_signal",
|
|
964
|
+
reason: "Current turn lacked clear factual, procedural, or task signal, so before-turn recall abstained."
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
signalLabels,
|
|
969
|
+
reason: `Current turn showed ${signalLabels.join(", ")} recall signal.`
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
function collectTurnSignalLabels(lowerTurn) {
|
|
973
|
+
const labels = [];
|
|
974
|
+
if (TASK_SIGNAL_RE.test(lowerTurn)) {
|
|
975
|
+
labels.push("task");
|
|
976
|
+
}
|
|
977
|
+
if (FACTUAL_SIGNAL_RE.test(lowerTurn)) {
|
|
978
|
+
labels.push("factual");
|
|
979
|
+
}
|
|
980
|
+
if (PROCEDURAL_SIGNAL_RE.test(lowerTurn)) {
|
|
981
|
+
labels.push("procedural");
|
|
982
|
+
}
|
|
983
|
+
return labels;
|
|
984
|
+
}
|
|
985
|
+
function assignRanks(items) {
|
|
986
|
+
return items.map((item, index) => ({
|
|
987
|
+
...item,
|
|
988
|
+
rank: index + 1
|
|
989
|
+
}));
|
|
990
|
+
}
|
|
991
|
+
function normalizePolicy(policy) {
|
|
992
|
+
const maxDurableEntries = normalizeCount(policy?.maxDurableEntries, DEFAULT_MAX_DURABLE_ENTRIES);
|
|
993
|
+
const maxHighConfidenceDurableEntries = Math.max(
|
|
994
|
+
maxDurableEntries,
|
|
995
|
+
normalizeCount(policy?.maxHighConfidenceDurableEntries, DEFAULT_MAX_HIGH_CONFIDENCE_DURABLE_ENTRIES)
|
|
996
|
+
);
|
|
997
|
+
return {
|
|
998
|
+
enableDurableRecall: policy?.enableDurableRecall !== false,
|
|
999
|
+
enableProcedureSuggestion: policy?.enableProcedureSuggestion !== false,
|
|
1000
|
+
maxRecentTurns: normalizeCount(policy?.maxRecentTurns, DEFAULT_MAX_RECENT_TURNS),
|
|
1001
|
+
maxQueryChars: normalizeCount(policy?.maxQueryChars, DEFAULT_MAX_QUERY_CHARS),
|
|
1002
|
+
maxDurableEntries,
|
|
1003
|
+
maxHighConfidenceDurableEntries,
|
|
1004
|
+
maxProcedureCandidates: normalizeCount(policy?.maxProcedureCandidates, DEFAULT_MAX_PROCEDURE_CANDIDATES),
|
|
1005
|
+
recallThreshold: normalizeThreshold(policy?.recallThreshold, DEFAULT_RECALL_THRESHOLD),
|
|
1006
|
+
highConfidenceRecallThreshold: normalizeThreshold(policy?.highConfidenceRecallThreshold, DEFAULT_HIGH_CONFIDENCE_RECALL_THRESHOLD),
|
|
1007
|
+
procedureThreshold: normalizeThreshold(policy?.procedureThreshold, DEFAULT_PROCEDURE_THRESHOLD),
|
|
1008
|
+
skipTrivialTurns: policy?.skipTrivialTurns ?? DEFAULT_SKIP_TRIVIAL_TURNS,
|
|
1009
|
+
requireTurnSignal: policy?.requireTurnSignal ?? DEFAULT_REQUIRE_TURN_SIGNAL
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
function normalizeCount(value, fallback) {
|
|
1013
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1014
|
+
return fallback;
|
|
1015
|
+
}
|
|
1016
|
+
return Math.max(0, Math.trunc(value));
|
|
1017
|
+
}
|
|
1018
|
+
function normalizeThreshold(value, fallback) {
|
|
1019
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1020
|
+
return fallback;
|
|
1021
|
+
}
|
|
1022
|
+
return Math.min(1, Math.max(0, value));
|
|
1023
|
+
}
|
|
1024
|
+
function normalizeRecentTurns(recentTurns, maxRecentTurns, currentTurnText) {
|
|
1025
|
+
if (!recentTurns || recentTurns.length === 0 || maxRecentTurns <= 0) {
|
|
1026
|
+
return [];
|
|
1027
|
+
}
|
|
1028
|
+
const normalizedTurns = recentTurns.flatMap((turn) => {
|
|
1029
|
+
if (turn.role !== "user" && turn.role !== "assistant") {
|
|
1030
|
+
return [];
|
|
1031
|
+
}
|
|
1032
|
+
const text = normalizeOptionalString(turn.text);
|
|
1033
|
+
return text ? [{ role: turn.role, text }] : [];
|
|
1034
|
+
});
|
|
1035
|
+
const currentTurnFingerprint = currentTurnText ? normalizeWhitespace(currentTurnText).toLowerCase() : void 0;
|
|
1036
|
+
const deduped = [...normalizedTurns];
|
|
1037
|
+
while (deduped.length > 0 && currentTurnFingerprint) {
|
|
1038
|
+
const last = deduped[deduped.length - 1];
|
|
1039
|
+
if (!last || last.role !== "user") {
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
if (normalizeWhitespace(last.text).toLowerCase() !== currentTurnFingerprint) {
|
|
1043
|
+
break;
|
|
1044
|
+
}
|
|
1045
|
+
deduped.pop();
|
|
1046
|
+
}
|
|
1047
|
+
return deduped.slice(-maxRecentTurns);
|
|
1048
|
+
}
|
|
1049
|
+
function normalizeOptionalString(value) {
|
|
1050
|
+
const normalized = value?.trim();
|
|
1051
|
+
return normalized && normalized.length > 0 ? normalized : void 0;
|
|
1052
|
+
}
|
|
1053
|
+
function normalizeWhitespace(value) {
|
|
1054
|
+
return value.replace(/\s+/g, " ").trim();
|
|
1055
|
+
}
|
|
1056
|
+
function truncate2(value, maxChars) {
|
|
1057
|
+
if (maxChars <= 0) {
|
|
1058
|
+
return "";
|
|
1059
|
+
}
|
|
1060
|
+
if (value.length <= maxChars) {
|
|
1061
|
+
return value;
|
|
1062
|
+
}
|
|
1063
|
+
return `${value.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
1064
|
+
}
|
|
1065
|
+
function formatErrorMessage(error) {
|
|
1066
|
+
if (error instanceof Error) {
|
|
1067
|
+
return error.message;
|
|
1068
|
+
}
|
|
1069
|
+
return String(error);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// src/adapters/shared/injection/entry-lines.ts
|
|
1073
|
+
var MAX_CONTENT_CHARS = 220;
|
|
1074
|
+
function formatInjectionEntryHeader(item) {
|
|
1075
|
+
const metadata = [
|
|
1076
|
+
`rank ${item.rank}`,
|
|
1077
|
+
item.entry.id,
|
|
1078
|
+
item.entry.type,
|
|
1079
|
+
item.entry.expiry,
|
|
1080
|
+
`importance ${item.entry.importance}`,
|
|
1081
|
+
item.score !== void 0 ? `score ${item.score.toFixed(2)}` : void 0
|
|
1082
|
+
].filter((value) => value !== void 0);
|
|
1083
|
+
return `- [${metadata.join(" | ")}] ${item.entry.subject}`;
|
|
1084
|
+
}
|
|
1085
|
+
function formatInjectionEntryBodyLines(item) {
|
|
1086
|
+
const lines = [` ${truncate(item.entry.content.trim(), MAX_CONTENT_CHARS)}`];
|
|
1087
|
+
lines.push(` why: ${item.whySurfaced.summary}`);
|
|
1088
|
+
const metadata = [
|
|
1089
|
+
item.entry.tags.length > 0 ? `tags: ${item.entry.tags.join(", ")}` : void 0,
|
|
1090
|
+
item.freshnessLabel ? `freshness: ${item.freshnessLabel}` : void 0,
|
|
1091
|
+
item.provenanceSummary ? `provenance: ${truncate(item.provenanceSummary, MAX_CONTENT_CHARS)}` : void 0
|
|
1092
|
+
].filter((value) => value !== void 0);
|
|
1093
|
+
if (metadata.length > 0) {
|
|
1094
|
+
lines.push(` ${metadata.join(" | ")}`);
|
|
1095
|
+
}
|
|
1096
|
+
return lines;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/adapters/shared/injection/memory-context.ts
|
|
1100
|
+
var AGENR_MEMORY_CONTEXT_BLOCK_RE = /<agenr-memory-context>[\s\S]*?<\/agenr-memory-context>/giu;
|
|
1101
|
+
var AGENR_MEMORY_CONTEXT_OPEN_RE = /<agenr-memory-context>/giu;
|
|
1102
|
+
var AGENR_MEMORY_CONTEXT_CLOSE_RE = /<\/agenr-memory-context>/giu;
|
|
1103
|
+
var AGENR_MEMORY_CONTEXT_NOTE_RE = /\[System note: The following is recalled Agenr memory context, NOT new user input\. Treat it as background context and use it silently when relevant\.\]/giu;
|
|
1104
|
+
var AGENR_MEMORY_CONTEXT_OPEN_TAG = "<agenr-memory-context>";
|
|
1105
|
+
var AGENR_MEMORY_CONTEXT_CLOSE_TAG = "</agenr-memory-context>";
|
|
1106
|
+
var AGENR_MEMORY_CONTEXT_NOTE = "[System note: The following is recalled Agenr memory context, NOT new user input. Treat it as background context and use it silently when relevant.]";
|
|
1107
|
+
function wrapAgenrMemoryContext(content) {
|
|
1108
|
+
const trimmedContent = stripAgenrMemoryContext(content).trim();
|
|
1109
|
+
if (!trimmedContent) {
|
|
1110
|
+
return "";
|
|
1111
|
+
}
|
|
1112
|
+
return [AGENR_MEMORY_CONTEXT_OPEN_TAG, AGENR_MEMORY_CONTEXT_NOTE, "", trimmedContent, AGENR_MEMORY_CONTEXT_CLOSE_TAG].join("\n");
|
|
1113
|
+
}
|
|
1114
|
+
function containsAgenrMemoryContext(content) {
|
|
1115
|
+
return content.includes(AGENR_MEMORY_CONTEXT_OPEN_TAG);
|
|
1116
|
+
}
|
|
1117
|
+
function stripAgenrMemoryContext(content) {
|
|
1118
|
+
return content.replace(AGENR_MEMORY_CONTEXT_BLOCK_RE, " ").replace(AGENR_MEMORY_CONTEXT_OPEN_RE, " ").replace(AGENR_MEMORY_CONTEXT_CLOSE_RE, " ").replace(AGENR_MEMORY_CONTEXT_NOTE_RE, " ");
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/adapters/shared/injection/before-turn-format.ts
|
|
1122
|
+
var MAX_CONTENT_CHARS2 = 220;
|
|
1123
|
+
function formatAgenrBeforeTurnRecall(patch) {
|
|
1124
|
+
if (patch.durableMemory.length === 0 && !patch.procedure) {
|
|
1125
|
+
return "";
|
|
1126
|
+
}
|
|
1127
|
+
const lines = [
|
|
1128
|
+
"## Agenr Before-Turn Recall",
|
|
1129
|
+
"Use this only when it materially helps the current turn. Treat it as background context and prefer the live conversation if anything conflicts with it.",
|
|
1130
|
+
""
|
|
1131
|
+
];
|
|
1132
|
+
if (patch.durableMemory.length > 0) {
|
|
1133
|
+
lines.push("### Relevant Durable Memory");
|
|
1134
|
+
for (const item of patch.durableMemory) {
|
|
1135
|
+
lines.push(formatInjectionEntryHeader(item));
|
|
1136
|
+
lines.push(...formatInjectionEntryBodyLines(item));
|
|
1137
|
+
}
|
|
1138
|
+
lines.push("");
|
|
1139
|
+
}
|
|
1140
|
+
if (patch.procedure) {
|
|
1141
|
+
lines.push("### Suggested Procedure");
|
|
1142
|
+
lines.push(formatProcedureHeader(patch.procedure));
|
|
1143
|
+
lines.push(...formatProcedureBodyLines(patch.procedure));
|
|
1144
|
+
lines.push("");
|
|
1145
|
+
}
|
|
1146
|
+
return wrapAgenrMemoryContext(lines.join("\n").trim());
|
|
1147
|
+
}
|
|
1148
|
+
function formatProcedureHeader(suggestion) {
|
|
1149
|
+
const metadata = [
|
|
1150
|
+
suggestion.procedure.id,
|
|
1151
|
+
suggestion.procedure.procedure_key,
|
|
1152
|
+
`score ${suggestion.score.toFixed(2)}`,
|
|
1153
|
+
`lexical ${suggestion.scores.lexical.toFixed(2)}`,
|
|
1154
|
+
`vector ${suggestion.scores.vector.toFixed(2)}`
|
|
1155
|
+
];
|
|
1156
|
+
return `- [${metadata.join(" | ")}] ${suggestion.procedure.title}`;
|
|
1157
|
+
}
|
|
1158
|
+
function formatProcedureBodyLines(suggestion) {
|
|
1159
|
+
const lines = [` goal: ${truncate(suggestion.procedure.goal.trim(), MAX_CONTENT_CHARS2)}`];
|
|
1160
|
+
lines.push(` why: ${suggestion.whySurfaced.summary}`);
|
|
1161
|
+
if (suggestion.procedure.when_to_use.length > 0) {
|
|
1162
|
+
lines.push(` when to use: ${truncate(suggestion.procedure.when_to_use.join("; "), MAX_CONTENT_CHARS2)}`);
|
|
1163
|
+
}
|
|
1164
|
+
if (suggestion.procedure.verification.length > 0) {
|
|
1165
|
+
lines.push(` verification: ${truncate(suggestion.procedure.verification.join("; "), MAX_CONTENT_CHARS2)}`);
|
|
1166
|
+
}
|
|
1167
|
+
return lines;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// src/app/session-memory/predecessor-artifacts.ts
|
|
1171
|
+
var SESSION_START_ARTIFACT_KINDS = [
|
|
1172
|
+
"compaction_checkpoint",
|
|
1173
|
+
"branch_abandonment",
|
|
1174
|
+
"session_episode"
|
|
1175
|
+
];
|
|
1176
|
+
async function resolvePredecessorSessionArtifacts(input, repository) {
|
|
1177
|
+
const sessionKey = input.sessionKey.trim();
|
|
1178
|
+
if (!sessionKey) {
|
|
1179
|
+
return {
|
|
1180
|
+
artifacts: []
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
const lineageEdge = await repository.getLatestLineageEdgeForChild(sessionKey);
|
|
1184
|
+
const predecessorSessionKey = lineageEdge?.parentSessionKey?.trim();
|
|
1185
|
+
const parentSourceRef = lineageEdge?.parentSourceRef?.trim();
|
|
1186
|
+
if (!predecessorSessionKey && !parentSourceRef) {
|
|
1187
|
+
return {
|
|
1188
|
+
...lineageEdge ? { lineageEdge } : {},
|
|
1189
|
+
artifacts: []
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
const artifacts = predecessorSessionKey ? await repository.listSessionArtifacts({
|
|
1193
|
+
sessionKey: predecessorSessionKey,
|
|
1194
|
+
kinds: [...SESSION_START_ARTIFACT_KINDS],
|
|
1195
|
+
limit: 10
|
|
1196
|
+
}) : await repository.listSessionArtifactsBySourceRef({
|
|
1197
|
+
sourceRef: parentSourceRef,
|
|
1198
|
+
kinds: [...SESSION_START_ARTIFACT_KINDS],
|
|
1199
|
+
limit: 10
|
|
1200
|
+
});
|
|
1201
|
+
return {
|
|
1202
|
+
...lineageEdge ? { lineageEdge } : {},
|
|
1203
|
+
artifacts
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// src/app/session-start/artifact-recall-query.ts
|
|
1208
|
+
var ARTIFACT_QUERY_TITLES = {
|
|
1209
|
+
compaction_checkpoint: "Compaction checkpoint",
|
|
1210
|
+
branch_abandonment: "Branch abandonment",
|
|
1211
|
+
session_episode: "Session episode"
|
|
1212
|
+
};
|
|
1213
|
+
var SESSION_START_QUERY_ARTIFACT_ORDER = new Map(SESSION_START_ARTIFACT_KINDS.map((kind, index) => [kind, index]));
|
|
1214
|
+
function buildSessionStartArtifactRecallQuery(artifacts, maxChars) {
|
|
1215
|
+
if (artifacts.length === 0 || maxChars <= 0) {
|
|
1216
|
+
return void 0;
|
|
1217
|
+
}
|
|
1218
|
+
const orderedArtifacts = [...artifacts].sort(compareSessionStartArtifacts);
|
|
1219
|
+
let remaining = maxChars;
|
|
1220
|
+
const parts = [];
|
|
1221
|
+
for (const artifact of orderedArtifacts) {
|
|
1222
|
+
if (remaining <= 0) {
|
|
1223
|
+
break;
|
|
1224
|
+
}
|
|
1225
|
+
const title = ARTIFACT_QUERY_TITLES[artifact.kind];
|
|
1226
|
+
if (!title) {
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
const normalizedContent = normalizeWhitespace2(artifact.summary);
|
|
1230
|
+
if (normalizedContent.length === 0) {
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
const labeled = `${title}: ${normalizedContent}`;
|
|
1234
|
+
const truncated = truncate3(labeled, remaining);
|
|
1235
|
+
if (truncated.length === 0) {
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
parts.push(truncated);
|
|
1239
|
+
remaining -= truncated.length;
|
|
1240
|
+
}
|
|
1241
|
+
const query = normalizeWhitespace2(parts.join("\n"));
|
|
1242
|
+
return query.length > 0 ? query : void 0;
|
|
1243
|
+
}
|
|
1244
|
+
function compareSessionStartArtifacts(left, right) {
|
|
1245
|
+
const leftOrder = SESSION_START_QUERY_ARTIFACT_ORDER.get(left.kind) ?? Number.MAX_SAFE_INTEGER;
|
|
1246
|
+
const rightOrder = SESSION_START_QUERY_ARTIFACT_ORDER.get(right.kind) ?? Number.MAX_SAFE_INTEGER;
|
|
1247
|
+
if (leftOrder !== rightOrder) {
|
|
1248
|
+
return leftOrder - rightOrder;
|
|
1249
|
+
}
|
|
1250
|
+
return right.createdAt.localeCompare(left.createdAt);
|
|
1251
|
+
}
|
|
1252
|
+
function normalizeWhitespace2(value) {
|
|
1253
|
+
return value.replace(/\s+/g, " ").trim();
|
|
1254
|
+
}
|
|
1255
|
+
function truncate3(value, maxChars) {
|
|
1256
|
+
if (maxChars <= 0) {
|
|
1257
|
+
return "";
|
|
1258
|
+
}
|
|
1259
|
+
if (value.length <= maxChars) {
|
|
1260
|
+
return value;
|
|
1261
|
+
}
|
|
1262
|
+
return `${value.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// src/app/session-start/service.ts
|
|
1266
|
+
var DEFAULT_MAX_CORE_ENTRIES = 4;
|
|
1267
|
+
var DEFAULT_MAX_ARTIFACT_RECALL_ENTRIES = 3;
|
|
1268
|
+
var DEFAULT_MAX_DURABLE_ENTRIES2 = 5;
|
|
1269
|
+
var DEFAULT_MAX_ARTIFACT_CHARS = 1200;
|
|
1270
|
+
var DEFAULT_MAX_PROFILE_SNAPSHOT_AGE_HOURS = 48;
|
|
1271
|
+
async function runSessionStart(input, deps) {
|
|
1272
|
+
const policy = normalizePolicy2(input.policy);
|
|
1273
|
+
const now = deps.now ?? /* @__PURE__ */ new Date();
|
|
1274
|
+
const nowMs = now.getTime();
|
|
1275
|
+
const profileSnapshot = await deps.repository.getActiveProfileSnapshot(policy.maxProfileSnapshotAgeHours * 60 * 60 * 1e3, now);
|
|
1276
|
+
const profileEntries = profileSnapshot ? filterCurrentEntries2(await deps.repository.listEntriesByIds(profileSnapshot.durableIds), nowMs) : [];
|
|
1277
|
+
const profileItems = profileEntries.map((entry) => buildProfilePatchItem(entry, profileSnapshot?.id ?? "unknown", nowMs));
|
|
1278
|
+
const proactiveDirectiveEntries = filterCurrentEntries2(await deps.listActiveProactiveDirectives?.() ?? [], nowMs);
|
|
1279
|
+
const proactiveDirectiveItems = proactiveDirectiveEntries.map((entry) => buildDirectivePatchItem(entry, nowMs));
|
|
1280
|
+
const coreEntries = await deps.repository.listCoreEntries(policy.maxCoreEntries, now);
|
|
1281
|
+
const coreItems = coreEntries.map((entry) => buildCorePatchItem(entry, nowMs));
|
|
1282
|
+
const diagnostics = {
|
|
1283
|
+
coreCandidateCount: coreEntries.length,
|
|
1284
|
+
profileCandidateCount: profileEntries.length,
|
|
1285
|
+
...profileSnapshot ? { activeProfileSnapshotId: profileSnapshot.id } : {},
|
|
1286
|
+
proactiveDirectiveCandidateCount: proactiveDirectiveEntries.length,
|
|
1287
|
+
artifactRecallCandidateCount: 0,
|
|
1288
|
+
artifactRecallUsed: false,
|
|
1289
|
+
notices: []
|
|
1290
|
+
};
|
|
1291
|
+
const artifactRecallQuery = await resolveSessionStartArtifactRecallQuery(input.sessionKey, policy, deps);
|
|
1292
|
+
if (!policy.enableArtifactRecall) {
|
|
1293
|
+
diagnostics.notices.push("Artifact-grounded durable recall disabled by session-start policy.");
|
|
1294
|
+
}
|
|
1295
|
+
const artifactRecallItems = artifactRecallQuery ? await runArtifactRecallSelection(artifactRecallQuery, input.sessionKey, policy, deps, diagnostics) : [];
|
|
1296
|
+
const mergedDurableMemory = mergeDurableMemory(profileItems, proactiveDirectiveItems, coreItems, artifactRecallItems, policy.maxDurableEntries);
|
|
1297
|
+
const visibleDurableMemory = await applyAbstainDirectivesForInjection(mergedDurableMemory, deps.listActiveAbstainDirectives, diagnostics);
|
|
1298
|
+
const durableMemory = assignRanks2(visibleDurableMemory);
|
|
1299
|
+
return {
|
|
1300
|
+
durableMemory,
|
|
1301
|
+
diagnostics
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
async function resolveSessionStartArtifactRecallQuery(sessionKey, policy, deps) {
|
|
1305
|
+
if (!policy.enableArtifactRecall) {
|
|
1306
|
+
return void 0;
|
|
1307
|
+
}
|
|
1308
|
+
const normalizedSessionKey = sessionKey?.trim();
|
|
1309
|
+
if (!normalizedSessionKey || !deps.sessionMemoryRepository) {
|
|
1310
|
+
return void 0;
|
|
1311
|
+
}
|
|
1312
|
+
const predecessor = await resolvePredecessorSessionArtifacts({ sessionKey: normalizedSessionKey }, deps.sessionMemoryRepository);
|
|
1313
|
+
return buildSessionStartArtifactRecallQuery(predecessor.artifacts, policy.maxArtifactChars);
|
|
1314
|
+
}
|
|
1315
|
+
function buildProfilePatchItem(entry, snapshotId, nowMs) {
|
|
1316
|
+
return {
|
|
1317
|
+
rank: 0,
|
|
1318
|
+
entry,
|
|
1319
|
+
sourceKind: "profile",
|
|
1320
|
+
whySurfaced: {
|
|
1321
|
+
summary: `active profile snapshot ${snapshotId}`,
|
|
1322
|
+
reasons: ["active profile snapshot", `snapshot ${snapshotId}`, `importance ${entry.importance}`]
|
|
1323
|
+
},
|
|
1324
|
+
memoryState: resolveMemoryState(entry, nowMs),
|
|
1325
|
+
claimStatus: resolveClaimStatus(entry),
|
|
1326
|
+
freshnessLabel: buildFreshnessLabel(entry),
|
|
1327
|
+
...buildProvenanceSummary(entry) ? { provenanceSummary: buildProvenanceSummary(entry) } : {}
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function buildDirectivePatchItem(entry, nowMs) {
|
|
1331
|
+
const metadata = parseDirectiveMetadata(entry);
|
|
1332
|
+
return {
|
|
1333
|
+
rank: 0,
|
|
1334
|
+
entry,
|
|
1335
|
+
sourceKind: "directive",
|
|
1336
|
+
whySurfaced: {
|
|
1337
|
+
summary: `proactive memory directive; trigger ${metadata?.trigger ?? "session_start"}`,
|
|
1338
|
+
reasons: ["proactive memory directive", `trigger ${metadata?.trigger ?? "session_start"}`, `importance ${entry.importance}`]
|
|
1339
|
+
},
|
|
1340
|
+
memoryState: resolveMemoryState(entry, nowMs),
|
|
1341
|
+
claimStatus: resolveClaimStatus(entry),
|
|
1342
|
+
freshnessLabel: buildFreshnessLabel(entry),
|
|
1343
|
+
...buildProvenanceSummary(entry) ? { provenanceSummary: buildProvenanceSummary(entry) } : {}
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
async function runArtifactRecallSelection(query, sessionKey, policy, deps, diagnostics) {
|
|
1347
|
+
diagnostics.artifactRecallUsed = true;
|
|
1348
|
+
diagnostics.artifactRecallQuery = query;
|
|
1349
|
+
let artifactRecallTrace;
|
|
1350
|
+
try {
|
|
1351
|
+
const recalled = await recall(
|
|
1352
|
+
{
|
|
1353
|
+
text: query,
|
|
1354
|
+
limit: policy.maxArtifactRecallEntries,
|
|
1355
|
+
threshold: policy.recallThreshold,
|
|
1356
|
+
sessionKey
|
|
1357
|
+
},
|
|
1358
|
+
deps.recall,
|
|
1359
|
+
{
|
|
1360
|
+
trace: {
|
|
1361
|
+
reportSummary(summary) {
|
|
1362
|
+
artifactRecallTrace = summary;
|
|
1363
|
+
}
|
|
1364
|
+
},
|
|
1365
|
+
slotPolicyConfig: deps.slotPolicyConfig,
|
|
1366
|
+
...deps.now ? { now: deps.now } : {}
|
|
1367
|
+
}
|
|
1368
|
+
);
|
|
1369
|
+
diagnostics.artifactRecallTrace = artifactRecallTrace;
|
|
1370
|
+
diagnostics.artifactRecallCandidateCount = recalled.length;
|
|
1371
|
+
if (artifactRecallTrace?.degraded.notices.length) {
|
|
1372
|
+
diagnostics.notices.push(...artifactRecallTrace.degraded.notices);
|
|
1373
|
+
}
|
|
1374
|
+
return recalled.map((item) => buildArtifactRecallPatchItem(item, deps));
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
diagnostics.artifactRecallTrace = artifactRecallTrace;
|
|
1377
|
+
diagnostics.notices.push(`Artifact-grounded durable recall failed: ${formatErrorMessage2(error)}`);
|
|
1378
|
+
return [];
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
function buildCorePatchItem(entry, nowMs) {
|
|
1382
|
+
return {
|
|
1383
|
+
rank: 0,
|
|
1384
|
+
entry,
|
|
1385
|
+
sourceKind: "core",
|
|
1386
|
+
whySurfaced: {
|
|
1387
|
+
summary: `always-on core memory; importance ${entry.importance}`,
|
|
1388
|
+
reasons: ["always-on core memory", `importance ${entry.importance}`, `expiry ${entry.expiry}`]
|
|
1389
|
+
},
|
|
1390
|
+
memoryState: resolveMemoryState(entry, nowMs),
|
|
1391
|
+
claimStatus: resolveClaimStatus(entry),
|
|
1392
|
+
freshnessLabel: buildFreshnessLabel(entry),
|
|
1393
|
+
...buildProvenanceSummary(entry) ? { provenanceSummary: buildProvenanceSummary(entry) } : {}
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
function buildArtifactRecallPatchItem(recalled, deps) {
|
|
1397
|
+
const projected = projectClaimCentricRecallEntry(recalled, {
|
|
1398
|
+
slotPolicyConfig: deps.slotPolicyConfig
|
|
1399
|
+
});
|
|
1400
|
+
return {
|
|
1401
|
+
rank: 0,
|
|
1402
|
+
entry: recalled.entry,
|
|
1403
|
+
sourceKind: "artifact_recall",
|
|
1404
|
+
score: recalled.score,
|
|
1405
|
+
whySurfaced: projected.whySurfaced,
|
|
1406
|
+
memoryState: projected.memoryState,
|
|
1407
|
+
claimStatus: projected.claimStatus,
|
|
1408
|
+
freshnessLabel: projected.freshness.label,
|
|
1409
|
+
...formatProjectedProvenance2(projected.provenance) ? { provenanceSummary: formatProjectedProvenance2(projected.provenance) } : {}
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
function mergeDurableMemory(profileItems, directiveItems, coreItems, artifactRecallItems, maxDurableEntries) {
|
|
1413
|
+
const merged = [];
|
|
1414
|
+
const seenEntryIds = /* @__PURE__ */ new Set();
|
|
1415
|
+
const tryAdd = (item) => {
|
|
1416
|
+
if (merged.length >= maxDurableEntries || seenEntryIds.has(item.entry.id)) {
|
|
1417
|
+
return false;
|
|
1418
|
+
}
|
|
1419
|
+
seenEntryIds.add(item.entry.id);
|
|
1420
|
+
merged.push(item);
|
|
1421
|
+
return true;
|
|
1422
|
+
};
|
|
1423
|
+
const addFrom = (items, maxAdd = Number.POSITIVE_INFINITY) => {
|
|
1424
|
+
let added = 0;
|
|
1425
|
+
for (const item of items) {
|
|
1426
|
+
if (merged.length >= maxDurableEntries || added >= maxAdd) {
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
if (tryAdd(item)) {
|
|
1430
|
+
added += 1;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
addFrom(profileItems);
|
|
1435
|
+
addFrom(directiveItems);
|
|
1436
|
+
if (merged.length >= maxDurableEntries) {
|
|
1437
|
+
return merged;
|
|
1438
|
+
}
|
|
1439
|
+
const uniqueArtifact = artifactRecallItems.find((item) => !seenEntryIds.has(item.entry.id));
|
|
1440
|
+
const remainingSlots = maxDurableEntries - merged.length;
|
|
1441
|
+
const coreLimit = uniqueArtifact ? Math.max(0, remainingSlots - 1) : remainingSlots;
|
|
1442
|
+
addFrom(coreItems, coreLimit);
|
|
1443
|
+
if (uniqueArtifact) {
|
|
1444
|
+
tryAdd(uniqueArtifact);
|
|
1445
|
+
}
|
|
1446
|
+
addFrom(coreItems);
|
|
1447
|
+
addFrom(artifactRecallItems);
|
|
1448
|
+
return merged;
|
|
1449
|
+
}
|
|
1450
|
+
function assignRanks2(items) {
|
|
1451
|
+
return items.map((item, index) => ({
|
|
1452
|
+
...item,
|
|
1453
|
+
rank: index + 1
|
|
1454
|
+
}));
|
|
1455
|
+
}
|
|
1456
|
+
function normalizePolicy2(policy) {
|
|
1457
|
+
const maxCoreEntries = normalizeCount2(policy?.maxCoreEntries, DEFAULT_MAX_CORE_ENTRIES);
|
|
1458
|
+
const maxArtifactRecallEntries = normalizeCount2(policy?.maxArtifactRecallEntries, DEFAULT_MAX_ARTIFACT_RECALL_ENTRIES);
|
|
1459
|
+
const maxDurableEntries = Math.max(maxCoreEntries, normalizeCount2(policy?.maxDurableEntries, DEFAULT_MAX_DURABLE_ENTRIES2));
|
|
1460
|
+
return {
|
|
1461
|
+
maxCoreEntries,
|
|
1462
|
+
enableArtifactRecall: policy?.enableArtifactRecall !== false,
|
|
1463
|
+
maxArtifactRecallEntries,
|
|
1464
|
+
maxDurableEntries,
|
|
1465
|
+
maxArtifactChars: normalizeCount2(policy?.maxArtifactChars, DEFAULT_MAX_ARTIFACT_CHARS),
|
|
1466
|
+
recallThreshold: normalizeThreshold2(policy?.recallThreshold),
|
|
1467
|
+
maxProfileSnapshotAgeHours: normalizeCount2(policy?.maxProfileSnapshotAgeHours, DEFAULT_MAX_PROFILE_SNAPSHOT_AGE_HOURS)
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
function filterCurrentEntries2(entries, nowMs) {
|
|
1471
|
+
return entries.filter((entry) => isWithinValidityWindow(entry.valid_from, entry.valid_to, nowMs));
|
|
1472
|
+
}
|
|
1473
|
+
function normalizeCount2(value, fallback) {
|
|
1474
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1475
|
+
return fallback;
|
|
1476
|
+
}
|
|
1477
|
+
return Math.max(0, Math.trunc(value));
|
|
1478
|
+
}
|
|
1479
|
+
function normalizeThreshold2(value) {
|
|
1480
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1481
|
+
return 0;
|
|
1482
|
+
}
|
|
1483
|
+
return Math.min(1, Math.max(0, value));
|
|
1484
|
+
}
|
|
1485
|
+
function resolveMemoryState(entry, nowMs) {
|
|
1486
|
+
if (entry.superseded_by) {
|
|
1487
|
+
return "superseded";
|
|
1488
|
+
}
|
|
1489
|
+
if (!isCurrentlyValidMemory(entry, nowMs)) {
|
|
1490
|
+
return "historical";
|
|
1491
|
+
}
|
|
1492
|
+
return "current";
|
|
1493
|
+
}
|
|
1494
|
+
function resolveClaimStatus(entry) {
|
|
1495
|
+
return resolveKeyedDurableLifecycleStatus(entry);
|
|
1496
|
+
}
|
|
1497
|
+
function buildFreshnessLabel(entry) {
|
|
1498
|
+
const parts = [`created ${entry.created_at}`];
|
|
1499
|
+
const validFrom = normalizeOptionalString2(entry.valid_from);
|
|
1500
|
+
const validTo = normalizeOptionalString2(entry.valid_to);
|
|
1501
|
+
if (validFrom || validTo) {
|
|
1502
|
+
parts.push(`valid ${validFrom ?? "?"} -> ${validTo ?? "ongoing"}`);
|
|
1503
|
+
}
|
|
1504
|
+
return parts.join(" | ");
|
|
1505
|
+
}
|
|
1506
|
+
function buildProvenanceSummary(entry) {
|
|
1507
|
+
const parts = [
|
|
1508
|
+
entry.superseded_by ? `superseded_by=${entry.superseded_by}` : void 0,
|
|
1509
|
+
entry.supersession_kind ? `kind=${entry.supersession_kind}` : void 0,
|
|
1510
|
+
entry.supersession_reason ? `reason=${entry.supersession_reason}` : void 0,
|
|
1511
|
+
entry.claim_support_source_kind ? `support=${entry.claim_support_source_kind}` : void 0,
|
|
1512
|
+
entry.claim_support_mode ? `support_mode=${entry.claim_support_mode}` : void 0,
|
|
1513
|
+
entry.claim_support_observed_at ? `observed=${entry.claim_support_observed_at}` : void 0,
|
|
1514
|
+
entry.claim_support_locator ? `locator=${entry.claim_support_locator}` : void 0
|
|
1515
|
+
].filter((value) => value !== void 0);
|
|
1516
|
+
return parts.length > 0 ? parts.join(" | ") : void 0;
|
|
1517
|
+
}
|
|
1518
|
+
function formatProjectedProvenance2(provenance) {
|
|
1519
|
+
const parts = [
|
|
1520
|
+
provenance.supersededById ? `superseded_by=${provenance.supersededById}` : void 0,
|
|
1521
|
+
provenance.supersessionKind ? `kind=${provenance.supersessionKind}` : void 0,
|
|
1522
|
+
provenance.supersessionReason ? `reason=${provenance.supersessionReason}` : void 0,
|
|
1523
|
+
provenance.supportSourceKind ? `support=${provenance.supportSourceKind}` : void 0,
|
|
1524
|
+
provenance.supportMode ? `support_mode=${provenance.supportMode}` : void 0,
|
|
1525
|
+
provenance.supportObservedAt ? `observed=${provenance.supportObservedAt}` : void 0,
|
|
1526
|
+
provenance.supportLocator ? `locator=${provenance.supportLocator}` : void 0
|
|
1527
|
+
].filter((value) => value !== void 0);
|
|
1528
|
+
return parts.length > 0 ? parts.join(" | ") : void 0;
|
|
1529
|
+
}
|
|
1530
|
+
function normalizeOptionalString2(value) {
|
|
1531
|
+
const normalized = value?.trim();
|
|
1532
|
+
return normalized && normalized.length > 0 ? normalized : void 0;
|
|
1533
|
+
}
|
|
1534
|
+
function formatErrorMessage2(error) {
|
|
1535
|
+
if (error instanceof Error) {
|
|
1536
|
+
return error.message;
|
|
1537
|
+
}
|
|
1538
|
+
return String(error);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// src/adapters/db/directives-repository.ts
|
|
1542
|
+
var MAX_DIRECTIVES = 50;
|
|
1543
|
+
async function listActiveAbstainDirectives(executor, now = /* @__PURE__ */ new Date()) {
|
|
1544
|
+
const nowIso = now.toISOString();
|
|
1545
|
+
const result = await executor.execute({
|
|
1546
|
+
sql: `
|
|
1547
|
+
SELECT
|
|
1548
|
+
${DURABLE_SELECT_COLUMNS}
|
|
1549
|
+
FROM durables
|
|
1550
|
+
WHERE ${buildActiveDurableClause()}
|
|
1551
|
+
AND (
|
|
1552
|
+
(type = 'directive' AND directive_polarity = 'abstain')
|
|
1553
|
+
OR (claim_key LIKE ? AND (directive_polarity IS NULL OR directive_polarity = 'abstain'))
|
|
1554
|
+
)
|
|
1555
|
+
AND ${buildValidAsOfClause()}
|
|
1556
|
+
ORDER BY created_at DESC
|
|
1557
|
+
LIMIT ?
|
|
1558
|
+
`,
|
|
1559
|
+
args: [`${MEMORY_DIRECTIVE_CLAIM_KEY_PREFIX}%`, nowIso, nowIso, MAX_DIRECTIVES]
|
|
1560
|
+
});
|
|
1561
|
+
return result.rows.map((row) => mapDurableRow(row));
|
|
1562
|
+
}
|
|
1563
|
+
async function listActiveSessionStartProactiveDirectives(executor, now = /* @__PURE__ */ new Date()) {
|
|
1564
|
+
const nowIso = now.toISOString();
|
|
1565
|
+
const result = await executor.execute({
|
|
1566
|
+
sql: `
|
|
1567
|
+
SELECT
|
|
1568
|
+
${DURABLE_SELECT_COLUMNS}
|
|
1569
|
+
FROM durables
|
|
1570
|
+
WHERE ${buildActiveDurableClause()}
|
|
1571
|
+
AND type = 'directive'
|
|
1572
|
+
AND directive_polarity = 'proactive'
|
|
1573
|
+
AND directive_trigger IN ('session_start', 'always')
|
|
1574
|
+
AND ${buildValidAsOfClause()}
|
|
1575
|
+
ORDER BY importance DESC, created_at DESC
|
|
1576
|
+
LIMIT ?
|
|
1577
|
+
`,
|
|
1578
|
+
args: [nowIso, nowIso, MAX_DIRECTIVES]
|
|
1579
|
+
});
|
|
1580
|
+
return result.rows.map((row) => mapDurableRow(row));
|
|
1581
|
+
}
|
|
1582
|
+
async function listActiveTopicProactiveDirectives(executor, now = /* @__PURE__ */ new Date()) {
|
|
1583
|
+
const nowIso = now.toISOString();
|
|
1584
|
+
const result = await executor.execute({
|
|
1585
|
+
sql: `
|
|
1586
|
+
SELECT
|
|
1587
|
+
${DURABLE_SELECT_COLUMNS}
|
|
1588
|
+
FROM durables
|
|
1589
|
+
WHERE ${buildActiveDurableClause()}
|
|
1590
|
+
AND type = 'directive'
|
|
1591
|
+
AND directive_polarity = 'proactive'
|
|
1592
|
+
AND directive_trigger LIKE 'topic:%'
|
|
1593
|
+
AND ${buildValidAsOfClause()}
|
|
1594
|
+
ORDER BY importance DESC, created_at DESC
|
|
1595
|
+
LIMIT ?
|
|
1596
|
+
`,
|
|
1597
|
+
args: [nowIso, nowIso, MAX_DIRECTIVES]
|
|
1598
|
+
});
|
|
1599
|
+
return result.rows.map((row) => mapDurableRow(row));
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// src/adapters/db/session-start-repository.ts
|
|
1603
|
+
function createSessionStartRepository(executor) {
|
|
1604
|
+
return {
|
|
1605
|
+
listCoreEntries: async (limit, now) => listCoreEntries(executor, limit, now),
|
|
1606
|
+
getActiveProfileSnapshot: async (maxAgeMs, now) => getActiveProfileSnapshot(executor, maxAgeMs, now),
|
|
1607
|
+
listEntriesByIds: async (ids) => getDurables(executor, ids)
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
async function getActiveProfileSnapshot(executor, maxAgeMs, now = /* @__PURE__ */ new Date()) {
|
|
1611
|
+
if (!Number.isFinite(maxAgeMs) || maxAgeMs <= 0) {
|
|
1612
|
+
return null;
|
|
1613
|
+
}
|
|
1614
|
+
const minCreatedAt = new Date(now.getTime() - maxAgeMs).toISOString();
|
|
1615
|
+
const result = await executor.execute({
|
|
1616
|
+
sql: `
|
|
1617
|
+
SELECT
|
|
1618
|
+
p.id,
|
|
1619
|
+
p.durable_ids,
|
|
1620
|
+
p.directive_ids,
|
|
1621
|
+
p.as_of,
|
|
1622
|
+
p.run_id,
|
|
1623
|
+
p.created_at
|
|
1624
|
+
FROM dream_state AS s
|
|
1625
|
+
JOIN profile_snapshots AS p ON p.id = s.active_profile_snapshot_id
|
|
1626
|
+
WHERE s.id = 'default'
|
|
1627
|
+
AND datetime(p.created_at) >= datetime(?)
|
|
1628
|
+
LIMIT 1
|
|
1629
|
+
`,
|
|
1630
|
+
args: [minCreatedAt]
|
|
1631
|
+
});
|
|
1632
|
+
const row = result.rows[0];
|
|
1633
|
+
if (!row) {
|
|
1634
|
+
return null;
|
|
1635
|
+
}
|
|
1636
|
+
return {
|
|
1637
|
+
id: readRequiredString(row, "id"),
|
|
1638
|
+
durableIds: parseJsonStringArray(readOptionalString(row, "durable_ids")),
|
|
1639
|
+
directiveIds: parseJsonStringArray(readOptionalString(row, "directive_ids")),
|
|
1640
|
+
asOf: readRequiredString(row, "as_of"),
|
|
1641
|
+
runId: readOptionalString(row, "run_id") ?? null,
|
|
1642
|
+
createdAt: readRequiredString(row, "created_at")
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
async function listCoreEntries(executor, limit, now = /* @__PURE__ */ new Date()) {
|
|
1646
|
+
if (limit <= 0) {
|
|
1647
|
+
return [];
|
|
1648
|
+
}
|
|
1649
|
+
const nowIso = now.toISOString();
|
|
1650
|
+
const result = await executor.execute({
|
|
1651
|
+
sql: `
|
|
1652
|
+
SELECT
|
|
1653
|
+
${DURABLE_SELECT_COLUMNS}
|
|
1654
|
+
FROM durables
|
|
1655
|
+
WHERE ${buildActiveDurableClause()}
|
|
1656
|
+
AND expiry = 'core'
|
|
1657
|
+
AND ${buildValidAsOfClause()}
|
|
1658
|
+
ORDER BY importance DESC, created_at DESC
|
|
1659
|
+
LIMIT ?
|
|
1660
|
+
`,
|
|
1661
|
+
args: [nowIso, nowIso, limit]
|
|
1662
|
+
});
|
|
1663
|
+
return result.rows.map((row) => mapDurableRow(row));
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
export {
|
|
1667
|
+
runBeforeTurn,
|
|
1668
|
+
listActiveAbstainDirectives,
|
|
1669
|
+
listActiveSessionStartProactiveDirectives,
|
|
1670
|
+
listActiveTopicProactiveDirectives,
|
|
1671
|
+
createSessionStartRepository,
|
|
1672
|
+
formatInjectionEntryHeader,
|
|
1673
|
+
formatInjectionEntryBodyLines,
|
|
1674
|
+
wrapAgenrMemoryContext,
|
|
1675
|
+
containsAgenrMemoryContext,
|
|
1676
|
+
stripAgenrMemoryContext,
|
|
1677
|
+
formatAgenrBeforeTurnRecall,
|
|
1678
|
+
runSessionStart
|
|
1679
|
+
};
|