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