@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 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 path22 from "path";
417
+ import path23 from "path";
398
418
  import { createHash as createHash4 } from "crypto";
399
- import { mkdir as mkdir16, readFile as readFile16, writeFile as writeFile15 } from "fs/promises";
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:generalist:main → type="main", id="default"
7081
- * - agent:generalist:discord:channel:1468425700242620516 → type="discord", id="1468425700242620516"
7082
- * - agent:generalist:cron:db61e2ac-... → type="cron", id="db61e2ac-..."
7083
- * - agent:generalist:slack:channel:C123456 → type="slack", id="C123456"
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/embedding-fallback.ts
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 = path11.join(config.memoryDir, "state", "embeddings.json");
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 readFile9(this.indexPath, "utf-8");
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 mkdir9(path11.dirname(this.indexPath), { recursive: true });
8595
- await writeFile9(this.indexPath, JSON.stringify(index), "utf-8");
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 (!path11.isAbsolute(filePath)) return filePath;
8601
- const rel = path11.relative(memoryDir, filePath);
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 path12 from "path";
8624
- import { readdir as readdir5, readFile as readFile10 } from "fs/promises";
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 readFile10(filePath, "utf-8");
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 : path12.relative(baseDir, filePath);
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 = path12.join(dir, entry.name);
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 mkdir10, writeFile as writeFile10, readFile as readFile11, readdir as readdir6 } from "fs/promises";
8999
- import path13 from "path";
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 path13.join(this.baseDir, BOX_DIR);
9454
+ return path14.join(this.baseDir, BOX_DIR);
9075
9455
  }
9076
9456
  get stateDir() {
9077
- return path13.join(this.baseDir, STATE_DIR);
9457
+ return path14.join(this.baseDir, STATE_DIR);
9078
9458
  }
9079
9459
  get openBoxStatePath() {
9080
- return path13.join(this.stateDir, OPEN_BOX_STATE_FILE);
9460
+ return path14.join(this.stateDir, OPEN_BOX_STATE_FILE);
9081
9461
  }
