@inetafrica/open-claudia 3.2.0 → 3.2.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.
@@ -4,7 +4,7 @@
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
6
  const { SOUL_FILE, CRONS_FILE, VAULT_FILE, FILES_DIR, BOT_DIR, config } = require("./config");
7
- const { canSpeak } = require("./media");
7
+ const { canSpeak, canTranscribe } = require("./media");
8
8
  const CONFIG_DIR = require("../config-dir");
9
9
  const { currentState, getProjectKey, getProviderSession } = require("./state");
10
10
  const { currentAdapter, currentChannelId, currentUserId, currentSpeakerName } = require("./context");
@@ -202,6 +202,7 @@ function buildCoreInstructionsUncached() {
202
202
  // Gate on actual TTS capability, not local Whisper: hosted pods hear and
203
203
  // speak through the speech gateway with no whisper binary installed.
204
204
  const hasVoice = canSpeak();
205
+ const hasEars = canTranscribe();
205
206
  return `
206
207
  ${soul}
207
208
  ${buildPersonaBlock()}
@@ -235,6 +236,13 @@ Reply normally in your final answer. To send a file, image, or voice clip back t
235
236
  - \`open-claudia send-voice <path>\` — ogg/opus voice note
236
237
  - \`open-claudia say "<text>"\` — synthesize the text and send it as a voice note (use this when asked for a voice reply)
237
238
  Never print or embed bot tokens in prompts, commands, logs, or messages.
239
+ ${hasVoice || hasEars ? `
240
+ ## Speech services (TTS / STT)
241
+ Text-to-speech and speech-to-text are general capabilities, not just the voice-note reply path. Synthesis and transcription run inside the credentialed bot process — your own shell holds NO speech token or vendor key, so never call the speech gateway or TTS/STT vendor APIs directly and never conclude "the key is missing"; use these commands (they work from inside any bot-spawned task):
242
+ ${hasVoice ? `- \`open-claudia tts "<text>" --out <path>.mp3\` — synthesize narration to a FILE without sending it to chat: video voice-overs, explainer narration, IVR prompts, any audio artifact. The extension picks the container (.ogg native, .mp3/.wav transcoded); prints the final path. \`--voice <id>\` selects a specific gateway voice (e.g. a cloned voice).
243
+ - \`open-claudia say "<text>"\` — synthesize AND send as a voice note to the current chat (listed under Delivery above).` : ""}
244
+ ${hasEars ? `- \`open-claudia transcribe <file>\` — speech-to-text: prints the transcript of an audio OR video file (voice notes, meeting recordings, mp3/m4a/wav/ogg, mp4/mkv — the audio track is extracted automatically). Use it whenever the user sends a recording and wants its contents.` : ""}
245
+ For long narration, synthesize per-paragraph with \`tts\` and assemble with ffmpeg.` : ""}
238
246
 
239
247
  ## Background work
240
248
  You can persist work across turns and wake yourself up later. These survive bot restarts.
