@inetafrica/open-claudia 3.4.0 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.4.2 — The bot was typing into a room it had never joined
4
+
5
+ - **The typing and thinking indicators had been dead since 2026-08-23, and nothing said so.** chat-central `7aa9276` ("authorize typing") made `handleTypingStart` return early unless the sending socket had joined that chat's Socket.IO room — a correct fix, since `socket.to(chatId)` addresses a room by name and would otherwise let any authenticated user inject an indicator into any chat whose id they knew. But the Kazee adapter only ever joined the bot's own user room, which is how `message:new` reaches it. So messages kept flowing while every `typing:start` was discarded server-side, which is exactly why this read as a rendering bug in the web client.
6
+ - **The adapter now joins the chat room, and waits for the ack before signalling.** The ordering is the fix, not an optimisation: `chat:join` awaits a participant lookup while `typing:start` is handled synchronously, so emitting both back-to-back loses the race and the indicator is dropped anyway. Membership is cached so a per-second heartbeat does not re-join on every tick, concurrent ticks share one in-flight join, and the cache is cleared on connect *and* disconnect because server-side room membership dies with the socket. A refused join now emits nothing rather than something silently discarded, and `typingStop` never joins a room merely to leave it.
7
+ - Regression coverage in `test-kazee-typing-join.js`, wired into `pretest`: join-before-emit ordering, the `thinking` state surviving, one join per chat rather than per tick, shared in-flight joins, no indicator after a refusal, re-join after reconnect, and stop-only-for-joined-rooms. The reconnect case earned its place immediately by catching a real defect in the first cut of the fix — a synchronous ack settled the join promise before it was registered, stranding a resolved entry that would have suppressed the re-join.
8
+
9
+ ## v3.4.1 — Fable 5.1, and the CLI floor it quietly needs
10
+
11
+ - **`claude-fable-5-1` joins the Claude model picker.** The identifier is dash-form; `claude-fable-5.1` does not exist and fails as an unknown model.
12
+ - **It carries a CLI floor: Claude Code >= 2.1.251.** An older CLI does not degrade gracefully — it returns an API 400 naming the required version. The local install was taken 2.1.150 -> 2.1.258 before the model was listed, and the picker offering a button the local binary cannot serve is the failure mode to avoid. Pod images install Claude Code unpinned, so a rebuild clears the floor by itself, but pods still on older images will 400 on this model until they are rolled.
13
+ - Verified by building the real main invocation through the provider and spawning it, rather than by a hand-written CLI probe: the API reported `claude-fable-5-1` as the serving model, and `claude-opus-4-8` was re-run through the same harness to prove the incumbent path was untouched. Full suite 135/135.
14
+
3
15
  ## v3.4.0 — Who you are, where you are, and what you may do there are three different questions
4
16
 
5
17
  - **The bug that started it: a wakeup scheduled in a Codex room kept running on Claude.** `getActiveProvider` resolves the room's overlay from `state.roomKey`, but that key was stamped from `currentConversationKey()` — and a scheduled job resumes its own saved thread, whose conversation key is the job's *project name*, not a room. The overlay never resolved, so every job silently fell back to the creator's personal default. The thread and the room are now separate values (`roomKey`, defaulting to the conversation key so every inbound path is unchanged); the scheduler passes the room the job was created in. The same fusion is why a group answered as Codex to one person and Claude to another. Relatedly, a run that reverts to the provider it was scheduled under because the room's current choice is not runnable now **says so** — in the prompt and in a ⚠️ line on the advisory — instead of leaving a Codex room reporting a Claude quota error with nothing to explain where Claude came from.
@@ -19,6 +19,11 @@ const kazeeState = require("./state");
19
19
  // Group catch-up context sizing: how many messages an engaged turn may carry,
20
20
  // how long each rendered body may be, and how far back a chat's very first
21
21
  // engage looks (no watermark yet — recent window instead of the whole backlog).
22
+ // How long to wait for chat-central's chat:join ack before giving up on the
23
+ // typing indicator for this tick. Short on purpose: an indicator that arrives
24
+ // late is worth less than the next heartbeat tick, which retries the join.
25
+ const KAZEE_JOIN_ACK_TIMEOUT_MS = 5000;
26
+
22
27
  const CONTEXT_MAX_MESSAGES = 40;
23
28
  const CONTEXT_BODY_LIMIT = 280;
24
29
  const COLD_START_WINDOW = 15;
