@threadbase-sh/streamer 1.55.2 → 1.55.4

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
@@ -996,6 +996,7 @@ import { Terminal } from "@xterm/headless";
996
996
  var PTY_COLS = 120;
997
997
  var PTY_ROWS = 40;
998
998
  var SCREEN_SCROLLBACK = 1e3;
999
+ var REPLAY_MAX_LINES = SCREEN_SCROLLBACK + PTY_ROWS;
999
1000
  function digestBytes(s) {
1000
1001
  const escaped = s.replace(new RegExp(String.fromCharCode(27), "g"), "\\x1b").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
1001
1002
  if (escaped.length <= 200) return escaped;
@@ -1070,6 +1071,38 @@ var CODEX_ACTIVE_WRITER_CODE = "codex_active_writer";
1070
1071
  var CODEX_USAGE_LIMIT_RE = /you(?:'ve| have) hit your usage limit/i;
1071
1072
  var CODEX_USAGE_RESET_TIP_RE = /usage limit reset available/i;
1072
1073
  var CODEX_RATE_LIMIT_MENU_RE = /approaching rate limits/i;
1074
+ var CODEX_COMMAND_APPROVAL_HEADING_RE = /^\s*E\s*X\s*E\s*C\s*$/i;
1075
+ var CODEX_COMMAND_APPROVAL_ENV_RE = /^\s*Environment:\s*.+/i;
1076
+ var CODEX_COMMAND_APPROVAL_REASON_RE = /^\s*Reason:\s*.+/i;
1077
+ var CODEX_COMMAND_APPROVAL_CONFIRM_RE = /(?:enter|return) to (?:confirm|approve)|press (?:enter|return) to (?:confirm|approve)/i;
1078
+ var CODEX_COMMAND_APPROVAL_YES_RE = /^\s*›?\s*\d+\.\s*yes\b/i;
1079
+ var CODEX_COMMAND_APPROVAL_NO_RE = /^\s*›?\s*\d+\.\s*no\b/i;
1080
+ function detectCodexCommandApproval(lines) {
1081
+ let heading = -1;
1082
+ for (let i = 0; i < lines.length; i++) {
1083
+ if (CODEX_COMMAND_APPROVAL_HEADING_RE.test(lines[i])) heading = i;
1084
+ }
1085
+ if (heading < 0) return null;
1086
+ if (lines.some((line) => CODEX_USAGE_LIMIT_RE.test(line) || CODEX_RATE_LIMIT_MENU_RE.test(line))) {
1087
+ return null;
1088
+ }
1089
+ const card = lines.slice(heading);
1090
+ 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))) {
1091
+ return null;
1092
+ }
1093
+ const detail = card.filter((line) => {
1094
+ const trimmed = line.trim();
1095
+ 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);
1096
+ }).join("\n");
1097
+ return {
1098
+ prompt: "Codex requests command approval",
1099
+ ...detail ? { detail } : {},
1100
+ options: [
1101
+ { index: 1, label: "Yes", answerKeys: "y" },
1102
+ { index: 2, label: "No", answerKeys: "\x1B" }
1103
+ ]
1104
+ };
1105
+ }
1073
1106
  function parseCodexNumberedOptions(lines) {
1074
1107
  const options = [];
1075
1108
  for (const line of lines) {
@@ -1250,6 +1283,10 @@ var CodexPtyRunner = class {
1250
1283
  turnBusy = /* @__PURE__ */ new Set();
1251
1284
  // Usage-limit / rate-limit menus — content key for deduped permission cards.
1252
1285
  openBlockingPrompt = /* @__PURE__ */ new Map();
1286
+ // Command-approval cards are independent of quota cards: both use the
1287
+ // permission transport, but a repaint/removal of one must not suppress the
1288
+ // other detector's state.
1289
+ openCommandApproval = /* @__PURE__ */ new Map();
1253
1290
  // Last codex.screen fingerprint per session — only emit when it changes so
1254
1291
  // MCP boot redraw storms don't flood the log.
1255
1292
  lastScreenLog = /* @__PURE__ */ new Map();
@@ -1482,7 +1519,7 @@ var CodexPtyRunner = class {
1482
1519
  if (session.status === "idle") {
1483
1520
  throw new Error(`Session is idle (no active PTY): ${sessionId}`);
1484
1521
  }
1485
- if (this.pendingReady.has(sessionId) || this.openGate.has(sessionId)) {
1522
+ if (this.pendingReady.has(sessionId) || this.openGate.has(sessionId) || this.openCommandApproval.has(sessionId)) {
1486
1523
  const queue = this.queuedInputs.get(sessionId) ?? [];
1487
1524
  queue.push(input);
1488
1525
  this.queuedInputs.set(sessionId, queue);
@@ -1609,7 +1646,9 @@ var CodexPtyRunner = class {
1609
1646
  // pendingReady (markReady drains it) — the gate-close path re-drives it for
1610
1647
  // the ready-with-gate-open case.
1611
1648
  flushQueuedInputs(sessionId) {
1612
- if (this.openGate.has(sessionId) || this.pendingReady.has(sessionId)) return;
1649
+ if (this.openGate.has(sessionId) || this.openCommandApproval.has(sessionId) || this.pendingReady.has(sessionId)) {
1650
+ return;
1651
+ }
1613
1652
  const queue = this.queuedInputs.get(sessionId);
1614
1653
  if (!queue || queue.length === 0) return;
1615
1654
  this.queuedInputs.delete(sessionId);
@@ -1689,6 +1728,9 @@ var CodexPtyRunner = class {
1689
1728
  if (this.openBlockingPrompt.delete(sessionId)) {
1690
1729
  this.onPermissionChange?.(sessionId, null);
1691
1730
  }
1731
+ if (this.openCommandApproval.delete(sessionId)) {
1732
+ this.onPermissionChange?.(sessionId, null);
1733
+ }
1692
1734
  this.lastScreenLog.delete(sessionId);
1693
1735
  }
1694
1736
  getOutput(sessionId) {
@@ -1768,6 +1810,7 @@ var CodexPtyRunner = class {
1768
1810
  this.lastChunkAt.clear();
1769
1811
  this.turnBusy.clear();
1770
1812
  this.openBlockingPrompt.clear();
1813
+ this.openCommandApproval.clear();
1771
1814
  this.lastScreenLog.clear();
1772
1815
  }
1773
1816
  handleOutput(sessionId, data) {
@@ -1838,6 +1881,13 @@ var CodexPtyRunner = class {
1838
1881
  this.onPermissionChange?.(sessionId, null);
1839
1882
  this.flushQueuedInputs(sessionId);
1840
1883
  }
1884
+ const commandApproval = gate ? null : detectCodexCommandApproval(lines);
1885
+ if (commandApproval) {
1886
+ this.handleCommandApproval(sessionId, commandApproval);
1887
+ } else if (this.openCommandApproval.delete(sessionId)) {
1888
+ this.onPermissionChange?.(sessionId, null);
1889
+ this.flushQueuedInputs(sessionId);
1890
+ }
1841
1891
  const blocking = detectCodexBlockingPrompt(lines);
1842
1892
  if (blocking) {
1843
1893
  const elevateSoft = !blocking.soft || session.status === "running" && session.statusSource === "user-input" && !this.turnBusy.has(sessionId) && !this.pendingReady.has(sessionId);
@@ -1917,6 +1967,20 @@ var CodexPtyRunner = class {
1917
1967
  this.markReady(sessionId, session, "quiet-fallback", "usage-limit");
1918
1968
  }
1919
1969
  }
1970
+ // Surface Codex's EXEC card through the existing permission event. The
1971
+ // rendered screen is authoritative: keying it by content suppresses TUI
1972
+ // repaint repeats and the absence path above clears it only after the card
1973
+ // has left the screen.
1974
+ handleCommandApproval(sessionId, approval) {
1975
+ const key = `${approval.detail ?? ""}\0${approval.options.map((o) => o.answerKeys).join(",")}`;
1976
+ if (this.openCommandApproval.get(sessionId) === key) return;
1977
+ this.openCommandApproval.set(sessionId, key);
1978
+ this.log.info(`[codex.command_approval] ${sessionId.slice(0, 8)}`, {
1979
+ event: "codex.command_approval",
1980
+ sessionId
1981
+ });
1982
+ this.onPermissionChange?.(sessionId, approval);
1983
+ }
1920
1984
  // Answer a gate from the persisted remember-store, or surface it as a
1921
1985
  // question card over the permission transport. Actioned once per session and
1922
1986
  // gate type — repaints of the same dialog neither re-write nor re-broadcast.
@@ -12252,7 +12316,7 @@ function createApiDeps(deps) {
12252
12316
  }
12253
12317
  deps.addSessionSubscriber(msg.sessionId, ws);
12254
12318
  if (deps.ptyManager.hasSession(msg.sessionId)) {
12255
- const lines = await deps.ptyManager.getOutputLines(msg.sessionId, 200);
12319
+ const lines = await deps.ptyManager.getOutputLines(msg.sessionId, REPLAY_MAX_LINES);
12256
12320
  const userMessages = deps.ptyManager.getInputHistory(msg.sessionId);
12257
12321
  ws.send(
12258
12322
  JSON.stringify({