@evident-ai/cli 3.0.1-dev.7272711 → 3.0.1-dev.7bc32fd
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 +143 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1126,6 +1126,36 @@ async function sessionExists(port, id) {
|
|
|
1126
1126
|
return null;
|
|
1127
1127
|
}
|
|
1128
1128
|
}
|
|
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
|
+
}
|
|
1129
1159
|
async function createOpenCodeSession(port, directory) {
|
|
1130
1160
|
const url = new URL(`${opencodeBase(port)}/session`);
|
|
1131
1161
|
if (directory && directory.trim()) {
|
|
@@ -1790,6 +1820,7 @@ var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
|
1790
1820
|
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
1791
1821
|
var DEFAULT_STUCK_QUEUED_MS = 6e4;
|
|
1792
1822
|
var HEARTBEAT_MS = 6e4;
|
|
1823
|
+
var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
|
|
1793
1824
|
var POLL_MISS_GRACE_MS = HEARTBEAT_MS;
|
|
1794
1825
|
var ChannelAuthError = class extends Error {
|
|
1795
1826
|
constructor(message) {
|
|
@@ -1887,6 +1918,15 @@ var ChannelDriver = class {
|
|
|
1887
1918
|
* the row leaves the processing list, exactly like `dontRedispatch`.
|
|
1888
1919
|
*/
|
|
1889
1920
|
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();
|
|
1890
1930
|
/**
|
|
1891
1931
|
* "A null-id re-adopt re-dispatch is in flight, awaiting its read-back" (WI-5
|
|
1892
1932
|
* Task 5.4, High-2). Since we no longer send a caller-supplied id, a re-dispatch
|
|
@@ -2274,6 +2314,7 @@ var ChannelDriver = class {
|
|
|
2274
2314
|
opencodeMessageId,
|
|
2275
2315
|
message,
|
|
2276
2316
|
dispatchedAt: now,
|
|
2317
|
+
processingAnchorMs: now,
|
|
2277
2318
|
deadline: now + this.pausedMaxWaitMs,
|
|
2278
2319
|
started: false,
|
|
2279
2320
|
done: false,
|
|
@@ -2332,6 +2373,10 @@ var ChannelDriver = class {
|
|
|
2332
2373
|
opencodeMessageId,
|
|
2333
2374
|
message,
|
|
2334
2375
|
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,
|
|
2335
2380
|
deadline: processedAtMs + this.pausedMaxWaitMs,
|
|
2336
2381
|
// The server row is ALREADY `processing`; do not re-fire markProcessing.
|
|
2337
2382
|
started: true,
|
|
@@ -2620,6 +2665,19 @@ var ChannelDriver = class {
|
|
|
2620
2665
|
});
|
|
2621
2666
|
}
|
|
2622
2667
|
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
|
+
}
|
|
2623
2681
|
if (activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS) {
|
|
2624
2682
|
inFlight.aliveInFlight = true;
|
|
2625
2683
|
void this.postSignal(conv.id, inFlight.evidentMessageId, "alive").then((ok) => {
|
|
@@ -2682,12 +2740,17 @@ var ChannelDriver = class {
|
|
|
2682
2740
|
*/
|
|
2683
2741
|
async readoptProcessing() {
|
|
2684
2742
|
const rows = await this.getProcessingMessages();
|
|
2685
|
-
if (this.dontRedispatch.size > 0 || this.doneUndeliverable.size > 0) {
|
|
2743
|
+
if (this.dontRedispatch.size > 0 || this.doneUndeliverable.size > 0 || this.readoptPollUnresolvedSignalled.size > 0) {
|
|
2686
2744
|
const stillProcessing = new Set(rows.map((r) => r.id));
|
|
2687
|
-
for (const id of [
|
|
2745
|
+
for (const id of [
|
|
2746
|
+
...this.dontRedispatch,
|
|
2747
|
+
...this.doneUndeliverable,
|
|
2748
|
+
...this.readoptPollUnresolvedSignalled
|
|
2749
|
+
]) {
|
|
2688
2750
|
if (!stillProcessing.has(id)) {
|
|
2689
2751
|
const cleared = this.dontRedispatch.delete(id);
|
|
2690
2752
|
const clearedUndeliverable = this.doneUndeliverable.delete(id);
|
|
2753
|
+
this.readoptPollUnresolvedSignalled.delete(id);
|
|
2691
2754
|
if (cleared || clearedUndeliverable) {
|
|
2692
2755
|
this.log({
|
|
2693
2756
|
level: "info",
|
|
@@ -2741,8 +2804,10 @@ var ChannelDriver = class {
|
|
|
2741
2804
|
});
|
|
2742
2805
|
continue;
|
|
2743
2806
|
}
|
|
2807
|
+
const anyUntracked = sessionRows.some((row) => !this.isTracked(sessionId, row.id));
|
|
2808
|
+
const sessionOngoing = anyUntracked ? await isSessionOngoing(this.port, sessionId) : null;
|
|
2744
2809
|
for (const row of sessionRows) {
|
|
2745
|
-
await this.readoptOne(sessionId, row, messages);
|
|
2810
|
+
await this.readoptOne(sessionId, row, messages, sessionOngoing);
|
|
2746
2811
|
}
|
|
2747
2812
|
}
|
|
2748
2813
|
}
|
|
@@ -2764,7 +2829,7 @@ var ChannelDriver = class {
|
|
|
2764
2829
|
*
|
|
2765
2830
|
* Only `ChannelAuthError` propagates.
|
|
2766
2831
|
*/
|
|
2767
|
-
async readoptOne(sessionId, row, messages) {
|
|
2832
|
+
async readoptOne(sessionId, row, messages, sessionOngoing) {
|
|
2768
2833
|
if (this.isTracked(sessionId, row.id)) {
|
|
2769
2834
|
this.log({
|
|
2770
2835
|
level: "info",
|
|
@@ -2804,6 +2869,7 @@ var ChannelDriver = class {
|
|
|
2804
2869
|
conversation_id: row.conversation_id,
|
|
2805
2870
|
message_id: row.id
|
|
2806
2871
|
});
|
|
2872
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_undeliverable");
|
|
2807
2873
|
return;
|
|
2808
2874
|
}
|
|
2809
2875
|
this.log({
|
|
@@ -2815,6 +2881,7 @@ var ChannelDriver = class {
|
|
|
2815
2881
|
return;
|
|
2816
2882
|
}
|
|
2817
2883
|
this.dontRedispatch.delete(row.id);
|
|
2884
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_done");
|
|
2818
2885
|
return;
|
|
2819
2886
|
}
|
|
2820
2887
|
if (state === "failed") {
|
|
@@ -2837,6 +2904,7 @@ var ChannelDriver = class {
|
|
|
2837
2904
|
conversation_id: row.conversation_id,
|
|
2838
2905
|
message_id: row.id
|
|
2839
2906
|
});
|
|
2907
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_undeliverable");
|
|
2840
2908
|
return;
|
|
2841
2909
|
}
|
|
2842
2910
|
this.log({
|
|
@@ -2848,6 +2916,7 @@ var ChannelDriver = class {
|
|
|
2848
2916
|
return;
|
|
2849
2917
|
}
|
|
2850
2918
|
this.dontRedispatch.delete(row.id);
|
|
2919
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_failed");
|
|
2851
2920
|
return;
|
|
2852
2921
|
}
|
|
2853
2922
|
if (this.dontRedispatch.has(row.id)) {
|
|
@@ -2859,7 +2928,52 @@ var ChannelDriver = class {
|
|
|
2859
2928
|
});
|
|
2860
2929
|
return;
|
|
2861
2930
|
}
|
|
2862
|
-
|
|
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)) {
|
|
2863
2977
|
const descendantAlive = await this.isAnyDescendantSessionAlive(sessionId);
|
|
2864
2978
|
if (descendantAlive === true) {
|
|
2865
2979
|
this.log({
|
|
@@ -2892,6 +3006,7 @@ var ChannelDriver = class {
|
|
|
2892
3006
|
conversation_id: row.conversation_id,
|
|
2893
3007
|
message_id: row.id
|
|
2894
3008
|
});
|
|
3009
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_reattached");
|
|
2895
3010
|
return;
|
|
2896
3011
|
}
|
|
2897
3012
|
await this.forceReadoptRun(sessionId, row);
|
|
@@ -2944,6 +3059,7 @@ var ChannelDriver = class {
|
|
|
2944
3059
|
conversation_id: row.conversation_id,
|
|
2945
3060
|
message_id: row.id
|
|
2946
3061
|
});
|
|
3062
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_window_elapsed");
|
|
2947
3063
|
return;
|
|
2948
3064
|
}
|
|
2949
3065
|
const options = {
|
|
@@ -2972,6 +3088,7 @@ var ChannelDriver = class {
|
|
|
2972
3088
|
conversation_id: row.conversation_id,
|
|
2973
3089
|
message_id: row.id
|
|
2974
3090
|
});
|
|
3091
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_orphan_unsent");
|
|
2975
3092
|
return;
|
|
2976
3093
|
}
|
|
2977
3094
|
if (ocId === null) {
|
|
@@ -2982,6 +3099,7 @@ var ChannelDriver = class {
|
|
|
2982
3099
|
conversation_id: row.conversation_id,
|
|
2983
3100
|
message_id: row.id
|
|
2984
3101
|
});
|
|
3102
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_orphan_unsent");
|
|
2985
3103
|
return;
|
|
2986
3104
|
}
|
|
2987
3105
|
const conv = this.convForRow(sessionId, row);
|
|
@@ -2991,6 +3109,7 @@ var ChannelDriver = class {
|
|
|
2991
3109
|
this.readopted.add(row.id);
|
|
2992
3110
|
this.awaitingReadopt.delete(row.id);
|
|
2993
3111
|
this.ensureWatcherRunning(sessionId);
|
|
3112
|
+
void this.postSignal(row.conversation_id, row.id, "readopt_redispatched");
|
|
2994
3113
|
}
|
|
2995
3114
|
/**
|
|
2996
3115
|
* True if `evidentMessageId` is already being driven — either in the
|
|
@@ -3258,6 +3377,25 @@ var ChannelDriver = class {
|
|
|
3258
3377
|
}
|
|
3259
3378
|
return false;
|
|
3260
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
|
+
}
|
|
3261
3399
|
/**
|
|
3262
3400
|
* Attribute a surfaced interaction to the in-flight message it paused on (M-1).
|
|
3263
3401
|
*
|