@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/cli.cjs +70 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +67 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +67 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1049,6 +1049,7 @@ var import_headless = require("@xterm/headless");
|
|
|
1049
1049
|
var PTY_COLS = 120;
|
|
1050
1050
|
var PTY_ROWS = 40;
|
|
1051
1051
|
var SCREEN_SCROLLBACK = 1e3;
|
|
1052
|
+
var REPLAY_MAX_LINES = SCREEN_SCROLLBACK + PTY_ROWS;
|
|
1052
1053
|
function digestBytes(s) {
|
|
1053
1054
|
const escaped = s.replace(new RegExp(String.fromCharCode(27), "g"), "\\x1b").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
|
|
1054
1055
|
if (escaped.length <= 200) return escaped;
|
|
@@ -1123,6 +1124,38 @@ var CODEX_ACTIVE_WRITER_CODE = "codex_active_writer";
|
|
|
1123
1124
|
var CODEX_USAGE_LIMIT_RE = /you(?:'ve| have) hit your usage limit/i;
|
|
1124
1125
|
var CODEX_USAGE_RESET_TIP_RE = /usage limit reset available/i;
|
|
1125
1126
|
var CODEX_RATE_LIMIT_MENU_RE = /approaching rate limits/i;
|
|
1127
|
+
var CODEX_COMMAND_APPROVAL_HEADING_RE = /^\s*E\s*X\s*E\s*C\s*$/i;
|
|
1128
|
+
var CODEX_COMMAND_APPROVAL_ENV_RE = /^\s*Environment:\s*.+/i;
|
|
1129
|
+
var CODEX_COMMAND_APPROVAL_REASON_RE = /^\s*Reason:\s*.+/i;
|
|
1130
|
+
var CODEX_COMMAND_APPROVAL_CONFIRM_RE = /(?:enter|return) to (?:confirm|approve)|press (?:enter|return) to (?:confirm|approve)/i;
|
|
1131
|
+
var CODEX_COMMAND_APPROVAL_YES_RE = /^\s*›?\s*\d+\.\s*yes\b/i;
|
|
1132
|
+
var CODEX_COMMAND_APPROVAL_NO_RE = /^\s*›?\s*\d+\.\s*no\b/i;
|
|
1133
|
+
function detectCodexCommandApproval(lines) {
|
|
1134
|
+
let heading = -1;
|
|
1135
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1136
|
+
if (CODEX_COMMAND_APPROVAL_HEADING_RE.test(lines[i])) heading = i;
|
|
1137
|
+
}
|
|
1138
|
+
if (heading < 0) return null;
|
|
1139
|
+
if (lines.some((line) => CODEX_USAGE_LIMIT_RE.test(line) || CODEX_RATE_LIMIT_MENU_RE.test(line))) {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
const card = lines.slice(heading);
|
|
1143
|
+
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))) {
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
const detail = card.filter((line) => {
|
|
1147
|
+
const trimmed = line.trim();
|
|
1148
|
+
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);
|
|
1149
|
+
}).join("\n");
|
|
1150
|
+
return {
|
|
1151
|
+
prompt: "Codex requests command approval",
|
|
1152
|
+
...detail ? { detail } : {},
|
|
1153
|
+
options: [
|
|
1154
|
+
{ index: 1, label: "Yes", answerKeys: "y" },
|
|
1155
|
+
{ index: 2, label: "No", answerKeys: "\x1B" }
|
|
1156
|
+
]
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1126
1159
|
function parseCodexNumberedOptions(lines) {
|
|
1127
1160
|
const options = [];
|
|
1128
1161
|
for (const line of lines) {
|
|
@@ -1303,6 +1336,10 @@ var CodexPtyRunner = class {
|
|
|
1303
1336
|
turnBusy = /* @__PURE__ */ new Set();
|
|
1304
1337
|
// Usage-limit / rate-limit menus — content key for deduped permission cards.
|
|
1305
1338
|
openBlockingPrompt = /* @__PURE__ */ new Map();
|
|
1339
|
+
// Command-approval cards are independent of quota cards: both use the
|
|
1340
|
+
// permission transport, but a repaint/removal of one must not suppress the
|
|
1341
|
+
// other detector's state.
|
|
1342
|
+
openCommandApproval = /* @__PURE__ */ new Map();
|
|
1306
1343
|
// Last codex.screen fingerprint per session — only emit when it changes so
|
|
1307
1344
|
// MCP boot redraw storms don't flood the log.
|
|
1308
1345
|
lastScreenLog = /* @__PURE__ */ new Map();
|
|
@@ -1535,7 +1572,7 @@ var CodexPtyRunner = class {
|
|
|
1535
1572
|
if (session.status === "idle") {
|
|
1536
1573
|
throw new Error(`Session is idle (no active PTY): ${sessionId}`);
|
|
1537
1574
|
}
|
|
1538
|
-
if (this.pendingReady.has(sessionId) || this.openGate.has(sessionId)) {
|
|
1575
|
+
if (this.pendingReady.has(sessionId) || this.openGate.has(sessionId) || this.openCommandApproval.has(sessionId)) {
|
|
1539
1576
|
const queue = this.queuedInputs.get(sessionId) ?? [];
|
|
1540
1577
|
queue.push(input);
|
|
1541
1578
|
this.queuedInputs.set(sessionId, queue);
|
|
@@ -1662,7 +1699,9 @@ var CodexPtyRunner = class {
|
|
|
1662
1699
|
// pendingReady (markReady drains it) — the gate-close path re-drives it for
|
|
1663
1700
|
// the ready-with-gate-open case.
|
|
1664
1701
|
flushQueuedInputs(sessionId) {
|
|
1665
|
-
if (this.openGate.has(sessionId) || this.pendingReady.has(sessionId))
|
|
1702
|
+
if (this.openGate.has(sessionId) || this.openCommandApproval.has(sessionId) || this.pendingReady.has(sessionId)) {
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1666
1705
|
const queue = this.queuedInputs.get(sessionId);
|
|
1667
1706
|
if (!queue || queue.length === 0) return;
|
|
1668
1707
|
this.queuedInputs.delete(sessionId);
|
|
@@ -1742,6 +1781,9 @@ var CodexPtyRunner = class {
|
|
|
1742
1781
|
if (this.openBlockingPrompt.delete(sessionId)) {
|
|
1743
1782
|
this.onPermissionChange?.(sessionId, null);
|
|
1744
1783
|
}
|
|
1784
|
+
if (this.openCommandApproval.delete(sessionId)) {
|
|
1785
|
+
this.onPermissionChange?.(sessionId, null);
|
|
1786
|
+
}
|
|
1745
1787
|
this.lastScreenLog.delete(sessionId);
|
|
1746
1788
|
}
|
|
1747
1789
|
getOutput(sessionId) {
|
|
@@ -1821,6 +1863,7 @@ var CodexPtyRunner = class {
|
|
|
1821
1863
|
this.lastChunkAt.clear();
|
|
1822
1864
|
this.turnBusy.clear();
|
|
1823
1865
|
this.openBlockingPrompt.clear();
|
|
1866
|
+
this.openCommandApproval.clear();
|
|
1824
1867
|
this.lastScreenLog.clear();
|
|
1825
1868
|
}
|
|
1826
1869
|
handleOutput(sessionId, data) {
|
|
@@ -1891,6 +1934,13 @@ var CodexPtyRunner = class {
|
|
|
1891
1934
|
this.onPermissionChange?.(sessionId, null);
|
|
1892
1935
|
this.flushQueuedInputs(sessionId);
|
|
1893
1936
|
}
|
|
1937
|
+
const commandApproval = gate ? null : detectCodexCommandApproval(lines);
|
|
1938
|
+
if (commandApproval) {
|
|
1939
|
+
this.handleCommandApproval(sessionId, commandApproval);
|
|
1940
|
+
} else if (this.openCommandApproval.delete(sessionId)) {
|
|
1941
|
+
this.onPermissionChange?.(sessionId, null);
|
|
1942
|
+
this.flushQueuedInputs(sessionId);
|
|
1943
|
+
}
|
|
1894
1944
|
const blocking = detectCodexBlockingPrompt(lines);
|
|
1895
1945
|
if (blocking) {
|
|
1896
1946
|
const elevateSoft = !blocking.soft || session.status === "running" && session.statusSource === "user-input" && !this.turnBusy.has(sessionId) && !this.pendingReady.has(sessionId);
|
|
@@ -1970,6 +2020,20 @@ var CodexPtyRunner = class {
|
|
|
1970
2020
|
this.markReady(sessionId, session, "quiet-fallback", "usage-limit");
|
|
1971
2021
|
}
|
|
1972
2022
|
}
|
|
2023
|
+
// Surface Codex's EXEC card through the existing permission event. The
|
|
2024
|
+
// rendered screen is authoritative: keying it by content suppresses TUI
|
|
2025
|
+
// repaint repeats and the absence path above clears it only after the card
|
|
2026
|
+
// has left the screen.
|
|
2027
|
+
handleCommandApproval(sessionId, approval) {
|
|
2028
|
+
const key = `${approval.detail ?? ""}\0${approval.options.map((o) => o.answerKeys).join(",")}`;
|
|
2029
|
+
if (this.openCommandApproval.get(sessionId) === key) return;
|
|
2030
|
+
this.openCommandApproval.set(sessionId, key);
|
|
2031
|
+
this.log.info(`[codex.command_approval] ${sessionId.slice(0, 8)}`, {
|
|
2032
|
+
event: "codex.command_approval",
|
|
2033
|
+
sessionId
|
|
2034
|
+
});
|
|
2035
|
+
this.onPermissionChange?.(sessionId, approval);
|
|
2036
|
+
}
|
|
1973
2037
|
// Answer a gate from the persisted remember-store, or surface it as a
|
|
1974
2038
|
// question card over the permission transport. Actioned once per session and
|
|
1975
2039
|
// gate type — repaints of the same dialog neither re-write nor re-broadcast.
|
|
@@ -12295,7 +12359,7 @@ function createApiDeps(deps) {
|
|
|
12295
12359
|
}
|
|
12296
12360
|
deps.addSessionSubscriber(msg.sessionId, ws);
|
|
12297
12361
|
if (deps.ptyManager.hasSession(msg.sessionId)) {
|
|
12298
|
-
const lines = await deps.ptyManager.getOutputLines(msg.sessionId,
|
|
12362
|
+
const lines = await deps.ptyManager.getOutputLines(msg.sessionId, REPLAY_MAX_LINES);
|
|
12299
12363
|
const userMessages = deps.ptyManager.getInputHistory(msg.sessionId);
|
|
12300
12364
|
ws.send(
|
|
12301
12365
|
JSON.stringify({
|