@threadbase-sh/streamer 1.55.1 → 1.55.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/dist/index.js CHANGED
@@ -1070,6 +1070,38 @@ var CODEX_ACTIVE_WRITER_CODE = "codex_active_writer";
1070
1070
  var CODEX_USAGE_LIMIT_RE = /you(?:'ve| have) hit your usage limit/i;
1071
1071
  var CODEX_USAGE_RESET_TIP_RE = /usage limit reset available/i;
1072
1072
  var CODEX_RATE_LIMIT_MENU_RE = /approaching rate limits/i;
1073
+ var CODEX_COMMAND_APPROVAL_HEADING_RE = /^\s*E\s*X\s*E\s*C\s*$/i;
1074
+ var CODEX_COMMAND_APPROVAL_ENV_RE = /^\s*Environment:\s*.+/i;
1075
+ var CODEX_COMMAND_APPROVAL_REASON_RE = /^\s*Reason:\s*.+/i;
1076
+ var CODEX_COMMAND_APPROVAL_CONFIRM_RE = /(?:enter|return) to (?:confirm|approve)|press (?:enter|return) to (?:confirm|approve)/i;
1077
+ var CODEX_COMMAND_APPROVAL_YES_RE = /^\s*›?\s*\d+\.\s*yes\b/i;
1078
+ var CODEX_COMMAND_APPROVAL_NO_RE = /^\s*›?\s*\d+\.\s*no\b/i;
1079
+ function detectCodexCommandApproval(lines) {
1080
+ let heading = -1;
1081
+ for (let i = 0; i < lines.length; i++) {
1082
+ if (CODEX_COMMAND_APPROVAL_HEADING_RE.test(lines[i])) heading = i;
1083
+ }
1084
+ if (heading < 0) return null;
1085
+ if (lines.some((line) => CODEX_USAGE_LIMIT_RE.test(line) || CODEX_RATE_LIMIT_MENU_RE.test(line))) {
1086
+ return null;
1087
+ }
1088
+ const card = lines.slice(heading);
1089
+ if (!card.some((line) => CODEX_COMMAND_APPROVAL_ENV_RE.test(line)) || !card.some((line) => CODEX_COMMAND_APPROVAL_REASON_RE.test(line)) || !card.some((line) => CODEX_COMMAND_APPROVAL_YES_RE.test(line)) || !card.some((line) => CODEX_COMMAND_APPROVAL_NO_RE.test(line)) || !card.some((line) => CODEX_COMMAND_APPROVAL_CONFIRM_RE.test(line))) {
1090
+ return null;
1091
+ }
1092
+ const detail = card.filter((line) => {
1093
+ const trimmed = line.trim();
1094
+ return trimmed.length > 0 && !CODEX_COMMAND_APPROVAL_HEADING_RE.test(line) && !CODEX_COMMAND_APPROVAL_YES_RE.test(line) && !CODEX_COMMAND_APPROVAL_NO_RE.test(line) && !CODEX_COMMAND_APPROVAL_CONFIRM_RE.test(line);
1095
+ }).join("\n");
1096
+ return {
1097
+ prompt: "Codex requests command approval",
1098
+ ...detail ? { detail } : {},
1099
+ options: [
1100
+ { index: 1, label: "Yes", answerKeys: "y" },
1101
+ { index: 2, label: "No", answerKeys: "\x1B" }
1102
+ ]
1103
+ };
1104
+ }
1073
1105
  function parseCodexNumberedOptions(lines) {
1074
1106
  const options = [];
1075
1107
  for (const line of lines) {
@@ -1250,6 +1282,10 @@ var CodexPtyRunner = class {
1250
1282
  turnBusy = /* @__PURE__ */ new Set();
1251
1283
  // Usage-limit / rate-limit menus — content key for deduped permission cards.
1252
1284
  openBlockingPrompt = /* @__PURE__ */ new Map();
1285
+ // Command-approval cards are independent of quota cards: both use the
1286
+ // permission transport, but a repaint/removal of one must not suppress the
1287
+ // other detector's state.
1288
+ openCommandApproval = /* @__PURE__ */ new Map();
1253
1289
  // Last codex.screen fingerprint per session — only emit when it changes so
1254
1290
  // MCP boot redraw storms don't flood the log.
1255
1291
  lastScreenLog = /* @__PURE__ */ new Map();
@@ -1482,7 +1518,7 @@ var CodexPtyRunner = class {
1482
1518
  if (session.status === "idle") {
1483
1519
  throw new Error(`Session is idle (no active PTY): ${sessionId}`);
1484
1520
  }
1485
- if (this.pendingReady.has(sessionId) || this.openGate.has(sessionId)) {
1521
+ if (this.pendingReady.has(sessionId) || this.openGate.has(sessionId) || this.openCommandApproval.has(sessionId)) {
1486
1522
  const queue = this.queuedInputs.get(sessionId) ?? [];
1487
1523
  queue.push(input);
1488
1524
  this.queuedInputs.set(sessionId, queue);
@@ -1609,7 +1645,9 @@ var CodexPtyRunner = class {
1609
1645
  // pendingReady (markReady drains it) — the gate-close path re-drives it for
1610
1646
  // the ready-with-gate-open case.
1611
1647
  flushQueuedInputs(sessionId) {
1612
- if (this.openGate.has(sessionId) || this.pendingReady.has(sessionId)) return;
1648
+ if (this.openGate.has(sessionId) || this.openCommandApproval.has(sessionId) || this.pendingReady.has(sessionId)) {
1649
+ return;
1650
+ }
1613
1651
  const queue = this.queuedInputs.get(sessionId);
1614
1652
  if (!queue || queue.length === 0) return;
1615
1653
  this.queuedInputs.delete(sessionId);
@@ -1689,6 +1727,9 @@ var CodexPtyRunner = class {
1689
1727
  if (this.openBlockingPrompt.delete(sessionId)) {
1690
1728
  this.onPermissionChange?.(sessionId, null);
1691
1729
  }
1730
+ if (this.openCommandApproval.delete(sessionId)) {
1731
+ this.onPermissionChange?.(sessionId, null);
1732
+ }
1692
1733
  this.lastScreenLog.delete(sessionId);
1693
1734
  }
1694
1735
  getOutput(sessionId) {
@@ -1768,6 +1809,7 @@ var CodexPtyRunner = class {
1768
1809
  this.lastChunkAt.clear();
1769
1810
  this.turnBusy.clear();
1770
1811
  this.openBlockingPrompt.clear();
1812
+ this.openCommandApproval.clear();
1771
1813
  this.lastScreenLog.clear();
1772
1814
  }
1773
1815
  handleOutput(sessionId, data) {
@@ -1838,6 +1880,13 @@ var CodexPtyRunner = class {
1838
1880
  this.onPermissionChange?.(sessionId, null);
1839
1881
  this.flushQueuedInputs(sessionId);
1840
1882
  }
1883
+ const commandApproval = gate ? null : detectCodexCommandApproval(lines);
1884
+ if (commandApproval) {
1885
+ this.handleCommandApproval(sessionId, commandApproval);
1886
+ } else if (this.openCommandApproval.delete(sessionId)) {
1887
+ this.onPermissionChange?.(sessionId, null);
1888
+ this.flushQueuedInputs(sessionId);
1889
+ }
1841
1890
  const blocking = detectCodexBlockingPrompt(lines);
1842
1891
  if (blocking) {
1843
1892
  const elevateSoft = !blocking.soft || session.status === "running" && session.statusSource === "user-input" && !this.turnBusy.has(sessionId) && !this.pendingReady.has(sessionId);
@@ -1917,6 +1966,20 @@ var CodexPtyRunner = class {
1917
1966
  this.markReady(sessionId, session, "quiet-fallback", "usage-limit");
1918
1967
  }
1919
1968
  }
1969
+ // Surface Codex's EXEC card through the existing permission event. The
1970
+ // rendered screen is authoritative: keying it by content suppresses TUI
1971
+ // repaint repeats and the absence path above clears it only after the card
1972
+ // has left the screen.
1973
+ handleCommandApproval(sessionId, approval) {
1974
+ const key = `${approval.detail ?? ""}\0${approval.options.map((o) => o.answerKeys).join(",")}`;
1975
+ if (this.openCommandApproval.get(sessionId) === key) return;
1976
+ this.openCommandApproval.set(sessionId, key);
1977
+ this.log.info(`[codex.command_approval] ${sessionId.slice(0, 8)}`, {
1978
+ event: "codex.command_approval",
1979
+ sessionId
1980
+ });
1981
+ this.onPermissionChange?.(sessionId, approval);
1982
+ }
1920
1983
  // Answer a gate from the persisted remember-store, or surface it as a
1921
1984
  // question card over the permission transport. Actioned once per session and
1922
1985
  // gate type — repaints of the same dialog neither re-write nor re-broadcast.
@@ -12071,10 +12134,7 @@ function createLiveSessionOptions(deps) {
12071
12134
  },
12072
12135
  onLiveQuestionGone: (sessionId) => {
12073
12136
  deps.pendingQuestionKey.delete(sessionId);
12074
- const pq = deps.pendingQuestions.get(sessionId);
12075
- if (pq?.toolUseId.startsWith("screen:")) {
12076
- deps.cancelPendingQuestion(sessionId);
12077
- }
12137
+ deps.cancelPendingQuestion(sessionId);
12078
12138
  },
12079
12139
  onReady: (session) => {
12080
12140
  const resp = deps.sessionStore.get(session.id, deps.ptyAttachedIds());
@@ -16024,6 +16084,19 @@ var StreamerServer = class {
16024
16084
  ciphertext: sealed.ciphertext,
16025
16085
  nonce: sealed.nonce,
16026
16086
  ephemeralPublicKey: sealed.ephemeralPublicKey,
16087
+ // Advertises, does not dictate. This is what the server believes its
16088
+ // public address to be; the address the client talks to is the one its
16089
+ // user typed or scanned. Do not build anything on the assumption that a
16090
+ // client adopts this.
16091
+ //
16092
+ // Mobile used to resolve its server address as `publicUrl ?? typedUrl`,
16093
+ // so this field silently replaced what the user entered — and since the
16094
+ // reply is unauthenticated before E2EE, one response could relocate a
16095
+ // device permanently (TB-S-13). Fixed client-side in threadbase-mobile#720;
16096
+ // still sent, now recorded rather than applied.
16097
+ //
16098
+ // Released builds that predate that fix DO still adopt it, so changing
16099
+ // this value moves where old devices talk. It is not a free field.
16027
16100
  publicUrl: this.publicUrl,
16028
16101
  machineName: hostname3(),
16029
16102
  ...device && {