@martian-engineering/lossless-claw 0.5.0 → 0.5.1
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/package.json +1 -1
- package/src/assembler.ts +37 -3
- package/src/db/migration.ts +63 -0
- package/src/store/conversation-store.ts +18 -8
- package/src/summarize.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martian-engineering/lossless-claw",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Lossless Context Management plugin for OpenClaw — DAG-based conversation summarization with incremental compaction",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
package/src/assembler.ts
CHANGED
|
@@ -238,9 +238,12 @@ export function toolCallBlockFromPart(part: MessagePartRecord, rawType?: string)
|
|
|
238
238
|
return block;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
// Always set id — downstream providers (e.g. Anthropic) call
|
|
242
|
+
// normalizeToolCallId(block.id) which crashes on undefined.
|
|
243
|
+
block.id =
|
|
244
|
+
typeof part.toolCallId === "string" && part.toolCallId.length > 0
|
|
245
|
+
? part.toolCallId
|
|
246
|
+
: `toolu_lcm_${part.partId ?? "unknown"}`;
|
|
244
247
|
if (typeof part.toolName === "string" && part.toolName.length > 0) {
|
|
245
248
|
block.name = part.toolName;
|
|
246
249
|
}
|
|
@@ -362,6 +365,37 @@ export function blockFromPart(part: MessagePartRecord): unknown {
|
|
|
362
365
|
if (!isToolBlock) {
|
|
363
366
|
return metadata.raw;
|
|
364
367
|
}
|
|
368
|
+
|
|
369
|
+
// When tool blocks are routed through toolCallBlockFromPart (below) instead
|
|
370
|
+
// of returning raw directly, the function reads part.toolCallId / part.toolName
|
|
371
|
+
// from the DB columns. For rows stored as part_type='text' those columns are
|
|
372
|
+
// often NULL — the values only live inside metadata.raw. Backfill them here
|
|
373
|
+
// so the reconstructed block keeps the original id/name.
|
|
374
|
+
const rawRecord = metadata.raw as Record<string, unknown>;
|
|
375
|
+
const rawToolCallId =
|
|
376
|
+
typeof rawRecord.id === "string" && rawRecord.id.length > 0
|
|
377
|
+
? rawRecord.id
|
|
378
|
+
: typeof rawRecord.call_id === "string" && rawRecord.call_id.length > 0
|
|
379
|
+
? rawRecord.call_id
|
|
380
|
+
: undefined;
|
|
381
|
+
if (rawToolCallId) {
|
|
382
|
+
if (typeof part.toolCallId !== "string" || part.toolCallId.length === 0) {
|
|
383
|
+
part.toolCallId = rawToolCallId;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (typeof rawRecord.name === "string" && rawRecord.name.length > 0) {
|
|
387
|
+
if (typeof part.toolName !== "string" || part.toolName.length === 0) {
|
|
388
|
+
part.toolName = rawRecord.name;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Backfill toolInput from raw arguments/input so toolCallBlockFromPart
|
|
392
|
+
// can reconstruct the full block.
|
|
393
|
+
if (part.toolInput == null || part.toolInput === "") {
|
|
394
|
+
const rawArgs = rawRecord.arguments ?? rawRecord.input;
|
|
395
|
+
if (rawArgs !== undefined) {
|
|
396
|
+
part.toolInput = typeof rawArgs === "string" ? rawArgs : JSON.stringify(rawArgs);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
365
399
|
}
|
|
366
400
|
|
|
367
401
|
if (part.partType === "reasoning") {
|
package/src/db/migration.ts
CHANGED
|
@@ -363,6 +363,68 @@ function backfillSummaryMetadata(db: DatabaseSync): void {
|
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Backfill tool_call_id, tool_name, and tool_input from metadata JSON for rows
|
|
368
|
+
* where the DB columns are NULL but the values exist in metadata. This covers
|
|
369
|
+
* legacy text-type parts where the string-content ingestion path stored tool
|
|
370
|
+
* info only in the metadata JSON (see #158).
|
|
371
|
+
*/
|
|
372
|
+
function backfillToolCallColumns(db: DatabaseSync): void {
|
|
373
|
+
db.exec(
|
|
374
|
+
`UPDATE message_parts
|
|
375
|
+
SET tool_call_id = COALESCE(
|
|
376
|
+
json_extract(metadata, '$.toolCallId'),
|
|
377
|
+
json_extract(metadata, '$.raw.id'),
|
|
378
|
+
json_extract(metadata, '$.raw.call_id'),
|
|
379
|
+
json_extract(metadata, '$.raw.toolCallId'),
|
|
380
|
+
json_extract(metadata, '$.raw.tool_call_id')
|
|
381
|
+
)
|
|
382
|
+
WHERE tool_call_id IS NULL
|
|
383
|
+
AND metadata IS NOT NULL
|
|
384
|
+
AND COALESCE(
|
|
385
|
+
json_extract(metadata, '$.toolCallId'),
|
|
386
|
+
json_extract(metadata, '$.raw.id'),
|
|
387
|
+
json_extract(metadata, '$.raw.call_id'),
|
|
388
|
+
json_extract(metadata, '$.raw.toolCallId'),
|
|
389
|
+
json_extract(metadata, '$.raw.tool_call_id')
|
|
390
|
+
) IS NOT NULL`,
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
db.exec(
|
|
394
|
+
`UPDATE message_parts
|
|
395
|
+
SET tool_name = COALESCE(
|
|
396
|
+
json_extract(metadata, '$.toolName'),
|
|
397
|
+
json_extract(metadata, '$.raw.name'),
|
|
398
|
+
json_extract(metadata, '$.raw.toolName'),
|
|
399
|
+
json_extract(metadata, '$.raw.tool_name')
|
|
400
|
+
)
|
|
401
|
+
WHERE tool_name IS NULL
|
|
402
|
+
AND metadata IS NOT NULL
|
|
403
|
+
AND COALESCE(
|
|
404
|
+
json_extract(metadata, '$.toolName'),
|
|
405
|
+
json_extract(metadata, '$.raw.name'),
|
|
406
|
+
json_extract(metadata, '$.raw.toolName'),
|
|
407
|
+
json_extract(metadata, '$.raw.tool_name')
|
|
408
|
+
) IS NOT NULL`,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
db.exec(
|
|
412
|
+
`UPDATE message_parts
|
|
413
|
+
SET tool_input = COALESCE(
|
|
414
|
+
json_extract(metadata, '$.raw.input'),
|
|
415
|
+
json_extract(metadata, '$.raw.arguments'),
|
|
416
|
+
json_extract(metadata, '$.raw.toolInput')
|
|
417
|
+
)
|
|
418
|
+
WHERE tool_input IS NULL
|
|
419
|
+
AND metadata IS NOT NULL
|
|
420
|
+
AND COALESCE(
|
|
421
|
+
json_extract(metadata, '$.raw.input'),
|
|
422
|
+
json_extract(metadata, '$.raw.arguments'),
|
|
423
|
+
json_extract(metadata, '$.raw.toolInput')
|
|
424
|
+
) IS NOT NULL`,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
366
428
|
export function runLcmMigrations(
|
|
367
429
|
db: DatabaseSync,
|
|
368
430
|
options?: { fts5Available?: boolean },
|
|
@@ -523,6 +585,7 @@ export function runLcmMigrations(
|
|
|
523
585
|
ensureSummaryModelColumn(db);
|
|
524
586
|
backfillSummaryDepths(db);
|
|
525
587
|
backfillSummaryMetadata(db);
|
|
588
|
+
backfillToolCallColumns(db);
|
|
526
589
|
|
|
527
590
|
const fts5Available = options?.fts5Available ?? getLcmDbFeatures(db).fts5Available;
|
|
528
591
|
if (!fts5Available) {
|
|
@@ -804,14 +804,24 @@ export class ConversationStore {
|
|
|
804
804
|
)
|
|
805
805
|
.all(...args) as unknown as MessageRow[];
|
|
806
806
|
|
|
807
|
-
return rows
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
807
|
+
return rows
|
|
808
|
+
.map((row) => {
|
|
809
|
+
const normalizedContent = normalizeMessageContentForFullTextIndex(row.content) ?? row.content;
|
|
810
|
+
const haystack = normalizedContent.toLowerCase();
|
|
811
|
+
const matchesAllTerms = plan.terms.every((term) => haystack.includes(term));
|
|
812
|
+
if (!matchesAllTerms) {
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
return {
|
|
816
|
+
messageId: row.message_id,
|
|
817
|
+
conversationId: row.conversation_id,
|
|
818
|
+
role: row.role,
|
|
819
|
+
snippet: createFallbackSnippet(normalizedContent, plan.terms),
|
|
820
|
+
createdAt: new Date(row.created_at),
|
|
821
|
+
rank: 0,
|
|
822
|
+
};
|
|
823
|
+
})
|
|
824
|
+
.filter((row): row is MessageSearchResult => row !== null);
|
|
815
825
|
}
|
|
816
826
|
|
|
817
827
|
private searchRegex(
|
package/src/summarize.ts
CHANGED
|
@@ -897,11 +897,14 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
897
897
|
console.error(`[lcm] createLcmSummarize: empty provider="${provider}" or model="${model}"`);
|
|
898
898
|
return undefined;
|
|
899
899
|
}
|
|
900
|
-
const
|
|
900
|
+
const legacyAuthProfileId =
|
|
901
901
|
typeof params.legacyParams.authProfileId === "string" &&
|
|
902
902
|
params.legacyParams.authProfileId.trim()
|
|
903
903
|
? params.legacyParams.authProfileId.trim()
|
|
904
904
|
: undefined;
|
|
905
|
+
// When LCM selects a dedicated summarizer model/provider, do not leak the
|
|
906
|
+
// active session's auth profile into that separate credential lookup.
|
|
907
|
+
const authProfileId = resolvedSummary === undefined ? legacyAuthProfileId : undefined;
|
|
905
908
|
const agentDir =
|
|
906
909
|
typeof params.legacyParams.agentDir === "string" && params.legacyParams.agentDir.trim()
|
|
907
910
|
? params.legacyParams.agentDir.trim()
|