@joshuaswarren/openclaw-engram 8.3.53 → 8.3.55

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
@@ -425,7 +425,7 @@ function parseConfig(raw) {
425
425
  }
426
426
 
427
427
  // src/orchestrator.ts
428
- import path24 from "path";
428
+ import path25 from "path";
429
429
  import { createHash as createHash5 } from "crypto";
430
430
  import { mkdir as mkdir18, readFile as readFile18, writeFile as writeFile17 } from "fs/promises";
431
431
 
@@ -10927,6 +10927,18 @@ endTs: ${c.endTs}
10927
10927
  }
10928
10928
  return written;
10929
10929
  }
10930
+ async function upsertConversationChunksFailOpen(adapter, chunks) {
10931
+ if (!adapter) {
10932
+ return { upserted: 0, skipped: true, reason: "adapter-unavailable" };
10933
+ }
10934
+ try {
10935
+ const upserted = await adapter.upsertChunks(chunks);
10936
+ return { upserted, skipped: false };
10937
+ } catch (err) {
10938
+ log.debug(`conversation index FAISS upsert failed (fail-open): ${err}`);
10939
+ return { upserted: 0, skipped: true, reason: "adapter-error" };
10940
+ }
10941
+ }
10930
10942
 
10931
10943
  // src/conversation-index/cleanup.ts
10932
10944
  import { readdir as readdir8, rm } from "fs/promises";
@@ -10962,8 +10974,212 @@ async function cleanupConversationChunks(rootDir, retentionDays) {
10962
10974
  }
10963
10975
  }
10964
10976
 
10965
- // src/namespaces/storage.ts
10977
+ // src/conversation-index/faiss-adapter.ts
10978
+ import * as childProcess from "child_process";
10979
+ import { fileURLToPath } from "url";
10966
10980
  import path20 from "path";
10981
+ var FaissAdapterError = class extends Error {
10982
+ constructor(message, code) {
10983
+ super(message);
10984
+ this.code = code;
10985
+ this.name = "FaissAdapterError";
10986
+ }
10987
+ };
10988
+ function resolveDefaultFaissScriptPath(fromModuleUrl = import.meta.url) {
10989
+ const currentFile = fileURLToPath(fromModuleUrl);
10990
+ const moduleDir = path20.dirname(currentFile);
10991
+ if (moduleDir.endsWith(`${path20.sep}conversation-index`)) {
10992
+ return path20.resolve(moduleDir, "..", "..", "scripts", "faiss_index.py");
10993
+ }
10994
+ return path20.resolve(moduleDir, "..", "scripts", "faiss_index.py");
10995
+ }
10996
+ var FaissConversationIndexAdapter = class {
10997
+ constructor(config) {
10998
+ this.config = config;
10999
+ this.pythonBin = config.pythonBin && config.pythonBin.trim().length > 0 ? config.pythonBin.trim() : "python3";
11000
+ this.scriptPath = config.scriptPath && config.scriptPath.trim().length > 0 ? config.scriptPath.trim() : resolveDefaultFaissScriptPath();
11001
+ this.indexPath = path20.isAbsolute(config.indexDir) ? config.indexDir : path20.join(config.memoryDir, config.indexDir);
11002
+ this.spawnFn = config.spawnFn ?? childProcess.spawn;
11003
+ }
11004
+ pythonBin;
11005
+ scriptPath;
11006
+ indexPath;
11007
+ spawnFn;
11008
+ async upsertChunks(chunks) {
11009
+ if (this.config.maxBatchSize <= 0) return 0;
11010
+ let totalUpserted = 0;
11011
+ for (let offset = 0; offset < chunks.length; offset += this.config.maxBatchSize) {
11012
+ const batch = chunks.slice(offset, offset + this.config.maxBatchSize);
11013
+ if (batch.length === 0) continue;
11014
+ const payload = {
11015
+ modelId: this.config.modelId,
11016
+ indexPath: this.indexPath,
11017
+ chunks: batch.map((chunk) => ({
11018
+ id: chunk.id,
11019
+ sessionKey: chunk.sessionKey,
11020
+ text: chunk.text,
11021
+ startTs: chunk.startTs,
11022
+ endTs: chunk.endTs
11023
+ }))
11024
+ };
11025
+ const result = await this.runCommand("upsert", payload, this.config.upsertTimeoutMs);
11026
+ const upserted = result.upserted;
11027
+ if (typeof upserted !== "number" || !Number.isFinite(upserted)) {
11028
+ throw new FaissAdapterError("FAISS sidecar produced malformed upsert response", "malformed_output");
11029
+ }
11030
+ totalUpserted += Math.max(0, Math.floor(upserted));
11031
+ }
11032
+ return totalUpserted;
11033
+ }
11034
+ async searchChunks(query, topK) {
11035
+ const requestedTopK = Number.isFinite(topK) ? Math.floor(topK) : 0;
11036
+ const boundedTopK = this.config.maxSearchK > 0 ? Math.max(0, Math.min(requestedTopK, this.config.maxSearchK)) : 0;
11037
+ if (boundedTopK <= 0 || query.trim().length === 0) return [];
11038
+ const payload = {
11039
+ modelId: this.config.modelId,
11040
+ indexPath: this.indexPath,
11041
+ query,
11042
+ topK: boundedTopK
11043
+ };
11044
+ const result = await this.runCommand("search", payload, this.config.searchTimeoutMs);
11045
+ if (!Array.isArray(result.results)) {
11046
+ throw new FaissAdapterError("FAISS sidecar produced malformed search response", "malformed_output");
11047
+ }
11048
+ const rows = result.results;
11049
+ return rows.filter(
11050
+ (row) => row && typeof row.path === "string" && typeof row.snippet === "string" && typeof row.score === "number"
11051
+ ).map((row) => ({ path: row.path, snippet: row.snippet, score: row.score }));
11052
+ }
11053
+ async health() {
11054
+ const payload = {
11055
+ modelId: this.config.modelId,
11056
+ indexPath: this.indexPath
11057
+ };
11058
+ const result = await this.runCommand("health", payload, this.config.healthTimeoutMs);
11059
+ if (result.status !== "ok" && result.status !== "degraded" && result.status !== "error") {
11060
+ throw new FaissAdapterError("FAISS sidecar produced malformed health response", "malformed_output");
11061
+ }
11062
+ return {
11063
+ ok: result.ok === true,
11064
+ status: result.status,
11065
+ indexPath: this.indexPath,
11066
+ message: typeof result.error === "string" && result.error.length > 0 ? result.error : void 0
11067
+ };
11068
+ }
11069
+ async runCommand(command, payload, timeoutMs) {
11070
+ const args = [this.scriptPath, command];
11071
+ const child = this.spawnFn(this.pythonBin, args, {
11072
+ stdio: ["pipe", "pipe", "pipe"]
11073
+ });
11074
+ const stdoutChunks = [];
11075
+ const stderrChunks = [];
11076
+ let timedOut = false;
11077
+ const timer = timeoutMs > 0 ? setTimeout(() => {
11078
+ timedOut = true;
11079
+ child.kill("SIGKILL");
11080
+ }, timeoutMs) : void 0;
11081
+ child.stdout.on("data", (chunk) => {
11082
+ stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
11083
+ });
11084
+ child.stderr.on("data", (chunk) => {
11085
+ stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
11086
+ });
11087
+ let code;
11088
+ try {
11089
+ child.stdin.write(JSON.stringify(payload));
11090
+ child.stdin.end();
11091
+ code = await new Promise((resolve, reject) => {
11092
+ const rejectAsProcessError = (err) => {
11093
+ const msg = err instanceof Error ? err.message : String(err);
11094
+ reject(new FaissAdapterError(`FAISS sidecar stream/process error (${command}): ${msg}`, "non_zero_exit"));
11095
+ };
11096
+ child.once("error", rejectAsProcessError);
11097
+ child.stdin.once("error", rejectAsProcessError);
11098
+ child.once("close", (exitCode) => resolve(exitCode));
11099
+ });
11100
+ } catch (err) {
11101
+ if (err instanceof FaissAdapterError) throw err;
11102
+ const msg = err instanceof Error ? err.message : String(err);
11103
+ throw new FaissAdapterError(`FAISS sidecar stream/process error (${command}): ${msg}`, "non_zero_exit");
11104
+ } finally {
11105
+ if (timer) clearTimeout(timer);
11106
+ }
11107
+ const stdout = Buffer.concat(stdoutChunks).toString("utf-8").trim();
11108
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
11109
+ if (timedOut) {
11110
+ throw new FaissAdapterError(
11111
+ `FAISS sidecar command timed out (${command}, ${timeoutMs}ms)`,
11112
+ "timeout"
11113
+ );
11114
+ }
11115
+ if (code !== 0) {
11116
+ throw new FaissAdapterError(
11117
+ `FAISS sidecar exited non-zero (${command}, code=${code ?? "null"})${stderr ? `: ${stderr}` : ""}`,
11118
+ "non_zero_exit"
11119
+ );
11120
+ }
11121
+ if (stdout.length === 0) {
11122
+ throw new FaissAdapterError(
11123
+ `FAISS sidecar produced empty output (${command})`,
11124
+ "malformed_output"
11125
+ );
11126
+ }
11127
+ let parsed;
11128
+ try {
11129
+ parsed = JSON.parse(stdout);
11130
+ } catch {
11131
+ throw new FaissAdapterError(
11132
+ `FAISS sidecar produced malformed JSON (${command})`,
11133
+ "malformed_output"
11134
+ );
11135
+ }
11136
+ if (parsed.ok === false) {
11137
+ const message = typeof parsed.error === "string" && parsed.error.length > 0 ? parsed.error : `FAISS sidecar command failed (${command})`;
11138
+ throw new FaissAdapterError(message, "non_zero_exit");
11139
+ }
11140
+ if (parsed.ok !== true) {
11141
+ throw new FaissAdapterError(
11142
+ `FAISS sidecar produced malformed success envelope (${command})`,
11143
+ "malformed_output"
11144
+ );
11145
+ }
11146
+ return parsed;
11147
+ }
11148
+ };
11149
+ async function failOpenFaissHealth(adapter) {
11150
+ if (!adapter) {
11151
+ return { ok: false, status: "error", indexPath: "", message: "adapter-unavailable" };
11152
+ }
11153
+ try {
11154
+ return await adapter.health();
11155
+ } catch (err) {
11156
+ log.debug(`faiss adapter health failed (fail-open): ${err}`);
11157
+ return { ok: false, status: "error", indexPath: "", message: "adapter-error" };
11158
+ }
11159
+ }
11160
+
11161
+ // src/conversation-index/search.ts
11162
+ async function searchConversationIndex(qmd, query, maxResults) {
11163
+ try {
11164
+ const results = await qmd.search(query, void 0, maxResults);
11165
+ return results.map((r) => ({ path: r.path, snippet: r.snippet, score: r.score }));
11166
+ } catch (err) {
11167
+ log.debug(`conversation index search failed: ${err}`);
11168
+ return [];
11169
+ }
11170
+ }
11171
+ async function searchConversationIndexFaissFailOpen(adapter, query, maxResults) {
11172
+ if (!adapter) return [];
11173
+ try {
11174
+ return await adapter.searchChunks(query, maxResults);
11175
+ } catch (err) {
11176
+ log.debug(`conversation index FAISS search failed (fail-open): ${err}`);
11177
+ return [];
11178
+ }
11179
+ }
11180
+
11181
+ // src/namespaces/storage.ts
11182
+ import path21 from "path";
10967
11183
  import { access } from "fs/promises";
