@joshuaswarren/openclaw-engram 8.3.25 → 8.3.26
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 +722 -282
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +44 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,18 @@ import {
|
|
|
6
6
|
|
|
7
7
|
// src/config.ts
|
|
8
8
|
import path from "path";
|
|
9
|
+
|
|
10
|
+
// src/session-observer-bands.ts
|
|
11
|
+
var DEFAULT_SESSION_OBSERVER_BANDS = [
|
|
12
|
+
{ maxBytes: 5e4, triggerDeltaBytes: 4800, triggerDeltaTokens: 1200 },
|
|
13
|
+
{ maxBytes: 2e5, triggerDeltaBytes: 9600, triggerDeltaTokens: 2400 },
|
|
14
|
+
{ maxBytes: 1e9, triggerDeltaBytes: 19200, triggerDeltaTokens: 4800 }
|
|
15
|
+
];
|
|
16
|
+
function cloneDefaultSessionObserverBands() {
|
|
17
|
+
return DEFAULT_SESSION_OBSERVER_BANDS.map((band) => ({ ...band }));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/config.ts
|
|
9
21
|
var DEFAULT_MEMORY_DIR = path.join(
|
|
10
22
|
process.env.HOME ?? "~",
|
|
11
23
|
".openclaw",
|
|
@@ -81,6 +93,11 @@ function parseConfig(raw) {
|
|
|
81
93
|
const rawIdentityInjectionMode = cfg.identityInjectionMode;
|
|
82
94
|
const identityInjectionMode = rawIdentityInjectionMode && VALID_IDENTITY_INJECTION_MODES.includes(rawIdentityInjectionMode) ? rawIdentityInjectionMode : "recovery_only";
|
|
83
95
|
const identityContinuityEnabled = cfg.identityContinuityEnabled === true;
|
|
96
|
+
const sessionObserverBands = Array.isArray(cfg.sessionObserverBands) ? cfg.sessionObserverBands.map((band) => ({
|
|
97
|
+
maxBytes: typeof band?.maxBytes === "number" ? Math.max(0, Math.floor(band.maxBytes)) : 0,
|
|
98
|
+
triggerDeltaBytes: typeof band?.triggerDeltaBytes === "number" ? Math.max(0, Math.floor(band.triggerDeltaBytes)) : 0,
|
|
99
|
+
triggerDeltaTokens: typeof band?.triggerDeltaTokens === "number" ? Math.max(0, Math.floor(band.triggerDeltaTokens)) : 0
|
|
100
|
+
})).filter((band) => band.maxBytes > 0) : cloneDefaultSessionObserverBands();
|
|
84
101
|
const principalRules = Array.isArray(cfg.principalFromSessionKeyRules) ? cfg.principalFromSessionKeyRules.map((r) => ({
|
|
85
102
|
match: typeof r?.match === "string" ? r.match : "",
|
|
86
103
|
principal: typeof r?.principal === "string" ? r.principal : ""
|
|
@@ -138,6 +155,9 @@ function parseConfig(raw) {
|
|
|
138
155
|
identityMaxInjectChars: typeof cfg.identityMaxInjectChars === "number" ? Math.max(0, Math.floor(cfg.identityMaxInjectChars)) : 1200,
|
|
139
156
|
continuityIncidentLoggingEnabled: typeof cfg.continuityIncidentLoggingEnabled === "boolean" ? cfg.continuityIncidentLoggingEnabled : identityContinuityEnabled,
|
|
140
157
|
continuityAuditEnabled: cfg.continuityAuditEnabled === true,
|
|
158
|
+
sessionObserverEnabled: cfg.sessionObserverEnabled === true,
|
|
159
|
+
sessionObserverDebounceMs: typeof cfg.sessionObserverDebounceMs === "number" ? Math.max(0, Math.floor(cfg.sessionObserverDebounceMs)) : 12e4,
|
|
160
|
+
sessionObserverBands,
|
|
141
161
|
injectQuestions: cfg.injectQuestions === true,
|
|
142
162
|
commitmentDecayDays: typeof cfg.commitmentDecayDays === "number" ? cfg.commitmentDecayDays : 90,
|
|
143
163
|
workspaceDir: typeof cfg.workspaceDir === "string" && cfg.workspaceDir.length > 0 ? cfg.workspaceDir : DEFAULT_WORKSPACE_DIR,
|
|
@@ -394,9 +414,9 @@ function parseConfig(raw) {
|
|
|
394
414
|
}
|
|
395
415
|
|
|
396
416
|
// src/orchestrator.ts
|
|
397
|
-
import
|
|
417
|
+
import path23 from "path";
|
|
398
418
|
import { createHash as createHash4 } from "crypto";
|
|
399
|
-
import { mkdir as
|
|
419
|
+
import { mkdir as mkdir17, readFile as readFile17, writeFile as writeFile16 } from "fs/promises";
|
|
400
420
|
|
|
401
421
|
// src/signal.ts
|
|
402
422
|
var BUILTIN_HIGH_PATTERNS = [
|
|
@@ -7054,7 +7074,7 @@ function extractTopics(memories, topN = 50) {
|
|
|
7054
7074
|
}
|
|
7055
7075
|
|
|
7056
7076
|
// src/transcript.ts
|
|
7057
|
-
import { appendFile as appendFile2, mkdir as mkdir4, readdir as readdir3, readFile as readFile4, unlink as unlink2, writeFile as writeFile4 } from "fs/promises";
|
|
7077
|
+
import { appendFile as appendFile2, mkdir as mkdir4, readdir as readdir3, readFile as readFile4, stat as stat2, unlink as unlink2, writeFile as writeFile4 } from "fs/promises";
|
|
7058
7078
|
import path6 from "path";
|
|
7059
7079
|
var TranscriptManager = class _TranscriptManager {
|
|
7060
7080
|
transcriptsDir;
|
|
@@ -7062,6 +7082,7 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7062
7082
|
stateDir;
|
|
7063
7083
|
toolUsageDir;
|
|
7064
7084
|
config;
|
|
7085
|
+
sessionFootprintCache = /* @__PURE__ */ new Map();
|
|
7065
7086
|
/** Default checkpoint TTL in hours */
|
|
7066
7087
|
static DEFAULT_CHECKPOINT_TTL_HOURS = 24;
|
|
7067
7088
|
/** Approximate characters per token for rough estimation */
|
|
@@ -7077,10 +7098,10 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7077
7098
|
* Parse a sessionKey to extract channel type and ID.
|
|
7078
7099
|
*
|
|
7079
7100
|
* SessionKey patterns:
|
|
7080
|
-
* - agent
|
|
7081
|
-
* - agent
|
|
7082
|
-
* - agent
|
|
7083
|
-
* - agent
|
|
7101
|
+
* - agent:<agent-id>:main → type="main", id="default"
|
|
7102
|
+
* - agent:<agent-id>:discord:channel:<channel-id> → type="discord", id="<channel-id>"
|
|
7103
|
+
* - agent:<agent-id>:cron:<job-id> → type="cron", id="<job-id>"
|
|
7104
|
+
* - agent:<agent-id>:slack:channel:<channel-id> → type="slack", id="<channel-id>"
|
|
7084
7105
|
*
|
|
7085
7106
|
* @returns Object with dir (channel type/channel id) and file (YYYY-MM-DD.jsonl)
|
|
7086
7107
|
*/
|
|
@@ -7195,6 +7216,101 @@ var TranscriptManager = class _TranscriptManager {
|
|
|
7195
7216
|
return [];
|
|
7196
7217
|
}
|
|
7197
7218
|
}
|
|
7219
|
+
async estimateSessionFootprint(sessionKey) {
|
|
7220
|
+
const { dir } = this.getTranscriptPath(sessionKey);
|
|
7221
|
+
const channelDir = path6.join(this.transcriptsDir, dir);
|
|
7222
|
+
let bytes = 0;
|
|
7223
|
+
try {
|
|
7224
|
+
const files = (await readdir3(channelDir)).filter((file) => file.endsWith(".jsonl")).sort();
|
|
7225
|
+
const cached = this.sessionFootprintCache.get(sessionKey);
|
|
7226
|
+
if (!cached) {
|
|
7227
|
+
const fileBytes = /* @__PURE__ */ new Map();
|
|
7228
|
+
const fileSizes = /* @__PURE__ */ new Map();
|
|
7229
|
+
for (const file of files) {
|
|
7230
|
+
try {
|
|
7231
|
+
const fullPath = path6.join(channelDir, file);
|
|
7232
|
+
const fileInfo = await stat2(fullPath);
|
|
7233
|
+
const sessionBytes = await this.estimateSessionBytesInFile(
|
|
7234
|
+
fullPath,
|
|
7235
|
+
sessionKey
|
|
7236
|
+
);
|
|
7237
|
+
fileBytes.set(file, sessionBytes);
|
|
7238
|
+
fileSizes.set(file, Math.max(0, fileInfo.size));
|
|
7239
|
+
bytes += sessionBytes;
|
|
7240
|
+
} catch {
|
|
7241
|
+
}
|
|
7242
|
+
}
|
|
7243
|
+
this.sessionFootprintCache.set(sessionKey, { totalBytes: bytes, fileBytes, fileSizes });
|
|
7244
|
+
} else {
|
|
7245
|
+
bytes = cached.totalBytes;
|
|
7246
|
+
const seen = new Set(files);
|
|
7247
|
+
for (const [cachedFile, cachedSessionBytes] of cached.fileBytes.entries()) {
|
|
7248
|
+
if (!seen.has(cachedFile)) {
|
|
7249
|
+
bytes -= cachedSessionBytes;
|
|
7250
|
+
cached.fileBytes.delete(cachedFile);
|
|
7251
|
+
cached.fileSizes.delete(cachedFile);
|
|
7252
|
+
}
|
|
7253
|
+
}
|
|
7254
|
+
for (const file of files) {
|
|
7255
|
+
if (cached.fileBytes.has(file)) continue;
|
|
7256
|
+
try {
|
|
7257
|
+
const fullPath = path6.join(channelDir, file);
|
|
7258
|
+
const fileInfo = await stat2(fullPath);
|
|
7259
|
+
const sessionBytes = await this.estimateSessionBytesInFile(fullPath, sessionKey);
|
|
7260
|
+
cached.fileBytes.set(file, sessionBytes);
|
|
7261
|
+
cached.fileSizes.set(file, Math.max(0, fileInfo.size));
|
|
7262
|
+
bytes += sessionBytes;
|
|
7263
|
+
} catch {
|
|
7264
|
+
}
|
|
7265
|
+
}
|
|
7266
|
+
const newestFile = files[files.length - 1];
|
|
7267
|
+
if (newestFile) {
|
|
7268
|
+
try {
|
|
7269
|
+
const newestPath = path6.join(channelDir, newestFile);
|
|
7270
|
+
const fileInfo = await stat2(newestPath);
|
|
7271
|
+
const size = Math.max(0, fileInfo.size);
|
|
7272
|
+
const previousSessionBytes = cached.fileBytes.get(newestFile) ?? 0;
|
|
7273
|
+
const previousSize = cached.fileSizes.get(newestFile) ?? -1;
|
|
7274
|
+
if (size !== previousSize) {
|
|
7275
|
+
const sessionBytes = await this.estimateSessionBytesInFile(newestPath, sessionKey);
|
|
7276
|
+
cached.fileBytes.set(newestFile, sessionBytes);
|
|
7277
|
+
cached.fileSizes.set(newestFile, size);
|
|
7278
|
+
bytes += sessionBytes - previousSessionBytes;
|
|
7279
|
+
}
|
|
7280
|
+
} catch {
|
|
7281
|
+
}
|
|
7282
|
+
}
|
|
7283
|
+
if (bytes < 0) bytes = 0;
|
|
7284
|
+
cached.totalBytes = bytes;
|
|
7285
|
+
}
|
|
7286
|
+
} catch {
|
|
7287
|
+
this.sessionFootprintCache.delete(sessionKey);
|
|
7288
|
+
}
|
|
7289
|
+
return {
|
|
7290
|
+
bytes,
|
|
7291
|
+
tokens: Math.floor(bytes / _TranscriptManager.CHARS_PER_TOKEN)
|
|
7292
|
+
};
|
|
7293
|
+
}
|
|
7294
|
+
async estimateSessionBytesInFile(filePath, sessionKey) {
|
|
7295
|
+
try {
|
|
7296
|
+
const raw = await readFile4(filePath, "utf-8");
|
|
7297
|
+
let total = 0;
|
|
7298
|
+
for (const line of raw.split("\n")) {
|
|
7299
|
+
if (!line.trim()) continue;
|
|
7300
|
+
try {
|
|
7301
|
+
const parsed = JSON.parse(line);
|
|
7302
|
+
if (parsed.sessionKey === sessionKey) {
|
|
7303
|
+
total += Buffer.byteLength(`${line}
|
|
7304
|
+
`, "utf-8");
|
|
7305
|
+
}
|
|
7306
|
+
} catch {
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
7309
|
+
return total;
|
|
7310
|
+
} catch {
|
|
7311
|
+
return 0;
|
|
7312
|
+
}
|
|
7313
|
+
}
|
|
7198
7314
|
/**
|
|
7199
7315
|
* Check if a file is a legacy flat transcript file (YYYY-MM-DD.jsonl format).
|
|
7200
7316
|
*/
|
|
@@ -8452,14 +8568,278 @@ var LastRecallStore = class {
|
|
|
8452
8568
|
}
|
|
8453
8569
|
};
|
|
8454
8570
|
|
|
8455
|
-
// src/
|
|
8571
|
+
// src/session-observer-state.ts
|
|
8456
8572
|
import path11 from "path";
|
|
8457
|
-
import { mkdir as mkdir9, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
|
|
8573
|
+
import { mkdir as mkdir9, open, readFile as readFile9, stat as stat3, unlink as unlink3, writeFile as writeFile9 } from "fs/promises";
|
|
8574
|
+
function sanitizeNonNegativeInt(value) {
|
|
8575
|
+
if (!Number.isFinite(value)) return 0;
|
|
8576
|
+
return Math.max(0, Math.floor(value));
|
|
8577
|
+
}
|
|
8578
|
+
function parseIsoMs(value) {
|
|
8579
|
+
if (!value) return 0;
|
|
8580
|
+
const ms = Date.parse(value);
|
|
8581
|
+
return Number.isFinite(ms) ? ms : 0;
|
|
8582
|
+
}
|
|
8583
|
+
function mergeSessionCursor(existing, incoming) {
|
|
8584
|
+
const existingObservedMs = parseIsoMs(existing.lastObservedAt);
|
|
8585
|
+
const incomingObservedMs = parseIsoMs(incoming.lastObservedAt);
|
|
8586
|
+
const existingTriggeredMs = parseIsoMs(existing.lastTriggeredAt);
|
|
8587
|
+
const incomingTriggeredMs = parseIsoMs(incoming.lastTriggeredAt);
|
|
8588
|
+
const existingResetMs = parseIsoMs(existing.lastResetAt);
|
|
8589
|
+
const incomingResetMs = parseIsoMs(incoming.lastResetAt);
|
|
8590
|
+
const observedAt = incomingObservedMs >= existingObservedMs ? incoming.lastObservedAt : existing.lastObservedAt;
|
|
8591
|
+
const triggeredAt = incomingTriggeredMs >= existingTriggeredMs ? incoming.lastTriggeredAt : existing.lastTriggeredAt;
|
|
8592
|
+
const incomingIsNewer = incomingObservedMs >= existingObservedMs;
|
|
8593
|
+
const incomingHasNewerReset = incomingResetMs > existingResetMs;
|
|
8594
|
+
const allowIncomingReset = incomingIsNewer && incomingHasNewerReset;
|
|
8595
|
+
const keepExistingReset = existingResetMs > incomingResetMs && existingObservedMs >= incomingObservedMs;
|
|
8596
|
+
let cursorBytes = Math.max(
|
|
8597
|
+
sanitizeNonNegativeInt(existing.cursorBytes),
|
|
8598
|
+
sanitizeNonNegativeInt(incoming.cursorBytes)
|
|
8599
|
+
);
|
|
8600
|
+
let cursorTokens = Math.max(
|
|
8601
|
+
sanitizeNonNegativeInt(existing.cursorTokens),
|
|
8602
|
+
sanitizeNonNegativeInt(incoming.cursorTokens)
|
|
8603
|
+
);
|
|
8604
|
+
if (keepExistingReset) {
|
|
8605
|
+
cursorBytes = sanitizeNonNegativeInt(existing.cursorBytes);
|
|
8606
|
+
cursorTokens = sanitizeNonNegativeInt(existing.cursorTokens);
|
|
8607
|
+
} else if (allowIncomingReset) {
|
|
8608
|
+
cursorBytes = sanitizeNonNegativeInt(incoming.cursorBytes);
|
|
8609
|
+
cursorTokens = sanitizeNonNegativeInt(incoming.cursorTokens);
|
|
8610
|
+
}
|
|
8611
|
+
return {
|
|
8612
|
+
sessionKey: existing.sessionKey,
|
|
8613
|
+
cursorBytes,
|
|
8614
|
+
cursorTokens,
|
|
8615
|
+
lastObservedAt: observedAt,
|
|
8616
|
+
lastTriggeredAt: triggeredAt,
|
|
8617
|
+
lastResetAt: incomingResetMs >= existingResetMs ? incoming.lastResetAt : existing.lastResetAt
|
|
8618
|
+
};
|
|
8619
|
+
}
|
|
8620
|
+
function normalizeObserverBands(bands) {
|
|
8621
|
+
const normalized = bands.map((band) => ({
|
|
8622
|
+
maxBytes: sanitizeNonNegativeInt(band.maxBytes),
|
|
8623
|
+
triggerDeltaBytes: sanitizeNonNegativeInt(band.triggerDeltaBytes),
|
|
8624
|
+
triggerDeltaTokens: sanitizeNonNegativeInt(band.triggerDeltaTokens)
|
|
8625
|
+
})).filter((band) => band.maxBytes > 0).sort((a, b) => a.maxBytes - b.maxBytes);
|
|
8626
|
+
if (normalized.length === 0) {
|
|
8627
|
+
return cloneDefaultSessionObserverBands();
|
|
8628
|
+
}
|
|
8629
|
+
const last = normalized[normalized.length - 1];
|
|
8630
|
+
if (last && last.maxBytes < 1e9) {
|
|
8631
|
+
normalized.push({
|
|
8632
|
+
maxBytes: 1e9,
|
|
8633
|
+
triggerDeltaBytes: last.triggerDeltaBytes,
|
|
8634
|
+
triggerDeltaTokens: last.triggerDeltaTokens
|
|
8635
|
+
});
|
|
8636
|
+
}
|
|
8637
|
+
return normalized;
|
|
8638
|
+
}
|
|
8639
|
+
var SessionObserverState = class {
|
|
8640
|
+
statePath;
|
|
8641
|
+
lockPath;
|
|
8642
|
+
lockStaleMs = 12e4;
|
|
8643
|
+
debounceMs;
|
|
8644
|
+
bands;
|
|
8645
|
+
sessions = /* @__PURE__ */ new Map();
|
|
8646
|
+
saveQueue = Promise.resolve();
|
|
8647
|
+
async readPersistedState() {
|
|
8648
|
+
try {
|
|
8649
|
+
const raw = await readFile9(this.statePath, "utf-8");
|
|
8650
|
+
const parsed = JSON.parse(raw);
|
|
8651
|
+
if (parsed?.version !== 1 || !parsed.sessions || typeof parsed.sessions !== "object") {
|
|
8652
|
+
return null;
|
|
8653
|
+
}
|
|
8654
|
+
return parsed;
|
|
8655
|
+
} catch {
|
|
8656
|
+
return null;
|
|
8657
|
+
}
|
|
8658
|
+
}
|
|
8659
|
+
normalizePersistedSessions(sessions) {
|
|
8660
|
+
const next = /* @__PURE__ */ new Map();
|
|
8661
|
+
for (const [sessionKey, value] of Object.entries(sessions)) {
|
|
8662
|
+
if (!value || typeof value !== "object") continue;
|
|
8663
|
+
next.set(sessionKey, {
|
|
8664
|
+
sessionKey,
|
|
8665
|
+
cursorBytes: sanitizeNonNegativeInt(value.cursorBytes),
|
|
8666
|
+
cursorTokens: sanitizeNonNegativeInt(value.cursorTokens),
|
|
8667
|
+
lastObservedAt: typeof value.lastObservedAt === "string" ? value.lastObservedAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
8668
|
+
lastTriggeredAt: typeof value.lastTriggeredAt === "string" ? value.lastTriggeredAt : void 0,
|
|
8669
|
+
lastResetAt: typeof value.lastResetAt === "string" ? value.lastResetAt : void 0
|
|
8670
|
+
});
|
|
8671
|
+
}
|
|
8672
|
+
return next;
|
|
8673
|
+
}
|
|
8674
|
+
constructor(opts) {
|
|
8675
|
+
this.statePath = path11.join(opts.memoryDir, "state", "session-observer-state.json");
|
|
8676
|
+
this.lockPath = path11.join(opts.memoryDir, "state", "session-observer-state.lock");
|
|
8677
|
+
this.debounceMs = Math.max(0, Math.floor(opts.debounceMs));
|
|
8678
|
+
this.bands = normalizeObserverBands(opts.bands);
|
|
8679
|
+
}
|
|
8680
|
+
async withSaveLock(fn) {
|
|
8681
|
+
await mkdir9(path11.dirname(this.lockPath), { recursive: true });
|
|
8682
|
+
for (let attempt = 0; attempt < 80; attempt++) {
|
|
8683
|
+
try {
|
|
8684
|
+
const handle = await open(this.lockPath, "wx");
|
|
8685
|
+
try {
|
|
8686
|
+
await fn();
|
|
8687
|
+
} finally {
|
|
8688
|
+
await handle.close();
|
|
8689
|
+
await unlink3(this.lockPath).catch(() => {
|
|
8690
|
+
});
|
|
8691
|
+
}
|
|
8692
|
+
return;
|
|
8693
|
+
} catch (err) {
|
|
8694
|
+
if (err?.code !== "EEXIST") throw err;
|
|
8695
|
+
try {
|
|
8696
|
+
const lockInfo = await stat3(this.lockPath);
|
|
8697
|
+
if (Date.now() - lockInfo.mtimeMs > this.lockStaleMs) {
|
|
8698
|
+
await unlink3(this.lockPath).catch(() => {
|
|
8699
|
+
});
|
|
8700
|
+
continue;
|
|
8701
|
+
}
|
|
8702
|
+
} catch {
|
|
8703
|
+
}
|
|
8704
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
8705
|
+
}
|
|
8706
|
+
}
|
|
8707
|
+
const error = new Error("session observer save lock timeout");
|
|
8708
|
+
log.debug(error.message);
|
|
8709
|
+
throw error;
|
|
8710
|
+
}
|
|
8711
|
+
async load() {
|
|
8712
|
+
const parsed = await this.readPersistedState();
|
|
8713
|
+
if (!parsed) {
|
|
8714
|
+
this.sessions.clear();
|
|
8715
|
+
return;
|
|
8716
|
+
}
|
|
8717
|
+
this.sessions = this.normalizePersistedSessions(parsed.sessions);
|
|
8718
|
+
}
|
|
8719
|
+
async save() {
|
|
8720
|
+
await this.withSaveLock(async () => {
|
|
8721
|
+
const merged = /* @__PURE__ */ new Map();
|
|
8722
|
+
const persisted = await this.readPersistedState();
|
|
8723
|
+
if (persisted) {
|
|
8724
|
+
for (const [key, value] of this.normalizePersistedSessions(persisted.sessions).entries()) {
|
|
8725
|
+
merged.set(key, value);
|
|
8726
|
+
}
|
|
8727
|
+
}
|
|
8728
|
+
for (const [key, current] of this.sessions.entries()) {
|
|
8729
|
+
const existing = merged.get(key);
|
|
8730
|
+
if (!existing) {
|
|
8731
|
+
merged.set(key, current);
|
|
8732
|
+
continue;
|
|
8733
|
+
}
|
|
8734
|
+
merged.set(key, mergeSessionCursor(existing, current));
|
|
8735
|
+
}
|
|
8736
|
+
this.sessions = merged;
|
|
8737
|
+
const sessions = {};
|
|
8738
|
+
for (const [key, value] of merged.entries()) {
|
|
8739
|
+
sessions[key] = value;
|
|
8740
|
+
}
|
|
8741
|
+
const payload = { version: 1, sessions };
|
|
8742
|
+
await mkdir9(path11.dirname(this.statePath), { recursive: true });
|
|
8743
|
+
await writeFile9(this.statePath, JSON.stringify(payload, null, 2), "utf-8");
|
|
8744
|
+
});
|
|
8745
|
+
}
|
|
8746
|
+
enqueueSave() {
|
|
8747
|
+
this.saveQueue = this.saveQueue.catch(() => void 0).then(() => this.save());
|
|
8748
|
+
return this.saveQueue;
|
|
8749
|
+
}
|
|
8750
|
+
bandForTotalBytes(totalBytes) {
|
|
8751
|
+
const bytes = sanitizeNonNegativeInt(totalBytes);
|
|
8752
|
+
for (const band of this.bands) {
|
|
8753
|
+
if (bytes <= band.maxBytes) return band;
|
|
8754
|
+
}
|
|
8755
|
+
return this.bands[this.bands.length - 1];
|
|
8756
|
+
}
|
|
8757
|
+
async observe(input) {
|
|
8758
|
+
const nowIso = input.observedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
8759
|
+
const totalBytes = sanitizeNonNegativeInt(input.totalBytes);
|
|
8760
|
+
const totalTokens = sanitizeNonNegativeInt(input.totalTokens);
|
|
8761
|
+
const band = this.bandForTotalBytes(totalBytes);
|
|
8762
|
+
const existing = this.sessions.get(input.sessionKey);
|
|
8763
|
+
if (!existing) {
|
|
8764
|
+
this.sessions.set(input.sessionKey, {
|
|
8765
|
+
sessionKey: input.sessionKey,
|
|
8766
|
+
cursorBytes: totalBytes,
|
|
8767
|
+
cursorTokens: totalTokens,
|
|
8768
|
+
lastObservedAt: nowIso
|
|
8769
|
+
});
|
|
8770
|
+
await this.enqueueSave();
|
|
8771
|
+
return {
|
|
8772
|
+
triggered: false,
|
|
8773
|
+
deltaBytes: 0,
|
|
8774
|
+
deltaTokens: 0,
|
|
8775
|
+
band,
|
|
8776
|
+
reason: "baseline"
|
|
8777
|
+
};
|
|
8778
|
+
}
|
|
8779
|
+
const session = { ...existing };
|
|
8780
|
+
if (totalBytes < session.cursorBytes || totalTokens < session.cursorTokens) {
|
|
8781
|
+
session.cursorBytes = totalBytes;
|
|
8782
|
+
session.cursorTokens = totalTokens;
|
|
8783
|
+
session.lastObservedAt = nowIso;
|
|
8784
|
+
session.lastResetAt = nowIso;
|
|
8785
|
+
this.sessions.set(input.sessionKey, session);
|
|
8786
|
+
await this.enqueueSave();
|
|
8787
|
+
return { triggered: false, deltaBytes: 0, deltaTokens: 0, band, reason: "baseline" };
|
|
8788
|
+
}
|
|
8789
|
+
const deltaBytes = totalBytes - session.cursorBytes;
|
|
8790
|
+
const deltaTokens = totalTokens - session.cursorTokens;
|
|
8791
|
+
const crossedThreshold = band.triggerDeltaBytes > 0 && deltaBytes >= band.triggerDeltaBytes || band.triggerDeltaTokens > 0 && deltaTokens >= band.triggerDeltaTokens;
|
|
8792
|
+
session.lastObservedAt = nowIso;
|
|
8793
|
+
if (!crossedThreshold) {
|
|
8794
|
+
const unchanged = deltaBytes === 0 && deltaTokens === 0;
|
|
8795
|
+
if (!unchanged) {
|
|
8796
|
+
this.sessions.set(input.sessionKey, session);
|
|
8797
|
+
await this.enqueueSave();
|
|
8798
|
+
}
|
|
8799
|
+
return {
|
|
8800
|
+
triggered: false,
|
|
8801
|
+
deltaBytes,
|
|
8802
|
+
deltaTokens,
|
|
8803
|
+
band
|
|
8804
|
+
};
|
|
8805
|
+
}
|
|
8806
|
+
const nowMs = Date.parse(nowIso);
|
|
8807
|
+
const lastTriggeredMs = session.lastTriggeredAt ? Date.parse(session.lastTriggeredAt) : NaN;
|
|
8808
|
+
const withinDebounce = Number.isFinite(lastTriggeredMs) && nowMs - lastTriggeredMs < this.debounceMs;
|
|
8809
|
+
if (withinDebounce) {
|
|
8810
|
+
this.sessions.set(input.sessionKey, session);
|
|
8811
|
+
await this.enqueueSave();
|
|
8812
|
+
return {
|
|
8813
|
+
triggered: false,
|
|
8814
|
+
deltaBytes,
|
|
8815
|
+
deltaTokens,
|
|
8816
|
+
band,
|
|
8817
|
+
reason: "debounced"
|
|
8818
|
+
};
|
|
8819
|
+
}
|
|
8820
|
+
session.lastTriggeredAt = nowIso;
|
|
8821
|
+
session.cursorBytes = totalBytes;
|
|
8822
|
+
session.cursorTokens = totalTokens;
|
|
8823
|
+
this.sessions.set(input.sessionKey, session);
|
|
8824
|
+
await this.enqueueSave();
|
|
8825
|
+
return {
|
|
8826
|
+
triggered: true,
|
|
8827
|
+
deltaBytes,
|
|
8828
|
+
deltaTokens,
|
|
8829
|
+
band,
|
|
8830
|
+
reason: "threshold"
|
|
8831
|
+
};
|
|
8832
|
+
}
|
|
8833
|
+
};
|
|
8834
|
+
|
|
8835
|
+
// src/embedding-fallback.ts
|
|
8836
|
+
import path12 from "path";
|
|
8837
|
+
import { mkdir as mkdir10, readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
|
|
8458
8838
|
var DEFAULT_OPENAI_MODEL = "text-embedding-3-small";
|
|
8459
8839
|
var EmbeddingFallback = class {
|
|
8460
8840
|
constructor(config) {
|
|
8461
8841
|
this.config = config;
|
|
8462
|
-
this.indexPath =
|
|
8842
|
+
this.indexPath = path12.join(config.memoryDir, "state", "embeddings.json");
|
|
8463
8843
|
}
|
|
8464
8844
|
indexPath;
|
|
8465
8845
|
loaded = null;
|
|
@@ -8569,7 +8949,7 @@ var EmbeddingFallback = class {
|
|
|
8569
8949
|
return this.loaded;
|
|
8570
8950
|
}
|
|
8571
8951
|
try {
|
|
8572
|
-
const raw = await
|
|
8952
|
+
const raw = await readFile10(this.indexPath, "utf-8");
|
|
8573
8953
|
const parsed = JSON.parse(raw);
|
|
8574
8954
|
if (parsed && parsed.version === 1 && parsed.entries && typeof parsed.entries === "object") {
|
|
8575
8955
|
this.loaded = {
|
|
@@ -8591,14 +8971,14 @@ var EmbeddingFallback = class {
|
|
|
8591
8971
|
return this.loaded;
|
|
8592
8972
|
}
|
|
8593
8973
|
async saveIndex(index) {
|
|
8594
|
-
await
|
|
8595
|
-
await
|
|
8974
|
+
await mkdir10(path12.dirname(this.indexPath), { recursive: true });
|
|
8975
|
+
await writeFile10(this.indexPath, JSON.stringify(index), "utf-8");
|
|
8596
8976
|
this.loaded = index;
|
|
8597
8977
|
}
|
|
8598
8978
|
};
|
|
8599
8979
|
function toMemoryRelativePath(memoryDir, filePath) {
|
|
8600
|
-
if (!
|
|
8601
|
-
const rel =
|
|
8980
|
+
if (!path12.isAbsolute(filePath)) return filePath;
|
|
8981
|
+
const rel = path12.relative(memoryDir, filePath);
|
|
8602
8982
|
return rel.startsWith("..") ? filePath : rel;
|
|
8603
8983
|
}
|
|
8604
8984
|
function cosineSimilarity(a, b) {
|
|
@@ -8620,8 +9000,8 @@ function cosineSimilarity(a, b) {
|
|
|
8620
9000
|
}
|
|
8621
9001
|
|
|
8622
9002
|
// src/bootstrap.ts
|
|
8623
|
-
import
|
|
8624
|
-
import { readdir as readdir5, readFile as
|
|
9003
|
+
import path13 from "path";
|
|
9004
|
+
import { readdir as readdir5, readFile as readFile11 } from "fs/promises";
|
|
8625
9005
|
var BootstrapEngine = class {
|
|
8626
9006
|
constructor(config, orchestrator) {
|
|
8627
9007
|
this.config = config;
|
|
@@ -8700,7 +9080,7 @@ var BootstrapEngine = class {
|
|
|
8700
9080
|
for (const filePath of files) {
|
|
8701
9081
|
let raw = "";
|
|
8702
9082
|
try {
|
|
8703
|
-
raw = await
|
|
9083
|
+
raw = await readFile11(filePath, "utf-8");
|
|
8704
9084
|
} catch {
|
|
8705
9085
|
continue;
|
|
8706
9086
|
}
|
|
@@ -8718,7 +9098,7 @@ var BootstrapEngine = class {
|
|
|
8718
9098
|
const role = String(parsed?.role ?? "");
|
|
8719
9099
|
const content = typeof parsed?.content === "string" ? parsed.content : "";
|
|
8720
9100
|
if (!role || !content) continue;
|
|
8721
|
-
const sessionKey = typeof parsed?.sessionKey === "string" && parsed.sessionKey.length > 0 ? parsed.sessionKey :
|
|
9101
|
+
const sessionKey = typeof parsed?.sessionKey === "string" && parsed.sessionKey.length > 0 ? parsed.sessionKey : path13.relative(baseDir, filePath);
|
|
8722
9102
|
const list = bySession.get(sessionKey) ?? [];
|
|
8723
9103
|
list.push({
|
|
8724
9104
|
role,
|
|
@@ -8738,7 +9118,7 @@ var BootstrapEngine = class {
|
|
|
8738
9118
|
const out = [];
|
|
8739
9119
|
const entries = await readdir5(dir, { withFileTypes: true }).catch(() => []);
|
|
8740
9120
|
for (const entry of entries) {
|
|
8741
|
-
const full =
|
|
9121
|
+
const full = path13.join(dir, entry.name);
|
|
8742
9122
|
if (entry.isDirectory()) {
|
|
8743
9123
|
out.push(...await this.listJsonlFiles(full));
|
|
8744
9124
|
} else if (entry.isFile() && full.endsWith(".jsonl")) {
|
|
@@ -8995,8 +9375,8 @@ function buildRecallQueryPolicy(prompt, sessionKey, cfg) {
|
|
|
8995
9375
|
}
|
|
8996
9376
|
|
|
8997
9377
|
// src/boxes.ts
|
|
8998
|
-
import { mkdir as
|
|
8999
|
-
import
|
|
9378
|
+
import { mkdir as mkdir11, writeFile as writeFile11, readFile as readFile12, readdir as readdir6 } from "fs/promises";
|
|
9379
|
+
import path14 from "path";
|
|
9000
9380
|
import { createHash as createHash3 } from "crypto";
|
|
9001
9381
|
var BOX_DIR = "boxes";
|
|
9002
9382
|
var STATE_DIR = "state";
|
|
@@ -9071,42 +9451,42 @@ var BoxBuilder = class {
|
|
|
9071
9451
|
this.cfg = cfg;
|
|
9072
9452
|
}
|
|
9073
9453
|
get boxBaseDir() {
|
|
9074
|
-
return
|
|
9454
|
+
return path14.join(this.baseDir, BOX_DIR);
|
|
9075
9455
|
}
|
|
9076
9456
|
get stateDir() {
|
|
9077
|
-
return
|
|
9457
|
+
return path14.join(this.baseDir, STATE_DIR);
|
|
9078
9458
|
}
|
|
9079
9459
|
get openBoxStatePath() {
|
|
9080
|
-
return
|
|
9460
|
+
return path14.join(this.stateDir, OPEN_BOX_STATE_FILE);
|
|
9081
9461
|
}
|
|
9082
9462
|
get tracesPath() {
|
|
9083
|
-
return
|
|
9463
|
+
return path14.join(this.stateDir, TRACES_FILE);
|
|
9084
9464
|
}
|
|
9085
9465
|
// ── State persistence ────────────────────────────────────────────────────
|
|
9086
9466
|
async loadOpenBox() {
|
|
9087
9467
|
if (this.stateLoaded) return;
|
|
9088
9468
|
this.stateLoaded = true;
|
|
9089
9469
|
try {
|
|
9090
|
-
const raw = await
|
|
9470
|
+
const raw = await readFile12(this.openBoxStatePath, "utf-8");
|
|
9091
9471
|
this.openBox = JSON.parse(raw);
|
|
9092
9472
|
} catch {
|
|
9093
9473
|
this.openBox = null;
|
|
9094
9474
|
}
|
|
9095
9475
|
}
|
|
9096
9476
|
async saveOpenBox() {
|
|
9097
|
-
await
|
|
9477
|
+
await mkdir11(this.stateDir, { recursive: true });
|
|
9098
9478
|
if (this.openBox) {
|
|
9099
|
-
await
|
|
9479
|
+
await writeFile11(this.openBoxStatePath, JSON.stringify(this.openBox, null, 2), "utf-8");
|
|
9100
9480
|
} else {
|
|
9101
9481
|
try {
|
|
9102
|
-
await
|
|
9482
|
+
await writeFile11(this.openBoxStatePath, "null", "utf-8");
|
|
9103
9483
|
} catch {
|
|
9104
9484
|
}
|
|
9105
9485
|
}
|
|
9106
9486
|
}
|
|
9107
9487
|
async loadTraceIndex() {
|
|
9108
9488
|
try {
|
|
9109
|
-
const raw = await
|
|
9489
|
+
const raw = await readFile12(this.tracesPath, "utf-8");
|
|
9110
9490
|
const parsed = JSON.parse(raw);
|
|
9111
9491
|
parsed.traceLastSeen ??= {};
|
|
9112
9492
|
return parsed;
|
|
@@ -9116,8 +9496,8 @@ var BoxBuilder = class {
|
|
|
9116
9496
|
}
|
|
9117
9497
|
async saveTraceIndex(idx) {
|
|
9118
9498
|
try {
|
|
9119
|
-
await
|
|
9120
|
-
await
|
|
9499
|
+
await mkdir11(this.stateDir, { recursive: true });
|
|
9500
|
+
await writeFile11(this.tracesPath, JSON.stringify(idx, null, 2), "utf-8");
|
|
9121
9501
|
} catch (err) {
|
|
9122
9502
|
log.warn(`[engram/boxes] Failed to save trace index: ${err.message}`);
|
|
9123
9503
|
}
|
|
@@ -9193,8 +9573,8 @@ var BoxBuilder = class {
|
|
|
9193
9573
|
}
|
|
9194
9574
|
const sealedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9195
9575
|
const day = sealedAt.slice(0, 10);
|
|
9196
|
-
const dir =
|
|
9197
|
-
await
|
|
9576
|
+
const dir = path14.join(this.boxBaseDir, day);
|
|
9577
|
+
await mkdir11(dir, { recursive: true });
|
|
9198
9578
|
let traceId;
|
|
9199
9579
|
if (this.cfg.traceWeaverEnabled && box.topics.length > 0) {
|
|
9200
9580
|
traceId = await this.resolveTrace(box.id, box.topics);
|
|
@@ -9213,8 +9593,8 @@ var BoxBuilder = class {
|
|
|
9213
9593
|
|
|
9214
9594
|
<!-- Topics: ${box.topics.join(", ")} | Memories: ${box.memoryIds.length} -->
|
|
9215
9595
|
`;
|
|
9216
|
-
const filePath =
|
|
9217
|
-
await
|
|
9596
|
+
const filePath = path14.join(dir, `${box.id}.md`);
|
|
9597
|
+
await writeFile11(filePath, content, "utf-8");
|
|
9218
9598
|
log.debug(`[boxes] sealed box ${box.id} (${reason}): ${box.memoryIds.length} memories, topics=[${box.topics.join(",")}]`);
|
|
9219
9599
|
await this.saveOpenBox();
|
|
9220
9600
|
return box.id;
|
|
@@ -9265,12 +9645,12 @@ var BoxBuilder = class {
|
|
|
9265
9645
|
try {
|
|
9266
9646
|
const entries = await readdir6(dir, { withFileTypes: true });
|
|
9267
9647
|
for (const e of entries) {
|
|
9268
|
-
const full =
|
|
9648
|
+
const full = path14.join(dir, e.name);
|
|
9269
9649
|
if (e.isDirectory()) {
|
|
9270
9650
|
await walkDir(full);
|
|
9271
9651
|
} else if (e.name.endsWith(".md")) {
|
|
9272
9652
|
try {
|
|
9273
|
-
const raw = await
|
|
9653
|
+
const raw = await readFile12(full, "utf-8");
|
|
9274
9654
|
const parsed = parseBoxFrontmatter(raw);
|
|
9275
9655
|
if (parsed && new Date(parsed.sealedAt) >= cutoff) {
|
|
9276
9656
|
boxes.push(parsed);
|
|
@@ -9378,8 +9758,8 @@ function classifyMemoryKind(content, tags, category) {
|
|
|
9378
9758
|
|
|
9379
9759
|
// src/tmt.ts
|
|
9380
9760
|
import * as fs from "fs";
|
|
9381
|
-
import * as
|
|
9382
|
-
import { mkdir as
|
|
9761
|
+
import * as path15 from "path";
|
|
9762
|
+
import { mkdir as mkdir12, readFile as readFile13, writeFile as writeFile12, readdir as readdir7 } from "fs/promises";
|
|
9383
9763
|
var TMT_DIR = "tmt";
|
|
9384
9764
|
var TMT_LEVEL_INPUT_LIMITS = {
|
|
9385
9765
|
hour: { totalChars: 48e3, itemChars: 2e3, maxItems: 64 },
|
|
@@ -9408,19 +9788,19 @@ function capTmtSummaryInputs(inputs, level) {
|
|
|
9408
9788
|
return [fallback.length > itemChars ? `${fallback.slice(0, itemChars - 1)}\u2026` : fallback];
|
|
9409
9789
|
}
|
|
9410
9790
|
function tmtDir(baseDir) {
|
|
9411
|
-
return
|
|
9791
|
+
return path15.join(baseDir, TMT_DIR);
|
|
9412
9792
|
}
|
|
9413
9793
|
function hourNodePath(baseDir, date, hour) {
|
|
9414
|
-
return
|
|
9794
|
+
return path15.join(tmtDir(baseDir), date, `hour-${hour}.md`);
|
|
9415
9795
|
}
|
|
9416
9796
|
function dayNodePath(baseDir, date) {
|
|
9417
|
-
return
|
|
9797
|
+
return path15.join(tmtDir(baseDir), date, "day.md");
|
|
9418
9798
|
}
|
|
9419
9799
|
function weekNodePath(baseDir, weekKey) {
|
|
9420
|
-
return
|
|
9800
|
+
return path15.join(tmtDir(baseDir), `week-${weekKey}.md`);
|
|
9421
9801
|
}
|
|
9422
9802
|
function personaNodePath(baseDir) {
|
|
9423
|
-
return
|
|
9803
|
+
return path15.join(tmtDir(baseDir), "persona.md");
|
|
9424
9804
|
}
|
|
9425
9805
|
function serialiseTmtNode(fm, summary) {
|
|
9426
9806
|
const yaml = [
|
|
@@ -9466,7 +9846,7 @@ var TmtBuilder = class {
|
|
|
9466
9846
|
async maybeRebuildNodes(memories, summarize) {
|
|
9467
9847
|
if (!this.cfg.temporalMemoryTreeEnabled || memories.length === 0) return;
|
|
9468
9848
|
try {
|
|
9469
|
-
await
|
|
9849
|
+
await mkdir12(tmtDir(this.baseDir), { recursive: true });
|
|
9470
9850
|
await this.buildHourNodes(memories, summarize);
|
|
9471
9851
|
await this.buildDayNodes(memories, summarize);
|
|
9472
9852
|
await this.buildWeekNodes(memories, summarize);
|
|
@@ -9491,7 +9871,7 @@ var TmtBuilder = class {
|
|
|
9491
9871
|
let shouldBuild = !fs.existsSync(nodePath);
|
|
9492
9872
|
if (!shouldBuild) {
|
|
9493
9873
|
try {
|
|
9494
|
-
const existing = await
|
|
9874
|
+
const existing = await readFile13(nodePath, "utf8");
|
|
9495
9875
|
const countMatch = existing.match(/memoryCount: (\d+)/);
|
|
9496
9876
|
if (!countMatch || parseInt(countMatch[1], 10) < entries.length) {
|
|
9497
9877
|
shouldBuild = true;
|
|
@@ -9517,8 +9897,8 @@ var TmtBuilder = class {
|
|
|
9517
9897
|
sourceIds: entries.map((e) => e.id),
|
|
9518
9898
|
builtAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9519
9899
|
};
|
|
9520
|
-
await
|
|
9521
|
-
await
|
|
9900
|
+
await mkdir12(path15.dirname(nodePath), { recursive: true });
|
|
9901
|
+
await writeFile12(nodePath, serialiseTmtNode(fm, summary), "utf8");
|
|
9522
9902
|
}
|
|
9523
9903
|
}
|
|
9524
9904
|
async buildDayNodes(memories, summarize) {
|
|
@@ -9533,7 +9913,7 @@ var TmtBuilder = class {
|
|
|
9533
9913
|
let shouldBuild = !fs.existsSync(nodePath);
|
|
9534
9914
|
if (!shouldBuild) {
|
|
9535
9915
|
try {
|
|
9536
|
-
const existing = await
|
|
9916
|
+
const existing = await readFile13(nodePath, "utf8");
|
|
9537
9917
|
const countMatch = existing.match(/memoryCount: (\d+)/);
|
|
9538
9918
|
if (!countMatch || parseInt(countMatch[1], 10) < entries.length) {
|
|
9539
9919
|
shouldBuild = true;
|
|
@@ -9554,7 +9934,7 @@ var TmtBuilder = class {
|
|
|
9554
9934
|
const hPath = hourNodePath(this.baseDir, date, h);
|
|
9555
9935
|
if (fs.existsSync(hPath)) {
|
|
9556
9936
|
try {
|
|
9557
|
-
const hContent = await
|
|
9937
|
+
const hContent = await readFile13(hPath, "utf8");
|
|
9558
9938
|
const hSummary = hContent.replace(/^---[\s\S]*?---\n\n?/, "").trim();
|
|
9559
9939
|
if (hSummary) {
|
|
9560
9940
|
inputs.push(hSummary);
|
|
@@ -9582,8 +9962,8 @@ var TmtBuilder = class {
|
|
|
9582
9962
|
sourceIds: entries.map((e) => e.id),
|
|
9583
9963
|
builtAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9584
9964
|
};
|
|
9585
|
-
await
|
|
9586
|
-
await
|
|
9965
|
+
await mkdir12(path15.dirname(nodePath), { recursive: true });
|
|
9966
|
+
await writeFile12(nodePath, serialiseTmtNode(fm, summary), "utf8");
|
|
9587
9967
|
}
|
|
9588
9968
|
}
|
|
9589
9969
|
/**
|
|
@@ -9604,7 +9984,7 @@ var TmtBuilder = class {
|
|
|
9604
9984
|
let shouldBuild = !fs.existsSync(nodePath);
|
|
9605
9985
|
if (!shouldBuild) {
|
|
9606
9986
|
try {
|
|
9607
|
-
const existing = await
|
|
9987
|
+
const existing = await readFile13(nodePath, "utf8");
|
|
9608
9988
|
const countMatch = existing.match(/memoryCount: (\d+)/);
|
|
9609
9989
|
if (!countMatch || parseInt(countMatch[1], 10) < entries.length) {
|
|
9610
9990
|
shouldBuild = true;
|
|
@@ -9630,7 +10010,7 @@ var TmtBuilder = class {
|
|
|
9630
10010
|
const dayPath = dayNodePath(this.baseDir, dateDir);
|
|
9631
10011
|
if (fs.existsSync(dayPath)) {
|
|
9632
10012
|
try {
|
|
9633
|
-
const content = await
|
|
10013
|
+
const content = await readFile13(dayPath, "utf8");
|
|
9634
10014
|
const summary2 = content.replace(/^---[\s\S]*?---\n\n?/, "").trim();
|
|
9635
10015
|
if (summary2) daySummaries.push(summary2);
|
|
9636
10016
|
} catch {
|
|
@@ -9648,8 +10028,8 @@ var TmtBuilder = class {
|
|
|
9648
10028
|
sourceIds: entries.map((e) => e.id),
|
|
9649
10029
|
builtAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9650
10030
|
};
|
|
9651
|
-
await
|
|
9652
|
-
await
|
|
10031
|
+
await mkdir12(path15.dirname(nodePath), { recursive: true });
|
|
10032
|
+
await writeFile12(nodePath, serialiseTmtNode(fm, summary), "utf8");
|
|
9653
10033
|
} catch (err) {
|
|
9654
10034
|
console.warn(`[engram] tmt: week node build failed for ${week} (ignored): ${err}`);
|
|
9655
10035
|
}
|
|
@@ -9677,7 +10057,7 @@ var TmtBuilder = class {
|
|
|
9677
10057
|
let latestEnd;
|
|
9678
10058
|
for (const f of weekFiles) {
|
|
9679
10059
|
try {
|
|
9680
|
-
const content = await
|
|
10060
|
+
const content = await readFile13(path15.join(dir, f), "utf8");
|
|
9681
10061
|
const summary2 = content.replace(/^---[\s\S]*?---\n\n?/, "").trim();
|
|
9682
10062
|
if (summary2) weekSummaries.push(summary2);
|
|
9683
10063
|
const countMatch = content.match(/memoryCount: (\d+)/);
|
|
@@ -9698,7 +10078,7 @@ var TmtBuilder = class {
|
|
|
9698
10078
|
let shouldBuild = !fs.existsSync(nodePath);
|
|
9699
10079
|
if (!shouldBuild) {
|
|
9700
10080
|
try {
|
|
9701
|
-
const existing = await
|
|
10081
|
+
const existing = await readFile13(nodePath, "utf8");
|
|
9702
10082
|
const countMatch = existing.match(/memoryCount: (\d+)/);
|
|
9703
10083
|
if (!countMatch || parseInt(countMatch[1], 10) !== totalCount) {
|
|
9704
10084
|
shouldBuild = true;
|
|
@@ -9718,7 +10098,7 @@ var TmtBuilder = class {
|
|
|
9718
10098
|
sourceIds: [],
|
|
9719
10099
|
builtAt: now
|
|
9720
10100
|
};
|
|
9721
|
-
await
|
|
10101
|
+
await writeFile12(nodePath, serialiseTmtNode(fm, summary), "utf8");
|
|
9722
10102
|
} catch (err) {
|
|
9723
10103
|
console.warn(`[engram] tmt: persona node build failed (ignored): ${err}`);
|
|
9724
10104
|
}
|
|
@@ -9736,7 +10116,7 @@ var TmtBuilder = class {
|
|
|
9736
10116
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9737
10117
|
const todayDay = dayNodePath(this.baseDir, today);
|
|
9738
10118
|
if (fs.existsSync(todayDay)) {
|
|
9739
|
-
const content = await
|
|
10119
|
+
const content = await readFile13(todayDay, "utf8");
|
|
9740
10120
|
const summary = content.replace(/^---[\s\S]*?---\n\n?/, "").trim();
|
|
9741
10121
|
if (summary) return { level: "day", summary };
|
|
9742
10122
|
}
|
|
@@ -9750,7 +10130,7 @@ var TmtBuilder = class {
|
|
|
9750
10130
|
for (const dateDir of dateDirs) {
|
|
9751
10131
|
const dayPath = dayNodePath(this.baseDir, dateDir);
|
|
9752
10132
|
if (fs.existsSync(dayPath)) {
|
|
9753
|
-
const content = await
|
|
10133
|
+
const content = await readFile13(dayPath, "utf8");
|
|
9754
10134
|
const summary = content.replace(/^---[\s\S]*?---\n\n?/, "").trim();
|
|
9755
10135
|
if (summary) return { level: "day", summary };
|
|
9756
10136
|
}
|
|
@@ -9775,13 +10155,13 @@ function clamp01(value) {
|
|
|
9775
10155
|
if (value > 1) return 1;
|
|
9776
10156
|
return value;
|
|
9777
10157
|
}
|
|
9778
|
-
function
|
|
10158
|
+
function parseIsoMs2(value) {
|
|
9779
10159
|
if (!value) return null;
|
|
9780
10160
|
const ms = Date.parse(value);
|
|
9781
10161
|
return Number.isFinite(ms) ? ms : null;
|
|
9782
10162
|
}
|
|
9783
10163
|
function daysSince(value, nowMs) {
|
|
9784
|
-
const ts =
|
|
10164
|
+
const ts = parseIsoMs2(value);
|
|
9785
10165
|
if (ts === null) return 365;
|
|
9786
10166
|
return Math.max(0, (nowMs - ts) / 864e5);
|
|
9787
10167
|
}
|
|
@@ -9925,18 +10305,18 @@ function decideLifecycleTransition(memory, policy, now, signals) {
|
|
|
9925
10305
|
|
|
9926
10306
|
// src/temporal-index.ts
|
|
9927
10307
|
import * as fs2 from "fs";
|
|
9928
|
-
import * as
|
|
10308
|
+
import * as path16 from "path";
|
|
9929
10309
|
var INDEX_VERSION = 1;
|
|
9930
10310
|
var TEMPORAL_INDEX_FILE = "index_time.json";
|
|
9931
10311
|
var TAG_INDEX_FILE = "index_tags.json";
|
|
9932
10312
|
function stateDir(memoryDir) {
|
|
9933
|
-
return
|
|
10313
|
+
return path16.join(memoryDir, "state");
|
|
9934
10314
|
}
|
|
9935
10315
|
function temporalIndexPath(memoryDir) {
|
|
9936
|
-
return
|
|
10316
|
+
return path16.join(stateDir(memoryDir), TEMPORAL_INDEX_FILE);
|
|
9937
10317
|
}
|
|
9938
10318
|
function tagIndexPath(memoryDir) {
|
|
9939
|
-
return
|
|
10319
|
+
return path16.join(stateDir(memoryDir), TAG_INDEX_FILE);
|
|
9940
10320
|
}
|
|
9941
10321
|
function ensureStateDir(memoryDir) {
|
|
9942
10322
|
const dir = stateDir(memoryDir);
|
|
@@ -10170,8 +10550,8 @@ function recencyWindowFromPrompt(prompt, nowMs = Date.now()) {
|
|
|
10170
10550
|
}
|
|
10171
10551
|
|
|
10172
10552
|
// src/graph.ts
|
|
10173
|
-
import { mkdir as
|
|
10174
|
-
import * as
|
|
10553
|
+
import { mkdir as mkdir13, appendFile as appendFile4, readFile as readFile14 } from "fs/promises";
|
|
10554
|
+
import * as path17 from "path";
|
|
10175
10555
|
var CAUSAL_PHRASES = [
|
|
10176
10556
|
"as a result",
|
|
10177
10557
|
"led to",
|
|
@@ -10181,13 +10561,13 @@ var CAUSAL_PHRASES = [
|
|
|
10181
10561
|
"because"
|
|
10182
10562
|
];
|
|
10183
10563
|
function graphsDir(memoryDir) {
|
|
10184
|
-
return
|
|
10564
|
+
return path17.join(memoryDir, "state", "graphs");
|
|
10185
10565
|
}
|
|
10186
10566
|
function graphFilePath(memoryDir, type) {
|
|
10187
|
-
return
|
|
10567
|
+
return path17.join(graphsDir(memoryDir), `${type}.jsonl`);
|
|
10188
10568
|
}
|
|
10189
10569
|
async function ensureGraphsDir(memoryDir) {
|
|
10190
|
-
await
|
|
10570
|
+
await mkdir13(graphsDir(memoryDir), { recursive: true });
|
|
10191
10571
|
}
|
|
10192
10572
|
async function appendEdge(memoryDir, edge) {
|
|
10193
10573
|
await ensureGraphsDir(memoryDir);
|
|
@@ -10197,7 +10577,7 @@ async function appendEdge(memoryDir, edge) {
|
|
|
10197
10577
|
async function readEdges(memoryDir, type) {
|
|
10198
10578
|
const filePath = graphFilePath(memoryDir, type);
|
|
10199
10579
|
try {
|
|
10200
|
-
const raw = await
|
|
10580
|
+
const raw = await readFile14(filePath, "utf8");
|
|
10201
10581
|
const edges = [];
|
|
10202
10582
|
for (const line of raw.split("\n")) {
|
|
10203
10583
|
const trimmed = line.trim();
|
|
@@ -10386,8 +10766,8 @@ function chunkTranscriptEntries(sessionKey, entries, opts) {
|
|
|
10386
10766
|
}
|
|
10387
10767
|
|
|
10388
10768
|
// src/conversation-index/indexer.ts
|
|
10389
|
-
import { mkdir as
|
|
10390
|
-
import
|
|
10769
|
+
import { mkdir as mkdir14, writeFile as writeFile13 } from "fs/promises";
|
|
10770
|
+
import path18 from "path";
|
|
10391
10771
|
function sanitizeSessionKey(sessionKey) {
|
|
10392
10772
|
const raw = typeof sessionKey === "string" && sessionKey.trim().length > 0 ? sessionKey : "unknown-session";
|
|
10393
10773
|
return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "_").slice(0, 200);
|
|
@@ -10397,9 +10777,9 @@ async function writeConversationChunks(rootDir, chunks) {
|
|
|
10397
10777
|
for (const c of chunks) {
|
|
10398
10778
|
const safe = sanitizeSessionKey(c.sessionKey);
|
|
10399
10779
|
const date = c.startTs.slice(0, 10);
|
|
10400
|
-
const dir =
|
|
10401
|
-
await
|
|
10402
|
-
const fp =
|
|
10780
|
+
const dir = path18.join(rootDir, safe, date);
|
|
10781
|
+
await mkdir14(dir, { recursive: true });
|
|
10782
|
+
const fp = path18.join(dir, `${c.id}.md`);
|
|
10403
10783
|
const content = `---
|
|
10404
10784
|
kind: conversation_chunk
|
|
10405
10785
|
sessionKey: ${c.sessionKey}
|
|
@@ -10408,7 +10788,7 @@ endTs: ${c.endTs}
|
|
|
10408
10788
|
---
|
|
10409
10789
|
|
|
10410
10790
|
` + c.text + "\n";
|
|
10411
|
-
await
|
|
10791
|
+
await writeFile13(fp, content, "utf-8");
|
|
10412
10792
|
written.push(fp);
|
|
10413
10793
|
}
|
|
10414
10794
|
return written;
|
|
@@ -10416,7 +10796,7 @@ endTs: ${c.endTs}
|
|
|
10416
10796
|
|
|
10417
10797
|
// src/conversation-index/cleanup.ts
|
|
10418
10798
|
import { readdir as readdir8, rm } from "fs/promises";
|
|
10419
|
-
import
|
|
10799
|
+
import path19 from "path";
|
|
10420
10800
|
async function cleanupConversationChunks(rootDir, retentionDays) {
|
|
10421
10801
|
if (!Number.isFinite(retentionDays) || retentionDays <= 0) return;
|
|
10422
10802
|
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
@@ -10424,7 +10804,7 @@ async function cleanupConversationChunks(rootDir, retentionDays) {
|
|
|
10424
10804
|
const sessions = await readdir8(rootDir, { withFileTypes: true });
|
|
10425
10805
|
for (const s of sessions) {
|
|
10426
10806
|
if (!s.isDirectory()) continue;
|
|
10427
|
-
const sessionDir =
|
|
10807
|
+
const sessionDir = path19.join(rootDir, s.name);
|
|
10428
10808
|
const dayDirs = await readdir8(sessionDir, { withFileTypes: true });
|
|
10429
10809
|
for (const d of dayDirs) {
|
|
10430
10810
|
if (!d.isDirectory()) continue;
|
|
@@ -10432,7 +10812,7 @@ async function cleanupConversationChunks(rootDir, retentionDays) {
|
|
|
10432
10812
|
const dayMs = (/* @__PURE__ */ new Date(d.name + "T00:00:00.000Z")).getTime();
|
|
10433
10813
|
if (!Number.isFinite(dayMs)) continue;
|
|
10434
10814
|
if (dayMs < cutoffMs) {
|
|
10435
|
-
await rm(
|
|
10815
|
+
await rm(path19.join(sessionDir, d.name), { recursive: true, force: true });
|
|
10436
10816
|
}
|
|
10437
10817
|
}
|
|
10438
10818
|
try {
|
|
@@ -10449,7 +10829,7 @@ async function cleanupConversationChunks(rootDir, retentionDays) {
|
|
|
10449
10829
|
}
|
|
10450
10830
|
|
|
10451
10831
|
// src/namespaces/storage.ts
|
|
10452
|
-
import
|
|
10832
|
+
import path20 from "path";
|
|
10453
10833
|
import { access } from "fs/promises";
|
|
10454
10834
|
async function exists(p) {
|
|
10455
10835
|
try {
|
|
@@ -10471,7 +10851,7 @@ var NamespaceStorageRouter = class {
|
|
|
10471
10851
|
this.defaultNsRootResolved = this.config.memoryDir;
|
|
10472
10852
|
return this.defaultNsRootResolved;
|
|
10473
10853
|
}
|
|
10474
|
-
const nsDir =
|
|
10854
|
+
const nsDir = path20.join(this.config.memoryDir, "namespaces", this.config.defaultNamespace);
|
|
10475
10855
|
this.defaultNsRootResolved = await exists(nsDir) ? nsDir : this.config.memoryDir;
|
|
10476
10856
|
return this.defaultNsRootResolved;
|
|
10477
10857
|
}
|
|
@@ -10480,7 +10860,7 @@ var NamespaceStorageRouter = class {
|
|
|
10480
10860
|
if (namespace === this.config.defaultNamespace) {
|
|
10481
10861
|
return this.defaultNsRootResolved ?? this.config.memoryDir;
|
|
10482
10862
|
}
|
|
10483
|
-
return
|
|
10863
|
+
return path20.join(this.config.memoryDir, "namespaces", namespace);
|
|
10484
10864
|
}
|
|
10485
10865
|
async storageFor(namespace) {
|
|
10486
10866
|
const ns = namespace || this.config.defaultNamespace;
|
|
@@ -10556,8 +10936,8 @@ function recallNamespacesForPrincipal(principal, config) {
|
|
|
10556
10936
|
}
|
|
10557
10937
|
|
|
10558
10938
|
// src/shared-context/manager.ts
|
|
10559
|
-
import { mkdir as
|
|
10560
|
-
import
|
|
10939
|
+
import { mkdir as mkdir15, readFile as readFile15, readdir as readdir9, appendFile as appendFile5, writeFile as writeFile14, stat as stat4 } from "fs/promises";
|
|
10940
|
+
import path21 from "path";
|
|
10561
10941
|
import os3 from "os";
|
|
10562
10942
|
import { z as z3 } from "zod";
|
|
10563
10943
|
var SharedFeedbackEntrySchema = z3.object({
|
|
@@ -10579,15 +10959,15 @@ function ymd(d) {
|
|
|
10579
10959
|
var SharedContextManager = class {
|
|
10580
10960
|
constructor(config) {
|
|
10581
10961
|
this.config = config;
|
|
10582
|
-
const base = typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0 ? config.sharedContextDir :
|
|
10962
|
+
const base = typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0 ? config.sharedContextDir : path21.join(os3.homedir(), ".openclaw", "workspace", "shared-context");
|
|
10583
10963
|
this.dir = base;
|
|
10584
|
-
this.prioritiesPath =
|
|
10585
|
-
this.prioritiesInboxPath =
|
|
10586
|
-
this.outputsDir =
|
|
10587
|
-
this.roundtableDir =
|
|
10588
|
-
this.feedbackDir =
|
|
10589
|
-
this.feedbackInboxPath =
|
|
10590
|
-
this.crossSignalsDir =
|
|
10964
|
+
this.prioritiesPath = path21.join(base, "priorities.md");
|
|
10965
|
+
this.prioritiesInboxPath = path21.join(base, "priorities.inbox.md");
|
|
10966
|
+
this.outputsDir = path21.join(base, "agent-outputs");
|
|
10967
|
+
this.roundtableDir = path21.join(base, "roundtable");
|
|
10968
|
+
this.feedbackDir = path21.join(base, "feedback");
|
|
10969
|
+
this.feedbackInboxPath = path21.join(this.feedbackDir, "inbox.jsonl");
|
|
10970
|
+
this.crossSignalsDir = path21.join(base, "cross-signals");
|
|
10591
10971
|
}
|
|
10592
10972
|
dir;
|
|
10593
10973
|
prioritiesPath;
|
|
@@ -10598,15 +10978,15 @@ var SharedContextManager = class {
|
|
|
10598
10978
|
feedbackInboxPath;
|
|
10599
10979
|
crossSignalsDir;
|
|
10600
10980
|
async ensureStructure() {
|
|
10601
|
-
await
|
|
10602
|
-
await
|
|
10603
|
-
await
|
|
10604
|
-
await
|
|
10605
|
-
await
|
|
10606
|
-
await
|
|
10607
|
-
await
|
|
10608
|
-
await
|
|
10609
|
-
await
|
|
10981
|
+
await mkdir15(this.dir, { recursive: true });
|
|
10982
|
+
await mkdir15(this.outputsDir, { recursive: true });
|
|
10983
|
+
await mkdir15(this.roundtableDir, { recursive: true });
|
|
10984
|
+
await mkdir15(this.feedbackDir, { recursive: true });
|
|
10985
|
+
await mkdir15(this.crossSignalsDir, { recursive: true });
|
|
10986
|
+
await mkdir15(path21.join(this.dir, "staging"), { recursive: true });
|
|
10987
|
+
await mkdir15(path21.join(this.dir, "kpis"), { recursive: true });
|
|
10988
|
+
await mkdir15(path21.join(this.dir, "calendar"), { recursive: true });
|
|
10989
|
+
await mkdir15(path21.join(this.dir, "content-calendar"), { recursive: true });
|
|
10610
10990
|
await this.ensureFile(
|
|
10611
10991
|
this.prioritiesPath,
|
|
10612
10992
|
[
|
|
@@ -10635,14 +11015,14 @@ var SharedContextManager = class {
|
|
|
10635
11015
|
}
|
|
10636
11016
|
async ensureFile(fp, content) {
|
|
10637
11017
|
try {
|
|
10638
|
-
await
|
|
11018
|
+
await stat4(fp);
|
|
10639
11019
|
} catch {
|
|
10640
|
-
await
|
|
11020
|
+
await writeFile14(fp, content, "utf-8");
|
|
10641
11021
|
}
|
|
10642
11022
|
}
|
|
10643
11023
|
async readPriorities() {
|
|
10644
11024
|
try {
|
|
10645
|
-
return await
|
|
11025
|
+
return await readFile15(this.prioritiesPath, "utf-8");
|
|
10646
11026
|
} catch {
|
|
10647
11027
|
return "";
|
|
10648
11028
|
}
|
|
@@ -10650,9 +11030,9 @@ var SharedContextManager = class {
|
|
|
10650
11030
|
async readLatestRoundtable() {
|
|
10651
11031
|
try {
|
|
10652
11032
|
const files = (await readdir9(this.roundtableDir)).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
10653
|
-
const fp = files[0] ?
|
|
11033
|
+
const fp = files[0] ? path21.join(this.roundtableDir, files[0]) : null;
|
|
10654
11034
|
if (!fp) return "";
|
|
10655
|
-
return await
|
|
11035
|
+
return await readFile15(fp, "utf-8");
|
|
10656
11036
|
} catch {
|
|
10657
11037
|
return "";
|
|
10658
11038
|
}
|
|
@@ -10662,9 +11042,9 @@ var SharedContextManager = class {
|
|
|
10662
11042
|
const date = ymd(createdAt);
|
|
10663
11043
|
const time = createdAt.toISOString().slice(11, 19).replace(/:/g, "");
|
|
10664
11044
|
const slug = safeSlug(opts.title);
|
|
10665
|
-
const dir =
|
|
10666
|
-
await
|
|
10667
|
-
const fp =
|
|
11045
|
+
const dir = path21.join(this.outputsDir, opts.agentId, date);
|
|
11046
|
+
await mkdir15(dir, { recursive: true });
|
|
11047
|
+
const fp = path21.join(dir, `${time}-${slug}.md`);
|
|
10668
11048
|
const body = `---
|
|
10669
11049
|
kind: agent_output
|
|
10670
11050
|
agent: ${opts.agentId}
|
|
@@ -10673,7 +11053,7 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
|
|
|
10673
11053
|
---
|
|
10674
11054
|
|
|
10675
11055
|
` + opts.content.trimEnd() + "\n";
|
|
10676
|
-
await
|
|
11056
|
+
await writeFile14(fp, body, "utf-8");
|
|
10677
11057
|
return fp;
|
|
10678
11058
|
}
|
|
10679
11059
|
async appendFeedback(entry) {
|
|
@@ -10699,12 +11079,12 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
|
|
|
10699
11079
|
const agents = await readdir9(this.outputsDir, { withFileTypes: true });
|
|
10700
11080
|
for (const a of agents) {
|
|
10701
11081
|
if (!a.isDirectory()) continue;
|
|
10702
|
-
const dayDir =
|
|
11082
|
+
const dayDir = path21.join(this.outputsDir, a.name, date);
|
|
10703
11083
|
try {
|
|
10704
11084
|
const files = (await readdir9(dayDir)).filter((f) => f.endsWith(".md")).sort();
|
|
10705
11085
|
for (const f of files) {
|
|
10706
|
-
const p =
|
|
10707
|
-
const raw = await
|
|
11086
|
+
const p = path21.join(dayDir, f);
|
|
11087
|
+
const raw = await readFile15(p, "utf-8");
|
|
10708
11088
|
const title = (raw.match(/^title:\s*(.+)$/m)?.[1] ?? f).trim();
|
|
10709
11089
|
outputs.push({ path: p, title });
|
|
10710
11090
|
}
|
|
@@ -10715,7 +11095,7 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
|
|
|
10715
11095
|
}
|
|
10716
11096
|
const feedback = [];
|
|
10717
11097
|
try {
|
|
10718
|
-
const raw = await
|
|
11098
|
+
const raw = await readFile15(this.feedbackInboxPath, "utf-8");
|
|
10719
11099
|
for (const line of raw.split("\n")) {
|
|
10720
11100
|
if (!line.trim()) continue;
|
|
10721
11101
|
try {
|
|
@@ -10743,16 +11123,16 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
|
|
|
10743
11123
|
];
|
|
10744
11124
|
const out = md.join("\n");
|
|
10745
11125
|
const trimmed = out.length > maxChars ? out.slice(0, maxChars) + "\n\n...(trimmed)\n" : out;
|
|
10746
|
-
const fp =
|
|
10747
|
-
await
|
|
11126
|
+
const fp = path21.join(this.roundtableDir, `${date}.md`);
|
|
11127
|
+
await writeFile14(fp, trimmed, "utf-8");
|
|
10748
11128
|
log.info(`shared-context curated daily roundtable: ${fp}`);
|
|
10749
11129
|
return fp;
|
|
10750
11130
|
}
|
|
10751
11131
|
};
|
|
10752
11132
|
|
|
10753
11133
|
// src/compounding/engine.ts
|
|
10754
|
-
import { mkdir as
|
|
10755
|
-
import
|
|
11134
|
+
import { mkdir as mkdir16, readFile as readFile16, readdir as readdir10, writeFile as writeFile15 } from "fs/promises";
|
|
11135
|
+
import path22 from "path";
|
|
10756
11136
|
import os4 from "os";
|
|
10757
11137
|
function isoWeekId(d) {
|
|
10758
11138
|
const dt = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
|
@@ -10783,7 +11163,7 @@ function sharedContextDir(config) {
|
|
|
10783
11163
|
if (typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0) {
|
|
10784
11164
|
return config.sharedContextDir;
|
|
10785
11165
|
}
|
|
10786
|
-
return
|
|
11166
|
+
return path22.join(os4.homedir(), ".openclaw", "workspace", "shared-context");
|
|
10787
11167
|
}
|
|
10788
11168
|
function cadenceStaleWindowMs(cadence) {
|
|
10789
11169
|
switch (cadence) {
|
|
@@ -10802,14 +11182,14 @@ function cadenceStaleWindowMs(cadence) {
|
|
|
10802
11182
|
var CompoundingEngine = class {
|
|
10803
11183
|
constructor(config) {
|
|
10804
11184
|
this.config = config;
|
|
10805
|
-
this.weeklyDir =
|
|
10806
|
-
this.mistakesPath =
|
|
10807
|
-
this.feedbackInboxPath =
|
|
10808
|
-
this.identityAnchorPath =
|
|
10809
|
-
this.identityIncidentsDir =
|
|
10810
|
-
this.identityAuditWeeklyDir =
|
|
10811
|
-
this.identityAuditMonthlyDir =
|
|
10812
|
-
this.identityImprovementLoopsPath =
|
|
11185
|
+
this.weeklyDir = path22.join(config.memoryDir, "compounding", "weekly");
|
|
11186
|
+
this.mistakesPath = path22.join(config.memoryDir, "compounding", "mistakes.json");
|
|
11187
|
+
this.feedbackInboxPath = path22.join(sharedContextDir(config), "feedback", "inbox.jsonl");
|
|
11188
|
+
this.identityAnchorPath = path22.join(config.memoryDir, "identity", "identity-anchor.md");
|
|
11189
|
+
this.identityIncidentsDir = path22.join(config.memoryDir, "identity", "incidents");
|
|
11190
|
+
this.identityAuditWeeklyDir = path22.join(config.memoryDir, "identity", "audits", "weekly");
|
|
11191
|
+
this.identityAuditMonthlyDir = path22.join(config.memoryDir, "identity", "audits", "monthly");
|
|
11192
|
+
this.identityImprovementLoopsPath = path22.join(config.memoryDir, "identity", "improvement-loops.md");
|
|
10813
11193
|
}
|
|
10814
11194
|
weeklyDir;
|
|
10815
11195
|
mistakesPath;
|
|
@@ -10820,8 +11200,8 @@ var CompoundingEngine = class {
|
|
|
10820
11200
|
identityAuditMonthlyDir;
|
|
10821
11201
|
identityImprovementLoopsPath;
|
|
10822
11202
|
async ensureDirs() {
|
|
10823
|
-
await
|
|
10824
|
-
await
|
|
11203
|
+
await mkdir16(this.weeklyDir, { recursive: true });
|
|
11204
|
+
await mkdir16(path22.dirname(this.mistakesPath), { recursive: true });
|
|
10825
11205
|
}
|
|
10826
11206
|
async synthesizeWeekly(opts) {
|
|
10827
11207
|
await this.ensureDirs();
|
|
@@ -10829,10 +11209,10 @@ var CompoundingEngine = class {
|
|
|
10829
11209
|
const entries = await this.readFeedbackEntriesForWeek(weekId);
|
|
10830
11210
|
const mistakes = this.buildMistakes(entries);
|
|
10831
11211
|
const continuity = this.config.continuityAuditEnabled ? await this.readContinuityAuditReferences(weekId) : { monthId: monthIdFromIsoWeek(weekId), weeklyPath: null, monthlyPath: null };
|
|
10832
|
-
const reportPath =
|
|
11212
|
+
const reportPath = path22.join(this.weeklyDir, `${weekId}.md`);
|
|
10833
11213
|
const md = this.formatWeeklyReport(weekId, entries, mistakes.patterns, continuity);
|
|
10834
|
-
await
|
|
10835
|
-
await
|
|
11214
|
+
await writeFile15(reportPath, md, "utf-8");
|
|
11215
|
+
await writeFile15(this.mistakesPath, JSON.stringify(mistakes, null, 2) + "\n", "utf-8");
|
|
10836
11216
|
log.info(`compounding: wrote weekly=${reportPath} mistakes=${this.mistakesPath}`);
|
|
10837
11217
|
return { weekId, reportPath, mistakesCount: mistakes.patterns.length };
|
|
10838
11218
|
}
|
|
@@ -10908,14 +11288,14 @@ var CompoundingEngine = class {
|
|
|
10908
11288
|
""
|
|
10909
11289
|
];
|
|
10910
11290
|
const dir = period === "weekly" ? this.identityAuditWeeklyDir : this.identityAuditMonthlyDir;
|
|
10911
|
-
await
|
|
10912
|
-
const reportPath =
|
|
10913
|
-
await
|
|
11291
|
+
await mkdir16(dir, { recursive: true });
|
|
11292
|
+
const reportPath = path22.join(dir, `${key}.md`);
|
|
11293
|
+
await writeFile15(reportPath, lines.join("\n"), "utf-8");
|
|
10914
11294
|
return { period, key, reportPath };
|
|
10915
11295
|
}
|
|
10916
11296
|
async readMistakes() {
|
|
10917
11297
|
try {
|
|
10918
|
-
const raw = await
|
|
11298
|
+
const raw = await readFile16(this.mistakesPath, "utf-8");
|
|
10919
11299
|
const parsed = JSON.parse(raw);
|
|
10920
11300
|
if (!parsed || !Array.isArray(parsed.patterns)) return null;
|
|
10921
11301
|
return parsed;
|
|
@@ -10926,7 +11306,7 @@ var CompoundingEngine = class {
|
|
|
10926
11306
|
async readFeedbackEntriesForWeek(weekId) {
|
|
10927
11307
|
const out = [];
|
|
10928
11308
|
try {
|
|
10929
|
-
const raw = await
|
|
11309
|
+
const raw = await readFile16(this.feedbackInboxPath, "utf-8");
|
|
10930
11310
|
for (const line of raw.split("\n")) {
|
|
10931
11311
|
if (!line.trim()) continue;
|
|
10932
11312
|
try {
|
|
@@ -11014,7 +11394,7 @@ var CompoundingEngine = class {
|
|
|
11014
11394
|
}
|
|
11015
11395
|
async readNonEmptyFile(filePath) {
|
|
11016
11396
|
try {
|
|
11017
|
-
const raw = await
|
|
11397
|
+
const raw = await readFile16(filePath, "utf-8");
|
|
11018
11398
|
return raw.trim().length > 0;
|
|
11019
11399
|
} catch {
|
|
11020
11400
|
return false;
|
|
@@ -11022,7 +11402,7 @@ var CompoundingEngine = class {
|
|
|
11022
11402
|
}
|
|
11023
11403
|
async readOptionalFile(filePath) {
|
|
11024
11404
|
try {
|
|
11025
|
-
const raw = await
|
|
11405
|
+
const raw = await readFile16(filePath, "utf-8");
|
|
11026
11406
|
return raw.trim().length > 0 ? raw : null;
|
|
11027
11407
|
} catch {
|
|
11028
11408
|
return null;
|
|
@@ -11038,9 +11418,9 @@ var CompoundingEngine = class {
|
|
|
11038
11418
|
const files = names.filter((n) => n.endsWith(".md")).sort().reverse();
|
|
11039
11419
|
for (const file of files) {
|
|
11040
11420
|
if (incidents.length >= cappedLimit) break;
|
|
11041
|
-
const filePath =
|
|
11421
|
+
const filePath = path22.join(this.identityIncidentsDir, file);
|
|
11042
11422
|
try {
|
|
11043
|
-
const raw = await
|
|
11423
|
+
const raw = await readFile16(filePath, "utf-8");
|
|
11044
11424
|
const parsed = parseContinuityIncident(raw);
|
|
11045
11425
|
if (!parsed) continue;
|
|
11046
11426
|
if (state && parsed.state !== state) continue;
|
|
@@ -11054,8 +11434,8 @@ var CompoundingEngine = class {
|
|
|
11054
11434
|
}
|
|
11055
11435
|
async readContinuityAuditReferences(weekId) {
|
|
11056
11436
|
const monthId = monthIdFromIsoWeek(weekId);
|
|
11057
|
-
const weeklyPath =
|
|
11058
|
-
const monthlyPath =
|
|
11437
|
+
const weeklyPath = path22.join(this.identityAuditWeeklyDir, `${weekId}.md`);
|
|
11438
|
+
const monthlyPath = path22.join(this.identityAuditMonthlyDir, `${monthId}.md`);
|
|
11059
11439
|
const weeklyExists = await this.readNonEmptyFile(weeklyPath);
|
|
11060
11440
|
const monthlyExists = await this.readNonEmptyFile(monthlyPath);
|
|
11061
11441
|
return {
|
|
@@ -11246,11 +11626,11 @@ function mergeGraphExpandedResults(primary, expanded) {
|
|
|
11246
11626
|
return Array.from(mergedByPath.values());
|
|
11247
11627
|
}
|
|
11248
11628
|
function graphPathRelativeToStorage(storageDir, candidatePath) {
|
|
11249
|
-
const absolutePath =
|
|
11250
|
-
const rel =
|
|
11629
|
+
const absolutePath = path23.isAbsolute(candidatePath) ? candidatePath : path23.resolve(storageDir, candidatePath);
|
|
11630
|
+
const rel = path23.relative(storageDir, absolutePath);
|
|
11251
11631
|
if (!rel || rel === ".") return null;
|
|
11252
11632
|
if (rel.startsWith("..")) return null;
|
|
11253
|
-
return rel.split(
|
|
11633
|
+
return rel.split(path23.sep).join("/");
|
|
11254
11634
|
}
|
|
11255
11635
|
function graphActivationScoreToRecallScore(score) {
|
|
11256
11636
|
const bounded = Number.isFinite(score) && score > 0 ? score : 0;
|
|
@@ -11292,7 +11672,7 @@ function buildMemoryPathById(allMemsForGraph, storageDir) {
|
|
|
11292
11672
|
for (const mem of allMemsForGraph ?? []) {
|
|
11293
11673
|
const id = mem.frontmatter.id;
|
|
11294
11674
|
if (!id) continue;
|
|
11295
|
-
pathById.set(id,
|
|
11675
|
+
pathById.set(id, path23.relative(storageDir, mem.path));
|
|
11296
11676
|
}
|
|
11297
11677
|
return pathById;
|
|
11298
11678
|
}
|
|
@@ -11300,7 +11680,7 @@ function appendMemoryToGraphContext(options) {
|
|
|
11300
11680
|
if (!Array.isArray(options.allMemsForGraph)) return;
|
|
11301
11681
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
11302
11682
|
options.allMemsForGraph.push({
|
|
11303
|
-
path:
|
|
11683
|
+
path: path23.join(options.storageDir, options.memoryRelPath),
|
|
11304
11684
|
content: options.content,
|
|
11305
11685
|
frontmatter: {
|
|
11306
11686
|
id: options.memoryId,
|
|
@@ -11320,15 +11700,15 @@ function resolvePersistedMemoryRelativePath(options) {
|
|
|
11320
11700
|
const persisted = options.pathById.get(options.memoryId);
|
|
11321
11701
|
if (persisted) return persisted;
|
|
11322
11702
|
if (options.category === "correction") {
|
|
11323
|
-
return
|
|
11703
|
+
return path23.join("corrections", `${options.memoryId}.md`);
|
|
11324
11704
|
}
|
|
11325
11705
|
const idParts = options.memoryId.split("-");
|
|
11326
11706
|
const maybeTimestamp = Number(idParts[1]);
|
|
11327
11707
|
if (Number.isFinite(maybeTimestamp) && maybeTimestamp > 0) {
|
|
11328
11708
|
const day = new Date(maybeTimestamp).toISOString().slice(0, 10);
|
|
11329
|
-
return
|
|
11709
|
+
return path23.join("facts", day, `${options.memoryId}.md`);
|
|
11330
11710
|
}
|
|
11331
|
-
return
|
|
11711
|
+
return path23.join("facts", `${options.memoryId}.md`);
|
|
11332
11712
|
}
|
|
11333
11713
|
var Orchestrator = class _Orchestrator {
|
|
11334
11714
|
storage;
|
|
@@ -11339,6 +11719,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
11339
11719
|
compounding;
|
|
11340
11720
|
buffer;
|
|
11341
11721
|
transcript;
|
|
11722
|
+
sessionObserver;
|
|
11342
11723
|
summarizer;
|
|
11343
11724
|
localLlm;
|
|
11344
11725
|
modelRegistry;
|
|
@@ -11367,6 +11748,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
11367
11748
|
// Queue stores promises that resolve when extraction should run
|
|
11368
11749
|
extractionQueue = [];
|
|
11369
11750
|
queueProcessing = false;
|
|
11751
|
+
heartbeatObserverChains = /* @__PURE__ */ new Map();
|
|
11370
11752
|
recentExtractionFingerprints = /* @__PURE__ */ new Map();
|
|
11371
11753
|
nonZeroExtractionsSinceConsolidation = 0;
|
|
11372
11754
|
lastConsolidationRunAtMs = 0;
|
|
@@ -11417,17 +11799,22 @@ var Orchestrator = class _Orchestrator {
|
|
|
11417
11799
|
this.compounding = config.compoundingEnabled ? new CompoundingEngine(config) : void 0;
|
|
11418
11800
|
this.buffer = new SmartBuffer(config, this.storage);
|
|
11419
11801
|
this.transcript = new TranscriptManager(config);
|
|
11420
|
-
this.conversationIndexDir =
|
|
11802
|
+
this.conversationIndexDir = path23.join(config.memoryDir, "conversation-index", "chunks");
|
|
11421
11803
|
this.modelRegistry = new ModelRegistry(config.memoryDir);
|
|
11422
11804
|
this.relevance = new RelevanceStore(config.memoryDir);
|
|
11423
11805
|
this.negatives = new NegativeExampleStore(config.memoryDir);
|
|
11424
11806
|
this.lastRecall = new LastRecallStore(config.memoryDir);
|
|
11807
|
+
this.sessionObserver = new SessionObserverState({
|
|
11808
|
+
memoryDir: config.memoryDir,
|
|
11809
|
+
debounceMs: config.sessionObserverDebounceMs ?? 12e4,
|
|
11810
|
+
bands: config.sessionObserverBands ?? []
|
|
11811
|
+
});
|
|
11425
11812
|
this.embeddingFallback = new EmbeddingFallback(config);
|
|
11426
11813
|
this.summarizer = new HourlySummarizer(config, config.gatewayConfig, this.modelRegistry, this.transcript);
|
|
11427
11814
|
this.localLlm = new LocalLlmClient(config, this.modelRegistry);
|
|
11428
11815
|
this.extraction = new ExtractionEngine(config, this.localLlm, config.gatewayConfig, this.modelRegistry);
|
|
11429
11816
|
this.threading = new ThreadingManager(
|
|
11430
|
-
|
|
11817
|
+
path23.join(config.memoryDir, "threads"),
|
|
11431
11818
|
config.threadingGapMinutes
|
|
11432
11819
|
);
|
|
11433
11820
|
this.tmtBuilder = new TmtBuilder(config.memoryDir, {
|
|
@@ -11533,8 +11920,9 @@ var Orchestrator = class _Orchestrator {
|
|
|
11533
11920
|
await this.relevance.load();
|
|
11534
11921
|
await this.negatives.load();
|
|
11535
11922
|
await this.lastRecall.load();
|
|
11923
|
+
await this.sessionObserver.load();
|
|
11536
11924
|
if (this.config.factDeduplicationEnabled) {
|
|
11537
|
-
const stateDir2 =
|
|
11925
|
+
const stateDir2 = path23.join(this.config.memoryDir, "state");
|
|
11538
11926
|
this.contentHashIndex = new ContentHashIndex(stateDir2);
|
|
11539
11927
|
await this.contentHashIndex.load();
|
|
11540
11928
|
log.info(`content-hash dedup: loaded ${this.contentHashIndex.size} hashes`);
|
|
@@ -11572,7 +11960,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
11572
11960
|
if (available) {
|
|
11573
11961
|
log.info(`Conversation index QMD: available ${this.conversationQmd.debugStatus()}`);
|
|
11574
11962
|
const collectionState = await this.conversationQmd.ensureCollection(
|
|
11575
|
-
|
|
11963
|
+
path23.join(this.config.memoryDir, "conversation-index")
|
|
11576
11964
|
);
|
|
11577
11965
|
if (collectionState === "missing") {
|
|
11578
11966
|
this.config.conversationIndexEnabled = false;
|
|
@@ -11608,12 +11996,12 @@ var Orchestrator = class _Orchestrator {
|
|
|
11608
11996
|
this.lastFileHygieneRunAtMs = now;
|
|
11609
11997
|
if (hygiene.rotateEnabled) {
|
|
11610
11998
|
for (const rel of hygiene.rotatePaths) {
|
|
11611
|
-
const abs =
|
|
11999
|
+
const abs = path23.isAbsolute(rel) ? rel : path23.join(this.config.workspaceDir, rel);
|
|
11612
12000
|
try {
|
|
11613
|
-
const raw = await
|
|
12001
|
+
const raw = await readFile17(abs, "utf-8");
|
|
11614
12002
|
if (raw.length > hygiene.rotateMaxBytes) {
|
|
11615
|
-
const archiveDir =
|
|
11616
|
-
const base =
|
|
12003
|
+
const archiveDir = path23.join(this.config.workspaceDir, hygiene.archiveDir);
|
|
12004
|
+
const base = path23.basename(abs);
|
|
11617
12005
|
const prefix = base.toUpperCase().replace(/\.MD$/i, "").replace(/[^A-Z0-9]+/g, "-") || "FILE";
|
|
11618
12006
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
11619
12007
|
filePath: abs,
|
|
@@ -11621,7 +12009,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
11621
12009
|
archivePrefix: prefix,
|
|
11622
12010
|
keepTailChars: hygiene.rotateKeepTailChars
|
|
11623
12011
|
});
|
|
11624
|
-
await
|
|
12012
|
+
await writeFile16(abs, newContent, "utf-8");
|
|
11625
12013
|
}
|
|
11626
12014
|
} catch {
|
|
11627
12015
|
}
|
|
@@ -11638,8 +12026,8 @@ var Orchestrator = class _Orchestrator {
|
|
|
11638
12026
|
log.warn(w.message);
|
|
11639
12027
|
}
|
|
11640
12028
|
if (hygiene.warningsLogEnabled && warnings.length > 0) {
|
|
11641
|
-
const fp =
|
|
11642
|
-
await
|
|
12029
|
+
const fp = path23.join(this.config.memoryDir, hygiene.warningsLogPath);
|
|
12030
|
+
await mkdir17(path23.dirname(fp), { recursive: true });
|
|
11643
12031
|
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11644
12032
|
const block = `
|
|
11645
12033
|
|
|
@@ -11648,11 +12036,11 @@ var Orchestrator = class _Orchestrator {
|
|
|
11648
12036
|
` + warnings.map((w) => `- ${w.message}`).join("\n") + "\n";
|
|
11649
12037
|
let existing = "";
|
|
11650
12038
|
try {
|
|
11651
|
-
existing = await
|
|
12039
|
+
existing = await readFile17(fp, "utf-8");
|
|
11652
12040
|
} catch {
|
|
11653
12041
|
existing = "# Engram File Hygiene Warnings\n";
|
|
11654
12042
|
}
|
|
11655
|
-
await
|
|
12043
|
+
await writeFile16(fp, existing + block, "utf-8");
|
|
11656
12044
|
}
|
|
11657
12045
|
}
|
|
11658
12046
|
}
|
|
@@ -11695,9 +12083,9 @@ var Orchestrator = class _Orchestrator {
|
|
|
11695
12083
|
}
|
|
11696
12084
|
async getLastGraphRecallSnapshot(namespace) {
|
|
11697
12085
|
const storage = await this.getStorage(namespace);
|
|
11698
|
-
const snapshotPath =
|
|
12086
|
+
const snapshotPath = path23.join(storage.dir, "state", "last_graph_recall.json");
|
|
11699
12087
|
try {
|
|
11700
|
-
const raw = await
|
|
12088
|
+
const raw = await readFile17(snapshotPath, "utf-8");
|
|
11701
12089
|
const parsed = JSON.parse(raw);
|
|
11702
12090
|
if (!parsed || typeof parsed !== "object") return null;
|
|
11703
12091
|
return {
|
|
@@ -11997,7 +12385,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
11997
12385
|
const storage = await this.storageRouter.storageFor(namespace);
|
|
11998
12386
|
const seedRelativePaths = nsResults.slice(0, perNamespaceSeedCap).map((result) => graphPathRelativeToStorage(storage.dir, result.path)).filter((value) => typeof value === "string" && value.length > 0);
|
|
11999
12387
|
if (seedRelativePaths.length === 0) continue;
|
|
12000
|
-
seedPaths.push(...seedRelativePaths.map((rel) =>
|
|
12388
|
+
seedPaths.push(...seedRelativePaths.map((rel) => path23.join(storage.dir, rel)));
|
|
12001
12389
|
const seedSet = new Set(seedRelativePaths);
|
|
12002
12390
|
const expanded = await this.graphIndexFor(storage).spreadingActivation(
|
|
12003
12391
|
seedRelativePaths,
|
|
@@ -12006,7 +12394,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
12006
12394
|
if (expanded.length === 0) continue;
|
|
12007
12395
|
for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
|
|
12008
12396
|
if (seedSet.has(candidate.path)) continue;
|
|
12009
|
-
const memoryPath =
|
|
12397
|
+
const memoryPath = path23.resolve(storage.dir, candidate.path);
|
|
12010
12398
|
const memory = await storage.readMemoryByPath(memoryPath);
|
|
12011
12399
|
if (!memory) continue;
|
|
12012
12400
|
if (isArtifactMemoryPath(memory.path)) continue;
|
|
@@ -12034,8 +12422,8 @@ var Orchestrator = class _Orchestrator {
|
|
|
12034
12422
|
}
|
|
12035
12423
|
async recordLastGraphRecallSnapshot(options) {
|
|
12036
12424
|
try {
|
|
12037
|
-
const snapshotPath =
|
|
12038
|
-
await
|
|
12425
|
+
const snapshotPath = path23.join(options.storage.dir, "state", "last_graph_recall.json");
|
|
12426
|
+
await mkdir17(path23.dirname(snapshotPath), { recursive: true });
|
|
12039
12427
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12040
12428
|
const payload = {
|
|
12041
12429
|
recordedAt: now,
|
|
@@ -12048,7 +12436,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
12048
12436
|
seeds: options.seedPaths,
|
|
12049
12437
|
expanded: options.expandedPaths
|
|
12050
12438
|
};
|
|
12051
|
-
await
|
|
12439
|
+
await writeFile16(snapshotPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
12052
12440
|
} catch (err) {
|
|
12053
12441
|
log.debug(`last graph recall write failed: ${err}`);
|
|
12054
12442
|
}
|
|
@@ -12730,9 +13118,48 @@ _Context: ${topQuestion.context}_`);
|
|
|
12730
13118
|
};
|
|
12731
13119
|
const decision = await this.buffer.addTurn(turn);
|
|
12732
13120
|
if (decision === "keep_buffering") return;
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
13121
|
+
await this.queueBufferedExtraction(this.buffer.getTurns(), "trigger_mode");
|
|
13122
|
+
}
|
|
13123
|
+
async observeSessionHeartbeat(sessionKey) {
|
|
13124
|
+
if (this.config.sessionObserverEnabled !== true) return;
|
|
13125
|
+
if (!sessionKey || sessionKey.length === 0) return;
|
|
13126
|
+
const previous = this.heartbeatObserverChains.get(sessionKey) ?? Promise.resolve();
|
|
13127
|
+
const next = previous.catch(() => void 0).then(async () => {
|
|
13128
|
+
const turns = this.buffer.getTurns();
|
|
13129
|
+
if (turns.length === 0) return;
|
|
13130
|
+
const mixedSessionTurns = turns.some((turn) => turn.sessionKey !== sessionKey);
|
|
13131
|
+
if (mixedSessionTurns) {
|
|
13132
|
+
log.debug(`heartbeat observer skipped: mixed session buffer for ${sessionKey}`);
|
|
13133
|
+
return;
|
|
13134
|
+
}
|
|
13135
|
+
if (!this.shouldQueueExtraction(turns, { commit: false })) {
|
|
13136
|
+
log.debug(`heartbeat observer skipped: extraction dedupe for ${sessionKey}`);
|
|
13137
|
+
return;
|
|
13138
|
+
}
|
|
13139
|
+
const footprint = await this.transcript.estimateSessionFootprint(sessionKey);
|
|
13140
|
+
const decision = await this.sessionObserver.observe({
|
|
13141
|
+
sessionKey,
|
|
13142
|
+
totalBytes: footprint.bytes,
|
|
13143
|
+
totalTokens: footprint.tokens
|
|
13144
|
+
});
|
|
13145
|
+
if (!decision.triggered) return;
|
|
13146
|
+
log.debug(
|
|
13147
|
+
`heartbeat observer trigger: session=${sessionKey} deltaBytes=${decision.deltaBytes} deltaTokens=${decision.deltaTokens}`
|
|
13148
|
+
);
|
|
13149
|
+
await this.queueBufferedExtraction(turns, "heartbeat_observer");
|
|
13150
|
+
});
|
|
13151
|
+
this.heartbeatObserverChains.set(sessionKey, next);
|
|
13152
|
+
try {
|
|
13153
|
+
await next;
|
|
13154
|
+
} finally {
|
|
13155
|
+
if (this.heartbeatObserverChains.get(sessionKey) === next) {
|
|
13156
|
+
this.heartbeatObserverChains.delete(sessionKey);
|
|
13157
|
+
}
|
|
13158
|
+
}
|
|
13159
|
+
}
|
|
13160
|
+
async queueBufferedExtraction(turnsToExtract, reason, options = {}) {
|
|
13161
|
+
if (!options.skipDedupeCheck && !this.shouldQueueExtraction(turnsToExtract)) {
|
|
13162
|
+
log.debug(`extraction dedupe skip: preserving buffer (${reason})`);
|
|
12736
13163
|
return;
|
|
12737
13164
|
}
|
|
12738
13165
|
this.extractionQueue.push(async () => {
|
|
@@ -12745,8 +13172,9 @@ _Context: ${topQuestion.context}_`);
|
|
|
12745
13172
|
this.queueProcessing = false;
|
|
12746
13173
|
});
|
|
12747
13174
|
}
|
|
13175
|
+
log.debug(`queued extraction from ${reason}`);
|
|
12748
13176
|
}
|
|
12749
|
-
shouldQueueExtraction(turns) {
|
|
13177
|
+
shouldQueueExtraction(turns, options = {}) {
|
|
12750
13178
|
if (!this.config.extractionDedupeEnabled) return true;
|
|
12751
13179
|
if (!Array.isArray(turns) || turns.length === 0) return false;
|
|
12752
13180
|
const normalized = turns.filter((t) => t.role === "user" || t.role === "assistant").map((t) => `${t.role}:${(t.content ?? "").trim().slice(0, this.config.extractionMaxTurnChars)}`).join("\n");
|
|
@@ -12758,8 +13186,10 @@ _Context: ${topQuestion.context}_`);
|
|
|
12758
13186
|
log.debug("extraction dedupe: skipped duplicate buffered turn set");
|
|
12759
13187
|
return false;
|
|
12760
13188
|
}
|
|
12761
|
-
|
|
12762
|
-
|
|
13189
|
+
if (options.commit !== false) {
|
|
13190
|
+
this.recentExtractionFingerprints.set(fingerprint, now);
|
|
13191
|
+
}
|
|
13192
|
+
if (options.commit !== false && this.recentExtractionFingerprints.size > 200) {
|
|
12763
13193
|
const entries = Array.from(this.recentExtractionFingerprints.entries()).sort(
|
|
12764
13194
|
(a, b) => a[1] - b[1]
|
|
12765
13195
|
);
|
|
@@ -13271,7 +13701,7 @@ _Context: ${topQuestion.context}_`);
|
|
|
13271
13701
|
const allMems = allMemsForGraph ?? [];
|
|
13272
13702
|
for (const m of allMems) {
|
|
13273
13703
|
if (m.frontmatter.entityRef === entityRef) {
|
|
13274
|
-
const rel =
|
|
13704
|
+
const rel = path23.relative(storage.dir, m.path);
|
|
13275
13705
|
if (rel !== memoryRelPath) entitySiblings.push(rel);
|
|
13276
13706
|
}
|
|
13277
13707
|
}
|
|
@@ -13648,9 +14078,9 @@ ${texts.map((t, i) => `[${i + 1}] ${t}`).join("\n\n")}`;
|
|
|
13648
14078
|
protectedCategories: this.config.lifecycleProtectedCategories
|
|
13649
14079
|
}
|
|
13650
14080
|
};
|
|
13651
|
-
const metricsPath =
|
|
13652
|
-
await
|
|
13653
|
-
await
|
|
14081
|
+
const metricsPath = path23.join(this.storage.dir, "state", "lifecycle-metrics.json");
|
|
14082
|
+
await mkdir17(path23.dirname(metricsPath), { recursive: true });
|
|
14083
|
+
await writeFile16(metricsPath, JSON.stringify(metrics, null, 2), "utf-8");
|
|
13654
14084
|
}
|
|
13655
14085
|
/**
|
|
13656
14086
|
* Archive old, low-importance, rarely-accessed facts (v6.0).
|
|
@@ -13916,7 +14346,7 @@ ${lines.join("\n\n")}`;
|
|
|
13916
14346
|
if (hits.length === 0) return [];
|
|
13917
14347
|
const results = [];
|
|
13918
14348
|
for (const hit of hits) {
|
|
13919
|
-
const fullPath =
|
|
14349
|
+
const fullPath = path23.isAbsolute(hit.path) ? hit.path : path23.join(this.config.memoryDir, hit.path);
|
|
13920
14350
|
const memory = await this.storage.readMemoryByPath(fullPath);
|
|
13921
14351
|
if (!memory) continue;
|
|
13922
14352
|
results.push({
|
|
@@ -14370,7 +14800,7 @@ ${lines.join("\n\n")}`;
|
|
|
14370
14800
|
};
|
|
14371
14801
|
|
|
14372
14802
|
// src/tools.ts
|
|
14373
|
-
import
|
|
14803
|
+
import path24 from "path";
|
|
14374
14804
|
import { createHash as createHash5 } from "crypto";
|
|
14375
14805
|
import { Type } from "@sinclair/typebox";
|
|
14376
14806
|
function toolResult(text) {
|
|
@@ -15516,7 +15946,7 @@ Best for:
|
|
|
15516
15946
|
- Reviewing identity development over time`,
|
|
15517
15947
|
parameters: Type.Object({}),
|
|
15518
15948
|
async execute() {
|
|
15519
|
-
const workspaceDir =
|
|
15949
|
+
const workspaceDir = path24.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
15520
15950
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
15521
15951
|
if (!identity) {
|
|
15522
15952
|
return toolResult("No identity file found. Identity reflections build automatically through conversations when identityEnabled is true.");
|
|
@@ -15765,12 +16195,12 @@ mistakes: ${res.mistakesCount} patterns`
|
|
|
15765
16195
|
}
|
|
15766
16196
|
|
|
15767
16197
|
// src/cli.ts
|
|
15768
|
-
import
|
|
15769
|
-
import { access as access2, readFile as
|
|
16198
|
+
import path34 from "path";
|
|
16199
|
+
import { access as access2, readFile as readFile23, readdir as readdir13, unlink as unlink4 } from "fs/promises";
|
|
15770
16200
|
|
|
15771
16201
|
// src/transfer/export-json.ts
|
|
15772
|
-
import
|
|
15773
|
-
import { mkdir as
|
|
16202
|
+
import path26 from "path";
|
|
16203
|
+
import { mkdir as mkdir19, readFile as readFile19 } from "fs/promises";
|
|
15774
16204
|
|
|
15775
16205
|
// src/transfer/constants.ts
|
|
15776
16206
|
var EXPORT_FORMAT = "openclaw-engram-export";
|
|
@@ -15778,10 +16208,10 @@ var EXPORT_SCHEMA_VERSION = 1;
|
|
|
15778
16208
|
|
|
15779
16209
|
// src/transfer/fs-utils.ts
|
|
15780
16210
|
import { createHash as createHash6 } from "crypto";
|
|
15781
|
-
import { mkdir as
|
|
15782
|
-
import
|
|
16211
|
+
import { mkdir as mkdir18, readdir as readdir11, readFile as readFile18, stat as stat5, writeFile as writeFile17 } from "fs/promises";
|
|
16212
|
+
import path25 from "path";
|
|
15783
16213
|
async function sha256File(filePath) {
|
|
15784
|
-
const buf = await
|
|
16214
|
+
const buf = await readFile18(filePath);
|
|
15785
16215
|
const sha256 = createHash6("sha256").update(buf).digest("hex");
|
|
15786
16216
|
return { sha256, bytes: buf.byteLength };
|
|
15787
16217
|
}
|
|
@@ -15791,11 +16221,11 @@ function sha256String(content) {
|
|
|
15791
16221
|
return { sha256, bytes: buf.byteLength };
|
|
15792
16222
|
}
|
|
15793
16223
|
async function writeJsonFile(filePath, value) {
|
|
15794
|
-
await
|
|
15795
|
-
await
|
|
16224
|
+
await mkdir18(path25.dirname(filePath), { recursive: true });
|
|
16225
|
+
await writeFile17(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
15796
16226
|
}
|
|
15797
16227
|
async function readJsonFile(filePath) {
|
|
15798
|
-
const raw = await
|
|
16228
|
+
const raw = await readFile18(filePath, "utf-8");
|
|
15799
16229
|
return JSON.parse(raw);
|
|
15800
16230
|
}
|
|
15801
16231
|
async function listFilesRecursive(rootDir) {
|
|
@@ -15803,7 +16233,7 @@ async function listFilesRecursive(rootDir) {
|
|
|
15803
16233
|
async function walk(dir) {
|
|
15804
16234
|
const entries = await readdir11(dir, { withFileTypes: true });
|
|
15805
16235
|
for (const ent of entries) {
|
|
15806
|
-
const fp =
|
|
16236
|
+
const fp = path25.join(dir, ent.name);
|
|
15807
16237
|
if (ent.isDirectory()) {
|
|
15808
16238
|
await walk(fp);
|
|
15809
16239
|
} else if (ent.isFile()) {
|
|
@@ -15816,18 +16246,18 @@ async function listFilesRecursive(rootDir) {
|
|
|
15816
16246
|
}
|
|
15817
16247
|
async function fileExists(filePath) {
|
|
15818
16248
|
try {
|
|
15819
|
-
await
|
|
16249
|
+
await stat5(filePath);
|
|
15820
16250
|
return true;
|
|
15821
16251
|
} catch {
|
|
15822
16252
|
return false;
|
|
15823
16253
|
}
|
|
15824
16254
|
}
|
|
15825
16255
|
function toPosixRelPath(absPath, rootDir) {
|
|
15826
|
-
const rel =
|
|
15827
|
-
return rel.split(
|
|
16256
|
+
const rel = path25.relative(rootDir, absPath);
|
|
16257
|
+
return rel.split(path25.sep).join("/");
|
|
15828
16258
|
}
|
|
15829
16259
|
function fromPosixRelPath(relPath) {
|
|
15830
|
-
return relPath.split("/").join(
|
|
16260
|
+
return relPath.split("/").join(path25.sep);
|
|
15831
16261
|
}
|
|
15832
16262
|
|
|
15833
16263
|
// src/transfer/export-json.ts
|
|
@@ -15843,24 +16273,24 @@ function shouldExclude(relPosix, includeTranscripts) {
|
|
|
15843
16273
|
}
|
|
15844
16274
|
async function exportJsonBundle(opts) {
|
|
15845
16275
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
15846
|
-
const outDirAbs =
|
|
15847
|
-
await
|
|
15848
|
-
const memoryDirAbs =
|
|
16276
|
+
const outDirAbs = path26.resolve(opts.outDir);
|
|
16277
|
+
await mkdir19(outDirAbs, { recursive: true });
|
|
16278
|
+
const memoryDirAbs = path26.resolve(opts.memoryDir);
|
|
15849
16279
|
const filesAbs = await listFilesRecursive(memoryDirAbs);
|
|
15850
16280
|
const records = [];
|
|
15851
16281
|
const manifestFiles = [];
|
|
15852
16282
|
for (const abs of filesAbs) {
|
|
15853
16283
|
const relPosix = toPosixRelPath(abs, memoryDirAbs);
|
|
15854
16284
|
if (shouldExclude(relPosix, includeTranscripts)) continue;
|
|
15855
|
-
const content = await
|
|
16285
|
+
const content = await readFile19(abs, "utf-8");
|
|
15856
16286
|
records.push({ path: relPosix, content });
|
|
15857
16287
|
const { sha256, bytes } = await sha256File(abs);
|
|
15858
16288
|
manifestFiles.push({ path: relPosix, sha256, bytes });
|
|
15859
16289
|
}
|
|
15860
16290
|
if (opts.includeWorkspaceIdentity !== false && opts.workspaceDir) {
|
|
15861
|
-
const identityPath =
|
|
16291
|
+
const identityPath = path26.join(opts.workspaceDir, "IDENTITY.md");
|
|
15862
16292
|
try {
|
|
15863
|
-
const content = await
|
|
16293
|
+
const content = await readFile19(identityPath, "utf-8");
|
|
15864
16294
|
const relPath = "workspace/IDENTITY.md";
|
|
15865
16295
|
records.push({ path: relPath, content });
|
|
15866
16296
|
const { sha256, bytes } = sha256String(content);
|
|
@@ -15877,13 +16307,13 @@ async function exportJsonBundle(opts) {
|
|
|
15877
16307
|
files: manifestFiles.sort((a, b) => a.path.localeCompare(b.path))
|
|
15878
16308
|
};
|
|
15879
16309
|
const bundle = { manifest, records };
|
|
15880
|
-
await writeJsonFile(
|
|
15881
|
-
await writeJsonFile(
|
|
16310
|
+
await writeJsonFile(path26.join(outDirAbs, "manifest.json"), manifest);
|
|
16311
|
+
await writeJsonFile(path26.join(outDirAbs, "bundle.json"), bundle);
|
|
15882
16312
|
}
|
|
15883
16313
|
|
|
15884
16314
|
// src/transfer/export-md.ts
|
|
15885
|
-
import
|
|
15886
|
-
import { mkdir as
|
|
16315
|
+
import path27 from "path";
|
|
16316
|
+
import { mkdir as mkdir20, readFile as readFile20, writeFile as writeFile18 } from "fs/promises";
|
|
15887
16317
|
function shouldExclude2(relPosix, includeTranscripts) {
|
|
15888
16318
|
const parts = relPosix.split("/");
|
|
15889
16319
|
if (!includeTranscripts && parts[0] === "transcripts") return true;
|
|
@@ -15891,18 +16321,18 @@ function shouldExclude2(relPosix, includeTranscripts) {
|
|
|
15891
16321
|
}
|
|
15892
16322
|
async function exportMarkdownBundle(opts) {
|
|
15893
16323
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
15894
|
-
const outDirAbs =
|
|
15895
|
-
await
|
|
15896
|
-
const memDirAbs =
|
|
16324
|
+
const outDirAbs = path27.resolve(opts.outDir);
|
|
16325
|
+
await mkdir20(outDirAbs, { recursive: true });
|
|
16326
|
+
const memDirAbs = path27.resolve(opts.memoryDir);
|
|
15897
16327
|
const filesAbs = await listFilesRecursive(memDirAbs);
|
|
15898
16328
|
const manifestFiles = [];
|
|
15899
16329
|
for (const abs of filesAbs) {
|
|
15900
16330
|
const relPosix = toPosixRelPath(abs, memDirAbs);
|
|
15901
16331
|
if (shouldExclude2(relPosix, includeTranscripts)) continue;
|
|
15902
|
-
const dstAbs =
|
|
15903
|
-
await
|
|
15904
|
-
const content = await
|
|
15905
|
-
await
|
|
16332
|
+
const dstAbs = path27.join(outDirAbs, ...relPosix.split("/"));
|
|
16333
|
+
await mkdir20(path27.dirname(dstAbs), { recursive: true });
|
|
16334
|
+
const content = await readFile20(abs);
|
|
16335
|
+
await writeFile18(dstAbs, content);
|
|
15906
16336
|
const { sha256, bytes } = await sha256File(abs);
|
|
15907
16337
|
manifestFiles.push({ path: relPosix, sha256, bytes });
|
|
15908
16338
|
}
|
|
@@ -15914,12 +16344,12 @@ async function exportMarkdownBundle(opts) {
|
|
|
15914
16344
|
includesTranscripts: includeTranscripts,
|
|
15915
16345
|
files: manifestFiles.sort((a, b) => a.path.localeCompare(b.path))
|
|
15916
16346
|
};
|
|
15917
|
-
await writeJsonFile(
|
|
16347
|
+
await writeJsonFile(path27.join(outDirAbs, "manifest.json"), manifest);
|
|
15918
16348
|
}
|
|
15919
16349
|
async function looksLikeEngramMdExport(fromDir) {
|
|
15920
|
-
const dirAbs =
|
|
16350
|
+
const dirAbs = path27.resolve(fromDir);
|
|
15921
16351
|
try {
|
|
15922
|
-
const raw = await
|
|
16352
|
+
const raw = await readFile20(path27.join(dirAbs, "manifest.json"), "utf-8");
|
|
15923
16353
|
const parsed = JSON.parse(raw);
|
|
15924
16354
|
return parsed.format === EXPORT_FORMAT && parsed.schemaVersion === EXPORT_SCHEMA_VERSION;
|
|
15925
16355
|
} catch {
|
|
@@ -15928,16 +16358,16 @@ async function looksLikeEngramMdExport(fromDir) {
|
|
|
15928
16358
|
}
|
|
15929
16359
|
|
|
15930
16360
|
// src/transfer/backup.ts
|
|
15931
|
-
import
|
|
15932
|
-
import { mkdir as
|
|
16361
|
+
import path28 from "path";
|
|
16362
|
+
import { mkdir as mkdir21, readdir as readdir12, rm as rm2 } from "fs/promises";
|
|
15933
16363
|
function timestampDirName(now) {
|
|
15934
16364
|
return now.toISOString().replace(/[:.]/g, "-");
|
|
15935
16365
|
}
|
|
15936
16366
|
async function backupMemoryDir(opts) {
|
|
15937
|
-
const outDirAbs =
|
|
15938
|
-
await
|
|
16367
|
+
const outDirAbs = path28.resolve(opts.outDir);
|
|
16368
|
+
await mkdir21(outDirAbs, { recursive: true });
|
|
15939
16369
|
const ts = timestampDirName(/* @__PURE__ */ new Date());
|
|
15940
|
-
const backupDir =
|
|
16370
|
+
const backupDir = path28.join(outDirAbs, ts);
|
|
15941
16371
|
await exportMarkdownBundle({
|
|
15942
16372
|
memoryDir: opts.memoryDir,
|
|
15943
16373
|
outDir: backupDir,
|
|
@@ -15962,15 +16392,15 @@ async function enforceRetention(outDirAbs, retentionDays) {
|
|
|
15962
16392
|
const tsMs = iso ? Date.parse(iso) : NaN;
|
|
15963
16393
|
if (!Number.isFinite(tsMs)) continue;
|
|
15964
16394
|
if (tsMs < cutoffMs) {
|
|
15965
|
-
await rm2(
|
|
16395
|
+
await rm2(path28.join(outDirAbs, name), { recursive: true, force: true });
|
|
15966
16396
|
}
|
|
15967
16397
|
}
|
|
15968
16398
|
}
|
|
15969
16399
|
|
|
15970
16400
|
// src/transfer/export-sqlite.ts
|
|
15971
|
-
import
|
|
16401
|
+
import path29 from "path";
|
|
15972
16402
|
import Database from "better-sqlite3";
|
|
15973
|
-
import { readFile as
|
|
16403
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
15974
16404
|
|
|
15975
16405
|
// src/transfer/sqlite-schema.ts
|
|
15976
16406
|
var SQLITE_SCHEMA_VERSION = 1;
|
|
@@ -15996,8 +16426,8 @@ function shouldExclude3(relPosix, includeTranscripts) {
|
|
|
15996
16426
|
}
|
|
15997
16427
|
async function exportSqlite(opts) {
|
|
15998
16428
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
15999
|
-
const memDirAbs =
|
|
16000
|
-
const outAbs =
|
|
16429
|
+
const memDirAbs = path29.resolve(opts.memoryDir);
|
|
16430
|
+
const outAbs = path29.resolve(opts.outFile);
|
|
16001
16431
|
const filesAbs = await listFilesRecursive(memDirAbs);
|
|
16002
16432
|
const db = new Database(outAbs);
|
|
16003
16433
|
try {
|
|
@@ -16018,7 +16448,7 @@ async function exportSqlite(opts) {
|
|
|
16018
16448
|
for (const abs of filesAbs) {
|
|
16019
16449
|
const relPosix = toPosixRelPath(abs, memDirAbs);
|
|
16020
16450
|
if (shouldExclude3(relPosix, includeTranscripts)) continue;
|
|
16021
|
-
const content = await
|
|
16451
|
+
const content = await readFile21(abs, "utf-8");
|
|
16022
16452
|
const { sha256, bytes } = await sha256File(abs);
|
|
16023
16453
|
rows.push({ rel: relPosix, bytes, sha256, content });
|
|
16024
16454
|
}
|
|
@@ -16029,8 +16459,8 @@ async function exportSqlite(opts) {
|
|
|
16029
16459
|
}
|
|
16030
16460
|
|
|
16031
16461
|
// src/transfer/import-json.ts
|
|
16032
|
-
import
|
|
16033
|
-
import { mkdir as
|
|
16462
|
+
import path30 from "path";
|
|
16463
|
+
import { mkdir as mkdir22, writeFile as writeFile19 } from "fs/promises";
|
|
16034
16464
|
|
|
16035
16465
|
// src/transfer/types.ts
|
|
16036
16466
|
import { z as z4 } from "zod";
|
|
@@ -16063,21 +16493,21 @@ function normalizeForDedupe(s) {
|
|
|
16063
16493
|
}
|
|
16064
16494
|
async function importJsonBundle(opts) {
|
|
16065
16495
|
const conflict = opts.conflict ?? "skip";
|
|
16066
|
-
const fromDirAbs =
|
|
16067
|
-
const bundlePath =
|
|
16496
|
+
const fromDirAbs = path30.resolve(opts.fromDir);
|
|
16497
|
+
const bundlePath = path30.join(fromDirAbs, "bundle.json");
|
|
16068
16498
|
const bundle = ExportBundleV1Schema.parse(await readJsonFile(bundlePath));
|
|
16069
|
-
const memDirAbs =
|
|
16499
|
+
const memDirAbs = path30.resolve(opts.targetMemoryDir);
|
|
16070
16500
|
const written = [];
|
|
16071
16501
|
let skipped = 0;
|
|
16072
16502
|
for (const rec of bundle.records) {
|
|
16073
16503
|
const isWorkspace = rec.path.startsWith("workspace/");
|
|
16074
|
-
const targetBase = isWorkspace ? opts.workspaceDir ?
|
|
16504
|
+
const targetBase = isWorkspace ? opts.workspaceDir ? path30.resolve(opts.workspaceDir) : null : memDirAbs;
|
|
16075
16505
|
if (isWorkspace && !targetBase) {
|
|
16076
16506
|
skipped += 1;
|
|
16077
16507
|
continue;
|
|
16078
16508
|
}
|
|
16079
16509
|
const relFs = fromPosixRelPath(isWorkspace ? rec.path.replace(/^workspace\//, "") : rec.path);
|
|
16080
|
-
const absTarget =
|
|
16510
|
+
const absTarget = path30.join(targetBase, relFs);
|
|
16081
16511
|
const exists3 = await fileExists(absTarget);
|
|
16082
16512
|
if (exists3) {
|
|
16083
16513
|
if (conflict === "skip") {
|
|
@@ -16101,30 +16531,30 @@ async function importJsonBundle(opts) {
|
|
|
16101
16531
|
return { written: 0, skipped };
|
|
16102
16532
|
}
|
|
16103
16533
|
for (const w of written) {
|
|
16104
|
-
await
|
|
16105
|
-
await
|
|
16534
|
+
await mkdir22(path30.dirname(w.abs), { recursive: true });
|
|
16535
|
+
await writeFile19(w.abs, w.content, "utf-8");
|
|
16106
16536
|
}
|
|
16107
16537
|
return { written: written.length, skipped };
|
|
16108
16538
|
}
|
|
16109
16539
|
function looksLikeEngramJsonExport(fromDir) {
|
|
16110
|
-
const dir =
|
|
16540
|
+
const dir = path30.resolve(fromDir);
|
|
16111
16541
|
return Promise.all([
|
|
16112
|
-
fileExists(
|
|
16113
|
-
fileExists(
|
|
16542
|
+
fileExists(path30.join(dir, "manifest.json")),
|
|
16543
|
+
fileExists(path30.join(dir, "bundle.json"))
|
|
16114
16544
|
]).then(([m, b]) => m && b);
|
|
16115
16545
|
}
|
|
16116
16546
|
|
|
16117
16547
|
// src/transfer/import-sqlite.ts
|
|
16118
|
-
import
|
|
16548
|
+
import path31 from "path";
|
|
16119
16549
|
import Database2 from "better-sqlite3";
|
|
16120
|
-
import { mkdir as
|
|
16550
|
+
import { mkdir as mkdir23, writeFile as writeFile20 } from "fs/promises";
|
|
16121
16551
|
function normalizeForDedupe2(s) {
|
|
16122
16552
|
return s.replace(/\s+/g, " ").trim();
|
|
16123
16553
|
}
|
|
16124
16554
|
async function importSqlite(opts) {
|
|
16125
16555
|
const conflict = opts.conflict ?? "skip";
|
|
16126
|
-
const memDirAbs =
|
|
16127
|
-
const fromAbs =
|
|
16556
|
+
const memDirAbs = path31.resolve(opts.targetMemoryDir);
|
|
16557
|
+
const fromAbs = path31.resolve(opts.fromFile);
|
|
16128
16558
|
const db = new Database2(fromAbs, { readonly: true });
|
|
16129
16559
|
const written = [];
|
|
16130
16560
|
let skipped = 0;
|
|
@@ -16137,7 +16567,7 @@ async function importSqlite(opts) {
|
|
|
16137
16567
|
const rows = db.prepare("SELECT path_rel, content FROM files").all();
|
|
16138
16568
|
for (const r of rows) {
|
|
16139
16569
|
const relFs = fromPosixRelPath(r.path_rel);
|
|
16140
|
-
const absTarget =
|
|
16570
|
+
const absTarget = path31.join(memDirAbs, relFs);
|
|
16141
16571
|
const exists3 = await fileExists(absTarget);
|
|
16142
16572
|
if (exists3) {
|
|
16143
16573
|
if (conflict === "skip") {
|
|
@@ -16162,30 +16592,30 @@ async function importSqlite(opts) {
|
|
|
16162
16592
|
}
|
|
16163
16593
|
if (opts.dryRun) return { written: 0, skipped };
|
|
16164
16594
|
for (const w of written) {
|
|
16165
|
-
await
|
|
16166
|
-
await
|
|
16595
|
+
await mkdir23(path31.dirname(w.abs), { recursive: true });
|
|
16596
|
+
await writeFile20(w.abs, w.content, "utf-8");
|
|
16167
16597
|
}
|
|
16168
16598
|
return { written: written.length, skipped };
|
|
16169
16599
|
}
|
|
16170
16600
|
|
|
16171
16601
|
// src/transfer/import-md.ts
|
|
16172
|
-
import
|
|
16173
|
-
import { mkdir as
|
|
16602
|
+
import path32 from "path";
|
|
16603
|
+
import { mkdir as mkdir24, readFile as readFile22, writeFile as writeFile21 } from "fs/promises";
|
|
16174
16604
|
function normalizeForDedupe3(s) {
|
|
16175
16605
|
return s.replace(/\s+/g, " ").trim();
|
|
16176
16606
|
}
|
|
16177
16607
|
async function importMarkdownBundle(opts) {
|
|
16178
16608
|
const conflict = opts.conflict ?? "skip";
|
|
16179
|
-
const fromAbs =
|
|
16180
|
-
const targetAbs =
|
|
16609
|
+
const fromAbs = path32.resolve(opts.fromDir);
|
|
16610
|
+
const targetAbs = path32.resolve(opts.targetMemoryDir);
|
|
16181
16611
|
const filesAbs = await listFilesRecursive(fromAbs);
|
|
16182
16612
|
const writes = [];
|
|
16183
16613
|
let skipped = 0;
|
|
16184
16614
|
for (const abs of filesAbs) {
|
|
16185
16615
|
const relPosix = toPosixRelPath(abs, fromAbs);
|
|
16186
16616
|
if (relPosix === "manifest.json") continue;
|
|
16187
|
-
const dstAbs =
|
|
16188
|
-
const content = await
|
|
16617
|
+
const dstAbs = path32.join(targetAbs, fromPosixRelPath(relPosix));
|
|
16618
|
+
const content = await readFile22(abs, "utf-8");
|
|
16189
16619
|
const exists3 = await fileExists(dstAbs);
|
|
16190
16620
|
if (exists3) {
|
|
16191
16621
|
if (conflict === "skip") {
|
|
@@ -16207,20 +16637,20 @@ async function importMarkdownBundle(opts) {
|
|
|
16207
16637
|
}
|
|
16208
16638
|
if (opts.dryRun) return { written: 0, skipped };
|
|
16209
16639
|
for (const w of writes) {
|
|
16210
|
-
await
|
|
16211
|
-
await
|
|
16640
|
+
await mkdir24(path32.dirname(w.abs), { recursive: true });
|
|
16641
|
+
await writeFile21(w.abs, w.content, "utf-8");
|
|
16212
16642
|
}
|
|
16213
16643
|
return { written: writes.length, skipped };
|
|
16214
16644
|
}
|
|
16215
16645
|
|
|
16216
16646
|
// src/transfer/autodetect.ts
|
|
16217
|
-
import
|
|
16218
|
-
import { stat as
|
|
16647
|
+
import path33 from "path";
|
|
16648
|
+
import { stat as stat6 } from "fs/promises";
|
|
16219
16649
|
async function detectImportFormat(fromPath) {
|
|
16220
|
-
const abs =
|
|
16650
|
+
const abs = path33.resolve(fromPath);
|
|
16221
16651
|
let st;
|
|
16222
16652
|
try {
|
|
16223
|
-
st = await
|
|
16653
|
+
st = await stat6(abs);
|
|
16224
16654
|
} catch {
|
|
16225
16655
|
return null;
|
|
16226
16656
|
}
|
|
@@ -16288,7 +16718,7 @@ function planAggressiveDuplicateDeletions(memories) {
|
|
|
16288
16718
|
async function getPluginVersion() {
|
|
16289
16719
|
try {
|
|
16290
16720
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
16291
|
-
const raw = await
|
|
16721
|
+
const raw = await readFile23(pkgPath, "utf-8");
|
|
16292
16722
|
const parsed = JSON.parse(raw);
|
|
16293
16723
|
return parsed.version ?? "unknown";
|
|
16294
16724
|
} catch {
|
|
@@ -16307,14 +16737,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
16307
16737
|
const ns = (namespace ?? "").trim();
|
|
16308
16738
|
if (!ns) return orchestrator.config.memoryDir;
|
|
16309
16739
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
16310
|
-
const candidate =
|
|
16740
|
+
const candidate = path34.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
16311
16741
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
16312
16742
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
16313
16743
|
}
|
|
16314
16744
|
return candidate;
|
|
16315
16745
|
}
|
|
16316
16746
|
async function readAllMemoryFiles(memoryDir) {
|
|
16317
|
-
const roots = [
|
|
16747
|
+
const roots = [path34.join(memoryDir, "facts"), path34.join(memoryDir, "corrections")];
|
|
16318
16748
|
const out = [];
|
|
16319
16749
|
const walk = async (dir) => {
|
|
16320
16750
|
let entries;
|
|
@@ -16325,14 +16755,14 @@ async function readAllMemoryFiles(memoryDir) {
|
|
|
16325
16755
|
}
|
|
16326
16756
|
for (const entry of entries) {
|
|
16327
16757
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
16328
|
-
const fullPath =
|
|
16758
|
+
const fullPath = path34.join(dir, entryName);
|
|
16329
16759
|
if (entry.isDirectory()) {
|
|
16330
16760
|
await walk(fullPath);
|
|
16331
16761
|
continue;
|
|
16332
16762
|
}
|
|
16333
16763
|
if (!entry.isFile() || !entryName.endsWith(".md")) continue;
|
|
16334
16764
|
try {
|
|
16335
|
-
const raw = await
|
|
16765
|
+
const raw = await readFile23(fullPath, "utf-8");
|
|
16336
16766
|
const parsed = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
16337
16767
|
if (!parsed) continue;
|
|
16338
16768
|
const fmRaw = parsed[1];
|
|
@@ -16543,7 +16973,7 @@ function registerCli(api, orchestrator) {
|
|
|
16543
16973
|
let deleted = 0;
|
|
16544
16974
|
for (const filePath of plan.deletePaths) {
|
|
16545
16975
|
try {
|
|
16546
|
-
await
|
|
16976
|
+
await unlink4(filePath);
|
|
16547
16977
|
deleted += 1;
|
|
16548
16978
|
} catch (err) {
|
|
16549
16979
|
console.log(` failed to delete ${filePath}: ${String(err)}`);
|
|
@@ -16591,7 +17021,7 @@ function registerCli(api, orchestrator) {
|
|
|
16591
17021
|
let deleted = 0;
|
|
16592
17022
|
for (const filePath of plan.deletePaths) {
|
|
16593
17023
|
try {
|
|
16594
|
-
await
|
|
17024
|
+
await unlink4(filePath);
|
|
16595
17025
|
deleted += 1;
|
|
16596
17026
|
} catch (err) {
|
|
16597
17027
|
console.log(` failed to delete ${filePath}: ${String(err)}`);
|
|
@@ -16755,7 +17185,7 @@ function registerCli(api, orchestrator) {
|
|
|
16755
17185
|
}
|
|
16756
17186
|
});
|
|
16757
17187
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
16758
|
-
const workspaceDir =
|
|
17188
|
+
const workspaceDir = path34.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
16759
17189
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
16760
17190
|
if (!identity) {
|
|
16761
17191
|
console.log("No identity file found.");
|
|
@@ -16978,8 +17408,8 @@ function registerCli(api, orchestrator) {
|
|
|
16978
17408
|
const options = args[0] ?? {};
|
|
16979
17409
|
const threadId = options.thread;
|
|
16980
17410
|
const top = parseInt(options.top ?? "10", 10);
|
|
16981
|
-
const memoryDir =
|
|
16982
|
-
const threading = new ThreadingManager(
|
|
17411
|
+
const memoryDir = path34.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
17412
|
+
const threading = new ThreadingManager(path34.join(memoryDir, "threads"));
|
|
16983
17413
|
if (threadId) {
|
|
16984
17414
|
const thread = await threading.loadThread(threadId);
|
|
16985
17415
|
if (!thread) {
|
|
@@ -17152,16 +17582,16 @@ function parseDuration(duration) {
|
|
|
17152
17582
|
}
|
|
17153
17583
|
|
|
17154
17584
|
// src/index.ts
|
|
17155
|
-
import { readFile as
|
|
17585
|
+
import { readFile as readFile24, writeFile as writeFile22 } from "fs/promises";
|
|
17156
17586
|
import { readFileSync as readFileSync4 } from "fs";
|
|
17157
|
-
import
|
|
17587
|
+
import path35 from "path";
|
|
17158
17588
|
import os5 from "os";
|
|
17159
17589
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
17160
17590
|
function loadPluginConfigFromFile() {
|
|
17161
17591
|
try {
|
|
17162
17592
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
17163
17593
|
const homeDir = process.env.HOME ?? os5.homedir();
|
|
17164
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
17594
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path35.join(homeDir, ".openclaw", "openclaw.json");
|
|
17165
17595
|
const content = readFileSync4(configPath, "utf-8");
|
|
17166
17596
|
const config = JSON.parse(content);
|
|
17167
17597
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
@@ -17322,6 +17752,16 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
17322
17752
|
}
|
|
17323
17753
|
}
|
|
17324
17754
|
);
|
|
17755
|
+
api.on(
|
|
17756
|
+
"agent_heartbeat",
|
|
17757
|
+
(_event, ctx) => {
|
|
17758
|
+
if (orchestrator.config.sessionObserverEnabled !== true) return;
|
|
17759
|
+
const sessionKey = ctx?.sessionKey ?? "default";
|
|
17760
|
+
void orchestrator.observeSessionHeartbeat(sessionKey).catch((err) => {
|
|
17761
|
+
log.debug(`agent_heartbeat observer failed: ${err}`);
|
|
17762
|
+
});
|
|
17763
|
+
}
|
|
17764
|
+
);
|
|
17325
17765
|
api.on(
|
|
17326
17766
|
"before_compaction",
|
|
17327
17767
|
async (event, ctx) => {
|
|
@@ -17359,11 +17799,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
17359
17799
|
);
|
|
17360
17800
|
async function ensureHourlySummaryCron(api2) {
|
|
17361
17801
|
const jobId = "engram-hourly-summary";
|
|
17362
|
-
const cronFilePath =
|
|
17802
|
+
const cronFilePath = path35.join(os5.homedir(), ".openclaw", "cron", "jobs.json");
|
|
17363
17803
|
try {
|
|
17364
17804
|
let jobsData = { version: 1, jobs: [] };
|
|
17365
17805
|
try {
|
|
17366
|
-
const content = await
|
|
17806
|
+
const content = await readFile24(cronFilePath, "utf-8");
|
|
17367
17807
|
jobsData = JSON.parse(content);
|
|
17368
17808
|
} catch {
|
|
17369
17809
|
}
|
|
@@ -17400,7 +17840,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
17400
17840
|
state: {}
|
|
17401
17841
|
};
|
|
17402
17842
|
jobsData.jobs.push(newJob);
|
|
17403
|
-
await
|
|
17843
|
+
await writeFile22(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
|
|
17404
17844
|
log.info("auto-registered hourly summary cron job");
|
|
17405
17845
|
} catch (err) {
|
|
17406
17846
|
log.error("failed to auto-register hourly summary cron job:", err);
|