@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,527 @@
|
|
|
1
|
+
const FILE_BLOCK_RE = /<file\b([^>]*)>([\s\S]*?)<\/file>/gi;
|
|
2
|
+
const FILE_ID_RE = /\bfile_[a-f0-9]{16}\b/gi;
|
|
3
|
+
|
|
4
|
+
const CODE_EXTENSIONS = new Set([
|
|
5
|
+
"c",
|
|
6
|
+
"cc",
|
|
7
|
+
"cpp",
|
|
8
|
+
"cs",
|
|
9
|
+
"go",
|
|
10
|
+
"h",
|
|
11
|
+
"hpp",
|
|
12
|
+
"java",
|
|
13
|
+
"js",
|
|
14
|
+
"jsx",
|
|
15
|
+
"kt",
|
|
16
|
+
"m",
|
|
17
|
+
"php",
|
|
18
|
+
"py",
|
|
19
|
+
"rb",
|
|
20
|
+
"rs",
|
|
21
|
+
"scala",
|
|
22
|
+
"sh",
|
|
23
|
+
"sql",
|
|
24
|
+
"swift",
|
|
25
|
+
"ts",
|
|
26
|
+
"tsx",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const STRUCTURED_EXTENSIONS = new Set(["csv", "json", "tsv", "xml", "yaml", "yml"]);
|
|
30
|
+
|
|
31
|
+
const MIME_EXTENSION_MAP: Record<string, string> = {
|
|
32
|
+
"application/json": "json",
|
|
33
|
+
"application/xml": "xml",
|
|
34
|
+
"application/yaml": "yaml",
|
|
35
|
+
"application/x-yaml": "yaml",
|
|
36
|
+
"application/x-ndjson": "json",
|
|
37
|
+
"application/csv": "csv",
|
|
38
|
+
"application/javascript": "js",
|
|
39
|
+
"application/typescript": "ts",
|
|
40
|
+
"application/x-python-code": "py",
|
|
41
|
+
"application/x-rust": "rs",
|
|
42
|
+
"application/x-sh": "sh",
|
|
43
|
+
"text/csv": "csv",
|
|
44
|
+
"text/markdown": "md",
|
|
45
|
+
"text/plain": "txt",
|
|
46
|
+
"text/tab-separated-values": "tsv",
|
|
47
|
+
"text/x-c": "c",
|
|
48
|
+
"text/x-c++": "cpp",
|
|
49
|
+
"text/x-go": "go",
|
|
50
|
+
"text/x-java": "java",
|
|
51
|
+
"text/x-python": "py",
|
|
52
|
+
"text/x-rust": "rs",
|
|
53
|
+
"text/x-script.python": "py",
|
|
54
|
+
"text/x-shellscript": "sh",
|
|
55
|
+
"text/x-typescript": "ts",
|
|
56
|
+
"text/xml": "xml",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const STRUCTURED_MIME_PREFIXES = [
|
|
60
|
+
"application/json",
|
|
61
|
+
"application/xml",
|
|
62
|
+
"application/yaml",
|
|
63
|
+
"application/x-yaml",
|
|
64
|
+
"application/x-ndjson",
|
|
65
|
+
"text/csv",
|
|
66
|
+
"text/tab-separated-values",
|
|
67
|
+
"text/xml",
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const CODE_MIME_PREFIXES = [
|
|
71
|
+
"application/javascript",
|
|
72
|
+
"application/typescript",
|
|
73
|
+
"application/x-python-code",
|
|
74
|
+
"application/x-rust",
|
|
75
|
+
"text/javascript",
|
|
76
|
+
"text/x-c",
|
|
77
|
+
"text/x-c++",
|
|
78
|
+
"text/x-go",
|
|
79
|
+
"text/x-java",
|
|
80
|
+
"text/x-python",
|
|
81
|
+
"text/x-rust",
|
|
82
|
+
"text/x-script.python",
|
|
83
|
+
"text/x-shellscript",
|
|
84
|
+
"text/x-typescript",
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const TEXT_SUMMARY_SLICE_CHARS = 2_400;
|
|
88
|
+
const TEXT_HEADER_LIMIT = 18;
|
|
89
|
+
|
|
90
|
+
export type FileBlock = {
|
|
91
|
+
fullMatch: string;
|
|
92
|
+
start: number;
|
|
93
|
+
end: number;
|
|
94
|
+
attributes: Record<string, string>;
|
|
95
|
+
fileName?: string;
|
|
96
|
+
mimeType?: string;
|
|
97
|
+
text: string;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type ExplorationSummaryInput = {
|
|
101
|
+
content: string;
|
|
102
|
+
fileName?: string;
|
|
103
|
+
mimeType?: string;
|
|
104
|
+
summarizeText?: (prompt: string) => Promise<string | null | undefined>;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
function parseFileAttributes(raw: string): Record<string, string> {
|
|
108
|
+
const attrs: Record<string, string> = {};
|
|
109
|
+
const attrRe = /([A-Za-z_:][A-Za-z0-9_:\-.]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/g;
|
|
110
|
+
|
|
111
|
+
let match: RegExpExecArray | null;
|
|
112
|
+
while ((match = attrRe.exec(raw)) !== null) {
|
|
113
|
+
const key = match[1].trim().toLowerCase();
|
|
114
|
+
const value = (match[3] ?? match[4] ?? match[5] ?? "").trim();
|
|
115
|
+
if (key.length > 0 && value.length > 0) {
|
|
116
|
+
attrs[key] = value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return attrs;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeTextForLine(text: string, maxLen: number): string {
|
|
124
|
+
const compact = text.replace(/\s+/g, " ").trim();
|
|
125
|
+
if (compact.length <= maxLen) {
|
|
126
|
+
return compact;
|
|
127
|
+
}
|
|
128
|
+
return `${compact.slice(0, maxLen)}...`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function collectFileNameExtension(fileName?: string): string | undefined {
|
|
132
|
+
if (!fileName) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
const base = fileName.trim().split(/[\\/]/).pop() ?? "";
|
|
136
|
+
const idx = base.lastIndexOf(".");
|
|
137
|
+
if (idx <= 0 || idx === base.length - 1) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const ext = base.slice(idx + 1).toLowerCase();
|
|
142
|
+
if (!/^[a-z0-9]{1,10}$/.test(ext)) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
return ext;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function guessMimeExtension(mimeType?: string): string | undefined {
|
|
149
|
+
if (!mimeType) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const normalized = mimeType.trim().toLowerCase();
|
|
153
|
+
return MIME_EXTENSION_MAP[normalized];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isStructured(params: { mimeType?: string; extension?: string }): boolean {
|
|
157
|
+
const mime = params.mimeType?.trim().toLowerCase();
|
|
158
|
+
if (mime && STRUCTURED_MIME_PREFIXES.some((candidate) => mime.startsWith(candidate))) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return params.extension ? STRUCTURED_EXTENSIONS.has(params.extension) : false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function isCode(params: { mimeType?: string; extension?: string }): boolean {
|
|
165
|
+
const mime = params.mimeType?.trim().toLowerCase();
|
|
166
|
+
if (mime && CODE_MIME_PREFIXES.some((candidate) => mime.startsWith(candidate))) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return params.extension ? CODE_EXTENSIONS.has(params.extension) : false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function uniqueOrdered(values: Iterable<string>): string[] {
|
|
173
|
+
const seen = new Set<string>();
|
|
174
|
+
const out: string[] = [];
|
|
175
|
+
for (const value of values) {
|
|
176
|
+
if (!seen.has(value)) {
|
|
177
|
+
seen.add(value);
|
|
178
|
+
out.push(value);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function exploreJson(content: string): string {
|
|
185
|
+
const parsed = JSON.parse(content) as unknown;
|
|
186
|
+
|
|
187
|
+
const describe = (value: unknown, depth = 0): string => {
|
|
188
|
+
if (depth >= 2) {
|
|
189
|
+
return "...";
|
|
190
|
+
}
|
|
191
|
+
if (Array.isArray(value)) {
|
|
192
|
+
const sample = value.slice(0, 3).map((item) => describe(item, depth + 1));
|
|
193
|
+
return `array(len=${value.length}${sample.length > 0 ? `, sample=[${sample.join(", ")}]` : ""})`;
|
|
194
|
+
}
|
|
195
|
+
if (!value || typeof value !== "object") {
|
|
196
|
+
return typeof value;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const keys = Object.keys(value as Record<string, unknown>);
|
|
200
|
+
const preview = keys.slice(0, 10).join(", ");
|
|
201
|
+
return `object(keys=${keys.length}${preview ? `: ${preview}` : ""})`;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const topLevel = Array.isArray(parsed) ? "array" : typeof parsed;
|
|
205
|
+
return [
|
|
206
|
+
`Structured summary (JSON):`,
|
|
207
|
+
`Top-level type: ${topLevel}.`,
|
|
208
|
+
`Shape: ${describe(parsed)}.`,
|
|
209
|
+
].join("\n");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function parseDelimitedLine(line: string, delimiter: "," | "\t"): string[] {
|
|
213
|
+
return line
|
|
214
|
+
.split(delimiter)
|
|
215
|
+
.map((item) => item.trim())
|
|
216
|
+
.filter((item) => item.length > 0);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function exploreDelimited(content: string, delimiter: "," | "\t", kind: "CSV" | "TSV"): string {
|
|
220
|
+
const lines = content
|
|
221
|
+
.split(/\r?\n/)
|
|
222
|
+
.map((line) => line.trim())
|
|
223
|
+
.filter((line) => line.length > 0);
|
|
224
|
+
|
|
225
|
+
if (lines.length === 0) {
|
|
226
|
+
return `Structured summary (${kind}): no rows found.`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const headers = parseDelimitedLine(lines[0], delimiter);
|
|
230
|
+
const rowCount = Math.max(0, lines.length - 1);
|
|
231
|
+
const firstData = lines[1] ? normalizeTextForLine(lines[1], 180) : "(no data rows)";
|
|
232
|
+
|
|
233
|
+
return [
|
|
234
|
+
`Structured summary (${kind}):`,
|
|
235
|
+
`Rows: ${rowCount.toLocaleString("en-US")}.`,
|
|
236
|
+
`Columns (${headers.length}): ${headers.join(", ") || "(none detected)"}.`,
|
|
237
|
+
`First row sample: ${firstData}.`,
|
|
238
|
+
].join("\n");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function exploreYaml(content: string): string {
|
|
242
|
+
const topLevelKeys = uniqueOrdered(
|
|
243
|
+
content
|
|
244
|
+
.split(/\r?\n/)
|
|
245
|
+
.map((line) => {
|
|
246
|
+
const match = line.match(/^([A-Za-z0-9_.-]+):\s*(?:#.*)?$/);
|
|
247
|
+
return match ? match[1] : "";
|
|
248
|
+
})
|
|
249
|
+
.filter((key) => key.length > 0),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return [
|
|
253
|
+
"Structured summary (YAML):",
|
|
254
|
+
`Top-level keys (${topLevelKeys.length}): ${topLevelKeys.slice(0, 30).join(", ") || "(none detected)"}.`,
|
|
255
|
+
].join("\n");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function exploreXml(content: string): string {
|
|
259
|
+
const rootMatch = content.match(/<([A-Za-z0-9_:-]+)(\s|>)/);
|
|
260
|
+
const rootTag = rootMatch?.[1] ?? "unknown";
|
|
261
|
+
const childTags = uniqueOrdered(
|
|
262
|
+
[...content.matchAll(/<([A-Za-z0-9_:-]+)(\s|>)/g)]
|
|
263
|
+
.map((match) => match[1])
|
|
264
|
+
.filter((tag) => tag !== rootTag)
|
|
265
|
+
.slice(0, 30),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
return [
|
|
269
|
+
"Structured summary (XML):",
|
|
270
|
+
`Root element: ${rootTag}.`,
|
|
271
|
+
`Child elements seen: ${childTags.join(", ") || "(none detected)"}.`,
|
|
272
|
+
].join("\n");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function exploreStructuredData(
|
|
276
|
+
content: string,
|
|
277
|
+
mimeType?: string,
|
|
278
|
+
fileName?: string,
|
|
279
|
+
): string {
|
|
280
|
+
const extension = collectFileNameExtension(fileName) ?? guessMimeExtension(mimeType);
|
|
281
|
+
const normalizedMime = mimeType?.trim().toLowerCase() ?? "";
|
|
282
|
+
|
|
283
|
+
if (extension === "json" || normalizedMime.startsWith("application/json")) {
|
|
284
|
+
try {
|
|
285
|
+
return exploreJson(content);
|
|
286
|
+
} catch {
|
|
287
|
+
return "Structured summary (JSON): failed to parse as valid JSON.";
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (extension === "csv" || normalizedMime.startsWith("text/csv")) {
|
|
292
|
+
return exploreDelimited(content, ",", "CSV");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (extension === "tsv" || normalizedMime.startsWith("text/tab-separated-values")) {
|
|
296
|
+
return exploreDelimited(content, "\t", "TSV");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (
|
|
300
|
+
extension === "xml" ||
|
|
301
|
+
normalizedMime.startsWith("text/xml") ||
|
|
302
|
+
normalizedMime.startsWith("application/xml")
|
|
303
|
+
) {
|
|
304
|
+
return exploreXml(content);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (extension === "yaml" || extension === "yml" || normalizedMime.includes("yaml")) {
|
|
308
|
+
return exploreYaml(content);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return [
|
|
312
|
+
"Structured summary:",
|
|
313
|
+
`Characters: ${content.length.toLocaleString("en-US")}.`,
|
|
314
|
+
`Lines: ${content.split(/\r?\n/).length.toLocaleString("en-US")}.`,
|
|
315
|
+
].join("\n");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function exploreCode(content: string, fileName?: string): string {
|
|
319
|
+
const lines = content.split(/\r?\n/);
|
|
320
|
+
const imports = uniqueOrdered(
|
|
321
|
+
lines
|
|
322
|
+
.filter((line) =>
|
|
323
|
+
/^\s*(import\s+|from\s+\S+\s+import\s+|const\s+\w+\s*=\s*require\()/.test(line),
|
|
324
|
+
)
|
|
325
|
+
.map((line) => normalizeTextForLine(line, 180))
|
|
326
|
+
.slice(0, 12),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const signatures = uniqueOrdered(
|
|
330
|
+
lines
|
|
331
|
+
.map((line) => line.trim())
|
|
332
|
+
.filter((line) =>
|
|
333
|
+
/^(export\s+)?(async\s+)?(function|class|interface|type|const\s+\w+\s*=\s*\(|def\s+\w+\(|struct\s+\w+)/.test(
|
|
334
|
+
line,
|
|
335
|
+
),
|
|
336
|
+
)
|
|
337
|
+
.map((line) => normalizeTextForLine(line, 200))
|
|
338
|
+
.slice(0, 24),
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
return [
|
|
342
|
+
`Code exploration summary${fileName ? ` (${fileName})` : ""}:`,
|
|
343
|
+
`Lines: ${lines.length.toLocaleString("en-US")}.`,
|
|
344
|
+
`Imports/dependencies (${imports.length}): ${imports.join(" | ") || "none detected"}.`,
|
|
345
|
+
`Top-level definitions (${signatures.length}): ${signatures.join(" | ") || "none detected"}.`,
|
|
346
|
+
].join("\n");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function extractTextHeaders(content: string): string[] {
|
|
350
|
+
const headers = uniqueOrdered(
|
|
351
|
+
content
|
|
352
|
+
.split(/\r?\n/)
|
|
353
|
+
.map((line) => line.trim())
|
|
354
|
+
.filter((line) => line.length > 1)
|
|
355
|
+
.filter((line) => /^#{1,6}\s+/.test(line) || /^[A-Z0-9][A-Z0-9\s:_-]{6,}$/.test(line))
|
|
356
|
+
.map((line) => normalizeTextForLine(line, 160))
|
|
357
|
+
.slice(0, TEXT_HEADER_LIMIT),
|
|
358
|
+
);
|
|
359
|
+
return headers;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function buildTextSample(content: string): string {
|
|
363
|
+
if (content.length <= TEXT_SUMMARY_SLICE_CHARS * 2) {
|
|
364
|
+
return content;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const middleStart = Math.max(
|
|
368
|
+
0,
|
|
369
|
+
Math.floor(content.length / 2) - Math.floor(TEXT_SUMMARY_SLICE_CHARS / 2),
|
|
370
|
+
);
|
|
371
|
+
const middleEnd = middleStart + TEXT_SUMMARY_SLICE_CHARS;
|
|
372
|
+
const head = content.slice(0, TEXT_SUMMARY_SLICE_CHARS);
|
|
373
|
+
const mid = content.slice(middleStart, middleEnd);
|
|
374
|
+
const tail = content.slice(-TEXT_SUMMARY_SLICE_CHARS);
|
|
375
|
+
|
|
376
|
+
return ["[Document Start]", head, "[Document Middle]", mid, "[Document End]", tail].join("\n\n");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function buildTextPrompt(params: {
|
|
380
|
+
content: string;
|
|
381
|
+
fileName?: string;
|
|
382
|
+
mimeType?: string;
|
|
383
|
+
headers: string[];
|
|
384
|
+
}): string {
|
|
385
|
+
const sample = buildTextSample(params.content);
|
|
386
|
+
return [
|
|
387
|
+
`Summarize this large file for retrieval-time context references.`,
|
|
388
|
+
`File name: ${params.fileName ?? "unknown"}`,
|
|
389
|
+
`Mime type: ${params.mimeType ?? "unknown"}`,
|
|
390
|
+
`Length: ${params.content.length.toLocaleString("en-US")} chars`,
|
|
391
|
+
`Line count: ${params.content.split(/\r?\n/).length.toLocaleString("en-US")}`,
|
|
392
|
+
params.headers.length > 0
|
|
393
|
+
? `Detected section headers: ${params.headers.join(" | ")}`
|
|
394
|
+
: "Detected section headers: none",
|
|
395
|
+
"Produce 200-300 words with:",
|
|
396
|
+
"- What the document is about",
|
|
397
|
+
"- Key sections and topics",
|
|
398
|
+
"- Important names, dates, and numbers",
|
|
399
|
+
"- Any action items or constraints",
|
|
400
|
+
"Do not quote long passages verbatim.",
|
|
401
|
+
"",
|
|
402
|
+
"Document sample:",
|
|
403
|
+
sample,
|
|
404
|
+
].join("\n");
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function exploreTextDeterministicFallback(content: string, fileName?: string): string {
|
|
408
|
+
const normalized = content.replace(/\s+/g, " ").trim();
|
|
409
|
+
const headers = extractTextHeaders(content);
|
|
410
|
+
const lineCount = content.split(/\r?\n/).length;
|
|
411
|
+
const wordCount = normalized.length > 0 ? normalized.split(/\s+/).length : 0;
|
|
412
|
+
const first = normalizeTextForLine(content.slice(0, 500), 500);
|
|
413
|
+
const last = normalizeTextForLine(content.slice(-500), 500);
|
|
414
|
+
|
|
415
|
+
return [
|
|
416
|
+
`Text exploration summary${fileName ? ` (${fileName})` : ""}:`,
|
|
417
|
+
`Characters: ${content.length.toLocaleString("en-US")}.`,
|
|
418
|
+
`Words: ${wordCount.toLocaleString("en-US")}.`,
|
|
419
|
+
`Lines: ${lineCount.toLocaleString("en-US")}.`,
|
|
420
|
+
`Detected section headers: ${headers.join(" | ") || "none detected"}.`,
|
|
421
|
+
`Opening excerpt: ${first || "(empty)"}.`,
|
|
422
|
+
`Closing excerpt: ${last || "(empty)"}.`,
|
|
423
|
+
].join("\n");
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function exploreText(params: ExplorationSummaryInput): Promise<string> {
|
|
427
|
+
const headers = extractTextHeaders(params.content);
|
|
428
|
+
|
|
429
|
+
if (params.summarizeText) {
|
|
430
|
+
const prompt = buildTextPrompt({
|
|
431
|
+
content: params.content,
|
|
432
|
+
fileName: params.fileName,
|
|
433
|
+
mimeType: params.mimeType,
|
|
434
|
+
headers,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
const summary = await params.summarizeText(prompt);
|
|
439
|
+
if (typeof summary === "string" && summary.trim().length > 0) {
|
|
440
|
+
return summary.trim();
|
|
441
|
+
}
|
|
442
|
+
} catch {
|
|
443
|
+
// Use deterministic fallback if model summarization fails.
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return exploreTextDeterministicFallback(params.content, params.fileName);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function parseFileBlocks(content: string): FileBlock[] {
|
|
451
|
+
const blocks: FileBlock[] = [];
|
|
452
|
+
let match: RegExpExecArray | null;
|
|
453
|
+
|
|
454
|
+
FILE_BLOCK_RE.lastIndex = 0;
|
|
455
|
+
while ((match = FILE_BLOCK_RE.exec(content)) !== null) {
|
|
456
|
+
const fullMatch = match[0];
|
|
457
|
+
const rawAttrs = match[1] ?? "";
|
|
458
|
+
const text = match[2] ?? "";
|
|
459
|
+
const start = match.index;
|
|
460
|
+
const end = start + fullMatch.length;
|
|
461
|
+
const attributes = parseFileAttributes(rawAttrs);
|
|
462
|
+
|
|
463
|
+
blocks.push({
|
|
464
|
+
fullMatch,
|
|
465
|
+
start,
|
|
466
|
+
end,
|
|
467
|
+
attributes,
|
|
468
|
+
fileName: attributes.name,
|
|
469
|
+
mimeType: attributes.mime,
|
|
470
|
+
text,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return blocks;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export function extensionFromNameOrMime(fileName?: string, mimeType?: string): string {
|
|
478
|
+
const fromName = collectFileNameExtension(fileName);
|
|
479
|
+
if (fromName) {
|
|
480
|
+
return fromName;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const fromMime = guessMimeExtension(mimeType);
|
|
484
|
+
if (fromMime) {
|
|
485
|
+
return fromMime;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return "txt";
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function extractFileIdsFromContent(content: string): string[] {
|
|
492
|
+
const matches = content.match(FILE_ID_RE) ?? [];
|
|
493
|
+
return uniqueOrdered(matches.map((id) => id.toLowerCase()));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export function formatFileReference(input: {
|
|
497
|
+
fileId: string;
|
|
498
|
+
fileName?: string;
|
|
499
|
+
mimeType?: string;
|
|
500
|
+
byteSize: number;
|
|
501
|
+
summary: string;
|
|
502
|
+
}): string {
|
|
503
|
+
const name = input.fileName?.trim() || "unknown";
|
|
504
|
+
const mime = input.mimeType?.trim() || "unknown";
|
|
505
|
+
const byteSize = Math.max(0, input.byteSize);
|
|
506
|
+
|
|
507
|
+
return [
|
|
508
|
+
`[LCM File: ${input.fileId} | ${name} | ${mime} | ${byteSize.toLocaleString("en-US")} bytes]`,
|
|
509
|
+
"",
|
|
510
|
+
"Exploration Summary:",
|
|
511
|
+
input.summary.trim() || "(no summary available)",
|
|
512
|
+
].join("\n");
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export async function generateExplorationSummary(input: ExplorationSummaryInput): Promise<string> {
|
|
516
|
+
const extension = extensionFromNameOrMime(input.fileName, input.mimeType);
|
|
517
|
+
|
|
518
|
+
if (isStructured({ mimeType: input.mimeType, extension })) {
|
|
519
|
+
return exploreStructuredData(input.content, input.mimeType, input.fileName);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (isCode({ mimeType: input.mimeType, extension })) {
|
|
523
|
+
return exploreCode(input.content, input.fileName);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return exploreText(input);
|
|
527
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility bridge for plugin-sdk context-engine symbols.
|
|
3
|
+
*
|
|
4
|
+
* This module intentionally exports only stable plugin-sdk surface area.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
ContextEngine,
|
|
9
|
+
ContextEngineInfo,
|
|
10
|
+
AssembleResult,
|
|
11
|
+
CompactResult,
|
|
12
|
+
IngestResult,
|
|
13
|
+
IngestBatchResult,
|
|
14
|
+
BootstrapResult,
|
|
15
|
+
SubagentSpawnPreparation,
|
|
16
|
+
SubagentEndReason,
|
|
17
|
+
} from "openclaw/plugin-sdk";
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
registerContextEngine,
|
|
21
|
+
type ContextEngineFactory,
|
|
22
|
+
} from "openclaw/plugin-sdk";
|