@joshuaswarren/openclaw-engram 9.0.84 → 9.0.86

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
@@ -1096,6 +1096,7 @@ function buildRecallPipelineConfig(cfg) {
1096
1096
  import path42 from "path";
1097
1097
  import os3 from "os";
1098
1098
  import { createHash as createHash7 } from "crypto";
1099
+ import { existsSync as existsSync6 } from "fs";
1099
1100
  import { mkdir as mkdir29, readdir as readdir14, readFile as readFile23, stat as stat6, unlink as unlink5, writeFile as writeFile28 } from "fs/promises";
1100
1101
 
1101
1102
  // src/signal.ts
@@ -20978,6 +20979,11 @@ var Orchestrator = class _Orchestrator {
20978
20979
  if (this.compounding) {
20979
20980
  await this.compounding.ensureDirs();
20980
20981
  }
20982
+ if (this.resolveInit) {
20983
+ this.resolveInit();
20984
+ this.resolveInit = null;
20985
+ log.info("init gate opened (essential state loaded)");
20986
+ }
20981
20987
  {
20982
20988
  const available = await this.qmd.probe();
20983
20989
  if (available) {
@@ -21047,10 +21053,58 @@ var Orchestrator = class _Orchestrator {
21047
21053
  log.debug("initialize: stale signal sweep failed:", err);
21048
21054
  }
21049
21055
  }
21050
- log.info("orchestrator initialized");
21051
- if (this.resolveInit) {
21052
- this.resolveInit();
21053
- this.resolveInit = null;
21056
+ log.info("orchestrator initialized (full)");
21057
+ if (this.config.daySummaryEnabled) {
21058
+ this.autoRegisterDaySummaryCron().catch((err) => {
21059
+ log.debug(`day-summary cron auto-register failed (non-fatal): ${err}`);
21060
+ });
21061
+ }
21062
+ }
21063
+ /**
21064
+ * Auto-register the engram-day-summary cron job in OpenClaw if it doesn't exist.
21065
+ * Fire-and-forget — never blocks init or crashes on failure.
21066
+ */
21067
+ async autoRegisterDaySummaryCron() {
21068
+ const CRON_ID = "engram-day-summary";
21069
+ const home = process.env.HOME || os3.homedir();
21070
+ const jobsPath = path42.join(home, ".openclaw", "cron", "jobs.json");
21071
+ try {
21072
+ if (!existsSync6(jobsPath)) {
21073
+ log.debug("day-summary cron: jobs.json not found, skipping auto-register");
21074
+ return;
21075
+ }
21076
+ const raw = await readFile23(jobsPath, "utf-8");
21077
+ const parsed = JSON.parse(raw);
21078
+ const jobsArray = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.jobs) ? parsed.jobs : null;
21079
+ if (!jobsArray) {
21080
+ log.debug("day-summary cron: jobs.json has unexpected structure, skipping auto-register");
21081
+ return;
21082
+ }
21083
+ if (jobsArray.some((j) => j.id === CRON_ID)) {
21084
+ log.debug("day-summary cron already exists, skipping auto-register");
21085
+ return;
21086
+ }
21087
+ jobsArray.push({
21088
+ id: CRON_ID,
21089
+ agentId: "main",
21090
+ name: "Engram Day Summary (auto)",
21091
+ enabled: true,
21092
+ schedule: { kind: "cron", expr: "47 23 * * *", tz: Intl.DateTimeFormat().resolvedOptions().timeZone },
21093
+ sessionTarget: "isolated",
21094
+ wakeMode: "now",
21095
+ payload: {
21096
+ kind: "agentTurn",
21097
+ timeoutSeconds: 900,
21098
+ thinking: "off",
21099
+ message: "You are OpenClaw automation. Call tool engram.day_summary with empty params (it will auto-gather today's facts). If successful output exactly NO_REPLY. On error output one concise line. Do NOT use message tool."
21100
+ },
21101
+ delivery: { mode: "none" }
21102
+ });
21103
+ const output = Array.isArray(parsed) ? jobsArray : { ...parsed, jobs: jobsArray };
21104
+ await writeFile28(jobsPath, JSON.stringify(output, null, 2) + "\n", "utf-8");
21105
+ log.info(`day-summary cron auto-registered (engram-day-summary, 23:47 ${Intl.DateTimeFormat().resolvedOptions().timeZone})`);
21106
+ } catch (err) {
21107
+ log.debug(`day-summary cron auto-register error: ${err}`);
21054
21108
  }
21055
21109
  }
21056
21110
  async applyBehaviorRuntimePolicy(state) {
@@ -21168,6 +21222,112 @@ var Orchestrator = class _Orchestrator {
21168
21222
  }
21169
21223
  return this.extraction.generateDaySummary(memories);
21170
21224
  }
21225
+ /**
21226
+ * Auto-gather today's facts and hourly summaries from storage, then generate a day summary.
21227
+ * Returns null if no facts are found for today.
21228
+ */
21229
+ async generateDaySummaryAuto(namespace) {
21230
+ const gathered = await this.gatherTodayFacts(namespace);
21231
+ if (!gathered || !gathered.trim()) {
21232
+ log.warn("generateDaySummaryAuto: no facts found for today, skipping");
21233
+ return null;
21234
+ }
21235
+ return this.generateDaySummary(gathered);
21236
+ }
21237
+ /**
21238
+ * Read today's facts and hourly summaries from storage, returning them
21239
+ * as a formatted string suitable for generateDaySummary().
21240
+ */
21241
+ async gatherTodayFacts(namespace) {
21242
+ const ns = namespace && namespace.length > 0 ? namespace : this.config.defaultNamespace;
21243
+ const storage = await this.storageRouter.storageFor(ns);
21244
+ const now = /* @__PURE__ */ new Date();
21245
+ const utcToday = now.toISOString().slice(0, 10);
21246
+ const yesterday = new Date(now.getTime() - 864e5).toISOString().slice(0, 10);
21247
+ const datesToScan = [yesterday, utcToday].filter((v, i, a) => a.indexOf(v) === i);
21248
+ const factsBaseDir = path42.join(storage.dir, "facts");
21249
+ const MAX_CHARS = 1e5;
21250
+ const facts = [];
21251
+ for (const date of datesToScan) {
21252
+ const factsDir = path42.join(factsBaseDir, date);
21253
+ try {
21254
+ const entries = await readdir14(factsDir, { withFileTypes: true });
21255
+ for (const entry of entries) {
21256
+ if (!entry.name.endsWith(".md")) continue;
21257
+ const fullPath = path42.join(factsDir, entry.name);
21258
+ try {
21259
+ const raw = await readFile23(fullPath, "utf-8");
21260
+ const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
21261
+ if (!fmMatch) continue;
21262
+ const fmBlock = fmMatch[1];
21263
+ const content = fmMatch[2].trim();
21264
+ const fm = {};
21265
+ for (const line of fmBlock.split("\n")) {
21266
+ const colonIdx = line.indexOf(":");
21267
+ if (colonIdx === -1) continue;
21268
+ fm[line.slice(0, colonIdx).trim()] = line.slice(colonIdx + 1).trim();
21269
+ }
21270
+ facts.push({
21271
+ path: fullPath,
21272
+ frontmatter: {
21273
+ id: fm.id || path42.basename(entry.name, ".md"),
21274
+ category: fm.category || "fact",
21275
+ created: fm.created || "unknown",
21276
+ updated: fm.updated || fm.created || "unknown",
21277
+ source: fm.source || "unknown",
21278
+ confidence: parseFloat(fm.confidence || "0.8"),
21279
+ confidenceTier: fm.confidenceTier || "implied",
21280
+ tags: []
21281
+ },
21282
+ content
21283
+ });
21284
+ } catch {
21285
+ }
21286
+ }
21287
+ } catch {
21288
+ }
21289
+ }
21290
+ facts.sort((a, b) => a.frontmatter.created < b.frontmatter.created ? -1 : 1);
21291
+ const hourlySummaries = [];
21292
+ const hourlyBaseDir = path42.join(storage.dir, "summaries", "hourly");
21293
+ try {
21294
+ const sessionKeys = await readdir14(hourlyBaseDir, { withFileTypes: true });
21295
+ for (const sk of sessionKeys) {
21296
+ if (!sk.isDirectory()) continue;
21297
+ for (const date of datesToScan) {
21298
+ const summaryFile = path42.join(hourlyBaseDir, sk.name, `${date}.md`);
21299
+ try {
21300
+ const raw = await readFile23(summaryFile, "utf-8");
21301
+ if (raw.trim().length > 0) {
21302
+ hourlySummaries.push(raw.trim());
21303
+ }
21304
+ } catch {
21305
+ }
21306
+ }
21307
+ }
21308
+ } catch {
21309
+ }
21310
+ let formatted = formatDaySummaryMemories(facts);
21311
+ if (hourlySummaries.length > 0) {
21312
+ formatted += "\n\n---\n## Hourly Summaries\n\n" + hourlySummaries.join("\n\n---\n\n");
21313
+ }
21314
+ if (formatted.length > MAX_CHARS) {
21315
+ while (facts.length > 1 && formatted.length > MAX_CHARS) {
21316
+ facts.shift();
21317
+ formatted = formatDaySummaryMemories(facts);
21318
+ if (hourlySummaries.length > 0) {
21319
+ formatted += "\n\n---\n## Hourly Summaries\n\n" + hourlySummaries.join("\n\n---\n\n");
21320
+ }
21321
+ }
21322
+ if (formatted.length > MAX_CHARS) {
21323
+ formatted = formatted.slice(0, MAX_CHARS);
21324
+ }
21325
+ }
21326
+ log.info(
21327
+ `gatherTodayFacts: collected ${facts.length} facts, ${hourlySummaries.length} hourly summaries (${formatted.length} chars)`
21328
+ );
21329
+ return formatted;
21330
+ }
21171
21331
  previewMemoryActionEvent(event) {
21172
21332
  const namespace = typeof event.namespace === "string" && event.namespace.length > 0 ? event.namespace : this.config.defaultNamespace;
21173
21333
  const eligibility = parseMemoryActionEligibilityContext(event.policyEligibility);
@@ -34932,11 +35092,11 @@ var EngramAccessService = class {
34932
35092
  if (!this.orchestrator.config.daySummaryEnabled) {
34933
35093
  throw new EngramAccessInputError("day summary is disabled");
34934
35094
  }
34935
- const memories = request.memories.trim();
35095
+ const memories = (request.memories ?? "").trim();
35096
+ const namespace = this.resolveRecallNamespace(request.namespace, request.sessionKey);
34936
35097
  if (memories.length === 0) {
34937
- throw new EngramAccessInputError("memories is required");
35098
+ return this.orchestrator.generateDaySummaryAuto(namespace);
34938
35099
  }
34939
- this.resolveRecallNamespace(request.namespace, request.sessionKey);
34940
35100
  return this.orchestrator.generateDaySummary(memories);
34941
35101
  }
34942
35102
  async recall(request) {
@@ -35554,7 +35714,7 @@ var EngramAccessService = class {
35554
35714
  // src/access-http.ts
35555
35715
  import { createServer as createServer3 } from "http";
35556
35716
  import { timingSafeEqual as timingSafeEqual2 } from "crypto";
35557
- import { existsSync as existsSync6 } from "fs";
35717
+ import { existsSync as existsSync7 } from "fs";
35558
35718
  import { readFile as readFile40 } from "fs/promises";
35559
35719
  import path66 from "path";
35560
35720
  import { fileURLToPath as fileURLToPath3, URL as URL3 } from "url";
@@ -35610,7 +35770,7 @@ var EngramMcpServer = class {
35610
35770
  },
35611
35771
  {
35612
35772
  name: "engram.day_summary",
35613
- description: "Generate a structured end-of-day summary from recent memory content.",
35773
+ description: "Generate a structured end-of-day summary. When memories is omitted or empty, auto-gathers today's facts and hourly summaries from storage.",
35614
35774
  inputSchema: {
35615
35775
  type: "object",
35616
35776
  properties: {
@@ -35618,7 +35778,7 @@ var EngramMcpServer = class {
35618
35778
  sessionKey: { type: "string" },
35619
35779
  namespace: { type: "string" }
35620
35780
  },
35621
- required: ["memories"],
35781
+ required: [],
35622
35782
  additionalProperties: false
35623
35783
  }
35624
35784
  },
@@ -35969,7 +36129,7 @@ function resolveDefaultAdminConsolePublicDir() {
35969
36129
  fileURLToPath3(new URL3("../admin-console/public", import.meta.url)),
35970
36130
  fileURLToPath3(new URL3("./admin-console/public", import.meta.url))
35971
36131
  ];
35972
- return candidates.find((candidate) => existsSync6(candidate)) ?? candidates[0];
36132
+ return candidates.find((candidate) => existsSync7(candidate)) ?? candidates[0];
35973
36133
  }
35974
36134
  var defaultAdminConsolePublicDir = resolveDefaultAdminConsolePublicDir();
35975
36135
  var WRITE_RATE_LIMIT_WINDOW_MS = 6e4;