@joshuaswarren/openclaw-engram 8.3.86 → 8.3.88
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/dist/index.js +1409 -522
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -456,9 +456,9 @@ function parseConfig(raw) {
|
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
// src/orchestrator.ts
|
|
459
|
-
import
|
|
459
|
+
import path28 from "path";
|
|
460
460
|
import { createHash as createHash6 } from "crypto";
|
|
461
|
-
import { mkdir as mkdir20, readdir as
|
|
461
|
+
import { mkdir as mkdir20, readdir as readdir12, readFile as readFile20, writeFile as writeFile19 } from "fs/promises";
|
|
462
462
|
|
|
463
463
|
// src/signal.ts
|
|
464
464
|
var BUILTIN_HIGH_PATTERNS = [
|
|
@@ -7505,8 +7505,410 @@ function extractTopics(memories, topN = 50) {
|
|
|
7505
7505
|
}
|
|
7506
7506
|
|
|
7507
7507
|
// src/transcript.ts
|
|
7508
|
-
import { appendFile as appendFile2, mkdir as mkdir4, readdir as
|
|
7508
|
+
import { appendFile as appendFile2, mkdir as mkdir4, readdir as readdir4, readFile as readFile5, stat as stat2, unlink as unlink3, writeFile as writeFile5 } from "fs/promises";
|
|
7509
|
+
import path7 from "path";
|
|
7510
|
+
|
|
7511
|
+
// src/session-integrity.ts
|
|
7509
7512
|
import path6 from "path";
|
|
7513
|
+
import { readFile as readFile4, readdir as readdir3, unlink as unlink2, writeFile as writeFile4 } from "fs/promises";
|
|
7514
|
+
function isObjectRecord(input) {
|
|
7515
|
+
return Boolean(input) && typeof input === "object";
|
|
7516
|
+
}
|
|
7517
|
+
function isTranscriptEntry(raw) {
|
|
7518
|
+
if (!isObjectRecord(raw)) return false;
|
|
7519
|
+
if (raw.role !== "user" && raw.role !== "assistant") return false;
|
|
7520
|
+
return typeof raw.timestamp === "string" && raw.timestamp.length > 0 && typeof raw.content === "string" && typeof raw.sessionKey === "string" && raw.sessionKey.length > 0 && typeof raw.turnId === "string" && raw.turnId.length > 0;
|
|
7521
|
+
}
|
|
7522
|
+
async function listTranscriptFiles(memoryDir) {
|
|
7523
|
+
const transcriptsDir = path6.join(memoryDir, "transcripts");
|
|
7524
|
+
const out = [];
|
|
7525
|
+
const stack = [transcriptsDir];
|
|
7526
|
+
while (stack.length > 0) {
|
|
7527
|
+
const dir = stack.pop();
|
|
7528
|
+
if (!dir) continue;
|
|
7529
|
+
let entries;
|
|
7530
|
+
try {
|
|
7531
|
+
entries = await readdir3(dir, { withFileTypes: true });
|
|
7532
|
+
} catch {
|
|
7533
|
+
continue;
|
|
7534
|
+
}
|
|
7535
|
+
for (const entry of entries) {
|
|
7536
|
+
const fullPath = path6.join(dir, entry.name);
|
|
7537
|
+
if (entry.isDirectory()) {
|
|
7538
|
+
stack.push(fullPath);
|
|
7539
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
7540
|
+
out.push(fullPath);
|
|
7541
|
+
}
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
return out.sort((a, b) => a.localeCompare(b));
|
|
7545
|
+
}
|
|
7546
|
+
async function parseTranscriptFile(filePath) {
|
|
7547
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
7548
|
+
const malformed = [];
|
|
7549
|
+
const invalid = [];
|
|
7550
|
+
const invalidBySession = /* @__PURE__ */ new Map();
|
|
7551
|
+
let raw = "";
|
|
7552
|
+
try {
|
|
7553
|
+
raw = await readFile4(filePath, "utf-8");
|
|
7554
|
+
} catch {
|
|
7555
|
+
return { bySession, malformed, invalid, invalidBySession };
|
|
7556
|
+
}
|
|
7557
|
+
const lines = raw.split("\n");
|
|
7558
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
7559
|
+
const line = lines[index]?.trim() ?? "";
|
|
7560
|
+
if (!line) continue;
|
|
7561
|
+
let parsed;
|
|
7562
|
+
try {
|
|
7563
|
+
parsed = JSON.parse(line);
|
|
7564
|
+
} catch {
|
|
7565
|
+
malformed.push({
|
|
7566
|
+
code: "transcript_malformed_line",
|
|
7567
|
+
severity: "warn",
|
|
7568
|
+
message: "Transcript line is not valid JSON.",
|
|
7569
|
+
filePath,
|
|
7570
|
+
line: index + 1
|
|
7571
|
+
});
|
|
7572
|
+
continue;
|
|
7573
|
+
}
|
|
7574
|
+
if (!isTranscriptEntry(parsed)) {
|
|
7575
|
+
const sessionKey = isObjectRecord(parsed) && typeof parsed.sessionKey === "string" && parsed.sessionKey.length > 0 ? parsed.sessionKey : void 0;
|
|
7576
|
+
invalid.push({
|
|
7577
|
+
code: "transcript_invalid_entry",
|
|
7578
|
+
severity: "warn",
|
|
7579
|
+
message: "Transcript entry is missing required fields.",
|
|
7580
|
+
filePath,
|
|
7581
|
+
line: index + 1,
|
|
7582
|
+
sessionKey
|
|
7583
|
+
});
|
|
7584
|
+
if (sessionKey) {
|
|
7585
|
+
invalidBySession.set(sessionKey, (invalidBySession.get(sessionKey) ?? 0) + 1);
|
|
7586
|
+
}
|
|
7587
|
+
continue;
|
|
7588
|
+
}
|
|
7589
|
+
const list = bySession.get(parsed.sessionKey) ?? [];
|
|
7590
|
+
list.push({ filePath, lineNumber: index + 1, entry: parsed });
|
|
7591
|
+
bySession.set(parsed.sessionKey, list);
|
|
7592
|
+
}
|
|
7593
|
+
return { bySession, malformed, invalid, invalidBySession };
|
|
7594
|
+
}
|
|
7595
|
+
function analyzeSessionEntries(sessionKey, refs) {
|
|
7596
|
+
function parseTimestampForSort(timestamp) {
|
|
7597
|
+
const parsed = Date.parse(timestamp);
|
|
7598
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
7599
|
+
return Number.MAX_SAFE_INTEGER;
|
|
7600
|
+
}
|
|
7601
|
+
const issues = [];
|
|
7602
|
+
const sorted = [...refs].sort((a, b) => {
|
|
7603
|
+
const tsA = parseTimestampForSort(a.entry.timestamp);
|
|
7604
|
+
const tsB = parseTimestampForSort(b.entry.timestamp);
|
|
7605
|
+
if (tsA !== tsB) return tsA - tsB;
|
|
7606
|
+
const rawTimestampCmp = a.entry.timestamp.localeCompare(b.entry.timestamp);
|
|
7607
|
+
if (rawTimestampCmp !== 0) return rawTimestampCmp;
|
|
7608
|
+
return a.entry.turnId.localeCompare(b.entry.turnId);
|
|
7609
|
+
});
|
|
7610
|
+
const turnIdSeen = /* @__PURE__ */ new Set();
|
|
7611
|
+
let duplicateTurnIds = 0;
|
|
7612
|
+
let brokenChains = 0;
|
|
7613
|
+
for (let i = 0; i < sorted.length; i += 1) {
|
|
7614
|
+
const current = sorted[i];
|
|
7615
|
+
if (turnIdSeen.has(current.entry.turnId)) {
|
|
7616
|
+
duplicateTurnIds += 1;
|
|
7617
|
+
issues.push({
|
|
7618
|
+
code: "transcript_duplicate_turn_id",
|
|
7619
|
+
severity: "warn",
|
|
7620
|
+
message: `Duplicate turnId detected: ${current.entry.turnId}`,
|
|
7621
|
+
sessionKey,
|
|
7622
|
+
filePath: current.filePath,
|
|
7623
|
+
line: current.lineNumber
|
|
7624
|
+
});
|
|
7625
|
+
} else {
|
|
7626
|
+
turnIdSeen.add(current.entry.turnId);
|
|
7627
|
+
}
|
|
7628
|
+
if (i > 0) {
|
|
7629
|
+
const previous = sorted[i - 1];
|
|
7630
|
+
if (previous && previous.entry.role === current.entry.role) {
|
|
7631
|
+
brokenChains += 1;
|
|
7632
|
+
issues.push({
|
|
7633
|
+
code: "transcript_broken_chain",
|
|
7634
|
+
severity: "warn",
|
|
7635
|
+
message: `Adjacent turns have the same role (${current.entry.role}).`,
|
|
7636
|
+
sessionKey,
|
|
7637
|
+
filePath: current.filePath,
|
|
7638
|
+
line: current.lineNumber
|
|
7639
|
+
});
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
let incompleteTurns = 0;
|
|
7644
|
+
if (sorted.length > 0 && sorted[sorted.length - 1]?.entry.role === "user") {
|
|
7645
|
+
incompleteTurns = 1;
|
|
7646
|
+
const last = sorted[sorted.length - 1];
|
|
7647
|
+
issues.push({
|
|
7648
|
+
code: "transcript_incomplete_turn",
|
|
7649
|
+
severity: "warn",
|
|
7650
|
+
message: "Session ends on a user turn without assistant response.",
|
|
7651
|
+
sessionKey,
|
|
7652
|
+
filePath: last?.filePath,
|
|
7653
|
+
line: last?.lineNumber
|
|
7654
|
+
});
|
|
7655
|
+
}
|
|
7656
|
+
return {
|
|
7657
|
+
stats: {
|
|
7658
|
+
sessionKey,
|
|
7659
|
+
entries: sorted.length,
|
|
7660
|
+
malformedLines: 0,
|
|
7661
|
+
invalidEntries: 0,
|
|
7662
|
+
duplicateTurnIds,
|
|
7663
|
+
brokenChains,
|
|
7664
|
+
incompleteTurns
|
|
7665
|
+
},
|
|
7666
|
+
issues
|
|
7667
|
+
};
|
|
7668
|
+
}
|
|
7669
|
+
function validateCheckpointRaw(checkpoint) {
|
|
7670
|
+
if (!isObjectRecord(checkpoint)) return false;
|
|
7671
|
+
return typeof checkpoint.sessionKey === "string" && checkpoint.sessionKey.length > 0 && typeof checkpoint.capturedAt === "string" && typeof checkpoint.ttl === "string" && Array.isArray(checkpoint.turns);
|
|
7672
|
+
}
|
|
7673
|
+
async function analyzeCheckpoint(memoryDir) {
|
|
7674
|
+
const checkpointPath = path6.join(memoryDir, "state", "checkpoint.json");
|
|
7675
|
+
const issues = [];
|
|
7676
|
+
const checkpoint = {
|
|
7677
|
+
present: false,
|
|
7678
|
+
healthy: true,
|
|
7679
|
+
path: checkpointPath
|
|
7680
|
+
};
|
|
7681
|
+
let raw = "";
|
|
7682
|
+
try {
|
|
7683
|
+
raw = await readFile4(checkpointPath, "utf-8");
|
|
7684
|
+
} catch {
|
|
7685
|
+
issues.push({
|
|
7686
|
+
code: "checkpoint_missing",
|
|
7687
|
+
severity: "info",
|
|
7688
|
+
message: "No checkpoint file present.",
|
|
7689
|
+
filePath: checkpointPath
|
|
7690
|
+
});
|
|
7691
|
+
return { checkpoint, issues };
|
|
7692
|
+
}
|
|
7693
|
+
checkpoint.present = true;
|
|
7694
|
+
let parsed;
|
|
7695
|
+
try {
|
|
7696
|
+
parsed = JSON.parse(raw);
|
|
7697
|
+
} catch {
|
|
7698
|
+
checkpoint.healthy = false;
|
|
7699
|
+
issues.push({
|
|
7700
|
+
code: "checkpoint_invalid_json",
|
|
7701
|
+
severity: "error",
|
|
7702
|
+
message: "Checkpoint file is invalid JSON.",
|
|
7703
|
+
filePath: checkpointPath
|
|
7704
|
+
});
|
|
7705
|
+
return { checkpoint, issues };
|
|
7706
|
+
}
|
|
7707
|
+
if (!validateCheckpointRaw(parsed)) {
|
|
7708
|
+
checkpoint.healthy = false;
|
|
7709
|
+
issues.push({
|
|
7710
|
+
code: "checkpoint_invalid_metadata",
|
|
7711
|
+
severity: "error",
|
|
7712
|
+
message: "Checkpoint file is missing required metadata fields.",
|
|
7713
|
+
filePath: checkpointPath
|
|
7714
|
+
});
|
|
7715
|
+
return { checkpoint, issues };
|
|
7716
|
+
}
|
|
7717
|
+
checkpoint.sessionKey = parsed.sessionKey;
|
|
7718
|
+
checkpoint.expiresAt = parsed.ttl;
|
|
7719
|
+
const ttlMs = Date.parse(parsed.ttl);
|
|
7720
|
+
const capturedAtMs = Date.parse(parsed.capturedAt);
|
|
7721
|
+
if (!Number.isFinite(ttlMs) || !Number.isFinite(capturedAtMs) || ttlMs <= capturedAtMs) {
|
|
7722
|
+
checkpoint.healthy = false;
|
|
7723
|
+
issues.push({
|
|
7724
|
+
code: "checkpoint_invalid_metadata",
|
|
7725
|
+
severity: "error",
|
|
7726
|
+
message: "Checkpoint timestamps are invalid or inconsistent.",
|
|
7727
|
+
filePath: checkpointPath,
|
|
7728
|
+
sessionKey: parsed.sessionKey
|
|
7729
|
+
});
|
|
7730
|
+
return { checkpoint, issues };
|
|
7731
|
+
}
|
|
7732
|
+
if (ttlMs < Date.now()) {
|
|
7733
|
+
checkpoint.healthy = false;
|
|
7734
|
+
issues.push({
|
|
7735
|
+
code: "checkpoint_expired",
|
|
7736
|
+
severity: "warn",
|
|
7737
|
+
message: "Checkpoint TTL has expired.",
|
|
7738
|
+
filePath: checkpointPath,
|
|
7739
|
+
sessionKey: parsed.sessionKey
|
|
7740
|
+
});
|
|
7741
|
+
}
|
|
7742
|
+
for (const turn of parsed.turns) {
|
|
7743
|
+
if (!isTranscriptEntry(turn)) {
|
|
7744
|
+
checkpoint.healthy = false;
|
|
7745
|
+
issues.push({
|
|
7746
|
+
code: "checkpoint_invalid_metadata",
|
|
7747
|
+
severity: "error",
|
|
7748
|
+
message: "Checkpoint contains invalid turn entries.",
|
|
7749
|
+
filePath: checkpointPath,
|
|
7750
|
+
sessionKey: parsed.sessionKey
|
|
7751
|
+
});
|
|
7752
|
+
break;
|
|
7753
|
+
}
|
|
7754
|
+
}
|
|
7755
|
+
return { checkpoint, issues };
|
|
7756
|
+
}
|
|
7757
|
+
async function analyzeSessionIntegrity(options) {
|
|
7758
|
+
const memoryDir = options.memoryDir;
|
|
7759
|
+
const reportIssues = [];
|
|
7760
|
+
const allSessionRefs = /* @__PURE__ */ new Map();
|
|
7761
|
+
const invalidBySession = /* @__PURE__ */ new Map();
|
|
7762
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
7763
|
+
const files = await listTranscriptFiles(memoryDir);
|
|
7764
|
+
for (const filePath of files) {
|
|
7765
|
+
const parsed = await parseTranscriptFile(filePath);
|
|
7766
|
+
reportIssues.push(...parsed.malformed, ...parsed.invalid);
|
|
7767
|
+
for (const [sessionKey, count] of parsed.invalidBySession.entries()) {
|
|
7768
|
+
invalidBySession.set(sessionKey, (invalidBySession.get(sessionKey) ?? 0) + count);
|
|
7769
|
+
}
|
|
7770
|
+
for (const [sessionKey, refs] of parsed.bySession.entries()) {
|
|
7771
|
+
const existing = allSessionRefs.get(sessionKey) ?? [];
|
|
7772
|
+
existing.push(...refs);
|
|
7773
|
+
allSessionRefs.set(sessionKey, existing);
|
|
7774
|
+
}
|
|
7775
|
+
}
|
|
7776
|
+
for (const [sessionKey, refs] of allSessionRefs.entries()) {
|
|
7777
|
+
const analyzed = analyzeSessionEntries(sessionKey, refs);
|
|
7778
|
+
reportIssues.push(...analyzed.issues);
|
|
7779
|
+
sessions.set(sessionKey, {
|
|
7780
|
+
...analyzed.stats,
|
|
7781
|
+
malformedLines: 0,
|
|
7782
|
+
invalidEntries: invalidBySession.get(sessionKey) ?? 0
|
|
7783
|
+
});
|
|
7784
|
+
}
|
|
7785
|
+
const checkpoint = await analyzeCheckpoint(memoryDir);
|
|
7786
|
+
reportIssues.push(...checkpoint.issues);
|
|
7787
|
+
const severeIssueCount = reportIssues.filter((issue) => issue.severity !== "info").length;
|
|
7788
|
+
return {
|
|
7789
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7790
|
+
memoryDir,
|
|
7791
|
+
healthy: severeIssueCount === 0,
|
|
7792
|
+
sessions: [...sessions.values()].sort((a, b) => a.sessionKey.localeCompare(b.sessionKey)),
|
|
7793
|
+
checkpoint: checkpoint.checkpoint,
|
|
7794
|
+
issues: reportIssues
|
|
7795
|
+
};
|
|
7796
|
+
}
|
|
7797
|
+
function collectTranscriptRewriteTargets(report) {
|
|
7798
|
+
const set = /* @__PURE__ */ new Set();
|
|
7799
|
+
for (const issue of report.issues) {
|
|
7800
|
+
if (!issue.filePath) continue;
|
|
7801
|
+
if (issue.code === "transcript_malformed_line" || issue.code === "transcript_invalid_entry") {
|
|
7802
|
+
set.add(issue.filePath);
|
|
7803
|
+
}
|
|
7804
|
+
}
|
|
7805
|
+
return [...set].sort((a, b) => a.localeCompare(b));
|
|
7806
|
+
}
|
|
7807
|
+
function planSessionRepair(options) {
|
|
7808
|
+
const actions = [];
|
|
7809
|
+
const transcriptTargets = collectTranscriptRewriteTargets(options.report);
|
|
7810
|
+
for (const targetPath of transcriptTargets) {
|
|
7811
|
+
actions.push({
|
|
7812
|
+
kind: "rewrite_transcript",
|
|
7813
|
+
targetPath,
|
|
7814
|
+
description: "Rewrite transcript file with only valid JSON transcript entries."
|
|
7815
|
+
});
|
|
7816
|
+
}
|
|
7817
|
+
const checkpointNeedsRepair = options.report.issues.some(
|
|
7818
|
+
(issue) => issue.code === "checkpoint_invalid_json" || issue.code === "checkpoint_invalid_metadata" || issue.code === "checkpoint_expired"
|
|
7819
|
+
);
|
|
7820
|
+
if (checkpointNeedsRepair && options.report.checkpoint.present) {
|
|
7821
|
+
actions.push({
|
|
7822
|
+
kind: "remove_checkpoint",
|
|
7823
|
+
targetPath: options.report.checkpoint.path,
|
|
7824
|
+
description: "Remove invalid or expired checkpoint file."
|
|
7825
|
+
});
|
|
7826
|
+
}
|
|
7827
|
+
if (options.sessionFilesDir && options.allowSessionFileRepair === true) {
|
|
7828
|
+
actions.push({
|
|
7829
|
+
kind: "repair_session_files",
|
|
7830
|
+
targetPath: options.sessionFilesDir,
|
|
7831
|
+
description: "Session file repair was requested; no automatic rewiring is performed.",
|
|
7832
|
+
details: "No-op by design. OpenClaw session files require explicit manual review."
|
|
7833
|
+
});
|
|
7834
|
+
}
|
|
7835
|
+
return {
|
|
7836
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7837
|
+
dryRun: options.dryRun,
|
|
7838
|
+
allowSessionFileRepair: options.allowSessionFileRepair === true,
|
|
7839
|
+
actions
|
|
7840
|
+
};
|
|
7841
|
+
}
|
|
7842
|
+
async function rewriteTranscriptFile(targetPath) {
|
|
7843
|
+
let raw = "";
|
|
7844
|
+
try {
|
|
7845
|
+
raw = await readFile4(targetPath, "utf-8");
|
|
7846
|
+
} catch {
|
|
7847
|
+
return;
|
|
7848
|
+
}
|
|
7849
|
+
const lines = raw.split("\n");
|
|
7850
|
+
const validLines = [];
|
|
7851
|
+
for (const line of lines) {
|
|
7852
|
+
const trimmed = line.trim();
|
|
7853
|
+
if (!trimmed) continue;
|
|
7854
|
+
try {
|
|
7855
|
+
const parsed = JSON.parse(trimmed);
|
|
7856
|
+
if (!isTranscriptEntry(parsed)) continue;
|
|
7857
|
+
validLines.push(JSON.stringify(parsed));
|
|
7858
|
+
} catch {
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7861
|
+
const body = validLines.length > 0 ? `${validLines.join("\n")}
|
|
7862
|
+
` : "";
|
|
7863
|
+
await writeFile4(targetPath, body, "utf-8");
|
|
7864
|
+
}
|
|
7865
|
+
async function applySessionRepair(options) {
|
|
7866
|
+
const { plan } = options;
|
|
7867
|
+
if (plan.dryRun) {
|
|
7868
|
+
return {
|
|
7869
|
+
applied: false,
|
|
7870
|
+
actionsAttempted: plan.actions.length,
|
|
7871
|
+
actionsApplied: 0,
|
|
7872
|
+
errors: []
|
|
7873
|
+
};
|
|
7874
|
+
}
|
|
7875
|
+
let actionsApplied = 0;
|
|
7876
|
+
const errors = [];
|
|
7877
|
+
for (const action of plan.actions) {
|
|
7878
|
+
try {
|
|
7879
|
+
if (action.kind === "rewrite_transcript") {
|
|
7880
|
+
await rewriteTranscriptFile(action.targetPath);
|
|
7881
|
+
actionsApplied += 1;
|
|
7882
|
+
continue;
|
|
7883
|
+
}
|
|
7884
|
+
if (action.kind === "remove_checkpoint") {
|
|
7885
|
+
try {
|
|
7886
|
+
await unlink2(action.targetPath);
|
|
7887
|
+
} catch (err) {
|
|
7888
|
+
const code = typeof err === "object" && err && "code" in err ? String(err.code ?? "") : "";
|
|
7889
|
+
if (code !== "ENOENT") {
|
|
7890
|
+
throw err;
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7893
|
+
actionsApplied += 1;
|
|
7894
|
+
continue;
|
|
7895
|
+
}
|
|
7896
|
+
if (action.kind === "repair_session_files") {
|
|
7897
|
+
actionsApplied += 1;
|
|
7898
|
+
}
|
|
7899
|
+
} catch (err) {
|
|
7900
|
+
errors.push(`Failed ${action.kind} ${action.targetPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
return {
|
|
7904
|
+
applied: true,
|
|
7905
|
+
actionsAttempted: plan.actions.length,
|
|
7906
|
+
actionsApplied,
|
|
7907
|
+
errors
|
|
7908
|
+
};
|
|
7909
|
+
}
|
|
7910
|
+
|
|
7911
|
+
// src/transcript.ts
|
|
7510
7912
|
var TranscriptManager = class _TranscriptManager {
|
|
7511
7913
|
transcriptsDir;
|
|
7512
7914
|
checkpointPath;
|
|
@@ -7520,10 +7922,10 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7520
7922
|
static CHARS_PER_TOKEN = 4;
|
|
7521
7923
|
constructor(config) {
|
|
7522
7924
|
this.config = config;
|
|
7523
|
-
this.transcriptsDir =
|
|
7524
|
-
this.stateDir =
|
|
7525
|
-
this.checkpointPath =
|
|
7526
|
-
this.toolUsageDir =
|
|
7925
|
+
this.transcriptsDir = path7.join(config.memoryDir, "transcripts");
|
|
7926
|
+
this.stateDir = path7.join(config.memoryDir, "state");
|
|
7927
|
+
this.checkpointPath = path7.join(this.stateDir, "checkpoint.json");
|
|
7928
|
+
this.toolUsageDir = path7.join(this.stateDir, "tool-usage");
|
|
7527
7929
|
}
|
|
7528
7930
|
/**
|
|
7529
7931
|
* Parse a sessionKey to extract channel type and ID.
|
|
@@ -7556,7 +7958,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7556
7958
|
}
|
|
7557
7959
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7558
7960
|
return {
|
|
7559
|
-
dir:
|
|
7961
|
+
dir: path7.join(channelType, channelId),
|
|
7560
7962
|
file: `${today}.jsonl`
|
|
7561
7963
|
};
|
|
7562
7964
|
}
|
|
@@ -7578,19 +7980,19 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7578
7980
|
const transcriptDir = this.transcriptsDir;
|
|
7579
7981
|
const sessionKeys = /* @__PURE__ */ new Set();
|
|
7580
7982
|
try {
|
|
7581
|
-
const typeEntries = await
|
|
7983
|
+
const typeEntries = await readdir4(transcriptDir, { withFileTypes: true });
|
|
7582
7984
|
for (const typeEnt of typeEntries) {
|
|
7583
7985
|
if (!typeEnt.isDirectory()) continue;
|
|
7584
|
-
const typeDir =
|
|
7585
|
-
const idEntries = await
|
|
7986
|
+
const typeDir = path7.join(transcriptDir, typeEnt.name);
|
|
7987
|
+
const idEntries = await readdir4(typeDir, { withFileTypes: true });
|
|
7586
7988
|
for (const idEnt of idEntries) {
|
|
7587
7989
|
if (!idEnt.isDirectory()) continue;
|
|
7588
|
-
const chanDir =
|
|
7589
|
-
const files = (await
|
|
7990
|
+
const chanDir = path7.join(typeDir, idEnt.name);
|
|
7991
|
+
const files = (await readdir4(chanDir)).filter((f) => f.endsWith(".jsonl")).sort();
|
|
7590
7992
|
const last = files[files.length - 1];
|
|
7591
7993
|
if (!last) continue;
|
|
7592
7994
|
try {
|
|
7593
|
-
const raw = await
|
|
7995
|
+
const raw = await readFile5(path7.join(chanDir, last), "utf-8");
|
|
7594
7996
|
const firstLine = raw.split("\n").find((l) => l.trim().length > 0);
|
|
7595
7997
|
if (!firstLine) continue;
|
|
7596
7998
|
const entry = JSON.parse(firstLine);
|
|
@@ -7612,21 +8014,21 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7612
8014
|
}
|
|
7613
8015
|
async appendToolUse(entry) {
|
|
7614
8016
|
const { dir, file } = this.getToolUsagePath(entry.sessionKey);
|
|
7615
|
-
const channelDir =
|
|
8017
|
+
const channelDir = path7.join(this.toolUsageDir, dir);
|
|
7616
8018
|
await mkdir4(channelDir, { recursive: true });
|
|
7617
|
-
const filePath =
|
|
8019
|
+
const filePath = path7.join(channelDir, file);
|
|
7618
8020
|
await appendFile2(filePath, JSON.stringify(entry) + "\n", "utf-8");
|
|
7619
8021
|
}
|
|
7620
8022
|
async readToolUse(sessionKey, startTime, endTime) {
|
|
7621
8023
|
const { dir } = this.getToolUsagePath(sessionKey);
|
|
7622
|
-
const channelDir =
|
|
8024
|
+
const channelDir = path7.join(this.toolUsageDir, dir);
|
|
7623
8025
|
try {
|
|
7624
|
-
const files = await
|
|
8026
|
+
const files = await readdir4(channelDir);
|
|
7625
8027
|
const out = [];
|
|
7626
8028
|
for (const file of files) {
|
|
7627
8029
|
if (!file.endsWith(".jsonl")) continue;
|
|
7628
|
-
const fp =
|
|
7629
|
-
const raw = await
|
|
8030
|
+
const fp = path7.join(channelDir, file);
|
|
8031
|
+
const raw = await readFile5(fp, "utf-8");
|
|
7630
8032
|
for (const line of raw.split("\n")) {
|
|
7631
8033
|
if (!line.trim()) continue;
|
|
7632
8034
|
try {
|
|
@@ -7649,17 +8051,17 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7649
8051
|
}
|
|
7650
8052
|
async estimateSessionFootprint(sessionKey) {
|
|
7651
8053
|
const { dir } = this.getTranscriptPath(sessionKey);
|
|
7652
|
-
const channelDir =
|
|
8054
|
+
const channelDir = path7.join(this.transcriptsDir, dir);
|
|
7653
8055
|
let bytes = 0;
|
|
7654
8056
|
try {
|
|
7655
|
-
const files = (await
|
|
8057
|
+
const files = (await readdir4(channelDir)).filter((file) => file.endsWith(".jsonl")).sort();
|
|
7656
8058
|
const cached = this.sessionFootprintCache.get(sessionKey);
|
|
7657
8059
|
if (!cached) {
|
|
7658
8060
|
const fileBytes = /* @__PURE__ */ new Map();
|
|
7659
8061
|
const fileSizes = /* @__PURE__ */ new Map();
|
|
7660
8062
|
for (const file of files) {
|
|
7661
8063
|
try {
|
|
7662
|
-
const fullPath =
|
|
8064
|
+
const fullPath = path7.join(channelDir, file);
|
|
7663
8065
|
const fileInfo = await stat2(fullPath);
|
|
7664
8066
|
const sessionBytes = await this.estimateSessionBytesInFile(
|
|
7665
8067
|
fullPath,
|
|
@@ -7685,7 +8087,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7685
8087
|
for (const file of files) {
|
|
7686
8088
|
if (cached.fileBytes.has(file)) continue;
|
|
7687
8089
|
try {
|
|
7688
|
-
const fullPath =
|
|
8090
|
+
const fullPath = path7.join(channelDir, file);
|
|
7689
8091
|
const fileInfo = await stat2(fullPath);
|
|
7690
8092
|
const sessionBytes = await this.estimateSessionBytesInFile(fullPath, sessionKey);
|
|
7691
8093
|
cached.fileBytes.set(file, sessionBytes);
|
|
@@ -7697,7 +8099,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7697
8099
|
const newestFile = files[files.length - 1];
|
|
7698
8100
|
if (newestFile) {
|
|
7699
8101
|
try {
|
|
7700
|
-
const newestPath =
|
|
8102
|
+
const newestPath = path7.join(channelDir, newestFile);
|
|
7701
8103
|
const fileInfo = await stat2(newestPath);
|
|
7702
8104
|
const size = Math.max(0, fileInfo.size);
|
|
7703
8105
|
const previousSessionBytes = cached.fileBytes.get(newestFile) ?? 0;
|
|
@@ -7724,7 +8126,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7724
8126
|
}
|
|
7725
8127
|
async estimateSessionBytesInFile(filePath, sessionKey) {
|
|
7726
8128
|
try {
|
|
7727
|
-
const raw = await
|
|
8129
|
+
const raw = await readFile5(filePath, "utf-8");
|
|
7728
8130
|
let total = 0;
|
|
7729
8131
|
for (const line of raw.split("\n")) {
|
|
7730
8132
|
if (!line.trim()) continue;
|
|
@@ -7757,12 +8159,12 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7757
8159
|
async append(entry) {
|
|
7758
8160
|
try {
|
|
7759
8161
|
const { dir, file } = this.getTranscriptPath(entry.sessionKey);
|
|
7760
|
-
const channelType = dir.split(
|
|
8162
|
+
const channelType = dir.split(path7.sep)[0] ?? dir;
|
|
7761
8163
|
if (this.config.transcriptSkipChannelTypes.includes(channelType)) {
|
|
7762
8164
|
return;
|
|
7763
8165
|
}
|
|
7764
|
-
const channelDir =
|
|
7765
|
-
const filePath =
|
|
8166
|
+
const channelDir = path7.join(this.transcriptsDir, dir);
|
|
8167
|
+
const filePath = path7.join(channelDir, file);
|
|
7766
8168
|
await mkdir4(channelDir, { recursive: true });
|
|
7767
8169
|
const line = JSON.stringify(entry) + "\n";
|
|
7768
8170
|
await appendFile2(filePath, line, "utf-8");
|
|
@@ -7779,26 +8181,26 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7779
8181
|
async getAllTranscriptFiles() {
|
|
7780
8182
|
const files = [];
|
|
7781
8183
|
try {
|
|
7782
|
-
const entries = await
|
|
8184
|
+
const entries = await readdir4(this.transcriptsDir, { withFileTypes: true });
|
|
7783
8185
|
for (const entry of entries) {
|
|
7784
8186
|
if (entry.isDirectory()) {
|
|
7785
|
-
const channelTypeDir =
|
|
8187
|
+
const channelTypeDir = path7.join(this.transcriptsDir, entry.name);
|
|
7786
8188
|
try {
|
|
7787
|
-
const channelTypeEntries = await
|
|
8189
|
+
const channelTypeEntries = await readdir4(channelTypeDir, { withFileTypes: true });
|
|
7788
8190
|
for (const channelTypeEntry of channelTypeEntries) {
|
|
7789
8191
|
if (channelTypeEntry.isDirectory()) {
|
|
7790
|
-
const channelDir =
|
|
8192
|
+
const channelDir = path7.join(channelTypeDir, channelTypeEntry.name);
|
|
7791
8193
|
try {
|
|
7792
|
-
const channelFiles = await
|
|
8194
|
+
const channelFiles = await readdir4(channelDir);
|
|
7793
8195
|
for (const file of channelFiles) {
|
|
7794
8196
|
if (file.endsWith(".jsonl")) {
|
|
7795
|
-
files.push(
|
|
8197
|
+
files.push(path7.join(entry.name, channelTypeEntry.name, file));
|
|
7796
8198
|
}
|
|
7797
8199
|
}
|
|
7798
8200
|
} catch {
|
|
7799
8201
|
}
|
|
7800
8202
|
} else if (channelTypeEntry.isFile() && channelTypeEntry.name.endsWith(".jsonl")) {
|
|
7801
|
-
files.push(
|
|
8203
|
+
files.push(path7.join(entry.name, channelTypeEntry.name));
|
|
7802
8204
|
}
|
|
7803
8205
|
}
|
|
7804
8206
|
} catch {
|
|
@@ -7823,9 +8225,9 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7823
8225
|
try {
|
|
7824
8226
|
const transcriptFiles = await this.getAllTranscriptFiles();
|
|
7825
8227
|
for (const relativePath of transcriptFiles) {
|
|
7826
|
-
const filePath =
|
|
8228
|
+
const filePath = path7.join(this.transcriptsDir, relativePath);
|
|
7827
8229
|
try {
|
|
7828
|
-
const content = await
|
|
8230
|
+
const content = await readFile5(filePath, "utf-8");
|
|
7829
8231
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
7830
8232
|
for (const line of lines) {
|
|
7831
8233
|
try {
|
|
@@ -7875,26 +8277,26 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7875
8277
|
cutoff.setHours(0, 0, 0, 0);
|
|
7876
8278
|
let processed = 0;
|
|
7877
8279
|
try {
|
|
7878
|
-
const entries = await
|
|
8280
|
+
const entries = await readdir4(this.transcriptsDir, { withFileTypes: true });
|
|
7879
8281
|
for (const entry of entries) {
|
|
7880
8282
|
if (entry.isDirectory()) {
|
|
7881
|
-
const channelTypeDir =
|
|
8283
|
+
const channelTypeDir = path7.join(this.transcriptsDir, entry.name);
|
|
7882
8284
|
try {
|
|
7883
|
-
const channelTypeEntries = await
|
|
8285
|
+
const channelTypeEntries = await readdir4(channelTypeDir, { withFileTypes: true });
|
|
7884
8286
|
for (const channelTypeEntry of channelTypeEntries) {
|
|
7885
8287
|
if (channelTypeEntry.isDirectory()) {
|
|
7886
|
-
const channelDir =
|
|
8288
|
+
const channelDir = path7.join(channelTypeDir, channelTypeEntry.name);
|
|
7887
8289
|
try {
|
|
7888
|
-
const channelFiles = await
|
|
8290
|
+
const channelFiles = await readdir4(channelDir);
|
|
7889
8291
|
for (const file of channelFiles) {
|
|
7890
8292
|
if (!file.endsWith(".jsonl")) continue;
|
|
7891
|
-
const filePath =
|
|
8293
|
+
const filePath = path7.join(channelDir, file);
|
|
7892
8294
|
if (this.isLegacyTranscriptFile(file)) {
|
|
7893
8295
|
const dateStr = file.slice(0, 10);
|
|
7894
8296
|
const fileDate = new Date(dateStr);
|
|
7895
8297
|
if (!isNaN(fileDate.getTime()) && fileDate < cutoff) {
|
|
7896
8298
|
try {
|
|
7897
|
-
await
|
|
8299
|
+
await unlink3(filePath);
|
|
7898
8300
|
processed++;
|
|
7899
8301
|
log.debug(`deleted old daily transcript file: ${entry.name}/${channelTypeEntry.name}/${file}`);
|
|
7900
8302
|
} catch (err) {
|
|
@@ -7912,7 +8314,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7912
8314
|
log.debug(`failed to process channel directory ${entry.name}/${channelTypeEntry.name}:`, err);
|
|
7913
8315
|
}
|
|
7914
8316
|
} else if (channelTypeEntry.isFile() && channelTypeEntry.name.endsWith(".jsonl")) {
|
|
7915
|
-
const filePath =
|
|
8317
|
+
const filePath = path7.join(channelTypeDir, channelTypeEntry.name);
|
|
7916
8318
|
const cleaned = await this.cleanupTranscriptFile(filePath, cutoff);
|
|
7917
8319
|
if (cleaned) {
|
|
7918
8320
|
processed++;
|
|
@@ -7927,9 +8329,9 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7927
8329
|
const dateStr = entry.name.slice(0, 10);
|
|
7928
8330
|
const fileDate = new Date(dateStr);
|
|
7929
8331
|
if (!isNaN(fileDate.getTime()) && fileDate < cutoff) {
|
|
7930
|
-
const filePath =
|
|
8332
|
+
const filePath = path7.join(this.transcriptsDir, entry.name);
|
|
7931
8333
|
try {
|
|
7932
|
-
await
|
|
8334
|
+
await unlink3(filePath);
|
|
7933
8335
|
processed++;
|
|
7934
8336
|
log.debug(`deleted old legacy transcript file: ${entry.name}`);
|
|
7935
8337
|
} catch (err) {
|
|
@@ -7955,7 +8357,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7955
8357
|
*/
|
|
7956
8358
|
async cleanupTranscriptFile(filePath, cutoff) {
|
|
7957
8359
|
try {
|
|
7958
|
-
const content = await
|
|
8360
|
+
const content = await readFile5(filePath, "utf-8");
|
|
7959
8361
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
7960
8362
|
const validLines = [];
|
|
7961
8363
|
let hasOldEntries = false;
|
|
@@ -7974,7 +8376,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7974
8376
|
}
|
|
7975
8377
|
if (validLines.length === 0) {
|
|
7976
8378
|
try {
|
|
7977
|
-
await
|
|
8379
|
+
await unlink3(filePath);
|
|
7978
8380
|
log.debug(`deleted empty transcript file: ${filePath}`);
|
|
7979
8381
|
return true;
|
|
7980
8382
|
} catch (err) {
|
|
@@ -7983,7 +8385,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7983
8385
|
}
|
|
7984
8386
|
}
|
|
7985
8387
|
if (hasOldEntries) {
|
|
7986
|
-
await
|
|
8388
|
+
await writeFile5(filePath, validLines.join("\n") + "\n", "utf-8");
|
|
7987
8389
|
log.debug(`cleaned old entries from transcript file: ${filePath}`);
|
|
7988
8390
|
return true;
|
|
7989
8391
|
}
|
|
@@ -7998,7 +8400,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7998
8400
|
*/
|
|
7999
8401
|
async saveCheckpoint(checkpoint) {
|
|
8000
8402
|
try {
|
|
8001
|
-
await
|
|
8403
|
+
await writeFile5(this.checkpointPath, JSON.stringify(checkpoint, null, 2), "utf-8");
|
|
8002
8404
|
log.info(`saved checkpoint for session ${checkpoint.sessionKey} with ${checkpoint.turns.length} turn(s)`);
|
|
8003
8405
|
} catch (err) {
|
|
8004
8406
|
log.error("failed to save checkpoint:", err);
|
|
@@ -8011,7 +8413,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
8011
8413
|
*/
|
|
8012
8414
|
async loadCheckpoint(sessionKey) {
|
|
8013
8415
|
try {
|
|
8014
|
-
const raw = await
|
|
8416
|
+
const raw = await readFile5(this.checkpointPath, "utf-8");
|
|
8015
8417
|
const checkpoint = JSON.parse(raw);
|
|
8016
8418
|
if (!checkpoint.sessionKey || !checkpoint.capturedAt || !checkpoint.ttl || !Array.isArray(checkpoint.turns)) {
|
|
8017
8419
|
log.warn("checkpoint file has invalid structure");
|
|
@@ -8043,7 +8445,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
8043
8445
|
*/
|
|
8044
8446
|
async clearCheckpoint() {
|
|
8045
8447
|
try {
|
|
8046
|
-
await
|
|
8448
|
+
await unlink3(this.checkpointPath);
|
|
8047
8449
|
log.info("cleared checkpoint");
|
|
8048
8450
|
} catch (err) {
|
|
8049
8451
|
log.debug("no checkpoint to clear");
|
|
@@ -8139,12 +8541,12 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
8139
8541
|
let totalEntries = 0;
|
|
8140
8542
|
const channelTypes = {};
|
|
8141
8543
|
for (const relativePath of allFiles) {
|
|
8142
|
-
const filePath =
|
|
8544
|
+
const filePath = path7.join(this.transcriptsDir, relativePath);
|
|
8143
8545
|
try {
|
|
8144
|
-
const content = await
|
|
8546
|
+
const content = await readFile5(filePath, "utf-8");
|
|
8145
8547
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
8146
8548
|
totalEntries += lines.length;
|
|
8147
|
-
const channelType = relativePath.includes(
|
|
8549
|
+
const channelType = relativePath.includes(path7.sep) ? relativePath.split(path7.sep)[0] : "legacy";
|
|
8148
8550
|
channelTypes[channelType] = (channelTypes[channelType] || 0) + 1;
|
|
8149
8551
|
} catch {
|
|
8150
8552
|
}
|
|
@@ -8167,11 +8569,32 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
8167
8569
|
};
|
|
8168
8570
|
}
|
|
8169
8571
|
}
|
|
8572
|
+
async analyzeIntegrity() {
|
|
8573
|
+
return analyzeSessionIntegrity({ memoryDir: this.config.memoryDir });
|
|
8574
|
+
}
|
|
8575
|
+
async getRecoverySummary(sessionKey) {
|
|
8576
|
+
const report = await this.analyzeIntegrity();
|
|
8577
|
+
const selectedSessions = sessionKey ? report.sessions.filter((session) => session.sessionKey === sessionKey) : report.sessions;
|
|
8578
|
+
const incompleteTurns = selectedSessions.reduce((sum, session) => sum + session.incompleteTurns, 0);
|
|
8579
|
+
const brokenChains = selectedSessions.reduce((sum, session) => sum + session.brokenChains, 0);
|
|
8580
|
+
const filteredIssues = report.issues.filter((issue) => !sessionKey || issue.sessionKey === sessionKey);
|
|
8581
|
+
const issueCount = filteredIssues.length;
|
|
8582
|
+
const severeIssueCount = filteredIssues.filter((issue) => issue.severity !== "info").length;
|
|
8583
|
+
return {
|
|
8584
|
+
generatedAt: report.generatedAt,
|
|
8585
|
+
sessionKey,
|
|
8586
|
+
healthy: sessionKey ? severeIssueCount === 0 && report.checkpoint.healthy : report.healthy,
|
|
8587
|
+
issueCount,
|
|
8588
|
+
incompleteTurns,
|
|
8589
|
+
brokenChains,
|
|
8590
|
+
checkpointHealthy: report.checkpoint.healthy
|
|
8591
|
+
};
|
|
8592
|
+
}
|
|
8170
8593
|
};
|
|
8171
8594
|
|
|
8172
8595
|
// src/summarizer.ts
|
|
8173
|
-
import { mkdir as mkdir5, readFile as
|
|
8174
|
-
import
|
|
8596
|
+
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile6, readdir as readdir5 } from "fs/promises";
|
|
8597
|
+
import path8 from "path";
|
|
8175
8598
|
import { z as z2 } from "zod";
|
|
8176
8599
|
var HourlySummarySchema = z2.object({
|
|
8177
8600
|
bullets: z2.array(z2.string()).describe("3-5 bullet points summarizing the hour's activity")
|
|
@@ -8191,7 +8614,7 @@ var HourlySummarizer = class {
|
|
|
8191
8614
|
transcript;
|
|
8192
8615
|
constructor(config, gatewayConfig, modelRegistry, transcript) {
|
|
8193
8616
|
this.config = config;
|
|
8194
|
-
this.summariesDir =
|
|
8617
|
+
this.summariesDir = path8.join(config.memoryDir, "summaries", "hourly");
|
|
8195
8618
|
this.modelRegistry = modelRegistry ?? new ModelRegistry(config.memoryDir);
|
|
8196
8619
|
this.transcript = transcript;
|
|
8197
8620
|
this.localLlm = new LocalLlmClient(config, this.modelRegistry);
|
|
@@ -8459,15 +8882,15 @@ ${truncatedConversation}`;
|
|
|
8459
8882
|
}
|
|
8460
8883
|
// Save summary to file
|
|
8461
8884
|
async saveSummary(summary) {
|
|
8462
|
-
const sessionDir =
|
|
8885
|
+
const sessionDir = path8.join(this.summariesDir, summary.sessionKey);
|
|
8463
8886
|
await mkdir5(sessionDir, { recursive: true });
|
|
8464
8887
|
const dateStr = summary.hour.slice(0, 10);
|
|
8465
|
-
const filePath =
|
|
8888
|
+
const filePath = path8.join(sessionDir, `${dateStr}.md`);
|
|
8466
8889
|
const hourStr = summary.hour.slice(11, 13);
|
|
8467
8890
|
const lines = [];
|
|
8468
8891
|
let existingContent = "";
|
|
8469
8892
|
try {
|
|
8470
|
-
existingContent = await
|
|
8893
|
+
existingContent = await readFile6(filePath, "utf-8");
|
|
8471
8894
|
} catch {
|
|
8472
8895
|
}
|
|
8473
8896
|
const hourHeader = `## ${hourStr}:00`;
|
|
@@ -8481,18 +8904,18 @@ ${truncatedConversation}`;
|
|
|
8481
8904
|
} else {
|
|
8482
8905
|
existingContent = beforeHour + newSection;
|
|
8483
8906
|
}
|
|
8484
|
-
await
|
|
8907
|
+
await writeFile6(filePath, existingContent, "utf-8");
|
|
8485
8908
|
log.debug(`updated hourly summary for ${summary.sessionKey} at ${hourStr}:00`);
|
|
8486
8909
|
} else {
|
|
8487
8910
|
const newSection = this.formatHourSection(summary, hourHeader);
|
|
8488
8911
|
if (existingContent) {
|
|
8489
|
-
await
|
|
8912
|
+
await writeFile6(filePath, existingContent.trimEnd() + "\n\n" + newSection, "utf-8");
|
|
8490
8913
|
} else {
|
|
8491
8914
|
const header = `# Hourly Summaries \u2014 ${dateStr}
|
|
8492
8915
|
|
|
8493
8916
|
*Session: ${summary.sessionKey}*
|
|
8494
8917
|
`;
|
|
8495
|
-
await
|
|
8918
|
+
await writeFile6(filePath, header + "\n" + newSection, "utf-8");
|
|
8496
8919
|
}
|
|
8497
8920
|
log.debug(`saved hourly summary for ${summary.sessionKey} at ${hourStr}:00`);
|
|
8498
8921
|
}
|
|
@@ -8537,15 +8960,15 @@ ${truncatedConversation}`;
|
|
|
8537
8960
|
}
|
|
8538
8961
|
// Read recent summaries for recall injection
|
|
8539
8962
|
async readRecent(sessionKey, hours) {
|
|
8540
|
-
const sessionDir =
|
|
8963
|
+
const sessionDir = path8.join(this.summariesDir, sessionKey);
|
|
8541
8964
|
try {
|
|
8542
|
-
const files = await
|
|
8965
|
+
const files = await readdir5(sessionDir);
|
|
8543
8966
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
8544
8967
|
const summaries = [];
|
|
8545
8968
|
const cutoffTime = Date.now() - hours * 60 * 60 * 1e3;
|
|
8546
8969
|
for (const file of mdFiles) {
|
|
8547
|
-
const filePath =
|
|
8548
|
-
const content = await
|
|
8970
|
+
const filePath = path8.join(sessionDir, file);
|
|
8971
|
+
const content = await readFile6(filePath, "utf-8");
|
|
8549
8972
|
const parsed = this.parseSummaryFile(content, sessionKey, file);
|
|
8550
8973
|
summaries.push(...parsed);
|
|
8551
8974
|
}
|
|
@@ -8633,22 +9056,22 @@ ${truncatedConversation}`;
|
|
|
8633
9056
|
}
|
|
8634
9057
|
// Get list of active sessions from transcript directory
|
|
8635
9058
|
async getActiveSessions() {
|
|
8636
|
-
const transcriptDir =
|
|
9059
|
+
const transcriptDir = path8.join(this.config.memoryDir, "transcripts");
|
|
8637
9060
|
try {
|
|
8638
9061
|
const sessionKeys = /* @__PURE__ */ new Set();
|
|
8639
|
-
const typeEntries = await
|
|
9062
|
+
const typeEntries = await readdir5(transcriptDir, { withFileTypes: true });
|
|
8640
9063
|
for (const typeEnt of typeEntries) {
|
|
8641
9064
|
if (!typeEnt.isDirectory()) continue;
|
|
8642
|
-
const typeDir =
|
|
8643
|
-
const idEntries = await
|
|
9065
|
+
const typeDir = path8.join(transcriptDir, typeEnt.name);
|
|
9066
|
+
const idEntries = await readdir5(typeDir, { withFileTypes: true });
|
|
8644
9067
|
for (const idEnt of idEntries) {
|
|
8645
9068
|
if (!idEnt.isDirectory()) continue;
|
|
8646
|
-
const chanDir =
|
|
8647
|
-
const files = (await
|
|
9069
|
+
const chanDir = path8.join(typeDir, idEnt.name);
|
|
9070
|
+
const files = (await readdir5(chanDir)).filter((f) => f.endsWith(".jsonl")).sort();
|
|
8648
9071
|
const last = files[files.length - 1];
|
|
8649
9072
|
if (!last) continue;
|
|
8650
9073
|
try {
|
|
8651
|
-
const raw = await
|
|
9074
|
+
const raw = await readFile6(path8.join(chanDir, last), "utf-8");
|
|
8652
9075
|
const firstLine = raw.split("\n").find((l) => l.trim().length > 0);
|
|
8653
9076
|
if (!firstLine) continue;
|
|
8654
9077
|
const entry = JSON.parse(firstLine);
|
|
@@ -8683,15 +9106,15 @@ ${truncatedConversation}`;
|
|
|
8683
9106
|
channelId = parts[3];
|
|
8684
9107
|
}
|
|
8685
9108
|
}
|
|
8686
|
-
const transcriptDir =
|
|
9109
|
+
const transcriptDir = path8.join(this.config.memoryDir, "transcripts", channelType, channelId);
|
|
8687
9110
|
try {
|
|
8688
|
-
const files = await
|
|
9111
|
+
const files = await readdir5(transcriptDir);
|
|
8689
9112
|
const entries = [];
|
|
8690
9113
|
for (const file of files) {
|
|
8691
9114
|
if (!file.endsWith(".jsonl")) continue;
|
|
8692
|
-
const transcriptPath =
|
|
9115
|
+
const transcriptPath = path8.join(transcriptDir, file);
|
|
8693
9116
|
try {
|
|
8694
|
-
const content = await
|
|
9117
|
+
const content = await readFile6(transcriptPath, "utf-8");
|
|
8695
9118
|
const lines = content.trim().split("\n");
|
|
8696
9119
|
for (const line of lines) {
|
|
8697
9120
|
if (!line.trim()) continue;
|
|
@@ -9011,17 +9434,17 @@ async function rerankLocalOrNoop(opts) {
|
|
|
9011
9434
|
}
|
|
9012
9435
|
|
|
9013
9436
|
// src/relevance.ts
|
|
9014
|
-
import { mkdir as mkdir6, readFile as
|
|
9015
|
-
import
|
|
9437
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
|
|
9438
|
+
import path9 from "path";
|
|
9016
9439
|
var RelevanceStore = class {
|
|
9017
9440
|
statePath;
|
|
9018
9441
|
state = {};
|
|
9019
9442
|
constructor(memoryDir) {
|
|
9020
|
-
this.statePath =
|
|
9443
|
+
this.statePath = path9.join(memoryDir, "state", "relevance.json");
|
|
9021
9444
|
}
|
|
9022
9445
|
async load() {
|
|
9023
9446
|
try {
|
|
9024
|
-
const raw = await
|
|
9447
|
+
const raw = await readFile7(this.statePath, "utf-8");
|
|
9025
9448
|
const parsed = JSON.parse(raw);
|
|
9026
9449
|
if (parsed && typeof parsed === "object") this.state = parsed;
|
|
9027
9450
|
} catch {
|
|
@@ -9043,8 +9466,8 @@ var RelevanceStore = class {
|
|
|
9043
9466
|
};
|
|
9044
9467
|
this.state[memoryId] = next;
|
|
9045
9468
|
try {
|
|
9046
|
-
await mkdir6(
|
|
9047
|
-
await
|
|
9469
|
+
await mkdir6(path9.dirname(this.statePath), { recursive: true });
|
|
9470
|
+
await writeFile7(this.statePath, JSON.stringify(this.state, null, 2), "utf-8");
|
|
9048
9471
|
} catch (err) {
|
|
9049
9472
|
log.debug(`relevance store write failed: ${err}`);
|
|
9050
9473
|
}
|
|
@@ -9063,17 +9486,17 @@ var RelevanceStore = class {
|
|
|
9063
9486
|
};
|
|
9064
9487
|
|
|
9065
9488
|
// src/negative.ts
|
|
9066
|
-
import { mkdir as mkdir7, readFile as
|
|
9067
|
-
import
|
|
9489
|
+
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
|
|
9490
|
+
import path10 from "path";
|
|
9068
9491
|
var NegativeExampleStore = class {
|
|
9069
9492
|
statePath;
|
|
9070
9493
|
state = {};
|
|
9071
9494
|
constructor(memoryDir) {
|
|
9072
|
-
this.statePath =
|
|
9495
|
+
this.statePath = path10.join(memoryDir, "state", "negative_examples.json");
|
|
9073
9496
|
}
|
|
9074
9497
|
async load() {
|
|
9075
9498
|
try {
|
|
9076
|
-
const raw = await
|
|
9499
|
+
const raw = await readFile8(this.statePath, "utf-8");
|
|
9077
9500
|
const parsed = JSON.parse(raw);
|
|
9078
9501
|
if (parsed && typeof parsed === "object") this.state = parsed;
|
|
9079
9502
|
} catch {
|
|
@@ -9096,8 +9519,8 @@ var NegativeExampleStore = class {
|
|
|
9096
9519
|
this.state[memoryId] = next;
|
|
9097
9520
|
}
|
|
9098
9521
|
try {
|
|
9099
|
-
await mkdir7(
|
|
9100
|
-
await
|
|
9522
|
+
await mkdir7(path10.dirname(this.statePath), { recursive: true });
|
|
9523
|
+
await writeFile8(this.statePath, JSON.stringify(this.state, null, 2), "utf-8");
|
|
9101
9524
|
} catch (err) {
|
|
9102
9525
|
log.debug(`negative example store write failed: ${err}`);
|
|
9103
9526
|
}
|
|
@@ -9116,8 +9539,8 @@ var NegativeExampleStore = class {
|
|
|
9116
9539
|
};
|
|
9117
9540
|
|
|
9118
9541
|
// src/recall-state.ts
|
|
9119
|
-
import { appendFile as appendFile3, mkdir as mkdir8, readFile as
|
|
9120
|
-
import
|
|
9542
|
+
import { appendFile as appendFile3, mkdir as mkdir8, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
|
|
9543
|
+
import path11 from "path";
|
|
9121
9544
|
import { createHash as createHash2 } from "crypto";
|
|
9122
9545
|
function clampGraphRecallExpandedEntries(entries, maxEntries = 64) {
|
|
9123
9546
|
const limit = Math.max(1, Math.floor(maxEntries));
|
|
@@ -9152,12 +9575,12 @@ var LastRecallStore = class {
|
|
|
9152
9575
|
impressionsPath;
|
|
9153
9576
|
state = {};
|
|
9154
9577
|
constructor(memoryDir) {
|
|
9155
|
-
this.statePath =
|
|
9156
|
-
this.impressionsPath =
|
|
9578
|
+
this.statePath = path11.join(memoryDir, "state", "last_recall.json");
|
|
9579
|
+
this.impressionsPath = path11.join(memoryDir, "state", "recall_impressions.jsonl");
|
|
9157
9580
|
}
|
|
9158
9581
|
async load() {
|
|
9159
9582
|
try {
|
|
9160
|
-
const raw = await
|
|
9583
|
+
const raw = await readFile9(this.statePath, "utf-8");
|
|
9161
9584
|
const parsed = JSON.parse(raw);
|
|
9162
9585
|
if (parsed && typeof parsed === "object") this.state = parsed;
|
|
9163
9586
|
} catch {
|
|
@@ -9200,13 +9623,13 @@ var LastRecallStore = class {
|
|
|
9200
9623
|
}
|
|
9201
9624
|
}
|
|
9202
9625
|
try {
|
|
9203
|
-
await mkdir8(
|
|
9204
|
-
await
|
|
9626
|
+
await mkdir8(path11.dirname(this.statePath), { recursive: true });
|
|
9627
|
+
await writeFile9(this.statePath, JSON.stringify(this.state, null, 2), "utf-8");
|
|
9205
9628
|
} catch (err) {
|
|
9206
9629
|
log.debug(`last recall store write failed: ${err}`);
|
|
9207
9630
|
}
|
|
9208
9631
|
try {
|
|
9209
|
-
await mkdir8(
|
|
9632
|
+
await mkdir8(path11.dirname(this.impressionsPath), { recursive: true });
|
|
9210
9633
|
await appendFile3(this.impressionsPath, JSON.stringify(snapshot) + "\n", "utf-8");
|
|
9211
9634
|
} catch (err) {
|
|
9212
9635
|
log.debug(`recall impressions append failed: ${err}`);
|
|
@@ -9217,11 +9640,11 @@ var TierMigrationStatusStore = class {
|
|
|
9217
9640
|
statePath;
|
|
9218
9641
|
state = structuredClone(DEFAULT_TIER_MIGRATION_STATUS);
|
|
9219
9642
|
constructor(memoryDir) {
|
|
9220
|
-
this.statePath =
|
|
9643
|
+
this.statePath = path11.join(memoryDir, "state", "tier-migration-status.json");
|
|
9221
9644
|
}
|
|
9222
9645
|
async load() {
|
|
9223
9646
|
try {
|
|
9224
|
-
const raw = await
|
|
9647
|
+
const raw = await readFile9(this.statePath, "utf-8");
|
|
9225
9648
|
const parsed = JSON.parse(raw);
|
|
9226
9649
|
if (!parsed || typeof parsed !== "object") {
|
|
9227
9650
|
this.state = structuredClone(DEFAULT_TIER_MIGRATION_STATUS);
|
|
@@ -9270,8 +9693,8 @@ var TierMigrationStatusStore = class {
|
|
|
9270
9693
|
};
|
|
9271
9694
|
this.state = next;
|
|
9272
9695
|
try {
|
|
9273
|
-
await mkdir8(
|
|
9274
|
-
await
|
|
9696
|
+
await mkdir8(path11.dirname(this.statePath), { recursive: true });
|
|
9697
|
+
await writeFile9(this.statePath, JSON.stringify(next, null, 2), "utf-8");
|
|
9275
9698
|
} catch (err) {
|
|
9276
9699
|
log.debug(`tier migration status write failed: ${err}`);
|
|
9277
9700
|
}
|
|
@@ -9279,8 +9702,8 @@ var TierMigrationStatusStore = class {
|
|
|
9279
9702
|
};
|
|
9280
9703
|
|
|
9281
9704
|
// src/session-observer-state.ts
|
|
9282
|
-
import
|
|
9283
|
-
import { mkdir as mkdir9, open, readFile as
|
|
9705
|
+
import path12 from "path";
|
|
9706
|
+
import { mkdir as mkdir9, open, readFile as readFile10, stat as stat3, unlink as unlink4, writeFile as writeFile10 } from "fs/promises";
|
|
9284
9707
|
function sanitizeNonNegativeInt(value) {
|
|
9285
9708
|
if (!Number.isFinite(value)) return 0;
|
|
9286
9709
|
return Math.max(0, Math.floor(value));
|
|
@@ -9356,7 +9779,7 @@ var SessionObserverState = class {
|
|
|
9356
9779
|
saveQueue = Promise.resolve();
|
|
9357
9780
|
async readPersistedState() {
|
|
9358
9781
|
try {
|
|
9359
|
-
const raw = await
|
|
9782
|
+
const raw = await readFile10(this.statePath, "utf-8");
|
|
9360
9783
|
const parsed = JSON.parse(raw);
|
|
9361
9784
|
if (parsed?.version !== 1 || !parsed.sessions || typeof parsed.sessions !== "object") {
|
|
9362
9785
|
return null;
|
|
@@ -9382,13 +9805,13 @@ var SessionObserverState = class {
|
|
|
9382
9805
|
return next;
|
|
9383
9806
|
}
|
|
9384
9807
|
constructor(opts) {
|
|
9385
|
-
this.statePath =
|
|
9386
|
-
this.lockPath =
|
|
9808
|
+
this.statePath = path12.join(opts.memoryDir, "state", "session-observer-state.json");
|
|
9809
|
+
this.lockPath = path12.join(opts.memoryDir, "state", "session-observer-state.lock");
|
|
9387
9810
|
this.debounceMs = Math.max(0, Math.floor(opts.debounceMs));
|
|
9388
9811
|
this.bands = normalizeObserverBands(opts.bands);
|
|
9389
9812
|
}
|
|
9390
9813
|
async withSaveLock(fn) {
|
|
9391
|
-
await mkdir9(
|
|
9814
|
+
await mkdir9(path12.dirname(this.lockPath), { recursive: true });
|
|
9392
9815
|
for (let attempt = 0; attempt < 80; attempt++) {
|
|
9393
9816
|
try {
|
|
9394
9817
|
const handle = await open(this.lockPath, "wx");
|
|
@@ -9396,7 +9819,7 @@ var SessionObserverState = class {
|
|
|
9396
9819
|
await fn();
|
|
9397
9820
|
} finally {
|
|
9398
9821
|
await handle.close();
|
|
9399
|
-
await
|
|
9822
|
+
await unlink4(this.lockPath).catch(() => {
|
|
9400
9823
|
});
|
|
9401
9824
|
}
|
|
9402
9825
|
return;
|
|
@@ -9405,7 +9828,7 @@ var SessionObserverState = class {
|
|
|
9405
9828
|
try {
|
|
9406
9829
|
const lockInfo = await stat3(this.lockPath);
|
|
9407
9830
|
if (Date.now() - lockInfo.mtimeMs > this.lockStaleMs) {
|
|
9408
|
-
await
|
|
9831
|
+
await unlink4(this.lockPath).catch(() => {
|
|
9409
9832
|
});
|
|
9410
9833
|
continue;
|
|
9411
9834
|
}
|
|
@@ -9449,8 +9872,8 @@ var SessionObserverState = class {
|
|
|
9449
9872
|
sessions[key] = value;
|
|
9450
9873
|
}
|
|
9451
9874
|
const payload = { version: 1, sessions };
|
|
9452
|
-
await mkdir9(
|
|
9453
|
-
await
|
|
9875
|
+
await mkdir9(path12.dirname(this.statePath), { recursive: true });
|
|
9876
|
+
await writeFile10(this.statePath, JSON.stringify(payload, null, 2), "utf-8");
|
|
9454
9877
|
});
|
|
9455
9878
|
}
|
|
9456
9879
|
enqueueSave() {
|
|
@@ -9543,13 +9966,13 @@ var SessionObserverState = class {
|
|
|
9543
9966
|
};
|
|
9544
9967
|
|
|
9545
9968
|
// src/embedding-fallback.ts
|
|
9546
|
-
import
|
|
9547
|
-
import { mkdir as mkdir10, readFile as
|
|
9969
|
+
import path13 from "path";
|
|
9970
|
+
import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
|
|
9548
9971
|
var DEFAULT_OPENAI_MODEL = "text-embedding-3-small";
|
|
9549
9972
|
var EmbeddingFallback = class {
|
|
9550
9973
|
constructor(config) {
|
|
9551
9974
|
this.config = config;
|
|
9552
|
-
this.indexPath =
|
|
9975
|
+
this.indexPath = path13.join(config.memoryDir, "state", "embeddings.json");
|
|
9553
9976
|
}
|
|
9554
9977
|
indexPath;
|
|
9555
9978
|
loaded = null;
|
|
@@ -9659,7 +10082,7 @@ var EmbeddingFallback = class {
|
|
|
9659
10082
|
return this.loaded;
|
|
9660
10083
|
}
|
|
9661
10084
|
try {
|
|
9662
|
-
const raw = await
|
|
10085
|
+
const raw = await readFile11(this.indexPath, "utf-8");
|
|
9663
10086
|
const parsed = JSON.parse(raw);
|
|
9664
10087
|
if (parsed && parsed.version === 1 && parsed.entries && typeof parsed.entries === "object") {
|
|
9665
10088
|
this.loaded = {
|
|
@@ -9681,14 +10104,14 @@ var EmbeddingFallback = class {
|
|
|
9681
10104
|
return this.loaded;
|
|
9682
10105
|
}
|
|
9683
10106
|
async saveIndex(index) {
|
|
9684
|
-
await mkdir10(
|
|
9685
|
-
await
|
|
10107
|
+
await mkdir10(path13.dirname(this.indexPath), { recursive: true });
|
|
10108
|
+
await writeFile11(this.indexPath, JSON.stringify(index), "utf-8");
|
|
9686
10109
|
this.loaded = index;
|
|
9687
10110
|
}
|
|
9688
10111
|
};
|
|
9689
10112
|
function toMemoryRelativePath(memoryDir, filePath) {
|
|
9690
|
-
if (!
|
|
9691
|
-
const rel =
|
|
10113
|
+
if (!path13.isAbsolute(filePath)) return filePath;
|
|
10114
|
+
const rel = path13.relative(memoryDir, filePath);
|
|
9692
10115
|
return rel.startsWith("..") ? filePath : rel;
|
|
9693
10116
|
}
|
|
9694
10117
|
function cosineSimilarity(a, b) {
|
|
@@ -9710,8 +10133,8 @@ function cosineSimilarity(a, b) {
|
|
|
9710
10133
|
}
|
|
9711
10134
|
|
|
9712
10135
|
// src/bootstrap.ts
|
|
9713
|
-
import
|
|
9714
|
-
import { readdir as
|
|
10136
|
+
import path14 from "path";
|
|
10137
|
+
import { readdir as readdir6, readFile as readFile12 } from "fs/promises";
|
|
9715
10138
|
var BootstrapEngine = class {
|
|
9716
10139
|
constructor(config, orchestrator) {
|
|
9717
10140
|
this.config = config;
|
|
@@ -9790,7 +10213,7 @@ var BootstrapEngine = class {
|
|
|
9790
10213
|
for (const filePath of files) {
|
|
9791
10214
|
let raw = "";
|
|
9792
10215
|
try {
|
|
9793
|
-
raw = await
|
|
10216
|
+
raw = await readFile12(filePath, "utf-8");
|
|
9794
10217
|
} catch {
|
|
9795
10218
|
continue;
|
|
9796
10219
|
}
|
|
@@ -9808,7 +10231,7 @@ var BootstrapEngine = class {
|
|
|
9808
10231
|
const role = String(parsed?.role ?? "");
|
|
9809
10232
|
const content = typeof parsed?.content === "string" ? parsed.content : "";
|
|
9810
10233
|
if (!role || !content) continue;
|
|
9811
|
-
const sessionKey = typeof parsed?.sessionKey === "string" && parsed.sessionKey.length > 0 ? parsed.sessionKey :
|
|
10234
|
+
const sessionKey = typeof parsed?.sessionKey === "string" && parsed.sessionKey.length > 0 ? parsed.sessionKey : path14.relative(baseDir, filePath);
|
|
9812
10235
|
const list = bySession.get(sessionKey) ?? [];
|
|
9813
10236
|
list.push({
|
|
9814
10237
|
role,
|
|
@@ -9826,9 +10249,9 @@ var BootstrapEngine = class {
|
|
|
9826
10249
|
}
|
|
9827
10250
|
async listJsonlFiles(dir) {
|
|
9828
10251
|
const out = [];
|
|
9829
|
-
const entries = await
|
|
10252
|
+
const entries = await readdir6(dir, { withFileTypes: true }).catch(() => []);
|
|
9830
10253
|
for (const entry of entries) {
|
|
9831
|
-
const full =
|
|
10254
|
+
const full = path14.join(dir, entry.name);
|
|
9832
10255
|
if (entry.isDirectory()) {
|
|
9833
10256
|
out.push(...await this.listJsonlFiles(full));
|
|
9834
10257
|
} else if (entry.isFile() && full.endsWith(".jsonl")) {
|
|
@@ -10343,8 +10766,8 @@ function renderCompressionGuidelinesMarkdown(candidate) {
|
|
|
10343
10766
|
}
|
|
10344
10767
|
|
|
10345
10768
|
// src/boxes.ts
|
|
10346
|
-
import { mkdir as mkdir11, writeFile as
|
|
10347
|
-
import
|
|
10769
|
+
import { mkdir as mkdir11, writeFile as writeFile12, readFile as readFile13, readdir as readdir7 } from "fs/promises";
|
|
10770
|
+
import path15 from "path";
|
|
10348
10771
|
import { createHash as createHash3 } from "crypto";
|
|
10349
10772
|
var BOX_DIR = "boxes";
|
|
10350
10773
|
var STATE_DIR = "state";
|
|
@@ -10419,23 +10842,23 @@ var BoxBuilder = class {
|
|
|
10419
10842
|
this.cfg = cfg;
|
|
10420
10843
|
}
|
|
10421
10844
|
get boxBaseDir() {
|
|
10422
|
-
return
|
|
10845
|
+
return path15.join(this.baseDir, BOX_DIR);
|
|
10423
10846
|
}
|
|
10424
10847
|
get stateDir() {
|
|
10425
|
-
return
|
|
10848
|
+
return path15.join(this.baseDir, STATE_DIR);
|
|
10426
10849
|
}
|
|
10427
10850
|
get openBoxStatePath() {
|
|
10428
|
-
return
|
|
10851
|
+
return path15.join(this.stateDir, OPEN_BOX_STATE_FILE);
|
|
10429
10852
|
}
|
|
10430
10853
|
get tracesPath() {
|
|
10431
|
-
return
|
|
10854
|
+
return path15.join(this.stateDir, TRACES_FILE);
|
|
10432
10855
|
}
|
|
10433
10856
|
// ── State persistence ────────────────────────────────────────────────────
|
|
10434
10857
|
async loadOpenBox() {
|
|
10435
10858
|
if (this.stateLoaded) return;
|
|
10436
10859
|
this.stateLoaded = true;
|
|
10437
10860
|
try {
|
|
10438
|
-
const raw = await
|
|
10861
|
+
const raw = await readFile13(this.openBoxStatePath, "utf-8");
|
|
10439
10862
|
this.openBox = JSON.parse(raw);
|
|
10440
10863
|
} catch {
|
|
10441
10864
|
this.openBox = null;
|
|
@@ -10444,17 +10867,17 @@ var BoxBuilder = class {
|
|
|
10444
10867
|
async saveOpenBox() {
|
|
10445
10868
|
await mkdir11(this.stateDir, { recursive: true });
|
|
10446
10869
|
if (this.openBox) {
|
|
10447
|
-
await
|
|
10870
|
+
await writeFile12(this.openBoxStatePath, JSON.stringify(this.openBox, null, 2), "utf-8");
|
|
10448
10871
|
} else {
|
|
10449
10872
|
try {
|
|
10450
|
-
await
|
|
10873
|
+
await writeFile12(this.openBoxStatePath, "null", "utf-8");
|
|
10451
10874
|
} catch {
|
|
10452
10875
|
}
|
|
10453
10876
|
}
|
|
10454
10877
|
}
|
|
10455
10878
|
async loadTraceIndex() {
|
|
10456
10879
|
try {
|
|
10457
|
-
const raw = await
|
|
10880
|
+
const raw = await readFile13(this.tracesPath, "utf-8");
|
|
10458
10881
|
const parsed = JSON.parse(raw);
|
|
10459
10882
|
parsed.traceLastSeen ??= {};
|
|
10460
10883
|
return parsed;
|
|
@@ -10465,7 +10888,7 @@ var BoxBuilder = class {
|
|
|
10465
10888
|
async saveTraceIndex(idx) {
|
|
10466
10889
|
try {
|
|
10467
10890
|
await mkdir11(this.stateDir, { recursive: true });
|
|
10468
|
-
await
|
|
10891
|
+
await writeFile12(this.tracesPath, JSON.stringify(idx, null, 2), "utf-8");
|
|
10469
10892
|
} catch (err) {
|
|
10470
10893
|
log.warn(`[engram/boxes] Failed to save trace index: ${err.message}`);
|
|
10471
10894
|
}
|
|
@@ -10541,7 +10964,7 @@ var BoxBuilder = class {
|
|
|
10541
10964
|
}
|
|
10542
10965
|
const sealedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10543
10966
|
const day = sealedAt.slice(0, 10);
|
|
10544
|
-
const dir =
|
|
10967
|
+
const dir = path15.join(this.boxBaseDir, day);
|
|
10545
10968
|
await mkdir11(dir, { recursive: true });
|
|
10546
10969
|
let traceId;
|
|
10547
10970
|
if (this.cfg.traceWeaverEnabled && box.topics.length > 0) {
|
|
@@ -10561,8 +10984,8 @@ var BoxBuilder = class {
|
|
|
10561
10984
|
|
|
10562
10985
|
<!-- Topics: ${box.topics.join(", ")} | Memories: ${box.memoryIds.length} -->
|
|
10563
10986
|
`;
|
|
10564
|
-
const filePath =
|
|
10565
|
-
await
|
|
10987
|
+
const filePath = path15.join(dir, `${box.id}.md`);
|
|
10988
|
+
await writeFile12(filePath, content, "utf-8");
|
|
10566
10989
|
log.debug(`[boxes] sealed box ${box.id} (${reason}): ${box.memoryIds.length} memories, topics=[${box.topics.join(",")}]`);
|
|
10567
10990
|
await this.saveOpenBox();
|
|
10568
10991
|
return box.id;
|
|
@@ -10611,14 +11034,14 @@ var BoxBuilder = class {
|
|
|
10611
11034
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
10612
11035
|
const walkDir = async (dir) => {
|
|
10613
11036
|
try {
|
|
10614
|
-
const entries = await
|
|
11037
|
+
const entries = await readdir7(dir, { withFileTypes: true });
|
|
10615
11038
|
for (const e of entries) {
|
|
10616
|
-
const full =
|
|
11039
|
+
const full = path15.join(dir, e.name);
|
|
10617
11040
|
if (e.isDirectory()) {
|
|
10618
11041
|
await walkDir(full);
|
|
10619
11042
|
} else if (e.name.endsWith(".md")) {
|
|
10620
11043
|
try {
|
|
10621
|
-
const raw = await
|
|
11044
|
+
const raw = await readFile13(full, "utf-8");
|
|
10622
11045
|
const parsed = parseBoxFrontmatter(raw);
|
|
10623
11046
|
if (parsed && new Date(parsed.sealedAt) >= cutoff) {
|
|
10624
11047
|
boxes.push(parsed);
|
|
@@ -10726,8 +11149,8 @@ function classifyMemoryKind(content, tags, category) {
|
|
|
10726
11149
|
|
|
10727
11150
|
// src/tmt.ts
|
|
10728
11151
|
import * as fs from "fs";
|
|
10729
|
-
import * as
|
|
10730
|
-
import { mkdir as mkdir12, readFile as
|
|
11152
|
+
import * as path16 from "path";
|
|
11153
|
+
import { mkdir as mkdir12, readFile as readFile14, writeFile as writeFile13, readdir as readdir8 } from "fs/promises";
|
|
10731
11154
|
var TMT_DIR = "tmt";
|
|
10732
11155
|
var TMT_LEVEL_INPUT_LIMITS = {
|
|
10733
11156
|
hour: { totalChars: 48e3, itemChars: 2e3, maxItems: 64 },
|
|
@@ -10756,19 +11179,19 @@ function capTmtSummaryInputs(inputs, level) {
|
|
|
10756
11179
|
return [fallback.length > itemChars ? `${fallback.slice(0, itemChars - 1)}\u2026` : fallback];
|
|
10757
11180
|
}
|
|
10758
11181
|
function tmtDir(baseDir) {
|
|
10759
|
-
return
|
|
11182
|
+
return path16.join(baseDir, TMT_DIR);
|
|
10760
11183
|
}
|
|
10761
11184
|
function hourNodePath(baseDir, date, hour) {
|
|
10762
|
-
return
|
|
11185
|
+
return path16.join(tmtDir(baseDir), date, `hour-${hour}.md`);
|
|
10763
11186
|
}
|
|
10764
11187
|
function dayNodePath(baseDir, date) {
|
|
10765
|
-
return
|
|
11188
|
+
return path16.join(tmtDir(baseDir), date, "day.md");
|
|
10766
11189
|
}
|
|
10767
11190
|
function weekNodePath(baseDir, weekKey) {
|
|
10768
|
-
return
|
|
11191
|
+
return path16.join(tmtDir(baseDir), `week-${weekKey}.md`);
|
|
10769
11192
|
}
|
|
10770
11193
|
function personaNodePath(baseDir) {
|
|
10771
|
-
return
|
|
11194
|
+
return path16.join(tmtDir(baseDir), "persona.md");
|
|
10772
11195
|
}
|
|
10773
11196
|
function serialiseTmtNode(fm, summary) {
|
|
10774
11197
|
const yaml = [
|
|
@@ -10839,7 +11262,7 @@ var TmtBuilder = class {
|
|
|
10839
11262
|
let shouldBuild = !fs.existsSync(nodePath);
|
|
10840
11263
|
if (!shouldBuild) {
|
|
10841
11264
|
try {
|
|
10842
|
-
const existing = await
|
|
11265
|
+
const existing = await readFile14(nodePath, "utf8");
|
|
10843
11266
|
const countMatch = existing.match(/memoryCount: (\d+)/);
|
|
10844
11267
|
if (!countMatch || parseInt(countMatch[1], 10) < entries.length) {
|
|
10845
11268
|
shouldBuild = true;
|
|
@@ -10865,8 +11288,8 @@ var TmtBuilder = class {
|
|
|
10865
11288
|
sourceIds: entries.map((e) => e.id),
|
|
10866
11289
|
builtAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10867
11290
|
};
|
|
10868
|
-
await mkdir12(
|
|
10869
|
-
await
|
|
11291
|
+
await mkdir12(path16.dirname(nodePath), { recursive: true });
|
|
11292
|
+
await writeFile13(nodePath, serialiseTmtNode(fm, summary), "utf8");
|
|
10870
11293
|
}
|
|
10871
11294
|
}
|
|
10872
11295
|
async buildDayNodes(memories, summarize2) {
|
|
@@ -10881,7 +11304,7 @@ var TmtBuilder = class {
|
|
|
10881
11304
|
let shouldBuild = !fs.existsSync(nodePath);
|
|
10882
11305
|
if (!shouldBuild) {
|
|
10883
11306
|
try {
|
|
10884
|
-
const existing = await
|
|
11307
|
+
const existing = await readFile14(nodePath, "utf8");
|
|
10885
11308
|
const countMatch = existing.match(/memoryCount: (\d+)/);
|
|
10886
11309
|
if (!countMatch || parseInt(countMatch[1], 10) < entries.length) {
|
|
10887
11310
|
shouldBuild = true;
|
|
@@ -10902,7 +11325,7 @@ var TmtBuilder = class {
|
|
|
10902
11325
|
const hPath = hourNodePath(this.baseDir, date, h);
|
|
10903
11326
|
if (fs.existsSync(hPath)) {
|
|
10904
11327
|
try {
|
|
10905
|
-
const hContent = await
|
|
11328
|
+
const hContent = await readFile14(hPath, "utf8");
|
|
10906
11329
|
const hSummary = hContent.replace(/^---[\s\S]*?---\n\n?/, "").trim();
|
|
10907
11330
|
if (hSummary) {
|
|
10908
11331
|
inputs.push(hSummary);
|
|
@@ -10930,8 +11353,8 @@ var TmtBuilder = class {
|
|
|
10930
11353
|
sourceIds: entries.map((e) => e.id),
|
|
10931
11354
|
builtAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10932
11355
|
};
|
|
10933
|
-
await mkdir12(
|
|
10934
|
-
await
|
|
11356
|
+
await mkdir12(path16.dirname(nodePath), { recursive: true });
|
|
11357
|
+
await writeFile13(nodePath, serialiseTmtNode(fm, summary), "utf8");
|
|
10935
11358
|
}
|
|
10936
11359
|
}
|
|
10937
11360
|
/**
|
|
@@ -10952,7 +11375,7 @@ var TmtBuilder = class {
|
|
|
10952
11375
|
let shouldBuild = !fs.existsSync(nodePath);
|
|
10953
11376
|
if (!shouldBuild) {
|
|
10954
11377
|
try {
|
|
10955
|
-
const existing = await
|
|
11378
|
+
const existing = await readFile14(nodePath, "utf8");
|
|
10956
11379
|
const countMatch = existing.match(/memoryCount: (\d+)/);
|
|
10957
11380
|
if (!countMatch || parseInt(countMatch[1], 10) < entries.length) {
|
|
10958
11381
|
shouldBuild = true;
|
|
@@ -10966,7 +11389,7 @@ var TmtBuilder = class {
|
|
|
10966
11389
|
const dir = tmtDir(this.baseDir);
|
|
10967
11390
|
let allDirs = [];
|
|
10968
11391
|
try {
|
|
10969
|
-
allDirs = await
|
|
11392
|
+
allDirs = await readdir8(dir);
|
|
10970
11393
|
} catch {
|
|
10971
11394
|
continue;
|
|
10972
11395
|
}
|
|
@@ -10978,7 +11401,7 @@ var TmtBuilder = class {
|
|
|
10978
11401
|
const dayPath = dayNodePath(this.baseDir, dateDir);
|
|
10979
11402
|
if (fs.existsSync(dayPath)) {
|
|
10980
11403
|
try {
|
|
10981
|
-
const content = await
|
|
11404
|
+
const content = await readFile14(dayPath, "utf8");
|
|
10982
11405
|
const summary2 = content.replace(/^---[\s\S]*?---\n\n?/, "").trim();
|
|
10983
11406
|
if (summary2) daySummaries.push(summary2);
|
|
10984
11407
|
} catch {
|
|
@@ -10996,8 +11419,8 @@ var TmtBuilder = class {
|
|
|
10996
11419
|
sourceIds: entries.map((e) => e.id),
|
|
10997
11420
|
builtAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10998
11421
|
};
|
|
10999
|
-
await mkdir12(
|
|
11000
|
-
await
|
|
11422
|
+
await mkdir12(path16.dirname(nodePath), { recursive: true });
|
|
11423
|
+
await writeFile13(nodePath, serialiseTmtNode(fm, summary), "utf8");
|
|
11001
11424
|
} catch (err) {
|
|
11002
11425
|
console.warn(`[engram] tmt: week node build failed for ${week} (ignored): ${err}`);
|
|
11003
11426
|
}
|
|
@@ -11013,7 +11436,7 @@ var TmtBuilder = class {
|
|
|
11013
11436
|
const dir = tmtDir(this.baseDir);
|
|
11014
11437
|
let allFiles = [];
|
|
11015
11438
|
try {
|
|
11016
|
-
allFiles = await
|
|
11439
|
+
allFiles = await readdir8(dir);
|
|
11017
11440
|
} catch {
|
|
11018
11441
|
return;
|
|
11019
11442
|
}
|
|
@@ -11025,7 +11448,7 @@ var TmtBuilder = class {
|
|
|
11025
11448
|
let latestEnd;
|
|
11026
11449
|
for (const f of weekFiles) {
|
|
11027
11450
|
try {
|
|
11028
|
-
const content = await
|
|
11451
|
+
const content = await readFile14(path16.join(dir, f), "utf8");
|
|
11029
11452
|
const summary2 = content.replace(/^---[\s\S]*?---\n\n?/, "").trim();
|
|
11030
11453
|
if (summary2) weekSummaries.push(summary2);
|
|
11031
11454
|
const countMatch = content.match(/memoryCount: (\d+)/);
|
|
@@ -11046,7 +11469,7 @@ var TmtBuilder = class {
|
|
|
11046
11469
|
let shouldBuild = !fs.existsSync(nodePath);
|
|
11047
11470
|
if (!shouldBuild) {
|
|
11048
11471
|
try {
|
|
11049
|
-
const existing = await
|
|
11472
|
+
const existing = await readFile14(nodePath, "utf8");
|
|
11050
11473
|
const countMatch = existing.match(/memoryCount: (\d+)/);
|
|
11051
11474
|
if (!countMatch || parseInt(countMatch[1], 10) !== totalCount) {
|
|
11052
11475
|
shouldBuild = true;
|
|
@@ -11066,7 +11489,7 @@ var TmtBuilder = class {
|
|
|
11066
11489
|
sourceIds: [],
|
|
11067
11490
|
builtAt: now
|
|
11068
11491
|
};
|
|
11069
|
-
await
|
|
11492
|
+
await writeFile13(nodePath, serialiseTmtNode(fm, summary), "utf8");
|
|
11070
11493
|
} catch (err) {
|
|
11071
11494
|
console.warn(`[engram] tmt: persona node build failed (ignored): ${err}`);
|
|
11072
11495
|
}
|
|
@@ -11084,13 +11507,13 @@ var TmtBuilder = class {
|
|
|
11084
11507
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
11085
11508
|
const todayDay = dayNodePath(this.baseDir, today);
|
|
11086
11509
|
if (fs.existsSync(todayDay)) {
|
|
11087
|
-
const content = await
|
|
11510
|
+
const content = await readFile14(todayDay, "utf8");
|
|
11088
11511
|
const summary = content.replace(/^---[\s\S]*?---\n\n?/, "").trim();
|
|
11089
11512
|
if (summary) return { level: "day", summary };
|
|
11090
11513
|
}
|
|
11091
11514
|
let entries = [];
|
|
11092
11515
|
try {
|
|
11093
|
-
entries = await
|
|
11516
|
+
entries = await readdir8(dir);
|
|
11094
11517
|
} catch {
|
|
11095
11518
|
return null;
|
|
11096
11519
|
}
|
|
@@ -11098,7 +11521,7 @@ var TmtBuilder = class {
|
|
|
11098
11521
|
for (const dateDir of dateDirs) {
|
|
11099
11522
|
const dayPath = dayNodePath(this.baseDir, dateDir);
|
|
11100
11523
|
if (fs.existsSync(dayPath)) {
|
|
11101
|
-
const content = await
|
|
11524
|
+
const content = await readFile14(dayPath, "utf8");
|
|
11102
11525
|
const summary = content.replace(/^---[\s\S]*?---\n\n?/, "").trim();
|
|
11103
11526
|
if (summary) return { level: "day", summary };
|
|
11104
11527
|
}
|
|
@@ -11112,18 +11535,18 @@ var TmtBuilder = class {
|
|
|
11112
11535
|
|
|
11113
11536
|
// src/temporal-index.ts
|
|
11114
11537
|
import * as fs2 from "fs";
|
|
11115
|
-
import * as
|
|
11538
|
+
import * as path17 from "path";
|
|
11116
11539
|
var INDEX_VERSION = 1;
|
|
11117
11540
|
var TEMPORAL_INDEX_FILE = "index_time.json";
|
|
11118
11541
|
var TAG_INDEX_FILE = "index_tags.json";
|
|
11119
11542
|
function stateDir(memoryDir) {
|
|
11120
|
-
return
|
|
11543
|
+
return path17.join(memoryDir, "state");
|
|
11121
11544
|
}
|
|
11122
11545
|
function temporalIndexPath(memoryDir) {
|
|
11123
|
-
return
|
|
11546
|
+
return path17.join(stateDir(memoryDir), TEMPORAL_INDEX_FILE);
|
|
11124
11547
|
}
|
|
11125
11548
|
function tagIndexPath(memoryDir) {
|
|
11126
|
-
return
|
|
11549
|
+
return path17.join(stateDir(memoryDir), TAG_INDEX_FILE);
|
|
11127
11550
|
}
|
|
11128
11551
|
function ensureStateDir(memoryDir) {
|
|
11129
11552
|
const dir = stateDir(memoryDir);
|
|
@@ -11357,8 +11780,8 @@ function recencyWindowFromPrompt(prompt, nowMs = Date.now()) {
|
|
|
11357
11780
|
}
|
|
11358
11781
|
|
|
11359
11782
|
// src/graph.ts
|
|
11360
|
-
import { mkdir as mkdir13, appendFile as appendFile4, readFile as
|
|
11361
|
-
import * as
|
|
11783
|
+
import { mkdir as mkdir13, appendFile as appendFile4, readFile as readFile15 } from "fs/promises";
|
|
11784
|
+
import * as path18 from "path";
|
|
11362
11785
|
var CAUSAL_PHRASES = [
|
|
11363
11786
|
"as a result",
|
|
11364
11787
|
"led to",
|
|
@@ -11368,10 +11791,10 @@ var CAUSAL_PHRASES = [
|
|
|
11368
11791
|
"because"
|
|
11369
11792
|
];
|
|
11370
11793
|
function graphsDir(memoryDir) {
|
|
11371
|
-
return
|
|
11794
|
+
return path18.join(memoryDir, "state", "graphs");
|
|
11372
11795
|
}
|
|
11373
11796
|
function graphFilePath(memoryDir, type) {
|
|
11374
|
-
return
|
|
11797
|
+
return path18.join(graphsDir(memoryDir), `${type}.jsonl`);
|
|
11375
11798
|
}
|
|
11376
11799
|
async function ensureGraphsDir(memoryDir) {
|
|
11377
11800
|
await mkdir13(graphsDir(memoryDir), { recursive: true });
|
|
@@ -11384,7 +11807,7 @@ async function appendEdge(memoryDir, edge) {
|
|
|
11384
11807
|
async function readEdges(memoryDir, type) {
|
|
11385
11808
|
const filePath = graphFilePath(memoryDir, type);
|
|
11386
11809
|
try {
|
|
11387
|
-
const raw = await
|
|
11810
|
+
const raw = await readFile15(filePath, "utf8");
|
|
11388
11811
|
const edges = [];
|
|
11389
11812
|
for (const line of raw.split("\n")) {
|
|
11390
11813
|
const trimmed = line.trim();
|
|
@@ -11427,7 +11850,7 @@ async function analyzeGraphHealth(memoryDir, options) {
|
|
|
11427
11850
|
let corruptLines = 0;
|
|
11428
11851
|
const nodes = /* @__PURE__ */ new Set();
|
|
11429
11852
|
try {
|
|
11430
|
-
const raw = await
|
|
11853
|
+
const raw = await readFile15(filePath, "utf8");
|
|
11431
11854
|
for (const line of raw.split("\n")) {
|
|
11432
11855
|
const trimmed = line.trim();
|
|
11433
11856
|
if (!trimmed) continue;
|
|
@@ -11751,8 +12174,8 @@ function chunkTranscriptEntries(sessionKey, entries, opts) {
|
|
|
11751
12174
|
}
|
|
11752
12175
|
|
|
11753
12176
|
// src/conversation-index/indexer.ts
|
|
11754
|
-
import { mkdir as mkdir14, writeFile as
|
|
11755
|
-
import
|
|
12177
|
+
import { mkdir as mkdir14, writeFile as writeFile14 } from "fs/promises";
|
|
12178
|
+
import path19 from "path";
|
|
11756
12179
|
function sanitizeSessionKey(sessionKey) {
|
|
11757
12180
|
const raw = typeof sessionKey === "string" && sessionKey.trim().length > 0 ? sessionKey : "unknown-session";
|
|
11758
12181
|
return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "_").slice(0, 200);
|
|
@@ -11762,9 +12185,9 @@ async function writeConversationChunks(rootDir, chunks) {
|
|
|
11762
12185
|
for (const c of chunks) {
|
|
11763
12186
|
const safe = sanitizeSessionKey(c.sessionKey);
|
|
11764
12187
|
const date = c.startTs.slice(0, 10);
|
|
11765
|
-
const dir =
|
|
12188
|
+
const dir = path19.join(rootDir, safe, date);
|
|
11766
12189
|
await mkdir14(dir, { recursive: true });
|
|
11767
|
-
const fp =
|
|
12190
|
+
const fp = path19.join(dir, `${c.id}.md`);
|
|
11768
12191
|
const content = `---
|
|
11769
12192
|
kind: conversation_chunk
|
|
11770
12193
|
sessionKey: ${c.sessionKey}
|
|
@@ -11773,7 +12196,7 @@ endTs: ${c.endTs}
|
|
|
11773
12196
|
---
|
|
11774
12197
|
|
|
11775
12198
|
` + c.text + "\n";
|
|
11776
|
-
await
|
|
12199
|
+
await writeFile14(fp, content, "utf-8");
|
|
11777
12200
|
written.push(fp);
|
|
11778
12201
|
}
|
|
11779
12202
|
return written;
|
|
@@ -11792,28 +12215,28 @@ async function upsertConversationChunksFailOpen(adapter, chunks) {
|
|
|
11792
12215
|
}
|
|
11793
12216
|
|
|
11794
12217
|
// src/conversation-index/cleanup.ts
|
|
11795
|
-
import { readdir as
|
|
11796
|
-
import
|
|
12218
|
+
import { readdir as readdir9, rm } from "fs/promises";
|
|
12219
|
+
import path20 from "path";
|
|
11797
12220
|
async function cleanupConversationChunks(rootDir, retentionDays) {
|
|
11798
12221
|
if (!Number.isFinite(retentionDays) || retentionDays <= 0) return;
|
|
11799
12222
|
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
11800
12223
|
try {
|
|
11801
|
-
const sessions = await
|
|
12224
|
+
const sessions = await readdir9(rootDir, { withFileTypes: true });
|
|
11802
12225
|
for (const s of sessions) {
|
|
11803
12226
|
if (!s.isDirectory()) continue;
|
|
11804
|
-
const sessionDir =
|
|
11805
|
-
const dayDirs = await
|
|
12227
|
+
const sessionDir = path20.join(rootDir, s.name);
|
|
12228
|
+
const dayDirs = await readdir9(sessionDir, { withFileTypes: true });
|
|
11806
12229
|
for (const d of dayDirs) {
|
|
11807
12230
|
if (!d.isDirectory()) continue;
|
|
11808
12231
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(d.name)) continue;
|
|
11809
12232
|
const dayMs = (/* @__PURE__ */ new Date(d.name + "T00:00:00.000Z")).getTime();
|
|
11810
12233
|
if (!Number.isFinite(dayMs)) continue;
|
|
11811
12234
|
if (dayMs < cutoffMs) {
|
|
11812
|
-
await rm(
|
|
12235
|
+
await rm(path20.join(sessionDir, d.name), { recursive: true, force: true });
|
|
11813
12236
|
}
|
|
11814
12237
|
}
|
|
11815
12238
|
try {
|
|
11816
|
-
const remaining = await
|
|
12239
|
+
const remaining = await readdir9(sessionDir);
|
|
11817
12240
|
if (remaining.length === 0) {
|
|
11818
12241
|
await rm(sessionDir, { recursive: true, force: true });
|
|
11819
12242
|
}
|
|
@@ -11828,7 +12251,7 @@ async function cleanupConversationChunks(rootDir, retentionDays) {
|
|
|
11828
12251
|
// src/conversation-index/faiss-adapter.ts
|
|
11829
12252
|
import * as childProcess from "child_process";
|
|
11830
12253
|
import { fileURLToPath } from "url";
|
|
11831
|
-
import
|
|
12254
|
+
import path21 from "path";
|
|
11832
12255
|
var FaissAdapterError = class extends Error {
|
|
11833
12256
|
constructor(message, code) {
|
|
11834
12257
|
super(message);
|
|
@@ -11838,18 +12261,18 @@ var FaissAdapterError = class extends Error {
|
|
|
11838
12261
|
};
|
|
11839
12262
|
function resolveDefaultFaissScriptPath(fromModuleUrl = import.meta.url) {
|
|
11840
12263
|
const currentFile = fileURLToPath(fromModuleUrl);
|
|
11841
|
-
const moduleDir =
|
|
11842
|
-
if (moduleDir.endsWith(`${
|
|
11843
|
-
return
|
|
12264
|
+
const moduleDir = path21.dirname(currentFile);
|
|
12265
|
+
if (moduleDir.endsWith(`${path21.sep}conversation-index`)) {
|
|
12266
|
+
return path21.resolve(moduleDir, "..", "..", "scripts", "faiss_index.py");
|
|
11844
12267
|
}
|
|
11845
|
-
return
|
|
12268
|
+
return path21.resolve(moduleDir, "..", "scripts", "faiss_index.py");
|
|
11846
12269
|
}
|
|
11847
12270
|
var FaissConversationIndexAdapter = class {
|
|
11848
12271
|
constructor(config) {
|
|
11849
12272
|
this.config = config;
|
|
11850
12273
|
this.pythonBin = config.pythonBin && config.pythonBin.trim().length > 0 ? config.pythonBin.trim() : "python3";
|
|
11851
12274
|
this.scriptPath = config.scriptPath && config.scriptPath.trim().length > 0 ? config.scriptPath.trim() : resolveDefaultFaissScriptPath();
|
|
11852
|
-
this.indexPath =
|
|
12275
|
+
this.indexPath = path21.isAbsolute(config.indexDir) ? config.indexDir : path21.join(config.memoryDir, config.indexDir);
|
|
11853
12276
|
this.spawnFn = config.spawnFn ?? childProcess.spawn;
|
|
11854
12277
|
}
|
|
11855
12278
|
pythonBin;
|
|
@@ -12030,7 +12453,7 @@ async function searchConversationIndexFaissFailOpen(adapter, query, maxResults)
|
|
|
12030
12453
|
}
|
|
12031
12454
|
|
|
12032
12455
|
// src/namespaces/storage.ts
|
|
12033
|
-
import
|
|
12456
|
+
import path22 from "path";
|
|
12034
12457
|
import { access } from "fs/promises";
|
|
12035
12458
|
async function exists(p) {
|
|
12036
12459
|
try {
|
|
@@ -12052,7 +12475,7 @@ var NamespaceStorageRouter = class {
|
|
|
12052
12475
|
this.defaultNsRootResolved = this.config.memoryDir;
|
|
12053
12476
|
return this.defaultNsRootResolved;
|
|
12054
12477
|
}
|
|
12055
|
-
const nsDir =
|
|
12478
|
+
const nsDir = path22.join(this.config.memoryDir, "namespaces", this.config.defaultNamespace);
|
|
12056
12479
|
this.defaultNsRootResolved = await exists(nsDir) ? nsDir : this.config.memoryDir;
|
|
12057
12480
|
return this.defaultNsRootResolved;
|
|
12058
12481
|
}
|
|
@@ -12061,7 +12484,7 @@ var NamespaceStorageRouter = class {
|
|
|
12061
12484
|
if (namespace === this.config.defaultNamespace) {
|
|
12062
12485
|
return this.defaultNsRootResolved ?? this.config.memoryDir;
|
|
12063
12486
|
}
|
|
12064
|
-
return
|
|
12487
|
+
return path22.join(this.config.memoryDir, "namespaces", namespace);
|
|
12065
12488
|
}
|
|
12066
12489
|
async storageFor(namespace) {
|
|
12067
12490
|
const ns = namespace || this.config.defaultNamespace;
|
|
@@ -12137,8 +12560,8 @@ function recallNamespacesForPrincipal(principal, config) {
|
|
|
12137
12560
|
}
|
|
12138
12561
|
|
|
12139
12562
|
// src/shared-context/manager.ts
|
|
12140
|
-
import { mkdir as mkdir15, readFile as
|
|
12141
|
-
import
|
|
12563
|
+
import { mkdir as mkdir15, readFile as readFile16, readdir as readdir10, appendFile as appendFile5, writeFile as writeFile15, stat as stat4 } from "fs/promises";
|
|
12564
|
+
import path23 from "path";
|
|
12142
12565
|
import os3 from "os";
|
|
12143
12566
|
import { z as z3 } from "zod";
|
|
12144
12567
|
var SharedFeedbackEntrySchema = z3.object({
|
|
@@ -12334,15 +12757,15 @@ async function computeSemanticOverlapsWithTimeout(sources, timeoutMs, maxCandida
|
|
|
12334
12757
|
var SharedContextManager = class {
|
|
12335
12758
|
constructor(config) {
|
|
12336
12759
|
this.config = config;
|
|
12337
|
-
const base = typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0 ? config.sharedContextDir :
|
|
12760
|
+
const base = typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0 ? config.sharedContextDir : path23.join(os3.homedir(), ".openclaw", "workspace", "shared-context");
|
|
12338
12761
|
this.dir = base;
|
|
12339
|
-
this.prioritiesPath =
|
|
12340
|
-
this.prioritiesInboxPath =
|
|
12341
|
-
this.outputsDir =
|
|
12342
|
-
this.roundtableDir =
|
|
12343
|
-
this.feedbackDir =
|
|
12344
|
-
this.feedbackInboxPath =
|
|
12345
|
-
this.crossSignalsDir =
|
|
12762
|
+
this.prioritiesPath = path23.join(base, "priorities.md");
|
|
12763
|
+
this.prioritiesInboxPath = path23.join(base, "priorities.inbox.md");
|
|
12764
|
+
this.outputsDir = path23.join(base, "agent-outputs");
|
|
12765
|
+
this.roundtableDir = path23.join(base, "roundtable");
|
|
12766
|
+
this.feedbackDir = path23.join(base, "feedback");
|
|
12767
|
+
this.feedbackInboxPath = path23.join(this.feedbackDir, "inbox.jsonl");
|
|
12768
|
+
this.crossSignalsDir = path23.join(base, "cross-signals");
|
|
12346
12769
|
}
|
|
12347
12770
|
dir;
|
|
12348
12771
|
prioritiesPath;
|
|
@@ -12358,10 +12781,10 @@ var SharedContextManager = class {
|
|
|
12358
12781
|
await mkdir15(this.roundtableDir, { recursive: true });
|
|
12359
12782
|
await mkdir15(this.feedbackDir, { recursive: true });
|
|
12360
12783
|
await mkdir15(this.crossSignalsDir, { recursive: true });
|
|
12361
|
-
await mkdir15(
|
|
12362
|
-
await mkdir15(
|
|
12363
|
-
await mkdir15(
|
|
12364
|
-
await mkdir15(
|
|
12784
|
+
await mkdir15(path23.join(this.dir, "staging"), { recursive: true });
|
|
12785
|
+
await mkdir15(path23.join(this.dir, "kpis"), { recursive: true });
|
|
12786
|
+
await mkdir15(path23.join(this.dir, "calendar"), { recursive: true });
|
|
12787
|
+
await mkdir15(path23.join(this.dir, "content-calendar"), { recursive: true });
|
|
12365
12788
|
await this.ensureFile(
|
|
12366
12789
|
this.prioritiesPath,
|
|
12367
12790
|
[
|
|
@@ -12392,22 +12815,22 @@ var SharedContextManager = class {
|
|
|
12392
12815
|
try {
|
|
12393
12816
|
await stat4(fp);
|
|
12394
12817
|
} catch {
|
|
12395
|
-
await
|
|
12818
|
+
await writeFile15(fp, content, "utf-8");
|
|
12396
12819
|
}
|
|
12397
12820
|
}
|
|
12398
12821
|
async readPriorities() {
|
|
12399
12822
|
try {
|
|
12400
|
-
return await
|
|
12823
|
+
return await readFile16(this.prioritiesPath, "utf-8");
|
|
12401
12824
|
} catch {
|
|
12402
12825
|
return "";
|
|
12403
12826
|
}
|
|
12404
12827
|
}
|
|
12405
12828
|
async readLatestRoundtable() {
|
|
12406
12829
|
try {
|
|
12407
|
-
const files = (await
|
|
12408
|
-
const fp = files[0] ?
|
|
12830
|
+
const files = (await readdir10(this.roundtableDir)).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
12831
|
+
const fp = files[0] ? path23.join(this.roundtableDir, files[0]) : null;
|
|
12409
12832
|
if (!fp) return "";
|
|
12410
|
-
return await
|
|
12833
|
+
return await readFile16(fp, "utf-8");
|
|
12411
12834
|
} catch {
|
|
12412
12835
|
return "";
|
|
12413
12836
|
}
|
|
@@ -12417,9 +12840,9 @@ var SharedContextManager = class {
|
|
|
12417
12840
|
const date = ymd(createdAt);
|
|
12418
12841
|
const time = createdAt.toISOString().slice(11, 19).replace(/:/g, "");
|
|
12419
12842
|
const slug = safeSlug(opts.title);
|
|
12420
|
-
const dir =
|
|
12843
|
+
const dir = path23.join(this.outputsDir, opts.agentId, date);
|
|
12421
12844
|
await mkdir15(dir, { recursive: true });
|
|
12422
|
-
const fp =
|
|
12845
|
+
const fp = path23.join(dir, `${time}-${slug}.md`);
|
|
12423
12846
|
const body = `---
|
|
12424
12847
|
kind: agent_output
|
|
12425
12848
|
agent: ${opts.agentId}
|
|
@@ -12428,7 +12851,7 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
|
|
|
12428
12851
|
---
|
|
12429
12852
|
|
|
12430
12853
|
` + opts.content.trimEnd() + "\n";
|
|
12431
|
-
await
|
|
12854
|
+
await writeFile15(fp, body, "utf-8");
|
|
12432
12855
|
return fp;
|
|
12433
12856
|
}
|
|
12434
12857
|
async appendFeedback(entry) {
|
|
@@ -12451,15 +12874,15 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
|
|
|
12451
12874
|
const maxChars = Math.max(2e3, opts.maxChars ?? 2e4);
|
|
12452
12875
|
const outputs = [];
|
|
12453
12876
|
try {
|
|
12454
|
-
const agents = await
|
|
12877
|
+
const agents = await readdir10(this.outputsDir, { withFileTypes: true });
|
|
12455
12878
|
for (const a of agents) {
|
|
12456
12879
|
if (!a.isDirectory()) continue;
|
|
12457
|
-
const dayDir =
|
|
12880
|
+
const dayDir = path23.join(this.outputsDir, a.name, date);
|
|
12458
12881
|
try {
|
|
12459
|
-
const files = (await
|
|
12882
|
+
const files = (await readdir10(dayDir)).filter((f) => f.endsWith(".md")).sort();
|
|
12460
12883
|
for (const f of files) {
|
|
12461
|
-
const p =
|
|
12462
|
-
const raw = await
|
|
12884
|
+
const p = path23.join(dayDir, f);
|
|
12885
|
+
const raw = await readFile16(p, "utf-8");
|
|
12463
12886
|
const title = (raw.match(/^title:\s*(.+)$/m)?.[1] ?? f).trim();
|
|
12464
12887
|
outputs.push({ agent: a.name, path: p, title, raw });
|
|
12465
12888
|
}
|
|
@@ -12470,7 +12893,7 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
|
|
|
12470
12893
|
}
|
|
12471
12894
|
const feedback = [];
|
|
12472
12895
|
try {
|
|
12473
|
-
const raw = await
|
|
12896
|
+
const raw = await readFile16(this.feedbackInboxPath, "utf-8");
|
|
12474
12897
|
for (const line of raw.split("\n")) {
|
|
12475
12898
|
if (!line.trim()) continue;
|
|
12476
12899
|
try {
|
|
@@ -12565,8 +12988,8 @@ ${body}`)
|
|
|
12565
12988
|
addedOverlapCount: semanticAddedOverlapCount
|
|
12566
12989
|
}
|
|
12567
12990
|
};
|
|
12568
|
-
const crossSignalsPath =
|
|
12569
|
-
await
|
|
12991
|
+
const crossSignalsPath = path23.join(this.crossSignalsDir, `${date}.json`);
|
|
12992
|
+
await writeFile15(crossSignalsPath, `${JSON.stringify(crossSignalReport, null, 2)}
|
|
12570
12993
|
`, "utf-8");
|
|
12571
12994
|
const overlapBullets = mergedOverlaps.length === 0 ? ["- No multi-agent topic overlap detected."] : mergedOverlaps.slice(0, 8).map((entry) => `- \`${entry.token}\` (${entry.agentCount} agents: ${entry.agents.join(", ")})`);
|
|
12572
12995
|
const md = [
|
|
@@ -12589,8 +13012,8 @@ ${body}`)
|
|
|
12589
13012
|
];
|
|
12590
13013
|
const out = md.join("\n");
|
|
12591
13014
|
const trimmed = out.length > maxChars ? out.slice(0, maxChars) + "\n\n...(trimmed)\n" : out;
|
|
12592
|
-
const roundtablePath =
|
|
12593
|
-
await
|
|
13015
|
+
const roundtablePath = path23.join(this.roundtableDir, `${date}.md`);
|
|
13016
|
+
await writeFile15(roundtablePath, trimmed, "utf-8");
|
|
12594
13017
|
log.info(`shared-context curated daily roundtable: ${roundtablePath}`);
|
|
12595
13018
|
return {
|
|
12596
13019
|
date,
|
|
@@ -12602,8 +13025,8 @@ ${body}`)
|
|
|
12602
13025
|
};
|
|
12603
13026
|
|
|
12604
13027
|
// src/compounding/engine.ts
|
|
12605
|
-
import { mkdir as mkdir16, readFile as
|
|
12606
|
-
import
|
|
13028
|
+
import { mkdir as mkdir16, readFile as readFile17, readdir as readdir11, writeFile as writeFile16 } from "fs/promises";
|
|
13029
|
+
import path24 from "path";
|
|
12607
13030
|
import os4 from "os";
|
|
12608
13031
|
function defaultTierMigrationCycleBudget(config, trigger) {
|
|
12609
13032
|
if (trigger === "extraction") {
|
|
@@ -12650,7 +13073,7 @@ function sharedContextDir(config) {
|
|
|
12650
13073
|
if (typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0) {
|
|
12651
13074
|
return config.sharedContextDir;
|
|
12652
13075
|
}
|
|
12653
|
-
return
|
|
13076
|
+
return path24.join(os4.homedir(), ".openclaw", "workspace", "shared-context");
|
|
12654
13077
|
}
|
|
12655
13078
|
function cadenceStaleWindowMs(cadence) {
|
|
12656
13079
|
switch (cadence) {
|
|
@@ -12669,16 +13092,16 @@ function cadenceStaleWindowMs(cadence) {
|
|
|
12669
13092
|
var CompoundingEngine = class {
|
|
12670
13093
|
constructor(config) {
|
|
12671
13094
|
this.config = config;
|
|
12672
|
-
this.weeklyDir =
|
|
12673
|
-
this.rubricsPath =
|
|
12674
|
-
this.mistakesPath =
|
|
12675
|
-
this.feedbackInboxPath =
|
|
12676
|
-
this.identityAnchorPath =
|
|
12677
|
-
this.identityIncidentsDir =
|
|
12678
|
-
this.identityAuditWeeklyDir =
|
|
12679
|
-
this.identityAuditMonthlyDir =
|
|
12680
|
-
this.identityImprovementLoopsPath =
|
|
12681
|
-
this.memoryActionEventsPath =
|
|
13095
|
+
this.weeklyDir = path24.join(config.memoryDir, "compounding", "weekly");
|
|
13096
|
+
this.rubricsPath = path24.join(config.memoryDir, "compounding", "rubrics.md");
|
|
13097
|
+
this.mistakesPath = path24.join(config.memoryDir, "compounding", "mistakes.json");
|
|
13098
|
+
this.feedbackInboxPath = path24.join(sharedContextDir(config), "feedback", "inbox.jsonl");
|
|
13099
|
+
this.identityAnchorPath = path24.join(config.memoryDir, "identity", "identity-anchor.md");
|
|
13100
|
+
this.identityIncidentsDir = path24.join(config.memoryDir, "identity", "incidents");
|
|
13101
|
+
this.identityAuditWeeklyDir = path24.join(config.memoryDir, "identity", "audits", "weekly");
|
|
13102
|
+
this.identityAuditMonthlyDir = path24.join(config.memoryDir, "identity", "audits", "monthly");
|
|
13103
|
+
this.identityImprovementLoopsPath = path24.join(config.memoryDir, "identity", "improvement-loops.md");
|
|
13104
|
+
this.memoryActionEventsPath = path24.join(config.memoryDir, "state", "memory-actions.jsonl");
|
|
12682
13105
|
}
|
|
12683
13106
|
weeklyDir;
|
|
12684
13107
|
rubricsPath;
|
|
@@ -12692,8 +13115,8 @@ var CompoundingEngine = class {
|
|
|
12692
13115
|
memoryActionEventsPath;
|
|
12693
13116
|
async ensureDirs() {
|
|
12694
13117
|
await mkdir16(this.weeklyDir, { recursive: true });
|
|
12695
|
-
await mkdir16(
|
|
12696
|
-
await mkdir16(
|
|
13118
|
+
await mkdir16(path24.dirname(this.mistakesPath), { recursive: true });
|
|
13119
|
+
await mkdir16(path24.dirname(this.rubricsPath), { recursive: true });
|
|
12697
13120
|
}
|
|
12698
13121
|
async synthesizeWeekly(opts) {
|
|
12699
13122
|
await this.ensureDirs();
|
|
@@ -12704,12 +13127,12 @@ var CompoundingEngine = class {
|
|
|
12704
13127
|
const promotionCandidates = this.config.compoundingSemanticEnabled ? this.derivePromotionCandidates(outcomeSummary) : [];
|
|
12705
13128
|
const mistakes = this.buildMistakes(entries, actionPatterns);
|
|
12706
13129
|
const continuity = this.config.continuityAuditEnabled ? await this.readContinuityAuditReferences(weekId) : { monthId: monthIdFromIsoWeek(weekId), weeklyPath: null, monthlyPath: null };
|
|
12707
|
-
const reportPath =
|
|
13130
|
+
const reportPath = path24.join(this.weeklyDir, `${weekId}.md`);
|
|
12708
13131
|
const md = this.formatWeeklyReport(weekId, entries, mistakes.patterns, mistakes.details, continuity, outcomeSummary, promotionCandidates);
|
|
12709
|
-
await
|
|
13132
|
+
await writeFile16(reportPath, md, "utf-8");
|
|
12710
13133
|
const rubrics = this.formatRubrics(entries, outcomeSummary);
|
|
12711
|
-
await
|
|
12712
|
-
await
|
|
13134
|
+
await writeFile16(this.rubricsPath, rubrics, "utf-8");
|
|
13135
|
+
await writeFile16(
|
|
12713
13136
|
this.mistakesPath,
|
|
12714
13137
|
JSON.stringify({ updatedAt: mistakes.updatedAt, patterns: mistakes.patterns }, null, 2) + "\n",
|
|
12715
13138
|
"utf-8"
|
|
@@ -12790,13 +13213,13 @@ var CompoundingEngine = class {
|
|
|
12790
13213
|
];
|
|
12791
13214
|
const dir = period === "weekly" ? this.identityAuditWeeklyDir : this.identityAuditMonthlyDir;
|
|
12792
13215
|
await mkdir16(dir, { recursive: true });
|
|
12793
|
-
const reportPath =
|
|
12794
|
-
await
|
|
13216
|
+
const reportPath = path24.join(dir, `${key}.md`);
|
|
13217
|
+
await writeFile16(reportPath, lines.join("\n"), "utf-8");
|
|
12795
13218
|
return { period, key, reportPath };
|
|
12796
13219
|
}
|
|
12797
13220
|
async readMistakes() {
|
|
12798
13221
|
try {
|
|
12799
|
-
const raw = await
|
|
13222
|
+
const raw = await readFile17(this.mistakesPath, "utf-8");
|
|
12800
13223
|
const parsed = JSON.parse(raw);
|
|
12801
13224
|
if (!parsed || !Array.isArray(parsed.patterns)) return null;
|
|
12802
13225
|
return parsed;
|
|
@@ -12810,7 +13233,7 @@ var CompoundingEngine = class {
|
|
|
12810
13233
|
async readFeedbackEntriesForWeek(weekId) {
|
|
12811
13234
|
const out = [];
|
|
12812
13235
|
try {
|
|
12813
|
-
const raw = await
|
|
13236
|
+
const raw = await readFile17(this.feedbackInboxPath, "utf-8");
|
|
12814
13237
|
const lines = raw.split("\n");
|
|
12815
13238
|
for (let idx = 0; idx < lines.length; idx += 1) {
|
|
12816
13239
|
const line = lines[idx];
|
|
@@ -12852,7 +13275,7 @@ var CompoundingEngine = class {
|
|
|
12852
13275
|
async readActionEventsForWeek(weekId) {
|
|
12853
13276
|
const out = [];
|
|
12854
13277
|
try {
|
|
12855
|
-
const raw = await
|
|
13278
|
+
const raw = await readFile17(this.memoryActionEventsPath, "utf-8");
|
|
12856
13279
|
const lines = raw.split("\n");
|
|
12857
13280
|
for (let idx = 0; idx < lines.length; idx += 1) {
|
|
12858
13281
|
const line = lines[idx];
|
|
@@ -12890,7 +13313,7 @@ var CompoundingEngine = class {
|
|
|
12890
13313
|
else if (event.outcome === "skipped") acc.counts.skipped += 1;
|
|
12891
13314
|
else if (event.outcome === "failed") acc.counts.failed += 1;
|
|
12892
13315
|
else acc.counts.unknown += 1;
|
|
12893
|
-
acc.provenance.add(`${
|
|
13316
|
+
acc.provenance.add(`${path24.basename(this.memoryActionEventsPath)}:L${event.line}`);
|
|
12894
13317
|
byAction.set(key, acc);
|
|
12895
13318
|
}
|
|
12896
13319
|
const out = [];
|
|
@@ -12928,7 +13351,7 @@ var CompoundingEngine = class {
|
|
|
12928
13351
|
const patterns = [];
|
|
12929
13352
|
for (const wrapped of entries) {
|
|
12930
13353
|
const e = wrapped.entry;
|
|
12931
|
-
const provenance = [`${
|
|
13354
|
+
const provenance = [`${path24.basename(wrapped.sourcePath)}:L${wrapped.sourceLine}#${wrapped.entryId}`];
|
|
12932
13355
|
if (e.learning && e.learning.trim().length > 0) {
|
|
12933
13356
|
patterns.push({ pattern: `${e.agent}: ${e.learning.trim()}`, provenance });
|
|
12934
13357
|
continue;
|
|
@@ -12938,7 +13361,7 @@ var CompoundingEngine = class {
|
|
|
12938
13361
|
}
|
|
12939
13362
|
}
|
|
12940
13363
|
for (const p of actionPatterns) {
|
|
12941
|
-
patterns.push({ pattern: p, provenance: [`${
|
|
13364
|
+
patterns.push({ pattern: p, provenance: [`${path24.basename(this.memoryActionEventsPath)}:*`] });
|
|
12942
13365
|
}
|
|
12943
13366
|
const byPattern = /* @__PURE__ */ new Map();
|
|
12944
13367
|
for (const p of patterns) {
|
|
@@ -12978,7 +13401,7 @@ var CompoundingEngine = class {
|
|
|
12978
13401
|
lines.push(`- approved: ${approved}`);
|
|
12979
13402
|
lines.push(`- approved_with_feedback: ${awf}`);
|
|
12980
13403
|
lines.push(`- rejected: ${rejected}`);
|
|
12981
|
-
const provenance = list.slice(0, 3).map((e) => `${
|
|
13404
|
+
const provenance = list.slice(0, 3).map((e) => `${path24.basename(e.sourcePath)}:L${e.sourceLine}#${e.entryId}`);
|
|
12982
13405
|
if (provenance.length > 0) {
|
|
12983
13406
|
lines.push(`- provenance: ${provenance.join(", ")}`);
|
|
12984
13407
|
}
|
|
@@ -13069,7 +13492,7 @@ var CompoundingEngine = class {
|
|
|
13069
13492
|
} else {
|
|
13070
13493
|
for (const item of learnings) {
|
|
13071
13494
|
const note = (item.entry.learning && item.entry.learning.trim().length > 0 ? item.entry.learning : item.entry.reason).trim();
|
|
13072
|
-
lines.push(`- ${note} _(source: ${
|
|
13495
|
+
lines.push(`- ${note} _(source: ${path24.basename(item.sourcePath)}:L${item.sourceLine}#${item.entryId})_`);
|
|
13073
13496
|
}
|
|
13074
13497
|
}
|
|
13075
13498
|
lines.push("");
|
|
@@ -13090,7 +13513,7 @@ var CompoundingEngine = class {
|
|
|
13090
13513
|
}
|
|
13091
13514
|
async readNonEmptyFile(filePath) {
|
|
13092
13515
|
try {
|
|
13093
|
-
const raw = await
|
|
13516
|
+
const raw = await readFile17(filePath, "utf-8");
|
|
13094
13517
|
return raw.trim().length > 0;
|
|
13095
13518
|
} catch {
|
|
13096
13519
|
return false;
|
|
@@ -13098,7 +13521,7 @@ var CompoundingEngine = class {
|
|
|
13098
13521
|
}
|
|
13099
13522
|
async readOptionalFile(filePath) {
|
|
13100
13523
|
try {
|
|
13101
|
-
const raw = await
|
|
13524
|
+
const raw = await readFile17(filePath, "utf-8");
|
|
13102
13525
|
return raw.trim().length > 0 ? raw : null;
|
|
13103
13526
|
} catch {
|
|
13104
13527
|
return null;
|
|
@@ -13110,13 +13533,13 @@ var CompoundingEngine = class {
|
|
|
13110
13533
|
if (cappedLimit === 0) return [];
|
|
13111
13534
|
const incidents = [];
|
|
13112
13535
|
try {
|
|
13113
|
-
const names = await
|
|
13536
|
+
const names = await readdir11(this.identityIncidentsDir);
|
|
13114
13537
|
const files = names.filter((n) => n.endsWith(".md")).sort().reverse();
|
|
13115
13538
|
for (const file of files) {
|
|
13116
13539
|
if (incidents.length >= cappedLimit) break;
|
|
13117
|
-
const filePath =
|
|
13540
|
+
const filePath = path24.join(this.identityIncidentsDir, file);
|
|
13118
13541
|
try {
|
|
13119
|
-
const raw = await
|
|
13542
|
+
const raw = await readFile17(filePath, "utf-8");
|
|
13120
13543
|
const parsed = parseContinuityIncident(raw);
|
|
13121
13544
|
if (!parsed) continue;
|
|
13122
13545
|
if (state && parsed.state !== state) continue;
|
|
@@ -13130,8 +13553,8 @@ var CompoundingEngine = class {
|
|
|
13130
13553
|
}
|
|
13131
13554
|
async readContinuityAuditReferences(weekId) {
|
|
13132
13555
|
const monthId = monthIdFromIsoWeek(weekId);
|
|
13133
|
-
const weeklyPath =
|
|
13134
|
-
const monthlyPath =
|
|
13556
|
+
const weeklyPath = path24.join(this.identityAuditWeeklyDir, `${weekId}.md`);
|
|
13557
|
+
const monthlyPath = path24.join(this.identityAuditMonthlyDir, `${monthId}.md`);
|
|
13135
13558
|
const weeklyExists = await this.readNonEmptyFile(weeklyPath);
|
|
13136
13559
|
const monthlyExists = await this.readNonEmptyFile(monthlyPath);
|
|
13137
13560
|
return {
|
|
@@ -13145,7 +13568,7 @@ var CompoundingEngine = class {
|
|
|
13145
13568
|
|
|
13146
13569
|
// src/tier-migration.ts
|
|
13147
13570
|
import { appendFile as appendFile6, mkdir as mkdir17 } from "fs/promises";
|
|
13148
|
-
import
|
|
13571
|
+
import path25 from "path";
|
|
13149
13572
|
var TierMigrationExecutor = class {
|
|
13150
13573
|
storage;
|
|
13151
13574
|
qmd;
|
|
@@ -13159,7 +13582,7 @@ var TierMigrationExecutor = class {
|
|
|
13159
13582
|
this.hotCollection = options.hotCollection;
|
|
13160
13583
|
this.coldCollection = options.coldCollection;
|
|
13161
13584
|
this.autoEmbed = options.autoEmbed === true;
|
|
13162
|
-
this.journalPath = options.journalPath ??
|
|
13585
|
+
this.journalPath = options.journalPath ?? path25.join(this.storage.dir, "state", "tier-migration-journal.jsonl");
|
|
13163
13586
|
}
|
|
13164
13587
|
async migrateMemory(request) {
|
|
13165
13588
|
const { memory, fromTier, toTier, reason } = request;
|
|
@@ -13212,7 +13635,7 @@ var TierMigrationExecutor = class {
|
|
|
13212
13635
|
reason: result.reason,
|
|
13213
13636
|
targetPath: result.targetPath
|
|
13214
13637
|
};
|
|
13215
|
-
await mkdir17(
|
|
13638
|
+
await mkdir17(path25.dirname(this.journalPath), { recursive: true });
|
|
13216
13639
|
await appendFile6(this.journalPath, `${JSON.stringify(entry)}
|
|
13217
13640
|
`, "utf-8");
|
|
13218
13641
|
}
|
|
@@ -13378,8 +13801,8 @@ function selectRouteRule(text, rules, options) {
|
|
|
13378
13801
|
}
|
|
13379
13802
|
|
|
13380
13803
|
// src/routing/store.ts
|
|
13381
|
-
import { lstat, mkdir as mkdir18, readFile as
|
|
13382
|
-
import
|
|
13804
|
+
import { lstat, mkdir as mkdir18, readFile as readFile18, realpath, rename as rename2, rm as rm2, stat as stat5, writeFile as writeFile17 } from "fs/promises";
|
|
13805
|
+
import path26 from "path";
|
|
13383
13806
|
import { createHash as createHash4 } from "crypto";
|
|
13384
13807
|
function defaultState() {
|
|
13385
13808
|
return {
|
|
@@ -13398,14 +13821,14 @@ function stableRuleId(rule) {
|
|
|
13398
13821
|
return `route-${createHash4("sha256").update(seed).digest("hex").slice(0, 12)}`;
|
|
13399
13822
|
}
|
|
13400
13823
|
function resolveStatePath(memoryDir, stateFile) {
|
|
13401
|
-
const root =
|
|
13402
|
-
const defaultPath =
|
|
13403
|
-
if (
|
|
13404
|
-
const absolute =
|
|
13405
|
-
return absolute.startsWith(root +
|
|
13824
|
+
const root = path26.resolve(memoryDir);
|
|
13825
|
+
const defaultPath = path26.join(root, "state", "routing-rules.json");
|
|
13826
|
+
if (path26.isAbsolute(stateFile)) {
|
|
13827
|
+
const absolute = path26.resolve(stateFile);
|
|
13828
|
+
return absolute.startsWith(root + path26.sep) ? absolute : defaultPath;
|
|
13406
13829
|
}
|
|
13407
|
-
const resolved =
|
|
13408
|
-
return resolved.startsWith(root +
|
|
13830
|
+
const resolved = path26.resolve(root, stateFile);
|
|
13831
|
+
return resolved.startsWith(root + path26.sep) ? resolved : defaultPath;
|
|
13409
13832
|
}
|
|
13410
13833
|
function normalizeRule(rule, options) {
|
|
13411
13834
|
if (!rule || typeof rule !== "object") return null;
|
|
@@ -13438,7 +13861,7 @@ var RoutingRulesStore = class {
|
|
|
13438
13861
|
lockPath;
|
|
13439
13862
|
writeQueue = Promise.resolve();
|
|
13440
13863
|
constructor(memoryDir, stateFile = "state/routing-rules.json") {
|
|
13441
|
-
this.memoryRoot =
|
|
13864
|
+
this.memoryRoot = path26.resolve(memoryDir);
|
|
13442
13865
|
this.statePath = resolveStatePath(memoryDir, stateFile);
|
|
13443
13866
|
this.lockPath = `${this.statePath}.lock`;
|
|
13444
13867
|
}
|
|
@@ -13476,7 +13899,7 @@ var RoutingRulesStore = class {
|
|
|
13476
13899
|
await this.withWriteLock(async () => {
|
|
13477
13900
|
const payload = defaultState();
|
|
13478
13901
|
await this.assertStatePathScoped();
|
|
13479
|
-
await
|
|
13902
|
+
await writeFile17(this.statePath, JSON.stringify(payload, null, 2), "utf-8");
|
|
13480
13903
|
});
|
|
13481
13904
|
}
|
|
13482
13905
|
dedupeById(rules) {
|
|
@@ -13489,7 +13912,7 @@ var RoutingRulesStore = class {
|
|
|
13489
13912
|
async readPersistedRules() {
|
|
13490
13913
|
try {
|
|
13491
13914
|
await this.assertStatePathScoped();
|
|
13492
|
-
const raw = await
|
|
13915
|
+
const raw = await readFile18(this.statePath, "utf-8");
|
|
13493
13916
|
const parsed = JSON.parse(raw);
|
|
13494
13917
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.rules)) return [];
|
|
13495
13918
|
const normalized = parsed.rules.map((rule) => normalizeRule(rule)).filter((rule) => rule !== null);
|
|
@@ -13510,7 +13933,7 @@ var RoutingRulesStore = class {
|
|
|
13510
13933
|
const tmpPath = `${this.statePath}.tmp-${process.pid}-${Date.now()}`;
|
|
13511
13934
|
try {
|
|
13512
13935
|
await this.assertStatePathScoped();
|
|
13513
|
-
await
|
|
13936
|
+
await writeFile17(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
13514
13937
|
await rename2(tmpPath, this.statePath);
|
|
13515
13938
|
} catch (err) {
|
|
13516
13939
|
log.debug(`routing rules write failed: ${err}`);
|
|
@@ -13544,7 +13967,7 @@ var RoutingRulesStore = class {
|
|
|
13544
13967
|
const timeoutMs = 5e3;
|
|
13545
13968
|
let unexpectedLockError = null;
|
|
13546
13969
|
await this.assertStatePathScoped();
|
|
13547
|
-
await mkdir18(
|
|
13970
|
+
await mkdir18(path26.dirname(this.lockPath), { recursive: true });
|
|
13548
13971
|
while (Date.now() - start < timeoutMs) {
|
|
13549
13972
|
try {
|
|
13550
13973
|
await mkdir18(this.lockPath);
|
|
@@ -13579,12 +14002,12 @@ var RoutingRulesStore = class {
|
|
|
13579
14002
|
async assertStatePathScoped() {
|
|
13580
14003
|
await mkdir18(this.memoryRoot, { recursive: true });
|
|
13581
14004
|
const canonicalRoot = await realpath(this.memoryRoot);
|
|
13582
|
-
const canonicalParent = await this.canonicalizePathWithoutCreating(
|
|
13583
|
-
const canonicalStatePath =
|
|
14005
|
+
const canonicalParent = await this.canonicalizePathWithoutCreating(path26.dirname(this.statePath));
|
|
14006
|
+
const canonicalStatePath = path26.join(canonicalParent, path26.basename(this.statePath));
|
|
13584
14007
|
if (!this.isPathInside(canonicalRoot, canonicalStatePath)) {
|
|
13585
14008
|
throw new Error(`routing rules state path escaped memoryDir: ${canonicalStatePath}`);
|
|
13586
14009
|
}
|
|
13587
|
-
await mkdir18(
|
|
14010
|
+
await mkdir18(path26.dirname(this.statePath), { recursive: true });
|
|
13588
14011
|
try {
|
|
13589
14012
|
const stateStats = await lstat(this.statePath);
|
|
13590
14013
|
if (stateStats.isSymbolicLink()) {
|
|
@@ -13601,28 +14024,28 @@ var RoutingRulesStore = class {
|
|
|
13601
14024
|
}
|
|
13602
14025
|
}
|
|
13603
14026
|
isPathInside(root, candidate) {
|
|
13604
|
-
const normalizedRoot =
|
|
13605
|
-
const normalizedCandidate =
|
|
14027
|
+
const normalizedRoot = path26.resolve(root);
|
|
14028
|
+
const normalizedCandidate = path26.resolve(candidate);
|
|
13606
14029
|
if (normalizedCandidate === normalizedRoot) return true;
|
|
13607
|
-
if (normalizedRoot ===
|
|
14030
|
+
if (normalizedRoot === path26.parse(normalizedRoot).root) {
|
|
13608
14031
|
return normalizedCandidate.startsWith(normalizedRoot);
|
|
13609
14032
|
}
|
|
13610
|
-
return normalizedCandidate.startsWith(`${normalizedRoot}${
|
|
14033
|
+
return normalizedCandidate.startsWith(`${normalizedRoot}${path26.sep}`);
|
|
13611
14034
|
}
|
|
13612
14035
|
async canonicalizePathWithoutCreating(targetPath) {
|
|
13613
|
-
const absoluteTarget =
|
|
14036
|
+
const absoluteTarget = path26.resolve(targetPath);
|
|
13614
14037
|
let probe = absoluteTarget;
|
|
13615
14038
|
while (true) {
|
|
13616
14039
|
try {
|
|
13617
14040
|
const canonicalProbe = await realpath(probe);
|
|
13618
|
-
const remainder =
|
|
13619
|
-
return
|
|
14041
|
+
const remainder = path26.relative(probe, absoluteTarget);
|
|
14042
|
+
return path26.resolve(canonicalProbe, remainder);
|
|
13620
14043
|
} catch (err) {
|
|
13621
14044
|
const code = err.code;
|
|
13622
14045
|
if (code !== "ENOENT") {
|
|
13623
14046
|
throw err;
|
|
13624
14047
|
}
|
|
13625
|
-
const parent =
|
|
14048
|
+
const parent = path26.dirname(probe);
|
|
13626
14049
|
if (parent === probe) {
|
|
13627
14050
|
return absoluteTarget;
|
|
13628
14051
|
}
|
|
@@ -13633,8 +14056,8 @@ var RoutingRulesStore = class {
|
|
|
13633
14056
|
};
|
|
13634
14057
|
|
|
13635
14058
|
// src/policy-runtime.ts
|
|
13636
|
-
import
|
|
13637
|
-
import { mkdir as mkdir19, readFile as
|
|
14059
|
+
import path27 from "path";
|
|
14060
|
+
import { mkdir as mkdir19, readFile as readFile19, rename as rename3, writeFile as writeFile18 } from "fs/promises";
|
|
13638
14061
|
var RUNTIME_POLICY_VERSION = 1;
|
|
13639
14062
|
var RUNTIME_POLICY_FILE = "policy-runtime.json";
|
|
13640
14063
|
var RUNTIME_POLICY_PREV_FILE = "policy-runtime.prev.json";
|
|
@@ -13663,7 +14086,7 @@ function isRuntimeParameter(parameter) {
|
|
|
13663
14086
|
}
|
|
13664
14087
|
async function readRuntimePolicySnapshot(filePath, options) {
|
|
13665
14088
|
try {
|
|
13666
|
-
const raw = await
|
|
14089
|
+
const raw = await readFile19(filePath, "utf-8");
|
|
13667
14090
|
const parsed = JSON.parse(raw);
|
|
13668
14091
|
if (!parsed || typeof parsed.version !== "number" || parsed.version < 1 || typeof parsed.updatedAt !== "string" || !parsed.values || typeof parsed.values !== "object" || typeof parsed.sourceAdjustmentCount !== "number" || parsed.sourceAdjustmentCount < 0) {
|
|
13669
14092
|
return null;
|
|
@@ -13682,8 +14105,8 @@ async function readRuntimePolicySnapshot(filePath, options) {
|
|
|
13682
14105
|
}
|
|
13683
14106
|
async function writeSnapshotAtomic(filePath, snapshot) {
|
|
13684
14107
|
const tempPath = `${filePath}.tmp`;
|
|
13685
|
-
await mkdir19(
|
|
13686
|
-
await
|
|
14108
|
+
await mkdir19(path27.dirname(filePath), { recursive: true });
|
|
14109
|
+
await writeFile18(tempPath, `${JSON.stringify(snapshot, null, 2)}
|
|
13687
14110
|
`, "utf-8");
|
|
13688
14111
|
await rename3(tempPath, filePath);
|
|
13689
14112
|
}
|
|
@@ -13691,9 +14114,9 @@ var PolicyRuntimeManager = class {
|
|
|
13691
14114
|
constructor(memoryDir, config) {
|
|
13692
14115
|
this.memoryDir = memoryDir;
|
|
13693
14116
|
this.config = config;
|
|
13694
|
-
const stateDir2 =
|
|
13695
|
-
this.runtimePath =
|
|
13696
|
-
this.runtimePrevPath =
|
|
14117
|
+
const stateDir2 = path27.join(memoryDir, "state");
|
|
14118
|
+
this.runtimePath = path27.join(stateDir2, RUNTIME_POLICY_FILE);
|
|
14119
|
+
this.runtimePrevPath = path27.join(stateDir2, RUNTIME_POLICY_PREV_FILE);
|
|
13697
14120
|
}
|
|
13698
14121
|
runtimePath;
|
|
13699
14122
|
runtimePrevPath;
|
|
@@ -13977,11 +14400,11 @@ function mergeGraphExpandedResults(primary, expanded) {
|
|
|
13977
14400
|
return Array.from(mergedByPath.values());
|
|
13978
14401
|
}
|
|
13979
14402
|
function graphPathRelativeToStorage(storageDir, candidatePath) {
|
|
13980
|
-
const absolutePath =
|
|
13981
|
-
const rel =
|
|
14403
|
+
const absolutePath = path28.isAbsolute(candidatePath) ? candidatePath : path28.resolve(storageDir, candidatePath);
|
|
14404
|
+
const rel = path28.relative(storageDir, absolutePath);
|
|
13982
14405
|
if (!rel || rel === ".") return null;
|
|
13983
14406
|
if (rel.startsWith("..")) return null;
|
|
13984
|
-
return rel.split(
|
|
14407
|
+
return rel.split(path28.sep).join("/");
|
|
13985
14408
|
}
|
|
13986
14409
|
function normalizeGraphActivationScore(score) {
|
|
13987
14410
|
const bounded = Number.isFinite(score) && score > 0 ? score : 0;
|
|
@@ -14056,7 +14479,7 @@ function buildMemoryPathById(allMemsForGraph, storageDir) {
|
|
|
14056
14479
|
for (const mem of allMemsForGraph ?? []) {
|
|
14057
14480
|
const id = mem.frontmatter.id;
|
|
14058
14481
|
if (!id) continue;
|
|
14059
|
-
pathById.set(id,
|
|
14482
|
+
pathById.set(id, path28.relative(storageDir, mem.path));
|
|
14060
14483
|
}
|
|
14061
14484
|
return pathById;
|
|
14062
14485
|
}
|
|
@@ -14064,7 +14487,7 @@ function appendMemoryToGraphContext(options) {
|
|
|
14064
14487
|
if (!Array.isArray(options.allMemsForGraph)) return;
|
|
14065
14488
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
14066
14489
|
options.allMemsForGraph.push({
|
|
14067
|
-
path:
|
|
14490
|
+
path: path28.join(options.storageDir, options.memoryRelPath),
|
|
14068
14491
|
content: options.content,
|
|
14069
14492
|
frontmatter: {
|
|
14070
14493
|
id: options.memoryId,
|
|
@@ -14084,15 +14507,15 @@ function resolvePersistedMemoryRelativePath(options) {
|
|
|
14084
14507
|
const persisted = options.pathById.get(options.memoryId);
|
|
14085
14508
|
if (persisted) return persisted;
|
|
14086
14509
|
if (options.category === "correction") {
|
|
14087
|
-
return
|
|
14510
|
+
return path28.join("corrections", `${options.memoryId}.md`);
|
|
14088
14511
|
}
|
|
14089
14512
|
const idParts = options.memoryId.split("-");
|
|
14090
14513
|
const maybeTimestamp = Number(idParts[1]);
|
|
14091
14514
|
if (Number.isFinite(maybeTimestamp) && maybeTimestamp > 0) {
|
|
14092
14515
|
const day = new Date(maybeTimestamp).toISOString().slice(0, 10);
|
|
14093
|
-
return
|
|
14516
|
+
return path28.join("facts", day, `${options.memoryId}.md`);
|
|
14094
14517
|
}
|
|
14095
|
-
return
|
|
14518
|
+
return path28.join("facts", `${options.memoryId}.md`);
|
|
14096
14519
|
}
|
|
14097
14520
|
var Orchestrator = class _Orchestrator {
|
|
14098
14521
|
storage;
|
|
@@ -14202,7 +14625,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
14202
14625
|
this.compounding = config.compoundingEnabled ? new CompoundingEngine(config) : void 0;
|
|
14203
14626
|
this.buffer = new SmartBuffer(config, this.storage);
|
|
14204
14627
|
this.transcript = new TranscriptManager(config);
|
|
14205
|
-
this.conversationIndexDir =
|
|
14628
|
+
this.conversationIndexDir = path28.join(config.memoryDir, "conversation-index", "chunks");
|
|
14206
14629
|
this.modelRegistry = new ModelRegistry(config.memoryDir);
|
|
14207
14630
|
this.relevance = new RelevanceStore(config.memoryDir);
|
|
14208
14631
|
this.negatives = new NegativeExampleStore(config.memoryDir);
|
|
@@ -14219,7 +14642,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
14219
14642
|
this.localLlm = new LocalLlmClient(config, this.modelRegistry);
|
|
14220
14643
|
this.extraction = new ExtractionEngine(config, this.localLlm, config.gatewayConfig, this.modelRegistry);
|
|
14221
14644
|
this.threading = new ThreadingManager(
|
|
14222
|
-
|
|
14645
|
+
path28.join(config.memoryDir, "threads"),
|
|
14223
14646
|
config.threadingGapMinutes
|
|
14224
14647
|
);
|
|
14225
14648
|
this.tmtBuilder = new TmtBuilder(config.memoryDir, {
|
|
@@ -14388,7 +14811,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
14388
14811
|
await this.sessionObserver.load();
|
|
14389
14812
|
this.runtimePolicyValues = await this.policyRuntime.loadRuntimeValues();
|
|
14390
14813
|
if (this.config.factDeduplicationEnabled) {
|
|
14391
|
-
const stateDir2 =
|
|
14814
|
+
const stateDir2 = path28.join(this.config.memoryDir, "state");
|
|
14392
14815
|
this.contentHashIndex = new ContentHashIndex(stateDir2);
|
|
14393
14816
|
await this.contentHashIndex.load();
|
|
14394
14817
|
log.info(`content-hash dedup: loaded ${this.contentHashIndex.size} hashes`);
|
|
@@ -14426,7 +14849,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
14426
14849
|
if (available) {
|
|
14427
14850
|
log.info(`Conversation index QMD: available ${this.conversationQmd.debugStatus()}`);
|
|
14428
14851
|
const collectionState = await this.conversationQmd.ensureCollection(
|
|
14429
|
-
|
|
14852
|
+
path28.join(this.config.memoryDir, "conversation-index")
|
|
14430
14853
|
);
|
|
14431
14854
|
if (collectionState === "missing") {
|
|
14432
14855
|
this.config.conversationIndexEnabled = false;
|
|
@@ -14480,12 +14903,12 @@ var Orchestrator = class _Orchestrator {
|
|
|
14480
14903
|
this.lastFileHygieneRunAtMs = now;
|
|
14481
14904
|
if (hygiene.rotateEnabled) {
|
|
14482
14905
|
for (const rel of hygiene.rotatePaths) {
|
|
14483
|
-
const abs =
|
|
14906
|
+
const abs = path28.isAbsolute(rel) ? rel : path28.join(this.config.workspaceDir, rel);
|
|
14484
14907
|
try {
|
|
14485
|
-
const raw = await
|
|
14908
|
+
const raw = await readFile20(abs, "utf-8");
|
|
14486
14909
|
if (raw.length > hygiene.rotateMaxBytes) {
|
|
14487
|
-
const archiveDir =
|
|
14488
|
-
const base =
|
|
14910
|
+
const archiveDir = path28.join(this.config.workspaceDir, hygiene.archiveDir);
|
|
14911
|
+
const base = path28.basename(abs);
|
|
14489
14912
|
const prefix = base.toUpperCase().replace(/\.MD$/i, "").replace(/[^A-Z0-9]+/g, "-") || "FILE";
|
|
14490
14913
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
14491
14914
|
filePath: abs,
|
|
@@ -14493,7 +14916,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
14493
14916
|
archivePrefix: prefix,
|
|
14494
14917
|
keepTailChars: hygiene.rotateKeepTailChars
|
|
14495
14918
|
});
|
|
14496
|
-
await
|
|
14919
|
+
await writeFile19(abs, newContent, "utf-8");
|
|
14497
14920
|
}
|
|
14498
14921
|
} catch {
|
|
14499
14922
|
}
|
|
@@ -14510,8 +14933,8 @@ var Orchestrator = class _Orchestrator {
|
|
|
14510
14933
|
log.warn(w.message);
|
|
14511
14934
|
}
|
|
14512
14935
|
if (hygiene.warningsLogEnabled && warnings.length > 0) {
|
|
14513
|
-
const fp =
|
|
14514
|
-
await mkdir20(
|
|
14936
|
+
const fp = path28.join(this.config.memoryDir, hygiene.warningsLogPath);
|
|
14937
|
+
await mkdir20(path28.dirname(fp), { recursive: true });
|
|
14515
14938
|
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
14516
14939
|
const block = `
|
|
14517
14940
|
|
|
@@ -14520,11 +14943,11 @@ var Orchestrator = class _Orchestrator {
|
|
|
14520
14943
|
` + warnings.map((w) => `- ${w.message}`).join("\n") + "\n";
|
|
14521
14944
|
let existing = "";
|
|
14522
14945
|
try {
|
|
14523
|
-
existing = await
|
|
14946
|
+
existing = await readFile20(fp, "utf-8");
|
|
14524
14947
|
} catch {
|
|
14525
14948
|
existing = "# Engram File Hygiene Warnings\n";
|
|
14526
14949
|
}
|
|
14527
|
-
await
|
|
14950
|
+
await writeFile19(fp, existing + block, "utf-8");
|
|
14528
14951
|
}
|
|
14529
14952
|
}
|
|
14530
14953
|
}
|
|
@@ -14600,9 +15023,9 @@ var Orchestrator = class _Orchestrator {
|
|
|
14600
15023
|
}
|
|
14601
15024
|
async getLastGraphRecallSnapshot(namespace) {
|
|
14602
15025
|
const storage = await this.getStorage(namespace);
|
|
14603
|
-
const snapshotPath =
|
|
15026
|
+
const snapshotPath = path28.join(storage.dir, "state", "last_graph_recall.json");
|
|
14604
15027
|
try {
|
|
14605
|
-
const raw = await
|
|
15028
|
+
const raw = await readFile20(snapshotPath, "utf-8");
|
|
14606
15029
|
const parsed = JSON.parse(raw);
|
|
14607
15030
|
if (!parsed || typeof parsed !== "object") return null;
|
|
14608
15031
|
return {
|
|
@@ -14668,10 +15091,10 @@ ${r.snippet.trim()}
|
|
|
14668
15091
|
}
|
|
14669
15092
|
async countConversationChunkDocs(dir) {
|
|
14670
15093
|
try {
|
|
14671
|
-
const entries = await
|
|
15094
|
+
const entries = await readdir12(dir, { withFileTypes: true });
|
|
14672
15095
|
let total = 0;
|
|
14673
15096
|
for (const entry of entries) {
|
|
14674
|
-
const fullPath =
|
|
15097
|
+
const fullPath = path28.join(dir, entry.name);
|
|
14675
15098
|
if (entry.isDirectory()) {
|
|
14676
15099
|
total += await this.countConversationChunkDocs(fullPath);
|
|
14677
15100
|
continue;
|
|
@@ -14719,6 +15142,9 @@ ${r.snippet.trim()}
|
|
|
14719
15142
|
qmdAvailable
|
|
14720
15143
|
};
|
|
14721
15144
|
}
|
|
15145
|
+
async getRecoverySummary(sessionKey) {
|
|
15146
|
+
return this.transcript.getRecoverySummary(sessionKey);
|
|
15147
|
+
}
|
|
14722
15148
|
async updateConversationIndex(sessionKey, hours = 24, opts) {
|
|
14723
15149
|
if (!this.config.conversationIndexEnabled) {
|
|
14724
15150
|
return { chunks: 0, skipped: true, reason: "disabled", embedded: false };
|
|
@@ -14987,7 +15413,7 @@ ${r.snippet.trim()}
|
|
|
14987
15413
|
const seedRelativePaths = seedCandidates.map((result) => graphPathRelativeToStorage(storage.dir, result.path)).filter((value) => typeof value === "string" && value.length > 0);
|
|
14988
15414
|
if (seedRelativePaths.length === 0) continue;
|
|
14989
15415
|
const seedRecallScore = seedCandidates.reduce((max, item) => Math.max(max, item.score), 0);
|
|
14990
|
-
seedPaths.push(...seedRelativePaths.map((rel) =>
|
|
15416
|
+
seedPaths.push(...seedRelativePaths.map((rel) => path28.join(storage.dir, rel)));
|
|
14991
15417
|
const seedSet = new Set(seedRelativePaths);
|
|
14992
15418
|
const expanded = await this.graphIndexFor(storage).spreadingActivation(
|
|
14993
15419
|
seedRelativePaths,
|
|
@@ -14996,7 +15422,7 @@ ${r.snippet.trim()}
|
|
|
14996
15422
|
if (expanded.length === 0) continue;
|
|
14997
15423
|
for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
|
|
14998
15424
|
if (seedSet.has(candidate.path)) continue;
|
|
14999
|
-
const memoryPath =
|
|
15425
|
+
const memoryPath = path28.resolve(storage.dir, candidate.path);
|
|
15000
15426
|
const memory = await storage.readMemoryByPath(memoryPath);
|
|
15001
15427
|
if (!memory) continue;
|
|
15002
15428
|
if (isArtifactMemoryPath(memory.path)) continue;
|
|
@@ -15019,7 +15445,7 @@ ${r.snippet.trim()}
|
|
|
15019
15445
|
path: memory.path,
|
|
15020
15446
|
score,
|
|
15021
15447
|
namespace,
|
|
15022
|
-
seed:
|
|
15448
|
+
seed: path28.resolve(storage.dir, candidate.seed),
|
|
15023
15449
|
hopDepth: candidate.hopDepth,
|
|
15024
15450
|
decayedWeight: candidate.decayedWeight,
|
|
15025
15451
|
graphType: candidate.graphType
|
|
@@ -15034,8 +15460,8 @@ ${r.snippet.trim()}
|
|
|
15034
15460
|
}
|
|
15035
15461
|
async recordLastGraphRecallSnapshot(options) {
|
|
15036
15462
|
try {
|
|
15037
|
-
const snapshotPath =
|
|
15038
|
-
await mkdir20(
|
|
15463
|
+
const snapshotPath = path28.join(options.storage.dir, "state", "last_graph_recall.json");
|
|
15464
|
+
await mkdir20(path28.dirname(snapshotPath), { recursive: true });
|
|
15039
15465
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15040
15466
|
const totalSeedCount = options.seedPaths.length;
|
|
15041
15467
|
const totalExpandedCount = options.expandedPaths.length;
|
|
@@ -15052,7 +15478,7 @@ ${r.snippet.trim()}
|
|
|
15052
15478
|
seeds,
|
|
15053
15479
|
expanded
|
|
15054
15480
|
};
|
|
15055
|
-
await
|
|
15481
|
+
await writeFile19(snapshotPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
15056
15482
|
} catch (err) {
|
|
15057
15483
|
log.debug(`last graph recall write failed: ${err}`);
|
|
15058
15484
|
}
|
|
@@ -16063,7 +16489,7 @@ _Context: ${topQuestion.context}_`);
|
|
|
16063
16489
|
};
|
|
16064
16490
|
this.tierMigrationInFlight = true;
|
|
16065
16491
|
try {
|
|
16066
|
-
const coldStorage = new StorageManager(
|
|
16492
|
+
const coldStorage = new StorageManager(path28.join(storage.dir, "cold"));
|
|
16067
16493
|
const [hotMemories, coldMemories] = await Promise.all([
|
|
16068
16494
|
storage.readAllMemories(),
|
|
16069
16495
|
coldStorage.readAllMemories()
|
|
@@ -16657,7 +17083,7 @@ _Context: ${topQuestion.context}_`);
|
|
|
16657
17083
|
const allMems = allMemsForGraph ?? [];
|
|
16658
17084
|
for (const m of allMems) {
|
|
16659
17085
|
if (m.frontmatter.entityRef === entityRef) {
|
|
16660
|
-
const rel =
|
|
17086
|
+
const rel = path28.relative(storage.dir, m.path);
|
|
16661
17087
|
if (rel !== memoryRelPath) entitySiblings.push(rel);
|
|
16662
17088
|
}
|
|
16663
17089
|
}
|
|
@@ -17215,9 +17641,9 @@ ${texts.map((t, i) => `[${i + 1}] ${t}`).join("\n\n")}`;
|
|
|
17215
17641
|
protectedCategories: this.config.lifecycleProtectedCategories
|
|
17216
17642
|
}
|
|
17217
17643
|
};
|
|
17218
|
-
const metricsPath =
|
|
17219
|
-
await mkdir20(
|
|
17220
|
-
await
|
|
17644
|
+
const metricsPath = path28.join(this.storage.dir, "state", "lifecycle-metrics.json");
|
|
17645
|
+
await mkdir20(path28.dirname(metricsPath), { recursive: true });
|
|
17646
|
+
await writeFile19(metricsPath, JSON.stringify(metrics, null, 2), "utf-8");
|
|
17221
17647
|
}
|
|
17222
17648
|
/**
|
|
17223
17649
|
* Archive old, low-importance, rarely-accessed facts (v6.0).
|
|
@@ -17484,7 +17910,7 @@ ${lines.join("\n\n")}`;
|
|
|
17484
17910
|
if (hits.length === 0) return [];
|
|
17485
17911
|
const results = [];
|
|
17486
17912
|
for (const hit of hits) {
|
|
17487
|
-
const fullPath =
|
|
17913
|
+
const fullPath = path28.isAbsolute(hit.path) ? hit.path : path28.join(this.config.memoryDir, hit.path);
|
|
17488
17914
|
const memory = await this.storage.readMemoryByPath(fullPath);
|
|
17489
17915
|
if (!memory) continue;
|
|
17490
17916
|
results.push({
|
|
@@ -17942,8 +18368,8 @@ ${lines.join("\n\n")}`;
|
|
|
17942
18368
|
}
|
|
17943
18369
|
namespaceFromStorageDir(storageDir) {
|
|
17944
18370
|
if (!this.config.namespacesEnabled) return this.config.defaultNamespace;
|
|
17945
|
-
const resolvedStorageDir =
|
|
17946
|
-
const resolvedMemoryDir =
|
|
18371
|
+
const resolvedStorageDir = path28.resolve(storageDir);
|
|
18372
|
+
const resolvedMemoryDir = path28.resolve(this.config.memoryDir);
|
|
17947
18373
|
if (resolvedStorageDir === resolvedMemoryDir) return this.config.defaultNamespace;
|
|
17948
18374
|
const m = resolvedStorageDir.match(/[\\/]namespaces[\\/]([^\\/]+)$/);
|
|
17949
18375
|
return m && m[1] ? m[1] : this.config.defaultNamespace;
|
|
@@ -17971,14 +18397,14 @@ ${lines.join("\n\n")}`;
|
|
|
17971
18397
|
};
|
|
17972
18398
|
|
|
17973
18399
|
// src/tools.ts
|
|
17974
|
-
import
|
|
18400
|
+
import path30 from "path";
|
|
17975
18401
|
import { createHash as createHash7 } from "crypto";
|
|
17976
18402
|
import { Type } from "@sinclair/typebox";
|
|
17977
18403
|
|
|
17978
18404
|
// src/work/storage.ts
|
|
17979
|
-
import
|
|
18405
|
+
import path29 from "path";
|
|
17980
18406
|
import { randomUUID } from "crypto";
|
|
17981
|
-
import { mkdir as mkdir21, readdir as
|
|
18407
|
+
import { mkdir as mkdir21, readdir as readdir13, readFile as readFile21, rm as rm3, writeFile as writeFile20 } from "fs/promises";
|
|
17982
18408
|
var TASK_TRANSITIONS = {
|
|
17983
18409
|
todo: /* @__PURE__ */ new Set(["in_progress", "blocked", "cancelled"]),
|
|
17984
18410
|
in_progress: /* @__PURE__ */ new Set(["todo", "blocked", "done", "cancelled"]),
|
|
@@ -18053,8 +18479,8 @@ function ensureProjectStatus(value) {
|
|
|
18053
18479
|
var WorkStorage = class {
|
|
18054
18480
|
constructor(memoryDir) {
|
|
18055
18481
|
this.memoryDir = memoryDir;
|
|
18056
|
-
this.tasksDir =
|
|
18057
|
-
this.projectsDir =
|
|
18482
|
+
this.tasksDir = path29.join(memoryDir, "work", "tasks");
|
|
18483
|
+
this.projectsDir = path29.join(memoryDir, "work", "projects");
|
|
18058
18484
|
}
|
|
18059
18485
|
tasksDir;
|
|
18060
18486
|
projectsDir;
|
|
@@ -18064,11 +18490,11 @@ var WorkStorage = class {
|
|
|
18064
18490
|
}
|
|
18065
18491
|
taskPath(id) {
|
|
18066
18492
|
assertValidWorkId(id, "task");
|
|
18067
|
-
return
|
|
18493
|
+
return path29.join(this.tasksDir, `${id}.md`);
|
|
18068
18494
|
}
|
|
18069
18495
|
projectPath(id) {
|
|
18070
18496
|
assertValidWorkId(id, "project");
|
|
18071
|
-
return
|
|
18497
|
+
return path29.join(this.projectsDir, `${id}.md`);
|
|
18072
18498
|
}
|
|
18073
18499
|
serializeTask(task) {
|
|
18074
18500
|
return `${serializeFrontmatter2(task)}
|
|
@@ -18140,7 +18566,7 @@ ${project.description}
|
|
|
18140
18566
|
throw new Error(`project not found: ${task.projectId}`);
|
|
18141
18567
|
}
|
|
18142
18568
|
}
|
|
18143
|
-
await
|
|
18569
|
+
await writeFile20(this.taskPath(task.id), this.serializeTask(task), "utf-8");
|
|
18144
18570
|
if (task.projectId) {
|
|
18145
18571
|
await this.addTaskIdToProject(task.projectId, task.id, now);
|
|
18146
18572
|
}
|
|
@@ -18148,7 +18574,7 @@ ${project.description}
|
|
|
18148
18574
|
}
|
|
18149
18575
|
async getTask(id) {
|
|
18150
18576
|
try {
|
|
18151
|
-
const raw = await
|
|
18577
|
+
const raw = await readFile21(this.taskPath(id), "utf-8");
|
|
18152
18578
|
return this.parseTask(raw);
|
|
18153
18579
|
} catch {
|
|
18154
18580
|
return null;
|
|
@@ -18156,11 +18582,11 @@ ${project.description}
|
|
|
18156
18582
|
}
|
|
18157
18583
|
async listTasks(filter) {
|
|
18158
18584
|
await this.ensureDirectories();
|
|
18159
|
-
const entries = await
|
|
18585
|
+
const entries = await readdir13(this.tasksDir, { withFileTypes: true });
|
|
18160
18586
|
const out = [];
|
|
18161
18587
|
for (const entry of entries) {
|
|
18162
18588
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
18163
|
-
const raw = await
|
|
18589
|
+
const raw = await readFile21(path29.join(this.tasksDir, entry.name), "utf-8");
|
|
18164
18590
|
const task = this.parseTask(raw);
|
|
18165
18591
|
if (!task) continue;
|
|
18166
18592
|
if (filter?.status && task.status !== filter.status) continue;
|
|
@@ -18202,7 +18628,7 @@ ${project.description}
|
|
|
18202
18628
|
tags: patch.tags ?? existing.tags,
|
|
18203
18629
|
updatedAt: now.toISOString()
|
|
18204
18630
|
};
|
|
18205
|
-
await
|
|
18631
|
+
await writeFile20(this.taskPath(id), this.serializeTask(next), "utf-8");
|
|
18206
18632
|
return next;
|
|
18207
18633
|
}
|
|
18208
18634
|
async transitionTask(id, nextStatus, now = /* @__PURE__ */ new Date()) {
|
|
@@ -18242,12 +18668,12 @@ ${project.description}
|
|
|
18242
18668
|
createdAt: timestamp,
|
|
18243
18669
|
updatedAt: timestamp
|
|
18244
18670
|
};
|
|
18245
|
-
await
|
|
18671
|
+
await writeFile20(this.projectPath(project.id), this.serializeProject(project), "utf-8");
|
|
18246
18672
|
return project;
|
|
18247
18673
|
}
|
|
18248
18674
|
async getProject(id) {
|
|
18249
18675
|
try {
|
|
18250
|
-
const raw = await
|
|
18676
|
+
const raw = await readFile21(this.projectPath(id), "utf-8");
|
|
18251
18677
|
return this.parseProject(raw);
|
|
18252
18678
|
} catch {
|
|
18253
18679
|
return null;
|
|
@@ -18255,11 +18681,11 @@ ${project.description}
|
|
|
18255
18681
|
}
|
|
18256
18682
|
async listProjects() {
|
|
18257
18683
|
await this.ensureDirectories();
|
|
18258
|
-
const entries = await
|
|
18684
|
+
const entries = await readdir13(this.projectsDir, { withFileTypes: true });
|
|
18259
18685
|
const out = [];
|
|
18260
18686
|
for (const entry of entries) {
|
|
18261
18687
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
18262
|
-
const raw = await
|
|
18688
|
+
const raw = await readFile21(path29.join(this.projectsDir, entry.name), "utf-8");
|
|
18263
18689
|
const project = this.parseProject(raw);
|
|
18264
18690
|
if (project) out.push(project);
|
|
18265
18691
|
}
|
|
@@ -18276,7 +18702,7 @@ ${project.description}
|
|
|
18276
18702
|
taskIds: patch.taskIds ? [...patch.taskIds].sort() : existing.taskIds,
|
|
18277
18703
|
updatedAt: now.toISOString()
|
|
18278
18704
|
};
|
|
18279
|
-
await
|
|
18705
|
+
await writeFile20(this.projectPath(id), this.serializeProject(next), "utf-8");
|
|
18280
18706
|
return next;
|
|
18281
18707
|
}
|
|
18282
18708
|
async deleteProject(id) {
|
|
@@ -19815,7 +20241,7 @@ Best for:
|
|
|
19815
20241
|
- Reviewing identity development over time`,
|
|
19816
20242
|
parameters: Type.Object({}),
|
|
19817
20243
|
async execute() {
|
|
19818
|
-
const workspaceDir =
|
|
20244
|
+
const workspaceDir = path30.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
19819
20245
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
19820
20246
|
if (!identity) {
|
|
19821
20247
|
return toolResult("No identity file found. Identity reflections build automatically through conversations when identityEnabled is true.");
|
|
@@ -20332,13 +20758,13 @@ promotionCandidates: ${res.promotionCandidateCount}`
|
|
|
20332
20758
|
}
|
|
20333
20759
|
|
|
20334
20760
|
// src/cli.ts
|
|
20335
|
-
import
|
|
20336
|
-
import { access as access3, readFile as
|
|
20337
|
-
import { createHash as
|
|
20761
|
+
import path48 from "path";
|
|
20762
|
+
import { access as access3, readFile as readFile34, readdir as readdir20, unlink as unlink6 } from "fs/promises";
|
|
20763
|
+
import { createHash as createHash10 } from "crypto";
|
|
20338
20764
|
|
|
20339
20765
|
// src/transfer/export-json.ts
|
|
20340
|
-
import
|
|
20341
|
-
import { mkdir as mkdir23, readFile as
|
|
20766
|
+
import path32 from "path";
|
|
20767
|
+
import { mkdir as mkdir23, readFile as readFile23 } from "fs/promises";
|
|
20342
20768
|
|
|
20343
20769
|
// src/transfer/constants.ts
|
|
20344
20770
|
var EXPORT_FORMAT = "openclaw-engram-export";
|
|
@@ -20346,10 +20772,10 @@ var EXPORT_SCHEMA_VERSION = 1;
|
|
|
20346
20772
|
|
|
20347
20773
|
// src/transfer/fs-utils.ts
|
|
20348
20774
|
import { createHash as createHash8 } from "crypto";
|
|
20349
|
-
import { mkdir as mkdir22, readdir as
|
|
20350
|
-
import
|
|
20775
|
+
import { mkdir as mkdir22, readdir as readdir14, readFile as readFile22, stat as stat6, writeFile as writeFile21 } from "fs/promises";
|
|
20776
|
+
import path31 from "path";
|
|
20351
20777
|
async function sha256File(filePath) {
|
|
20352
|
-
const buf = await
|
|
20778
|
+
const buf = await readFile22(filePath);
|
|
20353
20779
|
const sha256 = createHash8("sha256").update(buf).digest("hex");
|
|
20354
20780
|
return { sha256, bytes: buf.byteLength };
|
|
20355
20781
|
}
|
|
@@ -20359,19 +20785,19 @@ function sha256String(content) {
|
|
|
20359
20785
|
return { sha256, bytes: buf.byteLength };
|
|
20360
20786
|
}
|
|
20361
20787
|
async function writeJsonFile(filePath, value) {
|
|
20362
|
-
await mkdir22(
|
|
20363
|
-
await
|
|
20788
|
+
await mkdir22(path31.dirname(filePath), { recursive: true });
|
|
20789
|
+
await writeFile21(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
20364
20790
|
}
|
|
20365
20791
|
async function readJsonFile(filePath) {
|
|
20366
|
-
const raw = await
|
|
20792
|
+
const raw = await readFile22(filePath, "utf-8");
|
|
20367
20793
|
return JSON.parse(raw);
|
|
20368
20794
|
}
|
|
20369
20795
|
async function listFilesRecursive(rootDir) {
|
|
20370
20796
|
const out = [];
|
|
20371
20797
|
async function walk(dir) {
|
|
20372
|
-
const entries = await
|
|
20798
|
+
const entries = await readdir14(dir, { withFileTypes: true });
|
|
20373
20799
|
for (const ent of entries) {
|
|
20374
|
-
const fp =
|
|
20800
|
+
const fp = path31.join(dir, ent.name);
|
|
20375
20801
|
if (ent.isDirectory()) {
|
|
20376
20802
|
await walk(fp);
|
|
20377
20803
|
} else if (ent.isFile()) {
|
|
@@ -20391,11 +20817,11 @@ async function fileExists(filePath) {
|
|
|
20391
20817
|
}
|
|
20392
20818
|
}
|
|
20393
20819
|
function toPosixRelPath(absPath, rootDir) {
|
|
20394
|
-
const rel =
|
|
20395
|
-
return rel.split(
|
|
20820
|
+
const rel = path31.relative(rootDir, absPath);
|
|
20821
|
+
return rel.split(path31.sep).join("/");
|
|
20396
20822
|
}
|
|
20397
20823
|
function fromPosixRelPath(relPath) {
|
|
20398
|
-
return relPath.split("/").join(
|
|
20824
|
+
return relPath.split("/").join(path31.sep);
|
|
20399
20825
|
}
|
|
20400
20826
|
|
|
20401
20827
|
// src/transfer/export-json.ts
|
|
@@ -20411,24 +20837,24 @@ function shouldExclude(relPosix, includeTranscripts) {
|
|
|
20411
20837
|
}
|
|
20412
20838
|
async function exportJsonBundle(opts) {
|
|
20413
20839
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
20414
|
-
const outDirAbs =
|
|
20840
|
+
const outDirAbs = path32.resolve(opts.outDir);
|
|
20415
20841
|
await mkdir23(outDirAbs, { recursive: true });
|
|
20416
|
-
const memoryDirAbs =
|
|
20842
|
+
const memoryDirAbs = path32.resolve(opts.memoryDir);
|
|
20417
20843
|
const filesAbs = await listFilesRecursive(memoryDirAbs);
|
|
20418
20844
|
const records = [];
|
|
20419
20845
|
const manifestFiles = [];
|
|
20420
20846
|
for (const abs of filesAbs) {
|
|
20421
20847
|
const relPosix = toPosixRelPath(abs, memoryDirAbs);
|
|
20422
20848
|
if (shouldExclude(relPosix, includeTranscripts)) continue;
|
|
20423
|
-
const content = await
|
|
20849
|
+
const content = await readFile23(abs, "utf-8");
|
|
20424
20850
|
records.push({ path: relPosix, content });
|
|
20425
20851
|
const { sha256, bytes } = await sha256File(abs);
|
|
20426
20852
|
manifestFiles.push({ path: relPosix, sha256, bytes });
|
|
20427
20853
|
}
|
|
20428
20854
|
if (opts.includeWorkspaceIdentity !== false && opts.workspaceDir) {
|
|
20429
|
-
const identityPath =
|
|
20855
|
+
const identityPath = path32.join(opts.workspaceDir, "IDENTITY.md");
|
|
20430
20856
|
try {
|
|
20431
|
-
const content = await
|
|
20857
|
+
const content = await readFile23(identityPath, "utf-8");
|
|
20432
20858
|
const relPath = "workspace/IDENTITY.md";
|
|
20433
20859
|
records.push({ path: relPath, content });
|
|
20434
20860
|
const { sha256, bytes } = sha256String(content);
|
|
@@ -20445,13 +20871,13 @@ async function exportJsonBundle(opts) {
|
|
|
20445
20871
|
files: manifestFiles.sort((a, b) => a.path.localeCompare(b.path))
|
|
20446
20872
|
};
|
|
20447
20873
|
const bundle = { manifest, records };
|
|
20448
|
-
await writeJsonFile(
|
|
20449
|
-
await writeJsonFile(
|
|
20874
|
+
await writeJsonFile(path32.join(outDirAbs, "manifest.json"), manifest);
|
|
20875
|
+
await writeJsonFile(path32.join(outDirAbs, "bundle.json"), bundle);
|
|
20450
20876
|
}
|
|
20451
20877
|
|
|
20452
20878
|
// src/transfer/export-md.ts
|
|
20453
|
-
import
|
|
20454
|
-
import { mkdir as mkdir24, readFile as
|
|
20879
|
+
import path33 from "path";
|
|
20880
|
+
import { mkdir as mkdir24, readFile as readFile24, writeFile as writeFile22 } from "fs/promises";
|
|
20455
20881
|
function shouldExclude2(relPosix, includeTranscripts) {
|
|
20456
20882
|
const parts = relPosix.split("/");
|
|
20457
20883
|
if (!includeTranscripts && parts[0] === "transcripts") return true;
|
|
@@ -20459,18 +20885,18 @@ function shouldExclude2(relPosix, includeTranscripts) {
|
|
|
20459
20885
|
}
|
|
20460
20886
|
async function exportMarkdownBundle(opts) {
|
|
20461
20887
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
20462
|
-
const outDirAbs =
|
|
20888
|
+
const outDirAbs = path33.resolve(opts.outDir);
|
|
20463
20889
|
await mkdir24(outDirAbs, { recursive: true });
|
|
20464
|
-
const memDirAbs =
|
|
20890
|
+
const memDirAbs = path33.resolve(opts.memoryDir);
|
|
20465
20891
|
const filesAbs = await listFilesRecursive(memDirAbs);
|
|
20466
20892
|
const manifestFiles = [];
|
|
20467
20893
|
for (const abs of filesAbs) {
|
|
20468
20894
|
const relPosix = toPosixRelPath(abs, memDirAbs);
|
|
20469
20895
|
if (shouldExclude2(relPosix, includeTranscripts)) continue;
|
|
20470
|
-
const dstAbs =
|
|
20471
|
-
await mkdir24(
|
|
20472
|
-
const content = await
|
|
20473
|
-
await
|
|
20896
|
+
const dstAbs = path33.join(outDirAbs, ...relPosix.split("/"));
|
|
20897
|
+
await mkdir24(path33.dirname(dstAbs), { recursive: true });
|
|
20898
|
+
const content = await readFile24(abs);
|
|
20899
|
+
await writeFile22(dstAbs, content);
|
|
20474
20900
|
const { sha256, bytes } = await sha256File(abs);
|
|
20475
20901
|
manifestFiles.push({ path: relPosix, sha256, bytes });
|
|
20476
20902
|
}
|
|
@@ -20482,12 +20908,12 @@ async function exportMarkdownBundle(opts) {
|
|
|
20482
20908
|
includesTranscripts: includeTranscripts,
|
|
20483
20909
|
files: manifestFiles.sort((a, b) => a.path.localeCompare(b.path))
|
|
20484
20910
|
};
|
|
20485
|
-
await writeJsonFile(
|
|
20911
|
+
await writeJsonFile(path33.join(outDirAbs, "manifest.json"), manifest);
|
|
20486
20912
|
}
|
|
20487
20913
|
async function looksLikeEngramMdExport(fromDir) {
|
|
20488
|
-
const dirAbs =
|
|
20914
|
+
const dirAbs = path33.resolve(fromDir);
|
|
20489
20915
|
try {
|
|
20490
|
-
const raw = await
|
|
20916
|
+
const raw = await readFile24(path33.join(dirAbs, "manifest.json"), "utf-8");
|
|
20491
20917
|
const parsed = JSON.parse(raw);
|
|
20492
20918
|
return parsed.format === EXPORT_FORMAT && parsed.schemaVersion === EXPORT_SCHEMA_VERSION;
|
|
20493
20919
|
} catch {
|
|
@@ -20496,16 +20922,16 @@ async function looksLikeEngramMdExport(fromDir) {
|
|
|
20496
20922
|
}
|
|
20497
20923
|
|
|
20498
20924
|
// src/transfer/backup.ts
|
|
20499
|
-
import
|
|
20500
|
-
import { mkdir as mkdir25, readdir as
|
|
20925
|
+
import path34 from "path";
|
|
20926
|
+
import { mkdir as mkdir25, readdir as readdir15, rm as rm4 } from "fs/promises";
|
|
20501
20927
|
function timestampDirName(now) {
|
|
20502
20928
|
return now.toISOString().replace(/[:.]/g, "-");
|
|
20503
20929
|
}
|
|
20504
20930
|
async function backupMemoryDir(opts) {
|
|
20505
|
-
const outDirAbs =
|
|
20931
|
+
const outDirAbs = path34.resolve(opts.outDir);
|
|
20506
20932
|
await mkdir25(outDirAbs, { recursive: true });
|
|
20507
20933
|
const ts = timestampDirName(/* @__PURE__ */ new Date());
|
|
20508
|
-
const backupDir =
|
|
20934
|
+
const backupDir = path34.join(outDirAbs, ts);
|
|
20509
20935
|
await exportMarkdownBundle({
|
|
20510
20936
|
memoryDir: opts.memoryDir,
|
|
20511
20937
|
outDir: backupDir,
|
|
@@ -20518,7 +20944,7 @@ async function backupMemoryDir(opts) {
|
|
|
20518
20944
|
return backupDir;
|
|
20519
20945
|
}
|
|
20520
20946
|
async function enforceRetention(outDirAbs, retentionDays) {
|
|
20521
|
-
const entries = await
|
|
20947
|
+
const entries = await readdir15(outDirAbs, { withFileTypes: true });
|
|
20522
20948
|
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
20523
20949
|
for (const ent of entries) {
|
|
20524
20950
|
if (!ent.isDirectory()) continue;
|
|
@@ -20530,15 +20956,15 @@ async function enforceRetention(outDirAbs, retentionDays) {
|
|
|
20530
20956
|
const tsMs = iso ? Date.parse(iso) : NaN;
|
|
20531
20957
|
if (!Number.isFinite(tsMs)) continue;
|
|
20532
20958
|
if (tsMs < cutoffMs) {
|
|
20533
|
-
await rm4(
|
|
20959
|
+
await rm4(path34.join(outDirAbs, name), { recursive: true, force: true });
|
|
20534
20960
|
}
|
|
20535
20961
|
}
|
|
20536
20962
|
}
|
|
20537
20963
|
|
|
20538
20964
|
// src/transfer/export-sqlite.ts
|
|
20539
|
-
import
|
|
20965
|
+
import path35 from "path";
|
|
20540
20966
|
import Database from "better-sqlite3";
|
|
20541
|
-
import { readFile as
|
|
20967
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
20542
20968
|
|
|
20543
20969
|
// src/transfer/sqlite-schema.ts
|
|
20544
20970
|
var SQLITE_SCHEMA_VERSION = 1;
|
|
@@ -20564,8 +20990,8 @@ function shouldExclude3(relPosix, includeTranscripts) {
|
|
|
20564
20990
|
}
|
|
20565
20991
|
async function exportSqlite(opts) {
|
|
20566
20992
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
20567
|
-
const memDirAbs =
|
|
20568
|
-
const outAbs =
|
|
20993
|
+
const memDirAbs = path35.resolve(opts.memoryDir);
|
|
20994
|
+
const outAbs = path35.resolve(opts.outFile);
|
|
20569
20995
|
const filesAbs = await listFilesRecursive(memDirAbs);
|
|
20570
20996
|
const db = new Database(outAbs);
|
|
20571
20997
|
try {
|
|
@@ -20586,7 +21012,7 @@ async function exportSqlite(opts) {
|
|
|
20586
21012
|
for (const abs of filesAbs) {
|
|
20587
21013
|
const relPosix = toPosixRelPath(abs, memDirAbs);
|
|
20588
21014
|
if (shouldExclude3(relPosix, includeTranscripts)) continue;
|
|
20589
|
-
const content = await
|
|
21015
|
+
const content = await readFile25(abs, "utf-8");
|
|
20590
21016
|
const { sha256, bytes } = await sha256File(abs);
|
|
20591
21017
|
rows.push({ rel: relPosix, bytes, sha256, content });
|
|
20592
21018
|
}
|
|
@@ -20597,8 +21023,8 @@ async function exportSqlite(opts) {
|
|
|
20597
21023
|
}
|
|
20598
21024
|
|
|
20599
21025
|
// src/transfer/import-json.ts
|
|
20600
|
-
import
|
|
20601
|
-
import { mkdir as mkdir26, writeFile as
|
|
21026
|
+
import path36 from "path";
|
|
21027
|
+
import { mkdir as mkdir26, writeFile as writeFile23 } from "fs/promises";
|
|
20602
21028
|
|
|
20603
21029
|
// src/transfer/types.ts
|
|
20604
21030
|
import { z as z4 } from "zod";
|
|
@@ -20631,21 +21057,21 @@ function normalizeForDedupe(s) {
|
|
|
20631
21057
|
}
|
|
20632
21058
|
async function importJsonBundle(opts) {
|
|
20633
21059
|
const conflict = opts.conflict ?? "skip";
|
|
20634
|
-
const fromDirAbs =
|
|
20635
|
-
const bundlePath =
|
|
21060
|
+
const fromDirAbs = path36.resolve(opts.fromDir);
|
|
21061
|
+
const bundlePath = path36.join(fromDirAbs, "bundle.json");
|
|
20636
21062
|
const bundle = ExportBundleV1Schema.parse(await readJsonFile(bundlePath));
|
|
20637
|
-
const memDirAbs =
|
|
21063
|
+
const memDirAbs = path36.resolve(opts.targetMemoryDir);
|
|
20638
21064
|
const written = [];
|
|
20639
21065
|
let skipped = 0;
|
|
20640
21066
|
for (const rec of bundle.records) {
|
|
20641
21067
|
const isWorkspace = rec.path.startsWith("workspace/");
|
|
20642
|
-
const targetBase = isWorkspace ? opts.workspaceDir ?
|
|
21068
|
+
const targetBase = isWorkspace ? opts.workspaceDir ? path36.resolve(opts.workspaceDir) : null : memDirAbs;
|
|
20643
21069
|
if (isWorkspace && !targetBase) {
|
|
20644
21070
|
skipped += 1;
|
|
20645
21071
|
continue;
|
|
20646
21072
|
}
|
|
20647
21073
|
const relFs = fromPosixRelPath(isWorkspace ? rec.path.replace(/^workspace\//, "") : rec.path);
|
|
20648
|
-
const absTarget =
|
|
21074
|
+
const absTarget = path36.join(targetBase, relFs);
|
|
20649
21075
|
const exists3 = await fileExists(absTarget);
|
|
20650
21076
|
if (exists3) {
|
|
20651
21077
|
if (conflict === "skip") {
|
|
@@ -20669,30 +21095,30 @@ async function importJsonBundle(opts) {
|
|
|
20669
21095
|
return { written: 0, skipped };
|
|
20670
21096
|
}
|
|
20671
21097
|
for (const w of written) {
|
|
20672
|
-
await mkdir26(
|
|
20673
|
-
await
|
|
21098
|
+
await mkdir26(path36.dirname(w.abs), { recursive: true });
|
|
21099
|
+
await writeFile23(w.abs, w.content, "utf-8");
|
|
20674
21100
|
}
|
|
20675
21101
|
return { written: written.length, skipped };
|
|
20676
21102
|
}
|
|
20677
21103
|
function looksLikeEngramJsonExport(fromDir) {
|
|
20678
|
-
const dir =
|
|
21104
|
+
const dir = path36.resolve(fromDir);
|
|
20679
21105
|
return Promise.all([
|
|
20680
|
-
fileExists(
|
|
20681
|
-
fileExists(
|
|
21106
|
+
fileExists(path36.join(dir, "manifest.json")),
|
|
21107
|
+
fileExists(path36.join(dir, "bundle.json"))
|
|
20682
21108
|
]).then(([m, b]) => m && b);
|
|
20683
21109
|
}
|
|
20684
21110
|
|
|
20685
21111
|
// src/transfer/import-sqlite.ts
|
|
20686
|
-
import
|
|
21112
|
+
import path37 from "path";
|
|
20687
21113
|
import Database2 from "better-sqlite3";
|
|
20688
|
-
import { mkdir as mkdir27, writeFile as
|
|
21114
|
+
import { mkdir as mkdir27, writeFile as writeFile24 } from "fs/promises";
|
|
20689
21115
|
function normalizeForDedupe2(s) {
|
|
20690
21116
|
return s.replace(/\s+/g, " ").trim();
|
|
20691
21117
|
}
|
|
20692
21118
|
async function importSqlite(opts) {
|
|
20693
21119
|
const conflict = opts.conflict ?? "skip";
|
|
20694
|
-
const memDirAbs =
|
|
20695
|
-
const fromAbs =
|
|
21120
|
+
const memDirAbs = path37.resolve(opts.targetMemoryDir);
|
|
21121
|
+
const fromAbs = path37.resolve(opts.fromFile);
|
|
20696
21122
|
const db = new Database2(fromAbs, { readonly: true });
|
|
20697
21123
|
const written = [];
|
|
20698
21124
|
let skipped = 0;
|
|
@@ -20705,7 +21131,7 @@ async function importSqlite(opts) {
|
|
|
20705
21131
|
const rows = db.prepare("SELECT path_rel, content FROM files").all();
|
|
20706
21132
|
for (const r of rows) {
|
|
20707
21133
|
const relFs = fromPosixRelPath(r.path_rel);
|
|
20708
|
-
const absTarget =
|
|
21134
|
+
const absTarget = path37.join(memDirAbs, relFs);
|
|
20709
21135
|
const exists3 = await fileExists(absTarget);
|
|
20710
21136
|
if (exists3) {
|
|
20711
21137
|
if (conflict === "skip") {
|
|
@@ -20730,30 +21156,30 @@ async function importSqlite(opts) {
|
|
|
20730
21156
|
}
|
|
20731
21157
|
if (opts.dryRun) return { written: 0, skipped };
|
|
20732
21158
|
for (const w of written) {
|
|
20733
|
-
await mkdir27(
|
|
20734
|
-
await
|
|
21159
|
+
await mkdir27(path37.dirname(w.abs), { recursive: true });
|
|
21160
|
+
await writeFile24(w.abs, w.content, "utf-8");
|
|
20735
21161
|
}
|
|
20736
21162
|
return { written: written.length, skipped };
|
|
20737
21163
|
}
|
|
20738
21164
|
|
|
20739
21165
|
// src/transfer/import-md.ts
|
|
20740
|
-
import
|
|
20741
|
-
import { mkdir as mkdir28, readFile as
|
|
21166
|
+
import path38 from "path";
|
|
21167
|
+
import { mkdir as mkdir28, readFile as readFile26, writeFile as writeFile25 } from "fs/promises";
|
|
20742
21168
|
function normalizeForDedupe3(s) {
|
|
20743
21169
|
return s.replace(/\s+/g, " ").trim();
|
|
20744
21170
|
}
|
|
20745
21171
|
async function importMarkdownBundle(opts) {
|
|
20746
21172
|
const conflict = opts.conflict ?? "skip";
|
|
20747
|
-
const fromAbs =
|
|
20748
|
-
const targetAbs =
|
|
21173
|
+
const fromAbs = path38.resolve(opts.fromDir);
|
|
21174
|
+
const targetAbs = path38.resolve(opts.targetMemoryDir);
|
|
20749
21175
|
const filesAbs = await listFilesRecursive(fromAbs);
|
|
20750
21176
|
const writes = [];
|
|
20751
21177
|
let skipped = 0;
|
|
20752
21178
|
for (const abs of filesAbs) {
|
|
20753
21179
|
const relPosix = toPosixRelPath(abs, fromAbs);
|
|
20754
21180
|
if (relPosix === "manifest.json") continue;
|
|
20755
|
-
const dstAbs =
|
|
20756
|
-
const content = await
|
|
21181
|
+
const dstAbs = path38.join(targetAbs, fromPosixRelPath(relPosix));
|
|
21182
|
+
const content = await readFile26(abs, "utf-8");
|
|
20757
21183
|
const exists3 = await fileExists(dstAbs);
|
|
20758
21184
|
if (exists3) {
|
|
20759
21185
|
if (conflict === "skip") {
|
|
@@ -20775,17 +21201,17 @@ async function importMarkdownBundle(opts) {
|
|
|
20775
21201
|
}
|
|
20776
21202
|
if (opts.dryRun) return { written: 0, skipped };
|
|
20777
21203
|
for (const w of writes) {
|
|
20778
|
-
await mkdir28(
|
|
20779
|
-
await
|
|
21204
|
+
await mkdir28(path38.dirname(w.abs), { recursive: true });
|
|
21205
|
+
await writeFile25(w.abs, w.content, "utf-8");
|
|
20780
21206
|
}
|
|
20781
21207
|
return { written: writes.length, skipped };
|
|
20782
21208
|
}
|
|
20783
21209
|
|
|
20784
21210
|
// src/transfer/autodetect.ts
|
|
20785
|
-
import
|
|
21211
|
+
import path39 from "path";
|
|
20786
21212
|
import { stat as stat7 } from "fs/promises";
|
|
20787
21213
|
async function detectImportFormat(fromPath) {
|
|
20788
|
-
const abs =
|
|
21214
|
+
const abs = path39.resolve(fromPath);
|
|
20789
21215
|
let st;
|
|
20790
21216
|
try {
|
|
20791
21217
|
st = await stat7(abs);
|
|
@@ -21213,8 +21639,8 @@ function gatherCandidates(input, warnings) {
|
|
|
21213
21639
|
const record = rec;
|
|
21214
21640
|
const content = typeof record.content === "string" ? record.content : null;
|
|
21215
21641
|
if (!content) continue;
|
|
21216
|
-
const
|
|
21217
|
-
if (!
|
|
21642
|
+
const path50 = typeof record.path === "string" ? record.path : "";
|
|
21643
|
+
if (!path50.startsWith("transcripts/") && !path50.includes("/transcripts/")) continue;
|
|
21218
21644
|
rows.push(...parseJsonl(content, warnings));
|
|
21219
21645
|
}
|
|
21220
21646
|
return rows;
|
|
@@ -21270,8 +21696,8 @@ var openclawReplayNormalizer = {
|
|
|
21270
21696
|
};
|
|
21271
21697
|
|
|
21272
21698
|
// src/maintenance/archive-observations.ts
|
|
21273
|
-
import
|
|
21274
|
-
import { mkdir as mkdir29, readdir as
|
|
21699
|
+
import path40 from "path";
|
|
21700
|
+
import { mkdir as mkdir29, readdir as readdir16, readFile as readFile27, unlink as unlink5, writeFile as writeFile26 } from "fs/promises";
|
|
21275
21701
|
var DATE_FILE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})\.(jsonl|md)$/;
|
|
21276
21702
|
function normalizeRetentionDays(value) {
|
|
21277
21703
|
if (!Number.isFinite(value)) return 30;
|
|
@@ -21292,13 +21718,13 @@ async function listFilesRecursive2(root, relPrefix = "") {
|
|
|
21292
21718
|
const out = [];
|
|
21293
21719
|
let entries;
|
|
21294
21720
|
try {
|
|
21295
|
-
entries = await
|
|
21721
|
+
entries = await readdir16(root, { withFileTypes: true });
|
|
21296
21722
|
} catch {
|
|
21297
21723
|
return out;
|
|
21298
21724
|
}
|
|
21299
21725
|
for (const entry of entries) {
|
|
21300
|
-
const rel = relPrefix ?
|
|
21301
|
-
const full =
|
|
21726
|
+
const rel = relPrefix ? path40.join(relPrefix, entry.name) : entry.name;
|
|
21727
|
+
const full = path40.join(root, entry.name);
|
|
21302
21728
|
if (entry.isDirectory()) {
|
|
21303
21729
|
out.push(...await listFilesRecursive2(full, rel));
|
|
21304
21730
|
continue;
|
|
@@ -21308,19 +21734,19 @@ async function listFilesRecursive2(root, relPrefix = "") {
|
|
|
21308
21734
|
return out;
|
|
21309
21735
|
}
|
|
21310
21736
|
async function collectArchiveCandidates(memoryDir, cutoffTimeMs) {
|
|
21311
|
-
const roots = ["transcripts",
|
|
21737
|
+
const roots = ["transcripts", path40.join("state", "tool-usage"), path40.join("summaries", "hourly")];
|
|
21312
21738
|
const out = [];
|
|
21313
21739
|
for (const relRoot of roots) {
|
|
21314
|
-
const absRoot =
|
|
21740
|
+
const absRoot = path40.join(memoryDir, relRoot);
|
|
21315
21741
|
const files = await listFilesRecursive2(absRoot);
|
|
21316
21742
|
for (const fileRel of files) {
|
|
21317
|
-
const filename =
|
|
21743
|
+
const filename = path40.basename(fileRel);
|
|
21318
21744
|
const parsedDate = extractDateFromFilename(filename);
|
|
21319
21745
|
if (!parsedDate) continue;
|
|
21320
21746
|
if (parsedDate.getTime() >= cutoffTimeMs) continue;
|
|
21321
21747
|
out.push({
|
|
21322
|
-
absolutePath:
|
|
21323
|
-
relativePath:
|
|
21748
|
+
absolutePath: path40.join(absRoot, fileRel),
|
|
21749
|
+
relativePath: path40.join(relRoot, fileRel)
|
|
21324
21750
|
});
|
|
21325
21751
|
}
|
|
21326
21752
|
}
|
|
@@ -21335,7 +21761,7 @@ async function archiveObservations(options) {
|
|
|
21335
21761
|
new Date(now.getTime() - retentionDays * 24 * 60 * 60 * 1e3)
|
|
21336
21762
|
);
|
|
21337
21763
|
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
21338
|
-
const archiveRoot =
|
|
21764
|
+
const archiveRoot = path40.join(options.memoryDir, "archive", "observations", stamp);
|
|
21339
21765
|
const candidates = retentionDays === 0 ? [] : await collectArchiveCandidates(
|
|
21340
21766
|
options.memoryDir,
|
|
21341
21767
|
cutoffDayStartUtc
|
|
@@ -21346,12 +21772,12 @@ async function archiveObservations(options) {
|
|
|
21346
21772
|
if (!dryRun && candidates.length > 0) {
|
|
21347
21773
|
await mkdir29(archiveRoot, { recursive: true });
|
|
21348
21774
|
for (const candidate of candidates) {
|
|
21349
|
-
const archivePath =
|
|
21350
|
-
const archiveDir =
|
|
21775
|
+
const archivePath = path40.join(archiveRoot, candidate.relativePath);
|
|
21776
|
+
const archiveDir = path40.dirname(archivePath);
|
|
21351
21777
|
await mkdir29(archiveDir, { recursive: true });
|
|
21352
|
-
const raw = await
|
|
21353
|
-
await
|
|
21354
|
-
await
|
|
21778
|
+
const raw = await readFile27(candidate.absolutePath);
|
|
21779
|
+
await writeFile26(archivePath, raw);
|
|
21780
|
+
await unlink5(candidate.absolutePath);
|
|
21355
21781
|
archivedFiles += 1;
|
|
21356
21782
|
archivedBytes += raw.byteLength;
|
|
21357
21783
|
archivedRelativePaths.push(candidate.relativePath);
|
|
@@ -21371,12 +21797,12 @@ async function archiveObservations(options) {
|
|
|
21371
21797
|
}
|
|
21372
21798
|
|
|
21373
21799
|
// src/maintenance/rebuild-observations.ts
|
|
21374
|
-
import
|
|
21375
|
-
import { readdir as
|
|
21800
|
+
import path42 from "path";
|
|
21801
|
+
import { readdir as readdir17, readFile as readFile29 } from "fs/promises";
|
|
21376
21802
|
|
|
21377
21803
|
// src/maintenance/observation-ledger-utils.ts
|
|
21378
|
-
import
|
|
21379
|
-
import { mkdir as mkdir30, readFile as
|
|
21804
|
+
import path41 from "path";
|
|
21805
|
+
import { mkdir as mkdir30, readFile as readFile28, writeFile as writeFile27 } from "fs/promises";
|
|
21380
21806
|
function toHourBucketIso(timestamp) {
|
|
21381
21807
|
const normalized = /(?:Z|[+-]\d{2}:\d{2})$/u.test(timestamp) ? timestamp : `${timestamp}Z`;
|
|
21382
21808
|
const ms = Date.parse(normalized);
|
|
@@ -21387,17 +21813,17 @@ function toHourBucketIso(timestamp) {
|
|
|
21387
21813
|
}
|
|
21388
21814
|
async function backupAndWriteRebuiltObservations(options) {
|
|
21389
21815
|
const stamp = options.now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
21390
|
-
const archiveRoot =
|
|
21391
|
-
let backupPath =
|
|
21816
|
+
const archiveRoot = path41.join(options.memoryDir, "archive", "observations", stamp);
|
|
21817
|
+
let backupPath = path41.join(
|
|
21392
21818
|
archiveRoot,
|
|
21393
21819
|
"state",
|
|
21394
21820
|
"observation-ledger",
|
|
21395
21821
|
"rebuilt-observations.jsonl"
|
|
21396
21822
|
);
|
|
21397
21823
|
try {
|
|
21398
|
-
const existing = await
|
|
21399
|
-
await mkdir30(
|
|
21400
|
-
await
|
|
21824
|
+
const existing = await readFile28(options.outputPath, "utf-8");
|
|
21825
|
+
await mkdir30(path41.dirname(backupPath), { recursive: true });
|
|
21826
|
+
await writeFile27(backupPath, existing, "utf-8");
|
|
21401
21827
|
} catch (err) {
|
|
21402
21828
|
const code = err.code;
|
|
21403
21829
|
if (code && code === "ENOENT") {
|
|
@@ -21413,8 +21839,8 @@ async function backupAndWriteRebuiltObservations(options) {
|
|
|
21413
21839
|
rebuiltAt
|
|
21414
21840
|
})
|
|
21415
21841
|
);
|
|
21416
|
-
await mkdir30(
|
|
21417
|
-
await
|
|
21842
|
+
await mkdir30(path41.dirname(options.outputPath), { recursive: true });
|
|
21843
|
+
await writeFile27(options.outputPath, lines.length > 0 ? `${lines.join("\n")}
|
|
21418
21844
|
` : "", "utf-8");
|
|
21419
21845
|
return backupPath;
|
|
21420
21846
|
}
|
|
@@ -21423,11 +21849,11 @@ async function backupAndWriteRebuiltObservations(options) {
|
|
|
21423
21849
|
function toSortableKey(sessionKey, hour) {
|
|
21424
21850
|
return `${sessionKey}\0${hour}`;
|
|
21425
21851
|
}
|
|
21426
|
-
async function
|
|
21852
|
+
async function listTranscriptFiles2(root) {
|
|
21427
21853
|
const out = [];
|
|
21428
21854
|
let entries;
|
|
21429
21855
|
try {
|
|
21430
|
-
entries = await
|
|
21856
|
+
entries = await readdir17(root, { withFileTypes: true });
|
|
21431
21857
|
} catch (err) {
|
|
21432
21858
|
const code = err.code;
|
|
21433
21859
|
if (code && code === "ENOENT") return out;
|
|
@@ -21436,9 +21862,9 @@ async function listTranscriptFiles(root) {
|
|
|
21436
21862
|
for (const entry of entries) {
|
|
21437
21863
|
if (entry.name === "." || entry.name === "..") continue;
|
|
21438
21864
|
if (entry.isSymbolicLink()) continue;
|
|
21439
|
-
const full =
|
|
21865
|
+
const full = path42.join(root, entry.name);
|
|
21440
21866
|
if (entry.isDirectory()) {
|
|
21441
|
-
out.push(...await
|
|
21867
|
+
out.push(...await listTranscriptFiles2(full));
|
|
21442
21868
|
continue;
|
|
21443
21869
|
}
|
|
21444
21870
|
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -21496,18 +21922,18 @@ function buildLedgerRows(linesByFile) {
|
|
|
21496
21922
|
async function rebuildObservations(options) {
|
|
21497
21923
|
const dryRun = options.dryRun !== false;
|
|
21498
21924
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
21499
|
-
const transcriptsRoot =
|
|
21500
|
-
const outputPath =
|
|
21925
|
+
const transcriptsRoot = path42.join(options.memoryDir, "transcripts");
|
|
21926
|
+
const outputPath = path42.join(
|
|
21501
21927
|
options.memoryDir,
|
|
21502
21928
|
"state",
|
|
21503
21929
|
"observation-ledger",
|
|
21504
21930
|
"rebuilt-observations.jsonl"
|
|
21505
21931
|
);
|
|
21506
|
-
const transcriptFiles = await
|
|
21932
|
+
const transcriptFiles = await listTranscriptFiles2(transcriptsRoot);
|
|
21507
21933
|
const contents = [];
|
|
21508
21934
|
for (const file of transcriptFiles) {
|
|
21509
21935
|
try {
|
|
21510
|
-
contents.push(await
|
|
21936
|
+
contents.push(await readFile29(file, "utf-8"));
|
|
21511
21937
|
} catch {
|
|
21512
21938
|
}
|
|
21513
21939
|
}
|
|
@@ -21533,8 +21959,8 @@ async function rebuildObservations(options) {
|
|
|
21533
21959
|
}
|
|
21534
21960
|
|
|
21535
21961
|
// src/maintenance/migrate-observations.ts
|
|
21536
|
-
import
|
|
21537
|
-
import { readdir as
|
|
21962
|
+
import path43 from "path";
|
|
21963
|
+
import { readdir as readdir18, readFile as readFile30 } from "fs/promises";
|
|
21538
21964
|
function toNonNegativeInt(value) {
|
|
21539
21965
|
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
21540
21966
|
const normalized = Math.floor(value);
|
|
@@ -21585,7 +22011,7 @@ function toCounts(row) {
|
|
|
21585
22011
|
async function listLegacyObservationFiles(root) {
|
|
21586
22012
|
let entries;
|
|
21587
22013
|
try {
|
|
21588
|
-
entries = await
|
|
22014
|
+
entries = await readdir18(root, { withFileTypes: true });
|
|
21589
22015
|
} catch (err) {
|
|
21590
22016
|
const code = err.code;
|
|
21591
22017
|
if (code && code === "ENOENT") return [];
|
|
@@ -21597,16 +22023,16 @@ async function listLegacyObservationFiles(root) {
|
|
|
21597
22023
|
async function migrateObservations(options) {
|
|
21598
22024
|
const dryRun = options.dryRun !== false;
|
|
21599
22025
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
21600
|
-
const ledgerRoot =
|
|
21601
|
-
const outputPath =
|
|
22026
|
+
const ledgerRoot = path43.join(options.memoryDir, "state", "observation-ledger");
|
|
22027
|
+
const outputPath = path43.join(ledgerRoot, "rebuilt-observations.jsonl");
|
|
21602
22028
|
const legacyFiles = await listLegacyObservationFiles(ledgerRoot);
|
|
21603
|
-
const sourceRelativePaths = legacyFiles.map((name) =>
|
|
22029
|
+
const sourceRelativePaths = legacyFiles.map((name) => path43.join("state", "observation-ledger", name));
|
|
21604
22030
|
const byKey = /* @__PURE__ */ new Map();
|
|
21605
22031
|
let parsedRows = 0;
|
|
21606
22032
|
let malformedLines = 0;
|
|
21607
22033
|
for (const file of legacyFiles) {
|
|
21608
|
-
const full =
|
|
21609
|
-
const raw = await
|
|
22034
|
+
const full = path43.join(ledgerRoot, file);
|
|
22035
|
+
const raw = await readFile30(full, "utf-8");
|
|
21610
22036
|
for (const line of raw.split("\n")) {
|
|
21611
22037
|
if (!line.trim()) continue;
|
|
21612
22038
|
let parsed;
|
|
@@ -21795,10 +22221,10 @@ var defaultCommandRunner = (command, args, options) => {
|
|
|
21795
22221
|
|
|
21796
22222
|
// src/network/webdav.ts
|
|
21797
22223
|
import { createReadStream } from "fs";
|
|
21798
|
-
import { mkdir as mkdir31, readdir as
|
|
22224
|
+
import { mkdir as mkdir31, readdir as readdir19, realpath as realpath2, stat as stat9 } from "fs/promises";
|
|
21799
22225
|
import { createServer } from "http";
|
|
21800
22226
|
import { timingSafeEqual } from "crypto";
|
|
21801
|
-
import
|
|
22227
|
+
import path44 from "path";
|
|
21802
22228
|
import { pipeline } from "stream/promises";
|
|
21803
22229
|
import { URL as URL2 } from "url";
|
|
21804
22230
|
function hostToUrlAuthority(host) {
|
|
@@ -21834,10 +22260,10 @@ var WebDavServer = class _WebDavServer {
|
|
|
21834
22260
|
const allowedRoots = [];
|
|
21835
22261
|
const aliasSet = /* @__PURE__ */ new Set();
|
|
21836
22262
|
for (const dir of options.allowlistDirs) {
|
|
21837
|
-
const resolved =
|
|
22263
|
+
const resolved = path44.resolve(dir);
|
|
21838
22264
|
await mkdir31(resolved, { recursive: true });
|
|
21839
22265
|
const canonical = await realpath2(resolved);
|
|
21840
|
-
const alias =
|
|
22266
|
+
const alias = path44.basename(canonical) || "root";
|
|
21841
22267
|
if (aliasSet.has(alias)) {
|
|
21842
22268
|
throw new Error(`duplicate webdav allowlist alias: ${alias}`);
|
|
21843
22269
|
}
|
|
@@ -21993,7 +22419,7 @@ var WebDavServer = class _WebDavServer {
|
|
|
21993
22419
|
if (decodedPath.includes("\0")) {
|
|
21994
22420
|
return { ok: false, code: 400, message: "invalid path" };
|
|
21995
22421
|
}
|
|
21996
|
-
const normalized =
|
|
22422
|
+
const normalized = path44.posix.normalize(decodedPath);
|
|
21997
22423
|
const segments = normalized.split("/").filter((segment) => segment.length > 0);
|
|
21998
22424
|
if (segments.length === 0) {
|
|
21999
22425
|
return { ok: false, code: 403, message: "root listing is not allowed" };
|
|
@@ -22007,7 +22433,7 @@ var WebDavServer = class _WebDavServer {
|
|
|
22007
22433
|
if (relative.some((segment) => segment === ".." || segment.includes("\\"))) {
|
|
22008
22434
|
return { ok: false, code: 403, message: "path traversal is not allowed" };
|
|
22009
22435
|
}
|
|
22010
|
-
const candidate =
|
|
22436
|
+
const candidate = path44.resolve(root.absolute, ...relative);
|
|
22011
22437
|
if (!this.isPathInside(root.absolute, candidate)) {
|
|
22012
22438
|
return { ok: false, code: 403, message: "path escaped allowlist" };
|
|
22013
22439
|
}
|
|
@@ -22063,7 +22489,7 @@ var WebDavServer = class _WebDavServer {
|
|
|
22063
22489
|
}
|
|
22064
22490
|
const entries = [];
|
|
22065
22491
|
if (info.isDirectory()) {
|
|
22066
|
-
const children = await
|
|
22492
|
+
const children = await readdir19(absolutePath, { withFileTypes: true });
|
|
22067
22493
|
for (const child of children) {
|
|
22068
22494
|
const childHref = toEncodedHref(`${displayPath.replace(/\/$/, "")}/${child.name}`);
|
|
22069
22495
|
entries.push(`
|
|
@@ -22085,10 +22511,10 @@ var WebDavServer = class _WebDavServer {
|
|
|
22085
22511
|
}
|
|
22086
22512
|
isPathInside(root, target) {
|
|
22087
22513
|
if (target === root) return true;
|
|
22088
|
-
if (root ===
|
|
22514
|
+
if (root === path44.parse(root).root) {
|
|
22089
22515
|
return target.startsWith(root);
|
|
22090
22516
|
}
|
|
22091
|
-
return target.startsWith(`${root}${
|
|
22517
|
+
return target.startsWith(`${root}${path44.sep}`);
|
|
22092
22518
|
}
|
|
22093
22519
|
};
|
|
22094
22520
|
function xmlEscape(value) {
|
|
@@ -22098,9 +22524,362 @@ function toEncodedHref(pathname) {
|
|
|
22098
22524
|
return pathname.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
22099
22525
|
}
|
|
22100
22526
|
|
|
22527
|
+
// src/dashboard-runtime.ts
|
|
22528
|
+
import { createHash as createHash9 } from "crypto";
|
|
22529
|
+
import { createServer as createServer2 } from "http";
|
|
22530
|
+
import { watch } from "fs";
|
|
22531
|
+
import { readFile as readFile32 } from "fs/promises";
|
|
22532
|
+
import path46 from "path";
|
|
22533
|
+
|
|
22534
|
+
// src/graph-dashboard-parser.ts
|
|
22535
|
+
import path45 from "path";
|
|
22536
|
+
import { readFile as readFile31 } from "fs/promises";
|
|
22537
|
+
|
|
22538
|
+
// src/graph-dashboard-key.ts
|
|
22539
|
+
function graphEdgeKey(edge) {
|
|
22540
|
+
return `${edge.type}|${edge.from}|${edge.to}|${edge.label}|${edge.ts}`;
|
|
22541
|
+
}
|
|
22542
|
+
|
|
22543
|
+
// src/graph-dashboard-parser.ts
|
|
22544
|
+
var GRAPH_TYPES = ["entity", "time", "causal"];
|
|
22545
|
+
function graphFile(memoryDir, type) {
|
|
22546
|
+
return path45.join(memoryDir, "state", "graphs", `${type}.jsonl`);
|
|
22547
|
+
}
|
|
22548
|
+
function isGraphEdge(raw, expectedType) {
|
|
22549
|
+
if (!raw || typeof raw !== "object") return false;
|
|
22550
|
+
const edge = raw;
|
|
22551
|
+
return edge.type === expectedType && typeof edge.from === "string" && edge.from.length > 0 && typeof edge.to === "string" && edge.to.length > 0 && typeof edge.weight === "number" && Number.isFinite(edge.weight) && typeof edge.label === "string" && typeof edge.ts === "string";
|
|
22552
|
+
}
|
|
22553
|
+
async function graphSnapshotFromMemoryDir(memoryDir) {
|
|
22554
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
22555
|
+
const edges = [];
|
|
22556
|
+
const filesMissing = [];
|
|
22557
|
+
let malformedLines = 0;
|
|
22558
|
+
const seenEdges = /* @__PURE__ */ new Set();
|
|
22559
|
+
for (const type of GRAPH_TYPES) {
|
|
22560
|
+
const filePath = graphFile(memoryDir, type);
|
|
22561
|
+
let raw = "";
|
|
22562
|
+
try {
|
|
22563
|
+
raw = await readFile31(filePath, "utf-8");
|
|
22564
|
+
} catch {
|
|
22565
|
+
filesMissing.push(type);
|
|
22566
|
+
continue;
|
|
22567
|
+
}
|
|
22568
|
+
for (const line of raw.split("\n")) {
|
|
22569
|
+
const trimmed = line.trim();
|
|
22570
|
+
if (!trimmed) continue;
|
|
22571
|
+
let parsed;
|
|
22572
|
+
try {
|
|
22573
|
+
parsed = JSON.parse(trimmed);
|
|
22574
|
+
} catch {
|
|
22575
|
+
malformedLines += 1;
|
|
22576
|
+
continue;
|
|
22577
|
+
}
|
|
22578
|
+
if (!isGraphEdge(parsed, type)) {
|
|
22579
|
+
malformedLines += 1;
|
|
22580
|
+
continue;
|
|
22581
|
+
}
|
|
22582
|
+
const key = graphEdgeKey(parsed);
|
|
22583
|
+
if (seenEdges.has(key)) continue;
|
|
22584
|
+
seenEdges.add(key);
|
|
22585
|
+
edges.push(parsed);
|
|
22586
|
+
nodes.add(parsed.from);
|
|
22587
|
+
nodes.add(parsed.to);
|
|
22588
|
+
}
|
|
22589
|
+
}
|
|
22590
|
+
const sortedEdges = edges.sort(
|
|
22591
|
+
(a, b) => a.type.localeCompare(b.type) || a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || a.ts.localeCompare(b.ts)
|
|
22592
|
+
);
|
|
22593
|
+
const sortedNodes = [...nodes].sort((a, b) => a.localeCompare(b)).map((id) => ({ id }));
|
|
22594
|
+
return {
|
|
22595
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22596
|
+
nodes: sortedNodes,
|
|
22597
|
+
edges: sortedEdges,
|
|
22598
|
+
stats: {
|
|
22599
|
+
nodes: sortedNodes.length,
|
|
22600
|
+
edges: sortedEdges.length,
|
|
22601
|
+
malformedLines,
|
|
22602
|
+
filesMissing
|
|
22603
|
+
}
|
|
22604
|
+
};
|
|
22605
|
+
}
|
|
22606
|
+
|
|
22607
|
+
// src/graph-dashboard-diff.ts
|
|
22608
|
+
function diffGraphSnapshots(previous, next) {
|
|
22609
|
+
const prevNodeSet = new Set(previous.nodes.map((node) => node.id));
|
|
22610
|
+
const nextNodeSet = new Set(next.nodes.map((node) => node.id));
|
|
22611
|
+
const prevEdges = new Map(previous.edges.map((edge) => [graphEdgeKey(edge), edge]));
|
|
22612
|
+
const nextEdges = new Map(next.edges.map((edge) => [graphEdgeKey(edge), edge]));
|
|
22613
|
+
const addedNodes = [...nextNodeSet].filter((id) => !prevNodeSet.has(id)).sort((a, b) => a.localeCompare(b));
|
|
22614
|
+
const removedNodes = [...prevNodeSet].filter((id) => !nextNodeSet.has(id)).sort((a, b) => a.localeCompare(b));
|
|
22615
|
+
const addedEdges = [...nextEdges.entries()].filter(([key]) => !prevEdges.has(key)).map(([, edge]) => edge).sort((a, b) => a.type.localeCompare(b.type) || a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
22616
|
+
const removedEdges = [...prevEdges.entries()].filter(([key]) => !nextEdges.has(key)).map(([, edge]) => edge).sort((a, b) => a.type.localeCompare(b.type) || a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
22617
|
+
return {
|
|
22618
|
+
addedNodes,
|
|
22619
|
+
removedNodes,
|
|
22620
|
+
addedEdges,
|
|
22621
|
+
removedEdges
|
|
22622
|
+
};
|
|
22623
|
+
}
|
|
22624
|
+
|
|
22625
|
+
// src/dashboard-runtime.ts
|
|
22626
|
+
function websocketAcceptKey(clientKey) {
|
|
22627
|
+
return createHash9("sha1").update(`${clientKey}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest("base64");
|
|
22628
|
+
}
|
|
22629
|
+
function encodeTextFrame(payload) {
|
|
22630
|
+
const payloadBuffer = Buffer.from(payload, "utf-8");
|
|
22631
|
+
const len = payloadBuffer.length;
|
|
22632
|
+
const header = [129];
|
|
22633
|
+
if (len <= 125) {
|
|
22634
|
+
header.push(len);
|
|
22635
|
+
} else if (len <= 65535) {
|
|
22636
|
+
header.push(126, len >> 8 & 255, len & 255);
|
|
22637
|
+
} else {
|
|
22638
|
+
const high = Math.floor(len / 2 ** 32);
|
|
22639
|
+
const low = len >>> 0;
|
|
22640
|
+
header.push(127, high >> 24 & 255, high >> 16 & 255, high >> 8 & 255, high & 255, low >> 24 & 255, low >> 16 & 255, low >> 8 & 255, low & 255);
|
|
22641
|
+
}
|
|
22642
|
+
return Buffer.concat([Buffer.from(header), payloadBuffer]);
|
|
22643
|
+
}
|
|
22644
|
+
var GraphDashboardServer = class {
|
|
22645
|
+
memoryDir;
|
|
22646
|
+
host;
|
|
22647
|
+
requestedPort;
|
|
22648
|
+
publicDir;
|
|
22649
|
+
watchDebounceMs;
|
|
22650
|
+
server = null;
|
|
22651
|
+
watcher = null;
|
|
22652
|
+
clients = /* @__PURE__ */ new Map();
|
|
22653
|
+
graphSnapshot = {
|
|
22654
|
+
generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
22655
|
+
nodes: [],
|
|
22656
|
+
edges: [],
|
|
22657
|
+
stats: { nodes: 0, edges: 0, malformedLines: 0, filesMissing: [] }
|
|
22658
|
+
};
|
|
22659
|
+
debounceTimer = null;
|
|
22660
|
+
lastError = null;
|
|
22661
|
+
boundPort = 0;
|
|
22662
|
+
constructor(options) {
|
|
22663
|
+
this.memoryDir = options.memoryDir;
|
|
22664
|
+
this.host = options.host?.trim() || "127.0.0.1";
|
|
22665
|
+
this.requestedPort = Number.isFinite(options.port) ? Math.max(0, Math.floor(options.port ?? 0)) : 0;
|
|
22666
|
+
this.publicDir = options.publicDir ?? path46.join(process.cwd(), "dashboard", "public");
|
|
22667
|
+
this.watchDebounceMs = Math.max(50, Math.floor(options.watchDebounceMs ?? 300));
|
|
22668
|
+
}
|
|
22669
|
+
async start() {
|
|
22670
|
+
if (this.server) {
|
|
22671
|
+
return this.status();
|
|
22672
|
+
}
|
|
22673
|
+
await this.rebuildSnapshot();
|
|
22674
|
+
const candidate = createServer2((req, res) => {
|
|
22675
|
+
void this.handleHttp(req, res);
|
|
22676
|
+
});
|
|
22677
|
+
candidate.on("upgrade", (req, socket) => {
|
|
22678
|
+
this.handleUpgrade(req, socket);
|
|
22679
|
+
});
|
|
22680
|
+
try {
|
|
22681
|
+
await new Promise((resolve, reject) => {
|
|
22682
|
+
const onError = (err) => {
|
|
22683
|
+
candidate.off("listening", onListening);
|
|
22684
|
+
reject(err);
|
|
22685
|
+
};
|
|
22686
|
+
const onListening = () => {
|
|
22687
|
+
candidate.off("error", onError);
|
|
22688
|
+
resolve();
|
|
22689
|
+
};
|
|
22690
|
+
candidate.once("error", onError);
|
|
22691
|
+
candidate.once("listening", onListening);
|
|
22692
|
+
candidate.listen(this.requestedPort, this.host);
|
|
22693
|
+
});
|
|
22694
|
+
} catch (err) {
|
|
22695
|
+
this.lastError = err instanceof Error ? err.message : String(err);
|
|
22696
|
+
try {
|
|
22697
|
+
candidate.close();
|
|
22698
|
+
} catch {
|
|
22699
|
+
}
|
|
22700
|
+
throw err;
|
|
22701
|
+
}
|
|
22702
|
+
this.server = candidate;
|
|
22703
|
+
const addr = candidate.address();
|
|
22704
|
+
this.boundPort = typeof addr === "object" && addr ? addr.port : this.requestedPort;
|
|
22705
|
+
this.startWatcher();
|
|
22706
|
+
return this.status();
|
|
22707
|
+
}
|
|
22708
|
+
async stop() {
|
|
22709
|
+
const closeServer = this.server;
|
|
22710
|
+
this.server = null;
|
|
22711
|
+
this.boundPort = 0;
|
|
22712
|
+
if (this.debounceTimer) {
|
|
22713
|
+
clearTimeout(this.debounceTimer);
|
|
22714
|
+
this.debounceTimer = null;
|
|
22715
|
+
}
|
|
22716
|
+
if (this.watcher) {
|
|
22717
|
+
this.watcher.close();
|
|
22718
|
+
this.watcher = null;
|
|
22719
|
+
}
|
|
22720
|
+
for (const client of this.clients.values()) {
|
|
22721
|
+
try {
|
|
22722
|
+
client.socket.destroy();
|
|
22723
|
+
} catch {
|
|
22724
|
+
}
|
|
22725
|
+
}
|
|
22726
|
+
this.clients.clear();
|
|
22727
|
+
if (closeServer) {
|
|
22728
|
+
await new Promise((resolve, reject) => {
|
|
22729
|
+
closeServer.close((err) => {
|
|
22730
|
+
if (err) reject(err);
|
|
22731
|
+
else resolve();
|
|
22732
|
+
});
|
|
22733
|
+
});
|
|
22734
|
+
}
|
|
22735
|
+
}
|
|
22736
|
+
status() {
|
|
22737
|
+
return {
|
|
22738
|
+
running: this.server !== null,
|
|
22739
|
+
host: this.host,
|
|
22740
|
+
port: this.boundPort,
|
|
22741
|
+
watching: this.watcher !== null,
|
|
22742
|
+
lastUpdatedAt: this.graphSnapshot.generatedAt,
|
|
22743
|
+
graphNodeCount: this.graphSnapshot.stats.nodes,
|
|
22744
|
+
graphEdgeCount: this.graphSnapshot.stats.edges
|
|
22745
|
+
};
|
|
22746
|
+
}
|
|
22747
|
+
async handleHttp(req, res) {
|
|
22748
|
+
const url = req.url ?? "/";
|
|
22749
|
+
if (req.method === "GET" && url === "/api/health") {
|
|
22750
|
+
this.respondJson(res, 200, {
|
|
22751
|
+
ok: true,
|
|
22752
|
+
running: this.server !== null,
|
|
22753
|
+
watching: this.watcher !== null,
|
|
22754
|
+
graph: this.graphSnapshot.stats,
|
|
22755
|
+
clients: this.clients.size,
|
|
22756
|
+
lastError: this.lastError ?? void 0
|
|
22757
|
+
});
|
|
22758
|
+
return;
|
|
22759
|
+
}
|
|
22760
|
+
if (req.method === "GET" && url === "/api/graph") {
|
|
22761
|
+
this.respondJson(res, 200, this.graphSnapshot);
|
|
22762
|
+
return;
|
|
22763
|
+
}
|
|
22764
|
+
if (req.method === "GET" && url === "/app.js") {
|
|
22765
|
+
await this.respondStatic(res, path46.join(this.publicDir, "app.js"), "application/javascript; charset=utf-8");
|
|
22766
|
+
return;
|
|
22767
|
+
}
|
|
22768
|
+
if (req.method === "GET" && (url === "/" || url === "/index.html")) {
|
|
22769
|
+
await this.respondStatic(res, path46.join(this.publicDir, "index.html"), "text/html; charset=utf-8");
|
|
22770
|
+
return;
|
|
22771
|
+
}
|
|
22772
|
+
this.respondJson(res, 404, { error: "Not found" });
|
|
22773
|
+
}
|
|
22774
|
+
respondJson(res, status, payload) {
|
|
22775
|
+
const body = JSON.stringify(payload, null, 2);
|
|
22776
|
+
res.statusCode = status;
|
|
22777
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
22778
|
+
res.setHeader("content-length", String(Buffer.byteLength(body)));
|
|
22779
|
+
res.end(body);
|
|
22780
|
+
}
|
|
22781
|
+
async respondStatic(res, filePath, contentType) {
|
|
22782
|
+
try {
|
|
22783
|
+
const body = await readFile32(filePath, "utf-8");
|
|
22784
|
+
res.statusCode = 200;
|
|
22785
|
+
res.setHeader("content-type", contentType);
|
|
22786
|
+
res.setHeader("content-length", String(Buffer.byteLength(body)));
|
|
22787
|
+
res.end(body);
|
|
22788
|
+
} catch {
|
|
22789
|
+
this.respondJson(res, 404, { error: "Not found" });
|
|
22790
|
+
}
|
|
22791
|
+
}
|
|
22792
|
+
handleUpgrade(req, socket) {
|
|
22793
|
+
const upgrade = typeof req.headers.upgrade === "string" ? req.headers.upgrade.toLowerCase() : "";
|
|
22794
|
+
const key = req.headers["sec-websocket-key"];
|
|
22795
|
+
if (upgrade !== "websocket" || typeof key !== "string") {
|
|
22796
|
+
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
22797
|
+
socket.destroy();
|
|
22798
|
+
return;
|
|
22799
|
+
}
|
|
22800
|
+
const accept = websocketAcceptKey(key);
|
|
22801
|
+
socket.write(
|
|
22802
|
+
[
|
|
22803
|
+
"HTTP/1.1 101 Switching Protocols",
|
|
22804
|
+
"Upgrade: websocket",
|
|
22805
|
+
"Connection: Upgrade",
|
|
22806
|
+
`Sec-WebSocket-Accept: ${accept}`,
|
|
22807
|
+
"",
|
|
22808
|
+
""
|
|
22809
|
+
].join("\r\n")
|
|
22810
|
+
);
|
|
22811
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
22812
|
+
this.clients.set(id, { id, socket });
|
|
22813
|
+
socket.on("close", () => {
|
|
22814
|
+
this.clients.delete(id);
|
|
22815
|
+
});
|
|
22816
|
+
socket.on("error", () => {
|
|
22817
|
+
this.clients.delete(id);
|
|
22818
|
+
});
|
|
22819
|
+
const hello = JSON.stringify({
|
|
22820
|
+
type: "hello",
|
|
22821
|
+
graph: this.graphSnapshot
|
|
22822
|
+
});
|
|
22823
|
+
socket.write(encodeTextFrame(hello));
|
|
22824
|
+
}
|
|
22825
|
+
broadcast(payload) {
|
|
22826
|
+
const frame = encodeTextFrame(JSON.stringify(payload));
|
|
22827
|
+
for (const [id, client] of this.clients.entries()) {
|
|
22828
|
+
try {
|
|
22829
|
+
client.socket.write(frame);
|
|
22830
|
+
} catch {
|
|
22831
|
+
this.clients.delete(id);
|
|
22832
|
+
}
|
|
22833
|
+
}
|
|
22834
|
+
}
|
|
22835
|
+
startWatcher() {
|
|
22836
|
+
const graphDir = path46.join(this.memoryDir, "state", "graphs");
|
|
22837
|
+
try {
|
|
22838
|
+
this.watcher = watch(graphDir, { persistent: false }, () => {
|
|
22839
|
+
this.scheduleRebuild();
|
|
22840
|
+
});
|
|
22841
|
+
this.watcher.on("error", (err) => {
|
|
22842
|
+
this.lastError = err instanceof Error ? err.message : String(err);
|
|
22843
|
+
});
|
|
22844
|
+
} catch (err) {
|
|
22845
|
+
this.lastError = err instanceof Error ? err.message : String(err);
|
|
22846
|
+
this.watcher = null;
|
|
22847
|
+
}
|
|
22848
|
+
}
|
|
22849
|
+
scheduleRebuild() {
|
|
22850
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
22851
|
+
this.debounceTimer = setTimeout(() => {
|
|
22852
|
+
this.debounceTimer = null;
|
|
22853
|
+
void this.rebuildAndBroadcast();
|
|
22854
|
+
}, this.watchDebounceMs);
|
|
22855
|
+
}
|
|
22856
|
+
async rebuildAndBroadcast() {
|
|
22857
|
+
const previous = this.graphSnapshot;
|
|
22858
|
+
await this.rebuildSnapshot();
|
|
22859
|
+
const patch = diffGraphSnapshots(previous, this.graphSnapshot);
|
|
22860
|
+
if (patch.addedEdges.length === 0 && patch.removedEdges.length === 0 && patch.addedNodes.length === 0 && patch.removedNodes.length === 0) {
|
|
22861
|
+
return;
|
|
22862
|
+
}
|
|
22863
|
+
this.broadcast({
|
|
22864
|
+
type: "graph_patch",
|
|
22865
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22866
|
+
patch,
|
|
22867
|
+
graph: this.graphSnapshot
|
|
22868
|
+
});
|
|
22869
|
+
}
|
|
22870
|
+
async rebuildSnapshot() {
|
|
22871
|
+
try {
|
|
22872
|
+
this.graphSnapshot = await graphSnapshotFromMemoryDir(this.memoryDir);
|
|
22873
|
+
this.lastError = null;
|
|
22874
|
+
} catch (err) {
|
|
22875
|
+
this.lastError = err instanceof Error ? err.message : String(err);
|
|
22876
|
+
}
|
|
22877
|
+
}
|
|
22878
|
+
};
|
|
22879
|
+
|
|
22101
22880
|
// src/compat/checks.ts
|
|
22102
|
-
import { access as access2, readFile as
|
|
22103
|
-
import
|
|
22881
|
+
import { access as access2, readFile as readFile33 } from "fs/promises";
|
|
22882
|
+
import path47 from "path";
|
|
22104
22883
|
import { spawn as spawn4 } from "child_process";
|
|
22105
22884
|
var REQUIRED_HOOKS = ["before_agent_start", "agent_end"];
|
|
22106
22885
|
function isSafeCommandToken(command) {
|
|
@@ -22282,13 +23061,13 @@ function compareVersions(a, b) {
|
|
|
22282
23061
|
async function runCompatChecks(options) {
|
|
22283
23062
|
const checks = [];
|
|
22284
23063
|
const runner = options.runner ?? defaultRunner;
|
|
22285
|
-
const pluginJsonPath =
|
|
22286
|
-
const packageJsonPath =
|
|
22287
|
-
const indexPath =
|
|
23064
|
+
const pluginJsonPath = path47.join(options.repoRoot, "openclaw.plugin.json");
|
|
23065
|
+
const packageJsonPath = path47.join(options.repoRoot, "package.json");
|
|
23066
|
+
const indexPath = path47.join(options.repoRoot, "src", "index.ts");
|
|
22288
23067
|
let pluginRaw = "";
|
|
22289
23068
|
let pluginManifestPresent = false;
|
|
22290
23069
|
try {
|
|
22291
|
-
pluginRaw = await
|
|
23070
|
+
pluginRaw = await readFile33(pluginJsonPath, "utf-8");
|
|
22292
23071
|
pluginManifestPresent = true;
|
|
22293
23072
|
checks.push({
|
|
22294
23073
|
id: "plugin-manifest-present",
|
|
@@ -22337,7 +23116,7 @@ async function runCompatChecks(options) {
|
|
|
22337
23116
|
let packageRaw = "";
|
|
22338
23117
|
let packageJsonPresent = false;
|
|
22339
23118
|
try {
|
|
22340
|
-
packageRaw = await
|
|
23119
|
+
packageRaw = await readFile33(packageJsonPath, "utf-8");
|
|
22341
23120
|
packageJsonPresent = true;
|
|
22342
23121
|
} catch {
|
|
22343
23122
|
checks.push({
|
|
@@ -22408,7 +23187,7 @@ async function runCompatChecks(options) {
|
|
|
22408
23187
|
}
|
|
22409
23188
|
try {
|
|
22410
23189
|
await access2(indexPath);
|
|
22411
|
-
const indexRaw = await
|
|
23190
|
+
const indexRaw = await readFile33(indexPath, "utf-8");
|
|
22412
23191
|
const structuralSource = stripCommentsAndStrings(indexRaw);
|
|
22413
23192
|
const hooks = parseHookRegistrations(indexRaw);
|
|
22414
23193
|
const missingHooks = REQUIRED_HOOKS.filter((hook) => !hooks.has(hook));
|
|
@@ -22520,6 +23299,8 @@ function planAggressiveDuplicateDeletions(memories) {
|
|
|
22520
23299
|
}
|
|
22521
23300
|
var activeWebDavServer = null;
|
|
22522
23301
|
var webDavOperationChain = Promise.resolve();
|
|
23302
|
+
var activeDashboardServer = null;
|
|
23303
|
+
var dashboardOperationChain = Promise.resolve();
|
|
22523
23304
|
async function withWebDavLock(operation) {
|
|
22524
23305
|
const run = webDavOperationChain.then(operation, operation);
|
|
22525
23306
|
webDavOperationChain = run.then(
|
|
@@ -22528,6 +23309,14 @@ async function withWebDavLock(operation) {
|
|
|
22528
23309
|
);
|
|
22529
23310
|
return run;
|
|
22530
23311
|
}
|
|
23312
|
+
async function withDashboardLock(operation) {
|
|
23313
|
+
const run = dashboardOperationChain.then(operation, operation);
|
|
23314
|
+
dashboardOperationChain = run.then(
|
|
23315
|
+
() => void 0,
|
|
23316
|
+
() => void 0
|
|
23317
|
+
);
|
|
23318
|
+
return run;
|
|
23319
|
+
}
|
|
22531
23320
|
function isRoutePatternType(value) {
|
|
22532
23321
|
return value === "keyword" || value === "regex";
|
|
22533
23322
|
}
|
|
@@ -22615,6 +23404,21 @@ async function runGraphHealthCliCommand(options) {
|
|
|
22615
23404
|
includeRepairGuidance: options.includeRepairGuidance
|
|
22616
23405
|
});
|
|
22617
23406
|
}
|
|
23407
|
+
async function runSessionCheckCliCommand(options) {
|
|
23408
|
+
return analyzeSessionIntegrity({ memoryDir: options.memoryDir });
|
|
23409
|
+
}
|
|
23410
|
+
async function runSessionRepairCliCommand(options) {
|
|
23411
|
+
const report = await analyzeSessionIntegrity({ memoryDir: options.memoryDir });
|
|
23412
|
+
const dryRun = options.apply !== true || options.dryRun === true;
|
|
23413
|
+
const plan = planSessionRepair({
|
|
23414
|
+
report,
|
|
23415
|
+
dryRun,
|
|
23416
|
+
allowSessionFileRepair: options.allowSessionFileRepair === true,
|
|
23417
|
+
sessionFilesDir: options.sessionFilesDir
|
|
23418
|
+
});
|
|
23419
|
+
const applyResult = await applySessionRepair({ plan });
|
|
23420
|
+
return { report, plan, applyResult };
|
|
23421
|
+
}
|
|
22618
23422
|
async function runTierStatusCliCommand(orchestrator) {
|
|
22619
23423
|
return orchestrator.getTierMigrationStatus();
|
|
22620
23424
|
}
|
|
@@ -22824,10 +23628,10 @@ function effectivePolicyValuesForVersion(values, config) {
|
|
|
22824
23628
|
}
|
|
22825
23629
|
function policyVersionForValues(values, config) {
|
|
22826
23630
|
const normalized = effectivePolicyValuesForVersion(values, config);
|
|
22827
|
-
return
|
|
23631
|
+
return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
22828
23632
|
}
|
|
22829
23633
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
22830
|
-
const filePath =
|
|
23634
|
+
const filePath = path48.join(config.memoryDir, "state", fileName);
|
|
22831
23635
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
22832
23636
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
22833
23637
|
});
|
|
@@ -23064,8 +23868,8 @@ async function runWebDavServeCliCommand(options) {
|
|
|
23064
23868
|
const current = activeWebDavServer.status();
|
|
23065
23869
|
if (current.running) return current;
|
|
23066
23870
|
}
|
|
23067
|
-
const
|
|
23068
|
-
const server = await
|
|
23871
|
+
const createServer3 = options.createServer ?? WebDavServer.create;
|
|
23872
|
+
const server = await createServer3({
|
|
23069
23873
|
enabled: options.enabled ?? true,
|
|
23070
23874
|
host: options.host,
|
|
23071
23875
|
port: options.port ?? 8080,
|
|
@@ -23096,6 +23900,47 @@ async function runWebDavStopCliCommand() {
|
|
|
23096
23900
|
return { stopped: true };
|
|
23097
23901
|
});
|
|
23098
23902
|
}
|
|
23903
|
+
async function runDashboardStartCliCommand(options) {
|
|
23904
|
+
return withDashboardLock(async () => {
|
|
23905
|
+
if (activeDashboardServer) {
|
|
23906
|
+
const status = activeDashboardServer.status();
|
|
23907
|
+
if (status.running) return status;
|
|
23908
|
+
}
|
|
23909
|
+
const createServer3 = options.createServer ?? ((opts) => new GraphDashboardServer({
|
|
23910
|
+
memoryDir: opts.memoryDir,
|
|
23911
|
+
host: opts.host,
|
|
23912
|
+
port: opts.port,
|
|
23913
|
+
publicDir: opts.publicDir
|
|
23914
|
+
}));
|
|
23915
|
+
const server = createServer3(options);
|
|
23916
|
+
activeDashboardServer = server;
|
|
23917
|
+
try {
|
|
23918
|
+
return await server.start();
|
|
23919
|
+
} catch (err) {
|
|
23920
|
+
if (activeDashboardServer === server) {
|
|
23921
|
+
activeDashboardServer = null;
|
|
23922
|
+
}
|
|
23923
|
+
throw err;
|
|
23924
|
+
}
|
|
23925
|
+
});
|
|
23926
|
+
}
|
|
23927
|
+
async function runDashboardStopCliCommand() {
|
|
23928
|
+
return withDashboardLock(async () => {
|
|
23929
|
+
if (!activeDashboardServer) return { stopped: false };
|
|
23930
|
+
const server = activeDashboardServer;
|
|
23931
|
+
await server.stop();
|
|
23932
|
+
if (activeDashboardServer === server) {
|
|
23933
|
+
activeDashboardServer = null;
|
|
23934
|
+
}
|
|
23935
|
+
return { stopped: true };
|
|
23936
|
+
});
|
|
23937
|
+
}
|
|
23938
|
+
async function runDashboardStatusCliCommand() {
|
|
23939
|
+
return withDashboardLock(async () => {
|
|
23940
|
+
if (!activeDashboardServer) return { running: false };
|
|
23941
|
+
return activeDashboardServer.status();
|
|
23942
|
+
});
|
|
23943
|
+
}
|
|
23099
23944
|
async function runCompatCliCommand(options = {}) {
|
|
23100
23945
|
const report = await runCompatChecks({
|
|
23101
23946
|
repoRoot: options.repoRoot ?? process.cwd(),
|
|
@@ -23286,7 +24131,7 @@ async function withTimeout(promise, timeoutMs, timeoutMessage) {
|
|
|
23286
24131
|
}
|
|
23287
24132
|
async function runReplayCliCommand(orchestrator, options) {
|
|
23288
24133
|
const extractionIdleTimeoutMs = Number.isFinite(options.extractionIdleTimeoutMs) ? Math.max(1e3, Math.floor(options.extractionIdleTimeoutMs)) : 15 * 6e4;
|
|
23289
|
-
const inputRaw = await
|
|
24134
|
+
const inputRaw = await readFile34(options.inputPath, "utf-8");
|
|
23290
24135
|
const registry = buildReplayNormalizerRegistry([
|
|
23291
24136
|
openclawReplayNormalizer,
|
|
23292
24137
|
claudeReplayNormalizer,
|
|
@@ -23351,7 +24196,7 @@ async function runReplayCliCommand(orchestrator, options) {
|
|
|
23351
24196
|
async function getPluginVersion() {
|
|
23352
24197
|
try {
|
|
23353
24198
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
23354
|
-
const raw = await
|
|
24199
|
+
const raw = await readFile34(pkgPath, "utf-8");
|
|
23355
24200
|
const parsed = JSON.parse(raw);
|
|
23356
24201
|
return parsed.version ?? "unknown";
|
|
23357
24202
|
} catch {
|
|
@@ -23370,32 +24215,32 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
23370
24215
|
const ns = (namespace ?? "").trim();
|
|
23371
24216
|
if (!ns) return orchestrator.config.memoryDir;
|
|
23372
24217
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
23373
|
-
const candidate =
|
|
24218
|
+
const candidate = path48.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
23374
24219
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
23375
24220
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
23376
24221
|
}
|
|
23377
24222
|
return candidate;
|
|
23378
24223
|
}
|
|
23379
24224
|
async function readAllMemoryFiles(memoryDir) {
|
|
23380
|
-
const roots = [
|
|
24225
|
+
const roots = [path48.join(memoryDir, "facts"), path48.join(memoryDir, "corrections")];
|
|
23381
24226
|
const out = [];
|
|
23382
24227
|
const walk = async (dir) => {
|
|
23383
24228
|
let entries;
|
|
23384
24229
|
try {
|
|
23385
|
-
entries = await
|
|
24230
|
+
entries = await readdir20(dir, { withFileTypes: true });
|
|
23386
24231
|
} catch {
|
|
23387
24232
|
return;
|
|
23388
24233
|
}
|
|
23389
24234
|
for (const entry of entries) {
|
|
23390
24235
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
23391
|
-
const fullPath =
|
|
24236
|
+
const fullPath = path48.join(dir, entryName);
|
|
23392
24237
|
if (entry.isDirectory()) {
|
|
23393
24238
|
await walk(fullPath);
|
|
23394
24239
|
continue;
|
|
23395
24240
|
}
|
|
23396
24241
|
if (!entry.isFile() || !entryName.endsWith(".md")) continue;
|
|
23397
24242
|
try {
|
|
23398
|
-
const raw = await
|
|
24243
|
+
const raw = await readFile34(fullPath, "utf-8");
|
|
23399
24244
|
const parsed = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
23400
24245
|
if (!parsed) continue;
|
|
23401
24246
|
const fmRaw = parsed[1];
|
|
@@ -23673,6 +24518,25 @@ function registerCli(api, orchestrator) {
|
|
|
23673
24518
|
console.log(JSON.stringify(report, null, 2));
|
|
23674
24519
|
console.log("OK");
|
|
23675
24520
|
});
|
|
24521
|
+
cmd.command("session-check").description("Analyze transcript/checkpoint continuity integrity without mutating files").action(async () => {
|
|
24522
|
+
const report = await runSessionCheckCliCommand({
|
|
24523
|
+
memoryDir: orchestrator.config.memoryDir
|
|
24524
|
+
});
|
|
24525
|
+
console.log(JSON.stringify(report, null, 2));
|
|
24526
|
+
console.log("OK");
|
|
24527
|
+
});
|
|
24528
|
+
cmd.command("session-repair").description("Generate/apply bounded Engram session integrity repairs (dry-run by default)").option("--apply", "Apply repairs (default: dry-run)").option("--dry-run", "Force dry-run output").option("--allow-session-file-repair", "Allow explicit OpenClaw session-file repair path (still no automatic rewiring)").option("--session-files-dir <path>", "Optional OpenClaw session files directory for guarded repair workflow").action(async (...args) => {
|
|
24529
|
+
const options = args[0] ?? {};
|
|
24530
|
+
const result = await runSessionRepairCliCommand({
|
|
24531
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
24532
|
+
apply: options.apply === true,
|
|
24533
|
+
dryRun: options.dryRun === true,
|
|
24534
|
+
allowSessionFileRepair: options.allowSessionFileRepair === true,
|
|
24535
|
+
sessionFilesDir: typeof options.sessionFilesDir === "string" && options.sessionFilesDir.trim().length > 0 ? options.sessionFilesDir.trim() : void 0
|
|
24536
|
+
});
|
|
24537
|
+
console.log(JSON.stringify(result, null, 2));
|
|
24538
|
+
console.log("OK");
|
|
24539
|
+
});
|
|
23676
24540
|
cmd.command("tier-status").description("Show tier migration telemetry and last-cycle summary").action(async () => {
|
|
23677
24541
|
const status = await runTierStatusCliCommand(orchestrator);
|
|
23678
24542
|
console.log(JSON.stringify(status, null, 2));
|
|
@@ -23811,6 +24675,29 @@ function registerCli(api, orchestrator) {
|
|
|
23811
24675
|
console.log(JSON.stringify(result, null, 2));
|
|
23812
24676
|
console.log("OK");
|
|
23813
24677
|
});
|
|
24678
|
+
const dashboardCmd = cmd.command("dashboard").description("Manage live graph dashboard service");
|
|
24679
|
+
dashboardCmd.command("start").description("Start dashboard server (localhost by default)").option("--host <host>", "Bind host", "127.0.0.1").option("--port <n>", "Bind port", "4319").option("--public-dir <path>", "Override static dashboard assets path").action(async (...args) => {
|
|
24680
|
+
const options = args[0] ?? {};
|
|
24681
|
+
const portRaw = parseInt(String(options.port ?? "4319"), 10);
|
|
24682
|
+
const status = await runDashboardStartCliCommand({
|
|
24683
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
24684
|
+
host: typeof options.host === "string" ? options.host : "127.0.0.1",
|
|
24685
|
+
port: Number.isFinite(portRaw) ? portRaw : 4319,
|
|
24686
|
+
publicDir: typeof options.publicDir === "string" ? options.publicDir : void 0
|
|
24687
|
+
});
|
|
24688
|
+
console.log(JSON.stringify(status, null, 2));
|
|
24689
|
+
console.log("OK");
|
|
24690
|
+
});
|
|
24691
|
+
dashboardCmd.command("stop").description("Stop dashboard server").action(async () => {
|
|
24692
|
+
const result = await runDashboardStopCliCommand();
|
|
24693
|
+
console.log(JSON.stringify(result, null, 2));
|
|
24694
|
+
console.log("OK");
|
|
24695
|
+
});
|
|
24696
|
+
dashboardCmd.command("status").description("Show dashboard server status").action(async () => {
|
|
24697
|
+
const status = await runDashboardStatusCliCommand();
|
|
24698
|
+
console.log(JSON.stringify(status, null, 2));
|
|
24699
|
+
console.log("OK");
|
|
24700
|
+
});
|
|
23814
24701
|
const routeCmd = cmd.command("route").description("Manage custom memory routing rules");
|
|
23815
24702
|
routeCmd.command("list").description("List configured routing rules").action(async () => {
|
|
23816
24703
|
const rules = await runRouteCliCommand({
|
|
@@ -24051,7 +24938,7 @@ function registerCli(api, orchestrator) {
|
|
|
24051
24938
|
let deleted = 0;
|
|
24052
24939
|
for (const filePath of plan.deletePaths) {
|
|
24053
24940
|
try {
|
|
24054
|
-
await
|
|
24941
|
+
await unlink6(filePath);
|
|
24055
24942
|
deleted += 1;
|
|
24056
24943
|
} catch (err) {
|
|
24057
24944
|
console.log(` failed to delete ${filePath}: ${String(err)}`);
|
|
@@ -24099,7 +24986,7 @@ function registerCli(api, orchestrator) {
|
|
|
24099
24986
|
let deleted = 0;
|
|
24100
24987
|
for (const filePath of plan.deletePaths) {
|
|
24101
24988
|
try {
|
|
24102
|
-
await
|
|
24989
|
+
await unlink6(filePath);
|
|
24103
24990
|
deleted += 1;
|
|
24104
24991
|
} catch (err) {
|
|
24105
24992
|
console.log(` failed to delete ${filePath}: ${String(err)}`);
|
|
@@ -24263,7 +25150,7 @@ function registerCli(api, orchestrator) {
|
|
|
24263
25150
|
}
|
|
24264
25151
|
});
|
|
24265
25152
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
24266
|
-
const workspaceDir =
|
|
25153
|
+
const workspaceDir = path48.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
24267
25154
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
24268
25155
|
if (!identity) {
|
|
24269
25156
|
console.log("No identity file found.");
|
|
@@ -24486,8 +25373,8 @@ function registerCli(api, orchestrator) {
|
|
|
24486
25373
|
const options = args[0] ?? {};
|
|
24487
25374
|
const threadId = options.thread;
|
|
24488
25375
|
const top = parseInt(options.top ?? "10", 10);
|
|
24489
|
-
const memoryDir =
|
|
24490
|
-
const threading = new ThreadingManager(
|
|
25376
|
+
const memoryDir = path48.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
25377
|
+
const threading = new ThreadingManager(path48.join(memoryDir, "threads"));
|
|
24491
25378
|
if (threadId) {
|
|
24492
25379
|
const thread = await threading.loadThread(threadId);
|
|
24493
25380
|
if (!thread) {
|
|
@@ -24660,16 +25547,16 @@ function parseDuration(duration) {
|
|
|
24660
25547
|
}
|
|
24661
25548
|
|
|
24662
25549
|
// src/index.ts
|
|
24663
|
-
import { readFile as
|
|
25550
|
+
import { readFile as readFile35, writeFile as writeFile28 } from "fs/promises";
|
|
24664
25551
|
import { readFileSync as readFileSync4 } from "fs";
|
|
24665
|
-
import
|
|
25552
|
+
import path49 from "path";
|
|
24666
25553
|
import os5 from "os";
|
|
24667
25554
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
24668
25555
|
function loadPluginConfigFromFile() {
|
|
24669
25556
|
try {
|
|
24670
25557
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
24671
25558
|
const homeDir = process.env.HOME ?? os5.homedir();
|
|
24672
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
25559
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path49.join(homeDir, ".openclaw", "openclaw.json");
|
|
24673
25560
|
const content = readFileSync4(configPath, "utf-8");
|
|
24674
25561
|
const config = JSON.parse(content);
|
|
24675
25562
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
@@ -24877,11 +25764,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
24877
25764
|
);
|
|
24878
25765
|
async function ensureHourlySummaryCron(api2) {
|
|
24879
25766
|
const jobId = "engram-hourly-summary";
|
|
24880
|
-
const cronFilePath =
|
|
25767
|
+
const cronFilePath = path49.join(os5.homedir(), ".openclaw", "cron", "jobs.json");
|
|
24881
25768
|
try {
|
|
24882
25769
|
let jobsData = { version: 1, jobs: [] };
|
|
24883
25770
|
try {
|
|
24884
|
-
const content = await
|
|
25771
|
+
const content = await readFile35(cronFilePath, "utf-8");
|
|
24885
25772
|
jobsData = JSON.parse(content);
|
|
24886
25773
|
} catch {
|
|
24887
25774
|
}
|
|
@@ -24918,7 +25805,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
24918
25805
|
state: {}
|
|
24919
25806
|
};
|
|
24920
25807
|
jobsData.jobs.push(newJob);
|
|
24921
|
-
await
|
|
25808
|
+
await writeFile28(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
|
|
24922
25809
|
log.info("auto-registered hourly summary cron job");
|
|
24923
25810
|
} catch (err) {
|
|
24924
25811
|
log.error("failed to auto-register hourly summary cron job:", err);
|