@threadbase-sh/streamer 1.53.1 → 1.54.0
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 +113 -6
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +102 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -1
- package/dist/index.d.ts +97 -1
- package/dist/index.js +102 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -103179,12 +103179,15 @@ function detectCodexBlockingPrompt(lines) {
|
|
|
103179
103179
|
function codexStatusBarLine(lines) {
|
|
103180
103180
|
return [...lines].reverse().find((l) => l.trim() !== "") ?? "";
|
|
103181
103181
|
}
|
|
103182
|
-
function
|
|
103182
|
+
function codexScreenPreTurn(lines) {
|
|
103183
103183
|
const screenText = lines.join("\n");
|
|
103184
103184
|
if (CODEX_HOOKS_GATE_REGEX.test(screenText) || CODEX_TRUST_GATE_REGEX.test(screenText)) {
|
|
103185
103185
|
return true;
|
|
103186
103186
|
}
|
|
103187
|
-
|
|
103187
|
+
return CODEX_MCP_BOOT_RE.test(screenText);
|
|
103188
|
+
}
|
|
103189
|
+
function codexScreenBlocksComposer(lines) {
|
|
103190
|
+
if (codexScreenPreTurn(lines)) return true;
|
|
103188
103191
|
return CODEX_BUSY_STATUS_RE.test(codexStatusBarLine(lines));
|
|
103189
103192
|
}
|
|
103190
103193
|
function codexScreenShowsReady(lines) {
|
|
@@ -103230,12 +103233,13 @@ function gateCard(gate, lines) {
|
|
|
103230
103233
|
]
|
|
103231
103234
|
};
|
|
103232
103235
|
}
|
|
103233
|
-
var CODEX_PROMPT_READY_TEXT, CODEX_BUSY_STATUS_RE, CODEX_MCP_BOOT_RE, CODEX_TRUST_GATE_REGEX, CODEX_HOOKS_GATE_REGEX, CODEX_ACTIVE_WRITER_RE, CODEX_ACTIVE_WRITER_CODE, CODEX_USAGE_LIMIT_RE, CODEX_USAGE_RESET_TIP_RE, CODEX_RATE_LIMIT_MENU_RE;
|
|
103236
|
+
var CODEX_PROMPT_READY_TEXT, CODEX_BUSY_STATUS_RE, CODEX_WORKING_STATUS_RE, CODEX_MCP_BOOT_RE, CODEX_TRUST_GATE_REGEX, CODEX_HOOKS_GATE_REGEX, CODEX_ACTIVE_WRITER_RE, CODEX_ACTIVE_WRITER_CODE, CODEX_USAGE_LIMIT_RE, CODEX_USAGE_RESET_TIP_RE, CODEX_RATE_LIMIT_MENU_RE;
|
|
103234
103237
|
var init_codexScreen = __esm({
|
|
103235
103238
|
"src/services/questions/codexScreen.ts"() {
|
|
103236
103239
|
"use strict";
|
|
103237
103240
|
CODEX_PROMPT_READY_TEXT = "Ready";
|
|
103238
103241
|
CODEX_BUSY_STATUS_RE = /\b(?:Starting|Working)\b/;
|
|
103242
|
+
CODEX_WORKING_STATUS_RE = /\bWorking\b/;
|
|
103239
103243
|
CODEX_MCP_BOOT_RE = /Booting MCP|Starting MCP servers/i;
|
|
103240
103244
|
CODEX_TRUST_GATE_REGEX = /trust the contents/i;
|
|
103241
103245
|
CODEX_HOOKS_GATE_REGEX = /hooks need review/i;
|
|
@@ -120480,6 +120484,27 @@ var init_codexGateAnswers = __esm({
|
|
|
120480
120484
|
}
|
|
120481
120485
|
});
|
|
120482
120486
|
|
|
120487
|
+
// src/services/questions/parseAgentPhase.ts
|
|
120488
|
+
function codexPhase(lines) {
|
|
120489
|
+
if (codexScreenPreTurn(lines)) return null;
|
|
120490
|
+
return CODEX_WORKING_STATUS_RE.test(codexStatusBarLine(lines)) ? "working" : null;
|
|
120491
|
+
}
|
|
120492
|
+
function claudePhase(_lines) {
|
|
120493
|
+
return null;
|
|
120494
|
+
}
|
|
120495
|
+
function parseAgentPhase(lines, provider) {
|
|
120496
|
+
if (provider === CODEX_CLI_PROVIDER) return codexPhase(lines);
|
|
120497
|
+
if (provider === CLAUDE_CODE_PROVIDER) return claudePhase(lines);
|
|
120498
|
+
return null;
|
|
120499
|
+
}
|
|
120500
|
+
var init_parseAgentPhase = __esm({
|
|
120501
|
+
"src/services/questions/parseAgentPhase.ts"() {
|
|
120502
|
+
"use strict";
|
|
120503
|
+
init_providers();
|
|
120504
|
+
init_codexScreen();
|
|
120505
|
+
}
|
|
120506
|
+
});
|
|
120507
|
+
|
|
120483
120508
|
// src/utils/debounce.ts
|
|
120484
120509
|
function debounce(fn, waitMs) {
|
|
120485
120510
|
let timer = null;
|
|
@@ -120534,7 +120559,11 @@ function toPublicSession(s3) {
|
|
|
120534
120559
|
...s3.lastActivityAt != null && { lastActivityAt: s3.lastActivityAt },
|
|
120535
120560
|
...s3.statusSource != null && { statusSource: s3.statusSource },
|
|
120536
120561
|
...s3.statusUpdatedAt != null && { statusUpdatedAt: s3.statusUpdatedAt },
|
|
120537
|
-
...s3.filePath != null && { filePath: s3.filePath }
|
|
120562
|
+
...s3.filePath != null && { filePath: s3.filePath },
|
|
120563
|
+
// Unconditional — see PTYManager's toPublicSession: a streamer re-adopting
|
|
120564
|
+
// a surviving pty-host's sessions mid-turn has no other source for the
|
|
120565
|
+
// phase, and the host's change guard will not re-emit it.
|
|
120566
|
+
subStatus: s3.subStatus ?? null
|
|
120538
120567
|
};
|
|
120539
120568
|
}
|
|
120540
120569
|
var import_crypto9, import_fs25, import_path25, OUTPUT_BUFFER_MAX, INPUT_HISTORY_MAX, QUIET_DETECT_MS, CODEX_READY_FALLBACK_MS, SUBMIT_BYTES, CODEX_SUBMIT_DELAY_MS, CODEX_SUBMIT_MAX_WAIT_MS, CODEX_SUBMIT_STALE_MS, CodexPtyRunner;
|
|
@@ -120550,6 +120579,7 @@ var init_codex_pty_runner = __esm({
|
|
|
120550
120579
|
init_pty_shared();
|
|
120551
120580
|
init_codexGateAnswers();
|
|
120552
120581
|
init_codexScreen();
|
|
120582
|
+
init_parseAgentPhase();
|
|
120553
120583
|
init_debounce();
|
|
120554
120584
|
OUTPUT_BUFFER_MAX = 65536;
|
|
120555
120585
|
INPUT_HISTORY_MAX = 50;
|
|
@@ -120563,6 +120593,7 @@ var init_codex_pty_runner = __esm({
|
|
|
120563
120593
|
sessions = /* @__PURE__ */ new Map();
|
|
120564
120594
|
onOutput;
|
|
120565
120595
|
onStatusChange;
|
|
120596
|
+
onPhaseChange;
|
|
120566
120597
|
onReady;
|
|
120567
120598
|
// Broadcasts Codex's blocking startup gates (directory trust, hooks review)
|
|
120568
120599
|
// as question cards; null dismisses the card once the gate leaves the screen.
|
|
@@ -120611,6 +120642,7 @@ var init_codex_pty_runner = __esm({
|
|
|
120611
120642
|
constructor(options = {}) {
|
|
120612
120643
|
this.onOutput = options.onOutput;
|
|
120613
120644
|
this.onStatusChange = options.onStatusChange;
|
|
120645
|
+
this.onPhaseChange = options.onPhaseChange;
|
|
120614
120646
|
this.onReady = options.onReady;
|
|
120615
120647
|
this.onPermissionChange = options.onPermissionChange;
|
|
120616
120648
|
this.onLiveQuestion = options.onLiveQuestion;
|
|
@@ -121178,6 +121210,9 @@ var init_codex_pty_runner = __esm({
|
|
|
121178
121210
|
);
|
|
121179
121211
|
return;
|
|
121180
121212
|
}
|
|
121213
|
+
if (session.status === "running") {
|
|
121214
|
+
this.setPhase(sessionId, session, parseAgentPhase(lines, CODEX_CLI_PROVIDER));
|
|
121215
|
+
}
|
|
121181
121216
|
const gate = CODEX_HOOKS_GATE_REGEX.test(screenText) ? "hooks" : CODEX_TRUST_GATE_REGEX.test(screenText) ? "trust" : null;
|
|
121182
121217
|
if (gate) {
|
|
121183
121218
|
this.handleGate(sessionId, session, gate, lines);
|
|
@@ -121292,11 +121327,25 @@ var init_codex_pty_runner = __esm({
|
|
|
121292
121327
|
});
|
|
121293
121328
|
this.onPermissionChange?.(sessionId, card);
|
|
121294
121329
|
}
|
|
121330
|
+
/**
|
|
121331
|
+
* Record the agent's phase and notify only on a real change. The guard is
|
|
121332
|
+
* load-bearing, not an optimisation: detectScreenState runs on every chunk,
|
|
121333
|
+
* so an unguarded setter would fire the WS frame behind this several times a
|
|
121334
|
+
* second for a whole turn while reporting the same value. Same contract as
|
|
121335
|
+
* PTYManager.setPhase — the two runners deliberately stay separate classes.
|
|
121336
|
+
*/
|
|
121337
|
+
setPhase(sessionId, session, phase) {
|
|
121338
|
+
const next = phase ?? null;
|
|
121339
|
+
if ((session.subStatus ?? null) === next) return;
|
|
121340
|
+
session.subStatus = next;
|
|
121341
|
+
this.onPhaseChange?.(sessionId, next);
|
|
121342
|
+
}
|
|
121295
121343
|
markReady(sessionId, session, source, reason) {
|
|
121296
121344
|
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
121297
121345
|
session.status = "waiting_input";
|
|
121298
121346
|
session.statusSource = source;
|
|
121299
121347
|
session.statusUpdatedAt = /* @__PURE__ */ new Date();
|
|
121348
|
+
this.setPhase(sessionId, session, null);
|
|
121300
121349
|
this.log.info(`[codex.ready] ${sessionId.slice(0, 8)} ${reason}`, {
|
|
121301
121350
|
event: "codex.ready",
|
|
121302
121351
|
sessionId,
|
|
@@ -121394,7 +121443,7 @@ var PTY_HOST_PROTOCOL_VERSION, SESSION_DATE_FIELDS, LineDecoder;
|
|
|
121394
121443
|
var init_protocol = __esm({
|
|
121395
121444
|
"src/pty-host/protocol.ts"() {
|
|
121396
121445
|
"use strict";
|
|
121397
|
-
PTY_HOST_PROTOCOL_VERSION =
|
|
121446
|
+
PTY_HOST_PROTOCOL_VERSION = 3;
|
|
121398
121447
|
SESSION_DATE_FIELDS = [
|
|
121399
121448
|
"startedAt",
|
|
121400
121449
|
"completedAt",
|
|
@@ -121624,6 +121673,9 @@ var init_remote_session_runner = __esm({
|
|
|
121624
121673
|
case "permission-change":
|
|
121625
121674
|
this.options.onPermissionChange?.(event.sessionId, event.gate);
|
|
121626
121675
|
break;
|
|
121676
|
+
case "phase-change":
|
|
121677
|
+
this.options.onPhaseChange?.(event.sessionId, event.phase);
|
|
121678
|
+
break;
|
|
121627
121679
|
case "live-question":
|
|
121628
121680
|
this.options.onLiveQuestion?.(event.sessionId, event.questions);
|
|
121629
121681
|
break;
|
|
@@ -121884,7 +121936,12 @@ function toPublicSession2(s3) {
|
|
|
121884
121936
|
...s3.statusUpdatedAt != null && { statusUpdatedAt: s3.statusUpdatedAt },
|
|
121885
121937
|
...s3.filePath != null && { filePath: s3.filePath },
|
|
121886
121938
|
...s3.sessionName != null && { sessionName: s3.sessionName },
|
|
121887
|
-
...s3.firstMessageText != null && { firstMessageText: s3.firstMessageText }
|
|
121939
|
+
...s3.firstMessageText != null && { firstMessageText: s3.firstMessageText },
|
|
121940
|
+
// Unconditional: this shape crosses the pty-host boundary, and a streamer
|
|
121941
|
+
// re-adopting a surviving host's sessions mid-turn has no other source for
|
|
121942
|
+
// the phase — no snapshot or replay event carries it, and setPhase's change
|
|
121943
|
+
// guard means the host will never re-emit it for the rest of that turn.
|
|
121944
|
+
subStatus: s3.subStatus ?? null
|
|
121888
121945
|
};
|
|
121889
121946
|
}
|
|
121890
121947
|
var import_crypto10, import_fs26, import_path26, OUTPUT_BUFFER_MAX2, INPUT_HISTORY_MAX2, CLAUDE_PROMPT_MARKERS, QUIET_DETECT_MS2, OSC_TAIL_CHARS, SCRAPE_THROTTLE_MS, CLAUDE_READY_FALLBACK_MS, SUBMIT_BYTES2, SUBMIT_DELAY_MS, SUBMIT_MAX_WAIT_MS, PTYManager;
|
|
@@ -121902,6 +121959,7 @@ var init_pty_manager = __esm({
|
|
|
121902
121959
|
init_detectPermissionGate();
|
|
121903
121960
|
init_detectQuestionFromScreen();
|
|
121904
121961
|
init_detectShellPrompt();
|
|
121962
|
+
init_parseAgentPhase();
|
|
121905
121963
|
init_debounce();
|
|
121906
121964
|
init_deriveSessionName();
|
|
121907
121965
|
OUTPUT_BUFFER_MAX2 = 65536;
|
|
@@ -121920,6 +121978,7 @@ var init_pty_manager = __esm({
|
|
|
121920
121978
|
onStatusChange;
|
|
121921
121979
|
onReady;
|
|
121922
121980
|
onPermissionChange;
|
|
121981
|
+
onPhaseChange;
|
|
121923
121982
|
onLiveQuestion;
|
|
121924
121983
|
onLiveQuestionGone;
|
|
121925
121984
|
onUserMessage;
|
|
@@ -121981,6 +122040,7 @@ var init_pty_manager = __esm({
|
|
|
121981
122040
|
this.onStatusChange = options.onStatusChange;
|
|
121982
122041
|
this.onReady = options.onReady;
|
|
121983
122042
|
this.onPermissionChange = options.onPermissionChange;
|
|
122043
|
+
this.onPhaseChange = options.onPhaseChange;
|
|
121984
122044
|
this.onLiveQuestion = options.onLiveQuestion;
|
|
121985
122045
|
this.onLiveQuestionGone = options.onLiveQuestionGone;
|
|
121986
122046
|
this.onUserMessage = options.onUserMessage;
|
|
@@ -122487,6 +122547,10 @@ var init_pty_manager = __esm({
|
|
|
122487
122547
|
}
|
|
122488
122548
|
this.lastDetectAt.set(sessionId, nowMs);
|
|
122489
122549
|
const lines = await this.getOutputLines(sessionId, 60);
|
|
122550
|
+
const phaseSession = this.sessions.get(sessionId);
|
|
122551
|
+
if (phaseSession?.status === "running") {
|
|
122552
|
+
this.setPhase(sessionId, phaseSession, parseAgentPhase(lines, CLAUDE_CODE_PROVIDER));
|
|
122553
|
+
}
|
|
122490
122554
|
const askFooterOnScreen = lines.some((l) => /Enter to select/i.test(l));
|
|
122491
122555
|
if (oscPermission || hasAskFooter || askFooterOnScreen) {
|
|
122492
122556
|
this.log.debug?.(`[pty.prompt_detect] ${sessionId.slice(0, 8)} trigger`, {
|
|
@@ -122630,12 +122694,27 @@ var init_pty_manager = __esm({
|
|
|
122630
122694
|
}
|
|
122631
122695
|
// Transition a session from "running" to "waiting_input", clear pendingReady,
|
|
122632
122696
|
// and flush any queued input. Idempotent: callers can invoke at any chunk.
|
|
122697
|
+
/**
|
|
122698
|
+
* Record the agent's phase and notify only on a real change.
|
|
122699
|
+
*
|
|
122700
|
+
* The change guard is load-bearing, not an optimisation: the scrape pass runs
|
|
122701
|
+
* on every chunk, so an unguarded setter would fire the callback — and the
|
|
122702
|
+
* WS frame behind it — several times a second for the entire duration of a
|
|
122703
|
+
* turn while reporting the same value.
|
|
122704
|
+
*/
|
|
122705
|
+
setPhase(sessionId, session, phase) {
|
|
122706
|
+
const next = phase ?? null;
|
|
122707
|
+
if ((session.subStatus ?? null) === next) return;
|
|
122708
|
+
session.subStatus = next;
|
|
122709
|
+
this.onPhaseChange?.(sessionId, next);
|
|
122710
|
+
}
|
|
122633
122711
|
markReady(sessionId, session, source, reason) {
|
|
122634
122712
|
this.clearReadyFallback(sessionId);
|
|
122635
122713
|
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
122636
122714
|
session.status = "waiting_input";
|
|
122637
122715
|
session.statusSource = source;
|
|
122638
122716
|
session.statusUpdatedAt = /* @__PURE__ */ new Date();
|
|
122717
|
+
this.setPhase(sessionId, session, null);
|
|
122639
122718
|
const elapsedMs = Date.now() - (this.firstChunkAt.get(sessionId) ?? Date.now());
|
|
122640
122719
|
this.log.info(`[pty.ready] ${sessionId.slice(0, 8)} ${reason} (elapsed=${elapsedMs}ms)`, {
|
|
122641
122720
|
event: "pty.ready",
|
|
@@ -128105,6 +128184,7 @@ var init_host = __esm({
|
|
|
128105
128184
|
onStatusChange: (session) => this.emit({ type: "event", event: "status-change", session }),
|
|
128106
128185
|
onReady: (session) => this.emit({ type: "event", event: "ready", session }),
|
|
128107
128186
|
onPermissionChange: (sessionId, gate) => this.emit({ type: "event", event: "permission-change", sessionId, gate }),
|
|
128187
|
+
onPhaseChange: (sessionId, phase) => this.emit({ type: "event", event: "phase-change", sessionId, phase }),
|
|
128108
128188
|
onLiveQuestion: (sessionId, questions) => this.emit({ type: "event", event: "live-question", sessionId, questions }),
|
|
128109
128189
|
onLiveQuestionGone: (sessionId) => this.emit({ type: "event", event: "live-question-gone", sessionId }),
|
|
128110
128190
|
onUserMessage: (sessionId, text, ts2) => this.emit({ type: "event", event: "user-message", sessionId, text, ts: ts2 })
|
|
@@ -145287,6 +145367,12 @@ function conversationToResumableSession(c) {
|
|
|
145287
145367
|
branch: c.branch ?? void 0,
|
|
145288
145368
|
lastOutput: "",
|
|
145289
145369
|
elapsedMs: 0,
|
|
145370
|
+
// No PTY behind a cached conversation, so there is no phase — emitted
|
|
145371
|
+
// explicitly for the same reason as in managedToResponse/discoveredToResponse:
|
|
145372
|
+
// the client merges session frames, so an absent key keeps the previous
|
|
145373
|
+
// value and the indicator latches. `GET /api/sessions/:id` serves this
|
|
145374
|
+
// shape whenever the id is a conversation rather than a live session.
|
|
145375
|
+
subStatus: null,
|
|
145290
145376
|
promptCount: c.messageCount,
|
|
145291
145377
|
startedAt: c.lastActivity,
|
|
145292
145378
|
completedAt: null,
|
|
@@ -149714,6 +149800,15 @@ function createLiveSessionOptions(deps) {
|
|
|
149714
149800
|
seq
|
|
149715
149801
|
});
|
|
149716
149802
|
},
|
|
149803
|
+
onPhaseChange: (sessionId, phase) => {
|
|
149804
|
+
deps.sessionStore.updateManaged(sessionId, { subStatus: phase });
|
|
149805
|
+
deps.wsHub.broadcastToClients(deps.sessionSubscribers.get(sessionId) ?? [], {
|
|
149806
|
+
type: "session_phase",
|
|
149807
|
+
sessionId,
|
|
149808
|
+
phase,
|
|
149809
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
149810
|
+
});
|
|
149811
|
+
},
|
|
149717
149812
|
onUserMessage: (sessionId, text, ts2) => {
|
|
149718
149813
|
deps.wsHub.broadcastToClients(deps.sessionSubscribers.get(sessionId) ?? [], {
|
|
149719
149814
|
type: "user_message",
|
|
@@ -151786,6 +151881,7 @@ var SessionStore = class {
|
|
|
151786
151881
|
const session = this.managed.get(sessionId);
|
|
151787
151882
|
if (!session) return null;
|
|
151788
151883
|
Object.assign(session, updates);
|
|
151884
|
+
if (updates.status != null && updates.status !== "running") session.subStatus = null;
|
|
151789
151885
|
return session;
|
|
151790
151886
|
}
|
|
151791
151887
|
removeManaged(sessionId) {
|
|
@@ -151966,6 +152062,13 @@ function managedToResponse(s3, ptyAttached) {
|
|
|
151966
152062
|
promptCount: s3.promptCount,
|
|
151967
152063
|
startedAt: s3.startedAt.toISOString(),
|
|
151968
152064
|
completedAt: s3.completedAt?.toISOString() ?? null,
|
|
152065
|
+
// Unconditional, like completedAt above — do NOT move this into the
|
|
152066
|
+
// `...(x != null && { x })` block below. That guard uses loose `!=`, which
|
|
152067
|
+
// catches null as well as undefined, and would turn an explicit "no phase"
|
|
152068
|
+
// back into an absent key. The client merges session frames, so an absent
|
|
152069
|
+
// key keeps the previous value and the indicator latches on a finished
|
|
152070
|
+
// turn — the tb-mobile PR #647 bug, arriving through the serialiser.
|
|
152071
|
+
subStatus: s3.subStatus ?? null,
|
|
151969
152072
|
ptyAttached,
|
|
151970
152073
|
...s3.projectId != null && { projectId: s3.projectId },
|
|
151971
152074
|
...s3.sessionName != null && { sessionName: s3.sessionName },
|
|
@@ -152016,6 +152119,10 @@ function discoveredToResponse(d, conversationId) {
|
|
|
152016
152119
|
// cannot see the process's prompt state.
|
|
152017
152120
|
lifecycle: "detached",
|
|
152018
152121
|
lifecycleSource: "probe",
|
|
152122
|
+
// No PTY here, so nothing to scrape and no phase to report. Emitted
|
|
152123
|
+
// explicitly rather than omitted, for the same reason as in
|
|
152124
|
+
// managedToResponse: absence must never be a third state on the wire.
|
|
152125
|
+
subStatus: null,
|
|
152019
152126
|
projectPath: d.projectPath,
|
|
152020
152127
|
projectName: d.projectName,
|
|
152021
152128
|
branch: d.branch,
|