@inetafrica/open-claudia 3.1.2 → 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,12 @@
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
+
3
10
  ## v3.1.2 — On-demand recall query + cold-walker fallback fix
4
11
 
5
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.
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.1.2",
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": {
@@ -157,17 +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
- // 3.1.2 approved by Sumeet 2026-07-20 ("If everything you have works and is
161
- // ready fold it in and push all out in a new version"): on-demand recall query
162
- // CLI (`open-claudia recall query "<question>"`) — seeds the graph with the
163
- // agent's OWN working context (not the user's words), walks it with the
164
- // active-provider judge, returns pack sections + why-bullets; closes the
165
- // cold-start hole where mid-build vocabulary shared no words with the governing
166
- // pack. Also fixes a latent cold-walker bug: the warm→cold fallback sent an
167
- // array-typed outputSchema the Anthropic API rejects 400, so every fallback
168
- // silently failed-open — removed; freeform JSON parse is the proven contract on
169
- // both paths.
170
- assert.strictEqual(pkg.version, "3.1.2", "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");
171
164
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
172
165
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
173
166
  }