@inetafrica/open-claudia 2.6.40 → 2.6.41

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/core/dream.js CHANGED
@@ -41,6 +41,16 @@ const DREAM_MODEL = pickDreamModel();
41
41
  // reasoning effort by default. Overridable via DREAM_EFFORT (low|medium|high|xhigh|max).
42
42
  const DREAM_EFFORT = process.env.DREAM_EFFORT || "max";
43
43
  const DREAM_CRON = process.env.DREAM_CRON || "0 4 * * *";
44
+ // Consolidation model timeout. The model weighs the WHOLE corpus in one pass,
45
+ // so a fixed budget that was fine at 30 packs starves at 130 (the timeout that
46
+ // motivated this). Floor 20m, +6s per pack, capped at 40m; DREAM_TIMEOUT_MS
47
+ // (ms) overrides entirely. Even if it still times out, the deterministic phases
48
+ // (prefix nesting, graph tend) run regardless — the model pass is best-effort.
49
+ function dreamTimeoutMs(packCount = 0) {
50
+ const override = Number(process.env.DREAM_TIMEOUT_MS);
51
+ if (override > 0) return override;
52
+ return Math.min(20 * 60 * 1000 + packCount * 6000, 40 * 60 * 1000);
53
+ }
44
54
  const MAX_PACK_CHARS = 2500;
45
55
  const MAX_ENTITY_CHARS = 900;
