@martian-engineering/lossless-claw 0.8.0 → 0.8.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.
Files changed (52) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +19240 -0
  3. package/docs/configuration.md +15 -5
  4. package/openclaw.plugin.json +27 -3
  5. package/package.json +7 -6
  6. package/skills/lossless-claw/references/config.md +37 -0
  7. package/index.ts +0 -2
  8. package/src/assembler.ts +0 -1196
  9. package/src/compaction.ts +0 -1753
  10. package/src/db/config.ts +0 -345
  11. package/src/db/connection.ts +0 -151
  12. package/src/db/features.ts +0 -61
  13. package/src/db/migration.ts +0 -868
  14. package/src/engine.ts +0 -4486
  15. package/src/estimate-tokens.ts +0 -80
  16. package/src/expansion-auth.ts +0 -365
  17. package/src/expansion-policy.ts +0 -303
  18. package/src/expansion.ts +0 -383
  19. package/src/integrity.ts +0 -600
  20. package/src/large-files.ts +0 -546
  21. package/src/lcm-log.ts +0 -37
  22. package/src/openclaw-bridge.ts +0 -22
  23. package/src/plugin/index.ts +0 -2037
  24. package/src/plugin/lcm-command.ts +0 -1040
  25. package/src/plugin/lcm-doctor-apply.ts +0 -540
  26. package/src/plugin/lcm-doctor-cleaners.ts +0 -655
  27. package/src/plugin/lcm-doctor-shared.ts +0 -210
  28. package/src/plugin/shared-init.ts +0 -59
  29. package/src/prune.ts +0 -391
  30. package/src/retrieval.ts +0 -360
  31. package/src/session-patterns.ts +0 -23
  32. package/src/startup-banner-log.ts +0 -49
  33. package/src/store/compaction-telemetry-store.ts +0 -156
  34. package/src/store/conversation-store.ts +0 -929
  35. package/src/store/fts5-sanitize.ts +0 -50
  36. package/src/store/full-text-fallback.ts +0 -83
  37. package/src/store/full-text-sort.ts +0 -21
  38. package/src/store/index.ts +0 -39
  39. package/src/store/parse-utc-timestamp.ts +0 -25
  40. package/src/store/summary-store.ts +0 -1519
  41. package/src/summarize.ts +0 -1508
  42. package/src/tools/common.ts +0 -53
  43. package/src/tools/lcm-conversation-scope.ts +0 -127
  44. package/src/tools/lcm-describe-tool.ts +0 -245
  45. package/src/tools/lcm-expand-query-tool.ts +0 -1235
  46. package/src/tools/lcm-expand-tool.delegation.ts +0 -580
  47. package/src/tools/lcm-expand-tool.ts +0 -453
  48. package/src/tools/lcm-expansion-recursion-guard.ts +0 -373
  49. package/src/tools/lcm-grep-tool.ts +0 -228
  50. package/src/transaction-mutex.ts +0 -136
  51. package/src/transcript-repair.ts +0 -301
  52. package/src/types.ts +0 -165
