@joshuaswarren/openclaw-engram 9.0.45 → 9.0.47
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/README.md +3 -0
- package/dist/index.js +360 -17
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +20 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ AI agents forget everything between conversations. Engram fixes that.
|
|
|
46
46
|
- **Creation-memory ledger** — Engram can now, when `creationMemoryEnabled` is enabled, persist a typed work-product ledger for explicit outputs agents create or update, inspect it with `openclaw engram work-product-status`, and write deterministic entries through `openclaw engram work-product-record`.
|
|
47
47
|
- **Artifact recovery recall** — Engram can now, when both `creationMemoryEnabled` and `workProductRecallEnabled` are enabled, surface prompt-relevant work-product ledger entries back into recall as a dedicated `Work Products` section and inspect reuse candidates with `openclaw engram work-product-recall-search`.
|
|
48
48
|
- **Commitment lifecycle foundation** — Engram can now, when `creationMemoryEnabled`, `commitmentLedgerEnabled`, and `commitmentLifecycleEnabled` are enabled, transition existing commitments to `fulfilled` / `cancelled` / `expired`, inspect overdue and stale obligations in `openclaw engram commitment-status`, and run deterministic lifecycle cleanup with `openclaw engram commitment-lifecycle-run`.
|
|
49
|
+
- **Resume-bundle builder** — Engram can now, when `creationMemoryEnabled` and `resumeBundlesEnabled` are enabled, persist typed crash-recovery resume bundles, inspect them with `openclaw engram resume-bundle-status`, write explicit handoff shells through `openclaw engram resume-bundle-record`, and build bounded resume bundles from transcript recovery, recent objective-state snapshots, work products, and open commitments through `openclaw engram resume-bundle-build`.
|
|
49
50
|
- **Zero-config start** — Install, add an API key, restart. Engram works out of the box with sensible defaults and progressively unlocks advanced features as you enable them.
|
|
50
51
|
|
|
51
52
|
## Quick Start
|
|
@@ -228,6 +229,8 @@ Key settings:
|
|
|
228
229
|
| `commitmentLifecycleEnabled` | `false` | Enable commitment lifecycle transitions, stale tracking, and resolved-entry cleanup for the commitment ledger |
|
|
229
230
|
| `commitmentStaleDays` | `14` | Days before an open commitment without a due date is considered stale in lifecycle status |
|
|
230
231
|
| `commitmentLedgerDir` | `{memoryDir}/state/commitment-ledger` | Root directory for typed commitment ledger entries |
|
|
232
|
+
| `resumeBundlesEnabled` | `false` | Enable typed resume-bundle storage plus the operator-facing `resume-bundle-status`, `resume-bundle-record`, and `resume-bundle-build` commands |
|
|
233
|
+
| `resumeBundleDir` | `{memoryDir}/state/resume-bundles` | Root directory for typed resume bundles |
|
|
231
234
|
| `workProductRecallEnabled` | `false` | Inject prompt-relevant work-product ledger entries into recall and expose `openclaw engram work-product-recall-search` |
|
|
232
235
|
| `workProductLedgerDir` | `{memoryDir}/state/work-product-ledger` | Root directory for typed work-product ledger entries |
|
|
233
236
|
|
package/dist/index.js
CHANGED
|
@@ -312,6 +312,8 @@ function parseConfig(raw) {
|
|
|
312
312
|
commitmentLifecycleEnabled: cfg.commitmentLifecycleEnabled === true,
|
|
313
313
|
commitmentStaleDays: typeof cfg.commitmentStaleDays === "number" ? cfg.commitmentStaleDays : 14,
|
|
314
314
|
commitmentLedgerDir: typeof cfg.commitmentLedgerDir === "string" && cfg.commitmentLedgerDir.trim().length > 0 ? cfg.commitmentLedgerDir.trim() : path.join(memoryDir, "state", "commitment-ledger"),
|
|
315
|
+
resumeBundlesEnabled: cfg.resumeBundlesEnabled === true,
|
|
316
|
+
resumeBundleDir: typeof cfg.resumeBundleDir === "string" && cfg.resumeBundleDir.trim().length > 0 ? cfg.resumeBundleDir.trim() : path.join(memoryDir, "state", "resume-bundles"),
|
|
315
317
|
workProductRecallEnabled: cfg.workProductRecallEnabled === true,
|
|
316
318
|
workProductLedgerDir: typeof cfg.workProductLedgerDir === "string" && cfg.workProductLedgerDir.trim().length > 0 ? cfg.workProductLedgerDir.trim() : path.join(memoryDir, "state", "work-product-ledger"),
|
|
317
319
|
abstractionNodeStoreDir: typeof cfg.abstractionNodeStoreDir === "string" && cfg.abstractionNodeStoreDir.trim().length > 0 ? cfg.abstractionNodeStoreDir.trim() : path.join(memoryDir, "state", "abstraction-nodes"),
|
|
@@ -25307,7 +25309,7 @@ promotionCandidates: ${res.promotionCandidateCount}`
|
|
|
25307
25309
|
}
|
|
25308
25310
|
|
|
25309
25311
|
// src/cli.ts
|
|
25310
|
-
import
|
|
25312
|
+
import path60 from "path";
|
|
25311
25313
|
import { access as access3, readFile as readFile37, readdir as readdir24, unlink as unlink8 } from "fs/promises";
|
|
25312
25314
|
import { createHash as createHash10 } from "crypto";
|
|
25313
25315
|
|
|
@@ -26188,8 +26190,8 @@ function gatherCandidates(input, warnings) {
|
|
|
26188
26190
|
const record = rec;
|
|
26189
26191
|
const content = typeof record.content === "string" ? record.content : null;
|
|
26190
26192
|
if (!content) continue;
|
|
26191
|
-
const
|
|
26192
|
-
if (!
|
|
26193
|
+
const path62 = typeof record.path === "string" ? record.path : "";
|
|
26194
|
+
if (!path62.startsWith("transcripts/") && !path62.includes("/transcripts/")) continue;
|
|
26193
26195
|
rows.push(...parseJsonl(content, warnings));
|
|
26194
26196
|
}
|
|
26195
26197
|
return rows;
|
|
@@ -27823,6 +27825,237 @@ async function runCompatChecks(options) {
|
|
|
27823
27825
|
};
|
|
27824
27826
|
}
|
|
27825
27827
|
|
|
27828
|
+
// src/resume-bundles.ts
|
|
27829
|
+
import path59 from "path";
|
|
27830
|
+
import { mkdir as mkdir40, writeFile as writeFile37 } from "fs/promises";
|
|
27831
|
+
var DEFAULT_RESUME_BUNDLE_REF_LIMIT = 5;
|
|
27832
|
+
function resolveResumeBundleDir(memoryDir, overrideDir) {
|
|
27833
|
+
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
27834
|
+
return overrideDir.trim();
|
|
27835
|
+
}
|
|
27836
|
+
return path59.join(memoryDir, "state", "resume-bundles");
|
|
27837
|
+
}
|
|
27838
|
+
function validateResumeBundle(raw) {
|
|
27839
|
+
if (!isRecord2(raw)) throw new Error("resume bundle must be an object");
|
|
27840
|
+
if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
|
|
27841
|
+
const source = assertString2(raw.source, "source");
|
|
27842
|
+
if (!["tool_result", "cli", "system", "manual"].includes(source)) {
|
|
27843
|
+
throw new Error("source must be one of tool_result|cli|system|manual");
|
|
27844
|
+
}
|
|
27845
|
+
const recordedAt = assertIsoRecordedAt(assertString2(raw.recordedAt, "recordedAt"));
|
|
27846
|
+
if (!Number.isFinite(Date.parse(recordedAt))) {
|
|
27847
|
+
throw new Error("recordedAt must be an ISO timestamp");
|
|
27848
|
+
}
|
|
27849
|
+
return {
|
|
27850
|
+
schemaVersion: 1,
|
|
27851
|
+
bundleId: assertSafePathSegment(assertString2(raw.bundleId, "bundleId"), "bundleId"),
|
|
27852
|
+
recordedAt,
|
|
27853
|
+
sessionKey: assertString2(raw.sessionKey, "sessionKey"),
|
|
27854
|
+
source,
|
|
27855
|
+
scope: assertString2(raw.scope, "scope"),
|
|
27856
|
+
summary: assertString2(raw.summary, "summary"),
|
|
27857
|
+
objectiveStateSnapshotRefs: optionalStringArray2(raw.objectiveStateSnapshotRefs, "objectiveStateSnapshotRefs"),
|
|
27858
|
+
workProductEntryRefs: optionalStringArray2(raw.workProductEntryRefs, "workProductEntryRefs"),
|
|
27859
|
+
commitmentEntryRefs: optionalStringArray2(raw.commitmentEntryRefs, "commitmentEntryRefs"),
|
|
27860
|
+
keyFacts: optionalStringArray2(raw.keyFacts, "keyFacts"),
|
|
27861
|
+
nextActions: optionalStringArray2(raw.nextActions, "nextActions"),
|
|
27862
|
+
riskFlags: optionalStringArray2(raw.riskFlags, "riskFlags"),
|
|
27863
|
+
metadata: validateStringRecord(raw.metadata, "metadata")
|
|
27864
|
+
};
|
|
27865
|
+
}
|
|
27866
|
+
async function readValidatedItems(options) {
|
|
27867
|
+
const files = await listJsonFiles(options.rootDir);
|
|
27868
|
+
const items = [];
|
|
27869
|
+
for (const filePath of files) {
|
|
27870
|
+
try {
|
|
27871
|
+
items.push(options.validate(await readJsonFile(filePath)));
|
|
27872
|
+
} catch {
|
|
27873
|
+
}
|
|
27874
|
+
}
|
|
27875
|
+
return items;
|
|
27876
|
+
}
|
|
27877
|
+
async function readObjectiveStateSnapshotsForSession(options) {
|
|
27878
|
+
const rootDir = resolveObjectiveStateStoreDir(options.memoryDir, options.objectiveStateStoreDir);
|
|
27879
|
+
const items = await readValidatedItems({
|
|
27880
|
+
rootDir: path59.join(rootDir, "snapshots"),
|
|
27881
|
+
validate: validateObjectiveStateSnapshot
|
|
27882
|
+
});
|
|
27883
|
+
return items.filter((item) => item.sessionKey === options.sessionKey).sort((left, right) => right.recordedAt.localeCompare(left.recordedAt)).slice(0, options.maxResults ?? DEFAULT_RESUME_BUNDLE_REF_LIMIT);
|
|
27884
|
+
}
|
|
27885
|
+
async function readWorkProductEntriesForSession(options) {
|
|
27886
|
+
const rootDir = resolveWorkProductLedgerDir(options.memoryDir, options.workProductLedgerDir);
|
|
27887
|
+
const items = await readValidatedItems({
|
|
27888
|
+
rootDir: path59.join(rootDir, "entries"),
|
|
27889
|
+
validate: validateWorkProductLedgerEntry
|
|
27890
|
+
});
|
|
27891
|
+
return items.filter((item) => item.sessionKey === options.sessionKey).sort((left, right) => right.recordedAt.localeCompare(left.recordedAt)).slice(0, options.maxResults ?? DEFAULT_RESUME_BUNDLE_REF_LIMIT);
|
|
27892
|
+
}
|
|
27893
|
+
async function readCommitmentEntriesForSession(options) {
|
|
27894
|
+
const rootDir = resolveCommitmentLedgerDir(options.memoryDir, options.commitmentLedgerDir);
|
|
27895
|
+
const items = await readValidatedItems({
|
|
27896
|
+
rootDir: path59.join(rootDir, "entries"),
|
|
27897
|
+
validate: validateCommitmentLedgerEntry
|
|
27898
|
+
});
|
|
27899
|
+
return items.filter((item) => item.sessionKey === options.sessionKey).filter((item) => options.state ? item.state === options.state : true).sort((left, right) => right.recordedAt.localeCompare(left.recordedAt)).slice(0, options.maxResults ?? DEFAULT_RESUME_BUNDLE_REF_LIMIT);
|
|
27900
|
+
}
|
|
27901
|
+
function buildRecoveryFact(recoverySummary) {
|
|
27902
|
+
return recoverySummary.healthy ? `Transcript recovery healthy with ${recoverySummary.issueCount} issue(s), ${recoverySummary.incompleteTurns} incomplete turn(s), and ${recoverySummary.brokenChains} broken chain(s).` : `Transcript recovery flagged ${recoverySummary.issueCount} issue(s), ${recoverySummary.incompleteTurns} incomplete turn(s), and ${recoverySummary.brokenChains} broken chain(s); checkpoint healthy: ${recoverySummary.checkpointHealthy ? "yes" : "no"}.`;
|
|
27903
|
+
}
|
|
27904
|
+
function buildBundleSummary(options) {
|
|
27905
|
+
const parts = [
|
|
27906
|
+
`${options.openCommitmentCount} open commitment(s)`,
|
|
27907
|
+
`${options.workProductCount} recent work product(s)`,
|
|
27908
|
+
`${options.objectiveSnapshotCount} recent objective-state snapshot(s)`
|
|
27909
|
+
];
|
|
27910
|
+
if (options.recoveryHealthy === false) {
|
|
27911
|
+
parts.push("transcript recovery issues remain");
|
|
27912
|
+
}
|
|
27913
|
+
return `Resume ${options.sessionKey}: ${parts.join(", ")}.`;
|
|
27914
|
+
}
|
|
27915
|
+
async function buildResumeBundleFromState(options) {
|
|
27916
|
+
const recordedAt = assertIsoRecordedAt(options.recordedAt, "recordedAt");
|
|
27917
|
+
const maxRefsPerStore = Math.max(1, Math.floor(options.maxRefsPerStore ?? DEFAULT_RESUME_BUNDLE_REF_LIMIT));
|
|
27918
|
+
const objectiveSnapshots = options.objectiveStateMemoryEnabled ? await readObjectiveStateSnapshotsForSession({
|
|
27919
|
+
memoryDir: options.memoryDir,
|
|
27920
|
+
objectiveStateStoreDir: options.objectiveStateStoreDir,
|
|
27921
|
+
sessionKey: options.sessionKey,
|
|
27922
|
+
maxResults: maxRefsPerStore
|
|
27923
|
+
}) : [];
|
|
27924
|
+
const workProducts = options.creationMemoryEnabled ? await readWorkProductEntriesForSession({
|
|
27925
|
+
memoryDir: options.memoryDir,
|
|
27926
|
+
workProductLedgerDir: options.workProductLedgerDir,
|
|
27927
|
+
sessionKey: options.sessionKey,
|
|
27928
|
+
maxResults: maxRefsPerStore
|
|
27929
|
+
}) : [];
|
|
27930
|
+
const commitments = options.creationMemoryEnabled && options.commitmentLedgerEnabled ? await readCommitmentEntriesForSession({
|
|
27931
|
+
memoryDir: options.memoryDir,
|
|
27932
|
+
commitmentLedgerDir: options.commitmentLedgerDir,
|
|
27933
|
+
sessionKey: options.sessionKey,
|
|
27934
|
+
maxResults: maxRefsPerStore,
|
|
27935
|
+
state: "open"
|
|
27936
|
+
}) : [];
|
|
27937
|
+
const openCommitments = commitments;
|
|
27938
|
+
const recordedAtMs = Date.parse(recordedAt);
|
|
27939
|
+
const overdueCommitments = openCommitments.filter((entry) => {
|
|
27940
|
+
if (!entry.dueAt) return false;
|
|
27941
|
+
const dueAtMs = Date.parse(entry.dueAt);
|
|
27942
|
+
return Number.isFinite(dueAtMs) && dueAtMs < recordedAtMs;
|
|
27943
|
+
});
|
|
27944
|
+
let recoverySummary;
|
|
27945
|
+
if (options.transcriptEnabled) {
|
|
27946
|
+
const transcript = new TranscriptManager(parseConfig({
|
|
27947
|
+
memoryDir: options.memoryDir,
|
|
27948
|
+
transcriptEnabled: true
|
|
27949
|
+
}));
|
|
27950
|
+
await transcript.initialize();
|
|
27951
|
+
recoverySummary = await transcript.getRecoverySummary(options.sessionKey);
|
|
27952
|
+
}
|
|
27953
|
+
const keyFacts = [
|
|
27954
|
+
recoverySummary ? buildRecoveryFact(recoverySummary) : void 0,
|
|
27955
|
+
workProducts[0]?.summary,
|
|
27956
|
+
objectiveSnapshots[0]?.summary
|
|
27957
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
27958
|
+
const nextActions = openCommitments.map((entry) => entry.summary).filter((summary, index, values) => values.indexOf(summary) === index).slice(0, maxRefsPerStore);
|
|
27959
|
+
const riskFlags = [
|
|
27960
|
+
recoverySummary && !recoverySummary.healthy ? buildRecoveryFact(recoverySummary) : void 0,
|
|
27961
|
+
...objectiveSnapshots.filter((snapshot) => snapshot.outcome === "failure" || snapshot.outcome === "partial").map((snapshot) => snapshot.summary),
|
|
27962
|
+
...overdueCommitments.map((entry) => `Overdue commitment: ${entry.summary}`)
|
|
27963
|
+
].filter(
|
|
27964
|
+
(value, index, values) => typeof value === "string" && value.length > 0 && values.indexOf(value) === index
|
|
27965
|
+
);
|
|
27966
|
+
return validateResumeBundle({
|
|
27967
|
+
schemaVersion: 1,
|
|
27968
|
+
bundleId: options.bundleId,
|
|
27969
|
+
recordedAt,
|
|
27970
|
+
sessionKey: options.sessionKey,
|
|
27971
|
+
source: options.source ?? "system",
|
|
27972
|
+
scope: options.scope,
|
|
27973
|
+
summary: buildBundleSummary({
|
|
27974
|
+
sessionKey: options.sessionKey,
|
|
27975
|
+
openCommitmentCount: openCommitments.length,
|
|
27976
|
+
workProductCount: workProducts.length,
|
|
27977
|
+
objectiveSnapshotCount: objectiveSnapshots.length,
|
|
27978
|
+
recoveryHealthy: recoverySummary?.healthy
|
|
27979
|
+
}),
|
|
27980
|
+
objectiveStateSnapshotRefs: objectiveSnapshots.map((snapshot) => snapshot.snapshotId),
|
|
27981
|
+
workProductEntryRefs: workProducts.map((entry) => entry.entryId),
|
|
27982
|
+
commitmentEntryRefs: openCommitments.map((entry) => entry.entryId),
|
|
27983
|
+
keyFacts: keyFacts.length > 0 ? keyFacts : void 0,
|
|
27984
|
+
nextActions: nextActions.length > 0 ? nextActions : void 0,
|
|
27985
|
+
riskFlags: riskFlags.length > 0 ? riskFlags : void 0
|
|
27986
|
+
});
|
|
27987
|
+
}
|
|
27988
|
+
async function recordResumeBundle(options) {
|
|
27989
|
+
const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
|
|
27990
|
+
const validated = validateResumeBundle(options.bundle);
|
|
27991
|
+
const day = recordStoreDay(validated.recordedAt);
|
|
27992
|
+
const bundlesDir = path59.join(rootDir, "bundles", day);
|
|
27993
|
+
const filePath = path59.join(bundlesDir, `${validated.bundleId}.json`);
|
|
27994
|
+
await mkdir40(bundlesDir, { recursive: true });
|
|
27995
|
+
await writeFile37(filePath, JSON.stringify(validated, null, 2), "utf8");
|
|
27996
|
+
return filePath;
|
|
27997
|
+
}
|
|
27998
|
+
async function readResumeBundles(options) {
|
|
27999
|
+
const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
|
|
28000
|
+
const files = await listJsonFiles(path59.join(rootDir, "bundles"));
|
|
28001
|
+
const bundles = [];
|
|
28002
|
+
const invalidBundles = [];
|
|
28003
|
+
for (const filePath of files) {
|
|
28004
|
+
try {
|
|
28005
|
+
bundles.push(validateResumeBundle(await readJsonFile(filePath)));
|
|
28006
|
+
} catch (error) {
|
|
28007
|
+
invalidBundles.push({
|
|
28008
|
+
path: filePath,
|
|
28009
|
+
error: error instanceof Error ? error.message : String(error)
|
|
28010
|
+
});
|
|
28011
|
+
}
|
|
28012
|
+
}
|
|
28013
|
+
return { files, bundles, invalidBundles };
|
|
28014
|
+
}
|
|
28015
|
+
async function getResumeBundleStatus(options) {
|
|
28016
|
+
const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
|
|
28017
|
+
const bundlesDir = path59.join(rootDir, "bundles");
|
|
28018
|
+
if (!options.enabled) {
|
|
28019
|
+
return {
|
|
28020
|
+
enabled: false,
|
|
28021
|
+
rootDir,
|
|
28022
|
+
bundlesDir,
|
|
28023
|
+
bundles: {
|
|
28024
|
+
total: 0,
|
|
28025
|
+
valid: 0,
|
|
28026
|
+
invalid: 0,
|
|
28027
|
+
bySource: {}
|
|
28028
|
+
},
|
|
28029
|
+
invalidBundles: []
|
|
28030
|
+
};
|
|
28031
|
+
}
|
|
28032
|
+
const { files, bundles, invalidBundles } = await readResumeBundles(options);
|
|
28033
|
+
let latestBundle;
|
|
28034
|
+
const bySource = {};
|
|
28035
|
+
for (const bundle of bundles) {
|
|
28036
|
+
bySource[bundle.source] = (bySource[bundle.source] ?? 0) + 1;
|
|
28037
|
+
if (!latestBundle || Date.parse(bundle.recordedAt) > Date.parse(latestBundle.recordedAt)) {
|
|
28038
|
+
latestBundle = bundle;
|
|
28039
|
+
}
|
|
28040
|
+
}
|
|
28041
|
+
return {
|
|
28042
|
+
enabled: options.enabled,
|
|
28043
|
+
rootDir,
|
|
28044
|
+
bundlesDir,
|
|
28045
|
+
bundles: {
|
|
28046
|
+
total: files.length,
|
|
28047
|
+
valid: bundles.length,
|
|
28048
|
+
invalid: invalidBundles.length,
|
|
28049
|
+
bySource,
|
|
28050
|
+
latestBundleId: latestBundle?.bundleId,
|
|
28051
|
+
latestRecordedAt: latestBundle?.recordedAt,
|
|
28052
|
+
latestSessionKey: latestBundle?.sessionKey
|
|
28053
|
+
},
|
|
28054
|
+
latestBundle,
|
|
28055
|
+
invalidBundles
|
|
28056
|
+
};
|
|
28057
|
+
}
|
|
28058
|
+
|
|
27826
28059
|
// src/semantic-rule-promotion.ts
|
|
27827
28060
|
function normalizeRuleWhitespace(value) {
|
|
27828
28061
|
return value.replace(/\s+/g, " ").trim();
|
|
@@ -28229,6 +28462,46 @@ async function runWorkProductRecallSearchCliCommand(options) {
|
|
|
28229
28462
|
sessionKey: options.sessionKey
|
|
28230
28463
|
});
|
|
28231
28464
|
}
|
|
28465
|
+
async function runResumeBundleStatusCliCommand(options) {
|
|
28466
|
+
return getResumeBundleStatus({
|
|
28467
|
+
memoryDir: options.memoryDir,
|
|
28468
|
+
resumeBundleDir: options.resumeBundleDir,
|
|
28469
|
+
enabled: options.creationMemoryEnabled && options.resumeBundlesEnabled
|
|
28470
|
+
});
|
|
28471
|
+
}
|
|
28472
|
+
async function runResumeBundleRecordCliCommand(options) {
|
|
28473
|
+
if (!options.creationMemoryEnabled || !options.resumeBundlesEnabled) return null;
|
|
28474
|
+
return recordResumeBundle({
|
|
28475
|
+
memoryDir: options.memoryDir,
|
|
28476
|
+
resumeBundleDir: options.resumeBundleDir,
|
|
28477
|
+
bundle: options.bundle
|
|
28478
|
+
});
|
|
28479
|
+
}
|
|
28480
|
+
async function runResumeBundleBuildCliCommand(options) {
|
|
28481
|
+
if (!options.creationMemoryEnabled || !options.resumeBundlesEnabled) {
|
|
28482
|
+
return null;
|
|
28483
|
+
}
|
|
28484
|
+
const bundle = await buildResumeBundleFromState({
|
|
28485
|
+
memoryDir: options.memoryDir,
|
|
28486
|
+
sessionKey: options.sessionKey,
|
|
28487
|
+
bundleId: options.bundleId,
|
|
28488
|
+
recordedAt: options.recordedAt,
|
|
28489
|
+
scope: options.scope,
|
|
28490
|
+
transcriptEnabled: options.transcriptEnabled,
|
|
28491
|
+
objectiveStateMemoryEnabled: options.objectiveStateMemoryEnabled,
|
|
28492
|
+
objectiveStateStoreDir: options.objectiveStateStoreDir,
|
|
28493
|
+
creationMemoryEnabled: options.creationMemoryEnabled,
|
|
28494
|
+
workProductLedgerDir: options.workProductLedgerDir,
|
|
28495
|
+
commitmentLedgerEnabled: options.commitmentLedgerEnabled,
|
|
28496
|
+
commitmentLedgerDir: options.commitmentLedgerDir
|
|
28497
|
+
});
|
|
28498
|
+
const filePath = await recordResumeBundle({
|
|
28499
|
+
memoryDir: options.memoryDir,
|
|
28500
|
+
resumeBundleDir: options.resumeBundleDir,
|
|
28501
|
+
bundle
|
|
28502
|
+
});
|
|
28503
|
+
return { bundle, filePath };
|
|
28504
|
+
}
|
|
28232
28505
|
async function runCommitmentStatusCliCommand(options) {
|
|
28233
28506
|
return getCommitmentLedgerStatus({
|
|
28234
28507
|
memoryDir: options.memoryDir,
|
|
@@ -28518,7 +28791,7 @@ function policyVersionForValues(values, config) {
|
|
|
28518
28791
|
return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
28519
28792
|
}
|
|
28520
28793
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
28521
|
-
const filePath =
|
|
28794
|
+
const filePath = path60.join(config.memoryDir, "state", fileName);
|
|
28522
28795
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
28523
28796
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
28524
28797
|
});
|
|
@@ -29102,14 +29375,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
29102
29375
|
const ns = (namespace ?? "").trim();
|
|
29103
29376
|
if (!ns) return orchestrator.config.memoryDir;
|
|
29104
29377
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
29105
|
-
const candidate =
|
|
29378
|
+
const candidate = path60.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
29106
29379
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
29107
29380
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
29108
29381
|
}
|
|
29109
29382
|
return candidate;
|
|
29110
29383
|
}
|
|
29111
29384
|
async function readAllMemoryFiles(memoryDir) {
|
|
29112
|
-
const roots = [
|
|
29385
|
+
const roots = [path60.join(memoryDir, "facts"), path60.join(memoryDir, "corrections")];
|
|
29113
29386
|
const out = [];
|
|
29114
29387
|
const walk = async (dir) => {
|
|
29115
29388
|
let entries;
|
|
@@ -29120,7 +29393,7 @@ async function readAllMemoryFiles(memoryDir) {
|
|
|
29120
29393
|
}
|
|
29121
29394
|
for (const entry of entries) {
|
|
29122
29395
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
29123
|
-
const fullPath =
|
|
29396
|
+
const fullPath = path60.join(dir, entryName);
|
|
29124
29397
|
if (entry.isDirectory()) {
|
|
29125
29398
|
await walk(fullPath);
|
|
29126
29399
|
continue;
|
|
@@ -29499,6 +29772,76 @@ function registerCli(api, orchestrator) {
|
|
|
29499
29772
|
console.log(JSON.stringify(results, null, 2));
|
|
29500
29773
|
console.log("OK");
|
|
29501
29774
|
});
|
|
29775
|
+
cmd.command("resume-bundle-status").description("Show resume bundle status, bundle counts, and the latest recorded handoff bundle").action(async () => {
|
|
29776
|
+
const status = await runResumeBundleStatusCliCommand({
|
|
29777
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
29778
|
+
resumeBundleDir: orchestrator.config.resumeBundleDir,
|
|
29779
|
+
creationMemoryEnabled: orchestrator.config.creationMemoryEnabled,
|
|
29780
|
+
resumeBundlesEnabled: orchestrator.config.resumeBundlesEnabled
|
|
29781
|
+
});
|
|
29782
|
+
console.log(JSON.stringify(status, null, 2));
|
|
29783
|
+
console.log("OK");
|
|
29784
|
+
});
|
|
29785
|
+
cmd.command("resume-bundle-record").description("Record an explicit resume bundle when creation-memory handoff bundles are enabled").requiredOption("--bundle-id <bundleId>", "Resume bundle id").requiredOption("--recorded-at <recordedAt>", "ISO timestamp for the bundle").requiredOption("--session-key <sessionKey>", "Session key that owns the bundle").requiredOption("--source <source>", "Bundle source (tool_result|cli|system|manual)").requiredOption("--scope <scope>", "Primary scope or recovery domain for the bundle").requiredOption("--summary <summary>", "Human-readable summary of what this bundle preserves").option("--key-fact <keyFact...>", "Short facts that a resumed agent should retain").option("--next-action <nextAction...>", "Explicit next actions for the resumed agent").option("--risk-flag <riskFlag...>", "Open risks or cautions attached to the bundle").option(
|
|
29786
|
+
"--objective-state-snapshot-ref <objectiveStateSnapshotRef...>",
|
|
29787
|
+
"Objective-state snapshot refs attached to the bundle"
|
|
29788
|
+
).option(
|
|
29789
|
+
"--work-product-entry-ref <workProductEntryRef...>",
|
|
29790
|
+
"Work-product ledger refs attached to the bundle"
|
|
29791
|
+
).option(
|
|
29792
|
+
"--commitment-entry-ref <commitmentEntryRef...>",
|
|
29793
|
+
"Commitment ledger refs attached to the bundle"
|
|
29794
|
+
).action(async (...args) => {
|
|
29795
|
+
const options = args[0] ?? {};
|
|
29796
|
+
const filePath = await runResumeBundleRecordCliCommand({
|
|
29797
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
29798
|
+
resumeBundleDir: orchestrator.config.resumeBundleDir,
|
|
29799
|
+
creationMemoryEnabled: orchestrator.config.creationMemoryEnabled,
|
|
29800
|
+
resumeBundlesEnabled: orchestrator.config.resumeBundlesEnabled,
|
|
29801
|
+
bundle: {
|
|
29802
|
+
schemaVersion: 1,
|
|
29803
|
+
bundleId: String(options.bundleId ?? ""),
|
|
29804
|
+
recordedAt: String(options.recordedAt ?? ""),
|
|
29805
|
+
sessionKey: String(options.sessionKey ?? ""),
|
|
29806
|
+
source: String(options.source ?? ""),
|
|
29807
|
+
scope: String(options.scope ?? ""),
|
|
29808
|
+
summary: String(options.summary ?? ""),
|
|
29809
|
+
keyFacts: Array.isArray(options.keyFact) ? options.keyFact.map(String) : void 0,
|
|
29810
|
+
nextActions: Array.isArray(options.nextAction) ? options.nextAction.map(String) : void 0,
|
|
29811
|
+
riskFlags: Array.isArray(options.riskFlag) ? options.riskFlag.map(String) : void 0,
|
|
29812
|
+
objectiveStateSnapshotRefs: Array.isArray(options.objectiveStateSnapshotRef) ? options.objectiveStateSnapshotRef.map(String) : void 0,
|
|
29813
|
+
workProductEntryRefs: Array.isArray(options.workProductEntryRef) ? options.workProductEntryRef.map(String) : void 0,
|
|
29814
|
+
commitmentEntryRefs: Array.isArray(options.commitmentEntryRef) ? options.commitmentEntryRef.map(String) : void 0
|
|
29815
|
+
}
|
|
29816
|
+
});
|
|
29817
|
+
console.log(JSON.stringify({ wrote: filePath !== null, filePath }, null, 2));
|
|
29818
|
+
console.log("OK");
|
|
29819
|
+
});
|
|
29820
|
+
cmd.command("resume-bundle-build").description("Build and persist a resume bundle from transcript recovery, objective state, work products, and open commitments").requiredOption("--bundle-id <bundleId>", "Resume bundle id").requiredOption("--recorded-at <recordedAt>", "ISO timestamp for the bundle").requiredOption("--session-key <sessionKey>", "Session key that owns the bundle").requiredOption("--scope <scope>", "Primary scope or recovery domain for the bundle").action(async (...args) => {
|
|
29821
|
+
const options = args[0] ?? {};
|
|
29822
|
+
const built = await runResumeBundleBuildCliCommand({
|
|
29823
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
29824
|
+
resumeBundleDir: orchestrator.config.resumeBundleDir,
|
|
29825
|
+
objectiveStateStoreDir: orchestrator.config.objectiveStateStoreDir,
|
|
29826
|
+
workProductLedgerDir: orchestrator.config.workProductLedgerDir,
|
|
29827
|
+
commitmentLedgerDir: orchestrator.config.commitmentLedgerDir,
|
|
29828
|
+
creationMemoryEnabled: orchestrator.config.creationMemoryEnabled,
|
|
29829
|
+
resumeBundlesEnabled: orchestrator.config.resumeBundlesEnabled,
|
|
29830
|
+
transcriptEnabled: orchestrator.config.transcriptEnabled,
|
|
29831
|
+
objectiveStateMemoryEnabled: orchestrator.config.objectiveStateMemoryEnabled,
|
|
29832
|
+
commitmentLedgerEnabled: orchestrator.config.commitmentLedgerEnabled,
|
|
29833
|
+
bundleId: String(options.bundleId ?? ""),
|
|
29834
|
+
recordedAt: String(options.recordedAt ?? ""),
|
|
29835
|
+
sessionKey: String(options.sessionKey ?? ""),
|
|
29836
|
+
scope: String(options.scope ?? "")
|
|
29837
|
+
});
|
|
29838
|
+
console.log(JSON.stringify({
|
|
29839
|
+
wrote: built !== null,
|
|
29840
|
+
filePath: built?.filePath ?? null,
|
|
29841
|
+
bundle: built?.bundle ?? null
|
|
29842
|
+
}, null, 2));
|
|
29843
|
+
console.log("OK");
|
|
29844
|
+
});
|
|
29502
29845
|
cmd.command("commitment-status").description("Show commitment ledger status, entry counts, and the latest recorded commitment").action(async () => {
|
|
29503
29846
|
const status = await runCommitmentStatusCliCommand({
|
|
29504
29847
|
memoryDir: orchestrator.config.memoryDir,
|
|
@@ -30335,7 +30678,7 @@ function registerCli(api, orchestrator) {
|
|
|
30335
30678
|
}
|
|
30336
30679
|
});
|
|
30337
30680
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
30338
|
-
const workspaceDir =
|
|
30681
|
+
const workspaceDir = path60.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
30339
30682
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
30340
30683
|
if (!identity) {
|
|
30341
30684
|
console.log("No identity file found.");
|
|
@@ -30558,8 +30901,8 @@ function registerCli(api, orchestrator) {
|
|
|
30558
30901
|
const options = args[0] ?? {};
|
|
30559
30902
|
const threadId = options.thread;
|
|
30560
30903
|
const top = parseInt(options.top ?? "10", 10);
|
|
30561
|
-
const memoryDir =
|
|
30562
|
-
const threading = new ThreadingManager(
|
|
30904
|
+
const memoryDir = path60.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
30905
|
+
const threading = new ThreadingManager(path60.join(memoryDir, "threads"));
|
|
30563
30906
|
if (threadId) {
|
|
30564
30907
|
const thread = await threading.loadThread(threadId);
|
|
30565
30908
|
if (!thread) {
|
|
@@ -31035,9 +31378,9 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
31035
31378
|
}
|
|
31036
31379
|
|
|
31037
31380
|
// src/index.ts
|
|
31038
|
-
import { readFile as readFile38, writeFile as
|
|
31381
|
+
import { readFile as readFile38, writeFile as writeFile38 } from "fs/promises";
|
|
31039
31382
|
import { readFileSync as readFileSync4 } from "fs";
|
|
31040
|
-
import
|
|
31383
|
+
import path61 from "path";
|
|
31041
31384
|
import os6 from "os";
|
|
31042
31385
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
31043
31386
|
var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
|
|
@@ -31045,7 +31388,7 @@ function loadPluginConfigFromFile() {
|
|
|
31045
31388
|
try {
|
|
31046
31389
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
31047
31390
|
const homeDir = process.env.HOME ?? os6.homedir();
|
|
31048
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
31391
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path61.join(homeDir, ".openclaw", "openclaw.json");
|
|
31049
31392
|
const content = readFileSync4(configPath, "utf-8");
|
|
31050
31393
|
const config = JSON.parse(content);
|
|
31051
31394
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
@@ -31295,11 +31638,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
31295
31638
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
31296
31639
|
);
|
|
31297
31640
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
31298
|
-
const signalPath =
|
|
31641
|
+
const signalPath = path61.join(
|
|
31299
31642
|
workspaceDir,
|
|
31300
31643
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
31301
31644
|
);
|
|
31302
|
-
await
|
|
31645
|
+
await writeFile38(
|
|
31303
31646
|
signalPath,
|
|
31304
31647
|
JSON.stringify({
|
|
31305
31648
|
sessionKey,
|
|
@@ -31326,7 +31669,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
31326
31669
|
);
|
|
31327
31670
|
async function ensureHourlySummaryCron(api2) {
|
|
31328
31671
|
const jobId = "engram-hourly-summary";
|
|
31329
|
-
const cronFilePath =
|
|
31672
|
+
const cronFilePath = path61.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
|
|
31330
31673
|
try {
|
|
31331
31674
|
let jobsData = { version: 1, jobs: [] };
|
|
31332
31675
|
try {
|
|
@@ -31367,7 +31710,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
31367
31710
|
state: {}
|
|
31368
31711
|
};
|
|
31369
31712
|
jobsData.jobs.push(newJob);
|
|
31370
|
-
await
|
|
31713
|
+
await writeFile38(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
|
|
31371
31714
|
log.info("auto-registered hourly summary cron job");
|
|
31372
31715
|
} catch (err) {
|
|
31373
31716
|
log.error("failed to auto-register hourly summary cron job:", err);
|