46
56
  const LIMITS = {
@@ -277,7 +287,7 @@ function applyDream(decision, backupRoot) {
277
287
  const lines = [];
278
288
  const gone = new Set(); // dirs/slugs removed this run
279
289
 
280
- for (const m of decision.merges) {
290
+ for (const m of decision.merges || []) {
281
291
  try {
282
292
  const into = m?.into && packs.readPack(m.into);
283
293
  const from = [].concat(m?.from || []).filter((d) => d && d !== m.into && !gone.has(d) && packs.readPack(d));
@@ -312,7 +322,7 @@ function applyDream(decision, backupRoot) {
312
322
  } catch (e) { console.warn(`[dream] merge failed: ${e.message}`); }
313
323
  }
314
324
 
315
- for (const u of decision.umbrellas) {
325
+ for (const u of decision.umbrellas || []) {
316
326
  try {
317
327
  const dir = packs.slugify(u?.dir || u?.name);
318
328
  if (!dir || gone.has(dir)) continue;
@@ -345,7 +355,7 @@ function applyDream(decision, backupRoot) {
345
355
  } catch (e) { console.warn(`[dream] umbrella failed: ${e.message}`); }
346
356
  }
347
357
 
348
- for (const p of decision.parents) {
358
+ for (const p of decision.parents || []) {
349
359
  try {
350
360
  const child = p?.pack && !gone.has(p.pack) && packs.readPack(p.pack);
351
361
  if (!child || !p.parent || gone.has(p.parent) || !packs.readPack(p.parent)) continue;
@@ -358,7 +368,7 @@ function applyDream(decision, backupRoot) {
358
368
  } catch (e) { console.warn(`[dream] parent failed: ${e.message}`); }
359
369
  }
360
370
 
361
- for (const r of decision.retag) {
371
+ for (const r of decision.retag || []) {
362
372
  try {
363
373
  if (!r?.pack || gone.has(r.pack) || !packs.readPack(r.pack)) continue;
364
374
  const desc = typeof r.description === "string" ? r.description.trim() : "";
@@ -374,7 +384,7 @@ function applyDream(decision, backupRoot) {
374
384
  } catch (e) { console.warn(`[dream] retag failed: ${e.message}`); }
375
385
  }
376
386
 
377
- for (const em of decision.entity_merges) {
387
+ for (const em of decision.entity_merges || []) {
378
388
  try {
379
389
  const into = em?.into && entities.readEntity(em.into);
380
390
  const from = [].concat(em?.from || []).filter((s) => s && s !== em.into && !gone.has(s) && entities.readEntity(s));
@@ -397,7 +407,7 @@ function applyDream(decision, backupRoot) {
397
407
  } catch (e) { console.warn(`[dream] entity merge failed: ${e.message}`); }
398
408
  }
399
409
 
400
- for (const en of decision.entity_notes) {
410
+ for (const en of decision.entity_notes || []) {
401
411
  try {
402
412
  const ent = en?.entity && !gone.has(en.entity) && entities.readEntity(en.entity);
403
413
  if (!ent || typeof en.notes !== "string" || !en.notes.trim()) continue;
@@ -736,14 +746,26 @@ async function runDream({ trigger = "manual" } = {}) {
736
746
 
737
747
  _dreaming = true;
738
748
  try {
739
- const { text } = await spawnSubagent(buildDreamPrompt(), {
740
- model: DREAM_MODEL,
741
- effort: DREAM_EFFORT,
742
- timeoutMs: 8 * 60 * 1000,
743
- systemPrompt: "You are a background memory consolidation process. Reply with ONLY the requested JSON object. No prose, no markdown, no tool use.",
744
- });
745
- const decision = parseDream(text);
746
- if (!decision) return { skipped: "dream model returned unparseable output" };
749
+ // Best-effort consolidation: on a large corpus this call can time out or
750
+ // return unreadable JSON. That must NOT abort the dream — the deterministic
751
+ // phases below (prefix nesting, graph tend) need no model and are the
752
+ // load-bearing cleanup. Catch any model failure, fall back to an empty
753
+ // decision (applyDream no-ops), note it for the report, and carry on.
754
+ let decision = null;
755
+ let modelFailNote = "";
756
+ try {
757
+ const { text } = await spawnSubagent(buildDreamPrompt(), {
758
+ model: DREAM_MODEL,
759
+ effort: DREAM_EFFORT,
760
+ timeoutMs: dreamTimeoutMs(packCount),
761
+ systemPrompt: "You are a background memory consolidation process. Reply with ONLY the requested JSON object. No prose, no markdown, no tool use.",
762
+ });
763
+ decision = parseDream(text);
764
+ if (!decision) modelFailNote = "the AI consolidation step returned unreadable output";
765
+ } catch (e) {
766
+ modelFailNote = `the AI consolidation step didn't finish (${e.message})`;
767
+ }
768
+ if (!decision) decision = { report: "" };
747
769
 
748
770
  const backupRoot = makeBackupRoot();
749
771
  const applied = applyDream(decision, backupRoot);
@@ -754,7 +776,10 @@ async function runDream({ trigger = "manual" } = {}) {
754
776
  try { nestLines = nestPrefixFamilies(backupRoot).lines; }
755
777
  catch (e) { console.warn(`[dream] nesting pass failed: ${e.message}`); }
756
778
  const consolidationLines = applied.concat(nestLines);
757
- const report = decision.report || (consolidationLines.length > 0 ? "Tidied up my memory overnight." : "");
779
+ let report = decision.report || (consolidationLines.length > 0 ? "Tidied up my memory overnight." : "");
780
+ if (modelFailNote) {
781
+ report = `Heads up — ${modelFailNote}, so I skipped the AI-led merges this round but still ran the automatic filing + graph upkeep.${report ? " " + report : ""}`;
782
+ }
758
783
 
759
784
  // Phase 2: self-improvement introspection. Reviews the day's seeds, reads
760
785
  // its own code/memory READ-ONLY (allowedTools whitelist), promotes lessons
@@ -860,7 +885,7 @@ function initDream(adapters) {
860
885
  }
861
886
 
862
887
  module.exports = {
863
- runDream, initDream, buildDreamPrompt, parseDream, applyDream, manageAbilityTiers, nestPrefixFamilies,
888
+ runDream, initDream, buildDreamPrompt, parseDream, applyDream, manageAbilityTiers, nestPrefixFamilies, dreamTimeoutMs,
864
889
  buildIntrospectionPrompt, parseIntrospection, applyIntrospection, writeDreamReport,
865
890
  enabled, summaryEnabled, introspectEnabled, selfApplyEnabled,
866
891
  DREAM_CRON, DREAM_MODEL, DREAM_EFFORT, PROMOTE_MIN_PROJECTS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.6.40",
3
+ "version": "2.6.41",
4
4
  "description": "Your always-on AI coding assistant — Claude Code, Cursor Agent, and OpenAI Codex via Telegram or Kazee Chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -77,6 +77,21 @@ assert.ok(res.lines.some((l) => l.includes("billing")), "nest pass announces the
77
77
  const again = dream.nestPrefixFamilies(null);
78
78
  assert.strictEqual(again.filed, 0, "second pass files nothing new");
79
79
 
80
+ // ── dream resilience: the deterministic cleanup must not depend on the model ──
81
+ // applyDream tolerates an empty decision (the fallback used when the AI
82
+ // consolidation step times out / returns garbage) instead of throwing.
83
+ assert.deepStrictEqual(dream.applyDream({}, null), [], "empty decision applies nothing, throws nothing");
84
+ assert.deepStrictEqual(dream.applyDream({ report: "" }, null), [], "report-only fallback decision is safe");
85
+
86
+ // timeout scales with corpus: floor 20m, grows per pack, capped at 40m, env override.
87
+ assert.strictEqual(dream.dreamTimeoutMs(0), 20 * 60 * 1000, "floor is 20 minutes");
88
+ assert.ok(dream.dreamTimeoutMs(130) > dream.dreamTimeoutMs(30), "more packs → longer budget");
89
+ assert.strictEqual(dream.dreamTimeoutMs(100000), 40 * 60 * 1000, "capped at 40 minutes");
90
+ const prev = process.env.DREAM_TIMEOUT_MS;
91
+ process.env.DREAM_TIMEOUT_MS = "777000";
92
+ assert.strictEqual(dream.dreamTimeoutMs(50), 777000, "DREAM_TIMEOUT_MS overrides entirely");
93
+ if (prev === undefined) delete process.env.DREAM_TIMEOUT_MS; else process.env.DREAM_TIMEOUT_MS = prev;
94
+
80
95
  // ── versioned-only fold (FTS-gated): a versioned proposal folds into its pack,
81
96
  // a non-versioned near-duplicate does NOT. Skipped when node:sqlite is absent. ──
82
97
  if (packs.reindex()) {