@inetafrica/open-claudia 2.6.31 → 2.6.33

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/config.js CHANGED
@@ -90,7 +90,7 @@ const WORKSPACE = config.WORKSPACE;
90
90
  const CLAUDE_PATH = resolveExecutablePath(config.CLAUDE_PATH, null, "Claude CLI", { required: true });
91
91
  const CURSOR_PATH = config.CURSOR_PATH || null;
92
92
  const CODEX_PATH = config.CODEX_PATH || null;
93
- const DEFAULT_CLAUDE_MODEL = config.CLAUDE_MODEL || process.env.CLAUDE_MODEL || "claude-fable-5";
93
+ const DEFAULT_CLAUDE_MODEL = config.CLAUDE_MODEL || process.env.CLAUDE_MODEL || "claude-opus-4-8";
94
94
  const AUTO_COMPACT_TOKENS = parseInt(config.AUTO_COMPACT_TOKENS || process.env.AUTO_COMPACT_TOKENS || "380000", 10);
95
95
  const MIN_COMPACT_INTERVAL_MS = parseInt(config.MIN_COMPACT_INTERVAL_MS || process.env.MIN_COMPACT_INTERVAL_MS || "1800000", 10);
96
96
  const PROJECT_TRANSCRIPTS = configTruthy(config.PROJECT_TRANSCRIPTS || process.env.PROJECT_TRANSCRIPTS, true);
@@ -216,7 +216,7 @@ function reviewTurn({ userText, assistantText, channelId, announce }) {
216
216
  } else if (r) {
217
217
  lines.push(r.kind === "create"
218
218
  ? `šŸ“¦ New pack: ${r.name}\n${clipWords(r.note, 180)}\n↳ open-claudia pack show ${r.dir}`
219
- : `āœļø ${r.name} — ${clipWords(r.note, 180)}`);
219
+ : `āœļø ${r.name} — ${clipWords(r.note, 180)}\n↳ open-claudia pack show ${r.dir}`);
220
220
  }
221
221
  } catch (e) {
222
222
  console.warn(`[pack-review] apply failed: ${e.message}`);
package/core/runner.js CHANGED
@@ -842,16 +842,41 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
842
842
  } catch (e) { /* announcements are best-effort */ }
843
843
  };
844
844
 
