@evident-ai/cli 3.4.1-dev.b1bf8c5 → 3.4.1-dev.fbf2340

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
@@ -722,6 +722,14 @@ function toReportedWindow(window) {
722
722
  if (!window) return null;
723
723
  return { utilization: window.utilization, resets_at: window.resetsAt };
724
724
  }
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
+ }
725
733
  async function reportClaudeUsage(agentId, authHeader, snapshot) {
726
734
  try {
727
735
  const apiUrl = getApiUrlConfig();
@@ -730,7 +738,8 @@ async function reportClaudeUsage(agentId, authHeader, snapshot) {
730
738
  headers: { Authorization: authHeader, "Content-Type": "application/json" },
731
739
  body: JSON.stringify({
732
740
  five_hour: toReportedWindow(snapshot.fiveHour),
733
- seven_day: toReportedWindow(snapshot.sevenDay)
741
+ seven_day: toReportedWindow(snapshot.sevenDay),
742
+ owner: toReportedOwner(snapshot)
734
743
  }),
735
744
  signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
736
745
  });
@@ -996,7 +1005,10 @@ import { readFileSync } from "fs";
996
1005
  import { homedir } from "os";
997
1006
  import { join } from "path";
998
1007
  var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
1008
+ var CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
1009
+ var CLAUDE_PROFILE_TIMEOUT_MS = 2e3;
999
1010
  var KEYCHAIN_SERVICE = "Claude Code-credentials";
1011
+ var cachedOwner = null;
1000
1012
  var CLAUDE_CREDENTIALS_SEGMENTS = [".claude", ".credentials.json"];
1001
1013
  function parseClaudeCliCredentials(raw) {
1002
1014
  let parsed;
@@ -1070,6 +1082,47 @@ function toWindow(value) {
1070
1082
  }
1071
1083
  return { utilization: window.utilization, resetsAt };
1072
1084
  }
1085
+ function ownerLookupFailure(error2) {
1086
+ const name = error2?.name;
1087
+ return name === "TimeoutError" || name === "AbortError" ? "timed out" : "request failed";
1088
+ }
1089
+ async function getClaudeUsageOwner(accessToken) {
1090
+ if (cachedOwner?.accessToken === accessToken) {
1091
+ return { owner: cachedOwner.owner, ownerLookupError: null };
1092
+ }
1093
+ try {
1094
+ const response = await fetch(CLAUDE_PROFILE_URL, {
1095
+ headers: {
1096
+ Authorization: `Bearer ${accessToken}`,
1097
+ "Content-Type": "application/json",
1098
+ "anthropic-version": "2023-06-01"
1099
+ },
1100
+ signal: AbortSignal.timeout(CLAUDE_PROFILE_TIMEOUT_MS)
1101
+ });
1102
+ if (!response.ok) {
1103
+ return { owner: null, ownerLookupError: `HTTP ${response.status}` };
1104
+ }
1105
+ let body;
1106
+ try {
1107
+ body = await response.json();
1108
+ } catch (error2) {
1109
+ return { owner: null, ownerLookupError: "malformed response" };
1110
+ }
1111
+ const profile = body;
1112
+ if (typeof profile.account?.email !== "string" || !profile.account.email.trim()) {
1113
+ return { owner: null, ownerLookupError: "malformed response" };
1114
+ }
1115
+ const owner = {
1116
+ email: profile.account.email,
1117
+ organizationName: typeof profile.organization?.name === "string" && profile.organization.name.trim() ? profile.organization.name.trim() : null,
1118
+ rateLimitTier: typeof profile.organization?.rate_limit_tier === "string" && profile.organization.rate_limit_tier.trim() ? profile.organization.rate_limit_tier.trim() : null
1119
+ };
1120
+ cachedOwner = { accessToken, owner };
1121
+ return { owner, ownerLookupError: null };
1122
+ } catch (error2) {
1123
+ return { owner: null, ownerLookupError: ownerLookupFailure(error2) };
1124
+ }
1125
+ }
1073
1126
  async function getClaudeUsage() {
1074
1127
  const credentials2 = readClaudeCliCredentials();
1075
1128
  if (!credentials2) {
@@ -1089,15 +1142,19 @@ async function getClaudeUsage() {
1089
1142
  Authorization: `Bearer ${credentials2.accessToken}`,
1090
1143
  "Content-Type": "application/json",
1091
1144
  "anthropic-version": "2023-06-01"
1092
- }
1145
+ },
1146
+ signal: AbortSignal.timeout(CLAUDE_PROFILE_TIMEOUT_MS)
1093
1147
  });
