@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 +13 -0
- package/channels/kazee/adapter.js +75 -3
- package/core/handlers.js +2 -2
- package/core/runner.js +11 -0
- package/package.json +2 -2
- package/test-provider-language.js +21 -1
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) =>
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
}
|