@@ -59,6 +64,13 @@ class KazeeAdapter {
59
64
  this._listeners = { message: new Set(), action: new Set() };
60
65
  this._socket = null;
61
66
  this._chatMetaCache = new Map();
67
+ // Socket.IO rooms this socket has joined. chat-central only broadcasts a
68
+ // typing indicator from a socket that has joined the chat's room, so this
69
+ // tracks what we have joined to avoid re-joining on every keystroke tick.
70
+ // Server-side room membership dies with the connection, so it is cleared
71
+ // on every (re)connect.
72
+ this._joinedRooms = new Set();
73
+ this._joinPending = new Map();
62
74
  this._callManager = new KazeeCallManager({ adapter: this });
63
75
  }
64
76
 
@@ -119,13 +131,21 @@ class KazeeAdapter {
119
131
  userId: this.botUserId,
120
132
  onConnect: (s) => {
121
133
  console.log(`Kazee socket connected (${this.url})`);
134
+ // A reconnect is a NEW socket server-side with an empty joinedRooms,
135
+ // so anything we believed we had joined is stale.
136
+ this._joinedRooms.clear();
137
+ this._joinPending.clear();
122
138
  if (this.botUserId) {
123
139
  // Join the bot's personal room so the server can fan out
124
140
  // message:action presses directly to us.
125
141
  s.emit("user:join", { userId: this.botUserId });
126
142
  }
127
143
  },
128
- onDisconnect: (reason) => console.log(`Kazee socket disconnected: ${reason}`),
144
+ onDisconnect: (reason) => {
145
+ console.log(`Kazee socket disconnected: ${reason}`);
146
+ this._joinedRooms.clear();
147
+ this._joinPending.clear();
148
+ },
129
149
  onError: (err) => console.error("Kazee socket error:", err.message),
130
150
  });
131
151
  this._wireSocket(this._socket);
