@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 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 path59 from "path";
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 path61 = typeof record.path === "string" ? record.path : "";
26192
- if (!path61.startsWith("transcripts/") && !path61.includes("/transcripts/")) continue;
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 = path59.join(config.memoryDir, "state", fileName);
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 = path59.join(orchestrator.config.memoryDir, "namespaces", ns);
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 = [path59.join(memoryDir, "facts"), path59.join(memoryDir, "corrections")];
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 = path59.join(dir, entryName);
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 = path59.join(process.env.HOME ?? "~", ".openclaw", "workspace");
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 = path59.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
30562
- const threading = new ThreadingManager(path59.join(memoryDir, "threads"));
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 writeFile37 } from "fs/promises";
31381
+ import { readFile as readFile38, writeFile as writeFile38 } from "fs/promises";
31039
31382
  import { readFileSync as readFileSync4 } from "fs";
31040
- import path60 from "path";
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 : path60.join(homeDir, ".openclaw", "openclaw.json");
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 = path60.join(
31641
+ const signalPath = path61.join(
31299
31642
  workspaceDir,
31300
31643
  `.compaction-reset-signal-${safeSessionKey}`
31301
31644
  );
31302
- await writeFile37(
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 = path60.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
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 writeFile37(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
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);