@joshuaswarren/openclaw-engram 9.0.4 → 9.0.6
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 +221 -28
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +25 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -246,6 +246,8 @@ function parseConfig(raw) {
|
|
|
246
246
|
checkpointEnabled: cfg.checkpointEnabled !== false,
|
|
247
247
|
// default: true
|
|
248
248
|
checkpointTurns: typeof cfg.checkpointTurns === "number" ? cfg.checkpointTurns : 15,
|
|
249
|
+
// Compaction reset (opt-in, default: false)
|
|
250
|
+
compactionResetEnabled: cfg.compactionResetEnabled === true,
|
|
249
251
|
// Hourly summaries
|
|
250
252
|
hourlySummariesEnabled: cfg.hourlySummariesEnabled !== false,
|
|
251
253
|
// default: true
|
|
@@ -467,6 +469,9 @@ function parseConfig(raw) {
|
|
|
467
469
|
graphExpansionBlendMin: typeof cfg.graphExpansionBlendMin === "number" ? Math.min(1, Math.max(0, cfg.graphExpansionBlendMin)) : 0.05,
|
|
468
470
|
graphExpansionBlendMax: typeof cfg.graphExpansionBlendMax === "number" ? Math.min(1, Math.max(0, cfg.graphExpansionBlendMax)) : 0.95,
|
|
469
471
|
maxEntityGraphEdgesPerMemory: typeof cfg.maxEntityGraphEdgesPerMemory === "number" ? Math.max(0, cfg.maxEntityGraphEdgesPerMemory) : 10,
|
|
472
|
+
graphLateralInhibitionEnabled: cfg.graphLateralInhibitionEnabled !== false,
|
|
473
|
+
graphLateralInhibitionBeta: typeof cfg.graphLateralInhibitionBeta === "number" ? Math.max(0, Math.min(1, cfg.graphLateralInhibitionBeta)) : 0.15,
|
|
474
|
+
graphLateralInhibitionTopM: typeof cfg.graphLateralInhibitionTopM === "number" ? Math.max(0, Math.round(cfg.graphLateralInhibitionTopM)) : 7,
|
|
470
475
|
// v8.2: Temporal Memory Tree
|
|
471
476
|
temporalMemoryTreeEnabled: cfg.temporalMemoryTreeEnabled === true,
|
|
472
477
|
tmtHourlyMinMemories: typeof cfg.tmtHourlyMinMemories === "number" ? cfg.tmtHourlyMinMemories : 3,
|
|
@@ -570,8 +575,9 @@ function buildRecallPipelineConfig(cfg) {
|
|
|
570
575
|
|
|
571
576
|
// src/orchestrator.ts
|
|
572
577
|
import path30 from "path";
|
|
578
|
+
import os5 from "os";
|
|
573
579
|
import { createHash as createHash6 } from "crypto";
|
|
574
|
-
import { mkdir as mkdir21, readdir as readdir14, readFile as readFile22, writeFile as writeFile20 } from "fs/promises";
|
|
580
|
+
import { mkdir as mkdir21, readdir as readdir14, readFile as readFile22, stat as stat6, unlink as unlink5, writeFile as writeFile20 } from "fs/promises";
|
|
575
581
|
|
|
576
582
|
// src/signal.ts
|
|
577
583
|
var BUILTIN_HIGH_PATTERNS = [
|
|
@@ -13634,6 +13640,15 @@ var GraphIndex = class {
|
|
|
13634
13640
|
}
|
|
13635
13641
|
}
|
|
13636
13642
|
}
|
|
13643
|
+
if (this.cfg.graphLateralInhibitionEnabled && scores.size > 1) {
|
|
13644
|
+
const inhibited = applyLateralInhibition(scores, {
|
|
13645
|
+
beta: this.cfg.graphLateralInhibitionBeta,
|
|
13646
|
+
topM: this.cfg.graphLateralInhibitionTopM
|
|
13647
|
+
});
|
|
13648
|
+
for (const [k, v] of inhibited) {
|
|
13649
|
+
scores.set(k, v);
|
|
13650
|
+
}
|
|
13651
|
+
}
|
|
13637
13652
|
return Array.from(scores.entries()).map(([p, score]) => ({
|
|
13638
13653
|
path: p,
|
|
13639
13654
|
score,
|
|
@@ -13649,6 +13664,23 @@ var GraphIndex = class {
|
|
|
13649
13664
|
}
|
|
13650
13665
|
}
|
|
13651
13666
|
};
|
|
13667
|
+
function applyLateralInhibition(scores, opts) {
|
|
13668
|
+
const { beta, topM } = opts;
|
|
13669
|
+
if (beta === 0 || topM === 0) return new Map(scores);
|
|
13670
|
+
const sorted = Array.from(scores.entries()).sort((a, b) => b[1] - a[1]);
|
|
13671
|
+
const topCompetitors = sorted.slice(0, topM);
|
|
13672
|
+
const result = /* @__PURE__ */ new Map();
|
|
13673
|
+
for (const [node, u] of scores) {
|
|
13674
|
+
let inhibition = 0;
|
|
13675
|
+
for (const [, uK] of topCompetitors) {
|
|
13676
|
+
if (uK > u) {
|
|
13677
|
+
inhibition += uK - u;
|
|
13678
|
+
}
|
|
13679
|
+
}
|
|
13680
|
+
result.set(node, Math.max(0, u - beta * inhibition));
|
|
13681
|
+
}
|
|
13682
|
+
return result;
|
|
13683
|
+
}
|
|
13652
13684
|
|
|
13653
13685
|
// src/replay/types.ts
|
|
13654
13686
|
var VALID_SOURCES = /* @__PURE__ */ new Set(["openclaw", "claude", "chatgpt"]);
|
|
@@ -15851,6 +15883,15 @@ function dedupeBehaviorSignalsByMemoryAndHash(signals) {
|
|
|
15851
15883
|
}
|
|
15852
15884
|
|
|
15853
15885
|
// src/orchestrator.ts
|
|
15886
|
+
var COMPACTION_SIGNAL_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
15887
|
+
function defaultWorkspaceDir() {
|
|
15888
|
+
return path30.join(os5.homedir(), ".openclaw", "workspace");
|
|
15889
|
+
}
|
|
15890
|
+
function sanitizeSessionKeyForFilename(sessionKey) {
|
|
15891
|
+
const readable = sessionKey.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
15892
|
+
const hash = createHash6("sha256").update(sessionKey).digest("hex").slice(0, 12);
|
|
15893
|
+
return `${readable}-${hash}`;
|
|
15894
|
+
}
|
|
15854
15895
|
function isArtifactMemoryPath(filePath) {
|
|
15855
15896
|
return /(?:^|[\\/])artifacts(?:[\\/]|$)/i.test(filePath);
|
|
15856
15897
|
}
|
|
@@ -16131,6 +16172,13 @@ var Orchestrator = class _Orchestrator {
|
|
|
16131
16172
|
/** Temporal Memory Tree builder — builds hour/day/week/persona summary nodes. */
|
|
16132
16173
|
tmtBuilder;
|
|
16133
16174
|
rerankCache = new RerankCache();
|
|
16175
|
+
/**
|
|
16176
|
+
* Per-session workspace overrides keyed by sessionKey.
|
|
16177
|
+
* Set by the before_agent_start hook so recall() uses the correct
|
|
16178
|
+
* agent workspace for BOOT.md injection. Cleared after each recall.
|
|
16179
|
+
* Using a Map prevents concurrent sessions from overwriting each other.
|
|
16180
|
+
*/
|
|
16181
|
+
_recallWorkspaceOverrides = /* @__PURE__ */ new Map();
|
|
16134
16182
|
routingRulesStore = null;
|
|
16135
16183
|
contentHashIndex = null;
|
|
16136
16184
|
artifactSourceStatusCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -16163,6 +16211,14 @@ var Orchestrator = class _Orchestrator {
|
|
|
16163
16211
|
// Initialization gate: recall() awaits this before proceeding
|
|
16164
16212
|
initPromise = null;
|
|
16165
16213
|
resolveInit = null;
|
|
16214
|
+
/** Set per-session workspace for the next recall() call (compaction reset). @internal */
|
|
16215
|
+
setRecallWorkspaceOverride(sessionKey, dir) {
|
|
16216
|
+
this._recallWorkspaceOverrides.set(sessionKey, dir);
|
|
16217
|
+
}
|
|
16218
|
+
/** Remove a per-session workspace override (cleanup on error or early return). @internal */
|
|
16219
|
+
clearRecallWorkspaceOverride(sessionKey) {
|
|
16220
|
+
this._recallWorkspaceOverrides.delete(sessionKey);
|
|
16221
|
+
}
|
|
16166
16222
|
constructor(config) {
|
|
16167
16223
|
this.config = config;
|
|
16168
16224
|
this.storageRouter = new NamespaceStorageRouter(config);
|
|
@@ -16440,6 +16496,24 @@ var Orchestrator = class _Orchestrator {
|
|
|
16440
16496
|
if (this.config.localLlmEnabled) {
|
|
16441
16497
|
await this.validateLocalLlmModel();
|
|
16442
16498
|
}
|
|
16499
|
+
if (this.config.compactionResetEnabled) {
|
|
16500
|
+
try {
|
|
16501
|
+
const wsDir = this.config.workspaceDir || defaultWorkspaceDir();
|
|
16502
|
+
const files = await readdir14(wsDir).catch(() => []);
|
|
16503
|
+
for (const f of files) {
|
|
16504
|
+
if (!f.startsWith(".compaction-reset-signal-")) continue;
|
|
16505
|
+
const fp = path30.join(wsDir, f);
|
|
16506
|
+
const s = await stat6(fp).catch(() => null);
|
|
16507
|
+
if (s && Date.now() - s.mtimeMs >= COMPACTION_SIGNAL_MAX_AGE_MS) {
|
|
16508
|
+
await unlink5(fp).catch(() => {
|
|
16509
|
+
});
|
|
16510
|
+
log.debug(`initialize: removed stale compaction signal ${f}`);
|
|
16511
|
+
}
|
|
16512
|
+
}
|
|
16513
|
+
} catch (err) {
|
|
16514
|
+
log.debug("initialize: stale signal sweep failed:", err);
|
|
16515
|
+
}
|
|
16516
|
+
}
|
|
16443
16517
|
log.info("orchestrator initialized");
|
|
16444
16518
|
if (this.resolveInit) {
|
|
16445
16519
|
this.resolveInit();
|
|
@@ -17156,6 +17230,8 @@ ${r.snippet.trim()}
|
|
|
17156
17230
|
);
|
|
17157
17231
|
const embeddingFetchLimit = computedFetchLimit;
|
|
17158
17232
|
if (recallMode === "no_recall") {
|
|
17233
|
+
const earlySessionKey = sessionKey ?? "default";
|
|
17234
|
+
this._recallWorkspaceOverrides.delete(earlySessionKey);
|
|
17159
17235
|
timings.total = `${Date.now() - recallStart}ms`;
|
|
17160
17236
|
this.emitTrace({
|
|
17161
17237
|
kind: "recall_summary",
|
|
@@ -17329,6 +17405,58 @@ ${formatted}`;
|
|
|
17329
17405
|
timings.transcript = `${Date.now() - t0}ms`;
|
|
17330
17406
|
return section;
|
|
17331
17407
|
})();
|
|
17408
|
+
const compactionPromise = (async () => {
|
|
17409
|
+
const effectiveSessionKey = sessionKey ?? "default";
|
|
17410
|
+
const compactionWorkspaceDir = this._recallWorkspaceOverrides.get(effectiveSessionKey);
|
|
17411
|
+
this._recallWorkspaceOverrides.delete(effectiveSessionKey);
|
|
17412
|
+
if (!this.config.compactionResetEnabled) return null;
|
|
17413
|
+
const workspaceDir = compactionWorkspaceDir || this.config.workspaceDir || defaultWorkspaceDir();
|
|
17414
|
+
const safeSessionKey = sanitizeSessionKeyForFilename(effectiveSessionKey);
|
|
17415
|
+
const signalPath = path30.join(workspaceDir, `.compaction-reset-signal-${safeSessionKey}`);
|
|
17416
|
+
const bootPath = path30.join(workspaceDir, "BOOT.md");
|
|
17417
|
+
try {
|
|
17418
|
+
const signalStat = await stat6(signalPath).catch(() => null);
|
|
17419
|
+
if (!signalStat) return null;
|
|
17420
|
+
const signalAge = Date.now() - signalStat.mtimeMs;
|
|
17421
|
+
const signalData = JSON.parse(await readFile22(signalPath, "utf-8"));
|
|
17422
|
+
if (signalData.sessionKey !== effectiveSessionKey) {
|
|
17423
|
+
log.debug(
|
|
17424
|
+
`recall: compaction signal is for ${signalData.sessionKey}, not ${effectiveSessionKey} \u2014 skipping`
|
|
17425
|
+
);
|
|
17426
|
+
return null;
|
|
17427
|
+
}
|
|
17428
|
+
if (signalAge >= COMPACTION_SIGNAL_MAX_AGE_MS) {
|
|
17429
|
+
log.debug(
|
|
17430
|
+
`recall: stale compaction signal (${Math.round(signalAge / 1e3)}s old), skipping`
|
|
17431
|
+
);
|
|
17432
|
+
await unlink5(signalPath).catch(() => {
|
|
17433
|
+
});
|
|
17434
|
+
return null;
|
|
17435
|
+
}
|
|
17436
|
+
let section = "\n\n## Session Recovery (Post-Compaction)\n\n";
|
|
17437
|
+
section += `\u26A0\uFE0F A compaction occurred at ${signalData.compactedAt} and this is a fresh session.
|
|
17438
|
+
|
|
17439
|
+
`;
|
|
17440
|
+
try {
|
|
17441
|
+
const bootContent = await readFile22(bootPath, "utf-8");
|
|
17442
|
+
section += "### BOOT.md (working state before compaction)\n\n";
|
|
17443
|
+
section += bootContent + "\n";
|
|
17444
|
+
} catch {
|
|
17445
|
+
section += "### \u26A0\uFE0F BOOT.md is MISSING\n\n";
|
|
17446
|
+
section += "The memory flush may not have written BOOT.md before compaction. ";
|
|
17447
|
+
section += "Ask the user what you were working on \u2014 do not guess.\n";
|
|
17448
|
+
}
|
|
17449
|
+
log.info(`recall: injected compaction reset context for ${effectiveSessionKey}`);
|
|
17450
|
+
await unlink5(signalPath).catch(() => {
|
|
17451
|
+
});
|
|
17452
|
+
return section;
|
|
17453
|
+
} catch (err) {
|
|
17454
|
+
log.debug("recall: compaction signal check failed:", err);
|
|
17455
|
+
await unlink5(signalPath).catch(() => {
|
|
17456
|
+
});
|
|
17457
|
+
return null;
|
|
17458
|
+
}
|
|
17459
|
+
})();
|
|
17332
17460
|
const summariesPromise = (async () => {
|
|
17333
17461
|
const t0 = Date.now();
|
|
17334
17462
|
if (!this.config.hourlySummariesEnabled || !sessionKey || !this.isRecallSectionEnabled("summaries", true)) {
|
|
@@ -17416,6 +17544,7 @@ ${formatted}`;
|
|
|
17416
17544
|
artifacts,
|
|
17417
17545
|
qmdResult,
|
|
17418
17546
|
transcriptSection,
|
|
17547
|
+
compactionSection,
|
|
17419
17548
|
summariesSection,
|
|
17420
17549
|
conversationRecallSection,
|
|
17421
17550
|
compoundingSection
|
|
@@ -17427,6 +17556,7 @@ ${formatted}`;
|
|
|
17427
17556
|
artifactsPromise,
|
|
17428
17557
|
qmdPromise,
|
|
17429
17558
|
transcriptPromise,
|
|
17559
|
+
compactionPromise,
|
|
17430
17560
|
summariesPromise,
|
|
17431
17561
|
conversationRecallPromise,
|
|
17432
17562
|
compoundingPromise
|
|
@@ -17794,6 +17924,9 @@ ${tmtNode.summary}`);
|
|
|
17794
17924
|
if (transcriptSection) {
|
|
17795
17925
|
this.appendRecallSection(sectionBuckets, "transcript", transcriptSection);
|
|
17796
17926
|
}
|
|
17927
|
+
if (compactionSection) {
|
|
17928
|
+
this.appendRecallSection(sectionBuckets, "compaction-reset", compactionSection);
|
|
17929
|
+
}
|
|
17797
17930
|
if (summariesSection) {
|
|
17798
17931
|
this.appendRecallSection(sectionBuckets, "summaries", summariesSection);
|
|
17799
17932
|
}
|
|
@@ -22477,7 +22610,7 @@ promotionCandidates: ${res.promotionCandidateCount}`
|
|
|
22477
22610
|
|
|
22478
22611
|
// src/cli.ts
|
|
22479
22612
|
import path50 from "path";
|
|
22480
|
-
import { access as access3, readFile as readFile36, readdir as readdir22, unlink as
|
|
22613
|
+
import { access as access3, readFile as readFile36, readdir as readdir22, unlink as unlink7 } from "fs/promises";
|
|
22481
22614
|
import { createHash as createHash10 } from "crypto";
|
|
22482
22615
|
|
|
22483
22616
|
// src/transfer/export-json.ts
|
|
@@ -22490,7 +22623,7 @@ var EXPORT_SCHEMA_VERSION = 1;
|
|
|
22490
22623
|
|
|
22491
22624
|
// src/transfer/fs-utils.ts
|
|
22492
22625
|
import { createHash as createHash8 } from "crypto";
|
|
22493
|
-
import { mkdir as mkdir23, readdir as readdir16, readFile as readFile24, stat as
|
|
22626
|
+
import { mkdir as mkdir23, readdir as readdir16, readFile as readFile24, stat as stat7, writeFile as writeFile22 } from "fs/promises";
|
|
22494
22627
|
import path33 from "path";
|
|
22495
22628
|
async function sha256File(filePath) {
|
|
22496
22629
|
const buf = await readFile24(filePath);
|
|
@@ -22528,7 +22661,7 @@ async function listFilesRecursive(rootDir) {
|
|
|
22528
22661
|
}
|
|
22529
22662
|
async function fileExists(filePath) {
|
|
22530
22663
|
try {
|
|
22531
|
-
await
|
|
22664
|
+
await stat7(filePath);
|
|
22532
22665
|
return true;
|
|
22533
22666
|
} catch {
|
|
22534
22667
|
return false;
|
|
@@ -22927,12 +23060,12 @@ async function importMarkdownBundle(opts) {
|
|
|
22927
23060
|
|
|
22928
23061
|
// src/transfer/autodetect.ts
|
|
22929
23062
|
import path41 from "path";
|
|
22930
|
-
import { stat as
|
|
23063
|
+
import { stat as stat8 } from "fs/promises";
|
|
22931
23064
|
async function detectImportFormat(fromPath) {
|
|
22932
23065
|
const abs = path41.resolve(fromPath);
|
|
22933
23066
|
let st;
|
|
22934
23067
|
try {
|
|
22935
|
-
st = await
|
|
23068
|
+
st = await stat8(abs);
|
|
22936
23069
|
} catch {
|
|
22937
23070
|
return null;
|
|
22938
23071
|
}
|
|
@@ -23415,7 +23548,7 @@ var openclawReplayNormalizer = {
|
|
|
23415
23548
|
|
|
23416
23549
|
// src/maintenance/archive-observations.ts
|
|
23417
23550
|
import path42 from "path";
|
|
23418
|
-
import { mkdir as mkdir30, readdir as readdir18, readFile as readFile29, unlink as
|
|
23551
|
+
import { mkdir as mkdir30, readdir as readdir18, readFile as readFile29, unlink as unlink6, writeFile as writeFile27 } from "fs/promises";
|
|
23419
23552
|
var DATE_FILE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})\.(jsonl|md)$/;
|
|
23420
23553
|
function normalizeRetentionDays(value) {
|
|
23421
23554
|
if (!Number.isFinite(value)) return 30;
|
|
@@ -23495,7 +23628,7 @@ async function archiveObservations(options) {
|
|
|
23495
23628
|
await mkdir30(archiveDir, { recursive: true });
|
|
23496
23629
|
const raw = await readFile29(candidate.absolutePath);
|
|
23497
23630
|
await writeFile27(archivePath, raw);
|
|
23498
|
-
await
|
|
23631
|
+
await unlink6(candidate.absolutePath);
|
|
23499
23632
|
archivedFiles += 1;
|
|
23500
23633
|
archivedBytes += raw.byteLength;
|
|
23501
23634
|
archivedRelativePaths.push(candidate.relativePath);
|
|
@@ -23827,7 +23960,7 @@ async function migrateObservations(options) {
|
|
|
23827
23960
|
}
|
|
23828
23961
|
|
|
23829
23962
|
// src/network/tailscale.ts
|
|
23830
|
-
import { stat as
|
|
23963
|
+
import { stat as stat9 } from "fs/promises";
|
|
23831
23964
|
import { spawn as spawn3 } from "child_process";
|
|
23832
23965
|
var TailscaleHelper = class {
|
|
23833
23966
|
tailscaleBinary;
|
|
@@ -23892,7 +24025,7 @@ var TailscaleHelper = class {
|
|
|
23892
24025
|
}
|
|
23893
24026
|
};
|
|
23894
24027
|
async function assertReadableDirectory(dir) {
|
|
23895
|
-
const info = await
|
|
24028
|
+
const info = await stat9(dir);
|
|
23896
24029
|
if (!info.isDirectory()) {
|
|
23897
24030
|
throw new Error(`sourceDir must be a directory: ${dir}`);
|
|
23898
24031
|
}
|
|
@@ -23939,7 +24072,7 @@ var defaultCommandRunner = (command, args, options) => {
|
|
|
23939
24072
|
|
|
23940
24073
|
// src/network/webdav.ts
|
|
23941
24074
|
import { createReadStream } from "fs";
|
|
23942
|
-
import { mkdir as mkdir32, readdir as readdir21, realpath as realpath2, stat as
|
|
24075
|
+
import { mkdir as mkdir32, readdir as readdir21, realpath as realpath2, stat as stat10 } from "fs/promises";
|
|
23943
24076
|
import { createServer } from "http";
|
|
23944
24077
|
import { timingSafeEqual } from "crypto";
|
|
23945
24078
|
import path46 from "path";
|
|
@@ -24175,7 +24308,7 @@ var WebDavServer = class _WebDavServer {
|
|
|
24175
24308
|
async handleRead(method, absolutePath, res) {
|
|
24176
24309
|
let info;
|
|
24177
24310
|
try {
|
|
24178
|
-
info = await
|
|
24311
|
+
info = await stat10(absolutePath);
|
|
24179
24312
|
} catch {
|
|
24180
24313
|
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
24181
24314
|
res.end("not found");
|
|
@@ -24199,7 +24332,7 @@ var WebDavServer = class _WebDavServer {
|
|
|
24199
24332
|
async handlePropfind(absolutePath, displayPath, res) {
|
|
24200
24333
|
let info;
|
|
24201
24334
|
try {
|
|
24202
|
-
info = await
|
|
24335
|
+
info = await stat10(absolutePath);
|
|
24203
24336
|
} catch {
|
|
24204
24337
|
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
24205
24338
|
res.end("not found");
|
|
@@ -26656,7 +26789,7 @@ function registerCli(api, orchestrator) {
|
|
|
26656
26789
|
let deleted = 0;
|
|
26657
26790
|
for (const filePath of plan.deletePaths) {
|
|
26658
26791
|
try {
|
|
26659
|
-
await
|
|
26792
|
+
await unlink7(filePath);
|
|
26660
26793
|
deleted += 1;
|
|
26661
26794
|
} catch (err) {
|
|
26662
26795
|
console.log(` failed to delete ${filePath}: ${String(err)}`);
|
|
@@ -26704,7 +26837,7 @@ function registerCli(api, orchestrator) {
|
|
|
26704
26837
|
let deleted = 0;
|
|
26705
26838
|
for (const filePath of plan.deletePaths) {
|
|
26706
26839
|
try {
|
|
26707
|
-
await
|
|
26840
|
+
await unlink7(filePath);
|
|
26708
26841
|
deleted += 1;
|
|
26709
26842
|
} catch (err) {
|
|
26710
26843
|
console.log(` failed to delete ${filePath}: ${String(err)}`);
|
|
@@ -27268,12 +27401,13 @@ function parseDuration(duration) {
|
|
|
27268
27401
|
import { readFile as readFile37, writeFile as writeFile29 } from "fs/promises";
|
|
27269
27402
|
import { readFileSync as readFileSync4 } from "fs";
|
|
27270
27403
|
import path51 from "path";
|
|
27271
|
-
import
|
|
27404
|
+
import os6 from "os";
|
|
27272
27405
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
27406
|
+
var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
|
|
27273
27407
|
function loadPluginConfigFromFile() {
|
|
27274
27408
|
try {
|
|
27275
27409
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
27276
|
-
const homeDir = process.env.HOME ??
|
|
27410
|
+
const homeDir = process.env.HOME ?? os6.homedir();
|
|
27277
27411
|
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path51.join(homeDir, ".openclaw", "openclaw.json");
|
|
27278
27412
|
const content = readFileSync4(configPath, "utf-8");
|
|
27279
27413
|
const config = JSON.parse(content);
|
|
@@ -27321,13 +27455,19 @@ var index_default = {
|
|
|
27321
27455
|
log.info(
|
|
27322
27456
|
`initialized (debug=${cfg.debug}, qmdEnabled=${cfg.qmdEnabled}, transcriptEnabled=${cfg.transcriptEnabled}, hourlySummariesEnabled=${cfg.hourlySummariesEnabled}, localLlmEnabled=${cfg.localLlmEnabled})`
|
|
27323
27457
|
);
|
|
27324
|
-
if (globalThis[ENGRAM_REGISTERED_GUARD] === true) {
|
|
27325
|
-
log.debug("register called more than once; skipping duplicate hook/tool registration");
|
|
27326
|
-
return;
|
|
27327
|
-
}
|
|
27328
|
-
globalThis[ENGRAM_REGISTERED_GUARD] = true;
|
|
27329
27458
|
const existing = globalThis.__openclawEngramOrchestrator;
|
|
27330
27459
|
const orchestrator = existing?.recall ? existing : new Orchestrator(cfg);
|
|
27460
|
+
const isFirstRegistration = !globalThis[ENGRAM_REGISTERED_GUARD];
|
|
27461
|
+
globalThis[ENGRAM_REGISTERED_GUARD] = true;
|
|
27462
|
+
const hookApis = globalThis[ENGRAM_HOOK_APIS] ??= /* @__PURE__ */ new WeakSet();
|
|
27463
|
+
if (hookApis.has(api)) {
|
|
27464
|
+
log.debug("register: this api already has hooks bound \u2014 skipping duplicate hook registration");
|
|
27465
|
+
return;
|
|
27466
|
+
}
|
|
27467
|
+
hookApis.add(api);
|
|
27468
|
+
if (!isFirstRegistration) {
|
|
27469
|
+
log.debug("register called again (new registry); re-registering hooks with shared orchestrator");
|
|
27470
|
+
}
|
|
27331
27471
|
globalThis.__openclawEngramOrchestrator = orchestrator;
|
|
27332
27472
|
if (globalThis.__openclawEngramTrace === void 0) {
|
|
27333
27473
|
globalThis.__openclawEngramTrace = void 0;
|
|
@@ -27359,6 +27499,12 @@ var index_default = {
|
|
|
27359
27499
|
}
|
|
27360
27500
|
try {
|
|
27361
27501
|
await orchestrator.maybeRunFileHygiene().catch(() => void 0);
|
|
27502
|
+
if (orchestrator.config.compactionResetEnabled) {
|
|
27503
|
+
const agentWorkspace = ctx?.workspaceDir;
|
|
27504
|
+
if (agentWorkspace) {
|
|
27505
|
+
orchestrator.setRecallWorkspaceOverride(sessionKey, agentWorkspace);
|
|
27506
|
+
}
|
|
27507
|
+
}
|
|
27362
27508
|
const context = await orchestrator.recall(prompt, sessionKey);
|
|
27363
27509
|
log.debug(`before_agent_start: recall returned ${context?.length ?? 0} chars`);
|
|
27364
27510
|
if (!context) return;
|
|
@@ -27378,6 +27524,9 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
27378
27524
|
};
|
|
27379
27525
|
} catch (err) {
|
|
27380
27526
|
log.error("recall failed", err);
|
|
27527
|
+
if (orchestrator.config.compactionResetEnabled) {
|
|
27528
|
+
orchestrator.clearRecallWorkspaceOverride(sessionKey);
|
|
27529
|
+
}
|
|
27381
27530
|
return;
|
|
27382
27531
|
}
|
|
27383
27532
|
}
|
|
@@ -27478,15 +27627,56 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
27478
27627
|
async (event, ctx) => {
|
|
27479
27628
|
const sessionKey = ctx?.sessionKey ?? "default";
|
|
27480
27629
|
try {
|
|
27481
|
-
|
|
27630
|
+
if (!orchestrator.config.compactionResetEnabled) {
|
|
27631
|
+
log.debug(
|
|
27632
|
+
`compaction completed for ${sessionKey}, reset disabled \u2014 skipping`
|
|
27633
|
+
);
|
|
27634
|
+
return;
|
|
27635
|
+
}
|
|
27636
|
+
log.info(
|
|
27637
|
+
`compaction completed for ${sessionKey}, triggering session reset`
|
|
27638
|
+
);
|
|
27639
|
+
const workspaceDir = ctx?.workspaceDir || orchestrator.config.workspaceDir || defaultWorkspaceDir();
|
|
27640
|
+
const apiAny = api;
|
|
27641
|
+
if (typeof apiAny.resetSession === "function") {
|
|
27642
|
+
const result = await apiAny.resetSession(sessionKey, "new");
|
|
27643
|
+
if (result?.ok === true) {
|
|
27644
|
+
log.info(
|
|
27645
|
+
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
27646
|
+
);
|
|
27647
|
+
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
27648
|
+
const signalPath = path51.join(
|
|
27649
|
+
workspaceDir,
|
|
27650
|
+
`.compaction-reset-signal-${safeSessionKey}`
|
|
27651
|
+
);
|
|
27652
|
+
await writeFile29(
|
|
27653
|
+
signalPath,
|
|
27654
|
+
JSON.stringify({
|
|
27655
|
+
sessionKey,
|
|
27656
|
+
compactedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27657
|
+
messageCount: event.messageCount ?? 0
|
|
27658
|
+
}),
|
|
27659
|
+
"utf-8"
|
|
27660
|
+
);
|
|
27661
|
+
} else {
|
|
27662
|
+
const errorDetail = result && typeof result === "object" && "error" in result ? String(result.error ?? "unknown error") : `invalid result: ${JSON.stringify(result)}`;
|
|
27663
|
+
log.error(
|
|
27664
|
+
`api.resetSession failed for ${sessionKey}: ${errorDetail}`
|
|
27665
|
+
);
|
|
27666
|
+
}
|
|
27667
|
+
} else {
|
|
27668
|
+
log.error(
|
|
27669
|
+
`api.resetSession not available \u2014 compaction reset requires OC fork with PR #29985. Session ${sessionKey} will continue without reset.`
|
|
27670
|
+
);
|
|
27671
|
+
}
|
|
27482
27672
|
} catch (err) {
|
|
27483
|
-
log.error("after_compaction
|
|
27673
|
+
log.error("after_compaction reset failed", err);
|
|
27484
27674
|
}
|
|
27485
27675
|
}
|
|
27486
27676
|
);
|
|
27487
27677
|
async function ensureHourlySummaryCron(api2) {
|
|
27488
27678
|
const jobId = "engram-hourly-summary";
|
|
27489
|
-
const cronFilePath = path51.join(
|
|
27679
|
+
const cronFilePath = path51.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
|
|
27490
27680
|
try {
|
|
27491
27681
|
let jobsData = { version: 1, jobs: [] };
|
|
27492
27682
|
try {
|
|
@@ -27533,9 +27723,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
27533
27723
|
log.error("failed to auto-register hourly summary cron job:", err);
|
|
27534
27724
|
}
|
|
27535
27725
|
}
|
|
27536
|
-
|
|
27537
|
-
|
|
27538
|
-
|
|
27726
|
+
if (isFirstRegistration) {
|
|
27727
|
+
registerTools(api, orchestrator);
|
|
27728
|
+
registerCli(api, orchestrator);
|
|
27729
|
+
}
|
|
27730
|
+
if (isFirstRegistration) api.registerService({
|
|
27539
27731
|
id: "openclaw-engram",
|
|
27540
27732
|
start: async () => {
|
|
27541
27733
|
log.info("initializing engram memory system...");
|
|
@@ -27554,6 +27746,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
27554
27746
|
},
|
|
27555
27747
|
stop: () => {
|
|
27556
27748
|
globalThis[ENGRAM_REGISTERED_GUARD] = false;
|
|
27749
|
+
globalThis[ENGRAM_HOOK_APIS] = /* @__PURE__ */ new WeakSet();
|
|
27557
27750
|
log.info("stopped");
|
|
27558
27751
|
}
|
|
27559
27752
|
});
|