9082
9462
  get tracesPath() {
9083
- return path13.join(this.stateDir, TRACES_FILE);
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 readFile11(this.openBoxStatePath, "utf-8");
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 mkdir10(this.stateDir, { recursive: true });
9477
+ await mkdir11(this.stateDir, { recursive: true });
9098
9478
  if (this.openBox) {
9099
- await writeFile10(this.openBoxStatePath, JSON.stringify(this.openBox, null, 2), "utf-8");
9479
+ await writeFile11(this.openBoxStatePath, JSON.stringify(this.openBox, null, 2), "utf-8");
9100
9480
  } else {
9101
9481
  try {
9102
- await writeFile10(this.openBoxStatePath, "null", "utf-8");
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 readFile11(this.tracesPath, "utf-8");
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 mkdir10(this.stateDir, { recursive: true });
9120
- await writeFile10(this.tracesPath, JSON.stringify(idx, null, 2), "utf-8");
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 = path13.join(this.boxBaseDir, day);
9197
- await mkdir10(dir, { recursive: true });
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 = path13.join(dir, `${box.id}.md`);
9217
- await writeFile10(filePath, content, "utf-8");
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 = path13.join(dir, e.name);
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 readFile11(full, "utf-8");
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 path14 from "path";
9382
- import { mkdir as mkdir11, readFile as readFile12, writeFile as writeFile11, readdir as readdir7 } from "fs/promises";
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 path14.join(baseDir, TMT_DIR);
9791
+ return path15.join(baseDir, TMT_DIR);
9412
9792
  }
9413
9793
  function hourNodePath(baseDir, date, hour) {
9414
- return path14.join(tmtDir(baseDir), date, `hour-${hour}.md`);
9794
+ return path15.join(tmtDir(baseDir), date, `hour-${hour}.md`);
9415
9795
  }
9416
9796
  function dayNodePath(baseDir, date) {
9417
- return path14.join(tmtDir(baseDir), date, "day.md");
9797
+ return path15.join(tmtDir(baseDir), date, "day.md");
9418
9798
  }
9419
9799
  function weekNodePath(baseDir, weekKey) {
9420
- return path14.join(tmtDir(baseDir), `week-${weekKey}.md`);
9800
+ return path15.join(tmtDir(baseDir), `week-${weekKey}.md`);
9421
9801
  }
9422
9802
  function personaNodePath(baseDir) {
9423
- return path14.join(tmtDir(baseDir), "persona.md");
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 mkdir11(tmtDir(this.baseDir), { recursive: true });
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 readFile12(nodePath, "utf8");
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 mkdir11(path14.dirname(nodePath), { recursive: true });
9521
- await writeFile11(nodePath, serialiseTmtNode(fm, summary), "utf8");
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 readFile12(nodePath, "utf8");
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 readFile12(hPath, "utf8");
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 mkdir11(path14.dirname(nodePath), { recursive: true });
9586
- await writeFile11(nodePath, serialiseTmtNode(fm, summary), "utf8");
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 readFile12(nodePath, "utf8");
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 readFile12(dayPath, "utf8");
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 mkdir11(path14.dirname(nodePath), { recursive: true });
9652
- await writeFile11(nodePath, serialiseTmtNode(fm, summary), "utf8");
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 readFile12(path14.join(dir, f), "utf8");
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 readFile12(nodePath, "utf8");
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 writeFile11(nodePath, serialiseTmtNode(fm, summary), "utf8");
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 readFile12(todayDay, "utf8");
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 readFile12(dayPath, "utf8");
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 parseIsoMs(value) {
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 = parseIsoMs(value);
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 path15 from "path";
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 path15.join(memoryDir, "state");
10313
+ return path16.join(memoryDir, "state");
9934
10314
  }
9935
10315
  function temporalIndexPath(memoryDir) {
9936
- return path15.join(stateDir(memoryDir), TEMPORAL_INDEX_FILE);
10316
+ return path16.join(stateDir(memoryDir), TEMPORAL_INDEX_FILE);
9937
10317
  }
9938
10318
  function tagIndexPath(memoryDir) {
9939
- return path15.join(stateDir(memoryDir), TAG_INDEX_FILE);
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 mkdir12, appendFile as appendFile4, readFile as readFile13 } from "fs/promises";
10174
- import * as path16 from "path";
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 path16.join(memoryDir, "state", "graphs");
10564
+ return path17.join(memoryDir, "state", "graphs");
10185
10565
  }
10186
10566
  function graphFilePath(memoryDir, type) {
10187
- return path16.join(graphsDir(memoryDir), `${type}.jsonl`);
10567
+ return path17.join(graphsDir(memoryDir), `${type}.jsonl`);
10188
10568
  }
10189
10569
  async function ensureGraphsDir(memoryDir) {
10190
- await mkdir12(graphsDir(memoryDir), { recursive: true });
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 readFile13(filePath, "utf8");
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 mkdir13, writeFile as writeFile12 } from "fs/promises";
10390
- import path17 from "path";
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 = path17.join(rootDir, safe, date);
10401
- await mkdir13(dir, { recursive: true });
10402
- const fp = path17.join(dir, `${c.id}.md`);
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 writeFile12(fp, content, "utf-8");
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 path18 from "path";
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 = path18.join(rootDir, s.name);
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(path18.join(sessionDir, d.name), { recursive: true, force: true });
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 path19 from "path";
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 = path19.join(this.config.memoryDir, "namespaces", this.config.defaultNamespace);
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 path19.join(this.config.memoryDir, "namespaces", namespace);
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 mkdir14, readFile as readFile14, readdir as readdir9, appendFile as appendFile5, writeFile as writeFile13, stat as stat2 } from "fs/promises";
10560
- import path20 from "path";
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 : path20.join(os3.homedir(), ".openclaw", "workspace", "shared-context");
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 = path20.join(base, "priorities.md");
10585
- this.prioritiesInboxPath = path20.join(base, "priorities.inbox.md");
10586
- this.outputsDir = path20.join(base, "agent-outputs");
10587
- this.roundtableDir = path20.join(base, "roundtable");
10588
- this.feedbackDir = path20.join(base, "feedback");
10589
- this.feedbackInboxPath = path20.join(this.feedbackDir, "inbox.jsonl");
10590
- this.crossSignalsDir = path20.join(base, "cross-signals");
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 mkdir14(this.dir, { recursive: true });
10602
- await mkdir14(this.outputsDir, { recursive: true });
10603
- await mkdir14(this.roundtableDir, { recursive: true });
10604
- await mkdir14(this.feedbackDir, { recursive: true });
10605
- await mkdir14(this.crossSignalsDir, { recursive: true });
10606
- await mkdir14(path20.join(this.dir, "staging"), { recursive: true });
10607
- await mkdir14(path20.join(this.dir, "kpis"), { recursive: true });
10608
- await mkdir14(path20.join(this.dir, "calendar"), { recursive: true });
10609
- await mkdir14(path20.join(this.dir, "content-calendar"), { recursive: true });
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 stat2(fp);
11018
+ await stat4(fp);
10639
11019
  } catch {
10640
- await writeFile13(fp, content, "utf-8");
11020
+ await writeFile14(fp, content, "utf-8");
10641
11021
  }
