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