@joshuaswarren/openclaw-engram 8.3.7 → 8.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -322,6 +322,12 @@ function parseConfig(raw) {
322
322
  (c) => typeof c === "string" && VALID_MEMORY_CATEGORIES.has(c)
323
323
  ) : ["decision", "principle", "commitment", "preference"],
324
324
  lifecycleMetricsEnabled: typeof cfg.lifecycleMetricsEnabled === "boolean" ? cfg.lifecycleMetricsEnabled : cfg.lifecyclePolicyEnabled === true,
325
+ // v8.3 proactive + policy learning (default off)
326
+ proactiveExtractionEnabled: cfg.proactiveExtractionEnabled === true,
327
+ contextCompressionActionsEnabled: cfg.contextCompressionActionsEnabled === true,
328
+ compressionGuidelineLearningEnabled: cfg.compressionGuidelineLearningEnabled === true,
329
+ maxProactiveQuestionsPerExtraction: typeof cfg.maxProactiveQuestionsPerExtraction === "number" ? Math.max(0, Math.floor(cfg.maxProactiveQuestionsPerExtraction)) : 2,
330
+ maxCompressionTokensPerHour: typeof cfg.maxCompressionTokensPerHour === "number" ? Math.max(0, Math.floor(cfg.maxCompressionTokensPerHour)) : 1500,
325
331
  // v8.0 phase 1
326
332
  recallPlannerEnabled: cfg.recallPlannerEnabled !== false,
327
333
  recallPlannerMaxQmdResultsMinimal: typeof cfg.recallPlannerMaxQmdResultsMinimal === "number" ? cfg.recallPlannerMaxQmdResultsMinimal : 4,
