@evident-ai/cli 3.6.0 → 3.6.1-dev.10d71b3
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 +10 -0
- package/dist/index.js +1832 -267
- 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) {
|
|
@@ -703,6 +723,9 @@ async function reportResourceUsage(agentId, authHeader, usage) {
|
|
|
703
723
|
opencode_db_bytes: usage.opencodeDbBytes
|
|
704
724
|
});
|
|
705
725
|
}
|
|
726
|
+
async function reportSessionDeletions(agentId, authHeader, deletions) {
|
|
727
|
+
return postBestEffort(`/runners/${agentId}/session-deletions`, authHeader, { deletions });
|
|
728
|
+
}
|
|
706
729
|
async function getRunnerCredentialVault(agentId, authHeader) {
|
|
707
730
|
const empty = { prefix: null, vaultId: null, source: null };
|
|
708
731
|
try {
|
|
@@ -1156,9 +1179,9 @@ async function claudeUsage() {
|
|
|
1156
1179
|
}
|
|
1157
1180
|
|
|
1158
1181
|
// 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
|
|
1182
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1183
|
+
import { homedir as homedir8 } from "node:os";
|
|
1184
|
+
import { isAbsolute as isAbsolute4, join as join14, parse, resolve as resolvePath2 } from "node:path";
|
|
1162
1185
|
import chalk7 from "chalk";
|
|
1163
1186
|
|
|
1164
1187
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -1287,6 +1310,102 @@ var TUNNEL_USAGE_REARM_PING_PATH = "/__evident/usage-rearm";
|
|
|
1287
1310
|
var MAX_FILE_PUSH_BYTES = 64 * 1024;
|
|
1288
1311
|
var MAX_FILE_SYNC_DIRECTORIES = 16;
|
|
1289
1312
|
|
|
1313
|
+
// ../../packages/types/src/credential-proxy-native-config.ts
|
|
1314
|
+
var EVIDENT_PROXY_TOKEN_REFERENCE = "{env:EVIDENT_PROXY_TOKEN}";
|
|
1315
|
+
var CLAUDE_PROVIDER = {
|
|
1316
|
+
namespace: "evident-claude",
|
|
1317
|
+
name: "Evident Claude credential proxy",
|
|
1318
|
+
npm: "@ai-sdk/anthropic"
|
|
1319
|
+
};
|
|
1320
|
+
var NATIVE_PROVIDERS = {
|
|
1321
|
+
claude: CLAUDE_PROVIDER,
|
|
1322
|
+
anthropic: CLAUDE_PROVIDER,
|
|
1323
|
+
codex: {
|
|
1324
|
+
namespace: "evident-codex",
|
|
1325
|
+
name: "Evident Codex credential proxy",
|
|
1326
|
+
npm: "@ai-sdk/openai"
|
|
1327
|
+
},
|
|
1328
|
+
openai: {
|
|
1329
|
+
namespace: "evident-openai",
|
|
1330
|
+
name: "Evident OpenAI credential proxy",
|
|
1331
|
+
npm: "@ai-sdk/openai"
|
|
1332
|
+
},
|
|
1333
|
+
kimi: {
|
|
1334
|
+
namespace: "evident-kimi",
|
|
1335
|
+
name: "Evident Kimi credential proxy",
|
|
1336
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1337
|
+
sdkName: "evident-kimi"
|
|
1338
|
+
},
|
|
1339
|
+
antigravity: {
|
|
1340
|
+
namespace: "evident-antigravity",
|
|
1341
|
+
name: "Evident Antigravity credential proxy",
|
|
1342
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1343
|
+
sdkName: "evident-antigravity"
|
|
1344
|
+
},
|
|
1345
|
+
xai: {
|
|
1346
|
+
namespace: "evident-xai",
|
|
1347
|
+
name: "Evident xAI credential proxy",
|
|
1348
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1349
|
+
sdkName: "evident-xai"
|
|
1350
|
+
},
|
|
1351
|
+
mistral: {
|
|
1352
|
+
namespace: "evident-mistral",
|
|
1353
|
+
name: "Evident Mistral credential proxy",
|
|
1354
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1355
|
+
sdkName: "evident-mistral"
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
var CREDENTIAL_PROXY_NATIVE_NAMESPACES = [
|
|
1359
|
+
...new Set(Object.values(NATIVE_PROVIDERS).map(({ namespace }) => namespace))
|
|
1360
|
+
];
|
|
1361
|
+
function credentialProxyInferenceUrl(baseUrl) {
|
|
1362
|
+
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
1363
|
+
return /\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`;
|
|
1364
|
+
}
|
|
1365
|
+
function buildCredentialProxyNativeConfig({
|
|
1366
|
+
inferenceBaseUrl,
|
|
1367
|
+
modelDescriptors
|
|
1368
|
+
}) {
|
|
1369
|
+
const baseURL = credentialProxyInferenceUrl(inferenceBaseUrl);
|
|
1370
|
+
const providers = {};
|
|
1371
|
+
const unsupportedProviders = /* @__PURE__ */ new Set();
|
|
1372
|
+
for (const descriptor of modelDescriptors) {
|
|
1373
|
+
const definition = NATIVE_PROVIDERS[descriptor.provider.trim().toLowerCase()];
|
|
1374
|
+
if (!definition) {
|
|
1375
|
+
unsupportedProviders.add(descriptor.provider.trim() || descriptor.provider);
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
providers[definition.namespace] ??= definition.npm === "@ai-sdk/anthropic" ? {
|
|
1379
|
+
name: definition.name,
|
|
1380
|
+
npm: definition.npm,
|
|
1381
|
+
options: { baseURL, authToken: EVIDENT_PROXY_TOKEN_REFERENCE },
|
|
1382
|
+
models: {}
|
|
1383
|
+
} : definition.npm === "@ai-sdk/openai" ? {
|
|
1384
|
+
name: definition.name,
|
|
1385
|
+
npm: definition.npm,
|
|
1386
|
+
options: { baseURL, apiKey: EVIDENT_PROXY_TOKEN_REFERENCE },
|
|
1387
|
+
models: {}
|
|
1388
|
+
} : {
|
|
1389
|
+
name: definition.name,
|
|
1390
|
+
npm: definition.npm,
|
|
1391
|
+
options: {
|
|
1392
|
+
name: definition.sdkName,
|
|
1393
|
+
baseURL,
|
|
1394
|
+
apiKey: EVIDENT_PROXY_TOKEN_REFERENCE
|
|
1395
|
+
},
|
|
1396
|
+
models: {}
|
|
1397
|
+
};
|
|
1398
|
+
providers[definition.namespace].models[descriptor.id] = { name: descriptor.id };
|
|
1399
|
+
}
|
|
1400
|
+
return {
|
|
1401
|
+
unsupportedProviders: [...unsupportedProviders],
|
|
1402
|
+
config: {
|
|
1403
|
+
$schema: "https://opencode.ai/config.json",
|
|
1404
|
+
provider: providers
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1290
1409
|
// ../../packages/types/src/logging/index.ts
|
|
1291
1410
|
var CORRELATION_ID_HEADER = "x-evident-correlation-id";
|
|
1292
1411
|
var FORWARD_FAILURE_REASON_HEADER = "X-Evident-Failure-Reason";
|
|
@@ -1326,7 +1445,7 @@ import ora4 from "ora";
|
|
|
1326
1445
|
import { select as select4 } from "@inquirer/prompts";
|
|
1327
1446
|
|
|
1328
1447
|
// src/lib/telemetry.ts
|
|
1329
|
-
var CLI_VERSION = (true ? "3.6.
|
|
1448
|
+
var CLI_VERSION = (true ? "3.6.1-dev.10d71b3" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1330
1449
|
function getCliVersion() {
|
|
1331
1450
|
return CLI_VERSION;
|
|
1332
1451
|
}
|
|
@@ -1407,7 +1526,7 @@ async function flushEvents() {
|
|
|
1407
1526
|
const controller = new AbortController();
|
|
1408
1527
|
const timeout = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
|
|
1409
1528
|
try {
|
|
1410
|
-
const
|
|
1529
|
+
const request3 = {
|
|
1411
1530
|
events,
|
|
1412
1531
|
client_type: "cli",
|
|
1413
1532
|
client_version: CLI_VERSION
|
|
@@ -1418,7 +1537,7 @@ async function flushEvents() {
|
|
|
1418
1537
|
"Content-Type": "application/json",
|
|
1419
1538
|
Authorization: authHeader
|
|
1420
1539
|
},
|
|
1421
|
-
body: JSON.stringify(
|
|
1540
|
+
body: JSON.stringify(request3),
|
|
1422
1541
|
signal: controller.signal
|
|
1423
1542
|
});
|
|
1424
1543
|
if (!response.ok) {
|
|
@@ -2075,7 +2194,8 @@ function createDependencyInstallDiagnosticReporter({
|
|
|
2075
2194
|
var FORWARDED_LEVELS_BY_SOURCE = {
|
|
2076
2195
|
"cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
|
|
2077
2196
|
opencode: /* @__PURE__ */ new Set(["warn", "error"]),
|
|
2078
|
-
"cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"])
|
|
2197
|
+
"cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"]),
|
|
2198
|
+
"cli.runtime-provenance": /* @__PURE__ */ new Set(["info", "warn", "error"])
|
|
2079
2199
|
};
|
|
2080
2200
|
var SEVERITY_BY_LEVEL = {
|
|
2081
2201
|
info: "info",
|
|
@@ -2521,6 +2641,122 @@ function isSessionDbRecoveryRecord(value) {
|
|
|
2521
2641
|
);
|
|
2522
2642
|
}
|
|
2523
2643
|
|
|
2644
|
+
// src/lib/opencode/session-deletion-report.ts
|
|
2645
|
+
import { appendFileSync, mkdirSync, readFileSync as readFileSync3, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
|
|
2646
|
+
import { dirname as dirname4, join as join4 } from "node:path";
|
|
2647
|
+
var SESSION_DELETION_SPOOL_MAX_RECORDS = 1e3;
|
|
2648
|
+
var MAX_DIAGNOSTIC_LENGTH = 200;
|
|
2649
|
+
function sessionDeletionReportPath(homeDir, env) {
|
|
2650
|
+
const override = env.EVIDENT_SESSION_DELETION_REPORT?.trim();
|
|
2651
|
+
return override || join4(homeDir, ".local", "state", "evident", "session-deletions.jsonl");
|
|
2652
|
+
}
|
|
2653
|
+
function appendSessionDeletionRecords(path, records) {
|
|
2654
|
+
if (records.length === 0) return 0;
|
|
2655
|
+
mkdirSync(dirname4(path), { recursive: true });
|
|
2656
|
+
const current = readSessionDeletionRecords(path);
|
|
2657
|
+
if (current.readError !== null) {
|
|
2658
|
+
throw new Error(`cannot enforce the spool limit after reading ${path}: ${current.readError}`);
|
|
2659
|
+
}
|
|
2660
|
+
const combined = [...current.records, ...records];
|
|
2661
|
+
const kept = combined.slice(-SESSION_DELETION_SPOOL_MAX_RECORDS);
|
|
2662
|
+
const dropped = combined.length - kept.length;
|
|
2663
|
+
if (dropped > 0) {
|
|
2664
|
+
writeFileSync(path, `${kept.map((record) => JSON.stringify(record)).join("\n")}
|
|
2665
|
+
`, "utf8");
|
|
2666
|
+
} else {
|
|
2667
|
+
appendFileSync(path, `${records.map((record) => JSON.stringify(record)).join("\n")}
|
|
2668
|
+
`, "utf8");
|
|
2669
|
+
}
|
|
2670
|
+
return dropped;
|
|
2671
|
+
}
|
|
2672
|
+
function readSessionDeletionRecords(path) {
|
|
2673
|
+
let content;
|
|
2674
|
+
try {
|
|
2675
|
+
content = readFileSync3(path, "utf8");
|
|
2676
|
+
} catch (error2) {
|
|
2677
|
+
if (isMissingFile(error2)) return { records: [], skippedLines: 0, readError: null };
|
|
2678
|
+
const readError = boundedError(error2);
|
|
2679
|
+
console.error(`[session-deletion-report] could not read ${path}: ${readError}`);
|
|
2680
|
+
return { records: [], skippedLines: 0, readError };
|
|
2681
|
+
}
|
|
2682
|
+
let skippedLines = 0;
|
|
2683
|
+
const records = content.split("\n").flatMap((line) => {
|
|
2684
|
+
if (!line.trim()) return [];
|
|
2685
|
+
const record = parseRecord(line, path);
|
|
2686
|
+
if (record) return [record];
|
|
2687
|
+
skippedLines++;
|
|
2688
|
+
return [];
|
|
2689
|
+
});
|
|
2690
|
+
return { records, skippedLines, readError: null };
|
|
2691
|
+
}
|
|
2692
|
+
function removeDeliveredSessionDeletionRecords(path, delivered) {
|
|
2693
|
+
let content;
|
|
2694
|
+
try {
|
|
2695
|
+
content = readFileSync3(path, "utf8");
|
|
2696
|
+
} catch (error2) {
|
|
2697
|
+
if (isMissingFile(error2)) return;
|
|
2698
|
+
console.error(
|
|
2699
|
+
`[session-deletion-report] could not read ${path} before removing delivered records: ${boundedError(error2)}`
|
|
2700
|
+
);
|
|
2701
|
+
throw error2;
|
|
2702
|
+
}
|
|
2703
|
+
const toRemove = /* @__PURE__ */ new Map();
|
|
2704
|
+
for (const record of delivered) {
|
|
2705
|
+
toRemove.set(record.opencode_session_id, (toRemove.get(record.opencode_session_id) ?? 0) + 1);
|
|
2706
|
+
}
|
|
2707
|
+
const remaining = [];
|
|
2708
|
+
for (const line of content.split("\n")) {
|
|
2709
|
+
if (!line.trim()) continue;
|
|
2710
|
+
const record = parseRecord(line, path);
|
|
2711
|
+
const count = record ? toRemove.get(record.opencode_session_id) ?? 0 : 0;
|
|
2712
|
+
if (record && count > 0) {
|
|
2713
|
+
if (count === 1) toRemove.delete(record.opencode_session_id);
|
|
2714
|
+
else toRemove.set(record.opencode_session_id, count - 1);
|
|
2715
|
+
} else {
|
|
2716
|
+
remaining.push(line);
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
try {
|
|
2720
|
+
if (remaining.length === 0) unlinkSync2(path);
|
|
2721
|
+
else writeFileSync(path, `${remaining.join("\n")}
|
|
2722
|
+
`, "utf8");
|
|
2723
|
+
} catch (error2) {
|
|
2724
|
+
if (isMissingFile(error2)) return;
|
|
2725
|
+
console.error(
|
|
2726
|
+
`[session-deletion-report] could not write ${path} after removing delivered records: ${boundedError(error2)}`
|
|
2727
|
+
);
|
|
2728
|
+
throw error2;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
function parseRecord(line, path) {
|
|
2732
|
+
try {
|
|
2733
|
+
const value = JSON.parse(line);
|
|
2734
|
+
if (isSessionDeletionRecord(value)) return value;
|
|
2735
|
+
console.error(`[session-deletion-report] skipped malformed record in ${path}: invalid shape`);
|
|
2736
|
+
return null;
|
|
2737
|
+
} catch (error2) {
|
|
2738
|
+
console.error(
|
|
2739
|
+
`[session-deletion-report] skipped malformed record in ${path}: ${boundedError(error2)}`
|
|
2740
|
+
);
|
|
2741
|
+
return null;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
function isSessionDeletionRecord(value) {
|
|
2745
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2746
|
+
const record = value;
|
|
2747
|
+
const policy = record.policy;
|
|
2748
|
+
if (!policy || typeof policy !== "object" || Array.isArray(policy)) return false;
|
|
2749
|
+
const fields = policy;
|
|
2750
|
+
return typeof record.opencode_session_id === "string" && record.opencode_session_id.length > 0 && typeof record.deleted_at === "string" && record.reason === "auto_cleanup" && (fields.max_age_ms === null || typeof fields.max_age_ms === "number") && (fields.max_count === null || typeof fields.max_count === "number") && (fields.last_activity_at === null || typeof fields.last_activity_at === "string");
|
|
2751
|
+
}
|
|
2752
|
+
function isMissingFile(error2) {
|
|
2753
|
+
return error2 instanceof Error && "code" in error2 && error2.code === "ENOENT";
|
|
2754
|
+
}
|
|
2755
|
+
function boundedError(error2) {
|
|
2756
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
2757
|
+
return message.slice(0, MAX_DIAGNOSTIC_LENGTH);
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2524
2760
|
// src/lib/opencode/auth.ts
|
|
2525
2761
|
function buildOpenCodeBasicAuthHeader(password) {
|
|
2526
2762
|
return `Basic ${Buffer.from(["opencode", password].join(":")).toString("base64")}`;
|
|
@@ -2871,6 +3107,58 @@ async function listAllSessions(port, client) {
|
|
|
2871
3107
|
}
|
|
2872
3108
|
return { sessions };
|
|
2873
3109
|
}
|
|
3110
|
+
var INTERRUPT_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
3111
|
+
"ABORT_ERR",
|
|
3112
|
+
"EAI_AGAIN",
|
|
3113
|
+
"ECONNREFUSED",
|
|
3114
|
+
"ECONNRESET",
|
|
3115
|
+
"ENETUNREACH",
|
|
3116
|
+
"ENOTFOUND",
|
|
3117
|
+
"ETIMEDOUT",
|
|
3118
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
3119
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
3120
|
+
"UND_ERR_SOCKET"
|
|
3121
|
+
]);
|
|
3122
|
+
function interruptFailureDiagnostic(error2) {
|
|
3123
|
+
const message = error2 instanceof Error ? error2.message : "";
|
|
3124
|
+
const httpStatus = /^OpenCode request failed: HTTP ([1-5]\d{2})(?::|$)/.exec(message)?.[1];
|
|
3125
|
+
if (httpStatus) return `HTTP ${httpStatus}`;
|
|
3126
|
+
const candidates = [error2, error2 instanceof Error ? error2.cause : void 0];
|
|
3127
|
+
for (const candidate of candidates) {
|
|
3128
|
+
if (typeof candidate !== "object" || candidate === null || !("code" in candidate)) continue;
|
|
3129
|
+
const code = candidate.code;
|
|
3130
|
+
if (typeof code === "string" && INTERRUPT_NETWORK_CODES.has(code)) {
|
|
3131
|
+
return redactDiagnosticText(`network ${code}`).slice(0, 120);
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
const name = error2 instanceof Error && ["AbortError", "Error", "TimeoutError", "TypeError"].includes(error2.name) ? error2.name : "unknown error";
|
|
3135
|
+
return redactDiagnosticText(`request threw ${name}`).slice(0, 120);
|
|
3136
|
+
}
|
|
3137
|
+
async function interruptSession(port, id, client) {
|
|
3138
|
+
if (client?.version === "v2") {
|
|
3139
|
+
try {
|
|
3140
|
+
await interruptV2Session(client, id);
|
|
3141
|
+
return { kind: "confirmed_stopped" };
|
|
3142
|
+
} catch (error2) {
|
|
3143
|
+
return { kind: "not_confirmed", diagnostic: interruptFailureDiagnostic(error2) };
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
try {
|
|
3147
|
+
const res = await requestWithClient(
|
|
3148
|
+
port,
|
|
3149
|
+
client,
|
|
3150
|
+
`/session/${id}/abort`,
|
|
3151
|
+
{ method: "POST" },
|
|
3152
|
+
{ allowStatuses: ALL_HTTP_STATUSES }
|
|
3153
|
+
);
|
|
3154
|
+
if (res.status >= 200 && res.status < 300) {
|
|
3155
|
+
return { kind: "confirmed_stopped", status: res.status };
|
|
3156
|
+
}
|
|
3157
|
+
return { kind: "not_confirmed", status: res.status };
|
|
3158
|
+
} catch (error2) {
|
|
3159
|
+
return { kind: "threw", diagnostic: interruptFailureDiagnostic(error2) };
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
2874
3162
|
async function deleteSession(port, id, client) {
|
|
2875
3163
|
if (client?.version === "v2") return deleteV2Session(client, id);
|
|
2876
3164
|
try {
|
|
@@ -3040,6 +3328,52 @@ async function getModelAttachmentCapability(port, model, client) {
|
|
|
3040
3328
|
return null;
|
|
3041
3329
|
}
|
|
3042
3330
|
}
|
|
3331
|
+
async function isPinnedProviderLoaded(port, model, client) {
|
|
3332
|
+
if (client?.version === "v2") return null;
|
|
3333
|
+
const { model: baseModel } = splitModelVariant(model);
|
|
3334
|
+
if (!baseModel) return null;
|
|
3335
|
+
const slash = baseModel.indexOf("/");
|
|
3336
|
+
if (slash <= 0) return null;
|
|
3337
|
+
const providerId = baseModel.slice(0, slash);
|
|
3338
|
+
try {
|
|
3339
|
+
const res = await requestWithClient(port, client, "/config/providers");
|
|
3340
|
+
if (!res.ok) {
|
|
3341
|
+
console.error(
|
|
3342
|
+
`[isPinnedProviderLoaded] GET /config/providers returned HTTP ${res.status} (port ${port})`
|
|
3343
|
+
);
|
|
3344
|
+
return null;
|
|
3345
|
+
}
|
|
3346
|
+
const body = await res.json();
|
|
3347
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
3348
|
+
console.error(
|
|
3349
|
+
`[isPinnedProviderLoaded] GET /config/providers body was not a plain object (port ${port})`
|
|
3350
|
+
);
|
|
3351
|
+
return null;
|
|
3352
|
+
}
|
|
3353
|
+
const providers = body.providers;
|
|
3354
|
+
if (!Array.isArray(providers)) {
|
|
3355
|
+
console.error(
|
|
3356
|
+
`[isPinnedProviderLoaded] GET /config/providers body had no providers array (port ${port})`
|
|
3357
|
+
);
|
|
3358
|
+
return null;
|
|
3359
|
+
}
|
|
3360
|
+
const providerIds = providers.filter(
|
|
3361
|
+
(provider) => provider !== null && typeof provider === "object" && typeof provider.id === "string"
|
|
3362
|
+
).map((provider) => provider.id);
|
|
3363
|
+
if (providerIds.length === 0) {
|
|
3364
|
+
console.error(
|
|
3365
|
+
`[isPinnedProviderLoaded] GET /config/providers had no provider entries with string ids (port ${port})`
|
|
3366
|
+
);
|
|
3367
|
+
return null;
|
|
3368
|
+
}
|
|
3369
|
+
return providerIds.includes(providerId);
|
|
3370
|
+
} catch (err) {
|
|
3371
|
+
console.error(
|
|
3372
|
+
`[isPinnedProviderLoaded] GET /config/providers failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3373
|
+
);
|
|
3374
|
+
return null;
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3043
3377
|
async function buildFileParts(attachments, capable) {
|
|
3044
3378
|
const outcomes = [];
|
|
3045
3379
|
const parts = [];
|
|
@@ -3454,9 +3788,17 @@ function isPreamblePinnedRunning(messages, userMessageId) {
|
|
|
3454
3788
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
3455
3789
|
return completedOf(reply) != null && finishOf(reply) === "tool-calls";
|
|
3456
3790
|
}
|
|
3791
|
+
function isMidFlightPinnedRunning(messages, userMessageId) {
|
|
3792
|
+
if (messageRunState(messages, userMessageId) !== "running") return false;
|
|
3793
|
+
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
3794
|
+
return reply !== null && isAssistantInFlight(reply) && completedOf(reply) == null;
|
|
3795
|
+
}
|
|
3457
3796
|
function isB2AbandonmentConfirmed(params) {
|
|
3458
3797
|
return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false && params.rootOngoing === false;
|
|
3459
3798
|
}
|
|
3799
|
+
function isMidFlightOrphanConfirmed(params) {
|
|
3800
|
+
return params.pinnedForMs >= params.minPinnedMs && params.notOngoingStreak >= params.minNotOngoingStreak;
|
|
3801
|
+
}
|
|
3460
3802
|
function isAmbiguousTerminalFinish(m) {
|
|
3461
3803
|
if (completedOf(m) == null) return false;
|
|
3462
3804
|
if (errorOf(m) != null) return false;
|
|
@@ -4221,6 +4563,11 @@ async function listV2Permissions(client, sessionId) {
|
|
|
4221
4563
|
return null;
|
|
4222
4564
|
}
|
|
4223
4565
|
}
|
|
4566
|
+
async function interruptV2Session(client, sessionId) {
|
|
4567
|
+
await client.request(`/api/session/${encodeURIComponent(sessionId)}/interrupt`, {
|
|
4568
|
+
method: "POST"
|
|
4569
|
+
});
|
|
4570
|
+
}
|
|
4224
4571
|
async function getV2ActiveSessions(client) {
|
|
4225
4572
|
try {
|
|
4226
4573
|
const body = await readJson(await client.request("/api/session/active"));
|
|
@@ -4317,13 +4664,13 @@ async function readV2SessionErrorStream(client, options) {
|
|
|
4317
4664
|
}
|
|
4318
4665
|
|
|
4319
4666
|
// src/lib/opencode/credential-import-v2.ts
|
|
4320
|
-
import { readFileSync as
|
|
4321
|
-
import { join as
|
|
4667
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
4668
|
+
import { join as join6 } from "node:path";
|
|
4322
4669
|
|
|
4323
4670
|
// src/lib/openai-usage.ts
|
|
4324
|
-
import { readFileSync as
|
|
4671
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
4325
4672
|
import { homedir as homedir3 } from "node:os";
|
|
4326
|
-
import { join as
|
|
4673
|
+
import { join as join5 } from "node:path";
|
|
4327
4674
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
4328
4675
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
4329
4676
|
var OpenAiUsageError = class extends Error {
|
|
@@ -4337,7 +4684,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
4337
4684
|
}
|
|
4338
4685
|
function readOpenCodeChatGptCredentials() {
|
|
4339
4686
|
try {
|
|
4340
|
-
const raw =
|
|
4687
|
+
const raw = readFileSync4(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
4341
4688
|
let parsed;
|
|
4342
4689
|
try {
|
|
4343
4690
|
parsed = JSON.parse(raw);
|
|
@@ -4569,7 +4916,7 @@ function emptyResult(unsupported = [], outcome = "completed") {
|
|
|
4569
4916
|
}
|
|
4570
4917
|
function readOpenCodeCredentials(homeDir, log3) {
|
|
4571
4918
|
try {
|
|
4572
|
-
const raw =
|
|
4919
|
+
const raw = readFileSync5(join6(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
|
|
4573
4920
|
let parsed;
|
|
4574
4921
|
try {
|
|
4575
4922
|
parsed = JSON.parse(raw);
|
|
@@ -4623,11 +4970,11 @@ function failureMessage(status2) {
|
|
|
4623
4970
|
function isRetryableStatus(status2) {
|
|
4624
4971
|
return CONNECT_KEY_RETRY_STATUSES.includes(status2);
|
|
4625
4972
|
}
|
|
4626
|
-
async function importOneCredential(
|
|
4973
|
+
async function importOneCredential(request3, credential, sleepForRetry, log3) {
|
|
4627
4974
|
const path = `/api/integration/${encodeURIComponent(credential.integrationId)}/connect/key`;
|
|
4628
4975
|
for (let attempt = 1; attempt <= CONNECT_KEY_MAX_ATTEMPTS; attempt += 1) {
|
|
4629
4976
|
try {
|
|
4630
|
-
const response = await
|
|
4977
|
+
const response = await request3(
|
|
4631
4978
|
path,
|
|
4632
4979
|
{
|
|
4633
4980
|
method: "POST",
|
|
@@ -4662,7 +5009,7 @@ async function importOneCredential(request2, credential, sleepForRetry, log3) {
|
|
|
4662
5009
|
throw new Error("OpenCode credential import retry loop did not return");
|
|
4663
5010
|
}
|
|
4664
5011
|
async function importOpenCodeCredentials({
|
|
4665
|
-
request:
|
|
5012
|
+
request: request3,
|
|
4666
5013
|
classified,
|
|
4667
5014
|
connectedIntegrationIds,
|
|
4668
5015
|
sleepForRetry = sleep2,
|
|
@@ -4674,7 +5021,7 @@ async function importOpenCodeCredentials({
|
|
|
4674
5021
|
result.alreadyConnected.push(credential.integrationId);
|
|
4675
5022
|
continue;
|
|
4676
5023
|
}
|
|
4677
|
-
const outcome = await importOneCredential(
|
|
5024
|
+
const outcome = await importOneCredential(request3, credential, sleepForRetry, log3);
|
|
4678
5025
|
if (outcome.ok) result.imported.push(credential.integrationId);
|
|
4679
5026
|
else result.failed.push(outcome.failure);
|
|
4680
5027
|
}
|
|
@@ -4692,7 +5039,7 @@ function parseIntegrationList(value) {
|
|
|
4692
5039
|
return { integrationIds, hasData: value.data.length > 0 };
|
|
4693
5040
|
}
|
|
4694
5041
|
async function getConnectedOpenCodeIntegrationIds({
|
|
4695
|
-
request:
|
|
5042
|
+
request: request3,
|
|
4696
5043
|
directory,
|
|
4697
5044
|
sleepForRetry = sleep2,
|
|
4698
5045
|
log: log3
|
|
@@ -4700,7 +5047,7 @@ async function getConnectedOpenCodeIntegrationIds({
|
|
|
4700
5047
|
const path = `/api/integration?location%5Bdirectory%5D=${encodeURIComponent(directory)}`;
|
|
4701
5048
|
for (let attempt = 1; attempt <= INTEGRATION_LIST_MAX_READS; attempt += 1) {
|
|
4702
5049
|
try {
|
|
4703
|
-
const response = await
|
|
5050
|
+
const response = await request3(path);
|
|
4704
5051
|
const parsed = parseIntegrationList(await response.json());
|
|
4705
5052
|
if (parsed === null) {
|
|
4706
5053
|
log3(
|
|
@@ -4729,7 +5076,7 @@ async function getConnectedOpenCodeIntegrationIds({
|
|
|
4729
5076
|
return null;
|
|
4730
5077
|
}
|
|
4731
5078
|
async function importOpenCodeV2Credentials({
|
|
4732
|
-
request:
|
|
5079
|
+
request: request3,
|
|
4733
5080
|
homeDir,
|
|
4734
5081
|
directory,
|
|
4735
5082
|
sleepForRetry = sleep2,
|
|
@@ -4747,7 +5094,7 @@ async function importOpenCodeV2Credentials({
|
|
|
4747
5094
|
const classified = classifyOpenCodeCredentials(credentials2);
|
|
4748
5095
|
if (classified.importable.length === 0) return emptyResult(classified.unsupported);
|
|
4749
5096
|
const connectedIntegrationIds = await getConnectedOpenCodeIntegrationIds({
|
|
4750
|
-
request:
|
|
5097
|
+
request: request3,
|
|
4751
5098
|
directory,
|
|
4752
5099
|
sleepForRetry,
|
|
4753
5100
|
log: log3
|
|
@@ -4756,7 +5103,7 @@ async function importOpenCodeV2Credentials({
|
|
|
4756
5103
|
return emptyResult(classified.unsupported, "connection-list-unavailable");
|
|
4757
5104
|
}
|
|
4758
5105
|
return importOpenCodeCredentials({
|
|
4759
|
-
request:
|
|
5106
|
+
request: request3,
|
|
4760
5107
|
classified,
|
|
4761
5108
|
connectedIntegrationIds,
|
|
4762
5109
|
sleepForRetry,
|
|
@@ -4769,9 +5116,9 @@ async function importOpenCodeV2Credentials({
|
|
|
4769
5116
|
|
|
4770
5117
|
// src/lib/opencode/session-db-boot.ts
|
|
4771
5118
|
import { spawn as spawn2 } from "node:child_process";
|
|
4772
|
-
import { mkdirSync, statSync as statSync3, unlinkSync as
|
|
5119
|
+
import { mkdirSync as mkdirSync2, statSync as statSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4773
5120
|
import { homedir as homedir4 } from "node:os";
|
|
4774
|
-
import { dirname as
|
|
5121
|
+
import { dirname as dirname5, resolve as resolvePath } from "node:path";
|
|
4775
5122
|
|
|
4776
5123
|
// src/lib/runner-synchroniser.ts
|
|
4777
5124
|
import { spawn } from "node:child_process";
|
|
@@ -4887,7 +5234,7 @@ function reportRecord(stage, outcome, reason, litestreamExitCode, options) {
|
|
|
4887
5234
|
function clearMarker(options) {
|
|
4888
5235
|
if (!options.noReplicateMarker) return;
|
|
4889
5236
|
try {
|
|
4890
|
-
|
|
5237
|
+
unlinkSync3(options.noReplicateMarker);
|
|
4891
5238
|
} catch (error2) {
|
|
4892
5239
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return;
|
|
4893
5240
|
options.log(
|
|
@@ -4899,8 +5246,8 @@ function clearMarker(options) {
|
|
|
4899
5246
|
function markNoReplicate(options, message) {
|
|
4900
5247
|
if (options.noReplicateMarker) {
|
|
4901
5248
|
try {
|
|
4902
|
-
|
|
4903
|
-
|
|
5249
|
+
mkdirSync2(dirname5(options.noReplicateMarker), { recursive: true });
|
|
5250
|
+
writeFileSync2(options.noReplicateMarker, "");
|
|
4904
5251
|
} catch (error2) {
|
|
4905
5252
|
options.log(
|
|
4906
5253
|
`Could not write session-DB no-replicate marker ${options.noReplicateMarker}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
@@ -4913,7 +5260,7 @@ function markNoReplicate(options, message) {
|
|
|
4913
5260
|
function discardSessionDbDebris(options) {
|
|
4914
5261
|
for (const path of [options.dbPath, `${options.dbPath}-wal`, `${options.dbPath}-shm`]) {
|
|
4915
5262
|
try {
|
|
4916
|
-
|
|
5263
|
+
unlinkSync3(path);
|
|
4917
5264
|
} catch (error2) {
|
|
4918
5265
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") continue;
|
|
4919
5266
|
options.log(
|
|
@@ -5054,8 +5401,8 @@ async function ensureLitestreamConfig(options, env) {
|
|
|
5054
5401
|
return null;
|
|
5055
5402
|
}
|
|
5056
5403
|
try {
|
|
5057
|
-
|
|
5058
|
-
|
|
5404
|
+
mkdirSync2(dirname5(configPath), { recursive: true });
|
|
5405
|
+
writeFileSync2(configPath, rendered.stdout);
|
|
5059
5406
|
} catch (error2) {
|
|
5060
5407
|
options.log(
|
|
5061
5408
|
`Could not write ${configPath}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
@@ -5320,8 +5667,8 @@ async function restoreAndVerifySessionDb(options) {
|
|
|
5320
5667
|
|
|
5321
5668
|
// src/lib/opencode/session-db-provenance.ts
|
|
5322
5669
|
import { createRequire } from "node:module";
|
|
5323
|
-
import { mkdirSync as
|
|
5324
|
-
import { dirname as
|
|
5670
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5671
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
5325
5672
|
var require2 = createRequire(import.meta.url);
|
|
5326
5673
|
function readSessionDbMigrationIds(dbPath) {
|
|
5327
5674
|
let db;
|
|
@@ -5358,12 +5705,12 @@ function readSessionDbMigrationIds(dbPath) {
|
|
|
5358
5705
|
}
|
|
5359
5706
|
function sessionDbProvenanceStatePath(homeDir, env) {
|
|
5360
5707
|
const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
|
|
5361
|
-
return override ||
|
|
5708
|
+
return override || join7(homeDir, ".local", "state", "evident", "session-db-provenance.json");
|
|
5362
5709
|
}
|
|
5363
5710
|
function loadSessionDbProvenanceState(path) {
|
|
5364
5711
|
let value;
|
|
5365
5712
|
try {
|
|
5366
|
-
value = JSON.parse(
|
|
5713
|
+
value = JSON.parse(readFileSync6(path, "utf8"));
|
|
5367
5714
|
} catch (error2) {
|
|
5368
5715
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return {};
|
|
5369
5716
|
console.error(
|
|
@@ -5387,8 +5734,8 @@ function loadSessionDbProvenanceState(path) {
|
|
|
5387
5734
|
}
|
|
5388
5735
|
function saveSessionDbProvenanceState(path, state) {
|
|
5389
5736
|
try {
|
|
5390
|
-
|
|
5391
|
-
|
|
5737
|
+
mkdirSync3(dirname6(path), { recursive: true });
|
|
5738
|
+
writeFileSync3(path, `${JSON.stringify(state, null, 2)}
|
|
5392
5739
|
`, "utf8");
|
|
5393
5740
|
} catch (error2) {
|
|
5394
5741
|
console.error(
|
|
@@ -5447,7 +5794,11 @@ function isSessionDbProvenanceRecord(value) {
|
|
|
5447
5794
|
var OPENCODE2_PACKAGE_SPEC = "@opencode-ai/cli@0.0.0-beta-17823";
|
|
5448
5795
|
|
|
5449
5796
|
// src/lib/opencode/opencode-version-gate.ts
|
|
5450
|
-
var QUEUE_VALIDATED_OPENCODE_VERSIONS = [
|
|
5797
|
+
var QUEUE_VALIDATED_OPENCODE_VERSIONS = [
|
|
5798
|
+
"1.17.11",
|
|
5799
|
+
"1.18.3",
|
|
5800
|
+
"1.18.35"
|
|
5801
|
+
];
|
|
5451
5802
|
function isQueueValidatedVersion(version2) {
|
|
5452
5803
|
if (!version2) return false;
|
|
5453
5804
|
return QUEUE_VALIDATED_OPENCODE_VERSIONS.includes(version2);
|
|
@@ -6032,15 +6383,15 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
6032
6383
|
|
|
6033
6384
|
// src/lib/opencode/session-db-size.ts
|
|
6034
6385
|
import { statSync as statSync4 } from "node:fs";
|
|
6035
|
-
import { join as
|
|
6386
|
+
import { join as join8 } from "node:path";
|
|
6036
6387
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
6037
6388
|
function statSessionDbBytes(homeDir) {
|
|
6038
|
-
const dbPath =
|
|
6389
|
+
const dbPath = join8(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
6039
6390
|
try {
|
|
6040
6391
|
return statSync4(dbPath).size;
|
|
6041
6392
|
} catch (err) {
|
|
6042
|
-
const
|
|
6043
|
-
if (!
|
|
6393
|
+
const isMissingFile2 = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
6394
|
+
if (!isMissingFile2) {
|
|
6044
6395
|
console.error(
|
|
6045
6396
|
`[statSessionDbBytes] could not stat ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
6046
6397
|
);
|
|
@@ -6065,12 +6416,12 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
6065
6416
|
// src/lib/opencode/log-tail.ts
|
|
6066
6417
|
import { statSync as statSync5 } from "node:fs";
|
|
6067
6418
|
import { homedir as homedir5 } from "node:os";
|
|
6068
|
-
import { join as
|
|
6419
|
+
import { join as join9 } from "node:path";
|
|
6069
6420
|
import { open as open2, stat } from "node:fs/promises";
|
|
6070
6421
|
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
6071
6422
|
function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
|
|
6072
|
-
const dataDir = env.XDG_DATA_HOME ||
|
|
6073
|
-
return
|
|
6423
|
+
const dataDir = env.XDG_DATA_HOME || join9(homeDir, ".local", "share");
|
|
6424
|
+
return join9(dataDir, "opencode", "log", "opencode.log");
|
|
6074
6425
|
}
|
|
6075
6426
|
function isEnoent(error2) {
|
|
6076
6427
|
return error2?.code === "ENOENT";
|
|
@@ -6492,6 +6843,7 @@ function connectTunnel(options) {
|
|
|
6492
6843
|
}, 3e4);
|
|
6493
6844
|
let relaySilenceTimeout;
|
|
6494
6845
|
let relaySilenceTimedOut = false;
|
|
6846
|
+
let relayConnectionId;
|
|
6495
6847
|
const clearRelaySilenceTimeout = () => {
|
|
6496
6848
|
if (relaySilenceTimeout === void 0) return;
|
|
6497
6849
|
clearTimeout(relaySilenceTimeout);
|
|
@@ -6577,7 +6929,8 @@ function connectTunnel(options) {
|
|
|
6577
6929
|
armRelaySilenceTimeout();
|
|
6578
6930
|
forwarder.setBinaryFramesSupported(message.binary_frames === true);
|
|
6579
6931
|
const connectedAgentId = message.agent_id ?? agentId;
|
|
6580
|
-
|
|
6932
|
+
relayConnectionId = message.connection_id;
|
|
6933
|
+
onConnected?.(connectedAgentId, relayConnectionId);
|
|
6581
6934
|
resolve5({
|
|
6582
6935
|
ws,
|
|
6583
6936
|
close: () => ws.close(1e3, "CLI shutdown")
|
|
@@ -6614,7 +6967,7 @@ function connectTunnel(options) {
|
|
|
6614
6967
|
clearRelaySilenceTimeout();
|
|
6615
6968
|
const reasonStr = (relaySilenceTimedOut ? "no relay heartbeat for 90s \u2014 reconnecting" : void 0) || reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
|
|
6616
6969
|
forwarder.abortAll();
|
|
6617
|
-
onDisconnected?.(code, reasonStr);
|
|
6970
|
+
onDisconnected?.(code, reasonStr, relayConnectionId);
|
|
6618
6971
|
});
|
|
6619
6972
|
});
|
|
6620
6973
|
}
|
|
@@ -6677,7 +7030,7 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6677
7030
|
authHeader: this.opts.getAuthHeader(),
|
|
6678
7031
|
port: this.opts.port,
|
|
6679
7032
|
openCodePassword: this.opts.openCodePassword,
|
|
6680
|
-
onConnected: (agentId) => {
|
|
7033
|
+
onConnected: (agentId, relayConnectionId) => {
|
|
6681
7034
|
const attempt = this.reconnectAttempt + 1;
|
|
6682
7035
|
this.reconnectAttempt = 0;
|
|
6683
7036
|
this.reconnecting = false;
|
|
@@ -6687,13 +7040,17 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6687
7040
|
this.disconnectedAtMs = null;
|
|
6688
7041
|
events.onConnected(agentId, isReconnect, {
|
|
6689
7042
|
generation: this.generation,
|
|
7043
|
+
relayConnectionId: relayConnectionId ?? null,
|
|
6690
7044
|
attempt,
|
|
6691
7045
|
downtimeMs
|
|
6692
7046
|
});
|
|
6693
7047
|
},
|
|
6694
|
-
onDisconnected: (code, reason) => {
|
|
7048
|
+
onDisconnected: (code, reason, relayConnectionId) => {
|
|
6695
7049
|
this.disconnectedAtMs = this.now();
|
|
6696
|
-
events.onDisconnected(code, reason, {
|
|
7050
|
+
events.onDisconnected(code, reason, {
|
|
7051
|
+
generation: this.generation,
|
|
7052
|
+
relayConnectionId: relayConnectionId ?? null
|
|
7053
|
+
});
|
|
6697
7054
|
const relayRequestedReconnect = code === 1e3 && _RunnerConnection.RELAY_RECONNECT_CLOSE_REASONS.has(reason);
|
|
6698
7055
|
if (this.opts.isRunning() && (code !== 1e3 || relayRequestedReconnect) && !this.reconnecting) {
|
|
6699
7056
|
this.reconnectPromise = this.connectWithRetry(true).catch((err) => {
|
|
@@ -6731,10 +7088,10 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6731
7088
|
};
|
|
6732
7089
|
|
|
6733
7090
|
// src/lib/tunnel/ready-marker.ts
|
|
6734
|
-
import { writeFileSync as
|
|
7091
|
+
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
6735
7092
|
function writeTunnelReadyMarker(path, agentId) {
|
|
6736
7093
|
try {
|
|
6737
|
-
|
|
7094
|
+
writeFileSync4(path, `${agentId}
|
|
6738
7095
|
`);
|
|
6739
7096
|
return { ok: true };
|
|
6740
7097
|
} catch (error2) {
|
|
@@ -6759,7 +7116,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
|
|
|
6759
7116
|
}
|
|
6760
7117
|
|
|
6761
7118
|
// src/lib/process-liveness.ts
|
|
6762
|
-
import { readFileSync as
|
|
7119
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
6763
7120
|
function isProcessAlive(pid) {
|
|
6764
7121
|
try {
|
|
6765
7122
|
process.kill(pid, 0);
|
|
@@ -6774,7 +7131,7 @@ function isProcessAlive(pid) {
|
|
|
6774
7131
|
}
|
|
6775
7132
|
if (process.platform !== "linux") return true;
|
|
6776
7133
|
try {
|
|
6777
|
-
const status2 =
|
|
7134
|
+
const status2 = readFileSync7(`/proc/${pid}/status`, "utf8");
|
|
6778
7135
|
return !/^State:\s+Z(?:\s|$)/m.test(status2);
|
|
6779
7136
|
} catch (error2) {
|
|
6780
7137
|
console.error(
|
|
@@ -7133,18 +7490,18 @@ function createSessionStoreHealth(options = {}) {
|
|
|
7133
7490
|
import { homedir as homedir6 } from "node:os";
|
|
7134
7491
|
|
|
7135
7492
|
// src/lib/runner-file-sync.ts
|
|
7136
|
-
import { join as
|
|
7493
|
+
import { join as join11 } from "node:path";
|
|
7137
7494
|
|
|
7138
7495
|
// src/lib/file-push.ts
|
|
7139
7496
|
import { randomUUID } from "node:crypto";
|
|
7140
7497
|
import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
|
|
7141
|
-
import { basename as basename2, dirname as
|
|
7498
|
+
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join10, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
7142
7499
|
var FILE_MODE = 384;
|
|
7143
7500
|
var DIRECTORY_MODE = 448;
|
|
7144
|
-
async function writePushedFile(
|
|
7145
|
-
const { requestedPath, content, allowedDirectories, homeDir } =
|
|
7501
|
+
async function writePushedFile(request3) {
|
|
7502
|
+
const { requestedPath, content, allowedDirectories, allowedFiles, homeDir } = request3;
|
|
7146
7503
|
const bytes = content.byteLength;
|
|
7147
|
-
if (allowedDirectories.length === 0) {
|
|
7504
|
+
if (allowedDirectories.length === 0 && allowedFiles.length === 0) {
|
|
7148
7505
|
return refuse("file_sync_disabled", "File sync is not enabled on this runner.", {
|
|
7149
7506
|
path: requestedPath,
|
|
7150
7507
|
bytes
|
|
@@ -7169,10 +7526,10 @@ async function writePushedFile(request2) {
|
|
|
7169
7526
|
}
|
|
7170
7527
|
try {
|
|
7171
7528
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
7172
|
-
|
|
7529
|
+
dirname7(candidate)
|
|
7173
7530
|
);
|
|
7174
|
-
const realTarget =
|
|
7175
|
-
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
7531
|
+
const realTarget = join10(existingAncestor, ...missingSegments, basename2(candidate));
|
|
7532
|
+
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget) ?? await findMatchingAllowedFile(allowedFiles, realTarget);
|
|
7176
7533
|
if (allowedDirectory === null) {
|
|
7177
7534
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
7178
7535
|
path: realTarget,
|
|
@@ -7181,8 +7538,8 @@ async function writePushedFile(request2) {
|
|
|
7181
7538
|
}
|
|
7182
7539
|
if (missingSegments.length > 0) {
|
|
7183
7540
|
await createMissingDirectories(existingAncestor, missingSegments);
|
|
7184
|
-
const realParent = await realpath(
|
|
7185
|
-
if (realParent !==
|
|
7541
|
+
const realParent = await realpath(dirname7(realTarget));
|
|
7542
|
+
if (realParent !== dirname7(realTarget) || !contains(allowedDirectory, realTarget)) {
|
|
7186
7543
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
7187
7544
|
path: realTarget,
|
|
7188
7545
|
bytes,
|
|
@@ -7207,7 +7564,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
7207
7564
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
7208
7565
|
return null;
|
|
7209
7566
|
}
|
|
7210
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7567
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7211
7568
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
7212
7569
|
return null;
|
|
7213
7570
|
}
|
|
@@ -7225,7 +7582,7 @@ async function resolveNearestExistingAncestor(directory) {
|
|
|
7225
7582
|
try {
|
|
7226
7583
|
return { existingAncestor: await realpath(current), missingSegments };
|
|
7227
7584
|
} catch (err) {
|
|
7228
|
-
const parent =
|
|
7585
|
+
const parent = dirname7(current);
|
|
7229
7586
|
if (err.code !== "ENOENT" || parent === current) {
|
|
7230
7587
|
throw err;
|
|
7231
7588
|
}
|
|
@@ -7247,6 +7604,22 @@ async function findContainingAllowedDirectory(allowedDirectories, realTarget) {
|
|
|
7247
7604
|
}
|
|
7248
7605
|
return null;
|
|
7249
7606
|
}
|
|
7607
|
+
async function findMatchingAllowedFile(allowedFiles, realTarget) {
|
|
7608
|
+
for (const entry of allowedFiles) {
|
|
7609
|
+
if (!isAbsolute2(entry)) {
|
|
7610
|
+
log("warn", "file_push_allowed_directory_skipped", {
|
|
7611
|
+
directory: entry,
|
|
7612
|
+
reason: "not_absolute_file"
|
|
7613
|
+
});
|
|
7614
|
+
continue;
|
|
7615
|
+
}
|
|
7616
|
+
const realParent = await realpathCreatingIfMissing(dirname7(entry));
|
|
7617
|
+
if (realParent !== null && join10(realParent, basename2(entry)) === realTarget) {
|
|
7618
|
+
return realParent;
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
return null;
|
|
7622
|
+
}
|
|
7250
7623
|
async function realpathCreatingIfMissing(directory) {
|
|
7251
7624
|
try {
|
|
7252
7625
|
return await realpath(directory);
|
|
@@ -7280,13 +7653,13 @@ function contains(realDirectory, realTarget) {
|
|
|
7280
7653
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
7281
7654
|
let current = existingAncestor;
|
|
7282
7655
|
for (const segment of missingSegments) {
|
|
7283
|
-
current =
|
|
7656
|
+
current = join10(current, segment);
|
|
7284
7657
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
7285
7658
|
await chmod(current, DIRECTORY_MODE);
|
|
7286
7659
|
}
|
|
7287
7660
|
}
|
|
7288
7661
|
async function writeAtomically(realTarget, content) {
|
|
7289
|
-
const temporaryPath =
|
|
7662
|
+
const temporaryPath = join10(dirname7(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
7290
7663
|
let handle;
|
|
7291
7664
|
try {
|
|
7292
7665
|
handle = await open3(temporaryPath, "wx", FILE_MODE);
|
|
@@ -7416,16 +7789,16 @@ var NOT_APPLIED = {
|
|
|
7416
7789
|
opencodeAuthApplied: false
|
|
7417
7790
|
};
|
|
7418
7791
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
7419
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7420
|
-
return expanded ===
|
|
7792
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join11(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7793
|
+
return expanded === join11(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
7421
7794
|
}
|
|
7422
7795
|
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
7423
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7424
|
-
return expanded ===
|
|
7796
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join11(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7797
|
+
return expanded === join11(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
7425
7798
|
}
|
|
7426
7799
|
async function applyOne(options, file) {
|
|
7427
7800
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
7428
|
-
if (options.allowedDirectories.length === 0) {
|
|
7801
|
+
if (options.allowedDirectories.length === 0 && options.allowedFiles.length === 0) {
|
|
7429
7802
|
options.log({
|
|
7430
7803
|
level: "warn",
|
|
7431
7804
|
message: `Runner file ${label} rejected: file sync is not enabled on this runner (start it with --enable-file-sync-to)`
|
|
@@ -7452,6 +7825,7 @@ async function applyOne(options, file) {
|
|
|
7452
7825
|
requestedPath: file.path,
|
|
7453
7826
|
content: download.content,
|
|
7454
7827
|
allowedDirectories: options.allowedDirectories,
|
|
7828
|
+
allowedFiles: options.allowedFiles,
|
|
7455
7829
|
homeDir: options.homeDir
|
|
7456
7830
|
});
|
|
7457
7831
|
} catch (err) {
|
|
@@ -7558,107 +7932,317 @@ function describe(err) {
|
|
|
7558
7932
|
return err instanceof Error ? err.message : String(err);
|
|
7559
7933
|
}
|
|
7560
7934
|
|
|
7561
|
-
// src/lib/
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
const base = `OpenCode ended this turn after a tool call (finish="${finish ?? "(unknown)"}") ` + (outcome === "without-answer" ? "without writing an answer, and confirmed it is no longer working on this conversation. Evident is reporting the run as failed so the failure and its reason are visible in failed runs." : "after writing only part of an answer, and confirmed it is no longer working on this conversation. Evident delivered what was written and is reporting the run as finished.");
|
|
7570
|
-
if (replyShape === null) return base;
|
|
7571
|
-
return `${base} ${replyShape.lastToolName === null ? "No tool call was recorded" : `The last tool call was "${replyShape.lastToolName}" with status "${replyShape.lastToolStatus ?? "(unknown)"}"`}; the reply's final part types were [${replyShape.partTypes.join(", ")}]${replyShape.droppedPartCount > 0 ? ` (${replyShape.droppedPartCount} earlier part types omitted)` : ""}.`;
|
|
7572
|
-
}
|
|
7573
|
-
function cleanImageMime(contentType) {
|
|
7574
|
-
if (!contentType) return null;
|
|
7575
|
-
const media = contentType.split(";")[0].trim().toLowerCase();
|
|
7576
|
-
return /^image\/[a-z0-9.+-]+$/.test(media) ? media : null;
|
|
7577
|
-
}
|
|
7578
|
-
var LOG_LEVELS = {
|
|
7579
|
-
debug: 0,
|
|
7580
|
-
info: 1,
|
|
7581
|
-
warn: 2,
|
|
7582
|
-
error: 3
|
|
7583
|
-
};
|
|
7584
|
-
var DEFAULT_RETRY_POLICY = {
|
|
7585
|
-
maxAttempts: 6,
|
|
7586
|
-
baseDelayMs: 500,
|
|
7587
|
-
maxDelayMs: 3e4
|
|
7588
|
-
};
|
|
7589
|
-
var SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS = 2e3;
|
|
7590
|
-
var SESSION_ERROR_STREAM_HEALTHY_MS = 5e3;
|
|
7591
|
-
var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
7592
|
-
var MAX_BUFFERED_SESSION_ERRORS = 256;
|
|
7593
|
-
var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
|
|
7594
|
-
var SESSION_STORE_UNWRITABLE_ERROR = "The runner session database could not be written, so this turn did not finish. Nothing was lost from the queue. Clear disk space and retry.";
|
|
7595
|
-
var SESSION_ERROR_RETAINED_DETAIL = "Runner-observed OpenCode session error: ";
|
|
7596
|
-
var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
|
|
7597
|
-
var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
7598
|
-
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
7599
|
-
var DRIVER_SESSION_ENUMERATION_LIMIT = 20;
|
|
7600
|
-
var DEFAULT_STUCK_QUEUED_MS = 6e4;
|
|
7601
|
-
var HEARTBEAT_MS = 6e4;
|
|
7602
|
-
var ALIVE_WITHHELD_THRESHOLD_MS = 3 * HEARTBEAT_MS;
|
|
7603
|
-
var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
|
|
7604
|
-
var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
|
|
7605
|
-
var AMBIGUOUS_FINISH_MAX_PINNED_MS = 3 * 6e4;
|
|
7606
|
-
var B2_ABANDONMENT_RECHECK_MS = HEARTBEAT_MS;
|
|
7607
|
-
var POLL_MISS_GRACE_MS = HEARTBEAT_MS;
|
|
7608
|
-
var WATCHER_STALL_MS = 3 * POLL_MISS_GRACE_MS;
|
|
7609
|
-
var MAX_WATCHER_STALL_RESTARTS = 3;
|
|
7610
|
-
var MAX_RELEASED_OPENCODE_IDS = 256;
|
|
7611
|
-
var MAX_SUPERSEDED_CONVERSATIONS = 256;
|
|
7612
|
-
var MAX_IDENTICAL_REDRIVE_POLL_FAILURES = 5;
|
|
7613
|
-
var MAX_NEVER_STARTED_ATTEMPTS = 3;
|
|
7614
|
-
var WEDGE_WARNING_INTERVAL_MS = 5 * 60 * 1e3;
|
|
7615
|
-
var MAX_WEDGED_CONVERSATIONS = 256;
|
|
7616
|
-
var ChannelAuthError = class extends Error {
|
|
7617
|
-
constructor(message) {
|
|
7618
|
-
super(message);
|
|
7619
|
-
this.name = "ChannelAuthError";
|
|
7935
|
+
// src/lib/vault-file-sync.ts
|
|
7936
|
+
import { createHash } from "node:crypto";
|
|
7937
|
+
async function syncVaultFiles(options) {
|
|
7938
|
+
const files = await listVaultFiles(options);
|
|
7939
|
+
if (files === null) return { applied: 0 };
|
|
7940
|
+
const listedIds = new Set(files.map((file) => file.id));
|
|
7941
|
+
for (const fileId of options.outcomes.keys()) {
|
|
7942
|
+
if (!listedIds.has(fileId)) options.outcomes.delete(fileId);
|
|
7620
7943
|
}
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7944
|
+
let applied = 0;
|
|
7945
|
+
for (const file of files) {
|
|
7946
|
+
try {
|
|
7947
|
+
if (await reconcileVaultFile(options, file)) applied += 1;
|
|
7948
|
+
} catch (err) {
|
|
7949
|
+
options.log({
|
|
7950
|
+
level: "error",
|
|
7951
|
+
message: `Vault file ${file.id} reconciliation failed unexpectedly (message processing is unaffected): ${describe2(err)}`
|
|
7952
|
+
});
|
|
7953
|
+
}
|
|
7628
7954
|
}
|
|
7629
|
-
};
|
|
7630
|
-
function backoffDelay(attempt, policy) {
|
|
7631
|
-
const exp = policy.baseDelayMs * Math.pow(2, attempt);
|
|
7632
|
-
const capped = Math.min(policy.maxDelayMs, exp);
|
|
7633
|
-
return Math.floor(Math.random() * capped);
|
|
7634
|
-
}
|
|
7635
|
-
function isRetryableStatus2(status2) {
|
|
7636
|
-
return status2 === 429 || status2 >= 500 && status2 <= 599;
|
|
7955
|
+
return { applied };
|
|
7637
7956
|
}
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
}
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7957
|
+
async function listVaultFiles(options) {
|
|
7958
|
+
let response;
|
|
7959
|
+
try {
|
|
7960
|
+
response = await options.fetchImpl(`${options.apiUrl}/runners/${options.agentId}/vault-files`, {
|
|
7961
|
+
headers: { Authorization: options.getAuthHeader() }
|
|
7962
|
+
});
|
|
7963
|
+
} catch (err) {
|
|
7964
|
+
options.log({
|
|
7965
|
+
level: "warn",
|
|
7966
|
+
message: `Could not list vault files \u2014 retrying on the next drain: ${describe2(err)}`
|
|
7967
|
+
});
|
|
7968
|
+
return null;
|
|
7969
|
+
}
|
|
7970
|
+
if (!response.ok) {
|
|
7971
|
+
options.log({
|
|
7972
|
+
level: response.status === 404 ? "debug" : "warn",
|
|
7973
|
+
message: `Listing vault files returned HTTP ${response.status} \u2014 retrying on the next drain`
|
|
7974
|
+
});
|
|
7975
|
+
return null;
|
|
7976
|
+
}
|
|
7977
|
+
let body;
|
|
7978
|
+
try {
|
|
7979
|
+
body = await response.json();
|
|
7980
|
+
} catch (err) {
|
|
7981
|
+
options.log({
|
|
7982
|
+
level: "warn",
|
|
7983
|
+
message: `Vault file list was not readable JSON \u2014 retrying on the next drain: ${describe2(err)}`
|
|
7984
|
+
});
|
|
7985
|
+
return null;
|
|
7986
|
+
}
|
|
7987
|
+
if (body === null || typeof body !== "object" || !Array.isArray(body.files)) {
|
|
7988
|
+
options.log({
|
|
7989
|
+
level: "warn",
|
|
7990
|
+
message: "Vault file list was not an object with a files array \u2014 ignoring it for this drain"
|
|
7991
|
+
});
|
|
7992
|
+
return null;
|
|
7993
|
+
}
|
|
7994
|
+
const files = [];
|
|
7995
|
+
for (const entry of body.files) {
|
|
7996
|
+
const file = asVaultFile(entry);
|
|
7997
|
+
if (file === null) {
|
|
7998
|
+
options.log({
|
|
7999
|
+
level: "warn",
|
|
8000
|
+
message: "Ignoring a malformed vault file entry"
|
|
8001
|
+
});
|
|
8002
|
+
continue;
|
|
8003
|
+
}
|
|
8004
|
+
files.push(file);
|
|
8005
|
+
}
|
|
8006
|
+
return files;
|
|
8007
|
+
}
|
|
8008
|
+
function asVaultFile(entry) {
|
|
8009
|
+
if (entry === null || typeof entry !== "object") return null;
|
|
8010
|
+
const value = entry;
|
|
8011
|
+
if (typeof value.id !== "string" || value.id === "" || typeof value.destination_path !== "string" || value.destination_path === "" || typeof value.size_bytes !== "number" || !Number.isFinite(value.size_bytes) || value.size_bytes < 0 || typeof value.content_sha256 !== "string" || value.content_sha256 === "" || typeof value.created_at !== "string" || typeof value.updated_at !== "string") {
|
|
8012
|
+
return null;
|
|
8013
|
+
}
|
|
8014
|
+
return {
|
|
8015
|
+
id: value.id,
|
|
8016
|
+
destination_path: value.destination_path,
|
|
8017
|
+
size_bytes: value.size_bytes,
|
|
8018
|
+
content_sha256: value.content_sha256,
|
|
8019
|
+
created_at: value.created_at,
|
|
8020
|
+
updated_at: value.updated_at
|
|
8021
|
+
};
|
|
8022
|
+
}
|
|
8023
|
+
async function reconcileVaultFile(options, file) {
|
|
8024
|
+
const existing = options.outcomes.get(file.id);
|
|
8025
|
+
if (existing?.sha256 === file.content_sha256) {
|
|
8026
|
+
if (existing.reported || existing.reportAttempts >= MAX_ACK_ATTEMPTS) return false;
|
|
8027
|
+
await reportOutcome(options, file, existing);
|
|
8028
|
+
return false;
|
|
8029
|
+
}
|
|
8030
|
+
const content = await downloadVaultFile(options, file);
|
|
8031
|
+
if (content === null) return false;
|
|
8032
|
+
const actualSha256 = createHash("sha256").update(content).digest("hex");
|
|
8033
|
+
if (actualSha256 !== file.content_sha256) {
|
|
8034
|
+
options.log({
|
|
8035
|
+
level: "warn",
|
|
8036
|
+
message: `Vault file ${file.id} content hash mismatch: expected ${file.content_sha256}, received ${actualSha256} \u2014 retrying on the next drain`
|
|
8037
|
+
});
|
|
8038
|
+
return false;
|
|
8039
|
+
}
|
|
8040
|
+
const outcome = await applyVaultFile(options, file, content);
|
|
8041
|
+
const entry = {
|
|
8042
|
+
sha256: file.content_sha256,
|
|
8043
|
+
outcome: outcome.ok ? "applied" : "refused",
|
|
8044
|
+
reason: outcome.ok ? null : outcome.code,
|
|
8045
|
+
reported: false,
|
|
8046
|
+
reportAttempts: 0
|
|
8047
|
+
};
|
|
8048
|
+
options.outcomes.set(file.id, entry);
|
|
8049
|
+
await reportOutcome(options, file, entry);
|
|
8050
|
+
return outcome.ok;
|
|
8051
|
+
}
|
|
8052
|
+
async function downloadVaultFile(options, file) {
|
|
8053
|
+
try {
|
|
8054
|
+
const response = await options.fetchImpl(
|
|
8055
|
+
`${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/content`,
|
|
8056
|
+
{ headers: { Authorization: options.getAuthHeader() } }
|
|
8057
|
+
);
|
|
8058
|
+
if (!response.ok) {
|
|
8059
|
+
options.log({
|
|
8060
|
+
level: "warn",
|
|
8061
|
+
message: `Downloading vault file ${file.id} returned HTTP ${response.status} \u2014 retrying on the next drain`
|
|
8062
|
+
});
|
|
8063
|
+
return null;
|
|
8064
|
+
}
|
|
8065
|
+
return Buffer.from(await response.arrayBuffer());
|
|
8066
|
+
} catch (err) {
|
|
8067
|
+
options.log({
|
|
8068
|
+
level: "warn",
|
|
8069
|
+
message: `Downloading vault file ${file.id} failed \u2014 retrying on the next drain: ${describe2(err)}`
|
|
8070
|
+
});
|
|
8071
|
+
return null;
|
|
8072
|
+
}
|
|
8073
|
+
}
|
|
8074
|
+
async function applyVaultFile(options, file, content) {
|
|
8075
|
+
try {
|
|
8076
|
+
const outcome = await writePushedFile({
|
|
8077
|
+
requestedPath: file.destination_path,
|
|
8078
|
+
content,
|
|
8079
|
+
allowedDirectories: options.allowedDirectories,
|
|
8080
|
+
allowedFiles: options.allowedFiles,
|
|
8081
|
+
homeDir: options.homeDir
|
|
8082
|
+
});
|
|
8083
|
+
if (!outcome.ok) {
|
|
8084
|
+
options.log({
|
|
8085
|
+
level: "warn",
|
|
8086
|
+
message: `Vault file ${file.id} refused (${outcome.code}): ${outcome.message}`
|
|
8087
|
+
});
|
|
8088
|
+
} else {
|
|
8089
|
+
options.log({
|
|
8090
|
+
level: "info",
|
|
8091
|
+
message: `Vault file ${file.id} applied (${content.byteLength} bytes)`
|
|
8092
|
+
});
|
|
8093
|
+
}
|
|
8094
|
+
return outcome;
|
|
8095
|
+
} catch (err) {
|
|
8096
|
+
options.log({
|
|
8097
|
+
level: "error",
|
|
8098
|
+
message: `Vault file ${file.id} could not be written: ${describe2(err)}`
|
|
8099
|
+
});
|
|
8100
|
+
return { ok: false, code: "write_failed", message: "The runner could not write the file." };
|
|
8101
|
+
}
|
|
8102
|
+
}
|
|
8103
|
+
async function reportOutcome(options, file, entry) {
|
|
8104
|
+
const body = {
|
|
8105
|
+
status: entry.outcome === "applied" ? "applied" : "rejected",
|
|
8106
|
+
reported_sha256: entry.sha256
|
|
8107
|
+
};
|
|
8108
|
+
if (entry.outcome === "refused" && entry.reason !== null) body.reason = entry.reason;
|
|
8109
|
+
try {
|
|
8110
|
+
const response = await options.fetchImpl(
|
|
8111
|
+
`${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/status`,
|
|
8112
|
+
{
|
|
8113
|
+
method: "POST",
|
|
8114
|
+
headers: {
|
|
8115
|
+
Authorization: options.getAuthHeader(),
|
|
8116
|
+
"Content-Type": "application/json"
|
|
8117
|
+
},
|
|
8118
|
+
body: JSON.stringify(body)
|
|
8119
|
+
}
|
|
8120
|
+
);
|
|
8121
|
+
if (response.ok) {
|
|
8122
|
+
entry.reported = true;
|
|
8123
|
+
return;
|
|
8124
|
+
}
|
|
8125
|
+
recordReportFailure(options, file, entry, `HTTP ${response.status}`);
|
|
8126
|
+
} catch (err) {
|
|
8127
|
+
recordReportFailure(options, file, entry, describe2(err));
|
|
8128
|
+
}
|
|
8129
|
+
}
|
|
8130
|
+
function recordReportFailure(options, file, entry, reason) {
|
|
8131
|
+
entry.reportAttempts += 1;
|
|
8132
|
+
options.log({
|
|
8133
|
+
level: "error",
|
|
8134
|
+
message: entry.reportAttempts >= MAX_ACK_ATTEMPTS ? `Reporting vault file ${file.id} as ${entry.outcome} failed (${reason}) \u2014 giving up after ${entry.reportAttempts} attempts` : `Reporting vault file ${file.id} as ${entry.outcome} failed (${reason}) \u2014 retrying on the next drain (attempt ${entry.reportAttempts} of ${MAX_ACK_ATTEMPTS})`
|
|
8135
|
+
});
|
|
8136
|
+
}
|
|
8137
|
+
function describe2(err) {
|
|
8138
|
+
return err instanceof Error ? err.message : String(err);
|
|
8139
|
+
}
|
|
8140
|
+
|
|
8141
|
+
// src/lib/channels/driver.ts
|
|
8142
|
+
function messageIdOf(m) {
|
|
8143
|
+
if (!m || typeof m !== "object") return void 0;
|
|
8144
|
+
if (typeof m.id === "string") return m.id;
|
|
8145
|
+
const infoId = m.info?.id;
|
|
8146
|
+
return typeof infoId === "string" ? infoId : void 0;
|
|
8147
|
+
}
|
|
8148
|
+
function composeAbandonmentNote(finish, replyShape, outcome) {
|
|
8149
|
+
const base = outcome === "midflight-without-answer" || outcome === "midflight-with-answer" ? "OpenCode stopped this turn without ever finishing, and confirmed the session is no longer running. " + (outcome === "midflight-without-answer" ? "Evident is reporting the run as failed so the failure and its reason are visible in failed runs." : "Evident delivered what was written and is reporting the run as finished.") : `OpenCode ended this turn after a tool call (finish="${finish ?? "(unknown)"}") ` + (outcome === "without-answer" ? "without writing an answer, and confirmed it is no longer working on this conversation. Evident is reporting the run as failed so the failure and its reason are visible in failed runs." : "after writing only part of an answer, and confirmed it is no longer working on this conversation. Evident delivered what was written and is reporting the run as finished.");
|
|
8150
|
+
if (replyShape === null) return base;
|
|
8151
|
+
return `${base} ${replyShape.lastToolName === null ? "No tool call was recorded" : `The last tool call was "${replyShape.lastToolName}" with status "${replyShape.lastToolStatus ?? "(unknown)"}"`}; the reply's final part types were [${replyShape.partTypes.join(", ")}]${replyShape.droppedPartCount > 0 ? ` (${replyShape.droppedPartCount} earlier part types omitted)` : ""}.`;
|
|
8152
|
+
}
|
|
8153
|
+
function cleanImageMime(contentType) {
|
|
8154
|
+
if (!contentType) return null;
|
|
8155
|
+
const media = contentType.split(";")[0].trim().toLowerCase();
|
|
8156
|
+
return /^image\/[a-z0-9.+-]+$/.test(media) ? media : null;
|
|
8157
|
+
}
|
|
8158
|
+
var LOG_LEVELS = {
|
|
8159
|
+
debug: 0,
|
|
8160
|
+
info: 1,
|
|
8161
|
+
warn: 2,
|
|
8162
|
+
error: 3
|
|
8163
|
+
};
|
|
8164
|
+
var DEFAULT_RETRY_POLICY = {
|
|
8165
|
+
maxAttempts: 6,
|
|
8166
|
+
baseDelayMs: 500,
|
|
8167
|
+
maxDelayMs: 3e4
|
|
8168
|
+
};
|
|
8169
|
+
var SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS = 2e3;
|
|
8170
|
+
var SESSION_ERROR_STREAM_HEALTHY_MS = 5e3;
|
|
8171
|
+
var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
8172
|
+
var MAX_BUFFERED_SESSION_ERRORS = 256;
|
|
8173
|
+
var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
|
|
8174
|
+
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.";
|
|
8175
|
+
var SESSION_ERROR_RETAINED_DETAIL = "Runner-observed OpenCode session error: ";
|
|
8176
|
+
var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
|
|
8177
|
+
var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
8178
|
+
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
8179
|
+
var DRIVER_SESSION_ENUMERATION_LIMIT = 20;
|
|
8180
|
+
var DEFAULT_STUCK_QUEUED_MS = 6e4;
|
|
8181
|
+
var HEARTBEAT_MS = 6e4;
|
|
8182
|
+
var ALIVE_WITHHELD_THRESHOLD_MS = 3 * HEARTBEAT_MS;
|
|
8183
|
+
var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
|
|
8184
|
+
var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
|
|
8185
|
+
var AMBIGUOUS_FINISH_MAX_PINNED_MS = 3 * 6e4;
|
|
8186
|
+
var MIDFLIGHT_ORPHAN_MIN_PINNED_MS = 3 * 6e4;
|
|
8187
|
+
var MIDFLIGHT_ORPHAN_RECHECK_MS = 6e4;
|
|
8188
|
+
var MIDFLIGHT_ORPHAN_MIN_NOT_ONGOING_STREAK = 2;
|
|
8189
|
+
var B2_ABANDONMENT_RECHECK_MS = HEARTBEAT_MS;
|
|
8190
|
+
var POLL_MISS_GRACE_MS = HEARTBEAT_MS;
|
|
8191
|
+
var WATCHER_STALL_MS = 3 * POLL_MISS_GRACE_MS;
|
|
8192
|
+
var MAX_WATCHER_STALL_RESTARTS = 3;
|
|
8193
|
+
var MAX_RELEASED_OPENCODE_IDS = 256;
|
|
8194
|
+
var MAX_SUPERSEDED_CONVERSATIONS = 256;
|
|
8195
|
+
var MAX_IDENTICAL_REDRIVE_POLL_FAILURES = 5;
|
|
8196
|
+
var MAX_NEVER_STARTED_ATTEMPTS = 3;
|
|
8197
|
+
var WEDGE_WARNING_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8198
|
+
var MAX_WEDGED_CONVERSATIONS = 256;
|
|
8199
|
+
var ChannelAuthError = class extends Error {
|
|
8200
|
+
constructor(message) {
|
|
8201
|
+
super(message);
|
|
8202
|
+
this.name = "ChannelAuthError";
|
|
8203
|
+
}
|
|
8204
|
+
};
|
|
8205
|
+
var ChannelTerminalError = class extends Error {
|
|
8206
|
+
status;
|
|
8207
|
+
constructor(message, status2) {
|
|
8208
|
+
super(message);
|
|
8209
|
+
this.name = "ChannelTerminalError";
|
|
8210
|
+
this.status = status2;
|
|
8211
|
+
}
|
|
8212
|
+
};
|
|
8213
|
+
function backoffDelay(attempt, policy) {
|
|
8214
|
+
const exp = policy.baseDelayMs * Math.pow(2, attempt);
|
|
8215
|
+
const capped = Math.min(policy.maxDelayMs, exp);
|
|
8216
|
+
return Math.floor(Math.random() * capped);
|
|
8217
|
+
}
|
|
8218
|
+
function isRetryableStatus2(status2) {
|
|
8219
|
+
return status2 === 429 || status2 >= 500 && status2 <= 599;
|
|
8220
|
+
}
|
|
8221
|
+
var VOLATILE_BODY_FIELD_PATTERN = /("(?:ref|requestId|request_id|traceId|trace_id)"\s*:\s*)"[^"]*"/gi;
|
|
8222
|
+
function normalizeRedrivePollFailureBody(body) {
|
|
8223
|
+
return body.replace(VOLATILE_BODY_FIELD_PATTERN, '$1"<redacted>"').replace(/\s+/g, " ").trim().slice(0, 200);
|
|
8224
|
+
}
|
|
8225
|
+
var ChannelDriver = class _ChannelDriver {
|
|
8226
|
+
agentId;
|
|
8227
|
+
port;
|
|
8228
|
+
apiUrl;
|
|
8229
|
+
getAuthHeader;
|
|
8230
|
+
conversationFilter;
|
|
8231
|
+
retry;
|
|
8232
|
+
log;
|
|
8233
|
+
fetchImpl;
|
|
8234
|
+
sleep;
|
|
8235
|
+
pausedPollIntervalMs;
|
|
8236
|
+
pausedMaxWaitMs;
|
|
8237
|
+
stuckQueuedMs;
|
|
8238
|
+
now;
|
|
8239
|
+
fileSyncDirectories;
|
|
8240
|
+
fileSyncExactFiles;
|
|
8241
|
+
homeDir;
|
|
8242
|
+
maxActiveSessions;
|
|
8243
|
+
watcherStallMs;
|
|
8244
|
+
wedgeWarningIntervalMs;
|
|
8245
|
+
sessionStoreHealth;
|
|
7662
8246
|
openCodeClient;
|
|
7663
8247
|
/** Cache of conversationId → opencode sessionId. */
|
|
7664
8248
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -7966,6 +8550,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7966
8550
|
* re-downloaded and re-written every ~2s until the server expires it.
|
|
7967
8551
|
*/
|
|
7968
8552
|
fileAckFailures = /* @__PURE__ */ new Map();
|
|
8553
|
+
vaultFileOutcomes = /* @__PURE__ */ new Map();
|
|
7969
8554
|
/**
|
|
7970
8555
|
* Monotonic count of files this runner has pulled and written (#559). Only
|
|
7971
8556
|
* ever increases, so `run.ts` detects work by comparing it against the value
|
|
@@ -8022,6 +8607,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8022
8607
|
this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
|
|
8023
8608
|
this.now = config.now ?? (() => Date.now());
|
|
8024
8609
|
this.fileSyncDirectories = config.fileSyncDirectories ?? [];
|
|
8610
|
+
this.fileSyncExactFiles = config.fileSyncExactFiles ?? [];
|
|
8025
8611
|
this.homeDir = config.homeDir ?? homedir6();
|
|
8026
8612
|
this.maxActiveSessions = config.maxActiveSessions;
|
|
8027
8613
|
this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
|
|
@@ -8119,14 +8705,27 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8119
8705
|
getAuthHeader: this.getAuthHeader,
|
|
8120
8706
|
fetchImpl: this.fetchImpl,
|
|
8121
8707
|
allowedDirectories: this.fileSyncDirectories,
|
|
8708
|
+
allowedFiles: this.fileSyncExactFiles,
|
|
8122
8709
|
homeDir: this.homeDir,
|
|
8123
8710
|
ackFailures: this.fileAckFailures,
|
|
8124
8711
|
log: this.log
|
|
8125
8712
|
});
|
|
8126
|
-
|
|
8713
|
+
const vaultResult = await syncVaultFiles({
|
|
8714
|
+
agentId: this.agentId,
|
|
8715
|
+
apiUrl: this.apiUrl,
|
|
8716
|
+
getAuthHeader: this.getAuthHeader,
|
|
8717
|
+
fetchImpl: this.fetchImpl,
|
|
8718
|
+
allowedDirectories: this.fileSyncDirectories,
|
|
8719
|
+
allowedFiles: this.fileSyncExactFiles,
|
|
8720
|
+
homeDir: this.homeDir,
|
|
8721
|
+
outcomes: this.vaultFileOutcomes,
|
|
8722
|
+
log: this.log
|
|
8723
|
+
});
|
|
8724
|
+
const applied = result.applied + vaultResult.applied;
|
|
8725
|
+
this.appliedFileCount += applied;
|
|
8127
8726
|
if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
|
|
8128
8727
|
if (result.opencodeAuthApplied) this.opencodeAuthApplyCount += 1;
|
|
8129
|
-
return
|
|
8728
|
+
return applied;
|
|
8130
8729
|
} catch (err) {
|
|
8131
8730
|
this.log({
|
|
8132
8731
|
level: "error",
|
|
@@ -8538,6 +9137,47 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8538
9137
|
agent: message.opencode_agent ?? void 0,
|
|
8539
9138
|
model: message.opencode_model ?? void 0
|
|
8540
9139
|
};
|
|
9140
|
+
const pinnedProviderLoaded = await isPinnedProviderLoaded(
|
|
9141
|
+
this.port,
|
|
9142
|
+
options.model,
|
|
9143
|
+
this.openCodeClient
|
|
9144
|
+
);
|
|
9145
|
+
if (pinnedProviderLoaded === false) {
|
|
9146
|
+
const dispatchedModel = message.opencode_model;
|
|
9147
|
+
const separator = dispatchedModel?.indexOf("/") ?? -1;
|
|
9148
|
+
const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
|
|
9149
|
+
const providerId = hasProviderAndModel ? dispatchedModel.slice(0, separator) : null;
|
|
9150
|
+
const modelId = hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null;
|
|
9151
|
+
const errorMessage3 = `Provider namespace not loaded on this runner: ${message.opencode_model}. Refused before dispatch; no turn was started.`;
|
|
9152
|
+
this.log({
|
|
9153
|
+
level: "error",
|
|
9154
|
+
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`,
|
|
9155
|
+
conversation_id: conv.id,
|
|
9156
|
+
message_id: message.id
|
|
9157
|
+
});
|
|
9158
|
+
try {
|
|
9159
|
+
await this.markFailed(conv.id, message.id, sessionId, errorMessage3, void 0, {
|
|
9160
|
+
classification: {
|
|
9161
|
+
kind: "model_auth",
|
|
9162
|
+
providerId,
|
|
9163
|
+
modelId,
|
|
9164
|
+
reason: "missing"
|
|
9165
|
+
},
|
|
9166
|
+
errorShape: { name: null, statusCode: null }
|
|
9167
|
+
});
|
|
9168
|
+
this.signalDispatchNotStarted(conv, message, "model_provider_absent");
|
|
9169
|
+
} catch (markErr) {
|
|
9170
|
+
this.log({
|
|
9171
|
+
level: "warn",
|
|
9172
|
+
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)}`,
|
|
9173
|
+
conversation_id: conv.id,
|
|
9174
|
+
message_id: message.id
|
|
9175
|
+
});
|
|
9176
|
+
this.signalDispatchNotStarted(conv, message, "failure_unreported");
|
|
9177
|
+
}
|
|
9178
|
+
await this.repairProviderRegistry(conv.id, message.id);
|
|
9179
|
+
break;
|
|
9180
|
+
}
|
|
8541
9181
|
let opencodeMessageId;
|
|
8542
9182
|
let preDispatchUserIds = null;
|
|
8543
9183
|
try {
|
|
@@ -8891,6 +9531,34 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8891
9531
|
if (state === "failed" && isAbortedTerminalReply(messages, ocId ?? "")) {
|
|
8892
9532
|
const ongoing = await this.isSessionOngoing(sessionId);
|
|
8893
9533
|
if (ongoing === false) {
|
|
9534
|
+
const age = this.absoluteAgeForRow(message.processing_started_at);
|
|
9535
|
+
if (age.tripped && age.anchorMs !== null) {
|
|
9536
|
+
try {
|
|
9537
|
+
await this.markFailed(
|
|
9538
|
+
conv.id,
|
|
9539
|
+
message.id,
|
|
9540
|
+
sessionId,
|
|
9541
|
+
this.absoluteCeilingError(message.id, sessionId, age.anchorMs),
|
|
9542
|
+
void 0,
|
|
9543
|
+
{
|
|
9544
|
+
classification: null,
|
|
9545
|
+
errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
|
|
9546
|
+
}
|
|
9547
|
+
);
|
|
9548
|
+
} catch (err) {
|
|
9549
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
9550
|
+
this.log({
|
|
9551
|
+
level: "warn",
|
|
9552
|
+
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)}`,
|
|
9553
|
+
conversation_id: conv.id,
|
|
9554
|
+
message_id: message.id
|
|
9555
|
+
});
|
|
9556
|
+
return "unresolved";
|
|
9557
|
+
}
|
|
9558
|
+
this.clearRedriveUnresolved(message.id);
|
|
9559
|
+
void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
|
|
9560
|
+
return "settled";
|
|
9561
|
+
}
|
|
8894
9562
|
this.log({
|
|
8895
9563
|
level: "info",
|
|
8896
9564
|
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`,
|
|
@@ -8961,6 +9629,29 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8961
9629
|
message_id: message.id
|
|
8962
9630
|
});
|
|
8963
9631
|
}
|
|
9632
|
+
if (this.absoluteAgeForRow(message.processing_started_at).tripped) {
|
|
9633
|
+
try {
|
|
9634
|
+
await this.markFailed(
|
|
9635
|
+
conv.id,
|
|
9636
|
+
message.id,
|
|
9637
|
+
sessionId,
|
|
9638
|
+
this.absoluteCeilingError(message.id, sessionId, anchorMs)
|
|
9639
|
+
);
|
|
9640
|
+
} catch (err) {
|
|
9641
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
9642
|
+
this.log({
|
|
9643
|
+
level: "warn",
|
|
9644
|
+
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)}`,
|
|
9645
|
+
conversation_id: conv.id,
|
|
9646
|
+
message_id: message.id
|
|
9647
|
+
});
|
|
9648
|
+
const bound = await this.boundRedriveOutcome(conv, message, "reattach");
|
|
9649
|
+
return bound === "abandoned" ? "abandoned" : "unresolved";
|
|
9650
|
+
}
|
|
9651
|
+
this.clearRedriveUnresolved(message.id);
|
|
9652
|
+
void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
|
|
9653
|
+
return "abandoned";
|
|
9654
|
+
}
|
|
8964
9655
|
const title = await this.resolveSessionTitle(sessionId, conv.id);
|
|
8965
9656
|
try {
|
|
8966
9657
|
await this.markProcessing(conv.id, message.id, sessionId, ocId, title);
|
|
@@ -9227,8 +9918,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9227
9918
|
const since = this.redriveOutcomeFailingSince.get(message.id);
|
|
9228
9919
|
if (since === void 0) this.redriveOutcomeFailingSince.set(message.id, now);
|
|
9229
9920
|
const durationTripped = now - (since ?? now) >= this.pausedMaxWaitMs;
|
|
9230
|
-
const
|
|
9231
|
-
const absoluteAgeTripped = !Number.isNaN(parsed) && now - parsed >= ABSOLUTE_MAX_PROCESSING_MS;
|
|
9921
|
+
const absoluteAgeTripped = this.absoluteAgeForRow(message.processing_started_at).tripped;
|
|
9232
9922
|
if (!durationTripped && !absoluteAgeTripped) {
|
|
9233
9923
|
this.signalRedriveOutcomeUnreported(conv, message, outcome);
|
|
9234
9924
|
return "retry";
|
|
@@ -9267,6 +9957,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9267
9957
|
});
|
|
9268
9958
|
return "abandoned";
|
|
9269
9959
|
}
|
|
9960
|
+
/** Row-level guards use the durable server stamp; in-flight guards use processingAnchorMs. */
|
|
9961
|
+
absoluteAgeForRow(stamp) {
|
|
9962
|
+
const anchorMs = stamp ? Date.parse(stamp) : NaN;
|
|
9963
|
+
return Number.isNaN(anchorMs) ? { tripped: false, anchorMs: null } : { tripped: this.now() - anchorMs >= ABSOLUTE_MAX_PROCESSING_MS, anchorMs };
|
|
9964
|
+
}
|
|
9965
|
+
absoluteCeilingError(messageId, sessionId, anchorMs, retainedReason = this.readRetainedSessionError(sessionId)) {
|
|
9966
|
+
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.`;
|
|
9967
|
+
return retainedReason ? `${baseError} ${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}` : baseError;
|
|
9968
|
+
}
|
|
9270
9969
|
/**
|
|
9271
9970
|
* Record one poll outcome toward the re-drive fence's consecutive-identical-
|
|
9272
9971
|
* failure streak (#1348) and return the resulting count. `signature === null`
|
|
@@ -9603,6 +10302,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9603
10302
|
b2AbandonedWithoutAnswer: false,
|
|
9604
10303
|
ambiguousPinnedSinceMs: 0,
|
|
9605
10304
|
ambiguousResolved: false,
|
|
10305
|
+
midFlightPinnedSinceMs: 0,
|
|
10306
|
+
midFlightNotOngoingStreak: 0,
|
|
10307
|
+
midFlightLastStatusCheckMs: 0,
|
|
10308
|
+
midFlightOrphanSignalled: false,
|
|
10309
|
+
midFlightOrphanWithoutAnswer: false,
|
|
10310
|
+
midFlightHeldLogged: false,
|
|
9606
10311
|
sessionStoreFailurePending: false,
|
|
9607
10312
|
neverStartedResolved: false
|
|
9608
10313
|
});
|
|
@@ -9708,10 +10413,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9708
10413
|
opencodeMessageId,
|
|
9709
10414
|
message,
|
|
9710
10415
|
dispatchedAt: this.now(),
|
|
9711
|
-
|
|
9712
|
-
// `dispatchedAt` — so a re-adopted zombie's age reflects the real turn duration
|
|
9713
|
-
// and the ceiling fires on the ORIGINAL turn.
|
|
9714
|
-
processingAnchorMs: processedAtMs,
|
|
10416
|
+
processingAnchorMs: this.absoluteAgeForRow(message.processing_started_at).anchorMs ?? processedAtMs,
|
|
9715
10417
|
deadline: watchDeadlineAnchorMs + this.pausedMaxWaitMs,
|
|
9716
10418
|
// The server row is ALREADY `processing`; do not re-fire markProcessing.
|
|
9717
10419
|
started: true,
|
|
@@ -9745,6 +10447,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9745
10447
|
b2AbandonedWithoutAnswer: false,
|
|
9746
10448
|
ambiguousPinnedSinceMs: 0,
|
|
9747
10449
|
ambiguousResolved: false,
|
|
10450
|
+
midFlightPinnedSinceMs: 0,
|
|
10451
|
+
midFlightNotOngoingStreak: 0,
|
|
10452
|
+
midFlightLastStatusCheckMs: 0,
|
|
10453
|
+
midFlightOrphanSignalled: false,
|
|
10454
|
+
midFlightOrphanWithoutAnswer: false,
|
|
10455
|
+
midFlightHeldLogged: false,
|
|
9748
10456
|
sessionStoreFailurePending: false,
|
|
9749
10457
|
neverStartedResolved: false
|
|
9750
10458
|
});
|
|
@@ -10162,7 +10870,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10162
10870
|
}
|
|
10163
10871
|
const { openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk } = await this.pollInteractions(sessionId, watcher, messages);
|
|
10164
10872
|
if (watcher.generation !== generation) return;
|
|
10873
|
+
const ceilingAttemptsThisTick = /* @__PURE__ */ new Set();
|
|
10165
10874
|
for (const inFlight of [...watcher.inFlight.values()]) {
|
|
10875
|
+
if (ceilingAttemptsThisTick.has(inFlight.evidentMessageId)) continue;
|
|
10166
10876
|
await this.serviceInFlightMessage(
|
|
10167
10877
|
sessionId,
|
|
10168
10878
|
watcher,
|
|
@@ -10171,7 +10881,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10171
10881
|
openQuestions,
|
|
10172
10882
|
openPermissions,
|
|
10173
10883
|
questionsPolledOk,
|
|
10174
|
-
permissionsPolledOk
|
|
10884
|
+
permissionsPolledOk,
|
|
10885
|
+
ceilingAttemptsThisTick
|
|
10175
10886
|
);
|
|
10176
10887
|
}
|
|
10177
10888
|
}
|
|
@@ -10212,13 +10923,117 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10212
10923
|
inFlight.deadline = this.now() + this.pausedMaxWaitMs;
|
|
10213
10924
|
}
|
|
10214
10925
|
}
|
|
10926
|
+
async settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick) {
|
|
10927
|
+
const entries = [...watcher.inFlight.values()];
|
|
10928
|
+
const pastCeiling = entries.filter(
|
|
10929
|
+
(entry) => this.now() - entry.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS
|
|
10930
|
+
);
|
|
10931
|
+
const healthySiblings = entries.length - pastCeiling.length;
|
|
10932
|
+
const landed = [];
|
|
10933
|
+
let allRecordsLanded = true;
|
|
10934
|
+
const retainedReason = this.readRetainedSessionError(sessionId);
|
|
10935
|
+
for (const entry of pastCeiling) {
|
|
10936
|
+
if (!watcher.inFlight.has(entry.evidentMessageId)) continue;
|
|
10937
|
+
ceilingAttemptsThisTick.add(entry.evidentMessageId);
|
|
10938
|
+
const error2 = this.absoluteCeilingError(
|
|
10939
|
+
entry.evidentMessageId,
|
|
10940
|
+
sessionId,
|
|
10941
|
+
entry.processingAnchorMs,
|
|
10942
|
+
retainedReason
|
|
10943
|
+
);
|
|
10944
|
+
this.log({
|
|
10945
|
+
level: "warn",
|
|
10946
|
+
message: error2,
|
|
10947
|
+
conversation_id: watcher.conv.id,
|
|
10948
|
+
message_id: entry.evidentMessageId
|
|
10949
|
+
});
|
|
10950
|
+
try {
|
|
10951
|
+
await this.markFailed(
|
|
10952
|
+
watcher.conv.id,
|
|
10953
|
+
entry.evidentMessageId,
|
|
10954
|
+
sessionId,
|
|
10955
|
+
error2,
|
|
10956
|
+
void 0,
|
|
10957
|
+
{
|
|
10958
|
+
classification: null,
|
|
10959
|
+
errorShape: extractReplyErrorShape(
|
|
10960
|
+
findLastAssistantReplyFor(messages, entry.opencodeMessageId)
|
|
10961
|
+
)
|
|
10962
|
+
}
|
|
10963
|
+
);
|
|
10964
|
+
} catch (err) {
|
|
10965
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
10966
|
+
allRecordsLanded = false;
|
|
10967
|
+
const watched_for_ms = this.now() - entry.processingAnchorMs;
|
|
10968
|
+
if (err instanceof ChannelTerminalError) {
|
|
10969
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10970
|
+
watched_for_ms,
|
|
10971
|
+
interrupt_outcome: "record_terminal"
|
|
10972
|
+
});
|
|
10973
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10974
|
+
continue;
|
|
10975
|
+
}
|
|
10976
|
+
if (this.now() >= entry.deadline) {
|
|
10977
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10978
|
+
watched_for_ms,
|
|
10979
|
+
interrupt_outcome: "record_deadline"
|
|
10980
|
+
});
|
|
10981
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10982
|
+
continue;
|
|
10983
|
+
}
|
|
10984
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10985
|
+
watched_for_ms,
|
|
10986
|
+
interrupt_outcome: "record_retry"
|
|
10987
|
+
});
|
|
10988
|
+
this.log({
|
|
10989
|
+
level: "warn",
|
|
10990
|
+
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)}`,
|
|
10991
|
+
conversation_id: watcher.conv.id,
|
|
10992
|
+
message_id: entry.evidentMessageId
|
|
10993
|
+
});
|
|
10994
|
+
continue;
|
|
10995
|
+
}
|
|
10996
|
+
entry.done = true;
|
|
10997
|
+
landed.push(entry);
|
|
10998
|
+
}
|
|
10999
|
+
if (!allRecordsLanded) {
|
|
11000
|
+
for (const entry of landed) this.removeInFlight(watcher, entry.evidentMessageId);
|
|
11001
|
+
return;
|
|
11002
|
+
}
|
|
11003
|
+
if (healthySiblings > 0) {
|
|
11004
|
+
for (const entry of landed) {
|
|
11005
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
11006
|
+
watched_for_ms: this.now() - entry.processingAnchorMs,
|
|
11007
|
+
interrupt_outcome: "deferred_for_healthy_sibling",
|
|
11008
|
+
healthy_siblings: healthySiblings
|
|
11009
|
+
});
|
|
11010
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
11011
|
+
}
|
|
11012
|
+
return;
|
|
11013
|
+
}
|
|
11014
|
+
const interrupt = await interruptSession(this.port, sessionId, this.openCodeClient);
|
|
11015
|
+
for (const entry of landed) {
|
|
11016
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
11017
|
+
watched_for_ms: this.now() - entry.processingAnchorMs,
|
|
11018
|
+
interrupt_outcome: interrupt.kind
|
|
11019
|
+
});
|
|
11020
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
11021
|
+
}
|
|
11022
|
+
if (interrupt.kind !== "confirmed_stopped") {
|
|
11023
|
+
this.log({
|
|
11024
|
+
level: "warn",
|
|
11025
|
+
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`,
|
|
11026
|
+
conversation_id: watcher.conv.id
|
|
11027
|
+
});
|
|
11028
|
+
}
|
|
11029
|
+
}
|
|
10215
11030
|
/**
|
|
10216
11031
|
* Drive ONE in-flight message's lifecycle from the tick's message snapshot.
|
|
10217
11032
|
* Fires markProcessing on queued→running and markDone on done (each once),
|
|
10218
11033
|
* applies the idle-path re-dispatch guard, and removes the message from the
|
|
10219
11034
|
* in-flight set on completion or timeout.
|
|
10220
11035
|
*/
|
|
10221
|
-
async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk) {
|
|
11036
|
+
async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk, ceilingAttemptsThisTick) {
|
|
10222
11037
|
const conv = watcher.conv;
|
|
10223
11038
|
const state = messageRunState(messages, inFlight.opencodeMessageId);
|
|
10224
11039
|
const id = inFlight.evidentMessageId;
|
|
@@ -10409,7 +11224,11 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10409
11224
|
watcher,
|
|
10410
11225
|
inFlight,
|
|
10411
11226
|
messages,
|
|
10412
|
-
|
|
11227
|
+
composeAbandonmentNote(
|
|
11228
|
+
finish,
|
|
11229
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
11230
|
+
"without-answer"
|
|
11231
|
+
)
|
|
10413
11232
|
);
|
|
10414
11233
|
} else {
|
|
10415
11234
|
await this.settleMessageDone(
|
|
@@ -10468,7 +11287,11 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10468
11287
|
watcher,
|
|
10469
11288
|
inFlight,
|
|
10470
11289
|
messages,
|
|
10471
|
-
|
|
11290
|
+
composeAbandonmentNote(
|
|
11291
|
+
finish,
|
|
11292
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
11293
|
+
"without-answer"
|
|
11294
|
+
)
|
|
10472
11295
|
);
|
|
10473
11296
|
return;
|
|
10474
11297
|
}
|
|
@@ -10562,17 +11385,108 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10562
11385
|
return;
|
|
10563
11386
|
}
|
|
10564
11387
|
}
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
}
|
|
10575
|
-
|
|
11388
|
+
const midFlightPinnedNow = activelyRunning && isMidFlightPinnedRunning(messages, inFlight.opencodeMessageId);
|
|
11389
|
+
if (!midFlightPinnedNow) {
|
|
11390
|
+
if (snapshotReadable) {
|
|
11391
|
+
inFlight.midFlightPinnedSinceMs = 0;
|
|
11392
|
+
inFlight.midFlightNotOngoingStreak = 0;
|
|
11393
|
+
inFlight.midFlightLastStatusCheckMs = 0;
|
|
11394
|
+
inFlight.midFlightOrphanSignalled = false;
|
|
11395
|
+
inFlight.midFlightOrphanWithoutAnswer = false;
|
|
11396
|
+
inFlight.midFlightHeldLogged = false;
|
|
11397
|
+
}
|
|
11398
|
+
} else {
|
|
11399
|
+
if (inFlight.midFlightOrphanSignalled) {
|
|
11400
|
+
const note = composeAbandonmentNote(
|
|
11401
|
+
void 0,
|
|
11402
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
11403
|
+
inFlight.midFlightOrphanWithoutAnswer ? "midflight-without-answer" : "midflight-with-answer"
|
|
11404
|
+
);
|
|
11405
|
+
if (inFlight.midFlightOrphanWithoutAnswer) {
|
|
11406
|
+
await this.settleMessageAbandonedWithoutAnswer(
|
|
11407
|
+
sessionId,
|
|
11408
|
+
watcher,
|
|
11409
|
+
inFlight,
|
|
11410
|
+
messages,
|
|
11411
|
+
note
|
|
11412
|
+
);
|
|
11413
|
+
} else {
|
|
11414
|
+
await this.settleMessageDone(sessionId, watcher, inFlight, messages, note);
|
|
11415
|
+
}
|
|
11416
|
+
return;
|
|
11417
|
+
}
|
|
11418
|
+
if (inFlight.midFlightPinnedSinceMs === 0) {
|
|
11419
|
+
inFlight.midFlightPinnedSinceMs = this.now();
|
|
11420
|
+
}
|
|
11421
|
+
const pinnedForMs = this.now() - inFlight.midFlightPinnedSinceMs;
|
|
11422
|
+
if (pinnedForMs >= MIDFLIGHT_ORPHAN_MIN_PINNED_MS && this.now() - inFlight.midFlightLastStatusCheckMs >= MIDFLIGHT_ORPHAN_RECHECK_MS) {
|
|
11423
|
+
inFlight.midFlightLastStatusCheckMs = this.now();
|
|
11424
|
+
const rootOngoing = await this.isSessionOngoing(sessionId);
|
|
11425
|
+
inFlight.midFlightNotOngoingStreak = rootOngoing === false ? inFlight.midFlightNotOngoingStreak + 1 : 0;
|
|
11426
|
+
if (isMidFlightOrphanConfirmed({
|
|
11427
|
+
pinnedForMs,
|
|
11428
|
+
minPinnedMs: MIDFLIGHT_ORPHAN_MIN_PINNED_MS,
|
|
11429
|
+
notOngoingStreak: inFlight.midFlightNotOngoingStreak,
|
|
11430
|
+
minNotOngoingStreak: MIDFLIGHT_ORPHAN_MIN_NOT_ONGOING_STREAK
|
|
11431
|
+
})) {
|
|
11432
|
+
if (this.sessionStoreHealth?.isUnwritable()) {
|
|
11433
|
+
inFlight.sessionStoreFailurePending = true;
|
|
11434
|
+
void this.postSignal(conv.id, id, "midflight_orphan_resolved", {
|
|
11435
|
+
watched_for_ms: pinnedForMs,
|
|
11436
|
+
...rootOngoing != null ? { root_ongoing: rootOngoing } : {},
|
|
11437
|
+
opencode_message_id: inFlight.opencodeMessageId,
|
|
11438
|
+
run_state: "failed"
|
|
11439
|
+
});
|
|
11440
|
+
await this.settleMessageSessionStoreUnwritable(sessionId, watcher, inFlight);
|
|
11441
|
+
return;
|
|
11442
|
+
}
|
|
11443
|
+
inFlight.midFlightOrphanSignalled = true;
|
|
11444
|
+
const noteOutcome = extractReplyTextForMessage(messages, inFlight.opencodeMessageId) === null ? "midflight-without-answer" : "midflight-with-answer";
|
|
11445
|
+
inFlight.midFlightOrphanWithoutAnswer = noteOutcome === "midflight-without-answer";
|
|
11446
|
+
void this.postSignal(conv.id, id, "midflight_orphan_resolved", {
|
|
11447
|
+
watched_for_ms: pinnedForMs,
|
|
11448
|
+
...rootOngoing != null ? { root_ongoing: rootOngoing } : {},
|
|
11449
|
+
opencode_message_id: inFlight.opencodeMessageId,
|
|
11450
|
+
...inFlight.midFlightOrphanWithoutAnswer ? { run_state: "failed" } : {}
|
|
11451
|
+
});
|
|
11452
|
+
const note = composeAbandonmentNote(
|
|
11453
|
+
void 0,
|
|
11454
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
11455
|
+
noteOutcome
|
|
11456
|
+
);
|
|
11457
|
+
this.log({
|
|
11458
|
+
level: "warn",
|
|
11459
|
+
message: `Message ${id.slice(0, 8)} was pinned mid-flight for ${Math.round(pinnedForMs / 1e3)}s with root_ongoing=${rootOngoing} \u2014 resolving the orphaned turn`,
|
|
11460
|
+
conversation_id: conv.id,
|
|
11461
|
+
message_id: id
|
|
11462
|
+
});
|
|
11463
|
+
if (inFlight.midFlightOrphanWithoutAnswer) {
|
|
11464
|
+
await this.settleMessageAbandonedWithoutAnswer(
|
|
11465
|
+
sessionId,
|
|
11466
|
+
watcher,
|
|
11467
|
+
inFlight,
|
|
11468
|
+
messages,
|
|
11469
|
+
note
|
|
11470
|
+
);
|
|
11471
|
+
} else {
|
|
11472
|
+
await this.settleMessageDone(sessionId, watcher, inFlight, messages, note);
|
|
11473
|
+
}
|
|
11474
|
+
return;
|
|
11475
|
+
}
|
|
11476
|
+
if (rootOngoing === true && !inFlight.midFlightHeldLogged) {
|
|
11477
|
+
inFlight.midFlightHeldLogged = true;
|
|
11478
|
+
const replyShape = describeAbandonedReplyShape(messages, inFlight.opencodeMessageId);
|
|
11479
|
+
this.log({
|
|
11480
|
+
level: "warn",
|
|
11481
|
+
message: `Message ${id.slice(0, 8)} was pinned mid-flight for ${Math.round(pinnedForMs / 1e3)}s with root_ongoing=true \u2014 holding; last tool ${replyShape?.lastToolName ?? "(unknown)"} status ${replyShape?.lastToolStatus ?? "(unknown)"}`,
|
|
11482
|
+
conversation_id: conv.id,
|
|
11483
|
+
message_id: id
|
|
11484
|
+
});
|
|
11485
|
+
}
|
|
11486
|
+
}
|
|
11487
|
+
}
|
|
11488
|
+
if (watcher.inFlight.has(inFlight.evidentMessageId) && activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
11489
|
+
await this.settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick);
|
|
10576
11490
|
return;
|
|
10577
11491
|
}
|
|
10578
11492
|
const heartbeatDue = activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS;
|
|
@@ -10770,13 +11684,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10770
11684
|
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
10771
11685
|
}
|
|
10772
11686
|
/** Report a confirmed b2 abandonment that produced no answer text. */
|
|
10773
|
-
async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages,
|
|
11687
|
+
async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages, error2) {
|
|
10774
11688
|
const conv = watcher.conv;
|
|
10775
|
-
const error2 = composeAbandonmentNote(
|
|
10776
|
-
finish,
|
|
10777
|
-
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
10778
|
-
"without-answer"
|
|
10779
|
-
);
|
|
10780
11689
|
this.anchorDeliveryDeadline(inFlight);
|
|
10781
11690
|
this.log({
|
|
10782
11691
|
level: "warn",
|
|
@@ -11195,6 +12104,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11195
12104
|
}
|
|
11196
12105
|
const restartAborted = state === "failed" && sessionOngoing === false && isAbortedTerminalReply(messages, ocId ?? "");
|
|
11197
12106
|
if (restartAborted) {
|
|
12107
|
+
const age = this.absoluteAgeForRow(row.processing_started_at);
|
|
12108
|
+
if (age.tripped && age.anchorMs !== null) {
|
|
12109
|
+
try {
|
|
12110
|
+
await this.markFailed(
|
|
12111
|
+
row.conversation_id,
|
|
12112
|
+
row.id,
|
|
12113
|
+
sessionId,
|
|
12114
|
+
this.absoluteCeilingError(row.id, sessionId, age.anchorMs),
|
|
12115
|
+
void 0,
|
|
12116
|
+
{
|
|
12117
|
+
classification: null,
|
|
12118
|
+
errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
|
|
12119
|
+
}
|
|
12120
|
+
);
|
|
12121
|
+
} catch (err) {
|
|
12122
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
12123
|
+
this.log({
|
|
12124
|
+
level: "warn",
|
|
12125
|
+
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)}`,
|
|
12126
|
+
conversation_id: row.conversation_id,
|
|
12127
|
+
message_id: row.id
|
|
12128
|
+
});
|
|
12129
|
+
return;
|
|
12130
|
+
}
|
|
12131
|
+
void this.postSignal(row.conversation_id, row.id, "redrive_ceiling_refused");
|
|
12132
|
+
return;
|
|
12133
|
+
}
|
|
11198
12134
|
this.log({
|
|
11199
12135
|
level: "info",
|
|
11200
12136
|
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`,
|
|
@@ -11740,7 +12676,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11740
12676
|
source_message_id: row.source_message_id,
|
|
11741
12677
|
slack_user_id: row.slack_user_id,
|
|
11742
12678
|
attachments: row.attachments ?? null,
|
|
11743
|
-
opencode_message_id: row.opencode_message_id
|
|
12679
|
+
opencode_message_id: row.opencode_message_id,
|
|
12680
|
+
processing_started_at: row.processing_started_at
|
|
11744
12681
|
};
|
|
11745
12682
|
}
|
|
11746
12683
|
/**
|
|
@@ -13260,7 +14197,7 @@ Port ${port} is already in use.`));
|
|
|
13260
14197
|
}
|
|
13261
14198
|
|
|
13262
14199
|
// src/lib/runner-credentials.ts
|
|
13263
|
-
import { chmodSync as chmodSync2, writeFileSync as
|
|
14200
|
+
import { chmodSync as chmodSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13264
14201
|
import { spawn as spawn5 } from "node:child_process";
|
|
13265
14202
|
var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
|
|
13266
14203
|
var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
|
|
@@ -13503,8 +14440,8 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
13503
14440
|
}
|
|
13504
14441
|
try {
|
|
13505
14442
|
env.GIT_CONFIG_GLOBAL = GIT_CONFIG_GLOBAL;
|
|
13506
|
-
|
|
13507
|
-
|
|
14443
|
+
writeFileSync5(GIT_CONFIG_GLOBAL, "");
|
|
14444
|
+
writeFileSync5(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
|
|
13508
14445
|
chmodSync2(GIT_CREDENTIAL_HELPER, 448);
|
|
13509
14446
|
const config = [
|
|
13510
14447
|
["user.name", env.GIT_USER_NAME ?? "evident-bot"],
|
|
@@ -13533,11 +14470,27 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
13533
14470
|
);
|
|
13534
14471
|
});
|
|
13535
14472
|
}
|
|
13536
|
-
|
|
13537
|
-
// src/lib/opencode/config-overlay.ts
|
|
13538
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
13539
|
-
import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
|
|
13540
|
-
import { isAbsolute as isAbsolute3, join as
|
|
14473
|
+
|
|
14474
|
+
// src/lib/opencode/config-overlay.ts
|
|
14475
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
14476
|
+
import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
|
|
14477
|
+
import { isAbsolute as isAbsolute3, join as join12, resolve as resolve4 } from "node:path";
|
|
14478
|
+
function parseConfigContent(content) {
|
|
14479
|
+
if (!content?.trim()) return {};
|
|
14480
|
+
const parsed = JSON.parse(content);
|
|
14481
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
14482
|
+
throw new Error("OPENCODE_CONFIG_CONTENT must contain a JSON object");
|
|
14483
|
+
}
|
|
14484
|
+
return parsed;
|
|
14485
|
+
}
|
|
14486
|
+
function mergeManagedOpenCodeConfigContent(content, managedProviders) {
|
|
14487
|
+
const config = parseConfigContent(content);
|
|
14488
|
+
const provider = typeof config.provider === "object" && config.provider !== null && !Array.isArray(config.provider) ? { ...config.provider } : {};
|
|
14489
|
+
for (const namespace of CREDENTIAL_PROXY_NATIVE_NAMESPACES) {
|
|
14490
|
+
delete provider[namespace];
|
|
14491
|
+
}
|
|
14492
|
+
return JSON.stringify({ ...config, provider: { ...provider, ...managedProviders } });
|
|
14493
|
+
}
|
|
13541
14494
|
function isFile(filePath) {
|
|
13542
14495
|
return existsSync2(filePath) && statSync6(filePath).isFile();
|
|
13543
14496
|
}
|
|
@@ -13551,7 +14504,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
13551
14504
|
return;
|
|
13552
14505
|
}
|
|
13553
14506
|
const source = isAbsolute3(overlayPath) ? overlayPath : resolve4(cwd, overlayPath);
|
|
13554
|
-
const target = isFile(
|
|
14507
|
+
const target = isFile(join12(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
|
|
13555
14508
|
if (!isFile(source)) {
|
|
13556
14509
|
log3(
|
|
13557
14510
|
`RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
|
|
@@ -13559,7 +14512,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
13559
14512
|
);
|
|
13560
14513
|
return;
|
|
13561
14514
|
}
|
|
13562
|
-
copyFileSync(source,
|
|
14515
|
+
copyFileSync(source, join12(cwd, target));
|
|
13563
14516
|
try {
|
|
13564
14517
|
execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
|
|
13565
14518
|
stdio: "ignore"
|
|
@@ -13568,11 +14521,164 @@ function applyRunnerOpenCodeConfig({
|
|
|
13568
14521
|
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
13569
14522
|
log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
|
|
13570
14523
|
}
|
|
13571
|
-
log3(`Applied runner OpenCode config ${source} to ${
|
|
14524
|
+
log3(`Applied runner OpenCode config ${source} to ${join12(cwd, target)}`);
|
|
14525
|
+
}
|
|
14526
|
+
|
|
14527
|
+
// src/lib/opencode/runtime-provenance.ts
|
|
14528
|
+
import { readFileSync as readFileSync8, readdirSync } from "node:fs";
|
|
14529
|
+
import { homedir as homedir7 } from "node:os";
|
|
14530
|
+
import { join as join13 } from "node:path";
|
|
14531
|
+
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
|
|
14532
|
+
var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
|
|
14533
|
+
function errorKind(error2) {
|
|
14534
|
+
if (!(error2 instanceof Error)) return "unknown error";
|
|
14535
|
+
const code = "code" in error2 && typeof error2.code === "string" ? ` (${error2.code})` : "";
|
|
14536
|
+
return `${error2.name}${code}`;
|
|
14537
|
+
}
|
|
14538
|
+
function parseRegistryPluginSpec(value) {
|
|
14539
|
+
const separator = value.lastIndexOf("@");
|
|
14540
|
+
if (separator <= 0) return null;
|
|
14541
|
+
const name = value.slice(0, separator);
|
|
14542
|
+
const spec = value.slice(separator + 1);
|
|
14543
|
+
if (!PACKAGE_NAME.test(name) || !REGISTRY_SPEC.test(spec)) return null;
|
|
14544
|
+
return { name, spec };
|
|
14545
|
+
}
|
|
14546
|
+
function parseJsonc(value) {
|
|
14547
|
+
let output = "";
|
|
14548
|
+
let inString = false;
|
|
14549
|
+
let escaped = false;
|
|
14550
|
+
let lineComment = false;
|
|
14551
|
+
let blockComment = false;
|
|
14552
|
+
for (let index = 0; index < value.length; index++) {
|
|
14553
|
+
const character = value[index];
|
|
14554
|
+
const next = value[index + 1];
|
|
14555
|
+
if (lineComment) {
|
|
14556
|
+
if (character === "\n") {
|
|
14557
|
+
lineComment = false;
|
|
14558
|
+
output += character;
|
|
14559
|
+
}
|
|
14560
|
+
continue;
|
|
14561
|
+
}
|
|
14562
|
+
if (blockComment) {
|
|
14563
|
+
if (character === "*" && next === "/") {
|
|
14564
|
+
blockComment = false;
|
|
14565
|
+
index++;
|
|
14566
|
+
} else if (character === "\n") {
|
|
14567
|
+
output += character;
|
|
14568
|
+
}
|
|
14569
|
+
continue;
|
|
14570
|
+
}
|
|
14571
|
+
if (inString) {
|
|
14572
|
+
output += character;
|
|
14573
|
+
if (escaped) escaped = false;
|
|
14574
|
+
else if (character === "\\") escaped = true;
|
|
14575
|
+
else if (character === '"') inString = false;
|
|
14576
|
+
continue;
|
|
14577
|
+
}
|
|
14578
|
+
if (character === '"') {
|
|
14579
|
+
inString = true;
|
|
14580
|
+
output += character;
|
|
14581
|
+
} else if (character === "/" && next === "/") {
|
|
14582
|
+
lineComment = true;
|
|
14583
|
+
index++;
|
|
14584
|
+
} else if (character === "/" && next === "*") {
|
|
14585
|
+
blockComment = true;
|
|
14586
|
+
index++;
|
|
14587
|
+
} else if (character === ",") {
|
|
14588
|
+
let lookahead = index + 1;
|
|
14589
|
+
while (/\s/.test(value[lookahead] ?? "")) lookahead++;
|
|
14590
|
+
if (value[lookahead] !== "]" && value[lookahead] !== "}") output += character;
|
|
14591
|
+
} else {
|
|
14592
|
+
output += character;
|
|
14593
|
+
}
|
|
14594
|
+
}
|
|
14595
|
+
return JSON.parse(output);
|
|
14596
|
+
}
|
|
14597
|
+
function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
|
|
14598
|
+
return join13(env.XDG_CACHE_HOME ?? join13(homeDir, ".cache"), "opencode");
|
|
14599
|
+
}
|
|
14600
|
+
function readConfiguredPluginSpecs({
|
|
14601
|
+
cwd = process.cwd(),
|
|
14602
|
+
log: log3
|
|
14603
|
+
}) {
|
|
14604
|
+
let config;
|
|
14605
|
+
try {
|
|
14606
|
+
try {
|
|
14607
|
+
config = parseJsonc(readFileSync8(join13(cwd, "opencode.jsonc"), "utf8"));
|
|
14608
|
+
} catch (error2) {
|
|
14609
|
+
if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
|
|
14610
|
+
config = parseJsonc(readFileSync8(join13(cwd, "opencode.json"), "utf8"));
|
|
14611
|
+
}
|
|
14612
|
+
} catch (error2) {
|
|
14613
|
+
log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
|
|
14614
|
+
return [];
|
|
14615
|
+
}
|
|
14616
|
+
if (typeof config !== "object" || config === null || !("plugin" in config)) return [];
|
|
14617
|
+
const plugins = config.plugin;
|
|
14618
|
+
if (!Array.isArray(plugins)) {
|
|
14619
|
+
log3("Could not read OpenCode plugin configuration: plugin is not an array");
|
|
14620
|
+
return [];
|
|
14621
|
+
}
|
|
14622
|
+
const configured = plugins.filter(
|
|
14623
|
+
(plugin) => typeof plugin === "string" && parseRegistryPluginSpec(plugin) !== null
|
|
14624
|
+
);
|
|
14625
|
+
if (configured.length !== plugins.length) {
|
|
14626
|
+
log3(
|
|
14627
|
+
`Skipped ${plugins.length - configured.length} non-registry OpenCode plugin spec(s); their values were not recorded`
|
|
14628
|
+
);
|
|
14629
|
+
}
|
|
14630
|
+
return configured;
|
|
14631
|
+
}
|
|
14632
|
+
function resolveInstalledPluginVersions({
|
|
14633
|
+
cacheRoot,
|
|
14634
|
+
env = process.env,
|
|
14635
|
+
homeDir = homedir7(),
|
|
14636
|
+
log: log3
|
|
14637
|
+
}) {
|
|
14638
|
+
const packagesRoot = join13(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
|
|
14639
|
+
let directories;
|
|
14640
|
+
try {
|
|
14641
|
+
directories = readdirSync(packagesRoot, { withFileTypes: true });
|
|
14642
|
+
} catch (error2) {
|
|
14643
|
+
if ("code" in Object(error2) && Object(error2).code === "ENOENT") return [];
|
|
14644
|
+
log3(`Could not inspect the OpenCode plugin cache: ${errorKind(error2)}`);
|
|
14645
|
+
return [];
|
|
14646
|
+
}
|
|
14647
|
+
const plugins = [];
|
|
14648
|
+
let skipped = 0;
|
|
14649
|
+
for (const directory of directories) {
|
|
14650
|
+
if (!directory.isDirectory()) continue;
|
|
14651
|
+
const parsed = parseRegistryPluginSpec(directory.name);
|
|
14652
|
+
if (parsed === null) {
|
|
14653
|
+
skipped++;
|
|
14654
|
+
continue;
|
|
14655
|
+
}
|
|
14656
|
+
let resolvedVersion = null;
|
|
14657
|
+
try {
|
|
14658
|
+
const packageJson = JSON.parse(
|
|
14659
|
+
readFileSync8(
|
|
14660
|
+
join13(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
|
|
14661
|
+
"utf8"
|
|
14662
|
+
)
|
|
14663
|
+
);
|
|
14664
|
+
if (typeof packageJson.version === "string" && REGISTRY_SPEC.test(packageJson.version)) {
|
|
14665
|
+
resolvedVersion = packageJson.version;
|
|
14666
|
+
} else {
|
|
14667
|
+
log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: invalid version`);
|
|
14668
|
+
}
|
|
14669
|
+
} catch (error2) {
|
|
14670
|
+
log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: ${errorKind(error2)}`);
|
|
14671
|
+
}
|
|
14672
|
+
plugins.push({ ...parsed, resolvedVersion });
|
|
14673
|
+
}
|
|
14674
|
+
if (skipped > 0) {
|
|
14675
|
+
log3(`Skipped ${skipped} non-registry OpenCode package cache entry; its value was not recorded`);
|
|
14676
|
+
}
|
|
14677
|
+
return plugins;
|
|
13572
14678
|
}
|
|
13573
14679
|
|
|
13574
14680
|
// src/lib/credential-sync.ts
|
|
13575
|
-
import { renameSync, writeFileSync as
|
|
14681
|
+
import { renameSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
13576
14682
|
var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
|
|
13577
14683
|
var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
|
|
13578
14684
|
var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
|
|
@@ -13606,7 +14712,7 @@ function writeMarker(markerPath, outcomes, log3) {
|
|
|
13606
14712
|
`;
|
|
13607
14713
|
const temporaryPath = `${markerPath}.tmp`;
|
|
13608
14714
|
try {
|
|
13609
|
-
|
|
14715
|
+
writeFileSync6(temporaryPath, body, { mode: 384 });
|
|
13610
14716
|
renameSync(temporaryPath, markerPath);
|
|
13611
14717
|
} catch (error2) {
|
|
13612
14718
|
log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
|
|
@@ -13806,6 +14912,255 @@ function createCredentialSync({
|
|
|
13806
14912
|
};
|
|
13807
14913
|
}
|
|
13808
14914
|
|
|
14915
|
+
// src/lib/managed-inference-setup.ts
|
|
14916
|
+
var defaultStore = {
|
|
14917
|
+
get: getManagedInferenceCredential,
|
|
14918
|
+
set: setManagedInferenceCredential,
|
|
14919
|
+
clear: clearManagedInferenceCredential
|
|
14920
|
+
};
|
|
14921
|
+
function endpoint(options, operation) {
|
|
14922
|
+
return `${options.apiUrl}/runners/${options.agentId}/managed-credentials/${operation}`;
|
|
14923
|
+
}
|
|
14924
|
+
function requestHeaders(options) {
|
|
14925
|
+
return { Authorization: options.getAuthHeader(), "Content-Type": "application/json" };
|
|
14926
|
+
}
|
|
14927
|
+
function request2(options) {
|
|
14928
|
+
return withRequestTimeout(options.fetchImpl ?? fetch, REQUEST_TIMEOUT_MS);
|
|
14929
|
+
}
|
|
14930
|
+
function safeFailure(operation, status2) {
|
|
14931
|
+
return status2 === void 0 ? `${operation} could not reach the managed credential service` : `${operation} returned HTTP ${status2}`;
|
|
14932
|
+
}
|
|
14933
|
+
function logTransportFailure(options, operation, error2, authorization) {
|
|
14934
|
+
let detail = error2 instanceof Error ? error2.message : String(error2);
|
|
14935
|
+
const credential = authorization.split(/\s+/, 2)[1];
|
|
14936
|
+
for (const secret of [authorization, credential]) {
|
|
14937
|
+
if (secret) detail = detail.replaceAll(secret, "[redacted]");
|
|
14938
|
+
}
|
|
14939
|
+
options.log(
|
|
14940
|
+
`MANAGED-INFERENCE-TRANSPORT-FAILED: operation=${operation} detail=${redactDiagnosticText(detail)}`,
|
|
14941
|
+
"debug"
|
|
14942
|
+
);
|
|
14943
|
+
}
|
|
14944
|
+
async function responseJson(response) {
|
|
14945
|
+
try {
|
|
14946
|
+
const value = await response.json();
|
|
14947
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
14948
|
+
} catch (error2) {
|
|
14949
|
+
return null;
|
|
14950
|
+
}
|
|
14951
|
+
}
|
|
14952
|
+
function readSetup(value) {
|
|
14953
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
14954
|
+
const setup = value;
|
|
14955
|
+
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) {
|
|
14956
|
+
return null;
|
|
14957
|
+
}
|
|
14958
|
+
return setup;
|
|
14959
|
+
}
|
|
14960
|
+
function clearLocalSetup(options) {
|
|
14961
|
+
const env = options.env ?? process.env;
|
|
14962
|
+
const store = options.store ?? defaultStore;
|
|
14963
|
+
const stored = store.get(options.agentId);
|
|
14964
|
+
store.clear(options.agentId);
|
|
14965
|
+
const token = env.EVIDENT_PROXY_TOKEN;
|
|
14966
|
+
if (token !== void 0 && token === stored?.token) {
|
|
14967
|
+
delete env.EVIDENT_PROXY_TOKEN;
|
|
14968
|
+
} else if (token !== void 0) {
|
|
14969
|
+
options.log(
|
|
14970
|
+
`MANAGED-INFERENCE-CLEANUP-PRESERVED-EXTERNAL-TOKEN: runner=${options.agentId}`,
|
|
14971
|
+
"debug"
|
|
14972
|
+
);
|
|
14973
|
+
}
|
|
14974
|
+
}
|
|
14975
|
+
async function discoverManagedInferenceSetup(options) {
|
|
14976
|
+
const headers = requestHeaders(options);
|
|
14977
|
+
let response;
|
|
14978
|
+
try {
|
|
14979
|
+
response = await request2(options)(endpoint(options, "current"), {
|
|
14980
|
+
headers
|
|
14981
|
+
});
|
|
14982
|
+
} catch (error2) {
|
|
14983
|
+
logTransportFailure(options, "discovery", error2, headers.Authorization);
|
|
14984
|
+
return { kind: "failed", reason: safeFailure("Managed setup discovery") };
|
|
14985
|
+
}
|
|
14986
|
+
if (response.status === 404) {
|
|
14987
|
+
clearLocalSetup(options);
|
|
14988
|
+
return { kind: "none" };
|
|
14989
|
+
}
|
|
14990
|
+
if (!response.ok) {
|
|
14991
|
+
return { kind: "failed", reason: safeFailure("Managed setup discovery", response.status) };
|
|
14992
|
+
}
|
|
14993
|
+
const body = await responseJson(response);
|
|
14994
|
+
const setup = readSetup(body?.setup);
|
|
14995
|
+
return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed setup discovery returned an invalid response" };
|
|
14996
|
+
}
|
|
14997
|
+
async function enrollManagedInferenceSetup(options) {
|
|
14998
|
+
const response = await post(options, "enroll", {});
|
|
14999
|
+
if (!response) {
|
|
15000
|
+
return { kind: "failed", reason: safeFailure("Managed credential enrollment") };
|
|
15001
|
+
}
|
|
15002
|
+
if (response.status === 404) {
|
|
15003
|
+
return { kind: "none" };
|
|
15004
|
+
}
|
|
15005
|
+
const body = await responseJson(response);
|
|
15006
|
+
if (!response.ok) {
|
|
15007
|
+
const serverError = body?.error;
|
|
15008
|
+
return {
|
|
15009
|
+
kind: "failed",
|
|
15010
|
+
reason: typeof serverError === "string" && serverError.trim() ? redactDiagnosticText(serverError).slice(0, 500) : safeFailure("Managed credential enrollment", response.status)
|
|
15011
|
+
};
|
|
15012
|
+
}
|
|
15013
|
+
const setup = readSetup(body?.setup);
|
|
15014
|
+
return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed credential enrollment returned an invalid response" };
|
|
15015
|
+
}
|
|
15016
|
+
async function post(options, operation, body) {
|
|
15017
|
+
const headers = requestHeaders(options);
|
|
15018
|
+
try {
|
|
15019
|
+
return await request2(options)(endpoint(options, operation), {
|
|
15020
|
+
method: "POST",
|
|
15021
|
+
headers,
|
|
15022
|
+
body: JSON.stringify(body)
|
|
15023
|
+
});
|
|
15024
|
+
} catch (error2) {
|
|
15025
|
+
logTransportFailure(options, operation, error2, headers.Authorization);
|
|
15026
|
+
return null;
|
|
15027
|
+
}
|
|
15028
|
+
}
|
|
15029
|
+
function applyCredential(options, credential) {
|
|
15030
|
+
const env = options.env ?? process.env;
|
|
15031
|
+
let merged;
|
|
15032
|
+
if (options.autoConfigureCredentialsProxy) {
|
|
15033
|
+
const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
|
|
15034
|
+
inferenceBaseUrl: credential.inferenceBaseUrl,
|
|
15035
|
+
modelDescriptors: credential.modelDescriptors
|
|
15036
|
+
});
|
|
15037
|
+
if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
|
|
15038
|
+
return "Managed setup contains unsupported provider descriptors";
|
|
15039
|
+
}
|
|
15040
|
+
try {
|
|
15041
|
+
merged = mergeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT, config.provider);
|
|
15042
|
+
} catch (error2) {
|
|
15043
|
+
return "OPENCODE_CONFIG_CONTENT is malformed; managed providers were not installed";
|
|
15044
|
+
}
|
|
15045
|
+
}
|
|
15046
|
+
try {
|
|
15047
|
+
(options.store ?? defaultStore).set(options.agentId, credential);
|
|
15048
|
+
} catch (error2) {
|
|
15049
|
+
const detail = (error2 instanceof Error ? error2.message : String(error2)).replaceAll(
|
|
15050
|
+
credential.token,
|
|
15051
|
+
"[redacted]"
|
|
15052
|
+
);
|
|
15053
|
+
options.log(
|
|
15054
|
+
`MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation} detail=${redactDiagnosticText(detail)}`,
|
|
15055
|
+
"warn"
|
|
15056
|
+
);
|
|
15057
|
+
return "Managed credentials could not be written to the protected store";
|
|
15058
|
+
}
|
|
15059
|
+
env.EVIDENT_PROXY_TOKEN = credential.token;
|
|
15060
|
+
if (merged !== void 0) env.OPENCODE_CONFIG_CONTENT = merged;
|
|
15061
|
+
return null;
|
|
15062
|
+
}
|
|
15063
|
+
async function reportManagedInferenceApplyFailure(options, failure) {
|
|
15064
|
+
const response = await post(options, "report-failure", {
|
|
15065
|
+
generation: failure.generation,
|
|
15066
|
+
token_id: failure.tokenId
|
|
15067
|
+
});
|
|
15068
|
+
if (!response?.ok) {
|
|
15069
|
+
options.log(
|
|
15070
|
+
`MANAGED-INFERENCE-REPORT-FAILED: runner=${options.agentId} generation=${failure.generation}${response ? ` status=${response.status}` : ""}`,
|
|
15071
|
+
"warn"
|
|
15072
|
+
);
|
|
15073
|
+
}
|
|
15074
|
+
}
|
|
15075
|
+
async function reaffirmStored(options, credential) {
|
|
15076
|
+
const response = await post(options, "reaffirm", {
|
|
15077
|
+
generation: credential.generation,
|
|
15078
|
+
token_id: credential.tokenId
|
|
15079
|
+
});
|
|
15080
|
+
if (!response?.ok) {
|
|
15081
|
+
return {
|
|
15082
|
+
kind: "failed",
|
|
15083
|
+
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")
|
|
15084
|
+
};
|
|
15085
|
+
}
|
|
15086
|
+
const body = await responseJson(response);
|
|
15087
|
+
const setup = readSetup(body?.setup);
|
|
15088
|
+
if (!setup || setup.generation !== credential.generation || setup.token_id !== credential.tokenId) {
|
|
15089
|
+
return { kind: "failed", reason: "Managed credential reaffirmation returned a mismatch" };
|
|
15090
|
+
}
|
|
15091
|
+
const failure = applyCredential(options, credential);
|
|
15092
|
+
return failure ? { kind: "failed", reason: failure } : { kind: "ready", generation: credential.generation, tokenId: credential.tokenId };
|
|
15093
|
+
}
|
|
15094
|
+
async function prepareManagedInferenceSetup(options, discovery, health) {
|
|
15095
|
+
if (health.authFailed) {
|
|
15096
|
+
return {
|
|
15097
|
+
kind: "failed",
|
|
15098
|
+
reason: "OpenCode authentication failed; managed credentials were not rotated"
|
|
15099
|
+
};
|
|
15100
|
+
}
|
|
15101
|
+
const stored = (options.store ?? defaultStore).get(options.agentId);
|
|
15102
|
+
if (health.healthy) {
|
|
15103
|
+
if (!stored) {
|
|
15104
|
+
return {
|
|
15105
|
+
kind: "failed",
|
|
15106
|
+
reason: "Managed credentials are missing for the serving OpenCode process; restart required"
|
|
15107
|
+
};
|
|
15108
|
+
}
|
|
15109
|
+
return reaffirmStored(options, stored);
|
|
15110
|
+
}
|
|
15111
|
+
if (discovery.setup.acknowledged_at !== null && stored?.generation === discovery.setup.generation && stored.tokenId === discovery.setup.token_id) {
|
|
15112
|
+
return reaffirmStored(options, stored);
|
|
15113
|
+
}
|
|
15114
|
+
const response = await post(options, "bootstrap", { generation: discovery.setup.generation });
|
|
15115
|
+
if (!response?.ok) {
|
|
15116
|
+
return {
|
|
15117
|
+
kind: "failed",
|
|
15118
|
+
reason: response ? safeFailure("Managed credential bootstrap", response.status) : safeFailure("Managed credential bootstrap")
|
|
15119
|
+
};
|
|
15120
|
+
}
|
|
15121
|
+
const body = await responseJson(response);
|
|
15122
|
+
const setup = readSetup(body?.setup);
|
|
15123
|
+
const token = body?.inference_token;
|
|
15124
|
+
const inferenceBaseUrl = body?.inference_base_url;
|
|
15125
|
+
const modelDescriptors = body?.model_descriptors;
|
|
15126
|
+
if (!setup || setup.generation !== discovery.setup.generation || typeof setup.token_id !== "string" || typeof token !== "string" || typeof inferenceBaseUrl !== "string" || !Array.isArray(modelDescriptors) || !modelDescriptors.every(
|
|
15127
|
+
(descriptor) => typeof descriptor === "object" && descriptor !== null && typeof descriptor.id === "string" && typeof descriptor.provider === "string"
|
|
15128
|
+
)) {
|
|
15129
|
+
return { kind: "failed", reason: "Managed credential bootstrap returned an invalid response" };
|
|
15130
|
+
}
|
|
15131
|
+
const credential = {
|
|
15132
|
+
generation: setup.generation,
|
|
15133
|
+
tokenId: setup.token_id,
|
|
15134
|
+
token,
|
|
15135
|
+
inferenceBaseUrl,
|
|
15136
|
+
modelDescriptors
|
|
15137
|
+
};
|
|
15138
|
+
const failure = applyCredential(options, credential);
|
|
15139
|
+
if (failure) {
|
|
15140
|
+
await reportManagedInferenceApplyFailure(options, credential);
|
|
15141
|
+
return { kind: "failed", reason: failure };
|
|
15142
|
+
}
|
|
15143
|
+
return { kind: "ready", generation: credential.generation, tokenId: credential.tokenId };
|
|
15144
|
+
}
|
|
15145
|
+
async function acknowledgeManagedInferenceSetup(options, preparation) {
|
|
15146
|
+
const response = await post(options, "acknowledge", {
|
|
15147
|
+
generation: preparation.generation,
|
|
15148
|
+
token_id: preparation.tokenId,
|
|
15149
|
+
config_validated: options.autoConfigureCredentialsProxy === true
|
|
15150
|
+
});
|
|
15151
|
+
if (!response?.ok) {
|
|
15152
|
+
throw new Error(
|
|
15153
|
+
response ? safeFailure("Managed credential acknowledgment", response.status) : safeFailure("Managed credential acknowledgment")
|
|
15154
|
+
);
|
|
15155
|
+
}
|
|
15156
|
+
const body = await responseJson(response);
|
|
15157
|
+
const setup = readSetup(body?.setup);
|
|
15158
|
+
if (!setup || setup.generation !== preparation.generation || setup.token_id !== preparation.tokenId) {
|
|
15159
|
+
throw new Error("Managed credential acknowledgment returned a mismatch");
|
|
15160
|
+
}
|
|
15161
|
+
return response.status === 200 && setup.status === "acknowledged" ? "acknowledged" : "pending";
|
|
15162
|
+
}
|
|
15163
|
+
|
|
13809
15164
|
// src/commands/run.ts
|
|
13810
15165
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
13811
15166
|
var MAX_DISCONNECT_REASON_LENGTH = 200;
|
|
@@ -13839,33 +15194,49 @@ function resolveLogLevel(options) {
|
|
|
13839
15194
|
}
|
|
13840
15195
|
return "info";
|
|
13841
15196
|
}
|
|
13842
|
-
function
|
|
13843
|
-
const
|
|
15197
|
+
function resolveFileSyncEntry(entry, homeDir, flagName, labels) {
|
|
15198
|
+
const trimmed = entry.trim();
|
|
15199
|
+
if (trimmed === "") {
|
|
15200
|
+
throw new Error(`${flagName} requires a ${labels.noun} path (got an empty value)`);
|
|
15201
|
+
}
|
|
15202
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join14(homeDir, trimmed.slice(2)) : trimmed;
|
|
15203
|
+
if (!isAbsolute4(expanded)) {
|
|
15204
|
+
throw new Error(`${flagName} requires an absolute ${labels.noun} path; got "${entry}"`);
|
|
15205
|
+
}
|
|
15206
|
+
const normalized = resolvePath2(expanded);
|
|
15207
|
+
if (parse(normalized).root === normalized) {
|
|
15208
|
+
throw new Error(
|
|
15209
|
+
`${flagName} will not allow-list the filesystem root ("${entry}"); name the specific ${labels.noun} the credentials belong in (for example ${labels.rootExample})`
|
|
15210
|
+
);
|
|
15211
|
+
}
|
|
15212
|
+
return normalized;
|
|
15213
|
+
}
|
|
15214
|
+
function resolveFileSyncAllowList(raw, homeDir, flagName, labels) {
|
|
15215
|
+
const paths = [];
|
|
13844
15216
|
for (const entry of raw ?? []) {
|
|
13845
|
-
const
|
|
13846
|
-
if (
|
|
13847
|
-
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
13848
|
-
}
|
|
13849
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join12(homeDir, trimmed.slice(2)) : trimmed;
|
|
13850
|
-
if (!isAbsolute4(expanded)) {
|
|
13851
|
-
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
13852
|
-
}
|
|
13853
|
-
const normalized = resolvePath2(expanded);
|
|
13854
|
-
if (parse(normalized).root === normalized) {
|
|
13855
|
-
throw new Error(
|
|
13856
|
-
`--enable-file-sync-to will not allow-list the filesystem root ("${entry}"); name the specific directory the credentials belong in (for example ~/.claude)`
|
|
13857
|
-
);
|
|
13858
|
-
}
|
|
13859
|
-
if (!directories.includes(normalized)) {
|
|
13860
|
-
directories.push(normalized);
|
|
13861
|
-
}
|
|
15217
|
+
const normalized = resolveFileSyncEntry(entry, homeDir, flagName, labels);
|
|
15218
|
+
if (!paths.includes(normalized)) paths.push(normalized);
|
|
13862
15219
|
}
|
|
13863
|
-
if (
|
|
15220
|
+
if (paths.length > MAX_FILE_SYNC_DIRECTORIES) {
|
|
13864
15221
|
throw new Error(
|
|
13865
|
-
|
|
15222
|
+
`${flagName} accepts at most ${MAX_FILE_SYNC_DIRECTORIES} ${labels.plural}; got ${paths.length}`
|
|
13866
15223
|
);
|
|
13867
15224
|
}
|
|
13868
|
-
return
|
|
15225
|
+
return paths;
|
|
15226
|
+
}
|
|
15227
|
+
function resolveFileSyncDirectories(raw, homeDir) {
|
|
15228
|
+
return resolveFileSyncAllowList(raw, homeDir, "--enable-file-sync-to", {
|
|
15229
|
+
noun: "directory",
|
|
15230
|
+
plural: "directories",
|
|
15231
|
+
rootExample: "~/.claude"
|
|
15232
|
+
});
|
|
15233
|
+
}
|
|
15234
|
+
function resolveFileSyncExactFiles(raw, homeDir) {
|
|
15235
|
+
return resolveFileSyncAllowList(raw, homeDir, "--enable-file-sync-to-exact-file", {
|
|
15236
|
+
noun: "file",
|
|
15237
|
+
plural: "files",
|
|
15238
|
+
rootExample: "~/.local/share/opencode/mcp-auth.json"
|
|
15239
|
+
});
|
|
13869
15240
|
}
|
|
13870
15241
|
var DEFAULT_OPENCODE_START_TIMEOUT_SECONDS = 180;
|
|
13871
15242
|
var MAX_OPENCODE_START_TIMEOUT_SECONDS = 3600;
|
|
@@ -14001,7 +15372,7 @@ function logActivity(state, entry) {
|
|
|
14001
15372
|
}
|
|
14002
15373
|
function reportSessionDbRecovery(state) {
|
|
14003
15374
|
try {
|
|
14004
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
15375
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir8(), env: process.env });
|
|
14005
15376
|
for (const record of report.records) {
|
|
14006
15377
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
14007
15378
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -14082,7 +15453,7 @@ async function importOpenCodeV2CredentialsForState(state) {
|
|
|
14082
15453
|
}
|
|
14083
15454
|
const result = await importOpenCodeV2Credentials({
|
|
14084
15455
|
request: client.request,
|
|
14085
|
-
homeDir:
|
|
15456
|
+
homeDir: homedir8(),
|
|
14086
15457
|
directory,
|
|
14087
15458
|
guard: state.opencodeV2CredentialImportGuard,
|
|
14088
15459
|
log: (message, level = "info") => logActivity(state, { type: "info", level, message })
|
|
@@ -14502,7 +15873,7 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
14502
15873
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
14503
15874
|
}
|
|
14504
15875
|
function sessionDbPath() {
|
|
14505
|
-
return
|
|
15876
|
+
return join14(homedir8(), ".local", "share", "opencode", "opencode.db");
|
|
14506
15877
|
}
|
|
14507
15878
|
function logSessionCleanupActivity(state, phase, entry) {
|
|
14508
15879
|
logActivity(state, {
|
|
@@ -14511,6 +15882,47 @@ function logSessionCleanupActivity(state, phase, entry) {
|
|
|
14511
15882
|
metadata: { ...entry.metadata, phase }
|
|
14512
15883
|
});
|
|
14513
15884
|
}
|
|
15885
|
+
async function drainSessionDeletionReports(state) {
|
|
15886
|
+
const path = sessionDeletionReportPath(homedir8(), process.env);
|
|
15887
|
+
let delivered = 0;
|
|
15888
|
+
let failure = null;
|
|
15889
|
+
try {
|
|
15890
|
+
const spool = readSessionDeletionRecords(path);
|
|
15891
|
+
if (spool.readError !== null) {
|
|
15892
|
+
failure = `could not read spool: ${spool.readError}`;
|
|
15893
|
+
} else if (spool.records.length === 0) {
|
|
15894
|
+
return;
|
|
15895
|
+
} else if (!state.agentId || !state.authHeader) {
|
|
15896
|
+
failure = "runner authentication is not available";
|
|
15897
|
+
} else {
|
|
15898
|
+
for (let offset = 0; offset < spool.records.length; offset += 200) {
|
|
15899
|
+
const batch = spool.records.slice(offset, offset + 200);
|
|
15900
|
+
const result = await reportSessionDeletions(state.agentId, state.authHeader, batch);
|
|
15901
|
+
if (!result.ok) {
|
|
15902
|
+
failure = result.error ?? "unknown API error";
|
|
15903
|
+
break;
|
|
15904
|
+
}
|
|
15905
|
+
removeDeliveredSessionDeletionRecords(path, batch);
|
|
15906
|
+
delivered += batch.length;
|
|
15907
|
+
}
|
|
15908
|
+
}
|
|
15909
|
+
} catch (error2) {
|
|
15910
|
+
failure = error2 instanceof Error ? error2.message : String(error2);
|
|
15911
|
+
}
|
|
15912
|
+
const pending = readSessionDeletionRecords(path);
|
|
15913
|
+
const pendingCount = pending.readError === null ? pending.records.length : null;
|
|
15914
|
+
const message = `Session deletion reports: delivered ${delivered}, ${pendingCount === null ? "pending count unavailable" : `${pendingCount} pending`}` + (failure === null ? "." : `; delivery failed: ${failure}.`);
|
|
15915
|
+
logActivity(state, {
|
|
15916
|
+
type: "info",
|
|
15917
|
+
level: failure === null ? "info" : "warn",
|
|
15918
|
+
message,
|
|
15919
|
+
metadata: {
|
|
15920
|
+
outcome: failure === null ? "session-deletion-reports-drained" : "session-deletion-reports-pending",
|
|
15921
|
+
delivered,
|
|
15922
|
+
pending: pendingCount
|
|
15923
|
+
}
|
|
15924
|
+
});
|
|
15925
|
+
}
|
|
14514
15926
|
function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
|
|
14515
15927
|
const record = {
|
|
14516
15928
|
v: 1,
|
|
@@ -14568,17 +15980,22 @@ async function runSweep(state, driver, config) {
|
|
|
14568
15980
|
let failedThrew = 0;
|
|
14569
15981
|
let skippedNewlyActive = 0;
|
|
14570
15982
|
let remaining = 0;
|
|
15983
|
+
const deletionRecords = [];
|
|
14571
15984
|
let protectedNow = driver.protectedSessionIds();
|
|
14572
15985
|
if (sessions !== null) {
|
|
14573
|
-
const
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
protectedIds: protectedNow
|
|
14580
|
-
}
|
|
15986
|
+
const snapshots = sessions.map((session) => ({
|
|
15987
|
+
id: session.id,
|
|
15988
|
+
lastActivityMs: sessionLastActivityMs(session)
|
|
15989
|
+
}));
|
|
15990
|
+
const lastActivityById = new Map(
|
|
15991
|
+
snapshots.map(({ id, lastActivityMs }) => [id, lastActivityMs])
|
|
14581
15992
|
);
|
|
15993
|
+
const toDelete = selectSessionsToDelete(snapshots, {
|
|
15994
|
+
maxAgeMs: config.maxAgeMs,
|
|
15995
|
+
maxCount: config.maxCount,
|
|
15996
|
+
nowMs: Date.now(),
|
|
15997
|
+
protectedIds: protectedNow
|
|
15998
|
+
});
|
|
14582
15999
|
const eligibleToDelete = toDelete.filter(
|
|
14583
16000
|
(id) => (state.sessionDeleteFailures.get(id)?.isolatedFailures ?? 0) < SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES
|
|
14584
16001
|
);
|
|
@@ -14598,6 +16015,17 @@ async function runSweep(state, driver, config) {
|
|
|
14598
16015
|
const outcome = await deleteSession(state.port, id, state.opencodeClient ?? void 0);
|
|
14599
16016
|
if (outcome.ok) {
|
|
14600
16017
|
deleted++;
|
|
16018
|
+
const lastActivityMs = lastActivityById.get(id) ?? null;
|
|
16019
|
+
deletionRecords.push({
|
|
16020
|
+
opencode_session_id: id,
|
|
16021
|
+
deleted_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16022
|
+
reason: "auto_cleanup",
|
|
16023
|
+
policy: {
|
|
16024
|
+
max_age_ms: config.maxAgeMs ?? null,
|
|
16025
|
+
max_count: config.maxCount ?? null,
|
|
16026
|
+
last_activity_at: lastActivityMs === null ? null : new Date(lastActivityMs).toISOString()
|
|
16027
|
+
}
|
|
16028
|
+
});
|
|
14601
16029
|
} else if (outcome.status === "threw") {
|
|
14602
16030
|
failed++;
|
|
14603
16031
|
failedThrew++;
|
|
@@ -14653,6 +16081,32 @@ async function runSweep(state, driver, config) {
|
|
|
14653
16081
|
message: `Session cleanup: inspected ${sessions.length}, deleted ${deleted}${failedNote}${skippedNote}${remainingNote} (${mode})`
|
|
14654
16082
|
});
|
|
14655
16083
|
}
|
|
16084
|
+
if (deletionRecords.length > 0) {
|
|
16085
|
+
try {
|
|
16086
|
+
const dropped = appendSessionDeletionRecords(
|
|
16087
|
+
sessionDeletionReportPath(homedir8(), process.env),
|
|
16088
|
+
deletionRecords
|
|
16089
|
+
);
|
|
16090
|
+
if (dropped > 0) {
|
|
16091
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
16092
|
+
type: "info",
|
|
16093
|
+
level: "warn",
|
|
16094
|
+
message: `Session cleanup: dropped ${dropped} oldest pending deletion report(s) after reaching the spool limit`,
|
|
16095
|
+
metadata: { outcome: "deletion-reports-dropped", dropped_count: dropped }
|
|
16096
|
+
});
|
|
16097
|
+
}
|
|
16098
|
+
} catch (error2) {
|
|
16099
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
16100
|
+
type: "info",
|
|
16101
|
+
level: "warn",
|
|
16102
|
+
message: `Session cleanup: could not persist ${deletionRecords.length} confirmed deletion report(s): ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
16103
|
+
metadata: {
|
|
16104
|
+
outcome: "deletion-report-spool-write-failed",
|
|
16105
|
+
records: deletionRecords.length
|
|
16106
|
+
}
|
|
16107
|
+
});
|
|
16108
|
+
}
|
|
16109
|
+
}
|
|
14656
16110
|
const sessionMetadata = {
|
|
14657
16111
|
sessions_inspected: sessions?.length ?? null,
|
|
14658
16112
|
sessions_deleted: deleted,
|
|
@@ -14722,6 +16176,7 @@ async function runSweep(state, driver, config) {
|
|
|
14722
16176
|
}
|
|
14723
16177
|
});
|
|
14724
16178
|
}
|
|
16179
|
+
await drainSessionDeletionReports(state);
|
|
14725
16180
|
} catch (error2) {
|
|
14726
16181
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
14727
16182
|
logSessionCleanupActivity(state, "sweep", {
|
|
@@ -14746,7 +16201,7 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
14746
16201
|
for (const warning2 of config.warnings) {
|
|
14747
16202
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
14748
16203
|
}
|
|
14749
|
-
const dbBytes = statSessionDbBytes(
|
|
16204
|
+
const dbBytes = statSessionDbBytes(homedir8());
|
|
14750
16205
|
void (async () => {
|
|
14751
16206
|
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
14752
16207
|
if (reclaimAvailability !== null) {
|
|
@@ -14965,7 +16420,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14965
16420
|
});
|
|
14966
16421
|
return;
|
|
14967
16422
|
}
|
|
14968
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
16423
|
+
const { collect, stop } = createResourceUsageCollector(homedir8());
|
|
14969
16424
|
state.stopResourceUsageSampling = stop;
|
|
14970
16425
|
let consecutiveFailures = 0;
|
|
14971
16426
|
const tick = async () => {
|
|
@@ -15204,9 +16659,11 @@ async function run(options) {
|
|
|
15204
16659
|
const interactive = isInteractive(options.json);
|
|
15205
16660
|
let logLevel;
|
|
15206
16661
|
let fileSyncDirectories;
|
|
16662
|
+
let fileSyncExactFiles;
|
|
15207
16663
|
try {
|
|
15208
16664
|
logLevel = resolveLogLevel(options);
|
|
15209
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
16665
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir8());
|
|
16666
|
+
fileSyncExactFiles = resolveFileSyncExactFiles(options.enableFileSyncToExactFile, homedir8());
|
|
15210
16667
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
15211
16668
|
throw new Error(
|
|
15212
16669
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -15282,7 +16739,11 @@ async function run(options) {
|
|
|
15282
16739
|
}
|
|
15283
16740
|
if (fileSyncDirectories.length > 0) {
|
|
15284
16741
|
log2(state, `File sync enabled for: ${fileSyncDirectories.join(", ")}`);
|
|
15285
|
-
}
|
|
16742
|
+
}
|
|
16743
|
+
if (fileSyncExactFiles.length > 0) {
|
|
16744
|
+
log2(state, `File sync exact files enabled for: ${fileSyncExactFiles.join(", ")}`);
|
|
16745
|
+
}
|
|
16746
|
+
if (fileSyncDirectories.length === 0 && fileSyncExactFiles.length === 0) {
|
|
15286
16747
|
log2(state, "File sync is disabled (no --enable-file-sync-to given)", "debug");
|
|
15287
16748
|
}
|
|
15288
16749
|
if (!options.runner && options.agent) {
|
|
@@ -15492,7 +16953,7 @@ async function run(options) {
|
|
|
15492
16953
|
state.credentialSync?.arm();
|
|
15493
16954
|
const dependencyInstallDiagnosticReporter = createDependencyInstallDiagnosticReporter({
|
|
15494
16955
|
context: {
|
|
15495
|
-
homeDir:
|
|
16956
|
+
homeDir: homedir8(),
|
|
15496
16957
|
cwd: process.cwd(),
|
|
15497
16958
|
microvmId: process.env.MICROVM_ID?.trim(),
|
|
15498
16959
|
euid: process.getuid?.(),
|
|
@@ -15515,7 +16976,7 @@ async function run(options) {
|
|
|
15515
16976
|
dependencyInstallDiagnosticReporter
|
|
15516
16977
|
);
|
|
15517
16978
|
const openCodeLogTail = tailOpenCodeLogFile(
|
|
15518
|
-
resolveOpenCodeLogPath(
|
|
16979
|
+
resolveOpenCodeLogPath(homedir8(), process.env),
|
|
15519
16980
|
openCodeActivityForwarder
|
|
15520
16981
|
);
|
|
15521
16982
|
state.stopOpenCodeLogTail = () => {
|
|
@@ -15564,6 +17025,7 @@ async function run(options) {
|
|
|
15564
17025
|
}
|
|
15565
17026
|
}
|
|
15566
17027
|
reportSessionDbRecovery(state);
|
|
17028
|
+
await drainSessionDeletionReports(state);
|
|
15567
17029
|
if (sessionDbVerifyFatal) {
|
|
15568
17030
|
throw new Error(
|
|
15569
17031
|
sessionDbVerifyFatalReason === void 0 ? "SESSION-DB-INTEGRITY-EXHAUSTED: the corrupt session DB could not be proven separated from the active backup prefix or removed from disk" : `SESSION-DB-VERIFY-TIMEOUT: session DB verification timed out (${sessionDbVerifyFatalReason}); refusing to start`
|
|
@@ -15590,6 +17052,44 @@ async function run(options) {
|
|
|
15590
17052
|
for (const warning2 of opencodeVersionWarnings) {
|
|
15591
17053
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
15592
17054
|
}
|
|
17055
|
+
const managedSetupOptions = {
|
|
17056
|
+
agentId: state.agentId,
|
|
17057
|
+
apiUrl: getApiUrlConfig(),
|
|
17058
|
+
getAuthHeader: () => state.authHeader,
|
|
17059
|
+
autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
|
|
17060
|
+
log: (message, level = "info") => logActivity(state, {
|
|
17061
|
+
type: level === "error" ? "error" : "info",
|
|
17062
|
+
level,
|
|
17063
|
+
...level === "error" ? { error: message } : { message }
|
|
17064
|
+
})
|
|
17065
|
+
};
|
|
17066
|
+
let managedDiscovery = await discoverManagedInferenceSetup(managedSetupOptions);
|
|
17067
|
+
if (managedDiscovery.kind === "none") {
|
|
17068
|
+
managedDiscovery = await enrollManagedInferenceSetup(managedSetupOptions);
|
|
17069
|
+
}
|
|
17070
|
+
let managedPreparation = { kind: "none" };
|
|
17071
|
+
if (managedDiscovery.kind === "failed") {
|
|
17072
|
+
managedPreparation = managedDiscovery;
|
|
17073
|
+
managedSetupOptions.log(`MANAGED-INFERENCE-NOT-READY: ${managedDiscovery.reason}`, "warn");
|
|
17074
|
+
} else if (managedDiscovery.kind === "setup") {
|
|
17075
|
+
const managedHealth = opencodeVersion === "v2" ? await checkOpenCode2Health(state.port) : await checkOpenCodeHealth(state.port);
|
|
17076
|
+
managedPreparation = await prepareManagedInferenceSetup(
|
|
17077
|
+
managedSetupOptions,
|
|
17078
|
+
managedDiscovery,
|
|
17079
|
+
managedHealth
|
|
17080
|
+
);
|
|
17081
|
+
if (managedPreparation.kind === "failed") {
|
|
17082
|
+
managedSetupOptions.log(
|
|
17083
|
+
`MANAGED-INFERENCE-NOT-READY: ${managedPreparation.reason}`,
|
|
17084
|
+
"warn"
|
|
17085
|
+
);
|
|
17086
|
+
} else if (managedPreparation.kind === "ready") {
|
|
17087
|
+
managedSetupOptions.log(
|
|
17088
|
+
`MANAGED-INFERENCE-PREPARED: runner=${state.agentId} generation=${managedPreparation.generation}`,
|
|
17089
|
+
"info"
|
|
17090
|
+
);
|
|
17091
|
+
}
|
|
17092
|
+
}
|
|
15593
17093
|
const { value: maxActiveSessions, warnings: maxActiveSessionsWarnings } = resolveMaxActiveSessions(options, process.env);
|
|
15594
17094
|
for (const warning2 of maxActiveSessionsWarnings) {
|
|
15595
17095
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
@@ -15616,6 +17116,32 @@ async function run(options) {
|
|
|
15616
17116
|
state.opencodeProcess = options.opencodePidFile ? null : oc.process;
|
|
15617
17117
|
state.opencodeVersion = oc.version;
|
|
15618
17118
|
state.opencodeApiVersion = opencodeVersion;
|
|
17119
|
+
let configuredPlugins = [];
|
|
17120
|
+
let resolvedPlugins = [];
|
|
17121
|
+
try {
|
|
17122
|
+
const provenanceLog = (message) => logActivity(state, { type: "info", level: "warn", message });
|
|
17123
|
+
configuredPlugins = readConfiguredPluginSpecs({ log: provenanceLog });
|
|
17124
|
+
resolvedPlugins = resolveInstalledPluginVersions({ log: provenanceLog });
|
|
17125
|
+
} catch (error2) {
|
|
17126
|
+
const kind = error2 instanceof Error ? error2.name : "unknown error";
|
|
17127
|
+
logActivity(state, {
|
|
17128
|
+
type: "info",
|
|
17129
|
+
level: "warn",
|
|
17130
|
+
message: `Could not collect OpenCode runtime provenance: ${kind}`
|
|
17131
|
+
});
|
|
17132
|
+
}
|
|
17133
|
+
logActivity(state, {
|
|
17134
|
+
type: "info",
|
|
17135
|
+
level: "info",
|
|
17136
|
+
message: "OpenCode runtime provenance",
|
|
17137
|
+
metadata: {
|
|
17138
|
+
opencode_version: state.opencodeVersion,
|
|
17139
|
+
opencode_api_version: state.opencodeApiVersion,
|
|
17140
|
+
configured_plugins: JSON.stringify(configuredPlugins),
|
|
17141
|
+
resolved_plugins: JSON.stringify(resolvedPlugins)
|
|
17142
|
+
},
|
|
17143
|
+
source: "cli.runtime-provenance"
|
|
17144
|
+
});
|
|
15619
17145
|
let opencodePassword = null;
|
|
15620
17146
|
if (opencodeVersion === "v2" && "password" in oc) {
|
|
15621
17147
|
const value = oc.password;
|
|
@@ -15630,7 +17156,7 @@ async function run(options) {
|
|
|
15630
17156
|
state.opencodeClient = openCodeClient;
|
|
15631
17157
|
if (options.opencodePidFile && oc.process?.pid !== void 0) {
|
|
15632
17158
|
try {
|
|
15633
|
-
|
|
17159
|
+
writeFileSync7(options.opencodePidFile, `${oc.process.pid}
|
|
15634
17160
|
`, { mode: 384 });
|
|
15635
17161
|
chmodSync3(options.opencodePidFile, 384);
|
|
15636
17162
|
} catch (error2) {
|
|
@@ -15644,7 +17170,7 @@ async function run(options) {
|
|
|
15644
17170
|
const provenance = checkSessionDbProvenance({
|
|
15645
17171
|
dbPath: sessionDbPath(),
|
|
15646
17172
|
currentVersion: state.opencodeVersion,
|
|
15647
|
-
homeDir:
|
|
17173
|
+
homeDir: homedir8(),
|
|
15648
17174
|
env: process.env
|
|
15649
17175
|
});
|
|
15650
17176
|
if (provenance.anomaly) {
|
|
@@ -15658,6 +17184,30 @@ async function run(options) {
|
|
|
15658
17184
|
}
|
|
15659
17185
|
}
|
|
15660
17186
|
state.opencodeConnected = oc.notReadyReason === null;
|
|
17187
|
+
if (managedPreparation.kind === "ready" && oc.notReadyReason === null) {
|
|
17188
|
+
try {
|
|
17189
|
+
const acknowledgement = await acknowledgeManagedInferenceSetup(
|
|
17190
|
+
managedSetupOptions,
|
|
17191
|
+
managedPreparation
|
|
17192
|
+
);
|
|
17193
|
+
if (acknowledgement === "acknowledged") {
|
|
17194
|
+
managedSetupOptions.log(
|
|
17195
|
+
`MANAGED-INFERENCE-ACKNOWLEDGED: runner=${state.agentId} generation=${managedPreparation.generation}`,
|
|
17196
|
+
"info"
|
|
17197
|
+
);
|
|
17198
|
+
} else {
|
|
17199
|
+
managedSetupOptions.log(
|
|
17200
|
+
`MANAGED-INFERENCE-NOT-READY: policy refresh is pending for runner=${state.agentId} generation=${managedPreparation.generation}`,
|
|
17201
|
+
"warn"
|
|
17202
|
+
);
|
|
17203
|
+
}
|
|
17204
|
+
} catch (error2) {
|
|
17205
|
+
managedSetupOptions.log(
|
|
17206
|
+
`MANAGED-INFERENCE-NOT-READY: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
17207
|
+
"warn"
|
|
17208
|
+
);
|
|
17209
|
+
}
|
|
17210
|
+
}
|
|
15661
17211
|
const version2 = state.opencodeVersion ? ` (v${state.opencodeVersion})` : "";
|
|
15662
17212
|
ocSpinner?.succeed(`OpenCode running on port ${state.port}${version2}`);
|
|
15663
17213
|
if (!state.interactive && oc.notReadyReason !== null) {
|
|
@@ -15716,7 +17266,7 @@ async function run(options) {
|
|
|
15716
17266
|
let existingPid;
|
|
15717
17267
|
if (existsSync3(options.litestreamPidFile)) {
|
|
15718
17268
|
try {
|
|
15719
|
-
const rawPid =
|
|
17269
|
+
const rawPid = readFileSync9(options.litestreamPidFile, "utf8").trim();
|
|
15720
17270
|
const parsedPid = Number(rawPid);
|
|
15721
17271
|
if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
|
|
15722
17272
|
existingPid = parsedPid;
|
|
@@ -15753,7 +17303,7 @@ async function run(options) {
|
|
|
15753
17303
|
});
|
|
15754
17304
|
try {
|
|
15755
17305
|
if (litestreamProcess.pid !== void 0) {
|
|
15756
|
-
|
|
17306
|
+
writeFileSync7(options.litestreamPidFile, `${litestreamProcess.pid}
|
|
15757
17307
|
`, {
|
|
15758
17308
|
mode: 384
|
|
15759
17309
|
});
|
|
@@ -15812,10 +17362,11 @@ async function run(options) {
|
|
|
15812
17362
|
conversationFilter: state.conversationFilter,
|
|
15813
17363
|
stuckQueuedMs: CHANNEL_STUCK_QUEUED_MS,
|
|
15814
17364
|
sessionStoreHealth: state.sessionStoreHealth,
|
|
15815
|
-
// #559: the
|
|
15816
|
-
//
|
|
17365
|
+
// #559: the file-sync allow-lists. Empty ⇒ pending files are REJECTED with
|
|
17366
|
+
// `file_sync_disabled` on the ack, not silently ignored.
|
|
15817
17367
|
fileSyncDirectories,
|
|
15818
|
-
|
|
17368
|
+
fileSyncExactFiles,
|
|
17369
|
+
homeDir: homedir8(),
|
|
15819
17370
|
maxActiveSessions,
|
|
15820
17371
|
log: (entry) => (
|
|
15821
17372
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -15840,7 +17391,7 @@ async function run(options) {
|
|
|
15840
17391
|
onConnected: (agentId, isReconnect, transition) => {
|
|
15841
17392
|
state.connected = true;
|
|
15842
17393
|
state.agentId = agentId;
|
|
15843
|
-
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})`;
|
|
17394
|
+
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"})`;
|
|
15844
17395
|
logActivity(state, {
|
|
15845
17396
|
type: "info",
|
|
15846
17397
|
message: connectionMessage
|
|
@@ -15873,6 +17424,7 @@ async function run(options) {
|
|
|
15873
17424
|
connected: state.opencodeConnected
|
|
15874
17425
|
}),
|
|
15875
17426
|
connection_generation: transition.generation,
|
|
17427
|
+
...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {},
|
|
15876
17428
|
...isReconnect && transition.downtimeMs !== null ? {
|
|
15877
17429
|
reconnect_attempt: transition.attempt,
|
|
15878
17430
|
downtime_ms: transition.downtimeMs
|
|
@@ -15903,12 +17455,13 @@ async function run(options) {
|
|
|
15903
17455
|
const safeReason2 = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
|
|
15904
17456
|
logActivity(state, {
|
|
15905
17457
|
type: "info",
|
|
15906
|
-
message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason2})`
|
|
17458
|
+
message: `Tunnel disconnected (generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"}, code: ${code}, reason: ${safeReason2})`
|
|
15907
17459
|
});
|
|
15908
17460
|
emitAgentDisconnected(state.agentId, {
|
|
15909
17461
|
code,
|
|
15910
17462
|
reason: safeReason2,
|
|
15911
|
-
connection_generation: transition.generation
|
|
17463
|
+
connection_generation: transition.generation,
|
|
17464
|
+
...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {}
|
|
15912
17465
|
});
|
|
15913
17466
|
if (state.interactive) displayStatus(state);
|
|
15914
17467
|
},
|
|
@@ -16059,9 +17612,9 @@ program.name("evident").description("Run OpenCode locally and connect it to Evid
|
|
|
16059
17612
|
"--endpoint <url>",
|
|
16060
17613
|
"Evident API base URL (default: production; e.g. http://localhost:3001)"
|
|
16061
17614
|
).option("--tunnel <url>", "Tunnel WebSocket URL (default: production; e.g. ws://localhost:8787)").hook("preAction", (thisCommand) => {
|
|
16062
|
-
const { endpoint, tunnel } = thisCommand.opts();
|
|
16063
|
-
if (
|
|
16064
|
-
setEndpoint(
|
|
17615
|
+
const { endpoint: endpoint2, tunnel } = thisCommand.opts();
|
|
17616
|
+
if (endpoint2) {
|
|
17617
|
+
setEndpoint(endpoint2);
|
|
16065
17618
|
}
|
|
16066
17619
|
if (tunnel) {
|
|
16067
17620
|
setTunnelUrl(tunnel);
|
|
@@ -16111,6 +17664,11 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
16111
17664
|
"Allow Evident to write files into this directory (repeatable). Omit to disable file sync entirely.",
|
|
16112
17665
|
(value, previous) => previous.concat([value]),
|
|
16113
17666
|
[]
|
|
17667
|
+
).option(
|
|
17668
|
+
"--enable-file-sync-to-exact-file <path>",
|
|
17669
|
+
"Allow Evident to write files to exactly this path (repeatable).",
|
|
17670
|
+
(value, previous) => previous.concat([value]),
|
|
17671
|
+
[]
|
|
16114
17672
|
).option(
|
|
16115
17673
|
"--tunnel-ready-file <path>",
|
|
16116
17674
|
"Path to write once the tunnel is connected (opt-in; unused unless an operator/image sets it)"
|
|
@@ -16135,6 +17693,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
16135
17693
|
).option(
|
|
16136
17694
|
"--opencode-config-overlay <path>",
|
|
16137
17695
|
"Apply this runner-provided OpenCode config before starting OpenCode."
|
|
17696
|
+
).option(
|
|
17697
|
+
"--auto-configure-credentials-proxy",
|
|
17698
|
+
"Install Evident's generated OpenCode providers for the runner's credential pool (off by default)."
|
|
16138
17699
|
).option(
|
|
16139
17700
|
"--credential-sync-marker <path>",
|
|
16140
17701
|
"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."
|
|
@@ -16170,6 +17731,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
16170
17731
|
// Raw values — expansion/validation is single-sourced in run.ts's
|
|
16171
17732
|
// resolveFileSyncDirectories.
|
|
16172
17733
|
enableFileSyncTo: options.enableFileSyncTo,
|
|
17734
|
+
// Raw values — expansion/validation is single-sourced in run.ts's
|
|
17735
|
+
// resolveFileSyncExactFiles.
|
|
17736
|
+
enableFileSyncToExactFile: options.enableFileSyncToExactFile,
|
|
16173
17737
|
tunnelReadyFile: options.tunnelReadyFile,
|
|
16174
17738
|
litestreamConfig: options.litestreamConfig,
|
|
16175
17739
|
opencodePidFile: options.opencodePidFile,
|
|
@@ -16178,6 +17742,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
16178
17742
|
restoreSessionDb: options.restoreSessionDb,
|
|
16179
17743
|
restoreRunnerCredentials: options.restoreRunnerCredentials,
|
|
16180
17744
|
opencodeConfigOverlay: options.opencodeConfigOverlay,
|
|
17745
|
+
autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
|
|
16181
17746
|
credentialSyncMarker: options.credentialSyncMarker
|
|
16182
17747
|
});
|
|
16183
17748
|
}
|