@inetafrica/open-claudia 3.1.1 → 3.1.3

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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.1.3 — Pocket TTS voice-note provider
4
+
5
+ - Adds Agent Space Pocket TTS as the preferred voice synthesizer for Telegram and streamed voice replies.
6
+ - Requests WAV from the authenticated speech gateway and transcodes it to Ogg/Opus with ffmpeg.
7
+ - Keeps local Whisper transcription unchanged and falls back to ElevenLabs, then macOS `say`, on synthesis failure.
8
+ - Adds configurable `SPEECH_SERVICE_URL`, `SPEECH_API_KEY`, `POCKET_TTS_VOICE`, and `TTS_PROVIDER` settings while stripping the speech credential from provider subprocess environments.
9
+
10
+ ## v3.1.2 — On-demand recall query + cold-walker fallback fix
11
+
12
+ Two things fold in: a new `recall query` CLI so the working agent can ask long-term memory a question mid-task, and a latent reliability fix in the recall walker's fallback path.
13
+
14
+ - **New `open-claudia recall query "<question>"`** — the agent's side door into the seed → graph → walker pipeline. Where per-turn recall seeds on the *user's* message, this seeds the graph with the agent's OWN working context (via `--context`), walks the typed-edge graph with the **active-provider** judge, and returns the kept pack sections with why-bullets and pointers. It closes the documented cold-start hole: mid-build, when the vocabulary you're using shares no words with the pack that governs the work, ordinary recall never resurfaces it — an explicit query carrying your working context does. Read-only (no edge reinforcement), no pre-gate (an explicit ask always runs full), and fail-open (a failed judge returns the whole candidate list unjudged rather than nothing). Flags: `--context`, `--json`, `--no-walker`, `--no-episodes`, `--limit`, `--timeout`.
15
+ - **Cold-walker outputSchema 400 fix** — the recall walker prefers a warm persistent judge (Claude only) and falls back to a cold spawn otherwise. The cold path was passing an array-typed `outputSchema`, which the Anthropic API turns into a custom-tool `input_schema` and rejects with `400 input_schema.type: Input should be 'object'`. So any time the warm walker errored and fell back, recall silently 400'd and failed-open — judgement quietly disabled on the fallback path. Removed the schema entirely (the warm path never sent one, and `walk()` already extracts the JSON array from freeform text, filters to known ids, and clamps scores), so freeform is the proven contract on both paths.
16
+ - **Agent-space / OpenClaw:** new `core/recall/query.js` (`query()` + `mergeMatches`), `excerptFor` exported from the discoverer, `recall query` wired into `bin/recall.js`. New hermetic `test-recall-query.js` in the pretest chain (unjudged fail-open, judged keep-only, judged-empty, garbage fail-open, context-seed merge, blank-question reject).
17
+
3
18
  ## v3.1.1 — /logout and switch-account: re-auth is finally reachable from chat
4
19
 
5
20
  Reported live: with Claude or Codex already connected, there was **no way to sign into a different account** — `/login` showed "connected" and tapping a model just switched models, because the auth-aware picker (correctly, since v3.0.36) only starts a login flow when a provider is *not* authenticated. And `/clear_oauth_token` alone didn't help: it clears the bot-held token but the CLI credential store keeps `authStatus` reading "authenticated", so the picker still never offered a sign-in. Re-auth needs a *full* sign-out first — this release adds it.
package/bin/recall.js CHANGED
@@ -1,16 +1,26 @@
1
1
  // open-claudia recall-stats [--json]
2
2
  // open-claudia recall graph [--sync] [--json]
3
+ // open-claudia recall query "<question>" [--context "<text>"] [--json]
3
4
  //
4
5
  // Inspect the recall system: discoverer metrics (precision/miss-rescue/noise,
5
- // latency, cost) and the typed-edge graph (node/edge counts, sync).
6
+ // latency, cost), the typed-edge graph (node/edge counts, sync), and the
7
+ // on-demand walker query (ask long-term memory a question mid-task).
6
8
 
