@jonathangu/openclawbrain 0.3.0 → 0.3.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.
- package/README.md +140 -290
- package/docs/END_STATE.md +106 -94
- package/docs/EVIDENCE.md +71 -23
- package/docs/RELEASE_CONTRACT.md +46 -32
- package/docs/agent-tools.md +65 -34
- package/docs/architecture.md +128 -142
- package/docs/configuration.md +62 -25
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/channels-status.txt +20 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/config-snapshot.json +94 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/doctor.json +14 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/gateway-probe.txt +24 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/gateway-status.txt +31 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/init-capture.json +15 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/logs.txt +357 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/status-all.txt +61 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/status.json +275 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/summary.md +18 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/trace.json +222 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/validation-report.json +1515 -0
- package/docs/evidence/2026-03-16/1fc8ee6fd7892e3deb27d111434df948bca2a66b/workspace-inventory.json +4 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/channels-status.txt +20 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/config-snapshot.json +94 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/doctor.json +14 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/gateway-probe.txt +24 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/gateway-status.txt +31 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/init-capture.json +15 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/logs.txt +362 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/status-all.txt +61 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/status.json +275 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/summary.md +21 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/trace.json +222 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/validation-report.json +4400 -0
- package/docs/evidence/2026-03-16/4ccd71a22418b9170128b8d948f5a95801a10380/workspace-inventory.json +4 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/channels-status.txt +31 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/config-snapshot.json +94 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/doctor.json +14 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/gateway-probe.txt +34 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/gateway-status.txt +41 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/logs.txt +441 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/status-all.txt +60 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/status.json +276 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/summary.md +13 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/trace.json +4 -0
- package/docs/evidence/2026-03-16/d93f09feea123a08d020fcad8a4523b6c1d26507/validation-report.json +387 -0
- package/docs/tui.md +11 -4
- package/index.ts +194 -1
- package/package.json +1 -1
- package/src/brain-cli.ts +12 -1
- package/src/brain-harvest/scanner.ts +286 -16
- package/src/brain-harvest/self.ts +134 -6
- package/src/brain-runtime/evidence-detectors.ts +3 -1
- package/src/brain-runtime/harvester-extension.ts +3 -0
- package/src/brain-runtime/service.ts +2 -0
- package/src/brain-store/embedding.ts +29 -8
- package/src/brain-worker/worker.ts +40 -0
- package/src/engine.ts +1 -0
|
@@ -32,11 +32,14 @@ function parseJson(value: string | null | undefined): unknown {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
36
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
37
|
+
? value as Record<string, unknown>
|
|
38
|
+
: null;
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
function readPartMetadata(part: HarvestMessagePart): Record<string, unknown> {
|
|
36
|
-
|
|
37
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
38
|
-
? parsed as Record<string, unknown>
|
|
39
|
-
: {};
|
|
42
|
+
return asRecord(parseJson(part.metadata)) ?? {};
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
function isStructuredToolResultPart(part: HarvestMessagePart): boolean {
|
|
@@ -52,11 +55,133 @@ function isStructuredToolResultPart(part: HarvestMessagePart): boolean {
|
|
|
52
55
|
|| rawType === "function_call_output";
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
function readString(record: Record<string, unknown> | null, keys: string[]): string | undefined {
|
|
59
|
+
if (!record) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
const value = record[key];
|
|
64
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
65
|
+
return value.trim();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readNumber(record: Record<string, unknown> | null, keys: string[]): number | undefined {
|
|
72
|
+
if (!record) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
for (const key of keys) {
|
|
76
|
+
const value = record[key];
|
|
77
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readStringArray(value: unknown): string[] {
|
|
85
|
+
if (Array.isArray(value)) {
|
|
86
|
+
return value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0);
|
|
87
|
+
}
|
|
88
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
89
|
+
return [value.trim()];
|
|
90
|
+
}
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readCommand(value: unknown): string | undefined {
|
|
95
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
96
|
+
return value.trim();
|
|
97
|
+
}
|
|
98
|
+
if (Array.isArray(value)) {
|
|
99
|
+
const parts = value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0);
|
|
100
|
+
return parts.length > 0 ? parts.join(" ") : undefined;
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function extractCommand(input: unknown, output: unknown): string | undefined {
|
|
106
|
+
const inputRecord = asRecord(input);
|
|
107
|
+
const outputRecord = asRecord(output);
|
|
108
|
+
|
|
109
|
+
return readString(inputRecord, ["command", "cmd", "shellCommand"])
|
|
110
|
+
?? readCommand(inputRecord?.args)
|
|
111
|
+
?? readString(outputRecord, ["command", "cmd", "shellCommand"])
|
|
112
|
+
?? readCommand(outputRecord?.args)
|
|
113
|
+
?? (typeof input === "string" && input.trim().length > 0 ? input.trim() : undefined);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function extractFilesTouched(input: unknown, output: unknown): string[] | undefined {
|
|
117
|
+
const inputRecord = asRecord(input);
|
|
118
|
+
const outputRecord = asRecord(output);
|
|
119
|
+
const collected = new Set<string>();
|
|
120
|
+
|
|
121
|
+
for (const value of [
|
|
122
|
+
outputRecord?.filesTouched,
|
|
123
|
+
outputRecord?.changedFiles,
|
|
124
|
+
outputRecord?.files,
|
|
125
|
+
outputRecord?.paths,
|
|
126
|
+
inputRecord?.filesTouched,
|
|
127
|
+
inputRecord?.files,
|
|
128
|
+
inputRecord?.paths,
|
|
129
|
+
readString(outputRecord, ["filePath", "path"]),
|
|
130
|
+
readString(inputRecord, ["filePath", "path"]),
|
|
131
|
+
]) {
|
|
132
|
+
for (const item of readStringArray(value)) {
|
|
133
|
+
collected.add(item);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return collected.size > 0 ? Array.from(collected) : undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function extractArtifactPath(output: unknown): string | undefined {
|
|
141
|
+
const record = asRecord(output);
|
|
142
|
+
return readString(record, ["artifactPath", "outputPath", "reportPath", "logPath"]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function buildStructuredToolMetadata(part: HarvestMessagePart): Record<string, unknown> {
|
|
146
|
+
const metadata = readPartMetadata(part);
|
|
147
|
+
const rawType = typeof metadata.rawType === "string" ? metadata.rawType : null;
|
|
148
|
+
const parsedInput = parseJson(part.toolInput);
|
|
149
|
+
const parsedOutput = parseJson(part.toolOutput);
|
|
150
|
+
const result: Record<string, unknown> = {
|
|
151
|
+
toolCallId: part.toolCallId ?? null,
|
|
152
|
+
toolName: part.toolName ?? null,
|
|
153
|
+
partOrdinal: part.ordinal ?? null,
|
|
154
|
+
rawType,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const exitCode = readNumber(asRecord(parsedOutput), ["exitCode"]);
|
|
158
|
+
if (exitCode !== undefined) {
|
|
159
|
+
result.exitCode = exitCode;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const command = extractCommand(parsedInput, parsedOutput);
|
|
163
|
+
if (command) {
|
|
164
|
+
result.command = command;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const filesTouched = extractFilesTouched(parsedInput, parsedOutput);
|
|
168
|
+
if (filesTouched) {
|
|
169
|
+
result.filesTouched = filesTouched;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const artifactPath = extractArtifactPath(parsedOutput);
|
|
173
|
+
if (artifactPath) {
|
|
174
|
+
result.artifactPath = artifactPath;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
55
180
|
function classifyStructuredToolOutput(output: unknown): { ok: boolean; reason: string } | null {
|
|
56
|
-
|
|
181
|
+
const record = asRecord(output);
|
|
182
|
+
if (!record) {
|
|
57
183
|
return null;
|
|
58
184
|
}
|
|
59
|
-
const record = output as Record<string, unknown>;
|
|
60
185
|
|
|
61
186
|
if (record.isError === true || record.error !== undefined || record.errors !== undefined) {
|
|
62
187
|
return { ok: false, reason: "structured tool output indicates error" };
|
|
@@ -89,6 +214,7 @@ export function detectStructuredSelfEvidence(messageParts?: HarvestMessagePart[]
|
|
|
89
214
|
}
|
|
90
215
|
|
|
91
216
|
const metadata = readPartMetadata(part);
|
|
217
|
+
const evidenceMetadata = buildStructuredToolMetadata(part);
|
|
92
218
|
if (metadata.isError === true) {
|
|
93
219
|
return {
|
|
94
220
|
value: -0.5,
|
|
@@ -97,6 +223,7 @@ export function detectStructuredSelfEvidence(messageParts?: HarvestMessagePart[]
|
|
|
97
223
|
confidence: 0.9,
|
|
98
224
|
kind: "self_result",
|
|
99
225
|
extractor: "structured_tool_result",
|
|
226
|
+
metadata: evidenceMetadata,
|
|
100
227
|
};
|
|
101
228
|
}
|
|
102
229
|
|
|
@@ -112,6 +239,7 @@ export function detectStructuredSelfEvidence(messageParts?: HarvestMessagePart[]
|
|
|
112
239
|
confidence: 0.9,
|
|
113
240
|
kind: "self_result",
|
|
114
241
|
extractor: "structured_tool_result",
|
|
242
|
+
metadata: evidenceMetadata,
|
|
115
243
|
};
|
|
116
244
|
}
|
|
117
245
|
|
|
@@ -5,6 +5,7 @@ import { detectSelfEvidence, detectStructuredSelfEvidence } from "../brain-harve
|
|
|
5
5
|
|
|
6
6
|
export type HarvestMessagePart = {
|
|
7
7
|
partType: string;
|
|
8
|
+
ordinal?: number;
|
|
8
9
|
textContent?: string | null;
|
|
9
10
|
toolCallId?: string | null;
|
|
10
11
|
toolName?: string | null;
|
|
@@ -20,6 +21,7 @@ export interface HarvestResult {
|
|
|
20
21
|
confidence?: number;
|
|
21
22
|
kind: BrainEvidenceKind;
|
|
22
23
|
extractor?: string;
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export function detectEvidenceBatch(
|
|
@@ -37,7 +39,7 @@ export function detectEvidenceBatch(
|
|
|
37
39
|
const self = structuredSelf ?? detectSelfEvidence(content);
|
|
38
40
|
const results = [
|
|
39
41
|
self,
|
|
40
|
-
detectScannerEvidence(content),
|
|
42
|
+
detectScannerEvidence(content, messageParts),
|
|
41
43
|
].filter((result): result is HarvestResult => result !== null);
|
|
42
44
|
|
|
43
45
|
const deduped = new Map<string, HarvestResult>();
|
|
@@ -28,6 +28,7 @@ export class LabelHarvester {
|
|
|
28
28
|
*/
|
|
29
29
|
async harvestFromMessage(params: {
|
|
30
30
|
conversationId: number;
|
|
31
|
+
messageId?: number;
|
|
31
32
|
episodeId?: string;
|
|
32
33
|
role: string;
|
|
33
34
|
content: string;
|
|
@@ -71,6 +72,7 @@ export class LabelHarvester {
|
|
|
71
72
|
contentSnippet: params.content.slice(0, 240),
|
|
72
73
|
metadata: {
|
|
73
74
|
harvestedFromRole: params.role,
|
|
75
|
+
messageId: params.messageId ?? null,
|
|
74
76
|
explicitEpisodeId,
|
|
75
77
|
resolvedEpisodeId,
|
|
76
78
|
matchedEpisodeId: matchingEpisode.id,
|
|
@@ -79,6 +81,7 @@ export class LabelHarvester {
|
|
|
79
81
|
evidenceIndex: index,
|
|
80
82
|
evidenceCount: results.length,
|
|
81
83
|
messagePartCount: params.messageParts?.length ?? 0,
|
|
84
|
+
...(result.metadata ?? {}),
|
|
82
85
|
},
|
|
83
86
|
});
|
|
84
87
|
|
|
@@ -599,11 +599,13 @@ export class BrainService {
|
|
|
599
599
|
|
|
600
600
|
async harvestFromMessage(params: {
|
|
601
601
|
conversationId: number;
|
|
602
|
+
messageId?: number;
|
|
602
603
|
episodeId?: string;
|
|
603
604
|
role: string;
|
|
604
605
|
content: string;
|
|
605
606
|
messageParts?: Array<{
|
|
606
607
|
partType: string;
|
|
608
|
+
ordinal?: number;
|
|
607
609
|
textContent?: string | null;
|
|
608
610
|
toolCallId?: string | null;
|
|
609
611
|
toolName?: string | null;
|
|
@@ -27,6 +27,12 @@ function trimTrailingSlashes(value: string): string {
|
|
|
27
27
|
return value.replace(/\/+$/, "");
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function resolveEmbeddingTimeoutMs(): number {
|
|
31
|
+
const raw = process.env.OPENCLAWBRAIN_EMBEDDING_TIMEOUT_MS?.trim();
|
|
32
|
+
const parsed = raw ? Number.parseInt(raw, 10) : 10000;
|
|
33
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 10000;
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
function resolveExplicitBaseUrl(config: OpenClawBrainRuntimeConfig): string | null {
|
|
31
37
|
const explicit = config.embeddingBaseUrl.trim();
|
|
32
38
|
return explicit ? trimTrailingSlashes(explicit) : null;
|
|
@@ -152,14 +158,29 @@ export function createEmbeddingClient(options: BrainEmbeddingOptions): BrainEmbe
|
|
|
152
158
|
headers.authorization = `Bearer ${apiKey}`;
|
|
153
159
|
}
|
|
154
160
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
161
|
+
const timeoutMs = resolveEmbeddingTimeoutMs();
|
|
162
|
+
const controller = new AbortController();
|
|
163
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
164
|
+
|
|
165
|
+
let response;
|
|
166
|
+
try {
|
|
167
|
+
response = await fetch(`${baseUrl}/embeddings`, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers,
|
|
170
|
+
body: JSON.stringify({
|
|
171
|
+
model: config.embeddingModel,
|
|
172
|
+
input: text,
|
|
173
|
+
}),
|
|
174
|
+
signal: controller.signal,
|
|
175
|
+
});
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if ((error as { name?: string }).name === "AbortError") {
|
|
178
|
+
throw new Error(`Embedding request timed out after ${timeoutMs}ms: ${baseUrl}/embeddings`);
|
|
179
|
+
}
|
|
180
|
+
throw error;
|
|
181
|
+
} finally {
|
|
182
|
+
clearTimeout(timer);
|
|
183
|
+
}
|
|
163
184
|
|
|
164
185
|
if (!response.ok) {
|
|
165
186
|
const body = await response.text();
|
|
@@ -9,12 +9,43 @@ import { computeReinforceUpdates, updateBaseline, applyWeightUpdates } from "../
|
|
|
9
9
|
import { decayAllWeights } from "../brain-core/decay.js";
|
|
10
10
|
import { computeHealth } from "../brain-core/health.js";
|
|
11
11
|
|
|
12
|
+
function readExtractor(evidence: BrainEvidence): string | null {
|
|
13
|
+
const extractor = evidence.metadata?.extractor;
|
|
14
|
+
return typeof extractor === "string" && extractor.length > 0 ? extractor : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function evidenceSpecificityRank(evidence: BrainEvidence): number {
|
|
18
|
+
const extractor = readExtractor(evidence);
|
|
19
|
+
if (evidence.source === "scanner") {
|
|
20
|
+
switch (extractor) {
|
|
21
|
+
case "structured_guidance_parts":
|
|
22
|
+
return 3;
|
|
23
|
+
case "structured_tool_chain":
|
|
24
|
+
return 2;
|
|
25
|
+
case "scanner_marker":
|
|
26
|
+
return 1;
|
|
27
|
+
case "scanner_heuristic":
|
|
28
|
+
default:
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
12
36
|
function compareEvidencePriority(left: BrainEvidence, right: BrainEvidence): number {
|
|
13
37
|
const trustDelta = trustRank(left.source) - trustRank(right.source);
|
|
14
38
|
if (trustDelta !== 0) {
|
|
15
39
|
return trustDelta;
|
|
16
40
|
}
|
|
17
41
|
|
|
42
|
+
if (left.value !== right.value) {
|
|
43
|
+
const specificityDelta = evidenceSpecificityRank(left) - evidenceSpecificityRank(right);
|
|
44
|
+
if (specificityDelta !== 0) {
|
|
45
|
+
return specificityDelta;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
18
49
|
const confidenceDelta = left.confidence - right.confidence;
|
|
19
50
|
if (confidenceDelta !== 0) {
|
|
20
51
|
return confidenceDelta;
|
|
@@ -43,6 +74,15 @@ function losingEvidenceResolution(
|
|
|
43
74
|
};
|
|
44
75
|
}
|
|
45
76
|
|
|
77
|
+
const winnerSpecificity = evidenceSpecificityRank(winner);
|
|
78
|
+
const loserSpecificity = evidenceSpecificityRank(loser);
|
|
79
|
+
if (winnerSpecificity !== loserSpecificity) {
|
|
80
|
+
return {
|
|
81
|
+
resolution: "discarded_duplicate",
|
|
82
|
+
note: `same-trust evidence superseded by more-structured ${winner.source} evidence`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
46
86
|
if (winner.confidence !== loser.confidence) {
|
|
47
87
|
return {
|
|
48
88
|
resolution: "discarded_duplicate",
|
package/src/engine.ts
CHANGED
|
@@ -1263,6 +1263,7 @@ export class LcmContextEngine implements ContextEngine {
|
|
|
1263
1263
|
if (this.brainService) {
|
|
1264
1264
|
await this.brainService.harvestFromMessage({
|
|
1265
1265
|
conversationId,
|
|
1266
|
+
messageId: msgRecord.messageId,
|
|
1266
1267
|
episodeId: params.brainEpisodeId ?? this.pendingBrainEpisodeBySession.get(sessionId),
|
|
1267
1268
|
role: stored.role,
|
|
1268
1269
|
content: stored.content,
|