@inetafrica/open-claudia 3.4.1 → 3.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.4.3 — The chip knew it had started typing; nobody asked it in time
4
+
5
+ - **v3.4.2 restored the indicator and immediately exposed a second bug underneath it.** With delivery fixed, the chip reached the client — and then sat on "thinking" for the whole turn and stopped, never showing "typing". Same symptom, different layer: 3.4.2 was DELIVERY, this is CONTENT.
6
+ - **The phase was pushed by events but only read by a timer.** `onEvent` flips `state.thinkingPhase` correctly (`text_delta` → typing, `tool_start` → thinking), but `startTypingHeartbeat` re-read that flag on a 4s interval. An agentic turn spends nearly all of its time in tool calls, then streams its final answer and finishes — routinely inside one interval. So no tick ever sampled the flag while it was false, and the one state the user was waiting to see was the one state never transmitted. A slow, chatty turn would show it; the common case never did.
7
+ - **Transitions now push.** `state.typingTick` fires on an actual phase change, guarded so a long stream emits one frame rather than one per delta, and the stopper clears the pusher so a stale tick cannot outlive its turn (the same ownership discipline the heartbeat's stopper already used).
8
+ - `test-typing-phase-push.js` pins the sequence and was verified to FAIL against unmodified code with precisely the reported symptom — `["thinking"]` where `["thinking", "typing"]` was expected — rather than merely passing against the fix.
9
+
10
+ ## v3.4.2 — The bot was typing into a room it had never joined
11
+
12
+ - **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.
13
+ - **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.
14
+ - 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.
15
+
3
16
  ## v3.4.1 — Fable 5.1, and the CLI floor it quietly needs
4
17
 
5
18
  - **`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.
@@ -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) {
package/core/handlers.js CHANGED
@@ -1622,7 +1622,7 @@ register({
1622
1622
  if (state.streamInterval) { clearTimeout(state.streamInterval); state.streamInterval = null; }
1623
1623
  // Clearing the interval here orphans the run's own stopper (ownership
1624
1624
  // check makes it a no-op), so the explicit typing:stop must go out now.
1625
- if (state.typingHeartbeat) { clearInterval(state.typingHeartbeat); state.typingHeartbeat = null; state.thinkingPhase = false; }
1625
+ if (state.typingHeartbeat) { clearInterval(state.typingHeartbeat); state.typingHeartbeat = null; state.thinkingPhase = false; state.typingTick = null; }
1626
1626
  try { currentAdapter()?.typingStop?.(currentChannelId()); } catch (_) {}
1627
1627
  await sideCancellation;
1628
1628
  await send(`Cancelled.${queuedNote()}`);
@@ -1630,7 +1630,7 @@ register({
1630
1630
  // Turn is mid-recall/prompt-build — no process to kill yet. Flag it so
1631
1631
  // the runner's pre-spawn shouldAbort checkpoint bails before spawning.
1632
1632
  state.cancelRequested = true;
1633
- if (state.typingHeartbeat) { clearInterval(state.typingHeartbeat); state.typingHeartbeat = null; state.thinkingPhase = false; }
1633
+ if (state.typingHeartbeat) { clearInterval(state.typingHeartbeat); state.typingHeartbeat = null; state.thinkingPhase = false; state.typingTick = null; }
1634
1634
  try { currentAdapter()?.typingStop?.(currentChannelId()); } catch (_) {}
1635
1635
  await sideCancellation;
1636
1636
  await send(`Cancelled.${queuedNote()}`);
package/core/runner.js CHANGED
@@ -1742,12 +1742,18 @@ function startTypingHeartbeat(state, store) {
1742
1742
  if (state.typingHeartbeat) clearInterval(state.typingHeartbeat);
1743
1743
  const tick = () => adapter.typing(channelId, state.thinkingPhase ? "thinking" : undefined).catch(() => {});
1744
1744
  state.thinkingPhase = true;
1745
+ // Phase changes are PUSHED through this, not waited for. The interval only
1746
+ // samples every 4s, so a final answer that starts streaming and finishes
1747
+ // inside one interval would never emit a single "typing" frame — the chip
1748
+ // would sit on "thinking" for the whole turn and then stop.
1749
+ state.typingTick = tick;
1745
1750
  tick();
1746
1751
  const interval = setInterval(tick, 4000);
1747
1752
  state.typingHeartbeat = interval;
1748
1753
  return () => {
1749
1754
  if (state.typingHeartbeat !== interval) return;
1750
1755
  state.thinkingPhase = false;
1756
+ state.typingTick = null;
1751
1757
  clearInterval(interval);
1752
1758
  state.typingHeartbeat = null;
1753
1759
  try { adapter.typingStop?.(channelId); } catch (_) {}
@@ -1895,8 +1901,13 @@ function createForegroundService({ state, store, stopTyping, capture, ownsAdmiss
1895
1901
  onEvent(event, runContext) {
1896
1902
  // Streaming text reads as "typing"; tool work re-enters "thinking" so
1897
1903
  // the chip doesn't decay to a stale "typing" through long tool stretches.
1904
+ const phaseBefore = state.thinkingPhase;
1898
1905
  if (["text_delta", "text_final", "result", "error"].includes(event.type)) state.thinkingPhase = false;
1899
1906
  else if (event.type === "tool_start") state.thinkingPhase = true;
1907
+ // Emit on the TRANSITION rather than waiting for the next sample. Guarded
1908
+ // on an actual change, so a long text stream still sends one frame, not
1909
+ // one per delta.
1910
+ if (state.thinkingPhase !== phaseBefore) { try { state.typingTick?.(); } catch (_) {} }
1900
1911
  if (event.type === "tool_start" && ["Shell", "Bash"].includes(event.name)) {
1901
1912
  const command = typeof event.detail === "string" ? event.detail : event.detail?.command;
1902
1913
  if (command) { try { require("./tool-guard").noteRawOp(command); } catch (_) {} }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.4.1",
3
+ "version": "3.4.3",
4
4
  "description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "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-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-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": [
@@ -413,7 +413,27 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
413
413
  // building the real main invocation through the provider and spawning it: the
414
414
  // API reported claude-fable-5-1 as the serving model, with claude-opus-4-8
415
415
  // re-run through the same harness as a regression.
416
- assert.strictEqual(pkg.version, "3.4.1", "release version must remain unchanged without explicit approval");
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
+ // 3.4.3 approved by Sumeet 2026-09-02 ("yep") after v3.4.2 restored the
427
+ // indicator but he reported it "just saying thinking, never shifted to
428
+ // typing". Two separate bugs wore one symptom: 3.4.2 fixed DELIVERY (the room
429
+ // join) and was masking this one, which is CONTENT. startTypingHeartbeat only
430
+ // SAMPLED state.thinkingPhase every 4s, but an agentic turn sits in tool_start
431
+ // almost throughout and then streams its answer and finishes inside a single
432
+ // interval — so no tick ever landed while the phase was "typing". Phase
433
+ // changes now push through state.typingTick on the transition. Reproduced by a
434
+ // regression test that fails against unmodified code with exactly the reported
435
+ // symptom (["thinking"] where ["thinking","typing"] was expected).
436
+ assert.strictEqual(pkg.version, "3.4.3", "release version must remain unchanged without explicit approval");
417
437
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
418
438
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
419
439
  }