@evident-ai/cli 3.6.0 → 3.6.1-dev.10d71b3

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