@evident-ai/cli 3.4.1-dev.dbc0a1f → 3.4.1-dev.fb713ba
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 +3 -0
- package/dist/index.js +241 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -221,6 +221,9 @@ targets the **production** Evident platform by default.
|
|
|
221
221
|
flag wins if both are set.
|
|
222
222
|
- `EVIDENT_SESSION_CLEANUP_INTERVAL` — Equivalent to
|
|
223
223
|
`--session-cleanup-interval`; the flag wins if both are set.
|
|
224
|
+
- `EVIDENT_SESSION_DB_PROVENANCE_STATE` — Override the path where the CLI stores
|
|
225
|
+
session-database provenance state (default:
|
|
226
|
+
`~/.local/state/evident/session-db-provenance.json`).
|
|
224
227
|
|
|
225
228
|
Authentication precedence for `run`: `EVIDENT_RUNNER_KEY`/`EVIDENT_AGENT_KEY`
|
|
226
229
|
(tied; `EVIDENT_RUNNER_KEY` wins if both are set) → `EVIDENT_TOKEN` →
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { createRequire } from "module";
|
|
4
|
+
import { createRequire as createRequire2 } from "module";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/login.ts
|
|
@@ -1183,9 +1183,9 @@ async function claudeUsage() {
|
|
|
1183
1183
|
}
|
|
1184
1184
|
|
|
1185
1185
|
// src/commands/run.ts
|
|
1186
|
-
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as
|
|
1186
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1187
1187
|
import { homedir as homedir5 } from "os";
|
|
1188
|
-
import { isAbsolute as isAbsolute3, join as
|
|
1188
|
+
import { isAbsolute as isAbsolute3, join as join9, parse, resolve as resolvePath2 } from "path";
|
|
1189
1189
|
import chalk6 from "chalk";
|
|
1190
1190
|
|
|
1191
1191
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -1525,7 +1525,14 @@ function drainSessionDbRecoveryReport({
|
|
|
1525
1525
|
skippedLines++;
|
|
1526
1526
|
return [];
|
|
1527
1527
|
}
|
|
1528
|
-
return [
|
|
1528
|
+
return [
|
|
1529
|
+
{
|
|
1530
|
+
...value,
|
|
1531
|
+
provenance_reason: value.provenance_reason ?? null,
|
|
1532
|
+
provenance_migration_delta: value.provenance_migration_delta ?? null,
|
|
1533
|
+
replication_suspended: value.replication_suspended ?? false
|
|
1534
|
+
}
|
|
1535
|
+
];
|
|
1529
1536
|
} catch (error2) {
|
|
1530
1537
|
skippedLines++;
|
|
1531
1538
|
console.error(
|
|
@@ -1628,6 +1635,12 @@ function buildSessionDbRecoveryActivity(record) {
|
|
|
1628
1635
|
metadata: withoutContractFields(record),
|
|
1629
1636
|
message: `This runner did not come online because its damaged session database could not be safely separated from its active backup or proven removed. Backed-up session history remains readable at ${record.quarantine_destination ?? "its original location or the quarantine destination named in the boot logs"}; any local session-database files that remain were left in place and nothing opened or wrote them. See the runner boot logs for SESSION-DB-LOCAL-DISCARD-FAILED details.`
|
|
1630
1637
|
};
|
|
1638
|
+
case "schema_provenance_mismatch":
|
|
1639
|
+
return {
|
|
1640
|
+
level,
|
|
1641
|
+
metadata: withoutContractFields(record),
|
|
1642
|
+
message: `Session database schema provenance mismatch for ${record.dbPath ?? record.db_path ?? "unknown"}: recorded version=${record.recorded_version ?? "unknown"}, current version=${record.current_version ?? "unknown"}, reason=${record.provenance_reason ?? "unknown"}, migration delta=${record.provenance_migration_delta ?? "unknown"}. Inspect the session database and runner backup before continuing.`
|
|
1643
|
+
};
|
|
1631
1644
|
default:
|
|
1632
1645
|
return null;
|
|
1633
1646
|
}
|
|
@@ -1642,7 +1655,8 @@ var OUTCOMES = /* @__PURE__ */ new Set([
|
|
|
1642
1655
|
"fresh_session_db",
|
|
1643
1656
|
"history_rolled_back",
|
|
1644
1657
|
"restore_misconfigured",
|
|
1645
|
-
"session_db_boot_refused"
|
|
1658
|
+
"session_db_boot_refused",
|
|
1659
|
+
"schema_provenance_mismatch"
|
|
1646
1660
|
]);
|
|
1647
1661
|
var STAGES = /* @__PURE__ */ new Set(["restore", "verify"]);
|
|
1648
1662
|
var SEVERITIES = /* @__PURE__ */ new Set(["warning", "error"]);
|
|
@@ -1660,7 +1674,7 @@ var STRING_OR_NULL_FIELDS = ["quarantine_destination", "verified_restore_point"]
|
|
|
1660
1674
|
function isSessionDbRecoveryRecord(value) {
|
|
1661
1675
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1662
1676
|
const record = value;
|
|
1663
|
-
return record.v === 1 && record.event === "session_db_recovery" && typeof record.at === "string" && STAGES.has(record.stage) && OUTCOMES.has(record.outcome) && SEVERITIES.has(record.severity) && typeof record.reason === "string" && (record.replication_suspended === void 0 || typeof record.replication_suspended === "boolean") && NUMBER_FIELDS.every((field) => record[field] === null || Number.isInteger(record[field])) && STRING_OR_NULL_FIELDS.every(
|
|
1677
|
+
return record.v === 1 && record.event === "session_db_recovery" && typeof record.at === "string" && STAGES.has(record.stage) && OUTCOMES.has(record.outcome) && SEVERITIES.has(record.severity) && typeof record.reason === "string" && (record.replication_suspended === void 0 || typeof record.replication_suspended === "boolean") && (record.provenance_reason === void 0 || record.provenance_reason === null || typeof record.provenance_reason === "string") && (record.provenance_migration_delta === void 0 || record.provenance_migration_delta === null || Number.isInteger(record.provenance_migration_delta)) && NUMBER_FIELDS.every((field) => record[field] === null || Number.isInteger(record[field])) && STRING_OR_NULL_FIELDS.every(
|
|
1664
1678
|
(field) => record[field] === null || typeof record[field] === "string"
|
|
1665
1679
|
);
|
|
1666
1680
|
}
|
|
@@ -1780,6 +1794,8 @@ function reportRecord(stage, outcome, reason, litestreamExitCode, options) {
|
|
|
1780
1794
|
quarantined_bytes: null,
|
|
1781
1795
|
verified_restore_point: null,
|
|
1782
1796
|
restore_points_tried: null,
|
|
1797
|
+
provenance_reason: null,
|
|
1798
|
+
provenance_migration_delta: null,
|
|
1783
1799
|
replication_suspended: stage === "restore"
|
|
1784
1800
|
});
|
|
1785
1801
|
}
|
|
@@ -2200,6 +2216,131 @@ async function restoreAndVerifySessionDb(options) {
|
|
|
2200
2216
|
return { verifyFatal: await verifySessionDb(options, configPath, env) };
|
|
2201
2217
|
}
|
|
2202
2218
|
|
|
2219
|
+
// src/lib/opencode/session-db-provenance.ts
|
|
2220
|
+
import { createRequire } from "module";
|
|
2221
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2222
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
2223
|
+
var require2 = createRequire(import.meta.url);
|
|
2224
|
+
function readSessionDbMigrationIds(dbPath) {
|
|
2225
|
+
let db;
|
|
2226
|
+
try {
|
|
2227
|
+
const { DatabaseSync } = require2("node:sqlite");
|
|
2228
|
+
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
2229
|
+
const columns = db.prepare("PRAGMA table_info(migration)").all();
|
|
2230
|
+
const hasExpectedShape = columns.length === 2 && columns.some(
|
|
2231
|
+
(column) => column.name === "id" && typeof column.type === "string" && column.type.toUpperCase() === "TEXT" && column.pk === 1
|
|
2232
|
+
) && columns.some(
|
|
2233
|
+
(column) => column.name === "time_completed" && typeof column.type === "string" && column.type.toUpperCase() === "INTEGER" && column.notnull === 1 && column.pk === 0
|
|
2234
|
+
);
|
|
2235
|
+
if (!hasExpectedShape) {
|
|
2236
|
+
console.warn(`[readSessionDbMigrationIds] unsupported migration table shape in ${dbPath}`);
|
|
2237
|
+
return null;
|
|
2238
|
+
}
|
|
2239
|
+
const rows = db.prepare("SELECT id FROM migration ORDER BY id").all();
|
|
2240
|
+
if (rows.some((row) => typeof row.id !== "string")) return null;
|
|
2241
|
+
return rows.map((row) => row.id);
|
|
2242
|
+
} catch (error2) {
|
|
2243
|
+
console.warn(
|
|
2244
|
+
`[readSessionDbMigrationIds] could not read migration history from ${dbPath}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
2245
|
+
);
|
|
2246
|
+
return null;
|
|
2247
|
+
} finally {
|
|
2248
|
+
try {
|
|
2249
|
+
db?.close();
|
|
2250
|
+
} catch (error2) {
|
|
2251
|
+
console.warn(
|
|
2252
|
+
`[readSessionDbMigrationIds] could not close ${dbPath}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
function sessionDbProvenanceStatePath(homeDir, env) {
|
|
2258
|
+
const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
|
|
2259
|
+
return override || join3(homeDir, ".local", "state", "evident", "session-db-provenance.json");
|
|
2260
|
+
}
|
|
2261
|
+
function loadSessionDbProvenanceState(path) {
|
|
2262
|
+
let value;
|
|
2263
|
+
try {
|
|
2264
|
+
value = JSON.parse(readFileSync3(path, "utf8"));
|
|
2265
|
+
} catch (error2) {
|
|
2266
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return {};
|
|
2267
|
+
console.error(
|
|
2268
|
+
`[session-db-provenance] could not read ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
2269
|
+
);
|
|
2270
|
+
return {};
|
|
2271
|
+
}
|
|
2272
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2273
|
+
console.error(`[session-db-provenance] ignored malformed state in ${path}`);
|
|
2274
|
+
return {};
|
|
2275
|
+
}
|
|
2276
|
+
const state = {};
|
|
2277
|
+
for (const [dbPath, record] of Object.entries(value)) {
|
|
2278
|
+
if (!isSessionDbProvenanceRecord(record)) {
|
|
2279
|
+
console.error(`[session-db-provenance] ignored malformed state in ${path}`);
|
|
2280
|
+
return {};
|
|
2281
|
+
}
|
|
2282
|
+
state[dbPath] = record;
|
|
2283
|
+
}
|
|
2284
|
+
return state;
|
|
2285
|
+
}
|
|
2286
|
+
function saveSessionDbProvenanceState(path, state) {
|
|
2287
|
+
try {
|
|
2288
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
2289
|
+
writeFileSync2(path, `${JSON.stringify(state, null, 2)}
|
|
2290
|
+
`, "utf8");
|
|
2291
|
+
} catch (error2) {
|
|
2292
|
+
console.error(
|
|
2293
|
+
`[session-db-provenance] could not write ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
function evaluateSessionDbProvenance(input) {
|
|
2298
|
+
const { currentVersion, currentIds, previous } = input;
|
|
2299
|
+
if (!previous) return { anomaly: false, reason: null };
|
|
2300
|
+
const current = new Set(currentIds);
|
|
2301
|
+
const prior = new Set(previous.migrationIds);
|
|
2302
|
+
for (const id of prior) {
|
|
2303
|
+
if (!current.has(id)) return { anomaly: true, reason: "migration-history-regressed" };
|
|
2304
|
+
}
|
|
2305
|
+
if (current.size > prior.size && previous.opencodeVersion === currentVersion) {
|
|
2306
|
+
return { anomaly: true, reason: "foreign-version-migrations" };
|
|
2307
|
+
}
|
|
2308
|
+
return { anomaly: false, reason: null };
|
|
2309
|
+
}
|
|
2310
|
+
function checkSessionDbProvenance(input) {
|
|
2311
|
+
const { dbPath, currentVersion, homeDir, env } = input;
|
|
2312
|
+
const path = sessionDbProvenanceStatePath(homeDir, env);
|
|
2313
|
+
const state = loadSessionDbProvenanceState(path);
|
|
2314
|
+
const previous = state[dbPath];
|
|
2315
|
+
const currentIds = readSessionDbMigrationIds(dbPath);
|
|
2316
|
+
if (currentIds === null) {
|
|
2317
|
+
return {
|
|
2318
|
+
anomaly: false,
|
|
2319
|
+
reason: null,
|
|
2320
|
+
recordedVersion: previous?.opencodeVersion ?? null,
|
|
2321
|
+
migrationDelta: null
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
const decision = evaluateSessionDbProvenance({ currentVersion, currentIds, previous });
|
|
2325
|
+
const migrationDelta = previous ? new Set(currentIds).size - new Set(previous.migrationIds).size : null;
|
|
2326
|
+
state[dbPath] = {
|
|
2327
|
+
opencodeVersion: currentVersion,
|
|
2328
|
+
migrationIds: currentIds,
|
|
2329
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2330
|
+
};
|
|
2331
|
+
saveSessionDbProvenanceState(path, state);
|
|
2332
|
+
return {
|
|
2333
|
+
...decision,
|
|
2334
|
+
recordedVersion: previous?.opencodeVersion ?? null,
|
|
2335
|
+
migrationDelta
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
function isSessionDbProvenanceRecord(value) {
|
|
2339
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2340
|
+
const record = value;
|
|
2341
|
+
return typeof record.opencodeVersion === "string" && Array.isArray(record.migrationIds) && record.migrationIds.every((id) => typeof id === "string") && typeof record.updatedAt === "string";
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2203
2344
|
// src/lib/opencode/opencode-version-gate.ts
|
|
2204
2345
|
var QUEUE_VALIDATED_OPENCODE_VERSIONS = ["1.17.11", "1.18.3"];
|
|
2205
2346
|
function isQueueValidatedVersion(version2) {
|
|
@@ -3286,10 +3427,10 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
3286
3427
|
|
|
3287
3428
|
// src/lib/opencode/session-db-size.ts
|
|
3288
3429
|
import { statSync as statSync3 } from "fs";
|
|
3289
|
-
import { join as
|
|
3430
|
+
import { join as join4 } from "path";
|
|
3290
3431
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
3291
3432
|
function statSessionDbBytes(homeDir) {
|
|
3292
|
-
const dbPath =
|
|
3433
|
+
const dbPath = join4(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
3293
3434
|
try {
|
|
3294
3435
|
return statSync3(dbPath).size;
|
|
3295
3436
|
} catch (err) {
|
|
@@ -3318,10 +3459,10 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
3318
3459
|
|
|
3319
3460
|
// src/lib/opencode/session-db-reclaim.ts
|
|
3320
3461
|
import { statSync as statSync4, statfsSync } from "fs";
|
|
3321
|
-
import { dirname as
|
|
3462
|
+
import { dirname as dirname4 } from "path";
|
|
3322
3463
|
function insufficientSpaceReason(dbPath, requiredBytes) {
|
|
3323
3464
|
try {
|
|
3324
|
-
const fsStats = statfsSync(
|
|
3465
|
+
const fsStats = statfsSync(dirname4(dbPath));
|
|
3325
3466
|
const availableBytes = fsStats.bavail * fsStats.bsize;
|
|
3326
3467
|
if (availableBytes < requiredBytes) {
|
|
3327
3468
|
return `only ${availableBytes} bytes free, need ${requiredBytes} for a second copy`;
|
|
@@ -3847,10 +3988,10 @@ var RunnerConnection = class {
|
|
|
3847
3988
|
};
|
|
3848
3989
|
|
|
3849
3990
|
// src/lib/tunnel/ready-marker.ts
|
|
3850
|
-
import { writeFileSync as
|
|
3991
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
3851
3992
|
function writeTunnelReadyMarker(path, agentId) {
|
|
3852
3993
|
try {
|
|
3853
|
-
|
|
3994
|
+
writeFileSync3(path, `${agentId}
|
|
3854
3995
|
`);
|
|
3855
3996
|
return { ok: true };
|
|
3856
3997
|
} catch (error2) {
|
|
@@ -3875,7 +4016,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
|
|
|
3875
4016
|
}
|
|
3876
4017
|
|
|
3877
4018
|
// src/lib/process-liveness.ts
|
|
3878
|
-
import { readFileSync as
|
|
4019
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3879
4020
|
function isProcessAlive(pid) {
|
|
3880
4021
|
try {
|
|
3881
4022
|
process.kill(pid, 0);
|
|
@@ -3890,7 +4031,7 @@ function isProcessAlive(pid) {
|
|
|
3890
4031
|
}
|
|
3891
4032
|
if (process.platform !== "linux") return true;
|
|
3892
4033
|
try {
|
|
3893
|
-
const status2 =
|
|
4034
|
+
const status2 = readFileSync4(`/proc/${pid}/status`, "utf8");
|
|
3894
4035
|
return !/^State:\s+Z(?:\s|$)/m.test(status2);
|
|
3895
4036
|
} catch (error2) {
|
|
3896
4037
|
console.error(
|
|
@@ -3901,9 +4042,9 @@ function isProcessAlive(pid) {
|
|
|
3901
4042
|
}
|
|
3902
4043
|
|
|
3903
4044
|
// src/lib/openai-usage.ts
|
|
3904
|
-
import { readFileSync as
|
|
4045
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
3905
4046
|
import { homedir as homedir3 } from "os";
|
|
3906
|
-
import { join as
|
|
4047
|
+
import { join as join5 } from "path";
|
|
3907
4048
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
3908
4049
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
3909
4050
|
var OpenAiUsageError = class extends Error {
|
|
@@ -3917,7 +4058,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
3917
4058
|
}
|
|
3918
4059
|
function readOpenCodeChatGptCredentials() {
|
|
3919
4060
|
try {
|
|
3920
|
-
const raw =
|
|
4061
|
+
const raw = readFileSync5(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
3921
4062
|
let parsed;
|
|
3922
4063
|
try {
|
|
3923
4064
|
parsed = JSON.parse(raw);
|
|
@@ -4293,12 +4434,12 @@ function createResourceUsageCollector(homeDir) {
|
|
|
4293
4434
|
import { homedir as homedir4 } from "os";
|
|
4294
4435
|
|
|
4295
4436
|
// src/lib/runner-file-sync.ts
|
|
4296
|
-
import { join as
|
|
4437
|
+
import { join as join7 } from "path";
|
|
4297
4438
|
|
|
4298
4439
|
// src/lib/file-push.ts
|
|
4299
4440
|
import { randomUUID } from "crypto";
|
|
4300
4441
|
import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
|
|
4301
|
-
import { basename, dirname as
|
|
4442
|
+
import { basename, dirname as dirname5, isAbsolute, join as join6, relative, resolve as resolve2, sep } from "path";
|
|
4302
4443
|
var FILE_MODE = 384;
|
|
4303
4444
|
var DIRECTORY_MODE = 448;
|
|
4304
4445
|
async function writePushedFile(request) {
|
|
@@ -4329,9 +4470,9 @@ async function writePushedFile(request) {
|
|
|
4329
4470
|
}
|
|
4330
4471
|
try {
|
|
4331
4472
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
4332
|
-
|
|
4473
|
+
dirname5(candidate)
|
|
4333
4474
|
);
|
|
4334
|
-
const realTarget =
|
|
4475
|
+
const realTarget = join6(existingAncestor, ...missingSegments, basename(candidate));
|
|
4335
4476
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
4336
4477
|
if (allowedDirectory === null) {
|
|
4337
4478
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -4341,8 +4482,8 @@ async function writePushedFile(request) {
|
|
|
4341
4482
|
}
|
|
4342
4483
|
if (missingSegments.length > 0) {
|
|
4343
4484
|
await createMissingDirectories(existingAncestor, missingSegments);
|
|
4344
|
-
const realParent = await realpath(
|
|
4345
|
-
if (realParent !==
|
|
4485
|
+
const realParent = await realpath(dirname5(realTarget));
|
|
4486
|
+
if (realParent !== dirname5(realTarget) || !contains(allowedDirectory, realTarget)) {
|
|
4346
4487
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
4347
4488
|
path: realTarget,
|
|
4348
4489
|
bytes,
|
|
@@ -4367,7 +4508,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
4367
4508
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
4368
4509
|
return null;
|
|
4369
4510
|
}
|
|
4370
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4511
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join6(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
4371
4512
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
4372
4513
|
return null;
|
|
4373
4514
|
}
|
|
@@ -4385,7 +4526,7 @@ async function resolveNearestExistingAncestor(directory) {
|
|
|
4385
4526
|
try {
|
|
4386
4527
|
return { existingAncestor: await realpath(current), missingSegments };
|
|
4387
4528
|
} catch (err) {
|
|
4388
|
-
const parent =
|
|
4529
|
+
const parent = dirname5(current);
|
|
4389
4530
|
if (err.code !== "ENOENT" || parent === current) {
|
|
4390
4531
|
throw err;
|
|
4391
4532
|
}
|
|
@@ -4440,13 +4581,13 @@ function contains(realDirectory, realTarget) {
|
|
|
4440
4581
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
4441
4582
|
let current = existingAncestor;
|
|
4442
4583
|
for (const segment of missingSegments) {
|
|
4443
|
-
current =
|
|
4584
|
+
current = join6(current, segment);
|
|
4444
4585
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
4445
4586
|
await chmod(current, DIRECTORY_MODE);
|
|
4446
4587
|
}
|
|
4447
4588
|
}
|
|
4448
4589
|
async function writeAtomically(realTarget, content) {
|
|
4449
|
-
const temporaryPath =
|
|
4590
|
+
const temporaryPath = join6(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
4450
4591
|
let handle;
|
|
4451
4592
|
try {
|
|
4452
4593
|
handle = await open2(temporaryPath, "wx", FILE_MODE);
|
|
@@ -4576,12 +4717,12 @@ var NOT_APPLIED = {
|
|
|
4576
4717
|
opencodeAuthApplied: false
|
|
4577
4718
|
};
|
|
4578
4719
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
4579
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4580
|
-
return expanded ===
|
|
4720
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
4721
|
+
return expanded === join7(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
4581
4722
|
}
|
|
4582
4723
|
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
4583
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
4584
|
-
return expanded ===
|
|
4724
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join7(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
4725
|
+
return expanded === join7(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
4585
4726
|
}
|
|
4586
4727
|
async function applyOne(options, file) {
|
|
4587
4728
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -8629,7 +8770,7 @@ Port ${port} is already in use.`));
|
|
|
8629
8770
|
}
|
|
8630
8771
|
|
|
8631
8772
|
// src/lib/runner-credentials.ts
|
|
8632
|
-
import { chmodSync as chmodSync2, writeFileSync as
|
|
8773
|
+
import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
8633
8774
|
import { spawn as spawn5 } from "child_process";
|
|
8634
8775
|
var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
|
|
8635
8776
|
var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
|
|
@@ -8870,8 +9011,8 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
8870
9011
|
}
|
|
8871
9012
|
try {
|
|
8872
9013
|
env.GIT_CONFIG_GLOBAL = GIT_CONFIG_GLOBAL;
|
|
8873
|
-
|
|
8874
|
-
|
|
9014
|
+
writeFileSync4(GIT_CONFIG_GLOBAL, "");
|
|
9015
|
+
writeFileSync4(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
|
|
8875
9016
|
chmodSync2(GIT_CREDENTIAL_HELPER, 448);
|
|
8876
9017
|
const config = [
|
|
8877
9018
|
["user.name", env.GIT_USER_NAME ?? "evident-bot"],
|
|
@@ -8904,7 +9045,7 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
8904
9045
|
// src/lib/opencode/config-overlay.ts
|
|
8905
9046
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
8906
9047
|
import { copyFileSync, existsSync as existsSync2, statSync as statSync5 } from "fs";
|
|
8907
|
-
import { isAbsolute as isAbsolute2, join as
|
|
9048
|
+
import { isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "path";
|
|
8908
9049
|
function isFile(filePath) {
|
|
8909
9050
|
return existsSync2(filePath) && statSync5(filePath).isFile();
|
|
8910
9051
|
}
|
|
@@ -8918,7 +9059,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
8918
9059
|
return;
|
|
8919
9060
|
}
|
|
8920
9061
|
const source = isAbsolute2(overlayPath) ? overlayPath : resolve3(cwd, overlayPath);
|
|
8921
|
-
const target = isFile(
|
|
9062
|
+
const target = isFile(join8(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
|
|
8922
9063
|
if (!isFile(source)) {
|
|
8923
9064
|
log3(
|
|
8924
9065
|
`RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
|
|
@@ -8926,7 +9067,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
8926
9067
|
);
|
|
8927
9068
|
return;
|
|
8928
9069
|
}
|
|
8929
|
-
copyFileSync(source,
|
|
9070
|
+
copyFileSync(source, join8(cwd, target));
|
|
8930
9071
|
try {
|
|
8931
9072
|
execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
|
|
8932
9073
|
stdio: "ignore"
|
|
@@ -8935,7 +9076,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
8935
9076
|
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
8936
9077
|
log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
|
|
8937
9078
|
}
|
|
8938
|
-
log3(`Applied runner OpenCode config ${source} to ${
|
|
9079
|
+
log3(`Applied runner OpenCode config ${source} to ${join8(cwd, target)}`);
|
|
8939
9080
|
}
|
|
8940
9081
|
|
|
8941
9082
|
// src/commands/run.ts
|
|
@@ -8975,7 +9116,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
8975
9116
|
if (trimmed === "") {
|
|
8976
9117
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
8977
9118
|
}
|
|
8978
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
9119
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join9(homeDir, trimmed.slice(2)) : trimmed;
|
|
8979
9120
|
if (!isAbsolute3(expanded)) {
|
|
8980
9121
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
8981
9122
|
}
|
|
@@ -9318,7 +9459,43 @@ async function driveChannels(state, driver) {
|
|
|
9318
9459
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
9319
9460
|
var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
|
|
9320
9461
|
function sessionDbPath() {
|
|
9321
|
-
return
|
|
9462
|
+
return join9(homedir5(), ".local", "share", "opencode", "opencode.db");
|
|
9463
|
+
}
|
|
9464
|
+
function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
|
|
9465
|
+
const record = {
|
|
9466
|
+
v: 1,
|
|
9467
|
+
event: "session_db_recovery",
|
|
9468
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9469
|
+
stage: "verify",
|
|
9470
|
+
outcome: "schema_provenance_mismatch",
|
|
9471
|
+
severity: "error",
|
|
9472
|
+
reason: provenance.reason ?? "schema-provenance-mismatch",
|
|
9473
|
+
litestream_exit_code: null,
|
|
9474
|
+
attempt: null,
|
|
9475
|
+
replica_objects: null,
|
|
9476
|
+
replica_bytes: null,
|
|
9477
|
+
quarantine_destination: null,
|
|
9478
|
+
quarantined_objects: null,
|
|
9479
|
+
quarantine_failed_objects: null,
|
|
9480
|
+
quarantined_bytes: null,
|
|
9481
|
+
verified_restore_point: null,
|
|
9482
|
+
restore_points_tried: null,
|
|
9483
|
+
provenance_reason: provenance.reason,
|
|
9484
|
+
provenance_migration_delta: provenance.migrationDelta,
|
|
9485
|
+
replication_suspended: false,
|
|
9486
|
+
dbPath: sessionDbPath(),
|
|
9487
|
+
recorded_version: provenance.recordedVersion,
|
|
9488
|
+
current_version: currentVersion,
|
|
9489
|
+
provenance_pre_boot_migration_count: preBootMigrationCount
|
|
9490
|
+
};
|
|
9491
|
+
const activity = buildSessionDbRecoveryActivity(record);
|
|
9492
|
+
if (!activity) throw new Error("could not map session-DB provenance activity");
|
|
9493
|
+
logActivity(state, {
|
|
9494
|
+
type: activity.level === "error" ? "error" : "info",
|
|
9495
|
+
level: activity.level,
|
|
9496
|
+
...activity.level === "error" ? { error: activity.message } : { message: activity.message },
|
|
9497
|
+
metadata: activity.metadata
|
|
9498
|
+
});
|
|
9322
9499
|
}
|
|
9323
9500
|
async function runSweep(state, driver, config) {
|
|
9324
9501
|
const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
|
|
@@ -9365,7 +9542,7 @@ async function runSweep(state, driver, config) {
|
|
|
9365
9542
|
const reclaimResult = await reclaimSessionDbSpace({
|
|
9366
9543
|
dbPath: sessionDbPath(),
|
|
9367
9544
|
maxPages: SESSION_DB_RECLAIM_MAX_PAGES,
|
|
9368
|
-
allowFullVacuum: protectedNow.size === 0
|
|
9545
|
+
allowFullVacuum: protectedNow.size === 0 && !state.sessionDbProvenanceAnomaly
|
|
9369
9546
|
});
|
|
9370
9547
|
if (reclaimResult.ok) {
|
|
9371
9548
|
const beforeMib = (reclaimResult.beforeBytes / 1024 / 1024).toFixed(1);
|
|
@@ -9804,6 +9981,7 @@ async function run(options) {
|
|
|
9804
9981
|
connected: false,
|
|
9805
9982
|
opencodeConnected: false,
|
|
9806
9983
|
opencodeVersion: null,
|
|
9984
|
+
sessionDbProvenanceAnomaly: false,
|
|
9807
9985
|
opencodeProcess: null,
|
|
9808
9986
|
litestreamProcess: null,
|
|
9809
9987
|
connection: null,
|
|
@@ -10082,6 +10260,7 @@ async function run(options) {
|
|
|
10082
10260
|
for (const warning2 of maxActiveSessionsWarnings) {
|
|
10083
10261
|
logActivity(state, { type: "info", level: "warn", message: warning2 });
|
|
10084
10262
|
}
|
|
10263
|
+
const preBootMigrationIds = readSessionDbMigrationIds(sessionDbPath());
|
|
10085
10264
|
const ocSpinner = interactive && !state.json ? ora3("Checking OpenCode...").start() : null;
|
|
10086
10265
|
try {
|
|
10087
10266
|
const oc = await ensureOpenCodeRunning({
|
|
@@ -10097,7 +10276,7 @@ async function run(options) {
|
|
|
10097
10276
|
state.opencodeVersion = oc.version;
|
|
10098
10277
|
if (options.opencodePidFile && oc.process?.pid !== void 0) {
|
|
10099
10278
|
try {
|
|
10100
|
-
|
|
10279
|
+
writeFileSync5(options.opencodePidFile, `${oc.process.pid}
|
|
10101
10280
|
`, { mode: 384 });
|
|
10102
10281
|
chmodSync3(options.opencodePidFile, 384);
|
|
10103
10282
|
} catch (error2) {
|
|
@@ -10107,6 +10286,23 @@ async function run(options) {
|
|
|
10107
10286
|
});
|
|
10108
10287
|
}
|
|
10109
10288
|
}
|
|
10289
|
+
if (state.opencodeVersion !== null) {
|
|
10290
|
+
const provenance = checkSessionDbProvenance({
|
|
10291
|
+
dbPath: sessionDbPath(),
|
|
10292
|
+
currentVersion: state.opencodeVersion,
|
|
10293
|
+
homeDir: homedir5(),
|
|
10294
|
+
env: process.env
|
|
10295
|
+
});
|
|
10296
|
+
if (provenance.anomaly) {
|
|
10297
|
+
state.sessionDbProvenanceAnomaly = true;
|
|
10298
|
+
logSessionDbProvenanceMismatch(
|
|
10299
|
+
state,
|
|
10300
|
+
provenance,
|
|
10301
|
+
state.opencodeVersion,
|
|
10302
|
+
preBootMigrationIds?.length ?? null
|
|
10303
|
+
);
|
|
10304
|
+
}
|
|
10305
|
+
}
|
|
10110
10306
|
state.opencodeConnected = oc.notReadyReason === null;
|
|
10111
10307
|
const version2 = state.opencodeVersion ? ` (v${state.opencodeVersion})` : "";
|
|
10112
10308
|
ocSpinner?.succeed(`OpenCode running on port ${state.port}${version2}`);
|
|
@@ -10159,7 +10355,7 @@ async function run(options) {
|
|
|
10159
10355
|
let existingPid;
|
|
10160
10356
|
if (existsSync3(options.litestreamPidFile)) {
|
|
10161
10357
|
try {
|
|
10162
|
-
const rawPid =
|
|
10358
|
+
const rawPid = readFileSync6(options.litestreamPidFile, "utf8").trim();
|
|
10163
10359
|
const parsedPid = Number(rawPid);
|
|
10164
10360
|
if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
|
|
10165
10361
|
existingPid = parsedPid;
|
|
@@ -10196,7 +10392,7 @@ async function run(options) {
|
|
|
10196
10392
|
});
|
|
10197
10393
|
try {
|
|
10198
10394
|
if (litestreamProcess.pid !== void 0) {
|
|
10199
|
-
|
|
10395
|
+
writeFileSync5(options.litestreamPidFile, `${litestreamProcess.pid}
|
|
10200
10396
|
`, {
|
|
10201
10397
|
mode: 384
|
|
10202
10398
|
});
|
|
@@ -10448,7 +10644,7 @@ async function run(options) {
|
|
|
10448
10644
|
}
|
|
10449
10645
|
|
|
10450
10646
|
// src/index.ts
|
|
10451
|
-
var { version } =
|
|
10647
|
+
var { version } = createRequire2(import.meta.url)("../package.json");
|
|
10452
10648
|
var program = new Command();
|
|
10453
10649
|
program.name("evident").description("Run OpenCode locally and connect it to Evident").version(version).option(
|
|
10454
10650
|
"--endpoint <url>",
|