@evident-ai/cli 3.4.1-dev.8fa4d29 → 3.4.1-dev.aa71e9a
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 +540 -72
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -746,6 +746,40 @@ async function reportClaudeUsage(agentId, authHeader, snapshot) {
|
|
|
746
746
|
return { ok: false, error: describeBestEffortError(error2) };
|
|
747
747
|
}
|
|
748
748
|
}
|
|
749
|
+
function toReportedOpenAiWindow(window) {
|
|
750
|
+
if (!window) return null;
|
|
751
|
+
return {
|
|
752
|
+
utilization: window.utilization,
|
|
753
|
+
window_minutes: window.windowMinutes,
|
|
754
|
+
resets_at: window.resetsAt
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
async function reportOpenAiUsage(agentId, authHeader, snapshot) {
|
|
758
|
+
try {
|
|
759
|
+
const apiUrl = getApiUrlConfig();
|
|
760
|
+
const response = await fetch(`${apiUrl}/runners/${agentId}/openai-usage`, {
|
|
761
|
+
method: "POST",
|
|
762
|
+
headers: { Authorization: authHeader, "Content-Type": "application/json" },
|
|
763
|
+
body: JSON.stringify({
|
|
764
|
+
primary: toReportedOpenAiWindow(snapshot.primary),
|
|
765
|
+
secondary: toReportedOpenAiWindow(snapshot.secondary),
|
|
766
|
+
has_credits: snapshot.hasCredits,
|
|
767
|
+
credits_unlimited: snapshot.creditsUnlimited
|
|
768
|
+
}),
|
|
769
|
+
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
770
|
+
});
|
|
771
|
+
if (!response.ok) {
|
|
772
|
+
const serverMessage = await readErrorMessage(response);
|
|
773
|
+
return {
|
|
774
|
+
ok: false,
|
|
775
|
+
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
return { ok: true };
|
|
779
|
+
} catch (error2) {
|
|
780
|
+
return { ok: false, error: describeBestEffortError(error2) };
|
|
781
|
+
}
|
|
782
|
+
}
|
|
749
783
|
async function reportResourceUsage(agentId, authHeader, usage) {
|
|
750
784
|
try {
|
|
751
785
|
const apiUrl = getApiUrlConfig();
|
|
@@ -1092,8 +1126,8 @@ async function claudeUsage() {
|
|
|
1092
1126
|
}
|
|
1093
1127
|
|
|
1094
1128
|
// src/commands/run.ts
|
|
1095
|
-
import { homedir as
|
|
1096
|
-
import { isAbsolute as isAbsolute2, join as
|
|
1129
|
+
import { homedir as homedir4 } from "os";
|
|
1130
|
+
import { isAbsolute as isAbsolute2, join as join7, parse, resolve as resolvePath } from "path";
|
|
1097
1131
|
import chalk6 from "chalk";
|
|
1098
1132
|
|
|
1099
1133
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -1326,6 +1360,8 @@ var SEVERITY_BY_LEVEL = {
|
|
|
1326
1360
|
error: "error"
|
|
1327
1361
|
};
|
|
1328
1362
|
var MAX_MESSAGE_LENGTH = 500;
|
|
1363
|
+
var MAX_METADATA_VALUE_LENGTH = 200;
|
|
1364
|
+
var MAX_METADATA_ENTRIES = 20;
|
|
1329
1365
|
var TRUNCATION_MARKER = "\u2026";
|
|
1330
1366
|
function redact(message) {
|
|
1331
1367
|
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 +1370,24 @@ function truncate(message) {
|
|
|
1334
1370
|
if (message.length <= MAX_MESSAGE_LENGTH) return message;
|
|
1335
1371
|
return message.slice(0, MAX_MESSAGE_LENGTH - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
1336
1372
|
}
|
|
1373
|
+
function sanitiseMetadata(metadata) {
|
|
1374
|
+
if (!metadata) return {};
|
|
1375
|
+
const entries = Object.entries(metadata).slice(0, MAX_METADATA_ENTRIES);
|
|
1376
|
+
if (Object.keys(metadata).length > entries.length) {
|
|
1377
|
+
console.error(
|
|
1378
|
+
`[runner-activity-telemetry] dropped ${Object.keys(metadata).length - entries.length} metadata entries`
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
const sanitised = [];
|
|
1382
|
+
for (const [key, value] of entries) {
|
|
1383
|
+
if (typeof value === "string") {
|
|
1384
|
+
sanitised.push([key, truncate(redact(value).slice(0, MAX_METADATA_VALUE_LENGTH))]);
|
|
1385
|
+
} else if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
|
|
1386
|
+
sanitised.push([key, value]);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
return Object.fromEntries(sanitised);
|
|
1390
|
+
}
|
|
1337
1391
|
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
1338
1392
|
var RATE_LIMIT_MAX_EVENTS = 30;
|
|
1339
1393
|
var windowStartedAt = 0;
|
|
@@ -1372,7 +1426,7 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1372
1426
|
logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
|
|
1373
1427
|
severity: SEVERITY_BY_LEVEL[entry.level],
|
|
1374
1428
|
message,
|
|
1375
|
-
metadata: { source: "cli.run" },
|
|
1429
|
+
metadata: { ...sanitiseMetadata(entry.metadata), source: "cli.run" },
|
|
1376
1430
|
agentId: context.agentId
|
|
1377
1431
|
});
|
|
1378
1432
|
} catch (err) {
|
|
@@ -1382,6 +1436,143 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1382
1436
|
}
|
|
1383
1437
|
}
|
|
1384
1438
|
|
|
1439
|
+
// src/lib/opencode/session-db-recovery-report.ts
|
|
1440
|
+
import { readFileSync as readFileSync2, unlinkSync } from "fs";
|
|
1441
|
+
import { join as join2 } from "path";
|
|
1442
|
+
function sessionDbRecoveryReportPath(homeDir, env) {
|
|
1443
|
+
const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
|
|
1444
|
+
return override || join2(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
|
|
1445
|
+
}
|
|
1446
|
+
function drainSessionDbRecoveryReport({
|
|
1447
|
+
homeDir,
|
|
1448
|
+
env
|
|
1449
|
+
}) {
|
|
1450
|
+
const path = sessionDbRecoveryReportPath(homeDir, env);
|
|
1451
|
+
let content;
|
|
1452
|
+
try {
|
|
1453
|
+
content = readFileSync2(path, "utf8");
|
|
1454
|
+
} catch (error2) {
|
|
1455
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
|
|
1456
|
+
return { path, records: [], skippedLines: 0, readError: null };
|
|
1457
|
+
const readError = error2 instanceof Error ? error2.message : String(error2);
|
|
1458
|
+
console.error(`[session-db-recovery-report] could not read ${path}: ${readError}`);
|
|
1459
|
+
return { path, records: [], skippedLines: 0, readError };
|
|
1460
|
+
}
|
|
1461
|
+
let skippedLines = 0;
|
|
1462
|
+
const records = content.split("\n").flatMap((line) => {
|
|
1463
|
+
if (!line.trim()) return [];
|
|
1464
|
+
try {
|
|
1465
|
+
const value = JSON.parse(line);
|
|
1466
|
+
if (!isSessionDbRecoveryRecord(value)) {
|
|
1467
|
+
skippedLines++;
|
|
1468
|
+
return [];
|
|
1469
|
+
}
|
|
1470
|
+
return [value];
|
|
1471
|
+
} catch (error2) {
|
|
1472
|
+
skippedLines++;
|
|
1473
|
+
console.error(
|
|
1474
|
+
`[session-db-recovery-report] skipped malformed record in ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
1475
|
+
);
|
|
1476
|
+
return [];
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
return { path, records, skippedLines, readError: null };
|
|
1480
|
+
}
|
|
1481
|
+
function acknowledgeSessionDbRecoveryReport(path) {
|
|
1482
|
+
try {
|
|
1483
|
+
unlinkSync(path);
|
|
1484
|
+
} catch (error2) {
|
|
1485
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return;
|
|
1486
|
+
console.error(
|
|
1487
|
+
`[session-db-recovery-report] could not remove ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
function buildSessionDbRecoveryActivity(record) {
|
|
1492
|
+
const level = record.severity === "warning" ? "warn" : record.severity === "error" ? "error" : null;
|
|
1493
|
+
if (!level) return null;
|
|
1494
|
+
const noVerifiedPoint = record.verified_restore_point ? ` The verified restore point is ${record.verified_restore_point}.` : " No verified restore point is known.";
|
|
1495
|
+
switch (record.outcome) {
|
|
1496
|
+
case "fresh_session_db":
|
|
1497
|
+
return {
|
|
1498
|
+
level,
|
|
1499
|
+
metadata: withoutContractFields(record),
|
|
1500
|
+
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.`
|
|
1501
|
+
};
|
|
1502
|
+
case "restore_retried":
|
|
1503
|
+
return {
|
|
1504
|
+
level,
|
|
1505
|
+
metadata: withoutContractFields(record),
|
|
1506
|
+
message: "The initial session database restore failed; the runner will retry it. Monitor the runner backup for another restore failure."
|
|
1507
|
+
};
|
|
1508
|
+
case "replica_recovered":
|
|
1509
|
+
if (record.reason === "quarantine")
|
|
1510
|
+
return {
|
|
1511
|
+
level,
|
|
1512
|
+
metadata: withoutContractFields(record),
|
|
1513
|
+
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.`
|
|
1514
|
+
};
|
|
1515
|
+
if (record.reason === "prune")
|
|
1516
|
+
return {
|
|
1517
|
+
level,
|
|
1518
|
+
metadata: withoutContractFields(record),
|
|
1519
|
+
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."
|
|
1520
|
+
};
|
|
1521
|
+
if (record.reason === "clear")
|
|
1522
|
+
return {
|
|
1523
|
+
level,
|
|
1524
|
+
metadata: withoutContractFields(record),
|
|
1525
|
+
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."
|
|
1526
|
+
};
|
|
1527
|
+
return null;
|
|
1528
|
+
case "history_rolled_back":
|
|
1529
|
+
return {
|
|
1530
|
+
level,
|
|
1531
|
+
metadata: withoutContractFields(record),
|
|
1532
|
+
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.`
|
|
1533
|
+
};
|
|
1534
|
+
case "restore_misconfigured":
|
|
1535
|
+
return {
|
|
1536
|
+
level,
|
|
1537
|
+
metadata: withoutContractFields(record),
|
|
1538
|
+
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."
|
|
1539
|
+
};
|
|
1540
|
+
default:
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
function withoutContractFields(record) {
|
|
1545
|
+
const { v: _v, event: _event, ...metadata } = record;
|
|
1546
|
+
return metadata;
|
|
1547
|
+
}
|
|
1548
|
+
var OUTCOMES = /* @__PURE__ */ new Set([
|
|
1549
|
+
"replica_recovered",
|
|
1550
|
+
"restore_retried",
|
|
1551
|
+
"fresh_session_db",
|
|
1552
|
+
"history_rolled_back",
|
|
1553
|
+
"restore_misconfigured"
|
|
1554
|
+
]);
|
|
1555
|
+
var STAGES = /* @__PURE__ */ new Set(["restore", "verify"]);
|
|
1556
|
+
var SEVERITIES = /* @__PURE__ */ new Set(["warning", "error"]);
|
|
1557
|
+
var NUMBER_FIELDS = [
|
|
1558
|
+
"litestream_exit_code",
|
|
1559
|
+
"attempt",
|
|
1560
|
+
"replica_objects",
|
|
1561
|
+
"replica_bytes",
|
|
1562
|
+
"quarantined_objects",
|
|
1563
|
+
"quarantine_failed_objects",
|
|
1564
|
+
"quarantined_bytes",
|
|
1565
|
+
"restore_points_tried"
|
|
1566
|
+
];
|
|
1567
|
+
var STRING_OR_NULL_FIELDS = ["quarantine_destination", "verified_restore_point"];
|
|
1568
|
+
function isSessionDbRecoveryRecord(value) {
|
|
1569
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1570
|
+
const record = value;
|
|
1571
|
+
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(
|
|
1572
|
+
(field) => record[field] === null || typeof record[field] === "string"
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1385
1576
|
// src/lib/opencode/health.ts
|
|
1386
1577
|
async function checkOpenCodeHealth(port) {
|
|
1387
1578
|
try {
|
|
@@ -2419,10 +2610,10 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
2419
2610
|
|
|
2420
2611
|
// src/lib/opencode/session-db-size.ts
|
|
2421
2612
|
import { statSync as statSync2 } from "fs";
|
|
2422
|
-
import { join as
|
|
2613
|
+
import { join as join3 } from "path";
|
|
2423
2614
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
2424
2615
|
function statSessionDbBytes(homeDir) {
|
|
2425
|
-
const dbPath =
|
|
2616
|
+
const dbPath = join3(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
2426
2617
|
try {
|
|
2427
2618
|
return statSync2(dbPath).size;
|
|
2428
2619
|
} catch (err) {
|
|
@@ -2991,6 +3182,177 @@ function writeTunnelReadyMarker(path, agentId) {
|
|
|
2991
3182
|
}
|
|
2992
3183
|
}
|
|
2993
3184
|
|
|
3185
|
+
// src/lib/openai-usage.ts
|
|
3186
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
3187
|
+
import { homedir as homedir2 } from "os";
|
|
3188
|
+
import { join as join4 } from "path";
|
|
3189
|
+
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
3190
|
+
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
3191
|
+
var OpenAiUsageError = class extends Error {
|
|
3192
|
+
constructor(message, reason) {
|
|
3193
|
+
super(message);
|
|
3194
|
+
this.reason = reason;
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3197
|
+
function isLocalCredentialProblem2(err) {
|
|
3198
|
+
return err instanceof OpenAiUsageError && (err.reason === "no_credentials" || err.reason === "credentials_expired");
|
|
3199
|
+
}
|
|
3200
|
+
function readOpenCodeChatGptCredentials() {
|
|
3201
|
+
try {
|
|
3202
|
+
const raw = readFileSync3(join4(homedir2(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
3203
|
+
let parsed;
|
|
3204
|
+
try {
|
|
3205
|
+
parsed = JSON.parse(raw);
|
|
3206
|
+
} catch {
|
|
3207
|
+
return null;
|
|
3208
|
+
}
|
|
3209
|
+
const entry = parsed.openai;
|
|
3210
|
+
if (entry?.type !== "oauth" || typeof entry.access !== "string" || !entry.access || typeof entry.expires !== "number") {
|
|
3211
|
+
return null;
|
|
3212
|
+
}
|
|
3213
|
+
return { accessToken: entry.access, expiresAt: entry.expires };
|
|
3214
|
+
} catch (err) {
|
|
3215
|
+
const code = err.code;
|
|
3216
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
3217
|
+
console.warn(
|
|
3218
|
+
`readOpenCodeChatGptCredentials: reading auth.json failed (${code ?? "unknown"})`
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
return null;
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
function toWindow2(headers, name) {
|
|
3225
|
+
const utilizationHeader = headers.get(`x-codex-${name}-used-percent`);
|
|
3226
|
+
const windowMinutesHeader = headers.get(`x-codex-${name}-window-minutes`);
|
|
3227
|
+
if (utilizationHeader == null || utilizationHeader === "" || windowMinutesHeader == null) {
|
|
3228
|
+
return null;
|
|
3229
|
+
}
|
|
3230
|
+
const utilization = Number(utilizationHeader);
|
|
3231
|
+
const windowMinutes = Number(windowMinutesHeader);
|
|
3232
|
+
if (!Number.isFinite(utilization) || !Number.isFinite(windowMinutes) || windowMinutes <= 0) {
|
|
3233
|
+
return null;
|
|
3234
|
+
}
|
|
3235
|
+
const resetAtHeader = headers.get(`x-codex-${name}-reset-at`);
|
|
3236
|
+
const resetSeconds = resetAtHeader == null || resetAtHeader === "" ? NaN : Number(resetAtHeader);
|
|
3237
|
+
const resetsAt = Number.isFinite(resetSeconds) ? new Date(resetSeconds * 1e3).toISOString() : null;
|
|
3238
|
+
return { utilization: Math.min(100, Math.max(0, utilization)), windowMinutes, resetsAt };
|
|
3239
|
+
}
|
|
3240
|
+
function parseCodexUsageHeaders(headers) {
|
|
3241
|
+
return {
|
|
3242
|
+
primary: toWindow2(headers, "primary"),
|
|
3243
|
+
secondary: toWindow2(headers, "secondary"),
|
|
3244
|
+
hasCredits: headers.has("x-codex-credits-has-credits") ? headers.get("x-codex-credits-has-credits")?.toLowerCase() === "true" : null,
|
|
3245
|
+
creditsUnlimited: headers.has("x-codex-credits-unlimited") ? headers.get("x-codex-credits-unlimited")?.toLowerCase() === "true" : null
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
function normalizeProbeModel(model) {
|
|
3249
|
+
return model.endsWith("-fast") ? model.slice(0, -"-fast".length) : model;
|
|
3250
|
+
}
|
|
3251
|
+
async function resolveProbeModels(port) {
|
|
3252
|
+
try {
|
|
3253
|
+
const res = await withRequestTimeout(
|
|
3254
|
+
fetch,
|
|
3255
|
+
REQUEST_TIMEOUT_MS
|
|
3256
|
+
)(`${opencodeBase(port)}/config/providers`);
|
|
3257
|
+
if (!res.ok) {
|
|
3258
|
+
console.error(
|
|
3259
|
+
`[resolveProbeModels] GET /config/providers returned HTTP ${res.status} (port ${port})`
|
|
3260
|
+
);
|
|
3261
|
+
return [];
|
|
3262
|
+
}
|
|
3263
|
+
const body = await res.json();
|
|
3264
|
+
const provider = body?.providers?.find((candidate) => candidate?.id === "openai");
|
|
3265
|
+
if (!provider || !provider.models || typeof provider.models !== "object") return [];
|
|
3266
|
+
const candidates = [
|
|
3267
|
+
...typeof body?.default?.openai === "string" ? [body.default.openai] : [],
|
|
3268
|
+
...Object.keys(provider.models)
|
|
3269
|
+
].map(normalizeProbeModel);
|
|
3270
|
+
return [...new Set(candidates)].slice(0, 4);
|
|
3271
|
+
} catch (err) {
|
|
3272
|
+
console.error(
|
|
3273
|
+
`[resolveProbeModels] GET /config/providers failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3274
|
+
);
|
|
3275
|
+
return [];
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
function hasPrimaryHeaders(headers) {
|
|
3279
|
+
return [
|
|
3280
|
+
"x-codex-primary-used-percent",
|
|
3281
|
+
"x-codex-primary-window-minutes",
|
|
3282
|
+
"x-codex-primary-reset-at"
|
|
3283
|
+
].some((name) => headers.has(name));
|
|
3284
|
+
}
|
|
3285
|
+
async function getOpenAiUsage(port) {
|
|
3286
|
+
const credentials2 = readOpenCodeChatGptCredentials();
|
|
3287
|
+
if (!credentials2) {
|
|
3288
|
+
throw new OpenAiUsageError(
|
|
3289
|
+
"No ChatGPT login found. Connect a ChatGPT account to this runner, or run `opencode auth login`.",
|
|
3290
|
+
"no_credentials"
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
3293
|
+
if (credentials2.expiresAt < Date.now()) {
|
|
3294
|
+
throw new OpenAiUsageError(
|
|
3295
|
+
"ChatGPT credentials have expired. Run `opencode auth login` to refresh them.",
|
|
3296
|
+
"credentials_expired"
|
|
3297
|
+
);
|
|
3298
|
+
}
|
|
3299
|
+
const models = await resolveProbeModels(port);
|
|
3300
|
+
if (models.length === 0) {
|
|
3301
|
+
throw new OpenAiUsageError("No supported OpenAI probe model is available.", "no_probe_model");
|
|
3302
|
+
}
|
|
3303
|
+
let lastStatus;
|
|
3304
|
+
for (const model of models) {
|
|
3305
|
+
let res;
|
|
3306
|
+
try {
|
|
3307
|
+
res = await withRequestTimeout(fetch, REQUEST_TIMEOUT_MS)(CODEX_RESPONSES_URL, {
|
|
3308
|
+
method: "POST",
|
|
3309
|
+
headers: {
|
|
3310
|
+
Authorization: `Bearer ${credentials2.accessToken}`,
|
|
3311
|
+
"Content-Type": "application/json"
|
|
3312
|
+
},
|
|
3313
|
+
body: JSON.stringify({ model, store: false, stream: true })
|
|
3314
|
+
});
|
|
3315
|
+
} catch (err) {
|
|
3316
|
+
throw new OpenAiUsageError(
|
|
3317
|
+
`OpenAI usage probe request failed: ${err instanceof Error ? err.message : String(err)}.`,
|
|
3318
|
+
"request_failed"
|
|
3319
|
+
);
|
|
3320
|
+
}
|
|
3321
|
+
try {
|
|
3322
|
+
lastStatus = res.status;
|
|
3323
|
+
if (hasPrimaryHeaders(res.headers)) {
|
|
3324
|
+
const usage = parseCodexUsageHeaders(res.headers);
|
|
3325
|
+
if (!usage.primary && !usage.secondary) {
|
|
3326
|
+
throw new OpenAiUsageError(
|
|
3327
|
+
`OpenAI usage probe returned no usable window (HTTP ${res.status}).`,
|
|
3328
|
+
"no_usable_window"
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
return usage;
|
|
3332
|
+
}
|
|
3333
|
+
if (res.status === 401) {
|
|
3334
|
+
throw new OpenAiUsageError(
|
|
3335
|
+
"ChatGPT credentials have expired (HTTP 401).",
|
|
3336
|
+
"credentials_expired"
|
|
3337
|
+
);
|
|
3338
|
+
}
|
|
3339
|
+
if (res.status === 403 || res.status === 429) {
|
|
3340
|
+
throw new OpenAiUsageError(
|
|
3341
|
+
`OpenAI usage probe was blocked (HTTP ${res.status}).`,
|
|
3342
|
+
"probe_blocked"
|
|
3343
|
+
);
|
|
3344
|
+
}
|
|
3345
|
+
} finally {
|
|
3346
|
+
await res.body?.cancel().catch(() => {
|
|
3347
|
+
});
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
throw new OpenAiUsageError(
|
|
3351
|
+
`OpenAI usage probe failed: HTTP ${lastStatus ?? "unknown"}.`,
|
|
3352
|
+
"request_failed"
|
|
3353
|
+
);
|
|
3354
|
+
}
|
|
3355
|
+
|
|
2994
3356
|
// src/lib/reporting-schedule.ts
|
|
2995
3357
|
function jitteredDelayMs(baseMs, jitterFraction, random = Math.random) {
|
|
2996
3358
|
const jitterRangeMs = baseMs * jitterFraction;
|
|
@@ -2999,6 +3361,31 @@ function jitteredDelayMs(baseMs, jitterFraction, random = Math.random) {
|
|
|
2999
3361
|
function firstReportDelayMs(random = Math.random) {
|
|
3000
3362
|
return 5e3 + random() * 1e4;
|
|
3001
3363
|
}
|
|
3364
|
+
var VALID_USAGE_REPORTING_MODES = ["auto", "on", "off"];
|
|
3365
|
+
function resolveUsageReportingMode(flagValue, env, names) {
|
|
3366
|
+
const raw = flagValue ?? env[names.envVar];
|
|
3367
|
+
if (raw === void 0 || raw === "") return { mode: "auto", warnings: [] };
|
|
3368
|
+
const normalized = raw.trim().toLowerCase();
|
|
3369
|
+
if (VALID_USAGE_REPORTING_MODES.includes(normalized)) {
|
|
3370
|
+
return { mode: normalized, warnings: [] };
|
|
3371
|
+
}
|
|
3372
|
+
const source = flagValue !== void 0 ? names.flagName : names.envVar;
|
|
3373
|
+
return {
|
|
3374
|
+
mode: "auto",
|
|
3375
|
+
warnings: [
|
|
3376
|
+
`Ignoring invalid ${source} "${raw}": expected one of ${VALID_USAGE_REPORTING_MODES.join(", ")}; using auto`
|
|
3377
|
+
]
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
var BASE_USAGE_REPORT_DELAY_MS = 10 * 6e4;
|
|
3381
|
+
var USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
3382
|
+
function usageReportDelayMs(random = Math.random) {
|
|
3383
|
+
return jitteredDelayMs(BASE_USAGE_REPORT_DELAY_MS, USAGE_REPORT_DELAY_JITTER_FRACTION, random);
|
|
3384
|
+
}
|
|
3385
|
+
var USAGE_REPORT_FAILURE_REESCALATION_TICKS = 6;
|
|
3386
|
+
function usageReportFailureLogLevel(consecutiveFailures) {
|
|
3387
|
+
return reportFailureLogLevel(consecutiveFailures, USAGE_REPORT_FAILURE_REESCALATION_TICKS);
|
|
3388
|
+
}
|
|
3002
3389
|
function reportFailureLogLevel(consecutiveFailures, reescalationTicks) {
|
|
3003
3390
|
return consecutiveFailures === 1 || consecutiveFailures % reescalationTicks === 0 ? "warn" : "debug";
|
|
3004
3391
|
}
|
|
@@ -3007,33 +3394,26 @@ function failureStreakSuffix(consecutiveFailures) {
|
|
|
3007
3394
|
}
|
|
3008
3395
|
|
|
3009
3396
|
// src/lib/claude-usage-reporting.ts
|
|
3010
|
-
var VALID_MODES = ["auto", "on", "off"];
|
|
3011
3397
|
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
|
-
};
|
|
3398
|
+
return resolveUsageReportingMode(flagValue, env, {
|
|
3399
|
+
flagName: "--claude-usage-reporting",
|
|
3400
|
+
envVar: "EVIDENT_CLAUDE_USAGE_REPORTING"
|
|
3401
|
+
});
|
|
3027
3402
|
}
|
|
3028
|
-
var BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
3029
|
-
var REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
3030
3403
|
function nextReportDelayMs(random = Math.random) {
|
|
3031
|
-
return
|
|
3404
|
+
return usageReportDelayMs(random);
|
|
3032
3405
|
}
|
|
3033
3406
|
var FIRST_REPORT_DELAY_MS = firstReportDelayMs();
|
|
3034
|
-
var CLAUDE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
3035
3407
|
function claudeUsageFailureLogLevel(consecutiveFailures) {
|
|
3036
|
-
return
|
|
3408
|
+
return usageReportFailureLogLevel(consecutiveFailures);
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
// src/lib/openai-usage-reporting.ts
|
|
3412
|
+
function resolveOpenAiUsageReportingMode(flagValue, env) {
|
|
3413
|
+
return resolveUsageReportingMode(flagValue, env, {
|
|
3414
|
+
flagName: "--openai-usage-reporting",
|
|
3415
|
+
envVar: "EVIDENT_OPENAI_USAGE_REPORTING"
|
|
3416
|
+
});
|
|
3037
3417
|
}
|
|
3038
3418
|
|
|
3039
3419
|
// src/lib/resource-usage-reporting.ts
|
|
@@ -3192,15 +3572,15 @@ function createResourceUsageCollector(homeDir) {
|
|
|
3192
3572
|
}
|
|
3193
3573
|
|
|
3194
3574
|
// src/lib/channels/driver.ts
|
|
3195
|
-
import { homedir as
|
|
3575
|
+
import { homedir as homedir3 } from "os";
|
|
3196
3576
|
|
|
3197
3577
|
// src/lib/runner-file-sync.ts
|
|
3198
|
-
import { join as
|
|
3578
|
+
import { join as join6 } from "path";
|
|
3199
3579
|
|
|
3200
3580
|
// src/lib/file-push.ts
|
|
3201
3581
|
import { randomUUID } from "crypto";
|
|
3202
3582
|
import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
|
|
3203
|
-
import { basename, dirname as dirname3, isAbsolute, join as
|
|
3583
|
+
import { basename, dirname as dirname3, isAbsolute, join as join5, relative, resolve as resolve2, sep } from "path";
|
|
3204
3584
|
var FILE_MODE = 384;
|
|
3205
3585
|
var DIRECTORY_MODE = 448;
|
|
3206
3586
|
async function writePushedFile(request) {
|
|
@@ -3233,7 +3613,7 @@ async function writePushedFile(request) {
|
|
|
3233
3613
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
3234
3614
|
dirname3(candidate)
|
|
3235
3615
|
);
|
|
3236
|
-
const realTarget =
|
|
3616
|
+
const realTarget = join5(existingAncestor, ...missingSegments, basename(candidate));
|
|
3237
3617
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
3238
3618
|
if (allowedDirectory === null) {
|
|
3239
3619
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -3269,7 +3649,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
3269
3649
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
3270
3650
|
return null;
|
|
3271
3651
|
}
|
|
3272
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
3652
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join5(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3273
3653
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
3274
3654
|
return null;
|
|
3275
3655
|
}
|
|
@@ -3342,13 +3722,13 @@ function contains(realDirectory, realTarget) {
|
|
|
3342
3722
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
3343
3723
|
let current = existingAncestor;
|
|
3344
3724
|
for (const segment of missingSegments) {
|
|
3345
|
-
current =
|
|
3725
|
+
current = join5(current, segment);
|
|
3346
3726
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
3347
3727
|
await chmod(current, DIRECTORY_MODE);
|
|
3348
3728
|
}
|
|
3349
3729
|
}
|
|
3350
3730
|
async function writeAtomically(realTarget, content) {
|
|
3351
|
-
const temporaryPath =
|
|
3731
|
+
const temporaryPath = join5(dirname3(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
3352
3732
|
let handle;
|
|
3353
3733
|
try {
|
|
3354
3734
|
handle = await open2(temporaryPath, "wx", FILE_MODE);
|
|
@@ -3390,20 +3770,28 @@ async function syncPendingRunnerFiles(options) {
|
|
|
3390
3770
|
for (const id of options.ackFailures.keys()) {
|
|
3391
3771
|
if (!pendingIds.has(id)) options.ackFailures.delete(id);
|
|
3392
3772
|
}
|
|
3393
|
-
if (pending.length === 0)
|
|
3773
|
+
if (pending.length === 0) {
|
|
3774
|
+
return { applied: 0, claudeCredentialApplied: false, opencodeAuthApplied: false };
|
|
3775
|
+
}
|
|
3394
3776
|
options.log({
|
|
3395
3777
|
level: "info",
|
|
3396
3778
|
message: `Runner file sync: ${pending.length} file(s) queued for this runner`
|
|
3397
3779
|
});
|
|
3398
3780
|
let applied = 0;
|
|
3399
3781
|
let claudeCredentialApplied = false;
|
|
3782
|
+
let opencodeAuthApplied = false;
|
|
3400
3783
|
for (const file of pending) {
|
|
3401
3784
|
if ((options.ackFailures.get(file.id) ?? 0) >= MAX_ACK_ATTEMPTS) continue;
|
|
3402
3785
|
const outcome = await applyOne(options, file);
|
|
3403
3786
|
if (outcome.applied) applied += 1;
|
|
3404
3787
|
if (outcome.claudeCredentialApplied) claudeCredentialApplied = true;
|
|
3788
|
+
if (outcome.opencodeAuthApplied) opencodeAuthApplied = true;
|
|
3405
3789
|
}
|
|
3406
|
-
return {
|
|
3790
|
+
return {
|
|
3791
|
+
applied,
|
|
3792
|
+
claudeCredentialApplied,
|
|
3793
|
+
opencodeAuthApplied
|
|
3794
|
+
};
|
|
3407
3795
|
}
|
|
3408
3796
|
async function listPendingFiles(options) {
|
|
3409
3797
|
let res;
|
|
@@ -3464,10 +3852,18 @@ function asPendingFile(entry) {
|
|
|
3464
3852
|
if (typeof size !== "number" || !Number.isFinite(size) || size < 0) return null;
|
|
3465
3853
|
return { id, path, size };
|
|
3466
3854
|
}
|
|
3467
|
-
var NOT_APPLIED = {
|
|
3855
|
+
var NOT_APPLIED = {
|
|
3856
|
+
applied: false,
|
|
3857
|
+
claudeCredentialApplied: false,
|
|
3858
|
+
opencodeAuthApplied: false
|
|
3859
|
+
};
|
|
3468
3860
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
3469
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
3470
|
-
return expanded ===
|
|
3861
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3862
|
+
return expanded === join6(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
3863
|
+
}
|
|
3864
|
+
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
3865
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3866
|
+
return expanded === join6(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
3471
3867
|
}
|
|
3472
3868
|
async function applyOne(options, file) {
|
|
3473
3869
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -3523,7 +3919,8 @@ async function applyOne(options, file) {
|
|
|
3523
3919
|
await ack(options, file, "applied");
|
|
3524
3920
|
return {
|
|
3525
3921
|
applied: true,
|
|
3526
|
-
claudeCredentialApplied: isClaudeCredentialPath(file.path, options.homeDir)
|
|
3922
|
+
claudeCredentialApplied: isClaudeCredentialPath(file.path, options.homeDir),
|
|
3923
|
+
opencodeAuthApplied: isOpenCodeAuthPath(file.path, options.homeDir)
|
|
3527
3924
|
};
|
|
3528
3925
|
}
|
|
3529
3926
|
function durableDownloadCode(status2) {
|
|
@@ -3977,6 +4374,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3977
4374
|
* that way rather than "fixing" it into a count.
|
|
3978
4375
|
*/
|
|
3979
4376
|
claudeCredentialApplyCount = 0;
|
|
4377
|
+
opencodeAuthApplyCount = 0;
|
|
3980
4378
|
/**
|
|
3981
4379
|
* The currently-executing `drainPending()` promise, or null when idle. Lets a
|
|
3982
4380
|
* graceful shutdown (`waitForInFlight`) await an in-progress drain so a turn it
|
|
@@ -4011,7 +4409,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4011
4409
|
this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
|
|
4012
4410
|
this.now = config.now ?? (() => Date.now());
|
|
4013
4411
|
this.fileSyncDirectories = config.fileSyncDirectories ?? [];
|
|
4014
|
-
this.homeDir = config.homeDir ??
|
|
4412
|
+
this.homeDir = config.homeDir ?? homedir3();
|
|
4015
4413
|
this.maxActiveSessions = config.maxActiveSessions;
|
|
4016
4414
|
this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
|
|
4017
4415
|
this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
|
|
@@ -4081,6 +4479,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4081
4479
|
});
|
|
4082
4480
|
this.appliedFileCount += result.applied;
|
|
4083
4481
|
if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
|
|
4482
|
+
if (result.opencodeAuthApplied) this.opencodeAuthApplyCount += 1;
|
|
4084
4483
|
return result.applied;
|
|
4085
4484
|
} catch (err) {
|
|
4086
4485
|
this.log({
|
|
@@ -4188,7 +4587,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4188
4587
|
return {
|
|
4189
4588
|
appliedFiles: this.appliedFileCount,
|
|
4190
4589
|
inFlight: this.syncingFiles,
|
|
4191
|
-
claudeCredentialApplies: this.claudeCredentialApplyCount
|
|
4590
|
+
claudeCredentialApplies: this.claudeCredentialApplyCount,
|
|
4591
|
+
opencodeAuthApplyCount: this.opencodeAuthApplyCount
|
|
4192
4592
|
};
|
|
4193
4593
|
}
|
|
4194
4594
|
/**
|
|
@@ -7525,7 +7925,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
7525
7925
|
if (trimmed === "") {
|
|
7526
7926
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
7527
7927
|
}
|
|
7528
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
7928
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join7(homeDir, trimmed.slice(2)) : trimmed;
|
|
7529
7929
|
if (!isAbsolute2(expanded)) {
|
|
7530
7930
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
7531
7931
|
}
|
|
@@ -7623,7 +8023,7 @@ function logActivity(state, entry) {
|
|
|
7623
8023
|
const level = entry.level ?? (entry.type === "error" ? "error" : "info");
|
|
7624
8024
|
if (!meetsThreshold(state, level)) return;
|
|
7625
8025
|
forwardRunnerActivity(
|
|
7626
|
-
{ level, message: entry.message, error: entry.error },
|
|
8026
|
+
{ level, message: entry.message, error: entry.error, metadata: entry.metadata },
|
|
7627
8027
|
{ agentId: state.agentId, authHeader: state.authHeader }
|
|
7628
8028
|
);
|
|
7629
8029
|
const fullEntry = {
|
|
@@ -7643,6 +8043,26 @@ function logActivity(state, entry) {
|
|
|
7643
8043
|
}
|
|
7644
8044
|
}
|
|
7645
8045
|
}
|
|
8046
|
+
function reportSessionDbRecovery(state) {
|
|
8047
|
+
try {
|
|
8048
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir4(), env: process.env });
|
|
8049
|
+
for (const record of report.records) {
|
|
8050
|
+
const activity = buildSessionDbRecoveryActivity(record);
|
|
8051
|
+
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
8052
|
+
logActivity(state, {
|
|
8053
|
+
type: activity.level === "error" ? "error" : "info",
|
|
8054
|
+
level: activity.level,
|
|
8055
|
+
...activity.level === "error" ? { error: activity.message } : { message: activity.message },
|
|
8056
|
+
metadata: activity.metadata
|
|
8057
|
+
});
|
|
8058
|
+
}
|
|
8059
|
+
acknowledgeSessionDbRecoveryReport(report.path);
|
|
8060
|
+
} catch (error2) {
|
|
8061
|
+
console.error(
|
|
8062
|
+
`[run] could not report session-DB recovery activity: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
8063
|
+
);
|
|
8064
|
+
}
|
|
8065
|
+
}
|
|
7646
8066
|
function displayStatus(state) {
|
|
7647
8067
|
if (!state.interactive) return;
|
|
7648
8068
|
const attempt = state.connection?.reconnectAttempt ?? 0;
|
|
@@ -7733,6 +8153,7 @@ async function driveChannels(state, driver) {
|
|
|
7733
8153
|
let lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
7734
8154
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
7735
8155
|
let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
|
|
8156
|
+
let lastSeenOpencodeAuthApplies = driver.fileSyncActivity().opencodeAuthApplyCount;
|
|
7736
8157
|
while (state.running) {
|
|
7737
8158
|
const cycleStartedAtMs = performance.now();
|
|
7738
8159
|
let idleThisCycle = false;
|
|
@@ -7765,6 +8186,10 @@ async function driveChannels(state, driver) {
|
|
|
7765
8186
|
const claudeCredentialApplied = claudeCredentialApplies !== lastSeenClaudeApplies;
|
|
7766
8187
|
lastSeenClaudeApplies = claudeCredentialApplies;
|
|
7767
8188
|
if (claudeCredentialApplied) state.claudeUsageRearm?.();
|
|
8189
|
+
const opencodeAuthApplies = fileActivitySnapshot.opencodeAuthApplyCount;
|
|
8190
|
+
const opencodeAuthApplied = opencodeAuthApplies !== lastSeenOpencodeAuthApplies;
|
|
8191
|
+
lastSeenOpencodeAuthApplies = opencodeAuthApplies;
|
|
8192
|
+
if (opencodeAuthApplied) state.openaiUsageRearm?.();
|
|
7768
8193
|
if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
|
|
7769
8194
|
idlePolls = 0;
|
|
7770
8195
|
idleMs = 0;
|
|
@@ -7833,7 +8258,7 @@ async function driveChannels(state, driver) {
|
|
|
7833
8258
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
7834
8259
|
var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
|
|
7835
8260
|
function sessionDbPath() {
|
|
7836
|
-
return
|
|
8261
|
+
return join7(homedir4(), ".local", "share", "opencode", "opencode.db");
|
|
7837
8262
|
}
|
|
7838
8263
|
async function runSweep(state, driver, config) {
|
|
7839
8264
|
const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
|
|
@@ -7916,7 +8341,7 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
7916
8341
|
for (const warning2 of config.warnings) {
|
|
7917
8342
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
7918
8343
|
}
|
|
7919
|
-
const dbBytes = statSessionDbBytes(
|
|
8344
|
+
const dbBytes = statSessionDbBytes(homedir4());
|
|
7920
8345
|
void (async () => {
|
|
7921
8346
|
const reclaimSkipReason = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
7922
8347
|
const sizeWarning = buildSessionStoreSizeWarning({
|
|
@@ -7944,23 +8369,20 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
7944
8369
|
);
|
|
7945
8370
|
state.sessionCleanupTimers.push(interval, firstSweep);
|
|
7946
8371
|
}
|
|
7947
|
-
function
|
|
7948
|
-
const { mode, warnings } =
|
|
7949
|
-
options.claudeUsageReporting,
|
|
7950
|
-
process.env
|
|
7951
|
-
);
|
|
8372
|
+
function scheduleUsageReporting(state, params) {
|
|
8373
|
+
const { mode, warnings } = params.resolved;
|
|
7952
8374
|
for (const warning2 of warnings) {
|
|
7953
8375
|
logActivity(state, {
|
|
7954
8376
|
type: "info",
|
|
7955
8377
|
level: "warn",
|
|
7956
|
-
message:
|
|
8378
|
+
message: `${params.label} usage reporting: ${warning2}`
|
|
7957
8379
|
});
|
|
7958
8380
|
}
|
|
7959
8381
|
if (mode === "off") {
|
|
7960
8382
|
logActivity(state, {
|
|
7961
8383
|
type: "info",
|
|
7962
8384
|
level: "debug",
|
|
7963
|
-
message:
|
|
8385
|
+
message: `${params.label} usage reporting is off (${params.offFlagHint})`
|
|
7964
8386
|
});
|
|
7965
8387
|
return null;
|
|
7966
8388
|
}
|
|
@@ -7969,7 +8391,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7969
8391
|
let rearmRequested = false;
|
|
7970
8392
|
const armProbe = () => {
|
|
7971
8393
|
phase = "probe-pending";
|
|
7972
|
-
|
|
8394
|
+
params.setTimer(setTimeout(() => void tick(true), params.firstDelayMs()));
|
|
7973
8395
|
};
|
|
7974
8396
|
const scheduleNextTick = () => {
|
|
7975
8397
|
if (rearmRequested) {
|
|
@@ -7978,7 +8400,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7978
8400
|
return;
|
|
7979
8401
|
}
|
|
7980
8402
|
phase = "steady-pending";
|
|
7981
|
-
|
|
8403
|
+
params.setTimer(setTimeout(() => void tick(false), params.nextDelayMs()));
|
|
7982
8404
|
};
|
|
7983
8405
|
const rearm = () => {
|
|
7984
8406
|
switch (phase) {
|
|
@@ -7988,9 +8410,9 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7988
8410
|
case "probe-pending":
|
|
7989
8411
|
return;
|
|
7990
8412
|
case "steady-pending":
|
|
7991
|
-
if (
|
|
7992
|
-
clearTimeout(
|
|
7993
|
-
|
|
8413
|
+
if (params.getTimer()) {
|
|
8414
|
+
clearTimeout(params.getTimer());
|
|
8415
|
+
params.setTimer(null);
|
|
7994
8416
|
}
|
|
7995
8417
|
rearmRequested = false;
|
|
7996
8418
|
armProbe();
|
|
@@ -8004,45 +8426,45 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8004
8426
|
const tick = async (isProbe) => {
|
|
8005
8427
|
phase = "tick-in-flight";
|
|
8006
8428
|
try {
|
|
8007
|
-
const usage = await
|
|
8008
|
-
const result = await
|
|
8429
|
+
const usage = await params.fetchUsage();
|
|
8430
|
+
const result = await params.report(usage);
|
|
8009
8431
|
if (result.ok) {
|
|
8010
8432
|
if (consecutiveFailures > 0) {
|
|
8011
8433
|
logActivity(state, {
|
|
8012
8434
|
type: "info",
|
|
8013
8435
|
level: "info",
|
|
8014
|
-
message:
|
|
8436
|
+
message: `${params.label} usage reporting recovered`
|
|
8015
8437
|
});
|
|
8016
8438
|
}
|
|
8017
8439
|
consecutiveFailures = 0;
|
|
8018
8440
|
logActivity(state, {
|
|
8019
8441
|
type: "info",
|
|
8020
8442
|
level: "debug",
|
|
8021
|
-
message:
|
|
8443
|
+
message: `Reported ${params.label} usage to Evident`
|
|
8022
8444
|
});
|
|
8023
8445
|
} else {
|
|
8024
8446
|
consecutiveFailures++;
|
|
8025
8447
|
logActivity(state, {
|
|
8026
8448
|
type: "info",
|
|
8027
|
-
level:
|
|
8028
|
-
message: `Failed to report
|
|
8449
|
+
level: params.failureLogLevel(consecutiveFailures),
|
|
8450
|
+
message: `Failed to report ${params.label} usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
|
|
8029
8451
|
});
|
|
8030
8452
|
}
|
|
8031
8453
|
scheduleNextTick();
|
|
8032
8454
|
} catch (error2) {
|
|
8033
|
-
if (
|
|
8455
|
+
if (params.isLocalCredentialProblem(error2)) {
|
|
8034
8456
|
if (mode === "on") {
|
|
8035
8457
|
logActivity(state, {
|
|
8036
8458
|
type: "info",
|
|
8037
8459
|
level: "warn",
|
|
8038
|
-
message:
|
|
8460
|
+
message: `${params.label} usage reporting is forced on but no usable login was found \u2014 ${params.forcedOnHint}; reporting will keep retrying`
|
|
8039
8461
|
});
|
|
8040
8462
|
scheduleNextTick();
|
|
8041
8463
|
} else if (isProbe) {
|
|
8042
8464
|
logActivity(state, {
|
|
8043
8465
|
type: "info",
|
|
8044
8466
|
level: "debug",
|
|
8045
|
-
message:
|
|
8467
|
+
message: `${params.label} usage reporting: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
8046
8468
|
});
|
|
8047
8469
|
phase = "dormant";
|
|
8048
8470
|
if (rearmRequested) rearm();
|
|
@@ -8050,7 +8472,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8050
8472
|
logActivity(state, {
|
|
8051
8473
|
type: "info",
|
|
8052
8474
|
level: "debug",
|
|
8053
|
-
message:
|
|
8475
|
+
message: `${params.label} usage reporting: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
8054
8476
|
});
|
|
8055
8477
|
scheduleNextTick();
|
|
8056
8478
|
}
|
|
@@ -8059,8 +8481,8 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8059
8481
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
8060
8482
|
logActivity(state, {
|
|
8061
8483
|
type: "info",
|
|
8062
|
-
level:
|
|
8063
|
-
message:
|
|
8484
|
+
level: params.failureLogLevel(consecutiveFailures),
|
|
8485
|
+
message: `${params.label} usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
8064
8486
|
});
|
|
8065
8487
|
scheduleNextTick();
|
|
8066
8488
|
}
|
|
@@ -8069,6 +8491,24 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8069
8491
|
armProbe();
|
|
8070
8492
|
return rearm;
|
|
8071
8493
|
}
|
|
8494
|
+
function scheduleClaudeUsageReporting(state, options) {
|
|
8495
|
+
return scheduleUsageReporting(state, {
|
|
8496
|
+
label: "Claude",
|
|
8497
|
+
resolved: resolveClaudeUsageReportingMode(options.claudeUsageReporting, process.env),
|
|
8498
|
+
offFlagHint: "--claude-usage-reporting off",
|
|
8499
|
+
getTimer: () => state.claudeUsageTimer,
|
|
8500
|
+
setTimer: (timer) => {
|
|
8501
|
+
state.claudeUsageTimer = timer;
|
|
8502
|
+
},
|
|
8503
|
+
fetchUsage: getClaudeUsage,
|
|
8504
|
+
report: (usage) => reportClaudeUsage(state.agentId, state.authHeader, usage),
|
|
8505
|
+
isLocalCredentialProblem,
|
|
8506
|
+
forcedOnHint: "run `claude` to sign in",
|
|
8507
|
+
firstDelayMs: () => FIRST_REPORT_DELAY_MS,
|
|
8508
|
+
nextDelayMs: nextReportDelayMs,
|
|
8509
|
+
failureLogLevel: claudeUsageFailureLogLevel
|
|
8510
|
+
});
|
|
8511
|
+
}
|
|
8072
8512
|
var RESOURCE_USAGE_BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
8073
8513
|
var RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
8074
8514
|
var RESOURCE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
@@ -8092,7 +8532,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
8092
8532
|
});
|
|
8093
8533
|
return;
|
|
8094
8534
|
}
|
|
8095
|
-
const collect = createResourceUsageCollector(
|
|
8535
|
+
const collect = createResourceUsageCollector(homedir4());
|
|
8096
8536
|
let consecutiveFailures = 0;
|
|
8097
8537
|
const tick = async () => {
|
|
8098
8538
|
try {
|
|
@@ -8193,6 +8633,11 @@ async function cleanup(state, opts = {}) {
|
|
|
8193
8633
|
state.claudeUsageTimer = null;
|
|
8194
8634
|
}
|
|
8195
8635
|
state.claudeUsageRearm = null;
|
|
8636
|
+
if (state.openaiUsageTimer) {
|
|
8637
|
+
clearTimeout(state.openaiUsageTimer);
|
|
8638
|
+
state.openaiUsageTimer = null;
|
|
8639
|
+
}
|
|
8640
|
+
state.openaiUsageRearm = null;
|
|
8196
8641
|
if (state.resourceUsageTimer) {
|
|
8197
8642
|
clearTimeout(state.resourceUsageTimer);
|
|
8198
8643
|
state.resourceUsageTimer = null;
|
|
@@ -8244,7 +8689,7 @@ async function run(options) {
|
|
|
8244
8689
|
let fileSyncDirectories;
|
|
8245
8690
|
try {
|
|
8246
8691
|
logLevel = resolveLogLevel(options);
|
|
8247
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
8692
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir4());
|
|
8248
8693
|
} catch (error2) {
|
|
8249
8694
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
8250
8695
|
if (options.json) {
|
|
@@ -8279,6 +8724,8 @@ async function run(options) {
|
|
|
8279
8724
|
sessionCleanupTimers: [],
|
|
8280
8725
|
claudeUsageTimer: null,
|
|
8281
8726
|
claudeUsageRearm: null,
|
|
8727
|
+
openaiUsageTimer: null,
|
|
8728
|
+
openaiUsageRearm: null,
|
|
8282
8729
|
resourceUsageTimer: null,
|
|
8283
8730
|
authHeader: ""
|
|
8284
8731
|
};
|
|
@@ -8474,6 +8921,7 @@ async function run(options) {
|
|
|
8474
8921
|
} else {
|
|
8475
8922
|
log2(state, "Not running in a MicroVM (MICROVM_ID unset) \u2014 nothing to report", "debug");
|
|
8476
8923
|
}
|
|
8924
|
+
reportSessionDbRecovery(state);
|
|
8477
8925
|
const { timeoutMs: opencodeStartTimeoutMs, warnings: opencodeStartTimeoutWarnings } = resolveOpenCodeStartTimeoutMs(options, process.env);
|
|
8478
8926
|
for (const warning2 of opencodeStartTimeoutWarnings) {
|
|
8479
8927
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
@@ -8541,7 +8989,7 @@ async function run(options) {
|
|
|
8541
8989
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
8542
8990
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
8543
8991
|
fileSyncDirectories,
|
|
8544
|
-
homeDir:
|
|
8992
|
+
homeDir: homedir4(),
|
|
8545
8993
|
maxActiveSessions,
|
|
8546
8994
|
log: (entry) => (
|
|
8547
8995
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -8679,6 +9127,22 @@ async function run(options) {
|
|
|
8679
9127
|
}
|
|
8680
9128
|
scheduleSessionCleanup(state, channelDriver, options);
|
|
8681
9129
|
state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
|
|
9130
|
+
state.openaiUsageRearm = scheduleUsageReporting(state, {
|
|
9131
|
+
label: "OpenAI",
|
|
9132
|
+
resolved: resolveOpenAiUsageReportingMode(options.openaiUsageReporting, process.env),
|
|
9133
|
+
offFlagHint: "--openai-usage-reporting off",
|
|
9134
|
+
getTimer: () => state.openaiUsageTimer,
|
|
9135
|
+
setTimer: (timer) => {
|
|
9136
|
+
state.openaiUsageTimer = timer;
|
|
9137
|
+
},
|
|
9138
|
+
fetchUsage: () => getOpenAiUsage(state.port),
|
|
9139
|
+
report: (usage) => reportOpenAiUsage(state.agentId, state.authHeader, usage),
|
|
9140
|
+
isLocalCredentialProblem: isLocalCredentialProblem2,
|
|
9141
|
+
forcedOnHint: "connect a ChatGPT account to this runner, or run `opencode auth login`",
|
|
9142
|
+
firstDelayMs: firstReportDelayMs,
|
|
9143
|
+
nextDelayMs: usageReportDelayMs,
|
|
9144
|
+
failureLogLevel: usageReportFailureLogLevel
|
|
9145
|
+
});
|
|
8682
9146
|
scheduleResourceUsageReporting(state, options);
|
|
8683
9147
|
if (!interactive || state.json) {
|
|
8684
9148
|
log2(state, "Driving channel messages...");
|
|
@@ -8760,6 +9224,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
8760
9224
|
).option(
|
|
8761
9225
|
"--claude-usage-reporting <mode>",
|
|
8762
9226
|
"Report Claude subscription usage to Evident: auto | on | off (default: auto). Env: EVIDENT_CLAUDE_USAGE_REPORTING"
|
|
9227
|
+
).option(
|
|
9228
|
+
"--openai-usage-reporting <mode>",
|
|
9229
|
+
"Report OpenAI plan usage to Evident: auto | on | off (default: auto). Env: EVIDENT_OPENAI_USAGE_REPORTING"
|
|
8763
9230
|
).option(
|
|
8764
9231
|
"--no-resource-usage-reporting",
|
|
8765
9232
|
"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 +9262,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
8795
9262
|
// Raw string — the resolver in run.ts single-sources parsing
|
|
8796
9263
|
// (resolveClaudeUsageReportingMode).
|
|
8797
9264
|
claudeUsageReporting: options.claudeUsageReporting,
|
|
9265
|
+
openaiUsageReporting: options.openaiUsageReporting,
|
|
8798
9266
|
// Raw value — resolution is single-sourced in run.ts's
|
|
8799
9267
|
// resolveResourceUsageReportingEnabled.
|
|
8800
9268
|
resourceUsageReporting: options.resourceUsageReporting,
|