@@ -1,453 +0,0 @@
1
- import { Type } from "@sinclair/typebox";
2
- import type { LcmContextEngine } from "../engine.js";
3
- import type { LcmDependencies } from "../types.js";
4
- import type { AnyAgentTool } from "./common.js";
5
- import {
6
- getRuntimeExpansionAuthManager,
7
- resolveDelegatedExpansionGrantId,
8
- wrapWithAuth,
9
- } from "../expansion-auth.js";
10
- import { decideLcmExpansionRouting } from "../expansion-policy.js";
11
- import {
12
- ExpansionOrchestrator,
13
- distillForSubagent,
14
- type ExpansionResult,
15
- } from "../expansion.js";
16
- import { jsonResult } from "./common.js";
17
- import { resolveLcmConversationScope } from "./lcm-conversation-scope.js";
18
- import {
19
- normalizeSummaryIds,
20
- runDelegatedExpansionLoop,
21
- type DelegatedExpansionLoopResult,
22
- } from "./lcm-expand-tool.delegation.js";
23
-
24
- const LcmExpandSchema = Type.Object({
25
- summaryIds: Type.Optional(
26
- Type.Array(Type.String(), {
27
- description: "Summary IDs to expand (sum_xxx format). Required if query is not provided.",
28
- }),
29
- ),
30
- query: Type.Optional(
31
- Type.String({
32
- description:
33
- "Text query to grep for matching summaries before expanding. " +
34
- "If provided, summaryIds is ignored and the top grep results are expanded.",
35
- }),
36
- ),
37
- maxDepth: Type.Optional(
38
- Type.Number({
39
- description: "Max traversal depth per summary (default: 3).",
40
- minimum: 1,
41
- }),
42
- ),
43
- tokenCap: Type.Optional(
44
- Type.Number({
45
- description: "Max tokens across the entire expansion result.",
46
- minimum: 1,
47
- }),
48
- ),
49
- includeMessages: Type.Optional(
50
- Type.Boolean({
51
- description: "Whether to include raw source messages at leaf level (default: false).",
52
- }),
53
- ),
54
- conversationId: Type.Optional(
55
- Type.Number({
56
- description:
57
- "Conversation ID to scope the expansion to. If omitted, uses the current session's conversation.",
58
- }),
59
- ),
60
- allConversations: Type.Optional(
61
- Type.Boolean({
62
- description:
63
- "Set true to explicitly allow cross-conversation expansion. Ignored when conversationId is provided.",
64
- }),
65
- ),
66
- });
67
-
68
- function makeEmptyExpansionResult(): ExpansionResult {
69
- return {
70
- expansions: [],
71
- citedIds: [],
72
- totalTokens: 0,
73
- truncated: false,
74
- };
75
- }
76
-
77
- type LcmDelegatedRunReference = {
78
- pass: number;
79
- status: "ok" | "timeout" | "error";
80
- runId: string;
81
- childSessionKey: string;
82
- };
83
-
84
- /**
85
- * Extract delegated run references for deterministic orchestration diagnostics.
86
- */
87
- function toDelegatedRunReferences(
88
- delegated?: DelegatedExpansionLoopResult,
89
- ): LcmDelegatedRunReference[] | undefined {
90
- if (!delegated) {
91
- return undefined;
92
- }
93
- const refs = delegated.passes.map((pass) => ({
94
- pass: pass.pass,
95
- status: pass.status,
96
- runId: pass.runId,
97
- childSessionKey: pass.childSessionKey,
98
- }));
99
- return refs.length > 0 ? refs : undefined;
100
- }
101
-
102
- /**
103
- * Build stable debug metadata for route-vs-delegate orchestration decisions.
104
- */
105
- function buildOrchestrationObservability(input: {
106
- policy: ReturnType<typeof decideLcmExpansionRouting>;
107
- executionPath: "direct" | "delegated" | "direct_fallback";
108
- delegated?: DelegatedExpansionLoopResult;
109
- }) {
110
- return {
111
- decisionPath: {
112
- policyAction: input.policy.action,
113
- executionPath: input.executionPath,
114
- },
115
- policyReasons: input.policy.reasons,
116
- delegatedRunRefs: toDelegatedRunReferences(input.delegated),
117
- };
118
- }
119
-
120
- /**
121
- * Build the runtime LCM expansion tool with route-vs-delegate orchestration.
122
- */
123
- export function createLcmExpandTool(input: {
124
- deps: LcmDependencies;
125
- lcm?: LcmContextEngine;
126
- getLcm?: () => Promise<LcmContextEngine>;
127
- /** Runtime session key (used for delegated expansion auth scoping). */
128
- sessionId?: string;
129
- sessionKey?: string;
130
- }): AnyAgentTool {
131
- return {
132
- name: "lcm_expand",
133
- label: "LCM Expand",
134
- description:
135
- "Expand compacted conversation summaries from LCM (Lossless Context Management). " +
136
- "Traverses the summary DAG to retrieve children and source messages. " +
137
- "Use this to drill into previously-compacted context when you need detail " +
138
- "that was summarised away. Provide either summaryIds (direct expansion) or " +
139
- "query (grep-first, then expand top matches). Returns a compact text payload " +
140
- "plus cited IDs in tool output for follow-up.",
141
- parameters: LcmExpandSchema,
142
- async execute(_toolCallId, params) {
143
- const lcm = input.lcm ?? (await input.getLcm?.());
144
- if (!lcm) {
145
- throw new Error("LCM engine is unavailable.");
146
- }
147
- const retrieval = lcm.getRetrieval();
148
- const orchestrator = new ExpansionOrchestrator(retrieval);
149
- const runtimeAuthManager = getRuntimeExpansionAuthManager();
150
-
151
- const p = params as Record<string, unknown>;
152
- const summaryIds = p.summaryIds as string[] | undefined;
153
- const query = typeof p.query === "string" ? p.query.trim() : undefined;
154
- const maxDepth = typeof p.maxDepth === "number" ? Math.trunc(p.maxDepth) : undefined;
155
- const requestedTokenCap = typeof p.tokenCap === "number" ? Math.trunc(p.tokenCap) : undefined;
156
- const tokenCap =
157
- typeof requestedTokenCap === "number" && Number.isFinite(requestedTokenCap)
158
- ? Math.max(1, requestedTokenCap)
159
- : undefined;
160
- const includeMessages = typeof p.includeMessages === "boolean" ? p.includeMessages : false;
161
- const sessionKey =
162
- (typeof input.sessionKey === "string" ? input.sessionKey : input.sessionId)?.trim() ?? "";
163
- if (!input.deps.isSubagentSessionKey(sessionKey)) {
164
- return jsonResult({
165
- error:
166
- "lcm_expand is only available in sub-agent sessions. Use lcm_expand_query to ask a focused question against expanded summaries, or lcm_describe/lcm_grep for lighter lookups.",
167
- });
168
- }
169
- const isDelegatedSession = input.deps.isSubagentSessionKey(sessionKey);
170
- const delegatedGrantId = isDelegatedSession
171
- ? (resolveDelegatedExpansionGrantId(sessionKey) ?? undefined)
172
- : undefined;
173
- const delegatedGrant =
174
- delegatedGrantId !== undefined ? runtimeAuthManager.getGrant(delegatedGrantId) : null;
175
- const authorizedOrchestrator =
176
- delegatedGrantId !== undefined ? wrapWithAuth(orchestrator, runtimeAuthManager) : null;
177
-
178
- if (isDelegatedSession && !delegatedGrantId) {
179
- return jsonResult({
180
- error:
181
- "Delegated expansion requires a valid grant. This sub-agent session has no propagated expansion grant.",
182
- });
183
- }
184
-
185
- const conversationScope = await resolveLcmConversationScope({
186
- lcm,
187
- deps: input.deps,
188
- sessionId: input.sessionId,
189
- sessionKey: input.sessionKey,
190
- params: p,
191
- });
192
-
193
- const runExpand = async (input: {
194
- summaryIds: string[];
195
- conversationId: number;
196
- maxDepth?: number;
197
- tokenCap?: number;
198
- includeMessages?: boolean;
199
- }) => {
200
- if (!authorizedOrchestrator || !delegatedGrantId) {
201
- return orchestrator.expand(input);
202
- }
203
- return authorizedOrchestrator.expand(delegatedGrantId, input);
204
- };
205
-
206
- const resolvedConversationId =
207
- conversationScope.conversationId ??
208
- (delegatedGrant?.allowedConversationIds.length === 1
209
- ? delegatedGrant.allowedConversationIds[0]
210
- : undefined);
211
-
212
- if (query) {
213
- try {
214
- if (resolvedConversationId == null) {
215
- const result = await orchestrator.describeAndExpand({
216
- query,
217
- mode: "full_text",
218
- conversationId: undefined,
219
- maxDepth,
220
- tokenCap,
221
- });
222
- const text = distillForSubagent(result);
223
- const policy = decideLcmExpansionRouting({
224
- intent: "query_probe",
225
- query,
226
- requestedMaxDepth: maxDepth,
227
- candidateSummaryCount: result.expansions.length,
228
- tokenCap: tokenCap ?? Number.MAX_SAFE_INTEGER,
229
- includeMessages: false,
230
- });
231
- return {
232
- content: [{ type: "text", text }],
233
- details: {
234
- expansionCount: result.expansions.length,
235
- citedIds: result.citedIds,
236
- totalTokens: result.totalTokens,
237
- truncated: result.truncated,
238
- policy,
239
- executionPath: "direct",
240
- observability: buildOrchestrationObservability({
241
- policy,
242
- executionPath: "direct",
243
- }),
244
- },
245
- };
246
- }
247
- const grepResult = await retrieval.grep({
248
- query,
249
- mode: "full_text",
250
- scope: "summaries",
251
- conversationId: resolvedConversationId,
252
- });
253
- const matchedSummaryIds = grepResult.summaries.map((entry) => entry.summaryId);
254
- const policy = decideLcmExpansionRouting({
255
- intent: "query_probe",
256
- query,
257
- requestedMaxDepth: maxDepth,
258
- candidateSummaryCount: matchedSummaryIds.length,
259
- tokenCap: tokenCap ?? Number.MAX_SAFE_INTEGER,
260
- includeMessages: false,
261
- });
262
- const canDelegate =
263
- matchedSummaryIds.length > 0 &&
264
- policy.action === "delegate_traversal" &&
265
- !isDelegatedSession &&
266
- !!sessionKey;
267
- const delegated =
268
- canDelegate && resolvedConversationId != null
269
- ? await runDelegatedExpansionLoop({
270
- deps: input.deps,
271
- requesterSessionKey: sessionKey,
272
- conversationId: resolvedConversationId,
273
- summaryIds: matchedSummaryIds,
274
- maxDepth,
275
- tokenCap,
276
- includeMessages: false,
277
- query,
278
- })
279
- : undefined;
280
- if (delegated && delegated.status === "ok") {
281
- return {
282
- content: [{ type: "text", text: delegated.text }],
283
- details: {
284
- expansionCount: delegated.citedIds.length,
285
- citedIds: delegated.citedIds,
286
- totalTokens: delegated.totalTokens,
287
- truncated: delegated.truncated,
288
- policy,
289
- executionPath: "delegated",
290
- delegated,
291
- observability: buildOrchestrationObservability({
292
- policy,
293
- executionPath: "delegated",
294
- delegated,
295
- }),
296
- },
297
- };
298
- }
299
-
300
- const executionPath = delegated ? "direct_fallback" : "direct";
301
- const result =
302
- matchedSummaryIds.length === 0
303
- ? makeEmptyExpansionResult()
304
- : await runExpand({
305
- summaryIds: matchedSummaryIds,
306
- maxDepth,
307
- tokenCap,
308
- includeMessages: false,
309
- conversationId: resolvedConversationId,
310
- });
311
- const text = distillForSubagent(result);
312
- return {
313
- content: [{ type: "text", text }],
314
- details: {
315
- expansionCount: result.expansions.length,
316
- citedIds: result.citedIds,
317
- totalTokens: result.totalTokens,
318
- truncated: result.truncated,
319
- policy,
320
- executionPath,
321
- delegated:
322
- delegated && delegated.status !== "ok"
323
- ? {
324
- status: delegated.status,
325
- error: delegated.error,
326
- passes: delegated.passes,
327
- }
328
- : undefined,
329
- observability: buildOrchestrationObservability({
330
- policy,
331
- executionPath,
332
- delegated,
333
- }),
334
- },
335
- };
336
- } catch (err) {
337
- const message = err instanceof Error ? err.message : String(err);
338
- return jsonResult({ error: message });
339
- }
340
- }
341
-
342
- if (summaryIds && summaryIds.length > 0) {
343
- try {
344
- if (conversationScope.conversationId != null) {
345
- const outOfScope: string[] = [];
346
- for (const summaryId of summaryIds) {
347
- const described = await retrieval.describe(summaryId);
348
- if (
349
- described?.type === "summary" &&
350
- described.summary?.conversationId !== conversationScope.conversationId
351
- ) {
352
- outOfScope.push(summaryId);
353
- }
354
- }
355
- if (outOfScope.length > 0) {
356
- return jsonResult({
357
- error:
358
- `Some summaryIds are outside conversation ${conversationScope.conversationId}: ` +
359
- outOfScope.join(", "),
360
- hint: "Use allConversations=true for cross-conversation expansion.",
361
- });
362
- }
363
- }
364
-
365
- const policy = decideLcmExpansionRouting({
366
- intent: "explicit_expand",
367
- requestedMaxDepth: maxDepth,
368
- candidateSummaryCount: summaryIds.length,
369
- tokenCap: tokenCap ?? Number.MAX_SAFE_INTEGER,
370
- includeMessages,
371
- });
372
- const normalizedSummaryIds = normalizeSummaryIds(summaryIds);
373
- const canDelegate =
374
- normalizedSummaryIds.length > 0 &&
375
- policy.action === "delegate_traversal" &&
376
- !isDelegatedSession &&
377
- !!sessionKey &&
378
- resolvedConversationId != null;
379
- const delegated = canDelegate
380
- ? await runDelegatedExpansionLoop({
381
- deps: input.deps,
382
- requesterSessionKey: sessionKey,
383
- conversationId: resolvedConversationId,
384
- summaryIds: normalizedSummaryIds,
385
- maxDepth,
386
- tokenCap,
387
- includeMessages,
388
- })
389
- : undefined;
390
- if (delegated && delegated.status === "ok") {
391
- return {
392
- content: [{ type: "text", text: delegated.text }],
393
- details: {
394
- expansionCount: delegated.citedIds.length,
395
- citedIds: delegated.citedIds,
396
- totalTokens: delegated.totalTokens,
397
- truncated: delegated.truncated,
398
- policy,
399
- executionPath: "delegated",
400
- delegated,
401
- observability: buildOrchestrationObservability({
402
- policy,
403
- executionPath: "delegated",
404
- delegated,
405
- }),
406
- },
407
- };
408
- }
409
- const executionPath = delegated ? "direct_fallback" : "direct";
410
- const result = await runExpand({
411
- summaryIds: normalizedSummaryIds,
412
- maxDepth,
413
- tokenCap,
414
- includeMessages,
415
- conversationId: resolvedConversationId ?? 0,
416
- });
417
- const text = distillForSubagent(result);
418
- return {
419
- content: [{ type: "text", text }],
420
- details: {
421
- expansionCount: result.expansions.length,
422
- citedIds: result.citedIds,
423
- totalTokens: result.totalTokens,
424
- truncated: result.truncated,
425
- policy,
426
- executionPath,
427
- delegated:
428
- delegated && delegated.status !== "ok"
429
- ? {
430
- status: delegated.status,
431
- error: delegated.error,
432
- passes: delegated.passes,
433
- }
434
- : undefined,
435
- observability: buildOrchestrationObservability({
436
- policy,
437
- executionPath,
438
- delegated,
439
- }),
440
- },
441
- };
442
- } catch (err) {
443
- const message = err instanceof Error ? err.message : String(err);
444
- return jsonResult({ error: message });
445
- }
446
- }
447
-
448
- return jsonResult({
449
- error: "Either summaryIds or query must be provided.",
450
- });
451
- },
452
- };
453
- }