@evident-ai/cli 3.4.1-dev.59c7df3 → 3.4.1-dev.5eccf39

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { createRequire } from "module";
4
+ import { createRequire as createRequire2 } from "module";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/commands/login.ts
@@ -763,6 +763,13 @@ function toReportedOpenAiWindow(window) {
763
763
  resets_at: window.resetsAt
764
764
  };
765
765
  }
766
+ function toReportedOpenAiSubscription(snapshot) {
767
+ if (!snapshot.subscription) return null;
768
+ return {
769
+ owner_email: snapshot.subscription.ownerEmail,
770
+ plan_type: snapshot.subscription.planType
771
+ };
772
+ }
766
773
  async function reportOpenAiUsage(agentId, authHeader, snapshot) {
767
774
  try {
768
775
  const apiUrl = getApiUrlConfig();
@@ -773,7 +780,8 @@ async function reportOpenAiUsage(agentId, authHeader, snapshot) {
773
780
  primary: toReportedOpenAiWindow(snapshot.primary),
774
781
  secondary: toReportedOpenAiWindow(snapshot.secondary),
775
782
  has_credits: snapshot.hasCredits,
776
- credits_unlimited: snapshot.creditsUnlimited
783
+ credits_unlimited: snapshot.creditsUnlimited,
784
+ subscription: toReportedOpenAiSubscription(snapshot)
777
785
  }),
778
786
  signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
779
787
  });
