@evident-ai/cli 3.5.2-dev.ffa823b → 3.6.1-dev.8d3e144
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 +469 -81
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1293,7 +1293,11 @@ var FORWARD_FAILURE_REASON_HEADER = "X-Evident-Failure-Reason";
|
|
|
1293
1293
|
function log(level, event, fields) {
|
|
1294
1294
|
const method = level === "debug" ? "log" : level;
|
|
1295
1295
|
try {
|
|
1296
|
-
|
|
1296
|
+
const payload = Object.fromEntries(
|
|
1297
|
+
Object.entries({ level, event, ...fields }).filter(([, value]) => value !== void 0)
|
|
1298
|
+
);
|
|
1299
|
+
const secondArgument = globalThis.navigator?.userAgent === "Cloudflare-Workers" ? payload : JSON.stringify(payload);
|
|
1300
|
+
console[method]("[evident]", secondArgument);
|
|
1297
1301
|
} catch (err) {
|
|
1298
1302
|
console.error(
|
|
1299
1303
|
"[evident] log_serialize_failed",
|
|
@@ -1322,7 +1326,7 @@ import ora4 from "ora";
|
|
|
1322
1326
|
import { select as select4 } from "@inquirer/prompts";
|
|
1323
1327
|
|
|
1324
1328
|
// src/lib/telemetry.ts
|
|
1325
|
-
var CLI_VERSION = (true ? "3.
|
|
1329
|
+
var CLI_VERSION = (true ? "3.6.1-dev.8d3e144" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1326
1330
|
function getCliVersion() {
|
|
1327
1331
|
return CLI_VERSION;
|
|
1328
1332
|
}
|
|
@@ -3518,19 +3522,32 @@ function isAbortedTerminalReply(messages, userMessageId) {
|
|
|
3518
3522
|
}
|
|
3519
3523
|
return false;
|
|
3520
3524
|
}
|
|
3525
|
+
var CLAUDE_AUTH_EXPIRY_MESSAGES = Object.freeze([
|
|
3526
|
+
"Claude Code credentials are unavailable or expired. Run `claude` to refresh them."
|
|
3527
|
+
]);
|
|
3528
|
+
function extractReplyErrorShape(reply) {
|
|
3529
|
+
const error2 = errorOf(reply);
|
|
3530
|
+
if (error2 == null || typeof error2 !== "object") {
|
|
3531
|
+
return { name: null, statusCode: null };
|
|
3532
|
+
}
|
|
3533
|
+
const e = error2;
|
|
3534
|
+
const name = typeof e.name === "string" ? e.name.slice(0, 100) : null;
|
|
3535
|
+
const data = e.data;
|
|
3536
|
+
const statusCode = typeof data?.statusCode === "number" && Number.isFinite(data.statusCode) ? data.statusCode : null;
|
|
3537
|
+
return { name, statusCode };
|
|
3538
|
+
}
|
|
3521
3539
|
function classifyReplyAuthError(reply) {
|
|
3522
3540
|
const error2 = errorOf(reply);
|
|
3523
3541
|
if (error2 == null || typeof error2 !== "object") return null;
|
|
3524
3542
|
const e = error2;
|
|
3525
3543
|
const replyProviderId = reply?.info?.providerID ?? null;
|
|
3526
3544
|
const replyModelId = reply?.info?.modelID ?? null;
|
|
3545
|
+
const data = e.data;
|
|
3527
3546
|
if (e.name === "ProviderAuthError") {
|
|
3528
|
-
const data = e.data;
|
|
3529
3547
|
const providerId = typeof data?.providerID === "string" && data.providerID || replyProviderId;
|
|
3530
3548
|
return { kind: "model_auth", providerId, modelId: replyModelId, reason: "missing" };
|
|
3531
3549
|
}
|
|
3532
3550
|
if (e.name === "APIError") {
|
|
3533
|
-
const data = e.data;
|
|
3534
3551
|
const statusCode = data?.statusCode;
|
|
3535
3552
|
if (statusCode === 401 || statusCode === 403) {
|
|
3536
3553
|
return {
|
|
@@ -3541,11 +3558,23 @@ function classifyReplyAuthError(reply) {
|
|
|
3541
3558
|
};
|
|
3542
3559
|
}
|
|
3543
3560
|
}
|
|
3561
|
+
const errorMessage3 = typeof data?.message === "string" ? data.message : typeof e.message === "string" ? e.message : null;
|
|
3562
|
+
if (errorMessage3 !== null && CLAUDE_AUTH_EXPIRY_MESSAGES.some((knownMessage) => errorMessage3.includes(knownMessage))) {
|
|
3563
|
+
return {
|
|
3564
|
+
kind: "model_auth",
|
|
3565
|
+
providerId: typeof data?.providerID === "string" && data.providerID || replyProviderId,
|
|
3566
|
+
modelId: replyModelId,
|
|
3567
|
+
reason: "rejected"
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3544
3570
|
return null;
|
|
3545
3571
|
}
|
|
3546
3572
|
function messageFailure(messages, userMessageId) {
|
|
3547
3573
|
return classifyReplyAuthError(findLastAssistantReplyFor(messages, userMessageId));
|
|
3548
3574
|
}
|
|
3575
|
+
function isProviderModelNotFoundError(reason) {
|
|
3576
|
+
return reason.includes("Model not found:");
|
|
3577
|
+
}
|
|
3549
3578
|
function findLatestSubagentAuthOutcome(messages, sinceMs) {
|
|
3550
3579
|
if (!messages || messages.length === 0) return null;
|
|
3551
3580
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -3754,23 +3783,49 @@ async function readSessionErrorStream(port, options, client) {
|
|
|
3754
3783
|
if (reader) void reader.cancel().catch(() => void 0);
|
|
3755
3784
|
}
|
|
3756
3785
|
}
|
|
3757
|
-
async function
|
|
3758
|
-
if (client?.version === "v2") return;
|
|
3786
|
+
async function disposeAndReloadProviderConfig(port, client) {
|
|
3787
|
+
if (client?.version === "v2") return { outcome: "skipped_v2" };
|
|
3759
3788
|
try {
|
|
3760
|
-
const res = await requestWithClient(
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3789
|
+
const res = await requestWithClient(
|
|
3790
|
+
port,
|
|
3791
|
+
client,
|
|
3792
|
+
"/config",
|
|
3793
|
+
{
|
|
3794
|
+
method: "PATCH",
|
|
3795
|
+
headers: { "Content-Type": "application/json" },
|
|
3796
|
+
body: JSON.stringify({})
|
|
3797
|
+
},
|
|
3798
|
+
{ allowStatuses: ALL_HTTP_STATUSES }
|
|
3799
|
+
);
|
|
3765
3800
|
if (!res.ok) {
|
|
3766
3801
|
console.error(
|
|
3767
|
-
`[
|
|
3802
|
+
`[disposeAndReloadProviderConfig] PATCH /config returned HTTP ${res.status} (port ${port})`
|
|
3768
3803
|
);
|
|
3804
|
+
return { outcome: "failed", status: res.status, detail: null };
|
|
3769
3805
|
}
|
|
3806
|
+
return { outcome: "reloaded" };
|
|
3770
3807
|
} catch (err) {
|
|
3771
3808
|
console.error(
|
|
3772
|
-
`[
|
|
3809
|
+
`[disposeAndReloadProviderConfig] PATCH /config failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3773
3810
|
);
|
|
3811
|
+
return {
|
|
3812
|
+
outcome: "failed",
|
|
3813
|
+
status: null,
|
|
3814
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
async function probeOpenCodeRequestReadiness(port, client) {
|
|
3819
|
+
try {
|
|
3820
|
+
const res = await requestWithClient(port, client, "/config", void 0, {
|
|
3821
|
+
allowStatuses: ALL_HTTP_STATUSES
|
|
3822
|
+
});
|
|
3823
|
+
const body = await res.text();
|
|
3824
|
+
return { ok: res.ok, status: res.status, bodyEmpty: body.trim().length === 0, detail: null };
|
|
3825
|
+
} catch (err) {
|
|
3826
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
3827
|
+
console.error(`[probeOpenCodeRequestReadiness] GET /config failed (port ${port}): ${detail}`);
|
|
3828
|
+
return { ok: false, status: null, bodyEmpty: null, detail };
|
|
3774
3829
|
}
|
|
3775
3830
|
}
|
|
3776
3831
|
|
|
@@ -7537,7 +7592,7 @@ var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
|
7537
7592
|
var MAX_BUFFERED_SESSION_ERRORS = 256;
|
|
7538
7593
|
var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
|
|
7539
7594
|
var SESSION_STORE_UNWRITABLE_ERROR = "The runner session database could not be written, so this turn did not finish. Nothing was lost from the queue. Clear disk space and retry.";
|
|
7540
|
-
var SESSION_ERROR_RETAINED_DETAIL = "
|
|
7595
|
+
var SESSION_ERROR_RETAINED_DETAIL = "Runner-observed OpenCode session error: ";
|
|
7541
7596
|
var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
|
|
7542
7597
|
var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
7543
7598
|
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
@@ -7661,12 +7716,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7661
7716
|
/**
|
|
7662
7717
|
* Per-conversation throttle state for the #183 recurrence warning (#1618
|
|
7663
7718
|
* WI-4) — see `reportWedgedConversation`'s doc comment for why this exists.
|
|
7664
|
-
* `firstWedgedAt` anchors `stuck_for_ms`; `
|
|
7665
|
-
*
|
|
7666
|
-
*
|
|
7667
|
-
*
|
|
7668
|
-
* conversation dispatches anything
|
|
7669
|
-
*
|
|
7719
|
+
* `firstWedgedAt` anchors `stuck_for_ms`; `warnAnchorAt` is the throttle
|
|
7720
|
+
* anchor from first detection through the first warning, then from each
|
|
7721
|
+
* warning onward; `consecutiveTicks` is reported in the log text so the
|
|
7722
|
+
* operator sees magnitude, not repetition. Cleared the moment the
|
|
7723
|
+
* conversation dispatches anything, or when it drops off the pending list
|
|
7724
|
+
* (`runDrain`'s prune, the dominant real-world path — a wedge normally ends
|
|
7725
|
+
* with its message completing, not a fresh dispatch on the same
|
|
7726
|
+
* conversation) — either way a fresh wedge, if it recurs, is a new
|
|
7727
|
+
* incident. Bounded FIFO, mirroring `supersededSessions`
|
|
7670
7728
|
* (`MAX_WEDGED_CONVERSATIONS`).
|
|
7671
7729
|
*/
|
|
7672
7730
|
wedgeWarnings = /* @__PURE__ */ new Map();
|
|
@@ -7896,6 +7954,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7896
7954
|
subagentInvocationPrefetches = /* @__PURE__ */ new Map();
|
|
7897
7955
|
/** Serialises drains so a reconnect during a drain doesn't double-process. */
|
|
7898
7956
|
draining = false;
|
|
7957
|
+
pendingProviderRegistryRepair = null;
|
|
7899
7958
|
/**
|
|
7900
7959
|
* Serialises runner-file syncs (#559) so the ~2s poll tick and a concurrent
|
|
7901
7960
|
* drain ping don't download, write and ack the same file twice.
|
|
@@ -8081,8 +8140,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8081
8140
|
async runDrain() {
|
|
8082
8141
|
let dispatched = 0;
|
|
8083
8142
|
try {
|
|
8143
|
+
const pendingRepair = this.pendingProviderRegistryRepair;
|
|
8144
|
+
if (pendingRepair && this.protectedSessionIds().size === 0) {
|
|
8145
|
+
try {
|
|
8146
|
+
await this.issueProviderRegistryReload(
|
|
8147
|
+
pendingRepair.conversationId,
|
|
8148
|
+
pendingRepair.messageId
|
|
8149
|
+
);
|
|
8150
|
+
} catch (err) {
|
|
8151
|
+
this.log({
|
|
8152
|
+
level: "error",
|
|
8153
|
+
message: `Provider registry repair adoption failed for message ${pendingRepair.messageId.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`,
|
|
8154
|
+
conversation_id: pendingRepair.conversationId,
|
|
8155
|
+
message_id: pendingRepair.messageId
|
|
8156
|
+
});
|
|
8157
|
+
}
|
|
8158
|
+
return 0;
|
|
8159
|
+
}
|
|
8084
8160
|
this.pendingMessageIds.clear();
|
|
8085
8161
|
const conversations = await this.getPendingConversations();
|
|
8162
|
+
if (this.wedgeWarnings.size > 0) {
|
|
8163
|
+
const pendingConversationIds = new Set(conversations.map(({ id }) => id));
|
|
8164
|
+
for (const conversationId of this.wedgeWarnings.keys()) {
|
|
8165
|
+
if (!pendingConversationIds.has(conversationId)) {
|
|
8166
|
+
this.wedgeWarnings.delete(conversationId);
|
|
8167
|
+
}
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
8086
8170
|
if (this.recycleRequestedFlag) {
|
|
8087
8171
|
this.stop();
|
|
8088
8172
|
}
|
|
@@ -8619,14 +8703,13 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8619
8703
|
* skip-because-already-`dispatched`, the exact signature of a message stuck
|
|
8620
8704
|
* acknowledged-but-never-worked.
|
|
8621
8705
|
*
|
|
8622
|
-
*
|
|
8623
|
-
*
|
|
8624
|
-
*
|
|
8625
|
-
*
|
|
8626
|
-
*
|
|
8627
|
-
*
|
|
8628
|
-
*
|
|
8629
|
-
* than repetition.
|
|
8706
|
+
* The first detection starts the clock and emits a local `debug` entry. The
|
|
8707
|
+
* `warn` log and paired `dispatch_wedged` signal fire once the wedge has
|
|
8708
|
+
* lasted `wedgeWarningIntervalMs`, then at most once per interval, naming the
|
|
8709
|
+
* consecutive-tick count so the operator sees magnitude rather than repetition.
|
|
8710
|
+
* A watcher stall that the loop-liveness watchdog recovers is expected and is
|
|
8711
|
+
* already reported by its `watcher_recovered` signal, so it does not reach an
|
|
8712
|
+
* operator through this warning.
|
|
8630
8713
|
*
|
|
8631
8714
|
* Deliberately does NOT trigger a release: WI-1's `reconcileWatchers` runs
|
|
8632
8715
|
* unconditionally on this same tick and is already recovering anything it
|
|
@@ -8638,36 +8721,41 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8638
8721
|
const existing = this.wedgeWarnings.get(conv.id);
|
|
8639
8722
|
const firstWedgedAt = existing?.firstWedgedAt ?? now;
|
|
8640
8723
|
const consecutiveTicks = (existing?.consecutiveTicks ?? 0) + 1;
|
|
8641
|
-
const
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
this.
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8724
|
+
const warnAnchorAt = existing?.warnAnchorAt ?? now;
|
|
8725
|
+
const dueForWarn = now - warnAnchorAt >= this.wedgeWarningIntervalMs;
|
|
8726
|
+
if (!existing) {
|
|
8727
|
+
this.log({
|
|
8728
|
+
level: "debug",
|
|
8729
|
+
message: `Detected a possible wedge for conversation ${conv.id.slice(0, 8)} with ${messages.length} pending message(s); it will be reported only if it outlasts the watchdog's recovery.`,
|
|
8730
|
+
conversation_id: conv.id
|
|
8731
|
+
});
|
|
8732
|
+
}
|
|
8733
|
+
let untracked = 0;
|
|
8734
|
+
if (dueForWarn) {
|
|
8735
|
+
const stuckForMs = now - firstWedgedAt;
|
|
8736
|
+
untracked = this.countUntrackedIds(messages);
|
|
8737
|
+
this.log({
|
|
8738
|
+
level: "warn",
|
|
8739
|
+
message: `Conversation ${conv.id.slice(0, 8)} has ${messages.length} pending message(s) but ALL are already marked dispatched locally (in-flight set: ${this.dispatched.size}) \u2014 none sent to OpenCode for ${consecutiveTicks} consecutive tick(s) now (${Math.round(stuckForMs / 1e3)}s stuck). ` + (untracked > 0 ? `${untracked} of these id(s) are tracked by NO watcher \u2014 the dispatched/in-flight pairing invariant is violated for this conversation, which will NOT self-heal and needs a runner restart.` : `A watcher is tracking this work; the loop-liveness watchdog is already recovering it.`),
|
|
8740
|
+
conversation_id: conv.id
|
|
8648
8741
|
});
|
|
8649
|
-
return;
|
|
8650
8742
|
}
|
|
8651
|
-
const stuckForMs = now - firstWedgedAt;
|
|
8652
|
-
const untracked = this.countUntrackedIds(messages);
|
|
8653
|
-
this.log({
|
|
8654
|
-
level: "warn",
|
|
8655
|
-
message: `Conversation ${conv.id.slice(0, 8)} has ${messages.length} pending message(s) but ALL are already marked dispatched locally (in-flight set: ${this.dispatched.size}) \u2014 none sent to OpenCode for ${consecutiveTicks} consecutive tick(s) now (${stuckForMs}ms stuck). ` + (untracked > 0 ? `${untracked} of these id(s) are tracked by NO watcher \u2014 the dispatched/in-flight pairing invariant is violated for this conversation, which will NOT self-heal and needs a runner restart.` : `A watcher is tracking this work; the loop-liveness watchdog is already recovering it.`),
|
|
8656
|
-
conversation_id: conv.id
|
|
8657
|
-
});
|
|
8658
8743
|
this.wedgeWarnings.delete(conv.id);
|
|
8659
|
-
this.wedgeWarnings.set(conv.id, {
|
|
8744
|
+
this.wedgeWarnings.set(conv.id, {
|
|
8745
|
+
firstWedgedAt,
|
|
8746
|
+
warnAnchorAt: dueForWarn ? now : warnAnchorAt,
|
|
8747
|
+
consecutiveTicks
|
|
8748
|
+
});
|
|
8660
8749
|
while (this.wedgeWarnings.size > MAX_WEDGED_CONVERSATIONS) {
|
|
8661
8750
|
const oldest = this.wedgeWarnings.keys().next().value;
|
|
8662
8751
|
if (oldest === void 0) break;
|
|
8663
8752
|
this.wedgeWarnings.delete(oldest);
|
|
8664
8753
|
}
|
|
8665
|
-
if (
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
}
|
|
8754
|
+
if (!dueForWarn) return;
|
|
8755
|
+
void this.postSignal(conv.id, messages[0].id, "dispatch_wedged", {
|
|
8756
|
+
stuck_for_ms: now - firstWedgedAt,
|
|
8757
|
+
untracked
|
|
8758
|
+
});
|
|
8671
8759
|
}
|
|
8672
8760
|
/**
|
|
8673
8761
|
* How many of `messages`' ids are tracked by NO watcher's `inFlight` (#1618
|
|
@@ -9933,14 +10021,40 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9933
10021
|
conversation_id: watcher.conv.id,
|
|
9934
10022
|
message_id: inFlight.evidentMessageId
|
|
9935
10023
|
});
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
inFlight.
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
10024
|
+
const isMissingProvider = isProviderModelNotFoundError(event.reason);
|
|
10025
|
+
if (isMissingProvider) {
|
|
10026
|
+
const dispatchedModel = inFlight.message.opencode_model;
|
|
10027
|
+
const separator = dispatchedModel?.indexOf("/") ?? -1;
|
|
10028
|
+
const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
|
|
10029
|
+
await this.markFailed(
|
|
10030
|
+
watcher.conv.id,
|
|
10031
|
+
inFlight.evidentMessageId,
|
|
10032
|
+
event.sessionId,
|
|
10033
|
+
`OpenCode could not run this turn: ${event.reason}`,
|
|
10034
|
+
void 0,
|
|
10035
|
+
{
|
|
10036
|
+
classification: {
|
|
10037
|
+
kind: "model_auth",
|
|
10038
|
+
providerId: hasProviderAndModel ? dispatchedModel.slice(0, separator) : null,
|
|
10039
|
+
modelId: hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null,
|
|
10040
|
+
reason: "missing"
|
|
10041
|
+
},
|
|
10042
|
+
errorShape: { name: null, statusCode: null }
|
|
10043
|
+
}
|
|
10044
|
+
);
|
|
10045
|
+
} else {
|
|
10046
|
+
await this.markFailed(
|
|
10047
|
+
watcher.conv.id,
|
|
10048
|
+
inFlight.evidentMessageId,
|
|
10049
|
+
event.sessionId,
|
|
10050
|
+
`OpenCode could not run this turn: ${event.reason}`
|
|
10051
|
+
);
|
|
10052
|
+
}
|
|
9942
10053
|
inFlight.done = true;
|
|
9943
10054
|
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
10055
|
+
if (isMissingProvider) {
|
|
10056
|
+
await this.repairProviderRegistry(watcher.conv.id, inFlight.evidentMessageId);
|
|
10057
|
+
}
|
|
9944
10058
|
} catch (err) {
|
|
9945
10059
|
if (err instanceof ChannelAuthError) {
|
|
9946
10060
|
this.log({
|
|
@@ -9961,6 +10075,44 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9961
10075
|
this.sessionErrorHandled.delete(inFlight.evidentMessageId);
|
|
9962
10076
|
}
|
|
9963
10077
|
}
|
|
10078
|
+
async repairProviderRegistry(conversationId, messageId) {
|
|
10079
|
+
if (this.isV2) {
|
|
10080
|
+
void this.postSignal(conversationId, messageId, "provider_registry_reload_unsupported");
|
|
10081
|
+
return;
|
|
10082
|
+
}
|
|
10083
|
+
const activeSessions = this.protectedSessionIds().size;
|
|
10084
|
+
if (activeSessions > 0 || this.draining) {
|
|
10085
|
+
this.pendingProviderRegistryRepair = { conversationId, messageId };
|
|
10086
|
+
void this.postSignal(conversationId, messageId, "provider_registry_repair_deferred", {
|
|
10087
|
+
active_sessions: activeSessions
|
|
10088
|
+
});
|
|
10089
|
+
this.log({
|
|
10090
|
+
level: "warn",
|
|
10091
|
+
message: `Provider registry repair deferred until a lifecycle-safe point; ${activeSessions} active session(s) protected`,
|
|
10092
|
+
conversation_id: conversationId,
|
|
10093
|
+
message_id: messageId
|
|
10094
|
+
});
|
|
10095
|
+
return;
|
|
10096
|
+
}
|
|
10097
|
+
await this.issueProviderRegistryReload(conversationId, messageId);
|
|
10098
|
+
}
|
|
10099
|
+
async issueProviderRegistryReload(conversationId, messageId) {
|
|
10100
|
+
this.pendingProviderRegistryRepair = null;
|
|
10101
|
+
const result = await disposeAndReloadProviderConfig(this.port, this.openCodeClient);
|
|
10102
|
+
if (result.outcome === "failed") {
|
|
10103
|
+
const failure = result.status === null ? `transport failure${result.detail ? `: ${result.detail}` : ""}` : `HTTP ${result.status}`;
|
|
10104
|
+
this.log({
|
|
10105
|
+
level: "warn",
|
|
10106
|
+
message: `Provider registry reload failed (${failure})`,
|
|
10107
|
+
conversation_id: conversationId,
|
|
10108
|
+
message_id: messageId
|
|
10109
|
+
});
|
|
10110
|
+
}
|
|
10111
|
+
const repairOutcome = result.outcome === "reloaded" ? "reloaded" : "reload_failed";
|
|
10112
|
+
void this.postSignal(conversationId, messageId, "provider_registry_reload_issued", {
|
|
10113
|
+
repair_outcome: repairOutcome
|
|
10114
|
+
});
|
|
10115
|
+
}
|
|
9964
10116
|
/**
|
|
9965
10117
|
* The per-session polling loop (WI-3). Once per tick it:
|
|
9966
10118
|
* 1. polls `GET /session/:id/message` once and, per in-flight message,
|
|
@@ -10152,7 +10304,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10152
10304
|
if (!inFlight.done) {
|
|
10153
10305
|
const messageErrorText = messageError(messages, inFlight.opencodeMessageId);
|
|
10154
10306
|
const retainedReason = this.readRetainedSessionError(sessionId);
|
|
10155
|
-
const retainedError = retainedReason === null ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
|
|
10307
|
+
const retainedError = retainedReason === null || messageErrorText !== null && messageErrorText.includes(retainedReason) ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
|
|
10156
10308
|
const observedCause = this.sessionStoreHealth?.readCause(sessionId);
|
|
10157
10309
|
const causeError = observedCause === null || observedCause === void 0 ? null : `${SESSION_ERROR_OBSERVED_CAUSE}${observedCause}`;
|
|
10158
10310
|
const storeError = this.sessionStoreHealth?.isUnwritable() ? SESSION_STORE_UNWRITABLE_ERROR : null;
|
|
@@ -10648,7 +10800,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10648
10800
|
usage,
|
|
10649
10801
|
void 0,
|
|
10650
10802
|
usageAgentName,
|
|
10651
|
-
subagentInvocations
|
|
10803
|
+
subagentInvocations,
|
|
10804
|
+
true
|
|
10652
10805
|
);
|
|
10653
10806
|
} catch (err) {
|
|
10654
10807
|
if (err instanceof ChannelAuthError) throw err;
|
|
@@ -12494,7 +12647,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12494
12647
|
* exists but is wedged, so the next attempt must get a fresh one
|
|
12495
12648
|
* instead of reusing it — see WI-1's server-side null-clearing PATCH).
|
|
12496
12649
|
*/
|
|
12497
|
-
async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations) {
|
|
12650
|
+
async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations, abandonedWithoutAnswer) {
|
|
12498
12651
|
const body = { status: "failed" };
|
|
12499
12652
|
if (sessionId === null) {
|
|
12500
12653
|
body.opencode_session_id = null;
|
|
@@ -12507,12 +12660,19 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12507
12660
|
if (subagentInvocations && subagentInvocations.length > 0) {
|
|
12508
12661
|
body.subagent_invocations = subagentInvocations;
|
|
12509
12662
|
}
|
|
12510
|
-
if (failure) {
|
|
12511
|
-
body.
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
body.
|
|
12663
|
+
if (failure && failure.errorShape.name !== null) {
|
|
12664
|
+
body.failure_error_name = failure.errorShape.name;
|
|
12665
|
+
}
|
|
12666
|
+
if (failure && failure.errorShape.statusCode !== null) {
|
|
12667
|
+
body.failure_status_code = failure.errorShape.statusCode;
|
|
12515
12668
|
}
|
|
12669
|
+
if (failure?.classification) {
|
|
12670
|
+
body.failure_kind = failure.classification.kind;
|
|
12671
|
+
body.failure_provider_id = failure.classification.providerId;
|
|
12672
|
+
body.failure_model_id = failure.classification.modelId;
|
|
12673
|
+
body.failure_reason = failure.classification.reason;
|
|
12674
|
+
}
|
|
12675
|
+
if (abandonedWithoutAnswer) body.abandoned_without_answer = true;
|
|
12516
12676
|
try {
|
|
12517
12677
|
await this.callWithRetry(
|
|
12518
12678
|
"marking message as failed",
|
|
@@ -12532,27 +12692,28 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12532
12692
|
this.clearSubagentInvocationCaches(messageId);
|
|
12533
12693
|
}
|
|
12534
12694
|
/**
|
|
12535
|
-
* Classify an errored turn
|
|
12695
|
+
* Classify an errored turn and extract its bounded error shape for `markFailed`.
|
|
12536
12696
|
*
|
|
12537
12697
|
* `messageFailure` alone (structured OpenCode error → `model_auth`) covers
|
|
12538
12698
|
* most cases; when it returns `null` on this ALREADY-FAILED turn, fall back
|
|
12539
12699
|
* to the P1-2b zero-provider check — one extra loopback call to
|
|
12540
12700
|
* `hasAnyConfiguredProvider`, only reached when the structured classifier
|
|
12541
12701
|
* couldn't place it. Fails open (never throws): a fallback probe failure
|
|
12542
|
-
* (`null`/indeterminate) leaves the classification `null
|
|
12543
|
-
*
|
|
12702
|
+
* (`null`/indeterminate) leaves the classification `null`; the raw error shape
|
|
12703
|
+
* is still retained for diagnostics.
|
|
12544
12704
|
*/
|
|
12545
12705
|
async classifyModelAuthFailure(messages, userMessageId) {
|
|
12546
|
-
const classified = messageFailure(messages, userMessageId);
|
|
12547
|
-
if (classified != null) return classified;
|
|
12548
12706
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
12549
|
-
const
|
|
12550
|
-
|
|
12707
|
+
const errorShape = extractReplyErrorShape(reply);
|
|
12708
|
+
const classified = messageFailure(messages, userMessageId);
|
|
12709
|
+
if (classified != null) return { classification: classified, errorShape };
|
|
12710
|
+
const classification = applyZeroProviderFallback(
|
|
12551
12711
|
classified,
|
|
12552
|
-
|
|
12712
|
+
await this.hasAnyConfiguredProvider(),
|
|
12553
12713
|
reply?.info?.providerID ?? null,
|
|
12554
12714
|
reply?.info?.modelID ?? null
|
|
12555
12715
|
);
|
|
12716
|
+
return { classification, errorShape };
|
|
12556
12717
|
}
|
|
12557
12718
|
async recordSubagentModelAuthFailure(failure, conversationId, messageId) {
|
|
12558
12719
|
const providerId = failure.providerId ?? "(unknown)";
|
|
@@ -13649,6 +13810,8 @@ function createCredentialSync({
|
|
|
13649
13810
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
13650
13811
|
var MAX_DISCONNECT_REASON_LENGTH = 200;
|
|
13651
13812
|
var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
|
|
13813
|
+
var CREDENTIAL_ADOPTION_PROBE_ATTEMPTS = 3;
|
|
13814
|
+
var CREDENTIAL_ADOPTION_PROBE_INTERVAL_MS = 1e3;
|
|
13652
13815
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
13653
13816
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
13654
13817
|
var TELEMETRY_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
@@ -13996,6 +14159,125 @@ async function handleAuthError(state, error2) {
|
|
|
13996
14159
|
return { success: false };
|
|
13997
14160
|
}
|
|
13998
14161
|
}
|
|
14162
|
+
function logCredentialAdoptionActivity(state, activity, level) {
|
|
14163
|
+
const message = (() => {
|
|
14164
|
+
switch (activity.outcome) {
|
|
14165
|
+
case "deferred":
|
|
14166
|
+
return `CREDENTIAL-ADOPTION: deferred generation ${activity.generation} with ${activity.active_sessions} active session(s)`;
|
|
14167
|
+
case "reloaded_ready":
|
|
14168
|
+
return `CREDENTIAL-ADOPTION: adopted generation ${activity.generation} with ${activity.active_sessions} active session(s); readiness probe succeeded after ${activity.probe_attempts} attempt(s)`;
|
|
14169
|
+
case "reloaded_interrupted": {
|
|
14170
|
+
const response = activity.probe_status === null ? "without an HTTP response" : `with HTTP ${activity.probe_status} and ${activity.probe_body_empty ? "an empty" : "a non-empty"} body`;
|
|
14171
|
+
return `CREDENTIAL-ADOPTION: generation ${activity.generation} with ${activity.active_sessions} active session(s); replacement instance answered ${response} after ${activity.probe_attempts} probe attempt(s)`;
|
|
14172
|
+
}
|
|
14173
|
+
case "reload_failed":
|
|
14174
|
+
return `CREDENTIAL-ADOPTION: failed to adopt generation ${activity.generation} with ${activity.active_sessions} active session(s)`;
|
|
14175
|
+
case "skipped_v2":
|
|
14176
|
+
return `CREDENTIAL-ADOPTION: skipped generation ${activity.generation} with ${activity.active_sessions} active session(s) on OpenCode V2`;
|
|
14177
|
+
}
|
|
14178
|
+
})();
|
|
14179
|
+
logActivity(state, {
|
|
14180
|
+
type: level === "error" ? "error" : "info",
|
|
14181
|
+
level,
|
|
14182
|
+
...level === "error" ? { error: message } : { message },
|
|
14183
|
+
metadata: {
|
|
14184
|
+
...activity,
|
|
14185
|
+
opencode_version: state.opencodeVersion,
|
|
14186
|
+
port: state.port
|
|
14187
|
+
}
|
|
14188
|
+
});
|
|
14189
|
+
}
|
|
14190
|
+
async function adoptCredentialGeneration(state, generation, adoption, activeSessions, deferredForMs) {
|
|
14191
|
+
const reload = await disposeAndReloadProviderConfig(
|
|
14192
|
+
state.port,
|
|
14193
|
+
state.opencodeClient ?? void 0
|
|
14194
|
+
);
|
|
14195
|
+
if (reload.outcome === "failed") {
|
|
14196
|
+
logCredentialAdoptionActivity(
|
|
14197
|
+
state,
|
|
14198
|
+
{
|
|
14199
|
+
outcome: "reload_failed",
|
|
14200
|
+
generation,
|
|
14201
|
+
active_sessions: activeSessions,
|
|
14202
|
+
adoption,
|
|
14203
|
+
deferred_for_ms: deferredForMs,
|
|
14204
|
+
reload_status: reload.status,
|
|
14205
|
+
reload_detail: reload.detail,
|
|
14206
|
+
probe_attempts: null,
|
|
14207
|
+
probe_status: null,
|
|
14208
|
+
probe_body_empty: null,
|
|
14209
|
+
probe_detail: null
|
|
14210
|
+
},
|
|
14211
|
+
"error"
|
|
14212
|
+
);
|
|
14213
|
+
return;
|
|
14214
|
+
}
|
|
14215
|
+
if (reload.outcome === "skipped_v2") {
|
|
14216
|
+
logCredentialAdoptionActivity(
|
|
14217
|
+
state,
|
|
14218
|
+
{
|
|
14219
|
+
outcome: "skipped_v2",
|
|
14220
|
+
generation,
|
|
14221
|
+
active_sessions: activeSessions,
|
|
14222
|
+
adoption,
|
|
14223
|
+
deferred_for_ms: deferredForMs,
|
|
14224
|
+
reload_status: null,
|
|
14225
|
+
reload_detail: null,
|
|
14226
|
+
probe_attempts: null,
|
|
14227
|
+
probe_status: null,
|
|
14228
|
+
probe_body_empty: null,
|
|
14229
|
+
probe_detail: null
|
|
14230
|
+
},
|
|
14231
|
+
"debug"
|
|
14232
|
+
);
|
|
14233
|
+
return;
|
|
14234
|
+
}
|
|
14235
|
+
let lastProbe = null;
|
|
14236
|
+
let attempts = 0;
|
|
14237
|
+
for (attempts = 1; attempts <= CREDENTIAL_ADOPTION_PROBE_ATTEMPTS; attempts++) {
|
|
14238
|
+
lastProbe = await probeOpenCodeRequestReadiness(state.port, state.opencodeClient ?? void 0);
|
|
14239
|
+
if (lastProbe.ok) {
|
|
14240
|
+
logCredentialAdoptionActivity(
|
|
14241
|
+
state,
|
|
14242
|
+
{
|
|
14243
|
+
outcome: "reloaded_ready",
|
|
14244
|
+
generation,
|
|
14245
|
+
active_sessions: activeSessions,
|
|
14246
|
+
adoption,
|
|
14247
|
+
deferred_for_ms: deferredForMs,
|
|
14248
|
+
reload_status: null,
|
|
14249
|
+
reload_detail: null,
|
|
14250
|
+
probe_attempts: attempts,
|
|
14251
|
+
probe_status: lastProbe.status,
|
|
14252
|
+
probe_body_empty: lastProbe.bodyEmpty,
|
|
14253
|
+
probe_detail: lastProbe.detail
|
|
14254
|
+
},
|
|
14255
|
+
"warn"
|
|
14256
|
+
);
|
|
14257
|
+
return;
|
|
14258
|
+
}
|
|
14259
|
+
if (attempts < CREDENTIAL_ADOPTION_PROBE_ATTEMPTS) {
|
|
14260
|
+
await new Promise((resolve5) => setTimeout(resolve5, CREDENTIAL_ADOPTION_PROBE_INTERVAL_MS));
|
|
14261
|
+
}
|
|
14262
|
+
}
|
|
14263
|
+
logCredentialAdoptionActivity(
|
|
14264
|
+
state,
|
|
14265
|
+
{
|
|
14266
|
+
outcome: "reloaded_interrupted",
|
|
14267
|
+
generation,
|
|
14268
|
+
active_sessions: activeSessions,
|
|
14269
|
+
adoption,
|
|
14270
|
+
deferred_for_ms: deferredForMs,
|
|
14271
|
+
reload_status: null,
|
|
14272
|
+
reload_detail: null,
|
|
14273
|
+
probe_attempts: CREDENTIAL_ADOPTION_PROBE_ATTEMPTS,
|
|
14274
|
+
probe_status: lastProbe?.status ?? null,
|
|
14275
|
+
probe_body_empty: lastProbe?.bodyEmpty ?? null,
|
|
14276
|
+
probe_detail: lastProbe?.detail ?? null
|
|
14277
|
+
},
|
|
14278
|
+
"error"
|
|
14279
|
+
);
|
|
14280
|
+
}
|
|
13999
14281
|
async function driveChannels(state, driver) {
|
|
14000
14282
|
let idlePolls = 0;
|
|
14001
14283
|
let idleMs = 0;
|
|
@@ -14005,6 +14287,15 @@ async function driveChannels(state, driver) {
|
|
|
14005
14287
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
14006
14288
|
let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
|
|
14007
14289
|
let lastSeenOpencodeAuthApplies = driver.fileSyncActivity().opencodeAuthApplyCount;
|
|
14290
|
+
const credentialAdoption = {
|
|
14291
|
+
generation: 0,
|
|
14292
|
+
pendingGeneration: null,
|
|
14293
|
+
pendingSinceMs: null,
|
|
14294
|
+
deferredGeneration: null,
|
|
14295
|
+
adoption: 0
|
|
14296
|
+
};
|
|
14297
|
+
const stayAwakeHolds = () => state.stayAwakeUntilMs !== null && Date.now() < state.stayAwakeUntilMs;
|
|
14298
|
+
let stayAwakeSuppressionLogged = false;
|
|
14008
14299
|
while (state.running) {
|
|
14009
14300
|
const cycleStartedAtMs = performance.now();
|
|
14010
14301
|
let idleThisCycle = false;
|
|
@@ -14046,12 +14337,30 @@ async function driveChannels(state, driver) {
|
|
|
14046
14337
|
lastSeenOpencodeAuthApplies = opencodeAuthApplies;
|
|
14047
14338
|
if (opencodeAuthApplied) state.openaiUsageRearm?.();
|
|
14048
14339
|
if (claudeCredentialApplied || opencodeAuthApplied) {
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14340
|
+
credentialAdoption.generation++;
|
|
14341
|
+
if (state.opencodeApiVersion === "v2") {
|
|
14342
|
+
logCredentialAdoptionActivity(
|
|
14343
|
+
state,
|
|
14344
|
+
{
|
|
14345
|
+
outcome: "skipped_v2",
|
|
14346
|
+
generation: credentialAdoption.generation,
|
|
14347
|
+
active_sessions: driver.protectedSessionIds().size,
|
|
14348
|
+
adoption: null,
|
|
14349
|
+
deferred_for_ms: null,
|
|
14350
|
+
reload_status: null,
|
|
14351
|
+
reload_detail: null,
|
|
14352
|
+
probe_attempts: null,
|
|
14353
|
+
probe_status: null,
|
|
14354
|
+
probe_body_empty: null,
|
|
14355
|
+
probe_detail: null
|
|
14356
|
+
},
|
|
14357
|
+
"debug"
|
|
14358
|
+
);
|
|
14359
|
+
} else {
|
|
14360
|
+
credentialAdoption.pendingGeneration = credentialAdoption.generation;
|
|
14361
|
+
credentialAdoption.pendingSinceMs = Date.now();
|
|
14362
|
+
credentialAdoption.deferredGeneration = null;
|
|
14363
|
+
}
|
|
14055
14364
|
}
|
|
14056
14365
|
if (opencodeAuthApplied && state.opencodeApiVersion === "v2") {
|
|
14057
14366
|
void importOpenCodeV2CredentialsForState(state).catch(
|
|
@@ -14106,6 +14415,49 @@ async function driveChannels(state, driver) {
|
|
|
14106
14415
|
}
|
|
14107
14416
|
}
|
|
14108
14417
|
}
|
|
14418
|
+
if (credentialAdoption.pendingGeneration !== null) {
|
|
14419
|
+
const activeSessions = driver.protectedSessionIds();
|
|
14420
|
+
const generation = credentialAdoption.pendingGeneration;
|
|
14421
|
+
if (activeSessions.size > 0) {
|
|
14422
|
+
if (credentialAdoption.deferredGeneration !== generation) {
|
|
14423
|
+
credentialAdoption.deferredGeneration = generation;
|
|
14424
|
+
logCredentialAdoptionActivity(
|
|
14425
|
+
state,
|
|
14426
|
+
{
|
|
14427
|
+
outcome: "deferred",
|
|
14428
|
+
generation,
|
|
14429
|
+
active_sessions: activeSessions.size,
|
|
14430
|
+
adoption: null,
|
|
14431
|
+
deferred_for_ms: null,
|
|
14432
|
+
reload_status: null,
|
|
14433
|
+
reload_detail: null,
|
|
14434
|
+
probe_attempts: null,
|
|
14435
|
+
probe_status: null,
|
|
14436
|
+
probe_body_empty: null,
|
|
14437
|
+
probe_detail: null
|
|
14438
|
+
},
|
|
14439
|
+
"warn"
|
|
14440
|
+
);
|
|
14441
|
+
}
|
|
14442
|
+
} else {
|
|
14443
|
+
const deferredForMs = credentialAdoption.deferredGeneration === generation && credentialAdoption.pendingSinceMs !== null ? Math.max(0, Date.now() - credentialAdoption.pendingSinceMs) : null;
|
|
14444
|
+
credentialAdoption.pendingGeneration = null;
|
|
14445
|
+
credentialAdoption.pendingSinceMs = null;
|
|
14446
|
+
credentialAdoption.adoption++;
|
|
14447
|
+
void adoptCredentialGeneration(
|
|
14448
|
+
state,
|
|
14449
|
+
generation,
|
|
14450
|
+
credentialAdoption.adoption,
|
|
14451
|
+
activeSessions.size,
|
|
14452
|
+
deferredForMs
|
|
14453
|
+
).catch(
|
|
14454
|
+
(error2) => logActivity(state, {
|
|
14455
|
+
type: "error",
|
|
14456
|
+
error: `CREDENTIAL-ADOPTION: adoption failed for generation ${generation} with ${activeSessions.size} active session(s) on port ${state.port}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
14457
|
+
})
|
|
14458
|
+
);
|
|
14459
|
+
}
|
|
14460
|
+
}
|
|
14109
14461
|
await new Promise((resolve5) => setTimeout(resolve5, CHANNEL_POLL_INTERVAL_MS));
|
|
14110
14462
|
const cycleMs = performance.now() - cycleStartedAtMs;
|
|
14111
14463
|
if (idleThisCycle) idleMs += cycleMs;
|
|
@@ -14119,11 +14471,20 @@ async function driveChannels(state, driver) {
|
|
|
14119
14471
|
if (state.interactive) displayStatus(state);
|
|
14120
14472
|
break;
|
|
14121
14473
|
}
|
|
14122
|
-
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3) {
|
|
14474
|
+
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3 && !stayAwakeHolds()) {
|
|
14123
14475
|
logActivity(state, { type: "info", message: "Idle timeout reached" });
|
|
14124
14476
|
if (state.interactive) displayStatus(state);
|
|
14125
14477
|
break;
|
|
14126
14478
|
}
|
|
14479
|
+
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3 && stayAwakeHolds()) {
|
|
14480
|
+
if (!stayAwakeSuppressionLogged) {
|
|
14481
|
+
stayAwakeSuppressionLogged = true;
|
|
14482
|
+
logActivity(state, {
|
|
14483
|
+
type: "info",
|
|
14484
|
+
message: "Idle, but staying awake until the requested minimum passes"
|
|
14485
|
+
});
|
|
14486
|
+
}
|
|
14487
|
+
}
|
|
14127
14488
|
}
|
|
14128
14489
|
}
|
|
14129
14490
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
@@ -14452,10 +14813,12 @@ function scheduleUsageReporting(state, params) {
|
|
|
14452
14813
|
let phase = "dormant";
|
|
14453
14814
|
let rearmRequested = false;
|
|
14454
14815
|
const armProbe = () => {
|
|
14816
|
+
if (!state.running) return;
|
|
14455
14817
|
phase = "probe-pending";
|
|
14456
14818
|
params.setTimer(setTimeout(() => void tick(true), params.firstDelayMs()));
|
|
14457
14819
|
};
|
|
14458
14820
|
const scheduleNextTick = () => {
|
|
14821
|
+
if (!state.running) return;
|
|
14459
14822
|
if (rearmRequested) {
|
|
14460
14823
|
rearmRequested = false;
|
|
14461
14824
|
armProbe();
|
|
@@ -14489,6 +14852,7 @@ function scheduleUsageReporting(state, params) {
|
|
|
14489
14852
|
phase = "tick-in-flight";
|
|
14490
14853
|
try {
|
|
14491
14854
|
const usage = await params.fetchUsage();
|
|
14855
|
+
if (!state.running) return;
|
|
14492
14856
|
const result = await params.report(usage);
|
|
14493
14857
|
if (result.ok) {
|
|
14494
14858
|
if (consecutiveFailures > 0) {
|
|
@@ -14627,6 +14991,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14627
14991
|
message: lowDiskDecision.warning
|
|
14628
14992
|
});
|
|
14629
14993
|
}
|
|
14994
|
+
if (!state.running) return;
|
|
14630
14995
|
const result = await reportResourceUsage(state.agentId, state.authHeader, usage);
|
|
14631
14996
|
if (result.ok) {
|
|
14632
14997
|
if (consecutiveFailures > 0) {
|
|
@@ -14659,7 +15024,9 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14659
15024
|
message: `Resource usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
14660
15025
|
});
|
|
14661
15026
|
} finally {
|
|
14662
|
-
state.
|
|
15027
|
+
if (state.running) {
|
|
15028
|
+
state.resourceUsageTimer = setTimeout(() => void tick(), usageReportDelayMs());
|
|
15029
|
+
}
|
|
14663
15030
|
}
|
|
14664
15031
|
};
|
|
14665
15032
|
state.resourceUsageTimer = setTimeout(() => void tick(), firstReportDelayMs());
|
|
@@ -14862,6 +15229,8 @@ async function run(options) {
|
|
|
14862
15229
|
port: options.port ?? 4096,
|
|
14863
15230
|
conversationFilter: options.conversation ?? null,
|
|
14864
15231
|
idleTimeout: options.idleTimeout ?? null,
|
|
15232
|
+
stayAwakeSeconds: null,
|
|
15233
|
+
stayAwakeUntilMs: null,
|
|
14865
15234
|
json: options.json ?? false,
|
|
14866
15235
|
interactive,
|
|
14867
15236
|
logLevel,
|
|
@@ -15051,6 +15420,17 @@ async function run(options) {
|
|
|
15051
15420
|
}
|
|
15052
15421
|
spinner?.succeed(`Runner: ${validation.agent.name || state.agentId}`);
|
|
15053
15422
|
state.agentName = validation.agent.name;
|
|
15423
|
+
const requestedStayAwakeSeconds = validation.agent?.wake_stay_awake_seconds;
|
|
15424
|
+
if (typeof requestedStayAwakeSeconds === "number" && Number.isFinite(requestedStayAwakeSeconds) && requestedStayAwakeSeconds > 0) {
|
|
15425
|
+
state.stayAwakeSeconds = requestedStayAwakeSeconds;
|
|
15426
|
+
} else if (requestedStayAwakeSeconds !== void 0 && requestedStayAwakeSeconds !== null) {
|
|
15427
|
+
state.stayAwakeSeconds = null;
|
|
15428
|
+
log2(
|
|
15429
|
+
state,
|
|
15430
|
+
`Ignoring invalid wake_stay_awake_seconds (type: ${typeof requestedStayAwakeSeconds})`,
|
|
15431
|
+
"warn"
|
|
15432
|
+
);
|
|
15433
|
+
}
|
|
15054
15434
|
const microvmId = process.env.MICROVM_ID?.trim();
|
|
15055
15435
|
if (microvmId) {
|
|
15056
15436
|
const reported = await reportMicrovmId(state.agentId, state.authHeader, microvmId);
|
|
@@ -15294,7 +15674,7 @@ async function run(options) {
|
|
|
15294
15674
|
logActivity(state, { type: "info", level: "warn", message: versionWarning });
|
|
15295
15675
|
}
|
|
15296
15676
|
}
|
|
15297
|
-
await
|
|
15677
|
+
await disposeAndReloadProviderConfig(state.port, state.opencodeClient ?? void 0);
|
|
15298
15678
|
if (state.opencodeApiVersion === "v2" && state.opencodeClient !== null) {
|
|
15299
15679
|
await importOpenCodeV2CredentialsForState(state);
|
|
15300
15680
|
}
|
|
@@ -15465,6 +15845,13 @@ async function run(options) {
|
|
|
15465
15845
|
type: "info",
|
|
15466
15846
|
message: connectionMessage
|
|
15467
15847
|
});
|
|
15848
|
+
if (!isReconnect && state.stayAwakeSeconds !== null && state.stayAwakeUntilMs === null) {
|
|
15849
|
+
state.stayAwakeUntilMs = Date.now() + state.stayAwakeSeconds * 1e3;
|
|
15850
|
+
logActivity(state, {
|
|
15851
|
+
type: "info",
|
|
15852
|
+
message: `Staying awake for at least ${state.stayAwakeSeconds}s as requested by the wake`
|
|
15853
|
+
});
|
|
15854
|
+
}
|
|
15468
15855
|
if (options.tunnelReadyFile) {
|
|
15469
15856
|
const marker = writeTunnelReadyMarker(options.tunnelReadyFile, agentId);
|
|
15470
15857
|
if (marker.ok) {
|
|
@@ -15599,6 +15986,7 @@ async function run(options) {
|
|
|
15599
15986
|
if (error2.message === "Unauthorized") tunnelSpinner?.fail("Unauthorized");
|
|
15600
15987
|
throw error2;
|
|
15601
15988
|
}
|
|
15989
|
+
if (!state.running) return;
|
|
15602
15990
|
scheduleSessionCleanup(state, channelDriver, options);
|
|
15603
15991
|
state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
|
|
15604
15992
|
state.openaiUsageRearm = scheduleUsageReporting(state, {
|