@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 +450 -260
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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(
|
|
2717
|
-
return
|
|
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,
|
|
2751
|
-
const outcome = `${
|
|
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,
|
|
3134
|
+
constructor(message, status2) {
|
|
2834
3135
|
super(message);
|
|
2835
3136
|
this.name = "ChannelTerminalError";
|
|
2836
|
-
this.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(
|
|
2845
|
-
return
|
|
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(
|
|
3049
|
-
this.agentId =
|
|
3050
|
-
this.port =
|
|
3051
|
-
this.apiUrl =
|
|
3052
|
-
this.getAuthHeader =
|
|
3053
|
-
this.conversationFilter =
|
|
3054
|
-
this.retry = { ...DEFAULT_RETRY_POLICY, ...
|
|
3055
|
-
this.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 =
|
|
3058
|
-
this.sleep =
|
|
3059
|
-
this.pausedPollIntervalMs =
|
|
3060
|
-
this.pausedMaxWaitMs =
|
|
3061
|
-
this.stuckQueuedMs =
|
|
3062
|
-
this.now =
|
|
3063
|
-
this.fileSyncDirectories =
|
|
3064
|
-
this.homeDir =
|
|
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,
|
|
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 '${
|
|
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
|
|
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,
|
|
6057
|
-
const mode = `age=${
|
|
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:
|
|
6071
|
-
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
|
|
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
|
|
6279
|
+
for (const warning2 of config.warnings) {
|
|
6116
6280
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
6117
6281
|
}
|
|
6118
|
-
if (!
|
|
6282
|
+
if (!config.enabled) return;
|
|
6119
6283
|
logActivity(state, {
|
|
6120
6284
|
type: "info",
|
|
6121
|
-
message: `Session cleanup enabled (age=${
|
|
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,
|
|
6287
|
+
const interval = setInterval(() => void runSweep(state, driver, config), config.intervalMs);
|
|
6124
6288
|
const firstSweep = setTimeout(
|
|
6125
|
-
() => void runSweep(state, driver,
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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]",
|