@evident-ai/cli 3.5.2-dev.7947d6c → 3.5.2-dev.817a271
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +317 -81
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { createRequire as createRequire2 } from "module";
|
|
5
4
|
import { Command } from "commander";
|
|
6
5
|
|
|
7
6
|
// src/commands/login.ts
|
|
@@ -1063,7 +1062,7 @@ async function claudeUsage() {
|
|
|
1063
1062
|
|
|
1064
1063
|
// src/commands/run.ts
|
|
1065
1064
|
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1066
|
-
import { homedir as
|
|
1065
|
+
import { homedir as homedir7 } from "node:os";
|
|
1067
1066
|
import { isAbsolute as isAbsolute3, join as join11, parse, resolve as resolvePath2 } from "node:path";
|
|
1068
1067
|
import chalk7 from "chalk";
|
|
1069
1068
|
|
|
@@ -1158,7 +1157,7 @@ import ora4 from "ora";
|
|
|
1158
1157
|
import { select as select4 } from "@inquirer/prompts";
|
|
1159
1158
|
|
|
1160
1159
|
// src/lib/telemetry.ts
|
|
1161
|
-
var CLI_VERSION = (true ? "3.5.2-dev.
|
|
1160
|
+
var CLI_VERSION = (true ? "3.5.2-dev.817a271" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1162
1161
|
function getCliVersion() {
|
|
1163
1162
|
return CLI_VERSION;
|
|
1164
1163
|
}
|
|
@@ -1172,6 +1171,23 @@ var authProvider = null;
|
|
|
1172
1171
|
function setTelemetryAuthProvider(provider) {
|
|
1173
1172
|
authProvider = provider;
|
|
1174
1173
|
}
|
|
1174
|
+
var diagnosticsReporter = null;
|
|
1175
|
+
function setTelemetryDiagnosticsReporter(reporter) {
|
|
1176
|
+
diagnosticsReporter = reporter;
|
|
1177
|
+
}
|
|
1178
|
+
function reportTelemetryDiagnostic(message) {
|
|
1179
|
+
if (!diagnosticsReporter) {
|
|
1180
|
+
console.error(message);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
try {
|
|
1184
|
+
diagnosticsReporter(message);
|
|
1185
|
+
} catch (error2) {
|
|
1186
|
+
console.error(
|
|
1187
|
+
`Telemetry diagnostics reporter failed: ${error2 instanceof Error ? error2.message : String(error2)}; original diagnostic: ${message}`
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1175
1191
|
var FLUSH_FAILURE_LOG_INTERVAL_MS = 6e4;
|
|
1176
1192
|
var lastFlushFailureLoggedAt = 0;
|
|
1177
1193
|
var suppressedFlushFailureCount = 0;
|
|
@@ -1237,7 +1253,9 @@ async function flushEvents() {
|
|
|
1237
1253
|
signal: controller.signal
|
|
1238
1254
|
});
|
|
1239
1255
|
if (!response.ok) {
|
|
1240
|
-
|
|
1256
|
+
reportTelemetryDiagnostic(
|
|
1257
|
+
`Telemetry could not send events (HTTP ${response.status}); runner and OpenCode are unaffected`
|
|
1258
|
+
);
|
|
1241
1259
|
}
|
|
1242
1260
|
} finally {
|
|
1243
1261
|
clearTimeout(timeout);
|
|
@@ -1247,7 +1265,9 @@ async function flushEvents() {
|
|
|
1247
1265
|
if (now - lastFlushFailureLoggedAt >= FLUSH_FAILURE_LOG_INTERVAL_MS) {
|
|
1248
1266
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
1249
1267
|
const suffix = suppressedFlushFailureCount > 0 ? ` (${suppressedFlushFailureCount} more suppressed in the last ${FLUSH_FAILURE_LOG_INTERVAL_MS / 1e3}s)` : "";
|
|
1250
|
-
|
|
1268
|
+
reportTelemetryDiagnostic(
|
|
1269
|
+
`Telemetry flush failed: ${message}; runner and OpenCode are unaffected${suffix}`
|
|
1270
|
+
);
|
|
1251
1271
|
lastFlushFailureLoggedAt = now;
|
|
1252
1272
|
suppressedFlushFailureCount = 0;
|
|
1253
1273
|
} else {
|
|
@@ -1299,8 +1319,14 @@ var EventTypes = {
|
|
|
1299
1319
|
};
|
|
1300
1320
|
|
|
1301
1321
|
// src/lib/runner-activity-telemetry.ts
|
|
1302
|
-
|
|
1322
|
+
import { homedir as homedir2 } from "node:os";
|
|
1323
|
+
var FORWARDED_LEVELS_BY_SOURCE = {
|
|
1324
|
+
"cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
|
|
1325
|
+
opencode: /* @__PURE__ */ new Set(["warn", "error"]),
|
|
1326
|
+
"cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"])
|
|
1327
|
+
};
|
|
1303
1328
|
var SEVERITY_BY_LEVEL = {
|
|
1329
|
+
info: "info",
|
|
1304
1330
|
warn: "warning",
|
|
1305
1331
|
error: "error"
|
|
1306
1332
|
};
|
|
@@ -1311,25 +1337,72 @@ function parseOpenCodeLogLine(line) {
|
|
|
1311
1337
|
const level = levelMatch[1].toUpperCase();
|
|
1312
1338
|
if (level !== "WARN" && level !== "ERROR") return null;
|
|
1313
1339
|
const sessionMatch = normalisedLine.match(/(?:^|\s)sessionID=(\S+)/);
|
|
1314
|
-
|
|
1340
|
+
const messageMatch = normalisedLine.match(/(?:^|\s)message=(?:"((?:\\.|[^"])*)"|(\S+))/);
|
|
1341
|
+
const parsed = {
|
|
1342
|
+
level: level === "WARN" ? "warn" : "error",
|
|
1343
|
+
sessionID: sessionMatch?.[1]
|
|
1344
|
+
};
|
|
1345
|
+
const message = messageMatch?.[1] ?? messageMatch?.[2];
|
|
1346
|
+
if (message !== void 0) parsed.message = message;
|
|
1347
|
+
return parsed;
|
|
1315
1348
|
}
|
|
1316
1349
|
var MAX_LINE_BUFFER_BYTES = 16 * 1024;
|
|
1350
|
+
var REPEAT_SUPPRESSION_WINDOW_MS = 6e4;
|
|
1317
1351
|
function createOpenCodeActivityForwarder(getContext) {
|
|
1318
1352
|
let buffer = Buffer.alloc(0);
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
|
|
1353
|
+
const repeatStates = /* @__PURE__ */ new Map();
|
|
1354
|
+
const forwardLine = (line, parsed, repeatsSuppressed = 0) => {
|
|
1355
|
+
const metadata = {
|
|
1356
|
+
...parsed.sessionID ? { sessionID: parsed.sessionID } : {},
|
|
1357
|
+
...repeatsSuppressed > 0 ? { repeats_suppressed: repeatsSuppressed } : {}
|
|
1358
|
+
};
|
|
1322
1359
|
forwardRunnerActivity(
|
|
1323
1360
|
{
|
|
1324
1361
|
level: parsed.level,
|
|
1325
1362
|
error: line,
|
|
1326
|
-
metadata:
|
|
1363
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
|
|
1327
1364
|
source: "opencode"
|
|
1328
1365
|
},
|
|
1329
1366
|
getContext()
|
|
1330
1367
|
);
|
|
1331
1368
|
};
|
|
1332
|
-
|
|
1369
|
+
const expireRepeatStates = (now, currentKey) => {
|
|
1370
|
+
for (const [key, state] of repeatStates) {
|
|
1371
|
+
if (key === currentKey || now - state.lastForwardedAt < REPEAT_SUPPRESSION_WINDOW_MS) {
|
|
1372
|
+
continue;
|
|
1373
|
+
}
|
|
1374
|
+
if (state.repeatsSuppressed > 0) {
|
|
1375
|
+
forwardLine(state.line, state.parsed, state.repeatsSuppressed);
|
|
1376
|
+
}
|
|
1377
|
+
repeatStates.delete(key);
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
const flushLine = (line) => {
|
|
1381
|
+
const parsed = parseOpenCodeLogLine(line);
|
|
1382
|
+
if (!parsed) return;
|
|
1383
|
+
const now = Date.now();
|
|
1384
|
+
const key = parsed.message === void 0 ? void 0 : `${parsed.level}\0${parsed.message}`;
|
|
1385
|
+
expireRepeatStates(now, key);
|
|
1386
|
+
if (key === void 0) {
|
|
1387
|
+
forwardLine(line, parsed);
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const previous = repeatStates.get(key);
|
|
1391
|
+
if (!previous || now - previous.lastForwardedAt >= REPEAT_SUPPRESSION_WINDOW_MS) {
|
|
1392
|
+
forwardLine(line, parsed, previous?.repeatsSuppressed ?? 0);
|
|
1393
|
+
repeatStates.set(key, {
|
|
1394
|
+
line,
|
|
1395
|
+
parsed,
|
|
1396
|
+
lastForwardedAt: now,
|
|
1397
|
+
repeatsSuppressed: 0
|
|
1398
|
+
});
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
previous.line = line;
|
|
1402
|
+
previous.parsed = parsed;
|
|
1403
|
+
previous.repeatsSuppressed++;
|
|
1404
|
+
};
|
|
1405
|
+
const forward = ((chunk) => {
|
|
1333
1406
|
buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8")]);
|
|
1334
1407
|
let newlineIndex;
|
|
1335
1408
|
while ((newlineIndex = buffer.indexOf(10)) !== -1) {
|
|
@@ -1340,14 +1413,44 @@ function createOpenCodeActivityForwarder(getContext) {
|
|
|
1340
1413
|
flushLine(buffer.toString("utf-8"));
|
|
1341
1414
|
buffer = Buffer.alloc(0);
|
|
1342
1415
|
}
|
|
1416
|
+
});
|
|
1417
|
+
forward.flush = () => {
|
|
1418
|
+
for (const state of repeatStates.values()) {
|
|
1419
|
+
if (state.repeatsSuppressed > 0) {
|
|
1420
|
+
forwardLine(state.line, state.parsed, state.repeatsSuppressed);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
repeatStates.clear();
|
|
1343
1424
|
};
|
|
1425
|
+
return forward;
|
|
1344
1426
|
}
|
|
1345
1427
|
var MAX_MESSAGE_LENGTH = 500;
|
|
1346
1428
|
var MAX_METADATA_VALUE_LENGTH = 200;
|
|
1347
1429
|
var MAX_METADATA_ENTRIES = 20;
|
|
1348
1430
|
var TRUNCATION_MARKER = "\u2026";
|
|
1349
|
-
function
|
|
1350
|
-
|
|
1431
|
+
function redactHomeDirectory(message) {
|
|
1432
|
+
const home = homedir2();
|
|
1433
|
+
if (!home || home === "/") {
|
|
1434
|
+
return message;
|
|
1435
|
+
}
|
|
1436
|
+
const pathSegmentCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-";
|
|
1437
|
+
let result = "";
|
|
1438
|
+
let searchFrom = 0;
|
|
1439
|
+
let homeIndex;
|
|
1440
|
+
while ((homeIndex = message.indexOf(home, searchFrom)) !== -1) {
|
|
1441
|
+
const homeEnd = homeIndex + home.length;
|
|
1442
|
+
const previousCharacter = message[homeIndex - 1];
|
|
1443
|
+
const nextCharacter = message[homeEnd];
|
|
1444
|
+
const atPathBoundary = home === "/" || (!previousCharacter || !pathSegmentCharacters.includes(previousCharacter)) && (!nextCharacter || !pathSegmentCharacters.includes(nextCharacter));
|
|
1445
|
+
result += message.slice(searchFrom, homeIndex);
|
|
1446
|
+
result += atPathBoundary ? "~" : home;
|
|
1447
|
+
searchFrom = homeEnd;
|
|
1448
|
+
}
|
|
1449
|
+
return result + message.slice(searchFrom);
|
|
1450
|
+
}
|
|
1451
|
+
function redactDiagnosticText(message) {
|
|
1452
|
+
const redacted = message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/(?<![A-Za-z0-9_-])sk-ant-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-ant-***").replace(/(?<![A-Za-z0-9_-])sk-proj-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-proj-***").replace(/(?<![A-Za-z0-9_-])sk-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "sk-***").replace(/(?<![A-Za-z0-9_-])(gh[oprsu])_[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "$1_***").replace(/(?<![A-Za-z0-9_-])github_pat_[A-Za-z0-9_]{20,}(?![A-Za-z0-9_-])/g, "github_pat_***").replace(/https?:\/\/\S+/g, "<url>");
|
|
1453
|
+
return redactHomeDirectory(redacted);
|
|
1351
1454
|
}
|
|
1352
1455
|
function truncate(message) {
|
|
1353
1456
|
if (message.length <= MAX_MESSAGE_LENGTH) return message;
|
|
@@ -1357,14 +1460,17 @@ function sanitiseMetadata(metadata) {
|
|
|
1357
1460
|
if (!metadata) return {};
|
|
1358
1461
|
const entries = Object.entries(metadata).slice(0, MAX_METADATA_ENTRIES);
|
|
1359
1462
|
if (Object.keys(metadata).length > entries.length) {
|
|
1360
|
-
|
|
1361
|
-
`
|
|
1463
|
+
reportTelemetryDiagnostic(
|
|
1464
|
+
`Runner activity telemetry dropped ${Object.keys(metadata).length - entries.length} metadata entries; those entries were not sent`
|
|
1362
1465
|
);
|
|
1363
1466
|
}
|
|
1364
1467
|
const sanitised = [];
|
|
1365
1468
|
for (const [key, value] of entries) {
|
|
1366
1469
|
if (typeof value === "string") {
|
|
1367
|
-
sanitised.push([
|
|
1470
|
+
sanitised.push([
|
|
1471
|
+
key,
|
|
1472
|
+
truncate(redactDiagnosticText(value).slice(0, MAX_METADATA_VALUE_LENGTH))
|
|
1473
|
+
]);
|
|
1368
1474
|
} else if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
|
|
1369
1475
|
sanitised.push([key, value]);
|
|
1370
1476
|
}
|
|
@@ -1382,8 +1488,8 @@ function admitUnderRateLimit(source, now) {
|
|
|
1382
1488
|
}
|
|
1383
1489
|
if (now - window.windowStartedAt >= RATE_LIMIT_WINDOW_MS) {
|
|
1384
1490
|
if (window.windowDroppedCount > 0) {
|
|
1385
|
-
|
|
1386
|
-
`
|
|
1491
|
+
reportTelemetryDiagnostic(
|
|
1492
|
+
`Runner activity telemetry rate cap reached: dropped ${window.windowDroppedCount} ${window.windowDroppedCount === 1 ? "entry" : "entries"} in the last ${RATE_LIMIT_WINDOW_MS / 1e3}s (limit ${RATE_LIMIT_MAX_EVENTS}/min); those entries were not sent for source "${source}"`
|
|
1387
1493
|
);
|
|
1388
1494
|
}
|
|
1389
1495
|
window.windowStartedAt = now;
|
|
@@ -1393,8 +1499,8 @@ function admitUnderRateLimit(source, now) {
|
|
|
1393
1499
|
if (window.windowCount >= RATE_LIMIT_MAX_EVENTS) {
|
|
1394
1500
|
window.windowDroppedCount++;
|
|
1395
1501
|
if (window.windowDroppedCount === 1) {
|
|
1396
|
-
|
|
1397
|
-
`
|
|
1502
|
+
reportTelemetryDiagnostic(
|
|
1503
|
+
`Runner activity telemetry rate cap reached (${RATE_LIMIT_MAX_EVENTS} entries per ${RATE_LIMIT_WINDOW_MS / 1e3}s limit; further entries were not sent for source "${source}"`
|
|
1398
1504
|
);
|
|
1399
1505
|
}
|
|
1400
1506
|
return false;
|
|
@@ -1404,12 +1510,12 @@ function admitUnderRateLimit(source, now) {
|
|
|
1404
1510
|
}
|
|
1405
1511
|
function forwardRunnerActivity(entry, context) {
|
|
1406
1512
|
try {
|
|
1407
|
-
if (!FORWARDED_LEVELS.has(entry.level)) return;
|
|
1408
|
-
if (!context.agentId || !context.authHeader) return;
|
|
1409
1513
|
const source = entry.source ?? "cli.run";
|
|
1514
|
+
if (!FORWARDED_LEVELS_BY_SOURCE[source].has(entry.level)) return;
|
|
1515
|
+
if (!context.agentId || !context.authHeader) return;
|
|
1410
1516
|
if (!admitUnderRateLimit(source, Date.now())) return;
|
|
1411
1517
|
const rawMessage = entry.error ?? entry.message ?? "";
|
|
1412
|
-
const message = truncate(
|
|
1518
|
+
const message = truncate(redactDiagnosticText(rawMessage));
|
|
1413
1519
|
logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
|
|
1414
1520
|
severity: SEVERITY_BY_LEVEL[entry.level],
|
|
1415
1521
|
message,
|
|
@@ -1417,8 +1523,8 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1417
1523
|
agentId: context.agentId
|
|
1418
1524
|
});
|
|
1419
1525
|
} catch (err) {
|
|
1420
|
-
|
|
1421
|
-
`
|
|
1526
|
+
reportTelemetryDiagnostic(
|
|
1527
|
+
`Runner activity telemetry failed to forward runner activity: ${err instanceof Error ? err.message : String(err)}`
|
|
1422
1528
|
);
|
|
1423
1529
|
}
|
|
1424
1530
|
}
|
|
@@ -1708,8 +1814,46 @@ async function waitForOpenCode2Health(port, password, timeoutMs = 3e4) {
|
|
|
1708
1814
|
|
|
1709
1815
|
// src/lib/http-timeout.ts
|
|
1710
1816
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
1817
|
+
var RequestTimeoutError = class extends Error {
|
|
1818
|
+
constructor(operation, timeoutMs, elapsedMs) {
|
|
1819
|
+
super(`${operation} timed out after ${elapsedMs}ms (limit ${timeoutMs}ms)`);
|
|
1820
|
+
this.operation = operation;
|
|
1821
|
+
this.timeoutMs = timeoutMs;
|
|
1822
|
+
this.elapsedMs = elapsedMs;
|
|
1823
|
+
this.name = "TimeoutError";
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
function describeRequestOperation(input, init) {
|
|
1827
|
+
const method = (init?.method ?? input.method ?? "GET").toUpperCase();
|
|
1828
|
+
const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
1829
|
+
let path;
|
|
1830
|
+
try {
|
|
1831
|
+
path = new URL(rawUrl).pathname;
|
|
1832
|
+
} catch (error2) {
|
|
1833
|
+
if (!(error2 instanceof TypeError)) throw error2;
|
|
1834
|
+
path = rawUrl.split(/[?#]/, 1)[0];
|
|
1835
|
+
}
|
|
1836
|
+
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1837
|
+
const prefixedIdentifier = /^[a-z]+_[A-Za-z0-9_-]{10,}$/;
|
|
1838
|
+
const maskedPath = path.split("/").map((segment) => uuid.test(segment) || prefixedIdentifier.test(segment) ? ":id" : segment).join("/");
|
|
1839
|
+
return `${method} ${maskedPath}`;
|
|
1840
|
+
}
|
|
1711
1841
|
function withRequestTimeout(fetchImpl, timeoutMs) {
|
|
1712
|
-
return ((input, init) =>
|
|
1842
|
+
return (async (input, init) => {
|
|
1843
|
+
const startedAt = Date.now();
|
|
1844
|
+
try {
|
|
1845
|
+
return await fetchImpl(input, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
1846
|
+
} catch (error2) {
|
|
1847
|
+
if (error2?.name === "TimeoutError") {
|
|
1848
|
+
throw new RequestTimeoutError(
|
|
1849
|
+
describeRequestOperation(input, init),
|
|
1850
|
+
timeoutMs,
|
|
1851
|
+
Date.now() - startedAt
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
throw error2;
|
|
1855
|
+
}
|
|
1856
|
+
});
|
|
1713
1857
|
}
|
|
1714
1858
|
|
|
1715
1859
|
// src/lib/opencode/client.ts
|
|
@@ -3214,7 +3358,7 @@ import { join as join4 } from "node:path";
|
|
|
3214
3358
|
|
|
3215
3359
|
// src/lib/openai-usage.ts
|
|
3216
3360
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
3217
|
-
import { homedir as
|
|
3361
|
+
import { homedir as homedir3 } from "node:os";
|
|
3218
3362
|
import { join as join3 } from "node:path";
|
|
3219
3363
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
3220
3364
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
@@ -3229,7 +3373,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
3229
3373
|
}
|
|
3230
3374
|
function readOpenCodeChatGptCredentials() {
|
|
3231
3375
|
try {
|
|
3232
|
-
const raw = readFileSync3(join3(
|
|
3376
|
+
const raw = readFileSync3(join3(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
3233
3377
|
let parsed;
|
|
3234
3378
|
try {
|
|
3235
3379
|
parsed = JSON.parse(raw);
|
|
@@ -3370,16 +3514,17 @@ function hasPrimaryHeaders(headers) {
|
|
|
3370
3514
|
].some((name) => headers.has(name));
|
|
3371
3515
|
}
|
|
3372
3516
|
async function getOpenAiUsage(port, client) {
|
|
3517
|
+
const openCodeVersion = client?.version ?? "v1";
|
|
3373
3518
|
const credentials2 = readOpenCodeChatGptCredentials();
|
|
3374
3519
|
if (!credentials2) {
|
|
3375
3520
|
throw new OpenAiUsageError(
|
|
3376
|
-
"No ChatGPT login found. Connect a ChatGPT account to this runner, or run `opencode auth login`.",
|
|
3521
|
+
openCodeVersion === "v2" ? "OpenAI usage reporting is not available on OpenCode V2 because V2 keeps a connected ChatGPT login in a store Evident cannot read." : "No ChatGPT login found. Connect a ChatGPT account to this runner, or run `opencode auth login`.",
|
|
3377
3522
|
"no_credentials"
|
|
3378
3523
|
);
|
|
3379
3524
|
}
|
|
3380
3525
|
if (credentials2.expiresAt < Date.now()) {
|
|
3381
3526
|
throw new OpenAiUsageError(
|
|
3382
|
-
"ChatGPT credentials have expired. Run `opencode auth login` to refresh them.",
|
|
3527
|
+
openCodeVersion === "v2" ? "The delivered ChatGPT login has expired. OpenCode V2 cannot be given a replacement by file; connect the account again from the runner's page in Evident." : "ChatGPT credentials have expired. Run `opencode auth login` to refresh them.",
|
|
3383
3528
|
"credentials_expired"
|
|
3384
3529
|
);
|
|
3385
3530
|
}
|
|
@@ -3661,7 +3806,7 @@ async function importOpenCodeV2Credentials({
|
|
|
3661
3806
|
// src/lib/opencode/session-db-boot.ts
|
|
3662
3807
|
import { spawn as spawn2 } from "node:child_process";
|
|
3663
3808
|
import { mkdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
|
|
3664
|
-
import { homedir as
|
|
3809
|
+
import { homedir as homedir4 } from "node:os";
|
|
3665
3810
|
import { dirname as dirname2, resolve as resolvePath } from "node:path";
|
|
3666
3811
|
|
|
3667
3812
|
// src/lib/runner-synchroniser.ts
|
|
@@ -4017,14 +4162,14 @@ async function restoreSessionDb(options, configPath, env) {
|
|
|
4017
4162
|
case 0:
|
|
4018
4163
|
return;
|
|
4019
4164
|
case 31:
|
|
4020
|
-
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(
|
|
4165
|
+
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir4(), env));
|
|
4021
4166
|
options.log(
|
|
4022
4167
|
"SESSION-DB-REPLICA-UNUSABLE: booting with a fresh opencode.db and replicating into the existing prefix this boot",
|
|
4023
4168
|
"warn"
|
|
4024
4169
|
);
|
|
4025
4170
|
return;
|
|
4026
4171
|
case 32:
|
|
4027
|
-
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(
|
|
4172
|
+
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir4(), env));
|
|
4028
4173
|
discardSessionDbDebris(options);
|
|
4029
4174
|
markNoReplicate(
|
|
4030
4175
|
options,
|
|
@@ -4113,7 +4258,7 @@ function fileExists(path) {
|
|
|
4113
4258
|
async function restoreAndVerifySessionDb(options) {
|
|
4114
4259
|
const env = options.env ?? process.env;
|
|
4115
4260
|
clearMarker(options);
|
|
4116
|
-
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(
|
|
4261
|
+
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir4(), env));
|
|
4117
4262
|
const synchroniserEnv = await runSynchroniser(["env"], {
|
|
4118
4263
|
timeoutMs: SESSION_DB_SYNCHRONISER_TIMEOUT_MS,
|
|
4119
4264
|
env
|
|
@@ -4932,11 +5077,11 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
4932
5077
|
|
|
4933
5078
|
// src/lib/opencode/log-tail.ts
|
|
4934
5079
|
import { statSync as statSync4 } from "node:fs";
|
|
4935
|
-
import { homedir as
|
|
5080
|
+
import { homedir as homedir5 } from "node:os";
|
|
4936
5081
|
import { join as join7 } from "node:path";
|
|
4937
5082
|
import { open as open2, stat } from "node:fs/promises";
|
|
4938
5083
|
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
4939
|
-
function resolveOpenCodeLogPath(homeDir =
|
|
5084
|
+
function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
|
|
4940
5085
|
const dataDir = env.XDG_DATA_HOME || join7(homeDir, ".local", "share");
|
|
4941
5086
|
return join7(dataDir, "opencode", "log", "opencode.log");
|
|
4942
5087
|
}
|
|
@@ -5091,7 +5236,11 @@ async function reclaimSessionDbSpace(input) {
|
|
|
5091
5236
|
console.warn(
|
|
5092
5237
|
`[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: a session turn is live`
|
|
5093
5238
|
);
|
|
5094
|
-
return {
|
|
5239
|
+
return {
|
|
5240
|
+
ok: false,
|
|
5241
|
+
skipped: "full-vacuum-blocked",
|
|
5242
|
+
detail: "a session turn is live"
|
|
5243
|
+
};
|
|
5095
5244
|
}
|
|
5096
5245
|
const fileBytesForGuard = statSync5(dbPath).size;
|
|
5097
5246
|
const skipReason = insufficientSpaceReason(dbPath, fileBytesForGuard);
|
|
@@ -5099,7 +5248,7 @@ async function reclaimSessionDbSpace(input) {
|
|
|
5099
5248
|
console.warn(
|
|
5100
5249
|
`[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: ${skipReason}`
|
|
5101
5250
|
);
|
|
5102
|
-
return { ok: false, skipped: "insufficient-disk-space" };
|
|
5251
|
+
return { ok: false, skipped: "insufficient-disk-space", detail: skipReason };
|
|
5103
5252
|
}
|
|
5104
5253
|
const beforeBytes = readLogicalBytes(db);
|
|
5105
5254
|
db.exec("PRAGMA auto_vacuum=INCREMENTAL");
|
|
@@ -5586,6 +5735,7 @@ function connectTunnel(options) {
|
|
|
5586
5735
|
var RunnerConnection = class {
|
|
5587
5736
|
opts;
|
|
5588
5737
|
sleep;
|
|
5738
|
+
now;
|
|
5589
5739
|
connection = null;
|
|
5590
5740
|
resolvedAgentId;
|
|
5591
5741
|
/** True while a (re)connect loop is in flight. */
|
|
@@ -5594,10 +5744,13 @@ var RunnerConnection = class {
|
|
|
5594
5744
|
reconnectPromise = null;
|
|
5595
5745
|
/** 1-based count of the current reconnect attempt streak. */
|
|
5596
5746
|
reconnectAttempt = 0;
|
|
5747
|
+
generation = 0;
|
|
5748
|
+
disconnectedAtMs = null;
|
|
5597
5749
|
constructor(opts) {
|
|
5598
5750
|
this.opts = opts;
|
|
5599
5751
|
this.resolvedAgentId = opts.agentId;
|
|
5600
5752
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
5753
|
+
this.now = opts.now ?? Date.now;
|
|
5601
5754
|
}
|
|
5602
5755
|
get agentId() {
|
|
5603
5756
|
return this.resolvedAgentId;
|
|
@@ -5633,13 +5786,22 @@ var RunnerConnection = class {
|
|
|
5633
5786
|
port: this.opts.port,
|
|
5634
5787
|
openCodePassword: this.opts.openCodePassword,
|
|
5635
5788
|
onConnected: (agentId) => {
|
|
5789
|
+
const attempt = this.reconnectAttempt + 1;
|
|
5636
5790
|
this.reconnectAttempt = 0;
|
|
5637
5791
|
this.reconnecting = false;
|
|
5638
5792
|
this.resolvedAgentId = agentId;
|
|
5639
|
-
|
|
5793
|
+
this.generation++;
|
|
5794
|
+
const downtimeMs = this.disconnectedAtMs === null ? null : this.now() - this.disconnectedAtMs;
|
|
5795
|
+
this.disconnectedAtMs = null;
|
|
5796
|
+
events.onConnected(agentId, isReconnect, {
|
|
5797
|
+
generation: this.generation,
|
|
5798
|
+
attempt,
|
|
5799
|
+
downtimeMs
|
|
5800
|
+
});
|
|
5640
5801
|
},
|
|
5641
5802
|
onDisconnected: (code, reason) => {
|
|
5642
|
-
|
|
5803
|
+
this.disconnectedAtMs = this.now();
|
|
5804
|
+
events.onDisconnected(code, reason, { generation: this.generation });
|
|
5643
5805
|
if (this.opts.isRunning() && code !== 1e3 && !this.reconnecting) {
|
|
5644
5806
|
this.reconnectPromise = this.connectWithRetry(true).catch((err) => {
|
|
5645
5807
|
events.onError?.(`Reconnection failed: ${err.message}`);
|
|
@@ -5980,7 +6142,7 @@ function createResourceUsageCollector(homeDir) {
|
|
|
5980
6142
|
}
|
|
5981
6143
|
|
|
5982
6144
|
// src/lib/channels/driver.ts
|
|
5983
|
-
import { homedir as
|
|
6145
|
+
import { homedir as homedir6 } from "node:os";
|
|
5984
6146
|
|
|
5985
6147
|
// src/lib/runner-file-sync.ts
|
|
5986
6148
|
import { join as join9 } from "node:path";
|
|
@@ -6863,7 +7025,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6863
7025
|
this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
|
|
6864
7026
|
this.now = config.now ?? (() => Date.now());
|
|
6865
7027
|
this.fileSyncDirectories = config.fileSyncDirectories ?? [];
|
|
6866
|
-
this.homeDir = config.homeDir ??
|
|
7028
|
+
this.homeDir = config.homeDir ?? homedir6();
|
|
6867
7029
|
this.maxActiveSessions = config.maxActiveSessions;
|
|
6868
7030
|
this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
|
|
6869
7031
|
this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
|
|
@@ -12116,6 +12278,7 @@ function createCredentialSync({
|
|
|
12116
12278
|
|
|
12117
12279
|
// src/commands/run.ts
|
|
12118
12280
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
12281
|
+
var MAX_DISCONNECT_REASON_LENGTH = 200;
|
|
12119
12282
|
var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
|
|
12120
12283
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
12121
12284
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
@@ -12271,7 +12434,13 @@ function logActivity(state, entry) {
|
|
|
12271
12434
|
const level = entry.level ?? (entry.type === "error" ? "error" : "info");
|
|
12272
12435
|
if (!meetsThreshold(state, level)) return;
|
|
12273
12436
|
forwardRunnerActivity(
|
|
12274
|
-
{
|
|
12437
|
+
{
|
|
12438
|
+
level,
|
|
12439
|
+
message: entry.message,
|
|
12440
|
+
error: entry.error,
|
|
12441
|
+
metadata: entry.metadata,
|
|
12442
|
+
...entry.source ? { source: entry.source } : {}
|
|
12443
|
+
},
|
|
12275
12444
|
{ agentId: state.agentId, authHeader: state.authHeader }
|
|
12276
12445
|
);
|
|
12277
12446
|
const fullEntry = {
|
|
@@ -12293,7 +12462,7 @@ function logActivity(state, entry) {
|
|
|
12293
12462
|
}
|
|
12294
12463
|
function reportSessionDbRecovery(state) {
|
|
12295
12464
|
try {
|
|
12296
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
12465
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir7(), env: process.env });
|
|
12297
12466
|
for (const record of report.records) {
|
|
12298
12467
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
12299
12468
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -12328,7 +12497,9 @@ function displayStatus(state) {
|
|
|
12328
12497
|
const opencode = state.opencodeConnected ? chalk7.green(`opencode: :${state.port}`) : chalk7.red(`opencode: :${state.port} (down)`);
|
|
12329
12498
|
const messages = state.messageCount > 0 ? chalk7.dim(` \xB7 ${state.messageCount} processed`) : "";
|
|
12330
12499
|
const last = state.activityLog[state.activityLog.length - 1];
|
|
12331
|
-
const detail = last ? chalk7.dim(
|
|
12500
|
+
const detail = last ? chalk7.dim(
|
|
12501
|
+
` \xB7 ${last.timestamp.toISOString()} ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`
|
|
12502
|
+
) : "";
|
|
12332
12503
|
const agent = state.agentName ?? state.agentId;
|
|
12333
12504
|
console.log(
|
|
12334
12505
|
`${chalk7.bold("Evident")} ${chalk7.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
|
|
@@ -12347,7 +12518,7 @@ function reportOpenCodeV2CredentialImport(state, result) {
|
|
|
12347
12518
|
logActivity(state, {
|
|
12348
12519
|
type: "info",
|
|
12349
12520
|
level: "warn",
|
|
12350
|
-
message: `OpenCode V2 saved login(s) for ${integrationIds} on this machine, but OpenCode V2 will not use them.
|
|
12521
|
+
message: `OpenCode V2 saved login(s) for ${integrationIds} on this machine, but OpenCode V2 will not use them. Connect the provider again from the runner's page in Evident; no shell on this machine is needed.`
|
|
12351
12522
|
});
|
|
12352
12523
|
}
|
|
12353
12524
|
if (result.imported.length === 0 && result.unsupported.length === 0 && result.failed.length === 0) {
|
|
@@ -12372,7 +12543,7 @@ async function importOpenCodeV2CredentialsForState(state) {
|
|
|
12372
12543
|
}
|
|
12373
12544
|
const result = await importOpenCodeV2Credentials({
|
|
12374
12545
|
request: client.request,
|
|
12375
|
-
homeDir:
|
|
12546
|
+
homeDir: homedir7(),
|
|
12376
12547
|
directory,
|
|
12377
12548
|
guard: state.opencodeV2CredentialImportGuard,
|
|
12378
12549
|
log: (message, level = "info") => logActivity(state, { type: "info", level, message })
|
|
@@ -12591,7 +12762,14 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
12591
12762
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
12592
12763
|
}
|
|
12593
12764
|
function sessionDbPath() {
|
|
12594
|
-
return join11(
|
|
12765
|
+
return join11(homedir7(), ".local", "share", "opencode", "opencode.db");
|
|
12766
|
+
}
|
|
12767
|
+
function logSessionCleanupActivity(state, phase, entry) {
|
|
12768
|
+
logActivity(state, {
|
|
12769
|
+
...entry,
|
|
12770
|
+
source: "cli.session-cleanup",
|
|
12771
|
+
metadata: { ...entry.metadata, phase }
|
|
12772
|
+
});
|
|
12595
12773
|
}
|
|
12596
12774
|
function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
|
|
12597
12775
|
const record = {
|
|
@@ -12634,9 +12812,13 @@ async function runSweep(state, driver, config) {
|
|
|
12634
12812
|
try {
|
|
12635
12813
|
const sessions = await listSessions(state.port, state.opencodeClient ?? void 0);
|
|
12636
12814
|
if (sessions === null) {
|
|
12637
|
-
|
|
12815
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12638
12816
|
type: "info",
|
|
12639
|
-
message: `Session cleanup: could not list sessions (opencode unreachable); skipping this sweep (${mode})
|
|
12817
|
+
message: `Session cleanup: could not list sessions (opencode unreachable); skipping this sweep (${mode})`,
|
|
12818
|
+
metadata: {
|
|
12819
|
+
outcome: "sweep-skipped",
|
|
12820
|
+
reason: "opencode-unreachable"
|
|
12821
|
+
}
|
|
12640
12822
|
});
|
|
12641
12823
|
return;
|
|
12642
12824
|
}
|
|
@@ -12680,21 +12862,47 @@ async function runSweep(state, driver, config) {
|
|
|
12680
12862
|
const beforeMib = (reclaimResult.beforeBytes / 1024 / 1024).toFixed(1);
|
|
12681
12863
|
const afterMib = (reclaimResult.afterBytes / 1024 / 1024).toFixed(1);
|
|
12682
12864
|
const checkpointNote = reclaimResult.checkpoint.busy ? ` (on-disk file truncation deferred: checkpoint busy, ${reclaimResult.checkpoint.log} WAL frames pending)` : "";
|
|
12683
|
-
|
|
12865
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12684
12866
|
type: "info",
|
|
12685
|
-
message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB${checkpointNote}
|
|
12867
|
+
message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB${checkpointNote}`,
|
|
12868
|
+
metadata: {
|
|
12869
|
+
outcome: "reclaimed",
|
|
12870
|
+
mode: reclaimResult.mode,
|
|
12871
|
+
before_bytes: reclaimResult.beforeBytes,
|
|
12872
|
+
after_bytes: reclaimResult.afterBytes,
|
|
12873
|
+
checkpoint_busy: reclaimResult.checkpoint.busy,
|
|
12874
|
+
wal_frames_pending: reclaimResult.checkpoint.log,
|
|
12875
|
+
sessions_inspected: sessions.length,
|
|
12876
|
+
sessions_deleted: deleted,
|
|
12877
|
+
sessions_failed: failed,
|
|
12878
|
+
sessions_skipped_active: skippedNewlyActive
|
|
12879
|
+
}
|
|
12686
12880
|
});
|
|
12687
12881
|
} else {
|
|
12688
|
-
|
|
12882
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12689
12883
|
type: "info",
|
|
12690
|
-
|
|
12884
|
+
level: reclaimResult.skipped === "reclaim-error" || reclaimResult.skipped === "insufficient-disk-space" ? "warn" : "info",
|
|
12885
|
+
message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : ""),
|
|
12886
|
+
metadata: {
|
|
12887
|
+
outcome: "reclaim-skipped",
|
|
12888
|
+
reason: reclaimResult.skipped,
|
|
12889
|
+
...reclaimResult.detail !== void 0 ? { detail: reclaimResult.detail } : {},
|
|
12890
|
+
sessions_inspected: sessions.length,
|
|
12891
|
+
sessions_deleted: deleted,
|
|
12892
|
+
sessions_failed: failed,
|
|
12893
|
+
sessions_skipped_active: skippedNewlyActive
|
|
12894
|
+
}
|
|
12691
12895
|
});
|
|
12692
12896
|
}
|
|
12693
12897
|
} catch (error2) {
|
|
12694
12898
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
12695
|
-
|
|
12899
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12696
12900
|
type: "error",
|
|
12697
|
-
error: `Session cleanup sweep failed (non-fatal, ${mode}): ${message}
|
|
12901
|
+
error: `Session cleanup sweep failed (non-fatal, ${mode}): ${message}`,
|
|
12902
|
+
metadata: {
|
|
12903
|
+
outcome: "sweep-failed",
|
|
12904
|
+
reason: "sweep-error"
|
|
12905
|
+
}
|
|
12698
12906
|
});
|
|
12699
12907
|
}
|
|
12700
12908
|
}
|
|
@@ -12710,14 +12918,19 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
12710
12918
|
for (const warning2 of config.warnings) {
|
|
12711
12919
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
12712
12920
|
}
|
|
12713
|
-
const dbBytes = statSessionDbBytes(
|
|
12921
|
+
const dbBytes = statSessionDbBytes(homedir7());
|
|
12714
12922
|
void (async () => {
|
|
12715
12923
|
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
12716
12924
|
if (reclaimAvailability !== null) {
|
|
12717
|
-
|
|
12925
|
+
logSessionCleanupActivity(state, "startup", {
|
|
12718
12926
|
type: "info",
|
|
12719
12927
|
level: shouldWarnForReclaimSkip(reclaimAvailability.reason) ? "warn" : "info",
|
|
12720
|
-
message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : "")
|
|
12928
|
+
message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : ""),
|
|
12929
|
+
metadata: {
|
|
12930
|
+
outcome: "preflight-skipped",
|
|
12931
|
+
reason: reclaimAvailability.reason,
|
|
12932
|
+
...reclaimAvailability.detail !== void 0 ? { detail: reclaimAvailability.detail } : {}
|
|
12933
|
+
}
|
|
12721
12934
|
});
|
|
12722
12935
|
}
|
|
12723
12936
|
const sizeWarning = buildSessionStoreSizeWarning({
|
|
@@ -12734,9 +12947,15 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
12734
12947
|
);
|
|
12735
12948
|
});
|
|
12736
12949
|
if (!config.enabled) return;
|
|
12737
|
-
|
|
12950
|
+
logSessionCleanupActivity(state, "startup", {
|
|
12738
12951
|
type: "info",
|
|
12739
|
-
message: `Session cleanup enabled (age=${config.maxAgeMs ?? "\u2014"}, count=${config.maxCount ?? "\u2014"}, interval=${config.intervalMs}ms)
|
|
12952
|
+
message: `Session cleanup enabled (age=${config.maxAgeMs ?? "\u2014"}, count=${config.maxCount ?? "\u2014"}, interval=${config.intervalMs}ms)`,
|
|
12953
|
+
metadata: {
|
|
12954
|
+
outcome: "armed",
|
|
12955
|
+
interval_ms: config.intervalMs,
|
|
12956
|
+
max_age_ms: config.maxAgeMs ?? null,
|
|
12957
|
+
max_count: config.maxCount ?? null
|
|
12958
|
+
}
|
|
12740
12959
|
});
|
|
12741
12960
|
const interval = setInterval(() => void runSweep(state, driver, config), config.intervalMs);
|
|
12742
12961
|
const firstSweep = setTimeout(
|
|
@@ -12915,7 +13134,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
12915
13134
|
});
|
|
12916
13135
|
return;
|
|
12917
13136
|
}
|
|
12918
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
13137
|
+
const { collect, stop } = createResourceUsageCollector(homedir7());
|
|
12919
13138
|
state.stopResourceUsageSampling = stop;
|
|
12920
13139
|
let consecutiveFailures = 0;
|
|
12921
13140
|
const tick = async () => {
|
|
@@ -13140,7 +13359,7 @@ async function run(options) {
|
|
|
13140
13359
|
let fileSyncDirectories;
|
|
13141
13360
|
try {
|
|
13142
13361
|
logLevel = resolveLogLevel(options);
|
|
13143
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
13362
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir7());
|
|
13144
13363
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
13145
13364
|
throw new Error(
|
|
13146
13365
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -13195,6 +13414,7 @@ async function run(options) {
|
|
|
13195
13414
|
authHeader: ""
|
|
13196
13415
|
};
|
|
13197
13416
|
setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
|
|
13417
|
+
setTelemetryDiagnosticsReporter((message) => log2(state, message, "warn"));
|
|
13198
13418
|
if (options.credentialSyncMarker) {
|
|
13199
13419
|
state.credentialSync = createCredentialSync({
|
|
13200
13420
|
markerPath: options.credentialSyncMarker,
|
|
@@ -13380,13 +13600,18 @@ async function run(options) {
|
|
|
13380
13600
|
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
13381
13601
|
}
|
|
13382
13602
|
state.credentialSync?.arm();
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13603
|
+
const openCodeActivityForwarder = createOpenCodeActivityForwarder(() => ({
|
|
13604
|
+
agentId: state.agentId,
|
|
13605
|
+
authHeader: state.authHeader
|
|
13606
|
+
}));
|
|
13607
|
+
const openCodeLogTail = tailOpenCodeLogFile(
|
|
13608
|
+
resolveOpenCodeLogPath(homedir7(), process.env),
|
|
13609
|
+
openCodeActivityForwarder
|
|
13610
|
+
);
|
|
13611
|
+
state.stopOpenCodeLogTail = () => {
|
|
13612
|
+
openCodeActivityForwarder.flush();
|
|
13613
|
+
openCodeLogTail.stop();
|
|
13614
|
+
};
|
|
13390
13615
|
let sessionDbVerifyFatal = false;
|
|
13391
13616
|
if (!options.restoreSessionDb) {
|
|
13392
13617
|
log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
|
|
@@ -13503,7 +13728,7 @@ async function run(options) {
|
|
|
13503
13728
|
const provenance = checkSessionDbProvenance({
|
|
13504
13729
|
dbPath: sessionDbPath(),
|
|
13505
13730
|
currentVersion: state.opencodeVersion,
|
|
13506
|
-
homeDir:
|
|
13731
|
+
homeDir: homedir7(),
|
|
13507
13732
|
env: process.env
|
|
13508
13733
|
});
|
|
13509
13734
|
if (provenance.anomaly) {
|
|
@@ -13673,7 +13898,7 @@ async function run(options) {
|
|
|
13673
13898
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
13674
13899
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
13675
13900
|
fileSyncDirectories,
|
|
13676
|
-
homeDir:
|
|
13901
|
+
homeDir: homedir7(),
|
|
13677
13902
|
maxActiveSessions,
|
|
13678
13903
|
log: (entry) => (
|
|
13679
13904
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -13695,12 +13920,13 @@ async function run(options) {
|
|
|
13695
13920
|
openCodePassword: state.opencodePassword,
|
|
13696
13921
|
isRunning: () => state.running,
|
|
13697
13922
|
events: {
|
|
13698
|
-
onConnected: (agentId, isReconnect) => {
|
|
13923
|
+
onConnected: (agentId, isReconnect, transition) => {
|
|
13699
13924
|
state.connected = true;
|
|
13700
13925
|
state.agentId = agentId;
|
|
13926
|
+
const connectionMessage = isReconnect ? `Tunnel reconnected (runner: ${agentId}, generation: ${transition.generation}, attempt: ${transition.attempt}, offline for ${transition.downtimeMs}ms)` : `Tunnel connected (runner: ${agentId}, generation: ${transition.generation})`;
|
|
13701
13927
|
logActivity(state, {
|
|
13702
13928
|
type: "info",
|
|
13703
|
-
message:
|
|
13929
|
+
message: connectionMessage
|
|
13704
13930
|
});
|
|
13705
13931
|
if (options.tunnelReadyFile) {
|
|
13706
13932
|
const marker = writeTunnelReadyMarker(options.tunnelReadyFile, agentId);
|
|
@@ -13721,7 +13947,12 @@ async function run(options) {
|
|
|
13721
13947
|
version: state.opencodeVersion,
|
|
13722
13948
|
major: state.opencodeApiVersion,
|
|
13723
13949
|
connected: state.opencodeConnected
|
|
13724
|
-
})
|
|
13950
|
+
}),
|
|
13951
|
+
connection_generation: transition.generation,
|
|
13952
|
+
...isReconnect && transition.downtimeMs !== null ? {
|
|
13953
|
+
reconnect_attempt: transition.attempt,
|
|
13954
|
+
downtime_ms: transition.downtimeMs
|
|
13955
|
+
} : {}
|
|
13725
13956
|
});
|
|
13726
13957
|
if (!isReconnect) tunnelSpinner?.succeed("Tunnel connected");
|
|
13727
13958
|
if (state.interactive) displayStatus(state);
|
|
@@ -13743,13 +13974,18 @@ async function run(options) {
|
|
|
13743
13974
|
if (state.interactive) displayStatus(state);
|
|
13744
13975
|
});
|
|
13745
13976
|
},
|
|
13746
|
-
onDisconnected: (code, reason) => {
|
|
13977
|
+
onDisconnected: (code, reason, transition) => {
|
|
13747
13978
|
state.connected = false;
|
|
13979
|
+
const safeReason = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
|
|
13748
13980
|
logActivity(state, {
|
|
13749
13981
|
type: "info",
|
|
13750
|
-
message: `Tunnel disconnected (code: ${code}, reason: ${
|
|
13982
|
+
message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason})`
|
|
13983
|
+
});
|
|
13984
|
+
emitAgentDisconnected(state.agentId, {
|
|
13985
|
+
code,
|
|
13986
|
+
reason: safeReason,
|
|
13987
|
+
connection_generation: transition.generation
|
|
13751
13988
|
});
|
|
13752
|
-
emitAgentDisconnected(state.agentId, { code, reason });
|
|
13753
13989
|
if (state.interactive) displayStatus(state);
|
|
13754
13990
|
},
|
|
13755
13991
|
onError: (error2) => {
|
|
@@ -13849,7 +14085,7 @@ async function run(options) {
|
|
|
13849
14085
|
},
|
|
13850
14086
|
report: (usage) => reportOpenAiUsage(state.agentId, state.authHeader, usage),
|
|
13851
14087
|
isLocalCredentialProblem: isLocalCredentialProblem2,
|
|
13852
|
-
forcedOnHint: "connect a ChatGPT account to this runner, or run `opencode auth login`",
|
|
14088
|
+
forcedOnHint: state.opencodeApiVersion === "v2" ? "OpenCode V2 keeps its connected ChatGPT login in a store Evident cannot read" : "connect a ChatGPT account to this runner, or run `opencode auth login`",
|
|
13853
14089
|
firstDelayMs: firstReportDelayMs,
|
|
13854
14090
|
nextDelayMs: usageReportDelayMs,
|
|
13855
14091
|
failureLogLevel: usageReportFailureLogLevel
|
|
@@ -13892,7 +14128,7 @@ async function run(options) {
|
|
|
13892
14128
|
}
|
|
13893
14129
|
|
|
13894
14130
|
// src/index.ts
|
|
13895
|
-
var
|
|
14131
|
+
var version = getCliVersion();
|
|
13896
14132
|
var program = new Command();
|
|
13897
14133
|
program.name("evident").description("Run OpenCode locally and connect it to Evident").version(version).option(
|
|
13898
14134
|
"--endpoint <url>",
|