@evident-ai/cli 3.1.1-dev.9e90301 → 3.1.1-dev.9efabd5
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/README.md +32 -0
- package/dist/index.js +464 -267
- 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,9 +6125,15 @@ async function handleAuthError(state, error2) {
|
|
|
5989
6125
|
}
|
|
5990
6126
|
async function driveChannels(state, driver) {
|
|
5991
6127
|
let idlePolls = 0;
|
|
6128
|
+
let idleMs = 0;
|
|
6129
|
+
let consecutiveDrainFailures = 0;
|
|
6130
|
+
let unreachableMs = 0;
|
|
5992
6131
|
let lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
5993
6132
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
5994
6133
|
while (state.running) {
|
|
6134
|
+
const cycleStartedAtMs = performance.now();
|
|
6135
|
+
let idleThisCycle = false;
|
|
6136
|
+
let unreachableThisCycle = false;
|
|
5995
6137
|
if (state.connection?.reconnecting && state.connection.reconnectPromise) {
|
|
5996
6138
|
logActivity(state, { type: "info", message: "Waiting for tunnel reconnection..." });
|
|
5997
6139
|
if (state.interactive) displayStatus(state);
|
|
@@ -6006,17 +6148,23 @@ async function driveChannels(state, driver) {
|
|
|
6006
6148
|
);
|
|
6007
6149
|
try {
|
|
6008
6150
|
const processed = await driver.drainPending();
|
|
6151
|
+
consecutiveDrainFailures = 0;
|
|
6152
|
+
unreachableMs = 0;
|
|
6009
6153
|
state.messageCount += processed;
|
|
6010
6154
|
const proxiedActivity = state.lastProxiedActivityAt !== lastSeenProxiedActivityAt;
|
|
6011
6155
|
lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
6012
6156
|
const appliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
6013
|
-
const
|
|
6157
|
+
const filesApplied = appliedFiles !== lastSeenAppliedFiles;
|
|
6158
|
+
const fileActivity = carriedOverFileSync || filesApplied;
|
|
6014
6159
|
lastSeenAppliedFiles = appliedFiles;
|
|
6160
|
+
if (filesApplied) state.claudeUsageRearm?.();
|
|
6015
6161
|
if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
|
|
6016
6162
|
idlePolls = 0;
|
|
6163
|
+
idleMs = 0;
|
|
6017
6164
|
if (processed > 0 && state.interactive) displayStatus(state);
|
|
6018
6165
|
} else if (state.idleTimeout !== null) {
|
|
6019
6166
|
idlePolls++;
|
|
6167
|
+
idleThisCycle = true;
|
|
6020
6168
|
if (idlePolls === 1) {
|
|
6021
6169
|
logActivity(state, {
|
|
6022
6170
|
type: "info",
|
|
@@ -6040,21 +6188,44 @@ async function driveChannels(state, driver) {
|
|
|
6040
6188
|
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
6041
6189
|
logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage}` });
|
|
6042
6190
|
if (state.interactive) displayStatus(state);
|
|
6191
|
+
if (driver.hasInFlightWatchers()) {
|
|
6192
|
+
consecutiveDrainFailures = 0;
|
|
6193
|
+
unreachableMs = 0;
|
|
6194
|
+
} else if (state.idleTimeout !== null) {
|
|
6195
|
+
consecutiveDrainFailures++;
|
|
6196
|
+
unreachableThisCycle = true;
|
|
6197
|
+
if (consecutiveDrainFailures === 1) {
|
|
6198
|
+
logActivity(state, {
|
|
6199
|
+
type: "info",
|
|
6200
|
+
message: `Cannot reach Evident, will exit if this persists past the idle timeout (timeout: ${state.idleTimeout}s)...`
|
|
6201
|
+
});
|
|
6202
|
+
if (state.interactive) displayStatus(state);
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6043
6205
|
}
|
|
6044
6206
|
await new Promise((resolve3) => setTimeout(resolve3, CHANNEL_POLL_INTERVAL_MS));
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6207
|
+
const cycleMs = performance.now() - cycleStartedAtMs;
|
|
6208
|
+
if (idleThisCycle) idleMs += cycleMs;
|
|
6209
|
+
if (unreachableThisCycle) unreachableMs += cycleMs;
|
|
6210
|
+
if (state.idleTimeout !== null && consecutiveDrainFailures >= 2 && unreachableMs > state.idleTimeout * 1e3) {
|
|
6211
|
+
logActivity(state, {
|
|
6212
|
+
type: "info",
|
|
6213
|
+
level: "warn",
|
|
6214
|
+
message: `Exiting: could not reach Evident for ${consecutiveDrainFailures} consecutive polls (${Math.round(unreachableMs / 1e3)}s)`
|
|
6215
|
+
});
|
|
6216
|
+
if (state.interactive) displayStatus(state);
|
|
6217
|
+
break;
|
|
6218
|
+
}
|
|
6219
|
+
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3) {
|
|
6220
|
+
logActivity(state, { type: "info", message: "Idle timeout reached" });
|
|
6221
|
+
if (state.interactive) displayStatus(state);
|
|
6222
|
+
break;
|
|
6052
6223
|
}
|
|
6053
6224
|
}
|
|
6054
6225
|
}
|
|
6055
6226
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
6056
|
-
async function runSweep(state, driver,
|
|
6057
|
-
const mode = `age=${
|
|
6227
|
+
async function runSweep(state, driver, config) {
|
|
6228
|
+
const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
|
|
6058
6229
|
try {
|
|
6059
6230
|
const sessions = await listSessions(state.port);
|
|
6060
6231
|
if (sessions === null) {
|
|
@@ -6067,8 +6238,8 @@ async function runSweep(state, driver, config2) {
|
|
|
6067
6238
|
const toDelete = selectSessionsToDelete(
|
|
6068
6239
|
sessions.map((s) => ({ id: s.id, lastActivityMs: sessionLastActivityMs(s) })),
|
|
6069
6240
|
{
|
|
6070
|
-
maxAgeMs:
|
|
6071
|
-
maxCount:
|
|
6241
|
+
maxAgeMs: config.maxAgeMs,
|
|
6242
|
+
maxCount: config.maxCount,
|
|
6072
6243
|
nowMs: Date.now(),
|
|
6073
6244
|
protectedIds: driver.protectedSessionIds()
|
|
6074
6245
|
}
|
|
@@ -6104,7 +6275,7 @@ async function runSweep(state, driver, config2) {
|
|
|
6104
6275
|
}
|
|
6105
6276
|
}
|
|
6106
6277
|
function scheduleSessionCleanup(state, driver, options) {
|
|
6107
|
-
const
|
|
6278
|
+
const config = resolveSessionCleanupConfig(
|
|
6108
6279
|
{
|
|
6109
6280
|
maxAge: options.sessionCleanupMaxAge,
|
|
6110
6281
|
maxCount: options.sessionCleanupMaxCount,
|
|
@@ -6112,21 +6283,24 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
6112
6283
|
},
|
|
6113
6284
|
process.env
|
|
6114
6285
|
);
|
|
6115
|
-
for (const warning2 of
|
|
6286
|
+
for (const warning2 of config.warnings) {
|
|
6116
6287
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
6117
6288
|
}
|
|
6118
|
-
if (!
|
|
6289
|
+
if (!config.enabled) return;
|
|
6119
6290
|
logActivity(state, {
|
|
6120
6291
|
type: "info",
|
|
6121
|
-
message: `Session cleanup enabled (age=${
|
|
6292
|
+
message: `Session cleanup enabled (age=${config.maxAgeMs ?? "\u2014"}, count=${config.maxCount ?? "\u2014"}, interval=${config.intervalMs}ms)`
|
|
6122
6293
|
});
|
|
6123
|
-
const interval = setInterval(() => void runSweep(state, driver,
|
|
6294
|
+
const interval = setInterval(() => void runSweep(state, driver, config), config.intervalMs);
|
|
6124
6295
|
const firstSweep = setTimeout(
|
|
6125
|
-
() => void runSweep(state, driver,
|
|
6296
|
+
() => void runSweep(state, driver, config),
|
|
6126
6297
|
SESSION_CLEANUP_FIRST_SWEEP_MS
|
|
6127
6298
|
);
|
|
6128
6299
|
state.sessionCleanupTimers.push(interval, firstSweep);
|
|
6129
6300
|
}
|
|
6301
|
+
function claudeUsageFailureStreakSuffix(consecutiveFailures) {
|
|
6302
|
+
return consecutiveFailures > 1 ? ` (${consecutiveFailures} consecutive failures)` : "";
|
|
6303
|
+
}
|
|
6130
6304
|
function scheduleClaudeUsageReporting(state, options) {
|
|
6131
6305
|
const { mode, warnings } = resolveClaudeUsageReportingMode(
|
|
6132
6306
|
options.claudeUsageReporting,
|
|
@@ -6145,13 +6319,26 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
6145
6319
|
level: "debug",
|
|
6146
6320
|
message: "Claude usage reporting is off (--claude-usage-reporting off)"
|
|
6147
6321
|
});
|
|
6148
|
-
return;
|
|
6322
|
+
return null;
|
|
6149
6323
|
}
|
|
6150
6324
|
let consecutiveFailures = 0;
|
|
6325
|
+
let armed = false;
|
|
6326
|
+
let rearmRequested = false;
|
|
6151
6327
|
const scheduleNextTick = () => {
|
|
6328
|
+
armed = true;
|
|
6329
|
+
rearmRequested = false;
|
|
6152
6330
|
state.claudeUsageTimer = setTimeout(() => void tick(false), nextReportDelayMs());
|
|
6153
6331
|
};
|
|
6154
|
-
const
|
|
6332
|
+
const rearm = () => {
|
|
6333
|
+
if (armed) {
|
|
6334
|
+
rearmRequested = true;
|
|
6335
|
+
return;
|
|
6336
|
+
}
|
|
6337
|
+
rearmRequested = false;
|
|
6338
|
+
armed = true;
|
|
6339
|
+
state.claudeUsageTimer = setTimeout(() => void tick(true), FIRST_REPORT_DELAY_MS);
|
|
6340
|
+
};
|
|
6341
|
+
const tick = async (isProbe) => {
|
|
6155
6342
|
try {
|
|
6156
6343
|
const usage = await getClaudeUsage();
|
|
6157
6344
|
const result = await reportClaudeUsage(state.agentId, state.authHeader, usage);
|
|
@@ -6173,8 +6360,8 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
6173
6360
|
consecutiveFailures++;
|
|
6174
6361
|
logActivity(state, {
|
|
6175
6362
|
type: "info",
|
|
6176
|
-
level: consecutiveFailures
|
|
6177
|
-
message: `Failed to report Claude usage: ${result.error}`
|
|
6363
|
+
level: claudeUsageFailureLogLevel(consecutiveFailures),
|
|
6364
|
+
message: `Failed to report Claude usage: ${result.error}${claudeUsageFailureStreakSuffix(consecutiveFailures)}`
|
|
6178
6365
|
});
|
|
6179
6366
|
}
|
|
6180
6367
|
scheduleNextTick();
|
|
@@ -6187,12 +6374,14 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
6187
6374
|
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
6375
|
});
|
|
6189
6376
|
scheduleNextTick();
|
|
6190
|
-
} else if (
|
|
6377
|
+
} else if (isProbe) {
|
|
6191
6378
|
logActivity(state, {
|
|
6192
6379
|
type: "info",
|
|
6193
6380
|
level: "debug",
|
|
6194
6381
|
message: `Claude usage reporting: ${error2.message}`
|
|
6195
6382
|
});
|
|
6383
|
+
armed = false;
|
|
6384
|
+
if (rearmRequested) rearm();
|
|
6196
6385
|
} else {
|
|
6197
6386
|
logActivity(state, {
|
|
6198
6387
|
type: "info",
|
|
@@ -6206,14 +6395,16 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
6206
6395
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
6207
6396
|
logActivity(state, {
|
|
6208
6397
|
type: "info",
|
|
6209
|
-
level: consecutiveFailures
|
|
6210
|
-
message: `Claude usage reporting failed: ${message}`
|
|
6398
|
+
level: claudeUsageFailureLogLevel(consecutiveFailures),
|
|
6399
|
+
message: `Claude usage reporting failed: ${message}${claudeUsageFailureStreakSuffix(consecutiveFailures)}`
|
|
6211
6400
|
});
|
|
6212
6401
|
scheduleNextTick();
|
|
6213
6402
|
}
|
|
6214
6403
|
}
|
|
6215
6404
|
};
|
|
6405
|
+
armed = true;
|
|
6216
6406
|
state.claudeUsageTimer = setTimeout(() => void tick(true), FIRST_REPORT_DELAY_MS);
|
|
6407
|
+
return rearm;
|
|
6217
6408
|
}
|
|
6218
6409
|
async function notifyOffline(state) {
|
|
6219
6410
|
if (!state.agentId || !state.authHeader) return;
|
|
@@ -6254,6 +6445,7 @@ async function cleanup(state, opts = {}) {
|
|
|
6254
6445
|
clearTimeout(state.claudeUsageTimer);
|
|
6255
6446
|
state.claudeUsageTimer = null;
|
|
6256
6447
|
}
|
|
6448
|
+
state.claudeUsageRearm = null;
|
|
6257
6449
|
if (opts.graceful && state.channelDriver) {
|
|
6258
6450
|
state.channelDriver.stop();
|
|
6259
6451
|
log2(state, "Draining in-flight channel work before shutdown...");
|
|
@@ -6335,6 +6527,7 @@ async function run(options) {
|
|
|
6335
6527
|
lastProxiedActivityAt: null,
|
|
6336
6528
|
sessionCleanupTimers: [],
|
|
6337
6529
|
claudeUsageTimer: null,
|
|
6530
|
+
claudeUsageRearm: null,
|
|
6338
6531
|
authHeader: ""
|
|
6339
6532
|
};
|
|
6340
6533
|
setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
|
|
@@ -6414,6 +6607,7 @@ async function run(options) {
|
|
|
6414
6607
|
console.log(chalk6.dim("Or run `evident login` for interactive authentication"));
|
|
6415
6608
|
blank();
|
|
6416
6609
|
process.exit(1);
|
|
6610
|
+
return;
|
|
6417
6611
|
}
|
|
6418
6612
|
blank();
|
|
6419
6613
|
console.log(chalk6.yellow("You are not logged in to Evident."));
|
|
@@ -6458,6 +6652,7 @@ async function run(options) {
|
|
|
6458
6652
|
} else {
|
|
6459
6653
|
printError(resolved.error || "Failed to resolve runner ID from key");
|
|
6460
6654
|
process.exit(1);
|
|
6655
|
+
return;
|
|
6461
6656
|
}
|
|
6462
6657
|
} else {
|
|
6463
6658
|
printError(
|
|
@@ -6471,6 +6666,7 @@ async function run(options) {
|
|
|
6471
6666
|
);
|
|
6472
6667
|
blank();
|
|
6473
6668
|
process.exit(1);
|
|
6669
|
+
return;
|
|
6474
6670
|
}
|
|
6475
6671
|
}
|
|
6476
6672
|
telemetry.info(
|
|
@@ -6718,7 +6914,7 @@ async function run(options) {
|
|
|
6718
6914
|
throw error2;
|
|
6719
6915
|
}
|
|
6720
6916
|
scheduleSessionCleanup(state, channelDriver, options);
|
|
6721
|
-
scheduleClaudeUsageReporting(state, options);
|
|
6917
|
+
state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
|
|
6722
6918
|
if (!interactive || state.json) {
|
|
6723
6919
|
log2(state, "Driving channel messages...");
|
|
6724
6920
|
}
|
|
@@ -6773,6 +6969,7 @@ program.name("evident").description("Run OpenCode locally and connect it to Evid
|
|
|
6773
6969
|
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
6970
|
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
6971
|
program.command("whoami").description("Show the currently logged in user").action(whoami);
|
|
6972
|
+
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
6973
|
program.command("claude-usage").description("[spike] Show Claude subscription usage (requires a local `claude login`)").action(claudeUsage);
|
|
6777
6974
|
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
6975
|
"-a, --agent [id]",
|