@evident-ai/cli 3.5.2-dev.58beb33 → 3.5.2-dev.659ea21
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 +319 -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.659ea21" : 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");
|
|
@@ -5361,6 +5510,8 @@ var StreamForwarder = class {
|
|
|
5361
5510
|
if (done) break;
|
|
5362
5511
|
const chunk = Buffer.from(value);
|
|
5363
5512
|
for (let i = 0; i < chunk.length; i += MAX_FRAME_BYTES) {
|
|
5513
|
+
await stream.pausePromise;
|
|
5514
|
+
if (ac.signal.aborted) break;
|
|
5364
5515
|
const slice = chunk.subarray(i, i + MAX_FRAME_BYTES);
|
|
5365
5516
|
if (this.binaryFramesSupported) {
|
|
5366
5517
|
if (this.ws.readyState === WebSocket.OPEN) {
|
|
@@ -5586,6 +5737,7 @@ function connectTunnel(options) {
|
|
|
5586
5737
|
var RunnerConnection = class {
|
|
5587
5738
|
opts;
|
|
5588
5739
|
sleep;
|
|
5740
|
+
now;
|
|
5589
5741
|
connection = null;
|
|
5590
5742
|
resolvedAgentId;
|
|
5591
5743
|
/** True while a (re)connect loop is in flight. */
|
|
@@ -5594,10 +5746,13 @@ var RunnerConnection = class {
|
|
|
5594
5746
|
reconnectPromise = null;
|
|
5595
5747
|
/** 1-based count of the current reconnect attempt streak. */
|
|
5596
5748
|
reconnectAttempt = 0;
|
|
5749
|
+
generation = 0;
|
|
5750
|
+
disconnectedAtMs = null;
|
|
5597
5751
|
constructor(opts) {
|
|
5598
5752
|
this.opts = opts;
|
|
5599
5753
|
this.resolvedAgentId = opts.agentId;
|
|
5600
5754
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
5755
|
+
this.now = opts.now ?? Date.now;
|
|
5601
5756
|
}
|
|
5602
5757
|
get agentId() {
|
|
5603
5758
|
return this.resolvedAgentId;
|
|
@@ -5633,13 +5788,22 @@ var RunnerConnection = class {
|
|
|
5633
5788
|
port: this.opts.port,
|
|
5634
5789
|
openCodePassword: this.opts.openCodePassword,
|
|
5635
5790
|
onConnected: (agentId) => {
|
|
5791
|
+
const attempt = this.reconnectAttempt + 1;
|
|
5636
5792
|
this.reconnectAttempt = 0;
|
|
5637
5793
|
this.reconnecting = false;
|
|
5638
5794
|
this.resolvedAgentId = agentId;
|
|
5639
|
-
|
|
5795
|
+
this.generation++;
|
|
5796
|
+
const downtimeMs = this.disconnectedAtMs === null ? null : this.now() - this.disconnectedAtMs;
|
|
5797
|
+
this.disconnectedAtMs = null;
|
|
5798
|
+
events.onConnected(agentId, isReconnect, {
|
|
5799
|
+
generation: this.generation,
|
|
5800
|
+
attempt,
|
|
5801
|
+
downtimeMs
|
|
5802
|
+
});
|
|
5640
5803
|
},
|
|
5641
5804
|
onDisconnected: (code, reason) => {
|
|
5642
|
-
|
|
5805
|
+
this.disconnectedAtMs = this.now();
|
|
5806
|
+
events.onDisconnected(code, reason, { generation: this.generation });
|
|
5643
5807
|
if (this.opts.isRunning() && code !== 1e3 && !this.reconnecting) {
|
|
5644
5808
|
this.reconnectPromise = this.connectWithRetry(true).catch((err) => {
|
|
5645
5809
|
events.onError?.(`Reconnection failed: ${err.message}`);
|
|
@@ -5980,7 +6144,7 @@ function createResourceUsageCollector(homeDir) {
|
|
|
5980
6144
|
}
|
|
5981
6145
|
|
|
5982
6146
|
// src/lib/channels/driver.ts
|
|
5983
|
-
import { homedir as
|
|
6147
|
+
import { homedir as homedir6 } from "node:os";
|
|
5984
6148
|
|
|
5985
6149
|
// src/lib/runner-file-sync.ts
|
|
5986
6150
|
import { join as join9 } from "node:path";
|
|
@@ -6863,7 +7027,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6863
7027
|
this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
|
|
6864
7028
|
this.now = config.now ?? (() => Date.now());
|
|
6865
7029
|
this.fileSyncDirectories = config.fileSyncDirectories ?? [];
|
|
6866
|
-
this.homeDir = config.homeDir ??
|
|
7030
|
+
this.homeDir = config.homeDir ?? homedir6();
|
|
6867
7031
|
this.maxActiveSessions = config.maxActiveSessions;
|
|
6868
7032
|
this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
|
|
6869
7033
|
this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
|
|
@@ -12116,6 +12280,7 @@ function createCredentialSync({
|
|
|
12116
12280
|
|
|
12117
12281
|
// src/commands/run.ts
|
|
12118
12282
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
12283
|
+
var MAX_DISCONNECT_REASON_LENGTH = 200;
|
|
12119
12284
|
var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
|
|
12120
12285
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
12121
12286
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
@@ -12271,7 +12436,13 @@ function logActivity(state, entry) {
|
|
|
12271
12436
|
const level = entry.level ?? (entry.type === "error" ? "error" : "info");
|
|
12272
12437
|
if (!meetsThreshold(state, level)) return;
|
|
12273
12438
|
forwardRunnerActivity(
|
|
12274
|
-
{
|
|
12439
|
+
{
|
|
12440
|
+
level,
|
|
12441
|
+
message: entry.message,
|
|
12442
|
+
error: entry.error,
|
|
12443
|
+
metadata: entry.metadata,
|
|
12444
|
+
...entry.source ? { source: entry.source } : {}
|
|
12445
|
+
},
|
|
12275
12446
|
{ agentId: state.agentId, authHeader: state.authHeader }
|
|
12276
12447
|
);
|
|
12277
12448
|
const fullEntry = {
|
|
@@ -12293,7 +12464,7 @@ function logActivity(state, entry) {
|
|
|
12293
12464
|
}
|
|
12294
12465
|
function reportSessionDbRecovery(state) {
|
|
12295
12466
|
try {
|
|
12296
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
12467
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir7(), env: process.env });
|
|
12297
12468
|
for (const record of report.records) {
|
|
12298
12469
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
12299
12470
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -12328,7 +12499,9 @@ function displayStatus(state) {
|
|
|
12328
12499
|
const opencode = state.opencodeConnected ? chalk7.green(`opencode: :${state.port}`) : chalk7.red(`opencode: :${state.port} (down)`);
|
|
12329
12500
|
const messages = state.messageCount > 0 ? chalk7.dim(` \xB7 ${state.messageCount} processed`) : "";
|
|
12330
12501
|
const last = state.activityLog[state.activityLog.length - 1];
|
|
12331
|
-
const detail = last ? chalk7.dim(
|
|
12502
|
+
const detail = last ? chalk7.dim(
|
|
12503
|
+
` \xB7 ${last.timestamp.toISOString()} ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`
|
|
12504
|
+
) : "";
|
|
12332
12505
|
const agent = state.agentName ?? state.agentId;
|
|
12333
12506
|
console.log(
|
|
12334
12507
|
`${chalk7.bold("Evident")} ${chalk7.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
|
|
@@ -12347,7 +12520,7 @@ function reportOpenCodeV2CredentialImport(state, result) {
|
|
|
12347
12520
|
logActivity(state, {
|
|
12348
12521
|
type: "info",
|
|
12349
12522
|
level: "warn",
|
|
12350
|
-
message: `OpenCode V2 saved login(s) for ${integrationIds} on this machine, but OpenCode V2 will not use them.
|
|
12523
|
+
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
12524
|
});
|
|
12352
12525
|
}
|
|
12353
12526
|
if (result.imported.length === 0 && result.unsupported.length === 0 && result.failed.length === 0) {
|
|
@@ -12372,7 +12545,7 @@ async function importOpenCodeV2CredentialsForState(state) {
|
|
|
12372
12545
|
}
|
|
12373
12546
|
const result = await importOpenCodeV2Credentials({
|
|
12374
12547
|
request: client.request,
|
|
12375
|
-
homeDir:
|
|
12548
|
+
homeDir: homedir7(),
|
|
12376
12549
|
directory,
|
|
12377
12550
|
guard: state.opencodeV2CredentialImportGuard,
|
|
12378
12551
|
log: (message, level = "info") => logActivity(state, { type: "info", level, message })
|
|
@@ -12591,7 +12764,14 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
12591
12764
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
12592
12765
|
}
|
|
12593
12766
|
function sessionDbPath() {
|
|
12594
|
-
return join11(
|
|
12767
|
+
return join11(homedir7(), ".local", "share", "opencode", "opencode.db");
|
|
12768
|
+
}
|
|
12769
|
+
function logSessionCleanupActivity(state, phase, entry) {
|
|
12770
|
+
logActivity(state, {
|
|
12771
|
+
...entry,
|
|
12772
|
+
source: "cli.session-cleanup",
|
|
12773
|
+
metadata: { ...entry.metadata, phase }
|
|
12774
|
+
});
|
|
12595
12775
|
}
|
|
12596
12776
|
function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
|
|
12597
12777
|
const record = {
|
|
@@ -12634,9 +12814,13 @@ async function runSweep(state, driver, config) {
|
|
|
12634
12814
|
try {
|
|
12635
12815
|
const sessions = await listSessions(state.port, state.opencodeClient ?? void 0);
|
|
12636
12816
|
if (sessions === null) {
|
|
12637
|
-
|
|
12817
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12638
12818
|
type: "info",
|
|
12639
|
-
message: `Session cleanup: could not list sessions (opencode unreachable); skipping this sweep (${mode})
|
|
12819
|
+
message: `Session cleanup: could not list sessions (opencode unreachable); skipping this sweep (${mode})`,
|
|
12820
|
+
metadata: {
|
|
12821
|
+
outcome: "sweep-skipped",
|
|
12822
|
+
reason: "opencode-unreachable"
|
|
12823
|
+
}
|
|
12640
12824
|
});
|
|
12641
12825
|
return;
|
|
12642
12826
|
}
|
|
@@ -12680,21 +12864,47 @@ async function runSweep(state, driver, config) {
|
|
|
12680
12864
|
const beforeMib = (reclaimResult.beforeBytes / 1024 / 1024).toFixed(1);
|
|
12681
12865
|
const afterMib = (reclaimResult.afterBytes / 1024 / 1024).toFixed(1);
|
|
12682
12866
|
const checkpointNote = reclaimResult.checkpoint.busy ? ` (on-disk file truncation deferred: checkpoint busy, ${reclaimResult.checkpoint.log} WAL frames pending)` : "";
|
|
12683
|
-
|
|
12867
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12684
12868
|
type: "info",
|
|
12685
|
-
message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB${checkpointNote}
|
|
12869
|
+
message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB${checkpointNote}`,
|
|
12870
|
+
metadata: {
|
|
12871
|
+
outcome: "reclaimed",
|
|
12872
|
+
mode: reclaimResult.mode,
|
|
12873
|
+
before_bytes: reclaimResult.beforeBytes,
|
|
12874
|
+
after_bytes: reclaimResult.afterBytes,
|
|
12875
|
+
checkpoint_busy: reclaimResult.checkpoint.busy,
|
|
12876
|
+
wal_frames_pending: reclaimResult.checkpoint.log,
|
|
12877
|
+
sessions_inspected: sessions.length,
|
|
12878
|
+
sessions_deleted: deleted,
|
|
12879
|
+
sessions_failed: failed,
|
|
12880
|
+
sessions_skipped_active: skippedNewlyActive
|
|
12881
|
+
}
|
|
12686
12882
|
});
|
|
12687
12883
|
} else {
|
|
12688
|
-
|
|
12884
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12689
12885
|
type: "info",
|
|
12690
|
-
|
|
12886
|
+
level: reclaimResult.skipped === "reclaim-error" || reclaimResult.skipped === "insufficient-disk-space" ? "warn" : "info",
|
|
12887
|
+
message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : ""),
|
|
12888
|
+
metadata: {
|
|
12889
|
+
outcome: "reclaim-skipped",
|
|
12890
|
+
reason: reclaimResult.skipped,
|
|
12891
|
+
...reclaimResult.detail !== void 0 ? { detail: reclaimResult.detail } : {},
|
|
12892
|
+
sessions_inspected: sessions.length,
|
|
12893
|
+
sessions_deleted: deleted,
|
|
12894
|
+
sessions_failed: failed,
|
|
12895
|
+
sessions_skipped_active: skippedNewlyActive
|
|
12896
|
+
}
|
|
12691
12897
|
});
|
|
12692
12898
|
}
|
|
12693
12899
|
} catch (error2) {
|
|
12694
12900
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
12695
|
-
|
|
12901
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12696
12902
|
type: "error",
|
|
12697
|
-
error: `Session cleanup sweep failed (non-fatal, ${mode}): ${message}
|
|
12903
|
+
error: `Session cleanup sweep failed (non-fatal, ${mode}): ${message}`,
|
|
12904
|
+
metadata: {
|
|
12905
|
+
outcome: "sweep-failed",
|
|
12906
|
+
reason: "sweep-error"
|
|
12907
|
+
}
|
|
12698
12908
|
});
|
|
12699
12909
|
}
|
|
12700
12910
|
}
|
|
@@ -12710,14 +12920,19 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
12710
12920
|
for (const warning2 of config.warnings) {
|
|
12711
12921
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
12712
12922
|
}
|
|
12713
|
-
const dbBytes = statSessionDbBytes(
|
|
12923
|
+
const dbBytes = statSessionDbBytes(homedir7());
|
|
12714
12924
|
void (async () => {
|
|
12715
12925
|
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
12716
12926
|
if (reclaimAvailability !== null) {
|
|
12717
|
-
|
|
12927
|
+
logSessionCleanupActivity(state, "startup", {
|
|
12718
12928
|
type: "info",
|
|
12719
12929
|
level: shouldWarnForReclaimSkip(reclaimAvailability.reason) ? "warn" : "info",
|
|
12720
|
-
message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : "")
|
|
12930
|
+
message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : ""),
|
|
12931
|
+
metadata: {
|
|
12932
|
+
outcome: "preflight-skipped",
|
|
12933
|
+
reason: reclaimAvailability.reason,
|
|
12934
|
+
...reclaimAvailability.detail !== void 0 ? { detail: reclaimAvailability.detail } : {}
|
|
12935
|
+
}
|
|
12721
12936
|
});
|
|
12722
12937
|
}
|
|
12723
12938
|
const sizeWarning = buildSessionStoreSizeWarning({
|
|
@@ -12734,9 +12949,15 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
12734
12949
|
);
|
|
12735
12950
|
});
|
|
12736
12951
|
if (!config.enabled) return;
|
|
12737
|
-
|
|
12952
|
+
logSessionCleanupActivity(state, "startup", {
|
|
12738
12953
|
type: "info",
|
|
12739
|
-
message: `Session cleanup enabled (age=${config.maxAgeMs ?? "\u2014"}, count=${config.maxCount ?? "\u2014"}, interval=${config.intervalMs}ms)
|
|
12954
|
+
message: `Session cleanup enabled (age=${config.maxAgeMs ?? "\u2014"}, count=${config.maxCount ?? "\u2014"}, interval=${config.intervalMs}ms)`,
|
|
12955
|
+
metadata: {
|
|
12956
|
+
outcome: "armed",
|
|
12957
|
+
interval_ms: config.intervalMs,
|
|
12958
|
+
max_age_ms: config.maxAgeMs ?? null,
|
|
12959
|
+
max_count: config.maxCount ?? null
|
|
12960
|
+
}
|
|
12740
12961
|
});
|
|
12741
12962
|
const interval = setInterval(() => void runSweep(state, driver, config), config.intervalMs);
|
|
12742
12963
|
const firstSweep = setTimeout(
|
|
@@ -12915,7 +13136,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
12915
13136
|
});
|
|
12916
13137
|
return;
|
|
12917
13138
|
}
|
|
12918
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
13139
|
+
const { collect, stop } = createResourceUsageCollector(homedir7());
|
|
12919
13140
|
state.stopResourceUsageSampling = stop;
|
|
12920
13141
|
let consecutiveFailures = 0;
|
|
12921
13142
|
const tick = async () => {
|
|
@@ -13140,7 +13361,7 @@ async function run(options) {
|
|
|
13140
13361
|
let fileSyncDirectories;
|
|
13141
13362
|
try {
|
|
13142
13363
|
logLevel = resolveLogLevel(options);
|
|
13143
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
13364
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir7());
|
|
13144
13365
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
13145
13366
|
throw new Error(
|
|
13146
13367
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -13195,6 +13416,7 @@ async function run(options) {
|
|
|
13195
13416
|
authHeader: ""
|
|
13196
13417
|
};
|
|
13197
13418
|
setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
|
|
13419
|
+
setTelemetryDiagnosticsReporter((message) => log2(state, message, "warn"));
|
|
13198
13420
|
if (options.credentialSyncMarker) {
|
|
13199
13421
|
state.credentialSync = createCredentialSync({
|
|
13200
13422
|
markerPath: options.credentialSyncMarker,
|
|
@@ -13380,13 +13602,18 @@ async function run(options) {
|
|
|
13380
13602
|
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
13381
13603
|
}
|
|
13382
13604
|
state.credentialSync?.arm();
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13605
|
+
const openCodeActivityForwarder = createOpenCodeActivityForwarder(() => ({
|
|
13606
|
+
agentId: state.agentId,
|
|
13607
|
+
authHeader: state.authHeader
|
|
13608
|
+
}));
|
|
13609
|
+
const openCodeLogTail = tailOpenCodeLogFile(
|
|
13610
|
+
resolveOpenCodeLogPath(homedir7(), process.env),
|
|
13611
|
+
openCodeActivityForwarder
|
|
13612
|
+
);
|
|
13613
|
+
state.stopOpenCodeLogTail = () => {
|
|
13614
|
+
openCodeActivityForwarder.flush();
|
|
13615
|
+
openCodeLogTail.stop();
|
|
13616
|
+
};
|
|
13390
13617
|
let sessionDbVerifyFatal = false;
|
|
13391
13618
|
if (!options.restoreSessionDb) {
|
|
13392
13619
|
log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
|
|
@@ -13503,7 +13730,7 @@ async function run(options) {
|
|
|
13503
13730
|
const provenance = checkSessionDbProvenance({
|
|
13504
13731
|
dbPath: sessionDbPath(),
|
|
13505
13732
|
currentVersion: state.opencodeVersion,
|
|
13506
|
-
homeDir:
|
|
13733
|
+
homeDir: homedir7(),
|
|
13507
13734
|
env: process.env
|
|
13508
13735
|
});
|
|
13509
13736
|
if (provenance.anomaly) {
|
|
@@ -13673,7 +13900,7 @@ async function run(options) {
|
|
|
13673
13900
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
13674
13901
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
13675
13902
|
fileSyncDirectories,
|
|
13676
|
-
homeDir:
|
|
13903
|
+
homeDir: homedir7(),
|
|
13677
13904
|
maxActiveSessions,
|
|
13678
13905
|
log: (entry) => (
|
|
13679
13906
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -13695,12 +13922,13 @@ async function run(options) {
|
|
|
13695
13922
|
openCodePassword: state.opencodePassword,
|
|
13696
13923
|
isRunning: () => state.running,
|
|
13697
13924
|
events: {
|
|
13698
|
-
onConnected: (agentId, isReconnect) => {
|
|
13925
|
+
onConnected: (agentId, isReconnect, transition) => {
|
|
13699
13926
|
state.connected = true;
|
|
13700
13927
|
state.agentId = agentId;
|
|
13928
|
+
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
13929
|
logActivity(state, {
|
|
13702
13930
|
type: "info",
|
|
13703
|
-
message:
|
|
13931
|
+
message: connectionMessage
|
|
13704
13932
|
});
|
|
13705
13933
|
if (options.tunnelReadyFile) {
|
|
13706
13934
|
const marker = writeTunnelReadyMarker(options.tunnelReadyFile, agentId);
|
|
@@ -13721,7 +13949,12 @@ async function run(options) {
|
|
|
13721
13949
|
version: state.opencodeVersion,
|
|
13722
13950
|
major: state.opencodeApiVersion,
|
|
13723
13951
|
connected: state.opencodeConnected
|
|
13724
|
-
})
|
|
13952
|
+
}),
|
|
13953
|
+
connection_generation: transition.generation,
|
|
13954
|
+
...isReconnect && transition.downtimeMs !== null ? {
|
|
13955
|
+
reconnect_attempt: transition.attempt,
|
|
13956
|
+
downtime_ms: transition.downtimeMs
|
|
13957
|
+
} : {}
|
|
13725
13958
|
});
|
|
13726
13959
|
if (!isReconnect) tunnelSpinner?.succeed("Tunnel connected");
|
|
13727
13960
|
if (state.interactive) displayStatus(state);
|
|
@@ -13743,13 +13976,18 @@ async function run(options) {
|
|
|
13743
13976
|
if (state.interactive) displayStatus(state);
|
|
13744
13977
|
});
|
|
13745
13978
|
},
|
|
13746
|
-
onDisconnected: (code, reason) => {
|
|
13979
|
+
onDisconnected: (code, reason, transition) => {
|
|
13747
13980
|
state.connected = false;
|
|
13981
|
+
const safeReason = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
|
|
13748
13982
|
logActivity(state, {
|
|
13749
13983
|
type: "info",
|
|
13750
|
-
message: `Tunnel disconnected (code: ${code}, reason: ${
|
|
13984
|
+
message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason})`
|
|
13985
|
+
});
|
|
13986
|
+
emitAgentDisconnected(state.agentId, {
|
|
13987
|
+
code,
|
|
13988
|
+
reason: safeReason,
|
|
13989
|
+
connection_generation: transition.generation
|
|
13751
13990
|
});
|
|
13752
|
-
emitAgentDisconnected(state.agentId, { code, reason });
|
|
13753
13991
|
if (state.interactive) displayStatus(state);
|
|
13754
13992
|
},
|
|
13755
13993
|
onError: (error2) => {
|
|
@@ -13849,7 +14087,7 @@ async function run(options) {
|
|
|
13849
14087
|
},
|
|
13850
14088
|
report: (usage) => reportOpenAiUsage(state.agentId, state.authHeader, usage),
|
|
13851
14089
|
isLocalCredentialProblem: isLocalCredentialProblem2,
|
|
13852
|
-
forcedOnHint: "connect a ChatGPT account to this runner, or run `opencode auth login`",
|
|
14090
|
+
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
14091
|
firstDelayMs: firstReportDelayMs,
|
|
13854
14092
|
nextDelayMs: usageReportDelayMs,
|
|
13855
14093
|
failureLogLevel: usageReportFailureLogLevel
|
|
@@ -13892,7 +14130,7 @@ async function run(options) {
|
|
|
13892
14130
|
}
|
|
13893
14131
|
|
|
13894
14132
|
// src/index.ts
|
|
13895
|
-
var
|
|
14133
|
+
var version = getCliVersion();
|
|
13896
14134
|
var program = new Command();
|
|
13897
14135
|
program.name("evident").description("Run OpenCode locally and connect it to Evident").version(version).option(
|
|
13898
14136
|
"--endpoint <url>",
|