@evident-ai/cli 3.1.1-dev.9efabd5 → 3.1.1-dev.9fe57bf
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 +291 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -997,6 +997,9 @@ import { homedir as homedir3 } from "os";
|
|
|
997
997
|
import { isAbsolute as isAbsolute2, join as join3, parse, resolve as resolvePath } from "path";
|
|
998
998
|
import chalk6 from "chalk";
|
|
999
999
|
|
|
1000
|
+
// ../../packages/types/src/agents/index.ts
|
|
1001
|
+
var MICROVM_MAX_LIFETIME_MS = 8 * 60 * 6e4;
|
|
1002
|
+
|
|
1000
1003
|
// ../../packages/types/src/telemetry/index.ts
|
|
1001
1004
|
var TelemetryEventTypes = {
|
|
1002
1005
|
// Agent activity events (shown in web UI activity log)
|
|
@@ -2086,6 +2089,21 @@ function messageError(messages, userMessageId) {
|
|
|
2086
2089
|
}
|
|
2087
2090
|
return "The agent run failed.";
|
|
2088
2091
|
}
|
|
2092
|
+
function isAbortedTerminalReply(messages, userMessageId) {
|
|
2093
|
+
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
2094
|
+
const error2 = errorOf(reply);
|
|
2095
|
+
if (error2 == null) return false;
|
|
2096
|
+
if (typeof error2 === "string") return error2.trim() === "Aborted";
|
|
2097
|
+
if (typeof error2 === "object") {
|
|
2098
|
+
const e = error2;
|
|
2099
|
+
if (e.name === "MessageAbortedError") return true;
|
|
2100
|
+
if (e.name === "AbortError") return true;
|
|
2101
|
+
const dataMessage = e.data?.message;
|
|
2102
|
+
const rendered = typeof dataMessage === "string" ? dataMessage : typeof e.message === "string" ? e.message : null;
|
|
2103
|
+
return rendered != null && rendered.trim() === "Aborted";
|
|
2104
|
+
}
|
|
2105
|
+
return false;
|
|
2106
|
+
}
|
|
2089
2107
|
function messageFailure(messages, userMessageId) {
|
|
2090
2108
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
2091
2109
|
const error2 = errorOf(reply);
|
|
@@ -3258,6 +3276,24 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3258
3276
|
* processing list, exactly like `dontRedispatch`/`doneUndeliverable`.
|
|
3259
3277
|
*/
|
|
3260
3278
|
readoptPollUnresolvedSignalled = /* @__PURE__ */ new Set();
|
|
3279
|
+
/**
|
|
3280
|
+
* "Already emitted `redrive_unresolved` for this row" (#965). Mirrors
|
|
3281
|
+
* `readoptPollUnresolvedSignalled`: `resolveRedrive`'s `unresolved` leaf recurs
|
|
3282
|
+
* every ~2s drain until opencode's status becomes readable, but the
|
|
3283
|
+
* server-visible signal is an OUTCOME, so it fires at most once per row. Cleared
|
|
3284
|
+
* on any non-`unresolved` outcome so the set cannot grow beyond the currently
|
|
3285
|
+
* unresolvable rows.
|
|
3286
|
+
*/
|
|
3287
|
+
redriveUnresolvedSignalled = /* @__PURE__ */ new Set();
|
|
3288
|
+
/**
|
|
3289
|
+
* First `now()` a `pending` row's re-drive was observed `unresolved` (#965). A
|
|
3290
|
+
* `pending` row is invisible to every cron arm (all require `status =
|
|
3291
|
+
* 'processing'`), so an indefinitely-`unresolved` row would be stranded with
|
|
3292
|
+
* nothing driving it. Once `now - since >= pausedMaxWaitMs`, `resolveRedrive`
|
|
3293
|
+
* takes `dispatch` instead of `unresolved` (reusing the existing knob — see
|
|
3294
|
+
* ADR-0047's own "unreachable ⇒ bounded" rule). Cleared on any other outcome.
|
|
3295
|
+
*/
|
|
3296
|
+
redriveUnresolvedSince = /* @__PURE__ */ new Map();
|
|
3261
3297
|
/**
|
|
3262
3298
|
* "A null-id re-adopt re-dispatch is in flight, awaiting its read-back" (WI-5
|
|
3263
3299
|
* Task 5.4, High-2). Since we no longer send a caller-supplied id, a re-dispatch
|
|
@@ -3597,6 +3633,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3597
3633
|
skippedAlreadyDispatched += 1;
|
|
3598
3634
|
continue;
|
|
3599
3635
|
}
|
|
3636
|
+
if (message.opencode_message_id) {
|
|
3637
|
+
const outcome = await this.resolveRedrive(conv, sessionId, message, refusedSessionId);
|
|
3638
|
+
if (outcome !== "dispatch") {
|
|
3639
|
+
break;
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3600
3642
|
const options = {
|
|
3601
3643
|
agent: message.opencode_agent ?? void 0,
|
|
3602
3644
|
model: message.opencode_model ?? void 0
|
|
@@ -3686,6 +3728,234 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3686
3728
|
this.ensureWatcherRunning(sessionId);
|
|
3687
3729
|
return dispatched;
|
|
3688
3730
|
}
|
|
3731
|
+
/**
|
|
3732
|
+
* Poll a session's message list for the re-drive fence (#965), via the
|
|
3733
|
+
* INJECTED `fetchImpl` — NOT the imported `getSessionMessages` helper, which
|
|
3734
|
+
* hits the global `fetch` and would bypass the same override every other
|
|
3735
|
+
* opencode poll in this file respects. Mirrors `readoptProcessing`'s own
|
|
3736
|
+
* snapshot fetch (`:3081-3111`). `null` = unreadable (non-OK response,
|
|
3737
|
+
* non-array body, or a network exception) — treated as "can't observe",
|
|
3738
|
+
* never as "confirmed gone".
|
|
3739
|
+
*/
|
|
3740
|
+
async pollSessionMessagesForRedrive(conv, message, sessionId) {
|
|
3741
|
+
try {
|
|
3742
|
+
const res = await this.fetchImpl(`${this.opencodeBase}/session/${sessionId}/message`);
|
|
3743
|
+
if (!res.ok) {
|
|
3744
|
+
this.log({
|
|
3745
|
+
level: "warn",
|
|
3746
|
+
message: `Re-drive: polling session ${sessionId.slice(0, 8)} for message ${message.id.slice(0, 8)} returned HTTP ${res.status} \u2014 treating as unreadable this tick`,
|
|
3747
|
+
conversation_id: conv.id,
|
|
3748
|
+
message_id: message.id
|
|
3749
|
+
});
|
|
3750
|
+
return null;
|
|
3751
|
+
}
|
|
3752
|
+
const body = await res.json();
|
|
3753
|
+
if (!Array.isArray(body)) {
|
|
3754
|
+
this.log({
|
|
3755
|
+
level: "warn",
|
|
3756
|
+
message: `Re-drive: polling session ${sessionId.slice(0, 8)} for message ${message.id.slice(0, 8)} returned a non-array message body \u2014 treating as unreadable this tick`,
|
|
3757
|
+
conversation_id: conv.id,
|
|
3758
|
+
message_id: message.id
|
|
3759
|
+
});
|
|
3760
|
+
return null;
|
|
3761
|
+
}
|
|
3762
|
+
return body;
|
|
3763
|
+
} catch (err) {
|
|
3764
|
+
this.log({
|
|
3765
|
+
level: "warn",
|
|
3766
|
+
message: `Re-drive: failed to poll session ${sessionId.slice(0, 8)} for message ${message.id.slice(0, 8)} (will retry next drain): ${err instanceof Error ? err.message : String(err)}`,
|
|
3767
|
+
conversation_id: conv.id,
|
|
3768
|
+
message_id: message.id
|
|
3769
|
+
});
|
|
3770
|
+
return null;
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
/**
|
|
3774
|
+
* The re-drive fence for a `pending` row that already carries a stored
|
|
3775
|
+
* `opencode_message_id` (#965) — i.e. it has already been handed to opencode at
|
|
3776
|
+
* least once (see the invariant at `QueuedMessage.opencode_message_id`'s doc).
|
|
3777
|
+
* The lifecycle cron can falsely reclaim a `processing` row back to `pending`
|
|
3778
|
+
* mid-turn (a 5-minute liveness-staleness check racing a still-running turn);
|
|
3779
|
+
* without this fence the drain loop would re-`prompt_async` the SAME turn a
|
|
3780
|
+
* second time against live GitHub state. Mirrors `readoptOne`'s job for the
|
|
3781
|
+
* `processing` re-adopt path, but simpler: no b1/b2 preamble cross-check is
|
|
3782
|
+
* needed here because `refusedSessionId` already handles the one case
|
|
3783
|
+
* (#553 abandoned session) that path exists for.
|
|
3784
|
+
*
|
|
3785
|
+
* Only `ChannelAuthError` propagates; every other failure resolves to
|
|
3786
|
+
* `unresolved` and is retried whole on the next ~2s drain tick.
|
|
3787
|
+
*/
|
|
3788
|
+
async resolveRedrive(conv, sessionId, message, refusedSessionId) {
|
|
3789
|
+
const ocId = message.opencode_message_id ?? null;
|
|
3790
|
+
if (refusedSessionId) {
|
|
3791
|
+
this.clearRedriveUnresolved(message.id);
|
|
3792
|
+
void this.postSignal(conv.id, message.id, "redrive_redispatched");
|
|
3793
|
+
return "dispatch";
|
|
3794
|
+
}
|
|
3795
|
+
const messages = await this.pollSessionMessagesForRedrive(conv, message, sessionId);
|
|
3796
|
+
if (messages == null || messages.length === 0) {
|
|
3797
|
+
return this.resolveRedriveUnresolved(conv, message);
|
|
3798
|
+
}
|
|
3799
|
+
const state = messageRunState(messages, ocId ?? "");
|
|
3800
|
+
if (state === "failed" && isAbortedTerminalReply(messages, ocId ?? "")) {
|
|
3801
|
+
const ongoing = await isSessionOngoing(this.port, sessionId);
|
|
3802
|
+
if (ongoing === false) {
|
|
3803
|
+
this.log({
|
|
3804
|
+
level: "info",
|
|
3805
|
+
message: `Re-drive: message ${message.id.slice(0, 8)} carries an abort-shaped terminal error and session ${sessionId.slice(0, 8)} is not-ongoing per GET /session/status \u2014 restart orphan, re-dispatching instead of marking it permanently failed`,
|
|
3806
|
+
conversation_id: conv.id,
|
|
3807
|
+
message_id: message.id
|
|
3808
|
+
});
|
|
3809
|
+
this.clearRedriveUnresolved(message.id);
|
|
3810
|
+
void this.postSignal(conv.id, message.id, "redrive_redispatched");
|
|
3811
|
+
return "dispatch";
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
if (state === "done" || state === "failed") {
|
|
3815
|
+
return this.settleRedrive(conv, sessionId, message, ocId, messages, state);
|
|
3816
|
+
}
|
|
3817
|
+
if (state === "running" || state === "queued") {
|
|
3818
|
+
const ongoing = await isSessionOngoing(this.port, sessionId);
|
|
3819
|
+
if (ongoing === true) {
|
|
3820
|
+
return this.reattachRedrive(conv, sessionId, message, ocId);
|
|
3821
|
+
}
|
|
3822
|
+
if (ongoing === false) {
|
|
3823
|
+
this.clearRedriveUnresolved(message.id);
|
|
3824
|
+
void this.postSignal(conv.id, message.id, "redrive_redispatched");
|
|
3825
|
+
return "dispatch";
|
|
3826
|
+
}
|
|
3827
|
+
return this.resolveRedriveUnresolved(conv, message);
|
|
3828
|
+
}
|
|
3829
|
+
this.clearRedriveUnresolved(message.id);
|
|
3830
|
+
void this.postSignal(conv.id, message.id, "redrive_redispatched");
|
|
3831
|
+
return "dispatch";
|
|
3832
|
+
}
|
|
3833
|
+
/**
|
|
3834
|
+
* The `reattached` outcome (Task 3.3): the prior turn is STILL ONGOING per
|
|
3835
|
+
* opencode's own status map — undo the false reclaim instead of starting a
|
|
3836
|
+
* second turn.
|
|
3837
|
+
*/
|
|
3838
|
+
async reattachRedrive(conv, sessionId, message, ocId) {
|
|
3839
|
+
let anchorMs;
|
|
3840
|
+
const parsed = message.processing_started_at ? Date.parse(message.processing_started_at) : NaN;
|
|
3841
|
+
if (!Number.isNaN(parsed)) {
|
|
3842
|
+
anchorMs = parsed;
|
|
3843
|
+
} else {
|
|
3844
|
+
anchorMs = this.now();
|
|
3845
|
+
this.log({
|
|
3846
|
+
level: "error",
|
|
3847
|
+
message: `Re-drive: message ${message.id.slice(0, 8)} has null/unparseable processing_started_at (${String(message.processing_started_at)}) \u2014 anchoring the watcher's absolute-age ceiling to now (defensive)`,
|
|
3848
|
+
conversation_id: conv.id,
|
|
3849
|
+
message_id: message.id
|
|
3850
|
+
});
|
|
3851
|
+
}
|
|
3852
|
+
const title = await this.resolveSessionTitle(sessionId, conv.id);
|
|
3853
|
+
try {
|
|
3854
|
+
await this.markProcessing(conv.id, message.id, sessionId, ocId, title);
|
|
3855
|
+
} catch (err) {
|
|
3856
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
3857
|
+
this.log({
|
|
3858
|
+
level: "warn",
|
|
3859
|
+
message: `Re-drive: failed to restore message ${message.id.slice(0, 8)} to processing (will retry next drain): ${err instanceof Error ? err.message : String(err)}`,
|
|
3860
|
+
conversation_id: conv.id,
|
|
3861
|
+
message_id: message.id
|
|
3862
|
+
});
|
|
3863
|
+
return "unresolved";
|
|
3864
|
+
}
|
|
3865
|
+
this.clearRedriveUnresolved(message.id);
|
|
3866
|
+
this.registerReadopted(conv, sessionId, message, ocId ?? "", anchorMs);
|
|
3867
|
+
this.dispatched.add(message.id);
|
|
3868
|
+
this.readopted.add(message.id);
|
|
3869
|
+
this.ensureWatcherRunning(sessionId);
|
|
3870
|
+
const watchedForMs = this.now() - anchorMs;
|
|
3871
|
+
void this.postSignal(conv.id, message.id, "redrive_reattached", {
|
|
3872
|
+
watched_for_ms: watchedForMs
|
|
3873
|
+
});
|
|
3874
|
+
this.log({
|
|
3875
|
+
level: "warn",
|
|
3876
|
+
message: `Re-drive: message ${message.id.slice(0, 8)} (session ${sessionId.slice(0, 8)}) was wrongly reclaimed to pending while its turn was still running (watched ${watchedForMs}ms) \u2014 restored to processing instead of re-dispatching`,
|
|
3877
|
+
conversation_id: conv.id,
|
|
3878
|
+
message_id: message.id
|
|
3879
|
+
});
|
|
3880
|
+
return "reattached";
|
|
3881
|
+
}
|
|
3882
|
+
/**
|
|
3883
|
+
* The `settled` outcome (Task 3.2): the prior turn already finished (or
|
|
3884
|
+
* errored) while nobody was watching — deliver/report it instead of re-running.
|
|
3885
|
+
* Mirrors `readoptOne`'s `done`/`failed` branches' error discipline, simplified
|
|
3886
|
+
* (no `doneUndeliverable` park: a terminal PATCH failure here just retries next
|
|
3887
|
+
* drain, same as any other non-auth failure). The restart-abort carve-out that
|
|
3888
|
+
* keeps the two in step for `failed` lives in the caller (`resolveRedrive`, #1310),
|
|
3889
|
+
* so a row reaching this `failed` branch is a GENUINE failure.
|
|
3890
|
+
*/
|
|
3891
|
+
async settleRedrive(conv, sessionId, message, ocId, messages, state) {
|
|
3892
|
+
try {
|
|
3893
|
+
if (state === "done") {
|
|
3894
|
+
const title = await this.resolveSessionTitle(sessionId, conv.id);
|
|
3895
|
+
const usage = messageUsage(messages, ocId ?? "");
|
|
3896
|
+
this.log({
|
|
3897
|
+
level: "info",
|
|
3898
|
+
message: `Re-drive: message ${message.id.slice(0, 8)} completed while its row was wrongly reclaimed to pending \u2014 marking done instead of re-dispatching`,
|
|
3899
|
+
conversation_id: conv.id,
|
|
3900
|
+
message_id: message.id
|
|
3901
|
+
});
|
|
3902
|
+
await this.markDone(conv.id, message.id, sessionId, ocId, title, usage);
|
|
3903
|
+
} else {
|
|
3904
|
+
const error2 = messageError(messages, ocId ?? "") ?? void 0;
|
|
3905
|
+
const usage = messageUsage(messages, ocId ?? "");
|
|
3906
|
+
const failure = await this.classifyModelAuthFailure(messages, ocId ?? "");
|
|
3907
|
+
this.log({
|
|
3908
|
+
level: "error",
|
|
3909
|
+
message: `Re-drive: message ${message.id.slice(0, 8)} errored while its row was wrongly reclaimed to pending \u2014 marking failed instead of re-dispatching: ${error2 ?? "(no error text)"}`,
|
|
3910
|
+
conversation_id: conv.id,
|
|
3911
|
+
message_id: message.id
|
|
3912
|
+
});
|
|
3913
|
+
await this.markFailed(conv.id, message.id, sessionId, error2, usage, failure);
|
|
3914
|
+
}
|
|
3915
|
+
} catch (err) {
|
|
3916
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
3917
|
+
this.log({
|
|
3918
|
+
level: "warn",
|
|
3919
|
+
message: `Re-drive: failed to report message ${message.id.slice(0, 8)} ${state} (will retry next drain): ${err instanceof Error ? err.message : String(err)}`,
|
|
3920
|
+
conversation_id: conv.id,
|
|
3921
|
+
message_id: message.id
|
|
3922
|
+
});
|
|
3923
|
+
return "unresolved";
|
|
3924
|
+
}
|
|
3925
|
+
this.clearRedriveUnresolved(message.id);
|
|
3926
|
+
void this.postSignal(conv.id, message.id, "redrive_settled");
|
|
3927
|
+
return "settled";
|
|
3928
|
+
}
|
|
3929
|
+
/**
|
|
3930
|
+
* The bounded `unresolved` outcome (Task 3.4): opencode's state could not be
|
|
3931
|
+
* observed (snapshot unreadable/empty, or `isSessionOngoing` returned `null`).
|
|
3932
|
+
* A `pending` row is invisible to every cron arm (all require `status =
|
|
3933
|
+
* 'processing'`), so an indefinitely-`unresolved` row would be stranded with
|
|
3934
|
+
* nothing driving it — bound it to the existing `pausedMaxWaitMs` window
|
|
3935
|
+
* (reusing the knob, not a new constant) and take `dispatch` once elapsed.
|
|
3936
|
+
*/
|
|
3937
|
+
resolveRedriveUnresolved(conv, message) {
|
|
3938
|
+
const now = this.now();
|
|
3939
|
+
const since = this.redriveUnresolvedSince.get(message.id);
|
|
3940
|
+
if (since !== void 0 && now - since >= this.pausedMaxWaitMs) {
|
|
3941
|
+
this.clearRedriveUnresolved(message.id);
|
|
3942
|
+
void this.postSignal(conv.id, message.id, "redrive_redispatched");
|
|
3943
|
+
return "dispatch";
|
|
3944
|
+
}
|
|
3945
|
+
if (since === void 0) {
|
|
3946
|
+
this.redriveUnresolvedSince.set(message.id, now);
|
|
3947
|
+
}
|
|
3948
|
+
if (!this.redriveUnresolvedSignalled.has(message.id)) {
|
|
3949
|
+
this.redriveUnresolvedSignalled.add(message.id);
|
|
3950
|
+
void this.postSignal(conv.id, message.id, "redrive_unresolved");
|
|
3951
|
+
}
|
|
3952
|
+
return "unresolved";
|
|
3953
|
+
}
|
|
3954
|
+
/** Clear both `unresolved`-bound trackers for a row (any non-`unresolved` outcome). */
|
|
3955
|
+
clearRedriveUnresolved(messageId) {
|
|
3956
|
+
this.redriveUnresolvedSince.delete(messageId);
|
|
3957
|
+
this.redriveUnresolvedSignalled.delete(messageId);
|
|
3958
|
+
}
|
|
3689
3959
|
/**
|
|
3690
3960
|
* Record that `sessionId` is no longer a valid binding for `conversationId`
|
|
3691
3961
|
* (#553). Keyed by conversation and hard-capped, so it cannot grow with the
|
|
@@ -3969,9 +4239,10 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3969
4239
|
* opencode reports ACTIVELY `running` is watched to completion (its liveness
|
|
3970
4240
|
* heartbeat keeps the cron off its row), while a re-adopted turn that is paused
|
|
3971
4241
|
* awaiting a human — or queued/unreachable — is still bounded by `deadline` and
|
|
3972
|
-
* handed to the cron.
|
|
3973
|
-
*
|
|
3974
|
-
*
|
|
4242
|
+
* handed to the cron. Real invariant (#965): the cron MAY reclaim a row this
|
|
4243
|
+
* runner still holds; a reclaimed row that already ran is never re-dispatched
|
|
4244
|
+
* while opencode reports its turn ongoing (readopt's own gate here, and the
|
|
4245
|
+
* `pending`-row re-drive fence, `resolveRedrive`). `dispatchedAt` stays `now`
|
|
3975
4246
|
* (only the appear-guard uses it).
|
|
3976
4247
|
*
|
|
3977
4248
|
* `evidentMessageId` addresses the SERVER row (for markProcessing/markDone);
|
|
@@ -4531,7 +4802,10 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4531
4802
|
* re-dispatched (at most once, see `forceReadoptRun`):
|
|
4532
4803
|
* - `done` → `markDone` now (guarded like the watcher's done branch);
|
|
4533
4804
|
* - `failed` → `markFailed` with the surfaced error (issue #182), so an
|
|
4534
|
-
* errored turn is reported failed on restart, NOT re-dispatched
|
|
4805
|
+
* errored turn is reported failed on restart, NOT re-dispatched —
|
|
4806
|
+
* EXCEPT a restart-ABORTED turn under a not-ongoing session,
|
|
4807
|
+
* which is a restart orphan wearing a terminal error and is
|
|
4808
|
+
* re-dispatched instead (issue #1310, see the branch below);
|
|
4535
4809
|
* - `running`/`queued` → re-attach a watcher via `registerReadopted` (no re-dispatch),
|
|
4536
4810
|
* tracking the stored id so the reply correlates by it;
|
|
4537
4811
|
* - `unknown`/null id → re-dispatch (opencode assigns a fresh id) + attach a watcher.
|
|
@@ -4595,7 +4869,16 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4595
4869
|
void this.postSignal(row.conversation_id, row.id, "readopt_done");
|
|
4596
4870
|
return;
|
|
4597
4871
|
}
|
|
4598
|
-
|
|
4872
|
+
const restartAborted = state === "failed" && sessionOngoing === false && isAbortedTerminalReply(messages, ocId ?? "");
|
|
4873
|
+
if (restartAborted) {
|
|
4874
|
+
this.log({
|
|
4875
|
+
level: "info",
|
|
4876
|
+
message: `Re-adopt: message ${row.id.slice(0, 8)} carries an abort-shaped terminal error and session ${sessionId.slice(0, 8)} is not-ongoing per GET /session/status \u2014 restart orphan, re-dispatching instead of marking it permanently failed`,
|
|
4877
|
+
conversation_id: row.conversation_id,
|
|
4878
|
+
message_id: row.id
|
|
4879
|
+
});
|
|
4880
|
+
}
|
|
4881
|
+
if (state === "failed" && !restartAborted) {
|
|
4599
4882
|
const error2 = messageError(messages, ocId ?? "") ?? void 0;
|
|
4600
4883
|
const usage = messageUsage(messages, ocId ?? "");
|
|
4601
4884
|
const failure = await this.classifyModelAuthFailure(messages, ocId ?? "");
|
|
@@ -4873,7 +5156,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4873
5156
|
opencode_model: row.opencode_model,
|
|
4874
5157
|
source_message_id: row.source_message_id,
|
|
4875
5158
|
slack_user_id: row.slack_user_id,
|
|
4876
|
-
attachments: row.attachments ?? null
|
|
5159
|
+
attachments: row.attachments ?? null,
|
|
5160
|
+
opencode_message_id: row.opencode_message_id
|
|
4877
5161
|
};
|
|
4878
5162
|
}
|
|
4879
5163
|
/**
|
|
@@ -6999,7 +7283,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
6999
7283
|
[]
|
|
7000
7284
|
).option(
|
|
7001
7285
|
"--tunnel-ready-file <path>",
|
|
7002
|
-
"Path to write once the tunnel is connected (
|
|
7286
|
+
"Path to write once the tunnel is connected (opt-in; unused unless an operator/image sets it)"
|
|
7003
7287
|
).action(
|
|
7004
7288
|
(options) => {
|
|
7005
7289
|
run({
|