@evident-ai/cli 3.6.1-dev.8d3e144 → 3.6.1-dev.a11d5f5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/index.js +1592 -206
- 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.1-dev.
|
|
1448
|
+
var CLI_VERSION = (true ? "3.6.1-dev.a11d5f5" : 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 = [];
|
|
@@ -4221,6 +4555,11 @@ async function listV2Permissions(client, sessionId) {
|
|
|
4221
4555
|
return null;
|
|
4222
4556
|
}
|
|
4223
4557
|
}
|
|
4558
|
+
async function interruptV2Session(client, sessionId) {
|
|
4559
|
+
await client.request(`/api/session/${encodeURIComponent(sessionId)}/interrupt`, {
|
|
4560
|
+
method: "POST"
|
|
4561
|
+
});
|
|
4562
|
+
}
|
|
4224
4563
|
async function getV2ActiveSessions(client) {
|
|
4225
4564
|
try {
|
|
4226
4565
|
const body = await readJson(await client.request("/api/session/active"));
|
|
@@ -4317,13 +4656,13 @@ async function readV2SessionErrorStream(client, options) {
|
|
|
4317
4656
|
}
|
|
4318
4657
|
|
|
4319
4658
|
// src/lib/opencode/credential-import-v2.ts
|
|
4320
|
-
import { readFileSync as
|
|
4321
|
-
import { join as
|
|
4659
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
4660
|
+
import { join as join6 } from "node:path";
|
|
4322
4661
|
|
|
4323
4662
|
// src/lib/openai-usage.ts
|
|
4324
|
-
import { readFileSync as
|
|
4663
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
4325
4664
|
import { homedir as homedir3 } from "node:os";
|
|
4326
|
-
import { join as
|
|
4665
|
+
import { join as join5 } from "node:path";
|
|
4327
4666
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
4328
4667
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
4329
4668
|
var OpenAiUsageError = class extends Error {
|
|
@@ -4337,7 +4676,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
4337
4676
|
}
|
|
4338
4677
|
function readOpenCodeChatGptCredentials() {
|
|
4339
4678
|
try {
|
|
4340
|
-
const raw =
|
|
4679
|
+
const raw = readFileSync4(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
4341
4680
|
let parsed;
|
|
4342
4681
|
try {
|
|
4343
4682
|
parsed = JSON.parse(raw);
|
|
@@ -4569,7 +4908,7 @@ function emptyResult(unsupported = [], outcome = "completed") {
|
|
|
4569
4908
|
}
|
|
4570
4909
|
function readOpenCodeCredentials(homeDir, log3) {
|
|
4571
4910
|
try {
|
|
4572
|
-
const raw =
|
|
4911
|
+
const raw = readFileSync5(join6(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
|
|
4573
4912
|
let parsed;
|
|
4574
4913
|
try {
|
|
4575
4914
|
parsed = JSON.parse(raw);
|
|
@@ -4623,11 +4962,11 @@ function failureMessage(status2) {
|
|
|
4623
4962
|
function isRetryableStatus(status2) {
|
|
4624
4963
|
return CONNECT_KEY_RETRY_STATUSES.includes(status2);
|
|
4625
4964
|
}
|
|
4626
|
-
async function importOneCredential(
|
|
4965
|
+
async function importOneCredential(request3, credential, sleepForRetry, log3) {
|
|
4627
4966
|
const path = `/api/integration/${encodeURIComponent(credential.integrationId)}/connect/key`;
|
|
4628
4967
|
for (let attempt = 1; attempt <= CONNECT_KEY_MAX_ATTEMPTS; attempt += 1) {
|
|
4629
4968
|
try {
|
|
4630
|
-
const response = await
|
|
4969
|
+
const response = await request3(
|
|
4631
4970
|
path,
|
|
4632
4971
|
{
|
|
4633
4972
|
method: "POST",
|
|
@@ -4662,7 +5001,7 @@ async function importOneCredential(request2, credential, sleepForRetry, log3) {
|
|
|
4662
5001
|
throw new Error("OpenCode credential import retry loop did not return");
|
|
4663
5002
|
}
|
|
4664
5003
|
async function importOpenCodeCredentials({
|
|
4665
|
-
request:
|
|
5004
|
+
request: request3,
|
|
4666
5005
|
classified,
|
|
4667
5006
|
connectedIntegrationIds,
|
|
4668
5007
|
sleepForRetry = sleep2,
|
|
@@ -4674,7 +5013,7 @@ async function importOpenCodeCredentials({
|
|
|
4674
5013
|
result.alreadyConnected.push(credential.integrationId);
|
|
4675
5014
|
continue;
|
|
4676
5015
|
}
|
|
4677
|
-
const outcome = await importOneCredential(
|
|
5016
|
+
const outcome = await importOneCredential(request3, credential, sleepForRetry, log3);
|
|
4678
5017
|
if (outcome.ok) result.imported.push(credential.integrationId);
|
|
4679
5018
|
else result.failed.push(outcome.failure);
|
|
4680
5019
|
}
|
|
@@ -4692,7 +5031,7 @@ function parseIntegrationList(value) {
|
|
|
4692
5031
|
return { integrationIds, hasData: value.data.length > 0 };
|
|
4693
5032
|
}
|
|
4694
5033
|
async function getConnectedOpenCodeIntegrationIds({
|
|
4695
|
-
request:
|
|
5034
|
+
request: request3,
|
|
4696
5035
|
directory,
|
|
4697
5036
|
sleepForRetry = sleep2,
|
|
4698
5037
|
log: log3
|
|
@@ -4700,7 +5039,7 @@ async function getConnectedOpenCodeIntegrationIds({
|
|
|
4700
5039
|
const path = `/api/integration?location%5Bdirectory%5D=${encodeURIComponent(directory)}`;
|
|
4701
5040
|
for (let attempt = 1; attempt <= INTEGRATION_LIST_MAX_READS; attempt += 1) {
|
|
4702
5041
|
try {
|
|
4703
|
-
const response = await
|
|
5042
|
+
const response = await request3(path);
|
|
4704
5043
|
const parsed = parseIntegrationList(await response.json());
|
|
4705
5044
|
if (parsed === null) {
|
|
4706
5045
|
log3(
|
|
@@ -4729,7 +5068,7 @@ async function getConnectedOpenCodeIntegrationIds({
|
|
|
4729
5068
|
return null;
|
|
4730
5069
|
}
|
|
4731
5070
|
async function importOpenCodeV2Credentials({
|
|
4732
|
-
request:
|
|
5071
|
+
request: request3,
|
|
4733
5072
|
homeDir,
|
|
4734
5073
|
directory,
|
|
4735
5074
|
sleepForRetry = sleep2,
|
|
@@ -4747,7 +5086,7 @@ async function importOpenCodeV2Credentials({
|
|
|
4747
5086
|
const classified = classifyOpenCodeCredentials(credentials2);
|
|
4748
5087
|
if (classified.importable.length === 0) return emptyResult(classified.unsupported);
|
|
4749
5088
|
const connectedIntegrationIds = await getConnectedOpenCodeIntegrationIds({
|
|
4750
|
-
request:
|
|
5089
|
+
request: request3,
|
|
4751
5090
|
directory,
|
|
4752
5091
|
sleepForRetry,
|
|
4753
5092
|
log: log3
|
|
@@ -4756,7 +5095,7 @@ async function importOpenCodeV2Credentials({
|
|
|
4756
5095
|
return emptyResult(classified.unsupported, "connection-list-unavailable");
|
|
4757
5096
|
}
|
|
4758
5097
|
return importOpenCodeCredentials({
|
|
4759
|
-
request:
|
|
5098
|
+
request: request3,
|
|
4760
5099
|
classified,
|
|
4761
5100
|
connectedIntegrationIds,
|
|
4762
5101
|
sleepForRetry,
|
|
@@ -4769,9 +5108,9 @@ async function importOpenCodeV2Credentials({
|
|
|
4769
5108
|
|
|
4770
5109
|
// src/lib/opencode/session-db-boot.ts
|
|
4771
5110
|
import { spawn as spawn2 } from "node:child_process";
|
|
4772
|
-
import { mkdirSync, statSync as statSync3, unlinkSync as
|
|
5111
|
+
import { mkdirSync as mkdirSync2, statSync as statSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4773
5112
|
import { homedir as homedir4 } from "node:os";
|
|
4774
|
-
import { dirname as
|
|
5113
|
+
import { dirname as dirname5, resolve as resolvePath } from "node:path";
|
|
4775
5114
|
|
|
4776
5115
|
// src/lib/runner-synchroniser.ts
|
|
4777
5116
|
import { spawn } from "node:child_process";
|
|
@@ -4887,7 +5226,7 @@ function reportRecord(stage, outcome, reason, litestreamExitCode, options) {
|
|
|
4887
5226
|
function clearMarker(options) {
|
|
4888
5227
|
if (!options.noReplicateMarker) return;
|
|
4889
5228
|
try {
|
|
4890
|
-
|
|
5229
|
+
unlinkSync3(options.noReplicateMarker);
|
|
4891
5230
|
} catch (error2) {
|
|
4892
5231
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return;
|
|
4893
5232
|
options.log(
|
|
@@ -4899,8 +5238,8 @@ function clearMarker(options) {
|
|
|
4899
5238
|
function markNoReplicate(options, message) {
|
|
4900
5239
|
if (options.noReplicateMarker) {
|
|
4901
5240
|
try {
|
|
4902
|
-
|
|
4903
|
-
|
|
5241
|
+
mkdirSync2(dirname5(options.noReplicateMarker), { recursive: true });
|
|
5242
|
+
writeFileSync2(options.noReplicateMarker, "");
|
|
4904
5243
|
} catch (error2) {
|
|
4905
5244
|
options.log(
|
|
4906
5245
|
`Could not write session-DB no-replicate marker ${options.noReplicateMarker}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
@@ -4913,7 +5252,7 @@ function markNoReplicate(options, message) {
|
|
|
4913
5252
|
function discardSessionDbDebris(options) {
|
|
4914
5253
|
for (const path of [options.dbPath, `${options.dbPath}-wal`, `${options.dbPath}-shm`]) {
|
|
4915
5254
|
try {
|
|
4916
|
-
|
|
5255
|
+
unlinkSync3(path);
|
|
4917
5256
|
} catch (error2) {
|
|
4918
5257
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") continue;
|
|
4919
5258
|
options.log(
|
|
@@ -5054,8 +5393,8 @@ async function ensureLitestreamConfig(options, env) {
|
|
|
5054
5393
|
return null;
|
|
5055
5394
|
}
|
|
5056
5395
|
try {
|
|
5057
|
-
|
|
5058
|
-
|
|
5396
|
+
mkdirSync2(dirname5(configPath), { recursive: true });
|
|
5397
|
+
writeFileSync2(configPath, rendered.stdout);
|
|
5059
5398
|
} catch (error2) {
|
|
5060
5399
|
options.log(
|
|
5061
5400
|
`Could not write ${configPath}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
@@ -5320,8 +5659,8 @@ async function restoreAndVerifySessionDb(options) {
|
|
|
5320
5659
|
|
|
5321
5660
|
// src/lib/opencode/session-db-provenance.ts
|
|
5322
5661
|
import { createRequire } from "node:module";
|
|
5323
|
-
import { mkdirSync as
|
|
5324
|
-
import { dirname as
|
|
5662
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5663
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
5325
5664
|
var require2 = createRequire(import.meta.url);
|
|
5326
5665
|
function readSessionDbMigrationIds(dbPath) {
|
|
5327
5666
|
let db;
|
|
@@ -5358,12 +5697,12 @@ function readSessionDbMigrationIds(dbPath) {
|
|
|
5358
5697
|
}
|
|
5359
5698
|
function sessionDbProvenanceStatePath(homeDir, env) {
|
|
5360
5699
|
const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
|
|
5361
|
-
return override ||
|
|
5700
|
+
return override || join7(homeDir, ".local", "state", "evident", "session-db-provenance.json");
|
|
5362
5701
|
}
|
|
5363
5702
|
function loadSessionDbProvenanceState(path) {
|
|
5364
5703
|
let value;
|
|
5365
5704
|
try {
|
|
5366
|
-
value = JSON.parse(
|
|
5705
|
+
value = JSON.parse(readFileSync6(path, "utf8"));
|
|
5367
5706
|
} catch (error2) {
|
|
5368
5707
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return {};
|
|
5369
5708
|
console.error(
|
|
@@ -5387,8 +5726,8 @@ function loadSessionDbProvenanceState(path) {
|
|
|
5387
5726
|
}
|
|
5388
5727
|
function saveSessionDbProvenanceState(path, state) {
|
|
5389
5728
|
try {
|
|
5390
|
-
|
|
5391
|
-
|
|
5729
|
+
mkdirSync3(dirname6(path), { recursive: true });
|
|
5730
|
+
writeFileSync3(path, `${JSON.stringify(state, null, 2)}
|
|
5392
5731
|
`, "utf8");
|
|
5393
5732
|
} catch (error2) {
|
|
5394
5733
|
console.error(
|
|
@@ -5447,7 +5786,11 @@ function isSessionDbProvenanceRecord(value) {
|
|
|
5447
5786
|
var OPENCODE2_PACKAGE_SPEC = "@opencode-ai/cli@0.0.0-beta-17823";
|
|
5448
5787
|
|
|
5449
5788
|
// src/lib/opencode/opencode-version-gate.ts
|
|
5450
|
-
var QUEUE_VALIDATED_OPENCODE_VERSIONS = [
|
|
5789
|
+
var QUEUE_VALIDATED_OPENCODE_VERSIONS = [
|
|
5790
|
+
"1.17.11",
|
|
5791
|
+
"1.18.3",
|
|
5792
|
+
"1.18.35"
|
|
5793
|
+
];
|
|
5451
5794
|
function isQueueValidatedVersion(version2) {
|
|
5452
5795
|
if (!version2) return false;
|
|
5453
5796
|
return QUEUE_VALIDATED_OPENCODE_VERSIONS.includes(version2);
|
|
@@ -6032,15 +6375,15 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
6032
6375
|
|
|
6033
6376
|
// src/lib/opencode/session-db-size.ts
|
|
6034
6377
|
import { statSync as statSync4 } from "node:fs";
|
|
6035
|
-
import { join as
|
|
6378
|
+
import { join as join8 } from "node:path";
|
|
6036
6379
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
6037
6380
|
function statSessionDbBytes(homeDir) {
|
|
6038
|
-
const dbPath =
|
|
6381
|
+
const dbPath = join8(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
6039
6382
|
try {
|
|
6040
6383
|
return statSync4(dbPath).size;
|
|
6041
6384
|
} catch (err) {
|
|
6042
|
-
const
|
|
6043
|
-
if (!
|
|
6385
|
+
const isMissingFile2 = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
6386
|
+
if (!isMissingFile2) {
|
|
6044
6387
|
console.error(
|
|
6045
6388
|
`[statSessionDbBytes] could not stat ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
6046
6389
|
);
|
|
@@ -6065,12 +6408,12 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
6065
6408
|
// src/lib/opencode/log-tail.ts
|
|
6066
6409
|
import { statSync as statSync5 } from "node:fs";
|
|
6067
6410
|
import { homedir as homedir5 } from "node:os";
|
|
6068
|
-
import { join as
|
|
6411
|
+
import { join as join9 } from "node:path";
|
|
6069
6412
|
import { open as open2, stat } from "node:fs/promises";
|
|
6070
6413
|
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
6071
6414
|
function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
|
|
6072
|
-
const dataDir = env.XDG_DATA_HOME ||
|
|
6073
|
-
return
|
|
6415
|
+
const dataDir = env.XDG_DATA_HOME || join9(homeDir, ".local", "share");
|
|
6416
|
+
return join9(dataDir, "opencode", "log", "opencode.log");
|
|
6074
6417
|
}
|
|
6075
6418
|
function isEnoent(error2) {
|
|
6076
6419
|
return error2?.code === "ENOENT";
|
|
@@ -6492,6 +6835,7 @@ function connectTunnel(options) {
|
|
|
6492
6835
|
}, 3e4);
|
|
6493
6836
|
let relaySilenceTimeout;
|
|
6494
6837
|
let relaySilenceTimedOut = false;
|
|
6838
|
+
let relayConnectionId;
|
|
6495
6839
|
const clearRelaySilenceTimeout = () => {
|
|
6496
6840
|
if (relaySilenceTimeout === void 0) return;
|
|
6497
6841
|
clearTimeout(relaySilenceTimeout);
|
|
@@ -6577,7 +6921,8 @@ function connectTunnel(options) {
|
|
|
6577
6921
|
armRelaySilenceTimeout();
|
|
6578
6922
|
forwarder.setBinaryFramesSupported(message.binary_frames === true);
|
|
6579
6923
|
const connectedAgentId = message.agent_id ?? agentId;
|
|
6580
|
-
|
|
6924
|
+
relayConnectionId = message.connection_id;
|
|
6925
|
+
onConnected?.(connectedAgentId, relayConnectionId);
|
|
6581
6926
|
resolve5({
|
|
6582
6927
|
ws,
|
|
6583
6928
|
close: () => ws.close(1e3, "CLI shutdown")
|
|
@@ -6614,7 +6959,7 @@ function connectTunnel(options) {
|
|
|
6614
6959
|
clearRelaySilenceTimeout();
|
|
6615
6960
|
const reasonStr = (relaySilenceTimedOut ? "no relay heartbeat for 90s \u2014 reconnecting" : void 0) || reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
|
|
6616
6961
|
forwarder.abortAll();
|
|
6617
|
-
onDisconnected?.(code, reasonStr);
|
|
6962
|
+
onDisconnected?.(code, reasonStr, relayConnectionId);
|
|
6618
6963
|
});
|
|
6619
6964
|
});
|
|
6620
6965
|
}
|
|
@@ -6677,7 +7022,7 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6677
7022
|
authHeader: this.opts.getAuthHeader(),
|
|
6678
7023
|
port: this.opts.port,
|
|
6679
7024
|
openCodePassword: this.opts.openCodePassword,
|
|
6680
|
-
onConnected: (agentId) => {
|
|
7025
|
+
onConnected: (agentId, relayConnectionId) => {
|
|
6681
7026
|
const attempt = this.reconnectAttempt + 1;
|
|
6682
7027
|
this.reconnectAttempt = 0;
|
|
6683
7028
|
this.reconnecting = false;
|
|
@@ -6687,13 +7032,17 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6687
7032
|
this.disconnectedAtMs = null;
|
|
6688
7033
|
events.onConnected(agentId, isReconnect, {
|
|
6689
7034
|
generation: this.generation,
|
|
7035
|
+
relayConnectionId: relayConnectionId ?? null,
|
|
6690
7036
|
attempt,
|
|
6691
7037
|
downtimeMs
|
|
6692
7038
|
});
|
|
6693
7039
|
},
|
|
6694
|
-
onDisconnected: (code, reason) => {
|
|
7040
|
+
onDisconnected: (code, reason, relayConnectionId) => {
|
|
6695
7041
|
this.disconnectedAtMs = this.now();
|
|
6696
|
-
events.onDisconnected(code, reason, {
|
|
7042
|
+
events.onDisconnected(code, reason, {
|
|
7043
|
+
generation: this.generation,
|
|
7044
|
+
relayConnectionId: relayConnectionId ?? null
|
|
7045
|
+
});
|
|
6697
7046
|
const relayRequestedReconnect = code === 1e3 && _RunnerConnection.RELAY_RECONNECT_CLOSE_REASONS.has(reason);
|
|
6698
7047
|
if (this.opts.isRunning() && (code !== 1e3 || relayRequestedReconnect) && !this.reconnecting) {
|
|
6699
7048
|
this.reconnectPromise = this.connectWithRetry(true).catch((err) => {
|
|
@@ -6731,10 +7080,10 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6731
7080
|
};
|
|
6732
7081
|
|
|
6733
7082
|
// src/lib/tunnel/ready-marker.ts
|
|
6734
|
-
import { writeFileSync as
|
|
7083
|
+
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
6735
7084
|
function writeTunnelReadyMarker(path, agentId) {
|
|
6736
7085
|
try {
|
|
6737
|
-
|
|
7086
|
+
writeFileSync4(path, `${agentId}
|
|
6738
7087
|
`);
|
|
6739
7088
|
return { ok: true };
|
|
6740
7089
|
} catch (error2) {
|
|
@@ -6759,7 +7108,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
|
|
|
6759
7108
|
}
|
|
6760
7109
|
|
|
6761
7110
|
// src/lib/process-liveness.ts
|
|
6762
|
-
import { readFileSync as
|
|
7111
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
6763
7112
|
function isProcessAlive(pid) {
|
|
6764
7113
|
try {
|
|
6765
7114
|
process.kill(pid, 0);
|
|
@@ -6774,7 +7123,7 @@ function isProcessAlive(pid) {
|
|
|
6774
7123
|
}
|
|
6775
7124
|
if (process.platform !== "linux") return true;
|
|
6776
7125
|
try {
|
|
6777
|
-
const status2 =
|
|
7126
|
+
const status2 = readFileSync7(`/proc/${pid}/status`, "utf8");
|
|
6778
7127
|
return !/^State:\s+Z(?:\s|$)/m.test(status2);
|
|
6779
7128
|
} catch (error2) {
|
|
6780
7129
|
console.error(
|
|
@@ -7133,16 +7482,16 @@ function createSessionStoreHealth(options = {}) {
|
|
|
7133
7482
|
import { homedir as homedir6 } from "node:os";
|
|
7134
7483
|
|
|
7135
7484
|
// src/lib/runner-file-sync.ts
|
|
7136
|
-
import { join as
|
|
7485
|
+
import { join as join11 } from "node:path";
|
|
7137
7486
|
|
|
7138
7487
|
// src/lib/file-push.ts
|
|
7139
7488
|
import { randomUUID } from "node:crypto";
|
|
7140
7489
|
import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
|
|
7141
|
-
import { basename as basename2, dirname as
|
|
7490
|
+
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
7491
|
var FILE_MODE = 384;
|
|
7143
7492
|
var DIRECTORY_MODE = 448;
|
|
7144
|
-
async function writePushedFile(
|
|
7145
|
-
const { requestedPath, content, allowedDirectories, homeDir } =
|
|
7493
|
+
async function writePushedFile(request3) {
|
|
7494
|
+
const { requestedPath, content, allowedDirectories, homeDir } = request3;
|
|
7146
7495
|
const bytes = content.byteLength;
|
|
7147
7496
|
if (allowedDirectories.length === 0) {
|
|
7148
7497
|
return refuse("file_sync_disabled", "File sync is not enabled on this runner.", {
|
|
@@ -7169,9 +7518,9 @@ async function writePushedFile(request2) {
|
|
|
7169
7518
|
}
|
|
7170
7519
|
try {
|
|
7171
7520
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
7172
|
-
|
|
7521
|
+
dirname7(candidate)
|
|
7173
7522
|
);
|
|
7174
|
-
const realTarget =
|
|
7523
|
+
const realTarget = join10(existingAncestor, ...missingSegments, basename2(candidate));
|
|
7175
7524
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
7176
7525
|
if (allowedDirectory === null) {
|
|
7177
7526
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -7181,8 +7530,8 @@ async function writePushedFile(request2) {
|
|
|
7181
7530
|
}
|
|
7182
7531
|
if (missingSegments.length > 0) {
|
|
7183
7532
|
await createMissingDirectories(existingAncestor, missingSegments);
|
|
7184
|
-
const realParent = await realpath(
|
|
7185
|
-
if (realParent !==
|
|
7533
|
+
const realParent = await realpath(dirname7(realTarget));
|
|
7534
|
+
if (realParent !== dirname7(realTarget) || !contains(allowedDirectory, realTarget)) {
|
|
7186
7535
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
7187
7536
|
path: realTarget,
|
|
7188
7537
|
bytes,
|
|
@@ -7207,7 +7556,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
7207
7556
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
7208
7557
|
return null;
|
|
7209
7558
|
}
|
|
7210
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7559
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7211
7560
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
7212
7561
|
return null;
|
|
7213
7562
|
}
|
|
@@ -7225,7 +7574,7 @@ async function resolveNearestExistingAncestor(directory) {
|
|
|
7225
7574
|
try {
|
|
7226
7575
|
return { existingAncestor: await realpath(current), missingSegments };
|
|
7227
7576
|
} catch (err) {
|
|
7228
|
-
const parent =
|
|
7577
|
+
const parent = dirname7(current);
|
|
7229
7578
|
if (err.code !== "ENOENT" || parent === current) {
|
|
7230
7579
|
throw err;
|
|
7231
7580
|
}
|
|
@@ -7280,13 +7629,13 @@ function contains(realDirectory, realTarget) {
|
|
|
7280
7629
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
7281
7630
|
let current = existingAncestor;
|
|
7282
7631
|
for (const segment of missingSegments) {
|
|
7283
|
-
current =
|
|
7632
|
+
current = join10(current, segment);
|
|
7284
7633
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
7285
7634
|
await chmod(current, DIRECTORY_MODE);
|
|
7286
7635
|
}
|
|
7287
7636
|
}
|
|
7288
7637
|
async function writeAtomically(realTarget, content) {
|
|
7289
|
-
const temporaryPath =
|
|
7638
|
+
const temporaryPath = join10(dirname7(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
7290
7639
|
let handle;
|
|
7291
7640
|
try {
|
|
7292
7641
|
handle = await open3(temporaryPath, "wx", FILE_MODE);
|
|
@@ -7416,12 +7765,12 @@ var NOT_APPLIED = {
|
|
|
7416
7765
|
opencodeAuthApplied: false
|
|
7417
7766
|
};
|
|
7418
7767
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
7419
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7420
|
-
return expanded ===
|
|
7768
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join11(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7769
|
+
return expanded === join11(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
7421
7770
|
}
|
|
7422
7771
|
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
7423
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7424
|
-
return expanded ===
|
|
7772
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join11(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7773
|
+
return expanded === join11(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
7425
7774
|
}
|
|
7426
7775
|
async function applyOne(options, file) {
|
|
7427
7776
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -7558,81 +7907,286 @@ function describe(err) {
|
|
|
7558
7907
|
return err instanceof Error ? err.message : String(err);
|
|
7559
7908
|
}
|
|
7560
7909
|
|
|
7561
|
-
// src/lib/
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7910
|
+
// src/lib/vault-file-sync.ts
|
|
7911
|
+
import { createHash } from "node:crypto";
|
|
7912
|
+
async function syncVaultFiles(options) {
|
|
7913
|
+
const files = await listVaultFiles(options);
|
|
7914
|
+
if (files === null) return { applied: 0 };
|
|
7915
|
+
const listedIds = new Set(files.map((file) => file.id));
|
|
7916
|
+
for (const fileId of options.outcomes.keys()) {
|
|
7917
|
+
if (!listedIds.has(fileId)) options.outcomes.delete(fileId);
|
|
7918
|
+
}
|
|
7919
|
+
let applied = 0;
|
|
7920
|
+
for (const file of files) {
|
|
7921
|
+
try {
|
|
7922
|
+
if (await reconcileVaultFile(options, file)) applied += 1;
|
|
7923
|
+
} catch (err) {
|
|
7924
|
+
options.log({
|
|
7925
|
+
level: "error",
|
|
7926
|
+
message: `Vault file ${file.id} reconciliation failed unexpectedly (message processing is unaffected): ${describe2(err)}`
|
|
7927
|
+
});
|
|
7928
|
+
}
|
|
7929
|
+
}
|
|
7930
|
+
return { applied };
|
|
7577
7931
|
}
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
};
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
};
|
|
7589
|
-
|
|
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";
|
|
7932
|
+
async function listVaultFiles(options) {
|
|
7933
|
+
let response;
|
|
7934
|
+
try {
|
|
7935
|
+
response = await options.fetchImpl(`${options.apiUrl}/runners/${options.agentId}/vault-files`, {
|
|
7936
|
+
headers: { Authorization: options.getAuthHeader() }
|
|
7937
|
+
});
|
|
7938
|
+
} catch (err) {
|
|
7939
|
+
options.log({
|
|
7940
|
+
level: "warn",
|
|
7941
|
+
message: `Could not list vault files \u2014 retrying on the next drain: ${describe2(err)}`
|
|
7942
|
+
});
|
|
7943
|
+
return null;
|
|
7620
7944
|
}
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
this.status = status2;
|
|
7945
|
+
if (!response.ok) {
|
|
7946
|
+
options.log({
|
|
7947
|
+
level: response.status === 404 ? "debug" : "warn",
|
|
7948
|
+
message: `Listing vault files returned HTTP ${response.status} \u2014 retrying on the next drain`
|
|
7949
|
+
});
|
|
7950
|
+
return null;
|
|
7628
7951
|
}
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7952
|
+
let body;
|
|
7953
|
+
try {
|
|
7954
|
+
body = await response.json();
|
|
7955
|
+
} catch (err) {
|
|
7956
|
+
options.log({
|
|
7957
|
+
level: "warn",
|
|
7958
|
+
message: `Vault file list was not readable JSON \u2014 retrying on the next drain: ${describe2(err)}`
|
|
7959
|
+
});
|
|
7960
|
+
return null;
|
|
7961
|
+
}
|
|
7962
|
+
if (body === null || typeof body !== "object" || !Array.isArray(body.files)) {
|
|
7963
|
+
options.log({
|
|
7964
|
+
level: "warn",
|
|
7965
|
+
message: "Vault file list was not an object with a files array \u2014 ignoring it for this drain"
|
|
7966
|
+
});
|
|
7967
|
+
return null;
|
|
7968
|
+
}
|
|
7969
|
+
const files = [];
|
|
7970
|
+
for (const entry of body.files) {
|
|
7971
|
+
const file = asVaultFile(entry);
|
|
7972
|
+
if (file === null) {
|
|
7973
|
+
options.log({
|
|
7974
|
+
level: "warn",
|
|
7975
|
+
message: "Ignoring a malformed vault file entry"
|
|
7976
|
+
});
|
|
7977
|
+
continue;
|
|
7978
|
+
}
|
|
7979
|
+
files.push(file);
|
|
7980
|
+
}
|
|
7981
|
+
return files;
|
|
7634
7982
|
}
|
|
7635
|
-
function
|
|
7983
|
+
function asVaultFile(entry) {
|
|
7984
|
+
if (entry === null || typeof entry !== "object") return null;
|
|
7985
|
+
const value = entry;
|
|
7986
|
+
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") {
|
|
7987
|
+
return null;
|
|
7988
|
+
}
|
|
7989
|
+
return {
|
|
7990
|
+
id: value.id,
|
|
7991
|
+
destination_path: value.destination_path,
|
|
7992
|
+
size_bytes: value.size_bytes,
|
|
7993
|
+
content_sha256: value.content_sha256,
|
|
7994
|
+
created_at: value.created_at,
|
|
7995
|
+
updated_at: value.updated_at
|
|
7996
|
+
};
|
|
7997
|
+
}
|
|
7998
|
+
async function reconcileVaultFile(options, file) {
|
|
7999
|
+
const existing = options.outcomes.get(file.id);
|
|
8000
|
+
if (existing?.sha256 === file.content_sha256) {
|
|
8001
|
+
if (existing.reported || existing.reportAttempts >= MAX_ACK_ATTEMPTS) return false;
|
|
8002
|
+
await reportOutcome(options, file, existing);
|
|
8003
|
+
return false;
|
|
8004
|
+
}
|
|
8005
|
+
const content = await downloadVaultFile(options, file);
|
|
8006
|
+
if (content === null) return false;
|
|
8007
|
+
const actualSha256 = createHash("sha256").update(content).digest("hex");
|
|
8008
|
+
if (actualSha256 !== file.content_sha256) {
|
|
8009
|
+
options.log({
|
|
8010
|
+
level: "warn",
|
|
8011
|
+
message: `Vault file ${file.id} content hash mismatch: expected ${file.content_sha256}, received ${actualSha256} \u2014 retrying on the next drain`
|
|
8012
|
+
});
|
|
8013
|
+
return false;
|
|
8014
|
+
}
|
|
8015
|
+
const outcome = await applyVaultFile(options, file, content);
|
|
8016
|
+
const entry = {
|
|
8017
|
+
sha256: file.content_sha256,
|
|
8018
|
+
outcome: outcome.ok ? "applied" : "refused",
|
|
8019
|
+
reason: outcome.ok ? null : outcome.code,
|
|
8020
|
+
reported: false,
|
|
8021
|
+
reportAttempts: 0
|
|
8022
|
+
};
|
|
8023
|
+
options.outcomes.set(file.id, entry);
|
|
8024
|
+
await reportOutcome(options, file, entry);
|
|
8025
|
+
return outcome.ok;
|
|
8026
|
+
}
|
|
8027
|
+
async function downloadVaultFile(options, file) {
|
|
8028
|
+
try {
|
|
8029
|
+
const response = await options.fetchImpl(
|
|
8030
|
+
`${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/content`,
|
|
8031
|
+
{ headers: { Authorization: options.getAuthHeader() } }
|
|
8032
|
+
);
|
|
8033
|
+
if (!response.ok) {
|
|
8034
|
+
options.log({
|
|
8035
|
+
level: "warn",
|
|
8036
|
+
message: `Downloading vault file ${file.id} returned HTTP ${response.status} \u2014 retrying on the next drain`
|
|
8037
|
+
});
|
|
8038
|
+
return null;
|
|
8039
|
+
}
|
|
8040
|
+
return Buffer.from(await response.arrayBuffer());
|
|
8041
|
+
} catch (err) {
|
|
8042
|
+
options.log({
|
|
8043
|
+
level: "warn",
|
|
8044
|
+
message: `Downloading vault file ${file.id} failed \u2014 retrying on the next drain: ${describe2(err)}`
|
|
8045
|
+
});
|
|
8046
|
+
return null;
|
|
8047
|
+
}
|
|
8048
|
+
}
|
|
8049
|
+
async function applyVaultFile(options, file, content) {
|
|
8050
|
+
try {
|
|
8051
|
+
const outcome = await writePushedFile({
|
|
8052
|
+
requestedPath: file.destination_path,
|
|
8053
|
+
content,
|
|
8054
|
+
allowedDirectories: options.allowedDirectories,
|
|
8055
|
+
homeDir: options.homeDir
|
|
8056
|
+
});
|
|
8057
|
+
if (!outcome.ok) {
|
|
8058
|
+
options.log({
|
|
8059
|
+
level: "warn",
|
|
8060
|
+
message: `Vault file ${file.id} refused (${outcome.code}): ${outcome.message}`
|
|
8061
|
+
});
|
|
8062
|
+
} else {
|
|
8063
|
+
options.log({
|
|
8064
|
+
level: "info",
|
|
8065
|
+
message: `Vault file ${file.id} applied (${content.byteLength} bytes)`
|
|
8066
|
+
});
|
|
8067
|
+
}
|
|
8068
|
+
return outcome;
|
|
8069
|
+
} catch (err) {
|
|
8070
|
+
options.log({
|
|
8071
|
+
level: "error",
|
|
8072
|
+
message: `Vault file ${file.id} could not be written: ${describe2(err)}`
|
|
8073
|
+
});
|
|
8074
|
+
return { ok: false, code: "write_failed", message: "The runner could not write the file." };
|
|
8075
|
+
}
|
|
8076
|
+
}
|
|
8077
|
+
async function reportOutcome(options, file, entry) {
|
|
8078
|
+
const body = {
|
|
8079
|
+
status: entry.outcome === "applied" ? "applied" : "rejected",
|
|
8080
|
+
reported_sha256: entry.sha256
|
|
8081
|
+
};
|
|
8082
|
+
if (entry.outcome === "refused" && entry.reason !== null) body.reason = entry.reason;
|
|
8083
|
+
try {
|
|
8084
|
+
const response = await options.fetchImpl(
|
|
8085
|
+
`${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/status`,
|
|
8086
|
+
{
|
|
8087
|
+
method: "POST",
|
|
8088
|
+
headers: {
|
|
8089
|
+
Authorization: options.getAuthHeader(),
|
|
8090
|
+
"Content-Type": "application/json"
|
|
8091
|
+
},
|
|
8092
|
+
body: JSON.stringify(body)
|
|
8093
|
+
}
|
|
8094
|
+
);
|
|
8095
|
+
if (response.ok) {
|
|
8096
|
+
entry.reported = true;
|
|
8097
|
+
return;
|
|
8098
|
+
}
|
|
8099
|
+
recordReportFailure(options, file, entry, `HTTP ${response.status}`);
|
|
8100
|
+
} catch (err) {
|
|
8101
|
+
recordReportFailure(options, file, entry, describe2(err));
|
|
8102
|
+
}
|
|
8103
|
+
}
|
|
8104
|
+
function recordReportFailure(options, file, entry, reason) {
|
|
8105
|
+
entry.reportAttempts += 1;
|
|
8106
|
+
options.log({
|
|
8107
|
+
level: "error",
|
|
8108
|
+
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})`
|
|
8109
|
+
});
|
|
8110
|
+
}
|
|
8111
|
+
function describe2(err) {
|
|
8112
|
+
return err instanceof Error ? err.message : String(err);
|
|
8113
|
+
}
|
|
8114
|
+
|
|
8115
|
+
// src/lib/channels/driver.ts
|
|
8116
|
+
function messageIdOf(m) {
|
|
8117
|
+
if (!m || typeof m !== "object") return void 0;
|
|
8118
|
+
if (typeof m.id === "string") return m.id;
|
|
8119
|
+
const infoId = m.info?.id;
|
|
8120
|
+
return typeof infoId === "string" ? infoId : void 0;
|
|
8121
|
+
}
|
|
8122
|
+
function composeAbandonmentNote(finish, replyShape, outcome) {
|
|
8123
|
+
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.");
|
|
8124
|
+
if (replyShape === null) return base;
|
|
8125
|
+
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)` : ""}.`;
|
|
8126
|
+
}
|
|
8127
|
+
function cleanImageMime(contentType) {
|
|
8128
|
+
if (!contentType) return null;
|
|
8129
|
+
const media = contentType.split(";")[0].trim().toLowerCase();
|
|
8130
|
+
return /^image\/[a-z0-9.+-]+$/.test(media) ? media : null;
|
|
8131
|
+
}
|
|
8132
|
+
var LOG_LEVELS = {
|
|
8133
|
+
debug: 0,
|
|
8134
|
+
info: 1,
|
|
8135
|
+
warn: 2,
|
|
8136
|
+
error: 3
|
|
8137
|
+
};
|
|
8138
|
+
var DEFAULT_RETRY_POLICY = {
|
|
8139
|
+
maxAttempts: 6,
|
|
8140
|
+
baseDelayMs: 500,
|
|
8141
|
+
maxDelayMs: 3e4
|
|
8142
|
+
};
|
|
8143
|
+
var SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS = 2e3;
|
|
8144
|
+
var SESSION_ERROR_STREAM_HEALTHY_MS = 5e3;
|
|
8145
|
+
var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
8146
|
+
var MAX_BUFFERED_SESSION_ERRORS = 256;
|
|
8147
|
+
var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
|
|
8148
|
+
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.";
|
|
8149
|
+
var SESSION_ERROR_RETAINED_DETAIL = "Runner-observed OpenCode session error: ";
|
|
8150
|
+
var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
|
|
8151
|
+
var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
8152
|
+
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
8153
|
+
var DRIVER_SESSION_ENUMERATION_LIMIT = 20;
|
|
8154
|
+
var DEFAULT_STUCK_QUEUED_MS = 6e4;
|
|
8155
|
+
var HEARTBEAT_MS = 6e4;
|
|
8156
|
+
var ALIVE_WITHHELD_THRESHOLD_MS = 3 * HEARTBEAT_MS;
|
|
8157
|
+
var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
|
|
8158
|
+
var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
|
|
8159
|
+
var AMBIGUOUS_FINISH_MAX_PINNED_MS = 3 * 6e4;
|
|
8160
|
+
var B2_ABANDONMENT_RECHECK_MS = HEARTBEAT_MS;
|
|
8161
|
+
var POLL_MISS_GRACE_MS = HEARTBEAT_MS;
|
|
8162
|
+
var WATCHER_STALL_MS = 3 * POLL_MISS_GRACE_MS;
|
|
8163
|
+
var MAX_WATCHER_STALL_RESTARTS = 3;
|
|
8164
|
+
var MAX_RELEASED_OPENCODE_IDS = 256;
|
|
8165
|
+
var MAX_SUPERSEDED_CONVERSATIONS = 256;
|
|
8166
|
+
var MAX_IDENTICAL_REDRIVE_POLL_FAILURES = 5;
|
|
8167
|
+
var MAX_NEVER_STARTED_ATTEMPTS = 3;
|
|
8168
|
+
var WEDGE_WARNING_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8169
|
+
var MAX_WEDGED_CONVERSATIONS = 256;
|
|
8170
|
+
var ChannelAuthError = class extends Error {
|
|
8171
|
+
constructor(message) {
|
|
8172
|
+
super(message);
|
|
8173
|
+
this.name = "ChannelAuthError";
|
|
8174
|
+
}
|
|
8175
|
+
};
|
|
8176
|
+
var ChannelTerminalError = class extends Error {
|
|
8177
|
+
status;
|
|
8178
|
+
constructor(message, status2) {
|
|
8179
|
+
super(message);
|
|
8180
|
+
this.name = "ChannelTerminalError";
|
|
8181
|
+
this.status = status2;
|
|
8182
|
+
}
|
|
8183
|
+
};
|
|
8184
|
+
function backoffDelay(attempt, policy) {
|
|
8185
|
+
const exp = policy.baseDelayMs * Math.pow(2, attempt);
|
|
8186
|
+
const capped = Math.min(policy.maxDelayMs, exp);
|
|
8187
|
+
return Math.floor(Math.random() * capped);
|
|
8188
|
+
}
|
|
8189
|
+
function isRetryableStatus2(status2) {
|
|
7636
8190
|
return status2 === 429 || status2 >= 500 && status2 <= 599;
|
|
7637
8191
|
}
|
|
7638
8192
|
var VOLATILE_BODY_FIELD_PATTERN = /("(?:ref|requestId|request_id|traceId|trace_id)"\s*:\s*)"[^"]*"/gi;
|
|
@@ -7966,6 +8520,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7966
8520
|
* re-downloaded and re-written every ~2s until the server expires it.
|
|
7967
8521
|
*/
|
|
7968
8522
|
fileAckFailures = /* @__PURE__ */ new Map();
|
|
8523
|
+
vaultFileOutcomes = /* @__PURE__ */ new Map();
|
|
7969
8524
|
/**
|
|
7970
8525
|
* Monotonic count of files this runner has pulled and written (#559). Only
|
|
7971
8526
|
* ever increases, so `run.ts` detects work by comparing it against the value
|
|
@@ -8123,10 +8678,21 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8123
8678
|
ackFailures: this.fileAckFailures,
|
|
8124
8679
|
log: this.log
|
|
8125
8680
|
});
|
|
8126
|
-
|
|
8681
|
+
const vaultResult = await syncVaultFiles({
|
|
8682
|
+
agentId: this.agentId,
|
|
8683
|
+
apiUrl: this.apiUrl,
|
|
8684
|
+
getAuthHeader: this.getAuthHeader,
|
|
8685
|
+
fetchImpl: this.fetchImpl,
|
|
8686
|
+
allowedDirectories: this.fileSyncDirectories,
|
|
8687
|
+
homeDir: this.homeDir,
|
|
8688
|
+
outcomes: this.vaultFileOutcomes,
|
|
8689
|
+
log: this.log
|
|
8690
|
+
});
|
|
8691
|
+
const applied = result.applied + vaultResult.applied;
|
|
8692
|
+
this.appliedFileCount += applied;
|
|
8127
8693
|
if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
|
|
8128
8694
|
if (result.opencodeAuthApplied) this.opencodeAuthApplyCount += 1;
|
|
8129
|
-
return
|
|
8695
|
+
return applied;
|
|
8130
8696
|
} catch (err) {
|
|
8131
8697
|
this.log({
|
|
8132
8698
|
level: "error",
|
|
@@ -8538,6 +9104,47 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8538
9104
|
agent: message.opencode_agent ?? void 0,
|
|
8539
9105
|
model: message.opencode_model ?? void 0
|
|
8540
9106
|
};
|
|
9107
|
+
const pinnedProviderLoaded = await isPinnedProviderLoaded(
|
|
9108
|
+
this.port,
|
|
9109
|
+
options.model,
|
|
9110
|
+
this.openCodeClient
|
|
9111
|
+
);
|
|
9112
|
+
if (pinnedProviderLoaded === false) {
|
|
9113
|
+
const dispatchedModel = message.opencode_model;
|
|
9114
|
+
const separator = dispatchedModel?.indexOf("/") ?? -1;
|
|
9115
|
+
const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
|
|
9116
|
+
const providerId = hasProviderAndModel ? dispatchedModel.slice(0, separator) : null;
|
|
9117
|
+
const modelId = hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null;
|
|
9118
|
+
const errorMessage3 = `Provider namespace not loaded on this runner: ${message.opencode_model}. Refused before dispatch; no turn was started.`;
|
|
9119
|
+
this.log({
|
|
9120
|
+
level: "error",
|
|
9121
|
+
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`,
|
|
9122
|
+
conversation_id: conv.id,
|
|
9123
|
+
message_id: message.id
|
|
9124
|
+
});
|
|
9125
|
+
try {
|
|
9126
|
+
await this.markFailed(conv.id, message.id, sessionId, errorMessage3, void 0, {
|
|
9127
|
+
classification: {
|
|
9128
|
+
kind: "model_auth",
|
|
9129
|
+
providerId,
|
|
9130
|
+
modelId,
|
|
9131
|
+
reason: "missing"
|
|
9132
|
+
},
|
|
9133
|
+
errorShape: { name: null, statusCode: null }
|
|
9134
|
+
});
|
|
9135
|
+
this.signalDispatchNotStarted(conv, message, "model_provider_absent");
|
|
9136
|
+
} catch (markErr) {
|
|
9137
|
+
this.log({
|
|
9138
|
+
level: "warn",
|
|
9139
|
+
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)}`,
|
|
9140
|
+
conversation_id: conv.id,
|
|
9141
|
+
message_id: message.id
|
|
9142
|
+
});
|
|
9143
|
+
this.signalDispatchNotStarted(conv, message, "failure_unreported");
|
|
9144
|
+
}
|
|
9145
|
+
await this.repairProviderRegistry(conv.id, message.id);
|
|
9146
|
+
break;
|
|
9147
|
+
}
|
|
8541
9148
|
let opencodeMessageId;
|
|
8542
9149
|
let preDispatchUserIds = null;
|
|
8543
9150
|
try {
|
|
@@ -8891,6 +9498,34 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8891
9498
|
if (state === "failed" && isAbortedTerminalReply(messages, ocId ?? "")) {
|
|
8892
9499
|
const ongoing = await this.isSessionOngoing(sessionId);
|
|
8893
9500
|
if (ongoing === false) {
|
|
9501
|
+
const age = this.absoluteAgeForRow(message.processing_started_at);
|
|
9502
|
+
if (age.tripped && age.anchorMs !== null) {
|
|
9503
|
+
try {
|
|
9504
|
+
await this.markFailed(
|
|
9505
|
+
conv.id,
|
|
9506
|
+
message.id,
|
|
9507
|
+
sessionId,
|
|
9508
|
+
this.absoluteCeilingError(message.id, sessionId, age.anchorMs),
|
|
9509
|
+
void 0,
|
|
9510
|
+
{
|
|
9511
|
+
classification: null,
|
|
9512
|
+
errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
|
|
9513
|
+
}
|
|
9514
|
+
);
|
|
9515
|
+
} catch (err) {
|
|
9516
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
9517
|
+
this.log({
|
|
9518
|
+
level: "warn",
|
|
9519
|
+
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)}`,
|
|
9520
|
+
conversation_id: conv.id,
|
|
9521
|
+
message_id: message.id
|
|
9522
|
+
});
|
|
9523
|
+
return "unresolved";
|
|
9524
|
+
}
|
|
9525
|
+
this.clearRedriveUnresolved(message.id);
|
|
9526
|
+
void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
|
|
9527
|
+
return "settled";
|
|
9528
|
+
}
|
|
8894
9529
|
this.log({
|
|
8895
9530
|
level: "info",
|
|
8896
9531
|
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 +9596,29 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8961
9596
|
message_id: message.id
|
|
8962
9597
|
});
|
|
8963
9598
|
}
|
|
9599
|
+
if (this.absoluteAgeForRow(message.processing_started_at).tripped) {
|
|
9600
|
+
try {
|
|
9601
|
+
await this.markFailed(
|
|
9602
|
+
conv.id,
|
|
9603
|
+
message.id,
|
|
9604
|
+
sessionId,
|
|
9605
|
+
this.absoluteCeilingError(message.id, sessionId, anchorMs)
|
|
9606
|
+
);
|
|
9607
|
+
} catch (err) {
|
|
9608
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
9609
|
+
this.log({
|
|
9610
|
+
level: "warn",
|
|
9611
|
+
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)}`,
|
|
9612
|
+
conversation_id: conv.id,
|
|
9613
|
+
message_id: message.id
|
|
9614
|
+
});
|
|
9615
|
+
const bound = await this.boundRedriveOutcome(conv, message, "reattach");
|
|
9616
|
+
return bound === "abandoned" ? "abandoned" : "unresolved";
|
|
9617
|
+
}
|
|
9618
|
+
this.clearRedriveUnresolved(message.id);
|
|
9619
|
+
void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
|
|
9620
|
+
return "abandoned";
|
|
9621
|
+
}
|
|
8964
9622
|
const title = await this.resolveSessionTitle(sessionId, conv.id);
|
|
8965
9623
|
try {
|
|
8966
9624
|
await this.markProcessing(conv.id, message.id, sessionId, ocId, title);
|
|
@@ -9227,8 +9885,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9227
9885
|
const since = this.redriveOutcomeFailingSince.get(message.id);
|
|
9228
9886
|
if (since === void 0) this.redriveOutcomeFailingSince.set(message.id, now);
|
|
9229
9887
|
const durationTripped = now - (since ?? now) >= this.pausedMaxWaitMs;
|
|
9230
|
-
const
|
|
9231
|
-
const absoluteAgeTripped = !Number.isNaN(parsed) && now - parsed >= ABSOLUTE_MAX_PROCESSING_MS;
|
|
9888
|
+
const absoluteAgeTripped = this.absoluteAgeForRow(message.processing_started_at).tripped;
|
|
9232
9889
|
if (!durationTripped && !absoluteAgeTripped) {
|
|
9233
9890
|
this.signalRedriveOutcomeUnreported(conv, message, outcome);
|
|
9234
9891
|
return "retry";
|
|
@@ -9267,6 +9924,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9267
9924
|
});
|
|
9268
9925
|
return "abandoned";
|
|
9269
9926
|
}
|
|
9927
|
+
/** Row-level guards use the durable server stamp; in-flight guards use processingAnchorMs. */
|
|
9928
|
+
absoluteAgeForRow(stamp) {
|
|
9929
|
+
const anchorMs = stamp ? Date.parse(stamp) : NaN;
|
|
9930
|
+
return Number.isNaN(anchorMs) ? { tripped: false, anchorMs: null } : { tripped: this.now() - anchorMs >= ABSOLUTE_MAX_PROCESSING_MS, anchorMs };
|
|
9931
|
+
}
|
|
9932
|
+
absoluteCeilingError(messageId, sessionId, anchorMs, retainedReason = this.readRetainedSessionError(sessionId)) {
|
|
9933
|
+
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.`;
|
|
9934
|
+
return retainedReason ? `${baseError} ${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}` : baseError;
|
|
9935
|
+
}
|
|
9270
9936
|
/**
|
|
9271
9937
|
* Record one poll outcome toward the re-drive fence's consecutive-identical-
|
|
9272
9938
|
* failure streak (#1348) and return the resulting count. `signature === null`
|
|
@@ -9708,10 +10374,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9708
10374
|
opencodeMessageId,
|
|
9709
10375
|
message,
|
|
9710
10376
|
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,
|
|
10377
|
+
processingAnchorMs: this.absoluteAgeForRow(message.processing_started_at).anchorMs ?? processedAtMs,
|
|
9715
10378
|
deadline: watchDeadlineAnchorMs + this.pausedMaxWaitMs,
|
|
9716
10379
|
// The server row is ALREADY `processing`; do not re-fire markProcessing.
|
|
9717
10380
|
started: true,
|
|
@@ -10162,7 +10825,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10162
10825
|
}
|
|
10163
10826
|
const { openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk } = await this.pollInteractions(sessionId, watcher, messages);
|
|
10164
10827
|
if (watcher.generation !== generation) return;
|
|
10828
|
+
const ceilingAttemptsThisTick = /* @__PURE__ */ new Set();
|
|
10165
10829
|
for (const inFlight of [...watcher.inFlight.values()]) {
|
|
10830
|
+
if (ceilingAttemptsThisTick.has(inFlight.evidentMessageId)) continue;
|
|
10166
10831
|
await this.serviceInFlightMessage(
|
|
10167
10832
|
sessionId,
|
|
10168
10833
|
watcher,
|
|
@@ -10171,7 +10836,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10171
10836
|
openQuestions,
|
|
10172
10837
|
openPermissions,
|
|
10173
10838
|
questionsPolledOk,
|
|
10174
|
-
permissionsPolledOk
|
|
10839
|
+
permissionsPolledOk,
|
|
10840
|
+
ceilingAttemptsThisTick
|
|
10175
10841
|
);
|
|
10176
10842
|
}
|
|
10177
10843
|
}
|
|
@@ -10212,13 +10878,117 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10212
10878
|
inFlight.deadline = this.now() + this.pausedMaxWaitMs;
|
|
10213
10879
|
}
|
|
10214
10880
|
}
|
|
10881
|
+
async settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick) {
|
|
10882
|
+
const entries = [...watcher.inFlight.values()];
|
|
10883
|
+
const pastCeiling = entries.filter(
|
|
10884
|
+
(entry) => this.now() - entry.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS
|
|
10885
|
+
);
|
|
10886
|
+
const healthySiblings = entries.length - pastCeiling.length;
|
|
10887
|
+
const landed = [];
|
|
10888
|
+
let allRecordsLanded = true;
|
|
10889
|
+
const retainedReason = this.readRetainedSessionError(sessionId);
|
|
10890
|
+
for (const entry of pastCeiling) {
|
|
10891
|
+
if (!watcher.inFlight.has(entry.evidentMessageId)) continue;
|
|
10892
|
+
ceilingAttemptsThisTick.add(entry.evidentMessageId);
|
|
10893
|
+
const error2 = this.absoluteCeilingError(
|
|
10894
|
+
entry.evidentMessageId,
|
|
10895
|
+
sessionId,
|
|
10896
|
+
entry.processingAnchorMs,
|
|
10897
|
+
retainedReason
|
|
10898
|
+
);
|
|
10899
|
+
this.log({
|
|
10900
|
+
level: "warn",
|
|
10901
|
+
message: error2,
|
|
10902
|
+
conversation_id: watcher.conv.id,
|
|
10903
|
+
message_id: entry.evidentMessageId
|
|
10904
|
+
});
|
|
10905
|
+
try {
|
|
10906
|
+
await this.markFailed(
|
|
10907
|
+
watcher.conv.id,
|
|
10908
|
+
entry.evidentMessageId,
|
|
10909
|
+
sessionId,
|
|
10910
|
+
error2,
|
|
10911
|
+
void 0,
|
|
10912
|
+
{
|
|
10913
|
+
classification: null,
|
|
10914
|
+
errorShape: extractReplyErrorShape(
|
|
10915
|
+
findLastAssistantReplyFor(messages, entry.opencodeMessageId)
|
|
10916
|
+
)
|
|
10917
|
+
}
|
|
10918
|
+
);
|
|
10919
|
+
} catch (err) {
|
|
10920
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
10921
|
+
allRecordsLanded = false;
|
|
10922
|
+
const watched_for_ms = this.now() - entry.processingAnchorMs;
|
|
10923
|
+
if (err instanceof ChannelTerminalError) {
|
|
10924
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10925
|
+
watched_for_ms,
|
|
10926
|
+
interrupt_outcome: "record_terminal"
|
|
10927
|
+
});
|
|
10928
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10929
|
+
continue;
|
|
10930
|
+
}
|
|
10931
|
+
if (this.now() >= entry.deadline) {
|
|
10932
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10933
|
+
watched_for_ms,
|
|
10934
|
+
interrupt_outcome: "record_deadline"
|
|
10935
|
+
});
|
|
10936
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10937
|
+
continue;
|
|
10938
|
+
}
|
|
10939
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10940
|
+
watched_for_ms,
|
|
10941
|
+
interrupt_outcome: "record_retry"
|
|
10942
|
+
});
|
|
10943
|
+
this.log({
|
|
10944
|
+
level: "warn",
|
|
10945
|
+
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)}`,
|
|
10946
|
+
conversation_id: watcher.conv.id,
|
|
10947
|
+
message_id: entry.evidentMessageId
|
|
10948
|
+
});
|
|
10949
|
+
continue;
|
|
10950
|
+
}
|
|
10951
|
+
entry.done = true;
|
|
10952
|
+
landed.push(entry);
|
|
10953
|
+
}
|
|
10954
|
+
if (!allRecordsLanded) {
|
|
10955
|
+
for (const entry of landed) this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10956
|
+
return;
|
|
10957
|
+
}
|
|
10958
|
+
if (healthySiblings > 0) {
|
|
10959
|
+
for (const entry of landed) {
|
|
10960
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10961
|
+
watched_for_ms: this.now() - entry.processingAnchorMs,
|
|
10962
|
+
interrupt_outcome: "deferred_for_healthy_sibling",
|
|
10963
|
+
healthy_siblings: healthySiblings
|
|
10964
|
+
});
|
|
10965
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10966
|
+
}
|
|
10967
|
+
return;
|
|
10968
|
+
}
|
|
10969
|
+
const interrupt = await interruptSession(this.port, sessionId, this.openCodeClient);
|
|
10970
|
+
for (const entry of landed) {
|
|
10971
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10972
|
+
watched_for_ms: this.now() - entry.processingAnchorMs,
|
|
10973
|
+
interrupt_outcome: interrupt.kind
|
|
10974
|
+
});
|
|
10975
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10976
|
+
}
|
|
10977
|
+
if (interrupt.kind !== "confirmed_stopped") {
|
|
10978
|
+
this.log({
|
|
10979
|
+
level: "warn",
|
|
10980
|
+
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`,
|
|
10981
|
+
conversation_id: watcher.conv.id
|
|
10982
|
+
});
|
|
10983
|
+
}
|
|
10984
|
+
}
|
|
10215
10985
|
/**
|
|
10216
10986
|
* Drive ONE in-flight message's lifecycle from the tick's message snapshot.
|
|
10217
10987
|
* Fires markProcessing on queued→running and markDone on done (each once),
|
|
10218
10988
|
* applies the idle-path re-dispatch guard, and removes the message from the
|
|
10219
10989
|
* in-flight set on completion or timeout.
|
|
10220
10990
|
*/
|
|
10221
|
-
async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk) {
|
|
10991
|
+
async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk, ceilingAttemptsThisTick) {
|
|
10222
10992
|
const conv = watcher.conv;
|
|
10223
10993
|
const state = messageRunState(messages, inFlight.opencodeMessageId);
|
|
10224
10994
|
const id = inFlight.evidentMessageId;
|
|
@@ -10557,22 +11327,13 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10557
11327
|
});
|
|
10558
11328
|
void this.postSignal(conv.id, id, "ambiguous_finish_resolved", {
|
|
10559
11329
|
watched_for_ms: pinnedForMs
|
|
10560
|
-
});
|
|
10561
|
-
await this.settleMessageDone(sessionId, watcher, inFlight, messages);
|
|
10562
|
-
return;
|
|
10563
|
-
}
|
|
10564
|
-
}
|
|
10565
|
-
if (activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
10566
|
-
this.
|
|
10567
|
-
level: "warn",
|
|
10568
|
-
message: `Message ${inFlight.evidentMessageId.slice(0, 8)} exceeded the absolute processing ceiling (${Math.round((this.now() - inFlight.processingAnchorMs) / 6e4)}min, session ${sessionId}) while still actively running \u2014 releasing so the cron can reclaim it`,
|
|
10569
|
-
conversation_id: conv.id,
|
|
10570
|
-
message_id: inFlight.evidentMessageId
|
|
10571
|
-
});
|
|
10572
|
-
void this.postSignal(conv.id, inFlight.evidentMessageId, "gave_up", {
|
|
10573
|
-
watched_for_ms: this.now() - inFlight.processingAnchorMs
|
|
10574
|
-
});
|
|
10575
|
-
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
11330
|
+
});
|
|
11331
|
+
await this.settleMessageDone(sessionId, watcher, inFlight, messages);
|
|
11332
|
+
return;
|
|
11333
|
+
}
|
|
11334
|
+
}
|
|
11335
|
+
if (watcher.inFlight.has(inFlight.evidentMessageId) && activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
11336
|
+
await this.settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick);
|
|
10576
11337
|
return;
|
|
10577
11338
|
}
|
|
10578
11339
|
const heartbeatDue = activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS;
|
|
@@ -11195,6 +11956,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11195
11956
|
}
|
|
11196
11957
|
const restartAborted = state === "failed" && sessionOngoing === false && isAbortedTerminalReply(messages, ocId ?? "");
|
|
11197
11958
|
if (restartAborted) {
|
|
11959
|
+
const age = this.absoluteAgeForRow(row.processing_started_at);
|
|
11960
|
+
if (age.tripped && age.anchorMs !== null) {
|
|
11961
|
+
try {
|
|
11962
|
+
await this.markFailed(
|
|
11963
|
+
row.conversation_id,
|
|
11964
|
+
row.id,
|
|
11965
|
+
sessionId,
|
|
11966
|
+
this.absoluteCeilingError(row.id, sessionId, age.anchorMs),
|
|
11967
|
+
void 0,
|
|
11968
|
+
{
|
|
11969
|
+
classification: null,
|
|
11970
|
+
errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
|
|
11971
|
+
}
|
|
11972
|
+
);
|
|
11973
|
+
} catch (err) {
|
|
11974
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
11975
|
+
this.log({
|
|
11976
|
+
level: "warn",
|
|
11977
|
+
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)}`,
|
|
11978
|
+
conversation_id: row.conversation_id,
|
|
11979
|
+
message_id: row.id
|
|
11980
|
+
});
|
|
11981
|
+
return;
|
|
11982
|
+
}
|
|
11983
|
+
void this.postSignal(row.conversation_id, row.id, "redrive_ceiling_refused");
|
|
11984
|
+
return;
|
|
11985
|
+
}
|
|
11198
11986
|
this.log({
|
|
11199
11987
|
level: "info",
|
|
11200
11988
|
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 +12528,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11740
12528
|
source_message_id: row.source_message_id,
|
|
11741
12529
|
slack_user_id: row.slack_user_id,
|
|
11742
12530
|
attachments: row.attachments ?? null,
|
|
11743
|
-
opencode_message_id: row.opencode_message_id
|
|
12531
|
+
opencode_message_id: row.opencode_message_id,
|
|
12532
|
+
processing_started_at: row.processing_started_at
|
|
11744
12533
|
};
|
|
11745
12534
|
}
|
|
11746
12535
|
/**
|
|
@@ -13260,7 +14049,7 @@ Port ${port} is already in use.`));
|
|
|
13260
14049
|
}
|
|
13261
14050
|
|
|
13262
14051
|
// src/lib/runner-credentials.ts
|
|
13263
|
-
import { chmodSync as chmodSync2, writeFileSync as
|
|
14052
|
+
import { chmodSync as chmodSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13264
14053
|
import { spawn as spawn5 } from "node:child_process";
|
|
13265
14054
|
var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
|
|
13266
14055
|
var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
|
|
@@ -13503,8 +14292,8 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
13503
14292
|
}
|
|
13504
14293
|
try {
|
|
13505
14294
|
env.GIT_CONFIG_GLOBAL = GIT_CONFIG_GLOBAL;
|
|
13506
|
-
|
|
13507
|
-
|
|
14295
|
+
writeFileSync5(GIT_CONFIG_GLOBAL, "");
|
|
14296
|
+
writeFileSync5(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
|
|
13508
14297
|
chmodSync2(GIT_CREDENTIAL_HELPER, 448);
|
|
13509
14298
|
const config = [
|
|
13510
14299
|
["user.name", env.GIT_USER_NAME ?? "evident-bot"],
|
|
@@ -13537,7 +14326,23 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
13537
14326
|
// src/lib/opencode/config-overlay.ts
|
|
13538
14327
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
13539
14328
|
import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
|
|
13540
|
-
import { isAbsolute as isAbsolute3, join as
|
|
14329
|
+
import { isAbsolute as isAbsolute3, join as join12, resolve as resolve4 } from "node:path";
|
|
14330
|
+
function parseConfigContent(content) {
|
|
14331
|
+
if (!content?.trim()) return {};
|
|
14332
|
+
const parsed = JSON.parse(content);
|
|
14333
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
14334
|
+
throw new Error("OPENCODE_CONFIG_CONTENT must contain a JSON object");
|
|
14335
|
+
}
|
|
14336
|
+
return parsed;
|
|
14337
|
+
}
|
|
14338
|
+
function mergeManagedOpenCodeConfigContent(content, managedProviders) {
|
|
14339
|
+
const config = parseConfigContent(content);
|
|
14340
|
+
const provider = typeof config.provider === "object" && config.provider !== null && !Array.isArray(config.provider) ? { ...config.provider } : {};
|
|
14341
|
+
for (const namespace of CREDENTIAL_PROXY_NATIVE_NAMESPACES) {
|
|
14342
|
+
delete provider[namespace];
|
|
14343
|
+
}
|
|
14344
|
+
return JSON.stringify({ ...config, provider: { ...provider, ...managedProviders } });
|
|
14345
|
+
}
|
|
13541
14346
|
function isFile(filePath) {
|
|
13542
14347
|
return existsSync2(filePath) && statSync6(filePath).isFile();
|
|
13543
14348
|
}
|
|
@@ -13551,7 +14356,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
13551
14356
|
return;
|
|
13552
14357
|
}
|
|
13553
14358
|
const source = isAbsolute3(overlayPath) ? overlayPath : resolve4(cwd, overlayPath);
|
|
13554
|
-
const target = isFile(
|
|
14359
|
+
const target = isFile(join12(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
|
|
13555
14360
|
if (!isFile(source)) {
|
|
13556
14361
|
log3(
|
|
13557
14362
|
`RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
|
|
@@ -13559,7 +14364,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
13559
14364
|
);
|
|
13560
14365
|
return;
|
|
13561
14366
|
}
|
|
13562
|
-
copyFileSync(source,
|
|
14367
|
+
copyFileSync(source, join12(cwd, target));
|
|
13563
14368
|
try {
|
|
13564
14369
|
execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
|
|
13565
14370
|
stdio: "ignore"
|
|
@@ -13568,11 +14373,164 @@ function applyRunnerOpenCodeConfig({
|
|
|
13568
14373
|
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
13569
14374
|
log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
|
|
13570
14375
|
}
|
|
13571
|
-
log3(`Applied runner OpenCode config ${source} to ${
|
|
14376
|
+
log3(`Applied runner OpenCode config ${source} to ${join12(cwd, target)}`);
|
|
14377
|
+
}
|
|
14378
|
+
|
|
14379
|
+
// src/lib/opencode/runtime-provenance.ts
|
|
14380
|
+
import { readFileSync as readFileSync8, readdirSync } from "node:fs";
|
|
14381
|
+
import { homedir as homedir7 } from "node:os";
|
|
14382
|
+
import { join as join13 } from "node:path";
|
|
14383
|
+
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
|
|
14384
|
+
var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
|
|
14385
|
+
function errorKind(error2) {
|
|
14386
|
+
if (!(error2 instanceof Error)) return "unknown error";
|
|
14387
|
+
const code = "code" in error2 && typeof error2.code === "string" ? ` (${error2.code})` : "";
|
|
14388
|
+
return `${error2.name}${code}`;
|
|
14389
|
+
}
|
|
14390
|
+
function parseRegistryPluginSpec(value) {
|
|
14391
|
+
const separator = value.lastIndexOf("@");
|
|
14392
|
+
if (separator <= 0) return null;
|
|
14393
|
+
const name = value.slice(0, separator);
|
|
14394
|
+
const spec = value.slice(separator + 1);
|
|
14395
|
+
if (!PACKAGE_NAME.test(name) || !REGISTRY_SPEC.test(spec)) return null;
|
|
14396
|
+
return { name, spec };
|
|
14397
|
+
}
|
|
14398
|
+
function parseJsonc(value) {
|
|
14399
|
+
let output = "";
|
|
14400
|
+
let inString = false;
|
|
14401
|
+
let escaped = false;
|
|
14402
|
+
let lineComment = false;
|
|
14403
|
+
let blockComment = false;
|
|
14404
|
+
for (let index = 0; index < value.length; index++) {
|
|
14405
|
+
const character = value[index];
|
|
14406
|
+
const next = value[index + 1];
|
|
14407
|
+
if (lineComment) {
|
|
14408
|
+
if (character === "\n") {
|
|
14409
|
+
lineComment = false;
|
|
14410
|
+
output += character;
|
|
14411
|
+
}
|
|
14412
|
+
continue;
|
|
14413
|
+
}
|
|
14414
|
+
if (blockComment) {
|
|
14415
|
+
if (character === "*" && next === "/") {
|
|
14416
|
+
blockComment = false;
|
|
14417
|
+
index++;
|
|
14418
|
+
} else if (character === "\n") {
|
|
14419
|
+
output += character;
|
|
14420
|
+
}
|
|
14421
|
+
continue;
|
|
14422
|
+
}
|
|
14423
|
+
if (inString) {
|
|
14424
|
+
output += character;
|
|
14425
|
+
if (escaped) escaped = false;
|
|
14426
|
+
else if (character === "\\") escaped = true;
|
|
14427
|
+
else if (character === '"') inString = false;
|
|
14428
|
+
continue;
|
|
14429
|
+
}
|
|
14430
|
+
if (character === '"') {
|
|
14431
|
+
inString = true;
|
|
14432
|
+
output += character;
|
|
14433
|
+
} else if (character === "/" && next === "/") {
|
|
14434
|
+
lineComment = true;
|
|
14435
|
+
index++;
|
|
14436
|
+
} else if (character === "/" && next === "*") {
|
|
14437
|
+
blockComment = true;
|
|
14438
|
+
index++;
|
|
14439
|
+
} else if (character === ",") {
|
|
14440
|
+
let lookahead = index + 1;
|
|
14441
|
+
while (/\s/.test(value[lookahead] ?? "")) lookahead++;
|
|
14442
|
+
if (value[lookahead] !== "]" && value[lookahead] !== "}") output += character;
|
|
14443
|
+
} else {
|
|
14444
|
+
output += character;
|
|
14445
|
+
}
|
|
14446
|
+
}
|
|
14447
|
+
return JSON.parse(output);
|
|
14448
|
+
}
|
|
14449
|
+
function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
|
|
14450
|
+
return join13(env.XDG_CACHE_HOME ?? join13(homeDir, ".cache"), "opencode");
|
|
14451
|
+
}
|
|
14452
|
+
function readConfiguredPluginSpecs({
|
|
14453
|
+
cwd = process.cwd(),
|
|
14454
|
+
log: log3
|
|
14455
|
+
}) {
|
|
14456
|
+
let config;
|
|
14457
|
+
try {
|
|
14458
|
+
try {
|
|
14459
|
+
config = parseJsonc(readFileSync8(join13(cwd, "opencode.jsonc"), "utf8"));
|
|
14460
|
+
} catch (error2) {
|
|
14461
|
+
if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
|
|
14462
|
+
config = parseJsonc(readFileSync8(join13(cwd, "opencode.json"), "utf8"));
|
|
14463
|
+
}
|
|
14464
|
+
} catch (error2) {
|
|
14465
|
+
log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
|
|
14466
|
+
return [];
|
|
14467
|
+
}
|
|
14468
|
+
if (typeof config !== "object" || config === null || !("plugin" in config)) return [];
|
|
14469
|
+
const plugins = config.plugin;
|
|
14470
|
+
if (!Array.isArray(plugins)) {
|
|
14471
|
+
log3("Could not read OpenCode plugin configuration: plugin is not an array");
|
|
14472
|
+
return [];
|
|
14473
|
+
}
|
|
14474
|
+
const configured = plugins.filter(
|
|
14475
|
+
(plugin) => typeof plugin === "string" && parseRegistryPluginSpec(plugin) !== null
|
|
14476
|
+
);
|
|
14477
|
+
if (configured.length !== plugins.length) {
|
|
14478
|
+
log3(
|
|
14479
|
+
`Skipped ${plugins.length - configured.length} non-registry OpenCode plugin spec(s); their values were not recorded`
|
|
14480
|
+
);
|
|
14481
|
+
}
|
|
14482
|
+
return configured;
|
|
14483
|
+
}
|
|
14484
|
+
function resolveInstalledPluginVersions({
|
|
14485
|
+
cacheRoot,
|
|
14486
|
+
env = process.env,
|
|
14487
|
+
homeDir = homedir7(),
|
|
14488
|
+
log: log3
|
|
14489
|
+
}) {
|
|
14490
|
+
const packagesRoot = join13(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
|
|
14491
|
+
let directories;
|
|
14492
|
+
try {
|
|
14493
|
+
directories = readdirSync(packagesRoot, { withFileTypes: true });
|
|
14494
|
+
} catch (error2) {
|
|
14495
|
+
if ("code" in Object(error2) && Object(error2).code === "ENOENT") return [];
|
|
14496
|
+
log3(`Could not inspect the OpenCode plugin cache: ${errorKind(error2)}`);
|
|
14497
|
+
return [];
|
|
14498
|
+
}
|
|
14499
|
+
const plugins = [];
|
|
14500
|
+
let skipped = 0;
|
|
14501
|
+
for (const directory of directories) {
|
|
14502
|
+
if (!directory.isDirectory()) continue;
|
|
14503
|
+
const parsed = parseRegistryPluginSpec(directory.name);
|
|
14504
|
+
if (parsed === null) {
|
|
14505
|
+
skipped++;
|
|
14506
|
+
continue;
|
|
14507
|
+
}
|
|
14508
|
+
let resolvedVersion = null;
|
|
14509
|
+
try {
|
|
14510
|
+
const packageJson = JSON.parse(
|
|
14511
|
+
readFileSync8(
|
|
14512
|
+
join13(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
|
|
14513
|
+
"utf8"
|
|
14514
|
+
)
|
|
14515
|
+
);
|
|
14516
|
+
if (typeof packageJson.version === "string" && REGISTRY_SPEC.test(packageJson.version)) {
|
|
14517
|
+
resolvedVersion = packageJson.version;
|
|
14518
|
+
} else {
|
|
14519
|
+
log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: invalid version`);
|
|
14520
|
+
}
|
|
14521
|
+
} catch (error2) {
|
|
14522
|
+
log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: ${errorKind(error2)}`);
|
|
14523
|
+
}
|
|
14524
|
+
plugins.push({ ...parsed, resolvedVersion });
|
|
14525
|
+
}
|
|
14526
|
+
if (skipped > 0) {
|
|
14527
|
+
log3(`Skipped ${skipped} non-registry OpenCode package cache entry; its value was not recorded`);
|
|
14528
|
+
}
|
|
14529
|
+
return plugins;
|
|
13572
14530
|
}
|
|
13573
14531
|
|
|
13574
14532
|
// src/lib/credential-sync.ts
|
|
13575
|
-
import { renameSync, writeFileSync as
|
|
14533
|
+
import { renameSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
13576
14534
|
var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
|
|
13577
14535
|
var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
|
|
13578
14536
|
var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
|
|
@@ -13606,7 +14564,7 @@ function writeMarker(markerPath, outcomes, log3) {
|
|
|
13606
14564
|
`;
|
|
13607
14565
|
const temporaryPath = `${markerPath}.tmp`;
|
|
13608
14566
|
try {
|
|
13609
|
-
|
|
14567
|
+
writeFileSync6(temporaryPath, body, { mode: 384 });
|
|
13610
14568
|
renameSync(temporaryPath, markerPath);
|
|
13611
14569
|
} catch (error2) {
|
|
13612
14570
|
log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
|
|
@@ -13806,6 +14764,255 @@ function createCredentialSync({
|
|
|
13806
14764
|
};
|
|
13807
14765
|
}
|
|
13808
14766
|
|
|
14767
|
+
// src/lib/managed-inference-setup.ts
|
|
14768
|
+
var defaultStore = {
|
|
14769
|
+
get: getManagedInferenceCredential,
|
|
14770
|
+
set: setManagedInferenceCredential,
|
|
14771
|
+
clear: clearManagedInferenceCredential
|
|
14772
|
+
};
|
|
14773
|
+
function endpoint(options, operation) {
|
|
14774
|
+
return `${options.apiUrl}/runners/${options.agentId}/managed-credentials/${operation}`;
|
|
14775
|
+
}
|
|
14776
|
+
function requestHeaders(options) {
|
|
14777
|
+
return { Authorization: options.getAuthHeader(), "Content-Type": "application/json" };
|
|
14778
|
+
}
|
|
14779
|
+
function request2(options) {
|
|
14780
|
+
return withRequestTimeout(options.fetchImpl ?? fetch, REQUEST_TIMEOUT_MS);
|
|
14781
|
+
}
|
|
14782
|
+
function safeFailure(operation, status2) {
|
|
14783
|
+
return status2 === void 0 ? `${operation} could not reach the managed credential service` : `${operation} returned HTTP ${status2}`;
|
|
14784
|
+
}
|
|
14785
|
+
function logTransportFailure(options, operation, error2, authorization) {
|
|
14786
|
+
let detail = error2 instanceof Error ? error2.message : String(error2);
|
|
14787
|
+
const credential = authorization.split(/\s+/, 2)[1];
|
|
14788
|
+
for (const secret of [authorization, credential]) {
|
|
14789
|
+
if (secret) detail = detail.replaceAll(secret, "[redacted]");
|
|
14790
|
+
}
|
|
14791
|
+
options.log(
|
|
14792
|
+
`MANAGED-INFERENCE-TRANSPORT-FAILED: operation=${operation} detail=${redactDiagnosticText(detail)}`,
|
|
14793
|
+
"debug"
|
|
14794
|
+
);
|
|
14795
|
+
}
|
|
14796
|
+
async function responseJson(response) {
|
|
14797
|
+
try {
|
|
14798
|
+
const value = await response.json();
|
|
14799
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
14800
|
+
} catch (error2) {
|
|
14801
|
+
return null;
|
|
14802
|
+
}
|
|
14803
|
+
}
|
|
14804
|
+
function readSetup(value) {
|
|
14805
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
14806
|
+
const setup = value;
|
|
14807
|
+
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) {
|
|
14808
|
+
return null;
|
|
14809
|
+
}
|
|
14810
|
+
return setup;
|
|
14811
|
+
}
|
|
14812
|
+
function clearLocalSetup(options) {
|
|
14813
|
+
const env = options.env ?? process.env;
|
|
14814
|
+
const store = options.store ?? defaultStore;
|
|
14815
|
+
const stored = store.get(options.agentId);
|
|
14816
|
+
store.clear(options.agentId);
|
|
14817
|
+
const token = env.EVIDENT_PROXY_TOKEN;
|
|
14818
|
+
if (token !== void 0 && token === stored?.token) {
|
|
14819
|
+
delete env.EVIDENT_PROXY_TOKEN;
|
|
14820
|
+
} else if (token !== void 0) {
|
|
14821
|
+
options.log(
|
|
14822
|
+
`MANAGED-INFERENCE-CLEANUP-PRESERVED-EXTERNAL-TOKEN: runner=${options.agentId}`,
|
|
14823
|
+
"debug"
|
|
14824
|
+
);
|
|
14825
|
+
}
|
|
14826
|
+
}
|
|
14827
|
+
async function discoverManagedInferenceSetup(options) {
|
|
14828
|
+
const headers = requestHeaders(options);
|
|
14829
|
+
let response;
|
|
14830
|
+
try {
|
|
14831
|
+
response = await request2(options)(endpoint(options, "current"), {
|
|
14832
|
+
headers
|
|
14833
|
+
});
|
|
14834
|
+
} catch (error2) {
|
|
14835
|
+
logTransportFailure(options, "discovery", error2, headers.Authorization);
|
|
14836
|
+
return { kind: "failed", reason: safeFailure("Managed setup discovery") };
|
|
14837
|
+
}
|
|
14838
|
+
if (response.status === 404) {
|
|
14839
|
+
clearLocalSetup(options);
|
|
14840
|
+
return { kind: "none" };
|
|
14841
|
+
}
|
|
14842
|
+
if (!response.ok) {
|
|
14843
|
+
return { kind: "failed", reason: safeFailure("Managed setup discovery", response.status) };
|
|
14844
|
+
}
|
|
14845
|
+
const body = await responseJson(response);
|
|
14846
|
+
const setup = readSetup(body?.setup);
|
|
14847
|
+
return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed setup discovery returned an invalid response" };
|
|
14848
|
+
}
|
|
14849
|
+
async function enrollManagedInferenceSetup(options) {
|
|
14850
|
+
const response = await post(options, "enroll", {});
|
|
14851
|
+
if (!response) {
|
|
14852
|
+
return { kind: "failed", reason: safeFailure("Managed credential enrollment") };
|
|
14853
|
+
}
|
|
14854
|
+
if (response.status === 404) {
|
|
14855
|
+
return { kind: "none" };
|
|
14856
|
+
}
|
|
14857
|
+
const body = await responseJson(response);
|
|
14858
|
+
if (!response.ok) {
|
|
14859
|
+
const serverError = body?.error;
|
|
14860
|
+
return {
|
|
14861
|
+
kind: "failed",
|
|
14862
|
+
reason: typeof serverError === "string" && serverError.trim() ? redactDiagnosticText(serverError).slice(0, 500) : safeFailure("Managed credential enrollment", response.status)
|
|
14863
|
+
};
|
|
14864
|
+
}
|
|
14865
|
+
const setup = readSetup(body?.setup);
|
|
14866
|
+
return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed credential enrollment returned an invalid response" };
|
|
14867
|
+
}
|
|
14868
|
+
async function post(options, operation, body) {
|
|
14869
|
+
const headers = requestHeaders(options);
|
|
14870
|
+
try {
|
|
14871
|
+
return await request2(options)(endpoint(options, operation), {
|
|
14872
|
+
method: "POST",
|
|
14873
|
+
headers,
|
|
14874
|
+
body: JSON.stringify(body)
|
|
14875
|
+
});
|
|
14876
|
+
} catch (error2) {
|
|
14877
|
+
logTransportFailure(options, operation, error2, headers.Authorization);
|
|
14878
|
+
return null;
|
|
14879
|
+
}
|
|
14880
|
+
}
|
|
14881
|
+
function applyCredential(options, credential) {
|
|
14882
|
+
const env = options.env ?? process.env;
|
|
14883
|
+
let merged;
|
|
14884
|
+
if (options.autoConfigureCredentialsProxy) {
|
|
14885
|
+
const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
|
|
14886
|
+
inferenceBaseUrl: credential.inferenceBaseUrl,
|
|
14887
|
+
modelDescriptors: credential.modelDescriptors
|
|
14888
|
+
});
|
|
14889
|
+
if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
|
|
14890
|
+
return "Managed setup contains unsupported provider descriptors";
|
|
14891
|
+
}
|
|
14892
|
+
try {
|
|
14893
|
+
merged = mergeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT, config.provider);
|
|
14894
|
+
} catch (error2) {
|
|
14895
|
+
return "OPENCODE_CONFIG_CONTENT is malformed; managed providers were not installed";
|
|
14896
|
+
}
|
|
14897
|
+
}
|
|
14898
|
+
try {
|
|
14899
|
+
(options.store ?? defaultStore).set(options.agentId, credential);
|
|
14900
|
+
} catch (error2) {
|
|
14901
|
+
const detail = (error2 instanceof Error ? error2.message : String(error2)).replaceAll(
|
|
14902
|
+
credential.token,
|
|
14903
|
+
"[redacted]"
|
|
14904
|
+
);
|
|
14905
|
+
options.log(
|
|
14906
|
+
`MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation} detail=${redactDiagnosticText(detail)}`,
|
|
14907
|
+
"warn"
|
|
14908
|
+
);
|
|
14909
|
+
return "Managed credentials could not be written to the protected store";
|
|
14910
|
+
}
|
|
14911
|
+
env.EVIDENT_PROXY_TOKEN = credential.token;
|
|
14912
|
+
if (merged !== void 0) env.OPENCODE_CONFIG_CONTENT = merged;
|
|
14913
|
+
return null;
|
|
14914
|
+
}
|
|
14915
|
+
async function reportManagedInferenceApplyFailure(options, failure) {
|
|
14916
|
+
const response = await post(options, "report-failure", {
|
|
14917
|
+
generation: failure.generation,
|
|
14918
|
+
token_id: failure.tokenId
|
|
14919
|
+
});
|
|
14920
|
+
if (!response?.ok) {
|
|
14921
|
+
options.log(
|
|
14922
|
+
`MANAGED-INFERENCE-REPORT-FAILED: runner=${options.agentId} generation=${failure.generation}${response ? ` status=${response.status}` : ""}`,
|
|
14923
|
+
"warn"
|
|
14924
|
+
);
|
|
14925
|
+
}
|
|
14926
|
+
}
|
|
14927
|
+
async function reaffirmStored(options, credential) {
|
|
14928
|
+
const response = await post(options, "reaffirm", {
|
|
14929
|
+
generation: credential.generation,
|
|
14930
|
+
token_id: credential.tokenId
|
|
14931
|
+
});
|
|
14932
|
+
if (!response?.ok) {
|
|
14933
|
+
return {
|
|
14934
|
+
kind: "failed",
|
|
14935
|
+
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")
|
|
14936
|
+
};
|
|
14937
|
+
}
|
|
14938
|
+
const body = await responseJson(response);
|
|
14939
|
+
const setup = readSetup(body?.setup);
|
|
14940
|
+
if (!setup || setup.generation !== credential.generation || setup.token_id !== credential.tokenId) {
|
|
14941
|
+
return { kind: "failed", reason: "Managed credential reaffirmation returned a mismatch" };
|
|
14942
|
+
}
|
|
14943
|
+
const failure = applyCredential(options, credential);
|
|
14944
|
+
return failure ? { kind: "failed", reason: failure } : { kind: "ready", generation: credential.generation, tokenId: credential.tokenId };
|
|
14945
|
+
}
|
|
14946
|
+
async function prepareManagedInferenceSetup(options, discovery, health) {
|
|
14947
|
+
if (health.authFailed) {
|
|
14948
|
+
return {
|
|
14949
|
+
kind: "failed",
|
|
14950
|
+
reason: "OpenCode authentication failed; managed credentials were not rotated"
|
|
14951
|
+
};
|
|
14952
|
+
}
|
|
14953
|
+
const stored = (options.store ?? defaultStore).get(options.agentId);
|
|
14954
|
+
if (health.healthy) {
|
|
14955
|
+
if (!stored) {
|
|
14956
|
+
return {
|
|
14957
|
+
kind: "failed",
|
|
14958
|
+
reason: "Managed credentials are missing for the serving OpenCode process; restart required"
|
|
14959
|
+
};
|
|
14960
|
+
}
|
|
14961
|
+
return reaffirmStored(options, stored);
|
|
14962
|
+
}
|
|
14963
|
+
if (discovery.setup.acknowledged_at !== null && stored?.generation === discovery.setup.generation && stored.tokenId === discovery.setup.token_id) {
|
|
14964
|
+
return reaffirmStored(options, stored);
|
|
14965
|
+
}
|
|
14966
|
+
const response = await post(options, "bootstrap", { generation: discovery.setup.generation });
|
|
14967
|
+
if (!response?.ok) {
|
|
14968
|
+
return {
|
|
14969
|
+
kind: "failed",
|
|
14970
|
+
reason: response ? safeFailure("Managed credential bootstrap", response.status) : safeFailure("Managed credential bootstrap")
|
|
14971
|
+
};
|
|
14972
|
+
}
|
|
14973
|
+
const body = await responseJson(response);
|
|
14974
|
+
const setup = readSetup(body?.setup);
|
|
14975
|
+
const token = body?.inference_token;
|
|
14976
|
+
const inferenceBaseUrl = body?.inference_base_url;
|
|
14977
|
+
const modelDescriptors = body?.model_descriptors;
|
|
14978
|
+
if (!setup || setup.generation !== discovery.setup.generation || typeof setup.token_id !== "string" || typeof token !== "string" || typeof inferenceBaseUrl !== "string" || !Array.isArray(modelDescriptors) || !modelDescriptors.every(
|
|
14979
|
+
(descriptor) => typeof descriptor === "object" && descriptor !== null && typeof descriptor.id === "string" && typeof descriptor.provider === "string"
|
|
14980
|
+
)) {
|
|
14981
|
+
return { kind: "failed", reason: "Managed credential bootstrap returned an invalid response" };
|
|
14982
|
+
}
|
|
14983
|
+
const credential = {
|
|
14984
|
+
generation: setup.generation,
|
|
14985
|
+
tokenId: setup.token_id,
|
|
14986
|
+
token,
|
|
14987
|
+
inferenceBaseUrl,
|
|
14988
|
+
modelDescriptors
|
|
14989
|
+
};
|
|
14990
|
+
const failure = applyCredential(options, credential);
|
|
14991
|
+
if (failure) {
|
|
14992
|
+
await reportManagedInferenceApplyFailure(options, credential);
|
|
14993
|
+
return { kind: "failed", reason: failure };
|
|
14994
|
+
}
|
|
14995
|
+
return { kind: "ready", generation: credential.generation, tokenId: credential.tokenId };
|
|
14996
|
+
}
|
|
14997
|
+
async function acknowledgeManagedInferenceSetup(options, preparation) {
|
|
14998
|
+
const response = await post(options, "acknowledge", {
|
|
14999
|
+
generation: preparation.generation,
|
|
15000
|
+
token_id: preparation.tokenId,
|
|
15001
|
+
config_validated: options.autoConfigureCredentialsProxy === true
|
|
15002
|
+
});
|
|
15003
|
+
if (!response?.ok) {
|
|
15004
|
+
throw new Error(
|
|
15005
|
+
response ? safeFailure("Managed credential acknowledgment", response.status) : safeFailure("Managed credential acknowledgment")
|
|
15006
|
+
);
|
|
15007
|
+
}
|
|
15008
|
+
const body = await responseJson(response);
|
|
15009
|
+
const setup = readSetup(body?.setup);
|
|
15010
|
+
if (!setup || setup.generation !== preparation.generation || setup.token_id !== preparation.tokenId) {
|
|
15011
|
+
throw new Error("Managed credential acknowledgment returned a mismatch");
|
|
15012
|
+
}
|
|
15013
|
+
return response.status === 200 && setup.status === "acknowledged" ? "acknowledged" : "pending";
|
|
15014
|
+
}
|
|
15015
|
+
|
|
13809
15016
|
// src/commands/run.ts
|
|
13810
15017
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
13811
15018
|
var MAX_DISCONNECT_REASON_LENGTH = 200;
|
|
@@ -13846,7 +15053,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
13846
15053
|
if (trimmed === "") {
|
|
13847
15054
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
13848
15055
|
}
|
|
13849
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
15056
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join14(homeDir, trimmed.slice(2)) : trimmed;
|
|
13850
15057
|
if (!isAbsolute4(expanded)) {
|
|
13851
15058
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
13852
15059
|
}
|
|
@@ -14001,7 +15208,7 @@ function logActivity(state, entry) {
|
|
|
14001
15208
|
}
|
|
14002
15209
|
function reportSessionDbRecovery(state) {
|
|
14003
15210
|
try {
|
|
14004
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
15211
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir8(), env: process.env });
|
|
14005
15212
|
for (const record of report.records) {
|
|
14006
15213
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
14007
15214
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -14082,7 +15289,7 @@ async function importOpenCodeV2CredentialsForState(state) {
|
|
|
14082
15289
|
}
|
|
14083
15290
|
const result = await importOpenCodeV2Credentials({
|
|
14084
15291
|
request: client.request,
|
|
14085
|
-
homeDir:
|
|
15292
|
+
homeDir: homedir8(),
|
|
14086
15293
|
directory,
|
|
14087
15294
|
guard: state.opencodeV2CredentialImportGuard,
|
|
14088
15295
|
log: (message, level = "info") => logActivity(state, { type: "info", level, message })
|
|
@@ -14502,7 +15709,7 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
14502
15709
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
14503
15710
|
}
|
|
14504
15711
|
function sessionDbPath() {
|
|
14505
|
-
return
|
|
15712
|
+
return join14(homedir8(), ".local", "share", "opencode", "opencode.db");
|
|
14506
15713
|
}
|
|
14507
15714
|
function logSessionCleanupActivity(state, phase, entry) {
|
|
14508
15715
|
logActivity(state, {
|
|
@@ -14511,6 +15718,47 @@ function logSessionCleanupActivity(state, phase, entry) {
|
|
|
14511
15718
|
metadata: { ...entry.metadata, phase }
|
|
14512
15719
|
});
|
|
14513
15720
|
}
|
|
15721
|
+
async function drainSessionDeletionReports(state) {
|
|
15722
|
+
const path = sessionDeletionReportPath(homedir8(), process.env);
|
|
15723
|
+
let delivered = 0;
|
|
15724
|
+
let failure = null;
|
|
15725
|
+
try {
|
|
15726
|
+
const spool = readSessionDeletionRecords(path);
|
|
15727
|
+
if (spool.readError !== null) {
|
|
15728
|
+
failure = `could not read spool: ${spool.readError}`;
|
|
15729
|
+
} else if (spool.records.length === 0) {
|
|
15730
|
+
return;
|
|
15731
|
+
} else if (!state.agentId || !state.authHeader) {
|
|
15732
|
+
failure = "runner authentication is not available";
|
|
15733
|
+
} else {
|
|
15734
|
+
for (let offset = 0; offset < spool.records.length; offset += 200) {
|
|
15735
|
+
const batch = spool.records.slice(offset, offset + 200);
|
|
15736
|
+
const result = await reportSessionDeletions(state.agentId, state.authHeader, batch);
|
|
15737
|
+
if (!result.ok) {
|
|
15738
|
+
failure = result.error ?? "unknown API error";
|
|
15739
|
+
break;
|
|
15740
|
+
}
|
|
15741
|
+
removeDeliveredSessionDeletionRecords(path, batch);
|
|
15742
|
+
delivered += batch.length;
|
|
15743
|
+
}
|
|
15744
|
+
}
|
|
15745
|
+
} catch (error2) {
|
|
15746
|
+
failure = error2 instanceof Error ? error2.message : String(error2);
|
|
15747
|
+
}
|
|
15748
|
+
const pending = readSessionDeletionRecords(path);
|
|
15749
|
+
const pendingCount = pending.readError === null ? pending.records.length : null;
|
|
15750
|
+
const message = `Session deletion reports: delivered ${delivered}, ${pendingCount === null ? "pending count unavailable" : `${pendingCount} pending`}` + (failure === null ? "." : `; delivery failed: ${failure}.`);
|
|
15751
|
+
logActivity(state, {
|
|
15752
|
+
type: "info",
|
|
15753
|
+
level: failure === null ? "info" : "warn",
|
|
15754
|
+
message,
|
|
15755
|
+
metadata: {
|
|
15756
|
+
outcome: failure === null ? "session-deletion-reports-drained" : "session-deletion-reports-pending",
|
|
15757
|
+
delivered,
|
|
15758
|
+
pending: pendingCount
|
|
15759
|
+
}
|
|
15760
|
+
});
|
|
15761
|
+
}
|
|
14514
15762
|
function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
|
|
14515
15763
|
const record = {
|
|
14516
15764
|
v: 1,
|
|
@@ -14568,17 +15816,22 @@ async function runSweep(state, driver, config) {
|
|
|
14568
15816
|
let failedThrew = 0;
|
|
14569
15817
|
let skippedNewlyActive = 0;
|
|
14570
15818
|
let remaining = 0;
|
|
15819
|
+
const deletionRecords = [];
|
|
14571
15820
|
let protectedNow = driver.protectedSessionIds();
|
|
14572
15821
|
if (sessions !== null) {
|
|
14573
|
-
const
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
protectedIds: protectedNow
|
|
14580
|
-
}
|
|
15822
|
+
const snapshots = sessions.map((session) => ({
|
|
15823
|
+
id: session.id,
|
|
15824
|
+
lastActivityMs: sessionLastActivityMs(session)
|
|
15825
|
+
}));
|
|
15826
|
+
const lastActivityById = new Map(
|
|
15827
|
+
snapshots.map(({ id, lastActivityMs }) => [id, lastActivityMs])
|
|
14581
15828
|
);
|
|
15829
|
+
const toDelete = selectSessionsToDelete(snapshots, {
|
|
15830
|
+
maxAgeMs: config.maxAgeMs,
|
|
15831
|
+
maxCount: config.maxCount,
|
|
15832
|
+
nowMs: Date.now(),
|
|
15833
|
+
protectedIds: protectedNow
|
|
15834
|
+
});
|
|
14582
15835
|
const eligibleToDelete = toDelete.filter(
|
|
14583
15836
|
(id) => (state.sessionDeleteFailures.get(id)?.isolatedFailures ?? 0) < SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES
|
|
14584
15837
|
);
|
|
@@ -14598,6 +15851,17 @@ async function runSweep(state, driver, config) {
|
|
|
14598
15851
|
const outcome = await deleteSession(state.port, id, state.opencodeClient ?? void 0);
|
|
14599
15852
|
if (outcome.ok) {
|
|
14600
15853
|
deleted++;
|
|
15854
|
+
const lastActivityMs = lastActivityById.get(id) ?? null;
|
|
15855
|
+
deletionRecords.push({
|
|
15856
|
+
opencode_session_id: id,
|
|
15857
|
+
deleted_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15858
|
+
reason: "auto_cleanup",
|
|
15859
|
+
policy: {
|
|
15860
|
+
max_age_ms: config.maxAgeMs ?? null,
|
|
15861
|
+
max_count: config.maxCount ?? null,
|
|
15862
|
+
last_activity_at: lastActivityMs === null ? null : new Date(lastActivityMs).toISOString()
|
|
15863
|
+
}
|
|
15864
|
+
});
|
|
14601
15865
|
} else if (outcome.status === "threw") {
|
|
14602
15866
|
failed++;
|
|
14603
15867
|
failedThrew++;
|
|
@@ -14653,6 +15917,32 @@ async function runSweep(state, driver, config) {
|
|
|
14653
15917
|
message: `Session cleanup: inspected ${sessions.length}, deleted ${deleted}${failedNote}${skippedNote}${remainingNote} (${mode})`
|
|
14654
15918
|
});
|
|
14655
15919
|
}
|
|
15920
|
+
if (deletionRecords.length > 0) {
|
|
15921
|
+
try {
|
|
15922
|
+
const dropped = appendSessionDeletionRecords(
|
|
15923
|
+
sessionDeletionReportPath(homedir8(), process.env),
|
|
15924
|
+
deletionRecords
|
|
15925
|
+
);
|
|
15926
|
+
if (dropped > 0) {
|
|
15927
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
15928
|
+
type: "info",
|
|
15929
|
+
level: "warn",
|
|
15930
|
+
message: `Session cleanup: dropped ${dropped} oldest pending deletion report(s) after reaching the spool limit`,
|
|
15931
|
+
metadata: { outcome: "deletion-reports-dropped", dropped_count: dropped }
|
|
15932
|
+
});
|
|
15933
|
+
}
|
|
15934
|
+
} catch (error2) {
|
|
15935
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
15936
|
+
type: "info",
|
|
15937
|
+
level: "warn",
|
|
15938
|
+
message: `Session cleanup: could not persist ${deletionRecords.length} confirmed deletion report(s): ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
15939
|
+
metadata: {
|
|
15940
|
+
outcome: "deletion-report-spool-write-failed",
|
|
15941
|
+
records: deletionRecords.length
|
|
15942
|
+
}
|
|
15943
|
+
});
|
|
15944
|
+
}
|
|
15945
|
+
}
|
|
14656
15946
|
const sessionMetadata = {
|
|
14657
15947
|
sessions_inspected: sessions?.length ?? null,
|
|
14658
15948
|
sessions_deleted: deleted,
|
|
@@ -14722,6 +16012,7 @@ async function runSweep(state, driver, config) {
|
|
|
14722
16012
|
}
|
|
14723
16013
|
});
|
|
14724
16014
|
}
|
|
16015
|
+
await drainSessionDeletionReports(state);
|
|
14725
16016
|
} catch (error2) {
|
|
14726
16017
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
14727
16018
|
logSessionCleanupActivity(state, "sweep", {
|
|
@@ -14746,7 +16037,7 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
14746
16037
|
for (const warning2 of config.warnings) {
|
|
14747
16038
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
14748
16039
|
}
|
|
14749
|
-
const dbBytes = statSessionDbBytes(
|
|
16040
|
+
const dbBytes = statSessionDbBytes(homedir8());
|
|
14750
16041
|
void (async () => {
|
|
14751
16042
|
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
14752
16043
|
if (reclaimAvailability !== null) {
|
|
@@ -14965,7 +16256,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14965
16256
|
});
|
|
14966
16257
|
return;
|
|
14967
16258
|
}
|
|
14968
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
16259
|
+
const { collect, stop } = createResourceUsageCollector(homedir8());
|
|
14969
16260
|
state.stopResourceUsageSampling = stop;
|
|
14970
16261
|
let consecutiveFailures = 0;
|
|
14971
16262
|
const tick = async () => {
|
|
@@ -15206,7 +16497,7 @@ async function run(options) {
|
|
|
15206
16497
|
let fileSyncDirectories;
|
|
15207
16498
|
try {
|
|
15208
16499
|
logLevel = resolveLogLevel(options);
|
|
15209
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
16500
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir8());
|
|
15210
16501
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
15211
16502
|
throw new Error(
|
|
15212
16503
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -15492,7 +16783,7 @@ async function run(options) {
|
|
|
15492
16783
|
state.credentialSync?.arm();
|
|
15493
16784
|
const dependencyInstallDiagnosticReporter = createDependencyInstallDiagnosticReporter({
|
|
15494
16785
|
context: {
|
|
15495
|
-
homeDir:
|
|
16786
|
+
homeDir: homedir8(),
|
|
15496
16787
|
cwd: process.cwd(),
|
|
15497
16788
|
microvmId: process.env.MICROVM_ID?.trim(),
|
|
15498
16789
|
euid: process.getuid?.(),
|
|
@@ -15515,7 +16806,7 @@ async function run(options) {
|
|
|
15515
16806
|
dependencyInstallDiagnosticReporter
|
|
15516
16807
|
);
|
|
15517
16808
|
const openCodeLogTail = tailOpenCodeLogFile(
|
|
15518
|
-
resolveOpenCodeLogPath(
|
|
16809
|
+
resolveOpenCodeLogPath(homedir8(), process.env),
|
|
15519
16810
|
openCodeActivityForwarder
|
|
15520
16811
|
);
|
|
15521
16812
|
state.stopOpenCodeLogTail = () => {
|
|
@@ -15564,6 +16855,7 @@ async function run(options) {
|
|
|
15564
16855
|
}
|
|
15565
16856
|
}
|
|
15566
16857
|
reportSessionDbRecovery(state);
|
|
16858
|
+
await drainSessionDeletionReports(state);
|
|
15567
16859
|
if (sessionDbVerifyFatal) {
|
|
15568
16860
|
throw new Error(
|
|
15569
16861
|
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 +16882,44 @@ async function run(options) {
|
|
|
15590
16882
|
for (const warning2 of opencodeVersionWarnings) {
|
|
15591
16883
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
15592
16884
|
}
|
|
16885
|
+
const managedSetupOptions = {
|
|
16886
|
+
agentId: state.agentId,
|
|
16887
|
+
apiUrl: getApiUrlConfig(),
|
|
16888
|
+
getAuthHeader: () => state.authHeader,
|
|
16889
|
+
autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
|
|
16890
|
+
log: (message, level = "info") => logActivity(state, {
|
|
16891
|
+
type: level === "error" ? "error" : "info",
|
|
16892
|
+
level,
|
|
16893
|
+
...level === "error" ? { error: message } : { message }
|
|
16894
|
+
})
|
|
16895
|
+
};
|
|
16896
|
+
let managedDiscovery = await discoverManagedInferenceSetup(managedSetupOptions);
|
|
16897
|
+
if (managedDiscovery.kind === "none") {
|
|
16898
|
+
managedDiscovery = await enrollManagedInferenceSetup(managedSetupOptions);
|
|
16899
|
+
}
|
|
16900
|
+
let managedPreparation = { kind: "none" };
|
|
16901
|
+
if (managedDiscovery.kind === "failed") {
|
|
16902
|
+
managedPreparation = managedDiscovery;
|
|
16903
|
+
managedSetupOptions.log(`MANAGED-INFERENCE-NOT-READY: ${managedDiscovery.reason}`, "warn");
|
|
16904
|
+
} else if (managedDiscovery.kind === "setup") {
|
|
16905
|
+
const managedHealth = opencodeVersion === "v2" ? await checkOpenCode2Health(state.port) : await checkOpenCodeHealth(state.port);
|
|
16906
|
+
managedPreparation = await prepareManagedInferenceSetup(
|
|
16907
|
+
managedSetupOptions,
|
|
16908
|
+
managedDiscovery,
|
|
16909
|
+
managedHealth
|
|
16910
|
+
);
|
|
16911
|
+
if (managedPreparation.kind === "failed") {
|
|
16912
|
+
managedSetupOptions.log(
|
|
16913
|
+
`MANAGED-INFERENCE-NOT-READY: ${managedPreparation.reason}`,
|
|
16914
|
+
"warn"
|
|
16915
|
+
);
|
|
16916
|
+
} else if (managedPreparation.kind === "ready") {
|
|
16917
|
+
managedSetupOptions.log(
|
|
16918
|
+
`MANAGED-INFERENCE-PREPARED: runner=${state.agentId} generation=${managedPreparation.generation}`,
|
|
16919
|
+
"info"
|
|
16920
|
+
);
|
|
16921
|
+
}
|
|
16922
|
+
}
|
|
15593
16923
|
const { value: maxActiveSessions, warnings: maxActiveSessionsWarnings } = resolveMaxActiveSessions(options, process.env);
|
|
15594
16924
|
for (const warning2 of maxActiveSessionsWarnings) {
|
|
15595
16925
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
@@ -15616,6 +16946,32 @@ async function run(options) {
|
|
|
15616
16946
|
state.opencodeProcess = options.opencodePidFile ? null : oc.process;
|
|
15617
16947
|
state.opencodeVersion = oc.version;
|
|
15618
16948
|
state.opencodeApiVersion = opencodeVersion;
|
|
16949
|
+
let configuredPlugins = [];
|
|
16950
|
+
let resolvedPlugins = [];
|
|
16951
|
+
try {
|
|
16952
|
+
const provenanceLog = (message) => logActivity(state, { type: "info", level: "warn", message });
|
|
16953
|
+
configuredPlugins = readConfiguredPluginSpecs({ log: provenanceLog });
|
|
16954
|
+
resolvedPlugins = resolveInstalledPluginVersions({ log: provenanceLog });
|
|
16955
|
+
} catch (error2) {
|
|
16956
|
+
const kind = error2 instanceof Error ? error2.name : "unknown error";
|
|
16957
|
+
logActivity(state, {
|
|
16958
|
+
type: "info",
|
|
16959
|
+
level: "warn",
|
|
16960
|
+
message: `Could not collect OpenCode runtime provenance: ${kind}`
|
|
16961
|
+
});
|
|
16962
|
+
}
|
|
16963
|
+
logActivity(state, {
|
|
16964
|
+
type: "info",
|
|
16965
|
+
level: "info",
|
|
16966
|
+
message: "OpenCode runtime provenance",
|
|
16967
|
+
metadata: {
|
|
16968
|
+
opencode_version: state.opencodeVersion,
|
|
16969
|
+
opencode_api_version: state.opencodeApiVersion,
|
|
16970
|
+
configured_plugins: JSON.stringify(configuredPlugins),
|
|
16971
|
+
resolved_plugins: JSON.stringify(resolvedPlugins)
|
|
16972
|
+
},
|
|
16973
|
+
source: "cli.runtime-provenance"
|
|
16974
|
+
});
|
|
15619
16975
|
let opencodePassword = null;
|
|
15620
16976
|
if (opencodeVersion === "v2" && "password" in oc) {
|
|
15621
16977
|
const value = oc.password;
|
|
@@ -15630,7 +16986,7 @@ async function run(options) {
|
|
|
15630
16986
|
state.opencodeClient = openCodeClient;
|
|
15631
16987
|
if (options.opencodePidFile && oc.process?.pid !== void 0) {
|
|
15632
16988
|
try {
|
|
15633
|
-
|
|
16989
|
+
writeFileSync7(options.opencodePidFile, `${oc.process.pid}
|
|
15634
16990
|
`, { mode: 384 });
|
|
15635
16991
|
chmodSync3(options.opencodePidFile, 384);
|
|
15636
16992
|
} catch (error2) {
|
|
@@ -15644,7 +17000,7 @@ async function run(options) {
|
|
|
15644
17000
|
const provenance = checkSessionDbProvenance({
|
|
15645
17001
|
dbPath: sessionDbPath(),
|
|
15646
17002
|
currentVersion: state.opencodeVersion,
|
|
15647
|
-
homeDir:
|
|
17003
|
+
homeDir: homedir8(),
|
|
15648
17004
|
env: process.env
|
|
15649
17005
|
});
|
|
15650
17006
|
if (provenance.anomaly) {
|
|
@@ -15658,6 +17014,30 @@ async function run(options) {
|
|
|
15658
17014
|
}
|
|
15659
17015
|
}
|
|
15660
17016
|
state.opencodeConnected = oc.notReadyReason === null;
|
|
17017
|
+
if (managedPreparation.kind === "ready" && oc.notReadyReason === null) {
|
|
17018
|
+
try {
|
|
17019
|
+
const acknowledgement = await acknowledgeManagedInferenceSetup(
|
|
17020
|
+
managedSetupOptions,
|
|
17021
|
+
managedPreparation
|
|
17022
|
+
);
|
|
17023
|
+
if (acknowledgement === "acknowledged") {
|
|
17024
|
+
managedSetupOptions.log(
|
|
17025
|
+
`MANAGED-INFERENCE-ACKNOWLEDGED: runner=${state.agentId} generation=${managedPreparation.generation}`,
|
|
17026
|
+
"info"
|
|
17027
|
+
);
|
|
17028
|
+
} else {
|
|
17029
|
+
managedSetupOptions.log(
|
|
17030
|
+
`MANAGED-INFERENCE-NOT-READY: policy refresh is pending for runner=${state.agentId} generation=${managedPreparation.generation}`,
|
|
17031
|
+
"warn"
|
|
17032
|
+
);
|
|
17033
|
+
}
|
|
17034
|
+
} catch (error2) {
|
|
17035
|
+
managedSetupOptions.log(
|
|
17036
|
+
`MANAGED-INFERENCE-NOT-READY: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
17037
|
+
"warn"
|
|
17038
|
+
);
|
|
17039
|
+
}
|
|
17040
|
+
}
|
|
15661
17041
|
const version2 = state.opencodeVersion ? ` (v${state.opencodeVersion})` : "";
|
|
15662
17042
|
ocSpinner?.succeed(`OpenCode running on port ${state.port}${version2}`);
|
|
15663
17043
|
if (!state.interactive && oc.notReadyReason !== null) {
|
|
@@ -15716,7 +17096,7 @@ async function run(options) {
|
|
|
15716
17096
|
let existingPid;
|
|
15717
17097
|
if (existsSync3(options.litestreamPidFile)) {
|
|
15718
17098
|
try {
|
|
15719
|
-
const rawPid =
|
|
17099
|
+
const rawPid = readFileSync9(options.litestreamPidFile, "utf8").trim();
|
|
15720
17100
|
const parsedPid = Number(rawPid);
|
|
15721
17101
|
if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
|
|
15722
17102
|
existingPid = parsedPid;
|
|
@@ -15753,7 +17133,7 @@ async function run(options) {
|
|
|
15753
17133
|
});
|
|
15754
17134
|
try {
|
|
15755
17135
|
if (litestreamProcess.pid !== void 0) {
|
|
15756
|
-
|
|
17136
|
+
writeFileSync7(options.litestreamPidFile, `${litestreamProcess.pid}
|
|
15757
17137
|
`, {
|
|
15758
17138
|
mode: 384
|
|
15759
17139
|
});
|
|
@@ -15815,7 +17195,7 @@ async function run(options) {
|
|
|
15815
17195
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
15816
17196
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
15817
17197
|
fileSyncDirectories,
|
|
15818
|
-
homeDir:
|
|
17198
|
+
homeDir: homedir8(),
|
|
15819
17199
|
maxActiveSessions,
|
|
15820
17200
|
log: (entry) => (
|
|
15821
17201
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -15840,7 +17220,7 @@ async function run(options) {
|
|
|
15840
17220
|
onConnected: (agentId, isReconnect, transition) => {
|
|
15841
17221
|
state.connected = true;
|
|
15842
17222
|
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})`;
|
|
17223
|
+
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
17224
|
logActivity(state, {
|
|
15845
17225
|
type: "info",
|
|
15846
17226
|
message: connectionMessage
|
|
@@ -15873,6 +17253,7 @@ async function run(options) {
|
|
|
15873
17253
|
connected: state.opencodeConnected
|
|
15874
17254
|
}),
|
|
15875
17255
|
connection_generation: transition.generation,
|
|
17256
|
+
...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {},
|
|
15876
17257
|
...isReconnect && transition.downtimeMs !== null ? {
|
|
15877
17258
|
reconnect_attempt: transition.attempt,
|
|
15878
17259
|
downtime_ms: transition.downtimeMs
|
|
@@ -15903,12 +17284,13 @@ async function run(options) {
|
|
|
15903
17284
|
const safeReason2 = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
|
|
15904
17285
|
logActivity(state, {
|
|
15905
17286
|
type: "info",
|
|
15906
|
-
message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason2})`
|
|
17287
|
+
message: `Tunnel disconnected (generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"}, code: ${code}, reason: ${safeReason2})`
|
|
15907
17288
|
});
|
|
15908
17289
|
emitAgentDisconnected(state.agentId, {
|
|
15909
17290
|
code,
|
|
15910
17291
|
reason: safeReason2,
|
|
15911
|
-
connection_generation: transition.generation
|
|
17292
|
+
connection_generation: transition.generation,
|
|
17293
|
+
...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {}
|
|
15912
17294
|
});
|
|
15913
17295
|
if (state.interactive) displayStatus(state);
|
|
15914
17296
|
},
|
|
@@ -16059,9 +17441,9 @@ program.name("evident").description("Run OpenCode locally and connect it to Evid
|
|
|
16059
17441
|
"--endpoint <url>",
|
|
16060
17442
|
"Evident API base URL (default: production; e.g. http://localhost:3001)"
|
|
16061
17443
|
).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(
|
|
17444
|
+
const { endpoint: endpoint2, tunnel } = thisCommand.opts();
|
|
17445
|
+
if (endpoint2) {
|
|
17446
|
+
setEndpoint(endpoint2);
|
|
16065
17447
|
}
|
|
16066
17448
|
if (tunnel) {
|
|
16067
17449
|
setTunnelUrl(tunnel);
|
|
@@ -16135,6 +17517,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
16135
17517
|
).option(
|
|
16136
17518
|
"--opencode-config-overlay <path>",
|
|
16137
17519
|
"Apply this runner-provided OpenCode config before starting OpenCode."
|
|
17520
|
+
).option(
|
|
17521
|
+
"--auto-configure-credentials-proxy",
|
|
17522
|
+
"Install Evident's generated OpenCode providers for the runner's credential pool (off by default)."
|
|
16138
17523
|
).option(
|
|
16139
17524
|
"--credential-sync-marker <path>",
|
|
16140
17525
|
"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."
|
|
@@ -16178,6 +17563,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
16178
17563
|
restoreSessionDb: options.restoreSessionDb,
|
|
16179
17564
|
restoreRunnerCredentials: options.restoreRunnerCredentials,
|
|
16180
17565
|
opencodeConfigOverlay: options.opencodeConfigOverlay,
|
|
17566
|
+
autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
|
|
16181
17567
|
credentialSyncMarker: options.credentialSyncMarker
|
|
16182
17568
|
});
|
|
16183
17569
|
}
|