@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.
Files changed (38) hide show
  1. package/README.md +26 -6
  2. package/docs/agent-tools.md +16 -5
  3. package/docs/configuration.md +223 -214
  4. package/openclaw.plugin.json +123 -0
  5. package/package.json +1 -1
  6. package/skills/lossless-claw/SKILL.md +3 -2
  7. package/skills/lossless-claw/references/architecture.md +12 -0
  8. package/skills/lossless-claw/references/config.md +135 -3
  9. package/skills/lossless-claw/references/diagnostics.md +13 -0
  10. package/src/assembler.ts +17 -5
  11. package/src/compaction.ts +161 -53
  12. package/src/db/config.ts +102 -4
  13. package/src/db/connection.ts +35 -7
  14. package/src/db/features.ts +24 -5
  15. package/src/db/migration.ts +257 -78
  16. package/src/engine.ts +1007 -110
  17. package/src/estimate-tokens.ts +80 -0
  18. package/src/lcm-log.ts +37 -0
  19. package/src/plugin/index.ts +493 -101
  20. package/src/plugin/lcm-command.ts +288 -7
  21. package/src/plugin/lcm-doctor-apply.ts +1 -3
  22. package/src/plugin/lcm-doctor-cleaners.ts +655 -0
  23. package/src/plugin/shared-init.ts +59 -0
  24. package/src/prune.ts +391 -0
  25. package/src/retrieval.ts +8 -9
  26. package/src/startup-banner-log.ts +1 -0
  27. package/src/store/compaction-telemetry-store.ts +156 -0
  28. package/src/store/conversation-store.ts +6 -1
  29. package/src/store/fts5-sanitize.ts +25 -4
  30. package/src/store/full-text-sort.ts +21 -0
  31. package/src/store/index.ts +8 -0
  32. package/src/store/summary-store.ts +21 -14
  33. package/src/summarize.ts +55 -34
  34. package/src/tools/lcm-describe-tool.ts +9 -4
  35. package/src/tools/lcm-expand-query-tool.ts +609 -200
  36. package/src/tools/lcm-expand-tool.ts +9 -4
  37. package/src/tools/lcm-grep-tool.ts +22 -8
  38. package/src/types.ts +1 -0
