@martian-engineering/lossless-claw 0.6.3 → 0.8.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/README.md +26 -6
- package/docs/agent-tools.md +16 -5
- package/docs/configuration.md +223 -214
- package/openclaw.plugin.json +123 -0
- package/package.json +1 -1
- package/skills/lossless-claw/SKILL.md +3 -2
- package/skills/lossless-claw/references/architecture.md +12 -0
- package/skills/lossless-claw/references/config.md +135 -3
- package/skills/lossless-claw/references/diagnostics.md +13 -0
- package/src/assembler.ts +17 -5
- package/src/compaction.ts +161 -53
- package/src/db/config.ts +102 -4
- package/src/db/connection.ts +35 -7
- package/src/db/features.ts +24 -5
- package/src/db/migration.ts +257 -78
- package/src/engine.ts +1007 -110
- package/src/estimate-tokens.ts +80 -0
- package/src/lcm-log.ts +37 -0
- package/src/plugin/index.ts +493 -101
- package/src/plugin/lcm-command.ts +288 -7
- package/src/plugin/lcm-doctor-apply.ts +1 -3
- package/src/plugin/lcm-doctor-cleaners.ts +655 -0
- package/src/plugin/shared-init.ts +59 -0
- package/src/prune.ts +391 -0
- package/src/retrieval.ts +8 -9
- package/src/startup-banner-log.ts +1 -0
- package/src/store/compaction-telemetry-store.ts +156 -0
- package/src/store/conversation-store.ts +6 -1
- package/src/store/fts5-sanitize.ts +25 -4
- package/src/store/full-text-sort.ts +21 -0
- package/src/store/index.ts +8 -0
- package/src/store/summary-store.ts +21 -14
- package/src/summarize.ts +55 -34
- package/src/tools/lcm-describe-tool.ts +9 -4
- package/src/tools/lcm-expand-query-tool.ts +609 -200
- package/src/tools/lcm-expand-tool.ts +9 -4
- package/src/tools/lcm-grep-tool.ts +22 -8
- package/src/types.ts +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type SearchSort = "recency" | "relevance" | "hybrid";
|
|
2
|
+
|
|
3
|
+
export const AGE_DECAY_RATE = 0.001;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build the ORDER BY clause for FTS5-backed searches.
|
|
7
|
+
*
|
|
8
|
+
* `rank` is FTS5's BM25 score where lower (more negative) is better.
|
|
9
|
+
* `hybrid` keeps that relevance signal but applies a mild age penalty before
|
|
10
|
+
* LIMIT is enforced so older strong matches can still surface.
|
|
11
|
+
*/
|
|
12
|
+
export function buildFtsOrderBy(sort: SearchSort | undefined, createdAtExpr: string): string {
|
|
13
|
+
switch (sort ?? "recency") {
|
|
14
|
+
case "relevance":
|
|
15
|
+
return `rank ASC, ${createdAtExpr} DESC`;
|
|
16
|
+
case "hybrid":
|
|
17
|
+
return `(rank / (1 + ((julianday('now') - julianday(${createdAtExpr})) * 24 * ${AGE_DECAY_RATE}))) ASC, ${createdAtExpr} DESC`;
|
|
18
|
+
default:
|
|
19
|
+
return `${createdAtExpr} DESC`;
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/store/index.ts
CHANGED
|
@@ -29,3 +29,11 @@ export type {
|
|
|
29
29
|
UpsertConversationBootstrapStateInput,
|
|
30
30
|
ConversationBootstrapStateRecord,
|
|
31
31
|
} from "./summary-store.js";
|
|
32
|
+
|
|
33
|
+
export { CompactionTelemetryStore } from "./compaction-telemetry-store.js";
|
|
34
|
+
export type {
|
|
35
|
+
CacheState,
|
|
36
|
+
ActivityBand,
|
|
37
|
+
ConversationCompactionTelemetryRecord,
|
|
38
|
+
UpsertConversationCompactionTelemetryInput,
|
|
39
|
+
} from "./compaction-telemetry-store.js";
|
|
@@ -3,6 +3,7 @@ import { withDatabaseTransaction } from "../transaction-mutex.js";
|
|
|
3
3
|
import { sanitizeFts5Query } from "./fts5-sanitize.js";
|
|
4
4
|
import { buildLikeSearchPlan, containsCjk, createFallbackSnippet } from "./full-text-fallback.js";
|
|
5
5
|
import { parseUtcTimestamp, parseUtcTimestampOrNull } from "./parse-utc-timestamp.js";
|
|
6
|
+
import { buildFtsOrderBy, type SearchSort } from "./full-text-sort.js";
|
|
6
7
|
|
|
7
8
|
export type SummaryKind = "leaf" | "condensed";
|
|
8
9
|
export type ContextItemType = "message" | "summary";
|
|
@@ -68,6 +69,7 @@ export type SummarySearchInput = {
|
|
|
68
69
|
since?: Date;
|
|
69
70
|
before?: Date;
|
|
70
71
|
limit?: number;
|
|
72
|
+
sort?: SearchSort;
|
|
71
73
|
};
|
|
72
74
|
|
|
73
75
|
export type SummarySearchResult = {
|
|
@@ -958,7 +960,10 @@ export class SummaryStore {
|
|
|
958
960
|
.run(conversationId, startOrdinal, summaryId);
|
|
959
961
|
|
|
960
962
|
// 3. Resequence all ordinals to maintain contiguity (no gaps).
|
|
961
|
-
//
|
|
963
|
+
// Pre-compute ranks from a SELECT (safe snapshot), then apply
|
|
964
|
+
// via 2-pass UPDATE loop using negative temps to avoid UNIQUE
|
|
965
|
+
// constraint violations. The SELECT reads post-delete/insert
|
|
966
|
+
// state and provides a consistent snapshot for resequencing.
|
|
962
967
|
const items = this.db
|
|
963
968
|
.prepare(
|
|
964
969
|
`SELECT ordinal FROM context_items
|
|
@@ -967,18 +972,17 @@ export class SummaryStore {
|
|
|
967
972
|
)
|
|
968
973
|
.all(conversationId) as unknown as { ordinal: number }[];
|
|
969
974
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
updateStmt.run(i, conversationId, -(i + 1));
|
|
975
|
+
if (items.length > 0 && items.some((item, i) => item.ordinal !== i)) {
|
|
976
|
+
const updateStmt = this.db.prepare(
|
|
977
|
+
`UPDATE context_items SET ordinal = ?
|
|
978
|
+
WHERE conversation_id = ? AND ordinal = ?`,
|
|
979
|
+
);
|
|
980
|
+
for (let i = 0; i < items.length; i++) {
|
|
981
|
+
updateStmt.run(-(i + 1), conversationId, items[i].ordinal);
|
|
982
|
+
}
|
|
983
|
+
for (let i = 0; i < items.length; i++) {
|
|
984
|
+
updateStmt.run(i, conversationId, -(i + 1));
|
|
985
|
+
}
|
|
982
986
|
}
|
|
983
987
|
}
|
|
984
988
|
|
|
@@ -1051,6 +1055,7 @@ export class SummaryStore {
|
|
|
1051
1055
|
input.conversationId,
|
|
1052
1056
|
input.since,
|
|
1053
1057
|
input.before,
|
|
1058
|
+
input.sort,
|
|
1054
1059
|
);
|
|
1055
1060
|
} catch {
|
|
1056
1061
|
return this.searchLike(
|
|
@@ -1073,6 +1078,7 @@ export class SummaryStore {
|
|
|
1073
1078
|
conversationId?: number,
|
|
1074
1079
|
since?: Date,
|
|
1075
1080
|
before?: Date,
|
|
1081
|
+
sort?: SearchSort,
|
|
1076
1082
|
): SummarySearchResult[] {
|
|
1077
1083
|
const where: string[] = ["summaries_fts MATCH ?"];
|
|
1078
1084
|
const args: Array<string | number> = [sanitizeFts5Query(query)];
|
|
@@ -1089,6 +1095,7 @@ export class SummaryStore {
|
|
|
1089
1095
|
args.push(before.toISOString());
|
|
1090
1096
|
}
|
|
1091
1097
|
args.push(limit);
|
|
1098
|
+
const orderBy = buildFtsOrderBy(sort, "s.created_at");
|
|
1092
1099
|
|
|
1093
1100
|
const sql = `SELECT
|
|
1094
1101
|
summaries_fts.summary_id,
|
|
@@ -1100,7 +1107,7 @@ export class SummaryStore {
|
|
|
1100
1107
|
FROM summaries_fts
|
|
1101
1108
|
JOIN summaries s ON s.summary_id = summaries_fts.summary_id
|
|
1102
1109
|
WHERE ${where.join(" AND ")}
|
|
1103
|
-
ORDER BY
|
|
1110
|
+
ORDER BY ${orderBy}
|
|
1104
1111
|
LIMIT ?`;
|
|
1105
1112
|
const rows = this.db.prepare(sql).all(...args) as unknown as SummarySearchRow[];
|
|
1106
1113
|
return rows.map(toSearchResult);
|
package/src/summarize.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { describeLogError } from "./lcm-log.js";
|
|
1
2
|
import type { LcmDependencies } from "./types.js";
|
|
3
|
+
import { estimateTokens } from "./estimate-tokens.js";
|
|
2
4
|
|
|
3
5
|
export type LcmSummarizeOptions = {
|
|
4
6
|
previousSummary?: string;
|
|
@@ -173,10 +175,6 @@ function resolveProviderApiFromLegacyConfig(
|
|
|
173
175
|
return undefined;
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
/** Approximate token estimate used for target-sizing prompts. */
|
|
177
|
-
function estimateTokens(text: string): number {
|
|
178
|
-
return Math.ceil(text.length / 4);
|
|
179
|
-
}
|
|
180
178
|
|
|
181
179
|
/** Narrow unknown values to plain object records. */
|
|
182
180
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -1068,6 +1066,17 @@ function resolveSummaryCandidates(params: {
|
|
|
1068
1066
|
},
|
|
1069
1067
|
];
|
|
1070
1068
|
|
|
1069
|
+
// Append explicit fallback providers from config.
|
|
1070
|
+
for (const fb of params.deps.config.fallbackProviders ?? []) {
|
|
1071
|
+
resolutionCandidates.push({
|
|
1072
|
+
levelName: `explicit fallback (${fb.provider}/${fb.model})`,
|
|
1073
|
+
modelRef: `${fb.provider}/${fb.model}`,
|
|
1074
|
+
providerHint: fb.provider,
|
|
1075
|
+
hasExplicitProvider: true,
|
|
1076
|
+
useLegacyAuthProfile: false,
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1071
1080
|
const resolvedCandidates: ResolvedSummaryCandidate[] = [];
|
|
1072
1081
|
for (const candidate of resolutionCandidates) {
|
|
1073
1082
|
if (!candidate.modelRef) {
|
|
@@ -1088,9 +1097,8 @@ function resolveSummaryCandidates(params: {
|
|
|
1088
1097
|
});
|
|
1089
1098
|
}
|
|
1090
1099
|
} catch (err) {
|
|
1091
|
-
|
|
1092
|
-
`[lcm] createLcmSummarize: resolveModel FAILED at ${candidate.levelName}
|
|
1093
|
-
err instanceof Error ? err.message : err,
|
|
1100
|
+
params.deps.log.error(
|
|
1101
|
+
`[lcm] createLcmSummarize: resolveModel FAILED at ${candidate.levelName}: ${describeLogError(err)}`,
|
|
1094
1102
|
);
|
|
1095
1103
|
}
|
|
1096
1104
|
}
|
|
@@ -1111,7 +1119,7 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1111
1119
|
}): Promise<{ fn: LcmSummarizeFn; model: string; breakerKey: string } | undefined> {
|
|
1112
1120
|
const resolvedCandidates = resolveSummaryCandidates(params);
|
|
1113
1121
|
if (resolvedCandidates.length === 0) {
|
|
1114
|
-
|
|
1122
|
+
params.deps.log.error("[lcm] createLcmSummarize: no summary model candidates resolved");
|
|
1115
1123
|
return undefined;
|
|
1116
1124
|
}
|
|
1117
1125
|
|
|
@@ -1197,6 +1205,7 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1197
1205
|
requestApiKey: string | undefined,
|
|
1198
1206
|
label: string,
|
|
1199
1207
|
reasoning?: string,
|
|
1208
|
+
options?: { skipModelAuth?: boolean },
|
|
1200
1209
|
) =>
|
|
1201
1210
|
withTimeout(params.deps.complete({
|
|
1202
1211
|
provider,
|
|
@@ -1215,6 +1224,7 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1215
1224
|
],
|
|
1216
1225
|
maxTokens: targetTokens,
|
|
1217
1226
|
...(reasoning ? { reasoning } : {}),
|
|
1227
|
+
...(options?.skipModelAuth === true ? { skipModelAuth: true } : {}),
|
|
1218
1228
|
}), summarizerTimeoutMs, label);
|
|
1219
1229
|
|
|
1220
1230
|
const retryWithoutModelAuth = async (
|
|
@@ -1226,8 +1236,8 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1226
1236
|
if (runtimeManagedAuth) {
|
|
1227
1237
|
throw initialAuthError;
|
|
1228
1238
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1239
|
+
params.deps.log.warn(initialAuthError.message);
|
|
1240
|
+
params.deps.log.warn(
|
|
1231
1241
|
`[lcm] summarizer auth retry: retrying ${provider}/${model} without runtime.modelAuth credentials.`,
|
|
1232
1242
|
);
|
|
1233
1243
|
|
|
@@ -1236,14 +1246,16 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1236
1246
|
skipModelAuth: true,
|
|
1237
1247
|
});
|
|
1238
1248
|
if (!directApiKey) {
|
|
1239
|
-
|
|
1249
|
+
params.deps.log.warn(
|
|
1240
1250
|
`[lcm] summarizer auth retry unavailable: no direct credentials found for ${provider}/${model}.`,
|
|
1241
1251
|
);
|
|
1242
1252
|
throw initialAuthError;
|
|
1243
1253
|
}
|
|
1244
1254
|
|
|
1245
1255
|
try {
|
|
1246
|
-
const directResult = await runSummarizerCall(directApiKey, "auth-retry", reasoning
|
|
1256
|
+
const directResult = await runSummarizerCall(directApiKey, "auth-retry", reasoning, {
|
|
1257
|
+
skipModelAuth: true,
|
|
1258
|
+
});
|
|
1247
1259
|
// Use requireStructuralSignal on the retry success path too — the
|
|
1248
1260
|
// summary text may legitimately contain auth-error phrases.
|
|
1249
1261
|
const directFailure = extractProviderAuthFailure(directResult, {
|
|
@@ -1255,10 +1267,10 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1255
1267
|
model,
|
|
1256
1268
|
failure: directFailure,
|
|
1257
1269
|
});
|
|
1258
|
-
|
|
1270
|
+
params.deps.log.warn(retryAuthError.message);
|
|
1259
1271
|
throw retryAuthError;
|
|
1260
1272
|
}
|
|
1261
|
-
|
|
1273
|
+
params.deps.log.info(
|
|
1262
1274
|
`[lcm] summarizer auth retry succeeded; provider=${provider}; model=${model}; source=direct-credentials`,
|
|
1263
1275
|
);
|
|
1264
1276
|
return directResult;
|
|
@@ -1277,7 +1289,7 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1277
1289
|
model,
|
|
1278
1290
|
failure: directFailure,
|
|
1279
1291
|
});
|
|
1280
|
-
|
|
1292
|
+
params.deps.log.warn(retryAuthError.message);
|
|
1281
1293
|
throw retryAuthError;
|
|
1282
1294
|
}
|
|
1283
1295
|
throw directErr;
|
|
@@ -1317,31 +1329,35 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1317
1329
|
if (err instanceof LcmProviderAuthError) {
|
|
1318
1330
|
lastAuthError = err;
|
|
1319
1331
|
if (nextCandidate) {
|
|
1320
|
-
|
|
1321
|
-
`[lcm]
|
|
1332
|
+
params.deps.log.warn(
|
|
1333
|
+
`[lcm] PROVIDER FALLBACK: ${provider}/${model} auth failed → trying ${nextCandidate.provider}/${nextCandidate.model}`,
|
|
1322
1334
|
);
|
|
1335
|
+
const backoffMs = Math.min(500 * Math.pow(2, index), 8000);
|
|
1336
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
1323
1337
|
continue;
|
|
1324
1338
|
}
|
|
1325
1339
|
throw lastAuthError;
|
|
1326
1340
|
}
|
|
1327
1341
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1328
1342
|
const isTimeout = errMsg.includes("summarizer timeout");
|
|
1329
|
-
|
|
1343
|
+
params.deps.log.warn(
|
|
1330
1344
|
`[lcm] summarizer ${isTimeout ? "timed out" : "failed"}; provider=${provider}; model=${model}; timeout=${summarizerTimeoutMs}ms; error=${errMsg}`,
|
|
1331
1345
|
);
|
|
1332
1346
|
if (nextCandidate) {
|
|
1333
|
-
|
|
1334
|
-
`[lcm]
|
|
1347
|
+
params.deps.log.warn(
|
|
1348
|
+
`[lcm] PROVIDER FALLBACK: ${provider}/${model} ${isTimeout ? "timed out" : "failed"} → trying ${nextCandidate.provider}/${nextCandidate.model}`,
|
|
1335
1349
|
);
|
|
1350
|
+
const backoffMs = Math.min(500 * Math.pow(2, index), 8000);
|
|
1351
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
1336
1352
|
continue;
|
|
1337
1353
|
}
|
|
1338
1354
|
if (err instanceof SummarizerTimeoutError) {
|
|
1339
|
-
|
|
1355
|
+
params.deps.log.warn(
|
|
1340
1356
|
`[lcm] summarizer timed out; provider=${provider}; model=${model}; source=fallback`,
|
|
1341
1357
|
);
|
|
1342
1358
|
return buildDeterministicFallbackSummary(text, targetTokens);
|
|
1343
1359
|
}
|
|
1344
|
-
|
|
1360
|
+
break;
|
|
1345
1361
|
}
|
|
1346
1362
|
|
|
1347
1363
|
const normalized = normalizeCompletionSummary(result.content);
|
|
@@ -1358,7 +1374,7 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1358
1374
|
if (envelopeNormalized.summary) {
|
|
1359
1375
|
summary = envelopeNormalized.summary;
|
|
1360
1376
|
summarySource = "envelope";
|
|
1361
|
-
|
|
1377
|
+
params.deps.log.info(
|
|
1362
1378
|
`[lcm] recovered summary from response envelope; provider=${provider}; model=${model}; ` +
|
|
1363
1379
|
`block_types=${formatBlockTypes(envelopeNormalized.blockTypes)}; source=envelope`,
|
|
1364
1380
|
);
|
|
@@ -1386,7 +1402,7 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1386
1402
|
if (responseDiag) {
|
|
1387
1403
|
diagParts.push(responseDiag);
|
|
1388
1404
|
}
|
|
1389
|
-
|
|
1405
|
+
params.deps.log.warn(`${diagParts.join("; ")}; retrying with conservative settings`);
|
|
1390
1406
|
|
|
1391
1407
|
// Single retry with conservative parameters: low temperature and low
|
|
1392
1408
|
// reasoning budget to coax a textual response from providers that
|
|
@@ -1401,7 +1417,7 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1401
1417
|
|
|
1402
1418
|
if (summary) {
|
|
1403
1419
|
summarySource = "retry";
|
|
1404
|
-
|
|
1420
|
+
params.deps.log.info(
|
|
1405
1421
|
`[lcm] retry succeeded; provider=${provider}; model=${model}; ` +
|
|
1406
1422
|
`block_types=${formatBlockTypes(retryEnvelopeNormalized.blockTypes)}; source=retry`,
|
|
1407
1423
|
);
|
|
@@ -1418,21 +1434,23 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1418
1434
|
retryParts.push(retryDiag);
|
|
1419
1435
|
}
|
|
1420
1436
|
if (nextCandidate) {
|
|
1421
|
-
|
|
1437
|
+
params.deps.log.warn(
|
|
1422
1438
|
`${retryParts.join("; ")}; retrying with ${nextCandidate.provider}/${nextCandidate.model}`,
|
|
1423
1439
|
);
|
|
1424
1440
|
continue;
|
|
1425
1441
|
}
|
|
1426
|
-
|
|
1442
|
+
params.deps.log.warn(`${retryParts.join("; ")}; falling back to truncation`);
|
|
1427
1443
|
summary = initialSummary;
|
|
1428
1444
|
}
|
|
1429
1445
|
} catch (retryErr) {
|
|
1430
1446
|
if (retryErr instanceof LcmProviderAuthError) {
|
|
1431
1447
|
lastAuthError = retryErr;
|
|
1432
1448
|
if (nextCandidate) {
|
|
1433
|
-
|
|
1434
|
-
`[lcm]
|
|
1449
|
+
params.deps.log.warn(
|
|
1450
|
+
`[lcm] PROVIDER FALLBACK: ${provider}/${model} auth failed on retry → trying ${nextCandidate.provider}/${nextCandidate.model}`,
|
|
1435
1451
|
);
|
|
1452
|
+
const backoffMs = Math.min(500 * Math.pow(2, index), 8000);
|
|
1453
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
1436
1454
|
continue;
|
|
1437
1455
|
}
|
|
1438
1456
|
throw lastAuthError;
|
|
@@ -1441,12 +1459,12 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1441
1459
|
const retryErrMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
1442
1460
|
const isRetryTimeout = retryErrMsg.includes("summarizer timeout");
|
|
1443
1461
|
if (nextCandidate) {
|
|
1444
|
-
|
|
1462
|
+
params.deps.log.warn(
|
|
1445
1463
|
`[lcm] retry ${isRetryTimeout ? "timed out" : "failed"}; provider=${provider}; model=${model}; timeout=${summarizerTimeoutMs}ms; error=${retryErrMsg}; retrying with ${nextCandidate.provider}/${nextCandidate.model}`,
|
|
1446
1464
|
);
|
|
1447
1465
|
continue;
|
|
1448
1466
|
}
|
|
1449
|
-
|
|
1467
|
+
params.deps.log.warn(
|
|
1450
1468
|
`[lcm] retry ${isRetryTimeout ? "timed out" : "failed"}; provider=${provider}; model=${model}; timeout=${summarizerTimeoutMs}ms; error=${retryErrMsg}; falling back to truncation`,
|
|
1451
1469
|
);
|
|
1452
1470
|
summary = initialSummary;
|
|
@@ -1455,14 +1473,14 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1455
1473
|
|
|
1456
1474
|
if (!summary) {
|
|
1457
1475
|
summarySource = "fallback";
|
|
1458
|
-
|
|
1476
|
+
params.deps.log.error(
|
|
1459
1477
|
`[lcm] all extraction attempts exhausted; provider=${provider}; model=${model}; source=fallback`,
|
|
1460
1478
|
);
|
|
1461
1479
|
return buildDeterministicFallbackSummary(text, targetTokens);
|
|
1462
1480
|
}
|
|
1463
1481
|
|
|
1464
1482
|
if (summarySource !== "content") {
|
|
1465
|
-
|
|
1483
|
+
params.deps.log.info(
|
|
1466
1484
|
`[lcm] summary resolved via non-content path; provider=${provider}; model=${model}; source=${summarySource}`,
|
|
1467
1485
|
);
|
|
1468
1486
|
}
|
|
@@ -1470,10 +1488,13 @@ export async function createLcmSummarizeFromLegacyParams(params: {
|
|
|
1470
1488
|
return summary;
|
|
1471
1489
|
}
|
|
1472
1490
|
|
|
1491
|
+
params.deps.log.error(
|
|
1492
|
+
`[lcm] ALL PROVIDERS EXHAUSTED: ${resolvedCandidates.length} candidate(s) tried, none succeeded. Compaction falling back to deterministic truncation. Check provider keys and quotas.`,
|
|
1493
|
+
);
|
|
1473
1494
|
if (lastAuthError) {
|
|
1474
1495
|
throw lastAuthError;
|
|
1475
1496
|
}
|
|
1476
|
-
return
|
|
1497
|
+
return buildDeterministicFallbackSummary(text, targetTokens);
|
|
1477
1498
|
};
|
|
1478
1499
|
|
|
1479
1500
|
return {
|
|
@@ -57,7 +57,8 @@ function normalizeRequestedTokenCap(value: unknown): number | undefined {
|
|
|
57
57
|
|
|
58
58
|
export function createLcmDescribeTool(input: {
|
|
59
59
|
deps: LcmDependencies;
|
|
60
|
-
lcm
|
|
60
|
+
lcm?: LcmContextEngine;
|
|
61
|
+
getLcm?: () => Promise<LcmContextEngine>;
|
|
61
62
|
sessionId?: string;
|
|
62
63
|
sessionKey?: string;
|
|
63
64
|
}): AnyAgentTool {
|
|
@@ -71,12 +72,16 @@ export function createLcmDescribeTool(input: {
|
|
|
71
72
|
"token counts, and file exploration results.",
|
|
72
73
|
parameters: LcmDescribeSchema,
|
|
73
74
|
async execute(_toolCallId, params) {
|
|
74
|
-
const
|
|
75
|
-
|
|
75
|
+
const lcm = input.lcm ?? (await input.getLcm?.());
|
|
76
|
+
if (!lcm) {
|
|
77
|
+
throw new Error("LCM engine is unavailable.");
|
|
78
|
+
}
|
|
79
|
+
const retrieval = lcm.getRetrieval();
|
|
80
|
+
const timezone = lcm.timezone;
|
|
76
81
|
const p = params as Record<string, unknown>;
|
|
77
82
|
const id = (p.id as string).trim();
|
|
78
83
|
const conversationScope = await resolveLcmConversationScope({
|
|
79
|
-
lcm
|
|
84
|
+
lcm,
|
|
80
85
|
deps: input.deps,
|
|
81
86
|
sessionId: input.sessionId,
|
|
82
87
|
sessionKey: input.sessionKey,
|