@evident-ai/cli 3.4.0 → 3.4.1-dev.49da2cf
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 +10 -1
- package/dist/index.js +643 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -722,6 +722,14 @@ function toReportedWindow(window) {
|
|
|
722
722
|
if (!window) return null;
|
|
723
723
|
return { utilization: window.utilization, resets_at: window.resetsAt };
|
|
724
724
|
}
|
|
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
|
+
}
|
|
725
733
|
async function reportClaudeUsage(agentId, authHeader, snapshot) {
|
|
726
734
|
try {
|
|
727
735
|
const apiUrl = getApiUrlConfig();
|
|
@@ -730,7 +738,42 @@ async function reportClaudeUsage(agentId, authHeader, snapshot) {
|
|
|
730
738
|
headers: { Authorization: authHeader, "Content-Type": "application/json" },
|
|
731
739
|
body: JSON.stringify({
|
|
732
740
|
five_hour: toReportedWindow(snapshot.fiveHour),
|
|
733
|
-
seven_day: toReportedWindow(snapshot.sevenDay)
|
|
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
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function toReportedOpenAiWindow(window) {
|
|
759
|
+
if (!window) return null;
|
|
760
|
+
return {
|
|
761
|
+
utilization: window.utilization,
|
|
762
|
+
window_minutes: window.windowMinutes,
|
|
763
|
+
resets_at: window.resetsAt
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
async function reportOpenAiUsage(agentId, authHeader, snapshot) {
|
|
767
|
+
try {
|
|
768
|
+
const apiUrl = getApiUrlConfig();
|
|
769
|
+
const response = await fetch(`${apiUrl}/runners/${agentId}/openai-usage`, {
|
|
770
|
+
method: "POST",
|
|
771
|
+
headers: { Authorization: authHeader, "Content-Type": "application/json" },
|
|
772
|
+
body: JSON.stringify({
|
|
773
|
+
primary: toReportedOpenAiWindow(snapshot.primary),
|
|
774
|
+
secondary: toReportedOpenAiWindow(snapshot.secondary),
|
|
775
|
+
has_credits: snapshot.hasCredits,
|
|
776
|
+
credits_unlimited: snapshot.creditsUnlimited
|
|
734
777
|
}),
|
|
735
778
|
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
736
779
|
});
|
|
@@ -962,7 +1005,10 @@ import { readFileSync } from "fs";
|
|
|
962
1005
|
import { homedir } from "os";
|
|
963
1006
|
import { join } from "path";
|
|
964
1007
|
var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
1008
|
+
var CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
|
|
1009
|
+
var CLAUDE_PROFILE_TIMEOUT_MS = 2e3;
|
|
965
1010
|
var KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
1011
|
+
var cachedOwner = null;
|
|
966
1012
|
var CLAUDE_CREDENTIALS_SEGMENTS = [".claude", ".credentials.json"];
|
|
967
1013
|
function parseClaudeCliCredentials(raw) {
|
|
968
1014
|
let parsed;
|
|
@@ -1036,6 +1082,47 @@ function toWindow(value) {
|
|
|
1036
1082
|
}
|
|
1037
1083
|
return { utilization: window.utilization, resetsAt };
|
|
1038
1084
|
}
|
|
1085
|
+
function ownerLookupFailure(error2) {
|
|
1086
|
+
const name = error2?.name;
|
|
1087
|
+
return name === "TimeoutError" || name === "AbortError" ? "timed out" : "request failed";
|
|
1088
|
+
}
|
|
1089
|
+
async function getClaudeUsageOwner(accessToken) {
|
|
1090
|
+
if (cachedOwner?.accessToken === accessToken) {
|
|
1091
|
+
return { owner: cachedOwner.owner, ownerLookupError: null };
|
|
1092
|
+
}
|
|
1093
|
+
try {
|
|
1094
|
+
const response = await fetch(CLAUDE_PROFILE_URL, {
|
|
1095
|
+
headers: {
|
|
1096
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1097
|
+
"Content-Type": "application/json",
|
|
1098
|
+
"anthropic-version": "2023-06-01"
|
|
1099
|
+
},
|
|
1100
|
+
signal: AbortSignal.timeout(CLAUDE_PROFILE_TIMEOUT_MS)
|
|
1101
|
+
});
|
|
1102
|
+
if (!response.ok) {
|
|
1103
|
+
return { owner: null, ownerLookupError: `HTTP ${response.status}` };
|
|
1104
|
+
}
|
|
1105
|
+
let body;
|
|
1106
|
+
try {
|
|
1107
|
+
body = await response.json();
|
|
1108
|
+
} catch (error2) {
|
|
1109
|
+
return { owner: null, ownerLookupError: "malformed response" };
|
|
1110
|
+
}
|
|
1111
|
+
const profile = body;
|
|
1112
|
+
if (typeof profile.account?.email !== "string" || !profile.account.email.trim()) {
|
|
1113
|
+
return { owner: null, ownerLookupError: "malformed response" };
|
|
1114
|
+
}
|
|
1115
|
+
const owner = {
|
|
1116
|
+
email: profile.account.email,
|
|
1117
|
+
organizationName: typeof profile.organization?.name === "string" && profile.organization.name.trim() ? profile.organization.name.trim() : null,
|
|
1118
|
+
rateLimitTier: typeof profile.organization?.rate_limit_tier === "string" && profile.organization.rate_limit_tier.trim() ? profile.organization.rate_limit_tier.trim() : null
|
|
1119
|
+
};
|
|
1120
|
+
cachedOwner = { accessToken, owner };
|
|
1121
|
+
return { owner, ownerLookupError: null };
|
|
1122
|
+
} catch (error2) {
|
|
1123
|
+
return { owner: null, ownerLookupError: ownerLookupFailure(error2) };
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1039
1126
|
async function getClaudeUsage() {
|
|
1040
1127
|
const credentials2 = readClaudeCliCredentials();
|
|
1041
1128
|
if (!credentials2) {
|
|
@@ -1055,15 +1142,19 @@ async function getClaudeUsage() {
|
|
|
1055
1142
|
Authorization: `Bearer ${credentials2.accessToken}`,
|
|
1056
1143
|
"Content-Type": "application/json",
|
|
1057
1144
|
"anthropic-version": "2023-06-01"
|
|
1058
|
-
}
|
|
1145
|
+
},
|
|
1146
|
+
signal: AbortSignal.timeout(CLAUDE_PROFILE_TIMEOUT_MS)
|
|
1059
1147
|
});
|
|
1060
1148
|
if (!res.ok) {
|
|
1061
1149
|
throw new ClaudeUsageError(`Claude usage request failed: HTTP ${res.status}`, "request_failed");
|
|
1062
1150
|
}
|
|
1063
1151
|
const body = await res.json();
|
|
1152
|
+
const { owner, ownerLookupError } = await getClaudeUsageOwner(credentials2.accessToken);
|
|
1064
1153
|
return {
|
|
1065
1154
|
fiveHour: toWindow(body.five_hour),
|
|
1066
|
-
sevenDay: toWindow(body.seven_day)
|
|
1155
|
+
sevenDay: toWindow(body.seven_day),
|
|
1156
|
+
owner,
|
|
1157
|
+
ownerLookupError
|
|
1067
1158
|
};
|
|
1068
1159
|
}
|
|
1069
1160
|
|
|
@@ -1092,8 +1183,8 @@ async function claudeUsage() {
|
|
|
1092
1183
|
}
|
|
1093
1184
|
|
|
1094
1185
|
// src/commands/run.ts
|
|
1095
|
-
import { homedir as
|
|
1096
|
-
import { isAbsolute as isAbsolute2, join as
|
|
1186
|
+
import { homedir as homedir4 } from "os";
|
|
1187
|
+
import { isAbsolute as isAbsolute2, join as join7, parse, resolve as resolvePath } from "path";
|
|
1097
1188
|
import chalk6 from "chalk";
|
|
1098
1189
|
|
|
1099
1190
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -1326,6 +1417,8 @@ var SEVERITY_BY_LEVEL = {
|
|
|
1326
1417
|
error: "error"
|
|
1327
1418
|
};
|
|
1328
1419
|
var MAX_MESSAGE_LENGTH = 500;
|
|
1420
|
+
var MAX_METADATA_VALUE_LENGTH = 200;
|
|
1421
|
+
var MAX_METADATA_ENTRIES = 20;
|
|
1329
1422
|
var TRUNCATION_MARKER = "\u2026";
|
|
1330
1423
|
function redact(message) {
|
|
1331
1424
|
return message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/https?:\/\/\S+/g, "<url>");
|
|
@@ -1334,6 +1427,24 @@ function truncate(message) {
|
|
|
1334
1427
|
if (message.length <= MAX_MESSAGE_LENGTH) return message;
|
|
1335
1428
|
return message.slice(0, MAX_MESSAGE_LENGTH - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
1336
1429
|
}
|
|
1430
|
+
function sanitiseMetadata(metadata) {
|
|
1431
|
+
if (!metadata) return {};
|
|
1432
|
+
const entries = Object.entries(metadata).slice(0, MAX_METADATA_ENTRIES);
|
|
1433
|
+
if (Object.keys(metadata).length > entries.length) {
|
|
1434
|
+
console.error(
|
|
1435
|
+
`[runner-activity-telemetry] dropped ${Object.keys(metadata).length - entries.length} metadata entries`
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
const sanitised = [];
|
|
1439
|
+
for (const [key, value] of entries) {
|
|
1440
|
+
if (typeof value === "string") {
|
|
1441
|
+
sanitised.push([key, truncate(redact(value).slice(0, MAX_METADATA_VALUE_LENGTH))]);
|
|
1442
|
+
} else if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
|
|
1443
|
+
sanitised.push([key, value]);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return Object.fromEntries(sanitised);
|
|
1447
|
+
}
|
|
1337
1448
|
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
1338
1449
|
var RATE_LIMIT_MAX_EVENTS = 30;
|
|
1339
1450
|
var windowStartedAt = 0;
|
|
@@ -1372,7 +1483,7 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1372
1483
|
logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
|
|
1373
1484
|
severity: SEVERITY_BY_LEVEL[entry.level],
|
|
1374
1485
|
message,
|
|
1375
|
-
metadata: { source: "cli.run" },
|
|
1486
|
+
metadata: { ...sanitiseMetadata(entry.metadata), source: "cli.run" },
|
|
1376
1487
|
agentId: context.agentId
|
|
1377
1488
|
});
|
|
1378
1489
|
} catch (err) {
|
|
@@ -1382,6 +1493,150 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1382
1493
|
}
|
|
1383
1494
|
}
|
|
1384
1495
|
|
|
1496
|
+
// src/lib/opencode/session-db-recovery-report.ts
|
|
1497
|
+
import { readFileSync as readFileSync2, unlinkSync } from "fs";
|
|
1498
|
+
import { join as join2 } from "path";
|
|
1499
|
+
function sessionDbRecoveryReportPath(homeDir, env) {
|
|
1500
|
+
const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
|
|
1501
|
+
return override || join2(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
|
|
1502
|
+
}
|
|
1503
|
+
function drainSessionDbRecoveryReport({
|
|
1504
|
+
homeDir,
|
|
1505
|
+
env
|
|
1506
|
+
}) {
|
|
1507
|
+
const path = sessionDbRecoveryReportPath(homeDir, env);
|
|
1508
|
+
let content;
|
|
1509
|
+
try {
|
|
1510
|
+
content = readFileSync2(path, "utf8");
|
|
1511
|
+
} catch (error2) {
|
|
1512
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
1513
|
+
return { path, records: [], skippedLines: 0, readError: null };
|
|
1514
|
+
const readError = error2 instanceof Error ? error2.message : String(error2);
|
|
1515
|
+
console.error(`[session-db-recovery-report] could not read ${path}: ${readError}`);
|
|
1516
|
+
return { path, records: [], skippedLines: 0, readError };
|
|
1517
|
+
}
|
|
1518
|
+
let skippedLines = 0;
|
|
1519
|
+
const records = content.split("\n").flatMap((line) => {
|
|
1520
|
+
if (!line.trim()) return [];
|
|
1521
|
+
try {
|
|
1522
|
+
const value = JSON.parse(line);
|
|
1523
|
+
if (!isSessionDbRecoveryRecord(value)) {
|
|
1524
|
+
skippedLines++;
|
|
1525
|
+
return [];
|
|
1526
|
+
}
|
|
1527
|
+
return [value];
|
|
1528
|
+
} catch (error2) {
|
|
1529
|
+
skippedLines++;
|
|
1530
|
+
console.error(
|
|
1531
|
+
`[session-db-recovery-report] skipped malformed record in ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
1532
|
+
);
|
|
1533
|
+
return [];
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
return { path, records, skippedLines, readError: null };
|
|
1537
|
+
}
|
|
1538
|
+
function acknowledgeSessionDbRecoveryReport(path) {
|
|
1539
|
+
try {
|
|
1540
|
+
unlinkSync(path);
|
|
1541
|
+
} catch (error2) {
|
|
1542
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return;
|
|
1543
|
+
console.error(
|
|
1544
|
+
`[session-db-recovery-report] could not remove ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
function buildSessionDbRecoveryActivity(record) {
|
|
1549
|
+
const level = record.severity === "warning" ? "warn" : record.severity === "error" ? "error" : null;
|
|
1550
|
+
if (!level) return null;
|
|
1551
|
+
const noVerifiedPoint = record.verified_restore_point ? ` The verified restore point is ${record.verified_restore_point}.` : " No verified restore point is known.";
|
|
1552
|
+
switch (record.outcome) {
|
|
1553
|
+
case "fresh_session_db":
|
|
1554
|
+
return {
|
|
1555
|
+
level,
|
|
1556
|
+
metadata: withoutContractFields(record),
|
|
1557
|
+
message: `This runner started with a fresh session database. Earlier sessions are unavailable, and queued conversation or session references from before this start may no longer resolve.${noVerifiedPoint} Review the runner's backup configuration before relying on restored session history.`
|
|
1558
|
+
};
|
|
1559
|
+
case "restore_retried":
|
|
1560
|
+
return {
|
|
1561
|
+
level,
|
|
1562
|
+
metadata: withoutContractFields(record),
|
|
1563
|
+
message: "The initial session database restore failed; the runner will retry it. Monitor the runner backup for another restore failure."
|
|
1564
|
+
};
|
|
1565
|
+
case "replica_recovered":
|
|
1566
|
+
if (record.reason === "quarantine")
|
|
1567
|
+
return {
|
|
1568
|
+
level,
|
|
1569
|
+
metadata: withoutContractFields(record),
|
|
1570
|
+
message: `Session database recovery quarantined ${record.quarantined_objects ?? "unknown"} objects (${record.quarantined_bytes ?? "unknown"} bytes); ${record.quarantine_failed_objects ?? "unknown"} moves failed. Review the preserved backup at ${record.quarantine_destination ?? "an unknown destination"} before deleting it.`
|
|
1571
|
+
};
|
|
1572
|
+
if (record.reason === "prune")
|
|
1573
|
+
return {
|
|
1574
|
+
level,
|
|
1575
|
+
metadata: withoutContractFields(record),
|
|
1576
|
+
message: "Session database recovery discarded a damaged newest backup and retried. Sessions recorded after the previous backup point may be unavailable. Review the runner backup for another restore failure."
|
|
1577
|
+
};
|
|
1578
|
+
if (record.reason === "clear")
|
|
1579
|
+
return {
|
|
1580
|
+
level,
|
|
1581
|
+
metadata: withoutContractFields(record),
|
|
1582
|
+
message: "Session database recovery deleted the damaged backup and prior session history is unavailable. Review the runner backup configuration before relying on restored session history."
|
|
1583
|
+
};
|
|
1584
|
+
return null;
|
|
1585
|
+
case "history_rolled_back":
|
|
1586
|
+
return {
|
|
1587
|
+
level,
|
|
1588
|
+
metadata: withoutContractFields(record),
|
|
1589
|
+
message: `Session history was rolled back to verified restore point ${record.verified_restore_point ?? "unknown"}; everything after it is unavailable. Review the runner backup for another restore failure.`
|
|
1590
|
+
};
|
|
1591
|
+
case "restore_misconfigured":
|
|
1592
|
+
return {
|
|
1593
|
+
level,
|
|
1594
|
+
metadata: withoutContractFields(record),
|
|
1595
|
+
message: "This runner started with a fresh session database because it could not verify where its session history is backed up. Nothing in the backup was changed; check the runner backup configuration and permissions."
|
|
1596
|
+
};
|
|
1597
|
+
case "session_db_boot_refused":
|
|
1598
|
+
return {
|
|
1599
|
+
level,
|
|
1600
|
+
metadata: withoutContractFields(record),
|
|
1601
|
+
message: `This runner did not come online because its damaged session database could not be safely separated from its active backup or proven removed. Backed-up session history remains readable at ${record.quarantine_destination ?? "its original location or the quarantine destination named in the boot logs"}; any local session-database files that remain were left in place and nothing opened or wrote them. See the runner boot logs for SESSION-DB-LOCAL-DISCARD-FAILED details.`
|
|
1602
|
+
};
|
|
1603
|
+
default:
|
|
1604
|
+
return null;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
function withoutContractFields(record) {
|
|
1608
|
+
const { v: _v, event: _event, ...metadata } = record;
|
|
1609
|
+
return metadata;
|
|
1610
|
+
}
|
|
1611
|
+
var OUTCOMES = /* @__PURE__ */ new Set([
|
|
1612
|
+
"replica_recovered",
|
|
1613
|
+
"restore_retried",
|
|
1614
|
+
"fresh_session_db",
|
|
1615
|
+
"history_rolled_back",
|
|
1616
|
+
"restore_misconfigured",
|
|
1617
|
+
"session_db_boot_refused"
|
|
1618
|
+
]);
|
|
1619
|
+
var STAGES = /* @__PURE__ */ new Set(["restore", "verify"]);
|
|
1620
|
+
var SEVERITIES = /* @__PURE__ */ new Set(["warning", "error"]);
|
|
1621
|
+
var NUMBER_FIELDS = [
|
|
1622
|
+
"litestream_exit_code",
|
|
1623
|
+
"attempt",
|
|
1624
|
+
"replica_objects",
|
|
1625
|
+
"replica_bytes",
|
|
1626
|
+
"quarantined_objects",
|
|
1627
|
+
"quarantine_failed_objects",
|
|
1628
|
+
"quarantined_bytes",
|
|
1629
|
+
"restore_points_tried"
|
|
1630
|
+
];
|
|
1631
|
+
var STRING_OR_NULL_FIELDS = ["quarantine_destination", "verified_restore_point"];
|
|
1632
|
+
function isSessionDbRecoveryRecord(value) {
|
|
1633
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1634
|
+
const record = value;
|
|
1635
|
+
return record.v === 1 && record.event === "session_db_recovery" && typeof record.at === "string" && STAGES.has(record.stage) && OUTCOMES.has(record.outcome) && SEVERITIES.has(record.severity) && typeof record.reason === "string" && NUMBER_FIELDS.every((field) => record[field] === null || Number.isInteger(record[field])) && STRING_OR_NULL_FIELDS.every(
|
|
1636
|
+
(field) => record[field] === null || typeof record[field] === "string"
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1385
1640
|
// src/lib/opencode/health.ts
|
|
1386
1641
|
async function checkOpenCodeHealth(port) {
|
|
1387
1642
|
try {
|
|
@@ -2196,7 +2451,7 @@ function isPreamblePinnedRunning(messages, userMessageId) {
|
|
|
2196
2451
|
return completedOf(reply) != null && finishOf(reply) === "tool-calls";
|
|
2197
2452
|
}
|
|
2198
2453
|
function isB2AbandonmentConfirmed(params) {
|
|
2199
|
-
return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false;
|
|
2454
|
+
return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false && params.rootOngoing === false;
|
|
2200
2455
|
}
|
|
2201
2456
|
function isAmbiguousTerminalFinish(m) {
|
|
2202
2457
|
if (completedOf(m) == null) return false;
|
|
@@ -2209,7 +2464,7 @@ function isAmbiguousFinishPinnedRunning(messages, userMessageId) {
|
|
|
2209
2464
|
return isAmbiguousTerminalFinish(reply);
|
|
2210
2465
|
}
|
|
2211
2466
|
function isAmbiguousFinishResolved(params) {
|
|
2212
|
-
return params.sessionOngoing === false || params.pinnedForMs >= params.maxPinnedMs;
|
|
2467
|
+
return params.sessionOngoing === false || params.sessionOngoing !== true && params.pinnedForMs >= params.maxPinnedMs;
|
|
2213
2468
|
}
|
|
2214
2469
|
function messageError(messages, userMessageId) {
|
|
2215
2470
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
@@ -2419,10 +2674,10 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
2419
2674
|
|
|
2420
2675
|
// src/lib/opencode/session-db-size.ts
|
|
2421
2676
|
import { statSync as statSync2 } from "fs";
|
|
2422
|
-
import { join as
|
|
2677
|
+
import { join as join3 } from "path";
|
|
2423
2678
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
2424
2679
|
function statSessionDbBytes(homeDir) {
|
|
2425
|
-
const dbPath =
|
|
2680
|
+
const dbPath = join3(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
2426
2681
|
try {
|
|
2427
2682
|
return statSync2(dbPath).size;
|
|
2428
2683
|
} catch (err) {
|
|
@@ -2991,6 +3246,177 @@ function writeTunnelReadyMarker(path, agentId) {
|
|
|
2991
3246
|
}
|
|
2992
3247
|
}
|
|
2993
3248
|
|
|
3249
|
+
// src/lib/openai-usage.ts
|
|
3250
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
3251
|
+
import { homedir as homedir2 } from "os";
|
|
3252
|
+
import { join as join4 } from "path";
|
|
3253
|
+
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
3254
|
+
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
3255
|
+
var OpenAiUsageError = class extends Error {
|
|
3256
|
+
constructor(message, reason) {
|
|
3257
|
+
super(message);
|
|
3258
|
+
this.reason = reason;
|
|
3259
|
+
}
|
|
3260
|
+
};
|
|
3261
|
+
function isLocalCredentialProblem2(err) {
|
|
3262
|
+
return err instanceof OpenAiUsageError && (err.reason === "no_credentials" || err.reason === "credentials_expired");
|
|
3263
|
+
}
|
|
3264
|
+
function readOpenCodeChatGptCredentials() {
|
|
3265
|
+
try {
|
|
3266
|
+
const raw = readFileSync3(join4(homedir2(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
3267
|
+
let parsed;
|
|
3268
|
+
try {
|
|
3269
|
+
parsed = JSON.parse(raw);
|
|
3270
|
+
} catch {
|
|
3271
|
+
return null;
|
|
3272
|
+
}
|
|
3273
|
+
const entry = parsed.openai;
|
|
3274
|
+
if (entry?.type !== "oauth" || typeof entry.access !== "string" || !entry.access || typeof entry.expires !== "number") {
|
|
3275
|
+
return null;
|
|
3276
|
+
}
|
|
3277
|
+
return { accessToken: entry.access, expiresAt: entry.expires };
|
|
3278
|
+
} catch (err) {
|
|
3279
|
+
const code = err.code;
|
|
3280
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
3281
|
+
console.warn(
|
|
3282
|
+
`readOpenCodeChatGptCredentials: reading auth.json failed (${code ?? "unknown"})`
|
|
3283
|
+
);
|
|
3284
|
+
}
|
|
3285
|
+
return null;
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
function toWindow2(headers, name) {
|
|
3289
|
+
const utilizationHeader = headers.get(`x-codex-${name}-used-percent`);
|
|
3290
|
+
const windowMinutesHeader = headers.get(`x-codex-${name}-window-minutes`);
|
|
3291
|
+
if (utilizationHeader == null || utilizationHeader === "" || windowMinutesHeader == null) {
|
|
3292
|
+
return null;
|
|
3293
|
+
}
|
|
3294
|
+
const utilization = Number(utilizationHeader);
|
|
3295
|
+
const windowMinutes = Number(windowMinutesHeader);
|
|
3296
|
+
if (!Number.isFinite(utilization) || !Number.isFinite(windowMinutes) || windowMinutes <= 0) {
|
|
3297
|
+
return null;
|
|
3298
|
+
}
|
|
3299
|
+
const resetAtHeader = headers.get(`x-codex-${name}-reset-at`);
|
|
3300
|
+
const resetSeconds = resetAtHeader == null || resetAtHeader === "" ? NaN : Number(resetAtHeader);
|
|
3301
|
+
const resetsAt = Number.isFinite(resetSeconds) ? new Date(resetSeconds * 1e3).toISOString() : null;
|
|
3302
|
+
return { utilization: Math.min(100, Math.max(0, utilization)), windowMinutes, resetsAt };
|
|
3303
|
+
}
|
|
3304
|
+
function parseCodexUsageHeaders(headers) {
|
|
3305
|
+
return {
|
|
3306
|
+
primary: toWindow2(headers, "primary"),
|
|
3307
|
+
secondary: toWindow2(headers, "secondary"),
|
|
3308
|
+
hasCredits: headers.has("x-codex-credits-has-credits") ? headers.get("x-codex-credits-has-credits")?.toLowerCase() === "true" : null,
|
|
3309
|
+
creditsUnlimited: headers.has("x-codex-credits-unlimited") ? headers.get("x-codex-credits-unlimited")?.toLowerCase() === "true" : null
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
function normalizeProbeModel(model) {
|
|
3313
|
+
return model.endsWith("-fast") ? model.slice(0, -"-fast".length) : model;
|
|
3314
|
+
}
|
|
3315
|
+
async function resolveProbeModels(port) {
|
|
3316
|
+
try {
|
|
3317
|
+
const res = await withRequestTimeout(
|
|
3318
|
+
fetch,
|
|
3319
|
+
REQUEST_TIMEOUT_MS
|
|
3320
|
+
)(`${opencodeBase(port)}/config/providers`);
|
|
3321
|
+
if (!res.ok) {
|
|
3322
|
+
console.error(
|
|
3323
|
+
`[resolveProbeModels] GET /config/providers returned HTTP ${res.status} (port ${port})`
|
|
3324
|
+
);
|
|
3325
|
+
return [];
|
|
3326
|
+
}
|
|
3327
|
+
const body = await res.json();
|
|
3328
|
+
const provider = body?.providers?.find((candidate) => candidate?.id === "openai");
|
|
3329
|
+
if (!provider || !provider.models || typeof provider.models !== "object") return [];
|
|
3330
|
+
const candidates = [
|
|
3331
|
+
...typeof body?.default?.openai === "string" ? [body.default.openai] : [],
|
|
3332
|
+
...Object.keys(provider.models)
|
|
3333
|
+
].map(normalizeProbeModel);
|
|
3334
|
+
return [...new Set(candidates)].slice(0, 4);
|
|
3335
|
+
} catch (err) {
|
|
3336
|
+
console.error(
|
|
3337
|
+
`[resolveProbeModels] GET /config/providers failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3338
|
+
);
|
|
3339
|
+
return [];
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
function hasPrimaryHeaders(headers) {
|
|
3343
|
+
return [
|
|
3344
|
+
"x-codex-primary-used-percent",
|
|
3345
|
+
"x-codex-primary-window-minutes",
|
|
3346
|
+
"x-codex-primary-reset-at"
|
|
3347
|
+
].some((name) => headers.has(name));
|
|
3348
|
+
}
|
|
3349
|
+
async function getOpenAiUsage(port) {
|
|
3350
|
+
const credentials2 = readOpenCodeChatGptCredentials();
|
|
3351
|
+
if (!credentials2) {
|
|
3352
|
+
throw new OpenAiUsageError(
|
|
3353
|
+
"No ChatGPT login found. Connect a ChatGPT account to this runner, or run `opencode auth login`.",
|
|
3354
|
+
"no_credentials"
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
if (credentials2.expiresAt < Date.now()) {
|
|
3358
|
+
throw new OpenAiUsageError(
|
|
3359
|
+
"ChatGPT credentials have expired. Run `opencode auth login` to refresh them.",
|
|
3360
|
+
"credentials_expired"
|
|
3361
|
+
);
|
|
3362
|
+
}
|
|
3363
|
+
const models = await resolveProbeModels(port);
|
|
3364
|
+
if (models.length === 0) {
|
|
3365
|
+
throw new OpenAiUsageError("No supported OpenAI probe model is available.", "no_probe_model");
|
|
3366
|
+
}
|
|
3367
|
+
let lastStatus;
|
|
3368
|
+
for (const model of models) {
|
|
3369
|
+
let res;
|
|
3370
|
+
try {
|
|
3371
|
+
res = await withRequestTimeout(fetch, REQUEST_TIMEOUT_MS)(CODEX_RESPONSES_URL, {
|
|
3372
|
+
method: "POST",
|
|
3373
|
+
headers: {
|
|
3374
|
+
Authorization: `Bearer ${credentials2.accessToken}`,
|
|
3375
|
+
"Content-Type": "application/json"
|
|
3376
|
+
},
|
|
3377
|
+
body: JSON.stringify({ model, store: false, stream: true })
|
|
3378
|
+
});
|
|
3379
|
+
} catch (err) {
|
|
3380
|
+
throw new OpenAiUsageError(
|
|
3381
|
+
`OpenAI usage probe request failed: ${err instanceof Error ? err.message : String(err)}.`,
|
|
3382
|
+
"request_failed"
|
|
3383
|
+
);
|
|
3384
|
+
}
|
|
3385
|
+
try {
|
|
3386
|
+
lastStatus = res.status;
|
|
3387
|
+
if (hasPrimaryHeaders(res.headers)) {
|
|
3388
|
+
const usage = parseCodexUsageHeaders(res.headers);
|
|
3389
|
+
if (!usage.primary && !usage.secondary) {
|
|
3390
|
+
throw new OpenAiUsageError(
|
|
3391
|
+
`OpenAI usage probe returned no usable window (HTTP ${res.status}).`,
|
|
3392
|
+
"no_usable_window"
|
|
3393
|
+
);
|
|
3394
|
+
}
|
|
3395
|
+
return usage;
|
|
3396
|
+
}
|
|
3397
|
+
if (res.status === 401) {
|
|
3398
|
+
throw new OpenAiUsageError(
|
|
3399
|
+
"ChatGPT credentials have expired (HTTP 401).",
|
|
3400
|
+
"credentials_expired"
|
|
3401
|
+
);
|
|
3402
|
+
}
|
|
3403
|
+
if (res.status === 403 || res.status === 429) {
|
|
3404
|
+
throw new OpenAiUsageError(
|
|
3405
|
+
`OpenAI usage probe was blocked (HTTP ${res.status}).`,
|
|
3406
|
+
"probe_blocked"
|
|
3407
|
+
);
|
|
3408
|
+
}
|
|
3409
|
+
} finally {
|
|
3410
|
+
await res.body?.cancel().catch(() => {
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
throw new OpenAiUsageError(
|
|
3415
|
+
`OpenAI usage probe failed: HTTP ${lastStatus ?? "unknown"}.`,
|
|
3416
|
+
"request_failed"
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
|
|
2994
3420
|
// src/lib/reporting-schedule.ts
|
|
2995
3421
|
function jitteredDelayMs(baseMs, jitterFraction, random = Math.random) {
|
|
2996
3422
|
const jitterRangeMs = baseMs * jitterFraction;
|
|
@@ -2999,6 +3425,31 @@ function jitteredDelayMs(baseMs, jitterFraction, random = Math.random) {
|
|
|
2999
3425
|
function firstReportDelayMs(random = Math.random) {
|
|
3000
3426
|
return 5e3 + random() * 1e4;
|
|
3001
3427
|
}
|
|
3428
|
+
var VALID_USAGE_REPORTING_MODES = ["auto", "on", "off"];
|
|
3429
|
+
function resolveUsageReportingMode(flagValue, env, names) {
|
|
3430
|
+
const raw = flagValue ?? env[names.envVar];
|
|
3431
|
+
if (raw === void 0 || raw === "") return { mode: "auto", warnings: [] };
|
|
3432
|
+
const normalized = raw.trim().toLowerCase();
|
|
3433
|
+
if (VALID_USAGE_REPORTING_MODES.includes(normalized)) {
|
|
3434
|
+
return { mode: normalized, warnings: [] };
|
|
3435
|
+
}
|
|
3436
|
+
const source = flagValue !== void 0 ? names.flagName : names.envVar;
|
|
3437
|
+
return {
|
|
3438
|
+
mode: "auto",
|
|
3439
|
+
warnings: [
|
|
3440
|
+
`Ignoring invalid ${source} "${raw}": expected one of ${VALID_USAGE_REPORTING_MODES.join(", ")}; using auto`
|
|
3441
|
+
]
|
|
3442
|
+
};
|
|
3443
|
+
}
|
|
3444
|
+
var BASE_USAGE_REPORT_DELAY_MS = 10 * 6e4;
|
|
3445
|
+
var USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
3446
|
+
function usageReportDelayMs(random = Math.random) {
|
|
3447
|
+
return jitteredDelayMs(BASE_USAGE_REPORT_DELAY_MS, USAGE_REPORT_DELAY_JITTER_FRACTION, random);
|
|
3448
|
+
}
|
|
3449
|
+
var USAGE_REPORT_FAILURE_REESCALATION_TICKS = 6;
|
|
3450
|
+
function usageReportFailureLogLevel(consecutiveFailures) {
|
|
3451
|
+
return reportFailureLogLevel(consecutiveFailures, USAGE_REPORT_FAILURE_REESCALATION_TICKS);
|
|
3452
|
+
}
|
|
3002
3453
|
function reportFailureLogLevel(consecutiveFailures, reescalationTicks) {
|
|
3003
3454
|
return consecutiveFailures === 1 || consecutiveFailures % reescalationTicks === 0 ? "warn" : "debug";
|
|
3004
3455
|
}
|
|
@@ -3007,33 +3458,26 @@ function failureStreakSuffix(consecutiveFailures) {
|
|
|
3007
3458
|
}
|
|
3008
3459
|
|
|
3009
3460
|
// src/lib/claude-usage-reporting.ts
|
|
3010
|
-
var VALID_MODES = ["auto", "on", "off"];
|
|
3011
3461
|
function resolveClaudeUsageReportingMode(flagValue, env) {
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
}
|
|
3016
|
-
const normalized = raw.trim().toLowerCase();
|
|
3017
|
-
if (VALID_MODES.includes(normalized)) {
|
|
3018
|
-
return { mode: normalized, warnings: [] };
|
|
3019
|
-
}
|
|
3020
|
-
const source = flagValue !== void 0 ? "--claude-usage-reporting" : "EVIDENT_CLAUDE_USAGE_REPORTING";
|
|
3021
|
-
return {
|
|
3022
|
-
mode: "auto",
|
|
3023
|
-
warnings: [
|
|
3024
|
-
`Ignoring invalid ${source} "${raw}": expected one of ${VALID_MODES.join(", ")}; using auto`
|
|
3025
|
-
]
|
|
3026
|
-
};
|
|
3462
|
+
return resolveUsageReportingMode(flagValue, env, {
|
|
3463
|
+
flagName: "--claude-usage-reporting",
|
|
3464
|
+
envVar: "EVIDENT_CLAUDE_USAGE_REPORTING"
|
|
3465
|
+
});
|
|
3027
3466
|
}
|
|
3028
|
-
var BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
3029
|
-
var REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
3030
3467
|
function nextReportDelayMs(random = Math.random) {
|
|
3031
|
-
return
|
|
3468
|
+
return usageReportDelayMs(random);
|
|
3032
3469
|
}
|
|
3033
3470
|
var FIRST_REPORT_DELAY_MS = firstReportDelayMs();
|
|
3034
|
-
var CLAUDE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
3035
3471
|
function claudeUsageFailureLogLevel(consecutiveFailures) {
|
|
3036
|
-
return
|
|
3472
|
+
return usageReportFailureLogLevel(consecutiveFailures);
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
// src/lib/openai-usage-reporting.ts
|
|
3476
|
+
function resolveOpenAiUsageReportingMode(flagValue, env) {
|
|
3477
|
+
return resolveUsageReportingMode(flagValue, env, {
|
|
3478
|
+
flagName: "--openai-usage-reporting",
|
|
3479
|
+
envVar: "EVIDENT_OPENAI_USAGE_REPORTING"
|
|
3480
|
+
});
|
|
3037
3481
|
}
|
|
3038
3482
|
|
|
3039
3483
|
// src/lib/resource-usage-reporting.ts
|
|
@@ -3192,15 +3636,15 @@ function createResourceUsageCollector(homeDir) {
|
|
|
3192
3636
|
}
|
|
3193
3637
|
|
|
3194
3638
|
// src/lib/channels/driver.ts
|
|
3195
|
-
import { homedir as
|
|
3639
|
+
import { homedir as homedir3 } from "os";
|
|
3196
3640
|
|
|
3197
3641
|
// src/lib/runner-file-sync.ts
|
|
3198
|
-
import { join as
|
|
3642
|
+
import { join as join6 } from "path";
|
|
3199
3643
|
|
|
3200
3644
|
// src/lib/file-push.ts
|
|
3201
3645
|
import { randomUUID } from "crypto";
|
|
3202
3646
|
import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
|
|
3203
|
-
import { basename, dirname as dirname3, isAbsolute, join as
|
|
3647
|
+
import { basename, dirname as dirname3, isAbsolute, join as join5, relative, resolve as resolve2, sep } from "path";
|
|
3204
3648
|
var FILE_MODE = 384;
|
|
3205
3649
|
var DIRECTORY_MODE = 448;
|
|
3206
3650
|
async function writePushedFile(request) {
|
|
@@ -3233,7 +3677,7 @@ async function writePushedFile(request) {
|
|
|
3233
3677
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
3234
3678
|
dirname3(candidate)
|
|
3235
3679
|
);
|
|
3236
|
-
const realTarget =
|
|
3680
|
+
const realTarget = join5(existingAncestor, ...missingSegments, basename(candidate));
|
|
3237
3681
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
3238
3682
|
if (allowedDirectory === null) {
|
|
3239
3683
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -3269,7 +3713,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
3269
3713
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
3270
3714
|
return null;
|
|
3271
3715
|
}
|
|
3272
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
3716
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join5(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3273
3717
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
3274
3718
|
return null;
|
|
3275
3719
|
}
|
|
@@ -3342,13 +3786,13 @@ function contains(realDirectory, realTarget) {
|
|
|
3342
3786
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
3343
3787
|
let current = existingAncestor;
|
|
3344
3788
|
for (const segment of missingSegments) {
|
|
3345
|
-
current =
|
|
3789
|
+
current = join5(current, segment);
|
|
3346
3790
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
3347
3791
|
await chmod(current, DIRECTORY_MODE);
|
|
3348
3792
|
}
|
|
3349
3793
|
}
|
|
3350
3794
|
async function writeAtomically(realTarget, content) {
|
|
3351
|
-
const temporaryPath =
|
|
3795
|
+
const temporaryPath = join5(dirname3(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
3352
3796
|
let handle;
|
|
3353
3797
|
try {
|
|
3354
3798
|
handle = await open2(temporaryPath, "wx", FILE_MODE);
|
|
@@ -3390,20 +3834,28 @@ async function syncPendingRunnerFiles(options) {
|
|
|
3390
3834
|
for (const id of options.ackFailures.keys()) {
|
|
3391
3835
|
if (!pendingIds.has(id)) options.ackFailures.delete(id);
|
|
3392
3836
|
}
|
|
3393
|
-
if (pending.length === 0)
|
|
3837
|
+
if (pending.length === 0) {
|
|
3838
|
+
return { applied: 0, claudeCredentialApplied: false, opencodeAuthApplied: false };
|
|
3839
|
+
}
|
|
3394
3840
|
options.log({
|
|
3395
3841
|
level: "info",
|
|
3396
3842
|
message: `Runner file sync: ${pending.length} file(s) queued for this runner`
|
|
3397
3843
|
});
|
|
3398
3844
|
let applied = 0;
|
|
3399
3845
|
let claudeCredentialApplied = false;
|
|
3846
|
+
let opencodeAuthApplied = false;
|
|
3400
3847
|
for (const file of pending) {
|
|
3401
3848
|
if ((options.ackFailures.get(file.id) ?? 0) >= MAX_ACK_ATTEMPTS) continue;
|
|
3402
3849
|
const outcome = await applyOne(options, file);
|
|
3403
3850
|
if (outcome.applied) applied += 1;
|
|
3404
3851
|
if (outcome.claudeCredentialApplied) claudeCredentialApplied = true;
|
|
3852
|
+
if (outcome.opencodeAuthApplied) opencodeAuthApplied = true;
|
|
3405
3853
|
}
|
|
3406
|
-
return {
|
|
3854
|
+
return {
|
|
3855
|
+
applied,
|
|
3856
|
+
claudeCredentialApplied,
|
|
3857
|
+
opencodeAuthApplied
|
|
3858
|
+
};
|
|
3407
3859
|
}
|
|
3408
3860
|
async function listPendingFiles(options) {
|
|
3409
3861
|
let res;
|
|
@@ -3464,10 +3916,18 @@ function asPendingFile(entry) {
|
|
|
3464
3916
|
if (typeof size !== "number" || !Number.isFinite(size) || size < 0) return null;
|
|
3465
3917
|
return { id, path, size };
|
|
3466
3918
|
}
|
|
3467
|
-
var NOT_APPLIED = {
|
|
3919
|
+
var NOT_APPLIED = {
|
|
3920
|
+
applied: false,
|
|
3921
|
+
claudeCredentialApplied: false,
|
|
3922
|
+
opencodeAuthApplied: false
|
|
3923
|
+
};
|
|
3468
3924
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
3469
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
3470
|
-
return expanded ===
|
|
3925
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3926
|
+
return expanded === join6(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
3927
|
+
}
|
|
3928
|
+
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
3929
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3930
|
+
return expanded === join6(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
3471
3931
|
}
|
|
3472
3932
|
async function applyOne(options, file) {
|
|
3473
3933
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -3523,7 +3983,8 @@ async function applyOne(options, file) {
|
|
|
3523
3983
|
await ack(options, file, "applied");
|
|
3524
3984
|
return {
|
|
3525
3985
|
applied: true,
|
|
3526
|
-
claudeCredentialApplied: isClaudeCredentialPath(file.path, options.homeDir)
|
|
3986
|
+
claudeCredentialApplied: isClaudeCredentialPath(file.path, options.homeDir),
|
|
3987
|
+
opencodeAuthApplied: isOpenCodeAuthPath(file.path, options.homeDir)
|
|
3527
3988
|
};
|
|
3528
3989
|
}
|
|
3529
3990
|
function durableDownloadCode(status2) {
|
|
@@ -3977,6 +4438,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3977
4438
|
* that way rather than "fixing" it into a count.
|
|
3978
4439
|
*/
|
|
3979
4440
|
claudeCredentialApplyCount = 0;
|
|
4441
|
+
opencodeAuthApplyCount = 0;
|
|
3980
4442
|
/**
|
|
3981
4443
|
* The currently-executing `drainPending()` promise, or null when idle. Lets a
|
|
3982
4444
|
* graceful shutdown (`waitForInFlight`) await an in-progress drain so a turn it
|
|
@@ -4011,7 +4473,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4011
4473
|
this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
|
|
4012
4474
|
this.now = config.now ?? (() => Date.now());
|
|
4013
4475
|
this.fileSyncDirectories = config.fileSyncDirectories ?? [];
|
|
4014
|
-
this.homeDir = config.homeDir ??
|
|
4476
|
+
this.homeDir = config.homeDir ?? homedir3();
|
|
4015
4477
|
this.maxActiveSessions = config.maxActiveSessions;
|
|
4016
4478
|
this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
|
|
4017
4479
|
this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
|
|
@@ -4081,6 +4543,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4081
4543
|
});
|
|
4082
4544
|
this.appliedFileCount += result.applied;
|
|
4083
4545
|
if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
|
|
4546
|
+
if (result.opencodeAuthApplied) this.opencodeAuthApplyCount += 1;
|
|
4084
4547
|
return result.applied;
|
|
4085
4548
|
} catch (err) {
|
|
4086
4549
|
this.log({
|
|
@@ -4188,7 +4651,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4188
4651
|
return {
|
|
4189
4652
|
appliedFiles: this.appliedFileCount,
|
|
4190
4653
|
inFlight: this.syncingFiles,
|
|
4191
|
-
claudeCredentialApplies: this.claudeCredentialApplyCount
|
|
4654
|
+
claudeCredentialApplies: this.claudeCredentialApplyCount,
|
|
4655
|
+
opencodeAuthApplyCount: this.opencodeAuthApplyCount
|
|
4192
4656
|
};
|
|
4193
4657
|
}
|
|
4194
4658
|
/**
|
|
@@ -5276,6 +5740,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5276
5740
|
deliveryDeadlineAnchored: false,
|
|
5277
5741
|
b2PinnedSinceMs: 0,
|
|
5278
5742
|
b2LastDescendantCheckMs: 0,
|
|
5743
|
+
b2RootOngoingHeldLogged: false,
|
|
5279
5744
|
b2AbandonedSignalled: false,
|
|
5280
5745
|
ambiguousPinnedSinceMs: 0,
|
|
5281
5746
|
ambiguousResolved: false
|
|
@@ -5370,6 +5835,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5370
5835
|
deliveryDeadlineAnchored: false,
|
|
5371
5836
|
b2PinnedSinceMs: 0,
|
|
5372
5837
|
b2LastDescendantCheckMs: 0,
|
|
5838
|
+
b2RootOngoingHeldLogged: false,
|
|
5373
5839
|
b2AbandonedSignalled: false,
|
|
5374
5840
|
ambiguousPinnedSinceMs: 0,
|
|
5375
5841
|
ambiguousResolved: false
|
|
@@ -5723,6 +6189,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5723
6189
|
if (snapshotReadable) {
|
|
5724
6190
|
inFlight.b2PinnedSinceMs = 0;
|
|
5725
6191
|
inFlight.b2LastDescendantCheckMs = 0;
|
|
6192
|
+
inFlight.b2RootOngoingHeldLogged = false;
|
|
5726
6193
|
inFlight.b2AbandonedSignalled = false;
|
|
5727
6194
|
}
|
|
5728
6195
|
} else {
|
|
@@ -5734,11 +6201,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5734
6201
|
const pinnedForMs = this.now() - inFlight.b2PinnedSinceMs;
|
|
5735
6202
|
if (pinnedForMs >= B2_ABANDONMENT_MIN_PINNED_MS && this.now() - inFlight.b2LastDescendantCheckMs >= B2_ABANDONMENT_RECHECK_MS) {
|
|
5736
6203
|
inFlight.b2LastDescendantCheckMs = this.now();
|
|
5737
|
-
const descendantOngoing = await
|
|
6204
|
+
const [descendantOngoing, rootOngoing] = await Promise.all([
|
|
6205
|
+
this.isAnyDescendantSessionOngoing(sessionId),
|
|
6206
|
+
isSessionOngoing(this.port, sessionId)
|
|
6207
|
+
]);
|
|
5738
6208
|
if (isB2AbandonmentConfirmed({
|
|
5739
6209
|
pinnedForMs,
|
|
5740
6210
|
minPinnedMs: B2_ABANDONMENT_MIN_PINNED_MS,
|
|
5741
|
-
descendantOngoing
|
|
6211
|
+
descendantOngoing,
|
|
6212
|
+
rootOngoing
|
|
5742
6213
|
})) {
|
|
5743
6214
|
inFlight.b2AbandonedSignalled = true;
|
|
5744
6215
|
this.log({
|
|
@@ -5747,12 +6218,26 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5747
6218
|
conversation_id: conv.id,
|
|
5748
6219
|
message_id: id
|
|
5749
6220
|
});
|
|
6221
|
+
const reply = findLastAssistantReplyFor(messages, inFlight.opencodeMessageId);
|
|
5750
6222
|
void this.postSignal(conv.id, id, "b2_abandoned_resolved", {
|
|
5751
|
-
watched_for_ms: pinnedForMs
|
|
6223
|
+
watched_for_ms: pinnedForMs,
|
|
6224
|
+
finish: reply?.info?.finish ?? reply?.finish,
|
|
6225
|
+
...rootOngoing != null ? { root_ongoing: rootOngoing } : {},
|
|
6226
|
+
...descendantOngoing != null ? { descendant_ongoing: descendantOngoing } : {},
|
|
6227
|
+
opencode_message_id: inFlight.opencodeMessageId
|
|
5752
6228
|
});
|
|
5753
6229
|
await this.settleMessageDone(sessionId, watcher, inFlight, messages);
|
|
5754
6230
|
return;
|
|
5755
6231
|
}
|
|
6232
|
+
if (rootOngoing === true && !inFlight.b2RootOngoingHeldLogged) {
|
|
6233
|
+
inFlight.b2RootOngoingHeldLogged = true;
|
|
6234
|
+
this.log({
|
|
6235
|
+
level: "warn",
|
|
6236
|
+
message: `Message ${id.slice(0, 8)} b2-pinned for ${Math.round(pinnedForMs / 1e3)}s with root_ongoing=${rootOngoing} and descendant_ongoing=${descendantOngoing} \u2014 holding until OpenCode confirms the root is idle`,
|
|
6237
|
+
conversation_id: conv.id,
|
|
6238
|
+
message_id: id
|
|
6239
|
+
});
|
|
6240
|
+
}
|
|
5756
6241
|
}
|
|
5757
6242
|
}
|
|
5758
6243
|
const ambiguousPinnedNow = activelyRunning && isAmbiguousFinishPinnedRunning(messages, inFlight.opencodeMessageId);
|
|
@@ -7525,7 +8010,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
7525
8010
|
if (trimmed === "") {
|
|
7526
8011
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
7527
8012
|
}
|
|
7528
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
8013
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join7(homeDir, trimmed.slice(2)) : trimmed;
|
|
7529
8014
|
if (!isAbsolute2(expanded)) {
|
|
7530
8015
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
7531
8016
|
}
|
|
@@ -7623,7 +8108,7 @@ function logActivity(state, entry) {
|
|
|
7623
8108
|
const level = entry.level ?? (entry.type === "error" ? "error" : "info");
|
|
7624
8109
|
if (!meetsThreshold(state, level)) return;
|
|
7625
8110
|
forwardRunnerActivity(
|
|
7626
|
-
{ level, message: entry.message, error: entry.error },
|
|
8111
|
+
{ level, message: entry.message, error: entry.error, metadata: entry.metadata },
|
|
7627
8112
|
{ agentId: state.agentId, authHeader: state.authHeader }
|
|
7628
8113
|
);
|
|
7629
8114
|
const fullEntry = {
|
|
@@ -7643,6 +8128,26 @@ function logActivity(state, entry) {
|
|
|
7643
8128
|
}
|
|
7644
8129
|
}
|
|
7645
8130
|
}
|
|
8131
|
+
function reportSessionDbRecovery(state) {
|
|
8132
|
+
try {
|
|
8133
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir4(), env: process.env });
|
|
8134
|
+
for (const record of report.records) {
|
|
8135
|
+
const activity = buildSessionDbRecoveryActivity(record);
|
|
8136
|
+
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
8137
|
+
logActivity(state, {
|
|
8138
|
+
type: activity.level === "error" ? "error" : "info",
|
|
8139
|
+
level: activity.level,
|
|
8140
|
+
...activity.level === "error" ? { error: activity.message } : { message: activity.message },
|
|
8141
|
+
metadata: activity.metadata
|
|
8142
|
+
});
|
|
8143
|
+
}
|
|
8144
|
+
acknowledgeSessionDbRecoveryReport(report.path);
|
|
8145
|
+
} catch (error2) {
|
|
8146
|
+
console.error(
|
|
8147
|
+
`[run] could not report session-DB recovery activity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
8148
|
+
);
|
|
8149
|
+
}
|
|
8150
|
+
}
|
|
7646
8151
|
function displayStatus(state) {
|
|
7647
8152
|
if (!state.interactive) return;
|
|
7648
8153
|
const attempt = state.connection?.reconnectAttempt ?? 0;
|
|
@@ -7733,6 +8238,7 @@ async function driveChannels(state, driver) {
|
|
|
7733
8238
|
let lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
7734
8239
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
7735
8240
|
let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
|
|
8241
|
+
let lastSeenOpencodeAuthApplies = driver.fileSyncActivity().opencodeAuthApplyCount;
|
|
7736
8242
|
while (state.running) {
|
|
7737
8243
|
const cycleStartedAtMs = performance.now();
|
|
7738
8244
|
let idleThisCycle = false;
|
|
@@ -7765,6 +8271,10 @@ async function driveChannels(state, driver) {
|
|
|
7765
8271
|
const claudeCredentialApplied = claudeCredentialApplies !== lastSeenClaudeApplies;
|
|
7766
8272
|
lastSeenClaudeApplies = claudeCredentialApplies;
|
|
7767
8273
|
if (claudeCredentialApplied) state.claudeUsageRearm?.();
|
|
8274
|
+
const opencodeAuthApplies = fileActivitySnapshot.opencodeAuthApplyCount;
|
|
8275
|
+
const opencodeAuthApplied = opencodeAuthApplies !== lastSeenOpencodeAuthApplies;
|
|
8276
|
+
lastSeenOpencodeAuthApplies = opencodeAuthApplies;
|
|
8277
|
+
if (opencodeAuthApplied) state.openaiUsageRearm?.();
|
|
7768
8278
|
if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
|
|
7769
8279
|
idlePolls = 0;
|
|
7770
8280
|
idleMs = 0;
|
|
@@ -7833,7 +8343,7 @@ async function driveChannels(state, driver) {
|
|
|
7833
8343
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
7834
8344
|
var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
|
|
7835
8345
|
function sessionDbPath() {
|
|
7836
|
-
return
|
|
8346
|
+
return join7(homedir4(), ".local", "share", "opencode", "opencode.db");
|
|
7837
8347
|
}
|
|
7838
8348
|
async function runSweep(state, driver, config) {
|
|
7839
8349
|
const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
|
|
@@ -7916,7 +8426,7 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
7916
8426
|
for (const warning2 of config.warnings) {
|
|
7917
8427
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
7918
8428
|
}
|
|
7919
|
-
const dbBytes = statSessionDbBytes(
|
|
8429
|
+
const dbBytes = statSessionDbBytes(homedir4());
|
|
7920
8430
|
void (async () => {
|
|
7921
8431
|
const reclaimSkipReason = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
7922
8432
|
const sizeWarning = buildSessionStoreSizeWarning({
|
|
@@ -7944,23 +8454,20 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
7944
8454
|
);
|
|
7945
8455
|
state.sessionCleanupTimers.push(interval, firstSweep);
|
|
7946
8456
|
}
|
|
7947
|
-
function
|
|
7948
|
-
const { mode, warnings } =
|
|
7949
|
-
options.claudeUsageReporting,
|
|
7950
|
-
process.env
|
|
7951
|
-
);
|
|
8457
|
+
function scheduleUsageReporting(state, params) {
|
|
8458
|
+
const { mode, warnings } = params.resolved;
|
|
7952
8459
|
for (const warning2 of warnings) {
|
|
7953
8460
|
logActivity(state, {
|
|
7954
8461
|
type: "info",
|
|
7955
8462
|
level: "warn",
|
|
7956
|
-
message:
|
|
8463
|
+
message: `${params.label} usage reporting: ${warning2}`
|
|
7957
8464
|
});
|
|
7958
8465
|
}
|
|
7959
8466
|
if (mode === "off") {
|
|
7960
8467
|
logActivity(state, {
|
|
7961
8468
|
type: "info",
|
|
7962
8469
|
level: "debug",
|
|
7963
|
-
message:
|
|
8470
|
+
message: `${params.label} usage reporting is off (${params.offFlagHint})`
|
|
7964
8471
|
});
|
|
7965
8472
|
return null;
|
|
7966
8473
|
}
|
|
@@ -7969,7 +8476,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7969
8476
|
let rearmRequested = false;
|
|
7970
8477
|
const armProbe = () => {
|
|
7971
8478
|
phase = "probe-pending";
|
|
7972
|
-
|
|
8479
|
+
params.setTimer(setTimeout(() => void tick(true), params.firstDelayMs()));
|
|
7973
8480
|
};
|
|
7974
8481
|
const scheduleNextTick = () => {
|
|
7975
8482
|
if (rearmRequested) {
|
|
@@ -7978,7 +8485,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7978
8485
|
return;
|
|
7979
8486
|
}
|
|
7980
8487
|
phase = "steady-pending";
|
|
7981
|
-
|
|
8488
|
+
params.setTimer(setTimeout(() => void tick(false), params.nextDelayMs()));
|
|
7982
8489
|
};
|
|
7983
8490
|
const rearm = () => {
|
|
7984
8491
|
switch (phase) {
|
|
@@ -7988,9 +8495,9 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7988
8495
|
case "probe-pending":
|
|
7989
8496
|
return;
|
|
7990
8497
|
case "steady-pending":
|
|
7991
|
-
if (
|
|
7992
|
-
clearTimeout(
|
|
7993
|
-
|
|
8498
|
+
if (params.getTimer()) {
|
|
8499
|
+
clearTimeout(params.getTimer());
|
|
8500
|
+
params.setTimer(null);
|
|
7994
8501
|
}
|
|
7995
8502
|
rearmRequested = false;
|
|
7996
8503
|
armProbe();
|
|
@@ -8004,45 +8511,45 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8004
8511
|
const tick = async (isProbe) => {
|
|
8005
8512
|
phase = "tick-in-flight";
|
|
8006
8513
|
try {
|
|
8007
|
-
const usage = await
|
|
8008
|
-
const result = await
|
|
8514
|
+
const usage = await params.fetchUsage();
|
|
8515
|
+
const result = await params.report(usage);
|
|
8009
8516
|
if (result.ok) {
|
|
8010
8517
|
if (consecutiveFailures > 0) {
|
|
8011
8518
|
logActivity(state, {
|
|
8012
8519
|
type: "info",
|
|
8013
8520
|
level: "info",
|
|
8014
|
-
message:
|
|
8521
|
+
message: `${params.label} usage reporting recovered`
|
|
8015
8522
|
});
|
|
8016
8523
|
}
|
|
8017
8524
|
consecutiveFailures = 0;
|
|
8018
8525
|
logActivity(state, {
|
|
8019
8526
|
type: "info",
|
|
8020
8527
|
level: "debug",
|
|
8021
|
-
message:
|
|
8528
|
+
message: `Reported ${params.label} usage to Evident`
|
|
8022
8529
|
});
|
|
8023
8530
|
} else {
|
|
8024
8531
|
consecutiveFailures++;
|
|
8025
8532
|
logActivity(state, {
|
|
8026
8533
|
type: "info",
|
|
8027
|
-
level:
|
|
8028
|
-
message: `Failed to report
|
|
8534
|
+
level: params.failureLogLevel(consecutiveFailures),
|
|
8535
|
+
message: `Failed to report ${params.label} usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
|
|
8029
8536
|
});
|
|
8030
8537
|
}
|
|
8031
8538
|
scheduleNextTick();
|
|
8032
8539
|
} catch (error2) {
|
|
8033
|
-
if (
|
|
8540
|
+
if (params.isLocalCredentialProblem(error2)) {
|
|
8034
8541
|
if (mode === "on") {
|
|
8035
8542
|
logActivity(state, {
|
|
8036
8543
|
type: "info",
|
|
8037
8544
|
level: "warn",
|
|
8038
|
-
message:
|
|
8545
|
+
message: `${params.label} usage reporting is forced on but no usable login was found \u2014 ${params.forcedOnHint}; reporting will keep retrying`
|
|
8039
8546
|
});
|
|
8040
8547
|
scheduleNextTick();
|
|
8041
8548
|
} else if (isProbe) {
|
|
8042
8549
|
logActivity(state, {
|
|
8043
8550
|
type: "info",
|
|
8044
8551
|
level: "debug",
|
|
8045
|
-
message:
|
|
8552
|
+
message: `${params.label} usage reporting: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
8046
8553
|
});
|
|
8047
8554
|
phase = "dormant";
|
|
8048
8555
|
if (rearmRequested) rearm();
|
|
@@ -8050,7 +8557,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8050
8557
|
logActivity(state, {
|
|
8051
8558
|
type: "info",
|
|
8052
8559
|
level: "debug",
|
|
8053
|
-
message:
|
|
8560
|
+
message: `${params.label} usage reporting: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
8054
8561
|
});
|
|
8055
8562
|
scheduleNextTick();
|
|
8056
8563
|
}
|
|
@@ -8059,8 +8566,8 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8059
8566
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
8060
8567
|
logActivity(state, {
|
|
8061
8568
|
type: "info",
|
|
8062
|
-
level:
|
|
8063
|
-
message:
|
|
8569
|
+
level: params.failureLogLevel(consecutiveFailures),
|
|
8570
|
+
message: `${params.label} usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
8064
8571
|
});
|
|
8065
8572
|
scheduleNextTick();
|
|
8066
8573
|
}
|
|
@@ -8069,6 +8576,34 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8069
8576
|
armProbe();
|
|
8070
8577
|
return rearm;
|
|
8071
8578
|
}
|
|
8579
|
+
function scheduleClaudeUsageReporting(state, options) {
|
|
8580
|
+
return scheduleUsageReporting(state, {
|
|
8581
|
+
label: "Claude",
|
|
8582
|
+
resolved: resolveClaudeUsageReportingMode(options.claudeUsageReporting, process.env),
|
|
8583
|
+
offFlagHint: "--claude-usage-reporting off",
|
|
8584
|
+
getTimer: () => state.claudeUsageTimer,
|
|
8585
|
+
setTimer: (timer) => {
|
|
8586
|
+
state.claudeUsageTimer = timer;
|
|
8587
|
+
},
|
|
8588
|
+
fetchUsage: async () => {
|
|
8589
|
+
const usage = await getClaudeUsage();
|
|
8590
|
+
if (usage.ownerLookupError) {
|
|
8591
|
+
logActivity(state, {
|
|
8592
|
+
type: "info",
|
|
8593
|
+
level: "debug",
|
|
8594
|
+
message: `Claude usage owner lookup failed: ${usage.ownerLookupError}`
|
|
8595
|
+
});
|
|
8596
|
+
}
|
|
8597
|
+
return usage;
|
|
8598
|
+
},
|
|
8599
|
+
report: (usage) => reportClaudeUsage(state.agentId, state.authHeader, usage),
|
|
8600
|
+
isLocalCredentialProblem,
|
|
8601
|
+
forcedOnHint: "run `claude` to sign in",
|
|
8602
|
+
firstDelayMs: () => FIRST_REPORT_DELAY_MS,
|
|
8603
|
+
nextDelayMs: nextReportDelayMs,
|
|
8604
|
+
failureLogLevel: claudeUsageFailureLogLevel
|
|
8605
|
+
});
|
|
8606
|
+
}
|
|
8072
8607
|
var RESOURCE_USAGE_BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
8073
8608
|
var RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
8074
8609
|
var RESOURCE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
@@ -8092,7 +8627,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
8092
8627
|
});
|
|
8093
8628
|
return;
|
|
8094
8629
|
}
|
|
8095
|
-
const collect = createResourceUsageCollector(
|
|
8630
|
+
const collect = createResourceUsageCollector(homedir4());
|
|
8096
8631
|
let consecutiveFailures = 0;
|
|
8097
8632
|
const tick = async () => {
|
|
8098
8633
|
try {
|
|
@@ -8193,6 +8728,11 @@ async function cleanup(state, opts = {}) {
|
|
|
8193
8728
|
state.claudeUsageTimer = null;
|
|
8194
8729
|
}
|
|
8195
8730
|
state.claudeUsageRearm = null;
|
|
8731
|
+
if (state.openaiUsageTimer) {
|
|
8732
|
+
clearTimeout(state.openaiUsageTimer);
|
|
8733
|
+
state.openaiUsageTimer = null;
|
|
8734
|
+
}
|
|
8735
|
+
state.openaiUsageRearm = null;
|
|
8196
8736
|
if (state.resourceUsageTimer) {
|
|
8197
8737
|
clearTimeout(state.resourceUsageTimer);
|
|
8198
8738
|
state.resourceUsageTimer = null;
|
|
@@ -8244,7 +8784,7 @@ async function run(options) {
|
|
|
8244
8784
|
let fileSyncDirectories;
|
|
8245
8785
|
try {
|
|
8246
8786
|
logLevel = resolveLogLevel(options);
|
|
8247
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
8787
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir4());
|
|
8248
8788
|
} catch (error2) {
|
|
8249
8789
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
8250
8790
|
if (options.json) {
|
|
@@ -8279,6 +8819,8 @@ async function run(options) {
|
|
|
8279
8819
|
sessionCleanupTimers: [],
|
|
8280
8820
|
claudeUsageTimer: null,
|
|
8281
8821
|
claudeUsageRearm: null,
|
|
8822
|
+
openaiUsageTimer: null,
|
|
8823
|
+
openaiUsageRearm: null,
|
|
8282
8824
|
resourceUsageTimer: null,
|
|
8283
8825
|
authHeader: ""
|
|
8284
8826
|
};
|
|
@@ -8474,6 +9016,7 @@ async function run(options) {
|
|
|
8474
9016
|
} else {
|
|
8475
9017
|
log2(state, "Not running in a MicroVM (MICROVM_ID unset) \u2014 nothing to report", "debug");
|
|
8476
9018
|
}
|
|
9019
|
+
reportSessionDbRecovery(state);
|
|
8477
9020
|
const { timeoutMs: opencodeStartTimeoutMs, warnings: opencodeStartTimeoutWarnings } = resolveOpenCodeStartTimeoutMs(options, process.env);
|
|
8478
9021
|
for (const warning2 of opencodeStartTimeoutWarnings) {
|
|
8479
9022
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
@@ -8541,7 +9084,7 @@ async function run(options) {
|
|
|
8541
9084
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
8542
9085
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
8543
9086
|
fileSyncDirectories,
|
|
8544
|
-
homeDir:
|
|
9087
|
+
homeDir: homedir4(),
|
|
8545
9088
|
maxActiveSessions,
|
|
8546
9089
|
log: (entry) => (
|
|
8547
9090
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -8679,6 +9222,22 @@ async function run(options) {
|
|
|
8679
9222
|
}
|
|
8680
9223
|
scheduleSessionCleanup(state, channelDriver, options);
|
|
8681
9224
|
state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
|
|
9225
|
+
state.openaiUsageRearm = scheduleUsageReporting(state, {
|
|
9226
|
+
label: "OpenAI",
|
|
9227
|
+
resolved: resolveOpenAiUsageReportingMode(options.openaiUsageReporting, process.env),
|
|
9228
|
+
offFlagHint: "--openai-usage-reporting off",
|
|
9229
|
+
getTimer: () => state.openaiUsageTimer,
|
|
9230
|
+
setTimer: (timer) => {
|
|
9231
|
+
state.openaiUsageTimer = timer;
|
|
9232
|
+
},
|
|
9233
|
+
fetchUsage: () => getOpenAiUsage(state.port),
|
|
9234
|
+
report: (usage) => reportOpenAiUsage(state.agentId, state.authHeader, usage),
|
|
9235
|
+
isLocalCredentialProblem: isLocalCredentialProblem2,
|
|
9236
|
+
forcedOnHint: "connect a ChatGPT account to this runner, or run `opencode auth login`",
|
|
9237
|
+
firstDelayMs: firstReportDelayMs,
|
|
9238
|
+
nextDelayMs: usageReportDelayMs,
|
|
9239
|
+
failureLogLevel: usageReportFailureLogLevel
|
|
9240
|
+
});
|
|
8682
9241
|
scheduleResourceUsageReporting(state, options);
|
|
8683
9242
|
if (!interactive || state.json) {
|
|
8684
9243
|
log2(state, "Driving channel messages...");
|
|
@@ -8760,6 +9319,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
8760
9319
|
).option(
|
|
8761
9320
|
"--claude-usage-reporting <mode>",
|
|
8762
9321
|
"Report Claude subscription usage to Evident: auto | on | off (default: auto). Env: EVIDENT_CLAUDE_USAGE_REPORTING"
|
|
9322
|
+
).option(
|
|
9323
|
+
"--openai-usage-reporting <mode>",
|
|
9324
|
+
"Report OpenAI plan usage to Evident: auto | on | off (default: auto). Env: EVIDENT_OPENAI_USAGE_REPORTING"
|
|
8763
9325
|
).option(
|
|
8764
9326
|
"--no-resource-usage-reporting",
|
|
8765
9327
|
"Don't report this machine's CPU and memory usage to Evident (reporting is on by default). Env: EVIDENT_RESOURCE_USAGE_REPORTING=off"
|
|
@@ -8795,6 +9357,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
8795
9357
|
// Raw string — the resolver in run.ts single-sources parsing
|
|
8796
9358
|
// (resolveClaudeUsageReportingMode).
|
|
8797
9359
|
claudeUsageReporting: options.claudeUsageReporting,
|
|
9360
|
+
openaiUsageReporting: options.openaiUsageReporting,
|
|
8798
9361
|
// Raw value — resolution is single-sourced in run.ts's
|
|
8799
9362
|
// resolveResourceUsageReportingEnabled.
|
|
8800
9363
|
resourceUsageReporting: options.resourceUsageReporting,
|