@evident-ai/cli 3.1.1-dev.a98a2ef → 3.1.1-dev.b06d82f

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
@@ -38,11 +38,6 @@ function getApiUrl() {
38
38
  function getTunnelUrl() {
39
39
  return tunnelOverride ?? process.env.EVIDENT_TUNNEL_URL ?? defaults.tunnelUrl;
40
40
  }
41
- var config = new Conf({
42
- projectName: "evident",
43
- projectSuffix: "",
44
- defaults
45
- });
46
41
  var credentials = new Conf({
47
42
  projectName: "evident",
48
43
  projectSuffix: "",
@@ -537,6 +532,343 @@ async function whoami() {
537
532
  blank();
538
533
  }
539
534
 
535
+ // src/lib/auth.ts
536
+ async function getAuthCredentials() {
537
+ const runnerKey = process.env.EVIDENT_RUNNER_KEY;
538
+ const agentKey = process.env.EVIDENT_AGENT_KEY;
539
+ if (runnerKey) {
540
+ return {
541
+ token: runnerKey,
542
+ authType: "agent_key",
543
+ keySource: "runner_key",
544
+ notice: agentKey ? "Both EVIDENT_RUNNER_KEY and EVIDENT_AGENT_KEY are set; using EVIDENT_RUNNER_KEY." : void 0
545
+ };
546
+ }
547
+ if (agentKey) {
548
+ return { token: agentKey, authType: "agent_key", keySource: "agent_key" };
549
+ }
550
+ const userToken = process.env.EVIDENT_TOKEN;
551
+ if (userToken) {
552
+ return { token: userToken, authType: "bearer" };
553
+ }
554
+ const keychainCreds = await getToken();
555
+ if (keychainCreds) {
556
+ return {
557
+ token: keychainCreds.token,
558
+ authType: "bearer",
559
+ user: keychainCreds.user
560
+ };
561
+ }
562
+ return null;
563
+ }
564
+ function getAuthHeader(credentials2) {
565
+ if (credentials2.authType === "agent_key") {
566
+ return `SandboxKey ${credentials2.token}`;
567
+ }
568
+ return `Bearer ${credentials2.token}`;
569
+ }
570
+ function isInteractive(jsonOutput) {
571
+ if (jsonOutput) return false;
572
+ if (process.env.CI) return false;
573
+ if (process.env.GITHUB_ACTIONS) return false;
574
+ if (!process.stdin.isTTY) return false;
575
+ return true;
576
+ }
577
+
578
+ // src/commands/agent-lookup.ts
579
+ async function readErrorMessage(response) {
580
+ const text = await response.text().catch(() => "");
581
+ if (!text) return response.statusText || void 0;
582
+ try {
583
+ const data = JSON.parse(text);
584
+ const message = data.message ?? data.error;
585
+ if (typeof message === "string" && message.trim()) {
586
+ return message;
587
+ }
588
+ } catch {
589
+ }
590
+ return text.trim() || response.statusText || void 0;
591
+ }
592
+ function authFailureHint(apiUrl, serverMessage) {
593
+ const reason = serverMessage ? `: ${serverMessage}` : "";
594
+ return `Authentication failed${reason}. Your credentials were rejected by ${apiUrl}. This usually means you logged in against a different environment, or your session expired \u2014 log in again pointing at this endpoint and retry.`;
595
+ }
596
+ async function resolveAgentIdFromKey(authHeader) {
597
+ const apiUrl = getApiUrlConfig();
598
+ try {
599
+ const response = await fetch(`${apiUrl}/me`, {
600
+ headers: { Authorization: authHeader }
601
+ });
602
+ if (response.status === 401) {
603
+ const serverMessage = await readErrorMessage(response);
604
+ return { error: authFailureHint(apiUrl, serverMessage), authFailed: true };
605
+ }
606
+ if (!response.ok) {
607
+ const serverMessage = await readErrorMessage(response);
608
+ return {
609
+ error: `Failed to resolve runner from key (HTTP ${response.status})${serverMessage ? `: ${serverMessage}` : ""}`
610
+ };
611
+ }
612
+ const data = await response.json();
613
+ if (data.auth_type === "agent_key" && data.agent_id) {
614
+ return { agent_id: data.agent_id };
615
+ }
616
+ return {
617
+ error: "Cannot resolve runner ID: auth type is not agent_key. Please provide --runner explicitly."
618
+ };
619
+ } catch (error2) {
620
+ const message = error2 instanceof Error ? error2.message : "Unknown error";
621
+ return { error: `Failed to resolve runner from key: ${message}` };
622
+ }
623
+ }
624
+ var BEST_EFFORT_NOTIFY_TIMEOUT_MS = 2e3;
625
+ async function notifyAgentDisconnected(agentId, authHeader) {
626
+ const apiUrl = getApiUrlConfig();
627
+ try {
628
+ const response = await fetch(`${apiUrl}/runners/${agentId}/disconnect`, {
629
+ method: "POST",
630
+ headers: { Authorization: authHeader },
631
+ signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
632
+ });
633
+ if (!response.ok) {
634
+ const serverMessage = await readErrorMessage(response);
635
+ return {
636
+ ok: false,
637
+ error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
638
+ };
639
+ }
640
+ return { ok: true };
641
+ } catch (error2) {
642
+ return { ok: false, error: describeBestEffortError(error2) };
643
+ }
644
+ }
645
+ function describeBestEffortError(error2) {
646
+ const name = error2?.name;
647
+ if (name === "TimeoutError" || name === "AbortError") {
648
+ return `timed out after ${BEST_EFFORT_NOTIFY_TIMEOUT_MS}ms`;
649
+ }
650
+ return error2 instanceof Error ? error2.message : String(error2);
651
+ }
652
+ async function reportMicrovmId(agentId, authHeader, microvmId) {
653
+ try {
654
+ const apiUrl = getApiUrlConfig();
655
+ const response = await fetch(`${apiUrl}/runners/${agentId}/microvm`, {
656
+ method: "POST",
657
+ headers: { Authorization: authHeader, "Content-Type": "application/json" },
658
+ body: JSON.stringify({ microvm_id: microvmId }),
659
+ signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
660
+ });
661
+ if (!response.ok) {
662
+ const serverMessage = await readErrorMessage(response);
663
+ return {
664
+ ok: false,
665
+ error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
666
+ };
667
+ }
668
+ return { ok: true };
669
+ } catch (error2) {
670
+ return { ok: false, error: describeBestEffortError(error2) };
671
+ }
672
+ }
673
+ function toReportedWindow(window) {
674
+ if (!window) return null;
675
+ return { utilization: window.utilization, resets_at: window.resetsAt };
676
+ }
677
+ async function reportClaudeUsage(agentId, authHeader, snapshot) {
678
+ try {
679
+ const apiUrl = getApiUrlConfig();
680
+ const response = await fetch(`${apiUrl}/runners/${agentId}/claude-usage`, {
681
+ method: "POST",
682
+ headers: { Authorization: authHeader, "Content-Type": "application/json" },
683
+ body: JSON.stringify({
684
+ five_hour: toReportedWindow(snapshot.fiveHour),
685
+ seven_day: toReportedWindow(snapshot.sevenDay)
686
+ }),
687
+ signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
688
+ });
689
+ if (!response.ok) {
690
+ const serverMessage = await readErrorMessage(response);
691
+ return {
692
+ ok: false,
693
+ error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
694
+ };
695
+ }
696
+ return { ok: true };
697
+ } catch (error2) {
698
+ return { ok: false, error: describeBestEffortError(error2) };
699
+ }
700
+ }
701
+ async function getAgentInfo(agentId, authHeader) {
702
+ const apiUrl = getApiUrlConfig();
703
+ try {
704
+ const response = await fetch(`${apiUrl}/runners/${agentId}`, {
705
+ headers: { Authorization: authHeader }
706
+ });
707
+ if (response.status === 401) {
708
+ const serverMessage = await readErrorMessage(response);
709
+ return { valid: false, error: authFailureHint(apiUrl, serverMessage), authFailed: true };
710
+ }
711
+ if (response.status === 403) {
712
+ const serverMessage = await readErrorMessage(response);
713
+ return {
714
+ valid: false,
715
+ error: serverMessage ?? "You do not have access to this runner (it may belong to a different team or organization)."
716
+ };
717
+ }
718
+ if (response.status === 404) {
719
+ const serverMessage = await readErrorMessage(response);
720
+ return { valid: false, error: serverMessage ?? `Runner ${agentId} not found` };
721
+ }
722
+ if (!response.ok) {
723
+ const serverMessage = await readErrorMessage(response);
724
+ return {
725
+ valid: false,
726
+ error: `API error (HTTP ${response.status})${serverMessage ? `: ${serverMessage}` : ""}`
727
+ };
728
+ }
729
+ const agent = await response.json();
730
+ if (agent.agent_type !== "local") {
731
+ return {
732
+ valid: false,
733
+ error: `Runner is type '${agent.agent_type}', must be 'local' for CLI connection`
734
+ };
735
+ }
736
+ return { valid: true, agent };
737
+ } catch (error2) {
738
+ const message = error2 instanceof Error ? error2.message : "Unknown error";
739
+ return { valid: false, error: `Failed to validate runner: ${message}` };
740
+ }
741
+ }
742
+
743
+ // src/commands/status.ts
744
+ var STATUS_TIMEOUT_MS = 1e4;
745
+ function authLabelFor(credentials2) {
746
+ if (credentials2.authType === "agent_key") {
747
+ return credentials2.keySource === "agent_key" ? "runner key (EVIDENT_AGENT_KEY)" : "runner key (EVIDENT_RUNNER_KEY)";
748
+ }
749
+ return "user token";
750
+ }
751
+ function describeFetchError(error2) {
752
+ const name = error2?.name;
753
+ if (name === "TimeoutError" || name === "AbortError") {
754
+ return `timed out after ${STATUS_TIMEOUT_MS}ms waiting for a response`;
755
+ }
756
+ return error2 instanceof Error ? error2.message : String(error2);
757
+ }
758
+ async function checkStatus(jsonMode) {
759
+ const apiUrl = getApiUrlConfig();
760
+ const credentials2 = await getAuthCredentials();
761
+ if (!credentials2) {
762
+ return {
763
+ ok: false,
764
+ endpoint: apiUrl,
765
+ reason: "no_credentials",
766
+ error: "No credentials configured. Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY), or EVIDENT_TOKEN, or run `evident login`.",
767
+ exitCode: 1
768
+ };
769
+ }
770
+ if (credentials2.notice && !jsonMode) {
771
+ printWarning(credentials2.notice);
772
+ }
773
+ let response;
774
+ try {
775
+ response = await fetch(`${apiUrl}/me`, {
776
+ headers: { Authorization: getAuthHeader(credentials2) },
777
+ signal: AbortSignal.timeout(STATUS_TIMEOUT_MS)
778
+ });
779
+ } catch (error2) {
780
+ return {
781
+ ok: false,
782
+ endpoint: apiUrl,
783
+ authLabel: authLabelFor(credentials2),
784
+ reason: "unreachable",
785
+ error: `Could not reach ${apiUrl}: ${describeFetchError(error2)}. The credentials were NOT validated.`,
786
+ exitCode: 75
787
+ };
788
+ }
789
+ if (response.status === 401) {
790
+ const serverMessage = await readErrorMessage(response);
791
+ return {
792
+ ok: false,
793
+ endpoint: apiUrl,
794
+ authLabel: authLabelFor(credentials2),
795
+ reason: "unauthorized",
796
+ error: authFailureHint(apiUrl, serverMessage),
797
+ exitCode: 1
798
+ };
799
+ }
800
+ if (response.status >= 500) {
801
+ const serverMessage = await readErrorMessage(response);
802
+ return {
803
+ ok: false,
804
+ endpoint: apiUrl,
805
+ authLabel: authLabelFor(credentials2),
806
+ reason: "unreachable",
807
+ error: `${apiUrl} returned HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}. The credentials were NOT validated.`,
808
+ exitCode: 75
809
+ };
810
+ }
811
+ if (!response.ok) {
812
+ const serverMessage = await readErrorMessage(response);
813
+ return {
814
+ ok: false,
815
+ endpoint: apiUrl,
816
+ authLabel: authLabelFor(credentials2),
817
+ reason: "http_error",
818
+ error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`,
819
+ exitCode: 1
820
+ };
821
+ }
822
+ const data = await response.json();
823
+ return {
824
+ ok: true,
825
+ endpoint: apiUrl,
826
+ authType: data.auth_type,
827
+ authLabel: authLabelFor(credentials2),
828
+ runnerId: data.auth_type === "agent_key" ? data.agent_id : void 0,
829
+ reason: "ok",
830
+ exitCode: 0
831
+ };
832
+ }
833
+ function printJson(result) {
834
+ const payload = {
835
+ ok: result.ok,
836
+ endpoint: result.endpoint
837
+ };
838
+ if (result.authType) payload.auth_type = result.authType;
839
+ if (result.runnerId) payload.runner_id = result.runnerId;
840
+ if (result.reason) payload.reason = result.reason;
841
+ if (result.error) payload.error = result.error;
842
+ console.log(JSON.stringify(payload));
843
+ }
844
+ function printHuman(result) {
845
+ blank();
846
+ console.log(keyValue("Endpoint", result.endpoint));
847
+ if (result.ok) {
848
+ console.log(keyValue("Auth", result.authLabel ?? "\u2014"));
849
+ if (result.runnerId) {
850
+ console.log(keyValue("Runner", result.runnerId));
851
+ }
852
+ console.log(keyValue("Status", "OK \u2014 credentials accepted"));
853
+ blank();
854
+ return;
855
+ }
856
+ if (result.authLabel) {
857
+ console.log(keyValue("Auth", result.authLabel));
858
+ }
859
+ blank();
860
+ printError(result.error ?? "Unknown error");
861
+ }
862
+ async function status(options = {}) {
863
+ const result = await checkStatus(Boolean(options.json));
864
+ if (options.json) {
865
+ printJson(result);
866
+ } else {
867
+ printHuman(result);
868
+ }
869
+ process.exit(result.exitCode);
870
+ }
871
+
540
872
  // src/lib/claude-usage.ts
541
873
  import { execFileSync } from "child_process";
542
874
  import { readFileSync } from "fs";
@@ -587,6 +919,10 @@ var ClaudeUsageError = class extends Error {
587
919
  function isLocalCredentialProblem(err) {
588
920
  return err instanceof ClaudeUsageError && (err.reason === "no_credentials" || err.reason === "credentials_expired");
589
921
  }
922
+ function normalizeResetsAt(value) {
923
+ const ms = Date.parse(value);
924
+ return Number.isNaN(ms) ? null : new Date(ms).toISOString();
925
+ }
590
926
  function toWindow(value) {
591
927
  if (!value || typeof value !== "object") {
592
928
  return null;
@@ -595,7 +931,11 @@ function toWindow(value) {
595
931
  if (typeof window.utilization !== "number" || typeof window.resets_at !== "string") {
596
932
  return null;
597
933
  }
598
- return { utilization: window.utilization, resetsAt: window.resets_at };
934
+ const resetsAt = normalizeResetsAt(window.resets_at);
935
+ if (resetsAt === null) {
936
+ return null;
937
+ }
938
+ return { utilization: window.utilization, resetsAt };
599
939
  }
600
940
  async function getClaudeUsage() {
601
941
  const credentials2 = readClaudeCliCredentials();
@@ -939,49 +1279,6 @@ function forwardRunnerActivity(entry, context) {
939
1279
  }
940
1280
  }
941
1281
 
942
- // src/lib/auth.ts
943
- async function getAuthCredentials() {
944
- const runnerKey = process.env.EVIDENT_RUNNER_KEY;
945
- const agentKey = process.env.EVIDENT_AGENT_KEY;
946
- if (runnerKey) {
947
- return {
948
- token: runnerKey,
949
- authType: "agent_key",
950
- keySource: "runner_key",
951
- notice: agentKey ? "Both EVIDENT_RUNNER_KEY and EVIDENT_AGENT_KEY are set; using EVIDENT_RUNNER_KEY." : void 0
952
- };
953
- }
954
- if (agentKey) {
955
- return { token: agentKey, authType: "agent_key", keySource: "agent_key" };
956
- }
957
- const userToken = process.env.EVIDENT_TOKEN;
958
- if (userToken) {
959
- return { token: userToken, authType: "bearer" };
960
- }
961
- const keychainCreds = await getToken();
962
- if (keychainCreds) {
963
- return {
964
- token: keychainCreds.token,
965
- authType: "bearer",
966
- user: keychainCreds.user
967
- };
968
- }
969
- return null;
970
- }
971
- function getAuthHeader(credentials2) {
972
- if (credentials2.authType === "agent_key") {
973
- return `SandboxKey ${credentials2.token}`;
974
- }
975
- return `Bearer ${credentials2.token}`;
976
- }
977
- function isInteractive(jsonOutput) {
978
- if (jsonOutput) return false;
979
- if (process.env.CI) return false;
980
- if (process.env.GITHUB_ACTIONS) return false;
981
- if (!process.stdin.isTTY) return false;
982
- return true;
983
- }
984
-
985
1282
  // src/lib/opencode/health.ts
986
1283
  async function checkOpenCodeHealth(port) {
987
1284
  try {
@@ -2391,6 +2688,10 @@ function nextReportDelayMs(random = Math.random) {
2391
2688
  return BASE_REPORT_DELAY_MS - jitterRangeMs + random() * (2 * jitterRangeMs);
2392
2689
  }
2393
2690
  var FIRST_REPORT_DELAY_MS = 5e3 + Math.random() * 1e4;
2691
+ var CLAUDE_USAGE_FAILURE_REESCALATION_TICKS = 6;
2692
+ function claudeUsageFailureLogLevel(consecutiveFailures) {
2693
+ return consecutiveFailures === 1 || consecutiveFailures % CLAUDE_USAGE_FAILURE_REESCALATION_TICKS === 0 ? "warn" : "debug";
2694
+ }
2394
2695
 
2395
2696
  // src/lib/channels/driver.ts
2396
2697
  import { homedir as homedir2 } from "os";
@@ -2713,8 +3014,8 @@ async function applyOne(options, file) {
2713
3014
  await ack(options, file, "applied");
2714
3015
  return true;
2715
3016
  }
2716
- function durableDownloadCode(status) {
2717
- return status === 413 ? "file_too_large" : "write_failed";
3017
+ function durableDownloadCode(status2) {
3018
+ return status2 === 413 ? "file_too_large" : "write_failed";
2718
3019
  }
2719
3020
  async function downloadContent(options, file, label) {
2720
3021
  try {
@@ -2747,8 +3048,8 @@ async function downloadContent(options, file, label) {
2747
3048
  return { ok: false, terminal: false };
2748
3049
  }
2749
3050
  }
2750
- async function ack(options, file, status, reason) {
2751
- const outcome = `${status}${reason ? ` (${reason})` : ""}`;
3051
+ async function ack(options, file, status2, reason) {
3052
+ const outcome = `${status2}${reason ? ` (${reason})` : ""}`;
2752
3053
  try {
2753
3054
  const res = await options.fetchImpl(
2754
3055
  `${options.apiUrl}/runners/${options.agentId}/files/${file.id}/ack`,
@@ -2758,7 +3059,7 @@ async function ack(options, file, status, reason) {
2758
3059
  Authorization: options.getAuthHeader(),
2759
3060
  "Content-Type": "application/json"
2760
3061
  },
2761
- body: JSON.stringify(reason ? { status, reason } : { status })
3062
+ body: JSON.stringify(reason ? { status: status2, reason } : { status: status2 })
2762
3063
  }
2763
3064
  );
2764
3065
  if (!res.ok) {
@@ -2830,10 +3131,10 @@ var ChannelAuthError = class extends Error {
2830
3131
  };
2831
3132
  var ChannelTerminalError = class extends Error {
2832
3133
  status;
2833
- constructor(message, status) {
3134
+ constructor(message, status2) {
2834
3135
  super(message);
2835
3136
  this.name = "ChannelTerminalError";
2836
- this.status = status;
3137
+ this.status = status2;
2837
3138
  }
2838
3139
  };
2839
3140
  function backoffDelay(attempt, policy) {
@@ -2841,8 +3142,8 @@ function backoffDelay(attempt, policy) {
2841
3142
  const capped = Math.min(policy.maxDelayMs, exp);
2842
3143
  return Math.floor(Math.random() * capped);
2843
3144
  }
2844
- function isRetryableStatus(status) {
2845
- return status === 429 || status >= 500 && status <= 599;
3145
+ function isRetryableStatus(status2) {
3146
+ return status2 === 429 || status2 >= 500 && status2 <= 599;
2846
3147
  }
2847
3148
  var ChannelDriver = class _ChannelDriver {
2848
3149
  agentId;
@@ -3045,23 +3346,23 @@ var ChannelDriver = class _ChannelDriver {
3045
3346
  * and stops opencode.
3046
3347
  */
3047
3348
  stopped = false;
3048
- constructor(config2) {
3049
- this.agentId = config2.agentId;
3050
- this.port = config2.port;
3051
- this.apiUrl = config2.apiUrl.replace(/\/$/, "");
3052
- this.getAuthHeader = config2.getAuthHeader;
3053
- this.conversationFilter = config2.conversationFilter ?? null;
3054
- this.retry = { ...DEFAULT_RETRY_POLICY, ...config2.retry };
3055
- this.log = config2.log ?? (() => {
3349
+ constructor(config) {
3350
+ this.agentId = config.agentId;
3351
+ this.port = config.port;
3352
+ this.apiUrl = config.apiUrl.replace(/\/$/, "");
3353
+ this.getAuthHeader = config.getAuthHeader;
3354
+ this.conversationFilter = config.conversationFilter ?? null;
3355
+ this.retry = { ...DEFAULT_RETRY_POLICY, ...config.retry };
3356
+ this.log = config.log ?? (() => {
3056
3357
  });
3057
- this.fetchImpl = config2.fetchImpl ?? fetch;
3058
- this.sleep = config2.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
3059
- this.pausedPollIntervalMs = config2.pausedPollIntervalMs ?? DEFAULT_PAUSED_POLL_INTERVAL_MS;
3060
- this.pausedMaxWaitMs = config2.pausedMaxWaitMs ?? DEFAULT_PAUSED_MAX_WAIT_MS;
3061
- this.stuckQueuedMs = config2.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
3062
- this.now = config2.now ?? (() => Date.now());
3063
- this.fileSyncDirectories = config2.fileSyncDirectories ?? [];
3064
- this.homeDir = config2.homeDir ?? homedir2();
3358
+ this.fetchImpl = config.fetchImpl ?? fetch;
3359
+ this.sleep = config.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
3360
+ this.pausedPollIntervalMs = config.pausedPollIntervalMs ?? DEFAULT_PAUSED_POLL_INTERVAL_MS;
3361
+ this.pausedMaxWaitMs = config.pausedMaxWaitMs ?? DEFAULT_PAUSED_MAX_WAIT_MS;
3362
+ this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
3363
+ this.now = config.now ?? (() => Date.now());
3364
+ this.fileSyncDirectories = config.fileSyncDirectories ?? [];
3365
+ this.homeDir = config.homeDir ?? homedir2();
3065
3366
  }
3066
3367
  /** The IPv4-loopback base URL for the local `opencode serve`. */
3067
3368
  get opencodeBase() {
@@ -5164,11 +5465,11 @@ var ChannelDriver = class _ChannelDriver {
5164
5465
  * `ensureSession` guard, not this suppression, is what makes the self-heal
5165
5466
  * stick.
5166
5467
  */
5167
- sessionIdBody(sessionId, conversationId, messageId, status) {
5468
+ sessionIdBody(sessionId, conversationId, messageId, status2) {
5168
5469
  if (!this.isSuperseded(conversationId, sessionId)) return { opencode_session_id: sessionId };
5169
5470
  this.log({
5170
5471
  level: "debug",
5171
- message: `Omitting the abandoned OpenCode session ${sessionId.slice(0, 8)} from the '${status}' update for message ${messageId.slice(0, 8)} so it is not re-bound to conversation ${conversationId.slice(0, 8)}`,
5472
+ message: `Omitting the abandoned OpenCode session ${sessionId.slice(0, 8)} from the '${status2}' update for message ${messageId.slice(0, 8)} so it is not re-bound to conversation ${conversationId.slice(0, 8)}`,
5172
5473
  conversation_id: conversationId,
5173
5474
  message_id: messageId
5174
5475
  });
@@ -5614,171 +5915,6 @@ Port ${port} is already in use.`));
5614
5915
  return { port, process: null, version: null, notReadyReason: "you chose to continue without it" };
5615
5916
  }
5616
5917
 
5617
- // src/commands/agent-lookup.ts
5618
- async function readErrorMessage(response) {
5619
- const text = await response.text().catch(() => "");
5620
- if (!text) return response.statusText || void 0;
5621
- try {
5622
- const data = JSON.parse(text);
5623
- const message = data.message ?? data.error;
5624
- if (typeof message === "string" && message.trim()) {
5625
- return message;
5626
- }
5627
- } catch {
5628
- }
5629
- return text.trim() || response.statusText || void 0;
5630
- }
5631
- function authFailureHint(apiUrl, serverMessage) {
5632
- const reason = serverMessage ? `: ${serverMessage}` : "";
5633
- return `Authentication failed${reason}. Your credentials were rejected by ${apiUrl}. This usually means you logged in against a different environment, or your session expired \u2014 log in again pointing at this endpoint and retry.`;
5634
- }
5635
- async function resolveAgentIdFromKey(authHeader) {
5636
- const apiUrl = getApiUrlConfig();
5637
- try {
5638
- const response = await fetch(`${apiUrl}/me`, {
5639
- headers: { Authorization: authHeader }
5640
- });
5641
- if (response.status === 401) {
5642
- const serverMessage = await readErrorMessage(response);
5643
- return { error: authFailureHint(apiUrl, serverMessage), authFailed: true };
5644
- }
5645
- if (!response.ok) {
5646
- const serverMessage = await readErrorMessage(response);
5647
- return {
5648
- error: `Failed to resolve runner from key (HTTP ${response.status})${serverMessage ? `: ${serverMessage}` : ""}`
5649
- };
5650
- }
5651
- const data = await response.json();
5652
- if (data.auth_type === "agent_key" && data.agent_id) {
5653
- return { agent_id: data.agent_id };
5654
- }
5655
- return {
5656
- error: "Cannot resolve runner ID: auth type is not agent_key. Please provide --runner explicitly."
5657
- };
5658
- } catch (error2) {
5659
- const message = error2 instanceof Error ? error2.message : "Unknown error";
5660
- return { error: `Failed to resolve runner from key: ${message}` };
5661
- }
5662
- }
5663
- var BEST_EFFORT_NOTIFY_TIMEOUT_MS = 2e3;
5664
- async function notifyAgentDisconnected(agentId, authHeader) {
5665
- const apiUrl = getApiUrlConfig();
5666
- try {
5667
- const response = await fetch(`${apiUrl}/runners/${agentId}/disconnect`, {
5668
- method: "POST",
5669
- headers: { Authorization: authHeader },
5670
- signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
5671
- });
5672
- if (!response.ok) {
5673
- const serverMessage = await readErrorMessage(response);
5674
- return {
5675
- ok: false,
5676
- error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
5677
- };
5678
- }
5679
- return { ok: true };
5680
- } catch (error2) {
5681
- return { ok: false, error: describeBestEffortError(error2) };
5682
- }
5683
- }
5684
- function describeBestEffortError(error2) {
5685
- const name = error2?.name;
5686
- if (name === "TimeoutError" || name === "AbortError") {
5687
- return `timed out after ${BEST_EFFORT_NOTIFY_TIMEOUT_MS}ms`;
5688
- }
5689
- return error2 instanceof Error ? error2.message : String(error2);
5690
- }
5691
- async function reportMicrovmId(agentId, authHeader, microvmId) {
5692
- try {
5693
- const apiUrl = getApiUrlConfig();
5694
- const response = await fetch(`${apiUrl}/runners/${agentId}/microvm`, {
5695
- method: "POST",
5696
- headers: { Authorization: authHeader, "Content-Type": "application/json" },
5697
- body: JSON.stringify({ microvm_id: microvmId }),
5698
- signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
5699
- });
5700
- if (!response.ok) {
5701
- const serverMessage = await readErrorMessage(response);
5702
- return {
5703
- ok: false,
5704
- error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
5705
- };
5706
- }
5707
- return { ok: true };
5708
- } catch (error2) {
5709
- return { ok: false, error: describeBestEffortError(error2) };
5710
- }
5711
- }
5712
- function toReportedWindow(window) {
5713
- if (!window) return null;
5714
- return { utilization: window.utilization, resets_at: window.resetsAt };
5715
- }
5716
- async function reportClaudeUsage(agentId, authHeader, snapshot) {
5717
- try {
5718
- const apiUrl = getApiUrlConfig();
5719
- const response = await fetch(`${apiUrl}/runners/${agentId}/claude-usage`, {
5720
- method: "POST",
5721
- headers: { Authorization: authHeader, "Content-Type": "application/json" },
5722
- body: JSON.stringify({
5723
- five_hour: toReportedWindow(snapshot.fiveHour),
5724
- seven_day: toReportedWindow(snapshot.sevenDay)
5725
- }),
5726
- signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
5727
- });
5728
- if (!response.ok) {
5729
- const serverMessage = await readErrorMessage(response);
5730
- return {
5731
- ok: false,
5732
- error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
5733
- };
5734
- }
5735
- return { ok: true };
5736
- } catch (error2) {
5737
- return { ok: false, error: describeBestEffortError(error2) };
5738
- }
5739
- }
5740
- async function getAgentInfo(agentId, authHeader) {
5741
- const apiUrl = getApiUrlConfig();
5742
- try {
5743
- const response = await fetch(`${apiUrl}/runners/${agentId}`, {
5744
- headers: { Authorization: authHeader }
5745
- });
5746
- if (response.status === 401) {
5747
- const serverMessage = await readErrorMessage(response);
5748
- return { valid: false, error: authFailureHint(apiUrl, serverMessage), authFailed: true };
5749
- }
5750
- if (response.status === 403) {
5751
- const serverMessage = await readErrorMessage(response);
5752
- return {
5753
- valid: false,
5754
- error: serverMessage ?? "You do not have access to this runner (it may belong to a different team or organization)."
5755
- };
5756
- }
5757
- if (response.status === 404) {
5758
- const serverMessage = await readErrorMessage(response);
5759
- return { valid: false, error: serverMessage ?? `Runner ${agentId} not found` };
5760
- }
5761
- if (!response.ok) {
5762
- const serverMessage = await readErrorMessage(response);
5763
- return {
5764
- valid: false,
5765
- error: `API error (HTTP ${response.status})${serverMessage ? `: ${serverMessage}` : ""}`
5766
- };
5767
- }
5768
- const agent = await response.json();
5769
- if (agent.agent_type !== "local") {
5770
- return {
5771
- valid: false,
5772
- error: `Runner is type '${agent.agent_type}', must be 'local' for CLI connection`
5773
- };
5774
- }
5775
- return { valid: true, agent };
5776
- } catch (error2) {
5777
- const message = error2 instanceof Error ? error2.message : "Unknown error";
5778
- return { valid: false, error: `Failed to validate runner: ${message}` };
5779
- }
5780
- }
5781
-
5782
5918
  // src/commands/run.ts
5783
5919
  var MAX_ACTIVITY_LOG_ENTRIES = 10;
5784
5920
  var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
@@ -5989,6 +6125,7 @@ async function handleAuthError(state, error2) {
5989
6125
  }
5990
6126
  async function driveChannels(state, driver) {
5991
6127
  let idlePolls = 0;
6128
+ let consecutiveDrainFailures = 0;
5992
6129
  let lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
5993
6130
  let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
5994
6131
  while (state.running) {
@@ -6006,12 +6143,15 @@ async function driveChannels(state, driver) {
6006
6143
  );
6007
6144
  try {
6008
6145
  const processed = await driver.drainPending();
6146
+ consecutiveDrainFailures = 0;
6009
6147
  state.messageCount += processed;
6010
6148
  const proxiedActivity = state.lastProxiedActivityAt !== lastSeenProxiedActivityAt;
6011
6149
  lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
6012
6150
  const appliedFiles = driver.fileSyncActivity().appliedFiles;
6013
- const fileActivity = carriedOverFileSync || appliedFiles !== lastSeenAppliedFiles;
6151
+ const filesApplied = appliedFiles !== lastSeenAppliedFiles;
6152
+ const fileActivity = carriedOverFileSync || filesApplied;
6014
6153
  lastSeenAppliedFiles = appliedFiles;
6154
+ if (filesApplied) state.claudeUsageRearm?.();
6015
6155
  if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
6016
6156
  idlePolls = 0;
6017
6157
  if (processed > 0 && state.interactive) displayStatus(state);
@@ -6040,8 +6180,32 @@ async function driveChannels(state, driver) {
6040
6180
  const errorMessage = error2 instanceof Error ? error2.message : String(error2);
6041
6181
  logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage}` });
6042
6182
  if (state.interactive) displayStatus(state);
6183
+ if (driver.hasInFlightWatchers()) {
6184
+ consecutiveDrainFailures = 0;
6185
+ } else if (state.idleTimeout !== null) {
6186
+ consecutiveDrainFailures++;
6187
+ if (consecutiveDrainFailures === 1) {
6188
+ logActivity(state, {
6189
+ type: "info",
6190
+ message: `Cannot reach Evident, will exit if this persists past the idle timeout (timeout: ${state.idleTimeout}s)...`
6191
+ });
6192
+ if (state.interactive) displayStatus(state);
6193
+ }
6194
+ }
6043
6195
  }
6044
6196
  await new Promise((resolve3) => setTimeout(resolve3, CHANNEL_POLL_INTERVAL_MS));
6197
+ if (state.idleTimeout !== null && consecutiveDrainFailures >= 2) {
6198
+ const unreachableMs = consecutiveDrainFailures * CHANNEL_POLL_INTERVAL_MS;
6199
+ if (unreachableMs > state.idleTimeout * 1e3) {
6200
+ logActivity(state, {
6201
+ type: "info",
6202
+ level: "warn",
6203
+ message: `Exiting: could not reach Evident for ${consecutiveDrainFailures} consecutive polls (${Math.round(unreachableMs / 1e3)}s)`
6204
+ });
6205
+ if (state.interactive) displayStatus(state);
6206
+ break;
6207
+ }
6208
+ }
6045
6209
  if (state.idleTimeout !== null && idlePolls >= 2) {
6046
6210
  const idleMs = idlePolls * CHANNEL_POLL_INTERVAL_MS;
6047
6211
  if (idleMs > state.idleTimeout * 1e3) {
@@ -6053,8 +6217,8 @@ async function driveChannels(state, driver) {
6053
6217
  }
6054
6218
  }
6055
6219
  var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
6056
- async function runSweep(state, driver, config2) {
6057
- const mode = `age=${config2.maxAgeMs ?? "\u2014"} count=${config2.maxCount ?? "\u2014"}`;
6220
+ async function runSweep(state, driver, config) {
6221
+ const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
6058
6222
  try {
6059
6223
  const sessions = await listSessions(state.port);
6060
6224
  if (sessions === null) {
@@ -6067,8 +6231,8 @@ async function runSweep(state, driver, config2) {
6067
6231
  const toDelete = selectSessionsToDelete(
6068
6232
  sessions.map((s) => ({ id: s.id, lastActivityMs: sessionLastActivityMs(s) })),
6069
6233
  {
6070
- maxAgeMs: config2.maxAgeMs,
6071
- maxCount: config2.maxCount,
6234
+ maxAgeMs: config.maxAgeMs,
6235
+ maxCount: config.maxCount,
6072
6236
  nowMs: Date.now(),
6073
6237
  protectedIds: driver.protectedSessionIds()
6074
6238
  }
@@ -6104,7 +6268,7 @@ async function runSweep(state, driver, config2) {
6104
6268
  }
6105
6269
  }
6106
6270
  function scheduleSessionCleanup(state, driver, options) {
6107
- const config2 = resolveSessionCleanupConfig(
6271
+ const config = resolveSessionCleanupConfig(
6108
6272
  {
6109
6273
  maxAge: options.sessionCleanupMaxAge,
6110
6274
  maxCount: options.sessionCleanupMaxCount,
@@ -6112,21 +6276,24 @@ function scheduleSessionCleanup(state, driver, options) {
6112
6276
  },
6113
6277
  process.env
6114
6278
  );
6115
- for (const warning2 of config2.warnings) {
6279
+ for (const warning2 of config.warnings) {
6116
6280
  logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
6117
6281
  }
6118
- if (!config2.enabled) return;
6282
+ if (!config.enabled) return;
6119
6283
  logActivity(state, {
6120
6284
  type: "info",
6121
- message: `Session cleanup enabled (age=${config2.maxAgeMs ?? "\u2014"}, count=${config2.maxCount ?? "\u2014"}, interval=${config2.intervalMs}ms)`
6285
+ message: `Session cleanup enabled (age=${config.maxAgeMs ?? "\u2014"}, count=${config.maxCount ?? "\u2014"}, interval=${config.intervalMs}ms)`
6122
6286
  });
6123
- const interval = setInterval(() => void runSweep(state, driver, config2), config2.intervalMs);
6287
+ const interval = setInterval(() => void runSweep(state, driver, config), config.intervalMs);
6124
6288
  const firstSweep = setTimeout(
6125
- () => void runSweep(state, driver, config2),
6289
+ () => void runSweep(state, driver, config),
6126
6290
  SESSION_CLEANUP_FIRST_SWEEP_MS
6127
6291
  );
6128
6292
  state.sessionCleanupTimers.push(interval, firstSweep);
6129
6293
  }
6294
+ function claudeUsageFailureStreakSuffix(consecutiveFailures) {
6295
+ return consecutiveFailures > 1 ? ` (${consecutiveFailures} consecutive failures)` : "";
6296
+ }
6130
6297
  function scheduleClaudeUsageReporting(state, options) {
6131
6298
  const { mode, warnings } = resolveClaudeUsageReportingMode(
6132
6299
  options.claudeUsageReporting,
@@ -6145,13 +6312,26 @@ function scheduleClaudeUsageReporting(state, options) {
6145
6312
  level: "debug",
6146
6313
  message: "Claude usage reporting is off (--claude-usage-reporting off)"
6147
6314
  });
6148
- return;
6315
+ return null;
6149
6316
  }
6150
6317
  let consecutiveFailures = 0;
6318
+ let armed = false;
6319
+ let rearmRequested = false;
6151
6320
  const scheduleNextTick = () => {
6321
+ armed = true;
6322
+ rearmRequested = false;
6152
6323
  state.claudeUsageTimer = setTimeout(() => void tick(false), nextReportDelayMs());
6153
6324
  };
6154
- const tick = async (isFirst) => {
6325
+ const rearm = () => {
6326
+ if (armed) {
6327
+ rearmRequested = true;
6328
+ return;
6329
+ }
6330
+ rearmRequested = false;
6331
+ armed = true;
6332
+ state.claudeUsageTimer = setTimeout(() => void tick(true), FIRST_REPORT_DELAY_MS);
6333
+ };
6334
+ const tick = async (isProbe) => {
6155
6335
  try {
6156
6336
  const usage = await getClaudeUsage();
6157
6337
  const result = await reportClaudeUsage(state.agentId, state.authHeader, usage);
@@ -6173,8 +6353,8 @@ function scheduleClaudeUsageReporting(state, options) {
6173
6353
  consecutiveFailures++;
6174
6354
  logActivity(state, {
6175
6355
  type: "info",
6176
- level: consecutiveFailures === 1 ? "warn" : "debug",
6177
- message: `Failed to report Claude usage: ${result.error}`
6356
+ level: claudeUsageFailureLogLevel(consecutiveFailures),
6357
+ message: `Failed to report Claude usage: ${result.error}${claudeUsageFailureStreakSuffix(consecutiveFailures)}`
6178
6358
  });
6179
6359
  }
6180
6360
  scheduleNextTick();
@@ -6187,12 +6367,14 @@ function scheduleClaudeUsageReporting(state, options) {
6187
6367
  message: "Claude usage reporting is forced on but no usable Claude Code login was found \u2014 run `claude` to sign in; reporting will keep retrying"
6188
6368
  });
6189
6369
  scheduleNextTick();
6190
- } else if (isFirst) {
6370
+ } else if (isProbe) {
6191
6371
  logActivity(state, {
6192
6372
  type: "info",
6193
6373
  level: "debug",
6194
6374
  message: `Claude usage reporting: ${error2.message}`
6195
6375
  });
6376
+ armed = false;
6377
+ if (rearmRequested) rearm();
6196
6378
  } else {
6197
6379
  logActivity(state, {
6198
6380
  type: "info",
@@ -6206,14 +6388,16 @@ function scheduleClaudeUsageReporting(state, options) {
6206
6388
  const message = error2 instanceof Error ? error2.message : String(error2);
6207
6389
  logActivity(state, {
6208
6390
  type: "info",
6209
- level: consecutiveFailures === 1 ? "warn" : "debug",
6210
- message: `Claude usage reporting failed: ${message}`
6391
+ level: claudeUsageFailureLogLevel(consecutiveFailures),
6392
+ message: `Claude usage reporting failed: ${message}${claudeUsageFailureStreakSuffix(consecutiveFailures)}`
6211
6393
  });
6212
6394
  scheduleNextTick();
6213
6395
  }
6214
6396
  }
6215
6397
  };
6398
+ armed = true;
6216
6399
  state.claudeUsageTimer = setTimeout(() => void tick(true), FIRST_REPORT_DELAY_MS);
6400
+ return rearm;
6217
6401
  }
6218
6402
  async function notifyOffline(state) {
6219
6403
  if (!state.agentId || !state.authHeader) return;
@@ -6254,6 +6438,7 @@ async function cleanup(state, opts = {}) {
6254
6438
  clearTimeout(state.claudeUsageTimer);
6255
6439
  state.claudeUsageTimer = null;
6256
6440
  }
6441
+ state.claudeUsageRearm = null;
6257
6442
  if (opts.graceful && state.channelDriver) {
6258
6443
  state.channelDriver.stop();
6259
6444
  log2(state, "Draining in-flight channel work before shutdown...");
@@ -6335,6 +6520,7 @@ async function run(options) {
6335
6520
  lastProxiedActivityAt: null,
6336
6521
  sessionCleanupTimers: [],
6337
6522
  claudeUsageTimer: null,
6523
+ claudeUsageRearm: null,
6338
6524
  authHeader: ""
6339
6525
  };
6340
6526
  setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
@@ -6414,6 +6600,7 @@ async function run(options) {
6414
6600
  console.log(chalk6.dim("Or run `evident login` for interactive authentication"));
6415
6601
  blank();
6416
6602
  process.exit(1);
6603
+ return;
6417
6604
  }
6418
6605
  blank();
6419
6606
  console.log(chalk6.yellow("You are not logged in to Evident."));
@@ -6458,6 +6645,7 @@ async function run(options) {
6458
6645
  } else {
6459
6646
  printError(resolved.error || "Failed to resolve runner ID from key");
6460
6647
  process.exit(1);
6648
+ return;
6461
6649
  }
6462
6650
  } else {
6463
6651
  printError(
@@ -6471,6 +6659,7 @@ async function run(options) {
6471
6659
  );
6472
6660
  blank();
6473
6661
  process.exit(1);
6662
+ return;
6474
6663
  }
6475
6664
  }
6476
6665
  telemetry.info(
@@ -6718,7 +6907,7 @@ async function run(options) {
6718
6907
  throw error2;
6719
6908
  }
6720
6909
  scheduleSessionCleanup(state, channelDriver, options);
6721
- scheduleClaudeUsageReporting(state, options);
6910
+ state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
6722
6911
  if (!interactive || state.json) {
6723
6912
  log2(state, "Driving channel messages...");
6724
6913
  }
@@ -6773,6 +6962,7 @@ program.name("evident").description("Run OpenCode locally and connect it to Evid
6773
6962
  program.command("login").description("Authenticate with Evident").option("--token", "Use token-based authentication (for CI/CD)").option("--no-browser", "Do not open the browser automatically").action(login);
6774
6963
  program.command("logout").description("Remove stored credentials for the current endpoint").option("--all", "Remove stored credentials for all endpoints").action((options) => logout({ all: options.all }));
6775
6964
  program.command("whoami").description("Show the currently logged in user").action(whoami);
6965
+ program.command("status").description("Check whether the configured credentials can reach Evident").option("--json", "Output in JSON format").action((options) => status({ json: options.json }));
6776
6966
  program.command("claude-usage").description("[spike] Show Claude subscription usage (requires a local `claude login`)").action(claudeUsage);
6777
6967
  program.command("run").description("Connect to Evident and process messages").option("--runner [id]", "Runner ID to connect to (optional when EVIDENT_RUNNER_KEY is set)").option(
6778
6968
  "-a, --agent [id]",