@evident-ai/cli 3.5.2-dev.4f7b3ac → 3.5.2-dev.4f96188

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
@@ -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.4f7b3ac" : void 0) ?? process.env.npm_package_version ?? "unknown";
1325
+ var CLI_VERSION = (true ? "3.5.2-dev.4f96188" : void 0) ?? process.env.npm_package_version ?? "unknown";
1326
1326
  function getCliVersion() {
1327
1327
  return CLI_VERSION;
1328
1328
  }
@@ -1483,12 +1483,38 @@ 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";
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
1492
1518
  var SESSION_DB_RECLAIM_CHUNK_PAGES = 2e3;
1493
1519
  var SESSION_DB_RECLAIM_TIME_BUDGET_MS = 500;
1494
1520
  var SESSION_DB_RECLAIM_PAGE_CEILING = 4e4;
@@ -1496,7 +1522,7 @@ function errorMessage(error2) {
1496
1522
  if (!(error2 instanceof Error)) return String(error2);
1497
1523
  return error2.cause instanceof Error ? error2.cause.message : error2.message;
1498
1524
  }
1499
- function isSessionStoreWriteFailure(error2) {
1525
+ function errorMessages(error2) {
1500
1526
  const messages = [];
1501
1527
  const visited = /* @__PURE__ */ new Set();
1502
1528
  let current = error2;
@@ -1520,12 +1546,81 @@ function isSessionStoreWriteFailure(error2) {
1520
1546
  }
1521
1547
  break;
1522
1548
  }
1523
- return messages.some(
1549
+ return messages;
1550
+ }
1551
+ function isSessionStoreWriteFailure(error2) {
1552
+ return errorMessages(error2).some(
1524
1553
  (message) => /(?:database or disk is full|disk is full|no space left on device|SQLITE_(?:FULL|IOERR)(?:\b|_))/i.test(
1525
1554
  message
1526
1555
  )
1527
1556
  );
1528
1557
  }
1558
+ 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;
1559
+ var EFFECT_SQL_CAUSE_TOKEN = /(?:^|cause:\s+)(effect\/sql\/SqlError\/[A-Za-z][A-Za-z0-9_]*)/i;
1560
+ var PARAMS_FIELD = /\bparams\s*(?:=|:)/i;
1561
+ function isCauseBearingText(text) {
1562
+ return /^(?:SQLiteError:\s+|effect\/sql\/SqlError\/)/i.test(text.trim()) || /(?:^|\s)cause:\s+(?:SQLiteError:\s+|effect\/sql\/SqlError\/)/i.test(text);
1563
+ }
1564
+ function structuredLogFields(line) {
1565
+ const fields = [];
1566
+ for (let index = 0; index < line.length; index++) {
1567
+ if (line[index] === '"') {
1568
+ index++;
1569
+ while (index < line.length) {
1570
+ if (line[index] === "\\") {
1571
+ index++;
1572
+ } else if (line[index] === '"') {
1573
+ break;
1574
+ }
1575
+ index++;
1576
+ }
1577
+ continue;
1578
+ }
1579
+ const atFieldBoundary = index === 0 || /\s/.test(line[index - 1]);
1580
+ const key = line.startsWith("error=", index) ? "error=" : line.startsWith("cause=", index) ? "cause=" : null;
1581
+ if (!atFieldBoundary || key === null) continue;
1582
+ const valueStart = index + key.length;
1583
+ if (line[valueStart] !== '"') {
1584
+ const valueEnd2 = line.indexOf(" ", valueStart);
1585
+ fields.push(line.slice(valueStart, valueEnd2 === -1 ? line.length : valueEnd2));
1586
+ index = valueEnd2 === -1 ? line.length : valueEnd2;
1587
+ continue;
1588
+ }
1589
+ let valueEnd = valueStart + 1;
1590
+ for (; valueEnd < line.length; valueEnd++) {
1591
+ if (line[valueEnd] === "\\") {
1592
+ valueEnd++;
1593
+ } else if (line[valueEnd] === '"') {
1594
+ break;
1595
+ }
1596
+ }
1597
+ fields.push(line.slice(valueStart + 1, valueEnd));
1598
+ index = valueEnd;
1599
+ }
1600
+ return fields;
1601
+ }
1602
+ function structuredCauseMessages(text) {
1603
+ const fields = structuredLogFields(text);
1604
+ if (fields.length > 0) return fields;
1605
+ return isCauseBearingText(text) ? [text] : [];
1606
+ }
1607
+ function causeTokenFromText(text) {
1608
+ const paramsIndex = text.search(PARAMS_FIELD);
1609
+ for (const pattern of [SQLITE_CAUSE_TOKEN, EFFECT_SQL_CAUSE_TOKEN]) {
1610
+ const match = pattern.exec(text);
1611
+ if (match?.[1] && (paramsIndex === -1 || match.index < paramsIndex)) {
1612
+ return match[1];
1613
+ }
1614
+ }
1615
+ return null;
1616
+ }
1617
+ function sessionStoreCauseToken(value) {
1618
+ for (const message of structuredCauseMessages(value)) {
1619
+ const token = causeTokenFromText(message);
1620
+ if (token !== null) return redactDiagnosticText(token).slice(0, 120);
1621
+ }
1622
+ return null;
1623
+ }
1529
1624
  function insufficientSpaceReason(dbPath, requiredBytes) {
1530
1625
  try {
1531
1626
  const fsStats = statfsSync(dirname2(dbPath));
@@ -1759,16 +1854,22 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
1759
1854
  }
1760
1855
  };
1761
1856
  const flushLine = (line) => {
1762
- if (onSessionStoreWriteFailure && isSessionStoreWriteFailure(line)) {
1857
+ const parsed = parseOpenCodeLogLine(line);
1858
+ const isDiskFailure = isSessionStoreWriteFailure(line);
1859
+ const cause = sessionStoreCauseToken(line);
1860
+ if (onSessionStoreWriteFailure && (isDiskFailure || cause !== null)) {
1763
1861
  try {
1764
- onSessionStoreWriteFailure();
1862
+ onSessionStoreWriteFailure({
1863
+ isDiskFailure,
1864
+ sessionId: parsed?.sessionID ?? null,
1865
+ cause
1866
+ });
1765
1867
  } catch (err) {
1766
1868
  reportTelemetryDiagnostic(
1767
1869
  `Runner activity telemetry could not record session-store write failure: ${err instanceof Error ? err.message : String(err)}`
1768
1870
  );
1769
1871
  }
1770
1872
  }
1771
- const parsed = parseOpenCodeLogLine(line);
1772
1873
  if (!parsed) return;
1773
1874
  const now = Date.now();
1774
1875
  const key = parsed.message === void 0 ? void 0 : `${parsed.level}\0${parsed.message}\0${parsed.sessionID ?? ""}\0${parsed.messageID ?? ""}`;
@@ -1817,34 +1918,21 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
1817
1918
  var MAX_MESSAGE_LENGTH = 500;
1818
1919
  var MAX_METADATA_VALUE_LENGTH = 200;
1819
1920
  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
1921
  function truncate(message) {
1846
1922
  if (message.length <= MAX_MESSAGE_LENGTH) return message;
1847
- return message.slice(0, MAX_MESSAGE_LENGTH - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
1923
+ let dropped = message.length - MAX_MESSAGE_LENGTH;
1924
+ let marker = `\u2026${dropped} chars omitted\u2026`;
1925
+ let previousDropped = -1;
1926
+ while (dropped !== previousDropped) {
1927
+ previousDropped = dropped;
1928
+ const nextDropped = message.length - MAX_MESSAGE_LENGTH + marker.length;
1929
+ dropped = nextDropped;
1930
+ marker = `\u2026${dropped} chars omitted\u2026`;
1931
+ }
1932
+ const remaining = MAX_MESSAGE_LENGTH - marker.length;
1933
+ const headLength = Math.ceil(remaining * 0.6);
1934
+ const tailLength = remaining - headLength;
1935
+ return message.slice(0, headLength) + marker + message.slice(message.length - tailLength);
1848
1936
  }
1849
1937
  function sanitiseMetadata(metadata) {
1850
1938
  if (!metadata) return {};
@@ -3075,6 +3163,18 @@ function messageError(messages, userMessageId) {
3075
3163
  ) : void 0;
3076
3164
  return typeof toolPart?.tool === "string" && toolPart.tool.length > 0 ? `${rendered} (tool "${toolPart.tool}")` : rendered;
3077
3165
  }
3166
+ function describeAbandonedReplyShape(messages, userMessageId) {
3167
+ const reply = findLastAssistantReplyFor(messages, userMessageId);
3168
+ if (!reply) return null;
3169
+ const parts = Array.isArray(reply.parts) ? reply.parts : [];
3170
+ const lastTool = [...parts].reverse().find((part) => part?.type === "tool");
3171
+ return {
3172
+ partTypes: parts.slice(-12).map((part) => typeof part?.type === "string" ? part.type : "(unknown)"),
3173
+ droppedPartCount: Math.max(0, parts.length - 12),
3174
+ lastToolName: typeof lastTool?.tool === "string" && lastTool.tool.length > 0 ? lastTool.tool : null,
3175
+ lastToolStatus: typeof lastTool?.state?.status === "string" && lastTool.state.status.length > 0 ? lastTool.state.status : null
3176
+ };
3177
+ }
3078
3178
  function isAbortedTerminalReply(messages, userMessageId) {
3079
3179
  const reply = findLastAssistantReplyFor(messages, userMessageId);
3080
3180
  const error2 = errorOf(reply);
@@ -6602,6 +6702,7 @@ function createSessionStoreHealth(options = {}) {
6602
6702
  const stalenessMs = Math.max(0, options.stalenessMs ?? SESSION_STORE_HEALTH_STALENESS_MS);
6603
6703
  let lastUnwritableAt = null;
6604
6704
  let lastWritableAt = null;
6705
+ const causes = /* @__PURE__ */ new Map();
6605
6706
  return {
6606
6707
  observeUnwritable: () => {
6607
6708
  lastUnwritableAt = now();
@@ -6613,6 +6714,23 @@ function createSessionStoreHealth(options = {}) {
6613
6714
  if (lastUnwritableAt === null) return false;
6614
6715
  if (lastWritableAt !== null && lastWritableAt >= lastUnwritableAt) return false;
6615
6716
  return now() - lastUnwritableAt < stalenessMs;
6717
+ },
6718
+ observeCause: (sessionId, cause) => {
6719
+ if (sessionId === null) return;
6720
+ causes.delete(sessionId);
6721
+ causes.set(sessionId, { cause, observedAt: now() });
6722
+ while (causes.size > 256) {
6723
+ const oldest = causes.keys().next().value;
6724
+ if (typeof oldest !== "string") break;
6725
+ causes.delete(oldest);
6726
+ }
6727
+ },
6728
+ readCause: (sessionId) => {
6729
+ const observed = causes.get(sessionId);
6730
+ if (!observed) return null;
6731
+ causes.delete(sessionId);
6732
+ if (now() - observed.observedAt >= stalenessMs) return null;
6733
+ return observed.cause;
6616
6734
  }
6617
6735
  };
6618
6736
  }
@@ -7076,6 +7194,7 @@ var MAX_BUFFERED_SESSION_ERRORS = 256;
7076
7194
  var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
7077
7195
  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.";
7078
7196
  var SESSION_ERROR_RETAINED_DETAIL = "The runner's session database could not be written, so this turn stopped part-way. Runner-observed session error: ";
7197
+ var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
7079
7198
  var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
7080
7199
  var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
7081
7200
  var DRIVER_SESSION_ENUMERATION_LIMIT = 20;
@@ -9690,8 +9809,10 @@ var ChannelDriver = class _ChannelDriver {
9690
9809
  const messageErrorText = messageError(messages, inFlight.opencodeMessageId);
9691
9810
  const retainedReason = this.readRetainedSessionError(sessionId);
9692
9811
  const retainedError = retainedReason === null ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
9812
+ const observedCause = this.sessionStoreHealth?.readCause(sessionId);
9813
+ const causeError = observedCause === null || observedCause === void 0 ? null : `${SESSION_ERROR_OBSERVED_CAUSE}${observedCause}`;
9693
9814
  const storeError = this.sessionStoreHealth?.isUnwritable() ? SESSION_STORE_UNWRITABLE_ERROR : null;
9694
- const runnerDetails = [retainedError, storeError].filter(
9815
+ const runnerDetails = [retainedError, causeError, storeError].filter(
9695
9816
  (detail) => detail !== null
9696
9817
  );
9697
9818
  const error2 = messageErrorText === null ? runnerDetails.length === 0 ? void 0 : runnerDetails.join(" ") : [messageErrorText, ...runnerDetails].join(" ");
@@ -10137,7 +10258,9 @@ var ChannelDriver = class _ChannelDriver {
10137
10258
  /** Report a confirmed b2 abandonment that produced no answer text. */
10138
10259
  async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages, finish) {
10139
10260
  const conv = watcher.conv;
10140
- 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.`;
10261
+ const legacyError = `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.`;
10262
+ const replyShape = describeAbandonedReplyShape(messages, inFlight.opencodeMessageId);
10263
+ const error2 = replyShape === null ? legacyError : `${legacyError} ${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)` : ""}.`;
10141
10264
  this.anchorDeliveryDeadline(inFlight);
10142
10265
  this.log({
10143
10266
  level: "warn",
@@ -14564,7 +14687,12 @@ async function run(options) {
14564
14687
  agentId: state.agentId,
14565
14688
  authHeader: state.authHeader
14566
14689
  }),
14567
- state.sessionStoreHealth.observeUnwritable
14690
+ ({ isDiskFailure, sessionId, cause }) => {
14691
+ if (isDiskFailure) state.sessionStoreHealth.observeUnwritable();
14692
+ if (cause !== null && sessionId !== null) {
14693
+ state.sessionStoreHealth.observeCause(sessionId, cause);
14694
+ }
14695
+ }
14568
14696
  );
14569
14697
  const openCodeLogTail = tailOpenCodeLogFile(
14570
14698
  resolveOpenCodeLogPath(homedir7(), process.env),
@@ -14577,21 +14705,25 @@ async function run(options) {
14577
14705
  let sessionDbVerifyFatal = false;
14578
14706
  let sessionDbVerifyFatalReason;
14579
14707
  if (!options.restoreSessionDb) {
14580
- log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
14708
+ logActivity(state, {
14709
+ type: "info",
14710
+ level: "warn",
14711
+ message: "SESSION-DB-VERIFY-SKIPPED: reason=flag-absent \u2014 Skipping session-DB restore: --restore-session-db was not passed"
14712
+ });
14581
14713
  } else {
14582
14714
  const health = await checkOpenCodeHealth(state.port);
14583
14715
  if (health.healthy) {
14584
- log2(
14585
- state,
14586
- "Skipping session-DB restore: OpenCode is already serving this database",
14587
- "debug"
14588
- );
14716
+ logActivity(state, {
14717
+ type: "info",
14718
+ level: "warn",
14719
+ message: "SESSION-DB-VERIFY-SKIPPED: reason=opencode-serving \u2014 Skipping session-DB restore: OpenCode is already serving this database"
14720
+ });
14589
14721
  } else if (health.authFailed) {
14590
- log2(
14591
- state,
14592
- "Skipping session-DB restore: OpenCode answered with an authentication challenge; assuming this database is in use",
14593
- "debug"
14594
- );
14722
+ logActivity(state, {
14723
+ type: "info",
14724
+ level: "warn",
14725
+ 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"
14726
+ });
14595
14727
  } else {
14596
14728
  const result = await restoreAndVerifySessionDb({
14597
14729
  dbPath: sessionDbPath(),