@evident-ai/cli 3.6.1-dev.4831868 → 3.6.1-dev.4db2683

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
@@ -723,6 +723,9 @@ async function reportResourceUsage(agentId, authHeader, usage) {
723
723
  opencode_db_bytes: usage.opencodeDbBytes
724
724
  });
725
725
  }
726
+ async function reportSessionDeletions(agentId, authHeader, deletions) {
727
+ return postBestEffort(`/runners/${agentId}/session-deletions`, authHeader, { deletions });
728
+ }
726
729
  async function getRunnerCredentialVault(agentId, authHeader) {
727
730
  const empty = { prefix: null, vaultId: null, source: null };
728
731
  try {
@@ -1176,9 +1179,9 @@ async function claudeUsage() {
1176
1179
  }
1177
1180
 
1178
1181
  // src/commands/run.ts
1179
- import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
1182
+ import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
1180
1183
  import { homedir as homedir8 } from "node:os";
1181
- import { isAbsolute as isAbsolute4, join as join13, parse, resolve as resolvePath2 } from "node:path";
1184
+ import { isAbsolute as isAbsolute4, join as join14, parse, resolve as resolvePath2 } from "node:path";
1182
1185
  import chalk7 from "chalk";
1183
1186
 
1184
1187
  // ../../packages/types/src/agents/index.ts
@@ -1332,8 +1335,29 @@ var NATIVE_PROVIDERS = {
1332
1335
  name: "Evident Kimi credential proxy",
1333
1336
  npm: "@ai-sdk/openai-compatible",
1334
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"
1335
1356
  }
1336
1357
  };
1358
+ var CREDENTIAL_PROXY_NATIVE_NAMESPACES = [
1359
+ ...new Set(Object.values(NATIVE_PROVIDERS).map(({ namespace }) => namespace))
1360
+ ];
1337
1361
  function credentialProxyInferenceUrl(baseUrl) {
1338
1362
  const trimmed = baseUrl.trim().replace(/\/+$/, "");
1339
1363
  return /\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`;
@@ -1421,7 +1445,7 @@ import ora4 from "ora";
1421
1445
  import { select as select4 } from "@inquirer/prompts";
1422
1446
 
1423
1447
  // src/lib/telemetry.ts
1424
- var CLI_VERSION = (true ? "3.6.1-dev.4831868" : void 0) ?? process.env.npm_package_version ?? "unknown";
1448
+ var CLI_VERSION = (true ? "3.6.1-dev.4db2683" : void 0) ?? process.env.npm_package_version ?? "unknown";
1425
1449
  function getCliVersion() {
1426
1450
  return CLI_VERSION;
1427
1451
  }
@@ -2617,6 +2641,122 @@ function isSessionDbRecoveryRecord(value) {
2617
2641
  );
2618
2642
  }
2619
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
+
2620
2760
  // src/lib/opencode/auth.ts
2621
2761
  function buildOpenCodeBasicAuthHeader(password) {
2622
2762
  return `Basic ${Buffer.from(["opencode", password].join(":")).toString("base64")}`;
@@ -3188,6 +3328,52 @@ async function getModelAttachmentCapability(port, model, client) {
3188
3328
  return null;
3189
3329
  }
3190
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
+ }
3191
3377
  async function buildFileParts(attachments, capable) {
3192
3378
  const outcomes = [];
3193
3379
  const parts = [];
@@ -3602,9 +3788,17 @@ function isPreamblePinnedRunning(messages, userMessageId) {
3602
3788
  const reply = findLastAssistantReplyFor(messages, userMessageId);
3603
3789
  return completedOf(reply) != null && finishOf(reply) === "tool-calls";
3604
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
+ }
3605
3796
  function isB2AbandonmentConfirmed(params) {
3606
3797
  return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false && params.rootOngoing === false;
3607
3798
  }
3799
+ function isMidFlightOrphanConfirmed(params) {
3800
+ return params.pinnedForMs >= params.minPinnedMs && params.notOngoingStreak >= params.minNotOngoingStreak;
3801
+ }
3608
3802
  function isAmbiguousTerminalFinish(m) {
3609
3803
  if (completedOf(m) == null) return false;
3610
3804
  if (errorOf(m) != null) return false;
@@ -4470,13 +4664,13 @@ async function readV2SessionErrorStream(client, options) {
4470
4664
  }
4471
4665
 
4472
4666
  // src/lib/opencode/credential-import-v2.ts
4473
- import { readFileSync as readFileSync4 } from "node:fs";
4474
- import { join as join5 } from "node:path";
4667
+ import { readFileSync as readFileSync5 } from "node:fs";
4668
+ import { join as join6 } from "node:path";
4475
4669
 
4476
4670
  // src/lib/openai-usage.ts
4477
- import { readFileSync as readFileSync3 } from "node:fs";
4671
+ import { readFileSync as readFileSync4 } from "node:fs";
4478
4672
  import { homedir as homedir3 } from "node:os";
4479
- import { join as join4 } from "node:path";
4673
+ import { join as join5 } from "node:path";
4480
4674
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
4481
4675
  var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
4482
4676
  var OpenAiUsageError = class extends Error {
@@ -4490,7 +4684,7 @@ function isLocalCredentialProblem2(err) {
4490
4684
  }
4491
4685
  function readOpenCodeChatGptCredentials() {
4492
4686
  try {
4493
- const raw = readFileSync3(join4(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4687
+ const raw = readFileSync4(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4494
4688
  let parsed;
4495
4689
  try {
4496
4690
  parsed = JSON.parse(raw);
@@ -4722,7 +4916,7 @@ function emptyResult(unsupported = [], outcome = "completed") {
4722
4916
  }
4723
4917
  function readOpenCodeCredentials(homeDir, log3) {
4724
4918
  try {
4725
- const raw = readFileSync4(join5(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
4919
+ const raw = readFileSync5(join6(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
4726
4920
  let parsed;
4727
4921
  try {
4728
4922
  parsed = JSON.parse(raw);
@@ -4922,9 +5116,9 @@ async function importOpenCodeV2Credentials({
4922
5116
 
4923
5117
  // src/lib/opencode/session-db-boot.ts
4924
5118
  import { spawn as spawn2 } from "node:child_process";
4925
- 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";
4926
5120
  import { homedir as homedir4 } from "node:os";
4927
- import { dirname as dirname4, resolve as resolvePath } from "node:path";
5121
+ import { dirname as dirname5, resolve as resolvePath } from "node:path";
4928
5122
 
4929
5123
  // src/lib/runner-synchroniser.ts
4930
5124
  import { spawn } from "node:child_process";
@@ -5040,7 +5234,7 @@ function reportRecord(stage, outcome, reason, litestreamExitCode, options) {
5040
5234
  function clearMarker(options) {
5041
5235
  if (!options.noReplicateMarker) return;
5042
5236
  try {
5043
- unlinkSync2(options.noReplicateMarker);
5237
+ unlinkSync3(options.noReplicateMarker);
5044
5238
  } catch (error2) {
5045
5239
  if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return;
5046
5240
  options.log(
@@ -5052,8 +5246,8 @@ function clearMarker(options) {
5052
5246
  function markNoReplicate(options, message) {
5053
5247
  if (options.noReplicateMarker) {
5054
5248
  try {
5055
- mkdirSync(dirname4(options.noReplicateMarker), { recursive: true });
5056
- writeFileSync(options.noReplicateMarker, "");
5249
+ mkdirSync2(dirname5(options.noReplicateMarker), { recursive: true });
5250
+ writeFileSync2(options.noReplicateMarker, "");
5057
5251
  } catch (error2) {
5058
5252
  options.log(
5059
5253
  `Could not write session-DB no-replicate marker ${options.noReplicateMarker}: ${error2 instanceof Error ? error2.message : String(error2)}`,
@@ -5066,7 +5260,7 @@ function markNoReplicate(options, message) {
5066
5260
  function discardSessionDbDebris(options) {
5067
5261
  for (const path of [options.dbPath, `${options.dbPath}-wal`, `${options.dbPath}-shm`]) {
5068
5262
  try {
5069
- unlinkSync2(path);
5263
+ unlinkSync3(path);
5070
5264
  } catch (error2) {
5071
5265
  if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") continue;
5072
5266
  options.log(
@@ -5207,8 +5401,8 @@ async function ensureLitestreamConfig(options, env) {
5207
5401
  return null;
5208
5402
  }
5209
5403
  try {
5210
- mkdirSync(dirname4(configPath), { recursive: true });
5211
- writeFileSync(configPath, rendered.stdout);
5404
+ mkdirSync2(dirname5(configPath), { recursive: true });
5405
+ writeFileSync2(configPath, rendered.stdout);
5212
5406
  } catch (error2) {
5213
5407
  options.log(
5214
5408
  `Could not write ${configPath}: ${error2 instanceof Error ? error2.message : String(error2)}`,
@@ -5473,8 +5667,8 @@ async function restoreAndVerifySessionDb(options) {
5473
5667
 
5474
5668
  // src/lib/opencode/session-db-provenance.ts
5475
5669
  import { createRequire } from "node:module";
5476
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
5477
- 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";
5478
5672
  var require2 = createRequire(import.meta.url);
5479
5673
  function readSessionDbMigrationIds(dbPath) {
5480
5674
  let db;
@@ -5511,12 +5705,12 @@ function readSessionDbMigrationIds(dbPath) {
5511
5705
  }
5512
5706
  function sessionDbProvenanceStatePath(homeDir, env) {
5513
5707
  const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
5514
- return override || join6(homeDir, ".local", "state", "evident", "session-db-provenance.json");
5708
+ return override || join7(homeDir, ".local", "state", "evident", "session-db-provenance.json");
5515
5709
  }
5516
5710
  function loadSessionDbProvenanceState(path) {
5517
5711
  let value;
5518
5712
  try {
5519
- value = JSON.parse(readFileSync5(path, "utf8"));
5713
+ value = JSON.parse(readFileSync6(path, "utf8"));
5520
5714
  } catch (error2) {
5521
5715
  if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return {};
5522
5716
  console.error(
@@ -5540,8 +5734,8 @@ function loadSessionDbProvenanceState(path) {
5540
5734
  }
5541
5735
  function saveSessionDbProvenanceState(path, state) {
5542
5736
  try {
5543
- mkdirSync2(dirname5(path), { recursive: true });
5544
- writeFileSync2(path, `${JSON.stringify(state, null, 2)}
5737
+ mkdirSync3(dirname6(path), { recursive: true });
5738
+ writeFileSync3(path, `${JSON.stringify(state, null, 2)}
5545
5739
  `, "utf8");
5546
5740
  } catch (error2) {
5547
5741
  console.error(
@@ -5600,7 +5794,11 @@ function isSessionDbProvenanceRecord(value) {
5600
5794
  var OPENCODE2_PACKAGE_SPEC = "@opencode-ai/cli@0.0.0-beta-17823";
5601
5795
 
5602
5796
  // src/lib/opencode/opencode-version-gate.ts
5603
- 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
+ ];
5604
5802
  function isQueueValidatedVersion(version2) {
5605
5803
  if (!version2) return false;
5606
5804
  return QUEUE_VALIDATED_OPENCODE_VERSIONS.includes(version2);
@@ -6185,15 +6383,15 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
6185
6383
 
6186
6384
  // src/lib/opencode/session-db-size.ts
6187
6385
  import { statSync as statSync4 } from "node:fs";
6188
- import { join as join7 } from "node:path";
6386
+ import { join as join8 } from "node:path";
6189
6387
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
6190
6388
  function statSessionDbBytes(homeDir) {
6191
- const dbPath = join7(homeDir, ".local", "share", "opencode", "opencode.db");
6389
+ const dbPath = join8(homeDir, ".local", "share", "opencode", "opencode.db");
6192
6390
  try {
6193
6391
  return statSync4(dbPath).size;
6194
6392
  } catch (err) {
6195
- const isMissingFile = err instanceof Error && "code" in err && err.code === "ENOENT";
6196
- if (!isMissingFile) {
6393
+ const isMissingFile2 = err instanceof Error && "code" in err && err.code === "ENOENT";
6394
+ if (!isMissingFile2) {
6197
6395
  console.error(
6198
6396
  `[statSessionDbBytes] could not stat ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
6199
6397
  );
@@ -6218,12 +6416,12 @@ function buildSessionStoreSizeWarning(input) {
6218
6416
  // src/lib/opencode/log-tail.ts
6219
6417
  import { statSync as statSync5 } from "node:fs";
6220
6418
  import { homedir as homedir5 } from "node:os";
6221
- import { join as join8 } from "node:path";
6419
+ import { join as join9 } from "node:path";
6222
6420
  import { open as open2, stat } from "node:fs/promises";
6223
6421
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
6224
6422
  function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
6225
- const dataDir = env.XDG_DATA_HOME || join8(homeDir, ".local", "share");
6226
- 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");
6227
6425
  }
6228
6426
  function isEnoent(error2) {
6229
6427
  return error2?.code === "ENOENT";
@@ -6645,6 +6843,7 @@ function connectTunnel(options) {
6645
6843
  }, 3e4);
6646
6844
  let relaySilenceTimeout;
6647
6845
  let relaySilenceTimedOut = false;
6846
+ let relayConnectionId;
6648
6847
  const clearRelaySilenceTimeout = () => {
6649
6848
  if (relaySilenceTimeout === void 0) return;
6650
6849
  clearTimeout(relaySilenceTimeout);
@@ -6730,7 +6929,8 @@ function connectTunnel(options) {
6730
6929
  armRelaySilenceTimeout();
6731
6930
  forwarder.setBinaryFramesSupported(message.binary_frames === true);
6732
6931
  const connectedAgentId = message.agent_id ?? agentId;
6733
- onConnected?.(connectedAgentId);
6932
+ relayConnectionId = message.connection_id;
6933
+ onConnected?.(connectedAgentId, relayConnectionId);
6734
6934
  resolve5({
6735
6935
  ws,
6736
6936
  close: () => ws.close(1e3, "CLI shutdown")
@@ -6767,7 +6967,7 @@ function connectTunnel(options) {
6767
6967
  clearRelaySilenceTimeout();
6768
6968
  const reasonStr = (relaySilenceTimedOut ? "no relay heartbeat for 90s \u2014 reconnecting" : void 0) || reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
6769
6969
  forwarder.abortAll();
6770
- onDisconnected?.(code, reasonStr);
6970
+ onDisconnected?.(code, reasonStr, relayConnectionId);
6771
6971
  });
6772
6972
  });
6773
6973
  }
@@ -6830,7 +7030,7 @@ var RunnerConnection = class _RunnerConnection {
6830
7030
  authHeader: this.opts.getAuthHeader(),
6831
7031
  port: this.opts.port,
6832
7032
  openCodePassword: this.opts.openCodePassword,
6833
- onConnected: (agentId) => {
7033
+ onConnected: (agentId, relayConnectionId) => {
6834
7034
  const attempt = this.reconnectAttempt + 1;
6835
7035
  this.reconnectAttempt = 0;
6836
7036
  this.reconnecting = false;
@@ -6840,13 +7040,17 @@ var RunnerConnection = class _RunnerConnection {
6840
7040
  this.disconnectedAtMs = null;
6841
7041
  events.onConnected(agentId, isReconnect, {
6842
7042
  generation: this.generation,
7043
+ relayConnectionId: relayConnectionId ?? null,
6843
7044
  attempt,
6844
7045
  downtimeMs
6845
7046
  });
6846
7047
  },
6847
- onDisconnected: (code, reason) => {
7048
+ onDisconnected: (code, reason, relayConnectionId) => {
6848
7049
  this.disconnectedAtMs = this.now();
6849
- events.onDisconnected(code, reason, { generation: this.generation });
7050
+ events.onDisconnected(code, reason, {
7051
+ generation: this.generation,
7052
+ relayConnectionId: relayConnectionId ?? null
7053
+ });
6850
7054
  const relayRequestedReconnect = code === 1e3 && _RunnerConnection.RELAY_RECONNECT_CLOSE_REASONS.has(reason);
6851
7055
  if (this.opts.isRunning() && (code !== 1e3 || relayRequestedReconnect) && !this.reconnecting) {
6852
7056
  this.reconnectPromise = this.connectWithRetry(true).catch((err) => {
@@ -6884,10 +7088,10 @@ var RunnerConnection = class _RunnerConnection {
6884
7088
  };
6885
7089
 
6886
7090
  // src/lib/tunnel/ready-marker.ts
6887
- import { writeFileSync as writeFileSync3 } from "node:fs";
7091
+ import { writeFileSync as writeFileSync4 } from "node:fs";
6888
7092
  function writeTunnelReadyMarker(path, agentId) {
6889
7093
  try {
6890
- writeFileSync3(path, `${agentId}
7094
+ writeFileSync4(path, `${agentId}
6891
7095
  `);
6892
7096
  return { ok: true };
6893
7097
  } catch (error2) {
@@ -6912,7 +7116,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
6912
7116
  }
6913
7117
 
6914
7118
  // src/lib/process-liveness.ts
6915
- import { readFileSync as readFileSync6 } from "node:fs";
7119
+ import { readFileSync as readFileSync7 } from "node:fs";
6916
7120
  function isProcessAlive(pid) {
6917
7121
  try {
6918
7122
  process.kill(pid, 0);
@@ -6927,7 +7131,7 @@ function isProcessAlive(pid) {
6927
7131
  }
6928
7132
  if (process.platform !== "linux") return true;
6929
7133
  try {
6930
- const status2 = readFileSync6(`/proc/${pid}/status`, "utf8");
7134
+ const status2 = readFileSync7(`/proc/${pid}/status`, "utf8");
6931
7135
  return !/^State:\s+Z(?:\s|$)/m.test(status2);
6932
7136
  } catch (error2) {
6933
7137
  console.error(
@@ -7286,12 +7490,12 @@ function createSessionStoreHealth(options = {}) {
7286
7490
  import { homedir as homedir6 } from "node:os";
7287
7491
 
7288
7492
  // src/lib/runner-file-sync.ts
7289
- import { join as join10 } from "node:path";
7493
+ import { join as join11 } from "node:path";
7290
7494
 
7291
7495
  // src/lib/file-push.ts
7292
7496
  import { randomUUID } from "node:crypto";
7293
7497
  import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
7294
- 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";
7295
7499
  var FILE_MODE = 384;
7296
7500
  var DIRECTORY_MODE = 448;
7297
7501
  async function writePushedFile(request3) {
@@ -7322,9 +7526,9 @@ async function writePushedFile(request3) {
7322
7526
  }
7323
7527
  try {
7324
7528
  const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
7325
- dirname6(candidate)
7529
+ dirname7(candidate)
7326
7530
  );
7327
- const realTarget = join9(existingAncestor, ...missingSegments, basename2(candidate));
7531
+ const realTarget = join10(existingAncestor, ...missingSegments, basename2(candidate));
7328
7532
  const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
7329
7533
  if (allowedDirectory === null) {
7330
7534
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
@@ -7334,8 +7538,8 @@ async function writePushedFile(request3) {
7334
7538
  }
7335
7539
  if (missingSegments.length > 0) {
7336
7540
  await createMissingDirectories(existingAncestor, missingSegments);
7337
- const realParent = await realpath(dirname6(realTarget));
7338
- if (realParent !== dirname6(realTarget) || !contains(allowedDirectory, realTarget)) {
7541
+ const realParent = await realpath(dirname7(realTarget));
7542
+ if (realParent !== dirname7(realTarget) || !contains(allowedDirectory, realTarget)) {
7339
7543
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
7340
7544
  path: realTarget,
7341
7545
  bytes,
@@ -7360,7 +7564,7 @@ function expandAndValidate(requestedPath, homeDir) {
7360
7564
  if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
7361
7565
  return null;
7362
7566
  }
7363
- 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;
7364
7568
  if (expanded.split(/[/\\]/).includes("..")) {
7365
7569
  return null;
7366
7570
  }
@@ -7378,7 +7582,7 @@ async function resolveNearestExistingAncestor(directory) {
7378
7582
  try {
7379
7583
  return { existingAncestor: await realpath(current), missingSegments };
7380
7584
  } catch (err) {
7381
- const parent = dirname6(current);
7585
+ const parent = dirname7(current);
7382
7586
  if (err.code !== "ENOENT" || parent === current) {
7383
7587
  throw err;
7384
7588
  }
@@ -7433,13 +7637,13 @@ function contains(realDirectory, realTarget) {
7433
7637
  async function createMissingDirectories(existingAncestor, missingSegments) {
7434
7638
  let current = existingAncestor;
7435
7639
  for (const segment of missingSegments) {
7436
- current = join9(current, segment);
7640
+ current = join10(current, segment);
7437
7641
  await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
7438
7642
  await chmod(current, DIRECTORY_MODE);
7439
7643
  }
7440
7644
  }
7441
7645
  async function writeAtomically(realTarget, content) {
7442
- const temporaryPath = join9(dirname6(realTarget), `.evident-push-${randomUUID()}.tmp`);
7646
+ const temporaryPath = join10(dirname7(realTarget), `.evident-push-${randomUUID()}.tmp`);
7443
7647
  let handle;
7444
7648
  try {
7445
7649
  handle = await open3(temporaryPath, "wx", FILE_MODE);
@@ -7569,12 +7773,12 @@ var NOT_APPLIED = {
7569
7773
  opencodeAuthApplied: false
7570
7774
  };
7571
7775
  function isClaudeCredentialPath(requestedPath, homeDir) {
7572
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7573
- return expanded === join10(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
7776
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join11(homeDir, requestedPath.slice(2)) : requestedPath;
7777
+ return expanded === join11(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
7574
7778
  }
7575
7779
  function isOpenCodeAuthPath(requestedPath, homeDir) {
7576
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7577
- return expanded === join10(homeDir, ...OPENCODE_AUTH_SEGMENTS);
7780
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join11(homeDir, requestedPath.slice(2)) : requestedPath;
7781
+ return expanded === join11(homeDir, ...OPENCODE_AUTH_SEGMENTS);
7578
7782
  }
7579
7783
  async function applyOne(options, file) {
7580
7784
  const label = `${file.id.slice(0, 8)} (${file.path})`;
@@ -7711,6 +7915,211 @@ function describe(err) {
7711
7915
  return err instanceof Error ? err.message : String(err);
7712
7916
  }
7713
7917
 
7918
+ // src/lib/vault-file-sync.ts
7919
+ import { createHash } from "node:crypto";
7920
+ async function syncVaultFiles(options) {
7921
+ const files = await listVaultFiles(options);
7922
+ if (files === null) return { applied: 0 };
7923
+ const listedIds = new Set(files.map((file) => file.id));
7924
+ for (const fileId of options.outcomes.keys()) {
7925
+ if (!listedIds.has(fileId)) options.outcomes.delete(fileId);
7926
+ }
7927
+ let applied = 0;
7928
+ for (const file of files) {
7929
+ try {
7930
+ if (await reconcileVaultFile(options, file)) applied += 1;
7931
+ } catch (err) {
7932
+ options.log({
7933
+ level: "error",
7934
+ message: `Vault file ${file.id} reconciliation failed unexpectedly (message processing is unaffected): ${describe2(err)}`
7935
+ });
7936
+ }
7937
+ }
7938
+ return { applied };
7939
+ }
7940
+ async function listVaultFiles(options) {
7941
+ let response;
7942
+ try {
7943
+ response = await options.fetchImpl(`${options.apiUrl}/runners/${options.agentId}/vault-files`, {
7944
+ headers: { Authorization: options.getAuthHeader() }
7945
+ });
7946
+ } catch (err) {
7947
+ options.log({
7948
+ level: "warn",
7949
+ message: `Could not list vault files \u2014 retrying on the next drain: ${describe2(err)}`
7950
+ });
7951
+ return null;
7952
+ }
7953
+ if (!response.ok) {
7954
+ options.log({
7955
+ level: response.status === 404 ? "debug" : "warn",
7956
+ message: `Listing vault files returned HTTP ${response.status} \u2014 retrying on the next drain`
7957
+ });
7958
+ return null;
7959
+ }
7960
+ let body;
7961
+ try {
7962
+ body = await response.json();
7963
+ } catch (err) {
7964
+ options.log({
7965
+ level: "warn",
7966
+ message: `Vault file list was not readable JSON \u2014 retrying on the next drain: ${describe2(err)}`
7967
+ });
7968
+ return null;
7969
+ }
7970
+ if (body === null || typeof body !== "object" || !Array.isArray(body.files)) {
7971
+ options.log({
7972
+ level: "warn",
7973
+ message: "Vault file list was not an object with a files array \u2014 ignoring it for this drain"
7974
+ });
7975
+ return null;
7976
+ }
7977
+ const files = [];
7978
+ for (const entry of body.files) {
7979
+ const file = asVaultFile(entry);
7980
+ if (file === null) {
7981
+ options.log({
7982
+ level: "warn",
7983
+ message: "Ignoring a malformed vault file entry"
7984
+ });
7985
+ continue;
7986
+ }
7987
+ files.push(file);
7988
+ }
7989
+ return files;
7990
+ }
7991
+ function asVaultFile(entry) {
7992
+ if (entry === null || typeof entry !== "object") return null;
7993
+ const value = entry;
7994
+ 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") {
7995
+ return null;
7996
+ }
7997
+ return {
7998
+ id: value.id,
7999
+ destination_path: value.destination_path,
8000
+ size_bytes: value.size_bytes,
8001
+ content_sha256: value.content_sha256,
8002
+ created_at: value.created_at,
8003
+ updated_at: value.updated_at
8004
+ };
8005
+ }
8006
+ async function reconcileVaultFile(options, file) {
8007
+ const existing = options.outcomes.get(file.id);
8008
+ if (existing?.sha256 === file.content_sha256) {
8009
+ if (existing.reported || existing.reportAttempts >= MAX_ACK_ATTEMPTS) return false;
8010
+ await reportOutcome(options, file, existing);
8011
+ return false;
8012
+ }
8013
+ const content = await downloadVaultFile(options, file);
8014
+ if (content === null) return false;
8015
+ const actualSha256 = createHash("sha256").update(content).digest("hex");
8016
+ if (actualSha256 !== file.content_sha256) {
8017
+ options.log({
8018
+ level: "warn",
8019
+ message: `Vault file ${file.id} content hash mismatch: expected ${file.content_sha256}, received ${actualSha256} \u2014 retrying on the next drain`
8020
+ });
8021
+ return false;
8022
+ }
8023
+ const outcome = await applyVaultFile(options, file, content);
8024
+ const entry = {
8025
+ sha256: file.content_sha256,
8026
+ outcome: outcome.ok ? "applied" : "refused",
8027
+ reason: outcome.ok ? null : outcome.code,
8028
+ reported: false,
8029
+ reportAttempts: 0
8030
+ };
8031
+ options.outcomes.set(file.id, entry);
8032
+ await reportOutcome(options, file, entry);
8033
+ return outcome.ok;
8034
+ }
8035
+ async function downloadVaultFile(options, file) {
8036
+ try {
8037
+ const response = await options.fetchImpl(
8038
+ `${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/content`,
8039
+ { headers: { Authorization: options.getAuthHeader() } }
8040
+ );
8041
+ if (!response.ok) {
8042
+ options.log({
8043
+ level: "warn",
8044
+ message: `Downloading vault file ${file.id} returned HTTP ${response.status} \u2014 retrying on the next drain`
8045
+ });
8046
+ return null;
8047
+ }
8048
+ return Buffer.from(await response.arrayBuffer());
8049
+ } catch (err) {
8050
+ options.log({
8051
+ level: "warn",
8052
+ message: `Downloading vault file ${file.id} failed \u2014 retrying on the next drain: ${describe2(err)}`
8053
+ });
8054
+ return null;
8055
+ }
8056
+ }
8057
+ async function applyVaultFile(options, file, content) {
8058
+ try {
8059
+ const outcome = await writePushedFile({
8060
+ requestedPath: file.destination_path,
8061
+ content,
8062
+ allowedDirectories: options.allowedDirectories,
8063
+ homeDir: options.homeDir
8064
+ });
8065
+ if (!outcome.ok) {
8066
+ options.log({
8067
+ level: "warn",
8068
+ message: `Vault file ${file.id} refused (${outcome.code}): ${outcome.message}`
8069
+ });
8070
+ } else {
8071
+ options.log({
8072
+ level: "info",
8073
+ message: `Vault file ${file.id} applied (${content.byteLength} bytes)`
8074
+ });
8075
+ }
8076
+ return outcome;
8077
+ } catch (err) {
8078
+ options.log({
8079
+ level: "error",
8080
+ message: `Vault file ${file.id} could not be written: ${describe2(err)}`
8081
+ });
8082
+ return { ok: false, code: "write_failed", message: "The runner could not write the file." };
8083
+ }
8084
+ }
8085
+ async function reportOutcome(options, file, entry) {
8086
+ const body = {
8087
+ status: entry.outcome === "applied" ? "applied" : "rejected",
8088
+ reported_sha256: entry.sha256
8089
+ };
8090
+ if (entry.outcome === "refused" && entry.reason !== null) body.reason = entry.reason;
8091
+ try {
8092
+ const response = await options.fetchImpl(
8093
+ `${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/status`,
8094
+ {
8095
+ method: "POST",
8096
+ headers: {
8097
+ Authorization: options.getAuthHeader(),
8098
+ "Content-Type": "application/json"
8099
+ },
8100
+ body: JSON.stringify(body)
8101
+ }
8102
+ );
8103
+ if (response.ok) {
8104
+ entry.reported = true;
8105
+ return;
8106
+ }
8107
+ recordReportFailure(options, file, entry, `HTTP ${response.status}`);
8108
+ } catch (err) {
8109
+ recordReportFailure(options, file, entry, describe2(err));
8110
+ }
8111
+ }
8112
+ function recordReportFailure(options, file, entry, reason) {
8113
+ entry.reportAttempts += 1;
8114
+ options.log({
8115
+ level: "error",
8116
+ 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})`
8117
+ });
8118
+ }
8119
+ function describe2(err) {
8120
+ return err instanceof Error ? err.message : String(err);
8121
+ }
8122
+
7714
8123
  // src/lib/channels/driver.ts
7715
8124
  function messageIdOf(m) {
7716
8125
  if (!m || typeof m !== "object") return void 0;
@@ -7719,7 +8128,7 @@ function messageIdOf(m) {
7719
8128
  return typeof infoId === "string" ? infoId : void 0;
7720
8129
  }
7721
8130
  function composeAbandonmentNote(finish, replyShape, outcome) {
7722
- 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.");
8131
+ 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.");
7723
8132
  if (replyShape === null) return base;
7724
8133
  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)` : ""}.`;
7725
8134
  }
@@ -7756,6 +8165,9 @@ var ALIVE_WITHHELD_THRESHOLD_MS = 3 * HEARTBEAT_MS;
7756
8165
  var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
7757
8166
  var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
7758
8167
  var AMBIGUOUS_FINISH_MAX_PINNED_MS = 3 * 6e4;
8168
+ var MIDFLIGHT_ORPHAN_MIN_PINNED_MS = 3 * 6e4;
8169
+ var MIDFLIGHT_ORPHAN_RECHECK_MS = 6e4;
8170
+ var MIDFLIGHT_ORPHAN_MIN_NOT_ONGOING_STREAK = 2;
7759
8171
  var B2_ABANDONMENT_RECHECK_MS = HEARTBEAT_MS;
7760
8172
  var POLL_MISS_GRACE_MS = HEARTBEAT_MS;
7761
8173
  var WATCHER_STALL_MS = 3 * POLL_MISS_GRACE_MS;
@@ -8119,6 +8531,7 @@ var ChannelDriver = class _ChannelDriver {
8119
8531
  * re-downloaded and re-written every ~2s until the server expires it.
8120
8532
  */
8121
8533
  fileAckFailures = /* @__PURE__ */ new Map();
8534
+ vaultFileOutcomes = /* @__PURE__ */ new Map();
8122
8535
  /**
8123
8536
  * Monotonic count of files this runner has pulled and written (#559). Only
8124
8537
  * ever increases, so `run.ts` detects work by comparing it against the value
@@ -8276,10 +8689,21 @@ var ChannelDriver = class _ChannelDriver {
8276
8689
  ackFailures: this.fileAckFailures,
8277
8690
  log: this.log
8278
8691
  });
8279
- this.appliedFileCount += result.applied;
8692
+ const vaultResult = await syncVaultFiles({
8693
+ agentId: this.agentId,
8694
+ apiUrl: this.apiUrl,
8695
+ getAuthHeader: this.getAuthHeader,
8696
+ fetchImpl: this.fetchImpl,
8697
+ allowedDirectories: this.fileSyncDirectories,
8698
+ homeDir: this.homeDir,
8699
+ outcomes: this.vaultFileOutcomes,
8700
+ log: this.log
8701
+ });
8702
+ const applied = result.applied + vaultResult.applied;
8703
+ this.appliedFileCount += applied;
8280
8704
  if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
8281
8705
  if (result.opencodeAuthApplied) this.opencodeAuthApplyCount += 1;
8282
- return result.applied;
8706
+ return applied;
8283
8707
  } catch (err) {
8284
8708
  this.log({
8285
8709
  level: "error",
@@ -8691,6 +9115,47 @@ var ChannelDriver = class _ChannelDriver {
8691
9115
  agent: message.opencode_agent ?? void 0,
8692
9116
  model: message.opencode_model ?? void 0
8693
9117
  };
9118
+ const pinnedProviderLoaded = await isPinnedProviderLoaded(
9119
+ this.port,
9120
+ options.model,
9121
+ this.openCodeClient
9122
+ );
9123
+ if (pinnedProviderLoaded === false) {
9124
+ const dispatchedModel = message.opencode_model;
9125
+ const separator = dispatchedModel?.indexOf("/") ?? -1;
9126
+ const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
9127
+ const providerId = hasProviderAndModel ? dispatchedModel.slice(0, separator) : null;
9128
+ const modelId = hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null;
9129
+ const errorMessage3 = `Provider namespace not loaded on this runner: ${message.opencode_model}. Refused before dispatch; no turn was started.`;
9130
+ this.log({
9131
+ level: "error",
9132
+ 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`,
9133
+ conversation_id: conv.id,
9134
+ message_id: message.id
9135
+ });
9136
+ try {
9137
+ await this.markFailed(conv.id, message.id, sessionId, errorMessage3, void 0, {
9138
+ classification: {
9139
+ kind: "model_auth",
9140
+ providerId,
9141
+ modelId,
9142
+ reason: "missing"
9143
+ },
9144
+ errorShape: { name: null, statusCode: null }
9145
+ });
9146
+ this.signalDispatchNotStarted(conv, message, "model_provider_absent");
9147
+ } catch (markErr) {
9148
+ this.log({
9149
+ level: "warn",
9150
+ 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)}`,
9151
+ conversation_id: conv.id,
9152
+ message_id: message.id
9153
+ });
9154
+ this.signalDispatchNotStarted(conv, message, "failure_unreported");
9155
+ }
9156
+ await this.repairProviderRegistry(conv.id, message.id);
9157
+ break;
9158
+ }
8694
9159
  let opencodeMessageId;
8695
9160
  let preDispatchUserIds = null;
8696
9161
  try {
@@ -9815,6 +10280,12 @@ var ChannelDriver = class _ChannelDriver {
9815
10280
  b2AbandonedWithoutAnswer: false,
9816
10281
  ambiguousPinnedSinceMs: 0,
9817
10282
  ambiguousResolved: false,
10283
+ midFlightPinnedSinceMs: 0,
10284
+ midFlightNotOngoingStreak: 0,
10285
+ midFlightLastStatusCheckMs: 0,
10286
+ midFlightOrphanSignalled: false,
10287
+ midFlightOrphanWithoutAnswer: false,
10288
+ midFlightHeldLogged: false,
9818
10289
  sessionStoreFailurePending: false,
9819
10290
  neverStartedResolved: false
9820
10291
  });
@@ -9954,6 +10425,12 @@ var ChannelDriver = class _ChannelDriver {
9954
10425
  b2AbandonedWithoutAnswer: false,
9955
10426
  ambiguousPinnedSinceMs: 0,
9956
10427
  ambiguousResolved: false,
10428
+ midFlightPinnedSinceMs: 0,
10429
+ midFlightNotOngoingStreak: 0,
10430
+ midFlightLastStatusCheckMs: 0,
10431
+ midFlightOrphanSignalled: false,
10432
+ midFlightOrphanWithoutAnswer: false,
10433
+ midFlightHeldLogged: false,
9957
10434
  sessionStoreFailurePending: false,
9958
10435
  neverStartedResolved: false
9959
10436
  });
@@ -10725,7 +11202,11 @@ var ChannelDriver = class _ChannelDriver {
10725
11202
  watcher,
10726
11203
  inFlight,
10727
11204
  messages,
10728
- finish
11205
+ composeAbandonmentNote(
11206
+ finish,
11207
+ describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
11208
+ "without-answer"
11209
+ )
10729
11210
  );
10730
11211
  } else {
10731
11212
  await this.settleMessageDone(
@@ -10784,7 +11265,11 @@ var ChannelDriver = class _ChannelDriver {
10784
11265
  watcher,
10785
11266
  inFlight,
10786
11267
  messages,
10787
- finish
11268
+ composeAbandonmentNote(
11269
+ finish,
11270
+ describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
11271
+ "without-answer"
11272
+ )
10788
11273
  );
10789
11274
  return;
10790
11275
  }
@@ -10878,6 +11363,106 @@ var ChannelDriver = class _ChannelDriver {
10878
11363
  return;
10879
11364
  }
10880
11365
  }
11366
+ const midFlightPinnedNow = activelyRunning && isMidFlightPinnedRunning(messages, inFlight.opencodeMessageId);
11367
+ if (!midFlightPinnedNow) {
11368
+ if (snapshotReadable) {
11369
+ inFlight.midFlightPinnedSinceMs = 0;
11370
+ inFlight.midFlightNotOngoingStreak = 0;
11371
+ inFlight.midFlightLastStatusCheckMs = 0;
11372
+ inFlight.midFlightOrphanSignalled = false;
11373
+ inFlight.midFlightOrphanWithoutAnswer = false;
11374
+ inFlight.midFlightHeldLogged = false;
11375
+ }
11376
+ } else {
11377
+ if (inFlight.midFlightOrphanSignalled) {
11378
+ const note = composeAbandonmentNote(
11379
+ void 0,
11380
+ describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
11381
+ inFlight.midFlightOrphanWithoutAnswer ? "midflight-without-answer" : "midflight-with-answer"
11382
+ );
11383
+ if (inFlight.midFlightOrphanWithoutAnswer) {
11384
+ await this.settleMessageAbandonedWithoutAnswer(
11385
+ sessionId,
11386
+ watcher,
11387
+ inFlight,
11388
+ messages,
11389
+ note
11390
+ );
11391
+ } else {
11392
+ await this.settleMessageDone(sessionId, watcher, inFlight, messages, note);
11393
+ }
11394
+ return;
11395
+ }
11396
+ if (inFlight.midFlightPinnedSinceMs === 0) {
11397
+ inFlight.midFlightPinnedSinceMs = this.now();
11398
+ }
11399
+ const pinnedForMs = this.now() - inFlight.midFlightPinnedSinceMs;
11400
+ if (pinnedForMs >= MIDFLIGHT_ORPHAN_MIN_PINNED_MS && this.now() - inFlight.midFlightLastStatusCheckMs >= MIDFLIGHT_ORPHAN_RECHECK_MS) {
11401
+ inFlight.midFlightLastStatusCheckMs = this.now();
11402
+ const rootOngoing = await this.isSessionOngoing(sessionId);
11403
+ inFlight.midFlightNotOngoingStreak = rootOngoing === false ? inFlight.midFlightNotOngoingStreak + 1 : 0;
11404
+ if (isMidFlightOrphanConfirmed({
11405
+ pinnedForMs,
11406
+ minPinnedMs: MIDFLIGHT_ORPHAN_MIN_PINNED_MS,
11407
+ notOngoingStreak: inFlight.midFlightNotOngoingStreak,
11408
+ minNotOngoingStreak: MIDFLIGHT_ORPHAN_MIN_NOT_ONGOING_STREAK
11409
+ })) {
11410
+ if (this.sessionStoreHealth?.isUnwritable()) {
11411
+ inFlight.sessionStoreFailurePending = true;
11412
+ void this.postSignal(conv.id, id, "midflight_orphan_resolved", {
11413
+ watched_for_ms: pinnedForMs,
11414
+ ...rootOngoing != null ? { root_ongoing: rootOngoing } : {},
11415
+ opencode_message_id: inFlight.opencodeMessageId,
11416
+ run_state: "failed"
11417
+ });
11418
+ await this.settleMessageSessionStoreUnwritable(sessionId, watcher, inFlight);
11419
+ return;
11420
+ }
11421
+ inFlight.midFlightOrphanSignalled = true;
11422
+ const noteOutcome = extractReplyTextForMessage(messages, inFlight.opencodeMessageId) === null ? "midflight-without-answer" : "midflight-with-answer";
11423
+ inFlight.midFlightOrphanWithoutAnswer = noteOutcome === "midflight-without-answer";
11424
+ void this.postSignal(conv.id, id, "midflight_orphan_resolved", {
11425
+ watched_for_ms: pinnedForMs,
11426
+ ...rootOngoing != null ? { root_ongoing: rootOngoing } : {},
11427
+ opencode_message_id: inFlight.opencodeMessageId,
11428
+ ...inFlight.midFlightOrphanWithoutAnswer ? { run_state: "failed" } : {}
11429
+ });
11430
+ const note = composeAbandonmentNote(
11431
+ void 0,
11432
+ describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
11433
+ noteOutcome
11434
+ );
11435
+ this.log({
11436
+ level: "warn",
11437
+ 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`,
11438
+ conversation_id: conv.id,
11439
+ message_id: id
11440
+ });
11441
+ if (inFlight.midFlightOrphanWithoutAnswer) {
11442
+ await this.settleMessageAbandonedWithoutAnswer(
11443
+ sessionId,
11444
+ watcher,
11445
+ inFlight,
11446
+ messages,
11447
+ note
11448
+ );
11449
+ } else {
11450
+ await this.settleMessageDone(sessionId, watcher, inFlight, messages, note);
11451
+ }
11452
+ return;
11453
+ }
11454
+ if (rootOngoing === true && !inFlight.midFlightHeldLogged) {
11455
+ inFlight.midFlightHeldLogged = true;
11456
+ const replyShape = describeAbandonedReplyShape(messages, inFlight.opencodeMessageId);
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=true \u2014 holding; last tool ${replyShape?.lastToolName ?? "(unknown)"} status ${replyShape?.lastToolStatus ?? "(unknown)"}`,
11460
+ conversation_id: conv.id,
11461
+ message_id: id
11462
+ });
11463
+ }
11464
+ }
11465
+ }
10881
11466
  if (watcher.inFlight.has(inFlight.evidentMessageId) && activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
10882
11467
  await this.settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick);
10883
11468
  return;
@@ -11077,13 +11662,8 @@ var ChannelDriver = class _ChannelDriver {
11077
11662
  this.removeInFlight(watcher, inFlight.evidentMessageId);
11078
11663
  }
11079
11664
  /** Report a confirmed b2 abandonment that produced no answer text. */
11080
- async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages, finish) {
11665
+ async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages, error2) {
11081
11666
  const conv = watcher.conv;
11082
- const error2 = composeAbandonmentNote(
11083
- finish,
11084
- describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
11085
- "without-answer"
11086
- );
11087
11667
  this.anchorDeliveryDeadline(inFlight);
11088
11668
  this.log({
11089
11669
  level: "warn",
@@ -13595,7 +14175,7 @@ Port ${port} is already in use.`));
13595
14175
  }
13596
14176
 
13597
14177
  // src/lib/runner-credentials.ts
13598
- import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "node:fs";
14178
+ import { chmodSync as chmodSync2, writeFileSync as writeFileSync5 } from "node:fs";
13599
14179
  import { spawn as spawn5 } from "node:child_process";
13600
14180
  var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
13601
14181
  var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
@@ -13838,8 +14418,8 @@ async function configureGitHubAccess({ env, log: log3 }) {
13838
14418
  }
13839
14419
  try {
13840
14420
  env.GIT_CONFIG_GLOBAL = GIT_CONFIG_GLOBAL;
13841
- writeFileSync4(GIT_CONFIG_GLOBAL, "");
13842
- writeFileSync4(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
14421
+ writeFileSync5(GIT_CONFIG_GLOBAL, "");
14422
+ writeFileSync5(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
13843
14423
  chmodSync2(GIT_CREDENTIAL_HELPER, 448);
13844
14424
  const config = [
13845
14425
  ["user.name", env.GIT_USER_NAME ?? "evident-bot"],
@@ -13872,13 +14452,7 @@ async function configureGitHubAccess({ env, log: log3 }) {
13872
14452
  // src/lib/opencode/config-overlay.ts
13873
14453
  import { execFileSync as execFileSync2 } from "node:child_process";
13874
14454
  import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
13875
- import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
13876
- var MANAGED_PROVIDER_NAMESPACES = [
13877
- "evident-claude",
13878
- "evident-codex",
13879
- "evident-openai",
13880
- "evident-kimi"
13881
- ];
14455
+ import { isAbsolute as isAbsolute3, join as join12, resolve as resolve4 } from "node:path";
13882
14456
  function parseConfigContent(content) {
13883
14457
  if (!content?.trim()) return {};
13884
14458
  const parsed = JSON.parse(content);
@@ -13890,27 +14464,11 @@ function parseConfigContent(content) {
13890
14464
  function mergeManagedOpenCodeConfigContent(content, managedProviders) {
13891
14465
  const config = parseConfigContent(content);
13892
14466
  const provider = typeof config.provider === "object" && config.provider !== null && !Array.isArray(config.provider) ? { ...config.provider } : {};
13893
- for (const namespace of MANAGED_PROVIDER_NAMESPACES) {
14467
+ for (const namespace of CREDENTIAL_PROXY_NATIVE_NAMESPACES) {
13894
14468
  delete provider[namespace];
13895
14469
  }
13896
14470
  return JSON.stringify({ ...config, provider: { ...provider, ...managedProviders } });
13897
14471
  }
13898
- function removeManagedOpenCodeConfigContent(content) {
13899
- if (!content?.trim()) return content;
13900
- const config = parseConfigContent(content);
13901
- if (typeof config.provider !== "object" || config.provider === null || Array.isArray(config.provider)) {
13902
- return content;
13903
- }
13904
- const provider = { ...config.provider };
13905
- let changed = false;
13906
- for (const namespace of MANAGED_PROVIDER_NAMESPACES) {
13907
- if (namespace in provider) {
13908
- delete provider[namespace];
13909
- changed = true;
13910
- }
13911
- }
13912
- return changed ? JSON.stringify({ ...config, provider }) : content;
13913
- }
13914
14472
  function isFile(filePath) {
13915
14473
  return existsSync2(filePath) && statSync6(filePath).isFile();
13916
14474
  }
@@ -13924,7 +14482,7 @@ function applyRunnerOpenCodeConfig({
13924
14482
  return;
13925
14483
  }
13926
14484
  const source = isAbsolute3(overlayPath) ? overlayPath : resolve4(cwd, overlayPath);
13927
- const target = isFile(join11(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
14485
+ const target = isFile(join12(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
13928
14486
  if (!isFile(source)) {
13929
14487
  log3(
13930
14488
  `RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
@@ -13932,7 +14490,7 @@ function applyRunnerOpenCodeConfig({
13932
14490
  );
13933
14491
  return;
13934
14492
  }
13935
- copyFileSync(source, join11(cwd, target));
14493
+ copyFileSync(source, join12(cwd, target));
13936
14494
  try {
13937
14495
  execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
13938
14496
  stdio: "ignore"
@@ -13941,13 +14499,13 @@ function applyRunnerOpenCodeConfig({
13941
14499
  const detail = error2 instanceof Error ? error2.message : String(error2);
13942
14500
  log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
13943
14501
  }
13944
- log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
14502
+ log3(`Applied runner OpenCode config ${source} to ${join12(cwd, target)}`);
13945
14503
  }
13946
14504
 
13947
14505
  // src/lib/opencode/runtime-provenance.ts
13948
- import { readFileSync as readFileSync7, readdirSync } from "node:fs";
14506
+ import { readFileSync as readFileSync8, readdirSync } from "node:fs";
13949
14507
  import { homedir as homedir7 } from "node:os";
13950
- import { join as join12 } from "node:path";
14508
+ import { join as join13 } from "node:path";
13951
14509
  var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
13952
14510
  var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
13953
14511
  function errorKind(error2) {
@@ -14015,7 +14573,7 @@ function parseJsonc(value) {
14015
14573
  return JSON.parse(output);
14016
14574
  }
14017
14575
  function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
14018
- return join12(env.XDG_CACHE_HOME ?? join12(homeDir, ".cache"), "opencode");
14576
+ return join13(env.XDG_CACHE_HOME ?? join13(homeDir, ".cache"), "opencode");
14019
14577
  }
14020
14578
  function readConfiguredPluginSpecs({
14021
14579
  cwd = process.cwd(),
@@ -14024,10 +14582,10 @@ function readConfiguredPluginSpecs({
14024
14582
  let config;
14025
14583
  try {
14026
14584
  try {
14027
- config = parseJsonc(readFileSync7(join12(cwd, "opencode.jsonc"), "utf8"));
14585
+ config = parseJsonc(readFileSync8(join13(cwd, "opencode.jsonc"), "utf8"));
14028
14586
  } catch (error2) {
14029
14587
  if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
14030
- config = parseJsonc(readFileSync7(join12(cwd, "opencode.json"), "utf8"));
14588
+ config = parseJsonc(readFileSync8(join13(cwd, "opencode.json"), "utf8"));
14031
14589
  }
14032
14590
  } catch (error2) {
14033
14591
  log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
@@ -14055,7 +14613,7 @@ function resolveInstalledPluginVersions({
14055
14613
  homeDir = homedir7(),
14056
14614
  log: log3
14057
14615
  }) {
14058
- const packagesRoot = join12(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
14616
+ const packagesRoot = join13(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
14059
14617
  let directories;
14060
14618
  try {
14061
14619
  directories = readdirSync(packagesRoot, { withFileTypes: true });
@@ -14076,8 +14634,8 @@ function resolveInstalledPluginVersions({
14076
14634
  let resolvedVersion = null;
14077
14635
  try {
14078
14636
  const packageJson = JSON.parse(
14079
- readFileSync7(
14080
- join12(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
14637
+ readFileSync8(
14638
+ join13(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
14081
14639
  "utf8"
14082
14640
  )
14083
14641
  );
@@ -14098,7 +14656,7 @@ function resolveInstalledPluginVersions({
14098
14656
  }
14099
14657
 
14100
14658
  // src/lib/credential-sync.ts
14101
- import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
14659
+ import { renameSync, writeFileSync as writeFileSync6 } from "node:fs";
14102
14660
  var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
14103
14661
  var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
14104
14662
  var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
@@ -14132,7 +14690,7 @@ function writeMarker(markerPath, outcomes, log3) {
14132
14690
  `;
14133
14691
  const temporaryPath = `${markerPath}.tmp`;
14134
14692
  try {
14135
- writeFileSync5(temporaryPath, body, { mode: 384 });
14693
+ writeFileSync6(temporaryPath, body, { mode: 384 });
14136
14694
  renameSync(temporaryPath, markerPath);
14137
14695
  } catch (error2) {
14138
14696
  log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
@@ -14372,23 +14930,23 @@ async function responseJson(response) {
14372
14930
  function readSetup(value) {
14373
14931
  if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
14374
14932
  const setup = value;
14375
- if (typeof setup.runner_id !== "string" || typeof setup.generation !== "string" || typeof setup.status !== "string" || setup.token_id !== null && typeof setup.token_id !== "string") {
14933
+ 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) {
14376
14934
  return null;
14377
14935
  }
14378
14936
  return setup;
14379
14937
  }
14380
14938
  function clearLocalSetup(options) {
14381
14939
  const env = options.env ?? process.env;
14382
- (options.store ?? defaultStore).clear(options.agentId);
14383
- delete env.EVIDENT_PROXY_TOKEN;
14384
- try {
14385
- const content = removeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT);
14386
- if (content === void 0) delete env.OPENCODE_CONFIG_CONTENT;
14387
- else env.OPENCODE_CONFIG_CONTENT = content;
14388
- } catch (error2) {
14940
+ const store = options.store ?? defaultStore;
14941
+ const stored = store.get(options.agentId);
14942
+ store.clear(options.agentId);
14943
+ const token = env.EVIDENT_PROXY_TOKEN;
14944
+ if (token !== void 0 && token === stored?.token) {
14945
+ delete env.EVIDENT_PROXY_TOKEN;
14946
+ } else if (token !== void 0) {
14389
14947
  options.log(
14390
- "MANAGED-INFERENCE-CLEANUP-PARTIAL: stored credentials were removed, but malformed OPENCODE_CONFIG_CONTENT could not be inspected",
14391
- "warn"
14948
+ `MANAGED-INFERENCE-CLEANUP-PRESERVED-EXTERNAL-TOKEN: runner=${options.agentId}`,
14949
+ "debug"
14392
14950
  );
14393
14951
  }
14394
14952
  }
@@ -14414,6 +14972,25 @@ async function discoverManagedInferenceSetup(options) {
14414
14972
  const setup = readSetup(body?.setup);
14415
14973
  return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed setup discovery returned an invalid response" };
14416
14974
  }
14975
+ async function enrollManagedInferenceSetup(options) {
14976
+ const response = await post(options, "enroll", {});
14977
+ if (!response) {
14978
+ return { kind: "failed", reason: safeFailure("Managed credential enrollment") };
14979
+ }
14980
+ if (response.status === 404) {
14981
+ return { kind: "none" };
14982
+ }
14983
+ const body = await responseJson(response);
14984
+ if (!response.ok) {
14985
+ const serverError = body?.error;
14986
+ return {
14987
+ kind: "failed",
14988
+ reason: typeof serverError === "string" && serverError.trim() ? redactDiagnosticText(serverError).slice(0, 500) : safeFailure("Managed credential enrollment", response.status)
14989
+ };
14990
+ }
14991
+ const setup = readSetup(body?.setup);
14992
+ return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed credential enrollment returned an invalid response" };
14993
+ }
14417
14994
  async function post(options, operation, body) {
14418
14995
  const headers = requestHeaders(options);
14419
14996
  try {
@@ -14428,31 +15005,37 @@ async function post(options, operation, body) {
14428
15005
  }
14429
15006
  }
14430
15007
  function applyCredential(options, credential) {
14431
- const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
14432
- inferenceBaseUrl: credential.inferenceBaseUrl,
14433
- modelDescriptors: credential.modelDescriptors
14434
- });
14435
- if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
14436
- return "Managed setup contains unsupported provider descriptors";
14437
- }
14438
15008
  const env = options.env ?? process.env;
14439
15009
  let merged;
14440
- try {
14441
- merged = mergeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT, config.provider);
14442
- } catch (error2) {
14443
- return "OPENCODE_CONFIG_CONTENT is malformed; managed providers were not installed";
15010
+ if (options.autoConfigureCredentialsProxy) {
15011
+ const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
15012
+ inferenceBaseUrl: credential.inferenceBaseUrl,
15013
+ modelDescriptors: credential.modelDescriptors
15014
+ });
15015
+ if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
15016
+ return "Managed setup contains unsupported provider descriptors";
15017
+ }
15018
+ try {
15019
+ merged = mergeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT, config.provider);
15020
+ } catch (error2) {
15021
+ return "OPENCODE_CONFIG_CONTENT is malformed; managed providers were not installed";
15022
+ }
14444
15023
  }
14445
15024
  try {
14446
15025
  (options.store ?? defaultStore).set(options.agentId, credential);
14447
15026
  } catch (error2) {
15027
+ const detail = (error2 instanceof Error ? error2.message : String(error2)).replaceAll(
15028
+ credential.token,
15029
+ "[redacted]"
15030
+ );
14448
15031
  options.log(
14449
- `MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation}`,
15032
+ `MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation} detail=${redactDiagnosticText(detail)}`,
14450
15033
  "warn"
14451
15034
  );
14452
15035
  return "Managed credentials could not be written to the protected store";
14453
15036
  }
14454
15037
  env.EVIDENT_PROXY_TOKEN = credential.token;
14455
- env.OPENCODE_CONFIG_CONTENT = merged;
15038
+ if (merged !== void 0) env.OPENCODE_CONFIG_CONTENT = merged;
14456
15039
  return null;
14457
15040
  }
14458
15041
  async function reportManagedInferenceApplyFailure(options, failure) {
@@ -14503,7 +15086,7 @@ async function prepareManagedInferenceSetup(options, discovery, health) {
14503
15086
  }
14504
15087
  return reaffirmStored(options, stored);
14505
15088
  }
14506
- if (discovery.setup.status === "acknowledged" && stored?.generation === discovery.setup.generation && stored.tokenId === discovery.setup.token_id) {
15089
+ if (discovery.setup.acknowledged_at !== null && stored?.generation === discovery.setup.generation && stored.tokenId === discovery.setup.token_id) {
14507
15090
  return reaffirmStored(options, stored);
14508
15091
  }
14509
15092
  const response = await post(options, "bootstrap", { generation: discovery.setup.generation });
@@ -14541,7 +15124,7 @@ async function acknowledgeManagedInferenceSetup(options, preparation) {
14541
15124
  const response = await post(options, "acknowledge", {
14542
15125
  generation: preparation.generation,
14543
15126
  token_id: preparation.tokenId,
14544
- config_validated: true
15127
+ config_validated: options.autoConfigureCredentialsProxy === true
14545
15128
  });
14546
15129
  if (!response?.ok) {
14547
15130
  throw new Error(
@@ -14596,7 +15179,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
14596
15179
  if (trimmed === "") {
14597
15180
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
14598
15181
  }
14599
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join13(homeDir, trimmed.slice(2)) : trimmed;
15182
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join14(homeDir, trimmed.slice(2)) : trimmed;
14600
15183
  if (!isAbsolute4(expanded)) {
14601
15184
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
14602
15185
  }
@@ -15252,7 +15835,7 @@ function shouldWarnForReclaimSkip(reason) {
15252
15835
  return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
15253
15836
  }
15254
15837
  function sessionDbPath() {
15255
- return join13(homedir8(), ".local", "share", "opencode", "opencode.db");
15838
+ return join14(homedir8(), ".local", "share", "opencode", "opencode.db");
15256
15839
  }
15257
15840
  function logSessionCleanupActivity(state, phase, entry) {
15258
15841
  logActivity(state, {
@@ -15261,6 +15844,47 @@ function logSessionCleanupActivity(state, phase, entry) {
15261
15844
  metadata: { ...entry.metadata, phase }
15262
15845
  });
15263
15846
  }
15847
+ async function drainSessionDeletionReports(state) {
15848
+ const path = sessionDeletionReportPath(homedir8(), process.env);
15849
+ let delivered = 0;
15850
+ let failure = null;
15851
+ try {
15852
+ const spool = readSessionDeletionRecords(path);
15853
+ if (spool.readError !== null) {
15854
+ failure = `could not read spool: ${spool.readError}`;
15855
+ } else if (spool.records.length === 0) {
15856
+ return;
15857
+ } else if (!state.agentId || !state.authHeader) {
15858
+ failure = "runner authentication is not available";
15859
+ } else {
15860
+ for (let offset = 0; offset < spool.records.length; offset += 200) {
15861
+ const batch = spool.records.slice(offset, offset + 200);
15862
+ const result = await reportSessionDeletions(state.agentId, state.authHeader, batch);
15863
+ if (!result.ok) {
15864
+ failure = result.error ?? "unknown API error";
15865
+ break;
15866
+ }
15867
+ removeDeliveredSessionDeletionRecords(path, batch);
15868
+ delivered += batch.length;
15869
+ }
15870
+ }
15871
+ } catch (error2) {
15872
+ failure = error2 instanceof Error ? error2.message : String(error2);
15873
+ }
15874
+ const pending = readSessionDeletionRecords(path);
15875
+ const pendingCount = pending.readError === null ? pending.records.length : null;
15876
+ const message = `Session deletion reports: delivered ${delivered}, ${pendingCount === null ? "pending count unavailable" : `${pendingCount} pending`}` + (failure === null ? "." : `; delivery failed: ${failure}.`);
15877
+ logActivity(state, {
15878
+ type: "info",
15879
+ level: failure === null ? "info" : "warn",
15880
+ message,
15881
+ metadata: {
15882
+ outcome: failure === null ? "session-deletion-reports-drained" : "session-deletion-reports-pending",
15883
+ delivered,
15884
+ pending: pendingCount
15885
+ }
15886
+ });
15887
+ }
15264
15888
  function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
15265
15889
  const record = {
15266
15890
  v: 1,
@@ -15318,17 +15942,22 @@ async function runSweep(state, driver, config) {
15318
15942
  let failedThrew = 0;
15319
15943
  let skippedNewlyActive = 0;
15320
15944
  let remaining = 0;
15945
+ const deletionRecords = [];
15321
15946
  let protectedNow = driver.protectedSessionIds();
15322
15947
  if (sessions !== null) {
15323
- const toDelete = selectSessionsToDelete(
15324
- sessions.map((s) => ({ id: s.id, lastActivityMs: sessionLastActivityMs(s) })),
15325
- {
15326
- maxAgeMs: config.maxAgeMs,
15327
- maxCount: config.maxCount,
15328
- nowMs: Date.now(),
15329
- protectedIds: protectedNow
15330
- }
15948
+ const snapshots = sessions.map((session) => ({
15949
+ id: session.id,
15950
+ lastActivityMs: sessionLastActivityMs(session)
15951
+ }));
15952
+ const lastActivityById = new Map(
15953
+ snapshots.map(({ id, lastActivityMs }) => [id, lastActivityMs])
15331
15954
  );
15955
+ const toDelete = selectSessionsToDelete(snapshots, {
15956
+ maxAgeMs: config.maxAgeMs,
15957
+ maxCount: config.maxCount,
15958
+ nowMs: Date.now(),
15959
+ protectedIds: protectedNow
15960
+ });
15332
15961
  const eligibleToDelete = toDelete.filter(
15333
15962
  (id) => (state.sessionDeleteFailures.get(id)?.isolatedFailures ?? 0) < SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES
15334
15963
  );
@@ -15348,6 +15977,17 @@ async function runSweep(state, driver, config) {
15348
15977
  const outcome = await deleteSession(state.port, id, state.opencodeClient ?? void 0);
15349
15978
  if (outcome.ok) {
15350
15979
  deleted++;
15980
+ const lastActivityMs = lastActivityById.get(id) ?? null;
15981
+ deletionRecords.push({
15982
+ opencode_session_id: id,
15983
+ deleted_at: (/* @__PURE__ */ new Date()).toISOString(),
15984
+ reason: "auto_cleanup",
15985
+ policy: {
15986
+ max_age_ms: config.maxAgeMs ?? null,
15987
+ max_count: config.maxCount ?? null,
15988
+ last_activity_at: lastActivityMs === null ? null : new Date(lastActivityMs).toISOString()
15989
+ }
15990
+ });
15351
15991
  } else if (outcome.status === "threw") {
15352
15992
  failed++;
15353
15993
  failedThrew++;
@@ -15403,6 +16043,32 @@ async function runSweep(state, driver, config) {
15403
16043
  message: `Session cleanup: inspected ${sessions.length}, deleted ${deleted}${failedNote}${skippedNote}${remainingNote} (${mode})`
15404
16044
  });
15405
16045
  }
16046
+ if (deletionRecords.length > 0) {
16047
+ try {
16048
+ const dropped = appendSessionDeletionRecords(
16049
+ sessionDeletionReportPath(homedir8(), process.env),
16050
+ deletionRecords
16051
+ );
16052
+ if (dropped > 0) {
16053
+ logSessionCleanupActivity(state, "sweep", {
16054
+ type: "info",
16055
+ level: "warn",
16056
+ message: `Session cleanup: dropped ${dropped} oldest pending deletion report(s) after reaching the spool limit`,
16057
+ metadata: { outcome: "deletion-reports-dropped", dropped_count: dropped }
16058
+ });
16059
+ }
16060
+ } catch (error2) {
16061
+ logSessionCleanupActivity(state, "sweep", {
16062
+ type: "info",
16063
+ level: "warn",
16064
+ message: `Session cleanup: could not persist ${deletionRecords.length} confirmed deletion report(s): ${error2 instanceof Error ? error2.message : String(error2)}`,
16065
+ metadata: {
16066
+ outcome: "deletion-report-spool-write-failed",
16067
+ records: deletionRecords.length
16068
+ }
16069
+ });
16070
+ }
16071
+ }
15406
16072
  const sessionMetadata = {
15407
16073
  sessions_inspected: sessions?.length ?? null,
15408
16074
  sessions_deleted: deleted,
@@ -15472,6 +16138,7 @@ async function runSweep(state, driver, config) {
15472
16138
  }
15473
16139
  });
15474
16140
  }
16141
+ await drainSessionDeletionReports(state);
15475
16142
  } catch (error2) {
15476
16143
  const message = error2 instanceof Error ? error2.message : String(error2);
15477
16144
  logSessionCleanupActivity(state, "sweep", {
@@ -16314,6 +16981,7 @@ async function run(options) {
16314
16981
  }
16315
16982
  }
16316
16983
  reportSessionDbRecovery(state);
16984
+ await drainSessionDeletionReports(state);
16317
16985
  if (sessionDbVerifyFatal) {
16318
16986
  throw new Error(
16319
16987
  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`
@@ -16344,13 +17012,17 @@ async function run(options) {
16344
17012
  agentId: state.agentId,
16345
17013
  apiUrl: getApiUrlConfig(),
16346
17014
  getAuthHeader: () => state.authHeader,
17015
+ autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
16347
17016
  log: (message, level = "info") => logActivity(state, {
16348
17017
  type: level === "error" ? "error" : "info",
16349
17018
  level,
16350
17019
  ...level === "error" ? { error: message } : { message }
16351
17020
  })
16352
17021
  };
16353
- const managedDiscovery = await discoverManagedInferenceSetup(managedSetupOptions);
17022
+ let managedDiscovery = await discoverManagedInferenceSetup(managedSetupOptions);
17023
+ if (managedDiscovery.kind === "none") {
17024
+ managedDiscovery = await enrollManagedInferenceSetup(managedSetupOptions);
17025
+ }
16354
17026
  let managedPreparation = { kind: "none" };
16355
17027
  if (managedDiscovery.kind === "failed") {
16356
17028
  managedPreparation = managedDiscovery;
@@ -16440,7 +17112,7 @@ async function run(options) {
16440
17112
  state.opencodeClient = openCodeClient;
16441
17113
  if (options.opencodePidFile && oc.process?.pid !== void 0) {
16442
17114
  try {
16443
- writeFileSync6(options.opencodePidFile, `${oc.process.pid}
17115
+ writeFileSync7(options.opencodePidFile, `${oc.process.pid}
16444
17116
  `, { mode: 384 });
16445
17117
  chmodSync3(options.opencodePidFile, 384);
16446
17118
  } catch (error2) {
@@ -16550,7 +17222,7 @@ async function run(options) {
16550
17222
  let existingPid;
16551
17223
  if (existsSync3(options.litestreamPidFile)) {
16552
17224
  try {
16553
- const rawPid = readFileSync8(options.litestreamPidFile, "utf8").trim();
17225
+ const rawPid = readFileSync9(options.litestreamPidFile, "utf8").trim();
16554
17226
  const parsedPid = Number(rawPid);
16555
17227
  if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
16556
17228
  existingPid = parsedPid;
@@ -16587,7 +17259,7 @@ async function run(options) {
16587
17259
  });
16588
17260
  try {
16589
17261
  if (litestreamProcess.pid !== void 0) {
16590
- writeFileSync6(options.litestreamPidFile, `${litestreamProcess.pid}
17262
+ writeFileSync7(options.litestreamPidFile, `${litestreamProcess.pid}
16591
17263
  `, {
16592
17264
  mode: 384
16593
17265
  });
@@ -16674,7 +17346,7 @@ async function run(options) {
16674
17346
  onConnected: (agentId, isReconnect, transition) => {
16675
17347
  state.connected = true;
16676
17348
  state.agentId = agentId;
16677
- 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})`;
17349
+ 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"})`;
16678
17350
  logActivity(state, {
16679
17351
  type: "info",
16680
17352
  message: connectionMessage
@@ -16707,6 +17379,7 @@ async function run(options) {
16707
17379
  connected: state.opencodeConnected
16708
17380
  }),
16709
17381
  connection_generation: transition.generation,
17382
+ ...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {},
16710
17383
  ...isReconnect && transition.downtimeMs !== null ? {
16711
17384
  reconnect_attempt: transition.attempt,
16712
17385
  downtime_ms: transition.downtimeMs
@@ -16737,12 +17410,13 @@ async function run(options) {
16737
17410
  const safeReason2 = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
16738
17411
  logActivity(state, {
16739
17412
  type: "info",
16740
- message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason2})`
17413
+ message: `Tunnel disconnected (generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"}, code: ${code}, reason: ${safeReason2})`
16741
17414
  });
16742
17415
  emitAgentDisconnected(state.agentId, {
16743
17416
  code,
16744
17417
  reason: safeReason2,
16745
- connection_generation: transition.generation
17418
+ connection_generation: transition.generation,
17419
+ ...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {}
16746
17420
  });
16747
17421
  if (state.interactive) displayStatus(state);
16748
17422
  },
@@ -16969,6 +17643,9 @@ program.command("run").description("Connect to Evident and process messages").op
16969
17643
  ).option(
16970
17644
  "--opencode-config-overlay <path>",
16971
17645
  "Apply this runner-provided OpenCode config before starting OpenCode."
17646
+ ).option(
17647
+ "--auto-configure-credentials-proxy",
17648
+ "Install Evident's generated OpenCode providers for the runner's credential pool (off by default)."
16972
17649
  ).option(
16973
17650
  "--credential-sync-marker <path>",
16974
17651
  "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."
@@ -17012,6 +17689,7 @@ program.command("run").description("Connect to Evident and process messages").op
17012
17689
  restoreSessionDb: options.restoreSessionDb,
17013
17690
  restoreRunnerCredentials: options.restoreRunnerCredentials,
17014
17691
  opencodeConfigOverlay: options.opencodeConfigOverlay,
17692
+ autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
17015
17693
  credentialSyncMarker: options.credentialSyncMarker
17016
17694
  });
17017
17695
  }