@@ -3975,7 +3981,7 @@ ${stderr}`.trim();
3975
3981
  };
3976
3982
 
3977
3983
  // src/storage.ts
3978
- import { readdir, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, unlink } from "fs/promises";
3984
+ import { readdir, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, unlink, appendFile } from "fs/promises";
3979
3985
  import { appendFileSync, mkdirSync as mkdirSync2, statSync } from "fs";
3980
3986
  import { createHash } from "crypto";
3981
3987
  import path4 from "path";
@@ -4535,6 +4541,12 @@ var StorageManager = class _StorageManager {
4535
4541
  get profilePath() {
4536
4542
  return path4.join(this.baseDir, "profile.md");
4537
4543
  }
4544
+ get memoryActionsPath() {
4545
+ return path4.join(this.stateDir, "memory-actions.jsonl");
4546
+ }
4547
+ get compressionGuidelinesPath() {
4548
+ return path4.join(this.stateDir, "compression-guidelines.md");
4549
+ }
4538
4550
  /**
4539
4551
  * Load user-defined entity aliases from config/aliases.json in the memory store.
4540
4552
  * File format: { "variant": "canonical", "variant2": "canonical", ... }
@@ -5086,6 +5098,55 @@ ${memory.content}
5086
5098
  const metaPath = path4.join(this.stateDir, "meta.json");
5087
5099
  await writeFile2(metaPath, JSON.stringify(state, null, 2), "utf-8");
5088
5100
  }
5101
+ async appendMemoryActionEvents(events) {
5102
+ if (events.length === 0) return 0;
5103
+ await this.ensureDirectories();
5104
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
5105
+ const payload = events.map((event) => {
5106
+ const normalized = {
5107
+ ...event,
5108
+ timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
5109
+ };
5110
+ return `${JSON.stringify(normalized)}
5111
+ `;
5112
+ }).join("");
5113
+ await appendFile(this.memoryActionsPath, payload, "utf-8");
5114
+ return events.length;
5115
+ }
5116
+ async readMemoryActionEvents(limit = 200) {
5117
+ const cappedLimit = Math.max(0, Math.floor(limit));
5118
+ if (cappedLimit === 0) return [];
5119
+ try {
5120
+ const raw = await readFile2(this.memoryActionsPath, "utf-8");
5121
+ const out = [];
5122
+ const lines = raw.split("\n");
5123
+ for (let i = lines.length - 1; i >= 0 && out.length < cappedLimit; i -= 1) {
5124
+ const line = lines[i]?.trim();
5125
+ if (!line) continue;
5126
+ try {
5127
+ const parsed = JSON.parse(line);
5128
+ if (typeof parsed.timestamp === "string" && typeof parsed.action === "string" && typeof parsed.outcome === "string") {
5129
+ out.push(parsed);
5130
+ }
5131
+ } catch {
5132
+ }
5133
+ }
5134
+ return out.reverse();
5135
+ } catch {
5136
+ return [];
5137
+ }
5138
+ }
5139
+ async writeCompressionGuidelines(content) {
5140
+ await this.ensureDirectories();
5141
+ await writeFile2(this.compressionGuidelinesPath, content, "utf-8");
5142
+ }
5143
+ async readCompressionGuidelines() {
5144
+ try {
5145
+ return await readFile2(this.compressionGuidelinesPath, "utf-8");
5146
+ } catch {
5147
+ return null;
5148
+ }
5149
+ }
5089
5150
  // ---------------------------------------------------------------------------
5090
5151
  // Question storage
5091
5152
  // ---------------------------------------------------------------------------
@@ -6275,7 +6336,7 @@ function extractTopics(memories, topN = 50) {
6275
6336
  }
6276
6337
 
6277
6338
  // src/transcript.ts
6278
- import { appendFile, mkdir as mkdir4, readdir as readdir3, readFile as readFile4, unlink as unlink2, writeFile as writeFile4 } from "fs/promises";
6339
+ import { appendFile as appendFile2, mkdir as mkdir4, readdir as readdir3, readFile as readFile4, unlink as unlink2, writeFile as writeFile4 } from "fs/promises";
6279
6340
  import path6 from "path";
6280
6341
  var TranscriptManager = class _TranscriptManager {
6281
6342
  transcriptsDir;
@@ -6384,7 +6445,7 @@ var TranscriptManager = class _TranscriptManager {
6384
6445
  const channelDir = path6.join(this.toolUsageDir, dir);
6385
6446
  await mkdir4(channelDir, { recursive: true });
6386
6447
  const filePath = path6.join(channelDir, file);
6387
- await appendFile(filePath, JSON.stringify(entry) + "\n", "utf-8");
6448
+ await appendFile2(filePath, JSON.stringify(entry) + "\n", "utf-8");
6388
6449
  }
6389
6450
  async readToolUse(sessionKey, startTime, endTime) {
6390
6451
  const { dir } = this.getToolUsagePath(sessionKey);
@@ -6439,7 +6500,7 @@ var TranscriptManager = class _TranscriptManager {
6439
6500
  const filePath = path6.join(channelDir, file);
6440
6501
  await mkdir4(channelDir, { recursive: true });
6441
6502
  const line = JSON.stringify(entry) + "\n";
6442
- await appendFile(filePath, line, "utf-8");
6503
+ await appendFile2(filePath, line, "utf-8");
6443
6504
  log.debug(`appended transcript entry for ${entry.sessionKey}: ${entry.turnId}`);
6444
6505
  } catch (err) {
6445
6506
  log.error("failed to append transcript entry:", err);
@@ -7604,7 +7665,7 @@ var NegativeExampleStore = class {
7604
7665
  };
7605
7666
 
7606
7667
  // src/recall-state.ts
7607
- import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
7668
+ import { appendFile as appendFile3, mkdir as mkdir8, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
7608
7669
  import path10 from "path";
7609
7670
  import { createHash as createHash2 } from "crypto";
7610
7671
  var LastRecallStore = class {
@@ -7663,7 +7724,7 @@ var LastRecallStore = class {
7663
7724
  }
7664
7725
  try {
7665
7726
  await mkdir8(path10.dirname(this.impressionsPath), { recursive: true });
7666
- await appendFile2(this.impressionsPath, JSON.stringify(snapshot) + "\n", "utf-8");
7727
+ await appendFile3(this.impressionsPath, JSON.stringify(snapshot) + "\n", "utf-8");
7667
7728
  } catch (err) {
7668
7729
  log.debug(`recall impressions append failed: ${err}`);
7669
7730
  }
@@ -9201,7 +9262,7 @@ function recencyWindowFromPrompt(prompt, nowMs = Date.now()) {
9201
9262
  }
9202
9263
 
9203
9264
  // src/graph.ts
9204
- import { mkdir as mkdir12, appendFile as appendFile3, readFile as readFile13 } from "fs/promises";
9265
+ import { mkdir as mkdir12, appendFile as appendFile4, readFile as readFile13 } from "fs/promises";
9205
9266
  import * as path16 from "path";
9206
9267
  var CAUSAL_PHRASES = [
9207
9268
  "as a result",
@@ -9223,7 +9284,7 @@ async function ensureGraphsDir(memoryDir) {
9223
9284
  async function appendEdge(memoryDir, edge) {
9224
9285
  await ensureGraphsDir(memoryDir);
9225
9286
  const line = JSON.stringify(edge) + "\n";
9226
- await appendFile3(graphFilePath(memoryDir, edge.type), line, "utf8");
9287
+ await appendFile4(graphFilePath(memoryDir, edge.type), line, "utf8");
9227
9288
  }
9228
9289
  async function readEdges(memoryDir, type) {
9229
9290
  const filePath = graphFilePath(memoryDir, type);
@@ -9587,7 +9648,7 @@ function recallNamespacesForPrincipal(principal, config) {
9587
9648
  }
9588
9649
 
9589
9650
  // src/shared-context/manager.ts
9590
- import { mkdir as mkdir14, readFile as readFile14, readdir as readdir9, appendFile as appendFile4, writeFile as writeFile13, stat as stat2 } from "fs/promises";
9651
+ import { mkdir as mkdir14, readFile as readFile14, readdir as readdir9, appendFile as appendFile5, writeFile as writeFile13, stat as stat2 } from "fs/promises";
9591
9652
  import path20 from "path";
9592
9653
  import os3 from "os";
9593
9654
  import { z as z3 } from "zod";
@@ -9709,7 +9770,7 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
9709
9770
  }
9710
9771
  async appendFeedback(entry) {
9711
9772
  const parsed = SharedFeedbackEntrySchema.parse(entry);
9712
- await appendFile4(this.feedbackInboxPath, JSON.stringify(parsed) + "\n", "utf-8");
9773
+ await appendFile5(this.feedbackInboxPath, JSON.stringify(parsed) + "\n", "utf-8");
9713
9774
  }
9714
9775
  async appendPrioritiesInbox(opts) {
9715
9776
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -9720,7 +9781,7 @@ title: ${opts.title.replace(/\n/g, " ").slice(0, 200)}
9720
9781
  opts.text.trimEnd(),
9721
9782
  ""
9722
9783
  ].join("\n");
9723
- await appendFile4(this.prioritiesInboxPath, lines, "utf-8");
9784
+ await appendFile5(this.prioritiesInboxPath, lines, "utf-8");
9724
9785
  }
9725
9786
  async curateDaily(opts) {
9726
9787
  const date = opts.date ?? ymd(/* @__PURE__ */ new Date());
@@ -9937,6 +9998,42 @@ function filterRecallCandidates(candidates, options) {
9937
9998
  const scopedByNamespace = options.namespacesEnabled ? candidates.filter((r) => options.recallNamespaces.includes(options.resolveNamespace(r.path))) : candidates;
9938
9999
  return scopedByNamespace.filter((r) => !isArtifactMemoryPath(r.path)).slice(0, Math.max(0, options.limit));
9939
10000
  }
10001
+ function hasLifecycleMetadata(frontmatter) {
10002
+ return frontmatter.lifecycleState !== void 0 || frontmatter.verificationState !== void 0 || frontmatter.policyClass !== void 0 || frontmatter.lastValidatedAt !== void 0 || frontmatter.decayScore !== void 0 || frontmatter.heatScore !== void 0;
10003
+ }
10004
+ function shouldFilterLifecycleRecallCandidate(frontmatter, options) {
10005
+ if (!options.lifecyclePolicyEnabled || !options.lifecycleFilterStaleEnabled) return false;
10006
+ if (!hasLifecycleMetadata(frontmatter)) return false;
10007
+ const lifecycleState = resolveLifecycleState(frontmatter);
10008
+ return lifecycleState === "stale" || lifecycleState === "archived";
10009
+ }
10010
+ function lifecycleRecallScoreAdjustment(frontmatter, options) {
10011
+ if (!options.lifecyclePolicyEnabled) return 0;
10012
+ if (!hasLifecycleMetadata(frontmatter)) return 0;
10013
+ let delta = 0;
10014
+ const lifecycleState = resolveLifecycleState(frontmatter);
10015
+ switch (lifecycleState) {
10016
+ case "active":
10017
+ delta += 0.05;
10018
+ break;
10019
+ case "validated":
10020
+ delta += 0.03;
10021
+ break;
10022
+ case "candidate":
10023
+ delta -= 0.01;
10024
+ break;
10025
+ case "stale":
10026
+ delta -= 0.06;
10027
+ break;
10028
+ case "archived":
10029
+ delta -= 0.08;
10030
+ break;
10031
+ }
10032
+ if (frontmatter.verificationState === "disputed") {
10033
+ delta -= 0.12;
10034
+ }
10035
+ return delta;
10036
+ }
9940
10037
  function computeArtifactRecallLimit(recallMode, recallResultLimit, verbatimArtifactsMaxRecall) {
9941
10038
  if (recallMode === "no_recall") return 0;
9942
10039
  if (Math.max(0, recallResultLimit) === 0) return 0;
@@ -12402,10 +12499,19 @@ ${lines.join("\n\n")}`;
12402
12499
  tagCandidates = capSet(rawTags);
