@agenr/agenr-plugin 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MEHOGUZE.js → chunk-6T5RXGIR.js} +989 -70
- package/dist/{chunk-Y2BC7RCE.js → chunk-7TDALVPY.js} +1434 -305
- package/dist/{chunk-XD3446YW.js → chunk-DGV6D6Q3.js} +2 -21
- package/dist/chunk-IMQIJPIP.js +886 -0
- package/dist/chunk-MJIB6J5S.js +3059 -0
- package/dist/index.js +1466 -124
- package/openclaw.plugin.json +86 -2
- package/package.json +1 -1
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
import {
|
|
2
|
+
projectClaimCentricRecallEntry,
|
|
3
|
+
runProcedureRecall
|
|
4
|
+
} from "./chunk-7TDALVPY.js";
|
|
5
|
+
import {
|
|
6
|
+
recall
|
|
7
|
+
} from "./chunk-6T5RXGIR.js";
|
|
8
|
+
|
|
9
|
+
// src/app/before-turn/service.ts
|
|
10
|
+
var DEFAULT_MAX_DURABLE_ENTRIES = 1;
|
|
11
|
+
var DEFAULT_MAX_HIGH_CONFIDENCE_DURABLE_ENTRIES = 2;
|
|
12
|
+
var DEFAULT_MAX_RECENT_TURNS = 2;
|
|
13
|
+
var DEFAULT_MAX_QUERY_CHARS = 450;
|
|
14
|
+
var DEFAULT_MAX_PROCEDURE_CANDIDATES = 3;
|
|
15
|
+
var DEFAULT_RECALL_THRESHOLD = 0.6;
|
|
16
|
+
var DEFAULT_HIGH_CONFIDENCE_RECALL_THRESHOLD = 0.97;
|
|
17
|
+
var DEFAULT_PROCEDURE_THRESHOLD = 0.72;
|
|
18
|
+
var DEFAULT_SKIP_TRIVIAL_TURNS = true;
|
|
19
|
+
var DEFAULT_REQUIRE_TURN_SIGNAL = true;
|
|
20
|
+
var SHORT_TURN_MAX_WORDS = 4;
|
|
21
|
+
var SHORT_TURN_MAX_CHARS = 24;
|
|
22
|
+
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;
|
|
23
|
+
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;
|
|
24
|
+
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;
|
|
25
|
+
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;
|
|
26
|
+
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;
|
|
27
|
+
var HARD_CONTEXT_PREFIX_RE = /^(?:and\b|also\b|what about\b|how about\b|same\b|same as\b)/iu;
|
|
28
|
+
var SOFT_CONTEXT_FALLBACK_RE = /\b(?:next|follow up|follow-up|continue|continuation)\b/iu;
|
|
29
|
+
var CONTEXT_QUESTION_PREFIX_RE = /^(?:when|where|why|should|does|is|are|what should)\b/iu;
|
|
30
|
+
var MAX_CONTEXT_ANCHOR_CHARS = 120;
|
|
31
|
+
var DIRECTNESS_STABLE_GAP = 0.08;
|
|
32
|
+
var DIRECTNESS_SUBJECT_ENTITY_MATCH_BONUS = 0.16;
|
|
33
|
+
var DIRECTNESS_SUBJECT_IDENTITY_WRAPPER_BONUS = 0.12;
|
|
34
|
+
var DIRECTNESS_DEFINITIONAL_CONTENT_BONUS = 0.22;
|
|
35
|
+
var DIRECTNESS_CLAIM_KEY_ENTITY_MATCH_BONUS = 0.18;
|
|
36
|
+
var DIRECTNESS_ADJACENT_RELATIONSHIP_PENALTY = 0.18;
|
|
37
|
+
var DIRECTNESS_LIST_LORE_PENALTY = 0.08;
|
|
38
|
+
var ENTITY_DIRECTNESS_MAX_WORDS = 5;
|
|
39
|
+
var ENTITY_DIRECTNESS_RECALL_CANDIDATE_LIMIT = 5;
|
|
40
|
+
var DIRECTNESS_IDENTITY_WRAPPERS = /* @__PURE__ */ new Set(["identity", "profile", "bio", "biography", "definition", "overview", "summary"]);
|
|
41
|
+
var DIRECTNESS_RELATIONSHIP_KEYWORDS = /* @__PURE__ */ new Set([
|
|
42
|
+
"cousin",
|
|
43
|
+
"cousins",
|
|
44
|
+
"family",
|
|
45
|
+
"brother",
|
|
46
|
+
"brothers",
|
|
47
|
+
"sister",
|
|
48
|
+
"sisters",
|
|
49
|
+
"mother",
|
|
50
|
+
"father",
|
|
51
|
+
"parent",
|
|
52
|
+
"parents",
|
|
53
|
+
"friend",
|
|
54
|
+
"friends",
|
|
55
|
+
"relationship",
|
|
56
|
+
"relationships",
|
|
57
|
+
"owner",
|
|
58
|
+
"owners"
|
|
59
|
+
]);
|
|
60
|
+
var DIRECTNESS_LIST_LORE_KEYWORDS = /* @__PURE__ */ new Set(["list", "notes", "timeline", "history", "background", "facts", "lore"]);
|
|
61
|
+
async function runBeforeTurn(input, deps) {
|
|
62
|
+
const policy = normalizePolicy(input.policy);
|
|
63
|
+
const currentTurnText = normalizeOptionalString(input.currentTurnText);
|
|
64
|
+
const recentTurns = normalizeRecentTurns(input.recentTurns, policy.maxRecentTurns, currentTurnText);
|
|
65
|
+
const diagnostics = {
|
|
66
|
+
queryVariants: [],
|
|
67
|
+
recentTurnCount: recentTurns.length,
|
|
68
|
+
turnSignalLabels: [],
|
|
69
|
+
durableRecallUsed: false,
|
|
70
|
+
durableRecallCandidateCount: 0,
|
|
71
|
+
procedureRecallUsed: false,
|
|
72
|
+
procedureCandidateCount: 0,
|
|
73
|
+
abstained: false,
|
|
74
|
+
abstentionReasons: [],
|
|
75
|
+
notices: []
|
|
76
|
+
};
|
|
77
|
+
if (!currentTurnText) {
|
|
78
|
+
diagnostics.abstained = true;
|
|
79
|
+
diagnostics.abstentionReasons.push("Current turn text was empty after normalization.");
|
|
80
|
+
return {
|
|
81
|
+
durableMemory: [],
|
|
82
|
+
diagnostics
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const turnSignal = inspectTurnSignal(currentTurnText);
|
|
86
|
+
diagnostics.turnSignalLabels = turnSignal.signalLabels;
|
|
87
|
+
if (turnSignal.suppressedTurnCategory) {
|
|
88
|
+
diagnostics.suppressedTurnCategory = turnSignal.suppressedTurnCategory;
|
|
89
|
+
}
|
|
90
|
+
if (policy.skipTrivialTurns && turnSignal.suppressedTurnCategory && turnSignal.suppressedTurnCategory !== "low_signal") {
|
|
91
|
+
diagnostics.abstained = true;
|
|
92
|
+
diagnostics.abstentionReasons.push(turnSignal.reason);
|
|
93
|
+
return {
|
|
94
|
+
durableMemory: [],
|
|
95
|
+
diagnostics
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (policy.requireTurnSignal && turnSignal.signalLabels.length === 0) {
|
|
99
|
+
diagnostics.abstained = true;
|
|
100
|
+
diagnostics.abstentionReasons.push(turnSignal.reason);
|
|
101
|
+
return {
|
|
102
|
+
durableMemory: [],
|
|
103
|
+
diagnostics
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const durableQueryPlan = buildDurableRecallQueryPlan(currentTurnText, recentTurns, policy.maxQueryChars);
|
|
107
|
+
const procedureQuery = buildProcedureQuery(currentTurnText, recentTurns, policy.maxQueryChars);
|
|
108
|
+
if (!durableQueryPlan) {
|
|
109
|
+
diagnostics.abstained = true;
|
|
110
|
+
diagnostics.abstentionReasons.push("No usable before-turn query could be derived from the turn context.");
|
|
111
|
+
return {
|
|
112
|
+
durableMemory: [],
|
|
113
|
+
diagnostics
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const [durableMemory, procedure] = await Promise.all([
|
|
117
|
+
policy.enableDurableRecall ? runDurableRecallSelection(currentTurnText, durableQueryPlan, input.sessionKey, policy, deps, diagnostics) : Promise.resolve([]),
|
|
118
|
+
policy.enableProcedureSuggestion && procedureQuery ? runProcedureSelection(procedureQuery, policy, deps, diagnostics) : Promise.resolve(void 0)
|
|
119
|
+
]);
|
|
120
|
+
if (!policy.enableDurableRecall) {
|
|
121
|
+
diagnostics.abstentionReasons.push("Durable recall disabled by before-turn policy.");
|
|
122
|
+
} else if (durableMemory.length === 0) {
|
|
123
|
+
diagnostics.abstentionReasons.push("No durable memory entries cleared the before-turn threshold.");
|
|
124
|
+
}
|
|
125
|
+
if (!policy.enableProcedureSuggestion) {
|
|
126
|
+
diagnostics.abstentionReasons.push("Procedure suggestion disabled by before-turn policy.");
|
|
127
|
+
} else if (!procedure) {
|
|
128
|
+
diagnostics.abstentionReasons.push("No canonical procedure suggestion cleared the before-turn threshold.");
|
|
129
|
+
}
|
|
130
|
+
diagnostics.abstained = durableMemory.length === 0 && !procedure;
|
|
131
|
+
return {
|
|
132
|
+
durableMemory: assignRanks(durableMemory),
|
|
133
|
+
...procedure ? { procedure } : {},
|
|
134
|
+
diagnostics
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function runDurableRecallSelection(currentTurnText, queryPlan, sessionKey, policy, deps, diagnostics) {
|
|
138
|
+
diagnostics.durableRecallUsed = true;
|
|
139
|
+
const attemptedVariants = [];
|
|
140
|
+
const primaryResult = await runDurableRecallAttempt(currentTurnText, queryPlan.primary.query, sessionKey, policy, deps, diagnostics);
|
|
141
|
+
attemptedVariants.push({
|
|
142
|
+
kind: queryPlan.primary.kind,
|
|
143
|
+
query: queryPlan.primary.query,
|
|
144
|
+
candidateCount: primaryResult.candidateCount,
|
|
145
|
+
selected: primaryResult.items.length > 0
|
|
146
|
+
});
|
|
147
|
+
if (queryPlan.fallback === void 0 || primaryResult.items.length > 0 && !shouldRetryWeakPrimaryWithContext(primaryResult, policy)) {
|
|
148
|
+
diagnostics.query = queryPlan.primary.query;
|
|
149
|
+
diagnostics.queryPolicy = queryPlan.policy;
|
|
150
|
+
diagnostics.queryVariants = attemptedVariants;
|
|
151
|
+
diagnostics.durableRecallTrace = primaryResult.durableRecallTrace;
|
|
152
|
+
diagnostics.durableRecallCandidateCount = primaryResult.candidateCount;
|
|
153
|
+
diagnostics.directness = primaryResult.directness;
|
|
154
|
+
if (primaryResult.notices.length > 0) {
|
|
155
|
+
diagnostics.notices.push(...primaryResult.notices);
|
|
156
|
+
}
|
|
157
|
+
if (primaryResult.directness?.decision === "abstained") {
|
|
158
|
+
diagnostics.abstentionReasons.push(primaryResult.directness.reason);
|
|
159
|
+
}
|
|
160
|
+
return primaryResult.items;
|
|
161
|
+
}
|
|
162
|
+
const fallbackPlan = queryPlan.fallback;
|
|
163
|
+
const primaryItems = primaryResult.items;
|
|
164
|
+
const fallbackResult = await runDurableRecallAttempt(currentTurnText, fallbackPlan.query, sessionKey, policy, deps, diagnostics);
|
|
165
|
+
const shouldUseFallback = fallbackResult.items.length > 0;
|
|
166
|
+
attemptedVariants[0] = {
|
|
167
|
+
...attemptedVariants[0],
|
|
168
|
+
selected: primaryItems.length > 0 && !shouldUseFallback
|
|
169
|
+
};
|
|
170
|
+
attemptedVariants.push({
|
|
171
|
+
kind: fallbackPlan.kind,
|
|
172
|
+
query: fallbackPlan.query,
|
|
173
|
+
candidateCount: fallbackResult.candidateCount,
|
|
174
|
+
selected: shouldUseFallback
|
|
175
|
+
});
|
|
176
|
+
const selectedResult = shouldUseFallback ? fallbackResult : primaryResult;
|
|
177
|
+
const selectedQuery = shouldUseFallback ? fallbackPlan.query : queryPlan.primary.query;
|
|
178
|
+
const selectedPolicy = shouldUseFallback ? "contextual_fallback" : queryPlan.policy;
|
|
179
|
+
diagnostics.query = selectedQuery;
|
|
180
|
+
diagnostics.queryPolicy = selectedPolicy;
|
|
181
|
+
diagnostics.queryVariants = attemptedVariants;
|
|
182
|
+
diagnostics.durableRecallTrace = selectedResult.durableRecallTrace;
|
|
183
|
+
diagnostics.durableRecallCandidateCount = selectedResult.candidateCount;
|
|
184
|
+
diagnostics.directness = selectedResult.directness;
|
|
185
|
+
if (primaryResult.notices.length > 0) {
|
|
186
|
+
diagnostics.notices.push(...primaryResult.notices);
|
|
187
|
+
}
|
|
188
|
+
if (fallbackResult.notices.length > 0) {
|
|
189
|
+
diagnostics.notices.push(...fallbackResult.notices);
|
|
190
|
+
}
|
|
191
|
+
if (selectedResult.directness?.decision === "abstained") {
|
|
192
|
+
diagnostics.abstentionReasons.push(selectedResult.directness.reason);
|
|
193
|
+
}
|
|
194
|
+
return selectedResult.items;
|
|
195
|
+
}
|
|
196
|
+
function shouldRetryWeakPrimaryWithContext(primaryResult, policy) {
|
|
197
|
+
const topScore = primaryResult.items[0]?.score;
|
|
198
|
+
return typeof topScore === "number" && topScore < policy.highConfidenceRecallThreshold;
|
|
199
|
+
}
|
|
200
|
+
async function runDurableRecallAttempt(currentTurnText, query, sessionKey, policy, deps, diagnostics) {
|
|
201
|
+
const directnessQuery = detectEntityDefinitionTurn(currentTurnText);
|
|
202
|
+
const durableRecallLimit = directnessQuery ? Math.max(policy.maxDurableEntries, policy.maxHighConfidenceDurableEntries, ENTITY_DIRECTNESS_RECALL_CANDIDATE_LIMIT) : Math.max(policy.maxDurableEntries, policy.maxHighConfidenceDurableEntries);
|
|
203
|
+
let durableRecallTrace;
|
|
204
|
+
try {
|
|
205
|
+
const recalled = await recall(
|
|
206
|
+
{
|
|
207
|
+
text: query,
|
|
208
|
+
limit: durableRecallLimit,
|
|
209
|
+
threshold: policy.recallThreshold,
|
|
210
|
+
sessionKey
|
|
211
|
+
},
|
|
212
|
+
deps.recall,
|
|
213
|
+
{
|
|
214
|
+
trace: {
|
|
215
|
+
reportSummary(summary) {
|
|
216
|
+
durableRecallTrace = summary;
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
slotPolicyConfig: deps.slotPolicyConfig
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
const notices = durableRecallTrace?.degraded.notices.length ? [...durableRecallTrace.degraded.notices] : [];
|
|
223
|
+
const directnessSelection = applyDirectnessSelection(
|
|
224
|
+
currentTurnText,
|
|
225
|
+
recalled.map((item) => buildDurablePatchItem(item, deps))
|
|
226
|
+
);
|
|
227
|
+
return {
|
|
228
|
+
items: selectDurablePatchItems(directnessSelection.items, policy, diagnostics),
|
|
229
|
+
candidateCount: recalled.length,
|
|
230
|
+
durableRecallTrace,
|
|
231
|
+
directness: directnessSelection.diagnostics,
|
|
232
|
+
notices
|
|
233
|
+
};
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return {
|
|
236
|
+
items: [],
|
|
237
|
+
candidateCount: 0,
|
|
238
|
+
durableRecallTrace,
|
|
239
|
+
notices: [`Before-turn durable recall failed: ${formatErrorMessage(error)}`]
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function runProcedureSelection(query, policy, deps, diagnostics) {
|
|
244
|
+
diagnostics.procedureRecallUsed = true;
|
|
245
|
+
try {
|
|
246
|
+
const result = await runProcedureRecall(
|
|
247
|
+
{
|
|
248
|
+
text: query,
|
|
249
|
+
limit: policy.maxProcedureCandidates,
|
|
250
|
+
threshold: policy.procedureThreshold
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
db: deps.procedures,
|
|
254
|
+
...deps.embedQuery ? { embedQuery: deps.embedQuery } : {}
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
diagnostics.procedureCandidateCount = result.candidates.length;
|
|
258
|
+
if (result.notices.length > 0) {
|
|
259
|
+
diagnostics.notices.push(...result.notices);
|
|
260
|
+
}
|
|
261
|
+
const canonicalProcedure = result.canonicalProcedure;
|
|
262
|
+
if (!canonicalProcedure) {
|
|
263
|
+
return void 0;
|
|
264
|
+
}
|
|
265
|
+
const leader = result.candidates.find((candidate) => candidate.procedure.id === canonicalProcedure.id);
|
|
266
|
+
if (!leader) {
|
|
267
|
+
diagnostics.notices.push("Procedure recall returned a canonical procedure without a matching ranked candidate.");
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
procedure: canonicalProcedure,
|
|
272
|
+
score: leader.score,
|
|
273
|
+
scores: {
|
|
274
|
+
relevance: leader.scores.relevance,
|
|
275
|
+
lexical: leader.scores.lexical,
|
|
276
|
+
vector: leader.scores.vector
|
|
277
|
+
},
|
|
278
|
+
whySurfaced: {
|
|
279
|
+
summary: `canonical procedure match; score ${leader.score.toFixed(2)}`,
|
|
280
|
+
reasons: [
|
|
281
|
+
"canonical procedure match",
|
|
282
|
+
`score ${leader.score.toFixed(2)}`,
|
|
283
|
+
`lexical ${leader.scores.lexical.toFixed(2)}`,
|
|
284
|
+
`vector ${leader.scores.vector.toFixed(2)}`
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
} catch (error) {
|
|
289
|
+
diagnostics.notices.push(`Before-turn procedure recall failed: ${formatErrorMessage(error)}`);
|
|
290
|
+
return void 0;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function buildDurablePatchItem(recalled, deps) {
|
|
294
|
+
const projected = projectClaimCentricRecallEntry(recalled, {
|
|
295
|
+
slotPolicyConfig: deps.slotPolicyConfig
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
rank: 0,
|
|
299
|
+
entry: recalled.entry,
|
|
300
|
+
sourceKind: "turn_recall",
|
|
301
|
+
score: recalled.score,
|
|
302
|
+
whySurfaced: projected.whySurfaced,
|
|
303
|
+
memoryState: projected.memoryState,
|
|
304
|
+
claimStatus: projected.claimStatus,
|
|
305
|
+
freshnessLabel: projected.freshness.label,
|
|
306
|
+
...formatProjectedProvenance(projected.provenance) ? { provenanceSummary: formatProjectedProvenance(projected.provenance) } : {}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function buildDurableRecallQueryPlan(currentTurnText, recentTurns, maxChars) {
|
|
310
|
+
const currentOnlyQuery = buildCurrentTurnOnlyQuery(currentTurnText, maxChars);
|
|
311
|
+
if (!currentOnlyQuery) {
|
|
312
|
+
return void 0;
|
|
313
|
+
}
|
|
314
|
+
const contextualQuery = buildContextualAnchorQuery(currentOnlyQuery, recentTurns, maxChars);
|
|
315
|
+
if (!contextualQuery) {
|
|
316
|
+
return {
|
|
317
|
+
policy: "current_only",
|
|
318
|
+
primary: {
|
|
319
|
+
kind: "current_only",
|
|
320
|
+
query: currentOnlyQuery
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (requiresContextualQuery(currentTurnText)) {
|
|
325
|
+
return {
|
|
326
|
+
policy: "contextual_required",
|
|
327
|
+
primary: {
|
|
328
|
+
kind: "contextual_anchor",
|
|
329
|
+
query: contextualQuery
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (shouldAllowContextualFallback(currentTurnText, recentTurns)) {
|
|
334
|
+
return {
|
|
335
|
+
policy: "current_only",
|
|
336
|
+
primary: {
|
|
337
|
+
kind: "current_only",
|
|
338
|
+
query: currentOnlyQuery
|
|
339
|
+
},
|
|
340
|
+
fallback: {
|
|
341
|
+
kind: "contextual_anchor",
|
|
342
|
+
query: contextualQuery
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
policy: "current_only",
|
|
348
|
+
primary: {
|
|
349
|
+
kind: "current_only",
|
|
350
|
+
query: currentOnlyQuery
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function buildCurrentTurnOnlyQuery(currentTurnText, maxChars) {
|
|
355
|
+
if (maxChars <= 0) {
|
|
356
|
+
return void 0;
|
|
357
|
+
}
|
|
358
|
+
const query = truncate(normalizeWhitespace(currentTurnText), maxChars);
|
|
359
|
+
return query.length > 0 ? query : void 0;
|
|
360
|
+
}
|
|
361
|
+
function buildContextualAnchorQuery(currentTurnQuery, recentTurns, maxChars) {
|
|
362
|
+
const anchor = buildCompactContextAnchor(recentTurns);
|
|
363
|
+
if (!anchor || maxChars <= currentTurnQuery.length) {
|
|
364
|
+
return void 0;
|
|
365
|
+
}
|
|
366
|
+
const prefix = "Topic: ";
|
|
367
|
+
const separator = "\n";
|
|
368
|
+
const remaining = maxChars - currentTurnQuery.length - separator.length - prefix.length;
|
|
369
|
+
if (remaining <= 0) {
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
372
|
+
return `${currentTurnQuery}${separator}${prefix}${truncate(anchor, remaining)}`;
|
|
373
|
+
}
|
|
374
|
+
function buildCompactContextAnchor(recentTurns) {
|
|
375
|
+
const recentTurn = recentTurns[recentTurns.length - 1];
|
|
376
|
+
if (!recentTurn) {
|
|
377
|
+
return void 0;
|
|
378
|
+
}
|
|
379
|
+
const normalized = normalizeWhitespace(recentTurn.text);
|
|
380
|
+
return normalized.length > 0 ? truncate(normalized, MAX_CONTEXT_ANCHOR_CHARS) : void 0;
|
|
381
|
+
}
|
|
382
|
+
function requiresContextualQuery(currentTurnText) {
|
|
383
|
+
const normalizedTurn = normalizeWhitespace(currentTurnText);
|
|
384
|
+
const lowerTurn = normalizedTurn.toLowerCase();
|
|
385
|
+
const wordCount = normalizedTurn.split(/\s+/u).filter((token) => token.length > 0).length;
|
|
386
|
+
const hasContextReference = CONTEXT_REFERENCE_RE.test(lowerTurn);
|
|
387
|
+
if (HARD_CONTEXT_PREFIX_RE.test(lowerTurn) && (hasContextReference || lowerTurn.includes("other one"))) {
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
if (CONTEXT_QUESTION_PREFIX_RE.test(lowerTurn) && hasContextReference && wordCount <= 8) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
return hasContextReference && wordCount <= 6;
|
|
394
|
+
}
|
|
395
|
+
function shouldAllowContextualFallback(currentTurnText, recentTurns) {
|
|
396
|
+
if (recentTurns.length === 0 || requiresContextualQuery(currentTurnText)) {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
return SOFT_CONTEXT_FALLBACK_RE.test(normalizeWhitespace(currentTurnText).toLowerCase());
|
|
400
|
+
}
|
|
401
|
+
function buildProcedureQuery(currentTurnText, recentTurns, maxChars) {
|
|
402
|
+
const normalizedCurrentTurn = normalizeWhitespace(currentTurnText);
|
|
403
|
+
if (normalizedCurrentTurn.length > 0) {
|
|
404
|
+
return truncate(normalizedCurrentTurn, maxChars);
|
|
405
|
+
}
|
|
406
|
+
const recentUserTurn = [...recentTurns].reverse().find((turn) => turn.role === "user");
|
|
407
|
+
return recentUserTurn ? truncate(normalizeWhitespace(recentUserTurn.text), maxChars) : void 0;
|
|
408
|
+
}
|
|
409
|
+
function applyDirectnessSelection(currentTurnText, items) {
|
|
410
|
+
if (items.length === 0) {
|
|
411
|
+
return { items };
|
|
412
|
+
}
|
|
413
|
+
const queryMatch = detectEntityDefinitionTurn(currentTurnText);
|
|
414
|
+
if (!queryMatch) {
|
|
415
|
+
return { items };
|
|
416
|
+
}
|
|
417
|
+
const scoredCandidates = items.map((item, index) => scoreDirectnessCandidate(queryMatch, item, index + 1));
|
|
418
|
+
const rerankedCandidates = [...scoredCandidates].sort(compareDirectnessCandidates);
|
|
419
|
+
const winner = rerankedCandidates[0];
|
|
420
|
+
const runnerUp = rerankedCandidates[1];
|
|
421
|
+
const winnerGap = runnerUp ? winner.adjustedScore - runnerUp.adjustedScore : void 0;
|
|
422
|
+
const winnerHasPositiveIdentitySignal = hasPositiveIdentitySignal(winner);
|
|
423
|
+
const runnerUpHasPositiveIdentitySignal = runnerUp ? hasPositiveIdentitySignal(runnerUp) : false;
|
|
424
|
+
const winnerHasOnlyAdjacentSignals = winner.signals.includes("adjacent_relationship") && !winner.signals.includes("definitional_content") && !winner.signals.includes("subject_entity_match") && !winner.signals.includes("subject_identity_wrapper");
|
|
425
|
+
const requiresStrictStableGap = runnerUpHasPositiveIdentitySignal;
|
|
426
|
+
const winnerGapTooSmall = requiresStrictStableGap && runnerUp !== void 0 && winnerGap !== void 0 && winnerGap < DIRECTNESS_STABLE_GAP;
|
|
427
|
+
if (!winnerHasPositiveIdentitySignal || winnerHasOnlyAdjacentSignals || winnerGapTooSmall) {
|
|
428
|
+
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.`;
|
|
429
|
+
return {
|
|
430
|
+
items: [],
|
|
431
|
+
diagnostics: buildDirectnessDiagnostics(queryMatch, "abstained", reason2, rerankedCandidates, winnerGap)
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const decision = winner.baseRank === 1 ? "kept" : "reranked";
|
|
435
|
+
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}".`;
|
|
436
|
+
return {
|
|
437
|
+
items: [winner.item],
|
|
438
|
+
diagnostics: buildDirectnessDiagnostics(queryMatch, decision, reason, rerankedCandidates, winnerGap)
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function detectEntityDefinitionTurn(currentTurnText) {
|
|
442
|
+
const normalizedTurn = normalizeWhitespace(currentTurnText);
|
|
443
|
+
const patterns = [/^(?:who|what)\s+is\s+(.+?)(?:\s+again)?[?!.,]*$/iu, /^who'?s\s+(.+?)(?:\s+again)?[?!.,]*$/iu, /^tell\s+me\s+about\s+(.+?)[?!.,]*$/iu];
|
|
444
|
+
for (const pattern of patterns) {
|
|
445
|
+
const match = pattern.exec(normalizedTurn);
|
|
446
|
+
const candidateEntity = normalizeDirectnessEntity(match?.[1]);
|
|
447
|
+
if (candidateEntity) {
|
|
448
|
+
return {
|
|
449
|
+
kind: "entity_definition",
|
|
450
|
+
entity: candidateEntity,
|
|
451
|
+
normalizedEntity: normalizeDirectnessText(candidateEntity)
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return void 0;
|
|
456
|
+
}
|
|
457
|
+
function normalizeDirectnessEntity(entityText) {
|
|
458
|
+
const cleaned = entityText ? normalizeWhitespace(entityText).replace(/^[("'`]+/u, "").replace(/[)"'`?!.,]+$/u, "").replace(/^(?:the|a|an)\s+/iu, "").trim() : "";
|
|
459
|
+
if (cleaned.length === 0) {
|
|
460
|
+
return void 0;
|
|
461
|
+
}
|
|
462
|
+
const wordCount = cleaned.split(/\s+/u).filter((token) => token.length > 0).length;
|
|
463
|
+
const normalized = normalizeDirectnessText(cleaned);
|
|
464
|
+
if (wordCount === 0 || wordCount > ENTITY_DIRECTNESS_MAX_WORDS || CONTEXT_REFERENCE_RE.test(normalized) || normalized.includes("'s") || containsKeyword(normalized, DIRECTNESS_RELATIONSHIP_KEYWORDS)) {
|
|
465
|
+
return void 0;
|
|
466
|
+
}
|
|
467
|
+
return cleaned;
|
|
468
|
+
}
|
|
469
|
+
function scoreDirectnessCandidate(queryMatch, item, baseRank) {
|
|
470
|
+
const subject = normalizeDirectnessText(item.entry.subject);
|
|
471
|
+
const content = normalizeDirectnessText(item.entry.content);
|
|
472
|
+
const signals = [];
|
|
473
|
+
let directnessDelta = 0;
|
|
474
|
+
if (subject === queryMatch.normalizedEntity) {
|
|
475
|
+
signals.push("subject_entity_match");
|
|
476
|
+
directnessDelta += DIRECTNESS_SUBJECT_ENTITY_MATCH_BONUS;
|
|
477
|
+
} else if (isIdentityWrapperSubject(subject, queryMatch.normalizedEntity)) {
|
|
478
|
+
signals.push("subject_identity_wrapper");
|
|
479
|
+
directnessDelta += DIRECTNESS_SUBJECT_IDENTITY_WRAPPER_BONUS;
|
|
480
|
+
}
|
|
481
|
+
if (hasDefinitionalContent(content, queryMatch.normalizedEntity)) {
|
|
482
|
+
signals.push("definitional_content");
|
|
483
|
+
directnessDelta += DIRECTNESS_DEFINITIONAL_CONTENT_BONUS;
|
|
484
|
+
}
|
|
485
|
+
if (hasEntityClaimKey(item.entry.claim_key, queryMatch.normalizedEntity)) {
|
|
486
|
+
signals.push("claim_key_entity_match");
|
|
487
|
+
directnessDelta += DIRECTNESS_CLAIM_KEY_ENTITY_MATCH_BONUS;
|
|
488
|
+
}
|
|
489
|
+
if (looksLikeAdjacentRelationship(subject, content, queryMatch.normalizedEntity)) {
|
|
490
|
+
signals.push("adjacent_relationship");
|
|
491
|
+
directnessDelta -= DIRECTNESS_ADJACENT_RELATIONSHIP_PENALTY;
|
|
492
|
+
}
|
|
493
|
+
if (looksLikeListLore(subject, content)) {
|
|
494
|
+
signals.push("list_lore");
|
|
495
|
+
directnessDelta -= DIRECTNESS_LIST_LORE_PENALTY;
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
item,
|
|
499
|
+
baseRank,
|
|
500
|
+
baseScore: item.score,
|
|
501
|
+
directnessDelta,
|
|
502
|
+
adjustedScore: item.score + directnessDelta,
|
|
503
|
+
signals
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function buildDirectnessDiagnostics(queryMatch, decision, reason, candidates, winnerGap) {
|
|
507
|
+
const winner = decision === "abstained" ? void 0 : candidates[0];
|
|
508
|
+
const runnerUp = candidates[1];
|
|
509
|
+
return {
|
|
510
|
+
queryKind: queryMatch.kind,
|
|
511
|
+
entity: queryMatch.entity,
|
|
512
|
+
decision,
|
|
513
|
+
winnerEntryId: winner?.item.entry.id,
|
|
514
|
+
runnerUpEntryId: runnerUp?.item.entry.id,
|
|
515
|
+
...winnerGap !== void 0 ? { winnerGap: roundToThreeDecimals(winnerGap) } : {},
|
|
516
|
+
reason,
|
|
517
|
+
candidates: candidates.map((candidate) => ({
|
|
518
|
+
entryId: candidate.item.entry.id,
|
|
519
|
+
baseRank: candidate.baseRank,
|
|
520
|
+
baseScore: roundToThreeDecimals(candidate.baseScore),
|
|
521
|
+
directnessDelta: roundToThreeDecimals(candidate.directnessDelta),
|
|
522
|
+
adjustedScore: roundToThreeDecimals(candidate.adjustedScore),
|
|
523
|
+
signals: candidate.signals
|
|
524
|
+
}))
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function hasPositiveIdentitySignal(candidate) {
|
|
528
|
+
return candidate.signals.includes("definitional_content") || candidate.signals.includes("subject_entity_match") || candidate.signals.includes("subject_identity_wrapper");
|
|
529
|
+
}
|
|
530
|
+
function isIdentityWrapperSubject(subject, entity) {
|
|
531
|
+
for (const wrapper of DIRECTNESS_IDENTITY_WRAPPERS) {
|
|
532
|
+
if (subject === `${entity} ${wrapper}` || subject === `${wrapper} ${entity}`) {
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
function hasDefinitionalContent(content, entity) {
|
|
539
|
+
const escapedEntity = escapeRegExp(entity);
|
|
540
|
+
if (startsWithBareRelationshipPredicate(content, escapedEntity)) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
const anchoredPatterns = [new RegExp(`^${escapedEntity}\\s+(?:is|was|means)\\b`, "u"), new RegExp(`^${escapedEntity}\\s+refers\\s+to\\b`, "u")];
|
|
544
|
+
if (anchoredPatterns.some((pattern) => pattern.test(content))) {
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
return hasEmbeddedDefinitionalContent(content, escapedEntity);
|
|
548
|
+
}
|
|
549
|
+
function hasEmbeddedDefinitionalContent(content, escapedEntity) {
|
|
550
|
+
const embeddedLead = `(?:^|[.!?;:]\\s+)${escapedEntity}\\s+(?:is|was)\\s+`;
|
|
551
|
+
const fullNameLead = `(?:^|[.!?;:]\\s+)${escapedEntity}(?:\\s+[\\p{L}\\p{N}]+){1,2}\\s+(?:is|was)\\s+`;
|
|
552
|
+
if (startsWithBareRelationshipPredicate(content, escapedEntity)) {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
const patterns = [
|
|
556
|
+
new RegExp(`${embeddedLead}(?:a|an|the)\\b`, "u"),
|
|
557
|
+
new RegExp(`${embeddedLead}[\\p{L}\\p{N}]+(?:['\u2019]s)\\b`, "u"),
|
|
558
|
+
// Allow short-name queries like "who is John?" to match a leading
|
|
559
|
+
// full-name clause such as "John Doe is married to Beverly".
|
|
560
|
+
new RegExp(`${fullNameLead}(?:a|an|the)\\b`, "u"),
|
|
561
|
+
new RegExp(`${fullNameLead}[\\p{L}\\p{N}]+\\b`, "u")
|
|
562
|
+
];
|
|
563
|
+
return patterns.some((pattern) => pattern.test(content));
|
|
564
|
+
}
|
|
565
|
+
function startsWithBareRelationshipPredicate(content, escapedEntity) {
|
|
566
|
+
const relationshipAlternation = Array.from(DIRECTNESS_RELATIONSHIP_KEYWORDS).join("|");
|
|
567
|
+
const embeddedLead = `(?:^|[.!?;:]\\s+)${escapedEntity}\\s+(?:is|was)\\s+`;
|
|
568
|
+
return new RegExp(`${embeddedLead}(?:${relationshipAlternation})\\b`, "u").test(content);
|
|
569
|
+
}
|
|
570
|
+
function looksLikeAdjacentRelationship(subject, content, entity) {
|
|
571
|
+
if (!subject.includes(entity) && !content.includes(entity)) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
return containsKeyword(subject, DIRECTNESS_RELATIONSHIP_KEYWORDS) || containsKeyword(content, DIRECTNESS_RELATIONSHIP_KEYWORDS);
|
|
575
|
+
}
|
|
576
|
+
function looksLikeListLore(subject, content) {
|
|
577
|
+
return containsKeyword(subject, DIRECTNESS_LIST_LORE_KEYWORDS) || containsKeyword(content, DIRECTNESS_LIST_LORE_KEYWORDS);
|
|
578
|
+
}
|
|
579
|
+
function compareDirectnessCandidates(left, right) {
|
|
580
|
+
if (right.adjustedScore !== left.adjustedScore) {
|
|
581
|
+
return right.adjustedScore - left.adjustedScore;
|
|
582
|
+
}
|
|
583
|
+
if (right.baseScore !== left.baseScore) {
|
|
584
|
+
return right.baseScore - left.baseScore;
|
|
585
|
+
}
|
|
586
|
+
return left.baseRank - right.baseRank;
|
|
587
|
+
}
|
|
588
|
+
function normalizeDirectnessText(value) {
|
|
589
|
+
return normalizeWhitespace(value).toLocaleLowerCase();
|
|
590
|
+
}
|
|
591
|
+
function hasEntityClaimKey(claimKey, entity) {
|
|
592
|
+
const head = claimKey?.split("/", 1)[0]?.replace(/[-_]+/g, " ");
|
|
593
|
+
return normalizeDirectnessText(head ?? "") === entity;
|
|
594
|
+
}
|
|
595
|
+
function containsKeyword(text, keywords) {
|
|
596
|
+
const tokens = new Set(text.match(/[\p{L}\p{N}]+/gu) ?? []);
|
|
597
|
+
for (const keyword of keywords) {
|
|
598
|
+
if (tokens.has(keyword)) {
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
function escapeRegExp(value) {
|
|
605
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
606
|
+
}
|
|
607
|
+
function roundToThreeDecimals(value) {
|
|
608
|
+
return Math.round(value * 1e3) / 1e3;
|
|
609
|
+
}
|
|
610
|
+
function selectDurablePatchItems(items, policy, diagnostics) {
|
|
611
|
+
if (policy.maxDurableEntries <= 0 || items.length === 0) {
|
|
612
|
+
return [];
|
|
613
|
+
}
|
|
614
|
+
const boundedItems = items.slice(0, policy.maxDurableEntries);
|
|
615
|
+
const expandedLimit = Math.max(policy.maxDurableEntries, policy.maxHighConfidenceDurableEntries);
|
|
616
|
+
if (expandedLimit <= policy.maxDurableEntries || items.length <= policy.maxDurableEntries) {
|
|
617
|
+
return boundedItems;
|
|
618
|
+
}
|
|
619
|
+
const expansionCandidates = items.slice(0, expandedLimit);
|
|
620
|
+
const canExpand = expansionCandidates.length > policy.maxDurableEntries && expansionCandidates.every((item) => item.score >= policy.highConfidenceRecallThreshold);
|
|
621
|
+
if (canExpand) {
|
|
622
|
+
diagnostics.notices.push(`Before-turn durable recall expanded to ${expansionCandidates.length} high-confidence items.`);
|
|
623
|
+
return expansionCandidates;
|
|
624
|
+
}
|
|
625
|
+
diagnostics.notices.push(
|
|
626
|
+
`Before-turn durable recall kept the top ${boundedItems.length} item${boundedItems.length === 1 ? "" : "s"} because additional candidates were not high confidence.`
|
|
627
|
+
);
|
|
628
|
+
return boundedItems;
|
|
629
|
+
}
|
|
630
|
+
function inspectTurnSignal(currentTurnText) {
|
|
631
|
+
const normalizedTurn = normalizeWhitespace(currentTurnText);
|
|
632
|
+
const lowerTurn = normalizedTurn.toLowerCase();
|
|
633
|
+
const signalLabels = collectTurnSignalLabels(lowerTurn);
|
|
634
|
+
const wordCount = normalizedTurn.split(/\s+/u).filter((token) => token.length > 0).length;
|
|
635
|
+
const isShortTurn = wordCount <= SHORT_TURN_MAX_WORDS || normalizedTurn.length <= SHORT_TURN_MAX_CHARS;
|
|
636
|
+
if (signalLabels.length === 0 && (SOCIAL_TURN_RE.test(lowerTurn) || isShortTurn)) {
|
|
637
|
+
return {
|
|
638
|
+
signalLabels,
|
|
639
|
+
suppressedTurnCategory: "short_social",
|
|
640
|
+
reason: "Current turn was short or social without clear factual, procedural, or task intent."
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
if (signalLabels.length === 0) {
|
|
644
|
+
return {
|
|
645
|
+
signalLabels,
|
|
646
|
+
suppressedTurnCategory: "low_signal",
|
|
647
|
+
reason: "Current turn lacked clear factual, procedural, or task signal, so before-turn recall abstained."
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
signalLabels,
|
|
652
|
+
reason: `Current turn showed ${signalLabels.join(", ")} recall signal.`
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
function collectTurnSignalLabels(lowerTurn) {
|
|
656
|
+
const labels = [];
|
|
657
|
+
if (TASK_SIGNAL_RE.test(lowerTurn)) {
|
|
658
|
+
labels.push("task");
|
|
659
|
+
}
|
|
660
|
+
if (FACTUAL_SIGNAL_RE.test(lowerTurn)) {
|
|
661
|
+
labels.push("factual");
|
|
662
|
+
}
|
|
663
|
+
if (PROCEDURAL_SIGNAL_RE.test(lowerTurn)) {
|
|
664
|
+
labels.push("procedural");
|
|
665
|
+
}
|
|
666
|
+
return labels;
|
|
667
|
+
}
|
|
668
|
+
function assignRanks(items) {
|
|
669
|
+
return items.map((item, index) => ({
|
|
670
|
+
...item,
|
|
671
|
+
rank: index + 1
|
|
672
|
+
}));
|
|
673
|
+
}
|
|
674
|
+
function normalizePolicy(policy) {
|
|
675
|
+
const maxDurableEntries = normalizeCount(policy?.maxDurableEntries, DEFAULT_MAX_DURABLE_ENTRIES);
|
|
676
|
+
const maxHighConfidenceDurableEntries = Math.max(
|
|
677
|
+
maxDurableEntries,
|
|
678
|
+
normalizeCount(policy?.maxHighConfidenceDurableEntries, DEFAULT_MAX_HIGH_CONFIDENCE_DURABLE_ENTRIES)
|
|
679
|
+
);
|
|
680
|
+
return {
|
|
681
|
+
enableDurableRecall: policy?.enableDurableRecall !== false,
|
|
682
|
+
enableProcedureSuggestion: policy?.enableProcedureSuggestion !== false,
|
|
683
|
+
maxRecentTurns: normalizeCount(policy?.maxRecentTurns, DEFAULT_MAX_RECENT_TURNS),
|
|
684
|
+
maxQueryChars: normalizeCount(policy?.maxQueryChars, DEFAULT_MAX_QUERY_CHARS),
|
|
685
|
+
maxDurableEntries,
|
|
686
|
+
maxHighConfidenceDurableEntries,
|
|
687
|
+
maxProcedureCandidates: normalizeCount(policy?.maxProcedureCandidates, DEFAULT_MAX_PROCEDURE_CANDIDATES),
|
|
688
|
+
recallThreshold: normalizeThreshold(policy?.recallThreshold, DEFAULT_RECALL_THRESHOLD),
|
|
689
|
+
highConfidenceRecallThreshold: normalizeThreshold(policy?.highConfidenceRecallThreshold, DEFAULT_HIGH_CONFIDENCE_RECALL_THRESHOLD),
|
|
690
|
+
procedureThreshold: normalizeThreshold(policy?.procedureThreshold, DEFAULT_PROCEDURE_THRESHOLD),
|
|
691
|
+
skipTrivialTurns: policy?.skipTrivialTurns ?? DEFAULT_SKIP_TRIVIAL_TURNS,
|
|
692
|
+
requireTurnSignal: policy?.requireTurnSignal ?? DEFAULT_REQUIRE_TURN_SIGNAL
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function normalizeCount(value, fallback) {
|
|
696
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
697
|
+
return fallback;
|
|
698
|
+
}
|
|
699
|
+
return Math.max(0, Math.trunc(value));
|
|
700
|
+
}
|
|
701
|
+
function normalizeThreshold(value, fallback) {
|
|
702
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
703
|
+
return fallback;
|
|
704
|
+
}
|
|
705
|
+
return Math.min(1, Math.max(0, value));
|
|
706
|
+
}
|
|
707
|
+
function normalizeRecentTurns(recentTurns, maxRecentTurns, currentTurnText) {
|
|
708
|
+
if (!recentTurns || recentTurns.length === 0 || maxRecentTurns <= 0) {
|
|
709
|
+
return [];
|
|
710
|
+
}
|
|
711
|
+
const normalizedTurns = recentTurns.flatMap((turn) => {
|
|
712
|
+
if (turn.role !== "user" && turn.role !== "assistant") {
|
|
713
|
+
return [];
|
|
714
|
+
}
|
|
715
|
+
const text = normalizeOptionalString(turn.text);
|
|
716
|
+
return text ? [{ role: turn.role, text }] : [];
|
|
717
|
+
});
|
|
718
|
+
const currentTurnFingerprint = currentTurnText ? normalizeWhitespace(currentTurnText).toLowerCase() : void 0;
|
|
719
|
+
const deduped = [...normalizedTurns];
|
|
720
|
+
while (deduped.length > 0 && currentTurnFingerprint) {
|
|
721
|
+
const last = deduped[deduped.length - 1];
|
|
722
|
+
if (!last || last.role !== "user") {
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
if (normalizeWhitespace(last.text).toLowerCase() !== currentTurnFingerprint) {
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
deduped.pop();
|
|
729
|
+
}
|
|
730
|
+
return deduped.slice(-maxRecentTurns);
|
|
731
|
+
}
|
|
732
|
+
function formatProjectedProvenance(provenance) {
|
|
733
|
+
const parts = [
|
|
734
|
+
provenance.supersededById ? `superseded_by=${provenance.supersededById}` : void 0,
|
|
735
|
+
provenance.supersessionKind ? `kind=${provenance.supersessionKind}` : void 0,
|
|
736
|
+
provenance.supersessionReason ? `reason=${provenance.supersessionReason}` : void 0,
|
|
737
|
+
provenance.supportSourceKind ? `support=${provenance.supportSourceKind}` : void 0,
|
|
738
|
+
provenance.supportMode ? `support_mode=${provenance.supportMode}` : void 0,
|
|
739
|
+
provenance.supportObservedAt ? `observed=${provenance.supportObservedAt}` : void 0,
|
|
740
|
+
provenance.supportLocator ? `locator=${provenance.supportLocator}` : void 0
|
|
741
|
+
].filter((value) => value !== void 0);
|
|
742
|
+
return parts.length > 0 ? parts.join(" | ") : void 0;
|
|
743
|
+
}
|
|
744
|
+
function normalizeOptionalString(value) {
|
|
745
|
+
const normalized = value?.trim();
|
|
746
|
+
return normalized && normalized.length > 0 ? normalized : void 0;
|
|
747
|
+
}
|
|
748
|
+
function normalizeWhitespace(value) {
|
|
749
|
+
return value.replace(/\s+/g, " ").trim();
|
|
750
|
+
}
|
|
751
|
+
function truncate(value, maxChars) {
|
|
752
|
+
if (maxChars <= 0) {
|
|
753
|
+
return "";
|
|
754
|
+
}
|
|
755
|
+
if (value.length <= maxChars) {
|
|
756
|
+
return value;
|
|
757
|
+
}
|
|
758
|
+
return `${value.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
759
|
+
}
|
|
760
|
+
function formatErrorMessage(error) {
|
|
761
|
+
if (error instanceof Error) {
|
|
762
|
+
return error.message;
|
|
763
|
+
}
|
|
764
|
+
return String(error);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// src/adapters/openclaw/format/memory-context.ts
|
|
768
|
+
var AGENR_MEMORY_CONTEXT_BLOCK_RE = /<agenr-memory-context>[\s\S]*?<\/agenr-memory-context>/giu;
|
|
769
|
+
var AGENR_MEMORY_CONTEXT_OPEN_RE = /<agenr-memory-context>/giu;
|
|
770
|
+
var AGENR_MEMORY_CONTEXT_CLOSE_RE = /<\/agenr-memory-context>/giu;
|
|
771
|
+
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;
|
|
772
|
+
var AGENR_MEMORY_CONTEXT_OPEN_TAG = "<agenr-memory-context>";
|
|
773
|
+
var AGENR_MEMORY_CONTEXT_CLOSE_TAG = "</agenr-memory-context>";
|
|
774
|
+
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.]";
|
|
775
|
+
function wrapAgenrMemoryContext(content) {
|
|
776
|
+
const trimmedContent = stripAgenrMemoryContext(content).trim();
|
|
777
|
+
if (!trimmedContent) {
|
|
778
|
+
return "";
|
|
779
|
+
}
|
|
780
|
+
return [AGENR_MEMORY_CONTEXT_OPEN_TAG, AGENR_MEMORY_CONTEXT_NOTE, "", trimmedContent, AGENR_MEMORY_CONTEXT_CLOSE_TAG].join("\n");
|
|
781
|
+
}
|
|
782
|
+
function containsAgenrMemoryContext(content) {
|
|
783
|
+
return content.includes(AGENR_MEMORY_CONTEXT_OPEN_TAG);
|
|
784
|
+
}
|
|
785
|
+
function stripAgenrMemoryContext(content) {
|
|
786
|
+
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, " ");
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// src/adapters/openclaw/format/before-turn-format.ts
|
|
790
|
+
var MAX_CONTENT_CHARS = 220;
|
|
791
|
+
function formatAgenrBeforeTurnRecall(patch) {
|
|
792
|
+
if (patch.durableMemory.length === 0 && !patch.procedure) {
|
|
793
|
+
return "";
|
|
794
|
+
}
|
|
795
|
+
const lines = [
|
|
796
|
+
"## Agenr Before-Turn Recall",
|
|
797
|
+
"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.",
|
|
798
|
+
""
|
|
799
|
+
];
|
|
800
|
+
if (patch.durableMemory.length > 0) {
|
|
801
|
+
lines.push("### Relevant Durable Memory");
|
|
802
|
+
for (const item of patch.durableMemory) {
|
|
803
|
+
lines.push(formatEntryHeader(item));
|
|
804
|
+
lines.push(...formatEntryBodyLines(item));
|
|
805
|
+
}
|
|
806
|
+
lines.push("");
|
|
807
|
+
}
|
|
808
|
+
if (patch.procedure) {
|
|
809
|
+
lines.push("### Suggested Procedure");
|
|
810
|
+
lines.push(formatProcedureHeader(patch.procedure));
|
|
811
|
+
lines.push(...formatProcedureBodyLines(patch.procedure));
|
|
812
|
+
lines.push("");
|
|
813
|
+
}
|
|
814
|
+
return wrapAgenrMemoryContext(lines.join("\n").trim());
|
|
815
|
+
}
|
|
816
|
+
function formatEntryHeader(item) {
|
|
817
|
+
const metadata = [
|
|
818
|
+
`rank ${item.rank}`,
|
|
819
|
+
item.entry.id,
|
|
820
|
+
item.entry.type,
|
|
821
|
+
item.entry.expiry,
|
|
822
|
+
`importance ${item.entry.importance}`,
|
|
823
|
+
`score ${item.score.toFixed(2)}`
|
|
824
|
+
];
|
|
825
|
+
return `- [${metadata.join(" | ")}] ${item.entry.subject}`;
|
|
826
|
+
}
|
|
827
|
+
function formatEntryBodyLines(item) {
|
|
828
|
+
const lines = [` ${truncate2(item.entry.content.trim(), MAX_CONTENT_CHARS)}`];
|
|
829
|
+
lines.push(` why: ${item.whySurfaced.summary}`);
|
|
830
|
+
const metadata = [
|
|
831
|
+
item.entry.tags.length > 0 ? `tags: ${item.entry.tags.join(", ")}` : void 0,
|
|
832
|
+
item.freshnessLabel ? `freshness: ${item.freshnessLabel}` : void 0,
|
|
833
|
+
item.provenanceSummary ? `provenance: ${truncate2(item.provenanceSummary, MAX_CONTENT_CHARS)}` : void 0
|
|
834
|
+
].filter((value) => value !== void 0);
|
|
835
|
+
if (metadata.length > 0) {
|
|
836
|
+
lines.push(` ${metadata.join(" | ")}`);
|
|
837
|
+
}
|
|
838
|
+
return lines;
|
|
839
|
+
}
|
|
840
|
+
function formatProcedureHeader(suggestion) {
|
|
841
|
+
const metadata = [
|
|
842
|
+
suggestion.procedure.id,
|
|
843
|
+
suggestion.procedure.procedure_key,
|
|
844
|
+
`score ${suggestion.score.toFixed(2)}`,
|
|
845
|
+
`lexical ${suggestion.scores.lexical.toFixed(2)}`,
|
|
846
|
+
`vector ${suggestion.scores.vector.toFixed(2)}`
|
|
847
|
+
];
|
|
848
|
+
return `- [${metadata.join(" | ")}] ${suggestion.procedure.title}`;
|
|
849
|
+
}
|
|
850
|
+
function formatProcedureBodyLines(suggestion) {
|
|
851
|
+
const lines = [` goal: ${truncate2(suggestion.procedure.goal.trim(), MAX_CONTENT_CHARS)}`];
|
|
852
|
+
lines.push(` why: ${suggestion.whySurfaced.summary}`);
|
|
853
|
+
if (suggestion.procedure.when_to_use.length > 0) {
|
|
854
|
+
lines.push(` when to use: ${truncate2(suggestion.procedure.when_to_use.join("; "), MAX_CONTENT_CHARS)}`);
|
|
855
|
+
}
|
|
856
|
+
if (suggestion.procedure.verification.length > 0) {
|
|
857
|
+
lines.push(` verification: ${truncate2(suggestion.procedure.verification.join("; "), MAX_CONTENT_CHARS)}`);
|
|
858
|
+
}
|
|
859
|
+
return lines;
|
|
860
|
+
}
|
|
861
|
+
function truncate2(value, maxChars) {
|
|
862
|
+
if (value.length <= maxChars) {
|
|
863
|
+
return value;
|
|
864
|
+
}
|
|
865
|
+
return `${value.slice(0, maxChars - 3).trimEnd()}...`;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// src/app/debug-artifacts/before-turn.ts
|
|
869
|
+
var BEFORE_TURN_DEBUG_ARTIFACT_DEFAULT_TOP_K = 10;
|
|
870
|
+
var BEFORE_TURN_DEBUG_ARTIFACT_MAX_TOP_K = 25;
|
|
871
|
+
|
|
872
|
+
// src/app/debug-artifacts/recall.ts
|
|
873
|
+
var RECALL_DEBUG_ARTIFACT_DEFAULT_TOP_K = 10;
|
|
874
|
+
var RECALL_DEBUG_ARTIFACT_MAX_TOP_K = 25;
|
|
875
|
+
|
|
876
|
+
export {
|
|
877
|
+
runBeforeTurn,
|
|
878
|
+
wrapAgenrMemoryContext,
|
|
879
|
+
containsAgenrMemoryContext,
|
|
880
|
+
stripAgenrMemoryContext,
|
|
881
|
+
formatAgenrBeforeTurnRecall,
|
|
882
|
+
BEFORE_TURN_DEBUG_ARTIFACT_DEFAULT_TOP_K,
|
|
883
|
+
BEFORE_TURN_DEBUG_ARTIFACT_MAX_TOP_K,
|
|
884
|
+
RECALL_DEBUG_ARTIFACT_DEFAULT_TOP_K,
|
|
885
|
+
RECALL_DEBUG_ARTIFACT_MAX_TOP_K
|
|
886
|
+
};
|