@evident-ai/cli 3.4.1-dev.325472f → 3.4.1-dev.444e897
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 +525 -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
|
);
|
|
@@ -3416,6 +3489,24 @@ async function hasAnyConfiguredProvider(port) {
|
|
|
3416
3489
|
return null;
|
|
3417
3490
|
}
|
|
3418
3491
|
}
|
|
3492
|
+
async function reloadProviderCache(port) {
|
|
3493
|
+
try {
|
|
3494
|
+
const res = await timedFetch(`${opencodeBase(port)}/config`, {
|
|
3495
|
+
method: "PATCH",
|
|
3496
|
+
headers: { "Content-Type": "application/json" },
|
|
3497
|
+
body: JSON.stringify({})
|
|
3498
|
+
});
|
|
3499
|
+
if (!res.ok) {
|
|
3500
|
+
console.error(
|
|
3501
|
+
`[reloadProviderCache] PATCH /config returned HTTP ${res.status} (port ${port})`
|
|
3502
|
+
);
|
|
3503
|
+
}
|
|
3504
|
+
} catch (err) {
|
|
3505
|
+
console.error(
|
|
3506
|
+
`[reloadProviderCache] PATCH /config failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3507
|
+
);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3419
3510
|
|
|
3420
3511
|
// src/lib/opencode/session-cleanup.ts
|
|
3421
3512
|
var DURATION_UNIT_MS = {
|
|
@@ -3522,8 +3613,8 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
3522
3613
|
}
|
|
3523
3614
|
|
|
3524
3615
|
// src/lib/opencode/session-db-size.ts
|
|
3525
|
-
import { statSync as statSync3 } from "fs";
|
|
3526
|
-
import { join as join4 } from "path";
|
|
3616
|
+
import { statSync as statSync3 } from "node:fs";
|
|
3617
|
+
import { join as join4 } from "node:path";
|
|
3527
3618
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
3528
3619
|
function statSessionDbBytes(homeDir) {
|
|
3529
3620
|
const dbPath = join4(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
@@ -3553,9 +3644,96 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
3553
3644
|
return null;
|
|
3554
3645
|
}
|
|
3555
3646
|
|
|
3647
|
+
// src/lib/opencode/log-tail.ts
|
|
3648
|
+
import { statSync as statSync4 } from "node:fs";
|
|
3649
|
+
import { homedir as homedir3 } from "node:os";
|
|
3650
|
+
import { join as join5 } from "node:path";
|
|
3651
|
+
import { open as open2, stat } from "node:fs/promises";
|
|
3652
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
3653
|
+
function resolveOpenCodeLogPath(homeDir = homedir3(), env = process.env) {
|
|
3654
|
+
const dataDir = env.XDG_DATA_HOME || join5(homeDir, ".local", "share");
|
|
3655
|
+
return join5(dataDir, "opencode", "log", "opencode.log");
|
|
3656
|
+
}
|
|
3657
|
+
function isEnoent(error2) {
|
|
3658
|
+
return error2?.code === "ENOENT";
|
|
3659
|
+
}
|
|
3660
|
+
function reportFailure(operation, logPath, error2) {
|
|
3661
|
+
console.error(
|
|
3662
|
+
`[opencode-log-tail] ${operation} failed for ${logPath}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
3663
|
+
);
|
|
3664
|
+
}
|
|
3665
|
+
function tailOpenCodeLogFile(logPath, onChunk, opts = {}) {
|
|
3666
|
+
const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3667
|
+
let offset = 0;
|
|
3668
|
+
let inode = null;
|
|
3669
|
+
let baselineReady = true;
|
|
3670
|
+
try {
|
|
3671
|
+
const initial = statSync4(logPath);
|
|
3672
|
+
offset = initial.size;
|
|
3673
|
+
inode = initial.ino;
|
|
3674
|
+
} catch (error2) {
|
|
3675
|
+
if (!isEnoent(error2)) {
|
|
3676
|
+
reportFailure("initial stat", logPath, error2);
|
|
3677
|
+
baselineReady = false;
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
let polling = false;
|
|
3681
|
+
let stopped = false;
|
|
3682
|
+
const poll = async () => {
|
|
3683
|
+
if (polling || stopped) return;
|
|
3684
|
+
polling = true;
|
|
3685
|
+
try {
|
|
3686
|
+
let current;
|
|
3687
|
+
try {
|
|
3688
|
+
current = await stat(logPath);
|
|
3689
|
+
} catch (error2) {
|
|
3690
|
+
if (!isEnoent(error2)) reportFailure("stat", logPath, error2);
|
|
3691
|
+
return;
|
|
3692
|
+
}
|
|
3693
|
+
if (!baselineReady) {
|
|
3694
|
+
offset = current.size;
|
|
3695
|
+
inode = current.ino;
|
|
3696
|
+
baselineReady = true;
|
|
3697
|
+
return;
|
|
3698
|
+
}
|
|
3699
|
+
if (inode !== null && current.ino !== inode || current.size < offset) {
|
|
3700
|
+
offset = 0;
|
|
3701
|
+
}
|
|
3702
|
+
inode = current.ino;
|
|
3703
|
+
if (current.size === offset) return;
|
|
3704
|
+
const length = current.size - offset;
|
|
3705
|
+
const fh = await open2(logPath, "r");
|
|
3706
|
+
try {
|
|
3707
|
+
const buf = Buffer.alloc(length);
|
|
3708
|
+
const { bytesRead } = await fh.read(buf, 0, length, offset);
|
|
3709
|
+
offset += bytesRead;
|
|
3710
|
+
if (bytesRead > 0) onChunk(buf.subarray(0, bytesRead));
|
|
3711
|
+
} finally {
|
|
3712
|
+
await fh.close();
|
|
3713
|
+
}
|
|
3714
|
+
} catch (error2) {
|
|
3715
|
+
if (!isEnoent(error2)) reportFailure("poll", logPath, error2);
|
|
3716
|
+
} finally {
|
|
3717
|
+
polling = false;
|
|
3718
|
+
}
|
|
3719
|
+
};
|
|
3720
|
+
const interval = setInterval(() => void poll(), pollIntervalMs);
|
|
3721
|
+
void poll();
|
|
3722
|
+
return {
|
|
3723
|
+
stop: () => {
|
|
3724
|
+
stopped = true;
|
|
3725
|
+
clearInterval(interval);
|
|
3726
|
+
}
|
|
3727
|
+
};
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3556
3730
|
// src/lib/opencode/session-db-reclaim.ts
|
|
3557
|
-
import { statSync as
|
|
3558
|
-
import { dirname as dirname4 } from "path";
|
|
3731
|
+
import { statSync as statSync5, statfsSync } from "node:fs";
|
|
3732
|
+
import { dirname as dirname4 } from "node:path";
|
|
3733
|
+
function errorMessage(error2) {
|
|
3734
|
+
if (!(error2 instanceof Error)) return String(error2);
|
|
3735
|
+
return error2.cause instanceof Error ? error2.cause.message : error2.message;
|
|
3736
|
+
}
|
|
3559
3737
|
function insufficientSpaceReason(dbPath, requiredBytes) {
|
|
3560
3738
|
try {
|
|
3561
3739
|
const fsStats = statfsSync(dirname4(dbPath));
|
|
@@ -3581,17 +3759,17 @@ async function probeReclaimAvailability(input) {
|
|
|
3581
3759
|
const { dbPath, requiredBytes } = input;
|
|
3582
3760
|
let sqlite;
|
|
3583
3761
|
try {
|
|
3584
|
-
sqlite = await import("sqlite");
|
|
3762
|
+
sqlite = await import("node:sqlite");
|
|
3585
3763
|
} catch (err) {
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
return "sqlite-unavailable";
|
|
3764
|
+
const detail = `Node ${process.version}: ${errorMessage(err)}`;
|
|
3765
|
+
console.warn(`[probeReclaimAvailability] node:sqlite unavailable for ${dbPath}: ` + detail);
|
|
3766
|
+
return { reason: "sqlite-unavailable", detail };
|
|
3590
3767
|
}
|
|
3591
3768
|
let autoVacuum = null;
|
|
3592
3769
|
try {
|
|
3593
3770
|
const db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
|
|
3594
3771
|
try {
|
|
3772
|
+
db.exec("PRAGMA busy_timeout=5000");
|
|
3595
3773
|
autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
|
|
3596
3774
|
} finally {
|
|
3597
3775
|
db.close();
|
|
@@ -3602,23 +3780,25 @@ async function probeReclaimAvailability(input) {
|
|
|
3602
3780
|
);
|
|
3603
3781
|
}
|
|
3604
3782
|
if (autoVacuum !== 0) return null;
|
|
3605
|
-
return insufficientSpaceReason(dbPath, requiredBytes) !== null ? "insufficient-disk-space" : null;
|
|
3783
|
+
return insufficientSpaceReason(dbPath, requiredBytes) !== null ? { reason: "insufficient-disk-space" } : null;
|
|
3606
3784
|
}
|
|
3607
3785
|
async function reclaimSessionDbSpace(input) {
|
|
3608
3786
|
const { dbPath, maxPages, allowFullVacuum = true } = input;
|
|
3609
3787
|
let sqlite;
|
|
3610
3788
|
try {
|
|
3611
|
-
sqlite = await import("sqlite");
|
|
3789
|
+
sqlite = await import("node:sqlite");
|
|
3612
3790
|
} catch (err) {
|
|
3791
|
+
const detail = `Node ${process.version}: ${errorMessage(err)}`;
|
|
3613
3792
|
console.warn(
|
|
3614
|
-
`[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${
|
|
3793
|
+
`[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${detail}`
|
|
3615
3794
|
);
|
|
3616
|
-
return { ok: false, skipped: "sqlite-unavailable" };
|
|
3795
|
+
return { ok: false, skipped: "sqlite-unavailable", detail };
|
|
3617
3796
|
}
|
|
3618
3797
|
const { DatabaseSync } = sqlite;
|
|
3619
3798
|
let db;
|
|
3620
3799
|
try {
|
|
3621
3800
|
db = new DatabaseSync(dbPath);
|
|
3801
|
+
db.exec("PRAGMA busy_timeout=5000");
|
|
3622
3802
|
const autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
|
|
3623
3803
|
if (autoVacuum === 0) {
|
|
3624
3804
|
if (!allowFullVacuum) {
|
|
@@ -3627,7 +3807,7 @@ async function reclaimSessionDbSpace(input) {
|
|
|
3627
3807
|
);
|
|
3628
3808
|
return { ok: false, skipped: "full-vacuum-blocked" };
|
|
3629
3809
|
}
|
|
3630
|
-
const fileBytesForGuard =
|
|
3810
|
+
const fileBytesForGuard = statSync5(dbPath).size;
|
|
3631
3811
|
const skipReason = insufficientSpaceReason(dbPath, fileBytesForGuard);
|
|
3632
3812
|
if (skipReason !== null) {
|
|
3633
3813
|
console.warn(
|
|
@@ -3655,10 +3835,12 @@ async function reclaimSessionDbSpace(input) {
|
|
|
3655
3835
|
);
|
|
3656
3836
|
return { ok: false, skipped: "auto-vacuum-not-applicable" };
|
|
3657
3837
|
} catch (err) {
|
|
3658
|
-
console.error(
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3838
|
+
console.error(`[reclaimSessionDbSpace] reclaim failed for ${dbPath}: ` + errorMessage(err));
|
|
3839
|
+
return {
|
|
3840
|
+
ok: false,
|
|
3841
|
+
skipped: "reclaim-error",
|
|
3842
|
+
detail: errorMessage(err)
|
|
3843
|
+
};
|
|
3662
3844
|
} finally {
|
|
3663
3845
|
db?.close();
|
|
3664
3846
|
}
|
|
@@ -3950,8 +4132,8 @@ function connectTunnel(options) {
|
|
|
3950
4132
|
try {
|
|
3951
4133
|
message = JSON.parse(data.toString());
|
|
3952
4134
|
} catch (error2) {
|
|
3953
|
-
const
|
|
3954
|
-
onError?.(`Failed to handle message: ${
|
|
4135
|
+
const errorMessage3 = error2 instanceof Error ? error2.message : "Unknown error";
|
|
4136
|
+
onError?.(`Failed to handle message: ${errorMessage3}`);
|
|
3955
4137
|
return;
|
|
3956
4138
|
}
|
|
3957
4139
|
if (isStreamFrame(message)) {
|
|
@@ -4095,7 +4277,7 @@ var RunnerConnection = class {
|
|
|
4095
4277
|
};
|
|
4096
4278
|
|
|
4097
4279
|
// src/lib/tunnel/ready-marker.ts
|
|
4098
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
4280
|
+
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
4099
4281
|
function writeTunnelReadyMarker(path, agentId) {
|
|
4100
4282
|
try {
|
|
4101
4283
|
writeFileSync3(path, `${agentId}
|
|
@@ -4107,7 +4289,7 @@ function writeTunnelReadyMarker(path, agentId) {
|
|
|
4107
4289
|
}
|
|
4108
4290
|
|
|
4109
4291
|
// src/lib/replication.ts
|
|
4110
|
-
import { spawn as spawn4 } from "child_process";
|
|
4292
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
4111
4293
|
function startSessionDbReplication(configPath) {
|
|
4112
4294
|
return spawn4("litestream", ["replicate", "-config", configPath], {
|
|
4113
4295
|
stdio: "inherit"
|
|
@@ -4123,7 +4305,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
|
|
|
4123
4305
|
}
|
|
4124
4306
|
|
|
4125
4307
|
// src/lib/process-liveness.ts
|
|
4126
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
4308
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
4127
4309
|
function isProcessAlive(pid) {
|
|
4128
4310
|
try {
|
|
4129
4311
|
process.kill(pid, 0);
|
|
@@ -4149,9 +4331,9 @@ function isProcessAlive(pid) {
|
|
|
4149
4331
|
}
|
|
4150
4332
|
|
|
4151
4333
|
// src/lib/openai-usage.ts
|
|
4152
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
4153
|
-
import { homedir as
|
|
4154
|
-
import { join as
|
|
4334
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
4335
|
+
import { homedir as homedir4 } from "node:os";
|
|
4336
|
+
import { join as join6 } from "node:path";
|
|
4155
4337
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
4156
4338
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
4157
4339
|
var OpenAiUsageError = class extends Error {
|
|
@@ -4165,7 +4347,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
4165
4347
|
}
|
|
4166
4348
|
function readOpenCodeChatGptCredentials() {
|
|
4167
4349
|
try {
|
|
4168
|
-
const raw = readFileSync5(
|
|
4350
|
+
const raw = readFileSync5(join6(homedir4(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
4169
4351
|
let parsed;
|
|
4170
4352
|
try {
|
|
4171
4353
|
parsed = JSON.parse(raw);
|
|
@@ -4427,8 +4609,8 @@ function resolveResourceUsageReportingEnabled(flagValue, env) {
|
|
|
4427
4609
|
}
|
|
4428
4610
|
|
|
4429
4611
|
// src/lib/resource-usage.ts
|
|
4430
|
-
import { cpus, totalmem, freemem } from "os";
|
|
4431
|
-
import { statfsSync as statfsSync2 } from "fs";
|
|
4612
|
+
import { cpus, totalmem, freemem } from "node:os";
|
|
4613
|
+
import { statfsSync as statfsSync2 } from "node:fs";
|
|
4432
4614
|
|
|
4433
4615
|
// src/lib/ecs-task-metadata.ts
|
|
4434
4616
|
var ECS_METADATA_TIMEOUT_MS = 2e3;
|
|
@@ -4595,15 +4777,15 @@ function createResourceUsageCollector(homeDir) {
|
|
|
4595
4777
|
}
|
|
4596
4778
|
|
|
4597
4779
|
// src/lib/channels/driver.ts
|
|
4598
|
-
import { homedir as
|
|
4780
|
+
import { homedir as homedir5 } from "node:os";
|
|
4599
4781
|
|
|
4600
4782
|
// src/lib/runner-file-sync.ts
|
|
4601
|
-
import { join as
|
|
4783
|
+
import { join as join8 } from "node:path";
|
|
4602
4784
|
|
|
4603
4785
|
// 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
|
|
4786
|
+
import { randomUUID } from "node:crypto";
|
|
4787
|
+
import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
|
|
4788
|
+
import { basename, dirname as dirname5, isAbsolute, join as join7, relative, resolve as resolve2, sep } from "node:path";
|
|
4607
4789
|
var FILE_MODE = 384;
|
|
4608
4790
|
var DIRECTORY_MODE = 448;
|
|
4609
4791
|
async function writePushedFile(request) {
|
|
@@ -4636,7 +4818,7 @@ async function writePushedFile(request) {
|
|
|
4636
4818
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
4637
4819
|
dirname5(candidate)
|
|
4638
4820
|
);
|
|
4639
|
-
const realTarget =
|
|
4821
|
+
const realTarget = join7(existingAncestor, ...missingSegments, basename(candidate));
|
|
4640
4822
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
4641
4823
|
if (allowedDirectory === null) {
|
|
4642
4824
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -4672,7 +4854,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
4672
4854
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
4673
4855
|
return null;
|
|
4674
4856
|
}
|
|
4675
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4857
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
4676
4858
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
4677
4859
|
return null;
|
|
4678
4860
|
}
|
|
@@ -4745,16 +4927,16 @@ function contains(realDirectory, realTarget) {
|
|
|
4745
4927
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
4746
4928
|
let current = existingAncestor;
|
|
4747
4929
|
for (const segment of missingSegments) {
|
|
4748
|
-
current =
|
|
4930
|
+
current = join7(current, segment);
|
|
4749
4931
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
4750
4932
|
await chmod(current, DIRECTORY_MODE);
|
|
4751
4933
|
}
|
|
4752
4934
|
}
|
|
4753
4935
|
async function writeAtomically(realTarget, content) {
|
|
4754
|
-
const temporaryPath =
|
|
4936
|
+
const temporaryPath = join7(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
4755
4937
|
let handle;
|
|
4756
4938
|
try {
|
|
4757
|
-
handle = await
|
|
4939
|
+
handle = await open3(temporaryPath, "wx", FILE_MODE);
|
|
4758
4940
|
await handle.writeFile(content);
|
|
4759
4941
|
await handle.chmod(FILE_MODE);
|
|
4760
4942
|
await handle.close();
|
|
@@ -4881,12 +5063,12 @@ var NOT_APPLIED = {
|
|
|
4881
5063
|
opencodeAuthApplied: false
|
|
4882
5064
|
};
|
|
4883
5065
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
4884
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4885
|
-
return expanded ===
|
|
5066
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
5067
|
+
return expanded === join8(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
4886
5068
|
}
|
|
4887
5069
|
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
4888
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4889
|
-
return expanded ===
|
|
5070
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
5071
|
+
return expanded === join8(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
4890
5072
|
}
|
|
4891
5073
|
async function applyOne(options, file) {
|
|
4892
5074
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -5433,7 +5615,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5433
5615
|
this.stuckQueuedMs = config.stuckQueuedMs ?? DEFAULT_STUCK_QUEUED_MS;
|
|
5434
5616
|
this.now = config.now ?? (() => Date.now());
|
|
5435
5617
|
this.fileSyncDirectories = config.fileSyncDirectories ?? [];
|
|
5436
|
-
this.homeDir = config.homeDir ??
|
|
5618
|
+
this.homeDir = config.homeDir ?? homedir5();
|
|
5437
5619
|
this.maxActiveSessions = config.maxActiveSessions;
|
|
5438
5620
|
this.watcherStallMs = config.watcherStallMs ?? WATCHER_STALL_MS;
|
|
5439
5621
|
this.wedgeWarningIntervalMs = config.wedgeWarningIntervalMs ?? WEDGE_WARNING_INTERVAL_MS;
|
|
@@ -5795,7 +5977,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5795
5977
|
this.signalDispatchNotStarted(conv, message, "session_existence_unknown");
|
|
5796
5978
|
break;
|
|
5797
5979
|
}
|
|
5798
|
-
const
|
|
5980
|
+
const errorMessage3 = err instanceof Error ? err.message : String(err);
|
|
5799
5981
|
this.sessions.delete(conv.id);
|
|
5800
5982
|
this.supersede(conv.id, sessionId);
|
|
5801
5983
|
this.log({
|
|
@@ -5804,7 +5986,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5804
5986
|
conversation_id: conv.id,
|
|
5805
5987
|
message_id: message.id
|
|
5806
5988
|
});
|
|
5807
|
-
await this.markFailed(conv.id, message.id, null,
|
|
5989
|
+
await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
|
|
5808
5990
|
this.log({
|
|
5809
5991
|
level: "warn",
|
|
5810
5992
|
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 +5997,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5815
5997
|
});
|
|
5816
5998
|
this.log({
|
|
5817
5999
|
level: "error",
|
|
5818
|
-
message: `Message ${message.id.slice(0, 8)} dispatch failed: ${
|
|
6000
|
+
message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage3}`,
|
|
5819
6001
|
conversation_id: conv.id,
|
|
5820
6002
|
message_id: message.id
|
|
5821
6003
|
});
|
|
@@ -5836,14 +6018,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5836
6018
|
this.unconfirmedDispatchFailures.delete(message.id);
|
|
5837
6019
|
this.sessions.delete(conv.id);
|
|
5838
6020
|
this.supersede(conv.id, sessionId);
|
|
5839
|
-
const
|
|
6021
|
+
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
6022
|
this.log({
|
|
5841
6023
|
level: "error",
|
|
5842
|
-
message:
|
|
6024
|
+
message: errorMessage3,
|
|
5843
6025
|
conversation_id: conv.id,
|
|
5844
6026
|
message_id: message.id
|
|
5845
6027
|
});
|
|
5846
|
-
await this.markFailed(conv.id, message.id, null,
|
|
6028
|
+
await this.markFailed(conv.id, message.id, null, errorMessage3).catch((markErr) => {
|
|
5847
6029
|
this.log({
|
|
5848
6030
|
level: "warn",
|
|
5849
6031
|
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 +8008,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7826
8008
|
this.unconfirmedDispatchFailures.delete(row.id);
|
|
7827
8009
|
this.sessions.delete(readoptConv.id);
|
|
7828
8010
|
this.supersede(readoptConv.id, sessionId);
|
|
7829
|
-
const
|
|
8011
|
+
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
8012
|
this.log({
|
|
7831
8013
|
level: "error",
|
|
7832
|
-
message:
|
|
8014
|
+
message: errorMessage3,
|
|
7833
8015
|
conversation_id: row.conversation_id,
|
|
7834
8016
|
message_id: row.id
|
|
7835
8017
|
});
|
|
7836
|
-
await this.markFailed(row.conversation_id, row.id, null,
|
|
8018
|
+
await this.markFailed(row.conversation_id, row.id, null, errorMessage3).catch((markErr) => {
|
|
7837
8019
|
this.log({
|
|
7838
8020
|
level: "warn",
|
|
7839
8021
|
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 +9124,13 @@ import chalk5 from "chalk";
|
|
|
8942
9124
|
import ora2 from "ora";
|
|
8943
9125
|
import { select as select2 } from "@inquirer/prompts";
|
|
8944
9126
|
var INTERACTIVE_START_TIMEOUT_MS = 3e4;
|
|
9127
|
+
function checkNonInteractivePortConflict(port, isPortInUseFn) {
|
|
9128
|
+
if (isPortInUseFn(port)) {
|
|
9129
|
+
throw new Error(
|
|
9130
|
+
`Port ${port} is already in use by a non-OpenCode process. Free it or pass --port.`
|
|
9131
|
+
);
|
|
9132
|
+
}
|
|
9133
|
+
}
|
|
8945
9134
|
async function ensureOpenCodeRunning(ctx) {
|
|
8946
9135
|
const healthCheck = await checkOpenCodeHealth(ctx.port);
|
|
8947
9136
|
if (healthCheck.healthy) {
|
|
@@ -8989,6 +9178,7 @@ async function ensureOpenCodeRunning(ctx) {
|
|
|
8989
9178
|
}
|
|
8990
9179
|
}
|
|
8991
9180
|
if (!ctx.interactive) {
|
|
9181
|
+
checkNonInteractivePortConflict(ctx.port, isPortInUse);
|
|
8992
9182
|
ctx.log(`OpenCode is not running on port ${ctx.port}. Starting it automatically...`);
|
|
8993
9183
|
const proc = await startOpenCode(ctx.port, { inheritStdio: ctx.inheritStdio });
|
|
8994
9184
|
const health = await waitForOpenCodeHealth(ctx.port, ctx.startTimeoutMs);
|
|
@@ -9070,9 +9260,119 @@ Port ${port} is already in use.`));
|
|
|
9070
9260
|
return { port, process: null, version: null, notReadyReason: "you chose to continue without it" };
|
|
9071
9261
|
}
|
|
9072
9262
|
|
|
9263
|
+
// src/commands/ensure-opencode-v2.ts
|
|
9264
|
+
import chalk6 from "chalk";
|
|
9265
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
9266
|
+
async function probeOpenCode2WithoutPassword(port) {
|
|
9267
|
+
try {
|
|
9268
|
+
const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
|
|
9269
|
+
signal: AbortSignal.timeout(2e3)
|
|
9270
|
+
});
|
|
9271
|
+
if (response.status === 401) {
|
|
9272
|
+
return { healthy: false, authFailed: true, error: "HTTP 401" };
|
|
9273
|
+
}
|
|
9274
|
+
if (!response.ok) {
|
|
9275
|
+
return { healthy: false, error: `HTTP ${response.status}` };
|
|
9276
|
+
}
|
|
9277
|
+
return { healthy: true };
|
|
9278
|
+
} catch (error2) {
|
|
9279
|
+
return {
|
|
9280
|
+
healthy: false,
|
|
9281
|
+
error: error2 instanceof Error ? error2.message : "Unknown error"
|
|
9282
|
+
};
|
|
9283
|
+
}
|
|
9284
|
+
}
|
|
9285
|
+
function unknownPasswordError(port) {
|
|
9286
|
+
return new Error(
|
|
9287
|
+
`OpenCode V2 (opencode2) is already running on port ${port} with a password this runner doesn't know. Free the port or pass --port.`
|
|
9288
|
+
);
|
|
9289
|
+
}
|
|
9290
|
+
function v2SessionSupportIncompleteError() {
|
|
9291
|
+
return new Error(
|
|
9292
|
+
"OpenCode V2 (opencode2) session support is incomplete \u2014 see https://github.com/sroze/evident/issues/2075. Not starting a new opencode2 process."
|
|
9293
|
+
);
|
|
9294
|
+
}
|
|
9295
|
+
async function ensureOpenCode2Running(ctx) {
|
|
9296
|
+
const initialHealth = await probeOpenCode2WithoutPassword(ctx.port);
|
|
9297
|
+
if (initialHealth.authFailed) {
|
|
9298
|
+
throw unknownPasswordError(ctx.port);
|
|
9299
|
+
}
|
|
9300
|
+
if (initialHealth.healthy) {
|
|
9301
|
+
return {
|
|
9302
|
+
port: ctx.port,
|
|
9303
|
+
process: null,
|
|
9304
|
+
version: null,
|
|
9305
|
+
notReadyReason: null,
|
|
9306
|
+
password: null
|
|
9307
|
+
};
|
|
9308
|
+
}
|
|
9309
|
+
if (!isOpenCode2Installed()) {
|
|
9310
|
+
throw new Error(
|
|
9311
|
+
"OpenCode V2 (opencode2) is not installed. Install it with: npm install -g @opencode-ai/cli@beta"
|
|
9312
|
+
);
|
|
9313
|
+
}
|
|
9314
|
+
let port = ctx.port;
|
|
9315
|
+
if (!ctx.interactive) {
|
|
9316
|
+
checkNonInteractivePortConflict(port, isPortInUse);
|
|
9317
|
+
} else if (isPortInUse(port)) {
|
|
9318
|
+
console.log(chalk6.yellow(`
|
|
9319
|
+
Port ${port} is already in use.`));
|
|
9320
|
+
const alternativePort = findAvailablePort(port + 1);
|
|
9321
|
+
if (alternativePort) {
|
|
9322
|
+
const useAlternative = await select3({
|
|
9323
|
+
message: `Use port ${alternativePort} instead?`,
|
|
9324
|
+
choices: [
|
|
9325
|
+
{ name: `Yes, use port ${alternativePort}`, value: "yes" },
|
|
9326
|
+
{ name: "No, I will free the port manually", value: "no" }
|
|
9327
|
+
]
|
|
9328
|
+
});
|
|
9329
|
+
if (useAlternative === "yes") {
|
|
9330
|
+
port = alternativePort;
|
|
9331
|
+
} else {
|
|
9332
|
+
throw new Error(`Port ${ctx.port} is in use`);
|
|
9333
|
+
}
|
|
9334
|
+
}
|
|
9335
|
+
}
|
|
9336
|
+
if (!ctx.interactive) {
|
|
9337
|
+
throw v2SessionSupportIncompleteError();
|
|
9338
|
+
}
|
|
9339
|
+
console.log(chalk6.yellow(`
|
|
9340
|
+
${v2SessionSupportIncompleteError().message}`));
|
|
9341
|
+
const action = await select3({
|
|
9342
|
+
message: "OpenCode V2 is not running. What would you like to do?",
|
|
9343
|
+
choices: [
|
|
9344
|
+
{
|
|
9345
|
+
name: "Show me the command",
|
|
9346
|
+
value: "manual",
|
|
9347
|
+
description: "Display the command to run manually"
|
|
9348
|
+
},
|
|
9349
|
+
{
|
|
9350
|
+
name: "Continue without OpenCode V2",
|
|
9351
|
+
value: "continue",
|
|
9352
|
+
description: "Requests will fail until OpenCode V2 starts"
|
|
9353
|
+
}
|
|
9354
|
+
]
|
|
9355
|
+
});
|
|
9356
|
+
if (action === "manual") {
|
|
9357
|
+
blank();
|
|
9358
|
+
console.log(chalk6.bold("Run this command in another terminal:"));
|
|
9359
|
+
blank();
|
|
9360
|
+
console.log(` ${chalk6.cyan(`opencode2 serve --port ${port}`)}`);
|
|
9361
|
+
blank();
|
|
9362
|
+
throw new Error("Please start OpenCode V2 manually");
|
|
9363
|
+
}
|
|
9364
|
+
return {
|
|
9365
|
+
port,
|
|
9366
|
+
process: null,
|
|
9367
|
+
version: null,
|
|
9368
|
+
notReadyReason: "you chose to continue without OpenCode V2",
|
|
9369
|
+
password: null
|
|
9370
|
+
};
|
|
9371
|
+
}
|
|
9372
|
+
|
|
9073
9373
|
// src/lib/runner-credentials.ts
|
|
9074
|
-
import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
9075
|
-
import { spawn as spawn5 } from "child_process";
|
|
9374
|
+
import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9375
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
9076
9376
|
var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
|
|
9077
9377
|
var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
|
|
9078
9378
|
var GITHUB_PROBE_TIMEOUT_MS = 1e4;
|
|
@@ -9344,11 +9644,11 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
9344
9644
|
}
|
|
9345
9645
|
|
|
9346
9646
|
// 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
|
|
9647
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
9648
|
+
import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
|
|
9649
|
+
import { isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
|
|
9350
9650
|
function isFile(filePath) {
|
|
9351
|
-
return existsSync2(filePath) &&
|
|
9651
|
+
return existsSync2(filePath) && statSync6(filePath).isFile();
|
|
9352
9652
|
}
|
|
9353
9653
|
function applyRunnerOpenCodeConfig({
|
|
9354
9654
|
overlayPath,
|
|
@@ -9360,7 +9660,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
9360
9660
|
return;
|
|
9361
9661
|
}
|
|
9362
9662
|
const source = isAbsolute2(overlayPath) ? overlayPath : resolve3(cwd, overlayPath);
|
|
9363
|
-
const target = isFile(
|
|
9663
|
+
const target = isFile(join9(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
|
|
9364
9664
|
if (!isFile(source)) {
|
|
9365
9665
|
log3(
|
|
9366
9666
|
`RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
|
|
@@ -9368,7 +9668,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
9368
9668
|
);
|
|
9369
9669
|
return;
|
|
9370
9670
|
}
|
|
9371
|
-
copyFileSync(source,
|
|
9671
|
+
copyFileSync(source, join9(cwd, target));
|
|
9372
9672
|
try {
|
|
9373
9673
|
execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
|
|
9374
9674
|
stdio: "ignore"
|
|
@@ -9377,11 +9677,11 @@ function applyRunnerOpenCodeConfig({
|
|
|
9377
9677
|
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
9378
9678
|
log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
|
|
9379
9679
|
}
|
|
9380
|
-
log3(`Applied runner OpenCode config ${source} to ${
|
|
9680
|
+
log3(`Applied runner OpenCode config ${source} to ${join9(cwd, target)}`);
|
|
9381
9681
|
}
|
|
9382
9682
|
|
|
9383
9683
|
// src/lib/credential-sync.ts
|
|
9384
|
-
import { renameSync, writeFileSync as writeFileSync5 } from "fs";
|
|
9684
|
+
import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9385
9685
|
var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
|
|
9386
9686
|
var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
|
|
9387
9687
|
var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
|
|
@@ -9391,7 +9691,7 @@ var MAX_FLUSH_PASSES = 2;
|
|
|
9391
9691
|
function outcomesWith(outcome) {
|
|
9392
9692
|
return { claude: outcome, opencode: outcome };
|
|
9393
9693
|
}
|
|
9394
|
-
function
|
|
9694
|
+
function errorMessage2(error2) {
|
|
9395
9695
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
9396
9696
|
}
|
|
9397
9697
|
function waitForSettlement(promise, timeoutMs) {
|
|
@@ -9418,7 +9718,7 @@ function writeMarker(markerPath, outcomes, log3) {
|
|
|
9418
9718
|
writeFileSync5(temporaryPath, body, { mode: 384 });
|
|
9419
9719
|
renameSync(temporaryPath, markerPath);
|
|
9420
9720
|
} catch (error2) {
|
|
9421
|
-
log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${
|
|
9721
|
+
log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
|
|
9422
9722
|
}
|
|
9423
9723
|
}
|
|
9424
9724
|
function intervalSeconds(env, log3) {
|
|
@@ -9450,7 +9750,7 @@ async function runFlushStore(store, env, synchroniserRunner, log3, deadlineAt) {
|
|
|
9450
9750
|
},
|
|
9451
9751
|
(error2) => {
|
|
9452
9752
|
failed = true;
|
|
9453
|
-
log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${
|
|
9753
|
+
log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage2(error2)}`, "warn");
|
|
9454
9754
|
}
|
|
9455
9755
|
);
|
|
9456
9756
|
const abortTimer = setTimeout(() => controller.abort(), remainingMs);
|
|
@@ -9510,7 +9810,7 @@ function createCredentialSync({
|
|
|
9510
9810
|
outcomes[store] = !result.timedOut && result.code === 0 ? "ok" : "failed";
|
|
9511
9811
|
} catch (error2) {
|
|
9512
9812
|
outcomes[store] = "failed";
|
|
9513
|
-
log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${
|
|
9813
|
+
log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage2(error2)}`, "debug");
|
|
9514
9814
|
}
|
|
9515
9815
|
}
|
|
9516
9816
|
const failed = STORES.some((store) => outcomes[store] === "failed");
|
|
@@ -9652,7 +9952,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
9652
9952
|
if (trimmed === "") {
|
|
9653
9953
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
9654
9954
|
}
|
|
9655
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
9955
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join10(homeDir, trimmed.slice(2)) : trimmed;
|
|
9656
9956
|
if (!isAbsolute3(expanded)) {
|
|
9657
9957
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
9658
9958
|
}
|
|
@@ -9676,6 +9976,28 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
9676
9976
|
var DEFAULT_OPENCODE_START_TIMEOUT_SECONDS = 180;
|
|
9677
9977
|
var MAX_OPENCODE_START_TIMEOUT_SECONDS = 3600;
|
|
9678
9978
|
var OPENCODE_START_TIMEOUT_ENV = "EVIDENT_OPENCODE_START_TIMEOUT";
|
|
9979
|
+
var OPENCODE_VERSION_ENV = "EVIDENT_OPENCODE_VERSION";
|
|
9980
|
+
function resolveOpenCodeVersion(options, env = process.env) {
|
|
9981
|
+
let raw;
|
|
9982
|
+
let source;
|
|
9983
|
+
if (options.opencodeVersion !== void 0) {
|
|
9984
|
+
raw = options.opencodeVersion;
|
|
9985
|
+
source = "--opencode-version";
|
|
9986
|
+
} else if (env[OPENCODE_VERSION_ENV] !== void 0 && env[OPENCODE_VERSION_ENV] !== "") {
|
|
9987
|
+
raw = env[OPENCODE_VERSION_ENV];
|
|
9988
|
+
source = OPENCODE_VERSION_ENV;
|
|
9989
|
+
} else {
|
|
9990
|
+
return { version: "v1", warnings: [] };
|
|
9991
|
+
}
|
|
9992
|
+
const normalized = raw.trim().toLowerCase();
|
|
9993
|
+
if (normalized !== "v1" && normalized !== "v2") {
|
|
9994
|
+
return {
|
|
9995
|
+
version: "v1",
|
|
9996
|
+
warnings: [`Ignoring invalid ${source} "${raw}": expected v1 or v2; using the default v1`]
|
|
9997
|
+
};
|
|
9998
|
+
}
|
|
9999
|
+
return { version: normalized, warnings: [] };
|
|
10000
|
+
}
|
|
9679
10001
|
function resolveOpenCodeStartTimeoutMs(options, env = process.env) {
|
|
9680
10002
|
const defaultMs = DEFAULT_OPENCODE_START_TIMEOUT_SECONDS * 1e3;
|
|
9681
10003
|
let raw;
|
|
@@ -9742,7 +10064,7 @@ function log2(state, message, level = "info") {
|
|
|
9742
10064
|
})
|
|
9743
10065
|
);
|
|
9744
10066
|
} else if (!state.interactive) {
|
|
9745
|
-
const prefix = level === "error" ?
|
|
10067
|
+
const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
|
|
9746
10068
|
console.log(`${prefix} ${message}`);
|
|
9747
10069
|
}
|
|
9748
10070
|
}
|
|
@@ -9772,7 +10094,7 @@ function logActivity(state, entry) {
|
|
|
9772
10094
|
}
|
|
9773
10095
|
function reportSessionDbRecovery(state) {
|
|
9774
10096
|
try {
|
|
9775
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
10097
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir6(), env: process.env });
|
|
9776
10098
|
for (const record of report.records) {
|
|
9777
10099
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
9778
10100
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -9803,18 +10125,18 @@ function reportSessionDbRecoveryRecord(state, record) {
|
|
|
9803
10125
|
function displayStatus(state) {
|
|
9804
10126
|
if (!state.interactive) return;
|
|
9805
10127
|
const attempt = state.connection?.reconnectAttempt ?? 0;
|
|
9806
|
-
const tunnel = state.connected ?
|
|
9807
|
-
const opencode = state.opencodeConnected ?
|
|
9808
|
-
const messages = state.messageCount > 0 ?
|
|
10128
|
+
const tunnel = state.connected ? chalk7.green("tunnel: connected") : attempt > 0 ? chalk7.yellow(`tunnel: reconnecting (#${attempt})`) : chalk7.yellow("tunnel: connecting");
|
|
10129
|
+
const opencode = state.opencodeConnected ? chalk7.green(`opencode: :${state.port}`) : chalk7.red(`opencode: :${state.port} (down)`);
|
|
10130
|
+
const messages = state.messageCount > 0 ? chalk7.dim(` \xB7 ${state.messageCount} processed`) : "";
|
|
9809
10131
|
const last = state.activityLog[state.activityLog.length - 1];
|
|
9810
|
-
const detail = last ?
|
|
10132
|
+
const detail = last ? chalk7.dim(` \xB7 ${last.type === "error" ? last.error ?? "" : last.message ?? ""}`) : "";
|
|
9811
10133
|
const agent = state.agentName ?? state.agentId;
|
|
9812
10134
|
console.log(
|
|
9813
|
-
`${
|
|
10135
|
+
`${chalk7.bold("Evident")} ${chalk7.dim(agent)} ${tunnel} ${opencode}${messages}${detail}`
|
|
9814
10136
|
);
|
|
9815
10137
|
}
|
|
9816
10138
|
async function promptForLogin(promptMessage, successMessage) {
|
|
9817
|
-
const action = await
|
|
10139
|
+
const action = await select4({
|
|
9818
10140
|
message: promptMessage,
|
|
9819
10141
|
choices: [
|
|
9820
10142
|
{
|
|
@@ -9830,7 +10152,7 @@ async function promptForLogin(promptMessage, successMessage) {
|
|
|
9830
10152
|
]
|
|
9831
10153
|
});
|
|
9832
10154
|
if (action === "exit") {
|
|
9833
|
-
console.log(
|
|
10155
|
+
console.log(chalk7.dim(`
|
|
9834
10156
|
You can log in later by running: ${getCliName()} login`));
|
|
9835
10157
|
process.exit(0);
|
|
9836
10158
|
}
|
|
@@ -9841,7 +10163,7 @@ You can log in later by running: ${getCliName()} login`));
|
|
|
9841
10163
|
process.exit(1);
|
|
9842
10164
|
}
|
|
9843
10165
|
blank();
|
|
9844
|
-
console.log(
|
|
10166
|
+
console.log(chalk7.green(successMessage));
|
|
9845
10167
|
blank();
|
|
9846
10168
|
return { token: credentials2.token, authType: "bearer", user: credentials2.user };
|
|
9847
10169
|
}
|
|
@@ -9854,12 +10176,12 @@ async function handleAuthError(state, error2) {
|
|
|
9854
10176
|
if (state.interactive) displayStatus(state);
|
|
9855
10177
|
if (!state.interactive) {
|
|
9856
10178
|
blank();
|
|
9857
|
-
console.log(
|
|
9858
|
-
console.log(
|
|
10179
|
+
console.log(chalk7.red("Authentication expired"));
|
|
10180
|
+
console.log(chalk7.dim("Your authentication token is no longer valid."));
|
|
9859
10181
|
blank();
|
|
9860
|
-
console.log(
|
|
9861
|
-
console.log(
|
|
9862
|
-
console.log(
|
|
10182
|
+
console.log(chalk7.dim("To fix this:"));
|
|
10183
|
+
console.log(chalk7.dim(` 1. Run '${getCliName()} login' to re-authenticate`));
|
|
10184
|
+
console.log(chalk7.dim(" 2. Restart this command"));
|
|
9863
10185
|
blank();
|
|
9864
10186
|
await cleanup(state);
|
|
9865
10187
|
await shutdownTelemetry();
|
|
@@ -9867,7 +10189,7 @@ async function handleAuthError(state, error2) {
|
|
|
9867
10189
|
return { success: false };
|
|
9868
10190
|
}
|
|
9869
10191
|
blank();
|
|
9870
|
-
console.log(
|
|
10192
|
+
console.log(chalk7.yellow("Your authentication has expired."));
|
|
9871
10193
|
blank();
|
|
9872
10194
|
try {
|
|
9873
10195
|
const credentials2 = await promptForLogin(
|
|
@@ -9931,6 +10253,14 @@ async function driveChannels(state, driver) {
|
|
|
9931
10253
|
const opencodeAuthApplied = opencodeAuthApplies !== lastSeenOpencodeAuthApplies;
|
|
9932
10254
|
lastSeenOpencodeAuthApplies = opencodeAuthApplies;
|
|
9933
10255
|
if (opencodeAuthApplied) state.openaiUsageRearm?.();
|
|
10256
|
+
if (claudeCredentialApplied || opencodeAuthApplied) {
|
|
10257
|
+
void reloadProviderCache(state.port).catch(
|
|
10258
|
+
(error2) => logActivity(state, {
|
|
10259
|
+
type: "error",
|
|
10260
|
+
error: `OpenCode provider cache reload failed on port ${state.port}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
10261
|
+
})
|
|
10262
|
+
);
|
|
10263
|
+
}
|
|
9934
10264
|
if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
|
|
9935
10265
|
idlePolls = 0;
|
|
9936
10266
|
idleMs = 0;
|
|
@@ -9958,8 +10288,8 @@ async function driveChannels(state, driver) {
|
|
|
9958
10288
|
state.running = false;
|
|
9959
10289
|
break;
|
|
9960
10290
|
}
|
|
9961
|
-
const
|
|
9962
|
-
logActivity(state, { type: "error", error: `Channel processing error: ${
|
|
10291
|
+
const errorMessage3 = error2 instanceof Error ? error2.message : String(error2);
|
|
10292
|
+
logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage3}` });
|
|
9963
10293
|
if (state.interactive) displayStatus(state);
|
|
9964
10294
|
if (driver.hasInFlightWatchers()) {
|
|
9965
10295
|
consecutiveDrainFailures = 0;
|
|
@@ -9997,9 +10327,18 @@ async function driveChannels(state, driver) {
|
|
|
9997
10327
|
}
|
|
9998
10328
|
}
|
|
9999
10329
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
10000
|
-
var SESSION_DB_RECLAIM_MAX_PAGES =
|
|
10330
|
+
var SESSION_DB_RECLAIM_MAX_PAGES = 2e4;
|
|
10331
|
+
function shouldWarnForReclaimSkip(reason) {
|
|
10332
|
+
if (reason !== "sqlite-unavailable") return false;
|
|
10333
|
+
const version2 = /^v(\d+)\.(\d+)\.(\d+)$/.exec(process.version);
|
|
10334
|
+
if (!version2) return false;
|
|
10335
|
+
const major = Number(version2[1]);
|
|
10336
|
+
const minor = Number(version2[2]);
|
|
10337
|
+
const patch = Number(version2[3]);
|
|
10338
|
+
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
10339
|
+
}
|
|
10001
10340
|
function sessionDbPath() {
|
|
10002
|
-
return
|
|
10341
|
+
return join10(homedir6(), ".local", "share", "opencode", "opencode.db");
|
|
10003
10342
|
}
|
|
10004
10343
|
function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
|
|
10005
10344
|
const record = {
|
|
@@ -10095,7 +10434,7 @@ async function runSweep(state, driver, config) {
|
|
|
10095
10434
|
} else {
|
|
10096
10435
|
logActivity(state, {
|
|
10097
10436
|
type: "info",
|
|
10098
|
-
message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})`
|
|
10437
|
+
message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})` + (reclaimResult.detail ? `: ${reclaimResult.detail}` : "")
|
|
10099
10438
|
});
|
|
10100
10439
|
}
|
|
10101
10440
|
} catch (error2) {
|
|
@@ -10118,13 +10457,20 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
10118
10457
|
for (const warning2 of config.warnings) {
|
|
10119
10458
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
10120
10459
|
}
|
|
10121
|
-
const dbBytes = statSessionDbBytes(
|
|
10460
|
+
const dbBytes = statSessionDbBytes(homedir6());
|
|
10122
10461
|
void (async () => {
|
|
10123
|
-
const
|
|
10462
|
+
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
10463
|
+
if (reclaimAvailability !== null) {
|
|
10464
|
+
logActivity(state, {
|
|
10465
|
+
type: "info",
|
|
10466
|
+
level: shouldWarnForReclaimSkip(reclaimAvailability.reason) ? "warn" : "info",
|
|
10467
|
+
message: `Session cleanup: reclaim preflight skipped (${reclaimAvailability.reason})` + (reclaimAvailability.detail ? `: ${reclaimAvailability.detail}` : "")
|
|
10468
|
+
});
|
|
10469
|
+
}
|
|
10124
10470
|
const sizeWarning = buildSessionStoreSizeWarning({
|
|
10125
10471
|
dbBytes,
|
|
10126
10472
|
cleanupEnabled: config.enabled,
|
|
10127
|
-
reclaimSkipReason
|
|
10473
|
+
reclaimSkipReason: reclaimAvailability?.reason ?? null
|
|
10128
10474
|
});
|
|
10129
10475
|
if (sizeWarning !== null) {
|
|
10130
10476
|
logActivity(state, { type: "info", level: "warn", message: sizeWarning });
|
|
@@ -10319,7 +10665,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
10319
10665
|
});
|
|
10320
10666
|
return;
|
|
10321
10667
|
}
|
|
10322
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
10668
|
+
const { collect, stop } = createResourceUsageCollector(homedir6());
|
|
10323
10669
|
state.stopResourceUsageSampling = stop;
|
|
10324
10670
|
let consecutiveFailures = 0;
|
|
10325
10671
|
const tick = async () => {
|
|
@@ -10416,6 +10762,8 @@ async function cleanup(state, opts = {}) {
|
|
|
10416
10762
|
clearTimeout(timer);
|
|
10417
10763
|
}
|
|
10418
10764
|
state.sessionCleanupTimers = [];
|
|
10765
|
+
state.stopOpenCodeLogTail?.();
|
|
10766
|
+
state.stopOpenCodeLogTail = null;
|
|
10419
10767
|
if (state.claudeUsageTimer) {
|
|
10420
10768
|
clearTimeout(state.claudeUsageTimer);
|
|
10421
10769
|
state.claudeUsageTimer = null;
|
|
@@ -10554,7 +10902,7 @@ async function run(options) {
|
|
|
10554
10902
|
let fileSyncDirectories;
|
|
10555
10903
|
try {
|
|
10556
10904
|
logLevel = resolveLogLevel(options);
|
|
10557
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
10905
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir6());
|
|
10558
10906
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
10559
10907
|
throw new Error(
|
|
10560
10908
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -10585,6 +10933,7 @@ async function run(options) {
|
|
|
10585
10933
|
opencodeVersion: null,
|
|
10586
10934
|
sessionDbProvenanceAnomaly: false,
|
|
10587
10935
|
opencodeProcess: null,
|
|
10936
|
+
stopOpenCodeLogTail: null,
|
|
10588
10937
|
litestreamProcess: null,
|
|
10589
10938
|
connection: null,
|
|
10590
10939
|
channelDriver: null,
|
|
@@ -10652,15 +11001,15 @@ async function run(options) {
|
|
|
10652
11001
|
printError("Authentication required");
|
|
10653
11002
|
blank();
|
|
10654
11003
|
console.log(
|
|
10655
|
-
|
|
11004
|
+
chalk7.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
|
|
10656
11005
|
);
|
|
10657
|
-
console.log(
|
|
11006
|
+
console.log(chalk7.dim("Or run `evident login` for interactive authentication"));
|
|
10658
11007
|
blank();
|
|
10659
11008
|
process.exit(1);
|
|
10660
11009
|
return;
|
|
10661
11010
|
}
|
|
10662
11011
|
blank();
|
|
10663
|
-
console.log(
|
|
11012
|
+
console.log(chalk7.yellow("You are not logged in to Evident."));
|
|
10664
11013
|
blank();
|
|
10665
11014
|
credentials2 = await promptForLogin(
|
|
10666
11015
|
"Would you like to log in now?",
|
|
@@ -10710,7 +11059,7 @@ async function run(options) {
|
|
|
10710
11059
|
);
|
|
10711
11060
|
blank();
|
|
10712
11061
|
console.log(
|
|
10713
|
-
|
|
11062
|
+
chalk7.dim(
|
|
10714
11063
|
"Either provide --runner/--agent <id> or set EVIDENT_RUNNER_KEY/EVIDENT_AGENT_KEY"
|
|
10715
11064
|
)
|
|
10716
11065
|
);
|
|
@@ -10733,15 +11082,15 @@ async function run(options) {
|
|
|
10733
11082
|
);
|
|
10734
11083
|
if (interactive && !state.json) {
|
|
10735
11084
|
blank();
|
|
10736
|
-
console.log(
|
|
10737
|
-
console.log(
|
|
11085
|
+
console.log(chalk7.bold("Evident Run"));
|
|
11086
|
+
console.log(chalk7.dim("-".repeat(40)));
|
|
10738
11087
|
}
|
|
10739
11088
|
const spinner = interactive && !state.json ? ora3("Validating runner...").start() : null;
|
|
10740
11089
|
let validation = await getAgentInfo(state.agentId, state.authHeader);
|
|
10741
11090
|
if (!validation.valid && validation.authFailed && interactive) {
|
|
10742
11091
|
spinner?.fail("Authentication failed");
|
|
10743
11092
|
blank();
|
|
10744
|
-
console.log(
|
|
11093
|
+
console.log(chalk7.yellow("Your authentication token is invalid or expired."));
|
|
10745
11094
|
blank();
|
|
10746
11095
|
credentials2 = await promptForLogin(
|
|
10747
11096
|
"Would you like to log in again?",
|
|
@@ -10789,6 +11138,13 @@ async function run(options) {
|
|
|
10789
11138
|
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
10790
11139
|
}
|
|
10791
11140
|
state.credentialSync?.arm();
|
|
11141
|
+
state.stopOpenCodeLogTail = tailOpenCodeLogFile(
|
|
11142
|
+
resolveOpenCodeLogPath(homedir6(), process.env),
|
|
11143
|
+
createOpenCodeActivityForwarder(() => ({
|
|
11144
|
+
agentId: state.agentId,
|
|
11145
|
+
authHeader: state.authHeader
|
|
11146
|
+
}))
|
|
11147
|
+
).stop;
|
|
10792
11148
|
let sessionDbVerifyFatal = false;
|
|
10793
11149
|
if (!options.restoreSessionDb) {
|
|
10794
11150
|
log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
|
|
@@ -10838,6 +11194,13 @@ async function run(options) {
|
|
|
10838
11194
|
for (const warning2 of opencodeStartTimeoutWarnings) {
|
|
10839
11195
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
10840
11196
|
}
|
|
11197
|
+
const { version: opencodeVersion, warnings: opencodeVersionWarnings } = resolveOpenCodeVersion(
|
|
11198
|
+
options,
|
|
11199
|
+
process.env
|
|
11200
|
+
);
|
|
11201
|
+
for (const warning2 of opencodeVersionWarnings) {
|
|
11202
|
+
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
11203
|
+
}
|
|
10841
11204
|
const { value: maxActiveSessions, warnings: maxActiveSessionsWarnings } = resolveMaxActiveSessions(options, process.env);
|
|
10842
11205
|
for (const warning2 of maxActiveSessionsWarnings) {
|
|
10843
11206
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
@@ -10845,7 +11208,14 @@ async function run(options) {
|
|
|
10845
11208
|
const preBootMigrationIds = readSessionDbMigrationIds(sessionDbPath());
|
|
10846
11209
|
const ocSpinner = interactive && !state.json ? ora3("Checking OpenCode...").start() : null;
|
|
10847
11210
|
try {
|
|
10848
|
-
const oc = await
|
|
11211
|
+
const oc = opencodeVersion === "v2" ? await ensureOpenCode2Running({
|
|
11212
|
+
port: state.port,
|
|
11213
|
+
interactive: state.interactive,
|
|
11214
|
+
agentId: state.agentId,
|
|
11215
|
+
log: (message) => log2(state, message),
|
|
11216
|
+
startTimeoutMs: opencodeStartTimeoutMs,
|
|
11217
|
+
inheritStdio: Boolean(options.opencodePidFile)
|
|
11218
|
+
}) : await ensureOpenCodeRunning({
|
|
10849
11219
|
port: state.port,
|
|
10850
11220
|
interactive: state.interactive,
|
|
10851
11221
|
agentId: state.agentId,
|
|
@@ -10872,7 +11242,7 @@ async function run(options) {
|
|
|
10872
11242
|
const provenance = checkSessionDbProvenance({
|
|
10873
11243
|
dbPath: sessionDbPath(),
|
|
10874
11244
|
currentVersion: state.opencodeVersion,
|
|
10875
|
-
homeDir:
|
|
11245
|
+
homeDir: homedir6(),
|
|
10876
11246
|
env: process.env
|
|
10877
11247
|
});
|
|
10878
11248
|
if (provenance.anomaly) {
|
|
@@ -10907,10 +11277,10 @@ async function run(options) {
|
|
|
10907
11277
|
if (state.interactive && !state.json) {
|
|
10908
11278
|
logActivity(state, { type: "info", level: "warn", message: noProviderWarning });
|
|
10909
11279
|
blank();
|
|
10910
|
-
console.log(
|
|
11280
|
+
console.log(chalk7.yellow("\u26A0 No OpenCode model provider is configured."));
|
|
10911
11281
|
console.log(
|
|
10912
|
-
|
|
10913
|
-
`Run ${
|
|
11282
|
+
chalk7.dim(
|
|
11283
|
+
`Run ${chalk7.cyan("opencode auth login")} to set one up \u2014 messages will fail until then.`
|
|
10914
11284
|
)
|
|
10915
11285
|
);
|
|
10916
11286
|
blank();
|
|
@@ -11034,7 +11404,7 @@ async function run(options) {
|
|
|
11034
11404
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
11035
11405
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
11036
11406
|
fileSyncDirectories,
|
|
11037
|
-
homeDir:
|
|
11407
|
+
homeDir: homedir6(),
|
|
11038
11408
|
maxActiveSessions,
|
|
11039
11409
|
log: (entry) => (
|
|
11040
11410
|
// Thread the driver's real level straight through so `debug`/`warn`
|
|
@@ -11276,6 +11646,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
11276
11646
|
).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
11647
|
"--opencode-start-timeout <seconds>",
|
|
11278
11648
|
"Seconds to wait for OpenCode to become healthy when the runner starts it (default: 180). Env: EVIDENT_OPENCODE_START_TIMEOUT"
|
|
11649
|
+
).option(
|
|
11650
|
+
"--opencode-version <v1|v2>",
|
|
11651
|
+
"Which OpenCode major version to launch: v1 or v2 (default: v1). Env: EVIDENT_OPENCODE_VERSION"
|
|
11279
11652
|
).option("--json", "Output in JSON format").option(
|
|
11280
11653
|
"--session-cleanup-max-age <duration>",
|
|
11281
11654
|
"Delete OpenCode sessions idle longer than this (e.g. 7d, 24h). Enables cleanup. Env: EVIDENT_SESSION_CLEANUP_MAX_AGE"
|
|
@@ -11344,6 +11717,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
11344
11717
|
// Raw string — validation/precedence is single-sourced in run.ts's
|
|
11345
11718
|
// resolveOpenCodeStartTimeoutMs (flag > EVIDENT_OPENCODE_START_TIMEOUT > 180s default).
|
|
11346
11719
|
opencodeStartTimeout: options.opencodeStartTimeout,
|
|
11720
|
+
opencodeVersion: options.opencodeVersion,
|
|
11347
11721
|
json: options.json,
|
|
11348
11722
|
// Raw strings — the resolver in run.ts single-sources parsing (M1).
|
|
11349
11723
|
sessionCleanupMaxAge: options.sessionCleanupMaxAge,
|