7
9
  const path = require("path");
8
10
 
9
11
  const HELP = `
10
- Inspect the memory recall system.
12
+ Inspect and query the memory recall system.
11
13
 
12
14
  open-claudia recall-stats [--json] Discoverer metrics summary
13
15
  open-claudia recall graph [--sync] Recall-graph stats (optionally re-sync structural edges first)
16
+ open-claudia recall query "<question>" On-demand memory lookup: FTS seeds + graph expansion +
17
+ walker judgement → kept sections with why-bullets
18
+ --context "<text>" Working context to seed alongside the question (what you're doing now)
19
+ --json Machine-readable result
20
+ --no-walker Skip the LLM judge; print the raw seed+graph candidate list
21
+ --no-episodes Skip past-conversation transcript hits
22
+ --limit <n> Pack seed width (default 6)
23
+ --timeout <ms> Walker timeout (default 25000)
14
24
 
15
25
  The discoverer engine logs one line per turn to recall-metrics.jsonl and a
16
26
  rolling summary to recall-metrics-summary.json. Switch engines per chat with
@@ -59,10 +69,66 @@ function graph(args) {
59
69
  for (const [type, n] of Object.entries(s.byType || {})) console.log(` ${type}: ${n}`);
60
70
  }
61
71
 
72
+ async function queryCmd(args) {
73
+ const flags = { context: "", json: false, noWalker: false, noEpisodes: false, limit: 0, timeout: 0 };
74
+ const positional = [];
75
+ for (let i = 0; i < args.length; i++) {
76
+ const a = args[i];
77
+ if (a === "--json") flags.json = true;
78
+ else if (a === "--no-walker") flags.noWalker = true;
79
+ else if (a === "--no-episodes") flags.noEpisodes = true;
80
+ else if (a === "--context") flags.context = args[++i] || "";
81
+ else if (a === "--limit") flags.limit = Number(args[++i]);
82
+ else if (a === "--timeout") flags.timeout = Number(args[++i]);
83
+ else positional.push(a);
84
+ }
85
+ const question = positional.join(" ").trim();
86
+ if (!question) {
87
+ console.error('Usage: open-claudia recall query "<question>" [--context "<text>"] [--json] [--no-walker] [--no-episodes] [--limit <n>] [--timeout <ms>]');
88
+ process.exit(1);
89
+ }
90
+ // The walker reads its timeout from env at module load — set before require.
91
+ if (flags.timeout > 0) process.env.RECALL_DISCOVERER_TIMEOUT_MS = String(flags.timeout);
92
+ const lib = require(path.join(__dirname, "..", "core", "recall", "query"));
93
+ let res;
94
+ try {
95
+ res = await lib.query(question, {
96
+ context: flags.context,
97
+ walker: !flags.noWalker,
98
+ episodes: !flags.noEpisodes,
99
+ limit: flags.limit || undefined,
100
+ });
101
+ } catch (e) {
102
+ console.error(`recall query failed: ${e.message}`);
103
+ process.exit(1);
104
+ }
105
+ if (flags.json) { console.log(JSON.stringify(res, null, 2)); return; }
106
+ const w = res.walker || {};
107
+ console.log(`Recall query — ${res.results.length} kept of ${res.candidates} candidate(s) (seeds: ${res.seeds.packs} pack, ${res.seeds.entities} entity, ${res.seeds.tools} tool; +${res.activated} graph-activated)`);
108
+ console.log(res.judged
109
+ ? `Walker: ${w.provider || "?"}${w.model ? `/${w.model}` : ""} (${w.source}, ${w.ms}ms)`
110
+ : `Walker: ${w.source === "off" ? "off" : "failed"} — UNJUDGED candidate list (fail-open)`);
111
+ for (const r of res.results) {
112
+ console.log("");
113
+ const score = r.score == null ? "" : ` (score ${r.score})`;
114
+ const viaTag = r.activated ? ` [via ${r.via || "graph"}]` : "";
115
+ console.log(`■ ${r.kind}: ${r.name}${score}${viaTag}`);
116
+ if (r.why) console.log(` why: ${r.why}`);
117
+ if (r.kind === "pack" && r.lines && r.lines.length) {
118
+ for (const l of r.lines) console.log(` > [${l.section}:${l.lineno}] ${l.text}`);
119
+ } else if (r.excerpt) {
120
+ console.log(` > ${r.excerpt.replace(/\n/g, " ").slice(0, 300)}`);
121
+ }
122
+ if (r.pointer) console.log(` ↳ ${r.pointer}`);
123
+ }
124
+ if (!res.results.length) console.log('\n(nothing relevant — rephrase, add --context, or use --no-walker to see raw candidates)');
125
+ }
126
+
62
127
  function run(args) {
63
128
  const sub = args[0];
64
129
  if (sub === "--help" || sub === "help") { console.log(HELP); process.exit(0); }
65
130
  if (sub === "graph") { graph(args.slice(1)); return; }
131
+ if (sub === "query") { queryCmd(args.slice(1)); return; }
66
132
  // default / "stats": metrics summary
67
133
  stats(args.filter((a) => a !== "stats"));
68
134
  }
package/core/config.js CHANGED
@@ -141,6 +141,10 @@ const FFMPEG = config.FFMPEG || "";
141
141
  const ELEVENLABS_API_KEY = config.ELEVENLABS_API_KEY || process.env.ELEVENLABS_API_KEY || "";
142
142
  const ELEVENLABS_VOICE_ID = config.ELEVENLABS_VOICE_ID || process.env.ELEVENLABS_VOICE_ID || "EXAVITQu4vr4xnSDxMaL";
143
143
  const ELEVENLABS_MODEL = config.ELEVENLABS_MODEL || process.env.ELEVENLABS_MODEL || "eleven_v3";
144
+ const SPEECH_SERVICE_URL = config.SPEECH_SERVICE_URL || process.env.SPEECH_SERVICE_URL || "https://speech.agents.kazee.africa";
145
+ const SPEECH_API_KEY = config.SPEECH_API_KEY || process.env.SPEECH_API_KEY || "";
146
+ const POCKET_TTS_VOICE = config.POCKET_TTS_VOICE || process.env.POCKET_TTS_VOICE || "alba";
147
+ const TTS_PROVIDER = config.TTS_PROVIDER || process.env.TTS_PROVIDER || "pocket";
144
148
  const SOUL_FILE = config.SOUL_FILE || path.join(CONFIG_DIR, "soul.md");
145
149
  const CRONS_FILE = config.CRONS_FILE || path.join(CONFIG_DIR, "crons.json");
146
150
  const JOBS_FILE = config.JOBS_FILE || path.join(CONFIG_DIR, "jobs.json");
@@ -370,6 +374,7 @@ module.exports = {
370
374
  TRANSCRIPTS_DIR,
371
375
  WHISPER_CLI, WHISPER_MODEL, FFMPEG,
372
376
  ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID, ELEVENLABS_MODEL,
377
+ SPEECH_SERVICE_URL, SPEECH_API_KEY, POCKET_TTS_VOICE, TTS_PROVIDER,
373
378
  SOUL_FILE, CRONS_FILE, JOBS_FILE, TASKS_DIR, VAULT_FILE, AUTH_FILE, IDENTITIES_FILE,
374
379
  PEOPLE_FILE, INTROS_FILE, AUDIT_FILE,
375
380
  STATE_FILE, SESSIONS_FILE,
package/core/media.js CHANGED
@@ -4,7 +4,7 @@
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
6
  const { execSync } = require("child_process");
7
- const { WHISPER_CLI, WHISPER_MODEL, FFMPEG, TEMP_DIR, ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID, ELEVENLABS_MODEL } = require("./config");
7
+ const { WHISPER_CLI, WHISPER_MODEL, FFMPEG, TEMP_DIR, ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID, ELEVENLABS_MODEL, SPEECH_SERVICE_URL, SPEECH_API_KEY, POCKET_TTS_VOICE, TTS_PROVIDER } = require("./config");
8
8
 
9
9
  const TTS_CMD = process.platform === "darwin" ? "say" : null;
10
10
 
@@ -70,10 +70,42 @@ async function elevenLabsToVoice(clean) {
70
70
  }
71
71
  }
72
72
 
73
+ // Pocket TTS through the Agent Space speech gateway. The gateway returns WAV;
74
+ // Telegram and voice transports receive a compact Ogg/Opus file.
75
+ async function pocketToVoice(clean) {
76
+ if (TTS_PROVIDER === "elevenlabs" || !SPEECH_API_KEY || !SPEECH_SERVICE_URL || !FFMPEG) return null;
77
+ let wavPath = null;
78
+ try {
79
+ const res = await fetch(`${SPEECH_SERVICE_URL.replace(/\/$/, "")}/v1/audio/speech`, {
80
+ method: "POST",
81
+ headers: { Authorization: `Bearer ${SPEECH_API_KEY}`, "Content-Type": "application/json" },
82
+ body: JSON.stringify({ input: clean, voice: POCKET_TTS_VOICE, response_format: "wav" }),
83
+ signal: AbortSignal.timeout(60000),
84
+ });
85
+ if (!res.ok) {
86
+ console.error(`Pocket TTS failed: HTTP ${res.status}`);
87
+ return null;
88
+ }
89
+ const stem = `tts-pocket-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
90
+ wavPath = path.join(TEMP_DIR, `${stem}.wav`);
91
+ const oggPath = path.join(TEMP_DIR, `${stem}.ogg`);
92
+ fs.writeFileSync(wavPath, Buffer.from(await res.arrayBuffer()));
93
+ execSync(`"${FFMPEG}" -i "${wavPath}" -c:a libopus -y "${oggPath}" 2>/dev/null`, { timeout: 30000 });
94
+ return oggPath;
95
+ } catch (e) {
96
+ console.error("Pocket TTS error:", e.message);
97
+ return null;
98
+ } finally {
99
+ if (wavPath) { try { fs.unlinkSync(wavPath); } catch (e) {} }
100
+ }
101
+ }
102
+
73
103
  // Natural voice via ElevenLabs, falling back to macOS `say` only on no-key/error.
