@evident-ai/cli 3.5.1 → 3.5.2-dev.2828873
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 +243 -70
- 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.
|
|
1161
|
+
var CLI_VERSION = (true ? "3.5.2-dev.2828873" : 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";
|
|
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
|
+
}
|
|
1349
1452
|
function redact(message) {
|
|
1350
|
-
|
|
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,8 +1461,8 @@ 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 = [];
|
|
@@ -1382,8 +1486,8 @@ function admitUnderRateLimit(source, now) {
|
|
|
1382
1486
|
}
|
|
1383
1487
|
if (now - window.windowStartedAt >= RATE_LIMIT_WINDOW_MS) {
|
|
1384
1488
|
if (window.windowDroppedCount > 0) {
|
|
1385
|
-
|
|
1386
|
-
`
|
|
1489
|
+
reportTelemetryDiagnostic(
|
|
1490
|
+
`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
1491
|
);
|
|
1388
1492
|
}
|
|
1389
1493
|
window.windowStartedAt = now;
|
|
@@ -1393,8 +1497,8 @@ function admitUnderRateLimit(source, now) {
|
|
|
1393
1497
|
if (window.windowCount >= RATE_LIMIT_MAX_EVENTS) {
|
|
1394
1498
|
window.windowDroppedCount++;
|
|
1395
1499
|
if (window.windowDroppedCount === 1) {
|
|
1396
|
-
|
|
1397
|
-
`
|
|
1500
|
+
reportTelemetryDiagnostic(
|
|
1501
|
+
`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
1502
|
);
|
|
1399
1503
|
}
|
|
1400
1504
|
return false;
|
|
@@ -1404,9 +1508,9 @@ function admitUnderRateLimit(source, now) {
|
|
|
1404
1508
|
}
|
|
1405
1509
|
function forwardRunnerActivity(entry, context) {
|
|
1406
1510
|
try {
|
|
1407
|
-
if (!FORWARDED_LEVELS.has(entry.level)) return;
|
|
1408
|
-
if (!context.agentId || !context.authHeader) return;
|
|
1409
1511
|
const source = entry.source ?? "cli.run";
|
|
1512
|
+
if (!FORWARDED_LEVELS_BY_SOURCE[source].has(entry.level)) return;
|
|
1513
|
+
if (!context.agentId || !context.authHeader) return;
|
|
1410
1514
|
if (!admitUnderRateLimit(source, Date.now())) return;
|
|
1411
1515
|
const rawMessage = entry.error ?? entry.message ?? "";
|
|
1412
1516
|
const message = truncate(redact(rawMessage));
|
|
@@ -1417,8 +1521,8 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1417
1521
|
agentId: context.agentId
|
|
1418
1522
|
});
|
|
1419
1523
|
} catch (err) {
|
|
1420
|
-
|
|
1421
|
-
`
|
|
1524
|
+
reportTelemetryDiagnostic(
|
|
1525
|
+
`Runner activity telemetry failed to forward runner activity: ${err instanceof Error ? err.message : String(err)}`
|
|
1422
1526
|
);
|
|
1423
1527
|
}
|
|
1424
1528
|
}
|
|
@@ -3214,7 +3318,7 @@ import { join as join4 } from "node:path";
|
|
|
3214
3318
|
|
|
3215
3319
|
// src/lib/openai-usage.ts
|
|
3216
3320
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
3217
|
-
import { homedir as
|
|
3321
|
+
import { homedir as homedir3 } from "node:os";
|
|
3218
3322
|
import { join as join3 } from "node:path";
|
|
3219
3323
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
3220
3324
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
@@ -3229,7 +3333,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
3229
3333
|
}
|
|
3230
3334
|
function readOpenCodeChatGptCredentials() {
|
|
3231
3335
|
try {
|
|
3232
|
-
const raw = readFileSync3(join3(
|
|
3336
|
+
const raw = readFileSync3(join3(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
3233
3337
|
let parsed;
|
|
3234
3338
|
try {
|
|
3235
3339
|
parsed = JSON.parse(raw);
|
|
@@ -3370,16 +3474,17 @@ function hasPrimaryHeaders(headers) {
|
|
|
3370
3474
|
].some((name) => headers.has(name));
|
|
3371
3475
|
}
|
|
3372
3476
|
async function getOpenAiUsage(port, client) {
|
|
3477
|
+
const openCodeVersion = client?.version ?? "v1";
|
|
3373
3478
|
const credentials2 = readOpenCodeChatGptCredentials();
|
|
3374
3479
|
if (!credentials2) {
|
|
3375
3480
|
throw new OpenAiUsageError(
|
|
3376
|
-
"No ChatGPT login found. Connect a ChatGPT account to this runner, or run `opencode auth login`.",
|
|
3481
|
+
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
3482
|
"no_credentials"
|
|
3378
3483
|
);
|
|
3379
3484
|
}
|
|
3380
3485
|
if (credentials2.expiresAt < Date.now()) {
|
|
3381
3486
|
throw new OpenAiUsageError(
|
|
3382
|
-
"ChatGPT credentials have expired. Run `opencode auth login` to refresh them.",
|
|
3487
|
+
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
3488
|
"credentials_expired"
|
|
3384
3489
|
);
|
|
3385
3490
|
}
|
|
@@ -3661,7 +3766,7 @@ async function importOpenCodeV2Credentials({
|
|
|
3661
3766
|
// src/lib/opencode/session-db-boot.ts
|
|
3662
3767
|
import { spawn as spawn2 } from "node:child_process";
|
|
3663
3768
|
import { mkdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
|
|
3664
|
-
import { homedir as
|
|
3769
|
+
import { homedir as homedir4 } from "node:os";
|
|
3665
3770
|
import { dirname as dirname2, resolve as resolvePath } from "node:path";
|
|
3666
3771
|
|
|
3667
3772
|
// src/lib/runner-synchroniser.ts
|
|
@@ -4017,14 +4122,14 @@ async function restoreSessionDb(options, configPath, env) {
|
|
|
4017
4122
|
case 0:
|
|
4018
4123
|
return;
|
|
4019
4124
|
case 31:
|
|
4020
|
-
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(
|
|
4125
|
+
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir4(), env));
|
|
4021
4126
|
options.log(
|
|
4022
4127
|
"SESSION-DB-REPLICA-UNUSABLE: booting with a fresh opencode.db and replicating into the existing prefix this boot",
|
|
4023
4128
|
"warn"
|
|
4024
4129
|
);
|
|
4025
4130
|
return;
|
|
4026
4131
|
case 32:
|
|
4027
|
-
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(
|
|
4132
|
+
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir4(), env));
|
|
4028
4133
|
discardSessionDbDebris(options);
|
|
4029
4134
|
markNoReplicate(
|
|
4030
4135
|
options,
|
|
@@ -4113,7 +4218,7 @@ function fileExists(path) {
|
|
|
4113
4218
|
async function restoreAndVerifySessionDb(options) {
|
|
4114
4219
|
const env = options.env ?? process.env;
|
|
4115
4220
|
clearMarker(options);
|
|
4116
|
-
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(
|
|
4221
|
+
acknowledgeSessionDbRecoveryReport(sessionDbRecoveryReportPath(homedir4(), env));
|
|
4117
4222
|
const synchroniserEnv = await runSynchroniser(["env"], {
|
|
4118
4223
|
timeoutMs: SESSION_DB_SYNCHRONISER_TIMEOUT_MS,
|
|
4119
4224
|
env
|
|
@@ -4493,12 +4598,15 @@ function findProcessesByPattern(pgrepPattern, psPattern) {
|
|
|
4493
4598
|
}
|
|
4494
4599
|
} catch {
|
|
4495
4600
|
try {
|
|
4496
|
-
const psOutput = execSync(
|
|
4601
|
+
const psOutput = execSync("ps aux", {
|
|
4497
4602
|
encoding: "utf-8",
|
|
4498
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4603
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4604
|
+
// The whole table reaches Node; the 1 MiB default would surface a spurious failure.
|
|
4605
|
+
maxBuffer: 16 * 1024 * 1024
|
|
4499
4606
|
}).trim();
|
|
4500
4607
|
if (psOutput) {
|
|
4501
4608
|
for (const line of psOutput.split("\n")) {
|
|
4609
|
+
if (!psPattern.test(line)) continue;
|
|
4502
4610
|
const parts = line.trim().split(/\s+/);
|
|
4503
4611
|
if (parts.length >= 2) {
|
|
4504
4612
|
const pid = parseInt(parts[1], 10);
|
|
@@ -4507,8 +4615,9 @@ function findProcessesByPattern(pgrepPattern, psPattern) {
|
|
|
4507
4615
|
}
|
|
4508
4616
|
}
|
|
4509
4617
|
} catch (err) {
|
|
4618
|
+
const reason = (typeof err === "object" && err !== null && "stderr" in err && typeof err.stderr === "string" ? err.stderr : "").split("\n").map((line) => line.trim()).find(Boolean) ?? (err instanceof Error ? err.message : String(err)).split("\n").map((line) => line.trim()).find(Boolean) ?? "no error detail available";
|
|
4510
4619
|
console.warn(
|
|
4511
|
-
`
|
|
4620
|
+
`Could not list running processes, so an already-running OpenCode may not be detected: ${reason}`
|
|
4512
4621
|
);
|
|
4513
4622
|
}
|
|
4514
4623
|
}
|
|
@@ -4540,7 +4649,7 @@ function findProcessesByPattern(pgrepPattern, psPattern) {
|
|
|
4540
4649
|
return instances;
|
|
4541
4650
|
}
|
|
4542
4651
|
function findOpenCodeProcesses() {
|
|
4543
|
-
return findProcessesByPattern("opencode serve|opencode-serve",
|
|
4652
|
+
return findProcessesByPattern("opencode serve|opencode-serve", /opencode (serve|--port)/);
|
|
4544
4653
|
}
|
|
4545
4654
|
async function scanPortsForOpenCode() {
|
|
4546
4655
|
const instances = [];
|
|
@@ -4928,11 +5037,11 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
4928
5037
|
|
|
4929
5038
|
// src/lib/opencode/log-tail.ts
|
|
4930
5039
|
import { statSync as statSync4 } from "node:fs";
|
|
4931
|
-
import { homedir as
|
|
5040
|
+
import { homedir as homedir5 } from "node:os";
|
|
4932
5041
|
import { join as join7 } from "node:path";
|
|
4933
5042
|
import { open as open2, stat } from "node:fs/promises";
|
|
4934
5043
|
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
4935
|
-
function resolveOpenCodeLogPath(homeDir =
|
|
5044
|
+
function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
|
|
4936
5045
|
const dataDir = env.XDG_DATA_HOME || join7(homeDir, ".local", "share");
|
|
4937
5046
|
return join7(dataDir, "opencode", "log", "opencode.log");
|
|
4938
5047
|
}
|
|
@@ -5087,7 +5196,11 @@ async function reclaimSessionDbSpace(input) {
|
|
|
5087
5196
|
console.warn(
|
|
5088
5197
|
`[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: a session turn is live`
|
|
5089
5198
|
);
|
|
5090
|
-
return {
|
|
5199
|
+
return {
|
|
5200
|
+
ok: false,
|
|
5201
|
+
skipped: "full-vacuum-blocked",
|
|
5202
|
+
detail: "a session turn is live"
|
|
5203
|
+
};
|
|
5091
5204
|
}
|
|
5092
5205
|
const fileBytesForGuard = statSync5(dbPath).size;
|
|
5093
5206
|
const skipReason = insufficientSpaceReason(dbPath, fileBytesForGuard);
|
|
@@ -5095,7 +5208,7 @@ async function reclaimSessionDbSpace(input) {
|
|
|
5095
5208
|
console.warn(
|
|
5096
5209
|
`[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: ${skipReason}`
|
|
5097
5210
|
);
|
|
5098
|
-
return { ok: false, skipped: "insufficient-disk-space" };
|
|
5211
|
+
return { ok: false, skipped: "insufficient-disk-space", detail: skipReason };
|
|
5099
5212
|
}
|
|
5100
5213
|
const beforeBytes = readLogicalBytes(db);
|
|
5101
5214
|
db.exec("PRAGMA auto_vacuum=INCREMENTAL");
|
|
@@ -5976,7 +6089,7 @@ function createResourceUsageCollector(homeDir) {
|
|
|
5976
6089
|
}
|
|
5977
6090
|
|
|
5978
6091
|
// src/lib/channels/driver.ts
|
|
5979
|
-
import { homedir as
|
|
6092
|
+
import { homedir as homedir6 } from "node:os";
|
|
5980
6093
|
|
|
5981
6094
|
// src/lib/runner-file-sync.ts
|
|
5982
6095
|
import { join as join9 } from "node:path";
|
|
@@ -6859,7 +6972,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6859
6972
|
this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
|
|
6860
6973
|
this.now = config.now ?? (() => Date.now());
|
|
6861
6974
|
this.fileSyncDirectories = config.fileSyncDirectories ?? [];
|
|
6862
|
-
this.homeDir = config.homeDir ??
|
|
6975
|
+
this.homeDir = config.homeDir ?? homedir6();
|
|
6863
6976
|
this.maxActiveSessions = config.maxActiveSessions;
|
|
6864
6977
|
this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
|
|
6865
6978
|
this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
|
|
@@ -12267,7 +12380,13 @@ function logActivity(state, entry) {
|
|
|
12267
12380
|
const level = entry.level ?? (entry.type === "error" ? "error" : "info");
|
|
12268
12381
|
if (!meetsThreshold(state, level)) return;
|
|
12269
12382
|
forwardRunnerActivity(
|
|
12270
|
-
{
|
|
12383
|
+
{
|
|
12384
|
+
level,
|
|
12385
|
+
message: entry.message,
|
|
12386
|
+
error: entry.error,
|
|
12387
|
+
metadata: entry.metadata,
|
|
12388
|
+
...entry.source ? { source: entry.source } : {}
|
|
12389
|
+
},
|
|
12271
12390
|
{ agentId: state.agentId, authHeader: state.authHeader }
|
|
12272
12391
|
);
|
|
12273
12392
|
const fullEntry = {
|
|
@@ -12289,7 +12408,7 @@ function logActivity(state, entry) {
|
|
|
12289
12408
|
}
|
|
12290
12409
|
function reportSessionDbRecovery(state) {
|
|
12291
12410
|
try {
|
|
12292
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
12411
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir7(), env: process.env });
|
|
12293
12412
|
for (const record of report.records) {
|
|
12294
12413
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
12295
12414
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -12343,7 +12462,7 @@ function reportOpenCodeV2CredentialImport(state, result) {
|
|
|
12343
12462
|
logActivity(state, {
|
|
12344
12463
|
type: "info",
|
|
12345
12464
|
level: "warn",
|
|
12346
|
-
message: `OpenCode V2 saved login(s) for ${integrationIds} on this machine, but OpenCode V2 will not use them.
|
|
12465
|
+
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.`
|
|
12347
12466
|
});
|
|
12348
12467
|
}
|
|
12349
12468
|
if (result.imported.length === 0 && result.unsupported.length === 0 && result.failed.length === 0) {
|
|
@@ -12368,7 +12487,7 @@ async function importOpenCodeV2CredentialsForState(state) {
|
|
|
12368
12487
|
}
|
|
12369
12488
|
const result = await importOpenCodeV2Credentials({
|
|
12370
12489
|
request: client.request,
|
|
12371
|
-
homeDir:
|
|
12490
|
+
homeDir: homedir7(),
|
|
12372
12491
|
directory,
|
|
12373
12492
|
guard: state.opencodeV2CredentialImportGuard,
|
|
12374
12493
|
log: (message, level = "info") => logActivity(state, { type: "info", level, message })
|
|
@@ -12587,7 +12706,14 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
12587
12706
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
12588
12707
|
}
|
|
12589
12708
|
function sessionDbPath() {
|
|
12590
|
-
return join11(
|
|
12709
|
+
return join11(homedir7(), ".local", "share", "opencode", "opencode.db");
|
|
12710
|
+
}
|
|
12711
|
+
function logSessionCleanupActivity(state, phase, entry) {
|
|
12712
|
+
logActivity(state, {
|
|
12713
|
+
...entry,
|
|
12714
|
+
source: "cli.session-cleanup",
|
|
12715
|
+
metadata: { ...entry.metadata, phase }
|
|
12716
|
+
});
|
|
12591
12717
|
}
|
|
12592
12718
|
function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
|
|
12593
12719
|
const record = {
|
|
@@ -12630,9 +12756,13 @@ async function runSweep(state, driver, config) {
|
|
|
12630
12756
|
try {
|
|
12631
12757
|
const sessions = await listSessions(state.port, state.opencodeClient ?? void 0);
|
|
12632
12758
|
if (sessions === null) {
|
|
12633
|
-
|
|
12759
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12634
12760
|
type: "info",
|
|
12635
|
-
message: `Session cleanup: could not list sessions (opencode unreachable); skipping this sweep (${mode})
|
|
12761
|
+
message: `Session cleanup: could not list sessions (opencode unreachable); skipping this sweep (${mode})`,
|
|
12762
|
+
metadata: {
|
|
12763
|
+
outcome: "sweep-skipped",
|
|
12764
|
+
reason: "opencode-unreachable"
|
|
12765
|
+
}
|
|
12636
12766
|
});
|
|
12637
12767
|
return;
|
|
12638
12768
|
}
|
|
@@ -12676,21 +12806,47 @@ async function runSweep(state, driver, config) {
|
|
|
12676
12806
|
const beforeMib = (reclaimResult.beforeBytes / 1024 / 1024).toFixed(1);
|
|
12677
12807
|
const afterMib = (reclaimResult.afterBytes / 1024 / 1024).toFixed(1);
|
|
12678
12808
|
const checkpointNote = reclaimResult.checkpoint.busy ? ` (on-disk file truncation deferred: checkpoint busy, ${reclaimResult.checkpoint.log} WAL frames pending)` : "";
|
|
12679
|
-
|
|
12809
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12680
12810
|
type: "info",
|
|
12681
|
-
message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB${checkpointNote}
|
|
12811
|
+
message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB${checkpointNote}`,
|
|
12812
|
+
metadata: {
|
|
12813
|
+
outcome: "reclaimed",
|
|
12814
|
+
mode: reclaimResult.mode,
|
|
12815
|
+
before_bytes: reclaimResult.beforeBytes,
|
|
12816
|
+
after_bytes: reclaimResult.afterBytes,
|
|
12817
|
+
checkpoint_busy: reclaimResult.checkpoint.busy,
|
|
12818
|
+
wal_frames_pending: reclaimResult.checkpoint.log,
|
|
12819
|
+
sessions_inspected: sessions.length,
|
|
12820
|
+
sessions_deleted: deleted,
|
|
12821
|
+
sessions_failed: failed,
|
|
12822
|
+
sessions_skipped_active: skippedNewlyActive
|
|
12823
|
+
}
|
|
12682
12824
|
});
|
|
12683
12825
|
} else {
|
|
12684
|
-
|
|
12826
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12685
12827
|
type: "info",
|
|
12686
|
-
|
|
12828
|
+
level: reclaimResult.skipped === "reclaim-error" || reclaimResult.skipped === "insufficient-disk-space" ? "warn" : "info",
|
|
12829
|
+
message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : ""),
|
|
12830
|
+
metadata: {
|
|
12831
|
+
outcome: "reclaim-skipped",
|
|
12832
|
+
reason: reclaimResult.skipped,
|
|
12833
|
+
...reclaimResult.detail !== void 0 ? { detail: reclaimResult.detail } : {},
|
|
12834
|
+
sessions_inspected: sessions.length,
|
|
12835
|
+
sessions_deleted: deleted,
|
|
12836
|
+
sessions_failed: failed,
|
|
12837
|
+
sessions_skipped_active: skippedNewlyActive
|
|
12838
|
+
}
|
|
12687
12839
|
});
|
|
12688
12840
|
}
|
|
12689
12841
|
} catch (error2) {
|
|
12690
12842
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
12691
|
-
|
|
12843
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
12692
12844
|
type: "error",
|
|
12693
|
-
error: `Session cleanup sweep failed (non-fatal, ${mode}): ${message}
|
|
12845
|
+
error: `Session cleanup sweep failed (non-fatal, ${mode}): ${message}`,
|
|
12846
|
+
metadata: {
|
|
12847
|
+
outcome: "sweep-failed",
|
|
12848
|
+
reason: "sweep-error"
|
|
12849
|
+
}
|
|
12694
12850
|
});
|
|
12695
12851
|
}
|
|
12696
12852
|
}
|
|
@@ -12706,14 +12862,19 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
12706
12862
|
for (const warning2 of config.warnings) {
|
|
12707
12863
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
12708
12864
|
}
|
|
12709
|
-
const dbBytes = statSessionDbBytes(
|
|
12865
|
+
const dbBytes = statSessionDbBytes(homedir7());
|
|
12710
12866
|
void (async () => {
|
|
12711
12867
|
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
12712
12868
|
if (reclaimAvailability !== null) {
|
|
12713
|
-
|
|
12869
|
+
logSessionCleanupActivity(state, "startup", {
|
|
12714
12870
|
type: "info",
|
|
12715
12871
|
level: shouldWarnForReclaimSkip(reclaimAvailability.reason) ? "warn" : "info",
|
|
12716
|
-
message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : "")
|
|
12872
|
+
message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : ""),
|
|
12873
|
+
metadata: {
|
|
12874
|
+
outcome: "preflight-skipped",
|
|
12875
|
+
reason: reclaimAvailability.reason,
|
|
12876
|
+
...reclaimAvailability.detail !== void 0 ? { detail: reclaimAvailability.detail } : {}
|
|
12877
|
+
}
|
|
12717
12878
|
});
|
|
12718
12879
|
}
|
|
12719
12880
|
const sizeWarning = buildSessionStoreSizeWarning({
|
|
@@ -12730,9 +12891,15 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
12730
12891
|
);
|
|
12731
12892
|
});
|
|
12732
12893
|
if (!config.enabled) return;
|
|
12733
|
-
|
|
12894
|
+
logSessionCleanupActivity(state, "startup", {
|
|
12734
12895
|
type: "info",
|
|
12735
|
-
message: `Session cleanup enabled (age=${config.maxAgeMs ?? "\u2014"}, count=${config.maxCount ?? "\u2014"}, interval=${config.intervalMs}ms)
|
|
12896
|
+
message: `Session cleanup enabled (age=${config.maxAgeMs ?? "\u2014"}, count=${config.maxCount ?? "\u2014"}, interval=${config.intervalMs}ms)`,
|
|
12897
|
+
metadata: {
|
|
12898
|
+
outcome: "armed",
|
|
12899
|
+
interval_ms: config.intervalMs,
|
|
12900
|
+
max_age_ms: config.maxAgeMs ?? null,
|
|
12901
|
+
max_count: config.maxCount ?? null
|
|
12902
|
+
}
|
|
12736
12903
|
});
|
|
12737
12904
|
const interval = setInterval(() => void runSweep(state, driver, config), config.intervalMs);
|
|
12738
12905
|
const firstSweep = setTimeout(
|
|
@@ -12911,7 +13078,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
12911
13078
|
});
|
|
12912
13079
|
return;
|
|
12913
13080
|
}
|
|
12914
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
13081
|
+
const { collect, stop } = createResourceUsageCollector(homedir7());
|
|
12915
13082
|
state.stopResourceUsageSampling = stop;
|
|
12916
13083
|
let consecutiveFailures = 0;
|
|
12917
13084
|
const tick = async () => {
|
|
@@ -13136,7 +13303,7 @@ async function run(options) {
|
|
|
13136
13303
|
let fileSyncDirectories;
|
|
13137
13304
|
try {
|
|
13138
13305
|
logLevel = resolveLogLevel(options);
|
|
13139
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
13306
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir7());
|
|
13140
13307
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
13141
13308
|
throw new Error(
|
|
13142
13309
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -13191,6 +13358,7 @@ async function run(options) {
|
|
|
13191
13358
|
authHeader: ""
|
|
13192
13359
|
};
|
|
13193
13360
|
setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
|
|
13361
|
+
setTelemetryDiagnosticsReporter((message) => log2(state, message, "warn"));
|
|
13194
13362
|
if (options.credentialSyncMarker) {
|
|
13195
13363
|
state.credentialSync = createCredentialSync({
|
|
13196
13364
|
markerPath: options.credentialSyncMarker,
|
|
@@ -13376,13 +13544,18 @@ async function run(options) {
|
|
|
13376
13544
|
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
13377
13545
|
}
|
|
13378
13546
|
state.credentialSync?.arm();
|
|
13379
|
-
|
|
13380
|
-
|
|
13381
|
-
|
|
13382
|
-
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
13547
|
+
const openCodeActivityForwarder = createOpenCodeActivityForwarder(() => ({
|
|
13548
|
+
agentId: state.agentId,
|
|
13549
|
+
authHeader: state.authHeader
|
|
13550
|
+
}));
|
|
13551
|
+
const openCodeLogTail = tailOpenCodeLogFile(
|
|
13552
|
+
resolveOpenCodeLogPath(homedir7(), process.env),
|
|
13553
|
+
openCodeActivityForwarder
|
|
13554
|
+
);
|
|
13555
|
+
state.stopOpenCodeLogTail = () => {
|
|
13556
|
+
openCodeActivityForwarder.flush();
|
|
13557
|
+
openCodeLogTail.stop();
|
|
13558
|
+
};
|
|
13386
13559
|
let sessionDbVerifyFatal = false;
|
|
13387
13560
|
if (!options.restoreSessionDb) {
|
|
13388
13561
|
log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
|
|
@@ -13499,7 +13672,7 @@ async function run(options) {
|
|
|
13499
13672
|
const provenance = checkSessionDbProvenance({
|
|
13500
13673
|
dbPath: sessionDbPath(),
|
|
13501
13674
|
currentVersion: state.opencodeVersion,
|
|
13502
|
-
homeDir:
|
|
13675
|
+
homeDir: homedir7(),
|
|
13503
13676
|
env: process.env
|
|
13504
13677
|
});
|
|
13505
13678
|
if (provenance.anomaly) {
|
|
@@ -13669,7 +13842,7 @@ async function run(options) {
|
|
|
13669
13842
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
13670
13843
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
13671
13844
|
fileSyncDirectories,
|
|
13672
|
-
homeDir:
|
|
13845
|
+
homeDir: homedir7(),
|
|
13673
13846
|
maxActiveSessions,
|
|
13674
13847
|
log: (entry) => (
|
|
13675
13848
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -13845,7 +14018,7 @@ async function run(options) {
|
|
|
13845
14018
|
},
|
|
13846
14019
|
report: (usage) => reportOpenAiUsage(state.agentId, state.authHeader, usage),
|
|
13847
14020
|
isLocalCredentialProblem: isLocalCredentialProblem2,
|
|
13848
|
-
forcedOnHint: "connect a ChatGPT account to this runner, or run `opencode auth login`",
|
|
14021
|
+
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`",
|
|
13849
14022
|
firstDelayMs: firstReportDelayMs,
|
|
13850
14023
|
nextDelayMs: usageReportDelayMs,
|
|
13851
14024
|
failureLogLevel: usageReportFailureLogLevel
|