@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
|
@@ -0,0 +1,448 @@
|
|
|
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
|
+
/** Runtime session key (used for delegated expansion auth scoping). */
|
|
127
|
+
sessionId?: string;
|
|
128
|
+
sessionKey?: string;
|
|
129
|
+
}): AnyAgentTool {
|
|
130
|
+
return {
|
|
131
|
+
name: "lcm_expand",
|
|
132
|
+
label: "LCM Expand",
|
|
133
|
+
description:
|
|
134
|
+
"Expand compacted conversation summaries from LCM (Lossless Context Management). " +
|
|
135
|
+
"Traverses the summary DAG to retrieve children and source messages. " +
|
|
136
|
+
"Use this to drill into previously-compacted context when you need detail " +
|
|
137
|
+
"that was summarised away. Provide either summaryIds (direct expansion) or " +
|
|
138
|
+
"query (grep-first, then expand top matches). Returns a compact text payload " +
|
|
139
|
+
"with cited IDs for follow-up.",
|
|
140
|
+
parameters: LcmExpandSchema,
|
|
141
|
+
async execute(_toolCallId, params) {
|
|
142
|
+
const retrieval = input.lcm.getRetrieval();
|
|
143
|
+
const orchestrator = new ExpansionOrchestrator(retrieval);
|
|
144
|
+
const runtimeAuthManager = getRuntimeExpansionAuthManager();
|
|
145
|
+
|
|
146
|
+
const p = params as Record<string, unknown>;
|
|
147
|
+
const summaryIds = p.summaryIds as string[] | undefined;
|
|
148
|
+
const query = typeof p.query === "string" ? p.query.trim() : undefined;
|
|
149
|
+
const maxDepth = typeof p.maxDepth === "number" ? Math.trunc(p.maxDepth) : undefined;
|
|
150
|
+
const requestedTokenCap = typeof p.tokenCap === "number" ? Math.trunc(p.tokenCap) : undefined;
|
|
151
|
+
const tokenCap =
|
|
152
|
+
typeof requestedTokenCap === "number" && Number.isFinite(requestedTokenCap)
|
|
153
|
+
? Math.max(1, requestedTokenCap)
|
|
154
|
+
: undefined;
|
|
155
|
+
const includeMessages = typeof p.includeMessages === "boolean" ? p.includeMessages : false;
|
|
156
|
+
const sessionKey =
|
|
157
|
+
(typeof input.sessionKey === "string" ? input.sessionKey : input.sessionId)?.trim() ?? "";
|
|
158
|
+
if (!input.deps.isSubagentSessionKey(sessionKey)) {
|
|
159
|
+
return jsonResult({
|
|
160
|
+
error:
|
|
161
|
+
"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.",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const isDelegatedSession = input.deps.isSubagentSessionKey(sessionKey);
|
|
165
|
+
const delegatedGrantId = isDelegatedSession
|
|
166
|
+
? (resolveDelegatedExpansionGrantId(sessionKey) ?? undefined)
|
|
167
|
+
: undefined;
|
|
168
|
+
const delegatedGrant =
|
|
169
|
+
delegatedGrantId !== undefined ? runtimeAuthManager.getGrant(delegatedGrantId) : null;
|
|
170
|
+
const authorizedOrchestrator =
|
|
171
|
+
delegatedGrantId !== undefined ? wrapWithAuth(orchestrator, runtimeAuthManager) : null;
|
|
172
|
+
|
|
173
|
+
if (isDelegatedSession && !delegatedGrantId) {
|
|
174
|
+
return jsonResult({
|
|
175
|
+
error:
|
|
176
|
+
"Delegated expansion requires a valid grant. This sub-agent session has no propagated expansion grant.",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const conversationScope = await resolveLcmConversationScope({
|
|
181
|
+
lcm: input.lcm,
|
|
182
|
+
deps: input.deps,
|
|
183
|
+
sessionId: input.sessionId,
|
|
184
|
+
sessionKey: input.sessionKey,
|
|
185
|
+
params: p,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const runExpand = async (input: {
|
|
189
|
+
summaryIds: string[];
|
|
190
|
+
conversationId: number;
|
|
191
|
+
maxDepth?: number;
|
|
192
|
+
tokenCap?: number;
|
|
193
|
+
includeMessages?: boolean;
|
|
194
|
+
}) => {
|
|
195
|
+
if (!authorizedOrchestrator || !delegatedGrantId) {
|
|
196
|
+
return orchestrator.expand(input);
|
|
197
|
+
}
|
|
198
|
+
return authorizedOrchestrator.expand(delegatedGrantId, input);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const resolvedConversationId =
|
|
202
|
+
conversationScope.conversationId ??
|
|
203
|
+
(delegatedGrant?.allowedConversationIds.length === 1
|
|
204
|
+
? delegatedGrant.allowedConversationIds[0]
|
|
205
|
+
: undefined);
|
|
206
|
+
|
|
207
|
+
if (query) {
|
|
208
|
+
try {
|
|
209
|
+
if (resolvedConversationId == null) {
|
|
210
|
+
const result = await orchestrator.describeAndExpand({
|
|
211
|
+
query,
|
|
212
|
+
mode: "full_text",
|
|
213
|
+
conversationId: undefined,
|
|
214
|
+
maxDepth,
|
|
215
|
+
tokenCap,
|
|
216
|
+
});
|
|
217
|
+
const text = distillForSubagent(result);
|
|
218
|
+
const policy = decideLcmExpansionRouting({
|
|
219
|
+
intent: "query_probe",
|
|
220
|
+
query,
|
|
221
|
+
requestedMaxDepth: maxDepth,
|
|
222
|
+
candidateSummaryCount: result.expansions.length,
|
|
223
|
+
tokenCap: tokenCap ?? Number.MAX_SAFE_INTEGER,
|
|
224
|
+
includeMessages: false,
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
content: [{ type: "text", text }],
|
|
228
|
+
details: {
|
|
229
|
+
expansionCount: result.expansions.length,
|
|
230
|
+
citedIds: result.citedIds,
|
|
231
|
+
totalTokens: result.totalTokens,
|
|
232
|
+
truncated: result.truncated,
|
|
233
|
+
policy,
|
|
234
|
+
executionPath: "direct",
|
|
235
|
+
observability: buildOrchestrationObservability({
|
|
236
|
+
policy,
|
|
237
|
+
executionPath: "direct",
|
|
238
|
+
}),
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const grepResult = await retrieval.grep({
|
|
243
|
+
query,
|
|
244
|
+
mode: "full_text",
|
|
245
|
+
scope: "summaries",
|
|
246
|
+
conversationId: resolvedConversationId,
|
|
247
|
+
});
|
|
248
|
+
const matchedSummaryIds = grepResult.summaries.map((entry) => entry.summaryId);
|
|
249
|
+
const policy = decideLcmExpansionRouting({
|
|
250
|
+
intent: "query_probe",
|
|
251
|
+
query,
|
|
252
|
+
requestedMaxDepth: maxDepth,
|
|
253
|
+
candidateSummaryCount: matchedSummaryIds.length,
|
|
254
|
+
tokenCap: tokenCap ?? Number.MAX_SAFE_INTEGER,
|
|
255
|
+
includeMessages: false,
|
|
256
|
+
});
|
|
257
|
+
const canDelegate =
|
|
258
|
+
matchedSummaryIds.length > 0 &&
|
|
259
|
+
policy.action === "delegate_traversal" &&
|
|
260
|
+
!isDelegatedSession &&
|
|
261
|
+
!!sessionKey;
|
|
262
|
+
const delegated =
|
|
263
|
+
canDelegate && resolvedConversationId != null
|
|
264
|
+
? await runDelegatedExpansionLoop({
|
|
265
|
+
deps: input.deps,
|
|
266
|
+
requesterSessionKey: sessionKey,
|
|
267
|
+
conversationId: resolvedConversationId,
|
|
268
|
+
summaryIds: matchedSummaryIds,
|
|
269
|
+
maxDepth,
|
|
270
|
+
tokenCap,
|
|
271
|
+
includeMessages: false,
|
|
272
|
+
query,
|
|
273
|
+
})
|
|
274
|
+
: undefined;
|
|
275
|
+
if (delegated && delegated.status === "ok") {
|
|
276
|
+
return {
|
|
277
|
+
content: [{ type: "text", text: delegated.text }],
|
|
278
|
+
details: {
|
|
279
|
+
expansionCount: delegated.citedIds.length,
|
|
280
|
+
citedIds: delegated.citedIds,
|
|
281
|
+
totalTokens: delegated.totalTokens,
|
|
282
|
+
truncated: delegated.truncated,
|
|
283
|
+
policy,
|
|
284
|
+
executionPath: "delegated",
|
|
285
|
+
delegated,
|
|
286
|
+
observability: buildOrchestrationObservability({
|
|
287
|
+
policy,
|
|
288
|
+
executionPath: "delegated",
|
|
289
|
+
delegated,
|
|
290
|
+
}),
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const executionPath = delegated ? "direct_fallback" : "direct";
|
|
296
|
+
const result =
|
|
297
|
+
matchedSummaryIds.length === 0
|
|
298
|
+
? makeEmptyExpansionResult()
|
|
299
|
+
: await runExpand({
|
|
300
|
+
summaryIds: matchedSummaryIds,
|
|
301
|
+
maxDepth,
|
|
302
|
+
tokenCap,
|
|
303
|
+
includeMessages: false,
|
|
304
|
+
conversationId: resolvedConversationId,
|
|
305
|
+
});
|
|
306
|
+
const text = distillForSubagent(result);
|
|
307
|
+
return {
|
|
308
|
+
content: [{ type: "text", text }],
|
|
309
|
+
details: {
|
|
310
|
+
expansionCount: result.expansions.length,
|
|
311
|
+
citedIds: result.citedIds,
|
|
312
|
+
totalTokens: result.totalTokens,
|
|
313
|
+
truncated: result.truncated,
|
|
314
|
+
policy,
|
|
315
|
+
executionPath,
|
|
316
|
+
delegated:
|
|
317
|
+
delegated && delegated.status !== "ok"
|
|
318
|
+
? {
|
|
319
|
+
status: delegated.status,
|
|
320
|
+
error: delegated.error,
|
|
321
|
+
passes: delegated.passes,
|
|
322
|
+
}
|
|
323
|
+
: undefined,
|
|
324
|
+
observability: buildOrchestrationObservability({
|
|
325
|
+
policy,
|
|
326
|
+
executionPath,
|
|
327
|
+
delegated,
|
|
328
|
+
}),
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
} catch (err) {
|
|
332
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
333
|
+
return jsonResult({ error: message });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (summaryIds && summaryIds.length > 0) {
|
|
338
|
+
try {
|
|
339
|
+
if (conversationScope.conversationId != null) {
|
|
340
|
+
const outOfScope: string[] = [];
|
|
341
|
+
for (const summaryId of summaryIds) {
|
|
342
|
+
const described = await retrieval.describe(summaryId);
|
|
343
|
+
if (
|
|
344
|
+
described?.type === "summary" &&
|
|
345
|
+
described.summary?.conversationId !== conversationScope.conversationId
|
|
346
|
+
) {
|
|
347
|
+
outOfScope.push(summaryId);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (outOfScope.length > 0) {
|
|
351
|
+
return jsonResult({
|
|
352
|
+
error:
|
|
353
|
+
`Some summaryIds are outside conversation ${conversationScope.conversationId}: ` +
|
|
354
|
+
outOfScope.join(", "),
|
|
355
|
+
hint: "Use allConversations=true for cross-conversation expansion.",
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const policy = decideLcmExpansionRouting({
|
|
361
|
+
intent: "explicit_expand",
|
|
362
|
+
requestedMaxDepth: maxDepth,
|
|
363
|
+
candidateSummaryCount: summaryIds.length,
|
|
364
|
+
tokenCap: tokenCap ?? Number.MAX_SAFE_INTEGER,
|
|
365
|
+
includeMessages,
|
|
366
|
+
});
|
|
367
|
+
const normalizedSummaryIds = normalizeSummaryIds(summaryIds);
|
|
368
|
+
const canDelegate =
|
|
369
|
+
normalizedSummaryIds.length > 0 &&
|
|
370
|
+
policy.action === "delegate_traversal" &&
|
|
371
|
+
!isDelegatedSession &&
|
|
372
|
+
!!sessionKey &&
|
|
373
|
+
resolvedConversationId != null;
|
|
374
|
+
const delegated = canDelegate
|
|
375
|
+
? await runDelegatedExpansionLoop({
|
|
376
|
+
deps: input.deps,
|
|
377
|
+
requesterSessionKey: sessionKey,
|
|
378
|
+
conversationId: resolvedConversationId,
|
|
379
|
+
summaryIds: normalizedSummaryIds,
|
|
380
|
+
maxDepth,
|
|
381
|
+
tokenCap,
|
|
382
|
+
includeMessages,
|
|
383
|
+
})
|
|
384
|
+
: undefined;
|
|
385
|
+
if (delegated && delegated.status === "ok") {
|
|
386
|
+
return {
|
|
387
|
+
content: [{ type: "text", text: delegated.text }],
|
|
388
|
+
details: {
|
|
389
|
+
expansionCount: delegated.citedIds.length,
|
|
390
|
+
citedIds: delegated.citedIds,
|
|
391
|
+
totalTokens: delegated.totalTokens,
|
|
392
|
+
truncated: delegated.truncated,
|
|
393
|
+
policy,
|
|
394
|
+
executionPath: "delegated",
|
|
395
|
+
delegated,
|
|
396
|
+
observability: buildOrchestrationObservability({
|
|
397
|
+
policy,
|
|
398
|
+
executionPath: "delegated",
|
|
399
|
+
delegated,
|
|
400
|
+
}),
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
const executionPath = delegated ? "direct_fallback" : "direct";
|
|
405
|
+
const result = await runExpand({
|
|
406
|
+
summaryIds: normalizedSummaryIds,
|
|
407
|
+
maxDepth,
|
|
408
|
+
tokenCap,
|
|
409
|
+
includeMessages,
|
|
410
|
+
conversationId: resolvedConversationId ?? 0,
|
|
411
|
+
});
|
|
412
|
+
const text = distillForSubagent(result);
|
|
413
|
+
return {
|
|
414
|
+
content: [{ type: "text", text }],
|
|
415
|
+
details: {
|
|
416
|
+
expansionCount: result.expansions.length,
|
|
417
|
+
citedIds: result.citedIds,
|
|
418
|
+
totalTokens: result.totalTokens,
|
|
419
|
+
truncated: result.truncated,
|
|
420
|
+
policy,
|
|
421
|
+
executionPath,
|
|
422
|
+
delegated:
|
|
423
|
+
delegated && delegated.status !== "ok"
|
|
424
|
+
? {
|
|
425
|
+
status: delegated.status,
|
|
426
|
+
error: delegated.error,
|
|
427
|
+
passes: delegated.passes,
|
|
428
|
+
}
|
|
429
|
+
: undefined,
|
|
430
|
+
observability: buildOrchestrationObservability({
|
|
431
|
+
policy,
|
|
432
|
+
executionPath,
|
|
433
|
+
delegated,
|
|
434
|
+
}),
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
} catch (err) {
|
|
438
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
439
|
+
return jsonResult({ error: message });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return jsonResult({
|
|
444
|
+
error: "Either summaryIds or query must be provided.",
|
|
445
|
+
});
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}
|