@@ -610,9 +630,57 @@ class KazeeAdapter {
610
630
  // `state` is an optional richer indicator: "thinking" (the bot is reasoning
611
631
  // pre-first-token) vs the default "typing". Kazee clients render "thinking"
612
632
  // distinctly; older clients ignore the field and fall back to "typing".
633
+ // chat-central authorizes typing by room membership (handleTypingStart
634
+ // returns early unless socket.data.joinedRooms has the chat), and the bot
635
+ // only ever joined its OWN user room — so every indicator was being dropped
636
+ // server-side. Joining is acknowledged and must complete BEFORE the typing
637
+ // emit: chat:join awaits a participant lookup while typing:start is handled
638
+ // synchronously, so emitting both back-to-back loses the race.
639
+ async _ensureJoinedRoom(chatId) {
640
+ if (this._joinedRooms.has(chatId)) return true;
641
+ const socket = this._socket;
642
+ if (!socket?.emit) return false;
643
+ const inFlight = this._joinPending.get(chatId);
644
+ if (inFlight) return inFlight;
645
+
646
+ let settled = false;
647
+ const attempt = new Promise((resolve) => {
648
+ const finish = (ok) => {
649
+ if (settled) return;
650
+ settled = true;
651
+ if (ok) this._joinedRooms.add(chatId);
652
+ this._joinPending.delete(chatId);
653
+ resolve(ok);
654
+ };
655
+ // The ack is called with an ErrorPayload on refusal and no argument on
656
+ // success. A silent server (or a dropped connection) must not strand the
657
+ // entry in _joinPending, hence the timeout.
658
+ const timer = setTimeout(() => finish(false), KAZEE_JOIN_ACK_TIMEOUT_MS);
659
+ try {
660
+ socket.emit("chat:join", { chatId }, (err) => {
661
+ clearTimeout(timer);
662
+ if (err) console.error(`Kazee chat:join refused (${chatId}):`, err.message || err.code || err);
663
+ finish(!err);
664
+ });
665
+ } catch (e) {
666
+ clearTimeout(timer);
667
+ finish(false);
668
+ }
669
+ });
670
+ // An ack that fires synchronously settles `attempt` before we reach this
671
+ // line, and its own cleanup ran against an entry that did not exist yet.
672
+ // Registering it now would strand a permanently-resolved promise that
673
+ // outlives the membership it recorded — so a later reconnect would read a
674
+ // cached "already joined" and never re-join.
675
+ if (!settled) this._joinPending.set(chatId, attempt);
676
+ return attempt;
677
+ }
678
+
613
679
  async typing(channelId, state) {
614
680
  try {
615
- const payload = { chatId: channelId };
681
+ const chatId = String(channelId);
682
+ if (!(await this._ensureJoinedRoom(chatId))) return;
683
+ const payload = { chatId };
616
684
  if (state) payload.state = state;
617
685
  this._socket?.emit?.("typing:start", payload);
618
686
  } catch (e) {}
@@ -622,7 +690,11 @@ class KazeeAdapter {
622
690
  // has no equivalent (its chat action self-expires), so this is Kazee-only and
623
691
  // called via optional chaining from the core typing heartbeat cleanup.
624
692
  async typingStop(channelId) {
625
- try { this._socket?.emit?.("typing:stop", { chatId: channelId }); } catch (e) {}
693
+ const chatId = String(channelId);
694
+ // Never join just to stop: a stop from a socket that never started is a
695
+ // no-op server-side anyway, and joining here would race the teardown.
696
+ if (!this._joinedRooms.has(chatId)) return;
697
+ try { this._socket?.emit?.("typing:stop", { chatId }); } catch (e) {}
626
698
  }
627
699
 
628
700
  async downloadMedia(media) {
@@ -6,7 +6,10 @@ const { createJsonlDecoder } = require("./events");
6
6
  const { createClaudeParserState, normalizeClaudeEvent } = require("./claude-events");
7
7
  const { createClaudeHookTransport } = require("./claude-hook");
8
8
 
9
+ // claude-fable-5-1 requires Claude Code >= 2.1.251; older CLIs reject it with an
10
+ // API 400 rather than falling back, so a pod on a stale image will error on it.
9
11
  const CLAUDE_MODELS = Object.freeze([
12
+ "claude-fable-5-1",
10
13
  "claude-fable-5",
11
14
  "claude-opus-5",
12
15
  "claude-opus-4-8",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "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-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",
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-kazee-typing-join.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
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 && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-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-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-store-durability.js"
15
15
  },
16
16
  "files": [
@@ -45,6 +45,7 @@ assert.strictEqual(provider.label, "Claude Code");
45
45
  assert.strictEqual(provider.isAvailable(), true);
46
46
  assert.strictEqual(provider.executable(), "/fixture/claude");
47
47
  assert.strictEqual(provider.defaultModel(), "claude-default-fixture");
48
+ assert.ok(provider.modelChoices().includes("claude-fable-5-1"));
48
49
  assert.ok(provider.modelChoices().includes("claude-fable-5"));
49
50
  assert.ok(provider.modelChoices().includes("claude-sonnet-4-6"));
50
51
  assert.deepStrictEqual(provider.compatibilityStatus(), {
@@ -404,7 +404,26 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
404
404
  // anything), and the people/auth stores got atomic backed-up writes plus
405
405
  // cross-process locking, with hasOwner() failing CLOSED so a corrupt
406
406
  // auth.json can no longer read as an unclaimed pod.
407
- assert.strictEqual(pkg.version, "3.4.0", "release version must remain unchanged without explicit approval");
407
+ // 3.4.1 approved by Sumeet 2026-09-02 ("yes, but you tested it works?"):
408
+ // add claude-fable-5-1 to the Claude model picker. The ID is dash-form
409
+ // (claude-fable-5.1 does not exist) and it requires Claude Code >= 2.1.251 —
410
+ // an older CLI returns an API 400 rather than degrading, so the local CLI was
411
+ // taken 2.1.150 -> 2.1.258 first and the pod images (which install Claude Code
412
+ // unpinned) need a rebuild before the button is safe to tap there. Verified by
413
+ // building the real main invocation through the provider and spawning it: the
414
+ // API reported claude-fable-5-1 as the serving model, with claude-opus-4-8
415
+ // re-run through the same harness as a regression.
416
+ // 3.4.2 approved by Sumeet 2026-09-02 ("yeh", answering "Want me to cut
417
+ // v3.4.2 with this?") after he reported the typing/thinking indicator dead and
418
+ // asked whether Open Claudia or Kazee had changed: it was Kazee. chat-central
419
+ // 7aa9276 (2026-08-23, "authorize typing") made handleTypingStart return early
420
+ // unless the sending socket had JOINED the chat's room — and the bot only ever
421
+ // joined its own user room, so every indicator was dropped in silence for ten
422
+ // days while messages, which arrive by the personal-room fan-out, kept working.
423
+ // The adapter now joins the room and waits for the ACK before emitting:
424
+ // chat:join does an async participant lookup while typing:start is handled
425
+ // synchronously, so emitting both back-to-back loses the race.
426
+ assert.strictEqual(pkg.version, "3.4.2", "release version must remain unchanged without explicit approval");
408
427
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
409
428
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
410
429
  }