@evident-ai/cli 3.5.2-dev.f76a2fb → 3.5.2-dev.fdeb718
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 +379 -60
- 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.5.2-dev.
|
|
1329
|
+
var CLI_VERSION = (true ? "3.5.2-dev.fdeb718" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1326
1330
|
function getCliVersion() {
|
|
1327
1331
|
return CLI_VERSION;
|
|
1328
1332
|
}
|
|
@@ -3568,6 +3572,9 @@ function classifyReplyAuthError(reply) {
|
|
|
3568
3572
|
function messageFailure(messages, userMessageId) {
|
|
3569
3573
|
return classifyReplyAuthError(findLastAssistantReplyFor(messages, userMessageId));
|
|
3570
3574
|
}
|
|
3575
|
+
function isProviderModelNotFoundError(reason) {
|
|
3576
|
+
return reason.includes("Model not found:");
|
|
3577
|
+
}
|
|
3571
3578
|
function findLatestSubagentAuthOutcome(messages, sinceMs) {
|
|
3572
3579
|
if (!messages || messages.length === 0) return null;
|
|
3573
3580
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -3776,23 +3783,49 @@ async function readSessionErrorStream(port, options, client) {
|
|
|
3776
3783
|
if (reader) void reader.cancel().catch(() => void 0);
|
|
3777
3784
|
}
|
|
3778
3785
|
}
|
|
3779
|
-
async function
|
|
3780
|
-
if (client?.version === "v2") return;
|
|
3786
|
+
async function disposeAndReloadProviderConfig(port, client) {
|
|
3787
|
+
if (client?.version === "v2") return { outcome: "skipped_v2" };
|
|
3781
3788
|
try {
|
|
3782
|
-
const res = await requestWithClient(
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
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
|
+
);
|
|
3787
3800
|
if (!res.ok) {
|
|
3788
3801
|
console.error(
|
|
3789
|
-
`[
|
|
3802
|
+
`[disposeAndReloadProviderConfig] PATCH /config returned HTTP ${res.status} (port ${port})`
|
|
3790
3803
|
);
|
|
3804
|
+
return { outcome: "failed", status: res.status, detail: null };
|
|
3791
3805
|
}
|
|
3806
|
+
return { outcome: "reloaded" };
|
|
3792
3807
|
} catch (err) {
|
|
3793
3808
|
console.error(
|
|
3794
|
-
`[
|
|
3809
|
+
`[disposeAndReloadProviderConfig] PATCH /config failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3795
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 };
|
|
3796
3829
|
}
|
|
3797
3830
|
}
|
|
3798
3831
|
|
|
@@ -7683,12 +7716,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7683
7716
|
/**
|
|
7684
7717
|
* Per-conversation throttle state for the #183 recurrence warning (#1618
|
|
7685
7718
|
* WI-4) — see `reportWedgedConversation`'s doc comment for why this exists.
|
|
7686
|
-
* `firstWedgedAt` anchors `stuck_for_ms`; `
|
|
7687
|
-
*
|
|
7688
|
-
*
|
|
7689
|
-
*
|
|
7690
|
-
* conversation dispatches anything
|
|
7691
|
-
*
|
|
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`
|
|
7692
7728
|
* (`MAX_WEDGED_CONVERSATIONS`).
|
|
7693
7729
|
*/
|
|
7694
7730
|
wedgeWarnings = /* @__PURE__ */ new Map();
|
|
@@ -7918,6 +7954,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7918
7954
|
subagentInvocationPrefetches = /* @__PURE__ */ new Map();
|
|
7919
7955
|
/** Serialises drains so a reconnect during a drain doesn't double-process. */
|
|
7920
7956
|
draining = false;
|
|
7957
|
+
pendingProviderRegistryRepair = null;
|
|
7921
7958
|
/**
|
|
7922
7959
|
* Serialises runner-file syncs (#559) so the ~2s poll tick and a concurrent
|
|
7923
7960
|
* drain ping don't download, write and ack the same file twice.
|
|
@@ -8103,8 +8140,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8103
8140
|
async runDrain() {
|
|
8104
8141
|
let dispatched = 0;
|
|
8105
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
|
+
}
|
|
8106
8160
|
this.pendingMessageIds.clear();
|
|
8107
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
|
+
}
|
|
8108
8170
|
if (this.recycleRequestedFlag) {
|
|
8109
8171
|
this.stop();
|
|
8110
8172
|
}
|
|
@@ -8641,14 +8703,13 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8641
8703
|
* skip-because-already-`dispatched`, the exact signature of a message stuck
|
|
8642
8704
|
* acknowledged-but-never-worked.
|
|
8643
8705
|
*
|
|
8644
|
-
*
|
|
8645
|
-
*
|
|
8646
|
-
*
|
|
8647
|
-
*
|
|
8648
|
-
*
|
|
8649
|
-
*
|
|
8650
|
-
*
|
|
8651
|
-
* 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.
|
|
8652
8713
|
*
|
|
8653
8714
|
* Deliberately does NOT trigger a release: WI-1's `reconcileWatchers` runs
|
|
8654
8715
|
* unconditionally on this same tick and is already recovering anything it
|
|
@@ -8660,36 +8721,41 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8660
8721
|
const existing = this.wedgeWarnings.get(conv.id);
|
|
8661
8722
|
const firstWedgedAt = existing?.firstWedgedAt ?? now;
|
|
8662
8723
|
const consecutiveTicks = (existing?.consecutiveTicks ?? 0) + 1;
|
|
8663
|
-
const
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
this.
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
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
|
|
8670
8741
|
});
|
|
8671
|
-
return;
|
|
8672
8742
|
}
|
|
8673
|
-
const stuckForMs = now - firstWedgedAt;
|
|
8674
|
-
const untracked = this.countUntrackedIds(messages);
|
|
8675
|
-
this.log({
|
|
8676
|
-
level: "warn",
|
|
8677
|
-
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.`),
|
|
8678
|
-
conversation_id: conv.id
|
|
8679
|
-
});
|
|
8680
8743
|
this.wedgeWarnings.delete(conv.id);
|
|
8681
|
-
this.wedgeWarnings.set(conv.id, {
|
|
8744
|
+
this.wedgeWarnings.set(conv.id, {
|
|
8745
|
+
firstWedgedAt,
|
|
8746
|
+
warnAnchorAt: dueForWarn ? now : warnAnchorAt,
|
|
8747
|
+
consecutiveTicks
|
|
8748
|
+
});
|
|
8682
8749
|
while (this.wedgeWarnings.size > MAX_WEDGED_CONVERSATIONS) {
|
|
8683
8750
|
const oldest = this.wedgeWarnings.keys().next().value;
|
|
8684
8751
|
if (oldest === void 0) break;
|
|
8685
8752
|
this.wedgeWarnings.delete(oldest);
|
|
8686
8753
|
}
|
|
8687
|
-
if (
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
}
|
|
8754
|
+
if (!dueForWarn) return;
|
|
8755
|
+
void this.postSignal(conv.id, messages[0].id, "dispatch_wedged", {
|
|
8756
|
+
stuck_for_ms: now - firstWedgedAt,
|
|
8757
|
+
untracked
|
|
8758
|
+
});
|
|
8693
8759
|
}
|
|
8694
8760
|
/**
|
|
8695
8761
|
* How many of `messages`' ids are tracked by NO watcher's `inFlight` (#1618
|
|
@@ -9955,14 +10021,40 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9955
10021
|
conversation_id: watcher.conv.id,
|
|
9956
10022
|
message_id: inFlight.evidentMessageId
|
|
9957
10023
|
});
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
inFlight.
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
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
|
+
}
|
|
9964
10053
|
inFlight.done = true;
|
|
9965
10054
|
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
10055
|
+
if (isMissingProvider) {
|
|
10056
|
+
await this.repairProviderRegistry(watcher.conv.id, inFlight.evidentMessageId);
|
|
10057
|
+
}
|
|
9966
10058
|
} catch (err) {
|
|
9967
10059
|
if (err instanceof ChannelAuthError) {
|
|
9968
10060
|
this.log({
|
|
@@ -9983,6 +10075,44 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9983
10075
|
this.sessionErrorHandled.delete(inFlight.evidentMessageId);
|
|
9984
10076
|
}
|
|
9985
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
|
+
}
|
|
9986
10116
|
/**
|
|
9987
10117
|
* The per-session polling loop (WI-3). Once per tick it:
|
|
9988
10118
|
* 1. polls `GET /session/:id/message` once and, per in-flight message,
|
|
@@ -13678,6 +13808,8 @@ function createCredentialSync({
|
|
|
13678
13808
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
13679
13809
|
var MAX_DISCONNECT_REASON_LENGTH = 200;
|
|
13680
13810
|
var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
|
|
13811
|
+
var CREDENTIAL_ADOPTION_PROBE_ATTEMPTS = 3;
|
|
13812
|
+
var CREDENTIAL_ADOPTION_PROBE_INTERVAL_MS = 1e3;
|
|
13681
13813
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
13682
13814
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
13683
13815
|
var TELEMETRY_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
@@ -14025,6 +14157,125 @@ async function handleAuthError(state, error2) {
|
|
|
14025
14157
|
return { success: false };
|
|
14026
14158
|
}
|
|
14027
14159
|
}
|
|
14160
|
+
function logCredentialAdoptionActivity(state, activity, level) {
|
|
14161
|
+
const message = (() => {
|
|
14162
|
+
switch (activity.outcome) {
|
|
14163
|
+
case "deferred":
|
|
14164
|
+
return `CREDENTIAL-ADOPTION: deferred generation ${activity.generation} with ${activity.active_sessions} active session(s)`;
|
|
14165
|
+
case "reloaded_ready":
|
|
14166
|
+
return `CREDENTIAL-ADOPTION: adopted generation ${activity.generation} with ${activity.active_sessions} active session(s); readiness probe succeeded after ${activity.probe_attempts} attempt(s)`;
|
|
14167
|
+
case "reloaded_interrupted": {
|
|
14168
|
+
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`;
|
|
14169
|
+
return `CREDENTIAL-ADOPTION: generation ${activity.generation} with ${activity.active_sessions} active session(s); replacement instance answered ${response} after ${activity.probe_attempts} probe attempt(s)`;
|
|
14170
|
+
}
|
|
14171
|
+
case "reload_failed":
|
|
14172
|
+
return `CREDENTIAL-ADOPTION: failed to adopt generation ${activity.generation} with ${activity.active_sessions} active session(s)`;
|
|
14173
|
+
case "skipped_v2":
|
|
14174
|
+
return `CREDENTIAL-ADOPTION: skipped generation ${activity.generation} with ${activity.active_sessions} active session(s) on OpenCode V2`;
|
|
14175
|
+
}
|
|
14176
|
+
})();
|
|
14177
|
+
logActivity(state, {
|
|
14178
|
+
type: level === "error" ? "error" : "info",
|
|
14179
|
+
level,
|
|
14180
|
+
...level === "error" ? { error: message } : { message },
|
|
14181
|
+
metadata: {
|
|
14182
|
+
...activity,
|
|
14183
|
+
opencode_version: state.opencodeVersion,
|
|
14184
|
+
port: state.port
|
|
14185
|
+
}
|
|
14186
|
+
});
|
|
14187
|
+
}
|
|
14188
|
+
async function adoptCredentialGeneration(state, generation, adoption, activeSessions, deferredForMs) {
|
|
14189
|
+
const reload = await disposeAndReloadProviderConfig(
|
|
14190
|
+
state.port,
|
|
14191
|
+
state.opencodeClient ?? void 0
|
|
14192
|
+
);
|
|
14193
|
+
if (reload.outcome === "failed") {
|
|
14194
|
+
logCredentialAdoptionActivity(
|
|
14195
|
+
state,
|
|
14196
|
+
{
|
|
14197
|
+
outcome: "reload_failed",
|
|
14198
|
+
generation,
|
|
14199
|
+
active_sessions: activeSessions,
|
|
14200
|
+
adoption,
|
|
14201
|
+
deferred_for_ms: deferredForMs,
|
|
14202
|
+
reload_status: reload.status,
|
|
14203
|
+
reload_detail: reload.detail,
|
|
14204
|
+
probe_attempts: null,
|
|
14205
|
+
probe_status: null,
|
|
14206
|
+
probe_body_empty: null,
|
|
14207
|
+
probe_detail: null
|
|
14208
|
+
},
|
|
14209
|
+
"error"
|
|
14210
|
+
);
|
|
14211
|
+
return;
|
|
14212
|
+
}
|
|
14213
|
+
if (reload.outcome === "skipped_v2") {
|
|
14214
|
+
logCredentialAdoptionActivity(
|
|
14215
|
+
state,
|
|
14216
|
+
{
|
|
14217
|
+
outcome: "skipped_v2",
|
|
14218
|
+
generation,
|
|
14219
|
+
active_sessions: activeSessions,
|
|
14220
|
+
adoption,
|
|
14221
|
+
deferred_for_ms: deferredForMs,
|
|
14222
|
+
reload_status: null,
|
|
14223
|
+
reload_detail: null,
|
|
14224
|
+
probe_attempts: null,
|
|
14225
|
+
probe_status: null,
|
|
14226
|
+
probe_body_empty: null,
|
|
14227
|
+
probe_detail: null
|
|
14228
|
+
},
|
|
14229
|
+
"debug"
|
|
14230
|
+
);
|
|
14231
|
+
return;
|
|
14232
|
+
}
|
|
14233
|
+
let lastProbe = null;
|
|
14234
|
+
let attempts = 0;
|
|
14235
|
+
for (attempts = 1; attempts <= CREDENTIAL_ADOPTION_PROBE_ATTEMPTS; attempts++) {
|
|
14236
|
+
lastProbe = await probeOpenCodeRequestReadiness(state.port, state.opencodeClient ?? void 0);
|
|
14237
|
+
if (lastProbe.ok) {
|
|
14238
|
+
logCredentialAdoptionActivity(
|
|
14239
|
+
state,
|
|
14240
|
+
{
|
|
14241
|
+
outcome: "reloaded_ready",
|
|
14242
|
+
generation,
|
|
14243
|
+
active_sessions: activeSessions,
|
|
14244
|
+
adoption,
|
|
14245
|
+
deferred_for_ms: deferredForMs,
|
|
14246
|
+
reload_status: null,
|
|
14247
|
+
reload_detail: null,
|
|
14248
|
+
probe_attempts: attempts,
|
|
14249
|
+
probe_status: lastProbe.status,
|
|
14250
|
+
probe_body_empty: lastProbe.bodyEmpty,
|
|
14251
|
+
probe_detail: lastProbe.detail
|
|
14252
|
+
},
|
|
14253
|
+
"warn"
|
|
14254
|
+
);
|
|
14255
|
+
return;
|
|
14256
|
+
}
|
|
14257
|
+
if (attempts < CREDENTIAL_ADOPTION_PROBE_ATTEMPTS) {
|
|
14258
|
+
await new Promise((resolve5) => setTimeout(resolve5, CREDENTIAL_ADOPTION_PROBE_INTERVAL_MS));
|
|
14259
|
+
}
|
|
14260
|
+
}
|
|
14261
|
+
logCredentialAdoptionActivity(
|
|
14262
|
+
state,
|
|
14263
|
+
{
|
|
14264
|
+
outcome: "reloaded_interrupted",
|
|
14265
|
+
generation,
|
|
14266
|
+
active_sessions: activeSessions,
|
|
14267
|
+
adoption,
|
|
14268
|
+
deferred_for_ms: deferredForMs,
|
|
14269
|
+
reload_status: null,
|
|
14270
|
+
reload_detail: null,
|
|
14271
|
+
probe_attempts: CREDENTIAL_ADOPTION_PROBE_ATTEMPTS,
|
|
14272
|
+
probe_status: lastProbe?.status ?? null,
|
|
14273
|
+
probe_body_empty: lastProbe?.bodyEmpty ?? null,
|
|
14274
|
+
probe_detail: lastProbe?.detail ?? null
|
|
14275
|
+
},
|
|
14276
|
+
"error"
|
|
14277
|
+
);
|
|
14278
|
+
}
|
|
14028
14279
|
async function driveChannels(state, driver) {
|
|
14029
14280
|
let idlePolls = 0;
|
|
14030
14281
|
let idleMs = 0;
|
|
@@ -14034,6 +14285,13 @@ async function driveChannels(state, driver) {
|
|
|
14034
14285
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
14035
14286
|
let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
|
|
14036
14287
|
let lastSeenOpencodeAuthApplies = driver.fileSyncActivity().opencodeAuthApplyCount;
|
|
14288
|
+
const credentialAdoption = {
|
|
14289
|
+
generation: 0,
|
|
14290
|
+
pendingGeneration: null,
|
|
14291
|
+
pendingSinceMs: null,
|
|
14292
|
+
deferredGeneration: null,
|
|
14293
|
+
adoption: 0
|
|
14294
|
+
};
|
|
14037
14295
|
const stayAwakeHolds = () => state.stayAwakeUntilMs !== null && Date.now() < state.stayAwakeUntilMs;
|
|
14038
14296
|
let stayAwakeSuppressionLogged = false;
|
|
14039
14297
|
while (state.running) {
|
|
@@ -14077,12 +14335,30 @@ async function driveChannels(state, driver) {
|
|
|
14077
14335
|
lastSeenOpencodeAuthApplies = opencodeAuthApplies;
|
|
14078
14336
|
if (opencodeAuthApplied) state.openaiUsageRearm?.();
|
|
14079
14337
|
if (claudeCredentialApplied || opencodeAuthApplied) {
|
|
14080
|
-
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
14085
|
-
|
|
14338
|
+
credentialAdoption.generation++;
|
|
14339
|
+
if (state.opencodeApiVersion === "v2") {
|
|
14340
|
+
logCredentialAdoptionActivity(
|
|
14341
|
+
state,
|
|
14342
|
+
{
|
|
14343
|
+
outcome: "skipped_v2",
|
|
14344
|
+
generation: credentialAdoption.generation,
|
|
14345
|
+
active_sessions: driver.protectedSessionIds().size,
|
|
14346
|
+
adoption: null,
|
|
14347
|
+
deferred_for_ms: null,
|
|
14348
|
+
reload_status: null,
|
|
14349
|
+
reload_detail: null,
|
|
14350
|
+
probe_attempts: null,
|
|
14351
|
+
probe_status: null,
|
|
14352
|
+
probe_body_empty: null,
|
|
14353
|
+
probe_detail: null
|
|
14354
|
+
},
|
|
14355
|
+
"debug"
|
|
14356
|
+
);
|
|
14357
|
+
} else {
|
|
14358
|
+
credentialAdoption.pendingGeneration = credentialAdoption.generation;
|
|
14359
|
+
credentialAdoption.pendingSinceMs = Date.now();
|
|
14360
|
+
credentialAdoption.deferredGeneration = null;
|
|
14361
|
+
}
|
|
14086
14362
|
}
|
|
14087
14363
|
if (opencodeAuthApplied && state.opencodeApiVersion === "v2") {
|
|
14088
14364
|
void importOpenCodeV2CredentialsForState(state).catch(
|
|
@@ -14137,6 +14413,49 @@ async function driveChannels(state, driver) {
|
|
|
14137
14413
|
}
|
|
14138
14414
|
}
|
|
14139
14415
|
}
|
|
14416
|
+
if (credentialAdoption.pendingGeneration !== null) {
|
|
14417
|
+
const activeSessions = driver.protectedSessionIds();
|
|
14418
|
+
const generation = credentialAdoption.pendingGeneration;
|
|
14419
|
+
if (activeSessions.size > 0) {
|
|
14420
|
+
if (credentialAdoption.deferredGeneration !== generation) {
|
|
14421
|
+
credentialAdoption.deferredGeneration = generation;
|
|
14422
|
+
logCredentialAdoptionActivity(
|
|
14423
|
+
state,
|
|
14424
|
+
{
|
|
14425
|
+
outcome: "deferred",
|
|
14426
|
+
generation,
|
|
14427
|
+
active_sessions: activeSessions.size,
|
|
14428
|
+
adoption: null,
|
|
14429
|
+
deferred_for_ms: null,
|
|
14430
|
+
reload_status: null,
|
|
14431
|
+
reload_detail: null,
|
|
14432
|
+
probe_attempts: null,
|
|
14433
|
+
probe_status: null,
|
|
14434
|
+
probe_body_empty: null,
|
|
14435
|
+
probe_detail: null
|
|
14436
|
+
},
|
|
14437
|
+
"warn"
|
|
14438
|
+
);
|
|
14439
|
+
}
|
|
14440
|
+
} else {
|
|
14441
|
+
const deferredForMs = credentialAdoption.deferredGeneration === generation && credentialAdoption.pendingSinceMs !== null ? Math.max(0, Date.now() - credentialAdoption.pendingSinceMs) : null;
|
|
14442
|
+
credentialAdoption.pendingGeneration = null;
|
|
14443
|
+
credentialAdoption.pendingSinceMs = null;
|
|
14444
|
+
credentialAdoption.adoption++;
|
|
14445
|
+
void adoptCredentialGeneration(
|
|
14446
|
+
state,
|
|
14447
|
+
generation,
|
|
14448
|
+
credentialAdoption.adoption,
|
|
14449
|
+
activeSessions.size,
|
|
14450
|
+
deferredForMs
|
|
14451
|
+
).catch(
|
|
14452
|
+
(error2) => logActivity(state, {
|
|
14453
|
+
type: "error",
|
|
14454
|
+
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)}`
|
|
14455
|
+
})
|
|
14456
|
+
);
|
|
14457
|
+
}
|
|
14458
|
+
}
|
|
14140
14459
|
await new Promise((resolve5) => setTimeout(resolve5, CHANNEL_POLL_INTERVAL_MS));
|
|
14141
14460
|
const cycleMs = performance.now() - cycleStartedAtMs;
|
|
14142
14461
|
if (idleThisCycle) idleMs += cycleMs;
|
|
@@ -15353,7 +15672,7 @@ async function run(options) {
|
|
|
15353
15672
|
logActivity(state, { type: "info", level: "warn", message: versionWarning });
|
|
15354
15673
|
}
|
|
15355
15674
|
}
|
|
15356
|
-
await
|
|
15675
|
+
await disposeAndReloadProviderConfig(state.port, state.opencodeClient ?? void 0);
|
|
15357
15676
|
if (state.opencodeApiVersion === "v2" && state.opencodeClient !== null) {
|
|
15358
15677
|
await importOpenCodeV2CredentialsForState(state);
|
|
15359
15678
|
}
|