@evident-ai/cli 3.4.1-dev.325472f → 3.4.1-dev.444e897

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
  );
@@ -3416,6 +3489,24 @@ async function hasAnyConfiguredProvider(port) {
3416
3489
  return null;
3417
3490
  }
3418
3491
  }
3492
+ async function reloadProviderCache(port) {
3493
+ try {
3494
+ const res = await timedFetch(`${opencodeBase(port)}/config`, {
3495
+ method: "PATCH",
3496
+ headers: { "Content-Type": "application/json" },
3497
+ body: JSON.stringify({})
3498
+ });
3499
+ if (!res.ok) {
3500
+ console.error(
3501
+ `[reloadProviderCache] PATCH /config returned HTTP ${res.status} (port ${port})`
3502
+ );
3503
+ }
3504
+ } catch (err) {
3505
+ console.error(
3506
+ `[reloadProviderCache] PATCH /config failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
3507
+ );
3508
+ }
3509
+ }
3419
3510
 
3420
3511
  // src/lib/opencode/session-cleanup.ts
3421
3512
  var DURATION_UNIT_MS = {
@@ -3522,8 +3613,8 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
3522
3613
  }
3523
3614
 
3524
3615
  // src/lib/opencode/session-db-size.ts
3525
- import { statSync as statSync3 } from "fs";
3526
- import { join as join4 } from "path";
3616
+ import { statSync as statSync3 } from "node:fs";
3617
+ import { join as join4 } from "node:path";
3527
3618
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
3528
3619
  function statSessionDbBytes(homeDir) {
3529
3620
  const dbPath = join4(homeDir, ".local", "share", "opencode", "opencode.db");
@@ -3553,9 +3644,96 @@ function buildSessionStoreSizeWarning(input) {
3553
3644
  return null;
3554
3645
  }
3555
3646
 
3647
+ // src/lib/opencode/log-tail.ts
3648
+ import { statSync as statSync4 } from "node:fs";
3649
+ import { homedir as homedir3 } from "node:os";
3650
+ import { join as join5 } from "node:path";
3651
+ import { open as open2, stat } from "node:fs/promises";
3652
+ var DEFAULT_POLL_INTERVAL_MS = 1e3;
3653
+ function resolveOpenCodeLogPath(homeDir = homedir3(), env = process.env) {
3654
+ const dataDir = env.XDG_DATA_HOME || join5(homeDir, ".local", "share");
3655
+ return join5(dataDir, "opencode", "log", "opencode.log");
3656
+ }
3657
+ function isEnoent(error2) {
3658
+ return error2?.code === "ENOENT";
3659
+ }
3660
+ function reportFailure(operation, logPath, error2) {
3661
+ console.error(
3662
+ `[opencode-log-tail] ${operation} failed for ${logPath}: ${error2 instanceof Error ? error2.message : String(error2)}`
3663
+ );
3664
+ }
3665
+ function tailOpenCodeLogFile(logPath, onChunk, opts = {}) {
3666
+ const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
3667
+ let offset = 0;
3668
+ let inode = null;
3669
+ let baselineReady = true;
3670
+ try {
3671
+ const initial = statSync4(logPath);
3672
+ offset = initial.size;
3673
+ inode = initial.ino;
3674
+ } catch (error2) {
3675
+ if (!isEnoent(error2)) {
3676
+ reportFailure("initial stat", logPath, error2);
3677
+ baselineReady = false;
3678
+ }
3679
+ }
3680
+ let polling = false;
3681
+ let stopped = false;
3682
+ const poll = async () => {
3683
+ if (polling || stopped) return;
3684
+ polling = true;
3685
+ try {
3686
+ let current;
3687
+ try {
3688
+ current = await stat(logPath);
3689
+ } catch (error2) {
3690
+ if (!isEnoent(error2)) reportFailure("stat", logPath, error2);
3691
+ return;
3692
+ }
3693
+ if (!baselineReady) {
3694
+ offset = current.size;
3695
+ inode = current.ino;
3696
+ baselineReady = true;
3697
+ return;
3698
+ }
3699
+ if (inode !== null && current.ino !== inode || current.size < offset) {
3700
+ offset = 0;
3701
+ }
3702
+ inode = current.ino;
3703
+ if (current.size === offset) return;
3704
+ const length = current.size - offset;
3705
+ const fh = await open2(logPath, "r");
3706
+ try {
3707
+ const buf = Buffer.alloc(length);
3708
+ const { bytesRead } = await fh.read(buf, 0, length, offset);
3709
+ offset += bytesRead;
3710
+ if (bytesRead > 0) onChunk(buf.subarray(0, bytesRead));
3711
+ } finally {
3712
+ await fh.close();
3713
+ }
3714
+ } catch (error2) {
3715
+ if (!isEnoent(error2)) reportFailure("poll", logPath, error2);
3716
+ } finally {
3717
+ polling = false;
3718
+ }
3719
+ };
3720
+ const interval = setInterval(() => void poll(), pollIntervalMs);
3721
+ void poll();
3722
+ return {
3723
+ stop: () => {
3724
+ stopped = true;
3725
+ clearInterval(interval);
3726
+ }
3727
+ };
3728
+ }
3729
+
3556
3730
  // src/lib/opencode/session-db-reclaim.ts
3557
- import { statSync as statSync4, statfsSync } from "fs";
3558
- import { dirname as dirname4 } from "path";
3731
+ import { statSync as statSync5, statfsSync } from "node:fs";
3732
+ import { dirname as dirname4 } from "node:path";
3733
+ function errorMessage(error2) {
3734
+ if (!(error2 instanceof Error)) return String(error2);
3735
+ return error2.cause instanceof Error ? error2.cause.message : error2.message;
3736
+ }
3559
3737
  function insufficientSpaceReason(dbPath, requiredBytes) {
3560
3738
  try {
3561
3739
  const fsStats = statfsSync(dirname4(dbPath));
@@ -3581,17 +3759,17 @@ async function probeReclaimAvailability(input) {
3581
3759
  const { dbPath, requiredBytes } = input;
3582
3760
  let sqlite;
3583
3761
  try {
3584
- sqlite = await import("sqlite");
3762
+ sqlite = await import("node:sqlite");
3585
3763
  } catch (err) {
3586
- console.warn(
3587
- `[probeReclaimAvailability] node:sqlite unavailable for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
3588
- );
3589
- return "sqlite-unavailable";
3764
+ const detail = `Node ${process.version}: ${errorMessage(err)}`;
3765
+ console.warn(`[probeReclaimAvailability] node:sqlite unavailable for ${dbPath}: ` + detail);
3766
+ return { reason: "sqlite-unavailable", detail };
3590
3767
  }
3591
3768
  let autoVacuum = null;
3592
3769
  try {
3593
3770
  const db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
3594
3771
  try {
3772
+ db.exec("PRAGMA busy_timeout=5000");
3595
3773
  autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
3596
3774
  } finally {
3597
3775
  db.close();
@@ -3602,23 +3780,25 @@ async function probeReclaimAvailability(input) {
3602
3780
  );
3603
3781
  }
3604
3782
  if (autoVacuum !== 0) return null;
3605
- return insufficientSpaceReason(dbPath, requiredBytes) !== null ? "insufficient-disk-space" : null;
3783
+ return insufficientSpaceReason(dbPath, requiredBytes) !== null ? { reason: "insufficient-disk-space" } : null;
3606
3784
  }
3607
3785
  async function reclaimSessionDbSpace(input) {
3608
3786
  const { dbPath, maxPages, allowFullVacuum = true } = input;
3609
3787
  let sqlite;
3610
3788
  try {
3611
- sqlite = await import("sqlite");
3789
+ sqlite = await import("node:sqlite");
3612
3790
  } catch (err) {
3791
+ const detail = `Node ${process.version}: ${errorMessage(err)}`;
3613
3792
  console.warn(
3614
- `[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
3793
+ `[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${detail}`
3615
3794
  );
3616
- return { ok: false, skipped: "sqlite-unavailable" };
3795
+ return { ok: false, skipped: "sqlite-unavailable", detail };
3617
3796
  }
3618
3797
  const { DatabaseSync } = sqlite;
3619
3798
  let db;
3620
3799
  try {
3621
3800
  db = new DatabaseSync(dbPath);
3801
+ db.exec("PRAGMA busy_timeout=5000");
3622
3802
  const autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
3623
3803
  if (autoVacuum === 0) {
3624
3804
  if (!allowFullVacuum) {
@@ -3627,7 +3807,7 @@ async function reclaimSessionDbSpace(input) {
3627
3807
  );
3628
3808
  return { ok: false, skipped: "full-vacuum-blocked" };
3629
3809
  }
3630
- const fileBytesForGuard = statSync4(dbPath).size;
3810
+ const fileBytesForGuard = statSync5(dbPath).size;
3631
3811
  const skipReason = insufficientSpaceReason(dbPath, fileBytesForGuard);
3632
3812
  if (skipReason !== null) {
3633
3813
  console.warn(
@@ -3655,10 +3835,12 @@ async function reclaimSessionDbSpace(input) {
3655
3835
  );
3656
3836
  return { ok: false, skipped: "auto-vacuum-not-applicable" };
3657
3837
  } 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" };
3838
+ console.error(`[reclaimSessionDbSpace] reclaim failed for ${dbPath}: ` + errorMessage(err));
3839
+ return {
3840
+ ok: false,
3841
+ skipped: "reclaim-error",
3842
+ detail: errorMessage(err)
3843
+ };
3662
3844
  } finally {
3663
3845
  db?.close();
3664
3846
  }
@@ -3950,8 +4132,8 @@ function connectTunnel(options) {
3950
4132
  try {
3951
4133
  message = JSON.parse(data.toString());
3952
4134
  } catch (error2) {
3953
- const errorMessage2 = error2 instanceof Error ? error2.message : "Unknown error";
3954
- onError?.(`Failed to handle message: ${errorMessage2}`);
4135
+ const errorMessage3 = error2 instanceof Error ? error2.message : "Unknown error";
4136
+ onError?.(`Failed to handle message: ${errorMessage3}`);
3955
4137
  return;
3956
4138
  }
3957
4139
  if (isStreamFrame(message)) {
@@ -4095,7 +4277,7 @@ var RunnerConnection = class {
4095
4277
  };
4096
4278
 
4097
4279
  // src/lib/tunnel/ready-marker.ts
4098
- import { writeFileSync as writeFileSync3 } from "fs";
4280
+ import { writeFileSync as writeFileSync3 } from "node:fs";
4099
4281
  function writeTunnelReadyMarker(path, agentId) {
4100
4282
  try {
4101
4283
  writeFileSync3(path, `${agentId}
@@ -4107,7 +4289,7 @@ function writeTunnelReadyMarker(path, agentId) {
4107
4289
  }
4108
4290
 
4109
4291
  // src/lib/replication.ts
4110
- import { spawn as spawn4 } from "child_process";
4292
+ import { spawn as spawn4 } from "node:child_process";
4111
4293
  function startSessionDbReplication(configPath) {
4112
4294
  return spawn4("litestream", ["replicate", "-config", configPath], {
4113
4295
  stdio: "inherit"
@@ -4123,7 +4305,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
4123
4305
  }
4124
4306
 
4125
4307
  // src/lib/process-liveness.ts
4126
- import { readFileSync as readFileSync4 } from "fs";
4308
+ import { readFileSync as readFileSync4 } from "node:fs";
4127
4309
  function isProcessAlive(pid) {
4128
4310
  try {
4129
4311
  process.kill(pid, 0);
@@ -4149,9 +4331,9 @@ function isProcessAlive(pid) {
4149
4331
  }
4150
4332
 
4151
4333
  // 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";
4334
+ import { readFileSync as readFileSync5 } from "node:fs";
4335
+ import { homedir as homedir4 } from "node:os";
4336
+ import { join as join6 } from "node:path";
4155
4337
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
4156
4338
  var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
4157
4339
  var OpenAiUsageError = class extends Error {
@@ -4165,7 +4347,7 @@ function isLocalCredentialProblem2(err) {
4165
4347
  }
4166
4348
  function readOpenCodeChatGptCredentials() {
4167
4349
  try {
4168
- const raw = readFileSync5(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4350
+ const raw = readFileSync5(join6(homedir4(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4169
4351
  let parsed;
4170
4352
  try {
4171
4353
  parsed = JSON.parse(raw);
@@ -4427,8 +4609,8 @@ function resolveResourceUsageReportingEnabled(flagValue, env) {
4427
4609
  }
4428
4610
 
4429
4611
  // src/lib/resource-usage.ts
4430
- import { cpus, totalmem, freemem } from "os";
4431
- import { statfsSync as statfsSync2 } from "fs";
4612
+ import { cpus, totalmem, freemem } from "node:os";
4613
+ import { statfsSync as statfsSync2 } from "node:fs";
4432
4614
 
4433
4615
  // src/lib/ecs-task-metadata.ts
4434
4616
  var ECS_METADATA_TIMEOUT_MS = 2e3;
@@ -4595,15 +4777,15 @@ function createResourceUsageCollector(homeDir) {
4595
4777
  }
4596
4778
 
4597
4779
  // src/lib/channels/driver.ts
4598
- import { homedir as homedir4 } from "os";
4780
+ import { homedir as homedir5 } from "node:os";
4599
4781
 
4600
4782
  // src/lib/runner-file-sync.ts
4601
- import { join as join7 } from "path";
4783
+ import { join as join8 } from "node:path";
4602
4784
 
4603
4785
  // 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";
4786
+ import { randomUUID } from "node:crypto";
4787
+ import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
4788
+ import { basename, dirname as dirname5, isAbsolute, join as join7, relative, resolve as resolve2, sep } from "node:path";
4607
4789
  var FILE_MODE = 384;
4608
4790
  var DIRECTORY_MODE = 448;
4609
4791
  async function writePushedFile(request) {
@@ -4636,7 +4818,7 @@ async function writePushedFile(request) {
4636
4818
  const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
4637
4819
  dirname5(candidate)
4638
4820
  );
4639
- const realTarget = join6(existingAncestor, ...missingSegments, basename(candidate));
4821
+ const realTarget = join7(existingAncestor, ...missingSegments, basename(candidate));
4640
4822
  const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
4641
4823
  if (allowedDirectory === null) {
4642
4824
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
@@ -4672,7 +4854,7 @@ function expandAndValidate(requestedPath, homeDir) {
4672
4854
  if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
4673
4855
  return null;
4674
4856
  }
4675
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
4857
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
4676
4858
  if (expanded.split(/[/\\]/).includes("..")) {
4677
4859
  return null;
4678
4860
  }
@@ -4745,16 +4927,16 @@ function contains(realDirectory, realTarget) {
4745
4927
  async function createMissingDirectories(existingAncestor, missingSegments) {
4746
4928
  let current = existingAncestor;
4747
4929
  for (const segment of missingSegments) {
4748
- current = join6(current, segment);
4930
+ current = join7(current, segment);
4749
4931
  await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
4750
4932
  await chmod(current, DIRECTORY_MODE);
4751
4933
  }
4752
4934
  }
4753
4935
  async function writeAtomically(realTarget, content) {
4754
- const temporaryPath = join6(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
4936
+ const temporaryPath = join7(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
4755
4937
  let handle;
4756
4938
  try {
4757
- handle = await open2(temporaryPath, "wx", FILE_MODE);
4939
+ handle = await open3(temporaryPath, "wx", FILE_MODE);
4758
4940
  await handle.writeFile(content);
4759
4941
  await handle.chmod(FILE_MODE);
4760
4942
  await handle.close();
@@ -4881,12 +5063,12 @@ var NOT_APPLIED = {
4881
5063
  opencodeAuthApplied: false
4882
5064
  };
4883
5065
  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);
5066
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
5067
+ return expanded === join8(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
4886
5068
  }
4887
5069
  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);
5070
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
5071
+ return expanded === join8(homeDir, ...OPENCODE_AUTH_SEGMENTS);
4890
5072
  }
4891
5073
  async function applyOne(options, file) {
4892
5074
  const label = `${file.id.slice(0, 8)} (${file.path})`;
@@ -5433,7 +5615,7 @@ var ChannelDriver = class _ChannelDriver {
5433
5615
  this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
5434
5616
  this.now = config.now ?? (() => Date.now());
5435
5617
  this.fileSyncDirectories = config.fileSyncDirectories ?? [];
5436
- this.homeDir = config.homeDir ?? homedir4();
5618
+ this.homeDir = config.homeDir ?? homedir5();
5437
5619
  this.maxActiveSessions = config.maxActiveSessions;
5438
5620
  this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
5439
5621
  this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
@@ -5795,7 +5977,7 @@ var ChannelDriver = class _ChannelDriver {
5795
5977
  this.signalDispatchNotStarted(conv, message, "session_existence_unknown");
5796
5978
  break;
5797
5979
  }
5798
- const errorMessage2 = err instanceof Error ? err.message : String(err);
5980
+ const errorMessage3 = err instanceof Error ? err.message : String(err);
5799
5981
  this.sessions.delete(conv.id);
5800
5982
  this.supersede(conv.id, sessionId);
5801
5983
  this.log({
@@ -5804,7 +5986,7 @@ var ChannelDriver = class _ChannelDriver {
5804
5986
  conversation_id: conv.id,
5805
5987
  message_id: message.id
5806
5988
  });
5807
- await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
5989
+ await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
5808
5990
  this.log({
5809
5991
  level: "warn",
5810
5992
  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 +5997,7 @@ var ChannelDriver = class _ChannelDriver {
5815
5997
  });
5816
5998
  this.log({
5817
5999
  level: "error",
5818
- message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage2}`,
6000
+ message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage3}`,
5819
6001
  conversation_id: conv.id,
5820
6002
  message_id: message.id
5821
6003
  });
@@ -5836,14 +6018,14 @@ var ChannelDriver = class _ChannelDriver {
5836
6018
  this.unconfirmedDispatchFailures.delete(message.id);
5837
6019
  this.sessions.delete(conv.id);
5838
6020
  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.`;
6021
+ 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
6022
  this.log({
5841
6023
  level: "error",
5842
- message: errorMessage2,
6024
+ message: errorMessage3,
5843
6025
  conversation_id: conv.id,
5844
6026
  message_id: message.id
5845
6027
  });
5846
- await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
6028
+ await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
5847
6029
  this.log({
5848
6030
  level: "warn",
5849
6031
  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 +8008,14 @@ var ChannelDriver = class _ChannelDriver {
7826
8008
  this.unconfirmedDispatchFailures.delete(row.id);
7827
8009
  this.sessions.delete(readoptConv.id);
7828
8010
  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.`;
8011
+ 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
8012
  this.log({
7831
8013
  level: "error",
7832
- message: errorMessage2,
8014
+ message: errorMessage3,
7833
8015
  conversation_id: row.conversation_id,
7834
8016
  message_id: row.id
7835
8017
  });
7836
- await this.markFailed(row.conversation_id, row.id, null, errorMessage2).catch((markErr) => {
8018
+ await this.markFailed(row.conversation_id, row.id, null, errorMessage3).catch((markErr) => {
7837
8019
  this.log({
7838
8020
  level: "warn",
7839
8021
  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 +9124,13 @@ import chalk5 from "chalk";
8942
9124
  import ora2 from "ora";
8943
9125
  import { select as select2 } from "@inquirer/prompts";
8944
9126
  var INTERACTIVE_START_TIMEOUT_MS = 3e4;
9127
+ function checkNonInteractivePortConflict(port, isPortInUseFn) {
9128
+ if (isPortInUseFn(port)) {
9129
+ throw new Error(
9130
+ `Port ${port} is already in use by a non-OpenCode process. Free it or pass --port.`
9131
+ );
9132
+ }
9133
+ }
8945
9134
  async function ensureOpenCodeRunning(ctx) {
8946
9135
  const healthCheck = await checkOpenCodeHealth(ctx.port);
8947
9136
  if (healthCheck.healthy) {
@@ -8989,6 +9178,7 @@ async function ensureOpenCodeRunning(ctx) {
8989
9178
  }
8990
9179
  }
8991
9180
  if (!ctx.interactive) {
9181
+ checkNonInteractivePortConflict(ctx.port, isPortInUse);
8992
9182
  ctx.log(`OpenCode is not running on port ${ctx.port}. Starting it automatically...`);
8993
9183
  const proc = await startOpenCode(ctx.port, { inheritStdio: ctx.inheritStdio });
8994
9184
  const health = await waitForOpenCodeHealth(ctx.port, ctx.startTimeoutMs);
@@ -9070,9 +9260,119 @@ Port ${port} is already in use.`));
9070
9260
  return { port, process: null, version: null, notReadyReason: "you chose to continue without it" };
9071
9261
  }
9072
9262
 
9263
+ // src/commands/ensure-opencode-v2.ts
9264
+ import chalk6 from "chalk";
9265
+ import { select as select3 } from "@inquirer/prompts";
9266
+ async function probeOpenCode2WithoutPassword(port) {
9267
+ try {
9268
+ const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
9269
+ signal: AbortSignal.timeout(2e3)
9270
+ });
9271
+ if (response.status === 401) {
9272
+ return { healthy: false, authFailed: true, error: "HTTP 401" };
9273
+ }
9274
+ if (!response.ok) {
9275
+ return { healthy: false, error: `HTTP ${response.status}` };
9276
+ }
9277
+ return { healthy: true };
9278
+ } catch (error2) {
9279
+ return {
9280
+ healthy: false,
9281
+ error: error2 instanceof Error ? error2.message : "Unknown error"
9282
+ };
9283
+ }
9284
+ }
9285
+ function unknownPasswordError(port) {
9286
+ return new Error(
9287
+ `OpenCode V2 (opencode2) is already running on port ${port} with a password this runner doesn't know. Free the port or pass --port.`
9288
+ );
9289
+ }
9290
+ function v2SessionSupportIncompleteError() {
9291
+ return new Error(
9292
+ "OpenCode V2 (opencode2) session support is incomplete \u2014 see https://github.com/sroze/evident/issues/2075. Not starting a new opencode2 process."
9293
+ );
9294
+ }
9295
+ async function ensureOpenCode2Running(ctx) {
9296
+ const initialHealth = await probeOpenCode2WithoutPassword(ctx.port);
9297
+ if (initialHealth.authFailed) {
9298
+ throw unknownPasswordError(ctx.port);
9299
+ }
9300
+ if (initialHealth.healthy) {
9301
+ return {
9302
+ port: ctx.port,
9303
+ process: null,
9304
+ version: null,
9305
+ notReadyReason: null,
9306
+ password: null
9307
+ };
9308
+ }
9309
+ if (!isOpenCode2Installed()) {
9310
+ throw new Error(
9311
+ "OpenCode V2 (opencode2) is not installed. Install it with: npm install -g @opencode-ai/cli@beta"
9312
+ );
9313
+ }
9314
+ let port = ctx.port;
9315
+ if (!ctx.interactive) {
9316
+ checkNonInteractivePortConflict(port, isPortInUse);
9317
+ } else if (isPortInUse(port)) {
9318
+ console.log(chalk6.yellow(`
9319
+ Port ${port} is already in use.`));
9320
+ const alternativePort = findAvailablePort(port + 1);
9321
+ if (alternativePort) {
9322
+ const useAlternative = await select3({
9323
+ message: `Use port ${alternativePort} instead?`,
9324
+ choices: [
9325
+ { name: `Yes, use port ${alternativePort}`, value: "yes" },
9326
+ { name: "No, I will free the port manually", value: "no" }
9327
+ ]
9328
+ });
9329
+ if (useAlternative === "yes") {
9330
+ port = alternativePort;
9331
+ } else {
9332
+ throw new Error(`Port ${ctx.port} is in use`);
9333
+ }
9334
+ }
9335
+ }
9336
+ if (!ctx.interactive) {
9337
+ throw v2SessionSupportIncompleteError();
9338
+ }
9339
+ console.log(chalk6.yellow(`
9340
+ ${v2SessionSupportIncompleteError().message}`));
9341
+ const action = await select3({
9342
+ message: "OpenCode V2 is not running. What would you like to do?",
9343
+ choices: [
9344
+ {
9345
+ name: "Show me the command",
9346
+ value: "manual",
9347
+ description: "Display the command to run manually"
9348
+ },
9349
+ {
9350
+ name: "Continue without OpenCode V2",
9351
+ value: "continue",
9352
+ description: "Requests will fail until OpenCode V2 starts"
9353
+ }
9354
+ ]
9355
+ });
9356
+ if (action === "manual") {
9357
+ blank();
9358
+ console.log(chalk6.bold("Run this command in another terminal:"));
9359
+ blank();
9360
+ console.log(` ${chalk6.cyan(`opencode2 serve --port ${port}`)}`);
9361
+ blank();
9362
+ throw new Error("Please start OpenCode V2 manually");
9363
+ }
9364
+ return {
9365
+ port,
9366
+ process: null,
9367
+ version: null,
9368
+ notReadyReason: "you chose to continue without OpenCode V2",
9369
+ password: null
9370
+ };
9371
+ }
9372
+
9073
9373
  // src/lib/runner-credentials.ts
9074
- import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "fs";
9075
- import { spawn as spawn5 } from "child_process";
9374
+ import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "node:fs";
9375
+ import { spawn as spawn5 } from "node:child_process";
9076
9376
  var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
9077
9377
  var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
9078
9378
  var GITHUB_PROBE_TIMEOUT_MS = 1e4;
@@ -9344,11 +9644,11 @@ async function configureGitHubAccess({ env, log: log3 }) {
9344
9644
  }
9345
9645
 
9346
9646
  // 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";
9647
+ import { execFileSync as execFileSync2 } from "node:child_process";
9648
+ import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
9649
+ import { isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
9350
9650
  function isFile(filePath) {
9351
- return existsSync2(filePath) && statSync5(filePath).isFile();
9651
+ return existsSync2(filePath) && statSync6(filePath).isFile();
9352
9652
  }
9353
9653
  function applyRunnerOpenCodeConfig({
9354
9654
  overlayPath,
@@ -9360,7 +9660,7 @@ function applyRunnerOpenCodeConfig({
9360
9660
  return;
9361
9661
  }
9362
9662
  const source = isAbsolute2(overlayPath) ? overlayPath : resolve3(cwd, overlayPath);
9363
- const target = isFile(join8(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
9663
+ const target = isFile(join9(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
9364
9664
  if (!isFile(source)) {
9365
9665
  log3(
9366
9666
  `RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
@@ -9368,7 +9668,7 @@ function applyRunnerOpenCodeConfig({
9368
9668
  );
9369
9669
  return;
9370
9670
  }
9371
- copyFileSync(source, join8(cwd, target));
9671
+ copyFileSync(source, join9(cwd, target));
9372
9672
  try {
9373
9673
  execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
9374
9674
  stdio: "ignore"
@@ -9377,11 +9677,11 @@ function applyRunnerOpenCodeConfig({
9377
9677
  const detail = error2 instanceof Error ? error2.message : String(error2);
9378
9678
  log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
9379
9679
  }
9380
- log3(`Applied runner OpenCode config ${source} to ${join8(cwd, target)}`);
9680
+ log3(`Applied runner OpenCode config ${source} to ${join9(cwd, target)}`);
9381
9681
  }
9382
9682
 
9383
9683
  // src/lib/credential-sync.ts
9384
- import { renameSync, writeFileSync as writeFileSync5 } from "fs";
9684
+ import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
9385
9685
  var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
9386
9686
  var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
9387
9687
  var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
@@ -9391,7 +9691,7 @@ var MAX_FLUSH_PASSES = 2;
9391
9691
  function outcomesWith(outcome) {
9392
9692
  return { claude: outcome, opencode: outcome };
9393
9693
  }
9394
- function errorMessage(error2) {
9694
+ function errorMessage2(error2) {
9395
9695
  return error2 instanceof Error ? error2.message : String(error2);
9396
9696
  }
9397
9697
  function waitForSettlement(promise, timeoutMs) {
@@ -9418,7 +9718,7 @@ function writeMarker(markerPath, outcomes, log3) {
9418
9718
  writeFileSync5(temporaryPath, body, { mode: 384 });
9419
9719
  renameSync(temporaryPath, markerPath);
9420
9720
  } catch (error2) {
9421
- log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage(error2)}`, "warn");
9721
+ log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
9422
9722
  }
9423
9723
  }
9424
9724
  function intervalSeconds(env, log3) {
@@ -9450,7 +9750,7 @@ async function runFlushStore(store, env, synchroniserRunner, log3, deadlineAt) {
9450
9750
  },
9451
9751
  (error2) => {
9452
9752
  failed = true;
9453
- log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage(error2)}`, "warn");
9753
+ log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage2(error2)}`, "warn");
9454
9754
  }
9455
9755
  );
9456
9756
  const abortTimer = setTimeout(() => controller.abort(), remainingMs);
@@ -9510,7 +9810,7 @@ function createCredentialSync({
9510
9810
  outcomes[store] = !result.timedOut && result.code === 0 ? "ok" : "failed";
9511
9811
  } catch (error2) {
9512
9812
  outcomes[store] = "failed";
9513
- log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage(error2)}`, "debug");
9813
+ log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage2(error2)}`, "debug");
9514
9814
  }
9515
9815
  }
9516
9816
  const failed = STORES.some((store) => outcomes[store] === "failed");
@@ -9652,7 +9952,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
9652
9952
  if (trimmed === "") {
9653
9953
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
9654
9954
  }
9655
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join9(homeDir, trimmed.slice(2)) : trimmed;
9955
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join10(homeDir, trimmed.slice(2)) : trimmed;
9656
9956
  if (!isAbsolute3(expanded)) {
9657
9957
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
9658
9958
  }
@@ -9676,6 +9976,28 @@ function resolveFileSyncDirectories(raw, homeDir) {
9676
9976
  var DEFAULT_OPENCODE_START_TIMEOUT_SECONDS = 180;
9677
9977
  var MAX_OPENCODE_START_TIMEOUT_SECONDS = 3600;
9678
9978
  var OPENCODE_START_TIMEOUT_ENV = "EVIDENT_OPENCODE_START_TIMEOUT";
9979
+ var OPENCODE_VERSION_ENV = "EVIDENT_OPENCODE_VERSION";
9980
+ function resolveOpenCodeVersion(options, env = process.env) {
9981
+ let raw;
9982
+ let source;
9983
+ if (options.opencodeVersion !== void 0) {
9984
+ raw = options.opencodeVersion;
9985
+ source = "--opencode-version";
9986
+ } else if (env[OPENCODE_VERSION_ENV] !== void 0 && env[OPENCODE_VERSION_ENV] !== "") {
9987
+ raw = env[OPENCODE_VERSION_ENV];
9988
+ source = OPENCODE_VERSION_ENV;
9989
+ } else {
9990
+ return { version: "v1", warnings: [] };
9991
+ }
9992
+ const normalized = raw.trim().toLowerCase();
9993
+ if (normalized !== "v1" && normalized !== "v2") {
9994
+ return {
9995
+ version: "v1",
9996
+ warnings: [`Ignoring invalid ${source} "${raw}": expected v1 or v2; using the default v1`]
9997
+ };
9998
+ }
9999
+ return { version: normalized, warnings: [] };
10000
+ }
9679
10001
  function resolveOpenCodeStartTimeoutMs(options, env = process.env) {
9680
10002
  const defaultMs = DEFAULT_OPENCODE_START_TIMEOUT_SECONDS * 1e3;
9681
10003
  let raw;
@@ -9742,7 +10064,7 @@ function log2(state, message, level = "info") {
9742
10064
  })
9743
10065
  );
9744
10066
  } else if (!state.interactive) {
9745
- const prefix = level === "error" ? chalk6.red("\u2717") : level === "warn" ? chalk6.yellow("!") : level === "debug" ? chalk6.dim("\xB7") : chalk6.green("\u2022");
10067
+ const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
9746
10068
  console.log(`${prefix} ${message}`);
9747
10069
  }
9748
10070
  }
@@ -9772,7 +10094,7 @@ function logActivity(state, entry) {
9772
10094
  }
9773
10095
  function reportSessionDbRecovery(state) {
9774
10096
  try {
9775
- const report = drainSessionDbRecoveryReport({ homeDir: homedir5(), env: process.env });
10097
+ const report = drainSessionDbRecoveryReport({ homeDir: homedir6(), env: process.env });
9776
10098
  for (const record of report.records) {
9777
10099
  const activity = buildSessionDbRecoveryActivity(record);
9778
10100
  if (!activity) throw new Error("could not map session-DB recovery record");
@@ -9803,18 +10125,18 @@ function reportSessionDbRecoveryRecord(state, record) {
9803
10125
  function displayStatus(state) {
9804
10126
  if (!state.interactive) return;
9805
10127
  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`) : "";
10128
+ const tunnel = state.connected ? chalk7.green("tunnel: connected") : attempt > 0 ? chalk7.yellow(`tunnel: reconnecting (#${attempt})`) : chalk7.yellow("tunnel: connecting");
10129
+ const opencode = state.opencodeConnected ? chalk7.green(`opencode: :${state.port}`) : chalk7.red(`opencode: :${state.port} (down)`);
10130
+ const messages = state.messageCount > 0 ? chalk7.dim(` \xB7 ${state.messageCount} processed`) : "";
9809
10131
  const last = state.activityLog[state.activityLog.length - 1];
9810
- const detail = last ? chalk6.dim(` \xB7 ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`) : "";
10132
+ const detail = last ? chalk7.dim(` \xB7 ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`) : "";
9811
10133
  const agent = state.agentName ?? state.agentId;
9812
10134
  console.log(
9813
- `${chalk6.bold("Evident")} ${chalk6.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
10135
+ `${chalk7.bold("Evident")} ${chalk7.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
9814
10136
  );
9815
10137
  }
9816
10138
  async function promptForLogin(promptMessage, successMessage) {
9817
- const action = await select3({
10139
+ const action = await select4({
9818
10140
  message: promptMessage,
9819
10141
  choices: [
9820
10142
  {
@@ -9830,7 +10152,7 @@ async function promptForLogin(promptMessage, successMessage) {
9830
10152
  ]
9831
10153
  });
9832
10154
  if (action === "exit") {
9833
- console.log(chalk6.dim(`
10155
+ console.log(chalk7.dim(`
9834
10156
  You can log in later by running: ${getCliName()} login`));
9835
10157
  process.exit(0);
9836
10158
  }
@@ -9841,7 +10163,7 @@ You can log in later by running: ${getCliName()} login`));
9841
10163
  process.exit(1);
9842
10164
  }
9843
10165
  blank();
9844
- console.log(chalk6.green(successMessage));
10166
+ console.log(chalk7.green(successMessage));
9845
10167
  blank();
9846
10168
  return { token: credentials2.token, authType: "bearer", user: credentials2.user };
9847
10169
  }
@@ -9854,12 +10176,12 @@ async function handleAuthError(state, error2) {
9854
10176
  if (state.interactive) displayStatus(state);
9855
10177
  if (!state.interactive) {
9856
10178
  blank();
9857
- console.log(chalk6.red("Authentication expired"));
9858
- console.log(chalk6.dim("Your authentication token is no longer valid."));
10179
+ console.log(chalk7.red("Authentication expired"));
10180
+ console.log(chalk7.dim("Your authentication token is no longer valid."));
9859
10181
  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"));
10182
+ console.log(chalk7.dim("To fix this:"));
10183
+ console.log(chalk7.dim(` 1. Run '${getCliName()} login' to re-authenticate`));
10184
+ console.log(chalk7.dim(" 2. Restart this command"));
9863
10185
  blank();
9864
10186
  await cleanup(state);
9865
10187
  await shutdownTelemetry();
@@ -9867,7 +10189,7 @@ async function handleAuthError(state, error2) {
9867
10189
  return { success: false };
9868
10190
  }
9869
10191
  blank();
9870
- console.log(chalk6.yellow("Your authentication has expired."));
10192
+ console.log(chalk7.yellow("Your authentication has expired."));
9871
10193
  blank();
9872
10194
  try {
9873
10195
  const credentials2 = await promptForLogin(
@@ -9931,6 +10253,14 @@ async function driveChannels(state, driver) {
9931
10253
  const opencodeAuthApplied = opencodeAuthApplies !== lastSeenOpencodeAuthApplies;
9932
10254
  lastSeenOpencodeAuthApplies = opencodeAuthApplies;
9933
10255
  if (opencodeAuthApplied) state.openaiUsageRearm?.();
10256
+ if (claudeCredentialApplied || opencodeAuthApplied) {
10257
+ void reloadProviderCache(state.port).catch(
10258
+ (error2) => logActivity(state, {
10259
+ type: "error",
10260
+ error: `OpenCode provider cache reload failed on port ${state.port}: ${error2 instanceof Error ? error2.message : String(error2)}`
10261
+ })
10262
+ );
10263
+ }
9934
10264
  if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
9935
10265
  idlePolls = 0;
9936
10266
  idleMs = 0;
@@ -9958,8 +10288,8 @@ async function driveChannels(state, driver) {
9958
10288
  state.running = false;
9959
10289
  break;
9960
10290
  }
9961
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
9962
- logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage2}` });
10291
+ const errorMessage3 = error2 instanceof Error ? error2.message : String(error2);
10292
+ logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage3}` });
9963
10293
  if (state.interactive) displayStatus(state);
9964
10294
  if (driver.hasInFlightWatchers()) {
9965
10295
  consecutiveDrainFailures = 0;
@@ -9997,9 +10327,18 @@ async function driveChannels(state, driver) {
9997
10327
  }
9998
10328
  }
9999
10329
  var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
10000
- var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
10330
+ var SESSION_DB_RECLAIM_MAX_PAGES = 2e4;
10331
+ function shouldWarnForReclaimSkip(reason) {
10332
+ if (reason !== "sqlite-unavailable") return false;
10333
+ const version2 = /^v(\d+)\.(\d+)\.(\d+)$/.exec(process.version);
10334
+ if (!version2) return false;
10335
+ const major = Number(version2[1]);
10336
+ const minor = Number(version2[2]);
10337
+ const patch = Number(version2[3]);
10338
+ return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
10339
+ }
10001
10340
  function sessionDbPath() {
10002
- return join9(homedir5(), ".local", "share", "opencode", "opencode.db");
10341
+ return join10(homedir6(), ".local", "share", "opencode", "opencode.db");
10003
10342
  }
10004
10343
  function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
10005
10344
  const record = {
@@ -10095,7 +10434,7 @@ async function runSweep(state, driver, config) {
10095
10434
  } else {
10096
10435
  logActivity(state, {
10097
10436
  type: "info",
10098
- message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})`
10437
+ message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : "")
10099
10438
  });
10100
10439
  }
10101
10440
  } catch (error2) {
@@ -10118,13 +10457,20 @@ function scheduleSessionCleanup(state, driver, options) {
10118
10457
  for (const warning2 of config.warnings) {
10119
10458
  logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
10120
10459
  }
10121
- const dbBytes = statSessionDbBytes(homedir5());
10460
+ const dbBytes = statSessionDbBytes(homedir6());
10122
10461
  void (async () => {
10123
- const reclaimSkipReason = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
10462
+ const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
10463
+ if (reclaimAvailability !== null) {
10464
+ logActivity(state, {
10465
+ type: "info",
10466
+ level: shouldWarnForReclaimSkip(reclaimAvailability.reason) ? "warn" : "info",
10467
+ message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : "")
10468
+ });
10469
+ }
10124
10470
  const sizeWarning = buildSessionStoreSizeWarning({
10125
10471
  dbBytes,
10126
10472
  cleanupEnabled: config.enabled,
10127
- reclaimSkipReason
10473
+ reclaimSkipReason: reclaimAvailability?.reason ?? null
10128
10474
  });
10129
10475
  if (sizeWarning !== null) {
10130
10476
  logActivity(state, { type: "info", level: "warn", message: sizeWarning });
@@ -10319,7 +10665,7 @@ function scheduleResourceUsageReporting(state, options) {
10319
10665
  });
10320
10666
  return;
10321
10667
  }
10322
- const { collect, stop } = createResourceUsageCollector(homedir5());
10668
+ const { collect, stop } = createResourceUsageCollector(homedir6());
10323
10669
  state.stopResourceUsageSampling = stop;
10324
10670
  let consecutiveFailures = 0;
10325
10671
  const tick = async () => {
@@ -10416,6 +10762,8 @@ async function cleanup(state, opts = {}) {
10416
10762
  clearTimeout(timer);
10417
10763
  }
10418
10764
  state.sessionCleanupTimers = [];
10765
+ state.stopOpenCodeLogTail?.();
10766
+ state.stopOpenCodeLogTail = null;
10419
10767
  if (state.claudeUsageTimer) {
10420
10768
  clearTimeout(state.claudeUsageTimer);
10421
10769
  state.claudeUsageTimer = null;
@@ -10554,7 +10902,7 @@ async function run(options) {
10554
10902
  let fileSyncDirectories;
10555
10903
  try {
10556
10904
  logLevel = resolveLogLevel(options);
10557
- fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir5());
10905
+ fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir6());
10558
10906
  if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
10559
10907
  throw new Error(
10560
10908
  "--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
@@ -10585,6 +10933,7 @@ async function run(options) {
10585
10933
  opencodeVersion: null,
10586
10934
  sessionDbProvenanceAnomaly: false,
10587
10935
  opencodeProcess: null,
10936
+ stopOpenCodeLogTail: null,
10588
10937
  litestreamProcess: null,
10589
10938
  connection: null,
10590
10939
  channelDriver: null,
@@ -10652,15 +11001,15 @@ async function run(options) {
10652
11001
  printError("Authentication required");
10653
11002
  blank();
10654
11003
  console.log(
10655
- chalk6.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
11004
+ chalk7.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
10656
11005
  );
10657
- console.log(chalk6.dim("Or run `evident login` for interactive authentication"));
11006
+ console.log(chalk7.dim("Or run `evident login` for interactive authentication"));
10658
11007
  blank();
10659
11008
  process.exit(1);
10660
11009
  return;
10661
11010
  }
10662
11011
  blank();
10663
- console.log(chalk6.yellow("You are not logged in to Evident."));
11012
+ console.log(chalk7.yellow("You are not logged in to Evident."));
10664
11013
  blank();
10665
11014
  credentials2 = await promptForLogin(
10666
11015
  "Would you like to log in now?",
@@ -10710,7 +11059,7 @@ async function run(options) {
10710
11059
  );
10711
11060
  blank();
10712
11061
  console.log(
10713
- chalk6.dim(
11062
+ chalk7.dim(
10714
11063
  "Either provide --runner/--agent <id> or set EVIDENT_RUNNER_KEY/EVIDENT_AGENT_KEY"
10715
11064
  )
10716
11065
  );
@@ -10733,15 +11082,15 @@ async function run(options) {
10733
11082
  );
10734
11083
  if (interactive && !state.json) {
10735
11084
  blank();
10736
- console.log(chalk6.bold("Evident Run"));
10737
- console.log(chalk6.dim("-".repeat(40)));
11085
+ console.log(chalk7.bold("Evident Run"));
11086
+ console.log(chalk7.dim("-".repeat(40)));
10738
11087
  }
10739
11088
  const spinner = interactive && !state.json ? ora3("Validating runner...").start() : null;
10740
11089
  let validation = await getAgentInfo(state.agentId, state.authHeader);
10741
11090
  if (!validation.valid && validation.authFailed && interactive) {
10742
11091
  spinner?.fail("Authentication failed");
10743
11092
  blank();
10744
- console.log(chalk6.yellow("Your authentication token is invalid or expired."));
11093
+ console.log(chalk7.yellow("Your authentication token is invalid or expired."));
10745
11094
  blank();
10746
11095
  credentials2 = await promptForLogin(
10747
11096
  "Would you like to log in again?",
@@ -10789,6 +11138,13 @@ async function run(options) {
10789
11138
  if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
10790
11139
  }
10791
11140
  state.credentialSync?.arm();
11141
+ state.stopOpenCodeLogTail = tailOpenCodeLogFile(
11142
+ resolveOpenCodeLogPath(homedir6(), process.env),
11143
+ createOpenCodeActivityForwarder(() => ({
11144
+ agentId: state.agentId,
11145
+ authHeader: state.authHeader
11146
+ }))
11147
+ ).stop;
10792
11148
  let sessionDbVerifyFatal = false;
10793
11149
  if (!options.restoreSessionDb) {
10794
11150
  log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
@@ -10838,6 +11194,13 @@ async function run(options) {
10838
11194
  for (const warning2 of opencodeStartTimeoutWarnings) {
10839
11195
  logActivity(state, { type: "info", level: "warn", message: warning2 });
10840
11196
  }
11197
+ const { version: opencodeVersion, warnings: opencodeVersionWarnings } = resolveOpenCodeVersion(
11198
+ options,
11199
+ process.env
11200
+ );
11201
+ for (const warning2 of opencodeVersionWarnings) {
11202
+ logActivity(state, { type: "info", level: "warn", message: warning2 });
11203
+ }
10841
11204
  const { value: maxActiveSessions, warnings: maxActiveSessionsWarnings } = resolveMaxActiveSessions(options, process.env);
10842
11205
  for (const warning2 of maxActiveSessionsWarnings) {
10843
11206
  logActivity(state, { type: "info", level: "warn", message: warning2 });
@@ -10845,7 +11208,14 @@ async function run(options) {
10845
11208
  const preBootMigrationIds = readSessionDbMigrationIds(sessionDbPath());
10846
11209
  const ocSpinner = interactive && !state.json ? ora3("Checking OpenCode...").start() : null;
10847
11210
  try {
10848
- const oc = await ensureOpenCodeRunning({
11211
+ const oc = opencodeVersion === "v2" ? await ensureOpenCode2Running({
11212
+ port: state.port,
11213
+ interactive: state.interactive,
11214
+ agentId: state.agentId,
11215
+ log: (message) => log2(state, message),
11216
+ startTimeoutMs: opencodeStartTimeoutMs,
11217
+ inheritStdio: Boolean(options.opencodePidFile)
11218
+ }) : await ensureOpenCodeRunning({
10849
11219
  port: state.port,
10850
11220
  interactive: state.interactive,
10851
11221
  agentId: state.agentId,
@@ -10872,7 +11242,7 @@ async function run(options) {
10872
11242
  const provenance = checkSessionDbProvenance({
10873
11243
  dbPath: sessionDbPath(),
10874
11244
  currentVersion: state.opencodeVersion,
10875
- homeDir: homedir5(),
11245
+ homeDir: homedir6(),
10876
11246
  env: process.env
10877
11247
  });
10878
11248
  if (provenance.anomaly) {
@@ -10907,10 +11277,10 @@ async function run(options) {
10907
11277
  if (state.interactive && !state.json) {
10908
11278
  logActivity(state, { type: "info", level: "warn", message: noProviderWarning });
10909
11279
  blank();
10910
- console.log(chalk6.yellow("\u26A0 No OpenCode model provider is configured."));
11280
+ console.log(chalk7.yellow("\u26A0 No OpenCode model provider is configured."));
10911
11281
  console.log(
10912
- chalk6.dim(
10913
- `Run ${chalk6.cyan("opencode auth login")} to set one up \u2014 messages will fail until then.`
11282
+ chalk7.dim(
11283
+ `Run ${chalk7.cyan("opencode auth login")} to set one up \u2014 messages will fail until then.`
10914
11284
  )
10915
11285
  );
10916
11286
  blank();
@@ -11034,7 +11404,7 @@ async function run(options) {
11034
11404
  // #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
11035
11405
  // REJECTED with `file_sync_disabled` on the ack, not silently ignored.
11036
11406
  fileSyncDirectories,
11037
- homeDir: homedir5(),
11407
+ homeDir: homedir6(),
11038
11408
  maxActiveSessions,
11039
11409
  log: (entry) => (
11040
11410
  // Thread the driver's real level straight through so `debug`/`warn`
@@ -11276,6 +11646,9 @@ program.command("run").description("Connect to Evident and process messages").op
11276
11646
  ).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
11647
  "--opencode-start-timeout <seconds>",
11278
11648
  "Seconds to wait for OpenCode to become healthy when the runner starts it (default: 180). Env: EVIDENT_OPENCODE_START_TIMEOUT"
11649
+ ).option(
11650
+ "--opencode-version <v1|v2>",
11651
+ "Which OpenCode major version to launch: v1 or v2 (default: v1). Env: EVIDENT_OPENCODE_VERSION"
11279
11652
  ).option("--json", "Output in JSON format").option(
11280
11653
  "--session-cleanup-max-age <duration>",
11281
11654
  "Delete OpenCode sessions idle longer than this (e.g. 7d, 24h). Enables cleanup. Env: EVIDENT_SESSION_CLEANUP_MAX_AGE"
@@ -11344,6 +11717,7 @@ program.command("run").description("Connect to Evident and process messages").op
11344
11717
  // Raw string — validation/precedence is single-sourced in run.ts's
11345
11718
  // resolveOpenCodeStartTimeoutMs (flag > EVIDENT_OPENCODE_START_TIMEOUT > 180s default).
11346
11719
  opencodeStartTimeout: options.opencodeStartTimeout,
11720
+ opencodeVersion: options.opencodeVersion,
11347
11721
  json: options.json,
11348
11722
  // Raw strings — the resolver in run.ts single-sources parsing (M1).
11349
11723
  sessionCleanupMaxAge: options.sessionCleanupMaxAge,