@remnic/plugin-openclaw 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +845 -74
- package/openclaw.plugin.json +10 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1906,6 +1906,8 @@ function parseConfig(raw) {
|
|
|
1906
1906
|
lcmRecallBudgetShare: typeof cfg.lcmRecallBudgetShare === "number" ? Math.max(0, Math.min(1, cfg.lcmRecallBudgetShare)) : 0.15,
|
|
1907
1907
|
lcmDeterministicMaxTokens: typeof cfg.lcmDeterministicMaxTokens === "number" ? Math.max(64, Math.floor(cfg.lcmDeterministicMaxTokens)) : 512,
|
|
1908
1908
|
lcmArchiveRetentionDays: typeof cfg.lcmArchiveRetentionDays === "number" ? Math.max(1, Math.floor(cfg.lcmArchiveRetentionDays)) : 90,
|
|
1909
|
+
messagePartsEnabled: coerceBooleanLike(cfg.messagePartsEnabled) === true,
|
|
1910
|
+
messagePartsRecallMaxResults: typeof cfg.messagePartsRecallMaxResults === "number" ? Math.max(0, Math.floor(cfg.messagePartsRecallMaxResults)) : 6,
|
|
1909
1911
|
// v9.1 Parallel Specialized Retrieval
|
|
1910
1912
|
parallelRetrievalEnabled: cfg.parallelRetrievalEnabled === true,
|
|
1911
1913
|
parallelAgentWeights: (() => {
|
|
@@ -19349,7 +19351,9 @@ var RECALL_XRAY_SERVED_BY_VALUES = [
|
|
|
19349
19351
|
"graph",
|
|
19350
19352
|
"recent-scan",
|
|
19351
19353
|
"procedural",
|
|
19352
|
-
"review-context"
|
|
19354
|
+
"review-context",
|
|
19355
|
+
"lcm-file-parts",
|
|
19356
|
+
"lcm-tool-parts"
|
|
19353
19357
|
];
|
|
19354
19358
|
function isRecallXrayServedBy(value) {
|
|
19355
19359
|
return typeof value === "string" && RECALL_XRAY_SERVED_BY_VALUES.includes(value);
|
|
@@ -26180,7 +26184,7 @@ function validateReplayTurn(turn, index) {
|
|
|
26180
26184
|
// ../remnic-core/src/lcm/schema.ts
|
|
26181
26185
|
import path37 from "path";
|
|
26182
26186
|
import { mkdir as mkdir26 } from "fs/promises";
|
|
26183
|
-
var LCM_SCHEMA_VERSION =
|
|
26187
|
+
var LCM_SCHEMA_VERSION = 2;
|
|
26184
26188
|
function openLcmDatabase(memoryDir) {
|
|
26185
26189
|
const dbPath = path37.join(memoryDir, "state", "lcm.sqlite");
|
|
26186
26190
|
const db = openBetterSqlite3(dbPath);
|
|
@@ -26226,6 +26230,23 @@ function createTables(db) {
|
|
|
26226
26230
|
CREATE INDEX IF NOT EXISTS idx_lcm_messages_session
|
|
26227
26231
|
ON lcm_messages(session_id, turn_index);
|
|
26228
26232
|
|
|
26233
|
+
CREATE TABLE IF NOT EXISTS lcm_message_parts (
|
|
26234
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
26235
|
+
message_id INTEGER NOT NULL REFERENCES lcm_messages(id) ON DELETE CASCADE,
|
|
26236
|
+
ordinal INTEGER NOT NULL,
|
|
26237
|
+
kind TEXT NOT NULL,
|
|
26238
|
+
payload TEXT NOT NULL,
|
|
26239
|
+
tool_name TEXT,
|
|
26240
|
+
file_path TEXT,
|
|
26241
|
+
created_at TEXT NOT NULL
|
|
26242
|
+
);
|
|
26243
|
+
CREATE INDEX IF NOT EXISTS idx_lcm_message_parts_msg
|
|
26244
|
+
ON lcm_message_parts(message_id, ordinal);
|
|
26245
|
+
CREATE INDEX IF NOT EXISTS idx_lcm_message_parts_tool
|
|
26246
|
+
ON lcm_message_parts(tool_name);
|
|
26247
|
+
CREATE INDEX IF NOT EXISTS idx_lcm_message_parts_file
|
|
26248
|
+
ON lcm_message_parts(file_path);
|
|
26249
|
+
|
|
26229
26250
|
CREATE TABLE IF NOT EXISTS lcm_summary_nodes (
|
|
26230
26251
|
id TEXT PRIMARY KEY,
|
|
26231
26252
|
session_id TEXT NOT NULL,
|
|
@@ -26278,6 +26299,423 @@ function createTables(db) {
|
|
|
26278
26299
|
);
|
|
26279
26300
|
}
|
|
26280
26301
|
|
|
26302
|
+
// ../remnic-core/src/message-parts/index.ts
|
|
26303
|
+
var LCM_MESSAGE_PART_KINDS = [
|
|
26304
|
+
"text",
|
|
26305
|
+
"tool_call",
|
|
26306
|
+
"tool_result",
|
|
26307
|
+
"patch",
|
|
26308
|
+
"file_read",
|
|
26309
|
+
"file_write",
|
|
26310
|
+
"step_start",
|
|
26311
|
+
"step_finish",
|
|
26312
|
+
"snapshot",
|
|
26313
|
+
"retry"
|
|
26314
|
+
];
|
|
26315
|
+
var SECRET_KEY_RE = /(api[_-]?key|authorization|bearer|credential|password|secret|token)/i;
|
|
26316
|
+
var MAX_PAYLOAD_STRING = 8e3;
|
|
26317
|
+
var MAX_FILE_SCAN_CHARS = 2e4;
|
|
26318
|
+
function isLcmMessagePartKind(value) {
|
|
26319
|
+
return typeof value === "string" && LCM_MESSAGE_PART_KINDS.includes(value);
|
|
26320
|
+
}
|
|
26321
|
+
function parseMessageParts(input, options = {}) {
|
|
26322
|
+
const explicit = normalizeExplicitParts(input);
|
|
26323
|
+
if (explicit.length > 0) return explicit;
|
|
26324
|
+
const format = options.sourceFormat ?? inferSourceFormat(input);
|
|
26325
|
+
switch (format) {
|
|
26326
|
+
case "openai":
|
|
26327
|
+
return withRenderedFallback(parseOpenAiMessageParts(input, options), options);
|
|
26328
|
+
case "anthropic":
|
|
26329
|
+
return withRenderedFallback(parseAnthropicMessageParts(input, options), options);
|
|
26330
|
+
case "openclaw":
|
|
26331
|
+
return withRenderedFallback(parseOpenClawMessageParts(input, options), options);
|
|
26332
|
+
case "lossless-claw":
|
|
26333
|
+
case "remnic":
|
|
26334
|
+
return withRenderedFallback(normalizeExplicitParts(input), options);
|
|
26335
|
+
default:
|
|
26336
|
+
return renderedFallbackParts(options);
|
|
26337
|
+
}
|
|
26338
|
+
}
|
|
26339
|
+
function normalizeExplicitParts(input) {
|
|
26340
|
+
const rawParts = pickArray(input, "parts") ?? pickArray(input, "message_parts");
|
|
26341
|
+
if (!rawParts) return [];
|
|
26342
|
+
const parts = [];
|
|
26343
|
+
rawParts.forEach((raw, index) => {
|
|
26344
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
26345
|
+
const obj = raw;
|
|
26346
|
+
const kind = normalizeKind(obj.kind ?? obj.type);
|
|
26347
|
+
if (!kind) return;
|
|
26348
|
+
const payload = obj.payload && typeof obj.payload === "object" && !Array.isArray(obj.payload) ? obj.payload : { value: sanitizePayload(obj) };
|
|
26349
|
+
const toolName = asNonEmptyString(obj.toolName ?? obj.tool_name ?? obj.name);
|
|
26350
|
+
const filePath = asNonEmptyString(obj.filePath ?? obj.file_path ?? obj.path);
|
|
26351
|
+
const ordinal = typeof obj.ordinal === "number" && Number.isInteger(obj.ordinal) ? Math.max(0, obj.ordinal) : index;
|
|
26352
|
+
parts.push({
|
|
26353
|
+
ordinal,
|
|
26354
|
+
kind,
|
|
26355
|
+
payload: sanitizePayload(payload),
|
|
26356
|
+
toolName,
|
|
26357
|
+
filePath,
|
|
26358
|
+
createdAt: asNonEmptyString(obj.createdAt ?? obj.created_at)
|
|
26359
|
+
});
|
|
26360
|
+
});
|
|
26361
|
+
return parts;
|
|
26362
|
+
}
|
|
26363
|
+
function parseOpenAiMessageParts(input, _options = {}) {
|
|
26364
|
+
const items = gatherOpenAiItems(input);
|
|
26365
|
+
const parts = [];
|
|
26366
|
+
for (const item of items) {
|
|
26367
|
+
const type = asNonEmptyString(item.type) ?? asNonEmptyString(item.kind);
|
|
26368
|
+
if (!type) continue;
|
|
26369
|
+
if (isOpenAiContentBlock(item)) {
|
|
26370
|
+
const text = asNonEmptyString(item.text ?? item.content);
|
|
26371
|
+
if (text) parts.push(makePart("text", { type, text }, { filePath: firstFilePath(text) }));
|
|
26372
|
+
continue;
|
|
26373
|
+
}
|
|
26374
|
+
if (type === "message") {
|
|
26375
|
+
for (const block of gatherContentBlocks(item.content)) {
|
|
26376
|
+
const text = asNonEmptyString(block.text ?? block.content);
|
|
26377
|
+
if (text) parts.push(makePart("text", { type, text }, { filePath: firstFilePath(text) }));
|
|
26378
|
+
}
|
|
26379
|
+
continue;
|
|
26380
|
+
}
|
|
26381
|
+
if (type === "function_call") {
|
|
26382
|
+
const toolName = asNonEmptyString(item.name ?? item.tool_name);
|
|
26383
|
+
const payload = {
|
|
26384
|
+
id: item.id ?? item.call_id,
|
|
26385
|
+
name: toolName,
|
|
26386
|
+
arguments: parseMaybeJson(item.arguments)
|
|
26387
|
+
};
|
|
26388
|
+
parts.push(classifyToolPart(toolName, payload));
|
|
26389
|
+
continue;
|
|
26390
|
+
}
|
|
26391
|
+
if (type === "function_call_output") {
|
|
26392
|
+
const output = asNonEmptyString(item.output) ?? JSON.stringify(sanitizePayload(item.output ?? item));
|
|
26393
|
+
parts.push(makePart("tool_result", { id: item.id ?? item.call_id, output }, {
|
|
26394
|
+
filePath: firstFilePath(output)
|
|
26395
|
+
}));
|
|
26396
|
+
continue;
|
|
26397
|
+
}
|
|
26398
|
+
if (type === "reasoning") {
|
|
26399
|
+
parts.push(makePart("step_start", { type, summary: sanitizePayload(item.summary ?? item) }));
|
|
26400
|
+
continue;
|
|
26401
|
+
}
|
|
26402
|
+
if (type === "retry") {
|
|
26403
|
+
parts.push(makePart("retry", { type, item: sanitizePayload(item) }));
|
|
26404
|
+
}
|
|
26405
|
+
}
|
|
26406
|
+
return withOrdinals(parts);
|
|
26407
|
+
}
|
|
26408
|
+
function parseAnthropicMessageParts(input, _options = {}) {
|
|
26409
|
+
const blocks = gatherContentBlocks(
|
|
26410
|
+
Array.isArray(input) ? input : input && typeof input === "object" ? input.content : input
|
|
26411
|
+
);
|
|
26412
|
+
const parts = [];
|
|
26413
|
+
for (const block of blocks) {
|
|
26414
|
+
const type = asNonEmptyString(block.type);
|
|
26415
|
+
if (type === "text") {
|
|
26416
|
+
const text = asNonEmptyString(block.text);
|
|
26417
|
+
if (text) parts.push(makePart("text", { type, text }, { filePath: firstFilePath(text) }));
|
|
26418
|
+
continue;
|
|
26419
|
+
}
|
|
26420
|
+
if (type === "tool_use") {
|
|
26421
|
+
const toolName = asNonEmptyString(block.name);
|
|
26422
|
+
parts.push(classifyToolPart(toolName, {
|
|
26423
|
+
id: block.id,
|
|
26424
|
+
name: toolName,
|
|
26425
|
+
input: sanitizePayload(block.input)
|
|
26426
|
+
}));
|
|
26427
|
+
continue;
|
|
26428
|
+
}
|
|
26429
|
+
if (type === "tool_result") {
|
|
26430
|
+
const content = block.content;
|
|
26431
|
+
const rendered = renderUnknownContent(content);
|
|
26432
|
+
parts.push(makePart("tool_result", { id: block.tool_use_id, content: sanitizePayload(content) }, {
|
|
26433
|
+
filePath: firstFilePath(rendered)
|
|
26434
|
+
}));
|
|
26435
|
+
continue;
|
|
26436
|
+
}
|
|
26437
|
+
if (type === "thinking") {
|
|
26438
|
+
parts.push(makePart("step_start", {
|
|
26439
|
+
type,
|
|
26440
|
+
thinking: truncateString(asNonEmptyString(block.thinking) ?? ""),
|
|
26441
|
+
signature: asNonEmptyString(block.signature)
|
|
26442
|
+
}));
|
|
26443
|
+
continue;
|
|
26444
|
+
}
|
|
26445
|
+
if (type === "redacted_thinking") {
|
|
26446
|
+
parts.push(makePart("step_finish", { type }));
|
|
26447
|
+
}
|
|
26448
|
+
}
|
|
26449
|
+
return withOrdinals(parts);
|
|
26450
|
+
}
|
|
26451
|
+
function parseOpenClawMessageParts(input, options = {}) {
|
|
26452
|
+
const explicit = normalizeExplicitParts(input);
|
|
26453
|
+
if (explicit.length > 0) return explicit;
|
|
26454
|
+
if (!input || typeof input !== "object") return [];
|
|
26455
|
+
const obj = input;
|
|
26456
|
+
const content = obj.content;
|
|
26457
|
+
if (Array.isArray(content)) {
|
|
26458
|
+
const hasOpenAiBlocks = content.some(isOpenAiContentBlock);
|
|
26459
|
+
if (hasOpenAiBlocks) return parseOpenAiMessageParts(content, options);
|
|
26460
|
+
const hasAnthropicBlocks = content.some(
|
|
26461
|
+
(block) => block && typeof block === "object" && typeof block.type === "string"
|
|
26462
|
+
);
|
|
26463
|
+
if (hasAnthropicBlocks) return parseAnthropicMessageParts({ content }, options);
|
|
26464
|
+
}
|
|
26465
|
+
const toolName = asNonEmptyString(obj.toolName ?? obj.tool_name ?? obj.name);
|
|
26466
|
+
if (toolName) {
|
|
26467
|
+
return withOrdinals([
|
|
26468
|
+
classifyToolPart(toolName, {
|
|
26469
|
+
name: toolName,
|
|
26470
|
+
input: sanitizePayload(obj.input ?? obj.arguments ?? obj.params),
|
|
26471
|
+
output: sanitizePayload(obj.output ?? obj.result)
|
|
26472
|
+
})
|
|
26473
|
+
]);
|
|
26474
|
+
}
|
|
26475
|
+
const rendered = options.renderedContent ?? asNonEmptyString(obj.content);
|
|
26476
|
+
return rendered ? withOrdinals(partsFromRenderedText(rendered)) : [];
|
|
26477
|
+
}
|
|
26478
|
+
function partsFromRenderedText(text) {
|
|
26479
|
+
if (text.includes("*** Begin Patch")) {
|
|
26480
|
+
const paths2 = extractFilePaths(text);
|
|
26481
|
+
const patchPaths = extractPatchPaths(text);
|
|
26482
|
+
return withOrdinals((patchPaths.length > 0 ? patchPaths : paths2).map(
|
|
26483
|
+
(filePath) => makePart("patch", { text: truncateString(text) }, { filePath })
|
|
26484
|
+
));
|
|
26485
|
+
}
|
|
26486
|
+
const paths = extractFilePaths(text);
|
|
26487
|
+
if (paths.length === 0) return [];
|
|
26488
|
+
return withOrdinals(paths.map(
|
|
26489
|
+
(filePath) => makePart("file_read", { text: truncateString(text) }, { filePath })
|
|
26490
|
+
));
|
|
26491
|
+
}
|
|
26492
|
+
function inferSourceFormat(input) {
|
|
26493
|
+
if (input && typeof input === "object") {
|
|
26494
|
+
const obj = input;
|
|
26495
|
+
const explicit = asNonEmptyString(obj.sourceFormat ?? obj.source_format);
|
|
26496
|
+
if (explicit === "openai" || explicit === "anthropic" || explicit === "openclaw" || explicit === "lossless-claw" || explicit === "remnic") {
|
|
26497
|
+
return explicit;
|
|
26498
|
+
}
|
|
26499
|
+
if (Array.isArray(obj.output)) return "openai";
|
|
26500
|
+
if (isOpenAiResponseItem(obj)) return "openai";
|
|
26501
|
+
if (Array.isArray(obj.content) && obj.content.some(isOpenAiContentBlock)) return "openai";
|
|
26502
|
+
if (Array.isArray(obj.content)) return "anthropic";
|
|
26503
|
+
}
|
|
26504
|
+
if (Array.isArray(input)) {
|
|
26505
|
+
return input.some(
|
|
26506
|
+
(item) => isRecord3(item) && (isOpenAiResponseItem(item) || isOpenAiContentBlock(item))
|
|
26507
|
+
) ? "openai" : "anthropic";
|
|
26508
|
+
}
|
|
26509
|
+
return void 0;
|
|
26510
|
+
}
|
|
26511
|
+
function isOpenAiResponseItem(obj) {
|
|
26512
|
+
const type = asNonEmptyString(obj.type ?? obj.kind);
|
|
26513
|
+
return type === "message" || type === "function_call" || type === "function_call_output" || type === "reasoning" || type === "retry";
|
|
26514
|
+
}
|
|
26515
|
+
function isOpenAiContentBlock(value) {
|
|
26516
|
+
if (!isRecord3(value)) return false;
|
|
26517
|
+
const type = asNonEmptyString(value.type);
|
|
26518
|
+
return type === "input_text" || type === "output_text" || type === "input_image" || type === "input_file" || type === "refusal";
|
|
26519
|
+
}
|
|
26520
|
+
function gatherOpenAiItems(input) {
|
|
26521
|
+
if (Array.isArray(input)) return input.filter(isRecord3);
|
|
26522
|
+
if (!isRecord3(input)) return [];
|
|
26523
|
+
if (Array.isArray(input.output)) return input.output.filter(isRecord3);
|
|
26524
|
+
if (Array.isArray(input.items)) return input.items.filter(isRecord3);
|
|
26525
|
+
return [input];
|
|
26526
|
+
}
|
|
26527
|
+
function gatherContentBlocks(input) {
|
|
26528
|
+
if (Array.isArray(input)) return input.filter(isRecord3);
|
|
26529
|
+
if (typeof input === "string") return [{ type: "text", text: input }];
|
|
26530
|
+
if (isRecord3(input)) return [input];
|
|
26531
|
+
return [];
|
|
26532
|
+
}
|
|
26533
|
+
function classifyToolPart(toolName, payload) {
|
|
26534
|
+
const normalized = (toolName ?? "").toLowerCase();
|
|
26535
|
+
const rendered = renderUnknownContent(payload);
|
|
26536
|
+
const filePath = firstFilePathFromObject(payload) ?? firstFilePath(rendered) ?? null;
|
|
26537
|
+
if (normalized.includes("apply_patch") || rendered.includes("*** Begin Patch")) {
|
|
26538
|
+
return makePart("patch", payload, { toolName, filePath: filePath ?? extractPatchPaths(rendered)[0] ?? null });
|
|
26539
|
+
}
|
|
26540
|
+
if (/(write|edit|multiedit|create|save)/i.test(normalized)) {
|
|
26541
|
+
return makePart("file_write", payload, { toolName, filePath });
|
|
26542
|
+
}
|
|
26543
|
+
if (/(read|grep|glob|search|list|ls)/i.test(normalized)) {
|
|
26544
|
+
return makePart("file_read", payload, { toolName, filePath });
|
|
26545
|
+
}
|
|
26546
|
+
return makePart("tool_call", payload, { toolName, filePath });
|
|
26547
|
+
}
|
|
26548
|
+
function makePart(kind, payload, options = {}) {
|
|
26549
|
+
return {
|
|
26550
|
+
kind,
|
|
26551
|
+
payload: sanitizePayload(payload),
|
|
26552
|
+
toolName: options.toolName ?? null,
|
|
26553
|
+
filePath: options.filePath ?? null
|
|
26554
|
+
};
|
|
26555
|
+
}
|
|
26556
|
+
function withOrdinals(parts) {
|
|
26557
|
+
return parts.map((part, ordinal) => ({ ...part, ordinal: part.ordinal ?? ordinal }));
|
|
26558
|
+
}
|
|
26559
|
+
function withRenderedFallback(parts, options) {
|
|
26560
|
+
return parts.length > 0 ? parts : renderedFallbackParts(options);
|
|
26561
|
+
}
|
|
26562
|
+
function renderedFallbackParts(options) {
|
|
26563
|
+
const rendered = asNonEmptyString(options.renderedContent);
|
|
26564
|
+
return rendered ? partsFromRenderedText(rendered) : [];
|
|
26565
|
+
}
|
|
26566
|
+
function normalizeKind(value) {
|
|
26567
|
+
if (isLcmMessagePartKind(value)) return value;
|
|
26568
|
+
if (value === "tool_use" || value === "function_call") return "tool_call";
|
|
26569
|
+
if (value === "function_call_output") return "tool_result";
|
|
26570
|
+
if (value === "thinking" || value === "reasoning") return "step_start";
|
|
26571
|
+
return null;
|
|
26572
|
+
}
|
|
26573
|
+
function pickArray(input, key) {
|
|
26574
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
26575
|
+
const value = input[key];
|
|
26576
|
+
return Array.isArray(value) ? value : null;
|
|
26577
|
+
}
|
|
26578
|
+
function asNonEmptyString(value) {
|
|
26579
|
+
if (typeof value !== "string") return null;
|
|
26580
|
+
const trimmed = value.trim();
|
|
26581
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
26582
|
+
}
|
|
26583
|
+
function isRecord3(value) {
|
|
26584
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
26585
|
+
}
|
|
26586
|
+
function parseMaybeJson(value) {
|
|
26587
|
+
if (typeof value !== "string") return sanitizePayload(value);
|
|
26588
|
+
try {
|
|
26589
|
+
return sanitizePayload(JSON.parse(value));
|
|
26590
|
+
} catch {
|
|
26591
|
+
return truncateString(value);
|
|
26592
|
+
}
|
|
26593
|
+
}
|
|
26594
|
+
function sanitizePayload(value, depth = 0) {
|
|
26595
|
+
if (value === null || value === void 0) return value;
|
|
26596
|
+
if (typeof value === "string") return truncateString(value);
|
|
26597
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
26598
|
+
if (Array.isArray(value)) {
|
|
26599
|
+
if (depth >= 4) return "[truncated]";
|
|
26600
|
+
return value.slice(0, 100).map((item) => sanitizePayload(item, depth + 1));
|
|
26601
|
+
}
|
|
26602
|
+
if (typeof value === "object") {
|
|
26603
|
+
if (depth >= 4) return "[truncated]";
|
|
26604
|
+
const out = {};
|
|
26605
|
+
for (const [key, child] of Object.entries(value)) {
|
|
26606
|
+
out[key] = SECRET_KEY_RE.test(key) ? "[redacted]" : sanitizePayload(child, depth + 1);
|
|
26607
|
+
}
|
|
26608
|
+
return out;
|
|
26609
|
+
}
|
|
26610
|
+
return String(value);
|
|
26611
|
+
}
|
|
26612
|
+
function truncateString(value) {
|
|
26613
|
+
return value.length > MAX_PAYLOAD_STRING ? `${value.slice(0, MAX_PAYLOAD_STRING)}...[truncated]` : value;
|
|
26614
|
+
}
|
|
26615
|
+
function renderUnknownContent(value) {
|
|
26616
|
+
if (typeof value === "string") return value;
|
|
26617
|
+
try {
|
|
26618
|
+
return JSON.stringify(value ?? "");
|
|
26619
|
+
} catch {
|
|
26620
|
+
return String(value ?? "");
|
|
26621
|
+
}
|
|
26622
|
+
}
|
|
26623
|
+
function firstFilePathFromObject(value) {
|
|
26624
|
+
if (!isRecord3(value)) return null;
|
|
26625
|
+
const keys = ["file_path", "filePath", "path", "filename", "cwd"];
|
|
26626
|
+
for (const key of keys) {
|
|
26627
|
+
const candidate = asNonEmptyString(value[key]);
|
|
26628
|
+
if (candidate) return candidate;
|
|
26629
|
+
}
|
|
26630
|
+
for (const child of Object.values(value)) {
|
|
26631
|
+
if (typeof child === "string") {
|
|
26632
|
+
const fromText = extractPatchPaths(child)[0] ?? firstFilePath(child);
|
|
26633
|
+
if (fromText) return fromText;
|
|
26634
|
+
}
|
|
26635
|
+
if (isRecord3(child)) {
|
|
26636
|
+
const nested = firstFilePathFromObject(child);
|
|
26637
|
+
if (nested) return nested;
|
|
26638
|
+
}
|
|
26639
|
+
}
|
|
26640
|
+
return null;
|
|
26641
|
+
}
|
|
26642
|
+
function firstFilePath(text) {
|
|
26643
|
+
return extractFilePaths(text)[0] ?? null;
|
|
26644
|
+
}
|
|
26645
|
+
function extractFilePaths(text) {
|
|
26646
|
+
const out = /* @__PURE__ */ new Set();
|
|
26647
|
+
let token = "";
|
|
26648
|
+
const scanLength = Math.min(text.length, MAX_FILE_SCAN_CHARS);
|
|
26649
|
+
for (let index = 0; index <= scanLength; index += 1) {
|
|
26650
|
+
const char = index < scanLength ? text[index] : " ";
|
|
26651
|
+
if (isFilePathTokenSeparator(char)) {
|
|
26652
|
+
addFilePathCandidate(out, token);
|
|
26653
|
+
token = "";
|
|
26654
|
+
continue;
|
|
26655
|
+
}
|
|
26656
|
+
token += char;
|
|
26657
|
+
if (token.length > 512) {
|
|
26658
|
+
addFilePathCandidate(out, token);
|
|
26659
|
+
token = "";
|
|
26660
|
+
}
|
|
26661
|
+
}
|
|
26662
|
+
return [...out].slice(0, 20);
|
|
26663
|
+
}
|
|
26664
|
+
function isFilePathTokenSeparator(char) {
|
|
26665
|
+
return char === " " || char === "\n" || char === "\r" || char === " " || char === '"' || char === "'" || char === "`" || char === "(" || char === ")" || char === "[" || char === "]" || char === "{" || char === "}" || char === "<" || char === ">" || char === ",";
|
|
26666
|
+
}
|
|
26667
|
+
function addFilePathCandidate(out, raw) {
|
|
26668
|
+
const candidate = trimFilePathPunctuation(raw);
|
|
26669
|
+
if (candidate.length === 0 || candidate.includes("://")) return;
|
|
26670
|
+
if (isLikelyFilePath(candidate)) out.add(candidate);
|
|
26671
|
+
}
|
|
26672
|
+
function trimFilePathPunctuation(raw) {
|
|
26673
|
+
let start = 0;
|
|
26674
|
+
let end = raw.length;
|
|
26675
|
+
while (start < end && isLeadingFilePathPunctuation(raw[start])) start += 1;
|
|
26676
|
+
while (end > start && isTrailingFilePathPunctuation(raw[end - 1])) end -= 1;
|
|
26677
|
+
return raw.slice(start, end);
|
|
26678
|
+
}
|
|
26679
|
+
function isLeadingFilePathPunctuation(char) {
|
|
26680
|
+
return char === ":" || char === ";" || char === "!" || char === "?" || char === "|" || char === "*" || char === "=";
|
|
26681
|
+
}
|
|
26682
|
+
function isTrailingFilePathPunctuation(char) {
|
|
26683
|
+
return char === "." || char === ":" || char === ";" || char === "!" || char === "?" || char === "|" || char === "*" || char === "=";
|
|
26684
|
+
}
|
|
26685
|
+
function isLikelyFilePath(value) {
|
|
26686
|
+
if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("~/")) {
|
|
26687
|
+
return hasValidFileExtension(value);
|
|
26688
|
+
}
|
|
26689
|
+
if (value.includes("/")) return hasValidFileExtension(value);
|
|
26690
|
+
return hasValidFileExtension(value);
|
|
26691
|
+
}
|
|
26692
|
+
function hasValidFileExtension(value) {
|
|
26693
|
+
const lastSlash = value.lastIndexOf("/");
|
|
26694
|
+
const basename3 = value.slice(lastSlash + 1);
|
|
26695
|
+
const dot = basename3.lastIndexOf(".");
|
|
26696
|
+
if (dot <= 0 || dot === basename3.length - 1) return false;
|
|
26697
|
+
const ext = basename3.slice(dot + 1);
|
|
26698
|
+
if (ext.length < 1 || ext.length > 12) return false;
|
|
26699
|
+
for (const char of ext) {
|
|
26700
|
+
if (!isFileExtensionChar(char)) return false;
|
|
26701
|
+
}
|
|
26702
|
+
return true;
|
|
26703
|
+
}
|
|
26704
|
+
function isFileExtensionChar(char) {
|
|
26705
|
+
const code = char.charCodeAt(0);
|
|
26706
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || char === "_" || char === "+" || char === "-";
|
|
26707
|
+
}
|
|
26708
|
+
function extractPatchPaths(text) {
|
|
26709
|
+
const out = /* @__PURE__ */ new Set();
|
|
26710
|
+
for (const line of text.split(/\r?\n/)) {
|
|
26711
|
+
const match = line.match(/^\*\*\* (?:Add|Update|Delete) File: (.+)$/);
|
|
26712
|
+
if (match?.[1]) out.add(match[1].trim());
|
|
26713
|
+
const move = line.match(/^\*\*\* Move to: (.+)$/);
|
|
26714
|
+
if (move?.[1]) out.add(move[1].trim());
|
|
26715
|
+
}
|
|
26716
|
+
return [...out].slice(0, 20);
|
|
26717
|
+
}
|
|
26718
|
+
|
|
26281
26719
|
// ../remnic-core/src/lcm/archive.ts
|
|
26282
26720
|
function estimateTokens3(text) {
|
|
26283
26721
|
return Math.ceil(text.length / 4);
|
|
@@ -26288,7 +26726,7 @@ var LcmArchive = class {
|
|
|
26288
26726
|
}
|
|
26289
26727
|
db;
|
|
26290
26728
|
/** Append a message to the archive. Returns the row id. */
|
|
26291
|
-
appendMessage(sessionId, turnIndex, role, content, metadata) {
|
|
26729
|
+
appendMessage(sessionId, turnIndex, role, content, metadata, parts) {
|
|
26292
26730
|
const tokenCount = estimateTokens3(content);
|
|
26293
26731
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
26294
26732
|
const metaJson = metadata ? JSON.stringify(metadata) : null;
|
|
@@ -26299,18 +26737,58 @@ var LcmArchive = class {
|
|
|
26299
26737
|
const result = stmt.run(sessionId, turnIndex, role, content, tokenCount, now, metaJson);
|
|
26300
26738
|
const rowId = Number(result.lastInsertRowid);
|
|
26301
26739
|
this.db.prepare("INSERT INTO lcm_messages_fts (rowid, content) VALUES (?, ?)").run(rowId, content);
|
|
26740
|
+
if (parts && parts.length > 0) {
|
|
26741
|
+
this.insertMessageParts(rowId, parts, now);
|
|
26742
|
+
}
|
|
26302
26743
|
return rowId;
|
|
26303
26744
|
}
|
|
26304
26745
|
/** Append multiple messages in a single transaction. */
|
|
26305
|
-
appendMessages(sessionId, messages) {
|
|
26746
|
+
appendMessages(sessionId, messages, options = {}) {
|
|
26306
26747
|
if (messages.length === 0) return;
|
|
26748
|
+
const captureMessageParts = options.messagePartsEnabled !== false;
|
|
26307
26749
|
const txn = this.db.transaction(() => {
|
|
26308
26750
|
for (const msg of messages) {
|
|
26309
|
-
|
|
26751
|
+
const explicitParts = msg.parts && msg.parts.length > 0 ? msg.parts : void 0;
|
|
26752
|
+
const rawContent = msg.rawContent ?? msg.content;
|
|
26753
|
+
const parts = captureMessageParts ? explicitParts ?? parseMessageParts(rawContent, {
|
|
26754
|
+
sourceFormat: msg.sourceFormat,
|
|
26755
|
+
renderedContent: msg.content
|
|
26756
|
+
}) : void 0;
|
|
26757
|
+
this.appendMessage(
|
|
26758
|
+
sessionId,
|
|
26759
|
+
msg.turnIndex,
|
|
26760
|
+
msg.role,
|
|
26761
|
+
msg.content,
|
|
26762
|
+
msg.metadata,
|
|
26763
|
+
parts
|
|
26764
|
+
);
|
|
26310
26765
|
}
|
|
26311
26766
|
});
|
|
26312
26767
|
txn();
|
|
26313
26768
|
}
|
|
26769
|
+
insertMessageParts(messageId, parts, fallbackCreatedAt) {
|
|
26770
|
+
if (parts.length === 0) return;
|
|
26771
|
+
const stmt = this.db.prepare(`
|
|
26772
|
+
INSERT INTO lcm_message_parts (message_id, ordinal, kind, payload, tool_name, file_path, created_at)
|
|
26773
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
26774
|
+
`);
|
|
26775
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
26776
|
+
const part = parts[index];
|
|
26777
|
+
const rawPart = part;
|
|
26778
|
+
const toolName = part.toolName ?? asNullableString(rawPart.tool_name);
|
|
26779
|
+
const filePath = part.filePath ?? asNullableString(rawPart.file_path);
|
|
26780
|
+
const createdAt = part.createdAt ?? asNullableString(rawPart.created_at);
|
|
26781
|
+
stmt.run(
|
|
26782
|
+
messageId,
|
|
26783
|
+
part.ordinal ?? index,
|
|
26784
|
+
part.kind,
|
|
26785
|
+
JSON.stringify(part.payload ?? {}),
|
|
26786
|
+
toolName ?? null,
|
|
26787
|
+
filePath ?? null,
|
|
26788
|
+
createdAt ?? fallbackCreatedAt
|
|
26789
|
+
);
|
|
26790
|
+
}
|
|
26791
|
+
}
|
|
26314
26792
|
/** Get the highest turn_index for a session, or -1 if none. */
|
|
26315
26793
|
getMaxTurnIndex(sessionId) {
|
|
26316
26794
|
const row = this.db.prepare("SELECT MAX(turn_index) as max_turn FROM lcm_messages WHERE session_id = ?").get(sessionId);
|
|
@@ -26432,6 +26910,55 @@ var LcmArchive = class {
|
|
|
26432
26910
|
return [];
|
|
26433
26911
|
}
|
|
26434
26912
|
}
|
|
26913
|
+
searchStructuredParts(query, limit, sessionId) {
|
|
26914
|
+
const cappedLimit = Math.max(0, Math.min(20, Math.floor(limit)));
|
|
26915
|
+
if (cappedLimit === 0) return [];
|
|
26916
|
+
const fileTerms = extractStructuredFileTerms(query);
|
|
26917
|
+
const toolTerms = extractStructuredToolTerms(query);
|
|
26918
|
+
if (fileTerms.length === 0 && toolTerms.length === 0) return [];
|
|
26919
|
+
const matchWhere = [];
|
|
26920
|
+
const whereParams = [];
|
|
26921
|
+
for (const term of fileTerms) {
|
|
26922
|
+
matchWhere.push("(p.file_path = ? OR p.file_path LIKE ? ESCAPE '\\')");
|
|
26923
|
+
whereParams.push(term, `%${escapeLike(term)}%`);
|
|
26924
|
+
}
|
|
26925
|
+
for (const term of toolTerms) {
|
|
26926
|
+
matchWhere.push("p.tool_name LIKE ? ESCAPE '\\'");
|
|
26927
|
+
whereParams.push(`%${escapeLike(term)}%`);
|
|
26928
|
+
}
|
|
26929
|
+
const where = [`(${matchWhere.join(" OR ")})`];
|
|
26930
|
+
if (sessionId) {
|
|
26931
|
+
where.push("m.session_id = ?");
|
|
26932
|
+
whereParams.push(sessionId);
|
|
26933
|
+
}
|
|
26934
|
+
const exactFileScoreParams = [...fileTerms];
|
|
26935
|
+
const sqlParams = [...exactFileScoreParams, ...whereParams, cappedLimit];
|
|
26936
|
+
const rows = this.db.prepare(`
|
|
26937
|
+
SELECT
|
|
26938
|
+
p.id AS part_id,
|
|
26939
|
+
p.message_id AS message_id,
|
|
26940
|
+
m.turn_index AS turn_index,
|
|
26941
|
+
m.role AS role,
|
|
26942
|
+
m.content AS content,
|
|
26943
|
+
m.session_id AS session_id,
|
|
26944
|
+
p.kind AS kind,
|
|
26945
|
+
p.tool_name AS tool_name,
|
|
26946
|
+
p.file_path AS file_path,
|
|
26947
|
+
p.payload AS payload,
|
|
26948
|
+
CASE
|
|
26949
|
+
WHEN p.file_path IN (${fileTerms.map(() => "?").join(",") || "NULL"}) THEN 3
|
|
26950
|
+
WHEN p.file_path IS NOT NULL THEN 2
|
|
26951
|
+
WHEN p.tool_name IS NOT NULL THEN 1
|
|
26952
|
+
ELSE 0
|
|
26953
|
+
END AS score
|
|
26954
|
+
FROM lcm_message_parts p
|
|
26955
|
+
JOIN lcm_messages m ON m.id = p.message_id
|
|
26956
|
+
WHERE ${where.join(" AND ")}
|
|
26957
|
+
ORDER BY score DESC, m.turn_index DESC, p.ordinal ASC
|
|
26958
|
+
LIMIT ?
|
|
26959
|
+
`).all(...sqlParams);
|
|
26960
|
+
return rows;
|
|
26961
|
+
}
|
|
26435
26962
|
/** Get total message count for a session. */
|
|
26436
26963
|
getMessageCount(sessionId) {
|
|
26437
26964
|
const row = this.db.prepare("SELECT COUNT(*) as cnt FROM lcm_messages WHERE session_id = ?").get(sessionId);
|
|
@@ -26583,6 +27110,72 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
26583
27110
|
"we",
|
|
26584
27111
|
"they"
|
|
26585
27112
|
]);
|
|
27113
|
+
function extractStructuredFileTerms(query) {
|
|
27114
|
+
const terms = /* @__PURE__ */ new Set();
|
|
27115
|
+
for (const raw of splitQueryTerms(query)) {
|
|
27116
|
+
const cleaned = trimStructuredQueryTerm(raw);
|
|
27117
|
+
if (cleaned.includes("/") || hasStructuredFileExtension(cleaned)) {
|
|
27118
|
+
terms.add(cleaned);
|
|
27119
|
+
const basename3 = cleaned.split("/").pop();
|
|
27120
|
+
if (basename3 && basename3 !== cleaned) terms.add(basename3);
|
|
27121
|
+
}
|
|
27122
|
+
}
|
|
27123
|
+
return [...terms].filter((term) => term.length > 1).slice(0, 12);
|
|
27124
|
+
}
|
|
27125
|
+
function splitQueryTerms(query) {
|
|
27126
|
+
const terms = [];
|
|
27127
|
+
let term = "";
|
|
27128
|
+
for (const char of query.slice(0, 2e4)) {
|
|
27129
|
+
if (char === " " || char === "\n" || char === "\r" || char === " ") {
|
|
27130
|
+
if (term.length > 0) terms.push(term);
|
|
27131
|
+
term = "";
|
|
27132
|
+
continue;
|
|
27133
|
+
}
|
|
27134
|
+
term += char;
|
|
27135
|
+
if (term.length > 512) {
|
|
27136
|
+
terms.push(term);
|
|
27137
|
+
term = "";
|
|
27138
|
+
}
|
|
27139
|
+
}
|
|
27140
|
+
if (term.length > 0) terms.push(term);
|
|
27141
|
+
return terms;
|
|
27142
|
+
}
|
|
27143
|
+
function trimStructuredQueryTerm(raw) {
|
|
27144
|
+
const leading = /* @__PURE__ */ new Set(["`", "'", '"', "(", "[", "{"]);
|
|
27145
|
+
const trailing = /* @__PURE__ */ new Set(["`", "'", '"', ",", ".", "?", "!", ":", ";", ")", "]", "}"]);
|
|
27146
|
+
let start = 0;
|
|
27147
|
+
let end = raw.length;
|
|
27148
|
+
while (start < end && leading.has(raw[start])) start += 1;
|
|
27149
|
+
while (end > start && trailing.has(raw[end - 1])) end -= 1;
|
|
27150
|
+
return raw.slice(start, end);
|
|
27151
|
+
}
|
|
27152
|
+
function hasStructuredFileExtension(value) {
|
|
27153
|
+
const slash = value.lastIndexOf("/");
|
|
27154
|
+
const basename3 = value.slice(slash + 1);
|
|
27155
|
+
const dot = basename3.lastIndexOf(".");
|
|
27156
|
+
if (dot <= 0 || dot === basename3.length - 1) return false;
|
|
27157
|
+
const ext = basename3.slice(dot + 1);
|
|
27158
|
+
if (ext.length < 1 || ext.length > 12) return false;
|
|
27159
|
+
for (const char of ext) {
|
|
27160
|
+
const code = char.charCodeAt(0);
|
|
27161
|
+
const valid = code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || char === "_" || char === "+" || char === "-";
|
|
27162
|
+
if (!valid) return false;
|
|
27163
|
+
}
|
|
27164
|
+
return true;
|
|
27165
|
+
}
|
|
27166
|
+
function extractStructuredToolTerms(query) {
|
|
27167
|
+
const lower = query.toLowerCase();
|
|
27168
|
+
if (!/\b(tool|command|invocation|called|used|ran|read|write|patch|edit|grep|search)\b/.test(lower)) {
|
|
27169
|
+
return [];
|
|
27170
|
+
}
|
|
27171
|
+
return query.replace(/[^\w.-]/g, " ").split(/\s+/).filter((term) => term.length > 2 && !STOPWORDS.has(term.toLowerCase())).slice(0, 8);
|
|
27172
|
+
}
|
|
27173
|
+
function escapeLike(value) {
|
|
27174
|
+
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
27175
|
+
}
|
|
27176
|
+
function asNullableString(value) {
|
|
27177
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
27178
|
+
}
|
|
26586
27179
|
function sanitizeFtsQuery(raw) {
|
|
26587
27180
|
const words = raw.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 1 && !STOPWORDS.has(w.toLowerCase()));
|
|
26588
27181
|
if (words.length === 0) {
|
|
@@ -27052,7 +27645,9 @@ function extractLcmConfig(cfg) {
|
|
|
27052
27645
|
maxDepth: cfg.lcmMaxDepth ?? 5,
|
|
27053
27646
|
deterministicMaxTokens: cfg.lcmDeterministicMaxTokens ?? 512,
|
|
27054
27647
|
archiveRetentionDays: cfg.lcmArchiveRetentionDays ?? 90,
|
|
27055
|
-
recallBudgetShare: cfg.lcmRecallBudgetShare ?? 0.15
|
|
27648
|
+
recallBudgetShare: cfg.lcmRecallBudgetShare ?? 0.15,
|
|
27649
|
+
messagePartsEnabled: cfg.messagePartsEnabled === true,
|
|
27650
|
+
messagePartsRecallMaxResults: typeof cfg.messagePartsRecallMaxResults === "number" ? Math.max(0, Math.floor(cfg.messagePartsRecallMaxResults)) : 6
|
|
27056
27651
|
};
|
|
27057
27652
|
}
|
|
27058
27653
|
var LcmEngine = class {
|
|
@@ -27184,9 +27779,14 @@ var LcmEngine = class {
|
|
|
27184
27779
|
const newMessages = messages.map((m, i) => ({
|
|
27185
27780
|
turnIndex: currentMax + 1 + i,
|
|
27186
27781
|
role: m.role,
|
|
27187
|
-
content: m.content
|
|
27782
|
+
content: m.content,
|
|
27783
|
+
parts: this.config.messagePartsEnabled ? m.parts : void 0,
|
|
27784
|
+
rawContent: this.config.messagePartsEnabled ? m.rawContent : void 0,
|
|
27785
|
+
sourceFormat: this.config.messagePartsEnabled ? m.sourceFormat : void 0
|
|
27188
27786
|
}));
|
|
27189
|
-
this.archive.appendMessages(sessionId, newMessages
|
|
27787
|
+
this.archive.appendMessages(sessionId, newMessages, {
|
|
27788
|
+
messagePartsEnabled: this.config.messagePartsEnabled
|
|
27789
|
+
});
|
|
27190
27790
|
try {
|
|
27191
27791
|
await this.summarizer.summarizeIncremental(sessionId);
|
|
27192
27792
|
} catch (err) {
|
|
@@ -27264,6 +27864,28 @@ var LcmEngine = class {
|
|
|
27264
27864
|
budgetChars: effectiveBudget
|
|
27265
27865
|
});
|
|
27266
27866
|
}
|
|
27867
|
+
async searchStructuredParts(sessionId, query, limit = this.config.messagePartsRecallMaxResults) {
|
|
27868
|
+
if (!this.config.enabled || !this.config.messagePartsEnabled) return [];
|
|
27869
|
+
await this.ensureInitialized();
|
|
27870
|
+
if (!this.archive) return [];
|
|
27871
|
+
return this.archive.searchStructuredParts(query, limit, sessionId);
|
|
27872
|
+
}
|
|
27873
|
+
formatStructuredRecall(matches, budgetChars) {
|
|
27874
|
+
if (matches.length === 0 || budgetChars <= 0) return "";
|
|
27875
|
+
const lines = [];
|
|
27876
|
+
let used = "## Structured Session Matches\n\n".length;
|
|
27877
|
+
for (const match of matches) {
|
|
27878
|
+
const label = match.file_path ? `${match.kind} ${match.file_path}` : match.tool_name ? `${match.kind} ${match.tool_name}` : match.kind;
|
|
27879
|
+
const excerpt = match.content.replace(/\s+/g, " ").slice(0, 220);
|
|
27880
|
+
const line = `- turn ${match.turn_index} (${match.role}): ${label} \u2014 ${excerpt}`;
|
|
27881
|
+
if (used + line.length + 1 > budgetChars) break;
|
|
27882
|
+
lines.push(line);
|
|
27883
|
+
used += line.length + 1;
|
|
27884
|
+
}
|
|
27885
|
+
return lines.length > 0 ? `## Structured Session Matches
|
|
27886
|
+
|
|
27887
|
+
${lines.join("\n")}` : "";
|
|
27888
|
+
}
|
|
27267
27889
|
/** Flush pending summaries before compaction (called from before_compaction hook). */
|
|
27268
27890
|
async preCompactionFlush(sessionId) {
|
|
27269
27891
|
if (!this.config.enabled) return;
|
|
@@ -35504,6 +36126,7 @@ ${r.snippet.trim()}
|
|
|
35504
36126
|
let recalledMemoryIds = [];
|
|
35505
36127
|
let recalledMemoryPaths = [];
|
|
35506
36128
|
let xrayRecalledResults = [];
|
|
36129
|
+
const lcmStructuredXrayResults = [];
|
|
35507
36130
|
const xrayBranchPoolSize = {
|
|
35508
36131
|
hot_qmd: 0,
|
|
35509
36132
|
hot_embedding: 0,
|
|
@@ -37427,6 +38050,32 @@ ${tmtNode.summary}`
|
|
|
37427
38050
|
}
|
|
37428
38051
|
if (this.lcmEngine?.enabled && recallMode !== "minimal" && recallMode !== "no_recall") {
|
|
37429
38052
|
try {
|
|
38053
|
+
const structuredMatches = await this.lcmEngine.searchStructuredParts(
|
|
38054
|
+
sessionKey ?? "default",
|
|
38055
|
+
retrievalQuery
|
|
38056
|
+
);
|
|
38057
|
+
const structuredSection = this.lcmEngine.formatStructuredRecall(
|
|
38058
|
+
structuredMatches,
|
|
38059
|
+
Math.ceil(this.config.recallBudgetChars * 0.08)
|
|
38060
|
+
);
|
|
38061
|
+
if (structuredSection) {
|
|
38062
|
+
const structuredAppended = this.appendRecallSection(
|
|
38063
|
+
sectionBuckets,
|
|
38064
|
+
"lcm-message-parts",
|
|
38065
|
+
structuredSection
|
|
38066
|
+
);
|
|
38067
|
+
if (structuredAppended) {
|
|
38068
|
+
for (const match of structuredMatches) {
|
|
38069
|
+
lcmStructuredXrayResults.push({
|
|
38070
|
+
memoryId: `lcm-message-part-${match.part_id}`,
|
|
38071
|
+
path: `lcm://${match.session_id}/turn/${match.turn_index}/part/${match.part_id}`,
|
|
38072
|
+
servedBy: match.file_path ? "lcm-file-parts" : "lcm-tool-parts",
|
|
38073
|
+
scoreDecomposition: { final: match.score },
|
|
38074
|
+
admittedBy: ["lcm-message-parts"]
|
|
38075
|
+
});
|
|
38076
|
+
}
|
|
38077
|
+
}
|
|
38078
|
+
}
|
|
37430
38079
|
const lcmSection = await this.lcmEngine.assembleRecall(
|
|
37431
38080
|
sessionKey ?? "default",
|
|
37432
38081
|
this.config.recallBudgetChars
|
|
@@ -38327,10 +38976,17 @@ _Context: ${topQuestion.context}_`
|
|
|
38327
38976
|
admitted: recalledMemoryIds.length
|
|
38328
38977
|
}
|
|
38329
38978
|
];
|
|
38979
|
+
if (lcmStructuredXrayResults.length > 0) {
|
|
38980
|
+
filters.push({
|
|
38981
|
+
name: "lcm-message-parts",
|
|
38982
|
+
considered: lcmStructuredXrayResults.length,
|
|
38983
|
+
admitted: lcmStructuredXrayResults.length
|
|
38984
|
+
});
|
|
38985
|
+
}
|
|
38330
38986
|
this.lastXraySnapshot = buildXraySnapshot({
|
|
38331
38987
|
query: retrievalQuery,
|
|
38332
38988
|
tierExplain: null,
|
|
38333
|
-
results,
|
|
38989
|
+
results: [...results, ...lcmStructuredXrayResults],
|
|
38334
38990
|
filters,
|
|
38335
38991
|
budget: {
|
|
38336
38992
|
chars: this.getRecallBudgetChars(options.budgetCharsOverride),
|
|
@@ -38497,13 +39153,28 @@ _Context: ${topQuestion.context}_`
|
|
|
38497
39153
|
role: turn.role,
|
|
38498
39154
|
content: turn.content,
|
|
38499
39155
|
timestamp: turn.timestamp,
|
|
38500
|
-
sessionKey: key
|
|
39156
|
+
sessionKey: key,
|
|
39157
|
+
parts: turn.parts,
|
|
39158
|
+
rawContent: turn.rawContent,
|
|
39159
|
+
sourceFormat: turn.sourceFormat
|
|
38501
39160
|
});
|
|
38502
39161
|
bySession.set(key, list);
|
|
38503
39162
|
}
|
|
38504
39163
|
const replayTasks = [];
|
|
38505
39164
|
for (const [key, sessionTurns] of bySession.entries()) {
|
|
38506
39165
|
if (sessionTurns.length === 0) continue;
|
|
39166
|
+
if (options.archiveLcm !== false && this.lcmEngine?.enabled) {
|
|
39167
|
+
await this.lcmEngine.observeMessages(
|
|
39168
|
+
key,
|
|
39169
|
+
sessionTurns.map((turn) => ({
|
|
39170
|
+
role: turn.role,
|
|
39171
|
+
content: turn.content,
|
|
39172
|
+
parts: turn.parts,
|
|
39173
|
+
rawContent: turn.rawContent,
|
|
39174
|
+
sourceFormat: turn.sourceFormat
|
|
39175
|
+
}))
|
|
39176
|
+
);
|
|
39177
|
+
}
|
|
38507
39178
|
replayTasks.push(
|
|
38508
39179
|
new Promise((resolve2, reject) => {
|
|
38509
39180
|
void this.queueBufferedExtraction(sessionTurns, "trigger_mode", {
|
|
@@ -38592,10 +39263,25 @@ _Context: ${topQuestion.context}_`
|
|
|
38592
39263
|
role: turn.role,
|
|
38593
39264
|
content: turn.content,
|
|
38594
39265
|
timestamp: turn.timestamp,
|
|
38595
|
-
sessionKey
|
|
39266
|
+
sessionKey,
|
|
39267
|
+
parts: turn.parts,
|
|
39268
|
+
rawContent: turn.rawContent,
|
|
39269
|
+
sourceFormat: turn.sourceFormat
|
|
38596
39270
|
});
|
|
38597
39271
|
}
|
|
38598
39272
|
if (sessionTurns.length === 0) return;
|
|
39273
|
+
if (this.lcmEngine?.enabled) {
|
|
39274
|
+
await this.lcmEngine.observeMessages(
|
|
39275
|
+
sessionKey,
|
|
39276
|
+
sessionTurns.map((turn) => ({
|
|
39277
|
+
role: turn.role,
|
|
39278
|
+
content: turn.content,
|
|
39279
|
+
parts: turn.parts,
|
|
39280
|
+
rawContent: turn.rawContent,
|
|
39281
|
+
sourceFormat: turn.sourceFormat
|
|
39282
|
+
}))
|
|
39283
|
+
);
|
|
39284
|
+
}
|
|
38599
39285
|
await new Promise((resolve2, reject) => {
|
|
38600
39286
|
void this.queueBufferedExtraction(sessionTurns, "trigger_mode", {
|
|
38601
39287
|
skipDedupeCheck: true,
|
|
@@ -43124,13 +43810,13 @@ function toolJsonResult(value, options) {
|
|
|
43124
43810
|
function workLayerTextResult(text, options) {
|
|
43125
43811
|
return toolResult2(wrapWorkLayerContext(text, { linkToMemory: options?.linkToMemory === true }));
|
|
43126
43812
|
}
|
|
43127
|
-
function
|
|
43813
|
+
function asNonEmptyString2(value) {
|
|
43128
43814
|
if (typeof value !== "string") return void 0;
|
|
43129
43815
|
const trimmed = value.trim();
|
|
43130
43816
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
43131
43817
|
}
|
|
43132
43818
|
function normalizeToolNamespace(value) {
|
|
43133
|
-
return
|
|
43819
|
+
return asNonEmptyString2(value);
|
|
43134
43820
|
}
|
|
43135
43821
|
function clampUnitInterval(value, fallback) {
|
|
43136
43822
|
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
@@ -43179,17 +43865,17 @@ var WORK_TASK_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress", "blocke
|
|
|
43179
43865
|
var WORK_TASK_PRIORITIES = /* @__PURE__ */ new Set(["low", "medium", "high"]);
|
|
43180
43866
|
var WORK_PROJECT_STATUSES = /* @__PURE__ */ new Set(["active", "on_hold", "completed", "archived"]);
|
|
43181
43867
|
function asTaskStatus(value) {
|
|
43182
|
-
const normalized =
|
|
43868
|
+
const normalized = asNonEmptyString2(value);
|
|
43183
43869
|
if (!normalized || !WORK_TASK_STATUSES.has(normalized)) return void 0;
|
|
43184
43870
|
return normalized;
|
|
43185
43871
|
}
|
|
43186
43872
|
function asTaskPriority(value) {
|
|
43187
|
-
const normalized =
|
|
43873
|
+
const normalized = asNonEmptyString2(value);
|
|
43188
43874
|
if (!normalized || !WORK_TASK_PRIORITIES.has(normalized)) return void 0;
|
|
43189
43875
|
return normalized;
|
|
43190
43876
|
}
|
|
43191
43877
|
function asProjectStatus(value) {
|
|
43192
|
-
const normalized =
|
|
43878
|
+
const normalized = asNonEmptyString2(value);
|
|
43193
43879
|
if (!normalized || !WORK_PROJECT_STATUSES.has(normalized)) return void 0;
|
|
43194
43880
|
return normalized;
|
|
43195
43881
|
}
|
|
@@ -43329,7 +44015,7 @@ function registerTools(api, orchestrator) {
|
|
|
43329
44015
|
return createHash10("sha256").update(input).digest("hex").slice(0, 16);
|
|
43330
44016
|
}
|
|
43331
44017
|
function normalizeMemoryCategory(value, fallback) {
|
|
43332
|
-
const normalized =
|
|
44018
|
+
const normalized = asNonEmptyString2(value);
|
|
43333
44019
|
if (!normalized) return fallback;
|
|
43334
44020
|
if (!VALID_MEMORY_CATEGORIES.has(normalized)) return void 0;
|
|
43335
44021
|
return normalized;
|
|
@@ -43338,8 +44024,8 @@ function registerTools(api, orchestrator) {
|
|
|
43338
44024
|
return typeof value === "string" && actionTypeSet.has(value);
|
|
43339
44025
|
}
|
|
43340
44026
|
function buildActionInputSummary(action, params) {
|
|
43341
|
-
const primary =
|
|
43342
|
-
const content =
|
|
44027
|
+
const primary = asNonEmptyString2(params.memoryId) ?? asNonEmptyString2(params.category) ?? asNonEmptyString2(params.linkTargetId);
|
|
44028
|
+
const content = asNonEmptyString2(params.content);
|
|
43343
44029
|
let summary = primary ? `${action} => ${primary}` : action;
|
|
43344
44030
|
if (content) {
|
|
43345
44031
|
summary += ` | ${content.slice(0, 120)}`;
|
|
@@ -44224,14 +44910,14 @@ NOTE: You did not provide sessionKey; under concurrency this may not match your
|
|
|
44224
44910
|
return toolResult2(`Recorded context checkpoint telemetry in namespace=${ns}.`);
|
|
44225
44911
|
}
|
|
44226
44912
|
const validationErrors = [];
|
|
44227
|
-
if (!
|
|
44913
|
+
if (!asNonEmptyString2(sessionKey)) validationErrors.push("sessionKey is required");
|
|
44228
44914
|
if (!Array.isArray(turns) || turns.length === 0) validationErrors.push("turns must be a non-empty array");
|
|
44229
44915
|
const baseEvent = {
|
|
44230
44916
|
action: "summarize_node",
|
|
44231
44917
|
namespace: ns,
|
|
44232
44918
|
actor: "tool.context_checkpoint",
|
|
44233
44919
|
subsystem: "tools.context_checkpoint",
|
|
44234
|
-
sourceSessionKey:
|
|
44920
|
+
sourceSessionKey: asNonEmptyString2(sessionKey),
|
|
44235
44921
|
inputSummary: summary,
|
|
44236
44922
|
checkpointTurnCount: Array.isArray(turns) ? turns.length : void 0,
|
|
44237
44923
|
dryRun: dryRun === true,
|
|
@@ -44393,10 +45079,10 @@ NOTE: You did not provide sessionKey; under concurrency this may not match your
|
|
|
44393
45079
|
return toolResult2(`Validation failed: invalid action ${String(action)}.`);
|
|
44394
45080
|
}
|
|
44395
45081
|
const validationErrors = [];
|
|
44396
|
-
const contentValue =
|
|
44397
|
-
const memoryIdValue =
|
|
44398
|
-
const linkTargetIdValue =
|
|
44399
|
-
const linkTypeValue =
|
|
45082
|
+
const contentValue = asNonEmptyString2(content);
|
|
45083
|
+
const memoryIdValue = asNonEmptyString2(memoryId);
|
|
45084
|
+
const linkTargetIdValue = asNonEmptyString2(linkTargetId);
|
|
45085
|
+
const linkTypeValue = asNonEmptyString2(linkType);
|
|
44400
45086
|
const structuredActionRequest = execute === true || Object.prototype.hasOwnProperty.call(params, "content") || Object.prototype.hasOwnProperty.call(params, "category") || Object.prototype.hasOwnProperty.call(params, "linkTargetId") || Object.prototype.hasOwnProperty.call(params, "linkType") || Object.prototype.hasOwnProperty.call(params, "linkStrength") || Object.prototype.hasOwnProperty.call(params, "artifactType");
|
|
44401
45087
|
const baseEvent = {
|
|
44402
45088
|
action,
|
|
@@ -44405,7 +45091,7 @@ NOTE: You did not provide sessionKey; under concurrency this may not match your
|
|
|
44405
45091
|
subsystem: "tools.memory_action_apply",
|
|
44406
45092
|
reason,
|
|
44407
45093
|
memoryId: memoryIdValue,
|
|
44408
|
-
sourceSessionKey:
|
|
45094
|
+
sourceSessionKey: asNonEmptyString2(sessionKey),
|
|
44409
45095
|
inputSummary: buildActionInputSummary(action, params),
|
|
44410
45096
|
promptHash: promptHashForTelemetry(sourcePrompt)
|
|
44411
45097
|
};
|
|
@@ -45315,16 +46001,16 @@ Best for:
|
|
|
45315
46001
|
description: typeof p.description === "string" ? p.description : void 0,
|
|
45316
46002
|
status: asTaskStatus(p.status),
|
|
45317
46003
|
priority: asTaskPriority(p.priority),
|
|
45318
|
-
owner:
|
|
45319
|
-
assignee:
|
|
45320
|
-
projectId:
|
|
46004
|
+
owner: asNonEmptyString2(p.owner),
|
|
46005
|
+
assignee: asNonEmptyString2(p.assignee),
|
|
46006
|
+
projectId: asNonEmptyString2(p.projectId),
|
|
45321
46007
|
tags: Array.isArray(p.tags) ? p.tags.filter((x) => typeof x === "string") : void 0,
|
|
45322
|
-
dueAt:
|
|
46008
|
+
dueAt: asNonEmptyString2(p.dueAt)
|
|
45323
46009
|
});
|
|
45324
46010
|
return toolJsonResult({ action, task: created });
|
|
45325
46011
|
}
|
|
45326
46012
|
if (action === "get") {
|
|
45327
|
-
const taskId =
|
|
46013
|
+
const taskId = asNonEmptyString2(p.id);
|
|
45328
46014
|
if (!taskId) {
|
|
45329
46015
|
return workLayerTextResult("work_task.get requires `id`.");
|
|
45330
46016
|
}
|
|
@@ -45334,14 +46020,14 @@ Best for:
|
|
|
45334
46020
|
if (action === "list") {
|
|
45335
46021
|
const tasks = await storage.listTasks({
|
|
45336
46022
|
status: asTaskStatus(p.status),
|
|
45337
|
-
owner:
|
|
45338
|
-
assignee:
|
|
45339
|
-
projectId:
|
|
46023
|
+
owner: asNonEmptyString2(p.owner),
|
|
46024
|
+
assignee: asNonEmptyString2(p.assignee),
|
|
46025
|
+
projectId: asNonEmptyString2(p.projectId)
|
|
45340
46026
|
});
|
|
45341
46027
|
return toolJsonResult({ action, count: tasks.length, tasks });
|
|
45342
46028
|
}
|
|
45343
46029
|
if (action === "update") {
|
|
45344
|
-
const taskId =
|
|
46030
|
+
const taskId = asNonEmptyString2(p.id);
|
|
45345
46031
|
if (!taskId) {
|
|
45346
46032
|
return workLayerTextResult("work_task.update requires `id`.");
|
|
45347
46033
|
}
|
|
@@ -45365,8 +46051,8 @@ Best for:
|
|
|
45365
46051
|
return toolJsonResult({ action, task: updated });
|
|
45366
46052
|
}
|
|
45367
46053
|
if (action === "transition") {
|
|
45368
|
-
const taskId =
|
|
45369
|
-
const rawStatus =
|
|
46054
|
+
const taskId = asNonEmptyString2(p.id);
|
|
46055
|
+
const rawStatus = asNonEmptyString2(p.status);
|
|
45370
46056
|
if (!taskId || !rawStatus) {
|
|
45371
46057
|
return workLayerTextResult("work_task.transition requires `id` and `status`.");
|
|
45372
46058
|
}
|
|
@@ -45378,7 +46064,7 @@ Best for:
|
|
|
45378
46064
|
return toolJsonResult({ action, task });
|
|
45379
46065
|
}
|
|
45380
46066
|
if (action === "delete") {
|
|
45381
|
-
const taskId =
|
|
46067
|
+
const taskId = asNonEmptyString2(p.id);
|
|
45382
46068
|
if (!taskId) {
|
|
45383
46069
|
return workLayerTextResult("work_task.delete requires `id`.");
|
|
45384
46070
|
}
|
|
@@ -45426,13 +46112,13 @@ Best for:
|
|
|
45426
46112
|
name: p.name,
|
|
45427
46113
|
description: typeof p.description === "string" ? p.description : void 0,
|
|
45428
46114
|
status: asProjectStatus(p.status),
|
|
45429
|
-
owner:
|
|
46115
|
+
owner: asNonEmptyString2(p.owner),
|
|
45430
46116
|
tags: Array.isArray(p.tags) ? p.tags.filter((x) => typeof x === "string") : void 0
|
|
45431
46117
|
});
|
|
45432
46118
|
return toolJsonResult({ action, project });
|
|
45433
46119
|
}
|
|
45434
46120
|
if (action === "get") {
|
|
45435
|
-
const projectId =
|
|
46121
|
+
const projectId = asNonEmptyString2(p.id);
|
|
45436
46122
|
if (!projectId) {
|
|
45437
46123
|
return workLayerTextResult("work_project.get requires `id`.");
|
|
45438
46124
|
}
|
|
@@ -45444,7 +46130,7 @@ Best for:
|
|
|
45444
46130
|
return toolJsonResult({ action, count: projects.length, projects });
|
|
45445
46131
|
}
|
|
45446
46132
|
if (action === "update") {
|
|
45447
|
-
const projectId =
|
|
46133
|
+
const projectId = asNonEmptyString2(p.id);
|
|
45448
46134
|
if (!projectId) {
|
|
45449
46135
|
return workLayerTextResult("work_project.update requires `id`.");
|
|
45450
46136
|
}
|
|
@@ -45460,7 +46146,7 @@ Best for:
|
|
|
45460
46146
|
return toolJsonResult({ action, project });
|
|
45461
46147
|
}
|
|
45462
46148
|
if (action === "delete") {
|
|
45463
|
-
const projectId =
|
|
46149
|
+
const projectId = asNonEmptyString2(p.id);
|
|
45464
46150
|
if (!projectId) {
|
|
45465
46151
|
return workLayerTextResult("work_project.delete requires `id`.");
|
|
45466
46152
|
}
|
|
@@ -45468,8 +46154,8 @@ Best for:
|
|
|
45468
46154
|
return toolJsonResult({ action, deleted });
|
|
45469
46155
|
}
|
|
45470
46156
|
if (action === "link_task") {
|
|
45471
|
-
const taskId =
|
|
45472
|
-
const projectId =
|
|
46157
|
+
const taskId = asNonEmptyString2(p.taskId);
|
|
46158
|
+
const projectId = asNonEmptyString2(p.projectId);
|
|
45473
46159
|
if (!taskId || !projectId) {
|
|
45474
46160
|
return workLayerTextResult("work_project.link_task requires `taskId` and `projectId`.");
|
|
45475
46161
|
}
|
|
@@ -45505,7 +46191,7 @@ Best for:
|
|
|
45505
46191
|
async execute(_toolCallId, params) {
|
|
45506
46192
|
const p = params;
|
|
45507
46193
|
const action = String(p.action ?? "");
|
|
45508
|
-
const projectId =
|
|
46194
|
+
const projectId = asNonEmptyString2(p.projectId);
|
|
45509
46195
|
const linkToMemory = p.linkToMemory === true;
|
|
45510
46196
|
try {
|
|
45511
46197
|
await new WorkStorage(orchestrator.config.memoryDir).ensureDirectories();
|
|
@@ -45525,7 +46211,7 @@ Best for:
|
|
|
45525
46211
|
const result = await importWorkBoardSnapshot({
|
|
45526
46212
|
memoryDir: orchestrator.config.memoryDir,
|
|
45527
46213
|
snapshot,
|
|
45528
|
-
projectId:
|
|
46214
|
+
projectId: asNonEmptyString2(p.projectId)
|
|
45529
46215
|
});
|
|
45530
46216
|
return toolJsonResult({ action, result }, { linkToMemory });
|
|
45531
46217
|
}
|
|
@@ -45791,7 +46477,7 @@ Returns: Performance trace data with timing breakdown`,
|
|
|
45791
46477
|
"Profiling is disabled. Set profilingEnabled: true in your plugin config to enable."
|
|
45792
46478
|
);
|
|
45793
46479
|
}
|
|
45794
|
-
const format =
|
|
46480
|
+
const format = asNonEmptyString2(params.format) ?? "ascii";
|
|
45795
46481
|
const limit = Math.min(typeof params.limit === "number" ? params.limit : 5, 20);
|
|
45796
46482
|
const traces = profiler.getRecentTraces(limit);
|
|
45797
46483
|
const stats = profiler.getStats();
|
|
@@ -45862,7 +46548,7 @@ Returns: Performance trace data with timing breakdown`,
|
|
|
45862
46548
|
"Profiling is disabled. Set profilingEnabled: true in your plugin config to enable."
|
|
45863
46549
|
);
|
|
45864
46550
|
}
|
|
45865
|
-
const format =
|
|
46551
|
+
const format = asNonEmptyString2(params.format) ?? "ascii";
|
|
45866
46552
|
const limit = Math.min(typeof params.limit === "number" ? params.limit : 5, 20);
|
|
45867
46553
|
const traces = profiler.getRecentTraces(limit);
|
|
45868
46554
|
const stats = profiler.getStats();
|
|
@@ -55320,10 +56006,15 @@ var EngramAccessService = class {
|
|
|
55320
56006
|
sessionKey: lcmSessionKey,
|
|
55321
56007
|
role: m.role,
|
|
55322
56008
|
content: m.content,
|
|
56009
|
+
parts: m.parts,
|
|
56010
|
+
rawContent: m.rawContent,
|
|
56011
|
+
sourceFormat: m.sourceFormat,
|
|
55323
56012
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
55324
56013
|
}));
|
|
55325
56014
|
try {
|
|
55326
|
-
const extractionPromise = this.orchestrator.ingestReplayBatch(turns
|
|
56015
|
+
const extractionPromise = this.orchestrator.ingestReplayBatch(turns, {
|
|
56016
|
+
archiveLcm: false
|
|
56017
|
+
});
|
|
55327
56018
|
extractionPromise.catch((err) => {
|
|
55328
56019
|
log.error(`access-observe background extraction failed: ${err}`);
|
|
55329
56020
|
});
|
|
@@ -56705,7 +57396,33 @@ var setCodingContextRequestSchema = external_exports.object({
|
|
|
56705
57396
|
});
|
|
56706
57397
|
var messageSchema = external_exports.object({
|
|
56707
57398
|
role: external_exports.enum(["user", "assistant"]),
|
|
56708
|
-
content: external_exports.string().min(1, "message content must be non-empty")
|
|
57399
|
+
content: external_exports.string().min(1, "message content must be non-empty"),
|
|
57400
|
+
sourceFormat: external_exports.enum(["openai", "anthropic", "openclaw", "lossless-claw", "remnic"]).nullable().optional(),
|
|
57401
|
+
rawContent: external_exports.unknown().nullable().optional(),
|
|
57402
|
+
parts: external_exports.array(
|
|
57403
|
+
external_exports.object({
|
|
57404
|
+
ordinal: external_exports.number().int().min(0).nullable().optional(),
|
|
57405
|
+
kind: external_exports.enum([
|
|
57406
|
+
"text",
|
|
57407
|
+
"tool_call",
|
|
57408
|
+
"tool_result",
|
|
57409
|
+
"patch",
|
|
57410
|
+
"file_read",
|
|
57411
|
+
"file_write",
|
|
57412
|
+
"step_start",
|
|
57413
|
+
"step_finish",
|
|
57414
|
+
"snapshot",
|
|
57415
|
+
"retry"
|
|
57416
|
+
]),
|
|
57417
|
+
payload: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
57418
|
+
toolName: external_exports.string().nullable().optional(),
|
|
57419
|
+
tool_name: external_exports.string().nullable().optional(),
|
|
57420
|
+
filePath: external_exports.string().nullable().optional(),
|
|
57421
|
+
file_path: external_exports.string().nullable().optional(),
|
|
57422
|
+
createdAt: external_exports.string().nullable().optional(),
|
|
57423
|
+
created_at: external_exports.string().nullable().optional()
|
|
57424
|
+
})
|
|
57425
|
+
).nullable().optional()
|
|
56709
57426
|
});
|
|
56710
57427
|
var observeRequestSchema = external_exports.object({
|
|
56711
57428
|
sessionKey: external_exports.string().trim().min(1, "sessionKey is required").max(512),
|
|
@@ -57351,9 +58068,51 @@ var EngramMcpServer = class {
|
|
|
57351
58068
|
type: "object",
|
|
57352
58069
|
properties: {
|
|
57353
58070
|
role: { type: "string", enum: ["user", "assistant"] },
|
|
57354
|
-
content: { type: "string" }
|
|
58071
|
+
content: { type: "string" },
|
|
58072
|
+
sourceFormat: {
|
|
58073
|
+
type: "string",
|
|
58074
|
+
enum: ["openai", "anthropic", "openclaw", "lossless-claw", "remnic"]
|
|
58075
|
+
},
|
|
58076
|
+
rawContent: {
|
|
58077
|
+
description: "Optional native provider content blocks for structured message-part capture."
|
|
58078
|
+
},
|
|
58079
|
+
parts: {
|
|
58080
|
+
type: "array",
|
|
58081
|
+
description: "Optional normalized Remnic LCM message parts.",
|
|
58082
|
+
items: {
|
|
58083
|
+
type: "object",
|
|
58084
|
+
properties: {
|
|
58085
|
+
ordinal: { type: ["number", "null"], minimum: 0 },
|
|
58086
|
+
kind: {
|
|
58087
|
+
type: "string",
|
|
58088
|
+
enum: [
|
|
58089
|
+
"text",
|
|
58090
|
+
"tool_call",
|
|
58091
|
+
"tool_result",
|
|
58092
|
+
"patch",
|
|
58093
|
+
"file_read",
|
|
58094
|
+
"file_write",
|
|
58095
|
+
"step_start",
|
|
58096
|
+
"step_finish",
|
|
58097
|
+
"snapshot",
|
|
58098
|
+
"retry"
|
|
58099
|
+
]
|
|
58100
|
+
},
|
|
58101
|
+
payload: { type: "object", additionalProperties: true },
|
|
58102
|
+
toolName: { type: ["string", "null"] },
|
|
58103
|
+
tool_name: { type: ["string", "null"] },
|
|
58104
|
+
filePath: { type: ["string", "null"] },
|
|
58105
|
+
file_path: { type: ["string", "null"] },
|
|
58106
|
+
createdAt: { type: ["string", "null"] },
|
|
58107
|
+
created_at: { type: ["string", "null"] }
|
|
58108
|
+
},
|
|
58109
|
+
required: ["kind", "payload"],
|
|
58110
|
+
additionalProperties: false
|
|
58111
|
+
}
|
|
58112
|
+
}
|
|
57355
58113
|
},
|
|
57356
|
-
required: ["role", "content"]
|
|
58114
|
+
required: ["role", "content"],
|
|
58115
|
+
additionalProperties: false
|
|
57357
58116
|
},
|
|
57358
58117
|
description: "Conversation messages to observe"
|
|
57359
58118
|
},
|
|
@@ -58694,20 +59453,21 @@ ${body}`;
|
|
|
58694
59453
|
effectivePrincipal
|
|
58695
59454
|
);
|
|
58696
59455
|
case "engram.observe": {
|
|
58697
|
-
|
|
58698
|
-
throw new EngramAccessInputError("cwd must be a string");
|
|
58699
|
-
}
|
|
58700
|
-
if ("projectTag" in args && args.projectTag !== void 0 && args.projectTag !== null && typeof args.projectTag !== "string") {
|
|
58701
|
-
throw new EngramAccessInputError("projectTag must be a string");
|
|
58702
|
-
}
|
|
59456
|
+
const body = parseMcpRequest("observe", args);
|
|
58703
59457
|
return this.service.observe({
|
|
58704
|
-
sessionKey:
|
|
58705
|
-
messages:
|
|
58706
|
-
|
|
59458
|
+
sessionKey: body.sessionKey,
|
|
59459
|
+
messages: body.messages.map((message) => ({
|
|
59460
|
+
role: message.role,
|
|
59461
|
+
content: message.content,
|
|
59462
|
+
parts: message.parts ?? void 0,
|
|
59463
|
+
rawContent: message.rawContent ?? void 0,
|
|
59464
|
+
sourceFormat: message.sourceFormat ?? void 0
|
|
59465
|
+
})),
|
|
59466
|
+
namespace: body.namespace,
|
|
58707
59467
|
authenticatedPrincipal: effectivePrincipal,
|
|
58708
|
-
skipExtraction:
|
|
58709
|
-
cwd:
|
|
58710
|
-
projectTag:
|
|
59468
|
+
skipExtraction: body.skipExtraction === true,
|
|
59469
|
+
cwd: body.cwd,
|
|
59470
|
+
projectTag: body.projectTag
|
|
58711
59471
|
});
|
|
58712
59472
|
}
|
|
58713
59473
|
case "engram.lcm_search":
|
|
@@ -59886,7 +60646,13 @@ var EngramAccessHttpServer = class {
|
|
|
59886
60646
|
this.ensureWriteRateLimitAvailable();
|
|
59887
60647
|
const response = await this.service.observe({
|
|
59888
60648
|
sessionKey: body.sessionKey,
|
|
59889
|
-
messages: body.messages
|
|
60649
|
+
messages: body.messages.map((message) => ({
|
|
60650
|
+
role: message.role,
|
|
60651
|
+
content: message.content,
|
|
60652
|
+
sourceFormat: message.sourceFormat ?? void 0,
|
|
60653
|
+
rawContent: message.rawContent ?? void 0,
|
|
60654
|
+
parts: message.parts ?? void 0
|
|
60655
|
+
})),
|
|
59890
60656
|
namespace: this.resolveNamespace(req, body.namespace),
|
|
59891
60657
|
authenticatedPrincipal: this.resolveRequestPrincipal(req),
|
|
59892
60658
|
skipExtraction: body.skipExtraction === true,
|
|
@@ -67824,7 +68590,7 @@ import crypto2 from "crypto";
|
|
|
67824
68590
|
function hashSha256(value) {
|
|
67825
68591
|
return crypto2.createHash("sha256").update(value).digest("hex");
|
|
67826
68592
|
}
|
|
67827
|
-
function
|
|
68593
|
+
function isRecord4(value) {
|
|
67828
68594
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
67829
68595
|
}
|
|
67830
68596
|
function optionalString2(value) {
|
|
@@ -67840,11 +68606,11 @@ function normalizedToolName(toolName) {
|
|
|
67840
68606
|
return toolNameTokens(toolName).join("_");
|
|
67841
68607
|
}
|
|
67842
68608
|
function parseToolArguments(value) {
|
|
67843
|
-
if (
|
|
68609
|
+
if (isRecord4(value)) return value;
|
|
67844
68610
|
if (typeof value !== "string") return void 0;
|
|
67845
68611
|
try {
|
|
67846
68612
|
const parsed = JSON.parse(value);
|
|
67847
|
-
return
|
|
68613
|
+
return isRecord4(parsed) ? parsed : void 0;
|
|
67848
68614
|
} catch {
|
|
67849
68615
|
return void 0;
|
|
67850
68616
|
}
|
|
@@ -67854,13 +68620,13 @@ function extractTextContent(value) {
|
|
|
67854
68620
|
if (Array.isArray(value)) {
|
|
67855
68621
|
return value.map((block) => {
|
|
67856
68622
|
if (typeof block === "string") return block.trim();
|
|
67857
|
-
if (
|
|
68623
|
+
if (isRecord4(block) && block.type === "text" && typeof block.text === "string") {
|
|
67858
68624
|
return block.text.trim();
|
|
67859
68625
|
}
|
|
67860
68626
|
return "";
|
|
67861
68627
|
}).filter((item) => item.length > 0).join("\n");
|
|
67862
68628
|
}
|
|
67863
|
-
if (
|
|
68629
|
+
if (isRecord4(value)) {
|
|
67864
68630
|
return JSON.stringify(value);
|
|
67865
68631
|
}
|
|
67866
68632
|
return "";
|
|
@@ -67887,10 +68653,10 @@ function getToolCallContexts(messages) {
|
|
|
67887
68653
|
const toolCalls = message.tool_calls ?? message.toolCalls;
|
|
67888
68654
|
if (!Array.isArray(toolCalls)) continue;
|
|
67889
68655
|
for (const call of toolCalls) {
|
|
67890
|
-
if (!
|
|
68656
|
+
if (!isRecord4(call)) continue;
|
|
67891
68657
|
const toolCallId = optionalString2(call.id) ?? optionalString2(call.toolCallId);
|
|
67892
68658
|
if (!toolCallId) continue;
|
|
67893
|
-
const fn =
|
|
68659
|
+
const fn = isRecord4(call.function) ? call.function : void 0;
|
|
67894
68660
|
const toolName = optionalString2(fn?.name) ?? optionalString2(call.name);
|
|
67895
68661
|
const args = parseToolArguments(fn?.arguments) ?? parseToolArguments(call.arguments) ?? parseToolArguments(call.args) ?? parseToolArguments(call.input);
|
|
67896
68662
|
contexts.set(toolCallId, { toolCallId, toolName, args });
|
|
@@ -67933,7 +68699,7 @@ function fileContentHash(args) {
|
|
|
67933
68699
|
}
|
|
67934
68700
|
function inferOutcome(message, parsedPayload) {
|
|
67935
68701
|
if (message.isError === true) return "failure";
|
|
67936
|
-
if (
|
|
68702
|
+
if (isRecord4(parsedPayload)) {
|
|
67937
68703
|
if (parsedPayload.partial === true || parsedPayload.status === "partial") return "partial";
|
|
67938
68704
|
if (parsedPayload.success === false || parsedPayload.ok === false) return "failure";
|
|
67939
68705
|
if (parsedPayload.success === true || parsedPayload.ok === true) return "success";
|
|
@@ -72525,7 +73291,12 @@ Keep the reflection grounded in the evidence below.
|
|
|
72525
73291
|
const lcmMessages = lastTurn.map((msg) => {
|
|
72526
73292
|
const rawRole = typeof msg.role === "string" ? msg.role : "";
|
|
72527
73293
|
const content = extractTextContent2(msg);
|
|
72528
|
-
return {
|
|
73294
|
+
return {
|
|
73295
|
+
role: rawRole,
|
|
73296
|
+
content,
|
|
73297
|
+
rawContent: msg,
|
|
73298
|
+
sourceFormat: "openclaw"
|
|
73299
|
+
};
|
|
72529
73300
|
}).filter((m) => m.content.length > 0);
|
|
72530
73301
|
if (lcmMessages.length > 0) {
|
|
72531
73302
|
orchestrator.lcmEngine.enqueueObserveMessages(
|
package/openclaw.plugin.json
CHANGED
|
@@ -4260,6 +4260,16 @@
|
|
|
4260
4260
|
"default": 90,
|
|
4261
4261
|
"description": "Days to retain archived LCM summaries"
|
|
4262
4262
|
},
|
|
4263
|
+
"messagePartsEnabled": {
|
|
4264
|
+
"type": "boolean",
|
|
4265
|
+
"default": false,
|
|
4266
|
+
"description": "Opt in to structured LCM message-part capture for tool calls, file references, patches, and reasoning markers."
|
|
4267
|
+
},
|
|
4268
|
+
"messagePartsRecallMaxResults": {
|
|
4269
|
+
"type": "number",
|
|
4270
|
+
"default": 6,
|
|
4271
|
+
"description": "Maximum structured message-part matches to inject into recall when messagePartsEnabled is true."
|
|
4272
|
+
},
|
|
4263
4273
|
"ircEnabled": {
|
|
4264
4274
|
"type": "boolean",
|
|
4265
4275
|
"default": true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/plugin-openclaw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "OpenClaw adapter for Remnic memory — thin wrapper delegating to @remnic/core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"openai": "^6.0.0",
|
|
28
|
-
"@remnic/core": "^1.1.
|
|
28
|
+
"@remnic/core": "^1.1.5"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"openclaw": ">=2026.4.8"
|