@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/index.js CHANGED
@@ -7,34 +7,276 @@ import {
7
7
  parseTuiSessionKey,
8
8
  readOpenClawSessionsStore,
9
9
  storeEntriesDetailed
10
- } from "./chunk-XD3446YW.js";
10
+ } from "./chunk-DGV6D6Q3.js";
11
+ import {
12
+ BEFORE_TURN_DEBUG_ARTIFACT_DEFAULT_TOP_K,
13
+ BEFORE_TURN_DEBUG_ARTIFACT_MAX_TOP_K,
14
+ RECALL_DEBUG_ARTIFACT_DEFAULT_TOP_K,
15
+ RECALL_DEBUG_ARTIFACT_MAX_TOP_K,
16
+ containsAgenrMemoryContext,
17
+ formatAgenrBeforeTurnRecall,
18
+ runBeforeTurn,
19
+ stripAgenrMemoryContext,
20
+ wrapAgenrMemoryContext
21
+ } from "./chunk-IMQIJPIP.js";
11
22
  import {
12
23
  EMBEDDING_DIMENSIONS,
24
+ ENTRY_SELECT_COLUMNS,
13
25
  ENTRY_TYPES,
14
26
  EXPIRY_LEVELS,
27
+ attachCrossEncoderPort,
28
+ buildActiveEntryClause,
15
29
  createDatabase,
16
30
  createEmbeddingClient,
31
+ createOpenAICrossEncoder,
17
32
  createRecallAdapter,
33
+ mapEntryRow,
18
34
  normalizeManualClaimKeyUpdate,
35
+ projectClaimCentricRecallEntry,
19
36
  readConfig,
20
37
  resolveClaimExtractionConfig,
21
38
  resolveConfigPath,
39
+ resolveCrossEncoderApiKey,
22
40
  resolveDbPath,
23
41
  resolveEmbeddingApiKey,
24
42
  resolveEmbeddingModel,
43
+ resolveModel,
25
44
  runUnifiedRecall,
26
45
  validateTemporalValidityRange
27
- } from "./chunk-Y2BC7RCE.js";
46
+ } from "./chunk-7TDALVPY.js";
28
47
  import {
48
+ recall,
29
49
  resolveClaimSlotPolicy
30
- } from "./chunk-MEHOGUZE.js";
50
+ } from "./chunk-6T5RXGIR.js";
31
51
 
32
52
  // src/adapters/openclaw/index.ts
33
53
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
34
54
 
35
55
  // src/adapters/openclaw/tools/recall.ts
56
+ import { randomUUID } from "crypto";
36
57
  import { readNumberParam, readStringArrayParam, readStringParam as readStringParam2, textResult } from "openclaw/plugin-sdk/agent-runtime";
37
58
 
59
+ // src/adapters/openclaw/debug/sink.ts
60
+ import { appendFile, mkdir } from "fs/promises";
61
+ import path from "path";
62
+ var NOOP_SINK = {
63
+ enabled: false,
64
+ eventLevel: "basic",
65
+ maxTopCandidates: 0,
66
+ async emit() {
67
+ return;
68
+ },
69
+ async close() {
70
+ return;
71
+ }
72
+ };
73
+ function createNoopAgenrDebugSink() {
74
+ return NOOP_SINK;
75
+ }
76
+ function createAgenrDebugSink(config) {
77
+ if (!config.enabled || !config.logPath) {
78
+ return NOOP_SINK;
79
+ }
80
+ const basePath = config.logPath;
81
+ const perSessionFiles = config.perSessionFiles;
82
+ const eventLevel = config.eventLevel;
83
+ const maxTopCandidates = config.maxTopCandidates;
84
+ const directoriesEnsured = /* @__PURE__ */ new Set();
85
+ let writeChain = Promise.resolve();
86
+ let closed = false;
87
+ const ensureDirectoryOnce = async (filePath) => {
88
+ const directory = path.dirname(filePath);
89
+ if (directoriesEnsured.has(directory)) {
90
+ return;
91
+ }
92
+ await mkdir(directory, { recursive: true });
93
+ directoriesEnsured.add(directory);
94
+ };
95
+ const resolveFilePath = (event) => {
96
+ if (!perSessionFiles) {
97
+ return basePath;
98
+ }
99
+ const sessionSuffix = sanitizeSessionSuffix(event.sessionId) ?? sanitizeSessionSuffix(event.sessionKey);
100
+ if (!sessionSuffix) {
101
+ return basePath;
102
+ }
103
+ const directory = path.dirname(basePath);
104
+ const extension = path.extname(basePath);
105
+ const baseName = path.basename(basePath, extension);
106
+ const suffixed = `${baseName}.${sessionSuffix}${extension || ".jsonl"}`;
107
+ return path.join(directory, suffixed);
108
+ };
109
+ const writeLine = async (filePath, line) => {
110
+ await ensureDirectoryOnce(filePath);
111
+ await appendFile(filePath, `${line}
112
+ `, "utf8");
113
+ };
114
+ return {
115
+ enabled: true,
116
+ eventLevel,
117
+ maxTopCandidates,
118
+ logPath: basePath,
119
+ async emit(event) {
120
+ if (closed) {
121
+ return;
122
+ }
123
+ const filePath = resolveFilePath(event);
124
+ const line = formatEventLine(event);
125
+ writeChain = writeChain.then(async () => {
126
+ try {
127
+ await writeLine(filePath, line);
128
+ } catch {
129
+ }
130
+ });
131
+ await writeChain;
132
+ },
133
+ async close() {
134
+ closed = true;
135
+ await writeChain;
136
+ }
137
+ };
138
+ }
139
+ function formatEventLine(event) {
140
+ const line = {
141
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
142
+ ...event
143
+ };
144
+ return JSON.stringify(line);
145
+ }
146
+ function sanitizeSessionSuffix(value) {
147
+ if (typeof value !== "string") {
148
+ return void 0;
149
+ }
150
+ const trimmed = value.trim();
151
+ if (trimmed.length === 0) {
152
+ return void 0;
153
+ }
154
+ const sanitized = trimmed.replace(/[^A-Za-z0-9._-]+/gu, "_");
155
+ if (sanitized.length === 0) {
156
+ return void 0;
157
+ }
158
+ return sanitized.slice(0, 120);
159
+ }
160
+
161
+ // src/adapters/openclaw/debug/build-before-turn-artifact.ts
162
+ function buildLiveBeforeTurnDebugArtifact(params) {
163
+ const { caseId, patch, currentTurnText, trigger, eventLevel, maxTopCandidates } = params;
164
+ const diagnostics = patch.diagnostics;
165
+ const includeCandidateBreakdown = eventLevel === "detailed";
166
+ const topK = includeCandidateBreakdown ? clampTopK(maxTopCandidates) : 0;
167
+ const durableTopCandidates = includeCandidateBreakdown ? buildDurableCandidates(patch, topK) : [];
168
+ const procedureTopCandidates = includeCandidateBreakdown ? buildProcedureCandidates(patch, topK) : [];
169
+ const normalizedTrigger = trigger?.trim() || "unspecified";
170
+ return {
171
+ schemaVersion: "before-turn-debug-artifact.v1",
172
+ caseId,
173
+ input: {
174
+ trigger: normalizedTrigger,
175
+ currentTurnText
176
+ },
177
+ ...diagnostics.queryPolicy ? { queryPolicy: diagnostics.queryPolicy } : {},
178
+ ...diagnostics.queryVariants.length > 0 ? { queryVariants: [...diagnostics.queryVariants] } : {},
179
+ ...diagnostics.abstentionReasons.length > 0 ? { abstentionReasons: [...diagnostics.abstentionReasons] } : {},
180
+ selectedEntryIds: patch.durableMemory.map((item) => item.entry.id),
181
+ selectedProcedureKey: patch.procedure?.procedure.procedure_key ?? null,
182
+ ...durableTopCandidates.length > 0 ? { durableRecallTopCandidates: durableTopCandidates } : {},
183
+ ...procedureTopCandidates.length > 0 ? { procedureTopCandidates } : {}
184
+ };
185
+ }
186
+ function clampTopK(requested) {
187
+ if (!Number.isFinite(requested) || !Number.isInteger(requested)) {
188
+ return BEFORE_TURN_DEBUG_ARTIFACT_DEFAULT_TOP_K;
189
+ }
190
+ if (requested < 1) {
191
+ return 0;
192
+ }
193
+ if (requested > BEFORE_TURN_DEBUG_ARTIFACT_MAX_TOP_K) {
194
+ return BEFORE_TURN_DEBUG_ARTIFACT_MAX_TOP_K;
195
+ }
196
+ return requested;
197
+ }
198
+ function buildDurableCandidates(patch, topK) {
199
+ if (topK < 1) {
200
+ return [];
201
+ }
202
+ return patch.durableMemory.slice(0, topK).map((item) => {
203
+ const reasons = item.whySurfaced.reasons.length > 0 ? [...item.whySurfaced.reasons] : void 0;
204
+ return {
205
+ id: item.entry.id,
206
+ score: item.score,
207
+ ...reasons ? { reasons } : {}
208
+ };
209
+ });
210
+ }
211
+ function buildProcedureCandidates(patch, topK) {
212
+ if (!patch.procedure || topK < 1) {
213
+ return [];
214
+ }
215
+ const reasons = patch.procedure.whySurfaced.reasons.length > 0 ? [...patch.procedure.whySurfaced.reasons] : void 0;
216
+ return [
217
+ {
218
+ procedureKey: patch.procedure.procedure.procedure_key,
219
+ score: patch.procedure.score,
220
+ ...reasons ? { reasons } : {}
221
+ }
222
+ ];
223
+ }
224
+
225
+ // src/adapters/openclaw/debug/build-recall-artifact.ts
226
+ function buildLiveRecallDebugArtifact(params) {
227
+ const { caseId, query, result, eventLevel, maxTopCandidates } = params;
228
+ const selectedEntryIds = result.entries.map((entry) => entry.entry.id);
229
+ const includeCandidateBreakdown = eventLevel === "detailed";
230
+ const topK = includeCandidateBreakdown ? clampTopK2(maxTopCandidates) : 0;
231
+ const reasonsByEntryId = /* @__PURE__ */ new Map();
232
+ for (const projected of result.projectedEntries) {
233
+ if (projected.whySurfaced.reasons.length > 0) {
234
+ reasonsByEntryId.set(projected.entryId, [...projected.whySurfaced.reasons]);
235
+ }
236
+ }
237
+ const topCandidates = includeCandidateBreakdown ? buildTopCandidates(result, reasonsByEntryId, topK) : [];
238
+ return {
239
+ schemaVersion: "recall-debug-artifact.v1",
240
+ caseId,
241
+ request: {
242
+ recallPath: "unified",
243
+ query
244
+ },
245
+ routing: result.routing,
246
+ selectedEntryIds,
247
+ ...topCandidates.length > 0 ? { topCandidates } : {}
248
+ };
249
+ }
250
+ function clampTopK2(requested) {
251
+ if (!Number.isFinite(requested) || !Number.isInteger(requested)) {
252
+ return RECALL_DEBUG_ARTIFACT_DEFAULT_TOP_K;
253
+ }
254
+ if (requested < 1) {
255
+ return 0;
256
+ }
257
+ if (requested > RECALL_DEBUG_ARTIFACT_MAX_TOP_K) {
258
+ return RECALL_DEBUG_ARTIFACT_MAX_TOP_K;
259
+ }
260
+ return requested;
261
+ }
262
+ function buildTopCandidates(result, reasonsByEntryId, topK) {
263
+ if (topK < 1) {
264
+ return [];
265
+ }
266
+ return result.entries.slice(0, topK).map((entry) => {
267
+ const reasons = reasonsByEntryId.get(entry.entry.id);
268
+ return {
269
+ id: entry.entry.id,
270
+ score: entry.score,
271
+ lexicalScore: entry.scores.lexical,
272
+ vectorScore: entry.scores.vector,
273
+ recencyScore: entry.scores.recency,
274
+ importanceScore: entry.scores.importance,
275
+ ...reasons && reasons.length > 0 ? { reasons } : {}
276
+ };
277
+ });
278
+ }
279
+
38
280
  // src/adapters/openclaw/tools/shared.ts
39
281
  import { failedTextResult, readStringParam } from "openclaw/plugin-sdk/agent-runtime";
40
282
  var ENTRY_TYPE_DESCRIPTION = "Knowledge type to store. Use fact for durable truth about a person, system, place, or how something works. Use decision for a standing rule, constraint, policy, or chosen approach future sessions should follow - not a progress update or completed action. Use preference for what someone likes, wants, values, or wants avoided. Use lesson for a non-obvious takeaway learned from experience that should change future behavior. Use milestone for a rare one-time event with durable future significance - not ordinary execution progress. Use relationship for a meaningful durable connection between people, groups, or systems.";
@@ -636,6 +878,15 @@ function createAgenrRecallTool(ctx, servicesPromise, logger) {
636
878
  ...asOf ? { asOf } : {},
637
879
  sessionKey: ctx.sessionKey
638
880
  };
