@jonathangu/openclawbrain 0.3.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/LICENSE +21 -0
- package/README.md +412 -0
- package/bin/openclawbrain.js +15 -0
- package/docs/END_STATE.md +244 -0
- package/docs/EVIDENCE.md +128 -0
- package/docs/RELEASE_CONTRACT.md +91 -0
- package/docs/agent-tools.md +106 -0
- package/docs/architecture.md +224 -0
- package/docs/configuration.md +178 -0
- package/docs/evidence/2026-03-16/3188b50c4ed30f07dea111e35ce52aabefaced63/brain-teach-session-bound/status.json +87 -0
- package/docs/evidence/2026-03-16/3188b50c4ed30f07dea111e35ce52aabefaced63/brain-teach-session-bound/summary.md +16 -0
- package/docs/evidence/2026-03-16/3188b50c4ed30f07dea111e35ce52aabefaced63/brain-teach-session-bound/trace.json +273 -0
- package/docs/evidence/2026-03-16/3188b50c4ed30f07dea111e35ce52aabefaced63/brain-teach-session-bound/validation-report.json +652 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/channels-status.txt +31 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/config-snapshot.json +66 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/doctor.json +14 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/gateway-probe.txt +34 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/gateway-status.txt +41 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/logs.txt +428 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/status-all.txt +60 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/status.json +223 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/summary.md +13 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/trace.json +4 -0
- package/docs/evidence/2026-03-16/4941429588810da5d6f7ef1509f229f83fa08031/validation-report.json +334 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/channels-status.txt +25 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/config-snapshot.json +91 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/doctor.json +14 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/gateway-probe.txt +36 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/gateway-status.txt +44 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/logs.txt +428 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/short-static-classification/preflight-doctor.json +10 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/short-static-classification/preflight-sdk-probe.json +11 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/short-static-classification/preflight-setup-only.json +12 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/short-static-classification/summary.md +30 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/short-static-classification/validation-report.json +72 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/status-all.txt +63 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/status.json +200 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/summary.md +13 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/trace.json +4 -0
- package/docs/evidence/2026-03-16/7f8dbcb27e741abdeefd5656c210639d0acdd440/validation-report.json +311 -0
- package/docs/evidence/README.md +16 -0
- package/docs/fts5.md +161 -0
- package/docs/tui.md +506 -0
- package/index.ts +1372 -0
- package/openclaw.plugin.json +136 -0
- package/package.json +66 -0
- package/src/assembler.ts +804 -0
- package/src/brain-cli.ts +316 -0
- package/src/brain-core/decay.ts +35 -0
- package/src/brain-core/episode.ts +82 -0
- package/src/brain-core/graph.ts +321 -0
- package/src/brain-core/health.ts +116 -0
- package/src/brain-core/mutator.ts +281 -0
- package/src/brain-core/pack.ts +117 -0
- package/src/brain-core/policy.ts +153 -0
- package/src/brain-core/replay.ts +1 -0
- package/src/brain-core/teacher.ts +105 -0
- package/src/brain-core/trace.ts +40 -0
- package/src/brain-core/traverse.ts +230 -0
- package/src/brain-core/types.ts +405 -0
- package/src/brain-core/update.ts +123 -0
- package/src/brain-harvest/human.ts +46 -0
- package/src/brain-harvest/scanner.ts +98 -0
- package/src/brain-harvest/self.ts +147 -0
- package/src/brain-runtime/assembler-extension.ts +230 -0
- package/src/brain-runtime/evidence-detectors.ts +68 -0
- package/src/brain-runtime/graph-io.ts +72 -0
- package/src/brain-runtime/harvester-extension.ts +98 -0
- package/src/brain-runtime/service.ts +659 -0
- package/src/brain-runtime/tools.ts +109 -0
- package/src/brain-runtime/worker-state.ts +106 -0
- package/src/brain-runtime/worker-supervisor.ts +169 -0
- package/src/brain-store/embedding.ts +179 -0
- package/src/brain-store/init.ts +347 -0
- package/src/brain-store/migrations.ts +188 -0
- package/src/brain-store/store.ts +816 -0
- package/src/brain-worker/child-runner.ts +321 -0
- package/src/brain-worker/jobs.ts +12 -0
- package/src/brain-worker/mutation-job.ts +5 -0
- package/src/brain-worker/promotion-job.ts +5 -0
- package/src/brain-worker/protocol.ts +79 -0
- package/src/brain-worker/teacher-job.ts +5 -0
- package/src/brain-worker/update-job.ts +5 -0
- package/src/brain-worker/worker.ts +422 -0
- package/src/compaction.ts +1332 -0
- package/src/db/config.ts +265 -0
- package/src/db/connection.ts +72 -0
- package/src/db/features.ts +42 -0
- package/src/db/migration.ts +561 -0
- package/src/engine.ts +1995 -0
- package/src/expansion-auth.ts +351 -0
- package/src/expansion-policy.ts +303 -0
- package/src/expansion.ts +383 -0
- package/src/integrity.ts +600 -0
- package/src/large-files.ts +527 -0
- package/src/openclaw-bridge.ts +22 -0
- package/src/retrieval.ts +357 -0
- package/src/store/conversation-store.ts +748 -0
- package/src/store/fts5-sanitize.ts +29 -0
- package/src/store/full-text-fallback.ts +74 -0
- package/src/store/index.ts +29 -0
- package/src/store/summary-store.ts +918 -0
- package/src/summarize.ts +847 -0
- package/src/tools/common.ts +53 -0
- package/src/tools/lcm-conversation-scope.ts +76 -0
- package/src/tools/lcm-describe-tool.ts +234 -0
- package/src/tools/lcm-expand-query-tool.ts +594 -0
- package/src/tools/lcm-expand-tool.delegation.ts +556 -0
- package/src/tools/lcm-expand-tool.ts +448 -0
- package/src/tools/lcm-expansion-recursion-guard.ts +286 -0
- package/src/tools/lcm-grep-tool.ts +200 -0
- package/src/transcript-repair.ts +301 -0
- package/src/types.ts +149 -0
package/src/expansion.ts
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { LcmConfig } from "./db/config.js";
|
|
3
|
+
import type { RetrievalEngine, ExpandResult, GrepResult } from "./retrieval.js";
|
|
4
|
+
|
|
5
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export type ExpansionRequest = {
|
|
8
|
+
/** Summary IDs to expand */
|
|
9
|
+
summaryIds: string[];
|
|
10
|
+
/** Max traversal depth per summary (default: 3) */
|
|
11
|
+
maxDepth?: number;
|
|
12
|
+
/** Max tokens across the entire expansion (default: config.maxExpandTokens) */
|
|
13
|
+
tokenCap?: number;
|
|
14
|
+
/** Whether to include raw source messages at leaf level */
|
|
15
|
+
includeMessages?: boolean;
|
|
16
|
+
/** Conversation ID scope */
|
|
17
|
+
conversationId: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ExpansionResult = {
|
|
21
|
+
/** Expanded summaries with their children/messages */
|
|
22
|
+
expansions: Array<{
|
|
23
|
+
summaryId: string;
|
|
24
|
+
children: Array<{
|
|
25
|
+
summaryId: string;
|
|
26
|
+
kind: string;
|
|
27
|
+
snippet: string;
|
|
28
|
+
tokenCount: number;
|
|
29
|
+
}>;
|
|
30
|
+
messages: Array<{
|
|
31
|
+
messageId: number;
|
|
32
|
+
role: string;
|
|
33
|
+
snippet: string;
|
|
34
|
+
tokenCount: number;
|
|
35
|
+
}>;
|
|
36
|
+
}>;
|
|
37
|
+
/** Cited IDs for follow-up traversal */
|
|
38
|
+
citedIds: string[];
|
|
39
|
+
/** Total tokens in the result */
|
|
40
|
+
totalTokens: number;
|
|
41
|
+
/** Whether any expansion was truncated */
|
|
42
|
+
truncated: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const SNIPPET_MAX_CHARS = 200;
|
|
48
|
+
|
|
49
|
+
/** Truncate content to a short snippet for display. */
|
|
50
|
+
function truncateSnippet(content: string, maxChars: number = SNIPPET_MAX_CHARS): string {
|
|
51
|
+
if (content.length <= maxChars) {
|
|
52
|
+
return content;
|
|
53
|
+
}
|
|
54
|
+
return content.slice(0, maxChars) + "...";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the effective expansion token cap by applying a configured default
|
|
59
|
+
* and an explicit upper bound.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveExpansionTokenCap(input: {
|
|
62
|
+
requestedTokenCap?: number;
|
|
63
|
+
maxExpandTokens: number;
|
|
64
|
+
}): number {
|
|
65
|
+
const maxExpandTokens = Math.max(1, Math.trunc(input.maxExpandTokens));
|
|
66
|
+
const requestedTokenCap = input.requestedTokenCap;
|
|
67
|
+
if (typeof requestedTokenCap !== "number" || !Number.isFinite(requestedTokenCap)) {
|
|
68
|
+
return maxExpandTokens;
|
|
69
|
+
}
|
|
70
|
+
return Math.min(Math.max(1, Math.trunc(requestedTokenCap)), maxExpandTokens);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Convert a single RetrievalEngine.expand() result into the ExpansionResult
|
|
75
|
+
* entry format, truncating content to short snippets.
|
|
76
|
+
*/
|
|
77
|
+
function toExpansionEntry(
|
|
78
|
+
summaryId: string,
|
|
79
|
+
raw: ExpandResult,
|
|
80
|
+
): ExpansionResult["expansions"][number] {
|
|
81
|
+
return {
|
|
82
|
+
summaryId,
|
|
83
|
+
children: raw.children.map((c) => ({
|
|
84
|
+
summaryId: c.summaryId,
|
|
85
|
+
kind: c.kind,
|
|
86
|
+
snippet: truncateSnippet(c.content),
|
|
87
|
+
tokenCount: c.tokenCount,
|
|
88
|
+
})),
|
|
89
|
+
messages: raw.messages.map((m) => ({
|
|
90
|
+
messageId: m.messageId,
|
|
91
|
+
role: m.role,
|
|
92
|
+
snippet: truncateSnippet(m.content),
|
|
93
|
+
tokenCount: m.tokenCount,
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Collect all referenced summary IDs from an expansion entry. */
|
|
99
|
+
function collectCitedIds(entry: ExpansionResult["expansions"][number]): string[] {
|
|
100
|
+
const ids: string[] = [entry.summaryId];
|
|
101
|
+
for (const child of entry.children) {
|
|
102
|
+
ids.push(child.summaryId);
|
|
103
|
+
}
|
|
104
|
+
return ids;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── ExpansionOrchestrator ────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
export class ExpansionOrchestrator {
|
|
110
|
+
constructor(private retrieval: RetrievalEngine) {}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Expand each summary ID using the RetrievalEngine, collecting results and
|
|
114
|
+
* enforcing a global token cap across all expansions.
|
|
115
|
+
*/
|
|
116
|
+
async expand(request: ExpansionRequest): Promise<ExpansionResult> {
|
|
117
|
+
const maxDepth = request.maxDepth ?? 3;
|
|
118
|
+
const tokenCap = request.tokenCap ?? Infinity;
|
|
119
|
+
const includeMessages = request.includeMessages ?? false;
|
|
120
|
+
|
|
121
|
+
const result: ExpansionResult = {
|
|
122
|
+
expansions: [],
|
|
123
|
+
citedIds: [],
|
|
124
|
+
totalTokens: 0,
|
|
125
|
+
truncated: false,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const citedSet = new Set<string>();
|
|
129
|
+
|
|
130
|
+
for (const summaryId of request.summaryIds) {
|
|
131
|
+
if (result.truncated) {
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Calculate remaining budget for this expansion
|
|
136
|
+
const remainingBudget = tokenCap - result.totalTokens;
|
|
137
|
+
if (remainingBudget <= 0) {
|
|
138
|
+
result.truncated = true;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const raw = await this.retrieval.expand({
|
|
143
|
+
summaryId,
|
|
144
|
+
depth: maxDepth,
|
|
145
|
+
includeMessages,
|
|
146
|
+
tokenCap: remainingBudget,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const entry = toExpansionEntry(summaryId, raw);
|
|
150
|
+
result.expansions.push(entry);
|
|
151
|
+
result.totalTokens += raw.estimatedTokens;
|
|
152
|
+
|
|
153
|
+
// Track cited IDs
|
|
154
|
+
for (const id of collectCitedIds(entry)) {
|
|
155
|
+
citedSet.add(id);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (raw.truncated) {
|
|
159
|
+
result.truncated = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
result.citedIds = [...citedSet];
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Convenience method: grep for matching summaries, then expand the top results.
|
|
169
|
+
* Combines the routing pass (grep) with the deep expansion pass.
|
|
170
|
+
*/
|
|
171
|
+
async describeAndExpand(input: {
|
|
172
|
+
query: string;
|
|
173
|
+
mode: "regex" | "full_text";
|
|
174
|
+
conversationId?: number;
|
|
175
|
+
maxDepth?: number;
|
|
176
|
+
tokenCap?: number;
|
|
177
|
+
}): Promise<ExpansionResult> {
|
|
178
|
+
const grepResult: GrepResult = await this.retrieval.grep({
|
|
179
|
+
query: input.query,
|
|
180
|
+
mode: input.mode,
|
|
181
|
+
scope: "summaries",
|
|
182
|
+
conversationId: input.conversationId,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const summaryIds = [...grepResult.summaries]
|
|
186
|
+
.sort((a, b) => {
|
|
187
|
+
const recencyDelta = b.createdAt.getTime() - a.createdAt.getTime();
|
|
188
|
+
if (recencyDelta !== 0) {
|
|
189
|
+
return recencyDelta;
|
|
190
|
+
}
|
|
191
|
+
const aRank = a.rank ?? Number.POSITIVE_INFINITY;
|
|
192
|
+
const bRank = b.rank ?? Number.POSITIVE_INFINITY;
|
|
193
|
+
return aRank - bRank;
|
|
194
|
+
})
|
|
195
|
+
.map((s) => s.summaryId);
|
|
196
|
+
if (summaryIds.length === 0) {
|
|
197
|
+
return {
|
|
198
|
+
expansions: [],
|
|
199
|
+
citedIds: [],
|
|
200
|
+
totalTokens: 0,
|
|
201
|
+
truncated: false,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return this.expand({
|
|
206
|
+
summaryIds,
|
|
207
|
+
maxDepth: input.maxDepth,
|
|
208
|
+
tokenCap: input.tokenCap,
|
|
209
|
+
includeMessages: false,
|
|
210
|
+
conversationId: input.conversationId ?? 0,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── Distill for subagent ─────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Format an ExpansionResult into a compact text payload suitable for passing
|
|
219
|
+
* to a subagent or returning to the main agent.
|
|
220
|
+
*/
|
|
221
|
+
export function distillForSubagent(result: ExpansionResult): string {
|
|
222
|
+
const lines: string[] = [];
|
|
223
|
+
|
|
224
|
+
lines.push(
|
|
225
|
+
`## Expansion Results (${result.expansions.length} summaries, ${result.totalTokens} total tokens)`,
|
|
226
|
+
);
|
|
227
|
+
lines.push("");
|
|
228
|
+
|
|
229
|
+
for (const entry of result.expansions) {
|
|
230
|
+
// Determine kind from children presence: if it has children it was a condensed node
|
|
231
|
+
const kind = entry.children.length > 0 ? "condensed" : "leaf";
|
|
232
|
+
const tokenSum =
|
|
233
|
+
entry.children.reduce((sum, c) => sum + c.tokenCount, 0) +
|
|
234
|
+
entry.messages.reduce((sum, m) => sum + m.tokenCount, 0);
|
|
235
|
+
|
|
236
|
+
lines.push(`### ${entry.summaryId} (${kind}, ${tokenSum} tokens)`);
|
|
237
|
+
|
|
238
|
+
if (entry.children.length > 0) {
|
|
239
|
+
lines.push(`Children: ${entry.children.map((c) => c.summaryId).join(", ")}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (entry.messages.length > 0) {
|
|
243
|
+
const msgParts = entry.messages.map(
|
|
244
|
+
(m) => `msg#${m.messageId} (${m.role}, ${m.tokenCount} tokens)`,
|
|
245
|
+
);
|
|
246
|
+
lines.push(`Messages: ${msgParts.join(", ")}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Show a snippet for children that have content
|
|
250
|
+
for (const child of entry.children) {
|
|
251
|
+
if (child.snippet) {
|
|
252
|
+
lines.push(`[Snippet: ${truncateSnippet(child.snippet)}]`);
|
|
253
|
+
break; // Only show one snippet per entry to keep it compact
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
lines.push("");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (result.citedIds.length > 0) {
|
|
261
|
+
lines.push(`Cited IDs for follow-up: ${result.citedIds.join(", ")}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
lines.push(`[Truncated: ${result.truncated ? "yes" : "no"}]`);
|
|
265
|
+
|
|
266
|
+
return lines.join("\n");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── Tool definition ──────────────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
const LcmExpansionSchema = Type.Object({
|
|
272
|
+
summaryIds: Type.Optional(
|
|
273
|
+
Type.Array(Type.String(), {
|
|
274
|
+
description: "Summary IDs to expand (e.g. sum_abc123). Required if query is not provided.",
|
|
275
|
+
}),
|
|
276
|
+
),
|
|
277
|
+
query: Type.Optional(
|
|
278
|
+
Type.String({
|
|
279
|
+
description:
|
|
280
|
+
"Text query to grep for matching summaries before expanding. " +
|
|
281
|
+
"If provided, summaryIds is ignored and the top grep results are expanded instead.",
|
|
282
|
+
}),
|
|
283
|
+
),
|
|
284
|
+
maxDepth: Type.Optional(
|
|
285
|
+
Type.Number({
|
|
286
|
+
description: "Max traversal depth per summary (default: 3).",
|
|
287
|
+
minimum: 1,
|
|
288
|
+
maximum: 10,
|
|
289
|
+
}),
|
|
290
|
+
),
|
|
291
|
+
tokenCap: Type.Optional(
|
|
292
|
+
Type.Number({
|
|
293
|
+
description: "Max tokens across the entire expansion result.",
|
|
294
|
+
minimum: 1,
|
|
295
|
+
}),
|
|
296
|
+
),
|
|
297
|
+
includeMessages: Type.Optional(
|
|
298
|
+
Type.Boolean({
|
|
299
|
+
description: "Whether to include raw source messages at leaf level (default: false).",
|
|
300
|
+
}),
|
|
301
|
+
),
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Build a tool definition object for LCM expansion that can be registered as
|
|
306
|
+
* an agent tool. Follows the pattern used in `src/agents/tools/`.
|
|
307
|
+
*
|
|
308
|
+
* Requires an already-initialised ExpansionOrchestrator and an LcmConfig
|
|
309
|
+
* (for the default tokenCap).
|
|
310
|
+
*/
|
|
311
|
+
export function buildExpansionToolDefinition(options: {
|
|
312
|
+
orchestrator: ExpansionOrchestrator;
|
|
313
|
+
config: LcmConfig;
|
|
314
|
+
conversationId: number;
|
|
315
|
+
}) {
|
|
316
|
+
const { orchestrator, config, conversationId } = options;
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
name: "lcm_expand",
|
|
320
|
+
description:
|
|
321
|
+
"Expand compacted conversation summaries from LCM (Lossless Context Management). " +
|
|
322
|
+
"Traverses the summary DAG to retrieve children and source messages. " +
|
|
323
|
+
"Use this to drill into previously-compacted context when you need detail " +
|
|
324
|
+
"that was summarised away. Returns a compact text payload with cited IDs for follow-up.",
|
|
325
|
+
parameters: LcmExpansionSchema,
|
|
326
|
+
execute: async (
|
|
327
|
+
_toolCallId: string,
|
|
328
|
+
params: Record<string, unknown>,
|
|
329
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; details: unknown }> => {
|
|
330
|
+
const summaryIds = params.summaryIds as string[] | undefined;
|
|
331
|
+
const query = typeof params.query === "string" ? params.query.trim() : undefined;
|
|
332
|
+
const maxDepth =
|
|
333
|
+
typeof params.maxDepth === "number" ? Math.trunc(params.maxDepth) : undefined;
|
|
334
|
+
const requestedTokenCap =
|
|
335
|
+
typeof params.tokenCap === "number" ? Math.trunc(params.tokenCap) : undefined;
|
|
336
|
+
const tokenCap = resolveExpansionTokenCap({
|
|
337
|
+
requestedTokenCap,
|
|
338
|
+
maxExpandTokens: config.maxExpandTokens,
|
|
339
|
+
});
|
|
340
|
+
const includeMessages =
|
|
341
|
+
typeof params.includeMessages === "boolean" ? params.includeMessages : false;
|
|
342
|
+
|
|
343
|
+
let result: ExpansionResult;
|
|
344
|
+
|
|
345
|
+
if (query) {
|
|
346
|
+
// Grep-first path: find summaries matching the query, then expand
|
|
347
|
+
result = await orchestrator.describeAndExpand({
|
|
348
|
+
query,
|
|
349
|
+
mode: "full_text",
|
|
350
|
+
conversationId,
|
|
351
|
+
maxDepth,
|
|
352
|
+
tokenCap,
|
|
353
|
+
});
|
|
354
|
+
} else if (summaryIds && summaryIds.length > 0) {
|
|
355
|
+
// Direct expansion of specific summary IDs
|
|
356
|
+
result = await orchestrator.expand({
|
|
357
|
+
summaryIds,
|
|
358
|
+
maxDepth,
|
|
359
|
+
tokenCap,
|
|
360
|
+
includeMessages,
|
|
361
|
+
conversationId,
|
|
362
|
+
});
|
|
363
|
+
} else {
|
|
364
|
+
const text = "Error: either summaryIds or query must be provided.";
|
|
365
|
+
return {
|
|
366
|
+
content: [{ type: "text", text }],
|
|
367
|
+
details: { error: text },
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const distilled = distillForSubagent(result);
|
|
372
|
+
return {
|
|
373
|
+
content: [{ type: "text", text: distilled }],
|
|
374
|
+
details: {
|
|
375
|
+
expansionCount: result.expansions.length,
|
|
376
|
+
citedIds: result.citedIds,
|
|
377
|
+
totalTokens: result.totalTokens,
|
|
378
|
+
truncated: result.truncated,
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
}
|