1094
1148
  if (!res.ok) {
1095
1149
  throw new ClaudeUsageError(`Claude usage request failed: HTTP ${res.status}`, "request_failed");
1096
1150
  }
1097
1151
  const body = await res.json();
1152
+ const { owner, ownerLookupError } = await getClaudeUsageOwner(credentials2.accessToken);
1098
1153
  return {
1099
1154
  fiveHour: toWindow(body.five_hour),
1100
- sevenDay: toWindow(body.seven_day)
1155
+ sevenDay: toWindow(body.seven_day),
1156
+ owner,
1157
+ ownerLookupError
1101
1158
  };
1102
1159
  }
1103
1160
 
@@ -1127,7 +1184,7 @@ async function claudeUsage() {
1127
1184
 
1128
1185
  // src/commands/run.ts
1129
1186
  import { homedir as homedir4 } from "os";
1130
- import { isAbsolute as isAbsolute2, join as join6, parse, resolve as resolvePath } from "path";
1187
+ import { isAbsolute as isAbsolute2, join as join7, parse, resolve as resolvePath } from "path";
1131
1188
  import chalk6 from "chalk";
1132
1189
 
1133
1190
  // ../../packages/types/src/agents/index.ts
@@ -1360,6 +1417,8 @@ var SEVERITY_BY_LEVEL = {
1360
1417
  error: "error"
1361
1418
  };
1362
1419
  var MAX_MESSAGE_LENGTH = 500;
1420
+ var MAX_METADATA_VALUE_LENGTH = 200;
1421
+ var MAX_METADATA_ENTRIES = 20;
1363
1422
  var TRUNCATION_MARKER = "\u2026";
1364
1423
  function redact(message) {
1365
1424
  return message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/https?:\/\/\S+/g, "<url>");
@@ -1368,6 +1427,24 @@ function truncate(message) {
1368
1427
  if (message.length <= MAX_MESSAGE_LENGTH) return message;
1369
1428
  return message.slice(0, MAX_MESSAGE_LENGTH - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
1370
1429
  }
1430
+ function sanitiseMetadata(metadata) {
1431
+ if (!metadata) return {};
1432
+ const entries = Object.entries(metadata).slice(0, MAX_METADATA_ENTRIES);
1433
+ if (Object.keys(metadata).length > entries.length) {
1434
+ console.error(
1435
+ `[runner-activity-telemetry] dropped ${Object.keys(metadata).length - entries.length} metadata entries`
1436
+ );
1437
+ }
1438
+ const sanitised = [];
1439
+ for (const [key, value] of entries) {
1440
+ if (typeof value === "string") {
1441
+ sanitised.push([key, truncate(redact(value).slice(0, MAX_METADATA_VALUE_LENGTH))]);
1442
+ } else if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
1443
+ sanitised.push([key, value]);
1444
+ }
1445
+ }
1446
+ return Object.fromEntries(sanitised);
1447
+ }
1371
1448
  var RATE_LIMIT_WINDOW_MS = 6e4;
1372
1449
  var RATE_LIMIT_MAX_EVENTS = 30;
1373
1450
  var windowStartedAt = 0;
@@ -1406,7 +1483,7 @@ function forwardRunnerActivity(entry, context) {
1406
1483
  logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
1407
1484
  severity: SEVERITY_BY_LEVEL[entry.level],
1408
1485
  message,
1409
- metadata: { source: "cli.run" },
1486
+ metadata: { ...sanitiseMetadata(entry.metadata), source: "cli.run" },
1410
1487
  agentId: context.agentId
1411
1488
  });
1412
1489
  } catch (err) {
@@ -1416,6 +1493,150 @@ function forwardRunnerActivity(entry, context) {
1416
1493
  }
1417
1494
  }
1418
1495
 
