@evident-ai/cli 3.6.1-dev.43c1fd7 → 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
@@ -1442,7 +1445,7 @@ import ora4 from "ora";
1442
1445
  import { select as select4 } from "@inquirer/prompts";
1443
1446
 
1444
1447
  // src/lib/telemetry.ts
1445
- var CLI_VERSION = (true ? "3.6.1-dev.43c1fd7" : 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";
1446
1449
  function getCliVersion() {
1447
1450
  return CLI_VERSION;
1448
1451
  }
@@ -2638,6 +2641,122 @@ function isSessionDbRecoveryRecord(value) {
2638
2641
  );
2639
2642
  }
2640
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
+
2641
2760
  // src/lib/opencode/auth.ts
2642
2761
  function buildOpenCodeBasicAuthHeader(password) {
2643
2762
  return `Basic ${Buffer.from(["opencode", password].join(":")).toString("base64")}`;
@@ -3209,6 +3328,52 @@ async function getModelAttachmentCapability(port, model, client) {
3209
3328
  return null;
3210
3329
  }
3211
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
+ }
3212
3377
  async function buildFileParts(attachments, capable) {
3213
3378
  const outcomes = [];
3214
3379
  const parts = [];
@@ -3623,9 +3788,17 @@ function isPreamblePinnedRunning(messages, userMessageId) {
3623
3788
  const reply = findLastAssistantReplyFor(messages, userMessageId);
3624
3789
  return completedOf(reply) != null && finishOf(reply) === "tool-calls";
3625
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
+ }
3626
3796
  function isB2AbandonmentConfirmed(params) {
3627
3797
  return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false && params.rootOngoing === false;
3628
3798
  }
3799
+ function isMidFlightOrphanConfirmed(params) {
3800
+ return params.pinnedForMs >= params.minPinnedMs && params.notOngoingStreak >= params.minNotOngoingStreak;
3801
+ }
3629
3802
  function isAmbiguousTerminalFinish(m) {
3630
3803
  if (completedOf(m) == null) return false;
3631
3804
  if (errorOf(m) != null) return false;
@@ -4491,13 +4664,13 @@ async function readV2SessionErrorStream(client, options) {
4491
4664
  }
4492
4665
 
4493
4666
  // src/lib/opencode/credential-import-v2.ts
4494
- import { readFileSync as readFileSync4 } from "node:fs";
4495
- import { join as join5 } from "node:path";
4667
+ import { readFileSync as readFileSync5 } from "node:fs";
4668
+ import { join as join6 } from "node:path";
4496
4669
 
4497
4670
  // src/lib/openai-usage.ts
4498
- import { readFileSync as readFileSync3 } from "node:fs";
4671
+ import { readFileSync as readFileSync4 } from "node:fs";
4499
4672
  import { homedir as homedir3 } from "node:os";
4500
- import { join as join4 } from "node:path";
4673
+ import { join as join5 } from "node:path";
4501
4674
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
4502
4675
  var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
4503
4676
  var OpenAiUsageError = class extends Error {
@@ -4511,7 +4684,7 @@ function isLocalCredentialProblem2(err) {
4511
4684
  }
4512
4685
  function readOpenCodeChatGptCredentials() {
4513
4686
  try {
4514
- const raw = readFileSync3(join4(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4687
+ const raw = readFileSync4(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4515
4688
  let parsed;
4516
4689
  try {
4517
4690
  parsed = JSON.parse(raw);
@@ -4743,7 +4916,7 @@ function emptyResult(unsupported = [], outcome = "completed") {
4743
4916
  }
4744
4917
  function readOpenCodeCredentials(homeDir, log3) {
4745
4918
  try {
4746
- const raw = readFileSync4(join5(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
4919
+ const raw = readFileSync5(join6(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
4747
4920
  let parsed;
4748
4921
  try {
4749
4922
  parsed = JSON.parse(raw);
@@ -4943,9 +5116,9 @@ async function importOpenCodeV2Credentials({
4943
5116
 
4944
5117
  // src/lib/opencode/session-db-boot.ts
4945
5118
  import { spawn as spawn2 } from "node:child_process";
4946
- 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";
4947
5120
  import { homedir as homedir4 } from "node:os";
4948
- import { dirname as dirname4, resolve as resolvePath } from "node:path";
5121
+ import { dirname as dirname5, resolve as resolvePath } from "node:path";
4949
5122
 
4950
5123
  // src/lib/runner-synchroniser.ts
4951
5124
  import { spawn } from "node:child_process";
@@ -5061,7 +5234,7 @@ function reportRecord(stage, outcome, reason, litestreamExitCode, options) {
5061
5234
  function clearMarker(options) {
5062
5235
  if (!options.noReplicateMarker) return;
5063
5236
  try {
5064
- unlinkSync2(options.noReplicateMarker);
5237
+ unlinkSync3(options.noReplicateMarker);
5065
5238
  } catch (error2) {
5066
5239
  if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return;
5067
5240
  options.log(
@@ -5073,8 +5246,8 @@ function clearMarker(options) {
5073
5246
  function markNoReplicate(options, message) {
5074
5247
  if (options.noReplicateMarker) {
5075
5248
  try {
5076
- mkdirSync(dirname4(options.noReplicateMarker), { recursive: true });
5077
- writeFileSync(options.noReplicateMarker, "");
5249
+ mkdirSync2(dirname5(options.noReplicateMarker), { recursive: true });
5250
+ writeFileSync2(options.noReplicateMarker, "");
5078
5251
  } catch (error2) {
5079
5252
  options.log(
5080
5253
  `Could not write session-DB no-replicate marker ${options.noReplicateMarker}: ${error2 instanceof Error ? error2.message : String(error2)}`,
@@ -5087,7 +5260,7 @@ function markNoReplicate(options, message) {
5087
5260
  function discardSessionDbDebris(options) {
5088
5261
  for (const path of [options.dbPath, `${options.dbPath}-wal`, `${options.dbPath}-shm`]) {
5089
5262
  try {
5090
- unlinkSync2(path);
5263
+ unlinkSync3(path);
5091
5264
  } catch (error2) {
5092
5265
  if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") continue;
5093
5266
  options.log(
@@ -5228,8 +5401,8 @@ async function ensureLitestreamConfig(options, env) {
5228
5401
  return null;
5229
5402
  }
5230
5403
  try {
5231
- mkdirSync(dirname4(configPath), { recursive: true });
5232
- writeFileSync(configPath, rendered.stdout);
5404
+ mkdirSync2(dirname5(configPath), { recursive: true });
5405
+ writeFileSync2(configPath, rendered.stdout);
5233
5406
  } catch (error2) {
5234
5407
  options.log(
5235
5408
  `Could not write ${configPath}: ${error2 instanceof Error ? error2.message : String(error2)}`,
@@ -5494,8 +5667,8 @@ async function restoreAndVerifySessionDb(options) {
5494
5667
 
5495
5668
  // src/lib/opencode/session-db-provenance.ts
5496
5669
  import { createRequire } from "node:module";
5497
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
5498
- 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";
5499
5672
  var require2 = createRequire(import.meta.url);
5500
5673
  function readSessionDbMigrationIds(dbPath) {
5501
5674
  let db;
@@ -5532,12 +5705,12 @@ function readSessionDbMigrationIds(dbPath) {
5532
5705
  }
5533
5706
  function sessionDbProvenanceStatePath(homeDir, env) {
5534
5707
  const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
5535
- return override || join6(homeDir, ".local", "state", "evident", "session-db-provenance.json");
5708
+ return override || join7(homeDir, ".local", "state", "evident", "session-db-provenance.json");
5536
5709
  }
5537
5710
  function loadSessionDbProvenanceState(path) {
5538
5711
  let value;
5539
5712
  try {
5540
- value = JSON.parse(readFileSync5(path, "utf8"));
5713
+ value = JSON.parse(readFileSync6(path, "utf8"));
5541
5714
  } catch (error2) {
5542
5715
  if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return {};
5543
5716
  console.error(
@@ -5561,8 +5734,8 @@ function loadSessionDbProvenanceState(path) {
5561
5734
  }
5562
5735
  function saveSessionDbProvenanceState(path, state) {
5563
5736
  try {
5564
- mkdirSync2(dirname5(path), { recursive: true });
5565
- writeFileSync2(path, `${JSON.stringify(state, null, 2)}
5737
+ mkdirSync3(dirname6(path), { recursive: true });
5738
+ writeFileSync3(path, `${JSON.stringify(state, null, 2)}
5566
5739
  `, "utf8");
5567
5740
  } catch (error2) {
5568
5741
  console.error(
@@ -5621,7 +5794,11 @@ function isSessionDbProvenanceRecord(value) {
5621
5794
  var OPENCODE2_PACKAGE_SPEC = "@opencode-ai/cli@0.0.0-beta-17823";
5622
5795
 
5623
5796
  // src/lib/opencode/opencode-version-gate.ts
5624
- 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
+ ];
5625
5802
  function isQueueValidatedVersion(version2) {
5626
5803
  if (!version2) return false;
5627
5804
  return QUEUE_VALIDATED_OPENCODE_VERSIONS.includes(version2);
@@ -6206,15 +6383,15 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
6206
6383
 
6207
6384
  // src/lib/opencode/session-db-size.ts
6208
6385
  import { statSync as statSync4 } from "node:fs";
6209
- import { join as join7 } from "node:path";
6386
+ import { join as join8 } from "node:path";
6210
6387
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
6211
6388
  function statSessionDbBytes(homeDir) {
6212
- const dbPath = join7(homeDir, ".local", "share", "opencode", "opencode.db");
6389
+ const dbPath = join8(homeDir, ".local", "share", "opencode", "opencode.db");
6213
6390
  try {
6214
6391
  return statSync4(dbPath).size;
6215
6392
  } catch (err) {
6216
- const isMissingFile = err instanceof Error && "code" in err && err.code === "ENOENT";
6217
- if (!isMissingFile) {
6393
+ const isMissingFile2 = err instanceof Error && "code" in err && err.code === "ENOENT";
6394
+ if (!isMissingFile2) {
6218
6395
  console.error(
6219
6396
  `[statSessionDbBytes] could not stat ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
6220
6397
  );
@@ -6239,12 +6416,12 @@ function buildSessionStoreSizeWarning(input) {
6239
6416
  // src/lib/opencode/log-tail.ts
6240
6417
  import { statSync as statSync5 } from "node:fs";
6241
6418
  import { homedir as homedir5 } from "node:os";
6242
- import { join as join8 } from "node:path";
6419
+ import { join as join9 } from "node:path";
6243
6420
  import { open as open2, stat } from "node:fs/promises";
6244
6421
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
6245
6422
  function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
6246
- const dataDir = env.XDG_DATA_HOME || join8(homeDir, ".local", "share");
6247
- 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");
6248
6425
  }
6249
6426
  function isEnoent(error2) {
6250
6427
  return error2?.code === "ENOENT";
@@ -6911,10 +7088,10 @@ var RunnerConnection = class _RunnerConnection {
6911
7088
  };
6912
7089
 
6913
7090
  // src/lib/tunnel/ready-marker.ts
6914
- import { writeFileSync as writeFileSync3 } from "node:fs";
7091
+ import { writeFileSync as writeFileSync4 } from "node:fs";
6915
7092
  function writeTunnelReadyMarker(path, agentId) {
6916
7093
  try {
6917
- writeFileSync3(path, `${agentId}
7094
+ writeFileSync4(path, `${agentId}
6918
7095
  `);
6919
7096
  return { ok: true };
6920
7097
  } catch (error2) {
@@ -6939,7 +7116,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
6939
7116
  }
6940
7117
 
6941
7118
  // src/lib/process-liveness.ts
6942
- import { readFileSync as readFileSync6 } from "node:fs";
7119
+ import { readFileSync as readFileSync7 } from "node:fs";
6943
7120
  function isProcessAlive(pid) {
6944
7121
  try {
6945
7122
  process.kill(pid, 0);
@@ -6954,7 +7131,7 @@ function isProcessAlive(pid) {
6954
7131
  }
6955
7132
  if (process.platform !== "linux") return true;
6956
7133
  try {
6957
- const status2 = readFileSync6(`/proc/${pid}/status`, "utf8");
7134
+ const status2 = readFileSync7(`/proc/${pid}/status`, "utf8");
6958
7135
  return !/^State:\s+Z(?:\s|$)/m.test(status2);
6959
7136
  } catch (error2) {
6960
7137
  console.error(
@@ -7313,12 +7490,12 @@ function createSessionStoreHealth(options = {}) {
7313
7490
  import { homedir as homedir6 } from "node:os";
7314
7491
 
7315
7492
  // src/lib/runner-file-sync.ts
7316
- import { join as join10 } from "node:path";
7493
+ import { join as join11 } from "node:path";
7317
7494
 
7318
7495
  // src/lib/file-push.ts
7319
7496
  import { randomUUID } from "node:crypto";
7320
7497
  import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
7321
- 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";
7322
7499
  var FILE_MODE = 384;
7323
7500
  var DIRECTORY_MODE = 448;
7324
7501
  async function writePushedFile(request3) {
@@ -7349,9 +7526,9 @@ async function writePushedFile(request3) {
7349
7526
  }
7350
7527
  try {
7351
7528
  const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
7352
- dirname6(candidate)
7529
+ dirname7(candidate)
7353
7530
  );
7354
- const realTarget = join9(existingAncestor, ...missingSegments, basename2(candidate));
7531
+ const realTarget = join10(existingAncestor, ...missingSegments, basename2(candidate));
7355
7532
  const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
7356
7533
  if (allowedDirectory === null) {
7357
7534
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
@@ -7361,8 +7538,8 @@ async function writePushedFile(request3) {
7361
7538
  }
7362
7539
  if (missingSegments.length > 0) {
7363
7540
  await createMissingDirectories(existingAncestor, missingSegments);
7364
- const realParent = await realpath(dirname6(realTarget));
7365
- if (realParent !== dirname6(realTarget) || !contains(allowedDirectory, realTarget)) {
7541
+ const realParent = await realpath(dirname7(realTarget));
7542
+ if (realParent !== dirname7(realTarget) || !contains(allowedDirectory, realTarget)) {
7366
7543
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
7367
7544
  path: realTarget,
7368
7545
  bytes,
@@ -7387,7 +7564,7 @@ function expandAndValidate(requestedPath, homeDir) {
7387
7564
  if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
7388
7565
  return null;
7389
7566
  }
7390
- 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;
7391
7568
  if (expanded.split(/[/\\]/).includes("..")) {
7392
7569
  return null;
7393
7570
  }
@@ -7405,7 +7582,7 @@ async function resolveNearestExistingAncestor(directory) {
7405
7582
  try {
7406
7583
  return { existingAncestor: await realpath(current), missingSegments };
7407
7584
  } catch (err) {
7408
- const parent = dirname6(current);
7585
+ const parent = dirname7(current);
7409
7586
  if (err.code !== "ENOENT" || parent === current) {
7410
7587
  throw err;
7411
7588
  }
@@ -7460,13 +7637,13 @@ function contains(realDirectory, realTarget) {
7460
7637
  async function createMissingDirectories(existingAncestor, missingSegments) {
7461
7638
  let current = existingAncestor;
7462
7639
  for (const segment of missingSegments) {
7463
- current = join9(current, segment);
7640
+ current = join10(current, segment);
7464
7641
  await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
7465
7642
  await chmod(current, DIRECTORY_MODE);
7466
7643
  }
7467
7644
  }
7468
7645
  async function writeAtomically(realTarget, content) {
7469
- const temporaryPath = join9(dirname6(realTarget), `.evident-push-${randomUUID()}.tmp`);
7646
+ const temporaryPath = join10(dirname7(realTarget), `.evident-push-${randomUUID()}.tmp`);
7470
7647
  let handle;
7471
7648
  try {
7472
7649
  handle = await open3(temporaryPath, "wx", FILE_MODE);
@@ -7596,12 +7773,12 @@ var NOT_APPLIED = {
7596
7773
  opencodeAuthApplied: false
7597
7774
  };
7598
7775
  function isClaudeCredentialPath(requestedPath, homeDir) {
7599
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7600
- 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);
7601
7778
  }
7602
7779
  function isOpenCodeAuthPath(requestedPath, homeDir) {
7603
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7604
- 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);
7605
7782
  }
7606
7783
  async function applyOne(options, file) {
7607
7784
  const label = `${file.id.slice(0, 8)} (${file.path})`;
@@ -7738,6 +7915,211 @@ function describe(err) {
7738
7915
  return err instanceof Error ? err.message : String(err);
7739
7916
  }
7740
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
+
7741
8123
  // src/lib/channels/driver.ts
7742
8124
  function messageIdOf(m) {
7743
8125
  if (!m || typeof m !== "object") return void 0;
@@ -7746,7 +8128,7 @@ function messageIdOf(m) {
7746
8128
  return typeof infoId === "string" ? infoId : void 0;
7747
8129
  }
7748
8130
  function composeAbandonmentNote(finish, replyShape, outcome) {
7749
- 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.");
7750
8132
  if (replyShape === null) return base;
7751
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)` : ""}.`;
7752
8134
  }
@@ -7783,6 +8165,9 @@ var ALIVE_WITHHELD_THRESHOLD_MS = 3 * HEARTBEAT_MS;
7783
8165
  var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
7784
8166
  var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
7785
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;
7786
8171
  var B2_ABANDONMENT_RECHECK_MS = HEARTBEAT_MS;
7787
8172
  var POLL_MISS_GRACE_MS = HEARTBEAT_MS;
7788
8173
  var WATCHER_STALL_MS = 3 * POLL_MISS_GRACE_MS;
@@ -8146,6 +8531,7 @@ var ChannelDriver = class _ChannelDriver {
8146
8531
  * re-downloaded and re-written every ~2s until the server expires it.
8147
8532
  */
8148
8533
  fileAckFailures = /* @__PURE__ */ new Map();
8534
+ vaultFileOutcomes = /* @__PURE__ */ new Map();
8149
8535
  /**
8150
8536
  * Monotonic count of files this runner has pulled and written (#559). Only
8151
8537
  * ever increases, so `run.ts` detects work by comparing it against the value
@@ -8303,10 +8689,21 @@ var ChannelDriver = class _ChannelDriver {
8303
8689
  ackFailures: this.fileAckFailures,
8304
8690
  log: this.log
8305
8691
  });
8306
- 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;
8307
8704
  if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
8308
8705
  if (result.opencodeAuthApplied) this.opencodeAuthApplyCount += 1;
8309
- return result.applied;
8706
+ return applied;
8310
8707
  } catch (err) {
8311
8708
  this.log({
8312
8709
  level: "error",
@@ -8718,6 +9115,47 @@ var ChannelDriver = class _ChannelDriver {
8718
9115
  agent: message.opencode_agent ?? void 0,
8719
9116
  model: message.opencode_model ?? void 0
8720
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
+ }
8721
9159
  let opencodeMessageId;
8722
9160
  let preDispatchUserIds = null;
8723
9161
  try {
@@ -9842,6 +10280,12 @@ var ChannelDriver = class _ChannelDriver {
9842
10280
  b2AbandonedWithoutAnswer: false,
9843
10281
  ambiguousPinnedSinceMs: 0,
9844
10282
  ambiguousResolved: false,
10283
+ midFlightPinnedSinceMs: 0,
10284
+ midFlightNotOngoingStreak: 0,
10285
+ midFlightLastStatusCheckMs: 0,
10286
+ midFlightOrphanSignalled: false,
10287
+ midFlightOrphanWithoutAnswer: false,
10288
+ midFlightHeldLogged: false,
9845
10289
  sessionStoreFailurePending: false,
9846
10290
  neverStartedResolved: false
9847
10291
  });
@@ -9981,6 +10425,12 @@ var ChannelDriver = class _ChannelDriver {
9981
10425
  b2AbandonedWithoutAnswer: false,
9982
10426
  ambiguousPinnedSinceMs: 0,
9983
10427
  ambiguousResolved: false,
10428
+ midFlightPinnedSinceMs: 0,
10429
+ midFlightNotOngoingStreak: 0,
10430
+ midFlightLastStatusCheckMs: 0,
10431
+ midFlightOrphanSignalled: false,
10432
+ midFlightOrphanWithoutAnswer: false,
10433
+ midFlightHeldLogged: false,
9984
10434
  sessionStoreFailurePending: false,
9985
10435
  neverStartedResolved: false
9986
10436
  });
@@ -10752,7 +11202,11 @@ var ChannelDriver = class _ChannelDriver {
10752
11202
  watcher,
10753
11203
  inFlight,
10754
11204
  messages,
10755
- finish
11205
+ composeAbandonmentNote(
11206
+ finish,
11207
+ describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
11208
+ "without-answer"
11209
+ )
10756
11210
  );
10757
11211
  } else {
10758
11212
  await this.settleMessageDone(
@@ -10811,7 +11265,11 @@ var ChannelDriver = class _ChannelDriver {
10811
11265
  watcher,
10812
11266
  inFlight,
10813
11267
  messages,
10814
- finish
11268
+ composeAbandonmentNote(
11269
+ finish,
11270
+ describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
11271
+ "without-answer"
11272
+ )
10815
11273
  );
10816
11274
  return;
10817
11275
  }
@@ -10905,6 +11363,106 @@ var ChannelDriver = class _ChannelDriver {
10905
11363
  return;
10906
11364
  }
10907
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
+ }
10908
11466
  if (watcher.inFlight.has(inFlight.evidentMessageId) && activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
10909
11467
  await this.settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick);
10910
11468
  return;
@@ -11104,13 +11662,8 @@ var ChannelDriver = class _ChannelDriver {
11104
11662
  this.removeInFlight(watcher, inFlight.evidentMessageId);
11105
11663
  }
11106
11664
  /** Report a confirmed b2 abandonment that produced no answer text. */
11107
- async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages, finish) {
11665
+ async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages, error2) {
11108
11666
  const conv = watcher.conv;
11109
- const error2 = composeAbandonmentNote(
11110
- finish,
11111
- describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
11112
- "without-answer"
11113
- );
11114
11667
  this.anchorDeliveryDeadline(inFlight);
11115
11668
  this.log({
11116
11669
  level: "warn",
@@ -13622,7 +14175,7 @@ Port ${port} is already in use.`));
13622
14175
  }
13623
14176
 
13624
14177
  // src/lib/runner-credentials.ts
13625
- import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "node:fs";
14178
+ import { chmodSync as chmodSync2, writeFileSync as writeFileSync5 } from "node:fs";
13626
14179
  import { spawn as spawn5 } from "node:child_process";
13627
14180
  var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
13628
14181
  var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
@@ -13865,8 +14418,8 @@ async function configureGitHubAccess({ env, log: log3 }) {
13865
14418
  }
13866
14419
  try {
13867
14420
  env.GIT_CONFIG_GLOBAL = GIT_CONFIG_GLOBAL;
13868
- writeFileSync4(GIT_CONFIG_GLOBAL, "");
13869
- 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 });
13870
14423
  chmodSync2(GIT_CREDENTIAL_HELPER, 448);
13871
14424
  const config = [
13872
14425
  ["user.name", env.GIT_USER_NAME ?? "evident-bot"],
@@ -13899,7 +14452,7 @@ async function configureGitHubAccess({ env, log: log3 }) {
13899
14452
  // src/lib/opencode/config-overlay.ts
13900
14453
  import { execFileSync as execFileSync2 } from "node:child_process";
13901
14454
  import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
13902
- import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
14455
+ import { isAbsolute as isAbsolute3, join as join12, resolve as resolve4 } from "node:path";
13903
14456
  function parseConfigContent(content) {
13904
14457
  if (!content?.trim()) return {};
13905
14458
  const parsed = JSON.parse(content);
@@ -13916,22 +14469,6 @@ function mergeManagedOpenCodeConfigContent(content, managedProviders) {
13916
14469
  }
13917
14470
  return JSON.stringify({ ...config, provider: { ...provider, ...managedProviders } });
13918
14471
  }
13919
- function removeManagedOpenCodeConfigContent(content) {
13920
- if (!content?.trim()) return content;
13921
- const config = parseConfigContent(content);
13922
- if (typeof config.provider !== "object" || config.provider === null || Array.isArray(config.provider)) {
13923
- return content;
13924
- }
13925
- const provider = { ...config.provider };
13926
- let changed = false;
13927
- for (const namespace of CREDENTIAL_PROXY_NATIVE_NAMESPACES) {
13928
- if (namespace in provider) {
13929
- delete provider[namespace];
13930
- changed = true;
13931
- }
13932
- }
13933
- return changed ? JSON.stringify({ ...config, provider }) : content;
13934
- }
13935
14472
  function isFile(filePath) {
13936
14473
  return existsSync2(filePath) && statSync6(filePath).isFile();
13937
14474
  }
@@ -13945,7 +14482,7 @@ function applyRunnerOpenCodeConfig({
13945
14482
  return;
13946
14483
  }
13947
14484
  const source = isAbsolute3(overlayPath) ? overlayPath : resolve4(cwd, overlayPath);
13948
- const target = isFile(join11(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
14485
+ const target = isFile(join12(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
13949
14486
  if (!isFile(source)) {
13950
14487
  log3(
13951
14488
  `RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
@@ -13953,7 +14490,7 @@ function applyRunnerOpenCodeConfig({
13953
14490
  );
13954
14491
  return;
13955
14492
  }
13956
- copyFileSync(source, join11(cwd, target));
14493
+ copyFileSync(source, join12(cwd, target));
13957
14494
  try {
13958
14495
  execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
13959
14496
  stdio: "ignore"
@@ -13962,13 +14499,13 @@ function applyRunnerOpenCodeConfig({
13962
14499
  const detail = error2 instanceof Error ? error2.message : String(error2);
13963
14500
  log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
13964
14501
  }
13965
- log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
14502
+ log3(`Applied runner OpenCode config ${source} to ${join12(cwd, target)}`);
13966
14503
  }
13967
14504
 
13968
14505
  // src/lib/opencode/runtime-provenance.ts
13969
- import { readFileSync as readFileSync7, readdirSync } from "node:fs";
14506
+ import { readFileSync as readFileSync8, readdirSync } from "node:fs";
13970
14507
  import { homedir as homedir7 } from "node:os";
13971
- import { join as join12 } from "node:path";
14508
+ import { join as join13 } from "node:path";
13972
14509
  var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
13973
14510
  var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
13974
14511
  function errorKind(error2) {
@@ -14036,7 +14573,7 @@ function parseJsonc(value) {
14036
14573
  return JSON.parse(output);
14037
14574
  }
14038
14575
  function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
14039
- return join12(env.XDG_CACHE_HOME ?? join12(homeDir, ".cache"), "opencode");
14576
+ return join13(env.XDG_CACHE_HOME ?? join13(homeDir, ".cache"), "opencode");
14040
14577
  }
14041
14578
  function readConfiguredPluginSpecs({
14042
14579
  cwd = process.cwd(),
@@ -14045,10 +14582,10 @@ function readConfiguredPluginSpecs({
14045
14582
  let config;
14046
14583
  try {
14047
14584
  try {
14048
- config = parseJsonc(readFileSync7(join12(cwd, "opencode.jsonc"), "utf8"));
14585
+ config = parseJsonc(readFileSync8(join13(cwd, "opencode.jsonc"), "utf8"));
14049
14586
  } catch (error2) {
14050
14587
  if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
14051
- config = parseJsonc(readFileSync7(join12(cwd, "opencode.json"), "utf8"));
14588
+ config = parseJsonc(readFileSync8(join13(cwd, "opencode.json"), "utf8"));
14052
14589
  }
14053
14590
  } catch (error2) {
14054
14591
  log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
@@ -14076,7 +14613,7 @@ function resolveInstalledPluginVersions({
14076
14613
  homeDir = homedir7(),
14077
14614
  log: log3
14078
14615
  }) {
14079
- const packagesRoot = join12(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
14616
+ const packagesRoot = join13(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
14080
14617
  let directories;
14081
14618
  try {
14082
14619
  directories = readdirSync(packagesRoot, { withFileTypes: true });
@@ -14097,8 +14634,8 @@ function resolveInstalledPluginVersions({
14097
14634
  let resolvedVersion = null;
14098
14635
  try {
14099
14636
  const packageJson = JSON.parse(
14100
- readFileSync7(
14101
- join12(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
14637
+ readFileSync8(
14638
+ join13(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
14102
14639
  "utf8"
14103
14640
  )
14104
14641
  );
@@ -14119,7 +14656,7 @@ function resolveInstalledPluginVersions({
14119
14656
  }
14120
14657
 
14121
14658
  // src/lib/credential-sync.ts
14122
- import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
14659
+ import { renameSync, writeFileSync as writeFileSync6 } from "node:fs";
14123
14660
  var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
14124
14661
  var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
14125
14662
  var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
@@ -14153,7 +14690,7 @@ function writeMarker(markerPath, outcomes, log3) {
14153
14690
  `;
14154
14691
  const temporaryPath = `${markerPath}.tmp`;
14155
14692
  try {
14156
- writeFileSync5(temporaryPath, body, { mode: 384 });
14693
+ writeFileSync6(temporaryPath, body, { mode: 384 });
14157
14694
  renameSync(temporaryPath, markerPath);
14158
14695
  } catch (error2) {
14159
14696
  log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
@@ -14393,23 +14930,23 @@ async function responseJson(response) {
14393
14930
  function readSetup(value) {
14394
14931
  if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
14395
14932
  const setup = value;
14396
- 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) {
14397
14934
  return null;
14398
14935
  }
14399
14936
  return setup;
14400
14937
  }
14401
14938
  function clearLocalSetup(options) {
14402
14939
  const env = options.env ?? process.env;
14403
- (options.store ?? defaultStore).clear(options.agentId);
14404
- delete env.EVIDENT_PROXY_TOKEN;
14405
- try {
14406
- const content = removeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT);
14407
- if (content === void 0) delete env.OPENCODE_CONFIG_CONTENT;
14408
- else env.OPENCODE_CONFIG_CONTENT = content;
14409
- } 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) {
14410
14947
  options.log(
14411
- "MANAGED-INFERENCE-CLEANUP-PARTIAL: stored credentials were removed, but malformed OPENCODE_CONFIG_CONTENT could not be inspected",
14412
- "warn"
14948
+ `MANAGED-INFERENCE-CLEANUP-PRESERVED-EXTERNAL-TOKEN: runner=${options.agentId}`,
14949
+ "debug"
14413
14950
  );
14414
14951
  }
14415
14952
  }
@@ -14435,6 +14972,25 @@ async function discoverManagedInferenceSetup(options) {
14435
14972
  const setup = readSetup(body?.setup);
14436
14973
  return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed setup discovery returned an invalid response" };
14437
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
+ }
14438
14994
  async function post(options, operation, body) {
14439
14995
  const headers = requestHeaders(options);
14440
14996
  try {
@@ -14449,31 +15005,37 @@ async function post(options, operation, body) {
14449
15005
  }
14450
15006
  }
14451
15007
  function applyCredential(options, credential) {
14452
- const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
14453
- inferenceBaseUrl: credential.inferenceBaseUrl,
14454
- modelDescriptors: credential.modelDescriptors
14455
- });
14456
- if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
14457
- return "Managed setup contains unsupported provider descriptors";
14458
- }
14459
15008
  const env = options.env ?? process.env;
14460
15009
  let merged;
14461
- try {
14462
- merged = mergeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT, config.provider);
14463
- } catch (error2) {
14464
- 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
+ }
14465
15023
  }
14466
15024
  try {
14467
15025
  (options.store ?? defaultStore).set(options.agentId, credential);
14468
15026
  } catch (error2) {
15027
+ const detail = (error2 instanceof Error ? error2.message : String(error2)).replaceAll(
15028
+ credential.token,
15029
+ "[redacted]"
15030
+ );
14469
15031
  options.log(
14470
- `MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation}`,
15032
+ `MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation} detail=${redactDiagnosticText(detail)}`,
14471
15033
  "warn"
14472
15034
  );
14473
15035
  return "Managed credentials could not be written to the protected store";
14474
15036
  }
14475
15037
  env.EVIDENT_PROXY_TOKEN = credential.token;
14476
- env.OPENCODE_CONFIG_CONTENT = merged;
15038
+ if (merged !== void 0) env.OPENCODE_CONFIG_CONTENT = merged;
14477
15039
  return null;
14478
15040
  }
14479
15041
  async function reportManagedInferenceApplyFailure(options, failure) {
@@ -14524,7 +15086,7 @@ async function prepareManagedInferenceSetup(options, discovery, health) {
14524
15086
  }
14525
15087
  return reaffirmStored(options, stored);
14526
15088
  }
14527
- 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) {
14528
15090
  return reaffirmStored(options, stored);
14529
15091
  }
14530
15092
  const response = await post(options, "bootstrap", { generation: discovery.setup.generation });
@@ -14562,7 +15124,7 @@ async function acknowledgeManagedInferenceSetup(options, preparation) {
14562
15124
  const response = await post(options, "acknowledge", {
14563
15125
  generation: preparation.generation,
14564
15126
  token_id: preparation.tokenId,
14565
- config_validated: true
15127
+ config_validated: options.autoConfigureCredentialsProxy === true
14566
15128
  });
14567
15129
  if (!response?.ok) {
14568
15130
  throw new Error(
@@ -14617,7 +15179,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
14617
15179
  if (trimmed === "") {
14618
15180
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
14619
15181
  }
14620
- 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;
14621
15183
  if (!isAbsolute4(expanded)) {
14622
15184
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
14623
15185
  }
@@ -15273,7 +15835,7 @@ function shouldWarnForReclaimSkip(reason) {
15273
15835
  return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
15274
15836
  }
15275
15837
  function sessionDbPath() {
15276
- return join13(homedir8(), ".local", "share", "opencode", "opencode.db");
15838
+ return join14(homedir8(), ".local", "share", "opencode", "opencode.db");
15277
15839
  }
15278
15840
  function logSessionCleanupActivity(state, phase, entry) {
15279
15841
  logActivity(state, {
@@ -15282,6 +15844,47 @@ function logSessionCleanupActivity(state, phase, entry) {
15282
15844
  metadata: { ...entry.metadata, phase }
15283
15845
  });
15284
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
+ }
15285
15888
  function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
15286
15889
  const record = {
15287
15890
  v: 1,
@@ -15339,17 +15942,22 @@ async function runSweep(state, driver, config) {
15339
15942
  let failedThrew = 0;
15340
15943
  let skippedNewlyActive = 0;
15341
15944
  let remaining = 0;
15945
+ const deletionRecords = [];
15342
15946
  let protectedNow = driver.protectedSessionIds();
15343
15947
  if (sessions !== null) {
15344
- const toDelete = selectSessionsToDelete(
15345
- sessions.map((s) => ({ id: s.id, lastActivityMs: sessionLastActivityMs(s) })),
15346
- {
15347
- maxAgeMs: config.maxAgeMs,
15348
- maxCount: config.maxCount,
15349
- nowMs: Date.now(),
15350
- protectedIds: protectedNow
15351
- }
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])
15352
15954
  );
15955
+ const toDelete = selectSessionsToDelete(snapshots, {
15956
+ maxAgeMs: config.maxAgeMs,
15957
+ maxCount: config.maxCount,
15958
+ nowMs: Date.now(),
15959
+ protectedIds: protectedNow
15960
+ });
15353
15961
  const eligibleToDelete = toDelete.filter(
15354
15962
  (id) => (state.sessionDeleteFailures.get(id)?.isolatedFailures ?? 0) < SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES
15355
15963
  );
@@ -15369,6 +15977,17 @@ async function runSweep(state, driver, config) {
15369
15977
  const outcome = await deleteSession(state.port, id, state.opencodeClient ?? void 0);
15370
15978
  if (outcome.ok) {
15371
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
+ });
15372
15991
  } else if (outcome.status === "threw") {
15373
15992
  failed++;
15374
15993
  failedThrew++;
@@ -15424,6 +16043,32 @@ async function runSweep(state, driver, config) {
15424
16043
  message: `Session cleanup: inspected ${sessions.length}, deleted ${deleted}${failedNote}${skippedNote}${remainingNote} (${mode})`
15425
16044
  });
15426
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
+ }
15427
16072
  const sessionMetadata = {
15428
16073
  sessions_inspected: sessions?.length ?? null,
15429
16074
  sessions_deleted: deleted,
@@ -15493,6 +16138,7 @@ async function runSweep(state, driver, config) {
15493
16138
  }
15494
16139
  });
15495
16140
  }
16141
+ await drainSessionDeletionReports(state);
15496
16142
  } catch (error2) {
15497
16143
  const message = error2 instanceof Error ? error2.message : String(error2);
15498
16144
  logSessionCleanupActivity(state, "sweep", {
@@ -16335,6 +16981,7 @@ async function run(options) {
16335
16981
  }
16336
16982
  }
16337
16983
  reportSessionDbRecovery(state);
16984
+ await drainSessionDeletionReports(state);
16338
16985
  if (sessionDbVerifyFatal) {
16339
16986
  throw new Error(
16340
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`
@@ -16365,13 +17012,17 @@ async function run(options) {
16365
17012
  agentId: state.agentId,
16366
17013
  apiUrl: getApiUrlConfig(),
16367
17014
  getAuthHeader: () => state.authHeader,
17015
+ autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
16368
17016
  log: (message, level = "info") => logActivity(state, {
16369
17017
  type: level === "error" ? "error" : "info",
16370
17018
  level,
16371
17019
  ...level === "error" ? { error: message } : { message }
16372
17020
  })
16373
17021
  };
16374
- const managedDiscovery = await discoverManagedInferenceSetup(managedSetupOptions);
17022
+ let managedDiscovery = await discoverManagedInferenceSetup(managedSetupOptions);
17023
+ if (managedDiscovery.kind === "none") {
17024
+ managedDiscovery = await enrollManagedInferenceSetup(managedSetupOptions);
17025
+ }
16375
17026
  let managedPreparation = { kind: "none" };
16376
17027
  if (managedDiscovery.kind === "failed") {
16377
17028
  managedPreparation = managedDiscovery;
@@ -16461,7 +17112,7 @@ async function run(options) {
16461
17112
  state.opencodeClient = openCodeClient;
16462
17113
  if (options.opencodePidFile && oc.process?.pid !== void 0) {
16463
17114
  try {
16464
- writeFileSync6(options.opencodePidFile, `${oc.process.pid}
17115
+ writeFileSync7(options.opencodePidFile, `${oc.process.pid}
16465
17116
  `, { mode: 384 });
16466
17117
  chmodSync3(options.opencodePidFile, 384);
16467
17118
  } catch (error2) {
@@ -16571,7 +17222,7 @@ async function run(options) {
16571
17222
  let existingPid;
16572
17223
  if (existsSync3(options.litestreamPidFile)) {
16573
17224
  try {
16574
- const rawPid = readFileSync8(options.litestreamPidFile, "utf8").trim();
17225
+ const rawPid = readFileSync9(options.litestreamPidFile, "utf8").trim();
16575
17226
  const parsedPid = Number(rawPid);
16576
17227
  if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
16577
17228
  existingPid = parsedPid;
@@ -16608,7 +17259,7 @@ async function run(options) {
16608
17259
  });
16609
17260
  try {
16610
17261
  if (litestreamProcess.pid !== void 0) {
16611
- writeFileSync6(options.litestreamPidFile, `${litestreamProcess.pid}
17262
+ writeFileSync7(options.litestreamPidFile, `${litestreamProcess.pid}
16612
17263
  `, {
16613
17264
  mode: 384
16614
17265
  });
@@ -16992,6 +17643,9 @@ program.command("run").description("Connect to Evident and process messages").op
16992
17643
  ).option(
16993
17644
  "--opencode-config-overlay <path>",
16994
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)."
16995
17649
  ).option(
16996
17650
  "--credential-sync-marker <path>",
16997
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."
@@ -17035,6 +17689,7 @@ program.command("run").description("Connect to Evident and process messages").op
17035
17689
  restoreSessionDb: options.restoreSessionDb,
17036
17690
  restoreRunnerCredentials: options.restoreRunnerCredentials,
17037
17691
  opencodeConfigOverlay: options.opencodeConfigOverlay,
17692
+ autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
17038
17693
  credentialSyncMarker: options.credentialSyncMarker
17039
17694
  });
17040
17695
  }