@@ -122,7 +122,8 @@ function buildOrchestrationObservability(input: {
122
122
  */
123
123
  export function createLcmExpandTool(input: {
124
124
  deps: LcmDependencies;
125
- lcm: LcmContextEngine;
125
+ lcm?: LcmContextEngine;
126
+ getLcm?: () => Promise<LcmContextEngine>;
126
127
  /** Runtime session key (used for delegated expansion auth scoping). */
127
128
  sessionId?: string;
128
129
  sessionKey?: string;
@@ -136,10 +137,14 @@ export function createLcmExpandTool(input: {
136
137
  "Use this to drill into previously-compacted context when you need detail " +
137
138
  "that was summarised away. Provide either summaryIds (direct expansion) or " +
138
139
  "query (grep-first, then expand top matches). Returns a compact text payload " +
139
- "with cited IDs for follow-up.",
140
+ "plus cited IDs in tool output for follow-up.",
140
141
  parameters: LcmExpandSchema,
141
142
  async execute(_toolCallId, params) {
142
- const retrieval = input.lcm.getRetrieval();
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();
143
148
  const orchestrator = new ExpansionOrchestrator(retrieval);
144
149
  const runtimeAuthManager = getRuntimeExpansionAuthManager();
145
150
 
@@ -178,7 +183,7 @@ export function createLcmExpandTool(input: {
178
183
  }
179
184
 
180
185
  const conversationScope = await resolveLcmConversationScope({
181
- lcm: input.lcm,
186
+ lcm,
182
187
  deps: input.deps,
183
188
  sessionId: input.sessionId,
184
189
  sessionKey: input.sessionKey,
@@ -25,7 +25,7 @@ function formatDisplayTime(
25
25
  const LcmGrepSchema = Type.Object({
26
26
  pattern: Type.String({
27
27
  description:
28
- "Search pattern. Interpreted as regex when mode is 'regex', or as a text query for 'full_text' mode.",
28
+ 'Search pattern. Interpreted as regex when mode is "regex", or as an FTS5 text query when mode is "full_text". In full_text mode, FTS5 defaults to AND matching, so prefer 1-3 distinctive terms or one quoted multi-word phrase instead of padding with synonyms or extra keywords.',
29
29
  }),
30
30
  mode: Type.Optional(
31
31
  Type.String({
@@ -70,6 +70,13 @@ const LcmGrepSchema = Type.Object({
70
70
  maximum: 200,
71
71
  }),
72
72
  ),
73
+ sort: Type.Optional(
74
+ Type.String({
75
+ description:
76
+ 'Sort order: "recency" (newest first, default), "relevance" (best FTS5 match first, full_text mode only), or "hybrid" (full_text mode only; balances relevance with recency). Applied before limit is enforced.',
77
+ enum: ["recency", "relevance", "hybrid"],
78
+ }),
79
+ ),
73
80
  });
74
81
 
75
82
  function truncateSnippet(content: string, maxLen: number = 200): string {
@@ -82,7 +89,8 @@ function truncateSnippet(content: string, maxLen: number = 200): string {
82
89
 
83
90
  export function createLcmGrepTool(input: {
84
91
  deps: LcmDependencies;
85
- lcm: LcmContextEngine;
92
+ lcm?: LcmContextEngine;
93
+ getLcm?: () => Promise<LcmContextEngine>;
86
94
  sessionId?: string;
87
95
  sessionKey?: string;
88
96
  }): AnyAgentTool {
@@ -93,18 +101,24 @@ export function createLcmGrepTool(input: {
93
101
  "Search compacted conversation history using regex or full-text search. " +
94
102
  "Searches across messages and/or summaries stored by LCM. " +
95
103
  "Use this to find specific content that may have been compacted away from " +
96
- "active context. Returns matching snippets with their summary/message IDs " +
104
+ "active context. In full_text mode, queries use FTS5 AND semantics by default, so keep them short and focused; quoted phrases stay intact and optional sort modes can prioritize relevance for older topics. Returns matching snippets with their summary/message IDs " +
97
105
  "for follow-up with lcm_expand or lcm_describe.",
98
106
  parameters: LcmGrepSchema,
99
107
  async execute(_toolCallId, params) {
100
- const retrieval = input.lcm.getRetrieval();
101
- const timezone = input.lcm.timezone;
108
+ const lcm = input.lcm ?? (await input.getLcm?.());
109
+ if (!lcm) {
110
+ throw new Error("LCM engine is unavailable.");
111
+ }
112
+ const retrieval = lcm.getRetrieval();
113
+ const timezone = lcm.timezone;
102
114
 
103
115
  const p = params as Record<string, unknown>;
104
116
  const pattern = (p.pattern as string).trim();
105
117
  const mode = (p.mode as "regex" | "full_text") ?? "regex";
106
118
  const scope = (p.scope as "messages" | "summaries" | "both") ?? "both";
107
119
  const limit = typeof p.limit === "number" ? Math.trunc(p.limit) : 50;
120
+ const requestedSort = (p.sort as "recency" | "relevance" | "hybrid") ?? "recency";
121
+ const effectiveSort = mode === "full_text" ? requestedSort : "recency";
108
122
  let since: Date | undefined;
109
123
  let before: Date | undefined;
110
124
  try {
@@ -121,7 +135,7 @@ export function createLcmGrepTool(input: {
121
135
  });
122
136
  }
123
137
  const conversationScope = await resolveLcmConversationScope({
124
- lcm: input.lcm,
138
+ lcm,
125
139
  deps: input.deps,
126
140
  sessionId: input.sessionId,
127
141
  sessionKey: input.sessionKey,
@@ -133,7 +147,6 @@ export function createLcmGrepTool(input: {
133
147
  "No LCM conversation found for this session. Provide conversationId or set allConversations=true.",
134
148
  });
135
149
  }
136
-
137
150
  const result = await retrieval.grep({
138
151
  query: pattern,
139
152
  mode,
@@ -142,12 +155,13 @@ export function createLcmGrepTool(input: {
142
155
  limit,
143
156
  since,
144
157
  before,
158
+ sort: effectiveSort,
145
159
  });
146
160
 
147
161
  const lines: string[] = [];
148
162
  lines.push("## LCM Grep Results");
149
163
  lines.push(`**Pattern:** \`${pattern}\``);
150
- lines.push(`**Mode:** ${mode} | **Scope:** ${scope}`);
164
+ lines.push(`**Mode:** ${mode} | **Scope:** ${scope} | **Sort:** ${effectiveSort}`);
151
165
  if (conversationScope.allConversations) {
152
166
  lines.push("**Conversation scope:** all conversations");
153
167
  } else if (conversationScope.conversationId != null) {
package/src/types.ts CHANGED
@@ -39,6 +39,7 @@ export type CompleteFn = (params: {
39
39
  authProfileId?: string;
40
40
  agentDir?: string;
41
41
  runtimeConfig?: unknown;
42
+ skipModelAuth?: boolean;
42
43
  messages: Array<{ role: string; content: unknown }>;
43
44
  system?: string;
44
45
  maxTokens: number;