@evident-ai/cli 3.4.1-dev.5d99ded → 3.4.1-dev.5e8a101

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
@@ -11,8 +11,8 @@ import chalk2 from "chalk";
11
11
 
12
12
  // src/lib/config.ts
13
13
  import Conf from "conf";
14
- import { chmodSync, existsSync, statSync } from "fs";
15
- import { dirname } from "path";
14
+ import { chmodSync, existsSync, statSync } from "node:fs";
15
+ import { dirname } from "node:path";
16
16
  var PRODUCTION_API_URL = "https://api.production.evident.run/v1";
17
17
  var PRODUCTION_TUNNEL_URL = "wss://tunnel.production.evident.run";
18
18
  var defaults = {
@@ -1009,10 +1009,10 @@ async function status(options = {}) {
1009
1009
  }
1010
1010
 
1011
1011
  // src/lib/claude-usage.ts
1012
- import { execFileSync } from "child_process";
1013
- import { readFileSync } from "fs";
1014
- import { homedir } from "os";
1015
- import { join } from "path";
1012
+ import { execFileSync } from "node:child_process";
1013
+ import { readFileSync } from "node:fs";
1014
+ import { homedir } from "node:os";
1015
+ import { join } from "node:path";
1016
1016
  var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
1017
1017
  var CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
1018
1018
  var CLAUDE_PROFILE_TIMEOUT_MS = 2e3;
@@ -1192,10 +1192,10 @@ async function claudeUsage() {
1192
1192
  }
1193
1193
 
1194
1194
  // src/commands/run.ts
1195
- import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
1196
- import { homedir as homedir5 } from "os";
1197
- import { isAbsolute as isAbsolute3, join as join9, parse, resolve as resolvePath2 } from "path";
1198
- import chalk6 from "chalk";
1195
+ import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
1196
+ import { homedir as homedir6 } from "node:os";
1197
+ import { isAbsolute as isAbsolute3, join as join10, parse, resolve as resolvePath2 } from "node:path";
1198
+ import chalk7 from "chalk";
1199
1199
 
1200
1200
  // ../../packages/types/src/agents/index.ts
1201
1201
  var MICROVM_MAX_LIFETIME_MS = 8 * 60 * 6e4;
@@ -1254,7 +1254,7 @@ function stripQuery(url) {
1254
1254
 
1255
1255
  // src/commands/run.ts
1256
1256
  import ora3 from "ora";
1257
- import { select as select3 } from "@inquirer/prompts";
1257
+ import { select as select4 } from "@inquirer/prompts";
1258
1258
 
1259
1259
  // src/lib/telemetry.ts
1260
1260
  var CLI_VERSION = (true ? "3.0.0" : void 0) ?? process.env.npm_package_version ?? "unknown";
@@ -1427,12 +1427,50 @@ var SEVERITY_BY_LEVEL = {
1427
1427
  warn: "warning",
1428
1428
  error: "error"
1429
1429
  };
1430
+ function parseOpenCodeLogLine(line) {
1431
+ const normalisedLine = line.replace(/\r$/, "");
1432
+ const levelMatch = normalisedLine.match(/(?:^|\s)level=(\w+)/i);
1433
+ if (!levelMatch) return null;
1434
+ const level = levelMatch[1].toUpperCase();
1435
+ if (level !== "WARN" && level !== "ERROR") return null;
1436
+ const sessionMatch = normalisedLine.match(/(?:^|\s)sessionID=(\S+)/);
1437
+ return { level: level === "WARN" ? "warn" : "error", sessionID: sessionMatch?.[1] };
1438
+ }
1439
+ var MAX_LINE_BUFFER_BYTES = 16 * 1024;
1440
+ function createOpenCodeActivityForwarder(getContext) {
1441
+ let buffer = Buffer.alloc(0);
1442
+ const flushLine = (line) => {
1443
+ const parsed = parseOpenCodeLogLine(line);
1444
+ if (!parsed) return;
1445
+ forwardRunnerActivity(
1446
+ {
1447
+ level: parsed.level,
1448
+ error: line,
1449
+ metadata: parsed.sessionID ? { sessionID: parsed.sessionID } : void 0,
1450
+ source: "opencode"
1451
+ },
1452
+ getContext()
1453
+ );
1454
+ };
1455
+ return (chunk) => {
1456
+ buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8")]);
1457
+ let newlineIndex;
1458
+ while ((newlineIndex = buffer.indexOf(10)) !== -1) {
1459
+ flushLine(buffer.subarray(0, newlineIndex).toString("utf-8").replace(/\r$/, ""));
1460
+ buffer = buffer.subarray(newlineIndex + 1);
1461
+ }
1462
+ if (buffer.length > MAX_LINE_BUFFER_BYTES) {
1463
+ flushLine(buffer.toString("utf-8"));
1464
+ buffer = Buffer.alloc(0);
1465
+ }
1466
+ };
1467
+ }
1430
1468
  var MAX_MESSAGE_LENGTH = 500;
1431
1469
  var MAX_METADATA_VALUE_LENGTH = 200;
1432
1470
  var MAX_METADATA_ENTRIES = 20;
1433
1471
  var TRUNCATION_MARKER = "\u2026";
1434
1472
  function redact(message) {
1435
- return message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/https?:\/\/\S+/g, "<url>");
1473
+ return message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/(?<![A-Za-z0-9_-])sk-ant-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-ant-***").replace(/(?<![A-Za-z0-9_-])sk-proj-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-proj-***").replace(/(?<![A-Za-z0-9_-])sk-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "sk-***").replace(/(?<![A-Za-z0-9_-])(gh[oprsu])_[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "$1_***").replace(/(?<![A-Za-z0-9_-])github_pat_[A-Za-z0-9_]{20,}(?![A-Za-z0-9_-])/g, "github_pat_***").replace(/https?:\/\/\S+/g, "<url>");
1436
1474
  }
1437
1475
  function truncate(message) {
1438
1476
  if (message.length <= MAX_MESSAGE_LENGTH) return message;
@@ -1458,43 +1496,47 @@ function sanitiseMetadata(metadata) {
1458
1496
  }
1459
1497
  var RATE_LIMIT_WINDOW_MS = 6e4;
1460
1498
  var RATE_LIMIT_MAX_EVENTS = 30;
1461
- var windowStartedAt = 0;
1462
- var windowCount = 0;
1463
- var windowDroppedCount = 0;
1464
- function admitUnderRateLimit(now) {
1465
- if (now - windowStartedAt >= RATE_LIMIT_WINDOW_MS) {
1466
- if (windowDroppedCount > 0) {
1499
+ var rateWindows = /* @__PURE__ */ new Map();
1500
+ function admitUnderRateLimit(source, now) {
1501
+ let window = rateWindows.get(source);
1502
+ if (!window) {
1503
+ window = { windowStartedAt: 0, windowCount: 0, windowDroppedCount: 0 };
1504
+ rateWindows.set(source, window);
1505
+ }
1506
+ if (now - window.windowStartedAt >= RATE_LIMIT_WINDOW_MS) {
1507
+ if (window.windowDroppedCount > 0) {
1467
1508
  console.error(
1468
- `[runner-activity-telemetry] rate cap reached: dropped ${windowDroppedCount} ${windowDroppedCount === 1 ? "entry" : "entries"} in the last ${RATE_LIMIT_WINDOW_MS / 1e3}s (cap ${RATE_LIMIT_MAX_EVENTS}/min)`
1509
+ `[runner-activity-telemetry] rate cap reached: dropped ${window.windowDroppedCount} ${window.windowDroppedCount === 1 ? "entry" : "entries"} in the last ${RATE_LIMIT_WINDOW_MS / 1e3}s (cap ${RATE_LIMIT_MAX_EVENTS}/min) for source "${source}"`
1469
1510
  );
1470
1511
  }
1471
- windowStartedAt = now;
1472
- windowCount = 0;
1473
- windowDroppedCount = 0;
1512
+ window.windowStartedAt = now;
1513
+ window.windowCount = 0;
1514
+ window.windowDroppedCount = 0;
1474
1515
  }
1475
- if (windowCount >= RATE_LIMIT_MAX_EVENTS) {
1476
- windowDroppedCount++;
1477
- if (windowDroppedCount === 1) {
1516
+ if (window.windowCount >= RATE_LIMIT_MAX_EVENTS) {
1517
+ window.windowDroppedCount++;
1518
+ if (window.windowDroppedCount === 1) {
1478
1519
  console.error(
1479
- `[runner-activity-telemetry] rate cap reached (${RATE_LIMIT_MAX_EVENTS} per ${RATE_LIMIT_WINDOW_MS / 1e3}s) \u2014 dropping further entries this window`
1520
+ `[runner-activity-telemetry] rate cap reached (${RATE_LIMIT_MAX_EVENTS} per ${RATE_LIMIT_WINDOW_MS / 1e3}s) \u2014 dropping further entries this window for source "${source}"`
1480
1521
  );
1481
1522
  }
1482
1523
  return false;
1483
1524
  }
1484
- windowCount++;
1525
+ window.windowCount++;
1485
1526
  return true;
1486
1527
  }
1487
1528
  function forwardRunnerActivity(entry, context) {
1488
1529
  try {
1489
1530
  if (!FORWARDED_LEVELS.has(entry.level)) return;
1490
1531
  if (!context.agentId || !context.authHeader) return;
1491
- if (!admitUnderRateLimit(Date.now())) return;
1532
+ const source = entry.source ?? "cli.run";
1533
+ if (!admitUnderRateLimit(source, Date.now())) return;
1492
1534
  const rawMessage = entry.error ?? entry.message ?? "";
1493
1535
  const message = truncate(redact(rawMessage));
1494
1536
  logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
1495
1537
  severity: SEVERITY_BY_LEVEL[entry.level],
1496
1538
  message,
1497
- metadata: { ...sanitiseMetadata(entry.metadata), source: "cli.run" },
1539
+ metadata: { ...sanitiseMetadata(entry.metadata), source },
1498
1540
  agentId: context.agentId
1499
1541
  });
1500
1542
  } catch (err) {
@@ -1505,8 +1547,8 @@ function forwardRunnerActivity(entry, context) {
1505
1547
  }
1506
1548
 
1507
1549
  // src/lib/opencode/session-db-recovery-report.ts
1508
- import { readFileSync as readFileSync2, unlinkSync } from "fs";
1509
- import { join as join2 } from "path";
1550
+ import { readFileSync as readFileSync2, unlinkSync } from "node:fs";
1551
+ import { join as join2 } from "node:path";
1510
1552
  function sessionDbRecoveryReportPath(homeDir, env) {
1511
1553
  const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
1512
1554
  return override || join2(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
@@ -1719,13 +1761,13 @@ async function waitForOpenCodeHealth(port, timeoutMs = 3e4) {
1719
1761
  }
1720
1762
 
1721
1763
  // src/lib/opencode/session-db-boot.ts
1722
- import { spawn as spawn2 } from "child_process";
1723
- import { mkdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync } from "fs";
1724
- import { homedir as homedir2 } from "os";
1725
- import { dirname as dirname2, resolve as resolvePath } from "path";
1764
+ import { spawn as spawn2 } from "node:child_process";
1765
+ import { mkdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
1766
+ import { homedir as homedir2 } from "node:os";
1767
+ import { dirname as dirname2, resolve as resolvePath } from "node:path";
1726
1768
 
1727
1769
  // src/lib/runner-synchroniser.ts
1728
- import { spawn } from "child_process";
1770
+ import { spawn } from "node:child_process";
1729
1771
  function appendError(stderr, error2) {
1730
1772
  const message = error2 instanceof Error ? error2.message : String(error2);
1731
1773
  return stderr === "" ? message : `${stderr}
@@ -2250,9 +2292,9 @@ async function restoreAndVerifySessionDb(options) {
2250
2292
  }
2251
2293
 
2252
2294
  // src/lib/opencode/session-db-provenance.ts
2253
- import { createRequire } from "module";
2254
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
2255
- import { dirname as dirname3, join as join3 } from "path";
2295
+ import { createRequire } from "node:module";
2296
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
2297
+ import { dirname as dirname3, join as join3 } from "node:path";
2256
2298
  var require2 = createRequire(import.meta.url);
2257
2299
  function readSessionDbMigrationIds(dbPath) {
2258
2300
  let db;
@@ -2446,6 +2488,17 @@ async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
2446
2488
 
2447
2489
  // src/lib/opencode/process.ts
2448
2490
  var OPENCODE_PORT_RANGE = [4096, 4097, 4098, 4099, 4100];
2491
+ var VALID_OPENCODE_LOG_LEVELS = /* @__PURE__ */ new Set(["DEBUG", "INFO", "WARN", "ERROR"]);
2492
+ function resolveOpenCodeLogLevel(env) {
2493
+ const raw = env.OPENCODE_LOG_LEVEL;
2494
+ if (!raw) return "INFO";
2495
+ const upper = raw.toUpperCase();
2496
+ if (VALID_OPENCODE_LOG_LEVELS.has(upper)) return upper;
2497
+ console.warn(
2498
+ `startOpenCode: ignoring invalid OPENCODE_LOG_LEVEL "${raw}" (expected DEBUG|INFO|WARN|ERROR) \u2014 using INFO`
2499
+ );
2500
+ return "INFO";
2501
+ }
2449
2502
  function getProcessCwd(pid) {
2450
2503
  const platform = process.platform;
2451
2504
  try {
@@ -2494,14 +2547,14 @@ function findAvailablePort(startPort, maxAttempts = 10) {
2494
2547
  }
2495
2548
  return null;
2496
2549
  }
2497
- function findOpenCodeProcesses() {
2550
+ function findProcessesByPattern(pgrepPattern, psPattern) {
2498
2551
  const instances = [];
2499
2552
  try {
2500
2553
  const platform = process.platform;
2501
2554
  if (platform === "darwin" || platform === "linux") {
2502
2555
  let pids = [];
2503
2556
  try {
2504
- const pgrepOutput = execSync('pgrep -f "opencode serve|opencode-serve"', {
2557
+ const pgrepOutput = execSync(`pgrep -f "${pgrepPattern}"`, {
2505
2558
  encoding: "utf-8",
2506
2559
  stdio: ["pipe", "pipe", "pipe"]
2507
2560
  }).trim();
@@ -2510,7 +2563,7 @@ function findOpenCodeProcesses() {
2510
2563
  }
2511
2564
  } catch {
2512
2565
  try {
2513
- const psOutput = execSync('ps aux | grep -E "opencode (serve|--port)" | grep -v grep', {
2566
+ const psOutput = execSync(`ps aux | grep -E "${psPattern}" | grep -v grep`, {
2514
2567
  encoding: "utf-8",
2515
2568
  stdio: ["pipe", "pipe", "pipe"]
2516
2569
  }).trim();
@@ -2556,6 +2609,9 @@ function findOpenCodeProcesses() {
2556
2609
  }
2557
2610
  return instances;
2558
2611
  }
2612
+ function findOpenCodeProcesses() {
2613
+ return findProcessesByPattern("opencode serve|opencode-serve", "opencode (serve|--port)");
2614
+ }
2559
2615
  async function scanPortsForOpenCode() {
2560
2616
  const instances = [];
2561
2617
  const checks = OPENCODE_PORT_RANGE.map(async (port) => {
@@ -2602,7 +2658,7 @@ async function findHealthyOpenCodeInstances() {
2602
2658
  }
2603
2659
  async function startOpenCode(port, options = {}) {
2604
2660
  let command = "opencode";
2605
- const printLogs = options.inheritStdio ? ["--print-logs"] : [];
2661
+ const printLogs = options.inheritStdio ? ["--print-logs", "--log-level", resolveOpenCodeLogLevel(process.env)] : [];
2606
2662
  let args = ["serve", "--port", port.toString(), "--hostname", "127.0.0.1", ...printLogs];
2607
2663
  try {
2608
2664
  execSync("which opencode", { stdio: "ignore" });
@@ -2659,6 +2715,19 @@ function isOpenCodeInstalled() {
2659
2715
  return false;
2660
2716
  }
2661
2717
  }
2718
+ function isOpenCode2Installed() {
2719
+ try {
2720
+ const platform = process.platform;
2721
+ if (platform === "win32") {
2722
+ execSync2("where opencode2", { stdio: "ignore" });
2723
+ } else {
2724
+ execSync2("which opencode2", { stdio: "ignore" });
2725
+ }
2726
+ return true;
2727
+ } catch {
2728
+ return false;
2729
+ }
2730
+ }
2662
2731
  async function promptOpenCodeInstall(interactive) {
2663
2732
  if (!interactive) {
2664
2733
  console.log(
@@ -2668,7 +2737,11 @@ async function promptOpenCodeInstall(interactive) {
2668
2737
  install_url: OPENCODE_INSTALL_URL,
2669
2738
  install_commands: {
2670
2739
  npm: "npm install -g opencode-ai",
2671
- curl: "curl -fsSL https://opencode.ai/install.sh | sh"
2740
+ curl: "curl -fsSL https://opencode.ai/install.sh | sh",
2741
+ v2: {
2742
+ npm: "npm install -g @opencode-ai/cli@beta",
2743
+ curl: "curl -fsSL https://opencode.ai/v2/install | bash"
2744
+ }
2672
2745
  }
2673
2746
  })
2674
2747
  );
@@ -3522,8 +3595,8 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
3522
3595
  }
3523
3596
 
3524
3597
  // src/lib/opencode/session-db-size.ts
3525
- import { statSync as statSync3 } from "fs";
3526
- import { join as join4 } from "path";
3598
+ import { statSync as statSync3 } from "node:fs";
3599
+ import { join as join4 } from "node:path";
3527
3600
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
3528
3601
  function statSessionDbBytes(homeDir) {
3529
3602
  const dbPath = join4(homeDir, ".local", "share", "opencode", "opencode.db");
@@ -3553,9 +3626,96 @@ function buildSessionStoreSizeWarning(input) {
3553
3626
  return null;
3554
3627
  }
3555
3628
 
3629
+ // src/lib/opencode/log-tail.ts
3630
+ import { statSync as statSync4 } from "node:fs";
3631
+ import { homedir as homedir3 } from "node:os";
3632
+ import { join as join5 } from "node:path";
3633
+ import { open as open2, stat } from "node:fs/promises";
3634
+ var DEFAULT_POLL_INTERVAL_MS = 1e3;
3635
+ function resolveOpenCodeLogPath(homeDir = homedir3(), env = process.env) {
3636
+ const dataDir = env.XDG_DATA_HOME || join5(homeDir, ".local", "share");
3637
+ return join5(dataDir, "opencode", "log", "opencode.log");
3638
+ }
3639
+ function isEnoent(error2) {
3640
+ return error2?.code === "ENOENT";
3641
+ }
3642
+ function reportFailure(operation, logPath, error2) {
3643
+ console.error(
3644
+ `[opencode-log-tail] ${operation} failed for ${logPath}: ${error2 instanceof Error ? error2.message : String(error2)}`
3645
+ );
3646
+ }
3647
+ function tailOpenCodeLogFile(logPath, onChunk, opts = {}) {
3648
+ const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
3649
+ let offset = 0;
3650
+ let inode = null;
3651
+ let baselineReady = true;
3652
+ try {
3653
+ const initial = statSync4(logPath);
3654
+ offset = initial.size;
3655
+ inode = initial.ino;
3656
+ } catch (error2) {
3657
+ if (!isEnoent(error2)) {
3658
+ reportFailure("initial stat", logPath, error2);
3659
+ baselineReady = false;
3660
+ }
3661
+ }
3662
+ let polling = false;
3663
+ let stopped = false;
3664
+ const poll = async () => {
3665
+ if (polling || stopped) return;
3666
+ polling = true;
3667
+ try {
3668
+ let current;
3669
+ try {
3670
+ current = await stat(logPath);
3671
+ } catch (error2) {
3672
+ if (!isEnoent(error2)) reportFailure("stat", logPath, error2);
3673
+ return;
3674
+ }
3675
+ if (!baselineReady) {
3676
+ offset = current.size;
3677
+ inode = current.ino;
3678
+ baselineReady = true;
3679
+ return;
3680
+ }
3681
+ if (inode !== null && current.ino !== inode || current.size < offset) {
3682
+ offset = 0;
3683
+ }
3684
+ inode = current.ino;
3685
+ if (current.size === offset) return;
3686
+ const length = current.size - offset;
3687
+ const fh = await open2(logPath, "r");
3688
+ try {
3689
+ const buf = Buffer.alloc(length);
3690
+ const { bytesRead } = await fh.read(buf, 0, length, offset);
3691
+ offset += bytesRead;
3692
+ if (bytesRead > 0) onChunk(buf.subarray(0, bytesRead));
3693
+ } finally {
3694
+ await fh.close();
3695
+ }
3696
+ } catch (error2) {
3697
+ if (!isEnoent(error2)) reportFailure("poll", logPath, error2);
3698
+ } finally {
3699
+ polling = false;
3700
+ }
3701
+ };
3702
+ const interval = setInterval(() => void poll(), pollIntervalMs);
3703
+ void poll();
3704
+ return {
3705
+ stop: () => {
3706
+ stopped = true;
3707
+ clearInterval(interval);
3708
+ }
3709
+ };
3710
+ }
3711
+
3556
3712
  // src/lib/opencode/session-db-reclaim.ts
3557
- import { statSync as statSync4, statfsSync } from "fs";
3558
- import { dirname as dirname4 } from "path";
3713
+ import { statSync as statSync5, statfsSync } from "node:fs";
3714
+ import { dirname as dirname4 } from "node:path";
3715
+ function errorMessage(error2) {
3716
+ if (!(error2 instanceof Error)) return String(error2);
3717
+ return error2.cause instanceof Error ? error2.cause.message : error2.message;
3718
+ }
3559
3719
  function insufficientSpaceReason(dbPath, requiredBytes) {
3560
3720
  try {
3561
3721
  const fsStats = statfsSync(dirname4(dbPath));
@@ -3581,17 +3741,17 @@ async function probeReclaimAvailability(input) {
3581
3741
  const { dbPath, requiredBytes } = input;
3582
3742
  let sqlite;
3583
3743
  try {
3584
- sqlite = await import("sqlite");
3744
+ sqlite = await import("node:sqlite");
3585
3745
  } catch (err) {
3586
- console.warn(
3587
- `[probeReclaimAvailability] node:sqlite unavailable for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
3588
- );
3589
- return "sqlite-unavailable";
3746
+ const detail = `Node ${process.version}: ${errorMessage(err)}`;
3747
+ console.warn(`[probeReclaimAvailability] node:sqlite unavailable for ${dbPath}: ` + detail);
3748
+ return { reason: "sqlite-unavailable", detail };
3590
3749
  }
3591
3750
  let autoVacuum = null;
3592
3751
  try {
3593
3752
  const db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
3594
3753
  try {
3754
+ db.exec("PRAGMA busy_timeout=5000");
3595
3755
  autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
3596
3756
  } finally {
3597
3757
  db.close();
@@ -3602,23 +3762,25 @@ async function probeReclaimAvailability(input) {
3602
3762
  );
3603
3763
  }
3604
3764
  if (autoVacuum !== 0) return null;
3605
- return insufficientSpaceReason(dbPath, requiredBytes) !== null ? "insufficient-disk-space" : null;
3765
+ return insufficientSpaceReason(dbPath, requiredBytes) !== null ? { reason: "insufficient-disk-space" } : null;
3606
3766
  }
3607
3767
  async function reclaimSessionDbSpace(input) {
3608
3768
  const { dbPath, maxPages, allowFullVacuum = true } = input;
3609
3769
  let sqlite;
3610
3770
  try {
3611
- sqlite = await import("sqlite");
3771
+ sqlite = await import("node:sqlite");
3612
3772
  } catch (err) {
3773
+ const detail = `Node ${process.version}: ${errorMessage(err)}`;
3613
3774
  console.warn(
3614
- `[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
3775
+ `[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${detail}`
3615
3776
  );
3616
- return { ok: false, skipped: "sqlite-unavailable" };
3777
+ return { ok: false, skipped: "sqlite-unavailable", detail };
3617
3778
  }
3618
3779
  const { DatabaseSync } = sqlite;
3619
3780
  let db;
3620
3781
  try {
3621
3782
  db = new DatabaseSync(dbPath);
3783
+ db.exec("PRAGMA busy_timeout=5000");
3622
3784
  const autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
3623
3785
  if (autoVacuum === 0) {
3624
3786
  if (!allowFullVacuum) {
@@ -3627,7 +3789,7 @@ async function reclaimSessionDbSpace(input) {
3627
3789
  );
3628
3790
  return { ok: false, skipped: "full-vacuum-blocked" };
3629
3791
  }
3630
- const fileBytesForGuard = statSync4(dbPath).size;
3792
+ const fileBytesForGuard = statSync5(dbPath).size;
3631
3793
  const skipReason = insufficientSpaceReason(dbPath, fileBytesForGuard);
3632
3794
  if (skipReason !== null) {
3633
3795
  console.warn(
@@ -3655,10 +3817,12 @@ async function reclaimSessionDbSpace(input) {
3655
3817
  );
3656
3818
  return { ok: false, skipped: "auto-vacuum-not-applicable" };
3657
3819
  } catch (err) {
3658
- console.error(
3659
- `[reclaimSessionDbSpace] reclaim failed for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
3660
- );
3661
- return { ok: false, skipped: "reclaim-error" };
3820
+ console.error(`[reclaimSessionDbSpace] reclaim failed for ${dbPath}: ` + errorMessage(err));
3821
+ return {
3822
+ ok: false,
3823
+ skipped: "reclaim-error",
3824
+ detail: errorMessage(err)
3825
+ };
3662
3826
  } finally {
3663
3827
  db?.close();
3664
3828
  }
@@ -3950,8 +4114,8 @@ function connectTunnel(options) {
3950
4114
  try {
3951
4115
  message = JSON.parse(data.toString());
3952
4116
  } catch (error2) {
3953
- const errorMessage2 = error2 instanceof Error ? error2.message : "Unknown error";
3954
- onError?.(`Failed to handle message: ${errorMessage2}`);
4117
+ const errorMessage3 = error2 instanceof Error ? error2.message : "Unknown error";
4118
+ onError?.(`Failed to handle message: ${errorMessage3}`);
3955
4119
  return;
3956
4120
  }
3957
4121
  if (isStreamFrame(message)) {
@@ -4095,7 +4259,7 @@ var RunnerConnection = class {
4095
4259
  };
4096
4260
 
4097
4261
  // src/lib/tunnel/ready-marker.ts
4098
- import { writeFileSync as writeFileSync3 } from "fs";
4262
+ import { writeFileSync as writeFileSync3 } from "node:fs";
4099
4263
  function writeTunnelReadyMarker(path, agentId) {
4100
4264
  try {
4101
4265
  writeFileSync3(path, `${agentId}
@@ -4107,7 +4271,7 @@ function writeTunnelReadyMarker(path, agentId) {
4107
4271
  }
4108
4272
 
4109
4273
  // src/lib/replication.ts
4110
- import { spawn as spawn4 } from "child_process";
4274
+ import { spawn as spawn4 } from "node:child_process";
4111
4275
  function startSessionDbReplication(configPath) {
4112
4276
  return spawn4("litestream", ["replicate", "-config", configPath], {
4113
4277
  stdio: "inherit"
@@ -4123,7 +4287,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
4123
4287
  }
4124
4288
 
4125
4289
  // src/lib/process-liveness.ts
4126
- import { readFileSync as readFileSync4 } from "fs";
4290
+ import { readFileSync as readFileSync4 } from "node:fs";
4127
4291
  function isProcessAlive(pid) {
4128
4292
  try {
4129
4293
  process.kill(pid, 0);
@@ -4149,9 +4313,9 @@ function isProcessAlive(pid) {
4149
4313
  }
4150
4314
 
4151
4315
  // src/lib/openai-usage.ts
4152
- import { readFileSync as readFileSync5 } from "fs";
4153
- import { homedir as homedir3 } from "os";
4154
- import { join as join5 } from "path";
4316
+ import { readFileSync as readFileSync5 } from "node:fs";
4317
+ import { homedir as homedir4 } from "node:os";
4318
+ import { join as join6 } from "node:path";
4155
4319
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
4156
4320
  var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
4157
4321
  var OpenAiUsageError = class extends Error {
@@ -4165,7 +4329,7 @@ function isLocalCredentialProblem2(err) {
4165
4329
  }
4166
4330
  function readOpenCodeChatGptCredentials() {
4167
4331
  try {
4168
- const raw = readFileSync5(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4332
+ const raw = readFileSync5(join6(homedir4(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4169
4333
  let parsed;
4170
4334
  try {
4171
4335
  parsed = JSON.parse(raw);
@@ -4427,8 +4591,8 @@ function resolveResourceUsageReportingEnabled(flagValue, env) {
4427
4591
  }
4428
4592
 
4429
4593
  // src/lib/resource-usage.ts
4430
- import { cpus, totalmem, freemem } from "os";
4431
- import { statfsSync as statfsSync2 } from "fs";
4594
+ import { cpus, totalmem, freemem } from "node:os";
4595
+ import { statfsSync as statfsSync2 } from "node:fs";
4432
4596
 
4433
4597
  // src/lib/ecs-task-metadata.ts
4434
4598
  var ECS_METADATA_TIMEOUT_MS = 2e3;
@@ -4595,15 +4759,15 @@ function createResourceUsageCollector(homeDir) {
4595
4759
  }
4596
4760
 
4597
4761
  // src/lib/channels/driver.ts
4598
- import { homedir as homedir4 } from "os";
4762
+ import { homedir as homedir5 } from "node:os";
4599
4763
 
4600
4764
  // src/lib/runner-file-sync.ts
4601
- import { join as join7 } from "path";
4765
+ import { join as join8 } from "node:path";
4602
4766
 
4603
4767
  // src/lib/file-push.ts
4604
- import { randomUUID } from "crypto";
4605
- import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
4606
- import { basename, dirname as dirname5, isAbsolute, join as join6, relative, resolve as resolve2, sep } from "path";
4768
+ import { randomUUID } from "node:crypto";
4769
+ import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
4770
+ import { basename, dirname as dirname5, isAbsolute, join as join7, relative, resolve as resolve2, sep } from "node:path";
4607
4771
  var FILE_MODE = 384;
4608
4772
  var DIRECTORY_MODE = 448;
4609
4773
  async function writePushedFile(request) {
@@ -4636,7 +4800,7 @@ async function writePushedFile(request) {
4636
4800
  const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
4637
4801
  dirname5(candidate)
4638
4802
  );
4639
- const realTarget = join6(existingAncestor, ...missingSegments, basename(candidate));
4803
+ const realTarget = join7(existingAncestor, ...missingSegments, basename(candidate));
4640
4804
  const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
4641
4805
  if (allowedDirectory === null) {
4642
4806
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
@@ -4672,7 +4836,7 @@ function expandAndValidate(requestedPath, homeDir) {
4672
4836
  if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
4673
4837
  return null;
4674
4838
  }
4675
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
4839
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
4676
4840
  if (expanded.split(/[/\\]/).includes("..")) {
4677
4841
  return null;
4678
4842
  }
@@ -4745,16 +4909,16 @@ function contains(realDirectory, realTarget) {
4745
4909
  async function createMissingDirectories(existingAncestor, missingSegments) {
4746
4910
  let current = existingAncestor;
4747
4911
  for (const segment of missingSegments) {
4748
- current = join6(current, segment);
4912
+ current = join7(current, segment);
4749
4913
  await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
4750
4914
  await chmod(current, DIRECTORY_MODE);
4751
4915
  }
4752
4916
  }
4753
4917
  async function writeAtomically(realTarget, content) {
4754
- const temporaryPath = join6(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
4918
+ const temporaryPath = join7(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
4755
4919
  let handle;
4756
4920
  try {
4757
- handle = await open2(temporaryPath, "wx", FILE_MODE);
4921
+ handle = await open3(temporaryPath, "wx", FILE_MODE);
4758
4922
  await handle.writeFile(content);
4759
4923
  await handle.chmod(FILE_MODE);
4760
4924
  await handle.close();
@@ -4881,12 +5045,12 @@ var NOT_APPLIED = {
4881
5045
  opencodeAuthApplied: false
4882
5046
  };
4883
5047
  function isClaudeCredentialPath(requestedPath, homeDir) {
4884
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
4885
- return expanded === join7(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
5048
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
5049
+ return expanded === join8(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
4886
5050
  }
4887
5051
  function isOpenCodeAuthPath(requestedPath, homeDir) {
4888
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
4889
- return expanded === join7(homeDir, ...OPENCODE_AUTH_SEGMENTS);
5052
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
5053
+ return expanded === join8(homeDir, ...OPENCODE_AUTH_SEGMENTS);
4890
5054
  }
4891
5055
  async function applyOne(options, file) {
4892
5056
  const label = `${file.id.slice(0, 8)} (${file.path})`;
@@ -5433,7 +5597,7 @@ var ChannelDriver = class _ChannelDriver {
5433
5597
  this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
5434
5598
  this.now = config.now ?? (() => Date.now());
5435
5599
  this.fileSyncDirectories = config.fileSyncDirectories ?? [];
5436
- this.homeDir = config.homeDir ?? homedir4();
5600
+ this.homeDir = config.homeDir ?? homedir5();
5437
5601
  this.maxActiveSessions = config.maxActiveSessions;
5438
5602
  this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
5439
5603
  this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
@@ -5795,7 +5959,7 @@ var ChannelDriver = class _ChannelDriver {
5795
5959
  this.signalDispatchNotStarted(conv, message, "session_existence_unknown");
5796
5960
  break;
5797
5961
  }
5798
- const errorMessage2 = err instanceof Error ? err.message : String(err);
5962
+ const errorMessage3 = err instanceof Error ? err.message : String(err);
5799
5963
  this.sessions.delete(conv.id);
5800
5964
  this.supersede(conv.id, sessionId);
5801
5965
  this.log({
@@ -5804,7 +5968,7 @@ var ChannelDriver = class _ChannelDriver {
5804
5968
  conversation_id: conv.id,
5805
5969
  message_id: message.id
5806
5970
  });
5807
- await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
5971
+ await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
5808
5972
  this.log({
5809
5973
  level: "warn",
5810
5974
  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)}`,
@@ -5815,7 +5979,7 @@ var ChannelDriver = class _ChannelDriver {
5815
5979
  });
5816
5980
  this.log({
5817
5981
  level: "error",
5818
- message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage2}`,
5982
+ message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage3}`,
5819
5983
  conversation_id: conv.id,
5820
5984
  message_id: message.id
5821
5985
  });
@@ -5836,14 +6000,14 @@ var ChannelDriver = class _ChannelDriver {
5836
6000
  this.unconfirmedDispatchFailures.delete(message.id);
5837
6001
  this.sessions.delete(conv.id);
5838
6002
  this.supersede(conv.id, sessionId);
5839
- 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.`;
6003
+ const errorMessage3 = `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.`;
5840
6004
  this.log({
5841
6005
  level: "error",
5842
- message: errorMessage2,
6006
+ message: errorMessage3,
5843
6007
  conversation_id: conv.id,
5844
6008
  message_id: message.id
5845
6009
  });
5846
- await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
6010
+ await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
5847
6011
  this.log({
5848
6012
  level: "warn",
5849
6013
  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)}`,
@@ -7826,14 +7990,14 @@ var ChannelDriver = class _ChannelDriver {
7826
7990
  this.unconfirmedDispatchFailures.delete(row.id);
7827
7991
  this.sessions.delete(readoptConv.id);
7828
7992
  this.supersede(readoptConv.id, sessionId);
7829
- 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.`;
7993
+ const errorMessage3 = `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.`;
7830
7994
  this.log({
7831
7995
  level: "error",
7832
- message: errorMessage2,
7996
+ message: errorMessage3,
7833
7997
  conversation_id: row.conversation_id,
7834
7998
  message_id: row.id
7835
7999
  });
7836
- await this.markFailed(row.conversation_id, row.id, null, errorMessage2).catch((markErr) => {
8000
+ await this.markFailed(row.conversation_id, row.id, null, errorMessage3).catch((markErr) => {
7837
8001
  this.log({
7838
8002
  level: "warn",
7839
8003
  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)}`,
@@ -8942,6 +9106,13 @@ import chalk5 from "chalk";
8942
9106
  import ora2 from "ora";
8943
9107
  import { select as select2 } from "@inquirer/prompts";
8944
9108
  var INTERACTIVE_START_TIMEOUT_MS = 3e4;
9109
+ function checkNonInteractivePortConflict(port, isPortInUseFn) {
9110
+ if (isPortInUseFn(port)) {
9111
+ throw new Error(
9112
+ `Port ${port} is already in use by a non-OpenCode process. Free it or pass --port.`
9113
+ );
9114
+ }
9115
+ }
8945
9116
  async function ensureOpenCodeRunning(ctx) {
8946
9117
  const healthCheck = await checkOpenCodeHealth(ctx.port);
8947
9118
  if (healthCheck.healthy) {
@@ -8989,6 +9160,7 @@ async function ensureOpenCodeRunning(ctx) {
8989
9160
  }
8990
9161
  }
8991
9162
  if (!ctx.interactive) {
9163
+ checkNonInteractivePortConflict(ctx.port, isPortInUse);
8992
9164
  ctx.log(`OpenCode is not running on port ${ctx.port}. Starting it automatically...`);
8993
9165
  const proc = await startOpenCode(ctx.port, { inheritStdio: ctx.inheritStdio });
8994
9166
  const health = await waitForOpenCodeHealth(ctx.port, ctx.startTimeoutMs);
@@ -9070,9 +9242,119 @@ Port ${port} is already in use.`));
9070
9242
  return { port, process: null, version: null, notReadyReason: "you chose to continue without it" };
9071
9243
  }
9072
9244
 
9245
+ // src/commands/ensure-opencode-v2.ts
9246
+ import chalk6 from "chalk";
9247
+ import { select as select3 } from "@inquirer/prompts";
9248
+ async function probeOpenCode2WithoutPassword(port) {
9249
+ try {
9250
+ const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
9251
+ signal: AbortSignal.timeout(2e3)
9252
+ });
9253
+ if (response.status === 401) {
9254
+ return { healthy: false, authFailed: true, error: "HTTP 401" };
9255
+ }
9256
+ if (!response.ok) {
9257
+ return { healthy: false, error: `HTTP ${response.status}` };
9258
+ }
9259
+ return { healthy: true };
9260
+ } catch (error2) {
9261
+ return {
9262
+ healthy: false,
9263
+ error: error2 instanceof Error ? error2.message : "Unknown error"
9264
+ };
9265
+ }
9266
+ }
9267
+ function unknownPasswordError(port) {
9268
+ return new Error(
9269
+ `OpenCode V2 (opencode2) is already running on port ${port} with a password this runner doesn't know. Free the port or pass --port.`
9270
+ );
9271
+ }
9272
+ function v2SessionSupportIncompleteError() {
9273
+ return new Error(
9274
+ "OpenCode V2 (opencode2) session support is incomplete \u2014 see https://github.com/sroze/evident/issues/2075. Not starting a new opencode2 process."
9275
+ );
9276
+ }
9277
+ async function ensureOpenCode2Running(ctx) {
9278
+ const initialHealth = await probeOpenCode2WithoutPassword(ctx.port);
9279
+ if (initialHealth.authFailed) {
9280
+ throw unknownPasswordError(ctx.port);
9281
+ }
9282
+ if (initialHealth.healthy) {
9283
+ return {
9284
+ port: ctx.port,
9285
+ process: null,
9286
+ version: null,
9287
+ notReadyReason: null,
9288
+ password: null
9289
+ };
9290
+ }
9291
+ if (!isOpenCode2Installed()) {
9292
+ throw new Error(
9293
+ "OpenCode V2 (opencode2) is not installed. Install it with: npm install -g @opencode-ai/cli@beta"
9294
+ );
9295
+ }
9296
+ let port = ctx.port;
9297
+ if (!ctx.interactive) {
9298
+ checkNonInteractivePortConflict(port, isPortInUse);
9299
+ } else if (isPortInUse(port)) {
9300
+ console.log(chalk6.yellow(`
9301
+ Port ${port} is already in use.`));
9302
+ const alternativePort = findAvailablePort(port + 1);
9303
+ if (alternativePort) {
9304
+ const useAlternative = await select3({
9305
+ message: `Use port ${alternativePort} instead?`,
9306
+ choices: [
9307
+ { name: `Yes, use port ${alternativePort}`, value: "yes" },
9308
+ { name: "No, I will free the port manually", value: "no" }
9309
+ ]
9310
+ });
9311
+ if (useAlternative === "yes") {
9312
+ port = alternativePort;
9313
+ } else {
9314
+ throw new Error(`Port ${ctx.port} is in use`);
9315
+ }
9316
+ }
9317
+ }
9318
+ if (!ctx.interactive) {
9319
+ throw v2SessionSupportIncompleteError();
9320
+ }
9321
+ console.log(chalk6.yellow(`
9322
+ ${v2SessionSupportIncompleteError().message}`));
9323
+ const action = await select3({
9324
+ message: "OpenCode V2 is not running. What would you like to do?",
9325
+ choices: [
9326
+ {
9327
+ name: "Show me the command",
9328
+ value: "manual",
9329
+ description: "Display the command to run manually"
9330
+ },
9331
+ {
9332
+ name: "Continue without OpenCode V2",
9333
+ value: "continue",
9334
+ description: "Requests will fail until OpenCode V2 starts"
9335
+ }
9336
+ ]
9337
+ });
9338
+ if (action === "manual") {
9339
+ blank();
9340
+ console.log(chalk6.bold("Run this command in another terminal:"));
9341
+ blank();
9342
+ console.log(` ${chalk6.cyan(`opencode2 serve --port ${port}`)}`);
9343
+ blank();
9344
+ throw new Error("Please start OpenCode V2 manually");
9345
+ }
9346
+ return {
9347
+ port,
9348
+ process: null,
9349
+ version: null,
9350
+ notReadyReason: "you chose to continue without OpenCode V2",
9351
+ password: null
9352
+ };
9353
+ }
9354
+
9073
9355
  // src/lib/runner-credentials.ts
9074
- import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "fs";
9075
- import { spawn as spawn5 } from "child_process";
9356
+ import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "node:fs";
9357
+ import { spawn as spawn5 } from "node:child_process";
9076
9358
  var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
9077
9359
  var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
9078
9360
  var GITHUB_PROBE_TIMEOUT_MS = 1e4;
@@ -9344,11 +9626,11 @@ async function configureGitHubAccess({ env, log: log3 }) {
9344
9626
  }
9345
9627
 
9346
9628
  // src/lib/opencode/config-overlay.ts
9347
- import { execFileSync as execFileSync2 } from "child_process";
9348
- import { copyFileSync, existsSync as existsSync2, statSync as statSync5 } from "fs";
9349
- import { isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "path";
9629
+ import { execFileSync as execFileSync2 } from "node:child_process";
9630
+ import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
9631
+ import { isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
9350
9632
  function isFile(filePath) {
9351
- return existsSync2(filePath) && statSync5(filePath).isFile();
9633
+ return existsSync2(filePath) && statSync6(filePath).isFile();
9352
9634
  }
9353
9635
  function applyRunnerOpenCodeConfig({
9354
9636
  overlayPath,
@@ -9360,7 +9642,7 @@ function applyRunnerOpenCodeConfig({
9360
9642
  return;
9361
9643
  }
9362
9644
  const source = isAbsolute2(overlayPath) ? overlayPath : resolve3(cwd, overlayPath);
9363
- const target = isFile(join8(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
9645
+ const target = isFile(join9(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
9364
9646
  if (!isFile(source)) {
9365
9647
  log3(
9366
9648
  `RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
@@ -9368,7 +9650,7 @@ function applyRunnerOpenCodeConfig({
9368
9650
  );
9369
9651
  return;
9370
9652
  }
9371
- copyFileSync(source, join8(cwd, target));
9653
+ copyFileSync(source, join9(cwd, target));
9372
9654
  try {
9373
9655
  execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
9374
9656
  stdio: "ignore"
@@ -9377,11 +9659,11 @@ function applyRunnerOpenCodeConfig({
9377
9659
  const detail = error2 instanceof Error ? error2.message : String(error2);
9378
9660
  log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
9379
9661
  }
9380
- log3(`Applied runner OpenCode config ${source} to ${join8(cwd, target)}`);
9662
+ log3(`Applied runner OpenCode config ${source} to ${join9(cwd, target)}`);
9381
9663
  }
9382
9664
 
9383
9665
  // src/lib/credential-sync.ts
9384
- import { renameSync, writeFileSync as writeFileSync5 } from "fs";
9666
+ import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
9385
9667
  var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
9386
9668
  var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
9387
9669
  var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
@@ -9391,7 +9673,7 @@ var MAX_FLUSH_PASSES = 2;
9391
9673
  function outcomesWith(outcome) {
9392
9674
  return { claude: outcome, opencode: outcome };
9393
9675
  }
9394
- function errorMessage(error2) {
9676
+ function errorMessage2(error2) {
9395
9677
  return error2 instanceof Error ? error2.message : String(error2);
9396
9678
  }
9397
9679
  function waitForSettlement(promise, timeoutMs) {
@@ -9418,7 +9700,7 @@ function writeMarker(markerPath, outcomes, log3) {
9418
9700
  writeFileSync5(temporaryPath, body, { mode: 384 });
9419
9701
  renameSync(temporaryPath, markerPath);
9420
9702
  } catch (error2) {
9421
- log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage(error2)}`, "warn");
9703
+ log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
9422
9704
  }
9423
9705
  }
9424
9706
  function intervalSeconds(env, log3) {
@@ -9450,7 +9732,7 @@ async function runFlushStore(store, env, synchroniserRunner, log3, deadlineAt) {
9450
9732
  },
9451
9733
  (error2) => {
9452
9734
  failed = true;
9453
- log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage(error2)}`, "warn");
9735
+ log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage2(error2)}`, "warn");
9454
9736
  }
9455
9737
  );
9456
9738
  const abortTimer = setTimeout(() => controller.abort(), remainingMs);
@@ -9510,7 +9792,7 @@ function createCredentialSync({
9510
9792
  outcomes[store] = !result.timedOut && result.code === 0 ? "ok" : "failed";
9511
9793
  } catch (error2) {
9512
9794
  outcomes[store] = "failed";
9513
- log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage(error2)}`, "debug");
9795
+ log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage2(error2)}`, "debug");
9514
9796
  }
9515
9797
  }
9516
9798
  const failed = STORES.some((store) => outcomes[store] === "failed");
@@ -9652,7 +9934,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
9652
9934
  if (trimmed === "") {
9653
9935
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
9654
9936
  }
9655
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join9(homeDir, trimmed.slice(2)) : trimmed;
9937
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join10(homeDir, trimmed.slice(2)) : trimmed;
9656
9938
  if (!isAbsolute3(expanded)) {
9657
9939
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
9658
9940
  }
@@ -9676,6 +9958,28 @@ function resolveFileSyncDirectories(raw, homeDir) {
9676
9958
  var DEFAULT_OPENCODE_START_TIMEOUT_SECONDS = 180;
9677
9959
  var MAX_OPENCODE_START_TIMEOUT_SECONDS = 3600;
9678
9960
  var OPENCODE_START_TIMEOUT_ENV = "EVIDENT_OPENCODE_START_TIMEOUT";
9961
+ var OPENCODE_VERSION_ENV = "EVIDENT_OPENCODE_VERSION";
9962
+ function resolveOpenCodeVersion(options, env = process.env) {
9963
+ let raw;
9964
+ let source;
9965
+ if (options.opencodeVersion !== void 0) {
9966
+ raw = options.opencodeVersion;
9967
+ source = "--opencode-version";
9968
+ } else if (env[OPENCODE_VERSION_ENV] !== void 0 && env[OPENCODE_VERSION_ENV] !== "") {
9969
+ raw = env[OPENCODE_VERSION_ENV];
9970
+ source = OPENCODE_VERSION_ENV;
9971
+ } else {
9972
+ return { version: "v1", warnings: [] };
9973
+ }
9974
+ const normalized = raw.trim().toLowerCase();
9975
+ if (normalized !== "v1" && normalized !== "v2") {
9976
+ return {
9977
+ version: "v1",
9978
+ warnings: [`Ignoring invalid ${source} "${raw}": expected v1 or v2; using the default v1`]
9979
+ };
9980
+ }
9981
+ return { version: normalized, warnings: [] };
9982
+ }
9679
9983
  function resolveOpenCodeStartTimeoutMs(options, env = process.env) {
9680
9984
  const defaultMs = DEFAULT_OPENCODE_START_TIMEOUT_SECONDS * 1e3;
9681
9985
  let raw;
@@ -9742,7 +10046,7 @@ function log2(state, message, level = "info") {
9742
10046
  })
9743
10047
  );
9744
10048
  } else if (!state.interactive) {
9745
- const prefix = level === "error" ? chalk6.red("\u2717") : level === "warn" ? chalk6.yellow("!") : level === "debug" ? chalk6.dim("\xB7") : chalk6.green("\u2022");
10049
+ const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
9746
10050
  console.log(`${prefix} ${message}`);
9747
10051
  }
9748
10052
  }
@@ -9772,7 +10076,7 @@ function logActivity(state, entry) {
9772
10076
  }
9773
10077
  function reportSessionDbRecovery(state) {
9774
10078
  try {
9775
- const report = drainSessionDbRecoveryReport({ homeDir: homedir5(), env: process.env });
10079
+ const report = drainSessionDbRecoveryReport({ homeDir: homedir6(), env: process.env });
9776
10080
  for (const record of report.records) {
9777
10081
  const activity = buildSessionDbRecoveryActivity(record);
9778
10082
  if (!activity) throw new Error("could not map session-DB recovery record");
@@ -9803,18 +10107,18 @@ function reportSessionDbRecoveryRecord(state, record) {
9803
10107
  function displayStatus(state) {
9804
10108
  if (!state.interactive) return;
9805
10109
  const attempt = state.connection?.reconnectAttempt ?? 0;
9806
- const tunnel = state.connected ? chalk6.green("tunnel: connected") : attempt > 0 ? chalk6.yellow(`tunnel: reconnecting (#${attempt})`) : chalk6.yellow("tunnel: connecting");
9807
- const opencode = state.opencodeConnected ? chalk6.green(`opencode: :${state.port}`) : chalk6.red(`opencode: :${state.port} (down)`);
9808
- const messages = state.messageCount > 0 ? chalk6.dim(` \xB7 ${state.messageCount} processed`) : "";
10110
+ const tunnel = state.connected ? chalk7.green("tunnel: connected") : attempt > 0 ? chalk7.yellow(`tunnel: reconnecting (#${attempt})`) : chalk7.yellow("tunnel: connecting");
10111
+ const opencode = state.opencodeConnected ? chalk7.green(`opencode: :${state.port}`) : chalk7.red(`opencode: :${state.port} (down)`);
10112
+ const messages = state.messageCount > 0 ? chalk7.dim(` \xB7 ${state.messageCount} processed`) : "";
9809
10113
  const last = state.activityLog[state.activityLog.length - 1];
9810
- const detail = last ? chalk6.dim(` \xB7 ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`) : "";
10114
+ const detail = last ? chalk7.dim(` \xB7 ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`) : "";
9811
10115
  const agent = state.agentName ?? state.agentId;
9812
10116
  console.log(
9813
- `${chalk6.bold("Evident")} ${chalk6.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
10117
+ `${chalk7.bold("Evident")} ${chalk7.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
9814
10118
  );
9815
10119
  }
9816
10120
  async function promptForLogin(promptMessage, successMessage) {
9817
- const action = await select3({
10121
+ const action = await select4({
9818
10122
  message: promptMessage,
9819
10123
  choices: [
9820
10124
  {
@@ -9830,7 +10134,7 @@ async function promptForLogin(promptMessage, successMessage) {
9830
10134
  ]
9831
10135
  });
9832
10136
  if (action === "exit") {
9833
- console.log(chalk6.dim(`
10137
+ console.log(chalk7.dim(`
9834
10138
  You can log in later by running: ${getCliName()} login`));
9835
10139
  process.exit(0);
9836
10140
  }
@@ -9841,7 +10145,7 @@ You can log in later by running: ${getCliName()} login`));
9841
10145
  process.exit(1);
9842
10146
  }
9843
10147
  blank();
9844
- console.log(chalk6.green(successMessage));
10148
+ console.log(chalk7.green(successMessage));
9845
10149
  blank();
9846
10150
  return { token: credentials2.token, authType: "bearer", user: credentials2.user };
9847
10151
  }
@@ -9854,12 +10158,12 @@ async function handleAuthError(state, error2) {
9854
10158
  if (state.interactive) displayStatus(state);
9855
10159
  if (!state.interactive) {
9856
10160
  blank();
9857
- console.log(chalk6.red("Authentication expired"));
9858
- console.log(chalk6.dim("Your authentication token is no longer valid."));
10161
+ console.log(chalk7.red("Authentication expired"));
10162
+ console.log(chalk7.dim("Your authentication token is no longer valid."));
9859
10163
  blank();
9860
- console.log(chalk6.dim("To fix this:"));
9861
- console.log(chalk6.dim(` 1. Run '${getCliName()} login' to re-authenticate`));
9862
- console.log(chalk6.dim(" 2. Restart this command"));
10164
+ console.log(chalk7.dim("To fix this:"));
10165
+ console.log(chalk7.dim(` 1. Run '${getCliName()} login' to re-authenticate`));
10166
+ console.log(chalk7.dim(" 2. Restart this command"));
9863
10167
  blank();
9864
10168
  await cleanup(state);
9865
10169
  await shutdownTelemetry();
@@ -9867,7 +10171,7 @@ async function handleAuthError(state, error2) {
9867
10171
  return { success: false };
9868
10172
  }
9869
10173
  blank();
9870
- console.log(chalk6.yellow("Your authentication has expired."));
10174
+ console.log(chalk7.yellow("Your authentication has expired."));
9871
10175
  blank();
9872
10176
  try {
9873
10177
  const credentials2 = await promptForLogin(
@@ -9958,8 +10262,8 @@ async function driveChannels(state, driver) {
9958
10262
  state.running = false;
9959
10263
  break;
9960
10264
  }
9961
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
9962
- logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage2}` });
10265
+ const errorMessage3 = error2 instanceof Error ? error2.message : String(error2);
10266
+ logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage3}` });
9963
10267
  if (state.interactive) displayStatus(state);
9964
10268
  if (driver.hasInFlightWatchers()) {
9965
10269
  consecutiveDrainFailures = 0;
@@ -9997,9 +10301,18 @@ async function driveChannels(state, driver) {
9997
10301
  }
9998
10302
  }
9999
10303
  var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
10000
- var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
10304
+ var SESSION_DB_RECLAIM_MAX_PAGES = 2e4;
10305
+ function shouldWarnForReclaimSkip(reason) {
10306
+ if (reason !== "sqlite-unavailable") return false;
10307
+ const version2 = /^v(\d+)\.(\d+)\.(\d+)$/.exec(process.version);
10308
+ if (!version2) return false;
10309
+ const major = Number(version2[1]);
10310
+ const minor = Number(version2[2]);
10311
+ const patch = Number(version2[3]);
10312
+ return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
10313
+ }
10001
10314
  function sessionDbPath() {
10002
- return join9(homedir5(), ".local", "share", "opencode", "opencode.db");
10315
+ return join10(homedir6(), ".local", "share", "opencode", "opencode.db");
10003
10316
  }
10004
10317
  function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
10005
10318
  const record = {
@@ -10095,7 +10408,7 @@ async function runSweep(state, driver, config) {
10095
10408
  } else {
10096
10409
  logActivity(state, {
10097
10410
  type: "info",
10098
- message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})`
10411
+ message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : "")
10099
10412
  });
10100
10413
  }
10101
10414
  } catch (error2) {
@@ -10118,13 +10431,20 @@ function scheduleSessionCleanup(state, driver, options) {
10118
10431
  for (const warning2 of config.warnings) {
10119
10432
  logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
10120
10433
  }
10121
- const dbBytes = statSessionDbBytes(homedir5());
10434
+ const dbBytes = statSessionDbBytes(homedir6());
10122
10435
  void (async () => {
10123
- const reclaimSkipReason = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
10436
+ const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
10437
+ if (reclaimAvailability !== null) {
10438
+ logActivity(state, {
10439
+ type: "info",
10440
+ level: shouldWarnForReclaimSkip(reclaimAvailability.reason) ? "warn" : "info",
10441
+ message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : "")
10442
+ });
10443
+ }
10124
10444
  const sizeWarning = buildSessionStoreSizeWarning({
10125
10445
  dbBytes,
10126
10446
  cleanupEnabled: config.enabled,
10127
- reclaimSkipReason
10447
+ reclaimSkipReason: reclaimAvailability?.reason ?? null
10128
10448
  });
10129
10449
  if (sizeWarning !== null) {
10130
10450
  logActivity(state, { type: "info", level: "warn", message: sizeWarning });
@@ -10319,7 +10639,7 @@ function scheduleResourceUsageReporting(state, options) {
10319
10639
  });
10320
10640
  return;
10321
10641
  }
10322
- const { collect, stop } = createResourceUsageCollector(homedir5());
10642
+ const { collect, stop } = createResourceUsageCollector(homedir6());
10323
10643
  state.stopResourceUsageSampling = stop;
10324
10644
  let consecutiveFailures = 0;
10325
10645
  const tick = async () => {
@@ -10416,6 +10736,8 @@ async function cleanup(state, opts = {}) {
10416
10736
  clearTimeout(timer);
10417
10737
  }
10418
10738
  state.sessionCleanupTimers = [];
10739
+ state.stopOpenCodeLogTail?.();
10740
+ state.stopOpenCodeLogTail = null;
10419
10741
  if (state.claudeUsageTimer) {
10420
10742
  clearTimeout(state.claudeUsageTimer);
10421
10743
  state.claudeUsageTimer = null;
@@ -10554,7 +10876,7 @@ async function run(options) {
10554
10876
  let fileSyncDirectories;
10555
10877
  try {
10556
10878
  logLevel = resolveLogLevel(options);
10557
- fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir5());
10879
+ fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir6());
10558
10880
  if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
10559
10881
  throw new Error(
10560
10882
  "--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
@@ -10585,6 +10907,7 @@ async function run(options) {
10585
10907
  opencodeVersion: null,
10586
10908
  sessionDbProvenanceAnomaly: false,
10587
10909
  opencodeProcess: null,
10910
+ stopOpenCodeLogTail: null,
10588
10911
  litestreamProcess: null,
10589
10912
  connection: null,
10590
10913
  channelDriver: null,
@@ -10652,15 +10975,15 @@ async function run(options) {
10652
10975
  printError("Authentication required");
10653
10976
  blank();
10654
10977
  console.log(
10655
- chalk6.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
10978
+ chalk7.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
10656
10979
  );
10657
- console.log(chalk6.dim("Or run `evident login` for interactive authentication"));
10980
+ console.log(chalk7.dim("Or run `evident login` for interactive authentication"));
10658
10981
  blank();
10659
10982
  process.exit(1);
10660
10983
  return;
10661
10984
  }
10662
10985
  blank();
10663
- console.log(chalk6.yellow("You are not logged in to Evident."));
10986
+ console.log(chalk7.yellow("You are not logged in to Evident."));
10664
10987
  blank();
10665
10988
  credentials2 = await promptForLogin(
10666
10989
  "Would you like to log in now?",
@@ -10710,7 +11033,7 @@ async function run(options) {
10710
11033
  );
10711
11034
  blank();
10712
11035
  console.log(
10713
- chalk6.dim(
11036
+ chalk7.dim(
10714
11037
  "Either provide --runner/--agent <id> or set EVIDENT_RUNNER_KEY/EVIDENT_AGENT_KEY"
10715
11038
  )
10716
11039
  );
@@ -10733,15 +11056,15 @@ async function run(options) {
10733
11056
  );
10734
11057
  if (interactive && !state.json) {
10735
11058
  blank();
10736
- console.log(chalk6.bold("Evident Run"));
10737
- console.log(chalk6.dim("-".repeat(40)));
11059
+ console.log(chalk7.bold("Evident Run"));
11060
+ console.log(chalk7.dim("-".repeat(40)));
10738
11061
  }
10739
11062
  const spinner = interactive && !state.json ? ora3("Validating runner...").start() : null;
10740
11063
  let validation = await getAgentInfo(state.agentId, state.authHeader);
10741
11064
  if (!validation.valid && validation.authFailed && interactive) {
10742
11065
  spinner?.fail("Authentication failed");
10743
11066
  blank();
10744
- console.log(chalk6.yellow("Your authentication token is invalid or expired."));
11067
+ console.log(chalk7.yellow("Your authentication token is invalid or expired."));
10745
11068
  blank();
10746
11069
  credentials2 = await promptForLogin(
10747
11070
  "Would you like to log in again?",
@@ -10789,6 +11112,13 @@ async function run(options) {
10789
11112
  if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
10790
11113
  }
10791
11114
  state.credentialSync?.arm();
11115
+ state.stopOpenCodeLogTail = tailOpenCodeLogFile(
11116
+ resolveOpenCodeLogPath(homedir6(), process.env),
11117
+ createOpenCodeActivityForwarder(() => ({
11118
+ agentId: state.agentId,
11119
+ authHeader: state.authHeader
11120
+ }))
11121
+ ).stop;
10792
11122
  let sessionDbVerifyFatal = false;
10793
11123
  if (!options.restoreSessionDb) {
10794
11124
  log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
@@ -10838,6 +11168,13 @@ async function run(options) {
10838
11168
  for (const warning2 of opencodeStartTimeoutWarnings) {
10839
11169
  logActivity(state, { type: "info", level: "warn", message: warning2 });
10840
11170
  }
11171
+ const { version: opencodeVersion, warnings: opencodeVersionWarnings } = resolveOpenCodeVersion(
11172
+ options,
11173
+ process.env
11174
+ );
11175
+ for (const warning2 of opencodeVersionWarnings) {
11176
+ logActivity(state, { type: "info", level: "warn", message: warning2 });
11177
+ }
10841
11178
  const { value: maxActiveSessions, warnings: maxActiveSessionsWarnings } = resolveMaxActiveSessions(options, process.env);
10842
11179
  for (const warning2 of maxActiveSessionsWarnings) {
10843
11180
  logActivity(state, { type: "info", level: "warn", message: warning2 });
@@ -10845,7 +11182,14 @@ async function run(options) {
10845
11182
  const preBootMigrationIds = readSessionDbMigrationIds(sessionDbPath());
10846
11183
  const ocSpinner = interactive && !state.json ? ora3("Checking OpenCode...").start() : null;
10847
11184
  try {
10848
- const oc = await ensureOpenCodeRunning({
11185
+ const oc = opencodeVersion === "v2" ? await ensureOpenCode2Running({
11186
+ port: state.port,
11187
+ interactive: state.interactive,
11188
+ agentId: state.agentId,
11189
+ log: (message) => log2(state, message),
11190
+ startTimeoutMs: opencodeStartTimeoutMs,
11191
+ inheritStdio: Boolean(options.opencodePidFile)
11192
+ }) : await ensureOpenCodeRunning({
10849
11193
  port: state.port,
10850
11194
  interactive: state.interactive,
10851
11195
  agentId: state.agentId,
@@ -10872,7 +11216,7 @@ async function run(options) {
10872
11216
  const provenance = checkSessionDbProvenance({
10873
11217
  dbPath: sessionDbPath(),
10874
11218
  currentVersion: state.opencodeVersion,
10875
- homeDir: homedir5(),
11219
+ homeDir: homedir6(),
10876
11220
  env: process.env
10877
11221
  });
10878
11222
  if (provenance.anomaly) {
@@ -10907,10 +11251,10 @@ async function run(options) {
10907
11251
  if (state.interactive && !state.json) {
10908
11252
  logActivity(state, { type: "info", level: "warn", message: noProviderWarning });
10909
11253
  blank();
10910
- console.log(chalk6.yellow("\u26A0 No OpenCode model provider is configured."));
11254
+ console.log(chalk7.yellow("\u26A0 No OpenCode model provider is configured."));
10911
11255
  console.log(
10912
- chalk6.dim(
10913
- `Run ${chalk6.cyan("opencode auth login")} to set one up \u2014 messages will fail until then.`
11256
+ chalk7.dim(
11257
+ `Run ${chalk7.cyan("opencode auth login")} to set one up \u2014 messages will fail until then.`
10914
11258
  )
10915
11259
  );
10916
11260
  blank();
@@ -11034,7 +11378,7 @@ async function run(options) {
11034
11378
  // #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
11035
11379
  // REJECTED with `file_sync_disabled` on the ack, not silently ignored.
11036
11380
  fileSyncDirectories,
11037
- homeDir: homedir5(),
11381
+ homeDir: homedir6(),
11038
11382
  maxActiveSessions,
11039
11383
  log: (entry) => (
11040
11384
  // Thread the driver's real level straight through so `debug`/`warn`
@@ -11276,6 +11620,9 @@ program.command("run").description("Connect to Evident and process messages").op
11276
11620
  ).option("-v, --verbose", "Alias for --log-level debug (ignored if --log-level is set)").option("-c, --conversation <id>", "Process only this specific conversation").option("--idle-timeout <seconds>", "Exit after N seconds idle").option(
11277
11621
  "--opencode-start-timeout <seconds>",
11278
11622
  "Seconds to wait for OpenCode to become healthy when the runner starts it (default: 180). Env: EVIDENT_OPENCODE_START_TIMEOUT"
11623
+ ).option(
11624
+ "--opencode-version <v1|v2>",
11625
+ "Which OpenCode major version to launch: v1 or v2 (default: v1). Env: EVIDENT_OPENCODE_VERSION"
11279
11626
  ).option("--json", "Output in JSON format").option(
11280
11627
  "--session-cleanup-max-age <duration>",
11281
11628
  "Delete OpenCode sessions idle longer than this (e.g. 7d, 24h). Enables cleanup. Env: EVIDENT_SESSION_CLEANUP_MAX_AGE"
@@ -11344,6 +11691,7 @@ program.command("run").description("Connect to Evident and process messages").op
11344
11691
  // Raw string — validation/precedence is single-sourced in run.ts's
11345
11692
  // resolveOpenCodeStartTimeoutMs (flag > EVIDENT_OPENCODE_START_TIMEOUT > 180s default).
11346
11693
  opencodeStartTimeout: options.opencodeStartTimeout,
11694
+ opencodeVersion: options.opencodeVersion,
11347
11695
  json: options.json,
11348
11696
  // Raw strings — the resolver in run.ts single-sources parsing (M1).
11349
11697
  sessionCleanupMaxAge: options.sessionCleanupMaxAge,