10968
11184
  async function exists(p) {
10969
11185
  try {
@@ -10985,7 +11201,7 @@ var NamespaceStorageRouter = class {
10985
11201
  this.defaultNsRootResolved = this.config.memoryDir;
10986
11202
  return this.defaultNsRootResolved;
10987
11203
  }
10988
- const nsDir = path20.join(this.config.memoryDir, "namespaces", this.config.defaultNamespace);
11204
+ const nsDir = path21.join(this.config.memoryDir, "namespaces", this.config.defaultNamespace);
10989
11205
  this.defaultNsRootResolved = await exists(nsDir) ? nsDir : this.config.memoryDir;
10990
11206
  return this.defaultNsRootResolved;
10991
11207
  }
@@ -10994,7 +11210,7 @@ var NamespaceStorageRouter = class {
10994
11210
  if (namespace === this.config.defaultNamespace) {
10995
11211
  return this.defaultNsRootResolved ?? this.config.memoryDir;
10996
11212
  }
10997
- return path20.join(this.config.memoryDir, "namespaces", namespace);
11213
+ return path21.join(this.config.memoryDir, "namespaces", namespace);
10998
11214
  }
10999
11215
  async storageFor(namespace) {
11000
11216
  const ns = namespace || this.config.defaultNamespace;
@@ -11071,7 +11287,7 @@ function recallNamespacesForPrincipal(principal, config) {
11071
11287
 
11072
11288
  // src/shared-context/manager.ts
11073
11289
  import { mkdir as mkdir15, readFile as readFile15, readdir as readdir9, appendFile as appendFile5, writeFile as writeFile14, stat as stat4 } from "fs/promises";
11074
- import path21 from "path";
11290
+ import path22 from "path";
11075
11291
  import os3 from "os";
11076
11292
  import { z as z3 } from "zod";
11077
11293
  var SharedFeedbackEntrySchema = z3.object({
@@ -11093,15 +11309,15 @@ function ymd(d) {
11093
11309
  var SharedContextManager = class {
11094
11310
  constructor(config) {
11095
11311
  this.config = config;
11096
- const base = typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0 ? config.sharedContextDir : path21.join(os3.homedir(), ".openclaw", "workspace", "shared-context");
11312
+ const base = typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0 ? config.sharedContextDir : path22.join(os3.homedir(), ".openclaw", "workspace", "shared-context");
11097
11313
  this.dir = base;
11098
- this.prioritiesPath = path21.join(base, "priorities.md");
11099
- this.prioritiesInboxPath = path21.join(base, "priorities.inbox.md");
11100
- this.outputsDir = path21.join(base, "agent-outputs");
11101
- this.roundtableDir = path21.join(base, "roundtable");
11102
- this.feedbackDir = path21.join(base, "feedback");
11103
- this.feedbackInboxPath = path21.join(this.feedbackDir, "inbox.jsonl");
11104
- this.crossSignalsDir = path21.join(base, "cross-signals");
11314
+ this.prioritiesPath = path22.join(base, "priorities.md");
11315
+ this.prioritiesInboxPath = path22.join(base, "priorities.inbox.md");
11316
+ this.outputsDir = path22.join(base, "agent-outputs");
11317
+ this.roundtableDir = path22.join(base, "roundtable");
11318
+ this.feedbackDir = path22.join(base, "feedback");
11319
+ this.feedbackInboxPath = path22.join(this.feedbackDir, "inbox.jsonl");
11320
+ this.crossSignalsDir = path22.join(base, "cross-signals");
11105
11321
  }
11106
11322
  dir;
11107
11323
  prioritiesPath;
@@ -11117,10 +11333,10 @@ var SharedContextManager = class {
11117
11333
  await mkdir15(this.roundtableDir, { recursive: true });
11118
11334
  await mkdir15(this.feedbackDir, { recursive: true });
11119
11335
  await mkdir15(this.crossSignalsDir, { recursive: true });
11120
- await mkdir15(path21.join(this.dir, "staging"), { recursive: true });
11121
- await mkdir15(path21.join(this.dir, "kpis"), { recursive: true });
11122
- await mkdir15(path21.join(this.dir, "calendar"), { recursive: true });
11123
- await mkdir15(path21.join(this.dir, "content-calendar"), { recursive: true });
11336
+ await mkdir15(path22.join(this.dir, "staging"), { recursive: true });
11337
+ await mkdir15(path22.join(this.dir, "kpis"), { recursive: true });
11338
+ await mkdir15(path22.join(this.dir, "calendar"), { recursive: true });
11339
+ await mkdir15(path22.join(this.dir, "content-calendar"), { recursive: true });
11124
11340
  await this.ensureFile(
11125
11341
  this.prioritiesPath,
11126
11342
  [
@@ -11164,7 +11380,7 @@ var SharedContextManager = class {
11164
11380
  async readLatestRoundtable() {
11165
11381
  try {
11166
11382
  const files = (await readdir9(this.roundtableDir)).filter((f) => f.endsWith(".md")).sort().reverse();
11167
- const fp = files[0] ? path21.join(this.roundtableDir, files[0]) : null;
11383
+ const fp = files[0] ? path22.join(this.roundtableDir, files[0]) : null;
11168
11384
  if (!fp) return "";
11169
11385
  return await readFile15(fp, "utf-8");
11170
11386
  } catch {
@@ -11176,9 +11392,9 @@ var SharedContextManager = class {
11176
11392
  const date = ymd(createdAt);
11177
11393
  const time = createdAt.toISOString().slice(11, 19).replace(/:/g, "");
11178
11394
  const slug = safeSlug(opts.title);
11179
- const dir = path21.join(this.outputsDir, opts.agentId, date);
11395
+ const dir = path22.join(this.outputsDir, opts.agentId, date);
11180
11396
  await mkdir15(dir, { recursive: true });
11181
- const fp = path21.join(dir, `${time}-${slug}.md`);
11397
+ const fp = path22.join(dir, `${time}-${slug}.md`);
11182
11398
  const body = `---
11183
11399
  kind: agent_output
11184
11400
  agent: ${opts.agentId}
@@ -11213,11 +11429,11 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
11213
11429
  const agents = await readdir9(this.outputsDir, { withFileTypes: true });
11214
11430
  for (const a of agents) {
11215
11431
  if (!a.isDirectory()) continue;
11216
- const dayDir = path21.join(this.outputsDir, a.name, date);
11432
+ const dayDir = path22.join(this.outputsDir, a.name, date);
11217
11433
  try {
11218
11434
  const files = (await readdir9(dayDir)).filter((f) => f.endsWith(".md")).sort();
11219
11435
  for (const f of files) {
11220
- const p = path21.join(dayDir, f);
11436
+ const p = path22.join(dayDir, f);
11221
11437
  const raw = await readFile15(p, "utf-8");
11222
11438
  const title = (raw.match(/^title:\s*(.+)$/m)?.[1] ?? f).trim();
11223
11439
  outputs.push({ path: p, title });
@@ -11257,7 +11473,7 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
11257
11473
  ];
11258
11474
  const out = md.join("\n");
11259
11475
  const trimmed = out.length > maxChars ? out.slice(0, maxChars) + "\n\n...(trimmed)\n" : out;
11260
- const fp = path21.join(this.roundtableDir, `${date}.md`);
11476
+ const fp = path22.join(this.roundtableDir, `${date}.md`);
11261
11477
  await writeFile14(fp, trimmed, "utf-8");
11262
11478
  log.info(`shared-context curated daily roundtable: ${fp}`);
11263
11479
  return fp;
@@ -11266,7 +11482,7 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
11266
11482
 
11267
11483
  // src/compounding/engine.ts
11268
11484
  import { mkdir as mkdir16, readFile as readFile16, readdir as readdir10, writeFile as writeFile15 } from "fs/promises";
11269
- import path22 from "path";
11485
+ import path23 from "path";
11270
11486
  import os4 from "os";
11271
11487
  function isoWeekId(d) {
11272
11488
  const dt = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
@@ -11297,7 +11513,7 @@ function sharedContextDir(config) {
11297
11513
  if (typeof config.sharedContextDir === "string" && config.sharedContextDir.length > 0) {
11298
11514
  return config.sharedContextDir;
11299
11515
  }
11300
- return path22.join(os4.homedir(), ".openclaw", "workspace", "shared-context");
11516
+ return path23.join(os4.homedir(), ".openclaw", "workspace", "shared-context");
11301
11517
  }
11302
11518
  function cadenceStaleWindowMs(cadence) {
11303
11519
  switch (cadence) {
@@ -11316,14 +11532,14 @@ function cadenceStaleWindowMs(cadence) {
11316
11532
  var CompoundingEngine = class {
11317
11533
  constructor(config) {
11318
11534
  this.config = config;
11319
- this.weeklyDir = path22.join(config.memoryDir, "compounding", "weekly");
11320
- this.mistakesPath = path22.join(config.memoryDir, "compounding", "mistakes.json");
11321
- this.feedbackInboxPath = path22.join(sharedContextDir(config), "feedback", "inbox.jsonl");
11322
- this.identityAnchorPath = path22.join(config.memoryDir, "identity", "identity-anchor.md");
11323
- this.identityIncidentsDir = path22.join(config.memoryDir, "identity", "incidents");
11324
- this.identityAuditWeeklyDir = path22.join(config.memoryDir, "identity", "audits", "weekly");
11325
- this.identityAuditMonthlyDir = path22.join(config.memoryDir, "identity", "audits", "monthly");
11326
- this.identityImprovementLoopsPath = path22.join(config.memoryDir, "identity", "improvement-loops.md");
11535
+ this.weeklyDir = path23.join(config.memoryDir, "compounding", "weekly");
11536
+ this.mistakesPath = path23.join(config.memoryDir, "compounding", "mistakes.json");
11537
+ this.feedbackInboxPath = path23.join(sharedContextDir(config), "feedback", "inbox.jsonl");
11538
+ this.identityAnchorPath = path23.join(config.memoryDir, "identity", "identity-anchor.md");
11539
+ this.identityIncidentsDir = path23.join(config.memoryDir, "identity", "incidents");
11540
+ this.identityAuditWeeklyDir = path23.join(config.memoryDir, "identity", "audits", "weekly");
11541
+ this.identityAuditMonthlyDir = path23.join(config.memoryDir, "identity", "audits", "monthly");
11542
+ this.identityImprovementLoopsPath = path23.join(config.memoryDir, "identity", "improvement-loops.md");
11327
11543
  }
11328
11544
  weeklyDir;
11329
11545
  mistakesPath;
@@ -11335,7 +11551,7 @@ var CompoundingEngine = class {
11335
11551
  identityImprovementLoopsPath;
11336
11552
  async ensureDirs() {
11337
11553
  await mkdir16(this.weeklyDir, { recursive: true });
11338
- await mkdir16(path22.dirname(this.mistakesPath), { recursive: true });
11554
+ await mkdir16(path23.dirname(this.mistakesPath), { recursive: true });
11339
11555
  }
11340
11556
  async synthesizeWeekly(opts) {
11341
11557
  await this.ensureDirs();
@@ -11343,7 +11559,7 @@ var CompoundingEngine = class {
11343
11559
  const entries = await this.readFeedbackEntriesForWeek(weekId);
11344
11560
  const mistakes = this.buildMistakes(entries);
11345
11561
  const continuity = this.config.continuityAuditEnabled ? await this.readContinuityAuditReferences(weekId) : { monthId: monthIdFromIsoWeek(weekId), weeklyPath: null, monthlyPath: null };
11346
- const reportPath = path22.join(this.weeklyDir, `${weekId}.md`);
11562
+ const reportPath = path23.join(this.weeklyDir, `${weekId}.md`);
11347
11563
  const md = this.formatWeeklyReport(weekId, entries, mistakes.patterns, continuity);
11348
11564
  await writeFile15(reportPath, md, "utf-8");
11349
11565
  await writeFile15(this.mistakesPath, JSON.stringify(mistakes, null, 2) + "\n", "utf-8");
@@ -11423,7 +11639,7 @@ var CompoundingEngine = class {
11423
11639
  ];
11424
11640
  const dir = period === "weekly" ? this.identityAuditWeeklyDir : this.identityAuditMonthlyDir;
11425
11641
  await mkdir16(dir, { recursive: true });
11426
- const reportPath = path22.join(dir, `${key}.md`);
11642
+ const reportPath = path23.join(dir, `${key}.md`);
11427
11643
  await writeFile15(reportPath, lines.join("\n"), "utf-8");
11428
11644
  return { period, key, reportPath };
11429
11645
  }
@@ -11552,7 +11768,7 @@ var CompoundingEngine = class {
11552
11768
  const files = names.filter((n) => n.endsWith(".md")).sort().reverse();
11553
11769
  for (const file of files) {
11554
11770
  if (incidents.length >= cappedLimit) break;
11555
- const filePath = path22.join(this.identityIncidentsDir, file);
11771
+ const filePath = path23.join(this.identityIncidentsDir, file);
11556
11772
  try {
11557
11773
  const raw = await readFile16(filePath, "utf-8");
11558
11774
  const parsed = parseContinuityIncident(raw);
@@ -11568,8 +11784,8 @@ var CompoundingEngine = class {
11568
11784
  }
11569
11785
  async readContinuityAuditReferences(weekId) {
11570
11786
  const monthId = monthIdFromIsoWeek(weekId);
11571
- const weeklyPath = path22.join(this.identityAuditWeeklyDir, `${weekId}.md`);
11572
- const monthlyPath = path22.join(this.identityAuditMonthlyDir, `${monthId}.md`);
11787
+ const weeklyPath = path23.join(this.identityAuditWeeklyDir, `${weekId}.md`);
11788
+ const monthlyPath = path23.join(this.identityAuditMonthlyDir, `${monthId}.md`);
11573
11789
  const weeklyExists = await this.readNonEmptyFile(weeklyPath);
11574
11790
  const monthlyExists = await this.readNonEmptyFile(monthlyPath);
11575
11791
  return {
@@ -11685,7 +11901,7 @@ function selectRouteRule(text, rules, options) {
11685
11901
 
11686
11902
  // src/routing/store.ts
11687
11903
  import { lstat, mkdir as mkdir17, readFile as readFile17, realpath, rename as rename2, rm as rm2, stat as stat5, writeFile as writeFile16 } from "fs/promises";
11688
- import path23 from "path";
11904
+ import path24 from "path";
11689
11905
  import { createHash as createHash4 } from "crypto";
11690
11906
  function defaultState() {
11691
11907
  return {
@@ -11704,14 +11920,14 @@ function stableRuleId(rule) {
11704
11920
  return `route-${createHash4("sha256").update(seed).digest("hex").slice(0, 12)}`;
11705
11921
  }
11706
11922
  function resolveStatePath(memoryDir, stateFile) {
11707
- const root = path23.resolve(memoryDir);
11708
- const defaultPath = path23.join(root, "state", "routing-rules.json");
11709
- if (path23.isAbsolute(stateFile)) {
11710
- const absolute = path23.resolve(stateFile);
11711
- return absolute.startsWith(root + path23.sep) ? absolute : defaultPath;
11923
+ const root = path24.resolve(memoryDir);
11924
+ const defaultPath = path24.join(root, "state", "routing-rules.json");
11925
+ if (path24.isAbsolute(stateFile)) {
11926
+ const absolute = path24.resolve(stateFile);
11927
+ return absolute.startsWith(root + path24.sep) ? absolute : defaultPath;
11712
11928
  }
11713
- const resolved = path23.resolve(root, stateFile);
11714
- return resolved.startsWith(root + path23.sep) ? resolved : defaultPath;
11929
+ const resolved = path24.resolve(root, stateFile);
11930
+ return resolved.startsWith(root + path24.sep) ? resolved : defaultPath;
11715
11931
  }
11716
11932
  function normalizeRule(rule, options) {
11717
11933
  if (!rule || typeof rule !== "object") return null;
@@ -11744,7 +11960,7 @@ var RoutingRulesStore = class {
11744
11960
  lockPath;
11745
11961
  writeQueue = Promise.resolve();
11746
11962
  constructor(memoryDir, stateFile = "state/routing-rules.json") {
11747
- this.memoryRoot = path23.resolve(memoryDir);
11963
+ this.memoryRoot = path24.resolve(memoryDir);
11748
11964
  this.statePath = resolveStatePath(memoryDir, stateFile);
11749
11965
  this.lockPath = `${this.statePath}.lock`;
11750
11966
  }
@@ -11850,7 +12066,7 @@ var RoutingRulesStore = class {
11850
12066
  const timeoutMs = 5e3;
11851
12067
  let unexpectedLockError = null;
11852
12068
  await this.assertStatePathScoped();
11853
- await mkdir17(path23.dirname(this.lockPath), { recursive: true });
12069
+ await mkdir17(path24.dirname(this.lockPath), { recursive: true });
11854
12070
  while (Date.now() - start < timeoutMs) {
11855
12071
  try {
11856
12072
  await mkdir17(this.lockPath);
@@ -11885,12 +12101,12 @@ var RoutingRulesStore = class {
11885
12101
  async assertStatePathScoped() {
11886
12102
  await mkdir17(this.memoryRoot, { recursive: true });
11887
12103
  const canonicalRoot = await realpath(this.memoryRoot);
11888
- const canonicalParent = await this.canonicalizePathWithoutCreating(path23.dirname(this.statePath));
11889
- const canonicalStatePath = path23.join(canonicalParent, path23.basename(this.statePath));
12104
+ const canonicalParent = await this.canonicalizePathWithoutCreating(path24.dirname(this.statePath));
12105
+ const canonicalStatePath = path24.join(canonicalParent, path24.basename(this.statePath));
11890
12106
  if (!this.isPathInside(canonicalRoot, canonicalStatePath)) {
11891
12107
  throw new Error(`routing rules state path escaped memoryDir: ${canonicalStatePath}`);
11892
12108
  }
11893
- await mkdir17(path23.dirname(this.statePath), { recursive: true });
12109
+ await mkdir17(path24.dirname(this.statePath), { recursive: true });
11894
12110
  try {
11895
12111
  const stateStats = await lstat(this.statePath);
11896
12112
  if (stateStats.isSymbolicLink()) {
@@ -11907,28 +12123,28 @@ var RoutingRulesStore = class {
11907
12123
  }
11908
12124
  }
11909
12125
  isPathInside(root, candidate) {
11910
- const normalizedRoot = path23.resolve(root);
11911
- const normalizedCandidate = path23.resolve(candidate);
12126
+ const normalizedRoot = path24.resolve(root);
12127
+ const normalizedCandidate = path24.resolve(candidate);
11912
12128
  if (normalizedCandidate === normalizedRoot) return true;
11913
- if (normalizedRoot === path23.parse(normalizedRoot).root) {
12129
+ if (normalizedRoot === path24.parse(normalizedRoot).root) {
11914
12130
  return normalizedCandidate.startsWith(normalizedRoot);
11915
12131
  }
11916
- return normalizedCandidate.startsWith(`${normalizedRoot}${path23.sep}`);
12132
+ return normalizedCandidate.startsWith(`${normalizedRoot}${path24.sep}`);
11917
12133
  }
11918
12134
  async canonicalizePathWithoutCreating(targetPath) {
11919
- const absoluteTarget = path23.resolve(targetPath);
12135
+ const absoluteTarget = path24.resolve(targetPath);
11920
12136
  let probe = absoluteTarget;
11921
12137
  while (true) {
11922
12138
  try {
11923
12139
  const canonicalProbe = await realpath(probe);
11924
- const remainder = path23.relative(probe, absoluteTarget);
11925
- return path23.resolve(canonicalProbe, remainder);
12140
+ const remainder = path24.relative(probe, absoluteTarget);
12141
+ return path24.resolve(canonicalProbe, remainder);
11926
12142
  } catch (err) {
11927
12143
  const code = err.code;
11928
12144
  if (code !== "ENOENT") {
11929
12145
  throw err;
11930
12146
  }
11931
- const parent = path23.dirname(probe);
12147
+ const parent = path24.dirname(probe);
11932
12148
  if (parent === probe) {
11933
12149
  return absoluteTarget;
11934
12150
  }
@@ -12117,11 +12333,11 @@ function mergeGraphExpandedResults(primary, expanded) {
12117
12333
  return Array.from(mergedByPath.values());
12118
12334
  }
12119
12335
  function graphPathRelativeToStorage(storageDir, candidatePath) {
12120
- const absolutePath = path24.isAbsolute(candidatePath) ? candidatePath : path24.resolve(storageDir, candidatePath);
12121
- const rel = path24.relative(storageDir, absolutePath);
12336
+ const absolutePath = path25.isAbsolute(candidatePath) ? candidatePath : path25.resolve(storageDir, candidatePath);
12337
+ const rel = path25.relative(storageDir, absolutePath);
12122
12338
  if (!rel || rel === ".") return null;
12123
12339
  if (rel.startsWith("..")) return null;
12124
- return rel.split(path24.sep).join("/");
12340
+ return rel.split(path25.sep).join("/");
12125
12341
  }
12126
12342
  function graphActivationScoreToRecallScore(score) {
12127
12343
  const bounded = Number.isFinite(score) && score > 0 ? score : 0;
@@ -12163,7 +12379,7 @@ function buildMemoryPathById(allMemsForGraph, storageDir) {
12163
12379
  for (const mem of allMemsForGraph ?? []) {
12164
12380
  const id = mem.frontmatter.id;
12165
12381
  if (!id) continue;
12166
- pathById.set(id, path24.relative(storageDir, mem.path));
12382
+ pathById.set(id, path25.relative(storageDir, mem.path));
12167
12383
  }
12168
12384
  return pathById;
12169
12385
  }
@@ -12171,7 +12387,7 @@ function appendMemoryToGraphContext(options) {
12171
12387
  if (!Array.isArray(options.allMemsForGraph)) return;
12172
12388
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
12173
12389
  options.allMemsForGraph.push({
12174
- path: path24.join(options.storageDir, options.memoryRelPath),
12390
+ path: path25.join(options.storageDir, options.memoryRelPath),
12175
12391
  content: options.content,
12176
12392
  frontmatter: {
12177
12393
  id: options.memoryId,
@@ -12191,21 +12407,22 @@ function resolvePersistedMemoryRelativePath(options) {
12191
12407
  const persisted = options.pathById.get(options.memoryId);
12192
12408
  if (persisted) return persisted;
12193
12409
  if (options.category === "correction") {
12194
- return path24.join("corrections", `${options.memoryId}.md`);
12410
+ return path25.join("corrections", `${options.memoryId}.md`);
12195
12411
  }
12196
12412
  const idParts = options.memoryId.split("-");
12197
12413
  const maybeTimestamp = Number(idParts[1]);
12198
12414
  if (Number.isFinite(maybeTimestamp) && maybeTimestamp > 0) {
12199
12415
  const day = new Date(maybeTimestamp).toISOString().slice(0, 10);
12200
- return path24.join("facts", day, `${options.memoryId}.md`);
12416
+ return path25.join("facts", day, `${options.memoryId}.md`);
12201
12417
  }
12202
- return path24.join("facts", `${options.memoryId}.md`);
12418
+ return path25.join("facts", `${options.memoryId}.md`);
12203
12419
  }
12204
12420
  var Orchestrator = class _Orchestrator {
12205
12421
  storage;
12206
12422
  storageRouter;
12207
12423
  qmd;
12208
12424
  conversationQmd;
12425
+ conversationFaiss;
12209
12426
  sharedContext;
12210
12427
  compounding;
12211
12428
  buffer;
@@ -12287,11 +12504,23 @@ var Orchestrator = class _Orchestrator {
12287
12504
  daemonRecheckIntervalMs: config.qmdDaemonRecheckIntervalMs
12288
12505
  }
12289
12506
  ) : void 0;
12507
+ this.conversationFaiss = config.conversationIndexEnabled && config.conversationIndexBackend === "faiss" ? new FaissConversationIndexAdapter({
12508
+ memoryDir: config.memoryDir,
12509
+ scriptPath: config.conversationIndexFaissScriptPath,
12510
+ pythonBin: config.conversationIndexFaissPythonBin,
12511
+ modelId: config.conversationIndexFaissModelId,
12512
+ indexDir: config.conversationIndexFaissIndexDir,
12513
+ upsertTimeoutMs: config.conversationIndexFaissUpsertTimeoutMs,
12514
+ searchTimeoutMs: config.conversationIndexFaissSearchTimeoutMs,
12515
+ healthTimeoutMs: config.conversationIndexFaissHealthTimeoutMs,
12516
+ maxBatchSize: config.conversationIndexFaissMaxBatchSize,
12517
+ maxSearchK: config.conversationIndexFaissMaxSearchK
12518
+ }) : void 0;
12290
12519
  this.sharedContext = config.sharedContextEnabled ? new SharedContextManager(config) : void 0;
12291
12520
  this.compounding = config.compoundingEnabled ? new CompoundingEngine(config) : void 0;
12292
12521
  this.buffer = new SmartBuffer(config, this.storage);
12293
12522
  this.transcript = new TranscriptManager(config);
12294
- this.conversationIndexDir = path24.join(config.memoryDir, "conversation-index", "chunks");
12523
+ this.conversationIndexDir = path25.join(config.memoryDir, "conversation-index", "chunks");
12295
12524
  this.modelRegistry = new ModelRegistry(config.memoryDir);
12296
12525
  this.relevance = new RelevanceStore(config.memoryDir);
12297
12526
  this.negatives = new NegativeExampleStore(config.memoryDir);
@@ -12306,7 +12535,7 @@ var Orchestrator = class _Orchestrator {
12306
12535
  this.localLlm = new LocalLlmClient(config, this.modelRegistry);
12307
12536
  this.extraction = new ExtractionEngine(config, this.localLlm, config.gatewayConfig, this.modelRegistry);
12308
12537
  this.threading = new ThreadingManager(
12309
- path24.join(config.memoryDir, "threads"),
12538
+ path25.join(config.memoryDir, "threads"),
12310
12539
  config.threadingGapMinutes
12311
12540
  );
12312
12541
  this.tmtBuilder = new TmtBuilder(config.memoryDir, {
@@ -12442,7 +12671,7 @@ var Orchestrator = class _Orchestrator {
12442
12671
  await this.lastRecall.load();
12443
12672
  await this.sessionObserver.load();
12444
12673
  if (this.config.factDeduplicationEnabled) {
12445
- const stateDir2 = path24.join(this.config.memoryDir, "state");
12674
+ const stateDir2 = path25.join(this.config.memoryDir, "state");
12446
12675
  this.contentHashIndex = new ContentHashIndex(stateDir2);
12447
12676
  await this.contentHashIndex.load();
12448
12677
  log.info(`content-hash dedup: loaded ${this.contentHashIndex.size} hashes`);
@@ -12475,12 +12704,12 @@ var Orchestrator = class _Orchestrator {
12475
12704
  log.warn(`QMD: not available ${this.qmd.debugStatus()}`);
12476
12705
  }
12477
12706
  }
12478
- if (this.config.conversationIndexEnabled && this.conversationQmd) {
12707
+ if (this.config.conversationIndexEnabled && this.config.conversationIndexBackend === "qmd" && this.conversationQmd) {
12479
12708
  const available = await this.conversationQmd.probe();
12480
12709
  if (available) {
12481
12710
  log.info(`Conversation index QMD: available ${this.conversationQmd.debugStatus()}`);
12482
12711
  const collectionState = await this.conversationQmd.ensureCollection(
12483
- path24.join(this.config.memoryDir, "conversation-index")
12712
+ path25.join(this.config.memoryDir, "conversation-index")
12484
12713
  );
12485
12714
  if (collectionState === "missing") {
12486
12715
  this.config.conversationIndexEnabled = false;
@@ -12498,6 +12727,14 @@ var Orchestrator = class _Orchestrator {
12498
12727
  log.warn(`Conversation index QMD: not available ${this.conversationQmd.debugStatus()}`);
12499
12728
  }
12500
12729
  }
12730
+ if (this.config.conversationIndexEnabled && this.config.conversationIndexBackend === "faiss" && this.conversationFaiss) {
12731
+ const health = await failOpenFaissHealth(this.conversationFaiss);
12732
+ if (health.ok) {
12733
+ log.info(`Conversation index FAISS: available (status=${health.status})`);
12734
+ } else {
12735
+ log.warn(`Conversation index FAISS: degraded (${health.message ?? health.status})`);
12736
+ }
12737
+ }
12501
12738
  await this.buffer.load();
12502
12739
  if (this.config.localLlmEnabled) {
12503
12740
  await this.validateLocalLlmModel();
@@ -12516,12 +12753,12 @@ var Orchestrator = class _Orchestrator {
12516
12753
  this.lastFileHygieneRunAtMs = now;
12517
12754
  if (hygiene.rotateEnabled) {
12518
12755
  for (const rel of hygiene.rotatePaths) {
12519
- const abs = path24.isAbsolute(rel) ? rel : path24.join(this.config.workspaceDir, rel);
12756
+ const abs = path25.isAbsolute(rel) ? rel : path25.join(this.config.workspaceDir, rel);
12520
12757
  try {
12521
12758
  const raw = await readFile18(abs, "utf-8");
12522
12759
  if (raw.length > hygiene.rotateMaxBytes) {
12523
- const archiveDir = path24.join(this.config.workspaceDir, hygiene.archiveDir);
12524
- const base = path24.basename(abs);
12760
+ const archiveDir = path25.join(this.config.workspaceDir, hygiene.archiveDir);
12761
+ const base = path25.basename(abs);
12525
12762
  const prefix = base.toUpperCase().replace(/\.MD$/i, "").replace(/[^A-Z0-9]+/g, "-") || "FILE";
12526
12763
  const { newContent } = await rotateMarkdownFileToArchive({
12527
12764
  filePath: abs,
@@ -12546,8 +12783,8 @@ var Orchestrator = class _Orchestrator {
12546
12783
  log.warn(w.message);
12547
12784
  }
12548
12785
  if (hygiene.warningsLogEnabled && warnings.length > 0) {
12549
- const fp = path24.join(this.config.memoryDir, hygiene.warningsLogPath);
12550
- await mkdir18(path24.dirname(fp), { recursive: true });
12786
+ const fp = path25.join(this.config.memoryDir, hygiene.warningsLogPath);
12787
+ await mkdir18(path25.dirname(fp), { recursive: true });
12551
12788
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
12552
12789
  const block = `
12553
12790
 
@@ -12615,7 +12852,7 @@ var Orchestrator = class _Orchestrator {
12615
12852
  }
12616
12853
  async getLastGraphRecallSnapshot(namespace) {
12617
12854
  const storage = await this.getStorage(namespace);
12618
- const snapshotPath = path24.join(storage.dir, "state", "last_graph_recall.json");
12855
+ const snapshotPath = path25.join(storage.dir, "state", "last_graph_recall.json");
12619
12856
  try {
12620
12857
  const raw = await readFile18(snapshotPath, "utf-8");
12621
12858
  const parsed = JSON.parse(raw);
@@ -12655,6 +12892,32 @@ var Orchestrator = class _Orchestrator {
12655
12892
  ...expanded.map((e) => `- ${e.path} (score=${e.score.toFixed(3)}, ns=${e.namespace})`)
12656
12893
  ].join("\n");
12657
12894
  }
12895
+ async searchConversationRecallResults(retrievalQuery, topK) {
12896
+ if (this.config.conversationIndexBackend === "faiss") {
12897
+ return searchConversationIndexFaissFailOpen(this.conversationFaiss, retrievalQuery, topK);
12898
+ }
12899
+ if (this.conversationQmd && this.conversationQmd.isAvailable()) {
12900
+ return searchConversationIndex(this.conversationQmd, retrievalQuery, topK);
12901
+ }
12902
+ return [];
12903
+ }
12904
+ formatConversationRecallSection(results, maxChars) {
12905
+ if (!Array.isArray(results) || results.length === 0) return null;
12906
+ const lines = ["## Semantic Recall (Past Conversations)", ""];
12907
+ let used = 0;
12908
+ for (const r of results) {
12909
+ if (!r?.snippet) continue;
12910
+ const chunk = `### ${r.path}
12911
+ Score: ${r.score.toFixed(3)}
12912
+
12913
+ ${r.snippet.trim()}
12914
+ `;
12915
+ if (used + chunk.length > maxChars) break;
12916
+ lines.push(chunk);
12917
+ used += chunk.length;
12918
+ }
12919
+ return used > 0 ? lines.join("\n") : null;
12920
+ }
12658
12921
  async updateConversationIndex(sessionKey, hours = 24, opts) {
12659
12922
  if (!this.config.conversationIndexEnabled) {
12660
12923
  return { chunks: 0, skipped: true, reason: "disabled", embedded: false };
@@ -12685,15 +12948,19 @@ var Orchestrator = class _Orchestrator {
12685
12948
  this.conversationIndexDir,
12686
12949
  this.config.conversationIndexRetentionDays
12687
12950
  );
12688
- const q = this.conversationQmd ?? this.qmd;
12689
- const usingPrimaryQmdClient = q === this.qmd;
12690
12951
  const shouldEmbed = opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
12691
12952
  let embedded = false;
12692
- if ((!usingPrimaryQmdClient || this.config.qmdEnabled) && q.isAvailable()) {
12693
- await q.update();
12694
- if (shouldEmbed) {
12695
- await q.embed();
12696
- embedded = true;
12953
+ if (this.config.conversationIndexBackend === "faiss") {
12954
+ await upsertConversationChunksFailOpen(this.conversationFaiss, chunks);
12955
+ } else {
12956
+ const q = this.conversationQmd ?? this.qmd;
12957
+ const usingPrimaryQmdClient = q === this.qmd;
12958
+ if ((!usingPrimaryQmdClient || this.config.qmdEnabled) && q.isAvailable()) {
12959
+ await q.update();
12960
+ if (shouldEmbed) {
12961
+ await q.embed();
12962
+ embedded = true;
12963
+ }
12697
12964
  }
12698
12965
  }
12699
12966
  this.conversationIndexLastUpdateAtMs.set(sessionKey, Date.now());
@@ -12917,7 +13184,7 @@ var Orchestrator = class _Orchestrator {
12917
13184
  const storage = await this.storageRouter.storageFor(namespace);
12918
13185
  const seedRelativePaths = nsResults.slice(0, perNamespaceSeedCap).map((result) => graphPathRelativeToStorage(storage.dir, result.path)).filter((value) => typeof value === "string" && value.length > 0);
12919
13186
  if (seedRelativePaths.length === 0) continue;
12920
- seedPaths.push(...seedRelativePaths.map((rel) => path24.join(storage.dir, rel)));
13187
+ seedPaths.push(...seedRelativePaths.map((rel) => path25.join(storage.dir, rel)));
12921
13188
  const seedSet = new Set(seedRelativePaths);
12922
13189
  const expanded = await this.graphIndexFor(storage).spreadingActivation(
12923
13190
  seedRelativePaths,
@@ -12926,7 +13193,7 @@ var Orchestrator = class _Orchestrator {
12926
13193
  if (expanded.length === 0) continue;
12927
13194
  for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
12928
13195
  if (seedSet.has(candidate.path)) continue;
12929
- const memoryPath = path24.resolve(storage.dir, candidate.path);
13196
+ const memoryPath = path25.resolve(storage.dir, candidate.path);
12930
13197
  const memory = await storage.readMemoryByPath(memoryPath);
12931
13198
  if (!memory) continue;
12932
13199
  if (isArtifactMemoryPath(memory.path)) continue;
@@ -12954,8 +13221,8 @@ var Orchestrator = class _Orchestrator {
12954
13221
  }
12955
13222
  async recordLastGraphRecallSnapshot(options) {
12956
13223
  try {
12957
- const snapshotPath = path24.join(options.storage.dir, "state", "last_graph_recall.json");
12958
- await mkdir18(path24.dirname(snapshotPath), { recursive: true });
13224
+ const snapshotPath = path25.join(options.storage.dir, "state", "last_graph_recall.json");
13225
+ await mkdir18(path25.dirname(snapshotPath), { recursive: true });
12959
13226
  const now = (/* @__PURE__ */ new Date()).toISOString();
12960
13227
  const payload = {
12961
13228
  recordedAt: now,
@@ -13539,36 +13806,22 @@ ${formatted}`);
13539
13806
  }
13540
13807
  timings.summaries = `${Date.now() - summariesT0}ms`;
13541
13808
  const convT0 = Date.now();
13542
- if (this.config.conversationIndexEnabled && !queryPolicy.skipConversationRecall && this.conversationQmd && this.conversationQmd.isAvailable()) {
13809
+ if (this.config.conversationIndexEnabled && !queryPolicy.skipConversationRecall) {
13543
13810
  const startedAtMs = Date.now();
13544
13811
  const timeoutMs = Math.max(200, this.config.conversationRecallTimeoutMs);
13545
13812
  const topK = Math.max(1, this.config.conversationRecallTopK);
13546
13813
  const maxChars = Math.max(400, this.config.conversationRecallMaxChars);
13547
13814
  const results = await Promise.race([
13548
- this.conversationQmd.search(retrievalQuery, void 0, topK),
13815
+ this.searchConversationRecallResults(retrievalQuery, topK),
13549
13816
  new Promise((resolve) => setTimeout(() => resolve([]), timeoutMs))
13550
13817
  ]).catch(() => []);
13551
13818
  const durationMs = Date.now() - startedAtMs;
13552
13819
  if (durationMs >= timeoutMs) {
13553
13820
  log.debug(`conversation recall: timed out after ${timeoutMs}ms`);
13554
13821
  }
13555
- if (Array.isArray(results) && results.length > 0) {
13556
- const lines = ["## Semantic Recall (Past Conversations)", ""];
13557
- let used = 0;
13558
- for (const r of results) {
13559
- if (!r?.snippet) continue;
13560
- const chunk = `### ${r.path}
13561
- Score: ${r.score.toFixed(3)}
13562
-
13563
- ${r.snippet.trim()}
13564
- `;
13565
- if (used + chunk.length > maxChars) break;
13566
- lines.push(chunk);
13567
- used += chunk.length;
13568
- }
13569
- if (used > 0) {
13570
- sections.push(lines.join("\n"));
13571
- }
13822
+ const formattedConversationRecall = this.formatConversationRecallSection(results, maxChars);
13823
+ if (formattedConversationRecall) {
13824
+ sections.push(formattedConversationRecall);
13572
13825
  }
13573
13826
  }
13574
13827
  timings.convRecall = `${Date.now() - convT0}ms`;
@@ -14370,7 +14623,7 @@ _Context: ${topQuestion.context}_`);
14370
14623
  const allMems = allMemsForGraph ?? [];
14371
14624
  for (const m of allMems) {
14372
14625
  if (m.frontmatter.entityRef === entityRef) {
14373
- const rel = path24.relative(storage.dir, m.path);
14626
+ const rel = path25.relative(storage.dir, m.path);
14374
14627
  if (rel !== memoryRelPath) entitySiblings.push(rel);
14375
14628
  }
14376
14629
  }
@@ -14747,8 +15000,8 @@ ${texts.map((t, i) => `[${i + 1}] ${t}`).join("\n\n")}`;
14747
15000
  protectedCategories: this.config.lifecycleProtectedCategories
14748
15001
  }
14749
15002
  };
14750
- const metricsPath = path24.join(this.storage.dir, "state", "lifecycle-metrics.json");
14751
- await mkdir18(path24.dirname(metricsPath), { recursive: true });
15003
+ const metricsPath = path25.join(this.storage.dir, "state", "lifecycle-metrics.json");
15004
+ await mkdir18(path25.dirname(metricsPath), { recursive: true });
14752
15005
  await writeFile17(metricsPath, JSON.stringify(metrics, null, 2), "utf-8");
14753
15006
  }
14754
15007
  /**
@@ -15015,7 +15268,7 @@ ${lines.join("\n\n")}`;
15015
15268
  if (hits.length === 0) return [];
15016
15269
  const results = [];
15017
15270
  for (const hit of hits) {
15018
- const fullPath = path24.isAbsolute(hit.path) ? hit.path : path24.join(this.config.memoryDir, hit.path);
15271
+ const fullPath = path25.isAbsolute(hit.path) ? hit.path : path25.join(this.config.memoryDir, hit.path);
15019
15272
  const memory = await this.storage.readMemoryByPath(fullPath);
15020
15273
  if (!memory) continue;
15021
15274
  results.push({
@@ -15454,8 +15707,8 @@ ${lines.join("\n\n")}`;
15454
15707
  }
15455
15708
  namespaceFromStorageDir(storageDir) {
15456
15709
  if (!this.config.namespacesEnabled) return this.config.defaultNamespace;
15457
- const resolvedStorageDir = path24.resolve(storageDir);
15458
- const resolvedMemoryDir = path24.resolve(this.config.memoryDir);
15710
+ const resolvedStorageDir = path25.resolve(storageDir);
15711
+ const resolvedMemoryDir = path25.resolve(this.config.memoryDir);
15459
15712
  if (resolvedStorageDir === resolvedMemoryDir) return this.config.defaultNamespace;
15460
15713
  const m = resolvedStorageDir.match(/[\\/]namespaces[\\/]([^\\/]+)$/);
15461
15714
  return m && m[1] ? m[1] : this.config.defaultNamespace;
@@ -15483,12 +15736,12 @@ ${lines.join("\n\n")}`;
15483
15736
  };
15484
15737
 
15485
15738
  // src/tools.ts
15486
- import path26 from "path";
15739
+ import path27 from "path";
15487
15740
  import { createHash as createHash6 } from "crypto";
15488
15741
  import { Type } from "@sinclair/typebox";
15489
15742
 
15490
15743
  // src/work/storage.ts
15491
- import path25 from "path";
15744
+ import path26 from "path";
15492
15745
  import { randomUUID } from "crypto";
15493
15746
  import { mkdir as mkdir19, readdir as readdir11, readFile as readFile19, rm as rm3, writeFile as writeFile18 } from "fs/promises";
15494
15747
  var TASK_TRANSITIONS = {
@@ -15565,8 +15818,8 @@ function ensureProjectStatus(value) {
15565
15818
  var WorkStorage = class {
15566
15819
  constructor(memoryDir) {
15567
15820
  this.memoryDir = memoryDir;
15568
- this.tasksDir = path25.join(memoryDir, "work", "tasks");
15569
- this.projectsDir = path25.join(memoryDir, "work", "projects");
15821
+ this.tasksDir = path26.join(memoryDir, "work", "tasks");
15822
+ this.projectsDir = path26.join(memoryDir, "work", "projects");
15570
15823
  }
15571
15824
  tasksDir;
15572
15825
  projectsDir;
@@ -15576,11 +15829,11 @@ var WorkStorage = class {
15576
15829
  }
15577
15830
  taskPath(id) {
15578
15831
  assertValidWorkId(id, "task");
15579
- return path25.join(this.tasksDir, `${id}.md`);
15832
+ return path26.join(this.tasksDir, `${id}.md`);
15580
15833
  }
15581
15834
  projectPath(id) {
15582
15835
  assertValidWorkId(id, "project");
15583
- return path25.join(this.projectsDir, `${id}.md`);
15836
+ return path26.join(this.projectsDir, `${id}.md`);
15584
15837
  }
15585
15838
  serializeTask(task) {
15586
15839
  return `${serializeFrontmatter2(task)}
@@ -15672,7 +15925,7 @@ ${project.description}
15672
15925
  const out = [];
15673
15926
  for (const entry of entries) {
15674
15927
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
15675
- const raw = await readFile19(path25.join(this.tasksDir, entry.name), "utf-8");
15928
+ const raw = await readFile19(path26.join(this.tasksDir, entry.name), "utf-8");
15676
15929
  const task = this.parseTask(raw);
15677
15930
  if (!task) continue;
15678
15931
  if (filter?.status && task.status !== filter.status) continue;
@@ -15771,7 +16024,7 @@ ${project.description}
15771
16024
  const out = [];
15772
16025
  for (const entry of entries) {
15773
16026
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
15774
- const raw = await readFile19(path25.join(this.projectsDir, entry.name), "utf-8");
16027
+ const raw = await readFile19(path26.join(this.projectsDir, entry.name), "utf-8");
15775
16028
  const project = this.parseProject(raw);
15776
16029
  if (project) out.push(project);
15777
16030
  }
@@ -17272,7 +17525,7 @@ Best for:
17272
17525
  - Reviewing identity development over time`,
17273
17526
  parameters: Type.Object({}),
17274
17527
  async execute() {
17275
- const workspaceDir = path26.join(process.env.HOME ?? "~", ".openclaw", "workspace");
17528
+ const workspaceDir = path27.join(process.env.HOME ?? "~", ".openclaw", "workspace");
17276
17529
  const identity = await orchestrator.storage.readIdentity(workspaceDir);
17277
17530
  if (!identity) {
17278
17531
  return toolResult("No identity file found. Identity reflections build automatically through conversations when identityEnabled is true.");
@@ -17781,11 +18034,11 @@ mistakes: ${res.mistakesCount} patterns`
17781
18034
  }
17782
18035
 
17783
18036
  // src/cli.ts
17784
- import path42 from "path";
18037
+ import path43 from "path";
17785
18038
  import { access as access3, readFile as readFile30, readdir as readdir18, unlink as unlink5 } from "fs/promises";
17786
18039
 
17787
18040
  // src/transfer/export-json.ts
17788
- import path28 from "path";
18041
+ import path29 from "path";
17789
18042
  import { mkdir as mkdir21, readFile as readFile21 } from "fs/promises";
17790
18043
 
17791
18044
  // src/transfer/constants.ts
@@ -17795,7 +18048,7 @@ var EXPORT_SCHEMA_VERSION = 1;
17795
18048
  // src/transfer/fs-utils.ts
17796
18049
  import { createHash as createHash7 } from "crypto";
17797
18050
  import { mkdir as mkdir20, readdir as readdir12, readFile as readFile20, stat as stat6, writeFile as writeFile19 } from "fs/promises";
17798
- import path27 from "path";
18051
+ import path28 from "path";
17799
18052
  async function sha256File(filePath) {
17800
18053
  const buf = await readFile20(filePath);
17801
18054
  const sha256 = createHash7("sha256").update(buf).digest("hex");
@@ -17807,7 +18060,7 @@ function sha256String(content) {
17807
18060
  return { sha256, bytes: buf.byteLength };
17808
18061
  }
17809
18062
  async function writeJsonFile(filePath, value) {
17810
- await mkdir20(path27.dirname(filePath), { recursive: true });
18063
+ await mkdir20(path28.dirname(filePath), { recursive: true });
17811
18064
  await writeFile19(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
17812
18065
  }
17813
18066
  async function readJsonFile(filePath) {
@@ -17819,7 +18072,7 @@ async function listFilesRecursive(rootDir) {
17819
18072
  async function walk(dir) {
17820
18073
  const entries = await readdir12(dir, { withFileTypes: true });
17821
18074
  for (const ent of entries) {
17822
- const fp = path27.join(dir, ent.name);
18075
+ const fp = path28.join(dir, ent.name);
17823
18076
  if (ent.isDirectory()) {
17824
18077
  await walk(fp);
17825
18078
  } else if (ent.isFile()) {
@@ -17839,11 +18092,11 @@ async function fileExists(filePath) {
17839
18092
  }
17840
18093
  }
17841
18094
  function toPosixRelPath(absPath, rootDir) {
17842
- const rel = path27.relative(rootDir, absPath);
17843
- return rel.split(path27.sep).join("/");
18095
+ const rel = path28.relative(rootDir, absPath);
18096
+ return rel.split(path28.sep).join("/");
17844
18097
  }
17845
18098
  function fromPosixRelPath(relPath) {
17846
- return relPath.split("/").join(path27.sep);
18099
+ return relPath.split("/").join(path28.sep);
17847
18100
  }
17848
18101
 
17849
18102
  // src/transfer/export-json.ts
@@ -17859,9 +18112,9 @@ function shouldExclude(relPosix, includeTranscripts) {
17859
18112
  }
17860
18113
  async function exportJsonBundle(opts) {
17861
18114
  const includeTranscripts = opts.includeTranscripts === true;
17862
- const outDirAbs = path28.resolve(opts.outDir);
18115
+ const outDirAbs = path29.resolve(opts.outDir);
17863
18116
  await mkdir21(outDirAbs, { recursive: true });
17864
- const memoryDirAbs = path28.resolve(opts.memoryDir);
18117
+ const memoryDirAbs = path29.resolve(opts.memoryDir);
17865
18118
  const filesAbs = await listFilesRecursive(memoryDirAbs);
17866
18119
  const records = [];
17867
18120
  const manifestFiles = [];
@@ -17874,7 +18127,7 @@ async function exportJsonBundle(opts) {
17874
18127
  manifestFiles.push({ path: relPosix, sha256, bytes });
17875
18128
  }
17876
18129
  if (opts.includeWorkspaceIdentity !== false && opts.workspaceDir) {
17877
- const identityPath = path28.join(opts.workspaceDir, "IDENTITY.md");
18130
+ const identityPath = path29.join(opts.workspaceDir, "IDENTITY.md");
17878
18131
  try {
17879
18132
  const content = await readFile21(identityPath, "utf-8");
17880
18133
  const relPath = "workspace/IDENTITY.md";
@@ -17893,12 +18146,12 @@ async function exportJsonBundle(opts) {
17893
18146
  files: manifestFiles.sort((a, b) => a.path.localeCompare(b.path))
17894
18147
  };
17895
18148
  const bundle = { manifest, records };
17896
- await writeJsonFile(path28.join(outDirAbs, "manifest.json"), manifest);
17897
- await writeJsonFile(path28.join(outDirAbs, "bundle.json"), bundle);
18149
+ await writeJsonFile(path29.join(outDirAbs, "manifest.json"), manifest);
18150
+ await writeJsonFile(path29.join(outDirAbs, "bundle.json"), bundle);
17898
18151
  }
17899
18152
 
17900
18153
  // src/transfer/export-md.ts
17901
- import path29 from "path";
18154
+ import path30 from "path";
17902
18155
  import { mkdir as mkdir22, readFile as readFile22, writeFile as writeFile20 } from "fs/promises";
17903
18156
  function shouldExclude2(relPosix, includeTranscripts) {
17904
18157
  const parts = relPosix.split("/");
@@ -17907,16 +18160,16 @@ function shouldExclude2(relPosix, includeTranscripts) {
17907
18160
  }
17908
18161
  async function exportMarkdownBundle(opts) {
17909
18162
  const includeTranscripts = opts.includeTranscripts === true;
17910
- const outDirAbs = path29.resolve(opts.outDir);
18163
+ const outDirAbs = path30.resolve(opts.outDir);
17911
18164
  await mkdir22(outDirAbs, { recursive: true });
17912
- const memDirAbs = path29.resolve(opts.memoryDir);
18165
+ const memDirAbs = path30.resolve(opts.memoryDir);
17913
18166
  const filesAbs = await listFilesRecursive(memDirAbs);
17914
18167
  const manifestFiles = [];
17915
18168
  for (const abs of filesAbs) {
17916
18169
  const relPosix = toPosixRelPath(abs, memDirAbs);
17917
18170
  if (shouldExclude2(relPosix, includeTranscripts)) continue;
17918
- const dstAbs = path29.join(outDirAbs, ...relPosix.split("/"));
17919
- await mkdir22(path29.dirname(dstAbs), { recursive: true });
18171
+ const dstAbs = path30.join(outDirAbs, ...relPosix.split("/"));
18172
+ await mkdir22(path30.dirname(dstAbs), { recursive: true });
17920
18173
  const content = await readFile22(abs);
17921
18174
  await writeFile20(dstAbs, content);
17922
18175
  const { sha256, bytes } = await sha256File(abs);
@@ -17930,12 +18183,12 @@ async function exportMarkdownBundle(opts) {
17930
18183
  includesTranscripts: includeTranscripts,
17931
18184
  files: manifestFiles.sort((a, b) => a.path.localeCompare(b.path))
17932
18185
  };
17933
- await writeJsonFile(path29.join(outDirAbs, "manifest.json"), manifest);
18186
+ await writeJsonFile(path30.join(outDirAbs, "manifest.json"), manifest);
17934
18187
  }
17935
18188
  async function looksLikeEngramMdExport(fromDir) {
17936
- const dirAbs = path29.resolve(fromDir);
18189
+ const dirAbs = path30.resolve(fromDir);
17937
18190
  try {
17938
- const raw = await readFile22(path29.join(dirAbs, "manifest.json"), "utf-8");
18191
+ const raw = await readFile22(path30.join(dirAbs, "manifest.json"), "utf-8");
17939
18192
  const parsed = JSON.parse(raw);
17940
18193
  return parsed.format === EXPORT_FORMAT && parsed.schemaVersion === EXPORT_SCHEMA_VERSION;
17941
18194
  } catch {
@@ -17944,16 +18197,16 @@ async function looksLikeEngramMdExport(fromDir) {
17944
18197
  }
17945
18198
 
17946
18199
  // src/transfer/backup.ts
17947
- import path30 from "path";
18200
+ import path31 from "path";
17948
18201
  import { mkdir as mkdir23, readdir as readdir13, rm as rm4 } from "fs/promises";
17949
18202
  function timestampDirName(now) {
17950
18203
  return now.toISOString().replace(/[:.]/g, "-");
17951
18204
  }
17952
18205
  async function backupMemoryDir(opts) {
17953
- const outDirAbs = path30.resolve(opts.outDir);
18206
+ const outDirAbs = path31.resolve(opts.outDir);
17954
18207
  await mkdir23(outDirAbs, { recursive: true });
17955
18208
  const ts = timestampDirName(/* @__PURE__ */ new Date());
17956
- const backupDir = path30.join(outDirAbs, ts);
18209
+ const backupDir = path31.join(outDirAbs, ts);
17957
18210
  await exportMarkdownBundle({
17958
18211
  memoryDir: opts.memoryDir,
17959
18212
  outDir: backupDir,
@@ -17978,13 +18231,13 @@ async function enforceRetention(outDirAbs, retentionDays) {
17978
18231
  const tsMs = iso ? Date.parse(iso) : NaN;
17979
18232
  if (!Number.isFinite(tsMs)) continue;
17980
18233
  if (tsMs < cutoffMs) {
17981
- await rm4(path30.join(outDirAbs, name), { recursive: true, force: true });
18234
+ await rm4(path31.join(outDirAbs, name), { recursive: true, force: true });
17982
18235
  }
17983
18236
  }
17984
18237
  }
17985
18238
 
17986
18239
  // src/transfer/export-sqlite.ts
17987
- import path31 from "path";
18240
+ import path32 from "path";
17988
18241
  import Database from "better-sqlite3";
17989
18242
  import { readFile as readFile23 } from "fs/promises";
17990
18243
 
@@ -18012,8 +18265,8 @@ function shouldExclude3(relPosix, includeTranscripts) {
18012
18265
  }
18013
18266
  async function exportSqlite(opts) {
18014
18267
  const includeTranscripts = opts.includeTranscripts === true;
18015
- const memDirAbs = path31.resolve(opts.memoryDir);
18016
- const outAbs = path31.resolve(opts.outFile);
18268
+ const memDirAbs = path32.resolve(opts.memoryDir);
18269
+ const outAbs = path32.resolve(opts.outFile);
18017
18270
  const filesAbs = await listFilesRecursive(memDirAbs);
18018
18271
  const db = new Database(outAbs);
18019
18272
  try {
@@ -18045,7 +18298,7 @@ async function exportSqlite(opts) {
18045
18298
  }
18046
18299
 
18047
18300
  // src/transfer/import-json.ts
18048
- import path32 from "path";
18301
+ import path33 from "path";
18049
18302
  import { mkdir as mkdir24, writeFile as writeFile21 } from "fs/promises";
18050
18303
 
18051
18304
  // src/transfer/types.ts
@@ -18079,21 +18332,21 @@ function normalizeForDedupe(s) {
18079
18332
  }
18080
18333
  async function importJsonBundle(opts) {
18081
18334
  const conflict = opts.conflict ?? "skip";
18082
- const fromDirAbs = path32.resolve(opts.fromDir);
18083
- const bundlePath = path32.join(fromDirAbs, "bundle.json");
18335
+ const fromDirAbs = path33.resolve(opts.fromDir);
18336
+ const bundlePath = path33.join(fromDirAbs, "bundle.json");
18084
18337
  const bundle = ExportBundleV1Schema.parse(await readJsonFile(bundlePath));
18085
- const memDirAbs = path32.resolve(opts.targetMemoryDir);
18338
+ const memDirAbs = path33.resolve(opts.targetMemoryDir);
18086
18339
  const written = [];
18087
18340
  let skipped = 0;
18088
18341
  for (const rec of bundle.records) {
18089
18342
  const isWorkspace = rec.path.startsWith("workspace/");
18090
- const targetBase = isWorkspace ? opts.workspaceDir ? path32.resolve(opts.workspaceDir) : null : memDirAbs;
18343
+ const targetBase = isWorkspace ? opts.workspaceDir ? path33.resolve(opts.workspaceDir) : null : memDirAbs;
18091
18344
  if (isWorkspace && !targetBase) {
18092
18345
  skipped += 1;
18093
18346
  continue;
18094
18347
  }
18095
18348
  const relFs = fromPosixRelPath(isWorkspace ? rec.path.replace(/^workspace\//, "") : rec.path);
18096
- const absTarget = path32.join(targetBase, relFs);
18349
+ const absTarget = path33.join(targetBase, relFs);
18097
18350
  const exists3 = await fileExists(absTarget);
18098
18351
  if (exists3) {
18099
18352
  if (conflict === "skip") {
@@ -18117,21 +18370,21 @@ async function importJsonBundle(opts) {
18117
18370
  return { written: 0, skipped };
18118
18371
  }
18119
18372
  for (const w of written) {
18120
- await mkdir24(path32.dirname(w.abs), { recursive: true });
18373
+ await mkdir24(path33.dirname(w.abs), { recursive: true });
18121
18374
  await writeFile21(w.abs, w.content, "utf-8");
18122
18375
  }
18123
18376
  return { written: written.length, skipped };
18124
18377
  }
18125
18378
  function looksLikeEngramJsonExport(fromDir) {
18126
- const dir = path32.resolve(fromDir);
18379
+ const dir = path33.resolve(fromDir);
18127
18380
  return Promise.all([
18128
- fileExists(path32.join(dir, "manifest.json")),
18129
- fileExists(path32.join(dir, "bundle.json"))
18381
+ fileExists(path33.join(dir, "manifest.json")),
18382
+ fileExists(path33.join(dir, "bundle.json"))
18130
18383
  ]).then(([m, b]) => m && b);
18131
18384
  }
18132
18385
 
18133
18386
  // src/transfer/import-sqlite.ts
18134
- import path33 from "path";
18387
+ import path34 from "path";
18135
18388
  import Database2 from "better-sqlite3";
18136
18389
  import { mkdir as mkdir25, writeFile as writeFile22 } from "fs/promises";
18137
18390
  function normalizeForDedupe2(s) {
@@ -18139,8 +18392,8 @@ function normalizeForDedupe2(s) {
18139
18392
  }
18140
18393
  async function importSqlite(opts) {
18141
18394
  const conflict = opts.conflict ?? "skip";
18142
- const memDirAbs = path33.resolve(opts.targetMemoryDir);
18143
- const fromAbs = path33.resolve(opts.fromFile);
18395
+ const memDirAbs = path34.resolve(opts.targetMemoryDir);
18396
+ const fromAbs = path34.resolve(opts.fromFile);
18144
18397
  const db = new Database2(fromAbs, { readonly: true });
18145
18398
  const written = [];
18146
18399
  let skipped = 0;
@@ -18153,7 +18406,7 @@ async function importSqlite(opts) {
18153
18406
  const rows = db.prepare("SELECT path_rel, content FROM files").all();
18154
18407
  for (const r of rows) {
18155
18408
  const relFs = fromPosixRelPath(r.path_rel);
18156
- const absTarget = path33.join(memDirAbs, relFs);
18409
+ const absTarget = path34.join(memDirAbs, relFs);
18157
18410
  const exists3 = await fileExists(absTarget);
18158
18411
  if (exists3) {
18159
18412
  if (conflict === "skip") {
@@ -18178,29 +18431,29 @@ async function importSqlite(opts) {
18178
18431
  }
18179
18432
  if (opts.dryRun) return { written: 0, skipped };
18180
18433
  for (const w of written) {
18181
- await mkdir25(path33.dirname(w.abs), { recursive: true });
18434
+ await mkdir25(path34.dirname(w.abs), { recursive: true });
18182
18435
  await writeFile22(w.abs, w.content, "utf-8");
18183
18436
  }
18184
18437
  return { written: written.length, skipped };
18185
18438
  }
18186
18439
 
18187
18440
  // src/transfer/import-md.ts
18188
- import path34 from "path";
18441
+ import path35 from "path";
18189
18442
  import { mkdir as mkdir26, readFile as readFile24, writeFile as writeFile23 } from "fs/promises";
18190
18443
  function normalizeForDedupe3(s) {
18191
18444
  return s.replace(/\s+/g, " ").trim();
18192
18445
  }
18193
18446
  async function importMarkdownBundle(opts) {
18194
18447
  const conflict = opts.conflict ?? "skip";
18195
- const fromAbs = path34.resolve(opts.fromDir);
18196
- const targetAbs = path34.resolve(opts.targetMemoryDir);
18448
+ const fromAbs = path35.resolve(opts.fromDir);
18449
+ const targetAbs = path35.resolve(opts.targetMemoryDir);
18197
18450
  const filesAbs = await listFilesRecursive(fromAbs);
18198
18451
  const writes = [];
18199
18452
  let skipped = 0;
18200
18453
  for (const abs of filesAbs) {
18201
18454
  const relPosix = toPosixRelPath(abs, fromAbs);
18202
18455
  if (relPosix === "manifest.json") continue;
18203
- const dstAbs = path34.join(targetAbs, fromPosixRelPath(relPosix));
18456
+ const dstAbs = path35.join(targetAbs, fromPosixRelPath(relPosix));
18204
18457
  const content = await readFile24(abs, "utf-8");
18205
18458
  const exists3 = await fileExists(dstAbs);
18206
18459
  if (exists3) {
@@ -18223,17 +18476,17 @@ async function importMarkdownBundle(opts) {
18223
18476
  }
18224
18477
  if (opts.dryRun) return { written: 0, skipped };
18225
18478
  for (const w of writes) {
18226
- await mkdir26(path34.dirname(w.abs), { recursive: true });
18479
+ await mkdir26(path35.dirname(w.abs), { recursive: true });
18227
18480
  await writeFile23(w.abs, w.content, "utf-8");
18228
18481
  }
18229
18482
  return { written: writes.length, skipped };
18230
18483
  }
18231
18484
 
18232
18485
  // src/transfer/autodetect.ts
18233
- import path35 from "path";
18486
+ import path36 from "path";
18234
18487
  import { stat as stat7 } from "fs/promises";
18235
18488
  async function detectImportFormat(fromPath) {
18236
- const abs = path35.resolve(fromPath);
18489
+ const abs = path36.resolve(fromPath);
18237
18490
  let st;
18238
18491
  try {
18239
18492
  st = await stat7(abs);
@@ -18661,8 +18914,8 @@ function gatherCandidates(input, warnings) {
18661
18914
  const record = rec;
18662
18915
  const content = typeof record.content === "string" ? record.content : null;
18663
18916
  if (!content) continue;
18664
- const path44 = typeof record.path === "string" ? record.path : "";
18665
- if (!path44.startsWith("transcripts/") && !path44.includes("/transcripts/")) continue;
18917
+ const path45 = typeof record.path === "string" ? record.path : "";
18918
+ if (!path45.startsWith("transcripts/") && !path45.includes("/transcripts/")) continue;
18666
18919
  rows.push(...parseJsonl(content, warnings));
18667
18920
  }
18668
18921
  return rows;
@@ -18718,7 +18971,7 @@ var openclawReplayNormalizer = {
18718
18971
  };
18719
18972
 
18720
18973
  // src/maintenance/archive-observations.ts
18721
- import path36 from "path";
18974
+ import path37 from "path";
18722
18975
  import { mkdir as mkdir27, readdir as readdir14, readFile as readFile25, unlink as unlink4, writeFile as writeFile24 } from "fs/promises";
18723
18976
  var DATE_FILE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})\.(jsonl|md)$/;
18724
18977
  function normalizeRetentionDays(value) {
@@ -18745,8 +18998,8 @@ async function listFilesRecursive2(root, relPrefix = "") {
18745
18998
  return out;
18746
18999
  }
18747
19000
  for (const entry of entries) {
18748
- const rel = relPrefix ? path36.join(relPrefix, entry.name) : entry.name;
18749
- const full = path36.join(root, entry.name);
19001
+ const rel = relPrefix ? path37.join(relPrefix, entry.name) : entry.name;
19002
+ const full = path37.join(root, entry.name);
18750
19003
  if (entry.isDirectory()) {
18751
19004
  out.push(...await listFilesRecursive2(full, rel));
18752
19005
  continue;
@@ -18756,19 +19009,19 @@ async function listFilesRecursive2(root, relPrefix = "") {
18756
19009
  return out;
18757
19010
  }
18758
19011
  async function collectArchiveCandidates(memoryDir, cutoffTimeMs) {
18759
- const roots = ["transcripts", path36.join("state", "tool-usage"), path36.join("summaries", "hourly")];
19012
+ const roots = ["transcripts", path37.join("state", "tool-usage"), path37.join("summaries", "hourly")];
18760
19013
  const out = [];
18761
19014
  for (const relRoot of roots) {
18762
- const absRoot = path36.join(memoryDir, relRoot);
19015
+ const absRoot = path37.join(memoryDir, relRoot);
18763
19016
  const files = await listFilesRecursive2(absRoot);
18764
19017
  for (const fileRel of files) {
18765
- const filename = path36.basename(fileRel);
19018
+ const filename = path37.basename(fileRel);
18766
19019
  const parsedDate = extractDateFromFilename(filename);
18767
19020
  if (!parsedDate) continue;
18768
19021
  if (parsedDate.getTime() >= cutoffTimeMs) continue;
18769
19022
  out.push({
18770
- absolutePath: path36.join(absRoot, fileRel),
18771
- relativePath: path36.join(relRoot, fileRel)
19023
+ absolutePath: path37.join(absRoot, fileRel),
19024
+ relativePath: path37.join(relRoot, fileRel)
18772
19025
  });
18773
19026
  }
18774
19027
  }
@@ -18783,7 +19036,7 @@ async function archiveObservations(options) {
18783
19036
  new Date(now.getTime() - retentionDays * 24 * 60 * 60 * 1e3)
18784
19037
  );
18785
19038
  const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
18786
- const archiveRoot = path36.join(options.memoryDir, "archive", "observations", stamp);
19039
+ const archiveRoot = path37.join(options.memoryDir, "archive", "observations", stamp);
18787
19040
  const candidates = retentionDays === 0 ? [] : await collectArchiveCandidates(
18788
19041
  options.memoryDir,
18789
19042
  cutoffDayStartUtc
@@ -18794,8 +19047,8 @@ async function archiveObservations(options) {
18794
19047
  if (!dryRun && candidates.length > 0) {
18795
19048
  await mkdir27(archiveRoot, { recursive: true });
18796
19049
  for (const candidate of candidates) {
18797
- const archivePath = path36.join(archiveRoot, candidate.relativePath);
18798
- const archiveDir = path36.dirname(archivePath);
19050
+ const archivePath = path37.join(archiveRoot, candidate.relativePath);
19051
+ const archiveDir = path37.dirname(archivePath);
18799
19052
  await mkdir27(archiveDir, { recursive: true });
18800
19053
  const raw = await readFile25(candidate.absolutePath);
18801
19054
  await writeFile24(archivePath, raw);
@@ -18819,11 +19072,11 @@ async function archiveObservations(options) {
18819
19072
  }
18820
19073
 
18821
19074
  // src/maintenance/rebuild-observations.ts
18822
- import path38 from "path";
19075
+ import path39 from "path";
18823
19076
  import { readdir as readdir15, readFile as readFile27 } from "fs/promises";
18824
19077
 
18825
19078
  // src/maintenance/observation-ledger-utils.ts
18826
- import path37 from "path";
19079
+ import path38 from "path";
18827
19080
  import { mkdir as mkdir28, readFile as readFile26, writeFile as writeFile25 } from "fs/promises";
18828
19081
  function toHourBucketIso(timestamp) {
18829
19082
  const normalized = /(?:Z|[+-]\d{2}:\d{2})$/u.test(timestamp) ? timestamp : `${timestamp}Z`;
@@ -18835,8 +19088,8 @@ function toHourBucketIso(timestamp) {
18835
19088
  }
18836
19089
  async function backupAndWriteRebuiltObservations(options) {
18837
19090
  const stamp = options.now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
18838
- const archiveRoot = path37.join(options.memoryDir, "archive", "observations", stamp);
18839
- let backupPath = path37.join(
19091
+ const archiveRoot = path38.join(options.memoryDir, "archive", "observations", stamp);
19092
+ let backupPath = path38.join(
18840
19093
  archiveRoot,
18841
19094
  "state",
18842
19095
  "observation-ledger",
@@ -18844,7 +19097,7 @@ async function backupAndWriteRebuiltObservations(options) {
18844
19097
  );
18845
19098
  try {
18846
19099
  const existing = await readFile26(options.outputPath, "utf-8");
18847
- await mkdir28(path37.dirname(backupPath), { recursive: true });
19100
+ await mkdir28(path38.dirname(backupPath), { recursive: true });
18848
19101
  await writeFile25(backupPath, existing, "utf-8");
18849
19102
  } catch (err) {
18850
19103
  const code = err.code;
@@ -18861,7 +19114,7 @@ async function backupAndWriteRebuiltObservations(options) {
18861
19114
  rebuiltAt
18862
19115
  })
18863
19116
  );
18864
- await mkdir28(path37.dirname(options.outputPath), { recursive: true });
19117
+ await mkdir28(path38.dirname(options.outputPath), { recursive: true });
18865
19118
  await writeFile25(options.outputPath, lines.length > 0 ? `${lines.join("\n")}
18866
19119
  ` : "", "utf-8");
18867
19120
  return backupPath;
@@ -18884,7 +19137,7 @@ async function listTranscriptFiles(root) {
18884
19137
  for (const entry of entries) {
18885
19138
  if (entry.name === "." || entry.name === "..") continue;
18886
19139
  if (entry.isSymbolicLink()) continue;
18887
- const full = path38.join(root, entry.name);
19140
+ const full = path39.join(root, entry.name);
18888
19141
  if (entry.isDirectory()) {
18889
19142
  out.push(...await listTranscriptFiles(full));
18890
19143
  continue;
@@ -18944,8 +19197,8 @@ function buildLedgerRows(linesByFile) {
18944
19197
  async function rebuildObservations(options) {
18945
19198
  const dryRun = options.dryRun !== false;
18946
19199
  const now = options.now ?? /* @__PURE__ */ new Date();
18947
- const transcriptsRoot = path38.join(options.memoryDir, "transcripts");
18948
- const outputPath = path38.join(
19200
+ const transcriptsRoot = path39.join(options.memoryDir, "transcripts");
19201
+ const outputPath = path39.join(
18949
19202
  options.memoryDir,
18950
19203
  "state",
18951
19204
  "observation-ledger",
@@ -18981,7 +19234,7 @@ async function rebuildObservations(options) {
18981
19234
  }
18982
19235
 
18983
19236
  // src/maintenance/migrate-observations.ts
18984
- import path39 from "path";
19237
+ import path40 from "path";
18985
19238
  import { readdir as readdir16, readFile as readFile28 } from "fs/promises";
18986
19239
  function toNonNegativeInt(value) {
18987
19240
  if (typeof value !== "number" || !Number.isFinite(value)) return null;
@@ -19045,15 +19298,15 @@ async function listLegacyObservationFiles(root) {
19045
19298
  async function migrateObservations(options) {
19046
19299
  const dryRun = options.dryRun !== false;
19047
19300
  const now = options.now ?? /* @__PURE__ */ new Date();
19048
- const ledgerRoot = path39.join(options.memoryDir, "state", "observation-ledger");
19049
- const outputPath = path39.join(ledgerRoot, "rebuilt-observations.jsonl");
19301
+ const ledgerRoot = path40.join(options.memoryDir, "state", "observation-ledger");
19302
+ const outputPath = path40.join(ledgerRoot, "rebuilt-observations.jsonl");
19050
19303
  const legacyFiles = await listLegacyObservationFiles(ledgerRoot);
19051
- const sourceRelativePaths = legacyFiles.map((name) => path39.join("state", "observation-ledger", name));
19304
+ const sourceRelativePaths = legacyFiles.map((name) => path40.join("state", "observation-ledger", name));
19052
19305
  const byKey = /* @__PURE__ */ new Map();
19053
19306
  let parsedRows = 0;
19054
19307
  let malformedLines = 0;
19055
19308
  for (const file of legacyFiles) {
19056
- const full = path39.join(ledgerRoot, file);
19309
+ const full = path40.join(ledgerRoot, file);
19057
19310
  const raw = await readFile28(full, "utf-8");
19058
19311
  for (const line of raw.split("\n")) {
19059
19312
  if (!line.trim()) continue;
@@ -19132,7 +19385,7 @@ async function migrateObservations(options) {
19132
19385
 
19133
19386
  // src/network/tailscale.ts
19134
19387
  import { stat as stat8 } from "fs/promises";
19135
- import { spawn as spawn2 } from "child_process";
19388
+ import { spawn as spawn3 } from "child_process";
19136
19389
  var TailscaleHelper = class {
19137
19390
  tailscaleBinary;
19138
19391
  rsyncBinary;
@@ -19203,7 +19456,7 @@ async function assertReadableDirectory(dir) {
19203
19456
  }
19204
19457
  var defaultCommandRunner = (command, args, options) => {
19205
19458
  return new Promise((resolve) => {
19206
- const child = spawn2(command, args, {
19459
+ const child = spawn3(command, args, {
19207
19460
  stdio: ["ignore", "pipe", "pipe"]
19208
19461
  });
19209
19462
  let stdout = "";
@@ -19246,7 +19499,7 @@ import { createReadStream } from "fs";
19246
19499
  import { mkdir as mkdir29, readdir as readdir17, realpath as realpath2, stat as stat9 } from "fs/promises";
19247
19500
  import { createServer } from "http";
19248
19501
  import { timingSafeEqual } from "crypto";
19249
- import path40 from "path";
19502
+ import path41 from "path";
19250
19503
  import { pipeline } from "stream/promises";
19251
19504
  import { URL as URL2 } from "url";
19252
19505
  function hostToUrlAuthority(host) {
@@ -19282,10 +19535,10 @@ var WebDavServer = class _WebDavServer {
19282
19535
  const allowedRoots = [];
19283
19536
  const aliasSet = /* @__PURE__ */ new Set();
19284
19537
  for (const dir of options.allowlistDirs) {
19285
- const resolved = path40.resolve(dir);
19538
+ const resolved = path41.resolve(dir);
19286
19539
  await mkdir29(resolved, { recursive: true });
19287
19540
  const canonical = await realpath2(resolved);
19288
- const alias = path40.basename(canonical) || "root";
19541
+ const alias = path41.basename(canonical) || "root";
19289
19542
  if (aliasSet.has(alias)) {
19290
19543
  throw new Error(`duplicate webdav allowlist alias: ${alias}`);
19291
19544
  }
@@ -19441,7 +19694,7 @@ var WebDavServer = class _WebDavServer {
19441
19694
  if (decodedPath.includes("\0")) {
19442
19695
  return { ok: false, code: 400, message: "invalid path" };
19443
19696
  }
19444
- const normalized = path40.posix.normalize(decodedPath);
19697
+ const normalized = path41.posix.normalize(decodedPath);
19445
19698
  const segments = normalized.split("/").filter((segment) => segment.length > 0);
19446
19699
  if (segments.length === 0) {
19447
19700
  return { ok: false, code: 403, message: "root listing is not allowed" };
@@ -19455,7 +19708,7 @@ var WebDavServer = class _WebDavServer {
19455
19708
  if (relative.some((segment) => segment === ".." || segment.includes("\\"))) {
19456
19709
  return { ok: false, code: 403, message: "path traversal is not allowed" };
19457
19710
  }
19458
- const candidate = path40.resolve(root.absolute, ...relative);
19711
+ const candidate = path41.resolve(root.absolute, ...relative);
19459
19712
  if (!this.isPathInside(root.absolute, candidate)) {
19460
19713
  return { ok: false, code: 403, message: "path escaped allowlist" };
19461
19714
  }
@@ -19533,10 +19786,10 @@ var WebDavServer = class _WebDavServer {
19533
19786
  }
19534
19787
  isPathInside(root, target) {
19535
19788
  if (target === root) return true;
19536
- if (root === path40.parse(root).root) {
19789
+ if (root === path41.parse(root).root) {
19537
19790
  return target.startsWith(root);
19538
19791
  }
19539
- return target.startsWith(`${root}${path40.sep}`);
19792
+ return target.startsWith(`${root}${path41.sep}`);
19540
19793
  }
19541
19794
  };
19542
19795
  function xmlEscape(value) {
@@ -19548,8 +19801,8 @@ function toEncodedHref(pathname) {
19548
19801
 
19549
19802
  // src/compat/checks.ts
19550
19803
  import { access as access2, readFile as readFile29 } from "fs/promises";
19551
- import path41 from "path";
19552
- import { spawn as spawn3 } from "child_process";
19804
+ import path42 from "path";
19805
+ import { spawn as spawn4 } from "child_process";
19553
19806
  var REQUIRED_HOOKS = ["before_agent_start", "agent_end"];
19554
19807
  function isSafeCommandToken(command) {
19555
19808
  return /^[a-zA-Z0-9._-]+$/.test(command);
@@ -19560,7 +19813,7 @@ var defaultRunner = {
19560
19813
  const binary = process.platform === "win32" ? "where" : "which";
19561
19814
  const args = [command];
19562
19815
  return new Promise((resolve) => {
19563
- const child = spawn3(binary, args, { stdio: "ignore" });
19816
+ const child = spawn4(binary, args, { stdio: "ignore" });
19564
19817
  child.on("error", () => resolve(false));
19565
19818
  child.on("close", (code) => resolve(code === 0));
19566
19819
  });
@@ -19730,9 +19983,9 @@ function compareVersions(a, b) {
19730
19983
  async function runCompatChecks(options) {
19731
19984
  const checks = [];
19732
19985
  const runner = options.runner ?? defaultRunner;
19733
- const pluginJsonPath = path41.join(options.repoRoot, "openclaw.plugin.json");
19734
- const packageJsonPath = path41.join(options.repoRoot, "package.json");
19735
- const indexPath = path41.join(options.repoRoot, "src", "index.ts");
19986
+ const pluginJsonPath = path42.join(options.repoRoot, "openclaw.plugin.json");
19987
+ const packageJsonPath = path42.join(options.repoRoot, "package.json");
19988
+ const indexPath = path42.join(options.repoRoot, "src", "index.ts");
19736
19989
  let pluginRaw = "";
19737
19990
  let pluginManifestPresent = false;
19738
19991
  try {
@@ -20392,14 +20645,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
20392
20645
  const ns = (namespace ?? "").trim();
20393
20646
  if (!ns) return orchestrator.config.memoryDir;
20394
20647
  if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
20395
- const candidate = path42.join(orchestrator.config.memoryDir, "namespaces", ns);
20648
+ const candidate = path43.join(orchestrator.config.memoryDir, "namespaces", ns);
20396
20649
  if (ns === orchestrator.config.defaultNamespace) {
20397
20650
  return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
20398
20651
  }
20399
20652
  return candidate;
20400
20653
  }
20401
20654
  async function readAllMemoryFiles(memoryDir) {
20402
- const roots = [path42.join(memoryDir, "facts"), path42.join(memoryDir, "corrections")];
20655
+ const roots = [path43.join(memoryDir, "facts"), path43.join(memoryDir, "corrections")];
20403
20656
  const out = [];
20404
20657
  const walk = async (dir) => {
20405
20658
  let entries;
@@ -20410,7 +20663,7 @@ async function readAllMemoryFiles(memoryDir) {
20410
20663
  }
20411
20664
  for (const entry of entries) {
20412
20665
  const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
20413
- const fullPath = path42.join(dir, entryName);
20666
+ const fullPath = path43.join(dir, entryName);
20414
20667
  if (entry.isDirectory()) {
20415
20668
  await walk(fullPath);
20416
20669
  continue;
@@ -21182,7 +21435,7 @@ function registerCli(api, orchestrator) {
21182
21435
  }
21183
21436
  });
21184
21437
  cmd.command("identity").description("Show agent identity reflections").action(async () => {
21185
- const workspaceDir = path42.join(process.env.HOME ?? "~", ".openclaw", "workspace");
21438
+ const workspaceDir = path43.join(process.env.HOME ?? "~", ".openclaw", "workspace");
21186
21439
  const identity = await orchestrator.storage.readIdentity(workspaceDir);
21187
21440
  if (!identity) {
21188
21441
  console.log("No identity file found.");
@@ -21405,8 +21658,8 @@ function registerCli(api, orchestrator) {
21405
21658
  const options = args[0] ?? {};
21406
21659
  const threadId = options.thread;
21407
21660
  const top = parseInt(options.top ?? "10", 10);
21408
- const memoryDir = path42.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
21409
- const threading = new ThreadingManager(path42.join(memoryDir, "threads"));
21661
+ const memoryDir = path43.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
21662
+ const threading = new ThreadingManager(path43.join(memoryDir, "threads"));
21410
21663
  if (threadId) {
21411
21664
  const thread = await threading.loadThread(threadId);
21412
21665
  if (!thread) {
@@ -21581,14 +21834,14 @@ function parseDuration(duration) {
21581
21834
  // src/index.ts
21582
21835
  import { readFile as readFile31, writeFile as writeFile26 } from "fs/promises";
21583
21836
  import { readFileSync as readFileSync4 } from "fs";
21584
- import path43 from "path";
21837
+ import path44 from "path";
21585
21838
  import os5 from "os";
21586
21839
  var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
21587
21840
  function loadPluginConfigFromFile() {
21588
21841
  try {
21589
21842
  const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
21590
21843
  const homeDir = process.env.HOME ?? os5.homedir();
21591
- const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path43.join(homeDir, ".openclaw", "openclaw.json");
21844
+ const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path44.join(homeDir, ".openclaw", "openclaw.json");
21592
21845
  const content = readFileSync4(configPath, "utf-8");
21593
21846
  const config = JSON.parse(content);
21594
21847
  const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
@@ -21796,7 +22049,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
21796
22049
  );
21797
22050
  async function ensureHourlySummaryCron(api2) {
21798
22051
  const jobId = "engram-hourly-summary";
21799
- const cronFilePath = path43.join(os5.homedir(), ".openclaw", "cron", "jobs.json");
22052
+ const cronFilePath = path44.join(os5.homedir(), ".openclaw", "cron", "jobs.json");
21800
22053
  try {
21801
22054
  let jobsData = { version: 1, jobs: [] };
21802
22055
  try {