@evident-ai/cli 3.5.2-dev.ffa823b → 3.6.1-dev.37d1a87
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 +924 -117
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1156,9 +1156,9 @@ async function claudeUsage() {
|
|
|
1156
1156
|
}
|
|
1157
1157
|
|
|
1158
1158
|
// src/commands/run.ts
|
|
1159
|
-
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as
|
|
1160
|
-
import { homedir as
|
|
1161
|
-
import { isAbsolute as isAbsolute4, join as
|
|
1159
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1160
|
+
import { homedir as homedir8 } from "node:os";
|
|
1161
|
+
import { isAbsolute as isAbsolute4, join as join13, parse, resolve as resolvePath2 } from "node:path";
|
|
1162
1162
|
import chalk7 from "chalk";
|
|
1163
1163
|
|
|
1164
1164
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -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.37d1a87" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1326
1330
|
function getCliVersion() {
|
|
1327
1331
|
return CLI_VERSION;
|
|
1328
1332
|
}
|
|
@@ -2071,7 +2075,8 @@ function createDependencyInstallDiagnosticReporter({
|
|
|
2071
2075
|
var FORWARDED_LEVELS_BY_SOURCE = {
|
|
2072
2076
|
"cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
|
|
2073
2077
|
opencode: /* @__PURE__ */ new Set(["warn", "error"]),
|
|
2074
|
-
"cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"])
|
|
2078
|
+
"cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"]),
|
|
2079
|
+
"cli.runtime-provenance": /* @__PURE__ */ new Set(["info", "warn", "error"])
|
|
2075
2080
|
};
|
|
2076
2081
|
var SEVERITY_BY_LEVEL = {
|
|
2077
2082
|
info: "info",
|
|
@@ -2867,6 +2872,58 @@ async function listAllSessions(port, client) {
|
|
|
2867
2872
|
}
|
|
2868
2873
|
return { sessions };
|
|
2869
2874
|
}
|
|
2875
|
+
var INTERRUPT_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
2876
|
+
"ABORT_ERR",
|
|
2877
|
+
"EAI_AGAIN",
|
|
2878
|
+
"ECONNREFUSED",
|
|
2879
|
+
"ECONNRESET",
|
|
2880
|
+
"ENETUNREACH",
|
|
2881
|
+
"ENOTFOUND",
|
|
2882
|
+
"ETIMEDOUT",
|
|
2883
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
2884
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
2885
|
+
"UND_ERR_SOCKET"
|
|
2886
|
+
]);
|
|
2887
|
+
function interruptFailureDiagnostic(error2) {
|
|
2888
|
+
const message = error2 instanceof Error ? error2.message : "";
|
|
2889
|
+
const httpStatus = /^OpenCode request failed: HTTP ([1-5]\d{2})(?::|$)/.exec(message)?.[1];
|
|
2890
|
+
if (httpStatus) return `HTTP ${httpStatus}`;
|
|
2891
|
+
const candidates = [error2, error2 instanceof Error ? error2.cause : void 0];
|
|
2892
|
+
for (const candidate of candidates) {
|
|
2893
|
+
if (typeof candidate !== "object" || candidate === null || !("code" in candidate)) continue;
|
|
2894
|
+
const code = candidate.code;
|
|
2895
|
+
if (typeof code === "string" && INTERRUPT_NETWORK_CODES.has(code)) {
|
|
2896
|
+
return redactDiagnosticText(`network ${code}`).slice(0, 120);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
const name = error2 instanceof Error && ["AbortError", "Error", "TimeoutError", "TypeError"].includes(error2.name) ? error2.name : "unknown error";
|
|
2900
|
+
return redactDiagnosticText(`request threw ${name}`).slice(0, 120);
|
|
2901
|
+
}
|
|
2902
|
+
async function interruptSession(port, id, client) {
|
|
2903
|
+
if (client?.version === "v2") {
|
|
2904
|
+
try {
|
|
2905
|
+
await interruptV2Session(client, id);
|
|
2906
|
+
return { kind: "confirmed_stopped" };
|
|
2907
|
+
} catch (error2) {
|
|
2908
|
+
return { kind: "not_confirmed", diagnostic: interruptFailureDiagnostic(error2) };
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
try {
|
|
2912
|
+
const res = await requestWithClient(
|
|
2913
|
+
port,
|
|
2914
|
+
client,
|
|
2915
|
+
`/session/${id}/abort`,
|
|
2916
|
+
{ method: "POST" },
|
|
2917
|
+
{ allowStatuses: ALL_HTTP_STATUSES }
|
|
2918
|
+
);
|
|
2919
|
+
if (res.status >= 200 && res.status < 300) {
|
|
2920
|
+
return { kind: "confirmed_stopped", status: res.status };
|
|
2921
|
+
}
|
|
2922
|
+
return { kind: "not_confirmed", status: res.status };
|
|
2923
|
+
} catch (error2) {
|
|
2924
|
+
return { kind: "threw", diagnostic: interruptFailureDiagnostic(error2) };
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2870
2927
|
async function deleteSession(port, id, client) {
|
|
2871
2928
|
if (client?.version === "v2") return deleteV2Session(client, id);
|
|
2872
2929
|
try {
|
|
@@ -3518,19 +3575,32 @@ function isAbortedTerminalReply(messages, userMessageId) {
|
|
|
3518
3575
|
}
|
|
3519
3576
|
return false;
|
|
3520
3577
|
}
|
|
3578
|
+
var CLAUDE_AUTH_EXPIRY_MESSAGES = Object.freeze([
|
|
3579
|
+
"Claude Code credentials are unavailable or expired. Run `claude` to refresh them."
|
|
3580
|
+
]);
|
|
3581
|
+
function extractReplyErrorShape(reply) {
|
|
3582
|
+
const error2 = errorOf(reply);
|
|
3583
|
+
if (error2 == null || typeof error2 !== "object") {
|
|
3584
|
+
return { name: null, statusCode: null };
|
|
3585
|
+
}
|
|
3586
|
+
const e = error2;
|
|
3587
|
+
const name = typeof e.name === "string" ? e.name.slice(0, 100) : null;
|
|
3588
|
+
const data = e.data;
|
|
3589
|
+
const statusCode = typeof data?.statusCode === "number" && Number.isFinite(data.statusCode) ? data.statusCode : null;
|
|
3590
|
+
return { name, statusCode };
|
|
3591
|
+
}
|
|
3521
3592
|
function classifyReplyAuthError(reply) {
|
|
3522
3593
|
const error2 = errorOf(reply);
|
|
3523
3594
|
if (error2 == null || typeof error2 !== "object") return null;
|
|
3524
3595
|
const e = error2;
|
|
3525
3596
|
const replyProviderId = reply?.info?.providerID ?? null;
|
|
3526
3597
|
const replyModelId = reply?.info?.modelID ?? null;
|
|
3598
|
+
const data = e.data;
|
|
3527
3599
|
if (e.name === "ProviderAuthError") {
|
|
3528
|
-
const data = e.data;
|
|
3529
3600
|
const providerId = typeof data?.providerID === "string" && data.providerID || replyProviderId;
|
|
3530
3601
|
return { kind: "model_auth", providerId, modelId: replyModelId, reason: "missing" };
|
|
3531
3602
|
}
|
|
3532
3603
|
if (e.name === "APIError") {
|
|
3533
|
-
const data = e.data;
|
|
3534
3604
|
const statusCode = data?.statusCode;
|
|
3535
3605
|
if (statusCode === 401 || statusCode === 403) {
|
|
3536
3606
|
return {
|
|
@@ -3541,11 +3611,23 @@ function classifyReplyAuthError(reply) {
|
|
|
3541
3611
|
};
|
|
3542
3612
|
}
|
|
3543
3613
|
}
|
|
3614
|
+
const errorMessage3 = typeof data?.message === "string" ? data.message : typeof e.message === "string" ? e.message : null;
|
|
3615
|
+
if (errorMessage3 !== null && CLAUDE_AUTH_EXPIRY_MESSAGES.some((knownMessage) => errorMessage3.includes(knownMessage))) {
|
|
3616
|
+
return {
|
|
3617
|
+
kind: "model_auth",
|
|
3618
|
+
providerId: typeof data?.providerID === "string" && data.providerID || replyProviderId,
|
|
3619
|
+
modelId: replyModelId,
|
|
3620
|
+
reason: "rejected"
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3544
3623
|
return null;
|
|
3545
3624
|
}
|
|
3546
3625
|
function messageFailure(messages, userMessageId) {
|
|
3547
3626
|
return classifyReplyAuthError(findLastAssistantReplyFor(messages, userMessageId));
|
|
3548
3627
|
}
|
|
3628
|
+
function isProviderModelNotFoundError(reason) {
|
|
3629
|
+
return reason.includes("Model not found:");
|
|
3630
|
+
}
|
|
3549
3631
|
function findLatestSubagentAuthOutcome(messages, sinceMs) {
|
|
3550
3632
|
if (!messages || messages.length === 0) return null;
|
|
3551
3633
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -3754,23 +3836,49 @@ async function readSessionErrorStream(port, options, client) {
|
|
|
3754
3836
|
if (reader) void reader.cancel().catch(() => void 0);
|
|
3755
3837
|
}
|
|
3756
3838
|
}
|
|
3757
|
-
async function
|
|
3758
|
-
if (client?.version === "v2") return;
|
|
3839
|
+
async function disposeAndReloadProviderConfig(port, client) {
|
|
3840
|
+
if (client?.version === "v2") return { outcome: "skipped_v2" };
|
|
3759
3841
|
try {
|
|
3760
|
-
const res = await requestWithClient(
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3842
|
+
const res = await requestWithClient(
|
|
3843
|
+
port,
|
|
3844
|
+
client,
|
|
3845
|
+
"/config",
|
|
3846
|
+
{
|
|
3847
|
+
method: "PATCH",
|
|
3848
|
+
headers: { "Content-Type": "application/json" },
|
|
3849
|
+
body: JSON.stringify({})
|
|
3850
|
+
},
|
|
3851
|
+
{ allowStatuses: ALL_HTTP_STATUSES }
|
|
3852
|
+
);
|
|
3765
3853
|
if (!res.ok) {
|
|
3766
3854
|
console.error(
|
|
3767
|
-
`[
|
|
3855
|
+
`[disposeAndReloadProviderConfig] PATCH /config returned HTTP ${res.status} (port ${port})`
|
|
3768
3856
|
);
|
|
3857
|
+
return { outcome: "failed", status: res.status, detail: null };
|
|
3769
3858
|
}
|
|
3859
|
+
return { outcome: "reloaded" };
|
|
3770
3860
|
} catch (err) {
|
|
3771
3861
|
console.error(
|
|
3772
|
-
`[
|
|
3862
|
+
`[disposeAndReloadProviderConfig] PATCH /config failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3773
3863
|
);
|
|
3864
|
+
return {
|
|
3865
|
+
outcome: "failed",
|
|
3866
|
+
status: null,
|
|
3867
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
async function probeOpenCodeRequestReadiness(port, client) {
|
|
3872
|
+
try {
|
|
3873
|
+
const res = await requestWithClient(port, client, "/config", void 0, {
|
|
3874
|
+
allowStatuses: ALL_HTTP_STATUSES
|
|
3875
|
+
});
|
|
3876
|
+
const body = await res.text();
|
|
3877
|
+
return { ok: res.ok, status: res.status, bodyEmpty: body.trim().length === 0, detail: null };
|
|
3878
|
+
} catch (err) {
|
|
3879
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
3880
|
+
console.error(`[probeOpenCodeRequestReadiness] GET /config failed (port ${port}): ${detail}`);
|
|
3881
|
+
return { ok: false, status: null, bodyEmpty: null, detail };
|
|
3774
3882
|
}
|
|
3775
3883
|
}
|
|
3776
3884
|
|
|
@@ -4166,6 +4274,11 @@ async function listV2Permissions(client, sessionId) {
|
|
|
4166
4274
|
return null;
|
|
4167
4275
|
}
|
|
4168
4276
|
}
|
|
4277
|
+
async function interruptV2Session(client, sessionId) {
|
|
4278
|
+
await client.request(`/api/session/${encodeURIComponent(sessionId)}/interrupt`, {
|
|
4279
|
+
method: "POST"
|
|
4280
|
+
});
|
|
4281
|
+
}
|
|
4169
4282
|
async function getV2ActiveSessions(client) {
|
|
4170
4283
|
try {
|
|
4171
4284
|
const body = await readJson(await client.request("/api/session/active"));
|
|
@@ -7537,7 +7650,7 @@ var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
|
7537
7650
|
var MAX_BUFFERED_SESSION_ERRORS = 256;
|
|
7538
7651
|
var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
|
|
7539
7652
|
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 = "
|
|
7653
|
+
var SESSION_ERROR_RETAINED_DETAIL = "Runner-observed OpenCode session error: ";
|
|
7541
7654
|
var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
|
|
7542
7655
|
var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
7543
7656
|
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
@@ -7661,12 +7774,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7661
7774
|
/**
|
|
7662
7775
|
* Per-conversation throttle state for the #183 recurrence warning (#1618
|
|
7663
7776
|
* 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
|
-
*
|
|
7777
|
+
* `firstWedgedAt` anchors `stuck_for_ms`; `warnAnchorAt` is the throttle
|
|
7778
|
+
* anchor from first detection through the first warning, then from each
|
|
7779
|
+
* warning onward; `consecutiveTicks` is reported in the log text so the
|
|
7780
|
+
* operator sees magnitude, not repetition. Cleared the moment the
|
|
7781
|
+
* conversation dispatches anything, or when it drops off the pending list
|
|
7782
|
+
* (`runDrain`'s prune, the dominant real-world path — a wedge normally ends
|
|
7783
|
+
* with its message completing, not a fresh dispatch on the same
|
|
7784
|
+
* conversation) — either way a fresh wedge, if it recurs, is a new
|
|
7785
|
+
* incident. Bounded FIFO, mirroring `supersededSessions`
|
|
7670
7786
|
* (`MAX_WEDGED_CONVERSATIONS`).
|
|
7671
7787
|
*/
|
|
7672
7788
|
wedgeWarnings = /* @__PURE__ */ new Map();
|
|
@@ -7896,6 +8012,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7896
8012
|
subagentInvocationPrefetches = /* @__PURE__ */ new Map();
|
|
7897
8013
|
/** Serialises drains so a reconnect during a drain doesn't double-process. */
|
|
7898
8014
|
draining = false;
|
|
8015
|
+
pendingProviderRegistryRepair = null;
|
|
7899
8016
|
/**
|
|
7900
8017
|
* Serialises runner-file syncs (#559) so the ~2s poll tick and a concurrent
|
|
7901
8018
|
* drain ping don't download, write and ack the same file twice.
|
|
@@ -8081,8 +8198,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8081
8198
|
async runDrain() {
|
|
8082
8199
|
let dispatched = 0;
|
|
8083
8200
|
try {
|
|
8201
|
+
const pendingRepair = this.pendingProviderRegistryRepair;
|
|
8202
|
+
if (pendingRepair && this.protectedSessionIds().size === 0) {
|
|
8203
|
+
try {
|
|
8204
|
+
await this.issueProviderRegistryReload(
|
|
8205
|
+
pendingRepair.conversationId,
|
|
8206
|
+
pendingRepair.messageId
|
|
8207
|
+
);
|
|
8208
|
+
} catch (err) {
|
|
8209
|
+
this.log({
|
|
8210
|
+
level: "error",
|
|
8211
|
+
message: `Provider registry repair adoption failed for message ${pendingRepair.messageId.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`,
|
|
8212
|
+
conversation_id: pendingRepair.conversationId,
|
|
8213
|
+
message_id: pendingRepair.messageId
|
|
8214
|
+
});
|
|
8215
|
+
}
|
|
8216
|
+
return 0;
|
|
8217
|
+
}
|
|
8084
8218
|
this.pendingMessageIds.clear();
|
|
8085
8219
|
const conversations = await this.getPendingConversations();
|
|
8220
|
+
if (this.wedgeWarnings.size > 0) {
|
|
8221
|
+
const pendingConversationIds = new Set(conversations.map(({ id }) => id));
|
|
8222
|
+
for (const conversationId of this.wedgeWarnings.keys()) {
|
|
8223
|
+
if (!pendingConversationIds.has(conversationId)) {
|
|
8224
|
+
this.wedgeWarnings.delete(conversationId);
|
|
8225
|
+
}
|
|
8226
|
+
}
|
|
8227
|
+
}
|
|
8086
8228
|
if (this.recycleRequestedFlag) {
|
|
8087
8229
|
this.stop();
|
|
8088
8230
|
}
|
|
@@ -8619,14 +8761,13 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8619
8761
|
* skip-because-already-`dispatched`, the exact signature of a message stuck
|
|
8620
8762
|
* acknowledged-but-never-worked.
|
|
8621
8763
|
*
|
|
8622
|
-
*
|
|
8623
|
-
*
|
|
8624
|
-
*
|
|
8625
|
-
*
|
|
8626
|
-
*
|
|
8627
|
-
*
|
|
8628
|
-
*
|
|
8629
|
-
* than repetition.
|
|
8764
|
+
* The first detection starts the clock and emits a local `debug` entry. The
|
|
8765
|
+
* `warn` log and paired `dispatch_wedged` signal fire once the wedge has
|
|
8766
|
+
* lasted `wedgeWarningIntervalMs`, then at most once per interval, naming the
|
|
8767
|
+
* consecutive-tick count so the operator sees magnitude rather than repetition.
|
|
8768
|
+
* A watcher stall that the loop-liveness watchdog recovers is expected and is
|
|
8769
|
+
* already reported by its `watcher_recovered` signal, so it does not reach an
|
|
8770
|
+
* operator through this warning.
|
|
8630
8771
|
*
|
|
8631
8772
|
* Deliberately does NOT trigger a release: WI-1's `reconcileWatchers` runs
|
|
8632
8773
|
* unconditionally on this same tick and is already recovering anything it
|
|
@@ -8638,36 +8779,41 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8638
8779
|
const existing = this.wedgeWarnings.get(conv.id);
|
|
8639
8780
|
const firstWedgedAt = existing?.firstWedgedAt ?? now;
|
|
8640
8781
|
const consecutiveTicks = (existing?.consecutiveTicks ?? 0) + 1;
|
|
8641
|
-
const
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
this.
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8782
|
+
const warnAnchorAt = existing?.warnAnchorAt ?? now;
|
|
8783
|
+
const dueForWarn = now - warnAnchorAt >= this.wedgeWarningIntervalMs;
|
|
8784
|
+
if (!existing) {
|
|
8785
|
+
this.log({
|
|
8786
|
+
level: "debug",
|
|
8787
|
+
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.`,
|
|
8788
|
+
conversation_id: conv.id
|
|
8789
|
+
});
|
|
8790
|
+
}
|
|
8791
|
+
let untracked = 0;
|
|
8792
|
+
if (dueForWarn) {
|
|
8793
|
+
const stuckForMs = now - firstWedgedAt;
|
|
8794
|
+
untracked = this.countUntrackedIds(messages);
|
|
8795
|
+
this.log({
|
|
8796
|
+
level: "warn",
|
|
8797
|
+
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.`),
|
|
8798
|
+
conversation_id: conv.id
|
|
8648
8799
|
});
|
|
8649
|
-
return;
|
|
8650
8800
|
}
|
|
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
8801
|
this.wedgeWarnings.delete(conv.id);
|
|
8659
|
-
this.wedgeWarnings.set(conv.id, {
|
|
8802
|
+
this.wedgeWarnings.set(conv.id, {
|
|
8803
|
+
firstWedgedAt,
|
|
8804
|
+
warnAnchorAt: dueForWarn ? now : warnAnchorAt,
|
|
8805
|
+
consecutiveTicks
|
|
8806
|
+
});
|
|
8660
8807
|
while (this.wedgeWarnings.size > MAX_WEDGED_CONVERSATIONS) {
|
|
8661
8808
|
const oldest = this.wedgeWarnings.keys().next().value;
|
|
8662
8809
|
if (oldest === void 0) break;
|
|
8663
8810
|
this.wedgeWarnings.delete(oldest);
|
|
8664
8811
|
}
|
|
8665
|
-
if (
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
}
|
|
8812
|
+
if (!dueForWarn) return;
|
|
8813
|
+
void this.postSignal(conv.id, messages[0].id, "dispatch_wedged", {
|
|
8814
|
+
stuck_for_ms: now - firstWedgedAt,
|
|
8815
|
+
untracked
|
|
8816
|
+
});
|
|
8671
8817
|
}
|
|
8672
8818
|
/**
|
|
8673
8819
|
* How many of `messages`' ids are tracked by NO watcher's `inFlight` (#1618
|
|
@@ -8803,6 +8949,34 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8803
8949
|
if (state === "failed" && isAbortedTerminalReply(messages, ocId ?? "")) {
|
|
8804
8950
|
const ongoing = await this.isSessionOngoing(sessionId);
|
|
8805
8951
|
if (ongoing === false) {
|
|
8952
|
+
const age = this.absoluteAgeForRow(message.processing_started_at);
|
|
8953
|
+
if (age.tripped && age.anchorMs !== null) {
|
|
8954
|
+
try {
|
|
8955
|
+
await this.markFailed(
|
|
8956
|
+
conv.id,
|
|
8957
|
+
message.id,
|
|
8958
|
+
sessionId,
|
|
8959
|
+
this.absoluteCeilingError(message.id, sessionId, age.anchorMs),
|
|
8960
|
+
void 0,
|
|
8961
|
+
{
|
|
8962
|
+
classification: null,
|
|
8963
|
+
errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
|
|
8964
|
+
}
|
|
8965
|
+
);
|
|
8966
|
+
} catch (err) {
|
|
8967
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
8968
|
+
this.log({
|
|
8969
|
+
level: "warn",
|
|
8970
|
+
message: `Re-drive: failed to report past-ceiling restart orphan ${message.id.slice(0, 8)} terminal; refusing to re-dispatch: ${err instanceof Error ? err.message : String(err)}`,
|
|
8971
|
+
conversation_id: conv.id,
|
|
8972
|
+
message_id: message.id
|
|
8973
|
+
});
|
|
8974
|
+
return "unresolved";
|
|
8975
|
+
}
|
|
8976
|
+
this.clearRedriveUnresolved(message.id);
|
|
8977
|
+
void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
|
|
8978
|
+
return "settled";
|
|
8979
|
+
}
|
|
8806
8980
|
this.log({
|
|
8807
8981
|
level: "info",
|
|
8808
8982
|
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 the active-session status check \u2014 restart orphan, re-dispatching instead of marking it permanently failed`,
|
|
@@ -8873,6 +9047,29 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8873
9047
|
message_id: message.id
|
|
8874
9048
|
});
|
|
8875
9049
|
}
|
|
9050
|
+
if (this.absoluteAgeForRow(message.processing_started_at).tripped) {
|
|
9051
|
+
try {
|
|
9052
|
+
await this.markFailed(
|
|
9053
|
+
conv.id,
|
|
9054
|
+
message.id,
|
|
9055
|
+
sessionId,
|
|
9056
|
+
this.absoluteCeilingError(message.id, sessionId, anchorMs)
|
|
9057
|
+
);
|
|
9058
|
+
} catch (err) {
|
|
9059
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
9060
|
+
this.log({
|
|
9061
|
+
level: "warn",
|
|
9062
|
+
message: `Re-drive: failed to report past-ceiling message ${message.id.slice(0, 8)} terminal; refusing to re-attach: ${err instanceof Error ? err.message : String(err)}`,
|
|
9063
|
+
conversation_id: conv.id,
|
|
9064
|
+
message_id: message.id
|
|
9065
|
+
});
|
|
9066
|
+
const bound = await this.boundRedriveOutcome(conv, message, "reattach");
|
|
9067
|
+
return bound === "abandoned" ? "abandoned" : "unresolved";
|
|
9068
|
+
}
|
|
9069
|
+
this.clearRedriveUnresolved(message.id);
|
|
9070
|
+
void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
|
|
9071
|
+
return "abandoned";
|
|
9072
|
+
}
|
|
8876
9073
|
const title = await this.resolveSessionTitle(sessionId, conv.id);
|
|
8877
9074
|
try {
|
|
8878
9075
|
await this.markProcessing(conv.id, message.id, sessionId, ocId, title);
|
|
@@ -9139,8 +9336,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9139
9336
|
const since = this.redriveOutcomeFailingSince.get(message.id);
|
|
9140
9337
|
if (since === void 0) this.redriveOutcomeFailingSince.set(message.id, now);
|
|
9141
9338
|
const durationTripped = now - (since ?? now) >= this.pausedMaxWaitMs;
|
|
9142
|
-
const
|
|
9143
|
-
const absoluteAgeTripped = !Number.isNaN(parsed) && now - parsed >= ABSOLUTE_MAX_PROCESSING_MS;
|
|
9339
|
+
const absoluteAgeTripped = this.absoluteAgeForRow(message.processing_started_at).tripped;
|
|
9144
9340
|
if (!durationTripped && !absoluteAgeTripped) {
|
|
9145
9341
|
this.signalRedriveOutcomeUnreported(conv, message, outcome);
|
|
9146
9342
|
return "retry";
|
|
@@ -9179,6 +9375,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9179
9375
|
});
|
|
9180
9376
|
return "abandoned";
|
|
9181
9377
|
}
|
|
9378
|
+
/** Row-level guards use the durable server stamp; in-flight guards use processingAnchorMs. */
|
|
9379
|
+
absoluteAgeForRow(stamp) {
|
|
9380
|
+
const anchorMs = stamp ? Date.parse(stamp) : NaN;
|
|
9381
|
+
return Number.isNaN(anchorMs) ? { tripped: false, anchorMs: null } : { tripped: this.now() - anchorMs >= ABSOLUTE_MAX_PROCESSING_MS, anchorMs };
|
|
9382
|
+
}
|
|
9383
|
+
absoluteCeilingError(messageId, sessionId, anchorMs, retainedReason = this.readRetainedSessionError(sessionId)) {
|
|
9384
|
+
const baseError = `Message ${messageId.slice(0, 8)} exceeded the absolute processing ceiling (${Math.round((this.now() - anchorMs) / 6e4)}min, session ${sessionId}) while still actively running.`;
|
|
9385
|
+
return retainedReason ? `${baseError} ${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}` : baseError;
|
|
9386
|
+
}
|
|
9182
9387
|
/**
|
|
9183
9388
|
* Record one poll outcome toward the re-drive fence's consecutive-identical-
|
|
9184
9389
|
* failure streak (#1348) and return the resulting count. `signature === null`
|
|
@@ -9620,10 +9825,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9620
9825
|
opencodeMessageId,
|
|
9621
9826
|
message,
|
|
9622
9827
|
dispatchedAt: this.now(),
|
|
9623
|
-
|
|
9624
|
-
// `dispatchedAt` — so a re-adopted zombie's age reflects the real turn duration
|
|
9625
|
-
// and the ceiling fires on the ORIGINAL turn.
|
|
9626
|
-
processingAnchorMs: processedAtMs,
|
|
9828
|
+
processingAnchorMs: this.absoluteAgeForRow(message.processing_started_at).anchorMs ?? processedAtMs,
|
|
9627
9829
|
deadline: watchDeadlineAnchorMs + this.pausedMaxWaitMs,
|
|
9628
9830
|
// The server row is ALREADY `processing`; do not re-fire markProcessing.
|
|
9629
9831
|
started: true,
|
|
@@ -9933,14 +10135,40 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9933
10135
|
conversation_id: watcher.conv.id,
|
|
9934
10136
|
message_id: inFlight.evidentMessageId
|
|
9935
10137
|
});
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
inFlight.
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
10138
|
+
const isMissingProvider = isProviderModelNotFoundError(event.reason);
|
|
10139
|
+
if (isMissingProvider) {
|
|
10140
|
+
const dispatchedModel = inFlight.message.opencode_model;
|
|
10141
|
+
const separator = dispatchedModel?.indexOf("/") ?? -1;
|
|
10142
|
+
const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
|
|
10143
|
+
await this.markFailed(
|
|
10144
|
+
watcher.conv.id,
|
|
10145
|
+
inFlight.evidentMessageId,
|
|
10146
|
+
event.sessionId,
|
|
10147
|
+
`OpenCode could not run this turn: ${event.reason}`,
|
|
10148
|
+
void 0,
|
|
10149
|
+
{
|
|
10150
|
+
classification: {
|
|
10151
|
+
kind: "model_auth",
|
|
10152
|
+
providerId: hasProviderAndModel ? dispatchedModel.slice(0, separator) : null,
|
|
10153
|
+
modelId: hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null,
|
|
10154
|
+
reason: "missing"
|
|
10155
|
+
},
|
|
10156
|
+
errorShape: { name: null, statusCode: null }
|
|
10157
|
+
}
|
|
10158
|
+
);
|
|
10159
|
+
} else {
|
|
10160
|
+
await this.markFailed(
|
|
10161
|
+
watcher.conv.id,
|
|
10162
|
+
inFlight.evidentMessageId,
|
|
10163
|
+
event.sessionId,
|
|
10164
|
+
`OpenCode could not run this turn: ${event.reason}`
|
|
10165
|
+
);
|
|
10166
|
+
}
|
|
9942
10167
|
inFlight.done = true;
|
|
9943
10168
|
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
10169
|
+
if (isMissingProvider) {
|
|
10170
|
+
await this.repairProviderRegistry(watcher.conv.id, inFlight.evidentMessageId);
|
|
10171
|
+
}
|
|
9944
10172
|
} catch (err) {
|
|
9945
10173
|
if (err instanceof ChannelAuthError) {
|
|
9946
10174
|
this.log({
|
|
@@ -9961,6 +10189,44 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9961
10189
|
this.sessionErrorHandled.delete(inFlight.evidentMessageId);
|
|
9962
10190
|
}
|
|
9963
10191
|
}
|
|
10192
|
+
async repairProviderRegistry(conversationId, messageId) {
|
|
10193
|
+
if (this.isV2) {
|
|
10194
|
+
void this.postSignal(conversationId, messageId, "provider_registry_reload_unsupported");
|
|
10195
|
+
return;
|
|
10196
|
+
}
|
|
10197
|
+
const activeSessions = this.protectedSessionIds().size;
|
|
10198
|
+
if (activeSessions > 0 || this.draining) {
|
|
10199
|
+
this.pendingProviderRegistryRepair = { conversationId, messageId };
|
|
10200
|
+
void this.postSignal(conversationId, messageId, "provider_registry_repair_deferred", {
|
|
10201
|
+
active_sessions: activeSessions
|
|
10202
|
+
});
|
|
10203
|
+
this.log({
|
|
10204
|
+
level: "warn",
|
|
10205
|
+
message: `Provider registry repair deferred until a lifecycle-safe point; ${activeSessions} active session(s) protected`,
|
|
10206
|
+
conversation_id: conversationId,
|
|
10207
|
+
message_id: messageId
|
|
10208
|
+
});
|
|
10209
|
+
return;
|
|
10210
|
+
}
|
|
10211
|
+
await this.issueProviderRegistryReload(conversationId, messageId);
|
|
10212
|
+
}
|
|
10213
|
+
async issueProviderRegistryReload(conversationId, messageId) {
|
|
10214
|
+
this.pendingProviderRegistryRepair = null;
|
|
10215
|
+
const result = await disposeAndReloadProviderConfig(this.port, this.openCodeClient);
|
|
10216
|
+
if (result.outcome === "failed") {
|
|
10217
|
+
const failure = result.status === null ? `transport failure${result.detail ? `: ${result.detail}` : ""}` : `HTTP ${result.status}`;
|
|
10218
|
+
this.log({
|
|
10219
|
+
level: "warn",
|
|
10220
|
+
message: `Provider registry reload failed (${failure})`,
|
|
10221
|
+
conversation_id: conversationId,
|
|
10222
|
+
message_id: messageId
|
|
10223
|
+
});
|
|
10224
|
+
}
|
|
10225
|
+
const repairOutcome = result.outcome === "reloaded" ? "reloaded" : "reload_failed";
|
|
10226
|
+
void this.postSignal(conversationId, messageId, "provider_registry_reload_issued", {
|
|
10227
|
+
repair_outcome: repairOutcome
|
|
10228
|
+
});
|
|
10229
|
+
}
|
|
9964
10230
|
/**
|
|
9965
10231
|
* The per-session polling loop (WI-3). Once per tick it:
|
|
9966
10232
|
* 1. polls `GET /session/:id/message` once and, per in-flight message,
|
|
@@ -10010,7 +10276,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10010
10276
|
}
|
|
10011
10277
|
const { openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk } = await this.pollInteractions(sessionId, watcher, messages);
|
|
10012
10278
|
if (watcher.generation !== generation) return;
|
|
10279
|
+
const ceilingAttemptsThisTick = /* @__PURE__ */ new Set();
|
|
10013
10280
|
for (const inFlight of [...watcher.inFlight.values()]) {
|
|
10281
|
+
if (ceilingAttemptsThisTick.has(inFlight.evidentMessageId)) continue;
|
|
10014
10282
|
await this.serviceInFlightMessage(
|
|
10015
10283
|
sessionId,
|
|
10016
10284
|
watcher,
|
|
@@ -10019,7 +10287,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10019
10287
|
openQuestions,
|
|
10020
10288
|
openPermissions,
|
|
10021
10289
|
questionsPolledOk,
|
|
10022
|
-
permissionsPolledOk
|
|
10290
|
+
permissionsPolledOk,
|
|
10291
|
+
ceilingAttemptsThisTick
|
|
10023
10292
|
);
|
|
10024
10293
|
}
|
|
10025
10294
|
}
|
|
@@ -10060,13 +10329,117 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10060
10329
|
inFlight.deadline = this.now() + this.pausedMaxWaitMs;
|
|
10061
10330
|
}
|
|
10062
10331
|
}
|
|
10332
|
+
async settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick) {
|
|
10333
|
+
const entries = [...watcher.inFlight.values()];
|
|
10334
|
+
const pastCeiling = entries.filter(
|
|
10335
|
+
(entry) => this.now() - entry.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS
|
|
10336
|
+
);
|
|
10337
|
+
const healthySiblings = entries.length - pastCeiling.length;
|
|
10338
|
+
const landed = [];
|
|
10339
|
+
let allRecordsLanded = true;
|
|
10340
|
+
const retainedReason = this.readRetainedSessionError(sessionId);
|
|
10341
|
+
for (const entry of pastCeiling) {
|
|
10342
|
+
if (!watcher.inFlight.has(entry.evidentMessageId)) continue;
|
|
10343
|
+
ceilingAttemptsThisTick.add(entry.evidentMessageId);
|
|
10344
|
+
const error2 = this.absoluteCeilingError(
|
|
10345
|
+
entry.evidentMessageId,
|
|
10346
|
+
sessionId,
|
|
10347
|
+
entry.processingAnchorMs,
|
|
10348
|
+
retainedReason
|
|
10349
|
+
);
|
|
10350
|
+
this.log({
|
|
10351
|
+
level: "warn",
|
|
10352
|
+
message: error2,
|
|
10353
|
+
conversation_id: watcher.conv.id,
|
|
10354
|
+
message_id: entry.evidentMessageId
|
|
10355
|
+
});
|
|
10356
|
+
try {
|
|
10357
|
+
await this.markFailed(
|
|
10358
|
+
watcher.conv.id,
|
|
10359
|
+
entry.evidentMessageId,
|
|
10360
|
+
sessionId,
|
|
10361
|
+
error2,
|
|
10362
|
+
void 0,
|
|
10363
|
+
{
|
|
10364
|
+
classification: null,
|
|
10365
|
+
errorShape: extractReplyErrorShape(
|
|
10366
|
+
findLastAssistantReplyFor(messages, entry.opencodeMessageId)
|
|
10367
|
+
)
|
|
10368
|
+
}
|
|
10369
|
+
);
|
|
10370
|
+
} catch (err) {
|
|
10371
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
10372
|
+
allRecordsLanded = false;
|
|
10373
|
+
const watched_for_ms = this.now() - entry.processingAnchorMs;
|
|
10374
|
+
if (err instanceof ChannelTerminalError) {
|
|
10375
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10376
|
+
watched_for_ms,
|
|
10377
|
+
interrupt_outcome: "record_terminal"
|
|
10378
|
+
});
|
|
10379
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10380
|
+
continue;
|
|
10381
|
+
}
|
|
10382
|
+
if (this.now() >= entry.deadline) {
|
|
10383
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10384
|
+
watched_for_ms,
|
|
10385
|
+
interrupt_outcome: "record_deadline"
|
|
10386
|
+
});
|
|
10387
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10388
|
+
continue;
|
|
10389
|
+
}
|
|
10390
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10391
|
+
watched_for_ms,
|
|
10392
|
+
interrupt_outcome: "record_retry"
|
|
10393
|
+
});
|
|
10394
|
+
this.log({
|
|
10395
|
+
level: "warn",
|
|
10396
|
+
message: `Failed to record the absolute-ceiling failure for message ${entry.evidentMessageId.slice(0, 8)}; leaving its running turn untouched and retrying next tick: ${err instanceof Error ? err.message : String(err)}`,
|
|
10397
|
+
conversation_id: watcher.conv.id,
|
|
10398
|
+
message_id: entry.evidentMessageId
|
|
10399
|
+
});
|
|
10400
|
+
continue;
|
|
10401
|
+
}
|
|
10402
|
+
entry.done = true;
|
|
10403
|
+
landed.push(entry);
|
|
10404
|
+
}
|
|
10405
|
+
if (!allRecordsLanded) {
|
|
10406
|
+
for (const entry of landed) this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10407
|
+
return;
|
|
10408
|
+
}
|
|
10409
|
+
if (healthySiblings > 0) {
|
|
10410
|
+
for (const entry of landed) {
|
|
10411
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10412
|
+
watched_for_ms: this.now() - entry.processingAnchorMs,
|
|
10413
|
+
interrupt_outcome: "deferred_for_healthy_sibling",
|
|
10414
|
+
healthy_siblings: healthySiblings
|
|
10415
|
+
});
|
|
10416
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10417
|
+
}
|
|
10418
|
+
return;
|
|
10419
|
+
}
|
|
10420
|
+
const interrupt = await interruptSession(this.port, sessionId, this.openCodeClient);
|
|
10421
|
+
for (const entry of landed) {
|
|
10422
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10423
|
+
watched_for_ms: this.now() - entry.processingAnchorMs,
|
|
10424
|
+
interrupt_outcome: interrupt.kind
|
|
10425
|
+
});
|
|
10426
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10427
|
+
}
|
|
10428
|
+
if (interrupt.kind !== "confirmed_stopped") {
|
|
10429
|
+
this.log({
|
|
10430
|
+
level: "warn",
|
|
10431
|
+
message: `Absolute-ceiling records landed for session ${sessionId}, but OpenCode interruption was ${interrupt.kind}${interrupt.diagnostic ? ` (${interrupt.diagnostic})` : "status" in interrupt && typeof interrupt.status === "number" ? ` (HTTP ${interrupt.status})` : ""}; the rows are terminal and released, while the session may still be running`,
|
|
10432
|
+
conversation_id: watcher.conv.id
|
|
10433
|
+
});
|
|
10434
|
+
}
|
|
10435
|
+
}
|
|
10063
10436
|
/**
|
|
10064
10437
|
* Drive ONE in-flight message's lifecycle from the tick's message snapshot.
|
|
10065
10438
|
* Fires markProcessing on queued→running and markDone on done (each once),
|
|
10066
10439
|
* applies the idle-path re-dispatch guard, and removes the message from the
|
|
10067
10440
|
* in-flight set on completion or timeout.
|
|
10068
10441
|
*/
|
|
10069
|
-
async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk) {
|
|
10442
|
+
async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk, ceilingAttemptsThisTick) {
|
|
10070
10443
|
const conv = watcher.conv;
|
|
10071
10444
|
const state = messageRunState(messages, inFlight.opencodeMessageId);
|
|
10072
10445
|
const id = inFlight.evidentMessageId;
|
|
@@ -10152,7 +10525,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10152
10525
|
if (!inFlight.done) {
|
|
10153
10526
|
const messageErrorText = messageError(messages, inFlight.opencodeMessageId);
|
|
10154
10527
|
const retainedReason = this.readRetainedSessionError(sessionId);
|
|
10155
|
-
const retainedError = retainedReason === null ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
|
|
10528
|
+
const retainedError = retainedReason === null || messageErrorText !== null && messageErrorText.includes(retainedReason) ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
|
|
10156
10529
|
const observedCause = this.sessionStoreHealth?.readCause(sessionId);
|
|
10157
10530
|
const causeError = observedCause === null || observedCause === void 0 ? null : `${SESSION_ERROR_OBSERVED_CAUSE}${observedCause}`;
|
|
10158
10531
|
const storeError = this.sessionStoreHealth?.isUnwritable() ? SESSION_STORE_UNWRITABLE_ERROR : null;
|
|
@@ -10410,17 +10783,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10410
10783
|
return;
|
|
10411
10784
|
}
|
|
10412
10785
|
}
|
|
10413
|
-
if (activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
10414
|
-
this.
|
|
10415
|
-
level: "warn",
|
|
10416
|
-
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`,
|
|
10417
|
-
conversation_id: conv.id,
|
|
10418
|
-
message_id: inFlight.evidentMessageId
|
|
10419
|
-
});
|
|
10420
|
-
void this.postSignal(conv.id, inFlight.evidentMessageId, "gave_up", {
|
|
10421
|
-
watched_for_ms: this.now() - inFlight.processingAnchorMs
|
|
10422
|
-
});
|
|
10423
|
-
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
10786
|
+
if (watcher.inFlight.has(inFlight.evidentMessageId) && activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
10787
|
+
await this.settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick);
|
|
10424
10788
|
return;
|
|
10425
10789
|
}
|
|
10426
10790
|
const heartbeatDue = activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS;
|
|
@@ -10648,7 +11012,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10648
11012
|
usage,
|
|
10649
11013
|
void 0,
|
|
10650
11014
|
usageAgentName,
|
|
10651
|
-
subagentInvocations
|
|
11015
|
+
subagentInvocations,
|
|
11016
|
+
true
|
|
10652
11017
|
);
|
|
10653
11018
|
} catch (err) {
|
|
10654
11019
|
if (err instanceof ChannelAuthError) throw err;
|
|
@@ -11042,6 +11407,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11042
11407
|
}
|
|
11043
11408
|
const restartAborted = state === "failed" && sessionOngoing === false && isAbortedTerminalReply(messages, ocId ?? "");
|
|
11044
11409
|
if (restartAborted) {
|
|
11410
|
+
const age = this.absoluteAgeForRow(row.processing_started_at);
|
|
11411
|
+
if (age.tripped && age.anchorMs !== null) {
|
|
11412
|
+
try {
|
|
11413
|
+
await this.markFailed(
|
|
11414
|
+
row.conversation_id,
|
|
11415
|
+
row.id,
|
|
11416
|
+
sessionId,
|
|
11417
|
+
this.absoluteCeilingError(row.id, sessionId, age.anchorMs),
|
|
11418
|
+
void 0,
|
|
11419
|
+
{
|
|
11420
|
+
classification: null,
|
|
11421
|
+
errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
|
|
11422
|
+
}
|
|
11423
|
+
);
|
|
11424
|
+
} catch (err) {
|
|
11425
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
11426
|
+
this.log({
|
|
11427
|
+
level: "warn",
|
|
11428
|
+
message: `Re-adopt: failed to report past-ceiling restart orphan ${row.id.slice(0, 8)} terminal; refusing to re-dispatch: ${err instanceof Error ? err.message : String(err)}`,
|
|
11429
|
+
conversation_id: row.conversation_id,
|
|
11430
|
+
message_id: row.id
|
|
11431
|
+
});
|
|
11432
|
+
return;
|
|
11433
|
+
}
|
|
11434
|
+
void this.postSignal(row.conversation_id, row.id, "redrive_ceiling_refused");
|
|
11435
|
+
return;
|
|
11436
|
+
}
|
|
11045
11437
|
this.log({
|
|
11046
11438
|
level: "info",
|
|
11047
11439
|
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 the active-session status check \u2014 restart orphan, re-dispatching instead of marking it permanently failed`,
|
|
@@ -11587,7 +11979,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11587
11979
|
source_message_id: row.source_message_id,
|
|
11588
11980
|
slack_user_id: row.slack_user_id,
|
|
11589
11981
|
attachments: row.attachments ?? null,
|
|
11590
|
-
opencode_message_id: row.opencode_message_id
|
|
11982
|
+
opencode_message_id: row.opencode_message_id,
|
|
11983
|
+
processing_started_at: row.processing_started_at
|
|
11591
11984
|
};
|
|
11592
11985
|
}
|
|
11593
11986
|
/**
|
|
@@ -12494,7 +12887,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12494
12887
|
* exists but is wedged, so the next attempt must get a fresh one
|
|
12495
12888
|
* instead of reusing it — see WI-1's server-side null-clearing PATCH).
|
|
12496
12889
|
*/
|
|
12497
|
-
async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations) {
|
|
12890
|
+
async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations, abandonedWithoutAnswer) {
|
|
12498
12891
|
const body = { status: "failed" };
|
|
12499
12892
|
if (sessionId === null) {
|
|
12500
12893
|
body.opencode_session_id = null;
|
|
@@ -12507,12 +12900,19 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12507
12900
|
if (subagentInvocations && subagentInvocations.length > 0) {
|
|
12508
12901
|
body.subagent_invocations = subagentInvocations;
|
|
12509
12902
|
}
|
|
12510
|
-
if (failure) {
|
|
12511
|
-
body.
|
|
12512
|
-
body.failure_provider_id = failure.providerId;
|
|
12513
|
-
body.failure_model_id = failure.modelId;
|
|
12514
|
-
body.failure_reason = failure.reason;
|
|
12903
|
+
if (failure && failure.errorShape.name !== null) {
|
|
12904
|
+
body.failure_error_name = failure.errorShape.name;
|
|
12515
12905
|
}
|
|
12906
|
+
if (failure && failure.errorShape.statusCode !== null) {
|
|
12907
|
+
body.failure_status_code = failure.errorShape.statusCode;
|
|
12908
|
+
}
|
|
12909
|
+
if (failure?.classification) {
|
|
12910
|
+
body.failure_kind = failure.classification.kind;
|
|
12911
|
+
body.failure_provider_id = failure.classification.providerId;
|
|
12912
|
+
body.failure_model_id = failure.classification.modelId;
|
|
12913
|
+
body.failure_reason = failure.classification.reason;
|
|
12914
|
+
}
|
|
12915
|
+
if (abandonedWithoutAnswer) body.abandoned_without_answer = true;
|
|
12516
12916
|
try {
|
|
12517
12917
|
await this.callWithRetry(
|
|
12518
12918
|
"marking message as failed",
|
|
@@ -12532,27 +12932,28 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12532
12932
|
this.clearSubagentInvocationCaches(messageId);
|
|
12533
12933
|
}
|
|
12534
12934
|
/**
|
|
12535
|
-
* Classify an errored turn
|
|
12935
|
+
* Classify an errored turn and extract its bounded error shape for `markFailed`.
|
|
12536
12936
|
*
|
|
12537
12937
|
* `messageFailure` alone (structured OpenCode error → `model_auth`) covers
|
|
12538
12938
|
* most cases; when it returns `null` on this ALREADY-FAILED turn, fall back
|
|
12539
12939
|
* to the P1-2b zero-provider check — one extra loopback call to
|
|
12540
12940
|
* `hasAnyConfiguredProvider`, only reached when the structured classifier
|
|
12541
12941
|
* couldn't place it. Fails open (never throws): a fallback probe failure
|
|
12542
|
-
* (`null`/indeterminate) leaves the classification `null
|
|
12543
|
-
*
|
|
12942
|
+
* (`null`/indeterminate) leaves the classification `null`; the raw error shape
|
|
12943
|
+
* is still retained for diagnostics.
|
|
12544
12944
|
*/
|
|
12545
12945
|
async classifyModelAuthFailure(messages, userMessageId) {
|
|
12546
|
-
const classified = messageFailure(messages, userMessageId);
|
|
12547
|
-
if (classified != null) return classified;
|
|
12548
12946
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
12549
|
-
const
|
|
12550
|
-
|
|
12947
|
+
const errorShape = extractReplyErrorShape(reply);
|
|
12948
|
+
const classified = messageFailure(messages, userMessageId);
|
|
12949
|
+
if (classified != null) return { classification: classified, errorShape };
|
|
12950
|
+
const classification = applyZeroProviderFallback(
|
|
12551
12951
|
classified,
|
|
12552
|
-
|
|
12952
|
+
await this.hasAnyConfiguredProvider(),
|
|
12553
12953
|
reply?.info?.providerID ?? null,
|
|
12554
12954
|
reply?.info?.modelID ?? null
|
|
12555
12955
|
);
|
|
12956
|
+
return { classification, errorShape };
|
|
12556
12957
|
}
|
|
12557
12958
|
async recordSubagentModelAuthFailure(failure, conversationId, messageId) {
|
|
12558
12959
|
const providerId = failure.providerId ?? "(unknown)";
|
|
@@ -13410,6 +13811,159 @@ function applyRunnerOpenCodeConfig({
|
|
|
13410
13811
|
log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
|
|
13411
13812
|
}
|
|
13412
13813
|
|
|
13814
|
+
// src/lib/opencode/runtime-provenance.ts
|
|
13815
|
+
import { readFileSync as readFileSync7, readdirSync } from "node:fs";
|
|
13816
|
+
import { homedir as homedir7 } from "node:os";
|
|
13817
|
+
import { join as join12 } from "node:path";
|
|
13818
|
+
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
|
|
13819
|
+
var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
|
|
13820
|
+
function errorKind(error2) {
|
|
13821
|
+
if (!(error2 instanceof Error)) return "unknown error";
|
|
13822
|
+
const code = "code" in error2 && typeof error2.code === "string" ? ` (${error2.code})` : "";
|
|
13823
|
+
return `${error2.name}${code}`;
|
|
13824
|
+
}
|
|
13825
|
+
function parseRegistryPluginSpec(value) {
|
|
13826
|
+
const separator = value.lastIndexOf("@");
|
|
13827
|
+
if (separator <= 0) return null;
|
|
13828
|
+
const name = value.slice(0, separator);
|
|
13829
|
+
const spec = value.slice(separator + 1);
|
|
13830
|
+
if (!PACKAGE_NAME.test(name) || !REGISTRY_SPEC.test(spec)) return null;
|
|
13831
|
+
return { name, spec };
|
|
13832
|
+
}
|
|
13833
|
+
function parseJsonc(value) {
|
|
13834
|
+
let output = "";
|
|
13835
|
+
let inString = false;
|
|
13836
|
+
let escaped = false;
|
|
13837
|
+
let lineComment = false;
|
|
13838
|
+
let blockComment = false;
|
|
13839
|
+
for (let index = 0; index < value.length; index++) {
|
|
13840
|
+
const character = value[index];
|
|
13841
|
+
const next = value[index + 1];
|
|
13842
|
+
if (lineComment) {
|
|
13843
|
+
if (character === "\n") {
|
|
13844
|
+
lineComment = false;
|
|
13845
|
+
output += character;
|
|
13846
|
+
}
|
|
13847
|
+
continue;
|
|
13848
|
+
}
|
|
13849
|
+
if (blockComment) {
|
|
13850
|
+
if (character === "*" && next === "/") {
|
|
13851
|
+
blockComment = false;
|
|
13852
|
+
index++;
|
|
13853
|
+
} else if (character === "\n") {
|
|
13854
|
+
output += character;
|
|
13855
|
+
}
|
|
13856
|
+
continue;
|
|
13857
|
+
}
|
|
13858
|
+
if (inString) {
|
|
13859
|
+
output += character;
|
|
13860
|
+
if (escaped) escaped = false;
|
|
13861
|
+
else if (character === "\\") escaped = true;
|
|
13862
|
+
else if (character === '"') inString = false;
|
|
13863
|
+
continue;
|
|
13864
|
+
}
|
|
13865
|
+
if (character === '"') {
|
|
13866
|
+
inString = true;
|
|
13867
|
+
output += character;
|
|
13868
|
+
} else if (character === "/" && next === "/") {
|
|
13869
|
+
lineComment = true;
|
|
13870
|
+
index++;
|
|
13871
|
+
} else if (character === "/" && next === "*") {
|
|
13872
|
+
blockComment = true;
|
|
13873
|
+
index++;
|
|
13874
|
+
} else if (character === ",") {
|
|
13875
|
+
let lookahead = index + 1;
|
|
13876
|
+
while (/\s/.test(value[lookahead] ?? "")) lookahead++;
|
|
13877
|
+
if (value[lookahead] !== "]" && value[lookahead] !== "}") output += character;
|
|
13878
|
+
} else {
|
|
13879
|
+
output += character;
|
|
13880
|
+
}
|
|
13881
|
+
}
|
|
13882
|
+
return JSON.parse(output);
|
|
13883
|
+
}
|
|
13884
|
+
function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
|
|
13885
|
+
return join12(env.XDG_CACHE_HOME ?? join12(homeDir, ".cache"), "opencode");
|
|
13886
|
+
}
|
|
13887
|
+
function readConfiguredPluginSpecs({
|
|
13888
|
+
cwd = process.cwd(),
|
|
13889
|
+
log: log3
|
|
13890
|
+
}) {
|
|
13891
|
+
let config;
|
|
13892
|
+
try {
|
|
13893
|
+
try {
|
|
13894
|
+
config = parseJsonc(readFileSync7(join12(cwd, "opencode.jsonc"), "utf8"));
|
|
13895
|
+
} catch (error2) {
|
|
13896
|
+
if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
|
|
13897
|
+
config = parseJsonc(readFileSync7(join12(cwd, "opencode.json"), "utf8"));
|
|
13898
|
+
}
|
|
13899
|
+
} catch (error2) {
|
|
13900
|
+
log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
|
|
13901
|
+
return [];
|
|
13902
|
+
}
|
|
13903
|
+
if (typeof config !== "object" || config === null || !("plugin" in config)) return [];
|
|
13904
|
+
const plugins = config.plugin;
|
|
13905
|
+
if (!Array.isArray(plugins)) {
|
|
13906
|
+
log3("Could not read OpenCode plugin configuration: plugin is not an array");
|
|
13907
|
+
return [];
|
|
13908
|
+
}
|
|
13909
|
+
const configured = plugins.filter(
|
|
13910
|
+
(plugin) => typeof plugin === "string" && parseRegistryPluginSpec(plugin) !== null
|
|
13911
|
+
);
|
|
13912
|
+
if (configured.length !== plugins.length) {
|
|
13913
|
+
log3(
|
|
13914
|
+
`Skipped ${plugins.length - configured.length} non-registry OpenCode plugin spec(s); their values were not recorded`
|
|
13915
|
+
);
|
|
13916
|
+
}
|
|
13917
|
+
return configured;
|
|
13918
|
+
}
|
|
13919
|
+
function resolveInstalledPluginVersions({
|
|
13920
|
+
cacheRoot,
|
|
13921
|
+
env = process.env,
|
|
13922
|
+
homeDir = homedir7(),
|
|
13923
|
+
log: log3
|
|
13924
|
+
}) {
|
|
13925
|
+
const packagesRoot = join12(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
|
|
13926
|
+
let directories;
|
|
13927
|
+
try {
|
|
13928
|
+
directories = readdirSync(packagesRoot, { withFileTypes: true });
|
|
13929
|
+
} catch (error2) {
|
|
13930
|
+
if ("code" in Object(error2) && Object(error2).code === "ENOENT") return [];
|
|
13931
|
+
log3(`Could not inspect the OpenCode plugin cache: ${errorKind(error2)}`);
|
|
13932
|
+
return [];
|
|
13933
|
+
}
|
|
13934
|
+
const plugins = [];
|
|
13935
|
+
let skipped = 0;
|
|
13936
|
+
for (const directory of directories) {
|
|
13937
|
+
if (!directory.isDirectory()) continue;
|
|
13938
|
+
const parsed = parseRegistryPluginSpec(directory.name);
|
|
13939
|
+
if (parsed === null) {
|
|
13940
|
+
skipped++;
|
|
13941
|
+
continue;
|
|
13942
|
+
}
|
|
13943
|
+
let resolvedVersion = null;
|
|
13944
|
+
try {
|
|
13945
|
+
const packageJson = JSON.parse(
|
|
13946
|
+
readFileSync7(
|
|
13947
|
+
join12(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
|
|
13948
|
+
"utf8"
|
|
13949
|
+
)
|
|
13950
|
+
);
|
|
13951
|
+
if (typeof packageJson.version === "string" && REGISTRY_SPEC.test(packageJson.version)) {
|
|
13952
|
+
resolvedVersion = packageJson.version;
|
|
13953
|
+
} else {
|
|
13954
|
+
log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: invalid version`);
|
|
13955
|
+
}
|
|
13956
|
+
} catch (error2) {
|
|
13957
|
+
log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: ${errorKind(error2)}`);
|
|
13958
|
+
}
|
|
13959
|
+
plugins.push({ ...parsed, resolvedVersion });
|
|
13960
|
+
}
|
|
13961
|
+
if (skipped > 0) {
|
|
13962
|
+
log3(`Skipped ${skipped} non-registry OpenCode package cache entry; its value was not recorded`);
|
|
13963
|
+
}
|
|
13964
|
+
return plugins;
|
|
13965
|
+
}
|
|
13966
|
+
|
|
13413
13967
|
// src/lib/credential-sync.ts
|
|
13414
13968
|
import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13415
13969
|
var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
|
|
@@ -13649,6 +14203,8 @@ function createCredentialSync({
|
|
|
13649
14203
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
13650
14204
|
var MAX_DISCONNECT_REASON_LENGTH = 200;
|
|
13651
14205
|
var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
|
|
14206
|
+
var CREDENTIAL_ADOPTION_PROBE_ATTEMPTS = 3;
|
|
14207
|
+
var CREDENTIAL_ADOPTION_PROBE_INTERVAL_MS = 1e3;
|
|
13652
14208
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
13653
14209
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
13654
14210
|
var TELEMETRY_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
@@ -13683,7 +14239,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
13683
14239
|
if (trimmed === "") {
|
|
13684
14240
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
13685
14241
|
}
|
|
13686
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
14242
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join13(homeDir, trimmed.slice(2)) : trimmed;
|
|
13687
14243
|
if (!isAbsolute4(expanded)) {
|
|
13688
14244
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
13689
14245
|
}
|
|
@@ -13838,7 +14394,7 @@ function logActivity(state, entry) {
|
|
|
13838
14394
|
}
|
|
13839
14395
|
function reportSessionDbRecovery(state) {
|
|
13840
14396
|
try {
|
|
13841
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
14397
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir8(), env: process.env });
|
|
13842
14398
|
for (const record of report.records) {
|
|
13843
14399
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
13844
14400
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -13919,7 +14475,7 @@ async function importOpenCodeV2CredentialsForState(state) {
|
|
|
13919
14475
|
}
|
|
13920
14476
|
const result = await importOpenCodeV2Credentials({
|
|
13921
14477
|
request: client.request,
|
|
13922
|
-
homeDir:
|
|
14478
|
+
homeDir: homedir8(),
|
|
13923
14479
|
directory,
|
|
13924
14480
|
guard: state.opencodeV2CredentialImportGuard,
|
|
13925
14481
|
log: (message, level = "info") => logActivity(state, { type: "info", level, message })
|
|
@@ -13996,6 +14552,125 @@ async function handleAuthError(state, error2) {
|
|
|
13996
14552
|
return { success: false };
|
|
13997
14553
|
}
|
|
13998
14554
|
}
|
|
14555
|
+
function logCredentialAdoptionActivity(state, activity, level) {
|
|
14556
|
+
const message = (() => {
|
|
14557
|
+
switch (activity.outcome) {
|
|
14558
|
+
case "deferred":
|
|
14559
|
+
return `CREDENTIAL-ADOPTION: deferred generation ${activity.generation} with ${activity.active_sessions} active session(s)`;
|
|
14560
|
+
case "reloaded_ready":
|
|
14561
|
+
return `CREDENTIAL-ADOPTION: adopted generation ${activity.generation} with ${activity.active_sessions} active session(s); readiness probe succeeded after ${activity.probe_attempts} attempt(s)`;
|
|
14562
|
+
case "reloaded_interrupted": {
|
|
14563
|
+
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`;
|
|
14564
|
+
return `CREDENTIAL-ADOPTION: generation ${activity.generation} with ${activity.active_sessions} active session(s); replacement instance answered ${response} after ${activity.probe_attempts} probe attempt(s)`;
|
|
14565
|
+
}
|
|
14566
|
+
case "reload_failed":
|
|
14567
|
+
return `CREDENTIAL-ADOPTION: failed to adopt generation ${activity.generation} with ${activity.active_sessions} active session(s)`;
|
|
14568
|
+
case "skipped_v2":
|
|
14569
|
+
return `CREDENTIAL-ADOPTION: skipped generation ${activity.generation} with ${activity.active_sessions} active session(s) on OpenCode V2`;
|
|
14570
|
+
}
|
|
14571
|
+
})();
|
|
14572
|
+
logActivity(state, {
|
|
14573
|
+
type: level === "error" ? "error" : "info",
|
|
14574
|
+
level,
|
|
14575
|
+
...level === "error" ? { error: message } : { message },
|
|
14576
|
+
metadata: {
|
|
14577
|
+
...activity,
|
|
14578
|
+
opencode_version: state.opencodeVersion,
|
|
14579
|
+
port: state.port
|
|
14580
|
+
}
|
|
14581
|
+
});
|
|
14582
|
+
}
|
|
14583
|
+
async function adoptCredentialGeneration(state, generation, adoption, activeSessions, deferredForMs) {
|
|
14584
|
+
const reload = await disposeAndReloadProviderConfig(
|
|
14585
|
+
state.port,
|
|
14586
|
+
state.opencodeClient ?? void 0
|
|
14587
|
+
);
|
|
14588
|
+
if (reload.outcome === "failed") {
|
|
14589
|
+
logCredentialAdoptionActivity(
|
|
14590
|
+
state,
|
|
14591
|
+
{
|
|
14592
|
+
outcome: "reload_failed",
|
|
14593
|
+
generation,
|
|
14594
|
+
active_sessions: activeSessions,
|
|
14595
|
+
adoption,
|
|
14596
|
+
deferred_for_ms: deferredForMs,
|
|
14597
|
+
reload_status: reload.status,
|
|
14598
|
+
reload_detail: reload.detail,
|
|
14599
|
+
probe_attempts: null,
|
|
14600
|
+
probe_status: null,
|
|
14601
|
+
probe_body_empty: null,
|
|
14602
|
+
probe_detail: null
|
|
14603
|
+
},
|
|
14604
|
+
"error"
|
|
14605
|
+
);
|
|
14606
|
+
return;
|
|
14607
|
+
}
|
|
14608
|
+
if (reload.outcome === "skipped_v2") {
|
|
14609
|
+
logCredentialAdoptionActivity(
|
|
14610
|
+
state,
|
|
14611
|
+
{
|
|
14612
|
+
outcome: "skipped_v2",
|
|
14613
|
+
generation,
|
|
14614
|
+
active_sessions: activeSessions,
|
|
14615
|
+
adoption,
|
|
14616
|
+
deferred_for_ms: deferredForMs,
|
|
14617
|
+
reload_status: null,
|
|
14618
|
+
reload_detail: null,
|
|
14619
|
+
probe_attempts: null,
|
|
14620
|
+
probe_status: null,
|
|
14621
|
+
probe_body_empty: null,
|
|
14622
|
+
probe_detail: null
|
|
14623
|
+
},
|
|
14624
|
+
"debug"
|
|
14625
|
+
);
|
|
14626
|
+
return;
|
|
14627
|
+
}
|
|
14628
|
+
let lastProbe = null;
|
|
14629
|
+
let attempts = 0;
|
|
14630
|
+
for (attempts = 1; attempts <= CREDENTIAL_ADOPTION_PROBE_ATTEMPTS; attempts++) {
|
|
14631
|
+
lastProbe = await probeOpenCodeRequestReadiness(state.port, state.opencodeClient ?? void 0);
|
|
14632
|
+
if (lastProbe.ok) {
|
|
14633
|
+
logCredentialAdoptionActivity(
|
|
14634
|
+
state,
|
|
14635
|
+
{
|
|
14636
|
+
outcome: "reloaded_ready",
|
|
14637
|
+
generation,
|
|
14638
|
+
active_sessions: activeSessions,
|
|
14639
|
+
adoption,
|
|
14640
|
+
deferred_for_ms: deferredForMs,
|
|
14641
|
+
reload_status: null,
|
|
14642
|
+
reload_detail: null,
|
|
14643
|
+
probe_attempts: attempts,
|
|
14644
|
+
probe_status: lastProbe.status,
|
|
14645
|
+
probe_body_empty: lastProbe.bodyEmpty,
|
|
14646
|
+
probe_detail: lastProbe.detail
|
|
14647
|
+
},
|
|
14648
|
+
"warn"
|
|
14649
|
+
);
|
|
14650
|
+
return;
|
|
14651
|
+
}
|
|
14652
|
+
if (attempts < CREDENTIAL_ADOPTION_PROBE_ATTEMPTS) {
|
|
14653
|
+
await new Promise((resolve5) => setTimeout(resolve5, CREDENTIAL_ADOPTION_PROBE_INTERVAL_MS));
|
|
14654
|
+
}
|
|
14655
|
+
}
|
|
14656
|
+
logCredentialAdoptionActivity(
|
|
14657
|
+
state,
|
|
14658
|
+
{
|
|
14659
|
+
outcome: "reloaded_interrupted",
|
|
14660
|
+
generation,
|
|
14661
|
+
active_sessions: activeSessions,
|
|
14662
|
+
adoption,
|
|
14663
|
+
deferred_for_ms: deferredForMs,
|
|
14664
|
+
reload_status: null,
|
|
14665
|
+
reload_detail: null,
|
|
14666
|
+
probe_attempts: CREDENTIAL_ADOPTION_PROBE_ATTEMPTS,
|
|
14667
|
+
probe_status: lastProbe?.status ?? null,
|
|
14668
|
+
probe_body_empty: lastProbe?.bodyEmpty ?? null,
|
|
14669
|
+
probe_detail: lastProbe?.detail ?? null
|
|
14670
|
+
},
|
|
14671
|
+
"error"
|
|
14672
|
+
);
|
|
14673
|
+
}
|
|
13999
14674
|
async function driveChannels(state, driver) {
|
|
14000
14675
|
let idlePolls = 0;
|
|
14001
14676
|
let idleMs = 0;
|
|
@@ -14005,6 +14680,15 @@ async function driveChannels(state, driver) {
|
|
|
14005
14680
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
14006
14681
|
let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
|
|
14007
14682
|
let lastSeenOpencodeAuthApplies = driver.fileSyncActivity().opencodeAuthApplyCount;
|
|
14683
|
+
const credentialAdoption = {
|
|
14684
|
+
generation: 0,
|
|
14685
|
+
pendingGeneration: null,
|
|
14686
|
+
pendingSinceMs: null,
|
|
14687
|
+
deferredGeneration: null,
|
|
14688
|
+
adoption: 0
|
|
14689
|
+
};
|
|
14690
|
+
const stayAwakeHolds = () => state.stayAwakeUntilMs !== null && Date.now() < state.stayAwakeUntilMs;
|
|
14691
|
+
let stayAwakeSuppressionLogged = false;
|
|
14008
14692
|
while (state.running) {
|
|
14009
14693
|
const cycleStartedAtMs = performance.now();
|
|
14010
14694
|
let idleThisCycle = false;
|
|
@@ -14046,12 +14730,30 @@ async function driveChannels(state, driver) {
|
|
|
14046
14730
|
lastSeenOpencodeAuthApplies = opencodeAuthApplies;
|
|
14047
14731
|
if (opencodeAuthApplied) state.openaiUsageRearm?.();
|
|
14048
14732
|
if (claudeCredentialApplied || opencodeAuthApplied) {
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14733
|
+
credentialAdoption.generation++;
|
|
14734
|
+
if (state.opencodeApiVersion === "v2") {
|
|
14735
|
+
logCredentialAdoptionActivity(
|
|
14736
|
+
state,
|
|
14737
|
+
{
|
|
14738
|
+
outcome: "skipped_v2",
|
|
14739
|
+
generation: credentialAdoption.generation,
|
|
14740
|
+
active_sessions: driver.protectedSessionIds().size,
|
|
14741
|
+
adoption: null,
|
|
14742
|
+
deferred_for_ms: null,
|
|
14743
|
+
reload_status: null,
|
|
14744
|
+
reload_detail: null,
|
|
14745
|
+
probe_attempts: null,
|
|
14746
|
+
probe_status: null,
|
|
14747
|
+
probe_body_empty: null,
|
|
14748
|
+
probe_detail: null
|
|
14749
|
+
},
|
|
14750
|
+
"debug"
|
|
14751
|
+
);
|
|
14752
|
+
} else {
|
|
14753
|
+
credentialAdoption.pendingGeneration = credentialAdoption.generation;
|
|
14754
|
+
credentialAdoption.pendingSinceMs = Date.now();
|
|
14755
|
+
credentialAdoption.deferredGeneration = null;
|
|
14756
|
+
}
|
|
14055
14757
|
}
|
|
14056
14758
|
if (opencodeAuthApplied && state.opencodeApiVersion === "v2") {
|
|
14057
14759
|
void importOpenCodeV2CredentialsForState(state).catch(
|
|
@@ -14106,6 +14808,49 @@ async function driveChannels(state, driver) {
|
|
|
14106
14808
|
}
|
|
14107
14809
|
}
|
|
14108
14810
|
}
|
|
14811
|
+
if (credentialAdoption.pendingGeneration !== null) {
|
|
14812
|
+
const activeSessions = driver.protectedSessionIds();
|
|
14813
|
+
const generation = credentialAdoption.pendingGeneration;
|
|
14814
|
+
if (activeSessions.size > 0) {
|
|
14815
|
+
if (credentialAdoption.deferredGeneration !== generation) {
|
|
14816
|
+
credentialAdoption.deferredGeneration = generation;
|
|
14817
|
+
logCredentialAdoptionActivity(
|
|
14818
|
+
state,
|
|
14819
|
+
{
|
|
14820
|
+
outcome: "deferred",
|
|
14821
|
+
generation,
|
|
14822
|
+
active_sessions: activeSessions.size,
|
|
14823
|
+
adoption: null,
|
|
14824
|
+
deferred_for_ms: null,
|
|
14825
|
+
reload_status: null,
|
|
14826
|
+
reload_detail: null,
|
|
14827
|
+
probe_attempts: null,
|
|
14828
|
+
probe_status: null,
|
|
14829
|
+
probe_body_empty: null,
|
|
14830
|
+
probe_detail: null
|
|
14831
|
+
},
|
|
14832
|
+
"warn"
|
|
14833
|
+
);
|
|
14834
|
+
}
|
|
14835
|
+
} else {
|
|
14836
|
+
const deferredForMs = credentialAdoption.deferredGeneration === generation && credentialAdoption.pendingSinceMs !== null ? Math.max(0, Date.now() - credentialAdoption.pendingSinceMs) : null;
|
|
14837
|
+
credentialAdoption.pendingGeneration = null;
|
|
14838
|
+
credentialAdoption.pendingSinceMs = null;
|
|
14839
|
+
credentialAdoption.adoption++;
|
|
14840
|
+
void adoptCredentialGeneration(
|
|
14841
|
+
state,
|
|
14842
|
+
generation,
|
|
14843
|
+
credentialAdoption.adoption,
|
|
14844
|
+
activeSessions.size,
|
|
14845
|
+
deferredForMs
|
|
14846
|
+
).catch(
|
|
14847
|
+
(error2) => logActivity(state, {
|
|
14848
|
+
type: "error",
|
|
14849
|
+
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)}`
|
|
14850
|
+
})
|
|
14851
|
+
);
|
|
14852
|
+
}
|
|
14853
|
+
}
|
|
14109
14854
|
await new Promise((resolve5) => setTimeout(resolve5, CHANNEL_POLL_INTERVAL_MS));
|
|
14110
14855
|
const cycleMs = performance.now() - cycleStartedAtMs;
|
|
14111
14856
|
if (idleThisCycle) idleMs += cycleMs;
|
|
@@ -14119,11 +14864,20 @@ async function driveChannels(state, driver) {
|
|
|
14119
14864
|
if (state.interactive) displayStatus(state);
|
|
14120
14865
|
break;
|
|
14121
14866
|
}
|
|
14122
|
-
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3) {
|
|
14867
|
+
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3 && !stayAwakeHolds()) {
|
|
14123
14868
|
logActivity(state, { type: "info", message: "Idle timeout reached" });
|
|
14124
14869
|
if (state.interactive) displayStatus(state);
|
|
14125
14870
|
break;
|
|
14126
14871
|
}
|
|
14872
|
+
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3 && stayAwakeHolds()) {
|
|
14873
|
+
if (!stayAwakeSuppressionLogged) {
|
|
14874
|
+
stayAwakeSuppressionLogged = true;
|
|
14875
|
+
logActivity(state, {
|
|
14876
|
+
type: "info",
|
|
14877
|
+
message: "Idle, but staying awake until the requested minimum passes"
|
|
14878
|
+
});
|
|
14879
|
+
}
|
|
14880
|
+
}
|
|
14127
14881
|
}
|
|
14128
14882
|
}
|
|
14129
14883
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
@@ -14141,7 +14895,7 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
14141
14895
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
14142
14896
|
}
|
|
14143
14897
|
function sessionDbPath() {
|
|
14144
|
-
return
|
|
14898
|
+
return join13(homedir8(), ".local", "share", "opencode", "opencode.db");
|
|
14145
14899
|
}
|
|
14146
14900
|
function logSessionCleanupActivity(state, phase, entry) {
|
|
14147
14901
|
logActivity(state, {
|
|
@@ -14385,7 +15139,7 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
14385
15139
|
for (const warning2 of config.warnings) {
|
|
14386
15140
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
14387
15141
|
}
|
|
14388
|
-
const dbBytes = statSessionDbBytes(
|
|
15142
|
+
const dbBytes = statSessionDbBytes(homedir8());
|
|
14389
15143
|
void (async () => {
|
|
14390
15144
|
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
14391
15145
|
if (reclaimAvailability !== null) {
|
|
@@ -14452,10 +15206,12 @@ function scheduleUsageReporting(state, params) {
|
|
|
14452
15206
|
let phase = "dormant";
|
|
14453
15207
|
let rearmRequested = false;
|
|
14454
15208
|
const armProbe = () => {
|
|
15209
|
+
if (!state.running) return;
|
|
14455
15210
|
phase = "probe-pending";
|
|
14456
15211
|
params.setTimer(setTimeout(() => void tick(true), params.firstDelayMs()));
|
|
14457
15212
|
};
|
|
14458
15213
|
const scheduleNextTick = () => {
|
|
15214
|
+
if (!state.running) return;
|
|
14459
15215
|
if (rearmRequested) {
|
|
14460
15216
|
rearmRequested = false;
|
|
14461
15217
|
armProbe();
|
|
@@ -14489,6 +15245,7 @@ function scheduleUsageReporting(state, params) {
|
|
|
14489
15245
|
phase = "tick-in-flight";
|
|
14490
15246
|
try {
|
|
14491
15247
|
const usage = await params.fetchUsage();
|
|
15248
|
+
if (!state.running) return;
|
|
14492
15249
|
const result = await params.report(usage);
|
|
14493
15250
|
if (result.ok) {
|
|
14494
15251
|
if (consecutiveFailures > 0) {
|
|
@@ -14601,7 +15358,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14601
15358
|
});
|
|
14602
15359
|
return;
|
|
14603
15360
|
}
|
|
14604
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
15361
|
+
const { collect, stop } = createResourceUsageCollector(homedir8());
|
|
14605
15362
|
state.stopResourceUsageSampling = stop;
|
|
14606
15363
|
let consecutiveFailures = 0;
|
|
14607
15364
|
const tick = async () => {
|
|
@@ -14627,6 +15384,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14627
15384
|
message: lowDiskDecision.warning
|
|
14628
15385
|
});
|
|
14629
15386
|
}
|
|
15387
|
+
if (!state.running) return;
|
|
14630
15388
|
const result = await reportResourceUsage(state.agentId, state.authHeader, usage);
|
|
14631
15389
|
if (result.ok) {
|
|
14632
15390
|
if (consecutiveFailures > 0) {
|
|
@@ -14659,7 +15417,9 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14659
15417
|
message: `Resource usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
14660
15418
|
});
|
|
14661
15419
|
} finally {
|
|
14662
|
-
state.
|
|
15420
|
+
if (state.running) {
|
|
15421
|
+
state.resourceUsageTimer = setTimeout(() => void tick(), usageReportDelayMs());
|
|
15422
|
+
}
|
|
14663
15423
|
}
|
|
14664
15424
|
};
|
|
14665
15425
|
state.resourceUsageTimer = setTimeout(() => void tick(), firstReportDelayMs());
|
|
@@ -14839,7 +15599,7 @@ async function run(options) {
|
|
|
14839
15599
|
let fileSyncDirectories;
|
|
14840
15600
|
try {
|
|
14841
15601
|
logLevel = resolveLogLevel(options);
|
|
14842
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
15602
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir8());
|
|
14843
15603
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
14844
15604
|
throw new Error(
|
|
14845
15605
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -14862,6 +15622,8 @@ async function run(options) {
|
|
|
14862
15622
|
port: options.port ?? 4096,
|
|
14863
15623
|
conversationFilter: options.conversation ?? null,
|
|
14864
15624
|
idleTimeout: options.idleTimeout ?? null,
|
|
15625
|
+
stayAwakeSeconds: null,
|
|
15626
|
+
stayAwakeUntilMs: null,
|
|
14865
15627
|
json: options.json ?? false,
|
|
14866
15628
|
interactive,
|
|
14867
15629
|
logLevel,
|
|
@@ -15051,6 +15813,17 @@ async function run(options) {
|
|
|
15051
15813
|
}
|
|
15052
15814
|
spinner?.succeed(`Runner: ${validation.agent.name || state.agentId}`);
|
|
15053
15815
|
state.agentName = validation.agent.name;
|
|
15816
|
+
const requestedStayAwakeSeconds = validation.agent?.wake_stay_awake_seconds;
|
|
15817
|
+
if (typeof requestedStayAwakeSeconds === "number" && Number.isFinite(requestedStayAwakeSeconds) && requestedStayAwakeSeconds > 0) {
|
|
15818
|
+
state.stayAwakeSeconds = requestedStayAwakeSeconds;
|
|
15819
|
+
} else if (requestedStayAwakeSeconds !== void 0 && requestedStayAwakeSeconds !== null) {
|
|
15820
|
+
state.stayAwakeSeconds = null;
|
|
15821
|
+
log2(
|
|
15822
|
+
state,
|
|
15823
|
+
`Ignoring invalid wake_stay_awake_seconds (type: ${typeof requestedStayAwakeSeconds})`,
|
|
15824
|
+
"warn"
|
|
15825
|
+
);
|
|
15826
|
+
}
|
|
15054
15827
|
const microvmId = process.env.MICROVM_ID?.trim();
|
|
15055
15828
|
if (microvmId) {
|
|
15056
15829
|
const reported = await reportMicrovmId(state.agentId, state.authHeader, microvmId);
|
|
@@ -15112,7 +15885,7 @@ async function run(options) {
|
|
|
15112
15885
|
state.credentialSync?.arm();
|
|
15113
15886
|
const dependencyInstallDiagnosticReporter = createDependencyInstallDiagnosticReporter({
|
|
15114
15887
|
context: {
|
|
15115
|
-
homeDir:
|
|
15888
|
+
homeDir: homedir8(),
|
|
15116
15889
|
cwd: process.cwd(),
|
|
15117
15890
|
microvmId: process.env.MICROVM_ID?.trim(),
|
|
15118
15891
|
euid: process.getuid?.(),
|
|
@@ -15135,7 +15908,7 @@ async function run(options) {
|
|
|
15135
15908
|
dependencyInstallDiagnosticReporter
|
|
15136
15909
|
);
|
|
15137
15910
|
const openCodeLogTail = tailOpenCodeLogFile(
|
|
15138
|
-
resolveOpenCodeLogPath(
|
|
15911
|
+
resolveOpenCodeLogPath(homedir8(), process.env),
|
|
15139
15912
|
openCodeActivityForwarder
|
|
15140
15913
|
);
|
|
15141
15914
|
state.stopOpenCodeLogTail = () => {
|
|
@@ -15236,6 +16009,32 @@ async function run(options) {
|
|
|
15236
16009
|
state.opencodeProcess = options.opencodePidFile ? null : oc.process;
|
|
15237
16010
|
state.opencodeVersion = oc.version;
|
|
15238
16011
|
state.opencodeApiVersion = opencodeVersion;
|
|
16012
|
+
let configuredPlugins = [];
|
|
16013
|
+
let resolvedPlugins = [];
|
|
16014
|
+
try {
|
|
16015
|
+
const provenanceLog = (message) => logActivity(state, { type: "info", level: "warn", message });
|
|
16016
|
+
configuredPlugins = readConfiguredPluginSpecs({ log: provenanceLog });
|
|
16017
|
+
resolvedPlugins = resolveInstalledPluginVersions({ log: provenanceLog });
|
|
16018
|
+
} catch (error2) {
|
|
16019
|
+
const kind = error2 instanceof Error ? error2.name : "unknown error";
|
|
16020
|
+
logActivity(state, {
|
|
16021
|
+
type: "info",
|
|
16022
|
+
level: "warn",
|
|
16023
|
+
message: `Could not collect OpenCode runtime provenance: ${kind}`
|
|
16024
|
+
});
|
|
16025
|
+
}
|
|
16026
|
+
logActivity(state, {
|
|
16027
|
+
type: "info",
|
|
16028
|
+
level: "info",
|
|
16029
|
+
message: "OpenCode runtime provenance",
|
|
16030
|
+
metadata: {
|
|
16031
|
+
opencode_version: state.opencodeVersion,
|
|
16032
|
+
opencode_api_version: state.opencodeApiVersion,
|
|
16033
|
+
configured_plugins: JSON.stringify(configuredPlugins),
|
|
16034
|
+
resolved_plugins: JSON.stringify(resolvedPlugins)
|
|
16035
|
+
},
|
|
16036
|
+
source: "cli.runtime-provenance"
|
|
16037
|
+
});
|
|
15239
16038
|
let opencodePassword = null;
|
|
15240
16039
|
if (opencodeVersion === "v2" && "password" in oc) {
|
|
15241
16040
|
const value = oc.password;
|
|
@@ -15264,7 +16063,7 @@ async function run(options) {
|
|
|
15264
16063
|
const provenance = checkSessionDbProvenance({
|
|
15265
16064
|
dbPath: sessionDbPath(),
|
|
15266
16065
|
currentVersion: state.opencodeVersion,
|
|
15267
|
-
homeDir:
|
|
16066
|
+
homeDir: homedir8(),
|
|
15268
16067
|
env: process.env
|
|
15269
16068
|
});
|
|
15270
16069
|
if (provenance.anomaly) {
|
|
@@ -15294,7 +16093,7 @@ async function run(options) {
|
|
|
15294
16093
|
logActivity(state, { type: "info", level: "warn", message: versionWarning });
|
|
15295
16094
|
}
|
|
15296
16095
|
}
|
|
15297
|
-
await
|
|
16096
|
+
await disposeAndReloadProviderConfig(state.port, state.opencodeClient ?? void 0);
|
|
15298
16097
|
if (state.opencodeApiVersion === "v2" && state.opencodeClient !== null) {
|
|
15299
16098
|
await importOpenCodeV2CredentialsForState(state);
|
|
15300
16099
|
}
|
|
@@ -15336,7 +16135,7 @@ async function run(options) {
|
|
|
15336
16135
|
let existingPid;
|
|
15337
16136
|
if (existsSync3(options.litestreamPidFile)) {
|
|
15338
16137
|
try {
|
|
15339
|
-
const rawPid =
|
|
16138
|
+
const rawPid = readFileSync8(options.litestreamPidFile, "utf8").trim();
|
|
15340
16139
|
const parsedPid = Number(rawPid);
|
|
15341
16140
|
if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
|
|
15342
16141
|
existingPid = parsedPid;
|
|
@@ -15435,7 +16234,7 @@ async function run(options) {
|
|
|
15435
16234
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
15436
16235
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
15437
16236
|
fileSyncDirectories,
|
|
15438
|
-
homeDir:
|
|
16237
|
+
homeDir: homedir8(),
|
|
15439
16238
|
maxActiveSessions,
|
|
15440
16239
|
log: (entry) => (
|
|
15441
16240
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -15465,6 +16264,13 @@ async function run(options) {
|
|
|
15465
16264
|
type: "info",
|
|
15466
16265
|
message: connectionMessage
|
|
15467
16266
|
});
|
|
16267
|
+
if (!isReconnect && state.stayAwakeSeconds !== null && state.stayAwakeUntilMs === null) {
|
|
16268
|
+
state.stayAwakeUntilMs = Date.now() + state.stayAwakeSeconds * 1e3;
|
|
16269
|
+
logActivity(state, {
|
|
16270
|
+
type: "info",
|
|
16271
|
+
message: `Staying awake for at least ${state.stayAwakeSeconds}s as requested by the wake`
|
|
16272
|
+
});
|
|
16273
|
+
}
|
|
15468
16274
|
if (options.tunnelReadyFile) {
|
|
15469
16275
|
const marker = writeTunnelReadyMarker(options.tunnelReadyFile, agentId);
|
|
15470
16276
|
if (marker.ok) {
|
|
@@ -15599,6 +16405,7 @@ async function run(options) {
|
|
|
15599
16405
|
if (error2.message === "Unauthorized") tunnelSpinner?.fail("Unauthorized");
|
|
15600
16406
|
throw error2;
|
|
15601
16407
|
}
|
|
16408
|
+
if (!state.running) return;
|
|
15602
16409
|
scheduleSessionCleanup(state, channelDriver, options);
|
|
15603
16410
|
state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
|
|
15604
16411
|
state.openaiUsageRearm = scheduleUsageReporting(state, {
|