@evident-ai/cli 3.5.2-dev.4912967 → 3.5.2-dev.507f0a3
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 +225 -45
- 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
|
|
@@ -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.507f0a3" : 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 {
|
|
@@ -1317,25 +1337,72 @@ function parseOpenCodeLogLine(line) {
|
|
|
1317
1337
|
const level = levelMatch[1].toUpperCase();
|
|
1318
1338
|
if (level !== "WARN" && level !== "ERROR") return null;
|
|
1319
1339
|
const sessionMatch = normalisedLine.match(/(?:^|\s)sessionID=(\S+)/);
|
|
1320
|
-
|
|
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;
|
|
1321
1348
|
}
|
|
1322
1349
|
var MAX_LINE_BUFFER_BYTES = 16 * 1024;
|
|
1350
|
+
var REPEAT_SUPPRESSION_WINDOW_MS = 6e4;
|
|
1323
1351
|
function createOpenCodeActivityForwarder(getContext) {
|
|
1324
1352
|
let buffer = Buffer.alloc(0);
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
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
|
+
};
|
|
1328
1359
|
forwardRunnerActivity(
|
|
1329
1360
|
{
|
|
1330
1361
|
level: parsed.level,
|
|
1331
1362
|
error: line,
|
|
1332
|
-
metadata:
|
|
1363
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
|
|
1333
1364
|
source: "opencode"
|
|
1334
1365
|
},
|
|
1335
1366
|
getContext()
|
|
1336
1367
|
);
|
|
1337
1368
|
};
|
|
1338
|
-
|
|
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) => {
|
|
1339
1406
|
buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8")]);
|
|
1340
1407
|
let newlineIndex;
|
|
1341
1408
|
while ((newlineIndex = buffer.indexOf(10)) !== -1) {
|
|
@@ -1346,7 +1413,16 @@ function createOpenCodeActivityForwarder(getContext) {
|
|
|
1346
1413
|
flushLine(buffer.toString("utf-8"));
|
|
1347
1414
|
buffer = Buffer.alloc(0);
|
|
1348
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();
|
|
1349
1424
|
};
|
|
1425
|
+
return forward;
|
|
1350
1426
|
}
|
|
1351
1427
|
var MAX_MESSAGE_LENGTH = 500;
|
|
1352
1428
|
var MAX_METADATA_VALUE_LENGTH = 200;
|
|
@@ -1372,7 +1448,7 @@ function redactHomeDirectory(message) {
|
|
|
1372
1448
|
}
|
|
1373
1449
|
return result + message.slice(searchFrom);
|
|
1374
1450
|
}
|
|
1375
|
-
function
|
|
1451
|
+
function redactDiagnosticText(message) {
|
|
1376
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>");
|
|
1377
1453
|
return redactHomeDirectory(redacted);
|
|
1378
1454
|
}
|
|
@@ -1384,14 +1460,17 @@ function sanitiseMetadata(metadata) {
|
|
|
1384
1460
|
if (!metadata) return {};
|
|
1385
1461
|
const entries = Object.entries(metadata).slice(0, MAX_METADATA_ENTRIES);
|
|
1386
1462
|
if (Object.keys(metadata).length > entries.length) {
|
|
1387
|
-
|
|
1388
|
-
`
|
|
1463
|
+
reportTelemetryDiagnostic(
|
|
1464
|
+
`Runner activity telemetry dropped ${Object.keys(metadata).length - entries.length} metadata entries; those entries were not sent`
|
|
1389
1465
|
);
|
|
1390
1466
|
}
|
|
1391
1467
|
const sanitised = [];
|
|
1392
1468
|
for (const [key, value] of entries) {
|
|
1393
1469
|
if (typeof value === "string") {
|
|
1394
|
-
sanitised.push([
|
|
1470
|
+
sanitised.push([
|
|
1471
|
+
key,
|
|
1472
|
+
truncate(redactDiagnosticText(value).slice(0, MAX_METADATA_VALUE_LENGTH))
|
|
1473
|
+
]);
|
|
1395
1474
|
} else if (value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
|
|
1396
1475
|
sanitised.push([key, value]);
|
|
1397
1476
|
}
|
|
@@ -1409,8 +1488,8 @@ function admitUnderRateLimit(source, now) {
|
|
|
1409
1488
|
}
|
|
1410
1489
|
if (now - window.windowStartedAt >= RATE_LIMIT_WINDOW_MS) {
|
|
1411
1490
|
if (window.windowDroppedCount > 0) {
|
|
1412
|
-
|
|
1413
|
-
`
|
|
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}"`
|
|
1414
1493
|
);
|
|
1415
1494
|
}
|
|
1416
1495
|
window.windowStartedAt = now;
|
|
@@ -1420,8 +1499,8 @@ function admitUnderRateLimit(source, now) {
|
|
|
1420
1499
|
if (window.windowCount >= RATE_LIMIT_MAX_EVENTS) {
|
|
1421
1500
|
window.windowDroppedCount++;
|
|
1422
1501
|
if (window.windowDroppedCount === 1) {
|
|
1423
|
-
|
|
1424
|
-
`
|
|
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}"`
|
|
1425
1504
|
);
|
|
1426
1505
|
}
|
|
1427
1506
|
return false;
|
|
@@ -1436,7 +1515,7 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1436
1515
|
if (!context.agentId || !context.authHeader) return;
|
|
1437
1516
|
if (!admitUnderRateLimit(source, Date.now())) return;
|
|
1438
1517
|
const rawMessage = entry.error ?? entry.message ?? "";
|
|
1439
|
-
const message = truncate(
|
|
1518
|
+
const message = truncate(redactDiagnosticText(rawMessage));
|
|
1440
1519
|
logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
|
|
1441
1520
|
severity: SEVERITY_BY_LEVEL[entry.level],
|
|
1442
1521
|
message,
|
|
@@ -1444,8 +1523,8 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1444
1523
|
agentId: context.agentId
|
|
1445
1524
|
});
|
|
1446
1525
|
} catch (err) {
|
|
1447
|
-
|
|
1448
|
-
`
|
|
1526
|
+
reportTelemetryDiagnostic(
|
|
1527
|
+
`Runner activity telemetry failed to forward runner activity: ${err instanceof Error ? err.message : String(err)}`
|
|
1449
1528
|
);
|
|
1450
1529
|
}
|
|
1451
1530
|
}
|
|
@@ -1735,8 +1814,46 @@ async function waitForOpenCode2Health(port, password, timeoutMs = 3e4) {
|
|
|
1735
1814
|
|
|
1736
1815
|
// src/lib/http-timeout.ts
|
|
1737
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
|
+
}
|
|
1738
1841
|
function withRequestTimeout(fetchImpl, timeoutMs) {
|
|
1739
|
-
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
|
+
});
|
|
1740
1857
|
}
|
|
1741
1858
|
|
|
1742
1859
|
// src/lib/opencode/client.ts
|
|
@@ -3397,16 +3514,17 @@ function hasPrimaryHeaders(headers) {
|
|
|
3397
3514
|
].some((name) => headers.has(name));
|
|
3398
3515
|
}
|
|
3399
3516
|
async function getOpenAiUsage(port, client) {
|
|
3517
|
+
const openCodeVersion = client?.version ?? "v1";
|
|
3400
3518
|
const credentials2 = readOpenCodeChatGptCredentials();
|
|
3401
3519
|
if (!credentials2) {
|
|
3402
3520
|
throw new OpenAiUsageError(
|
|
3403
|
-
"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`.",
|
|
3404
3522
|
"no_credentials"
|
|
3405
3523
|
);
|
|
3406
3524
|
}
|
|
3407
3525
|
if (credentials2.expiresAt < Date.now()) {
|
|
3408
3526
|
throw new OpenAiUsageError(
|
|
3409
|
-
"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.",
|
|
3410
3528
|
"credentials_expired"
|
|
3411
3529
|
);
|
|
3412
3530
|
}
|
|
@@ -5392,6 +5510,8 @@ var StreamForwarder = class {
|
|
|
5392
5510
|
if (done) break;
|
|
5393
5511
|
const chunk = Buffer.from(value);
|
|
5394
5512
|
for (let i = 0; i < chunk.length; i += MAX_FRAME_BYTES) {
|
|
5513
|
+
await stream.pausePromise;
|
|
5514
|
+
if (ac.signal.aborted) break;
|
|
5395
5515
|
const slice = chunk.subarray(i, i + MAX_FRAME_BYTES);
|
|
5396
5516
|
if (this.binaryFramesSupported) {
|
|
5397
5517
|
if (this.ws.readyState === WebSocket.OPEN) {
|
|
@@ -5434,6 +5554,7 @@ function classifyUpgradeRejection(headers) {
|
|
|
5434
5554
|
}
|
|
5435
5555
|
var MAX_RECONNECT_DELAY = 3e4;
|
|
5436
5556
|
var BASE_RECONNECT_DELAY = 500;
|
|
5557
|
+
var RELAY_SILENCE_TIMEOUT_MS = 9e4;
|
|
5437
5558
|
function getReconnectDelay(attempt) {
|
|
5438
5559
|
const exponentialDelay = BASE_RECONNECT_DELAY * Math.pow(2, attempt);
|
|
5439
5560
|
const jitter = Math.random() * 1e3;
|
|
@@ -5505,10 +5626,27 @@ function connectTunnel(options) {
|
|
|
5505
5626
|
ws.close();
|
|
5506
5627
|
reject(new Error("Connection timeout"));
|
|
5507
5628
|
}, 3e4);
|
|
5629
|
+
let relaySilenceTimeout;
|
|
5630
|
+
let relaySilenceTimedOut = false;
|
|
5631
|
+
const clearRelaySilenceTimeout = () => {
|
|
5632
|
+
if (relaySilenceTimeout === void 0) return;
|
|
5633
|
+
clearTimeout(relaySilenceTimeout);
|
|
5634
|
+
relaySilenceTimeout = void 0;
|
|
5635
|
+
};
|
|
5636
|
+
const armRelaySilenceTimeout = () => {
|
|
5637
|
+
clearRelaySilenceTimeout();
|
|
5638
|
+
relaySilenceTimeout = setTimeout(() => {
|
|
5639
|
+
relaySilenceTimeout = void 0;
|
|
5640
|
+
relaySilenceTimedOut = true;
|
|
5641
|
+
ws.terminate();
|
|
5642
|
+
}, RELAY_SILENCE_TIMEOUT_MS);
|
|
5643
|
+
relaySilenceTimeout.unref?.();
|
|
5644
|
+
};
|
|
5508
5645
|
let upgradeRejection = null;
|
|
5509
5646
|
let upgradeRejectionReason = null;
|
|
5510
5647
|
ws.on("unexpected-response", (_req, res) => {
|
|
5511
5648
|
clearTimeout(connectionTimeout);
|
|
5649
|
+
clearRelaySilenceTimeout();
|
|
5512
5650
|
const reason = classifyUpgradeRejection(res.headers);
|
|
5513
5651
|
upgradeRejectionReason = reason;
|
|
5514
5652
|
const chunks = [];
|
|
@@ -5538,6 +5676,7 @@ function connectTunnel(options) {
|
|
|
5538
5676
|
onInfo?.("WebSocket connection established");
|
|
5539
5677
|
});
|
|
5540
5678
|
ws.on("message", (data, isBinary) => {
|
|
5679
|
+
if (relaySilenceTimeout !== void 0) armRelaySilenceTimeout();
|
|
5541
5680
|
if (isBinary) {
|
|
5542
5681
|
try {
|
|
5543
5682
|
const frame = decodeBinaryBodyFrame(toUint8Array(data));
|
|
@@ -5571,6 +5710,7 @@ function connectTunnel(options) {
|
|
|
5571
5710
|
switch (message.type) {
|
|
5572
5711
|
case "connected": {
|
|
5573
5712
|
clearTimeout(connectionTimeout);
|
|
5713
|
+
armRelaySilenceTimeout();
|
|
5574
5714
|
forwarder.setBinaryFramesSupported(message.binary_frames === true);
|
|
5575
5715
|
const connectedAgentId = message.agent_id ?? agentId;
|
|
5576
5716
|
onConnected?.(connectedAgentId);
|
|
@@ -5595,6 +5735,7 @@ function connectTunnel(options) {
|
|
|
5595
5735
|
});
|
|
5596
5736
|
ws.on("error", (error2) => {
|
|
5597
5737
|
clearTimeout(connectionTimeout);
|
|
5738
|
+
clearRelaySilenceTimeout();
|
|
5598
5739
|
const detail = upgradeRejection ?? describeSocketError(error2, url);
|
|
5599
5740
|
if (upgradeRejectionReason === "do_code_updated") {
|
|
5600
5741
|
onWarning?.("Relay redeployed \u2014 reconnecting");
|
|
@@ -5606,7 +5747,8 @@ function connectTunnel(options) {
|
|
|
5606
5747
|
);
|
|
5607
5748
|
});
|
|
5608
5749
|
ws.on("close", (code, reason) => {
|
|
5609
|
-
|
|
5750
|
+
clearRelaySilenceTimeout();
|
|
5751
|
+
const reasonStr = (relaySilenceTimedOut ? "no relay heartbeat for 90s \u2014 reconnecting" : void 0) || reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
|
|
5610
5752
|
forwarder.abortAll();
|
|
5611
5753
|
onDisconnected?.(code, reasonStr);
|
|
5612
5754
|
});
|
|
@@ -5614,9 +5756,10 @@ function connectTunnel(options) {
|
|
|
5614
5756
|
}
|
|
5615
5757
|
|
|
5616
5758
|
// src/lib/tunnel/runner-connection.ts
|
|
5617
|
-
var RunnerConnection = class {
|
|
5759
|
+
var RunnerConnection = class _RunnerConnection {
|
|
5618
5760
|
opts;
|
|
5619
5761
|
sleep;
|
|
5762
|
+
now;
|
|
5620
5763
|
connection = null;
|
|
5621
5764
|
resolvedAgentId;
|
|
5622
5765
|
/** True while a (re)connect loop is in flight. */
|
|
@@ -5625,10 +5768,17 @@ var RunnerConnection = class {
|
|
|
5625
5768
|
reconnectPromise = null;
|
|
5626
5769
|
/** 1-based count of the current reconnect attempt streak. */
|
|
5627
5770
|
reconnectAttempt = 0;
|
|
5771
|
+
generation = 0;
|
|
5772
|
+
disconnectedAtMs = null;
|
|
5773
|
+
static RELAY_RECONNECT_CLOSE_REASONS = /* @__PURE__ */ new Set([
|
|
5774
|
+
"No pong received",
|
|
5775
|
+
"Orphaned socket"
|
|
5776
|
+
]);
|
|
5628
5777
|
constructor(opts) {
|
|
5629
5778
|
this.opts = opts;
|
|
5630
5779
|
this.resolvedAgentId = opts.agentId;
|
|
5631
5780
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
5781
|
+
this.now = opts.now ?? Date.now;
|
|
5632
5782
|
}
|
|
5633
5783
|
get agentId() {
|
|
5634
5784
|
return this.resolvedAgentId;
|
|
@@ -5664,14 +5814,24 @@ var RunnerConnection = class {
|
|
|
5664
5814
|
port: this.opts.port,
|
|
5665
5815
|
openCodePassword: this.opts.openCodePassword,
|
|
5666
5816
|
onConnected: (agentId) => {
|
|
5817
|
+
const attempt = this.reconnectAttempt + 1;
|
|
5667
5818
|
this.reconnectAttempt = 0;
|
|
5668
5819
|
this.reconnecting = false;
|
|
5669
5820
|
this.resolvedAgentId = agentId;
|
|
5670
|
-
|
|
5821
|
+
this.generation++;
|
|
5822
|
+
const downtimeMs = this.disconnectedAtMs === null ? null : this.now() - this.disconnectedAtMs;
|
|
5823
|
+
this.disconnectedAtMs = null;
|
|
5824
|
+
events.onConnected(agentId, isReconnect, {
|
|
5825
|
+
generation: this.generation,
|
|
5826
|
+
attempt,
|
|
5827
|
+
downtimeMs
|
|
5828
|
+
});
|
|
5671
5829
|
},
|
|
5672
5830
|
onDisconnected: (code, reason) => {
|
|
5673
|
-
|
|
5674
|
-
|
|
5831
|
+
this.disconnectedAtMs = this.now();
|
|
5832
|
+
events.onDisconnected(code, reason, { generation: this.generation });
|
|
5833
|
+
const relayRequestedReconnect = code === 1e3 && _RunnerConnection.RELAY_RECONNECT_CLOSE_REASONS.has(reason);
|
|
5834
|
+
if (this.opts.isRunning() && (code !== 1e3 || relayRequestedReconnect) && !this.reconnecting) {
|
|
5675
5835
|
this.reconnectPromise = this.connectWithRetry(true).catch((err) => {
|
|
5676
5836
|
events.onError?.(`Reconnection failed: ${err.message}`);
|
|
5677
5837
|
});
|
|
@@ -12147,6 +12307,7 @@ function createCredentialSync({
|
|
|
12147
12307
|
|
|
12148
12308
|
// src/commands/run.ts
|
|
12149
12309
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
12310
|
+
var MAX_DISCONNECT_REASON_LENGTH = 200;
|
|
12150
12311
|
var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
|
|
12151
12312
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
12152
12313
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
@@ -12365,7 +12526,9 @@ function displayStatus(state) {
|
|
|
12365
12526
|
const opencode = state.opencodeConnected ? chalk7.green(`opencode: :${state.port}`) : chalk7.red(`opencode: :${state.port} (down)`);
|
|
12366
12527
|
const messages = state.messageCount > 0 ? chalk7.dim(` \xB7 ${state.messageCount} processed`) : "";
|
|
12367
12528
|
const last = state.activityLog[state.activityLog.length - 1];
|
|
12368
|
-
const detail = last ? chalk7.dim(
|
|
12529
|
+
const detail = last ? chalk7.dim(
|
|
12530
|
+
` \xB7 ${last.timestamp.toISOString()} ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`
|
|
12531
|
+
) : "";
|
|
12369
12532
|
const agent = state.agentName ?? state.agentId;
|
|
12370
12533
|
console.log(
|
|
12371
12534
|
`${chalk7.bold("Evident")} ${chalk7.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
|
|
@@ -12384,7 +12547,7 @@ function reportOpenCodeV2CredentialImport(state, result) {
|
|
|
12384
12547
|
logActivity(state, {
|
|
12385
12548
|
type: "info",
|
|
12386
12549
|
level: "warn",
|
|
12387
|
-
message: `OpenCode V2 saved login(s) for ${integrationIds} on this machine, but OpenCode V2 will not use them.
|
|
12550
|
+
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.`
|
|
12388
12551
|
});
|
|
12389
12552
|
}
|
|
12390
12553
|
if (result.imported.length === 0 && result.unsupported.length === 0 && result.failed.length === 0) {
|
|
@@ -13280,6 +13443,7 @@ async function run(options) {
|
|
|
13280
13443
|
authHeader: ""
|
|
13281
13444
|
};
|
|
13282
13445
|
setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
|
|
13446
|
+
setTelemetryDiagnosticsReporter((message) => log2(state, message, "warn"));
|
|
13283
13447
|
if (options.credentialSyncMarker) {
|
|
13284
13448
|
state.credentialSync = createCredentialSync({
|
|
13285
13449
|
markerPath: options.credentialSyncMarker,
|
|
@@ -13465,13 +13629,18 @@ async function run(options) {
|
|
|
13465
13629
|
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
13466
13630
|
}
|
|
13467
13631
|
state.credentialSync?.arm();
|
|
13468
|
-
|
|
13632
|
+
const openCodeActivityForwarder = createOpenCodeActivityForwarder(() => ({
|
|
13633
|
+
agentId: state.agentId,
|
|
13634
|
+
authHeader: state.authHeader
|
|
13635
|
+
}));
|
|
13636
|
+
const openCodeLogTail = tailOpenCodeLogFile(
|
|
13469
13637
|
resolveOpenCodeLogPath(homedir7(), process.env),
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13638
|
+
openCodeActivityForwarder
|
|
13639
|
+
);
|
|
13640
|
+
state.stopOpenCodeLogTail = () => {
|
|
13641
|
+
openCodeActivityForwarder.flush();
|
|
13642
|
+
openCodeLogTail.stop();
|
|
13643
|
+
};
|
|
13475
13644
|
let sessionDbVerifyFatal = false;
|
|
13476
13645
|
if (!options.restoreSessionDb) {
|
|
13477
13646
|
log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
|
|
@@ -13780,12 +13949,13 @@ async function run(options) {
|
|
|
13780
13949
|
openCodePassword: state.opencodePassword,
|
|
13781
13950
|
isRunning: () => state.running,
|
|
13782
13951
|
events: {
|
|
13783
|
-
onConnected: (agentId, isReconnect) => {
|
|
13952
|
+
onConnected: (agentId, isReconnect, transition) => {
|
|
13784
13953
|
state.connected = true;
|
|
13785
13954
|
state.agentId = agentId;
|
|
13955
|
+
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})`;
|
|
13786
13956
|
logActivity(state, {
|
|
13787
13957
|
type: "info",
|
|
13788
|
-
message:
|
|
13958
|
+
message: connectionMessage
|
|
13789
13959
|
});
|
|
13790
13960
|
if (options.tunnelReadyFile) {
|
|
13791
13961
|
const marker = writeTunnelReadyMarker(options.tunnelReadyFile, agentId);
|
|
@@ -13806,7 +13976,12 @@ async function run(options) {
|
|
|
13806
13976
|
version: state.opencodeVersion,
|
|
13807
13977
|
major: state.opencodeApiVersion,
|
|
13808
13978
|
connected: state.opencodeConnected
|
|
13809
|
-
})
|
|
13979
|
+
}),
|
|
13980
|
+
connection_generation: transition.generation,
|
|
13981
|
+
...isReconnect && transition.downtimeMs !== null ? {
|
|
13982
|
+
reconnect_attempt: transition.attempt,
|
|
13983
|
+
downtime_ms: transition.downtimeMs
|
|
13984
|
+
} : {}
|
|
13810
13985
|
});
|
|
13811
13986
|
if (!isReconnect) tunnelSpinner?.succeed("Tunnel connected");
|
|
13812
13987
|
if (state.interactive) displayStatus(state);
|
|
@@ -13828,13 +14003,18 @@ async function run(options) {
|
|
|
13828
14003
|
if (state.interactive) displayStatus(state);
|
|
13829
14004
|
});
|
|
13830
14005
|
},
|
|
13831
|
-
onDisconnected: (code, reason) => {
|
|
14006
|
+
onDisconnected: (code, reason, transition) => {
|
|
13832
14007
|
state.connected = false;
|
|
14008
|
+
const safeReason = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
|
|
13833
14009
|
logActivity(state, {
|
|
13834
14010
|
type: "info",
|
|
13835
|
-
message: `Tunnel disconnected (code: ${code}, reason: ${
|
|
14011
|
+
message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason})`
|
|
14012
|
+
});
|
|
14013
|
+
emitAgentDisconnected(state.agentId, {
|
|
14014
|
+
code,
|
|
14015
|
+
reason: safeReason,
|
|
14016
|
+
connection_generation: transition.generation
|
|
13836
14017
|
});
|
|
13837
|
-
emitAgentDisconnected(state.agentId, { code, reason });
|
|
13838
14018
|
if (state.interactive) displayStatus(state);
|
|
13839
14019
|
},
|
|
13840
14020
|
onError: (error2) => {
|
|
@@ -13934,7 +14114,7 @@ async function run(options) {
|
|
|
13934
14114
|
},
|
|
13935
14115
|
report: (usage) => reportOpenAiUsage(state.agentId, state.authHeader, usage),
|
|
13936
14116
|
isLocalCredentialProblem: isLocalCredentialProblem2,
|
|
13937
|
-
forcedOnHint: "connect a ChatGPT account to this runner, or run `opencode auth login`",
|
|
14117
|
+
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`",
|
|
13938
14118
|
firstDelayMs: firstReportDelayMs,
|
|
13939
14119
|
nextDelayMs: usageReportDelayMs,
|
|
13940
14120
|
failureLogLevel: usageReportFailureLogLevel
|
|
@@ -13977,7 +14157,7 @@ async function run(options) {
|
|
|
13977
14157
|
}
|
|
13978
14158
|
|
|
13979
14159
|
// src/index.ts
|
|
13980
|
-
var
|
|
14160
|
+
var version = getCliVersion();
|
|
13981
14161
|
var program = new Command();
|
|
13982
14162
|
program.name("evident").description("Run OpenCode locally and connect it to Evident").version(version).option(
|
|
13983
14163
|
"--endpoint <url>",
|