@inetafrica/open-claudia 3.4.13 → 3.4.14

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.4.14
4
+
5
+ - Subscribe to managed call audio only when gateway-authenticated publisher metadata identifies a User. Exclude bots, the current bot's other feeds, and missing or malformed classifications before creating subscriber connections or speech streams.
6
+ - Preserve metadata through the initial room response and live publisher events. Managed calls use that authenticated classification; legacy calls retain their existing display-prefix exclusion because client-supplied metadata is untrusted.
7
+ - Exercise the actual session subscription and offer/answer path for allowed users and rejected bot, self and unknown feeds.
8
+
3
9
  ## 3.4.13
4
10
 
5
11
  - **Codex is now measured the way Claude is.** After each Codex turn the bot reads Codex's own per-request token log (`$CODEX_HOME/sessions/…/rollout-*-<thread>.jsonl`) and uses the last request's size as the live context, instead of the turn's input summed over every tool call. That sum (33M on one turn) tripped auto-compact after almost every substantial Codex turn — 8 needless compactions on 2026-09-05, each a summary run plus a cold restart — while Codex's real window occupancy never exceeded 245k of its 258k window. Codex sessions now compact at 80% of the window Codex reports.
@@ -256,8 +256,8 @@ class KazeeCallSession {
256
256
  }