10642
11022
  }
10643
11023
  async readPriorities() {
10644
11024
  try {
10645
- return await readFile14(this.prioritiesPath, "utf-8");
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] ? path20.join(this.roundtableDir, files[0]) : null;
11033
+ const fp = files[0] ? path21.join(this.roundtableDir, files[0]) : null;
10654
11034
  if (!fp) return "";
10655
- return await readFile14(fp, "utf-8");
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 = path20.join(this.outputsDir, opts.agentId, date);
10666
- await mkdir14(dir, { recursive: true });
10667
- const fp = path20.join(dir, `${time}-${slug}.md`);
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 writeFile13(fp, body, "utf-8");
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 = path20.join(this.outputsDir, a.name, date);
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 = path20.join(dayDir, f);
10707
- const raw = await readFile14(p, "utf-8");
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 readFile14(this.feedbackInboxPath, "utf-8");
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 = path20.join(this.roundtableDir, `${date}.md`);
10747
- await writeFile13(fp, trimmed, "utf-8");
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 mkdir15, readFile as readFile15, readdir as readdir10, writeFile as writeFile14 } from "fs/promises";
10755
- import path21 from "path";
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 path21.join(os4.homedir(), ".openclaw", "workspace", "shared-context");
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 = path21.join(config.memoryDir, "compounding", "weekly");
10806
- this.mistakesPath = path21.join(config.memoryDir, "compounding", "mistakes.json");
10807
- this.feedbackInboxPath = path21.join(sharedContextDir(config), "feedback", "inbox.jsonl");
10808
- this.identityAnchorPath = path21.join(config.memoryDir, "identity", "identity-anchor.md");
10809
- this.identityIncidentsDir = path21.join(config.memoryDir, "identity", "incidents");
10810
- this.identityAuditWeeklyDir = path21.join(config.memoryDir, "identity", "audits", "weekly");
10811
- this.identityAuditMonthlyDir = path21.join(config.memoryDir, "identity", "audits", "monthly");
10812
- this.identityImprovementLoopsPath = path21.join(config.memoryDir, "identity", "improvement-loops.md");
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 mkdir15(this.weeklyDir, { recursive: true });
10824
- await mkdir15(path21.dirname(this.mistakesPath), { recursive: true });
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 = path21.join(this.weeklyDir, `${weekId}.md`);
11212
+ const reportPath = path22.join(this.weeklyDir, `${weekId}.md`);
10833
11213
  const md = this.formatWeeklyReport(weekId, entries, mistakes.patterns, continuity);
10834
- await writeFile14(reportPath, md, "utf-8");
10835
- await writeFile14(this.mistakesPath, JSON.stringify(mistakes, null, 2) + "\n", "utf-8");
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 mkdir15(dir, { recursive: true });
10912
- const reportPath = path21.join(dir, `${key}.md`);
10913
- await writeFile14(reportPath, lines.join("\n"), "utf-8");
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 readFile15(this.mistakesPath, "utf-8");
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 readFile15(this.feedbackInboxPath, "utf-8");
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 readFile15(filePath, "utf-8");
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 readFile15(filePath, "utf-8");
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 = path21.join(this.identityIncidentsDir, file);
11421
+ const filePath = path22.join(this.identityIncidentsDir, file);
11042
11422
  try {
11043
- const raw = await readFile15(filePath, "utf-8");
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 = path21.join(this.identityAuditWeeklyDir, `${weekId}.md`);
11058
- const monthlyPath = path21.join(this.identityAuditMonthlyDir, `${monthId}.md`);
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 = path22.isAbsolute(candidatePath) ? candidatePath : path22.resolve(storageDir, candidatePath);
11250
- const rel = path22.relative(storageDir, absolutePath);
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(path22.sep).join("/");
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, path22.relative(storageDir, mem.path));
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: path22.join(options.storageDir, options.memoryRelPath),
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 path22.join("corrections", `${options.memoryId}.md`);
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 path22.join("facts", day, `${options.memoryId}.md`);
11709
+ return path23.join("facts", day, `${options.memoryId}.md`);
11330
11710
  }
