@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/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 requestHeaders = {
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: requestHeaders,
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 readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
1160
- import { homedir as homedir7 } from "node:os";
1161
- import { isAbsolute as isAbsolute4, join as join12, parse, resolve as resolvePath2 } from "node:path";
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.8d3e144" : void 0) ?? process.env.npm_package_version ?? "unknown";
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 request2 = {
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(request2),
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 readFileSync4 } from "node:fs";
4321
- import { join as join5 } from "node:path";
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 readFileSync3 } from "node:fs";
4663
+ import { readFileSync as readFileSync4 } from "node:fs";
4325
4664
  import { homedir as homedir3 } from "node:os";
4326
- import { join as join4 } from "node:path";
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 = readFileSync3(join4(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
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 = readFileSync4(join5(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
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(request2, credential, sleepForRetry, log3) {
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 request2(
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: request2,
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(request2, credential, sleepForRetry, log3);
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: request2,
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 request2(path);
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: request2,
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: request2,
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: request2,
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 unlinkSync2, writeFileSync } from "node:fs";
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 dirname4, resolve as resolvePath } from "node:path";
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
- unlinkSync2(options.noReplicateMarker);
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
- mkdirSync(dirname4(options.noReplicateMarker), { recursive: true });
4903
- writeFileSync(options.noReplicateMarker, "");
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
- unlinkSync2(path);
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
- mkdirSync(dirname4(configPath), { recursive: true });
5058
- writeFileSync(configPath, rendered.stdout);
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 mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
5324
- import { dirname as dirname5, join as join6 } from "node:path";
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 || join6(homeDir, ".local", "state", "evident", "session-db-provenance.json");
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(readFileSync5(path, "utf8"));
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
- mkdirSync2(dirname5(path), { recursive: true });
5391
- writeFileSync2(path, `${JSON.stringify(state, null, 2)}
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 = ["1.17.11", "1.18.3"];
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 join7 } from "node:path";
6378
+ import { join as join8 } from "node:path";
6036
6379
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
6037
6380
  function statSessionDbBytes(homeDir) {
6038
- const dbPath = join7(homeDir, ".local", "share", "opencode", "opencode.db");
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 isMissingFile = err instanceof Error && "code" in err && err.code === "ENOENT";
6043
- if (!isMissingFile) {
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 join8 } from "node:path";
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 || join8(homeDir, ".local", "share");
6073
- return join8(dataDir, "opencode", "log", "opencode.log");
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
- onConnected?.(connectedAgentId);
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, { generation: this.generation });
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 writeFileSync3 } from "node:fs";
7083
+ import { writeFileSync as writeFileSync4 } from "node:fs";
6735
7084
  function writeTunnelReadyMarker(path, agentId) {
6736
7085
  try {
6737
- writeFileSync3(path, `${agentId}
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 readFileSync6 } from "node:fs";
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 = readFileSync6(`/proc/${pid}/status`, "utf8");
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 join10 } from "node:path";
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 dirname6, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
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(request2) {
7145
- const { requestedPath, content, allowedDirectories, homeDir } = request2;
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
- dirname6(candidate)
7521
+ dirname7(candidate)
7173
7522
  );
7174
- const realTarget = join9(existingAncestor, ...missingSegments, basename2(candidate));
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(dirname6(realTarget));
7185
- if (realParent !== dirname6(realTarget) || !contains(allowedDirectory, realTarget)) {
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("~/") ? join9(homeDir, requestedPath.slice(2)) : requestedPath;
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 = dirname6(current);
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 = join9(current, segment);
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 = join9(dirname6(realTarget), `.evident-push-${randomUUID()}.tmp`);
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("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7420
- return expanded === join10(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
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("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7424
- return expanded === join10(homeDir, ...OPENCODE_AUTH_SEGMENTS);
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/channels/driver.ts
7562
- function messageIdOf(m) {
7563
- if (!m || typeof m !== "object") return void 0;
7564
- if (typeof m.id === "string") return m.id;
7565
- const infoId = m.info?.id;
7566
- return typeof infoId === "string" ? infoId : void 0;
7567
- }
7568
- function composeAbandonmentNote(finish, replyShape, outcome) {
7569
- const base = `OpenCode ended this turn after a tool call (finish="${finish ?? "(unknown)"}") ` + (outcome === "without-answer" ? "without writing an answer, and confirmed it is no longer working on this conversation. Evident is reporting the run as failed so the failure and its reason are visible in failed runs." : "after writing only part of an answer, and confirmed it is no longer working on this conversation. Evident delivered what was written and is reporting the run as finished.");
7570
- if (replyShape === null) return base;
7571
- return `${base} ${replyShape.lastToolName === null ? "No tool call was recorded" : `The last tool call was "${replyShape.lastToolName}" with status "${replyShape.lastToolStatus ?? "(unknown)"}"`}; the reply's final part types were [${replyShape.partTypes.join(", ")}]${replyShape.droppedPartCount > 0 ? ` (${replyShape.droppedPartCount} earlier part types omitted)` : ""}.`;
7572
- }
7573
- function cleanImageMime(contentType) {
7574
- if (!contentType) return null;
7575
- const media = contentType.split(";")[0].trim().toLowerCase();
7576
- return /^image\/[a-z0-9.+-]+$/.test(media) ? media : null;
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
- var LOG_LEVELS = {
7579
- debug: 0,
7580
- info: 1,
7581
- warn: 2,
7582
- error: 3
7583
- };
7584
- var DEFAULT_RETRY_POLICY = {
7585
- maxAttempts: 6,
7586
- baseDelayMs: 500,
7587
- maxDelayMs: 3e4
7588
- };
7589
- var SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS = 2e3;
7590
- var SESSION_ERROR_STREAM_HEALTHY_MS = 5e3;
7591
- var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
7592
- var MAX_BUFFERED_SESSION_ERRORS = 256;
7593
- var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
7594
- var SESSION_STORE_UNWRITABLE_ERROR = "The runner session database could not be written, so this turn did not finish. Nothing was lost from the queue. Clear disk space and retry.";
7595
- var SESSION_ERROR_RETAINED_DETAIL = "Runner-observed OpenCode session error: ";
7596
- var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
7597
- var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
7598
- var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
7599
- var DRIVER_SESSION_ENUMERATION_LIMIT = 20;
7600
- var DEFAULT_STUCK_QUEUED_MS = 6e4;
7601
- var HEARTBEAT_MS = 6e4;
7602
- var ALIVE_WITHHELD_THRESHOLD_MS = 3 * HEARTBEAT_MS;
7603
- var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
7604
- var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
7605
- var AMBIGUOUS_FINISH_MAX_PINNED_MS = 3 * 6e4;
7606
- var B2_ABANDONMENT_RECHECK_MS = HEARTBEAT_MS;
7607
- var POLL_MISS_GRACE_MS = HEARTBEAT_MS;
7608
- var WATCHER_STALL_MS = 3 * POLL_MISS_GRACE_MS;
7609
- var MAX_WATCHER_STALL_RESTARTS = 3;
7610
- var MAX_RELEASED_OPENCODE_IDS = 256;
7611
- var MAX_SUPERSEDED_CONVERSATIONS = 256;
7612
- var MAX_IDENTICAL_REDRIVE_POLL_FAILURES = 5;
7613
- var MAX_NEVER_STARTED_ATTEMPTS = 3;
7614
- var WEDGE_WARNING_INTERVAL_MS = 5 * 60 * 1e3;
7615
- var MAX_WEDGED_CONVERSATIONS = 256;
7616
- var ChannelAuthError = class extends Error {
7617
- constructor(message) {
7618
- super(message);
7619
- this.name = "ChannelAuthError";
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
- var ChannelTerminalError = class extends Error {
7623
- status;
7624
- constructor(message, status2) {
7625
- super(message);
7626
- this.name = "ChannelTerminalError";
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
- function backoffDelay(attempt, policy) {
7631
- const exp = policy.baseDelayMs * Math.pow(2, attempt);
7632
- const capped = Math.min(policy.maxDelayMs, exp);
7633
- return Math.floor(Math.random() * capped);
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 isRetryableStatus2(status2) {
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
- this.appliedFileCount += result.applied;
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 result.applied;
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 parsed = message.processing_started_at ? Date.parse(message.processing_started_at) : NaN;
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
- // Anchor the absolute-age ceiling to the SERVER-SIDE `processed_at`, NOT
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.log({
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 writeFileSync4 } from "node:fs";
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
- writeFileSync4(GIT_CONFIG_GLOBAL, "");
13507
- writeFileSync4(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
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 join11, resolve as resolve4 } from "node:path";
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(join11(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
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, join11(cwd, target));
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 ${join11(cwd, target)}`);
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 writeFileSync5 } from "node:fs";
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
- writeFileSync5(temporaryPath, body, { mode: 384 });
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("~/") ? join12(homeDir, trimmed.slice(2)) : trimmed;
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: homedir7(), env: process.env });
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: homedir7(),
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 join12(homedir7(), ".local", "share", "opencode", "opencode.db");
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 toDelete = selectSessionsToDelete(
14574
- sessions.map((s) => ({ id: s.id, lastActivityMs: sessionLastActivityMs(s) })),
14575
- {
14576
- maxAgeMs: config.maxAgeMs,
14577
- maxCount: config.maxCount,
14578
- nowMs: Date.now(),
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(homedir7());
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(homedir7());
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, homedir7());
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: homedir7(),
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(homedir7(), process.env),
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
- writeFileSync6(options.opencodePidFile, `${oc.process.pid}
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: homedir7(),
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 = readFileSync7(options.litestreamPidFile, "utf8").trim();
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
- writeFileSync6(options.litestreamPidFile, `${litestreamProcess.pid}
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: homedir7(),
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 (endpoint) {
16064
- setEndpoint(endpoint);
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
  }