257
257
  for (const publisher of data.publishers || []) {
258
258
  const feedId = Number(publisher.id);
259
- if (Number.isSafeInteger(feedId) && feedId > 0 && feedId !== this.publisherId && !this.subscribers.has(feedId) && !String(publisher.display || "").startsWith("open-claudia:")) {
260
- await this._subscribe(feedId, publisher.display);
259
+ if (Number.isSafeInteger(feedId) && feedId > 0 && feedId !== this.publisherId && !this.subscribers.has(feedId)) {
260
+ await this._subscribe(feedId, publisher.display, publisher.metadata);
261
261
  }
262
262
  }
263
263
  if (data.videoroom === "joined") {
@@ -357,8 +357,21 @@ class KazeeCallSession {
357
357
  this.speak(this.greeting).catch((error) => console.error("Kazee call greeting:", error.message));
358
358
  }
359
359
 
360
- async _subscribe(feedId, display) {
361
- if (this.subscribers.has(feedId)) return;
360
+ async _subscribe(feedId, display, metadata) {
361
+ if (this.closed || feedId === this.publisherId || this.subscribers.has(feedId)) return;
362
+ if (this.managedRoom) {
363
+ // Only the enforced gateway owns this metadata. Display remains an email
364
+ // for presentation, and cannot tell us whether a publisher is another bot.
365
+ // Missing or malformed classification must not start STT on an unknown feed.
366
+ const principal = metadata?.kazee;
367
+ if (!principal || Array.isArray(principal) || principal.version !== 1 ||
368
+ typeof principal.userId !== "string" || !/^[a-f\d]{24}$/i.test(principal.userId) ||
369
+ !["User", "Bot"].includes(principal.principalKind) || principal.principalKind === "Bot" ||
370
+ principal.userId.toLowerCase() === this.botUserId.toLowerCase()) return;
371
+ } else if (String(display || "").startsWith("open-claudia:")) {
372
+ // Legacy/direct rooms allow client metadata, so it is not a trusted source.
373
+ return;
374
+ }
362
375
  // Reserve the slot before the first await: the join response and the async
363
376
  // publishers event both list the same feed, and while attach() is in
364
377
  // flight the second frame passes the caller's has() check and subscribes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.4.13",
3
+ "version": "3.4.14",
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": {
@@ -462,10 +462,11 @@ async function testManagedRoomCredentials() {
462
462
  assert.strictEqual(session.janus.options.token, "root-token");
463
463
  assert.strictEqual(session.janus.calls[0].token, "publisher-token");
464
464
  await session._consumePublisherFrame({ plugindata: { data: { publishers: [{ id: 41, display: 'canonical-bot@calling.invalid' }, { id: 42, display: 'open-claudia:legacy-bot' }] } } });
465
- assert.strictEqual(session.subscribers.size, 0, 'own joined publisher ID is excluded independently of canonical display; legacy bot exclusion remains');
465
+ assert.strictEqual(session.subscribers.size, 0, 'own joined publisher ID and unclassified managed feeds are excluded independently of display');
466
466
 
467
467
  session.roomTokenExpiresAt = Date.now() - 1;
468
- await Promise.all([session._subscribe(17, "owner"), session._subscribe(18, "other")]);
468
+ const userMetadata = { kazee: { version: 1, userId: 'a'.repeat(24), principalKind: 'User' } };
469
+ await Promise.all([session._subscribe(17, "owner", userMetadata), session._subscribe(18, "other", userMetadata)]);
469
470
  const subJoin = session.janus.calls.find(body => body.ptype === "subscriber");
470
471
  assert.strictEqual(subJoin.token, "subscriber-token");
471
472
  assert.strictEqual(subJoin.private_id, 999);
@@ -484,6 +485,66 @@ async function testManagedRoomCredentials() {
484
485
  assert.ok(!session.janus.calls.some(body => ["create", "destroy"].includes(body.request)), "server allocated room ownership never transfers to the bot");
485
486
  }
486
487
 
488
+ async function testTrustedPublisherMetadata() {
489
+ const botId = 'b'.repeat(24), userId = 'a'.repeat(24);
490
+ const metadata = (principalKind, id = userId) => ({ kazee: { version: 1, userId: id, principalKind } });
491
+ const publisher = (id, meta, display = 'speaker@calling.invalid') => ({ id, display, metadata: meta });
492
+ let initial = [];
493
+ class MetadataJanus extends FakeJanus {
494
+ constructor() { super(); this.calls = []; this.listeners = new Map(); }
495
+ onHandle(handle, listener) { this.listeners.set(handle, listener); }
496
+ async message(handle, body) {
497
+ this.calls.push({ handle, ...body });
498
+ if (body.request === 'join' && body.ptype === 'publisher') return { plugindata: { data: { videoroom: 'joined', id: 41, private_id: 999, publishers: initial } } };
499
+ if (body.request === 'configure') return { jsep: { type: 'answer', sdp: 'fixture' } };
500
+ if (body.request === 'join' && body.ptype === 'subscriber') return { plugindata: { data: { videoroom: 'attached' } }, jsep: { type: 'offer', sdp: 'fixture' } };
501
+ return {};
502
+ }
503
+ }
504
+ const make = managedRoom => new KazeeCallSession({ roomId: 7, chatId: 'chat', callerId: userId, botUserId: botId, managedRoom,
505
+ roomToken: 'fixture', roomTokenExpiresAt: Date.now() + 300000,
506
+ iceServers: [{ urls: 'turn:relay:3478', username: 'fixture', credential: 'fixture' }], iceCredentialsExpiresAt: Date.now() + 3600000,
507
+ JanusClientImpl: MetadataJanus, PeerConnectionImpl: FakePC, SpeechStreamImpl: FakeSpeechStream });
508
+ const invalid = [undefined, null, {}, { kazee: null }, { kazee: [] },
509
+ { kazee: { version: 2, userId, principalKind: 'User' } }, { kazee: { version: '1', userId, principalKind: 'User' } },
510
+ metadata('Human'), metadata('bot'), metadata(undefined), metadata('User', 'invalid'), { kazee: { version: 1, principalKind: 'User' } },
511
+ { kazee: { version: 1, userId: { $ne: null }, principalKind: 'User' } }];
512
+ initial = [publisher(41, metadata('User')), publisher(42, metadata('Bot', 'c'.repeat(24)), 'other-bot@calling.invalid'),
513
+ publisher(43, metadata('User')), publisher(44, metadata('User', botId.toUpperCase())),
514
+ ...invalid.map((meta, i) => publisher(100 + i, meta))];
515
+ const managed = make(true);
516
+ try {
517
+ await managed.start();
518
+ assert.deepStrictEqual([...managed.subscribers.keys()], [43], 'initial join reaches real subscription only for a classified human, never self/bot/unknown');
519
+ assert.strictEqual(managed.subscribers.get(43).started, true, 'authorized human reaches the actual subscriber offer/answer path');
520
+ const emit = publishers => managed.janus.listeners.get(managed.publisherHandle)({ janus: 'event', plugindata: { data: { publishers } } });
521
+ await emit([publisher(50, metadata('Bot', 'd'.repeat(24)), 'another-bot@calling.invalid'),
522
+ publisher(51, metadata('User'), 'open-claudia:display-is-not-managed-authority'),
523
+ ...invalid.map((meta, i) => publisher(200 + i, meta))]);
524
+ assert.deepStrictEqual([...managed.subscribers.keys()], [43, 51], 'live publisher metadata survives forwarding; managed classification never falls back to display prefix');
525
+ assert.strictEqual(managed.subscribers.get(51).display, 'open-claudia:display-is-not-managed-authority', 'presentation remains unchanged');
526
+ const handles = managed.janus.handles;
527
+ await managed._subscribe(60, 'unclassified@calling.invalid');
528
+ await managed._subscribe(61, 'bot@calling.invalid', metadata('Bot'));
529
+ await managed._subscribe(41, 'own@calling.invalid', metadata('User'));
530
+ assert.strictEqual(managed.janus.handles, handles, 'the subscription method itself fails closed before creating handles or speech streams');
531
+ assert.deepStrictEqual(managed.janus.calls.filter(body => body.ptype === 'subscriber').map(body => body.feed), [43, 51]);
532
+ } finally { await managed.close(); }
533
+
534
+ initial = [publisher(41, metadata('User')), publisher(42, metadata('User'), 'open-claudia:legacy-bot'),
535
+ publisher(43, metadata('Bot'), 'human@calling.invalid')];
536
+ const legacy = make(false);
537
+ try {
538
+ await legacy.start();
539
+ assert.deepStrictEqual([...legacy.subscribers.keys()], [43], 'legacy metadata cannot hide a human or impersonate one for a prefixed bot; own numeric feed remains excluded');
540
+ await legacy.janus.listeners.get(legacy.publisherHandle)({ janus: 'event', plugindata: { data: { publishers: [
541
+ publisher(50, metadata('Bot'), 'untrusted-kind@calling.invalid'), publisher(51, metadata('User'), 'open-claudia:other-bot'),
542
+ publisher(52, undefined, 'older-client@calling.invalid'),
543
+ ] } } });
544
+ assert.deepStrictEqual([...legacy.subscribers.keys()], [43, 50, 52], 'legacy live publishers keep display-prefix behavior regardless of spoofed or absent metadata');
545
+ } finally { await legacy.close(); }
546
+ }
547
+
487
548
  async function testAssignedRelayTransportLifetime() {
488
549
  const { RTCPeerConnection } = require('werift');
489
550
  const originalExpiry = Date.now() + 3600000;
@@ -538,6 +599,7 @@ async function testAssignedRelayTransportLifetime() {
538
599
  }
539
600
 
540
601
  (async () => {
602
+ await testTrustedPublisherMetadata();
541
603
  await testAssignedRelayTransportLifetime();
542
604
  await testManagedRoomCredentials();
543
605
  await testSpeakSerialization();
@@ -464,9 +464,10 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
464
464
  // tool_name "Bash" and the deny gate caught the probe, so the gate no longer
465
465
  // requires unified_exec off. Push approved by Sumeet before release.
466
466
  // 3.4.12: Sumeet approved the full calling rollout on 2026-09-05; preserve assigned media configuration across canary changes.
467
+ // 3.4.14: bot managed-call consumer (subscribe only to gateway-authenticated User publishers) — part of the Janus calling plan Sumeet approved on 2026-09-05.
467
468
  // 3.4.13: Codex usage accounting fix — Sumeet approved the three-bug fix on 2026-09-05 ("Yes");
468
469
  // prepared 2026-09-06 for his release go. Rollout-based Codex context, per-turn ledger, sub-agent + quota accounting.
469
- assert.strictEqual(pkg.version, "3.4.13", "release version must remain unchanged without explicit approval");
470
+ assert.strictEqual(pkg.version, "3.4.14", "release version must remain unchanged without explicit approval");
470
471
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
471
472
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
472
473
  }