74
104
  async function textToVoice(text) {
75
105
  const clean = cleanForTTS(text);
76
106
  if (!clean) return null;
107
+ const pocket = await pocketToVoice(clean);
108
+ if (pocket) return pocket;
77
109
  const eleven = await elevenLabsToVoice(clean);
78
110
  if (eleven) return eleven;
79
111
  return sayToVoice(clean);
@@ -105,6 +137,8 @@ function splitSentences(text, minLen = 30) {
105
137
  async function synthSentenceMp3(text) {
106
138
  const clean = cleanForTTS(text);
107
139
  if (!clean) return null;
140
+ const pocket = await pocketToVoice(clean);
141
+ if (pocket) return pocket;
108
142
  if (!ELEVENLABS_API_KEY) return sayToVoice(clean); // ogg fallback
109
143
  try {
110
144
  const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${ELEVENLABS_VOICE_ID}`, {
@@ -131,4 +165,4 @@ async function synthSentenceMp3(text) {
131
165
  }
132
166
  }
133
167
 
134
- module.exports = { transcribeAudio, textToVoice, splitSentences, synthSentenceMp3, TTS_CMD };
168
+ module.exports = { transcribeAudio, textToVoice, splitSentences, synthSentenceMp3, pocketToVoice, TTS_CMD };
@@ -53,6 +53,7 @@ const CONTROL_PLANE_KEYS = Object.freeze([
53
53
  "OC_SEND_TOKEN",
54
54
  "VOICE_BRIDGE_TOKEN",
55
55
  "ELEVENLABS_API_KEY",
56
+ "SPEECH_API_KEY",
56
57
  "AGENTSPACE_POD_TOKEN",
57
58
  "WEB_PASSWORD",
58
59
  "OPEN_CLAUDIA_KEYRING_CANARY",
@@ -282,6 +282,13 @@ async function runWalker(prompt, options = {}) {
282
282
  };
283
283
  } catch (e) { /* fall back to cold spawn below */ }
284
284
  }
285
+ // No outputSchema: a provider structured-output schema becomes a custom-tool
286
+ // input_schema, which the Anthropic API requires to be an OBJECT — the
287
+ // walker's top-level array schema is rejected 400 ("input_schema.type: Input
288
+ // should be 'object'"), so the cold path used to fail-open on every fallback.
289
+ // The warm path never sends a schema and the walk() parser already extracts
290
+ // the JSON array from freeform text, filters to known ids, and clamps scores,
291
+ // so freeform is the proven contract on both paths.
285
292
  const result = await runUtility(prompt, {
286
293
  purpose: "recall",
287
294
  provider: selection.providerId,
@@ -291,14 +298,6 @@ async function runWalker(prompt, options = {}) {
291
298
  legacyClaudeModelEnv: "RECALL_DISCOVERER_MODEL",
292
299
  systemPrompt: WALKER_SYSTEM,
293
300
  readOnly: true,
294
- outputSchema: {
295
- type: "array",
296
- items: {
297
- type: "object",
298
- required: ["id"],
299
- properties: { id: { type: "string" }, why: { type: "string" }, score: { type: "number" } },
300
- },
301
- },
302
301
  timeoutMs: WALKER_TIMEOUT_MS,
303
302
  cwd: options.cwd || process.cwd(),
304
303
  });
@@ -779,4 +778,4 @@ async function run(ctx) {
779
778
  };
780
779
  }
781
780
 
782
- module.exports = { name: "discoverer", run, runWalker, needsRecall, recallTier, walk, cascadeVerdict, cokeptPairs, scoutToolGaps, renderToolGaps, episodesAllowedForSpeaker };
781
+ module.exports = { name: "discoverer", run, runWalker, needsRecall, recallTier, walk, excerptFor, cascadeVerdict, cokeptPairs, scoutToolGaps, renderToolGaps, episodesAllowedForSpeaker };
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+
3
+ // On-demand recall: the working agent's side door into the same
4
+ // seed → graph-expansion → walker pipeline the discoverer runs per turn.
5
+ // `open-claudia recall query "<question>" [--context "<working context>"]`
6
+ //
7
+ // The per-turn engine seeds from the USER's message; mid-build the agent's
8
+ // actual question often shares zero words with the governing pack (measured
9
+ // 2026-07-16: the embeddings-measurement stance never resurfaced during the
10
+ // build it governed). Here the agent seeds with its own question + working
11
+ // context, and graph edges + walker judgement bridge the vocabulary gap.
12
+ //
13
+ // Deliberate differences from the per-turn engine:
14
+ // - no pre-gate: an explicit query is always intentional (always "full").
15
+ // - read-only: never reinforces edges, never warms co-kept pairs — a
16
+ // query is a lookup, not a turn.
17
+ // - cold walker only: a one-shot CLI process must not own a warm child.
18
+ // - fail-open returns ALL candidates marked unjudged — the caller is an
19
+ // agent that can judge for itself; an empty answer helps nobody.
20
+
21
+ const graph = require("./graph");
22
+ const metrics = require("./metrics");
23
+ const tuning = require("./tuning");
24
+ const discoverer = require("./discoverer");
25
+
26
+ let transcriptIndex = null;
27
+ try { transcriptIndex = require("../transcript-index"); } catch (e) {}
28
+
29
+ const COLD_ONLY_WARM = { isEnabled: () => false };
30
+ const EXCERPT_CHARS = 600;
31
+
32
+ function mergeMatches(primary, secondary, keyOf) {
33
+ const out = primary.map((m) => ({ ...m, origin: "user" }));
34
+ const seen = new Set(primary.map(keyOf));
35
+ for (const m of secondary) {
36
+ if (seen.has(keyOf(m))) continue;
37
+ out.push({ ...m, origin: "context" });
38
+ }
39
+ return out;
40
+ }
41
+
42
+ function clip(s, n) { return s && s.length > n ? s.slice(0, n) + "…" : (s || ""); }
43
+
44
+ async function query(question, opts = {}) {
45
+ const packsLib = opts.packsLib || require("../packs");
46
+ const entitiesLib = opts.entitiesLib || require("../entities");
47
+ const toolsLib = opts.toolsLib || require("../tools");
48
+ const started = Date.now();
49
+ const q = String(question || "").trim();
50
+ if (!q) throw new Error("recall query needs a question");
51
+ const context = String(opts.context || "").trim();
52
+ const packLimit = Number(opts.limit) > 0 ? Math.min(Number(opts.limit), 12) : 6;
53
+
54
+ // Seed via FTS on the question, then the working context (deduped).
55
+ let packSeeds = [];
56
+ let entSeeds = [];
57
+ try {
58
+ packSeeds = mergeMatches(
59
+ packsLib.matchPacks(q, { limit: packLimit }),
60
+ context ? packsLib.matchPacks(context, { limit: packLimit }) : [],
61
+ (m) => m.dir,
62
+ );
63
+ } catch (e) {}
64
+ try {
65
+ entSeeds = mergeMatches(
66
+ entitiesLib.matchEntities(q, { limit: 4 }),
67
+ context ? entitiesLib.matchEntities(context, { limit: 4 }) : [],
68
+ (m) => m.slug,
69
+ );
70
+ } catch (e) {}
71
+ const toolSeeds = new Map();
72
+ try {
73
+ for (const t of toolsLib.matchTools(q, { limit: 5 })) toolSeeds.set(t.name, { ...t, origin: "user" });
74
+ if (context) {
75
+ for (const t of toolsLib.matchTools(context, { limit: 5 })) {
76
+ if (!toolSeeds.has(t.name)) toolSeeds.set(t.name, { ...t, origin: "context" });
77
+ }
78
+ }
79
+ } catch (e) {}
80
+
81
+ // Spreading activation from seeds across the typed-edge graph.
82
+ const seedNodes = [
83
+ ...packSeeds.map((m) => ({ id: `pack:${m.dir}`, score: m.score || 2 })),
84
+ ...entSeeds.map((m) => ({ id: `entity:${m.slug}`, score: m.score || 2 })),
85
+ ];
86
+ const seedIds = new Set(seedNodes.map((s) => s.id));
87
+ let activated = new Map();
88
+ if (graph.available()) {
89
+ try { graph.syncFromCorpus(packsLib, entitiesLib); } catch (e) {}
90
+ try { activated = graph.expand(seedNodes, {}); } catch (e) {}
91
+ }
92
+
93
+ // Candidates: question seeds by score, context seeds, then graph-activated.
94
+ const seedRank = new Map();
95
+ for (const m of packSeeds) seedRank.set(`pack:${m.dir}`, { score: m.score || 2, origin: m.origin });
96
+ for (const m of entSeeds) seedRank.set(`entity:${m.slug}`, { score: m.score || 2, origin: m.origin });
97
+ const orderedIds = [...seedIds].sort((a, b) => {
98
+ const ra = seedRank.get(a) || {}, rb = seedRank.get(b) || {};
99
+ const ua = ra.origin === "user" ? 0 : 1, ub = rb.origin === "user" ? 0 : 1;
100
+ return ua - ub || (rb.score || 0) - (ra.score || 0);
101
+ });
102
+ for (const [id] of [...activated.entries()].sort((x, y) => (y[1].activation || 0) - (x[1].activation || 0))) {
103
+ if (!seedIds.has(id)) orderedIds.push(id);
104
+ }
105
+ const candidates = [];
106
+ for (const id of orderedIds.slice(0, tuning.get("walkerMaxCandidates"))) {
107
+ const base = discoverer.excerptFor(id, packsLib, entitiesLib);
108
+ if (!base) continue;
109
+ const act = activated.get(id);
110
+ candidates.push({ ...base, activated: !seedIds.has(id), via: act ? act.via : null });
111
+ }
112
+
113
+ // Tools: direct seeds plus tools declared by any candidate pack.
114
+ const candPackDirs = new Set();
115
+ for (const c of candidates) if (c.id.startsWith("pack:")) candPackDirs.add(c.id.slice(5));
116
+ const toolCand = new Map();
117
+ for (const [name] of toolSeeds) toolCand.set(name, { name, via: null });
118
+ if (candPackDirs.size) {
119
+ try {
120
+ for (const t of toolsLib.listTools()) {
121
+ if (t.pack && candPackDirs.has(t.pack) && !toolCand.has(t.name)) {
122
+ toolCand.set(t.name, { name: t.name, via: `pack:${t.pack}` });
123
+ }
124
+ }
125
+ } catch (e) {}
126
+ }
127
+ for (const c of toolCand.values()) {
128
+ const base = discoverer.excerptFor(`tool:${c.name}`, packsLib, entitiesLib);
129
+ if (!base) continue;
130
+ candidates.push({ ...base, activated: !!c.via, via: c.via });
131
+ }
132
+
133
+ // Episodes: FTS over past transcripts. The CLI has no speaker context, so
134
+ // the R6 owner gate is open by design (the CLI runs as the owner on their
135
+ // own box); bot-side callers still pass through the per-turn engine's gate.
136
+ if (opts.episodes !== false && transcriptIndex && transcriptIndex.available()) {
137
+ try {
138
+ const { hits } = transcriptIndex.search(q, { limit: tuning.get("episodeLimit") });
139
+ for (const h of (hits || [])) {
140
+ const terms = [...String(h.snip || "").matchAll(/>>(.+?)<</g)].map((m) => m[1]).slice(0, 4);
141
+ candidates.push({
142
+ id: `episode:${require("path").basename(String(h.file || ""), ".jsonl")}:${h.line}`,
143
+ name: `Past conversation${h.project ? ` (${h.project})` : ""}${h.ts ? ` ${String(h.ts).slice(0, 10)}` : ""}`,
144
+ description: "",
145
+ excerpt: clip(String(h.snip || "").replace(/>>|<</g, ""), EXCERPT_CHARS),
146
+ activated: true,
147
+ via: "transcript",
148
+ _terms: terms,
149
+ });
150
+ }
151
+ } catch (e) { /* episodic recall is best-effort */ }
152
+ }
153
+
154
+ // Walker judgement (cold spawn only). opts.walker === false skips the spawn.
155
+ let walkRes = { kept: null, scores: null, source: "off", walkerMs: 0, provider: null, model: null, costUsd: null };
156
+ if (opts.walker !== false && candidates.length) {
157
+ walkRes = await discoverer.walk(q, context, candidates, {
158
+ warmWalker: COLD_ONLY_WARM,
159
+ cwd: opts.cwd,
160
+ ...(opts.walkerOptions || {}),
161
+ });
162
+ }
163
+ const whyMap = walkRes.kept;
164
+ const scores = walkRes.scores;
165
+
166
+ const results = [];
167
+ for (const c of candidates) {
168
+ if (whyMap && !whyMap.has(c.id)) continue;
169
+ const kind = c.id.startsWith("pack:") ? "pack"
170
+ : c.id.startsWith("entity:") ? "entity"
171
+ : c.id.startsWith("tool:") ? "tool" : "episode";
172
+ const r = {
173
+ id: c.id,
174
+ kind,
175
+ name: c.name,
176
+ why: whyMap ? whyMap.get(c.id) || "" : "",
177
+ score: scores && scores.has(c.id) ? scores.get(c.id) : null,
178
+ activated: !!c.activated,
179
+ via: c.via || null,
180
+ excerpt: c.excerpt || "",
181
+ };
182
+ if (kind === "pack") {
183
+ const dir = c.id.slice(5);
184
+ r.pointer = `open-claudia pack show ${dir}`;
185
+ try {
186
+ const hits = packsLib.searchLines([q, context].filter(Boolean).join(" "), { dir, k: 8 });
187
+ if (hits && hits.length) r.lines = hits.map((h) => ({ section: h.section, lineno: h.lineno, text: h.text }));
188
+ } catch (e) { /* a pack with no line hits still shows its excerpt */ }
189
+ } else if (kind === "entity") {
190
+ r.pointer = `open-claudia entity show ${c.id.slice(7)}`;
191
+ } else if (kind === "tool") {
192
+ r.pointer = `open-claudia tool show ${c.id.slice(5)}`;
193
+ } else {
194
+ const qterms = (c._terms && c._terms.length
195
+ ? c._terms.join(" ")
196
+ : String(c.excerpt).split(/\s+/).slice(0, 4).join(" ")).replace(/"/g, "");
197
+ r.pointer = `open-claudia transcript-search "${qterms}" --all`;
198
+ }
199
+ results.push(r);
200
+ }
201
+ results.sort((a, b) => (b.score ?? -1) - (a.score ?? -1));
202
+
203
+ const out = {
204
+ question: q,
205
+ hasContext: !!context,
206
+ seeds: { packs: packSeeds.length, entities: entSeeds.length, tools: toolSeeds.size },
207
+ activated: activated.size,
208
+ candidates: candidates.length,
209
+ judged: !!whyMap,
210
+ walker: {
211
+ source: walkRes.source,
212
+ ms: walkRes.walkerMs || 0,
213
+ provider: walkRes.provider || null,
214
+ model: walkRes.model || null,
215
+ },
216
+ results,
217
+ latencyMs: Date.now() - started,
218
+ };
219
+
220
+ try {
221
+ metrics.logTurn({
222
+ engine: "query",
223
+ provider: walkRes.provider || "",
224
+ model: walkRes.model || "",
225
+ query: q,
226
+ tier: "query",
227
+ seeds: seedNodes,
228
+ activated: [...activated.entries()].map(([id, a]) => ({ id, activation: a.activation, hop: a.hop })),
229
+ cand: candidates.map((c) => ({ id: c.id, act: !!c.activated })),
230
+ kept: results.map((r) => ({ id: r.id, why: r.why, score: r.score })),
231
+ gated: false,
232
+ latencyMs: out.latencyMs,
233
+ walkerMs: walkRes.walkerMs,
234
+ walker: walkRes.source,
235
+ costUsd: walkRes.costUsd || 0,
236
+ });
237
+ } catch (e) {}
238
+
239
+ return out;
240
+ }
241
+
242
+ module.exports = { query, mergeMatches };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "setup": "node setup.js",
11
11
  "start": "node bot.js",
12
12
  "pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-turn-observer.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-refusal-recovery.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-spaces-comment-content.js && node test-boot-health.js && node test-lexical-floor.js && node test-safe-mode.js && node test-logout.js",
13
- "test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js"
13
+ "test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js"
14
14
  },
15
15
  "files": [
16
16
  "bot.js",
@@ -157,7 +157,10 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
157
157
  // never re-offers a sign-in); a 🔁 Switch account button on connected
158
158
  // providers in /login behind an explicit confirm that names the signed-in
159
159
  // account where the CLI exposes it, then chains logout → fresh login.
160
- assert.strictEqual(pkg.version, "3.1.1", "release version must remain unchanged without explicit approval");
160
+ // 3.1.3 approved by Sumeet 2026-07-21 ("ok fine go ahead") for Pocket TTS
161
+ // as the preferred voice-note provider, with ElevenLabs fallback and local
162
+ // Whisper transcription unchanged.
163
+ assert.strictEqual(pkg.version, "3.1.3", "release version must remain unchanged without explicit approval");
161
164
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
162
165
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
163
166
  }