@evident-ai/cli 3.5.1-dev.bf9e537 → 3.5.2-dev.2828873

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
@@ -1063,7 +1063,7 @@ async function claudeUsage() {
1063
1063
 
1064
1064
  // src/commands/run.ts
1065
1065
  import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
1066
- import { homedir as homedir6 } from "node:os";
1066
+ import { homedir as homedir7 } from "node:os";
1067
1067
  import { isAbsolute as isAbsolute3, join as join11, parse, resolve as resolvePath2 } from "node:path";
1068
1068
  import chalk7 from "chalk";
1069
1069
 
@@ -1158,7 +1158,7 @@ import ora4 from "ora";
1158
1158
  import { select as select4 } from "@inquirer/prompts";
1159
1159
 
1160
1160
  // src/lib/telemetry.ts
1161
- var CLI_VERSION = (true ? "3.5.1-dev.bf9e537" : void 0) ?? process.env.npm_package_version ?? "unknown";
1161
+ var CLI_VERSION = (true ? "3.5.2-dev.2828873" : void 0) ?? process.env.npm_package_version ?? "unknown";
1162
1162
  function getCliVersion() {
1163
1163
  return CLI_VERSION;
1164
1164
  }
@@ -1172,6 +1172,23 @@ var authProvider = null;
1172
1172
  function setTelemetryAuthProvider(provider) {
1173
1173
  authProvider = provider;
1174
1174
  }
1175
+ var diagnosticsReporter = null;
1176
+ function setTelemetryDiagnosticsReporter(reporter) {
1177
+ diagnosticsReporter = reporter;
1178
+ }
1179
+ function reportTelemetryDiagnostic(message) {
1180
+ if (!diagnosticsReporter) {
1181
+ console.error(message);
1182
+ return;
1183
+ }
1184
+ try {
1185
+ diagnosticsReporter(message);
1186
+ } catch (error2) {
1187
+ console.error(
1188
+ `Telemetry diagnostics reporter failed: ${error2 instanceof Error ? error2.message : String(error2)}; original diagnostic: ${message}`
1189
+ );
1190
+ }
1191
+ }
1175
1192
  var FLUSH_FAILURE_LOG_INTERVAL_MS = 6e4;
1176
1193
  var lastFlushFailureLoggedAt = 0;
1177
1194
  var suppressedFlushFailureCount = 0;
@@ -1237,7 +1254,9 @@ async function flushEvents() {
1237
1254
  signal: controller.signal
1238
1255
  });
1239
1256
  if (!response.ok) {
1240
- console.error(`Telemetry flush failed: ${response.status}`);
1257
+ reportTelemetryDiagnostic(
1258
+ `Telemetry could not send events (HTTP ${response.status}); runner and OpenCode are unaffected`
1259
+ );
1241
1260
  }
1242
1261
  } finally {
1243
1262
  clearTimeout(timeout);
@@ -1247,7 +1266,9 @@ async function flushEvents() {
1247
1266
  if (now - lastFlushFailureLoggedAt >= FLUSH_FAILURE_LOG_INTERVAL_MS) {
1248
1267
  const message = error2 instanceof Error ? error2.message : String(error2);
1249
1268
  const suffix = suppressedFlushFailureCount > 0 ? ` (${suppressedFlushFailureCount} more suppressed in the last ${FLUSH_FAILURE_LOG_INTERVAL_MS / 1e3}s)` : "";
1250
- console.error(`Telemetry flush error: ${message}${suffix}`);
1269
+ reportTelemetryDiagnostic(
1270
+ `Telemetry flush failed: ${message}; runner and OpenCode are unaffected${suffix}`
1271
+ );
1251
1272
  lastFlushFailureLoggedAt = now;
1252
1273
  suppressedFlushFailureCount = 0;
1253
1274
  } else {
@@ -1299,8 +1320,14 @@ var EventTypes = {
1299
1320
  };
1300
1321
 
1301
1322
  // src/lib/runner-activity-telemetry.ts
1302
- var FORWARDED_LEVELS = /* @__PURE__ */ new Set(["warn", "error"]);
1323
+ import { homedir as homedir2 } from "node:os";
1324
+ var FORWARDED_LEVELS_BY_SOURCE = {
1325
+ "cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
1326
+ opencode: /* @__PURE__ */ new Set(["warn", "error"]),
1327
+ "cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"])
1328
+ };
1303
1329
  var SEVERITY_BY_LEVEL = {
1330
+ info: "info",
1304
1331
  warn: "warning",
1305
1332
  error: "error"
1306
1333
  };
@@ -1311,25 +1338,72 @@ function parseOpenCodeLogLine(line) {
1311
1338
  const level = levelMatch[1].toUpperCase();
1312
1339
  if (level !== "WARN" && level !== "ERROR") return null;
1313
1340
  const sessionMatch = normalisedLine.match(/(?:^|\s)sessionID=(\S+)/);
1314
- return { level: level === "WARN" ? "warn" : "error", sessionID: sessionMatch?.[1] };
1341
+ const messageMatch = normalisedLine.match(/(?:^|\s)message=(?:"((?:\\.|[^"])*)"|(\S+))/);
1342
+ const parsed = {
1343
+ level: level === "WARN" ? "warn" : "error",
1344
+ sessionID: sessionMatch?.[1]
1345
+ };
1346
+ const message = messageMatch?.[1] ?? messageMatch?.[2];
1347
+ if (message !== void 0) parsed.message = message;
1348
+ return parsed;
1315
1349
  }
1316
1350
  var MAX_LINE_BUFFER_BYTES = 16 * 1024;
