@inetafrica/open-claudia 3.1.16 → 3.1.17
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,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v3.1.17 — Call bridge joins through the caller's room-setup race (fixes "Call declined")
|
|
4
|
+
|
|
5
|
+
- Answering a Kazee web call could fail deterministically fast: the caller creates the Janus videoroom during its own dial-time setup, and the bot answers quicker than any human — its `join` hit Janus before the room existed, got 426 "No such room", and the whole call was rejected as `connection_failed` (surfacing as "Call declined" in the web UI).
|
|
6
|
+
- `session.start()` now polls the publisher join through transient 426 (10 attempts × 500ms), mirroring the web receiver's own retry loop in `CallContext.startVideoRoom`. Persistent 426 still fails the call.
|
|
7
|
+
- Janus error frames now carry `error.code` alongside the message for future discrimination.
|
|
8
|
+
- New assertions: join retries through transient no-such-room and still gives up when the room never appears.
|
|
9
|
+
|
|
3
10
|
## v3.1.16 — CI-deflake: no live network in unit tests, CI-safe enforcer deadlines
|
|
4
11
|
|
|
5
12
|
- Identical bot behaviour to v3.1.15 (which never shipped: its pipeline died twice on loaded-runner test flakes — the tag is retired unretagged per release hygiene).
|
|
@@ -15,6 +15,8 @@ const PREBUFFER_FRAMES = 12; // 240ms of pre-onset audio so first syllables reac
|
|
|
15
15
|
const LIMIT_CHECK_MS = 10_000;
|
|
16
16
|
const DEFAULT_MAX_CALL_MS = 30 * 60_000;
|
|
17
17
|
const DEFAULT_IDLE_MS = 2 * 60_000;
|
|
18
|
+
const DEFAULT_JOIN_RETRIES = 10;
|
|
19
|
+
const DEFAULT_JOIN_RETRY_DELAY_MS = 500;
|
|
18
20
|
const MAX_DURATION_GOODBYE = "We've hit the call time limit, so I'll hang up now. Message me any time.";
|
|
19
21
|
const IDLE_GOODBYE = "I haven't heard anything for a while, so I'll hang up now. Message me any time.";
|
|
20
22
|
|
|
@@ -35,6 +37,8 @@ class KazeeCallSession {
|
|
|
35
37
|
this.synthesize = synthesize;
|
|
36
38
|
this.maxCallMs = Number(maxCallMs ?? process.env.KAZEE_CALL_MAX_MS) || DEFAULT_MAX_CALL_MS;
|
|
37
39
|
this.idleMs = Number(idleMs ?? process.env.KAZEE_CALL_IDLE_MS) || DEFAULT_IDLE_MS;
|
|
40
|
+
this.joinRetries = DEFAULT_JOIN_RETRIES;
|
|
41
|
+
this.joinRetryDelayMs = DEFAULT_JOIN_RETRY_DELAY_MS;
|
|
38
42
|
this.codec = new OpusAudioCodec();
|
|
39
43
|
this.publisher = null;
|
|
40
44
|
this.publisherHandle = null;
|
|
@@ -58,9 +62,20 @@ class KazeeCallSession {
|
|
|
58
62
|
await this.janus.connect();
|
|
59
63
|
this.publisherHandle = await this.janus.attach();
|
|
60
64
|
this.janus.onHandle(this.publisherHandle, (frame) => this._onPublisherEvent(frame));
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
// The caller creates the Janus room during its own call setup, which can
|
|
66
|
+
// land a beat after the invite reaches us — and a bot answers faster than
|
|
67
|
+
// any human. Poll join through transient 426 "No such room" like the web
|
|
68
|
+
// receiver's retry loop in CallContext.startVideoRoom.
|
|
69
|
+
let joined;
|
|
70
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
71
|
+
joined = await this.janus.message(this.publisherHandle, {
|
|
72
|
+
request: "join", ptype: "publisher", room: this.roomId, display: `open-claudia:${this.botUserId}`,
|
|
73
|
+
});
|
|
74
|
+
const data = joined?.plugindata?.data || {};
|
|
75
|
+
const noRoom = Number(data.error_code) === 426 || /no such room/i.test(String(data.error || ""));
|
|
76
|
+
if (!noRoom || attempt >= this.joinRetries || this.closed) break;
|
|
77
|
+
await wait(this.joinRetryDelayMs);
|
|
78
|
+
}
|
|
64
79
|
await this._consumePublisherFrame(joined);
|
|
65
80
|
this._startedAt = Date.now();
|
|
66
81
|
this._lastHeard = this._startedAt;
|
|
@@ -76,7 +91,11 @@ class KazeeCallSession {
|
|
|
76
91
|
|
|
77
92
|
async _consumePublisherFrame(frame) {
|
|
78
93
|
const data = frame?.plugindata?.data || {};
|
|
79
|
-
if (data.error)
|
|
94
|
+
if (data.error) {
|
|
95
|
+
const error = new Error(data.error);
|
|
96
|
+
if (data.error_code != null) error.code = Number(data.error_code);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
80
99
|
for (const publisher of data.publishers || []) {
|
|
81
100
|
const feedId = Number(publisher.id);
|
|
82
101
|
if (!this.subscribers.has(feedId) && !String(publisher.display || "").startsWith("open-claudia:")) {
|
|
@@ -135,7 +154,11 @@ class KazeeCallSession {
|
|
|
135
154
|
const sub = this.subscribers.get(feedId);
|
|
136
155
|
if (!sub) return;
|
|
137
156
|
const data = frame?.plugindata?.data || {};
|
|
138
|
-
if (data.error)
|
|
157
|
+
if (data.error) {
|
|
158
|
+
const error = new Error(data.error);
|
|
159
|
+
if (data.error_code != null) error.code = Number(data.error_code);
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
139
162
|
if (frame.jsep && !sub.started) {
|
|
140
163
|
sub.started = true;
|
|
141
164
|
await sub.pc.setRemoteDescription(frame.jsep);
|
package/package.json
CHANGED
|
@@ -202,6 +202,32 @@ async function testMaxDuration() {
|
|
|
202
202
|
assert.strictEqual(ended[0], "max-duration", "cap closes the call even mid-conversation");
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
async function testJoinRetriesNoSuchRoom() {
|
|
206
|
+
const { session } = makeSession({});
|
|
207
|
+
session.joinRetryDelayMs = 1;
|
|
208
|
+
let joins = 0;
|
|
209
|
+
session.janus.message = async (_handle, body) => {
|
|
210
|
+
if (body?.request !== "join") return {};
|
|
211
|
+
joins += 1;
|
|
212
|
+
if (joins < 3) return { plugindata: { data: { videoroom: "event", error_code: 426, error: "No such room (7)" } } };
|
|
213
|
+
return {};
|
|
214
|
+
};
|
|
215
|
+
await session.start();
|
|
216
|
+
assert.strictEqual(joins, 3, "join retried through transient no-such-room");
|
|
217
|
+
await session.close();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function testJoinGivesUpAfterRetries() {
|
|
221
|
+
const { session } = makeSession({});
|
|
222
|
+
session.joinRetryDelayMs = 1;
|
|
223
|
+
session.joinRetries = 2;
|
|
224
|
+
session.janus.message = async (_handle, body) => body?.request === "join"
|
|
225
|
+
? { plugindata: { data: { videoroom: "event", error_code: 426, error: "No such room (7)" } } }
|
|
226
|
+
: {};
|
|
227
|
+
await assert.rejects(() => session.start(), /No such room/, "persistent 426 still fails the call");
|
|
228
|
+
await session.close();
|
|
229
|
+
}
|
|
230
|
+
|
|
205
231
|
async function testCloseResolvesSpeech() {
|
|
206
232
|
const { session } = makeSession({
|
|
207
233
|
synthesize: async () => ({ pcm: Buffer.alloc(WEBRTC_SAMPLES * 2 * 100), sampleRate: 48000 }),
|
|
@@ -226,6 +252,8 @@ async function testCloseResolvesSpeech() {
|
|
|
226
252
|
await testIdleTimeout();
|
|
227
253
|
await testIdleBargeInKeepsCall();
|
|
228
254
|
await testMaxDuration();
|
|
255
|
+
await testJoinRetriesNoSuchRoom();
|
|
256
|
+
await testJoinGivesUpAfterRetries();
|
|
229
257
|
await testCloseResolvesSpeech();
|
|
230
258
|
console.log("kazee call session: ok");
|
|
231
259
|
})().catch((error) => { console.error(error); process.exitCode = 1; });
|
|
@@ -222,7 +222,12 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
222
222
|
// deflake — heal-guard test stubs its live api.telegram.org probe and isolates
|
|
223
223
|
// CONFIG_DIR; enforcer probe deadlines raised to CI-safe values (2000→8000ms
|
|
224
224
|
// backstop, 2500→5000ms deliberate-timeout boot headroom). No runtime changes.
|
|
225
|
-
|
|
225
|
+
// 3.1.17 approved by Sumeet 2026-07-23 (same calls-directive authority; his web
|
|
226
|
+
// call to an upgraded bot showed "Call declined"): the call bridge now retries
|
|
227
|
+
// the Janus publisher join through transient 426 "No such room" — the caller
|
|
228
|
+
// creates the room at dial time and the bot answers faster than any human, so
|
|
229
|
+
// the single naked join lost the race deterministically.
|
|
230
|
+
assert.strictEqual(pkg.version, "3.1.17", "release version must remain unchanged without explicit approval");
|
|
226
231
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
227
232
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
228
233
|
}
|