@evident-ai/cli 3.4.1-dev.f9c90e1 → 3.4.1-dev.fb92ede

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 = {
@@ -623,6 +623,19 @@ function isInteractive(jsonOutput) {
623
623
  return true;
624
624
  }
625
625
 
626
+ // src/lib/subscription-usage-report.ts
627
+ function toReportedSubscription(collected) {
628
+ if (!collected) return null;
629
+ if (collected.ownerEmail === null && collected.planType === null && collected.organizationName === null) {
630
+ return null;
631
+ }
632
+ return {
633
+ owner_email: collected.ownerEmail,
634
+ plan_type: collected.planType,
635
+ organization_name: collected.organizationName
636
+ };
637
+ }
638
+
626
639
  // src/commands/agent-lookup.ts
627
640
  async function readErrorMessage(response) {
628
641
  const text = await response.text().catch(() => "");
@@ -670,12 +683,15 @@ async function resolveAgentIdFromKey(authHeader) {
670
683
  }
671
684
  }
672
685
  var BEST_EFFORT_NOTIFY_TIMEOUT_MS = 2e3;
673
- async function notifyAgentDisconnected(agentId, authHeader) {
674
- const apiUrl = getApiUrlConfig();
686
+ async function postBestEffort(path, authHeader, body) {
675
687
  try {
676
- const response = await fetch(`${apiUrl}/runners/${agentId}/disconnect`, {
688
+ const apiUrl = getApiUrlConfig();
689
+ const headers = { Authorization: authHeader };
690
+ if (body !== void 0) headers["Content-Type"] = "application/json";
691
+ const response = await fetch(`${apiUrl}${path}`, {
677
692
  method: "POST",
678
- headers: { Authorization: authHeader },
693
+ headers,
694
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
679
695
  signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
680
696
  });
681
697
  if (!response.ok) {
@@ -687,73 +703,32 @@ async function notifyAgentDisconnected(agentId, authHeader) {
687
703
  }
688
704
  return { ok: true };
689
705
  } catch (error2) {
690
- return { ok: false, error: describeBestEffortError(error2) };
706
+ return { ok: false, error: describeTimeoutError(error2, BEST_EFFORT_NOTIFY_TIMEOUT_MS) };
691
707
  }
692
708
  }
693
- function describeBestEffortError(error2) {
709
+ function describeTimeoutError(error2, timeoutMs) {
694
710
  const name = error2?.name;
695
711
  if (name === "TimeoutError" || name === "AbortError") {
696
- return `timed out after ${BEST_EFFORT_NOTIFY_TIMEOUT_MS}ms`;
712
+ return `timed out after ${timeoutMs}ms`;
697
713
  }
698
714
  return error2 instanceof Error ? error2.message : String(error2);
699
715
  }
716
+ async function notifyAgentDisconnected(agentId, authHeader) {
717
+ return postBestEffort(`/runners/${agentId}/disconnect`, authHeader);
718
+ }
700
719
  async function reportMicrovmId(agentId, authHeader, microvmId) {
701
- try {
702
- const apiUrl = getApiUrlConfig();
703
- const response = await fetch(`${apiUrl}/runners/${agentId}/microvm`, {
704
- method: "POST",
705
- headers: { Authorization: authHeader, "Content-Type": "application/json" },
706
- body: JSON.stringify({ microvm_id: microvmId }),
707
- signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
708
- });
709
- if (!response.ok) {
710
- const serverMessage = await readErrorMessage(response);
711
- return {
712
- ok: false,
713
- error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
714
- };
715
- }
716
- return { ok: true };
717
- } catch (error2) {
718
- return { ok: false, error: describeBestEffortError(error2) };
719
- }
720
+ return postBestEffort(`/runners/${agentId}/microvm`, authHeader, { microvm_id: microvmId });
720
721
  }
721
722
  function toReportedWindow(window) {
722
723
  if (!window) return null;
723
724
  return { utilization: window.utilization, resets_at: window.resetsAt };
724
725
  }
725
- function toReportedOwner(snapshot) {
726
- if (!snapshot.owner) return null;
727
- return {
728
- email: snapshot.owner.email,
729
- organization_name: snapshot.owner.organizationName,
730
- rate_limit_tier: snapshot.owner.rateLimitTier
731
- };
732
- }
733
726
  async function reportClaudeUsage(agentId, authHeader, snapshot) {
734
- try {
735
- const apiUrl = getApiUrlConfig();
736
- const response = await fetch(`${apiUrl}/runners/${agentId}/claude-usage`, {
737
- method: "POST",
738
- headers: { Authorization: authHeader, "Content-Type": "application/json" },
739
- body: JSON.stringify({
740
- five_hour: toReportedWindow(snapshot.fiveHour),
741
- seven_day: toReportedWindow(snapshot.sevenDay),
742
- owner: toReportedOwner(snapshot)
743
- }),
744
- signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
745
- });
746
- if (!response.ok) {
747
- const serverMessage = await readErrorMessage(response);
748
- return {
749
- ok: false,
750
- error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
751
- };
752
- }
753
- return { ok: true };
754
- } catch (error2) {
755
- return { ok: false, error: describeBestEffortError(error2) };
756
- }
727
+ return postBestEffort(`/runners/${agentId}/claude-usage`, authHeader, {
728
+ five_hour: toReportedWindow(snapshot.fiveHour),
729
+ seven_day: toReportedWindow(snapshot.sevenDay),
730
+ subscription: toReportedSubscription(snapshot.subscription)
731
+ });
757
732
  }
758
733
  function toReportedOpenAiWindow(window) {
759
734
  if (!window) return null;
@@ -763,69 +738,26 @@ function toReportedOpenAiWindow(window) {
763
738
  resets_at: window.resetsAt
764
739
  };
765
740
  }
766
- function toReportedOpenAiSubscription(snapshot) {
767
- if (!snapshot.subscription) return null;
768
- return {
769
- owner_email: snapshot.subscription.ownerEmail,
770
- plan_type: snapshot.subscription.planType
771
- };
772
- }
773
741
  async function reportOpenAiUsage(agentId, authHeader, snapshot) {
774
- try {
775
- const apiUrl = getApiUrlConfig();
776
- const response = await fetch(`${apiUrl}/runners/${agentId}/openai-usage`, {
777
- method: "POST",
778
- headers: { Authorization: authHeader, "Content-Type": "application/json" },
779
- body: JSON.stringify({
780
- primary: toReportedOpenAiWindow(snapshot.primary),
781
- secondary: toReportedOpenAiWindow(snapshot.secondary),
782
- has_credits: snapshot.hasCredits,
783
- credits_unlimited: snapshot.creditsUnlimited,
784
- subscription: toReportedOpenAiSubscription(snapshot)
785
- }),
786
- signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
787
- });
788
- if (!response.ok) {
789
- const serverMessage = await readErrorMessage(response);
790
- return {
791
- ok: false,
792
- error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
793
- };
794
- }
795
- return { ok: true };
796
- } catch (error2) {
797
- return { ok: false, error: describeBestEffortError(error2) };
798
- }
742
+ return postBestEffort(`/runners/${agentId}/openai-usage`, authHeader, {
743
+ primary: toReportedOpenAiWindow(snapshot.primary),
744
+ secondary: toReportedOpenAiWindow(snapshot.secondary),
745
+ has_credits: snapshot.hasCredits,
746
+ credits_unlimited: snapshot.creditsUnlimited,
747
+ subscription: toReportedSubscription(snapshot.subscription)
748
+ });
799
749
  }
800
750
  async function reportResourceUsage(agentId, authHeader, usage) {
801
- try {
802
- const apiUrl = getApiUrlConfig();
803
- const response = await fetch(`${apiUrl}/runners/${agentId}/resource-usage`, {
804
- method: "POST",
805
- headers: { Authorization: authHeader, "Content-Type": "application/json" },
806
- body: JSON.stringify({
807
- cpu_percent: usage.cpuPercent,
808
- cpu_peak_percent: usage.cpuPeakPercent,
809
- cpu_count: usage.cpuCount,
810
- memory_total_bytes: usage.memoryTotalBytes,
811
- memory_available_bytes: usage.memoryAvailableBytes,
812
- disk_total_bytes: usage.diskTotalBytes,
813
- disk_free_bytes: usage.diskFreeBytes,
814
- opencode_db_bytes: usage.opencodeDbBytes
815
- }),
816
- signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
817
- });
818
- if (!response.ok) {
819
- const serverMessage = await readErrorMessage(response);
820
- return {
821
- ok: false,
822
- error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
823
- };
824
- }
825
- return { ok: true };
826
- } catch (error2) {
827
- return { ok: false, error: describeBestEffortError(error2) };
828
- }
751
+ return postBestEffort(`/runners/${agentId}/resource-usage`, authHeader, {
752
+ cpu_percent: usage.cpuPercent,
753
+ cpu_peak_percent: usage.cpuPeakPercent,
754
+ cpu_count: usage.cpuCount,
755
+ memory_total_bytes: usage.memoryTotalBytes,
756
+ memory_available_bytes: usage.memoryAvailableBytes,
757
+ disk_total_bytes: usage.diskTotalBytes,
758
+ disk_free_bytes: usage.diskFreeBytes,
759
+ opencode_db_bytes: usage.opencodeDbBytes
760
+ });
829
761
  }
830
762
  async function getAgentInfo(agentId, authHeader) {
831
763
  const apiUrl = getApiUrlConfig();
@@ -877,13 +809,6 @@ function authLabelFor(credentials2) {
877
809
  }
878
810
  return "user token";
879
811
  }
880
- function describeFetchError(error2) {
881
- const name = error2?.name;
882
- if (name === "TimeoutError" || name === "AbortError") {
883
- return `timed out after ${STATUS_TIMEOUT_MS}ms waiting for a response`;
884
- }
885
- return error2 instanceof Error ? error2.message : String(error2);
886
- }
887
812
  async function checkStatus(jsonMode) {
888
813
  const apiUrl = getApiUrlConfig();
889
814
  const credentials2 = await getAuthCredentials();
@@ -911,7 +836,7 @@ async function checkStatus(jsonMode) {
911
836
  endpoint: apiUrl,
912
837
  authLabel: authLabelFor(credentials2),
913
838
  reason: "unreachable",
914
- error: `Could not reach ${apiUrl}: ${describeFetchError(error2)}. The credentials were NOT validated.`,
839
+ error: `Could not reach ${apiUrl}: ${describeTimeoutError(error2, STATUS_TIMEOUT_MS)}. The credentials were NOT validated.`,
915
840
  exitCode: 75
916
841
  };
917
842
  }
@@ -1009,10 +934,10 @@ async function status(options = {}) {
1009
934
  }
1010
935
 
1011
936
  // 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";
937
+ import { execFileSync } from "node:child_process";
938
+ import { readFileSync } from "node:fs";
939
+ import { homedir } from "node:os";
940
+ import { join } from "node:path";
1016
941
  var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
1017
942
  var CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
1018
943
  var CLAUDE_PROFILE_TIMEOUT_MS = 2e3;
@@ -1097,7 +1022,7 @@ function ownerLookupFailure(error2) {
1097
1022
  }
1098
1023
  async function getClaudeUsageOwner(accessToken) {
1099
1024
  if (cachedOwner?.accessToken === accessToken) {
1100
- return { owner: cachedOwner.owner, ownerLookupError: null };
1025
+ return { subscription: cachedOwner.owner, ownerLookupError: null };
1101
1026
  }
1102
1027
  try {
1103
1028
  const response = await fetch(CLAUDE_PROFILE_URL, {
@@ -1109,27 +1034,27 @@ async function getClaudeUsageOwner(accessToken) {
1109
1034
  signal: AbortSignal.timeout(CLAUDE_PROFILE_TIMEOUT_MS)
1110
1035
  });
1111
1036
  if (!response.ok) {
1112
- return { owner: null, ownerLookupError: `HTTP ${response.status}` };
1037
+ return { subscription: null, ownerLookupError: `HTTP ${response.status}` };
1113
1038
  }
1114
1039
  let body;
1115
1040
  try {
1116
1041
  body = await response.json();
1117
1042
  } catch (error2) {
1118
- return { owner: null, ownerLookupError: "malformed response" };
1043
+ return { subscription: null, ownerLookupError: "malformed response" };
1119
1044
  }
1120
1045
  const profile = body;
1121
1046
  if (typeof profile.account?.email !== "string" || !profile.account.email.trim()) {
1122
- return { owner: null, ownerLookupError: "malformed response" };
1047
+ return { subscription: null, ownerLookupError: "malformed response" };
1123
1048
  }
1124
- const owner = {
1125
- email: profile.account.email,
1049
+ const subscription = {
1050
+ ownerEmail: profile.account.email,
1126
1051
  organizationName: typeof profile.organization?.name === "string" && profile.organization.name.trim() ? profile.organization.name.trim() : null,
1127
- rateLimitTier: typeof profile.organization?.rate_limit_tier === "string" && profile.organization.rate_limit_tier.trim() ? profile.organization.rate_limit_tier.trim() : null
1052
+ planType: typeof profile.organization?.rate_limit_tier === "string" && profile.organization.rate_limit_tier.trim() ? profile.organization.rate_limit_tier.trim() : null
1128
1053
  };
1129
- cachedOwner = { accessToken, owner };
1130
- return { owner, ownerLookupError: null };
1054
+ cachedOwner = { accessToken, owner: subscription };
1055
+ return { subscription, ownerLookupError: null };
1131
1056
  } catch (error2) {
1132
- return { owner: null, ownerLookupError: ownerLookupFailure(error2) };
1057
+ return { subscription: null, ownerLookupError: ownerLookupFailure(error2) };
1133
1058
  }
1134
1059
  }
1135
1060
  async function getClaudeUsage() {
@@ -1158,11 +1083,11 @@ async function getClaudeUsage() {
1158
1083
  throw new ClaudeUsageError(`Claude usage request failed: HTTP ${res.status}`, "request_failed");
1159
1084
  }
1160
1085
  const body = await res.json();
1161
- const { owner, ownerLookupError } = await getClaudeUsageOwner(credentials2.accessToken);
1086
+ const { subscription, ownerLookupError } = await getClaudeUsageOwner(credentials2.accessToken);
1162
1087
  return {
1163
1088
  fiveHour: toWindow(body.five_hour),
1164
1089
  sevenDay: toWindow(body.seven_day),
1165
- owner,
1090
+ subscription,
1166
1091
  ownerLookupError
1167
1092
  };
1168
1093
  }
@@ -1192,10 +1117,10 @@ async function claudeUsage() {
1192
1117
  }
1193
1118
 
1194
1119
  // 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";
1120
+ import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
1121
+ import { homedir as homedir6 } from "node:os";
1122
+ import { isAbsolute as isAbsolute3, join as join10, parse, resolve as resolvePath2 } from "node:path";
1123
+ import chalk7 from "chalk";
1199
1124
 
1200
1125
  // ../../packages/types/src/agents/index.ts
1201
1126
  var MICROVM_MAX_LIFETIME_MS = 8 * 60 * 6e4;
@@ -1254,7 +1179,7 @@ function stripQuery(url) {
1254
1179
 
1255
1180
  // src/commands/run.ts
1256
1181
  import ora3 from "ora";
1257
- import { select as select3 } from "@inquirer/prompts";
1182
+ import { select as select4 } from "@inquirer/prompts";
1258
1183
 
1259
1184
  // src/lib/telemetry.ts
1260
1185
  var CLI_VERSION = (true ? "3.0.0" : void 0) ?? process.env.npm_package_version ?? "unknown";
@@ -1427,12 +1352,50 @@ var SEVERITY_BY_LEVEL = {
1427
1352
  warn: "warning",
1428
1353
  error: "error"
1429
1354
  };
1355
+ function parseOpenCodeLogLine(line) {
1356
+ const normalisedLine = line.replace(/\r$/, "");
1357
+ const levelMatch = normalisedLine.match(/(?:^|\s)level=(\w+)/i);
1358
+ if (!levelMatch) return null;
1359
+ const level = levelMatch[1].toUpperCase();
1360
+ if (level !== "WARN" && level !== "ERROR") return null;
1361
+ const sessionMatch = normalisedLine.match(/(?:^|\s)sessionID=(\S+)/);
1362
+ return { level: level === "WARN" ? "warn" : "error", sessionID: sessionMatch?.[1] };
1363
+ }
1364
+ var MAX_LINE_BUFFER_BYTES = 16 * 1024;
1365
+ function createOpenCodeActivityForwarder(getContext) {
1366
+ let buffer = Buffer.alloc(0);
1367
+ const flushLine = (line) => {
1368
+ const parsed = parseOpenCodeLogLine(line);
1369
+ if (!parsed) return;
1370
+ forwardRunnerActivity(
1371
+ {
1372
+ level: parsed.level,
1373
+ error: line,
1374
+ metadata: parsed.sessionID ? { sessionID: parsed.sessionID } : void 0,
1375
+ source: "opencode"
1376
+ },
1377
+ getContext()
1378
+ );
1379
+ };
1380
+ return (chunk) => {
1381
+ buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8")]);
1382
+ let newlineIndex;
1383
+ while ((newlineIndex = buffer.indexOf(10)) !== -1) {
1384
+ flushLine(buffer.subarray(0, newlineIndex).toString("utf-8").replace(/\r$/, ""));
1385
+ buffer = buffer.subarray(newlineIndex + 1);
1386
+ }
1387
+ if (buffer.length > MAX_LINE_BUFFER_BYTES) {
1388
+ flushLine(buffer.toString("utf-8"));
1389
+ buffer = Buffer.alloc(0);
1390
+ }
1391
+ };
1392
+ }
1430
1393
  var MAX_MESSAGE_LENGTH = 500;
1431
1394
  var MAX_METADATA_VALUE_LENGTH = 200;
1432
1395
  var MAX_METADATA_ENTRIES = 20;
1433
1396
  var TRUNCATION_MARKER = "\u2026";
1434
1397
  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>");
1398
+ 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
1399
  }
1437
1400
  function truncate(message) {
1438
1401
  if (message.length <= MAX_MESSAGE_LENGTH) return message;
@@ -1458,43 +1421,47 @@ function sanitiseMetadata(metadata) {
1458
1421
  }
1459
1422
  var RATE_LIMIT_WINDOW_MS = 6e4;
1460
1423
  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) {
1424
+ var rateWindows = /* @__PURE__ */ new Map();
1425
+ function admitUnderRateLimit(source, now) {
1426
+ let window = rateWindows.get(source);
1427
+ if (!window) {
1428
+ window = { windowStartedAt: 0, windowCount: 0, windowDroppedCount: 0 };
1429
+ rateWindows.set(source, window);
1430
+ }
1431
+ if (now - window.windowStartedAt >= RATE_LIMIT_WINDOW_MS) {
1432
+ if (window.windowDroppedCount > 0) {
1467
1433
  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)`
1434
+ `[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
1435
  );
1470
1436
  }
1471
- windowStartedAt = now;
1472
- windowCount = 0;
1473
- windowDroppedCount = 0;
1437
+ window.windowStartedAt = now;
1438
+ window.windowCount = 0;
1439
+ window.windowDroppedCount = 0;
1474
1440
  }
1475
- if (windowCount >= RATE_LIMIT_MAX_EVENTS) {
1476
- windowDroppedCount++;
1477
- if (windowDroppedCount === 1) {
1441
+ if (window.windowCount >= RATE_LIMIT_MAX_EVENTS) {
1442
+ window.windowDroppedCount++;
1443
+ if (window.windowDroppedCount === 1) {
1478
1444
  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`
1445
+ `[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
1446
  );
1481
1447
  }
1482
1448
  return false;
1483
1449
  }
1484
- windowCount++;
1450
+ window.windowCount++;
1485
1451
  return true;
1486
1452
  }
1487
1453
  function forwardRunnerActivity(entry, context) {
1488
1454
  try {
1489
1455
  if (!FORWARDED_LEVELS.has(entry.level)) return;
1490
1456
  if (!context.agentId || !context.authHeader) return;
1491
- if (!admitUnderRateLimit(Date.now())) return;
1457
+ const source = entry.source ?? "cli.run";
1458
+ if (!admitUnderRateLimit(source, Date.now())) return;
1492
1459
  const rawMessage = entry.error ?? entry.message ?? "";
1493
1460
  const message = truncate(redact(rawMessage));
1494
1461
  logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
1495
1462
  severity: SEVERITY_BY_LEVEL[entry.level],
1496
1463
  message,
1497
- metadata: { ...sanitiseMetadata(entry.metadata), source: "cli.run" },
1464
+ metadata: { ...sanitiseMetadata(entry.metadata), source },
1498
1465
  agentId: context.agentId
1499
1466
  });
1500
1467
  } catch (err) {
@@ -1505,8 +1472,8 @@ function forwardRunnerActivity(entry, context) {
1505
1472
  }
1506
1473
 
1507
1474
  // src/lib/opencode/session-db-recovery-report.ts
1508
- import { readFileSync as readFileSync2, unlinkSync } from "fs";
1509
- import { join as join2 } from "path";
1475
+ import { readFileSync as readFileSync2, unlinkSync } from "node:fs";
1476
+ import { join as join2 } from "node:path";
1510
1477
  function sessionDbRecoveryReportPath(homeDir, env) {
1511
1478
  const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
1512
1479
  return override || join2(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
@@ -1719,13 +1686,13 @@ async function waitForOpenCodeHealth(port, timeoutMs = 3e4) {
1719
1686
  }
1720
1687
 
1721
1688
  // 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";
1689
+ import { spawn as spawn2 } from "node:child_process";
1690
+ import { mkdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
1691
+ import { homedir as homedir2 } from "node:os";
1692
+ import { dirname as dirname2, resolve as resolvePath } from "node:path";
1726
1693
 
1727
1694
  // src/lib/runner-synchroniser.ts
1728
- import { spawn } from "child_process";
1695
+ import { spawn } from "node:child_process";
1729
1696
  function appendError(stderr, error2) {
1730
1697
  const message = error2 instanceof Error ? error2.message : String(error2);
1731
1698
  return stderr === "" ? message : `${stderr}
@@ -2250,9 +2217,9 @@ async function restoreAndVerifySessionDb(options) {
2250
2217
  }
2251
2218
 
2252
2219
  // 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";
2220
+ import { createRequire } from "node:module";
2221
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
2222
+ import { dirname as dirname3, join as join3 } from "node:path";
2256
2223
  var require2 = createRequire(import.meta.url);
2257
2224
  function readSessionDbMigrationIds(dbPath) {
2258
2225
  let db;
@@ -2446,6 +2413,17 @@ async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
2446
2413
 
2447
2414
  // src/lib/opencode/process.ts
2448
2415
  var OPENCODE_PORT_RANGE = [4096, 4097, 4098, 4099, 4100];
2416
+ var VALID_OPENCODE_LOG_LEVELS = /* @__PURE__ */ new Set(["DEBUG", "INFO", "WARN", "ERROR"]);
2417
+ function resolveOpenCodeLogLevel(env) {
2418
+ const raw = env.OPENCODE_LOG_LEVEL;
2419
+ if (!raw) return "INFO";
2420
+ const upper = raw.toUpperCase();
2421
+ if (VALID_OPENCODE_LOG_LEVELS.has(upper)) return upper;
2422
+ console.warn(
2423
+ `startOpenCode: ignoring invalid OPENCODE_LOG_LEVEL "${raw}" (expected DEBUG|INFO|WARN|ERROR) \u2014 using INFO`
2424
+ );
2425
+ return "INFO";
2426
+ }
2449
2427
  function getProcessCwd(pid) {
2450
2428
  const platform = process.platform;
2451
2429
  try {
@@ -2494,14 +2472,14 @@ function findAvailablePort(startPort, maxAttempts = 10) {
2494
2472
  }
2495
2473
  return null;
2496
2474
  }
2497
- function findOpenCodeProcesses() {
2475
+ function findProcessesByPattern(pgrepPattern, psPattern) {
2498
2476
  const instances = [];
2499
2477
  try {
2500
2478
  const platform = process.platform;
2501
2479
  if (platform === "darwin" || platform === "linux") {
2502
2480
  let pids = [];
2503
2481
  try {
2504
- const pgrepOutput = execSync('pgrep -f "opencode serve|opencode-serve"', {
2482
+ const pgrepOutput = execSync(`pgrep -f "${pgrepPattern}"`, {
2505
2483
  encoding: "utf-8",
2506
2484
  stdio: ["pipe", "pipe", "pipe"]
2507
2485
  }).trim();
@@ -2510,7 +2488,7 @@ function findOpenCodeProcesses() {
2510
2488
  }
2511
2489
  } catch {
2512
2490
  try {
2513
- const psOutput = execSync('ps aux | grep -E "opencode (serve|--port)" | grep -v grep', {
2491
+ const psOutput = execSync(`ps aux | grep -E "${psPattern}" | grep -v grep`, {
2514
2492
  encoding: "utf-8",
2515
2493
  stdio: ["pipe", "pipe", "pipe"]
2516
2494
  }).trim();
@@ -2556,6 +2534,9 @@ function findOpenCodeProcesses() {
2556
2534
  }
2557
2535
  return instances;
2558
2536
  }
2537
+ function findOpenCodeProcesses() {
2538
+ return findProcessesByPattern("opencode serve|opencode-serve", "opencode (serve|--port)");
2539
+ }
2559
2540
  async function scanPortsForOpenCode() {
2560
2541
  const instances = [];
2561
2542
  const checks = OPENCODE_PORT_RANGE.map(async (port) => {
@@ -2602,7 +2583,7 @@ async function findHealthyOpenCodeInstances() {
2602
2583
  }
2603
2584
  async function startOpenCode(port, options = {}) {
2604
2585
  let command = "opencode";
2605
- const printLogs = options.inheritStdio ? ["--print-logs"] : [];
2586
+ const printLogs = options.inheritStdio ? ["--print-logs", "--log-level", resolveOpenCodeLogLevel(process.env)] : [];
2606
2587
  let args = ["serve", "--port", port.toString(), "--hostname", "127.0.0.1", ...printLogs];
2607
2588
  try {
2608
2589
  execSync("which opencode", { stdio: "ignore" });
@@ -2659,6 +2640,19 @@ function isOpenCodeInstalled() {
2659
2640
  return false;
2660
2641
  }
2661
2642
  }
2643
+ function isOpenCode2Installed() {
2644
+ try {
2645
+ const platform = process.platform;
2646
+ if (platform === "win32") {
2647
+ execSync2("where opencode2", { stdio: "ignore" });
2648
+ } else {
2649
+ execSync2("which opencode2", { stdio: "ignore" });
2650
+ }
2651
+ return true;
2652
+ } catch {
2653
+ return false;
2654
+ }
2655
+ }
2662
2656
  async function promptOpenCodeInstall(interactive) {
2663
2657
  if (!interactive) {
2664
2658
  console.log(
@@ -2668,7 +2662,11 @@ async function promptOpenCodeInstall(interactive) {
2668
2662
  install_url: OPENCODE_INSTALL_URL,
2669
2663
  install_commands: {
2670
2664
  npm: "npm install -g opencode-ai",
2671
- curl: "curl -fsSL https://opencode.ai/install.sh | sh"
2665
+ curl: "curl -fsSL https://opencode.ai/install.sh | sh",
2666
+ v2: {
2667
+ npm: "npm install -g @opencode-ai/cli@beta",
2668
+ curl: "curl -fsSL https://opencode.ai/v2/install | bash"
2669
+ }
2672
2670
  }
2673
2671
  })
2674
2672
  );
@@ -3190,21 +3188,74 @@ function collectSubagentSessions(messages, userMessageId) {
3190
3188
  }
3191
3189
  return refs;
3192
3190
  }
3193
- function messageUsage(messages, userMessageId) {
3194
- if (!messages || messages.length === 0) return null;
3195
- const byParentAll = messages.filter(
3196
- (m) => roleOf(m) === "assistant" && parentIdOf(m) === userMessageId
3191
+ function finiteNumber(value) {
3192
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
3193
+ }
3194
+ function taskCallModel(value) {
3195
+ if (!value || typeof value !== "object") return null;
3196
+ const model = value;
3197
+ const modelID = typeof model.modelID === "string" ? model.modelID : void 0;
3198
+ const providerID = typeof model.providerID === "string" ? model.providerID : void 0;
3199
+ return modelID || providerID ? { modelID, providerID } : null;
3200
+ }
3201
+ function collectTaskCalls(messages, userMessageId) {
3202
+ if (!messages || messages.length === 0) return [];
3203
+ const calls = [];
3204
+ for (const message of messages) {
3205
+ if (roleOf(message) !== "assistant" || parentIdOf(message) !== userMessageId) continue;
3206
+ for (const part of message.parts ?? []) {
3207
+ if (part.tool !== "task" || !part.callID || !part.state || part.state.status === "pending") {
3208
+ continue;
3209
+ }
3210
+ const rawName = part.state.input?.subagent_type;
3211
+ const subagentName = typeof rawName === "string" && rawName.trim().length > 0 ? rawName : rawName === void 0 ? "general" : "unknown";
3212
+ const metadata = part.state.metadata;
3213
+ calls.push({
3214
+ callID: part.callID,
3215
+ subagentName,
3216
+ childSessionId: typeof metadata?.sessionId === "string" ? metadata.sessionId : null,
3217
+ parentSessionId: typeof metadata?.parentSessionId === "string" ? metadata.parentSessionId : null,
3218
+ model: taskCallModel(metadata?.model),
3219
+ status: part.state.status ?? "unknown",
3220
+ timeStart: finiteNumber(part.state.time?.start),
3221
+ timeEnd: finiteNumber(part.state.time?.end)
3222
+ });
3223
+ }
3224
+ }
3225
+ return calls;
3226
+ }
3227
+ function attributeTaskCallUsage(messages, windows) {
3228
+ const eligibleWindows = windows.filter(
3229
+ (window) => window.timeStart !== null && Number.isFinite(window.timeStart)
3197
3230
  );
3198
- const byParentNonErrored = byParentAll.filter((m) => errorOf(m) == null);
3199
- const byParent = byParentNonErrored.length > 0 ? byParentNonErrored : byParentAll;
3200
- let correlated;
3201
- if (byParent.length > 0) {
3202
- correlated = byParent;
3203
- } else {
3204
- const reply = findAssistantReplyAfter(messages, userMessageId);
3205
- correlated = reply ? [reply] : [];
3231
+ const assignments = /* @__PURE__ */ new Map();
3232
+ for (const window of eligibleWindows) assignments.set(window.callID, []);
3233
+ const unattributed = [];
3234
+ for (const message of messages ?? []) {
3235
+ if (roleOf(message) !== "assistant") continue;
3236
+ const created = finiteNumber(createdOf(message));
3237
+ const matching = created === null ? [] : eligibleWindows.filter(
3238
+ (window) => window.timeStart <= created && (window.timeEnd === null || window.timeEnd === void 0 || created <= window.timeEnd)
3239
+ );
3240
+ if (matching.length === 0) {
3241
+ unattributed.push(message);
3242
+ continue;
3243
+ }
3244
+ matching.sort((a, b) => a.timeStart - b.timeStart);
3245
+ assignments.get(matching[0].callID)?.push(message);
3206
3246
  }
3207
- if (correlated.length === 0) return null;
3247
+ return {
3248
+ invocations: eligibleWindows.map((window) => {
3249
+ const assigned = assignments.get(window.callID) ?? [];
3250
+ return { callID: window.callID, messages: assigned, usage: sumAssistantUsage(assigned) };
3251
+ }),
3252
+ unattributed
3253
+ };
3254
+ }
3255
+ function sumAssistantUsage(messages) {
3256
+ if (!messages || messages.length === 0) return null;
3257
+ const nonErrored = messages.filter((message) => errorOf(message) == null);
3258
+ const selected = nonErrored.length > 0 ? nonErrored : messages;
3208
3259
  let sawAnyUsage = false;
3209
3260
  let inputSum = 0;
3210
3261
  let outputSum = 0;
@@ -3215,7 +3266,7 @@ function messageUsage(messages, userMessageId) {
3215
3266
  let sawCost = false;
3216
3267
  let modelId = null;
3217
3268
  let providerId = null;
3218
- for (const m of correlated) {
3269
+ for (const m of selected) {
3219
3270
  const info = m.info;
3220
3271
  if (!info) continue;
3221
3272
  const tokens = info.tokens;
@@ -3250,12 +3301,28 @@ function messageUsage(messages, userMessageId) {
3250
3301
  usage_tokens_reasoning: reasoningSum,
3251
3302
  usage_tokens_cache_read: cacheReadSum,
3252
3303
  usage_tokens_cache_write: cacheWriteSum,
3253
- // NULL means "OpenCode never reported a cost" (never inferred from
3254
- // tokens) distinct from a genuine 0-cost turn, which would set
3255
- // `sawCost` true with `costSum === 0`.
3304
+ // NULL means OpenCode never reported a cost; it is distinct from a genuine
3305
+ // zero-cost message, which sets `sawCost` with `costSum === 0`.
3256
3306
  usage_cost_usd: sawCost ? costSum : null
3257
3307
  };
3258
3308
  }
3309
+ function messageUsage(messages, userMessageId) {
3310
+ if (!messages || messages.length === 0) return null;
3311
+ const byParentAll = messages.filter(
3312
+ (m) => roleOf(m) === "assistant" && parentIdOf(m) === userMessageId
3313
+ );
3314
+ const byParentNonErrored = byParentAll.filter((m) => errorOf(m) == null);
3315
+ const byParent = byParentNonErrored.length > 0 ? byParentNonErrored : byParentAll;
3316
+ let correlated;
3317
+ if (byParent.length > 0) {
3318
+ correlated = byParent;
3319
+ } else {
3320
+ const reply = findAssistantReplyAfter(messages, userMessageId);
3321
+ correlated = reply ? [reply] : [];
3322
+ }
3323
+ if (correlated.length === 0) return null;
3324
+ return sumAssistantUsage(correlated);
3325
+ }
3259
3326
  function messageRunState(messages, userMessageId) {
3260
3327
  if (!messages || messages.length === 0) return "unknown";
3261
3328
  const hasUser = messages.some((m) => idOf(m) === userMessageId);
@@ -3385,6 +3452,28 @@ function hasRunningAssistantExcept(messages, exceptUserMessageId) {
3385
3452
  (m) => roleOf(m) === "assistant" && parentIdOf(m) !== exceptUserMessageId && isAssistantInFlight(m)
3386
3453
  );
3387
3454
  }
3455
+ function hasLaterSiblingTurnStarted(messages, userMessageId, siblingUserMessageIds) {
3456
+ if (!messages || messages.length === 0) return false;
3457
+ const userIndex = messages.findIndex((message) => idOf(message) === userMessageId);
3458
+ if (userIndex === -1) return false;
3459
+ let hasLaterUser = false;
3460
+ let hasStartedLaterUser = false;
3461
+ for (let i = userIndex + 1; i < messages.length; i++) {
3462
+ const message = messages[i];
3463
+ if (roleOf(message) !== "user") continue;
3464
+ hasLaterUser = true;
3465
+ const laterUserMessageId = idOf(message);
3466
+ if (laterUserMessageId === void 0 || !siblingUserMessageIds.has(laterUserMessageId)) {
3467
+ return false;
3468
+ }
3469
+ if (messages.some(
3470
+ (candidate) => roleOf(candidate) === "assistant" && parentIdOf(candidate) === laterUserMessageId
3471
+ )) {
3472
+ hasStartedLaterUser = true;
3473
+ }
3474
+ }
3475
+ return hasLaterUser && hasStartedLaterUser;
3476
+ }
3388
3477
  async function hasAnyConfiguredProvider(port) {
3389
3478
  try {
3390
3479
  const res = await timedFetch(`${opencodeBase(port)}/config/providers`);
@@ -3416,6 +3505,94 @@ async function hasAnyConfiguredProvider(port) {
3416
3505
  return null;
3417
3506
  }
3418
3507
  }
3508
+ function sessionErrorReason(error2) {
3509
+ const record = typeof error2 === "object" && error2 !== null ? error2 : null;
3510
+ const data = record?.data;
3511
+ const dataRecord = typeof data === "object" && data !== null ? data : null;
3512
+ const rawReason = typeof dataRecord?.message === "string" && dataRecord.message || typeof record?.message === "string" && record.message || typeof error2 === "string" && error2 || typeof record?.name === "string" && record.name || "OpenCode reported a session error with no details";
3513
+ const reason = rawReason.replace(/\s+/g, " ").trim().slice(0, 500);
3514
+ return reason || "OpenCode reported a session error with no details";
3515
+ }
3516
+ function parseSessionErrorFrame(data) {
3517
+ let parsed;
3518
+ try {
3519
+ parsed = JSON.parse(data);
3520
+ } catch (error2) {
3521
+ void error2;
3522
+ return null;
3523
+ }
3524
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
3525
+ const parsedRecord = parsed;
3526
+ const payload = parsedRecord.payload;
3527
+ const event = payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : parsedRecord;
3528
+ if (event.type !== "session.error") return null;
3529
+ const properties = event.properties;
3530
+ if (properties === null || typeof properties !== "object" || Array.isArray(properties)) {
3531
+ return null;
3532
+ }
3533
+ const propertiesRecord = properties;
3534
+ const sessionId = propertiesRecord.sessionID;
3535
+ if (typeof sessionId !== "string" || sessionId.length === 0) return null;
3536
+ return {
3537
+ sessionId,
3538
+ reason: sessionErrorReason(propertiesRecord.error)
3539
+ };
3540
+ }
3541
+ async function readSessionErrorStream(port, options) {
3542
+ let reader = null;
3543
+ try {
3544
+ const response = await fetch(`${opencodeBase(port)}/event`, {
3545
+ headers: { accept: "text/event-stream" },
3546
+ signal: options.signal
3547
+ });
3548
+ if (!response.ok || !response.body) {
3549
+ return { reason: "unavailable", detail: `HTTP ${response.status}` };
3550
+ }
3551
+ reader = response.body.getReader();
3552
+ const decoder = new TextDecoder();
3553
+ let buffer = "";
3554
+ const processLine = (line) => {
3555
+ const trimmed = line.trimEnd();
3556
+ if (!trimmed.startsWith("data:")) return;
3557
+ const event = parseSessionErrorFrame(trimmed.slice("data:".length).replace(/^ /, ""));
3558
+ if (event) options.onSessionError(event);
3559
+ };
3560
+ while (true) {
3561
+ const { done, value } = await reader.read();
3562
+ if (done) return { reason: "ended" };
3563
+ buffer += decoder.decode(value, { stream: true });
3564
+ const lines = buffer.split("\n");
3565
+ buffer = lines.pop() ?? "";
3566
+ for (const line of lines) processLine(line);
3567
+ }
3568
+ } catch (err) {
3569
+ if (options.signal.aborted) return { reason: "aborted" };
3570
+ return {
3571
+ reason: "unavailable",
3572
+ detail: err instanceof Error ? err.message : String(err)
3573
+ };
3574
+ } finally {
3575
+ if (reader) void reader.cancel().catch(() => void 0);
3576
+ }
3577
+ }
3578
+ async function reloadProviderCache(port) {
3579
+ try {
3580
+ const res = await timedFetch(`${opencodeBase(port)}/config`, {
3581
+ method: "PATCH",
3582
+ headers: { "Content-Type": "application/json" },
3583
+ body: JSON.stringify({})
3584
+ });
3585
+ if (!res.ok) {
3586
+ console.error(
3587
+ `[reloadProviderCache] PATCH /config returned HTTP ${res.status} (port ${port})`
3588
+ );
3589
+ }
3590
+ } catch (err) {
3591
+ console.error(
3592
+ `[reloadProviderCache] PATCH /config failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
3593
+ );
3594
+ }
3595
+ }
3419
3596
 
3420
3597
  // src/lib/opencode/session-cleanup.ts
3421
3598
  var DURATION_UNIT_MS = {
@@ -3522,8 +3699,8 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
3522
3699
  }
3523
3700
 
3524
3701
  // src/lib/opencode/session-db-size.ts
3525
- import { statSync as statSync3 } from "fs";
3526
- import { join as join4 } from "path";
3702
+ import { statSync as statSync3 } from "node:fs";
3703
+ import { join as join4 } from "node:path";
3527
3704
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
3528
3705
  function statSessionDbBytes(homeDir) {
3529
3706
  const dbPath = join4(homeDir, ".local", "share", "opencode", "opencode.db");
@@ -3553,9 +3730,96 @@ function buildSessionStoreSizeWarning(input) {
3553
3730
  return null;
3554
3731
  }
3555
3732
 
3733
+ // src/lib/opencode/log-tail.ts
3734
+ import { statSync as statSync4 } from "node:fs";
3735
+ import { homedir as homedir3 } from "node:os";
3736
+ import { join as join5 } from "node:path";
3737
+ import { open as open2, stat } from "node:fs/promises";
3738
+ var DEFAULT_POLL_INTERVAL_MS = 1e3;
3739
+ function resolveOpenCodeLogPath(homeDir = homedir3(), env = process.env) {
3740
+ const dataDir = env.XDG_DATA_HOME || join5(homeDir, ".local", "share");
3741
+ return join5(dataDir, "opencode", "log", "opencode.log");
3742
+ }
3743
+ function isEnoent(error2) {
3744
+ return error2?.code === "ENOENT";
3745
+ }
3746
+ function reportFailure(operation, logPath, error2) {
3747
+ console.error(
3748
+ `[opencode-log-tail] ${operation} failed for ${logPath}: ${error2 instanceof Error ? error2.message : String(error2)}`
3749
+ );
3750
+ }
3751
+ function tailOpenCodeLogFile(logPath, onChunk, opts = {}) {
3752
+ const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
3753
+ let offset = 0;
3754
+ let inode = null;
3755
+ let baselineReady = true;
3756
+ try {
3757
+ const initial = statSync4(logPath);
3758
+ offset = initial.size;
3759
+ inode = initial.ino;
3760
+ } catch (error2) {
3761
+ if (!isEnoent(error2)) {
3762
+ reportFailure("initial stat", logPath, error2);
3763
+ baselineReady = false;
3764
+ }
3765
+ }
3766
+ let polling = false;
3767
+ let stopped = false;
3768
+ const poll = async () => {
3769
+ if (polling || stopped) return;
3770
+ polling = true;
3771
+ try {
3772
+ let current;
3773
+ try {
3774
+ current = await stat(logPath);
3775
+ } catch (error2) {
3776
+ if (!isEnoent(error2)) reportFailure("stat", logPath, error2);
3777
+ return;
3778
+ }
3779
+ if (!baselineReady) {
3780
+ offset = current.size;
3781
+ inode = current.ino;
3782
+ baselineReady = true;
3783
+ return;
3784
+ }
3785
+ if (inode !== null && current.ino !== inode || current.size < offset) {
3786
+ offset = 0;
3787
+ }
3788
+ inode = current.ino;
3789
+ if (current.size === offset) return;
3790
+ const length = current.size - offset;
3791
+ const fh = await open2(logPath, "r");
3792
+ try {
3793
+ const buf = Buffer.alloc(length);
3794
+ const { bytesRead } = await fh.read(buf, 0, length, offset);
3795
+ offset += bytesRead;
3796
+ if (bytesRead > 0) onChunk(buf.subarray(0, bytesRead));
3797
+ } finally {
3798
+ await fh.close();
3799
+ }
3800
+ } catch (error2) {
3801
+ if (!isEnoent(error2)) reportFailure("poll", logPath, error2);
3802
+ } finally {
3803
+ polling = false;
3804
+ }
3805
+ };
3806
+ const interval = setInterval(() => void poll(), pollIntervalMs);
3807
+ void poll();
3808
+ return {
3809
+ stop: () => {
3810
+ stopped = true;
3811
+ clearInterval(interval);
3812
+ }
3813
+ };
3814
+ }
3815
+
3556
3816
  // src/lib/opencode/session-db-reclaim.ts
3557
- import { statSync as statSync4, statfsSync } from "fs";
3558
- import { dirname as dirname4 } from "path";
3817
+ import { statSync as statSync5, statfsSync } from "node:fs";
3818
+ import { dirname as dirname4 } from "node:path";
3819
+ function errorMessage(error2) {
3820
+ if (!(error2 instanceof Error)) return String(error2);
3821
+ return error2.cause instanceof Error ? error2.cause.message : error2.message;
3822
+ }
3559
3823
  function insufficientSpaceReason(dbPath, requiredBytes) {
3560
3824
  try {
3561
3825
  const fsStats = statfsSync(dirname4(dbPath));
@@ -3581,17 +3845,17 @@ async function probeReclaimAvailability(input) {
3581
3845
  const { dbPath, requiredBytes } = input;
3582
3846
  let sqlite;
3583
3847
  try {
3584
- sqlite = await import("sqlite");
3848
+ sqlite = await import("node:sqlite");
3585
3849
  } catch (err) {
3586
- console.warn(
3587
- `[probeReclaimAvailability] node:sqlite unavailable for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
3588
- );
3589
- return "sqlite-unavailable";
3850
+ const detail = `Node ${process.version}: ${errorMessage(err)}`;
3851
+ console.warn(`[probeReclaimAvailability] node:sqlite unavailable for ${dbPath}: ` + detail);
3852
+ return { reason: "sqlite-unavailable", detail };
3590
3853
  }
3591
3854
  let autoVacuum = null;
3592
3855
  try {
3593
3856
  const db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
3594
3857
  try {
3858
+ db.exec("PRAGMA busy_timeout=5000");
3595
3859
  autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
3596
3860
  } finally {
3597
3861
  db.close();
@@ -3602,23 +3866,25 @@ async function probeReclaimAvailability(input) {
3602
3866
  );
3603
3867
  }
3604
3868
  if (autoVacuum !== 0) return null;
3605
- return insufficientSpaceReason(dbPath, requiredBytes) !== null ? "insufficient-disk-space" : null;
3869
+ return insufficientSpaceReason(dbPath, requiredBytes) !== null ? { reason: "insufficient-disk-space" } : null;
3606
3870
  }
3607
3871
  async function reclaimSessionDbSpace(input) {
3608
3872
  const { dbPath, maxPages, allowFullVacuum = true } = input;
3609
3873
  let sqlite;
3610
3874
  try {
3611
- sqlite = await import("sqlite");
3875
+ sqlite = await import("node:sqlite");
3612
3876
  } catch (err) {
3877
+ const detail = `Node ${process.version}: ${errorMessage(err)}`;
3613
3878
  console.warn(
3614
- `[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
3879
+ `[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${detail}`
3615
3880
  );
3616
- return { ok: false, skipped: "sqlite-unavailable" };
3881
+ return { ok: false, skipped: "sqlite-unavailable", detail };
3617
3882
  }
3618
3883
  const { DatabaseSync } = sqlite;
3619
3884
  let db;
3620
3885
  try {
3621
3886
  db = new DatabaseSync(dbPath);
3887
+ db.exec("PRAGMA busy_timeout=5000");
3622
3888
  const autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
3623
3889
  if (autoVacuum === 0) {
3624
3890
  if (!allowFullVacuum) {
@@ -3627,7 +3893,7 @@ async function reclaimSessionDbSpace(input) {
3627
3893
  );
3628
3894
  return { ok: false, skipped: "full-vacuum-blocked" };
3629
3895
  }
3630
- const fileBytesForGuard = statSync4(dbPath).size;
3896
+ const fileBytesForGuard = statSync5(dbPath).size;
3631
3897
  const skipReason = insufficientSpaceReason(dbPath, fileBytesForGuard);
3632
3898
  if (skipReason !== null) {
3633
3899
  console.warn(
@@ -3655,10 +3921,12 @@ async function reclaimSessionDbSpace(input) {
3655
3921
  );
3656
3922
  return { ok: false, skipped: "auto-vacuum-not-applicable" };
3657
3923
  } 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" };
3924
+ console.error(`[reclaimSessionDbSpace] reclaim failed for ${dbPath}: ` + errorMessage(err));
3925
+ return {
3926
+ ok: false,
3927
+ skipped: "reclaim-error",
3928
+ detail: errorMessage(err)
3929
+ };
3662
3930
  } finally {
3663
3931
  db?.close();
3664
3932
  }
@@ -3950,8 +4218,8 @@ function connectTunnel(options) {
3950
4218
  try {
3951
4219
  message = JSON.parse(data.toString());
3952
4220
  } catch (error2) {
3953
- const errorMessage2 = error2 instanceof Error ? error2.message : "Unknown error";
3954
- onError?.(`Failed to handle message: ${errorMessage2}`);
4221
+ const errorMessage3 = error2 instanceof Error ? error2.message : "Unknown error";
4222
+ onError?.(`Failed to handle message: ${errorMessage3}`);
3955
4223
  return;
3956
4224
  }
3957
4225
  if (isStreamFrame(message)) {
@@ -4095,7 +4363,7 @@ var RunnerConnection = class {
4095
4363
  };
4096
4364
 
4097
4365
  // src/lib/tunnel/ready-marker.ts
4098
- import { writeFileSync as writeFileSync3 } from "fs";
4366
+ import { writeFileSync as writeFileSync3 } from "node:fs";
4099
4367
  function writeTunnelReadyMarker(path, agentId) {
4100
4368
  try {
4101
4369
  writeFileSync3(path, `${agentId}
@@ -4107,7 +4375,7 @@ function writeTunnelReadyMarker(path, agentId) {
4107
4375
  }
4108
4376
 
4109
4377
  // src/lib/replication.ts
4110
- import { spawn as spawn4 } from "child_process";
4378
+ import { spawn as spawn4 } from "node:child_process";
4111
4379
  function startSessionDbReplication(configPath) {
4112
4380
  return spawn4("litestream", ["replicate", "-config", configPath], {
4113
4381
  stdio: "inherit"
@@ -4123,7 +4391,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
4123
4391
  }
4124
4392
 
4125
4393
  // src/lib/process-liveness.ts
4126
- import { readFileSync as readFileSync4 } from "fs";
4394
+ import { readFileSync as readFileSync4 } from "node:fs";
4127
4395
  function isProcessAlive(pid) {
4128
4396
  try {
4129
4397
  process.kill(pid, 0);
@@ -4149,9 +4417,9 @@ function isProcessAlive(pid) {
4149
4417
  }
4150
4418
 
4151
4419
  // 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";
4420
+ import { readFileSync as readFileSync5 } from "node:fs";
4421
+ import { homedir as homedir4 } from "node:os";
4422
+ import { join as join6 } from "node:path";
4155
4423
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
4156
4424
  var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
4157
4425
  var OpenAiUsageError = class extends Error {
@@ -4165,7 +4433,7 @@ function isLocalCredentialProblem2(err) {
4165
4433
  }
4166
4434
  function readOpenCodeChatGptCredentials() {
4167
4435
  try {
4168
- const raw = readFileSync5(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4436
+ const raw = readFileSync5(join6(homedir4(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4169
4437
  let parsed;
4170
4438
  try {
4171
4439
  parsed = JSON.parse(raw);
@@ -4202,7 +4470,7 @@ function parseChatGptIdentity(accessToken) {
4202
4470
  const auth = payload["https://api.openai.com/auth"];
4203
4471
  const ownerEmail = typeof profile?.email === "string" ? profile.email.trim() || null : null;
4204
4472
  const planType = typeof auth?.chatgpt_plan_type === "string" ? auth.chatgpt_plan_type.trim() || null : null;
4205
- return ownerEmail === null && planType === null ? null : { ownerEmail, planType };
4473
+ return ownerEmail === null && planType === null ? null : { ownerEmail, planType, organizationName: null };
4206
4474
  }
4207
4475
  function toWindow2(headers, name) {
4208
4476
  const utilizationHeader = headers.get(`x-codex-${name}-used-percent`);
@@ -4384,13 +4652,6 @@ function resolveClaudeUsageReportingMode(flagValue, env) {
4384
4652
  envVar: "EVIDENT_CLAUDE_USAGE_REPORTING"
4385
4653
  });
4386
4654
  }
4387
- function nextReportDelayMs(random = Math.random) {
4388
- return usageReportDelayMs(random);
4389
- }
4390
- var FIRST_REPORT_DELAY_MS = firstReportDelayMs();
4391
- function claudeUsageFailureLogLevel(consecutiveFailures) {
4392
- return usageReportFailureLogLevel(consecutiveFailures);
4393
- }
4394
4655
 
4395
4656
  // src/lib/openai-usage-reporting.ts
4396
4657
  function resolveOpenAiUsageReportingMode(flagValue, env) {
@@ -4427,8 +4688,8 @@ function resolveResourceUsageReportingEnabled(flagValue, env) {
4427
4688
  }
4428
4689
 
4429
4690
  // src/lib/resource-usage.ts
4430
- import { cpus, totalmem, freemem } from "os";
4431
- import { statfsSync as statfsSync2 } from "fs";
4691
+ import { cpus, totalmem, freemem } from "node:os";
4692
+ import { statfsSync as statfsSync2 } from "node:fs";
4432
4693
 
4433
4694
  // src/lib/ecs-task-metadata.ts
4434
4695
  var ECS_METADATA_TIMEOUT_MS = 2e3;
@@ -4595,15 +4856,15 @@ function createResourceUsageCollector(homeDir) {
4595
4856
  }
4596
4857
 
4597
4858
  // src/lib/channels/driver.ts
4598
- import { homedir as homedir4 } from "os";
4859
+ import { homedir as homedir5 } from "node:os";
4599
4860
 
4600
4861
  // src/lib/runner-file-sync.ts
4601
- import { join as join7 } from "path";
4862
+ import { join as join8 } from "node:path";
4602
4863
 
4603
4864
  // 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";
4865
+ import { randomUUID } from "node:crypto";
4866
+ import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
4867
+ import { basename, dirname as dirname5, isAbsolute, join as join7, relative, resolve as resolve2, sep } from "node:path";
4607
4868
  var FILE_MODE = 384;
4608
4869
  var DIRECTORY_MODE = 448;
4609
4870
  async function writePushedFile(request) {
@@ -4636,7 +4897,7 @@ async function writePushedFile(request) {
4636
4897
  const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
4637
4898
  dirname5(candidate)
4638
4899
  );
4639
- const realTarget = join6(existingAncestor, ...missingSegments, basename(candidate));
4900
+ const realTarget = join7(existingAncestor, ...missingSegments, basename(candidate));
4640
4901
  const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
4641
4902
  if (allowedDirectory === null) {
4642
4903
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
@@ -4672,7 +4933,7 @@ function expandAndValidate(requestedPath, homeDir) {
4672
4933
  if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
4673
4934
  return null;
4674
4935
  }
4675
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
4936
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
4676
4937
  if (expanded.split(/[/\\]/).includes("..")) {
4677
4938
  return null;
4678
4939
  }
@@ -4745,16 +5006,16 @@ function contains(realDirectory, realTarget) {
4745
5006
  async function createMissingDirectories(existingAncestor, missingSegments) {
4746
5007
  let current = existingAncestor;
4747
5008
  for (const segment of missingSegments) {
4748
- current = join6(current, segment);
5009
+ current = join7(current, segment);
4749
5010
  await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
4750
5011
  await chmod(current, DIRECTORY_MODE);
4751
5012
  }
4752
5013
  }
4753
5014
  async function writeAtomically(realTarget, content) {
4754
- const temporaryPath = join6(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
5015
+ const temporaryPath = join7(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
4755
5016
  let handle;
4756
5017
  try {
4757
- handle = await open2(temporaryPath, "wx", FILE_MODE);
5018
+ handle = await open3(temporaryPath, "wx", FILE_MODE);
4758
5019
  await handle.writeFile(content);
4759
5020
  await handle.chmod(FILE_MODE);
4760
5021
  await handle.close();
@@ -4881,12 +5142,12 @@ var NOT_APPLIED = {
4881
5142
  opencodeAuthApplied: false
4882
5143
  };
4883
5144
  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);
5145
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
5146
+ return expanded === join8(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
4886
5147
  }
4887
5148
  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);
5149
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
5150
+ return expanded === join8(homeDir, ...OPENCODE_AUTH_SEGMENTS);
4890
5151
  }
4891
5152
  async function applyOne(options, file) {
4892
5153
  const label = `${file.id.slice(0, 8)} (${file.path})`;
@@ -5046,6 +5307,10 @@ var DEFAULT_RETRY_POLICY = {
5046
5307
  baseDelayMs: 500,
5047
5308
  maxDelayMs: 3e4
5048
5309
  };
5310
+ var SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS = 2e3;
5311
+ var SESSION_ERROR_STREAM_HEALTHY_MS = 5e3;
5312
+ var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
5313
+ var MAX_BUFFERED_SESSION_ERRORS = 256;
5049
5314
  var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
5050
5315
  var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
5051
5316
  var DEFAULT_STUCK_QUEUED_MS = 6e4;
@@ -5186,6 +5451,17 @@ var ChannelDriver = class _ChannelDriver {
5186
5451
  * message; it is removed once its in-flight set empties.
5187
5452
  */
5188
5453
  watchers = /* @__PURE__ */ new Map();
5454
+ sessionErrorStream = null;
5455
+ /**
5456
+ * Session-error failures currently being reported; entries are empty at rest
5457
+ * because each handoff deletes its id in `finally`.
5458
+ */
5459
+ sessionErrorHandled = /* @__PURE__ */ new Set();
5460
+ /**
5461
+ * Session errors that arrived before their dispatch was registered. Bounded FIFO
5462
+ * with a short TTL so an unmatched session cannot retain an event indefinitely.
5463
+ */
5464
+ bufferedSessionErrors = /* @__PURE__ */ new Map();
5189
5465
  /**
5190
5466
  * AUTHORITATIVE local dedup (WI-3): Evident message ids that have been
5191
5467
  * dispatched and are still in-flight. A message in this set is never
@@ -5369,6 +5645,13 @@ var ChannelDriver = class _ChannelDriver {
5369
5645
  * no watcher) can resolve the title.
5370
5646
  */
5371
5647
  sessionTitles = /* @__PURE__ */ new Map();
5648
+ /** One best-effort terminal subagent collection per Evident message id. */
5649
+ subagentInvocationCollections = /* @__PURE__ */ new Map();
5650
+ /**
5651
+ * Early snapshots are only liveness hints; they must not become the terminal
5652
+ * collection when the task parts or child transcript have advanced.
5653
+ */
5654
+ subagentInvocationPrefetches = /* @__PURE__ */ new Map();
5372
5655
  /** Serialises drains so a reconnect during a drain doesn't double-process. */
5373
5656
  draining = false;
5374
5657
  /**
@@ -5433,7 +5716,7 @@ var ChannelDriver = class _ChannelDriver {
5433
5716
  this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
5434
5717
  this.now = config.now ?? (() => Date.now());
5435
5718
  this.fileSyncDirectories = config.fileSyncDirectories ?? [];
5436
- this.homeDir = config.homeDir ?? homedir4();
5719
+ this.homeDir = config.homeDir ?? homedir5();
5437
5720
  this.maxActiveSessions = config.maxActiveSessions;
5438
5721
  this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
5439
5722
  this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
@@ -5583,6 +5866,21 @@ var ChannelDriver = class _ChannelDriver {
5583
5866
  }
5584
5867
  return ids;
5585
5868
  }
5869
+ /**
5870
+ * OpenCode user-message ids tracked for other Evident messages in a session.
5871
+ * Excluding this message makes an unattributed later row fail safe; a missing
5872
+ * watcher yields no attributions, per `hasLaterSiblingTurnStarted`'s docblock.
5873
+ */
5874
+ siblingOpencodeMessageIds(watcher, ownEvidentMessageId) {
5875
+ const ids = /* @__PURE__ */ new Set();
5876
+ if (!watcher) return ids;
5877
+ for (const inFlight of watcher.inFlight.values()) {
5878
+ if (inFlight.evidentMessageId !== ownEvidentMessageId) {
5879
+ ids.add(inFlight.opencodeMessageId);
5880
+ }
5881
+ }
5882
+ return ids;
5883
+ }
5586
5884
  /**
5587
5885
  * File-pull work, for `run.ts`'s idle accounting (#559).
5588
5886
  *
@@ -5649,6 +5947,8 @@ var ChannelDriver = class _ChannelDriver {
5649
5947
  */
5650
5948
  stop() {
5651
5949
  this.stopped = true;
5950
+ this.sessionErrorStream?.abort.abort();
5951
+ this.sessionErrorStream = null;
5652
5952
  }
5653
5953
  /**
5654
5954
  * The server clears this request when a new MicroVM identity is recorded, so a
@@ -5723,6 +6023,7 @@ var ChannelDriver = class _ChannelDriver {
5723
6023
  */
5724
6024
  async processConversation(conv) {
5725
6025
  const { sessionId, refusedSessionId, created: sessionCreated } = await this.ensureSession(conv);
6026
+ this.ensureSessionErrorStream();
5726
6027
  const messages = await this.getPendingMessages(conv.id);
5727
6028
  let dispatched = 0;
5728
6029
  let skippedAlreadyDispatched = 0;
@@ -5795,7 +6096,7 @@ var ChannelDriver = class _ChannelDriver {
5795
6096
  this.signalDispatchNotStarted(conv, message, "session_existence_unknown");
5796
6097
  break;
5797
6098
  }
5798
- const errorMessage2 = err instanceof Error ? err.message : String(err);
6099
+ const errorMessage3 = err instanceof Error ? err.message : String(err);
5799
6100
  this.sessions.delete(conv.id);
5800
6101
  this.supersede(conv.id, sessionId);
5801
6102
  this.log({
@@ -5804,7 +6105,7 @@ var ChannelDriver = class _ChannelDriver {
5804
6105
  conversation_id: conv.id,
5805
6106
  message_id: message.id
5806
6107
  });
5807
- await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
6108
+ await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
5808
6109
  this.log({
5809
6110
  level: "warn",
5810
6111
  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 +6116,7 @@ var ChannelDriver = class _ChannelDriver {
5815
6116
  });
5816
6117
  this.log({
5817
6118
  level: "error",
5818
- message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage2}`,
6119
+ message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage3}`,
5819
6120
  conversation_id: conv.id,
5820
6121
  message_id: message.id
5821
6122
  });
@@ -5836,14 +6137,14 @@ var ChannelDriver = class _ChannelDriver {
5836
6137
  this.unconfirmedDispatchFailures.delete(message.id);
5837
6138
  this.sessions.delete(conv.id);
5838
6139
  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.`;
6140
+ 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
6141
  this.log({
5841
6142
  level: "error",
5842
- message: errorMessage2,
6143
+ message: errorMessage3,
5843
6144
  conversation_id: conv.id,
5844
6145
  message_id: message.id
5845
6146
  });
5846
- await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
6147
+ await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
5847
6148
  this.log({
5848
6149
  level: "warn",
5849
6150
  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)}`,
@@ -6069,9 +6370,26 @@ var ChannelDriver = class _ChannelDriver {
6069
6370
  if (state === "running" || state === "queued") {
6070
6371
  const ongoing = await isSessionOngoing(this.port, sessionId);
6071
6372
  if (ongoing === true) {
6072
- return this.reattachRedrive(conv, sessionId, message, ocId);
6073
- }
6074
- if (ongoing === false) {
6373
+ if (state === "queued") {
6374
+ const siblingOcIds = this.siblingOpencodeMessageIds(
6375
+ this.watchers.get(sessionId),
6376
+ message.id
6377
+ );
6378
+ if (hasLaterSiblingTurnStarted(messages, ocId ?? "", siblingOcIds)) {
6379
+ this.log({
6380
+ level: "warn",
6381
+ message: `Re-drive: OpenCode already served a later, different Evident message's turn in session ${sessionId.slice(0, 8)} while message ${message.id.slice(0, 8)} produced no reply \u2014 re-dispatching instead of reattaching to someone else's turn`,
6382
+ conversation_id: conv.id,
6383
+ message_id: message.id
6384
+ });
6385
+ this.clearRedriveUnresolved(message.id);
6386
+ void this.postSignal(conv.id, message.id, "redrive_redispatched");
6387
+ return "dispatch";
6388
+ }
6389
+ }
6390
+ return this.reattachRedrive(conv, sessionId, message, ocId);
6391
+ }
6392
+ if (ongoing === false) {
6075
6393
  if (state === "running" && isAmbiguousFinishPinnedRunning(messages, ocId ?? "")) {
6076
6394
  return this.settleRedrive(conv, sessionId, message, ocId, messages, "done");
6077
6395
  }
@@ -6158,16 +6476,37 @@ var ChannelDriver = class _ChannelDriver {
6158
6476
  if (state === "done") {
6159
6477
  const title = await this.resolveSessionTitle(sessionId, conv.id);
6160
6478
  const usage = messageUsage(messages, ocId ?? "");
6479
+ const usageAgentName = this.usageAgentName(messages, ocId ?? "");
6480
+ const subagentInvocations = await this.resolveSubagentInvocations(
6481
+ messages,
6482
+ ocId ?? "",
6483
+ message.id
6484
+ );
6161
6485
  this.log({
6162
6486
  level: "info",
6163
6487
  message: `Re-drive: message ${message.id.slice(0, 8)} completed while its row was wrongly reclaimed to pending \u2014 marking done instead of re-dispatching`,
6164
6488
  conversation_id: conv.id,
6165
6489
  message_id: message.id
6166
6490
  });
6167
- await this.markDone(conv.id, message.id, sessionId, ocId, title, usage);
6491
+ await this.markDone(
6492
+ conv.id,
6493
+ message.id,
6494
+ sessionId,
6495
+ ocId,
6496
+ title,
6497
+ usage,
6498
+ usageAgentName,
6499
+ subagentInvocations
6500
+ );
6168
6501
  } else {
6169
6502
  const error2 = messageError(messages, ocId ?? "") ?? void 0;
6170
6503
  const usage = messageUsage(messages, ocId ?? "");
6504
+ const usageAgentName = this.usageAgentName(messages, ocId ?? "");
6505
+ const subagentInvocations = await this.resolveSubagentInvocations(
6506
+ messages,
6507
+ ocId ?? "",
6508
+ message.id
6509
+ );
6171
6510
  const failure = await this.classifyModelAuthFailure(messages, ocId ?? "");
6172
6511
  this.log({
6173
6512
  level: "error",
@@ -6175,7 +6514,16 @@ var ChannelDriver = class _ChannelDriver {
6175
6514
  conversation_id: conv.id,
6176
6515
  message_id: message.id
6177
6516
  });
6178
- await this.markFailed(conv.id, message.id, sessionId, error2, usage, failure);
6517
+ await this.markFailed(
6518
+ conv.id,
6519
+ message.id,
6520
+ sessionId,
6521
+ error2,
6522
+ usage,
6523
+ failure,
6524
+ usageAgentName,
6525
+ subagentInvocations
6526
+ );
6179
6527
  }
6180
6528
  if (ocId !== null) {
6181
6529
  await this.reportSubagentAuthFailures(conv.id, ocId, message.id, messages);
@@ -6719,6 +7067,24 @@ var ChannelDriver = class _ChannelDriver {
6719
7067
  ambiguousPinnedSinceMs: 0,
6720
7068
  ambiguousResolved: false
6721
7069
  });
7070
+ const buffered = this.bufferedSessionErrors.get(sessionId);
7071
+ if (!buffered) return;
7072
+ this.bufferedSessionErrors.delete(sessionId);
7073
+ if (this.now() - buffered.receivedAt < SESSION_ERROR_BUFFER_TTL_MS) {
7074
+ this.handleSessionError(buffered.event);
7075
+ }
7076
+ }
7077
+ bufferSessionError(event) {
7078
+ this.bufferedSessionErrors.delete(event.sessionId);
7079
+ this.bufferedSessionErrors.set(event.sessionId, {
7080
+ event,
7081
+ receivedAt: this.now()
7082
+ });
7083
+ while (this.bufferedSessionErrors.size > MAX_BUFFERED_SESSION_ERRORS) {
7084
+ const oldest = this.bufferedSessionErrors.keys().next().value;
7085
+ if (typeof oldest !== "string") break;
7086
+ this.bufferedSessionErrors.delete(oldest);
7087
+ }
6722
7088
  }
6723
7089
  /**
6724
7090
  * Build a fresh `SessionWatcher`. Seeds `lastTickAt`/`lastObservedTickAt`
@@ -6923,6 +7289,7 @@ var ChannelDriver = class _ChannelDriver {
6923
7289
  ensureWatcherRunning(sessionId) {
6924
7290
  const watcher = this.watchers.get(sessionId);
6925
7291
  if (!watcher) return;
7292
+ this.ensureSessionErrorStream();
6926
7293
  if (watcher.loop) return;
6927
7294
  if (watcher.inFlight.size === 0) {
6928
7295
  this.watchers.delete(sessionId);
@@ -6938,6 +7305,154 @@ var ChannelDriver = class _ChannelDriver {
6938
7305
  });
6939
7306
  watcher.loop = loop;
6940
7307
  }
7308
+ ensureSessionErrorStream() {
7309
+ if (this.sessionErrorStream || this.stopped) return;
7310
+ const abort = new AbortController();
7311
+ const loop = this.runSessionErrorStream(abort.signal);
7312
+ this.sessionErrorStream = { abort, loop };
7313
+ }
7314
+ async runSessionErrorStream(signal) {
7315
+ let attempt = 0;
7316
+ let warned = false;
7317
+ while (!this.stopped && !signal.aborted) {
7318
+ const openedAt = this.now();
7319
+ try {
7320
+ const outcome = await readSessionErrorStream(this.port, {
7321
+ signal,
7322
+ onSessionError: (event) => this.handleSessionError(event)
7323
+ });
7324
+ if (outcome.reason === "aborted" || signal.aborted) return;
7325
+ const healthy = this.now() - openedAt >= SESSION_ERROR_STREAM_HEALTHY_MS;
7326
+ if (outcome.reason === "unavailable" || outcome.reason === "ended") {
7327
+ if (!healthy) {
7328
+ const detail = outcome.reason === "unavailable" ? outcome.detail : "stream ended";
7329
+ this.log({
7330
+ level: warned ? "debug" : "warn",
7331
+ message: `OpenCode session-error stream ${warned ? "still unavailable" : "unavailable"} (${detail}); transcript polling remains the evidence path`
7332
+ });
7333
+ warned = true;
7334
+ }
7335
+ }
7336
+ if (healthy) {
7337
+ if (warned) {
7338
+ this.log({
7339
+ level: "info",
7340
+ message: "OpenCode session-error stream reconnected; transcript polling remains the evidence path"
7341
+ });
7342
+ warned = false;
7343
+ }
7344
+ attempt = 0;
7345
+ } else {
7346
+ attempt += 1;
7347
+ }
7348
+ if (this.stopped || signal.aborted) return;
7349
+ await this.sleep(backoffDelay(healthy ? 0 : attempt - 1, this.retry));
7350
+ } catch (err) {
7351
+ if (this.stopped || signal.aborted) return;
7352
+ this.log({
7353
+ level: "error",
7354
+ message: `OpenCode session-error stream failed unexpectedly: ${err instanceof Error ? err.message : String(err)}`
7355
+ });
7356
+ const healthy = this.now() - openedAt >= SESSION_ERROR_STREAM_HEALTHY_MS;
7357
+ const delayAttempt = healthy ? 0 : attempt;
7358
+ attempt = healthy ? 0 : attempt + 1;
7359
+ try {
7360
+ await this.sleep(backoffDelay(delayAttempt, this.retry));
7361
+ } catch (sleepErr) {
7362
+ this.log({
7363
+ level: "error",
7364
+ message: `OpenCode session-error stream backoff failed unexpectedly: ${sleepErr instanceof Error ? sleepErr.message : String(sleepErr)}`
7365
+ });
7366
+ }
7367
+ }
7368
+ }
7369
+ }
7370
+ handleSessionError(event) {
7371
+ try {
7372
+ const watcher = this.watchers.get(event.sessionId);
7373
+ if (!watcher) {
7374
+ this.bufferSessionError(event);
7375
+ this.log({
7376
+ level: "debug",
7377
+ message: `Ignoring session error for unknown session ${event.sessionId.slice(0, 8)}`
7378
+ });
7379
+ return;
7380
+ }
7381
+ if ([...watcher.inFlight.values()].some((message) => message.started && !message.done)) {
7382
+ this.log({
7383
+ level: "debug",
7384
+ message: `A turn is already running in session ${event.sessionId.slice(0, 8)} \u2014 deferring to transcript polling`,
7385
+ conversation_id: watcher.conv.id
7386
+ });
7387
+ return;
7388
+ }
7389
+ const inFlight = [...watcher.inFlight.values()].filter((message) => !message.started && !message.done).sort((a, b) => a.dispatchedAt - b.dispatchedAt)[0];
7390
+ if (!inFlight) {
7391
+ this.bufferSessionError(event);
7392
+ this.log({
7393
+ level: "debug",
7394
+ message: `No queued in-flight turn to correlate with session error in ${event.sessionId.slice(0, 8)}`,
7395
+ conversation_id: watcher.conv.id
7396
+ });
7397
+ return;
7398
+ }
7399
+ if (this.sessionErrorHandled.has(inFlight.evidentMessageId)) return;
7400
+ this.sessionErrorHandled.add(inFlight.evidentMessageId);
7401
+ void this.failFromSessionError(watcher, event, inFlight);
7402
+ } catch (err) {
7403
+ this.log({
7404
+ level: "error",
7405
+ message: `Failed to handle OpenCode session error for ${event.sessionId.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`
7406
+ });
7407
+ }
7408
+ }
7409
+ async failFromSessionError(watcher, event, inFlight) {
7410
+ try {
7411
+ const messages = await getSessionMessages(this.port, event.sessionId);
7412
+ const state = messageRunState(messages, inFlight.opencodeMessageId);
7413
+ if (state !== "queued") {
7414
+ this.log({
7415
+ level: "debug",
7416
+ message: `Session error for message ${inFlight.evidentMessageId.slice(0, 8)} observed state ${state}; leaving it to transcript polling`,
7417
+ conversation_id: watcher.conv.id,
7418
+ message_id: inFlight.evidentMessageId
7419
+ });
7420
+ return;
7421
+ }
7422
+ this.log({
7423
+ level: "error",
7424
+ message: `OpenCode could not run message ${inFlight.evidentMessageId.slice(0, 8)} in session ${event.sessionId.slice(0, 8)}: ${event.reason}`,
7425
+ conversation_id: watcher.conv.id,
7426
+ message_id: inFlight.evidentMessageId
7427
+ });
7428
+ await this.markFailed(
7429
+ watcher.conv.id,
7430
+ inFlight.evidentMessageId,
7431
+ event.sessionId,
7432
+ `OpenCode could not run this turn: ${event.reason}`
7433
+ );
7434
+ inFlight.done = true;
7435
+ this.removeInFlight(watcher, inFlight.evidentMessageId);
7436
+ } catch (err) {
7437
+ if (err instanceof ChannelAuthError) {
7438
+ this.log({
7439
+ level: "warn",
7440
+ message: `OpenCode session error could not mark message ${inFlight.evidentMessageId.slice(0, 8)} failed because authentication failed: ${err.message}; leaving it to transcript polling / the existing give-up path`,
7441
+ conversation_id: watcher.conv.id,
7442
+ message_id: inFlight.evidentMessageId
7443
+ });
7444
+ } else {
7445
+ this.log({
7446
+ level: "warn",
7447
+ message: `OpenCode session error could not mark message ${inFlight.evidentMessageId.slice(0, 8)} failed: ${err instanceof Error ? err.message : String(err)}; leaving it to transcript polling / the existing give-up path`,
7448
+ conversation_id: watcher.conv.id,
7449
+ message_id: inFlight.evidentMessageId
7450
+ });
7451
+ }
7452
+ } finally {
7453
+ this.sessionErrorHandled.delete(inFlight.evidentMessageId);
7454
+ }
7455
+ }
6941
7456
  /**
6942
7457
  * The per-session polling loop (WI-3). Once per tick it:
6943
7458
  * 1. polls `GET /session/:id/message` once and, per in-flight message,
@@ -7050,6 +7565,21 @@ var ChannelDriver = class _ChannelDriver {
7050
7565
  const conv = watcher.conv;
7051
7566
  const state = messageRunState(messages, inFlight.opencodeMessageId);
7052
7567
  const id = inFlight.evidentMessageId;
7568
+ if (messages && collectTaskCalls(messages, inFlight.opencodeMessageId).length > 0 && !this.subagentInvocationPrefetches.has(id)) {
7569
+ void this.resolveSubagentInvocations(
7570
+ messages,
7571
+ inFlight.opencodeMessageId,
7572
+ id,
7573
+ "prefetch"
7574
+ ).catch((err) => {
7575
+ this.log({
7576
+ level: "warn",
7577
+ message: `Best-effort subagent usage prefetch failed for message ${id.slice(0, 8)} \u2014 omitting invocation telemetry: ${err instanceof Error ? err.message : String(err)}`,
7578
+ conversation_id: conv.id,
7579
+ message_id: id
7580
+ });
7581
+ });
7582
+ }
7053
7583
  if (openQuestions.has(id)) inFlight.pausedOnQuestion = true;
7054
7584
  else if (questionsPolledOk) inFlight.pausedOnQuestion = false;
7055
7585
  if (openPermissions.has(id)) inFlight.pausedOnPermission = true;
@@ -7103,6 +7633,12 @@ var ChannelDriver = class _ChannelDriver {
7103
7633
  message_id: inFlight.evidentMessageId
7104
7634
  });
7105
7635
  const usage = messageUsage(messages, inFlight.opencodeMessageId);
7636
+ const usageAgentName = this.usageAgentName(messages ?? [], inFlight.opencodeMessageId);
7637
+ const subagentInvocations = await this.resolveSubagentInvocations(
7638
+ messages,
7639
+ inFlight.opencodeMessageId,
7640
+ inFlight.evidentMessageId
7641
+ );
7106
7642
  const failure = await this.classifyModelAuthFailure(messages, inFlight.opencodeMessageId);
7107
7643
  try {
7108
7644
  await this.markFailed(
@@ -7111,7 +7647,9 @@ var ChannelDriver = class _ChannelDriver {
7111
7647
  sessionId,
7112
7648
  error2,
7113
7649
  usage,
7114
- failure
7650
+ failure,
7651
+ usageAgentName,
7652
+ subagentInvocations
7115
7653
  );
7116
7654
  } catch (err) {
7117
7655
  if (err instanceof ChannelAuthError) throw err;
@@ -7154,9 +7692,11 @@ var ChannelDriver = class _ChannelDriver {
7154
7692
  this.removeInFlight(watcher, inFlight.evidentMessageId);
7155
7693
  return;
7156
7694
  }
7695
+ const siblingOcIds = this.siblingOpencodeMessageIds(watcher, inFlight.evidentMessageId);
7696
+ const skippedByOpencode = state === "queued" && hasLaterSiblingTurnStarted(messages, inFlight.opencodeMessageId, siblingOcIds);
7157
7697
  const pastStuckBound = this.now() - inFlight.dispatchedAt >= this.stuckQueuedMs;
7158
7698
  const sessionIdle = state === "queued" && !hasRunningAssistantExcept(messages, inFlight.opencodeMessageId);
7159
- if (state === "queued" && pastStuckBound && sessionIdle && !inFlight.stuckReported) {
7699
+ if (state === "queued" && pastStuckBound && (sessionIdle || skippedByOpencode) && !inFlight.stuckReported) {
7160
7700
  inFlight.stuckReported = true;
7161
7701
  void this.postSignal(conv.id, inFlight.evidentMessageId, "stuck_queued", {
7162
7702
  stuck_for_ms: this.now() - inFlight.dispatchedAt
@@ -7317,7 +7857,7 @@ var ChannelDriver = class _ChannelDriver {
7317
7857
  const hasActivelyRunningSibling = [...watcher.inFlight.values()].some(
7318
7858
  (sib) => sib.evidentMessageId !== inFlight.evidentMessageId && messageRunState(messages, sib.opencodeMessageId) === "running" && !siblingPaused(sib)
7319
7859
  );
7320
- const queuedBehindRunningSibling = state === "queued" && hasActivelyRunningSibling;
7860
+ const queuedBehindRunningSibling = state === "queued" && hasActivelyRunningSibling && !skippedByOpencode;
7321
7861
  if (!activelyRunning && !queuedBehindRunningSibling && this.now() >= inFlight.deadline) {
7322
7862
  this.log({
7323
7863
  level: "debug",
@@ -7352,6 +7892,12 @@ var ChannelDriver = class _ChannelDriver {
7352
7892
  });
7353
7893
  const title = await this.resolveSessionTitle(sessionId, watcher.conv.id);
7354
7894
  const usage = messageUsage(messages, inFlight.opencodeMessageId);
7895
+ const usageAgentName = this.usageAgentName(messages ?? [], inFlight.opencodeMessageId);
7896
+ const subagentInvocations = await this.resolveSubagentInvocations(
7897
+ messages,
7898
+ inFlight.opencodeMessageId,
7899
+ inFlight.evidentMessageId
7900
+ );
7355
7901
  try {
7356
7902
  await this.markDone(
7357
7903
  conv.id,
@@ -7359,7 +7905,9 @@ var ChannelDriver = class _ChannelDriver {
7359
7905
  sessionId,
7360
7906
  inFlight.opencodeMessageId,
7361
7907
  title,
7362
- usage
7908
+ usage,
7909
+ usageAgentName,
7910
+ subagentInvocations
7363
7911
  );
7364
7912
  } catch (err) {
7365
7913
  if (err instanceof ChannelAuthError) throw err;
@@ -7538,6 +8086,12 @@ var ChannelDriver = class _ChannelDriver {
7538
8086
  if (state === "failed" && !restartAborted) {
7539
8087
  const error2 = messageError(messages, ocId ?? "") ?? void 0;
7540
8088
  const usage = messageUsage(messages, ocId ?? "");
8089
+ const usageAgentName = this.usageAgentName(messages, ocId ?? "");
8090
+ const subagentInvocations = await this.resolveSubagentInvocations(
8091
+ messages,
8092
+ ocId ?? "",
8093
+ row.id
8094
+ );
7541
8095
  const failure = await this.classifyModelAuthFailure(messages, ocId ?? "");
7542
8096
  this.log({
7543
8097
  level: "error",
@@ -7546,7 +8100,16 @@ var ChannelDriver = class _ChannelDriver {
7546
8100
  message_id: row.id
7547
8101
  });
7548
8102
  try {
7549
- await this.markFailed(row.conversation_id, row.id, sessionId, error2, usage, failure);
8103
+ await this.markFailed(
8104
+ row.conversation_id,
8105
+ row.id,
8106
+ sessionId,
8107
+ error2,
8108
+ usage,
8109
+ failure,
8110
+ usageAgentName,
8111
+ subagentInvocations
8112
+ );
7550
8113
  } catch (err) {
7551
8114
  if (err instanceof ChannelAuthError) throw err;
7552
8115
  if (err instanceof ChannelTerminalError) {
@@ -7708,7 +8271,22 @@ var ChannelDriver = class _ChannelDriver {
7708
8271
  try {
7709
8272
  const title = await this.resolveSessionTitle(sessionId, row.conversation_id);
7710
8273
  const usage = messageUsage(messages, ocId ?? "");
7711
- await this.markDone(row.conversation_id, row.id, sessionId, ocId, title, usage);
8274
+ const usageAgentName = this.usageAgentName(messages, ocId ?? "");
8275
+ const subagentInvocations = await this.resolveSubagentInvocations(
8276
+ messages,
8277
+ ocId ?? "",
8278
+ row.id
8279
+ );
8280
+ await this.markDone(
8281
+ row.conversation_id,
8282
+ row.id,
8283
+ sessionId,
8284
+ ocId,
8285
+ title,
8286
+ usage,
8287
+ usageAgentName,
8288
+ subagentInvocations
8289
+ );
7712
8290
  } catch (err) {
7713
8291
  if (err instanceof ChannelAuthError) throw err;
7714
8292
  if (err instanceof ChannelTerminalError) {
@@ -7826,14 +8404,14 @@ var ChannelDriver = class _ChannelDriver {
7826
8404
  this.unconfirmedDispatchFailures.delete(row.id);
7827
8405
  this.sessions.delete(readoptConv.id);
7828
8406
  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.`;
8407
+ 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
8408
  this.log({
7831
8409
  level: "error",
7832
- message: errorMessage2,
8410
+ message: errorMessage3,
7833
8411
  conversation_id: row.conversation_id,
7834
8412
  message_id: row.id
7835
8413
  });
7836
- await this.markFailed(row.conversation_id, row.id, null, errorMessage2).catch((markErr) => {
8414
+ await this.markFailed(row.conversation_id, row.id, null, errorMessage3).catch((markErr) => {
7837
8415
  this.log({
7838
8416
  level: "warn",
7839
8417
  message: `markFailed PATCH for message ${row.id.slice(0, 8)} (conversation ${row.conversation_id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
@@ -8112,6 +8690,166 @@ var ChannelDriver = class _ChannelDriver {
8112
8690
  if (parent !== void 0) this.sessionParents.set(sessionId, parent);
8113
8691
  return parent;
8114
8692
  }
8693
+ usageAgentName(messages, userMessageId) {
8694
+ const reply = findLastAssistantReplyFor(messages, userMessageId);
8695
+ const mode = reply?.info?.mode;
8696
+ if (typeof mode === "string" && mode.length > 0) return mode;
8697
+ const agent = reply?.info?.agent;
8698
+ return typeof agent === "string" && agent.length > 0 ? agent : null;
8699
+ }
8700
+ async resolveSubagentInvocations(messages, userMessageId, messageId, phase = "terminal") {
8701
+ if (!messages) return void 0;
8702
+ const cache = phase === "prefetch" ? this.subagentInvocationPrefetches : this.subagentInvocationCollections;
8703
+ const cached = cache.get(messageId);
8704
+ if (cached) return cached;
8705
+ const collection = this.buildSubagentInvocations(messages, userMessageId, messageId).catch(
8706
+ (err) => {
8707
+ this.log({
8708
+ level: "warn",
8709
+ message: `Best-effort subagent usage collection failed for message ${messageId.slice(0, 8)} \u2014 omitting invocation telemetry: ${err instanceof Error ? err.message : String(err)}`,
8710
+ message_id: messageId
8711
+ });
8712
+ return void 0;
8713
+ }
8714
+ );
8715
+ cache.set(messageId, collection);
8716
+ const result = await collection;
8717
+ if (result === void 0 && cache.get(messageId) === collection) cache.delete(messageId);
8718
+ return result;
8719
+ }
8720
+ clearSubagentInvocationCaches(messageId) {
8721
+ this.subagentInvocationCollections.delete(messageId);
8722
+ this.subagentInvocationPrefetches.delete(messageId);
8723
+ }
8724
+ async buildSubagentInvocations(messages, userMessageId, messageId) {
8725
+ const rootCalls = collectTaskCalls(messages, userMessageId);
8726
+ if (rootCalls.length === 0) return void 0;
8727
+ const childMessages = /* @__PURE__ */ new Map();
8728
+ const seenCallIds = new Set(rootCalls.map((call) => call.callID));
8729
+ const work = rootCalls.map((call) => ({
8730
+ call,
8731
+ depth: 1
8732
+ }));
8733
+ const payload = [];
8734
+ const fetchChildMessages = (sessionId) => {
8735
+ const cached = childMessages.get(sessionId);
8736
+ if (cached) return cached;
8737
+ const pending = (async () => {
8738
+ try {
8739
+ const res = await this.fetchImpl(`${this.opencodeBase}/session/${sessionId}/message`);
8740
+ if (!res.ok) {
8741
+ this.log({
8742
+ level: "warn",
8743
+ message: `Best-effort subagent session fetch for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} returned HTTP ${res.status} \u2014 omitting invocation telemetry`,
8744
+ message_id: messageId
8745
+ });
8746
+ return null;
8747
+ }
8748
+ const body = await res.json();
8749
+ if (!Array.isArray(body)) throw new Error("response body was not a message array");
8750
+ return body;
8751
+ } catch (err) {
8752
+ this.log({
8753
+ level: "warn",
8754
+ message: `Best-effort subagent session fetch failed for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} \u2014 omitting invocation telemetry: ${err instanceof Error ? err.message : String(err)}`,
8755
+ message_id: messageId
8756
+ });
8757
+ return null;
8758
+ }
8759
+ })();
8760
+ childMessages.set(sessionId, pending);
8761
+ return pending;
8762
+ };
8763
+ const fetchChildWithoutBlocking = async (sessionId) => {
8764
+ const pending = fetchChildMessages(sessionId);
8765
+ let timer;
8766
+ const timeout = new Promise((resolve4) => {
8767
+ timer = setTimeout(() => {
8768
+ this.log({
8769
+ level: "warn",
8770
+ message: `Best-effort subagent session fetch for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} was slow \u2014 omitting invocation telemetry without delaying completion`,
8771
+ message_id: messageId
8772
+ });
8773
+ resolve4(null);
8774
+ }, SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS);
8775
+ });
8776
+ try {
8777
+ return await Promise.race([pending, timeout]);
8778
+ } finally {
8779
+ if (timer !== void 0) clearTimeout(timer);
8780
+ }
8781
+ };
8782
+ while (work.length > 0) {
8783
+ const groups = /* @__PURE__ */ new Map();
8784
+ for (const item of work.splice(0)) {
8785
+ const group = groups.get(item.call.childSessionId) ?? [];
8786
+ group.push(item);
8787
+ groups.set(item.call.childSessionId, group);
8788
+ }
8789
+ const groupResults = await Promise.all(
8790
+ [...groups].map(async ([sessionId, items]) => ({
8791
+ sessionId,
8792
+ items,
8793
+ messages: sessionId === null ? [] : await fetchChildWithoutBlocking(sessionId)
8794
+ }))
8795
+ );
8796
+ for (const { sessionId, items, messages: child } of groupResults) {
8797
+ if (sessionId !== null && child === null) continue;
8798
+ const attribution = sessionId === null ? { invocations: [], unattributed: [] } : attributeTaskCallUsage(
8799
+ child,
8800
+ items.map(({ call }) => ({
8801
+ callID: call.callID,
8802
+ timeStart: call.timeStart,
8803
+ timeEnd: call.timeEnd
8804
+ }))
8805
+ );
8806
+ if (sessionId !== null && attribution.unattributed.length > 0) {
8807
+ this.log({
8808
+ level: "warn",
8809
+ message: `Omitted ${attribution.unattributed.length} unattributable assistant message(s) from subagent usage for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} \u2014 assigned to no invocation window`,
8810
+ message_id: messageId
8811
+ });
8812
+ }
8813
+ const usageByCall = new Map(
8814
+ attribution.invocations.map((invocation) => [invocation.callID, invocation.usage])
8815
+ );
8816
+ const messagesByCall = new Map(
8817
+ attribution.invocations.map((invocation) => [invocation.callID, invocation.messages])
8818
+ );
8819
+ for (const { call, depth } of items) {
8820
+ const usage = usageByCall.get(call.callID) ?? null;
8821
+ payload.push({
8822
+ tool_call_id: call.callID,
8823
+ agent_name: call.subagentName,
8824
+ opencode_session_id: call.childSessionId,
8825
+ parent_opencode_session_id: call.parentSessionId,
8826
+ depth,
8827
+ status: call.status,
8828
+ started_at: call.timeStart === null ? null : new Date(call.timeStart).toISOString(),
8829
+ ended_at: call.timeEnd === null ? null : new Date(call.timeEnd).toISOString(),
8830
+ usage_provider_id: usage?.usage_provider_id ?? call.model?.providerID ?? null,
8831
+ usage_model_id: usage?.usage_model_id ?? call.model?.modelID ?? null,
8832
+ usage_tokens_input: usage?.usage_tokens_input ?? null,
8833
+ usage_tokens_output: usage?.usage_tokens_output ?? null,
8834
+ usage_tokens_reasoning: usage?.usage_tokens_reasoning ?? null,
8835
+ usage_tokens_cache_read: usage?.usage_tokens_cache_read ?? null,
8836
+ usage_tokens_cache_write: usage?.usage_tokens_cache_write ?? null,
8837
+ usage_cost_usd: usage?.usage_cost_usd ?? null
8838
+ });
8839
+ for (const assigned of messagesByCall.get(call.callID) ?? []) {
8840
+ const parentId = assigned.info?.parentID ?? assigned.parentID;
8841
+ if (!parentId) continue;
8842
+ for (const nested of collectTaskCalls([assigned], parentId)) {
8843
+ if (seenCallIds.has(nested.callID)) continue;
8844
+ seenCallIds.add(nested.callID);
8845
+ work.push({ call: nested, depth: depth + 1 });
8846
+ }
8847
+ }
8848
+ }
8849
+ }
8850
+ }
8851
+ return payload.length > 0 ? payload : void 0;
8852
+ }
8115
8853
  /**
8116
8854
  * OpenCode's synchronous default session title (e.g.
8117
8855
  * `"New session - 1737800000000"`), assigned immediately when a session is
@@ -8595,7 +9333,7 @@ var ChannelDriver = class _ChannelDriver {
8595
9333
  * watcher retries next tick within the
8596
9334
  * deadline, Finding 4).
8597
9335
  */
8598
- async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage) {
9336
+ async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage, usageAgentName, subagentInvocations) {
8599
9337
  const res = await this.fetchImpl(
8600
9338
  `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
8601
9339
  {
@@ -8611,15 +9349,21 @@ var ChannelDriver = class _ChannelDriver {
8611
9349
  opencode_session_id: sessionId,
8612
9350
  ...opencodeMessageId ? { opencode_message_id: opencodeMessageId } : {},
8613
9351
  ...title ? { title } : {},
8614
- ...usage ? usage : {}
9352
+ ...usage ? usage : {},
9353
+ ...usageAgentName ? { usage_agent_name: usageAgentName } : {},
9354
+ ...subagentInvocations && subagentInvocations.length > 0 ? { subagent_invocations: subagentInvocations } : {}
8615
9355
  })
8616
9356
  }
8617
9357
  );
8618
9358
  this.assertAuth(res, "marking message as done");
8619
- if (res.ok) return;
9359
+ if (res.ok) {
9360
+ this.clearSubagentInvocationCaches(messageId);
9361
+ return;
9362
+ }
8620
9363
  if (isRetryableStatus(res.status)) {
8621
9364
  throw new Error(`marking message as done: HTTP ${res.status}`);
8622
9365
  }
9366
+ this.clearSubagentInvocationCaches(messageId);
8623
9367
  throw new ChannelTerminalError(`marking message as done: HTTP ${res.status}`, res.status);
8624
9368
  }
8625
9369
  /**
@@ -8634,7 +9378,7 @@ var ChannelDriver = class _ChannelDriver {
8634
9378
  * exists but is wedged, so the next attempt must get a fresh one
8635
9379
  * instead of reusing it — see WI-1's server-side null-clearing PATCH).
8636
9380
  */
8637
- async markFailed(conversationId, messageId, sessionId, error2, usage, failure) {
9381
+ async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations) {
8638
9382
  const body = { status: "failed" };
8639
9383
  if (sessionId === null) {
8640
9384
  body.opencode_session_id = null;
@@ -8643,23 +9387,33 @@ var ChannelDriver = class _ChannelDriver {
8643
9387
  }
8644
9388
  if (error2 !== void 0) body.error = error2;
8645
9389
  if (usage) Object.assign(body, usage);
9390
+ if (usageAgentName) body.usage_agent_name = usageAgentName;
9391
+ if (subagentInvocations && subagentInvocations.length > 0) {
9392
+ body.subagent_invocations = subagentInvocations;
9393
+ }
8646
9394
  if (failure) {
8647
9395
  body.failure_kind = failure.kind;
8648
9396
  body.failure_provider_id = failure.providerId;
8649
9397
  body.failure_model_id = failure.modelId;
8650
9398
  body.failure_reason = failure.reason;
8651
9399
  }
8652
- await this.callWithRetry(
8653
- "marking message as failed",
8654
- () => this.fetchImpl(
8655
- `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
8656
- {
8657
- method: "PATCH",
8658
- headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
8659
- body: JSON.stringify(body)
8660
- }
8661
- )
8662
- );
9400
+ try {
9401
+ await this.callWithRetry(
9402
+ "marking message as failed",
9403
+ () => this.fetchImpl(
9404
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
9405
+ {
9406
+ method: "PATCH",
9407
+ headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
9408
+ body: JSON.stringify(body)
9409
+ }
9410
+ )
9411
+ );
9412
+ } catch (err) {
9413
+ if (err instanceof ChannelTerminalError) this.clearSubagentInvocationCaches(messageId);
9414
+ throw err;
9415
+ }
9416
+ this.clearSubagentInvocationCaches(messageId);
8663
9417
  }
8664
9418
  /**
8665
9419
  * Classify an errored turn as a model-auth failure (#736 P1-4), for `markFailed`.
@@ -8942,6 +9696,13 @@ import chalk5 from "chalk";
8942
9696
  import ora2 from "ora";
8943
9697
  import { select as select2 } from "@inquirer/prompts";
8944
9698
  var INTERACTIVE_START_TIMEOUT_MS = 3e4;
9699
+ function checkNonInteractivePortConflict(port, isPortInUseFn) {
9700
+ if (isPortInUseFn(port)) {
9701
+ throw new Error(
9702
+ `Port ${port} is already in use by a non-OpenCode process. Free it or pass --port.`
9703
+ );
9704
+ }
9705
+ }
8945
9706
  async function ensureOpenCodeRunning(ctx) {
8946
9707
  const healthCheck = await checkOpenCodeHealth(ctx.port);
8947
9708
  if (healthCheck.healthy) {
@@ -8989,6 +9750,7 @@ async function ensureOpenCodeRunning(ctx) {
8989
9750
  }
8990
9751
  }
8991
9752
  if (!ctx.interactive) {
9753
+ checkNonInteractivePortConflict(ctx.port, isPortInUse);
8992
9754
  ctx.log(`OpenCode is not running on port ${ctx.port}. Starting it automatically...`);
8993
9755
  const proc = await startOpenCode(ctx.port, { inheritStdio: ctx.inheritStdio });
8994
9756
  const health = await waitForOpenCodeHealth(ctx.port, ctx.startTimeoutMs);
@@ -9070,9 +9832,119 @@ Port ${port} is already in use.`));
9070
9832
  return { port, process: null, version: null, notReadyReason: "you chose to continue without it" };
9071
9833
  }
9072
9834
 
9835
+ // src/commands/ensure-opencode-v2.ts
9836
+ import chalk6 from "chalk";
9837
+ import { select as select3 } from "@inquirer/prompts";
9838
+ async function probeOpenCode2WithoutPassword(port) {
9839
+ try {
9840
+ const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
9841
+ signal: AbortSignal.timeout(2e3)
9842
+ });
9843
+ if (response.status === 401) {
9844
+ return { healthy: false, authFailed: true, error: "HTTP 401" };
9845
+ }
9846
+ if (!response.ok) {
9847
+ return { healthy: false, error: `HTTP ${response.status}` };
9848
+ }
9849
+ return { healthy: true };
9850
+ } catch (error2) {
9851
+ return {
9852
+ healthy: false,
9853
+ error: error2 instanceof Error ? error2.message : "Unknown error"
9854
+ };
9855
+ }
9856
+ }
9857
+ function unknownPasswordError(port) {
9858
+ return new Error(
9859
+ `OpenCode V2 (opencode2) is already running on port ${port} with a password this runner doesn't know. Free the port or pass --port.`
9860
+ );
9861
+ }
9862
+ function v2SessionSupportIncompleteError() {
9863
+ return new Error(
9864
+ "OpenCode V2 (opencode2) session support is incomplete \u2014 see https://github.com/sroze/evident/issues/2075. Not starting a new opencode2 process."
9865
+ );
9866
+ }
9867
+ async function ensureOpenCode2Running(ctx) {
9868
+ const initialHealth = await probeOpenCode2WithoutPassword(ctx.port);
9869
+ if (initialHealth.authFailed) {
9870
+ throw unknownPasswordError(ctx.port);
9871
+ }
9872
+ if (initialHealth.healthy) {
9873
+ return {
9874
+ port: ctx.port,
9875
+ process: null,
9876
+ version: null,
9877
+ notReadyReason: null,
9878
+ password: null
9879
+ };
9880
+ }
9881
+ if (!isOpenCode2Installed()) {
9882
+ throw new Error(
9883
+ "OpenCode V2 (opencode2) is not installed. Install it with: npm install -g @opencode-ai/cli@beta"
9884
+ );
9885
+ }
9886
+ let port = ctx.port;
9887
+ if (!ctx.interactive) {
9888
+ checkNonInteractivePortConflict(port, isPortInUse);
9889
+ } else if (isPortInUse(port)) {
9890
+ console.log(chalk6.yellow(`
9891
+ Port ${port} is already in use.`));
9892
+ const alternativePort = findAvailablePort(port + 1);
9893
+ if (alternativePort) {
9894
+ const useAlternative = await select3({
9895
+ message: `Use port ${alternativePort} instead?`,
9896
+ choices: [
9897
+ { name: `Yes, use port ${alternativePort}`, value: "yes" },
9898
+ { name: "No, I will free the port manually", value: "no" }
9899
+ ]
9900
+ });
9901
+ if (useAlternative === "yes") {
9902
+ port = alternativePort;
9903
+ } else {
9904
+ throw new Error(`Port ${ctx.port} is in use`);
9905
+ }
9906
+ }
9907
+ }
9908
+ if (!ctx.interactive) {
9909
+ throw v2SessionSupportIncompleteError();
9910
+ }
9911
+ console.log(chalk6.yellow(`
9912
+ ${v2SessionSupportIncompleteError().message}`));
9913
+ const action = await select3({
9914
+ message: "OpenCode V2 is not running. What would you like to do?",
9915
+ choices: [
9916
+ {
9917
+ name: "Show me the command",
9918
+ value: "manual",
9919
+ description: "Display the command to run manually"
9920
+ },
9921
+ {
9922
+ name: "Continue without OpenCode V2",
9923
+ value: "continue",
9924
+ description: "Requests will fail until OpenCode V2 starts"
9925
+ }
9926
+ ]
9927
+ });
9928
+ if (action === "manual") {
9929
+ blank();
9930
+ console.log(chalk6.bold("Run this command in another terminal:"));
9931
+ blank();
9932
+ console.log(` ${chalk6.cyan(`opencode2 serve --port ${port}`)}`);
9933
+ blank();
9934
+ throw new Error("Please start OpenCode V2 manually");
9935
+ }
9936
+ return {
9937
+ port,
9938
+ process: null,
9939
+ version: null,
9940
+ notReadyReason: "you chose to continue without OpenCode V2",
9941
+ password: null
9942
+ };
9943
+ }
9944
+
9073
9945
  // src/lib/runner-credentials.ts
9074
- import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "fs";
9075
- import { spawn as spawn5 } from "child_process";
9946
+ import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "node:fs";
9947
+ import { spawn as spawn5 } from "node:child_process";
9076
9948
  var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
9077
9949
  var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
9078
9950
  var GITHUB_PROBE_TIMEOUT_MS = 1e4;
@@ -9344,11 +10216,11 @@ async function configureGitHubAccess({ env, log: log3 }) {
9344
10216
  }
9345
10217
 
9346
10218
  // 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";
10219
+ import { execFileSync as execFileSync2 } from "node:child_process";
10220
+ import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
10221
+ import { isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
9350
10222
  function isFile(filePath) {
9351
- return existsSync2(filePath) && statSync5(filePath).isFile();
10223
+ return existsSync2(filePath) && statSync6(filePath).isFile();
9352
10224
  }
9353
10225
  function applyRunnerOpenCodeConfig({
9354
10226
  overlayPath,
@@ -9360,7 +10232,7 @@ function applyRunnerOpenCodeConfig({
9360
10232
  return;
9361
10233
  }
9362
10234
  const source = isAbsolute2(overlayPath) ? overlayPath : resolve3(cwd, overlayPath);
9363
- const target = isFile(join8(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
10235
+ const target = isFile(join9(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
9364
10236
  if (!isFile(source)) {
9365
10237
  log3(
9366
10238
  `RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
@@ -9368,7 +10240,7 @@ function applyRunnerOpenCodeConfig({
9368
10240
  );
9369
10241
  return;
9370
10242
  }
9371
- copyFileSync(source, join8(cwd, target));
10243
+ copyFileSync(source, join9(cwd, target));
9372
10244
  try {
9373
10245
  execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
9374
10246
  stdio: "ignore"
@@ -9377,11 +10249,11 @@ function applyRunnerOpenCodeConfig({
9377
10249
  const detail = error2 instanceof Error ? error2.message : String(error2);
9378
10250
  log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
9379
10251
  }
9380
- log3(`Applied runner OpenCode config ${source} to ${join8(cwd, target)}`);
10252
+ log3(`Applied runner OpenCode config ${source} to ${join9(cwd, target)}`);
9381
10253
  }
9382
10254
 
9383
10255
  // src/lib/credential-sync.ts
9384
- import { renameSync, writeFileSync as writeFileSync5 } from "fs";
10256
+ import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
9385
10257
  var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
9386
10258
  var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
9387
10259
  var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
@@ -9391,7 +10263,7 @@ var MAX_FLUSH_PASSES = 2;
9391
10263
  function outcomesWith(outcome) {
9392
10264
  return { claude: outcome, opencode: outcome };
9393
10265
  }
9394
- function errorMessage(error2) {
10266
+ function errorMessage2(error2) {
9395
10267
  return error2 instanceof Error ? error2.message : String(error2);
9396
10268
  }
9397
10269
  function waitForSettlement(promise, timeoutMs) {
@@ -9418,7 +10290,7 @@ function writeMarker(markerPath, outcomes, log3) {
9418
10290
  writeFileSync5(temporaryPath, body, { mode: 384 });
9419
10291
  renameSync(temporaryPath, markerPath);
9420
10292
  } catch (error2) {
9421
- log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage(error2)}`, "warn");
10293
+ log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
9422
10294
  }
9423
10295
  }
9424
10296
  function intervalSeconds(env, log3) {
@@ -9450,7 +10322,7 @@ async function runFlushStore(store, env, synchroniserRunner, log3, deadlineAt) {
9450
10322
  },
9451
10323
  (error2) => {
9452
10324
  failed = true;
9453
- log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage(error2)}`, "warn");
10325
+ log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage2(error2)}`, "warn");
9454
10326
  }
9455
10327
  );
9456
10328
  const abortTimer = setTimeout(() => controller.abort(), remainingMs);
@@ -9510,7 +10382,7 @@ function createCredentialSync({
9510
10382
  outcomes[store] = !result.timedOut && result.code === 0 ? "ok" : "failed";
9511
10383
  } catch (error2) {
9512
10384
  outcomes[store] = "failed";
9513
- log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage(error2)}`, "debug");
10385
+ log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage2(error2)}`, "debug");
9514
10386
  }
9515
10387
  }
9516
10388
  const failed = STORES.some((store) => outcomes[store] === "failed");
@@ -9652,7 +10524,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
9652
10524
  if (trimmed === "") {
9653
10525
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
9654
10526
  }
9655
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join9(homeDir, trimmed.slice(2)) : trimmed;
10527
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join10(homeDir, trimmed.slice(2)) : trimmed;
9656
10528
  if (!isAbsolute3(expanded)) {
9657
10529
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
9658
10530
  }
@@ -9676,6 +10548,28 @@ function resolveFileSyncDirectories(raw, homeDir) {
9676
10548
  var DEFAULT_OPENCODE_START_TIMEOUT_SECONDS = 180;
9677
10549
  var MAX_OPENCODE_START_TIMEOUT_SECONDS = 3600;
9678
10550
  var OPENCODE_START_TIMEOUT_ENV = "EVIDENT_OPENCODE_START_TIMEOUT";
10551
+ var OPENCODE_VERSION_ENV = "EVIDENT_OPENCODE_VERSION";
10552
+ function resolveOpenCodeVersion(options, env = process.env) {
10553
+ let raw;
10554
+ let source;
10555
+ if (options.opencodeVersion !== void 0) {
10556
+ raw = options.opencodeVersion;
10557
+ source = "--opencode-version";
10558
+ } else if (env[OPENCODE_VERSION_ENV] !== void 0 && env[OPENCODE_VERSION_ENV] !== "") {
10559
+ raw = env[OPENCODE_VERSION_ENV];
10560
+ source = OPENCODE_VERSION_ENV;
10561
+ } else {
10562
+ return { version: "v1", warnings: [] };
10563
+ }
10564
+ const normalized = raw.trim().toLowerCase();
10565
+ if (normalized !== "v1" && normalized !== "v2") {
10566
+ return {
10567
+ version: "v1",
10568
+ warnings: [`Ignoring invalid ${source} "${raw}": expected v1 or v2; using the default v1`]
10569
+ };
10570
+ }
10571
+ return { version: normalized, warnings: [] };
10572
+ }
9679
10573
  function resolveOpenCodeStartTimeoutMs(options, env = process.env) {
9680
10574
  const defaultMs = DEFAULT_OPENCODE_START_TIMEOUT_SECONDS * 1e3;
9681
10575
  let raw;
@@ -9742,7 +10636,7 @@ function log2(state, message, level = "info") {
9742
10636
  })
9743
10637
  );
9744
10638
  } else if (!state.interactive) {
9745
- const prefix = level === "error" ? chalk6.red("\u2717") : level === "warn" ? chalk6.yellow("!") : level === "debug" ? chalk6.dim("\xB7") : chalk6.green("\u2022");
10639
+ const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
9746
10640
  console.log(`${prefix} ${message}`);
9747
10641
  }
9748
10642
  }
@@ -9772,7 +10666,7 @@ function logActivity(state, entry) {
9772
10666
  }
9773
10667
  function reportSessionDbRecovery(state) {
9774
10668
  try {
9775
- const report = drainSessionDbRecoveryReport({ homeDir: homedir5(), env: process.env });
10669
+ const report = drainSessionDbRecoveryReport({ homeDir: homedir6(), env: process.env });
9776
10670
  for (const record of report.records) {
9777
10671
  const activity = buildSessionDbRecoveryActivity(record);
9778
10672
  if (!activity) throw new Error("could not map session-DB recovery record");
@@ -9803,18 +10697,18 @@ function reportSessionDbRecoveryRecord(state, record) {
9803
10697
  function displayStatus(state) {
9804
10698
  if (!state.interactive) return;
9805
10699
  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`) : "";
10700
+ const tunnel = state.connected ? chalk7.green("tunnel: connected") : attempt > 0 ? chalk7.yellow(`tunnel: reconnecting (#${attempt})`) : chalk7.yellow("tunnel: connecting");
10701
+ const opencode = state.opencodeConnected ? chalk7.green(`opencode: :${state.port}`) : chalk7.red(`opencode: :${state.port} (down)`);
10702
+ const messages = state.messageCount > 0 ? chalk7.dim(` \xB7 ${state.messageCount} processed`) : "";
9809
10703
  const last = state.activityLog[state.activityLog.length - 1];
9810
- const detail = last ? chalk6.dim(` \xB7 ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`) : "";
10704
+ const detail = last ? chalk7.dim(` \xB7 ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`) : "";
9811
10705
  const agent = state.agentName ?? state.agentId;
9812
10706
  console.log(
9813
- `${chalk6.bold("Evident")} ${chalk6.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
10707
+ `${chalk7.bold("Evident")} ${chalk7.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
9814
10708
  );
9815
10709
  }
9816
10710
  async function promptForLogin(promptMessage, successMessage) {
9817
- const action = await select3({
10711
+ const action = await select4({
9818
10712
  message: promptMessage,
9819
10713
  choices: [
9820
10714
  {
@@ -9830,7 +10724,7 @@ async function promptForLogin(promptMessage, successMessage) {
9830
10724
  ]
9831
10725
  });
9832
10726
  if (action === "exit") {
9833
- console.log(chalk6.dim(`
10727
+ console.log(chalk7.dim(`
9834
10728
  You can log in later by running: ${getCliName()} login`));
9835
10729
  process.exit(0);
9836
10730
  }
@@ -9841,7 +10735,7 @@ You can log in later by running: ${getCliName()} login`));
9841
10735
  process.exit(1);
9842
10736
  }
9843
10737
  blank();
9844
- console.log(chalk6.green(successMessage));
10738
+ console.log(chalk7.green(successMessage));
9845
10739
  blank();
9846
10740
  return { token: credentials2.token, authType: "bearer", user: credentials2.user };
9847
10741
  }
@@ -9854,12 +10748,12 @@ async function handleAuthError(state, error2) {
9854
10748
  if (state.interactive) displayStatus(state);
9855
10749
  if (!state.interactive) {
9856
10750
  blank();
9857
- console.log(chalk6.red("Authentication expired"));
9858
- console.log(chalk6.dim("Your authentication token is no longer valid."));
10751
+ console.log(chalk7.red("Authentication expired"));
10752
+ console.log(chalk7.dim("Your authentication token is no longer valid."));
9859
10753
  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"));
10754
+ console.log(chalk7.dim("To fix this:"));
10755
+ console.log(chalk7.dim(` 1. Run '${getCliName()} login' to re-authenticate`));
10756
+ console.log(chalk7.dim(" 2. Restart this command"));
9863
10757
  blank();
9864
10758
  await cleanup(state);
9865
10759
  await shutdownTelemetry();
@@ -9867,7 +10761,7 @@ async function handleAuthError(state, error2) {
9867
10761
  return { success: false };
9868
10762
  }
9869
10763
  blank();
9870
- console.log(chalk6.yellow("Your authentication has expired."));
10764
+ console.log(chalk7.yellow("Your authentication has expired."));
9871
10765
  blank();
9872
10766
  try {
9873
10767
  const credentials2 = await promptForLogin(
@@ -9931,6 +10825,14 @@ async function driveChannels(state, driver) {
9931
10825
  const opencodeAuthApplied = opencodeAuthApplies !== lastSeenOpencodeAuthApplies;
9932
10826
  lastSeenOpencodeAuthApplies = opencodeAuthApplies;
9933
10827
  if (opencodeAuthApplied) state.openaiUsageRearm?.();
10828
+ if (claudeCredentialApplied || opencodeAuthApplied) {
10829
+ void reloadProviderCache(state.port).catch(
10830
+ (error2) => logActivity(state, {
10831
+ type: "error",
10832
+ error: `OpenCode provider cache reload failed on port ${state.port}: ${error2 instanceof Error ? error2.message : String(error2)}`
10833
+ })
10834
+ );
10835
+ }
9934
10836
  if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
9935
10837
  idlePolls = 0;
9936
10838
  idleMs = 0;
@@ -9958,8 +10860,8 @@ async function driveChannels(state, driver) {
9958
10860
  state.running = false;
9959
10861
  break;
9960
10862
  }
9961
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
9962
- logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage2}` });
10863
+ const errorMessage3 = error2 instanceof Error ? error2.message : String(error2);
10864
+ logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage3}` });
9963
10865
  if (state.interactive) displayStatus(state);
9964
10866
  if (driver.hasInFlightWatchers()) {
9965
10867
  consecutiveDrainFailures = 0;
@@ -9997,9 +10899,18 @@ async function driveChannels(state, driver) {
9997
10899
  }
9998
10900
  }
9999
10901
  var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
10000
- var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
10902
+ var SESSION_DB_RECLAIM_MAX_PAGES = 2e4;
10903
+ function shouldWarnForReclaimSkip(reason) {
10904
+ if (reason !== "sqlite-unavailable") return false;
10905
+ const version2 = /^v(\d+)\.(\d+)\.(\d+)$/.exec(process.version);
10906
+ if (!version2) return false;
10907
+ const major = Number(version2[1]);
10908
+ const minor = Number(version2[2]);
10909
+ const patch = Number(version2[3]);
10910
+ return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
10911
+ }
10001
10912
  function sessionDbPath() {
10002
- return join9(homedir5(), ".local", "share", "opencode", "opencode.db");
10913
+ return join10(homedir6(), ".local", "share", "opencode", "opencode.db");
10003
10914
  }
10004
10915
  function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
10005
10916
  const record = {
@@ -10095,7 +11006,7 @@ async function runSweep(state, driver, config) {
10095
11006
  } else {
10096
11007
  logActivity(state, {
10097
11008
  type: "info",
10098
- message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})`
11009
+ message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : "")
10099
11010
  });
10100
11011
  }
10101
11012
  } catch (error2) {
@@ -10118,13 +11029,20 @@ function scheduleSessionCleanup(state, driver, options) {
10118
11029
  for (const warning2 of config.warnings) {
10119
11030
  logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
10120
11031
  }
10121
- const dbBytes = statSessionDbBytes(homedir5());
11032
+ const dbBytes = statSessionDbBytes(homedir6());
10122
11033
  void (async () => {
10123
- const reclaimSkipReason = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
11034
+ const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
11035
+ if (reclaimAvailability !== null) {
11036
+ logActivity(state, {
11037
+ type: "info",
11038
+ level: shouldWarnForReclaimSkip(reclaimAvailability.reason) ? "warn" : "info",
11039
+ message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : "")
11040
+ });
11041
+ }
10124
11042
  const sizeWarning = buildSessionStoreSizeWarning({
10125
11043
  dbBytes,
10126
11044
  cleanupEnabled: config.enabled,
10127
- reclaimSkipReason
11045
+ reclaimSkipReason: reclaimAvailability?.reason ?? null
10128
11046
  });
10129
11047
  if (sizeWarning !== null) {
10130
11048
  logActivity(state, { type: "info", level: "warn", message: sizeWarning });
@@ -10291,14 +11209,11 @@ function scheduleClaudeUsageReporting(state, options) {
10291
11209
  report: (usage) => reportClaudeUsage(state.agentId, state.authHeader, usage),
10292
11210
  isLocalCredentialProblem,
10293
11211
  forcedOnHint: "run `claude` to sign in",
10294
- firstDelayMs: () => FIRST_REPORT_DELAY_MS,
10295
- nextDelayMs: nextReportDelayMs,
10296
- failureLogLevel: claudeUsageFailureLogLevel
11212
+ firstDelayMs: firstReportDelayMs,
11213
+ nextDelayMs: usageReportDelayMs,
11214
+ failureLogLevel: usageReportFailureLogLevel
10297
11215
  });
10298
11216
  }
10299
- var RESOURCE_USAGE_BASE_REPORT_DELAY_MS = 10 * 6e4;
10300
- var RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
10301
- var RESOURCE_USAGE_FAILURE_REESCALATION_TICKS = 6;
10302
11217
  function scheduleResourceUsageReporting(state, options) {
10303
11218
  const { enabled, warnings } = resolveResourceUsageReportingEnabled(
10304
11219
  options.resourceUsageReporting,
@@ -10319,7 +11234,7 @@ function scheduleResourceUsageReporting(state, options) {
10319
11234
  });
10320
11235
  return;
10321
11236
  }
10322
- const { collect, stop } = createResourceUsageCollector(homedir5());
11237
+ const { collect, stop } = createResourceUsageCollector(homedir6());
10323
11238
  state.stopResourceUsageSampling = stop;
10324
11239
  let consecutiveFailures = 0;
10325
11240
  const tick = async () => {
@@ -10351,10 +11266,7 @@ function scheduleResourceUsageReporting(state, options) {
10351
11266
  consecutiveFailures++;
10352
11267
  logActivity(state, {
10353
11268
  type: "info",
10354
- level: reportFailureLogLevel(
10355
- consecutiveFailures,
10356
- RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
10357
- ),
11269
+ level: usageReportFailureLogLevel(consecutiveFailures),
10358
11270
  message: `Failed to report resource usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
10359
11271
  });
10360
11272
  }
@@ -10363,20 +11275,11 @@ function scheduleResourceUsageReporting(state, options) {
10363
11275
  const message = error2 instanceof Error ? error2.message : String(error2);
10364
11276
  logActivity(state, {
10365
11277
  type: "info",
10366
- level: reportFailureLogLevel(
10367
- consecutiveFailures,
10368
- RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
10369
- ),
11278
+ level: usageReportFailureLogLevel(consecutiveFailures),
10370
11279
  message: `Resource usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
10371
11280
  });
10372
11281
  } finally {
10373
- state.resourceUsageTimer = setTimeout(
10374
- () => void tick(),
10375
- jitteredDelayMs(
10376
- RESOURCE_USAGE_BASE_REPORT_DELAY_MS,
10377
- RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION
10378
- )
10379
- );
11282
+ state.resourceUsageTimer = setTimeout(() => void tick(), usageReportDelayMs());
10380
11283
  }
10381
11284
  };
10382
11285
  state.resourceUsageTimer = setTimeout(() => void tick(), firstReportDelayMs());
@@ -10416,6 +11319,8 @@ async function cleanup(state, opts = {}) {
10416
11319
  clearTimeout(timer);
10417
11320
  }
10418
11321
  state.sessionCleanupTimers = [];
11322
+ state.stopOpenCodeLogTail?.();
11323
+ state.stopOpenCodeLogTail = null;
10419
11324
  if (state.claudeUsageTimer) {
10420
11325
  clearTimeout(state.claudeUsageTimer);
10421
11326
  state.claudeUsageTimer = null;
@@ -10554,7 +11459,7 @@ async function run(options) {
10554
11459
  let fileSyncDirectories;
10555
11460
  try {
10556
11461
  logLevel = resolveLogLevel(options);
10557
- fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir5());
11462
+ fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir6());
10558
11463
  if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
10559
11464
  throw new Error(
10560
11465
  "--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
@@ -10585,6 +11490,7 @@ async function run(options) {
10585
11490
  opencodeVersion: null,
10586
11491
  sessionDbProvenanceAnomaly: false,
10587
11492
  opencodeProcess: null,
11493
+ stopOpenCodeLogTail: null,
10588
11494
  litestreamProcess: null,
10589
11495
  connection: null,
10590
11496
  channelDriver: null,
@@ -10652,15 +11558,15 @@ async function run(options) {
10652
11558
  printError("Authentication required");
10653
11559
  blank();
10654
11560
  console.log(
10655
- chalk6.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
11561
+ chalk7.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
10656
11562
  );
10657
- console.log(chalk6.dim("Or run `evident login` for interactive authentication"));
11563
+ console.log(chalk7.dim("Or run `evident login` for interactive authentication"));
10658
11564
  blank();
10659
11565
  process.exit(1);
10660
11566
  return;
10661
11567
  }
10662
11568
  blank();
10663
- console.log(chalk6.yellow("You are not logged in to Evident."));
11569
+ console.log(chalk7.yellow("You are not logged in to Evident."));
10664
11570
  blank();
10665
11571
  credentials2 = await promptForLogin(
10666
11572
  "Would you like to log in now?",
@@ -10710,7 +11616,7 @@ async function run(options) {
10710
11616
  );
10711
11617
  blank();
10712
11618
  console.log(
10713
- chalk6.dim(
11619
+ chalk7.dim(
10714
11620
  "Either provide --runner/--agent <id> or set EVIDENT_RUNNER_KEY/EVIDENT_AGENT_KEY"
10715
11621
  )
10716
11622
  );
@@ -10733,15 +11639,15 @@ async function run(options) {
10733
11639
  );
10734
11640
  if (interactive && !state.json) {
10735
11641
  blank();
10736
- console.log(chalk6.bold("Evident Run"));
10737
- console.log(chalk6.dim("-".repeat(40)));
11642
+ console.log(chalk7.bold("Evident Run"));
11643
+ console.log(chalk7.dim("-".repeat(40)));
10738
11644
  }
10739
11645
  const spinner = interactive && !state.json ? ora3("Validating runner...").start() : null;
10740
11646
  let validation = await getAgentInfo(state.agentId, state.authHeader);
10741
11647
  if (!validation.valid && validation.authFailed && interactive) {
10742
11648
  spinner?.fail("Authentication failed");
10743
11649
  blank();
10744
- console.log(chalk6.yellow("Your authentication token is invalid or expired."));
11650
+ console.log(chalk7.yellow("Your authentication token is invalid or expired."));
10745
11651
  blank();
10746
11652
  credentials2 = await promptForLogin(
10747
11653
  "Would you like to log in again?",
@@ -10789,6 +11695,13 @@ async function run(options) {
10789
11695
  if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
10790
11696
  }
10791
11697
  state.credentialSync?.arm();
11698
+ state.stopOpenCodeLogTail = tailOpenCodeLogFile(
11699
+ resolveOpenCodeLogPath(homedir6(), process.env),
11700
+ createOpenCodeActivityForwarder(() => ({
11701
+ agentId: state.agentId,
11702
+ authHeader: state.authHeader
11703
+ }))
11704
+ ).stop;
10792
11705
  let sessionDbVerifyFatal = false;
10793
11706
  if (!options.restoreSessionDb) {
10794
11707
  log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
@@ -10838,6 +11751,13 @@ async function run(options) {
10838
11751
  for (const warning2 of opencodeStartTimeoutWarnings) {
10839
11752
  logActivity(state, { type: "info", level: "warn", message: warning2 });
10840
11753
  }
11754
+ const { version: opencodeVersion, warnings: opencodeVersionWarnings } = resolveOpenCodeVersion(
11755
+ options,
11756
+ process.env
11757
+ );
11758
+ for (const warning2 of opencodeVersionWarnings) {
11759
+ logActivity(state, { type: "info", level: "warn", message: warning2 });
11760
+ }
10841
11761
  const { value: maxActiveSessions, warnings: maxActiveSessionsWarnings } = resolveMaxActiveSessions(options, process.env);
10842
11762
  for (const warning2 of maxActiveSessionsWarnings) {
10843
11763
  logActivity(state, { type: "info", level: "warn", message: warning2 });
@@ -10845,7 +11765,14 @@ async function run(options) {
10845
11765
  const preBootMigrationIds = readSessionDbMigrationIds(sessionDbPath());
10846
11766
  const ocSpinner = interactive && !state.json ? ora3("Checking OpenCode...").start() : null;
10847
11767
  try {
10848
- const oc = await ensureOpenCodeRunning({
11768
+ const oc = opencodeVersion === "v2" ? await ensureOpenCode2Running({
11769
+ port: state.port,
11770
+ interactive: state.interactive,
11771
+ agentId: state.agentId,
11772
+ log: (message) => log2(state, message),
11773
+ startTimeoutMs: opencodeStartTimeoutMs,
11774
+ inheritStdio: Boolean(options.opencodePidFile)
11775
+ }) : await ensureOpenCodeRunning({
10849
11776
  port: state.port,
10850
11777
  interactive: state.interactive,
10851
11778
  agentId: state.agentId,
@@ -10872,7 +11799,7 @@ async function run(options) {
10872
11799
  const provenance = checkSessionDbProvenance({
10873
11800
  dbPath: sessionDbPath(),
10874
11801
  currentVersion: state.opencodeVersion,
10875
- homeDir: homedir5(),
11802
+ homeDir: homedir6(),
10876
11803
  env: process.env
10877
11804
  });
10878
11805
  if (provenance.anomaly) {
@@ -10899,6 +11826,7 @@ async function run(options) {
10899
11826
  logActivity(state, { type: "info", level: "warn", message: versionWarning });
10900
11827
  }
10901
11828
  }
11829
+ await reloadProviderCache(state.port);
10902
11830
  const noProviderWarning = buildNoProviderWarning(
10903
11831
  await hasAnyConfiguredProvider(state.port)
10904
11832
  );
@@ -10907,10 +11835,10 @@ async function run(options) {
10907
11835
  if (state.interactive && !state.json) {
10908
11836
  logActivity(state, { type: "info", level: "warn", message: noProviderWarning });
10909
11837
  blank();
10910
- console.log(chalk6.yellow("\u26A0 No OpenCode model provider is configured."));
11838
+ console.log(chalk7.yellow("\u26A0 No OpenCode model provider is configured."));
10911
11839
  console.log(
10912
- chalk6.dim(
10913
- `Run ${chalk6.cyan("opencode auth login")} to set one up \u2014 messages will fail until then.`
11840
+ chalk7.dim(
11841
+ `Run ${chalk7.cyan("opencode auth login")} to set one up \u2014 messages will fail until then.`
10914
11842
  )
10915
11843
  );
10916
11844
  blank();
@@ -11034,7 +11962,7 @@ async function run(options) {
11034
11962
  // #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
11035
11963
  // REJECTED with `file_sync_disabled` on the ack, not silently ignored.
11036
11964
  fileSyncDirectories,
11037
- homeDir: homedir5(),
11965
+ homeDir: homedir6(),
11038
11966
  maxActiveSessions,
11039
11967
  log: (entry) => (
11040
11968
  // Thread the driver's real level straight through so `debug`/`warn`
@@ -11276,6 +12204,9 @@ program.command("run").description("Connect to Evident and process messages").op
11276
12204
  ).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
12205
  "--opencode-start-timeout <seconds>",
11278
12206
  "Seconds to wait for OpenCode to become healthy when the runner starts it (default: 180). Env: EVIDENT_OPENCODE_START_TIMEOUT"
12207
+ ).option(
12208
+ "--opencode-version <v1|v2>",
12209
+ "Which OpenCode major version to launch: v1 or v2 (default: v1). Env: EVIDENT_OPENCODE_VERSION"
11279
12210
  ).option("--json", "Output in JSON format").option(
11280
12211
  "--session-cleanup-max-age <duration>",
11281
12212
  "Delete OpenCode sessions idle longer than this (e.g. 7d, 24h). Enables cleanup. Env: EVIDENT_SESSION_CLEANUP_MAX_AGE"
@@ -11344,6 +12275,7 @@ program.command("run").description("Connect to Evident and process messages").op
11344
12275
  // Raw string — validation/precedence is single-sourced in run.ts's
11345
12276
  // resolveOpenCodeStartTimeoutMs (flag > EVIDENT_OPENCODE_START_TIMEOUT > 180s default).
11346
12277
  opencodeStartTimeout: options.opencodeStartTimeout,
12278
+ opencodeVersion: options.opencodeVersion,
11347
12279
  json: options.json,
11348
12280
  // Raw strings — the resolver in run.ts single-sources parsing (M1).
11349
12281
  sessionCleanupMaxAge: options.sessionCleanupMaxAge,