@evident-ai/cli 3.4.1-dev.f9c90e1 → 3.4.1-dev.fb92ede
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 +14 -0
- package/dist/index.js +1292 -360
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,8 +11,8 @@ import chalk2 from "chalk";
|
|
|
11
11
|
|
|
12
12
|
// src/lib/config.ts
|
|
13
13
|
import Conf from "conf";
|
|
14
|
-
import { chmodSync, existsSync, statSync } from "fs";
|
|
15
|
-
import { dirname } from "path";
|
|
14
|
+
import { chmodSync, existsSync, statSync } from "node:fs";
|
|
15
|
+
import { dirname } from "node:path";
|
|
16
16
|
var PRODUCTION_API_URL = "https://api.production.evident.run/v1";
|
|
17
17
|
var PRODUCTION_TUNNEL_URL = "wss://tunnel.production.evident.run";
|
|
18
18
|
var defaults = {
|
|
@@ -623,6 +623,19 @@ function isInteractive(jsonOutput) {
|
|
|
623
623
|
return true;
|
|
624
624
|
}
|
|
625
625
|
|
|
626
|
+
// src/lib/subscription-usage-report.ts
|
|
627
|
+
function toReportedSubscription(collected) {
|
|
628
|
+
if (!collected) return null;
|
|
629
|
+
if (collected.ownerEmail === null && collected.planType === null && collected.organizationName === null) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
owner_email: collected.ownerEmail,
|
|
634
|
+
plan_type: collected.planType,
|
|
635
|
+
organization_name: collected.organizationName
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
626
639
|
// src/commands/agent-lookup.ts
|
|
627
640
|
async function readErrorMessage(response) {
|
|
628
641
|
const text = await response.text().catch(() => "");
|
|
@@ -670,12 +683,15 @@ async function resolveAgentIdFromKey(authHeader) {
|
|
|
670
683
|
}
|
|
671
684
|
}
|
|
672
685
|
var BEST_EFFORT_NOTIFY_TIMEOUT_MS = 2e3;
|
|
673
|
-
async function
|
|
674
|
-
const apiUrl = getApiUrlConfig();
|
|
686
|
+
async function postBestEffort(path, authHeader, body) {
|
|
675
687
|
try {
|
|
676
|
-
const
|
|
688
|
+
const apiUrl = getApiUrlConfig();
|
|
689
|
+
const headers = { Authorization: authHeader };
|
|
690
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
691
|
+
const response = await fetch(`${apiUrl}${path}`, {
|
|
677
692
|
method: "POST",
|
|
678
|
-
headers
|
|
693
|
+
headers,
|
|
694
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
679
695
|
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
680
696
|
});
|
|
681
697
|
if (!response.ok) {
|
|
@@ -687,73 +703,32 @@ async function notifyAgentDisconnected(agentId, authHeader) {
|
|
|
687
703
|
}
|
|
688
704
|
return { ok: true };
|
|
689
705
|
} catch (error2) {
|
|
690
|
-
return { ok: false, error:
|
|
706
|
+
return { ok: false, error: describeTimeoutError(error2, BEST_EFFORT_NOTIFY_TIMEOUT_MS) };
|
|
691
707
|
}
|
|
692
708
|
}
|
|
693
|
-
function
|
|
709
|
+
function describeTimeoutError(error2, timeoutMs) {
|
|
694
710
|
const name = error2?.name;
|
|
695
711
|
if (name === "TimeoutError" || name === "AbortError") {
|
|
696
|
-
return `timed out after ${
|
|
712
|
+
return `timed out after ${timeoutMs}ms`;
|
|
697
713
|
}
|
|
698
714
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
699
715
|
}
|
|
716
|
+
async function notifyAgentDisconnected(agentId, authHeader) {
|
|
717
|
+
return postBestEffort(`/runners/${agentId}/disconnect`, authHeader);
|
|
718
|
+
}
|
|
700
719
|
async function reportMicrovmId(agentId, authHeader, microvmId) {
|
|
701
|
-
|
|
702
|
-
const apiUrl = getApiUrlConfig();
|
|
703
|
-
const response = await fetch(`${apiUrl}/runners/${agentId}/microvm`, {
|
|
704
|
-
method: "POST",
|
|
705
|
-
headers: { Authorization: authHeader, "Content-Type": "application/json" },
|
|
706
|
-
body: JSON.stringify({ microvm_id: microvmId }),
|
|
707
|
-
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
708
|
-
});
|
|
709
|
-
if (!response.ok) {
|
|
710
|
-
const serverMessage = await readErrorMessage(response);
|
|
711
|
-
return {
|
|
712
|
-
ok: false,
|
|
713
|
-
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
return { ok: true };
|
|
717
|
-
} catch (error2) {
|
|
718
|
-
return { ok: false, error: describeBestEffortError(error2) };
|
|
719
|
-
}
|
|
720
|
+
return postBestEffort(`/runners/${agentId}/microvm`, authHeader, { microvm_id: microvmId });
|
|
720
721
|
}
|
|
721
722
|
function toReportedWindow(window) {
|
|
722
723
|
if (!window) return null;
|
|
723
724
|
return { utilization: window.utilization, resets_at: window.resetsAt };
|
|
724
725
|
}
|
|
725
|
-
function toReportedOwner(snapshot) {
|
|
726
|
-
if (!snapshot.owner) return null;
|
|
727
|
-
return {
|
|
728
|
-
email: snapshot.owner.email,
|
|
729
|
-
organization_name: snapshot.owner.organizationName,
|
|
730
|
-
rate_limit_tier: snapshot.owner.rateLimitTier
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
726
|
async function reportClaudeUsage(agentId, authHeader, snapshot) {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
body: JSON.stringify({
|
|
740
|
-
five_hour: toReportedWindow(snapshot.fiveHour),
|
|
741
|
-
seven_day: toReportedWindow(snapshot.sevenDay),
|
|
742
|
-
owner: toReportedOwner(snapshot)
|
|
743
|
-
}),
|
|
744
|
-
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
745
|
-
});
|
|
746
|
-
if (!response.ok) {
|
|
747
|
-
const serverMessage = await readErrorMessage(response);
|
|
748
|
-
return {
|
|
749
|
-
ok: false,
|
|
750
|
-
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
return { ok: true };
|
|
754
|
-
} catch (error2) {
|
|
755
|
-
return { ok: false, error: describeBestEffortError(error2) };
|
|
756
|
-
}
|
|
727
|
+
return postBestEffort(`/runners/${agentId}/claude-usage`, authHeader, {
|
|
728
|
+
five_hour: toReportedWindow(snapshot.fiveHour),
|
|
729
|
+
seven_day: toReportedWindow(snapshot.sevenDay),
|
|
730
|
+
subscription: toReportedSubscription(snapshot.subscription)
|
|
731
|
+
});
|
|
757
732
|
}
|
|
758
733
|
function toReportedOpenAiWindow(window) {
|
|
759
734
|
if (!window) return null;
|
|
@@ -763,69 +738,26 @@ function toReportedOpenAiWindow(window) {
|
|
|
763
738
|
resets_at: window.resetsAt
|
|
764
739
|
};
|
|
765
740
|
}
|
|
766
|
-
function toReportedOpenAiSubscription(snapshot) {
|
|
767
|
-
if (!snapshot.subscription) return null;
|
|
768
|
-
return {
|
|
769
|
-
owner_email: snapshot.subscription.ownerEmail,
|
|
770
|
-
plan_type: snapshot.subscription.planType
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
741
|
async function reportOpenAiUsage(agentId, authHeader, snapshot) {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
secondary: toReportedOpenAiWindow(snapshot.secondary),
|
|
782
|
-
has_credits: snapshot.hasCredits,
|
|
783
|
-
credits_unlimited: snapshot.creditsUnlimited,
|
|
784
|
-
subscription: toReportedOpenAiSubscription(snapshot)
|
|
785
|
-
}),
|
|
786
|
-
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
787
|
-
});
|
|
788
|
-
if (!response.ok) {
|
|
789
|
-
const serverMessage = await readErrorMessage(response);
|
|
790
|
-
return {
|
|
791
|
-
ok: false,
|
|
792
|
-
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
return { ok: true };
|
|
796
|
-
} catch (error2) {
|
|
797
|
-
return { ok: false, error: describeBestEffortError(error2) };
|
|
798
|
-
}
|
|
742
|
+
return postBestEffort(`/runners/${agentId}/openai-usage`, authHeader, {
|
|
743
|
+
primary: toReportedOpenAiWindow(snapshot.primary),
|
|
744
|
+
secondary: toReportedOpenAiWindow(snapshot.secondary),
|
|
745
|
+
has_credits: snapshot.hasCredits,
|
|
746
|
+
credits_unlimited: snapshot.creditsUnlimited,
|
|
747
|
+
subscription: toReportedSubscription(snapshot.subscription)
|
|
748
|
+
});
|
|
799
749
|
}
|
|
800
750
|
async function reportResourceUsage(agentId, authHeader, usage) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
memory_available_bytes: usage.memoryAvailableBytes,
|
|
812
|
-
disk_total_bytes: usage.diskTotalBytes,
|
|
813
|
-
disk_free_bytes: usage.diskFreeBytes,
|
|
814
|
-
opencode_db_bytes: usage.opencodeDbBytes
|
|
815
|
-
}),
|
|
816
|
-
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
817
|
-
});
|
|
818
|
-
if (!response.ok) {
|
|
819
|
-
const serverMessage = await readErrorMessage(response);
|
|
820
|
-
return {
|
|
821
|
-
ok: false,
|
|
822
|
-
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
return { ok: true };
|
|
826
|
-
} catch (error2) {
|
|
827
|
-
return { ok: false, error: describeBestEffortError(error2) };
|
|
828
|
-
}
|
|
751
|
+
return postBestEffort(`/runners/${agentId}/resource-usage`, authHeader, {
|
|
752
|
+
cpu_percent: usage.cpuPercent,
|
|
753
|
+
cpu_peak_percent: usage.cpuPeakPercent,
|
|
754
|
+
cpu_count: usage.cpuCount,
|
|
755
|
+
memory_total_bytes: usage.memoryTotalBytes,
|
|
756
|
+
memory_available_bytes: usage.memoryAvailableBytes,
|
|
757
|
+
disk_total_bytes: usage.diskTotalBytes,
|
|
758
|
+
disk_free_bytes: usage.diskFreeBytes,
|
|
759
|
+
opencode_db_bytes: usage.opencodeDbBytes
|
|
760
|
+
});
|
|
829
761
|
}
|
|
830
762
|
async function getAgentInfo(agentId, authHeader) {
|
|
831
763
|
const apiUrl = getApiUrlConfig();
|
|
@@ -877,13 +809,6 @@ function authLabelFor(credentials2) {
|
|
|
877
809
|
}
|
|
878
810
|
return "user token";
|
|
879
811
|
}
|
|
880
|
-
function describeFetchError(error2) {
|
|
881
|
-
const name = error2?.name;
|
|
882
|
-
if (name === "TimeoutError" || name === "AbortError") {
|
|
883
|
-
return `timed out after ${STATUS_TIMEOUT_MS}ms waiting for a response`;
|
|
884
|
-
}
|
|
885
|
-
return error2 instanceof Error ? error2.message : String(error2);
|
|
886
|
-
}
|
|
887
812
|
async function checkStatus(jsonMode) {
|
|
888
813
|
const apiUrl = getApiUrlConfig();
|
|
889
814
|
const credentials2 = await getAuthCredentials();
|
|
@@ -911,7 +836,7 @@ async function checkStatus(jsonMode) {
|
|
|
911
836
|
endpoint: apiUrl,
|
|
912
837
|
authLabel: authLabelFor(credentials2),
|
|
913
838
|
reason: "unreachable",
|
|
914
|
-
error: `Could not reach ${apiUrl}: ${
|
|
839
|
+
error: `Could not reach ${apiUrl}: ${describeTimeoutError(error2, STATUS_TIMEOUT_MS)}. The credentials were NOT validated.`,
|
|
915
840
|
exitCode: 75
|
|
916
841
|
};
|
|
917
842
|
}
|
|
@@ -1009,10 +934,10 @@ async function status(options = {}) {
|
|
|
1009
934
|
}
|
|
1010
935
|
|
|
1011
936
|
// src/lib/claude-usage.ts
|
|
1012
|
-
import { execFileSync } from "child_process";
|
|
1013
|
-
import { readFileSync } from "fs";
|
|
1014
|
-
import { homedir } from "os";
|
|
1015
|
-
import { join } from "path";
|
|
937
|
+
import { execFileSync } from "node:child_process";
|
|
938
|
+
import { readFileSync } from "node:fs";
|
|
939
|
+
import { homedir } from "node:os";
|
|
940
|
+
import { join } from "node:path";
|
|
1016
941
|
var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
1017
942
|
var CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
|
|
1018
943
|
var CLAUDE_PROFILE_TIMEOUT_MS = 2e3;
|
|
@@ -1097,7 +1022,7 @@ function ownerLookupFailure(error2) {
|
|
|
1097
1022
|
}
|
|
1098
1023
|
async function getClaudeUsageOwner(accessToken) {
|
|
1099
1024
|
if (cachedOwner?.accessToken === accessToken) {
|
|
1100
|
-
return {
|
|
1025
|
+
return { subscription: cachedOwner.owner, ownerLookupError: null };
|
|
1101
1026
|
}
|
|
1102
1027
|
try {
|
|
1103
1028
|
const response = await fetch(CLAUDE_PROFILE_URL, {
|
|
@@ -1109,27 +1034,27 @@ async function getClaudeUsageOwner(accessToken) {
|
|
|
1109
1034
|
signal: AbortSignal.timeout(CLAUDE_PROFILE_TIMEOUT_MS)
|
|
1110
1035
|
});
|
|
1111
1036
|
if (!response.ok) {
|
|
1112
|
-
return {
|
|
1037
|
+
return { subscription: null, ownerLookupError: `HTTP ${response.status}` };
|
|
1113
1038
|
}
|
|
1114
1039
|
let body;
|
|
1115
1040
|
try {
|
|
1116
1041
|
body = await response.json();
|
|
1117
1042
|
} catch (error2) {
|
|
1118
|
-
return {
|
|
1043
|
+
return { subscription: null, ownerLookupError: "malformed response" };
|
|
1119
1044
|
}
|
|
1120
1045
|
const profile = body;
|
|
1121
1046
|
if (typeof profile.account?.email !== "string" || !profile.account.email.trim()) {
|
|
1122
|
-
return {
|
|
1047
|
+
return { subscription: null, ownerLookupError: "malformed response" };
|
|
1123
1048
|
}
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1049
|
+
const subscription = {
|
|
1050
|
+
ownerEmail: profile.account.email,
|
|
1126
1051
|
organizationName: typeof profile.organization?.name === "string" && profile.organization.name.trim() ? profile.organization.name.trim() : null,
|
|
1127
|
-
|
|
1052
|
+
planType: typeof profile.organization?.rate_limit_tier === "string" && profile.organization.rate_limit_tier.trim() ? profile.organization.rate_limit_tier.trim() : null
|
|
1128
1053
|
};
|
|
1129
|
-
cachedOwner = { accessToken, owner };
|
|
1130
|
-
return {
|
|
1054
|
+
cachedOwner = { accessToken, owner: subscription };
|
|
1055
|
+
return { subscription, ownerLookupError: null };
|
|
1131
1056
|
} catch (error2) {
|
|
1132
|
-
return {
|
|
1057
|
+
return { subscription: null, ownerLookupError: ownerLookupFailure(error2) };
|
|
1133
1058
|
}
|
|
1134
1059
|
}
|
|
1135
1060
|
async function getClaudeUsage() {
|
|
@@ -1158,11 +1083,11 @@ async function getClaudeUsage() {
|
|
|
1158
1083
|
throw new ClaudeUsageError(`Claude usage request failed: HTTP ${res.status}`, "request_failed");
|
|
1159
1084
|
}
|
|
1160
1085
|
const body = await res.json();
|
|
1161
|
-
const {
|
|
1086
|
+
const { subscription, ownerLookupError } = await getClaudeUsageOwner(credentials2.accessToken);
|
|
1162
1087
|
return {
|
|
1163
1088
|
fiveHour: toWindow(body.five_hour),
|
|
1164
1089
|
sevenDay: toWindow(body.seven_day),
|
|
1165
|
-
|
|
1090
|
+
subscription,
|
|
1166
1091
|
ownerLookupError
|
|
1167
1092
|
};
|
|
1168
1093
|
}
|
|
@@ -1192,10 +1117,10 @@ async function claudeUsage() {
|
|
|
1192
1117
|
}
|
|
1193
1118
|
|
|
1194
1119
|
// src/commands/run.ts
|
|
1195
|
-
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
1196
|
-
import { homedir as
|
|
1197
|
-
import { isAbsolute as isAbsolute3, join as
|
|
1198
|
-
import
|
|
1120
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1121
|
+
import { homedir as homedir6 } from "node:os";
|
|
1122
|
+
import { isAbsolute as isAbsolute3, join as join10, parse, resolve as resolvePath2 } from "node:path";
|
|
1123
|
+
import chalk7 from "chalk";
|
|
1199
1124
|
|
|
1200
1125
|
// ../../packages/types/src/agents/index.ts
|
|
1201
1126
|
var MICROVM_MAX_LIFETIME_MS = 8 * 60 * 6e4;
|
|
@@ -1254,7 +1179,7 @@ function stripQuery(url) {
|
|
|
1254
1179
|
|
|
1255
1180
|
// src/commands/run.ts
|
|
1256
1181
|
import ora3 from "ora";
|
|
1257
|
-
import { select as
|
|
1182
|
+
import { select as select4 } from "@inquirer/prompts";
|
|
1258
1183
|
|
|
1259
1184
|
// src/lib/telemetry.ts
|
|
1260
1185
|
var CLI_VERSION = (true ? "3.0.0" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
@@ -1427,12 +1352,50 @@ var SEVERITY_BY_LEVEL = {
|
|
|
1427
1352
|
warn: "warning",
|
|
1428
1353
|
error: "error"
|
|
1429
1354
|
};
|
|
1355
|
+
function parseOpenCodeLogLine(line) {
|
|
1356
|
+
const normalisedLine = line.replace(/\r$/, "");
|
|
1357
|
+
const levelMatch = normalisedLine.match(/(?:^|\s)level=(\w+)/i);
|
|
1358
|
+
if (!levelMatch) return null;
|
|
1359
|
+
const level = levelMatch[1].toUpperCase();
|
|
1360
|
+
if (level !== "WARN" && level !== "ERROR") return null;
|
|
1361
|
+
const sessionMatch = normalisedLine.match(/(?:^|\s)sessionID=(\S+)/);
|
|
1362
|
+
return { level: level === "WARN" ? "warn" : "error", sessionID: sessionMatch?.[1] };
|
|
1363
|
+
}
|
|
1364
|
+
var MAX_LINE_BUFFER_BYTES = 16 * 1024;
|
|
1365
|
+
function createOpenCodeActivityForwarder(getContext) {
|
|
1366
|
+
let buffer = Buffer.alloc(0);
|
|
1367
|
+
const flushLine = (line) => {
|
|
1368
|
+
const parsed = parseOpenCodeLogLine(line);
|
|
1369
|
+
if (!parsed) return;
|
|
1370
|
+
forwardRunnerActivity(
|
|
1371
|
+
{
|
|
1372
|
+
level: parsed.level,
|
|
1373
|
+
error: line,
|
|
1374
|
+
metadata: parsed.sessionID ? { sessionID: parsed.sessionID } : void 0,
|
|
1375
|
+
source: "opencode"
|
|
1376
|
+
},
|
|
1377
|
+
getContext()
|
|
1378
|
+
);
|
|
1379
|
+
};
|
|
1380
|
+
return (chunk) => {
|
|
1381
|
+
buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8")]);
|
|
1382
|
+
let newlineIndex;
|
|
1383
|
+
while ((newlineIndex = buffer.indexOf(10)) !== -1) {
|
|
1384
|
+
flushLine(buffer.subarray(0, newlineIndex).toString("utf-8").replace(/\r$/, ""));
|
|
1385
|
+
buffer = buffer.subarray(newlineIndex + 1);
|
|
1386
|
+
}
|
|
1387
|
+
if (buffer.length > MAX_LINE_BUFFER_BYTES) {
|
|
1388
|
+
flushLine(buffer.toString("utf-8"));
|
|
1389
|
+
buffer = Buffer.alloc(0);
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1430
1393
|
var MAX_MESSAGE_LENGTH = 500;
|
|
1431
1394
|
var MAX_METADATA_VALUE_LENGTH = 200;
|
|
1432
1395
|
var MAX_METADATA_ENTRIES = 20;
|
|
1433
1396
|
var TRUNCATION_MARKER = "\u2026";
|
|
1434
1397
|
function redact(message) {
|
|
1435
|
-
return message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/https?:\/\/\S+/g, "<url>");
|
|
1398
|
+
return message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/(?<![A-Za-z0-9_-])sk-ant-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-ant-***").replace(/(?<![A-Za-z0-9_-])sk-proj-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-proj-***").replace(/(?<![A-Za-z0-9_-])sk-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "sk-***").replace(/(?<![A-Za-z0-9_-])(gh[oprsu])_[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "$1_***").replace(/(?<![A-Za-z0-9_-])github_pat_[A-Za-z0-9_]{20,}(?![A-Za-z0-9_-])/g, "github_pat_***").replace(/https?:\/\/\S+/g, "<url>");
|
|
1436
1399
|
}
|
|
1437
1400
|
function truncate(message) {
|
|
1438
1401
|
if (message.length <= MAX_MESSAGE_LENGTH) return message;
|
|
@@ -1458,43 +1421,47 @@ function sanitiseMetadata(metadata) {
|
|
|
1458
1421
|
}
|
|
1459
1422
|
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
1460
1423
|
var RATE_LIMIT_MAX_EVENTS = 30;
|
|
1461
|
-
var
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1424
|
+
var rateWindows = /* @__PURE__ */ new Map();
|
|
1425
|
+
function admitUnderRateLimit(source, now) {
|
|
1426
|
+
let window = rateWindows.get(source);
|
|
1427
|
+
if (!window) {
|
|
1428
|
+
window = { windowStartedAt: 0, windowCount: 0, windowDroppedCount: 0 };
|
|
1429
|
+
rateWindows.set(source, window);
|
|
1430
|
+
}
|
|
1431
|
+
if (now - window.windowStartedAt >= RATE_LIMIT_WINDOW_MS) {
|
|
1432
|
+
if (window.windowDroppedCount > 0) {
|
|
1467
1433
|
console.error(
|
|
1468
|
-
`[runner-activity-telemetry] rate cap reached: dropped ${windowDroppedCount} ${windowDroppedCount === 1 ? "entry" : "entries"} in the last ${RATE_LIMIT_WINDOW_MS / 1e3}s (cap ${RATE_LIMIT_MAX_EVENTS}/min)`
|
|
1434
|
+
`[runner-activity-telemetry] rate cap reached: dropped ${window.windowDroppedCount} ${window.windowDroppedCount === 1 ? "entry" : "entries"} in the last ${RATE_LIMIT_WINDOW_MS / 1e3}s (cap ${RATE_LIMIT_MAX_EVENTS}/min) for source "${source}"`
|
|
1469
1435
|
);
|
|
1470
1436
|
}
|
|
1471
|
-
windowStartedAt = now;
|
|
1472
|
-
windowCount = 0;
|
|
1473
|
-
windowDroppedCount = 0;
|
|
1437
|
+
window.windowStartedAt = now;
|
|
1438
|
+
window.windowCount = 0;
|
|
1439
|
+
window.windowDroppedCount = 0;
|
|
1474
1440
|
}
|
|
1475
|
-
if (windowCount >= RATE_LIMIT_MAX_EVENTS) {
|
|
1476
|
-
windowDroppedCount++;
|
|
1477
|
-
if (windowDroppedCount === 1) {
|
|
1441
|
+
if (window.windowCount >= RATE_LIMIT_MAX_EVENTS) {
|
|
1442
|
+
window.windowDroppedCount++;
|
|
1443
|
+
if (window.windowDroppedCount === 1) {
|
|
1478
1444
|
console.error(
|
|
1479
|
-
`[runner-activity-telemetry] rate cap reached (${RATE_LIMIT_MAX_EVENTS} per ${RATE_LIMIT_WINDOW_MS / 1e3}s) \u2014 dropping further entries this window`
|
|
1445
|
+
`[runner-activity-telemetry] rate cap reached (${RATE_LIMIT_MAX_EVENTS} per ${RATE_LIMIT_WINDOW_MS / 1e3}s) \u2014 dropping further entries this window for source "${source}"`
|
|
1480
1446
|
);
|
|
1481
1447
|
}
|
|
1482
1448
|
return false;
|
|
1483
1449
|
}
|
|
1484
|
-
windowCount++;
|
|
1450
|
+
window.windowCount++;
|
|
1485
1451
|
return true;
|
|
1486
1452
|
}
|
|
1487
1453
|
function forwardRunnerActivity(entry, context) {
|
|
1488
1454
|
try {
|
|
1489
1455
|
if (!FORWARDED_LEVELS.has(entry.level)) return;
|
|
1490
1456
|
if (!context.agentId || !context.authHeader) return;
|
|
1491
|
-
|
|
1457
|
+
const source = entry.source ?? "cli.run";
|
|
1458
|
+
if (!admitUnderRateLimit(source, Date.now())) return;
|
|
1492
1459
|
const rawMessage = entry.error ?? entry.message ?? "";
|
|
1493
1460
|
const message = truncate(redact(rawMessage));
|
|
1494
1461
|
logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
|
|
1495
1462
|
severity: SEVERITY_BY_LEVEL[entry.level],
|
|
1496
1463
|
message,
|
|
1497
|
-
metadata: { ...sanitiseMetadata(entry.metadata), source
|
|
1464
|
+
metadata: { ...sanitiseMetadata(entry.metadata), source },
|
|
1498
1465
|
agentId: context.agentId
|
|
1499
1466
|
});
|
|
1500
1467
|
} catch (err) {
|
|
@@ -1505,8 +1472,8 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1505
1472
|
}
|
|
1506
1473
|
|
|
1507
1474
|
// src/lib/opencode/session-db-recovery-report.ts
|
|
1508
|
-
import { readFileSync as readFileSync2, unlinkSync } from "fs";
|
|
1509
|
-
import { join as join2 } from "path";
|
|
1475
|
+
import { readFileSync as readFileSync2, unlinkSync } from "node:fs";
|
|
1476
|
+
import { join as join2 } from "node:path";
|
|
1510
1477
|
function sessionDbRecoveryReportPath(homeDir, env) {
|
|
1511
1478
|
const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
|
|
1512
1479
|
return override || join2(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
|
|
@@ -1719,13 +1686,13 @@ async function waitForOpenCodeHealth(port, timeoutMs = 3e4) {
|
|
|
1719
1686
|
}
|
|
1720
1687
|
|
|
1721
1688
|
// src/lib/opencode/session-db-boot.ts
|
|
1722
|
-
import { spawn as spawn2 } from "child_process";
|
|
1723
|
-
import { mkdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
1724
|
-
import { homedir as homedir2 } from "os";
|
|
1725
|
-
import { dirname as dirname2, resolve as resolvePath } from "path";
|
|
1689
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1690
|
+
import { mkdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
|
|
1691
|
+
import { homedir as homedir2 } from "node:os";
|
|
1692
|
+
import { dirname as dirname2, resolve as resolvePath } from "node:path";
|
|
1726
1693
|
|
|
1727
1694
|
// src/lib/runner-synchroniser.ts
|
|
1728
|
-
import { spawn } from "child_process";
|
|
1695
|
+
import { spawn } from "node:child_process";
|
|
1729
1696
|
function appendError(stderr, error2) {
|
|
1730
1697
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
1731
1698
|
return stderr === "" ? message : `${stderr}
|
|
@@ -2250,9 +2217,9 @@ async function restoreAndVerifySessionDb(options) {
|
|
|
2250
2217
|
}
|
|
2251
2218
|
|
|
2252
2219
|
// src/lib/opencode/session-db-provenance.ts
|
|
2253
|
-
import { createRequire } from "module";
|
|
2254
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2255
|
-
import { dirname as dirname3, join as join3 } from "path";
|
|
2220
|
+
import { createRequire } from "node:module";
|
|
2221
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2222
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
2256
2223
|
var require2 = createRequire(import.meta.url);
|
|
2257
2224
|
function readSessionDbMigrationIds(dbPath) {
|
|
2258
2225
|
let db;
|
|
@@ -2446,6 +2413,17 @@ async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
|
|
|
2446
2413
|
|
|
2447
2414
|
// src/lib/opencode/process.ts
|
|
2448
2415
|
var OPENCODE_PORT_RANGE = [4096, 4097, 4098, 4099, 4100];
|
|
2416
|
+
var VALID_OPENCODE_LOG_LEVELS = /* @__PURE__ */ new Set(["DEBUG", "INFO", "WARN", "ERROR"]);
|
|
2417
|
+
function resolveOpenCodeLogLevel(env) {
|
|
2418
|
+
const raw = env.OPENCODE_LOG_LEVEL;
|
|
2419
|
+
if (!raw) return "INFO";
|
|
2420
|
+
const upper = raw.toUpperCase();
|
|
2421
|
+
if (VALID_OPENCODE_LOG_LEVELS.has(upper)) return upper;
|
|
2422
|
+
console.warn(
|
|
2423
|
+
`startOpenCode: ignoring invalid OPENCODE_LOG_LEVEL "${raw}" (expected DEBUG|INFO|WARN|ERROR) \u2014 using INFO`
|
|
2424
|
+
);
|
|
2425
|
+
return "INFO";
|
|
2426
|
+
}
|
|
2449
2427
|
function getProcessCwd(pid) {
|
|
2450
2428
|
const platform = process.platform;
|
|
2451
2429
|
try {
|
|
@@ -2494,14 +2472,14 @@ function findAvailablePort(startPort, maxAttempts = 10) {
|
|
|
2494
2472
|
}
|
|
2495
2473
|
return null;
|
|
2496
2474
|
}
|
|
2497
|
-
function
|
|
2475
|
+
function findProcessesByPattern(pgrepPattern, psPattern) {
|
|
2498
2476
|
const instances = [];
|
|
2499
2477
|
try {
|
|
2500
2478
|
const platform = process.platform;
|
|
2501
2479
|
if (platform === "darwin" || platform === "linux") {
|
|
2502
2480
|
let pids = [];
|
|
2503
2481
|
try {
|
|
2504
|
-
const pgrepOutput = execSync(
|
|
2482
|
+
const pgrepOutput = execSync(`pgrep -f "${pgrepPattern}"`, {
|
|
2505
2483
|
encoding: "utf-8",
|
|
2506
2484
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2507
2485
|
}).trim();
|
|
@@ -2510,7 +2488,7 @@ function findOpenCodeProcesses() {
|
|
|
2510
2488
|
}
|
|
2511
2489
|
} catch {
|
|
2512
2490
|
try {
|
|
2513
|
-
const psOutput = execSync(
|
|
2491
|
+
const psOutput = execSync(`ps aux | grep -E "${psPattern}" | grep -v grep`, {
|
|
2514
2492
|
encoding: "utf-8",
|
|
2515
2493
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2516
2494
|
}).trim();
|
|
@@ -2556,6 +2534,9 @@ function findOpenCodeProcesses() {
|
|
|
2556
2534
|
}
|
|
2557
2535
|
return instances;
|
|
2558
2536
|
}
|
|
2537
|
+
function findOpenCodeProcesses() {
|
|
2538
|
+
return findProcessesByPattern("opencode serve|opencode-serve", "opencode (serve|--port)");
|
|
2539
|
+
}
|
|
2559
2540
|
async function scanPortsForOpenCode() {
|
|
2560
2541
|
const instances = [];
|
|
2561
2542
|
const checks = OPENCODE_PORT_RANGE.map(async (port) => {
|
|
@@ -2602,7 +2583,7 @@ async function findHealthyOpenCodeInstances() {
|
|
|
2602
2583
|
}
|
|
2603
2584
|
async function startOpenCode(port, options = {}) {
|
|
2604
2585
|
let command = "opencode";
|
|
2605
|
-
const printLogs = options.inheritStdio ? ["--print-logs"] : [];
|
|
2586
|
+
const printLogs = options.inheritStdio ? ["--print-logs", "--log-level", resolveOpenCodeLogLevel(process.env)] : [];
|
|
2606
2587
|
let args = ["serve", "--port", port.toString(), "--hostname", "127.0.0.1", ...printLogs];
|
|
2607
2588
|
try {
|
|
2608
2589
|
execSync("which opencode", { stdio: "ignore" });
|
|
@@ -2659,6 +2640,19 @@ function isOpenCodeInstalled() {
|
|
|
2659
2640
|
return false;
|
|
2660
2641
|
}
|
|
2661
2642
|
}
|
|
2643
|
+
function isOpenCode2Installed() {
|
|
2644
|
+
try {
|
|
2645
|
+
const platform = process.platform;
|
|
2646
|
+
if (platform === "win32") {
|
|
2647
|
+
execSync2("where opencode2", { stdio: "ignore" });
|
|
2648
|
+
} else {
|
|
2649
|
+
execSync2("which opencode2", { stdio: "ignore" });
|
|
2650
|
+
}
|
|
2651
|
+
return true;
|
|
2652
|
+
} catch {
|
|
2653
|
+
return false;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2662
2656
|
async function promptOpenCodeInstall(interactive) {
|
|
2663
2657
|
if (!interactive) {
|
|
2664
2658
|
console.log(
|
|
@@ -2668,7 +2662,11 @@ async function promptOpenCodeInstall(interactive) {
|
|
|
2668
2662
|
install_url: OPENCODE_INSTALL_URL,
|
|
2669
2663
|
install_commands: {
|
|
2670
2664
|
npm: "npm install -g opencode-ai",
|
|
2671
|
-
curl: "curl -fsSL https://opencode.ai/install.sh | sh"
|
|
2665
|
+
curl: "curl -fsSL https://opencode.ai/install.sh | sh",
|
|
2666
|
+
v2: {
|
|
2667
|
+
npm: "npm install -g @opencode-ai/cli@beta",
|
|
2668
|
+
curl: "curl -fsSL https://opencode.ai/v2/install | bash"
|
|
2669
|
+
}
|
|
2672
2670
|
}
|
|
2673
2671
|
})
|
|
2674
2672
|
);
|
|
@@ -3190,21 +3188,74 @@ function collectSubagentSessions(messages, userMessageId) {
|
|
|
3190
3188
|
}
|
|
3191
3189
|
return refs;
|
|
3192
3190
|
}
|
|
3193
|
-
function
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3191
|
+
function finiteNumber(value) {
|
|
3192
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3193
|
+
}
|
|
3194
|
+
function taskCallModel(value) {
|
|
3195
|
+
if (!value || typeof value !== "object") return null;
|
|
3196
|
+
const model = value;
|
|
3197
|
+
const modelID = typeof model.modelID === "string" ? model.modelID : void 0;
|
|
3198
|
+
const providerID = typeof model.providerID === "string" ? model.providerID : void 0;
|
|
3199
|
+
return modelID || providerID ? { modelID, providerID } : null;
|
|
3200
|
+
}
|
|
3201
|
+
function collectTaskCalls(messages, userMessageId) {
|
|
3202
|
+
if (!messages || messages.length === 0) return [];
|
|
3203
|
+
const calls = [];
|
|
3204
|
+
for (const message of messages) {
|
|
3205
|
+
if (roleOf(message) !== "assistant" || parentIdOf(message) !== userMessageId) continue;
|
|
3206
|
+
for (const part of message.parts ?? []) {
|
|
3207
|
+
if (part.tool !== "task" || !part.callID || !part.state || part.state.status === "pending") {
|
|
3208
|
+
continue;
|
|
3209
|
+
}
|
|
3210
|
+
const rawName = part.state.input?.subagent_type;
|
|
3211
|
+
const subagentName = typeof rawName === "string" && rawName.trim().length > 0 ? rawName : rawName === void 0 ? "general" : "unknown";
|
|
3212
|
+
const metadata = part.state.metadata;
|
|
3213
|
+
calls.push({
|
|
3214
|
+
callID: part.callID,
|
|
3215
|
+
subagentName,
|
|
3216
|
+
childSessionId: typeof metadata?.sessionId === "string" ? metadata.sessionId : null,
|
|
3217
|
+
parentSessionId: typeof metadata?.parentSessionId === "string" ? metadata.parentSessionId : null,
|
|
3218
|
+
model: taskCallModel(metadata?.model),
|
|
3219
|
+
status: part.state.status ?? "unknown",
|
|
3220
|
+
timeStart: finiteNumber(part.state.time?.start),
|
|
3221
|
+
timeEnd: finiteNumber(part.state.time?.end)
|
|
3222
|
+
});
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
return calls;
|
|
3226
|
+
}
|
|
3227
|
+
function attributeTaskCallUsage(messages, windows) {
|
|
3228
|
+
const eligibleWindows = windows.filter(
|
|
3229
|
+
(window) => window.timeStart !== null && Number.isFinite(window.timeStart)
|
|
3197
3230
|
);
|
|
3198
|
-
const
|
|
3199
|
-
const
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
const
|
|
3205
|
-
|
|
3231
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
3232
|
+
for (const window of eligibleWindows) assignments.set(window.callID, []);
|
|
3233
|
+
const unattributed = [];
|
|
3234
|
+
for (const message of messages ?? []) {
|
|
3235
|
+
if (roleOf(message) !== "assistant") continue;
|
|
3236
|
+
const created = finiteNumber(createdOf(message));
|
|
3237
|
+
const matching = created === null ? [] : eligibleWindows.filter(
|
|
3238
|
+
(window) => window.timeStart <= created && (window.timeEnd === null || window.timeEnd === void 0 || created <= window.timeEnd)
|
|
3239
|
+
);
|
|
3240
|
+
if (matching.length === 0) {
|
|
3241
|
+
unattributed.push(message);
|
|
3242
|
+
continue;
|
|
3243
|
+
}
|
|
3244
|
+
matching.sort((a, b) => a.timeStart - b.timeStart);
|
|
3245
|
+
assignments.get(matching[0].callID)?.push(message);
|
|
3206
3246
|
}
|
|
3207
|
-
|
|
3247
|
+
return {
|
|
3248
|
+
invocations: eligibleWindows.map((window) => {
|
|
3249
|
+
const assigned = assignments.get(window.callID) ?? [];
|
|
3250
|
+
return { callID: window.callID, messages: assigned, usage: sumAssistantUsage(assigned) };
|
|
3251
|
+
}),
|
|
3252
|
+
unattributed
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
function sumAssistantUsage(messages) {
|
|
3256
|
+
if (!messages || messages.length === 0) return null;
|
|
3257
|
+
const nonErrored = messages.filter((message) => errorOf(message) == null);
|
|
3258
|
+
const selected = nonErrored.length > 0 ? nonErrored : messages;
|
|
3208
3259
|
let sawAnyUsage = false;
|
|
3209
3260
|
let inputSum = 0;
|
|
3210
3261
|
let outputSum = 0;
|
|
@@ -3215,7 +3266,7 @@ function messageUsage(messages, userMessageId) {
|
|
|
3215
3266
|
let sawCost = false;
|
|
3216
3267
|
let modelId = null;
|
|
3217
3268
|
let providerId = null;
|
|
3218
|
-
for (const m of
|
|
3269
|
+
for (const m of selected) {
|
|
3219
3270
|
const info = m.info;
|
|
3220
3271
|
if (!info) continue;
|
|
3221
3272
|
const tokens = info.tokens;
|
|
@@ -3250,12 +3301,28 @@ function messageUsage(messages, userMessageId) {
|
|
|
3250
3301
|
usage_tokens_reasoning: reasoningSum,
|
|
3251
3302
|
usage_tokens_cache_read: cacheReadSum,
|
|
3252
3303
|
usage_tokens_cache_write: cacheWriteSum,
|
|
3253
|
-
// NULL means
|
|
3254
|
-
//
|
|
3255
|
-
// `sawCost` true with `costSum === 0`.
|
|
3304
|
+
// NULL means OpenCode never reported a cost; it is distinct from a genuine
|
|
3305
|
+
// zero-cost message, which sets `sawCost` with `costSum === 0`.
|
|
3256
3306
|
usage_cost_usd: sawCost ? costSum : null
|
|
3257
3307
|
};
|
|
3258
3308
|
}
|
|
3309
|
+
function messageUsage(messages, userMessageId) {
|
|
3310
|
+
if (!messages || messages.length === 0) return null;
|
|
3311
|
+
const byParentAll = messages.filter(
|
|
3312
|
+
(m) => roleOf(m) === "assistant" && parentIdOf(m) === userMessageId
|
|
3313
|
+
);
|
|
3314
|
+
const byParentNonErrored = byParentAll.filter((m) => errorOf(m) == null);
|
|
3315
|
+
const byParent = byParentNonErrored.length > 0 ? byParentNonErrored : byParentAll;
|
|
3316
|
+
let correlated;
|
|
3317
|
+
if (byParent.length > 0) {
|
|
3318
|
+
correlated = byParent;
|
|
3319
|
+
} else {
|
|
3320
|
+
const reply = findAssistantReplyAfter(messages, userMessageId);
|
|
3321
|
+
correlated = reply ? [reply] : [];
|
|
3322
|
+
}
|
|
3323
|
+
if (correlated.length === 0) return null;
|
|
3324
|
+
return sumAssistantUsage(correlated);
|
|
3325
|
+
}
|
|
3259
3326
|
function messageRunState(messages, userMessageId) {
|
|
3260
3327
|
if (!messages || messages.length === 0) return "unknown";
|
|
3261
3328
|
const hasUser = messages.some((m) => idOf(m) === userMessageId);
|
|
@@ -3385,6 +3452,28 @@ function hasRunningAssistantExcept(messages, exceptUserMessageId) {
|
|
|
3385
3452
|
(m) => roleOf(m) === "assistant" && parentIdOf(m) !== exceptUserMessageId && isAssistantInFlight(m)
|
|
3386
3453
|
);
|
|
3387
3454
|
}
|
|
3455
|
+
function hasLaterSiblingTurnStarted(messages, userMessageId, siblingUserMessageIds) {
|
|
3456
|
+
if (!messages || messages.length === 0) return false;
|
|
3457
|
+
const userIndex = messages.findIndex((message) => idOf(message) === userMessageId);
|
|
3458
|
+
if (userIndex === -1) return false;
|
|
3459
|
+
let hasLaterUser = false;
|
|
3460
|
+
let hasStartedLaterUser = false;
|
|
3461
|
+
for (let i = userIndex + 1; i < messages.length; i++) {
|
|
3462
|
+
const message = messages[i];
|
|
3463
|
+
if (roleOf(message) !== "user") continue;
|
|
3464
|
+
hasLaterUser = true;
|
|
3465
|
+
const laterUserMessageId = idOf(message);
|
|
3466
|
+
if (laterUserMessageId === void 0 || !siblingUserMessageIds.has(laterUserMessageId)) {
|
|
3467
|
+
return false;
|
|
3468
|
+
}
|
|
3469
|
+
if (messages.some(
|
|
3470
|
+
(candidate) => roleOf(candidate) === "assistant" && parentIdOf(candidate) === laterUserMessageId
|
|
3471
|
+
)) {
|
|
3472
|
+
hasStartedLaterUser = true;
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
return hasLaterUser && hasStartedLaterUser;
|
|
3476
|
+
}
|
|
3388
3477
|
async function hasAnyConfiguredProvider(port) {
|
|
3389
3478
|
try {
|
|
3390
3479
|
const res = await timedFetch(`${opencodeBase(port)}/config/providers`);
|
|
@@ -3416,6 +3505,94 @@ async function hasAnyConfiguredProvider(port) {
|
|
|
3416
3505
|
return null;
|
|
3417
3506
|
}
|
|
3418
3507
|
}
|
|
3508
|
+
function sessionErrorReason(error2) {
|
|
3509
|
+
const record = typeof error2 === "object" && error2 !== null ? error2 : null;
|
|
3510
|
+
const data = record?.data;
|
|
3511
|
+
const dataRecord = typeof data === "object" && data !== null ? data : null;
|
|
3512
|
+
const rawReason = typeof dataRecord?.message === "string" && dataRecord.message || typeof record?.message === "string" && record.message || typeof error2 === "string" && error2 || typeof record?.name === "string" && record.name || "OpenCode reported a session error with no details";
|
|
3513
|
+
const reason = rawReason.replace(/\s+/g, " ").trim().slice(0, 500);
|
|
3514
|
+
return reason || "OpenCode reported a session error with no details";
|
|
3515
|
+
}
|
|
3516
|
+
function parseSessionErrorFrame(data) {
|
|
3517
|
+
let parsed;
|
|
3518
|
+
try {
|
|
3519
|
+
parsed = JSON.parse(data);
|
|
3520
|
+
} catch (error2) {
|
|
3521
|
+
void error2;
|
|
3522
|
+
return null;
|
|
3523
|
+
}
|
|
3524
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3525
|
+
const parsedRecord = parsed;
|
|
3526
|
+
const payload = parsedRecord.payload;
|
|
3527
|
+
const event = payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : parsedRecord;
|
|
3528
|
+
if (event.type !== "session.error") return null;
|
|
3529
|
+
const properties = event.properties;
|
|
3530
|
+
if (properties === null || typeof properties !== "object" || Array.isArray(properties)) {
|
|
3531
|
+
return null;
|
|
3532
|
+
}
|
|
3533
|
+
const propertiesRecord = properties;
|
|
3534
|
+
const sessionId = propertiesRecord.sessionID;
|
|
3535
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) return null;
|
|
3536
|
+
return {
|
|
3537
|
+
sessionId,
|
|
3538
|
+
reason: sessionErrorReason(propertiesRecord.error)
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
async function readSessionErrorStream(port, options) {
|
|
3542
|
+
let reader = null;
|
|
3543
|
+
try {
|
|
3544
|
+
const response = await fetch(`${opencodeBase(port)}/event`, {
|
|
3545
|
+
headers: { accept: "text/event-stream" },
|
|
3546
|
+
signal: options.signal
|
|
3547
|
+
});
|
|
3548
|
+
if (!response.ok || !response.body) {
|
|
3549
|
+
return { reason: "unavailable", detail: `HTTP ${response.status}` };
|
|
3550
|
+
}
|
|
3551
|
+
reader = response.body.getReader();
|
|
3552
|
+
const decoder = new TextDecoder();
|
|
3553
|
+
let buffer = "";
|
|
3554
|
+
const processLine = (line) => {
|
|
3555
|
+
const trimmed = line.trimEnd();
|
|
3556
|
+
if (!trimmed.startsWith("data:")) return;
|
|
3557
|
+
const event = parseSessionErrorFrame(trimmed.slice("data:".length).replace(/^ /, ""));
|
|
3558
|
+
if (event) options.onSessionError(event);
|
|
3559
|
+
};
|
|
3560
|
+
while (true) {
|
|
3561
|
+
const { done, value } = await reader.read();
|
|
3562
|
+
if (done) return { reason: "ended" };
|
|
3563
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3564
|
+
const lines = buffer.split("\n");
|
|
3565
|
+
buffer = lines.pop() ?? "";
|
|
3566
|
+
for (const line of lines) processLine(line);
|
|
3567
|
+
}
|
|
3568
|
+
} catch (err) {
|
|
3569
|
+
if (options.signal.aborted) return { reason: "aborted" };
|
|
3570
|
+
return {
|
|
3571
|
+
reason: "unavailable",
|
|
3572
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
3573
|
+
};
|
|
3574
|
+
} finally {
|
|
3575
|
+
if (reader) void reader.cancel().catch(() => void 0);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
async function reloadProviderCache(port) {
|
|
3579
|
+
try {
|
|
3580
|
+
const res = await timedFetch(`${opencodeBase(port)}/config`, {
|
|
3581
|
+
method: "PATCH",
|
|
3582
|
+
headers: { "Content-Type": "application/json" },
|
|
3583
|
+
body: JSON.stringify({})
|
|
3584
|
+
});
|
|
3585
|
+
if (!res.ok) {
|
|
3586
|
+
console.error(
|
|
3587
|
+
`[reloadProviderCache] PATCH /config returned HTTP ${res.status} (port ${port})`
|
|
3588
|
+
);
|
|
3589
|
+
}
|
|
3590
|
+
} catch (err) {
|
|
3591
|
+
console.error(
|
|
3592
|
+
`[reloadProviderCache] PATCH /config failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3593
|
+
);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3419
3596
|
|
|
3420
3597
|
// src/lib/opencode/session-cleanup.ts
|
|
3421
3598
|
var DURATION_UNIT_MS = {
|
|
@@ -3522,8 +3699,8 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
3522
3699
|
}
|
|
3523
3700
|
|
|
3524
3701
|
// src/lib/opencode/session-db-size.ts
|
|
3525
|
-
import { statSync as statSync3 } from "fs";
|
|
3526
|
-
import { join as join4 } from "path";
|
|
3702
|
+
import { statSync as statSync3 } from "node:fs";
|
|
3703
|
+
import { join as join4 } from "node:path";
|
|
3527
3704
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
3528
3705
|
function statSessionDbBytes(homeDir) {
|
|
3529
3706
|
const dbPath = join4(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
@@ -3553,9 +3730,96 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
3553
3730
|
return null;
|
|
3554
3731
|
}
|
|
3555
3732
|
|
|
3733
|
+
// src/lib/opencode/log-tail.ts
|
|
3734
|
+
import { statSync as statSync4 } from "node:fs";
|
|
3735
|
+
import { homedir as homedir3 } from "node:os";
|
|
3736
|
+
import { join as join5 } from "node:path";
|
|
3737
|
+
import { open as open2, stat } from "node:fs/promises";
|
|
3738
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
3739
|
+
function resolveOpenCodeLogPath(homeDir = homedir3(), env = process.env) {
|
|
3740
|
+
const dataDir = env.XDG_DATA_HOME || join5(homeDir, ".local", "share");
|
|
3741
|
+
return join5(dataDir, "opencode", "log", "opencode.log");
|
|
3742
|
+
}
|
|
3743
|
+
function isEnoent(error2) {
|
|
3744
|
+
return error2?.code === "ENOENT";
|
|
3745
|
+
}
|
|
3746
|
+
function reportFailure(operation, logPath, error2) {
|
|
3747
|
+
console.error(
|
|
3748
|
+
`[opencode-log-tail] ${operation} failed for ${logPath}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
3749
|
+
);
|
|
3750
|
+
}
|
|
3751
|
+
function tailOpenCodeLogFile(logPath, onChunk, opts = {}) {
|
|
3752
|
+
const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3753
|
+
let offset = 0;
|
|
3754
|
+
let inode = null;
|
|
3755
|
+
let baselineReady = true;
|
|
3756
|
+
try {
|
|
3757
|
+
const initial = statSync4(logPath);
|
|
3758
|
+
offset = initial.size;
|
|
3759
|
+
inode = initial.ino;
|
|
3760
|
+
} catch (error2) {
|
|
3761
|
+
if (!isEnoent(error2)) {
|
|
3762
|
+
reportFailure("initial stat", logPath, error2);
|
|
3763
|
+
baselineReady = false;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
let polling = false;
|
|
3767
|
+
let stopped = false;
|
|
3768
|
+
const poll = async () => {
|
|
3769
|
+
if (polling || stopped) return;
|
|
3770
|
+
polling = true;
|
|
3771
|
+
try {
|
|
3772
|
+
let current;
|
|
3773
|
+
try {
|
|
3774
|
+
current = await stat(logPath);
|
|
3775
|
+
} catch (error2) {
|
|
3776
|
+
if (!isEnoent(error2)) reportFailure("stat", logPath, error2);
|
|
3777
|
+
return;
|
|
3778
|
+
}
|
|
3779
|
+
if (!baselineReady) {
|
|
3780
|
+
offset = current.size;
|
|
3781
|
+
inode = current.ino;
|
|
3782
|
+
baselineReady = true;
|
|
3783
|
+
return;
|
|
3784
|
+
}
|
|
3785
|
+
if (inode !== null && current.ino !== inode || current.size < offset) {
|
|
3786
|
+
offset = 0;
|
|
3787
|
+
}
|
|
3788
|
+
inode = current.ino;
|
|
3789
|
+
if (current.size === offset) return;
|
|
3790
|
+
const length = current.size - offset;
|
|
3791
|
+
const fh = await open2(logPath, "r");
|
|
3792
|
+
try {
|
|
3793
|
+
const buf = Buffer.alloc(length);
|
|
3794
|
+
const { bytesRead } = await fh.read(buf, 0, length, offset);
|
|
3795
|
+
offset += bytesRead;
|
|
3796
|
+
if (bytesRead > 0) onChunk(buf.subarray(0, bytesRead));
|
|
3797
|
+
} finally {
|
|
3798
|
+
await fh.close();
|
|
3799
|
+
}
|
|
3800
|
+
} catch (error2) {
|
|
3801
|
+
if (!isEnoent(error2)) reportFailure("poll", logPath, error2);
|
|
3802
|
+
} finally {
|
|
3803
|
+
polling = false;
|
|
3804
|
+
}
|
|
3805
|
+
};
|
|
3806
|
+
const interval = setInterval(() => void poll(), pollIntervalMs);
|
|
3807
|
+
void poll();
|
|
3808
|
+
return {
|
|
3809
|
+
stop: () => {
|
|
3810
|
+
stopped = true;
|
|
3811
|
+
clearInterval(interval);
|
|
3812
|
+
}
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3556
3816
|
// src/lib/opencode/session-db-reclaim.ts
|
|
3557
|
-
import { statSync as
|
|
3558
|
-
import { dirname as dirname4 } from "path";
|
|
3817
|
+
import { statSync as statSync5, statfsSync } from "node:fs";
|
|
3818
|
+
import { dirname as dirname4 } from "node:path";
|
|
3819
|
+
function errorMessage(error2) {
|
|
3820
|
+
if (!(error2 instanceof Error)) return String(error2);
|
|
3821
|
+
return error2.cause instanceof Error ? error2.cause.message : error2.message;
|
|
3822
|
+
}
|
|
3559
3823
|
function insufficientSpaceReason(dbPath, requiredBytes) {
|
|
3560
3824
|
try {
|
|
3561
3825
|
const fsStats = statfsSync(dirname4(dbPath));
|
|
@@ -3581,17 +3845,17 @@ async function probeReclaimAvailability(input) {
|
|
|
3581
3845
|
const { dbPath, requiredBytes } = input;
|
|
3582
3846
|
let sqlite;
|
|
3583
3847
|
try {
|
|
3584
|
-
sqlite = await import("sqlite");
|
|
3848
|
+
sqlite = await import("node:sqlite");
|
|
3585
3849
|
} catch (err) {
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
return "sqlite-unavailable";
|
|
3850
|
+
const detail = `Node ${process.version}: ${errorMessage(err)}`;
|
|
3851
|
+
console.warn(`[probeReclaimAvailability] node:sqlite unavailable for ${dbPath}: ` + detail);
|
|
3852
|
+
return { reason: "sqlite-unavailable", detail };
|
|
3590
3853
|
}
|
|
3591
3854
|
let autoVacuum = null;
|
|
3592
3855
|
try {
|
|
3593
3856
|
const db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
|
|
3594
3857
|
try {
|
|
3858
|
+
db.exec("PRAGMA busy_timeout=5000");
|
|
3595
3859
|
autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
|
|
3596
3860
|
} finally {
|
|
3597
3861
|
db.close();
|
|
@@ -3602,23 +3866,25 @@ async function probeReclaimAvailability(input) {
|
|
|
3602
3866
|
);
|
|
3603
3867
|
}
|
|
3604
3868
|
if (autoVacuum !== 0) return null;
|
|
3605
|
-
return insufficientSpaceReason(dbPath, requiredBytes) !== null ? "insufficient-disk-space" : null;
|
|
3869
|
+
return insufficientSpaceReason(dbPath, requiredBytes) !== null ? { reason: "insufficient-disk-space" } : null;
|
|
3606
3870
|
}
|
|
3607
3871
|
async function reclaimSessionDbSpace(input) {
|
|
3608
3872
|
const { dbPath, maxPages, allowFullVacuum = true } = input;
|
|
3609
3873
|
let sqlite;
|
|
3610
3874
|
try {
|
|
3611
|
-
sqlite = await import("sqlite");
|
|
3875
|
+
sqlite = await import("node:sqlite");
|
|
3612
3876
|
} catch (err) {
|
|
3877
|
+
const detail = `Node ${process.version}: ${errorMessage(err)}`;
|
|
3613
3878
|
console.warn(
|
|
3614
|
-
`[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${
|
|
3879
|
+
`[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${detail}`
|
|
3615
3880
|
);
|
|
3616
|
-
return { ok: false, skipped: "sqlite-unavailable" };
|
|
3881
|
+
return { ok: false, skipped: "sqlite-unavailable", detail };
|
|
3617
3882
|
}
|
|
3618
3883
|
const { DatabaseSync } = sqlite;
|
|
3619
3884
|
let db;
|
|
3620
3885
|
try {
|
|
3621
3886
|
db = new DatabaseSync(dbPath);
|
|
3887
|
+
db.exec("PRAGMA busy_timeout=5000");
|
|
3622
3888
|
const autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
|
|
3623
3889
|
if (autoVacuum === 0) {
|
|
3624
3890
|
if (!allowFullVacuum) {
|
|
@@ -3627,7 +3893,7 @@ async function reclaimSessionDbSpace(input) {
|
|
|
3627
3893
|
);
|
|
3628
3894
|
return { ok: false, skipped: "full-vacuum-blocked" };
|
|
3629
3895
|
}
|
|
3630
|
-
const fileBytesForGuard =
|
|
3896
|
+
const fileBytesForGuard = statSync5(dbPath).size;
|
|
3631
3897
|
const skipReason = insufficientSpaceReason(dbPath, fileBytesForGuard);
|
|
3632
3898
|
if (skipReason !== null) {
|
|
3633
3899
|
console.warn(
|
|
@@ -3655,10 +3921,12 @@ async function reclaimSessionDbSpace(input) {
|
|
|
3655
3921
|
);
|
|
3656
3922
|
return { ok: false, skipped: "auto-vacuum-not-applicable" };
|
|
3657
3923
|
} catch (err) {
|
|
3658
|
-
console.error(
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3924
|
+
console.error(`[reclaimSessionDbSpace] reclaim failed for ${dbPath}: ` + errorMessage(err));
|
|
3925
|
+
return {
|
|
3926
|
+
ok: false,
|
|
3927
|
+
skipped: "reclaim-error",
|
|
3928
|
+
detail: errorMessage(err)
|
|
3929
|
+
};
|
|
3662
3930
|
} finally {
|
|
3663
3931
|
db?.close();
|
|
3664
3932
|
}
|
|
@@ -3950,8 +4218,8 @@ function connectTunnel(options) {
|
|
|
3950
4218
|
try {
|
|
3951
4219
|
message = JSON.parse(data.toString());
|
|
3952
4220
|
} catch (error2) {
|
|
3953
|
-
const
|
|
3954
|
-
onError?.(`Failed to handle message: ${
|
|
4221
|
+
const errorMessage3 = error2 instanceof Error ? error2.message : "Unknown error";
|
|
4222
|
+
onError?.(`Failed to handle message: ${errorMessage3}`);
|
|
3955
4223
|
return;
|
|
3956
4224
|
}
|
|
3957
4225
|
if (isStreamFrame(message)) {
|
|
@@ -4095,7 +4363,7 @@ var RunnerConnection = class {
|
|
|
4095
4363
|
};
|
|
4096
4364
|
|
|
4097
4365
|
// src/lib/tunnel/ready-marker.ts
|
|
4098
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
4366
|
+
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
4099
4367
|
function writeTunnelReadyMarker(path, agentId) {
|
|
4100
4368
|
try {
|
|
4101
4369
|
writeFileSync3(path, `${agentId}
|
|
@@ -4107,7 +4375,7 @@ function writeTunnelReadyMarker(path, agentId) {
|
|
|
4107
4375
|
}
|
|
4108
4376
|
|
|
4109
4377
|
// src/lib/replication.ts
|
|
4110
|
-
import { spawn as spawn4 } from "child_process";
|
|
4378
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
4111
4379
|
function startSessionDbReplication(configPath) {
|
|
4112
4380
|
return spawn4("litestream", ["replicate", "-config", configPath], {
|
|
4113
4381
|
stdio: "inherit"
|
|
@@ -4123,7 +4391,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
|
|
|
4123
4391
|
}
|
|
4124
4392
|
|
|
4125
4393
|
// src/lib/process-liveness.ts
|
|
4126
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
4394
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
4127
4395
|
function isProcessAlive(pid) {
|
|
4128
4396
|
try {
|
|
4129
4397
|
process.kill(pid, 0);
|
|
@@ -4149,9 +4417,9 @@ function isProcessAlive(pid) {
|
|
|
4149
4417
|
}
|
|
4150
4418
|
|
|
4151
4419
|
// src/lib/openai-usage.ts
|
|
4152
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
4153
|
-
import { homedir as
|
|
4154
|
-
import { join as
|
|
4420
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
4421
|
+
import { homedir as homedir4 } from "node:os";
|
|
4422
|
+
import { join as join6 } from "node:path";
|
|
4155
4423
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
4156
4424
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
4157
4425
|
var OpenAiUsageError = class extends Error {
|
|
@@ -4165,7 +4433,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
4165
4433
|
}
|
|
4166
4434
|
function readOpenCodeChatGptCredentials() {
|
|
4167
4435
|
try {
|
|
4168
|
-
const raw = readFileSync5(
|
|
4436
|
+
const raw = readFileSync5(join6(homedir4(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
4169
4437
|
let parsed;
|
|
4170
4438
|
try {
|
|
4171
4439
|
parsed = JSON.parse(raw);
|
|
@@ -4202,7 +4470,7 @@ function parseChatGptIdentity(accessToken) {
|
|
|
4202
4470
|
const auth = payload["https://api.openai.com/auth"];
|
|
4203
4471
|
const ownerEmail = typeof profile?.email === "string" ? profile.email.trim() || null : null;
|
|
4204
4472
|
const planType = typeof auth?.chatgpt_plan_type === "string" ? auth.chatgpt_plan_type.trim() || null : null;
|
|
4205
|
-
return ownerEmail === null && planType === null ? null : { ownerEmail, planType };
|
|
4473
|
+
return ownerEmail === null && planType === null ? null : { ownerEmail, planType, organizationName: null };
|
|
4206
4474
|
}
|
|
4207
4475
|
function toWindow2(headers, name) {
|
|
4208
4476
|
const utilizationHeader = headers.get(`x-codex-${name}-used-percent`);
|
|
@@ -4384,13 +4652,6 @@ function resolveClaudeUsageReportingMode(flagValue, env) {
|
|
|
4384
4652
|
envVar: "EVIDENT_CLAUDE_USAGE_REPORTING"
|
|
4385
4653
|
});
|
|
4386
4654
|
}
|
|
4387
|
-
function nextReportDelayMs(random = Math.random) {
|
|
4388
|
-
return usageReportDelayMs(random);
|
|
4389
|
-
}
|
|
4390
|
-
var FIRST_REPORT_DELAY_MS = firstReportDelayMs();
|
|
4391
|
-
function claudeUsageFailureLogLevel(consecutiveFailures) {
|
|
4392
|
-
return usageReportFailureLogLevel(consecutiveFailures);
|
|
4393
|
-
}
|
|
4394
4655
|
|
|
4395
4656
|
// src/lib/openai-usage-reporting.ts
|
|
4396
4657
|
function resolveOpenAiUsageReportingMode(flagValue, env) {
|
|
@@ -4427,8 +4688,8 @@ function resolveResourceUsageReportingEnabled(flagValue, env) {
|
|
|
4427
4688
|
}
|
|
4428
4689
|
|
|
4429
4690
|
// src/lib/resource-usage.ts
|
|
4430
|
-
import { cpus, totalmem, freemem } from "os";
|
|
4431
|
-
import { statfsSync as statfsSync2 } from "fs";
|
|
4691
|
+
import { cpus, totalmem, freemem } from "node:os";
|
|
4692
|
+
import { statfsSync as statfsSync2 } from "node:fs";
|
|
4432
4693
|
|
|
4433
4694
|
// src/lib/ecs-task-metadata.ts
|
|
4434
4695
|
var ECS_METADATA_TIMEOUT_MS = 2e3;
|
|
@@ -4595,15 +4856,15 @@ function createResourceUsageCollector(homeDir) {
|
|
|
4595
4856
|
}
|
|
4596
4857
|
|
|
4597
4858
|
// src/lib/channels/driver.ts
|
|
4598
|
-
import { homedir as
|
|
4859
|
+
import { homedir as homedir5 } from "node:os";
|
|
4599
4860
|
|
|
4600
4861
|
// src/lib/runner-file-sync.ts
|
|
4601
|
-
import { join as
|
|
4862
|
+
import { join as join8 } from "node:path";
|
|
4602
4863
|
|
|
4603
4864
|
// src/lib/file-push.ts
|
|
4604
|
-
import { randomUUID } from "crypto";
|
|
4605
|
-
import { chmod, mkdir, open as
|
|
4606
|
-
import { basename, dirname as dirname5, isAbsolute, join as
|
|
4865
|
+
import { randomUUID } from "node:crypto";
|
|
4866
|
+
import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
|
|
4867
|
+
import { basename, dirname as dirname5, isAbsolute, join as join7, relative, resolve as resolve2, sep } from "node:path";
|
|
4607
4868
|
var FILE_MODE = 384;
|
|
4608
4869
|
var DIRECTORY_MODE = 448;
|
|
4609
4870
|
async function writePushedFile(request) {
|
|
@@ -4636,7 +4897,7 @@ async function writePushedFile(request) {
|
|
|
4636
4897
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
4637
4898
|
dirname5(candidate)
|
|
4638
4899
|
);
|
|
4639
|
-
const realTarget =
|
|
4900
|
+
const realTarget = join7(existingAncestor, ...missingSegments, basename(candidate));
|
|
4640
4901
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
4641
4902
|
if (allowedDirectory === null) {
|
|
4642
4903
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -4672,7 +4933,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
4672
4933
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
4673
4934
|
return null;
|
|
4674
4935
|
}
|
|
4675
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4936
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
4676
4937
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
4677
4938
|
return null;
|
|
4678
4939
|
}
|
|
@@ -4745,16 +5006,16 @@ function contains(realDirectory, realTarget) {
|
|
|
4745
5006
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
4746
5007
|
let current = existingAncestor;
|
|
4747
5008
|
for (const segment of missingSegments) {
|
|
4748
|
-
current =
|
|
5009
|
+
current = join7(current, segment);
|
|
4749
5010
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
4750
5011
|
await chmod(current, DIRECTORY_MODE);
|
|
4751
5012
|
}
|
|
4752
5013
|
}
|
|
4753
5014
|
async function writeAtomically(realTarget, content) {
|
|
4754
|
-
const temporaryPath =
|
|
5015
|
+
const temporaryPath = join7(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
4755
5016
|
let handle;
|
|
4756
5017
|
try {
|
|
4757
|
-
handle = await
|
|
5018
|
+
handle = await open3(temporaryPath, "wx", FILE_MODE);
|
|
4758
5019
|
await handle.writeFile(content);
|
|
4759
5020
|
await handle.chmod(FILE_MODE);
|
|
4760
5021
|
await handle.close();
|
|
@@ -4881,12 +5142,12 @@ var NOT_APPLIED = {
|
|
|
4881
5142
|
opencodeAuthApplied: false
|
|
4882
5143
|
};
|
|
4883
5144
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
4884
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4885
|
-
return expanded ===
|
|
5145
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
5146
|
+
return expanded === join8(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
4886
5147
|
}
|
|
4887
5148
|
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
4888
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4889
|
-
return expanded ===
|
|
5149
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
5150
|
+
return expanded === join8(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
4890
5151
|
}
|
|
4891
5152
|
async function applyOne(options, file) {
|
|
4892
5153
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -5046,6 +5307,10 @@ var DEFAULT_RETRY_POLICY = {
|
|
|
5046
5307
|
baseDelayMs: 500,
|
|
5047
5308
|
maxDelayMs: 3e4
|
|
5048
5309
|
};
|
|
5310
|
+
var SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS = 2e3;
|
|
5311
|
+
var SESSION_ERROR_STREAM_HEALTHY_MS = 5e3;
|
|
5312
|
+
var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
5313
|
+
var MAX_BUFFERED_SESSION_ERRORS = 256;
|
|
5049
5314
|
var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
5050
5315
|
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
5051
5316
|
var DEFAULT_STUCK_QUEUED_MS = 6e4;
|
|
@@ -5186,6 +5451,17 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5186
5451
|
* message; it is removed once its in-flight set empties.
|
|
5187
5452
|
*/
|
|
5188
5453
|
watchers = /* @__PURE__ */ new Map();
|
|
5454
|
+
sessionErrorStream = null;
|
|
5455
|
+
/**
|
|
5456
|
+
* Session-error failures currently being reported; entries are empty at rest
|
|
5457
|
+
* because each handoff deletes its id in `finally`.
|
|
5458
|
+
*/
|
|
5459
|
+
sessionErrorHandled = /* @__PURE__ */ new Set();
|
|
5460
|
+
/**
|
|
5461
|
+
* Session errors that arrived before their dispatch was registered. Bounded FIFO
|
|
5462
|
+
* with a short TTL so an unmatched session cannot retain an event indefinitely.
|
|
5463
|
+
*/
|
|
5464
|
+
bufferedSessionErrors = /* @__PURE__ */ new Map();
|
|
5189
5465
|
/**
|
|
5190
5466
|
* AUTHORITATIVE local dedup (WI-3): Evident message ids that have been
|
|
5191
5467
|
* dispatched and are still in-flight. A message in this set is never
|
|
@@ -5369,6 +5645,13 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5369
5645
|
* no watcher) can resolve the title.
|
|
5370
5646
|
*/
|
|
5371
5647
|
sessionTitles = /* @__PURE__ */ new Map();
|
|
5648
|
+
/** One best-effort terminal subagent collection per Evident message id. */
|
|
5649
|
+
subagentInvocationCollections = /* @__PURE__ */ new Map();
|
|
5650
|
+
/**
|
|
5651
|
+
* Early snapshots are only liveness hints; they must not become the terminal
|
|
5652
|
+
* collection when the task parts or child transcript have advanced.
|
|
5653
|
+
*/
|
|
5654
|
+
subagentInvocationPrefetches = /* @__PURE__ */ new Map();
|
|
5372
5655
|
/** Serialises drains so a reconnect during a drain doesn't double-process. */
|
|
5373
5656
|
draining = false;
|
|
5374
5657
|
/**
|
|
@@ -5433,7 +5716,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5433
5716
|
this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
|
|
5434
5717
|
this.now = config.now ?? (() => Date.now());
|
|
5435
5718
|
this.fileSyncDirectories = config.fileSyncDirectories ?? [];
|
|
5436
|
-
this.homeDir = config.homeDir ??
|
|
5719
|
+
this.homeDir = config.homeDir ?? homedir5();
|
|
5437
5720
|
this.maxActiveSessions = config.maxActiveSessions;
|
|
5438
5721
|
this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
|
|
5439
5722
|
this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
|
|
@@ -5583,6 +5866,21 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5583
5866
|
}
|
|
5584
5867
|
return ids;
|
|
5585
5868
|
}
|
|
5869
|
+
/**
|
|
5870
|
+
* OpenCode user-message ids tracked for other Evident messages in a session.
|
|
5871
|
+
* Excluding this message makes an unattributed later row fail safe; a missing
|
|
5872
|
+
* watcher yields no attributions, per `hasLaterSiblingTurnStarted`'s docblock.
|
|
5873
|
+
*/
|
|
5874
|
+
siblingOpencodeMessageIds(watcher, ownEvidentMessageId) {
|
|
5875
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5876
|
+
if (!watcher) return ids;
|
|
5877
|
+
for (const inFlight of watcher.inFlight.values()) {
|
|
5878
|
+
if (inFlight.evidentMessageId !== ownEvidentMessageId) {
|
|
5879
|
+
ids.add(inFlight.opencodeMessageId);
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
return ids;
|
|
5883
|
+
}
|
|
5586
5884
|
/**
|
|
5587
5885
|
* File-pull work, for `run.ts`'s idle accounting (#559).
|
|
5588
5886
|
*
|
|
@@ -5649,6 +5947,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5649
5947
|
*/
|
|
5650
5948
|
stop() {
|
|
5651
5949
|
this.stopped = true;
|
|
5950
|
+
this.sessionErrorStream?.abort.abort();
|
|
5951
|
+
this.sessionErrorStream = null;
|
|
5652
5952
|
}
|
|
5653
5953
|
/**
|
|
5654
5954
|
* The server clears this request when a new MicroVM identity is recorded, so a
|
|
@@ -5723,6 +6023,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5723
6023
|
*/
|
|
5724
6024
|
async processConversation(conv) {
|
|
5725
6025
|
const { sessionId, refusedSessionId, created: sessionCreated } = await this.ensureSession(conv);
|
|
6026
|
+
this.ensureSessionErrorStream();
|
|
5726
6027
|
const messages = await this.getPendingMessages(conv.id);
|
|
5727
6028
|
let dispatched = 0;
|
|
5728
6029
|
let skippedAlreadyDispatched = 0;
|
|
@@ -5795,7 +6096,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5795
6096
|
this.signalDispatchNotStarted(conv, message, "session_existence_unknown");
|
|
5796
6097
|
break;
|
|
5797
6098
|
}
|
|
5798
|
-
const
|
|
6099
|
+
const errorMessage3 = err instanceof Error ? err.message : String(err);
|
|
5799
6100
|
this.sessions.delete(conv.id);
|
|
5800
6101
|
this.supersede(conv.id, sessionId);
|
|
5801
6102
|
this.log({
|
|
@@ -5804,7 +6105,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5804
6105
|
conversation_id: conv.id,
|
|
5805
6106
|
message_id: message.id
|
|
5806
6107
|
});
|
|
5807
|
-
await this.markFailed(conv.id, message.id, null,
|
|
6108
|
+
await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
|
|
5808
6109
|
this.log({
|
|
5809
6110
|
level: "warn",
|
|
5810
6111
|
message: `markFailed PATCH for message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
@@ -5815,7 +6116,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5815
6116
|
});
|
|
5816
6117
|
this.log({
|
|
5817
6118
|
level: "error",
|
|
5818
|
-
message: `Message ${message.id.slice(0, 8)} dispatch failed: ${
|
|
6119
|
+
message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage3}`,
|
|
5819
6120
|
conversation_id: conv.id,
|
|
5820
6121
|
message_id: message.id
|
|
5821
6122
|
});
|
|
@@ -5836,14 +6137,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5836
6137
|
this.unconfirmedDispatchFailures.delete(message.id);
|
|
5837
6138
|
this.sessions.delete(conv.id);
|
|
5838
6139
|
this.supersede(conv.id, sessionId);
|
|
5839
|
-
const
|
|
6140
|
+
const errorMessage3 = `OpenCode accepted this message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
|
|
5840
6141
|
this.log({
|
|
5841
6142
|
level: "error",
|
|
5842
|
-
message:
|
|
6143
|
+
message: errorMessage3,
|
|
5843
6144
|
conversation_id: conv.id,
|
|
5844
6145
|
message_id: message.id
|
|
5845
6146
|
});
|
|
5846
|
-
await this.markFailed(conv.id, message.id, null,
|
|
6147
|
+
await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
|
|
5847
6148
|
this.log({
|
|
5848
6149
|
level: "warn",
|
|
5849
6150
|
message: `markFailed PATCH for message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
@@ -6069,9 +6370,26 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6069
6370
|
if (state === "running" || state === "queued") {
|
|
6070
6371
|
const ongoing = await isSessionOngoing(this.port, sessionId);
|
|
6071
6372
|
if (ongoing === true) {
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6373
|
+
if (state === "queued") {
|
|
6374
|
+
const siblingOcIds = this.siblingOpencodeMessageIds(
|
|
6375
|
+
this.watchers.get(sessionId),
|
|
6376
|
+
message.id
|
|
6377
|
+
);
|
|
6378
|
+
if (hasLaterSiblingTurnStarted(messages, ocId ?? "", siblingOcIds)) {
|
|
6379
|
+
this.log({
|
|
6380
|
+
level: "warn",
|
|
6381
|
+
message: `Re-drive: OpenCode already served a later, different Evident message's turn in session ${sessionId.slice(0, 8)} while message ${message.id.slice(0, 8)} produced no reply \u2014 re-dispatching instead of reattaching to someone else's turn`,
|
|
6382
|
+
conversation_id: conv.id,
|
|
6383
|
+
message_id: message.id
|
|
6384
|
+
});
|
|
6385
|
+
this.clearRedriveUnresolved(message.id);
|
|
6386
|
+
void this.postSignal(conv.id, message.id, "redrive_redispatched");
|
|
6387
|
+
return "dispatch";
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
return this.reattachRedrive(conv, sessionId, message, ocId);
|
|
6391
|
+
}
|
|
6392
|
+
if (ongoing === false) {
|
|
6075
6393
|
if (state === "running" && isAmbiguousFinishPinnedRunning(messages, ocId ?? "")) {
|
|
6076
6394
|
return this.settleRedrive(conv, sessionId, message, ocId, messages, "done");
|
|
6077
6395
|
}
|
|
@@ -6158,16 +6476,37 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6158
6476
|
if (state === "done") {
|
|
6159
6477
|
const title = await this.resolveSessionTitle(sessionId, conv.id);
|
|
6160
6478
|
const usage = messageUsage(messages, ocId ?? "");
|
|
6479
|
+
const usageAgentName = this.usageAgentName(messages, ocId ?? "");
|
|
6480
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
6481
|
+
messages,
|
|
6482
|
+
ocId ?? "",
|
|
6483
|
+
message.id
|
|
6484
|
+
);
|
|
6161
6485
|
this.log({
|
|
6162
6486
|
level: "info",
|
|
6163
6487
|
message: `Re-drive: message ${message.id.slice(0, 8)} completed while its row was wrongly reclaimed to pending \u2014 marking done instead of re-dispatching`,
|
|
6164
6488
|
conversation_id: conv.id,
|
|
6165
6489
|
message_id: message.id
|
|
6166
6490
|
});
|
|
6167
|
-
await this.markDone(
|
|
6491
|
+
await this.markDone(
|
|
6492
|
+
conv.id,
|
|
6493
|
+
message.id,
|
|
6494
|
+
sessionId,
|
|
6495
|
+
ocId,
|
|
6496
|
+
title,
|
|
6497
|
+
usage,
|
|
6498
|
+
usageAgentName,
|
|
6499
|
+
subagentInvocations
|
|
6500
|
+
);
|
|
6168
6501
|
} else {
|
|
6169
6502
|
const error2 = messageError(messages, ocId ?? "") ?? void 0;
|
|
6170
6503
|
const usage = messageUsage(messages, ocId ?? "");
|
|
6504
|
+
const usageAgentName = this.usageAgentName(messages, ocId ?? "");
|
|
6505
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
6506
|
+
messages,
|
|
6507
|
+
ocId ?? "",
|
|
6508
|
+
message.id
|
|
6509
|
+
);
|
|
6171
6510
|
const failure = await this.classifyModelAuthFailure(messages, ocId ?? "");
|
|
6172
6511
|
this.log({
|
|
6173
6512
|
level: "error",
|
|
@@ -6175,7 +6514,16 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6175
6514
|
conversation_id: conv.id,
|
|
6176
6515
|
message_id: message.id
|
|
6177
6516
|
});
|
|
6178
|
-
await this.markFailed(
|
|
6517
|
+
await this.markFailed(
|
|
6518
|
+
conv.id,
|
|
6519
|
+
message.id,
|
|
6520
|
+
sessionId,
|
|
6521
|
+
error2,
|
|
6522
|
+
usage,
|
|
6523
|
+
failure,
|
|
6524
|
+
usageAgentName,
|
|
6525
|
+
subagentInvocations
|
|
6526
|
+
);
|
|
6179
6527
|
}
|
|
6180
6528
|
if (ocId !== null) {
|
|
6181
6529
|
await this.reportSubagentAuthFailures(conv.id, ocId, message.id, messages);
|
|
@@ -6719,6 +7067,24 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6719
7067
|
ambiguousPinnedSinceMs: 0,
|
|
6720
7068
|
ambiguousResolved: false
|
|
6721
7069
|
});
|
|
7070
|
+
const buffered = this.bufferedSessionErrors.get(sessionId);
|
|
7071
|
+
if (!buffered) return;
|
|
7072
|
+
this.bufferedSessionErrors.delete(sessionId);
|
|
7073
|
+
if (this.now() - buffered.receivedAt < SESSION_ERROR_BUFFER_TTL_MS) {
|
|
7074
|
+
this.handleSessionError(buffered.event);
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
bufferSessionError(event) {
|
|
7078
|
+
this.bufferedSessionErrors.delete(event.sessionId);
|
|
7079
|
+
this.bufferedSessionErrors.set(event.sessionId, {
|
|
7080
|
+
event,
|
|
7081
|
+
receivedAt: this.now()
|
|
7082
|
+
});
|
|
7083
|
+
while (this.bufferedSessionErrors.size > MAX_BUFFERED_SESSION_ERRORS) {
|
|
7084
|
+
const oldest = this.bufferedSessionErrors.keys().next().value;
|
|
7085
|
+
if (typeof oldest !== "string") break;
|
|
7086
|
+
this.bufferedSessionErrors.delete(oldest);
|
|
7087
|
+
}
|
|
6722
7088
|
}
|
|
6723
7089
|
/**
|
|
6724
7090
|
* Build a fresh `SessionWatcher`. Seeds `lastTickAt`/`lastObservedTickAt`
|
|
@@ -6923,6 +7289,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6923
7289
|
ensureWatcherRunning(sessionId) {
|
|
6924
7290
|
const watcher = this.watchers.get(sessionId);
|
|
6925
7291
|
if (!watcher) return;
|
|
7292
|
+
this.ensureSessionErrorStream();
|
|
6926
7293
|
if (watcher.loop) return;
|
|
6927
7294
|
if (watcher.inFlight.size === 0) {
|
|
6928
7295
|
this.watchers.delete(sessionId);
|
|
@@ -6938,6 +7305,154 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6938
7305
|
});
|
|
6939
7306
|
watcher.loop = loop;
|
|
6940
7307
|
}
|
|
7308
|
+
ensureSessionErrorStream() {
|
|
7309
|
+
if (this.sessionErrorStream || this.stopped) return;
|
|
7310
|
+
const abort = new AbortController();
|
|
7311
|
+
const loop = this.runSessionErrorStream(abort.signal);
|
|
7312
|
+
this.sessionErrorStream = { abort, loop };
|
|
7313
|
+
}
|
|
7314
|
+
async runSessionErrorStream(signal) {
|
|
7315
|
+
let attempt = 0;
|
|
7316
|
+
let warned = false;
|
|
7317
|
+
while (!this.stopped && !signal.aborted) {
|
|
7318
|
+
const openedAt = this.now();
|
|
7319
|
+
try {
|
|
7320
|
+
const outcome = await readSessionErrorStream(this.port, {
|
|
7321
|
+
signal,
|
|
7322
|
+
onSessionError: (event) => this.handleSessionError(event)
|
|
7323
|
+
});
|
|
7324
|
+
if (outcome.reason === "aborted" || signal.aborted) return;
|
|
7325
|
+
const healthy = this.now() - openedAt >= SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
7326
|
+
if (outcome.reason === "unavailable" || outcome.reason === "ended") {
|
|
7327
|
+
if (!healthy) {
|
|
7328
|
+
const detail = outcome.reason === "unavailable" ? outcome.detail : "stream ended";
|
|
7329
|
+
this.log({
|
|
7330
|
+
level: warned ? "debug" : "warn",
|
|
7331
|
+
message: `OpenCode session-error stream ${warned ? "still unavailable" : "unavailable"} (${detail}); transcript polling remains the evidence path`
|
|
7332
|
+
});
|
|
7333
|
+
warned = true;
|
|
7334
|
+
}
|
|
7335
|
+
}
|
|
7336
|
+
if (healthy) {
|
|
7337
|
+
if (warned) {
|
|
7338
|
+
this.log({
|
|
7339
|
+
level: "info",
|
|
7340
|
+
message: "OpenCode session-error stream reconnected; transcript polling remains the evidence path"
|
|
7341
|
+
});
|
|
7342
|
+
warned = false;
|
|
7343
|
+
}
|
|
7344
|
+
attempt = 0;
|
|
7345
|
+
} else {
|
|
7346
|
+
attempt += 1;
|
|
7347
|
+
}
|
|
7348
|
+
if (this.stopped || signal.aborted) return;
|
|
7349
|
+
await this.sleep(backoffDelay(healthy ? 0 : attempt - 1, this.retry));
|
|
7350
|
+
} catch (err) {
|
|
7351
|
+
if (this.stopped || signal.aborted) return;
|
|
7352
|
+
this.log({
|
|
7353
|
+
level: "error",
|
|
7354
|
+
message: `OpenCode session-error stream failed unexpectedly: ${err instanceof Error ? err.message : String(err)}`
|
|
7355
|
+
});
|
|
7356
|
+
const healthy = this.now() - openedAt >= SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
7357
|
+
const delayAttempt = healthy ? 0 : attempt;
|
|
7358
|
+
attempt = healthy ? 0 : attempt + 1;
|
|
7359
|
+
try {
|
|
7360
|
+
await this.sleep(backoffDelay(delayAttempt, this.retry));
|
|
7361
|
+
} catch (sleepErr) {
|
|
7362
|
+
this.log({
|
|
7363
|
+
level: "error",
|
|
7364
|
+
message: `OpenCode session-error stream backoff failed unexpectedly: ${sleepErr instanceof Error ? sleepErr.message : String(sleepErr)}`
|
|
7365
|
+
});
|
|
7366
|
+
}
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7369
|
+
}
|
|
7370
|
+
handleSessionError(event) {
|
|
7371
|
+
try {
|
|
7372
|
+
const watcher = this.watchers.get(event.sessionId);
|
|
7373
|
+
if (!watcher) {
|
|
7374
|
+
this.bufferSessionError(event);
|
|
7375
|
+
this.log({
|
|
7376
|
+
level: "debug",
|
|
7377
|
+
message: `Ignoring session error for unknown session ${event.sessionId.slice(0, 8)}`
|
|
7378
|
+
});
|
|
7379
|
+
return;
|
|
7380
|
+
}
|
|
7381
|
+
if ([...watcher.inFlight.values()].some((message) => message.started && !message.done)) {
|
|
7382
|
+
this.log({
|
|
7383
|
+
level: "debug",
|
|
7384
|
+
message: `A turn is already running in session ${event.sessionId.slice(0, 8)} \u2014 deferring to transcript polling`,
|
|
7385
|
+
conversation_id: watcher.conv.id
|
|
7386
|
+
});
|
|
7387
|
+
return;
|
|
7388
|
+
}
|
|
7389
|
+
const inFlight = [...watcher.inFlight.values()].filter((message) => !message.started && !message.done).sort((a, b) => a.dispatchedAt - b.dispatchedAt)[0];
|
|
7390
|
+
if (!inFlight) {
|
|
7391
|
+
this.bufferSessionError(event);
|
|
7392
|
+
this.log({
|
|
7393
|
+
level: "debug",
|
|
7394
|
+
message: `No queued in-flight turn to correlate with session error in ${event.sessionId.slice(0, 8)}`,
|
|
7395
|
+
conversation_id: watcher.conv.id
|
|
7396
|
+
});
|
|
7397
|
+
return;
|
|
7398
|
+
}
|
|
7399
|
+
if (this.sessionErrorHandled.has(inFlight.evidentMessageId)) return;
|
|
7400
|
+
this.sessionErrorHandled.add(inFlight.evidentMessageId);
|
|
7401
|
+
void this.failFromSessionError(watcher, event, inFlight);
|
|
7402
|
+
} catch (err) {
|
|
7403
|
+
this.log({
|
|
7404
|
+
level: "error",
|
|
7405
|
+
message: `Failed to handle OpenCode session error for ${event.sessionId.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`
|
|
7406
|
+
});
|
|
7407
|
+
}
|
|
7408
|
+
}
|
|
7409
|
+
async failFromSessionError(watcher, event, inFlight) {
|
|
7410
|
+
try {
|
|
7411
|
+
const messages = await getSessionMessages(this.port, event.sessionId);
|
|
7412
|
+
const state = messageRunState(messages, inFlight.opencodeMessageId);
|
|
7413
|
+
if (state !== "queued") {
|
|
7414
|
+
this.log({
|
|
7415
|
+
level: "debug",
|
|
7416
|
+
message: `Session error for message ${inFlight.evidentMessageId.slice(0, 8)} observed state ${state}; leaving it to transcript polling`,
|
|
7417
|
+
conversation_id: watcher.conv.id,
|
|
7418
|
+
message_id: inFlight.evidentMessageId
|
|
7419
|
+
});
|
|
7420
|
+
return;
|
|
7421
|
+
}
|
|
7422
|
+
this.log({
|
|
7423
|
+
level: "error",
|
|
7424
|
+
message: `OpenCode could not run message ${inFlight.evidentMessageId.slice(0, 8)} in session ${event.sessionId.slice(0, 8)}: ${event.reason}`,
|
|
7425
|
+
conversation_id: watcher.conv.id,
|
|
7426
|
+
message_id: inFlight.evidentMessageId
|
|
7427
|
+
});
|
|
7428
|
+
await this.markFailed(
|
|
7429
|
+
watcher.conv.id,
|
|
7430
|
+
inFlight.evidentMessageId,
|
|
7431
|
+
event.sessionId,
|
|
7432
|
+
`OpenCode could not run this turn: ${event.reason}`
|
|
7433
|
+
);
|
|
7434
|
+
inFlight.done = true;
|
|
7435
|
+
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
7436
|
+
} catch (err) {
|
|
7437
|
+
if (err instanceof ChannelAuthError) {
|
|
7438
|
+
this.log({
|
|
7439
|
+
level: "warn",
|
|
7440
|
+
message: `OpenCode session error could not mark message ${inFlight.evidentMessageId.slice(0, 8)} failed because authentication failed: ${err.message}; leaving it to transcript polling / the existing give-up path`,
|
|
7441
|
+
conversation_id: watcher.conv.id,
|
|
7442
|
+
message_id: inFlight.evidentMessageId
|
|
7443
|
+
});
|
|
7444
|
+
} else {
|
|
7445
|
+
this.log({
|
|
7446
|
+
level: "warn",
|
|
7447
|
+
message: `OpenCode session error could not mark message ${inFlight.evidentMessageId.slice(0, 8)} failed: ${err instanceof Error ? err.message : String(err)}; leaving it to transcript polling / the existing give-up path`,
|
|
7448
|
+
conversation_id: watcher.conv.id,
|
|
7449
|
+
message_id: inFlight.evidentMessageId
|
|
7450
|
+
});
|
|
7451
|
+
}
|
|
7452
|
+
} finally {
|
|
7453
|
+
this.sessionErrorHandled.delete(inFlight.evidentMessageId);
|
|
7454
|
+
}
|
|
7455
|
+
}
|
|
6941
7456
|
/**
|
|
6942
7457
|
* The per-session polling loop (WI-3). Once per tick it:
|
|
6943
7458
|
* 1. polls `GET /session/:id/message` once and, per in-flight message,
|
|
@@ -7050,6 +7565,21 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7050
7565
|
const conv = watcher.conv;
|
|
7051
7566
|
const state = messageRunState(messages, inFlight.opencodeMessageId);
|
|
7052
7567
|
const id = inFlight.evidentMessageId;
|
|
7568
|
+
if (messages && collectTaskCalls(messages, inFlight.opencodeMessageId).length > 0 && !this.subagentInvocationPrefetches.has(id)) {
|
|
7569
|
+
void this.resolveSubagentInvocations(
|
|
7570
|
+
messages,
|
|
7571
|
+
inFlight.opencodeMessageId,
|
|
7572
|
+
id,
|
|
7573
|
+
"prefetch"
|
|
7574
|
+
).catch((err) => {
|
|
7575
|
+
this.log({
|
|
7576
|
+
level: "warn",
|
|
7577
|
+
message: `Best-effort subagent usage prefetch failed for message ${id.slice(0, 8)} \u2014 omitting invocation telemetry: ${err instanceof Error ? err.message : String(err)}`,
|
|
7578
|
+
conversation_id: conv.id,
|
|
7579
|
+
message_id: id
|
|
7580
|
+
});
|
|
7581
|
+
});
|
|
7582
|
+
}
|
|
7053
7583
|
if (openQuestions.has(id)) inFlight.pausedOnQuestion = true;
|
|
7054
7584
|
else if (questionsPolledOk) inFlight.pausedOnQuestion = false;
|
|
7055
7585
|
if (openPermissions.has(id)) inFlight.pausedOnPermission = true;
|
|
@@ -7103,6 +7633,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7103
7633
|
message_id: inFlight.evidentMessageId
|
|
7104
7634
|
});
|
|
7105
7635
|
const usage = messageUsage(messages, inFlight.opencodeMessageId);
|
|
7636
|
+
const usageAgentName = this.usageAgentName(messages ?? [], inFlight.opencodeMessageId);
|
|
7637
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
7638
|
+
messages,
|
|
7639
|
+
inFlight.opencodeMessageId,
|
|
7640
|
+
inFlight.evidentMessageId
|
|
7641
|
+
);
|
|
7106
7642
|
const failure = await this.classifyModelAuthFailure(messages, inFlight.opencodeMessageId);
|
|
7107
7643
|
try {
|
|
7108
7644
|
await this.markFailed(
|
|
@@ -7111,7 +7647,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7111
7647
|
sessionId,
|
|
7112
7648
|
error2,
|
|
7113
7649
|
usage,
|
|
7114
|
-
failure
|
|
7650
|
+
failure,
|
|
7651
|
+
usageAgentName,
|
|
7652
|
+
subagentInvocations
|
|
7115
7653
|
);
|
|
7116
7654
|
} catch (err) {
|
|
7117
7655
|
if (err instanceof ChannelAuthError) throw err;
|
|
@@ -7154,9 +7692,11 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7154
7692
|
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
7155
7693
|
return;
|
|
7156
7694
|
}
|
|
7695
|
+
const siblingOcIds = this.siblingOpencodeMessageIds(watcher, inFlight.evidentMessageId);
|
|
7696
|
+
const skippedByOpencode = state === "queued" && hasLaterSiblingTurnStarted(messages, inFlight.opencodeMessageId, siblingOcIds);
|
|
7157
7697
|
const pastStuckBound = this.now() - inFlight.dispatchedAt >= this.stuckQueuedMs;
|
|
7158
7698
|
const sessionIdle = state === "queued" && !hasRunningAssistantExcept(messages, inFlight.opencodeMessageId);
|
|
7159
|
-
if (state === "queued" && pastStuckBound && sessionIdle && !inFlight.stuckReported) {
|
|
7699
|
+
if (state === "queued" && pastStuckBound && (sessionIdle || skippedByOpencode) && !inFlight.stuckReported) {
|
|
7160
7700
|
inFlight.stuckReported = true;
|
|
7161
7701
|
void this.postSignal(conv.id, inFlight.evidentMessageId, "stuck_queued", {
|
|
7162
7702
|
stuck_for_ms: this.now() - inFlight.dispatchedAt
|
|
@@ -7317,7 +7857,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7317
7857
|
const hasActivelyRunningSibling = [...watcher.inFlight.values()].some(
|
|
7318
7858
|
(sib) => sib.evidentMessageId !== inFlight.evidentMessageId && messageRunState(messages, sib.opencodeMessageId) === "running" && !siblingPaused(sib)
|
|
7319
7859
|
);
|
|
7320
|
-
const queuedBehindRunningSibling = state === "queued" && hasActivelyRunningSibling;
|
|
7860
|
+
const queuedBehindRunningSibling = state === "queued" && hasActivelyRunningSibling && !skippedByOpencode;
|
|
7321
7861
|
if (!activelyRunning && !queuedBehindRunningSibling && this.now() >= inFlight.deadline) {
|
|
7322
7862
|
this.log({
|
|
7323
7863
|
level: "debug",
|
|
@@ -7352,6 +7892,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7352
7892
|
});
|
|
7353
7893
|
const title = await this.resolveSessionTitle(sessionId, watcher.conv.id);
|
|
7354
7894
|
const usage = messageUsage(messages, inFlight.opencodeMessageId);
|
|
7895
|
+
const usageAgentName = this.usageAgentName(messages ?? [], inFlight.opencodeMessageId);
|
|
7896
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
7897
|
+
messages,
|
|
7898
|
+
inFlight.opencodeMessageId,
|
|
7899
|
+
inFlight.evidentMessageId
|
|
7900
|
+
);
|
|
7355
7901
|
try {
|
|
7356
7902
|
await this.markDone(
|
|
7357
7903
|
conv.id,
|
|
@@ -7359,7 +7905,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7359
7905
|
sessionId,
|
|
7360
7906
|
inFlight.opencodeMessageId,
|
|
7361
7907
|
title,
|
|
7362
|
-
usage
|
|
7908
|
+
usage,
|
|
7909
|
+
usageAgentName,
|
|
7910
|
+
subagentInvocations
|
|
7363
7911
|
);
|
|
7364
7912
|
} catch (err) {
|
|
7365
7913
|
if (err instanceof ChannelAuthError) throw err;
|
|
@@ -7538,6 +8086,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7538
8086
|
if (state === "failed" && !restartAborted) {
|
|
7539
8087
|
const error2 = messageError(messages, ocId ?? "") ?? void 0;
|
|
7540
8088
|
const usage = messageUsage(messages, ocId ?? "");
|
|
8089
|
+
const usageAgentName = this.usageAgentName(messages, ocId ?? "");
|
|
8090
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
8091
|
+
messages,
|
|
8092
|
+
ocId ?? "",
|
|
8093
|
+
row.id
|
|
8094
|
+
);
|
|
7541
8095
|
const failure = await this.classifyModelAuthFailure(messages, ocId ?? "");
|
|
7542
8096
|
this.log({
|
|
7543
8097
|
level: "error",
|
|
@@ -7546,7 +8100,16 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7546
8100
|
message_id: row.id
|
|
7547
8101
|
});
|
|
7548
8102
|
try {
|
|
7549
|
-
await this.markFailed(
|
|
8103
|
+
await this.markFailed(
|
|
8104
|
+
row.conversation_id,
|
|
8105
|
+
row.id,
|
|
8106
|
+
sessionId,
|
|
8107
|
+
error2,
|
|
8108
|
+
usage,
|
|
8109
|
+
failure,
|
|
8110
|
+
usageAgentName,
|
|
8111
|
+
subagentInvocations
|
|
8112
|
+
);
|
|
7550
8113
|
} catch (err) {
|
|
7551
8114
|
if (err instanceof ChannelAuthError) throw err;
|
|
7552
8115
|
if (err instanceof ChannelTerminalError) {
|
|
@@ -7708,7 +8271,22 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7708
8271
|
try {
|
|
7709
8272
|
const title = await this.resolveSessionTitle(sessionId, row.conversation_id);
|
|
7710
8273
|
const usage = messageUsage(messages, ocId ?? "");
|
|
7711
|
-
|
|
8274
|
+
const usageAgentName = this.usageAgentName(messages, ocId ?? "");
|
|
8275
|
+
const subagentInvocations = await this.resolveSubagentInvocations(
|
|
8276
|
+
messages,
|
|
8277
|
+
ocId ?? "",
|
|
8278
|
+
row.id
|
|
8279
|
+
);
|
|
8280
|
+
await this.markDone(
|
|
8281
|
+
row.conversation_id,
|
|
8282
|
+
row.id,
|
|
8283
|
+
sessionId,
|
|
8284
|
+
ocId,
|
|
8285
|
+
title,
|
|
8286
|
+
usage,
|
|
8287
|
+
usageAgentName,
|
|
8288
|
+
subagentInvocations
|
|
8289
|
+
);
|
|
7712
8290
|
} catch (err) {
|
|
7713
8291
|
if (err instanceof ChannelAuthError) throw err;
|
|
7714
8292
|
if (err instanceof ChannelTerminalError) {
|
|
@@ -7826,14 +8404,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7826
8404
|
this.unconfirmedDispatchFailures.delete(row.id);
|
|
7827
8405
|
this.sessions.delete(readoptConv.id);
|
|
7828
8406
|
this.supersede(readoptConv.id, sessionId);
|
|
7829
|
-
const
|
|
8407
|
+
const errorMessage3 = `OpenCode accepted this re-dispatched message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
|
|
7830
8408
|
this.log({
|
|
7831
8409
|
level: "error",
|
|
7832
|
-
message:
|
|
8410
|
+
message: errorMessage3,
|
|
7833
8411
|
conversation_id: row.conversation_id,
|
|
7834
8412
|
message_id: row.id
|
|
7835
8413
|
});
|
|
7836
|
-
await this.markFailed(row.conversation_id, row.id, null,
|
|
8414
|
+
await this.markFailed(row.conversation_id, row.id, null, errorMessage3).catch((markErr) => {
|
|
7837
8415
|
this.log({
|
|
7838
8416
|
level: "warn",
|
|
7839
8417
|
message: `markFailed PATCH for message ${row.id.slice(0, 8)} (conversation ${row.conversation_id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
@@ -8112,6 +8690,166 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8112
8690
|
if (parent !== void 0) this.sessionParents.set(sessionId, parent);
|
|
8113
8691
|
return parent;
|
|
8114
8692
|
}
|
|
8693
|
+
usageAgentName(messages, userMessageId) {
|
|
8694
|
+
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
8695
|
+
const mode = reply?.info?.mode;
|
|
8696
|
+
if (typeof mode === "string" && mode.length > 0) return mode;
|
|
8697
|
+
const agent = reply?.info?.agent;
|
|
8698
|
+
return typeof agent === "string" && agent.length > 0 ? agent : null;
|
|
8699
|
+
}
|
|
8700
|
+
async resolveSubagentInvocations(messages, userMessageId, messageId, phase = "terminal") {
|
|
8701
|
+
if (!messages) return void 0;
|
|
8702
|
+
const cache = phase === "prefetch" ? this.subagentInvocationPrefetches : this.subagentInvocationCollections;
|
|
8703
|
+
const cached = cache.get(messageId);
|
|
8704
|
+
if (cached) return cached;
|
|
8705
|
+
const collection = this.buildSubagentInvocations(messages, userMessageId, messageId).catch(
|
|
8706
|
+
(err) => {
|
|
8707
|
+
this.log({
|
|
8708
|
+
level: "warn",
|
|
8709
|
+
message: `Best-effort subagent usage collection failed for message ${messageId.slice(0, 8)} \u2014 omitting invocation telemetry: ${err instanceof Error ? err.message : String(err)}`,
|
|
8710
|
+
message_id: messageId
|
|
8711
|
+
});
|
|
8712
|
+
return void 0;
|
|
8713
|
+
}
|
|
8714
|
+
);
|
|
8715
|
+
cache.set(messageId, collection);
|
|
8716
|
+
const result = await collection;
|
|
8717
|
+
if (result === void 0 && cache.get(messageId) === collection) cache.delete(messageId);
|
|
8718
|
+
return result;
|
|
8719
|
+
}
|
|
8720
|
+
clearSubagentInvocationCaches(messageId) {
|
|
8721
|
+
this.subagentInvocationCollections.delete(messageId);
|
|
8722
|
+
this.subagentInvocationPrefetches.delete(messageId);
|
|
8723
|
+
}
|
|
8724
|
+
async buildSubagentInvocations(messages, userMessageId, messageId) {
|
|
8725
|
+
const rootCalls = collectTaskCalls(messages, userMessageId);
|
|
8726
|
+
if (rootCalls.length === 0) return void 0;
|
|
8727
|
+
const childMessages = /* @__PURE__ */ new Map();
|
|
8728
|
+
const seenCallIds = new Set(rootCalls.map((call) => call.callID));
|
|
8729
|
+
const work = rootCalls.map((call) => ({
|
|
8730
|
+
call,
|
|
8731
|
+
depth: 1
|
|
8732
|
+
}));
|
|
8733
|
+
const payload = [];
|
|
8734
|
+
const fetchChildMessages = (sessionId) => {
|
|
8735
|
+
const cached = childMessages.get(sessionId);
|
|
8736
|
+
if (cached) return cached;
|
|
8737
|
+
const pending = (async () => {
|
|
8738
|
+
try {
|
|
8739
|
+
const res = await this.fetchImpl(`${this.opencodeBase}/session/${sessionId}/message`);
|
|
8740
|
+
if (!res.ok) {
|
|
8741
|
+
this.log({
|
|
8742
|
+
level: "warn",
|
|
8743
|
+
message: `Best-effort subagent session fetch for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} returned HTTP ${res.status} \u2014 omitting invocation telemetry`,
|
|
8744
|
+
message_id: messageId
|
|
8745
|
+
});
|
|
8746
|
+
return null;
|
|
8747
|
+
}
|
|
8748
|
+
const body = await res.json();
|
|
8749
|
+
if (!Array.isArray(body)) throw new Error("response body was not a message array");
|
|
8750
|
+
return body;
|
|
8751
|
+
} catch (err) {
|
|
8752
|
+
this.log({
|
|
8753
|
+
level: "warn",
|
|
8754
|
+
message: `Best-effort subagent session fetch failed for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} \u2014 omitting invocation telemetry: ${err instanceof Error ? err.message : String(err)}`,
|
|
8755
|
+
message_id: messageId
|
|
8756
|
+
});
|
|
8757
|
+
return null;
|
|
8758
|
+
}
|
|
8759
|
+
})();
|
|
8760
|
+
childMessages.set(sessionId, pending);
|
|
8761
|
+
return pending;
|
|
8762
|
+
};
|
|
8763
|
+
const fetchChildWithoutBlocking = async (sessionId) => {
|
|
8764
|
+
const pending = fetchChildMessages(sessionId);
|
|
8765
|
+
let timer;
|
|
8766
|
+
const timeout = new Promise((resolve4) => {
|
|
8767
|
+
timer = setTimeout(() => {
|
|
8768
|
+
this.log({
|
|
8769
|
+
level: "warn",
|
|
8770
|
+
message: `Best-effort subagent session fetch for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} was slow \u2014 omitting invocation telemetry without delaying completion`,
|
|
8771
|
+
message_id: messageId
|
|
8772
|
+
});
|
|
8773
|
+
resolve4(null);
|
|
8774
|
+
}, SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS);
|
|
8775
|
+
});
|
|
8776
|
+
try {
|
|
8777
|
+
return await Promise.race([pending, timeout]);
|
|
8778
|
+
} finally {
|
|
8779
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
8780
|
+
}
|
|
8781
|
+
};
|
|
8782
|
+
while (work.length > 0) {
|
|
8783
|
+
const groups = /* @__PURE__ */ new Map();
|
|
8784
|
+
for (const item of work.splice(0)) {
|
|
8785
|
+
const group = groups.get(item.call.childSessionId) ?? [];
|
|
8786
|
+
group.push(item);
|
|
8787
|
+
groups.set(item.call.childSessionId, group);
|
|
8788
|
+
}
|
|
8789
|
+
const groupResults = await Promise.all(
|
|
8790
|
+
[...groups].map(async ([sessionId, items]) => ({
|
|
8791
|
+
sessionId,
|
|
8792
|
+
items,
|
|
8793
|
+
messages: sessionId === null ? [] : await fetchChildWithoutBlocking(sessionId)
|
|
8794
|
+
}))
|
|
8795
|
+
);
|
|
8796
|
+
for (const { sessionId, items, messages: child } of groupResults) {
|
|
8797
|
+
if (sessionId !== null && child === null) continue;
|
|
8798
|
+
const attribution = sessionId === null ? { invocations: [], unattributed: [] } : attributeTaskCallUsage(
|
|
8799
|
+
child,
|
|
8800
|
+
items.map(({ call }) => ({
|
|
8801
|
+
callID: call.callID,
|
|
8802
|
+
timeStart: call.timeStart,
|
|
8803
|
+
timeEnd: call.timeEnd
|
|
8804
|
+
}))
|
|
8805
|
+
);
|
|
8806
|
+
if (sessionId !== null && attribution.unattributed.length > 0) {
|
|
8807
|
+
this.log({
|
|
8808
|
+
level: "warn",
|
|
8809
|
+
message: `Omitted ${attribution.unattributed.length} unattributable assistant message(s) from subagent usage for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} \u2014 assigned to no invocation window`,
|
|
8810
|
+
message_id: messageId
|
|
8811
|
+
});
|
|
8812
|
+
}
|
|
8813
|
+
const usageByCall = new Map(
|
|
8814
|
+
attribution.invocations.map((invocation) => [invocation.callID, invocation.usage])
|
|
8815
|
+
);
|
|
8816
|
+
const messagesByCall = new Map(
|
|
8817
|
+
attribution.invocations.map((invocation) => [invocation.callID, invocation.messages])
|
|
8818
|
+
);
|
|
8819
|
+
for (const { call, depth } of items) {
|
|
8820
|
+
const usage = usageByCall.get(call.callID) ?? null;
|
|
8821
|
+
payload.push({
|
|
8822
|
+
tool_call_id: call.callID,
|
|
8823
|
+
agent_name: call.subagentName,
|
|
8824
|
+
opencode_session_id: call.childSessionId,
|
|
8825
|
+
parent_opencode_session_id: call.parentSessionId,
|
|
8826
|
+
depth,
|
|
8827
|
+
status: call.status,
|
|
8828
|
+
started_at: call.timeStart === null ? null : new Date(call.timeStart).toISOString(),
|
|
8829
|
+
ended_at: call.timeEnd === null ? null : new Date(call.timeEnd).toISOString(),
|
|
8830
|
+
usage_provider_id: usage?.usage_provider_id ?? call.model?.providerID ?? null,
|
|
8831
|
+
usage_model_id: usage?.usage_model_id ?? call.model?.modelID ?? null,
|
|
8832
|
+
usage_tokens_input: usage?.usage_tokens_input ?? null,
|
|
8833
|
+
usage_tokens_output: usage?.usage_tokens_output ?? null,
|
|
8834
|
+
usage_tokens_reasoning: usage?.usage_tokens_reasoning ?? null,
|
|
8835
|
+
usage_tokens_cache_read: usage?.usage_tokens_cache_read ?? null,
|
|
8836
|
+
usage_tokens_cache_write: usage?.usage_tokens_cache_write ?? null,
|
|
8837
|
+
usage_cost_usd: usage?.usage_cost_usd ?? null
|
|
8838
|
+
});
|
|
8839
|
+
for (const assigned of messagesByCall.get(call.callID) ?? []) {
|
|
8840
|
+
const parentId = assigned.info?.parentID ?? assigned.parentID;
|
|
8841
|
+
if (!parentId) continue;
|
|
8842
|
+
for (const nested of collectTaskCalls([assigned], parentId)) {
|
|
8843
|
+
if (seenCallIds.has(nested.callID)) continue;
|
|
8844
|
+
seenCallIds.add(nested.callID);
|
|
8845
|
+
work.push({ call: nested, depth: depth + 1 });
|
|
8846
|
+
}
|
|
8847
|
+
}
|
|
8848
|
+
}
|
|
8849
|
+
}
|
|
8850
|
+
}
|
|
8851
|
+
return payload.length > 0 ? payload : void 0;
|
|
8852
|
+
}
|
|
8115
8853
|
/**
|
|
8116
8854
|
* OpenCode's synchronous default session title (e.g.
|
|
8117
8855
|
* `"New session - 1737800000000"`), assigned immediately when a session is
|
|
@@ -8595,7 +9333,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8595
9333
|
* watcher retries next tick within the
|
|
8596
9334
|
* deadline, Finding 4).
|
|
8597
9335
|
*/
|
|
8598
|
-
async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage) {
|
|
9336
|
+
async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage, usageAgentName, subagentInvocations) {
|
|
8599
9337
|
const res = await this.fetchImpl(
|
|
8600
9338
|
`${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
|
|
8601
9339
|
{
|
|
@@ -8611,15 +9349,21 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8611
9349
|
opencode_session_id: sessionId,
|
|
8612
9350
|
...opencodeMessageId ? { opencode_message_id: opencodeMessageId } : {},
|
|
8613
9351
|
...title ? { title } : {},
|
|
8614
|
-
...usage ? usage : {}
|
|
9352
|
+
...usage ? usage : {},
|
|
9353
|
+
...usageAgentName ? { usage_agent_name: usageAgentName } : {},
|
|
9354
|
+
...subagentInvocations && subagentInvocations.length > 0 ? { subagent_invocations: subagentInvocations } : {}
|
|
8615
9355
|
})
|
|
8616
9356
|
}
|
|
8617
9357
|
);
|
|
8618
9358
|
this.assertAuth(res, "marking message as done");
|
|
8619
|
-
if (res.ok)
|
|
9359
|
+
if (res.ok) {
|
|
9360
|
+
this.clearSubagentInvocationCaches(messageId);
|
|
9361
|
+
return;
|
|
9362
|
+
}
|
|
8620
9363
|
if (isRetryableStatus(res.status)) {
|
|
8621
9364
|
throw new Error(`marking message as done: HTTP ${res.status}`);
|
|
8622
9365
|
}
|
|
9366
|
+
this.clearSubagentInvocationCaches(messageId);
|
|
8623
9367
|
throw new ChannelTerminalError(`marking message as done: HTTP ${res.status}`, res.status);
|
|
8624
9368
|
}
|
|
8625
9369
|
/**
|
|
@@ -8634,7 +9378,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8634
9378
|
* exists but is wedged, so the next attempt must get a fresh one
|
|
8635
9379
|
* instead of reusing it — see WI-1's server-side null-clearing PATCH).
|
|
8636
9380
|
*/
|
|
8637
|
-
async markFailed(conversationId, messageId, sessionId, error2, usage, failure) {
|
|
9381
|
+
async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations) {
|
|
8638
9382
|
const body = { status: "failed" };
|
|
8639
9383
|
if (sessionId === null) {
|
|
8640
9384
|
body.opencode_session_id = null;
|
|
@@ -8643,23 +9387,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8643
9387
|
}
|
|
8644
9388
|
if (error2 !== void 0) body.error = error2;
|
|
8645
9389
|
if (usage) Object.assign(body, usage);
|
|
9390
|
+
if (usageAgentName) body.usage_agent_name = usageAgentName;
|
|
9391
|
+
if (subagentInvocations && subagentInvocations.length > 0) {
|
|
9392
|
+
body.subagent_invocations = subagentInvocations;
|
|
9393
|
+
}
|
|
8646
9394
|
if (failure) {
|
|
8647
9395
|
body.failure_kind = failure.kind;
|
|
8648
9396
|
body.failure_provider_id = failure.providerId;
|
|
8649
9397
|
body.failure_model_id = failure.modelId;
|
|
8650
9398
|
body.failure_reason = failure.reason;
|
|
8651
9399
|
}
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
9400
|
+
try {
|
|
9401
|
+
await this.callWithRetry(
|
|
9402
|
+
"marking message as failed",
|
|
9403
|
+
() => this.fetchImpl(
|
|
9404
|
+
`${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
|
|
9405
|
+
{
|
|
9406
|
+
method: "PATCH",
|
|
9407
|
+
headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
|
|
9408
|
+
body: JSON.stringify(body)
|
|
9409
|
+
}
|
|
9410
|
+
)
|
|
9411
|
+
);
|
|
9412
|
+
} catch (err) {
|
|
9413
|
+
if (err instanceof ChannelTerminalError) this.clearSubagentInvocationCaches(messageId);
|
|
9414
|
+
throw err;
|
|
9415
|
+
}
|
|
9416
|
+
this.clearSubagentInvocationCaches(messageId);
|
|
8663
9417
|
}
|
|
8664
9418
|
/**
|
|
8665
9419
|
* Classify an errored turn as a model-auth failure (#736 P1-4), for `markFailed`.
|
|
@@ -8942,6 +9696,13 @@ import chalk5 from "chalk";
|
|
|
8942
9696
|
import ora2 from "ora";
|
|
8943
9697
|
import { select as select2 } from "@inquirer/prompts";
|
|
8944
9698
|
var INTERACTIVE_START_TIMEOUT_MS = 3e4;
|
|
9699
|
+
function checkNonInteractivePortConflict(port, isPortInUseFn) {
|
|
9700
|
+
if (isPortInUseFn(port)) {
|
|
9701
|
+
throw new Error(
|
|
9702
|
+
`Port ${port} is already in use by a non-OpenCode process. Free it or pass --port.`
|
|
9703
|
+
);
|
|
9704
|
+
}
|
|
9705
|
+
}
|
|
8945
9706
|
async function ensureOpenCodeRunning(ctx) {
|
|
8946
9707
|
const healthCheck = await checkOpenCodeHealth(ctx.port);
|
|
8947
9708
|
if (healthCheck.healthy) {
|
|
@@ -8989,6 +9750,7 @@ async function ensureOpenCodeRunning(ctx) {
|
|
|
8989
9750
|
}
|
|
8990
9751
|
}
|
|
8991
9752
|
if (!ctx.interactive) {
|
|
9753
|
+
checkNonInteractivePortConflict(ctx.port, isPortInUse);
|
|
8992
9754
|
ctx.log(`OpenCode is not running on port ${ctx.port}. Starting it automatically...`);
|
|
8993
9755
|
const proc = await startOpenCode(ctx.port, { inheritStdio: ctx.inheritStdio });
|
|
8994
9756
|
const health = await waitForOpenCodeHealth(ctx.port, ctx.startTimeoutMs);
|
|
@@ -9070,9 +9832,119 @@ Port ${port} is already in use.`));
|
|
|
9070
9832
|
return { port, process: null, version: null, notReadyReason: "you chose to continue without it" };
|
|
9071
9833
|
}
|
|
9072
9834
|
|
|
9835
|
+
// src/commands/ensure-opencode-v2.ts
|
|
9836
|
+
import chalk6 from "chalk";
|
|
9837
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
9838
|
+
async function probeOpenCode2WithoutPassword(port) {
|
|
9839
|
+
try {
|
|
9840
|
+
const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
|
|
9841
|
+
signal: AbortSignal.timeout(2e3)
|
|
9842
|
+
});
|
|
9843
|
+
if (response.status === 401) {
|
|
9844
|
+
return { healthy: false, authFailed: true, error: "HTTP 401" };
|
|
9845
|
+
}
|
|
9846
|
+
if (!response.ok) {
|
|
9847
|
+
return { healthy: false, error: `HTTP ${response.status}` };
|
|
9848
|
+
}
|
|
9849
|
+
return { healthy: true };
|
|
9850
|
+
} catch (error2) {
|
|
9851
|
+
return {
|
|
9852
|
+
healthy: false,
|
|
9853
|
+
error: error2 instanceof Error ? error2.message : "Unknown error"
|
|
9854
|
+
};
|
|
9855
|
+
}
|
|
9856
|
+
}
|
|
9857
|
+
function unknownPasswordError(port) {
|
|
9858
|
+
return new Error(
|
|
9859
|
+
`OpenCode V2 (opencode2) is already running on port ${port} with a password this runner doesn't know. Free the port or pass --port.`
|
|
9860
|
+
);
|
|
9861
|
+
}
|
|
9862
|
+
function v2SessionSupportIncompleteError() {
|
|
9863
|
+
return new Error(
|
|
9864
|
+
"OpenCode V2 (opencode2) session support is incomplete \u2014 see https://github.com/sroze/evident/issues/2075. Not starting a new opencode2 process."
|
|
9865
|
+
);
|
|
9866
|
+
}
|
|
9867
|
+
async function ensureOpenCode2Running(ctx) {
|
|
9868
|
+
const initialHealth = await probeOpenCode2WithoutPassword(ctx.port);
|
|
9869
|
+
if (initialHealth.authFailed) {
|
|
9870
|
+
throw unknownPasswordError(ctx.port);
|
|
9871
|
+
}
|
|
9872
|
+
if (initialHealth.healthy) {
|
|
9873
|
+
return {
|
|
9874
|
+
port: ctx.port,
|
|
9875
|
+
process: null,
|
|
9876
|
+
version: null,
|
|
9877
|
+
notReadyReason: null,
|
|
9878
|
+
password: null
|
|
9879
|
+
};
|
|
9880
|
+
}
|
|
9881
|
+
if (!isOpenCode2Installed()) {
|
|
9882
|
+
throw new Error(
|
|
9883
|
+
"OpenCode V2 (opencode2) is not installed. Install it with: npm install -g @opencode-ai/cli@beta"
|
|
9884
|
+
);
|
|
9885
|
+
}
|
|
9886
|
+
let port = ctx.port;
|
|
9887
|
+
if (!ctx.interactive) {
|
|
9888
|
+
checkNonInteractivePortConflict(port, isPortInUse);
|
|
9889
|
+
} else if (isPortInUse(port)) {
|
|
9890
|
+
console.log(chalk6.yellow(`
|
|
9891
|
+
Port ${port} is already in use.`));
|
|
9892
|
+
const alternativePort = findAvailablePort(port + 1);
|
|
9893
|
+
if (alternativePort) {
|
|
9894
|
+
const useAlternative = await select3({
|
|
9895
|
+
message: `Use port ${alternativePort} instead?`,
|
|
9896
|
+
choices: [
|
|
9897
|
+
{ name: `Yes, use port ${alternativePort}`, value: "yes" },
|
|
9898
|
+
{ name: "No, I will free the port manually", value: "no" }
|
|
9899
|
+
]
|
|
9900
|
+
});
|
|
9901
|
+
if (useAlternative === "yes") {
|
|
9902
|
+
port = alternativePort;
|
|
9903
|
+
} else {
|
|
9904
|
+
throw new Error(`Port ${ctx.port} is in use`);
|
|
9905
|
+
}
|
|
9906
|
+
}
|
|
9907
|
+
}
|
|
9908
|
+
if (!ctx.interactive) {
|
|
9909
|
+
throw v2SessionSupportIncompleteError();
|
|
9910
|
+
}
|
|
9911
|
+
console.log(chalk6.yellow(`
|
|
9912
|
+
${v2SessionSupportIncompleteError().message}`));
|
|
9913
|
+
const action = await select3({
|
|
9914
|
+
message: "OpenCode V2 is not running. What would you like to do?",
|
|
9915
|
+
choices: [
|
|
9916
|
+
{
|
|
9917
|
+
name: "Show me the command",
|
|
9918
|
+
value: "manual",
|
|
9919
|
+
description: "Display the command to run manually"
|
|
9920
|
+
},
|
|
9921
|
+
{
|
|
9922
|
+
name: "Continue without OpenCode V2",
|
|
9923
|
+
value: "continue",
|
|
9924
|
+
description: "Requests will fail until OpenCode V2 starts"
|
|
9925
|
+
}
|
|
9926
|
+
]
|
|
9927
|
+
});
|
|
9928
|
+
if (action === "manual") {
|
|
9929
|
+
blank();
|
|
9930
|
+
console.log(chalk6.bold("Run this command in another terminal:"));
|
|
9931
|
+
blank();
|
|
9932
|
+
console.log(` ${chalk6.cyan(`opencode2 serve --port ${port}`)}`);
|
|
9933
|
+
blank();
|
|
9934
|
+
throw new Error("Please start OpenCode V2 manually");
|
|
9935
|
+
}
|
|
9936
|
+
return {
|
|
9937
|
+
port,
|
|
9938
|
+
process: null,
|
|
9939
|
+
version: null,
|
|
9940
|
+
notReadyReason: "you chose to continue without OpenCode V2",
|
|
9941
|
+
password: null
|
|
9942
|
+
};
|
|
9943
|
+
}
|
|
9944
|
+
|
|
9073
9945
|
// src/lib/runner-credentials.ts
|
|
9074
|
-
import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
9075
|
-
import { spawn as spawn5 } from "child_process";
|
|
9946
|
+
import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9947
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
9076
9948
|
var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
|
|
9077
9949
|
var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
|
|
9078
9950
|
var GITHUB_PROBE_TIMEOUT_MS = 1e4;
|
|
@@ -9344,11 +10216,11 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
9344
10216
|
}
|
|
9345
10217
|
|
|
9346
10218
|
// src/lib/opencode/config-overlay.ts
|
|
9347
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
9348
|
-
import { copyFileSync, existsSync as existsSync2, statSync as
|
|
9349
|
-
import { isAbsolute as isAbsolute2, join as
|
|
10219
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
10220
|
+
import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
|
|
10221
|
+
import { isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
|
|
9350
10222
|
function isFile(filePath) {
|
|
9351
|
-
return existsSync2(filePath) &&
|
|
10223
|
+
return existsSync2(filePath) && statSync6(filePath).isFile();
|
|
9352
10224
|
}
|
|
9353
10225
|
function applyRunnerOpenCodeConfig({
|
|
9354
10226
|
overlayPath,
|
|
@@ -9360,7 +10232,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
9360
10232
|
return;
|
|
9361
10233
|
}
|
|
9362
10234
|
const source = isAbsolute2(overlayPath) ? overlayPath : resolve3(cwd, overlayPath);
|
|
9363
|
-
const target = isFile(
|
|
10235
|
+
const target = isFile(join9(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
|
|
9364
10236
|
if (!isFile(source)) {
|
|
9365
10237
|
log3(
|
|
9366
10238
|
`RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
|
|
@@ -9368,7 +10240,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
9368
10240
|
);
|
|
9369
10241
|
return;
|
|
9370
10242
|
}
|
|
9371
|
-
copyFileSync(source,
|
|
10243
|
+
copyFileSync(source, join9(cwd, target));
|
|
9372
10244
|
try {
|
|
9373
10245
|
execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
|
|
9374
10246
|
stdio: "ignore"
|
|
@@ -9377,11 +10249,11 @@ function applyRunnerOpenCodeConfig({
|
|
|
9377
10249
|
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
9378
10250
|
log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
|
|
9379
10251
|
}
|
|
9380
|
-
log3(`Applied runner OpenCode config ${source} to ${
|
|
10252
|
+
log3(`Applied runner OpenCode config ${source} to ${join9(cwd, target)}`);
|
|
9381
10253
|
}
|
|
9382
10254
|
|
|
9383
10255
|
// src/lib/credential-sync.ts
|
|
9384
|
-
import { renameSync, writeFileSync as writeFileSync5 } from "fs";
|
|
10256
|
+
import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9385
10257
|
var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
|
|
9386
10258
|
var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
|
|
9387
10259
|
var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
|
|
@@ -9391,7 +10263,7 @@ var MAX_FLUSH_PASSES = 2;
|
|
|
9391
10263
|
function outcomesWith(outcome) {
|
|
9392
10264
|
return { claude: outcome, opencode: outcome };
|
|
9393
10265
|
}
|
|
9394
|
-
function
|
|
10266
|
+
function errorMessage2(error2) {
|
|
9395
10267
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
9396
10268
|
}
|
|
9397
10269
|
function waitForSettlement(promise, timeoutMs) {
|
|
@@ -9418,7 +10290,7 @@ function writeMarker(markerPath, outcomes, log3) {
|
|
|
9418
10290
|
writeFileSync5(temporaryPath, body, { mode: 384 });
|
|
9419
10291
|
renameSync(temporaryPath, markerPath);
|
|
9420
10292
|
} catch (error2) {
|
|
9421
|
-
log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${
|
|
10293
|
+
log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
|
|
9422
10294
|
}
|
|
9423
10295
|
}
|
|
9424
10296
|
function intervalSeconds(env, log3) {
|
|
@@ -9450,7 +10322,7 @@ async function runFlushStore(store, env, synchroniserRunner, log3, deadlineAt) {
|
|
|
9450
10322
|
},
|
|
9451
10323
|
(error2) => {
|
|
9452
10324
|
failed = true;
|
|
9453
|
-
log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${
|
|
10325
|
+
log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage2(error2)}`, "warn");
|
|
9454
10326
|
}
|
|
9455
10327
|
);
|
|
9456
10328
|
const abortTimer = setTimeout(() => controller.abort(), remainingMs);
|
|
@@ -9510,7 +10382,7 @@ function createCredentialSync({
|
|
|
9510
10382
|
outcomes[store] = !result.timedOut && result.code === 0 ? "ok" : "failed";
|
|
9511
10383
|
} catch (error2) {
|
|
9512
10384
|
outcomes[store] = "failed";
|
|
9513
|
-
log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${
|
|
10385
|
+
log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage2(error2)}`, "debug");
|
|
9514
10386
|
}
|
|
9515
10387
|
}
|
|
9516
10388
|
const failed = STORES.some((store) => outcomes[store] === "failed");
|
|
@@ -9652,7 +10524,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
9652
10524
|
if (trimmed === "") {
|
|
9653
10525
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
9654
10526
|
}
|
|
9655
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
10527
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join10(homeDir, trimmed.slice(2)) : trimmed;
|
|
9656
10528
|
if (!isAbsolute3(expanded)) {
|
|
9657
10529
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
9658
10530
|
}
|
|
@@ -9676,6 +10548,28 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
9676
10548
|
var DEFAULT_OPENCODE_START_TIMEOUT_SECONDS = 180;
|
|
9677
10549
|
var MAX_OPENCODE_START_TIMEOUT_SECONDS = 3600;
|
|
9678
10550
|
var OPENCODE_START_TIMEOUT_ENV = "EVIDENT_OPENCODE_START_TIMEOUT";
|
|
10551
|
+
var OPENCODE_VERSION_ENV = "EVIDENT_OPENCODE_VERSION";
|
|
10552
|
+
function resolveOpenCodeVersion(options, env = process.env) {
|
|
10553
|
+
let raw;
|
|
10554
|
+
let source;
|
|
10555
|
+
if (options.opencodeVersion !== void 0) {
|
|
10556
|
+
raw = options.opencodeVersion;
|
|
10557
|
+
source = "--opencode-version";
|
|
10558
|
+
} else if (env[OPENCODE_VERSION_ENV] !== void 0 && env[OPENCODE_VERSION_ENV] !== "") {
|
|
10559
|
+
raw = env[OPENCODE_VERSION_ENV];
|
|
10560
|
+
source = OPENCODE_VERSION_ENV;
|
|
10561
|
+
} else {
|
|
10562
|
+
return { version: "v1", warnings: [] };
|
|
10563
|
+
}
|
|
10564
|
+
const normalized = raw.trim().toLowerCase();
|
|
10565
|
+
if (normalized !== "v1" && normalized !== "v2") {
|
|
10566
|
+
return {
|
|
10567
|
+
version: "v1",
|
|
10568
|
+
warnings: [`Ignoring invalid ${source} "${raw}": expected v1 or v2; using the default v1`]
|
|
10569
|
+
};
|
|
10570
|
+
}
|
|
10571
|
+
return { version: normalized, warnings: [] };
|
|
10572
|
+
}
|
|
9679
10573
|
function resolveOpenCodeStartTimeoutMs(options, env = process.env) {
|
|
9680
10574
|
const defaultMs = DEFAULT_OPENCODE_START_TIMEOUT_SECONDS * 1e3;
|
|
9681
10575
|
let raw;
|
|
@@ -9742,7 +10636,7 @@ function log2(state, message, level = "info") {
|
|
|
9742
10636
|
})
|
|
9743
10637
|
);
|
|
9744
10638
|
} else if (!state.interactive) {
|
|
9745
|
-
const prefix = level === "error" ?
|
|
10639
|
+
const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
|
|
9746
10640
|
console.log(`${prefix} ${message}`);
|
|
9747
10641
|
}
|
|
9748
10642
|
}
|
|
@@ -9772,7 +10666,7 @@ function logActivity(state, entry) {
|
|
|
9772
10666
|
}
|
|
9773
10667
|
function reportSessionDbRecovery(state) {
|
|
9774
10668
|
try {
|
|
9775
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
10669
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir6(), env: process.env });
|
|
9776
10670
|
for (const record of report.records) {
|
|
9777
10671
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
9778
10672
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -9803,18 +10697,18 @@ function reportSessionDbRecoveryRecord(state, record) {
|
|
|
9803
10697
|
function displayStatus(state) {
|
|
9804
10698
|
if (!state.interactive) return;
|
|
9805
10699
|
const attempt = state.connection?.reconnectAttempt ?? 0;
|
|
9806
|
-
const tunnel = state.connected ?
|
|
9807
|
-
const opencode = state.opencodeConnected ?
|
|
9808
|
-
const messages = state.messageCount > 0 ?
|
|
10700
|
+
const tunnel = state.connected ? chalk7.green("tunnel: connected") : attempt > 0 ? chalk7.yellow(`tunnel: reconnecting (#${attempt})`) : chalk7.yellow("tunnel: connecting");
|
|
10701
|
+
const opencode = state.opencodeConnected ? chalk7.green(`opencode: :${state.port}`) : chalk7.red(`opencode: :${state.port} (down)`);
|
|
10702
|
+
const messages = state.messageCount > 0 ? chalk7.dim(` \xB7 ${state.messageCount} processed`) : "";
|
|
9809
10703
|
const last = state.activityLog[state.activityLog.length - 1];
|
|
9810
|
-
const detail = last ?
|
|
10704
|
+
const detail = last ? chalk7.dim(` \xB7 ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`) : "";
|
|
9811
10705
|
const agent = state.agentName ?? state.agentId;
|
|
9812
10706
|
console.log(
|
|
9813
|
-
`${
|
|
10707
|
+
`${chalk7.bold("Evident")} ${chalk7.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
|
|
9814
10708
|
);
|
|
9815
10709
|
}
|
|
9816
10710
|
async function promptForLogin(promptMessage, successMessage) {
|
|
9817
|
-
const action = await
|
|
10711
|
+
const action = await select4({
|
|
9818
10712
|
message: promptMessage,
|
|
9819
10713
|
choices: [
|
|
9820
10714
|
{
|
|
@@ -9830,7 +10724,7 @@ async function promptForLogin(promptMessage, successMessage) {
|
|
|
9830
10724
|
]
|
|
9831
10725
|
});
|
|
9832
10726
|
if (action === "exit") {
|
|
9833
|
-
console.log(
|
|
10727
|
+
console.log(chalk7.dim(`
|
|
9834
10728
|
You can log in later by running: ${getCliName()} login`));
|
|
9835
10729
|
process.exit(0);
|
|
9836
10730
|
}
|
|
@@ -9841,7 +10735,7 @@ You can log in later by running: ${getCliName()} login`));
|
|
|
9841
10735
|
process.exit(1);
|
|
9842
10736
|
}
|
|
9843
10737
|
blank();
|
|
9844
|
-
console.log(
|
|
10738
|
+
console.log(chalk7.green(successMessage));
|
|
9845
10739
|
blank();
|
|
9846
10740
|
return { token: credentials2.token, authType: "bearer", user: credentials2.user };
|
|
9847
10741
|
}
|
|
@@ -9854,12 +10748,12 @@ async function handleAuthError(state, error2) {
|
|
|
9854
10748
|
if (state.interactive) displayStatus(state);
|
|
9855
10749
|
if (!state.interactive) {
|
|
9856
10750
|
blank();
|
|
9857
|
-
console.log(
|
|
9858
|
-
console.log(
|
|
10751
|
+
console.log(chalk7.red("Authentication expired"));
|
|
10752
|
+
console.log(chalk7.dim("Your authentication token is no longer valid."));
|
|
9859
10753
|
blank();
|
|
9860
|
-
console.log(
|
|
9861
|
-
console.log(
|
|
9862
|
-
console.log(
|
|
10754
|
+
console.log(chalk7.dim("To fix this:"));
|
|
10755
|
+
console.log(chalk7.dim(` 1. Run '${getCliName()} login' to re-authenticate`));
|
|
10756
|
+
console.log(chalk7.dim(" 2. Restart this command"));
|
|
9863
10757
|
blank();
|
|
9864
10758
|
await cleanup(state);
|
|
9865
10759
|
await shutdownTelemetry();
|
|
@@ -9867,7 +10761,7 @@ async function handleAuthError(state, error2) {
|
|
|
9867
10761
|
return { success: false };
|
|
9868
10762
|
}
|
|
9869
10763
|
blank();
|
|
9870
|
-
console.log(
|
|
10764
|
+
console.log(chalk7.yellow("Your authentication has expired."));
|
|
9871
10765
|
blank();
|
|
9872
10766
|
try {
|
|
9873
10767
|
const credentials2 = await promptForLogin(
|
|
@@ -9931,6 +10825,14 @@ async function driveChannels(state, driver) {
|
|
|
9931
10825
|
const opencodeAuthApplied = opencodeAuthApplies !== lastSeenOpencodeAuthApplies;
|
|
9932
10826
|
lastSeenOpencodeAuthApplies = opencodeAuthApplies;
|
|
9933
10827
|
if (opencodeAuthApplied) state.openaiUsageRearm?.();
|
|
10828
|
+
if (claudeCredentialApplied || opencodeAuthApplied) {
|
|
10829
|
+
void reloadProviderCache(state.port).catch(
|
|
10830
|
+
(error2) => logActivity(state, {
|
|
10831
|
+
type: "error",
|
|
10832
|
+
error: `OpenCode provider cache reload failed on port ${state.port}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
10833
|
+
})
|
|
10834
|
+
);
|
|
10835
|
+
}
|
|
9934
10836
|
if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
|
|
9935
10837
|
idlePolls = 0;
|
|
9936
10838
|
idleMs = 0;
|
|
@@ -9958,8 +10860,8 @@ async function driveChannels(state, driver) {
|
|
|
9958
10860
|
state.running = false;
|
|
9959
10861
|
break;
|
|
9960
10862
|
}
|
|
9961
|
-
const
|
|
9962
|
-
logActivity(state, { type: "error", error: `Channel processing error: ${
|
|
10863
|
+
const errorMessage3 = error2 instanceof Error ? error2.message : String(error2);
|
|
10864
|
+
logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage3}` });
|
|
9963
10865
|
if (state.interactive) displayStatus(state);
|
|
9964
10866
|
if (driver.hasInFlightWatchers()) {
|
|
9965
10867
|
consecutiveDrainFailures = 0;
|
|
@@ -9997,9 +10899,18 @@ async function driveChannels(state, driver) {
|
|
|
9997
10899
|
}
|
|
9998
10900
|
}
|
|
9999
10901
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
10000
|
-
var SESSION_DB_RECLAIM_MAX_PAGES =
|
|
10902
|
+
var SESSION_DB_RECLAIM_MAX_PAGES = 2e4;
|
|
10903
|
+
function shouldWarnForReclaimSkip(reason) {
|
|
10904
|
+
if (reason !== "sqlite-unavailable") return false;
|
|
10905
|
+
const version2 = /^v(\d+)\.(\d+)\.(\d+)$/.exec(process.version);
|
|
10906
|
+
if (!version2) return false;
|
|
10907
|
+
const major = Number(version2[1]);
|
|
10908
|
+
const minor = Number(version2[2]);
|
|
10909
|
+
const patch = Number(version2[3]);
|
|
10910
|
+
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
10911
|
+
}
|
|
10001
10912
|
function sessionDbPath() {
|
|
10002
|
-
return
|
|
10913
|
+
return join10(homedir6(), ".local", "share", "opencode", "opencode.db");
|
|
10003
10914
|
}
|
|
10004
10915
|
function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
|
|
10005
10916
|
const record = {
|
|
@@ -10095,7 +11006,7 @@ async function runSweep(state, driver, config) {
|
|
|
10095
11006
|
} else {
|
|
10096
11007
|
logActivity(state, {
|
|
10097
11008
|
type: "info",
|
|
10098
|
-
message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})`
|
|
11009
|
+
message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : "")
|
|
10099
11010
|
});
|
|
10100
11011
|
}
|
|
10101
11012
|
} catch (error2) {
|
|
@@ -10118,13 +11029,20 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
10118
11029
|
for (const warning2 of config.warnings) {
|
|
10119
11030
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
10120
11031
|
}
|
|
10121
|
-
const dbBytes = statSessionDbBytes(
|
|
11032
|
+
const dbBytes = statSessionDbBytes(homedir6());
|
|
10122
11033
|
void (async () => {
|
|
10123
|
-
const
|
|
11034
|
+
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
11035
|
+
if (reclaimAvailability !== null) {
|
|
11036
|
+
logActivity(state, {
|
|
11037
|
+
type: "info",
|
|
11038
|
+
level: shouldWarnForReclaimSkip(reclaimAvailability.reason) ? "warn" : "info",
|
|
11039
|
+
message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : "")
|
|
11040
|
+
});
|
|
11041
|
+
}
|
|
10124
11042
|
const sizeWarning = buildSessionStoreSizeWarning({
|
|
10125
11043
|
dbBytes,
|
|
10126
11044
|
cleanupEnabled: config.enabled,
|
|
10127
|
-
reclaimSkipReason
|
|
11045
|
+
reclaimSkipReason: reclaimAvailability?.reason ?? null
|
|
10128
11046
|
});
|
|
10129
11047
|
if (sizeWarning !== null) {
|
|
10130
11048
|
logActivity(state, { type: "info", level: "warn", message: sizeWarning });
|
|
@@ -10291,14 +11209,11 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
10291
11209
|
report: (usage) => reportClaudeUsage(state.agentId, state.authHeader, usage),
|
|
10292
11210
|
isLocalCredentialProblem,
|
|
10293
11211
|
forcedOnHint: "run `claude` to sign in",
|
|
10294
|
-
firstDelayMs:
|
|
10295
|
-
nextDelayMs:
|
|
10296
|
-
failureLogLevel:
|
|
11212
|
+
firstDelayMs: firstReportDelayMs,
|
|
11213
|
+
nextDelayMs: usageReportDelayMs,
|
|
11214
|
+
failureLogLevel: usageReportFailureLogLevel
|
|
10297
11215
|
});
|
|
10298
11216
|
}
|
|
10299
|
-
var RESOURCE_USAGE_BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
10300
|
-
var RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
10301
|
-
var RESOURCE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
10302
11217
|
function scheduleResourceUsageReporting(state, options) {
|
|
10303
11218
|
const { enabled, warnings } = resolveResourceUsageReportingEnabled(
|
|
10304
11219
|
options.resourceUsageReporting,
|
|
@@ -10319,7 +11234,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
10319
11234
|
});
|
|
10320
11235
|
return;
|
|
10321
11236
|
}
|
|
10322
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
11237
|
+
const { collect, stop } = createResourceUsageCollector(homedir6());
|
|
10323
11238
|
state.stopResourceUsageSampling = stop;
|
|
10324
11239
|
let consecutiveFailures = 0;
|
|
10325
11240
|
const tick = async () => {
|
|
@@ -10351,10 +11266,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
10351
11266
|
consecutiveFailures++;
|
|
10352
11267
|
logActivity(state, {
|
|
10353
11268
|
type: "info",
|
|
10354
|
-
level:
|
|
10355
|
-
consecutiveFailures,
|
|
10356
|
-
RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
|
|
10357
|
-
),
|
|
11269
|
+
level: usageReportFailureLogLevel(consecutiveFailures),
|
|
10358
11270
|
message: `Failed to report resource usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
|
|
10359
11271
|
});
|
|
10360
11272
|
}
|
|
@@ -10363,20 +11275,11 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
10363
11275
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
10364
11276
|
logActivity(state, {
|
|
10365
11277
|
type: "info",
|
|
10366
|
-
level:
|
|
10367
|
-
consecutiveFailures,
|
|
10368
|
-
RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
|
|
10369
|
-
),
|
|
11278
|
+
level: usageReportFailureLogLevel(consecutiveFailures),
|
|
10370
11279
|
message: `Resource usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
10371
11280
|
});
|
|
10372
11281
|
} finally {
|
|
10373
|
-
state.resourceUsageTimer = setTimeout(
|
|
10374
|
-
() => void tick(),
|
|
10375
|
-
jitteredDelayMs(
|
|
10376
|
-
RESOURCE_USAGE_BASE_REPORT_DELAY_MS,
|
|
10377
|
-
RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION
|
|
10378
|
-
)
|
|
10379
|
-
);
|
|
11282
|
+
state.resourceUsageTimer = setTimeout(() => void tick(), usageReportDelayMs());
|
|
10380
11283
|
}
|
|
10381
11284
|
};
|
|
10382
11285
|
state.resourceUsageTimer = setTimeout(() => void tick(), firstReportDelayMs());
|
|
@@ -10416,6 +11319,8 @@ async function cleanup(state, opts = {}) {
|
|
|
10416
11319
|
clearTimeout(timer);
|
|
10417
11320
|
}
|
|
10418
11321
|
state.sessionCleanupTimers = [];
|
|
11322
|
+
state.stopOpenCodeLogTail?.();
|
|
11323
|
+
state.stopOpenCodeLogTail = null;
|
|
10419
11324
|
if (state.claudeUsageTimer) {
|
|
10420
11325
|
clearTimeout(state.claudeUsageTimer);
|
|
10421
11326
|
state.claudeUsageTimer = null;
|
|
@@ -10554,7 +11459,7 @@ async function run(options) {
|
|
|
10554
11459
|
let fileSyncDirectories;
|
|
10555
11460
|
try {
|
|
10556
11461
|
logLevel = resolveLogLevel(options);
|
|
10557
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
11462
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir6());
|
|
10558
11463
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
10559
11464
|
throw new Error(
|
|
10560
11465
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -10585,6 +11490,7 @@ async function run(options) {
|
|
|
10585
11490
|
opencodeVersion: null,
|
|
10586
11491
|
sessionDbProvenanceAnomaly: false,
|
|
10587
11492
|
opencodeProcess: null,
|
|
11493
|
+
stopOpenCodeLogTail: null,
|
|
10588
11494
|
litestreamProcess: null,
|
|
10589
11495
|
connection: null,
|
|
10590
11496
|
channelDriver: null,
|
|
@@ -10652,15 +11558,15 @@ async function run(options) {
|
|
|
10652
11558
|
printError("Authentication required");
|
|
10653
11559
|
blank();
|
|
10654
11560
|
console.log(
|
|
10655
|
-
|
|
11561
|
+
chalk7.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
|
|
10656
11562
|
);
|
|
10657
|
-
console.log(
|
|
11563
|
+
console.log(chalk7.dim("Or run `evident login` for interactive authentication"));
|
|
10658
11564
|
blank();
|
|
10659
11565
|
process.exit(1);
|
|
10660
11566
|
return;
|
|
10661
11567
|
}
|
|
10662
11568
|
blank();
|
|
10663
|
-
console.log(
|
|
11569
|
+
console.log(chalk7.yellow("You are not logged in to Evident."));
|
|
10664
11570
|
blank();
|
|
10665
11571
|
credentials2 = await promptForLogin(
|
|
10666
11572
|
"Would you like to log in now?",
|
|
@@ -10710,7 +11616,7 @@ async function run(options) {
|
|
|
10710
11616
|
);
|
|
10711
11617
|
blank();
|
|
10712
11618
|
console.log(
|
|
10713
|
-
|
|
11619
|
+
chalk7.dim(
|
|
10714
11620
|
"Either provide --runner/--agent <id> or set EVIDENT_RUNNER_KEY/EVIDENT_AGENT_KEY"
|
|
10715
11621
|
)
|
|
10716
11622
|
);
|
|
@@ -10733,15 +11639,15 @@ async function run(options) {
|
|
|
10733
11639
|
);
|
|
10734
11640
|
if (interactive && !state.json) {
|
|
10735
11641
|
blank();
|
|
10736
|
-
console.log(
|
|
10737
|
-
console.log(
|
|
11642
|
+
console.log(chalk7.bold("Evident Run"));
|
|
11643
|
+
console.log(chalk7.dim("-".repeat(40)));
|
|
10738
11644
|
}
|
|
10739
11645
|
const spinner = interactive && !state.json ? ora3("Validating runner...").start() : null;
|
|
10740
11646
|
let validation = await getAgentInfo(state.agentId, state.authHeader);
|
|
10741
11647
|
if (!validation.valid && validation.authFailed && interactive) {
|
|
10742
11648
|
spinner?.fail("Authentication failed");
|
|
10743
11649
|
blank();
|
|
10744
|
-
console.log(
|
|
11650
|
+
console.log(chalk7.yellow("Your authentication token is invalid or expired."));
|
|
10745
11651
|
blank();
|
|
10746
11652
|
credentials2 = await promptForLogin(
|
|
10747
11653
|
"Would you like to log in again?",
|
|
@@ -10789,6 +11695,13 @@ async function run(options) {
|
|
|
10789
11695
|
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
10790
11696
|
}
|
|
10791
11697
|
state.credentialSync?.arm();
|
|
11698
|
+
state.stopOpenCodeLogTail = tailOpenCodeLogFile(
|
|
11699
|
+
resolveOpenCodeLogPath(homedir6(), process.env),
|
|
11700
|
+
createOpenCodeActivityForwarder(() => ({
|
|
11701
|
+
agentId: state.agentId,
|
|
11702
|
+
authHeader: state.authHeader
|
|
11703
|
+
}))
|
|
11704
|
+
).stop;
|
|
10792
11705
|
let sessionDbVerifyFatal = false;
|
|
10793
11706
|
if (!options.restoreSessionDb) {
|
|
10794
11707
|
log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
|
|
@@ -10838,6 +11751,13 @@ async function run(options) {
|
|
|
10838
11751
|
for (const warning2 of opencodeStartTimeoutWarnings) {
|
|
10839
11752
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
10840
11753
|
}
|
|
11754
|
+
const { version: opencodeVersion, warnings: opencodeVersionWarnings } = resolveOpenCodeVersion(
|
|
11755
|
+
options,
|
|
11756
|
+
process.env
|
|
11757
|
+
);
|
|
11758
|
+
for (const warning2 of opencodeVersionWarnings) {
|
|
11759
|
+
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
11760
|
+
}
|
|
10841
11761
|
const { value: maxActiveSessions, warnings: maxActiveSessionsWarnings } = resolveMaxActiveSessions(options, process.env);
|
|
10842
11762
|
for (const warning2 of maxActiveSessionsWarnings) {
|
|
10843
11763
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
@@ -10845,7 +11765,14 @@ async function run(options) {
|
|
|
10845
11765
|
const preBootMigrationIds = readSessionDbMigrationIds(sessionDbPath());
|
|
10846
11766
|
const ocSpinner = interactive && !state.json ? ora3("Checking OpenCode...").start() : null;
|
|
10847
11767
|
try {
|
|
10848
|
-
const oc = await
|
|
11768
|
+
const oc = opencodeVersion === "v2" ? await ensureOpenCode2Running({
|
|
11769
|
+
port: state.port,
|
|
11770
|
+
interactive: state.interactive,
|
|
11771
|
+
agentId: state.agentId,
|
|
11772
|
+
log: (message) => log2(state, message),
|
|
11773
|
+
startTimeoutMs: opencodeStartTimeoutMs,
|
|
11774
|
+
inheritStdio: Boolean(options.opencodePidFile)
|
|
11775
|
+
}) : await ensureOpenCodeRunning({
|
|
10849
11776
|
port: state.port,
|
|
10850
11777
|
interactive: state.interactive,
|
|
10851
11778
|
agentId: state.agentId,
|
|
@@ -10872,7 +11799,7 @@ async function run(options) {
|
|
|
10872
11799
|
const provenance = checkSessionDbProvenance({
|
|
10873
11800
|
dbPath: sessionDbPath(),
|
|
10874
11801
|
currentVersion: state.opencodeVersion,
|
|
10875
|
-
homeDir:
|
|
11802
|
+
homeDir: homedir6(),
|
|
10876
11803
|
env: process.env
|
|
10877
11804
|
});
|
|
10878
11805
|
if (provenance.anomaly) {
|
|
@@ -10899,6 +11826,7 @@ async function run(options) {
|
|
|
10899
11826
|
logActivity(state, { type: "info", level: "warn", message: versionWarning });
|
|
10900
11827
|
}
|
|
10901
11828
|
}
|
|
11829
|
+
await reloadProviderCache(state.port);
|
|
10902
11830
|
const noProviderWarning = buildNoProviderWarning(
|
|
10903
11831
|
await hasAnyConfiguredProvider(state.port)
|
|
10904
11832
|
);
|
|
@@ -10907,10 +11835,10 @@ async function run(options) {
|
|
|
10907
11835
|
if (state.interactive && !state.json) {
|
|
10908
11836
|
logActivity(state, { type: "info", level: "warn", message: noProviderWarning });
|
|
10909
11837
|
blank();
|
|
10910
|
-
console.log(
|
|
11838
|
+
console.log(chalk7.yellow("\u26A0 No OpenCode model provider is configured."));
|
|
10911
11839
|
console.log(
|
|
10912
|
-
|
|
10913
|
-
`Run ${
|
|
11840
|
+
chalk7.dim(
|
|
11841
|
+
`Run ${chalk7.cyan("opencode auth login")} to set one up \u2014 messages will fail until then.`
|
|
10914
11842
|
)
|
|
10915
11843
|
);
|
|
10916
11844
|
blank();
|
|
@@ -11034,7 +11962,7 @@ async function run(options) {
|
|
|
11034
11962
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
11035
11963
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
11036
11964
|
fileSyncDirectories,
|
|
11037
|
-
homeDir:
|
|
11965
|
+
homeDir: homedir6(),
|
|
11038
11966
|
maxActiveSessions,
|
|
11039
11967
|
log: (entry) => (
|
|
11040
11968
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -11276,6 +12204,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
11276
12204
|
).option("-v, --verbose", "Alias for --log-level debug (ignored if --log-level is set)").option("-c, --conversation <id>", "Process only this specific conversation").option("--idle-timeout <seconds>", "Exit after N seconds idle").option(
|
|
11277
12205
|
"--opencode-start-timeout <seconds>",
|
|
11278
12206
|
"Seconds to wait for OpenCode to become healthy when the runner starts it (default: 180). Env: EVIDENT_OPENCODE_START_TIMEOUT"
|
|
12207
|
+
).option(
|
|
12208
|
+
"--opencode-version <v1|v2>",
|
|
12209
|
+
"Which OpenCode major version to launch: v1 or v2 (default: v1). Env: EVIDENT_OPENCODE_VERSION"
|
|
11279
12210
|
).option("--json", "Output in JSON format").option(
|
|
11280
12211
|
"--session-cleanup-max-age <duration>",
|
|
11281
12212
|
"Delete OpenCode sessions idle longer than this (e.g. 7d, 24h). Enables cleanup. Env: EVIDENT_SESSION_CLEANUP_MAX_AGE"
|
|
@@ -11344,6 +12275,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
11344
12275
|
// Raw string — validation/precedence is single-sourced in run.ts's
|
|
11345
12276
|
// resolveOpenCodeStartTimeoutMs (flag > EVIDENT_OPENCODE_START_TIMEOUT > 180s default).
|
|
11346
12277
|
opencodeStartTimeout: options.opencodeStartTimeout,
|
|
12278
|
+
opencodeVersion: options.opencodeVersion,
|
|
11347
12279
|
json: options.json,
|
|
11348
12280
|
// Raw strings — the resolver in run.ts single-sources parsing (M1).
|
|
11349
12281
|
sessionCleanupMaxAge: options.sessionCleanupMaxAge,
|