1496
+ // src/lib/opencode/session-db-recovery-report.ts
1497
+ import { readFileSync as readFileSync2, unlinkSync } from "fs";
1498
+ import { join as join2 } from "path";
1499
+ function sessionDbRecoveryReportPath(homeDir, env) {
1500
+ const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
1501
+ return override || join2(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
1502
+ }
1503
+ function drainSessionDbRecoveryReport({
1504
+ homeDir,
1505
+ env
1506
+ }) {
1507
+ const path = sessionDbRecoveryReportPath(homeDir, env);
1508
+ let content;
1509
+ try {
1510
+ content = readFileSync2(path, "utf8");
1511
+ } catch (error2) {
1512
+ if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
1513
+ return { path, records: [], skippedLines: 0, readError: null };
1514
+ const readError = error2 instanceof Error ? error2.message : String(error2);
1515
+ console.error(`[session-db-recovery-report] could not read ${path}: ${readError}`);
1516
+ return { path, records: [], skippedLines: 0, readError };
1517
+ }
1518
+ let skippedLines = 0;
1519
+ const records = content.split("\n").flatMap((line) => {
1520
+ if (!line.trim()) return [];
1521
+ try {
1522
+ const value = JSON.parse(line);
1523
+ if (!isSessionDbRecoveryRecord(value)) {
1524
+ skippedLines++;
1525
+ return [];
1526
+ }
1527
+ return [value];
1528
+ } catch (error2) {
1529
+ skippedLines++;
1530
+ console.error(
1531
+ `[session-db-recovery-report] skipped malformed record in ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
1532
+ );
1533
+ return [];
1534
+ }
1535
+ });
1536
+ return { path, records, skippedLines, readError: null };
1537
+ }
1538
+ function acknowledgeSessionDbRecoveryReport(path) {
1539
+ try {
1540
+ unlinkSync(path);
1541
+ } catch (error2) {
1542
+ if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return;
1543
+ console.error(
1544
+ `[session-db-recovery-report] could not remove ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
1545
+ );
1546
+ }
1547
+ }
1548
+ function buildSessionDbRecoveryActivity(record) {
1549
+ const level = record.severity === "warning" ? "warn" : record.severity === "error" ? "error" : null;
1550
+ if (!level) return null;
1551
+ const noVerifiedPoint = record.verified_restore_point ? ` The verified restore point is ${record.verified_restore_point}.` : " No verified restore point is known.";
1552
+ switch (record.outcome) {
1553
+ case "fresh_session_db":
1554
+ return {
1555
+ level,
1556
+ metadata: withoutContractFields(record),
1557
+ message: `This runner started with a fresh session database. Earlier sessions are unavailable, and queued conversation or session references from before this start may no longer resolve.${noVerifiedPoint} Review the runner's backup configuration before relying on restored session history.`
1558
+ };
1559
+ case "restore_retried":
1560
+ return {
1561
+ level,
1562
+ metadata: withoutContractFields(record),
1563
+ message: "The initial session database restore failed; the runner will retry it. Monitor the runner backup for another restore failure."
1564
+ };
1565
+ case "replica_recovered":
1566
+ if (record.reason === "quarantine")
1567
+ return {
1568
+ level,
1569
+ metadata: withoutContractFields(record),
1570
+ message: `Session database recovery quarantined ${record.quarantined_objects ?? "unknown"} objects (${record.quarantined_bytes ?? "unknown"} bytes); ${record.quarantine_failed_objects ?? "unknown"} moves failed. Review the preserved backup at ${record.quarantine_destination ?? "an unknown destination"} before deleting it.`
1571
+ };
1572
+ if (record.reason === "prune")
1573
+ return {
1574
+ level,
1575
+ metadata: withoutContractFields(record),
1576
+ message: "Session database recovery discarded a damaged newest backup and retried. Sessions recorded after the previous backup point may be unavailable. Review the runner backup for another restore failure."
1577
+ };
1578
+ if (record.reason === "clear")
1579
+ return {
1580
+ level,
1581
+ metadata: withoutContractFields(record),
1582
+ message: "Session database recovery deleted the damaged backup and prior session history is unavailable. Review the runner backup configuration before relying on restored session history."
1583
+ };
1584
+ return null;
1585
+ case "history_rolled_back":
1586
+ return {
1587
+ level,
1588
+ metadata: withoutContractFields(record),
1589
+ message: `Session history was rolled back to verified restore point ${record.verified_restore_point ?? "unknown"}; everything after it is unavailable. Review the runner backup for another restore failure.`
1590
+ };
1591
+ case "restore_misconfigured":
1592
+ return {
1593
+ level,
1594
+ metadata: withoutContractFields(record),
1595
+ message: "This runner started with a fresh session database because it could not verify where its session history is backed up. Nothing in the backup was changed; check the runner backup configuration and permissions."
1596
+ };
1597
+ case "session_db_boot_refused":
1598
+ return {
1599
+ level,
1600
+ metadata: withoutContractFields(record),
1601
+ message: `This runner did not come online because its damaged session database could not be safely separated from its active backup or proven removed. Backed-up session history remains readable at ${record.quarantine_destination ?? "its original location or the quarantine destination named in the boot logs"}; any local session-database files that remain were left in place and nothing opened or wrote them. See the runner boot logs for SESSION-DB-LOCAL-DISCARD-FAILED details.`
1602
+ };
1603
+ default:
1604
+ return null;
1605
+ }
1606
+ }
1607
+ function withoutContractFields(record) {
1608
+ const { v: _v, event: _event, ...metadata } = record;
1609
+ return metadata;
1610
+ }
1611
+ var OUTCOMES = /* @__PURE__ */ new Set([
1612
+ "replica_recovered",
1613
+ "restore_retried",
1614
+ "fresh_session_db",
1615
+ "history_rolled_back",
1616
+ "restore_misconfigured",
1617
+ "session_db_boot_refused"
1618
+ ]);
1619
+ var STAGES = /* @__PURE__ */ new Set(["restore", "verify"]);
1620
+ var SEVERITIES = /* @__PURE__ */ new Set(["warning", "error"]);
1621
+ var NUMBER_FIELDS = [
1622
+ "litestream_exit_code",
1623
+ "attempt",
1624
+ "replica_objects",
1625
+ "replica_bytes",
1626
+ "quarantined_objects",
1627
+ "quarantine_failed_objects",
1628
+ "quarantined_bytes",
1629
+ "restore_points_tried"
1630
+ ];
1631
+ var STRING_OR_NULL_FIELDS = ["quarantine_destination", "verified_restore_point"];
1632
+ function isSessionDbRecoveryRecord(value) {
1633
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1634
+ const record = value;
1635
+ return record.v === 1 && record.event === "session_db_recovery" && typeof record.at === "string" && STAGES.has(record.stage) && OUTCOMES.has(record.outcome) && SEVERITIES.has(record.severity) && typeof record.reason === "string" && NUMBER_FIELDS.every((field) => record[field] === null || Number.isInteger(record[field])) && STRING_OR_NULL_FIELDS.every(
1636
+ (field) => record[field] === null || typeof record[field] === "string"
1637
+ );
1638
+ }
1639
+
1419
1640
  // src/lib/opencode/health.ts
1420
1641
  async function checkOpenCodeHealth(port) {
1421
1642
  try {
@@ -2453,10 +2674,10 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
2453
2674
 
2454
2675
  // src/lib/opencode/session-db-size.ts
2455
2676
  import { statSync as statSync2 } from "fs";
2456
- import { join as join2 } from "path";
2677
+ import { join as join3 } from "path";
2457
2678
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
2458
2679
  function statSessionDbBytes(homeDir) {
2459
- const dbPath = join2(homeDir, ".local", "share", "opencode", "opencode.db");
2680
+ const dbPath = join3(homeDir, ".local", "share", "opencode", "opencode.db");
2460
2681
  try {
2461
2682
  return statSync2(dbPath).size;
2462
2683
  } catch (err) {
@@ -3026,9 +3247,9 @@ function writeTunnelReadyMarker(path, agentId) {
3026
3247
  }
3027
3248
 
3028
3249
  // src/lib/openai-usage.ts
3029
- import { readFileSync as readFileSync2 } from "fs";
3250
+ import { readFileSync as readFileSync3 } from "fs";
3030
3251
  import { homedir as homedir2 } from "os";
3031
- import { join as join3 } from "path";
3252
+ import { join as join4 } from "path";
3032
3253
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
3033
3254
  var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
3034
3255
  var OpenAiUsageError = class extends Error {
@@ -3042,7 +3263,7 @@ function isLocalCredentialProblem2(err) {
3042
3263
  }
3043
3264
  function readOpenCodeChatGptCredentials() {
3044
3265
  try {
3045
- const raw = readFileSync2(join3(homedir2(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
3266
+ const raw = readFileSync3(join4(homedir2(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
3046
3267
  let parsed;
3047
3268
  try {
3048
3269
  parsed = JSON.parse(raw);
@@ -3418,12 +3639,12 @@ function createResourceUsageCollector(homeDir) {
3418
3639
  import { homedir as homedir3 } from "os";
3419
3640
 
3420
3641
  // src/lib/runner-file-sync.ts
3421
- import { join as join5 } from "path";
3642
+ import { join as join6 } from "path";
3422
3643
 
3423
3644
  // src/lib/file-push.ts
3424
3645
  import { randomUUID } from "crypto";
3425
3646
  import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
3426
- import { basename, dirname as dirname3, isAbsolute, join as join4, relative, resolve as resolve2, sep } from "path";
3647
+ import { basename, dirname as dirname3, isAbsolute, join as join5, relative, resolve as resolve2, sep } from "path";
3427
3648
  var FILE_MODE = 384;
3428
3649
  var DIRECTORY_MODE = 448;
3429
3650
  async function writePushedFile(request) {
@@ -3456,7 +3677,7 @@ async function writePushedFile(request) {
3456
3677
  const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
3457
3678
  dirname3(candidate)
3458
3679
  );
3459
- const realTarget = join4(existingAncestor, ...missingSegments, basename(candidate));
3680
+ const realTarget = join5(existingAncestor, ...missingSegments, basename(candidate));
3460
3681
  const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
3461
3682
  if (allowedDirectory === null) {
3462
3683
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
@@ -3492,7 +3713,7 @@ function expandAndValidate(requestedPath, homeDir) {
3492
3713
  if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
3493
3714
  return null;
3494
3715
  }
3495
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join4(homeDir, requestedPath.slice(2)) : requestedPath;
3716
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join5(homeDir, requestedPath.slice(2)) : requestedPath;
3496
3717
  if (expanded.split(/[/\\]/).includes("..")) {
3497
3718
  return null;
3498
3719
  }
@@ -3565,13 +3786,13 @@ function contains(realDirectory, realTarget) {
3565
3786
  async function createMissingDirectories(existingAncestor, missingSegments) {
3566
3787
  let current = existingAncestor;
3567
3788
  for (const segment of missingSegments) {
3568
- current = join4(current, segment);
3789
+ current = join5(current, segment);
3569
3790
  await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
3570
3791
  await chmod(current, DIRECTORY_MODE);
3571
3792
  }
3572
3793
  }
3573
3794
  async function writeAtomically(realTarget, content) {
3574
- const temporaryPath = join4(dirname3(realTarget), `.evident-push-${randomUUID()}.tmp`);
3795
+ const temporaryPath = join5(dirname3(realTarget), `.evident-push-${randomUUID()}.tmp`);
3575
3796
  let handle;
3576
3797
  try {
3577
3798
  handle = await open2(temporaryPath, "wx", FILE_MODE);
@@ -3701,12 +3922,12 @@ var NOT_APPLIED = {
3701
3922
  opencodeAuthApplied: false
3702
3923
  };
3703
3924
  function isClaudeCredentialPath(requestedPath, homeDir) {
3704
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join5(homeDir, requestedPath.slice(2)) : requestedPath;
3705
- return expanded === join5(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
3925
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
3926
+ return expanded === join6(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
3706
3927
  }
3707
3928
  function isOpenCodeAuthPath(requestedPath, homeDir) {
3708
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join5(homeDir, requestedPath.slice(2)) : requestedPath;
3709
- return expanded === join5(homeDir, ...OPENCODE_AUTH_SEGMENTS);
3929
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
3930
+ return expanded === join6(homeDir, ...OPENCODE_AUTH_SEGMENTS);
3710
3931
  }
3711
3932
  async function applyOne(options, file) {
3712
3933
  const label = `${file.id.slice(0, 8)} (${file.path})`;
@@ -7768,7 +7989,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
7768
7989
  if (trimmed === "") {
7769
7990
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
7770
7991
  }
7771
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join6(homeDir, trimmed.slice(2)) : trimmed;
7992
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join7(homeDir, trimmed.slice(2)) : trimmed;
7772
7993
  if (!isAbsolute2(expanded)) {
7773
7994
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
7774
7995
  }
@@ -7866,7 +8087,7 @@ function logActivity(state, entry) {
7866
8087
  const level = entry.level ?? (entry.type === "error" ? "error" : "info");
7867
8088
  if (!meetsThreshold(state, level)) return;
7868
8089
  forwardRunnerActivity(
7869
- { level, message: entry.message, error: entry.error },
8090
+ { level, message: entry.message, error: entry.error, metadata: entry.metadata },
7870
8091
  { agentId: state.agentId, authHeader: state.authHeader }
7871
8092
  );
7872
8093
  const fullEntry = {
@@ -7886,6 +8107,26 @@ function logActivity(state, entry) {
7886
8107
  }
7887
8108
  }
7888
8109
  }
8110
+ function reportSessionDbRecovery(state) {
8111
+ try {
8112
+ const report = drainSessionDbRecoveryReport({ homeDir: homedir4(), env: process.env });
8113
+ for (const record of report.records) {
8114
+ const activity = buildSessionDbRecoveryActivity(record);
8115
+ if (!activity) throw new Error("could not map session-DB recovery record");
8116
+ logActivity(state, {
8117
+ type: activity.level === "error" ? "error" : "info",
8118
+ level: activity.level,
8119
+ ...activity.level === "error" ? { error: activity.message } : { message: activity.message },
8120
+ metadata: activity.metadata
8121
+ });
8122
+ }
8123
+ acknowledgeSessionDbRecoveryReport(report.path);
8124
+ } catch (error2) {
8125
+ console.error(
8126
+ `[run] could not report session-DB recovery activity: ${error2 instanceof Error ? error2.message : String(error2)}`
8127
+ );
8128
+ }
8129
+ }
7889
8130
  function displayStatus(state) {
7890
8131
  if (!state.interactive) return;
7891
8132
  const attempt = state.connection?.reconnectAttempt ?? 0;
@@ -8081,7 +8322,7 @@ async function driveChannels(state, driver) {
8081
8322
  var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
8082
8323
  var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
8083
8324
  function sessionDbPath() {
8084
- return join6(homedir4(), ".local", "share", "opencode", "opencode.db");
8325
+ return join7(homedir4(), ".local", "share", "opencode", "opencode.db");
8085
8326
  }
8086
8327
  async function runSweep(state, driver, config) {
8087
8328
  const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
@@ -8323,7 +8564,17 @@ function scheduleClaudeUsageReporting(state, options) {
8323
8564
  setTimer: (timer) => {
8324
8565
  state.claudeUsageTimer = timer;
8325
8566
  },
8326
- fetchUsage: getClaudeUsage,
8567
+ fetchUsage: async () => {
8568
+ const usage = await getClaudeUsage();
8569
+ if (usage.ownerLookupError) {
8570
+ logActivity(state, {
8571
+ type: "info",
8572
+ level: "debug",
8573
+ message: `Claude usage owner lookup failed: ${usage.ownerLookupError}`
8574
+ });
8575
+ }
8576
+ return usage;
8577
+ },
8327
8578
  report: (usage) => reportClaudeUsage(state.agentId, state.authHeader, usage),
8328
8579
  isLocalCredentialProblem,
8329
8580
  forcedOnHint: "run `claude` to sign in",
@@ -8744,6 +8995,7 @@ async function run(options) {
8744
8995
  } else {
8745
8996
  log2(state, "Not running in a MicroVM (MICROVM_ID unset) \u2014 nothing to report", "debug");
8746
8997
  }
8998
+ reportSessionDbRecovery(state);
8747
8999
  const { timeoutMs: opencodeStartTimeoutMs, warnings: opencodeStartTimeoutWarnings } = resolveOpenCodeStartTimeoutMs(options, process.env);
8748
9000
  for (const warning2 of opencodeStartTimeoutWarnings) {
8749
9001
  logActivity(state, { type: "info", level: "warn", message: warning2 });