@evident-ai/cli 3.5.2-dev.ffa823b → 3.6.1-dev.1a9c5e7
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/README.md +4 -0
- package/dist/index.js +1525 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -100,6 +100,26 @@ function clearCredentials() {
|
|
|
100
100
|
credentials.set("byEndpoint", byEndpoint);
|
|
101
101
|
hardenCredentialsPermissions();
|
|
102
102
|
}
|
|
103
|
+
function getManagedInferenceCredential(runnerId) {
|
|
104
|
+
hardenCredentialsPermissions();
|
|
105
|
+
return credentials.get("managedInferenceByEndpoint")?.[credentialsKey()]?.[runnerId];
|
|
106
|
+
}
|
|
107
|
+
function setManagedInferenceCredential(runnerId, credential) {
|
|
108
|
+
const byEndpoint = credentials.get("managedInferenceByEndpoint") ?? {};
|
|
109
|
+
byEndpoint[credentialsKey()] ??= {};
|
|
110
|
+
byEndpoint[credentialsKey()][runnerId] = credential;
|
|
111
|
+
credentials.set("managedInferenceByEndpoint", byEndpoint);
|
|
112
|
+
hardenCredentialsPermissions();
|
|
113
|
+
}
|
|
114
|
+
function clearManagedInferenceCredential(runnerId) {
|
|
115
|
+
const byEndpoint = credentials.get("managedInferenceByEndpoint") ?? {};
|
|
116
|
+
const runners = byEndpoint[credentialsKey()];
|
|
117
|
+
if (!runners || !(runnerId in runners)) return;
|
|
118
|
+
delete runners[runnerId];
|
|
119
|
+
if (Object.keys(runners).length === 0) delete byEndpoint[credentialsKey()];
|
|
120
|
+
credentials.set("managedInferenceByEndpoint", byEndpoint);
|
|
121
|
+
hardenCredentialsPermissions();
|
|
122
|
+
}
|
|
103
123
|
function clearAllCredentials() {
|
|
104
124
|
credentials.clear();
|
|
105
125
|
hardenCredentialsPermissions();
|
|
@@ -119,13 +139,13 @@ function getCliName() {
|
|
|
119
139
|
// src/lib/api.ts
|
|
120
140
|
async function request(path, method, body, headers = {}) {
|
|
121
141
|
const url = `${getApiUrlConfig()}${path}`;
|
|
122
|
-
const
|
|
142
|
+
const requestHeaders2 = {
|
|
123
143
|
"Content-Type": "application/json",
|
|
124
144
|
...headers
|
|
125
145
|
};
|
|
126
146
|
const response = await fetch(url, {
|
|
127
147
|
method,
|
|
128
|
-
headers:
|
|
148
|
+
headers: requestHeaders2,
|
|
129
149
|
body: body ? JSON.stringify(body) : void 0
|
|
130
150
|
});
|
|
131
151
|
if (!response.ok) {
|
|
@@ -1156,9 +1176,9 @@ async function claudeUsage() {
|
|
|
1156
1176
|
}
|
|
1157
1177
|
|
|
1158
1178
|
// 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
|
|
1179
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1180
|
+
import { homedir as homedir8 } from "node:os";
|
|
1181
|
+
import { isAbsolute as isAbsolute4, join as join13, parse, resolve as resolvePath2 } from "node:path";
|
|
1162
1182
|
import chalk7 from "chalk";
|
|
1163
1183
|
|
|
1164
1184
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -1287,13 +1307,113 @@ var TUNNEL_USAGE_REARM_PING_PATH = "/__evident/usage-rearm";
|
|
|
1287
1307
|
var MAX_FILE_PUSH_BYTES = 64 * 1024;
|
|
1288
1308
|
var MAX_FILE_SYNC_DIRECTORIES = 16;
|
|
1289
1309
|
|
|
1310
|
+
// ../../packages/types/src/credential-proxy-native-config.ts
|
|
1311
|
+
var EVIDENT_PROXY_TOKEN_REFERENCE = "{env:EVIDENT_PROXY_TOKEN}";
|
|
1312
|
+
var CLAUDE_PROVIDER = {
|
|
1313
|
+
namespace: "evident-claude",
|
|
1314
|
+
name: "Evident Claude credential proxy",
|
|
1315
|
+
npm: "@ai-sdk/anthropic"
|
|
1316
|
+
};
|
|
1317
|
+
var NATIVE_PROVIDERS = {
|
|
1318
|
+
claude: CLAUDE_PROVIDER,
|
|
1319
|
+
anthropic: CLAUDE_PROVIDER,
|
|
1320
|
+
codex: {
|
|
1321
|
+
namespace: "evident-codex",
|
|
1322
|
+
name: "Evident Codex credential proxy",
|
|
1323
|
+
npm: "@ai-sdk/openai"
|
|
1324
|
+
},
|
|
1325
|
+
openai: {
|
|
1326
|
+
namespace: "evident-openai",
|
|
1327
|
+
name: "Evident OpenAI credential proxy",
|
|
1328
|
+
npm: "@ai-sdk/openai"
|
|
1329
|
+
},
|
|
1330
|
+
kimi: {
|
|
1331
|
+
namespace: "evident-kimi",
|
|
1332
|
+
name: "Evident Kimi credential proxy",
|
|
1333
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1334
|
+
sdkName: "evident-kimi"
|
|
1335
|
+
},
|
|
1336
|
+
antigravity: {
|
|
1337
|
+
namespace: "evident-antigravity",
|
|
1338
|
+
name: "Evident Antigravity credential proxy",
|
|
1339
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1340
|
+
sdkName: "evident-antigravity"
|
|
1341
|
+
},
|
|
1342
|
+
xai: {
|
|
1343
|
+
namespace: "evident-xai",
|
|
1344
|
+
name: "Evident xAI credential proxy",
|
|
1345
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1346
|
+
sdkName: "evident-xai"
|
|
1347
|
+
},
|
|
1348
|
+
mistral: {
|
|
1349
|
+
namespace: "evident-mistral",
|
|
1350
|
+
name: "Evident Mistral credential proxy",
|
|
1351
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1352
|
+
sdkName: "evident-mistral"
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
var CREDENTIAL_PROXY_NATIVE_NAMESPACES = [
|
|
1356
|
+
...new Set(Object.values(NATIVE_PROVIDERS).map(({ namespace }) => namespace))
|
|
1357
|
+
];
|
|
1358
|
+
function credentialProxyInferenceUrl(baseUrl) {
|
|
1359
|
+
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
1360
|
+
return /\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`;
|
|
1361
|
+
}
|
|
1362
|
+
function buildCredentialProxyNativeConfig({
|
|
1363
|
+
inferenceBaseUrl,
|
|
1364
|
+
modelDescriptors
|
|
1365
|
+
}) {
|
|
1366
|
+
const baseURL = credentialProxyInferenceUrl(inferenceBaseUrl);
|
|
1367
|
+
const providers = {};
|
|
1368
|
+
const unsupportedProviders = /* @__PURE__ */ new Set();
|
|
1369
|
+
for (const descriptor of modelDescriptors) {
|
|
1370
|
+
const definition = NATIVE_PROVIDERS[descriptor.provider.trim().toLowerCase()];
|
|
1371
|
+
if (!definition) {
|
|
1372
|
+
unsupportedProviders.add(descriptor.provider.trim() || descriptor.provider);
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
providers[definition.namespace] ??= definition.npm === "@ai-sdk/anthropic" ? {
|
|
1376
|
+
name: definition.name,
|
|
1377
|
+
npm: definition.npm,
|
|
1378
|
+
options: { baseURL, authToken: EVIDENT_PROXY_TOKEN_REFERENCE },
|
|
1379
|
+
models: {}
|
|
1380
|
+
} : definition.npm === "@ai-sdk/openai" ? {
|
|
1381
|
+
name: definition.name,
|
|
1382
|
+
npm: definition.npm,
|
|
1383
|
+
options: { baseURL, apiKey: EVIDENT_PROXY_TOKEN_REFERENCE },
|
|
1384
|
+
models: {}
|
|
1385
|
+
} : {
|
|
1386
|
+
name: definition.name,
|
|
1387
|
+
npm: definition.npm,
|
|
1388
|
+
options: {
|
|
1389
|
+
name: definition.sdkName,
|
|
1390
|
+
baseURL,
|
|
1391
|
+
apiKey: EVIDENT_PROXY_TOKEN_REFERENCE
|
|
1392
|
+
},
|
|
1393
|
+
models: {}
|
|
1394
|
+
};
|
|
1395
|
+
providers[definition.namespace].models[descriptor.id] = { name: descriptor.id };
|
|
1396
|
+
}
|
|
1397
|
+
return {
|
|
1398
|
+
unsupportedProviders: [...unsupportedProviders],
|
|
1399
|
+
config: {
|
|
1400
|
+
$schema: "https://opencode.ai/config.json",
|
|
1401
|
+
provider: providers
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1290
1406
|
// ../../packages/types/src/logging/index.ts
|
|
1291
1407
|
var CORRELATION_ID_HEADER = "x-evident-correlation-id";
|
|
1292
1408
|
var FORWARD_FAILURE_REASON_HEADER = "X-Evident-Failure-Reason";
|
|
1293
1409
|
function log(level, event, fields) {
|
|
1294
1410
|
const method = level === "debug" ? "log" : level;
|
|
1295
1411
|
try {
|
|
1296
|
-
|
|
1412
|
+
const payload = Object.fromEntries(
|
|
1413
|
+
Object.entries({ level, event, ...fields }).filter(([, value]) => value !== void 0)
|
|
1414
|
+
);
|
|
1415
|
+
const secondArgument = globalThis.navigator?.userAgent === "Cloudflare-Workers" ? payload : JSON.stringify(payload);
|
|
1416
|
+
console[method]("[evident]", secondArgument);
|
|
1297
1417
|
} catch (err) {
|
|
1298
1418
|
console.error(
|
|
1299
1419
|
"[evident] log_serialize_failed",
|
|
@@ -1322,7 +1442,7 @@ import ora4 from "ora";
|
|
|
1322
1442
|
import { select as select4 } from "@inquirer/prompts";
|
|
1323
1443
|
|
|
1324
1444
|
// src/lib/telemetry.ts
|
|
1325
|
-
var CLI_VERSION = (true ? "3.
|
|
1445
|
+
var CLI_VERSION = (true ? "3.6.1-dev.1a9c5e7" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1326
1446
|
function getCliVersion() {
|
|
1327
1447
|
return CLI_VERSION;
|
|
1328
1448
|
}
|
|
@@ -1403,7 +1523,7 @@ async function flushEvents() {
|
|
|
1403
1523
|
const controller = new AbortController();
|
|
1404
1524
|
const timeout = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
|
|
1405
1525
|
try {
|
|
1406
|
-
const
|
|
1526
|
+
const request3 = {
|
|
1407
1527
|
events,
|
|
1408
1528
|
client_type: "cli",
|
|
1409
1529
|
client_version: CLI_VERSION
|
|
@@ -1414,7 +1534,7 @@ async function flushEvents() {
|
|
|
1414
1534
|
"Content-Type": "application/json",
|
|
1415
1535
|
Authorization: authHeader
|
|
1416
1536
|
},
|
|
1417
|
-
body: JSON.stringify(
|
|
1537
|
+
body: JSON.stringify(request3),
|
|
1418
1538
|
signal: controller.signal
|
|
1419
1539
|
});
|
|
1420
1540
|
if (!response.ok) {
|
|
@@ -2071,7 +2191,8 @@ function createDependencyInstallDiagnosticReporter({
|
|
|
2071
2191
|
var FORWARDED_LEVELS_BY_SOURCE = {
|
|
2072
2192
|
"cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
|
|
2073
2193
|
opencode: /* @__PURE__ */ new Set(["warn", "error"]),
|
|
2074
|
-
"cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"])
|
|
2194
|
+
"cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"]),
|
|
2195
|
+
"cli.runtime-provenance": /* @__PURE__ */ new Set(["info", "warn", "error"])
|
|
2075
2196
|
};
|
|
2076
2197
|
var SEVERITY_BY_LEVEL = {
|
|
2077
2198
|
info: "info",
|
|
@@ -2867,6 +2988,58 @@ async function listAllSessions(port, client) {
|
|
|
2867
2988
|
}
|
|
2868
2989
|
return { sessions };
|
|
2869
2990
|
}
|
|
2991
|
+
var INTERRUPT_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
2992
|
+
"ABORT_ERR",
|
|
2993
|
+
"EAI_AGAIN",
|
|
2994
|
+
"ECONNREFUSED",
|
|
2995
|
+
"ECONNRESET",
|
|
2996
|
+
"ENETUNREACH",
|
|
2997
|
+
"ENOTFOUND",
|
|
2998
|
+
"ETIMEDOUT",
|
|
2999
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
3000
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
3001
|
+
"UND_ERR_SOCKET"
|
|
3002
|
+
]);
|
|
3003
|
+
function interruptFailureDiagnostic(error2) {
|
|
3004
|
+
const message = error2 instanceof Error ? error2.message : "";
|
|
3005
|
+
const httpStatus = /^OpenCode request failed: HTTP ([1-5]\d{2})(?::|$)/.exec(message)?.[1];
|
|
3006
|
+
if (httpStatus) return `HTTP ${httpStatus}`;
|
|
3007
|
+
const candidates = [error2, error2 instanceof Error ? error2.cause : void 0];
|
|
3008
|
+
for (const candidate of candidates) {
|
|
3009
|
+
if (typeof candidate !== "object" || candidate === null || !("code" in candidate)) continue;
|
|
3010
|
+
const code = candidate.code;
|
|
3011
|
+
if (typeof code === "string" && INTERRUPT_NETWORK_CODES.has(code)) {
|
|
3012
|
+
return redactDiagnosticText(`network ${code}`).slice(0, 120);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
const name = error2 instanceof Error && ["AbortError", "Error", "TimeoutError", "TypeError"].includes(error2.name) ? error2.name : "unknown error";
|
|
3016
|
+
return redactDiagnosticText(`request threw ${name}`).slice(0, 120);
|
|
3017
|
+
}
|
|
3018
|
+
async function interruptSession(port, id, client) {
|
|
3019
|
+
if (client?.version === "v2") {
|
|
3020
|
+
try {
|
|
3021
|
+
await interruptV2Session(client, id);
|
|
3022
|
+
return { kind: "confirmed_stopped" };
|
|
3023
|
+
} catch (error2) {
|
|
3024
|
+
return { kind: "not_confirmed", diagnostic: interruptFailureDiagnostic(error2) };
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
try {
|
|
3028
|
+
const res = await requestWithClient(
|
|
3029
|
+
port,
|
|
3030
|
+
client,
|
|
3031
|
+
`/session/${id}/abort`,
|
|
3032
|
+
{ method: "POST" },
|
|
3033
|
+
{ allowStatuses: ALL_HTTP_STATUSES }
|
|
3034
|
+
);
|
|
3035
|
+
if (res.status >= 200 && res.status < 300) {
|
|
3036
|
+
return { kind: "confirmed_stopped", status: res.status };
|
|
3037
|
+
}
|
|
3038
|
+
return { kind: "not_confirmed", status: res.status };
|
|
3039
|
+
} catch (error2) {
|
|
3040
|
+
return { kind: "threw", diagnostic: interruptFailureDiagnostic(error2) };
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
2870
3043
|
async function deleteSession(port, id, client) {
|
|
2871
3044
|
if (client?.version === "v2") return deleteV2Session(client, id);
|
|
2872
3045
|
try {
|
|
@@ -3036,6 +3209,52 @@ async function getModelAttachmentCapability(port, model, client) {
|
|
|
3036
3209
|
return null;
|
|
3037
3210
|
}
|
|
3038
3211
|
}
|
|
3212
|
+
async function isPinnedProviderLoaded(port, model, client) {
|
|
3213
|
+
if (client?.version === "v2") return null;
|
|
3214
|
+
const { model: baseModel } = splitModelVariant(model);
|
|
3215
|
+
if (!baseModel) return null;
|
|
3216
|
+
const slash = baseModel.indexOf("/");
|
|
3217
|
+
if (slash <= 0) return null;
|
|
3218
|
+
const providerId = baseModel.slice(0, slash);
|
|
3219
|
+
try {
|
|
3220
|
+
const res = await requestWithClient(port, client, "/config/providers");
|
|
3221
|
+
if (!res.ok) {
|
|
3222
|
+
console.error(
|
|
3223
|
+
`[isPinnedProviderLoaded] GET /config/providers returned HTTP ${res.status} (port ${port})`
|
|
3224
|
+
);
|
|
3225
|
+
return null;
|
|
3226
|
+
}
|
|
3227
|
+
const body = await res.json();
|
|
3228
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
3229
|
+
console.error(
|
|
3230
|
+
`[isPinnedProviderLoaded] GET /config/providers body was not a plain object (port ${port})`
|
|
3231
|
+
);
|
|
3232
|
+
return null;
|
|
3233
|
+
}
|
|
3234
|
+
const providers = body.providers;
|
|
3235
|
+
if (!Array.isArray(providers)) {
|
|
3236
|
+
console.error(
|
|
3237
|
+
`[isPinnedProviderLoaded] GET /config/providers body had no providers array (port ${port})`
|
|
3238
|
+
);
|
|
3239
|
+
return null;
|
|
3240
|
+
}
|
|
3241
|
+
const providerIds = providers.filter(
|
|
3242
|
+
(provider) => provider !== null && typeof provider === "object" && typeof provider.id === "string"
|
|
3243
|
+
).map((provider) => provider.id);
|
|
3244
|
+
if (providerIds.length === 0) {
|
|
3245
|
+
console.error(
|
|
3246
|
+
`[isPinnedProviderLoaded] GET /config/providers had no provider entries with string ids (port ${port})`
|
|
3247
|
+
);
|
|
3248
|
+
return null;
|
|
3249
|
+
}
|
|
3250
|
+
return providerIds.includes(providerId);
|
|
3251
|
+
} catch (err) {
|
|
3252
|
+
console.error(
|
|
3253
|
+
`[isPinnedProviderLoaded] GET /config/providers failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3254
|
+
);
|
|
3255
|
+
return null;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3039
3258
|
async function buildFileParts(attachments, capable) {
|
|
3040
3259
|
const outcomes = [];
|
|
3041
3260
|
const parts = [];
|
|
@@ -3518,19 +3737,32 @@ function isAbortedTerminalReply(messages, userMessageId) {
|
|
|
3518
3737
|
}
|
|
3519
3738
|
return false;
|
|
3520
3739
|
}
|
|
3740
|
+
var CLAUDE_AUTH_EXPIRY_MESSAGES = Object.freeze([
|
|
3741
|
+
"Claude Code credentials are unavailable or expired. Run `claude` to refresh them."
|
|
3742
|
+
]);
|
|
3743
|
+
function extractReplyErrorShape(reply) {
|
|
3744
|
+
const error2 = errorOf(reply);
|
|
3745
|
+
if (error2 == null || typeof error2 !== "object") {
|
|
3746
|
+
return { name: null, statusCode: null };
|
|
3747
|
+
}
|
|
3748
|
+
const e = error2;
|
|
3749
|
+
const name = typeof e.name === "string" ? e.name.slice(0, 100) : null;
|
|
3750
|
+
const data = e.data;
|
|
3751
|
+
const statusCode = typeof data?.statusCode === "number" && Number.isFinite(data.statusCode) ? data.statusCode : null;
|
|
3752
|
+
return { name, statusCode };
|
|
3753
|
+
}
|
|
3521
3754
|
function classifyReplyAuthError(reply) {
|
|
3522
3755
|
const error2 = errorOf(reply);
|
|
3523
3756
|
if (error2 == null || typeof error2 !== "object") return null;
|
|
3524
3757
|
const e = error2;
|
|
3525
3758
|
const replyProviderId = reply?.info?.providerID ?? null;
|
|
3526
3759
|
const replyModelId = reply?.info?.modelID ?? null;
|
|
3760
|
+
const data = e.data;
|
|
3527
3761
|
if (e.name === "ProviderAuthError") {
|
|
3528
|
-
const data = e.data;
|
|
3529
3762
|
const providerId = typeof data?.providerID === "string" && data.providerID || replyProviderId;
|
|
3530
3763
|
return { kind: "model_auth", providerId, modelId: replyModelId, reason: "missing" };
|
|
3531
3764
|
}
|
|
3532
3765
|
if (e.name === "APIError") {
|
|
3533
|
-
const data = e.data;
|
|
3534
3766
|
const statusCode = data?.statusCode;
|
|
3535
3767
|
if (statusCode === 401 || statusCode === 403) {
|
|
3536
3768
|
return {
|
|
@@ -3541,11 +3773,23 @@ function classifyReplyAuthError(reply) {
|
|
|
3541
3773
|
};
|
|
3542
3774
|
}
|
|
3543
3775
|
}
|
|
3776
|
+
const errorMessage3 = typeof data?.message === "string" ? data.message : typeof e.message === "string" ? e.message : null;
|
|
3777
|
+
if (errorMessage3 !== null && CLAUDE_AUTH_EXPIRY_MESSAGES.some((knownMessage) => errorMessage3.includes(knownMessage))) {
|
|
3778
|
+
return {
|
|
3779
|
+
kind: "model_auth",
|
|
3780
|
+
providerId: typeof data?.providerID === "string" && data.providerID || replyProviderId,
|
|
3781
|
+
modelId: replyModelId,
|
|
3782
|
+
reason: "rejected"
|
|
3783
|
+
};
|
|
3784
|
+
}
|
|
3544
3785
|
return null;
|
|
3545
3786
|
}
|
|
3546
3787
|
function messageFailure(messages, userMessageId) {
|
|
3547
3788
|
return classifyReplyAuthError(findLastAssistantReplyFor(messages, userMessageId));
|
|
3548
3789
|
}
|
|
3790
|
+
function isProviderModelNotFoundError(reason) {
|
|
3791
|
+
return reason.includes("Model not found:");
|
|
3792
|
+
}
|
|
3549
3793
|
function findLatestSubagentAuthOutcome(messages, sinceMs) {
|
|
3550
3794
|
if (!messages || messages.length === 0) return null;
|
|
3551
3795
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -3754,23 +3998,49 @@ async function readSessionErrorStream(port, options, client) {
|
|
|
3754
3998
|
if (reader) void reader.cancel().catch(() => void 0);
|
|
3755
3999
|
}
|
|
3756
4000
|
}
|
|
3757
|
-
async function
|
|
3758
|
-
if (client?.version === "v2") return;
|
|
4001
|
+
async function disposeAndReloadProviderConfig(port, client) {
|
|
4002
|
+
if (client?.version === "v2") return { outcome: "skipped_v2" };
|
|
3759
4003
|
try {
|
|
3760
|
-
const res = await requestWithClient(
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
4004
|
+
const res = await requestWithClient(
|
|
4005
|
+
port,
|
|
4006
|
+
client,
|
|
4007
|
+
"/config",
|
|
4008
|
+
{
|
|
4009
|
+
method: "PATCH",
|
|
4010
|
+
headers: { "Content-Type": "application/json" },
|
|
4011
|
+
body: JSON.stringify({})
|
|
4012
|
+
},
|
|
4013
|
+
{ allowStatuses: ALL_HTTP_STATUSES }
|
|
4014
|
+
);
|
|
3765
4015
|
if (!res.ok) {
|
|
3766
4016
|
console.error(
|
|
3767
|
-
`[
|
|
4017
|
+
`[disposeAndReloadProviderConfig] PATCH /config returned HTTP ${res.status} (port ${port})`
|
|
3768
4018
|
);
|
|
4019
|
+
return { outcome: "failed", status: res.status, detail: null };
|
|
3769
4020
|
}
|
|
4021
|
+
return { outcome: "reloaded" };
|
|
3770
4022
|
} catch (err) {
|
|
3771
4023
|
console.error(
|
|
3772
|
-
`[
|
|
4024
|
+
`[disposeAndReloadProviderConfig] PATCH /config failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3773
4025
|
);
|
|
4026
|
+
return {
|
|
4027
|
+
outcome: "failed",
|
|
4028
|
+
status: null,
|
|
4029
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
4030
|
+
};
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
async function probeOpenCodeRequestReadiness(port, client) {
|
|
4034
|
+
try {
|
|
4035
|
+
const res = await requestWithClient(port, client, "/config", void 0, {
|
|
4036
|
+
allowStatuses: ALL_HTTP_STATUSES
|
|
4037
|
+
});
|
|
4038
|
+
const body = await res.text();
|
|
4039
|
+
return { ok: res.ok, status: res.status, bodyEmpty: body.trim().length === 0, detail: null };
|
|
4040
|
+
} catch (err) {
|
|
4041
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
4042
|
+
console.error(`[probeOpenCodeRequestReadiness] GET /config failed (port ${port}): ${detail}`);
|
|
4043
|
+
return { ok: false, status: null, bodyEmpty: null, detail };
|
|
3774
4044
|
}
|
|
3775
4045
|
}
|
|
3776
4046
|
|
|
@@ -4166,6 +4436,11 @@ async function listV2Permissions(client, sessionId) {
|
|
|
4166
4436
|
return null;
|
|
4167
4437
|
}
|
|
4168
4438
|
}
|
|
4439
|
+
async function interruptV2Session(client, sessionId) {
|
|
4440
|
+
await client.request(`/api/session/${encodeURIComponent(sessionId)}/interrupt`, {
|
|
4441
|
+
method: "POST"
|
|
4442
|
+
});
|
|
4443
|
+
}
|
|
4169
4444
|
async function getV2ActiveSessions(client) {
|
|
4170
4445
|
try {
|
|
4171
4446
|
const body = await readJson(await client.request("/api/session/active"));
|
|
@@ -4568,11 +4843,11 @@ function failureMessage(status2) {
|
|
|
4568
4843
|
function isRetryableStatus(status2) {
|
|
4569
4844
|
return CONNECT_KEY_RETRY_STATUSES.includes(status2);
|
|
4570
4845
|
}
|
|
4571
|
-
async function importOneCredential(
|
|
4846
|
+
async function importOneCredential(request3, credential, sleepForRetry, log3) {
|
|
4572
4847
|
const path = `/api/integration/${encodeURIComponent(credential.integrationId)}/connect/key`;
|
|
4573
4848
|
for (let attempt = 1; attempt <= CONNECT_KEY_MAX_ATTEMPTS; attempt += 1) {
|
|
4574
4849
|
try {
|
|
4575
|
-
const response = await
|
|
4850
|
+
const response = await request3(
|
|
4576
4851
|
path,
|
|
4577
4852
|
{
|
|
4578
4853
|
method: "POST",
|
|
@@ -4607,7 +4882,7 @@ async function importOneCredential(request2, credential, sleepForRetry, log3) {
|
|
|
4607
4882
|
throw new Error("OpenCode credential import retry loop did not return");
|
|
4608
4883
|
}
|
|
4609
4884
|
async function importOpenCodeCredentials({
|
|
4610
|
-
request:
|
|
4885
|
+
request: request3,
|
|
4611
4886
|
classified,
|
|
4612
4887
|
connectedIntegrationIds,
|
|
4613
4888
|
sleepForRetry = sleep2,
|
|
@@ -4619,7 +4894,7 @@ async function importOpenCodeCredentials({
|
|
|
4619
4894
|
result.alreadyConnected.push(credential.integrationId);
|
|
4620
4895
|
continue;
|
|
4621
4896
|
}
|
|
4622
|
-
const outcome = await importOneCredential(
|
|
4897
|
+
const outcome = await importOneCredential(request3, credential, sleepForRetry, log3);
|
|
4623
4898
|
if (outcome.ok) result.imported.push(credential.integrationId);
|
|
4624
4899
|
else result.failed.push(outcome.failure);
|
|
4625
4900
|
}
|
|
@@ -4637,7 +4912,7 @@ function parseIntegrationList(value) {
|
|
|
4637
4912
|
return { integrationIds, hasData: value.data.length > 0 };
|
|
4638
4913
|
}
|
|
4639
4914
|
async function getConnectedOpenCodeIntegrationIds({
|
|
4640
|
-
request:
|
|
4915
|
+
request: request3,
|
|
4641
4916
|
directory,
|
|
4642
4917
|
sleepForRetry = sleep2,
|
|
4643
4918
|
log: log3
|
|
@@ -4645,7 +4920,7 @@ async function getConnectedOpenCodeIntegrationIds({
|
|
|
4645
4920
|
const path = `/api/integration?location%5Bdirectory%5D=${encodeURIComponent(directory)}`;
|
|
4646
4921
|
for (let attempt = 1; attempt <= INTEGRATION_LIST_MAX_READS; attempt += 1) {
|
|
4647
4922
|
try {
|
|
4648
|
-
const response = await
|
|
4923
|
+
const response = await request3(path);
|
|
4649
4924
|
const parsed = parseIntegrationList(await response.json());
|
|
4650
4925
|
if (parsed === null) {
|
|
4651
4926
|
log3(
|
|
@@ -4674,7 +4949,7 @@ async function getConnectedOpenCodeIntegrationIds({
|
|
|
4674
4949
|
return null;
|
|
4675
4950
|
}
|
|
4676
4951
|
async function importOpenCodeV2Credentials({
|
|
4677
|
-
request:
|
|
4952
|
+
request: request3,
|
|
4678
4953
|
homeDir,
|
|
4679
4954
|
directory,
|
|
4680
4955
|
sleepForRetry = sleep2,
|
|
@@ -4692,7 +4967,7 @@ async function importOpenCodeV2Credentials({
|
|
|
4692
4967
|
const classified = classifyOpenCodeCredentials(credentials2);
|
|
4693
4968
|
if (classified.importable.length === 0) return emptyResult(classified.unsupported);
|
|
4694
4969
|
const connectedIntegrationIds = await getConnectedOpenCodeIntegrationIds({
|
|
4695
|
-
request:
|
|
4970
|
+
request: request3,
|
|
4696
4971
|
directory,
|
|
4697
4972
|
sleepForRetry,
|
|
4698
4973
|
log: log3
|
|
@@ -4701,7 +4976,7 @@ async function importOpenCodeV2Credentials({
|
|
|
4701
4976
|
return emptyResult(classified.unsupported, "connection-list-unavailable");
|
|
4702
4977
|
}
|
|
4703
4978
|
return importOpenCodeCredentials({
|
|
4704
|
-
request:
|
|
4979
|
+
request: request3,
|
|
4705
4980
|
classified,
|
|
4706
4981
|
connectedIntegrationIds,
|
|
4707
4982
|
sleepForRetry,
|
|
@@ -5392,7 +5667,11 @@ function isSessionDbProvenanceRecord(value) {
|
|
|
5392
5667
|
var OPENCODE2_PACKAGE_SPEC = "@opencode-ai/cli@0.0.0-beta-17823";
|
|
5393
5668
|
|
|
5394
5669
|
// src/lib/opencode/opencode-version-gate.ts
|
|
5395
|
-
var QUEUE_VALIDATED_OPENCODE_VERSIONS = [
|
|
5670
|
+
var QUEUE_VALIDATED_OPENCODE_VERSIONS = [
|
|
5671
|
+
"1.17.11",
|
|
5672
|
+
"1.18.3",
|
|
5673
|
+
"1.18.35"
|
|
5674
|
+
];
|
|
5396
5675
|
function isQueueValidatedVersion(version2) {
|
|
5397
5676
|
if (!version2) return false;
|
|
5398
5677
|
return QUEUE_VALIDATED_OPENCODE_VERSIONS.includes(version2);
|
|
@@ -6437,6 +6716,7 @@ function connectTunnel(options) {
|
|
|
6437
6716
|
}, 3e4);
|
|
6438
6717
|
let relaySilenceTimeout;
|
|
6439
6718
|
let relaySilenceTimedOut = false;
|
|
6719
|
+
let relayConnectionId;
|
|
6440
6720
|
const clearRelaySilenceTimeout = () => {
|
|
6441
6721
|
if (relaySilenceTimeout === void 0) return;
|
|
6442
6722
|
clearTimeout(relaySilenceTimeout);
|
|
@@ -6522,7 +6802,8 @@ function connectTunnel(options) {
|
|
|
6522
6802
|
armRelaySilenceTimeout();
|
|
6523
6803
|
forwarder.setBinaryFramesSupported(message.binary_frames === true);
|
|
6524
6804
|
const connectedAgentId = message.agent_id ?? agentId;
|
|
6525
|
-
|
|
6805
|
+
relayConnectionId = message.connection_id;
|
|
6806
|
+
onConnected?.(connectedAgentId, relayConnectionId);
|
|
6526
6807
|
resolve5({
|
|
6527
6808
|
ws,
|
|
6528
6809
|
close: () => ws.close(1e3, "CLI shutdown")
|
|
@@ -6559,7 +6840,7 @@ function connectTunnel(options) {
|
|
|
6559
6840
|
clearRelaySilenceTimeout();
|
|
6560
6841
|
const reasonStr = (relaySilenceTimedOut ? "no relay heartbeat for 90s \u2014 reconnecting" : void 0) || reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
|
|
6561
6842
|
forwarder.abortAll();
|
|
6562
|
-
onDisconnected?.(code, reasonStr);
|
|
6843
|
+
onDisconnected?.(code, reasonStr, relayConnectionId);
|
|
6563
6844
|
});
|
|
6564
6845
|
});
|
|
6565
6846
|
}
|
|
@@ -6622,7 +6903,7 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6622
6903
|
authHeader: this.opts.getAuthHeader(),
|
|
6623
6904
|
port: this.opts.port,
|
|
6624
6905
|
openCodePassword: this.opts.openCodePassword,
|
|
6625
|
-
onConnected: (agentId) => {
|
|
6906
|
+
onConnected: (agentId, relayConnectionId) => {
|
|
6626
6907
|
const attempt = this.reconnectAttempt + 1;
|
|
6627
6908
|
this.reconnectAttempt = 0;
|
|
6628
6909
|
this.reconnecting = false;
|
|
@@ -6632,13 +6913,17 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6632
6913
|
this.disconnectedAtMs = null;
|
|
6633
6914
|
events.onConnected(agentId, isReconnect, {
|
|
6634
6915
|
generation: this.generation,
|
|
6916
|
+
relayConnectionId: relayConnectionId ?? null,
|
|
6635
6917
|
attempt,
|
|
6636
6918
|
downtimeMs
|
|
6637
6919
|
});
|
|
6638
6920
|
},
|
|
6639
|
-
onDisconnected: (code, reason) => {
|
|
6921
|
+
onDisconnected: (code, reason, relayConnectionId) => {
|
|
6640
6922
|
this.disconnectedAtMs = this.now();
|
|
6641
|
-
events.onDisconnected(code, reason, {
|
|
6923
|
+
events.onDisconnected(code, reason, {
|
|
6924
|
+
generation: this.generation,
|
|
6925
|
+
relayConnectionId: relayConnectionId ?? null
|
|
6926
|
+
});
|
|
6642
6927
|
const relayRequestedReconnect = code === 1e3 && _RunnerConnection.RELAY_RECONNECT_CLOSE_REASONS.has(reason);
|
|
6643
6928
|
if (this.opts.isRunning() && (code !== 1e3 || relayRequestedReconnect) && !this.reconnecting) {
|
|
6644
6929
|
this.reconnectPromise = this.connectWithRetry(true).catch((err) => {
|
|
@@ -7086,8 +7371,8 @@ import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/p
|
|
|
7086
7371
|
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
7087
7372
|
var FILE_MODE = 384;
|
|
7088
7373
|
var DIRECTORY_MODE = 448;
|
|
7089
|
-
async function writePushedFile(
|
|
7090
|
-
const { requestedPath, content, allowedDirectories, homeDir } =
|
|
7374
|
+
async function writePushedFile(request3) {
|
|
7375
|
+
const { requestedPath, content, allowedDirectories, homeDir } = request3;
|
|
7091
7376
|
const bytes = content.byteLength;
|
|
7092
7377
|
if (allowedDirectories.length === 0) {
|
|
7093
7378
|
return refuse("file_sync_disabled", "File sync is not enabled on this runner.", {
|
|
@@ -7537,7 +7822,7 @@ var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
|
7537
7822
|
var MAX_BUFFERED_SESSION_ERRORS = 256;
|
|
7538
7823
|
var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
|
|
7539
7824
|
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 = "
|
|
7825
|
+
var SESSION_ERROR_RETAINED_DETAIL = "Runner-observed OpenCode session error: ";
|
|
7541
7826
|
var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
|
|
7542
7827
|
var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
7543
7828
|
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
@@ -7661,12 +7946,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7661
7946
|
/**
|
|
7662
7947
|
* Per-conversation throttle state for the #183 recurrence warning (#1618
|
|
7663
7948
|
* 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
|
-
*
|
|
7949
|
+
* `firstWedgedAt` anchors `stuck_for_ms`; `warnAnchorAt` is the throttle
|
|
7950
|
+
* anchor from first detection through the first warning, then from each
|
|
7951
|
+
* warning onward; `consecutiveTicks` is reported in the log text so the
|
|
7952
|
+
* operator sees magnitude, not repetition. Cleared the moment the
|
|
7953
|
+
* conversation dispatches anything, or when it drops off the pending list
|
|
7954
|
+
* (`runDrain`'s prune, the dominant real-world path — a wedge normally ends
|
|
7955
|
+
* with its message completing, not a fresh dispatch on the same
|
|
7956
|
+
* conversation) — either way a fresh wedge, if it recurs, is a new
|
|
7957
|
+
* incident. Bounded FIFO, mirroring `supersededSessions`
|
|
7670
7958
|
* (`MAX_WEDGED_CONVERSATIONS`).
|
|
7671
7959
|
*/
|
|
7672
7960
|
wedgeWarnings = /* @__PURE__ */ new Map();
|
|
@@ -7896,6 +8184,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7896
8184
|
subagentInvocationPrefetches = /* @__PURE__ */ new Map();
|
|
7897
8185
|
/** Serialises drains so a reconnect during a drain doesn't double-process. */
|
|
7898
8186
|
draining = false;
|
|
8187
|
+
pendingProviderRegistryRepair = null;
|
|
7899
8188
|
/**
|
|
7900
8189
|
* Serialises runner-file syncs (#559) so the ~2s poll tick and a concurrent
|
|
7901
8190
|
* drain ping don't download, write and ack the same file twice.
|
|
@@ -8081,8 +8370,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8081
8370
|
async runDrain() {
|
|
8082
8371
|
let dispatched = 0;
|
|
8083
8372
|
try {
|
|
8373
|
+
const pendingRepair = this.pendingProviderRegistryRepair;
|
|
8374
|
+
if (pendingRepair && this.protectedSessionIds().size === 0) {
|
|
8375
|
+
try {
|
|
8376
|
+
await this.issueProviderRegistryReload(
|
|
8377
|
+
pendingRepair.conversationId,
|
|
8378
|
+
pendingRepair.messageId
|
|
8379
|
+
);
|
|
8380
|
+
} catch (err) {
|
|
8381
|
+
this.log({
|
|
8382
|
+
level: "error",
|
|
8383
|
+
message: `Provider registry repair adoption failed for message ${pendingRepair.messageId.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`,
|
|
8384
|
+
conversation_id: pendingRepair.conversationId,
|
|
8385
|
+
message_id: pendingRepair.messageId
|
|
8386
|
+
});
|
|
8387
|
+
}
|
|
8388
|
+
return 0;
|
|
8389
|
+
}
|
|
8084
8390
|
this.pendingMessageIds.clear();
|
|
8085
8391
|
const conversations = await this.getPendingConversations();
|
|
8392
|
+
if (this.wedgeWarnings.size > 0) {
|
|
8393
|
+
const pendingConversationIds = new Set(conversations.map(({ id }) => id));
|
|
8394
|
+
for (const conversationId of this.wedgeWarnings.keys()) {
|
|
8395
|
+
if (!pendingConversationIds.has(conversationId)) {
|
|
8396
|
+
this.wedgeWarnings.delete(conversationId);
|
|
8397
|
+
}
|
|
8398
|
+
}
|
|
8399
|
+
}
|
|
8086
8400
|
if (this.recycleRequestedFlag) {
|
|
8087
8401
|
this.stop();
|
|
8088
8402
|
}
|
|
@@ -8454,6 +8768,47 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8454
8768
|
agent: message.opencode_agent ?? void 0,
|
|
8455
8769
|
model: message.opencode_model ?? void 0
|
|
8456
8770
|
};
|
|
8771
|
+
const pinnedProviderLoaded = await isPinnedProviderLoaded(
|
|
8772
|
+
this.port,
|
|
8773
|
+
options.model,
|
|
8774
|
+
this.openCodeClient
|
|
8775
|
+
);
|
|
8776
|
+
if (pinnedProviderLoaded === false) {
|
|
8777
|
+
const dispatchedModel = message.opencode_model;
|
|
8778
|
+
const separator = dispatchedModel?.indexOf("/") ?? -1;
|
|
8779
|
+
const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
|
|
8780
|
+
const providerId = hasProviderAndModel ? dispatchedModel.slice(0, separator) : null;
|
|
8781
|
+
const modelId = hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null;
|
|
8782
|
+
const errorMessage3 = `Provider namespace not loaded on this runner: ${message.opencode_model}. Refused before dispatch; no turn was started.`;
|
|
8783
|
+
this.log({
|
|
8784
|
+
level: "error",
|
|
8785
|
+
message: `Refusing message ${message.id.slice(0, 8)} in conversation ${conv.id.slice(0, 8)}: provider namespace ${providerId ?? "(unknown)"} is not loaded; no turn was started`,
|
|
8786
|
+
conversation_id: conv.id,
|
|
8787
|
+
message_id: message.id
|
|
8788
|
+
});
|
|
8789
|
+
try {
|
|
8790
|
+
await this.markFailed(conv.id, message.id, sessionId, errorMessage3, void 0, {
|
|
8791
|
+
classification: {
|
|
8792
|
+
kind: "model_auth",
|
|
8793
|
+
providerId,
|
|
8794
|
+
modelId,
|
|
8795
|
+
reason: "missing"
|
|
8796
|
+
},
|
|
8797
|
+
errorShape: { name: null, statusCode: null }
|
|
8798
|
+
});
|
|
8799
|
+
this.signalDispatchNotStarted(conv, message, "model_provider_absent");
|
|
8800
|
+
} catch (markErr) {
|
|
8801
|
+
this.log({
|
|
8802
|
+
level: "warn",
|
|
8803
|
+
message: `markFailed PATCH for refused message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed: ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
8804
|
+
conversation_id: conv.id,
|
|
8805
|
+
message_id: message.id
|
|
8806
|
+
});
|
|
8807
|
+
this.signalDispatchNotStarted(conv, message, "failure_unreported");
|
|
8808
|
+
}
|
|
8809
|
+
await this.repairProviderRegistry(conv.id, message.id);
|
|
8810
|
+
break;
|
|
8811
|
+
}
|
|
8457
8812
|
let opencodeMessageId;
|
|
8458
8813
|
let preDispatchUserIds = null;
|
|
8459
8814
|
try {
|
|
@@ -8619,14 +8974,13 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8619
8974
|
* skip-because-already-`dispatched`, the exact signature of a message stuck
|
|
8620
8975
|
* acknowledged-but-never-worked.
|
|
8621
8976
|
*
|
|
8622
|
-
*
|
|
8623
|
-
*
|
|
8624
|
-
*
|
|
8625
|
-
*
|
|
8626
|
-
*
|
|
8627
|
-
*
|
|
8628
|
-
*
|
|
8629
|
-
* than repetition.
|
|
8977
|
+
* The first detection starts the clock and emits a local `debug` entry. The
|
|
8978
|
+
* `warn` log and paired `dispatch_wedged` signal fire once the wedge has
|
|
8979
|
+
* lasted `wedgeWarningIntervalMs`, then at most once per interval, naming the
|
|
8980
|
+
* consecutive-tick count so the operator sees magnitude rather than repetition.
|
|
8981
|
+
* A watcher stall that the loop-liveness watchdog recovers is expected and is
|
|
8982
|
+
* already reported by its `watcher_recovered` signal, so it does not reach an
|
|
8983
|
+
* operator through this warning.
|
|
8630
8984
|
*
|
|
8631
8985
|
* Deliberately does NOT trigger a release: WI-1's `reconcileWatchers` runs
|
|
8632
8986
|
* unconditionally on this same tick and is already recovering anything it
|
|
@@ -8638,36 +8992,41 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8638
8992
|
const existing = this.wedgeWarnings.get(conv.id);
|
|
8639
8993
|
const firstWedgedAt = existing?.firstWedgedAt ?? now;
|
|
8640
8994
|
const consecutiveTicks = (existing?.consecutiveTicks ?? 0) + 1;
|
|
8641
|
-
const
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
this.
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8995
|
+
const warnAnchorAt = existing?.warnAnchorAt ?? now;
|
|
8996
|
+
const dueForWarn = now - warnAnchorAt >= this.wedgeWarningIntervalMs;
|
|
8997
|
+
if (!existing) {
|
|
8998
|
+
this.log({
|
|
8999
|
+
level: "debug",
|
|
9000
|
+
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.`,
|
|
9001
|
+
conversation_id: conv.id
|
|
9002
|
+
});
|
|
9003
|
+
}
|
|
9004
|
+
let untracked = 0;
|
|
9005
|
+
if (dueForWarn) {
|
|
9006
|
+
const stuckForMs = now - firstWedgedAt;
|
|
9007
|
+
untracked = this.countUntrackedIds(messages);
|
|
9008
|
+
this.log({
|
|
9009
|
+
level: "warn",
|
|
9010
|
+
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.`),
|
|
9011
|
+
conversation_id: conv.id
|
|
8648
9012
|
});
|
|
8649
|
-
return;
|
|
8650
9013
|
}
|
|
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
9014
|
this.wedgeWarnings.delete(conv.id);
|
|
8659
|
-
this.wedgeWarnings.set(conv.id, {
|
|
9015
|
+
this.wedgeWarnings.set(conv.id, {
|
|
9016
|
+
firstWedgedAt,
|
|
9017
|
+
warnAnchorAt: dueForWarn ? now : warnAnchorAt,
|
|
9018
|
+
consecutiveTicks
|
|
9019
|
+
});
|
|
8660
9020
|
while (this.wedgeWarnings.size > MAX_WEDGED_CONVERSATIONS) {
|
|
8661
9021
|
const oldest = this.wedgeWarnings.keys().next().value;
|
|
8662
9022
|
if (oldest === void 0) break;
|
|
8663
9023
|
this.wedgeWarnings.delete(oldest);
|
|
8664
9024
|
}
|
|
8665
|
-
if (
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
}
|
|
9025
|
+
if (!dueForWarn) return;
|
|
9026
|
+
void this.postSignal(conv.id, messages[0].id, "dispatch_wedged", {
|
|
9027
|
+
stuck_for_ms: now - firstWedgedAt,
|
|
9028
|
+
untracked
|
|
9029
|
+
});
|
|
8671
9030
|
}
|
|
8672
9031
|
/**
|
|
8673
9032
|
* How many of `messages`' ids are tracked by NO watcher's `inFlight` (#1618
|
|
@@ -8803,6 +9162,34 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8803
9162
|
if (state === "failed" && isAbortedTerminalReply(messages, ocId ?? "")) {
|
|
8804
9163
|
const ongoing = await this.isSessionOngoing(sessionId);
|
|
8805
9164
|
if (ongoing === false) {
|
|
9165
|
+
const age = this.absoluteAgeForRow(message.processing_started_at);
|
|
9166
|
+
if (age.tripped && age.anchorMs !== null) {
|
|
9167
|
+
try {
|
|
9168
|
+
await this.markFailed(
|
|
9169
|
+
conv.id,
|
|
9170
|
+
message.id,
|
|
9171
|
+
sessionId,
|
|
9172
|
+
this.absoluteCeilingError(message.id, sessionId, age.anchorMs),
|
|
9173
|
+
void 0,
|
|
9174
|
+
{
|
|
9175
|
+
classification: null,
|
|
9176
|
+
errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
|
|
9177
|
+
}
|
|
9178
|
+
);
|
|
9179
|
+
} catch (err) {
|
|
9180
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
9181
|
+
this.log({
|
|
9182
|
+
level: "warn",
|
|
9183
|
+
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)}`,
|
|
9184
|
+
conversation_id: conv.id,
|
|
9185
|
+
message_id: message.id
|
|
9186
|
+
});
|
|
9187
|
+
return "unresolved";
|
|
9188
|
+
}
|
|
9189
|
+
this.clearRedriveUnresolved(message.id);
|
|
9190
|
+
void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
|
|
9191
|
+
return "settled";
|
|
9192
|
+
}
|
|
8806
9193
|
this.log({
|
|
8807
9194
|
level: "info",
|
|
8808
9195
|
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 +9260,29 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8873
9260
|
message_id: message.id
|
|
8874
9261
|
});
|
|
8875
9262
|
}
|
|
9263
|
+
if (this.absoluteAgeForRow(message.processing_started_at).tripped) {
|
|
9264
|
+
try {
|
|
9265
|
+
await this.markFailed(
|
|
9266
|
+
conv.id,
|
|
9267
|
+
message.id,
|
|
9268
|
+
sessionId,
|
|
9269
|
+
this.absoluteCeilingError(message.id, sessionId, anchorMs)
|
|
9270
|
+
);
|
|
9271
|
+
} catch (err) {
|
|
9272
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
9273
|
+
this.log({
|
|
9274
|
+
level: "warn",
|
|
9275
|
+
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)}`,
|
|
9276
|
+
conversation_id: conv.id,
|
|
9277
|
+
message_id: message.id
|
|
9278
|
+
});
|
|
9279
|
+
const bound = await this.boundRedriveOutcome(conv, message, "reattach");
|
|
9280
|
+
return bound === "abandoned" ? "abandoned" : "unresolved";
|
|
9281
|
+
}
|
|
9282
|
+
this.clearRedriveUnresolved(message.id);
|
|
9283
|
+
void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
|
|
9284
|
+
return "abandoned";
|
|
9285
|
+
}
|
|
8876
9286
|
const title = await this.resolveSessionTitle(sessionId, conv.id);
|
|
8877
9287
|
try {
|
|
8878
9288
|
await this.markProcessing(conv.id, message.id, sessionId, ocId, title);
|
|
@@ -9139,8 +9549,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9139
9549
|
const since = this.redriveOutcomeFailingSince.get(message.id);
|
|
9140
9550
|
if (since === void 0) this.redriveOutcomeFailingSince.set(message.id, now);
|
|
9141
9551
|
const durationTripped = now - (since ?? now) >= this.pausedMaxWaitMs;
|
|
9142
|
-
const
|
|
9143
|
-
const absoluteAgeTripped = !Number.isNaN(parsed) && now - parsed >= ABSOLUTE_MAX_PROCESSING_MS;
|
|
9552
|
+
const absoluteAgeTripped = this.absoluteAgeForRow(message.processing_started_at).tripped;
|
|
9144
9553
|
if (!durationTripped && !absoluteAgeTripped) {
|
|
9145
9554
|
this.signalRedriveOutcomeUnreported(conv, message, outcome);
|
|
9146
9555
|
return "retry";
|
|
@@ -9179,6 +9588,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9179
9588
|
});
|
|
9180
9589
|
return "abandoned";
|
|
9181
9590
|
}
|
|
9591
|
+
/** Row-level guards use the durable server stamp; in-flight guards use processingAnchorMs. */
|
|
9592
|
+
absoluteAgeForRow(stamp) {
|
|
9593
|
+
const anchorMs = stamp ? Date.parse(stamp) : NaN;
|
|
9594
|
+
return Number.isNaN(anchorMs) ? { tripped: false, anchorMs: null } : { tripped: this.now() - anchorMs >= ABSOLUTE_MAX_PROCESSING_MS, anchorMs };
|
|
9595
|
+
}
|
|
9596
|
+
absoluteCeilingError(messageId, sessionId, anchorMs, retainedReason = this.readRetainedSessionError(sessionId)) {
|
|
9597
|
+
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.`;
|
|
9598
|
+
return retainedReason ? `${baseError} ${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}` : baseError;
|
|
9599
|
+
}
|
|
9182
9600
|
/**
|
|
9183
9601
|
* Record one poll outcome toward the re-drive fence's consecutive-identical-
|
|
9184
9602
|
* failure streak (#1348) and return the resulting count. `signature === null`
|
|
@@ -9620,10 +10038,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9620
10038
|
opencodeMessageId,
|
|
9621
10039
|
message,
|
|
9622
10040
|
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,
|
|
10041
|
+
processingAnchorMs: this.absoluteAgeForRow(message.processing_started_at).anchorMs ?? processedAtMs,
|
|
9627
10042
|
deadline: watchDeadlineAnchorMs + this.pausedMaxWaitMs,
|
|
9628
10043
|
// The server row is ALREADY `processing`; do not re-fire markProcessing.
|
|
9629
10044
|
started: true,
|
|
@@ -9933,14 +10348,40 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9933
10348
|
conversation_id: watcher.conv.id,
|
|
9934
10349
|
message_id: inFlight.evidentMessageId
|
|
9935
10350
|
});
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
inFlight.
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
10351
|
+
const isMissingProvider = isProviderModelNotFoundError(event.reason);
|
|
10352
|
+
if (isMissingProvider) {
|
|
10353
|
+
const dispatchedModel = inFlight.message.opencode_model;
|
|
10354
|
+
const separator = dispatchedModel?.indexOf("/") ?? -1;
|
|
10355
|
+
const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
|
|
10356
|
+
await this.markFailed(
|
|
10357
|
+
watcher.conv.id,
|
|
10358
|
+
inFlight.evidentMessageId,
|
|
10359
|
+
event.sessionId,
|
|
10360
|
+
`OpenCode could not run this turn: ${event.reason}`,
|
|
10361
|
+
void 0,
|
|
10362
|
+
{
|
|
10363
|
+
classification: {
|
|
10364
|
+
kind: "model_auth",
|
|
10365
|
+
providerId: hasProviderAndModel ? dispatchedModel.slice(0, separator) : null,
|
|
10366
|
+
modelId: hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null,
|
|
10367
|
+
reason: "missing"
|
|
10368
|
+
},
|
|
10369
|
+
errorShape: { name: null, statusCode: null }
|
|
10370
|
+
}
|
|
10371
|
+
);
|
|
10372
|
+
} else {
|
|
10373
|
+
await this.markFailed(
|
|
10374
|
+
watcher.conv.id,
|
|
10375
|
+
inFlight.evidentMessageId,
|
|
10376
|
+
event.sessionId,
|
|
10377
|
+
`OpenCode could not run this turn: ${event.reason}`
|
|
10378
|
+
);
|
|
10379
|
+
}
|
|
9942
10380
|
inFlight.done = true;
|
|
9943
10381
|
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
10382
|
+
if (isMissingProvider) {
|
|
10383
|
+
await this.repairProviderRegistry(watcher.conv.id, inFlight.evidentMessageId);
|
|
10384
|
+
}
|
|
9944
10385
|
} catch (err) {
|
|
9945
10386
|
if (err instanceof ChannelAuthError) {
|
|
9946
10387
|
this.log({
|
|
@@ -9961,6 +10402,44 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9961
10402
|
this.sessionErrorHandled.delete(inFlight.evidentMessageId);
|
|
9962
10403
|
}
|
|
9963
10404
|
}
|
|
10405
|
+
async repairProviderRegistry(conversationId, messageId) {
|
|
10406
|
+
if (this.isV2) {
|
|
10407
|
+
void this.postSignal(conversationId, messageId, "provider_registry_reload_unsupported");
|
|
10408
|
+
return;
|
|
10409
|
+
}
|
|
10410
|
+
const activeSessions = this.protectedSessionIds().size;
|
|
10411
|
+
if (activeSessions > 0 || this.draining) {
|
|
10412
|
+
this.pendingProviderRegistryRepair = { conversationId, messageId };
|
|
10413
|
+
void this.postSignal(conversationId, messageId, "provider_registry_repair_deferred", {
|
|
10414
|
+
active_sessions: activeSessions
|
|
10415
|
+
});
|
|
10416
|
+
this.log({
|
|
10417
|
+
level: "warn",
|
|
10418
|
+
message: `Provider registry repair deferred until a lifecycle-safe point; ${activeSessions} active session(s) protected`,
|
|
10419
|
+
conversation_id: conversationId,
|
|
10420
|
+
message_id: messageId
|
|
10421
|
+
});
|
|
10422
|
+
return;
|
|
10423
|
+
}
|
|
10424
|
+
await this.issueProviderRegistryReload(conversationId, messageId);
|
|
10425
|
+
}
|
|
10426
|
+
async issueProviderRegistryReload(conversationId, messageId) {
|
|
10427
|
+
this.pendingProviderRegistryRepair = null;
|
|
10428
|
+
const result = await disposeAndReloadProviderConfig(this.port, this.openCodeClient);
|
|
10429
|
+
if (result.outcome === "failed") {
|
|
10430
|
+
const failure = result.status === null ? `transport failure${result.detail ? `: ${result.detail}` : ""}` : `HTTP ${result.status}`;
|
|
10431
|
+
this.log({
|
|
10432
|
+
level: "warn",
|
|
10433
|
+
message: `Provider registry reload failed (${failure})`,
|
|
10434
|
+
conversation_id: conversationId,
|
|
10435
|
+
message_id: messageId
|
|
10436
|
+
});
|
|
10437
|
+
}
|
|
10438
|
+
const repairOutcome = result.outcome === "reloaded" ? "reloaded" : "reload_failed";
|
|
10439
|
+
void this.postSignal(conversationId, messageId, "provider_registry_reload_issued", {
|
|
10440
|
+
repair_outcome: repairOutcome
|
|
10441
|
+
});
|
|
10442
|
+
}
|
|
9964
10443
|
/**
|
|
9965
10444
|
* The per-session polling loop (WI-3). Once per tick it:
|
|
9966
10445
|
* 1. polls `GET /session/:id/message` once and, per in-flight message,
|
|
@@ -10010,7 +10489,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10010
10489
|
}
|
|
10011
10490
|
const { openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk } = await this.pollInteractions(sessionId, watcher, messages);
|
|
10012
10491
|
if (watcher.generation !== generation) return;
|
|
10492
|
+
const ceilingAttemptsThisTick = /* @__PURE__ */ new Set();
|
|
10013
10493
|
for (const inFlight of [...watcher.inFlight.values()]) {
|
|
10494
|
+
if (ceilingAttemptsThisTick.has(inFlight.evidentMessageId)) continue;
|
|
10014
10495
|
await this.serviceInFlightMessage(
|
|
10015
10496
|
sessionId,
|
|
10016
10497
|
watcher,
|
|
@@ -10019,7 +10500,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10019
10500
|
openQuestions,
|
|
10020
10501
|
openPermissions,
|
|
10021
10502
|
questionsPolledOk,
|
|
10022
|
-
permissionsPolledOk
|
|
10503
|
+
permissionsPolledOk,
|
|
10504
|
+
ceilingAttemptsThisTick
|
|
10023
10505
|
);
|
|
10024
10506
|
}
|
|
10025
10507
|
}
|
|
@@ -10060,35 +10542,139 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10060
10542
|
inFlight.deadline = this.now() + this.pausedMaxWaitMs;
|
|
10061
10543
|
}
|
|
10062
10544
|
}
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
const
|
|
10072
|
-
const
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10545
|
+
async settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick) {
|
|
10546
|
+
const entries = [...watcher.inFlight.values()];
|
|
10547
|
+
const pastCeiling = entries.filter(
|
|
10548
|
+
(entry) => this.now() - entry.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS
|
|
10549
|
+
);
|
|
10550
|
+
const healthySiblings = entries.length - pastCeiling.length;
|
|
10551
|
+
const landed = [];
|
|
10552
|
+
let allRecordsLanded = true;
|
|
10553
|
+
const retainedReason = this.readRetainedSessionError(sessionId);
|
|
10554
|
+
for (const entry of pastCeiling) {
|
|
10555
|
+
if (!watcher.inFlight.has(entry.evidentMessageId)) continue;
|
|
10556
|
+
ceilingAttemptsThisTick.add(entry.evidentMessageId);
|
|
10557
|
+
const error2 = this.absoluteCeilingError(
|
|
10558
|
+
entry.evidentMessageId,
|
|
10559
|
+
sessionId,
|
|
10560
|
+
entry.processingAnchorMs,
|
|
10561
|
+
retainedReason
|
|
10562
|
+
);
|
|
10563
|
+
this.log({
|
|
10564
|
+
level: "warn",
|
|
10565
|
+
message: error2,
|
|
10566
|
+
conversation_id: watcher.conv.id,
|
|
10567
|
+
message_id: entry.evidentMessageId
|
|
10568
|
+
});
|
|
10569
|
+
try {
|
|
10570
|
+
await this.markFailed(
|
|
10571
|
+
watcher.conv.id,
|
|
10572
|
+
entry.evidentMessageId,
|
|
10573
|
+
sessionId,
|
|
10574
|
+
error2,
|
|
10575
|
+
void 0,
|
|
10576
|
+
{
|
|
10577
|
+
classification: null,
|
|
10578
|
+
errorShape: extractReplyErrorShape(
|
|
10579
|
+
findLastAssistantReplyFor(messages, entry.opencodeMessageId)
|
|
10580
|
+
)
|
|
10581
|
+
}
|
|
10582
|
+
);
|
|
10583
|
+
} catch (err) {
|
|
10584
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
10585
|
+
allRecordsLanded = false;
|
|
10586
|
+
const watched_for_ms = this.now() - entry.processingAnchorMs;
|
|
10587
|
+
if (err instanceof ChannelTerminalError) {
|
|
10588
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10589
|
+
watched_for_ms,
|
|
10590
|
+
interrupt_outcome: "record_terminal"
|
|
10591
|
+
});
|
|
10592
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10593
|
+
continue;
|
|
10594
|
+
}
|
|
10595
|
+
if (this.now() >= entry.deadline) {
|
|
10596
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10597
|
+
watched_for_ms,
|
|
10598
|
+
interrupt_outcome: "record_deadline"
|
|
10599
|
+
});
|
|
10600
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10601
|
+
continue;
|
|
10602
|
+
}
|
|
10603
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10604
|
+
watched_for_ms,
|
|
10605
|
+
interrupt_outcome: "record_retry"
|
|
10606
|
+
});
|
|
10607
|
+
this.log({
|
|
10608
|
+
level: "warn",
|
|
10609
|
+
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)}`,
|
|
10610
|
+
conversation_id: watcher.conv.id,
|
|
10611
|
+
message_id: entry.evidentMessageId
|
|
10612
|
+
});
|
|
10613
|
+
continue;
|
|
10614
|
+
}
|
|
10615
|
+
entry.done = true;
|
|
10616
|
+
landed.push(entry);
|
|
10617
|
+
}
|
|
10618
|
+
if (!allRecordsLanded) {
|
|
10619
|
+
for (const entry of landed) this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10620
|
+
return;
|
|
10621
|
+
}
|
|
10622
|
+
if (healthySiblings > 0) {
|
|
10623
|
+
for (const entry of landed) {
|
|
10624
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10625
|
+
watched_for_ms: this.now() - entry.processingAnchorMs,
|
|
10626
|
+
interrupt_outcome: "deferred_for_healthy_sibling",
|
|
10627
|
+
healthy_siblings: healthySiblings
|
|
10628
|
+
});
|
|
10629
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10630
|
+
}
|
|
10631
|
+
return;
|
|
10632
|
+
}
|
|
10633
|
+
const interrupt = await interruptSession(this.port, sessionId, this.openCodeClient);
|
|
10634
|
+
for (const entry of landed) {
|
|
10635
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10636
|
+
watched_for_ms: this.now() - entry.processingAnchorMs,
|
|
10637
|
+
interrupt_outcome: interrupt.kind
|
|
10638
|
+
});
|
|
10639
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10640
|
+
}
|
|
10641
|
+
if (interrupt.kind !== "confirmed_stopped") {
|
|
10642
|
+
this.log({
|
|
10643
|
+
level: "warn",
|
|
10644
|
+
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`,
|
|
10645
|
+
conversation_id: watcher.conv.id
|
|
10646
|
+
});
|
|
10647
|
+
}
|
|
10648
|
+
}
|
|
10649
|
+
/**
|
|
10650
|
+
* Drive ONE in-flight message's lifecycle from the tick's message snapshot.
|
|
10651
|
+
* Fires markProcessing on queued→running and markDone on done (each once),
|
|
10652
|
+
* applies the idle-path re-dispatch guard, and removes the message from the
|
|
10653
|
+
* in-flight set on completion or timeout.
|
|
10654
|
+
*/
|
|
10655
|
+
async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk, ceilingAttemptsThisTick) {
|
|
10656
|
+
const conv = watcher.conv;
|
|
10657
|
+
const state = messageRunState(messages, inFlight.opencodeMessageId);
|
|
10658
|
+
const id = inFlight.evidentMessageId;
|
|
10659
|
+
if (state === "running" || state === "done" || state === "failed") {
|
|
10660
|
+
this.neverStartedAttempts.delete(id);
|
|
10661
|
+
}
|
|
10662
|
+
if (inFlight.sessionStoreFailurePending) {
|
|
10663
|
+
await this.settleMessageSessionStoreUnwritable(sessionId, watcher, inFlight);
|
|
10664
|
+
return;
|
|
10665
|
+
}
|
|
10666
|
+
if (inFlight.neverStartedResolved) {
|
|
10667
|
+
if (state === "running" || state === "done" || state === "failed") {
|
|
10668
|
+
inFlight.neverStartedResolved = false;
|
|
10669
|
+
} else {
|
|
10670
|
+
await this.settleMessageNeverStarted(
|
|
10671
|
+
sessionId,
|
|
10672
|
+
watcher,
|
|
10673
|
+
inFlight,
|
|
10674
|
+
this.neverStartedAttempts.get(id)?.count ?? MAX_NEVER_STARTED_ATTEMPTS
|
|
10675
|
+
);
|
|
10676
|
+
return;
|
|
10677
|
+
}
|
|
10092
10678
|
}
|
|
10093
10679
|
if (messages && collectTaskCalls(messages, inFlight.opencodeMessageId).length > 0 && !this.subagentInvocationPrefetches.has(id)) {
|
|
10094
10680
|
void this.resolveSubagentInvocations(
|
|
@@ -10152,7 +10738,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10152
10738
|
if (!inFlight.done) {
|
|
10153
10739
|
const messageErrorText = messageError(messages, inFlight.opencodeMessageId);
|
|
10154
10740
|
const retainedReason = this.readRetainedSessionError(sessionId);
|
|
10155
|
-
const retainedError = retainedReason === null ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
|
|
10741
|
+
const retainedError = retainedReason === null || messageErrorText !== null && messageErrorText.includes(retainedReason) ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
|
|
10156
10742
|
const observedCause = this.sessionStoreHealth?.readCause(sessionId);
|
|
10157
10743
|
const causeError = observedCause === null || observedCause === void 0 ? null : `${SESSION_ERROR_OBSERVED_CAUSE}${observedCause}`;
|
|
10158
10744
|
const storeError = this.sessionStoreHealth?.isUnwritable() ? SESSION_STORE_UNWRITABLE_ERROR : null;
|
|
@@ -10410,17 +10996,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10410
10996
|
return;
|
|
10411
10997
|
}
|
|
10412
10998
|
}
|
|
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);
|
|
10999
|
+
if (watcher.inFlight.has(inFlight.evidentMessageId) && activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
11000
|
+
await this.settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick);
|
|
10424
11001
|
return;
|
|
10425
11002
|
}
|
|
10426
11003
|
const heartbeatDue = activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS;
|
|
@@ -10648,7 +11225,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10648
11225
|
usage,
|
|
10649
11226
|
void 0,
|
|
10650
11227
|
usageAgentName,
|
|
10651
|
-
subagentInvocations
|
|
11228
|
+
subagentInvocations,
|
|
11229
|
+
true
|
|
10652
11230
|
);
|
|
10653
11231
|
} catch (err) {
|
|
10654
11232
|
if (err instanceof ChannelAuthError) throw err;
|
|
@@ -11042,6 +11620,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11042
11620
|
}
|
|
11043
11621
|
const restartAborted = state === "failed" && sessionOngoing === false && isAbortedTerminalReply(messages, ocId ?? "");
|
|
11044
11622
|
if (restartAborted) {
|
|
11623
|
+
const age = this.absoluteAgeForRow(row.processing_started_at);
|
|
11624
|
+
if (age.tripped && age.anchorMs !== null) {
|
|
11625
|
+
try {
|
|
11626
|
+
await this.markFailed(
|
|
11627
|
+
row.conversation_id,
|
|
11628
|
+
row.id,
|
|
11629
|
+
sessionId,
|
|
11630
|
+
this.absoluteCeilingError(row.id, sessionId, age.anchorMs),
|
|
11631
|
+
void 0,
|
|
11632
|
+
{
|
|
11633
|
+
classification: null,
|
|
11634
|
+
errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
|
|
11635
|
+
}
|
|
11636
|
+
);
|
|
11637
|
+
} catch (err) {
|
|
11638
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
11639
|
+
this.log({
|
|
11640
|
+
level: "warn",
|
|
11641
|
+
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)}`,
|
|
11642
|
+
conversation_id: row.conversation_id,
|
|
11643
|
+
message_id: row.id
|
|
11644
|
+
});
|
|
11645
|
+
return;
|
|
11646
|
+
}
|
|
11647
|
+
void this.postSignal(row.conversation_id, row.id, "redrive_ceiling_refused");
|
|
11648
|
+
return;
|
|
11649
|
+
}
|
|
11045
11650
|
this.log({
|
|
11046
11651
|
level: "info",
|
|
11047
11652
|
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 +12192,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11587
12192
|
source_message_id: row.source_message_id,
|
|
11588
12193
|
slack_user_id: row.slack_user_id,
|
|
11589
12194
|
attachments: row.attachments ?? null,
|
|
11590
|
-
opencode_message_id: row.opencode_message_id
|
|
12195
|
+
opencode_message_id: row.opencode_message_id,
|
|
12196
|
+
processing_started_at: row.processing_started_at
|
|
11591
12197
|
};
|
|
11592
12198
|
}
|
|
11593
12199
|
/**
|
|
@@ -12494,7 +13100,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12494
13100
|
* exists but is wedged, so the next attempt must get a fresh one
|
|
12495
13101
|
* instead of reusing it — see WI-1's server-side null-clearing PATCH).
|
|
12496
13102
|
*/
|
|
12497
|
-
async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations) {
|
|
13103
|
+
async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations, abandonedWithoutAnswer) {
|
|
12498
13104
|
const body = { status: "failed" };
|
|
12499
13105
|
if (sessionId === null) {
|
|
12500
13106
|
body.opencode_session_id = null;
|
|
@@ -12507,12 +13113,19 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12507
13113
|
if (subagentInvocations && subagentInvocations.length > 0) {
|
|
12508
13114
|
body.subagent_invocations = subagentInvocations;
|
|
12509
13115
|
}
|
|
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;
|
|
13116
|
+
if (failure && failure.errorShape.name !== null) {
|
|
13117
|
+
body.failure_error_name = failure.errorShape.name;
|
|
12515
13118
|
}
|
|
13119
|
+
if (failure && failure.errorShape.statusCode !== null) {
|
|
13120
|
+
body.failure_status_code = failure.errorShape.statusCode;
|
|
13121
|
+
}
|
|
13122
|
+
if (failure?.classification) {
|
|
13123
|
+
body.failure_kind = failure.classification.kind;
|
|
13124
|
+
body.failure_provider_id = failure.classification.providerId;
|
|
13125
|
+
body.failure_model_id = failure.classification.modelId;
|
|
13126
|
+
body.failure_reason = failure.classification.reason;
|
|
13127
|
+
}
|
|
13128
|
+
if (abandonedWithoutAnswer) body.abandoned_without_answer = true;
|
|
12516
13129
|
try {
|
|
12517
13130
|
await this.callWithRetry(
|
|
12518
13131
|
"marking message as failed",
|
|
@@ -12532,27 +13145,28 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12532
13145
|
this.clearSubagentInvocationCaches(messageId);
|
|
12533
13146
|
}
|
|
12534
13147
|
/**
|
|
12535
|
-
* Classify an errored turn
|
|
13148
|
+
* Classify an errored turn and extract its bounded error shape for `markFailed`.
|
|
12536
13149
|
*
|
|
12537
13150
|
* `messageFailure` alone (structured OpenCode error → `model_auth`) covers
|
|
12538
13151
|
* most cases; when it returns `null` on this ALREADY-FAILED turn, fall back
|
|
12539
13152
|
* to the P1-2b zero-provider check — one extra loopback call to
|
|
12540
13153
|
* `hasAnyConfiguredProvider`, only reached when the structured classifier
|
|
12541
13154
|
* couldn't place it. Fails open (never throws): a fallback probe failure
|
|
12542
|
-
* (`null`/indeterminate) leaves the classification `null
|
|
12543
|
-
*
|
|
13155
|
+
* (`null`/indeterminate) leaves the classification `null`; the raw error shape
|
|
13156
|
+
* is still retained for diagnostics.
|
|
12544
13157
|
*/
|
|
12545
13158
|
async classifyModelAuthFailure(messages, userMessageId) {
|
|
12546
|
-
const classified = messageFailure(messages, userMessageId);
|
|
12547
|
-
if (classified != null) return classified;
|
|
12548
13159
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
12549
|
-
const
|
|
12550
|
-
|
|
13160
|
+
const errorShape = extractReplyErrorShape(reply);
|
|
13161
|
+
const classified = messageFailure(messages, userMessageId);
|
|
13162
|
+
if (classified != null) return { classification: classified, errorShape };
|
|
13163
|
+
const classification = applyZeroProviderFallback(
|
|
12551
13164
|
classified,
|
|
12552
|
-
|
|
13165
|
+
await this.hasAnyConfiguredProvider(),
|
|
12553
13166
|
reply?.info?.providerID ?? null,
|
|
12554
13167
|
reply?.info?.modelID ?? null
|
|
12555
13168
|
);
|
|
13169
|
+
return { classification, errorShape };
|
|
12556
13170
|
}
|
|
12557
13171
|
async recordSubagentModelAuthFailure(failure, conversationId, messageId) {
|
|
12558
13172
|
const providerId = failure.providerId ?? "(unknown)";
|
|
@@ -13377,6 +13991,22 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
13377
13991
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
13378
13992
|
import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
|
|
13379
13993
|
import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
|
|
13994
|
+
function parseConfigContent(content) {
|
|
13995
|
+
if (!content?.trim()) return {};
|
|
13996
|
+
const parsed = JSON.parse(content);
|
|
13997
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
13998
|
+
throw new Error("OPENCODE_CONFIG_CONTENT must contain a JSON object");
|
|
13999
|
+
}
|
|
14000
|
+
return parsed;
|
|
14001
|
+
}
|
|
14002
|
+
function mergeManagedOpenCodeConfigContent(content, managedProviders) {
|
|
14003
|
+
const config = parseConfigContent(content);
|
|
14004
|
+
const provider = typeof config.provider === "object" && config.provider !== null && !Array.isArray(config.provider) ? { ...config.provider } : {};
|
|
14005
|
+
for (const namespace of CREDENTIAL_PROXY_NATIVE_NAMESPACES) {
|
|
14006
|
+
delete provider[namespace];
|
|
14007
|
+
}
|
|
14008
|
+
return JSON.stringify({ ...config, provider: { ...provider, ...managedProviders } });
|
|
14009
|
+
}
|
|
13380
14010
|
function isFile(filePath) {
|
|
13381
14011
|
return existsSync2(filePath) && statSync6(filePath).isFile();
|
|
13382
14012
|
}
|
|
@@ -13410,6 +14040,159 @@ function applyRunnerOpenCodeConfig({
|
|
|
13410
14040
|
log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
|
|
13411
14041
|
}
|
|
13412
14042
|
|
|
14043
|
+
// src/lib/opencode/runtime-provenance.ts
|
|
14044
|
+
import { readFileSync as readFileSync7, readdirSync } from "node:fs";
|
|
14045
|
+
import { homedir as homedir7 } from "node:os";
|
|
14046
|
+
import { join as join12 } from "node:path";
|
|
14047
|
+
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
|
|
14048
|
+
var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
|
|
14049
|
+
function errorKind(error2) {
|
|
14050
|
+
if (!(error2 instanceof Error)) return "unknown error";
|
|
14051
|
+
const code = "code" in error2 && typeof error2.code === "string" ? ` (${error2.code})` : "";
|
|
14052
|
+
return `${error2.name}${code}`;
|
|
14053
|
+
}
|
|
14054
|
+
function parseRegistryPluginSpec(value) {
|
|
14055
|
+
const separator = value.lastIndexOf("@");
|
|
14056
|
+
if (separator <= 0) return null;
|
|
14057
|
+
const name = value.slice(0, separator);
|
|
14058
|
+
const spec = value.slice(separator + 1);
|
|
14059
|
+
if (!PACKAGE_NAME.test(name) || !REGISTRY_SPEC.test(spec)) return null;
|
|
14060
|
+
return { name, spec };
|
|
14061
|
+
}
|
|
14062
|
+
function parseJsonc(value) {
|
|
14063
|
+
let output = "";
|
|
14064
|
+
let inString = false;
|
|
14065
|
+
let escaped = false;
|
|
14066
|
+
let lineComment = false;
|
|
14067
|
+
let blockComment = false;
|
|
14068
|
+
for (let index = 0; index < value.length; index++) {
|
|
14069
|
+
const character = value[index];
|
|
14070
|
+
const next = value[index + 1];
|
|
14071
|
+
if (lineComment) {
|
|
14072
|
+
if (character === "\n") {
|
|
14073
|
+
lineComment = false;
|
|
14074
|
+
output += character;
|
|
14075
|
+
}
|
|
14076
|
+
continue;
|
|
14077
|
+
}
|
|
14078
|
+
if (blockComment) {
|
|
14079
|
+
if (character === "*" && next === "/") {
|
|
14080
|
+
blockComment = false;
|
|
14081
|
+
index++;
|
|
14082
|
+
} else if (character === "\n") {
|
|
14083
|
+
output += character;
|
|
14084
|
+
}
|
|
14085
|
+
continue;
|
|
14086
|
+
}
|
|
14087
|
+
if (inString) {
|
|
14088
|
+
output += character;
|
|
14089
|
+
if (escaped) escaped = false;
|
|
14090
|
+
else if (character === "\\") escaped = true;
|
|
14091
|
+
else if (character === '"') inString = false;
|
|
14092
|
+
continue;
|
|
14093
|
+
}
|
|
14094
|
+
if (character === '"') {
|
|
14095
|
+
inString = true;
|
|
14096
|
+
output += character;
|
|
14097
|
+
} else if (character === "/" && next === "/") {
|
|
14098
|
+
lineComment = true;
|
|
14099
|
+
index++;
|
|
14100
|
+
} else if (character === "/" && next === "*") {
|
|
14101
|
+
blockComment = true;
|
|
14102
|
+
index++;
|
|
14103
|
+
} else if (character === ",") {
|
|
14104
|
+
let lookahead = index + 1;
|
|
14105
|
+
while (/\s/.test(value[lookahead] ?? "")) lookahead++;
|
|
14106
|
+
if (value[lookahead] !== "]" && value[lookahead] !== "}") output += character;
|
|
14107
|
+
} else {
|
|
14108
|
+
output += character;
|
|
14109
|
+
}
|
|
14110
|
+
}
|
|
14111
|
+
return JSON.parse(output);
|
|
14112
|
+
}
|
|
14113
|
+
function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
|
|
14114
|
+
return join12(env.XDG_CACHE_HOME ?? join12(homeDir, ".cache"), "opencode");
|
|
14115
|
+
}
|
|
14116
|
+
function readConfiguredPluginSpecs({
|
|
14117
|
+
cwd = process.cwd(),
|
|
14118
|
+
log: log3
|
|
14119
|
+
}) {
|
|
14120
|
+
let config;
|
|
14121
|
+
try {
|
|
14122
|
+
try {
|
|
14123
|
+
config = parseJsonc(readFileSync7(join12(cwd, "opencode.jsonc"), "utf8"));
|
|
14124
|
+
} catch (error2) {
|
|
14125
|
+
if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
|
|
14126
|
+
config = parseJsonc(readFileSync7(join12(cwd, "opencode.json"), "utf8"));
|
|
14127
|
+
}
|
|
14128
|
+
} catch (error2) {
|
|
14129
|
+
log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
|
|
14130
|
+
return [];
|
|
14131
|
+
}
|
|
14132
|
+
if (typeof config !== "object" || config === null || !("plugin" in config)) return [];
|
|
14133
|
+
const plugins = config.plugin;
|
|
14134
|
+
if (!Array.isArray(plugins)) {
|
|
14135
|
+
log3("Could not read OpenCode plugin configuration: plugin is not an array");
|
|
14136
|
+
return [];
|
|
14137
|
+
}
|
|
14138
|
+
const configured = plugins.filter(
|
|
14139
|
+
(plugin) => typeof plugin === "string" && parseRegistryPluginSpec(plugin) !== null
|
|
14140
|
+
);
|
|
14141
|
+
if (configured.length !== plugins.length) {
|
|
14142
|
+
log3(
|
|
14143
|
+
`Skipped ${plugins.length - configured.length} non-registry OpenCode plugin spec(s); their values were not recorded`
|
|
14144
|
+
);
|
|
14145
|
+
}
|
|
14146
|
+
return configured;
|
|
14147
|
+
}
|
|
14148
|
+
function resolveInstalledPluginVersions({
|
|
14149
|
+
cacheRoot,
|
|
14150
|
+
env = process.env,
|
|
14151
|
+
homeDir = homedir7(),
|
|
14152
|
+
log: log3
|
|
14153
|
+
}) {
|
|
14154
|
+
const packagesRoot = join12(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
|
|
14155
|
+
let directories;
|
|
14156
|
+
try {
|
|
14157
|
+
directories = readdirSync(packagesRoot, { withFileTypes: true });
|
|
14158
|
+
} catch (error2) {
|
|
14159
|
+
if ("code" in Object(error2) && Object(error2).code === "ENOENT") return [];
|
|
14160
|
+
log3(`Could not inspect the OpenCode plugin cache: ${errorKind(error2)}`);
|
|
14161
|
+
return [];
|
|
14162
|
+
}
|
|
14163
|
+
const plugins = [];
|
|
14164
|
+
let skipped = 0;
|
|
14165
|
+
for (const directory of directories) {
|
|
14166
|
+
if (!directory.isDirectory()) continue;
|
|
14167
|
+
const parsed = parseRegistryPluginSpec(directory.name);
|
|
14168
|
+
if (parsed === null) {
|
|
14169
|
+
skipped++;
|
|
14170
|
+
continue;
|
|
14171
|
+
}
|
|
14172
|
+
let resolvedVersion = null;
|
|
14173
|
+
try {
|
|
14174
|
+
const packageJson = JSON.parse(
|
|
14175
|
+
readFileSync7(
|
|
14176
|
+
join12(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
|
|
14177
|
+
"utf8"
|
|
14178
|
+
)
|
|
14179
|
+
);
|
|
14180
|
+
if (typeof packageJson.version === "string" && REGISTRY_SPEC.test(packageJson.version)) {
|
|
14181
|
+
resolvedVersion = packageJson.version;
|
|
14182
|
+
} else {
|
|
14183
|
+
log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: invalid version`);
|
|
14184
|
+
}
|
|
14185
|
+
} catch (error2) {
|
|
14186
|
+
log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: ${errorKind(error2)}`);
|
|
14187
|
+
}
|
|
14188
|
+
plugins.push({ ...parsed, resolvedVersion });
|
|
14189
|
+
}
|
|
14190
|
+
if (skipped > 0) {
|
|
14191
|
+
log3(`Skipped ${skipped} non-registry OpenCode package cache entry; its value was not recorded`);
|
|
14192
|
+
}
|
|
14193
|
+
return plugins;
|
|
14194
|
+
}
|
|
14195
|
+
|
|
13413
14196
|
// src/lib/credential-sync.ts
|
|
13414
14197
|
import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13415
14198
|
var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
|
|
@@ -13645,10 +14428,261 @@ function createCredentialSync({
|
|
|
13645
14428
|
};
|
|
13646
14429
|
}
|
|
13647
14430
|
|
|
14431
|
+
// src/lib/managed-inference-setup.ts
|
|
14432
|
+
var defaultStore = {
|
|
14433
|
+
get: getManagedInferenceCredential,
|
|
14434
|
+
set: setManagedInferenceCredential,
|
|
14435
|
+
clear: clearManagedInferenceCredential
|
|
14436
|
+
};
|
|
14437
|
+
function endpoint(options, operation) {
|
|
14438
|
+
return `${options.apiUrl}/runners/${options.agentId}/managed-credentials/${operation}`;
|
|
14439
|
+
}
|
|
14440
|
+
function requestHeaders(options) {
|
|
14441
|
+
return { Authorization: options.getAuthHeader(), "Content-Type": "application/json" };
|
|
14442
|
+
}
|
|
14443
|
+
function request2(options) {
|
|
14444
|
+
return withRequestTimeout(options.fetchImpl ?? fetch, REQUEST_TIMEOUT_MS);
|
|
14445
|
+
}
|
|
14446
|
+
function safeFailure(operation, status2) {
|
|
14447
|
+
return status2 === void 0 ? `${operation} could not reach the managed credential service` : `${operation} returned HTTP ${status2}`;
|
|
14448
|
+
}
|
|
14449
|
+
function logTransportFailure(options, operation, error2, authorization) {
|
|
14450
|
+
let detail = error2 instanceof Error ? error2.message : String(error2);
|
|
14451
|
+
const credential = authorization.split(/\s+/, 2)[1];
|
|
14452
|
+
for (const secret of [authorization, credential]) {
|
|
14453
|
+
if (secret) detail = detail.replaceAll(secret, "[redacted]");
|
|
14454
|
+
}
|
|
14455
|
+
options.log(
|
|
14456
|
+
`MANAGED-INFERENCE-TRANSPORT-FAILED: operation=${operation} detail=${redactDiagnosticText(detail)}`,
|
|
14457
|
+
"debug"
|
|
14458
|
+
);
|
|
14459
|
+
}
|
|
14460
|
+
async function responseJson(response) {
|
|
14461
|
+
try {
|
|
14462
|
+
const value = await response.json();
|
|
14463
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
14464
|
+
} catch (error2) {
|
|
14465
|
+
return null;
|
|
14466
|
+
}
|
|
14467
|
+
}
|
|
14468
|
+
function readSetup(value) {
|
|
14469
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
14470
|
+
const setup = value;
|
|
14471
|
+
if (typeof setup.runner_id !== "string" || typeof setup.generation !== "string" || typeof setup.status !== "string" || setup.token_id !== null && typeof setup.token_id !== "string" || typeof setup.acknowledged_at !== "string" && setup.acknowledged_at !== null) {
|
|
14472
|
+
return null;
|
|
14473
|
+
}
|
|
14474
|
+
return setup;
|
|
14475
|
+
}
|
|
14476
|
+
function clearLocalSetup(options) {
|
|
14477
|
+
const env = options.env ?? process.env;
|
|
14478
|
+
const store = options.store ?? defaultStore;
|
|
14479
|
+
const stored = store.get(options.agentId);
|
|
14480
|
+
store.clear(options.agentId);
|
|
14481
|
+
const token = env.EVIDENT_PROXY_TOKEN;
|
|
14482
|
+
if (token !== void 0 && token === stored?.token) {
|
|
14483
|
+
delete env.EVIDENT_PROXY_TOKEN;
|
|
14484
|
+
} else if (token !== void 0) {
|
|
14485
|
+
options.log(
|
|
14486
|
+
`MANAGED-INFERENCE-CLEANUP-PRESERVED-EXTERNAL-TOKEN: runner=${options.agentId}`,
|
|
14487
|
+
"debug"
|
|
14488
|
+
);
|
|
14489
|
+
}
|
|
14490
|
+
}
|
|
14491
|
+
async function discoverManagedInferenceSetup(options) {
|
|
14492
|
+
const headers = requestHeaders(options);
|
|
14493
|
+
let response;
|
|
14494
|
+
try {
|
|
14495
|
+
response = await request2(options)(endpoint(options, "current"), {
|
|
14496
|
+
headers
|
|
14497
|
+
});
|
|
14498
|
+
} catch (error2) {
|
|
14499
|
+
logTransportFailure(options, "discovery", error2, headers.Authorization);
|
|
14500
|
+
return { kind: "failed", reason: safeFailure("Managed setup discovery") };
|
|
14501
|
+
}
|
|
14502
|
+
if (response.status === 404) {
|
|
14503
|
+
clearLocalSetup(options);
|
|
14504
|
+
return { kind: "none" };
|
|
14505
|
+
}
|
|
14506
|
+
if (!response.ok) {
|
|
14507
|
+
return { kind: "failed", reason: safeFailure("Managed setup discovery", response.status) };
|
|
14508
|
+
}
|
|
14509
|
+
const body = await responseJson(response);
|
|
14510
|
+
const setup = readSetup(body?.setup);
|
|
14511
|
+
return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed setup discovery returned an invalid response" };
|
|
14512
|
+
}
|
|
14513
|
+
async function enrollManagedInferenceSetup(options) {
|
|
14514
|
+
const response = await post(options, "enroll", {});
|
|
14515
|
+
if (!response) {
|
|
14516
|
+
return { kind: "failed", reason: safeFailure("Managed credential enrollment") };
|
|
14517
|
+
}
|
|
14518
|
+
if (response.status === 404) {
|
|
14519
|
+
return { kind: "none" };
|
|
14520
|
+
}
|
|
14521
|
+
const body = await responseJson(response);
|
|
14522
|
+
if (!response.ok) {
|
|
14523
|
+
const serverError = body?.error;
|
|
14524
|
+
return {
|
|
14525
|
+
kind: "failed",
|
|
14526
|
+
reason: typeof serverError === "string" && serverError.trim() ? redactDiagnosticText(serverError).slice(0, 500) : safeFailure("Managed credential enrollment", response.status)
|
|
14527
|
+
};
|
|
14528
|
+
}
|
|
14529
|
+
const setup = readSetup(body?.setup);
|
|
14530
|
+
return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed credential enrollment returned an invalid response" };
|
|
14531
|
+
}
|
|
14532
|
+
async function post(options, operation, body) {
|
|
14533
|
+
const headers = requestHeaders(options);
|
|
14534
|
+
try {
|
|
14535
|
+
return await request2(options)(endpoint(options, operation), {
|
|
14536
|
+
method: "POST",
|
|
14537
|
+
headers,
|
|
14538
|
+
body: JSON.stringify(body)
|
|
14539
|
+
});
|
|
14540
|
+
} catch (error2) {
|
|
14541
|
+
logTransportFailure(options, operation, error2, headers.Authorization);
|
|
14542
|
+
return null;
|
|
14543
|
+
}
|
|
14544
|
+
}
|
|
14545
|
+
function applyCredential(options, credential) {
|
|
14546
|
+
const env = options.env ?? process.env;
|
|
14547
|
+
let merged;
|
|
14548
|
+
if (options.autoConfigureCredentialsProxy) {
|
|
14549
|
+
const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
|
|
14550
|
+
inferenceBaseUrl: credential.inferenceBaseUrl,
|
|
14551
|
+
modelDescriptors: credential.modelDescriptors
|
|
14552
|
+
});
|
|
14553
|
+
if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
|
|
14554
|
+
return "Managed setup contains unsupported provider descriptors";
|
|
14555
|
+
}
|
|
14556
|
+
try {
|
|
14557
|
+
merged = mergeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT, config.provider);
|
|
14558
|
+
} catch (error2) {
|
|
14559
|
+
return "OPENCODE_CONFIG_CONTENT is malformed; managed providers were not installed";
|
|
14560
|
+
}
|
|
14561
|
+
}
|
|
14562
|
+
try {
|
|
14563
|
+
(options.store ?? defaultStore).set(options.agentId, credential);
|
|
14564
|
+
} catch (error2) {
|
|
14565
|
+
const detail = (error2 instanceof Error ? error2.message : String(error2)).replaceAll(
|
|
14566
|
+
credential.token,
|
|
14567
|
+
"[redacted]"
|
|
14568
|
+
);
|
|
14569
|
+
options.log(
|
|
14570
|
+
`MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation} detail=${redactDiagnosticText(detail)}`,
|
|
14571
|
+
"warn"
|
|
14572
|
+
);
|
|
14573
|
+
return "Managed credentials could not be written to the protected store";
|
|
14574
|
+
}
|
|
14575
|
+
env.EVIDENT_PROXY_TOKEN = credential.token;
|
|
14576
|
+
if (merged !== void 0) env.OPENCODE_CONFIG_CONTENT = merged;
|
|
14577
|
+
return null;
|
|
14578
|
+
}
|
|
14579
|
+
async function reportManagedInferenceApplyFailure(options, failure) {
|
|
14580
|
+
const response = await post(options, "report-failure", {
|
|
14581
|
+
generation: failure.generation,
|
|
14582
|
+
token_id: failure.tokenId
|
|
14583
|
+
});
|
|
14584
|
+
if (!response?.ok) {
|
|
14585
|
+
options.log(
|
|
14586
|
+
`MANAGED-INFERENCE-REPORT-FAILED: runner=${options.agentId} generation=${failure.generation}${response ? ` status=${response.status}` : ""}`,
|
|
14587
|
+
"warn"
|
|
14588
|
+
);
|
|
14589
|
+
}
|
|
14590
|
+
}
|
|
14591
|
+
async function reaffirmStored(options, credential) {
|
|
14592
|
+
const response = await post(options, "reaffirm", {
|
|
14593
|
+
generation: credential.generation,
|
|
14594
|
+
token_id: credential.tokenId
|
|
14595
|
+
});
|
|
14596
|
+
if (!response?.ok) {
|
|
14597
|
+
return {
|
|
14598
|
+
kind: "failed",
|
|
14599
|
+
reason: response?.status === 409 ? "Stored managed credential no longer matches this setup; restart required" : response ? safeFailure("Managed credential reaffirmation", response.status) : safeFailure("Managed credential reaffirmation")
|
|
14600
|
+
};
|
|
14601
|
+
}
|
|
14602
|
+
const body = await responseJson(response);
|
|
14603
|
+
const setup = readSetup(body?.setup);
|
|
14604
|
+
if (!setup || setup.generation !== credential.generation || setup.token_id !== credential.tokenId) {
|
|
14605
|
+
return { kind: "failed", reason: "Managed credential reaffirmation returned a mismatch" };
|
|
14606
|
+
}
|
|
14607
|
+
const failure = applyCredential(options, credential);
|
|
14608
|
+
return failure ? { kind: "failed", reason: failure } : { kind: "ready", generation: credential.generation, tokenId: credential.tokenId };
|
|
14609
|
+
}
|
|
14610
|
+
async function prepareManagedInferenceSetup(options, discovery, health) {
|
|
14611
|
+
if (health.authFailed) {
|
|
14612
|
+
return {
|
|
14613
|
+
kind: "failed",
|
|
14614
|
+
reason: "OpenCode authentication failed; managed credentials were not rotated"
|
|
14615
|
+
};
|
|
14616
|
+
}
|
|
14617
|
+
const stored = (options.store ?? defaultStore).get(options.agentId);
|
|
14618
|
+
if (health.healthy) {
|
|
14619
|
+
if (!stored) {
|
|
14620
|
+
return {
|
|
14621
|
+
kind: "failed",
|
|
14622
|
+
reason: "Managed credentials are missing for the serving OpenCode process; restart required"
|
|
14623
|
+
};
|
|
14624
|
+
}
|
|
14625
|
+
return reaffirmStored(options, stored);
|
|
14626
|
+
}
|
|
14627
|
+
if (discovery.setup.acknowledged_at !== null && stored?.generation === discovery.setup.generation && stored.tokenId === discovery.setup.token_id) {
|
|
14628
|
+
return reaffirmStored(options, stored);
|
|
14629
|
+
}
|
|
14630
|
+
const response = await post(options, "bootstrap", { generation: discovery.setup.generation });
|
|
14631
|
+
if (!response?.ok) {
|
|
14632
|
+
return {
|
|
14633
|
+
kind: "failed",
|
|
14634
|
+
reason: response ? safeFailure("Managed credential bootstrap", response.status) : safeFailure("Managed credential bootstrap")
|
|
14635
|
+
};
|
|
14636
|
+
}
|
|
14637
|
+
const body = await responseJson(response);
|
|
14638
|
+
const setup = readSetup(body?.setup);
|
|
14639
|
+
const token = body?.inference_token;
|
|
14640
|
+
const inferenceBaseUrl = body?.inference_base_url;
|
|
14641
|
+
const modelDescriptors = body?.model_descriptors;
|
|
14642
|
+
if (!setup || setup.generation !== discovery.setup.generation || typeof setup.token_id !== "string" || typeof token !== "string" || typeof inferenceBaseUrl !== "string" || !Array.isArray(modelDescriptors) || !modelDescriptors.every(
|
|
14643
|
+
(descriptor) => typeof descriptor === "object" && descriptor !== null && typeof descriptor.id === "string" && typeof descriptor.provider === "string"
|
|
14644
|
+
)) {
|
|
14645
|
+
return { kind: "failed", reason: "Managed credential bootstrap returned an invalid response" };
|
|
14646
|
+
}
|
|
14647
|
+
const credential = {
|
|
14648
|
+
generation: setup.generation,
|
|
14649
|
+
tokenId: setup.token_id,
|
|
14650
|
+
token,
|
|
14651
|
+
inferenceBaseUrl,
|
|
14652
|
+
modelDescriptors
|
|
14653
|
+
};
|
|
14654
|
+
const failure = applyCredential(options, credential);
|
|
14655
|
+
if (failure) {
|
|
14656
|
+
await reportManagedInferenceApplyFailure(options, credential);
|
|
14657
|
+
return { kind: "failed", reason: failure };
|
|
14658
|
+
}
|
|
14659
|
+
return { kind: "ready", generation: credential.generation, tokenId: credential.tokenId };
|
|
14660
|
+
}
|
|
14661
|
+
async function acknowledgeManagedInferenceSetup(options, preparation) {
|
|
14662
|
+
const response = await post(options, "acknowledge", {
|
|
14663
|
+
generation: preparation.generation,
|
|
14664
|
+
token_id: preparation.tokenId,
|
|
14665
|
+
config_validated: options.autoConfigureCredentialsProxy === true
|
|
14666
|
+
});
|
|
14667
|
+
if (!response?.ok) {
|
|
14668
|
+
throw new Error(
|
|
14669
|
+
response ? safeFailure("Managed credential acknowledgment", response.status) : safeFailure("Managed credential acknowledgment")
|
|
14670
|
+
);
|
|
14671
|
+
}
|
|
14672
|
+
const body = await responseJson(response);
|
|
14673
|
+
const setup = readSetup(body?.setup);
|
|
14674
|
+
if (!setup || setup.generation !== preparation.generation || setup.token_id !== preparation.tokenId) {
|
|
14675
|
+
throw new Error("Managed credential acknowledgment returned a mismatch");
|
|
14676
|
+
}
|
|
14677
|
+
return response.status === 200 && setup.status === "acknowledged" ? "acknowledged" : "pending";
|
|
14678
|
+
}
|
|
14679
|
+
|
|
13648
14680
|
// src/commands/run.ts
|
|
13649
14681
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
13650
14682
|
var MAX_DISCONNECT_REASON_LENGTH = 200;
|
|
13651
14683
|
var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
|
|
14684
|
+
var CREDENTIAL_ADOPTION_PROBE_ATTEMPTS = 3;
|
|
14685
|
+
var CREDENTIAL_ADOPTION_PROBE_INTERVAL_MS = 1e3;
|
|
13652
14686
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
13653
14687
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
13654
14688
|
var TELEMETRY_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
@@ -13683,7 +14717,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
13683
14717
|
if (trimmed === "") {
|
|
13684
14718
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
13685
14719
|
}
|
|
13686
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
14720
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join13(homeDir, trimmed.slice(2)) : trimmed;
|
|
13687
14721
|
if (!isAbsolute4(expanded)) {
|
|
13688
14722
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
13689
14723
|
}
|
|
@@ -13838,7 +14872,7 @@ function logActivity(state, entry) {
|
|
|
13838
14872
|
}
|
|
13839
14873
|
function reportSessionDbRecovery(state) {
|
|
13840
14874
|
try {
|
|
13841
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
14875
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir8(), env: process.env });
|
|
13842
14876
|
for (const record of report.records) {
|
|
13843
14877
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
13844
14878
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -13919,7 +14953,7 @@ async function importOpenCodeV2CredentialsForState(state) {
|
|
|
13919
14953
|
}
|
|
13920
14954
|
const result = await importOpenCodeV2Credentials({
|
|
13921
14955
|
request: client.request,
|
|
13922
|
-
homeDir:
|
|
14956
|
+
homeDir: homedir8(),
|
|
13923
14957
|
directory,
|
|
13924
14958
|
guard: state.opencodeV2CredentialImportGuard,
|
|
13925
14959
|
log: (message, level = "info") => logActivity(state, { type: "info", level, message })
|
|
@@ -13996,6 +15030,125 @@ async function handleAuthError(state, error2) {
|
|
|
13996
15030
|
return { success: false };
|
|
13997
15031
|
}
|
|
13998
15032
|
}
|
|
15033
|
+
function logCredentialAdoptionActivity(state, activity, level) {
|
|
15034
|
+
const message = (() => {
|
|
15035
|
+
switch (activity.outcome) {
|
|
15036
|
+
case "deferred":
|
|
15037
|
+
return `CREDENTIAL-ADOPTION: deferred generation ${activity.generation} with ${activity.active_sessions} active session(s)`;
|
|
15038
|
+
case "reloaded_ready":
|
|
15039
|
+
return `CREDENTIAL-ADOPTION: adopted generation ${activity.generation} with ${activity.active_sessions} active session(s); readiness probe succeeded after ${activity.probe_attempts} attempt(s)`;
|
|
15040
|
+
case "reloaded_interrupted": {
|
|
15041
|
+
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`;
|
|
15042
|
+
return `CREDENTIAL-ADOPTION: generation ${activity.generation} with ${activity.active_sessions} active session(s); replacement instance answered ${response} after ${activity.probe_attempts} probe attempt(s)`;
|
|
15043
|
+
}
|
|
15044
|
+
case "reload_failed":
|
|
15045
|
+
return `CREDENTIAL-ADOPTION: failed to adopt generation ${activity.generation} with ${activity.active_sessions} active session(s)`;
|
|
15046
|
+
case "skipped_v2":
|
|
15047
|
+
return `CREDENTIAL-ADOPTION: skipped generation ${activity.generation} with ${activity.active_sessions} active session(s) on OpenCode V2`;
|
|
15048
|
+
}
|
|
15049
|
+
})();
|
|
15050
|
+
logActivity(state, {
|
|
15051
|
+
type: level === "error" ? "error" : "info",
|
|
15052
|
+
level,
|
|
15053
|
+
...level === "error" ? { error: message } : { message },
|
|
15054
|
+
metadata: {
|
|
15055
|
+
...activity,
|
|
15056
|
+
opencode_version: state.opencodeVersion,
|
|
15057
|
+
port: state.port
|
|
15058
|
+
}
|
|
15059
|
+
});
|
|
15060
|
+
}
|
|
15061
|
+
async function adoptCredentialGeneration(state, generation, adoption, activeSessions, deferredForMs) {
|
|
15062
|
+
const reload = await disposeAndReloadProviderConfig(
|
|
15063
|
+
state.port,
|
|
15064
|
+
state.opencodeClient ?? void 0
|
|
15065
|
+
);
|
|
15066
|
+
if (reload.outcome === "failed") {
|
|
15067
|
+
logCredentialAdoptionActivity(
|
|
15068
|
+
state,
|
|
15069
|
+
{
|
|
15070
|
+
outcome: "reload_failed",
|
|
15071
|
+
generation,
|
|
15072
|
+
active_sessions: activeSessions,
|
|
15073
|
+
adoption,
|
|
15074
|
+
deferred_for_ms: deferredForMs,
|
|
15075
|
+
reload_status: reload.status,
|
|
15076
|
+
reload_detail: reload.detail,
|
|
15077
|
+
probe_attempts: null,
|
|
15078
|
+
probe_status: null,
|
|
15079
|
+
probe_body_empty: null,
|
|
15080
|
+
probe_detail: null
|
|
15081
|
+
},
|
|
15082
|
+
"error"
|
|
15083
|
+
);
|
|
15084
|
+
return;
|
|
15085
|
+
}
|
|
15086
|
+
if (reload.outcome === "skipped_v2") {
|
|
15087
|
+
logCredentialAdoptionActivity(
|
|
15088
|
+
state,
|
|
15089
|
+
{
|
|
15090
|
+
outcome: "skipped_v2",
|
|
15091
|
+
generation,
|
|
15092
|
+
active_sessions: activeSessions,
|
|
15093
|
+
adoption,
|
|
15094
|
+
deferred_for_ms: deferredForMs,
|
|
15095
|
+
reload_status: null,
|
|
15096
|
+
reload_detail: null,
|
|
15097
|
+
probe_attempts: null,
|
|
15098
|
+
probe_status: null,
|
|
15099
|
+
probe_body_empty: null,
|
|
15100
|
+
probe_detail: null
|
|
15101
|
+
},
|
|
15102
|
+
"debug"
|
|
15103
|
+
);
|
|
15104
|
+
return;
|
|
15105
|
+
}
|
|
15106
|
+
let lastProbe = null;
|
|
15107
|
+
let attempts = 0;
|
|
15108
|
+
for (attempts = 1; attempts <= CREDENTIAL_ADOPTION_PROBE_ATTEMPTS; attempts++) {
|
|
15109
|
+
lastProbe = await probeOpenCodeRequestReadiness(state.port, state.opencodeClient ?? void 0);
|
|
15110
|
+
if (lastProbe.ok) {
|
|
15111
|
+
logCredentialAdoptionActivity(
|
|
15112
|
+
state,
|
|
15113
|
+
{
|
|
15114
|
+
outcome: "reloaded_ready",
|
|
15115
|
+
generation,
|
|
15116
|
+
active_sessions: activeSessions,
|
|
15117
|
+
adoption,
|
|
15118
|
+
deferred_for_ms: deferredForMs,
|
|
15119
|
+
reload_status: null,
|
|
15120
|
+
reload_detail: null,
|
|
15121
|
+
probe_attempts: attempts,
|
|
15122
|
+
probe_status: lastProbe.status,
|
|
15123
|
+
probe_body_empty: lastProbe.bodyEmpty,
|
|
15124
|
+
probe_detail: lastProbe.detail
|
|
15125
|
+
},
|
|
15126
|
+
"warn"
|
|
15127
|
+
);
|
|
15128
|
+
return;
|
|
15129
|
+
}
|
|
15130
|
+
if (attempts < CREDENTIAL_ADOPTION_PROBE_ATTEMPTS) {
|
|
15131
|
+
await new Promise((resolve5) => setTimeout(resolve5, CREDENTIAL_ADOPTION_PROBE_INTERVAL_MS));
|
|
15132
|
+
}
|
|
15133
|
+
}
|
|
15134
|
+
logCredentialAdoptionActivity(
|
|
15135
|
+
state,
|
|
15136
|
+
{
|
|
15137
|
+
outcome: "reloaded_interrupted",
|
|
15138
|
+
generation,
|
|
15139
|
+
active_sessions: activeSessions,
|
|
15140
|
+
adoption,
|
|
15141
|
+
deferred_for_ms: deferredForMs,
|
|
15142
|
+
reload_status: null,
|
|
15143
|
+
reload_detail: null,
|
|
15144
|
+
probe_attempts: CREDENTIAL_ADOPTION_PROBE_ATTEMPTS,
|
|
15145
|
+
probe_status: lastProbe?.status ?? null,
|
|
15146
|
+
probe_body_empty: lastProbe?.bodyEmpty ?? null,
|
|
15147
|
+
probe_detail: lastProbe?.detail ?? null
|
|
15148
|
+
},
|
|
15149
|
+
"error"
|
|
15150
|
+
);
|
|
15151
|
+
}
|
|
13999
15152
|
async function driveChannels(state, driver) {
|
|
14000
15153
|
let idlePolls = 0;
|
|
14001
15154
|
let idleMs = 0;
|
|
@@ -14005,6 +15158,15 @@ async function driveChannels(state, driver) {
|
|
|
14005
15158
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
14006
15159
|
let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
|
|
14007
15160
|
let lastSeenOpencodeAuthApplies = driver.fileSyncActivity().opencodeAuthApplyCount;
|
|
15161
|
+
const credentialAdoption = {
|
|
15162
|
+
generation: 0,
|
|
15163
|
+
pendingGeneration: null,
|
|
15164
|
+
pendingSinceMs: null,
|
|
15165
|
+
deferredGeneration: null,
|
|
15166
|
+
adoption: 0
|
|
15167
|
+
};
|
|
15168
|
+
const stayAwakeHolds = () => state.stayAwakeUntilMs !== null && Date.now() < state.stayAwakeUntilMs;
|
|
15169
|
+
let stayAwakeSuppressionLogged = false;
|
|
14008
15170
|
while (state.running) {
|
|
14009
15171
|
const cycleStartedAtMs = performance.now();
|
|
14010
15172
|
let idleThisCycle = false;
|
|
@@ -14046,12 +15208,30 @@ async function driveChannels(state, driver) {
|
|
|
14046
15208
|
lastSeenOpencodeAuthApplies = opencodeAuthApplies;
|
|
14047
15209
|
if (opencodeAuthApplied) state.openaiUsageRearm?.();
|
|
14048
15210
|
if (claudeCredentialApplied || opencodeAuthApplied) {
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
15211
|
+
credentialAdoption.generation++;
|
|
15212
|
+
if (state.opencodeApiVersion === "v2") {
|
|
15213
|
+
logCredentialAdoptionActivity(
|
|
15214
|
+
state,
|
|
15215
|
+
{
|
|
15216
|
+
outcome: "skipped_v2",
|
|
15217
|
+
generation: credentialAdoption.generation,
|
|
15218
|
+
active_sessions: driver.protectedSessionIds().size,
|
|
15219
|
+
adoption: null,
|
|
15220
|
+
deferred_for_ms: null,
|
|
15221
|
+
reload_status: null,
|
|
15222
|
+
reload_detail: null,
|
|
15223
|
+
probe_attempts: null,
|
|
15224
|
+
probe_status: null,
|
|
15225
|
+
probe_body_empty: null,
|
|
15226
|
+
probe_detail: null
|
|
15227
|
+
},
|
|
15228
|
+
"debug"
|
|
15229
|
+
);
|
|
15230
|
+
} else {
|
|
15231
|
+
credentialAdoption.pendingGeneration = credentialAdoption.generation;
|
|
15232
|
+
credentialAdoption.pendingSinceMs = Date.now();
|
|
15233
|
+
credentialAdoption.deferredGeneration = null;
|
|
15234
|
+
}
|
|
14055
15235
|
}
|
|
14056
15236
|
if (opencodeAuthApplied && state.opencodeApiVersion === "v2") {
|
|
14057
15237
|
void importOpenCodeV2CredentialsForState(state).catch(
|
|
@@ -14106,6 +15286,49 @@ async function driveChannels(state, driver) {
|
|
|
14106
15286
|
}
|
|
14107
15287
|
}
|
|
14108
15288
|
}
|
|
15289
|
+
if (credentialAdoption.pendingGeneration !== null) {
|
|
15290
|
+
const activeSessions = driver.protectedSessionIds();
|
|
15291
|
+
const generation = credentialAdoption.pendingGeneration;
|
|
15292
|
+
if (activeSessions.size > 0) {
|
|
15293
|
+
if (credentialAdoption.deferredGeneration !== generation) {
|
|
15294
|
+
credentialAdoption.deferredGeneration = generation;
|
|
15295
|
+
logCredentialAdoptionActivity(
|
|
15296
|
+
state,
|
|
15297
|
+
{
|
|
15298
|
+
outcome: "deferred",
|
|
15299
|
+
generation,
|
|
15300
|
+
active_sessions: activeSessions.size,
|
|
15301
|
+
adoption: null,
|
|
15302
|
+
deferred_for_ms: null,
|
|
15303
|
+
reload_status: null,
|
|
15304
|
+
reload_detail: null,
|
|
15305
|
+
probe_attempts: null,
|
|
15306
|
+
probe_status: null,
|
|
15307
|
+
probe_body_empty: null,
|
|
15308
|
+
probe_detail: null
|
|
15309
|
+
},
|
|
15310
|
+
"warn"
|
|
15311
|
+
);
|
|
15312
|
+
}
|
|
15313
|
+
} else {
|
|
15314
|
+
const deferredForMs = credentialAdoption.deferredGeneration === generation && credentialAdoption.pendingSinceMs !== null ? Math.max(0, Date.now() - credentialAdoption.pendingSinceMs) : null;
|
|
15315
|
+
credentialAdoption.pendingGeneration = null;
|
|
15316
|
+
credentialAdoption.pendingSinceMs = null;
|
|
15317
|
+
credentialAdoption.adoption++;
|
|
15318
|
+
void adoptCredentialGeneration(
|
|
15319
|
+
state,
|
|
15320
|
+
generation,
|
|
15321
|
+
credentialAdoption.adoption,
|
|
15322
|
+
activeSessions.size,
|
|
15323
|
+
deferredForMs
|
|
15324
|
+
).catch(
|
|
15325
|
+
(error2) => logActivity(state, {
|
|
15326
|
+
type: "error",
|
|
15327
|
+
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)}`
|
|
15328
|
+
})
|
|
15329
|
+
);
|
|
15330
|
+
}
|
|
15331
|
+
}
|
|
14109
15332
|
await new Promise((resolve5) => setTimeout(resolve5, CHANNEL_POLL_INTERVAL_MS));
|
|
14110
15333
|
const cycleMs = performance.now() - cycleStartedAtMs;
|
|
14111
15334
|
if (idleThisCycle) idleMs += cycleMs;
|
|
@@ -14119,11 +15342,20 @@ async function driveChannels(state, driver) {
|
|
|
14119
15342
|
if (state.interactive) displayStatus(state);
|
|
14120
15343
|
break;
|
|
14121
15344
|
}
|
|
14122
|
-
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3) {
|
|
15345
|
+
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3 && !stayAwakeHolds()) {
|
|
14123
15346
|
logActivity(state, { type: "info", message: "Idle timeout reached" });
|
|
14124
15347
|
if (state.interactive) displayStatus(state);
|
|
14125
15348
|
break;
|
|
14126
15349
|
}
|
|
15350
|
+
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3 && stayAwakeHolds()) {
|
|
15351
|
+
if (!stayAwakeSuppressionLogged) {
|
|
15352
|
+
stayAwakeSuppressionLogged = true;
|
|
15353
|
+
logActivity(state, {
|
|
15354
|
+
type: "info",
|
|
15355
|
+
message: "Idle, but staying awake until the requested minimum passes"
|
|
15356
|
+
});
|
|
15357
|
+
}
|
|
15358
|
+
}
|
|
14127
15359
|
}
|
|
14128
15360
|
}
|
|
14129
15361
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
@@ -14141,7 +15373,7 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
14141
15373
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
14142
15374
|
}
|
|
14143
15375
|
function sessionDbPath() {
|
|
14144
|
-
return
|
|
15376
|
+
return join13(homedir8(), ".local", "share", "opencode", "opencode.db");
|
|
14145
15377
|
}
|
|
14146
15378
|
function logSessionCleanupActivity(state, phase, entry) {
|
|
14147
15379
|
logActivity(state, {
|
|
@@ -14385,7 +15617,7 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
14385
15617
|
for (const warning2 of config.warnings) {
|
|
14386
15618
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
14387
15619
|
}
|
|
14388
|
-
const dbBytes = statSessionDbBytes(
|
|
15620
|
+
const dbBytes = statSessionDbBytes(homedir8());
|
|
14389
15621
|
void (async () => {
|
|
14390
15622
|
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
14391
15623
|
if (reclaimAvailability !== null) {
|
|
@@ -14452,10 +15684,12 @@ function scheduleUsageReporting(state, params) {
|
|
|
14452
15684
|
let phase = "dormant";
|
|
14453
15685
|
let rearmRequested = false;
|
|
14454
15686
|
const armProbe = () => {
|
|
15687
|
+
if (!state.running) return;
|
|
14455
15688
|
phase = "probe-pending";
|
|
14456
15689
|
params.setTimer(setTimeout(() => void tick(true), params.firstDelayMs()));
|
|
14457
15690
|
};
|
|
14458
15691
|
const scheduleNextTick = () => {
|
|
15692
|
+
if (!state.running) return;
|
|
14459
15693
|
if (rearmRequested) {
|
|
14460
15694
|
rearmRequested = false;
|
|
14461
15695
|
armProbe();
|
|
@@ -14489,6 +15723,7 @@ function scheduleUsageReporting(state, params) {
|
|
|
14489
15723
|
phase = "tick-in-flight";
|
|
14490
15724
|
try {
|
|
14491
15725
|
const usage = await params.fetchUsage();
|
|
15726
|
+
if (!state.running) return;
|
|
14492
15727
|
const result = await params.report(usage);
|
|
14493
15728
|
if (result.ok) {
|
|
14494
15729
|
if (consecutiveFailures > 0) {
|
|
@@ -14601,7 +15836,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14601
15836
|
});
|
|
14602
15837
|
return;
|
|
14603
15838
|
}
|
|
14604
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
15839
|
+
const { collect, stop } = createResourceUsageCollector(homedir8());
|
|
14605
15840
|
state.stopResourceUsageSampling = stop;
|
|
14606
15841
|
let consecutiveFailures = 0;
|
|
14607
15842
|
const tick = async () => {
|
|
@@ -14627,6 +15862,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14627
15862
|
message: lowDiskDecision.warning
|
|
14628
15863
|
});
|
|
14629
15864
|
}
|
|
15865
|
+
if (!state.running) return;
|
|
14630
15866
|
const result = await reportResourceUsage(state.agentId, state.authHeader, usage);
|
|
14631
15867
|
if (result.ok) {
|
|
14632
15868
|
if (consecutiveFailures > 0) {
|
|
@@ -14659,7 +15895,9 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14659
15895
|
message: `Resource usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
14660
15896
|
});
|
|
14661
15897
|
} finally {
|
|
14662
|
-
state.
|
|
15898
|
+
if (state.running) {
|
|
15899
|
+
state.resourceUsageTimer = setTimeout(() => void tick(), usageReportDelayMs());
|
|
15900
|
+
}
|
|
14663
15901
|
}
|
|
14664
15902
|
};
|
|
14665
15903
|
state.resourceUsageTimer = setTimeout(() => void tick(), firstReportDelayMs());
|
|
@@ -14839,7 +16077,7 @@ async function run(options) {
|
|
|
14839
16077
|
let fileSyncDirectories;
|
|
14840
16078
|
try {
|
|
14841
16079
|
logLevel = resolveLogLevel(options);
|
|
14842
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
16080
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir8());
|
|
14843
16081
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
14844
16082
|
throw new Error(
|
|
14845
16083
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -14862,6 +16100,8 @@ async function run(options) {
|
|
|
14862
16100
|
port: options.port ?? 4096,
|
|
14863
16101
|
conversationFilter: options.conversation ?? null,
|
|
14864
16102
|
idleTimeout: options.idleTimeout ?? null,
|
|
16103
|
+
stayAwakeSeconds: null,
|
|
16104
|
+
stayAwakeUntilMs: null,
|
|
14865
16105
|
json: options.json ?? false,
|
|
14866
16106
|
interactive,
|
|
14867
16107
|
logLevel,
|
|
@@ -15051,6 +16291,17 @@ async function run(options) {
|
|
|
15051
16291
|
}
|
|
15052
16292
|
spinner?.succeed(`Runner: ${validation.agent.name || state.agentId}`);
|
|
15053
16293
|
state.agentName = validation.agent.name;
|
|
16294
|
+
const requestedStayAwakeSeconds = validation.agent?.wake_stay_awake_seconds;
|
|
16295
|
+
if (typeof requestedStayAwakeSeconds === "number" && Number.isFinite(requestedStayAwakeSeconds) && requestedStayAwakeSeconds > 0) {
|
|
16296
|
+
state.stayAwakeSeconds = requestedStayAwakeSeconds;
|
|
16297
|
+
} else if (requestedStayAwakeSeconds !== void 0 && requestedStayAwakeSeconds !== null) {
|
|
16298
|
+
state.stayAwakeSeconds = null;
|
|
16299
|
+
log2(
|
|
16300
|
+
state,
|
|
16301
|
+
`Ignoring invalid wake_stay_awake_seconds (type: ${typeof requestedStayAwakeSeconds})`,
|
|
16302
|
+
"warn"
|
|
16303
|
+
);
|
|
16304
|
+
}
|
|
15054
16305
|
const microvmId = process.env.MICROVM_ID?.trim();
|
|
15055
16306
|
if (microvmId) {
|
|
15056
16307
|
const reported = await reportMicrovmId(state.agentId, state.authHeader, microvmId);
|
|
@@ -15112,7 +16363,7 @@ async function run(options) {
|
|
|
15112
16363
|
state.credentialSync?.arm();
|
|
15113
16364
|
const dependencyInstallDiagnosticReporter = createDependencyInstallDiagnosticReporter({
|
|
15114
16365
|
context: {
|
|
15115
|
-
homeDir:
|
|
16366
|
+
homeDir: homedir8(),
|
|
15116
16367
|
cwd: process.cwd(),
|
|
15117
16368
|
microvmId: process.env.MICROVM_ID?.trim(),
|
|
15118
16369
|
euid: process.getuid?.(),
|
|
@@ -15135,7 +16386,7 @@ async function run(options) {
|
|
|
15135
16386
|
dependencyInstallDiagnosticReporter
|
|
15136
16387
|
);
|
|
15137
16388
|
const openCodeLogTail = tailOpenCodeLogFile(
|
|
15138
|
-
resolveOpenCodeLogPath(
|
|
16389
|
+
resolveOpenCodeLogPath(homedir8(), process.env),
|
|
15139
16390
|
openCodeActivityForwarder
|
|
15140
16391
|
);
|
|
15141
16392
|
state.stopOpenCodeLogTail = () => {
|
|
@@ -15210,6 +16461,44 @@ async function run(options) {
|
|
|
15210
16461
|
for (const warning2 of opencodeVersionWarnings) {
|
|
15211
16462
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
15212
16463
|
}
|
|
16464
|
+
const managedSetupOptions = {
|
|
16465
|
+
agentId: state.agentId,
|
|
16466
|
+
apiUrl: getApiUrlConfig(),
|
|
16467
|
+
getAuthHeader: () => state.authHeader,
|
|
16468
|
+
autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
|
|
16469
|
+
log: (message, level = "info") => logActivity(state, {
|
|
16470
|
+
type: level === "error" ? "error" : "info",
|
|
16471
|
+
level,
|
|
16472
|
+
...level === "error" ? { error: message } : { message }
|
|
16473
|
+
})
|
|
16474
|
+
};
|
|
16475
|
+
let managedDiscovery = await discoverManagedInferenceSetup(managedSetupOptions);
|
|
16476
|
+
if (managedDiscovery.kind === "none") {
|
|
16477
|
+
managedDiscovery = await enrollManagedInferenceSetup(managedSetupOptions);
|
|
16478
|
+
}
|
|
16479
|
+
let managedPreparation = { kind: "none" };
|
|
16480
|
+
if (managedDiscovery.kind === "failed") {
|
|
16481
|
+
managedPreparation = managedDiscovery;
|
|
16482
|
+
managedSetupOptions.log(`MANAGED-INFERENCE-NOT-READY: ${managedDiscovery.reason}`, "warn");
|
|
16483
|
+
} else if (managedDiscovery.kind === "setup") {
|
|
16484
|
+
const managedHealth = opencodeVersion === "v2" ? await checkOpenCode2Health(state.port) : await checkOpenCodeHealth(state.port);
|
|
16485
|
+
managedPreparation = await prepareManagedInferenceSetup(
|
|
16486
|
+
managedSetupOptions,
|
|
16487
|
+
managedDiscovery,
|
|
16488
|
+
managedHealth
|
|
16489
|
+
);
|
|
16490
|
+
if (managedPreparation.kind === "failed") {
|
|
16491
|
+
managedSetupOptions.log(
|
|
16492
|
+
`MANAGED-INFERENCE-NOT-READY: ${managedPreparation.reason}`,
|
|
16493
|
+
"warn"
|
|
16494
|
+
);
|
|
16495
|
+
} else if (managedPreparation.kind === "ready") {
|
|
16496
|
+
managedSetupOptions.log(
|
|
16497
|
+
`MANAGED-INFERENCE-PREPARED: runner=${state.agentId} generation=${managedPreparation.generation}`,
|
|
16498
|
+
"info"
|
|
16499
|
+
);
|
|
16500
|
+
}
|
|
16501
|
+
}
|
|
15213
16502
|
const { value: maxActiveSessions, warnings: maxActiveSessionsWarnings } = resolveMaxActiveSessions(options, process.env);
|
|
15214
16503
|
for (const warning2 of maxActiveSessionsWarnings) {
|
|
15215
16504
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
@@ -15236,6 +16525,32 @@ async function run(options) {
|
|
|
15236
16525
|
state.opencodeProcess = options.opencodePidFile ? null : oc.process;
|
|
15237
16526
|
state.opencodeVersion = oc.version;
|
|
15238
16527
|
state.opencodeApiVersion = opencodeVersion;
|
|
16528
|
+
let configuredPlugins = [];
|
|
16529
|
+
let resolvedPlugins = [];
|
|
16530
|
+
try {
|
|
16531
|
+
const provenanceLog = (message) => logActivity(state, { type: "info", level: "warn", message });
|
|
16532
|
+
configuredPlugins = readConfiguredPluginSpecs({ log: provenanceLog });
|
|
16533
|
+
resolvedPlugins = resolveInstalledPluginVersions({ log: provenanceLog });
|
|
16534
|
+
} catch (error2) {
|
|
16535
|
+
const kind = error2 instanceof Error ? error2.name : "unknown error";
|
|
16536
|
+
logActivity(state, {
|
|
16537
|
+
type: "info",
|
|
16538
|
+
level: "warn",
|
|
16539
|
+
message: `Could not collect OpenCode runtime provenance: ${kind}`
|
|
16540
|
+
});
|
|
16541
|
+
}
|
|
16542
|
+
logActivity(state, {
|
|
16543
|
+
type: "info",
|
|
16544
|
+
level: "info",
|
|
16545
|
+
message: "OpenCode runtime provenance",
|
|
16546
|
+
metadata: {
|
|
16547
|
+
opencode_version: state.opencodeVersion,
|
|
16548
|
+
opencode_api_version: state.opencodeApiVersion,
|
|
16549
|
+
configured_plugins: JSON.stringify(configuredPlugins),
|
|
16550
|
+
resolved_plugins: JSON.stringify(resolvedPlugins)
|
|
16551
|
+
},
|
|
16552
|
+
source: "cli.runtime-provenance"
|
|
16553
|
+
});
|
|
15239
16554
|
let opencodePassword = null;
|
|
15240
16555
|
if (opencodeVersion === "v2" && "password" in oc) {
|
|
15241
16556
|
const value = oc.password;
|
|
@@ -15264,7 +16579,7 @@ async function run(options) {
|
|
|
15264
16579
|
const provenance = checkSessionDbProvenance({
|
|
15265
16580
|
dbPath: sessionDbPath(),
|
|
15266
16581
|
currentVersion: state.opencodeVersion,
|
|
15267
|
-
homeDir:
|
|
16582
|
+
homeDir: homedir8(),
|
|
15268
16583
|
env: process.env
|
|
15269
16584
|
});
|
|
15270
16585
|
if (provenance.anomaly) {
|
|
@@ -15278,6 +16593,30 @@ async function run(options) {
|
|
|
15278
16593
|
}
|
|
15279
16594
|
}
|
|
15280
16595
|
state.opencodeConnected = oc.notReadyReason === null;
|
|
16596
|
+
if (managedPreparation.kind === "ready" && oc.notReadyReason === null) {
|
|
16597
|
+
try {
|
|
16598
|
+
const acknowledgement = await acknowledgeManagedInferenceSetup(
|
|
16599
|
+
managedSetupOptions,
|
|
16600
|
+
managedPreparation
|
|
16601
|
+
);
|
|
16602
|
+
if (acknowledgement === "acknowledged") {
|
|
16603
|
+
managedSetupOptions.log(
|
|
16604
|
+
`MANAGED-INFERENCE-ACKNOWLEDGED: runner=${state.agentId} generation=${managedPreparation.generation}`,
|
|
16605
|
+
"info"
|
|
16606
|
+
);
|
|
16607
|
+
} else {
|
|
16608
|
+
managedSetupOptions.log(
|
|
16609
|
+
`MANAGED-INFERENCE-NOT-READY: policy refresh is pending for runner=${state.agentId} generation=${managedPreparation.generation}`,
|
|
16610
|
+
"warn"
|
|
16611
|
+
);
|
|
16612
|
+
}
|
|
16613
|
+
} catch (error2) {
|
|
16614
|
+
managedSetupOptions.log(
|
|
16615
|
+
`MANAGED-INFERENCE-NOT-READY: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
16616
|
+
"warn"
|
|
16617
|
+
);
|
|
16618
|
+
}
|
|
16619
|
+
}
|
|
15281
16620
|
const version2 = state.opencodeVersion ? ` (v${state.opencodeVersion})` : "";
|
|
15282
16621
|
ocSpinner?.succeed(`OpenCode running on port ${state.port}${version2}`);
|
|
15283
16622
|
if (!state.interactive && oc.notReadyReason !== null) {
|
|
@@ -15294,7 +16633,7 @@ async function run(options) {
|
|
|
15294
16633
|
logActivity(state, { type: "info", level: "warn", message: versionWarning });
|
|
15295
16634
|
}
|
|
15296
16635
|
}
|
|
15297
|
-
await
|
|
16636
|
+
await disposeAndReloadProviderConfig(state.port, state.opencodeClient ?? void 0);
|
|
15298
16637
|
if (state.opencodeApiVersion === "v2" && state.opencodeClient !== null) {
|
|
15299
16638
|
await importOpenCodeV2CredentialsForState(state);
|
|
15300
16639
|
}
|
|
@@ -15336,7 +16675,7 @@ async function run(options) {
|
|
|
15336
16675
|
let existingPid;
|
|
15337
16676
|
if (existsSync3(options.litestreamPidFile)) {
|
|
15338
16677
|
try {
|
|
15339
|
-
const rawPid =
|
|
16678
|
+
const rawPid = readFileSync8(options.litestreamPidFile, "utf8").trim();
|
|
15340
16679
|
const parsedPid = Number(rawPid);
|
|
15341
16680
|
if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
|
|
15342
16681
|
existingPid = parsedPid;
|
|
@@ -15435,7 +16774,7 @@ async function run(options) {
|
|
|
15435
16774
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
15436
16775
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
15437
16776
|
fileSyncDirectories,
|
|
15438
|
-
homeDir:
|
|
16777
|
+
homeDir: homedir8(),
|
|
15439
16778
|
maxActiveSessions,
|
|
15440
16779
|
log: (entry) => (
|
|
15441
16780
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -15460,11 +16799,18 @@ async function run(options) {
|
|
|
15460
16799
|
onConnected: (agentId, isReconnect, transition) => {
|
|
15461
16800
|
state.connected = true;
|
|
15462
16801
|
state.agentId = agentId;
|
|
15463
|
-
const connectionMessage = isReconnect ? `Tunnel reconnected (runner: ${agentId}, generation: ${transition.generation}, attempt: ${transition.attempt}, offline for ${transition.downtimeMs}ms)` : `Tunnel connected (runner: ${agentId}, generation: ${transition.generation})`;
|
|
16802
|
+
const connectionMessage = isReconnect ? `Tunnel reconnected (runner: ${agentId}, generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"}, attempt: ${transition.attempt}, offline for ${transition.downtimeMs}ms)` : `Tunnel connected (runner: ${agentId}, generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"})`;
|
|
15464
16803
|
logActivity(state, {
|
|
15465
16804
|
type: "info",
|
|
15466
16805
|
message: connectionMessage
|
|
15467
16806
|
});
|
|
16807
|
+
if (!isReconnect && state.stayAwakeSeconds !== null && state.stayAwakeUntilMs === null) {
|
|
16808
|
+
state.stayAwakeUntilMs = Date.now() + state.stayAwakeSeconds * 1e3;
|
|
16809
|
+
logActivity(state, {
|
|
16810
|
+
type: "info",
|
|
16811
|
+
message: `Staying awake for at least ${state.stayAwakeSeconds}s as requested by the wake`
|
|
16812
|
+
});
|
|
16813
|
+
}
|
|
15468
16814
|
if (options.tunnelReadyFile) {
|
|
15469
16815
|
const marker = writeTunnelReadyMarker(options.tunnelReadyFile, agentId);
|
|
15470
16816
|
if (marker.ok) {
|
|
@@ -15486,6 +16832,7 @@ async function run(options) {
|
|
|
15486
16832
|
connected: state.opencodeConnected
|
|
15487
16833
|
}),
|
|
15488
16834
|
connection_generation: transition.generation,
|
|
16835
|
+
...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {},
|
|
15489
16836
|
...isReconnect && transition.downtimeMs !== null ? {
|
|
15490
16837
|
reconnect_attempt: transition.attempt,
|
|
15491
16838
|
downtime_ms: transition.downtimeMs
|
|
@@ -15516,12 +16863,13 @@ async function run(options) {
|
|
|
15516
16863
|
const safeReason2 = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
|
|
15517
16864
|
logActivity(state, {
|
|
15518
16865
|
type: "info",
|
|
15519
|
-
message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason2})`
|
|
16866
|
+
message: `Tunnel disconnected (generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"}, code: ${code}, reason: ${safeReason2})`
|
|
15520
16867
|
});
|
|
15521
16868
|
emitAgentDisconnected(state.agentId, {
|
|
15522
16869
|
code,
|
|
15523
16870
|
reason: safeReason2,
|
|
15524
|
-
connection_generation: transition.generation
|
|
16871
|
+
connection_generation: transition.generation,
|
|
16872
|
+
...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {}
|
|
15525
16873
|
});
|
|
15526
16874
|
if (state.interactive) displayStatus(state);
|
|
15527
16875
|
},
|
|
@@ -15599,6 +16947,7 @@ async function run(options) {
|
|
|
15599
16947
|
if (error2.message === "Unauthorized") tunnelSpinner?.fail("Unauthorized");
|
|
15600
16948
|
throw error2;
|
|
15601
16949
|
}
|
|
16950
|
+
if (!state.running) return;
|
|
15602
16951
|
scheduleSessionCleanup(state, channelDriver, options);
|
|
15603
16952
|
state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
|
|
15604
16953
|
state.openaiUsageRearm = scheduleUsageReporting(state, {
|
|
@@ -15671,9 +17020,9 @@ program.name("evident").description("Run OpenCode locally and connect it to Evid
|
|
|
15671
17020
|
"--endpoint <url>",
|
|
15672
17021
|
"Evident API base URL (default: production; e.g. http://localhost:3001)"
|
|
15673
17022
|
).option("--tunnel <url>", "Tunnel WebSocket URL (default: production; e.g. ws://localhost:8787)").hook("preAction", (thisCommand) => {
|
|
15674
|
-
const { endpoint, tunnel } = thisCommand.opts();
|
|
15675
|
-
if (
|
|
15676
|
-
setEndpoint(
|
|
17023
|
+
const { endpoint: endpoint2, tunnel } = thisCommand.opts();
|
|
17024
|
+
if (endpoint2) {
|
|
17025
|
+
setEndpoint(endpoint2);
|
|
15677
17026
|
}
|
|
15678
17027
|
if (tunnel) {
|
|
15679
17028
|
setTunnelUrl(tunnel);
|
|
@@ -15747,6 +17096,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
15747
17096
|
).option(
|
|
15748
17097
|
"--opencode-config-overlay <path>",
|
|
15749
17098
|
"Apply this runner-provided OpenCode config before starting OpenCode."
|
|
17099
|
+
).option(
|
|
17100
|
+
"--auto-configure-credentials-proxy",
|
|
17101
|
+
"Install Evident's generated OpenCode providers for the runner's credential pool (off by default)."
|
|
15750
17102
|
).option(
|
|
15751
17103
|
"--credential-sync-marker <path>",
|
|
15752
17104
|
"Own the interval credential sync and write this marker once the shutdown flush has finished, so the runner image's lifecycle hooks can wait on it."
|
|
@@ -15790,6 +17142,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
15790
17142
|
restoreSessionDb: options.restoreSessionDb,
|
|
15791
17143
|
restoreRunnerCredentials: options.restoreRunnerCredentials,
|
|
15792
17144
|
opencodeConfigOverlay: options.opencodeConfigOverlay,
|
|
17145
|
+
autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
|
|
15793
17146
|
credentialSyncMarker: options.credentialSyncMarker
|
|
15794
17147
|
});
|
|
15795
17148
|
}
|