@inetafrica/open-claudia 3.5.10 → 3.5.13

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/.env.example CHANGED
@@ -63,6 +63,8 @@ OC_THREAD_TITLES=on
63
63
  THREAD_TITLE_PROVIDER=
64
64
  THREAD_TITLE_MODEL_CLAUDE=
65
65
  THREAD_TITLE_MODEL_CODEX=
66
+ # Token usage alerts to the owner: on | off (default off). Never posted in group chats.
67
+ USAGE_ALERTS=off
66
68
  USAGE_ALERT_CONTEXT_TOKENS=120000
67
69
  USAGE_ALERT_RATE_MULTIPLIER=1.75
68
70
  USAGE_ALERT_BASELINE_TURNS=20
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.5.13
4
+
5
+ Group chats stop seeing notes meant only for the owner, and token usage alerts are off by default. Canary first, on the sales bot.
6
+
7
+ - **Fix: no more junk "📌 standing rule" posts.** After every owner turn the rule detector scanned the whole prompt, including the group or thread messages the bot was shown, so it saved the bot's own lines as the owner's rules. Its own "📌 Got it — I'll keep this loaded as a standing rule from now on" note then set off the next one, one more after every owner message in a staff group (sales bot, 4 Oct; same cause as the 1 Oct thread report). Rules now come only from the owner's own words (the typed message, voice transcript or caption), and only in a direct chat with the bot: group chats never capture rules or see 📌 notes.
8
+ - **Token usage alerts are off by default** (Sumeet, 4 Oct: "turn off this token usage thing by default on all the bots"). `USAGE_ALERTS=on` brings them back, with the same `USAGE_ALERT_*` thresholds, and even then they never go to a group chat.
9
+ - **Tests:** `test-group-status-notes.js` (new).
10
+
11
+ ## 3.5.12
12
+
13
+ Two fixes: "No response requested." no longer reaches the chat, and voice notes wait out a busy speech server. Canary first, on the sales bot. (3.5.11 was tagged but never published: its release run failed on the new test, which assumed an agent CLI is installed; CI has none.)
14
+
15
+ - **Fix: "No response requested." never reaches the chat.** When a restart cut a turn short, Claude Code resumed that conversation by writing two lines into the bot's history that nobody sent: "Continue from where you left off." and a synthetic assistant reply, "No response requested." (model `<synthetic>`, never streamed). The bot then apologised for that reply or copied it as a real answer (reported by Sumeet, 3 Oct). The runner now drops a reply that is only that line, and the system prompt explains the two lines, so the bot finishes the interrupted work instead of mentioning them.
16
+ - **Fix: voice notes wait out the speech queue and say why they fail.** Hosted transcription waits up to 4 minutes in total and retries 429/502/503/504 and network drops after 2 s and 6 s; a 500, a 4xx or a timeout is not retried. "Couldn't transcribe" now names the reason, e.g. "(the speech server answered HTTP 500)", and `open-claudia transcribe` reports the reason too. The local Whisper fallback converts into its own temp file; it used to overwrite the .wav it was reading.
17
+ - **Tests:** `test-placeholder-reply.js` (new), and `test-media-speech.js` now runs in `npm test` (it existed since 3.1.15 but was never in the chain).
18
+
3
19
  ## 3.5.10
4
20
 
