clawvault 2.1.2 → 2.2.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/bin/command-registration.test.js +6 -1
- package/bin/help-contract.test.js +2 -0
- package/bin/register-maintenance-commands.js +111 -0
- package/bin/register-query-commands.js +32 -1
- package/bin/register-session-lifecycle-commands.js +2 -0
- package/dist/{chunk-5MQB7B37.js → chunk-2HM7ZI4X.js} +268 -434
- package/dist/{chunk-TBVI4N53.js → chunk-6RIHODNR.js} +120 -95
- package/dist/chunk-73P7XCQM.js +104 -0
- package/dist/{chunk-MIIXBNO3.js → chunk-DHBDH4DN.js} +4 -0
- package/dist/chunk-GJEGPO7U.js +49 -0
- package/dist/chunk-GQVYQCY5.js +396 -0
- package/dist/{chunk-TXO34J3O.js → chunk-H7JW4L7H.js} +1 -1
- package/dist/{chunk-QFBKWDYR.js → chunk-IFGDPIFI.js} +3 -3
- package/dist/chunk-K6XHCUFL.js +123 -0
- package/dist/{chunk-PIJGYMQZ.js → chunk-KNDVXXKC.js} +1 -1
- package/dist/chunk-L6NB43WV.js +472 -0
- package/dist/{chunk-FEQ2CQ3Y.js → chunk-LB6P4CD5.js} +20 -7
- package/dist/chunk-MGDEINGP.js +99 -0
- package/dist/chunk-MQUJNOHK.js +58 -0
- package/dist/chunk-P5EPF6MB.js +182 -0
- package/dist/chunk-VR5NE7PZ.js +45 -0
- package/dist/chunk-WZI3OAE5.js +111 -0
- package/dist/chunk-Z2XBWN7A.js +247 -0
- package/dist/{chunk-O5V7SD5C.js → chunk-ZZA73MFY.js} +1 -1
- package/dist/commands/archive.d.ts +11 -0
- package/dist/commands/archive.js +11 -0
- package/dist/commands/context.d.ts +1 -1
- package/dist/commands/context.js +6 -4
- package/dist/commands/doctor.js +6 -6
- package/dist/commands/graph.js +2 -2
- package/dist/commands/link.js +1 -1
- package/dist/commands/migrate-observations.d.ts +19 -0
- package/dist/commands/migrate-observations.js +13 -0
- package/dist/commands/observe.js +5 -2
- package/dist/commands/rebuild.d.ts +11 -0
- package/dist/commands/rebuild.js +12 -0
- package/dist/commands/reflect.d.ts +11 -0
- package/dist/commands/reflect.js +13 -0
- package/dist/commands/replay.d.ts +16 -0
- package/dist/commands/replay.js +14 -0
- package/dist/commands/setup.js +2 -2
- package/dist/commands/sleep.d.ts +1 -0
- package/dist/commands/sleep.js +29 -6
- package/dist/commands/status.js +6 -6
- package/dist/commands/sync-bd.d.ts +10 -0
- package/dist/commands/sync-bd.js +9 -0
- package/dist/commands/wake.js +53 -35
- package/dist/{context-COo8oq1k.d.ts → context-BUGaWpyL.d.ts} +1 -0
- package/dist/index.d.ts +55 -20
- package/dist/index.js +67 -16
- package/hooks/clawvault/HOOK.md +3 -2
- package/hooks/clawvault/handler.js +51 -0
- package/hooks/clawvault/handler.test.js +20 -0
- package/package.json +2 -2
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DATE_HEADING_RE,
|
|
3
|
+
inferObservationType,
|
|
4
|
+
normalizeObservationContent,
|
|
5
|
+
parseObservationLine,
|
|
6
|
+
parseObservationMarkdown,
|
|
7
|
+
renderObservationMarkdown,
|
|
8
|
+
renderScoredObservationLine
|
|
9
|
+
} from "./chunk-K6XHCUFL.js";
|
|
10
|
+
import {
|
|
11
|
+
ensureLedgerStructure,
|
|
12
|
+
ensureParentDir,
|
|
13
|
+
getLegacyObservationPath,
|
|
14
|
+
getObservationPath,
|
|
15
|
+
getRawTranscriptPath,
|
|
16
|
+
toDateKey
|
|
17
|
+
} from "./chunk-Z2XBWN7A.js";
|
|
18
|
+
|
|
1
19
|
// src/observer/compressor.ts
|
|
2
|
-
var DATE_HEADING_RE = /^##\s+(\d{4}-\d{2}-\d{2})\s*$/;
|
|
3
|
-
var OBSERVATION_LINE_RE = /^(🔴|🟡|🟢)\s+(.+)$/u;
|
|
4
20
|
var CRITICAL_RE = /(?:\b(?:decision|decided|chose|chosen|selected|picked|opted|switched to)\s*:?|\bdecid(?:e|ed|ing|ion)\b|\berror\b|\bfail(?:ed|ure|ing)?\b|\bblock(?:ed|er)?\b|\bbreaking(?:\s+change)?s?\b|\bcritical\b|\b\w+\s+chosen\s+(?:for|over|as)\b|\bpublish(?:ed)?\b.*@?\d+\.\d+|\bmerge[d]?\s+(?:PR|pull\s+request)\b|\bshipped\b|\breleased?\b.*v?\d+\.\d+|\bsigned\b.*\b(?:contract|agreement|deal)\b|\bpricing\b.*\$|\bdemo\b.*\b(?:completed?|done|finished)\b|\bmeeting\b.*\b(?:completed?|done|finished)\b|\bstrategy\b.*\b(?:pivot|change|shift)\b)/i;
|
|
5
21
|
var DEADLINE_WITH_DATE_RE = /(?:(?:\bdeadline\b|\bdue(?:\s+date)?\b|\bcutoff\b).*(?:\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}(?:\/\d{2,4})?|(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+\d{1,2})|(?:\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}(?:\/\d{2,4})?|(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+\d{1,2}).*(?:\bdeadline\b|\bdue(?:\s+date)?\b|\bcutoff\b))/i;
|
|
6
22
|
var NOTABLE_RE = /\b(prefer(?:ence|s)?|likes?|dislikes?|context|pattern|architecture|approach|trade[- ]?off|milestone|stakeholder|teammate|collaborat(?:e|ed|ion)|discussion|notable|deadline|due|timeline|deploy(?:ed|ment)?|built|configured|launched|proposal|pitch|onboard(?:ed|ing)?|migrat(?:e|ed|ion)|domain|DNS|infra(?:structure)?)\b/i;
|
|
@@ -53,11 +69,12 @@ var Compressor = class {
|
|
|
53
69
|
"Rules:",
|
|
54
70
|
"- Output markdown only.",
|
|
55
71
|
"- Group observations by date heading: ## YYYY-MM-DD",
|
|
56
|
-
"- Each line
|
|
57
|
-
"-
|
|
58
|
-
"-
|
|
59
|
-
"-
|
|
60
|
-
"-
|
|
72
|
+
"- Each observation line MUST follow: - [type|c=<0.00-1.00>|i=<0.00-1.00>] <observation>",
|
|
73
|
+
"- Allowed type tags: decision, preference, fact, commitment, milestone, lesson, relationship, project",
|
|
74
|
+
"- i >= 0.80 for structural/persistent observations (major decisions, blockers, releases, commitments)",
|
|
75
|
+
"- i 0.40-0.79 for potentially important observations (notable context, preferences, milestones)",
|
|
76
|
+
"- i < 0.40 for contextual/routine observations",
|
|
77
|
+
"- Confidence c reflects extraction certainty, not importance.",
|
|
61
78
|
"- Preserve source tags when present (e.g., [main], [telegram-dm], [discord], [telegram-group]).",
|
|
62
79
|
"",
|
|
63
80
|
"QUALITY FILTERS (important):",
|
|
@@ -74,19 +91,16 @@ var Compressor = class {
|
|
|
74
91
|
"",
|
|
75
92
|
"PROJECT MILESTONES (critical \u2014 these are the most valuable observations):",
|
|
76
93
|
"Projects are NOT just code. Milestones include business, strategy, client, and operational events.",
|
|
77
|
-
"-
|
|
78
|
-
"-
|
|
94
|
+
"- Use milestone/decision/commitment types for strategic events with high importance.",
|
|
95
|
+
"- Use preference/lesson/relationship/project/fact when appropriate.",
|
|
79
96
|
"- Examples:",
|
|
80
|
-
' "
|
|
81
|
-
' "
|
|
82
|
-
' "
|
|
83
|
-
' "\u{1F534} 14:00 Merged PR #2: Active session observation into master"',
|
|
84
|
-
' "\u{1F7E1} 14:00 Deployed pitch deck to artemisa-pitch-deck.vercel.app"',
|
|
85
|
-
' "\u{1F7E1} 14:00 Moved docs.clawvault.dev domain to new Vercel project"',
|
|
97
|
+
' "- [decision|c=0.95|i=0.90] 14:00 Pricing decision: $33K one-time + $3K/mo for Artemisa"',
|
|
98
|
+
' "- [milestone|c=0.93|i=0.88] 14:00 Published clawvault@2.1.0 to npm"',
|
|
99
|
+
' "- [project|c=0.84|i=0.58] 14:00 Deployed pitch deck to artemisa-pitch-deck.vercel.app"',
|
|
86
100
|
"- Do NOT collapse multiple milestones into one line \u2014 each matters for history.",
|
|
87
101
|
"",
|
|
88
102
|
"COMMITMENT FORMAT (when someone promises/agrees to something):",
|
|
89
|
-
'-
|
|
103
|
+
'- Prefer: "- [commitment|c=...|i=...] HH:MM [COMMITMENT] <who> committed to <what> by <when>"',
|
|
90
104
|
"",
|
|
91
105
|
"Keep observations concise and factual. Aim for signal, not completeness.",
|
|
92
106
|
"",
|
|
@@ -179,7 +193,7 @@ var Compressor = class {
|
|
|
179
193
|
}
|
|
180
194
|
const cleaned = output.replace(/^```(?:markdown)?\s*/i, "").replace(/\s*```$/, "").trim();
|
|
181
195
|
const lines = cleaned.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
182
|
-
const hasObservationLine = lines.some((line) =>
|
|
196
|
+
const hasObservationLine = lines.some((line) => line.startsWith("- [") || /^(?:-\s*)?(🔴|🟡|🟢)\s+/.test(line));
|
|
183
197
|
if (!hasObservationLine) {
|
|
184
198
|
return "";
|
|
185
199
|
}
|
|
@@ -188,7 +202,7 @@ var Compressor = class {
|
|
|
188
202
|
|
|
189
203
|
${cleaned}`;
|
|
190
204
|
const sanitized = this.sanitizeWikiLinks(result);
|
|
191
|
-
return this.
|
|
205
|
+
return this.enforceImportanceRules(sanitized);
|
|
192
206
|
}
|
|
193
207
|
/**
|
|
194
208
|
* Fix wiki-link corruption from LLM compression.
|
|
@@ -204,24 +218,43 @@ ${cleaned}`;
|
|
|
204
218
|
result = result.replace(/ {2,}/g, " ");
|
|
205
219
|
return result;
|
|
206
220
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
enforceImportanceRules(markdown) {
|
|
222
|
+
const parsed = parseObservationMarkdown(markdown);
|
|
223
|
+
if (parsed.length === 0) {
|
|
224
|
+
return "";
|
|
225
|
+
}
|
|
226
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
227
|
+
for (const record of parsed) {
|
|
228
|
+
const adjusted = this.enforceImportanceForRecord(record);
|
|
229
|
+
const bucket = grouped.get(record.date) ?? [];
|
|
230
|
+
bucket.push(adjusted);
|
|
231
|
+
grouped.set(record.date, bucket);
|
|
232
|
+
}
|
|
233
|
+
return renderObservationMarkdown(grouped);
|
|
234
|
+
}
|
|
235
|
+
enforceImportanceForRecord(record) {
|
|
236
|
+
let importance = record.importance;
|
|
237
|
+
let confidence = record.confidence;
|
|
238
|
+
let type = record.type;
|
|
239
|
+
if (this.isCriticalContent(record.content)) {
|
|
240
|
+
importance = Math.max(importance, 0.85);
|
|
241
|
+
confidence = Math.max(confidence, 0.85);
|
|
242
|
+
if (type === "fact") {
|
|
243
|
+
type = inferObservationType(record.content);
|
|
222
244
|
}
|
|
223
|
-
|
|
224
|
-
|
|
245
|
+
} else if (this.isNotableContent(record.content)) {
|
|
246
|
+
importance = Math.max(importance, 0.5);
|
|
247
|
+
confidence = Math.max(confidence, 0.75);
|
|
248
|
+
}
|
|
249
|
+
if (type === "decision" || type === "commitment" || type === "milestone") {
|
|
250
|
+
importance = Math.max(importance, 0.6);
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
type,
|
|
254
|
+
confidence: this.clamp01(confidence),
|
|
255
|
+
importance: this.clamp01(importance),
|
|
256
|
+
content: record.content
|
|
257
|
+
};
|
|
225
258
|
}
|
|
226
259
|
fallbackCompression(messages) {
|
|
227
260
|
const sections = /* @__PURE__ */ new Map();
|
|
@@ -231,107 +264,96 @@ ${cleaned}`;
|
|
|
231
264
|
if (!normalized) continue;
|
|
232
265
|
const date = this.extractDate(message) ?? this.formatDate(this.now());
|
|
233
266
|
const time = this.extractTime(message) ?? this.formatTime(this.now());
|
|
234
|
-
const priority = this.inferPriority(normalized);
|
|
235
267
|
const line = `${time} ${normalized}`;
|
|
236
|
-
const
|
|
268
|
+
const type = inferObservationType(line);
|
|
269
|
+
const importance = this.inferImportance(line, type);
|
|
270
|
+
const confidence = this.inferConfidence(line, type, importance);
|
|
271
|
+
const dedupeKey = `${date}|${type}|${normalizeObservationContent(line)}`;
|
|
237
272
|
if (seen.has(dedupeKey)) continue;
|
|
238
273
|
seen.add(dedupeKey);
|
|
239
274
|
const bucket = sections.get(date) ?? [];
|
|
240
|
-
bucket.push({
|
|
275
|
+
bucket.push({ type, confidence, importance, content: line });
|
|
241
276
|
sections.set(date, bucket);
|
|
242
277
|
}
|
|
243
278
|
if (sections.size === 0) {
|
|
244
279
|
const date = this.formatDate(this.now());
|
|
245
|
-
sections.set(date, [{
|
|
280
|
+
sections.set(date, [{
|
|
281
|
+
type: "fact",
|
|
282
|
+
confidence: 0.7,
|
|
283
|
+
importance: 0.2,
|
|
284
|
+
content: `${this.formatTime(this.now())} Processed session updates.`
|
|
285
|
+
}]);
|
|
246
286
|
}
|
|
247
287
|
return this.renderSections(sections);
|
|
248
288
|
}
|
|
249
289
|
mergeObservations(existing, incoming) {
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
if (
|
|
290
|
+
const existingRecords = parseObservationMarkdown(existing);
|
|
291
|
+
const incomingRecords = parseObservationMarkdown(incoming);
|
|
292
|
+
if (incomingRecords.length === 0) {
|
|
253
293
|
return existing.trim();
|
|
254
294
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
for (const line of lines) {
|
|
265
|
-
const normalized = this.normalizeObservationContent(line.content);
|
|
266
|
-
if (!normalized || seen.has(normalized)) {
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
seen.add(normalized);
|
|
270
|
-
current.push(line);
|
|
271
|
-
}
|
|
272
|
-
existingSections.set(date, current);
|
|
295
|
+
const merged = /* @__PURE__ */ new Map();
|
|
296
|
+
for (const record of existingRecords) {
|
|
297
|
+
this.mergeRecord(merged, {
|
|
298
|
+
date: record.date,
|
|
299
|
+
type: record.type,
|
|
300
|
+
confidence: record.confidence,
|
|
301
|
+
importance: record.importance,
|
|
302
|
+
content: record.content
|
|
303
|
+
});
|
|
273
304
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
seen.add(normalized);
|
|
285
|
-
deduped.push(line);
|
|
305
|
+
for (const record of incomingRecords) {
|
|
306
|
+
this.mergeRecord(merged, {
|
|
307
|
+
date: record.date,
|
|
308
|
+
type: record.type,
|
|
309
|
+
confidence: record.confidence,
|
|
310
|
+
importance: record.importance,
|
|
311
|
+
content: record.content
|
|
312
|
+
});
|
|
286
313
|
}
|
|
287
|
-
return
|
|
314
|
+
return this.renderSections(merged);
|
|
288
315
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
let currentDate = null;
|
|
295
|
-
for (const rawLine of markdown.split(/\r?\n/)) {
|
|
296
|
-
const dateMatch = rawLine.match(DATE_HEADING_RE);
|
|
297
|
-
if (dateMatch) {
|
|
298
|
-
currentDate = dateMatch[1];
|
|
299
|
-
if (!sections.has(currentDate)) {
|
|
300
|
-
sections.set(currentDate, []);
|
|
301
|
-
}
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
if (!currentDate) continue;
|
|
305
|
-
const lineMatch = rawLine.match(OBSERVATION_LINE_RE);
|
|
306
|
-
if (!lineMatch) continue;
|
|
307
|
-
const bucket = sections.get(currentDate) ?? [];
|
|
316
|
+
mergeRecord(sections, input) {
|
|
317
|
+
const bucket = sections.get(input.date) ?? [];
|
|
318
|
+
const key = normalizeObservationContent(input.content);
|
|
319
|
+
const index = bucket.findIndex((line) => normalizeObservationContent(line.content) === key);
|
|
320
|
+
if (index === -1) {
|
|
308
321
|
bucket.push({
|
|
309
|
-
|
|
310
|
-
|
|
322
|
+
type: input.type,
|
|
323
|
+
confidence: this.clamp01(input.confidence),
|
|
324
|
+
importance: this.clamp01(input.importance),
|
|
325
|
+
content: input.content.trim()
|
|
311
326
|
});
|
|
312
|
-
sections.set(
|
|
327
|
+
sections.set(input.date, bucket);
|
|
328
|
+
return;
|
|
313
329
|
}
|
|
314
|
-
|
|
330
|
+
const existing = bucket[index];
|
|
331
|
+
bucket[index] = {
|
|
332
|
+
type: input.importance >= existing.importance ? input.type : existing.type,
|
|
333
|
+
confidence: this.clamp01(Math.max(existing.confidence, input.confidence)),
|
|
334
|
+
importance: this.clamp01(Math.max(existing.importance, input.importance)),
|
|
335
|
+
content: existing.content.length >= input.content.length ? existing.content : input.content
|
|
336
|
+
};
|
|
337
|
+
sections.set(input.date, bucket);
|
|
315
338
|
}
|
|
316
339
|
renderSections(sections) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
340
|
+
return renderObservationMarkdown(sections);
|
|
341
|
+
}
|
|
342
|
+
inferImportance(text, type) {
|
|
343
|
+
if (this.isCriticalContent(text)) return 0.9;
|
|
344
|
+
if (this.isNotableContent(text)) return 0.6;
|
|
345
|
+
if (type === "decision" || type === "commitment" || type === "milestone") return 0.55;
|
|
346
|
+
if (type === "preference" || type === "lesson" || type === "relationship" || type === "project") return 0.45;
|
|
347
|
+
return 0.2;
|
|
348
|
+
}
|
|
349
|
+
inferConfidence(text, type, importance) {
|
|
350
|
+
let confidence = 0.72;
|
|
351
|
+
if (importance >= 0.8) confidence += 0.12;
|
|
352
|
+
if (type === "decision" || type === "commitment" || type === "milestone") confidence += 0.06;
|
|
353
|
+
if (/\b(?:decided|chose|committed|deadline|released|merged)\b/i.test(text)) {
|
|
354
|
+
confidence += 0.05;
|
|
328
355
|
}
|
|
329
|
-
return
|
|
330
|
-
}
|
|
331
|
-
inferPriority(text) {
|
|
332
|
-
if (this.isCriticalContent(text)) return "\u{1F534}";
|
|
333
|
-
if (this.isNotableContent(text)) return "\u{1F7E1}";
|
|
334
|
-
return "\u{1F7E2}";
|
|
356
|
+
return this.clamp01(confidence);
|
|
335
357
|
}
|
|
336
358
|
isCriticalContent(text) {
|
|
337
359
|
return CRITICAL_RE.test(text) || DEADLINE_WITH_DATE_RE.test(text);
|
|
@@ -359,11 +381,17 @@ ${cleaned}`;
|
|
|
359
381
|
formatTime(date) {
|
|
360
382
|
return date.toISOString().slice(11, 16);
|
|
361
383
|
}
|
|
384
|
+
clamp01(value) {
|
|
385
|
+
if (!Number.isFinite(value)) return 0;
|
|
386
|
+
if (value < 0) return 0;
|
|
387
|
+
if (value > 1) return 1;
|
|
388
|
+
return value;
|
|
389
|
+
}
|
|
362
390
|
};
|
|
363
391
|
|
|
364
392
|
// src/observer/reflector.ts
|
|
365
393
|
var DATE_HEADING_RE2 = /^##\s+(\d{4}-\d{2}-\d{2})\s*$/;
|
|
366
|
-
var
|
|
394
|
+
var OBSERVATION_LINE_RE = /^(🔴|🟡|🟢)\s+(.+)$/u;
|
|
367
395
|
var Reflector = class {
|
|
368
396
|
now;
|
|
369
397
|
constructor(options = {}) {
|
|
@@ -431,7 +459,7 @@ var Reflector = class {
|
|
|
431
459
|
continue;
|
|
432
460
|
}
|
|
433
461
|
if (!currentDate) continue;
|
|
434
|
-
const lineMatch = rawLine.match(
|
|
462
|
+
const lineMatch = rawLine.match(OBSERVATION_LINE_RE);
|
|
435
463
|
if (!lineMatch) continue;
|
|
436
464
|
const bucket = sections.get(currentDate) ?? [];
|
|
437
465
|
bucket.push({
|
|
@@ -524,8 +552,16 @@ var CATEGORY_PATTERNS = [
|
|
|
524
552
|
]
|
|
525
553
|
}
|
|
526
554
|
];
|
|
527
|
-
var
|
|
528
|
-
|
|
555
|
+
var TYPE_TO_CATEGORY = {
|
|
556
|
+
decision: "decisions",
|
|
557
|
+
preference: "preferences",
|
|
558
|
+
fact: "facts",
|
|
559
|
+
commitment: "commitments",
|
|
560
|
+
milestone: "projects",
|
|
561
|
+
lesson: "lessons",
|
|
562
|
+
relationship: "people",
|
|
563
|
+
project: "projects"
|
|
564
|
+
};
|
|
529
565
|
var Router = class {
|
|
530
566
|
vaultPath;
|
|
531
567
|
constructor(vaultPath) {
|
|
@@ -533,17 +569,25 @@ var Router = class {
|
|
|
533
569
|
}
|
|
534
570
|
/**
|
|
535
571
|
* Takes observation markdown and routes items to appropriate vault categories.
|
|
536
|
-
*
|
|
572
|
+
* Routes only items with importance >= 0.4.
|
|
537
573
|
* Returns a summary of what was routed where.
|
|
538
574
|
*/
|
|
539
575
|
route(observationMarkdown) {
|
|
540
576
|
const items = this.parseObservations(observationMarkdown);
|
|
541
577
|
const routed = [];
|
|
542
578
|
for (const item of items) {
|
|
543
|
-
if (item.
|
|
544
|
-
const category = this.categorize(item.content);
|
|
579
|
+
if (item.importance < 0.4) continue;
|
|
580
|
+
const category = this.categorize(item.type, item.content);
|
|
545
581
|
if (!category) continue;
|
|
546
|
-
const routedItem = {
|
|
582
|
+
const routedItem = {
|
|
583
|
+
category,
|
|
584
|
+
title: item.title,
|
|
585
|
+
content: item.content,
|
|
586
|
+
type: item.type,
|
|
587
|
+
confidence: item.confidence,
|
|
588
|
+
importance: item.importance,
|
|
589
|
+
date: item.date
|
|
590
|
+
};
|
|
547
591
|
routed.push(routedItem);
|
|
548
592
|
this.appendToCategory(category, routedItem);
|
|
549
593
|
}
|
|
@@ -551,24 +595,21 @@ var Router = class {
|
|
|
551
595
|
return { routed, summary };
|
|
552
596
|
}
|
|
553
597
|
parseObservations(markdown) {
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
598
|
+
const records = parseObservationMarkdown(markdown);
|
|
599
|
+
return records.map((record) => ({
|
|
600
|
+
type: record.type,
|
|
601
|
+
confidence: record.confidence,
|
|
602
|
+
importance: record.importance,
|
|
603
|
+
content: record.content,
|
|
604
|
+
date: record.date,
|
|
605
|
+
title: record.content.slice(0, 80).replace(/[^a-zA-Z0-9\s-]/g, "").trim()
|
|
606
|
+
}));
|
|
607
|
+
}
|
|
608
|
+
categorize(type, content) {
|
|
609
|
+
const typedCategory = TYPE_TO_CATEGORY[type];
|
|
610
|
+
if (typedCategory) {
|
|
611
|
+
return typedCategory;
|
|
568
612
|
}
|
|
569
|
-
return results;
|
|
570
|
-
}
|
|
571
|
-
categorize(content) {
|
|
572
613
|
for (const { category, patterns } of CATEGORY_PATTERNS) {
|
|
573
614
|
if (patterns.some((p) => p.test(content))) {
|
|
574
615
|
return category;
|
|
@@ -577,7 +618,9 @@ var Router = class {
|
|
|
577
618
|
return null;
|
|
578
619
|
}
|
|
579
620
|
normalizeForDedup(content) {
|
|
580
|
-
return
|
|
621
|
+
return normalizeObservationContent(
|
|
622
|
+
content.replace(/\[\[[^\]]*\]\]/g, (match) => match.replace(/\[\[|\]\]/g, ""))
|
|
623
|
+
);
|
|
581
624
|
}
|
|
582
625
|
/**
|
|
583
626
|
* Extract entity slug from observation content for people/projects routing.
|
|
@@ -634,12 +677,15 @@ var Router = class {
|
|
|
634
677
|
const normalizedNew = this.normalizeForDedup(item.content);
|
|
635
678
|
const existingLines = existing.split(/\r?\n/);
|
|
636
679
|
for (const line of existingLines) {
|
|
637
|
-
const lineContent = line.replace(/^-\s
|
|
638
|
-
|
|
680
|
+
const lineContent = line.replace(/^-\s*/, "").trim();
|
|
681
|
+
const parsed = parseObservationLine(lineContent, item.date);
|
|
682
|
+
const candidate = parsed ? parsed.content : lineContent;
|
|
683
|
+
if (this.normalizeForDedup(candidate) === normalizedNew) return;
|
|
639
684
|
}
|
|
640
685
|
for (const line of existingLines) {
|
|
641
|
-
const lineContent = line.replace(/^-\s
|
|
642
|
-
const
|
|
686
|
+
const lineContent = line.replace(/^-\s*/, "").trim();
|
|
687
|
+
const parsed = parseObservationLine(lineContent, item.date);
|
|
688
|
+
const normalizedExisting = this.normalizeForDedup(parsed ? parsed.content : lineContent);
|
|
643
689
|
if (normalizedExisting.length > 10 && normalizedNew.length > 10) {
|
|
644
690
|
const shorter = normalizedNew.length < normalizedExisting.length ? normalizedNew : normalizedExisting;
|
|
645
691
|
const longer = normalizedNew.length >= normalizedExisting.length ? normalizedNew : normalizedExisting;
|
|
@@ -647,7 +693,12 @@ var Router = class {
|
|
|
647
693
|
}
|
|
648
694
|
}
|
|
649
695
|
const linkedContent = this.addWikiLinks(item.content);
|
|
650
|
-
const entry =
|
|
696
|
+
const entry = renderScoredObservationLine({
|
|
697
|
+
type: item.type,
|
|
698
|
+
confidence: item.confidence,
|
|
699
|
+
importance: item.importance,
|
|
700
|
+
content: linkedContent
|
|
701
|
+
});
|
|
651
702
|
const entitySlug = this.extractEntitySlug(item.content, category);
|
|
652
703
|
const headerLabel = entitySlug ? `${category}/${entitySlug}` : category;
|
|
653
704
|
const header = existing ? "" : `# ${headerLabel} \u2014 ${item.date}
|
|
@@ -842,44 +893,48 @@ ${entry}
|
|
|
842
893
|
};
|
|
843
894
|
|
|
844
895
|
// src/observer/observer.ts
|
|
845
|
-
var DATE_HEADING_RE4 = /^##\s+(\d{4}-\d{2}-\d{2})\s*$/;
|
|
846
|
-
var OBSERVATION_LINE_RE4 = /^(🔴|🟡|🟢)\s+(.+)$/u;
|
|
847
896
|
var Observer = class {
|
|
848
897
|
vaultPath;
|
|
849
|
-
observationsDir;
|
|
850
898
|
tokenThreshold;
|
|
899
|
+
// Kept for backwards API compatibility with callers that still pass this.
|
|
900
|
+
// Reflection now runs explicitly via clawvault reflect.
|
|
851
901
|
reflectThreshold;
|
|
852
902
|
compressor;
|
|
853
903
|
reflector;
|
|
854
904
|
now;
|
|
905
|
+
rawCapture;
|
|
855
906
|
router;
|
|
856
907
|
pendingMessages = [];
|
|
857
908
|
observationsCache = "";
|
|
858
909
|
lastRoutingSummary = "";
|
|
859
910
|
constructor(vaultPath, options = {}) {
|
|
860
911
|
this.vaultPath = path2.resolve(vaultPath);
|
|
861
|
-
this.observationsDir = path2.join(this.vaultPath, "observations");
|
|
862
912
|
this.tokenThreshold = options.tokenThreshold ?? 3e4;
|
|
863
913
|
this.reflectThreshold = options.reflectThreshold ?? 4e4;
|
|
864
914
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
865
915
|
this.compressor = options.compressor ?? new Compressor({ model: options.model, now: this.now });
|
|
866
916
|
this.reflector = options.reflector ?? new Reflector({ now: this.now });
|
|
917
|
+
this.rawCapture = options.rawCapture ?? true;
|
|
867
918
|
this.router = new Router(vaultPath);
|
|
868
|
-
|
|
919
|
+
ensureLedgerStructure(this.vaultPath);
|
|
869
920
|
this.observationsCache = this.readTodayObservations();
|
|
870
921
|
}
|
|
871
|
-
async processMessages(messages) {
|
|
922
|
+
async processMessages(messages, options = {}) {
|
|
872
923
|
const incoming = messages.map((message) => message.trim()).filter(Boolean);
|
|
873
924
|
if (incoming.length === 0) {
|
|
874
925
|
return;
|
|
875
926
|
}
|
|
927
|
+
if (this.rawCapture) {
|
|
928
|
+
this.persistRawMessages(incoming, options);
|
|
929
|
+
}
|
|
876
930
|
this.pendingMessages.push(...incoming);
|
|
877
931
|
const buffered = this.pendingMessages.join("\n");
|
|
878
932
|
if (this.estimateTokens(buffered) < this.tokenThreshold) {
|
|
879
933
|
return;
|
|
880
934
|
}
|
|
881
|
-
const
|
|
882
|
-
const
|
|
935
|
+
const today = this.now();
|
|
936
|
+
const todayPath = getObservationPath(this.vaultPath, today);
|
|
937
|
+
const existingRaw = this.readObservationForDate(today);
|
|
883
938
|
const existing = this.deduplicateObservationMarkdown(existingRaw);
|
|
884
939
|
if (existingRaw.trim() !== existing) {
|
|
885
940
|
this.writeObservationFile(todayPath, existing);
|
|
@@ -896,7 +951,6 @@ var Observer = class {
|
|
|
896
951
|
if (summary) {
|
|
897
952
|
this.lastRoutingSummary = summary;
|
|
898
953
|
}
|
|
899
|
-
await this.reflectIfNeeded();
|
|
900
954
|
}
|
|
901
955
|
/**
|
|
902
956
|
* Force-flush pending messages regardless of threshold.
|
|
@@ -906,8 +960,9 @@ var Observer = class {
|
|
|
906
960
|
if (this.pendingMessages.length === 0) {
|
|
907
961
|
return { observations: this.observationsCache, routingSummary: this.lastRoutingSummary };
|
|
908
962
|
}
|
|
909
|
-
const
|
|
910
|
-
const
|
|
963
|
+
const today = this.now();
|
|
964
|
+
const todayPath = getObservationPath(this.vaultPath, today);
|
|
965
|
+
const existingRaw = this.readObservationForDate(today);
|
|
911
966
|
const existing = this.deduplicateObservationMarkdown(existingRaw);
|
|
912
967
|
if (existingRaw.trim() !== existing) {
|
|
913
968
|
this.writeObservationFile(todayPath, existing);
|
|
@@ -920,7 +975,6 @@ var Observer = class {
|
|
|
920
975
|
this.observationsCache = compressed;
|
|
921
976
|
const { summary } = this.router.route(compressed);
|
|
922
977
|
this.lastRoutingSummary = summary;
|
|
923
|
-
await this.reflectIfNeeded();
|
|
924
978
|
}
|
|
925
979
|
return { observations: this.observationsCache, routingSummary: this.lastRoutingSummary };
|
|
926
980
|
}
|
|
@@ -931,13 +985,16 @@ var Observer = class {
|
|
|
931
985
|
estimateTokens(input) {
|
|
932
986
|
return Math.ceil(input.length / 4);
|
|
933
987
|
}
|
|
934
|
-
getObservationPath(date) {
|
|
935
|
-
const datePart = date.toISOString().split("T")[0];
|
|
936
|
-
return path2.join(this.observationsDir, `${datePart}.md`);
|
|
937
|
-
}
|
|
938
988
|
readTodayObservations() {
|
|
939
|
-
|
|
940
|
-
|
|
989
|
+
return this.readObservationForDate(this.now());
|
|
990
|
+
}
|
|
991
|
+
readObservationForDate(date) {
|
|
992
|
+
const ledgerPath = getObservationPath(this.vaultPath, date);
|
|
993
|
+
const ledgerValue = this.readObservationFile(ledgerPath);
|
|
994
|
+
if (ledgerValue) {
|
|
995
|
+
return ledgerValue;
|
|
996
|
+
}
|
|
997
|
+
return this.readObservationFile(getLegacyObservationPath(this.vaultPath, toDateKey(date)));
|
|
941
998
|
}
|
|
942
999
|
readObservationFile(filePath) {
|
|
943
1000
|
if (!fs2.existsSync(filePath)) {
|
|
@@ -946,292 +1003,69 @@ var Observer = class {
|
|
|
946
1003
|
return fs2.readFileSync(filePath, "utf-8").trim();
|
|
947
1004
|
}
|
|
948
1005
|
writeObservationFile(filePath, content) {
|
|
949
|
-
|
|
1006
|
+
ensureParentDir(filePath);
|
|
950
1007
|
fs2.writeFileSync(filePath, `${content.trim()}
|
|
951
1008
|
`, "utf-8");
|
|
952
1009
|
}
|
|
953
|
-
getObservationFiles() {
|
|
954
|
-
if (!fs2.existsSync(this.observationsDir)) {
|
|
955
|
-
return [];
|
|
956
|
-
}
|
|
957
|
-
return fs2.readdirSync(this.observationsDir).filter((name) => name.endsWith(".md")).sort((a, b) => a.localeCompare(b)).map((name) => path2.join(this.observationsDir, name));
|
|
958
|
-
}
|
|
959
|
-
readObservationCorpus() {
|
|
960
|
-
const files = this.getObservationFiles();
|
|
961
|
-
if (files.length === 0) {
|
|
962
|
-
return "";
|
|
963
|
-
}
|
|
964
|
-
return files.map((filePath) => this.readObservationFile(filePath)).filter(Boolean).join("\n\n");
|
|
965
|
-
}
|
|
966
1010
|
deduplicateObservationMarkdown(markdown) {
|
|
967
|
-
const parsed =
|
|
968
|
-
if (parsed.
|
|
1011
|
+
const parsed = parseObservationMarkdown(markdown);
|
|
1012
|
+
if (parsed.length === 0) {
|
|
969
1013
|
return markdown.trim();
|
|
970
1014
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
if (!sections.has(currentDate)) {
|
|
994
|
-
sections.set(currentDate, []);
|
|
995
|
-
}
|
|
996
|
-
continue;
|
|
997
|
-
}
|
|
998
|
-
if (!currentDate) {
|
|
999
|
-
continue;
|
|
1000
|
-
}
|
|
1001
|
-
const lineMatch = rawLine.match(OBSERVATION_LINE_RE4);
|
|
1002
|
-
if (!lineMatch) {
|
|
1003
|
-
continue;
|
|
1015
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1016
|
+
for (const record of parsed) {
|
|
1017
|
+
const bucket = grouped.get(record.date) ?? [];
|
|
1018
|
+
const normalized = normalizeObservationContent(record.content);
|
|
1019
|
+
const existingIndex = bucket.findIndex(
|
|
1020
|
+
(line) => normalizeObservationContent(line.content) === normalized
|
|
1021
|
+
);
|
|
1022
|
+
if (existingIndex === -1) {
|
|
1023
|
+
bucket.push({
|
|
1024
|
+
type: record.type,
|
|
1025
|
+
confidence: record.confidence,
|
|
1026
|
+
importance: record.importance,
|
|
1027
|
+
content: record.content
|
|
1028
|
+
});
|
|
1029
|
+
} else {
|
|
1030
|
+
const existing = bucket[existingIndex];
|
|
1031
|
+
bucket[existingIndex] = {
|
|
1032
|
+
type: record.importance >= existing.importance ? record.type : existing.type,
|
|
1033
|
+
confidence: Math.max(existing.confidence, record.confidence),
|
|
1034
|
+
importance: Math.max(existing.importance, record.importance),
|
|
1035
|
+
content: existing.content.length >= record.content.length ? existing.content : record.content
|
|
1036
|
+
};
|
|
1004
1037
|
}
|
|
1005
|
-
|
|
1006
|
-
current.push({
|
|
1007
|
-
priority: lineMatch[1],
|
|
1008
|
-
content: lineMatch[2].trim()
|
|
1009
|
-
});
|
|
1010
|
-
sections.set(currentDate, current);
|
|
1038
|
+
grouped.set(record.date, bucket);
|
|
1011
1039
|
}
|
|
1012
|
-
return
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
const
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
return chunks.join("\n").trim();
|
|
1030
|
-
}
|
|
1031
|
-
normalizeObservationContent(content) {
|
|
1032
|
-
return content.replace(/^\d{2}:\d{2}\s+/, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
1040
|
+
return renderObservationMarkdown(grouped);
|
|
1041
|
+
}
|
|
1042
|
+
persistRawMessages(messages, options) {
|
|
1043
|
+
const source = this.sanitizeSource(options.source ?? "openclaw");
|
|
1044
|
+
const messageTimestamp = options.timestamp ?? this.now();
|
|
1045
|
+
const rawPath = getRawTranscriptPath(this.vaultPath, source, messageTimestamp);
|
|
1046
|
+
ensureParentDir(rawPath);
|
|
1047
|
+
const records = messages.map((message) => JSON.stringify({
|
|
1048
|
+
recordedAt: this.now().toISOString(),
|
|
1049
|
+
timestamp: messageTimestamp.toISOString(),
|
|
1050
|
+
source,
|
|
1051
|
+
sessionKey: options.sessionKey ?? null,
|
|
1052
|
+
transcriptId: options.transcriptId ?? null,
|
|
1053
|
+
message
|
|
1054
|
+
}));
|
|
1055
|
+
fs2.appendFileSync(rawPath, `${records.join("\n")}
|
|
1056
|
+
`, "utf-8");
|
|
1033
1057
|
}
|
|
1034
|
-
|
|
1035
|
-
const
|
|
1036
|
-
if (
|
|
1037
|
-
return;
|
|
1038
|
-
}
|
|
1039
|
-
for (const filePath of this.getObservationFiles()) {
|
|
1040
|
-
const current = this.readObservationFile(filePath);
|
|
1041
|
-
if (!current) continue;
|
|
1042
|
-
const reflected = this.reflector.reflect(current).trim();
|
|
1043
|
-
if (!reflected) {
|
|
1044
|
-
fs2.rmSync(filePath, { force: true });
|
|
1045
|
-
continue;
|
|
1046
|
-
}
|
|
1047
|
-
this.writeObservationFile(filePath, reflected);
|
|
1058
|
+
sanitizeSource(source) {
|
|
1059
|
+
const normalized = source.trim().toLowerCase();
|
|
1060
|
+
if (/^[a-z0-9_-]{1,64}$/.test(normalized)) {
|
|
1061
|
+
return normalized;
|
|
1048
1062
|
}
|
|
1049
|
-
|
|
1063
|
+
return "openclaw";
|
|
1050
1064
|
}
|
|
1051
1065
|
};
|
|
1052
1066
|
|
|
1053
|
-
// src/observer/session-parser.ts
|
|
1054
|
-
import * as fs3 from "fs";
|
|
1055
|
-
import * as path3 from "path";
|
|
1056
|
-
var JSONL_SAMPLE_LIMIT = 20;
|
|
1057
|
-
var MARKDOWN_SIGNAL_RE = /^(#{1,6}\s|[-*+]\s|>\s)/;
|
|
1058
|
-
var MARKDOWN_INLINE_RE = /(\[[^\]]+\]\([^)]+\)|[*_`~])/;
|
|
1059
|
-
function normalizeText(value) {
|
|
1060
|
-
return value.replace(/\s+/g, " ").trim();
|
|
1061
|
-
}
|
|
1062
|
-
function extractText(value) {
|
|
1063
|
-
if (typeof value === "string") {
|
|
1064
|
-
return normalizeText(value);
|
|
1065
|
-
}
|
|
1066
|
-
if (Array.isArray(value)) {
|
|
1067
|
-
const parts = [];
|
|
1068
|
-
for (const part of value) {
|
|
1069
|
-
const extracted = extractText(part);
|
|
1070
|
-
if (extracted) {
|
|
1071
|
-
parts.push(extracted);
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
return normalizeText(parts.join(" "));
|
|
1075
|
-
}
|
|
1076
|
-
if (!value || typeof value !== "object") {
|
|
1077
|
-
return "";
|
|
1078
|
-
}
|
|
1079
|
-
const record = value;
|
|
1080
|
-
if (typeof record.text === "string") {
|
|
1081
|
-
return normalizeText(record.text);
|
|
1082
|
-
}
|
|
1083
|
-
if (typeof record.content === "string") {
|
|
1084
|
-
return normalizeText(record.content);
|
|
1085
|
-
}
|
|
1086
|
-
return "";
|
|
1087
|
-
}
|
|
1088
|
-
function normalizeRole(role) {
|
|
1089
|
-
if (typeof role !== "string") {
|
|
1090
|
-
return "";
|
|
1091
|
-
}
|
|
1092
|
-
const normalized = role.trim().toLowerCase();
|
|
1093
|
-
if (!normalized) {
|
|
1094
|
-
return "";
|
|
1095
|
-
}
|
|
1096
|
-
return normalized;
|
|
1097
|
-
}
|
|
1098
|
-
function isLikelyJsonMessage(value) {
|
|
1099
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1100
|
-
return false;
|
|
1101
|
-
}
|
|
1102
|
-
const record = value;
|
|
1103
|
-
if ("role" in record && "content" in record) {
|
|
1104
|
-
return true;
|
|
1105
|
-
}
|
|
1106
|
-
if (record.type === "message" && record.message && typeof record.message === "object") {
|
|
1107
|
-
return true;
|
|
1108
|
-
}
|
|
1109
|
-
return false;
|
|
1110
|
-
}
|
|
1111
|
-
function parseJsonLine(line) {
|
|
1112
|
-
let parsed;
|
|
1113
|
-
try {
|
|
1114
|
-
parsed = JSON.parse(line);
|
|
1115
|
-
} catch {
|
|
1116
|
-
return "";
|
|
1117
|
-
}
|
|
1118
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1119
|
-
return "";
|
|
1120
|
-
}
|
|
1121
|
-
const entry = parsed;
|
|
1122
|
-
if ("role" in entry && "content" in entry) {
|
|
1123
|
-
const role = normalizeRole(entry.role);
|
|
1124
|
-
const content = extractText(entry.content);
|
|
1125
|
-
if (!content) return "";
|
|
1126
|
-
return role ? `${role}: ${content}` : content;
|
|
1127
|
-
}
|
|
1128
|
-
if (entry.type === "message" && entry.message && typeof entry.message === "object") {
|
|
1129
|
-
const message = entry.message;
|
|
1130
|
-
const role = normalizeRole(message.role);
|
|
1131
|
-
const content = extractText(message.content);
|
|
1132
|
-
if (!content) return "";
|
|
1133
|
-
return role ? `${role}: ${content}` : content;
|
|
1134
|
-
}
|
|
1135
|
-
return "";
|
|
1136
|
-
}
|
|
1137
|
-
function parseJsonLines(raw) {
|
|
1138
|
-
const messages = [];
|
|
1139
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
1140
|
-
const trimmed = line.trim();
|
|
1141
|
-
if (!trimmed) continue;
|
|
1142
|
-
const parsed = parseJsonLine(trimmed);
|
|
1143
|
-
if (parsed) {
|
|
1144
|
-
messages.push(parsed);
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
return messages;
|
|
1148
|
-
}
|
|
1149
|
-
function stripMarkdownSyntax(text) {
|
|
1150
|
-
return normalizeText(
|
|
1151
|
-
text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[*_`~]/g, "").replace(/<[^>]+>/g, "")
|
|
1152
|
-
);
|
|
1153
|
-
}
|
|
1154
|
-
function normalizeMarkdownLine(line) {
|
|
1155
|
-
return stripMarkdownSyntax(
|
|
1156
|
-
line.replace(/^>\s*/, "").replace(/^[-*+]\s+/, "").replace(/^#{1,6}\s+/, "")
|
|
1157
|
-
);
|
|
1158
|
-
}
|
|
1159
|
-
function parseMarkdown(raw) {
|
|
1160
|
-
const withoutCodeBlocks = raw.replace(/```[\s\S]*?```/g, " ");
|
|
1161
|
-
const blocks = withoutCodeBlocks.split(/\r?\n\s*\r?\n/).map((block) => block.trim()).filter(Boolean);
|
|
1162
|
-
const messages = [];
|
|
1163
|
-
for (const block of blocks) {
|
|
1164
|
-
const lines = block.split(/\r?\n/).map((line) => normalizeMarkdownLine(line)).filter(Boolean);
|
|
1165
|
-
if (lines.length === 0) {
|
|
1166
|
-
continue;
|
|
1167
|
-
}
|
|
1168
|
-
const joined = stripMarkdownSyntax(lines.join(" "));
|
|
1169
|
-
if (!joined) continue;
|
|
1170
|
-
const roleMatch = /^(user|assistant|system|tool)\s*:?\s*(.+)$/i.exec(joined);
|
|
1171
|
-
if (roleMatch) {
|
|
1172
|
-
const role = normalizeRole(roleMatch[1]);
|
|
1173
|
-
const content = normalizeText(roleMatch[2]);
|
|
1174
|
-
if (content) {
|
|
1175
|
-
messages.push(`${role}: ${content}`);
|
|
1176
|
-
}
|
|
1177
|
-
continue;
|
|
1178
|
-
}
|
|
1179
|
-
messages.push(joined);
|
|
1180
|
-
}
|
|
1181
|
-
return messages;
|
|
1182
|
-
}
|
|
1183
|
-
function parsePlainText(raw) {
|
|
1184
|
-
return raw.split(/\r?\n/).map((line) => normalizeText(line)).filter(Boolean);
|
|
1185
|
-
}
|
|
1186
|
-
function detectSessionFormat(raw, filePath) {
|
|
1187
|
-
const nonEmptyLines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1188
|
-
if (nonEmptyLines.length === 0) {
|
|
1189
|
-
return "plain";
|
|
1190
|
-
}
|
|
1191
|
-
const sample = nonEmptyLines.slice(0, JSONL_SAMPLE_LIMIT);
|
|
1192
|
-
const jsonHits = sample.filter((line) => {
|
|
1193
|
-
try {
|
|
1194
|
-
const parsed = JSON.parse(line);
|
|
1195
|
-
return isLikelyJsonMessage(parsed);
|
|
1196
|
-
} catch {
|
|
1197
|
-
return false;
|
|
1198
|
-
}
|
|
1199
|
-
}).length;
|
|
1200
|
-
if (jsonHits >= Math.max(1, Math.ceil(sample.length * 0.6))) {
|
|
1201
|
-
return "jsonl";
|
|
1202
|
-
}
|
|
1203
|
-
const ext = path3.extname(filePath).toLowerCase();
|
|
1204
|
-
if (ext === ".md" || ext === ".markdown") {
|
|
1205
|
-
return "markdown";
|
|
1206
|
-
}
|
|
1207
|
-
const markdownSignals = sample.filter((line) => MARKDOWN_SIGNAL_RE.test(line) || MARKDOWN_INLINE_RE.test(line)).length;
|
|
1208
|
-
if (markdownSignals >= Math.max(2, Math.ceil(sample.length * 0.4))) {
|
|
1209
|
-
return "markdown";
|
|
1210
|
-
}
|
|
1211
|
-
return "plain";
|
|
1212
|
-
}
|
|
1213
|
-
function parseSessionFile(filePath) {
|
|
1214
|
-
const resolved = path3.resolve(filePath);
|
|
1215
|
-
const raw = fs3.readFileSync(resolved, "utf-8");
|
|
1216
|
-
const format = detectSessionFormat(raw, resolved);
|
|
1217
|
-
if (format === "jsonl") {
|
|
1218
|
-
const parsed = parseJsonLines(raw);
|
|
1219
|
-
if (parsed.length > 0) {
|
|
1220
|
-
return parsed;
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
if (format === "markdown") {
|
|
1224
|
-
const parsed = parseMarkdown(raw);
|
|
1225
|
-
if (parsed.length > 0) {
|
|
1226
|
-
return parsed;
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
return parsePlainText(raw);
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
1067
|
export {
|
|
1233
1068
|
Compressor,
|
|
1234
1069
|
Reflector,
|
|
1235
|
-
Observer
|
|
1236
|
-
parseSessionFile
|
|
1070
|
+
Observer
|
|
1237
1071
|
};
|