845
+ // Read-side recall: fire "šŸ“– Recalled my notes on …" only when the agent
846
+ // actually pulls a full pack/entity via the CLI (it decided it was worth
847
+ // reading), not when a headline was auto-injected. Deduped per turn through
848
+ // notifySkill's Set so repeated `pack show` calls don't spam the chat.
849
+ const noteRecallFromShell = (command) => {
850
+ try {
851
+ const cmd = String(command || "");
852
+ if (!cmd.includes("open-claudia")) return;
853
+ let m;
854
+ const packRe = /\bpack\s+show\s+["']?([a-z0-9][\w.-]*)/gi;
855
+ while ((m = packRe.exec(cmd))) {
856
+ const dir = m[1];
857
+ const pack = packsLib.readPack(dir);
858
+ const name = (pack && (pack.name || pack.dir)) || dir;
859
+ notifySkill(`recall:pack:${dir}`, `šŸ“– Recalled my notes on: ${name}`);
860
+ }
861
+ const entRe = /\bentity\s+show\s+["']?([a-z0-9][\w.-]*)/gi;
862
+ while ((m = entRe.exec(cmd))) {
863
+ const slug = m[1];
864
+ const ent = entitiesLib.readEntity(slug);
865
+ const name = (ent && (ent.name || ent.slug)) || slug;
866
+ notifySkill(`recall:entity:${slug}`, `šŸ“– Recalled my notes on: ${name}`);
867
+ }
868
+ } catch (e) { /* announcements are best-effort */ }
869
+ };
870
+
845
871
  const args = await buildClaudeArgs(prompt, opts);
846
- // Recall announcements: mirror the write-side "🧠 jotted down" notes with
847
- // a read-side line whenever packs/entities freshly enter context this turn.
848
- try {
849
- const { packs, entities } = require("./system-prompt").consumeLastInjected();
850
- const recalled = [...packs, ...entities];
851
- if (recalled.length > 0) {
852
- chatContext.run(store, () => send(`šŸ“– Recalled my notes on: ${recalled.join(", ")}`).catch(() => {}));
853
- }
854
- } catch (e) { /* announcements are best-effort */ }
872
+ // Recall announcements are now fired at READ time, not injection time:
873
+ // matched packs/entities enter context only as small headlines (see
874
+ // system-prompt.js recallHeadline). The "šŸ“– Recalled my notes on …" line is
875
+ // emitted by noteRecallFromShell below when the agent actually runs
876
+ // `open-claudia pack show <dir>` / `entity show <slug>` — so the banner
877
+ // reflects what was read, not what was pushed. (consumeLastInjected is
878
+ // drained here to keep the per-turn buffer from leaking into the next turn.)
879
+ try { require("./system-prompt").consumeLastInjected(); } catch (e) { /* best-effort */ }
855
880
  const binaryPath = getActiveBinary();
856
881
  const proc = spawn(binaryPath, args, {
857
882
  cwd,
@@ -928,6 +953,7 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
928
953
  else if (block.name === "Skill" && input.skill) currentToolDetail = input.skill;
929
954
  else currentToolDetail = "";
930
955
  noteSkillToolUse(block.name, input);
956
+ if (block.name === "Bash" && input.command) noteRecallFromShell(input.command);
931
957
  }
932
958
  }
933
959
  }
@@ -937,6 +963,7 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
937
963
  const a = tc.shellToolCall.args || {};
938
964
  currentTool = "Shell"; toolUses.push("Shell");
939
965
  currentToolDetail = (a.description || a.command || "").slice(0, 80);
966
+ if (a.command) noteRecallFromShell(a.command);
940
967
  } else if (tc.readToolCall) {
941
968
  currentTool = "Read"; toolUses.push("Read");
942
969
  currentToolDetail = (tc.readToolCall.args?.path || "").split("/").slice(-2).join("/");
@@ -983,6 +1010,7 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
983
1010
  if (it.type === "command_execution" && it.command) {
984
1011
  currentTool = "Shell"; toolUses.push("Shell");
985
1012
  currentToolDetail = String(it.command).slice(0, 80);
1013
+ noteRecallFromShell(it.command);
986
1014
  } else if (it.type === "file_change" && (it.path || it.file_path)) {
987
1015
  currentTool = "Edit"; toolUses.push("Edit");
988
1016
  const p = it.path || it.file_path;
@@ -304,6 +304,17 @@ function packMatchLimit() {
304
304
  return intSetting("PACK_MATCH_LIMIT", packProgressive() ? 6 : 3);
305
305
  }
306
306
 
307
+ // Pull model: when on, a matched pack/entity is injected as a *headline*
308
+ // (description + Stance for packs, description for entities) plus a pointer.
309
+ // The heavier sections (State/Procedure/Journal, entity Notes/Log) are NOT
310
+ // pushed — the agent decides which packs are worth a real read and pulls them
311
+ // on demand via `open-claudia pack show <dir>` / `entity show <slug>`. This is
312
+ // what makes the "šŸ“– Recalled my notes on …" banner mean "what I actually
313
+ // read" (fired at read time in the runner) instead of "what got pushed at me".
314
+ function recallHeadline() {
315
+ return String(config.RECALL_HEADLINE ?? process.env.RECALL_HEADLINE ?? "on").toLowerCase() === "on";
316
+ }
317
+
307
318
  function intSetting(key, fallback) {
308
319
  const raw = config[key] ?? process.env[key];
309
320
  if (raw === undefined || raw === null || raw === "") return fallback;
@@ -346,6 +357,7 @@ function consumeLastInjected() {
346
357
  }
347
358
 
348
359
  function formatPackForContext(pack, packsLib, opts = {}) {
360
+ const headline = !!opts.headline;
349
361
  const progressive = !!opts.progressive;
350
362
  const clip = (s, n) => (s.length > n ? s.slice(0, n) + "\n…[truncated — read the full pack file]" : s);
351
363
  const parts = [`### Pack: ${pack.name} (${pack.dir})`];
@@ -362,12 +374,22 @@ function formatPackForContext(pack, packsLib, opts = {}) {
362
374
  // Progressive mode injects only the live sections (Stance = how to think,
363
375
  // State = where we are) plus a pointer; full mode also inlines Procedure
364
376
  // and the recent Journal.
365
- const sections = progressive ? ["Stance", "State"] : ["Stance", "Procedure", "State"];
377
+ // Headline = description + Stance only (Stance carries hard rules/prefs that
378
+ // must always apply when the topic comes up). Everything else is pulled on
379
+ // demand. Progressive = Stance + State. Full = Stance + Procedure + State.
380
+ const sections = headline
381
+ ? ["Stance"]
382
+ : progressive
383
+ ? ["Stance", "State"]
384
+ : ["Stance", "Procedure", "State"];
366
385
  for (const section of sections) {
367
386
  const body = (pack.sections[section] || "").trim();
368
387
  if (body) parts.push(`#### ${section}\n${body}`);
369
388
  }
370
- if (progressive) {
389
+ if (headline) {
390
+ const hidden = ["State", "Procedure", "Journal"].filter((s) => (pack.sections[s] || "").trim());
391
+ if (hidden.length) parts.push(`#### More\n${hidden.join(" + ")} not shown — run \`open-claudia pack show ${pack.dir}\` to read them.`);
392
+ } else if (progressive) {
371
393
  const hasMore = (pack.sections.Procedure || "").trim() || (pack.sections.Journal || "").trim();
372
394
  if (hasMore) parts.push(`#### More\nProcedure + Journal not shown — run \`open-claudia pack show ${pack.dir}\` to read them.`);
373
395
  } else {
@@ -386,6 +408,7 @@ function buildPackBlock(matches, budget) {
386
408
  const channelId = currentChannelId();
387
409
  const sess = state.lastSessionId || "new";
388
410
  const progressive = packProgressive();
411
+ const headline = recallHeadline();
389
412
  const blocks = [];
390
413
  const used = [];
391
414
  for (const m of matches) {
@@ -400,7 +423,7 @@ function buildPackBlock(matches, budget) {
400
423
  // as the task tree), so the pack survives compaction without paying
401
424
  // to re-stamp an anchor on every intervening turn.
402
425
  if (packsInjectedFor.get(key) === stamp) continue;
403
- const block = formatPackForContext(pack, packsLib, { progressive });
426
+ const block = formatPackForContext(pack, packsLib, { progressive, headline });
404
427
  if (!tryUseRecallBudget(budget, block)) continue;
405
428
  packsInjectedFor.set(key, stamp);
406
429
  lastInjected.packs.push(pack.name || m.dir);
@@ -408,7 +431,10 @@ function buildPackBlock(matches, budget) {
408
431
  }
409
432
  if (used.length > 0) setImmediate(() => { try { packsLib.touchUsed(used); } catch (e) {} });
410
433
  if (blocks.length === 0) return "";
411
- return `\n\n## Active Open Claudia skills / context packs\nLong-term topic context auto-matched to this message. If the user asked for a skill by name, treat the matching pack's Procedure section as that Open Claudia skill. Source files live under ${packsLib.PACKS_DIR}/<dir>/PACK.md — read or edit them directly when deeper context or a correction is needed.\n\n${blocks.join("\n\n---\n\n")}`;
434
+ const intro = headline
435
+ ? `Long-term topic context auto-matched to this message — these are *headlines* (description + Stance only), not full packs. Decide which are actually relevant and pull the full State/Procedure/Journal on demand with \`open-claudia pack show <dir>\`. If the user asked for a skill by name, \`pack show\` the matching pack to load its Procedure. Source files live under ${packsLib.PACKS_DIR}/<dir>/PACK.md — read or edit them directly when deeper context or a correction is needed.`
436
+ : `Long-term topic context auto-matched to this message. If the user asked for a skill by name, treat the matching pack's Procedure section as that Open Claudia skill. Source files live under ${packsLib.PACKS_DIR}/<dir>/PACK.md — read or edit them directly when deeper context or a correction is needed.`;
437
+ return `\n\n## Active Open Claudia skills / context packs\n${intro}\n\n${blocks.join("\n\n---\n\n")}`;
412
438
  } catch (e) {
413
439
  return "";
414
440
  }
@@ -420,14 +446,23 @@ function buildPackBlock(matches, budget) {
420
446
  const entitiesInjectedFor = new Map(); // `${adapterId}:${channelId}:${slug}` -> `${sessionId}:${updated}`
421
447
  const ENTITY_INJECT_MAX_CHARS = 1200;
422
448
 
423
- function formatEntityForContext(ent) {
449
+ function formatEntityForContext(ent, opts = {}) {
450
+ const headline = !!opts.headline;
451
+ const slug = opts.slug || ent.slug;
424
452
  const clip = (s, n) => (s.length > n ? s.slice(0, n) + "\n…[truncated — read the full entity file]" : s);
425
453
  const head = `### ${ent.name} (${ent.type}${ent.aliases.length ? `, aka ${ent.aliases.join(", ")}` : ""})`;
426
454
  const parts = [head];
427
455
  if (ent.description) parts.push(ent.description);
428
- if (ent.sections.Notes) parts.push(ent.sections.Notes);
429
- const log = ent.sections.Log.split("\n").filter(Boolean).slice(-4).join("\n");
430
- if (log) parts.push(`Recent:\n${log}`);
456
+ if (headline) {
457
+ const hidden = [];
458
+ if ((ent.sections.Notes || "").trim()) hidden.push("Notes");
459
+ if ((ent.sections.Log || "").trim()) hidden.push("Log");
460
+ if (hidden.length) parts.push(`${hidden.join(" + ")} not shown — run \`open-claudia entity show ${slug}\` to read them.`);
461
+ } else {
462
+ if (ent.sections.Notes) parts.push(ent.sections.Notes);
463
+ const log = ent.sections.Log.split("\n").filter(Boolean).slice(-4).join("\n");
464
+ if (log) parts.push(`Recent:\n${log}`);
465
+ }
431
466
  return clip(parts.join("\n\n"), ENTITY_INJECT_MAX_CHARS);
432
467
  }
433
468
 
@@ -448,7 +483,7 @@ function buildEntityBlock(matches, budget) {
448
483
  const key = `${adapter?.id || "?"}:${channelId || "?"}:${m.slug}`;
449
484
  const stamp = `${sess}:${ent.updated}`;
450
485
  if (entitiesInjectedFor.get(key) === stamp) continue;
451
- const block = formatEntityForContext(ent);
486
+ const block = formatEntityForContext(ent, { headline: recallHeadline(), slug: m.slug });
452
487
  if (!tryUseRecallBudget(budget, block)) continue;
453
488
  entitiesInjectedFor.set(key, stamp);
454
489
  lastInjected.entities.push(ent.name || m.slug);
@@ -456,7 +491,10 @@ function buildEntityBlock(matches, budget) {
456
491
  }
457
492
  if (seen.length > 0) setImmediate(() => { try { entitiesLib.touchSeen(seen); } catch (e) {} });
458
493
  if (blocks.length === 0) return "";
459
- return `\n\n## Known entities\nMemory notes on people/places/projects auto-matched to this message. Source files live under ${entitiesLib.ENTITIES_DIR}/<slug>.md — read or edit them directly to correct or deepen.\n\n${blocks.join("\n\n---\n\n")}`;
494
+ const intro = recallHeadline()
495
+ ? `Memory notes on people/places/projects auto-matched to this message — headlines only (description + a pointer). Pull a full note's Notes/Log on demand with \`open-claudia entity show <slug>\`. Source files live under ${entitiesLib.ENTITIES_DIR}/<slug>.md — read or edit them directly to correct or deepen.`
496
+ : `Memory notes on people/places/projects auto-matched to this message. Source files live under ${entitiesLib.ENTITIES_DIR}/<slug>.md — read or edit them directly to correct or deepen.`;
497
+ return `\n\n## Known entities\n${intro}\n\n${blocks.join("\n\n---\n\n")}`;
460
498
  } catch (e) {
461
499
  return "";
462
500
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.6.31",
3
+ "version": "2.6.33",
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": {