12403
12500
  }
12404
12501
  }
12405
- const boosted = results.map((r) => {
12502
+ let lifecycleFilteredCount = 0;
12503
+ const boosted = [];
12504
+ for (const r of results) {
12406
12505
  const memory = memoryByPath.get(r.path);
12407
12506
  let score = r.score;
12408
12507
  if (memory) {
12508
+ if (shouldFilterLifecycleRecallCandidate(memory.frontmatter, {
12509
+ lifecyclePolicyEnabled: this.config.lifecyclePolicyEnabled,
12510
+ lifecycleFilterStaleEnabled: this.config.lifecycleFilterStaleEnabled
12511
+ })) {
12512
+ lifecycleFilteredCount += 1;
12513
+ continue;
12514
+ }
12409
12515
  if (this.config.recencyWeight > 0) {
12410
12516
  const createdAt = new Date(memory.frontmatter.created).getTime();
12411
12517
  const ageMs = now - createdAt;
@@ -12456,9 +12562,15 @@ ${lines.join("\n\n")}`;
12456
12562
  score += 0.06;
12457
12563
  }
12458
12564
  }
12565
+ score += lifecycleRecallScoreAdjustment(memory.frontmatter, {
12566
+ lifecyclePolicyEnabled: this.config.lifecyclePolicyEnabled
12567
+ });
12459
12568
  }
12460
- return { ...r, score };
12461
- });
12569
+ boosted.push({ ...r, score });
12570
+ }
12571
+ if (lifecycleFilteredCount > 0) {
12572
+ log.debug(`lifecycle retrieval filter removed ${lifecycleFilteredCount} stale/archived candidates`);
12573
+ }
12462
12574
  return boosted.sort((a, b) => b.score - a.score);
12463
12575
  }
12464
12576
  /**