@evident-ai/cli 3.4.1-dev.5d99ded → 3.4.1-dev.5e8a101
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/README.md +11 -0
- package/dist/index.js +499 -151
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,8 +11,8 @@ import chalk2 from "chalk";
|
|
|
11
11
|
|
|
12
12
|
// src/lib/config.ts
|
|
13
13
|
import Conf from "conf";
|
|
14
|
-
import { chmodSync, existsSync, statSync } from "fs";
|
|
15
|
-
import { dirname } from "path";
|
|
14
|
+
import { chmodSync, existsSync, statSync } from "node:fs";
|
|
15
|
+
import { dirname } from "node:path";
|
|
16
16
|
var PRODUCTION_API_URL = "https://api.production.evident.run/v1";
|
|
17
17
|
var PRODUCTION_TUNNEL_URL = "wss://tunnel.production.evident.run";
|
|
18
18
|
var defaults = {
|
|
@@ -1009,10 +1009,10 @@ async function status(options = {}) {
|
|
|
1009
1009
|
}
|
|
1010
1010
|
|
|
1011
1011
|
// src/lib/claude-usage.ts
|
|
1012
|
-
import { execFileSync } from "child_process";
|
|
1013
|
-
import { readFileSync } from "fs";
|
|
1014
|
-
import { homedir } from "os";
|
|
1015
|
-
import { join } from "path";
|
|
1012
|
+
import { execFileSync } from "node:child_process";
|
|
1013
|
+
import { readFileSync } from "node:fs";
|
|
1014
|
+
import { homedir } from "node:os";
|
|
1015
|
+
import { join } from "node:path";
|
|
1016
1016
|
var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
1017
1017
|
var CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
|
|
1018
1018
|
var CLAUDE_PROFILE_TIMEOUT_MS = 2e3;
|
|
@@ -1192,10 +1192,10 @@ async function claudeUsage() {
|
|
|
1192
1192
|
}
|
|
1193
1193
|
|
|
1194
1194
|
// src/commands/run.ts
|
|
1195
|
-
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
1196
|
-
import { homedir as
|
|
1197
|
-
import { isAbsolute as isAbsolute3, join as
|
|
1198
|
-
import
|
|
1195
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1196
|
+
import { homedir as homedir6 } from "node:os";
|
|
1197
|
+
import { isAbsolute as isAbsolute3, join as join10, parse, resolve as resolvePath2 } from "node:path";
|
|
1198
|
+
import chalk7 from "chalk";
|
|
1199
1199
|
|
|
1200
1200
|
// ../../packages/types/src/agents/index.ts
|
|
1201
1201
|
var MICROVM_MAX_LIFETIME_MS = 8 * 60 * 6e4;
|
|
@@ -1254,7 +1254,7 @@ function stripQuery(url) {
|
|
|
1254
1254
|
|
|
1255
1255
|
// src/commands/run.ts
|
|
1256
1256
|
import ora3 from "ora";
|
|
1257
|
-
import { select as
|
|
1257
|
+
import { select as select4 } from "@inquirer/prompts";
|
|
1258
1258
|
|
|
1259
1259
|
// src/lib/telemetry.ts
|
|
1260
1260
|
var CLI_VERSION = (true ? "3.0.0" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
@@ -1427,12 +1427,50 @@ var SEVERITY_BY_LEVEL = {
|
|
|
1427
1427
|
warn: "warning",
|
|
1428
1428
|
error: "error"
|
|
1429
1429
|
};
|
|
1430
|
+
function parseOpenCodeLogLine(line) {
|
|
1431
|
+
const normalisedLine = line.replace(/\r$/, "");
|
|
1432
|
+
const levelMatch = normalisedLine.match(/(?:^|\s)level=(\w+)/i);
|
|
1433
|
+
if (!levelMatch) return null;
|
|
1434
|
+
const level = levelMatch[1].toUpperCase();
|
|
1435
|
+
if (level !== "WARN" && level !== "ERROR") return null;
|
|
1436
|
+
const sessionMatch = normalisedLine.match(/(?:^|\s)sessionID=(\S+)/);
|
|
1437
|
+
return { level: level === "WARN" ? "warn" : "error", sessionID: sessionMatch?.[1] };
|
|
1438
|
+
}
|
|
1439
|
+
var MAX_LINE_BUFFER_BYTES = 16 * 1024;
|
|
1440
|
+
function createOpenCodeActivityForwarder(getContext) {
|
|
1441
|
+
let buffer = Buffer.alloc(0);
|
|
1442
|
+
const flushLine = (line) => {
|
|
1443
|
+
const parsed = parseOpenCodeLogLine(line);
|
|
1444
|
+
if (!parsed) return;
|
|
1445
|
+
forwardRunnerActivity(
|
|
1446
|
+
{
|
|
1447
|
+
level: parsed.level,
|
|
1448
|
+
error: line,
|
|
1449
|
+
metadata: parsed.sessionID ? { sessionID: parsed.sessionID } : void 0,
|
|
1450
|
+
source: "opencode"
|
|
1451
|
+
},
|
|
1452
|
+
getContext()
|
|
1453
|
+
);
|
|
1454
|
+
};
|
|
1455
|
+
return (chunk) => {
|
|
1456
|
+
buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8")]);
|
|
1457
|
+
let newlineIndex;
|
|
1458
|
+
while ((newlineIndex = buffer.indexOf(10)) !== -1) {
|
|
1459
|
+
flushLine(buffer.subarray(0, newlineIndex).toString("utf-8").replace(/\r$/, ""));
|
|
1460
|
+
buffer = buffer.subarray(newlineIndex + 1);
|
|
1461
|
+
}
|
|
1462
|
+
if (buffer.length > MAX_LINE_BUFFER_BYTES) {
|
|
1463
|
+
flushLine(buffer.toString("utf-8"));
|
|
1464
|
+
buffer = Buffer.alloc(0);
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1430
1468
|
var MAX_MESSAGE_LENGTH = 500;
|
|
1431
1469
|
var MAX_METADATA_VALUE_LENGTH = 200;
|
|
1432
1470
|
var MAX_METADATA_ENTRIES = 20;
|
|
1433
1471
|
var TRUNCATION_MARKER = "\u2026";
|
|
1434
1472
|
function redact(message) {
|
|
1435
|
-
return message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/https?:\/\/\S+/g, "<url>");
|
|
1473
|
+
return 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>");
|
|
1436
1474
|
}
|
|
1437
1475
|
function truncate(message) {
|
|
1438
1476
|
if (message.length <= MAX_MESSAGE_LENGTH) return message;
|
|
@@ -1458,43 +1496,47 @@ function sanitiseMetadata(metadata) {
|
|
|
1458
1496
|
}
|
|
1459
1497
|
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
1460
1498
|
var RATE_LIMIT_MAX_EVENTS = 30;
|
|
1461
|
-
var
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1499
|
+
var rateWindows = /* @__PURE__ */ new Map();
|
|
1500
|
+
function admitUnderRateLimit(source, now) {
|
|
1501
|
+
let window = rateWindows.get(source);
|
|
1502
|
+
if (!window) {
|
|
1503
|
+
window = { windowStartedAt: 0, windowCount: 0, windowDroppedCount: 0 };
|
|
1504
|
+
rateWindows.set(source, window);
|
|
1505
|
+
}
|
|
1506
|
+
if (now - window.windowStartedAt >= RATE_LIMIT_WINDOW_MS) {
|
|
1507
|
+
if (window.windowDroppedCount > 0) {
|
|
1467
1508
|
console.error(
|
|
1468
|
-
`[runner-activity-telemetry] rate cap reached: dropped ${windowDroppedCount} ${windowDroppedCount === 1 ? "entry" : "entries"} in the last ${RATE_LIMIT_WINDOW_MS / 1e3}s (cap ${RATE_LIMIT_MAX_EVENTS}/min)`
|
|
1509
|
+
`[runner-activity-telemetry] rate cap reached: dropped ${window.windowDroppedCount} ${window.windowDroppedCount === 1 ? "entry" : "entries"} in the last ${RATE_LIMIT_WINDOW_MS / 1e3}s (cap ${RATE_LIMIT_MAX_EVENTS}/min) for source "${source}"`
|
|
1469
1510
|
);
|
|
1470
1511
|
}
|
|
1471
|
-
windowStartedAt = now;
|
|
1472
|
-
windowCount = 0;
|
|
1473
|
-
windowDroppedCount = 0;
|
|
1512
|
+
window.windowStartedAt = now;
|
|
1513
|
+
window.windowCount = 0;
|
|
1514
|
+
window.windowDroppedCount = 0;
|
|
1474
1515
|
}
|
|
1475
|
-
if (windowCount >= RATE_LIMIT_MAX_EVENTS) {
|
|
1476
|
-
windowDroppedCount++;
|
|
1477
|
-
if (windowDroppedCount === 1) {
|
|
1516
|
+
if (window.windowCount >= RATE_LIMIT_MAX_EVENTS) {
|
|
1517
|
+
window.windowDroppedCount++;
|
|
1518
|
+
if (window.windowDroppedCount === 1) {
|
|
1478
1519
|
console.error(
|
|
1479
|
-
`[runner-activity-telemetry] rate cap reached (${RATE_LIMIT_MAX_EVENTS} per ${RATE_LIMIT_WINDOW_MS / 1e3}s) \u2014 dropping further entries this window`
|
|
1520
|
+
`[runner-activity-telemetry] rate cap reached (${RATE_LIMIT_MAX_EVENTS} per ${RATE_LIMIT_WINDOW_MS / 1e3}s) \u2014 dropping further entries this window for source "${source}"`
|
|
1480
1521
|
);
|
|
1481
1522
|
}
|
|
1482
1523
|
return false;
|
|
1483
1524
|
}
|
|
1484
|
-
windowCount++;
|
|
1525
|
+
window.windowCount++;
|
|
1485
1526
|
return true;
|
|
1486
1527
|
}
|
|
1487
1528
|
function forwardRunnerActivity(entry, context) {
|
|
1488
1529
|
try {
|
|
1489
1530
|
if (!FORWARDED_LEVELS.has(entry.level)) return;
|
|
1490
1531
|
if (!context.agentId || !context.authHeader) return;
|
|
1491
|
-
|
|
1532
|
+
const source = entry.source ?? "cli.run";
|
|
1533
|
+
if (!admitUnderRateLimit(source, Date.now())) return;
|
|
1492
1534
|
const rawMessage = entry.error ?? entry.message ?? "";
|
|
1493
1535
|
const message = truncate(redact(rawMessage));
|
|
1494
1536
|
logEvent(TelemetryEventTypes.RUNNER_ACTIVITY, {
|
|
1495
1537
|
severity: SEVERITY_BY_LEVEL[entry.level],
|
|
1496
1538
|
message,
|
|
1497
|
-
metadata: { ...sanitiseMetadata(entry.metadata), source
|
|
1539
|
+
metadata: { ...sanitiseMetadata(entry.metadata), source },
|
|
1498
1540
|
agentId: context.agentId
|
|
1499
1541
|
});
|
|
1500
1542
|
} catch (err) {
|
|
@@ -1505,8 +1547,8 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1505
1547
|
}
|
|
1506
1548
|
|
|
1507
1549
|
// src/lib/opencode/session-db-recovery-report.ts
|
|
1508
|
-
import { readFileSync as readFileSync2, unlinkSync } from "fs";
|
|
1509
|
-
import { join as join2 } from "path";
|
|
1550
|
+
import { readFileSync as readFileSync2, unlinkSync } from "node:fs";
|
|
1551
|
+
import { join as join2 } from "node:path";
|
|
1510
1552
|
function sessionDbRecoveryReportPath(homeDir, env) {
|
|
1511
1553
|
const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
|
|
1512
1554
|
return override || join2(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
|
|
@@ -1719,13 +1761,13 @@ async function waitForOpenCodeHealth(port, timeoutMs = 3e4) {
|
|
|
1719
1761
|
}
|
|
1720
1762
|
|
|
1721
1763
|
// src/lib/opencode/session-db-boot.ts
|
|
1722
|
-
import { spawn as spawn2 } from "child_process";
|
|
1723
|
-
import { mkdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
1724
|
-
import { homedir as homedir2 } from "os";
|
|
1725
|
-
import { dirname as dirname2, resolve as resolvePath } from "path";
|
|
1764
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1765
|
+
import { mkdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
|
|
1766
|
+
import { homedir as homedir2 } from "node:os";
|
|
1767
|
+
import { dirname as dirname2, resolve as resolvePath } from "node:path";
|
|
1726
1768
|
|
|
1727
1769
|
// src/lib/runner-synchroniser.ts
|
|
1728
|
-
import { spawn } from "child_process";
|
|
1770
|
+
import { spawn } from "node:child_process";
|
|
1729
1771
|
function appendError(stderr, error2) {
|
|
1730
1772
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
1731
1773
|
return stderr === "" ? message : `${stderr}
|
|
@@ -2250,9 +2292,9 @@ async function restoreAndVerifySessionDb(options) {
|
|
|
2250
2292
|
}
|
|
2251
2293
|
|
|
2252
2294
|
// src/lib/opencode/session-db-provenance.ts
|
|
2253
|
-
import { createRequire } from "module";
|
|
2254
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2255
|
-
import { dirname as dirname3, join as join3 } from "path";
|
|
2295
|
+
import { createRequire } from "node:module";
|
|
2296
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2297
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
2256
2298
|
var require2 = createRequire(import.meta.url);
|
|
2257
2299
|
function readSessionDbMigrationIds(dbPath) {
|
|
2258
2300
|
let db;
|
|
@@ -2446,6 +2488,17 @@ async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
|
|
|
2446
2488
|
|
|
2447
2489
|
// src/lib/opencode/process.ts
|
|
2448
2490
|
var OPENCODE_PORT_RANGE = [4096, 4097, 4098, 4099, 4100];
|
|
2491
|
+
var VALID_OPENCODE_LOG_LEVELS = /* @__PURE__ */ new Set(["DEBUG", "INFO", "WARN", "ERROR"]);
|
|
2492
|
+
function resolveOpenCodeLogLevel(env) {
|
|
2493
|
+
const raw = env.OPENCODE_LOG_LEVEL;
|
|
2494
|
+
if (!raw) return "INFO";
|
|
2495
|
+
const upper = raw.toUpperCase();
|
|
2496
|
+
if (VALID_OPENCODE_LOG_LEVELS.has(upper)) return upper;
|
|
2497
|
+
console.warn(
|
|
2498
|
+
`startOpenCode: ignoring invalid OPENCODE_LOG_LEVEL "${raw}" (expected DEBUG|INFO|WARN|ERROR) \u2014 using INFO`
|
|
2499
|
+
);
|
|
2500
|
+
return "INFO";
|
|
2501
|
+
}
|
|
2449
2502
|
function getProcessCwd(pid) {
|
|
2450
2503
|
const platform = process.platform;
|
|
2451
2504
|
try {
|
|
@@ -2494,14 +2547,14 @@ function findAvailablePort(startPort, maxAttempts = 10) {
|
|
|
2494
2547
|
}
|
|
2495
2548
|
return null;
|
|
2496
2549
|
}
|
|
2497
|
-
function
|
|
2550
|
+
function findProcessesByPattern(pgrepPattern, psPattern) {
|
|
2498
2551
|
const instances = [];
|
|
2499
2552
|
try {
|
|
2500
2553
|
const platform = process.platform;
|
|
2501
2554
|
if (platform === "darwin" || platform === "linux") {
|
|
2502
2555
|
let pids = [];
|
|
2503
2556
|
try {
|
|
2504
|
-
const pgrepOutput = execSync(
|
|
2557
|
+
const pgrepOutput = execSync(`pgrep -f "${pgrepPattern}"`, {
|
|
2505
2558
|
encoding: "utf-8",
|
|
2506
2559
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2507
2560
|
}).trim();
|
|
@@ -2510,7 +2563,7 @@ function findOpenCodeProcesses() {
|
|
|
2510
2563
|
}
|
|
2511
2564
|
} catch {
|
|
2512
2565
|
try {
|
|
2513
|
-
const psOutput = execSync(
|
|
2566
|
+
const psOutput = execSync(`ps aux | grep -E "${psPattern}" | grep -v grep`, {
|
|
2514
2567
|
encoding: "utf-8",
|
|
2515
2568
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2516
2569
|
}).trim();
|
|
@@ -2556,6 +2609,9 @@ function findOpenCodeProcesses() {
|
|
|
2556
2609
|
}
|
|
2557
2610
|
return instances;
|
|
2558
2611
|
}
|
|
2612
|
+
function findOpenCodeProcesses() {
|
|
2613
|
+
return findProcessesByPattern("opencode serve|opencode-serve", "opencode (serve|--port)");
|
|
2614
|
+
}
|
|
2559
2615
|
async function scanPortsForOpenCode() {
|
|
2560
2616
|
const instances = [];
|
|
2561
2617
|
const checks = OPENCODE_PORT_RANGE.map(async (port) => {
|
|
@@ -2602,7 +2658,7 @@ async function findHealthyOpenCodeInstances() {
|
|
|
2602
2658
|
}
|
|
2603
2659
|
async function startOpenCode(port, options = {}) {
|
|
2604
2660
|
let command = "opencode";
|
|
2605
|
-
const printLogs = options.inheritStdio ? ["--print-logs"] : [];
|
|
2661
|
+
const printLogs = options.inheritStdio ? ["--print-logs", "--log-level", resolveOpenCodeLogLevel(process.env)] : [];
|
|
2606
2662
|
let args = ["serve", "--port", port.toString(), "--hostname", "127.0.0.1", ...printLogs];
|
|
2607
2663
|
try {
|
|
2608
2664
|
execSync("which opencode", { stdio: "ignore" });
|
|
@@ -2659,6 +2715,19 @@ function isOpenCodeInstalled() {
|
|
|
2659
2715
|
return false;
|
|
2660
2716
|
}
|
|
2661
2717
|
}
|
|
2718
|
+
function isOpenCode2Installed() {
|
|
2719
|
+
try {
|
|
2720
|
+
const platform = process.platform;
|
|
2721
|
+
if (platform === "win32") {
|
|
2722
|
+
execSync2("where opencode2", { stdio: "ignore" });
|
|
2723
|
+
} else {
|
|
2724
|
+
execSync2("which opencode2", { stdio: "ignore" });
|
|
2725
|
+
}
|
|
2726
|
+
return true;
|
|
2727
|
+
} catch {
|
|
2728
|
+
return false;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2662
2731
|
async function promptOpenCodeInstall(interactive) {
|
|
2663
2732
|
if (!interactive) {
|
|
2664
2733
|
console.log(
|
|
@@ -2668,7 +2737,11 @@ async function promptOpenCodeInstall(interactive) {
|
|
|
2668
2737
|
install_url: OPENCODE_INSTALL_URL,
|
|
2669
2738
|
install_commands: {
|
|
2670
2739
|
npm: "npm install -g opencode-ai",
|
|
2671
|
-
curl: "curl -fsSL https://opencode.ai/install.sh | sh"
|
|
2740
|
+
curl: "curl -fsSL https://opencode.ai/install.sh | sh",
|
|
2741
|
+
v2: {
|
|
2742
|
+
npm: "npm install -g @opencode-ai/cli@beta",
|
|
2743
|
+
curl: "curl -fsSL https://opencode.ai/v2/install | bash"
|
|
2744
|
+
}
|
|
2672
2745
|
}
|
|
2673
2746
|
})
|
|
2674
2747
|
);
|
|
@@ -3522,8 +3595,8 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
3522
3595
|
}
|
|
3523
3596
|
|
|
3524
3597
|
// src/lib/opencode/session-db-size.ts
|
|
3525
|
-
import { statSync as statSync3 } from "fs";
|
|
3526
|
-
import { join as join4 } from "path";
|
|
3598
|
+
import { statSync as statSync3 } from "node:fs";
|
|
3599
|
+
import { join as join4 } from "node:path";
|
|
3527
3600
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
3528
3601
|
function statSessionDbBytes(homeDir) {
|
|
3529
3602
|
const dbPath = join4(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
@@ -3553,9 +3626,96 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
3553
3626
|
return null;
|
|
3554
3627
|
}
|
|
3555
3628
|
|
|
3629
|
+
// src/lib/opencode/log-tail.ts
|
|
3630
|
+
import { statSync as statSync4 } from "node:fs";
|
|
3631
|
+
import { homedir as homedir3 } from "node:os";
|
|
3632
|
+
import { join as join5 } from "node:path";
|
|
3633
|
+
import { open as open2, stat } from "node:fs/promises";
|
|
3634
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
3635
|
+
function resolveOpenCodeLogPath(homeDir = homedir3(), env = process.env) {
|
|
3636
|
+
const dataDir = env.XDG_DATA_HOME || join5(homeDir, ".local", "share");
|
|
3637
|
+
return join5(dataDir, "opencode", "log", "opencode.log");
|
|
3638
|
+
}
|
|
3639
|
+
function isEnoent(error2) {
|
|
3640
|
+
return error2?.code === "ENOENT";
|
|
3641
|
+
}
|
|
3642
|
+
function reportFailure(operation, logPath, error2) {
|
|
3643
|
+
console.error(
|
|
3644
|
+
`[opencode-log-tail] ${operation} failed for ${logPath}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
3645
|
+
);
|
|
3646
|
+
}
|
|
3647
|
+
function tailOpenCodeLogFile(logPath, onChunk, opts = {}) {
|
|
3648
|
+
const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3649
|
+
let offset = 0;
|
|
3650
|
+
let inode = null;
|
|
3651
|
+
let baselineReady = true;
|
|
3652
|
+
try {
|
|
3653
|
+
const initial = statSync4(logPath);
|
|
3654
|
+
offset = initial.size;
|
|
3655
|
+
inode = initial.ino;
|
|
3656
|
+
} catch (error2) {
|
|
3657
|
+
if (!isEnoent(error2)) {
|
|
3658
|
+
reportFailure("initial stat", logPath, error2);
|
|
3659
|
+
baselineReady = false;
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
let polling = false;
|
|
3663
|
+
let stopped = false;
|
|
3664
|
+
const poll = async () => {
|
|
3665
|
+
if (polling || stopped) return;
|
|
3666
|
+
polling = true;
|
|
3667
|
+
try {
|
|
3668
|
+
let current;
|
|
3669
|
+
try {
|
|
3670
|
+
current = await stat(logPath);
|
|
3671
|
+
} catch (error2) {
|
|
3672
|
+
if (!isEnoent(error2)) reportFailure("stat", logPath, error2);
|
|
3673
|
+
return;
|
|
3674
|
+
}
|
|
3675
|
+
if (!baselineReady) {
|
|
3676
|
+
offset = current.size;
|
|
3677
|
+
inode = current.ino;
|
|
3678
|
+
baselineReady = true;
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
if (inode !== null && current.ino !== inode || current.size < offset) {
|
|
3682
|
+
offset = 0;
|
|
3683
|
+
}
|
|
3684
|
+
inode = current.ino;
|
|
3685
|
+
if (current.size === offset) return;
|
|
3686
|
+
const length = current.size - offset;
|
|
3687
|
+
const fh = await open2(logPath, "r");
|
|
3688
|
+
try {
|
|
3689
|
+
const buf = Buffer.alloc(length);
|
|
3690
|
+
const { bytesRead } = await fh.read(buf, 0, length, offset);
|
|
3691
|
+
offset += bytesRead;
|
|
3692
|
+
if (bytesRead > 0) onChunk(buf.subarray(0, bytesRead));
|
|
3693
|
+
} finally {
|
|
3694
|
+
await fh.close();
|
|
3695
|
+
}
|
|
3696
|
+
} catch (error2) {
|
|
3697
|
+
if (!isEnoent(error2)) reportFailure("poll", logPath, error2);
|
|
3698
|
+
} finally {
|
|
3699
|
+
polling = false;
|
|
3700
|
+
}
|
|
3701
|
+
};
|
|
3702
|
+
const interval = setInterval(() => void poll(), pollIntervalMs);
|
|
3703
|
+
void poll();
|
|
3704
|
+
return {
|
|
3705
|
+
stop: () => {
|
|
3706
|
+
stopped = true;
|
|
3707
|
+
clearInterval(interval);
|
|
3708
|
+
}
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3556
3712
|
// src/lib/opencode/session-db-reclaim.ts
|
|
3557
|
-
import { statSync as
|
|
3558
|
-
import { dirname as dirname4 } from "path";
|
|
3713
|
+
import { statSync as statSync5, statfsSync } from "node:fs";
|
|
3714
|
+
import { dirname as dirname4 } from "node:path";
|
|
3715
|
+
function errorMessage(error2) {
|
|
3716
|
+
if (!(error2 instanceof Error)) return String(error2);
|
|
3717
|
+
return error2.cause instanceof Error ? error2.cause.message : error2.message;
|
|
3718
|
+
}
|
|
3559
3719
|
function insufficientSpaceReason(dbPath, requiredBytes) {
|
|
3560
3720
|
try {
|
|
3561
3721
|
const fsStats = statfsSync(dirname4(dbPath));
|
|
@@ -3581,17 +3741,17 @@ async function probeReclaimAvailability(input) {
|
|
|
3581
3741
|
const { dbPath, requiredBytes } = input;
|
|
3582
3742
|
let sqlite;
|
|
3583
3743
|
try {
|
|
3584
|
-
sqlite = await import("sqlite");
|
|
3744
|
+
sqlite = await import("node:sqlite");
|
|
3585
3745
|
} catch (err) {
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
return "sqlite-unavailable";
|
|
3746
|
+
const detail = `Node ${process.version}: ${errorMessage(err)}`;
|
|
3747
|
+
console.warn(`[probeReclaimAvailability] node:sqlite unavailable for ${dbPath}: ` + detail);
|
|
3748
|
+
return { reason: "sqlite-unavailable", detail };
|
|
3590
3749
|
}
|
|
3591
3750
|
let autoVacuum = null;
|
|
3592
3751
|
try {
|
|
3593
3752
|
const db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
|
|
3594
3753
|
try {
|
|
3754
|
+
db.exec("PRAGMA busy_timeout=5000");
|
|
3595
3755
|
autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
|
|
3596
3756
|
} finally {
|
|
3597
3757
|
db.close();
|
|
@@ -3602,23 +3762,25 @@ async function probeReclaimAvailability(input) {
|
|
|
3602
3762
|
);
|
|
3603
3763
|
}
|
|
3604
3764
|
if (autoVacuum !== 0) return null;
|
|
3605
|
-
return insufficientSpaceReason(dbPath, requiredBytes) !== null ? "insufficient-disk-space" : null;
|
|
3765
|
+
return insufficientSpaceReason(dbPath, requiredBytes) !== null ? { reason: "insufficient-disk-space" } : null;
|
|
3606
3766
|
}
|
|
3607
3767
|
async function reclaimSessionDbSpace(input) {
|
|
3608
3768
|
const { dbPath, maxPages, allowFullVacuum = true } = input;
|
|
3609
3769
|
let sqlite;
|
|
3610
3770
|
try {
|
|
3611
|
-
sqlite = await import("sqlite");
|
|
3771
|
+
sqlite = await import("node:sqlite");
|
|
3612
3772
|
} catch (err) {
|
|
3773
|
+
const detail = `Node ${process.version}: ${errorMessage(err)}`;
|
|
3613
3774
|
console.warn(
|
|
3614
|
-
`[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${
|
|
3775
|
+
`[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${detail}`
|
|
3615
3776
|
);
|
|
3616
|
-
return { ok: false, skipped: "sqlite-unavailable" };
|
|
3777
|
+
return { ok: false, skipped: "sqlite-unavailable", detail };
|
|
3617
3778
|
}
|
|
3618
3779
|
const { DatabaseSync } = sqlite;
|
|
3619
3780
|
let db;
|
|
3620
3781
|
try {
|
|
3621
3782
|
db = new DatabaseSync(dbPath);
|
|
3783
|
+
db.exec("PRAGMA busy_timeout=5000");
|
|
3622
3784
|
const autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
|
|
3623
3785
|
if (autoVacuum === 0) {
|
|
3624
3786
|
if (!allowFullVacuum) {
|
|
@@ -3627,7 +3789,7 @@ async function reclaimSessionDbSpace(input) {
|
|
|
3627
3789
|
);
|
|
3628
3790
|
return { ok: false, skipped: "full-vacuum-blocked" };
|
|
3629
3791
|
}
|
|
3630
|
-
const fileBytesForGuard =
|
|
3792
|
+
const fileBytesForGuard = statSync5(dbPath).size;
|
|
3631
3793
|
const skipReason = insufficientSpaceReason(dbPath, fileBytesForGuard);
|
|
3632
3794
|
if (skipReason !== null) {
|
|
3633
3795
|
console.warn(
|
|
@@ -3655,10 +3817,12 @@ async function reclaimSessionDbSpace(input) {
|
|
|
3655
3817
|
);
|
|
3656
3818
|
return { ok: false, skipped: "auto-vacuum-not-applicable" };
|
|
3657
3819
|
} catch (err) {
|
|
3658
|
-
console.error(
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3820
|
+
console.error(`[reclaimSessionDbSpace] reclaim failed for ${dbPath}: ` + errorMessage(err));
|
|
3821
|
+
return {
|
|
3822
|
+
ok: false,
|
|
3823
|
+
skipped: "reclaim-error",
|
|
3824
|
+
detail: errorMessage(err)
|
|
3825
|
+
};
|
|
3662
3826
|
} finally {
|
|
3663
3827
|
db?.close();
|
|
3664
3828
|
}
|
|
@@ -3950,8 +4114,8 @@ function connectTunnel(options) {
|
|
|
3950
4114
|
try {
|
|
3951
4115
|
message = JSON.parse(data.toString());
|
|
3952
4116
|
} catch (error2) {
|
|
3953
|
-
const
|
|
3954
|
-
onError?.(`Failed to handle message: ${
|
|
4117
|
+
const errorMessage3 = error2 instanceof Error ? error2.message : "Unknown error";
|
|
4118
|
+
onError?.(`Failed to handle message: ${errorMessage3}`);
|
|
3955
4119
|
return;
|
|
3956
4120
|
}
|
|
3957
4121
|
if (isStreamFrame(message)) {
|
|
@@ -4095,7 +4259,7 @@ var RunnerConnection = class {
|
|
|
4095
4259
|
};
|
|
4096
4260
|
|
|
4097
4261
|
// src/lib/tunnel/ready-marker.ts
|
|
4098
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
4262
|
+
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
4099
4263
|
function writeTunnelReadyMarker(path, agentId) {
|
|
4100
4264
|
try {
|
|
4101
4265
|
writeFileSync3(path, `${agentId}
|
|
@@ -4107,7 +4271,7 @@ function writeTunnelReadyMarker(path, agentId) {
|
|
|
4107
4271
|
}
|
|
4108
4272
|
|
|
4109
4273
|
// src/lib/replication.ts
|
|
4110
|
-
import { spawn as spawn4 } from "child_process";
|
|
4274
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
4111
4275
|
function startSessionDbReplication(configPath) {
|
|
4112
4276
|
return spawn4("litestream", ["replicate", "-config", configPath], {
|
|
4113
4277
|
stdio: "inherit"
|
|
@@ -4123,7 +4287,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
|
|
|
4123
4287
|
}
|
|
4124
4288
|
|
|
4125
4289
|
// src/lib/process-liveness.ts
|
|
4126
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
4290
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
4127
4291
|
function isProcessAlive(pid) {
|
|
4128
4292
|
try {
|
|
4129
4293
|
process.kill(pid, 0);
|
|
@@ -4149,9 +4313,9 @@ function isProcessAlive(pid) {
|
|
|
4149
4313
|
}
|
|
4150
4314
|
|
|
4151
4315
|
// src/lib/openai-usage.ts
|
|
4152
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
4153
|
-
import { homedir as
|
|
4154
|
-
import { join as
|
|
4316
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
4317
|
+
import { homedir as homedir4 } from "node:os";
|
|
4318
|
+
import { join as join6 } from "node:path";
|
|
4155
4319
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
4156
4320
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
4157
4321
|
var OpenAiUsageError = class extends Error {
|
|
@@ -4165,7 +4329,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
4165
4329
|
}
|
|
4166
4330
|
function readOpenCodeChatGptCredentials() {
|
|
4167
4331
|
try {
|
|
4168
|
-
const raw = readFileSync5(
|
|
4332
|
+
const raw = readFileSync5(join6(homedir4(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
4169
4333
|
let parsed;
|
|
4170
4334
|
try {
|
|
4171
4335
|
parsed = JSON.parse(raw);
|
|
@@ -4427,8 +4591,8 @@ function resolveResourceUsageReportingEnabled(flagValue, env) {
|
|
|
4427
4591
|
}
|
|
4428
4592
|
|
|
4429
4593
|
// src/lib/resource-usage.ts
|
|
4430
|
-
import { cpus, totalmem, freemem } from "os";
|
|
4431
|
-
import { statfsSync as statfsSync2 } from "fs";
|
|
4594
|
+
import { cpus, totalmem, freemem } from "node:os";
|
|
4595
|
+
import { statfsSync as statfsSync2 } from "node:fs";
|
|
4432
4596
|
|
|
4433
4597
|
// src/lib/ecs-task-metadata.ts
|
|
4434
4598
|
var ECS_METADATA_TIMEOUT_MS = 2e3;
|
|
@@ -4595,15 +4759,15 @@ function createResourceUsageCollector(homeDir) {
|
|
|
4595
4759
|
}
|
|
4596
4760
|
|
|
4597
4761
|
// src/lib/channels/driver.ts
|
|
4598
|
-
import { homedir as
|
|
4762
|
+
import { homedir as homedir5 } from "node:os";
|
|
4599
4763
|
|
|
4600
4764
|
// src/lib/runner-file-sync.ts
|
|
4601
|
-
import { join as
|
|
4765
|
+
import { join as join8 } from "node:path";
|
|
4602
4766
|
|
|
4603
4767
|
// src/lib/file-push.ts
|
|
4604
|
-
import { randomUUID } from "crypto";
|
|
4605
|
-
import { chmod, mkdir, open as
|
|
4606
|
-
import { basename, dirname as dirname5, isAbsolute, join as
|
|
4768
|
+
import { randomUUID } from "node:crypto";
|
|
4769
|
+
import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
|
|
4770
|
+
import { basename, dirname as dirname5, isAbsolute, join as join7, relative, resolve as resolve2, sep } from "node:path";
|
|
4607
4771
|
var FILE_MODE = 384;
|
|
4608
4772
|
var DIRECTORY_MODE = 448;
|
|
4609
4773
|
async function writePushedFile(request) {
|
|
@@ -4636,7 +4800,7 @@ async function writePushedFile(request) {
|
|
|
4636
4800
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
4637
4801
|
dirname5(candidate)
|
|
4638
4802
|
);
|
|
4639
|
-
const realTarget =
|
|
4803
|
+
const realTarget = join7(existingAncestor, ...missingSegments, basename(candidate));
|
|
4640
4804
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
4641
4805
|
if (allowedDirectory === null) {
|
|
4642
4806
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -4672,7 +4836,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
4672
4836
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
4673
4837
|
return null;
|
|
4674
4838
|
}
|
|
4675
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4839
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
4676
4840
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
4677
4841
|
return null;
|
|
4678
4842
|
}
|
|
@@ -4745,16 +4909,16 @@ function contains(realDirectory, realTarget) {
|
|
|
4745
4909
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
4746
4910
|
let current = existingAncestor;
|
|
4747
4911
|
for (const segment of missingSegments) {
|
|
4748
|
-
current =
|
|
4912
|
+
current = join7(current, segment);
|
|
4749
4913
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
4750
4914
|
await chmod(current, DIRECTORY_MODE);
|
|
4751
4915
|
}
|
|
4752
4916
|
}
|
|
4753
4917
|
async function writeAtomically(realTarget, content) {
|
|
4754
|
-
const temporaryPath =
|
|
4918
|
+
const temporaryPath = join7(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
4755
4919
|
let handle;
|
|
4756
4920
|
try {
|
|
4757
|
-
handle = await
|
|
4921
|
+
handle = await open3(temporaryPath, "wx", FILE_MODE);
|
|
4758
4922
|
await handle.writeFile(content);
|
|
4759
4923
|
await handle.chmod(FILE_MODE);
|
|
4760
4924
|
await handle.close();
|
|
@@ -4881,12 +5045,12 @@ var NOT_APPLIED = {
|
|
|
4881
5045
|
opencodeAuthApplied: false
|
|
4882
5046
|
};
|
|
4883
5047
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
4884
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4885
|
-
return expanded ===
|
|
5048
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
5049
|
+
return expanded === join8(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
4886
5050
|
}
|
|
4887
5051
|
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
4888
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4889
|
-
return expanded ===
|
|
5052
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
5053
|
+
return expanded === join8(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
4890
5054
|
}
|
|
4891
5055
|
async function applyOne(options, file) {
|
|
4892
5056
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -5433,7 +5597,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5433
5597
|
this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
|
|
5434
5598
|
this.now = config.now ?? (() => Date.now());
|
|
5435
5599
|
this.fileSyncDirectories = config.fileSyncDirectories ?? [];
|
|
5436
|
-
this.homeDir = config.homeDir ??
|
|
5600
|
+
this.homeDir = config.homeDir ?? homedir5();
|
|
5437
5601
|
this.maxActiveSessions = config.maxActiveSessions;
|
|
5438
5602
|
this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
|
|
5439
5603
|
this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
|
|
@@ -5795,7 +5959,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5795
5959
|
this.signalDispatchNotStarted(conv, message, "session_existence_unknown");
|
|
5796
5960
|
break;
|
|
5797
5961
|
}
|
|
5798
|
-
const
|
|
5962
|
+
const errorMessage3 = err instanceof Error ? err.message : String(err);
|
|
5799
5963
|
this.sessions.delete(conv.id);
|
|
5800
5964
|
this.supersede(conv.id, sessionId);
|
|
5801
5965
|
this.log({
|
|
@@ -5804,7 +5968,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5804
5968
|
conversation_id: conv.id,
|
|
5805
5969
|
message_id: message.id
|
|
5806
5970
|
});
|
|
5807
|
-
await this.markFailed(conv.id, message.id, null,
|
|
5971
|
+
await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
|
|
5808
5972
|
this.log({
|
|
5809
5973
|
level: "warn",
|
|
5810
5974
|
message: `markFailed PATCH for message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
@@ -5815,7 +5979,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5815
5979
|
});
|
|
5816
5980
|
this.log({
|
|
5817
5981
|
level: "error",
|
|
5818
|
-
message: `Message ${message.id.slice(0, 8)} dispatch failed: ${
|
|
5982
|
+
message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage3}`,
|
|
5819
5983
|
conversation_id: conv.id,
|
|
5820
5984
|
message_id: message.id
|
|
5821
5985
|
});
|
|
@@ -5836,14 +6000,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5836
6000
|
this.unconfirmedDispatchFailures.delete(message.id);
|
|
5837
6001
|
this.sessions.delete(conv.id);
|
|
5838
6002
|
this.supersede(conv.id, sessionId);
|
|
5839
|
-
const
|
|
6003
|
+
const errorMessage3 = `OpenCode accepted this message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
|
|
5840
6004
|
this.log({
|
|
5841
6005
|
level: "error",
|
|
5842
|
-
message:
|
|
6006
|
+
message: errorMessage3,
|
|
5843
6007
|
conversation_id: conv.id,
|
|
5844
6008
|
message_id: message.id
|
|
5845
6009
|
});
|
|
5846
|
-
await this.markFailed(conv.id, message.id, null,
|
|
6010
|
+
await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
|
|
5847
6011
|
this.log({
|
|
5848
6012
|
level: "warn",
|
|
5849
6013
|
message: `markFailed PATCH for message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
@@ -7826,14 +7990,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7826
7990
|
this.unconfirmedDispatchFailures.delete(row.id);
|
|
7827
7991
|
this.sessions.delete(readoptConv.id);
|
|
7828
7992
|
this.supersede(readoptConv.id, sessionId);
|
|
7829
|
-
const
|
|
7993
|
+
const errorMessage3 = `OpenCode accepted this re-dispatched message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
|
|
7830
7994
|
this.log({
|
|
7831
7995
|
level: "error",
|
|
7832
|
-
message:
|
|
7996
|
+
message: errorMessage3,
|
|
7833
7997
|
conversation_id: row.conversation_id,
|
|
7834
7998
|
message_id: row.id
|
|
7835
7999
|
});
|
|
7836
|
-
await this.markFailed(row.conversation_id, row.id, null,
|
|
8000
|
+
await this.markFailed(row.conversation_id, row.id, null, errorMessage3).catch((markErr) => {
|
|
7837
8001
|
this.log({
|
|
7838
8002
|
level: "warn",
|
|
7839
8003
|
message: `markFailed PATCH for message ${row.id.slice(0, 8)} (conversation ${row.conversation_id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
@@ -8942,6 +9106,13 @@ import chalk5 from "chalk";
|
|
|
8942
9106
|
import ora2 from "ora";
|
|
8943
9107
|
import { select as select2 } from "@inquirer/prompts";
|
|
8944
9108
|
var INTERACTIVE_START_TIMEOUT_MS = 3e4;
|
|
9109
|
+
function checkNonInteractivePortConflict(port, isPortInUseFn) {
|
|
9110
|
+
if (isPortInUseFn(port)) {
|
|
9111
|
+
throw new Error(
|
|
9112
|
+
`Port ${port} is already in use by a non-OpenCode process. Free it or pass --port.`
|
|
9113
|
+
);
|
|
9114
|
+
}
|
|
9115
|
+
}
|
|
8945
9116
|
async function ensureOpenCodeRunning(ctx) {
|
|
8946
9117
|
const healthCheck = await checkOpenCodeHealth(ctx.port);
|
|
8947
9118
|
if (healthCheck.healthy) {
|
|
@@ -8989,6 +9160,7 @@ async function ensureOpenCodeRunning(ctx) {
|
|
|
8989
9160
|
}
|
|
8990
9161
|
}
|
|
8991
9162
|
if (!ctx.interactive) {
|
|
9163
|
+
checkNonInteractivePortConflict(ctx.port, isPortInUse);
|
|
8992
9164
|
ctx.log(`OpenCode is not running on port ${ctx.port}. Starting it automatically...`);
|
|
8993
9165
|
const proc = await startOpenCode(ctx.port, { inheritStdio: ctx.inheritStdio });
|
|
8994
9166
|
const health = await waitForOpenCodeHealth(ctx.port, ctx.startTimeoutMs);
|
|
@@ -9070,9 +9242,119 @@ Port ${port} is already in use.`));
|
|
|
9070
9242
|
return { port, process: null, version: null, notReadyReason: "you chose to continue without it" };
|
|
9071
9243
|
}
|
|
9072
9244
|
|
|
9245
|
+
// src/commands/ensure-opencode-v2.ts
|
|
9246
|
+
import chalk6 from "chalk";
|
|
9247
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
9248
|
+
async function probeOpenCode2WithoutPassword(port) {
|
|
9249
|
+
try {
|
|
9250
|
+
const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
|
|
9251
|
+
signal: AbortSignal.timeout(2e3)
|
|
9252
|
+
});
|
|
9253
|
+
if (response.status === 401) {
|
|
9254
|
+
return { healthy: false, authFailed: true, error: "HTTP 401" };
|
|
9255
|
+
}
|
|
9256
|
+
if (!response.ok) {
|
|
9257
|
+
return { healthy: false, error: `HTTP ${response.status}` };
|
|
9258
|
+
}
|
|
9259
|
+
return { healthy: true };
|
|
9260
|
+
} catch (error2) {
|
|
9261
|
+
return {
|
|
9262
|
+
healthy: false,
|
|
9263
|
+
error: error2 instanceof Error ? error2.message : "Unknown error"
|
|
9264
|
+
};
|
|
9265
|
+
}
|
|
9266
|
+
}
|
|
9267
|
+
function unknownPasswordError(port) {
|
|
9268
|
+
return new Error(
|
|
9269
|
+
`OpenCode V2 (opencode2) is already running on port ${port} with a password this runner doesn't know. Free the port or pass --port.`
|
|
9270
|
+
);
|
|
9271
|
+
}
|
|
9272
|
+
function v2SessionSupportIncompleteError() {
|
|
9273
|
+
return new Error(
|
|
9274
|
+
"OpenCode V2 (opencode2) session support is incomplete \u2014 see https://github.com/sroze/evident/issues/2075. Not starting a new opencode2 process."
|
|
9275
|
+
);
|
|
9276
|
+
}
|
|
9277
|
+
async function ensureOpenCode2Running(ctx) {
|
|
9278
|
+
const initialHealth = await probeOpenCode2WithoutPassword(ctx.port);
|
|
9279
|
+
if (initialHealth.authFailed) {
|
|
9280
|
+
throw unknownPasswordError(ctx.port);
|
|
9281
|
+
}
|
|
9282
|
+
if (initialHealth.healthy) {
|
|
9283
|
+
return {
|
|
9284
|
+
port: ctx.port,
|
|
9285
|
+
process: null,
|
|
9286
|
+
version: null,
|
|
9287
|
+
notReadyReason: null,
|
|
9288
|
+
password: null
|
|
9289
|
+
};
|
|
9290
|
+
}
|
|
9291
|
+
if (!isOpenCode2Installed()) {
|
|
9292
|
+
throw new Error(
|
|
9293
|
+
"OpenCode V2 (opencode2) is not installed. Install it with: npm install -g @opencode-ai/cli@beta"
|
|
9294
|
+
);
|
|
9295
|
+
}
|
|
9296
|
+
let port = ctx.port;
|
|
9297
|
+
if (!ctx.interactive) {
|
|
9298
|
+
checkNonInteractivePortConflict(port, isPortInUse);
|
|
9299
|
+
} else if (isPortInUse(port)) {
|
|
9300
|
+
console.log(chalk6.yellow(`
|
|
9301
|
+
Port ${port} is already in use.`));
|
|
9302
|
+
const alternativePort = findAvailablePort(port + 1);
|
|
9303
|
+
if (alternativePort) {
|
|
9304
|
+
const useAlternative = await select3({
|
|
9305
|
+
message: `Use port ${alternativePort} instead?`,
|
|
9306
|
+
choices: [
|
|
9307
|
+
{ name: `Yes, use port ${alternativePort}`, value: "yes" },
|
|
9308
|
+
{ name: "No, I will free the port manually", value: "no" }
|
|
9309
|
+
]
|
|
9310
|
+
});
|
|
9311
|
+
if (useAlternative === "yes") {
|
|
9312
|
+
port = alternativePort;
|
|
9313
|
+
} else {
|
|
9314
|
+
throw new Error(`Port ${ctx.port} is in use`);
|
|
9315
|
+
}
|
|
9316
|
+
}
|
|
9317
|
+
}
|
|
9318
|
+
if (!ctx.interactive) {
|
|
9319
|
+
throw v2SessionSupportIncompleteError();
|
|
9320
|
+
}
|
|
9321
|
+
console.log(chalk6.yellow(`
|
|
9322
|
+
${v2SessionSupportIncompleteError().message}`));
|
|
9323
|
+
const action = await select3({
|
|
9324
|
+
message: "OpenCode V2 is not running. What would you like to do?",
|
|
9325
|
+
choices: [
|
|
9326
|
+
{
|
|
9327
|
+
name: "Show me the command",
|
|
9328
|
+
value: "manual",
|
|
9329
|
+
description: "Display the command to run manually"
|
|
9330
|
+
},
|
|
9331
|
+
{
|
|
9332
|
+
name: "Continue without OpenCode V2",
|
|
9333
|
+
value: "continue",
|
|
9334
|
+
description: "Requests will fail until OpenCode V2 starts"
|
|
9335
|
+
}
|
|
9336
|
+
]
|
|
9337
|
+
});
|
|
9338
|
+
if (action === "manual") {
|
|
9339
|
+
blank();
|
|
9340
|
+
console.log(chalk6.bold("Run this command in another terminal:"));
|
|
9341
|
+
blank();
|
|
9342
|
+
console.log(` ${chalk6.cyan(`opencode2 serve --port ${port}`)}`);
|
|
9343
|
+
blank();
|
|
9344
|
+
throw new Error("Please start OpenCode V2 manually");
|
|
9345
|
+
}
|
|
9346
|
+
return {
|
|
9347
|
+
port,
|
|
9348
|
+
process: null,
|
|
9349
|
+
version: null,
|
|
9350
|
+
notReadyReason: "you chose to continue without OpenCode V2",
|
|
9351
|
+
password: null
|
|
9352
|
+
};
|
|
9353
|
+
}
|
|
9354
|
+
|
|
9073
9355
|
// src/lib/runner-credentials.ts
|
|
9074
|
-
import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
9075
|
-
import { spawn as spawn5 } from "child_process";
|
|
9356
|
+
import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9357
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
9076
9358
|
var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
|
|
9077
9359
|
var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
|
|
9078
9360
|
var GITHUB_PROBE_TIMEOUT_MS = 1e4;
|
|
@@ -9344,11 +9626,11 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
9344
9626
|
}
|
|
9345
9627
|
|
|
9346
9628
|
// src/lib/opencode/config-overlay.ts
|
|
9347
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
9348
|
-
import { copyFileSync, existsSync as existsSync2, statSync as
|
|
9349
|
-
import { isAbsolute as isAbsolute2, join as
|
|
9629
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
9630
|
+
import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
|
|
9631
|
+
import { isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
|
|
9350
9632
|
function isFile(filePath) {
|
|
9351
|
-
return existsSync2(filePath) &&
|
|
9633
|
+
return existsSync2(filePath) && statSync6(filePath).isFile();
|
|
9352
9634
|
}
|
|
9353
9635
|
function applyRunnerOpenCodeConfig({
|
|
9354
9636
|
overlayPath,
|
|
@@ -9360,7 +9642,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
9360
9642
|
return;
|
|
9361
9643
|
}
|
|
9362
9644
|
const source = isAbsolute2(overlayPath) ? overlayPath : resolve3(cwd, overlayPath);
|
|
9363
|
-
const target = isFile(
|
|
9645
|
+
const target = isFile(join9(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
|
|
9364
9646
|
if (!isFile(source)) {
|
|
9365
9647
|
log3(
|
|
9366
9648
|
`RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
|
|
@@ -9368,7 +9650,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
9368
9650
|
);
|
|
9369
9651
|
return;
|
|
9370
9652
|
}
|
|
9371
|
-
copyFileSync(source,
|
|
9653
|
+
copyFileSync(source, join9(cwd, target));
|
|
9372
9654
|
try {
|
|
9373
9655
|
execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
|
|
9374
9656
|
stdio: "ignore"
|
|
@@ -9377,11 +9659,11 @@ function applyRunnerOpenCodeConfig({
|
|
|
9377
9659
|
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
9378
9660
|
log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
|
|
9379
9661
|
}
|
|
9380
|
-
log3(`Applied runner OpenCode config ${source} to ${
|
|
9662
|
+
log3(`Applied runner OpenCode config ${source} to ${join9(cwd, target)}`);
|
|
9381
9663
|
}
|
|
9382
9664
|
|
|
9383
9665
|
// src/lib/credential-sync.ts
|
|
9384
|
-
import { renameSync, writeFileSync as writeFileSync5 } from "fs";
|
|
9666
|
+
import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9385
9667
|
var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
|
|
9386
9668
|
var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
|
|
9387
9669
|
var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
|
|
@@ -9391,7 +9673,7 @@ var MAX_FLUSH_PASSES = 2;
|
|
|
9391
9673
|
function outcomesWith(outcome) {
|
|
9392
9674
|
return { claude: outcome, opencode: outcome };
|
|
9393
9675
|
}
|
|
9394
|
-
function
|
|
9676
|
+
function errorMessage2(error2) {
|
|
9395
9677
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
9396
9678
|
}
|
|
9397
9679
|
function waitForSettlement(promise, timeoutMs) {
|
|
@@ -9418,7 +9700,7 @@ function writeMarker(markerPath, outcomes, log3) {
|
|
|
9418
9700
|
writeFileSync5(temporaryPath, body, { mode: 384 });
|
|
9419
9701
|
renameSync(temporaryPath, markerPath);
|
|
9420
9702
|
} catch (error2) {
|
|
9421
|
-
log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${
|
|
9703
|
+
log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
|
|
9422
9704
|
}
|
|
9423
9705
|
}
|
|
9424
9706
|
function intervalSeconds(env, log3) {
|
|
@@ -9450,7 +9732,7 @@ async function runFlushStore(store, env, synchroniserRunner, log3, deadlineAt) {
|
|
|
9450
9732
|
},
|
|
9451
9733
|
(error2) => {
|
|
9452
9734
|
failed = true;
|
|
9453
|
-
log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${
|
|
9735
|
+
log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage2(error2)}`, "warn");
|
|
9454
9736
|
}
|
|
9455
9737
|
);
|
|
9456
9738
|
const abortTimer = setTimeout(() => controller.abort(), remainingMs);
|
|
@@ -9510,7 +9792,7 @@ function createCredentialSync({
|
|
|
9510
9792
|
outcomes[store] = !result.timedOut && result.code === 0 ? "ok" : "failed";
|
|
9511
9793
|
} catch (error2) {
|
|
9512
9794
|
outcomes[store] = "failed";
|
|
9513
|
-
log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${
|
|
9795
|
+
log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage2(error2)}`, "debug");
|
|
9514
9796
|
}
|
|
9515
9797
|
}
|
|
9516
9798
|
const failed = STORES.some((store) => outcomes[store] === "failed");
|
|
@@ -9652,7 +9934,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
9652
9934
|
if (trimmed === "") {
|
|
9653
9935
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
9654
9936
|
}
|
|
9655
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
9937
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join10(homeDir, trimmed.slice(2)) : trimmed;
|
|
9656
9938
|
if (!isAbsolute3(expanded)) {
|
|
9657
9939
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
9658
9940
|
}
|
|
@@ -9676,6 +9958,28 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
9676
9958
|
var DEFAULT_OPENCODE_START_TIMEOUT_SECONDS = 180;
|
|
9677
9959
|
var MAX_OPENCODE_START_TIMEOUT_SECONDS = 3600;
|
|
9678
9960
|
var OPENCODE_START_TIMEOUT_ENV = "EVIDENT_OPENCODE_START_TIMEOUT";
|
|
9961
|
+
var OPENCODE_VERSION_ENV = "EVIDENT_OPENCODE_VERSION";
|
|
9962
|
+
function resolveOpenCodeVersion(options, env = process.env) {
|
|
9963
|
+
let raw;
|
|
9964
|
+
let source;
|
|
9965
|
+
if (options.opencodeVersion !== void 0) {
|
|
9966
|
+
raw = options.opencodeVersion;
|
|
9967
|
+
source = "--opencode-version";
|
|
9968
|
+
} else if (env[OPENCODE_VERSION_ENV] !== void 0 && env[OPENCODE_VERSION_ENV] !== "") {
|
|
9969
|
+
raw = env[OPENCODE_VERSION_ENV];
|
|
9970
|
+
source = OPENCODE_VERSION_ENV;
|
|
9971
|
+
} else {
|
|
9972
|
+
return { version: "v1", warnings: [] };
|
|
9973
|
+
}
|
|
9974
|
+
const normalized = raw.trim().toLowerCase();
|
|
9975
|
+
if (normalized !== "v1" && normalized !== "v2") {
|
|
9976
|
+
return {
|
|
9977
|
+
version: "v1",
|
|
9978
|
+
warnings: [`Ignoring invalid ${source} "${raw}": expected v1 or v2; using the default v1`]
|
|
9979
|
+
};
|
|
9980
|
+
}
|
|
9981
|
+
return { version: normalized, warnings: [] };
|
|
9982
|
+
}
|
|
9679
9983
|
function resolveOpenCodeStartTimeoutMs(options, env = process.env) {
|
|
9680
9984
|
const defaultMs = DEFAULT_OPENCODE_START_TIMEOUT_SECONDS * 1e3;
|
|
9681
9985
|
let raw;
|
|
@@ -9742,7 +10046,7 @@ function log2(state, message, level = "info") {
|
|
|
9742
10046
|
})
|
|
9743
10047
|
);
|
|
9744
10048
|
} else if (!state.interactive) {
|
|
9745
|
-
const prefix = level === "error" ?
|
|
10049
|
+
const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
|
|
9746
10050
|
console.log(`${prefix} ${message}`);
|
|
9747
10051
|
}
|
|
9748
10052
|
}
|
|
@@ -9772,7 +10076,7 @@ function logActivity(state, entry) {
|
|
|
9772
10076
|
}
|
|
9773
10077
|
function reportSessionDbRecovery(state) {
|
|
9774
10078
|
try {
|
|
9775
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
10079
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir6(), env: process.env });
|
|
9776
10080
|
for (const record of report.records) {
|
|
9777
10081
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
9778
10082
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -9803,18 +10107,18 @@ function reportSessionDbRecoveryRecord(state, record) {
|
|
|
9803
10107
|
function displayStatus(state) {
|
|
9804
10108
|
if (!state.interactive) return;
|
|
9805
10109
|
const attempt = state.connection?.reconnectAttempt ?? 0;
|
|
9806
|
-
const tunnel = state.connected ?
|
|
9807
|
-
const opencode = state.opencodeConnected ?
|
|
9808
|
-
const messages = state.messageCount > 0 ?
|
|
10110
|
+
const tunnel = state.connected ? chalk7.green("tunnel: connected") : attempt > 0 ? chalk7.yellow(`tunnel: reconnecting (#${attempt})`) : chalk7.yellow("tunnel: connecting");
|
|
10111
|
+
const opencode = state.opencodeConnected ? chalk7.green(`opencode: :${state.port}`) : chalk7.red(`opencode: :${state.port} (down)`);
|
|
10112
|
+
const messages = state.messageCount > 0 ? chalk7.dim(` \xB7 ${state.messageCount} processed`) : "";
|
|
9809
10113
|
const last = state.activityLog[state.activityLog.length - 1];
|
|
9810
|
-
const detail = last ?
|
|
10114
|
+
const detail = last ? chalk7.dim(` \xB7 ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`) : "";
|
|
9811
10115
|
const agent = state.agentName ?? state.agentId;
|
|
9812
10116
|
console.log(
|
|
9813
|
-
`${
|
|
10117
|
+
`${chalk7.bold("Evident")} ${chalk7.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
|
|
9814
10118
|
);
|
|
9815
10119
|
}
|
|
9816
10120
|
async function promptForLogin(promptMessage, successMessage) {
|
|
9817
|
-
const action = await
|
|
10121
|
+
const action = await select4({
|
|
9818
10122
|
message: promptMessage,
|
|
9819
10123
|
choices: [
|
|
9820
10124
|
{
|
|
@@ -9830,7 +10134,7 @@ async function promptForLogin(promptMessage, successMessage) {
|
|
|
9830
10134
|
]
|
|
9831
10135
|
});
|
|
9832
10136
|
if (action === "exit") {
|
|
9833
|
-
console.log(
|
|
10137
|
+
console.log(chalk7.dim(`
|
|
9834
10138
|
You can log in later by running: ${getCliName()} login`));
|
|
9835
10139
|
process.exit(0);
|
|
9836
10140
|
}
|
|
@@ -9841,7 +10145,7 @@ You can log in later by running: ${getCliName()} login`));
|
|
|
9841
10145
|
process.exit(1);
|
|
9842
10146
|
}
|
|
9843
10147
|
blank();
|
|
9844
|
-
console.log(
|
|
10148
|
+
console.log(chalk7.green(successMessage));
|
|
9845
10149
|
blank();
|
|
9846
10150
|
return { token: credentials2.token, authType: "bearer", user: credentials2.user };
|
|
9847
10151
|
}
|
|
@@ -9854,12 +10158,12 @@ async function handleAuthError(state, error2) {
|
|
|
9854
10158
|
if (state.interactive) displayStatus(state);
|
|
9855
10159
|
if (!state.interactive) {
|
|
9856
10160
|
blank();
|
|
9857
|
-
console.log(
|
|
9858
|
-
console.log(
|
|
10161
|
+
console.log(chalk7.red("Authentication expired"));
|
|
10162
|
+
console.log(chalk7.dim("Your authentication token is no longer valid."));
|
|
9859
10163
|
blank();
|
|
9860
|
-
console.log(
|
|
9861
|
-
console.log(
|
|
9862
|
-
console.log(
|
|
10164
|
+
console.log(chalk7.dim("To fix this:"));
|
|
10165
|
+
console.log(chalk7.dim(` 1. Run '${getCliName()} login' to re-authenticate`));
|
|
10166
|
+
console.log(chalk7.dim(" 2. Restart this command"));
|
|
9863
10167
|
blank();
|
|
9864
10168
|
await cleanup(state);
|
|
9865
10169
|
await shutdownTelemetry();
|
|
@@ -9867,7 +10171,7 @@ async function handleAuthError(state, error2) {
|
|
|
9867
10171
|
return { success: false };
|
|
9868
10172
|
}
|
|
9869
10173
|
blank();
|
|
9870
|
-
console.log(
|
|
10174
|
+
console.log(chalk7.yellow("Your authentication has expired."));
|
|
9871
10175
|
blank();
|
|
9872
10176
|
try {
|
|
9873
10177
|
const credentials2 = await promptForLogin(
|
|
@@ -9958,8 +10262,8 @@ async function driveChannels(state, driver) {
|
|
|
9958
10262
|
state.running = false;
|
|
9959
10263
|
break;
|
|
9960
10264
|
}
|
|
9961
|
-
const
|
|
9962
|
-
logActivity(state, { type: "error", error: `Channel processing error: ${
|
|
10265
|
+
const errorMessage3 = error2 instanceof Error ? error2.message : String(error2);
|
|
10266
|
+
logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage3}` });
|
|
9963
10267
|
if (state.interactive) displayStatus(state);
|
|
9964
10268
|
if (driver.hasInFlightWatchers()) {
|
|
9965
10269
|
consecutiveDrainFailures = 0;
|
|
@@ -9997,9 +10301,18 @@ async function driveChannels(state, driver) {
|
|
|
9997
10301
|
}
|
|
9998
10302
|
}
|
|
9999
10303
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
10000
|
-
var SESSION_DB_RECLAIM_MAX_PAGES =
|
|
10304
|
+
var SESSION_DB_RECLAIM_MAX_PAGES = 2e4;
|
|
10305
|
+
function shouldWarnForReclaimSkip(reason) {
|
|
10306
|
+
if (reason !== "sqlite-unavailable") return false;
|
|
10307
|
+
const version2 = /^v(\d+)\.(\d+)\.(\d+)$/.exec(process.version);
|
|
10308
|
+
if (!version2) return false;
|
|
10309
|
+
const major = Number(version2[1]);
|
|
10310
|
+
const minor = Number(version2[2]);
|
|
10311
|
+
const patch = Number(version2[3]);
|
|
10312
|
+
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
10313
|
+
}
|
|
10001
10314
|
function sessionDbPath() {
|
|
10002
|
-
return
|
|
10315
|
+
return join10(homedir6(), ".local", "share", "opencode", "opencode.db");
|
|
10003
10316
|
}
|
|
10004
10317
|
function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
|
|
10005
10318
|
const record = {
|
|
@@ -10095,7 +10408,7 @@ async function runSweep(state, driver, config) {
|
|
|
10095
10408
|
} else {
|
|
10096
10409
|
logActivity(state, {
|
|
10097
10410
|
type: "info",
|
|
10098
|
-
message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})`
|
|
10411
|
+
message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : "")
|
|
10099
10412
|
});
|
|
10100
10413
|
}
|
|
10101
10414
|
} catch (error2) {
|
|
@@ -10118,13 +10431,20 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
10118
10431
|
for (const warning2 of config.warnings) {
|
|
10119
10432
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
10120
10433
|
}
|
|
10121
|
-
const dbBytes = statSessionDbBytes(
|
|
10434
|
+
const dbBytes = statSessionDbBytes(homedir6());
|
|
10122
10435
|
void (async () => {
|
|
10123
|
-
const
|
|
10436
|
+
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
10437
|
+
if (reclaimAvailability !== null) {
|
|
10438
|
+
logActivity(state, {
|
|
10439
|
+
type: "info",
|
|
10440
|
+
level: shouldWarnForReclaimSkip(reclaimAvailability.reason) ? "warn" : "info",
|
|
10441
|
+
message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : "")
|
|
10442
|
+
});
|
|
10443
|
+
}
|
|
10124
10444
|
const sizeWarning = buildSessionStoreSizeWarning({
|
|
10125
10445
|
dbBytes,
|
|
10126
10446
|
cleanupEnabled: config.enabled,
|
|
10127
|
-
reclaimSkipReason
|
|
10447
|
+
reclaimSkipReason: reclaimAvailability?.reason ?? null
|
|
10128
10448
|
});
|
|
10129
10449
|
if (sizeWarning !== null) {
|
|
10130
10450
|
logActivity(state, { type: "info", level: "warn", message: sizeWarning });
|
|
@@ -10319,7 +10639,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
10319
10639
|
});
|
|
10320
10640
|
return;
|
|
10321
10641
|
}
|
|
10322
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
10642
|
+
const { collect, stop } = createResourceUsageCollector(homedir6());
|
|
10323
10643
|
state.stopResourceUsageSampling = stop;
|
|
10324
10644
|
let consecutiveFailures = 0;
|
|
10325
10645
|
const tick = async () => {
|
|
@@ -10416,6 +10736,8 @@ async function cleanup(state, opts = {}) {
|
|
|
10416
10736
|
clearTimeout(timer);
|
|
10417
10737
|
}
|
|
10418
10738
|
state.sessionCleanupTimers = [];
|
|
10739
|
+
state.stopOpenCodeLogTail?.();
|
|
10740
|
+
state.stopOpenCodeLogTail = null;
|
|
10419
10741
|
if (state.claudeUsageTimer) {
|
|
10420
10742
|
clearTimeout(state.claudeUsageTimer);
|
|
10421
10743
|
state.claudeUsageTimer = null;
|
|
@@ -10554,7 +10876,7 @@ async function run(options) {
|
|
|
10554
10876
|
let fileSyncDirectories;
|
|
10555
10877
|
try {
|
|
10556
10878
|
logLevel = resolveLogLevel(options);
|
|
10557
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
10879
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir6());
|
|
10558
10880
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
10559
10881
|
throw new Error(
|
|
10560
10882
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -10585,6 +10907,7 @@ async function run(options) {
|
|
|
10585
10907
|
opencodeVersion: null,
|
|
10586
10908
|
sessionDbProvenanceAnomaly: false,
|
|
10587
10909
|
opencodeProcess: null,
|
|
10910
|
+
stopOpenCodeLogTail: null,
|
|
10588
10911
|
litestreamProcess: null,
|
|
10589
10912
|
connection: null,
|
|
10590
10913
|
channelDriver: null,
|
|
@@ -10652,15 +10975,15 @@ async function run(options) {
|
|
|
10652
10975
|
printError("Authentication required");
|
|
10653
10976
|
blank();
|
|
10654
10977
|
console.log(
|
|
10655
|
-
|
|
10978
|
+
chalk7.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
|
|
10656
10979
|
);
|
|
10657
|
-
console.log(
|
|
10980
|
+
console.log(chalk7.dim("Or run `evident login` for interactive authentication"));
|
|
10658
10981
|
blank();
|
|
10659
10982
|
process.exit(1);
|
|
10660
10983
|
return;
|
|
10661
10984
|
}
|
|
10662
10985
|
blank();
|
|
10663
|
-
console.log(
|
|
10986
|
+
console.log(chalk7.yellow("You are not logged in to Evident."));
|
|
10664
10987
|
blank();
|
|
10665
10988
|
credentials2 = await promptForLogin(
|
|
10666
10989
|
"Would you like to log in now?",
|
|
@@ -10710,7 +11033,7 @@ async function run(options) {
|
|
|
10710
11033
|
);
|
|
10711
11034
|
blank();
|
|
10712
11035
|
console.log(
|
|
10713
|
-
|
|
11036
|
+
chalk7.dim(
|
|
10714
11037
|
"Either provide --runner/--agent <id> or set EVIDENT_RUNNER_KEY/EVIDENT_AGENT_KEY"
|
|
10715
11038
|
)
|
|
10716
11039
|
);
|
|
@@ -10733,15 +11056,15 @@ async function run(options) {
|
|
|
10733
11056
|
);
|
|
10734
11057
|
if (interactive && !state.json) {
|
|
10735
11058
|
blank();
|
|
10736
|
-
console.log(
|
|
10737
|
-
console.log(
|
|
11059
|
+
console.log(chalk7.bold("Evident Run"));
|
|
11060
|
+
console.log(chalk7.dim("-".repeat(40)));
|
|
10738
11061
|
}
|
|
10739
11062
|
const spinner = interactive && !state.json ? ora3("Validating runner...").start() : null;
|
|
10740
11063
|
let validation = await getAgentInfo(state.agentId, state.authHeader);
|
|
10741
11064
|
if (!validation.valid && validation.authFailed && interactive) {
|
|
10742
11065
|
spinner?.fail("Authentication failed");
|
|
10743
11066
|
blank();
|
|
10744
|
-
console.log(
|
|
11067
|
+
console.log(chalk7.yellow("Your authentication token is invalid or expired."));
|
|
10745
11068
|
blank();
|
|
10746
11069
|
credentials2 = await promptForLogin(
|
|
10747
11070
|
"Would you like to log in again?",
|
|
@@ -10789,6 +11112,13 @@ async function run(options) {
|
|
|
10789
11112
|
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
10790
11113
|
}
|
|
10791
11114
|
state.credentialSync?.arm();
|
|
11115
|
+
state.stopOpenCodeLogTail = tailOpenCodeLogFile(
|
|
11116
|
+
resolveOpenCodeLogPath(homedir6(), process.env),
|
|
11117
|
+
createOpenCodeActivityForwarder(() => ({
|
|
11118
|
+
agentId: state.agentId,
|
|
11119
|
+
authHeader: state.authHeader
|
|
11120
|
+
}))
|
|
11121
|
+
).stop;
|
|
10792
11122
|
let sessionDbVerifyFatal = false;
|
|
10793
11123
|
if (!options.restoreSessionDb) {
|
|
10794
11124
|
log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
|
|
@@ -10838,6 +11168,13 @@ async function run(options) {
|
|
|
10838
11168
|
for (const warning2 of opencodeStartTimeoutWarnings) {
|
|
10839
11169
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
10840
11170
|
}
|
|
11171
|
+
const { version: opencodeVersion, warnings: opencodeVersionWarnings } = resolveOpenCodeVersion(
|
|
11172
|
+
options,
|
|
11173
|
+
process.env
|
|
11174
|
+
);
|
|
11175
|
+
for (const warning2 of opencodeVersionWarnings) {
|
|
11176
|
+
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
11177
|
+
}
|
|
10841
11178
|
const { value: maxActiveSessions, warnings: maxActiveSessionsWarnings } = resolveMaxActiveSessions(options, process.env);
|
|
10842
11179
|
for (const warning2 of maxActiveSessionsWarnings) {
|
|
10843
11180
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
@@ -10845,7 +11182,14 @@ async function run(options) {
|
|
|
10845
11182
|
const preBootMigrationIds = readSessionDbMigrationIds(sessionDbPath());
|
|
10846
11183
|
const ocSpinner = interactive && !state.json ? ora3("Checking OpenCode...").start() : null;
|
|
10847
11184
|
try {
|
|
10848
|
-
const oc = await
|
|
11185
|
+
const oc = opencodeVersion === "v2" ? await ensureOpenCode2Running({
|
|
11186
|
+
port: state.port,
|
|
11187
|
+
interactive: state.interactive,
|
|
11188
|
+
agentId: state.agentId,
|
|
11189
|
+
log: (message) => log2(state, message),
|
|
11190
|
+
startTimeoutMs: opencodeStartTimeoutMs,
|
|
11191
|
+
inheritStdio: Boolean(options.opencodePidFile)
|
|
11192
|
+
}) : await ensureOpenCodeRunning({
|
|
10849
11193
|
port: state.port,
|
|
10850
11194
|
interactive: state.interactive,
|
|
10851
11195
|
agentId: state.agentId,
|
|
@@ -10872,7 +11216,7 @@ async function run(options) {
|
|
|
10872
11216
|
const provenance = checkSessionDbProvenance({
|
|
10873
11217
|
dbPath: sessionDbPath(),
|
|
10874
11218
|
currentVersion: state.opencodeVersion,
|
|
10875
|
-
homeDir:
|
|
11219
|
+
homeDir: homedir6(),
|
|
10876
11220
|
env: process.env
|
|
10877
11221
|
});
|
|
10878
11222
|
if (provenance.anomaly) {
|
|
@@ -10907,10 +11251,10 @@ async function run(options) {
|
|
|
10907
11251
|
if (state.interactive && !state.json) {
|
|
10908
11252
|
logActivity(state, { type: "info", level: "warn", message: noProviderWarning });
|
|
10909
11253
|
blank();
|
|
10910
|
-
console.log(
|
|
11254
|
+
console.log(chalk7.yellow("\u26A0 No OpenCode model provider is configured."));
|
|
10911
11255
|
console.log(
|
|
10912
|
-
|
|
10913
|
-
`Run ${
|
|
11256
|
+
chalk7.dim(
|
|
11257
|
+
`Run ${chalk7.cyan("opencode auth login")} to set one up \u2014 messages will fail until then.`
|
|
10914
11258
|
)
|
|
10915
11259
|
);
|
|
10916
11260
|
blank();
|
|
@@ -11034,7 +11378,7 @@ async function run(options) {
|
|
|
11034
11378
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
11035
11379
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
11036
11380
|
fileSyncDirectories,
|
|
11037
|
-
homeDir:
|
|
11381
|
+
homeDir: homedir6(),
|
|
11038
11382
|
maxActiveSessions,
|
|
11039
11383
|
log: (entry) => (
|
|
11040
11384
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -11276,6 +11620,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
11276
11620
|
).option("-v, --verbose", "Alias for --log-level debug (ignored if --log-level is set)").option("-c, --conversation <id>", "Process only this specific conversation").option("--idle-timeout <seconds>", "Exit after N seconds idle").option(
|
|
11277
11621
|
"--opencode-start-timeout <seconds>",
|
|
11278
11622
|
"Seconds to wait for OpenCode to become healthy when the runner starts it (default: 180). Env: EVIDENT_OPENCODE_START_TIMEOUT"
|
|
11623
|
+
).option(
|
|
11624
|
+
"--opencode-version <v1|v2>",
|
|
11625
|
+
"Which OpenCode major version to launch: v1 or v2 (default: v1). Env: EVIDENT_OPENCODE_VERSION"
|
|
11279
11626
|
).option("--json", "Output in JSON format").option(
|
|
11280
11627
|
"--session-cleanup-max-age <duration>",
|
|
11281
11628
|
"Delete OpenCode sessions idle longer than this (e.g. 7d, 24h). Enables cleanup. Env: EVIDENT_SESSION_CLEANUP_MAX_AGE"
|
|
@@ -11344,6 +11691,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
11344
11691
|
// Raw string — validation/precedence is single-sourced in run.ts's
|
|
11345
11692
|
// resolveOpenCodeStartTimeoutMs (flag > EVIDENT_OPENCODE_START_TIMEOUT > 180s default).
|
|
11346
11693
|
opencodeStartTimeout: options.opencodeStartTimeout,
|
|
11694
|
+
opencodeVersion: options.opencodeVersion,
|
|
11347
11695
|
json: options.json,
|
|
11348
11696
|
// Raw strings — the resolver in run.ts single-sources parsing (M1).
|
|
11349
11697
|
sessionCleanupMaxAge: options.sessionCleanupMaxAge,
|