5
21
  Passive replies in Kazee groups, and no endless bot-to-bot loops. Needs chat-central with the "Bot replies" group setting (an older server never sends the new mode, so groups keep today's behaviour). Canary first, on the sales bot.
package/README.md CHANGED
@@ -448,8 +448,9 @@ All stored in `~/.open-claudia/`:
448
448
  | `AGENT_ENV_PASSTHROUGH` | No | Comma-separated project environment keys to pass to provider children; reserved provider/control/keyring keys are always filtered and re-added only by the selected provider |
449
449
  | `CLAUDE_MODEL` / `CODEX_MODEL` | No | Optional default model for the matching provider |
450
450
  | `AUTO_COMPACT_TOKENS` | No | Auto-compact threshold in tokens (also settable via `/compactwindow`) |
451
- | `USAGE_ALERT_CONTEXT_TOKENS` | No | Alert when one completed turn's context tokens exceed this ceiling (default `120000`, `off` disables) |
452
- | `USAGE_ALERT_RATE_MULTIPLIER` | No | Alert when the latest context-token rate exceeds the recent baseline by this multiple (default `1.75`, `off` disables) |
451
+ | `USAGE_ALERTS` | No | Token usage alerts to the owner: `on` or `off` (default `off`). Never posted in group chats. The thresholds below apply only when on |
452
+ | `USAGE_ALERT_CONTEXT_TOKENS` | No | With alerts on: alert when one completed turn's context tokens exceed this ceiling (default `120000`, `off` disables) |
453
+ | `USAGE_ALERT_RATE_MULTIPLIER` | No | With alerts on: alert when the latest context-token rate exceeds the recent baseline by this multiple (default `1.75`, `off` disables) |
453
454
  | `USAGE_ALERT_BASELINE_TURNS` / `USAGE_ALERT_MIN_BASELINE_TURNS` / `USAGE_ALERT_COOLDOWN_MS` | No | Tune token-rate baseline size, minimum sample size, and alert cooldown |
454
455
  | `MEMORY_RECALL_MAX_CHARS` | No | Hard cap for auto-injected pack/entity memory per turn (default `9000`, `off` disables auto recall injection) |
455
456
  | `RECALL_ENGINE` | No | Default recall engine when a chat hasn't set one via `/engine` (`classic` or `discoverer`, default `discoverer`) |
package/core/media.js CHANGED
@@ -15,37 +15,95 @@ function speechBearerToken() {
15
15
 
16
16
  function transcribeAudioLocal(oggPath) {
17
17
  if (!WHISPER_CLI || !FFMPEG) return null;
18
- const wavPath = oggPath.replace(/\.[^.]+$/, ".wav");
19
- execSync(`"${FFMPEG}" -i "${oggPath}" -ar 16000 -ac 1 -y "${wavPath}" 2>/dev/null`);
20
- const output = execSync(`"${WHISPER_CLI}" -m "${WHISPER_MODEL}" --no-timestamps -f "${wavPath}" 2>/dev/null`, { encoding: "utf-8" });
21
- try { fs.unlinkSync(wavPath); } catch (e) {}
22
- return output.split("\n")
23
- .filter((l) => l.trim() && !l.startsWith("whisper_") && !l.startsWith("ggml_") && !l.startsWith("load_"))
24
- .join(" ").trim();
18
+ // Its own temp file, never the input's path with .wav: a .wav input (every
19
+ // transcribeMedia call) made ffmpeg overwrite the file it was reading.
20
+ const wavPath = path.join(TEMP_DIR, `stt-local-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.wav`);
21
+ try {
22
+ execSync(`"${FFMPEG}" -i "${oggPath}" -ar 16000 -ac 1 -y "${wavPath}" 2>/dev/null`);
23
+ const output = execSync(`"${WHISPER_CLI}" -m "${WHISPER_MODEL}" --no-timestamps -f "${wavPath}" 2>/dev/null`, { encoding: "utf-8" });
24
+ return output.split("\n")
25
+ .filter((l) => l.trim() && !l.startsWith("whisper_") && !l.startsWith("ggml_") && !l.startsWith("load_"))
26
+ .join(" ").trim();
27
+ } finally {
28
+ try { fs.unlinkSync(wavPath); } catch (e) {}
29
+ }
25
30
  }
26
31
 
27
- async function transcribeAudio(oggPath) {
28
- const token = speechBearerToken();
29
- if (SPEECH_SERVICE_URL && token) {
32
+ // Hosted STT: one engine serves every bot and takes notes in turn, so a burst
33
+ // can queue for a while. Wait for it, and retry only failures that mean busy or
34
+ // briefly away (rate limit, gateway restart or timeout, network drop).
35
+ const STT_TIMING = { waitMs: 240000, retryDelaysMs: [2000, 6000] };
36
+ const STT_RETRY_STATUSES = new Set([429, 502, 503, 504]);
37
+
38
+ async function transcribeHosted(oggPath, token) {
39
+ const deadline = Date.now() + STT_TIMING.waitMs;
40
+ let audio;
41
+ try {
42
+ audio = fs.readFileSync(oggPath);
43
+ } catch (e) {
44
+ console.error("Hosted STT error:", e.message);
45
+ return { error: "the audio file could not be read" };
46
+ }
47
+ for (let attempt = 0; ; attempt++) {
48
+ let failure;
49
+ let retry = false;
30
50
  try {
31
51
  const body = new FormData();
32
- body.append("file", new Blob([fs.readFileSync(oggPath)]), path.basename(oggPath));
52
+ body.append("file", new Blob([audio]), path.basename(oggPath));
33
53
  const res = await fetch(`${SPEECH_SERVICE_URL.replace(/\/$/, "")}/v1/audio/transcriptions`, {
34
54
  method: "POST",
35
55
  headers: { Authorization: `Bearer ${token}` },
36
56
  body,
37
- signal: AbortSignal.timeout(120000),
57
+ signal: AbortSignal.timeout(Math.max(1000, deadline - Date.now())),
38
58
  });
39
59
  if (res.ok) {
40
- const payload = await res.json();
41
- return String(payload.text || "").trim();
60
+ const payload = await res.json().catch(() => null);
61
+ if (payload) return { text: String(payload.text || "").trim() };
62
+ failure = "the speech server sent an unreadable answer";
63
+ } else {
64
+ failure = `the speech server answered HTTP ${res.status}`;
65
+ retry = STT_RETRY_STATUSES.has(res.status);
66
+ }
67
+ } catch (e) {
68
+ // A timeout means the server is still working through its queue; asking
69
+ // again would only queue a second copy behind the first.
70
+ if (e?.name === "TimeoutError" || e?.name === "AbortError") {
71
+ failure = `the speech server did not answer within ${Math.round(STT_TIMING.waitMs / 60000)} minutes`;
72
+ } else {
73
+ failure = `could not reach the speech server (${e.message})`;
74
+ retry = true;
42
75
  }
43
- console.error(`Hosted STT failed: HTTP ${res.status}`);
76
+ }
77
+ const delay = STT_TIMING.retryDelaysMs[attempt];
78
+ if (!retry || delay === undefined || Date.now() + delay >= deadline) {
79
+ console.error(`Hosted STT failed: ${failure}`);
80
+ return { error: failure };
81
+ }
82
+ console.error(`Hosted STT: ${failure}; retrying in ${delay / 1000}s`);
83
+ await new Promise((resolve) => setTimeout(resolve, delay));
84
+ }
85
+ }
86
+
87
+ // The transcript, or null when no route produced one; report.error then says
88
+ // why, in words fit for the person who sent the audio.
89
+ async function transcribeAudio(oggPath, report = {}) {
90
+ const reasons = [];
91
+ const token = speechBearerToken();
92
+ if (SPEECH_SERVICE_URL && token) {
93
+ const hosted = await transcribeHosted(oggPath, token);
94
+ if (hosted.text !== undefined) return hosted.text;
95
+ reasons.push(hosted.error);
96
+ }
97
+ if (WHISPER_CLI && FFMPEG) {
98
+ try {
99
+ return transcribeAudioLocal(oggPath);
44
100
  } catch (e) {
45
- console.error("Hosted STT error:", e.message);
101
+ console.error("Local STT error:", e.message);
102
+ reasons.push("the local transcriber failed");
46
103
  }
47
104
  }
48
- return transcribeAudioLocal(oggPath);
105
+ report.error = reasons.join(", and ") || "no speech-to-text is set up";
106
+ return null;
49
107
  }
50
108
 
51
109
  function cleanForTTS(text) {
@@ -278,7 +336,8 @@ function canTranscribe() {
278
336
  // Transcribe ANY audio/video file (mp3, m4a, wav, ogg, mp4, mkv...): normalize
279
337
  // to 16kHz mono wav first when ffmpeg is available — that extracts the audio
280
338
  // track from video containers and shrinks the gateway upload. Returns the
281
- // transcript string, or null when no STT route exists.
339
+ // transcript string, or null when no STT route exists; throws with the reason
340
+ // when a route exists but failed.
282
341
  async function transcribeMedia(filePath) {
283
342
  if (!canTranscribe()) return null;
284
343
  let wavPath = null;
@@ -296,7 +355,10 @@ async function transcribeMedia(filePath) {
296
355
  }
297
356
  }
298
357
  try {
299
- return await transcribeAudio(sendPath);
358
+ const report = {};
359
+ const text = await transcribeAudio(sendPath, report);
360
+ if (text === null) throw new Error(report.error);
361
+ return text;
300
362
  } finally {
301
363
  if (wavPath) { try { fs.unlinkSync(wavPath); } catch (e) {} }
302
364
  }
@@ -356,4 +418,4 @@ async function synthSentenceMp3(text) {
356
418
  }
357
419
  }
358
420
 
359
- module.exports = { transcribeAudio, transcribeAudioLocal, transcribeMedia, canTranscribe, textToVoice, textToVoiceFile, splitSentences, synthSentenceMp3, pocketToVoice, chunkForTTS, cloneVoiceFromClip, speechBearerToken, canSpeak, TTS_CMD };
421
+ module.exports = { transcribeAudio, transcribeAudioLocal, transcribeMedia, STT_TIMING, canTranscribe, textToVoice, textToVoiceFile, splitSentences, synthSentenceMp3, pocketToVoice, chunkForTTS, cloneVoiceFromClip, speechBearerToken, canSpeak, TTS_CMD };
package/core/router.js CHANGED
@@ -244,6 +244,12 @@ function startPreTurnTyping(state) {
244
244
  try { return startTypingHeartbeat(state); } catch (_) { return () => {}; }
245
245
  }
246
246
 
247
+ // "Couldn't transcribe", with the reason when a speech route failed (silence
248
+ // leaves none), so a broken speech server is visible from the chat.
249
+ function transcribeFailure(report, tail = "") {
250
+ return `Couldn't transcribe${report.error ? ` (${report.error})` : ""}.${tail}`;
251
+ }
252
+
247
253
  async function handleVoice(envelope) {
248
254
  if (isDuplicate(envelope)) return;
249
255
  const state = currentState();
@@ -270,10 +276,11 @@ async function handleVoice(envelope) {
270
276
  const tStart = Date.now();
271
277
  const oggPath = await envelope.adapter.downloadMedia(media);
272
278
  const tAfterDownload = Date.now();
273
- const transcript = await transcribeAudio(oggPath);
279
+ const stt = {};
280
+ const transcript = await transcribeAudio(oggPath, stt);
274
281
  const tAfterStt = Date.now();
275
282
  try { fs.unlinkSync(oggPath); } catch (e) {}
276
- if (!transcript) return send("Couldn't transcribe. Try typing it.");
283
+ if (!transcript) return send(transcribeFailure(stt, " Try typing it."));
277
284
  await send(`Heard: "${transcript}"`, { replyTo: envelope.messageId });
278
285
  if (!(await admitGuardedTurn(transcript, envelope, state))) return;
279
286
  // Voice flags live on the conversation's lane: a voice note in one thread
@@ -312,9 +319,10 @@ async function handleAudio(envelope) {
312
319
  });
313
320
  stopPreTurnTyping = startPreTurnTyping(state);
314
321
  const p = await envelope.adapter.downloadMedia(media);
315
- const t = await transcribeAudio(p);
322
+ const stt = {};
323
+ const t = await transcribeAudio(p, stt);
316
324
  try { fs.unlinkSync(p); } catch (e) {}
317
- if (!t) return send("Couldn't transcribe.");
325
+ if (!t) return send(transcribeFailure(stt));
318
326
  await send(`Heard: "${t}"`, { replyTo: envelope.messageId });
319
327
  if (!(await admitGuardedTurn(t, envelope, state))) return;
320
328
  return runWithContext(t, runContext, envelope, {
package/core/runner.js CHANGED
@@ -234,10 +234,13 @@ function logTurnUsage(state, usage, costUsd, announce, opts = {}) {
234
234
  if (result.alert) {
235
235
  state.usageAlertLastAt = Date.now();
236
236
  // Owner-only: version/model/context internals must never surface to an
237
- // external speaker, regardless of the usage-alert policy.
237
+ // external speaker, regardless of the usage-alert policy. Never in a group
238
+ // chat either: everyone else in the room sees only noise.
238
239
  let usageOwnerOnly = true;
239
240
  try { usageOwnerOnly = !require("./relationship").isCurrentSpeakerGuarded(); } catch (e) {}
240
- if (announce && usageOwnerOnly) {
241
+ let inGroup = false;
242
+ try { inGroup = require("./context").currentIsGroup(); } catch (e) {}
243
+ if (announce && usageOwnerOnly && !inGroup) {
241
244
  const baseline = result.trend?.baselineAvgContextTokens || 0;
242
245
  const rate = result.trend?.latestRate || 0;
243
246
  const contextLabel = usageScope === "session_total" && !hasPeak ? "Session context total" : "Context this turn";
@@ -1704,6 +1707,13 @@ async function finalizeStreamPreview(lane, result, runContext = null) {
1704
1707
  } catch (_) { /* preview cleanup is best-effort; the final answer still goes out */ }
1705
1708
  }
1706
1709
 
1710
+ // When a restart cuts a turn short, Claude Code resumes that session by writing
1711
+ // two lines into it: a user "Continue from where you left off." and a synthetic
1712
+ // assistant "No response requested." (model "<synthetic>", never streamed to us).
1713
+ // A model that later sees them in its history can answer a wake-up with the same
1714
+ // words. That is never an answer, so it is never posted.
1715
+ const PLACEHOLDER_REPLY = /^no response requested\.?$/i;
1716
+
1707
1717
  async function deliverForegroundResult(result, runContext, state = currentState()) {
1708
1718
  const lane = laneForRun(state, runContext);
1709
1719
  if (runContext.lastInputWasVoice) lane.lastInputWasVoice = false;
@@ -1722,6 +1732,11 @@ async function deliverForegroundResult(result, runContext, state = currentState(
1722
1732
  const cleanAnswer = String(result.finalAnswerText || "").trim();
1723
1733
  let finalText = result.ok ? (cleanAnswer || result.text || "(no output)") : (diagnostic || result.diagnostic || "Provider run failed");
1724
1734
 
1735
+ if (result.ok && PLACEHOLDER_REPLY.test(finalText.trim())) {
1736
+ console.log(`[runner] not posting the placeholder reply "No response requested." (${runContext.purpose || "foreground"} run ${runContext.runId || "?"})`);
1737
+ return { ok: true, held: true, suppressed: "placeholder" };
1738
+ }
1739
+
1725
1740
  if (result.ok) {
1726
1741
  let guarded = false;
1727
1742
  try { guarded = require("./relationship").isCurrentSpeakerGuarded(); } catch (error) {
@@ -2219,16 +2234,27 @@ function nameThreadAfterTurn(result, runContext, store) {
2219
2234
  // detector spots durable-rule statements ("from now on / never / stop") in the
2220
2235
  // owner's message and captures them into lessons.md (or asks first). Owner-only
2221
2236
  // and best-effort: it never blocks or breaks a turn.
2237
+ //
2238
+ // Only the owner's own words count: store.inboundText (the typed message,
2239
+ // voice transcript or caption, set by admitGuardedTurn), never the prompt,
2240
+ // which also carries injected group/thread messages, quoted replies and the
2241
+ // bot's own earlier lines. Reading the prompt saved the bot's own "📌 ... from
2242
+ // now on" notes as the owner's rules, one more after every owner turn in a
2243
+ // group (sales bot, 4 Oct 2026). Group chats never capture: a rule note there
2244
+ // is noise for everyone else, so rules are set in a direct chat with the bot.
2222
2245
  async function considerGuardrail(result, runContext, store) {
2223
2246
  try {
2224
2247
  if (!result.ok || runContext.purpose !== "foreground") return;
2248
+ if (store?.isGroup) return;
2249
+ const ownWords = String(store?.inboundText || "").trim();
2250
+ if (!ownWords) return;
2225
2251
  const guardrail = require("./guardrail");
2226
2252
  if (!guardrail.enabled()) return;
2227
2253
  const relationship = require("./relationship");
2228
2254
  const speaker = relationship.speakerFor(store?.adapter?.type, store?.channelId, store?.userId);
2229
2255
  if (relationship.guardActive(speaker)) return; // external speakers never capture
2230
2256
  const announce = (text, opts) => chatContext.run(store, () => (statusChatterMuted() ? true : send(text, opts)));
2231
- await guardrail.consider({ text: runContext.userPrompt, speaker, announce });
2257
+ await guardrail.consider({ text: ownWords, speaker, announce });
2232
2258
  } catch (e) { /* best-effort — a guardrail miss must never break a turn */ }
2233
2259
  }
2234
2260
 
@@ -2706,4 +2732,6 @@ module.exports = {
2706
2732
  archiveCompactionBrief,
2707
2733
  collectRepoStateFacts,
2708
2734
  collectRecentTurns,
2735
+ considerGuardrail,
2736
+ logTurnUsage,
2709
2737
  };
@@ -372,6 +372,8 @@ Notes: a background daemon persists between commands, so navigation state carrie
372
372
 
373
373
  If you tell the user "I'll check back in N minutes" or "I'll run this every morning", you MUST schedule it with one of the commands above in the same turn. Otherwise nothing happens.
374
374
 
375
+ Interrupted turns: when a restart cuts a turn short, the agent CLI resumes the conversation by writing two lines into your history, "Continue from where you left off." and the reply "No response requested.". Neither came from the user or from you, and the user never saw them. Never mention or apologise for them, and never reply "No response requested." yourself: finish the interrupted work if it still matters, then act on the current message.
376
+
375
377
  ## Guidelines
376
378
  - Keep responses concise — many users are on mobile.
377
379
  - Be time-aware: the Runtime state block carries the current local time. Before suggesting that the user contact, test with, or wait on a colleague, check it against that person's working hours (entity notes often record their timezone). Late evening / night / early morning → schedule a wakeup for their next working morning instead of suggesting it now.
package/core/usage-log.js CHANGED
@@ -56,11 +56,20 @@ function floatEnv(env, key, fallback) {
56
56
  return Number.isFinite(n) ? n : fallback;
57
57
  }
58
58
 
59
+ // Token usage alerts are an operator's tuning aid, so they are off unless
60
+ // USAGE_ALERTS=on (Sumeet, 4 Oct 2026: "turn off this token usage thing by
61
+ // default on all the bots", after one landed in a staff group). The
62
+ // thresholds below only apply once they are switched on.
63
+ function usageAlertsOn(env = {}) {
64
+ const raw = env.USAGE_ALERTS ?? process.env.USAGE_ALERTS;
65
+ return /^(on|true|yes|1)$/i.test(String(raw ?? "").trim());
66
+ }
67
+
59
68
  function usageAlertPolicy(env = {}) {
60
69
  const absoluteContextTokens = intEnv(env, "USAGE_ALERT_CONTEXT_TOKENS", 120000);
61
70
  const rateMultiplier = floatEnv(env, "USAGE_ALERT_RATE_MULTIPLIER", 1.75);
62
71
  return {
63
- enabled: absoluteContextTokens > 0 || rateMultiplier > 0,
72
+ enabled: usageAlertsOn(env) && (absoluteContextTokens > 0 || rateMultiplier > 0),
64
73
  absoluteContextTokens,
65
74
  rateMultiplier,
66
75
  baselineTurns: intEnv(env, "USAGE_ALERT_BASELINE_TURNS", 20),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.5.10",
3
+ "version": "3.5.13",
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,8 +10,8 @@
10
10
  "test:kazee-call": "node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-call-speech.js && node test-kazee-call-session.js",
11
11
  "setup": "node setup.js",
12
12
  "start": "node bot.js",
13
- "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-codex-device-login.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-upgrade-path.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-kazee-context-media.js && node test-kazee-typing-join.js && node test-kazee-inbound-dedup.js && node test-typing-phase-push.js && node test-socket-reconnect.js && node test-speech-loopback.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 && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-call-speech.js && node test-kazee-call-session.js",
14
- "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 && node test-codex-rollout.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-transcript-room-scope.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-read-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-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-mandate-hygiene.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-external-rule-scopes.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-cards.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-kazee-group-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-kazee-group-context.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-threads.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-people-owner-seed.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-thread-lanes.js && node test-thread-titles.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-thread-stop.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-typing-heartbeat.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-stream-preview-purpose.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 && 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-say-loopback.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-loopback-threads.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-transcript-threads.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-union.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-subst-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-section-budgets.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-dedup-adjudicator.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-prospective.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-regressions.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-cost-report.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-context.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-mining.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-certification.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-apprenticeship.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-attest.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-attest.js && node test-sandbox-probe.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-bot-endpoint.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-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-cli-owner-gates.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-verify-contact.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-loopback-owner-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-watch-check-env.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-intro-adoption.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-grants.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-room-provider-settings.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-store-durability.js && node test-kazee-cards.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-passive-replies-router.js && node test-loopback-cards.js"
13
+ "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-codex-device-login.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-upgrade-path.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-kazee-context-media.js && node test-kazee-typing-join.js && node test-kazee-inbound-dedup.js && node test-typing-phase-push.js && node test-socket-reconnect.js && node test-speech-loopback.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-group-status-notes.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 && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-call-speech.js && node test-kazee-call-session.js",
14
+ "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 && node test-codex-rollout.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-transcript-room-scope.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-read-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-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-mandate-hygiene.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-external-rule-scopes.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-cards.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-kazee-group-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-kazee-group-context.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-threads.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-people-owner-seed.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-thread-lanes.js && node test-thread-titles.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-thread-stop.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-typing-heartbeat.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-stream-preview-purpose.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 && 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-say-loopback.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-loopback-threads.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-transcript-threads.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-union.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-subst-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-section-budgets.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-dedup-adjudicator.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-prospective.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-regressions.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-cost-report.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-context.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-mining.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-certification.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-apprenticeship.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-attest.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-attest.js && node test-sandbox-probe.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-bot-endpoint.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-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-cli-owner-gates.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-verify-contact.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-loopback-owner-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-watch-check-env.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-intro-adoption.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-grants.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-room-provider-settings.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-store-durability.js && node test-kazee-cards.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-placeholder-reply.js && node test-media-speech.js && node test-passive-replies-router.js && node test-loopback-cards.js"
15
15
  },
16
16
  "files": [
17
17
  "bot.js",
@@ -85,6 +85,7 @@
85
85
  "test-agency-ledger.js",
86
86
  "test-agency-deliberate.js",
87
87
  "test-guardrail.js",
88
+ "test-group-status-notes.js",
88
89
  "test-delivery-contract.js",
89
90
  "test-run-lock.js",
90
91
  "test-thread-lanes.js",
@@ -0,0 +1,140 @@
1
+ // Notes meant only for the owner stay out of group chats, and a standing rule
2
+ // comes only from the owner's own words. Sales bot, 4 Oct 2026: after each of
3
+ // Sumeet's turns in a staff group it posted "📌 Got it — I'll keep this loaded
4
+ // as a standing rule from now on" and saved its OWN previous 📌 line (shown to
5
+ // it as group context) as his rule, one more per turn; a "Token usage alert"
6
+ // landed in the same group. Token alerts are now off unless USAGE_ALERTS=on.
7
+ // No network, redirected config dir.
8
+
9
+ const assert = require("assert");
10
+ const fs = require("fs");
11
+ const os = require("os");
12
+ const path = require("path");
13
+
14
+ const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "oc-group-notes-"));
15
+ process.env.OPEN_CLAUDIA_TEST = "1";
16
+ process.env.OPEN_CLAUDIA_CONFIG_DIR = TMP;
17
+ process.env.WORKSPACE = TMP;
18
+ process.env.GUARDRAIL_CAPTURE = "on";
19
+ delete process.env.USAGE_ALERTS;
20
+
21
+ const lessons = require("./core/lessons");
22
+ const guardrail = require("./core/guardrail");
23
+ const relationship = require("./core/relationship");
24
+ const { runInChat } = require("./core/context");
25
+ const { usageAlertPolicy, evaluateUsageAlert } = require("./core/usage-log");
26
+ const { considerGuardrail, logTurnUsage } = require("./core/runner");
27
+
28
+ // The speaker is the owner throughout: who may capture at all is
29
+ // test-guardrail.js's job, this file is about which words and which rooms.
30
+ relationship.speakerFor = () => ({ isOwner: true, channelId: "kazee:room" });
31
+ relationship.guardActive = () => false;
32
+ relationship.isCurrentSpeakerGuarded = () => false;
33
+
34
+ function resetLessons() {
35
+ try { fs.rmSync(lessons.LESSONS_FILE, { force: true }); } catch (e) {}
36
+ try { fs.rmSync(lessons.LESSONS_META_FILE, { force: true }); } catch (e) {}
37
+ try { fs.rmSync(guardrail.PENDING_FILE, { force: true }); } catch (e) {}
38
+ }
39
+
40
+ // A chat store as the router builds it: inboundText is what the person said
41
+ // (typed text, voice transcript or caption), isGroup marks a group room.
42
+ function chat({ isGroup, inboundText }) {
43
+ const sent = [];
44
+ const adapter = {
45
+ type: "kazee",
46
+ send: async (channelId, text) => { sent.push(text); return { ok: true, messageId: `m${sent.length}` }; },
47
+ };
48
+ return { sent, store: { adapter, channelId: "kazee:room", userId: "owner", isGroup, inboundText } };
49
+ }
50
+
51
+ const OK = { ok: true };
52
+ const turn = (userPrompt) => ({ purpose: "foreground", userPrompt });
53
+ const RULE = "From now on, always send the deep link after posting a Spaces comment.";
54
+ const PIN = "📌 Got it — I'll keep this loaded as a standing rule from now on";
55
+
56
+ (async () => {
57
+ // 1. The live case: a question in a group whose context starts with the
58
+ // bot's own last 📌 note. Nothing is saved and nothing is posted.
59
+ resetLessons();
60
+ let c = chat({ isGroup: true, inboundText: "How many leads did we get last month?" });
61
+ await considerGuardrail(OK, turn([
62
+ "[Group context] Messages since your last reply:",
63
+ ` [1d ago] You: ${PIN}`,
64
+ " [2m ago] Sumeet Rajpal: How many leads did we get last month?",
65
+ "",
66
+ "How many leads did we get last month?",
67
+ ].join("\n")), c.store);
68
+ assert.deepStrictEqual(lessons.readLessons(), [], "the bot's own 📌 line in the context is not the owner's rule");
69
+ assert.deepStrictEqual(c.sent, [], "no 📌 note in the group");
70
+
71
+ // 2. A direct thread with the bot quoting its earlier 📌 note: the owner's
72
+ // own words hold no rule, so nothing is saved.
73
+ resetLessons();
74
+ c = chat({ isGroup: false, inboundText: "And deals?" });
75
+ await considerGuardrail(OK, turn(`[Kazee thread context]\n [36s ago] You: ${PIN}\n\nAnd deals?`), c.store);
76
+ assert.deepStrictEqual(lessons.readLessons(), [], "a quoted bot line never becomes a rule");
77
+ assert.deepStrictEqual(c.sent, []);
78
+
79
+ // 3. The owner states a rule in a direct chat: saved, and the 📌 note goes
80
+ // to that chat with its Undo.
81
+ resetLessons();
82
+ c = chat({ isGroup: false, inboundText: RULE });
83
+ await considerGuardrail(OK, turn(`[Kazee thread context]\n [5m ago] You: Done.\n\n${RULE}`), c.store);
84
+ const saved = lessons.readLessons();
85
+ assert.strictEqual(saved.length, 1, "the owner's own rule is saved");
86
+ assert.ok(/deep link/i.test(saved[0].text), saved[0].text);
87
+ assert.strictEqual(c.sent.length, 1);
88
+ assert.ok(c.sent[0].startsWith("📌"), c.sent[0]);
89
+
90
+ // 4. The same words in a group: rules are set in a direct chat, so the
91
+ // group gets no note and nothing is saved.
92
+ resetLessons();
93
+ c = chat({ isGroup: true, inboundText: RULE });
94
+ await considerGuardrail(OK, turn(RULE), c.store);
95
+ assert.deepStrictEqual(lessons.readLessons(), [], "groups never capture");
96
+ assert.deepStrictEqual(c.sent, []);
97
+
98
+ // 5. A turn with none of the owner's words (wake-up, cron): the prompt alone
99
+ // never captures, even when it reads like a rule.
100
+ resetLessons();
101
+ c = chat({ isGroup: false, inboundText: "" });
102
+ await considerGuardrail(OK, turn(`[Scheduled wakeup "x" fired] ${RULE}`), c.store);
103
+ assert.deepStrictEqual(lessons.readLessons(), [], "no own words, no capture");
104
+
105
+ // Token usage alerts: off by default; thresholds alone do not switch them
106
+ // on; USAGE_ALERTS=on does, unless both thresholds are off.
107
+ const big = { contextTokens: 127758 };
108
+ assert.strictEqual(usageAlertPolicy({}).enabled, false, "alerts are off by default");
109
+ assert.strictEqual(evaluateUsageAlert([], big, usageAlertPolicy({})).alert, false);
110
+ assert.strictEqual(usageAlertPolicy({ USAGE_ALERT_CONTEXT_TOKENS: "120000" }).enabled, false);
111
+ assert.strictEqual(usageAlertPolicy({ USAGE_ALERTS: "on" }).enabled, true);
112
+ assert.strictEqual(evaluateUsageAlert([], big, usageAlertPolicy({ USAGE_ALERTS: "on" })).alert, true);
113
+ assert.strictEqual(usageAlertPolicy({
114
+ USAGE_ALERTS: "on", USAGE_ALERT_CONTEXT_TOKENS: "off", USAGE_ALERT_RATE_MULTIPLIER: "off",
115
+ }).enabled, false);
116
+
117
+ // The turn's usage log: a 127.8k-context turn announces nothing by default,
118
+ // announces once in a direct chat with USAGE_ALERTS=on, and never in a group.
119
+ const usage = { input_tokens: 1000, output_tokens: 200, cache_read_input_tokens: 126558 };
120
+ async function alertsFor({ isGroup }) {
121
+ const alerts = [];
122
+ const announce = async (text) => { alerts.push(text); };
123
+ const state = { userId: "owner", sessionUsage: {} };
124
+ const { store } = chat({ isGroup, inboundText: "And deals?" });
125
+ await runInChat({ ...store }, async () => logTurnUsage(state, usage, 0, announce, {
126
+ backend: "claude", model: "claude-test", sessionId: "s1", liveContextTokens: 127758,
127
+ }));
128
+ return alerts;
129
+ }
130
+ assert.deepStrictEqual(await alertsFor({ isGroup: false }), [], "off by default in a direct chat too");
131
+ process.env.USAGE_ALERTS = "on";
132
+ const direct = await alertsFor({ isGroup: false });
133
+ assert.strictEqual(direct.length, 1, "USAGE_ALERTS=on announces in a direct chat");
134
+ assert.ok(direct[0].startsWith("Token usage alert:"), direct[0]);
135
+ assert.deepStrictEqual(await alertsFor({ isGroup: true }), [], "never in a group");
136
+ delete process.env.USAGE_ALERTS;
137
+
138
+ console.log("group status notes: ok");
139
+ process.exit(0);
140
+ })().catch((error) => { console.error(error); process.exit(1); });
@@ -540,7 +540,20 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
540
540
  // then let's test on something like sales bot which is in groups"): passive
541
541
  // replies in Kazee groups, no endless bot-to-bot loops, and decide asks in the
542
542
  // thread that queued it. Canary on the sales bot only.
543
- assert.strictEqual(pkg.version, "3.5.10", "release version must remain unchanged without explicit approval");
543
+ // 3.5.12 approved by Sumeet 2026-10-04 08:47 EAT ("Yes, ship it" on the card
544
+ // "Ship Open Claudia 3.5.11 and move the sales bot onto it?"): never post
545
+ // Claude Code's "No response requested." placeholder, and voice notes wait out
546
+ // the speech queue and say why they fail. 3.5.11 was tagged but never
547
+ // published (its release run failed on a test), so the same release ships as
548
+ // 3.5.12. Canary on the sales bot only.
549
+ // 3.5.13 approved by Sumeet 2026-10-04 12:27 EAT ("Yes, fix and ship" on the card
550
+ // "Switch token alerts off on every bot, fix the junk 📌 posts, and ship 3.5.13
551
+ // to the sales bot?"), after asking at 11:44 EAT ("Can we turn of[f] this token
552
+ // usage thing by default on all the bots"): rules come only from the owner's own
553
+ // words in a direct chat, so group chats never get junk "standing rule" posts,
554
+ // and token usage alerts are off by default and never go to a group chat.
555
+ // Canary on the sales bot only.
556
+ assert.strictEqual(pkg.version, "3.5.13", "release version must remain unchanged without explicit approval");
544
557
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
545
558
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
546
559
  }