@inetafrica/open-claudia 2.6.41 → 2.6.42
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/CHANGELOG.md +3 -0
- package/core/recall/read-signal.js +32 -0
- package/core/runner.js +19 -1
- package/package.json +4 -3
- package/test-read-signal.js +57 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.6.42
|
|
4
|
+
- **Read-time `📖 Recalled my notes on …` now fires for direct file reads, not just the CLI.** The read-side recall banner — and the Hebbian co-use signal that reinforces the recall graph — was wired only to shell commands, watching for `open-claudia pack show <dir>` / `entity show <slug>`. When the agent opened one of its own notes by reading the raw `…/packs/<dir>/PACK.md` or `…/entities/<slug>.md` straight through the Read tool, the detector never saw it: no banner, and the open never counted as co-use. A new `core/recall/read-signal.js` resolves a file path to its memory node (mirroring the existing Write/Edit path-detection), and `runner.js` calls it on Read tool-uses across the Claude and Cursor backends — deduped against the CLI path via the shared notify key, so reading the same node both ways still announces once. Adds `test-read-signal.js`.
|
|
5
|
+
|
|
3
6
|
## v2.6.37
|
|
4
7
|
- **`/recall [on|off]` — watch recall work.** A per-chat debug toggle that, when on, posts a short `🧠 Recall this turn` line just before each reply listing the packs/entities that surfaced — and on the **discoverer** engine, the one-line why-bullet for each. On a gated turn (pre-gate skipped recall) it says so; on a quiet turn with no matches it stays silent. The engines now return a `why` map + `gated` flag, `promptWithDynamicContext` captures a recall summary into the per-turn `consumeLastInjected()` buffer, and `runner.js` renders it when `settings.showRecall` is set. Off by default; flip with `/recall` (buttons) or `/recall on`.
|
|
5
8
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Read-time recall signal: resolve a file path to the memory node it
|
|
2
|
+
// represents — a context pack (…/packs/<dir>/PACK.md) or an entity note
|
|
3
|
+
// (…/entities/<slug>.md) — or null if it's neither.
|
|
4
|
+
//
|
|
5
|
+
// The runner fires "📖 Recalled my notes on …" and reinforces the recall
|
|
6
|
+
// co-use graph when the agent OPENS one of its own notes. That detection used
|
|
7
|
+
// to watch only shell commands (`open-claudia pack show <dir>` / `entity show
|
|
8
|
+
// <slug>`), so reading the raw .md straight through the Read tool was invisible
|
|
9
|
+
// to both the banner and the graph. This mirrors the Write/Edit path-detection
|
|
10
|
+
// so a direct read counts the same as a CLI read. Best-effort: it returns a
|
|
11
|
+
// display name when the doc is on disk and falls back to the id otherwise.
|
|
12
|
+
const packsLib = require("../packs");
|
|
13
|
+
const entitiesLib = require("../entities");
|
|
14
|
+
|
|
15
|
+
function recallNodeFromPath(filePath) {
|
|
16
|
+
if (!filePath) return null;
|
|
17
|
+
try {
|
|
18
|
+
const packDir = packsLib.packNameFromPath(filePath);
|
|
19
|
+
if (packDir) {
|
|
20
|
+
const pack = packsLib.readPack(packDir);
|
|
21
|
+
return { kind: "pack", id: packDir, name: (pack && (pack.name || pack.dir)) || packDir };
|
|
22
|
+
}
|
|
23
|
+
const entSlug = entitiesLib.entityNameFromPath(filePath);
|
|
24
|
+
if (entSlug) {
|
|
25
|
+
const ent = entitiesLib.readEntity(entSlug);
|
|
26
|
+
return { kind: "entity", id: entSlug, name: (ent && (ent.name || ent.slug)) || entSlug };
|
|
27
|
+
}
|
|
28
|
+
} catch (e) { /* best-effort: announcements never block a turn */ }
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { recallNodeFromPath };
|
package/core/runner.js
CHANGED
|
@@ -28,6 +28,7 @@ const skillsLib = require("./skills");
|
|
|
28
28
|
const packsLib = require("./packs");
|
|
29
29
|
const entitiesLib = require("./entities");
|
|
30
30
|
const packReview = require("./pack-review");
|
|
31
|
+
const { recallNodeFromPath } = require("./recall/read-signal");
|
|
31
32
|
const {
|
|
32
33
|
appendUsageRecord,
|
|
33
34
|
loadUsageHistory,
|
|
@@ -897,6 +898,20 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
897
898
|
} catch (e) { /* announcements are best-effort */ }
|
|
898
899
|
};
|
|
899
900
|
|
|
901
|
+
// The same 📖 signal when the agent opens a note with the Read tool instead
|
|
902
|
+
// of the CLI: noteRecallFromShell only sees shell commands, so a raw-file
|
|
903
|
+
// read of a pack/entity would otherwise be invisible to both the banner and
|
|
904
|
+
// the co-use graph. Deduped against the CLI path via the shared notifySkill
|
|
905
|
+
// key, so reading the same node both ways still announces only once.
|
|
906
|
+
const noteRecallFromReadPath = (filePath) => {
|
|
907
|
+
try {
|
|
908
|
+
const node = recallNodeFromPath(filePath);
|
|
909
|
+
if (!node) return;
|
|
910
|
+
openedThisTurn.add(`${node.kind}:${node.id}`);
|
|
911
|
+
notifySkill(`recall:${node.kind}:${node.id}`, `📖 Recalled my notes on: ${node.name}`);
|
|
912
|
+
} catch (e) { /* announcements are best-effort */ }
|
|
913
|
+
};
|
|
914
|
+
|
|
900
915
|
let args;
|
|
901
916
|
try {
|
|
902
917
|
args = await buildClaudeArgs(prompt, opts);
|
|
@@ -1009,6 +1024,7 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1009
1024
|
else currentToolDetail = "";
|
|
1010
1025
|
noteSkillToolUse(block.name, input);
|
|
1011
1026
|
if (block.name === "Bash" && input.command) noteRecallFromShell(input.command);
|
|
1027
|
+
else if (block.name === "Read") noteRecallFromReadPath(input.file_path || input.filePath);
|
|
1012
1028
|
}
|
|
1013
1029
|
}
|
|
1014
1030
|
}
|
|
@@ -1021,7 +1037,9 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1021
1037
|
if (a.command) noteRecallFromShell(a.command);
|
|
1022
1038
|
} else if (tc.readToolCall) {
|
|
1023
1039
|
currentTool = "Read"; toolUses.push("Read");
|
|
1024
|
-
|
|
1040
|
+
const readPath = tc.readToolCall.args?.path;
|
|
1041
|
+
currentToolDetail = (readPath || "").split("/").slice(-2).join("/");
|
|
1042
|
+
noteRecallFromReadPath(readPath);
|
|
1025
1043
|
} else if (tc.editToolCall) {
|
|
1026
1044
|
currentTool = "Edit"; toolUses.push("Edit");
|
|
1027
1045
|
currentToolDetail = (tc.editToolCall.args?.filePath || "").split("/").slice(-2).join("/");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.42",
|
|
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": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"setup": "node setup.js",
|
|
11
11
|
"start": "node bot.js",
|
|
12
|
-
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-pack-nesting.js"
|
|
12
|
+
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=node node test-read-signal.js"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"bot.js",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"test-ability-tiers.js",
|
|
43
43
|
"test-ability-merge-guard.js",
|
|
44
44
|
"test-learning-e2e.js",
|
|
45
|
-
"test-pack-nesting.js"
|
|
45
|
+
"test-pack-nesting.js",
|
|
46
|
+
"test-read-signal.js"
|
|
46
47
|
],
|
|
47
48
|
"keywords": [
|
|
48
49
|
"claude",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Read-time recall signal: a Read of a pack/entity .md must resolve to the
|
|
2
|
+
// right memory node so the runner can fire "📖 Recalled my notes on …" (and
|
|
3
|
+
// reinforce the co-use graph) even when the agent reads the raw file directly
|
|
4
|
+
// instead of going through `open-claudia pack show` / `entity show`.
|
|
5
|
+
const assert = require("assert");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
|
|
10
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "read-signal-"));
|
|
11
|
+
process.env.PACKS_DIR = path.join(tmp, "packs");
|
|
12
|
+
process.env.ENTITIES_DIR = path.join(tmp, "entities");
|
|
13
|
+
|
|
14
|
+
const packs = require("./core/packs");
|
|
15
|
+
const entities = require("./core/entities");
|
|
16
|
+
const { recallNodeFromPath } = require("./core/recall/read-signal");
|
|
17
|
+
|
|
18
|
+
// ── a pack PACK.md resolves to its pack node (display name preferred) ──
|
|
19
|
+
packs.createPack({ dir: "billing-invoices", name: "Billing Invoices", description: "invoice generation" });
|
|
20
|
+
const packPath = path.join(process.env.PACKS_DIR, "billing-invoices", "PACK.md");
|
|
21
|
+
assert.deepStrictEqual(
|
|
22
|
+
recallNodeFromPath(packPath),
|
|
23
|
+
{ kind: "pack", id: "billing-invoices", name: "Billing Invoices" },
|
|
24
|
+
"pack path → pack node carrying the display name"
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// ── an entity .md resolves to its entity node ──
|
|
28
|
+
entities.upsertEntity({ name: "David Omondi", type: "person", description: "store keeper" });
|
|
29
|
+
const entPath = path.join(process.env.ENTITIES_DIR, "david-omondi.md");
|
|
30
|
+
assert.deepStrictEqual(
|
|
31
|
+
recallNodeFromPath(entPath),
|
|
32
|
+
{ kind: "entity", id: "david-omondi", name: "David Omondi" },
|
|
33
|
+
"entity path → entity node carrying the display name"
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// ── a node that isn't on disk still resolves by id (name falls back to id) ──
|
|
37
|
+
assert.deepStrictEqual(
|
|
38
|
+
recallNodeFromPath(path.join(process.env.PACKS_DIR, "ghost-pack", "PACK.md")),
|
|
39
|
+
{ kind: "pack", id: "ghost-pack", name: "ghost-pack" },
|
|
40
|
+
"missing pack falls back to id as name"
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// ── anything that isn't a pack/entity file resolves to nothing ──
|
|
44
|
+
assert.strictEqual(recallNodeFromPath(null), null, "null path → null");
|
|
45
|
+
assert.strictEqual(recallNodeFromPath("/etc/passwd"), null, "outside the memory dirs → null");
|
|
46
|
+
assert.strictEqual(
|
|
47
|
+
recallNodeFromPath(path.join(process.env.PACKS_DIR, "billing-invoices", "notes.md")),
|
|
48
|
+
null,
|
|
49
|
+
"a non-PACK.md file inside a pack dir → null"
|
|
50
|
+
);
|
|
51
|
+
assert.strictEqual(
|
|
52
|
+
recallNodeFromPath(path.join(process.env.ENTITIES_DIR, "sub", "deep.md")),
|
|
53
|
+
null,
|
|
54
|
+
"an entity .md in a subdir → null"
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
console.log("read signal OK");
|