@joshuaswarren/openclaw-engram 8.3.54 → 8.3.56
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 +557 -243
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -425,9 +425,9 @@ function parseConfig(raw) {
|
|
|
425
425
|
}
|
|
426
426
|
|
|
427
427
|
// src/orchestrator.ts
|
|
428
|
-
import
|
|
428
|
+
import path25 from "path";
|
|
429
429
|
import { createHash as createHash5 } from "crypto";
|
|
430
|
-
import { mkdir as mkdir18, readFile as readFile18, writeFile as writeFile17 } from "fs/promises";
|
|
430
|
+
import { mkdir as mkdir18, readdir as readdir11, readFile as readFile18, writeFile as writeFile17 } from "fs/promises";
|
|
431
431
|
|
|
432
432
|
// src/signal.ts
|
|
433
433
|
var BUILTIN_HIGH_PATTERNS = [
|
|
@@ -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/
|
|
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 =
|
|
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
|
|
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
|
|
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 :
|
|
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 =
|
|
11099
|
-
this.prioritiesInboxPath =
|
|
11100
|
-
this.outputsDir =
|
|
11101
|
-
this.roundtableDir =
|
|
11102
|
-
this.feedbackDir =
|
|
11103
|
-
this.feedbackInboxPath =
|
|
11104
|
-
this.crossSignalsDir =
|
|
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(
|
|
11121
|
-
await mkdir15(
|
|
11122
|
-
await mkdir15(
|
|
11123
|
-
await mkdir15(
|
|
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] ?
|
|
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 =
|
|
11395
|
+
const dir = path22.join(this.outputsDir, opts.agentId, date);
|
|
11180
11396
|
await mkdir15(dir, { recursive: true });
|
|
11181
|
-
const fp =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
11320
|
-
this.mistakesPath =
|
|
11321
|
-
this.feedbackInboxPath =
|
|
11322
|
-
this.identityAnchorPath =
|
|
11323
|
-
this.identityIncidentsDir =
|
|
11324
|
-
this.identityAuditWeeklyDir =
|
|
11325
|
-
this.identityAuditMonthlyDir =
|
|
11326
|
-
this.identityImprovementLoopsPath =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
11572
|
-
const monthlyPath =
|
|
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
|
|
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 =
|
|
11708
|
-
const defaultPath =
|
|
11709
|
-
if (
|
|
11710
|
-
const absolute =
|
|
11711
|
-
return absolute.startsWith(root +
|
|
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 =
|
|
11714
|
-
return resolved.startsWith(root +
|
|
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 =
|
|
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(
|
|
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(
|
|
11889
|
-
const canonicalStatePath =
|
|
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(
|
|
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 =
|
|
11911
|
-
const normalizedCandidate =
|
|
12126
|
+
const normalizedRoot = path24.resolve(root);
|
|
12127
|
+
const normalizedCandidate = path24.resolve(candidate);
|
|
11912
12128
|
if (normalizedCandidate === normalizedRoot) return true;
|
|
11913
|
-
if (normalizedRoot ===
|
|
12129
|
+
if (normalizedRoot === path24.parse(normalizedRoot).root) {
|
|
11914
12130
|
return normalizedCandidate.startsWith(normalizedRoot);
|
|
11915
12131
|
}
|
|
11916
|
-
return normalizedCandidate.startsWith(`${normalizedRoot}${
|
|
12132
|
+
return normalizedCandidate.startsWith(`${normalizedRoot}${path24.sep}`);
|
|
11917
12133
|
}
|
|
11918
12134
|
async canonicalizePathWithoutCreating(targetPath) {
|
|
11919
|
-
const absoluteTarget =
|
|
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 =
|
|
11925
|
-
return
|
|
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 =
|
|
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 =
|
|
12121
|
-
const rel =
|
|
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(
|
|
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,
|
|
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:
|
|
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
|
|
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
|
|
12416
|
+
return path25.join("facts", day, `${options.memoryId}.md`);
|
|
12201
12417
|
}
|
|
12202
|
-
return
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
12524
|
-
const base =
|
|
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 =
|
|
12550
|
-
await mkdir18(
|
|
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 =
|
|
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,85 @@ 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
|
+
}
|
|
12921
|
+
async countConversationChunkDocs(dir) {
|
|
12922
|
+
try {
|
|
12923
|
+
const entries = await readdir11(dir, { withFileTypes: true });
|
|
12924
|
+
let total = 0;
|
|
12925
|
+
for (const entry of entries) {
|
|
12926
|
+
const fullPath = path25.join(dir, entry.name);
|
|
12927
|
+
if (entry.isDirectory()) {
|
|
12928
|
+
total += await this.countConversationChunkDocs(fullPath);
|
|
12929
|
+
continue;
|
|
12930
|
+
}
|
|
12931
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
12932
|
+
total += 1;
|
|
12933
|
+
}
|
|
12934
|
+
}
|
|
12935
|
+
return total;
|
|
12936
|
+
} catch {
|
|
12937
|
+
return 0;
|
|
12938
|
+
}
|
|
12939
|
+
}
|
|
12940
|
+
async getConversationIndexHealth() {
|
|
12941
|
+
const chunkDocCount = await this.countConversationChunkDocs(this.conversationIndexDir);
|
|
12942
|
+
const lastUpdateAtMs = Math.max(0, ...this.conversationIndexLastUpdateAtMs.values());
|
|
12943
|
+
const lastUpdateAt = lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
|
|
12944
|
+
if (!this.config.conversationIndexEnabled) {
|
|
12945
|
+
return {
|
|
12946
|
+
enabled: false,
|
|
12947
|
+
backend: this.config.conversationIndexBackend,
|
|
12948
|
+
status: "disabled",
|
|
12949
|
+
chunkDocCount,
|
|
12950
|
+
lastUpdateAt
|
|
12951
|
+
};
|
|
12952
|
+
}
|
|
12953
|
+
if (this.config.conversationIndexBackend === "faiss") {
|
|
12954
|
+
const faiss = await failOpenFaissHealth(this.conversationFaiss);
|
|
12955
|
+
return {
|
|
12956
|
+
enabled: true,
|
|
12957
|
+
backend: "faiss",
|
|
12958
|
+
status: faiss.ok ? "ok" : "degraded",
|
|
12959
|
+
chunkDocCount,
|
|
12960
|
+
lastUpdateAt,
|
|
12961
|
+
faiss
|
|
12962
|
+
};
|
|
12963
|
+
}
|
|
12964
|
+
const qmdAvailable = !!this.conversationQmd?.isAvailable();
|
|
12965
|
+
return {
|
|
12966
|
+
enabled: true,
|
|
12967
|
+
backend: "qmd",
|
|
12968
|
+
status: qmdAvailable ? "ok" : "degraded",
|
|
12969
|
+
chunkDocCount,
|
|
12970
|
+
lastUpdateAt,
|
|
12971
|
+
qmdAvailable
|
|
12972
|
+
};
|
|
12973
|
+
}
|
|
12658
12974
|
async updateConversationIndex(sessionKey, hours = 24, opts) {
|
|
12659
12975
|
if (!this.config.conversationIndexEnabled) {
|
|
12660
12976
|
return { chunks: 0, skipped: true, reason: "disabled", embedded: false };
|
|
@@ -12685,15 +13001,19 @@ var Orchestrator = class _Orchestrator {
|
|
|
12685
13001
|
this.conversationIndexDir,
|
|
12686
13002
|
this.config.conversationIndexRetentionDays
|
|
12687
13003
|
);
|
|
12688
|
-
const q = this.conversationQmd ?? this.qmd;
|
|
12689
|
-
const usingPrimaryQmdClient = q === this.qmd;
|
|
12690
13004
|
const shouldEmbed = opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
|
|
12691
13005
|
let embedded = false;
|
|
12692
|
-
if (
|
|
12693
|
-
await
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
13006
|
+
if (this.config.conversationIndexBackend === "faiss") {
|
|
13007
|
+
await upsertConversationChunksFailOpen(this.conversationFaiss, chunks);
|
|
13008
|
+
} else {
|
|
13009
|
+
const q = this.conversationQmd ?? this.qmd;
|
|
13010
|
+
const usingPrimaryQmdClient = q === this.qmd;
|
|
13011
|
+
if ((!usingPrimaryQmdClient || this.config.qmdEnabled) && q.isAvailable()) {
|
|
13012
|
+
await q.update();
|
|
13013
|
+
if (shouldEmbed) {
|
|
13014
|
+
await q.embed();
|
|
13015
|
+
embedded = true;
|
|
13016
|
+
}
|
|
12697
13017
|
}
|
|
12698
13018
|
}
|
|
12699
13019
|
this.conversationIndexLastUpdateAtMs.set(sessionKey, Date.now());
|
|
@@ -12917,7 +13237,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
12917
13237
|
const storage = await this.storageRouter.storageFor(namespace);
|
|
12918
13238
|
const seedRelativePaths = nsResults.slice(0, perNamespaceSeedCap).map((result) => graphPathRelativeToStorage(storage.dir, result.path)).filter((value) => typeof value === "string" && value.length > 0);
|
|
12919
13239
|
if (seedRelativePaths.length === 0) continue;
|
|
12920
|
-
seedPaths.push(...seedRelativePaths.map((rel) =>
|
|
13240
|
+
seedPaths.push(...seedRelativePaths.map((rel) => path25.join(storage.dir, rel)));
|
|
12921
13241
|
const seedSet = new Set(seedRelativePaths);
|
|
12922
13242
|
const expanded = await this.graphIndexFor(storage).spreadingActivation(
|
|
12923
13243
|
seedRelativePaths,
|
|
@@ -12926,7 +13246,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
12926
13246
|
if (expanded.length === 0) continue;
|
|
12927
13247
|
for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
|
|
12928
13248
|
if (seedSet.has(candidate.path)) continue;
|
|
12929
|
-
const memoryPath =
|
|
13249
|
+
const memoryPath = path25.resolve(storage.dir, candidate.path);
|
|
12930
13250
|
const memory = await storage.readMemoryByPath(memoryPath);
|
|
12931
13251
|
if (!memory) continue;
|
|
12932
13252
|
if (isArtifactMemoryPath(memory.path)) continue;
|
|
@@ -12954,8 +13274,8 @@ var Orchestrator = class _Orchestrator {
|
|
|
12954
13274
|
}
|
|
12955
13275
|
async recordLastGraphRecallSnapshot(options) {
|
|
12956
13276
|
try {
|
|
12957
|
-
const snapshotPath =
|
|
12958
|
-
await mkdir18(
|
|
13277
|
+
const snapshotPath = path25.join(options.storage.dir, "state", "last_graph_recall.json");
|
|
13278
|
+
await mkdir18(path25.dirname(snapshotPath), { recursive: true });
|
|
12959
13279
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12960
13280
|
const payload = {
|
|
12961
13281
|
recordedAt: now,
|
|
@@ -13539,36 +13859,22 @@ ${formatted}`);
|
|
|
13539
13859
|
}
|
|
13540
13860
|
timings.summaries = `${Date.now() - summariesT0}ms`;
|
|
13541
13861
|
const convT0 = Date.now();
|
|
13542
|
-
if (this.config.conversationIndexEnabled && !queryPolicy.skipConversationRecall
|
|
13862
|
+
if (this.config.conversationIndexEnabled && !queryPolicy.skipConversationRecall) {
|
|
13543
13863
|
const startedAtMs = Date.now();
|
|
13544
13864
|
const timeoutMs = Math.max(200, this.config.conversationRecallTimeoutMs);
|
|
13545
13865
|
const topK = Math.max(1, this.config.conversationRecallTopK);
|
|
13546
13866
|
const maxChars = Math.max(400, this.config.conversationRecallMaxChars);
|
|
13547
13867
|
const results = await Promise.race([
|
|
13548
|
-
this.
|
|
13868
|
+
this.searchConversationRecallResults(retrievalQuery, topK),
|
|
13549
13869
|
new Promise((resolve) => setTimeout(() => resolve([]), timeoutMs))
|
|
13550
13870
|
]).catch(() => []);
|
|
13551
13871
|
const durationMs = Date.now() - startedAtMs;
|
|
13552
13872
|
if (durationMs >= timeoutMs) {
|
|
13553
13873
|
log.debug(`conversation recall: timed out after ${timeoutMs}ms`);
|
|
13554
13874
|
}
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
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
|
-
}
|
|
13875
|
+
const formattedConversationRecall = this.formatConversationRecallSection(results, maxChars);
|
|
13876
|
+
if (formattedConversationRecall) {
|
|
13877
|
+
sections.push(formattedConversationRecall);
|
|
13572
13878
|
}
|
|
13573
13879
|
}
|
|
13574
13880
|
timings.convRecall = `${Date.now() - convT0}ms`;
|
|
@@ -14370,7 +14676,7 @@ _Context: ${topQuestion.context}_`);
|
|
|
14370
14676
|
const allMems = allMemsForGraph ?? [];
|
|
14371
14677
|
for (const m of allMems) {
|
|
14372
14678
|
if (m.frontmatter.entityRef === entityRef) {
|
|
14373
|
-
const rel =
|
|
14679
|
+
const rel = path25.relative(storage.dir, m.path);
|
|
14374
14680
|
if (rel !== memoryRelPath) entitySiblings.push(rel);
|
|
14375
14681
|
}
|
|
14376
14682
|
}
|
|
@@ -14747,8 +15053,8 @@ ${texts.map((t, i) => `[${i + 1}] ${t}`).join("\n\n")}`;
|
|
|
14747
15053
|
protectedCategories: this.config.lifecycleProtectedCategories
|
|
14748
15054
|
}
|
|
14749
15055
|
};
|
|
14750
|
-
const metricsPath =
|
|
14751
|
-
await mkdir18(
|
|
15056
|
+
const metricsPath = path25.join(this.storage.dir, "state", "lifecycle-metrics.json");
|
|
15057
|
+
await mkdir18(path25.dirname(metricsPath), { recursive: true });
|
|
14752
15058
|
await writeFile17(metricsPath, JSON.stringify(metrics, null, 2), "utf-8");
|
|
14753
15059
|
}
|
|
14754
15060
|
/**
|
|
@@ -15015,7 +15321,7 @@ ${lines.join("\n\n")}`;
|
|
|
15015
15321
|
if (hits.length === 0) return [];
|
|
15016
15322
|
const results = [];
|
|
15017
15323
|
for (const hit of hits) {
|
|
15018
|
-
const fullPath =
|
|
15324
|
+
const fullPath = path25.isAbsolute(hit.path) ? hit.path : path25.join(this.config.memoryDir, hit.path);
|
|
15019
15325
|
const memory = await this.storage.readMemoryByPath(fullPath);
|
|
15020
15326
|
if (!memory) continue;
|
|
15021
15327
|
results.push({
|
|
@@ -15454,8 +15760,8 @@ ${lines.join("\n\n")}`;
|
|
|
15454
15760
|
}
|
|
15455
15761
|
namespaceFromStorageDir(storageDir) {
|
|
15456
15762
|
if (!this.config.namespacesEnabled) return this.config.defaultNamespace;
|
|
15457
|
-
const resolvedStorageDir =
|
|
15458
|
-
const resolvedMemoryDir =
|
|
15763
|
+
const resolvedStorageDir = path25.resolve(storageDir);
|
|
15764
|
+
const resolvedMemoryDir = path25.resolve(this.config.memoryDir);
|
|
15459
15765
|
if (resolvedStorageDir === resolvedMemoryDir) return this.config.defaultNamespace;
|
|
15460
15766
|
const m = resolvedStorageDir.match(/[\\/]namespaces[\\/]([^\\/]+)$/);
|
|
15461
15767
|
return m && m[1] ? m[1] : this.config.defaultNamespace;
|
|
@@ -15483,14 +15789,14 @@ ${lines.join("\n\n")}`;
|
|
|
15483
15789
|
};
|
|
15484
15790
|
|
|
15485
15791
|
// src/tools.ts
|
|
15486
|
-
import
|
|
15792
|
+
import path27 from "path";
|
|
15487
15793
|
import { createHash as createHash6 } from "crypto";
|
|
15488
15794
|
import { Type } from "@sinclair/typebox";
|
|
15489
15795
|
|
|
15490
15796
|
// src/work/storage.ts
|
|
15491
|
-
import
|
|
15797
|
+
import path26 from "path";
|
|
15492
15798
|
import { randomUUID } from "crypto";
|
|
15493
|
-
import { mkdir as mkdir19, readdir as
|
|
15799
|
+
import { mkdir as mkdir19, readdir as readdir12, readFile as readFile19, rm as rm3, writeFile as writeFile18 } from "fs/promises";
|
|
15494
15800
|
var TASK_TRANSITIONS = {
|
|
15495
15801
|
todo: /* @__PURE__ */ new Set(["in_progress", "blocked", "cancelled"]),
|
|
15496
15802
|
in_progress: /* @__PURE__ */ new Set(["todo", "blocked", "done", "cancelled"]),
|
|
@@ -15565,8 +15871,8 @@ function ensureProjectStatus(value) {
|
|
|
15565
15871
|
var WorkStorage = class {
|
|
15566
15872
|
constructor(memoryDir) {
|
|
15567
15873
|
this.memoryDir = memoryDir;
|
|
15568
|
-
this.tasksDir =
|
|
15569
|
-
this.projectsDir =
|
|
15874
|
+
this.tasksDir = path26.join(memoryDir, "work", "tasks");
|
|
15875
|
+
this.projectsDir = path26.join(memoryDir, "work", "projects");
|
|
15570
15876
|
}
|
|
15571
15877
|
tasksDir;
|
|
15572
15878
|
projectsDir;
|
|
@@ -15576,11 +15882,11 @@ var WorkStorage = class {
|
|
|
15576
15882
|
}
|
|
15577
15883
|
taskPath(id) {
|
|
15578
15884
|
assertValidWorkId(id, "task");
|
|
15579
|
-
return
|
|
15885
|
+
return path26.join(this.tasksDir, `${id}.md`);
|
|
15580
15886
|
}
|
|
15581
15887
|
projectPath(id) {
|
|
15582
15888
|
assertValidWorkId(id, "project");
|
|
15583
|
-
return
|
|
15889
|
+
return path26.join(this.projectsDir, `${id}.md`);
|
|
15584
15890
|
}
|
|
15585
15891
|
serializeTask(task) {
|
|
15586
15892
|
return `${serializeFrontmatter2(task)}
|
|
@@ -15668,11 +15974,11 @@ ${project.description}
|
|
|
15668
15974
|
}
|
|
15669
15975
|
async listTasks(filter) {
|
|
15670
15976
|
await this.ensureDirectories();
|
|
15671
|
-
const entries = await
|
|
15977
|
+
const entries = await readdir12(this.tasksDir, { withFileTypes: true });
|
|
15672
15978
|
const out = [];
|
|
15673
15979
|
for (const entry of entries) {
|
|
15674
15980
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
15675
|
-
const raw = await readFile19(
|
|
15981
|
+
const raw = await readFile19(path26.join(this.tasksDir, entry.name), "utf-8");
|
|
15676
15982
|
const task = this.parseTask(raw);
|
|
15677
15983
|
if (!task) continue;
|
|
15678
15984
|
if (filter?.status && task.status !== filter.status) continue;
|
|
@@ -15767,11 +16073,11 @@ ${project.description}
|
|
|
15767
16073
|
}
|
|
15768
16074
|
async listProjects() {
|
|
15769
16075
|
await this.ensureDirectories();
|
|
15770
|
-
const entries = await
|
|
16076
|
+
const entries = await readdir12(this.projectsDir, { withFileTypes: true });
|
|
15771
16077
|
const out = [];
|
|
15772
16078
|
for (const entry of entries) {
|
|
15773
16079
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
15774
|
-
const raw = await readFile19(
|
|
16080
|
+
const raw = await readFile19(path26.join(this.projectsDir, entry.name), "utf-8");
|
|
15775
16081
|
const project = this.parseProject(raw);
|
|
15776
16082
|
if (project) out.push(project);
|
|
15777
16083
|
}
|
|
@@ -17272,7 +17578,7 @@ Best for:
|
|
|
17272
17578
|
- Reviewing identity development over time`,
|
|
17273
17579
|
parameters: Type.Object({}),
|
|
17274
17580
|
async execute() {
|
|
17275
|
-
const workspaceDir =
|
|
17581
|
+
const workspaceDir = path27.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
17276
17582
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
17277
17583
|
if (!identity) {
|
|
17278
17584
|
return toolResult("No identity file found. Identity reflections build automatically through conversations when identityEnabled is true.");
|
|
@@ -17781,11 +18087,11 @@ mistakes: ${res.mistakesCount} patterns`
|
|
|
17781
18087
|
}
|
|
17782
18088
|
|
|
17783
18089
|
// src/cli.ts
|
|
17784
|
-
import
|
|
17785
|
-
import { access as access3, readFile as readFile30, readdir as
|
|
18090
|
+
import path43 from "path";
|
|
18091
|
+
import { access as access3, readFile as readFile30, readdir as readdir19, unlink as unlink5 } from "fs/promises";
|
|
17786
18092
|
|
|
17787
18093
|
// src/transfer/export-json.ts
|
|
17788
|
-
import
|
|
18094
|
+
import path29 from "path";
|
|
17789
18095
|
import { mkdir as mkdir21, readFile as readFile21 } from "fs/promises";
|
|
17790
18096
|
|
|
17791
18097
|
// src/transfer/constants.ts
|
|
@@ -17794,8 +18100,8 @@ var EXPORT_SCHEMA_VERSION = 1;
|
|
|
17794
18100
|
|
|
17795
18101
|
// src/transfer/fs-utils.ts
|
|
17796
18102
|
import { createHash as createHash7 } from "crypto";
|
|
17797
|
-
import { mkdir as mkdir20, readdir as
|
|
17798
|
-
import
|
|
18103
|
+
import { mkdir as mkdir20, readdir as readdir13, readFile as readFile20, stat as stat6, writeFile as writeFile19 } from "fs/promises";
|
|
18104
|
+
import path28 from "path";
|
|
17799
18105
|
async function sha256File(filePath) {
|
|
17800
18106
|
const buf = await readFile20(filePath);
|
|
17801
18107
|
const sha256 = createHash7("sha256").update(buf).digest("hex");
|
|
@@ -17807,7 +18113,7 @@ function sha256String(content) {
|
|
|
17807
18113
|
return { sha256, bytes: buf.byteLength };
|
|
17808
18114
|
}
|
|
17809
18115
|
async function writeJsonFile(filePath, value) {
|
|
17810
|
-
await mkdir20(
|
|
18116
|
+
await mkdir20(path28.dirname(filePath), { recursive: true });
|
|
17811
18117
|
await writeFile19(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
17812
18118
|
}
|
|
17813
18119
|
async function readJsonFile(filePath) {
|
|
@@ -17817,9 +18123,9 @@ async function readJsonFile(filePath) {
|
|
|
17817
18123
|
async function listFilesRecursive(rootDir) {
|
|
17818
18124
|
const out = [];
|
|
17819
18125
|
async function walk(dir) {
|
|
17820
|
-
const entries = await
|
|
18126
|
+
const entries = await readdir13(dir, { withFileTypes: true });
|
|
17821
18127
|
for (const ent of entries) {
|
|
17822
|
-
const fp =
|
|
18128
|
+
const fp = path28.join(dir, ent.name);
|
|
17823
18129
|
if (ent.isDirectory()) {
|
|
17824
18130
|
await walk(fp);
|
|
17825
18131
|
} else if (ent.isFile()) {
|
|
@@ -17839,11 +18145,11 @@ async function fileExists(filePath) {
|
|
|
17839
18145
|
}
|
|
17840
18146
|
}
|
|
17841
18147
|
function toPosixRelPath(absPath, rootDir) {
|
|
17842
|
-
const rel =
|
|
17843
|
-
return rel.split(
|
|
18148
|
+
const rel = path28.relative(rootDir, absPath);
|
|
18149
|
+
return rel.split(path28.sep).join("/");
|
|
17844
18150
|
}
|
|
17845
18151
|
function fromPosixRelPath(relPath) {
|
|
17846
|
-
return relPath.split("/").join(
|
|
18152
|
+
return relPath.split("/").join(path28.sep);
|
|
17847
18153
|
}
|
|
17848
18154
|
|
|
17849
18155
|
// src/transfer/export-json.ts
|
|
@@ -17859,9 +18165,9 @@ function shouldExclude(relPosix, includeTranscripts) {
|
|
|
17859
18165
|
}
|
|
17860
18166
|
async function exportJsonBundle(opts) {
|
|
17861
18167
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
17862
|
-
const outDirAbs =
|
|
18168
|
+
const outDirAbs = path29.resolve(opts.outDir);
|
|
17863
18169
|
await mkdir21(outDirAbs, { recursive: true });
|
|
17864
|
-
const memoryDirAbs =
|
|
18170
|
+
const memoryDirAbs = path29.resolve(opts.memoryDir);
|
|
17865
18171
|
const filesAbs = await listFilesRecursive(memoryDirAbs);
|
|
17866
18172
|
const records = [];
|
|
17867
18173
|
const manifestFiles = [];
|
|
@@ -17874,7 +18180,7 @@ async function exportJsonBundle(opts) {
|
|
|
17874
18180
|
manifestFiles.push({ path: relPosix, sha256, bytes });
|
|
17875
18181
|
}
|
|
17876
18182
|
if (opts.includeWorkspaceIdentity !== false && opts.workspaceDir) {
|
|
17877
|
-
const identityPath =
|
|
18183
|
+
const identityPath = path29.join(opts.workspaceDir, "IDENTITY.md");
|
|
17878
18184
|
try {
|
|
17879
18185
|
const content = await readFile21(identityPath, "utf-8");
|
|
17880
18186
|
const relPath = "workspace/IDENTITY.md";
|
|
@@ -17893,12 +18199,12 @@ async function exportJsonBundle(opts) {
|
|
|
17893
18199
|
files: manifestFiles.sort((a, b) => a.path.localeCompare(b.path))
|
|
17894
18200
|
};
|
|
17895
18201
|
const bundle = { manifest, records };
|
|
17896
|
-
await writeJsonFile(
|
|
17897
|
-
await writeJsonFile(
|
|
18202
|
+
await writeJsonFile(path29.join(outDirAbs, "manifest.json"), manifest);
|
|
18203
|
+
await writeJsonFile(path29.join(outDirAbs, "bundle.json"), bundle);
|
|
17898
18204
|
}
|
|
17899
18205
|
|
|
17900
18206
|
// src/transfer/export-md.ts
|
|
17901
|
-
import
|
|
18207
|
+
import path30 from "path";
|
|
17902
18208
|
import { mkdir as mkdir22, readFile as readFile22, writeFile as writeFile20 } from "fs/promises";
|
|
17903
18209
|
function shouldExclude2(relPosix, includeTranscripts) {
|
|
17904
18210
|
const parts = relPosix.split("/");
|
|
@@ -17907,16 +18213,16 @@ function shouldExclude2(relPosix, includeTranscripts) {
|
|
|
17907
18213
|
}
|
|
17908
18214
|
async function exportMarkdownBundle(opts) {
|
|
17909
18215
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
17910
|
-
const outDirAbs =
|
|
18216
|
+
const outDirAbs = path30.resolve(opts.outDir);
|
|
17911
18217
|
await mkdir22(outDirAbs, { recursive: true });
|
|
17912
|
-
const memDirAbs =
|
|
18218
|
+
const memDirAbs = path30.resolve(opts.memoryDir);
|
|
17913
18219
|
const filesAbs = await listFilesRecursive(memDirAbs);
|
|
17914
18220
|
const manifestFiles = [];
|
|
17915
18221
|
for (const abs of filesAbs) {
|
|
17916
18222
|
const relPosix = toPosixRelPath(abs, memDirAbs);
|
|
17917
18223
|
if (shouldExclude2(relPosix, includeTranscripts)) continue;
|
|
17918
|
-
const dstAbs =
|
|
17919
|
-
await mkdir22(
|
|
18224
|
+
const dstAbs = path30.join(outDirAbs, ...relPosix.split("/"));
|
|
18225
|
+
await mkdir22(path30.dirname(dstAbs), { recursive: true });
|
|
17920
18226
|
const content = await readFile22(abs);
|
|
17921
18227
|
await writeFile20(dstAbs, content);
|
|
17922
18228
|
const { sha256, bytes } = await sha256File(abs);
|
|
@@ -17930,12 +18236,12 @@ async function exportMarkdownBundle(opts) {
|
|
|
17930
18236
|
includesTranscripts: includeTranscripts,
|
|
17931
18237
|
files: manifestFiles.sort((a, b) => a.path.localeCompare(b.path))
|
|
17932
18238
|
};
|
|
17933
|
-
await writeJsonFile(
|
|
18239
|
+
await writeJsonFile(path30.join(outDirAbs, "manifest.json"), manifest);
|
|
17934
18240
|
}
|
|
17935
18241
|
async function looksLikeEngramMdExport(fromDir) {
|
|
17936
|
-
const dirAbs =
|
|
18242
|
+
const dirAbs = path30.resolve(fromDir);
|
|
17937
18243
|
try {
|
|
17938
|
-
const raw = await readFile22(
|
|
18244
|
+
const raw = await readFile22(path30.join(dirAbs, "manifest.json"), "utf-8");
|
|
17939
18245
|
const parsed = JSON.parse(raw);
|
|
17940
18246
|
return parsed.format === EXPORT_FORMAT && parsed.schemaVersion === EXPORT_SCHEMA_VERSION;
|
|
17941
18247
|
} catch {
|
|
@@ -17944,16 +18250,16 @@ async function looksLikeEngramMdExport(fromDir) {
|
|
|
17944
18250
|
}
|
|
17945
18251
|
|
|
17946
18252
|
// src/transfer/backup.ts
|
|
17947
|
-
import
|
|
17948
|
-
import { mkdir as mkdir23, readdir as
|
|
18253
|
+
import path31 from "path";
|
|
18254
|
+
import { mkdir as mkdir23, readdir as readdir14, rm as rm4 } from "fs/promises";
|
|
17949
18255
|
function timestampDirName(now) {
|
|
17950
18256
|
return now.toISOString().replace(/[:.]/g, "-");
|
|
17951
18257
|
}
|
|
17952
18258
|
async function backupMemoryDir(opts) {
|
|
17953
|
-
const outDirAbs =
|
|
18259
|
+
const outDirAbs = path31.resolve(opts.outDir);
|
|
17954
18260
|
await mkdir23(outDirAbs, { recursive: true });
|
|
17955
18261
|
const ts = timestampDirName(/* @__PURE__ */ new Date());
|
|
17956
|
-
const backupDir =
|
|
18262
|
+
const backupDir = path31.join(outDirAbs, ts);
|
|
17957
18263
|
await exportMarkdownBundle({
|
|
17958
18264
|
memoryDir: opts.memoryDir,
|
|
17959
18265
|
outDir: backupDir,
|
|
@@ -17966,7 +18272,7 @@ async function backupMemoryDir(opts) {
|
|
|
17966
18272
|
return backupDir;
|
|
17967
18273
|
}
|
|
17968
18274
|
async function enforceRetention(outDirAbs, retentionDays) {
|
|
17969
|
-
const entries = await
|
|
18275
|
+
const entries = await readdir14(outDirAbs, { withFileTypes: true });
|
|
17970
18276
|
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
17971
18277
|
for (const ent of entries) {
|
|
17972
18278
|
if (!ent.isDirectory()) continue;
|
|
@@ -17978,13 +18284,13 @@ async function enforceRetention(outDirAbs, retentionDays) {
|
|
|
17978
18284
|
const tsMs = iso ? Date.parse(iso) : NaN;
|
|
17979
18285
|
if (!Number.isFinite(tsMs)) continue;
|
|
17980
18286
|
if (tsMs < cutoffMs) {
|
|
17981
|
-
await rm4(
|
|
18287
|
+
await rm4(path31.join(outDirAbs, name), { recursive: true, force: true });
|
|
17982
18288
|
}
|
|
17983
18289
|
}
|
|
17984
18290
|
}
|
|
17985
18291
|
|
|
17986
18292
|
// src/transfer/export-sqlite.ts
|
|
17987
|
-
import
|
|
18293
|
+
import path32 from "path";
|
|
17988
18294
|
import Database from "better-sqlite3";
|
|
17989
18295
|
import { readFile as readFile23 } from "fs/promises";
|
|
17990
18296
|
|
|
@@ -18012,8 +18318,8 @@ function shouldExclude3(relPosix, includeTranscripts) {
|
|
|
18012
18318
|
}
|
|
18013
18319
|
async function exportSqlite(opts) {
|
|
18014
18320
|
const includeTranscripts = opts.includeTranscripts === true;
|
|
18015
|
-
const memDirAbs =
|
|
18016
|
-
const outAbs =
|
|
18321
|
+
const memDirAbs = path32.resolve(opts.memoryDir);
|
|
18322
|
+
const outAbs = path32.resolve(opts.outFile);
|
|
18017
18323
|
const filesAbs = await listFilesRecursive(memDirAbs);
|
|
18018
18324
|
const db = new Database(outAbs);
|
|
18019
18325
|
try {
|
|
@@ -18045,7 +18351,7 @@ async function exportSqlite(opts) {
|
|
|
18045
18351
|
}
|
|
18046
18352
|
|
|
18047
18353
|
// src/transfer/import-json.ts
|
|
18048
|
-
import
|
|
18354
|
+
import path33 from "path";
|
|
18049
18355
|
import { mkdir as mkdir24, writeFile as writeFile21 } from "fs/promises";
|
|
18050
18356
|
|
|
18051
18357
|
// src/transfer/types.ts
|
|
@@ -18079,21 +18385,21 @@ function normalizeForDedupe(s) {
|
|
|
18079
18385
|
}
|
|
18080
18386
|
async function importJsonBundle(opts) {
|
|
18081
18387
|
const conflict = opts.conflict ?? "skip";
|
|
18082
|
-
const fromDirAbs =
|
|
18083
|
-
const bundlePath =
|
|
18388
|
+
const fromDirAbs = path33.resolve(opts.fromDir);
|
|
18389
|
+
const bundlePath = path33.join(fromDirAbs, "bundle.json");
|
|
18084
18390
|
const bundle = ExportBundleV1Schema.parse(await readJsonFile(bundlePath));
|
|
18085
|
-
const memDirAbs =
|
|
18391
|
+
const memDirAbs = path33.resolve(opts.targetMemoryDir);
|
|
18086
18392
|
const written = [];
|
|
18087
18393
|
let skipped = 0;
|
|
18088
18394
|
for (const rec of bundle.records) {
|
|
18089
18395
|
const isWorkspace = rec.path.startsWith("workspace/");
|
|
18090
|
-
const targetBase = isWorkspace ? opts.workspaceDir ?
|
|
18396
|
+
const targetBase = isWorkspace ? opts.workspaceDir ? path33.resolve(opts.workspaceDir) : null : memDirAbs;
|
|
18091
18397
|
if (isWorkspace && !targetBase) {
|
|
18092
18398
|
skipped += 1;
|
|
18093
18399
|
continue;
|
|
18094
18400
|
}
|
|
18095
18401
|
const relFs = fromPosixRelPath(isWorkspace ? rec.path.replace(/^workspace\//, "") : rec.path);
|
|
18096
|
-
const absTarget =
|
|
18402
|
+
const absTarget = path33.join(targetBase, relFs);
|
|
18097
18403
|
const exists3 = await fileExists(absTarget);
|
|
18098
18404
|
if (exists3) {
|
|
18099
18405
|
if (conflict === "skip") {
|
|
@@ -18117,21 +18423,21 @@ async function importJsonBundle(opts) {
|
|
|
18117
18423
|
return { written: 0, skipped };
|
|
18118
18424
|
}
|
|
18119
18425
|
for (const w of written) {
|
|
18120
|
-
await mkdir24(
|
|
18426
|
+
await mkdir24(path33.dirname(w.abs), { recursive: true });
|
|
18121
18427
|
await writeFile21(w.abs, w.content, "utf-8");
|
|
18122
18428
|
}
|
|
18123
18429
|
return { written: written.length, skipped };
|
|
18124
18430
|
}
|
|
18125
18431
|
function looksLikeEngramJsonExport(fromDir) {
|
|
18126
|
-
const dir =
|
|
18432
|
+
const dir = path33.resolve(fromDir);
|
|
18127
18433
|
return Promise.all([
|
|
18128
|
-
fileExists(
|
|
18129
|
-
fileExists(
|
|
18434
|
+
fileExists(path33.join(dir, "manifest.json")),
|
|
18435
|
+
fileExists(path33.join(dir, "bundle.json"))
|
|
18130
18436
|
]).then(([m, b]) => m && b);
|
|
18131
18437
|
}
|
|
18132
18438
|
|
|
18133
18439
|
// src/transfer/import-sqlite.ts
|
|
18134
|
-
import
|
|
18440
|
+
import path34 from "path";
|
|
18135
18441
|
import Database2 from "better-sqlite3";
|
|
18136
18442
|
import { mkdir as mkdir25, writeFile as writeFile22 } from "fs/promises";
|
|
18137
18443
|
function normalizeForDedupe2(s) {
|
|
@@ -18139,8 +18445,8 @@ function normalizeForDedupe2(s) {
|
|
|
18139
18445
|
}
|
|
18140
18446
|
async function importSqlite(opts) {
|
|
18141
18447
|
const conflict = opts.conflict ?? "skip";
|
|
18142
|
-
const memDirAbs =
|
|
18143
|
-
const fromAbs =
|
|
18448
|
+
const memDirAbs = path34.resolve(opts.targetMemoryDir);
|
|
18449
|
+
const fromAbs = path34.resolve(opts.fromFile);
|
|
18144
18450
|
const db = new Database2(fromAbs, { readonly: true });
|
|
18145
18451
|
const written = [];
|
|
18146
18452
|
let skipped = 0;
|
|
@@ -18153,7 +18459,7 @@ async function importSqlite(opts) {
|
|
|
18153
18459
|
const rows = db.prepare("SELECT path_rel, content FROM files").all();
|
|
18154
18460
|
for (const r of rows) {
|
|
18155
18461
|
const relFs = fromPosixRelPath(r.path_rel);
|
|
18156
|
-
const absTarget =
|
|
18462
|
+
const absTarget = path34.join(memDirAbs, relFs);
|
|
18157
18463
|
const exists3 = await fileExists(absTarget);
|
|
18158
18464
|
if (exists3) {
|
|
18159
18465
|
if (conflict === "skip") {
|
|
@@ -18178,29 +18484,29 @@ async function importSqlite(opts) {
|
|
|
18178
18484
|
}
|
|
18179
18485
|
if (opts.dryRun) return { written: 0, skipped };
|
|
18180
18486
|
for (const w of written) {
|
|
18181
|
-
await mkdir25(
|
|
18487
|
+
await mkdir25(path34.dirname(w.abs), { recursive: true });
|
|
18182
18488
|
await writeFile22(w.abs, w.content, "utf-8");
|
|
18183
18489
|
}
|
|
18184
18490
|
return { written: written.length, skipped };
|
|
18185
18491
|
}
|
|
18186
18492
|
|
|
18187
18493
|
// src/transfer/import-md.ts
|
|
18188
|
-
import
|
|
18494
|
+
import path35 from "path";
|
|
18189
18495
|
import { mkdir as mkdir26, readFile as readFile24, writeFile as writeFile23 } from "fs/promises";
|
|
18190
18496
|
function normalizeForDedupe3(s) {
|
|
18191
18497
|
return s.replace(/\s+/g, " ").trim();
|
|
18192
18498
|
}
|
|
18193
18499
|
async function importMarkdownBundle(opts) {
|
|
18194
18500
|
const conflict = opts.conflict ?? "skip";
|
|
18195
|
-
const fromAbs =
|
|
18196
|
-
const targetAbs =
|
|
18501
|
+
const fromAbs = path35.resolve(opts.fromDir);
|
|
18502
|
+
const targetAbs = path35.resolve(opts.targetMemoryDir);
|
|
18197
18503
|
const filesAbs = await listFilesRecursive(fromAbs);
|
|
18198
18504
|
const writes = [];
|
|
18199
18505
|
let skipped = 0;
|
|
18200
18506
|
for (const abs of filesAbs) {
|
|
18201
18507
|
const relPosix = toPosixRelPath(abs, fromAbs);
|
|
18202
18508
|
if (relPosix === "manifest.json") continue;
|
|
18203
|
-
const dstAbs =
|
|
18509
|
+
const dstAbs = path35.join(targetAbs, fromPosixRelPath(relPosix));
|
|
18204
18510
|
const content = await readFile24(abs, "utf-8");
|
|
18205
18511
|
const exists3 = await fileExists(dstAbs);
|
|
18206
18512
|
if (exists3) {
|
|
@@ -18223,17 +18529,17 @@ async function importMarkdownBundle(opts) {
|
|
|
18223
18529
|
}
|
|
18224
18530
|
if (opts.dryRun) return { written: 0, skipped };
|
|
18225
18531
|
for (const w of writes) {
|
|
18226
|
-
await mkdir26(
|
|
18532
|
+
await mkdir26(path35.dirname(w.abs), { recursive: true });
|
|
18227
18533
|
await writeFile23(w.abs, w.content, "utf-8");
|
|
18228
18534
|
}
|
|
18229
18535
|
return { written: writes.length, skipped };
|
|
18230
18536
|
}
|
|
18231
18537
|
|
|
18232
18538
|
// src/transfer/autodetect.ts
|
|
18233
|
-
import
|
|
18539
|
+
import path36 from "path";
|
|
18234
18540
|
import { stat as stat7 } from "fs/promises";
|
|
18235
18541
|
async function detectImportFormat(fromPath) {
|
|
18236
|
-
const abs =
|
|
18542
|
+
const abs = path36.resolve(fromPath);
|
|
18237
18543
|
let st;
|
|
18238
18544
|
try {
|
|
18239
18545
|
st = await stat7(abs);
|
|
@@ -18661,8 +18967,8 @@ function gatherCandidates(input, warnings) {
|
|
|
18661
18967
|
const record = rec;
|
|
18662
18968
|
const content = typeof record.content === "string" ? record.content : null;
|
|
18663
18969
|
if (!content) continue;
|
|
18664
|
-
const
|
|
18665
|
-
if (!
|
|
18970
|
+
const path45 = typeof record.path === "string" ? record.path : "";
|
|
18971
|
+
if (!path45.startsWith("transcripts/") && !path45.includes("/transcripts/")) continue;
|
|
18666
18972
|
rows.push(...parseJsonl(content, warnings));
|
|
18667
18973
|
}
|
|
18668
18974
|
return rows;
|
|
@@ -18718,8 +19024,8 @@ var openclawReplayNormalizer = {
|
|
|
18718
19024
|
};
|
|
18719
19025
|
|
|
18720
19026
|
// src/maintenance/archive-observations.ts
|
|
18721
|
-
import
|
|
18722
|
-
import { mkdir as mkdir27, readdir as
|
|
19027
|
+
import path37 from "path";
|
|
19028
|
+
import { mkdir as mkdir27, readdir as readdir15, readFile as readFile25, unlink as unlink4, writeFile as writeFile24 } from "fs/promises";
|
|
18723
19029
|
var DATE_FILE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})\.(jsonl|md)$/;
|
|
18724
19030
|
function normalizeRetentionDays(value) {
|
|
18725
19031
|
if (!Number.isFinite(value)) return 30;
|
|
@@ -18740,13 +19046,13 @@ async function listFilesRecursive2(root, relPrefix = "") {
|
|
|
18740
19046
|
const out = [];
|
|
18741
19047
|
let entries;
|
|
18742
19048
|
try {
|
|
18743
|
-
entries = await
|
|
19049
|
+
entries = await readdir15(root, { withFileTypes: true });
|
|
18744
19050
|
} catch {
|
|
18745
19051
|
return out;
|
|
18746
19052
|
}
|
|
18747
19053
|
for (const entry of entries) {
|
|
18748
|
-
const rel = relPrefix ?
|
|
18749
|
-
const full =
|
|
19054
|
+
const rel = relPrefix ? path37.join(relPrefix, entry.name) : entry.name;
|
|
19055
|
+
const full = path37.join(root, entry.name);
|
|
18750
19056
|
if (entry.isDirectory()) {
|
|
18751
19057
|
out.push(...await listFilesRecursive2(full, rel));
|
|
18752
19058
|
continue;
|
|
@@ -18756,19 +19062,19 @@ async function listFilesRecursive2(root, relPrefix = "") {
|
|
|
18756
19062
|
return out;
|
|
18757
19063
|
}
|
|
18758
19064
|
async function collectArchiveCandidates(memoryDir, cutoffTimeMs) {
|
|
18759
|
-
const roots = ["transcripts",
|
|
19065
|
+
const roots = ["transcripts", path37.join("state", "tool-usage"), path37.join("summaries", "hourly")];
|
|
18760
19066
|
const out = [];
|
|
18761
19067
|
for (const relRoot of roots) {
|
|
18762
|
-
const absRoot =
|
|
19068
|
+
const absRoot = path37.join(memoryDir, relRoot);
|
|
18763
19069
|
const files = await listFilesRecursive2(absRoot);
|
|
18764
19070
|
for (const fileRel of files) {
|
|
18765
|
-
const filename =
|
|
19071
|
+
const filename = path37.basename(fileRel);
|
|
18766
19072
|
const parsedDate = extractDateFromFilename(filename);
|
|
18767
19073
|
if (!parsedDate) continue;
|
|
18768
19074
|
if (parsedDate.getTime() >= cutoffTimeMs) continue;
|
|
18769
19075
|
out.push({
|
|
18770
|
-
absolutePath:
|
|
18771
|
-
relativePath:
|
|
19076
|
+
absolutePath: path37.join(absRoot, fileRel),
|
|
19077
|
+
relativePath: path37.join(relRoot, fileRel)
|
|
18772
19078
|
});
|
|
18773
19079
|
}
|
|
18774
19080
|
}
|
|
@@ -18783,7 +19089,7 @@ async function archiveObservations(options) {
|
|
|
18783
19089
|
new Date(now.getTime() - retentionDays * 24 * 60 * 60 * 1e3)
|
|
18784
19090
|
);
|
|
18785
19091
|
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
18786
|
-
const archiveRoot =
|
|
19092
|
+
const archiveRoot = path37.join(options.memoryDir, "archive", "observations", stamp);
|
|
18787
19093
|
const candidates = retentionDays === 0 ? [] : await collectArchiveCandidates(
|
|
18788
19094
|
options.memoryDir,
|
|
18789
19095
|
cutoffDayStartUtc
|
|
@@ -18794,8 +19100,8 @@ async function archiveObservations(options) {
|
|
|
18794
19100
|
if (!dryRun && candidates.length > 0) {
|
|
18795
19101
|
await mkdir27(archiveRoot, { recursive: true });
|
|
18796
19102
|
for (const candidate of candidates) {
|
|
18797
|
-
const archivePath =
|
|
18798
|
-
const archiveDir =
|
|
19103
|
+
const archivePath = path37.join(archiveRoot, candidate.relativePath);
|
|
19104
|
+
const archiveDir = path37.dirname(archivePath);
|
|
18799
19105
|
await mkdir27(archiveDir, { recursive: true });
|
|
18800
19106
|
const raw = await readFile25(candidate.absolutePath);
|
|
18801
19107
|
await writeFile24(archivePath, raw);
|
|
@@ -18819,11 +19125,11 @@ async function archiveObservations(options) {
|
|
|
18819
19125
|
}
|
|
18820
19126
|
|
|
18821
19127
|
// src/maintenance/rebuild-observations.ts
|
|
18822
|
-
import
|
|
18823
|
-
import { readdir as
|
|
19128
|
+
import path39 from "path";
|
|
19129
|
+
import { readdir as readdir16, readFile as readFile27 } from "fs/promises";
|
|
18824
19130
|
|
|
18825
19131
|
// src/maintenance/observation-ledger-utils.ts
|
|
18826
|
-
import
|
|
19132
|
+
import path38 from "path";
|
|
18827
19133
|
import { mkdir as mkdir28, readFile as readFile26, writeFile as writeFile25 } from "fs/promises";
|
|
18828
19134
|
function toHourBucketIso(timestamp) {
|
|
18829
19135
|
const normalized = /(?:Z|[+-]\d{2}:\d{2})$/u.test(timestamp) ? timestamp : `${timestamp}Z`;
|
|
@@ -18835,8 +19141,8 @@ function toHourBucketIso(timestamp) {
|
|
|
18835
19141
|
}
|
|
18836
19142
|
async function backupAndWriteRebuiltObservations(options) {
|
|
18837
19143
|
const stamp = options.now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
18838
|
-
const archiveRoot =
|
|
18839
|
-
let backupPath =
|
|
19144
|
+
const archiveRoot = path38.join(options.memoryDir, "archive", "observations", stamp);
|
|
19145
|
+
let backupPath = path38.join(
|
|
18840
19146
|
archiveRoot,
|
|
18841
19147
|
"state",
|
|
18842
19148
|
"observation-ledger",
|
|
@@ -18844,7 +19150,7 @@ async function backupAndWriteRebuiltObservations(options) {
|
|
|
18844
19150
|
);
|
|
18845
19151
|
try {
|
|
18846
19152
|
const existing = await readFile26(options.outputPath, "utf-8");
|
|
18847
|
-
await mkdir28(
|
|
19153
|
+
await mkdir28(path38.dirname(backupPath), { recursive: true });
|
|
18848
19154
|
await writeFile25(backupPath, existing, "utf-8");
|
|
18849
19155
|
} catch (err) {
|
|
18850
19156
|
const code = err.code;
|
|
@@ -18861,7 +19167,7 @@ async function backupAndWriteRebuiltObservations(options) {
|
|
|
18861
19167
|
rebuiltAt
|
|
18862
19168
|
})
|
|
18863
19169
|
);
|
|
18864
|
-
await mkdir28(
|
|
19170
|
+
await mkdir28(path38.dirname(options.outputPath), { recursive: true });
|
|
18865
19171
|
await writeFile25(options.outputPath, lines.length > 0 ? `${lines.join("\n")}
|
|
18866
19172
|
` : "", "utf-8");
|
|
18867
19173
|
return backupPath;
|
|
@@ -18875,7 +19181,7 @@ async function listTranscriptFiles(root) {
|
|
|
18875
19181
|
const out = [];
|
|
18876
19182
|
let entries;
|
|
18877
19183
|
try {
|
|
18878
|
-
entries = await
|
|
19184
|
+
entries = await readdir16(root, { withFileTypes: true });
|
|
18879
19185
|
} catch (err) {
|
|
18880
19186
|
const code = err.code;
|
|
18881
19187
|
if (code && code === "ENOENT") return out;
|
|
@@ -18884,7 +19190,7 @@ async function listTranscriptFiles(root) {
|
|
|
18884
19190
|
for (const entry of entries) {
|
|
18885
19191
|
if (entry.name === "." || entry.name === "..") continue;
|
|
18886
19192
|
if (entry.isSymbolicLink()) continue;
|
|
18887
|
-
const full =
|
|
19193
|
+
const full = path39.join(root, entry.name);
|
|
18888
19194
|
if (entry.isDirectory()) {
|
|
18889
19195
|
out.push(...await listTranscriptFiles(full));
|
|
18890
19196
|
continue;
|
|
@@ -18944,8 +19250,8 @@ function buildLedgerRows(linesByFile) {
|
|
|
18944
19250
|
async function rebuildObservations(options) {
|
|
18945
19251
|
const dryRun = options.dryRun !== false;
|
|
18946
19252
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
18947
|
-
const transcriptsRoot =
|
|
18948
|
-
const outputPath =
|
|
19253
|
+
const transcriptsRoot = path39.join(options.memoryDir, "transcripts");
|
|
19254
|
+
const outputPath = path39.join(
|
|
18949
19255
|
options.memoryDir,
|
|
18950
19256
|
"state",
|
|
18951
19257
|
"observation-ledger",
|
|
@@ -18981,8 +19287,8 @@ async function rebuildObservations(options) {
|
|
|
18981
19287
|
}
|
|
18982
19288
|
|
|
18983
19289
|
// src/maintenance/migrate-observations.ts
|
|
18984
|
-
import
|
|
18985
|
-
import { readdir as
|
|
19290
|
+
import path40 from "path";
|
|
19291
|
+
import { readdir as readdir17, readFile as readFile28 } from "fs/promises";
|
|
18986
19292
|
function toNonNegativeInt(value) {
|
|
18987
19293
|
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
18988
19294
|
const normalized = Math.floor(value);
|
|
@@ -19033,7 +19339,7 @@ function toCounts(row) {
|
|
|
19033
19339
|
async function listLegacyObservationFiles(root) {
|
|
19034
19340
|
let entries;
|
|
19035
19341
|
try {
|
|
19036
|
-
entries = await
|
|
19342
|
+
entries = await readdir17(root, { withFileTypes: true });
|
|
19037
19343
|
} catch (err) {
|
|
19038
19344
|
const code = err.code;
|
|
19039
19345
|
if (code && code === "ENOENT") return [];
|
|
@@ -19045,15 +19351,15 @@ async function listLegacyObservationFiles(root) {
|
|
|
19045
19351
|
async function migrateObservations(options) {
|
|
19046
19352
|
const dryRun = options.dryRun !== false;
|
|
19047
19353
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
19048
|
-
const ledgerRoot =
|
|
19049
|
-
const outputPath =
|
|
19354
|
+
const ledgerRoot = path40.join(options.memoryDir, "state", "observation-ledger");
|
|
19355
|
+
const outputPath = path40.join(ledgerRoot, "rebuilt-observations.jsonl");
|
|
19050
19356
|
const legacyFiles = await listLegacyObservationFiles(ledgerRoot);
|
|
19051
|
-
const sourceRelativePaths = legacyFiles.map((name) =>
|
|
19357
|
+
const sourceRelativePaths = legacyFiles.map((name) => path40.join("state", "observation-ledger", name));
|
|
19052
19358
|
const byKey = /* @__PURE__ */ new Map();
|
|
19053
19359
|
let parsedRows = 0;
|
|
19054
19360
|
let malformedLines = 0;
|
|
19055
19361
|
for (const file of legacyFiles) {
|
|
19056
|
-
const full =
|
|
19362
|
+
const full = path40.join(ledgerRoot, file);
|
|
19057
19363
|
const raw = await readFile28(full, "utf-8");
|
|
19058
19364
|
for (const line of raw.split("\n")) {
|
|
19059
19365
|
if (!line.trim()) continue;
|
|
@@ -19132,7 +19438,7 @@ async function migrateObservations(options) {
|
|
|
19132
19438
|
|
|
19133
19439
|
// src/network/tailscale.ts
|
|
19134
19440
|
import { stat as stat8 } from "fs/promises";
|
|
19135
|
-
import { spawn as
|
|
19441
|
+
import { spawn as spawn3 } from "child_process";
|
|
19136
19442
|
var TailscaleHelper = class {
|
|
19137
19443
|
tailscaleBinary;
|
|
19138
19444
|
rsyncBinary;
|
|
@@ -19203,7 +19509,7 @@ async function assertReadableDirectory(dir) {
|
|
|
19203
19509
|
}
|
|
19204
19510
|
var defaultCommandRunner = (command, args, options) => {
|
|
19205
19511
|
return new Promise((resolve) => {
|
|
19206
|
-
const child =
|
|
19512
|
+
const child = spawn3(command, args, {
|
|
19207
19513
|
stdio: ["ignore", "pipe", "pipe"]
|
|
19208
19514
|
});
|
|
19209
19515
|
let stdout = "";
|
|
@@ -19243,10 +19549,10 @@ var defaultCommandRunner = (command, args, options) => {
|
|
|
19243
19549
|
|
|
19244
19550
|
// src/network/webdav.ts
|
|
19245
19551
|
import { createReadStream } from "fs";
|
|
19246
|
-
import { mkdir as mkdir29, readdir as
|
|
19552
|
+
import { mkdir as mkdir29, readdir as readdir18, realpath as realpath2, stat as stat9 } from "fs/promises";
|
|
19247
19553
|
import { createServer } from "http";
|
|
19248
19554
|
import { timingSafeEqual } from "crypto";
|
|
19249
|
-
import
|
|
19555
|
+
import path41 from "path";
|
|
19250
19556
|
import { pipeline } from "stream/promises";
|
|
19251
19557
|
import { URL as URL2 } from "url";
|
|
19252
19558
|
function hostToUrlAuthority(host) {
|
|
@@ -19282,10 +19588,10 @@ var WebDavServer = class _WebDavServer {
|
|
|
19282
19588
|
const allowedRoots = [];
|
|
19283
19589
|
const aliasSet = /* @__PURE__ */ new Set();
|
|
19284
19590
|
for (const dir of options.allowlistDirs) {
|
|
19285
|
-
const resolved =
|
|
19591
|
+
const resolved = path41.resolve(dir);
|
|
19286
19592
|
await mkdir29(resolved, { recursive: true });
|
|
19287
19593
|
const canonical = await realpath2(resolved);
|
|
19288
|
-
const alias =
|
|
19594
|
+
const alias = path41.basename(canonical) || "root";
|
|
19289
19595
|
if (aliasSet.has(alias)) {
|
|
19290
19596
|
throw new Error(`duplicate webdav allowlist alias: ${alias}`);
|
|
19291
19597
|
}
|
|
@@ -19441,7 +19747,7 @@ var WebDavServer = class _WebDavServer {
|
|
|
19441
19747
|
if (decodedPath.includes("\0")) {
|
|
19442
19748
|
return { ok: false, code: 400, message: "invalid path" };
|
|
19443
19749
|
}
|
|
19444
|
-
const normalized =
|
|
19750
|
+
const normalized = path41.posix.normalize(decodedPath);
|
|
19445
19751
|
const segments = normalized.split("/").filter((segment) => segment.length > 0);
|
|
19446
19752
|
if (segments.length === 0) {
|
|
19447
19753
|
return { ok: false, code: 403, message: "root listing is not allowed" };
|
|
@@ -19455,7 +19761,7 @@ var WebDavServer = class _WebDavServer {
|
|
|
19455
19761
|
if (relative.some((segment) => segment === ".." || segment.includes("\\"))) {
|
|
19456
19762
|
return { ok: false, code: 403, message: "path traversal is not allowed" };
|
|
19457
19763
|
}
|
|
19458
|
-
const candidate =
|
|
19764
|
+
const candidate = path41.resolve(root.absolute, ...relative);
|
|
19459
19765
|
if (!this.isPathInside(root.absolute, candidate)) {
|
|
19460
19766
|
return { ok: false, code: 403, message: "path escaped allowlist" };
|
|
19461
19767
|
}
|
|
@@ -19511,7 +19817,7 @@ var WebDavServer = class _WebDavServer {
|
|
|
19511
19817
|
}
|
|
19512
19818
|
const entries = [];
|
|
19513
19819
|
if (info.isDirectory()) {
|
|
19514
|
-
const children = await
|
|
19820
|
+
const children = await readdir18(absolutePath, { withFileTypes: true });
|
|
19515
19821
|
for (const child of children) {
|
|
19516
19822
|
const childHref = toEncodedHref(`${displayPath.replace(/\/$/, "")}/${child.name}`);
|
|
19517
19823
|
entries.push(`
|
|
@@ -19533,10 +19839,10 @@ var WebDavServer = class _WebDavServer {
|
|
|
19533
19839
|
}
|
|
19534
19840
|
isPathInside(root, target) {
|
|
19535
19841
|
if (target === root) return true;
|
|
19536
|
-
if (root ===
|
|
19842
|
+
if (root === path41.parse(root).root) {
|
|
19537
19843
|
return target.startsWith(root);
|
|
19538
19844
|
}
|
|
19539
|
-
return target.startsWith(`${root}${
|
|
19845
|
+
return target.startsWith(`${root}${path41.sep}`);
|
|
19540
19846
|
}
|
|
19541
19847
|
};
|
|
19542
19848
|
function xmlEscape(value) {
|
|
@@ -19548,8 +19854,8 @@ function toEncodedHref(pathname) {
|
|
|
19548
19854
|
|
|
19549
19855
|
// src/compat/checks.ts
|
|
19550
19856
|
import { access as access2, readFile as readFile29 } from "fs/promises";
|
|
19551
|
-
import
|
|
19552
|
-
import { spawn as
|
|
19857
|
+
import path42 from "path";
|
|
19858
|
+
import { spawn as spawn4 } from "child_process";
|
|
19553
19859
|
var REQUIRED_HOOKS = ["before_agent_start", "agent_end"];
|
|
19554
19860
|
function isSafeCommandToken(command) {
|
|
19555
19861
|
return /^[a-zA-Z0-9._-]+$/.test(command);
|
|
@@ -19560,7 +19866,7 @@ var defaultRunner = {
|
|
|
19560
19866
|
const binary = process.platform === "win32" ? "where" : "which";
|
|
19561
19867
|
const args = [command];
|
|
19562
19868
|
return new Promise((resolve) => {
|
|
19563
|
-
const child =
|
|
19869
|
+
const child = spawn4(binary, args, { stdio: "ignore" });
|
|
19564
19870
|
child.on("error", () => resolve(false));
|
|
19565
19871
|
child.on("close", (code) => resolve(code === 0));
|
|
19566
19872
|
});
|
|
@@ -19730,9 +20036,9 @@ function compareVersions(a, b) {
|
|
|
19730
20036
|
async function runCompatChecks(options) {
|
|
19731
20037
|
const checks = [];
|
|
19732
20038
|
const runner = options.runner ?? defaultRunner;
|
|
19733
|
-
const pluginJsonPath =
|
|
19734
|
-
const packageJsonPath =
|
|
19735
|
-
const indexPath =
|
|
20039
|
+
const pluginJsonPath = path42.join(options.repoRoot, "openclaw.plugin.json");
|
|
20040
|
+
const packageJsonPath = path42.join(options.repoRoot, "package.json");
|
|
20041
|
+
const indexPath = path42.join(options.repoRoot, "src", "index.ts");
|
|
19736
20042
|
let pluginRaw = "";
|
|
19737
20043
|
let pluginManifestPresent = false;
|
|
19738
20044
|
try {
|
|
@@ -20052,6 +20358,9 @@ async function runMigrateObservationsCliCommand(options) {
|
|
|
20052
20358
|
now: options.now
|
|
20053
20359
|
});
|
|
20054
20360
|
}
|
|
20361
|
+
async function runConversationIndexHealthCliCommand(orchestrator) {
|
|
20362
|
+
return orchestrator.getConversationIndexHealth();
|
|
20363
|
+
}
|
|
20055
20364
|
async function runTailscaleStatusCliCommand(options = {}) {
|
|
20056
20365
|
const helper = options.helper ?? new TailscaleHelper({ timeoutMs: options.timeoutMs });
|
|
20057
20366
|
return helper.status();
|
|
@@ -20392,25 +20701,25 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
20392
20701
|
const ns = (namespace ?? "").trim();
|
|
20393
20702
|
if (!ns) return orchestrator.config.memoryDir;
|
|
20394
20703
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
20395
|
-
const candidate =
|
|
20704
|
+
const candidate = path43.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
20396
20705
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
20397
20706
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
20398
20707
|
}
|
|
20399
20708
|
return candidate;
|
|
20400
20709
|
}
|
|
20401
20710
|
async function readAllMemoryFiles(memoryDir) {
|
|
20402
|
-
const roots = [
|
|
20711
|
+
const roots = [path43.join(memoryDir, "facts"), path43.join(memoryDir, "corrections")];
|
|
20403
20712
|
const out = [];
|
|
20404
20713
|
const walk = async (dir) => {
|
|
20405
20714
|
let entries;
|
|
20406
20715
|
try {
|
|
20407
|
-
entries = await
|
|
20716
|
+
entries = await readdir19(dir, { withFileTypes: true });
|
|
20408
20717
|
} catch {
|
|
20409
20718
|
return;
|
|
20410
20719
|
}
|
|
20411
20720
|
for (const entry of entries) {
|
|
20412
20721
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
20413
|
-
const fullPath =
|
|
20722
|
+
const fullPath = path43.join(dir, entryName);
|
|
20414
20723
|
if (entry.isDirectory()) {
|
|
20415
20724
|
await walk(fullPath);
|
|
20416
20725
|
continue;
|
|
@@ -20678,6 +20987,11 @@ function registerCli(api, orchestrator) {
|
|
|
20678
20987
|
}
|
|
20679
20988
|
console.log("OK");
|
|
20680
20989
|
});
|
|
20990
|
+
cmd.command("conversation-index-health").description("Show conversation index backend health and index stats").action(async () => {
|
|
20991
|
+
const health = await runConversationIndexHealthCliCommand(orchestrator);
|
|
20992
|
+
console.log(JSON.stringify(health, null, 2));
|
|
20993
|
+
console.log("OK");
|
|
20994
|
+
});
|
|
20681
20995
|
cmd.command("tailscale-status").description("Show Tailscale availability and daemon status").option("--timeout-ms <n>", "Command timeout in milliseconds", "10000").action(async (...args) => {
|
|
20682
20996
|
const options = args[0] ?? {};
|
|
20683
20997
|
const timeoutMsRaw = parseInt(String(options.timeoutMs ?? "10000"), 10);
|
|
@@ -21182,7 +21496,7 @@ function registerCli(api, orchestrator) {
|
|
|
21182
21496
|
}
|
|
21183
21497
|
});
|
|
21184
21498
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
21185
|
-
const workspaceDir =
|
|
21499
|
+
const workspaceDir = path43.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
21186
21500
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
21187
21501
|
if (!identity) {
|
|
21188
21502
|
console.log("No identity file found.");
|
|
@@ -21405,8 +21719,8 @@ function registerCli(api, orchestrator) {
|
|
|
21405
21719
|
const options = args[0] ?? {};
|
|
21406
21720
|
const threadId = options.thread;
|
|
21407
21721
|
const top = parseInt(options.top ?? "10", 10);
|
|
21408
|
-
const memoryDir =
|
|
21409
|
-
const threading = new ThreadingManager(
|
|
21722
|
+
const memoryDir = path43.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
21723
|
+
const threading = new ThreadingManager(path43.join(memoryDir, "threads"));
|
|
21410
21724
|
if (threadId) {
|
|
21411
21725
|
const thread = await threading.loadThread(threadId);
|
|
21412
21726
|
if (!thread) {
|
|
@@ -21581,14 +21895,14 @@ function parseDuration(duration) {
|
|
|
21581
21895
|
// src/index.ts
|
|
21582
21896
|
import { readFile as readFile31, writeFile as writeFile26 } from "fs/promises";
|
|
21583
21897
|
import { readFileSync as readFileSync4 } from "fs";
|
|
21584
|
-
import
|
|
21898
|
+
import path44 from "path";
|
|
21585
21899
|
import os5 from "os";
|
|
21586
21900
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
21587
21901
|
function loadPluginConfigFromFile() {
|
|
21588
21902
|
try {
|
|
21589
21903
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
21590
21904
|
const homeDir = process.env.HOME ?? os5.homedir();
|
|
21591
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
21905
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path44.join(homeDir, ".openclaw", "openclaw.json");
|
|
21592
21906
|
const content = readFileSync4(configPath, "utf-8");
|
|
21593
21907
|
const config = JSON.parse(content);
|
|
21594
21908
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
@@ -21796,7 +22110,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
21796
22110
|
);
|
|
21797
22111
|
async function ensureHourlySummaryCron(api2) {
|
|
21798
22112
|
const jobId = "engram-hourly-summary";
|
|
21799
|
-
const cronFilePath =
|
|
22113
|
+
const cronFilePath = path44.join(os5.homedir(), ".openclaw", "cron", "jobs.json");
|
|
21800
22114
|
try {
|
|
21801
22115
|
let jobsData = { version: 1, jobs: [] };
|
|
21802
22116
|
try {
|