@evident-ai/cli 3.5.2-dev.2828873 → 3.5.2-dev.3431f9f

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
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { createRequire as createRequire2 } from "module";
5
4
  import { Command } from "commander";
6
5
 
7
6
  // src/commands/login.ts
@@ -1158,7 +1157,7 @@ import ora4 from "ora";
1158
1157
  import { select as select4 } from "@inquirer/prompts";
1159
1158
 
1160
1159
  // src/lib/telemetry.ts
1161
- var CLI_VERSION = (true ? "3.5.2-dev.2828873" : void 0) ?? process.env.npm_package_version ?? "unknown";
1160
+ var CLI_VERSION = (true ? "3.5.2-dev.3431f9f" : void 0) ?? process.env.npm_package_version ?? "unknown";
1162
1161
  function getCliVersion() {
1163
1162
  return CLI_VERSION;
1164
1163
  }
@@ -1449,7 +1448,7 @@ function redactHomeDirectory(message) {
1449
1448
  }
1450
1449
  return result + message.slice(searchFrom);
1451
1450
  }
1452
- function redact(message) {
1451
+ function redactDiagnosticText(message) {
1453
1452
  const redacted = message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/(?<![A-Za-z0-9_-])sk-ant-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-ant-***").replace(/(?<![A-Za-z0-9_-])sk-proj-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-proj-***").replace(/(?<![A-Za-z0-9_-])sk-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "sk-***").replace(/(?<![A-Za-z0-9_-])(gh[oprsu])_[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "$1_***").replace(/(?<![A-Za-z0-9_-])github_pat_[A-Za-z0-9_]{20,}(?![A-Za-z0-9_-])/g, "github_pat_***").replace(/https?:\/\/\S+/g, "<url>");
1454
1453
  return redactHomeDirectory(redacted);
1455
1454
  }
@@ -1468,7 +1467,10 @@ function sanitiseMetadata(metadata) {
1468
1467
  const sanitised = [];
1469
1468
  for (const [key, value] of entries) {
1470
1469
  if (typeof value === "string") {
1471
- sanitised.push([key, truncate(redact(value).slice(0, MAX_METADATA_VALUE_LENGTH))]);
1470
+ sanitised.push([
1471
+ key,
1472
+ truncate(redactDiagnosticText(value).slice(0, MAX_METADATA_VALUE_LENGTH))
1473
+ ]);
1472
1474
  } else if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
1473
1475
  sanitised.push([key, value]);
1474
1476
  }
@@ -1513,7 +1515,7 @@ function forwardRunnerActivity(entry, context) {
1513
1515
  if (!context.agentId || !context.authHeader) return;
1514
1516
  if (!admitUnderRateLimit(source, Date.now())) return;
1515
1517
  const rawMessage = entry.error ?? entry.message ?? "";
1516
- const message = truncate(redact(rawMessage));
1518
+ const message = truncate(redactDiagnosticText(rawMessage));
1517
1519
  logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
1518
1520
  severity: SEVERITY_BY_LEVEL[entry.level],
1519
1521
  message,
@@ -1812,8 +1814,46 @@ async function waitForOpenCode2Health(port, password, timeoutMs = 3e4) {
1812
1814
 
1813
1815
  // src/lib/http-timeout.ts
1814
1816
  var REQUEST_TIMEOUT_MS = 6e4;
1817
+ var RequestTimeoutError = class extends Error {
1818
+ constructor(operation, timeoutMs, elapsedMs) {
1819
+ super(`${operation} timed out after ${elapsedMs}ms (limit ${timeoutMs}ms)`);
1820
+ this.operation = operation;
1821
+ this.timeoutMs = timeoutMs;
1822
+ this.elapsedMs = elapsedMs;
1823
+ this.name = "TimeoutError";
1824
+ }
1825
+ };
1826
+ function describeRequestOperation(input, init) {
1827
+ const method = (init?.method ?? input.method ?? "GET").toUpperCase();
1828
+ const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1829
+ let path;
1830
+ try {
1831
+ path = new URL(rawUrl).pathname;
1832
+ } catch (error2) {
1833
+ if (!(error2 instanceof TypeError)) throw error2;
1834
+ path = rawUrl.split(/[?#]/, 1)[0];
1835
+ }
1836
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1837
+ const prefixedIdentifier = /^[a-z]+_[A-Za-z0-9_-]{10,}$/;
1838
+ const maskedPath = path.split("/").map((segment) => uuid.test(segment) || prefixedIdentifier.test(segment) ? ":id" : segment).join("/");
1839
+ return `${method} ${maskedPath}`;
1840
+ }
1815
1841
  function withRequestTimeout(fetchImpl, timeoutMs) {
1816
- return ((input, init) => fetchImpl(input, { ...init, signal: AbortSignal.timeout(timeoutMs) }));
1842
+ return (async (input, init) => {
1843
+ const startedAt = Date.now();
1844
+ try {
1845
+ return await fetchImpl(input, { ...init, signal: AbortSignal.timeout(timeoutMs) });
1846
+ } catch (error2) {
1847
+ if (error2?.name === "TimeoutError") {
1848
+ throw new RequestTimeoutError(
1849
+ describeRequestOperation(input, init),
1850
+ timeoutMs,
1851
+ Date.now() - startedAt
1852
+ );
1853
+ }
1854
+ throw error2;
1855
+ }
1856
+ });
1817
1857
  }
1818
1858
 
1819
1859
  // src/lib/opencode/client.ts
@@ -5470,6 +5510,8 @@ var StreamForwarder = class {
5470
5510
  if (done) break;
5471
5511
  const chunk = Buffer.from(value);
5472
5512
  for (let i = 0; i < chunk.length; i += MAX_FRAME_BYTES) {
5513
+ await stream.pausePromise;
5514
+ if (ac.signal.aborted) break;
5473
5515
  const slice = chunk.subarray(i, i + MAX_FRAME_BYTES);
5474
5516
  if (this.binaryFramesSupported) {
5475
5517
  if (this.ws.readyState === WebSocket.OPEN) {
@@ -5512,6 +5554,7 @@ function classifyUpgradeRejection(headers) {
5512
5554
  }
5513
5555
  var MAX_RECONNECT_DELAY = 3e4;
5514
5556
  var BASE_RECONNECT_DELAY = 500;
5557
+ var RELAY_SILENCE_TIMEOUT_MS = 9e4;
5515
5558
  function getReconnectDelay(attempt) {
5516
5559
  const exponentialDelay = BASE_RECONNECT_DELAY * Math.pow(2, attempt);
5517
5560
  const jitter = Math.random() * 1e3;
@@ -5583,10 +5626,27 @@ function connectTunnel(options) {
5583
5626
  ws.close();
5584
5627
  reject(new Error("Connection timeout"));
5585
5628
  }, 3e4);
5629
+ let relaySilenceTimeout;
5630
+ let relaySilenceTimedOut = false;
5631
+ const clearRelaySilenceTimeout = () => {
5632
+ if (relaySilenceTimeout === void 0) return;
5633
+ clearTimeout(relaySilenceTimeout);
5634
+ relaySilenceTimeout = void 0;
5635
+ };
5636
+ const armRelaySilenceTimeout = () => {
5637
+ clearRelaySilenceTimeout();
5638
+ relaySilenceTimeout = setTimeout(() => {
5639
+ relaySilenceTimeout = void 0;
5640
+ relaySilenceTimedOut = true;
5641
+ ws.terminate();
5642
+ }, RELAY_SILENCE_TIMEOUT_MS);
5643
+ relaySilenceTimeout.unref?.();
5644
+ };
5586
5645
  let upgradeRejection = null;
5587
5646
  let upgradeRejectionReason = null;
5588
5647
  ws.on("unexpected-response", (_req, res) => {
5589
5648
  clearTimeout(connectionTimeout);
5649
+ clearRelaySilenceTimeout();
5590
5650
  const reason = classifyUpgradeRejection(res.headers);
5591
5651
  upgradeRejectionReason = reason;
5592
5652
  const chunks = [];
@@ -5616,6 +5676,7 @@ function connectTunnel(options) {
5616
5676
  onInfo?.("WebSocket connection established");
5617
5677
  });
5618
5678
  ws.on("message", (data, isBinary) => {
5679
+ if (relaySilenceTimeout !== void 0) armRelaySilenceTimeout();
5619
5680
  if (isBinary) {
5620
5681
  try {
5621
5682
  const frame = decodeBinaryBodyFrame(toUint8Array(data));
@@ -5649,6 +5710,7 @@ function connectTunnel(options) {
5649
5710
  switch (message.type) {
5650
5711
  case "connected": {
5651
5712
  clearTimeout(connectionTimeout);
5713
+ armRelaySilenceTimeout();
5652
5714
  forwarder.setBinaryFramesSupported(message.binary_frames === true);
5653
5715
  const connectedAgentId = message.agent_id ?? agentId;
5654
5716
  onConnected?.(connectedAgentId);
@@ -5673,6 +5735,7 @@ function connectTunnel(options) {
5673
5735
  });
5674
5736
  ws.on("error", (error2) => {
5675
5737
  clearTimeout(connectionTimeout);
5738
+ clearRelaySilenceTimeout();
5676
5739
  const detail = upgradeRejection ?? describeSocketError(error2, url);
5677
5740
  if (upgradeRejectionReason === "do_code_updated") {
5678
5741
  onWarning?.("Relay redeployed \u2014 reconnecting");
@@ -5684,7 +5747,8 @@ function connectTunnel(options) {
5684
5747
  );
5685
5748
  });
5686
5749
  ws.on("close", (code, reason) => {
5687
- const reasonStr = reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
5750
+ clearRelaySilenceTimeout();
5751
+ const reasonStr = (relaySilenceTimedOut ? "no relay heartbeat for 90s \u2014 reconnecting" : void 0) || reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
5688
5752
  forwarder.abortAll();
5689
5753
  onDisconnected?.(code, reasonStr);
5690
5754
  });
@@ -5692,9 +5756,10 @@ function connectTunnel(options) {
5692
5756
  }
5693
5757
 
5694
5758
  // src/lib/tunnel/runner-connection.ts
5695
- var RunnerConnection = class {
5759
+ var RunnerConnection = class _RunnerConnection {
5696
5760
  opts;
5697
5761
  sleep;
5762
+ now;
5698
5763
  connection = null;
5699
5764
  resolvedAgentId;
5700
5765
  /** True while a (re)connect loop is in flight. */
@@ -5703,10 +5768,17 @@ var RunnerConnection = class {
5703
5768
  reconnectPromise = null;
5704
5769
  /** 1-based count of the current reconnect attempt streak. */
5705
5770
  reconnectAttempt = 0;
5771
+ generation = 0;
5772
+ disconnectedAtMs = null;
5773
+ static RELAY_RECONNECT_CLOSE_REASONS = /* @__PURE__ */ new Set([
5774
+ "No pong received",
5775
+ "Orphaned socket"
5776
+ ]);
5706
5777
  constructor(opts) {
5707
5778
  this.opts = opts;
5708
5779
  this.resolvedAgentId = opts.agentId;
5709
5780
  this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
5781
+ this.now = opts.now ?? Date.now;
5710
5782
  }
5711
5783
  get agentId() {
5712
5784
  return this.resolvedAgentId;
@@ -5742,14 +5814,24 @@ var RunnerConnection = class {
5742
5814
  port: this.opts.port,
5743
5815
  openCodePassword: this.opts.openCodePassword,
5744
5816
  onConnected: (agentId) => {
5817
+ const attempt = this.reconnectAttempt + 1;
5745
5818
  this.reconnectAttempt = 0;
5746
5819
  this.reconnecting = false;
5747
5820
  this.resolvedAgentId = agentId;
5748
- events.onConnected(agentId, isReconnect);
5821
+ this.generation++;
5822
+ const downtimeMs = this.disconnectedAtMs === null ? null : this.now() - this.disconnectedAtMs;
5823
+ this.disconnectedAtMs = null;
5824
+ events.onConnected(agentId, isReconnect, {
5825
+ generation: this.generation,
5826
+ attempt,
5827
+ downtimeMs
5828
+ });
5749
5829
  },
5750
5830
  onDisconnected: (code, reason) => {
5751
- events.onDisconnected(code, reason);
5752
- if (this.opts.isRunning() && code !== 1e3 && !this.reconnecting) {
5831
+ this.disconnectedAtMs = this.now();
5832
+ events.onDisconnected(code, reason, { generation: this.generation });
5833
+ const relayRequestedReconnect = code === 1e3 && _RunnerConnection.RELAY_RECONNECT_CLOSE_REASONS.has(reason);
5834
+ if (this.opts.isRunning() && (code !== 1e3 || relayRequestedReconnect) && !this.reconnecting) {
5753
5835
  this.reconnectPromise = this.connectWithRetry(true).catch((err) => {
5754
5836
  events.onError?.(`Reconnection failed: ${err.message}`);
5755
5837
  });
@@ -12225,6 +12307,7 @@ function createCredentialSync({
12225
12307
 
12226
12308
  // src/commands/run.ts
12227
12309
  var MAX_ACTIVITY_LOG_ENTRIES = 10;
12310
+ var MAX_DISCONNECT_REASON_LENGTH = 200;
12228
12311
  var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
12229
12312
  var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
12230
12313
  var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
@@ -12443,7 +12526,9 @@ function displayStatus(state) {
12443
12526
  const opencode = state.opencodeConnected ? chalk7.green(`opencode: :${state.port}`) : chalk7.red(`opencode: :${state.port} (down)`);
12444
12527
  const messages = state.messageCount > 0 ? chalk7.dim(` \xB7 ${state.messageCount} processed`) : "";
12445
12528
  const last = state.activityLog[state.activityLog.length - 1];
12446
- const detail = last ? chalk7.dim(` \xB7 ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`) : "";
12529
+ const detail = last ? chalk7.dim(
12530
+ ` \xB7 ${last.timestamp.toISOString()} ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`
12531
+ ) : "";
12447
12532
  const agent = state.agentName ?? state.agentId;
12448
12533
  console.log(
12449
12534
  `${chalk7.bold("Evident")} ${chalk7.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
@@ -13864,12 +13949,13 @@ async function run(options) {
13864
13949
  openCodePassword: state.opencodePassword,
13865
13950
  isRunning: () => state.running,
13866
13951
  events: {
13867
- onConnected: (agentId, isReconnect) => {
13952
+ onConnected: (agentId, isReconnect, transition) => {
13868
13953
  state.connected = true;
13869
13954
  state.agentId = agentId;
13955
+ const connectionMessage = isReconnect ? `Tunnel reconnected (runner: ${agentId}, generation: ${transition.generation}, attempt: ${transition.attempt}, offline for ${transition.downtimeMs}ms)` : `Tunnel connected (runner: ${agentId}, generation: ${transition.generation})`;
13870
13956
  logActivity(state, {
13871
13957
  type: "info",
13872
- message: `Tunnel ${isReconnect ? "reconnected" : "connected"} (runner: ${agentId})`
13958
+ message: connectionMessage
13873
13959
  });
13874
13960
  if (options.tunnelReadyFile) {
13875
13961
  const marker = writeTunnelReadyMarker(options.tunnelReadyFile, agentId);
@@ -13890,7 +13976,12 @@ async function run(options) {
13890
13976
  version: state.opencodeVersion,
13891
13977
  major: state.opencodeApiVersion,
13892
13978
  connected: state.opencodeConnected
13893
- })
13979
+ }),
13980
+ connection_generation: transition.generation,
13981
+ ...isReconnect && transition.downtimeMs !== null ? {
13982
+ reconnect_attempt: transition.attempt,
13983
+ downtime_ms: transition.downtimeMs
13984
+ } : {}
13894
13985
  });
13895
13986
  if (!isReconnect) tunnelSpinner?.succeed("Tunnel connected");
13896
13987
  if (state.interactive) displayStatus(state);
@@ -13912,13 +14003,18 @@ async function run(options) {
13912
14003
  if (state.interactive) displayStatus(state);
13913
14004
  });
13914
14005
  },
13915
- onDisconnected: (code, reason) => {
14006
+ onDisconnected: (code, reason, transition) => {
13916
14007
  state.connected = false;
14008
+ const safeReason = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
13917
14009
  logActivity(state, {
13918
14010
  type: "info",
13919
- message: `Tunnel disconnected (code: ${code}, reason: ${reason})`
14011
+ message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason})`
14012
+ });
14013
+ emitAgentDisconnected(state.agentId, {
14014
+ code,
14015
+ reason: safeReason,
14016
+ connection_generation: transition.generation
13920
14017
  });
13921
- emitAgentDisconnected(state.agentId, { code, reason });
13922
14018
  if (state.interactive) displayStatus(state);
13923
14019
  },
13924
14020
  onError: (error2) => {
@@ -14061,7 +14157,7 @@ async function run(options) {
14061
14157
  }
14062
14158
 
14063
14159
  // src/index.ts
14064
- var { version } = createRequire2(import.meta.url)("../package.json");
14160
+ var version = getCliVersion();
14065
14161
  var program = new Command();
14066
14162
  program.name("evident").description("Run OpenCode locally and connect it to Evident").version(version).option(
14067
14163
  "--endpoint <url>",