@@ -247,6 +255,7 @@ Wake-ups and crons (real schedulers, not hallucinations — use them instead of
247
255
 
248
256
  Watches (a silent background monitor — poll a condition WITHOUT waking every tick; wake only when it actually happens). Use this instead of a minute-by-minute cron whenever you'd otherwise fire a turn just to check "is it done yet?" and stay quiet most times:
249
257
  - \`open-claudia watch-add "<check-cmd>" "<wake-prompt>"\` — the scheduler polls \`<check-cmd>\` as a shell command (default every minute). The poll is SILENT: nothing reaches the chat while the check keeps failing. The instant \`<check-cmd>\` exits 0, you wake in this same conversation with \`<wake-prompt>\`, exactly like a wakeup. Non-zero exit = still waiting = nothing happens. Default is ONE-SHOT (fires once, then removes itself); add \`--repeat\` to keep polling after each hit. \`--every "<5-field cron>"\` changes the poll cadence.
258
+ - Every watch has a hard timeout — \`--timeout <duration|ISO>\` (default 2h). A watch that reaches its timeout without the check ever passing FIRES anyway on the next poll, with the prompt marked "Watch expired" so you know the condition did NOT happen. No watch can end silently, so you no longer need a separate backup wakeup for every watch — but size \`--timeout\` honestly to the work being watched.
250
259
  - \`open-claudia watch-list\` / \`open-claudia watch-remove <id>\` — inspect/cancel watches on this channel.
251
260
  - The owner has a global master switch: \`/watch off\` pauses ALL watch polling (and \`/watch on\` resumes). If \`watch-list\` shows the switch OFF, watches won't fire until it's back on.
252
261
  - Example: after kicking off a long external job, \`open-claudia watch-add "test -f /tmp/build-done || exit 1" "the build finished — verify and report"\` beats a wakeup guess, because it fires the moment it's actually done, not on a fixed timer. (Never use a watch to poll harness-tracked work you started — that notifies you automatically. Watches are for EXTERNAL state the harness can't see.)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.2.0",
3
+ "version": "3.2.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,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-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-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-kazee-media-caption.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-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 && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-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-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-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 && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-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"
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-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-kazee-media-caption.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-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 && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-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-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-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-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-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"
15
15
  },
16
16
  "files": [
17
17
  "bot.js",
@@ -487,8 +487,12 @@ async function main() {
487
487
  throw new Error("FAKE_AGENT_KIND must be claude or codex");
488
488
  }
489
489
  if (handleCompatibilityCommand()) return;
490
- captureInvocation();
490
+ // Arm signal handlers BEFORE writing the boot capture: the capture is the
491
+ // parent's green light to send SIGTERM, and on a loaded machine the child
492
+ // can be preempted between these calls — a TERM then hits node's default
493
+ // disposition and kills the process before the signal capture ever writes.
491
494
  installSignalControls();
495
+ captureInvocation();
492
496
  spawnDescendantFixture();
493
497
  const stdin = await readStdin();
494
498
  captureStdin(stdin);
@@ -11,7 +11,7 @@ class FakeSession {
11
11
  }
12
12
  function fixture() {
13
13
  const events = [], inbound = [];
14
- const socket = { on() {}, emit(name, payload) { events.push({ name, payload }); } };
14
+ const socket = { connected: true, on() {}, emit(name, payload) { events.push({ name, payload }); } };
15
15
  const adapter = { ownerUserId: "owner-1", botUserId: "bot-1", _socket: socket, _emit(name, payload) { inbound.push({ name, payload }); } };
16
16
  return { events, inbound, manager: new KazeeCallManager({ adapter, SessionImpl: FakeSession }) };
17
17
  }
@@ -36,5 +36,48 @@ function fixture() {
36
36
  assert.strictEqual(f.manager.session.spoken, "Your agenda is clear.");
37
37
  await f.manager.ended({ roomId: 12 });
38
38
  assert.strictEqual(f.manager.session, null);
39
+
40
+ // Outbound: session starts in caller mode, invite goes out, answer clears the ring timer.
41
+ f = fixture();
42
+ const res = await f.manager.outgoing({ chatId: "chat-2" });
43
+ assert.strictEqual(res.toUserId, "owner-1");
44
+ const outSession = f.manager.session;
45
+ assert.strictEqual(outSession.options.asCaller, true);
46
+ assert.ok(outSession.options.greeting);
47
+ assert.strictEqual(outSession.started, true);
48
+ const invite = f.events.find((e) => e.name === "call:invite");
49
+ assert.deepStrictEqual(invite.payload, { toUserId: "owner-1", callType: "audio", chatId: "chat-2", roomId: res.roomId });
50
+ assert.ok(f.manager._ringTimer);
51
+ // A callee device auto-ending its ring must NOT tear down the outbound call —
52
+ // chat-central revives the CallLog if the human answers late.
53
+ await f.manager.ended({ chatId: "chat-2" });
54
+ assert.ok(f.manager.session, "ended-while-ringing is ignored");
55
+ f.manager.accepted({ chatId: "chat-2" });
56
+ assert.strictEqual(f.manager._ringTimer, null);
57
+ // Outbound transcripts dispatch with the remote human as the "from" side.
58
+ outSession.options.onTranscript("Hello there");
59
+ assert.strictEqual(f.inbound[0].payload.channelId, "chat-2");
60
+ assert.strictEqual(f.inbound[0].payload.userId, "owner-1");
61
+ assert.strictEqual(f.inbound[0].payload.raw.ephemeralAudio, true);
62
+ await assert.rejects(() => f.manager.outgoing({ chatId: "chat-2" }), /busy/);
63
+ await f.manager.ended({ roomId: res.roomId });
64
+ assert.strictEqual(f.manager.session, null);
65
+
66
+ // A deliberate decline (call:rejected) still tears down mid-ring.
67
+ f = fixture();
68
+ await f.manager.outgoing({ chatId: "chat-4" });
69
+ assert.ok(f.manager._ringTimer);
70
+ await f.manager.rejected({ chatId: "chat-4" });
71
+ assert.strictEqual(f.manager.session, null, "reject-while-ringing closes the call");
72
+ assert.strictEqual(f.manager._ringTimer, null);
73
+
74
+ // Unanswered: ring timeout tells the server to finalize as missed and hangs up.
75
+ f = fixture();
76
+ const res2 = await f.manager.outgoing({ chatId: "chat-3", toUserId: "owner-1" });
77
+ f.manager._clearRingTimer();
78
+ await f.manager._ringTimeout("owner-1", "chat-3", res2.roomId);
79
+ const end = f.events.find((e) => e.name === "call:end");
80
+ assert.deepStrictEqual(end.payload, { toUserId: "owner-1", chatId: "chat-3" });
81
+ assert.strictEqual(f.manager.session, null);
39
82
  console.log("kazee call manager: ok");
40
83
  })().catch((error) => { console.error(error); process.exitCode = 1; });
@@ -41,12 +41,12 @@ class FakeSpeechStream {
41
41
  close() { this.closedStream = true; }
42
42
  }
43
43
 
44
- function makeSession({ synthesize, maxCallMs, idleMs } = {}) {
44
+ function makeSession({ synthesize, maxCallMs, idleMs, asCaller, greeting } = {}) {
45
45
  const ended = [];
46
46
  const transcripts = [];
47
47
  const session = new KazeeCallSession({
48
48
  roomId: 7, chatId: "chat", callerId: "owner", botUserId: "bot", janusUrl: "ws://test",
49
- maxCallMs, idleMs,
49
+ maxCallMs, idleMs, asCaller, greeting,
50
50
  synthesize: synthesize || (async () => ({ pcm: Buffer.alloc(WEBRTC_SAMPLES * 2 * 2), sampleRate: 48000 })),
51
51
  onTranscript: (text, meta) => transcripts.push({ text, meta }),
52
52
  onEnded: (reason) => ended.push(reason),
@@ -228,6 +228,47 @@ async function testJoinGivesUpAfterRetries() {
228
228
  await session.close();
229
229
  }
230
230
 
231
+ async function testCallerModeRoomLifecycle() {
232
+ const { session } = makeSession({ asCaller: true, greeting: "hi there" });
233
+ const messages = [];
234
+ session.janus.message = async (_handle, body) => {
235
+ messages.push(body);
236
+ if (body?.request === "create") return { plugindata: { data: { videoroom: "created", room: 7 } } };
237
+ return {};
238
+ };
239
+ await session.start();
240
+ assert.strictEqual(messages[0].request, "create", "caller creates the room before joining");
241
+ assert.strictEqual(messages[0].room, 7);
242
+ assert.strictEqual(messages[0].audiocodec, "opus");
243
+ assert.strictEqual(messages[1].request, "join");
244
+ assert.strictEqual(session._createdRoom, true);
245
+ const spoken = [];
246
+ session.speak = async (text) => { spoken.push(text); return true; };
247
+ await session._subscribe(101, "sumeet@kazee");
248
+ await session._subscribe(102, "second@kazee");
249
+ assert.deepStrictEqual(spoken, ["hi there"], "greeting speaks exactly once, on first remote feed");
250
+ await session.close();
251
+ const destroy = messages.find((m) => m?.request === "destroy");
252
+ assert.ok(destroy && destroy.room === 7, "created room is destroyed on close");
253
+ }
254
+
255
+ async function testCallerModeRoomExistsAndFailure() {
256
+ const { session } = makeSession({ asCaller: true });
257
+ session.janus.message = async (_handle, body) => body?.request === "create"
258
+ ? { plugindata: { data: { videoroom: "event", error_code: 427, error: "Room 7 already exists" } } }
259
+ : {};
260
+ await session.start();
261
+ assert.strictEqual(session._createdRoom, true, "already-exists (427) counts as created");
262
+ await session.close();
263
+
264
+ const bad = makeSession({ asCaller: true }).session;
265
+ bad.janus.message = async (_handle, body) => body?.request === "create"
266
+ ? { plugindata: { data: { videoroom: "event", error_code: 499, error: "boom" } } }
267
+ : {};
268
+ await assert.rejects(() => bad.start(), /boom/, "hard create failure fails the call");
269
+ await bad.close();
270
+ }
271
+
231
272
  async function testThinkingCues() {
232
273
  const { session } = makeSession({
233
274
  synthesize: async () => ({ pcm: Buffer.alloc(WEBRTC_SAMPLES * 2 * 3), sampleRate: 48000 }),
@@ -300,6 +341,8 @@ async function testCloseResolvesSpeech() {
300
341
  await testMaxDuration();
301
342
  await testJoinRetriesNoSuchRoom();
302
343
  await testJoinGivesUpAfterRetries();
344
+ await testCallerModeRoomLifecycle();
345
+ await testCallerModeRoomExistsAndFailure();
303
346
  await testThinkingCues();
304
347
  await testCloseResolvesSpeech();
305
348
  console.log("kazee call session: ok");
@@ -346,7 +346,26 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
346
346
  // into a new version work through till the end and deploy out a new version"):
347
347
  // the employee batch — memory adoption A1-A4, the codegraph reusable tool,
348
348
  // employee capabilities C1-C6, and the Kazee group speaker-identity fix.
349
- assert.strictEqual(pkg.version, "3.2.0", "release version must remain unchanged without explicit approval");
349
+ // 3.2.1 approved by Sumeet 2026-08-08 ("Push it", answering "Your go on the
350
+ // v3.2.1 cut → commit, tag, CI image, fleet roll"): terminal socket-reconnect
351
+ // fix from the chat-central outage, the call-platform batch (outgoing calls,
352
+ // greetings, TURN via /webrtc/config, ring-phase teardown fixes), the
353
+ // received-media .bin extension fix, and the tts/transcribe agent verbs.
354
+ // 3.2.2 approved by Sumeet 2026-08-09 ("Yeh", answering "Want me to build the
355
+ // chip fix now and fold it in? Then a single /restart live-verifies everything
356
+ // and I cut"): the presence-chip batch — thinking-on-receipt (heartbeat starts
357
+ // the moment a turn-bound message arrives, covering enforcer triage/download/
358
+ // STT dead air), tool stretches re-enter "thinking" instead of decaying the
359
+ // chip, /stop emits an explicit typing:stop, and the dedup adjudicator's
360
+ // "dedup" utility purpose registered so the journal dedup gate actually runs.
361
+ // 3.2.3 approved by Sumeet 2026-08-13 ("While we wait can we update open
362
+ // Claudia so a watcher requires a timeout or defaults to something so that
363
+ // when it expires it acts as a wake up so no watcher can silently fail?"):
364
+ // watch expiry — every new watch carries expiresAt (--timeout flag, default
365
+ // 2h); a lapsed deadline with a still-failing check fires the wake anyway as
366
+ // a marked "Watch expired" prompt and removes the watch, so a watch whose
367
+ // condition never comes true can no longer end silently.
368
+ assert.strictEqual(pkg.version, "3.2.3", "release version must remain unchanged without explicit approval");
350
369
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
351
370
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
352
371
  }
@@ -826,6 +826,40 @@ async function watchGateProbe(createSchedulerService) {
826
826
  assert.strictEqual(repeatJob.status, "pending");
827
827
  assert.strictEqual(repeatJob.occurrenceId, "next-occ-1", "repeat watch advances to a new occurrence");
828
828
 
829
+ // Failing check with a FUTURE expiry → still the normal silent idle skip.
830
+ const pending = schedulerHarness(createSchedulerService, [watchJob({ id: "watch-pending", expiresAt: 1_720_000_100_000 })], {
831
+ runWatchCheck: async () => ({ code: 1, timedOut: false }),
832
+ outcomes: [successOutcome()],
833
+ });
834
+ const pendingResult = await pending.service.fireJob("watch-pending");
835
+ assert.strictEqual(pendingResult.status, "watch-idle");
836
+ assert.strictEqual(pending.runCalls.length, 0, "unexpired failing check stays silent");
837
+
838
+ // Failing check with a LAPSED expiry → fires anyway as an expiry wake-up:
839
+ // distinct header, advisory says timed out, removed even though repeat=true.
840
+ const expired = schedulerHarness(createSchedulerService, [watchJob({ id: "watch-expired", repeat: true, expiresAt: 1_720_000_000_000 })], {
841
+ runWatchCheck: async () => ({ code: 1, timedOut: false }),
842
+ outcomes: [successOutcome()],
843
+ });
844
+ const expiredResult = await expired.service.fireJob("watch-expired");
845
+ assert.strictEqual(expiredResult.ok, true);
846
+ assert.strictEqual(expiredResult.status, "succeeded");
847
+ assert.strictEqual(expired.runCalls.length, 1, "expired watch wakes the agent exactly once");
848
+ assert.match(expired.runCalls[0].prompt, /Watch expired/, "expiry fire carries the Watch expired header");
849
+ assert.match(expired.runCalls[0].prompt, /did NOT happen/, "expiry fire tells the agent the condition never came true");
850
+ assert.ok(expired.sends.some((text) => /timed out without triggering/.test(text)), "expiry advisory says it timed out");
851
+ assert.strictEqual(expired.jobsStore.get("watch-expired"), null, "expired watch is removed even when repeat was requested");
852
+
853
+ // Check that passes ON the expiry tick → a normal trigger, not an expiry.
854
+ const lastGasp = schedulerHarness(createSchedulerService, [watchJob({ id: "watch-lastgasp", expiresAt: 1_720_000_000_000 })], {
855
+ runWatchCheck: async () => ({ code: 0, timedOut: false }),
856
+ outcomes: [successOutcome()],
857
+ });
858
+ await lastGasp.service.fireJob("watch-lastgasp");
859
+ assert.strictEqual(lastGasp.runCalls.length, 1);
860
+ assert.match(lastGasp.runCalls[0].prompt, /Watch triggered/, "a passing check at the deadline is a real trigger");
861
+ assert.ok(!/Watch expired/.test(lastGasp.runCalls[0].prompt), "a passing check never reports as expired");
862
+
829
863
  // Global master switch OFF → skip before the check even runs.
830
864
  fs.writeFileSync(watchesEnabledFile, "off");
831
865
  let checkRan = false;
@@ -841,6 +875,45 @@ async function watchGateProbe(createSchedulerService) {
841
875
  fs.rmSync(watchesEnabledFile, { force: true });
842
876
  }
843
877
 
878
+ function watchExpiryStampProbe(createSchedulerService, DEFAULT_WATCH_TIMEOUT_MS) {
879
+ assert.strictEqual(typeof DEFAULT_WATCH_TIMEOUT_MS, "number", "scheduler must export the default watch timeout");
880
+ const state = {
881
+ userId: "fixture-user",
882
+ currentSession: { name: "alpha", dir: "/workspace/alpha" },
883
+ settings: { backend: "claude" },
884
+ activeSessions: { alpha: { claude: "thread-alpha" } },
885
+ providerSettings: { claude: {} },
886
+ };
887
+ const service = createSchedulerService({
888
+ jobsStore: memoryJobsStore([]),
889
+ currentState: () => state,
890
+ now: () => 1_720_000_000_500,
891
+ setTimer: () => ({}),
892
+ clearTimer: () => {},
893
+ scheduleCron: () => ({ stop() {} }),
894
+ });
895
+ const stamped = service.addWatch({
896
+ adapter: "telegram-fixture",
897
+ adapterType: "telegram",
898
+ channelId: "fixture-channel",
899
+ canonicalUserId: "fixture-user",
900
+ check: "true",
901
+ prompt: "watch prompt",
902
+ });
903
+ assert.strictEqual(stamped.expiresAt, 1_720_000_000_500 + DEFAULT_WATCH_TIMEOUT_MS, "watch-add stamps the default expiry when none is given");
904
+ const explicit = service.addWatch({
905
+ adapter: "telegram-fixture",
906
+ adapterType: "telegram",
907
+ channelId: "fixture-channel",
908
+ canonicalUserId: "fixture-user",
909
+ check: "true",
910
+ prompt: "watch prompt 2",
911
+ expiresAt: 1_720_000_999_999,
912
+ });
913
+ assert.strictEqual(explicit.expiresAt, 1_720_000_999_999, "an explicit --timeout expiry passes through unchanged");
914
+ service.stopAll();
915
+ }
916
+
844
917
  function staticSchedulingProbe() {
845
918
  const files = [
846
919
  "core/jobs.js",
@@ -863,6 +936,12 @@ function staticSchedulingProbe() {
863
936
  assert.match(source["core/loopback.js"], /watch-add/, "loopback must route watch-add");
864
937
  assert.match(source["bin/schedule.js"], /runWatchAdd/, "schedule CLI must expose watch-add");
865
938
  assert.match(source["bin/cli.js"], /watch-add/, "cli must dispatch watch-add");
939
+
940
+ // Watch expiry must be wired end to end: default stamp → gate → CLI flag.
941
+ assert.match(source["core/scheduler.js"], /DEFAULT_WATCH_TIMEOUT_MS/, "scheduler must define the default watch timeout");
942
+ assert.match(source["core/scheduler.js"], /watchExpired/, "scheduler must mark expiry fires distinctly");
943
+ assert.match(source["core/loopback.js"], /expiresAt/, "loopback must pass the watch expiry through");
944
+ assert.match(source["bin/schedule.js"], /--timeout/, "watch-add CLI must expose --timeout");
866
945
  }
867
946
 
868
947
  async function main() {
@@ -884,6 +963,7 @@ async function main() {
884
963
  await unavailableAndFallbackProbe(schedulerApi.createSchedulerService);
885
964
  await cronOccurrenceProbe(schedulerApi.createSchedulerService);
886
965
  await watchGateProbe(schedulerApi.createSchedulerService);
966
+ watchExpiryStampProbe(schedulerApi.createSchedulerService, schedulerApi.DEFAULT_WATCH_TIMEOUT_MS);
887
967
  creationEnrichmentProbe(schedulerApi.createSchedulerService);
888
968
  loopbackMetadataProbe(require("./core/runner"), require("./bin/loopback-client"));
889
969
  staticSchedulingProbe();
@@ -185,6 +185,28 @@ async function main() {
185
185
  "legacy Claude model aliases never leak into Codex",
186
186
  );
187
187
 
188
+ // v3.2.2: "dedup" (journal dedup adjudicator) used to be rejected outright
189
+ // with INVALID_UTILITY_PURPOSE, silently disabling the dedup gate on every
190
+ // memory review. It rides the review pipeline: MEMORY_PROVIDER steers it,
191
+ // PACK_DEDUP_MODEL is its legacy Claude model alias.
192
+ assert.ok(
193
+ createUtilityPolicy({ registry: allReady, env: { UTILITY_PROVIDER: "claude" } })
194
+ .resolve({ purpose: "dedup", tier: "low" }).providerId,
195
+ "dedup is a valid utility purpose",
196
+ );
197
+ assert.strictEqual(
198
+ createUtilityPolicy({ registry: allReady, env: { UTILITY_PROVIDER: "claude", MEMORY_PROVIDER: "codex" } })
199
+ .resolve({ purpose: "dedup", tier: "low" }).providerId,
200
+ "codex",
201
+ "dedup follows the review pipeline's MEMORY_PROVIDER override",
202
+ );
203
+ assert.strictEqual(
204
+ createUtilityPolicy({ registry: allReady, env: { UTILITY_PROVIDER: "claude", PACK_DEDUP_MODEL: "legacy-claude-dedup" } })
205
+ .resolve({ purpose: "dedup", tier: "low" }).model,
206
+ "legacy-claude-dedup",
207
+ "dedup honors its PACK_DEDUP_MODEL legacy alias",
208
+ );
209
+
188
210
  const fallbackRegistry = registry(map, {
189
211
  claude: { availability: { state: "missing", executable: null, reason: "fixture unavailable" } },
190
212
  });