1351
+ var REPEAT_SUPPRESSION_WINDOW_MS = 6e4;
1317
1352
  function createOpenCodeActivityForwarder(getContext) {
1318
1353
  let buffer = Buffer.alloc(0);
1319
- const flushLine = (line) => {
1320
- const parsed = parseOpenCodeLogLine(line);
1321
- if (!parsed) return;
1354
+ const repeatStates = /* @__PURE__ */ new Map();
1355
+ const forwardLine = (line, parsed, repeatsSuppressed = 0) => {
1356
+ const metadata = {
1357
+ ...parsed.sessionID ? { sessionID: parsed.sessionID } : {},
1358
+ ...repeatsSuppressed > 0 ? { repeats_suppressed: repeatsSuppressed } : {}
1359
+ };
1322
1360
  forwardRunnerActivity(
1323
1361
  {
1324
1362
  level: parsed.level,
1325
1363
  error: line,
1326
- metadata: parsed.sessionID ? { sessionID: parsed.sessionID } : void 0,
1364
+ metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
1327
1365
  source: "opencode"
1328
1366
  },
1329
1367
  getContext()
1330
1368
  );
1331
1369
  };
1332
- return (chunk) => {
1370
+ const expireRepeatStates = (now, currentKey) => {
1371
+ for (const [key, state] of repeatStates) {
1372
+ if (key === currentKey || now - state.lastForwardedAt < REPEAT_SUPPRESSION_WINDOW_MS) {
1373
+ continue;
1374
+ }
1375
+ if (state.repeatsSuppressed > 0) {
1376
+ forwardLine(state.line, state.parsed, state.repeatsSuppressed);
1377
+ }
1378
+ repeatStates.delete(key);
1379
+ }
1380
+ };
1381
+ const flushLine = (line) => {
1382
+ const parsed = parseOpenCodeLogLine(line);
1383
+ if (!parsed) return;
1384
+ const now = Date.now();
1385
+ const key = parsed.message === void 0 ? void 0 : `${parsed.level}\0${parsed.message}`;
1386
+ expireRepeatStates(now, key);
1387
+ if (key === void 0) {
1388
+ forwardLine(line, parsed);
1389
+ return;
1390
+ }
1391
+ const previous = repeatStates.get(key);
1392
+ if (!previous || now - previous.lastForwardedAt >= REPEAT_SUPPRESSION_WINDOW_MS) {
1393
+ forwardLine(line, parsed, previous?.repeatsSuppressed ?? 0);
1394
+ repeatStates.set(key, {
1395
+ line,
1396
+ parsed,
1397
+ lastForwardedAt: now,
1398
+ repeatsSuppressed: 0
1399
+ });
1400
+ return;
1401
+ }
1402
+ previous.line = line;
1403
+ previous.parsed = parsed;
1404
+ previous.repeatsSuppressed++;
1405
+ };
1406
+ const forward = ((chunk) => {
1333
1407
  buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8")]);
1334
1408
  let newlineIndex;
1335
1409
  while ((newlineIndex = buffer.indexOf(10)) !== -1) {
@@ -1340,14 +1414,44 @@ function createOpenCodeActivityForwarder(getContext) {
1340
1414
  flushLine(buffer.toString("utf-8"));
1341
1415
  buffer = Buffer.alloc(0);
1342
1416
  }
1417
+ });
1418
+ forward.flush = () => {
1419
+ for (const state of repeatStates.values()) {
1420
+ if (state.repeatsSuppressed > 0) {
1421
+ forwardLine(state.line, state.parsed, state.repeatsSuppressed);
1422
+ }
1423
+ }
1424
+ repeatStates.clear();
1343
1425
  };
1426
+ return forward;
1344
1427
  }
1345
1428
  var MAX_MESSAGE_LENGTH = 500;
1346
1429
  var MAX_METADATA_VALUE_LENGTH = 200;
1347
1430
  var MAX_METADATA_ENTRIES = 20;
1348
1431
  var TRUNCATION_MARKER = "\u2026";
1432
+ function redactHomeDirectory(message) {
1433
+ const home = homedir2();
1434
+ if (!home || home === "/") {
1435
+ return message;
1436
+ }
1437
+ const pathSegmentCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-";
1438
+ let result = "";
1439
+ let searchFrom = 0;
1440
+ let homeIndex;
1441
+ while ((homeIndex = message.indexOf(home, searchFrom)) !== -1) {
1442
+ const homeEnd = homeIndex + home.length;
1443
+ const previousCharacter = message[homeIndex - 1];
1444
+ const nextCharacter = message[homeEnd];
1445
+ const atPathBoundary = home === "/" || (!previousCharacter || !pathSegmentCharacters.includes(previousCharacter)) && (!nextCharacter || !pathSegmentCharacters.includes(nextCharacter));
1446
+ result += message.slice(searchFrom, homeIndex);
1447
+ result += atPathBoundary ? "~" : home;
1448
+ searchFrom = homeEnd;
1449
+ }
1450
+ return result + message.slice(searchFrom);
1451
+ }
1349
1452
  function redact(message) {
1350
- 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>");
1453
+ const redacted = 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>");
1454
+ return redactHomeDirectory(redacted);
1351
1455
  }
1352
1456
  function truncate(message) {
1353
1457
  if (message.length <= MAX_MESSAGE_LENGTH) return message;
@@ -1357,8 +1461,8 @@ function sanitiseMetadata(metadata) {
1357
1461
  if (!metadata) return {};
1358
1462
  const entries = Object.entries(metadata).slice(0, MAX_METADATA_ENTRIES);
1359
1463
  if (Object.keys(metadata).length > entries.length) {
1360
- console.error(
1361
- `[runner-activity-telemetry] dropped ${Object.keys(metadata).length - entries.length} metadata entries`
1464
+ reportTelemetryDiagnostic(
1465
+ `Runner activity telemetry dropped ${Object.keys(metadata).length - entries.length} metadata entries; those entries were not sent`
1362
1466
  );
1363
1467
  }
1364
1468
  const sanitised = [];
@@ -1382,8 +1486,8 @@ function admitUnderRateLimit(source, now) {
1382
1486
  }
1383
1487
  if (now - window.windowStartedAt >= RATE_LIMIT_WINDOW_MS) {
1384
1488
  if (window.windowDroppedCount > 0) {
1385
- console.error(
1386
- `[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}"`
1489
+ reportTelemetryDiagnostic(
1490
+ `Runner activity telemetry rate cap reached: dropped ${window.windowDroppedCount} ${window.windowDroppedCount === 1 ? "entry" : "entries"} in the last ${RATE_LIMIT_WINDOW_MS / 1e3}s (limit ${RATE_LIMIT_MAX_EVENTS}/min); those entries were not sent for source "${source}"`
1387
1491
  );
1388
1492
  }
1389
1493
  window.windowStartedAt = now;
@@ -1393,8 +1497,8 @@ function admitUnderRateLimit(source, now) {
1393
1497
  if (window.windowCount >= RATE_LIMIT_MAX_EVENTS) {
1394
1498
  window.windowDroppedCount++;
1395
1499
  if (window.windowDroppedCount === 1) {
1396
- console.error(
1397
- `[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}"`
1500
+ reportTelemetryDiagnostic(
1501
+ `Runner activity telemetry rate cap reached (${RATE_LIMIT_MAX_EVENTS} entries per ${RATE_LIMIT_WINDOW_MS / 1e3}s limit; further entries were not sent for source "${source}"`
1398
1502
  );
1399
1503
  }
1400
1504
  return false;
@@ -1404,9 +1508,9 @@ function admitUnderRateLimit(source, now) {
1404
1508
  }
1405
1509
  function forwardRunnerActivity(entry, context) {
1406
1510
  try {
1407
- if (!FORWARDED_LEVELS.has(entry.level)) return;
1408
- if (!context.agentId || !context.authHeader) return;
1409
1511
  const source = entry.source ?? "cli.run";
1512
+ if (!FORWARDED_LEVELS_BY_SOURCE[source].has(entry.level)) return;
1513
+ if (!context.agentId || !context.authHeader) return;
1410
1514
  if (!admitUnderRateLimit(source, Date.now())) return;
1411
1515
  const rawMessage = entry.error ?? entry.message ?? "";
1412
1516
  const message = truncate(redact(rawMessage));
@@ -1417,8 +1521,8 @@ function forwardRunnerActivity(entry, context) {
1417
1521
  agentId: context.agentId
1418
1522
  });
1419
1523
  } catch (err) {
1420
- console.error(
1421
- `[runner-activity-telemetry] failed to forward runner activity: ${err instanceof Error ? err.message : String(err)}`
1524
+ reportTelemetryDiagnostic(
1525
+ `Runner activity telemetry failed to forward runner activity: ${err instanceof Error ? err.message : String(err)}`
1422
1526
  );
1423
1527
  }
1424
1528
  }
@@ -1620,6 +1724,9 @@ async function checkOpenCodeHealth(port) {
1620
1724
  signal: AbortSignal.timeout(2e3)
1621
1725
  // 2 second timeout
1622
1726
  });
1727
+ if (response.status === 401) {
1728
+ return { healthy: false, authFailed: true, error: "HTTP 401" };
1729
+ }
1623
1730
  if (!response.ok) {
1624
1731
  return { healthy: false, error: `HTTP ${response.status}` };
1625
1732
  }
@@ -1684,7 +1791,7 @@ async function waitForOpenCodeHealth(port, timeoutMs = 3e4) {
1684
1791
  const startTime = Date.now();
1685
1792
  while (Date.now() - startTime < timeoutMs) {
1686
1793
  const health = await checkOpenCodeHealth(port);
1687
- if (health.healthy) {
1794
+ if (health.healthy || health.authFailed) {
1688
1795
  return health;
1689
1796
  }
1690
1797
  await new Promise((resolve4) => setTimeout(resolve4, 1e3));
@@ -3211,7 +3318,7 @@ import { join as join4 } from "node:path";
3211
3318
 
3212
3319
  // src/lib/openai-usage.ts
3213
3320
  import { readFileSync as readFileSync3 } from "node:fs";
3214
- import { homedir as homedir2 } from "node:os";
3321
+ import { homedir as homedir3 } from "node:os";
3215
3322
  import { join as join3 } from "node:path";
3216
3323
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
3217
3324
  var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
@@ -3226,7 +3333,7 @@ function isLocalCredentialProblem2(err) {
3226
3333
  }
3227
3334
  function readOpenCodeChatGptCredentials() {
3228
3335
  try {
3229
- const raw = readFileSync3(join3(homedir2(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
3336
+ const raw = readFileSync3(join3(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
3230
3337
  let parsed;
3231
3338
  try {
3232
3339
  parsed = JSON.parse(raw);
@@ -3367,16 +3474,17 @@ function hasPrimaryHeaders(headers) {
3367
3474
  ].some((name) => headers.has(name));
3368
3475
  }
3369
3476
  async function getOpenAiUsage(port, client) {
3477
+ const openCodeVersion = client?.version ?? "v1";
3370
3478
  const credentials2 = readOpenCodeChatGptCredentials();
3371
3479
  if (!credentials2) {
3372
3480
  throw new OpenAiUsageError(
3373
- "No ChatGPT login found. Connect a ChatGPT account to this runner, or run `opencode auth login`.",
3481
+ openCodeVersion === "v2" ? "OpenAI usage reporting is not available on OpenCode V2 because V2 keeps a connected ChatGPT login in a store Evident cannot read." : "No ChatGPT login found. Connect a ChatGPT account to this runner, or run `opencode auth login`.",
3374
3482
  "no_credentials"
3375
3483
  );
3376
3484
  }
3377
3485
  if (credentials2.expiresAt < Date.now()) {
3378
3486
  throw new OpenAiUsageError(
3379
- "ChatGPT credentials have expired. Run `opencode auth login` to refresh them.",
3487
+ openCodeVersion === "v2" ? "The delivered ChatGPT login has expired. OpenCode V2 cannot be given a replacement by file; connect the account again from the runner's page in Evident." : "ChatGPT credentials have expired. Run `opencode auth login` to refresh them.",
3380
3488
  "credentials_expired"
3381
3489
  );
3382
3490
  }
@@ -3658,7 +3766,7 @@ async function importOpenCodeV2Credentials({
3658
3766
  // src/lib/opencode/session-db-boot.ts
3659
3767
  import { spawn as spawn2 } from "node:child_process";
3660
3768
  import { mkdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
3661
- import { homedir as homedir3 } from "node:os";
3769
+ import { homedir as homedir4 } from "node:os";
3662
3770
  import { dirname as dirname2, resolve as resolvePath } from "node:path";
3663
3771
 
3664
3772
  // src/lib/runner-synchroniser.ts
@@ -4014,14 +4122,14 @@ async function restoreSessionDb(options, configPath, env) {
4014
4122
  case 0:
4015
4123
  return;
4016
4124
  case 31:
4017
- acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir3(), env));
4125
+ acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir4(), env));
4018
4126
  options.log(
4019
4127
  "SESSION-DB-REPLICA-UNUSABLE: booting with a fresh opencode.db and replicating into the existing prefix this boot",
4020
4128
  "warn"
4021
4129
  );
4022
4130
  return;
4023
4131
  case 32:
4024
- acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir3(), env));
4132
+ acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir4(), env));
4025
4133
  discardSessionDbDebris(options);
4026
4134
  markNoReplicate(
4027
4135
  options,
@@ -4110,7 +4218,7 @@ function fileExists(path) {
4110
4218
  async function restoreAndVerifySessionDb(options) {
4111
4219
  const env = options.env ?? process.env;
4112
4220
  clearMarker(options);
4113
- acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir3(), env));
4221
+ acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir4(), env));
4114
4222
  const synchroniserEnv = await runSynchroniser(["env"], {
4115
4223
  timeoutMs: SESSION_DB_SYNCHRONISER_TIMEOUT_MS,
4116
4224
  env
@@ -4311,6 +4419,9 @@ function isSessionDbProvenanceRecord(value) {
4311
4419
  return typeof record.opencodeVersion === "string" && Array.isArray(record.migrationIds) && record.migrationIds.every((id) => typeof id === "string") && typeof record.updatedAt === "string";
4312
4420
  }
4313
4421
 
4422
+ // src/lib/opencode/opencode2-package.ts
4423
+ var OPENCODE2_PACKAGE_SPEC = "@opencode-ai/cli@0.0.0-beta-17823";
4424
+
4314
4425
  // src/lib/opencode/opencode-version-gate.ts
4315
4426
  var QUEUE_VALIDATED_OPENCODE_VERSIONS = ["1.17.11", "1.18.3"];
4316
4427
  function isQueueValidatedVersion(version2) {
@@ -4487,12 +4598,15 @@ function findProcessesByPattern(pgrepPattern, psPattern) {
4487
4598
  }
4488
4599
  } catch {
4489
4600
  try {
4490
- const psOutput = execSync(`ps aux | grep -E "${psPattern}" | grep -v grep`, {
4601
+ const psOutput = execSync("ps aux", {
4491
4602
  encoding: "utf-8",
4492
- stdio: ["pipe", "pipe", "pipe"]
4603
+ stdio: ["pipe", "pipe", "pipe"],
4604
+ // The whole table reaches Node; the 1 MiB default would surface a spurious failure.
4605
+ maxBuffer: 16 * 1024 * 1024
4493
4606
  }).trim();
4494
4607
  if (psOutput) {
4495
4608
  for (const line of psOutput.split("\n")) {
4609
+ if (!psPattern.test(line)) continue;
4496
4610
  const parts = line.trim().split(/\s+/);
4497
4611
  if (parts.length >= 2) {
4498
4612
  const pid = parseInt(parts[1], 10);
@@ -4501,8 +4615,9 @@ function findProcessesByPattern(pgrepPattern, psPattern) {
4501
4615
  }
4502
4616
  }
4503
4617
  } catch (err) {
4618
+ const reason = (typeof err === "object" && err !== null && "stderr" in err && typeof err.stderr === "string" ? err.stderr : "").split("\n").map((line) => line.trim()).find(Boolean) ?? (err instanceof Error ? err.message : String(err)).split("\n").map((line) => line.trim()).find(Boolean) ?? "no error detail available";
4504
4619
  console.warn(
4505
- `findOpenCodeProcesses: ps fallback failed: ${err instanceof Error ? err.message : String(err)}`
4620
+ `Could not list running processes, so an already-running OpenCode may not be detected: ${reason}`
4506
4621
  );
4507
4622
  }
4508
4623
  }
@@ -4534,7 +4649,7 @@ function findProcessesByPattern(pgrepPattern, psPattern) {
4534
4649
  return instances;
4535
4650
  }
4536
4651
  function findOpenCodeProcesses() {
4537
- return findProcessesByPattern("opencode serve|opencode-serve", "opencode (serve|--port)");
4652
+ return findProcessesByPattern("opencode serve|opencode-serve", /opencode (serve|--port)/);
4538
4653
  }
4539
4654
  async function scanPortsForOpenCode() {
4540
4655
  const instances = [];
@@ -4601,7 +4716,8 @@ async function startOpenCode(port, options = {}) {
4601
4716
  const child = spawn3(command, args, {
4602
4717
  detached: true,
4603
4718
  stdio: options.inheritStdio ? "inherit" : "ignore",
4604
- cwd: process.cwd()
4719
+ cwd: process.cwd(),
4720
+ env: { ...process.env, OPENCODE_SERVER_PASSWORD: void 0 }
4605
4721
  });
4606
4722
  return child;
4607
4723
  }
@@ -4617,7 +4733,7 @@ async function startOpenCode2(port, options = {}) {
4617
4733
  args = [
4618
4734
  "-y",
4619
4735
  "-p",
4620
- "@opencode-ai/cli@beta",
4736
+ OPENCODE2_PACKAGE_SPEC,
4621
4737
  "--",
4622
4738
  "opencode2",
4623
4739
  "serve",
@@ -4694,7 +4810,7 @@ async function promptOpenCodeInstall(interactive) {
4694
4810
  npm: "npm install -g opencode-ai",
4695
4811
  curl: "curl -fsSL https://opencode.ai/install.sh | sh",
4696
4812
  v2: {
4697
- npm: "npm install -g @opencode-ai/cli@beta",
4813
+ npm: `npm install -g ${OPENCODE2_PACKAGE_SPEC}`,
4698
4814
  curl: "curl -fsSL https://opencode.ai/v2/install | bash"
4699
4815
  }
4700
4816
  }
@@ -4921,11 +5037,11 @@ function buildSessionStoreSizeWarning(input) {
4921
5037
 
4922
5038
  // src/lib/opencode/log-tail.ts
4923
5039
  import { statSync as statSync4 } from "node:fs";
4924
- import { homedir as homedir4 } from "node:os";
5040
+ import { homedir as homedir5 } from "node:os";
4925
5041
  import { join as join7 } from "node:path";
4926
5042
  import { open as open2, stat } from "node:fs/promises";
4927
5043
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
4928
- function resolveOpenCodeLogPath(homeDir = homedir4(), env = process.env) {
5044
+ function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
4929
5045
  const dataDir = env.XDG_DATA_HOME || join7(homeDir, ".local", "share");
4930
5046
  return join7(dataDir, "opencode", "log", "opencode.log");
4931
5047
  }
@@ -5080,7 +5196,11 @@ async function reclaimSessionDbSpace(input) {
5080
5196
  console.warn(
5081
5197
  `[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: a session turn is live`
5082
5198
  );
5083
- return { ok: false, skipped: "full-vacuum-blocked" };
5199
+ return {
5200
+ ok: false,
5201
+ skipped: "full-vacuum-blocked",
5202
+ detail: "a session turn is live"
5203
+ };
5084
5204
  }
5085
5205
  const fileBytesForGuard = statSync5(dbPath).size;
5086
5206
  const skipReason = insufficientSpaceReason(dbPath, fileBytesForGuard);
@@ -5088,7 +5208,7 @@ async function reclaimSessionDbSpace(input) {
5088
5208
  console.warn(
5089
5209
  `[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: ${skipReason}`
5090
5210
  );
5091
- return { ok: false, skipped: "insufficient-disk-space" };
5211
+ return { ok: false, skipped: "insufficient-disk-space", detail: skipReason };
5092
5212
  }
5093
5213
  const beforeBytes = readLogicalBytes(db);
5094
5214
  db.exec("PRAGMA auto_vacuum=INCREMENTAL");
@@ -5969,7 +6089,7 @@ function createResourceUsageCollector(homeDir) {
5969
6089
  }
5970
6090
 
5971
6091
  // src/lib/channels/driver.ts
5972
- import { homedir as homedir5 } from "node:os";
6092
+ import { homedir as homedir6 } from "node:os";
5973
6093
 
5974
6094
  // src/lib/runner-file-sync.ts
5975
6095
  import { join as join9 } from "node:path";
@@ -6852,7 +6972,7 @@ var ChannelDriver = class _ChannelDriver {
6852
6972
  this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
6853
6973
  this.now = config.now ?? (() => Date.now());
6854
6974
  this.fileSyncDirectories = config.fileSyncDirectories ?? [];
6855
- this.homeDir = config.homeDir ?? homedir5();
6975
+ this.homeDir = config.homeDir ?? homedir6();
6856
6976
  this.maxActiveSessions = config.maxActiveSessions;
6857
6977
  this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
6858
6978
  this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
@@ -11279,8 +11399,16 @@ function checkNonInteractivePortConflict(port, isPortInUseFn) {
11279
11399
  );
11280
11400
  }
11281
11401
  }
11402
+ function unknownPasswordError(port) {
11403
+ return new Error(
11404
+ `OpenCode is already running on port ${port}, but this runner cannot authenticate to it. It may have been started with OPENCODE_SERVER_PASSWORD set in its environment. Free the port or pass --port.`
11405
+ );
11406
+ }
11282
11407
  async function ensureOpenCodeRunning(ctx) {
11283
11408
  const healthCheck = await checkOpenCodeHealth(ctx.port);
11409
+ if (healthCheck.authFailed) {
11410
+ throw unknownPasswordError(ctx.port);
11411
+ }
11284
11412
  if (healthCheck.healthy) {
11285
11413
  return {
11286
11414
  port: ctx.port,
@@ -11331,6 +11459,14 @@ async function ensureOpenCodeRunning(ctx) {
11331
11459
  const proc = await startOpenCode(ctx.port, { inheritStdio: ctx.inheritStdio });
11332
11460
  const health = await waitForOpenCodeHealth(ctx.port, ctx.startTimeoutMs);
11333
11461
  if (!health.healthy) {
11462
+ if (health.authFailed) {
11463
+ return {
11464
+ port: ctx.port,
11465
+ process: proc,
11466
+ version: null,
11467
+ notReadyReason: "it is answering with an authentication challenge, likely because OPENCODE_SERVER_PASSWORD is set in the environment of the process that started it; a longer start timeout will not resolve this"
11468
+ };
11469
+ }
11334
11470
  return {
11335
11471
  port: ctx.port,
11336
11472
  process: proc,
@@ -11400,6 +11536,9 @@ Port ${port} is already in use.`));
11400
11536
  const health = await waitForOpenCodeHealth(port, INTERACTIVE_START_TIMEOUT_MS);
11401
11537
  if (!health.healthy) {
11402
11538
  spinner.fail("Failed to start OpenCode");
11539
+ if (health.authFailed) {
11540
+ throw unknownPasswordError(port);
11541
+ }
11403
11542
  throw new Error("OpenCode failed to start");
11404
11543
  }
11405
11544
  spinner.stop();
@@ -11412,7 +11551,7 @@ Port ${port} is already in use.`));
11412
11551
  import chalk6 from "chalk";
11413
11552
  import ora3 from "ora";
11414
11553
  import { select as select3 } from "@inquirer/prompts";
11415
- function unknownPasswordError(port) {
11554
+ function unknownPasswordError2(port) {
11416
11555
  return new Error(
11417
11556
  `OpenCode V2 (opencode2) is already running on port ${port} with a password this runner doesn't know. Free the port or pass --port.`
11418
11557
  );
@@ -11421,7 +11560,7 @@ var INTERACTIVE_START_TIMEOUT_MS2 = 3e4;
11421
11560
  async function ensureOpenCode2Running(ctx) {
11422
11561
  const initialHealth = await checkOpenCode2Health(ctx.port);
11423
11562
  if (initialHealth.authFailed) {
11424
- throw unknownPasswordError(ctx.port);
11563
+ throw unknownPasswordError2(ctx.port);
11425
11564
  }
11426
11565
  if (initialHealth.healthy) {
11427
11566
  return {
@@ -11434,7 +11573,7 @@ async function ensureOpenCode2Running(ctx) {
11434
11573
  }
11435
11574
  if (!isOpenCode2Installed()) {
11436
11575
  throw new Error(
11437
- "OpenCode V2 (opencode2) is not installed. Install it with: npm install -g @opencode-ai/cli@beta"
11576
+ `OpenCode V2 (opencode2) is not installed. Install it with: npm install -g ${OPENCODE2_PACKAGE_SPEC}`
11438
11577
  );
11439
11578
  }
11440
11579
  let port = ctx.port;
@@ -12241,7 +12380,13 @@ function logActivity(state, entry) {
12241
12380
  const level = entry.level ?? (entry.type === "error" ? "error" : "info");
12242
12381
  if (!meetsThreshold(state, level)) return;
12243
12382
  forwardRunnerActivity(
12244
- { level, message: entry.message, error: entry.error, metadata: entry.metadata },
12383
+ {
12384
+ level,
12385
+ message: entry.message,
12386
+ error: entry.error,
12387
+ metadata: entry.metadata,
12388
+ ...entry.source ? { source: entry.source } : {}
12389
+ },
12245
12390
  { agentId: state.agentId, authHeader: state.authHeader }
12246
12391
  );
12247
12392
  const fullEntry = {
@@ -12263,7 +12408,7 @@ function logActivity(state, entry) {
12263
12408
  }
12264
12409
  function reportSessionDbRecovery(state) {
12265
12410
  try {
12266
- const report = drainSessionDbRecoveryReport({ homeDir: homedir6(), env: process.env });
12411
+ const report = drainSessionDbRecoveryReport({ homeDir: homedir7(), env: process.env });
12267
12412
  for (const record of report.records) {
12268
12413
  const activity = buildSessionDbRecoveryActivity(record);
12269
12414
  if (!activity) throw new Error("could not map session-DB recovery record");
@@ -12317,7 +12462,7 @@ function reportOpenCodeV2CredentialImport(state, result) {
12317
12462
  logActivity(state, {
12318
12463
  type: "info",
12319
12464
  level: "warn",
12320
- message: `OpenCode V2 saved login(s) for ${integrationIds} on this machine, but OpenCode V2 will not use them. See #2251 for importing existing logins.`
12465
+ message: `OpenCode V2 saved login(s) for ${integrationIds} on this machine, but OpenCode V2 will not use them. Connect the provider again from the runner's page in Evident; no shell on this machine is needed.`
12321
12466
  });
12322
12467
  }
12323
12468
  if (result.imported.length === 0 && result.unsupported.length === 0 && result.failed.length === 0) {
@@ -12342,7 +12487,7 @@ async function importOpenCodeV2CredentialsForState(state) {
12342
12487
  }
12343
12488
  const result = await importOpenCodeV2Credentials({
12344
12489
  request: client.request,
12345
- homeDir: homedir6(),
12490
+ homeDir: homedir7(),
12346
12491
  directory,
12347
12492
  guard: state.opencodeV2CredentialImportGuard,
12348
12493
  log: (message, level = "info") => logActivity(state, { type: "info", level, message })
@@ -12561,7 +12706,14 @@ function shouldWarnForReclaimSkip(reason) {
12561
12706
  return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
12562
12707
  }
12563
12708
  function sessionDbPath() {
12564
- return join11(homedir6(), ".local", "share", "opencode", "opencode.db");
12709
+ return join11(homedir7(), ".local", "share", "opencode", "opencode.db");
12710
+ }
12711
+ function logSessionCleanupActivity(state, phase, entry) {
12712
+ logActivity(state, {
12713
+ ...entry,
12714
+ source: "cli.session-cleanup",
12715
+ metadata: { ...entry.metadata, phase }
12716
+ });
12565
12717
  }
12566
12718
  function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
12567
12719
  const record = {
@@ -12604,9 +12756,13 @@ async function runSweep(state, driver, config) {
12604
12756
  try {
12605
12757
  const sessions = await listSessions(state.port, state.opencodeClient ?? void 0);
12606
12758
  if (sessions === null) {
12607
- logActivity(state, {
12759
+ logSessionCleanupActivity(state, "sweep", {
12608
12760
  type: "info",
12609
- message: `Session cleanup: could not list sessions (opencode unreachable); skipping this sweep (${mode})`
12761
+ message: `Session cleanup: could not list sessions (opencode unreachable); skipping this sweep (${mode})`,
12762
+ metadata: {
12763
+ outcome: "sweep-skipped",
12764
+ reason: "opencode-unreachable"
12765
+ }
12610
12766
  });
12611
12767
  return;
12612
12768
  }
@@ -12650,21 +12806,47 @@ async function runSweep(state, driver, config) {
12650
12806
  const beforeMib = (reclaimResult.beforeBytes / 1024 / 1024).toFixed(1);
12651
12807
  const afterMib = (reclaimResult.afterBytes / 1024 / 1024).toFixed(1);
12652
12808
  const checkpointNote = reclaimResult.checkpoint.busy ? ` (on-disk file truncation deferred: checkpoint busy, ${reclaimResult.checkpoint.log} WAL frames pending)` : "";
12653
- logActivity(state, {
12809
+ logSessionCleanupActivity(state, "sweep", {
12654
12810
  type: "info",
12655
- message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB${checkpointNote}`
12811
+ message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB${checkpointNote}`,
12812
+ metadata: {
12813
+ outcome: "reclaimed",
12814
+ mode: reclaimResult.mode,
12815
+ before_bytes: reclaimResult.beforeBytes,
12816
+ after_bytes: reclaimResult.afterBytes,
12817
+ checkpoint_busy: reclaimResult.checkpoint.busy,
12818
+ wal_frames_pending: reclaimResult.checkpoint.log,
12819
+ sessions_inspected: sessions.length,
12820
+ sessions_deleted: deleted,
12821
+ sessions_failed: failed,
12822
+ sessions_skipped_active: skippedNewlyActive
12823
+ }
12656
12824
  });
12657
12825
  } else {
12658
- logActivity(state, {
12826
+ logSessionCleanupActivity(state, "sweep", {
12659
12827
  type: "info",
12660
- message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : "")
12828
+ level: reclaimResult.skipped === "reclaim-error" || reclaimResult.skipped === "insufficient-disk-space" ? "warn" : "info",
12829
+ message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : ""),
12830
+ metadata: {
12831
+ outcome: "reclaim-skipped",
12832
+ reason: reclaimResult.skipped,
12833
+ ...reclaimResult.detail !== void 0 ? { detail: reclaimResult.detail } : {},
12834
+ sessions_inspected: sessions.length,
12835
+ sessions_deleted: deleted,
12836
+ sessions_failed: failed,
12837
+ sessions_skipped_active: skippedNewlyActive
12838
+ }
12661
12839
  });
12662
12840
  }
12663
12841
  } catch (error2) {
12664
12842
  const message = error2 instanceof Error ? error2.message : String(error2);
12665
- logActivity(state, {
12843
+ logSessionCleanupActivity(state, "sweep", {
12666
12844
  type: "error",
12667
- error: `Session cleanup sweep failed (non-fatal, ${mode}): ${message}`
12845
+ error: `Session cleanup sweep failed (non-fatal, ${mode}): ${message}`,
12846
+ metadata: {
12847
+ outcome: "sweep-failed",
12848
+ reason: "sweep-error"
12849
+ }
12668
12850
  });
12669
12851
  }
12670
12852
  }
@@ -12680,14 +12862,19 @@ function scheduleSessionCleanup(state, driver, options) {
12680
12862
  for (const warning2 of config.warnings) {
12681
12863
  logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
12682
12864
  }
12683
- const dbBytes = statSessionDbBytes(homedir6());
12865
+ const dbBytes = statSessionDbBytes(homedir7());
12684
12866
  void (async () => {
12685
12867
  const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
12686
12868
  if (reclaimAvailability !== null) {
12687
- logActivity(state, {
12869
+ logSessionCleanupActivity(state, "startup", {
12688
12870
  type: "info",
12689
12871
  level: shouldWarnForReclaimSkip(reclaimAvailability.reason) ? "warn" : "info",
12690
- message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : "")
12872
+ message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : ""),
12873
+ metadata: {
12874
+ outcome: "preflight-skipped",
12875
+ reason: reclaimAvailability.reason,
12876
+ ...reclaimAvailability.detail !== void 0 ? { detail: reclaimAvailability.detail } : {}
12877
+ }
12691
12878
  });
12692
12879
  }
12693
12880
  const sizeWarning = buildSessionStoreSizeWarning({
@@ -12704,9 +12891,15 @@ function scheduleSessionCleanup(state, driver, options) {
12704
12891
  );
12705
12892
  });
12706
12893
  if (!config.enabled) return;
12707
- logActivity(state, {
12894
+ logSessionCleanupActivity(state, "startup", {
12708
12895
  type: "info",
12709
- message: `Session cleanup enabled (age=${config.maxAgeMs ?? "\u2014"}, count=${config.maxCount ?? "\u2014"}, interval=${config.intervalMs}ms)`
12896
+ message: `Session cleanup enabled (age=${config.maxAgeMs ?? "\u2014"}, count=${config.maxCount ?? "\u2014"}, interval=${config.intervalMs}ms)`,
12897
+ metadata: {
12898
+ outcome: "armed",
12899
+ interval_ms: config.intervalMs,
12900
+ max_age_ms: config.maxAgeMs ?? null,
12901
+ max_count: config.maxCount ?? null
12902
+ }
12710
12903
  });
12711
12904
  const interval = setInterval(() => void runSweep(state, driver, config), config.intervalMs);
12712
12905
  const firstSweep = setTimeout(
@@ -12885,7 +13078,7 @@ function scheduleResourceUsageReporting(state, options) {
12885
13078
  });
12886
13079
  return;
12887
13080
  }
12888
- const { collect, stop } = createResourceUsageCollector(homedir6());
13081
+ const { collect, stop } = createResourceUsageCollector(homedir7());
12889
13082
  state.stopResourceUsageSampling = stop;
12890
13083
  let consecutiveFailures = 0;
12891
13084
  const tick = async () => {
@@ -13110,7 +13303,7 @@ async function run(options) {
13110
13303
  let fileSyncDirectories;
13111
13304
  try {
13112
13305
  logLevel = resolveLogLevel(options);
13113
- fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir6());
13306
+ fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir7());
13114
13307
  if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
13115
13308
  throw new Error(
13116
13309
  "--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
@@ -13165,6 +13358,7 @@ async function run(options) {
13165
13358
  authHeader: ""
13166
13359
  };
13167
13360
  setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
13361
+ setTelemetryDiagnosticsReporter((message) => log2(state, message, "warn"));
13168
13362
  if (options.credentialSyncMarker) {
13169
13363
  state.credentialSync = createCredentialSync({
13170
13364
  markerPath: options.credentialSyncMarker,
@@ -13350,13 +13544,18 @@ async function run(options) {
13350
13544
  if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
13351
13545
  }
13352
13546
  state.credentialSync?.arm();
13353
- state.stopOpenCodeLogTail = tailOpenCodeLogFile(
13354
- resolveOpenCodeLogPath(homedir6(), process.env),
13355
- createOpenCodeActivityForwarder(() => ({
13356
- agentId: state.agentId,
13357
- authHeader: state.authHeader
13358
- }))
13359
- ).stop;
13547
+ const openCodeActivityForwarder = createOpenCodeActivityForwarder(() => ({
13548
+ agentId: state.agentId,
13549
+ authHeader: state.authHeader
13550
+ }));
13551
+ const openCodeLogTail = tailOpenCodeLogFile(
13552
+ resolveOpenCodeLogPath(homedir7(), process.env),
13553
+ openCodeActivityForwarder
13554
+ );
13555
+ state.stopOpenCodeLogTail = () => {
13556
+ openCodeActivityForwarder.flush();
13557
+ openCodeLogTail.stop();
13558
+ };
13360
13559
  let sessionDbVerifyFatal = false;
13361
13560
  if (!options.restoreSessionDb) {
13362
13561
  log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
@@ -13368,6 +13567,12 @@ async function run(options) {
13368
13567
  "Skipping session-DB restore: OpenCode is already serving this database",
13369
13568
  "debug"
13370
13569
  );
13570
+ } else if (health.authFailed) {
13571
+ log2(
13572
+ state,
13573
+ "Skipping session-DB restore: OpenCode answered with an authentication challenge; assuming this database is in use",
13574
+ "debug"
13575
+ );
13371
13576
  } else {
13372
13577
  const result = await restoreAndVerifySessionDb({
13373
13578
  dbPath: sessionDbPath(),
@@ -13467,7 +13672,7 @@ async function run(options) {
13467
13672
  const provenance = checkSessionDbProvenance({
13468
13673
  dbPath: sessionDbPath(),
13469
13674
  currentVersion: state.opencodeVersion,
13470
- homeDir: homedir6(),
13675
+ homeDir: homedir7(),
13471
13676
  env: process.env
13472
13677
  });
13473
13678
  if (provenance.anomaly) {
@@ -13637,7 +13842,7 @@ async function run(options) {
13637
13842
  // #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
13638
13843
  // REJECTED with `file_sync_disabled` on the ack, not silently ignored.
13639
13844
  fileSyncDirectories,
13640
- homeDir: homedir6(),
13845
+ homeDir: homedir7(),
13641
13846
  maxActiveSessions,
13642
13847
  log: (entry) => (
13643
13848
  // Thread the driver's real level straight through so `debug`/`warn`
@@ -13813,7 +14018,7 @@ async function run(options) {
13813
14018
  },
13814
14019
  report: (usage) => reportOpenAiUsage(state.agentId, state.authHeader, usage),
13815
14020
  isLocalCredentialProblem: isLocalCredentialProblem2,
13816
- forcedOnHint: "connect a ChatGPT account to this runner, or run `opencode auth login`",
14021
+ forcedOnHint: state.opencodeApiVersion === "v2" ? "OpenCode V2 keeps its connected ChatGPT login in a store Evident cannot read" : "connect a ChatGPT account to this runner, or run `opencode auth login`",
13817
14022
  firstDelayMs: firstReportDelayMs,
13818
14023
  nextDelayMs: usageReportDelayMs,
13819
14024
  failureLogLevel: usageReportFailureLogLevel