@@ -797,6 +805,7 @@ async function reportResourceUsage(agentId, authHeader, usage) {
797
805
  headers: { Authorization: authHeader, "Content-Type": "application/json" },
798
806
  body: JSON.stringify({
799
807
  cpu_percent: usage.cpuPercent,
808
+ cpu_peak_percent: usage.cpuPeakPercent,
800
809
  cpu_count: usage.cpuCount,
801
810
  memory_total_bytes: usage.memoryTotalBytes,
802
811
  memory_available_bytes: usage.memoryAvailableBytes,
@@ -1183,9 +1192,9 @@ async function claudeUsage() {
1183
1192
  }
1184
1193
 
1185
1194
  // src/commands/run.ts
1186
- import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1195
+ import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
1187
1196
  import { homedir as homedir5 } from "os";
1188
- import { isAbsolute as isAbsolute3, join as join8, parse, resolve as resolvePath2 } from "path";
1197
+ import { isAbsolute as isAbsolute3, join as join9, parse, resolve as resolvePath2 } from "path";
1189
1198
  import chalk6 from "chalk";
1190
1199
 
1191
1200
  // ../../packages/types/src/agents/index.ts
@@ -1525,7 +1534,14 @@ function drainSessionDbRecoveryReport({
1525
1534
  skippedLines++;
1526
1535
  return [];
1527
1536
  }
1528
- return [{ ...value, replication_suspended: value.replication_suspended ?? false }];
1537
+ return [
1538
+ {
1539
+ ...value,
1540
+ provenance_reason: value.provenance_reason ?? null,
1541
+ provenance_migration_delta: value.provenance_migration_delta ?? null,
1542
+ replication_suspended: value.replication_suspended ?? false
1543
+ }
1544
+ ];
1529
1545
  } catch (error2) {
1530
1546
  skippedLines++;
1531
1547
  console.error(
@@ -1628,6 +1644,12 @@ function buildSessionDbRecoveryActivity(record) {
1628
1644
  metadata: withoutContractFields(record),
1629
1645
  message: `This runner did not come online because its damaged session database could not be safely separated from its active backup or proven removed. Backed-up session history remains readable at ${record.quarantine_destination ?? "its original location or the quarantine destination named in the boot logs"}; any local session-database files that remain were left in place and nothing opened or wrote them. See the runner boot logs for SESSION-DB-LOCAL-DISCARD-FAILED details.`
1630
1646
  };
1647
+ case "schema_provenance_mismatch":
1648
+ return {
1649
+ level,
1650
+ metadata: withoutContractFields(record),
1651
+ message: `Session database schema provenance mismatch for ${record.dbPath ?? record.db_path ?? "unknown"}: recorded version=${record.recorded_version ?? "unknown"}, current version=${record.current_version ?? "unknown"}, reason=${record.provenance_reason ?? "unknown"}, migration delta=${record.provenance_migration_delta ?? "unknown"}. Inspect the session database and runner backup before continuing.`
1652
+ };
1631
1653
  default:
1632
1654
  return null;
1633
1655
  }
@@ -1642,7 +1664,8 @@ var OUTCOMES = /* @__PURE__ */ new Set([
1642
1664
  "fresh_session_db",
1643
1665
  "history_rolled_back",
1644
1666
  "restore_misconfigured",
1645
- "session_db_boot_refused"
1667
+ "session_db_boot_refused",
1668
+ "schema_provenance_mismatch"
1646
1669
  ]);
1647
1670
  var STAGES = /* @__PURE__ */ new Set(["restore", "verify"]);
1648
1671
  var SEVERITIES = /* @__PURE__ */ new Set(["warning", "error"]);
@@ -1660,7 +1683,7 @@ var STRING_OR_NULL_FIELDS = ["quarantine_destination", "verified_restore_point"]
1660
1683
  function isSessionDbRecoveryRecord(value) {
1661
1684
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1662
1685
  const record = value;
1663
- return record.v === 1 && record.event === "session_db_recovery" && typeof record.at === "string" && STAGES.has(record.stage) && OUTCOMES.has(record.outcome) && SEVERITIES.has(record.severity) && typeof record.reason === "string" && (record.replication_suspended === void 0 || typeof record.replication_suspended === "boolean") && NUMBER_FIELDS.every((field) => record[field] === null || Number.isInteger(record[field])) && STRING_OR_NULL_FIELDS.every(
1686
+ return record.v === 1 && record.event === "session_db_recovery" && typeof record.at === "string" && STAGES.has(record.stage) && OUTCOMES.has(record.outcome) && SEVERITIES.has(record.severity) && typeof record.reason === "string" && (record.replication_suspended === void 0 || typeof record.replication_suspended === "boolean") && (record.provenance_reason === void 0 || record.provenance_reason === null || typeof record.provenance_reason === "string") && (record.provenance_migration_delta === void 0 || record.provenance_migration_delta === null || Number.isInteger(record.provenance_migration_delta)) && NUMBER_FIELDS.every((field) => record[field] === null || Number.isInteger(record[field])) && STRING_OR_NULL_FIELDS.every(
1664
1687
  (field) => record[field] === null || typeof record[field] === "string"
1665
1688
  );
1666
1689
  }
@@ -1714,10 +1737,14 @@ function runSynchroniser(args, opts) {
1714
1737
  let stderr = "";
1715
1738
  let settled = false;
1716
1739
  const timer = {};
1740
+ let abortListener;
1741
+ let spawnListener;
1717
1742
  const finish = (result) => {
1718
1743
  if (settled) return;
1719
1744
  settled = true;
1720
1745
  if (timer.handle) clearTimeout(timer.handle);
1746
+ if (abortListener) opts.signal?.removeEventListener("abort", abortListener);
1747
+ if (spawnListener) child.removeListener("spawn", spawnListener);
1721
1748
  resolve4(result);
1722
1749
  };
1723
1750
  try {
@@ -1743,6 +1770,25 @@ function runSynchroniser(args, opts) {
1743
1770
  child.once("close", (code) => {
1744
1771
  finish({ code, stdout, stderr, timedOut: false });
1745
1772
  });
1773
+ if (opts.signal) {
1774
+ const killChild = () => {
1775
+ if (child.pid === void 0) {
1776
+ if (!spawnListener) {
1777
+ spawnListener = killChild;
1778
+ child.once("spawn", spawnListener);
1779
+ }
1780
+ return;
1781
+ }
1782
+ child.kill("SIGKILL");
1783
+ };
1784
+ abortListener = killChild;
1785
+ if (opts.signal.aborted) {
1786
+ abortListener();
1787
+ } else {
1788
+ opts.signal.addEventListener("abort", abortListener, { once: true });
1789
+ if (opts.signal.aborted) abortListener();
1790
+ }
1791
+ }
1746
1792
  timer.handle = setTimeout(
1747
1793
  () => {
1748
1794
  child.kill("SIGKILL");
@@ -1780,6 +1826,8 @@ function reportRecord(stage, outcome, reason, litestreamExitCode, options) {
1780
1826
  quarantined_bytes: null,
1781
1827
  verified_restore_point: null,
1782
1828
  restore_points_tried: null,
1829
+ provenance_reason: null,
1830
+ provenance_migration_delta: null,
1783
1831
  replication_suspended: stage === "restore"
1784
1832
  });
1785
1833
  }
@@ -2200,6 +2248,131 @@ async function restoreAndVerifySessionDb(options) {
2200
2248
  return { verifyFatal: await verifySessionDb(options, configPath, env) };
2201
2249
  }
2202
2250
 
2251
+ // src/lib/opencode/session-db-provenance.ts
2252
+ import { createRequire } from "module";
2253
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
2254
+ import { dirname as dirname3, join as join3 } from "path";
2255
+ var require2 = createRequire(import.meta.url);
2256
+ function readSessionDbMigrationIds(dbPath) {
2257
+ let db;
2258
+ try {
2259
+ const { DatabaseSync } = require2("node:sqlite");
2260
+ db = new DatabaseSync(dbPath, { readOnly: true });
2261
+ const columns = db.prepare("PRAGMA table_info(migration)").all();
2262
+ const hasExpectedShape = columns.length === 2 && columns.some(
2263
+ (column) => column.name === "id" && typeof column.type === "string" && column.type.toUpperCase() === "TEXT" && column.pk === 1
2264
+ ) && columns.some(
2265
+ (column) => column.name === "time_completed" && typeof column.type === "string" && column.type.toUpperCase() === "INTEGER" && column.notnull === 1 && column.pk === 0
2266
+ );
2267
+ if (!hasExpectedShape) {
2268
+ console.warn(`[readSessionDbMigrationIds] unsupported migration table shape in ${dbPath}`);
2269
+ return null;
2270
+ }
2271
+ const rows = db.prepare("SELECT id FROM migration ORDER BY id").all();
2272
+ if (rows.some((row) => typeof row.id !== "string")) return null;
2273
+ return rows.map((row) => row.id);
2274
+ } catch (error2) {
2275
+ console.warn(
2276
+ `[readSessionDbMigrationIds] could not read migration history from ${dbPath}: ${error2 instanceof Error ? error2.message : String(error2)}`
2277
+ );
2278
+ return null;
2279
+ } finally {
2280
+ try {
2281
+ db?.close();
2282
+ } catch (error2) {
2283
+ console.warn(
2284
+ `[readSessionDbMigrationIds] could not close ${dbPath}: ${error2 instanceof Error ? error2.message : String(error2)}`
2285
+ );
2286
+ }
2287
+ }
2288
+ }
2289
+ function sessionDbProvenanceStatePath(homeDir, env) {
2290
+ const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
2291
+ return override || join3(homeDir, ".local", "state", "evident", "session-db-provenance.json");
2292
+ }
2293
+ function loadSessionDbProvenanceState(path) {
2294
+ let value;
2295
+ try {
2296
+ value = JSON.parse(readFileSync3(path, "utf8"));
2297
+ } catch (error2) {
2298
+ if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return {};
2299
+ console.error(
2300
+ `[session-db-provenance] could not read ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
2301
+ );
2302
+ return {};
2303
+ }
2304
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2305
+ console.error(`[session-db-provenance] ignored malformed state in ${path}`);
2306
+ return {};
2307
+ }
2308
+ const state = {};
2309
+ for (const [dbPath, record] of Object.entries(value)) {
2310
+ if (!isSessionDbProvenanceRecord(record)) {
2311
+ console.error(`[session-db-provenance] ignored malformed state in ${path}`);
2312
+ return {};
2313
+ }
2314
+ state[dbPath] = record;
2315
+ }
2316
+ return state;
2317
+ }
2318
+ function saveSessionDbProvenanceState(path, state) {
2319
+ try {
2320
+ mkdirSync2(dirname3(path), { recursive: true });
2321
+ writeFileSync2(path, `${JSON.stringify(state, null, 2)}
2322
+ `, "utf8");
2323
+ } catch (error2) {
2324
+ console.error(
2325
+ `[session-db-provenance] could not write ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
2326
+ );
2327
+ }
2328
+ }
2329
+ function evaluateSessionDbProvenance(input) {
2330
+ const { currentVersion, currentIds, previous } = input;
2331
+ if (!previous) return { anomaly: false, reason: null };
2332
+ const current = new Set(currentIds);
2333
+ const prior = new Set(previous.migrationIds);
2334
+ for (const id of prior) {
2335
+ if (!current.has(id)) return { anomaly: true, reason: "migration-history-regressed" };
2336
+ }
2337
+ if (current.size > prior.size && previous.opencodeVersion === currentVersion) {
2338
+ return { anomaly: true, reason: "foreign-version-migrations" };
2339
+ }
2340
+ return { anomaly: false, reason: null };
2341
+ }
2342
+ function checkSessionDbProvenance(input) {
2343
+ const { dbPath, currentVersion, homeDir, env } = input;
2344
+ const path = sessionDbProvenanceStatePath(homeDir, env);
2345
+ const state = loadSessionDbProvenanceState(path);
2346
+ const previous = state[dbPath];
2347
+ const currentIds = readSessionDbMigrationIds(dbPath);
2348
+ if (currentIds === null) {
2349
+ return {
2350
+ anomaly: false,
2351
+ reason: null,
2352
+ recordedVersion: previous?.opencodeVersion ?? null,
2353
+ migrationDelta: null
2354
+ };
2355
+ }
2356
+ const decision = evaluateSessionDbProvenance({ currentVersion, currentIds, previous });
2357
+ const migrationDelta = previous ? new Set(currentIds).size - new Set(previous.migrationIds).size : null;
2358
+ state[dbPath] = {
2359
+ opencodeVersion: currentVersion,
2360
+ migrationIds: currentIds,
2361
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2362
+ };
2363
+ saveSessionDbProvenanceState(path, state);
2364
+ return {
2365
+ ...decision,
2366
+ recordedVersion: previous?.opencodeVersion ?? null,
2367
+ migrationDelta
2368
+ };
2369
+ }
2370
+ function isSessionDbProvenanceRecord(value) {
2371
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2372
+ const record = value;
2373
+ return typeof record.opencodeVersion === "string" && Array.isArray(record.migrationIds) && record.migrationIds.every((id) => typeof id === "string") && typeof record.updatedAt === "string";
2374
+ }
2375
+
2203
2376
  // src/lib/opencode/opencode-version-gate.ts
2204
2377
  var QUEUE_VALIDATED_OPENCODE_VERSIONS = ["1.17.11", "1.18.3"];
2205
2378
  function isQueueValidatedVersion(version2) {
@@ -3286,10 +3459,10 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
3286
3459
 
3287
3460
  // src/lib/opencode/session-db-size.ts
3288
3461
  import { statSync as statSync3 } from "fs";
3289
- import { join as join3 } from "path";
3462
+ import { join as join4 } from "path";
3290
3463
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
3291
3464
  function statSessionDbBytes(homeDir) {
3292
- const dbPath = join3(homeDir, ".local", "share", "opencode", "opencode.db");
3465
+ const dbPath = join4(homeDir, ".local", "share", "opencode", "opencode.db");
3293
3466
  try {
3294
3467
  return statSync3(dbPath).size;
3295
3468
  } catch (err) {
@@ -3318,10 +3491,10 @@ function buildSessionStoreSizeWarning(input) {
3318
3491
 
3319
3492
  // src/lib/opencode/session-db-reclaim.ts
3320
3493
  import { statSync as statSync4, statfsSync } from "fs";
3321
- import { dirname as dirname3 } from "path";
3494
+ import { dirname as dirname4 } from "path";
3322
3495
  function insufficientSpaceReason(dbPath, requiredBytes) {
3323
3496
  try {
3324
- const fsStats = statfsSync(dirname3(dbPath));
3497
+ const fsStats = statfsSync(dirname4(dbPath));
3325
3498
  const availableBytes = fsStats.bavail * fsStats.bsize;
3326
3499
  if (availableBytes < requiredBytes) {
3327
3500
  return `only ${availableBytes} bytes free, need ${requiredBytes} for a second copy`;
@@ -3703,8 +3876,8 @@ function connectTunnel(options) {
3703
3876
  try {
3704
3877
  message = JSON.parse(data.toString());
3705
3878
  } catch (error2) {
3706
- const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
3707
- onError?.(`Failed to handle message: ${errorMessage}`);
3879
+ const errorMessage2 = error2 instanceof Error ? error2.message : "Unknown error";
3880
+ onError?.(`Failed to handle message: ${errorMessage2}`);
3708
3881
  return;
3709
3882
  }
3710
3883
  if (isStreamFrame(message)) {
@@ -3847,10 +4020,10 @@ var RunnerConnection = class {
3847
4020
  };
3848
4021
 
3849
4022
  // src/lib/tunnel/ready-marker.ts
3850
- import { writeFileSync as writeFileSync2 } from "fs";
4023
+ import { writeFileSync as writeFileSync3 } from "fs";
3851
4024
  function writeTunnelReadyMarker(path, agentId) {
3852
4025
  try {
3853
- writeFileSync2(path, `${agentId}
4026
+ writeFileSync3(path, `${agentId}
3854
4027
  `);
3855
4028
  return { ok: true };
3856
4029
  } catch (error2) {
@@ -3875,7 +4048,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
3875
4048
  }
3876
4049
 
3877
4050
  // src/lib/process-liveness.ts
3878
- import { readFileSync as readFileSync3 } from "fs";
4051
+ import { readFileSync as readFileSync4 } from "fs";
3879
4052
  function isProcessAlive(pid) {
3880
4053
  try {
3881
4054
  process.kill(pid, 0);
@@ -3890,7 +4063,7 @@ function isProcessAlive(pid) {
3890
4063
  }
3891
4064
  if (process.platform !== "linux") return true;
3892
4065
  try {
3893
- const status2 = readFileSync3(`/proc/${pid}/status`, "utf8");
4066
+ const status2 = readFileSync4(`/proc/${pid}/status`, "utf8");
3894
4067
  return !/^State:\s+Z(?:\s|$)/m.test(status2);
3895
4068
  } catch (error2) {
3896
4069
  console.error(
@@ -3901,9 +4074,9 @@ function isProcessAlive(pid) {
3901
4074
  }
3902
4075
 
3903
4076
  // src/lib/openai-usage.ts
3904
- import { readFileSync as readFileSync4 } from "fs";
4077
+ import { readFileSync as readFileSync5 } from "fs";
3905
4078
  import { homedir as homedir3 } from "os";
3906
- import { join as join4 } from "path";
4079
+ import { join as join5 } from "path";
3907
4080
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
3908
4081
  var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
3909
4082
  var OpenAiUsageError = class extends Error {
@@ -3917,7 +4090,7 @@ function isLocalCredentialProblem2(err) {
3917
4090
  }
3918
4091
  function readOpenCodeChatGptCredentials() {
3919
4092
  try {
3920
- const raw = readFileSync4(join4(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4093
+ const raw = readFileSync5(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
3921
4094
  let parsed;
3922
4095
  try {
3923
4096
  parsed = JSON.parse(raw);
@@ -3939,6 +4112,23 @@ function readOpenCodeChatGptCredentials() {
3939
4112
  return null;
3940
4113
  }
3941
4114
  }
4115
+ function parseChatGptIdentity(accessToken) {
4116
+ const segments = accessToken.split(".");
4117
+ if (segments.length !== 3) return null;
4118
+ let payload;
4119
+ try {
4120
+ const parsed = JSON.parse(Buffer.from(segments[1], "base64url").toString("utf8"));
4121
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
4122
+ payload = parsed;
4123
+ } catch {
4124
+ return null;
4125
+ }
4126
+ const profile = payload["https://api.openai.com/profile"];
4127
+ const auth = payload["https://api.openai.com/auth"];
4128
+ const ownerEmail = typeof profile?.email === "string" ? profile.email.trim() || null : null;
4129
+ const planType = typeof auth?.chatgpt_plan_type === "string" ? auth.chatgpt_plan_type.trim() || null : null;
4130
+ return ownerEmail === null && planType === null ? null : { ownerEmail, planType };
4131
+ }
3942
4132
  function toWindow2(headers, name) {
3943
4133
  const utilizationHeader = headers.get(`x-codex-${name}-used-percent`);
3944
4134
  const windowMinutesHeader = headers.get(`x-codex-${name}-window-minutes`);
@@ -4014,6 +4204,7 @@ async function getOpenAiUsage(port) {
4014
4204
  "credentials_expired"
4015
4205
  );
4016
4206
  }
4207
+ const subscription = parseChatGptIdentity(credentials2.accessToken);
4017
4208
  const models = await resolveProbeModels(port);
4018
4209
  if (models.length === 0) {
4019
4210
  throw new OpenAiUsageError("No supported OpenAI probe model is available.", "no_probe_model");
@@ -4046,7 +4237,7 @@ async function getOpenAiUsage(port) {
4046
4237
  "no_usable_window"
4047
4238
  );
4048
4239
  }
4049
- return usage;
4240
+ return { ...usage, subscription };
4050
4241
  }
4051
4242
  if (res.status === 401) {
4052
4243
  throw new OpenAiUsageError(
@@ -4247,45 +4438,84 @@ function readDisk(homeDir) {
4247
4438
  };
4248
4439
  }
4249
4440
  }
4250
- function createResourceUsageCollector(homeDir) {
4251
- let previous = readCpuSample();
4252
- return async () => {
4441
+ var CPU_PEAK_WINDOW_MS = 6e4;
4442
+ var CPU_PEAK_WINDOW_SAMPLE_COUNT = 4;
4443
+ var CPU_PEAK_SAMPLE_INTERVAL_MS = CPU_PEAK_WINDOW_MS / CPU_PEAK_WINDOW_SAMPLE_COUNT;
4444
+ function createCpuPeakSampler() {
4445
+ const sampleHistory = new Array(CPU_PEAK_WINDOW_SAMPLE_COUNT);
4446
+ sampleHistory[0] = readCpuSample();
4447
+ let nextSampleIndex = 1;
4448
+ let sampleCount = 1;
4449
+ let peak = null;
4450
+ const timer = setInterval(() => {
4253
4451
  const current = readCpuSample();
4254
- const hostCpuPercent = cpuPercentBetween(previous, current);
4255
- const hostCpuCount = cpus().length;
4256
- previous = current;
4257
- const disk = readDisk(homeDir);
4258
- const opencodeDbBytes = statSessionDbBytes(homeDir);
4259
- const { limits, warning: ecsWarning } = await readEcsTaskLimits(process.env);
4260
- const warnings = [];
4261
- if (disk.warning) warnings.push(disk.warning);
4262
- if (ecsWarning) warnings.push(ecsWarning);
4263
- let cpuPercent = hostCpuPercent;
4264
- let cpuCount = hostCpuCount;
4265
- let memoryTotalBytes = totalmem();
4266
- let memoryAvailableBytes = freemem();
4267
- if (limits !== null) {
4268
- cpuCount = limits.cpuCount;
4269
- memoryTotalBytes = limits.memoryTotalBytes;
4270
- memoryAvailableBytes = clamp(
4271
- limits.memoryTotalBytes - (totalmem() - freemem()),
4272
- 0,
4273
- limits.memoryTotalBytes
4274
- );
4275
- cpuPercent = hostCpuPercent === null ? null : clamp(round2(hostCpuPercent * hostCpuCount / limits.cpuCount), 0, 100);
4452
+ const sampleFromWindowAgo = sampleCount === CPU_PEAK_WINDOW_SAMPLE_COUNT ? sampleHistory[nextSampleIndex] : void 0;
4453
+ if (sampleFromWindowAgo !== void 0) {
4454
+ const percentage = cpuPercentBetween(sampleFromWindowAgo, current);
4455
+ if (percentage !== null) {
4456
+ peak = peak === null ? percentage : Math.max(peak, percentage);
4457
+ }
4276
4458
  }
4277
- return {
4278
- usage: {
4279
- cpuPercent,
4280
- cpuCount,
4281
- memoryTotalBytes,
4282
- memoryAvailableBytes,
4283
- diskTotalBytes: disk.totalBytes,
4284
- diskFreeBytes: disk.freeBytes,
4285
- opencodeDbBytes
4286
- },
4287
- warnings
4288
- };
4459
+ sampleHistory[nextSampleIndex] = current;
4460
+ nextSampleIndex = (nextSampleIndex + 1) % CPU_PEAK_WINDOW_SAMPLE_COUNT;
4461
+ sampleCount = Math.min(sampleCount + 1, CPU_PEAK_WINDOW_SAMPLE_COUNT);
4462
+ }, CPU_PEAK_SAMPLE_INTERVAL_MS);
4463
+ return {
4464
+ takeAndReset: () => {
4465
+ const currentPeak = peak;
4466
+ peak = null;
4467
+ return currentPeak;
4468
+ },
4469
+ stop: () => clearInterval(timer)
4470
+ };
4471
+ }
4472
+ function createResourceUsageCollector(homeDir) {
4473
+ let previous = readCpuSample();
4474
+ const cpuPeakSampler = createCpuPeakSampler();
4475
+ return {
4476
+ collect: async () => {
4477
+ const current = readCpuSample();
4478
+ const hostCpuPercent = cpuPercentBetween(previous, current);
4479
+ const hostCpuPeakPercent = cpuPeakSampler.takeAndReset();
4480
+ const hostCpuCount = cpus().length;
4481
+ previous = current;
4482
+ const disk = readDisk(homeDir);
4483
+ const opencodeDbBytes = statSessionDbBytes(homeDir);
4484
+ const { limits, warning: ecsWarning } = await readEcsTaskLimits(process.env);
4485
+ const warnings = [];
4486
+ if (disk.warning) warnings.push(disk.warning);
4487
+ if (ecsWarning) warnings.push(ecsWarning);
4488
+ let cpuPercent = hostCpuPercent;
4489
+ let cpuPeakPercent = hostCpuPeakPercent;
4490
+ let cpuCount = hostCpuCount;
4491
+ let memoryTotalBytes = totalmem();
4492
+ let memoryAvailableBytes = freemem();
4493
+ if (limits !== null) {
4494
+ cpuCount = limits.cpuCount;
4495
+ memoryTotalBytes = limits.memoryTotalBytes;
4496
+ memoryAvailableBytes = clamp(
4497
+ limits.memoryTotalBytes - (totalmem() - freemem()),
4498
+ 0,
4499
+ limits.memoryTotalBytes
4500
+ );
4501
+ cpuPercent = hostCpuPercent === null ? null : clamp(round2(hostCpuPercent * hostCpuCount / limits.cpuCount), 0, 100);
4502
+ cpuPeakPercent = hostCpuPeakPercent === null ? null : clamp(round2(hostCpuPeakPercent * hostCpuCount / limits.cpuCount), 0, 100);
4503
+ }
4504
+ return {
4505
+ usage: {
4506
+ cpuPercent,
4507
+ cpuPeakPercent,
4508
+ cpuCount,
4509
+ memoryTotalBytes,
4510
+ memoryAvailableBytes,
4511
+ diskTotalBytes: disk.totalBytes,
4512
+ diskFreeBytes: disk.freeBytes,
4513
+ opencodeDbBytes
4514
+ },
4515
+ warnings
4516
+ };
4517
+ },
4518
+ stop: cpuPeakSampler.stop
4289
4519
  };
4290
4520
  }
4291
4521
 
@@ -4293,12 +4523,12 @@ function createResourceUsageCollector(homeDir) {
4293
4523
  import { homedir as homedir4 } from "os";
4294
4524
 
4295
4525
  // src/lib/runner-file-sync.ts
4296
- import { join as join6 } from "path";
4526
+ import { join as join7 } from "path";
4297
4527
 
4298
4528
  // src/lib/file-push.ts
4299
4529
  import { randomUUID } from "crypto";
4300
4530
  import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
4301
- import { basename, dirname as dirname4, isAbsolute, join as join5, relative, resolve as resolve2, sep } from "path";
4531
+ import { basename, dirname as dirname5, isAbsolute, join as join6, relative, resolve as resolve2, sep } from "path";
4302
4532
  var FILE_MODE = 384;
4303
4533
  var DIRECTORY_MODE = 448;
4304
4534
  async function writePushedFile(request) {
@@ -4329,9 +4559,9 @@ async function writePushedFile(request) {
4329
4559
  }
4330
4560
  try {
4331
4561
  const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
4332
- dirname4(candidate)
4562
+ dirname5(candidate)
4333
4563
  );
4334
- const realTarget = join5(existingAncestor, ...missingSegments, basename(candidate));
4564
+ const realTarget = join6(existingAncestor, ...missingSegments, basename(candidate));
4335
4565
  const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
4336
4566
  if (allowedDirectory === null) {
4337
4567
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
@@ -4341,8 +4571,8 @@ async function writePushedFile(request) {
4341
4571
  }
4342
4572
  if (missingSegments.length > 0) {
4343
4573
  await createMissingDirectories(existingAncestor, missingSegments);
4344
- const realParent = await realpath(dirname4(realTarget));
4345
- if (realParent !== dirname4(realTarget) || !contains(allowedDirectory, realTarget)) {
4574
+ const realParent = await realpath(dirname5(realTarget));
4575
+ if (realParent !== dirname5(realTarget) || !contains(allowedDirectory, realTarget)) {
4346
4576
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
4347
4577
  path: realTarget,
4348
4578
  bytes,
@@ -4367,7 +4597,7 @@ function expandAndValidate(requestedPath, homeDir) {
4367
4597
  if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
4368
4598
  return null;
4369
4599
  }
4370
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join5(homeDir, requestedPath.slice(2)) : requestedPath;
4600
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
4371
4601
  if (expanded.split(/[/\\]/).includes("..")) {
4372
4602
  return null;
4373
4603
  }
@@ -4385,7 +4615,7 @@ async function resolveNearestExistingAncestor(directory) {
4385
4615
  try {
4386
4616
  return { existingAncestor: await realpath(current), missingSegments };
4387
4617
  } catch (err) {
4388
- const parent = dirname4(current);
4618
+ const parent = dirname5(current);
4389
4619
  if (err.code !== "ENOENT" || parent === current) {
4390
4620
  throw err;
4391
4621
  }
@@ -4440,13 +4670,13 @@ function contains(realDirectory, realTarget) {
4440
4670
  async function createMissingDirectories(existingAncestor, missingSegments) {
4441
4671
  let current = existingAncestor;
4442
4672
  for (const segment of missingSegments) {
4443
- current = join5(current, segment);
4673
+ current = join6(current, segment);
4444
4674
  await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
4445
4675
  await chmod(current, DIRECTORY_MODE);
4446
4676
  }
4447
4677
  }
4448
4678
  async function writeAtomically(realTarget, content) {
4449
- const temporaryPath = join5(dirname4(realTarget), `.evident-push-${randomUUID()}.tmp`);
4679
+ const temporaryPath = join6(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
4450
4680
  let handle;
4451
4681
  try {
4452
4682
  handle = await open2(temporaryPath, "wx", FILE_MODE);
@@ -4576,12 +4806,12 @@ var NOT_APPLIED = {
4576
4806
  opencodeAuthApplied: false
4577
4807
  };
4578
4808
  function isClaudeCredentialPath(requestedPath, homeDir) {
4579
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
4580
- return expanded === join6(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
4809
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
4810
+ return expanded === join7(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
4581
4811
  }
4582
4812
  function isOpenCodeAuthPath(requestedPath, homeDir) {
4583
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
4584
- return expanded === join6(homeDir, ...OPENCODE_AUTH_SEGMENTS);
4813
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
4814
+ return expanded === join7(homeDir, ...OPENCODE_AUTH_SEGMENTS);
4585
4815
  }
4586
4816
  async function applyOne(options, file) {
4587
4817
  const label = `${file.id.slice(0, 8)} (${file.path})`;
@@ -5108,6 +5338,7 @@ var ChannelDriver = class _ChannelDriver {
5108
5338
  * and stops opencode.
5109
5339
  */
5110
5340
  stopped = false;
5341
+ recycleRequestedFlag = false;
5111
5342
  constructor(config) {
5112
5343
  this.agentId = config.agentId;
5113
5344
  this.port = config.port;
@@ -5213,6 +5444,9 @@ var ChannelDriver = class _ChannelDriver {
5213
5444
  let dispatched = 0;
5214
5445
  try {
5215
5446
  const conversations = await this.getPendingConversations();
5447
+ if (this.recycleRequestedFlag) {
5448
+ this.stop();
5449
+ }
5216
5450
  if (conversations.length > 0) {
5217
5451
  const total = conversations.reduce((sum, c) => sum + (c.pending_message_count ?? 0), 0);
5218
5452
  this.log({
@@ -5341,6 +5575,14 @@ var ChannelDriver = class _ChannelDriver {
5341
5575
  stop() {
5342
5576
  this.stopped = true;
5343
5577
  }
5578
+ /**
5579
+ * The server clears this request when a new MicroVM identity is recorded, so a
5580
+ * same-VM tunnel reconnect does not consume it. This is a plain read rather
5581
+ * than a consume; `run.ts` guards the action once-only.
5582
+ */
5583
+ get recycleRequested() {
5584
+ return this.recycleRequestedFlag;
5585
+ }
5344
5586
  /**
5345
5587
  * Wait (up to `timeoutMs`) for in-flight watcher work to settle during a
5346
5588
  * graceful shutdown, so a turn whose reply is ready — or completes within the
@@ -5478,7 +5720,7 @@ var ChannelDriver = class _ChannelDriver {
5478
5720
  this.signalDispatchNotStarted(conv, message, "session_existence_unknown");
5479
5721
  break;
5480
5722
  }
5481
- const errorMessage = err instanceof Error ? err.message : String(err);
5723
+ const errorMessage2 = err instanceof Error ? err.message : String(err);
5482
5724
  this.sessions.delete(conv.id);
5483
5725
  this.supersede(conv.id, sessionId);
5484
5726
  this.log({
@@ -5487,7 +5729,7 @@ var ChannelDriver = class _ChannelDriver {
5487
5729
  conversation_id: conv.id,
5488
5730
  message_id: message.id
5489
5731
  });
5490
- await this.markFailed(conv.id, message.id, null, errorMessage).catch((markErr) => {
5732
+ await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
5491
5733
  this.log({
5492
5734
  level: "warn",
5493
5735
  message: `markFailed PATCH for message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
@@ -5498,7 +5740,7 @@ var ChannelDriver = class _ChannelDriver {
5498
5740
  });
5499
5741
  this.log({
5500
5742
  level: "error",
5501
- message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage}`,
5743
+ message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage2}`,
5502
5744
  conversation_id: conv.id,
5503
5745
  message_id: message.id
5504
5746
  });
@@ -5519,14 +5761,14 @@ var ChannelDriver = class _ChannelDriver {
5519
5761
  this.unconfirmedDispatchFailures.delete(message.id);
5520
5762
  this.sessions.delete(conv.id);
5521
5763
  this.supersede(conv.id, sessionId);
5522
- const errorMessage = `OpenCode accepted this message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
5764
+ const errorMessage2 = `OpenCode accepted this message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
5523
5765
  this.log({
5524
5766
  level: "error",
5525
- message: errorMessage,
5767
+ message: errorMessage2,
5526
5768
  conversation_id: conv.id,
5527
5769
  message_id: message.id
5528
5770
  });
5529
- await this.markFailed(conv.id, message.id, null, errorMessage).catch((markErr) => {
5771
+ await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
5530
5772
  this.log({
5531
5773
  level: "warn",
5532
5774
  message: `markFailed PATCH for message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
@@ -7490,14 +7732,14 @@ var ChannelDriver = class _ChannelDriver {
7490
7732
  this.unconfirmedDispatchFailures.delete(row.id);
7491
7733
  this.sessions.delete(readoptConv.id);
7492
7734
  this.supersede(readoptConv.id, sessionId);
7493
- const errorMessage = `OpenCode accepted this re-dispatched message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
7735
+ const errorMessage2 = `OpenCode accepted this re-dispatched message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
7494
7736
  this.log({
7495
7737
  level: "error",
7496
- message: errorMessage,
7738
+ message: errorMessage2,
7497
7739
  conversation_id: row.conversation_id,
7498
7740
  message_id: row.id
7499
7741
  });
7500
- await this.markFailed(row.conversation_id, row.id, null, errorMessage).catch((markErr) => {
7742
+ await this.markFailed(row.conversation_id, row.id, null, errorMessage2).catch((markErr) => {
7501
7743
  this.log({
7502
7744
  level: "warn",
7503
7745
  message: `markFailed PATCH for message ${row.id.slice(0, 8)} (conversation ${row.conversation_id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
@@ -8112,6 +8354,7 @@ var ChannelDriver = class _ChannelDriver {
8112
8354
  throw new Error(`Failed to get pending conversations: HTTP ${res.status}`);
8113
8355
  }
8114
8356
  const data = await res.json();
8357
+ this.recycleRequestedFlag = data.recycle_requested === true;
8115
8358
  let conversations = data.conversations;
8116
8359
  if (this.conversationFilter) {
8117
8360
  conversations = conversations.filter((c) => c.id === this.conversationFilter);
@@ -8629,7 +8872,7 @@ Port ${port} is already in use.`));
8629
8872
  }
8630
8873
 
8631
8874
  // src/lib/runner-credentials.ts
8632
- import { chmodSync as chmodSync2, writeFileSync as writeFileSync3 } from "fs";
8875
+ import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "fs";
8633
8876
  import { spawn as spawn5 } from "child_process";
8634
8877
  var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
8635
8878
  var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
@@ -8870,8 +9113,8 @@ async function configureGitHubAccess({ env, log: log3 }) {
8870
9113
  }
8871
9114
  try {
8872
9115
  env.GIT_CONFIG_GLOBAL = GIT_CONFIG_GLOBAL;
8873
- writeFileSync3(GIT_CONFIG_GLOBAL, "");
8874
- writeFileSync3(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
9116
+ writeFileSync4(GIT_CONFIG_GLOBAL, "");
9117
+ writeFileSync4(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
8875
9118
  chmodSync2(GIT_CREDENTIAL_HELPER, 448);
8876
9119
  const config = [
8877
9120
  ["user.name", env.GIT_USER_NAME ?? "evident-bot"],
@@ -8904,7 +9147,7 @@ async function configureGitHubAccess({ env, log: log3 }) {
8904
9147
  // src/lib/opencode/config-overlay.ts
8905
9148
  import { execFileSync as execFileSync2 } from "child_process";
8906
9149
  import { copyFileSync, existsSync as existsSync2, statSync as statSync5 } from "fs";
8907
- import { isAbsolute as isAbsolute2, join as join7, resolve as resolve3 } from "path";
9150
+ import { isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "path";
8908
9151
  function isFile(filePath) {
8909
9152
  return existsSync2(filePath) && statSync5(filePath).isFile();
8910
9153
  }
@@ -8918,7 +9161,7 @@ function applyRunnerOpenCodeConfig({
8918
9161
  return;
8919
9162
  }
8920
9163
  const source = isAbsolute2(overlayPath) ? overlayPath : resolve3(cwd, overlayPath);
8921
- const target = isFile(join7(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
9164
+ const target = isFile(join8(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
8922
9165
  if (!isFile(source)) {
8923
9166
  log3(
8924
9167
  `RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
@@ -8926,7 +9169,7 @@ function applyRunnerOpenCodeConfig({
8926
9169
  );
8927
9170
  return;
8928
9171
  }
8929
- copyFileSync(source, join7(cwd, target));
9172
+ copyFileSync(source, join8(cwd, target));
8930
9173
  try {
8931
9174
  execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
8932
9175
  stdio: "ignore"
@@ -8935,7 +9178,242 @@ function applyRunnerOpenCodeConfig({
8935
9178
  const detail = error2 instanceof Error ? error2.message : String(error2);
8936
9179
  log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
8937
9180
  }
8938
- log3(`Applied runner OpenCode config ${source} to ${join7(cwd, target)}`);
9181
+ log3(`Applied runner OpenCode config ${source} to ${join8(cwd, target)}`);
9182
+ }
9183
+
9184
+ // src/lib/credential-sync.ts
9185
+ import { renameSync, writeFileSync as writeFileSync5 } from "fs";
9186
+ var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
9187
+ var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
9188
+ var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
9189
+ var DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS = 60;
9190
+ var STORES = ["claude", "opencode"];
9191
+ var MAX_FLUSH_PASSES = 2;
9192
+ function outcomesWith(outcome) {
9193
+ return { claude: outcome, opencode: outcome };
9194
+ }
9195
+ function errorMessage(error2) {
9196
+ return error2 instanceof Error ? error2.message : String(error2);
9197
+ }
9198
+ function waitForSettlement(promise, timeoutMs) {
9199
+ return new Promise((resolve4) => {
9200
+ let settled = false;
9201
+ const timer = setTimeout(() => finish(false), Math.max(0, timeoutMs));
9202
+ const finish = (value) => {
9203
+ if (settled) return;
9204
+ settled = true;
9205
+ clearTimeout(timer);
9206
+ resolve4(value);
9207
+ };
9208
+ promise.then(
9209
+ () => finish(true),
9210
+ () => finish(true)
9211
+ );
9212
+ });
9213
+ }
9214
+ function writeMarker(markerPath, outcomes, log3) {
9215
+ const body = `${STORES.map((store) => `${store}=${outcomes[store]}`).join("\n")}
9216
+ `;
9217
+ const temporaryPath = `${markerPath}.tmp`;
9218
+ try {
9219
+ writeFileSync5(temporaryPath, body, { mode: 384 });
9220
+ renameSync(temporaryPath, markerPath);
9221
+ } catch (error2) {
9222
+ log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage(error2)}`, "warn");
9223
+ }
9224
+ }
9225
+ function intervalSeconds(env, log3) {
9226
+ const raw = env.CREDS_SYNC_INTERVAL;
9227
+ if (raw === void 0 || /^[1-9][0-9]*$/.test(raw) && Number.isSafeInteger(Number(raw))) {
9228
+ return raw === void 0 ? DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS : Number(raw);
9229
+ }
9230
+ log3(
9231
+ `CREDENTIAL-SYNC-INTERVAL-INVALID: CREDS_SYNC_INTERVAL='${raw}' is not a positive integer; using ${DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS}s`,
9232
+ "warn"
9233
+ );
9234
+ return DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS;
9235
+ }
9236
+ async function runFlushStore(store, env, synchroniserRunner, log3, deadlineAt) {
9237
+ const remainingMs = deadlineAt - Date.now();
9238
+ if (remainingMs <= 0) return { outcome: "timeout", orphaned: false };
9239
+ const controller = new AbortController();
9240
+ let result;
9241
+ let failed = false;
9242
+ const completion = Promise.resolve().then(
9243
+ () => synchroniserRunner(["sync-once", store], {
9244
+ timeoutMs: remainingMs,
9245
+ env,
9246
+ signal: controller.signal
9247
+ })
9248
+ ).then(
9249
+ (value) => {
9250
+ result = value;
9251
+ },
9252
+ (error2) => {
9253
+ failed = true;
9254
+ log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage(error2)}`, "warn");
9255
+ }
9256
+ );
9257
+ const abortTimer = setTimeout(() => controller.abort(), remainingMs);
9258
+ const settledBeforeDeadline = await waitForSettlement(completion, remainingMs);
9259
+ clearTimeout(abortTimer);
9260
+ if (!settledBeforeDeadline) {
9261
+ controller.abort();
9262
+ const settledAfterAbort = await waitForSettlement(completion, CREDENTIAL_FLUSH_ABORT_GRACE_MS);
9263
+ if (!settledAfterAbort) return { outcome: "timeout", orphaned: true };
9264
+ return { outcome: "timeout", orphaned: false };
9265
+ }
9266
+ if (failed || !result) return { outcome: "failed", orphaned: false };
9267
+ if (result.timedOut || Date.now() >= deadlineAt) {
9268
+ return { outcome: "timeout", orphaned: false };
9269
+ }
9270
+ return { outcome: result.code === 0 ? "ok" : "failed", orphaned: false };
9271
+ }
9272
+ function createCredentialSync({
9273
+ markerPath,
9274
+ env,
9275
+ log: log3,
9276
+ synchroniserRunner = runSynchroniser
9277
+ }) {
9278
+ const persistenceDisabled = !env.PERSISTENCE_BUCKET;
9279
+ let disabled = persistenceDisabled;
9280
+ let armed = false;
9281
+ let stopped = false;
9282
+ let timer;
9283
+ let inFlight;
9284
+ let activeTickAbort;
9285
+ let lastTickFailed;
9286
+ let flushPromise;
9287
+ const scheduleTick = (intervalMs, startTick2) => {
9288
+ if (stopped) return;
9289
+ timer = setTimeout(() => {
9290
+ timer = void 0;
9291
+ startTick2();
9292
+ }, intervalMs);
9293
+ };
9294
+ const startTick = (intervalMs) => {
9295
+ if (stopped) return;
9296
+ const controller = new AbortController();
9297
+ activeTickAbort = controller;
9298
+ const tick = (async () => {
9299
+ const outcomes = {
9300
+ claude: "failed",
9301
+ opencode: "failed"
9302
+ };
9303
+ for (const store of STORES) {
9304
+ if (controller.signal.aborted) break;
9305
+ try {
9306
+ const result = await synchroniserRunner(["sync-once", store], {
9307
+ timeoutMs: CREDENTIAL_SYNC_TICK_TIMEOUT_MS,
9308
+ env,
9309
+ signal: controller.signal
9310
+ });
9311
+ outcomes[store] = !result.timedOut && result.code === 0 ? "ok" : "failed";
9312
+ } catch (error2) {
9313
+ outcomes[store] = "failed";
9314
+ log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage(error2)}`, "debug");
9315
+ }
9316
+ }
9317
+ const failed = STORES.some((store) => outcomes[store] === "failed");
9318
+ log3(
9319
+ `CREDENTIAL-SYNC-TICK: claude=${outcomes.claude}, opencode=${outcomes.opencode}`,
9320
+ "debug"
9321
+ );
9322
+ if (failed && lastTickFailed !== true) {
9323
+ log3(
9324
+ "CREDENTIAL-SYNC-FAILED: an interval credential sync failed; retrying on the next tick",
9325
+ "warn"
9326
+ );
9327
+ } else if (!failed && lastTickFailed === true) {
9328
+ log3("CREDENTIAL-SYNC-RECOVERED: interval credential sync succeeded again", "warn");
9329
+ }
9330
+ lastTickFailed = failed;
9331
+ })().finally(() => {
9332
+ if (activeTickAbort === controller) activeTickAbort = void 0;
9333
+ if (inFlight === tick) inFlight = void 0;
9334
+ scheduleTick(intervalMs, () => startTick(intervalMs));
9335
+ });
9336
+ inFlight = tick;
9337
+ };
9338
+ const performFlush = async () => {
9339
+ stopped = true;
9340
+ if (timer) {
9341
+ clearTimeout(timer);
9342
+ timer = void 0;
9343
+ }
9344
+ const deadlineAt = Date.now() + CREDENTIAL_FLUSH_DEADLINE_MS;
9345
+ if (inFlight) {
9346
+ const settled = await waitForSettlement(inFlight, CREDENTIAL_FLUSH_DEADLINE_MS);
9347
+ if (!settled) {
9348
+ activeTickAbort?.abort();
9349
+ const settledAfterAbort = await waitForSettlement(
9350
+ inFlight,
9351
+ CREDENTIAL_FLUSH_ABORT_GRACE_MS
9352
+ );
9353
+ if (!settledAfterAbort) {
9354
+ log3(
9355
+ "CREDENTIAL-FLUSH-ORPHANED-TICK: a sync-once child could not be proven settled; skipping the boundary flush rather than becoming a second writer",
9356
+ "warn"
9357
+ );
9358
+ return { outcomes: outcomesWith("timeout"), orphaned: true };
9359
+ }
9360
+ }
9361
+ }
9362
+ if (disabled) return { outcomes: outcomesWith("skipped"), orphaned: false };
9363
+ const outcomes = outcomesWith("timeout");
9364
+ for (const store of STORES) {
9365
+ const result = await runFlushStore(store, env, synchroniserRunner, log3, deadlineAt);
9366
+ if (result.orphaned) {
9367
+ log3(
9368
+ "CREDENTIAL-FLUSH-ORPHANED-CHILD: a flush sync-once child could not be proven settled; refusing another flush pass rather than becoming a second writer",
9369
+ "warn"
9370
+ );
9371
+ return { outcomes: outcomesWith("timeout"), orphaned: true };
9372
+ }
9373
+ outcomes[store] = result.outcome;
9374
+ }
9375
+ return { outcomes, orphaned: false };
9376
+ };
9377
+ let flushPasses = 0;
9378
+ let lastFlush;
9379
+ return {
9380
+ arm() {
9381
+ if (stopped || armed) return;
9382
+ armed = true;
9383
+ if (persistenceDisabled) {
9384
+ disabled = true;
9385
+ log3(
9386
+ "CREDENTIAL-SYNC-DISABLED: LITESTREAM_BUCKET/LITESTREAM_PREFIX are not both set; no interval credential sync this boot",
9387
+ "warn"
9388
+ );
9389
+ return;
9390
+ }
9391
+ disabled = false;
9392
+ const intervalMs = intervalSeconds(env, log3) * 1e3;
9393
+ scheduleTick(intervalMs, () => startTick(intervalMs));
9394
+ },
9395
+ async stopAndFlush(publish) {
9396
+ let result;
9397
+ const runningFlush = flushPromise;
9398
+ if (runningFlush) {
9399
+ result = await runningFlush;
9400
+ } else if (flushPasses >= MAX_FLUSH_PASSES || lastFlush?.orphaned) {
9401
+ result = lastFlush ?? { outcomes: outcomesWith("timeout"), orphaned: true };
9402
+ } else {
9403
+ flushPasses++;
9404
+ const currentFlush = performFlush();
9405
+ flushPromise = currentFlush;
9406
+ try {
9407
+ result = await currentFlush;
9408
+ lastFlush = result;
9409
+ } finally {
9410
+ if (flushPromise === currentFlush) flushPromise = void 0;
9411
+ }
9412
+ }
9413
+ if (publish) writeMarker(markerPath, result.outcomes, log3);
9414
+ return result.outcomes;
9415
+ }
9416
+ };
8939
9417
  }
8940
9418
 
8941
9419
  // src/commands/run.ts
@@ -8975,7 +9453,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
8975
9453
  if (trimmed === "") {
8976
9454
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
8977
9455
  }
8978
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join8(homeDir, trimmed.slice(2)) : trimmed;
9456
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join9(homeDir, trimmed.slice(2)) : trimmed;
8979
9457
  if (!isAbsolute3(expanded)) {
8980
9458
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
8981
9459
  }
@@ -9235,6 +9713,10 @@ async function driveChannels(state, driver) {
9235
9713
  consecutiveDrainFailures = 0;
9236
9714
  unreachableMs = 0;
9237
9715
  state.messageCount += processed;
9716
+ if (driver.recycleRequested) {
9717
+ await beginGracefulShutdown(state, "recycle");
9718
+ return;
9719
+ }
9238
9720
  const proxiedActivity = state.lastProxiedActivityAt !== lastSeenProxiedActivityAt;
9239
9721
  lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
9240
9722
  const fileActivitySnapshot = driver.fileSyncActivity();
@@ -9277,8 +9759,8 @@ async function driveChannels(state, driver) {
9277
9759
  state.running = false;
9278
9760
  break;
9279
9761
  }
9280
- const errorMessage = error2 instanceof Error ? error2.message : String(error2);
9281
- logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage}` });
9762
+ const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
9763
+ logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage2}` });
9282
9764
  if (state.interactive) displayStatus(state);
9283
9765
  if (driver.hasInFlightWatchers()) {
9284
9766
  consecutiveDrainFailures = 0;
@@ -9318,7 +9800,43 @@ async function driveChannels(state, driver) {
9318
9800
  var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
9319
9801
  var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
9320
9802
  function sessionDbPath() {
9321
- return join8(homedir5(), ".local", "share", "opencode", "opencode.db");
9803
+ return join9(homedir5(), ".local", "share", "opencode", "opencode.db");
9804
+ }
9805
+ function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
9806
+ const record = {
9807
+ v: 1,
9808
+ event: "session_db_recovery",
9809
+ at: (/* @__PURE__ */ new Date()).toISOString(),
9810
+ stage: "verify",
9811
+ outcome: "schema_provenance_mismatch",
9812
+ severity: "error",
9813
+ reason: provenance.reason ?? "schema-provenance-mismatch",
9814
+ litestream_exit_code: null,
9815
+ attempt: null,
9816
+ replica_objects: null,
9817
+ replica_bytes: null,
9818
+ quarantine_destination: null,
9819
+ quarantined_objects: null,
9820
+ quarantine_failed_objects: null,
9821
+ quarantined_bytes: null,
9822
+ verified_restore_point: null,
9823
+ restore_points_tried: null,
9824
+ provenance_reason: provenance.reason,
9825
+ provenance_migration_delta: provenance.migrationDelta,
9826
+ replication_suspended: false,
9827
+ dbPath: sessionDbPath(),
9828
+ recorded_version: provenance.recordedVersion,
9829
+ current_version: currentVersion,
9830
+ provenance_pre_boot_migration_count: preBootMigrationCount
9831
+ };
9832
+ const activity = buildSessionDbRecoveryActivity(record);
9833
+ if (!activity) throw new Error("could not map session-DB provenance activity");
9834
+ logActivity(state, {
9835
+ type: activity.level === "error" ? "error" : "info",
9836
+ level: activity.level,
9837
+ ...activity.level === "error" ? { error: activity.message } : { message: activity.message },
9838
+ metadata: activity.metadata
9839
+ });
9322
9840
  }
9323
9841
  async function runSweep(state, driver, config) {
9324
9842
  const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
@@ -9365,7 +9883,7 @@ async function runSweep(state, driver, config) {
9365
9883
  const reclaimResult = await reclaimSessionDbSpace({
9366
9884
  dbPath: sessionDbPath(),
9367
9885
  maxPages: SESSION_DB_RECLAIM_MAX_PAGES,
9368
- allowFullVacuum: protectedNow.size === 0
9886
+ allowFullVacuum: protectedNow.size === 0 && !state.sessionDbProvenanceAnomaly
9369
9887
  });
9370
9888
  if (reclaimResult.ok) {
9371
9889
  const beforeMib = (reclaimResult.beforeBytes / 1024 / 1024).toFixed(1);
@@ -9602,7 +10120,8 @@ function scheduleResourceUsageReporting(state, options) {
9602
10120
  });
9603
10121
  return;
9604
10122
  }
9605
- const collect = createResourceUsageCollector(homedir5());
10123
+ const { collect, stop } = createResourceUsageCollector(homedir5());
10124
+ state.stopResourceUsageSampling = stop;
9606
10125
  let consecutiveFailures = 0;
9607
10126
  const tick = async () => {
9608
10127
  try {
@@ -9712,21 +10231,41 @@ async function cleanup(state, opts = {}) {
9712
10231
  clearTimeout(state.resourceUsageTimer);
9713
10232
  state.resourceUsageTimer = null;
9714
10233
  }
10234
+ state.stopResourceUsageSampling?.();
10235
+ state.stopResourceUsageSampling = null;
10236
+ const credentialSync = state.credentialSync;
10237
+ const flushCredentials = credentialSync ? async (phase, publish) => {
10238
+ await timeShutdownPhase(state, durations, phase, async () => {
10239
+ const outcomes = await credentialSync.stopAndFlush(publish);
10240
+ const level = Object.values(outcomes).every((outcome) => outcome === "ok") ? "info" : "warn";
10241
+ log2(
10242
+ state,
10243
+ `Credential flush: claude=${outcomes.claude}, opencode=${outcomes.opencode}`,
10244
+ level
10245
+ );
10246
+ });
10247
+ } : void 0;
10248
+ let drainSettled = true;
9715
10249
  if (opts.graceful && state.channelDriver) {
9716
10250
  state.channelDriver.stop();
10251
+ }
10252
+ if (flushCredentials) {
10253
+ await flushCredentials("credential_flush", !(opts.graceful && state.channelDriver));
10254
+ }
10255
+ if (opts.graceful && state.channelDriver) {
9717
10256
  log2(state, "Draining in-flight channel work before shutdown...");
9718
10257
  if (state.interactive) {
9719
10258
  logActivity(state, { type: "info", message: "Draining in-flight work before shutdown..." });
9720
10259
  displayStatus(state);
9721
10260
  }
9722
10261
  const driver = state.channelDriver;
9723
- const settled = await timeShutdownPhase(
10262
+ drainSettled = await timeShutdownPhase(
9724
10263
  state,
9725
10264
  durations,
9726
10265
  "drain",
9727
10266
  () => driver.waitForInFlight(SHUTDOWN_DRAIN_TIMEOUT_MS)
9728
10267
  );
9729
- if (!settled) {
10268
+ if (!drainSettled) {
9730
10269
  logActivity(state, {
9731
10270
  type: "info",
9732
10271
  message: "Shutdown drain timed out with work still in flight \u2014 leaving it for restart recovery"
@@ -9734,6 +10273,9 @@ async function cleanup(state, opts = {}) {
9734
10273
  if (state.interactive) displayStatus(state);
9735
10274
  }
9736
10275
  }
10276
+ if (opts.graceful && state.channelDriver && flushCredentials && drainSettled) {
10277
+ await flushCredentials("credential_flush_final", true);
10278
+ }
9737
10279
  await timeShutdownPhase(state, durations, "offline_notify", () => notifyOffline(state));
9738
10280
  if (state.connection) {
9739
10281
  const connection = state.connection;
@@ -9769,6 +10311,44 @@ async function cleanup(state, opts = {}) {
9769
10311
  }
9770
10312
  return durations;
9771
10313
  }
10314
+ async function beginGracefulShutdown(state, trigger) {
10315
+ if (state.shuttingDown) return;
10316
+ state.shuttingDown = true;
10317
+ const shutdownStartedAt = Date.now();
10318
+ const shutdownMessage = trigger === "recycle" ? "Recycle requested \u2014 finishing in-flight work, then exiting" : "Shutting down...";
10319
+ if (state.interactive) {
10320
+ logActivity(state, { type: "info", message: shutdownMessage });
10321
+ displayStatus(state);
10322
+ } else {
10323
+ log2(state, shutdownMessage);
10324
+ }
10325
+ const durations = await cleanup(state, { graceful: true });
10326
+ const telemetryBudgetMs = Number(process.env.EVIDENT_TELEMETRY_SHUTDOWN_MS) || TELEMETRY_SHUTDOWN_TIMEOUT_MS;
10327
+ await timeShutdownPhase(state, durations, "telemetry_flush", async () => {
10328
+ let timer;
10329
+ const flushed = shutdownTelemetry().then(
10330
+ () => true,
10331
+ (error2) => {
10332
+ log2(
10333
+ state,
10334
+ `Telemetry flush failed during shutdown: ${error2 instanceof Error ? error2.message : String(error2)}`,
10335
+ "warn"
10336
+ );
10337
+ return true;
10338
+ }
10339
+ );
10340
+ const timedOut = new Promise((resolve4) => {
10341
+ timer = setTimeout(() => resolve4(false), telemetryBudgetMs);
10342
+ });
10343
+ if (!await Promise.race([flushed, timedOut])) {
10344
+ log2(state, `Telemetry flush exceeded ${telemetryBudgetMs}ms \u2014 exiting anyway`, "warn");
10345
+ }
10346
+ clearTimeout(timer);
10347
+ });
10348
+ const breakdown = Object.entries(durations).map(([phase, ms]) => `${phase}=${ms}ms`).join(" ");
10349
+ log2(state, `Shutdown complete in ${Date.now() - shutdownStartedAt}ms (${breakdown})`);
10350
+ process.exit(0);
10351
+ }
9772
10352
  async function run(options) {
9773
10353
  const interactive = isInteractive(options.json);
9774
10354
  let logLevel;
@@ -9804,6 +10384,7 @@ async function run(options) {
9804
10384
  connected: false,
9805
10385
  opencodeConnected: false,
9806
10386
  opencodeVersion: null,
10387
+ sessionDbProvenanceAnomaly: false,
9807
10388
  opencodeProcess: null,
9808
10389
  litestreamProcess: null,
9809
10390
  connection: null,
@@ -9819,9 +10400,24 @@ async function run(options) {
9819
10400
  openaiUsageTimer: null,
9820
10401
  openaiUsageRearm: null,
9821
10402
  resourceUsageTimer: null,
10403
+ stopResourceUsageSampling: null,
10404
+ credentialSync: null,
9822
10405
  authHeader: ""
9823
10406
  };
9824
10407
  setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
10408
+ if (options.credentialSyncMarker) {
10409
+ state.credentialSync = createCredentialSync({
10410
+ markerPath: options.credentialSyncMarker,
10411
+ env: process.env,
10412
+ log: (message, level = "info") => {
10413
+ if (level === "error") {
10414
+ logActivity(state, { type: "error", error: message });
10415
+ } else {
10416
+ logActivity(state, { type: "info", level, message });
10417
+ }
10418
+ }
10419
+ });
10420
+ }
9825
10421
  if (fileSyncDirectories.length > 0) {
9826
10422
  log2(state, `File sync enabled for: ${fileSyncDirectories.join(", ")}`);
9827
10423
  } else {
@@ -9847,43 +10443,7 @@ async function run(options) {
9847
10443
  "warn"
9848
10444
  );
9849
10445
  }
9850
- const handleSignal = async () => {
9851
- if (state.shuttingDown) return;
9852
- state.shuttingDown = true;
9853
- const shutdownStartedAt = Date.now();
9854
- if (state.interactive) {
9855
- logActivity(state, { type: "info", message: "Shutting down..." });
9856
- displayStatus(state);
9857
- } else {
9858
- log2(state, "Shutting down...");
9859
- }
9860
- const durations = await cleanup(state, { graceful: true });
9861
- const telemetryBudgetMs = Number(process.env.EVIDENT_TELEMETRY_SHUTDOWN_MS) || TELEMETRY_SHUTDOWN_TIMEOUT_MS;
9862
- await timeShutdownPhase(state, durations, "telemetry_flush", async () => {
9863
- let timer;
9864
- const flushed = shutdownTelemetry().then(
9865
- () => true,
9866
- (error2) => {
9867
- log2(
9868
- state,
9869
- `Telemetry flush failed during shutdown: ${error2 instanceof Error ? error2.message : String(error2)}`,
9870
- "warn"
9871
- );
9872
- return true;
9873
- }
9874
- );
9875
- const timedOut = new Promise((resolve4) => {
9876
- timer = setTimeout(() => resolve4(false), telemetryBudgetMs);
9877
- });
9878
- if (!await Promise.race([flushed, timedOut])) {
9879
- log2(state, `Telemetry flush exceeded ${telemetryBudgetMs}ms \u2014 exiting anyway`, "warn");
9880
- }
9881
- clearTimeout(timer);
9882
- });
9883
- const breakdown = Object.entries(durations).map(([phase, ms]) => `${phase}=${ms}ms`).join(" ");
9884
- log2(state, `Shutdown complete in ${Date.now() - shutdownStartedAt}ms (${breakdown})`);
9885
- process.exit(0);
9886
- };
10446
+ const handleSignal = () => beginGracefulShutdown(state, "signal");
9887
10447
  process.on("SIGINT", handleSignal);
9888
10448
  process.on("SIGTERM", handleSignal);
9889
10449
  try {
@@ -10029,6 +10589,7 @@ async function run(options) {
10029
10589
  await restoreCredentialStores(credentialContext);
10030
10590
  if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
10031
10591
  }
10592
+ state.credentialSync?.arm();
10032
10593
  let sessionDbVerifyFatal = false;
10033
10594
  if (!options.restoreSessionDb) {
10034
10595
  log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
@@ -10082,6 +10643,7 @@ async function run(options) {
10082
10643
  for (const warning2 of maxActiveSessionsWarnings) {
10083
10644
  logActivity(state, { type: "info", level: "warn", message: warning2 });
10084
10645
  }
10646
+ const preBootMigrationIds = readSessionDbMigrationIds(sessionDbPath());
10085
10647
  const ocSpinner = interactive && !state.json ? ora3("Checking OpenCode...").start() : null;
10086
10648
  try {
10087
10649
  const oc = await ensureOpenCodeRunning({
@@ -10097,7 +10659,7 @@ async function run(options) {
10097
10659
  state.opencodeVersion = oc.version;
10098
10660
  if (options.opencodePidFile && oc.process?.pid !== void 0) {
10099
10661
  try {
10100
- writeFileSync4(options.opencodePidFile, `${oc.process.pid}
10662
+ writeFileSync6(options.opencodePidFile, `${oc.process.pid}
10101
10663
  `, { mode: 384 });
10102
10664
  chmodSync3(options.opencodePidFile, 384);
10103
10665
  } catch (error2) {
@@ -10107,6 +10669,23 @@ async function run(options) {
10107
10669
  });
10108
10670
  }
10109
10671
  }
10672
+ if (state.opencodeVersion !== null) {
10673
+ const provenance = checkSessionDbProvenance({
10674
+ dbPath: sessionDbPath(),
10675
+ currentVersion: state.opencodeVersion,
10676
+ homeDir: homedir5(),
10677
+ env: process.env
10678
+ });
10679
+ if (provenance.anomaly) {
10680
+ state.sessionDbProvenanceAnomaly = true;
10681
+ logSessionDbProvenanceMismatch(
10682
+ state,
10683
+ provenance,
10684
+ state.opencodeVersion,
10685
+ preBootMigrationIds?.length ?? null
10686
+ );
10687
+ }
10688
+ }
10110
10689
  state.opencodeConnected = oc.notReadyReason === null;
10111
10690
  const version2 = state.opencodeVersion ? ` (v${state.opencodeVersion})` : "";
10112
10691
  ocSpinner?.succeed(`OpenCode running on port ${state.port}${version2}`);
@@ -10159,7 +10738,7 @@ async function run(options) {
10159
10738
  let existingPid;
10160
10739
  if (existsSync3(options.litestreamPidFile)) {
10161
10740
  try {
10162
- const rawPid = readFileSync5(options.litestreamPidFile, "utf8").trim();
10741
+ const rawPid = readFileSync6(options.litestreamPidFile, "utf8").trim();
10163
10742
  const parsedPid = Number(rawPid);
10164
10743
  if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
10165
10744
  existingPid = parsedPid;
@@ -10196,7 +10775,7 @@ async function run(options) {
10196
10775
  });
10197
10776
  try {
10198
10777
  if (litestreamProcess.pid !== void 0) {
10199
- writeFileSync4(options.litestreamPidFile, `${litestreamProcess.pid}
10778
+ writeFileSync6(options.litestreamPidFile, `${litestreamProcess.pid}
10200
10779
  `, {
10201
10780
  mode: 384
10202
10781
  });
@@ -10402,7 +10981,17 @@ async function run(options) {
10402
10981
  setTimer: (timer) => {
10403
10982
  state.openaiUsageTimer = timer;
10404
10983
  },
10405
- fetchUsage: () => getOpenAiUsage(state.port),
10984
+ fetchUsage: async () => {
10985
+ const usage = await getOpenAiUsage(state.port);
10986
+ if (usage.subscription === null) {
10987
+ logActivity(state, {
10988
+ type: "info",
10989
+ level: "debug",
10990
+ message: "OpenAI usage subscription could not be identified from the local credential"
10991
+ });
10992
+ }
10993
+ return usage;
10994
+ },
10406
10995
  report: (usage) => reportOpenAiUsage(state.agentId, state.authHeader, usage),
10407
10996
  isLocalCredentialProblem: isLocalCredentialProblem2,
10408
10997
  forcedOnHint: "connect a ChatGPT account to this runner, or run `opencode auth login`",
@@ -10448,7 +11037,7 @@ async function run(options) {
10448
11037
  }
10449
11038
 
10450
11039
  // src/index.ts
10451
- var { version } = createRequire(import.meta.url)("../package.json");
11040
+ var { version } = createRequire2(import.meta.url)("../package.json");
10452
11041
  var program = new Command();
10453
11042
  program.name("evident").description("Run OpenCode locally and connect it to Evident").version(version).option(
10454
11043
  "--endpoint <url>",
@@ -10526,6 +11115,9 @@ program.command("run").description("Connect to Evident and process messages").op
10526
11115
  ).option(
10527
11116
  "--opencode-config-overlay <path>",
10528
11117
  "Apply this runner-provided OpenCode config before starting OpenCode."
11118
+ ).option(
11119
+ "--credential-sync-marker <path>",
11120
+ "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."
10529
11121
  ).action(
10530
11122
  (options) => {
10531
11123
  run({
@@ -10564,7 +11156,8 @@ program.command("run").description("Connect to Evident and process messages").op
10564
11156
  sessionDbNoReplicateMarker: options.sessionDbNoReplicateMarker,
10565
11157
  restoreSessionDb: options.restoreSessionDb,
10566
11158
  restoreRunnerCredentials: options.restoreRunnerCredentials,
10567
- opencodeConfigOverlay: options.opencodeConfigOverlay
11159
+ opencodeConfigOverlay: options.opencodeConfigOverlay,
11160
+ credentialSyncMarker: options.credentialSyncMarker
10568
11161
  });
10569
11162
  }
10570
11163
  );