@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 +1 -1
- package/core/pack-review.js +1 -1
- package/core/runner.js +37 -9
- package/core/system-prompt.js +48 -10
- package/package.json +1 -1
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-
|
|
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);
|
package/core/pack-review.js
CHANGED
|
@@ -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
|
|
847
|
-
//
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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;
|
package/core/system-prompt.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
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