@evident-ai/cli 3.0.1-dev.39949ec → 3.0.1-dev.39d9b5f
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 +4 -231
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1078,12 +1078,6 @@ async function getSessionMessages(port, sessionId) {
|
|
|
1078
1078
|
return null;
|
|
1079
1079
|
}
|
|
1080
1080
|
}
|
|
1081
|
-
function isSessionActivelyGenerating(messages) {
|
|
1082
|
-
if (!messages || messages.length === 0) return false;
|
|
1083
|
-
const last = messages[messages.length - 1];
|
|
1084
|
-
if (roleOf(last) !== "assistant") return false;
|
|
1085
|
-
return completedOf(last) == null;
|
|
1086
|
-
}
|
|
1087
1081
|
function sessionLastActivityMs(session) {
|
|
1088
1082
|
const candidates = [
|
|
1089
1083
|
session.time?.updated,
|
|
@@ -1126,36 +1120,6 @@ async function sessionExists(port, id) {
|
|
|
1126
1120
|
return null;
|
|
1127
1121
|
}
|
|
1128
1122
|
}
|
|
1129
|
-
async function getSessionStatuses(port) {
|
|
1130
|
-
try {
|
|
1131
|
-
const res = await fetch(`${opencodeBase(port)}/session/status`);
|
|
1132
|
-
if (!res.ok) {
|
|
1133
|
-
console.error(
|
|
1134
|
-
`[getSessionStatuses] GET /session/status returned HTTP ${res.status} (port ${port})`
|
|
1135
|
-
);
|
|
1136
|
-
return null;
|
|
1137
|
-
}
|
|
1138
|
-
const body = await res.json();
|
|
1139
|
-
if (body == null || typeof body !== "object" || Array.isArray(body)) {
|
|
1140
|
-
console.error(
|
|
1141
|
-
`[getSessionStatuses] GET /session/status body was not a plain object (port ${port})`
|
|
1142
|
-
);
|
|
1143
|
-
return null;
|
|
1144
|
-
}
|
|
1145
|
-
return body;
|
|
1146
|
-
} catch (err) {
|
|
1147
|
-
console.error(
|
|
1148
|
-
`[getSessionStatuses] GET /session/status failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
1149
|
-
);
|
|
1150
|
-
return null;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
async function isSessionOngoing(port, id) {
|
|
1154
|
-
const map = await getSessionStatuses(port);
|
|
1155
|
-
if (map == null) return null;
|
|
1156
|
-
const entry = map[id];
|
|
1157
|
-
return entry != null && entry.type !== "idle";
|
|
1158
|
-
}
|
|
1159
1123
|
async function createOpenCodeSession(port, directory) {
|
|
1160
1124
|
const url = new URL(`${opencodeBase(port)}/session`);
|
|
1161
1125
|
if (directory && directory.trim()) {
|
|
@@ -1282,11 +1246,6 @@ function messageRunState(messages, userMessageId) {
|
|
|
1282
1246
|
if (isAssistantInFlight(reply)) return "running";
|
|
1283
1247
|
return errorOf(reply) != null ? "failed" : "done";
|
|
1284
1248
|
}
|
|
1285
|
-
function isPreamblePinnedRunning(messages, userMessageId) {
|
|
1286
|
-
if (messageRunState(messages, userMessageId) !== "running") return false;
|
|
1287
|
-
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
1288
|
-
return completedOf(reply) != null && finishOf(reply) === "tool-calls";
|
|
1289
|
-
}
|
|
1290
1249
|
function messageError(messages, userMessageId) {
|
|
1291
1250
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
1292
1251
|
const error2 = errorOf(reply);
|
|
@@ -1820,7 +1779,6 @@ var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
|
1820
1779
|
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
1821
1780
|
var DEFAULT_STUCK_QUEUED_MS = 6e4;
|
|
1822
1781
|
var HEARTBEAT_MS = 6e4;
|
|
1823
|
-
var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
|
|
1824
1782
|
var POLL_MISS_GRACE_MS = HEARTBEAT_MS;
|
|
1825
1783
|
var ChannelAuthError = class extends Error {
|
|
1826
1784
|
constructor(message) {
|
|
@@ -1918,15 +1876,6 @@ var ChannelDriver = class {
|
|
|
1918
1876
|
* the row leaves the processing list, exactly like `dontRedispatch`.
|
|
1919
1877
|
*/
|
|
1920
1878
|
doneUndeliverable = /* @__PURE__ */ new Set();
|
|
1921
|
-
/**
|
|
1922
|
-
* "Already emitted `readopt_poll_unresolved` for this row" (#229). The b1 /
|
|
1923
|
-
* unreadable-status re-evaluate leaf leaves the row UN-tracked so it is re-read
|
|
1924
|
-
* every ~2s drain until the status map becomes readable — but the server-visible
|
|
1925
|
-
* signal is an OUTCOME, so it must fire at most ONCE per row, not once per drain
|
|
1926
|
-
* (Bugbot "Re-adopt signals flood every drain"). Cleared when the row leaves the
|
|
1927
|
-
* processing list, exactly like `dontRedispatch`/`doneUndeliverable`.
|
|
1928
|
-
*/
|
|
1929
|
-
readoptPollUnresolvedSignalled = /* @__PURE__ */ new Set();
|
|
1930
1879
|
/**
|
|
1931
1880
|
* "A null-id re-adopt re-dispatch is in flight, awaiting its read-back" (WI-5
|
|
1932
1881
|
* Task 5.4, High-2). Since we no longer send a caller-supplied id, a re-dispatch
|
|
@@ -2314,7 +2263,6 @@ var ChannelDriver = class {
|
|
|
2314
2263
|
opencodeMessageId,
|
|
2315
2264
|
message,
|
|
2316
2265
|
dispatchedAt: now,
|
|
2317
|
-
processingAnchorMs: now,
|
|
2318
2266
|
deadline: now + this.pausedMaxWaitMs,
|
|
2319
2267
|
started: false,
|
|
2320
2268
|
done: false,
|
|
@@ -2373,10 +2321,6 @@ var ChannelDriver = class {
|
|
|
2373
2321
|
opencodeMessageId,
|
|
2374
2322
|
message,
|
|
2375
2323
|
dispatchedAt: this.now(),
|
|
2376
|
-
// Anchor the absolute-age ceiling to the SERVER-SIDE `processed_at` (the same
|
|
2377
|
-
// value seeding `deadline`), NOT `dispatchedAt` — so a re-adopted zombie's age
|
|
2378
|
-
// reflects the real turn duration and the ceiling fires on the ORIGINAL turn.
|
|
2379
|
-
processingAnchorMs: processedAtMs,
|
|
2380
2324
|
deadline: processedAtMs + this.pausedMaxWaitMs,
|
|
2381
2325
|
// The server row is ALREADY `processing`; do not re-fire markProcessing.
|
|
2382
2326
|
started: true,
|
|
@@ -2665,19 +2609,6 @@ var ChannelDriver = class {
|
|
|
2665
2609
|
});
|
|
2666
2610
|
}
|
|
2667
2611
|
const activelyRunning = state === "running" && !awaitingHuman;
|
|
2668
|
-
if (activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
2669
|
-
this.log({
|
|
2670
|
-
level: "error",
|
|
2671
|
-
message: `Message ${inFlight.evidentMessageId.slice(0, 8)} exceeded the absolute processing ceiling (${Math.round((this.now() - inFlight.processingAnchorMs) / 6e4)}min, session ${sessionId}) while still actively running \u2014 releasing so the cron can reclaim it`,
|
|
2672
|
-
conversation_id: conv.id,
|
|
2673
|
-
message_id: inFlight.evidentMessageId
|
|
2674
|
-
});
|
|
2675
|
-
void this.postSignal(conv.id, inFlight.evidentMessageId, "gave_up", {
|
|
2676
|
-
watched_for_ms: this.now() - inFlight.processingAnchorMs
|
|
2677
|
-
});
|
|
2678
|
-
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
2679
|
-
return;
|
|
2680
|
-
}
|
|
2681
2612
|
if (activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS) {
|
|
2682
2613
|
inFlight.aliveInFlight = true;
|
|
2683
2614
|
void this.postSignal(conv.id, inFlight.evidentMessageId, "alive").then((ok) => {
|
|
@@ -2740,17 +2671,12 @@ var ChannelDriver = class {
|
|
|
2740
2671
|
*/
|
|
2741
2672
|
async readoptProcessing() {
|
|
2742
2673
|
const rows = await this.getProcessingMessages();
|
|
2743
|
-
if (this.dontRedispatch.size > 0 || this.doneUndeliverable.size > 0
|
|
2674
|
+
if (this.dontRedispatch.size > 0 || this.doneUndeliverable.size > 0) {
|
|
2744
2675
|
const stillProcessing = new Set(rows.map((r) => r.id));
|
|
2745
|
-
for (const id of [
|
|
2746
|
-
...this.dontRedispatch,
|
|
2747
|
-
...this.doneUndeliverable,
|
|
2748
|
-
...this.readoptPollUnresolvedSignalled
|
|
2749
|
-
]) {
|
|
2676
|
+
for (const id of [...this.dontRedispatch, ...this.doneUndeliverable]) {
|
|
2750
2677
|
if (!stillProcessing.has(id)) {
|
|
2751
2678
|
const cleared = this.dontRedispatch.delete(id);
|
|
2752
2679
|
const clearedUndeliverable = this.doneUndeliverable.delete(id);
|
|
2753
|
-
this.readoptPollUnresolvedSignalled.delete(id);
|
|
2754
2680
|
if (cleared || clearedUndeliverable) {
|
|
2755
2681
|
this.log({
|
|
2756
2682
|
level: "info",
|
|
@@ -2804,10 +2730,8 @@ var ChannelDriver = class {
|
|
|
2804
2730
|
});
|
|
2805
2731
|
continue;
|
|
2806
2732
|
}
|
|
2807
|
-
const anyUntracked = sessionRows.some((row) => !this.isTracked(sessionId, row.id));
|
|
2808
|
-
const sessionOngoing = anyUntracked ? await isSessionOngoing(this.port, sessionId) : null;
|
|
2809
2733
|
for (const row of sessionRows) {
|
|
2810
|
-
await this.readoptOne(sessionId, row, messages
|
|
2734
|
+
await this.readoptOne(sessionId, row, messages);
|
|
2811
2735
|
}
|
|
2812
2736
|
}
|
|
2813
2737
|
}
|
|
@@ -2829,7 +2753,7 @@ var ChannelDriver = class {
|
|
|
2829
2753
|
*
|
|
2830
2754
|
* Only `ChannelAuthError` propagates.
|
|
2831
2755
|
*/
|
|
2832
|
-
async readoptOne(sessionId, row, messages
|
|
2756
|
+
async readoptOne(sessionId, row, messages) {
|
|
2833
2757
|
if (this.isTracked(sessionId, row.id)) {
|
|
2834
2758
|
this.log({
|
|
2835
2759
|
level: "info",
|
|
@@ -2869,7 +2793,6 @@ var ChannelDriver = class {
|
|
|
2869
2793
|
conversation_id: row.conversation_id,
|
|
2870
2794
|
message_id: row.id
|
|
2871
2795
|
});
|
|
2872
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_undeliverable");
|
|
2873
2796
|
return;
|
|
2874
2797
|
}
|
|
2875
2798
|
this.log({
|
|
@@ -2881,7 +2804,6 @@ var ChannelDriver = class {
|
|
|
2881
2804
|
return;
|
|
2882
2805
|
}
|
|
2883
2806
|
this.dontRedispatch.delete(row.id);
|
|
2884
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_done");
|
|
2885
2807
|
return;
|
|
2886
2808
|
}
|
|
2887
2809
|
if (state === "failed") {
|
|
@@ -2904,7 +2826,6 @@ var ChannelDriver = class {
|
|
|
2904
2826
|
conversation_id: row.conversation_id,
|
|
2905
2827
|
message_id: row.id
|
|
2906
2828
|
});
|
|
2907
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_undeliverable");
|
|
2908
2829
|
return;
|
|
2909
2830
|
}
|
|
2910
2831
|
this.log({
|
|
@@ -2916,7 +2837,6 @@ var ChannelDriver = class {
|
|
|
2916
2837
|
return;
|
|
2917
2838
|
}
|
|
2918
2839
|
this.dontRedispatch.delete(row.id);
|
|
2919
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_failed");
|
|
2920
2840
|
return;
|
|
2921
2841
|
}
|
|
2922
2842
|
if (this.dontRedispatch.has(row.id)) {
|
|
@@ -2928,71 +2848,6 @@ var ChannelDriver = class {
|
|
|
2928
2848
|
});
|
|
2929
2849
|
return;
|
|
2930
2850
|
}
|
|
2931
|
-
let statusReadableOngoing = null;
|
|
2932
|
-
if (state === "running" && ocId) {
|
|
2933
|
-
const reply = findLastAssistantReplyFor(messages, ocId);
|
|
2934
|
-
const shape = this.replyCompletionShape(reply);
|
|
2935
|
-
const ongoing = sessionOngoing;
|
|
2936
|
-
statusReadableOngoing = ongoing;
|
|
2937
|
-
if (ongoing === false) {
|
|
2938
|
-
this.log({
|
|
2939
|
-
level: "info",
|
|
2940
|
-
message: `Re-adopt: message ${row.id.slice(0, 8)} running/${shape} but session ${sessionId.slice(0, 8)} is not-ongoing per GET /session/status (absent/idle) \u2014 re-dispatching from scratch (status-gated recovery)`,
|
|
2941
|
-
conversation_id: row.conversation_id,
|
|
2942
|
-
message_id: row.id
|
|
2943
|
-
});
|
|
2944
|
-
await this.forceReadoptRun(sessionId, row);
|
|
2945
|
-
return;
|
|
2946
|
-
}
|
|
2947
|
-
if (ongoing === true) {
|
|
2948
|
-
this.log({
|
|
2949
|
-
level: "info",
|
|
2950
|
-
message: `Re-adopt: message ${row.id.slice(0, 8)} running/${shape} and session ${sessionId.slice(0, 8)} is ongoing per GET /session/status (busy/retry) \u2014 re-attaching watcher (no re-dispatch)`,
|
|
2951
|
-
conversation_id: row.conversation_id,
|
|
2952
|
-
message_id: row.id
|
|
2953
|
-
});
|
|
2954
|
-
} else {
|
|
2955
|
-
if (shape === "b1") {
|
|
2956
|
-
this.log({
|
|
2957
|
-
level: "info",
|
|
2958
|
-
message: `Re-adopt: message ${row.id.slice(0, 8)} running/b1 but GET /session/status was unreadable (null) for session ${sessionId.slice(0, 8)} \u2014 NOT latching a b1 row on a transient status blip; leaving it un-tracked to re-evaluate on the next drain`,
|
|
2959
|
-
conversation_id: row.conversation_id,
|
|
2960
|
-
message_id: row.id
|
|
2961
|
-
});
|
|
2962
|
-
if (!this.readoptPollUnresolvedSignalled.has(row.id)) {
|
|
2963
|
-
this.readoptPollUnresolvedSignalled.add(row.id);
|
|
2964
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_poll_unresolved");
|
|
2965
|
-
}
|
|
2966
|
-
return;
|
|
2967
|
-
}
|
|
2968
|
-
this.log({
|
|
2969
|
-
level: "info",
|
|
2970
|
-
message: `Re-adopt: message ${row.id.slice(0, 8)} running/${shape} but GET /session/status was unreadable (null) for session ${sessionId.slice(0, 8)} \u2014 falling back to the #253 preamble + descendant cross-check`,
|
|
2971
|
-
conversation_id: row.conversation_id,
|
|
2972
|
-
message_id: row.id
|
|
2973
|
-
});
|
|
2974
|
-
}
|
|
2975
|
-
}
|
|
2976
|
-
if (statusReadableOngoing === null && state === "running" && ocId && isPreamblePinnedRunning(messages, ocId)) {
|
|
2977
|
-
const descendantAlive = await this.isAnyDescendantSessionAlive(sessionId);
|
|
2978
|
-
if (descendantAlive === true) {
|
|
2979
|
-
this.log({
|
|
2980
|
-
level: "info",
|
|
2981
|
-
message: `Re-adopt: message ${row.id.slice(0, 8)} preamble-pinned running (root ${sessionId.slice(0, 8)}) but a live descendant sub-agent session was found \u2014 treating as still running, re-attaching watcher (no re-dispatch)`,
|
|
2982
|
-
conversation_id: row.conversation_id,
|
|
2983
|
-
message_id: row.id
|
|
2984
|
-
});
|
|
2985
|
-
} else {
|
|
2986
|
-
this.log({
|
|
2987
|
-
level: "info",
|
|
2988
|
-
message: `Re-adopt: message ${row.id.slice(0, 8)} preamble-pinned on recovery (root ${sessionId.slice(0, 8)}), no live descendant runner \u2014 re-dispatching from scratch${descendantAlive === null ? " (descendant liveness indeterminate; a restart guarantees no live runner, so this does NOT block the re-dispatch)" : ""}`,
|
|
2989
|
-
conversation_id: row.conversation_id,
|
|
2990
|
-
message_id: row.id
|
|
2991
|
-
});
|
|
2992
|
-
await this.forceReadoptRun(sessionId, row);
|
|
2993
|
-
return;
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
2851
|
if ((state === "running" || state === "queued") && ocId) {
|
|
2997
2852
|
const conv = this.convForRow(sessionId, row);
|
|
2998
2853
|
const message = this.queuedMessageForRow(row);
|
|
@@ -3006,7 +2861,6 @@ var ChannelDriver = class {
|
|
|
3006
2861
|
conversation_id: row.conversation_id,
|
|
3007
2862
|
message_id: row.id
|
|
3008
2863
|
});
|
|
3009
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_reattached");
|
|
3010
2864
|
return;
|
|
3011
2865
|
}
|
|
3012
2866
|
await this.forceReadoptRun(sessionId, row);
|
|
@@ -3059,7 +2913,6 @@ var ChannelDriver = class {
|
|
|
3059
2913
|
conversation_id: row.conversation_id,
|
|
3060
2914
|
message_id: row.id
|
|
3061
2915
|
});
|
|
3062
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_window_elapsed");
|
|
3063
2916
|
return;
|
|
3064
2917
|
}
|
|
3065
2918
|
const options = {
|
|
@@ -3088,7 +2941,6 @@ var ChannelDriver = class {
|
|
|
3088
2941
|
conversation_id: row.conversation_id,
|
|
3089
2942
|
message_id: row.id
|
|
3090
2943
|
});
|
|
3091
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_orphan_unsent");
|
|
3092
2944
|
return;
|
|
3093
2945
|
}
|
|
3094
2946
|
if (ocId === null) {
|
|
@@ -3099,7 +2951,6 @@ var ChannelDriver = class {
|
|
|
3099
2951
|
conversation_id: row.conversation_id,
|
|
3100
2952
|
message_id: row.id
|
|
3101
2953
|
});
|
|
3102
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_orphan_unsent");
|
|
3103
2954
|
return;
|
|
3104
2955
|
}
|
|
3105
2956
|
const conv = this.convForRow(sessionId, row);
|
|
@@ -3109,7 +2960,6 @@ var ChannelDriver = class {
|
|
|
3109
2960
|
this.readopted.add(row.id);
|
|
3110
2961
|
this.awaitingReadopt.delete(row.id);
|
|
3111
2962
|
this.ensureWatcherRunning(sessionId);
|
|
3112
|
-
void this.postSignal(row.conversation_id, row.id, "readopt_redispatched");
|
|
3113
2963
|
}
|
|
3114
2964
|
/**
|
|
3115
2965
|
* True if `evidentMessageId` is already being driven — either in the
|
|
@@ -3319,83 +3169,6 @@ var ChannelDriver = class {
|
|
|
3319
3169
|
if (parent !== void 0) this.sessionParents.set(sessionId, parent);
|
|
3320
3170
|
return parent;
|
|
3321
3171
|
}
|
|
3322
|
-
/**
|
|
3323
|
-
* DEFENSIVE cross-check for the restart-recovery path (WI-2): is any descendant
|
|
3324
|
-
* (`task` sub-agent) session under `rootSessionId` still genuinely doing work?
|
|
3325
|
-
*
|
|
3326
|
-
* The PRIMARY recovery trigger is "preamble-pinned on recovery ⇒ idle" — a
|
|
3327
|
-
* runner restart wipes OpenCode's in-memory `SessionStatus`/`Runner`, so a
|
|
3328
|
-
* completed `finish: "tool-calls"` root reply encountered during re-adoption is
|
|
3329
|
-
* idle by OpenCode's own definition and is re-dispatched. This method exists only
|
|
3330
|
-
* so the WI-3 caller can VETO that re-dispatch in the rare case a descendant is
|
|
3331
|
-
* provably in flight at the exact moment of recovery.
|
|
3332
|
-
*
|
|
3333
|
-
* "Alive" criterion (TIGHTENED): a descendant is alive only when it is PROVABLY,
|
|
3334
|
-
* ACTIVELY generating — its LAST message is an assistant still mid-generation
|
|
3335
|
-
* (`completed == null`, via `isSessionActivelyGenerating`). An
|
|
3336
|
-
* INCOMPLETE-BUT-NOT-GENERATING child — last message a user message, or a
|
|
3337
|
-
* completed `finish: "tool-calls"` step — is NOT alive after a restart (nothing
|
|
3338
|
-
* is generating once the runner is gone), so it does NOT veto. (This is
|
|
3339
|
-
* deliberately NOT `!isTurnComplete`, which also matches those dead-but-non-terminal
|
|
3340
|
-
* shapes and would falsely veto — re-hanging the very turn this path recovers.)
|
|
3341
|
-
*
|
|
3342
|
-
* Return contract (encoded so WI-3 need not re-derive it):
|
|
3343
|
-
* - `true` → a descendant is provably, actively generating (veto re-dispatch).
|
|
3344
|
-
* - `false` → descendants exist but none is actively generating (the restart
|
|
3345
|
-
* case), OR no descendant is found at all.
|
|
3346
|
-
* - `null` → liveness is INDETERMINATE (enumeration via `listSessions` failed).
|
|
3347
|
-
*
|
|
3348
|
-
* ⚠️ `null` (UNKNOWN) MUST NOT be treated as "alive": WI-3 treats `null` the same
|
|
3349
|
-
* as `false` and does NOT veto — a restart guarantees no live runner, so an
|
|
3350
|
-
* indeterminate cross-check almost always means "couldn't reach a child that no
|
|
3351
|
-
* longer exists". The inversion lives in the caller; this method just reports
|
|
3352
|
-
* true/false/null faithfully.
|
|
3353
|
-
*
|
|
3354
|
-
* VERIFY-BEFORE-DEPEND: we depend ONLY on (a) `parentID` from `GET /session/:id`
|
|
3355
|
-
* (already proven by the existing child-session interaction tests, via
|
|
3356
|
-
* `resolveSessionParent`/`sessionBelongsTo`) and (b) the child's own message-list
|
|
3357
|
-
* terminal state. We do NOT depend on any session-level `busy`/`idle` field —
|
|
3358
|
-
* there is none on `GET /session/:id`; OpenCode's busy state is in-memory
|
|
3359
|
-
* `SessionStatus` only.
|
|
3360
|
-
*/
|
|
3361
|
-
async isAnyDescendantSessionAlive(rootSessionId) {
|
|
3362
|
-
const sessions = await listSessions(this.port);
|
|
3363
|
-
if (!sessions) {
|
|
3364
|
-
this.log({
|
|
3365
|
-
level: "error",
|
|
3366
|
-
message: `Re-adopt: could not enumerate sessions to cross-check descendant liveness for root ${rootSessionId} (listSessions failed) \u2014 treating child liveness as indeterminate`
|
|
3367
|
-
});
|
|
3368
|
-
return null;
|
|
3369
|
-
}
|
|
3370
|
-
for (const candidate of sessions) {
|
|
3371
|
-
if (!candidate?.id || candidate.id === rootSessionId) continue;
|
|
3372
|
-
if (!await this.sessionBelongsTo(candidate.id, rootSessionId)) continue;
|
|
3373
|
-
const childMsgs = await getSessionMessages(this.port, candidate.id);
|
|
3374
|
-
if (isSessionActivelyGenerating(childMsgs)) {
|
|
3375
|
-
return true;
|
|
3376
|
-
}
|
|
3377
|
-
}
|
|
3378
|
-
return false;
|
|
3379
|
-
}
|
|
3380
|
-
/**
|
|
3381
|
-
* Cheap decision-telemetry label for a running row's LAST correlated reply
|
|
3382
|
-
* (WI-2 Task 2.2): which running SHAPE it is, for the status-gated recovery log.
|
|
3383
|
-
* - `b1` — the reply itself is still in flight (`time.completed == null`) —
|
|
3384
|
-
* the aborted-in-flight production bug after a restart.
|
|
3385
|
-
* - `b2` — a COMPLETED reply pinned running only by `finish === "tool-calls"`
|
|
3386
|
-
* (the sub-agent preamble — #253's shape).
|
|
3387
|
-
* - `other` — any other shape (defensive; a running row is normally b1 or b2).
|
|
3388
|
-
* Reads `info.time.completed` / `info.finish` (tolerating the legacy top-level
|
|
3389
|
-
* shape) directly rather than re-importing the module-private `completedOf`/
|
|
3390
|
-
* `finishOf` — this is a display label only, not a correctness predicate.
|
|
3391
|
-
*/
|
|
3392
|
-
replyCompletionShape(reply) {
|
|
3393
|
-
if (!reply) return "other";
|
|
3394
|
-
const completed = reply.info?.time?.completed ?? reply.time?.completed;
|
|
3395
|
-
if (completed == null) return "b1";
|
|
3396
|
-
const finish = reply.info?.finish ?? reply.finish;
|
|
3397
|
-
return finish === "tool-calls" ? "b2" : "other";
|
|
3398
|
-
}
|
|
3399
3172
|
/**
|
|
3400
3173
|
* Attribute a surfaced interaction to the in-flight message it paused on (M-1).
|
|
3401
3174
|
*
|