@evident-ai/cli 3.5.2-dev.3e69672 → 3.5.2-dev.3f3949d

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
@@ -315,14 +315,14 @@ function blank() {
315
315
  console.log();
316
316
  }
317
317
  function waitForEnter(prompt = "Press Enter to continue...") {
318
- return new Promise((resolve4) => {
318
+ return new Promise((resolve5) => {
319
319
  process.stdout.write(chalk.dim(prompt));
320
320
  const handler = () => {
321
321
  process.stdin.removeListener("data", handler);
322
322
  process.stdin.setRawMode?.(false);
323
323
  process.stdin.pause();
324
324
  console.log();
325
- resolve4();
325
+ resolve5();
326
326
  };
327
327
  if (process.stdin.isTTY) {
328
328
  process.stdin.setRawMode?.(true);
@@ -332,7 +332,7 @@ function waitForEnter(prompt = "Press Enter to continue...") {
332
332
  });
333
333
  }
334
334
  function sleep(ms) {
335
- return new Promise((resolve4) => setTimeout(resolve4, ms));
335
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
336
336
  }
337
337
 
338
338
  // src/commands/login.ts
@@ -410,19 +410,19 @@ async function tokenLogin() {
410
410
  );
411
411
  blank();
412
412
  process.stdout.write("Paste token: ");
413
- const token = await new Promise((resolve4) => {
413
+ const token = await new Promise((resolve5) => {
414
414
  let data = "";
415
415
  process.stdin.setEncoding("utf8");
416
416
  process.stdin.on("data", (chunk) => {
417
417
  data += chunk;
418
418
  });
419
419
  process.stdin.on("end", () => {
420
- resolve4(data.trim());
420
+ resolve5(data.trim());
421
421
  });
422
422
  if (process.stdin.isTTY) {
423
423
  process.stdin.once("data", (chunk) => {
424
424
  process.stdin.pause();
425
- resolve4(chunk.toString().trim());
425
+ resolve5(chunk.toString().trim());
426
426
  });
427
427
  process.stdin.resume();
428
428
  }
@@ -1158,7 +1158,7 @@ async function claudeUsage() {
1158
1158
  // src/commands/run.ts
1159
1159
  import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
1160
1160
  import { homedir as homedir7 } from "node:os";
1161
- import { isAbsolute as isAbsolute3, join as join11, parse, resolve as resolvePath2 } from "node:path";
1161
+ import { isAbsolute as isAbsolute4, join as join12, parse, resolve as resolvePath2 } from "node:path";
1162
1162
  import chalk7 from "chalk";
1163
1163
 
1164
1164
  // ../../packages/types/src/agents/index.ts
@@ -1322,7 +1322,7 @@ import ora4 from "ora";
1322
1322
  import { select as select4 } from "@inquirer/prompts";
1323
1323
 
1324
1324
  // src/lib/telemetry.ts
1325
- var CLI_VERSION = (true ? "3.5.2-dev.3e69672" : void 0) ?? process.env.npm_package_version ?? "unknown";
1325
+ var CLI_VERSION = (true ? "3.5.2-dev.3f3949d" : void 0) ?? process.env.npm_package_version ?? "unknown";
1326
1326
  function getCliVersion() {
1327
1327
  return CLI_VERSION;
1328
1328
  }
@@ -1483,20 +1483,47 @@ var EventTypes = {
1483
1483
  DEPRECATED_AGENT_KEY_ENV_USED: "cli.deprecated_agent_key_env_used"
1484
1484
  };
1485
1485
 
1486
- // src/lib/runner-activity-telemetry.ts
1487
- import { homedir as homedir2 } from "node:os";
1488
-
1489
1486
  // src/lib/opencode/session-db-reclaim.ts
1490
1487
  import { statSync as statSync2, statfsSync } from "node:fs";
1491
1488
  import { dirname as dirname2 } from "node:path";
1492
- var SESSION_DB_RECLAIM_CHUNK_PAGES = 2e3;
1489
+
1490
+ // src/lib/diagnostic-redaction.ts
1491
+ import { homedir as homedir2 } from "node:os";
1492
+ function redactHomeDirectory(message) {
1493
+ const home = homedir2();
1494
+ if (!home || home === "/") {
1495
+ return message;
1496
+ }
1497
+ const pathSegmentCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-";
1498
+ let result = "";
1499
+ let searchFrom = 0;
1500
+ let homeIndex;
1501
+ while ((homeIndex = message.indexOf(home, searchFrom)) !== -1) {
1502
+ const homeEnd = homeIndex + home.length;
1503
+ const previousCharacter = message[homeIndex - 1];
1504
+ const nextCharacter = message[homeEnd];
1505
+ const atPathBoundary = (!previousCharacter || !pathSegmentCharacters.includes(previousCharacter)) && (!nextCharacter || !pathSegmentCharacters.includes(nextCharacter));
1506
+ result += message.slice(searchFrom, homeIndex);
1507
+ result += atPathBoundary ? "~" : home;
1508
+ searchFrom = homeEnd;
1509
+ }
1510
+ return result + message.slice(searchFrom);
1511
+ }
1512
+ function redactDiagnosticText(message) {
1513
+ const redacted = message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/(?<![A-Za-z0-9_-])sk-ant-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-ant-***").replace(/(?<![A-Za-z0-9_-])sk-proj-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-proj-***").replace(/(?<![A-Za-z0-9_-])sk-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "sk-***").replace(/(?<![A-Za-z0-9_-])(gh[oprsu])_[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "$1_***").replace(/(?<![A-Za-z0-9_-])github_pat_[A-Za-z0-9_]{20,}(?![A-Za-z0-9_-])/g, "github_pat_***").replace(/https?:\/\/\S+/g, "<url>");
1514
+ return redactHomeDirectory(redacted);
1515
+ }
1516
+
1517
+ // src/lib/opencode/session-db-reclaim.ts
1518
+ var SESSION_DB_RECLAIM_CHUNK_PAGES = 100;
1493
1519
  var SESSION_DB_RECLAIM_TIME_BUDGET_MS = 500;
1494
1520
  var SESSION_DB_RECLAIM_PAGE_CEILING = 4e4;
1521
+ var SESSION_DB_RECLAIM_BUSY_TIMEOUT_MS = 5e3;
1495
1522
  function errorMessage(error2) {
1496
1523
  if (!(error2 instanceof Error)) return String(error2);
1497
1524
  return error2.cause instanceof Error ? error2.cause.message : error2.message;
1498
1525
  }
1499
- function isSessionStoreWriteFailure(error2) {
1526
+ function errorMessages(error2) {
1500
1527
  const messages = [];
1501
1528
  const visited = /* @__PURE__ */ new Set();
1502
1529
  let current = error2;
@@ -1520,12 +1547,81 @@ function isSessionStoreWriteFailure(error2) {
1520
1547
  }
1521
1548
  break;
1522
1549
  }
1523
- return messages.some(
1550
+ return messages;
1551
+ }
1552
+ function isSessionStoreWriteFailure(error2) {
1553
+ return errorMessages(error2).some(
1524
1554
  (message) => /(?:database or disk is full|disk is full|no space left on device|SQLITE_(?:FULL|IOERR)(?:\b|_))/i.test(
1525
1555
  message
1526
1556
  )
1527
1557
  );
1528
1558
  }
1559
+ var SQLITE_CAUSE_TOKEN = /(?:^|cause:\s+)(SQLiteError:\s+[A-Za-z][A-Za-z0-9]*(?:\s+[A-Za-z][A-Za-z0-9]*){0,7})(?=\s*(?:[)\]"',;]|$))/i;
1560
+ var EFFECT_SQL_CAUSE_TOKEN = /(?:^|cause:\s+)(effect\/sql\/SqlError\/[A-Za-z][A-Za-z0-9_]*)/i;
1561
+ var PARAMS_FIELD = /\bparams\s*(?:=|:)/i;
1562
+ function isCauseBearingText(text) {
1563
+ return /^(?:SQLiteError:\s+|effect\/sql\/SqlError\/)/i.test(text.trim()) || /(?:^|\s)cause:\s+(?:SQLiteError:\s+|effect\/sql\/SqlError\/)/i.test(text);
1564
+ }
1565
+ function structuredLogFields(line) {
1566
+ const fields = [];
1567
+ for (let index = 0; index < line.length; index++) {
1568
+ if (line[index] === '"') {
1569
+ index++;
1570
+ while (index < line.length) {
1571
+ if (line[index] === "\\") {
1572
+ index++;
1573
+ } else if (line[index] === '"') {
1574
+ break;
1575
+ }
1576
+ index++;
1577
+ }
1578
+ continue;
1579
+ }
1580
+ const atFieldBoundary = index === 0 || /\s/.test(line[index - 1]);
1581
+ const key = line.startsWith("error=", index) ? "error=" : line.startsWith("cause=", index) ? "cause=" : null;
1582
+ if (!atFieldBoundary || key === null) continue;
1583
+ const valueStart = index + key.length;
1584
+ if (line[valueStart] !== '"') {
1585
+ const valueEnd2 = line.indexOf(" ", valueStart);
1586
+ fields.push(line.slice(valueStart, valueEnd2 === -1 ? line.length : valueEnd2));
1587
+ index = valueEnd2 === -1 ? line.length : valueEnd2;
1588
+ continue;
1589
+ }
1590
+ let valueEnd = valueStart + 1;
1591
+ for (; valueEnd < line.length; valueEnd++) {
1592
+ if (line[valueEnd] === "\\") {
1593
+ valueEnd++;
1594
+ } else if (line[valueEnd] === '"') {
1595
+ break;
1596
+ }
1597
+ }
1598
+ fields.push(line.slice(valueStart + 1, valueEnd));
1599
+ index = valueEnd;
1600
+ }
1601
+ return fields;
1602
+ }
1603
+ function structuredCauseMessages(text) {
1604
+ const fields = structuredLogFields(text);
1605
+ if (fields.length > 0) return fields;
1606
+ return isCauseBearingText(text) ? [text] : [];
1607
+ }
1608
+ function causeTokenFromText(text) {
1609
+ const paramsIndex = text.search(PARAMS_FIELD);
1610
+ for (const pattern of [SQLITE_CAUSE_TOKEN, EFFECT_SQL_CAUSE_TOKEN]) {
1611
+ const match = pattern.exec(text);
1612
+ if (match?.[1] && (paramsIndex === -1 || match.index < paramsIndex)) {
1613
+ return match[1];
1614
+ }
1615
+ }
1616
+ return null;
1617
+ }
1618
+ function sessionStoreCauseToken(value) {
1619
+ for (const message of structuredCauseMessages(value)) {
1620
+ const token = causeTokenFromText(message);
1621
+ if (token !== null) return redactDiagnosticText(token).slice(0, 120);
1622
+ }
1623
+ return null;
1624
+ }
1529
1625
  function insufficientSpaceReason(dbPath, requiredBytes) {
1530
1626
  try {
1531
1627
  const fsStats = statfsSync(dirname2(dbPath));
@@ -1547,6 +1643,7 @@ function readFreelistCount(db) {
1547
1643
  return db.prepare("PRAGMA freelist_count").get().freelist_count;
1548
1644
  }
1549
1645
  function readCheckpointResult(db) {
1646
+ db.exec("PRAGMA busy_timeout=0");
1550
1647
  const row = db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
1551
1648
  return { busy: row.busy !== 0, log: row.log, checkpointed: row.checkpointed };
1552
1649
  }
@@ -1564,7 +1661,7 @@ async function probeReclaimAvailability(input) {
1564
1661
  try {
1565
1662
  const db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
1566
1663
  try {
1567
- db.exec("PRAGMA busy_timeout=5000");
1664
+ db.exec(`PRAGMA busy_timeout=${SESSION_DB_RECLAIM_BUSY_TIMEOUT_MS}`);
1568
1665
  autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
1569
1666
  } finally {
1570
1667
  db.close();
@@ -1584,7 +1681,8 @@ async function reclaimSessionDbSpace(input) {
1584
1681
  timeBudgetMs = SESSION_DB_RECLAIM_TIME_BUDGET_MS,
1585
1682
  chunkPages = SESSION_DB_RECLAIM_CHUNK_PAGES,
1586
1683
  now = () => performance.now(),
1587
- allowFullVacuum = true
1684
+ hasLiveTurn = false,
1685
+ hasProvenanceAnomaly = false
1588
1686
  } = input;
1589
1687
  let sqlite;
1590
1688
  try {
@@ -1600,17 +1698,19 @@ async function reclaimSessionDbSpace(input) {
1600
1698
  let db;
1601
1699
  try {
1602
1700
  db = new DatabaseSync(dbPath);
1603
- db.exec("PRAGMA busy_timeout=5000");
1701
+ db.exec(`PRAGMA busy_timeout=${SESSION_DB_RECLAIM_BUSY_TIMEOUT_MS}`);
1604
1702
  const autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
1605
1703
  if (autoVacuum === 0) {
1606
- if (!allowFullVacuum) {
1607
- console.warn(
1608
- `[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: a session turn is live`
1609
- );
1704
+ if (hasLiveTurn || hasProvenanceAnomaly) {
1705
+ const detail = [
1706
+ ...hasLiveTurn ? ["a session turn is live"] : [],
1707
+ ...hasProvenanceAnomaly ? ["session-DB provenance is anomalous"] : []
1708
+ ].join(" and ");
1709
+ console.warn(`[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: ${detail}`);
1610
1710
  return {
1611
1711
  ok: false,
1612
1712
  skipped: "full-vacuum-blocked",
1613
- detail: "a session turn is live"
1713
+ detail
1614
1714
  };
1615
1715
  }
1616
1716
  const fileBytesForGuard = statSync2(dbPath).size;
@@ -1628,7 +1728,9 @@ async function reclaimSessionDbSpace(input) {
1628
1728
  db.exec("VACUUM");
1629
1729
  const afterBytes = readLogicalBytes(db);
1630
1730
  const freelistAfter = readFreelistCount(db);
1731
+ const checkpointStartedAt = now();
1631
1732
  const checkpoint = readCheckpointResult(db);
1733
+ const checkpointElapsedMs = Math.max(0, now() - checkpointStartedAt);
1632
1734
  return {
1633
1735
  ok: true,
1634
1736
  mode: "convert",
@@ -1639,10 +1741,18 @@ async function reclaimSessionDbSpace(input) {
1639
1741
  pagesReclaimed: Math.max(0, freelistBefore - freelistAfter),
1640
1742
  stoppedBy: "freelist-drained",
1641
1743
  elapsedMs: Math.max(0, now() - startedAt),
1744
+ checkpointElapsedMs,
1642
1745
  checkpoint
1643
1746
  };
1644
1747
  }
1645
1748
  if (autoVacuum === 2) {
1749
+ if (hasLiveTurn) {
1750
+ const detail = "a session turn is live";
1751
+ console.warn(
1752
+ `[reclaimSessionDbSpace] skipping incremental reclaim of ${dbPath}: ${detail}`
1753
+ );
1754
+ return { ok: false, skipped: "live-turn", detail };
1755
+ }
1646
1756
  const beforeBytes = readLogicalBytes(db);
1647
1757
  const freelistBefore = readFreelistCount(db);
1648
1758
  const startedAt = now();
@@ -1667,7 +1777,9 @@ async function reclaimSessionDbSpace(input) {
1667
1777
  freelistAfter = readFreelistCount(db);
1668
1778
  }
1669
1779
  const afterBytes = readLogicalBytes(db);
1780
+ const checkpointStartedAt = now();
1670
1781
  const checkpoint = readCheckpointResult(db);
1782
+ const checkpointElapsedMs = Math.max(0, now() - checkpointStartedAt);
1671
1783
  return {
1672
1784
  ok: true,
1673
1785
  mode: "incremental",
@@ -1678,6 +1790,7 @@ async function reclaimSessionDbSpace(input) {
1678
1790
  pagesReclaimed: Math.max(0, freelistBefore - freelistAfter),
1679
1791
  stoppedBy,
1680
1792
  elapsedMs: Math.max(0, now() - startedAt),
1793
+ checkpointElapsedMs,
1681
1794
  checkpoint
1682
1795
  };
1683
1796
  }
@@ -1697,6 +1810,263 @@ async function reclaimSessionDbSpace(input) {
1697
1810
  }
1698
1811
  }
1699
1812
 
1813
+ // src/lib/opencode/dependency-install-diagnostic.ts
1814
+ import { lstatSync, realpathSync } from "node:fs";
1815
+ import { basename, dirname as dirname3, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
1816
+ var DEPENDENCY_INSTALL_FAILURE_MESSAGE = "background dependency install failed";
1817
+ var MAX_DISTINCT_DIAGNOSTICS = 32;
1818
+ var EFFECT = "OpenCode could not install background dependencies for this scope; its plugins and tools may be unavailable";
1819
+ function readOpenCodeLogField(line, key) {
1820
+ const match = line.match(new RegExp(`(?:^|\\s)${key}=(?:"((?:\\\\.|[^"])*)"|(\\S+))`));
1821
+ if (!match) return void 0;
1822
+ if (match[1] === void 0) return match[2];
1823
+ return match[1].replace(/\\(["\\n])/g, (_match, escaped) => {
1824
+ return escaped === "n" ? "\n" : escaped;
1825
+ });
1826
+ }
1827
+ function parseTrigger(line) {
1828
+ if (readOpenCodeLogField(line, "message") !== DEPENDENCY_INSTALL_FAILURE_MESSAGE) {
1829
+ return null;
1830
+ }
1831
+ const error2 = readOpenCodeLogField(line, "error");
1832
+ if (!error2 || !/\bEACCES\b/.test(error2)) return null;
1833
+ const errnoMatch = error2.match(/\bEACCES:\s*permission denied,\s*([a-z_]+)\s+'([^']*)'/);
1834
+ const capturedPath = errnoMatch?.[2];
1835
+ return {
1836
+ dir: readOpenCodeLogField(line, "dir"),
1837
+ op: errnoMatch?.[1],
1838
+ path: capturedPath && isAbsolute(capturedPath) ? capturedPath : void 0
1839
+ };
1840
+ }
1841
+ function getErrorCode(error2) {
1842
+ if (typeof error2 === "object" && error2 !== null && "code" in error2) {
1843
+ const code = error2.code;
1844
+ if (typeof code === "string") return code;
1845
+ }
1846
+ return "UNKNOWN";
1847
+ }
1848
+ function isWithin(path, root) {
1849
+ const candidate = resolve(path);
1850
+ const resolvedRoot = resolve(root);
1851
+ return candidate === resolvedRoot || candidate.startsWith(`${resolvedRoot}${sep}`);
1852
+ }
1853
+ function rootSpecs(context) {
1854
+ return [
1855
+ { token: "~", configured: resolve(context.homeDir) },
1856
+ { token: "$WORKSPACE", configured: resolve(context.cwd) }
1857
+ ];
1858
+ }
1859
+ function resolveRoots(context) {
1860
+ const resolved = [];
1861
+ const unresolved = [];
1862
+ for (const spec of rootSpecs(context)) {
1863
+ try {
1864
+ resolved.push({ ...spec, real: realpathSync(spec.configured) });
1865
+ } catch (_error) {
1866
+ unresolved.push(spec);
1867
+ }
1868
+ }
1869
+ return { resolved, unresolved };
1870
+ }
1871
+ function resolveNearestAncestor(failingPath, remaining, error2) {
1872
+ let ancestor = dirname3(failingPath);
1873
+ const suffix = [basename(failingPath), ...remaining];
1874
+ while (ancestor) {
1875
+ try {
1876
+ return {
1877
+ path: resolve(realpathSync(ancestor), ...suffix),
1878
+ failure: { path: resolve(failingPath), error: error2 }
1879
+ };
1880
+ } catch (ancestorError) {
1881
+ if (ancestor === dirname3(ancestor) || !getErrorCode(ancestorError).startsWith("E")) {
1882
+ return null;
1883
+ }
1884
+ suffix.unshift(basename(ancestor));
1885
+ ancestor = dirname3(ancestor);
1886
+ }
1887
+ }
1888
+ return null;
1889
+ }
1890
+ function resolveExistingPath(absolutePath) {
1891
+ if (!isAbsolute(absolutePath)) return null;
1892
+ const candidate = resolve(absolutePath);
1893
+ const remaining = [];
1894
+ let current = candidate;
1895
+ while (current) {
1896
+ try {
1897
+ const stat2 = lstatSync(current);
1898
+ if (stat2.isSymbolicLink()) {
1899
+ if (remaining.length === 0) {
1900
+ return { path: join2(realpathSync(dirname3(current)), basename(current)) };
1901
+ }
1902
+ return { path: resolve(realpathSync(current), ...remaining) };
1903
+ }
1904
+ return { path: resolve(realpathSync(current), ...remaining) };
1905
+ } catch (error2) {
1906
+ const code = getErrorCode(error2);
1907
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
1908
+ return resolveNearestAncestor(current, remaining, error2);
1909
+ }
1910
+ }
1911
+ if (current === dirname3(current)) return null;
1912
+ remaining.unshift(basename(current));
1913
+ current = dirname3(current);
1914
+ }
1915
+ return null;
1916
+ }
1917
+ function tokenise(absolutePath, roots) {
1918
+ const pathResolution = resolveExistingPath(absolutePath);
1919
+ if (!pathResolution) return null;
1920
+ const root = roots.reduce((selected, candidate) => {
1921
+ if (!isWithin(pathResolution.path, candidate.real)) return selected;
1922
+ if (!selected || candidate.real.length > selected.real.length || candidate.real.length === selected.real.length && candidate.token === "$WORKSPACE") {
1923
+ return candidate;
1924
+ }
1925
+ return selected;
1926
+ }, null);
1927
+ if (!root) return null;
1928
+ const suffix = relative(root.real, pathResolution.path);
1929
+ return {
1930
+ value: suffix ? `${root.token}/${suffix.split(sep).join("/")}` : root.token,
1931
+ root,
1932
+ resolutionFailure: pathResolution.failure
1933
+ };
1934
+ }
1935
+ function classifyScope(dir, roots) {
1936
+ if (!dir) return "other";
1937
+ const tokenisedDir = tokenise(dir, roots.resolved);
1938
+ if (tokenisedDir) return tokenisedDir.root.token === "$WORKSPACE" ? "workspace" : "global";
1939
+ for (const spec of roots.unresolved) {
1940
+ if (isWithin(dir, spec.configured)) {
1941
+ return spec.token === "$WORKSPACE" ? "workspace" : "global";
1942
+ }
1943
+ }
1944
+ return "other";
1945
+ }
1946
+ function unresolvedRootForPath(path, unresolved) {
1947
+ return unresolved.find((spec) => isWithin(path, spec.configured));
1948
+ }
1949
+ function statType(stat2) {
1950
+ if (stat2.isDirectory()) return "d";
1951
+ if (stat2.isFile()) return "f";
1952
+ if (stat2.isSymbolicLink()) return "l";
1953
+ return "o";
1954
+ }
1955
+ function renderStat(name, stat2) {
1956
+ const mode = (stat2.mode & 4095).toString(8).padStart(4, "0");
1957
+ return `${name}:${statType(stat2)}:${stat2.uid}:${stat2.gid}:${mode}`;
1958
+ }
1959
+ function renderStatError(name, error2) {
1960
+ const code = getErrorCode(error2);
1961
+ return `${name}:${renderErrorCode(code)}`;
1962
+ }
1963
+ function renderErrorCode(code) {
1964
+ if (code === "ENOENT") return "missing";
1965
+ return code.startsWith("E") ? code : `E${code}`;
1966
+ }
1967
+ function inspectChain(rootReal, rootToken, absolutePath, resolutionFailure) {
1968
+ const target = resolve(absolutePath);
1969
+ const relativePath = relative(rootReal, target);
1970
+ const components = relativePath ? relativePath.split(sep) : [];
1971
+ const entries = [];
1972
+ let current = rootReal;
1973
+ let status2 = "complete";
1974
+ let finalStat;
1975
+ for (const name of [rootToken, ...components]) {
1976
+ if (name !== rootToken) current = join2(current, name);
1977
+ if (resolutionFailure && resolve(current) === resolve(resolutionFailure.path)) {
1978
+ entries.push(renderStatError(name, resolutionFailure.error));
1979
+ status2 = "stat_failed";
1980
+ break;
1981
+ }
1982
+ try {
1983
+ const stat2 = lstatSync(current);
1984
+ entries.push(renderStat(name, stat2));
1985
+ finalStat = stat2;
1986
+ } catch (error2) {
1987
+ entries.push(renderStatError(name, error2));
1988
+ if (getErrorCode(error2) !== "ENOENT") status2 = "stat_failed";
1989
+ break;
1990
+ }
1991
+ }
1992
+ let symlinkTarget;
1993
+ let symlinkMeta;
1994
+ if (finalStat?.isSymbolicLink() && entries.length === components.length + 1) {
1995
+ try {
1996
+ symlinkTarget = realpathSync(current);
1997
+ try {
1998
+ symlinkMeta = renderStat("", lstatSync(symlinkTarget)).slice(1);
1999
+ } catch (error2) {
2000
+ symlinkMeta = renderErrorCode(getErrorCode(error2));
2001
+ status2 = "stat_failed";
2002
+ }
2003
+ } catch (error2) {
2004
+ symlinkTarget = "<unresolved>";
2005
+ symlinkMeta = renderErrorCode(getErrorCode(error2));
2006
+ status2 = "stat_failed";
2007
+ }
2008
+ }
2009
+ return { status: status2, chain: entries.join(","), symlinkTarget, symlinkMeta };
2010
+ }
2011
+ function buildDiagnostic(line, context) {
2012
+ const trigger = parseTrigger(line);
2013
+ if (!trigger) return null;
2014
+ const roots = resolveRoots(context);
2015
+ const scope = classifyScope(trigger.dir, roots);
2016
+ const pathSource = trigger.path ? "error" : trigger.dir ? "dir" : "none";
2017
+ const inspectedPath = trigger.path ?? trigger.dir;
2018
+ const microvmId = context.microvmId?.trim() || "unknown";
2019
+ const euid = context.euid === void 0 ? "unavailable" : String(context.euid);
2020
+ const egid = context.egid === void 0 ? "unavailable" : String(context.egid);
2021
+ const op = trigger.op ?? "unknown";
2022
+ if (!inspectedPath) {
2023
+ return {
2024
+ event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=no_path path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=unknown cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=unavailable`,
2025
+ localDetail: null
2026
+ };
2027
+ }
2028
+ const tokenisedPath = tokenise(inspectedPath, roots.resolved);
2029
+ if (!tokenisedPath) {
2030
+ const unresolvedRoot = unresolvedRootForPath(inspectedPath, roots.unresolved);
2031
+ if (unresolvedRoot) {
2032
+ return {
2033
+ event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=root_unresolved path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=unknown cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=unavailable`,
2034
+ localDetail: null
2035
+ };
2036
+ }
2037
+ return {
2038
+ event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=outside_roots path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=<outside-approved-roots> cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=unavailable`,
2039
+ localDetail: `OPENCODE-DEPENDENCY-INSTALL-EACCES (local only): outside approved roots: ${inspectedPath}`
2040
+ };
2041
+ }
2042
+ const inspection = inspectChain(
2043
+ tokenisedPath.root.real,
2044
+ tokenisedPath.root.token,
2045
+ inspectedPath,
2046
+ tokenisedPath.resolutionFailure
2047
+ );
2048
+ const optionalSymlinkFields = inspection.symlinkTarget ? ` symlink_target=${tokenise(inspection.symlinkTarget, roots.resolved)?.value ?? "<outside-approved-roots>"}` + (inspection.symlinkMeta ? ` symlink_meta=${inspection.symlinkMeta}` : "") : "";
2049
+ return {
2050
+ event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=${inspection.status} path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=${tokenisedPath.value} cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=${inspection.chain}${optionalSymlinkFields}`,
2051
+ localDetail: null
2052
+ };
2053
+ }
2054
+ function createDependencyInstallDiagnosticReporter({
2055
+ context,
2056
+ emitEvent: emitEvent2,
2057
+ emitLocal
2058
+ }) {
2059
+ const emittedEvents = /* @__PURE__ */ new Set();
2060
+ return (line) => {
2061
+ const diagnostic = buildDiagnostic(line, context);
2062
+ if (!diagnostic || emittedEvents.has(diagnostic.event)) return;
2063
+ if (emittedEvents.size >= MAX_DISTINCT_DIAGNOSTICS) return;
2064
+ emittedEvents.add(diagnostic.event);
2065
+ emitEvent2(diagnostic.event);
2066
+ if (diagnostic.localDetail) emitLocal(diagnostic.localDetail);
2067
+ };
2068
+ }
2069
+
1700
2070
  // src/lib/runner-activity-telemetry.ts
1701
2071
  var FORWARDED_LEVELS_BY_SOURCE = {
1702
2072
  "cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
@@ -1728,7 +2098,17 @@ function parseOpenCodeLogLine(line) {
1728
2098
  }
1729
2099
  var MAX_LINE_BUFFER_BYTES = 16 * 1024;
1730
2100
  var REPEAT_SUPPRESSION_WINDOW_MS = 6e4;
1731
- function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure) {
2101
+ function sanitiseDependencyInstallWarning(line) {
2102
+ const withoutDirectory = line.replace(
2103
+ /((?:^|\s)dir=)(?:"(?:\\.|[^"])*"|\/[^\s)"']+|\S+)/g,
2104
+ "$1<path>"
2105
+ );
2106
+ return withoutDirectory.replace(
2107
+ /((?:^|\s)error=)"((?:\\.|[^"])*)"/,
2108
+ (_match, prefix, error2) => `${prefix}"${error2.replace(/'\/[^']*'/g, "'<path>'")}"`
2109
+ );
2110
+ }
2111
+ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure, onDependencyInstallFailure) {
1732
2112
  let buffer = Buffer.alloc(0);
1733
2113
  const repeatStates = /* @__PURE__ */ new Map();
1734
2114
  const forwardLine = (line, parsed, repeatsSuppressed = 0) => {
@@ -1740,7 +2120,7 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
1740
2120
  forwardRunnerActivity(
1741
2121
  {
1742
2122
  level: parsed.level,
1743
- error: line,
2123
+ error: parsed.message === DEPENDENCY_INSTALL_FAILURE_MESSAGE ? sanitiseDependencyInstallWarning(line) : line,
1744
2124
  metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
1745
2125
  source: "opencode"
1746
2126
  },
@@ -1758,18 +2138,7 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
1758
2138
  repeatStates.delete(key);
1759
2139
  }
1760
2140
  };
1761
- const flushLine = (line) => {
1762
- if (onSessionStoreWriteFailure && isSessionStoreWriteFailure(line)) {
1763
- try {
1764
- onSessionStoreWriteFailure();
1765
- } catch (err) {
1766
- reportTelemetryDiagnostic(
1767
- `Runner activity telemetry could not record session-store write failure: ${err instanceof Error ? err.message : String(err)}`
1768
- );
1769
- }
1770
- }
1771
- const parsed = parseOpenCodeLogLine(line);
1772
- if (!parsed) return;
2141
+ const forwardWithRepeatSuppression = (line, parsed) => {
1773
2142
  const now = Date.now();
1774
2143
  const key = parsed.message === void 0 ? void 0 : `${parsed.level}\0${parsed.message}\0${parsed.sessionID ?? ""}\0${parsed.messageID ?? ""}`;
1775
2144
  expireRepeatStates(now, key);
@@ -1792,6 +2161,35 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
1792
2161
  previous.parsed = parsed;
1793
2162
  previous.repeatsSuppressed++;
1794
2163
  };
2164
+ const flushLine = (line) => {
2165
+ const parsed = parseOpenCodeLogLine(line);
2166
+ const isDiskFailure = isSessionStoreWriteFailure(line);
2167
+ const cause = sessionStoreCauseToken(line);
2168
+ if (onSessionStoreWriteFailure && (isDiskFailure || cause !== null)) {
2169
+ try {
2170
+ onSessionStoreWriteFailure({
2171
+ isDiskFailure,
2172
+ sessionId: parsed?.sessionID ?? null,
2173
+ cause
2174
+ });
2175
+ } catch (err) {
2176
+ reportTelemetryDiagnostic(
2177
+ `Runner activity telemetry could not record session-store write failure: ${err instanceof Error ? err.message : String(err)}`
2178
+ );
2179
+ }
2180
+ }
2181
+ if (!parsed) return;
2182
+ forwardWithRepeatSuppression(line, parsed);
2183
+ if (onDependencyInstallFailure && parsed.message === DEPENDENCY_INSTALL_FAILURE_MESSAGE) {
2184
+ try {
2185
+ onDependencyInstallFailure(line);
2186
+ } catch (err) {
2187
+ reportTelemetryDiagnostic(
2188
+ `Runner activity telemetry could not record dependency-install failure diagnostic: ${err instanceof Error ? err.message : String(err)}`
2189
+ );
2190
+ }
2191
+ }
2192
+ };
1795
2193
  const forward = ((chunk) => {
1796
2194
  buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8")]);
1797
2195
  let newlineIndex;
@@ -1817,34 +2215,38 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
1817
2215
  var MAX_MESSAGE_LENGTH = 500;
1818
2216
  var MAX_METADATA_VALUE_LENGTH = 200;
1819
2217
  var MAX_METADATA_ENTRIES = 20;
1820
- var TRUNCATION_MARKER = "\u2026";
1821
- function redactHomeDirectory(message) {
1822
- const home = homedir2();
1823
- if (!home || home === "/") {
1824
- return message;
1825
- }
1826
- const pathSegmentCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-";
1827
- let result = "";
1828
- let searchFrom = 0;
1829
- let homeIndex;
1830
- while ((homeIndex = message.indexOf(home, searchFrom)) !== -1) {
1831
- const homeEnd = homeIndex + home.length;
1832
- const previousCharacter = message[homeIndex - 1];
1833
- const nextCharacter = message[homeEnd];
1834
- const atPathBoundary = home === "/" || (!previousCharacter || !pathSegmentCharacters.includes(previousCharacter)) && (!nextCharacter || !pathSegmentCharacters.includes(nextCharacter));
1835
- result += message.slice(searchFrom, homeIndex);
1836
- result += atPathBoundary ? "~" : home;
1837
- searchFrom = homeEnd;
1838
- }
1839
- return result + message.slice(searchFrom);
1840
- }
1841
- function redactDiagnosticText(message) {
1842
- const redacted = message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/(?<![A-Za-z0-9_-])sk-ant-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-ant-***").replace(/(?<![A-Za-z0-9_-])sk-proj-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-proj-***").replace(/(?<![A-Za-z0-9_-])sk-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "sk-***").replace(/(?<![A-Za-z0-9_-])(gh[oprsu])_[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "$1_***").replace(/(?<![A-Za-z0-9_-])github_pat_[A-Za-z0-9_]{20,}(?![A-Za-z0-9_-])/g, "github_pat_***").replace(/https?:\/\/\S+/g, "<url>");
1843
- return redactHomeDirectory(redacted);
1844
- }
1845
2218
  function truncate(message) {
1846
2219
  if (message.length <= MAX_MESSAGE_LENGTH) return message;
1847
- return message.slice(0, MAX_MESSAGE_LENGTH - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
2220
+ let dropped = message.length - MAX_MESSAGE_LENGTH;
2221
+ let marker = `\u2026${dropped} chars omitted\u2026`;
2222
+ let previousDropped = -1;
2223
+ while (dropped !== previousDropped) {
2224
+ previousDropped = dropped;
2225
+ const nextDropped = message.length - MAX_MESSAGE_LENGTH + marker.length;
2226
+ dropped = nextDropped;
2227
+ marker = `\u2026${dropped} chars omitted\u2026`;
2228
+ }
2229
+ const remaining = MAX_MESSAGE_LENGTH - marker.length;
2230
+ const headLength = Math.ceil(remaining * 0.6);
2231
+ const tailLength = remaining - headLength;
2232
+ const effectMatch = message.startsWith("OPENCODE-DEPENDENCY-INSTALL-EACCES:") ? message.match(/ effect="[^"]*"/) : null;
2233
+ if (effectMatch?.index !== void 0) {
2234
+ const effectEnd = effectMatch.index + effectMatch[0].length;
2235
+ if (effectEnd > headLength && effectEnd < message.length) {
2236
+ const effectLength = effectMatch[0].length;
2237
+ const nonEffectLength = remaining - effectLength;
2238
+ const preservedTailLength = Math.min(
2239
+ message.length - effectEnd,
2240
+ nonEffectLength - Math.min(effectMatch.index, Math.ceil(nonEffectLength * 0.6))
2241
+ );
2242
+ const preservedHeadLength = Math.min(
2243
+ effectMatch.index,
2244
+ nonEffectLength - preservedTailLength
2245
+ );
2246
+ return message.slice(0, preservedHeadLength) + marker + message.slice(effectMatch.index, effectEnd) + message.slice(message.length - preservedTailLength);
2247
+ }
2248
+ }
2249
+ return message.slice(0, headLength) + marker + message.slice(message.length - tailLength);
1848
2250
  }
1849
2251
  function sanitiseMetadata(metadata) {
1850
2252
  if (!metadata) return {};
@@ -1921,10 +2323,10 @@ function forwardRunnerActivity(entry, context) {
1921
2323
 
1922
2324
  // src/lib/opencode/session-db-recovery-report.ts
1923
2325
  import { readFileSync as readFileSync2, unlinkSync } from "node:fs";
1924
- import { join as join2 } from "node:path";
2326
+ import { join as join3 } from "node:path";
1925
2327
  function sessionDbRecoveryReportPath(homeDir, env) {
1926
2328
  const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
1927
- return override || join2(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
2329
+ return override || join3(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
1928
2330
  }
1929
2331
  function drainSessionDbRecoveryReport({
1930
2332
  homeDir,
@@ -2054,12 +2456,23 @@ function buildSessionDbRecoveryActivity(record) {
2054
2456
  metadata: withoutContractFields(record),
2055
2457
  message: `This runner started without restoring session history 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.${replication}`
2056
2458
  };
2057
- case "session_db_boot_refused":
2058
- return {
2059
- level,
2060
- metadata: withoutContractFields(record),
2061
- 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.`
2062
- };
2459
+ case "session_db_boot_refused": {
2460
+ const message = (() => {
2461
+ switch (record.reason) {
2462
+ case "local_discard_failed":
2463
+ return `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.`;
2464
+ case "replica_separation_unproven":
2465
+ return `This runner did not come online because its damaged session database could not be safely separated from its active backup. The SESSION-DB-REPLICA-SEPARATION-UNVERIFIED boot log contains the partial-quarantine moved and remaining counts. 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.`;
2466
+ case "verification_timeout_before_separation":
2467
+ return "This runner did not come online because session database verification timed out after detecting corruption, before replica separation was attempted. No replica progress is claimed. See SESSION-DB-VERIFY-TIMEOUT in the runner boot logs before retrying.";
2468
+ case "verification_timeout_during_separation":
2469
+ return "This runner did not come online because session database verification timed out while replica separation was in progress. Separation completion and object removal were not proven. See SESSION-DB-VERIFY-TIMEOUT in the runner boot logs before retrying.";
2470
+ default:
2471
+ return "This runner did not come online because session database recovery could not be completed. Inspect the runner boot logs for the refusal reason before retrying.";
2472
+ }
2473
+ })();
2474
+ return { level, metadata: withoutContractFields(record), message };
2475
+ }
2063
2476
  case "schema_provenance_mismatch":
2064
2477
  return {
2065
2478
  level,
@@ -2186,7 +2599,7 @@ async function waitForOpenCodeHealth(port, timeoutMs = 3e4) {
2186
2599
  if (health.healthy || health.authFailed) {
2187
2600
  return health;
2188
2601
  }
2189
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
2602
+ await new Promise((resolve5) => setTimeout(resolve5, 1e3));
2190
2603
  }
2191
2604
  return { healthy: false, error: "Timeout waiting for OpenCode to be healthy" };
2192
2605
  }
@@ -2197,7 +2610,7 @@ async function waitForOpenCode2Health(port, password, timeoutMs = 3e4) {
2197
2610
  if (health.healthy || health.authFailed) {
2198
2611
  return health;
2199
2612
  }
2200
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
2613
+ await new Promise((resolve5) => setTimeout(resolve5, 1e3));
2201
2614
  }
2202
2615
  return { healthy: false, error: "Timeout waiting for OpenCode to be healthy" };
2203
2616
  }
@@ -2464,7 +2877,18 @@ async function deleteSession(port, id, client) {
2464
2877
  { method: "DELETE" },
2465
2878
  { allowStatuses: ALL_HTTP_STATUSES }
2466
2879
  );
2467
- return { ok: res.status >= 200 && res.status < 300, status: res.status };
2880
+ if (res.status >= 200 && res.status < 300) {
2881
+ return { ok: true, status: res.status };
2882
+ }
2883
+ try {
2884
+ const body = await res.text();
2885
+ return body ? { ok: false, status: res.status, body } : { ok: false, status: res.status };
2886
+ } catch (error2) {
2887
+ console.error(
2888
+ `[deleteSession] DELETE /session/${id} response body read failed for HTTP ${res.status}: ${error2 instanceof Error ? error2.message : String(error2)}`
2889
+ );
2890
+ return { ok: false, status: res.status };
2891
+ }
2468
2892
  } catch (error2) {
2469
2893
  console.error(
2470
2894
  `[deleteSession] DELETE /session/${id} failed: ${error2 instanceof Error ? error2.message : String(error2)}`
@@ -2776,7 +3200,7 @@ async function sendPromptAsync(port, sessionId, content, options, attachments, c
2776
3200
  return dispatchedUserMessageId;
2777
3201
  }
2778
3202
  if (attempt < READ_BACK_ATTEMPTS - 1) {
2779
- await new Promise((resolve4) => setTimeout(resolve4, READ_BACK_DELAY_MS));
3203
+ await new Promise((resolve5) => setTimeout(resolve5, READ_BACK_DELAY_MS));
2780
3204
  }
2781
3205
  }
2782
3206
  return null;
@@ -2948,9 +3372,11 @@ function sumAssistantUsage(messages) {
2948
3372
  let cacheWriteSum = 0;
2949
3373
  let costSum = 0;
2950
3374
  let sawCost = false;
3375
+ let toolCallCount = 0;
2951
3376
  let modelId = null;
2952
3377
  let providerId = null;
2953
3378
  for (const m of selected) {
3379
+ toolCallCount += m.parts?.filter((part) => part.type === "tool").length ?? 0;
2954
3380
  const info = m.info;
2955
3381
  if (!info) continue;
2956
3382
  const tokens = info.tokens;
@@ -2987,7 +3413,8 @@ function sumAssistantUsage(messages) {
2987
3413
  usage_tokens_cache_write: cacheWriteSum,
2988
3414
  // NULL means OpenCode never reported a cost; it is distinct from a genuine
2989
3415
  // zero-cost message, which sets `sawCost` with `costSum === 0`.
2990
- usage_cost_usd: sawCost ? costSum : null
3416
+ usage_cost_usd: sawCost ? costSum : null,
3417
+ turn_tool_calls: toolCallCount
2991
3418
  };
2992
3419
  }
2993
3420
  function messageUsage(messages, userMessageId) {
@@ -3064,6 +3491,18 @@ function messageError(messages, userMessageId) {
3064
3491
  ) : void 0;
3065
3492
  return typeof toolPart?.tool === "string" && toolPart.tool.length > 0 ? `${rendered} (tool "${toolPart.tool}")` : rendered;
3066
3493
  }
3494
+ function describeAbandonedReplyShape(messages, userMessageId) {
3495
+ const reply = findLastAssistantReplyFor(messages, userMessageId);
3496
+ if (!reply) return null;
3497
+ const parts = Array.isArray(reply.parts) ? reply.parts : [];
3498
+ const lastTool = [...parts].reverse().find((part) => part?.type === "tool");
3499
+ return {
3500
+ partTypes: parts.slice(-12).map((part) => typeof part?.type === "string" ? part.type : "(unknown)"),
3501
+ droppedPartCount: Math.max(0, parts.length - 12),
3502
+ lastToolName: typeof lastTool?.tool === "string" && lastTool.tool.length > 0 ? lastTool.tool : null,
3503
+ lastToolStatus: typeof lastTool?.state?.status === "string" && lastTool.state.status.length > 0 ? lastTool.state.status : null
3504
+ };
3505
+ }
3067
3506
  function isAbortedTerminalReply(messages, userMessageId) {
3068
3507
  const reply = findLastAssistantReplyFor(messages, userMessageId);
3069
3508
  const error2 = errorOf(reply);
@@ -3637,7 +4076,18 @@ async function deleteV2Session(client, sessionId) {
3637
4076
  { method: "DELETE" },
3638
4077
  { allowStatuses: Array.from({ length: 500 }, (_, index) => index + 100) }
3639
4078
  );
3640
- return { ok: response.status >= 200 && response.status < 300, status: response.status };
4079
+ if (response.status >= 200 && response.status < 300) {
4080
+ return { ok: true, status: response.status };
4081
+ }
4082
+ try {
4083
+ const body = await response.text();
4084
+ return body ? { ok: false, status: response.status, body } : { ok: false, status: response.status };
4085
+ } catch (error2) {
4086
+ console.error(
4087
+ `[deleteV2Session] DELETE /api/session/${sessionId} response body read failed for HTTP ${response.status}: ${error2 instanceof Error ? error2.message : String(error2)}`
4088
+ );
4089
+ return { ok: false, status: response.status };
4090
+ }
3641
4091
  } catch (error2) {
3642
4092
  console.error(
3643
4093
  `[deleteV2Session] DELETE /api/session/${sessionId} failed: ${error2 instanceof Error ? error2.message : String(error2)}`
@@ -3813,12 +4263,12 @@ async function readV2SessionErrorStream(client, options) {
3813
4263
 
3814
4264
  // src/lib/opencode/credential-import-v2.ts
3815
4265
  import { readFileSync as readFileSync4 } from "node:fs";
3816
- import { join as join4 } from "node:path";
4266
+ import { join as join5 } from "node:path";
3817
4267
 
3818
4268
  // src/lib/openai-usage.ts
3819
4269
  import { readFileSync as readFileSync3 } from "node:fs";
3820
4270
  import { homedir as homedir3 } from "node:os";
3821
- import { join as join3 } from "node:path";
4271
+ import { join as join4 } from "node:path";
3822
4272
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
3823
4273
  var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
3824
4274
  var OpenAiUsageError = class extends Error {
@@ -3832,7 +4282,7 @@ function isLocalCredentialProblem2(err) {
3832
4282
  }
3833
4283
  function readOpenCodeChatGptCredentials() {
3834
4284
  try {
3835
- const raw = readFileSync3(join3(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4285
+ const raw = readFileSync3(join4(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
3836
4286
  let parsed;
3837
4287
  try {
3838
4288
  parsed = JSON.parse(raw);
@@ -4058,13 +4508,13 @@ var INTEGRATION_LIST_RETRY_DELAY_MS = 1e3;
4058
4508
  function isRecord3(value) {
4059
4509
  return typeof value === "object" && value !== null && !Array.isArray(value);
4060
4510
  }
4061
- var sleep2 = (milliseconds) => new Promise((resolve4) => setTimeout(resolve4, milliseconds));
4511
+ var sleep2 = (milliseconds) => new Promise((resolve5) => setTimeout(resolve5, milliseconds));
4062
4512
  function emptyResult(unsupported = [], outcome = "completed") {
4063
4513
  return { outcome, imported: [], alreadyConnected: [], unsupported, failed: [] };
4064
4514
  }
4065
4515
  function readOpenCodeCredentials(homeDir, log3) {
4066
4516
  try {
4067
- const raw = readFileSync4(join4(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
4517
+ const raw = readFileSync4(join5(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
4068
4518
  let parsed;
4069
4519
  try {
4070
4520
  parsed = JSON.parse(raw);
@@ -4266,7 +4716,7 @@ async function importOpenCodeV2Credentials({
4266
4716
  import { spawn as spawn2 } from "node:child_process";
4267
4717
  import { mkdirSync, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
4268
4718
  import { homedir as homedir4 } from "node:os";
4269
- import { dirname as dirname3, resolve as resolvePath } from "node:path";
4719
+ import { dirname as dirname4, resolve as resolvePath } from "node:path";
4270
4720
 
4271
4721
  // src/lib/runner-synchroniser.ts
4272
4722
  import { spawn } from "node:child_process";
@@ -4276,7 +4726,7 @@ function appendError(stderr, error2) {
4276
4726
  ${message}`;
4277
4727
  }
4278
4728
  function runSynchroniser(args, opts) {
4279
- return new Promise((resolve4) => {
4729
+ return new Promise((resolve5) => {
4280
4730
  let child;
4281
4731
  let stdout = "";
4282
4732
  let stderr = "";
@@ -4290,7 +4740,7 @@ function runSynchroniser(args, opts) {
4290
4740
  if (timer.handle) clearTimeout(timer.handle);
4291
4741
  if (abortListener) opts.signal?.removeEventListener("abort", abortListener);
4292
4742
  if (spawnListener) child.removeListener("spawn", spawnListener);
4293
- resolve4(result);
4743
+ resolve5(result);
4294
4744
  };
4295
4745
  try {
4296
4746
  child = spawn("runner-synchroniser", args, {
@@ -4346,9 +4796,12 @@ function runSynchroniser(args, opts) {
4346
4796
 
4347
4797
  // src/lib/opencode/session-db-boot.ts
4348
4798
  var SESSION_DB_RESTORE_TIMEOUT_MS = 3e5;
4349
- var SESSION_DB_VERIFY_TIMEOUT_MS = 12e4;
4799
+ var SESSION_DB_VERIFY_TIMEOUT_MS = 36e4;
4350
4800
  var SESSION_DB_SYNCHRONISER_TIMEOUT_MS = 12e4;
4351
- var SESSION_DB_VERIFY_WALKBACK_BUDGET_SECONDS = 120;
4801
+ var SESSION_DB_VERIFY_WALKBACK_BUDGET_SECONDS = 40;
4802
+ var SESSION_DB_VERIFY_RESTORE_POINTS_TIMEOUT_SECONDS = 30;
4803
+ var SESSION_DB_VERIFY_RESTORE_CANDIDATE_TIMEOUT_SECONDS = 120;
4804
+ var SESSION_DB_VERIFY_DISPOSAL_BUDGET_SECONDS = 90;
4352
4805
  function commandError(result) {
4353
4806
  return result.stderr.trim() || `process exited with code ${result.code ?? "null"}`;
4354
4807
  }
@@ -4391,7 +4844,7 @@ function clearMarker(options) {
4391
4844
  function markNoReplicate(options, message) {
4392
4845
  if (options.noReplicateMarker) {
4393
4846
  try {
4394
- mkdirSync(dirname3(options.noReplicateMarker), { recursive: true });
4847
+ mkdirSync(dirname4(options.noReplicateMarker), { recursive: true });
4395
4848
  writeFileSync(options.noReplicateMarker, "");
4396
4849
  } catch (error2) {
4397
4850
  options.log(
@@ -4450,7 +4903,7 @@ function parseSynchroniserEnv(stdout) {
4450
4903
  return values;
4451
4904
  }
4452
4905
  function runCommand(command, args, options) {
4453
- return new Promise((resolve4) => {
4906
+ return new Promise((resolve5) => {
4454
4907
  let child;
4455
4908
  let stdout = "";
4456
4909
  let stderr = "";
@@ -4459,7 +4912,7 @@ function runCommand(command, args, options) {
4459
4912
  if (settled) return;
4460
4913
  settled = true;
4461
4914
  if (timer) clearTimeout(timer);
4462
- resolve4(result);
4915
+ resolve5(result);
4463
4916
  };
4464
4917
  try {
4465
4918
  child = spawn2(command, args, {
@@ -4467,7 +4920,7 @@ function runCommand(command, args, options) {
4467
4920
  stdio: ["ignore", "pipe", "pipe"]
4468
4921
  });
4469
4922
  } catch (error2) {
4470
- resolve4({
4923
+ resolve5({
4471
4924
  code: null,
4472
4925
  stdout,
4473
4926
  stderr: error2 instanceof Error ? error2.message : String(error2),
@@ -4546,7 +4999,7 @@ async function ensureLitestreamConfig(options, env) {
4546
4999
  return null;
4547
5000
  }
4548
5001
  try {
4549
- mkdirSync(dirname3(configPath), { recursive: true });
5002
+ mkdirSync(dirname4(configPath), { recursive: true });
4550
5003
  writeFileSync(configPath, rendered.stdout);
4551
5004
  } catch (error2) {
4552
5005
  options.log(
@@ -4659,20 +5112,37 @@ async function verifySessionDb(options, configPath, env) {
4659
5112
  timeoutMs: SESSION_DB_VERIFY_TIMEOUT_MS,
4660
5113
  env: {
4661
5114
  ...env,
4662
- // The synchroniser reads this value in SECONDS. Keep this at 120, not
4663
- // 120_000, so the walkback gives up before the outer process bound.
5115
+ // The synchroniser reads all four values in SECONDS.
4664
5116
  EVIDENT_SESSION_DB_WALKBACK_BUDGET_SECONDS: String(
4665
5117
  SESSION_DB_VERIFY_WALKBACK_BUDGET_SECONDS
5118
+ ),
5119
+ EVIDENT_SESSION_DB_RESTORE_POINTS_TIMEOUT_SECONDS: String(
5120
+ SESSION_DB_VERIFY_RESTORE_POINTS_TIMEOUT_SECONDS
5121
+ ),
5122
+ EVIDENT_SESSION_DB_RESTORE_CANDIDATE_TIMEOUT_SECONDS: String(
5123
+ SESSION_DB_VERIFY_RESTORE_CANDIDATE_TIMEOUT_SECONDS
5124
+ ),
5125
+ EVIDENT_SESSION_DB_DISPOSAL_BUDGET_SECONDS: String(
5126
+ SESSION_DB_VERIFY_DISPOSAL_BUDGET_SECONDS
4666
5127
  )
4667
5128
  }
4668
5129
  });
4669
5130
  logSynchroniserDiagnostics(result, options);
4670
5131
  if (result.timedOut || result.code === 124 || result.code === 137) {
5132
+ if (result.stderr.includes("SESSION-DB-INTEGRITY-FAILED")) {
5133
+ const reason = result.stderr.includes("SESSION-DB-REPLICA-SIZE") ? "verification_timeout_during_separation" : "verification_timeout_before_separation";
5134
+ options.log(
5135
+ `SESSION-DB-VERIFY-TIMEOUT: verification did not finish within its ${SESSION_DB_VERIFY_TIMEOUT_MS}ms deadline; refusing to start with the restored opencode.db (${reason})`,
5136
+ "error"
5137
+ );
5138
+ reportRecord("verify", "session_db_boot_refused", reason, null, options);
5139
+ return { verifyFatal: true, verifyFatalReason: reason };
5140
+ }
4671
5141
  options.log(
4672
5142
  `SESSION-DB-VERIFY-TIMEOUT: verification did not finish within its ${SESSION_DB_VERIFY_TIMEOUT_MS}ms deadline; continuing with the restored opencode.db as-is, unverified`,
4673
5143
  "warn"
4674
5144
  );
4675
- return false;
5145
+ return { verifyFatal: false };
4676
5146
  }
4677
5147
  if (result.code === 34) {
4678
5148
  reportRecord(
@@ -4682,14 +5152,14 @@ async function verifySessionDb(options, configPath, env) {
4682
5152
  null,
4683
5153
  options
4684
5154
  );
4685
- return true;
5155
+ return { verifyFatal: true };
4686
5156
  }
4687
5157
  if (result.code === 33) {
4688
5158
  options.log(
4689
5159
  "SESSION-DB-INTEGRITY-EXHAUSTED: booting continues and litestream still replicates, starting an empty backup chain after the corrupt replica was separated.",
4690
5160
  "warn"
4691
5161
  );
4692
- return false;
5162
+ return { verifyFatal: false };
4693
5163
  }
4694
5164
  if (result.code !== 0) {
4695
5165
  options.log(
@@ -4697,13 +5167,13 @@ async function verifySessionDb(options, configPath, env) {
4697
5167
  "warn"
4698
5168
  );
4699
5169
  }
4700
- return false;
5170
+ return { verifyFatal: false };
4701
5171
  }
4702
5172
  options.log(
4703
5173
  "skipping session-DB verification: this boot's session DB was not proven safe to replicate",
4704
5174
  "debug"
4705
5175
  );
4706
- return false;
5176
+ return { verifyFatal: false };
4707
5177
  }
4708
5178
  function fileExists(path) {
4709
5179
  try {
@@ -4790,13 +5260,13 @@ async function restoreAndVerifySessionDb(options) {
4790
5260
  if (options.noReplicateMarker && fileExists(options.noReplicateMarker)) {
4791
5261
  return { verifyFatal: false };
4792
5262
  }
4793
- return { verifyFatal: await verifySessionDb(options, configPath, env) };
5263
+ return verifySessionDb(options, configPath, env);
4794
5264
  }
4795
5265
 
4796
5266
  // src/lib/opencode/session-db-provenance.ts
4797
5267
  import { createRequire } from "node:module";
4798
5268
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
4799
- import { dirname as dirname4, join as join5 } from "node:path";
5269
+ import { dirname as dirname5, join as join6 } from "node:path";
4800
5270
  var require2 = createRequire(import.meta.url);
4801
5271
  function readSessionDbMigrationIds(dbPath) {
4802
5272
  let db;
@@ -4833,7 +5303,7 @@ function readSessionDbMigrationIds(dbPath) {
4833
5303
  }
4834
5304
  function sessionDbProvenanceStatePath(homeDir, env) {
4835
5305
  const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
4836
- return override || join5(homeDir, ".local", "state", "evident", "session-db-provenance.json");
5306
+ return override || join6(homeDir, ".local", "state", "evident", "session-db-provenance.json");
4837
5307
  }
4838
5308
  function loadSessionDbProvenanceState(path) {
4839
5309
  let value;
@@ -4862,7 +5332,7 @@ function loadSessionDbProvenanceState(path) {
4862
5332
  }
4863
5333
  function saveSessionDbProvenanceState(path, state) {
4864
5334
  try {
4865
- mkdirSync2(dirname4(path), { recursive: true });
5335
+ mkdirSync2(dirname5(path), { recursive: true });
4866
5336
  writeFileSync2(path, `${JSON.stringify(state, null, 2)}
4867
5337
  `, "utf8");
4868
5338
  } catch (error2) {
@@ -4953,7 +5423,7 @@ async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
4953
5423
  if (child.exitCode !== null || child.signalCode !== null) {
4954
5424
  return { outcome: "exited", code: child.exitCode, signal: child.signalCode };
4955
5425
  }
4956
- return new Promise((resolve4, reject) => {
5426
+ return new Promise((resolve5, reject) => {
4957
5427
  let forced = false;
4958
5428
  let settled = false;
4959
5429
  const timer = setTimeout(() => {
@@ -4973,7 +5443,7 @@ async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
4973
5443
  settled = true;
4974
5444
  clearTimeout(timer);
4975
5445
  child.removeListener("exit", onExit);
4976
- resolve4(result);
5446
+ resolve5(result);
4977
5447
  };
4978
5448
  const fail = (error2) => {
4979
5449
  if (settled) return;
@@ -5449,7 +5919,7 @@ function selectSessionsToDelete(sessions, opts) {
5449
5919
  );
5450
5920
  }
5451
5921
  var DEFAULT_INTERVAL = "1h";
5452
- function resolve(flag, envValue, fallback) {
5922
+ function resolve2(flag, envValue, fallback) {
5453
5923
  return flag ?? envValue ?? fallback;
5454
5924
  }
5455
5925
  function parseMaxCount(input) {
@@ -5465,9 +5935,9 @@ function parseMaxCount(input) {
5465
5935
  }
5466
5936
  function resolveSessionCleanupConfig(flags, env = process.env) {
5467
5937
  const warnings = [];
5468
- const maxAgeRaw = resolve(flags.maxAge, env.EVIDENT_SESSION_CLEANUP_MAX_AGE);
5469
- const maxCountRaw = resolve(flags.maxCount, env.EVIDENT_SESSION_CLEANUP_MAX_COUNT);
5470
- const intervalRaw = resolve(
5938
+ const maxAgeRaw = resolve2(flags.maxAge, env.EVIDENT_SESSION_CLEANUP_MAX_AGE);
5939
+ const maxCountRaw = resolve2(flags.maxCount, env.EVIDENT_SESSION_CLEANUP_MAX_COUNT);
5940
+ const intervalRaw = resolve2(
5471
5941
  flags.interval,
5472
5942
  env.EVIDENT_SESSION_CLEANUP_INTERVAL,
5473
5943
  DEFAULT_INTERVAL
@@ -5507,10 +5977,10 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
5507
5977
 
5508
5978
  // src/lib/opencode/session-db-size.ts
5509
5979
  import { statSync as statSync4 } from "node:fs";
5510
- import { join as join6 } from "node:path";
5980
+ import { join as join7 } from "node:path";
5511
5981
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
5512
5982
  function statSessionDbBytes(homeDir) {
5513
- const dbPath = join6(homeDir, ".local", "share", "opencode", "opencode.db");
5983
+ const dbPath = join7(homeDir, ".local", "share", "opencode", "opencode.db");
5514
5984
  try {
5515
5985
  return statSync4(dbPath).size;
5516
5986
  } catch (err) {
@@ -5540,12 +6010,12 @@ function buildSessionStoreSizeWarning(input) {
5540
6010
  // src/lib/opencode/log-tail.ts
5541
6011
  import { statSync as statSync5 } from "node:fs";
5542
6012
  import { homedir as homedir5 } from "node:os";
5543
- import { join as join7 } from "node:path";
6013
+ import { join as join8 } from "node:path";
5544
6014
  import { open as open2, stat } from "node:fs/promises";
5545
6015
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
5546
6016
  function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
5547
- const dataDir = env.XDG_DATA_HOME || join7(homeDir, ".local", "share");
5548
- return join7(dataDir, "opencode", "log", "opencode.log");
6017
+ const dataDir = env.XDG_DATA_HOME || join8(homeDir, ".local", "share");
6018
+ return join8(dataDir, "opencode", "log", "opencode.log");
5549
6019
  }
5550
6020
  function isEnoent(error2) {
5551
6021
  return error2?.code === "ENOENT";
@@ -5700,8 +6170,8 @@ var StreamForwarder = class {
5700
6170
  pauseStream(sid, stream) {
5701
6171
  if (!stream.paused) {
5702
6172
  stream.paused = true;
5703
- stream.pausePromise = new Promise((resolve4) => {
5704
- stream.resume = resolve4;
6173
+ stream.pausePromise = new Promise((resolve5) => {
6174
+ stream.resume = resolve5;
5705
6175
  });
5706
6176
  }
5707
6177
  if (stream.pauseTimer !== null) clearTimeout(stream.pauseTimer);
@@ -5768,12 +6238,12 @@ var StreamForwarder = class {
5768
6238
  let endBody;
5769
6239
  if (has_body) {
5770
6240
  const chunks = [];
5771
- bodyPromise = new Promise((resolve4) => {
6241
+ bodyPromise = new Promise((resolve5) => {
5772
6242
  pushBody = (buf) => {
5773
6243
  chunks.push(buf);
5774
6244
  };
5775
6245
  endBody = () => {
5776
- resolve4(Buffer.concat(chunks));
6246
+ resolve5(Buffer.concat(chunks));
5777
6247
  };
5778
6248
  });
5779
6249
  }
@@ -5944,7 +6414,7 @@ function connectTunnel(options) {
5944
6414
  } = options;
5945
6415
  const tunnelUrl = getTunnelUrlConfig();
5946
6416
  const url = `${tunnelUrl}/tunnel/${agentId}/connect`;
5947
- return new Promise((resolve4, reject) => {
6417
+ return new Promise((resolve5, reject) => {
5948
6418
  const ws = new WebSocket2(url, {
5949
6419
  headers: {
5950
6420
  Authorization: authHeader,
@@ -6053,7 +6523,7 @@ function connectTunnel(options) {
6053
6523
  forwarder.setBinaryFramesSupported(message.binary_frames === true);
6054
6524
  const connectedAgentId = message.agent_id ?? agentId;
6055
6525
  onConnected?.(connectedAgentId);
6056
- resolve4({
6526
+ resolve5({
6057
6527
  ws,
6058
6528
  close: () => ws.close(1e3, "CLI shutdown")
6059
6529
  });
@@ -6571,6 +7041,7 @@ function createSessionStoreHealth(options = {}) {
6571
7041
  const stalenessMs = Math.max(0, options.stalenessMs ?? SESSION_STORE_HEALTH_STALENESS_MS);
6572
7042
  let lastUnwritableAt = null;
6573
7043
  let lastWritableAt = null;
7044
+ const causes = /* @__PURE__ */ new Map();
6574
7045
  return {
6575
7046
  observeUnwritable: () => {
6576
7047
  lastUnwritableAt = now();
@@ -6582,6 +7053,23 @@ function createSessionStoreHealth(options = {}) {
6582
7053
  if (lastUnwritableAt === null) return false;
6583
7054
  if (lastWritableAt !== null && lastWritableAt >= lastUnwritableAt) return false;
6584
7055
  return now() - lastUnwritableAt < stalenessMs;
7056
+ },
7057
+ observeCause: (sessionId, cause) => {
7058
+ if (sessionId === null) return;
7059
+ causes.delete(sessionId);
7060
+ causes.set(sessionId, { cause, observedAt: now() });
7061
+ while (causes.size > 256) {
7062
+ const oldest = causes.keys().next().value;
7063
+ if (typeof oldest !== "string") break;
7064
+ causes.delete(oldest);
7065
+ }
7066
+ },
7067
+ readCause: (sessionId) => {
7068
+ const observed = causes.get(sessionId);
7069
+ if (!observed) return null;
7070
+ causes.delete(sessionId);
7071
+ if (now() - observed.observedAt >= stalenessMs) return null;
7072
+ return observed.cause;
6585
7073
  }
6586
7074
  };
6587
7075
  }
@@ -6590,12 +7078,12 @@ function createSessionStoreHealth(options = {}) {
6590
7078
  import { homedir as homedir6 } from "node:os";
6591
7079
 
6592
7080
  // src/lib/runner-file-sync.ts
6593
- import { join as join9 } from "node:path";
7081
+ import { join as join10 } from "node:path";
6594
7082
 
6595
7083
  // src/lib/file-push.ts
6596
7084
  import { randomUUID } from "node:crypto";
6597
7085
  import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
6598
- import { basename, dirname as dirname5, isAbsolute, join as join8, relative, resolve as resolve2, sep } from "node:path";
7086
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
6599
7087
  var FILE_MODE = 384;
6600
7088
  var DIRECTORY_MODE = 448;
6601
7089
  async function writePushedFile(request2) {
@@ -6626,9 +7114,9 @@ async function writePushedFile(request2) {
6626
7114
  }
6627
7115
  try {
6628
7116
  const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
6629
- dirname5(candidate)
7117
+ dirname6(candidate)
6630
7118
  );
6631
- const realTarget = join8(existingAncestor, ...missingSegments, basename(candidate));
7119
+ const realTarget = join9(existingAncestor, ...missingSegments, basename2(candidate));
6632
7120
  const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
6633
7121
  if (allowedDirectory === null) {
6634
7122
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
@@ -6638,8 +7126,8 @@ async function writePushedFile(request2) {
6638
7126
  }
6639
7127
  if (missingSegments.length > 0) {
6640
7128
  await createMissingDirectories(existingAncestor, missingSegments);
6641
- const realParent = await realpath(dirname5(realTarget));
6642
- if (realParent !== dirname5(realTarget) || !contains(allowedDirectory, realTarget)) {
7129
+ const realParent = await realpath(dirname6(realTarget));
7130
+ if (realParent !== dirname6(realTarget) || !contains(allowedDirectory, realTarget)) {
6643
7131
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
6644
7132
  path: realTarget,
6645
7133
  bytes,
@@ -6664,15 +7152,15 @@ function expandAndValidate(requestedPath, homeDir) {
6664
7152
  if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
6665
7153
  return null;
6666
7154
  }
6667
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
7155
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join9(homeDir, requestedPath.slice(2)) : requestedPath;
6668
7156
  if (expanded.split(/[/\\]/).includes("..")) {
6669
7157
  return null;
6670
7158
  }
6671
- if (!isAbsolute(expanded)) {
7159
+ if (!isAbsolute2(expanded)) {
6672
7160
  return null;
6673
7161
  }
6674
- const candidate = resolve2(expanded);
6675
- const name = basename(candidate);
7162
+ const candidate = resolve3(expanded);
7163
+ const name = basename2(candidate);
6676
7164
  return name === "" || name === "." || name === ".." ? null : candidate;
6677
7165
  }
6678
7166
  async function resolveNearestExistingAncestor(directory) {
@@ -6682,18 +7170,18 @@ async function resolveNearestExistingAncestor(directory) {
6682
7170
  try {
6683
7171
  return { existingAncestor: await realpath(current), missingSegments };
6684
7172
  } catch (err) {
6685
- const parent = dirname5(current);
7173
+ const parent = dirname6(current);
6686
7174
  if (err.code !== "ENOENT" || parent === current) {
6687
7175
  throw err;
6688
7176
  }
6689
- missingSegments.unshift(basename(current));
7177
+ missingSegments.unshift(basename2(current));
6690
7178
  current = parent;
6691
7179
  }
6692
7180
  }
6693
7181
  }
6694
7182
  async function findContainingAllowedDirectory(allowedDirectories, realTarget) {
6695
7183
  for (const directory of allowedDirectories) {
6696
- if (!isAbsolute(directory)) {
7184
+ if (!isAbsolute2(directory)) {
6697
7185
  log("warn", "file_push_allowed_directory_skipped", { directory, reason: "not_absolute" });
6698
7186
  continue;
6699
7187
  }
@@ -6731,19 +7219,19 @@ async function realpathCreatingIfMissing(directory) {
6731
7219
  }
6732
7220
  }
6733
7221
  function contains(realDirectory, realTarget) {
6734
- const rel = relative(realDirectory, realTarget);
6735
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
7222
+ const rel = relative2(realDirectory, realTarget);
7223
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute2(rel);
6736
7224
  }
6737
7225
  async function createMissingDirectories(existingAncestor, missingSegments) {
6738
7226
  let current = existingAncestor;
6739
7227
  for (const segment of missingSegments) {
6740
- current = join8(current, segment);
7228
+ current = join9(current, segment);
6741
7229
  await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
6742
7230
  await chmod(current, DIRECTORY_MODE);
6743
7231
  }
6744
7232
  }
6745
7233
  async function writeAtomically(realTarget, content) {
6746
- const temporaryPath = join8(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
7234
+ const temporaryPath = join9(dirname6(realTarget), `.evident-push-${randomUUID()}.tmp`);
6747
7235
  let handle;
6748
7236
  try {
6749
7237
  handle = await open3(temporaryPath, "wx", FILE_MODE);
@@ -6873,12 +7361,12 @@ var NOT_APPLIED = {
6873
7361
  opencodeAuthApplied: false
6874
7362
  };
6875
7363
  function isClaudeCredentialPath(requestedPath, homeDir) {
6876
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join9(homeDir, requestedPath.slice(2)) : requestedPath;
6877
- return expanded === join9(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
7364
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7365
+ return expanded === join10(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
6878
7366
  }
6879
7367
  function isOpenCodeAuthPath(requestedPath, homeDir) {
6880
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join9(homeDir, requestedPath.slice(2)) : requestedPath;
6881
- return expanded === join9(homeDir, ...OPENCODE_AUTH_SEGMENTS);
7368
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7369
+ return expanded === join10(homeDir, ...OPENCODE_AUTH_SEGMENTS);
6882
7370
  }
6883
7371
  async function applyOne(options, file) {
6884
7372
  const label = `${file.id.slice(0, 8)} (${file.path})`;
@@ -7022,6 +7510,11 @@ function messageIdOf(m) {
7022
7510
  const infoId = m.info?.id;
7023
7511
  return typeof infoId === "string" ? infoId : void 0;
7024
7512
  }
7513
+ function composeAbandonmentNote(finish, replyShape, outcome) {
7514
+ const base = `OpenCode ended this turn after a tool call (finish="${finish ?? "(unknown)"}") ` + (outcome === "without-answer" ? "without writing an answer, and confirmed it is no longer working on this conversation. Evident is reporting the run as failed so the failure and its reason are visible in failed runs." : "after writing only part of an answer, and confirmed it is no longer working on this conversation. Evident delivered what was written and is reporting the run as finished.");
7515
+ if (replyShape === null) return base;
7516
+ return `${base} ${replyShape.lastToolName === null ? "No tool call was recorded" : `The last tool call was "${replyShape.lastToolName}" with status "${replyShape.lastToolStatus ?? "(unknown)"}"`}; the reply's final part types were [${replyShape.partTypes.join(", ")}]${replyShape.droppedPartCount > 0 ? ` (${replyShape.droppedPartCount} earlier part types omitted)` : ""}.`;
7517
+ }
7025
7518
  function cleanImageMime(contentType) {
7026
7519
  if (!contentType) return null;
7027
7520
  const media = contentType.split(";")[0].trim().toLowerCase();
@@ -7042,6 +7535,10 @@ var SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS = 2e3;
7042
7535
  var SESSION_ERROR_STREAM_HEALTHY_MS = 5e3;
7043
7536
  var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
7044
7537
  var MAX_BUFFERED_SESSION_ERRORS = 256;
7538
+ var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
7539
+ var SESSION_STORE_UNWRITABLE_ERROR = "The runner session database could not be written, so this turn did not finish. Nothing was lost from the queue. Clear disk space and retry.";
7540
+ var SESSION_ERROR_RETAINED_DETAIL = "Runner-observed OpenCode session error: ";
7541
+ var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
7045
7542
  var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
7046
7543
  var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
7047
7544
  var DRIVER_SESSION_ENUMERATION_LIMIT = 20;
@@ -7199,6 +7696,7 @@ var ChannelDriver = class _ChannelDriver {
7199
7696
  * with a short TTL so an unmatched session cannot retain an event indefinitely.
7200
7697
  */
7201
7698
  bufferedSessionErrors = /* @__PURE__ */ new Map();
7699
+ retainedSessionErrors = /* @__PURE__ */ new Map();
7202
7700
  /**
7203
7701
  * AUTHORITATIVE local dedup (WI-3): Evident message ids that have been
7204
7702
  * dispatched and are still in-flight. A message in this set is never
@@ -9039,6 +9537,28 @@ var ChannelDriver = class _ChannelDriver {
9039
9537
  this.bufferedSessionErrors.delete(oldest);
9040
9538
  }
9041
9539
  }
9540
+ retainSessionError(event) {
9541
+ this.retainedSessionErrors.delete(event.sessionId);
9542
+ this.retainedSessionErrors.set(event.sessionId, {
9543
+ reason: event.reason,
9544
+ observedAt: this.now()
9545
+ });
9546
+ while (this.retainedSessionErrors.size > MAX_BUFFERED_SESSION_ERRORS) {
9547
+ const oldest = this.retainedSessionErrors.keys().next().value;
9548
+ if (typeof oldest !== "string") break;
9549
+ this.retainedSessionErrors.delete(oldest);
9550
+ }
9551
+ }
9552
+ readRetainedSessionError(sessionId) {
9553
+ const retained = this.retainedSessionErrors.get(sessionId);
9554
+ if (!retained) return null;
9555
+ if (this.now() - retained.observedAt >= SESSION_ERROR_RETENTION_TTL_MS) {
9556
+ this.retainedSessionErrors.delete(sessionId);
9557
+ return null;
9558
+ }
9559
+ this.retainedSessionErrors.delete(sessionId);
9560
+ return retained.reason;
9561
+ }
9042
9562
  /**
9043
9563
  * Build a fresh `SessionWatcher`. Seeds `lastTickAt`/`lastObservedTickAt`
9044
9564
  * EQUAL (#1618) so a watcher whose loop has not started ticking yet is never
@@ -9365,9 +9885,10 @@ var ChannelDriver = class _ChannelDriver {
9365
9885
  return;
9366
9886
  }
9367
9887
  if ([...watcher.inFlight.values()].some((message) => message.started && !message.done)) {
9888
+ this.retainSessionError(event);
9368
9889
  this.log({
9369
- level: "debug",
9370
- message: `A turn is already running in session ${event.sessionId.slice(0, 8)} \u2014 deferring to transcript polling`,
9890
+ level: "warn",
9891
+ message: `A turn is already running in session ${event.sessionId.slice(0, 8)} \u2014 retaining the session error and deferring to transcript polling: ${event.reason}`,
9371
9892
  conversation_id: watcher.conv.id
9372
9893
  });
9373
9894
  return;
@@ -9397,9 +9918,10 @@ var ChannelDriver = class _ChannelDriver {
9397
9918
  const messages = await this.getSessionMessages(event.sessionId);
9398
9919
  const state = messageRunState(messages, inFlight.opencodeMessageId);
9399
9920
  if (state !== "queued") {
9921
+ this.retainSessionError(event);
9400
9922
  this.log({
9401
- level: "debug",
9402
- message: `Session error for message ${inFlight.evidentMessageId.slice(0, 8)} observed state ${state}; leaving it to transcript polling`,
9923
+ level: "warn",
9924
+ message: `Session error for message ${inFlight.evidentMessageId.slice(0, 8)} in session ${event.sessionId.slice(0, 8)} observed state ${state}; retaining the reason for transcript polling: ${event.reason}`,
9403
9925
  conversation_id: watcher.conv.id,
9404
9926
  message_id: inFlight.evidentMessageId
9405
9927
  });
@@ -9628,7 +10150,16 @@ var ChannelDriver = class _ChannelDriver {
9628
10150
  if (state === "failed") {
9629
10151
  this.anchorDeliveryDeadline(inFlight);
9630
10152
  if (!inFlight.done) {
9631
- const error2 = messageError(messages, inFlight.opencodeMessageId) ?? void 0;
10153
+ const messageErrorText = messageError(messages, inFlight.opencodeMessageId);
10154
+ const retainedReason = this.readRetainedSessionError(sessionId);
10155
+ const retainedError = retainedReason === null || messageErrorText !== null && messageErrorText.includes(retainedReason) ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
10156
+ const observedCause = this.sessionStoreHealth?.readCause(sessionId);
10157
+ const causeError = observedCause === null || observedCause === void 0 ? null : `${SESSION_ERROR_OBSERVED_CAUSE}${observedCause}`;
10158
+ const storeError = this.sessionStoreHealth?.isUnwritable() ? SESSION_STORE_UNWRITABLE_ERROR : null;
10159
+ const runnerDetails = [retainedError, causeError, storeError].filter(
10160
+ (detail) => detail !== null
10161
+ );
10162
+ const error2 = messageErrorText === null ? runnerDetails.length === 0 ? void 0 : runnerDetails.join(" ") : [messageErrorText, ...runnerDetails].join(" ");
9632
10163
  this.log({
9633
10164
  level: "error",
9634
10165
  message: `Message ${inFlight.evidentMessageId.slice(0, 8)} errored \u2014 marking failed: ${error2 ?? "(no error text)"}`,
@@ -9718,17 +10249,28 @@ var ChannelDriver = class _ChannelDriver {
9718
10249
  }
9719
10250
  } else {
9720
10251
  if (inFlight.b2AbandonedSignalled) {
10252
+ const reply = findLastAssistantReplyFor(messages, inFlight.opencodeMessageId);
10253
+ const finish = reply?.info?.finish ?? reply?.finish;
9721
10254
  if (inFlight.b2AbandonedWithoutAnswer) {
9722
- const reply = findLastAssistantReplyFor(messages, inFlight.opencodeMessageId);
9723
10255
  await this.settleMessageAbandonedWithoutAnswer(
9724
10256
  sessionId,
9725
10257
  watcher,
9726
10258
  inFlight,
9727
10259
  messages,
9728
- reply?.info?.finish ?? reply?.finish
10260
+ finish
9729
10261
  );
9730
10262
  } else {
9731
- await this.settleMessageDone(sessionId, watcher, inFlight, messages);
10263
+ await this.settleMessageDone(
10264
+ sessionId,
10265
+ watcher,
10266
+ inFlight,
10267
+ messages,
10268
+ composeAbandonmentNote(
10269
+ finish,
10270
+ describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
10271
+ "with-answer"
10272
+ )
10273
+ );
9732
10274
  }
9733
10275
  return;
9734
10276
  }
@@ -9791,7 +10333,17 @@ var ChannelDriver = class _ChannelDriver {
9791
10333
  ...descendantOngoing != null ? { descendant_ongoing: descendantOngoing } : {},
9792
10334
  opencode_message_id: inFlight.opencodeMessageId
9793
10335
  });
9794
- await this.settleMessageDone(sessionId, watcher, inFlight, messages);
10336
+ await this.settleMessageDone(
10337
+ sessionId,
10338
+ watcher,
10339
+ inFlight,
10340
+ messages,
10341
+ composeAbandonmentNote(
10342
+ finish,
10343
+ describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
10344
+ "with-answer"
10345
+ )
10346
+ );
9795
10347
  return;
9796
10348
  }
9797
10349
  if (rootOngoing === true && !inFlight.b2RootOngoingHeldLogged) {
@@ -9991,15 +10543,11 @@ var ChannelDriver = class _ChannelDriver {
9991
10543
  }
9992
10544
  }
9993
10545
  /**
9994
- * Settle a message whose run-state has resolved `'done'` — extracted verbatim
9995
- * (pure refactor, no behavior change) from `serviceInFlightMessage`'s former
9996
- * inline `state === 'done'` branch body, so a SECOND caller (the #721
9997
- * b2-abandonment resolution) can reach the exact same completion behavior
9998
- * (delivery-deadline anchoring, title resolution, usage extraction, and
9999
- * `markDone`'s auth/terminal/transient-retry discipline) without duplicating it
10000
- * and risking the two copies silently drifting apart.
10546
+ * Settle a message whose run-state has resolved `'done'`. Normal completion and
10547
+ * confirmed b2 abandonment share the same delivery, usage, and terminal handling.
10548
+ * Normal completions omit the optional terminal diagnostic.
10001
10549
  */
10002
- async settleMessageDone(sessionId, watcher, inFlight, messages) {
10550
+ async settleMessageDone(sessionId, watcher, inFlight, messages, note) {
10003
10551
  const conv = watcher.conv;
10004
10552
  this.anchorDeliveryDeadline(inFlight);
10005
10553
  if (!inFlight.done) {
@@ -10026,7 +10574,8 @@ var ChannelDriver = class _ChannelDriver {
10026
10574
  title,
10027
10575
  usage,
10028
10576
  usageAgentName,
10029
- subagentInvocations
10577
+ subagentInvocations,
10578
+ note
10030
10579
  );
10031
10580
  } catch (err) {
10032
10581
  if (err instanceof ChannelAuthError) throw err;
@@ -10071,7 +10620,11 @@ var ChannelDriver = class _ChannelDriver {
10071
10620
  /** Report a confirmed b2 abandonment that produced no answer text. */
10072
10621
  async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages, finish) {
10073
10622
  const conv = watcher.conv;
10074
- const error2 = `OpenCode ended this turn after a tool call (finish="${finish ?? "(unknown)"}") without writing an answer, and confirmed it is no longer working on this conversation. Evident is reporting the run as failed so the failure and its reason are visible in failed runs.`;
10623
+ const error2 = composeAbandonmentNote(
10624
+ finish,
10625
+ describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
10626
+ "without-answer"
10627
+ );
10075
10628
  this.anchorDeliveryDeadline(inFlight);
10076
10629
  this.log({
10077
10630
  level: "warn",
@@ -10198,7 +10751,7 @@ var ChannelDriver = class _ChannelDriver {
10198
10751
  */
10199
10752
  async settleMessageSessionStoreUnwritable(sessionId, watcher, inFlight) {
10200
10753
  const conv = watcher.conv;
10201
- const error2 = "The runner session database could not be written, so this turn did not finish. Nothing was lost from the queue. Clear disk space and retry.";
10754
+ const error2 = SESSION_STORE_UNWRITABLE_ERROR;
10202
10755
  this.log({
10203
10756
  level: "error",
10204
10757
  message: `Message ${inFlight.evidentMessageId.slice(0, 8)} was not resolved as done: ${error2}`,
@@ -11308,14 +11861,14 @@ var ChannelDriver = class _ChannelDriver {
11308
11861
  const fetchChildWithoutBlocking = async (sessionId) => {
11309
11862
  const pending = fetchChildMessages(sessionId);
11310
11863
  let timer;
11311
- const timeout = new Promise((resolve4) => {
11864
+ const timeout = new Promise((resolve5) => {
11312
11865
  timer = setTimeout(() => {
11313
11866
  this.log({
11314
11867
  level: "warn",
11315
11868
  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`,
11316
11869
  message_id: messageId
11317
11870
  });
11318
- resolve4(null);
11871
+ resolve5(null);
11319
11872
  }, SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS);
11320
11873
  });
11321
11874
  try {
@@ -11875,7 +12428,7 @@ var ChannelDriver = class _ChannelDriver {
11875
12428
  * watcher retries next tick within the
11876
12429
  * deadline, Finding 4).
11877
12430
  */
11878
- async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage, usageAgentName, subagentInvocations) {
12431
+ async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage, usageAgentName, subagentInvocations, error2) {
11879
12432
  const res = await this.fetchImpl(
11880
12433
  `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
11881
12434
  {
@@ -11893,7 +12446,8 @@ var ChannelDriver = class _ChannelDriver {
11893
12446
  ...title ? { title } : {},
11894
12447
  ...usage ? usage : {},
11895
12448
  ...usageAgentName ? { usage_agent_name: usageAgentName } : {},
11896
- ...subagentInvocations && subagentInvocations.length > 0 ? { subagent_invocations: subagentInvocations } : {}
12449
+ ...subagentInvocations && subagentInvocations.length > 0 ? { subagent_invocations: subagentInvocations } : {},
12450
+ ...error2 ? { error: error2 } : {}
11897
12451
  })
11898
12452
  }
11899
12453
  );
@@ -12556,7 +13110,7 @@ function commandError2(result) {
12556
13110
  return result.stderr.trim() || `process exited with code ${result.code ?? "null"}`;
12557
13111
  }
12558
13112
  var runCommand2 = (command, args, opts) => {
12559
- return new Promise((resolve4) => {
13113
+ return new Promise((resolve5) => {
12560
13114
  let child;
12561
13115
  let stdout = "";
12562
13116
  let stderr = "";
@@ -12566,7 +13120,7 @@ var runCommand2 = (command, args, opts) => {
12566
13120
  if (settled) return;
12567
13121
  settled = true;
12568
13122
  if (timer.handle) clearTimeout(timer.handle);
12569
- resolve4(result);
13123
+ resolve5(result);
12570
13124
  };
12571
13125
  try {
12572
13126
  child = spawn5(command, args, { env: opts.env, stdio: ["ignore", "pipe", "pipe"] });
@@ -12621,7 +13175,9 @@ async function installRunnerSecret({
12621
13175
  }) {
12622
13176
  const arn = env.RUNNER_SECRET_ARN?.trim();
12623
13177
  if (!arn) {
12624
- log3("runner secret is not configured; continuing without GitHub and MCP credentials");
13178
+ if (!env.GH_TOKEN) {
13179
+ log3("runner secret is not configured; continuing without GitHub and MCP credentials");
13180
+ }
12625
13181
  return false;
12626
13182
  }
12627
13183
  const result = await (commandRunner ?? runCommand2)(
@@ -12820,7 +13376,7 @@ async function configureGitHubAccess({ env, log: log3 }) {
12820
13376
  // src/lib/opencode/config-overlay.ts
12821
13377
  import { execFileSync as execFileSync2 } from "node:child_process";
12822
13378
  import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
12823
- import { isAbsolute as isAbsolute2, join as join10, resolve as resolve3 } from "node:path";
13379
+ import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
12824
13380
  function isFile(filePath) {
12825
13381
  return existsSync2(filePath) && statSync6(filePath).isFile();
12826
13382
  }
@@ -12833,8 +13389,8 @@ function applyRunnerOpenCodeConfig({
12833
13389
  log3("runner OpenCode config is not configured; using the baked project config", "debug");
12834
13390
  return;
12835
13391
  }
12836
- const source = isAbsolute2(overlayPath) ? overlayPath : resolve3(cwd, overlayPath);
12837
- const target = isFile(join10(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
13392
+ const source = isAbsolute3(overlayPath) ? overlayPath : resolve4(cwd, overlayPath);
13393
+ const target = isFile(join11(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
12838
13394
  if (!isFile(source)) {
12839
13395
  log3(
12840
13396
  `RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
@@ -12842,7 +13398,7 @@ function applyRunnerOpenCodeConfig({
12842
13398
  );
12843
13399
  return;
12844
13400
  }
12845
- copyFileSync(source, join10(cwd, target));
13401
+ copyFileSync(source, join11(cwd, target));
12846
13402
  try {
12847
13403
  execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
12848
13404
  stdio: "ignore"
@@ -12851,7 +13407,7 @@ function applyRunnerOpenCodeConfig({
12851
13407
  const detail = error2 instanceof Error ? error2.message : String(error2);
12852
13408
  log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
12853
13409
  }
12854
- log3(`Applied runner OpenCode config ${source} to ${join10(cwd, target)}`);
13410
+ log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
12855
13411
  }
12856
13412
 
12857
13413
  // src/lib/credential-sync.ts
@@ -12869,14 +13425,14 @@ function errorMessage2(error2) {
12869
13425
  return error2 instanceof Error ? error2.message : String(error2);
12870
13426
  }
12871
13427
  function waitForSettlement(promise, timeoutMs) {
12872
- return new Promise((resolve4) => {
13428
+ return new Promise((resolve5) => {
12873
13429
  let settled = false;
12874
13430
  const timer = setTimeout(() => finish(false), Math.max(0, timeoutMs));
12875
13431
  const finish = (value) => {
12876
13432
  if (settled) return;
12877
13433
  settled = true;
12878
13434
  clearTimeout(timer);
12879
- resolve4(value);
13435
+ resolve5(value);
12880
13436
  };
12881
13437
  promise.then(
12882
13438
  () => finish(true),
@@ -13127,8 +13683,8 @@ function resolveFileSyncDirectories(raw, homeDir) {
13127
13683
  if (trimmed === "") {
13128
13684
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
13129
13685
  }
13130
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join11(homeDir, trimmed.slice(2)) : trimmed;
13131
- if (!isAbsolute3(expanded)) {
13686
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join12(homeDir, trimmed.slice(2)) : trimmed;
13687
+ if (!isAbsolute4(expanded)) {
13132
13688
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
13133
13689
  }
13134
13690
  const normalized = resolvePath2(expanded);
@@ -13228,8 +13784,7 @@ function resolveMaxActiveSessions(options, env = process.env) {
13228
13784
  function meetsThreshold(state, level) {
13229
13785
  return LOG_LEVELS[level] >= LOG_LEVELS[state.logLevel];
13230
13786
  }
13231
- function log2(state, message, level = "info") {
13232
- if (!meetsThreshold(state, level)) return;
13787
+ function writeConsoleLog(state, message, level) {
13233
13788
  if (state.json) {
13234
13789
  console.log(
13235
13790
  JSON.stringify({
@@ -13238,10 +13793,18 @@ function log2(state, message, level = "info") {
13238
13793
  message
13239
13794
  })
13240
13795
  );
13241
- } else if (!state.interactive) {
13242
- const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
13243
- console.log(`${prefix} ${message}`);
13796
+ return;
13244
13797
  }
13798
+ const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
13799
+ console.log(`${prefix} ${message}`);
13800
+ }
13801
+ function log2(state, message, level = "info") {
13802
+ if (!meetsThreshold(state, level)) return;
13803
+ if (state.json || !state.interactive) writeConsoleLog(state, message, level);
13804
+ }
13805
+ function logLocalOnly(state, message, level = "info") {
13806
+ if (!meetsThreshold(state, level)) return;
13807
+ writeConsoleLog(state, message, level);
13245
13808
  }
13246
13809
  function logActivity(state, entry) {
13247
13810
  const level = entry.level ?? (entry.type === "error" ? "error" : "info");
@@ -13543,7 +14106,7 @@ async function driveChannels(state, driver) {
13543
14106
  }
13544
14107
  }
13545
14108
  }
13546
- await new Promise((resolve4) => setTimeout(resolve4, CHANNEL_POLL_INTERVAL_MS));
14109
+ await new Promise((resolve5) => setTimeout(resolve5, CHANNEL_POLL_INTERVAL_MS));
13547
14110
  const cycleMs = performance.now() - cycleStartedAtMs;
13548
14111
  if (idleThisCycle) idleMs += cycleMs;
13549
14112
  if (unreachableThisCycle) unreachableMs += cycleMs;
@@ -13565,6 +14128,9 @@ async function driveChannels(state, driver) {
13565
14128
  }
13566
14129
  var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
13567
14130
  var SESSION_CLEANUP_MAX_DELETES_PER_SWEEP = 100;
14131
+ var SESSION_CLEANUP_MAX_ISOLATED_DELETE_FAILURES = 2;
14132
+ var SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES = 2;
14133
+ var MAX_SESSION_DELETE_BODY_LENGTH = 200;
13568
14134
  function shouldWarnForReclaimSkip(reason) {
13569
14135
  if (reason !== "sqlite-unavailable" && reason !== "disk-exhaustion") return false;
13570
14136
  const version2 = /^v(\d+)\.(\d+)\.(\d+)$/.exec(process.version);
@@ -13575,7 +14141,7 @@ function shouldWarnForReclaimSkip(reason) {
13575
14141
  return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
13576
14142
  }
13577
14143
  function sessionDbPath() {
13578
- return join11(homedir7(), ".local", "share", "opencode", "opencode.db");
14144
+ return join12(homedir7(), ".local", "share", "opencode", "opencode.db");
13579
14145
  }
13580
14146
  function logSessionCleanupActivity(state, phase, entry) {
13581
14147
  logActivity(state, {
@@ -13652,8 +14218,12 @@ async function runSweep(state, driver, config) {
13652
14218
  protectedIds: protectedNow
13653
14219
  }
13654
14220
  );
13655
- const deleteCandidates = toDelete.slice(0, SESSION_CLEANUP_MAX_DELETES_PER_SWEEP);
13656
- remaining = toDelete.length - deleteCandidates.length;
14221
+ const eligibleToDelete = toDelete.filter(
14222
+ (id) => (state.sessionDeleteFailures.get(id)?.isolatedFailures ?? 0) < SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES
14223
+ );
14224
+ const deleteCandidates = eligibleToDelete.slice(0, SESSION_CLEANUP_MAX_DELETES_PER_SWEEP);
14225
+ remaining = eligibleToDelete.length - deleteCandidates.length;
14226
+ const answeredFailures = [];
13657
14227
  protectedNow = driver.protectedSessionIds();
13658
14228
  for (const id of deleteCandidates) {
13659
14229
  if (protectedNow.has(id)) {
@@ -13676,8 +14246,44 @@ async function runSweep(state, driver, config) {
13676
14246
  if (statusClass === 4) failed4xx++;
13677
14247
  else if (statusClass === 5) failed5xx++;
13678
14248
  else failedOtherStatus++;
14249
+ answeredFailures.push({
14250
+ id,
14251
+ status: outcome.status,
14252
+ ...outcome.body !== void 0 ? { body: outcome.body } : {}
14253
+ });
13679
14254
  }
13680
14255
  }
14256
+ const givenUpFailures = [];
14257
+ if (failed > SESSION_CLEANUP_MAX_ISOLATED_DELETE_FAILURES) {
14258
+ for (const { id } of answeredFailures) state.sessionDeleteFailures.delete(id);
14259
+ } else {
14260
+ for (const failure of answeredFailures) {
14261
+ const previous = state.sessionDeleteFailures.get(failure.id);
14262
+ const record = previous?.status === failure.status ? { status: failure.status, isolatedFailures: previous.isolatedFailures + 1 } : { status: failure.status, isolatedFailures: 1 };
14263
+ state.sessionDeleteFailures.set(failure.id, record);
14264
+ if (record.isolatedFailures >= SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES) {
14265
+ givenUpFailures.push(failure);
14266
+ }
14267
+ }
14268
+ }
14269
+ for (const failure of givenUpFailures) {
14270
+ const excerpt = redactDiagnosticText(failure.body ?? "(no response body)").slice(
14271
+ 0,
14272
+ MAX_SESSION_DELETE_BODY_LENGTH
14273
+ );
14274
+ logSessionCleanupActivity(state, "sweep", {
14275
+ type: "info",
14276
+ level: "warn",
14277
+ message: `Session cleanup: gave up on session ${failure.id} after ${SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES} isolated delete failures (status ${failure.status}): ${excerpt}`,
14278
+ metadata: {
14279
+ outcome: "session-delete-given-up",
14280
+ session_id: failure.id,
14281
+ status: failure.status,
14282
+ body_excerpt: excerpt,
14283
+ given_up_count: givenUpFailures.length
14284
+ }
14285
+ });
14286
+ }
13681
14287
  const failedNote = failed > 0 ? `, failed ${failed}` : "";
13682
14288
  const skippedNote = skippedNewlyActive > 0 ? `, skipped ${skippedNewlyActive} newly-active` : "";
13683
14289
  const remainingNote = remaining > 0 ? `, remaining ${remaining}` : "";
@@ -13698,21 +14304,31 @@ async function runSweep(state, driver, config) {
13698
14304
  sessions_remaining: remaining,
13699
14305
  ...enumerationReason !== null ? { session_enumeration_reason: enumerationReason } : {}
13700
14306
  };
14307
+ const protectedForReclaim = driver.protectedSessionIds();
13701
14308
  const reclaimResult = await reclaimSessionDbSpace({
13702
14309
  dbPath: sessionDbPath(),
13703
14310
  chunkPages: SESSION_DB_RECLAIM_CHUNK_PAGES,
13704
14311
  pageCeiling: SESSION_DB_RECLAIM_PAGE_CEILING,
13705
14312
  timeBudgetMs: SESSION_DB_RECLAIM_TIME_BUDGET_MS,
13706
- allowFullVacuum: protectedNow.size === 0 && !state.sessionDbProvenanceAnomaly
14313
+ hasLiveTurn: protectedForReclaim.size > 0,
14314
+ hasProvenanceAnomaly: state.sessionDbProvenanceAnomaly
13707
14315
  });
13708
14316
  if (reclaimResult.ok) {
13709
14317
  state.sessionStoreHealth.observeWritable();
13710
14318
  const beforeMib = (reclaimResult.beforeBytes / 1024 / 1024).toFixed(1);
13711
14319
  const afterMib = (reclaimResult.afterBytes / 1024 / 1024).toFixed(1);
13712
14320
  const checkpointNote = reclaimResult.checkpoint.busy ? ` (on-disk file truncation deferred: checkpoint busy, ${reclaimResult.checkpoint.log} WAL frames pending)` : "";
14321
+ const loopElapsedMs = Math.max(
14322
+ 0,
14323
+ reclaimResult.elapsedMs - reclaimResult.checkpointElapsedMs
14324
+ );
14325
+ const reclaimIsSlow = reclaimResult.elapsedMs >= 4 * SESSION_DB_RECLAIM_TIME_BUDGET_MS;
14326
+ const perPageNote = reclaimResult.pagesReclaimed > 0 ? `, per-page ${(loopElapsedMs / reclaimResult.pagesReclaimed).toFixed(2)} ms/page` : "";
14327
+ const slowReclaimNote = reclaimIsSlow ? ` (slow reclaim: total elapsed ${reclaimResult.elapsedMs} ms, checkpoint ${reclaimResult.checkpointElapsedMs} ms, loop ${loopElapsedMs} ms, budget ${SESSION_DB_RECLAIM_TIME_BUDGET_MS} ms${perPageNote})` : "";
13713
14328
  logSessionCleanupActivity(state, "sweep", {
13714
14329
  type: "info",
13715
- message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB (stopped by ${reclaimResult.stoppedBy}; freelist ${reclaimResult.freelistBefore} -> ${reclaimResult.freelistAfter} pages)` + checkpointNote,
14330
+ level: reclaimIsSlow ? "warn" : "info",
14331
+ message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB (stopped by ${reclaimResult.stoppedBy}; freelist ${reclaimResult.freelistBefore} -> ${reclaimResult.freelistAfter} pages)` + checkpointNote + slowReclaimNote,
13716
14332
  metadata: {
13717
14333
  outcome: "reclaimed",
13718
14334
  mode: reclaimResult.mode,
@@ -13725,6 +14341,7 @@ async function runSweep(state, driver, config) {
13725
14341
  freelist_after: reclaimResult.freelistAfter,
13726
14342
  pages_reclaimed: reclaimResult.pagesReclaimed,
13727
14343
  reclaim_elapsed_ms: reclaimResult.elapsedMs,
14344
+ checkpoint_elapsed_ms: reclaimResult.checkpointElapsedMs,
13728
14345
  ...sessionMetadata
13729
14346
  }
13730
14347
  });
@@ -14204,8 +14821,8 @@ async function beginGracefulShutdown(state, trigger) {
14204
14821
  return true;
14205
14822
  }
14206
14823
  );
14207
- const timedOut = new Promise((resolve4) => {
14208
- timer = setTimeout(() => resolve4(false), telemetryBudgetMs);
14824
+ const timedOut = new Promise((resolve5) => {
14825
+ timer = setTimeout(() => resolve5(false), telemetryBudgetMs);
14209
14826
  });
14210
14827
  if (!await Promise.race([flushed, timedOut])) {
14211
14828
  log2(state, `Telemetry flush exceeded ${telemetryBudgetMs}ms \u2014 exiting anyway`, "warn");
@@ -14268,6 +14885,7 @@ async function run(options) {
14268
14885
  messageCount: 0,
14269
14886
  lastProxiedActivityAt: null,
14270
14887
  sessionCleanupTimers: [],
14888
+ sessionDeleteFailures: /* @__PURE__ */ new Map(),
14271
14889
  claudeUsageTimer: null,
14272
14890
  claudeUsageRearm: null,
14273
14891
  openaiUsageTimer: null,
@@ -14476,29 +15094,45 @@ async function run(options) {
14476
15094
  );
14477
15095
  }
14478
15096
  }
14479
- if (options.restoreRunnerCredentials) {
14480
- log2(state, "Restoring runner credentials before starting OpenCode");
14481
- const credentialContext = {
14482
- env: process.env,
14483
- log: (message, level = "info") => {
14484
- if (level === "error") {
14485
- logActivity(state, { type: "error", error: message });
14486
- } else {
14487
- logActivity(state, { type: "info", level, message });
14488
- }
15097
+ const credentialContext = {
15098
+ env: process.env,
15099
+ log: (message, level = "info") => {
15100
+ if (level === "error") {
15101
+ logActivity(state, { type: "error", error: message });
15102
+ } else {
15103
+ logActivity(state, { type: "info", level, message });
14489
15104
  }
14490
- };
14491
- const githubTokenPopulated = await installRunnerSecret(credentialContext);
15105
+ }
15106
+ };
15107
+ const githubTokenPopulated = await installRunnerSecret(credentialContext);
15108
+ if (options.restoreRunnerCredentials) {
14492
15109
  await restoreCredentialStores(credentialContext);
14493
- if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
14494
15110
  }
15111
+ if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
14495
15112
  state.credentialSync?.arm();
15113
+ const dependencyInstallDiagnosticReporter = createDependencyInstallDiagnosticReporter({
15114
+ context: {
15115
+ homeDir: homedir7(),
15116
+ cwd: process.cwd(),
15117
+ microvmId: process.env.MICROVM_ID?.trim(),
15118
+ euid: process.getuid?.(),
15119
+ egid: process.getgid?.()
15120
+ },
15121
+ emitEvent: (message) => logActivity(state, { type: "info", level: "warn", message }),
15122
+ emitLocal: (message) => logLocalOnly(state, message, "warn")
15123
+ });
14496
15124
  const openCodeActivityForwarder = createOpenCodeActivityForwarder(
14497
15125
  () => ({
14498
15126
  agentId: state.agentId,
14499
15127
  authHeader: state.authHeader
14500
15128
  }),
14501
- state.sessionStoreHealth.observeUnwritable
15129
+ ({ isDiskFailure, sessionId, cause }) => {
15130
+ if (isDiskFailure) state.sessionStoreHealth.observeUnwritable();
15131
+ if (cause !== null && sessionId !== null) {
15132
+ state.sessionStoreHealth.observeCause(sessionId, cause);
15133
+ }
15134
+ },
15135
+ dependencyInstallDiagnosticReporter
14502
15136
  );
14503
15137
  const openCodeLogTail = tailOpenCodeLogFile(
14504
15138
  resolveOpenCodeLogPath(homedir7(), process.env),
@@ -14509,22 +15143,27 @@ async function run(options) {
14509
15143
  openCodeLogTail.stop();
14510
15144
  };
14511
15145
  let sessionDbVerifyFatal = false;
15146
+ let sessionDbVerifyFatalReason;
14512
15147
  if (!options.restoreSessionDb) {
14513
- log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
15148
+ logActivity(state, {
15149
+ type: "info",
15150
+ level: "warn",
15151
+ message: "SESSION-DB-VERIFY-SKIPPED: reason=flag-absent \u2014 Skipping session-DB restore: --restore-session-db was not passed"
15152
+ });
14514
15153
  } else {
14515
15154
  const health = await checkOpenCodeHealth(state.port);
14516
15155
  if (health.healthy) {
14517
- log2(
14518
- state,
14519
- "Skipping session-DB restore: OpenCode is already serving this database",
14520
- "debug"
14521
- );
15156
+ logActivity(state, {
15157
+ type: "info",
15158
+ level: "warn",
15159
+ message: "SESSION-DB-VERIFY-SKIPPED: reason=opencode-serving \u2014 Skipping session-DB restore: OpenCode is already serving this database"
15160
+ });
14522
15161
  } else if (health.authFailed) {
14523
- log2(
14524
- state,
14525
- "Skipping session-DB restore: OpenCode answered with an authentication challenge; assuming this database is in use",
14526
- "debug"
14527
- );
15162
+ logActivity(state, {
15163
+ type: "info",
15164
+ level: "warn",
15165
+ message: "SESSION-DB-VERIFY-SKIPPED: reason=opencode-auth-challenge \u2014 Skipping session-DB restore: OpenCode answered with an authentication challenge; assuming this database is in use"
15166
+ });
14528
15167
  } else {
14529
15168
  const result = await restoreAndVerifySessionDb({
14530
15169
  dbPath: sessionDbPath(),
@@ -14541,12 +15180,13 @@ async function run(options) {
14541
15180
  reportRecovery: (record) => reportSessionDbRecoveryRecord(state, record)
14542
15181
  });
14543
15182
  sessionDbVerifyFatal = result.verifyFatal;
15183
+ sessionDbVerifyFatalReason = result.verifyFatalReason;
14544
15184
  }
14545
15185
  }
14546
15186
  reportSessionDbRecovery(state);
14547
15187
  if (sessionDbVerifyFatal) {
14548
15188
  throw new Error(
14549
- "SESSION-DB-INTEGRITY-EXHAUSTED: the corrupt session DB could not be proven separated from the active backup prefix or removed from disk"
15189
+ sessionDbVerifyFatalReason === void 0 ? "SESSION-DB-INTEGRITY-EXHAUSTED: the corrupt session DB could not be proven separated from the active backup prefix or removed from disk" : `SESSION-DB-VERIFY-TIMEOUT: session DB verification timed out (${sessionDbVerifyFatalReason}); refusing to start`
14550
15190
  );
14551
15191
  }
14552
15192
  applyRunnerOpenCodeConfig({