@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/cli.cjs +80 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +79 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +79 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1123,6 +1123,38 @@ var CODEX_ACTIVE_WRITER_CODE = "codex_active_writer";
|
|
|
1123
1123
|
var CODEX_USAGE_LIMIT_RE = /you(?:'ve| have) hit your usage limit/i;
|
|
1124
1124
|
var CODEX_USAGE_RESET_TIP_RE = /usage limit reset available/i;
|
|
1125
1125
|
var CODEX_RATE_LIMIT_MENU_RE = /approaching rate limits/i;
|
|
1126
|
+
var CODEX_COMMAND_APPROVAL_HEADING_RE = /^\s*E\s*X\s*E\s*C\s*$/i;
|
|
1127
|
+
var CODEX_COMMAND_APPROVAL_ENV_RE = /^\s*Environment:\s*.+/i;
|
|
1128
|
+
var CODEX_COMMAND_APPROVAL_REASON_RE = /^\s*Reason:\s*.+/i;
|
|
1129
|
+
var CODEX_COMMAND_APPROVAL_CONFIRM_RE = /(?:enter|return) to (?:confirm|approve)|press (?:enter|return) to (?:confirm|approve)/i;
|
|
1130
|
+
var CODEX_COMMAND_APPROVAL_YES_RE = /^\s*›?\s*\d+\.\s*yes\b/i;
|
|
1131
|
+
var CODEX_COMMAND_APPROVAL_NO_RE = /^\s*›?\s*\d+\.\s*no\b/i;
|
|
1132
|
+
function detectCodexCommandApproval(lines) {
|
|
1133
|
+
let heading = -1;
|
|
1134
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1135
|
+
if (CODEX_COMMAND_APPROVAL_HEADING_RE.test(lines[i])) heading = i;
|
|
1136
|
+
}
|
|
1137
|
+
if (heading < 0) return null;
|
|
1138
|
+
if (lines.some((line) => CODEX_USAGE_LIMIT_RE.test(line) || CODEX_RATE_LIMIT_MENU_RE.test(line))) {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
const card = lines.slice(heading);
|
|
1142
|
+
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))) {
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
const detail = card.filter((line) => {
|
|
1146
|
+
const trimmed = line.trim();
|
|
1147
|
+
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);
|
|
1148
|
+
}).join("\n");
|
|
1149
|
+
return {
|
|
1150
|
+
prompt: "Codex requests command approval",
|
|
1151
|
+
...detail ? { detail } : {},
|
|
1152
|
+
options: [
|
|
1153
|
+
{ index: 1, label: "Yes", answerKeys: "y" },
|
|
1154
|
+
{ index: 2, label: "No", answerKeys: "\x1B" }
|
|
1155
|
+
]
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1126
1158
|
function parseCodexNumberedOptions(lines) {
|
|
1127
1159
|
const options = [];
|
|
1128
1160
|
for (const line of lines) {
|
|
@@ -1303,6 +1335,10 @@ var CodexPtyRunner = class {
|
|
|
1303
1335
|
turnBusy = /* @__PURE__ */ new Set();
|
|
1304
1336
|
// Usage-limit / rate-limit menus — content key for deduped permission cards.
|
|
1305
1337
|
openBlockingPrompt = /* @__PURE__ */ new Map();
|
|
1338
|
+
// Command-approval cards are independent of quota cards: both use the
|
|
1339
|
+
// permission transport, but a repaint/removal of one must not suppress the
|
|
1340
|
+
// other detector's state.
|
|
1341
|
+
openCommandApproval = /* @__PURE__ */ new Map();
|
|
1306
1342
|
// Last codex.screen fingerprint per session — only emit when it changes so
|
|
1307
1343
|
// MCP boot redraw storms don't flood the log.
|
|
1308
1344
|
lastScreenLog = /* @__PURE__ */ new Map();
|
|
@@ -1535,7 +1571,7 @@ var CodexPtyRunner = class {
|
|
|
1535
1571
|
if (session.status === "idle") {
|
|
1536
1572
|
throw new Error(`Session is idle (no active PTY): ${sessionId}`);
|
|
1537
1573
|
}
|
|
1538
|
-
if (this.pendingReady.has(sessionId) || this.openGate.has(sessionId)) {
|
|
1574
|
+
if (this.pendingReady.has(sessionId) || this.openGate.has(sessionId) || this.openCommandApproval.has(sessionId)) {
|
|
1539
1575
|
const queue = this.queuedInputs.get(sessionId) ?? [];
|
|
1540
1576
|
queue.push(input);
|
|
1541
1577
|
this.queuedInputs.set(sessionId, queue);
|
|
@@ -1662,7 +1698,9 @@ var CodexPtyRunner = class {
|
|
|
1662
1698
|
// pendingReady (markReady drains it) — the gate-close path re-drives it for
|
|
1663
1699
|
// the ready-with-gate-open case.
|
|
1664
1700
|
flushQueuedInputs(sessionId) {
|
|
1665
|
-
if (this.openGate.has(sessionId) || this.pendingReady.has(sessionId))
|
|
1701
|
+
if (this.openGate.has(sessionId) || this.openCommandApproval.has(sessionId) || this.pendingReady.has(sessionId)) {
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1666
1704
|
const queue = this.queuedInputs.get(sessionId);
|
|
1667
1705
|
if (!queue || queue.length === 0) return;
|
|
1668
1706
|
this.queuedInputs.delete(sessionId);
|
|
@@ -1742,6 +1780,9 @@ var CodexPtyRunner = class {
|
|
|
1742
1780
|
if (this.openBlockingPrompt.delete(sessionId)) {
|
|
1743
1781
|
this.onPermissionChange?.(sessionId, null);
|
|
1744
1782
|
}
|
|
1783
|
+
if (this.openCommandApproval.delete(sessionId)) {
|
|
1784
|
+
this.onPermissionChange?.(sessionId, null);
|
|
1785
|
+
}
|
|
1745
1786
|
this.lastScreenLog.delete(sessionId);
|
|
1746
1787
|
}
|
|
1747
1788
|
getOutput(sessionId) {
|
|
@@ -1821,6 +1862,7 @@ var CodexPtyRunner = class {
|
|
|
1821
1862
|
this.lastChunkAt.clear();
|
|
1822
1863
|
this.turnBusy.clear();
|
|
1823
1864
|
this.openBlockingPrompt.clear();
|
|
1865
|
+
this.openCommandApproval.clear();
|
|
1824
1866
|
this.lastScreenLog.clear();
|
|
1825
1867
|
}
|
|
1826
1868
|
handleOutput(sessionId, data) {
|
|
@@ -1891,6 +1933,13 @@ var CodexPtyRunner = class {
|
|
|
1891
1933
|
this.onPermissionChange?.(sessionId, null);
|
|
1892
1934
|
this.flushQueuedInputs(sessionId);
|
|
1893
1935
|
}
|
|
1936
|
+
const commandApproval = gate ? null : detectCodexCommandApproval(lines);
|
|
1937
|
+
if (commandApproval) {
|
|
1938
|
+
this.handleCommandApproval(sessionId, commandApproval);
|
|
1939
|
+
} else if (this.openCommandApproval.delete(sessionId)) {
|
|
1940
|
+
this.onPermissionChange?.(sessionId, null);
|
|
1941
|
+
this.flushQueuedInputs(sessionId);
|
|
1942
|
+
}
|
|
1894
1943
|
const blocking = detectCodexBlockingPrompt(lines);
|
|
1895
1944
|
if (blocking) {
|
|
1896
1945
|
const elevateSoft = !blocking.soft || session.status === "running" && session.statusSource === "user-input" && !this.turnBusy.has(sessionId) && !this.pendingReady.has(sessionId);
|
|
@@ -1970,6 +2019,20 @@ var CodexPtyRunner = class {
|
|
|
1970
2019
|
this.markReady(sessionId, session, "quiet-fallback", "usage-limit");
|
|
1971
2020
|
}
|
|
1972
2021
|
}
|
|
2022
|
+
// Surface Codex's EXEC card through the existing permission event. The
|
|
2023
|
+
// rendered screen is authoritative: keying it by content suppresses TUI
|
|
2024
|
+
// repaint repeats and the absence path above clears it only after the card
|
|
2025
|
+
// has left the screen.
|
|
2026
|
+
handleCommandApproval(sessionId, approval) {
|
|
2027
|
+
const key = `${approval.detail ?? ""}\0${approval.options.map((o) => o.answerKeys).join(",")}`;
|
|
2028
|
+
if (this.openCommandApproval.get(sessionId) === key) return;
|
|
2029
|
+
this.openCommandApproval.set(sessionId, key);
|
|
2030
|
+
this.log.info(`[codex.command_approval] ${sessionId.slice(0, 8)}`, {
|
|
2031
|
+
event: "codex.command_approval",
|
|
2032
|
+
sessionId
|
|
2033
|
+
});
|
|
2034
|
+
this.onPermissionChange?.(sessionId, approval);
|
|
2035
|
+
}
|
|
1973
2036
|
// Answer a gate from the persisted remember-store, or surface it as a
|
|
1974
2037
|
// question card over the permission transport. Actioned once per session and
|
|
1975
2038
|
// gate type — repaints of the same dialog neither re-write nor re-broadcast.
|
|
@@ -12114,10 +12177,7 @@ function createLiveSessionOptions(deps) {
|
|
|
12114
12177
|
},
|
|
12115
12178
|
onLiveQuestionGone: (sessionId) => {
|
|
12116
12179
|
deps.pendingQuestionKey.delete(sessionId);
|
|
12117
|
-
|
|
12118
|
-
if (pq?.toolUseId.startsWith("screen:")) {
|
|
12119
|
-
deps.cancelPendingQuestion(sessionId);
|
|
12120
|
-
}
|
|
12180
|
+
deps.cancelPendingQuestion(sessionId);
|
|
12121
12181
|
},
|
|
12122
12182
|
onReady: (session) => {
|
|
12123
12183
|
const resp = deps.sessionStore.get(session.id, deps.ptyAttachedIds());
|
|
@@ -16067,6 +16127,19 @@ var StreamerServer = class {
|
|
|
16067
16127
|
ciphertext: sealed.ciphertext,
|
|
16068
16128
|
nonce: sealed.nonce,
|
|
16069
16129
|
ephemeralPublicKey: sealed.ephemeralPublicKey,
|
|
16130
|
+
// Advertises, does not dictate. This is what the server believes its
|
|
16131
|
+
// public address to be; the address the client talks to is the one its
|
|
16132
|
+
// user typed or scanned. Do not build anything on the assumption that a
|
|
16133
|
+
// client adopts this.
|
|
16134
|
+
//
|
|
16135
|
+
// Mobile used to resolve its server address as `publicUrl ?? typedUrl`,
|
|
16136
|
+
// so this field silently replaced what the user entered — and since the
|
|
16137
|
+
// reply is unauthenticated before E2EE, one response could relocate a
|
|
16138
|
+
// device permanently (TB-S-13). Fixed client-side in threadbase-mobile#720;
|
|
16139
|
+
// still sent, now recorded rather than applied.
|
|
16140
|
+
//
|
|
16141
|
+
// Released builds that predate that fix DO still adopt it, so changing
|
|
16142
|
+
// this value moves where old devices talk. It is not a free field.
|
|
16070
16143
|
publicUrl: this.publicUrl,
|
|
16071
16144
|
machineName: (0, import_os14.hostname)(),
|
|
16072
16145
|
...device && {
|