agenr 3.1.0 → 3.3.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/CHANGELOG.md +55 -0
- package/README.md +7 -5
- package/dist/adapters/skeln/index.d.ts +62 -4
- package/dist/adapters/skeln/index.js +2629 -85
- package/dist/{chunk-V5CDMHRN.js → chunk-KH52KJSJ.js} +1 -1
- package/dist/{chunk-EEEL53X4.js → chunk-M5M65AYP.js} +1 -2
- package/dist/{chunk-NOIZQRQV.js → chunk-RYMSM3OS.js} +10 -8
- package/dist/{chunk-NNO2V4GH.js → chunk-Z5X7T4QZ.js} +163 -190
- package/dist/{ports-CpzWESmZ.d.ts → claim-slot-policy-CQ-h0GaV.d.ts} +10 -55
- package/dist/cli.js +1352 -158
- package/dist/core/recall/index.d.ts +2 -3
- package/dist/internal-eval-server.js +3 -4
- package/dist/internal-recall-eval-server.js +3 -4
- package/package.json +8 -5
- package/dist/adapters/openclaw/index.d.ts +0 -32
- package/dist/adapters/openclaw/index.js +0 -3112
- package/dist/chunk-E2DHUFZK.js +0 -2660
- package/dist/chunk-GELCEVFA.js +0 -14
- package/dist/chunk-JSVQILB3.js +0 -1207
- package/dist/claim-slot-policy-CdrW_1l4.d.ts +0 -13
|
@@ -1,3112 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
OpenClawTranscriptParser,
|
|
3
|
-
deriveOpenClawSessionIdFromFilePath,
|
|
4
|
-
openClawTranscriptParser,
|
|
5
|
-
parseTuiSessionKey,
|
|
6
|
-
readOpenClawSessionsStore
|
|
7
|
-
} from "../../chunk-JSVQILB3.js";
|
|
8
|
-
import {
|
|
9
|
-
BEFORE_TURN_DEBUG_ARTIFACT_DEFAULT_TOP_K,
|
|
10
|
-
BEFORE_TURN_DEBUG_ARTIFACT_MAX_TOP_K,
|
|
11
|
-
RECALL_DEBUG_ARTIFACT_DEFAULT_TOP_K,
|
|
12
|
-
RECALL_DEBUG_ARTIFACT_MAX_TOP_K
|
|
13
|
-
} from "../../chunk-GELCEVFA.js";
|
|
14
|
-
import {
|
|
15
|
-
EPISODE_SUMMARY_TIMEOUT_MS,
|
|
16
|
-
FETCH_TOOL_PARAMETERS,
|
|
17
|
-
RECALL_TOOL_PARAMETERS,
|
|
18
|
-
STORE_TOOL_PARAMETERS,
|
|
19
|
-
UPDATE_TOOL_PARAMETERS,
|
|
20
|
-
buildClaimExtractionRuntime,
|
|
21
|
-
buildRecallToolServices,
|
|
22
|
-
composeHostPluginServices,
|
|
23
|
-
createDeadlineAwareEpisodeSummaryLlm,
|
|
24
|
-
createSessionStartTracker,
|
|
25
|
-
embedEpisodeSummaryWithinBudget,
|
|
26
|
-
extractRecentTurnsFromMessages,
|
|
27
|
-
formatAgenrSessionStartRecall,
|
|
28
|
-
formatUnifiedRecallResults,
|
|
29
|
-
normalizeOptionalBoolean,
|
|
30
|
-
normalizeOptionalPositiveInteger,
|
|
31
|
-
normalizePluginInjectionMemoryPolicyConfig,
|
|
32
|
-
normalizePromptText,
|
|
33
|
-
parseFetchToolParams,
|
|
34
|
-
parseRecallToolParams,
|
|
35
|
-
parseStoreToolParams,
|
|
36
|
-
parseUpdateToolParams,
|
|
37
|
-
resolveBeforeTurnPolicy,
|
|
38
|
-
resolveSessionIdentityKey,
|
|
39
|
-
resolveSessionStartPolicy,
|
|
40
|
-
runFetchMemoryTool,
|
|
41
|
-
runRecallMemoryTool,
|
|
42
|
-
runSessionStart,
|
|
43
|
-
runStoreMemoryTool,
|
|
44
|
-
runUpdateMemoryTool,
|
|
45
|
-
writeBoundedSingleTranscriptEpisode
|
|
46
|
-
} from "../../chunk-E2DHUFZK.js";
|
|
47
|
-
import {
|
|
48
|
-
asRecord,
|
|
49
|
-
buildEntryMemoryResolverPorts,
|
|
50
|
-
createSingleTranscriptDiscoveryPort,
|
|
51
|
-
formatErrorMessage,
|
|
52
|
-
formatTargetSelector,
|
|
53
|
-
readBooleanParam,
|
|
54
|
-
resolveTargetEntry,
|
|
55
|
-
sanitizeFetchToolParams,
|
|
56
|
-
sanitizeUpdateToolParams
|
|
57
|
-
} from "../../chunk-EEEL53X4.js";
|
|
58
|
-
import {
|
|
59
|
-
containsAgenrMemoryContext,
|
|
60
|
-
formatAgenrBeforeTurnRecall,
|
|
61
|
-
runBeforeTurn,
|
|
62
|
-
stripAgenrMemoryContext
|
|
63
|
-
} from "../../chunk-V5CDMHRN.js";
|
|
64
|
-
import {
|
|
65
|
-
EMBEDDING_DIMENSIONS,
|
|
66
|
-
buildRecallToolDetails,
|
|
67
|
-
formatRecallToolSummary,
|
|
68
|
-
formatUnifiedRecallLogSummary,
|
|
69
|
-
sanitizeRecallToolParams,
|
|
70
|
-
sanitizeStoreToolParams,
|
|
71
|
-
truncate
|
|
72
|
-
} from "../../chunk-NNO2V4GH.js";
|
|
73
|
-
import {
|
|
74
|
-
resolveClaimSlotPolicy
|
|
75
|
-
} from "../../chunk-5LADPJ4C.js";
|
|
76
|
-
|
|
77
|
-
// src/adapters/openclaw/index.ts
|
|
78
|
-
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
79
|
-
|
|
80
|
-
// src/adapters/openclaw/tools/shared.ts
|
|
81
|
-
import { failedTextResult, readNumberParam, readStringArrayParam, readStringParam, textResult } from "openclaw/plugin-sdk/agent-runtime";
|
|
82
|
-
var OPENCLAW_PARAM_READER = {
|
|
83
|
-
readString: readStringParam,
|
|
84
|
-
readNumber: readNumberParam,
|
|
85
|
-
readStringArray: readStringArrayParam
|
|
86
|
-
};
|
|
87
|
-
function toOpenClawToolResult(outcome) {
|
|
88
|
-
if (outcome.failed) {
|
|
89
|
-
return failedTextResult(outcome.text, {
|
|
90
|
-
...outcome.details,
|
|
91
|
-
status: "failed"
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
return textResult(outcome.text, outcome.details);
|
|
95
|
-
}
|
|
96
|
-
async function resolveTargetEntry2(services, params, options = {}) {
|
|
97
|
-
return resolveTargetEntry(buildEntryMemoryResolverPorts(services), params, options);
|
|
98
|
-
}
|
|
99
|
-
function logToolCall(logger, toolName, ctx, summary, sanitizedParams) {
|
|
100
|
-
logger.info(`[agenr] tool=${toolName} ${formatToolSessionContext(ctx)} ${summary}`);
|
|
101
|
-
logger.info(`[agenr] tool=${toolName} ${formatToolSessionContext(ctx)} params=${JSON.stringify(sanitizedParams)}`);
|
|
102
|
-
}
|
|
103
|
-
function logToolFailure(logger, toolName, ctx, error) {
|
|
104
|
-
logger.warn(`[agenr] tool=${toolName} ${formatToolSessionContext(ctx)} failed: ${formatErrorMessage(error)}`);
|
|
105
|
-
}
|
|
106
|
-
function sanitizeRetireToolParams(params) {
|
|
107
|
-
return {
|
|
108
|
-
...params.id ? { id: params.id } : {},
|
|
109
|
-
...params.subject ? { subject: params.subject } : {},
|
|
110
|
-
...params.reason !== void 0 ? { reasonLength: params.reason.length } : {}
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
function sanitizeTraceToolParams(params) {
|
|
114
|
-
return {
|
|
115
|
-
...params.id ? { id: params.id } : {},
|
|
116
|
-
...params.subject ? { subject: params.subject } : {},
|
|
117
|
-
...params.last !== void 0 ? { last: params.last } : {}
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
function formatTrace(entry, supersededBy, supersedes, claimFamily, recallEvents) {
|
|
121
|
-
const slotPolicy = entry.claim_key ? claimFamily ? {
|
|
122
|
-
policy: claimFamily.slotPolicy ?? resolveClaimSlotPolicy(claimFamily.claimKey).policy,
|
|
123
|
-
reason: claimFamily.slotPolicyReason ?? resolveClaimSlotPolicy(claimFamily.claimKey).reason
|
|
124
|
-
} : resolveClaimSlotPolicy(entry.claim_key) : void 0;
|
|
125
|
-
const lines = [
|
|
126
|
-
`Trace for ${entry.id} | ${entry.subject}`,
|
|
127
|
-
`type=${entry.type} expiry=${entry.expiry} importance=${entry.importance} retired=${entry.retired}`,
|
|
128
|
-
`content=${truncate(entry.content, 220)}`
|
|
129
|
-
];
|
|
130
|
-
if (supersededBy) {
|
|
131
|
-
lines.push(`superseded_by=${supersededBy.id} | ${supersededBy.subject}`);
|
|
132
|
-
}
|
|
133
|
-
if (supersedes.length > 0) {
|
|
134
|
-
lines.push(`supersedes=${supersedes.map((item) => `${item.id} (${item.subject})`).join(", ")}`);
|
|
135
|
-
}
|
|
136
|
-
if (entry.claim_key) {
|
|
137
|
-
lines.push(`claim_key=${entry.claim_key}`);
|
|
138
|
-
if (slotPolicy) {
|
|
139
|
-
lines.push(`slot_policy=${slotPolicy.policy}`);
|
|
140
|
-
lines.push(`slot_policy_reason=${slotPolicy.reason}`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (claimFamily && claimFamily.entries.length > 0) {
|
|
144
|
-
lines.push(
|
|
145
|
-
`claim_family=${claimFamily.claimKey} | slot_policy=${slotPolicy?.policy ?? "exclusive"} | ${claimFamily.entries.map((item) => `${item.id}:${describeTraceEntryState(item)}:${formatClaimLifecycleLabel(item)}`).join(", ")}`
|
|
146
|
-
);
|
|
147
|
-
if (slotPolicy) {
|
|
148
|
-
lines.push(`claim_family_policy_reason=${slotPolicy.reason}`);
|
|
149
|
-
}
|
|
150
|
-
const transitionSummary = summarizeTraceClaimFamilyTransition(claimFamily.entries);
|
|
151
|
-
if (transitionSummary) {
|
|
152
|
-
lines.push(`transition=${transitionSummary}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (entry.valid_from || entry.valid_to) {
|
|
156
|
-
lines.push(`validity=${entry.valid_from ?? "?"} -> ${entry.valid_to ?? "ongoing"}`);
|
|
157
|
-
}
|
|
158
|
-
if (entry.supersession_kind) {
|
|
159
|
-
lines.push(`supersession_kind=${entry.supersession_kind}${entry.supersession_reason ? ` reason=${truncate(entry.supersession_reason, 120)}` : ""}`);
|
|
160
|
-
}
|
|
161
|
-
if (recallEvents.length > 0) {
|
|
162
|
-
lines.push(
|
|
163
|
-
`recent_recalls=${recallEvents.map((event) => `${event.recalledAt}${event.query ? ` query=${event.query}` : ""}${event.sessionKey ? ` session=${event.sessionKey}` : ""}`).join(" ; ")}`
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
return lines.join("\n");
|
|
167
|
-
}
|
|
168
|
-
function toolFailureResult(error) {
|
|
169
|
-
return failedTextResult(formatErrorMessage(error), {
|
|
170
|
-
status: "failed"
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
function formatToolSessionContext(ctx) {
|
|
174
|
-
const normalizedSessionId = ctx.sessionId?.trim();
|
|
175
|
-
const normalizedSessionKey = ctx.sessionKey?.trim();
|
|
176
|
-
if (normalizedSessionId && normalizedSessionKey) {
|
|
177
|
-
return `session=${normalizedSessionId} key=${normalizedSessionKey}`;
|
|
178
|
-
}
|
|
179
|
-
if (normalizedSessionId) {
|
|
180
|
-
return `session=${normalizedSessionId}`;
|
|
181
|
-
}
|
|
182
|
-
if (normalizedSessionKey) {
|
|
183
|
-
return `key=${normalizedSessionKey}`;
|
|
184
|
-
}
|
|
185
|
-
return "session=unknown";
|
|
186
|
-
}
|
|
187
|
-
function describeTraceEntryState(entry) {
|
|
188
|
-
if (entry.superseded_by) {
|
|
189
|
-
return "superseded";
|
|
190
|
-
}
|
|
191
|
-
if (entry.retired || entry.valid_to) {
|
|
192
|
-
return "historical";
|
|
193
|
-
}
|
|
194
|
-
return "current";
|
|
195
|
-
}
|
|
196
|
-
function formatClaimLifecycleLabel(entry) {
|
|
197
|
-
if (!entry.claim_key) {
|
|
198
|
-
return "no-key";
|
|
199
|
-
}
|
|
200
|
-
return entry.claim_key_status ?? "legacy";
|
|
201
|
-
}
|
|
202
|
-
function summarizeTraceClaimFamilyTransition(entries) {
|
|
203
|
-
const current = entries.find((entry) => !entry.retired && !entry.superseded_by);
|
|
204
|
-
const prior = [...entries].reverse().find((entry) => entry.id !== current?.id && (entry.superseded_by !== void 0 || entry.retired || entry.valid_to !== void 0));
|
|
205
|
-
if (current && prior) {
|
|
206
|
-
return `${prior.id} -> ${current.id}`;
|
|
207
|
-
}
|
|
208
|
-
if (prior) {
|
|
209
|
-
return `${prior.id} is historical with no current sibling in the traced family`;
|
|
210
|
-
}
|
|
211
|
-
if (current) {
|
|
212
|
-
return `${current.id} is the only current sibling in the traced family`;
|
|
213
|
-
}
|
|
214
|
-
return void 0;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// src/adapters/openclaw/tools/fetch.ts
|
|
218
|
-
function createAgenrFetchTool(ctx, servicesPromise, logger) {
|
|
219
|
-
return {
|
|
220
|
-
name: "agenr_fetch",
|
|
221
|
-
label: "Agenr Fetch",
|
|
222
|
-
description: "Fetch the full body and metadata for one durable memory entry by id or subject.",
|
|
223
|
-
parameters: FETCH_TOOL_PARAMETERS,
|
|
224
|
-
async execute(_toolCallId, rawParams) {
|
|
225
|
-
try {
|
|
226
|
-
const params = parseFetchToolParams(rawParams, OPENCLAW_PARAM_READER);
|
|
227
|
-
logToolCall(logger, "agenr_fetch", ctx, `target=${formatTargetSelector(params.id, params.subject)}`, sanitizeFetchToolParams(params));
|
|
228
|
-
const services = await servicesPromise;
|
|
229
|
-
return toOpenClawToolResult(
|
|
230
|
-
await runFetchMemoryTool(params, services, {
|
|
231
|
-
extraDetails: { sessionKey: ctx.sessionKey }
|
|
232
|
-
})
|
|
233
|
-
);
|
|
234
|
-
} catch (error) {
|
|
235
|
-
logToolFailure(logger, "agenr_fetch", ctx, error);
|
|
236
|
-
return toolFailureResult(error);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// src/adapters/openclaw/tools/recall.ts
|
|
243
|
-
import { textResult as textResult2 } from "openclaw/plugin-sdk/agent-runtime";
|
|
244
|
-
import { randomUUID } from "crypto";
|
|
245
|
-
|
|
246
|
-
// src/adapters/openclaw/debug/sink.ts
|
|
247
|
-
import { appendFile, mkdir } from "fs/promises";
|
|
248
|
-
import path from "path";
|
|
249
|
-
var NOOP_SINK = {
|
|
250
|
-
enabled: false,
|
|
251
|
-
eventLevel: "basic",
|
|
252
|
-
maxTopCandidates: 0,
|
|
253
|
-
async emit() {
|
|
254
|
-
return;
|
|
255
|
-
},
|
|
256
|
-
async close() {
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
function createNoopAgenrDebugSink() {
|
|
261
|
-
return NOOP_SINK;
|
|
262
|
-
}
|
|
263
|
-
function createAgenrDebugSink(config) {
|
|
264
|
-
if (!config.enabled || !config.logPath) {
|
|
265
|
-
return NOOP_SINK;
|
|
266
|
-
}
|
|
267
|
-
const basePath = config.logPath;
|
|
268
|
-
const perSessionFiles = config.perSessionFiles;
|
|
269
|
-
const eventLevel = config.eventLevel;
|
|
270
|
-
const maxTopCandidates = config.maxTopCandidates;
|
|
271
|
-
const directoriesEnsured = /* @__PURE__ */ new Set();
|
|
272
|
-
let writeChain = Promise.resolve();
|
|
273
|
-
let closed = false;
|
|
274
|
-
const ensureDirectoryOnce = async (filePath) => {
|
|
275
|
-
const directory = path.dirname(filePath);
|
|
276
|
-
if (directoriesEnsured.has(directory)) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
await mkdir(directory, { recursive: true });
|
|
280
|
-
directoriesEnsured.add(directory);
|
|
281
|
-
};
|
|
282
|
-
const resolveFilePath = (event) => {
|
|
283
|
-
if (!perSessionFiles) {
|
|
284
|
-
return basePath;
|
|
285
|
-
}
|
|
286
|
-
const sessionSuffix = sanitizeSessionSuffix(event.sessionId) ?? sanitizeSessionSuffix(event.sessionKey);
|
|
287
|
-
if (!sessionSuffix) {
|
|
288
|
-
return basePath;
|
|
289
|
-
}
|
|
290
|
-
const directory = path.dirname(basePath);
|
|
291
|
-
const extension = path.extname(basePath);
|
|
292
|
-
const baseName = path.basename(basePath, extension);
|
|
293
|
-
const suffixed = `${baseName}.${sessionSuffix}${extension || ".jsonl"}`;
|
|
294
|
-
return path.join(directory, suffixed);
|
|
295
|
-
};
|
|
296
|
-
const writeLine = async (filePath, line) => {
|
|
297
|
-
await ensureDirectoryOnce(filePath);
|
|
298
|
-
await appendFile(filePath, `${line}
|
|
299
|
-
`, "utf8");
|
|
300
|
-
};
|
|
301
|
-
return {
|
|
302
|
-
enabled: true,
|
|
303
|
-
eventLevel,
|
|
304
|
-
maxTopCandidates,
|
|
305
|
-
logPath: basePath,
|
|
306
|
-
async emit(event) {
|
|
307
|
-
if (closed) {
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
const filePath = resolveFilePath(event);
|
|
311
|
-
const line = formatEventLine(event);
|
|
312
|
-
writeChain = writeChain.then(async () => {
|
|
313
|
-
try {
|
|
314
|
-
await writeLine(filePath, line);
|
|
315
|
-
} catch {
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
await writeChain;
|
|
319
|
-
},
|
|
320
|
-
async close() {
|
|
321
|
-
closed = true;
|
|
322
|
-
await writeChain;
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
function formatEventLine(event) {
|
|
327
|
-
const line = {
|
|
328
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
329
|
-
...event
|
|
330
|
-
};
|
|
331
|
-
return JSON.stringify(line);
|
|
332
|
-
}
|
|
333
|
-
function sanitizeSessionSuffix(value) {
|
|
334
|
-
if (typeof value !== "string") {
|
|
335
|
-
return void 0;
|
|
336
|
-
}
|
|
337
|
-
const trimmed = value.trim();
|
|
338
|
-
if (trimmed.length === 0) {
|
|
339
|
-
return void 0;
|
|
340
|
-
}
|
|
341
|
-
const sanitized = trimmed.replace(/[^A-Za-z0-9._-]+/gu, "_");
|
|
342
|
-
if (sanitized.length === 0) {
|
|
343
|
-
return void 0;
|
|
344
|
-
}
|
|
345
|
-
return sanitized.slice(0, 120);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// src/adapters/openclaw/debug/build-before-turn-artifact.ts
|
|
349
|
-
function buildLiveBeforeTurnDebugArtifact(params) {
|
|
350
|
-
const { caseId, patch, currentTurnText, trigger, eventLevel, maxTopCandidates } = params;
|
|
351
|
-
const diagnostics = patch.diagnostics;
|
|
352
|
-
const includeCandidateBreakdown = eventLevel === "detailed";
|
|
353
|
-
const topK = includeCandidateBreakdown ? clampTopK(maxTopCandidates) : 0;
|
|
354
|
-
const durableTopCandidates = includeCandidateBreakdown ? buildDurableCandidates(patch, topK) : [];
|
|
355
|
-
const procedureTopCandidates = includeCandidateBreakdown ? buildProcedureCandidates(patch, topK) : [];
|
|
356
|
-
const normalizedTrigger = trigger?.trim() || "unspecified";
|
|
357
|
-
return {
|
|
358
|
-
schemaVersion: "before-turn-debug-artifact.v1",
|
|
359
|
-
caseId,
|
|
360
|
-
input: {
|
|
361
|
-
trigger: normalizedTrigger,
|
|
362
|
-
currentTurnText
|
|
363
|
-
},
|
|
364
|
-
...diagnostics.queryPolicy ? { queryPolicy: diagnostics.queryPolicy } : {},
|
|
365
|
-
...diagnostics.queryVariants.length > 0 ? { queryVariants: [...diagnostics.queryVariants] } : {},
|
|
366
|
-
...diagnostics.abstentionReasons.length > 0 ? { abstentionReasons: [...diagnostics.abstentionReasons] } : {},
|
|
367
|
-
selectedEntryIds: patch.durableMemory.map((item) => item.entry.id),
|
|
368
|
-
selectedProcedureKey: patch.procedure?.procedure.procedure_key ?? null,
|
|
369
|
-
...durableTopCandidates.length > 0 ? { durableRecallTopCandidates: durableTopCandidates } : {},
|
|
370
|
-
...procedureTopCandidates.length > 0 ? { procedureTopCandidates } : {}
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
function clampTopK(requested) {
|
|
374
|
-
if (!Number.isFinite(requested) || !Number.isInteger(requested)) {
|
|
375
|
-
return BEFORE_TURN_DEBUG_ARTIFACT_DEFAULT_TOP_K;
|
|
376
|
-
}
|
|
377
|
-
if (requested < 1) {
|
|
378
|
-
return 0;
|
|
379
|
-
}
|
|
380
|
-
if (requested > BEFORE_TURN_DEBUG_ARTIFACT_MAX_TOP_K) {
|
|
381
|
-
return BEFORE_TURN_DEBUG_ARTIFACT_MAX_TOP_K;
|
|
382
|
-
}
|
|
383
|
-
return requested;
|
|
384
|
-
}
|
|
385
|
-
function buildDurableCandidates(patch, topK) {
|
|
386
|
-
if (topK < 1) {
|
|
387
|
-
return [];
|
|
388
|
-
}
|
|
389
|
-
return patch.durableMemory.slice(0, topK).map((item) => {
|
|
390
|
-
const reasons = item.whySurfaced.reasons.length > 0 ? [...item.whySurfaced.reasons] : void 0;
|
|
391
|
-
return {
|
|
392
|
-
id: item.entry.id,
|
|
393
|
-
score: item.score,
|
|
394
|
-
...reasons ? { reasons } : {}
|
|
395
|
-
};
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
function buildProcedureCandidates(patch, topK) {
|
|
399
|
-
if (!patch.procedure || topK < 1) {
|
|
400
|
-
return [];
|
|
401
|
-
}
|
|
402
|
-
const reasons = patch.procedure.whySurfaced.reasons.length > 0 ? [...patch.procedure.whySurfaced.reasons] : void 0;
|
|
403
|
-
return [
|
|
404
|
-
{
|
|
405
|
-
procedureKey: patch.procedure.procedure.procedure_key,
|
|
406
|
-
score: patch.procedure.score,
|
|
407
|
-
...reasons ? { reasons } : {}
|
|
408
|
-
}
|
|
409
|
-
];
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// src/adapters/openclaw/debug/build-recall-artifact.ts
|
|
413
|
-
function buildLiveRecallDebugArtifact(params) {
|
|
414
|
-
const { caseId, query, result, eventLevel, maxTopCandidates } = params;
|
|
415
|
-
const selectedEntryIds = result.entries.map((entry) => entry.entry.id);
|
|
416
|
-
const includeCandidateBreakdown = eventLevel === "detailed";
|
|
417
|
-
const topK = includeCandidateBreakdown ? clampTopK2(maxTopCandidates) : 0;
|
|
418
|
-
const reasonsByEntryId = /* @__PURE__ */ new Map();
|
|
419
|
-
for (const projected of result.projectedEntries) {
|
|
420
|
-
if (projected.whySurfaced.reasons.length > 0) {
|
|
421
|
-
reasonsByEntryId.set(projected.entryId, [...projected.whySurfaced.reasons]);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
const topCandidates = includeCandidateBreakdown ? buildTopCandidates(result, reasonsByEntryId, topK) : [];
|
|
425
|
-
return {
|
|
426
|
-
schemaVersion: "recall-debug-artifact.v1",
|
|
427
|
-
caseId,
|
|
428
|
-
request: {
|
|
429
|
-
recallPath: "unified",
|
|
430
|
-
query
|
|
431
|
-
},
|
|
432
|
-
routing: result.routing,
|
|
433
|
-
selectedEntryIds,
|
|
434
|
-
...topCandidates.length > 0 ? { topCandidates } : {}
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
function clampTopK2(requested) {
|
|
438
|
-
if (!Number.isFinite(requested) || !Number.isInteger(requested)) {
|
|
439
|
-
return RECALL_DEBUG_ARTIFACT_DEFAULT_TOP_K;
|
|
440
|
-
}
|
|
441
|
-
if (requested < 1) {
|
|
442
|
-
return 0;
|
|
443
|
-
}
|
|
444
|
-
if (requested > RECALL_DEBUG_ARTIFACT_MAX_TOP_K) {
|
|
445
|
-
return RECALL_DEBUG_ARTIFACT_MAX_TOP_K;
|
|
446
|
-
}
|
|
447
|
-
return requested;
|
|
448
|
-
}
|
|
449
|
-
function buildTopCandidates(result, reasonsByEntryId, topK) {
|
|
450
|
-
if (topK < 1) {
|
|
451
|
-
return [];
|
|
452
|
-
}
|
|
453
|
-
return result.entries.slice(0, topK).map((entry) => {
|
|
454
|
-
const reasons = reasonsByEntryId.get(entry.entry.id);
|
|
455
|
-
return {
|
|
456
|
-
id: entry.entry.id,
|
|
457
|
-
score: entry.score,
|
|
458
|
-
lexicalScore: entry.scores.lexical,
|
|
459
|
-
vectorScore: entry.scores.vector,
|
|
460
|
-
recencyScore: entry.scores.recency,
|
|
461
|
-
importanceScore: entry.scores.importance,
|
|
462
|
-
...reasons && reasons.length > 0 ? { reasons } : {}
|
|
463
|
-
};
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// src/adapters/openclaw/tools/recall.ts
|
|
468
|
-
function createAgenrRecallTool(ctx, servicesPromise, logger) {
|
|
469
|
-
return {
|
|
470
|
-
name: "agenr_recall",
|
|
471
|
-
label: "Agenr Recall",
|
|
472
|
-
description: "Retrieve knowledge from agenr long-term memory. Use mode=auto for the normal path, including historical-state questions like what was the previous approach or what changed from X to Y and procedural questions like how to do something or what steps to follow; use mode=entries for exact facts and decisions; use mode=episodes for time-bounded 'what happened' questions; use mode=procedures for canonical methods and checklists. Time periods are parsed from the query text. Session-start recall is already handled automatically.",
|
|
473
|
-
parameters: RECALL_TOOL_PARAMETERS,
|
|
474
|
-
async execute(_toolCallId, rawParams) {
|
|
475
|
-
try {
|
|
476
|
-
const params = parseRecallToolParams(rawParams, OPENCLAW_PARAM_READER);
|
|
477
|
-
const sanitizedParams = sanitizeRecallToolParams(params);
|
|
478
|
-
logToolCall(logger, "agenr_recall", ctx, formatRecallToolSummary(params), sanitizedParams);
|
|
479
|
-
const services = await servicesPromise;
|
|
480
|
-
void services.debugSink.emit({
|
|
481
|
-
type: "tool_call",
|
|
482
|
-
tool: "agenr_recall",
|
|
483
|
-
...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
|
|
484
|
-
...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
|
|
485
|
-
params: sanitizedParams
|
|
486
|
-
});
|
|
487
|
-
const result = await runRecallMemoryTool(params, buildRecallToolServices(services), {
|
|
488
|
-
sessionKey: ctx.sessionKey,
|
|
489
|
-
slotPolicyConfig: services.pluginConfig.memoryPolicy?.slotPolicies,
|
|
490
|
-
debugLog: (message) => {
|
|
491
|
-
logger.debug?.(message);
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
logger.info(
|
|
495
|
-
`[agenr] tool=agenr_recall session=${ctx.sessionId ?? "unknown"} key=${ctx.sessionKey ?? "unknown"} result: ${formatUnifiedRecallLogSummary(result)}`
|
|
496
|
-
);
|
|
497
|
-
emitRecallDebugArtifacts(services, ctx, params.query, result);
|
|
498
|
-
return textResult2(formatUnifiedRecallResults(result), buildRecallToolDetails(result));
|
|
499
|
-
} catch (error) {
|
|
500
|
-
logToolFailure(logger, "agenr_recall", ctx, error);
|
|
501
|
-
await emitRecallError(servicesPromise, ctx, error);
|
|
502
|
-
return toolFailureResult(error);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
function emitRecallDebugArtifacts(services, ctx, query, result) {
|
|
508
|
-
if (!services.debugSink.enabled) {
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
const sessionIdPayload = ctx.sessionId ? { sessionId: ctx.sessionId } : {};
|
|
512
|
-
const sessionKeyPayload = ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {};
|
|
513
|
-
void services.debugSink.emit({
|
|
514
|
-
type: "tool_result",
|
|
515
|
-
tool: "agenr_recall",
|
|
516
|
-
...sessionIdPayload,
|
|
517
|
-
...sessionKeyPayload,
|
|
518
|
-
summary: {
|
|
519
|
-
count: result.count,
|
|
520
|
-
routing: {
|
|
521
|
-
requested: result.routing.requested,
|
|
522
|
-
detectedIntent: result.routing.detectedIntent,
|
|
523
|
-
queried: [...result.routing.queried],
|
|
524
|
-
reason: result.routing.reason
|
|
525
|
-
},
|
|
526
|
-
selectedEntryIds: result.entries.map((entry) => entry.entry.id),
|
|
527
|
-
episodeIds: result.episodes.map((episode) => episode.episode.id),
|
|
528
|
-
selectedProcedureKey: result.procedure?.procedure_key ?? null,
|
|
529
|
-
notices: [...result.notices],
|
|
530
|
-
procedureNotices: [...result.procedureNotices]
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
void services.debugSink.emit({
|
|
534
|
-
type: "unified_recall",
|
|
535
|
-
...sessionIdPayload,
|
|
536
|
-
...sessionKeyPayload,
|
|
537
|
-
debug: buildLiveRecallDebugArtifact({
|
|
538
|
-
caseId: `live-${randomUUID()}`,
|
|
539
|
-
query,
|
|
540
|
-
result,
|
|
541
|
-
eventLevel: services.debugSink.eventLevel,
|
|
542
|
-
maxTopCandidates: services.debugSink.maxTopCandidates
|
|
543
|
-
})
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
async function emitRecallError(servicesPromise, ctx, error) {
|
|
547
|
-
try {
|
|
548
|
-
const services = await servicesPromise;
|
|
549
|
-
if (services.debugSink.enabled) {
|
|
550
|
-
void services.debugSink.emit({
|
|
551
|
-
type: "error",
|
|
552
|
-
...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
|
|
553
|
-
...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
|
|
554
|
-
scope: "agenr_recall",
|
|
555
|
-
error: { message: error instanceof Error ? error.message : String(error) }
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
} catch {
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// src/adapters/openclaw/tools/retire.ts
|
|
563
|
-
import { failedTextResult as failedTextResult2, readStringParam as readStringParam2, textResult as textResult3 } from "openclaw/plugin-sdk/agent-runtime";
|
|
564
|
-
var RETIRE_TOOL_PARAMETERS = {
|
|
565
|
-
type: "object",
|
|
566
|
-
additionalProperties: false,
|
|
567
|
-
properties: {
|
|
568
|
-
id: {
|
|
569
|
-
type: "string",
|
|
570
|
-
description: "Entry id to retire. Provide exactly one of id or subject."
|
|
571
|
-
},
|
|
572
|
-
subject: {
|
|
573
|
-
type: "string",
|
|
574
|
-
description: "Subject text to resolve when the id is unknown. The most recent exact or substring match wins. Provide exactly one of id or subject."
|
|
575
|
-
},
|
|
576
|
-
reason: {
|
|
577
|
-
type: "string",
|
|
578
|
-
description: "Optional retirement reason so later trace output explains why this memory was removed."
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
};
|
|
582
|
-
function createAgenrRetireTool(ctx, servicesPromise, logger) {
|
|
583
|
-
return {
|
|
584
|
-
name: "agenr_retire",
|
|
585
|
-
label: "Agenr Retire",
|
|
586
|
-
description: "Mark a memory entry as retired (soft delete). Retired entries are excluded from all recall.",
|
|
587
|
-
parameters: RETIRE_TOOL_PARAMETERS,
|
|
588
|
-
async execute(_toolCallId, rawParams) {
|
|
589
|
-
try {
|
|
590
|
-
const params = asRecord(rawParams);
|
|
591
|
-
const id = readStringParam2(params, "id");
|
|
592
|
-
const subject = readStringParam2(params, "subject");
|
|
593
|
-
const reason = readStringParam2(params, "reason");
|
|
594
|
-
logToolCall(logger, "agenr_retire", ctx, `target=${formatTargetSelector(id, subject)}`, sanitizeRetireToolParams({ id, subject, reason }));
|
|
595
|
-
const services = await servicesPromise;
|
|
596
|
-
const entry = await resolveTargetEntry2(services, params);
|
|
597
|
-
const retired = await services.entries.retireEntry(entry.id, reason);
|
|
598
|
-
if (!retired) {
|
|
599
|
-
return failedTextResult2(`Entry ${entry.id} is not active, so it could not be retired.`, {
|
|
600
|
-
status: "failed",
|
|
601
|
-
entryId: entry.id
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
return textResult3(`Retired "${entry.subject}".`, {
|
|
605
|
-
status: "retired",
|
|
606
|
-
entryId: entry.id,
|
|
607
|
-
subject: entry.subject,
|
|
608
|
-
sessionKey: ctx.sessionKey
|
|
609
|
-
});
|
|
610
|
-
} catch (error) {
|
|
611
|
-
logToolFailure(logger, "agenr_retire", ctx, error);
|
|
612
|
-
return toolFailureResult(error);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// src/adapters/openclaw/tools/store.ts
|
|
619
|
-
function createAgenrStoreTool(ctx, servicesPromise, logger) {
|
|
620
|
-
return {
|
|
621
|
-
name: "agenr_store",
|
|
622
|
-
label: "Agenr Store",
|
|
623
|
-
description: "Store a new durable memory entry in agenr. Apply the future-session test first: will a fresh future session make a better decision because this was stored, or are you just logging that something happened?\n\nIf another system is already the canonical record - such as version control, a task or ticket tracker, a calendar, a signed document, a chat or email thread, or a database/CRM - usually do not store that record here. Store only the durable takeaway: the standing implication, rule, lesson, preference, risk, or relationship.\n\nType guide: fact = durable truth about a person, system, place, or how something works. decision = a standing rule, constraint, policy, or chosen approach future sessions should follow. preference = what someone likes, wants, values, or wants avoided. lesson = a non-obvious takeaway learned from experience that should change future behavior. milestone = a rare one-time event with durable future significance, not ordinary execution progress. relationship = a meaningful durable connection between people, groups, or systems.\n\nUsually do not store: 'I merged PR #123.', 'I filed a ticket with support.', 'We had a meeting at 3 PM.', 'I sent the contract for signature.', 'We spent two hours debugging the outage.' Do store the takeaway instead: 'Always use the structured export path because raw sync corrupts timestamps.' 'Jim prefers text-first updates and dislikes surprise calls.' 'Service restarts fail unless config Y is enabled.' 'The office Wi-Fi name is Acorn-5G.'\n\nDo not use decision as a catch-all for important activity updates. Do not store plans, checklists, speculative future state, progress snapshots, session narration, or rephrased recalled material.\n\nWhen replacing an existing fact, pass `supersedes` with the old entry's ID. When storing a slot-like fact (for example, a library version or a rollout strategy), pass `claimKey` to enable future supersession detection.\n\nDo not ask before storing - but do ask whether future-you actually needs it.",
|
|
624
|
-
parameters: STORE_TOOL_PARAMETERS,
|
|
625
|
-
async execute(_toolCallId, rawParams) {
|
|
626
|
-
try {
|
|
627
|
-
const params = parseStoreToolParams(rawParams, OPENCLAW_PARAM_READER);
|
|
628
|
-
logToolCall(logger, "agenr_store", ctx, `store 1 entry subject=${JSON.stringify(params.subject)} type=${params.type}`, sanitizeStoreToolParams(params));
|
|
629
|
-
const services = await servicesPromise;
|
|
630
|
-
return toOpenClawToolResult(
|
|
631
|
-
await runStoreMemoryTool(params, services, {
|
|
632
|
-
session: ctx,
|
|
633
|
-
sourcePrefix: "openclaw-session",
|
|
634
|
-
defaultSourceContext: "Stored via agenr_store from OpenClaw.",
|
|
635
|
-
onWarning: (warning) => logger.warn(`[agenr] tool=agenr_store session=${ctx.sessionId ?? "unknown"} warning: ${warning}`)
|
|
636
|
-
})
|
|
637
|
-
);
|
|
638
|
-
} catch (error) {
|
|
639
|
-
logToolFailure(logger, "agenr_store", ctx, error);
|
|
640
|
-
return toolFailureResult(error);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// src/adapters/openclaw/tools/trace.ts
|
|
647
|
-
import { failedTextResult as failedTextResult3, readStringParam as readStringParam3, textResult as textResult4 } from "openclaw/plugin-sdk/agent-runtime";
|
|
648
|
-
var TRACE_TOOL_PARAMETERS = {
|
|
649
|
-
type: "object",
|
|
650
|
-
additionalProperties: false,
|
|
651
|
-
properties: {
|
|
652
|
-
id: {
|
|
653
|
-
type: "string",
|
|
654
|
-
description: "Entry id to trace. Provide exactly one of id, subject, or last."
|
|
655
|
-
},
|
|
656
|
-
subject: {
|
|
657
|
-
type: "string",
|
|
658
|
-
description: "Subject text to resolve when the id is unknown. The most recent exact or substring match wins. Provide exactly one of id, subject, or last."
|
|
659
|
-
},
|
|
660
|
-
last: {
|
|
661
|
-
type: "boolean",
|
|
662
|
-
description: "Set true to trace the most recently created agenr entry. Provide exactly one of id, subject, or last."
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
};
|
|
666
|
-
function createAgenrTraceTool(ctx, servicesPromise, logger) {
|
|
667
|
-
return {
|
|
668
|
-
name: "agenr_trace",
|
|
669
|
-
label: "Agenr Trace",
|
|
670
|
-
description: "Trace the provenance of a knowledge entry. The current v1 trace view shows the entry itself, supersession links, and recent recall history. Accepts id, subject, or last for lookup.",
|
|
671
|
-
parameters: TRACE_TOOL_PARAMETERS,
|
|
672
|
-
async execute(_toolCallId, rawParams) {
|
|
673
|
-
try {
|
|
674
|
-
const params = asRecord(rawParams);
|
|
675
|
-
const id = readStringParam3(params, "id");
|
|
676
|
-
const subject = readStringParam3(params, "subject");
|
|
677
|
-
const last = readBooleanParam(params, "last");
|
|
678
|
-
logToolCall(logger, "agenr_trace", ctx, `target=${formatTargetSelector(id, subject, last)}`, sanitizeTraceToolParams({ id, subject, last }));
|
|
679
|
-
const services = await servicesPromise;
|
|
680
|
-
const entry = await resolveTargetEntry2(services, params, { allowLast: true });
|
|
681
|
-
const trace = await services.memory.getEntryTrace(entry.id);
|
|
682
|
-
if (!trace) {
|
|
683
|
-
return failedTextResult3(`Entry ${entry.id} was not found for tracing.`, {
|
|
684
|
-
status: "failed",
|
|
685
|
-
entryId: entry.id
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
return textResult4(formatTrace(trace.entry, trace.supersededBy, trace.supersedes, trace.claimFamily, trace.recallEvents), {
|
|
689
|
-
status: "ok",
|
|
690
|
-
sessionKey: ctx.sessionKey,
|
|
691
|
-
trace: {
|
|
692
|
-
entry: trace.entry,
|
|
693
|
-
supersededBy: trace.supersededBy,
|
|
694
|
-
supersedes: trace.supersedes,
|
|
695
|
-
claimFamily: trace.claimFamily,
|
|
696
|
-
recallEvents: trace.recallEvents
|
|
697
|
-
}
|
|
698
|
-
});
|
|
699
|
-
} catch (error) {
|
|
700
|
-
logToolFailure(logger, "agenr_trace", ctx, error);
|
|
701
|
-
return toolFailureResult(error);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// src/adapters/openclaw/tools/update.ts
|
|
708
|
-
function createAgenrUpdateTool(ctx, servicesPromise, logger) {
|
|
709
|
-
return {
|
|
710
|
-
name: "agenr_update",
|
|
711
|
-
label: "Agenr Update",
|
|
712
|
-
description: "Update an existing memory entry in place. Currently supports importance, expiry, claim_key, valid_from, and valid_to.",
|
|
713
|
-
parameters: UPDATE_TOOL_PARAMETERS,
|
|
714
|
-
async execute(_toolCallId, rawParams) {
|
|
715
|
-
try {
|
|
716
|
-
const params = parseUpdateToolParams(rawParams, OPENCLAW_PARAM_READER);
|
|
717
|
-
logToolCall(
|
|
718
|
-
logger,
|
|
719
|
-
"agenr_update",
|
|
720
|
-
ctx,
|
|
721
|
-
`target=${formatTargetSelector(params.id, params.subject)}${params.importance !== void 0 ? ` importance=${params.importance}` : ""}${params.expiry !== void 0 ? ` expiry=${params.expiry}` : ""}`,
|
|
722
|
-
sanitizeUpdateToolParams({
|
|
723
|
-
id: params.id,
|
|
724
|
-
subject: params.subject,
|
|
725
|
-
importance: params.importance,
|
|
726
|
-
expiry: params.expiry,
|
|
727
|
-
claimKey: params.claimKeyInput,
|
|
728
|
-
validFrom: params.validFrom,
|
|
729
|
-
validTo: params.validTo
|
|
730
|
-
})
|
|
731
|
-
);
|
|
732
|
-
const services = await servicesPromise;
|
|
733
|
-
return toOpenClawToolResult(
|
|
734
|
-
await runUpdateMemoryTool(params, services, {
|
|
735
|
-
session: ctx,
|
|
736
|
-
sourcePrefix: "openclaw-session",
|
|
737
|
-
successDetails: { sessionKey: ctx.sessionKey }
|
|
738
|
-
})
|
|
739
|
-
);
|
|
740
|
-
} catch (error) {
|
|
741
|
-
logToolFailure(logger, "agenr_update", ctx, error);
|
|
742
|
-
return toolFailureResult(error);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// src/adapters/openclaw/tools/index.ts
|
|
749
|
-
function registerAgenrOpenClawTools(api, servicesPromise, logger) {
|
|
750
|
-
api.registerTool((ctx) => createAgenrStoreTool(ctx, servicesPromise, logger), { names: ["agenr_store"] });
|
|
751
|
-
api.registerTool((ctx) => createAgenrRecallTool(ctx, servicesPromise, logger), { names: ["agenr_recall"] });
|
|
752
|
-
api.registerTool((ctx) => createAgenrFetchTool(ctx, servicesPromise, logger), { names: ["agenr_fetch"] });
|
|
753
|
-
api.registerTool((ctx) => createAgenrRetireTool(ctx, servicesPromise, logger), { names: ["agenr_retire"] });
|
|
754
|
-
api.registerTool((ctx) => createAgenrUpdateTool(ctx, servicesPromise, logger), { names: ["agenr_update"] });
|
|
755
|
-
api.registerTool((ctx) => createAgenrTraceTool(ctx, servicesPromise, logger), { names: ["agenr_trace"] });
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// src/adapters/openclaw/openclaw.plugin.json
|
|
759
|
-
var openclaw_plugin_default = {
|
|
760
|
-
id: "agenr",
|
|
761
|
-
name: "agenr",
|
|
762
|
-
version: "3.1.0",
|
|
763
|
-
description: "agenr memory plugin for OpenClaw",
|
|
764
|
-
kind: "memory",
|
|
765
|
-
contracts: {
|
|
766
|
-
tools: ["agenr_store", "agenr_recall", "agenr_fetch", "agenr_retire", "agenr_update", "agenr_trace"]
|
|
767
|
-
},
|
|
768
|
-
uiHints: {
|
|
769
|
-
dbPath: {
|
|
770
|
-
label: "Database path",
|
|
771
|
-
help: "Optional override for the agenr knowledge database. Defaults to the dbPath in agenr's config.json, then ~/.agenr/knowledge.db."
|
|
772
|
-
},
|
|
773
|
-
configPath: {
|
|
774
|
-
label: "Config path",
|
|
775
|
-
help: "Optional path to agenr's config.json. Defaults to AGENR_CONFIG_PATH, then config.json next to dbPath when dbPath is set, then ~/.agenr/config.json."
|
|
776
|
-
},
|
|
777
|
-
continuityModel: {
|
|
778
|
-
label: "Continuity summary model",
|
|
779
|
-
help: "Optional model override for continuity summary generation (provider/model format). Falls back to the agent's primary model when unset."
|
|
780
|
-
},
|
|
781
|
-
episodeModel: {
|
|
782
|
-
label: "Episode summary model",
|
|
783
|
-
help: "Optional model override for episode summary generation (provider/model format). Falls back to the agent's primary model when unset."
|
|
784
|
-
},
|
|
785
|
-
claimExtractionModel: {
|
|
786
|
-
label: "Claim extraction model",
|
|
787
|
-
help: "Model used for claim-key extraction when storing entries. Uses OpenClaw's configured auth. Format: provider/model (for example, openai/gpt-5.4-nano). Defaults to the agent's primary model."
|
|
788
|
-
},
|
|
789
|
-
storeNudge: {
|
|
790
|
-
label: "Store nudge",
|
|
791
|
-
help: "Optional mid-session reminder settings for prompting durable memory storage after several turns of memory silence."
|
|
792
|
-
},
|
|
793
|
-
memoryPolicy: {
|
|
794
|
-
label: "Memory policy",
|
|
795
|
-
help: "Optional runtime overrides for claim-aware read behavior, session-start memory injection, and proactive before-turn surfacing."
|
|
796
|
-
},
|
|
797
|
-
debug: {
|
|
798
|
-
label: "Debug log",
|
|
799
|
-
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."
|
|
800
|
-
}
|
|
801
|
-
},
|
|
802
|
-
configSchema: {
|
|
803
|
-
type: "object",
|
|
804
|
-
additionalProperties: false,
|
|
805
|
-
properties: {
|
|
806
|
-
dbPath: {
|
|
807
|
-
type: "string",
|
|
808
|
-
minLength: 1
|
|
809
|
-
},
|
|
810
|
-
configPath: {
|
|
811
|
-
type: "string",
|
|
812
|
-
minLength: 1
|
|
813
|
-
},
|
|
814
|
-
continuityModel: {
|
|
815
|
-
type: "string",
|
|
816
|
-
minLength: 1,
|
|
817
|
-
description: "Model override for continuity summary generation (e.g. 'openai/gpt-5.4-mini'). Falls back to the agent's primary model."
|
|
818
|
-
},
|
|
819
|
-
episodeModel: {
|
|
820
|
-
type: "string",
|
|
821
|
-
minLength: 1,
|
|
822
|
-
description: "Model override for episode summary generation (e.g. 'openai/gpt-5.4-mini'). Falls back to the agent's primary model."
|
|
823
|
-
},
|
|
824
|
-
claimExtractionModel: {
|
|
825
|
-
type: "string",
|
|
826
|
-
minLength: 1,
|
|
827
|
-
description: "Model override for claim-key extraction at store time (e.g. 'openai/gpt-5.4-mini'). Uses OpenClaw auth and falls back to the agent's primary model."
|
|
828
|
-
},
|
|
829
|
-
storeNudge: {
|
|
830
|
-
type: "object",
|
|
831
|
-
additionalProperties: false,
|
|
832
|
-
description: "Optional mid-session reminder settings for prompting durable memory storage after several turns without memory activity.",
|
|
833
|
-
properties: {
|
|
834
|
-
enabled: {
|
|
835
|
-
type: "boolean",
|
|
836
|
-
description: "Enable or disable mid-session store nudges. Defaults to true."
|
|
837
|
-
},
|
|
838
|
-
threshold: {
|
|
839
|
-
type: "integer",
|
|
840
|
-
minimum: 1,
|
|
841
|
-
description: "Turns without durable memory activity before prompting the agent to review recent conversation. Defaults to 8."
|
|
842
|
-
},
|
|
843
|
-
maxPerSession: {
|
|
844
|
-
type: "integer",
|
|
845
|
-
minimum: 1,
|
|
846
|
-
description: "Maximum nudges injected during one session lifetime. Defaults to 5."
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
},
|
|
850
|
-
debug: {
|
|
851
|
-
type: "object",
|
|
852
|
-
additionalProperties: false,
|
|
853
|
-
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.",
|
|
854
|
-
properties: {
|
|
855
|
-
enabled: {
|
|
856
|
-
type: "boolean",
|
|
857
|
-
description: "Enable or disable the agenr JSONL debug sink. Defaults to false."
|
|
858
|
-
},
|
|
859
|
-
logPath: {
|
|
860
|
-
type: "string",
|
|
861
|
-
minLength: 1,
|
|
862
|
-
description: "Optional explicit log-file path. Defaults to agenr-debug.jsonl inside the OpenClaw state directory."
|
|
863
|
-
},
|
|
864
|
-
eventLevel: {
|
|
865
|
-
type: "string",
|
|
866
|
-
enum: ["basic", "detailed"],
|
|
867
|
-
description: "Event detail level. Detailed enables bounded top-K candidate breakdowns for recall and before-turn events. Defaults to basic."
|
|
868
|
-
},
|
|
869
|
-
perSessionFiles: {
|
|
870
|
-
type: "boolean",
|
|
871
|
-
description: "Split one JSONL file per OpenClaw session id. Defaults to false."
|
|
872
|
-
},
|
|
873
|
-
maxTopCandidates: {
|
|
874
|
-
type: "integer",
|
|
875
|
-
minimum: 1,
|
|
876
|
-
maximum: 25,
|
|
877
|
-
description: "Cap for top-K candidate breakdowns included in detailed events. Defaults to 10."
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
},
|
|
881
|
-
memoryPolicy: {
|
|
882
|
-
type: "object",
|
|
883
|
-
additionalProperties: false,
|
|
884
|
-
description: "Optional runtime overrides for claim-aware read behavior exposed by the OpenClaw adapter.",
|
|
885
|
-
properties: {
|
|
886
|
-
sessionStart: {
|
|
887
|
-
type: "object",
|
|
888
|
-
additionalProperties: false,
|
|
889
|
-
description: "Optional session-start overrides for prompt-time memory injection behavior.",
|
|
890
|
-
properties: {
|
|
891
|
-
enabled: {
|
|
892
|
-
type: "boolean",
|
|
893
|
-
description: "Enable or disable all session-start memory injection. Defaults to true."
|
|
894
|
-
},
|
|
895
|
-
coreMemory: {
|
|
896
|
-
type: "boolean",
|
|
897
|
-
description: "Enable or disable always-on Core Memory injection at session start. Defaults to true."
|
|
898
|
-
},
|
|
899
|
-
relevantDurableMemory: {
|
|
900
|
-
type: "boolean",
|
|
901
|
-
description: "Enable or disable artifact-grounded Relevant Durable Memory injection at session start. Defaults to true."
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
},
|
|
905
|
-
beforeTurn: {
|
|
906
|
-
type: "object",
|
|
907
|
-
additionalProperties: false,
|
|
908
|
-
description: "Optional before-turn overrides for proactive prompt-time memory injection behavior.",
|
|
909
|
-
properties: {
|
|
910
|
-
enabled: {
|
|
911
|
-
type: "boolean",
|
|
912
|
-
description: "Enable or disable the proactive before-turn memory patch. Defaults to true."
|
|
913
|
-
},
|
|
914
|
-
procedureSuggestion: {
|
|
915
|
-
type: "boolean",
|
|
916
|
-
description: "Enable or disable proactive high-confidence procedure suggestion inside the before-turn patch. Defaults to true."
|
|
917
|
-
},
|
|
918
|
-
maxDurableEntries: {
|
|
919
|
-
type: "integer",
|
|
920
|
-
minimum: 1,
|
|
921
|
-
description: "Normal durable-item cap for before-turn recall. Defaults to 1 and only expands when all surfaced items are very high confidence."
|
|
922
|
-
},
|
|
923
|
-
recallThreshold: {
|
|
924
|
-
type: "number",
|
|
925
|
-
minimum: 0,
|
|
926
|
-
maximum: 1,
|
|
927
|
-
description: "Durable-recall score threshold required before an entry can surface during before-turn recall. Defaults to 0.6."
|
|
928
|
-
},
|
|
929
|
-
highConfidenceRecallThreshold: {
|
|
930
|
-
type: "number",
|
|
931
|
-
minimum: 0,
|
|
932
|
-
maximum: 1,
|
|
933
|
-
description: "Durable-recall score threshold required before before-turn recall can expand beyond the normal durable-item cap. Defaults to 0.85."
|
|
934
|
-
},
|
|
935
|
-
procedureThreshold: {
|
|
936
|
-
type: "number",
|
|
937
|
-
minimum: 0,
|
|
938
|
-
maximum: 1,
|
|
939
|
-
description: "Procedure-recall score threshold required before a proactive procedure can surface. Defaults to 0.72."
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
},
|
|
943
|
-
slotPolicies: {
|
|
944
|
-
type: "object",
|
|
945
|
-
additionalProperties: false,
|
|
946
|
-
description: "Claim-slot policy overrides keyed by canonical claim-key attribute head.",
|
|
947
|
-
properties: {
|
|
948
|
-
attributeHeads: {
|
|
949
|
-
type: "object",
|
|
950
|
-
description: "Map canonical attribute heads such as `integration` or `preference` to `exclusive` or `multivalued` read-time slot behavior.",
|
|
951
|
-
propertyNames: {
|
|
952
|
-
pattern: "^[A-Za-z0-9][A-Za-z0-9_-]*$"
|
|
953
|
-
},
|
|
954
|
-
additionalProperties: {
|
|
955
|
-
type: "string",
|
|
956
|
-
enum: ["exclusive", "multivalued"]
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
};
|
|
966
|
-
|
|
967
|
-
// src/adapters/openclaw/config.ts
|
|
968
|
-
var manifest = openclaw_plugin_default;
|
|
969
|
-
var DEFAULT_STORE_NUDGE_THRESHOLD = 8;
|
|
970
|
-
var DEFAULT_STORE_NUDGE_MAX_PER_SESSION = 5;
|
|
971
|
-
var DEFAULT_DEBUG_EVENT_LEVEL = "basic";
|
|
972
|
-
var DEFAULT_DEBUG_PER_SESSION_FILES = false;
|
|
973
|
-
var DEFAULT_DEBUG_MAX_TOP_CANDIDATES = 10;
|
|
974
|
-
var MAX_DEBUG_MAX_TOP_CANDIDATES = 25;
|
|
975
|
-
function normalizeAgenrOpenClawPluginConfig(value) {
|
|
976
|
-
if (value === void 0) {
|
|
977
|
-
return { ok: true, value: {} };
|
|
978
|
-
}
|
|
979
|
-
if (!isRecord(value)) {
|
|
980
|
-
return { ok: false, errors: ["config must be an object"] };
|
|
981
|
-
}
|
|
982
|
-
const errors = [];
|
|
983
|
-
const rawDbPath = value.dbPath;
|
|
984
|
-
const dbPath = typeof rawDbPath === "string" ? rawDbPath.trim() : void 0;
|
|
985
|
-
if (rawDbPath !== void 0 && !dbPath) {
|
|
986
|
-
errors.push("dbPath must be a non-empty string");
|
|
987
|
-
}
|
|
988
|
-
const rawConfigPath = value.configPath;
|
|
989
|
-
const configPath = typeof rawConfigPath === "string" ? rawConfigPath.trim() : void 0;
|
|
990
|
-
if (rawConfigPath !== void 0 && !configPath) {
|
|
991
|
-
errors.push("configPath must be a non-empty string when provided");
|
|
992
|
-
}
|
|
993
|
-
const rawContinuityModel = value.continuityModel;
|
|
994
|
-
const continuityModel = typeof rawContinuityModel === "string" ? rawContinuityModel.trim() : void 0;
|
|
995
|
-
if (rawContinuityModel !== void 0 && !continuityModel) {
|
|
996
|
-
errors.push("continuityModel must be a non-empty string when provided");
|
|
997
|
-
} else if (continuityModel && !continuityModel.includes("/")) {
|
|
998
|
-
errors.push("continuityModel must use provider/model format when provided");
|
|
999
|
-
}
|
|
1000
|
-
const rawEpisodeModel = value.episodeModel;
|
|
1001
|
-
const episodeModel = typeof rawEpisodeModel === "string" ? rawEpisodeModel.trim() : void 0;
|
|
1002
|
-
if (rawEpisodeModel !== void 0 && !episodeModel) {
|
|
1003
|
-
errors.push("episodeModel must be a non-empty string when provided");
|
|
1004
|
-
} else if (episodeModel && !episodeModel.includes("/")) {
|
|
1005
|
-
errors.push("episodeModel must use provider/model format when provided");
|
|
1006
|
-
}
|
|
1007
|
-
const rawClaimExtractionModel = value.claimExtractionModel;
|
|
1008
|
-
const claimExtractionModel = typeof rawClaimExtractionModel === "string" ? rawClaimExtractionModel.trim() : void 0;
|
|
1009
|
-
if (rawClaimExtractionModel !== void 0 && !claimExtractionModel) {
|
|
1010
|
-
errors.push("claimExtractionModel must be a non-empty string when provided");
|
|
1011
|
-
} else if (claimExtractionModel && !claimExtractionModel.includes("/")) {
|
|
1012
|
-
errors.push("claimExtractionModel must use provider/model format when provided");
|
|
1013
|
-
}
|
|
1014
|
-
const storeNudgeResult = normalizeStoreNudgeConfig(value.storeNudge);
|
|
1015
|
-
if (!storeNudgeResult.ok) {
|
|
1016
|
-
errors.push(...storeNudgeResult.errors);
|
|
1017
|
-
}
|
|
1018
|
-
const memoryPolicyResult = normalizePluginInjectionMemoryPolicyConfig(value.memoryPolicy);
|
|
1019
|
-
if (!memoryPolicyResult.ok) {
|
|
1020
|
-
errors.push(...memoryPolicyResult.errors);
|
|
1021
|
-
}
|
|
1022
|
-
const debugResult = normalizeDebugConfig(value.debug);
|
|
1023
|
-
if (!debugResult.ok) {
|
|
1024
|
-
errors.push(...debugResult.errors);
|
|
1025
|
-
}
|
|
1026
|
-
const allowedKeys = /* @__PURE__ */ new Set(["dbPath", "configPath", "continuityModel", "episodeModel", "claimExtractionModel", "storeNudge", "memoryPolicy", "debug"]);
|
|
1027
|
-
for (const key of Object.keys(value)) {
|
|
1028
|
-
if (!allowedKeys.has(key)) {
|
|
1029
|
-
errors.push(`unknown config field: ${key}`);
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
if (errors.length > 0) {
|
|
1033
|
-
return { ok: false, errors };
|
|
1034
|
-
}
|
|
1035
|
-
return {
|
|
1036
|
-
ok: true,
|
|
1037
|
-
value: {
|
|
1038
|
-
...dbPath ? { dbPath } : {},
|
|
1039
|
-
...configPath ? { configPath } : {},
|
|
1040
|
-
...continuityModel ? { continuityModel } : {},
|
|
1041
|
-
...episodeModel ? { episodeModel } : {},
|
|
1042
|
-
...claimExtractionModel ? { claimExtractionModel } : {},
|
|
1043
|
-
...storeNudgeResult.ok && storeNudgeResult.value ? { storeNudge: storeNudgeResult.value } : {},
|
|
1044
|
-
...memoryPolicyResult.ok && memoryPolicyResult.value ? { memoryPolicy: memoryPolicyResult.value } : {},
|
|
1045
|
-
...debugResult.ok && debugResult.value ? { debug: debugResult.value } : {}
|
|
1046
|
-
}
|
|
1047
|
-
};
|
|
1048
|
-
}
|
|
1049
|
-
function coerceAgenrOpenClawPluginConfig(value) {
|
|
1050
|
-
const normalized = normalizeAgenrOpenClawPluginConfig(value);
|
|
1051
|
-
if (normalized.ok) {
|
|
1052
|
-
return normalized.value;
|
|
1053
|
-
}
|
|
1054
|
-
throw new Error(`Invalid agenr OpenClaw plugin config: ${normalized.errors.join("; ")}`);
|
|
1055
|
-
}
|
|
1056
|
-
function createAgenrOpenClawPluginConfigSchema() {
|
|
1057
|
-
return {
|
|
1058
|
-
validate(value) {
|
|
1059
|
-
const parsed = normalizeAgenrOpenClawPluginConfig(value);
|
|
1060
|
-
return parsed.ok ? { ok: true, value: parsed.value } : { ok: false, errors: parsed.errors };
|
|
1061
|
-
},
|
|
1062
|
-
...manifest.uiHints ? { uiHints: manifest.uiHints } : {},
|
|
1063
|
-
jsonSchema: manifest.configSchema
|
|
1064
|
-
};
|
|
1065
|
-
}
|
|
1066
|
-
function isRecord(value) {
|
|
1067
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1068
|
-
}
|
|
1069
|
-
function resolveDebugConfig(value) {
|
|
1070
|
-
const logPath = value?.logPath?.trim();
|
|
1071
|
-
return {
|
|
1072
|
-
enabled: value?.enabled ?? false,
|
|
1073
|
-
...logPath ? { logPath } : {},
|
|
1074
|
-
eventLevel: value?.eventLevel ?? DEFAULT_DEBUG_EVENT_LEVEL,
|
|
1075
|
-
perSessionFiles: value?.perSessionFiles ?? DEFAULT_DEBUG_PER_SESSION_FILES,
|
|
1076
|
-
maxTopCandidates: value?.maxTopCandidates ?? DEFAULT_DEBUG_MAX_TOP_CANDIDATES
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
function normalizeDebugConfig(value) {
|
|
1080
|
-
if (value === void 0) {
|
|
1081
|
-
return { ok: true, value: void 0 };
|
|
1082
|
-
}
|
|
1083
|
-
if (!isRecord(value)) {
|
|
1084
|
-
return { ok: false, errors: ["debug must be an object when provided"] };
|
|
1085
|
-
}
|
|
1086
|
-
const errors = [];
|
|
1087
|
-
const enabled = normalizeOptionalBoolean(value.enabled, "debug.enabled", errors);
|
|
1088
|
-
const logPathRaw = value.logPath;
|
|
1089
|
-
let logPath;
|
|
1090
|
-
if (logPathRaw !== void 0) {
|
|
1091
|
-
if (typeof logPathRaw !== "string" || logPathRaw.trim().length === 0) {
|
|
1092
|
-
errors.push("debug.logPath must be a non-empty string when provided");
|
|
1093
|
-
} else {
|
|
1094
|
-
logPath = logPathRaw.trim();
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
const eventLevel = normalizeOptionalDebugEventLevel(value.eventLevel, errors);
|
|
1098
|
-
const perSessionFiles = normalizeOptionalBoolean(value.perSessionFiles, "debug.perSessionFiles", errors);
|
|
1099
|
-
const maxTopCandidates = normalizeOptionalTopCandidateCap(value.maxTopCandidates, errors);
|
|
1100
|
-
const allowedKeys = /* @__PURE__ */ new Set(["enabled", "logPath", "eventLevel", "perSessionFiles", "maxTopCandidates"]);
|
|
1101
|
-
for (const key of Object.keys(value)) {
|
|
1102
|
-
if (!allowedKeys.has(key)) {
|
|
1103
|
-
errors.push(`unknown config field: debug.${key}`);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
if (errors.length > 0) {
|
|
1107
|
-
return { ok: false, errors };
|
|
1108
|
-
}
|
|
1109
|
-
const normalized = {
|
|
1110
|
-
...enabled !== void 0 ? { enabled } : {},
|
|
1111
|
-
...logPath !== void 0 ? { logPath } : {},
|
|
1112
|
-
...eventLevel !== void 0 ? { eventLevel } : {},
|
|
1113
|
-
...perSessionFiles !== void 0 ? { perSessionFiles } : {},
|
|
1114
|
-
...maxTopCandidates !== void 0 ? { maxTopCandidates } : {}
|
|
1115
|
-
};
|
|
1116
|
-
return {
|
|
1117
|
-
ok: true,
|
|
1118
|
-
value: Object.keys(normalized).length > 0 ? normalized : void 0
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
function normalizeOptionalDebugEventLevel(value, errors) {
|
|
1122
|
-
if (value === void 0) {
|
|
1123
|
-
return void 0;
|
|
1124
|
-
}
|
|
1125
|
-
if (value === "basic" || value === "detailed") {
|
|
1126
|
-
return value;
|
|
1127
|
-
}
|
|
1128
|
-
errors.push('debug.eventLevel must be "basic" or "detailed" when provided');
|
|
1129
|
-
return void 0;
|
|
1130
|
-
}
|
|
1131
|
-
function normalizeOptionalTopCandidateCap(value, errors) {
|
|
1132
|
-
if (value === void 0) {
|
|
1133
|
-
return void 0;
|
|
1134
|
-
}
|
|
1135
|
-
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0 || value > MAX_DEBUG_MAX_TOP_CANDIDATES) {
|
|
1136
|
-
errors.push(`debug.maxTopCandidates must be an integer between 1 and ${MAX_DEBUG_MAX_TOP_CANDIDATES} when provided`);
|
|
1137
|
-
return void 0;
|
|
1138
|
-
}
|
|
1139
|
-
return value;
|
|
1140
|
-
}
|
|
1141
|
-
function resolveStoreNudgeConfig(value) {
|
|
1142
|
-
return {
|
|
1143
|
-
enabled: value?.enabled ?? true,
|
|
1144
|
-
threshold: value?.threshold ?? DEFAULT_STORE_NUDGE_THRESHOLD,
|
|
1145
|
-
maxPerSession: value?.maxPerSession ?? DEFAULT_STORE_NUDGE_MAX_PER_SESSION
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
function normalizeStoreNudgeConfig(value) {
|
|
1149
|
-
if (value === void 0) {
|
|
1150
|
-
return { ok: true, value: void 0 };
|
|
1151
|
-
}
|
|
1152
|
-
if (!isRecord(value)) {
|
|
1153
|
-
return { ok: false, errors: ["storeNudge must be an object when provided"] };
|
|
1154
|
-
}
|
|
1155
|
-
const errors = [];
|
|
1156
|
-
const enabled = normalizeOptionalBoolean(value.enabled, "storeNudge.enabled", errors);
|
|
1157
|
-
const threshold = normalizeOptionalPositiveInteger(value.threshold, "storeNudge.threshold", errors);
|
|
1158
|
-
const maxPerSession = normalizeOptionalPositiveInteger(value.maxPerSession, "storeNudge.maxPerSession", errors);
|
|
1159
|
-
const allowedKeys = /* @__PURE__ */ new Set(["enabled", "threshold", "maxPerSession"]);
|
|
1160
|
-
for (const key of Object.keys(value)) {
|
|
1161
|
-
if (!allowedKeys.has(key)) {
|
|
1162
|
-
errors.push(`unknown config field: storeNudge.${key}`);
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
if (errors.length > 0) {
|
|
1166
|
-
return { ok: false, errors };
|
|
1167
|
-
}
|
|
1168
|
-
const normalizedValue = {
|
|
1169
|
-
...enabled !== void 0 ? { enabled } : {},
|
|
1170
|
-
...threshold !== void 0 ? { threshold } : {},
|
|
1171
|
-
...maxPerSession !== void 0 ? { maxPerSession } : {}
|
|
1172
|
-
};
|
|
1173
|
-
return {
|
|
1174
|
-
ok: true,
|
|
1175
|
-
value: Object.keys(normalizedValue).length > 0 ? resolveStoreNudgeConfig(normalizedValue) : void 0
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// src/adapters/openclaw/format/prompt-section.ts
|
|
1180
|
-
var MEMORY_TOOL_NAMES = {
|
|
1181
|
-
recall: "agenr_recall",
|
|
1182
|
-
fetch: "agenr_fetch",
|
|
1183
|
-
store: "agenr_store",
|
|
1184
|
-
update: "agenr_update",
|
|
1185
|
-
retire: "agenr_retire",
|
|
1186
|
-
trace: "agenr_trace"
|
|
1187
|
-
};
|
|
1188
|
-
var MEMORY_TOOL_NAME_SET = new Set(Object.values(MEMORY_TOOL_NAMES));
|
|
1189
|
-
var ORCHESTRATION_TOOL_PATTERNS = [/subagent/iu, /delegate/iu, /background/iu, /fork/iu, /teammate/iu, /parallel/iu, /spawn/iu];
|
|
1190
|
-
function buildAgenrMemoryPromptSection({
|
|
1191
|
-
availableTools,
|
|
1192
|
-
citationsMode
|
|
1193
|
-
}) {
|
|
1194
|
-
if (!availableTools.has(MEMORY_TOOL_NAMES.recall)) {
|
|
1195
|
-
return [];
|
|
1196
|
-
}
|
|
1197
|
-
const hasOrchestrationTool = [...availableTools].some(
|
|
1198
|
-
(toolName) => !MEMORY_TOOL_NAME_SET.has(toolName) && ORCHESTRATION_TOOL_PATTERNS.some((pattern) => pattern.test(toolName))
|
|
1199
|
-
);
|
|
1200
|
-
const lines = [
|
|
1201
|
-
"## Memory Recall",
|
|
1202
|
-
"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.",
|
|
1203
|
-
"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.",
|
|
1204
|
-
"agenr_recall returns truncated entry previews with ids, scores, and preview_truncated flags.",
|
|
1205
|
-
"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.",
|
|
1206
|
-
"One focused agenr_recall call with the right scope beats several broad ones.",
|
|
1207
|
-
"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.",
|
|
1208
|
-
"Memory authority, strongest to weakest:",
|
|
1209
|
-
"- Durable entries are the canonical record for verified facts, decisions, preferences, and lessons unless live evidence contradicts them.",
|
|
1210
|
-
"- Episode recall explains what happened in completed sessions, but it is a narrative summary, not an exact log.",
|
|
1211
|
-
"- Session handoffs and continuity summaries are approximate restart context and may be incomplete or stale.",
|
|
1212
|
-
"- Live verification beats stored memory whenever you can check quickly.",
|
|
1213
|
-
"The newest completed session may not be consolidated into episodes yet, so very recent work can be missing from episode recall."
|
|
1214
|
-
];
|
|
1215
|
-
if (availableTools.has(MEMORY_TOOL_NAMES.store)) {
|
|
1216
|
-
lines.push(
|
|
1217
|
-
"Use agenr_store for durable memory, not for logging. Apply the future-session test: will a fresh future session make a better decision because this was stored, or are you just recording that something happened?"
|
|
1218
|
-
);
|
|
1219
|
-
lines.push(
|
|
1220
|
-
"If another system already holds the canonical record - such as version control, a task or ticket tracker, a calendar, a signed document, a chat or email thread, or a database/CRM - usually do not store that record. Store only the durable takeaway: the standing rule, implication, lesson, preference, risk, or relationship."
|
|
1221
|
-
);
|
|
1222
|
-
lines.push(
|
|
1223
|
-
"Type guide: fact = durable truth about a person, system, place, or how something works; decision = standing rule, constraint, policy, or chosen approach future sessions should follow; preference = what someone likes, wants, values, or wants avoided; lesson = non-obvious takeaway from experience that should change future behavior; milestone = rare one-time event with durable future significance, not ordinary task completion."
|
|
1224
|
-
);
|
|
1225
|
-
lines.push("Do not use decision as a catch-all for important activity updates.");
|
|
1226
|
-
lines.push(
|
|
1227
|
-
"Usually do not store: 'I merged PR #123.', 'I filed a support ticket.', 'We had a meeting at 3 PM.', 'I sent the contract for signature.', or 'We spent two hours debugging the outage.'"
|
|
1228
|
-
);
|
|
1229
|
-
lines.push(
|
|
1230
|
-
"Do store the durable takeaway instead: 'Always use the structured export path because raw sync corrupts timestamps.' (decision or lesson), 'Jim prefers text-first updates and dislikes surprise calls.' (preference), 'Service restarts fail unless config Y is enabled.' (lesson), 'The office Wi-Fi name is Acorn-5G.' (fact)."
|
|
1231
|
-
);
|
|
1232
|
-
lines.push("Do not store progress snapshots or current-state narration about what is happening right now as durable memory.");
|
|
1233
|
-
lines.push("Do not store plans, checklists, or speculative future state as facts or decisions.");
|
|
1234
|
-
lines.push("Do not re-store recalled entries, episode summaries, continuity text, or conversation summaries as new evidence.");
|
|
1235
|
-
lines.push("Do not store meta narration about the current session.");
|
|
1236
|
-
lines.push(
|
|
1237
|
-
"Use memory lifetimes deliberately: core is injected at every session start and should be rare, permanent is durable recall-on-demand memory, and temporary is short-horizon. Importance is 1 to 10; 7 is normal durable memory and 9 to 10 is rare and critical."
|
|
1238
|
-
);
|
|
1239
|
-
}
|
|
1240
|
-
if (availableTools.has(MEMORY_TOOL_NAMES.update) || availableTools.has(MEMORY_TOOL_NAMES.retire)) {
|
|
1241
|
-
lines.push("When memory is contradicted by live evidence, fix it with agenr_update or agenr_retire instead of silently working around it.");
|
|
1242
|
-
}
|
|
1243
|
-
if (availableTools.has(MEMORY_TOOL_NAMES.fetch)) {
|
|
1244
|
-
lines.push("Call agenr_fetch with id when preview_truncated=true or exact stored wording is required.");
|
|
1245
|
-
}
|
|
1246
|
-
if (availableTools.has(MEMORY_TOOL_NAMES.trace)) {
|
|
1247
|
-
lines.push("Use agenr_trace when provenance, recall history, or supersession matters.");
|
|
1248
|
-
}
|
|
1249
|
-
if (hasOrchestrationTool) {
|
|
1250
|
-
lines.push("Do not summarize or assert results from unfinished background work.");
|
|
1251
|
-
lines.push("Do not fabricate completion status for delegated tasks.");
|
|
1252
|
-
}
|
|
1253
|
-
if (citationsMode === "off") {
|
|
1254
|
-
lines.push("Citations are disabled: do not mention agenr entry IDs in user-facing replies unless the user asks for them.");
|
|
1255
|
-
} else {
|
|
1256
|
-
lines.push("Citations: mention the relevant agenr entry ID only when it helps the user verify a claim.");
|
|
1257
|
-
}
|
|
1258
|
-
lines.push("");
|
|
1259
|
-
return lines;
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
// src/adapters/openclaw/logging.ts
|
|
1263
|
-
function formatSessionContext(sessionId, sessionKey) {
|
|
1264
|
-
const normalizedSessionId = sessionId?.trim();
|
|
1265
|
-
const normalizedSessionKey = sessionKey?.trim();
|
|
1266
|
-
if (normalizedSessionId && normalizedSessionKey) {
|
|
1267
|
-
return `session=${normalizedSessionId} key=${normalizedSessionKey}`;
|
|
1268
|
-
}
|
|
1269
|
-
if (normalizedSessionId) {
|
|
1270
|
-
return `session=${normalizedSessionId}`;
|
|
1271
|
-
}
|
|
1272
|
-
if (normalizedSessionKey) {
|
|
1273
|
-
return `key=${normalizedSessionKey}`;
|
|
1274
|
-
}
|
|
1275
|
-
return "session=unknown";
|
|
1276
|
-
}
|
|
1277
|
-
function formatErrorMessage2(error) {
|
|
1278
|
-
return error instanceof Error ? error.message : String(error);
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
// src/adapters/openclaw/session/state.ts
|
|
1282
|
-
function createMidSessionTracker() {
|
|
1283
|
-
const states = /* @__PURE__ */ new Map();
|
|
1284
|
-
return {
|
|
1285
|
-
getOrCreate(sessionId, sessionKey) {
|
|
1286
|
-
const identityKey = resolveSessionIdentityKey(sessionId, sessionKey);
|
|
1287
|
-
if (!identityKey) {
|
|
1288
|
-
return void 0;
|
|
1289
|
-
}
|
|
1290
|
-
const existingState = states.get(identityKey);
|
|
1291
|
-
if (existingState) {
|
|
1292
|
-
return existingState;
|
|
1293
|
-
}
|
|
1294
|
-
const nextState = createMidSessionState();
|
|
1295
|
-
states.set(identityKey, nextState);
|
|
1296
|
-
return nextState;
|
|
1297
|
-
},
|
|
1298
|
-
peek(sessionId, sessionKey) {
|
|
1299
|
-
const identityKey = resolveSessionIdentityKey(sessionId, sessionKey);
|
|
1300
|
-
return identityKey ? states.get(identityKey) : void 0;
|
|
1301
|
-
},
|
|
1302
|
-
recordTurn(sessionId, sessionKey) {
|
|
1303
|
-
const state = this.getOrCreate(sessionId, sessionKey);
|
|
1304
|
-
if (!state) {
|
|
1305
|
-
return void 0;
|
|
1306
|
-
}
|
|
1307
|
-
state.turnCount += 1;
|
|
1308
|
-
return state;
|
|
1309
|
-
},
|
|
1310
|
-
clear(sessionId, sessionKey) {
|
|
1311
|
-
const identityKey = resolveSessionIdentityKey(sessionId, sessionKey);
|
|
1312
|
-
return identityKey ? states.delete(identityKey) : false;
|
|
1313
|
-
},
|
|
1314
|
-
activeCount() {
|
|
1315
|
-
return states.size;
|
|
1316
|
-
}
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
function pushMidSessionStoredSubject(state, subject) {
|
|
1320
|
-
const normalizedSubject = subject?.trim();
|
|
1321
|
-
if (!normalizedSubject) {
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
const existingIndex = state.storedSubjects.findIndex((value) => value === normalizedSubject);
|
|
1325
|
-
if (existingIndex >= 0) {
|
|
1326
|
-
state.storedSubjects.splice(existingIndex, 1);
|
|
1327
|
-
}
|
|
1328
|
-
state.storedSubjects.push(normalizedSubject);
|
|
1329
|
-
while (state.storedSubjects.length > MAX_STORED_SUBJECTS) {
|
|
1330
|
-
state.storedSubjects.shift();
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
function createMidSessionState() {
|
|
1334
|
-
return {
|
|
1335
|
-
turnCount: 0,
|
|
1336
|
-
lastSuccessfulStoreTurn: 0,
|
|
1337
|
-
lastMemoryActionTurn: 0,
|
|
1338
|
-
lastExplicitMemoryActionTurn: 0,
|
|
1339
|
-
nudgeCount: 0,
|
|
1340
|
-
entriesStored: 0,
|
|
1341
|
-
storedSubjects: []
|
|
1342
|
-
};
|
|
1343
|
-
}
|
|
1344
|
-
var MAX_STORED_SUBJECTS = 5;
|
|
1345
|
-
|
|
1346
|
-
// src/adapters/openclaw/hooks/after-tool-call.ts
|
|
1347
|
-
var STORE_TOOL_NAME = "agenr_store";
|
|
1348
|
-
var UPDATE_TOOL_NAME = "agenr_update";
|
|
1349
|
-
var RETIRE_TOOL_NAME = "agenr_retire";
|
|
1350
|
-
function handleAgenrAfterToolCall(event, ctx, params) {
|
|
1351
|
-
if (event.toolName !== STORE_TOOL_NAME && event.toolName !== UPDATE_TOOL_NAME && event.toolName !== RETIRE_TOOL_NAME) {
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
1354
|
-
const state = params.midSessionTracker.getOrCreate(ctx.sessionId, ctx.sessionKey);
|
|
1355
|
-
if (!state) {
|
|
1356
|
-
params.logger.debug?.(`[agenr] after_tool_call: skipped mid-session tracking without session identity for tool=${event.toolName}`);
|
|
1357
|
-
return;
|
|
1358
|
-
}
|
|
1359
|
-
if (event.toolName === STORE_TOOL_NAME) {
|
|
1360
|
-
handleStoreToolResult(event, ctx, state, params.logger);
|
|
1361
|
-
return;
|
|
1362
|
-
}
|
|
1363
|
-
state.lastMemoryActionTurn = state.turnCount;
|
|
1364
|
-
state.lastExplicitMemoryActionTurn = state.turnCount;
|
|
1365
|
-
params.logger.debug?.(
|
|
1366
|
-
`[agenr] after_tool_call: memory maintenance tool=${event.toolName} turn=${state.turnCount} ${formatSessionContext(ctx.sessionId, ctx.sessionKey)}`
|
|
1367
|
-
);
|
|
1368
|
-
}
|
|
1369
|
-
function handleStoreToolResult(event, ctx, state, logger) {
|
|
1370
|
-
const status = readToolStatus(event.result);
|
|
1371
|
-
const sessionContext = formatSessionContext(ctx.sessionId, ctx.sessionKey);
|
|
1372
|
-
const hasExplicitMemoryParams = hasNonEmptyString(event.params.claimKey) || hasNonEmptyString(event.params.supersedes);
|
|
1373
|
-
if (status === "stored" && !event.error) {
|
|
1374
|
-
state.entriesStored += 1;
|
|
1375
|
-
state.lastSuccessfulStoreTurn = state.turnCount;
|
|
1376
|
-
state.lastMemoryActionTurn = state.turnCount;
|
|
1377
|
-
if (hasExplicitMemoryParams) {
|
|
1378
|
-
state.lastExplicitMemoryActionTurn = state.turnCount;
|
|
1379
|
-
}
|
|
1380
|
-
pushMidSessionStoredSubject(state, readString(event.params.subject));
|
|
1381
|
-
logger.debug?.(`[agenr] after_tool_call: store recorded status=stored turn=${state.turnCount} entriesStored=${state.entriesStored} ${sessionContext}`);
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
|
-
if (status === "skipped" && !event.error) {
|
|
1385
|
-
state.lastMemoryActionTurn = state.turnCount;
|
|
1386
|
-
if (hasExplicitMemoryParams) {
|
|
1387
|
-
state.lastExplicitMemoryActionTurn = state.turnCount;
|
|
1388
|
-
}
|
|
1389
|
-
logger.debug?.(`[agenr] after_tool_call: store recorded status=skipped turn=${state.turnCount} ${sessionContext}`);
|
|
1390
|
-
return;
|
|
1391
|
-
}
|
|
1392
|
-
if (hasExplicitMemoryParams) {
|
|
1393
|
-
state.lastExplicitMemoryActionTurn = state.turnCount;
|
|
1394
|
-
}
|
|
1395
|
-
logger.debug?.(
|
|
1396
|
-
`[agenr] after_tool_call: store ignored for nudge reset status=${status ?? "unknown"} error=${event.error ?? "none"} turn=${state.turnCount} ${sessionContext}`
|
|
1397
|
-
);
|
|
1398
|
-
}
|
|
1399
|
-
function readToolStatus(value) {
|
|
1400
|
-
const record = asRecord2(value);
|
|
1401
|
-
const directStatus = readString(record?.status);
|
|
1402
|
-
if (directStatus) {
|
|
1403
|
-
return directStatus;
|
|
1404
|
-
}
|
|
1405
|
-
return readString(asRecord2(record?.details)?.status);
|
|
1406
|
-
}
|
|
1407
|
-
function asRecord2(value) {
|
|
1408
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1409
|
-
}
|
|
1410
|
-
function readString(value) {
|
|
1411
|
-
if (typeof value !== "string") {
|
|
1412
|
-
return void 0;
|
|
1413
|
-
}
|
|
1414
|
-
const normalizedValue = value.trim();
|
|
1415
|
-
return normalizedValue.length > 0 ? normalizedValue : void 0;
|
|
1416
|
-
}
|
|
1417
|
-
function hasNonEmptyString(value) {
|
|
1418
|
-
return readString(value) !== void 0;
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
// src/adapters/openclaw/hooks/before-prompt-build.ts
|
|
1422
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
1423
|
-
import path5 from "path";
|
|
1424
|
-
|
|
1425
|
-
// src/adapters/openclaw/episode/episode-writer.ts
|
|
1426
|
-
import { resolveAgentEffectiveModelPrimary as resolveAgentEffectiveModelPrimary2, resolveDefaultAgentId as resolveDefaultAgentId2 } from "openclaw/plugin-sdk/agent-runtime";
|
|
1427
|
-
|
|
1428
|
-
// src/adapters/openclaw/llm/openclaw-llm-client.ts
|
|
1429
|
-
import { completeSimple, getModel } from "@earendil-works/pi-ai";
|
|
1430
|
-
|
|
1431
|
-
// src/adapters/openclaw/embedded-agent/task-runner.ts
|
|
1432
|
-
import * as fs from "fs/promises";
|
|
1433
|
-
import os from "os";
|
|
1434
|
-
import path2 from "path";
|
|
1435
|
-
import { DEFAULT_MODEL, DEFAULT_PROVIDER, parseModelRef, resolveAgentEffectiveModelPrimary, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
|
1436
|
-
function resolveOpenClawEmbeddedAgentExecution(params) {
|
|
1437
|
-
const agentId = params.requestedAgentId?.trim() || resolveDefaultAgentId(params.openClaw.config);
|
|
1438
|
-
if (params.modelOverride) {
|
|
1439
|
-
const parsedModelRef2 = parseModelRef(params.modelOverride, DEFAULT_PROVIDER);
|
|
1440
|
-
if (!parsedModelRef2) {
|
|
1441
|
-
throw new Error(`Invalid ${params.invalidOverrideLabel}: ${params.modelOverride}`);
|
|
1442
|
-
}
|
|
1443
|
-
return {
|
|
1444
|
-
agentId,
|
|
1445
|
-
agentDir: params.openClaw.runtime.agent.resolveAgentDir(params.openClaw.config, agentId),
|
|
1446
|
-
workspaceDir: params.openClaw.runtime.agent.resolveAgentWorkspaceDir(params.openClaw.config, agentId),
|
|
1447
|
-
modelRef: params.modelOverride,
|
|
1448
|
-
provider: parsedModelRef2.provider,
|
|
1449
|
-
model: parsedModelRef2.model
|
|
1450
|
-
};
|
|
1451
|
-
}
|
|
1452
|
-
const modelRef = resolveAgentEffectiveModelPrimary(params.openClaw.config, agentId);
|
|
1453
|
-
const parsedModelRef = modelRef ? parseModelRef(modelRef, DEFAULT_PROVIDER) : null;
|
|
1454
|
-
return {
|
|
1455
|
-
agentId,
|
|
1456
|
-
agentDir: params.openClaw.runtime.agent.resolveAgentDir(params.openClaw.config, agentId),
|
|
1457
|
-
workspaceDir: params.openClaw.runtime.agent.resolveAgentWorkspaceDir(params.openClaw.config, agentId),
|
|
1458
|
-
modelRef,
|
|
1459
|
-
provider: parsedModelRef?.provider ?? DEFAULT_PROVIDER,
|
|
1460
|
-
model: parsedModelRef?.model ?? DEFAULT_MODEL
|
|
1461
|
-
};
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
// src/adapters/openclaw/llm/openclaw-llm-client.ts
|
|
1465
|
-
var getModelWithStrings = getModel;
|
|
1466
|
-
async function createOpenClawLlmClient(openClaw, modelRef, label = "model override") {
|
|
1467
|
-
const execution = resolveOpenClawEmbeddedAgentExecution({
|
|
1468
|
-
openClaw,
|
|
1469
|
-
modelOverride: modelRef,
|
|
1470
|
-
invalidOverrideLabel: label
|
|
1471
|
-
});
|
|
1472
|
-
const model = getModelWithStrings(execution.provider, execution.model);
|
|
1473
|
-
const auth = await openClaw.runtime.modelAuth.resolveApiKeyForProvider({
|
|
1474
|
-
provider: execution.provider,
|
|
1475
|
-
cfg: openClaw.config
|
|
1476
|
-
});
|
|
1477
|
-
const apiKey = normalizeOptionalString(auth.apiKey);
|
|
1478
|
-
if (!apiKey) {
|
|
1479
|
-
throw new Error(`OpenClaw auth did not resolve an API-key-compatible credential for ${execution.provider} (source=${auth.source}, mode=${auth.mode}).`);
|
|
1480
|
-
}
|
|
1481
|
-
const complete = async (systemPrompt, userMessage) => {
|
|
1482
|
-
const response = await completeSimple(
|
|
1483
|
-
model,
|
|
1484
|
-
{
|
|
1485
|
-
systemPrompt,
|
|
1486
|
-
messages: [
|
|
1487
|
-
{
|
|
1488
|
-
role: "user",
|
|
1489
|
-
content: userMessage,
|
|
1490
|
-
timestamp: Date.now()
|
|
1491
|
-
}
|
|
1492
|
-
]
|
|
1493
|
-
},
|
|
1494
|
-
{
|
|
1495
|
-
apiKey
|
|
1496
|
-
}
|
|
1497
|
-
);
|
|
1498
|
-
if (response.stopReason === "error") {
|
|
1499
|
-
throw new Error(response.errorMessage ?? `LLM completion failed for ${execution.provider}/${execution.model}.`);
|
|
1500
|
-
}
|
|
1501
|
-
return extractText(response);
|
|
1502
|
-
};
|
|
1503
|
-
return {
|
|
1504
|
-
complete,
|
|
1505
|
-
completeJson: async (systemPrompt, userMessage) => {
|
|
1506
|
-
const text = await complete(systemPrompt, userMessage);
|
|
1507
|
-
return JSON.parse(stripCodeFence(text));
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
}
|
|
1511
|
-
function extractText(response) {
|
|
1512
|
-
if (!response.content) {
|
|
1513
|
-
return "";
|
|
1514
|
-
}
|
|
1515
|
-
return response.content.filter((block) => block.type === "text" && block.text).map((block) => block.text ?? "").join("");
|
|
1516
|
-
}
|
|
1517
|
-
function stripCodeFence(text) {
|
|
1518
|
-
const trimmed = text.trim();
|
|
1519
|
-
const match = /^```(?:json)?\s*([\s\S]+?)\s*```$/iu.exec(trimmed);
|
|
1520
|
-
return match?.[1]?.trim() ?? trimmed;
|
|
1521
|
-
}
|
|
1522
|
-
function normalizeOptionalString(value) {
|
|
1523
|
-
const normalized = value?.trim();
|
|
1524
|
-
return normalized ? normalized : void 0;
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
// src/adapters/openclaw/episode/episode-summary-prompt.ts
|
|
1528
|
-
var OPENCLAW_EPISODE_GENERATOR_VERSION = "openclaw-episodic-summary-v1";
|
|
1529
|
-
|
|
1530
|
-
// src/adapters/openclaw/episode/episode-writer.ts
|
|
1531
|
-
async function writeOpenClawPredecessorEpisode(params) {
|
|
1532
|
-
const sessionContext = formatSessionContext(params.ctx.sessionId, params.ctx.sessionKey);
|
|
1533
|
-
const writeStartedAtMs = Date.now();
|
|
1534
|
-
if (!params.predecessor) {
|
|
1535
|
-
params.logger.info(`[agenr] session-start predecessor episode write skipped for ${sessionContext} reason=no_predecessor`);
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1538
|
-
const predecessor = params.predecessor;
|
|
1539
|
-
params.logger.info(`[agenr] session-start predecessor episode write triggered for ${sessionContext} predecessor=${predecessor.sessionFile}`);
|
|
1540
|
-
const episodeModelRef = resolveOpenClawEpisodeModelRef(params.services.openClaw, params.ctx.agentId, params.services.pluginConfig.episodeModel);
|
|
1541
|
-
const episodeModel = episodeModelRef ?? "default";
|
|
1542
|
-
const summaryDeadlineMs = writeStartedAtMs + EPISODE_SUMMARY_TIMEOUT_MS;
|
|
1543
|
-
const llm = await createOpenClawLlmClient(params.services.openClaw, episodeModelRef, "episode model override");
|
|
1544
|
-
const summaryLlm = createDeadlineAwareEpisodeSummaryLlm(
|
|
1545
|
-
{
|
|
1546
|
-
complete: llm.complete.bind(llm),
|
|
1547
|
-
completeJson: llm.completeJson.bind(llm),
|
|
1548
|
-
metadata: {
|
|
1549
|
-
modelRef: episodeModel,
|
|
1550
|
-
pricing: {
|
|
1551
|
-
input: 0,
|
|
1552
|
-
output: 0,
|
|
1553
|
-
cacheRead: 0,
|
|
1554
|
-
cacheWrite: 0
|
|
1555
|
-
},
|
|
1556
|
-
usage: {
|
|
1557
|
-
calls: 0,
|
|
1558
|
-
inputTokens: 0,
|
|
1559
|
-
outputTokens: 0,
|
|
1560
|
-
cacheReadTokens: 0,
|
|
1561
|
-
cacheWriteTokens: 0,
|
|
1562
|
-
totalTokens: 0,
|
|
1563
|
-
totalCost: 0
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
},
|
|
1567
|
-
summaryDeadlineMs
|
|
1568
|
-
);
|
|
1569
|
-
await writeBoundedSingleTranscriptEpisode({
|
|
1570
|
-
filePath: predecessor.sessionFile,
|
|
1571
|
-
context: sessionContext,
|
|
1572
|
-
actionLabel: "session-start predecessor episode write",
|
|
1573
|
-
logger: params.logger,
|
|
1574
|
-
summaryDeadlineMs,
|
|
1575
|
-
fileField: "predecessor",
|
|
1576
|
-
shortCountField: "cleanedMessages",
|
|
1577
|
-
failureModelRef: episodeModel,
|
|
1578
|
-
unexpectedFailureLevel: "info",
|
|
1579
|
-
ports: {
|
|
1580
|
-
files: createSingleTranscriptDiscoveryPort(predecessor.sessionFile),
|
|
1581
|
-
transcript: openClawTranscriptParser,
|
|
1582
|
-
episodes: params.services.episodes,
|
|
1583
|
-
createSummaryLlm: () => summaryLlm,
|
|
1584
|
-
embedSummary: (summary) => embedEpisodeSummaryWithinBudget({
|
|
1585
|
-
summary,
|
|
1586
|
-
embedding: params.services.embedding,
|
|
1587
|
-
embeddingAvailable: params.services.embeddingStatus.available,
|
|
1588
|
-
deadlineMs: summaryDeadlineMs,
|
|
1589
|
-
logger: params.logger,
|
|
1590
|
-
logContext: `[agenr] session-start predecessor episode embedding skipped for ${sessionContext} predecessor=${predecessor.sessionFile}`
|
|
1591
|
-
})
|
|
1592
|
-
},
|
|
1593
|
-
ingestOptions: {
|
|
1594
|
-
genVersion: OPENCLAW_EPISODE_GENERATOR_VERSION,
|
|
1595
|
-
skipActiveSessionCheck: true,
|
|
1596
|
-
candidateOverrides: {
|
|
1597
|
-
sessionId: predecessor.sessionId,
|
|
1598
|
-
agentId: trimOptionalString(params.ctx.agentId) ?? null,
|
|
1599
|
-
surface: resolveSessionSurface(params.ctx) ?? null,
|
|
1600
|
-
metadataSource: "registry"
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
});
|
|
1604
|
-
}
|
|
1605
|
-
function resolveSessionSurface(ctx) {
|
|
1606
|
-
const sessionKey = ctx.sessionKey?.trim() ?? "";
|
|
1607
|
-
if (/^agent:[^:]+:tui/i.test(sessionKey)) {
|
|
1608
|
-
return "tui";
|
|
1609
|
-
}
|
|
1610
|
-
const provider = ctx.messageProvider?.trim();
|
|
1611
|
-
if (provider) {
|
|
1612
|
-
return provider.toLowerCase();
|
|
1613
|
-
}
|
|
1614
|
-
return void 0;
|
|
1615
|
-
}
|
|
1616
|
-
function trimOptionalString(value) {
|
|
1617
|
-
const trimmed = value?.trim();
|
|
1618
|
-
return trimmed ? trimmed : void 0;
|
|
1619
|
-
}
|
|
1620
|
-
function resolveOpenClawEpisodeModelRef(openClaw, agentId, modelOverride) {
|
|
1621
|
-
const requestedOverride = trimOptionalString(modelOverride);
|
|
1622
|
-
if (requestedOverride) {
|
|
1623
|
-
return requestedOverride;
|
|
1624
|
-
}
|
|
1625
|
-
const resolvedAgentId = trimOptionalString(agentId) ?? resolveDefaultAgentId2(openClaw.config);
|
|
1626
|
-
return resolveAgentEffectiveModelPrimary2(openClaw.config, resolvedAgentId) ?? void 0;
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
// src/adapters/openclaw/format/nudge-format.ts
|
|
1630
|
-
var MAX_DISPLAY_SUBJECTS = 3;
|
|
1631
|
-
var MAX_SUBJECT_LENGTH = 48;
|
|
1632
|
-
function buildStoreNudgeMessage(state, maxPerSession) {
|
|
1633
|
-
const isFirstNudge = state.nudgeCount === 1;
|
|
1634
|
-
const isFinalNudge = state.nudgeCount >= maxPerSession;
|
|
1635
|
-
const hasStoredEntries = state.entriesStored > 0;
|
|
1636
|
-
const subjectsLabel = formatStoredSubjects(state.storedSubjects) ?? "recent durable session memory";
|
|
1637
|
-
if (hasStoredEntries) {
|
|
1638
|
-
if (isFinalNudge) {
|
|
1639
|
-
return "[MEMORY CHECK] Session may end soon. Capture final durable takeaways: validated preferences, confirmed lessons, important decisions, open risks. Skip transient progress and derivable project state.";
|
|
1640
|
-
}
|
|
1641
|
-
if (isFirstNudge) {
|
|
1642
|
-
return `[MEMORY CHECK] You've stored ${state.entriesStored} ${state.entriesStored === 1 ? "entry" : "entries"} this session covering ${subjectsLabel}. Review recent conversation for anything else worth keeping - decisions, confirmed approaches, preferences, lessons, or open risks. Store both corrections and validated wins. Skip transient progress and derivable repo facts.`;
|
|
1643
|
-
}
|
|
1644
|
-
return `[MEMORY CHECK] Session memory covers ${subjectsLabel}. Check for newer durable knowledge - especially non-obvious approaches the user confirmed worked well. Use memory for future-session knowledge, not current task state.`;
|
|
1645
|
-
}
|
|
1646
|
-
if (isFinalNudge) {
|
|
1647
|
-
return "[MEMORY CHECK] Session may end soon and nothing has been stored. Capture durable takeaways a future session would need: validated preferences, confirmed lessons, important decisions, durable facts, and open risks. Skip transient progress and derivable project state.";
|
|
1648
|
-
}
|
|
1649
|
-
if (isFirstNudge) {
|
|
1650
|
-
return "[MEMORY CHECK] You haven't stored anything this session. Review recent conversation for future-session knowledge: decisions, preferences, lessons (including confirmed successes, not just corrections), durable facts, and open risks. Skip transient progress, derivable repo facts, and conversation summaries.";
|
|
1651
|
-
}
|
|
1652
|
-
return "[MEMORY CHECK] Still no stores this session. If anything here matters after session reset, store it now. Record both what went wrong and what worked well. For lessons and preferences, capture the rule, why it matters, and when it applies.";
|
|
1653
|
-
}
|
|
1654
|
-
function formatStoredSubjects(subjects) {
|
|
1655
|
-
if (subjects.length === 0) {
|
|
1656
|
-
return void 0;
|
|
1657
|
-
}
|
|
1658
|
-
const displaySubjects = subjects.slice(-MAX_DISPLAY_SUBJECTS).map((subject) => `"${truncateSubject(subject)}"`);
|
|
1659
|
-
const overflowCount = subjects.length - displaySubjects.length;
|
|
1660
|
-
if (overflowCount > 0) {
|
|
1661
|
-
displaySubjects.push(`and ${overflowCount} more`);
|
|
1662
|
-
}
|
|
1663
|
-
return displaySubjects.join(", ");
|
|
1664
|
-
}
|
|
1665
|
-
function truncateSubject(subject) {
|
|
1666
|
-
const trimmedSubject = subject.trim();
|
|
1667
|
-
if (trimmedSubject.length <= MAX_SUBJECT_LENGTH) {
|
|
1668
|
-
return trimmedSubject;
|
|
1669
|
-
}
|
|
1670
|
-
return `${trimmedSubject.slice(0, MAX_SUBJECT_LENGTH - 3).trimEnd()}...`;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
// src/adapters/openclaw/session/continuity/continuity-summary-generator.ts
|
|
1674
|
-
import * as fs3 from "fs/promises";
|
|
1675
|
-
import { resolveAgentEffectiveModelPrimary as resolveAgentEffectiveModelPrimary3, resolveDefaultAgentId as resolveDefaultAgentId3 } from "openclaw/plugin-sdk/agent-runtime";
|
|
1676
|
-
|
|
1677
|
-
// src/adapters/openclaw/session/continuity/continuity-summary-reader.ts
|
|
1678
|
-
import * as fs2 from "fs/promises";
|
|
1679
|
-
import path3 from "path";
|
|
1680
|
-
function resolveOpenClawContinuitySummaryPath(sessionFile, logger) {
|
|
1681
|
-
const normalizedSessionFile = sessionFile.trim();
|
|
1682
|
-
const sessionId = deriveOpenClawSessionIdFromFilePath(normalizedSessionFile, logger);
|
|
1683
|
-
if (!sessionId) {
|
|
1684
|
-
return void 0;
|
|
1685
|
-
}
|
|
1686
|
-
const continuitySummaryPath = path3.join(path3.dirname(normalizedSessionFile), `${sessionId}.continuity-summary.md`);
|
|
1687
|
-
debugLog(logger, "continuity-summary-reader", `resolved continuity summary path for session=${sessionId}: ${continuitySummaryPath}`);
|
|
1688
|
-
return continuitySummaryPath;
|
|
1689
|
-
}
|
|
1690
|
-
async function readOpenClawContinuitySummaryFile(sessionFile, logger) {
|
|
1691
|
-
const continuitySummaryPath = resolveOpenClawContinuitySummaryPath(sessionFile, logger);
|
|
1692
|
-
const sessionId = deriveOpenClawSessionIdFromFilePath(sessionFile, logger);
|
|
1693
|
-
if (!continuitySummaryPath || !sessionId) {
|
|
1694
|
-
return null;
|
|
1695
|
-
}
|
|
1696
|
-
try {
|
|
1697
|
-
const continuitySummaryContent = (await fs2.readFile(continuitySummaryPath, "utf8")).trim();
|
|
1698
|
-
if (continuitySummaryContent.length === 0) {
|
|
1699
|
-
debugLog(logger, "continuity-summary-reader", `continuity summary file is empty for session=${sessionId} path=${continuitySummaryPath}`);
|
|
1700
|
-
return null;
|
|
1701
|
-
}
|
|
1702
|
-
debugLog(
|
|
1703
|
-
logger,
|
|
1704
|
-
"continuity-summary-reader",
|
|
1705
|
-
`loaded continuity summary file for session=${sessionId} path=${continuitySummaryPath} chars=${continuitySummaryContent.length}`
|
|
1706
|
-
);
|
|
1707
|
-
return {
|
|
1708
|
-
sessionId,
|
|
1709
|
-
continuitySummaryPath,
|
|
1710
|
-
content: continuitySummaryContent
|
|
1711
|
-
};
|
|
1712
|
-
} catch (error) {
|
|
1713
|
-
if (isFileNotFound(error)) {
|
|
1714
|
-
debugLog(logger, "continuity-summary-reader", `continuity summary file missing for session=${sessionId} path=${continuitySummaryPath}`);
|
|
1715
|
-
return null;
|
|
1716
|
-
}
|
|
1717
|
-
throw error;
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
function debugLog(logger, subsystem, message) {
|
|
1721
|
-
logger?.debug?.(`[agenr] ${subsystem}: ${message}`);
|
|
1722
|
-
}
|
|
1723
|
-
function isFileNotFound(error) {
|
|
1724
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
// src/adapters/openclaw/session/continuity/continuity-summary-generator.ts
|
|
1728
|
-
var MIN_CONTINUITY_SUMMARY_MESSAGES = 4;
|
|
1729
|
-
var MAX_CONTINUITY_TRANSCRIPT_CHARS = 14e3;
|
|
1730
|
-
var CONTINUITY_SUMMARY_TIMEOUT_MS = 3e4;
|
|
1731
|
-
var CONTINUITY_SUMMARY_SYSTEM_PROMPT = [
|
|
1732
|
-
"You write concise narrative continuity summaries that help the next session continue smoothly.",
|
|
1733
|
-
"The transcript can be about any domain. Do not assume technical, project, or coding context unless the transcript shows it.",
|
|
1734
|
-
"Write 200 to 500 words in plain Markdown with no code fences.",
|
|
1735
|
-
"Capture:",
|
|
1736
|
-
"- what topics were discussed",
|
|
1737
|
-
"- what was learned, decided, agreed on, or corrected",
|
|
1738
|
-
"- what remains unfinished, open, or pending",
|
|
1739
|
-
"- user preferences, clarifications, and constraints that matter for continuity",
|
|
1740
|
-
"- the overall direction or intent of the conversation",
|
|
1741
|
-
"Do not replay the transcript turn by turn. Do not invent facts. If something is uncertain, say so briefly."
|
|
1742
|
-
].join("\n");
|
|
1743
|
-
var ContinuitySummaryTimeoutError = class extends Error {
|
|
1744
|
-
/**
|
|
1745
|
-
* Creates a timeout error with a stable name for caller-side handling.
|
|
1746
|
-
*/
|
|
1747
|
-
constructor() {
|
|
1748
|
-
super("Continuity summary generation timed out.");
|
|
1749
|
-
this.name = "ContinuitySummaryTimeoutError";
|
|
1750
|
-
}
|
|
1751
|
-
};
|
|
1752
|
-
async function generateAndWriteOpenClawContinuitySummary(params) {
|
|
1753
|
-
const sessionFile = params.sessionFile.trim();
|
|
1754
|
-
const continuitySummaryPath = resolveOpenClawContinuitySummaryPath(sessionFile, params.logger);
|
|
1755
|
-
if (!continuitySummaryPath) {
|
|
1756
|
-
return {
|
|
1757
|
-
status: "skipped",
|
|
1758
|
-
reason: "missing_session_id"
|
|
1759
|
-
};
|
|
1760
|
-
}
|
|
1761
|
-
const parsedTranscript = await openClawTranscriptParser.parseFile(sessionFile);
|
|
1762
|
-
const cleanedMessages = parsedTranscript.messages.filter((message) => message.text.trim().length > 0);
|
|
1763
|
-
const transcript = renderTranscriptForContinuitySummary(cleanedMessages);
|
|
1764
|
-
const normalizedTranscript = capContinuityTranscript(transcript, MAX_CONTINUITY_TRANSCRIPT_CHARS);
|
|
1765
|
-
debugLog2(
|
|
1766
|
-
params.logger,
|
|
1767
|
-
"continuity-summary",
|
|
1768
|
-
`transcript adapter output for file=${sessionFile}: messages=${cleanedMessages.length} chars=${normalizedTranscript.length}`
|
|
1769
|
-
);
|
|
1770
|
-
if (cleanedMessages.length === 0 || normalizedTranscript.length === 0) {
|
|
1771
|
-
return {
|
|
1772
|
-
status: "skipped",
|
|
1773
|
-
reason: "empty",
|
|
1774
|
-
continuitySummaryPath,
|
|
1775
|
-
messageCount: cleanedMessages.length,
|
|
1776
|
-
transcriptChars: normalizedTranscript.length
|
|
1777
|
-
};
|
|
1778
|
-
}
|
|
1779
|
-
if (cleanedMessages.length < MIN_CONTINUITY_SUMMARY_MESSAGES) {
|
|
1780
|
-
return {
|
|
1781
|
-
status: "skipped",
|
|
1782
|
-
reason: "too_short",
|
|
1783
|
-
continuitySummaryPath,
|
|
1784
|
-
messageCount: cleanedMessages.length,
|
|
1785
|
-
transcriptChars: normalizedTranscript.length
|
|
1786
|
-
};
|
|
1787
|
-
}
|
|
1788
|
-
const continuityModelRef = resolveOpenClawSummaryModelRef(params.openClaw, params.agentId, params.pluginConfig?.continuityModel);
|
|
1789
|
-
const continuityModel = continuityModelRef ?? "default";
|
|
1790
|
-
const prompt = [
|
|
1791
|
-
"Produce a concise continuity summary for the next session.",
|
|
1792
|
-
"Prefer short paragraphs. Use a short 'Open loops' section only if it adds clarity.",
|
|
1793
|
-
"",
|
|
1794
|
-
"Transcript:",
|
|
1795
|
-
normalizedTranscript
|
|
1796
|
-
].join("\n");
|
|
1797
|
-
debugLog2(
|
|
1798
|
-
params.logger,
|
|
1799
|
-
"continuity-summary",
|
|
1800
|
-
`sending continuity summary prompt model=${continuityModel} promptChars=${prompt.length} transcriptChars=${normalizedTranscript.length}`
|
|
1801
|
-
);
|
|
1802
|
-
params.logger.info(`[agenr] continuity-summary: using OpenClaw LLM client model=${continuityModel}`);
|
|
1803
|
-
const startedAt = Date.now();
|
|
1804
|
-
try {
|
|
1805
|
-
const llm = await createOpenClawLlmClient(params.openClaw, continuityModelRef, "continuity model override");
|
|
1806
|
-
const rawSummary = await Promise.race([
|
|
1807
|
-
llm.complete(CONTINUITY_SUMMARY_SYSTEM_PROMPT, prompt),
|
|
1808
|
-
new Promise((_, reject) => {
|
|
1809
|
-
setTimeout(() => reject(new ContinuitySummaryTimeoutError()), CONTINUITY_SUMMARY_TIMEOUT_MS);
|
|
1810
|
-
})
|
|
1811
|
-
]);
|
|
1812
|
-
const durationMs = Date.now() - startedAt;
|
|
1813
|
-
const normalizedContinuitySummary = normalizeContinuitySummary(rawSummary);
|
|
1814
|
-
debugLog2(
|
|
1815
|
-
params.logger,
|
|
1816
|
-
"continuity-summary",
|
|
1817
|
-
`received continuity summary response model=${continuityModel} durationMs=${durationMs} chars=${normalizedContinuitySummary.length}`
|
|
1818
|
-
);
|
|
1819
|
-
if (normalizedContinuitySummary.length === 0) {
|
|
1820
|
-
return {
|
|
1821
|
-
status: "failed",
|
|
1822
|
-
reason: "empty_response",
|
|
1823
|
-
continuitySummaryPath,
|
|
1824
|
-
messageCount: cleanedMessages.length,
|
|
1825
|
-
transcriptChars: normalizedTranscript.length,
|
|
1826
|
-
model: continuityModel,
|
|
1827
|
-
durationMs
|
|
1828
|
-
};
|
|
1829
|
-
}
|
|
1830
|
-
const existingContinuitySummary = await readOpenClawContinuitySummaryFile(sessionFile, params.logger);
|
|
1831
|
-
if (existingContinuitySummary?.continuitySummaryPath === continuitySummaryPath) {
|
|
1832
|
-
debugLog2(
|
|
1833
|
-
params.logger,
|
|
1834
|
-
"continuity-summary",
|
|
1835
|
-
`continuity summary file already exists at write time path=${continuitySummaryPath} chars=${existingContinuitySummary.content.length}`
|
|
1836
|
-
);
|
|
1837
|
-
return {
|
|
1838
|
-
status: "skipped",
|
|
1839
|
-
reason: "already_exists",
|
|
1840
|
-
continuitySummaryPath,
|
|
1841
|
-
content: existingContinuitySummary.content,
|
|
1842
|
-
messageCount: cleanedMessages.length,
|
|
1843
|
-
transcriptChars: normalizedTranscript.length,
|
|
1844
|
-
model: continuityModel,
|
|
1845
|
-
durationMs
|
|
1846
|
-
};
|
|
1847
|
-
}
|
|
1848
|
-
const continuitySummaryBytes = Buffer.byteLength(`${normalizedContinuitySummary}
|
|
1849
|
-
`, "utf8");
|
|
1850
|
-
await fs3.writeFile(continuitySummaryPath, `${normalizedContinuitySummary}
|
|
1851
|
-
`, "utf8");
|
|
1852
|
-
debugLog2(
|
|
1853
|
-
params.logger,
|
|
1854
|
-
"continuity-summary",
|
|
1855
|
-
`wrote continuity summary file path=${continuitySummaryPath} chars=${normalizedContinuitySummary.length} bytes=${continuitySummaryBytes}`
|
|
1856
|
-
);
|
|
1857
|
-
return {
|
|
1858
|
-
status: "written",
|
|
1859
|
-
continuitySummaryPath,
|
|
1860
|
-
content: normalizedContinuitySummary,
|
|
1861
|
-
messageCount: cleanedMessages.length,
|
|
1862
|
-
transcriptChars: normalizedTranscript.length,
|
|
1863
|
-
model: continuityModel,
|
|
1864
|
-
durationMs,
|
|
1865
|
-
bytesWritten: continuitySummaryBytes
|
|
1866
|
-
};
|
|
1867
|
-
} catch (error) {
|
|
1868
|
-
const durationMs = Date.now() - startedAt;
|
|
1869
|
-
if (error instanceof ContinuitySummaryTimeoutError) {
|
|
1870
|
-
return {
|
|
1871
|
-
status: "failed",
|
|
1872
|
-
reason: "timeout",
|
|
1873
|
-
continuitySummaryPath,
|
|
1874
|
-
messageCount: cleanedMessages.length,
|
|
1875
|
-
transcriptChars: normalizedTranscript.length,
|
|
1876
|
-
model: continuityModel,
|
|
1877
|
-
durationMs
|
|
1878
|
-
};
|
|
1879
|
-
}
|
|
1880
|
-
debugLog2(params.logger, "continuity-summary", `continuity summary generation error for file=${sessionFile}: ${formatErrorMessage3(error)}`);
|
|
1881
|
-
return {
|
|
1882
|
-
status: "failed",
|
|
1883
|
-
reason: formatErrorMessage3(error),
|
|
1884
|
-
continuitySummaryPath,
|
|
1885
|
-
messageCount: cleanedMessages.length,
|
|
1886
|
-
transcriptChars: normalizedTranscript.length,
|
|
1887
|
-
model: continuityModel,
|
|
1888
|
-
durationMs
|
|
1889
|
-
};
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
function renderTranscriptForContinuitySummary(messages) {
|
|
1893
|
-
return messages.map((message) => `${message.role === "user" ? "User" : "Assistant"}: ${message.text.trim()}`).join("\n");
|
|
1894
|
-
}
|
|
1895
|
-
function debugLog2(logger, subsystem, message) {
|
|
1896
|
-
logger.debug?.(`[agenr] ${subsystem}: ${message}`);
|
|
1897
|
-
}
|
|
1898
|
-
function normalizeContinuitySummary(value) {
|
|
1899
|
-
const trimmed = value.trim();
|
|
1900
|
-
return trimmed.replace(/^# .+\n+/u, "").trim();
|
|
1901
|
-
}
|
|
1902
|
-
function formatErrorMessage3(error) {
|
|
1903
|
-
return error instanceof Error ? error.message : String(error);
|
|
1904
|
-
}
|
|
1905
|
-
function resolveOpenClawSummaryModelRef(openClaw, agentId, modelOverride) {
|
|
1906
|
-
const requestedOverride = trimOptionalString2(modelOverride);
|
|
1907
|
-
if (requestedOverride) {
|
|
1908
|
-
return requestedOverride;
|
|
1909
|
-
}
|
|
1910
|
-
const resolvedAgentId = trimOptionalString2(agentId) ?? resolveDefaultAgentId3(openClaw.config);
|
|
1911
|
-
return resolveAgentEffectiveModelPrimary3(openClaw.config, resolvedAgentId) ?? void 0;
|
|
1912
|
-
}
|
|
1913
|
-
function trimOptionalString2(value) {
|
|
1914
|
-
const trimmed = value?.trim();
|
|
1915
|
-
return trimmed ? trimmed : void 0;
|
|
1916
|
-
}
|
|
1917
|
-
function capContinuityTranscript(transcript, maxChars) {
|
|
1918
|
-
if (transcript.length <= maxChars) {
|
|
1919
|
-
return transcript;
|
|
1920
|
-
}
|
|
1921
|
-
const omissionMarker = "\n\n[Earlier middle transcript omitted for brevity]\n\n";
|
|
1922
|
-
const headBudget = Math.max(0, Math.floor((maxChars - omissionMarker.length) * 0.35));
|
|
1923
|
-
const tailBudget = Math.max(0, maxChars - omissionMarker.length - headBudget);
|
|
1924
|
-
const head = trimToBoundary(transcript.slice(0, headBudget), false);
|
|
1925
|
-
const tail = trimToBoundary(transcript.slice(-tailBudget), true);
|
|
1926
|
-
return `${head}${omissionMarker}${tail}`.trim();
|
|
1927
|
-
}
|
|
1928
|
-
function trimToBoundary(value, fromStart) {
|
|
1929
|
-
if (value.length === 0) {
|
|
1930
|
-
return value;
|
|
1931
|
-
}
|
|
1932
|
-
if (fromStart) {
|
|
1933
|
-
const boundary = value.search(/\s/);
|
|
1934
|
-
return boundary >= 0 ? value.slice(boundary).trimStart() : value.trim();
|
|
1935
|
-
}
|
|
1936
|
-
const reversedBoundary = value.trimEnd().search(/\s\S*$/u);
|
|
1937
|
-
return reversedBoundary >= 0 ? value.slice(0, reversedBoundary).trimEnd() : value.trim();
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
|
-
// src/adapters/openclaw/session/continuity/predecessor-resolver.ts
|
|
1941
|
-
import * as fs4 from "fs/promises";
|
|
1942
|
-
import path4 from "path";
|
|
1943
|
-
|
|
1944
|
-
// src/adapters/openclaw/session/session-key-parser.ts
|
|
1945
|
-
var AGENT_SESSION_KEY_PATTERN = /^agent:([^:]+):(.+)$/i;
|
|
1946
|
-
var TOPIC_SUFFIX_TOKENS = 2;
|
|
1947
|
-
var THREAD_SUFFIX_TOKENS = 2;
|
|
1948
|
-
var ACCOUNT_SCOPED_DIRECT_MIN_TOKENS = 4;
|
|
1949
|
-
var CHANNEL_SCOPED_MIN_TOKENS = 3;
|
|
1950
|
-
function parseOpenClawSessionContinuityKey(sessionKey, params) {
|
|
1951
|
-
const normalizedSessionKey = sessionKey.trim();
|
|
1952
|
-
const normalizedMainKey = normalizeMainKey(params?.mainKey);
|
|
1953
|
-
if (normalizedSessionKey.length === 0) {
|
|
1954
|
-
return buildUnknownIdentity();
|
|
1955
|
-
}
|
|
1956
|
-
const tuiIdentity = parseTuiSessionKey(normalizedSessionKey);
|
|
1957
|
-
if (tuiIdentity) {
|
|
1958
|
-
return {
|
|
1959
|
-
agentId: tuiIdentity.agentId,
|
|
1960
|
-
kind: "tui",
|
|
1961
|
-
stableLane: tuiIdentity.stableLane,
|
|
1962
|
-
instanceLane: tuiIdentity.instanceLane,
|
|
1963
|
-
eligible: true
|
|
1964
|
-
};
|
|
1965
|
-
}
|
|
1966
|
-
const agentMatch = AGENT_SESSION_KEY_PATTERN.exec(normalizedSessionKey);
|
|
1967
|
-
if (!agentMatch) {
|
|
1968
|
-
return buildUnknownIdentity();
|
|
1969
|
-
}
|
|
1970
|
-
const [, rawAgentId, rawLane] = agentMatch;
|
|
1971
|
-
const agentId = rawAgentId?.trim() ?? "";
|
|
1972
|
-
const lane = rawLane?.trim() ?? "";
|
|
1973
|
-
if (!agentId || !lane) {
|
|
1974
|
-
return buildUnknownIdentity();
|
|
1975
|
-
}
|
|
1976
|
-
if (lane === normalizedMainKey) {
|
|
1977
|
-
return {
|
|
1978
|
-
agentId,
|
|
1979
|
-
kind: "main",
|
|
1980
|
-
stableLane: normalizedMainKey,
|
|
1981
|
-
instanceLane: normalizedMainKey,
|
|
1982
|
-
eligible: true
|
|
1983
|
-
};
|
|
1984
|
-
}
|
|
1985
|
-
const tokens = lane.split(":").map((token) => token.trim()).filter((token) => token.length > 0);
|
|
1986
|
-
if (tokens.length === 0) {
|
|
1987
|
-
return buildUnknownIdentity(agentId);
|
|
1988
|
-
}
|
|
1989
|
-
if (tokens[0] === "subagent" && tokens.length >= 2) {
|
|
1990
|
-
return buildIneligibleIdentity(agentId, "subagent", lane);
|
|
1991
|
-
}
|
|
1992
|
-
if (tokens[0] === "acp" && tokens.length >= 2) {
|
|
1993
|
-
return buildIneligibleIdentity(agentId, "acp", lane);
|
|
1994
|
-
}
|
|
1995
|
-
if (tokens[0] === "direct" && tokens.length >= 2) {
|
|
1996
|
-
return buildEligibleIdentity(agentId, "direct", lane);
|
|
1997
|
-
}
|
|
1998
|
-
if (tokens.length >= ACCOUNT_SCOPED_DIRECT_MIN_TOKENS && tokens[2] === "direct") {
|
|
1999
|
-
return buildEligibleIdentity(agentId, "direct", lane);
|
|
2000
|
-
}
|
|
2001
|
-
if (tokens.length >= CHANNEL_SCOPED_MIN_TOKENS && tokens[1] === "direct") {
|
|
2002
|
-
return buildEligibleIdentity(agentId, "direct", lane);
|
|
2003
|
-
}
|
|
2004
|
-
if (isSupportedGroupLane(tokens)) {
|
|
2005
|
-
return buildEligibleIdentity(agentId, "group", lane);
|
|
2006
|
-
}
|
|
2007
|
-
if (isSupportedChannelLane(tokens)) {
|
|
2008
|
-
return buildEligibleIdentity(agentId, "channel", lane);
|
|
2009
|
-
}
|
|
2010
|
-
return buildUnknownIdentity(agentId);
|
|
2011
|
-
}
|
|
2012
|
-
function isSupportedGroupLane(tokens) {
|
|
2013
|
-
if (tokens.length < CHANNEL_SCOPED_MIN_TOKENS || tokens[1] !== "group" || !tokens[0] || !tokens[2]) {
|
|
2014
|
-
return false;
|
|
2015
|
-
}
|
|
2016
|
-
if (tokens.length === CHANNEL_SCOPED_MIN_TOKENS) {
|
|
2017
|
-
return true;
|
|
2018
|
-
}
|
|
2019
|
-
return tokens.length === CHANNEL_SCOPED_MIN_TOKENS + TOPIC_SUFFIX_TOKENS && tokens[3] === "topic" && Boolean(tokens[4]);
|
|
2020
|
-
}
|
|
2021
|
-
function isSupportedChannelLane(tokens) {
|
|
2022
|
-
if (tokens.length < CHANNEL_SCOPED_MIN_TOKENS || tokens[1] !== "channel" || !tokens[0] || !tokens[2]) {
|
|
2023
|
-
return false;
|
|
2024
|
-
}
|
|
2025
|
-
if (tokens.length === CHANNEL_SCOPED_MIN_TOKENS) {
|
|
2026
|
-
return true;
|
|
2027
|
-
}
|
|
2028
|
-
return tokens.length === CHANNEL_SCOPED_MIN_TOKENS + THREAD_SUFFIX_TOKENS && tokens[3] === "thread" && Boolean(tokens[4]);
|
|
2029
|
-
}
|
|
2030
|
-
function normalizeMainKey(mainKey) {
|
|
2031
|
-
return mainKey?.trim() || "main";
|
|
2032
|
-
}
|
|
2033
|
-
function buildEligibleIdentity(agentId, kind, lane) {
|
|
2034
|
-
return {
|
|
2035
|
-
agentId,
|
|
2036
|
-
kind,
|
|
2037
|
-
stableLane: lane,
|
|
2038
|
-
instanceLane: lane,
|
|
2039
|
-
eligible: true
|
|
2040
|
-
};
|
|
2041
|
-
}
|
|
2042
|
-
function buildIneligibleIdentity(agentId, kind, lane) {
|
|
2043
|
-
return {
|
|
2044
|
-
agentId,
|
|
2045
|
-
kind,
|
|
2046
|
-
stableLane: lane,
|
|
2047
|
-
instanceLane: lane,
|
|
2048
|
-
eligible: false
|
|
2049
|
-
};
|
|
2050
|
-
}
|
|
2051
|
-
function buildUnknownIdentity(agentId = null) {
|
|
2052
|
-
return {
|
|
2053
|
-
agentId,
|
|
2054
|
-
kind: "unknown",
|
|
2055
|
-
stableLane: null,
|
|
2056
|
-
instanceLane: null,
|
|
2057
|
-
eligible: false
|
|
2058
|
-
};
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
// src/adapters/openclaw/session/continuity/predecessor-resolver.ts
|
|
2062
|
-
async function resolveOpenClawSessionPredecessor(ctx, tracker, params) {
|
|
2063
|
-
const sessionContext = formatSessionContext2(ctx.sessionId, ctx.sessionKey);
|
|
2064
|
-
const currentIdentity = parseOpenClawSessionContinuityKey(ctx.sessionKey ?? "", {
|
|
2065
|
-
mainKey: params.mainKey
|
|
2066
|
-
});
|
|
2067
|
-
debugLog3(
|
|
2068
|
-
params.logger,
|
|
2069
|
-
"predecessor",
|
|
2070
|
-
`parsed current identity for ${sessionContext}: kind=${currentIdentity.kind} stableLane=${currentIdentity.stableLane ?? "unknown"} eligible=${currentIdentity.eligible}`
|
|
2071
|
-
);
|
|
2072
|
-
if (!currentIdentity.eligible || !currentIdentity.agentId) {
|
|
2073
|
-
debugLog3(
|
|
2074
|
-
params.logger,
|
|
2075
|
-
"predecessor",
|
|
2076
|
-
`skipping predecessor resolution for ${sessionContext}: kind=${currentIdentity.kind} eligible=${currentIdentity.eligible} agentId=${currentIdentity.agentId ?? "unknown"}`
|
|
2077
|
-
);
|
|
2078
|
-
return void 0;
|
|
2079
|
-
}
|
|
2080
|
-
const sessionsDir = resolveOpenClawSessionsDirectory(ctx, currentIdentity.agentId, params.resolveStateDir);
|
|
2081
|
-
if (!sessionsDir) {
|
|
2082
|
-
params.logger?.info?.(`[agenr] predecessor: no predecessor found for ${sessionContext} reason=no_sessions_dir`);
|
|
2083
|
-
return void 0;
|
|
2084
|
-
}
|
|
2085
|
-
const resumedFrom = tracker.getResumedFrom(ctx.sessionId);
|
|
2086
|
-
if (resumedFrom) {
|
|
2087
|
-
debugLog3(params.logger, "predecessor", `session_start resumedFrom for ${sessionContext}: ${resumedFrom}`);
|
|
2088
|
-
const resumedFromPredecessor = await resolveResumedFromPredecessor(sessionsDir, resumedFrom, params.logger);
|
|
2089
|
-
if (resumedFromPredecessor) {
|
|
2090
|
-
params.logger?.info?.(
|
|
2091
|
-
`[agenr] predecessor: predecessor found for ${sessionContext} strategy=resumed_from predecessorKey=session_start predecessor=${resumedFromPredecessor.sessionFile}`
|
|
2092
|
-
);
|
|
2093
|
-
return resumedFromPredecessor;
|
|
2094
|
-
}
|
|
2095
|
-
debugLog3(
|
|
2096
|
-
params.logger,
|
|
2097
|
-
"predecessor",
|
|
2098
|
-
`session_start resumedFrom file not found for ${sessionContext}: resumedFrom=${resumedFrom} sessionsDir=${sessionsDir}`
|
|
2099
|
-
);
|
|
2100
|
-
} else {
|
|
2101
|
-
debugLog3(params.logger, "predecessor", `session_start resumedFrom unavailable for ${sessionContext}`);
|
|
2102
|
-
}
|
|
2103
|
-
if (!isSessionsStoreFallbackEligible(currentIdentity.kind)) {
|
|
2104
|
-
if (resumedFrom) {
|
|
2105
|
-
params.logger?.info?.(`[agenr] predecessor: no predecessor found for ${sessionContext} strategy=resumed_from reason=cold_start_after_resumed_from_miss`);
|
|
2106
|
-
}
|
|
2107
|
-
return void 0;
|
|
2108
|
-
}
|
|
2109
|
-
params.logger?.info?.(
|
|
2110
|
-
`[agenr] predecessor: predecessor resolution for ${sessionContext} strategy=sessions_json_scan sessionKey=${ctx.sessionKey?.trim() ?? "unknown"} kind=${currentIdentity.kind} stableLane=${currentIdentity.stableLane ?? "unknown"}`
|
|
2111
|
-
);
|
|
2112
|
-
const fallbackResolution = await findSessionsStoreFallbackPredecessor(
|
|
2113
|
-
currentIdentity,
|
|
2114
|
-
ctx.sessionKey ?? "",
|
|
2115
|
-
ctx.sessionId,
|
|
2116
|
-
sessionsDir,
|
|
2117
|
-
resumedFrom,
|
|
2118
|
-
params.mainKey,
|
|
2119
|
-
params.logger
|
|
2120
|
-
);
|
|
2121
|
-
if (!fallbackResolution.predecessor) {
|
|
2122
|
-
params.logger?.info?.(`[agenr] predecessor: no predecessor found for ${sessionContext} strategy=sessions_json_scan reason=${fallbackResolution.reason}`);
|
|
2123
|
-
return void 0;
|
|
2124
|
-
}
|
|
2125
|
-
params.logger?.info?.(
|
|
2126
|
-
`[agenr] predecessor: predecessor found for ${sessionContext} strategy=sessions_json_scan predecessorKey=${fallbackResolution.predecessor.sessionKey} predecessor=${fallbackResolution.predecessor.sessionFile}`
|
|
2127
|
-
);
|
|
2128
|
-
return {
|
|
2129
|
-
sessionId: fallbackResolution.predecessor.sessionId,
|
|
2130
|
-
sessionFile: fallbackResolution.predecessor.sessionFile
|
|
2131
|
-
};
|
|
2132
|
-
}
|
|
2133
|
-
async function resolveResumedFromPredecessor(sessionsDir, resumedFrom, logger) {
|
|
2134
|
-
const normalizedResumedFrom = resumedFrom.trim();
|
|
2135
|
-
if (!normalizedResumedFrom) {
|
|
2136
|
-
return void 0;
|
|
2137
|
-
}
|
|
2138
|
-
const candidates = await findResumedFromTranscriptCandidates(sessionsDir, normalizedResumedFrom, logger);
|
|
2139
|
-
const winner = candidates[0];
|
|
2140
|
-
if (!winner) {
|
|
2141
|
-
return void 0;
|
|
2142
|
-
}
|
|
2143
|
-
const sessionId = resolvePredecessorSessionId(normalizedResumedFrom, winner, logger);
|
|
2144
|
-
if (!sessionId) {
|
|
2145
|
-
return void 0;
|
|
2146
|
-
}
|
|
2147
|
-
return {
|
|
2148
|
-
sessionId,
|
|
2149
|
-
sessionFile: winner
|
|
2150
|
-
};
|
|
2151
|
-
}
|
|
2152
|
-
async function findResumedFromTranscriptCandidates(sessionsDir, sessionId, logger) {
|
|
2153
|
-
try {
|
|
2154
|
-
const dirEntries = await fs4.readdir(sessionsDir, { withFileTypes: true });
|
|
2155
|
-
const prefix = `${sessionId}.jsonl`;
|
|
2156
|
-
const liveMatches = [];
|
|
2157
|
-
const resetMatches = [];
|
|
2158
|
-
const deletedMatches = [];
|
|
2159
|
-
for (const entry of dirEntries) {
|
|
2160
|
-
if (!entry.isFile()) {
|
|
2161
|
-
continue;
|
|
2162
|
-
}
|
|
2163
|
-
const fileName = entry.name.trim();
|
|
2164
|
-
if (fileName === prefix) {
|
|
2165
|
-
liveMatches.push(path4.join(sessionsDir, fileName));
|
|
2166
|
-
continue;
|
|
2167
|
-
}
|
|
2168
|
-
if (fileName.startsWith(`${prefix}.reset.`)) {
|
|
2169
|
-
resetMatches.push(path4.join(sessionsDir, fileName));
|
|
2170
|
-
continue;
|
|
2171
|
-
}
|
|
2172
|
-
if (fileName.startsWith(`${prefix}.deleted.`)) {
|
|
2173
|
-
deletedMatches.push(path4.join(sessionsDir, fileName));
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
const ordered = [
|
|
2177
|
-
...liveMatches.sort((left, right) => left.localeCompare(right)),
|
|
2178
|
-
...resetMatches.sort(compareArchivePathsDescending),
|
|
2179
|
-
...deletedMatches.sort(compareArchivePathsDescending)
|
|
2180
|
-
];
|
|
2181
|
-
debugLog3(logger, "predecessor", `resumedFrom file discovery for sessionId=${sessionId}: candidates=${ordered.length} sessionsDir=${sessionsDir}`);
|
|
2182
|
-
return ordered;
|
|
2183
|
-
} catch (error) {
|
|
2184
|
-
if (isFileNotFound2(error)) {
|
|
2185
|
-
debugLog3(logger, "predecessor", `resumedFrom file discovery skipped missing directory=${sessionsDir}`);
|
|
2186
|
-
return [];
|
|
2187
|
-
}
|
|
2188
|
-
throw error;
|
|
2189
|
-
}
|
|
2190
|
-
}
|
|
2191
|
-
async function findSessionsStoreFallbackPredecessor(currentIdentity, currentSessionKey, currentSessionId, sessionsDir, resumedFrom, mainKey, logger) {
|
|
2192
|
-
if (!isSessionsStoreFallbackEligible(currentIdentity.kind)) {
|
|
2193
|
-
return {
|
|
2194
|
-
reason: "not_scan_eligible"
|
|
2195
|
-
};
|
|
2196
|
-
}
|
|
2197
|
-
const entries = await readOpenClawSessionsStore(sessionsDir, logger);
|
|
2198
|
-
debugLog3(
|
|
2199
|
-
logger,
|
|
2200
|
-
"predecessor",
|
|
2201
|
-
`sessions.json read result for kind=${currentIdentity.kind} stableLane=${currentIdentity.stableLane ?? "unknown"} entries=${entries.length}`
|
|
2202
|
-
);
|
|
2203
|
-
const sameAgentEntries = entries.filter((entry) => {
|
|
2204
|
-
const candidateIdentity = parseOpenClawSessionContinuityKey(entry.sessionKey, { mainKey });
|
|
2205
|
-
if (candidateIdentity.agentId !== currentIdentity.agentId) {
|
|
2206
|
-
debugLog3(
|
|
2207
|
-
logger,
|
|
2208
|
-
"predecessor",
|
|
2209
|
-
`excluded candidate=${entry.sessionKey} reason=agent_mismatch expected=${currentIdentity.agentId} actual=${candidateIdentity.agentId ?? "unknown"}`
|
|
2210
|
-
);
|
|
2211
|
-
return false;
|
|
2212
|
-
}
|
|
2213
|
-
return true;
|
|
2214
|
-
});
|
|
2215
|
-
debugLog3(logger, "predecessor", `same-agent candidate count for current=${currentSessionKey}: count=${sameAgentEntries.length}`);
|
|
2216
|
-
const matchedEntries = sameAgentEntries.filter(
|
|
2217
|
-
(entry) => isMatchingFallbackCandidate(entry, currentIdentity, currentSessionKey, currentSessionId, mainKey, logger)
|
|
2218
|
-
);
|
|
2219
|
-
debugLog3(logger, "predecessor", `lane-matched candidate count for current=${currentSessionKey}: count=${matchedEntries.length}`);
|
|
2220
|
-
if (matchedEntries.length === 0) {
|
|
2221
|
-
return {
|
|
2222
|
-
reason: "no_matching_sessions"
|
|
2223
|
-
};
|
|
2224
|
-
}
|
|
2225
|
-
if (resumedFrom?.trim()) {
|
|
2226
|
-
const resumedFromMatch = matchedEntries.find((entry) => resolveEntrySessionId(entry, logger) === resumedFrom.trim());
|
|
2227
|
-
if (resumedFromMatch) {
|
|
2228
|
-
const resolvedPredecessor = toResolvedCandidatePredecessor(resumedFromMatch, logger);
|
|
2229
|
-
if (resolvedPredecessor) {
|
|
2230
|
-
return {
|
|
2231
|
-
reason: "resolved",
|
|
2232
|
-
predecessor: resolvedPredecessor
|
|
2233
|
-
};
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
}
|
|
2237
|
-
const sortedCandidates = matchedEntries.filter((entry) => {
|
|
2238
|
-
if (!entry.sessionFile?.trim()) {
|
|
2239
|
-
debugLog3(logger, "predecessor", `excluded candidate=${entry.sessionKey} reason=missing_session_file`);
|
|
2240
|
-
return false;
|
|
2241
|
-
}
|
|
2242
|
-
if (entry.updatedAt === void 0) {
|
|
2243
|
-
debugLog3(logger, "predecessor", `excluded candidate=${entry.sessionKey} reason=missing_updated_at`);
|
|
2244
|
-
return false;
|
|
2245
|
-
}
|
|
2246
|
-
return true;
|
|
2247
|
-
}).sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0));
|
|
2248
|
-
if (sortedCandidates.length === 0) {
|
|
2249
|
-
return {
|
|
2250
|
-
reason: "no_matching_sessions"
|
|
2251
|
-
};
|
|
2252
|
-
}
|
|
2253
|
-
for (const candidate of sortedCandidates) {
|
|
2254
|
-
const resolvedPredecessor = toResolvedCandidatePredecessor(candidate, logger);
|
|
2255
|
-
if (resolvedPredecessor) {
|
|
2256
|
-
return {
|
|
2257
|
-
reason: "resolved",
|
|
2258
|
-
predecessor: resolvedPredecessor
|
|
2259
|
-
};
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
return {
|
|
2263
|
-
reason: "missing_session_id"
|
|
2264
|
-
};
|
|
2265
|
-
}
|
|
2266
|
-
function isSessionsStoreFallbackEligible(kind) {
|
|
2267
|
-
return kind === "main" || kind === "tui";
|
|
2268
|
-
}
|
|
2269
|
-
function isMatchingFallbackCandidate(entry, currentIdentity, currentSessionKey, currentSessionId, mainKey, logger) {
|
|
2270
|
-
const normalizedCandidateKey = entry.sessionKey.trim();
|
|
2271
|
-
const candidateSessionId = resolveEntrySessionId(entry, logger);
|
|
2272
|
-
if (candidateSessionId && currentSessionId?.trim() === candidateSessionId) {
|
|
2273
|
-
debugLog3(logger, "predecessor", `excluded candidate=${entry.sessionKey} reason=current_session_id`);
|
|
2274
|
-
return false;
|
|
2275
|
-
}
|
|
2276
|
-
if (normalizedCandidateKey === currentSessionKey.trim() && currentIdentity.kind !== "main") {
|
|
2277
|
-
debugLog3(logger, "predecessor", `excluded candidate=${entry.sessionKey} reason=current_session_key`);
|
|
2278
|
-
return false;
|
|
2279
|
-
}
|
|
2280
|
-
if (normalizedCandidateKey === currentSessionKey.trim() && !candidateSessionId) {
|
|
2281
|
-
debugLog3(logger, "predecessor", `excluded candidate=${entry.sessionKey} reason=current_session_key_without_session_id`);
|
|
2282
|
-
return false;
|
|
2283
|
-
}
|
|
2284
|
-
const candidateIdentity = parseOpenClawSessionContinuityKey(entry.sessionKey, { mainKey });
|
|
2285
|
-
if (currentIdentity.kind === "main") {
|
|
2286
|
-
if (candidateIdentity.kind !== "main" || candidateIdentity.stableLane !== currentIdentity.stableLane) {
|
|
2287
|
-
debugLog3(
|
|
2288
|
-
logger,
|
|
2289
|
-
"predecessor",
|
|
2290
|
-
`excluded candidate=${entry.sessionKey} reason=main_lane_mismatch currentStableLane=${currentIdentity.stableLane ?? "unknown"} candidateKind=${candidateIdentity.kind} candidateStableLane=${candidateIdentity.stableLane ?? "unknown"}`
|
|
2291
|
-
);
|
|
2292
|
-
return false;
|
|
2293
|
-
}
|
|
2294
|
-
return true;
|
|
2295
|
-
}
|
|
2296
|
-
if (currentIdentity.kind !== "tui") {
|
|
2297
|
-
return false;
|
|
2298
|
-
}
|
|
2299
|
-
if (currentIdentity.stableLane === "tui" && candidateIdentity.kind === "main") {
|
|
2300
|
-
return true;
|
|
2301
|
-
}
|
|
2302
|
-
if (candidateIdentity.kind !== "tui") {
|
|
2303
|
-
debugLog3(logger, "predecessor", `excluded candidate=${entry.sessionKey} reason=not_tui_candidate kind=${candidateIdentity.kind}`);
|
|
2304
|
-
return false;
|
|
2305
|
-
}
|
|
2306
|
-
if (!isSameTuiLane(currentIdentity.stableLane, candidateIdentity.stableLane)) {
|
|
2307
|
-
debugLog3(
|
|
2308
|
-
logger,
|
|
2309
|
-
"predecessor",
|
|
2310
|
-
`excluded candidate=${entry.sessionKey} reason=lane_mismatch currentStableLane=${currentIdentity.stableLane ?? "unknown"} candidateStableLane=${candidateIdentity.stableLane ?? "unknown"}`
|
|
2311
|
-
);
|
|
2312
|
-
return false;
|
|
2313
|
-
}
|
|
2314
|
-
return true;
|
|
2315
|
-
}
|
|
2316
|
-
function resolvePredecessorSessionId(sessionId, sessionFile, logger) {
|
|
2317
|
-
const normalizedSessionId = sessionId?.trim();
|
|
2318
|
-
if (normalizedSessionId) {
|
|
2319
|
-
return normalizedSessionId;
|
|
2320
|
-
}
|
|
2321
|
-
if (!sessionFile?.trim()) {
|
|
2322
|
-
return void 0;
|
|
2323
|
-
}
|
|
2324
|
-
return deriveOpenClawSessionIdFromFilePath(sessionFile, logger);
|
|
2325
|
-
}
|
|
2326
|
-
function resolveEntrySessionId(entry, logger) {
|
|
2327
|
-
return resolvePredecessorSessionId(entry.sessionId, entry.sessionFile, logger);
|
|
2328
|
-
}
|
|
2329
|
-
function toResolvedCandidatePredecessor(candidate, logger) {
|
|
2330
|
-
const sessionFile = candidate.sessionFile?.trim();
|
|
2331
|
-
if (!sessionFile) {
|
|
2332
|
-
return void 0;
|
|
2333
|
-
}
|
|
2334
|
-
const sessionId = resolvePredecessorSessionId(candidate.sessionId, sessionFile, logger);
|
|
2335
|
-
if (!sessionId) {
|
|
2336
|
-
return void 0;
|
|
2337
|
-
}
|
|
2338
|
-
return {
|
|
2339
|
-
sessionFile,
|
|
2340
|
-
sessionId,
|
|
2341
|
-
sessionKey: candidate.sessionKey
|
|
2342
|
-
};
|
|
2343
|
-
}
|
|
2344
|
-
function resolveOpenClawSessionsDirectory(ctx, parsedAgentId, resolveStateDir) {
|
|
2345
|
-
const agentId = ctx.agentId?.trim() || parsedAgentId.trim();
|
|
2346
|
-
if (!agentId) {
|
|
2347
|
-
return void 0;
|
|
2348
|
-
}
|
|
2349
|
-
return path4.join(resolveStateDir(process.env), "agents", agentId, "sessions");
|
|
2350
|
-
}
|
|
2351
|
-
function isSameTuiLane(currentStableLane, candidateStableLane) {
|
|
2352
|
-
if (!currentStableLane || !candidateStableLane) {
|
|
2353
|
-
return false;
|
|
2354
|
-
}
|
|
2355
|
-
if (currentStableLane === "tui") {
|
|
2356
|
-
return candidateStableLane.toLowerCase().startsWith("tui");
|
|
2357
|
-
}
|
|
2358
|
-
return currentStableLane === candidateStableLane;
|
|
2359
|
-
}
|
|
2360
|
-
function compareArchivePathsDescending(left, right) {
|
|
2361
|
-
return path4.basename(right).localeCompare(path4.basename(left));
|
|
2362
|
-
}
|
|
2363
|
-
function debugLog3(logger, subsystem, message) {
|
|
2364
|
-
logger?.debug?.(`[agenr] ${subsystem}: ${message}`);
|
|
2365
|
-
}
|
|
2366
|
-
function isFileNotFound2(error) {
|
|
2367
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2368
|
-
}
|
|
2369
|
-
function formatSessionContext2(sessionId, sessionKey) {
|
|
2370
|
-
const normalizedSessionId = sessionId?.trim();
|
|
2371
|
-
const normalizedSessionKey = sessionKey?.trim();
|
|
2372
|
-
if (normalizedSessionId && normalizedSessionKey) {
|
|
2373
|
-
return `session=${normalizedSessionId} key=${normalizedSessionKey}`;
|
|
2374
|
-
}
|
|
2375
|
-
if (normalizedSessionId) {
|
|
2376
|
-
return `session=${normalizedSessionId}`;
|
|
2377
|
-
}
|
|
2378
|
-
if (normalizedSessionKey) {
|
|
2379
|
-
return `key=${normalizedSessionKey}`;
|
|
2380
|
-
}
|
|
2381
|
-
return "session=unknown";
|
|
2382
|
-
}
|
|
2383
|
-
|
|
2384
|
-
// src/adapters/openclaw/session/continuity/recent-session.ts
|
|
2385
|
-
var RECENT_SESSION_MESSAGE_LIMIT = 6;
|
|
2386
|
-
var RECENT_SESSION_MAX_CHARS = 1800;
|
|
2387
|
-
var SESSION_START_SECTION_HEADINGS = [
|
|
2388
|
-
"## Previous session summary",
|
|
2389
|
-
"## Recent session",
|
|
2390
|
-
"## Agenr Session Recall",
|
|
2391
|
-
"### Core Memory",
|
|
2392
|
-
"### Relevant Durable Memory",
|
|
2393
|
-
"## Agenr Before-Turn Recall",
|
|
2394
|
-
"### Suggested Procedure"
|
|
2395
|
-
];
|
|
2396
|
-
var INLINE_METADATA_SENTINELS = [
|
|
2397
|
-
"Sender (untrusted metadata):",
|
|
2398
|
-
"Conversation info (untrusted metadata):",
|
|
2399
|
-
"Thread starter (untrusted, for context):",
|
|
2400
|
-
"Replied message (untrusted, for context):",
|
|
2401
|
-
"Forwarded message context (untrusted metadata):",
|
|
2402
|
-
"Chat history since last reply (untrusted, for context):"
|
|
2403
|
-
];
|
|
2404
|
-
async function renderRecentSessionSection(sessionFile, logger) {
|
|
2405
|
-
try {
|
|
2406
|
-
const transcript = await openClawTranscriptParser.parseFile(sessionFile);
|
|
2407
|
-
const sanitizedMessages = transcript.messages.map((message) => ({
|
|
2408
|
-
prefix: message.role === "user" ? "U" : "A",
|
|
2409
|
-
text: sanitizeRecentSessionMessage(message.text, message.role)
|
|
2410
|
-
})).filter((message) => message.text.length > 0);
|
|
2411
|
-
const tail = sanitizedMessages.slice(-RECENT_SESSION_MESSAGE_LIMIT);
|
|
2412
|
-
const body = capRecentSession(tail.map((message) => `${message.prefix}: ${message.text}`).join("\n"), RECENT_SESSION_MAX_CHARS);
|
|
2413
|
-
logger.debug?.(`[agenr] before_prompt_build: recent session tail for file=${sessionFile}: messages=${tail.length} chars=${body.length}`);
|
|
2414
|
-
return body;
|
|
2415
|
-
} catch (error) {
|
|
2416
|
-
logger.debug?.(`[agenr] before_prompt_build: failed to build recent session tail for file=${sessionFile}: ${formatErrorMessage4(error)}`);
|
|
2417
|
-
return "";
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2420
|
-
function formatErrorMessage4(error) {
|
|
2421
|
-
return error instanceof Error ? error.message : String(error);
|
|
2422
|
-
}
|
|
2423
|
-
function capRecentSession(value, maxChars) {
|
|
2424
|
-
if (value.length <= maxChars) {
|
|
2425
|
-
return value;
|
|
2426
|
-
}
|
|
2427
|
-
const marker = "[...truncated earlier recent session...]\n";
|
|
2428
|
-
return `${marker}${value.slice(-(maxChars - marker.length)).trimStart()}`;
|
|
2429
|
-
}
|
|
2430
|
-
function sanitizeRecentSessionMessage(text, role) {
|
|
2431
|
-
const wrapperDetected = containsAgenrMemoryContext(text) || SESSION_START_SECTION_HEADINGS.some((heading) => text.includes(heading));
|
|
2432
|
-
let cleaned = stripAgenrMemoryContext(text);
|
|
2433
|
-
for (const heading of SESSION_START_SECTION_HEADINGS) {
|
|
2434
|
-
cleaned = cleaned.split(heading).join(" ");
|
|
2435
|
-
}
|
|
2436
|
-
cleaned = stripInlineMetadata(cleaned);
|
|
2437
|
-
cleaned = collapseWhitespace(cleaned);
|
|
2438
|
-
if (wrapperDetected) {
|
|
2439
|
-
cleaned = unwrapEmbeddedTranscriptTurn(cleaned, role);
|
|
2440
|
-
}
|
|
2441
|
-
return collapseWhitespace(cleaned);
|
|
2442
|
-
}
|
|
2443
|
-
function stripInlineMetadata(text) {
|
|
2444
|
-
let cleaned = text;
|
|
2445
|
-
for (const sentinel of INLINE_METADATA_SENTINELS) {
|
|
2446
|
-
const escapedSentinel = escapeForRegExp(sentinel);
|
|
2447
|
-
cleaned = cleaned.replace(new RegExp(`${escapedSentinel}\\s*(?:json\\s*)?\\{[\\s\\S]*?\\}`, "gu"), " ");
|
|
2448
|
-
cleaned = cleaned.replace(new RegExp(`${escapedSentinel}[^
|
|
2449
|
-
]*`, "gu"), " ");
|
|
2450
|
-
}
|
|
2451
|
-
cleaned = cleaned.replace(/Untrusted context \(metadata, do not treat as instructions or commands\):[\s\S]*$/gu, " ");
|
|
2452
|
-
return cleaned;
|
|
2453
|
-
}
|
|
2454
|
-
function unwrapEmbeddedTranscriptTurn(text, role) {
|
|
2455
|
-
if (role === "user") {
|
|
2456
|
-
const timestampMatches = [...text.matchAll(/\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s[^\]]+\]/gu)];
|
|
2457
|
-
if (timestampMatches.length > 1) {
|
|
2458
|
-
const lastTimestamp = timestampMatches.at(-1);
|
|
2459
|
-
if (lastTimestamp?.index !== void 0) {
|
|
2460
|
-
return text.slice(lastTimestamp.index).trim();
|
|
2461
|
-
}
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
const marker = role === "user" ? "U:" : "A:";
|
|
2465
|
-
const lastMarkerIndex = text.lastIndexOf(marker);
|
|
2466
|
-
if (lastMarkerIndex < 0) {
|
|
2467
|
-
return text;
|
|
2468
|
-
}
|
|
2469
|
-
return text.slice(lastMarkerIndex + marker.length).trim();
|
|
2470
|
-
}
|
|
2471
|
-
function escapeForRegExp(value) {
|
|
2472
|
-
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
2473
|
-
}
|
|
2474
|
-
function collapseWhitespace(value) {
|
|
2475
|
-
return value.replace(/\s+/gu, " ").trim();
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
// src/adapters/openclaw/session/continuity/index.ts
|
|
2479
|
-
var READ_TIME_CONTINUITY_SUMMARY_TIMEOUT_MS = 35e3;
|
|
2480
|
-
var READ_TIME_CONTINUITY_SUMMARY_TIMEOUT = /* @__PURE__ */ Symbol("read-time-continuity-summary-timeout");
|
|
2481
|
-
async function resolvePredecessorContinuity(ctx, tracker, services, logger) {
|
|
2482
|
-
const sessionContext = formatSessionContext(ctx.sessionId, ctx.sessionKey);
|
|
2483
|
-
const predecessor = await resolveOpenClawSessionPredecessor(ctx, tracker, {
|
|
2484
|
-
logger,
|
|
2485
|
-
mainKey: resolveOpenClawMainKey(services),
|
|
2486
|
-
resolveStateDir: services.openClaw.runtime.state.resolveStateDir
|
|
2487
|
-
});
|
|
2488
|
-
if (!predecessor) {
|
|
2489
|
-
logger.info(`[agenr] session-start predecessor continuity summary not found for ${sessionContext} reason=no_predecessor`);
|
|
2490
|
-
return {
|
|
2491
|
-
continuitySummaryContent: "",
|
|
2492
|
-
recentSessionContent: ""
|
|
2493
|
-
};
|
|
2494
|
-
}
|
|
2495
|
-
return {
|
|
2496
|
-
predecessor,
|
|
2497
|
-
continuitySummaryContent: await loadPredecessorContinuitySummaryContent(sessionContext, predecessor.sessionFile, ctx.agentId, services, logger),
|
|
2498
|
-
recentSessionContent: await renderRecentSessionSection(predecessor.sessionFile, logger)
|
|
2499
|
-
};
|
|
2500
|
-
}
|
|
2501
|
-
function resolveOpenClawMainKey(services) {
|
|
2502
|
-
const configWithSession = services.openClaw.config;
|
|
2503
|
-
return configWithSession.session?.mainKey?.trim() || "main";
|
|
2504
|
-
}
|
|
2505
|
-
async function loadPredecessorContinuitySummaryContent(sessionContext, sessionFile, agentId, services, logger) {
|
|
2506
|
-
try {
|
|
2507
|
-
const existingContinuitySummary = await readOpenClawContinuitySummaryFile(sessionFile, logger);
|
|
2508
|
-
if (existingContinuitySummary) {
|
|
2509
|
-
logger.info(
|
|
2510
|
-
`[agenr] session-start read-time continuity summary generation skipped for ${sessionContext} predecessor=${sessionFile} reason=already_exists path=${existingContinuitySummary.continuitySummaryPath}`
|
|
2511
|
-
);
|
|
2512
|
-
logger.info(`[agenr] session-start predecessor continuity summary found for ${sessionContext} path=${existingContinuitySummary.continuitySummaryPath}`);
|
|
2513
|
-
return existingContinuitySummary.content;
|
|
2514
|
-
}
|
|
2515
|
-
logger.info(`[agenr] session-start predecessor continuity summary not found for ${sessionContext} predecessor=${sessionFile}`);
|
|
2516
|
-
} catch (error) {
|
|
2517
|
-
logger.info(
|
|
2518
|
-
`[agenr] session-start predecessor continuity summary not found for ${sessionContext} predecessor=${sessionFile} reason=${formatErrorMessage2(error)}`
|
|
2519
|
-
);
|
|
2520
|
-
logger.debug?.(`[agenr] before_prompt_build: failed reading predecessor continuity summary for ${sessionContext}: ${formatErrorMessage2(error)}`);
|
|
2521
|
-
return "";
|
|
2522
|
-
}
|
|
2523
|
-
logger.info(
|
|
2524
|
-
`[agenr] session-start read-time continuity summary generation triggered for ${sessionContext} predecessor=${sessionFile} reason=no_existing_continuity_summary`
|
|
2525
|
-
);
|
|
2526
|
-
const startedAt = Date.now();
|
|
2527
|
-
try {
|
|
2528
|
-
const result = await awaitWithTimeout(
|
|
2529
|
-
generateAndWriteOpenClawContinuitySummary({
|
|
2530
|
-
sessionFile,
|
|
2531
|
-
agentId,
|
|
2532
|
-
openClaw: services.openClaw,
|
|
2533
|
-
logger,
|
|
2534
|
-
pluginConfig: services.pluginConfig
|
|
2535
|
-
}),
|
|
2536
|
-
READ_TIME_CONTINUITY_SUMMARY_TIMEOUT_MS
|
|
2537
|
-
);
|
|
2538
|
-
const elapsedMs = Date.now() - startedAt;
|
|
2539
|
-
if (result === READ_TIME_CONTINUITY_SUMMARY_TIMEOUT) {
|
|
2540
|
-
logger.info(
|
|
2541
|
-
`[agenr] session-start read-time continuity summary generation failed for ${sessionContext} predecessor=${sessionFile} reason=timeout elapsedMs=${elapsedMs}`
|
|
2542
|
-
);
|
|
2543
|
-
logger.debug?.(
|
|
2544
|
-
`[agenr] before_prompt_build: read-time continuity summary generation timed out for ${sessionContext}: predecessor=${sessionFile} timeoutMs=${READ_TIME_CONTINUITY_SUMMARY_TIMEOUT_MS}`
|
|
2545
|
-
);
|
|
2546
|
-
return "";
|
|
2547
|
-
}
|
|
2548
|
-
return handleReadTimeContinuitySummaryResult(sessionContext, sessionFile, result, elapsedMs, logger);
|
|
2549
|
-
} catch (error) {
|
|
2550
|
-
const elapsedMs = Date.now() - startedAt;
|
|
2551
|
-
logger.info(
|
|
2552
|
-
`[agenr] session-start read-time continuity summary generation failed for ${sessionContext} predecessor=${sessionFile} reason=${formatErrorMessage2(error)} elapsedMs=${elapsedMs}`
|
|
2553
|
-
);
|
|
2554
|
-
logger.debug?.(
|
|
2555
|
-
`[agenr] before_prompt_build: unexpected read-time continuity summary generation failure for ${sessionContext}: ${formatErrorMessage2(error)}`
|
|
2556
|
-
);
|
|
2557
|
-
return "";
|
|
2558
|
-
}
|
|
2559
|
-
}
|
|
2560
|
-
function handleReadTimeContinuitySummaryResult(sessionContext, sessionFile, result, elapsedMs, logger) {
|
|
2561
|
-
if (result.status === "written" && result.content && result.continuitySummaryPath) {
|
|
2562
|
-
logger.info(
|
|
2563
|
-
`[agenr] session-start read-time continuity summary generation completed for ${sessionContext} predecessor=${sessionFile} elapsedMs=${elapsedMs} path=${result.continuitySummaryPath}`
|
|
2564
|
-
);
|
|
2565
|
-
logger.info(`[agenr] session-start predecessor continuity summary found for ${sessionContext} path=${result.continuitySummaryPath}`);
|
|
2566
|
-
return result.content;
|
|
2567
|
-
}
|
|
2568
|
-
if (result.status === "skipped") {
|
|
2569
|
-
logger.info(
|
|
2570
|
-
`[agenr] session-start read-time continuity summary generation skipped for ${sessionContext} predecessor=${sessionFile} reason=${result.reason ?? "unknown"} path=${result.continuitySummaryPath ?? "n/a"}`
|
|
2571
|
-
);
|
|
2572
|
-
logger.debug?.(
|
|
2573
|
-
`[agenr] before_prompt_build: read-time continuity summary generation skipped for ${sessionContext}: predecessor=${sessionFile} transcriptChars=${result.transcriptChars ?? 0} cleanedMessages=${result.messageCount ?? 0}`
|
|
2574
|
-
);
|
|
2575
|
-
if (result.reason === "already_exists" && result.content && result.continuitySummaryPath) {
|
|
2576
|
-
logger.info(`[agenr] session-start predecessor continuity summary found for ${sessionContext} path=${result.continuitySummaryPath}`);
|
|
2577
|
-
return result.content;
|
|
2578
|
-
}
|
|
2579
|
-
return "";
|
|
2580
|
-
}
|
|
2581
|
-
logger.info(
|
|
2582
|
-
`[agenr] session-start read-time continuity summary generation failed for ${sessionContext} predecessor=${sessionFile} reason=${result.reason ?? "unknown"} elapsedMs=${elapsedMs} model=${result.model ?? "unknown"}`
|
|
2583
|
-
);
|
|
2584
|
-
logger.debug?.(
|
|
2585
|
-
`[agenr] before_prompt_build: read-time continuity summary generation failed for ${sessionContext}: predecessor=${sessionFile} durationMs=${result.durationMs ?? 0} transcriptChars=${result.transcriptChars ?? 0}`
|
|
2586
|
-
);
|
|
2587
|
-
return "";
|
|
2588
|
-
}
|
|
2589
|
-
async function awaitWithTimeout(promise, timeoutMs) {
|
|
2590
|
-
return new Promise((resolve, reject) => {
|
|
2591
|
-
const timeout = setTimeout(() => {
|
|
2592
|
-
resolve(READ_TIME_CONTINUITY_SUMMARY_TIMEOUT);
|
|
2593
|
-
}, timeoutMs);
|
|
2594
|
-
promise.then(
|
|
2595
|
-
(value) => {
|
|
2596
|
-
clearTimeout(timeout);
|
|
2597
|
-
resolve(value);
|
|
2598
|
-
},
|
|
2599
|
-
(error) => {
|
|
2600
|
-
clearTimeout(timeout);
|
|
2601
|
-
reject(error);
|
|
2602
|
-
}
|
|
2603
|
-
);
|
|
2604
|
-
});
|
|
2605
|
-
}
|
|
2606
|
-
|
|
2607
|
-
// src/adapters/openclaw/hooks/before-prompt-build.ts
|
|
2608
|
-
var NON_USER_TRIGGER_SET = /* @__PURE__ */ new Set(["heartbeat", "cron", "memory"]);
|
|
2609
|
-
var DEFAULT_STORE_NUDGE_CONFIG = resolveStoreNudgeConfig(void 0);
|
|
2610
|
-
var INLINE_METADATA_SENTINELS2 = [
|
|
2611
|
-
"Sender (untrusted metadata):",
|
|
2612
|
-
"Conversation info (untrusted metadata):",
|
|
2613
|
-
"Thread starter (untrusted, for context):",
|
|
2614
|
-
"Replied message (untrusted, for context):",
|
|
2615
|
-
"Forwarded message context (untrusted metadata):",
|
|
2616
|
-
"Chat history since last reply (untrusted, for context):"
|
|
2617
|
-
];
|
|
2618
|
-
async function handleAgenrBeforePromptBuild(event, ctx, params) {
|
|
2619
|
-
const sessionContext = formatSessionContext(ctx.sessionId, ctx.sessionKey);
|
|
2620
|
-
const trackerState = params.tracker.consume(ctx.sessionId, ctx.sessionKey);
|
|
2621
|
-
if (!trackerState.isFirst) {
|
|
2622
|
-
params.logger.debug?.(`[agenr] before_prompt_build: session tracker duplicate blocked for ${sessionContext}`);
|
|
2623
|
-
params.logger.debug?.(`[agenr] before_prompt_build: session tracker active count=${trackerState.activeCount}`);
|
|
2624
|
-
params.logger.info(`[agenr] session-start recall skipped (already ran) for ${sessionContext}`);
|
|
2625
|
-
return await resolveNonFirstTurnResult(event, ctx, sessionContext, params);
|
|
2626
|
-
}
|
|
2627
|
-
params.logger.debug?.(`[agenr] before_prompt_build: session tracker first start for ${sessionContext}`);
|
|
2628
|
-
params.logger.debug?.(`[agenr] before_prompt_build: session tracker active count=${trackerState.activeCount}`);
|
|
2629
|
-
params.logger.info(`[agenr] session-start recall for ${sessionContext}`);
|
|
2630
|
-
try {
|
|
2631
|
-
const services = await params.servicesPromise;
|
|
2632
|
-
if (services.pluginConfig.memoryPolicy?.sessionStart?.enabled === false) {
|
|
2633
|
-
params.logger.info(`[agenr] session-start recall disabled by memoryPolicy for ${sessionContext}`);
|
|
2634
|
-
return await resolveNonFirstTurnResult(event, ctx, sessionContext, params);
|
|
2635
|
-
}
|
|
2636
|
-
const continuity = await resolvePredecessorContinuity(ctx, params.tracker, services, params.logger);
|
|
2637
|
-
emitContinuityEvent(services.debugSink, ctx, continuity);
|
|
2638
|
-
void writeOpenClawPredecessorEpisode({
|
|
2639
|
-
ctx,
|
|
2640
|
-
predecessor: continuity.predecessor,
|
|
2641
|
-
services,
|
|
2642
|
-
logger: params.logger
|
|
2643
|
-
});
|
|
2644
|
-
const sessionStartPatch = await runSessionStart(
|
|
2645
|
-
{
|
|
2646
|
-
sessionKey: ctx.sessionKey,
|
|
2647
|
-
continuitySummaryText: continuity.continuitySummaryContent,
|
|
2648
|
-
recentSessionText: continuity.recentSessionContent,
|
|
2649
|
-
policy: resolveSessionStartPolicy(services.pluginConfig.memoryPolicy)
|
|
2650
|
-
},
|
|
2651
|
-
services.sessionStart
|
|
2652
|
-
);
|
|
2653
|
-
const prependContext = formatAgenrSessionStartRecall(sessionStartPatch);
|
|
2654
|
-
if (services.debugSink.enabled) {
|
|
2655
|
-
void services.debugSink.emit({
|
|
2656
|
-
type: "session_start_recall",
|
|
2657
|
-
...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
|
|
2658
|
-
...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
|
|
2659
|
-
debug: {
|
|
2660
|
-
durableMemoryCount: sessionStartPatch.durableMemory.length,
|
|
2661
|
-
selectedEntryIds: sessionStartPatch.durableMemory.map((item) => item.entry.id),
|
|
2662
|
-
coreCandidateCount: sessionStartPatch.diagnostics.coreCandidateCount,
|
|
2663
|
-
artifactRecallCandidateCount: sessionStartPatch.diagnostics.artifactRecallCandidateCount,
|
|
2664
|
-
artifactRecallUsed: sessionStartPatch.diagnostics.artifactRecallUsed,
|
|
2665
|
-
notices: [...sessionStartPatch.diagnostics.notices]
|
|
2666
|
-
}
|
|
2667
|
-
});
|
|
2668
|
-
}
|
|
2669
|
-
params.logger.info(
|
|
2670
|
-
`[agenr] session-start recall: ${sessionStartPatch.durableMemory.length} durable entries for ${sessionContext} (core_candidates=${sessionStartPatch.diagnostics.coreCandidateCount} artifact_candidates=${sessionStartPatch.diagnostics.artifactRecallCandidateCount})`
|
|
2671
|
-
);
|
|
2672
|
-
if (sessionStartPatch.diagnostics.artifactRecallUsed) {
|
|
2673
|
-
params.logger.debug?.(
|
|
2674
|
-
`[agenr] before_prompt_build: session-start artifact recall for ${sessionContext} query_length=${sessionStartPatch.diagnostics.artifactRecallQuery?.length ?? 0} notices=${sessionStartPatch.diagnostics.notices.length}`
|
|
2675
|
-
);
|
|
2676
|
-
}
|
|
2677
|
-
if (sessionStartPatch.diagnostics.notices.length > 0) {
|
|
2678
|
-
params.logger.info(`[agenr] session-start recall notices for ${sessionContext}: ${sessionStartPatch.diagnostics.notices.join(" | ")}`);
|
|
2679
|
-
}
|
|
2680
|
-
params.logger.debug?.(
|
|
2681
|
-
`[agenr] before_prompt_build: session-start durable entries for ${sessionContext}: ${formatEntryRefs(sessionStartPatch.durableMemory.map((item) => item.entry))}`
|
|
2682
|
-
);
|
|
2683
|
-
params.logger.debug?.(`[agenr] before_prompt_build: session-start prependContext length for ${sessionContext}: ${prependContext.length} chars`);
|
|
2684
|
-
if (prependContext.length === 0) {
|
|
2685
|
-
params.logger.info(`[agenr] session-start recall: nothing to inject for ${sessionContext}`);
|
|
2686
|
-
return void 0;
|
|
2687
|
-
}
|
|
2688
|
-
return { prependContext };
|
|
2689
|
-
} catch (error) {
|
|
2690
|
-
params.logger.warn(`[agenr] session-start recall failed for ${sessionContext}: ${formatErrorMessage2(error)}`);
|
|
2691
|
-
try {
|
|
2692
|
-
const services = await params.servicesPromise;
|
|
2693
|
-
if (services.debugSink.enabled) {
|
|
2694
|
-
void services.debugSink.emit({
|
|
2695
|
-
type: "error",
|
|
2696
|
-
...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
|
|
2697
|
-
...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
|
|
2698
|
-
scope: "session_start_recall",
|
|
2699
|
-
error: { message: error instanceof Error ? error.message : String(error) }
|
|
2700
|
-
});
|
|
2701
|
-
}
|
|
2702
|
-
} catch {
|
|
2703
|
-
}
|
|
2704
|
-
return void 0;
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
function emitContinuityEvent(sink, ctx, continuity) {
|
|
2708
|
-
if (!sink.enabled) {
|
|
2709
|
-
return;
|
|
2710
|
-
}
|
|
2711
|
-
void sink.emit({
|
|
2712
|
-
type: "continuity_resolution",
|
|
2713
|
-
...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
|
|
2714
|
-
...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
|
|
2715
|
-
summary: {
|
|
2716
|
-
predecessorFound: Boolean(continuity.predecessor),
|
|
2717
|
-
...continuity.predecessor ? { predecessorFileBasename: path5.basename(continuity.predecessor.sessionFile) } : {},
|
|
2718
|
-
hasContinuitySummary: continuity.continuitySummaryContent.length > 0,
|
|
2719
|
-
hasRecentSession: continuity.recentSessionContent.length > 0,
|
|
2720
|
-
continuitySummaryChars: continuity.continuitySummaryContent.length,
|
|
2721
|
-
recentSessionChars: continuity.recentSessionContent.length
|
|
2722
|
-
}
|
|
2723
|
-
});
|
|
2724
|
-
}
|
|
2725
|
-
async function resolveNonFirstTurnResult(event, ctx, sessionContext, params) {
|
|
2726
|
-
const beforeTurnResult = await resolveBeforeTurnResult(event, ctx, sessionContext, params);
|
|
2727
|
-
if (beforeTurnResult) {
|
|
2728
|
-
return beforeTurnResult;
|
|
2729
|
-
}
|
|
2730
|
-
return resolveStoreNudgeResult(event, ctx, sessionContext, params);
|
|
2731
|
-
}
|
|
2732
|
-
async function resolveBeforeTurnResult(event, ctx, sessionContext, params) {
|
|
2733
|
-
const normalizedTrigger = ctx.trigger?.trim().toLowerCase();
|
|
2734
|
-
if (normalizedTrigger && NON_USER_TRIGGER_SET.has(normalizedTrigger)) {
|
|
2735
|
-
params.logger.debug?.(`[agenr] before_prompt_build: before-turn skipped for ${sessionContext} reason=non_user_trigger trigger=${normalizedTrigger}`);
|
|
2736
|
-
return void 0;
|
|
2737
|
-
}
|
|
2738
|
-
const services = await params.servicesPromise;
|
|
2739
|
-
if (services.pluginConfig.memoryPolicy?.beforeTurn?.enabled === false) {
|
|
2740
|
-
params.logger.debug?.(`[agenr] before_prompt_build: before-turn skipped for ${sessionContext} reason=disabled`);
|
|
2741
|
-
return void 0;
|
|
2742
|
-
}
|
|
2743
|
-
const currentTurnText = normalizePromptText(event.prompt, {
|
|
2744
|
-
stripInlineMetadata: true,
|
|
2745
|
-
inlineMetadataSentinels: INLINE_METADATA_SENTINELS2,
|
|
2746
|
-
stripTimestampPrefix: true,
|
|
2747
|
-
stripUserPrefix: true
|
|
2748
|
-
});
|
|
2749
|
-
if (!currentTurnText) {
|
|
2750
|
-
params.logger.debug?.(`[agenr] before_prompt_build: before-turn skipped for ${sessionContext} reason=empty_prompt`);
|
|
2751
|
-
return void 0;
|
|
2752
|
-
}
|
|
2753
|
-
try {
|
|
2754
|
-
const beforeTurnPatch = await runBeforeTurn(
|
|
2755
|
-
{
|
|
2756
|
-
sessionKey: ctx.sessionKey,
|
|
2757
|
-
currentTurnText,
|
|
2758
|
-
recentTurns: extractRecentTurnsFromMessages(
|
|
2759
|
-
event.messages.filter((message) => Boolean(message) && typeof message === "object"),
|
|
2760
|
-
{ stripMemoryCheck: true }
|
|
2761
|
-
),
|
|
2762
|
-
trigger: ctx.trigger,
|
|
2763
|
-
policy: resolveBeforeTurnPolicy(services.pluginConfig.memoryPolicy)
|
|
2764
|
-
},
|
|
2765
|
-
services.beforeTurn
|
|
2766
|
-
);
|
|
2767
|
-
const prependContext = formatAgenrBeforeTurnRecall(beforeTurnPatch);
|
|
2768
|
-
if (services.debugSink.enabled) {
|
|
2769
|
-
void services.debugSink.emit({
|
|
2770
|
-
type: "before_turn_decision",
|
|
2771
|
-
...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
|
|
2772
|
-
...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
|
|
2773
|
-
debug: buildLiveBeforeTurnDebugArtifact({
|
|
2774
|
-
caseId: `live-${randomUUID2()}`,
|
|
2775
|
-
patch: beforeTurnPatch,
|
|
2776
|
-
currentTurnText,
|
|
2777
|
-
trigger: ctx.trigger,
|
|
2778
|
-
eventLevel: services.debugSink.eventLevel,
|
|
2779
|
-
maxTopCandidates: services.debugSink.maxTopCandidates
|
|
2780
|
-
})
|
|
2781
|
-
});
|
|
2782
|
-
}
|
|
2783
|
-
params.logger.info(
|
|
2784
|
-
`[agenr] before-turn recall: ${beforeTurnPatch.durableMemory.length} durable entries for ${sessionContext} (durable_candidates=${beforeTurnPatch.diagnostics.durableRecallCandidateCount} procedure_candidates=${beforeTurnPatch.diagnostics.procedureCandidateCount})`
|
|
2785
|
-
);
|
|
2786
|
-
if (beforeTurnPatch.procedure) {
|
|
2787
|
-
params.logger.info(
|
|
2788
|
-
`[agenr] before-turn procedure suggestion for ${sessionContext}: ${beforeTurnPatch.procedure.procedure.procedure_key} score=${beforeTurnPatch.procedure.score.toFixed(2)}`
|
|
2789
|
-
);
|
|
2790
|
-
}
|
|
2791
|
-
if (beforeTurnPatch.diagnostics.notices.length > 0) {
|
|
2792
|
-
params.logger.info(`[agenr] before-turn recall notices for ${sessionContext}: ${beforeTurnPatch.diagnostics.notices.join(" | ")}`);
|
|
2793
|
-
}
|
|
2794
|
-
if (beforeTurnPatch.diagnostics.abstained) {
|
|
2795
|
-
params.logger.debug?.(
|
|
2796
|
-
`[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"}`
|
|
2797
|
-
);
|
|
2798
|
-
}
|
|
2799
|
-
params.logger.debug?.(`[agenr] before_prompt_build: before-turn diagnostics for ${sessionContext}: ${formatBeforeTurnDiagnosticsForLog(beforeTurnPatch)}`);
|
|
2800
|
-
params.logger.debug?.(
|
|
2801
|
-
`[agenr] before_prompt_build: before-turn durable entries for ${sessionContext}: ${formatEntryRefs(
|
|
2802
|
-
beforeTurnPatch.durableMemory.map((item) => item.entry)
|
|
2803
|
-
)}`
|
|
2804
|
-
);
|
|
2805
|
-
params.logger.debug?.(`[agenr] before_prompt_build: before-turn prependContext length for ${sessionContext}: ${prependContext.length} chars`);
|
|
2806
|
-
if (prependContext.length === 0) {
|
|
2807
|
-
return void 0;
|
|
2808
|
-
}
|
|
2809
|
-
return { prependContext };
|
|
2810
|
-
} catch (error) {
|
|
2811
|
-
params.logger.warn(`[agenr] before-turn recall failed for ${sessionContext}: ${formatErrorMessage2(error)}`);
|
|
2812
|
-
try {
|
|
2813
|
-
if (services.debugSink.enabled) {
|
|
2814
|
-
void services.debugSink.emit({
|
|
2815
|
-
type: "error",
|
|
2816
|
-
...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
|
|
2817
|
-
...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
|
|
2818
|
-
scope: "before_turn_decision",
|
|
2819
|
-
error: { message: error instanceof Error ? error.message : String(error) }
|
|
2820
|
-
});
|
|
2821
|
-
}
|
|
2822
|
-
} catch {
|
|
2823
|
-
}
|
|
2824
|
-
return void 0;
|
|
2825
|
-
}
|
|
2826
|
-
}
|
|
2827
|
-
function resolveStoreNudgeResult(_event, ctx, sessionContext, params) {
|
|
2828
|
-
const normalizedTrigger = ctx.trigger?.trim().toLowerCase();
|
|
2829
|
-
if (normalizedTrigger && NON_USER_TRIGGER_SET.has(normalizedTrigger)) {
|
|
2830
|
-
params.logger.debug?.(`[agenr] before_prompt_build: store nudge skipped for ${sessionContext} reason=non_user_trigger trigger=${normalizedTrigger}`);
|
|
2831
|
-
return void 0;
|
|
2832
|
-
}
|
|
2833
|
-
const storeNudgeConfig = params.storeNudgeConfig ?? DEFAULT_STORE_NUDGE_CONFIG;
|
|
2834
|
-
if (!storeNudgeConfig.enabled) {
|
|
2835
|
-
params.logger.debug?.(`[agenr] before_prompt_build: store nudge skipped for ${sessionContext} reason=disabled`);
|
|
2836
|
-
return void 0;
|
|
2837
|
-
}
|
|
2838
|
-
const midSessionTracker = params.midSessionTracker ?? createMidSessionTracker();
|
|
2839
|
-
const state = midSessionTracker.recordTurn(ctx.sessionId, ctx.sessionKey);
|
|
2840
|
-
if (!state) {
|
|
2841
|
-
params.logger.debug?.(`[agenr] before_prompt_build: store nudge skipped for ${sessionContext} reason=no_session_identity`);
|
|
2842
|
-
return void 0;
|
|
2843
|
-
}
|
|
2844
|
-
const gapSinceSuccessfulStore = state.turnCount - state.lastSuccessfulStoreTurn;
|
|
2845
|
-
const gapSinceMemoryAction = state.turnCount - state.lastMemoryActionTurn;
|
|
2846
|
-
params.logger.debug?.(
|
|
2847
|
-
`[agenr] before_prompt_build: store nudge check for ${sessionContext} gapSinceSuccessfulStore=${gapSinceSuccessfulStore} gapSinceMemoryAction=${gapSinceMemoryAction} nudgeCount=${state.nudgeCount} maxPerSession=${storeNudgeConfig.maxPerSession}`
|
|
2848
|
-
);
|
|
2849
|
-
if (gapSinceSuccessfulStore < storeNudgeConfig.threshold || gapSinceMemoryAction < storeNudgeConfig.threshold) {
|
|
2850
|
-
params.logger.debug?.(`[agenr] before_prompt_build: store nudge skipped for ${sessionContext} reason=cooldown`);
|
|
2851
|
-
return void 0;
|
|
2852
|
-
}
|
|
2853
|
-
if (state.nudgeCount >= storeNudgeConfig.maxPerSession) {
|
|
2854
|
-
params.logger.debug?.(`[agenr] before_prompt_build: store nudge skipped for ${sessionContext} reason=max_reached`);
|
|
2855
|
-
return void 0;
|
|
2856
|
-
}
|
|
2857
|
-
state.nudgeCount += 1;
|
|
2858
|
-
state.lastSuccessfulStoreTurn = state.turnCount;
|
|
2859
|
-
const prependContext = buildStoreNudgeMessage(state, storeNudgeConfig.maxPerSession);
|
|
2860
|
-
params.logger.info(`[agenr] store nudge injected for ${sessionContext} ordinal=${state.nudgeCount} turn=${state.turnCount} gap=${gapSinceSuccessfulStore}`);
|
|
2861
|
-
return { prependContext };
|
|
2862
|
-
}
|
|
2863
|
-
function formatEntryRefs(entries) {
|
|
2864
|
-
return entries.length === 0 ? "none" : entries.map((entry) => `${entry.subject} [${entry.id}]`).join(", ");
|
|
2865
|
-
}
|
|
2866
|
-
function formatBeforeTurnDiagnosticsForLog(patch) {
|
|
2867
|
-
return JSON.stringify({
|
|
2868
|
-
query: truncateForLog(patch.diagnostics.query, 160),
|
|
2869
|
-
queryPolicy: patch.diagnostics.queryPolicy,
|
|
2870
|
-
queryVariants: patch.diagnostics.queryVariants.map((variant) => ({
|
|
2871
|
-
kind: variant.kind,
|
|
2872
|
-
query: truncateForLog(variant.query, 120),
|
|
2873
|
-
candidateCount: variant.candidateCount,
|
|
2874
|
-
selected: variant.selected
|
|
2875
|
-
})),
|
|
2876
|
-
turnSignalLabels: patch.diagnostics.turnSignalLabels,
|
|
2877
|
-
suppressedTurnCategory: patch.diagnostics.suppressedTurnCategory,
|
|
2878
|
-
durableRecallCandidateCount: patch.diagnostics.durableRecallCandidateCount,
|
|
2879
|
-
procedureCandidateCount: patch.diagnostics.procedureCandidateCount,
|
|
2880
|
-
directness: patch.diagnostics.directness ? {
|
|
2881
|
-
queryKind: patch.diagnostics.directness.queryKind,
|
|
2882
|
-
entity: patch.diagnostics.directness.entity,
|
|
2883
|
-
decision: patch.diagnostics.directness.decision,
|
|
2884
|
-
winnerEntryId: patch.diagnostics.directness.winnerEntryId,
|
|
2885
|
-
runnerUpEntryId: patch.diagnostics.directness.runnerUpEntryId,
|
|
2886
|
-
winnerGap: patch.diagnostics.directness.winnerGap,
|
|
2887
|
-
reason: truncateForLog(patch.diagnostics.directness.reason, 180),
|
|
2888
|
-
candidates: patch.diagnostics.directness.candidates.map((candidate) => ({
|
|
2889
|
-
entryId: candidate.entryId,
|
|
2890
|
-
baseRank: candidate.baseRank,
|
|
2891
|
-
baseScore: candidate.baseScore,
|
|
2892
|
-
directnessDelta: candidate.directnessDelta,
|
|
2893
|
-
adjustedScore: candidate.adjustedScore,
|
|
2894
|
-
signals: candidate.signals
|
|
2895
|
-
}))
|
|
2896
|
-
} : void 0,
|
|
2897
|
-
abstained: patch.diagnostics.abstained,
|
|
2898
|
-
abstentionReasons: patch.diagnostics.abstentionReasons.map((reason) => truncateForLog(reason, 180)),
|
|
2899
|
-
notices: patch.diagnostics.notices.map((notice) => truncateForLog(notice, 180)),
|
|
2900
|
-
selectedEntries: patch.durableMemory.map((item) => ({
|
|
2901
|
-
id: item.entry.id,
|
|
2902
|
-
subject: truncateForLog(item.entry.subject, 80),
|
|
2903
|
-
score: Number(item.score.toFixed(3))
|
|
2904
|
-
})),
|
|
2905
|
-
procedure: patch.procedure ? {
|
|
2906
|
-
procedureKey: patch.procedure.procedure.procedure_key,
|
|
2907
|
-
score: Number(patch.procedure.score.toFixed(3))
|
|
2908
|
-
} : void 0
|
|
2909
|
-
});
|
|
2910
|
-
}
|
|
2911
|
-
function truncateForLog(value, maxChars) {
|
|
2912
|
-
if (typeof value !== "string") {
|
|
2913
|
-
return void 0;
|
|
2914
|
-
}
|
|
2915
|
-
const normalized = value.replace(/\s+/gu, " ").trim();
|
|
2916
|
-
if (normalized.length === 0) {
|
|
2917
|
-
return void 0;
|
|
2918
|
-
}
|
|
2919
|
-
return normalized.length <= maxChars ? normalized : `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
2920
|
-
}
|
|
2921
|
-
|
|
2922
|
-
// src/adapters/openclaw/memory/flush-plan.ts
|
|
2923
|
-
function buildAgenrMemoryFlushPlan(_params, logger) {
|
|
2924
|
-
logger.debug?.("[agenr] flush-plan: pass-through (no custom flush)");
|
|
2925
|
-
return null;
|
|
2926
|
-
}
|
|
2927
|
-
|
|
2928
|
-
// src/adapters/openclaw/memory/runtime.ts
|
|
2929
|
-
function createAgenrMemoryRuntime(servicesPromise) {
|
|
2930
|
-
return {
|
|
2931
|
-
async getMemorySearchManager() {
|
|
2932
|
-
try {
|
|
2933
|
-
const services = await servicesPromise;
|
|
2934
|
-
const snapshot = await services.memory.getMemoryStatusSnapshot();
|
|
2935
|
-
const vectorAvailable = await services.memory.probeVectorAvailability();
|
|
2936
|
-
const status = {
|
|
2937
|
-
backend: "builtin",
|
|
2938
|
-
provider: "agenr",
|
|
2939
|
-
model: services.embeddingStatus.model,
|
|
2940
|
-
dbPath: services.config.dbPath,
|
|
2941
|
-
files: snapshot.sourceFiles,
|
|
2942
|
-
chunks: snapshot.activeEntries,
|
|
2943
|
-
vector: {
|
|
2944
|
-
enabled: true,
|
|
2945
|
-
available: vectorAvailable,
|
|
2946
|
-
dims: EMBEDDING_DIMENSIONS
|
|
2947
|
-
},
|
|
2948
|
-
custom: {
|
|
2949
|
-
activeEntries: snapshot.activeEntries,
|
|
2950
|
-
coreEntries: snapshot.coreEntries,
|
|
2951
|
-
sourceFiles: snapshot.sourceFiles
|
|
2952
|
-
}
|
|
2953
|
-
};
|
|
2954
|
-
return {
|
|
2955
|
-
manager: {
|
|
2956
|
-
async search() {
|
|
2957
|
-
return [];
|
|
2958
|
-
},
|
|
2959
|
-
async readFile({ relPath }) {
|
|
2960
|
-
throw new Error(`[agenr] memory file reads are not supported for "${relPath}"`);
|
|
2961
|
-
},
|
|
2962
|
-
status() {
|
|
2963
|
-
return status;
|
|
2964
|
-
},
|
|
2965
|
-
async probeEmbeddingAvailability() {
|
|
2966
|
-
return {
|
|
2967
|
-
ok: services.embeddingStatus.available,
|
|
2968
|
-
...services.embeddingStatus.error ? { error: services.embeddingStatus.error } : {}
|
|
2969
|
-
};
|
|
2970
|
-
},
|
|
2971
|
-
async probeVectorAvailability() {
|
|
2972
|
-
return vectorAvailable;
|
|
2973
|
-
},
|
|
2974
|
-
async sync() {
|
|
2975
|
-
return;
|
|
2976
|
-
}
|
|
2977
|
-
}
|
|
2978
|
-
};
|
|
2979
|
-
} catch (error) {
|
|
2980
|
-
return {
|
|
2981
|
-
manager: null,
|
|
2982
|
-
error: `[agenr] memory runtime unavailable: ${formatErrorMessage2(error)}`
|
|
2983
|
-
};
|
|
2984
|
-
}
|
|
2985
|
-
},
|
|
2986
|
-
resolveMemoryBackendConfig() {
|
|
2987
|
-
return { backend: "builtin" };
|
|
2988
|
-
},
|
|
2989
|
-
async closeAllMemorySearchManagers() {
|
|
2990
|
-
try {
|
|
2991
|
-
const services = await servicesPromise;
|
|
2992
|
-
await services.close();
|
|
2993
|
-
} catch {
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
|
-
};
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
// src/app/openclaw/runtime.ts
|
|
3000
|
-
import path6 from "path";
|
|
3001
|
-
async function createAgenrOpenClawServices(config, options) {
|
|
3002
|
-
const debugSink = createDebugSink(options.openClaw, config);
|
|
3003
|
-
return composeHostPluginServices({
|
|
3004
|
-
config,
|
|
3005
|
-
resolvePath: options.resolvePath,
|
|
3006
|
-
readSlotPolicies: (hostConfig) => hostConfig.memoryPolicy?.slotPolicies,
|
|
3007
|
-
resolveClaimExtraction: ({ agenrConfig, hostConfig }) => buildClaimExtractionRuntime(
|
|
3008
|
-
agenrConfig,
|
|
3009
|
-
() => createOpenClawLlmClient(options.openClaw, hostConfig.claimExtractionModel, "claim extraction model override")
|
|
3010
|
-
),
|
|
3011
|
-
onBeforeClose: () => debugSink.close(),
|
|
3012
|
-
extend: ({ resolvedConfig, agenrConfig, runtimeServices }) => ({
|
|
3013
|
-
...runtimeServices,
|
|
3014
|
-
openClaw: options.openClaw,
|
|
3015
|
-
config: resolvedConfig,
|
|
3016
|
-
pluginConfig: config,
|
|
3017
|
-
agenrConfig,
|
|
3018
|
-
debugSink
|
|
3019
|
-
})
|
|
3020
|
-
});
|
|
3021
|
-
}
|
|
3022
|
-
function createDebugSink(openClaw, pluginConfig) {
|
|
3023
|
-
const resolved = resolveDebugConfig(pluginConfig.debug);
|
|
3024
|
-
if (!resolved.enabled) {
|
|
3025
|
-
return createNoopAgenrDebugSink();
|
|
3026
|
-
}
|
|
3027
|
-
const withLogPath = ensureDebugLogPath(resolved, openClaw);
|
|
3028
|
-
return createAgenrDebugSink(withLogPath);
|
|
3029
|
-
}
|
|
3030
|
-
function ensureDebugLogPath(resolved, openClaw) {
|
|
3031
|
-
if (resolved.logPath) {
|
|
3032
|
-
return resolved;
|
|
3033
|
-
}
|
|
3034
|
-
try {
|
|
3035
|
-
const stateDir = openClaw.runtime.state.resolveStateDir(process.env);
|
|
3036
|
-
return {
|
|
3037
|
-
...resolved,
|
|
3038
|
-
logPath: path6.join(stateDir, "agenr", "logs", "debug.jsonl")
|
|
3039
|
-
};
|
|
3040
|
-
} catch {
|
|
3041
|
-
return { ...resolved, enabled: false };
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
3044
|
-
|
|
3045
|
-
// src/adapters/openclaw/index.ts
|
|
3046
|
-
var openclaw_default = definePluginEntry({
|
|
3047
|
-
id: "agenr",
|
|
3048
|
-
name: "agenr",
|
|
3049
|
-
description: "agenr memory plugin for OpenClaw",
|
|
3050
|
-
kind: "memory",
|
|
3051
|
-
configSchema: createAgenrOpenClawPluginConfigSchema(),
|
|
3052
|
-
register(api) {
|
|
3053
|
-
const sessionStartTracker = createSessionStartTracker();
|
|
3054
|
-
const midSessionTracker = createMidSessionTracker();
|
|
3055
|
-
const pluginConfig = coerceAgenrOpenClawPluginConfig(api.pluginConfig);
|
|
3056
|
-
const storeNudgeConfig = resolveStoreNudgeConfig(pluginConfig.storeNudge);
|
|
3057
|
-
const servicesPromise = createAgenrOpenClawServices(pluginConfig, {
|
|
3058
|
-
openClaw: {
|
|
3059
|
-
config: api.config,
|
|
3060
|
-
runtime: {
|
|
3061
|
-
agent: api.runtime.agent,
|
|
3062
|
-
state: api.runtime.state,
|
|
3063
|
-
modelAuth: api.runtime.modelAuth
|
|
3064
|
-
}
|
|
3065
|
-
},
|
|
3066
|
-
resolvePath: api.resolvePath
|
|
3067
|
-
});
|
|
3068
|
-
void servicesPromise.catch((error) => {
|
|
3069
|
-
api.logger.error(`[agenr] startup failed: ${formatErrorMessage2(error)}`);
|
|
3070
|
-
});
|
|
3071
|
-
api.registerMemoryCapability({
|
|
3072
|
-
promptBuilder: buildAgenrMemoryPromptSection,
|
|
3073
|
-
flushPlanResolver: (params) => buildAgenrMemoryFlushPlan(params, api.logger),
|
|
3074
|
-
runtime: createAgenrMemoryRuntime(servicesPromise)
|
|
3075
|
-
});
|
|
3076
|
-
registerAgenrOpenClawTools(api, servicesPromise, api.logger);
|
|
3077
|
-
api.on(
|
|
3078
|
-
"before_prompt_build",
|
|
3079
|
-
(event, ctx) => handleAgenrBeforePromptBuild(event, ctx, {
|
|
3080
|
-
logger: api.logger,
|
|
3081
|
-
servicesPromise,
|
|
3082
|
-
tracker: sessionStartTracker,
|
|
3083
|
-
midSessionTracker,
|
|
3084
|
-
storeNudgeConfig
|
|
3085
|
-
})
|
|
3086
|
-
);
|
|
3087
|
-
api.on("session_start", (event) => {
|
|
3088
|
-
sessionStartTracker.rememberSessionStart(event.sessionId, event.sessionKey, event.resumedFrom);
|
|
3089
|
-
});
|
|
3090
|
-
api.on("after_tool_call", (event, ctx) => {
|
|
3091
|
-
handleAgenrAfterToolCall(event, ctx, {
|
|
3092
|
-
logger: api.logger,
|
|
3093
|
-
midSessionTracker
|
|
3094
|
-
});
|
|
3095
|
-
});
|
|
3096
|
-
api.on("session_end", (event) => {
|
|
3097
|
-
midSessionTracker.clear(event.sessionId, event.sessionKey);
|
|
3098
|
-
});
|
|
3099
|
-
api.on("gateway_stop", async () => {
|
|
3100
|
-
try {
|
|
3101
|
-
const services = await servicesPromise;
|
|
3102
|
-
await services.close();
|
|
3103
|
-
} catch {
|
|
3104
|
-
}
|
|
3105
|
-
});
|
|
3106
|
-
}
|
|
3107
|
-
});
|
|
3108
|
-
export {
|
|
3109
|
-
OpenClawTranscriptParser,
|
|
3110
|
-
openclaw_default as default,
|
|
3111
|
-
openClawTranscriptParser
|
|
3112
|
-
};
|