881
+ const sanitizedParams = sanitizeRecallToolParams({
882
+ query,
883
+ mode,
884
+ limit,
885
+ threshold,
886
+ types,
887
+ tags,
888
+ ...asOf ? { asOf } : {}
889
+ });
639
890
  logToolCall(
640
891
  logger,
641
892
  "agenr_recall",
@@ -648,16 +899,15 @@ function createAgenrRecallTool(ctx, servicesPromise, logger) {
648
899
  tags,
649
900
  ...asOf ? { asOf } : {}
650
901
  }),
651
- sanitizeRecallToolParams({
652
- query,
653
- mode,
654
- limit,
655
- threshold,
656
- types,
657
- tags,
658
- ...asOf ? { asOf } : {}
659
- })
902
+ sanitizedParams
660
903
  );
904
+ void services.debugSink.emit({
905
+ type: "tool_call",
906
+ tool: "agenr_recall",
907
+ ...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
908
+ ...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
909
+ params: sanitizedParams
910
+ });
661
911
  const result = await runUnifiedRecall(request, {
662
912
  database: services.episodes,
663
913
  procedures: services.procedures,
@@ -679,6 +929,42 @@ function createAgenrRecallTool(ctx, servicesPromise, logger) {
679
929
  logger.info(
680
930
  `[agenr] tool=agenr_recall session=${ctx.sessionId ?? "unknown"} key=${ctx.sessionKey ?? "unknown"} result: ${formatUnifiedRecallLogSummary(result)}`
681
931
  );
932
+ if (services.debugSink.enabled) {
933
+ const sessionIdPayload = ctx.sessionId ? { sessionId: ctx.sessionId } : {};
934
+ const sessionKeyPayload = ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {};
935
+ void services.debugSink.emit({
936
+ type: "tool_result",
937
+ tool: "agenr_recall",
938
+ ...sessionIdPayload,
939
+ ...sessionKeyPayload,
940
+ summary: {
941
+ count: result.count,
942
+ routing: {
943
+ requested: result.routing.requested,
944
+ detectedIntent: result.routing.detectedIntent,
945
+ queried: [...result.routing.queried],
946
+ reason: result.routing.reason
947
+ },
948
+ selectedEntryIds: result.entries.map((entry) => entry.entry.id),
949
+ episodeIds: result.episodes.map((episode) => episode.episode.id),
950
+ selectedProcedureKey: result.procedure?.procedure_key ?? null,
951
+ notices: [...result.notices],
952
+ procedureNotices: [...result.procedureNotices]
953
+ }
954
+ });
955
+ void services.debugSink.emit({
956
+ type: "unified_recall",
957
+ ...sessionIdPayload,
958
+ ...sessionKeyPayload,
959
+ debug: buildLiveRecallDebugArtifact({
960
+ caseId: `live-${randomUUID()}`,
961
+ query,
962
+ result,
963
+ eventLevel: services.debugSink.eventLevel,
964
+ maxTopCandidates: services.debugSink.maxTopCandidates
965
+ })
966
+ });
967
+ }
682
968
  return textResult(formatUnifiedRecallResults(result), {
683
969
  status: "ok",
684
970
  count: result.count,
@@ -758,6 +1044,19 @@ function createAgenrRecallTool(ctx, servicesPromise, logger) {
758
1044
  });
759
1045
  } catch (error) {
760
1046
  logToolFailure(logger, "agenr_recall", ctx, error);
1047
+ try {
1048
+ const services = await servicesPromise;
1049
+ if (services.debugSink.enabled) {
1050
+ void services.debugSink.emit({
1051
+ type: "error",
1052
+ ...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
1053
+ ...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
1054
+ scope: "agenr_recall",
1055
+ error: { message: error instanceof Error ? error.message : String(error) }
1056
+ });
1057
+ }
1058
+ } catch {
1059
+ }
761
1060
  return toolFailureResult(error);
762
1061
  }
763
1062
  }
@@ -1175,7 +1474,7 @@ function registerAgenrOpenClawTools(api, servicesPromise, logger) {
1175
1474
  var openclaw_plugin_default = {
1176
1475
  id: "agenr",
1177
1476
  name: "agenr",
1178
- version: "2.0.0",
1477
+ version: "2.1.0",
1179
1478
  description: "agenr memory plugin for OpenClaw",
1180
1479
  kind: "memory",
1181
1480
  contracts: {
@@ -1208,7 +1507,11 @@ var openclaw_plugin_default = {
1208
1507
  },
1209
1508
  memoryPolicy: {
1210
1509
  label: "Memory policy",
1211
- help: "Optional runtime overrides for claim-aware read behavior such as slot-policy classes."
1510
+ help: "Optional runtime overrides for claim-aware read behavior, session-start memory injection, and proactive before-turn surfacing."
1511
+ },
1512
+ debug: {
1513
+ label: "Debug log",
1514
+ help: "Optional opt-in JSONL debug sink that records recall, session-start, and before-turn decisions into a dedicated agenr log file separate from OpenClaw host logs."
1212
1515
  }
1213
1516
  },
1214
1517
  configSchema: {
@@ -1259,11 +1562,91 @@ var openclaw_plugin_default = {
1259
1562
  }
1260
1563
  }
1261
1564
  },
1565
+ debug: {
1566
+ type: "object",
1567
+ additionalProperties: false,
1568
+ description: "Optional opt-in JSONL debug sink for live OpenClaw runs. Writes agenr-only events to a dedicated log file rather than the shared host log.",
1569
+ properties: {
1570
+ enabled: {
1571
+ type: "boolean",
1572
+ description: "Enable or disable the agenr JSONL debug sink. Defaults to false."
1573
+ },
1574
+ logPath: {
1575
+ type: "string",
1576
+ minLength: 1,
1577
+ description: "Optional explicit log-file path. Defaults to agenr-debug.jsonl inside the OpenClaw state directory."
1578
+ },
1579
+ eventLevel: {
1580
+ type: "string",
1581
+ enum: ["basic", "detailed"],
1582
+ description: "Event detail level. Detailed enables bounded top-K candidate breakdowns for recall and before-turn events. Defaults to basic."
1583
+ },
1584
+ perSessionFiles: {
1585
+ type: "boolean",
1586
+ description: "Split one JSONL file per OpenClaw session id. Defaults to false."
1587
+ },
1588
+ maxTopCandidates: {
1589
+ type: "integer",
1590
+ minimum: 1,
1591
+ maximum: 25,
1592
+ description: "Cap for top-K candidate breakdowns included in detailed events. Defaults to 10."
1593
+ }
1594
+ }
1595
+ },
1262
1596
  memoryPolicy: {
1263
1597
  type: "object",
1264
1598
  additionalProperties: false,
1265
1599
  description: "Optional runtime overrides for claim-aware read behavior exposed by the OpenClaw adapter.",
1266
1600
  properties: {
1601
+ sessionStart: {
1602
+ type: "object",
1603
+ additionalProperties: false,
1604
+ description: "Optional session-start overrides for prompt-time memory injection behavior.",
1605
+ properties: {
1606
+ relevantDurableMemory: {
1607
+ type: "boolean",
1608
+ description: "Enable or disable artifact-grounded Relevant Durable Memory injection at session start. Defaults to true."
1609
+ }
1610
+ }
1611
+ },
1612
+ beforeTurn: {
1613
+ type: "object",
1614
+ additionalProperties: false,
1615
+ description: "Optional before-turn overrides for proactive prompt-time memory injection behavior.",
1616
+ properties: {
1617
+ enabled: {
1618
+ type: "boolean",
1619
+ description: "Enable or disable the proactive before-turn memory patch. Defaults to true."
1620
+ },
1621
+ procedureSuggestion: {
1622
+ type: "boolean",
1623
+ description: "Enable or disable proactive high-confidence procedure suggestion inside the before-turn patch. Defaults to true."
1624
+ },
1625
+ maxDurableEntries: {
1626
+ type: "integer",
1627
+ minimum: 1,
1628
+ description: "Normal durable-item cap for before-turn recall. Defaults to 1 and only expands when all surfaced items are very high confidence."
1629
+ },
1630
+ recallThreshold: {
1631
+ type: "number",
1632
+ minimum: 0,
1633
+ maximum: 1,
1634
+ description: "Durable-recall score threshold required before an entry can surface during before-turn recall. Defaults to 0.6."
1635
+ },
1636
+ highConfidenceRecallThreshold: {
1637
+ type: "number",
1638
+ minimum: 0,
1639
+ maximum: 1,
1640
+ description: "Durable-recall score threshold required before before-turn recall can expand beyond the normal durable-item cap. Defaults to 0.85."
1641
+ },
1642
+ procedureThreshold: {
1643
+ type: "number",
1644
+ minimum: 0,
1645
+ maximum: 1,
1646
+ description: "Procedure-recall score threshold required before a proactive procedure can surface. Defaults to 0.72."
1647
+ }
1648
+ }
1649
+ },
1267
1650
  slotPolicies: {
1268
1651
  type: "object",
1269
1652
  additionalProperties: false,
@@ -1292,6 +1675,10 @@ var openclaw_plugin_default = {
1292
1675
  var manifest = openclaw_plugin_default;
1293
1676
  var DEFAULT_STORE_NUDGE_THRESHOLD = 8;
1294
1677
  var DEFAULT_STORE_NUDGE_MAX_PER_SESSION = 5;
1678
+ var DEFAULT_DEBUG_EVENT_LEVEL = "basic";
1679
+ var DEFAULT_DEBUG_PER_SESSION_FILES = false;
1680
+ var DEFAULT_DEBUG_MAX_TOP_CANDIDATES = 10;
1681
+ var MAX_DEBUG_MAX_TOP_CANDIDATES = 25;
1295
1682
  function normalizeAgenrOpenClawPluginConfig(value) {
1296
1683
  if (value === void 0) {
1297
1684
  return { ok: true, value: {} };
@@ -1339,7 +1726,11 @@ function normalizeAgenrOpenClawPluginConfig(value) {
1339
1726
  if (!memoryPolicyResult.ok) {
1340
1727
  errors.push(...memoryPolicyResult.errors);
1341
1728
  }
1342
- const allowedKeys = /* @__PURE__ */ new Set(["dbPath", "configPath", "continuityModel", "episodeModel", "claimExtractionModel", "storeNudge", "memoryPolicy"]);
1729
+ const debugResult = normalizeDebugConfig(value.debug);
1730
+ if (!debugResult.ok) {
1731
+ errors.push(...debugResult.errors);
1732
+ }
1733
+ const allowedKeys = /* @__PURE__ */ new Set(["dbPath", "configPath", "continuityModel", "episodeModel", "claimExtractionModel", "storeNudge", "memoryPolicy", "debug"]);
1343
1734
  for (const key of Object.keys(value)) {
1344
1735
  if (!allowedKeys.has(key)) {
1345
1736
  errors.push(`unknown config field: ${key}`);
@@ -1357,7 +1748,8 @@ function normalizeAgenrOpenClawPluginConfig(value) {
1357
1748
  ...episodeModel ? { episodeModel } : {},
1358
1749
  ...claimExtractionModel ? { claimExtractionModel } : {},
1359
1750
  ...storeNudgeResult.ok && storeNudgeResult.value ? { storeNudge: storeNudgeResult.value } : {},
1360
- ...memoryPolicyResult.ok && memoryPolicyResult.value ? { memoryPolicy: memoryPolicyResult.value } : {}
1751
+ ...memoryPolicyResult.ok && memoryPolicyResult.value ? { memoryPolicy: memoryPolicyResult.value } : {},
1752
+ ...debugResult.ok && debugResult.value ? { debug: debugResult.value } : {}
1361
1753
  }
1362
1754
  };
1363
1755
  }
@@ -1381,6 +1773,78 @@ function createAgenrOpenClawPluginConfigSchema() {
1381
1773
  function isRecord(value) {
1382
1774
  return typeof value === "object" && value !== null && !Array.isArray(value);
1383
1775
  }
1776
+ function resolveDebugConfig(value) {
1777
+ const logPath = value?.logPath?.trim();
1778
+ return {
1779
+ enabled: value?.enabled ?? false,
1780
+ ...logPath ? { logPath } : {},
1781
+ eventLevel: value?.eventLevel ?? DEFAULT_DEBUG_EVENT_LEVEL,
1782
+ perSessionFiles: value?.perSessionFiles ?? DEFAULT_DEBUG_PER_SESSION_FILES,
1783
+ maxTopCandidates: value?.maxTopCandidates ?? DEFAULT_DEBUG_MAX_TOP_CANDIDATES
1784
+ };
1785
+ }
1786
+ function normalizeDebugConfig(value) {
1787
+ if (value === void 0) {
1788
+ return { ok: true, value: void 0 };
1789
+ }
1790
+ if (!isRecord(value)) {
1791
+ return { ok: false, errors: ["debug must be an object when provided"] };
1792
+ }
1793
+ const errors = [];
1794
+ const enabled = normalizeOptionalBoolean(value.enabled, "debug.enabled", errors);
1795
+ const logPathRaw = value.logPath;
1796
+ let logPath;
1797
+ if (logPathRaw !== void 0) {
1798
+ if (typeof logPathRaw !== "string" || logPathRaw.trim().length === 0) {
1799
+ errors.push("debug.logPath must be a non-empty string when provided");
1800
+ } else {
1801
+ logPath = logPathRaw.trim();
1802
+ }
1803
+ }
1804
+ const eventLevel = normalizeOptionalDebugEventLevel(value.eventLevel, errors);
1805
+ const perSessionFiles = normalizeOptionalBoolean(value.perSessionFiles, "debug.perSessionFiles", errors);
1806
+ const maxTopCandidates = normalizeOptionalTopCandidateCap(value.maxTopCandidates, errors);
1807
+ const allowedKeys = /* @__PURE__ */ new Set(["enabled", "logPath", "eventLevel", "perSessionFiles", "maxTopCandidates"]);
1808
+ for (const key of Object.keys(value)) {
1809
+ if (!allowedKeys.has(key)) {
1810
+ errors.push(`unknown config field: debug.${key}`);
1811
+ }
1812
+ }
1813
+ if (errors.length > 0) {
1814
+ return { ok: false, errors };
1815
+ }
1816
+ const normalized = {
1817
+ ...enabled !== void 0 ? { enabled } : {},
1818
+ ...logPath !== void 0 ? { logPath } : {},
1819
+ ...eventLevel !== void 0 ? { eventLevel } : {},
1820
+ ...perSessionFiles !== void 0 ? { perSessionFiles } : {},
1821
+ ...maxTopCandidates !== void 0 ? { maxTopCandidates } : {}
1822
+ };
1823
+ return {
1824
+ ok: true,
1825
+ value: Object.keys(normalized).length > 0 ? normalized : void 0
1826
+ };
1827
+ }
1828
+ function normalizeOptionalDebugEventLevel(value, errors) {
1829
+ if (value === void 0) {
1830
+ return void 0;
1831
+ }
1832
+ if (value === "basic" || value === "detailed") {
1833
+ return value;
1834
+ }
1835
+ errors.push('debug.eventLevel must be "basic" or "detailed" when provided');
1836
+ return void 0;
1837
+ }
1838
+ function normalizeOptionalTopCandidateCap(value, errors) {
1839
+ if (value === void 0) {
1840
+ return void 0;
1841
+ }
1842
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0 || value > MAX_DEBUG_MAX_TOP_CANDIDATES) {
1843
+ errors.push(`debug.maxTopCandidates must be an integer between 1 and ${MAX_DEBUG_MAX_TOP_CANDIDATES} when provided`);
1844
+ return void 0;
1845
+ }
1846
+ return value;
1847
+ }
1384
1848
  function resolveStoreNudgeConfig(value) {
1385
1849
  return {
1386
1850
  enabled: value?.enabled ?? true,
@@ -1430,7 +1894,15 @@ function normalizeMemoryPolicyConfig(value) {
1430
1894
  if (!slotPoliciesResult.ok) {
1431
1895
  errors.push(...slotPoliciesResult.errors);
1432
1896
  }
1433
- const allowedKeys = /* @__PURE__ */ new Set(["slotPolicies"]);
1897
+ const sessionStartResult = normalizeSessionStartMemoryPolicyConfig(value.sessionStart);
1898
+ if (!sessionStartResult.ok) {
1899
+ errors.push(...sessionStartResult.errors);
1900
+ }
1901
+ const beforeTurnResult = normalizeBeforeTurnMemoryPolicyConfig(value.beforeTurn);
1902
+ if (!beforeTurnResult.ok) {
1903
+ errors.push(...beforeTurnResult.errors);
1904
+ }
1905
+ const allowedKeys = /* @__PURE__ */ new Set(["slotPolicies", "sessionStart", "beforeTurn"]);
1434
1906
  for (const key of Object.keys(value)) {
1435
1907
  if (!allowedKeys.has(key)) {
1436
1908
  errors.push(`unknown config field: memoryPolicy.${key}`);
@@ -1441,8 +1913,79 @@ function normalizeMemoryPolicyConfig(value) {
1441
1913
  }
1442
1914
  return {
1443
1915
  ok: true,
1444
- value: slotPoliciesResult.ok && slotPoliciesResult.value ? {
1445
- slotPolicies: slotPoliciesResult.value
1916
+ value: slotPoliciesResult.ok && slotPoliciesResult.value || sessionStartResult.ok && sessionStartResult.value || beforeTurnResult.ok && beforeTurnResult.value ? {
1917
+ ...slotPoliciesResult.ok && slotPoliciesResult.value ? { slotPolicies: slotPoliciesResult.value } : {},
1918
+ ...sessionStartResult.ok && sessionStartResult.value ? { sessionStart: sessionStartResult.value } : {},
1919
+ ...beforeTurnResult.ok && beforeTurnResult.value ? { beforeTurn: beforeTurnResult.value } : {}
1920
+ } : void 0
1921
+ };
1922
+ }
1923
+ function normalizeSessionStartMemoryPolicyConfig(value) {
1924
+ if (value === void 0) {
1925
+ return { ok: true, value: void 0 };
1926
+ }
1927
+ if (!isRecord(value)) {
1928
+ return { ok: false, errors: ["memoryPolicy.sessionStart must be an object when provided"] };
1929
+ }
1930
+ const errors = [];
1931
+ const relevantDurableMemory = normalizeOptionalBoolean(value.relevantDurableMemory, "memoryPolicy.sessionStart.relevantDurableMemory", errors);
1932
+ const allowedKeys = /* @__PURE__ */ new Set(["relevantDurableMemory"]);
1933
+ for (const key of Object.keys(value)) {
1934
+ if (!allowedKeys.has(key)) {
1935
+ errors.push(`unknown config field: memoryPolicy.sessionStart.${key}`);
1936
+ }
1937
+ }
1938
+ if (errors.length > 0) {
1939
+ return { ok: false, errors };
1940
+ }
1941
+ return {
1942
+ ok: true,
1943
+ value: relevantDurableMemory !== void 0 ? { relevantDurableMemory } : void 0
1944
+ };
1945
+ }
1946
+ function normalizeBeforeTurnMemoryPolicyConfig(value) {
1947
+ if (value === void 0) {
1948
+ return { ok: true, value: void 0 };
1949
+ }
1950
+ if (!isRecord(value)) {
1951
+ return { ok: false, errors: ["memoryPolicy.beforeTurn must be an object when provided"] };
1952
+ }
1953
+ const errors = [];
1954
+ const enabled = normalizeOptionalBoolean(value.enabled, "memoryPolicy.beforeTurn.enabled", errors);
1955
+ const procedureSuggestion = normalizeOptionalBoolean(value.procedureSuggestion, "memoryPolicy.beforeTurn.procedureSuggestion", errors);
1956
+ const maxDurableEntries = normalizeOptionalPositiveInteger(value.maxDurableEntries, "memoryPolicy.beforeTurn.maxDurableEntries", errors);
1957
+ const recallThreshold = normalizeOptionalUnitInterval(value.recallThreshold, "memoryPolicy.beforeTurn.recallThreshold", errors);
1958
+ const highConfidenceRecallThreshold = normalizeOptionalUnitInterval(
1959
+ value.highConfidenceRecallThreshold,
1960
+ "memoryPolicy.beforeTurn.highConfidenceRecallThreshold",
1961
+ errors
1962
+ );
1963
+ const procedureThreshold = normalizeOptionalUnitInterval(value.procedureThreshold, "memoryPolicy.beforeTurn.procedureThreshold", errors);
1964
+ const allowedKeys = /* @__PURE__ */ new Set([
1965
+ "enabled",
1966
+ "procedureSuggestion",
1967
+ "maxDurableEntries",
1968
+ "recallThreshold",
1969
+ "highConfidenceRecallThreshold",
1970
+ "procedureThreshold"
1971
+ ]);
1972
+ for (const key of Object.keys(value)) {
1973
+ if (!allowedKeys.has(key)) {
1974
+ errors.push(`unknown config field: memoryPolicy.beforeTurn.${key}`);
1975
+ }
1976
+ }
1977
+ if (errors.length > 0) {
1978
+ return { ok: false, errors };
1979
+ }
1980
+ return {
1981
+ ok: true,
1982
+ value: enabled !== void 0 || procedureSuggestion !== void 0 || maxDurableEntries !== void 0 || recallThreshold !== void 0 || highConfidenceRecallThreshold !== void 0 || procedureThreshold !== void 0 ? {
1983
+ ...enabled !== void 0 ? { enabled } : {},
1984
+ ...procedureSuggestion !== void 0 ? { procedureSuggestion } : {},
1985
+ ...maxDurableEntries !== void 0 ? { maxDurableEntries } : {},
1986
+ ...recallThreshold !== void 0 ? { recallThreshold } : {},
1987
+ ...highConfidenceRecallThreshold !== void 0 ? { highConfidenceRecallThreshold } : {},
1988
+ ...procedureThreshold !== void 0 ? { procedureThreshold } : {}
1446
1989
  } : void 0
1447
1990
  };
1448
1991
  }
@@ -1512,6 +2055,16 @@ function normalizeOptionalPositiveInteger(value, label, errors) {
1512
2055
  }
1513
2056
  return value;
1514
2057
  }
2058
+ function normalizeOptionalUnitInterval(value, label, errors) {
2059
+ if (value === void 0) {
2060
+ return void 0;
2061
+ }
2062
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
2063
+ errors.push(`${label} must be a number between 0 and 1 when provided`);
2064
+ return void 0;
2065
+ }
2066
+ return value;
2067
+ }
1515
2068
 
1516
2069
  // src/adapters/openclaw/format/prompt-section.ts
1517
2070
  var MEMORY_TOOL_NAMES = {
@@ -1535,10 +2088,11 @@ function buildAgenrMemoryPromptSection({
1535
2088
  );
1536
2089
  const lines = [
1537
2090
  "## Memory Recall",
1538
- "Before answering anything about prior work, decisions, preferences, people, dates, unfinished work, or past sessions, call agenr_recall first. Session-start recall is automatic; use agenr_recall mid-session when you need context you do not already have.",
2091
+ "Before answering anything about prior work, decisions, preferences, people, dates, unfinished work, or past sessions, call agenr_recall first. Session-start recall is automatic, and conservative before-turn recall may also appear as injected background context; use agenr_recall mid-session when you need context you do not already have.",
1539
2092
  "agenr_recall supports exact fact recall plus historical and episodic recall behind one tool: use mode=entries for exact facts, decisions, thresholds, and versions; use mode=auto for prior-state questions like what was the previous approach, what did we use before, or what changed from X to Y; use mode=episodes when you explicitly want session narrative recall.",
1540
2093
  "For temporal narrative questions, put the time phrase in the query itself: examples include yesterday, last week, this month, 2 weeks ago, or in March.",
1541
2094
  "One focused agenr_recall call with the right scope beats several broad ones.",
2095
+ "When Agenr injects memory automatically, treat it as non-user background context and use it silently when relevant rather than forcing it into the reply.",
1542
2096
  "Memory authority, strongest to weakest:",
1543
2097
  "- Durable entries are the canonical record for verified facts, decisions, preferences, and lessons unless live evidence contradicts them.",
1544
2098
  "- Episode recall explains what happened in completed sessions, but it is a narrative summary, not an exact log.",
@@ -1800,6 +2354,267 @@ function hasNonEmptyString(value) {
1800
2354
  return readString(value) !== void 0;
1801
2355
  }
1802
2356
 
2357
+ // src/adapters/openclaw/hooks/before-prompt-build.ts
2358
+ import { randomUUID as randomUUID2 } from "crypto";
2359
+
2360
+ // src/app/session-start/service.ts
2361
+ var DEFAULT_MAX_CORE_ENTRIES = 4;
2362
+ var DEFAULT_MAX_ARTIFACT_RECALL_ENTRIES = 3;
2363
+ var DEFAULT_MAX_DURABLE_ENTRIES = 5;
2364
+ var DEFAULT_MAX_ARTIFACT_CHARS = 1200;
2365
+ async function runSessionStart(input, deps) {
2366
+ const policy = normalizePolicy(input.policy);
2367
+ const contextSections = buildContextSections(input);
2368
+ const coreEntries = await deps.repository.listCoreEntries(policy.maxCoreEntries);
2369
+ const coreItems = coreEntries.map((entry) => buildCorePatchItem(entry));
2370
+ const diagnostics = {
2371
+ coreCandidateCount: coreEntries.length,
2372
+ artifactRecallCandidateCount: 0,
2373
+ artifactRecallUsed: false,
2374
+ notices: []
2375
+ };
2376
+ const artifactRecallQuery = policy.enableArtifactRecall ? buildArtifactRecallQuery(contextSections, policy.maxArtifactChars) : void 0;
2377
+ if (!policy.enableArtifactRecall) {
2378
+ diagnostics.notices.push("Artifact-grounded durable recall disabled by session-start policy.");
2379
+ }
2380
+ const artifactRecallItems = artifactRecallQuery ? await runArtifactRecallSelection(artifactRecallQuery, input.sessionKey, policy, deps, diagnostics) : [];
2381
+ const durableMemory = assignRanks(mergeDurableMemory(coreItems, artifactRecallItems, policy.maxDurableEntries));
2382
+ return {
2383
+ contextSections,
2384
+ durableMemory,
2385
+ diagnostics
2386
+ };
2387
+ }
2388
+ function buildContextSections(input) {
2389
+ const sections = [];
2390
+ const continuitySummaryText = normalizeOptionalString(input.continuitySummaryText);
2391
+ if (continuitySummaryText) {
2392
+ sections.push({
2393
+ kind: "continuity_summary",
2394
+ title: "Previous session summary",
2395
+ content: continuitySummaryText
2396
+ });
2397
+ }
2398
+ const recentSessionText = normalizeOptionalString(input.recentSessionText);
2399
+ if (recentSessionText) {
2400
+ sections.push({
2401
+ kind: "recent_session",
2402
+ title: "Recent session",
2403
+ content: recentSessionText
2404
+ });
2405
+ }
2406
+ return sections;
2407
+ }
2408
+ async function runArtifactRecallSelection(query, sessionKey, policy, deps, diagnostics) {
2409
+ diagnostics.artifactRecallUsed = true;
2410
+ diagnostics.artifactRecallQuery = query;
2411
+ let artifactRecallTrace;
2412
+ try {
2413
+ const recalled = await recall(
2414
+ {
2415
+ text: query,
2416
+ limit: policy.maxArtifactRecallEntries,
2417
+ threshold: policy.recallThreshold,
2418
+ sessionKey
2419
+ },
2420
+ deps.recall,
2421
+ {
2422
+ trace: {
2423
+ reportSummary(summary) {
2424
+ artifactRecallTrace = summary;
2425
+ }
2426
+ },
2427
+ slotPolicyConfig: deps.slotPolicyConfig
2428
+ }
2429
+ );
2430
+ diagnostics.artifactRecallTrace = artifactRecallTrace;
2431
+ diagnostics.artifactRecallCandidateCount = recalled.length;
2432
+ if (artifactRecallTrace?.degraded.notices.length) {
2433
+ diagnostics.notices.push(...artifactRecallTrace.degraded.notices);
2434
+ }
2435
+ return recalled.map((item) => buildArtifactRecallPatchItem(item, deps));
2436
+ } catch (error) {
2437
+ diagnostics.artifactRecallTrace = artifactRecallTrace;
2438
+ diagnostics.notices.push(`Artifact-grounded durable recall failed: ${formatErrorMessage3(error)}`);
2439
+ return [];
2440
+ }
2441
+ }
2442
+ function buildCorePatchItem(entry) {
2443
+ return {
2444
+ rank: 0,
2445
+ entry,
2446
+ sourceKind: "core",
2447
+ whySurfaced: {
2448
+ summary: `always-on core memory; importance ${entry.importance}`,
2449
+ reasons: ["always-on core memory", `importance ${entry.importance}`, `expiry ${entry.expiry}`]
2450
+ },
2451
+ memoryState: resolveMemoryState(entry),
2452
+ claimStatus: resolveClaimStatus(entry),
2453
+ freshnessLabel: buildFreshnessLabel(entry),
2454
+ ...buildProvenanceSummary(entry) ? { provenanceSummary: buildProvenanceSummary(entry) } : {}
2455
+ };
2456
+ }
2457
+ function buildArtifactRecallPatchItem(recalled, deps) {
2458
+ const projected = projectClaimCentricRecallEntry(recalled, {
2459
+ slotPolicyConfig: deps.slotPolicyConfig
2460
+ });
2461
+ return {
2462
+ rank: 0,
2463
+ entry: recalled.entry,
2464
+ sourceKind: "artifact_recall",
2465
+ score: recalled.score,
2466
+ whySurfaced: projected.whySurfaced,
2467
+ memoryState: projected.memoryState,
2468
+ claimStatus: projected.claimStatus,
2469
+ freshnessLabel: projected.freshness.label,
2470
+ ...formatProjectedProvenance(projected.provenance) ? { provenanceSummary: formatProjectedProvenance(projected.provenance) } : {}
2471
+ };
2472
+ }
2473
+ function buildArtifactRecallQuery(sections, maxChars) {
2474
+ if (sections.length === 0 || maxChars <= 0) {
2475
+ return void 0;
2476
+ }
2477
+ let remaining = maxChars;
2478
+ const parts = [];
2479
+ for (const section of sections) {
2480
+ if (remaining <= 0) {
2481
+ break;
2482
+ }
2483
+ const normalizedContent = normalizeWhitespace(section.content);
2484
+ if (normalizedContent.length === 0) {
2485
+ continue;
2486
+ }
2487
+ const labeled = `${section.title}: ${normalizedContent}`;
2488
+ const truncated = truncate2(labeled, remaining);
2489
+ if (truncated.length === 0) {
2490
+ continue;
2491
+ }
2492
+ parts.push(truncated);
2493
+ remaining -= truncated.length;
2494
+ }
2495
+ const query = normalizeWhitespace(parts.join("\n"));
2496
+ return query.length > 0 ? query : void 0;
2497
+ }
2498
+ function mergeDurableMemory(coreItems, artifactRecallItems, maxDurableEntries) {
2499
+ const merged = [];
2500
+ const seenEntryIds = /* @__PURE__ */ new Set();
2501
+ for (const item of [...coreItems, ...artifactRecallItems]) {
2502
+ if (seenEntryIds.has(item.entry.id)) {
2503
+ continue;
2504
+ }
2505
+ seenEntryIds.add(item.entry.id);
2506
+ merged.push(item);
2507
+ if (merged.length >= maxDurableEntries) {
2508
+ break;
2509
+ }
2510
+ }
2511
+ return merged;
2512
+ }
2513
+ function assignRanks(items) {
2514
+ return items.map((item, index) => ({
2515
+ ...item,
2516
+ rank: index + 1
2517
+ }));
2518
+ }
2519
+ function normalizePolicy(policy) {
2520
+ const maxCoreEntries = normalizeCount(policy?.maxCoreEntries, DEFAULT_MAX_CORE_ENTRIES);
2521
+ const maxArtifactRecallEntries = normalizeCount(policy?.maxArtifactRecallEntries, DEFAULT_MAX_ARTIFACT_RECALL_ENTRIES);
2522
+ const maxDurableEntries = Math.max(maxCoreEntries, normalizeCount(policy?.maxDurableEntries, DEFAULT_MAX_DURABLE_ENTRIES));
2523
+ return {
2524
+ maxCoreEntries,
2525
+ enableArtifactRecall: policy?.enableArtifactRecall !== false,
2526
+ maxArtifactRecallEntries,
2527
+ maxDurableEntries,
2528
+ maxArtifactChars: normalizeCount(policy?.maxArtifactChars, DEFAULT_MAX_ARTIFACT_CHARS),
2529
+ recallThreshold: normalizeThreshold(policy?.recallThreshold)
2530
+ };
2531
+ }
2532
+ function normalizeCount(value, fallback) {
2533
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2534
+ return fallback;
2535
+ }
2536
+ return Math.max(0, Math.trunc(value));
2537
+ }
2538
+ function normalizeThreshold(value) {
2539
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2540
+ return 0;
2541
+ }
2542
+ return Math.min(1, Math.max(0, value));
2543
+ }
2544
+ function resolveMemoryState(entry) {
2545
+ if (entry.superseded_by) {
2546
+ return "superseded";
2547
+ }
2548
+ if (entry.retired || entry.valid_to) {
2549
+ return "historical";
2550
+ }
2551
+ return "current";
2552
+ }
2553
+ function resolveClaimStatus(entry) {
2554
+ if (!normalizeOptionalString(entry.claim_key)) {
2555
+ return "no_key";
2556
+ }
2557
+ return entry.claim_key_status ?? "legacy";
2558
+ }
2559
+ function buildFreshnessLabel(entry) {
2560
+ const parts = [`created ${entry.created_at}`];
2561
+ const validFrom = normalizeOptionalString(entry.valid_from);
2562
+ const validTo = normalizeOptionalString(entry.valid_to);
2563
+ if (validFrom || validTo) {
2564
+ parts.push(`valid ${validFrom ?? "?"} -> ${validTo ?? "ongoing"}`);
2565
+ }
2566
+ return parts.join(" | ");
2567
+ }
2568
+ function buildProvenanceSummary(entry) {
2569
+ const parts = [
2570
+ entry.superseded_by ? `superseded_by=${entry.superseded_by}` : void 0,
2571
+ entry.supersession_kind ? `kind=${entry.supersession_kind}` : void 0,
2572
+ entry.supersession_reason ? `reason=${entry.supersession_reason}` : void 0,
2573
+ entry.claim_support_source_kind ? `support=${entry.claim_support_source_kind}` : void 0,
2574
+ entry.claim_support_mode ? `support_mode=${entry.claim_support_mode}` : void 0,
2575
+ entry.claim_support_observed_at ? `observed=${entry.claim_support_observed_at}` : void 0,
2576
+ entry.claim_support_locator ? `locator=${entry.claim_support_locator}` : void 0
2577
+ ].filter((value) => value !== void 0);
2578
+ return parts.length > 0 ? parts.join(" | ") : void 0;
2579
+ }
2580
+ function formatProjectedProvenance(provenance) {
2581
+ const parts = [
2582
+ provenance.supersededById ? `superseded_by=${provenance.supersededById}` : void 0,
2583
+ provenance.supersessionKind ? `kind=${provenance.supersessionKind}` : void 0,
2584
+ provenance.supersessionReason ? `reason=${provenance.supersessionReason}` : void 0,
2585
+ provenance.supportSourceKind ? `support=${provenance.supportSourceKind}` : void 0,
2586
+ provenance.supportMode ? `support_mode=${provenance.supportMode}` : void 0,
2587
+ provenance.supportObservedAt ? `observed=${provenance.supportObservedAt}` : void 0,
2588
+ provenance.supportLocator ? `locator=${provenance.supportLocator}` : void 0
2589
+ ].filter((value) => value !== void 0);
2590
+ return parts.length > 0 ? parts.join(" | ") : void 0;
2591
+ }
2592
+ function normalizeOptionalString(value) {
2593
+ const normalized = value?.trim();
2594
+ return normalized && normalized.length > 0 ? normalized : void 0;
2595
+ }
2596
+ function normalizeWhitespace(value) {
2597
+ return value.replace(/\s+/g, " ").trim();
2598
+ }
2599
+ function truncate2(value, maxChars) {
2600
+ if (maxChars <= 0) {
2601
+ return "";
2602
+ }
2603
+ if (value.length <= maxChars) {
2604
+ return value;
2605
+ }
2606
+ return `${value.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
2607
+ }
2608
+ function formatErrorMessage3(error) {
2609
+ if (error instanceof Error) {
2610
+ return error.message;
2611
+ }
2612
+ return String(error);
2613
+ }
2614
+
2615
+ // src/adapters/openclaw/hooks/before-prompt-build.ts
2616
+ import path5 from "path";
2617
+
1803
2618
  // src/adapters/openclaw/episode/episode-writer.ts
1804
2619
  import { resolveAgentEffectiveModelPrimary as resolveAgentEffectiveModelPrimary2, resolveDefaultAgentId as resolveDefaultAgentId2 } from "openclaw/plugin-sdk/agent-runtime";
1805
2620
 
@@ -1809,7 +2624,7 @@ import { completeSimple, getModel } from "@mariozechner/pi-ai";
1809
2624
  // src/adapters/openclaw/embedded-agent/task-runner.ts
1810
2625
  import * as fs from "fs/promises";
1811
2626
  import os from "os";
1812
- import path from "path";
2627
+ import path2 from "path";
1813
2628
  import { DEFAULT_MODEL, DEFAULT_PROVIDER, parseModelRef, resolveAgentEffectiveModelPrimary, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
1814
2629
  function resolveOpenClawEmbeddedAgentExecution(params) {
1815
2630
  const agentId = params.requestedAgentId?.trim() || resolveDefaultAgentId(params.openClaw.config);
@@ -1852,7 +2667,7 @@ async function createOpenClawLlmClient(openClaw, modelRef, label = "model overri
1852
2667
  provider: execution.provider,
1853
2668
  cfg: openClaw.config
1854
2669
  });
1855
- const apiKey = normalizeOptionalString(auth.apiKey);
2670
+ const apiKey = normalizeOptionalString2(auth.apiKey);
1856
2671
  if (!apiKey) {
1857
2672
  throw new Error(`OpenClaw auth did not resolve an API-key-compatible credential for ${execution.provider} (source=${auth.source}, mode=${auth.mode}).`);
1858
2673
  }
@@ -1897,7 +2712,7 @@ function stripCodeFence(text) {
1897
2712
  const match = /^```(?:json)?\s*([\s\S]+?)\s*```$/iu.exec(trimmed);
1898
2713
  return match?.[1]?.trim() ?? trimmed;
1899
2714
  }
1900
- function normalizeOptionalString(value) {
2715
+ function normalizeOptionalString2(value) {
1901
2716
  const normalized = value?.trim();
1902
2717
  return normalized ? normalized : void 0;
1903
2718
  }
@@ -2216,33 +3031,51 @@ function truncateSubject(subject) {
2216
3031
  }
2217
3032
 
2218
3033
  // src/adapters/openclaw/format/recall-format.ts
2219
- var MAX_CONTENT_CHARS = 280;
2220
- function formatAgenrSessionStartRecall(recall) {
2221
- const sections = buildSections(recall);
2222
- if (sections.length === 0) {
3034
+ var MAX_CONTENT_CHARS = 220;
3035
+ function formatAgenrSessionStartRecall(patch) {
3036
+ if (patch.contextSections.length === 0 && patch.durableMemory.length === 0) {
2223
3037
  return "";
2224
3038
  }
2225
- const lines = ["## Agenr Session Recall", "Use this as prior context. Confirm anything important if the current conversation conflicts with it.", ""];
2226
- for (const section of sections) {
2227
- lines.push(`### ${section.title}`);
2228
- for (const item of section.entries) {
2229
- lines.push(formatEntryHeader(item));
2230
- lines.push(formatEntryBody(item.entry));
2231
- }
3039
+ const lines = [];
3040
+ for (const section of patch.contextSections) {
3041
+ lines.push(`## ${section.title}`);
3042
+ lines.push(section.content);
2232
3043
  lines.push("");
2233
3044
  }
3045
+ const durableSections = buildSections(patch);
3046
+ if (durableSections.length > 0) {
3047
+ const recallLines = [
3048
+ "## Agenr Session Recall",
3049
+ "Use this as prior context. Confirm anything important if the current conversation conflicts with it.",
3050
+ ""
3051
+ ];
3052
+ for (const section of durableSections) {
3053
+ recallLines.push(`### ${section.title}`);
3054
+ for (const item of section.entries) {
3055
+ recallLines.push(formatEntryHeader(item));
3056
+ recallLines.push(...formatEntryBodyLines(item));
3057
+ }
3058
+ recallLines.push("");
3059
+ }
3060
+ lines.push(wrapAgenrMemoryContext(recallLines.join("\n").trim()));
3061
+ }
2234
3062
  return lines.join("\n").trim();
2235
3063
  }
2236
- function buildSections(recall) {
3064
+ function buildSections(patch) {
2237
3065
  const sections = [];
2238
- const coreEntries = recall.core.map((entry) => ({ entry }));
3066
+ const coreEntries = patch.durableMemory.filter((item) => item.sourceKind === "core");
2239
3067
  if (coreEntries.length > 0) {
2240
3068
  sections.push({ title: "Core Memory", entries: coreEntries });
2241
3069
  }
3070
+ const artifactRecallEntries = patch.durableMemory.filter((item) => item.sourceKind === "artifact_recall");
3071
+ if (artifactRecallEntries.length > 0) {
3072
+ sections.push({ title: "Relevant Durable Memory", entries: artifactRecallEntries });
3073
+ }
2242
3074
  return sections;
2243
3075
  }
2244
3076
  function formatEntryHeader(item) {
2245
3077
  const metadata = [
3078
+ `rank ${item.rank}`,
2246
3079
  item.entry.id,
2247
3080
  item.entry.type,
2248
3081
  item.entry.expiry,
@@ -2251,18 +3084,20 @@ function formatEntryHeader(item) {
2251
3084
  ].filter((value) => value !== void 0);
2252
3085
  return `- [${metadata.join(" | ")}] ${item.entry.subject}`;
2253
3086
  }
2254
- function formatEntryBody(entry) {
2255
- const content = truncate2(entry.content.trim(), MAX_CONTENT_CHARS);
2256
- const extra = [
2257
- entry.tags.length > 0 ? `tags: ${entry.tags.join(", ")}` : void 0,
2258
- entry.created_at ? `created: ${entry.created_at.slice(0, 10)}` : void 0
3087
+ function formatEntryBodyLines(item) {
3088
+ const lines = [` ${truncate3(item.entry.content.trim(), MAX_CONTENT_CHARS)}`];
3089
+ lines.push(` why: ${item.whySurfaced.summary}`);
3090
+ const metadata = [
3091
+ item.entry.tags.length > 0 ? `tags: ${item.entry.tags.join(", ")}` : void 0,
3092
+ item.freshnessLabel ? `freshness: ${item.freshnessLabel}` : void 0,
3093
+ item.provenanceSummary ? `provenance: ${truncate3(item.provenanceSummary, MAX_CONTENT_CHARS)}` : void 0
2259
3094
  ].filter((value) => value !== void 0);
2260
- if (extra.length === 0) {
2261
- return ` ${content}`;
3095
+ if (metadata.length > 0) {
3096
+ lines.push(` ${metadata.join(" | ")}`);
2262
3097
  }
2263
- return ` ${content} (${extra.join(" | ")})`;
3098
+ return lines;
2264
3099
  }
2265
- function truncate2(value, maxChars) {
3100
+ function truncate3(value, maxChars) {
2266
3101
  if (value.length <= maxChars) {
2267
3102
  return value;
2268
3103
  }
@@ -2275,14 +3110,14 @@ import { resolveAgentEffectiveModelPrimary as resolveAgentEffectiveModelPrimary3
2275
3110
 
2276
3111
  // src/adapters/openclaw/session/continuity/continuity-summary-reader.ts
2277
3112
  import * as fs2 from "fs/promises";
2278
- import path2 from "path";
3113
+ import path3 from "path";
2279
3114
  function resolveOpenClawContinuitySummaryPath(sessionFile, logger) {
2280
3115
  const normalizedSessionFile = sessionFile.trim();
2281
3116
  const sessionId = deriveOpenClawSessionIdFromFilePath(normalizedSessionFile, logger);
2282
3117
  if (!sessionId) {
2283
3118
  return void 0;
2284
3119
  }
2285
- const continuitySummaryPath = path2.join(path2.dirname(normalizedSessionFile), `${sessionId}.continuity-summary.md`);
3120
+ const continuitySummaryPath = path3.join(path3.dirname(normalizedSessionFile), `${sessionId}.continuity-summary.md`);
2286
3121
  debugLog(logger, "continuity-summary-reader", `resolved continuity summary path for session=${sessionId}: ${continuitySummaryPath}`);
2287
3122
  return continuitySummaryPath;
2288
3123
  }
@@ -2476,10 +3311,10 @@ async function generateAndWriteOpenClawContinuitySummary(params) {
2476
3311
  durationMs
2477
3312
  };
2478
3313
  }
2479
- debugLog2(params.logger, "continuity-summary", `continuity summary generation error for file=${sessionFile}: ${formatErrorMessage3(error)}`);
3314
+ debugLog2(params.logger, "continuity-summary", `continuity summary generation error for file=${sessionFile}: ${formatErrorMessage4(error)}`);
2480
3315
  return {
2481
3316
  status: "failed",
2482
- reason: formatErrorMessage3(error),
3317
+ reason: formatErrorMessage4(error),
2483
3318
  continuitySummaryPath,
2484
3319
  messageCount: cleanedMessages.length,
2485
3320
  transcriptChars: normalizedTranscript.length,
@@ -2498,7 +3333,7 @@ function normalizeContinuitySummary(value) {
2498
3333
  const trimmed = value.trim();
2499
3334
  return trimmed.replace(/^# .+\n+/u, "").trim();
2500
3335
  }
2501
- function formatErrorMessage3(error) {
3336
+ function formatErrorMessage4(error) {
2502
3337
  return error instanceof Error ? error.message : String(error);
2503
3338
  }
2504
3339
  function resolveOpenClawSummaryModelRef(openClaw, agentId, modelOverride) {
@@ -2538,7 +3373,7 @@ function trimToBoundary(value, fromStart) {
2538
3373
 
2539
3374
  // src/adapters/openclaw/session/continuity/predecessor-resolver.ts
2540
3375
  import * as fs4 from "fs/promises";
2541
- import path3 from "path";
3376
+ import path4 from "path";
2542
3377
 
2543
3378
  // src/adapters/openclaw/session/session-key-parser.ts
2544
3379
  var AGENT_SESSION_KEY_PATTERN = /^agent:([^:]+):(.+)$/i;
@@ -2761,15 +3596,15 @@ async function findResumedFromTranscriptCandidates(sessionsDir, sessionId, logge
2761
3596
  }
2762
3597
  const fileName = entry.name.trim();
2763
3598
  if (fileName === prefix) {
2764
- liveMatches.push(path3.join(sessionsDir, fileName));
3599
+ liveMatches.push(path4.join(sessionsDir, fileName));
2765
3600
  continue;
2766
3601
  }
2767
3602
  if (fileName.startsWith(`${prefix}.reset.`)) {
2768
- resetMatches.push(path3.join(sessionsDir, fileName));
3603
+ resetMatches.push(path4.join(sessionsDir, fileName));
2769
3604
  continue;
2770
3605
  }
2771
3606
  if (fileName.startsWith(`${prefix}.deleted.`)) {
2772
- deletedMatches.push(path3.join(sessionsDir, fileName));
3607
+ deletedMatches.push(path4.join(sessionsDir, fileName));
2773
3608
  }
2774
3609
  }
2775
3610
  const ordered = [
@@ -2945,7 +3780,7 @@ function resolveOpenClawSessionsDirectory(ctx, parsedAgentId, resolveStateDir) {
2945
3780
  if (!agentId) {
2946
3781
  return void 0;
2947
3782
  }
2948
- return path3.join(resolveStateDir(process.env), "agents", agentId, "sessions");
3783
+ return path4.join(resolveStateDir(process.env), "agents", agentId, "sessions");
2949
3784
  }
2950
3785
  function isSameTuiLane(currentStableLane, candidateStableLane) {
2951
3786
  if (!currentStableLane || !candidateStableLane) {
@@ -2957,7 +3792,7 @@ function isSameTuiLane(currentStableLane, candidateStableLane) {
2957
3792
  return currentStableLane === candidateStableLane;
2958
3793
  }
2959
3794
  function compareArchivePathsDescending(left, right) {
2960
- return path3.basename(right).localeCompare(path3.basename(left));
3795
+ return path4.basename(right).localeCompare(path4.basename(left));
2961
3796
  }
2962
3797
  function debugLog3(logger, subsystem, message) {
2963
3798
  logger?.debug?.(`[agenr] ${subsystem}: ${message}`);
@@ -2983,19 +3818,40 @@ function formatSessionContext2(sessionId, sessionKey) {
2983
3818
  // src/adapters/openclaw/session/continuity/recent-session.ts
2984
3819
  var RECENT_SESSION_MESSAGE_LIMIT = 6;
2985
3820
  var RECENT_SESSION_MAX_CHARS = 1800;
3821
+ var SESSION_START_SECTION_HEADINGS = [
3822
+ "## Previous session summary",
3823
+ "## Recent session",
3824
+ "## Agenr Session Recall",
3825
+ "### Core Memory",
3826
+ "### Relevant Durable Memory",
3827
+ "## Agenr Before-Turn Recall",
3828
+ "### Suggested Procedure"
3829
+ ];
3830
+ var INLINE_METADATA_SENTINELS = [
3831
+ "Sender (untrusted metadata):",
3832
+ "Conversation info (untrusted metadata):",
3833
+ "Thread starter (untrusted, for context):",
3834
+ "Replied message (untrusted, for context):",
3835
+ "Forwarded message context (untrusted metadata):",
3836
+ "Chat history since last reply (untrusted, for context):"
3837
+ ];
2986
3838
  async function renderRecentSessionSection(sessionFile, logger) {
2987
3839
  try {
2988
3840
  const transcript = await openClawTranscriptParser.parseFile(sessionFile);
2989
- const tail = transcript.messages.slice(-RECENT_SESSION_MESSAGE_LIMIT);
2990
- const body = capRecentSession(tail.map((message) => `${message.role === "user" ? "U" : "A"}: ${message.text}`).join("\n"), RECENT_SESSION_MAX_CHARS);
3841
+ const sanitizedMessages = transcript.messages.map((message) => ({
3842
+ prefix: message.role === "user" ? "U" : "A",
3843
+ text: sanitizeRecentSessionMessage(message.text, message.role)
3844
+ })).filter((message) => message.text.length > 0);
3845
+ const tail = sanitizedMessages.slice(-RECENT_SESSION_MESSAGE_LIMIT);
3846
+ const body = capRecentSession(tail.map((message) => `${message.prefix}: ${message.text}`).join("\n"), RECENT_SESSION_MAX_CHARS);
2991
3847
  logger.debug?.(`[agenr] before_prompt_build: recent session tail for file=${sessionFile}: messages=${tail.length} chars=${body.length}`);
2992
3848
  return body;
2993
3849
  } catch (error) {
2994
- logger.debug?.(`[agenr] before_prompt_build: failed to build recent session tail for file=${sessionFile}: ${formatErrorMessage4(error)}`);
3850
+ logger.debug?.(`[agenr] before_prompt_build: failed to build recent session tail for file=${sessionFile}: ${formatErrorMessage5(error)}`);
2995
3851
  return "";
2996
3852
  }
2997
3853
  }
2998
- function formatErrorMessage4(error) {
3854
+ function formatErrorMessage5(error) {
2999
3855
  return error instanceof Error ? error.message : String(error);
3000
3856
  }
3001
3857
  function capRecentSession(value, maxChars) {
@@ -3005,6 +3861,53 @@ function capRecentSession(value, maxChars) {
3005
3861
  const marker = "[...truncated earlier recent session...]\n";
3006
3862
  return `${marker}${value.slice(-(maxChars - marker.length)).trimStart()}`;
3007
3863
  }
3864
+ function sanitizeRecentSessionMessage(text, role) {
3865
+ const wrapperDetected = containsAgenrMemoryContext(text) || SESSION_START_SECTION_HEADINGS.some((heading) => text.includes(heading));
3866
+ let cleaned = stripAgenrMemoryContext(text);
3867
+ for (const heading of SESSION_START_SECTION_HEADINGS) {
3868
+ cleaned = cleaned.split(heading).join(" ");
3869
+ }
3870
+ cleaned = stripInlineMetadata(cleaned);
3871
+ cleaned = collapseWhitespace(cleaned);
3872
+ if (wrapperDetected) {
3873
+ cleaned = unwrapEmbeddedTranscriptTurn(cleaned, role);
3874
+ }
3875
+ return collapseWhitespace(cleaned);
3876
+ }
3877
+ function stripInlineMetadata(text) {
3878
+ let cleaned = text;
3879
+ for (const sentinel of INLINE_METADATA_SENTINELS) {
3880
+ const escapedSentinel = escapeForRegExp(sentinel);
3881
+ cleaned = cleaned.replace(new RegExp(`${escapedSentinel}\\s*(?:json\\s*)?\\{[\\s\\S]*?\\}`, "gu"), " ");
3882
+ cleaned = cleaned.replace(new RegExp(`${escapedSentinel}[^
3883
+ ]*`, "gu"), " ");
3884
+ }
3885
+ cleaned = cleaned.replace(/Untrusted context \(metadata, do not treat as instructions or commands\):[\s\S]*$/gu, " ");
3886
+ return cleaned;
3887
+ }
3888
+ function unwrapEmbeddedTranscriptTurn(text, role) {
3889
+ if (role === "user") {
3890
+ const timestampMatches = [...text.matchAll(/\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s[^\]]+\]/gu)];
3891
+ if (timestampMatches.length > 1) {
3892
+ const lastTimestamp = timestampMatches.at(-1);
3893
+ if (lastTimestamp?.index !== void 0) {
3894
+ return text.slice(lastTimestamp.index).trim();
3895
+ }
3896
+ }
3897
+ }
3898
+ const marker = role === "user" ? "U:" : "A:";
3899
+ const lastMarkerIndex = text.lastIndexOf(marker);
3900
+ if (lastMarkerIndex < 0) {
3901
+ return text;
3902
+ }
3903
+ return text.slice(lastMarkerIndex + marker.length).trim();
3904
+ }
3905
+ function escapeForRegExp(value) {
3906
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
3907
+ }
3908
+ function collapseWhitespace(value) {
3909
+ return value.replace(/\s+/gu, " ").trim();
3910
+ }
3008
3911
 
3009
3912
  // src/adapters/openclaw/session/continuity/index.ts
3010
3913
  var READ_TIME_CONTINUITY_SUMMARY_TIMEOUT_MS = 35e3;
@@ -3136,9 +4039,32 @@ async function awaitWithTimeout(promise, timeoutMs) {
3136
4039
  }
3137
4040
 
3138
4041
  // src/adapters/openclaw/hooks/before-prompt-build.ts
3139
- var CORE_ENTRY_LIMIT = 4;
4042
+ var DEFAULT_SESSION_START_POLICY = {
4043
+ maxCoreEntries: 4,
4044
+ maxArtifactRecallEntries: 3,
4045
+ maxDurableEntries: 5,
4046
+ maxArtifactChars: 1200
4047
+ };
4048
+ var DEFAULT_BEFORE_TURN_POLICY = {
4049
+ maxDurableEntries: 1,
4050
+ maxHighConfidenceDurableEntries: 2,
4051
+ maxRecentTurns: 2,
4052
+ maxQueryChars: 450,
4053
+ maxProcedureCandidates: 3,
4054
+ recallThreshold: 0.6,
4055
+ highConfidenceRecallThreshold: 0.85,
4056
+ procedureThreshold: 0.72
4057
+ };
3140
4058
  var NON_USER_TRIGGER_SET = /* @__PURE__ */ new Set(["heartbeat", "cron", "memory"]);
3141
4059
  var DEFAULT_STORE_NUDGE_CONFIG = resolveStoreNudgeConfig(void 0);
4060
+ var INLINE_METADATA_SENTINELS2 = [
4061
+ "Sender (untrusted metadata):",
4062
+ "Conversation info (untrusted metadata):",
4063
+ "Thread starter (untrusted, for context):",
4064
+ "Replied message (untrusted, for context):",
4065
+ "Forwarded message context (untrusted metadata):",
4066
+ "Chat history since last reply (untrusted, for context):"
4067
+ ];
3142
4068
  async function handleAgenrBeforePromptBuild(event, ctx, params) {
3143
4069
  const sessionContext = formatSessionContext(ctx.sessionId, ctx.sessionKey);
3144
4070
  const trackerState = params.tracker.consume(ctx.sessionId, ctx.sessionKey);
@@ -3146,7 +4072,7 @@ async function handleAgenrBeforePromptBuild(event, ctx, params) {
3146
4072
  params.logger.debug?.(`[agenr] before_prompt_build: session tracker duplicate blocked for ${sessionContext}`);
3147
4073
  params.logger.debug?.(`[agenr] before_prompt_build: session tracker active count=${trackerState.activeCount}`);
3148
4074
  params.logger.info(`[agenr] session-start recall skipped (already ran) for ${sessionContext}`);
3149
- return resolveStoreNudgeResult(event, ctx, sessionContext, params);
4075
+ return await resolveNonFirstTurnResult(event, ctx, sessionContext, params);
3150
4076
  }
3151
4077
  params.logger.debug?.(`[agenr] before_prompt_build: session tracker first start for ${sessionContext}`);
3152
4078
  params.logger.debug?.(`[agenr] before_prompt_build: session tracker active count=${trackerState.activeCount}`);
@@ -3154,24 +4080,52 @@ async function handleAgenrBeforePromptBuild(event, ctx, params) {
3154
4080
  try {
3155
4081
  const services = await params.servicesPromise;
3156
4082
  const continuity = await resolvePredecessorContinuity(ctx, params.tracker, services, params.logger);
4083
+ emitContinuityEvent(services.debugSink, ctx, continuity);
3157
4084
  void writeOpenClawPredecessorEpisode({
3158
4085
  ctx,
3159
4086
  predecessor: continuity.predecessor,
3160
4087
  services,
3161
4088
  logger: params.logger
3162
4089
  });
3163
- const sessionStartRecall = await runAgenrSessionStartRecall(services);
3164
- const memoryContext = formatAgenrSessionStartRecall(sessionStartRecall);
3165
- const sections = [
3166
- continuity.continuitySummaryContent && `## Previous session summary
3167
- ${continuity.continuitySummaryContent}`,
3168
- continuity.recentSessionContent && `## Recent session
3169
- ${continuity.recentSessionContent}`,
3170
- memoryContext
3171
- ].filter((value) => Boolean(value && value.trim().length > 0));
3172
- const prependContext = sections.join("\n\n");
3173
- params.logger.info(`[agenr] session-start recall: ${sessionStartRecall.core.length} core entries for ${sessionContext}`);
3174
- params.logger.debug?.(`[agenr] before_prompt_build: session-start core entries for ${sessionContext}: ${formatEntryRefs(sessionStartRecall.core)}`);
4090
+ const sessionStartPatch = await runSessionStart(
4091
+ {
4092
+ sessionKey: ctx.sessionKey,
4093
+ continuitySummaryText: continuity.continuitySummaryContent,
4094
+ recentSessionText: continuity.recentSessionContent,
4095
+ policy: resolveSessionStartPolicy(services)
4096
+ },
4097
+ services.sessionStart
4098
+ );
4099
+ const prependContext = formatAgenrSessionStartRecall(sessionStartPatch);
4100
+ if (services.debugSink.enabled) {
4101
+ void services.debugSink.emit({
4102
+ type: "session_start_recall",
4103
+ ...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
4104
+ ...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
4105
+ debug: {
4106
+ durableMemoryCount: sessionStartPatch.durableMemory.length,
4107
+ selectedEntryIds: sessionStartPatch.durableMemory.map((item) => item.entry.id),
4108
+ coreCandidateCount: sessionStartPatch.diagnostics.coreCandidateCount,
4109
+ artifactRecallCandidateCount: sessionStartPatch.diagnostics.artifactRecallCandidateCount,
4110
+ artifactRecallUsed: sessionStartPatch.diagnostics.artifactRecallUsed,
4111
+ notices: [...sessionStartPatch.diagnostics.notices]
4112
+ }
4113
+ });
4114
+ }
4115
+ params.logger.info(
4116
+ `[agenr] session-start recall: ${sessionStartPatch.durableMemory.length} durable entries for ${sessionContext} (core_candidates=${sessionStartPatch.diagnostics.coreCandidateCount} artifact_candidates=${sessionStartPatch.diagnostics.artifactRecallCandidateCount})`
4117
+ );
4118
+ if (sessionStartPatch.diagnostics.artifactRecallUsed) {
4119
+ params.logger.debug?.(
4120
+ `[agenr] before_prompt_build: session-start artifact recall for ${sessionContext} query_length=${sessionStartPatch.diagnostics.artifactRecallQuery?.length ?? 0} notices=${sessionStartPatch.diagnostics.notices.length}`
4121
+ );
4122
+ }
4123
+ if (sessionStartPatch.diagnostics.notices.length > 0) {
4124
+ params.logger.info(`[agenr] session-start recall notices for ${sessionContext}: ${sessionStartPatch.diagnostics.notices.join(" | ")}`);
4125
+ }
4126
+ params.logger.debug?.(
4127
+ `[agenr] before_prompt_build: session-start durable entries for ${sessionContext}: ${formatEntryRefs(sessionStartPatch.durableMemory.map((item) => item.entry))}`
4128
+ );
3175
4129
  params.logger.debug?.(`[agenr] before_prompt_build: session-start prependContext length for ${sessionContext}: ${prependContext.length} chars`);
3176
4130
  if (prependContext.length === 0) {
3177
4131
  params.logger.info(`[agenr] session-start recall: nothing to inject for ${sessionContext}`);
@@ -3180,6 +4134,131 @@ ${continuity.recentSessionContent}`,
3180
4134
  return { prependContext };
3181
4135
  } catch (error) {
3182
4136
  params.logger.warn(`[agenr] session-start recall failed for ${sessionContext}: ${formatErrorMessage2(error)}`);
4137
+ try {
4138
+ const services = await params.servicesPromise;
4139
+ if (services.debugSink.enabled) {
4140
+ void services.debugSink.emit({
4141
+ type: "error",
4142
+ ...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
4143
+ ...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
4144
+ scope: "session_start_recall",
4145
+ error: { message: error instanceof Error ? error.message : String(error) }
4146
+ });
4147
+ }
4148
+ } catch {
4149
+ }
4150
+ return void 0;
4151
+ }
4152
+ }
4153
+ function emitContinuityEvent(sink, ctx, continuity) {
4154
+ if (!sink.enabled) {
4155
+ return;
4156
+ }
4157
+ void sink.emit({
4158
+ type: "continuity_resolution",
4159
+ ...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
4160
+ ...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
4161
+ summary: {
4162
+ predecessorFound: Boolean(continuity.predecessor),
4163
+ ...continuity.predecessor ? { predecessorFileBasename: path5.basename(continuity.predecessor.sessionFile) } : {},
4164
+ hasContinuitySummary: continuity.continuitySummaryContent.length > 0,
4165
+ hasRecentSession: continuity.recentSessionContent.length > 0,
4166
+ continuitySummaryChars: continuity.continuitySummaryContent.length,
4167
+ recentSessionChars: continuity.recentSessionContent.length
4168
+ }
4169
+ });
4170
+ }
4171
+ async function resolveNonFirstTurnResult(event, ctx, sessionContext, params) {
4172
+ const beforeTurnResult = await resolveBeforeTurnResult(event, ctx, sessionContext, params);
4173
+ if (beforeTurnResult) {
4174
+ return beforeTurnResult;
4175
+ }
4176
+ return resolveStoreNudgeResult(event, ctx, sessionContext, params);
4177
+ }
4178
+ async function resolveBeforeTurnResult(event, ctx, sessionContext, params) {
4179
+ const normalizedTrigger = ctx.trigger?.trim().toLowerCase();
4180
+ if (normalizedTrigger && NON_USER_TRIGGER_SET.has(normalizedTrigger)) {
4181
+ params.logger.debug?.(`[agenr] before_prompt_build: before-turn skipped for ${sessionContext} reason=non_user_trigger trigger=${normalizedTrigger}`);
4182
+ return void 0;
4183
+ }
4184
+ const services = await params.servicesPromise;
4185
+ if (services.pluginConfig.memoryPolicy?.beforeTurn?.enabled === false) {
4186
+ params.logger.debug?.(`[agenr] before_prompt_build: before-turn skipped for ${sessionContext} reason=disabled`);
4187
+ return void 0;
4188
+ }
4189
+ const currentTurnText = normalizePromptText(event.prompt);
4190
+ if (!currentTurnText) {
4191
+ params.logger.debug?.(`[agenr] before_prompt_build: before-turn skipped for ${sessionContext} reason=empty_prompt`);
4192
+ return void 0;
4193
+ }
4194
+ try {
4195
+ const beforeTurnPatch = await runBeforeTurn(
4196
+ {
4197
+ sessionKey: ctx.sessionKey,
4198
+ currentTurnText,
4199
+ recentTurns: extractRecentTurns(event.messages),
4200
+ trigger: ctx.trigger,
4201
+ policy: resolveBeforeTurnPolicy(services)
4202
+ },
4203
+ services.beforeTurn
4204
+ );
4205
+ const prependContext = formatAgenrBeforeTurnRecall(beforeTurnPatch);
4206
+ if (services.debugSink.enabled) {
4207
+ void services.debugSink.emit({
4208
+ type: "before_turn_decision",
4209
+ ...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
4210
+ ...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
4211
+ debug: buildLiveBeforeTurnDebugArtifact({
4212
+ caseId: `live-${randomUUID2()}`,
4213
+ patch: beforeTurnPatch,
4214
+ currentTurnText,
4215
+ trigger: ctx.trigger,
4216
+ eventLevel: services.debugSink.eventLevel,
4217
+ maxTopCandidates: services.debugSink.maxTopCandidates
4218
+ })
4219
+ });
4220
+ }
4221
+ params.logger.info(
4222
+ `[agenr] before-turn recall: ${beforeTurnPatch.durableMemory.length} durable entries for ${sessionContext} (durable_candidates=${beforeTurnPatch.diagnostics.durableRecallCandidateCount} procedure_candidates=${beforeTurnPatch.diagnostics.procedureCandidateCount})`
4223
+ );
4224
+ if (beforeTurnPatch.procedure) {
4225
+ params.logger.info(
4226
+ `[agenr] before-turn procedure suggestion for ${sessionContext}: ${beforeTurnPatch.procedure.procedure.procedure_key} score=${beforeTurnPatch.procedure.score.toFixed(2)}`
4227
+ );
4228
+ }
4229
+ if (beforeTurnPatch.diagnostics.notices.length > 0) {
4230
+ params.logger.info(`[agenr] before-turn recall notices for ${sessionContext}: ${beforeTurnPatch.diagnostics.notices.join(" | ")}`);
4231
+ }
4232
+ if (beforeTurnPatch.diagnostics.abstained) {
4233
+ params.logger.debug?.(
4234
+ `[agenr] before_prompt_build: before-turn abstained for ${sessionContext}: category=${beforeTurnPatch.diagnostics.suppressedTurnCategory ?? "none"} signals=${beforeTurnPatch.diagnostics.turnSignalLabels.join(",") || "none"} reasons=${beforeTurnPatch.diagnostics.abstentionReasons.join(" | ") || "none"}`
4235
+ );
4236
+ }
4237
+ params.logger.debug?.(`[agenr] before_prompt_build: before-turn diagnostics for ${sessionContext}: ${formatBeforeTurnDiagnosticsForLog(beforeTurnPatch)}`);
4238
+ params.logger.debug?.(
4239
+ `[agenr] before_prompt_build: before-turn durable entries for ${sessionContext}: ${formatEntryRefs(
4240
+ beforeTurnPatch.durableMemory.map((item) => item.entry)
4241
+ )}`
4242
+ );
4243
+ params.logger.debug?.(`[agenr] before_prompt_build: before-turn prependContext length for ${sessionContext}: ${prependContext.length} chars`);
4244
+ if (prependContext.length === 0) {
4245
+ return void 0;
4246
+ }
4247
+ return { prependContext };
4248
+ } catch (error) {
4249
+ params.logger.warn(`[agenr] before-turn recall failed for ${sessionContext}: ${formatErrorMessage2(error)}`);
4250
+ try {
4251
+ if (services.debugSink.enabled) {
4252
+ void services.debugSink.emit({
4253
+ type: "error",
4254
+ ...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
4255
+ ...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
4256
+ scope: "before_turn_decision",
4257
+ error: { message: error instanceof Error ? error.message : String(error) }
4258
+ });
4259
+ }
4260
+ } catch {
4261
+ }
3183
4262
  return void 0;
3184
4263
  }
3185
4264
  }
@@ -3219,12 +4298,182 @@ function resolveStoreNudgeResult(_event, ctx, sessionContext, params) {
3219
4298
  params.logger.info(`[agenr] store nudge injected for ${sessionContext} ordinal=${state.nudgeCount} turn=${state.turnCount} gap=${gapSinceSuccessfulStore}`);
3220
4299
  return { prependContext };
3221
4300
  }
3222
- async function runAgenrSessionStartRecall(services) {
3223
- return { core: await services.memory.listCoreEntries(CORE_ENTRY_LIMIT) };
3224
- }
3225
4301
  function formatEntryRefs(entries) {
3226
4302
  return entries.length === 0 ? "none" : entries.map((entry) => `${entry.subject} [${entry.id}]`).join(", ");
3227
4303
  }
4304
+ function formatBeforeTurnDiagnosticsForLog(patch) {
4305
+ return JSON.stringify({
4306
+ query: truncateForLog(patch.diagnostics.query, 160),
4307
+ queryPolicy: patch.diagnostics.queryPolicy,
4308
+ queryVariants: patch.diagnostics.queryVariants.map((variant) => ({
4309
+ kind: variant.kind,
4310
+ query: truncateForLog(variant.query, 120),
4311
+ candidateCount: variant.candidateCount,
4312
+ selected: variant.selected
4313
+ })),
4314
+ turnSignalLabels: patch.diagnostics.turnSignalLabels,
4315
+ suppressedTurnCategory: patch.diagnostics.suppressedTurnCategory,
4316
+ durableRecallCandidateCount: patch.diagnostics.durableRecallCandidateCount,
4317
+ procedureCandidateCount: patch.diagnostics.procedureCandidateCount,
4318
+ directness: patch.diagnostics.directness ? {
4319
+ queryKind: patch.diagnostics.directness.queryKind,
4320
+ entity: patch.diagnostics.directness.entity,
4321
+ decision: patch.diagnostics.directness.decision,
4322
+ winnerEntryId: patch.diagnostics.directness.winnerEntryId,
4323
+ runnerUpEntryId: patch.diagnostics.directness.runnerUpEntryId,
4324
+ winnerGap: patch.diagnostics.directness.winnerGap,
4325
+ reason: truncateForLog(patch.diagnostics.directness.reason, 180),
4326
+ candidates: patch.diagnostics.directness.candidates.map((candidate) => ({
4327
+ entryId: candidate.entryId,
4328
+ baseRank: candidate.baseRank,
4329
+ baseScore: candidate.baseScore,
4330
+ directnessDelta: candidate.directnessDelta,
4331
+ adjustedScore: candidate.adjustedScore,
4332
+ signals: candidate.signals
4333
+ }))
4334
+ } : void 0,
4335
+ abstained: patch.diagnostics.abstained,
4336
+ abstentionReasons: patch.diagnostics.abstentionReasons.map((reason) => truncateForLog(reason, 180)),
4337
+ notices: patch.diagnostics.notices.map((notice) => truncateForLog(notice, 180)),
4338
+ selectedEntries: patch.durableMemory.map((item) => ({
4339
+ id: item.entry.id,
4340
+ subject: truncateForLog(item.entry.subject, 80),
4341
+ score: Number(item.score.toFixed(3))
4342
+ })),
4343
+ procedure: patch.procedure ? {
4344
+ procedureKey: patch.procedure.procedure.procedure_key,
4345
+ score: Number(patch.procedure.score.toFixed(3))
4346
+ } : void 0
4347
+ });
4348
+ }
4349
+ function truncateForLog(value, maxChars) {
4350
+ if (typeof value !== "string") {
4351
+ return void 0;
4352
+ }
4353
+ const normalized = value.replace(/\s+/gu, " ").trim();
4354
+ if (normalized.length === 0) {
4355
+ return void 0;
4356
+ }
4357
+ return normalized.length <= maxChars ? normalized : `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
4358
+ }
4359
+ function resolveSessionStartPolicy(services) {
4360
+ return {
4361
+ ...DEFAULT_SESSION_START_POLICY,
4362
+ enableArtifactRecall: services.pluginConfig.memoryPolicy?.sessionStart?.relevantDurableMemory !== false
4363
+ };
4364
+ }
4365
+ function resolveBeforeTurnPolicy(services) {
4366
+ return {
4367
+ ...DEFAULT_BEFORE_TURN_POLICY,
4368
+ enableProcedureSuggestion: services.pluginConfig.memoryPolicy?.beforeTurn?.procedureSuggestion !== false,
4369
+ ...services.pluginConfig.memoryPolicy?.beforeTurn?.maxDurableEntries !== void 0 ? { maxDurableEntries: services.pluginConfig.memoryPolicy.beforeTurn.maxDurableEntries } : {},
4370
+ ...services.pluginConfig.memoryPolicy?.beforeTurn?.recallThreshold !== void 0 ? { recallThreshold: services.pluginConfig.memoryPolicy.beforeTurn.recallThreshold } : {},
4371
+ ...services.pluginConfig.memoryPolicy?.beforeTurn?.highConfidenceRecallThreshold !== void 0 ? { highConfidenceRecallThreshold: services.pluginConfig.memoryPolicy.beforeTurn.highConfidenceRecallThreshold } : {},
4372
+ ...services.pluginConfig.memoryPolicy?.beforeTurn?.procedureThreshold !== void 0 ? { procedureThreshold: services.pluginConfig.memoryPolicy.beforeTurn.procedureThreshold } : {}
4373
+ };
4374
+ }
4375
+ function extractRecentTurns(messages) {
4376
+ const turns = [];
4377
+ for (const message of messages) {
4378
+ if (!message || typeof message !== "object") {
4379
+ continue;
4380
+ }
4381
+ const typed = message;
4382
+ const role = typed.role === "user" || typed.role === "assistant" ? typed.role : void 0;
4383
+ if (!role) {
4384
+ continue;
4385
+ }
4386
+ const text = sanitizeRecentTurnText(extractMessageText(typed.content), role);
4387
+ if (!text) {
4388
+ continue;
4389
+ }
4390
+ turns.push({ role, text });
4391
+ }
4392
+ return turns;
4393
+ }
4394
+ function extractMessageText(content) {
4395
+ if (typeof content === "string") {
4396
+ return content;
4397
+ }
4398
+ if (!Array.isArray(content)) {
4399
+ return "";
4400
+ }
4401
+ const blocks = [];
4402
+ for (const block of content) {
4403
+ if (typeof block === "string") {
4404
+ blocks.push(block);
4405
+ continue;
4406
+ }
4407
+ if (!block || typeof block !== "object") {
4408
+ continue;
4409
+ }
4410
+ const typed = block;
4411
+ if (typeof typed.text === "string") {
4412
+ blocks.push(typed.text);
4413
+ continue;
4414
+ }
4415
+ const type = typeof typed.type === "string" ? typed.type.trim().toLowerCase() : "";
4416
+ if (typeof typed.content === "string" && (type === "text" || type === "input_text" || type === "output_text")) {
4417
+ blocks.push(typed.content);
4418
+ }
4419
+ }
4420
+ return blocks.join("\n");
4421
+ }
4422
+ function sanitizeRecentTurnText(text, role) {
4423
+ if (!text.trim()) {
4424
+ return "";
4425
+ }
4426
+ const wrapperDetected = containsAgenrMemoryContext(text) || text.includes("## Agenr Session Recall") || text.includes("## Agenr Before-Turn Recall") || text.includes("[MEMORY CHECK]");
4427
+ let cleaned = stripAgenrMemoryContext(text);
4428
+ const headings = [
4429
+ "## Previous session summary",
4430
+ "## Recent session",
4431
+ "## Agenr Session Recall",
4432
+ "### Core Memory",
4433
+ "### Relevant Durable Memory",
4434
+ "## Agenr Before-Turn Recall",
4435
+ "### Suggested Procedure"
4436
+ ];
4437
+ for (const heading of headings) {
4438
+ cleaned = cleaned.split(heading).join(" ");
4439
+ }
4440
+ cleaned = cleaned.replace(/\[MEMORY CHECK\][^\n]*/gu, " ");
4441
+ cleaned = collapseWhitespace2(cleaned);
4442
+ if (!wrapperDetected) {
4443
+ return cleaned;
4444
+ }
4445
+ const segments = stripAgenrMemoryContext(text).split(/\n\s*\n/gu).map((segment) => collapseWhitespace2(segment)).filter((segment) => segment.length > 0);
4446
+ const fallbackSegment = segments.at(-1);
4447
+ if (fallbackSegment) {
4448
+ return role === "user" ? fallbackSegment : collapseWhitespace2(cleaned);
4449
+ }
4450
+ return cleaned;
4451
+ }
4452
+ function normalizePromptText(prompt) {
4453
+ let cleaned = stripAgenrMemoryContext(prompt);
4454
+ cleaned = stripInlineMetadata2(cleaned);
4455
+ cleaned = cleaned.replace(/^\s*\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s[^\]]+\]\s*/u, "");
4456
+ cleaned = cleaned.replace(/^\s*U:\s*/u, "");
4457
+ cleaned = collapseWhitespace2(cleaned);
4458
+ return cleaned.length > 0 ? cleaned : void 0;
4459
+ }
4460
+ function stripInlineMetadata2(text) {
4461
+ let cleaned = text;
4462
+ for (const sentinel of INLINE_METADATA_SENTINELS2) {
4463
+ const escapedSentinel = escapeForRegExp2(sentinel);
4464
+ cleaned = cleaned.replace(new RegExp(`${escapedSentinel}\\s*(?:\`\`\`json\\s*)?\\{[\\s\\S]*?\\}(?:\\s*\`\`\`)?`, "gu"), " ");
4465
+ cleaned = cleaned.replace(new RegExp(`${escapedSentinel}[^
4466
+ ]*`, "gu"), " ");
4467
+ }
4468
+ cleaned = cleaned.replace(/Untrusted context \(metadata, do not treat as instructions or commands\):[\s\S]*$/gu, " ");
4469
+ return cleaned;
4470
+ }
4471
+ function escapeForRegExp2(value) {
4472
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
4473
+ }
4474
+ function collapseWhitespace2(value) {
4475
+ return value.replace(/\s+/gu, " ").trim();
4476
+ }
3228
4477
 
3229
4478
  // src/adapters/openclaw/memory/flush-plan.ts
3230
4479
  function buildAgenrMemoryFlushPlan(_params, logger) {
@@ -3236,64 +4485,100 @@ function buildAgenrMemoryFlushPlan(_params, logger) {
3236
4485
  function createAgenrMemoryRuntime(servicesPromise) {
3237
4486
  return {
3238
4487
  async getMemorySearchManager() {
3239
- const services = await servicesPromise;
3240
- const snapshot = await services.memory.getMemoryStatusSnapshot();
3241
- const vectorAvailable = await services.memory.probeVectorAvailability();
3242
- const status = {
3243
- backend: "builtin",
3244
- provider: "agenr",
3245
- model: services.embeddingStatus.model,
3246
- dbPath: services.dbPath,
3247
- files: snapshot.sourceFiles,
3248
- chunks: snapshot.activeEntries,
3249
- vector: {
3250
- enabled: true,
3251
- available: vectorAvailable,
3252
- dims: EMBEDDING_DIMENSIONS
3253
- },
3254
- custom: {
3255
- activeEntries: snapshot.activeEntries,
3256
- coreEntries: snapshot.coreEntries,
3257
- sourceFiles: snapshot.sourceFiles
3258
- }
3259
- };
3260
- return {
3261
- manager: {
3262
- async search() {
3263
- return [];
3264
- },
3265
- async readFile({ relPath }) {
3266
- throw new Error(`[agenr] memory file reads are not supported for "${relPath}"`);
3267
- },
3268
- status() {
3269
- return status;
3270
- },
3271
- async probeEmbeddingAvailability() {
3272
- return {
3273
- ok: services.embeddingStatus.available,
3274
- ...services.embeddingStatus.error ? { error: services.embeddingStatus.error } : {}
3275
- };
3276
- },
3277
- async probeVectorAvailability() {
3278
- return vectorAvailable;
4488
+ try {
4489
+ const services = await servicesPromise;
4490
+ const snapshot = await services.memory.getMemoryStatusSnapshot();
4491
+ const vectorAvailable = await services.memory.probeVectorAvailability();
4492
+ const status = {
4493
+ backend: "builtin",
4494
+ provider: "agenr",
4495
+ model: services.embeddingStatus.model,
4496
+ dbPath: services.dbPath,
4497
+ files: snapshot.sourceFiles,
4498
+ chunks: snapshot.activeEntries,
4499
+ vector: {
4500
+ enabled: true,
4501
+ available: vectorAvailable,
4502
+ dims: EMBEDDING_DIMENSIONS
3279
4503
  },
3280
- async sync() {
3281
- return;
4504
+ custom: {
4505
+ activeEntries: snapshot.activeEntries,
4506
+ coreEntries: snapshot.coreEntries,
4507
+ sourceFiles: snapshot.sourceFiles
3282
4508
  }
3283
- }
3284
- };
4509
+ };
4510
+ return {
4511
+ manager: {
4512
+ async search() {
4513
+ return [];
4514
+ },
4515
+ async readFile({ relPath }) {
4516
+ throw new Error(`[agenr] memory file reads are not supported for "${relPath}"`);
4517
+ },
4518
+ status() {
4519
+ return status;
4520
+ },
4521
+ async probeEmbeddingAvailability() {
4522
+ return {
4523
+ ok: services.embeddingStatus.available,
4524
+ ...services.embeddingStatus.error ? { error: services.embeddingStatus.error } : {}
4525
+ };
4526
+ },
4527
+ async probeVectorAvailability() {
4528
+ return vectorAvailable;
4529
+ },
4530
+ async sync() {
4531
+ return;
4532
+ }
4533
+ }
4534
+ };
4535
+ } catch (error) {
4536
+ return {
4537
+ manager: null,
4538
+ error: `[agenr] memory runtime unavailable: ${formatErrorMessage2(error)}`
4539
+ };
4540
+ }
3285
4541
  },
3286
4542
  resolveMemoryBackendConfig() {
3287
4543
  return { backend: "builtin" };
3288
4544
  },
3289
4545
  async closeAllMemorySearchManagers() {
3290
- const services = await servicesPromise;
3291
- await services.close();
4546
+ try {
4547
+ const services = await servicesPromise;
4548
+ await services.close();
4549
+ } catch {
4550
+ }
3292
4551
  }
3293
4552
  };
3294
4553
  }
3295
4554
 
4555
+ // src/adapters/db/session-start-repository.ts
4556
+ function createSessionStartRepository(executor) {
4557
+ return {
4558
+ listCoreEntries: async (limit) => listCoreEntries(executor, limit)
4559
+ };
4560
+ }
4561
+ async function listCoreEntries(executor, limit) {
4562
+ if (limit <= 0) {
4563
+ return [];
4564
+ }
4565
+ const result = await executor.execute({
4566
+ sql: `
4567
+ SELECT
4568
+ ${ENTRY_SELECT_COLUMNS}
4569
+ FROM entries
4570
+ WHERE ${buildActiveEntryClause()}
4571
+ AND expiry = 'core'
4572
+ ORDER BY importance DESC, created_at DESC
4573
+ LIMIT ?
4574
+ `,
4575
+ args: [limit]
4576
+ });
4577
+ return result.rows.map((row) => mapEntryRow(row));
4578
+ }
4579
+
3296
4580
  // src/app/openclaw/runtime.ts
4581
+ import path6 from "path";
3297
4582
  async function createAgenrOpenClawServices(config, options) {
3298
4583
  const { resolvedConfig, agenrConfig: loadedAgenrConfig } = resolveRuntimeConfig(config, options.resolvePath);
3299
4584
  const agenrConfig = {
@@ -3315,10 +4600,13 @@ async function createAgenrOpenClawServices(config, options) {
3315
4600
  episodes: runtimeServices.episodes,
3316
4601
  procedures: runtimeServices.procedures,
3317
4602
  memory: runtimeServices.memory,
4603
+ sessionStart: runtimeServices.sessionStart,
4604
+ beforeTurn: runtimeServices.beforeTurn,
3318
4605
  embedding: runtimeServices.embedding,
3319
4606
  recall: runtimeServices.recall,
3320
4607
  claimExtraction: runtimeServices.claimExtraction,
3321
4608
  embeddingStatus: toPublicEmbeddingStatus(embeddingStatus),
4609
+ debugSink: runtimeServices.debugSink,
3322
4610
  close: runtimeServices.close
3323
4611
  };
3324
4612
  }
@@ -3345,7 +4633,11 @@ function resolveEmbeddingStatus(config) {
3345
4633
  async function createRuntimeServices(dbPath, config, embeddingStatus, openClawContext) {
3346
4634
  const database = await createDatabase(dbPath);
3347
4635
  const embedding = embeddingStatus.available ? createEmbeddingClient(requireApiKey(embeddingStatus), embeddingStatus.model) : createUnavailableEmbeddingPort(embeddingStatus.error ?? "Embeddings are unavailable.");
4636
+ const baseRecall = createRecallAdapter(database, embedding);
4637
+ const crossEncoder = resolveCrossEncoder(config);
4638
+ const recall2 = attachCrossEncoderPort(baseRecall, crossEncoder);
3348
4639
  const claimExtraction = await createClaimExtractionRuntime(config, openClawContext.openClaw, openClawContext.pluginConfig);
4640
+ const debugSink = createDebugSink(openClawContext.openClaw, openClawContext.pluginConfig);
3349
4641
  let closed = false;
3350
4642
  return {
3351
4643
  entries: database,
@@ -3354,18 +4646,56 @@ async function createRuntimeServices(dbPath, config, embeddingStatus, openClawCo
3354
4646
  memory: createOpenClawRepository(database, {
3355
4647
  claimSlotPolicyConfig: openClawContext.pluginConfig.memoryPolicy?.slotPolicies
3356
4648
  }),
4649
+ sessionStart: {
4650
+ repository: createSessionStartRepository(database),
4651
+ recall: recall2,
4652
+ slotPolicyConfig: openClawContext.pluginConfig.memoryPolicy?.slotPolicies
4653
+ },
4654
+ beforeTurn: {
4655
+ recall: recall2,
4656
+ procedures: database,
4657
+ embedQuery: embeddingStatus.available ? async (text) => {
4658
+ const vectors = await embedding.embed([text]);
4659
+ return vectors[0] ?? [];
4660
+ } : void 0,
4661
+ slotPolicyConfig: openClawContext.pluginConfig.memoryPolicy?.slotPolicies
4662
+ },
3357
4663
  embedding,
3358
- recall: createRecallAdapter(database, embedding),
4664
+ recall: recall2,
3359
4665
  claimExtraction,
4666
+ debugSink,
3360
4667
  async close() {
3361
4668
  if (closed) {
3362
4669
  return;
3363
4670
  }
3364
4671
  closed = true;
4672
+ await debugSink.close();
3365
4673
  await database.close();
3366
4674
  }
3367
4675
  };
3368
4676
  }
4677
+ function createDebugSink(openClaw, pluginConfig) {
4678
+ const resolved = resolveDebugConfig(pluginConfig.debug);
4679
+ if (!resolved.enabled) {
4680
+ return createNoopAgenrDebugSink();
4681
+ }
4682
+ const withLogPath = ensureDebugLogPath(resolved, openClaw);
4683
+ return createAgenrDebugSink(withLogPath);
4684
+ }
4685
+ function ensureDebugLogPath(resolved, openClaw) {
4686
+ if (resolved.logPath) {
4687
+ return resolved;
4688
+ }
4689
+ try {
4690
+ const stateDir = openClaw.runtime.state.resolveStateDir(process.env);
4691
+ return {
4692
+ ...resolved,
4693
+ logPath: path6.join(stateDir, "agenr", "logs", "debug.jsonl")
4694
+ };
4695
+ } catch {
4696
+ return { ...resolved, enabled: false };
4697
+ }
4698
+ }
3369
4699
  function toPublicEmbeddingStatus(status) {
3370
4700
  return {
3371
4701
  available: status.available,
@@ -3415,6 +4745,15 @@ function requireApiKey(status) {
3415
4745
  }
3416
4746
  return status.apiKey;
3417
4747
  }
4748
+ function resolveCrossEncoder(config) {
4749
+ try {
4750
+ const apiKey = resolveCrossEncoderApiKey(config);
4751
+ const { modelId } = resolveModel(config, "cross_encoder");
4752
+ return createOpenAICrossEncoder({ apiKey, model: modelId });
4753
+ } catch {
4754
+ return void 0;
4755
+ }
4756
+ }
3418
4757
  async function createClaimExtractionRuntime(config, openClaw, pluginConfig) {
3419
4758
  const claimExtractionConfig = resolveClaimExtractionConfig(config);
3420
4759
  if (!claimExtractionConfig.enabled) {
@@ -3453,6 +4792,9 @@ var openclaw_default = definePluginEntry({
3453
4792
  },
3454
4793
  resolvePath: api.resolvePath
3455
4794
  });
4795
+ void servicesPromise.catch((error) => {
4796
+ api.logger.error(`[agenr] startup failed: ${formatErrorMessage2(error)}`);
4797
+ });
3456
4798
  api.registerMemoryCapability({
3457
4799
  promptBuilder: buildAgenrMemoryPromptSection,
3458
4800
  flushPlanResolver: (params) => buildAgenrMemoryFlushPlan(params, api.logger),