@threadbase-sh/streamer 1.58.0 → 1.58.1

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/dist/index.cjs CHANGED
@@ -1305,11 +1305,19 @@ var CodexPtyRunner = class {
1305
1305
  onStatusChange;
1306
1306
  onPhaseChange;
1307
1307
  onReady;
1308
- // Broadcasts Codex's blocking startup gates (directory trust, hooks review)
1309
- // as question cards; null dismisses the card once the gate leaves the screen.
1308
+ // Every Codex prompt the client can answer — startup gates (directory trust,
1309
+ // hooks review), command approvals, and the rate-limit model picker is
1310
+ // broadcast through this one channel; null dismisses the card once the prompt
1311
+ // leaves the screen.
1312
+ //
1313
+ // Deliberately NOT onLiveQuestion/onLiveQuestionGone, which is Claude's
1314
+ // AskUserQuestion transport. Both channels land on the same mobile
1315
+ // QuestionCard, and the permission one is the correct fit for Codex: its menus
1316
+ // are answered by the option's real on-screen number (parseCodexNumberedOptions
1317
+ // emits `answerKeys: "2\r"`), which is exactly what `permissionIndices` carries
1318
+ // and what AskUserQuestion's down-arrow-count model cannot express. Wiring the
1319
+ // question channel as well would be a second path to the same card.
1310
1320
  onPermissionChange;
1311
- onLiveQuestion;
1312
- onLiveQuestionGone;
1313
1321
  onUserMessage;
1314
1322
  log;
1315
1323
  // Tracks sessions whose PTY has spawned but Codex hasn't yet reached its
@@ -1359,8 +1367,6 @@ var CodexPtyRunner = class {
1359
1367
  this.onPhaseChange = options.onPhaseChange;
1360
1368
  this.onReady = options.onReady;
1361
1369
  this.onPermissionChange = options.onPermissionChange;
1362
- this.onLiveQuestion = options.onLiveQuestion;
1363
- this.onLiveQuestionGone = options.onLiveQuestionGone;
1364
1370
  this.onUserMessage = options.onUserMessage;
1365
1371
  this.log = options.logger ?? getLogger("codex-pty");
1366
1372
  }
@@ -11391,7 +11397,37 @@ var PairTokenStore = class {
11391
11397
  expiresInSeconds: Math.floor(this.ttlMs / 1e3)
11392
11398
  };
11393
11399
  }
11400
+ /**
11401
+ * Whether `consume` would succeed right now, WITHOUT spending the token.
11402
+ *
11403
+ * Exists so a caller can reject a bad token before doing any work, and still
11404
+ * spend the token only once the work has succeeded. A pair token is
11405
+ * single-use and lives 180 seconds, so spending it on a request that then
11406
+ * fails costs the user a whole new QR — and, worse, makes their retry
11407
+ * indistinguishable from an attacker replaying a photographed code, which is
11408
+ * the one signal `design.md` §2.6 designates as replay detection.
11409
+ *
11410
+ * Advisory, not a reservation: it takes no lock and holds nothing. The
11411
+ * authoritative answer is still `consume`'s.
11412
+ */
11413
+ verify(token) {
11414
+ const result = this.check(token);
11415
+ return result.ok ? { ok: true } : result;
11416
+ }
11394
11417
  consume(token) {
11418
+ const result = this.check(token);
11419
+ if (!result.ok) return result;
11420
+ result.record.used = true;
11421
+ return { ok: true };
11422
+ }
11423
+ /**
11424
+ * The shared predicate behind `verify` and `consume`.
11425
+ *
11426
+ * One implementation on purpose: two copies of "is this token usable" is two
11427
+ * places for the expiry or single-use rule to drift, and a drift in this
11428
+ * direction fails open.
11429
+ */
11430
+ check(token) {
11395
11431
  const record2 = this.current;
11396
11432
  if (!record2 || record2.token !== token) return { ok: false, reason: "unknown" };
11397
11433
  if (Date.now() > record2.expiresAt) {
@@ -11399,8 +11435,7 @@ var PairTokenStore = class {
11399
11435
  return { ok: false, reason: "expired" };
11400
11436
  }
11401
11437
  if (record2.used) return { ok: false, reason: "used" };
11402
- record2.used = true;
11403
- return { ok: true };
11438
+ return { ok: true, record: record2 };
11404
11439
  }
11405
11440
  peek() {
11406
11441
  return this.current;
@@ -16190,9 +16225,9 @@ var StreamerServer = class {
16190
16225
  json(res, 400, { error: "Missing token or clientPublicKey" });
16191
16226
  return;
16192
16227
  }
16193
- const result = this.pairTokens.consume(token);
16194
- if (!result.ok) {
16195
- json(res, 401, { error: `Pair token ${result.reason}` });
16228
+ const precheck = this.pairTokens.verify(token);
16229
+ if (!precheck.ok) {
16230
+ json(res, 401, { error: `Pair token ${precheck.reason}` });
16196
16231
  return;
16197
16232
  }
16198
16233
  let sealed;
@@ -16203,6 +16238,11 @@ var StreamerServer = class {
16203
16238
  json(res, 400, { error: message });
16204
16239
  return;
16205
16240
  }
16241
+ const result = this.pairTokens.consume(token);
16242
+ if (!result.ok) {
16243
+ json(res, 401, { error: `Pair token ${result.reason}` });
16244
+ return;
16245
+ }
16206
16246
  const ts = (/* @__PURE__ */ new Date()).toISOString();
16207
16247
  this.log.info(`[pair] token exchanged from ${ip} at ${ts}`, {
16208
16248
  event: "pair.token_exchanged",