@joshuaswarren/openclaw-engram 8.0.3 → 8.0.4
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 +551 -148
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +10 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -368,12 +368,15 @@ function parseConfig(raw) {
|
|
|
368
368
|
traceWeaverOverlapThreshold: typeof cfg.traceWeaverOverlapThreshold === "number" ? cfg.traceWeaverOverlapThreshold : 0.4,
|
|
369
369
|
boxRecallDays: typeof cfg.boxRecallDays === "number" ? cfg.boxRecallDays : 3,
|
|
370
370
|
// v8.0 Phase 2B: Episode/Note dual store (HiMem)
|
|
371
|
-
episodeNoteModeEnabled: cfg.episodeNoteModeEnabled === true
|
|
371
|
+
episodeNoteModeEnabled: cfg.episodeNoteModeEnabled === true,
|
|
372
|
+
// v8.1: Temporal + Tag Indexes (SwiftMem-inspired)
|
|
373
|
+
queryAwareIndexingEnabled: cfg.queryAwareIndexingEnabled === true,
|
|
374
|
+
queryAwareIndexingMaxCandidates: typeof cfg.queryAwareIndexingMaxCandidates === "number" ? Math.max(0, cfg.queryAwareIndexingMaxCandidates) : 200
|
|
372
375
|
};
|
|
373
376
|
}
|
|
374
377
|
|
|
375
378
|
// src/orchestrator.ts
|
|
376
|
-
import
|
|
379
|
+
import path20 from "path";
|
|
377
380
|
import { createHash as createHash4 } from "crypto";
|
|
378
381
|
import { mkdir as mkdir14, readFile as readFile14, writeFile as writeFile14 } from "fs/promises";
|
|
379
382
|
|
|
@@ -1101,8 +1104,8 @@ var LocalLlmClient = class _LocalLlmClient {
|
|
|
1101
1104
|
log.debug(`local LLM: request body length=${requestBodyJson.length}`);
|
|
1102
1105
|
if (this.config.debug) {
|
|
1103
1106
|
try {
|
|
1104
|
-
const { writeFileSync:
|
|
1105
|
-
|
|
1107
|
+
const { writeFileSync: writeFileSync3 } = await import("fs");
|
|
1108
|
+
writeFileSync3("/tmp/engram-last-request.json", requestBodyJson);
|
|
1106
1109
|
} catch {
|
|
1107
1110
|
}
|
|
1108
1111
|
}
|
|
@@ -4300,8 +4303,8 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
4300
4303
|
hashes = /* @__PURE__ */ new Set();
|
|
4301
4304
|
dirty = false;
|
|
4302
4305
|
filePath;
|
|
4303
|
-
constructor(
|
|
4304
|
-
this.filePath = path4.join(
|
|
4306
|
+
constructor(stateDir2) {
|
|
4307
|
+
this.filePath = path4.join(stateDir2, "fact-hashes.txt");
|
|
4305
4308
|
}
|
|
4306
4309
|
/** Load existing hashes from disk. Safe to call multiple times. */
|
|
4307
4310
|
async load() {
|
|
@@ -4998,23 +5001,23 @@ ${sanitized.text}
|
|
|
4998
5001
|
async cleanExpiredTTL() {
|
|
4999
5002
|
const memories = await this.readAllMemories();
|
|
5000
5003
|
const now = Date.now();
|
|
5001
|
-
|
|
5004
|
+
const deleted = [];
|
|
5002
5005
|
for (const m of memories) {
|
|
5003
5006
|
if (!m.frontmatter.expiresAt) continue;
|
|
5004
5007
|
const expiresAt = new Date(m.frontmatter.expiresAt).getTime();
|
|
5005
5008
|
if (expiresAt < now) {
|
|
5006
5009
|
try {
|
|
5007
5010
|
await unlink(m.path);
|
|
5008
|
-
|
|
5011
|
+
deleted.push(m);
|
|
5009
5012
|
log.debug(`cleaned expired memory ${m.frontmatter.id} (TTL expired)`);
|
|
5010
5013
|
} catch {
|
|
5011
5014
|
}
|
|
5012
5015
|
}
|
|
5013
5016
|
}
|
|
5014
|
-
if (
|
|
5017
|
+
if (deleted.length > 0) {
|
|
5015
5018
|
this.bumpMemoryStatusVersion();
|
|
5016
5019
|
}
|
|
5017
|
-
return
|
|
5020
|
+
return deleted;
|
|
5018
5021
|
}
|
|
5019
5022
|
async loadBuffer() {
|
|
5020
5023
|
const bufferPath = path4.join(this.stateDir, "buffer.json");
|
|
@@ -5499,7 +5502,7 @@ ${rows.join("\n")}
|
|
|
5499
5502
|
async cleanExpiredCommitments(decayDays) {
|
|
5500
5503
|
const memories = await this.readAllMemories();
|
|
5501
5504
|
const cutoff = Date.now() - decayDays * 24 * 60 * 60 * 1e3;
|
|
5502
|
-
|
|
5505
|
+
const deleted = [];
|
|
5503
5506
|
for (const m of memories) {
|
|
5504
5507
|
if (m.frontmatter.category !== "commitment") continue;
|
|
5505
5508
|
const isResolved = m.frontmatter.tags.some(
|
|
@@ -5510,16 +5513,16 @@ ${rows.join("\n")}
|
|
|
5510
5513
|
if (updatedAt < cutoff) {
|
|
5511
5514
|
try {
|
|
5512
5515
|
await unlink(m.path);
|
|
5513
|
-
|
|
5516
|
+
deleted.push(m);
|
|
5514
5517
|
log.debug(`cleaned expired commitment ${m.frontmatter.id}`);
|
|
5515
5518
|
} catch {
|
|
5516
5519
|
}
|
|
5517
5520
|
}
|
|
5518
5521
|
}
|
|
5519
|
-
if (
|
|
5522
|
+
if (deleted.length > 0) {
|
|
5520
5523
|
this.bumpMemoryStatusVersion();
|
|
5521
5524
|
}
|
|
5522
|
-
return
|
|
5525
|
+
return deleted;
|
|
5523
5526
|
}
|
|
5524
5527
|
// ---------------------------------------------------------------------------
|
|
5525
5528
|
// Access Tracking (Phase 1A)
|
|
@@ -8378,6 +8381,252 @@ function classifyMemoryKind(content, tags, category) {
|
|
|
8378
8381
|
return "episode";
|
|
8379
8382
|
}
|
|
8380
8383
|
|
|
8384
|
+
// src/temporal-index.ts
|
|
8385
|
+
import * as fs from "fs";
|
|
8386
|
+
import * as path14 from "path";
|
|
8387
|
+
var INDEX_VERSION = 1;
|
|
8388
|
+
var TEMPORAL_INDEX_FILE = "index_time.json";
|
|
8389
|
+
var TAG_INDEX_FILE = "index_tags.json";
|
|
8390
|
+
function stateDir(memoryDir) {
|
|
8391
|
+
return path14.join(memoryDir, "state");
|
|
8392
|
+
}
|
|
8393
|
+
function temporalIndexPath(memoryDir) {
|
|
8394
|
+
return path14.join(stateDir(memoryDir), TEMPORAL_INDEX_FILE);
|
|
8395
|
+
}
|
|
8396
|
+
function tagIndexPath(memoryDir) {
|
|
8397
|
+
return path14.join(stateDir(memoryDir), TAG_INDEX_FILE);
|
|
8398
|
+
}
|
|
8399
|
+
function ensureStateDir(memoryDir) {
|
|
8400
|
+
const dir = stateDir(memoryDir);
|
|
8401
|
+
if (!fs.existsSync(dir)) {
|
|
8402
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
8403
|
+
}
|
|
8404
|
+
}
|
|
8405
|
+
function readJsonSafe(filePath, fallback) {
|
|
8406
|
+
try {
|
|
8407
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
8408
|
+
return JSON.parse(raw);
|
|
8409
|
+
} catch {
|
|
8410
|
+
return fallback;
|
|
8411
|
+
}
|
|
8412
|
+
}
|
|
8413
|
+
function writeJsonSafe(filePath, data) {
|
|
8414
|
+
try {
|
|
8415
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
8416
|
+
} catch {
|
|
8417
|
+
}
|
|
8418
|
+
}
|
|
8419
|
+
function writeJsonAtomic(filePath, data) {
|
|
8420
|
+
const tmp = `${filePath}.tmp`;
|
|
8421
|
+
try {
|
|
8422
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
|
|
8423
|
+
fs.renameSync(tmp, filePath);
|
|
8424
|
+
} catch {
|
|
8425
|
+
writeJsonSafe(filePath, data);
|
|
8426
|
+
try {
|
|
8427
|
+
fs.unlinkSync(tmp);
|
|
8428
|
+
} catch {
|
|
8429
|
+
}
|
|
8430
|
+
}
|
|
8431
|
+
}
|
|
8432
|
+
function isoDateFromTimestamp(isoString) {
|
|
8433
|
+
if (typeof isoString !== "string" || isoString.length < 10) {
|
|
8434
|
+
console.warn(`[engram] temporal-index: malformed timestamp "${isoString}", falling back to today`);
|
|
8435
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8436
|
+
}
|
|
8437
|
+
return isoString.slice(0, 10);
|
|
8438
|
+
}
|
|
8439
|
+
function addPathToSet(record, key, p) {
|
|
8440
|
+
if (!record[key]) {
|
|
8441
|
+
record[key] = [];
|
|
8442
|
+
}
|
|
8443
|
+
if (!record[key].includes(p)) {
|
|
8444
|
+
record[key].push(p);
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
function removePathFromSet(record, key, p) {
|
|
8448
|
+
if (!record[key]) return;
|
|
8449
|
+
record[key] = record[key].filter((x) => x !== p);
|
|
8450
|
+
if (record[key].length === 0) {
|
|
8451
|
+
delete record[key];
|
|
8452
|
+
}
|
|
8453
|
+
}
|
|
8454
|
+
function indexMemory(memoryDir, memoryPath, createdAt, tags) {
|
|
8455
|
+
try {
|
|
8456
|
+
ensureStateDir(memoryDir);
|
|
8457
|
+
const tPath = temporalIndexPath(memoryDir);
|
|
8458
|
+
const tIndex = readJsonSafe(tPath, { version: INDEX_VERSION, dates: {} });
|
|
8459
|
+
const dateKey = isoDateFromTimestamp(createdAt);
|
|
8460
|
+
addPathToSet(tIndex.dates, dateKey, memoryPath);
|
|
8461
|
+
writeJsonAtomic(tPath, tIndex);
|
|
8462
|
+
const gPath = tagIndexPath(memoryDir);
|
|
8463
|
+
const gIndex = readJsonSafe(gPath, { version: INDEX_VERSION, tags: {} });
|
|
8464
|
+
for (const tag of tags) {
|
|
8465
|
+
if (tag && typeof tag === "string") {
|
|
8466
|
+
addPathToSet(gIndex.tags, tag.toLowerCase(), memoryPath);
|
|
8467
|
+
}
|
|
8468
|
+
}
|
|
8469
|
+
writeJsonAtomic(gPath, gIndex);
|
|
8470
|
+
} catch {
|
|
8471
|
+
}
|
|
8472
|
+
}
|
|
8473
|
+
function deindexMemory(memoryDir, memoryPath, createdAt, tags) {
|
|
8474
|
+
try {
|
|
8475
|
+
ensureStateDir(memoryDir);
|
|
8476
|
+
const tPath = temporalIndexPath(memoryDir);
|
|
8477
|
+
const tIndex = readJsonSafe(tPath, { version: INDEX_VERSION, dates: {} });
|
|
8478
|
+
const dateKey = isoDateFromTimestamp(createdAt);
|
|
8479
|
+
removePathFromSet(tIndex.dates, dateKey, memoryPath);
|
|
8480
|
+
writeJsonAtomic(tPath, tIndex);
|
|
8481
|
+
const gPath = tagIndexPath(memoryDir);
|
|
8482
|
+
const gIndex = readJsonSafe(gPath, { version: INDEX_VERSION, tags: {} });
|
|
8483
|
+
for (const tag of tags) {
|
|
8484
|
+
if (tag && typeof tag === "string") {
|
|
8485
|
+
removePathFromSet(gIndex.tags, tag.toLowerCase(), memoryPath);
|
|
8486
|
+
}
|
|
8487
|
+
}
|
|
8488
|
+
writeJsonAtomic(gPath, gIndex);
|
|
8489
|
+
} catch {
|
|
8490
|
+
}
|
|
8491
|
+
}
|
|
8492
|
+
function clearIndexes(memoryDir) {
|
|
8493
|
+
try {
|
|
8494
|
+
ensureStateDir(memoryDir);
|
|
8495
|
+
writeJsonAtomic(temporalIndexPath(memoryDir), { version: INDEX_VERSION, dates: {} });
|
|
8496
|
+
writeJsonAtomic(tagIndexPath(memoryDir), { version: INDEX_VERSION, tags: {} });
|
|
8497
|
+
} catch {
|
|
8498
|
+
}
|
|
8499
|
+
}
|
|
8500
|
+
function indexesExist(memoryDir) {
|
|
8501
|
+
try {
|
|
8502
|
+
return fs.existsSync(temporalIndexPath(memoryDir)) && fs.existsSync(tagIndexPath(memoryDir));
|
|
8503
|
+
} catch {
|
|
8504
|
+
return false;
|
|
8505
|
+
}
|
|
8506
|
+
}
|
|
8507
|
+
function indexMemoriesBatch(memoryDir, entries) {
|
|
8508
|
+
if (entries.length === 0) return;
|
|
8509
|
+
try {
|
|
8510
|
+
ensureStateDir(memoryDir);
|
|
8511
|
+
const tPath = temporalIndexPath(memoryDir);
|
|
8512
|
+
const tIndex = readJsonSafe(tPath, { version: INDEX_VERSION, dates: {} });
|
|
8513
|
+
const gPath = tagIndexPath(memoryDir);
|
|
8514
|
+
const gIndex = readJsonSafe(gPath, { version: INDEX_VERSION, tags: {} });
|
|
8515
|
+
for (const entry of entries) {
|
|
8516
|
+
const dateKey = isoDateFromTimestamp(entry.createdAt);
|
|
8517
|
+
addPathToSet(tIndex.dates, dateKey, entry.path);
|
|
8518
|
+
for (const tag of entry.tags) {
|
|
8519
|
+
if (tag && typeof tag === "string") {
|
|
8520
|
+
addPathToSet(gIndex.tags, tag.toLowerCase(), entry.path);
|
|
8521
|
+
}
|
|
8522
|
+
}
|
|
8523
|
+
}
|
|
8524
|
+
writeJsonAtomic(tPath, tIndex);
|
|
8525
|
+
writeJsonAtomic(gPath, gIndex);
|
|
8526
|
+
} catch {
|
|
8527
|
+
}
|
|
8528
|
+
}
|
|
8529
|
+
async function queryByDateRangeAsync(memoryDir, fromDate, toDate) {
|
|
8530
|
+
try {
|
|
8531
|
+
const tPath = temporalIndexPath(memoryDir);
|
|
8532
|
+
let raw;
|
|
8533
|
+
try {
|
|
8534
|
+
raw = await fs.promises.readFile(tPath, "utf8");
|
|
8535
|
+
} catch {
|
|
8536
|
+
return null;
|
|
8537
|
+
}
|
|
8538
|
+
let tIndex;
|
|
8539
|
+
try {
|
|
8540
|
+
tIndex = JSON.parse(raw);
|
|
8541
|
+
} catch {
|
|
8542
|
+
tIndex = { version: INDEX_VERSION, dates: {} };
|
|
8543
|
+
}
|
|
8544
|
+
const end = toDate ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8545
|
+
const results = /* @__PURE__ */ new Set();
|
|
8546
|
+
for (const [date, paths] of Object.entries(tIndex.dates)) {
|
|
8547
|
+
if (date >= fromDate && date <= end) {
|
|
8548
|
+
for (const p of paths) {
|
|
8549
|
+
results.add(p);
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
}
|
|
8553
|
+
return results;
|
|
8554
|
+
} catch {
|
|
8555
|
+
return null;
|
|
8556
|
+
}
|
|
8557
|
+
}
|
|
8558
|
+
async function queryByTagsAsync(memoryDir, tags) {
|
|
8559
|
+
if (tags.length === 0) return null;
|
|
8560
|
+
try {
|
|
8561
|
+
const gPath = tagIndexPath(memoryDir);
|
|
8562
|
+
let raw;
|
|
8563
|
+
try {
|
|
8564
|
+
raw = await fs.promises.readFile(gPath, "utf8");
|
|
8565
|
+
} catch {
|
|
8566
|
+
return null;
|
|
8567
|
+
}
|
|
8568
|
+
let gIndex;
|
|
8569
|
+
try {
|
|
8570
|
+
gIndex = JSON.parse(raw);
|
|
8571
|
+
} catch {
|
|
8572
|
+
gIndex = { version: INDEX_VERSION, tags: {} };
|
|
8573
|
+
}
|
|
8574
|
+
const results = /* @__PURE__ */ new Set();
|
|
8575
|
+
for (const tag of tags) {
|
|
8576
|
+
const key = tag.toLowerCase();
|
|
8577
|
+
const paths = gIndex.tags[key] ?? [];
|
|
8578
|
+
for (const p of paths) {
|
|
8579
|
+
results.add(p);
|
|
8580
|
+
}
|
|
8581
|
+
}
|
|
8582
|
+
return results.size > 0 ? results : null;
|
|
8583
|
+
} catch {
|
|
8584
|
+
return null;
|
|
8585
|
+
}
|
|
8586
|
+
}
|
|
8587
|
+
function extractTagsFromPrompt(prompt) {
|
|
8588
|
+
const found = /* @__PURE__ */ new Set();
|
|
8589
|
+
const hashMatches = prompt.matchAll(/#([a-zA-Z][\w-]{1,30})/g);
|
|
8590
|
+
for (const m of hashMatches) {
|
|
8591
|
+
found.add(m[1].toLowerCase());
|
|
8592
|
+
}
|
|
8593
|
+
return Array.from(found);
|
|
8594
|
+
}
|
|
8595
|
+
function isTemporalQuery(prompt) {
|
|
8596
|
+
return /\b(today|yesterday|this week|last week|this month|last month|recent(?:ly)?|lately|just now|earlier today|this morning|last night|\d+ days? ago|\d+ hours? ago)\b/i.test(
|
|
8597
|
+
prompt
|
|
8598
|
+
);
|
|
8599
|
+
}
|
|
8600
|
+
function recencyWindowFromPrompt(prompt, nowMs = Date.now()) {
|
|
8601
|
+
const p = prompt.toLowerCase();
|
|
8602
|
+
let daysBack = 7;
|
|
8603
|
+
if (/\btoday\b/.test(p) || /\bthis morning\b/.test(p) || /\bjust now\b/.test(p) || /\bearlier today\b/.test(p)) {
|
|
8604
|
+
daysBack = 0;
|
|
8605
|
+
} else if (/\byesterday\b/.test(p) || /\blast night\b/.test(p)) {
|
|
8606
|
+
daysBack = 1;
|
|
8607
|
+
} else if (/\bthis week\b/.test(p)) {
|
|
8608
|
+
daysBack = 7;
|
|
8609
|
+
} else if (/\blast week\b/.test(p)) {
|
|
8610
|
+
daysBack = 14;
|
|
8611
|
+
} else if (/\bthis month\b/.test(p)) {
|
|
8612
|
+
daysBack = 31;
|
|
8613
|
+
} else if (/\blast month\b/.test(p)) {
|
|
8614
|
+
daysBack = 62;
|
|
8615
|
+
} else {
|
|
8616
|
+
const numMatch = p.match(/(\d{1,5})\s*days?\s*ago/);
|
|
8617
|
+
if (numMatch) {
|
|
8618
|
+
daysBack = Math.min(365, parseInt(numMatch[1], 10));
|
|
8619
|
+
} else {
|
|
8620
|
+
const hrMatch = p.match(/(\d{1,5})\s*hours?\s*ago/);
|
|
8621
|
+
if (hrMatch) {
|
|
8622
|
+
daysBack = Math.max(1, Math.ceil(parseInt(hrMatch[1], 10) / 24));
|
|
8623
|
+
}
|
|
8624
|
+
}
|
|
8625
|
+
}
|
|
8626
|
+
const from = new Date(nowMs - daysBack * 24 * 60 * 60 * 1e3);
|
|
8627
|
+
return from.toISOString().slice(0, 10);
|
|
8628
|
+
}
|
|
8629
|
+
|
|
8381
8630
|
// src/conversation-index/chunker.ts
|
|
8382
8631
|
function chunkTranscriptEntries(sessionKey, entries, opts) {
|
|
8383
8632
|
const maxChars = Math.max(500, opts.maxChars);
|
|
@@ -8412,7 +8661,7 @@ function chunkTranscriptEntries(sessionKey, entries, opts) {
|
|
|
8412
8661
|
|
|
8413
8662
|
// src/conversation-index/indexer.ts
|
|
8414
8663
|
import { mkdir as mkdir11, writeFile as writeFile11 } from "fs/promises";
|
|
8415
|
-
import
|
|
8664
|
+
import path15 from "path";
|
|
8416
8665
|
function sanitizeSessionKey(sessionKey) {
|
|
8417
8666
|
const raw = typeof sessionKey === "string" && sessionKey.trim().length > 0 ? sessionKey : "unknown-session";
|
|
8418
8667
|
return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "_").slice(0, 200);
|
|
@@ -8422,9 +8671,9 @@ async function writeConversationChunks(rootDir, chunks) {
|
|
|
8422
8671
|
for (const c of chunks) {
|
|
8423
8672
|
const safe = sanitizeSessionKey(c.sessionKey);
|
|
8424
8673
|
const date = c.startTs.slice(0, 10);
|
|
8425
|
-
const dir =
|
|
8674
|
+
const dir = path15.join(rootDir, safe, date);
|
|
8426
8675
|
await mkdir11(dir, { recursive: true });
|
|
8427
|
-
const fp =
|
|
8676
|
+
const fp = path15.join(dir, `${c.id}.md`);
|
|
8428
8677
|
const content = `---
|
|
8429
8678
|
kind: conversation_chunk
|
|
8430
8679
|
sessionKey: ${c.sessionKey}
|
|
@@ -8441,7 +8690,7 @@ endTs: ${c.endTs}
|
|
|
8441
8690
|
|
|
8442
8691
|
// src/conversation-index/cleanup.ts
|
|
8443
8692
|
import { readdir as readdir7, rm } from "fs/promises";
|
|
8444
|
-
import
|
|
8693
|
+
import path16 from "path";
|
|
8445
8694
|
async function cleanupConversationChunks(rootDir, retentionDays) {
|
|
8446
8695
|
if (!Number.isFinite(retentionDays) || retentionDays <= 0) return;
|
|
8447
8696
|
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
@@ -8449,7 +8698,7 @@ async function cleanupConversationChunks(rootDir, retentionDays) {
|
|
|
8449
8698
|
const sessions = await readdir7(rootDir, { withFileTypes: true });
|
|
8450
8699
|
for (const s of sessions) {
|
|
8451
8700
|
if (!s.isDirectory()) continue;
|
|
8452
|
-
const sessionDir =
|
|
8701
|
+
const sessionDir = path16.join(rootDir, s.name);
|
|
8453
8702
|
const dayDirs = await readdir7(sessionDir, { withFileTypes: true });
|
|
8454
8703
|
for (const d of dayDirs) {
|
|
8455
8704
|
if (!d.isDirectory()) continue;
|
|
@@ -8457,7 +8706,7 @@ async function cleanupConversationChunks(rootDir, retentionDays) {
|
|
|
8457
8706
|
const dayMs = (/* @__PURE__ */ new Date(d.name + "T00:00:00.000Z")).getTime();
|
|
8458
8707
|
if (!Number.isFinite(dayMs)) continue;
|
|
8459
8708
|
if (dayMs < cutoffMs) {
|
|
8460
|
-
await rm(
|
|
8709
|
+
await rm(path16.join(sessionDir, d.name), { recursive: true, force: true });
|
|
8461
8710
|
}
|
|
8462
8711
|
}
|
|
8463
8712
|
try {
|
|
@@ -8474,7 +8723,7 @@ async function cleanupConversationChunks(rootDir, retentionDays) {
|
|
|
8474
8723
|
}
|
|
8475
8724
|
|
|
8476
8725
|
// src/namespaces/storage.ts
|
|
8477
|
-
import
|
|
8726
|
+
import path17 from "path";
|
|
8478
8727
|
import { access } from "fs/promises";
|
|
8479
8728
|
async function exists(p) {
|
|
8480
8729
|
try {
|
|
@@ -8496,7 +8745,7 @@ var NamespaceStorageRouter = class {
|
|
|
8496
8745
|
this.defaultNsRootResolved = this.config.memoryDir;
|
|
8497
8746
|
return this.defaultNsRootResolved;
|
|
8498
8747
|
}
|
|
8499
|
-
const nsDir =
|
|
8748
|
+
const nsDir = path17.join(this.config.memoryDir, "namespaces", this.config.defaultNamespace);
|
|
8500
8749
|
this.defaultNsRootResolved = await exists(nsDir) ? nsDir : this.config.memoryDir;
|
|
8501
8750
|
return this.defaultNsRootResolved;
|
|
8502
8751
|
}
|
|
@@ -8505,7 +8754,7 @@ var NamespaceStorageRouter = class {
|
|
|
8505
8754
|
if (namespace === this.config.defaultNamespace) {
|
|
8506
8755
|
return this.defaultNsRootResolved ?? this.config.memoryDir;
|
|
8507
8756
|
}
|
|
8508
|
-
return
|
|
8757
|
+
return path17.join(this.config.memoryDir, "namespaces", namespace);
|
|
8509
8758
|
}
|
|
8510
8759
|
async storageFor(namespace) {
|
|
8511
8760
|
const ns = namespace || this.config.defaultNamespace;
|
|
@@ -8582,7 +8831,7 @@ function recallNamespacesForPrincipal(principal, config) {
|
|
|
8582
8831
|
|
|
8583
8832
|
// src/shared-context/manager.ts
|
|
8584
8833
|
import { mkdir as mkdir12, readFile as readFile12, readdir as readdir8, appendFile as appendFile3, writeFile as writeFile12, stat as stat2 } from "fs/promises";
|
|
8585
|
-
import
|
|
8834
|
+
import path18 from "path";
|
|
8586
8835
|
import os3 from "os";
|
|
8587
8836
|
import { z as z3 } from "zod";
|
|
8588
8837
|
var SharedFeedbackEntrySchema = z3.object({
|
|
@@ -8604,15 +8853,15 @@ function ymd(d) {
|
|
|
8604
8853
|
var SharedContextManager = class {
|
|
8605
8854
|
constructor(config) {
|
|
8606
8855
|
this.config = config;
|
|
8607
|
-
const base = typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0 ? config.sharedContextDir :
|
|
8856
|
+
const base = typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0 ? config.sharedContextDir : path18.join(os3.homedir(), ".openclaw", "workspace", "shared-context");
|
|
8608
8857
|
this.dir = base;
|
|
8609
|
-
this.prioritiesPath =
|
|
8610
|
-
this.prioritiesInboxPath =
|
|
8611
|
-
this.outputsDir =
|
|
8612
|
-
this.roundtableDir =
|
|
8613
|
-
this.feedbackDir =
|
|
8614
|
-
this.feedbackInboxPath =
|
|
8615
|
-
this.crossSignalsDir =
|
|
8858
|
+
this.prioritiesPath = path18.join(base, "priorities.md");
|
|
8859
|
+
this.prioritiesInboxPath = path18.join(base, "priorities.inbox.md");
|
|
8860
|
+
this.outputsDir = path18.join(base, "agent-outputs");
|
|
8861
|
+
this.roundtableDir = path18.join(base, "roundtable");
|
|
8862
|
+
this.feedbackDir = path18.join(base, "feedback");
|
|
8863
|
+
this.feedbackInboxPath = path18.join(this.feedbackDir, "inbox.jsonl");
|
|
8864
|
+
this.crossSignalsDir = path18.join(base, "cross-signals");
|
|
8616
8865
|
}
|
|
8617
8866
|
dir;
|
|
8618
8867
|
prioritiesPath;
|
|
@@ -8628,10 +8877,10 @@ var SharedContextManager = class {
|
|
|
8628
8877
|
await mkdir12(this.roundtableDir, { recursive: true });
|
|
8629
8878
|
await mkdir12(this.feedbackDir, { recursive: true });
|
|
8630
8879
|
await mkdir12(this.crossSignalsDir, { recursive: true });
|
|
8631
|
-
await mkdir12(
|
|
8632
|
-
await mkdir12(
|
|
8633
|
-
await mkdir12(
|
|
8634
|
-
await mkdir12(
|
|
8880
|
+
await mkdir12(path18.join(this.dir, "staging"), { recursive: true });
|
|
8881
|
+
await mkdir12(path18.join(this.dir, "kpis"), { recursive: true });
|
|
8882
|
+
await mkdir12(path18.join(this.dir, "calendar"), { recursive: true });
|
|
8883
|
+
await mkdir12(path18.join(this.dir, "content-calendar"), { recursive: true });
|
|
8635
8884
|
await this.ensureFile(
|
|
8636
8885
|
this.prioritiesPath,
|
|
8637
8886
|
[
|
|
@@ -8675,7 +8924,7 @@ var SharedContextManager = class {
|
|
|
8675
8924
|
async readLatestRoundtable() {
|
|
8676
8925
|
try {
|
|
8677
8926
|
const files = (await readdir8(this.roundtableDir)).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
8678
|
-
const fp = files[0] ?
|
|
8927
|
+
const fp = files[0] ? path18.join(this.roundtableDir, files[0]) : null;
|
|
8679
8928
|
if (!fp) return "";
|
|
8680
8929
|
return await readFile12(fp, "utf-8");
|
|
8681
8930
|
} catch {
|
|
@@ -8687,9 +8936,9 @@ var SharedContextManager = class {
|
|
|
8687
8936
|
const date = ymd(createdAt);
|
|
8688
8937
|
const time = createdAt.toISOString().slice(11, 19).replace(/:/g, "");
|
|
8689
8938
|
const slug = safeSlug(opts.title);
|
|
8690
|
-
const dir =
|
|
8939
|
+
const dir = path18.join(this.outputsDir, opts.agentId, date);
|
|
8691
8940
|
await mkdir12(dir, { recursive: true });
|
|
8692
|
-
const fp =
|
|
8941
|
+
const fp = path18.join(dir, `${time}-${slug}.md`);
|
|
8693
8942
|
const body = `---
|
|
8694
8943
|
kind: agent_output
|
|
8695
8944
|
agent: ${opts.agentId}
|
|
@@ -8724,11 +8973,11 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
|
|
|
8724
8973
|
const agents = await readdir8(this.outputsDir, { withFileTypes: true });
|
|
8725
8974
|
for (const a of agents) {
|
|
8726
8975
|
if (!a.isDirectory()) continue;
|
|
8727
|
-
const dayDir =
|
|
8976
|
+
const dayDir = path18.join(this.outputsDir, a.name, date);
|
|
8728
8977
|
try {
|
|
8729
8978
|
const files = (await readdir8(dayDir)).filter((f) => f.endsWith(".md")).sort();
|
|
8730
8979
|
for (const f of files) {
|
|
8731
|
-
const p =
|
|
8980
|
+
const p = path18.join(dayDir, f);
|
|
8732
8981
|
const raw = await readFile12(p, "utf-8");
|
|
8733
8982
|
const title = (raw.match(/^title:\s*(.+)$/m)?.[1] ?? f).trim();
|
|
8734
8983
|
outputs.push({ path: p, title });
|
|
@@ -8768,7 +9017,7 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
|
|
|
8768
9017
|
];
|
|
8769
9018
|
const out = md.join("\n");
|
|
8770
9019
|
const trimmed = out.length > maxChars ? out.slice(0, maxChars) + "\n\n...(trimmed)\n" : out;
|
|
8771
|
-
const fp =
|
|
9020
|
+
const fp = path18.join(this.roundtableDir, `${date}.md`);
|
|
8772
9021
|
await writeFile12(fp, trimmed, "utf-8");
|
|
8773
9022
|
log.info(`shared-context curated daily roundtable: ${fp}`);
|
|
8774
9023
|
return fp;
|
|
@@ -8777,7 +9026,7 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
|
|
|
8777
9026
|
|
|
8778
9027
|
// src/compounding/engine.ts
|
|
8779
9028
|
import { mkdir as mkdir13, readFile as readFile13, writeFile as writeFile13 } from "fs/promises";
|
|
8780
|
-
import
|
|
9029
|
+
import path19 from "path";
|
|
8781
9030
|
import os4 from "os";
|
|
8782
9031
|
function isoWeekId(d) {
|
|
8783
9032
|
const dt = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
|
@@ -8792,28 +9041,28 @@ function sharedContextDir(config) {
|
|
|
8792
9041
|
if (typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0) {
|
|
8793
9042
|
return config.sharedContextDir;
|
|
8794
9043
|
}
|
|
8795
|
-
return
|
|
9044
|
+
return path19.join(os4.homedir(), ".openclaw", "workspace", "shared-context");
|
|
8796
9045
|
}
|
|
8797
9046
|
var CompoundingEngine = class {
|
|
8798
9047
|
constructor(config) {
|
|
8799
9048
|
this.config = config;
|
|
8800
|
-
this.weeklyDir =
|
|
8801
|
-
this.mistakesPath =
|
|
8802
|
-
this.feedbackInboxPath =
|
|
9049
|
+
this.weeklyDir = path19.join(config.memoryDir, "compounding", "weekly");
|
|
9050
|
+
this.mistakesPath = path19.join(config.memoryDir, "compounding", "mistakes.json");
|
|
9051
|
+
this.feedbackInboxPath = path19.join(sharedContextDir(config), "feedback", "inbox.jsonl");
|
|
8803
9052
|
}
|
|
8804
9053
|
weeklyDir;
|
|
8805
9054
|
mistakesPath;
|
|
8806
9055
|
feedbackInboxPath;
|
|
8807
9056
|
async ensureDirs() {
|
|
8808
9057
|
await mkdir13(this.weeklyDir, { recursive: true });
|
|
8809
|
-
await mkdir13(
|
|
9058
|
+
await mkdir13(path19.dirname(this.mistakesPath), { recursive: true });
|
|
8810
9059
|
}
|
|
8811
9060
|
async synthesizeWeekly(opts) {
|
|
8812
9061
|
await this.ensureDirs();
|
|
8813
9062
|
const weekId = opts?.weekId ?? isoWeekId(/* @__PURE__ */ new Date());
|
|
8814
9063
|
const entries = await this.readFeedbackEntriesForWeek(weekId);
|
|
8815
9064
|
const mistakes = this.buildMistakes(entries);
|
|
8816
|
-
const reportPath =
|
|
9065
|
+
const reportPath = path19.join(this.weeklyDir, `${weekId}.md`);
|
|
8817
9066
|
const md = this.formatWeeklyReport(weekId, entries, mistakes.patterns);
|
|
8818
9067
|
await writeFile13(reportPath, md, "utf-8");
|
|
8819
9068
|
await writeFile13(this.mistakesPath, JSON.stringify(mistakes, null, 2) + "\n", "utf-8");
|
|
@@ -9057,7 +9306,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
9057
9306
|
this.compounding = config.compoundingEnabled ? new CompoundingEngine(config) : void 0;
|
|
9058
9307
|
this.buffer = new SmartBuffer(config, this.storage);
|
|
9059
9308
|
this.transcript = new TranscriptManager(config);
|
|
9060
|
-
this.conversationIndexDir =
|
|
9309
|
+
this.conversationIndexDir = path20.join(config.memoryDir, "conversation-index", "chunks");
|
|
9061
9310
|
this.modelRegistry = new ModelRegistry(config.memoryDir);
|
|
9062
9311
|
this.relevance = new RelevanceStore(config.memoryDir);
|
|
9063
9312
|
this.negatives = new NegativeExampleStore(config.memoryDir);
|
|
@@ -9067,7 +9316,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
9067
9316
|
this.localLlm = new LocalLlmClient(config, this.modelRegistry);
|
|
9068
9317
|
this.extraction = new ExtractionEngine(config, this.localLlm, config.gatewayConfig, this.modelRegistry);
|
|
9069
9318
|
this.threading = new ThreadingManager(
|
|
9070
|
-
|
|
9319
|
+
path20.join(config.memoryDir, "threads"),
|
|
9071
9320
|
config.threadingGapMinutes
|
|
9072
9321
|
);
|
|
9073
9322
|
this.initPromise = new Promise((resolve) => {
|
|
@@ -9169,8 +9418,8 @@ var Orchestrator = class _Orchestrator {
|
|
|
9169
9418
|
await this.negatives.load();
|
|
9170
9419
|
await this.lastRecall.load();
|
|
9171
9420
|
if (this.config.factDeduplicationEnabled) {
|
|
9172
|
-
const
|
|
9173
|
-
this.contentHashIndex = new ContentHashIndex(
|
|
9421
|
+
const stateDir2 = path20.join(this.config.memoryDir, "state");
|
|
9422
|
+
this.contentHashIndex = new ContentHashIndex(stateDir2);
|
|
9174
9423
|
await this.contentHashIndex.load();
|
|
9175
9424
|
log.info(`content-hash dedup: loaded ${this.contentHashIndex.size} hashes`);
|
|
9176
9425
|
}
|
|
@@ -9207,7 +9456,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
9207
9456
|
if (available) {
|
|
9208
9457
|
log.info(`Conversation index QMD: available ${this.conversationQmd.debugStatus()}`);
|
|
9209
9458
|
const collectionState = await this.conversationQmd.ensureCollection(
|
|
9210
|
-
|
|
9459
|
+
path20.join(this.config.memoryDir, "conversation-index")
|
|
9211
9460
|
);
|
|
9212
9461
|
if (collectionState === "missing") {
|
|
9213
9462
|
this.config.conversationIndexEnabled = false;
|
|
@@ -9243,12 +9492,12 @@ var Orchestrator = class _Orchestrator {
|
|
|
9243
9492
|
this.lastFileHygieneRunAtMs = now;
|
|
9244
9493
|
if (hygiene.rotateEnabled) {
|
|
9245
9494
|
for (const rel of hygiene.rotatePaths) {
|
|
9246
|
-
const abs =
|
|
9495
|
+
const abs = path20.isAbsolute(rel) ? rel : path20.join(this.config.workspaceDir, rel);
|
|
9247
9496
|
try {
|
|
9248
9497
|
const raw = await readFile14(abs, "utf-8");
|
|
9249
9498
|
if (raw.length > hygiene.rotateMaxBytes) {
|
|
9250
|
-
const archiveDir =
|
|
9251
|
-
const base =
|
|
9499
|
+
const archiveDir = path20.join(this.config.workspaceDir, hygiene.archiveDir);
|
|
9500
|
+
const base = path20.basename(abs);
|
|
9252
9501
|
const prefix = base.toUpperCase().replace(/\.MD$/i, "").replace(/[^A-Z0-9]+/g, "-") || "FILE";
|
|
9253
9502
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
9254
9503
|
filePath: abs,
|
|
@@ -9273,8 +9522,8 @@ var Orchestrator = class _Orchestrator {
|
|
|
9273
9522
|
log.warn(w.message);
|
|
9274
9523
|
}
|
|
9275
9524
|
if (hygiene.warningsLogEnabled && warnings.length > 0) {
|
|
9276
|
-
const fp =
|
|
9277
|
-
await mkdir14(
|
|
9525
|
+
const fp = path20.join(this.config.memoryDir, hygiene.warningsLogPath);
|
|
9526
|
+
await mkdir14(path20.dirname(fp), { recursive: true });
|
|
9278
9527
|
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
9279
9528
|
const block = `
|
|
9280
9529
|
|
|
@@ -9803,21 +10052,26 @@ ${boxLines.join("\n")}`);
|
|
|
9803
10052
|
const activeMemories = memories.filter(
|
|
9804
10053
|
(m) => !m.frontmatter.status || m.frontmatter.status === "active"
|
|
9805
10054
|
);
|
|
9806
|
-
const
|
|
10055
|
+
const recentSorted = activeMemories.sort(
|
|
9807
10056
|
(a, b) => new Date(b.frontmatter.updated).getTime() - new Date(a.frontmatter.updated).getTime()
|
|
9808
|
-
)
|
|
9809
|
-
const
|
|
10057
|
+
);
|
|
10058
|
+
const preloadedMap = new Map(
|
|
10059
|
+
activeMemories.filter((m) => m.path).map((m) => [m.path, m])
|
|
10060
|
+
);
|
|
10061
|
+
const recentAsResults = recentSorted.map((m, i) => ({
|
|
10062
|
+
docid: m.frontmatter.id,
|
|
10063
|
+
path: m.path,
|
|
10064
|
+
snippet: m.content,
|
|
10065
|
+
score: 1 - i / Math.max(recentSorted.length, 1)
|
|
10066
|
+
}));
|
|
10067
|
+
const recent = (await this.boostSearchResults(recentAsResults, recallNamespaces, prompt, preloadedMap)).sort((a, b) => b.score - a.score).slice(0, recallResultLimit);
|
|
10068
|
+
const memoryIds = recent.map((r) => r.docid).filter(Boolean);
|
|
9810
10069
|
this.trackMemoryAccess(memoryIds);
|
|
9811
10070
|
if (sessionKey) {
|
|
9812
10071
|
const unique = Array.from(new Set(memoryIds)).slice(0, 40);
|
|
9813
10072
|
this.lastRecall.record({ sessionKey, query: prompt, memoryIds: unique }).catch((err) => log.debug(`last recall record failed: ${err}`));
|
|
9814
10073
|
}
|
|
9815
|
-
|
|
9816
|
-
(m) => `- [${m.frontmatter.category}] ${m.content}`
|
|
9817
|
-
);
|
|
9818
|
-
sections.push(`## Recent Memories
|
|
9819
|
-
|
|
9820
|
-
${lines.join("\n")}`);
|
|
10074
|
+
sections.push(this.formatQmdResults("Recent Memories", recent));
|
|
9821
10075
|
}
|
|
9822
10076
|
}
|
|
9823
10077
|
if (isDisagreementPrompt(prompt)) {
|
|
@@ -10234,7 +10488,8 @@ _Context: ${topQuestion.context}_`);
|
|
|
10234
10488
|
this.contentHashIndex.add(fact.content);
|
|
10235
10489
|
}
|
|
10236
10490
|
for (const chunk of chunkResult.chunks) {
|
|
10237
|
-
|
|
10491
|
+
const chunkId = `${parentId}-chunk-${chunk.index}`;
|
|
10492
|
+
await this.indexPersistedMemory(storage, chunkId);
|
|
10238
10493
|
}
|
|
10239
10494
|
if (this.config.verbatimArtifactsEnabled && this.config.verbatimArtifactCategories.includes(fact.category) && fact.confidence >= this.config.verbatimArtifactsMinConfidence) {
|
|
10240
10495
|
await storage.writeArtifact(fact.content, {
|
|
@@ -10262,6 +10517,14 @@ _Context: ${topQuestion.context}_`);
|
|
|
10262
10517
|
strength: contradiction.confidence,
|
|
10263
10518
|
reason: contradiction.reason
|
|
10264
10519
|
});
|
|
10520
|
+
if (this.config.queryAwareIndexingEnabled && contradiction.supersededPath) {
|
|
10521
|
+
deindexMemory(
|
|
10522
|
+
this.config.memoryDir,
|
|
10523
|
+
contradiction.supersededPath,
|
|
10524
|
+
contradiction.supersededCreated,
|
|
10525
|
+
contradiction.supersededTags
|
|
10526
|
+
);
|
|
10527
|
+
}
|
|
10265
10528
|
}
|
|
10266
10529
|
}
|
|
10267
10530
|
if (this.config.memoryLinkingEnabled && this.qmd.isAvailable()) {
|
|
@@ -10366,6 +10629,9 @@ _Context: ${topQuestion.context}_`);
|
|
|
10366
10629
|
log.info(
|
|
10367
10630
|
`persisted: ${facts.length - dedupedCount} facts${dedupSuffix}, ${entities.length} entities, ${questions.length} questions, ${profileUpdates.length} profile updates`
|
|
10368
10631
|
);
|
|
10632
|
+
this.updateTemporalTagIndexes(storage, persistedIds).catch(
|
|
10633
|
+
(err) => log.debug(`temporal-index update error (non-fatal): ${err}`)
|
|
10634
|
+
);
|
|
10369
10635
|
return persistedIds;
|
|
10370
10636
|
}
|
|
10371
10637
|
async indexPersistedMemory(storage, memoryId) {
|
|
@@ -10375,6 +10641,52 @@ _Context: ${topQuestion.context}_`);
|
|
|
10375
10641
|
if (!memory) return;
|
|
10376
10642
|
await this.embeddingFallback.indexFile(memoryId, memory.content, memory.path);
|
|
10377
10643
|
}
|
|
10644
|
+
/**
|
|
10645
|
+
* Batch-update temporal and tag indexes after extraction (v8.1).
|
|
10646
|
+
* Reads each persisted memory's path + frontmatter and adds them to
|
|
10647
|
+
* state/index_time.json and state/index_tags.json.
|
|
10648
|
+
* Fail-open: any error is logged but does not abort extraction.
|
|
10649
|
+
*/
|
|
10650
|
+
async updateTemporalTagIndexes(storage, persistedIds) {
|
|
10651
|
+
if (!this.config.queryAwareIndexingEnabled) return;
|
|
10652
|
+
const needsFullRebuild = !indexesExist(this.config.memoryDir);
|
|
10653
|
+
if (!needsFullRebuild && persistedIds.length === 0) return;
|
|
10654
|
+
try {
|
|
10655
|
+
const allMemories = needsFullRebuild && this.config.namespacesEnabled ? await this.readAllMemoriesForNamespaces(
|
|
10656
|
+
Array.from(/* @__PURE__ */ new Set([
|
|
10657
|
+
this.config.defaultNamespace,
|
|
10658
|
+
this.config.sharedNamespace,
|
|
10659
|
+
...this.config.namespacePolicies.map((p) => p.name)
|
|
10660
|
+
]))
|
|
10661
|
+
) : await storage.readAllMemories();
|
|
10662
|
+
const isActive = (m) => !m.frontmatter.status || m.frontmatter.status === "active";
|
|
10663
|
+
const pool = needsFullRebuild ? allMemories.filter(isActive) : (() => {
|
|
10664
|
+
const idSet = new Set(persistedIds);
|
|
10665
|
+
return allMemories.filter((m) => idSet.has(m.frontmatter.id));
|
|
10666
|
+
})();
|
|
10667
|
+
const entries = [];
|
|
10668
|
+
for (const mem of pool) {
|
|
10669
|
+
if (mem.path && mem.frontmatter?.created) {
|
|
10670
|
+
entries.push({
|
|
10671
|
+
path: mem.path,
|
|
10672
|
+
createdAt: mem.frontmatter.created,
|
|
10673
|
+
tags: mem.frontmatter.tags ?? []
|
|
10674
|
+
});
|
|
10675
|
+
}
|
|
10676
|
+
}
|
|
10677
|
+
if (needsFullRebuild) {
|
|
10678
|
+
clearIndexes(this.config.memoryDir);
|
|
10679
|
+
if (entries.length > 0) {
|
|
10680
|
+
indexMemoriesBatch(this.config.memoryDir, entries);
|
|
10681
|
+
}
|
|
10682
|
+
log.info(`temporal-index: bootstrapped from ${entries.length} active memories`);
|
|
10683
|
+
} else if (entries.length > 0) {
|
|
10684
|
+
indexMemoriesBatch(this.config.memoryDir, entries);
|
|
10685
|
+
}
|
|
10686
|
+
} catch (err) {
|
|
10687
|
+
log.debug(`temporal-index update failed (non-fatal): ${err}`);
|
|
10688
|
+
}
|
|
10689
|
+
}
|
|
10378
10690
|
/** IDs of facts persisted in the last extraction */
|
|
10379
10691
|
lastPersistedIds = [];
|
|
10380
10692
|
async runConsolidation() {
|
|
@@ -10396,14 +10708,25 @@ _Context: ${topQuestion.context}_`);
|
|
|
10396
10708
|
);
|
|
10397
10709
|
const profile = await this.storage.readProfile();
|
|
10398
10710
|
const result = await this.extraction.consolidate(recent, older, profile);
|
|
10711
|
+
const memoryLookup = this.config.queryAwareIndexingEnabled ? new Map(allMemories.map((m) => [m.frontmatter.id, m])) : null;
|
|
10399
10712
|
for (const item of result.items) {
|
|
10400
10713
|
switch (item.action) {
|
|
10401
|
-
case "INVALIDATE":
|
|
10714
|
+
case "INVALIDATE": {
|
|
10715
|
+
const toInvalidate = this.config.queryAwareIndexingEnabled ? memoryLookup?.get(item.existingId) ?? null : null;
|
|
10402
10716
|
if (await this.storage.invalidateMemory(item.existingId)) {
|
|
10403
10717
|
invalidated += 1;
|
|
10404
10718
|
await this.embeddingFallback.removeFromIndex(item.existingId);
|
|
10719
|
+
if (toInvalidate?.path && toInvalidate.frontmatter?.created) {
|
|
10720
|
+
deindexMemory(
|
|
10721
|
+
this.config.memoryDir,
|
|
10722
|
+
toInvalidate.path,
|
|
10723
|
+
toInvalidate.frontmatter.created,
|
|
10724
|
+
toInvalidate.frontmatter.tags ?? []
|
|
10725
|
+
);
|
|
10726
|
+
}
|
|
10405
10727
|
}
|
|
10406
10728
|
break;
|
|
10729
|
+
}
|
|
10407
10730
|
case "UPDATE":
|
|
10408
10731
|
if (item.updatedContent) {
|
|
10409
10732
|
await this.storage.updateMemory(item.existingId, item.updatedContent, {
|
|
@@ -10419,10 +10742,19 @@ _Context: ${topQuestion.context}_`);
|
|
|
10419
10742
|
lineage: [item.existingId, item.mergeWith]
|
|
10420
10743
|
});
|
|
10421
10744
|
await this.indexPersistedMemory(this.storage, item.existingId);
|
|
10745
|
+
const toMergeInvalidate = this.config.queryAwareIndexingEnabled ? memoryLookup?.get(item.mergeWith) ?? null : null;
|
|
10422
10746
|
if (await this.storage.invalidateMemory(item.mergeWith)) {
|
|
10423
10747
|
invalidated += 1;
|
|
10424
10748
|
merged += 1;
|
|
10425
10749
|
await this.embeddingFallback.removeFromIndex(item.mergeWith);
|
|
10750
|
+
if (toMergeInvalidate?.path && toMergeInvalidate.frontmatter?.created) {
|
|
10751
|
+
deindexMemory(
|
|
10752
|
+
this.config.memoryDir,
|
|
10753
|
+
toMergeInvalidate.path,
|
|
10754
|
+
toMergeInvalidate.frontmatter.created,
|
|
10755
|
+
toMergeInvalidate.frontmatter.tags ?? []
|
|
10756
|
+
);
|
|
10757
|
+
}
|
|
10426
10758
|
}
|
|
10427
10759
|
}
|
|
10428
10760
|
break;
|
|
@@ -10477,13 +10809,23 @@ _Context: ${topQuestion.context}_`);
|
|
|
10477
10809
|
log.debug(`entity summary pass failed: ${err}`);
|
|
10478
10810
|
}
|
|
10479
10811
|
}
|
|
10480
|
-
const
|
|
10481
|
-
if (
|
|
10482
|
-
log.info(`cleaned ${
|
|
10812
|
+
const deletedCommitments = await this.storage.cleanExpiredCommitments(this.config.commitmentDecayDays);
|
|
10813
|
+
if (deletedCommitments.length > 0) {
|
|
10814
|
+
log.info(`cleaned ${deletedCommitments.length} expired commitments`);
|
|
10815
|
+
if (this.config.queryAwareIndexingEnabled) {
|
|
10816
|
+
for (const m of deletedCommitments) {
|
|
10817
|
+
deindexMemory(this.config.memoryDir, m.path, m.frontmatter.created, m.frontmatter.tags ?? []);
|
|
10818
|
+
}
|
|
10819
|
+
}
|
|
10483
10820
|
}
|
|
10484
|
-
const
|
|
10485
|
-
if (
|
|
10486
|
-
log.info(`cleaned ${
|
|
10821
|
+
const deletedTTL = await this.storage.cleanExpiredTTL();
|
|
10822
|
+
if (deletedTTL.length > 0) {
|
|
10823
|
+
log.info(`cleaned ${deletedTTL.length} TTL-expired memories`);
|
|
10824
|
+
if (this.config.queryAwareIndexingEnabled) {
|
|
10825
|
+
for (const m of deletedTTL) {
|
|
10826
|
+
deindexMemory(this.config.memoryDir, m.path, m.frontmatter.created, m.frontmatter.tags ?? []);
|
|
10827
|
+
}
|
|
10828
|
+
}
|
|
10487
10829
|
}
|
|
10488
10830
|
if (this.config.factArchivalEnabled) {
|
|
10489
10831
|
const archived = await this.runFactArchival(allMemories);
|
|
@@ -10544,6 +10886,14 @@ _Context: ${topQuestion.context}_`);
|
|
|
10544
10886
|
this.contentHashIndex.remove(memory.content);
|
|
10545
10887
|
}
|
|
10546
10888
|
await this.embeddingFallback.removeFromIndex(memory.frontmatter.id);
|
|
10889
|
+
if (this.config.queryAwareIndexingEnabled && memory.path && memory.frontmatter?.created) {
|
|
10890
|
+
deindexMemory(
|
|
10891
|
+
this.config.memoryDir,
|
|
10892
|
+
memory.path,
|
|
10893
|
+
memory.frontmatter.created,
|
|
10894
|
+
memory.frontmatter.tags ?? []
|
|
10895
|
+
);
|
|
10896
|
+
}
|
|
10547
10897
|
archivedCount++;
|
|
10548
10898
|
}
|
|
10549
10899
|
}
|
|
@@ -10671,7 +11021,7 @@ ${lines.join("\n\n")}`;
|
|
|
10671
11021
|
if (hits.length === 0) return [];
|
|
10672
11022
|
const results = [];
|
|
10673
11023
|
for (const hit of hits) {
|
|
10674
|
-
const fullPath =
|
|
11024
|
+
const fullPath = path20.isAbsolute(hit.path) ? hit.path : path20.join(this.config.memoryDir, hit.path);
|
|
10675
11025
|
const memory = await this.storage.readMemoryByPath(fullPath);
|
|
10676
11026
|
if (!memory) continue;
|
|
10677
11027
|
results.push({
|
|
@@ -10751,18 +11101,48 @@ ${lines.join("\n\n")}`;
|
|
|
10751
11101
|
* Apply recency, access count, and importance boosting to QMD search results.
|
|
10752
11102
|
* Returns re-ranked results.
|
|
10753
11103
|
*/
|
|
10754
|
-
async boostSearchResults(results, _recallNamespaces, prompt) {
|
|
11104
|
+
async boostSearchResults(results, _recallNamespaces, prompt, preloadedMemoryMap) {
|
|
10755
11105
|
if (results.length === 0) return results;
|
|
10756
11106
|
const now = Date.now();
|
|
10757
|
-
const memoryByPath = /* @__PURE__ */ new Map();
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
11107
|
+
const memoryByPath = preloadedMemoryMap ? new Map(preloadedMemoryMap) : /* @__PURE__ */ new Map();
|
|
11108
|
+
const resultPaths = new Set(results.map((r) => r.path).filter(Boolean));
|
|
11109
|
+
let temporalFromDate = null;
|
|
11110
|
+
let promptTags = [];
|
|
11111
|
+
if (this.config.queryAwareIndexingEnabled && prompt) {
|
|
11112
|
+
if (isTemporalQuery(prompt)) {
|
|
11113
|
+
temporalFromDate = recencyWindowFromPrompt(prompt, now);
|
|
11114
|
+
}
|
|
11115
|
+
promptTags = extractTagsFromPrompt(prompt);
|
|
11116
|
+
}
|
|
11117
|
+
const [, rawTemporal, rawTags] = await Promise.all([
|
|
11118
|
+
Promise.all(
|
|
11119
|
+
results.map(async (r) => {
|
|
11120
|
+
if (!r.path || memoryByPath.has(r.path)) return;
|
|
11121
|
+
const mem = await this.storage.readMemoryByPath(r.path);
|
|
11122
|
+
if (mem) memoryByPath.set(r.path, mem);
|
|
11123
|
+
})
|
|
11124
|
+
),
|
|
11125
|
+
temporalFromDate !== null ? queryByDateRangeAsync(this.config.memoryDir, temporalFromDate) : Promise.resolve(null),
|
|
11126
|
+
promptTags.length > 0 ? queryByTagsAsync(this.config.memoryDir, promptTags) : Promise.resolve(null)
|
|
11127
|
+
]);
|
|
10765
11128
|
const queryIntent = this.config.intentRoutingEnabled && prompt ? inferIntentFromText(prompt) : null;
|
|
11129
|
+
let temporalCandidates = null;
|
|
11130
|
+
let tagCandidates = null;
|
|
11131
|
+
if (this.config.queryAwareIndexingEnabled && prompt) {
|
|
11132
|
+
const maxCandidates = this.config.queryAwareIndexingMaxCandidates;
|
|
11133
|
+
const capSet = (s) => {
|
|
11134
|
+
if (!s) return null;
|
|
11135
|
+
const scoped = new Set(Array.from(s).filter((p) => resultPaths.has(p)));
|
|
11136
|
+
if (maxCandidates === 0 || scoped.size <= maxCandidates) return scoped.size > 0 ? scoped : null;
|
|
11137
|
+
return new Set(Array.from(scoped).slice(0, maxCandidates));
|
|
11138
|
+
};
|
|
11139
|
+
if (temporalFromDate !== null) {
|
|
11140
|
+
temporalCandidates = capSet(rawTemporal);
|
|
11141
|
+
}
|
|
11142
|
+
if (promptTags.length > 0) {
|
|
11143
|
+
tagCandidates = capSet(rawTags);
|
|
11144
|
+
}
|
|
11145
|
+
}
|
|
10766
11146
|
const boosted = results.map((r) => {
|
|
10767
11147
|
const memory = memoryByPath.get(r.path);
|
|
10768
11148
|
let score = r.score;
|
|
@@ -10809,6 +11189,14 @@ ${lines.join("\n\n")}`;
|
|
|
10809
11189
|
});
|
|
10810
11190
|
score += compatibility * this.config.intentRoutingBoost;
|
|
10811
11191
|
}
|
|
11192
|
+
if (this.config.queryAwareIndexingEnabled && r.path) {
|
|
11193
|
+
if (temporalCandidates?.has(r.path)) {
|
|
11194
|
+
score += 0.08;
|
|
11195
|
+
}
|
|
11196
|
+
if (tagCandidates?.has(r.path)) {
|
|
11197
|
+
score += 0.06;
|
|
11198
|
+
}
|
|
11199
|
+
}
|
|
10812
11200
|
}
|
|
10813
11201
|
return { ...r, score };
|
|
10814
11202
|
});
|
|
@@ -10877,7 +11265,10 @@ ${lines.join("\n\n")}`;
|
|
|
10877
11265
|
return {
|
|
10878
11266
|
supersededId: existingMemory.frontmatter.id,
|
|
10879
11267
|
confidence: verification.confidence,
|
|
10880
|
-
reason: verification.reasoning
|
|
11268
|
+
reason: verification.reasoning,
|
|
11269
|
+
supersededPath: existingMemory.path,
|
|
11270
|
+
supersededCreated: existingMemory.frontmatter.created,
|
|
11271
|
+
supersededTags: existingMemory.frontmatter.tags ?? []
|
|
10881
11272
|
};
|
|
10882
11273
|
}
|
|
10883
11274
|
}
|
|
@@ -10942,7 +11333,7 @@ ${lines.join("\n\n")}`;
|
|
|
10942
11333
|
};
|
|
10943
11334
|
|
|
10944
11335
|
// src/tools.ts
|
|
10945
|
-
import
|
|
11336
|
+
import path21 from "path";
|
|
10946
11337
|
import { Type } from "@sinclair/typebox";
|
|
10947
11338
|
function toolResult(text) {
|
|
10948
11339
|
return { content: [{ type: "text", text }], details: void 0 };
|
|
@@ -11217,6 +11608,12 @@ Best for:
|
|
|
11217
11608
|
source: "explicit"
|
|
11218
11609
|
}
|
|
11219
11610
|
);
|
|
11611
|
+
if (orchestrator.config.queryAwareIndexingEnabled && indexesExist(orchestrator.config.memoryDir)) {
|
|
11612
|
+
const mem = await storage.getMemoryById(id).catch(() => null);
|
|
11613
|
+
if (mem?.path && mem.frontmatter?.created) {
|
|
11614
|
+
indexMemory(orchestrator.config.memoryDir, mem.path, mem.frontmatter.created, mem.frontmatter.tags ?? []);
|
|
11615
|
+
}
|
|
11616
|
+
}
|
|
11220
11617
|
orchestrator.requestQmdMaintenanceForTool("memory_store");
|
|
11221
11618
|
return toolResult(`Memory stored: ${id}${namespace ? ` (namespace: ${namespace})` : ""}
|
|
11222
11619
|
|
|
@@ -11274,6 +11671,12 @@ Content: ${content}`);
|
|
|
11274
11671
|
supersedes: mem.frontmatter.supersedes,
|
|
11275
11672
|
links: mem.frontmatter.links
|
|
11276
11673
|
});
|
|
11674
|
+
if (orchestrator.config.queryAwareIndexingEnabled && indexesExist(orchestrator.config.memoryDir)) {
|
|
11675
|
+
const promoted = await dst.getMemoryById(newId).catch(() => null);
|
|
11676
|
+
if (promoted?.path && promoted.frontmatter?.created) {
|
|
11677
|
+
indexMemory(orchestrator.config.memoryDir, promoted.path, promoted.frontmatter.created, promoted.frontmatter.tags ?? []);
|
|
11678
|
+
}
|
|
11679
|
+
}
|
|
11277
11680
|
return toolResult(`Promoted ${srcNs}:${memoryId} \u2192 ${dstNs}:${newId}`);
|
|
11278
11681
|
}
|
|
11279
11682
|
},
|
|
@@ -11420,7 +11823,7 @@ Best for:
|
|
|
11420
11823
|
- Reviewing identity development over time`,
|
|
11421
11824
|
parameters: Type.Object({}),
|
|
11422
11825
|
async execute() {
|
|
11423
|
-
const workspaceDir =
|
|
11826
|
+
const workspaceDir = path21.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
11424
11827
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
11425
11828
|
if (!identity) {
|
|
11426
11829
|
return toolResult("No identity file found. Identity reflections build automatically through conversations when identityEnabled is true.");
|
|
@@ -11669,11 +12072,11 @@ mistakes: ${res.mistakesCount} patterns`
|
|
|
11669
12072
|
}
|
|
11670
12073
|
|
|
11671
12074
|
// src/cli.ts
|
|
11672
|
-
import
|
|
12075
|
+
import path31 from "path";
|
|
11673
12076
|
import { access as access2, readFile as readFile20 } from "fs/promises";
|
|
11674
12077
|
|
|
11675
12078
|
// src/transfer/export-json.ts
|
|
11676
|
-
import
|
|
12079
|
+
import path23 from "path";
|
|
11677
12080
|
import { mkdir as mkdir16, readFile as readFile16 } from "fs/promises";
|
|
11678
12081
|
|
|
11679
12082
|
// src/transfer/constants.ts
|
|
@@ -11683,7 +12086,7 @@ var EXPORT_SCHEMA_VERSION = 1;
|
|
|
11683
12086
|
// src/transfer/fs-utils.ts
|
|
11684
12087
|
import { createHash as createHash5 } from "crypto";
|
|
11685
12088
|
import { mkdir as mkdir15, readdir as readdir9, readFile as readFile15, stat as stat3, writeFile as writeFile15 } from "fs/promises";
|
|
11686
|
-
import
|
|
12089
|
+
import path22 from "path";
|
|
11687
12090
|
async function sha256File(filePath) {
|
|
11688
12091
|
const buf = await readFile15(filePath);
|
|
11689
12092
|
const sha256 = createHash5("sha256").update(buf).digest("hex");
|
|
@@ -11695,7 +12098,7 @@ function sha256String(content) {
|
|
|
11695
12098
|
return { sha256, bytes: buf.byteLength };
|
|
11696
12099
|
}
|
|
11697
12100
|
async function writeJsonFile(filePath, value) {
|
|
11698
|
-
await mkdir15(
|
|
12101
|
+
await mkdir15(path22.dirname(filePath), { recursive: true });
|
|
11699
12102
|
await writeFile15(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
11700
12103
|
}
|
|
11701
12104
|
async function readJsonFile(filePath) {
|
|
@@ -11707,7 +12110,7 @@ async function listFilesRecursive(rootDir) {
|
|
|
11707
12110
|
async function walk(dir) {
|
|
11708
12111
|
const entries = await readdir9(dir, { withFileTypes: true });
|
|
11709
12112
|
for (const ent of entries) {
|
|
11710
|
-
const fp =
|
|
12113
|
+
const fp = path22.join(dir, ent.name);
|
|
11711
12114
|
if (ent.isDirectory()) {
|
|
11712
12115
|
await walk(fp);
|
|
11713
12116
|
} else if (ent.isFile()) {
|
|
@@ -11727,11 +12130,11 @@ async function fileExists(filePath) {
|
|
|
11727
12130
|
}
|
|
11728
12131
|
}
|
|
11729
12132
|
function toPosixRelPath(absPath, rootDir) {
|
|
11730
|
-
const rel =
|
|
11731
|
-
return rel.split(
|
|
12133
|
+
const rel = path22.relative(rootDir, absPath);
|
|
12134
|
+
return rel.split(path22.sep).join("/");
|
|
11732
12135
|
}
|
|
11733
12136
|
function fromPosixRelPath(relPath) {
|
|
11734
|
-
return relPath.split("/").join(
|
|
12137
|
+
return relPath.split("/").join(path22.sep);
|
|
11735
12138
|
}
|
|
11736
12139
|
|
|
11737
12140
|
// src/transfer/export-json.ts
|
|
@@ -11747,9 +12150,9 @@ function shouldExclude(relPosix, includeTranscripts) {
|
|
|
11747
12150
|
}
|
|
11748
12151
|
async function exportJsonBundle(opts) {
|
|
11749
12152
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
11750
|
-
const outDirAbs =
|
|
12153
|
+
const outDirAbs = path23.resolve(opts.outDir);
|
|
11751
12154
|
await mkdir16(outDirAbs, { recursive: true });
|
|
11752
|
-
const memoryDirAbs =
|
|
12155
|
+
const memoryDirAbs = path23.resolve(opts.memoryDir);
|
|
11753
12156
|
const filesAbs = await listFilesRecursive(memoryDirAbs);
|
|
11754
12157
|
const records = [];
|
|
11755
12158
|
const manifestFiles = [];
|
|
@@ -11762,7 +12165,7 @@ async function exportJsonBundle(opts) {
|
|
|
11762
12165
|
manifestFiles.push({ path: relPosix, sha256, bytes });
|
|
11763
12166
|
}
|
|
11764
12167
|
if (opts.includeWorkspaceIdentity !== false && opts.workspaceDir) {
|
|
11765
|
-
const identityPath =
|
|
12168
|
+
const identityPath = path23.join(opts.workspaceDir, "IDENTITY.md");
|
|
11766
12169
|
try {
|
|
11767
12170
|
const content = await readFile16(identityPath, "utf-8");
|
|
11768
12171
|
const relPath = "workspace/IDENTITY.md";
|
|
@@ -11781,12 +12184,12 @@ async function exportJsonBundle(opts) {
|
|
|
11781
12184
|
files: manifestFiles.sort((a, b) => a.path.localeCompare(b.path))
|
|
11782
12185
|
};
|
|
11783
12186
|
const bundle = { manifest, records };
|
|
11784
|
-
await writeJsonFile(
|
|
11785
|
-
await writeJsonFile(
|
|
12187
|
+
await writeJsonFile(path23.join(outDirAbs, "manifest.json"), manifest);
|
|
12188
|
+
await writeJsonFile(path23.join(outDirAbs, "bundle.json"), bundle);
|
|
11786
12189
|
}
|
|
11787
12190
|
|
|
11788
12191
|
// src/transfer/export-md.ts
|
|
11789
|
-
import
|
|
12192
|
+
import path24 from "path";
|
|
11790
12193
|
import { mkdir as mkdir17, readFile as readFile17, writeFile as writeFile16 } from "fs/promises";
|
|
11791
12194
|
function shouldExclude2(relPosix, includeTranscripts) {
|
|
11792
12195
|
const parts = relPosix.split("/");
|
|
@@ -11795,16 +12198,16 @@ function shouldExclude2(relPosix, includeTranscripts) {
|
|
|
11795
12198
|
}
|
|
11796
12199
|
async function exportMarkdownBundle(opts) {
|
|
11797
12200
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
11798
|
-
const outDirAbs =
|
|
12201
|
+
const outDirAbs = path24.resolve(opts.outDir);
|
|
11799
12202
|
await mkdir17(outDirAbs, { recursive: true });
|
|
11800
|
-
const memDirAbs =
|
|
12203
|
+
const memDirAbs = path24.resolve(opts.memoryDir);
|
|
11801
12204
|
const filesAbs = await listFilesRecursive(memDirAbs);
|
|
11802
12205
|
const manifestFiles = [];
|
|
11803
12206
|
for (const abs of filesAbs) {
|
|
11804
12207
|
const relPosix = toPosixRelPath(abs, memDirAbs);
|
|
11805
12208
|
if (shouldExclude2(relPosix, includeTranscripts)) continue;
|
|
11806
|
-
const dstAbs =
|
|
11807
|
-
await mkdir17(
|
|
12209
|
+
const dstAbs = path24.join(outDirAbs, ...relPosix.split("/"));
|
|
12210
|
+
await mkdir17(path24.dirname(dstAbs), { recursive: true });
|
|
11808
12211
|
const content = await readFile17(abs);
|
|
11809
12212
|
await writeFile16(dstAbs, content);
|
|
11810
12213
|
const { sha256, bytes } = await sha256File(abs);
|
|
@@ -11818,12 +12221,12 @@ async function exportMarkdownBundle(opts) {
|
|
|
11818
12221
|
includesTranscripts: includeTranscripts,
|
|
11819
12222
|
files: manifestFiles.sort((a, b) => a.path.localeCompare(b.path))
|
|
11820
12223
|
};
|
|
11821
|
-
await writeJsonFile(
|
|
12224
|
+
await writeJsonFile(path24.join(outDirAbs, "manifest.json"), manifest);
|
|
11822
12225
|
}
|
|
11823
12226
|
async function looksLikeEngramMdExport(fromDir) {
|
|
11824
|
-
const dirAbs =
|
|
12227
|
+
const dirAbs = path24.resolve(fromDir);
|
|
11825
12228
|
try {
|
|
11826
|
-
const raw = await readFile17(
|
|
12229
|
+
const raw = await readFile17(path24.join(dirAbs, "manifest.json"), "utf-8");
|
|
11827
12230
|
const parsed = JSON.parse(raw);
|
|
11828
12231
|
return parsed.format === EXPORT_FORMAT && parsed.schemaVersion === EXPORT_SCHEMA_VERSION;
|
|
11829
12232
|
} catch {
|
|
@@ -11832,16 +12235,16 @@ async function looksLikeEngramMdExport(fromDir) {
|
|
|
11832
12235
|
}
|
|
11833
12236
|
|
|
11834
12237
|
// src/transfer/backup.ts
|
|
11835
|
-
import
|
|
12238
|
+
import path25 from "path";
|
|
11836
12239
|
import { mkdir as mkdir18, readdir as readdir10, rm as rm2 } from "fs/promises";
|
|
11837
12240
|
function timestampDirName(now) {
|
|
11838
12241
|
return now.toISOString().replace(/[:.]/g, "-");
|
|
11839
12242
|
}
|
|
11840
12243
|
async function backupMemoryDir(opts) {
|
|
11841
|
-
const outDirAbs =
|
|
12244
|
+
const outDirAbs = path25.resolve(opts.outDir);
|
|
11842
12245
|
await mkdir18(outDirAbs, { recursive: true });
|
|
11843
12246
|
const ts = timestampDirName(/* @__PURE__ */ new Date());
|
|
11844
|
-
const backupDir =
|
|
12247
|
+
const backupDir = path25.join(outDirAbs, ts);
|
|
11845
12248
|
await exportMarkdownBundle({
|
|
11846
12249
|
memoryDir: opts.memoryDir,
|
|
11847
12250
|
outDir: backupDir,
|
|
@@ -11866,13 +12269,13 @@ async function enforceRetention(outDirAbs, retentionDays) {
|
|
|
11866
12269
|
const tsMs = iso ? Date.parse(iso) : NaN;
|
|
11867
12270
|
if (!Number.isFinite(tsMs)) continue;
|
|
11868
12271
|
if (tsMs < cutoffMs) {
|
|
11869
|
-
await rm2(
|
|
12272
|
+
await rm2(path25.join(outDirAbs, name), { recursive: true, force: true });
|
|
11870
12273
|
}
|
|
11871
12274
|
}
|
|
11872
12275
|
}
|
|
11873
12276
|
|
|
11874
12277
|
// src/transfer/export-sqlite.ts
|
|
11875
|
-
import
|
|
12278
|
+
import path26 from "path";
|
|
11876
12279
|
import Database from "better-sqlite3";
|
|
11877
12280
|
import { readFile as readFile18 } from "fs/promises";
|
|
11878
12281
|
|
|
@@ -11900,8 +12303,8 @@ function shouldExclude3(relPosix, includeTranscripts) {
|
|
|
11900
12303
|
}
|
|
11901
12304
|
async function exportSqlite(opts) {
|
|
11902
12305
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
11903
|
-
const memDirAbs =
|
|
11904
|
-
const outAbs =
|
|
12306
|
+
const memDirAbs = path26.resolve(opts.memoryDir);
|
|
12307
|
+
const outAbs = path26.resolve(opts.outFile);
|
|
11905
12308
|
const filesAbs = await listFilesRecursive(memDirAbs);
|
|
11906
12309
|
const db = new Database(outAbs);
|
|
11907
12310
|
try {
|
|
@@ -11933,7 +12336,7 @@ async function exportSqlite(opts) {
|
|
|
11933
12336
|
}
|
|
11934
12337
|
|
|
11935
12338
|
// src/transfer/import-json.ts
|
|
11936
|
-
import
|
|
12339
|
+
import path27 from "path";
|
|
11937
12340
|
import { mkdir as mkdir19, writeFile as writeFile17 } from "fs/promises";
|
|
11938
12341
|
|
|
11939
12342
|
// src/transfer/types.ts
|
|
@@ -11967,21 +12370,21 @@ function normalizeForDedupe(s) {
|
|
|
11967
12370
|
}
|
|
11968
12371
|
async function importJsonBundle(opts) {
|
|
11969
12372
|
const conflict = opts.conflict ?? "skip";
|
|
11970
|
-
const fromDirAbs =
|
|
11971
|
-
const bundlePath =
|
|
12373
|
+
const fromDirAbs = path27.resolve(opts.fromDir);
|
|
12374
|
+
const bundlePath = path27.join(fromDirAbs, "bundle.json");
|
|
11972
12375
|
const bundle = ExportBundleV1Schema.parse(await readJsonFile(bundlePath));
|
|
11973
|
-
const memDirAbs =
|
|
12376
|
+
const memDirAbs = path27.resolve(opts.targetMemoryDir);
|
|
11974
12377
|
const written = [];
|
|
11975
12378
|
let skipped = 0;
|
|
11976
12379
|
for (const rec of bundle.records) {
|
|
11977
12380
|
const isWorkspace = rec.path.startsWith("workspace/");
|
|
11978
|
-
const targetBase = isWorkspace ? opts.workspaceDir ?
|
|
12381
|
+
const targetBase = isWorkspace ? opts.workspaceDir ? path27.resolve(opts.workspaceDir) : null : memDirAbs;
|
|
11979
12382
|
if (isWorkspace && !targetBase) {
|
|
11980
12383
|
skipped += 1;
|
|
11981
12384
|
continue;
|
|
11982
12385
|
}
|
|
11983
12386
|
const relFs = fromPosixRelPath(isWorkspace ? rec.path.replace(/^workspace\//, "") : rec.path);
|
|
11984
|
-
const absTarget =
|
|
12387
|
+
const absTarget = path27.join(targetBase, relFs);
|
|
11985
12388
|
const exists3 = await fileExists(absTarget);
|
|
11986
12389
|
if (exists3) {
|
|
11987
12390
|
if (conflict === "skip") {
|
|
@@ -12005,21 +12408,21 @@ async function importJsonBundle(opts) {
|
|
|
12005
12408
|
return { written: 0, skipped };
|
|
12006
12409
|
}
|
|
12007
12410
|
for (const w of written) {
|
|
12008
|
-
await mkdir19(
|
|
12411
|
+
await mkdir19(path27.dirname(w.abs), { recursive: true });
|
|
12009
12412
|
await writeFile17(w.abs, w.content, "utf-8");
|
|
12010
12413
|
}
|
|
12011
12414
|
return { written: written.length, skipped };
|
|
12012
12415
|
}
|
|
12013
12416
|
function looksLikeEngramJsonExport(fromDir) {
|
|
12014
|
-
const dir =
|
|
12417
|
+
const dir = path27.resolve(fromDir);
|
|
12015
12418
|
return Promise.all([
|
|
12016
|
-
fileExists(
|
|
12017
|
-
fileExists(
|
|
12419
|
+
fileExists(path27.join(dir, "manifest.json")),
|
|
12420
|
+
fileExists(path27.join(dir, "bundle.json"))
|
|
12018
12421
|
]).then(([m, b]) => m && b);
|
|
12019
12422
|
}
|
|
12020
12423
|
|
|
12021
12424
|
// src/transfer/import-sqlite.ts
|
|
12022
|
-
import
|
|
12425
|
+
import path28 from "path";
|
|
12023
12426
|
import Database2 from "better-sqlite3";
|
|
12024
12427
|
import { mkdir as mkdir20, writeFile as writeFile18 } from "fs/promises";
|
|
12025
12428
|
function normalizeForDedupe2(s) {
|
|
@@ -12027,8 +12430,8 @@ function normalizeForDedupe2(s) {
|
|
|
12027
12430
|
}
|
|
12028
12431
|
async function importSqlite(opts) {
|
|
12029
12432
|
const conflict = opts.conflict ?? "skip";
|
|
12030
|
-
const memDirAbs =
|
|
12031
|
-
const fromAbs =
|
|
12433
|
+
const memDirAbs = path28.resolve(opts.targetMemoryDir);
|
|
12434
|
+
const fromAbs = path28.resolve(opts.fromFile);
|
|
12032
12435
|
const db = new Database2(fromAbs, { readonly: true });
|
|
12033
12436
|
const written = [];
|
|
12034
12437
|
let skipped = 0;
|
|
@@ -12041,7 +12444,7 @@ async function importSqlite(opts) {
|
|
|
12041
12444
|
const rows = db.prepare("SELECT path_rel, content FROM files").all();
|
|
12042
12445
|
for (const r of rows) {
|
|
12043
12446
|
const relFs = fromPosixRelPath(r.path_rel);
|
|
12044
|
-
const absTarget =
|
|
12447
|
+
const absTarget = path28.join(memDirAbs, relFs);
|
|
12045
12448
|
const exists3 = await fileExists(absTarget);
|
|
12046
12449
|
if (exists3) {
|
|
12047
12450
|
if (conflict === "skip") {
|
|
@@ -12066,29 +12469,29 @@ async function importSqlite(opts) {
|
|
|
12066
12469
|
}
|
|
12067
12470
|
if (opts.dryRun) return { written: 0, skipped };
|
|
12068
12471
|
for (const w of written) {
|
|
12069
|
-
await mkdir20(
|
|
12472
|
+
await mkdir20(path28.dirname(w.abs), { recursive: true });
|
|
12070
12473
|
await writeFile18(w.abs, w.content, "utf-8");
|
|
12071
12474
|
}
|
|
12072
12475
|
return { written: written.length, skipped };
|
|
12073
12476
|
}
|
|
12074
12477
|
|
|
12075
12478
|
// src/transfer/import-md.ts
|
|
12076
|
-
import
|
|
12479
|
+
import path29 from "path";
|
|
12077
12480
|
import { mkdir as mkdir21, readFile as readFile19, writeFile as writeFile19 } from "fs/promises";
|
|
12078
12481
|
function normalizeForDedupe3(s) {
|
|
12079
12482
|
return s.replace(/\s+/g, " ").trim();
|
|
12080
12483
|
}
|
|
12081
12484
|
async function importMarkdownBundle(opts) {
|
|
12082
12485
|
const conflict = opts.conflict ?? "skip";
|
|
12083
|
-
const fromAbs =
|
|
12084
|
-
const targetAbs =
|
|
12486
|
+
const fromAbs = path29.resolve(opts.fromDir);
|
|
12487
|
+
const targetAbs = path29.resolve(opts.targetMemoryDir);
|
|
12085
12488
|
const filesAbs = await listFilesRecursive(fromAbs);
|
|
12086
12489
|
const writes = [];
|
|
12087
12490
|
let skipped = 0;
|
|
12088
12491
|
for (const abs of filesAbs) {
|
|
12089
12492
|
const relPosix = toPosixRelPath(abs, fromAbs);
|
|
12090
12493
|
if (relPosix === "manifest.json") continue;
|
|
12091
|
-
const dstAbs =
|
|
12494
|
+
const dstAbs = path29.join(targetAbs, fromPosixRelPath(relPosix));
|
|
12092
12495
|
const content = await readFile19(abs, "utf-8");
|
|
12093
12496
|
const exists3 = await fileExists(dstAbs);
|
|
12094
12497
|
if (exists3) {
|
|
@@ -12111,17 +12514,17 @@ async function importMarkdownBundle(opts) {
|
|
|
12111
12514
|
}
|
|
12112
12515
|
if (opts.dryRun) return { written: 0, skipped };
|
|
12113
12516
|
for (const w of writes) {
|
|
12114
|
-
await mkdir21(
|
|
12517
|
+
await mkdir21(path29.dirname(w.abs), { recursive: true });
|
|
12115
12518
|
await writeFile19(w.abs, w.content, "utf-8");
|
|
12116
12519
|
}
|
|
12117
12520
|
return { written: writes.length, skipped };
|
|
12118
12521
|
}
|
|
12119
12522
|
|
|
12120
12523
|
// src/transfer/autodetect.ts
|
|
12121
|
-
import
|
|
12524
|
+
import path30 from "path";
|
|
12122
12525
|
import { stat as stat4 } from "fs/promises";
|
|
12123
12526
|
async function detectImportFormat(fromPath) {
|
|
12124
|
-
const abs =
|
|
12527
|
+
const abs = path30.resolve(fromPath);
|
|
12125
12528
|
let st;
|
|
12126
12529
|
try {
|
|
12127
12530
|
st = await stat4(abs);
|
|
@@ -12163,7 +12566,7 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
12163
12566
|
const ns = (namespace ?? "").trim();
|
|
12164
12567
|
if (!ns) return orchestrator.config.memoryDir;
|
|
12165
12568
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
12166
|
-
const candidate =
|
|
12569
|
+
const candidate = path31.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
12167
12570
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
12168
12571
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
12169
12572
|
}
|
|
@@ -12446,7 +12849,7 @@ function registerCli(api, orchestrator) {
|
|
|
12446
12849
|
}
|
|
12447
12850
|
});
|
|
12448
12851
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
12449
|
-
const workspaceDir =
|
|
12852
|
+
const workspaceDir = path31.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
12450
12853
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
12451
12854
|
if (!identity) {
|
|
12452
12855
|
console.log("No identity file found.");
|
|
@@ -12586,8 +12989,8 @@ function registerCli(api, orchestrator) {
|
|
|
12586
12989
|
const options = args[0] ?? {};
|
|
12587
12990
|
const threadId = options.thread;
|
|
12588
12991
|
const top = parseInt(options.top ?? "10", 10);
|
|
12589
|
-
const memoryDir =
|
|
12590
|
-
const threading = new ThreadingManager(
|
|
12992
|
+
const memoryDir = path31.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
12993
|
+
const threading = new ThreadingManager(path31.join(memoryDir, "threads"));
|
|
12591
12994
|
if (threadId) {
|
|
12592
12995
|
const thread = await threading.loadThread(threadId);
|
|
12593
12996
|
if (!thread) {
|
|
@@ -12761,15 +13164,15 @@ function parseDuration(duration) {
|
|
|
12761
13164
|
|
|
12762
13165
|
// src/index.ts
|
|
12763
13166
|
import { readFile as readFile21, writeFile as writeFile20 } from "fs/promises";
|
|
12764
|
-
import { readFileSync as
|
|
12765
|
-
import
|
|
13167
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
13168
|
+
import path32 from "path";
|
|
12766
13169
|
import os5 from "os";
|
|
12767
13170
|
function loadPluginConfigFromFile() {
|
|
12768
13171
|
try {
|
|
12769
13172
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
12770
13173
|
const homeDir = process.env.HOME ?? os5.homedir();
|
|
12771
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
12772
|
-
const content =
|
|
13174
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path32.join(homeDir, ".openclaw", "openclaw.json");
|
|
13175
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
12773
13176
|
const config = JSON.parse(content);
|
|
12774
13177
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
12775
13178
|
return pluginEntry?.config;
|
|
@@ -12925,7 +13328,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
12925
13328
|
);
|
|
12926
13329
|
async function ensureHourlySummaryCron(api2) {
|
|
12927
13330
|
const jobId = "engram-hourly-summary";
|
|
12928
|
-
const cronFilePath =
|
|
13331
|
+
const cronFilePath = path32.join(os5.homedir(), ".openclaw", "cron", "jobs.json");
|
|
12929
13332
|
try {
|
|
12930
13333
|
let jobsData = { version: 1, jobs: [] };
|
|
12931
13334
|
try {
|