11331
- return path22.join("facts", `${options.memoryId}.md`);
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 = path22.join(config.memoryDir, "conversation-index", "chunks");
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
- path22.join(config.memoryDir, "threads"),
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 = path22.join(this.config.memoryDir, "state");
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
- path22.join(this.config.memoryDir, "conversation-index")
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 = path22.isAbsolute(rel) ? rel : path22.join(this.config.workspaceDir, rel);
11999
+ const abs = path23.isAbsolute(rel) ? rel : path23.join(this.config.workspaceDir, rel);
11612
12000
  try {
11613
- const raw = await readFile16(abs, "utf-8");
12001
+ const raw = await readFile17(abs, "utf-8");
11614
12002
  if (raw.length > hygiene.rotateMaxBytes) {
11615
- const archiveDir = path22.join(this.config.workspaceDir, hygiene.archiveDir);
11616
- const base = path22.basename(abs);
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 writeFile15(abs, newContent, "utf-8");
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 = path22.join(this.config.memoryDir, hygiene.warningsLogPath);
11642
- await mkdir16(path22.dirname(fp), { recursive: true });
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 readFile16(fp, "utf-8");
12039
+ existing = await readFile17(fp, "utf-8");
11652
12040
  } catch {
11653
12041
  existing = "# Engram File Hygiene Warnings\n";
11654
12042
  }
11655
- await writeFile15(fp, existing + block, "utf-8");
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 = path22.join(storage.dir, "state", "last_graph_recall.json");
12086
+ const snapshotPath = path23.join(storage.dir, "state", "last_graph_recall.json");
11699
12087
  try {
11700
- const raw = await readFile16(snapshotPath, "utf-8");
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) => path22.join(storage.dir, 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 = path22.resolve(storage.dir, candidate.path);
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 = path22.join(options.storage.dir, "state", "last_graph_recall.json");
12038
- await mkdir16(path22.dirname(snapshotPath), { recursive: true });
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 writeFile15(snapshotPath, JSON.stringify(payload, null, 2), "utf-8");
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
- const turnsToExtract = this.buffer.getTurns();
12734
- if (!this.shouldQueueExtraction(turnsToExtract)) {
12735
- await this.buffer.clearAfterExtraction();
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
- this.recentExtractionFingerprints.set(fingerprint, now);
12762
- if (this.recentExtractionFingerprints.size > 200) {
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 = path22.relative(storage.dir, m.path);
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 = path22.join(this.storage.dir, "state", "lifecycle-metrics.json");
13652
- await mkdir16(path22.dirname(metricsPath), { recursive: true });
13653
- await writeFile15(metricsPath, JSON.stringify(metrics, null, 2), "utf-8");
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 = path22.isAbsolute(hit.path) ? hit.path : path22.join(this.config.memoryDir, hit.path);
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 path23 from "path";
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 = path23.join(process.env.HOME ?? "~", ".openclaw", "workspace");
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 path33 from "path";
15769
- import { access as access2, readFile as readFile22, readdir as readdir13, unlink as unlink3 } from "fs/promises";
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 path25 from "path";
15773
- import { mkdir as mkdir18, readFile as readFile18 } from "fs/promises";
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 mkdir17, readdir as readdir11, readFile as readFile17, stat as stat3, writeFile as writeFile16 } from "fs/promises";
15782
- import path24 from "path";
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 readFile17(filePath);
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 mkdir17(path24.dirname(filePath), { recursive: true });
15795
- await writeFile16(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
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 readFile17(filePath, "utf-8");
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 = path24.join(dir, ent.name);
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 stat3(filePath);
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 = path24.relative(rootDir, absPath);
15827
- return rel.split(path24.sep).join("/");
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(path24.sep);
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 = path25.resolve(opts.outDir);
15847
- await mkdir18(outDirAbs, { recursive: true });
15848
- const memoryDirAbs = path25.resolve(opts.memoryDir);
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 readFile18(abs, "utf-8");
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 = path25.join(opts.workspaceDir, "IDENTITY.md");
16291
+ const identityPath = path26.join(opts.workspaceDir, "IDENTITY.md");
15862
16292
  try {
15863
- const content = await readFile18(identityPath, "utf-8");
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(path25.join(outDirAbs, "manifest.json"), manifest);
15881
- await writeJsonFile(path25.join(outDirAbs, "bundle.json"), bundle);
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 path26 from "path";
15886
- import { mkdir as mkdir19, readFile as readFile19, writeFile as writeFile17 } from "fs/promises";
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 = path26.resolve(opts.outDir);
15895
- await mkdir19(outDirAbs, { recursive: true });
15896
- const memDirAbs = path26.resolve(opts.memoryDir);
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 = path26.join(outDirAbs, ...relPosix.split("/"));
15903
- await mkdir19(path26.dirname(dstAbs), { recursive: true });
15904
- const content = await readFile19(abs);
15905
- await writeFile17(dstAbs, content);
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(path26.join(outDirAbs, "manifest.json"), manifest);
16347
+ await writeJsonFile(path27.join(outDirAbs, "manifest.json"), manifest);
15918
16348
  }
15919
16349
  async function looksLikeEngramMdExport(fromDir) {
15920
- const dirAbs = path26.resolve(fromDir);
16350
+ const dirAbs = path27.resolve(fromDir);
15921
16351
  try {
15922
- const raw = await readFile19(path26.join(dirAbs, "manifest.json"), "utf-8");
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 path27 from "path";
15932
- import { mkdir as mkdir20, readdir as readdir12, rm as rm2 } from "fs/promises";
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 = path27.resolve(opts.outDir);
15938
- await mkdir20(outDirAbs, { recursive: true });
16367
+ const outDirAbs = path28.resolve(opts.outDir);
16368
+ await mkdir21(outDirAbs, { recursive: true });
15939
16369
  const ts = timestampDirName(/* @__PURE__ */ new Date());
15940
- const backupDir = path27.join(outDirAbs, ts);
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(path27.join(outDirAbs, name), { recursive: true, force: true });
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 path28 from "path";
16401
+ import path29 from "path";
15972
16402
  import Database from "better-sqlite3";
15973
- import { readFile as readFile20 } from "fs/promises";
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 = path28.resolve(opts.memoryDir);
16000
- const outAbs = path28.resolve(opts.outFile);
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 readFile20(abs, "utf-8");
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 path29 from "path";
16033
- import { mkdir as mkdir21, writeFile as writeFile18 } from "fs/promises";
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 = path29.resolve(opts.fromDir);
16067
- const bundlePath = path29.join(fromDirAbs, "bundle.json");
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 = path29.resolve(opts.targetMemoryDir);
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 ? path29.resolve(opts.workspaceDir) : null : memDirAbs;
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 = path29.join(targetBase, relFs);
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 mkdir21(path29.dirname(w.abs), { recursive: true });
16105
- await writeFile18(w.abs, w.content, "utf-8");
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 = path29.resolve(fromDir);
16540
+ const dir = path30.resolve(fromDir);
16111
16541
  return Promise.all([
16112
- fileExists(path29.join(dir, "manifest.json")),
16113
- fileExists(path29.join(dir, "bundle.json"))
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 path30 from "path";
16548
+ import path31 from "path";
16119
16549
  import Database2 from "better-sqlite3";
16120
- import { mkdir as mkdir22, writeFile as writeFile19 } from "fs/promises";
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 = path30.resolve(opts.targetMemoryDir);
16127
- const fromAbs = path30.resolve(opts.fromFile);
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 = path30.join(memDirAbs, relFs);
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 mkdir22(path30.dirname(w.abs), { recursive: true });
16166
- await writeFile19(w.abs, w.content, "utf-8");
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 path31 from "path";
16173
- import { mkdir as mkdir23, readFile as readFile21, writeFile as writeFile20 } from "fs/promises";
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 = path31.resolve(opts.fromDir);
16180
- const targetAbs = path31.resolve(opts.targetMemoryDir);
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 = path31.join(targetAbs, fromPosixRelPath(relPosix));
16188
- const content = await readFile21(abs, "utf-8");
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 mkdir23(path31.dirname(w.abs), { recursive: true });
16211
- await writeFile20(w.abs, w.content, "utf-8");
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 path32 from "path";
16218
- import { stat as stat4 } from "fs/promises";
16647
+ import path33 from "path";
16648
+ import { stat as stat6 } from "fs/promises";
16219
16649
  async function detectImportFormat(fromPath) {
16220
- const abs = path32.resolve(fromPath);
16650
+ const abs = path33.resolve(fromPath);
16221
16651
  let st;
16222
16652
  try {
16223
- st = await stat4(abs);
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 readFile22(pkgPath, "utf-8");
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 = path33.join(orchestrator.config.memoryDir, "namespaces", ns);
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 = [path33.join(memoryDir, "facts"), path33.join(memoryDir, "corrections")];
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 = path33.join(dir, entryName);
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 readFile22(fullPath, "utf-8");
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 unlink3(filePath);
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 unlink3(filePath);
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 = path33.join(process.env.HOME ?? "~", ".openclaw", "workspace");
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 = path33.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
16982
- const threading = new ThreadingManager(path33.join(memoryDir, "threads"));
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 readFile23, writeFile as writeFile21 } from "fs/promises";
17585
+ import { readFile as readFile24, writeFile as writeFile22 } from "fs/promises";
17156
17586
  import { readFileSync as readFileSync4 } from "fs";
17157
- import path34 from "path";
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 : path34.join(homeDir, ".openclaw", "openclaw.json");
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 = path34.join(os5.homedir(), ".openclaw", "cron", "jobs.json");
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 readFile23(cronFilePath, "utf-8");
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 writeFile21(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
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);