@evident-ai/cli 3.2.1-dev.7244588 → 3.2.1-dev.7cd8a52
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 +5 -3
- package/dist/index.js +212 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,8 +113,8 @@ Options:
|
|
|
113
113
|
and ignored, which can leave cleanup off if it was the only rule set. Env:
|
|
114
114
|
`EVIDENT_SESSION_CLEANUP_MAX_AGE`. With cleanup off, a runner whose local
|
|
115
115
|
session store has grown large warns once at startup, on the runner's page,
|
|
116
|
-
naming this flag —
|
|
117
|
-
|
|
116
|
+
naming this flag — turning cleanup on both stops the store growing further
|
|
117
|
+
and reclaims disk space already used by deleted sessions.
|
|
118
118
|
- `--session-cleanup-max-count <n>` — Keep only the newest N OpenCode sessions
|
|
119
119
|
by last activity, deleting the rest. Also enables cleanup; combines with
|
|
120
120
|
`--session-cleanup-max-age` as OR. A session with a turn actively in progress
|
|
@@ -135,7 +135,9 @@ Options:
|
|
|
135
135
|
`EVIDENT_MAX_ACTIVE_SESSIONS`.
|
|
136
136
|
- `--session-cleanup-interval <duration>` — How often the cleanup sweep runs
|
|
137
137
|
(default: `1h`). An invalid value falls back to the default rather than
|
|
138
|
-
disabling cleanup.
|
|
138
|
+
disabling cleanup. Each sweep also reclaims disk space freed by the sessions
|
|
139
|
+
it deleted, so the store shrinks rather than merely stopping its growth. Env:
|
|
140
|
+
`EVIDENT_SESSION_CLEANUP_INTERVAL`.
|
|
139
141
|
- `--enable-file-sync-to <dir>` — Let the runner write files Evident has queued
|
|
140
142
|
for it into this directory — it collects them as part of the polling it
|
|
141
143
|
already does, so they land a couple of seconds after you hand them over.
|
package/dist/index.js
CHANGED
|
@@ -1052,6 +1052,7 @@ var MAX_FILE_SYNC_DIRECTORIES = 16;
|
|
|
1052
1052
|
|
|
1053
1053
|
// ../../packages/types/src/logging/index.ts
|
|
1054
1054
|
var CORRELATION_ID_HEADER = "x-evident-correlation-id";
|
|
1055
|
+
var FORWARD_FAILURE_REASON_HEADER = "X-Evident-Failure-Reason";
|
|
1055
1056
|
function log(level, event, fields) {
|
|
1056
1057
|
const method = level === "debug" ? "log" : level;
|
|
1057
1058
|
try {
|
|
@@ -2356,10 +2357,128 @@ function statSessionDbBytes(homeDir) {
|
|
|
2356
2357
|
}
|
|
2357
2358
|
}
|
|
2358
2359
|
function buildSessionStoreSizeWarning(input) {
|
|
2359
|
-
const { dbBytes, cleanupEnabled } = input;
|
|
2360
|
-
if (dbBytes === null || dbBytes <= LARGE_DB_THRESHOLD_BYTES
|
|
2360
|
+
const { dbBytes, cleanupEnabled, reclaimSkipReason } = input;
|
|
2361
|
+
if (dbBytes === null || dbBytes <= LARGE_DB_THRESHOLD_BYTES) return null;
|
|
2361
2362
|
const mib = Math.round(dbBytes / 1024 / 1024);
|
|
2362
|
-
|
|
2363
|
+
if (!cleanupEnabled) {
|
|
2364
|
+
return `Session store is large: opencode.db is ${mib} MiB and automatic session cleanup is off. Enable it with --session-cleanup-max-age 24h (env EVIDENT_SESSION_CLEANUP_MAX_AGE) to stop it growing \u2014 a store much larger than this can exceed a hosted runner's session-history restore budget on the next start, losing this runner's session history.`;
|
|
2365
|
+
}
|
|
2366
|
+
if (reclaimSkipReason === "sqlite-unavailable" || reclaimSkipReason === "insufficient-disk-space") {
|
|
2367
|
+
const reasonText = reclaimSkipReason === "sqlite-unavailable" ? "this Node runtime lacks node:sqlite (needs Node >=22.5)" : "there is not enough free disk space to compact it";
|
|
2368
|
+
return `Session store is large: opencode.db is ${mib} MiB. Automatic session cleanup is on, but space cannot currently be reclaimed because ${reasonText} \u2014 a store this large can exceed a hosted runner's session-history restore budget on the next start, losing this runner's session history.`;
|
|
2369
|
+
}
|
|
2370
|
+
return null;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// src/lib/opencode/session-db-reclaim.ts
|
|
2374
|
+
import { statSync as statSync3, statfsSync } from "fs";
|
|
2375
|
+
import { dirname as dirname2 } from "path";
|
|
2376
|
+
function insufficientSpaceReason(dbPath, requiredBytes) {
|
|
2377
|
+
try {
|
|
2378
|
+
const fsStats = statfsSync(dirname2(dbPath));
|
|
2379
|
+
const availableBytes = fsStats.bavail * fsStats.bsize;
|
|
2380
|
+
if (availableBytes < requiredBytes) {
|
|
2381
|
+
return `only ${availableBytes} bytes free, need ${requiredBytes} for a second copy`;
|
|
2382
|
+
}
|
|
2383
|
+
return null;
|
|
2384
|
+
} catch (err) {
|
|
2385
|
+
return `could not check free space (${err instanceof Error ? err.message : String(err)}); refusing to guess`;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
function readLogicalBytes(db) {
|
|
2389
|
+
const pageCount = db.prepare("PRAGMA page_count").get().page_count;
|
|
2390
|
+
const pageSize = db.prepare("PRAGMA page_size").get().page_size;
|
|
2391
|
+
return pageCount * pageSize;
|
|
2392
|
+
}
|
|
2393
|
+
function readCheckpointResult(db) {
|
|
2394
|
+
const row = db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
2395
|
+
return { busy: row.busy !== 0, log: row.log, checkpointed: row.checkpointed };
|
|
2396
|
+
}
|
|
2397
|
+
async function probeReclaimAvailability(input) {
|
|
2398
|
+
const { dbPath, requiredBytes } = input;
|
|
2399
|
+
let sqlite;
|
|
2400
|
+
try {
|
|
2401
|
+
sqlite = await import("sqlite");
|
|
2402
|
+
} catch (err) {
|
|
2403
|
+
console.warn(
|
|
2404
|
+
`[probeReclaimAvailability] node:sqlite unavailable for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2405
|
+
);
|
|
2406
|
+
return "sqlite-unavailable";
|
|
2407
|
+
}
|
|
2408
|
+
let autoVacuum = null;
|
|
2409
|
+
try {
|
|
2410
|
+
const db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
|
|
2411
|
+
try {
|
|
2412
|
+
autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
|
|
2413
|
+
} finally {
|
|
2414
|
+
db.close();
|
|
2415
|
+
}
|
|
2416
|
+
} catch (err) {
|
|
2417
|
+
console.warn(
|
|
2418
|
+
`[probeReclaimAvailability] could not read auto_vacuum mode for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2419
|
+
);
|
|
2420
|
+
}
|
|
2421
|
+
if (autoVacuum !== 0) return null;
|
|
2422
|
+
return insufficientSpaceReason(dbPath, requiredBytes) !== null ? "insufficient-disk-space" : null;
|
|
2423
|
+
}
|
|
2424
|
+
async function reclaimSessionDbSpace(input) {
|
|
2425
|
+
const { dbPath, maxPages, allowFullVacuum = true } = input;
|
|
2426
|
+
let sqlite;
|
|
2427
|
+
try {
|
|
2428
|
+
sqlite = await import("sqlite");
|
|
2429
|
+
} catch (err) {
|
|
2430
|
+
console.warn(
|
|
2431
|
+
`[reclaimSessionDbSpace] node:sqlite unavailable (need Node >=22.5, >=22.13 unflagged) for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2432
|
+
);
|
|
2433
|
+
return { ok: false, skipped: "sqlite-unavailable" };
|
|
2434
|
+
}
|
|
2435
|
+
const { DatabaseSync } = sqlite;
|
|
2436
|
+
let db;
|
|
2437
|
+
try {
|
|
2438
|
+
db = new DatabaseSync(dbPath);
|
|
2439
|
+
const autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
|
|
2440
|
+
if (autoVacuum === 0) {
|
|
2441
|
+
if (!allowFullVacuum) {
|
|
2442
|
+
console.warn(
|
|
2443
|
+
`[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: a session turn is live`
|
|
2444
|
+
);
|
|
2445
|
+
return { ok: false, skipped: "full-vacuum-blocked" };
|
|
2446
|
+
}
|
|
2447
|
+
const fileBytesForGuard = statSync3(dbPath).size;
|
|
2448
|
+
const skipReason = insufficientSpaceReason(dbPath, fileBytesForGuard);
|
|
2449
|
+
if (skipReason !== null) {
|
|
2450
|
+
console.warn(
|
|
2451
|
+
`[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: ${skipReason}`
|
|
2452
|
+
);
|
|
2453
|
+
return { ok: false, skipped: "insufficient-disk-space" };
|
|
2454
|
+
}
|
|
2455
|
+
const beforeBytes = readLogicalBytes(db);
|
|
2456
|
+
db.exec("PRAGMA auto_vacuum=INCREMENTAL");
|
|
2457
|
+
db.exec("VACUUM");
|
|
2458
|
+
const afterBytes = readLogicalBytes(db);
|
|
2459
|
+
const checkpoint = readCheckpointResult(db);
|
|
2460
|
+
return { ok: true, mode: "convert", beforeBytes, afterBytes, checkpoint };
|
|
2461
|
+
}
|
|
2462
|
+
if (autoVacuum === 2) {
|
|
2463
|
+
const beforeBytes = readLogicalBytes(db);
|
|
2464
|
+
const bound = Math.max(0, Math.trunc(maxPages));
|
|
2465
|
+
db.exec(`PRAGMA incremental_vacuum(${bound})`);
|
|
2466
|
+
const afterBytes = readLogicalBytes(db);
|
|
2467
|
+
const checkpoint = readCheckpointResult(db);
|
|
2468
|
+
return { ok: true, mode: "incremental", beforeBytes, afterBytes, checkpoint };
|
|
2469
|
+
}
|
|
2470
|
+
console.warn(
|
|
2471
|
+
`[reclaimSessionDbSpace] ${dbPath} has auto_vacuum=${autoVacuum} (neither NONE nor INCREMENTAL); nothing to reclaim`
|
|
2472
|
+
);
|
|
2473
|
+
return { ok: false, skipped: "auto-vacuum-not-applicable" };
|
|
2474
|
+
} catch (err) {
|
|
2475
|
+
console.error(
|
|
2476
|
+
`[reclaimSessionDbSpace] reclaim failed for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2477
|
+
);
|
|
2478
|
+
return { ok: false, skipped: "reclaim-error" };
|
|
2479
|
+
} finally {
|
|
2480
|
+
db?.close();
|
|
2481
|
+
}
|
|
2363
2482
|
}
|
|
2364
2483
|
|
|
2365
2484
|
// src/lib/tunnel/connection.ts
|
|
@@ -2527,6 +2646,17 @@ var StreamForwarder = class {
|
|
|
2527
2646
|
};
|
|
2528
2647
|
|
|
2529
2648
|
// src/lib/tunnel/connection.ts
|
|
2649
|
+
var FAILURE_REASON_HEADER_LC = FORWARD_FAILURE_REASON_HEADER.toLowerCase();
|
|
2650
|
+
var TunnelUpgradeRejectedError = class extends Error {
|
|
2651
|
+
constructor(message, reason) {
|
|
2652
|
+
super(message);
|
|
2653
|
+
this.reason = reason;
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
function classifyUpgradeRejection(headers) {
|
|
2657
|
+
const value = headers[FAILURE_REASON_HEADER_LC];
|
|
2658
|
+
return value === "do_code_updated" ? "do_code_updated" : "unknown";
|
|
2659
|
+
}
|
|
2530
2660
|
var MAX_RECONNECT_DELAY = 3e4;
|
|
2531
2661
|
var BASE_RECONNECT_DELAY = 500;
|
|
2532
2662
|
function getReconnectDelay(attempt) {
|
|
@@ -2571,6 +2701,7 @@ function connectTunnel(options) {
|
|
|
2571
2701
|
onError,
|
|
2572
2702
|
onResponse,
|
|
2573
2703
|
onInfo,
|
|
2704
|
+
onWarning,
|
|
2574
2705
|
onDrainPing
|
|
2575
2706
|
} = options;
|
|
2576
2707
|
const tunnelUrl = getTunnelUrlConfig();
|
|
@@ -2590,8 +2721,11 @@ function connectTunnel(options) {
|
|
|
2590
2721
|
reject(new Error("Connection timeout"));
|
|
2591
2722
|
}, 3e4);
|
|
2592
2723
|
let upgradeRejection = null;
|
|
2724
|
+
let upgradeRejectionReason = null;
|
|
2593
2725
|
ws.on("unexpected-response", (_req, res) => {
|
|
2594
2726
|
clearTimeout(connectionTimeout);
|
|
2727
|
+
const reason = classifyUpgradeRejection(res.headers);
|
|
2728
|
+
upgradeRejectionReason = reason;
|
|
2595
2729
|
const chunks = [];
|
|
2596
2730
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
2597
2731
|
res.on("end", () => {
|
|
@@ -2605,8 +2739,14 @@ function connectTunnel(options) {
|
|
|
2605
2739
|
}
|
|
2606
2740
|
const statusLine = `HTTP ${res.statusCode}${res.statusMessage ? ` ${res.statusMessage}` : ""}`;
|
|
2607
2741
|
upgradeRejection = detail ? `${statusLine}: ${detail}` : statusLine;
|
|
2608
|
-
|
|
2609
|
-
|
|
2742
|
+
if (reason === "do_code_updated") {
|
|
2743
|
+
onWarning?.("Relay redeployed \u2014 reconnecting");
|
|
2744
|
+
} else {
|
|
2745
|
+
onError?.(`Tunnel refused by relay (${upgradeRejection})`);
|
|
2746
|
+
}
|
|
2747
|
+
reject(
|
|
2748
|
+
new TunnelUpgradeRejectedError(`Tunnel handshake rejected: ${upgradeRejection}`, reason)
|
|
2749
|
+
);
|
|
2610
2750
|
});
|
|
2611
2751
|
});
|
|
2612
2752
|
ws.on("open", () => {
|
|
@@ -2652,8 +2792,14 @@ function connectTunnel(options) {
|
|
|
2652
2792
|
ws.on("error", (error2) => {
|
|
2653
2793
|
clearTimeout(connectionTimeout);
|
|
2654
2794
|
const detail = upgradeRejection ?? describeSocketError(error2, url);
|
|
2655
|
-
|
|
2656
|
-
|
|
2795
|
+
if (upgradeRejectionReason === "do_code_updated") {
|
|
2796
|
+
onWarning?.("Relay redeployed \u2014 reconnecting");
|
|
2797
|
+
} else {
|
|
2798
|
+
onError?.(`Connection error: ${detail}`);
|
|
2799
|
+
}
|
|
2800
|
+
reject(
|
|
2801
|
+
upgradeRejectionReason !== null ? new TunnelUpgradeRejectedError(detail, upgradeRejectionReason) : new Error(detail)
|
|
2802
|
+
);
|
|
2657
2803
|
});
|
|
2658
2804
|
ws.on("close", (code, reason) => {
|
|
2659
2805
|
const reasonStr = reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
|
|
@@ -2729,7 +2875,8 @@ var RunnerConnection = class {
|
|
|
2729
2875
|
onError: (error2) => events.onError?.(error2),
|
|
2730
2876
|
onResponse: () => events.onResponse?.(),
|
|
2731
2877
|
onDrainPing: () => events.onDrainPing?.(),
|
|
2732
|
-
onInfo: (message) => events.onInfo?.(message)
|
|
2878
|
+
onInfo: (message) => events.onInfo?.(message),
|
|
2879
|
+
onWarning: (message) => events.onWarning?.(message)
|
|
2733
2880
|
});
|
|
2734
2881
|
return;
|
|
2735
2882
|
} catch (error2) {
|
|
@@ -2740,7 +2887,12 @@ var RunnerConnection = class {
|
|
|
2740
2887
|
}
|
|
2741
2888
|
const delay = getReconnectDelay(this.reconnectAttempt);
|
|
2742
2889
|
events.onReconnecting?.(this.reconnectAttempt);
|
|
2743
|
-
|
|
2890
|
+
const retryMessage = `Connection failed, retrying in ${Math.round(delay / 1e3)}s...`;
|
|
2891
|
+
if (error2 instanceof TunnelUpgradeRejectedError && error2.reason === "do_code_updated") {
|
|
2892
|
+
events.onWarning?.(retryMessage);
|
|
2893
|
+
} else {
|
|
2894
|
+
events.onError?.(retryMessage);
|
|
2895
|
+
}
|
|
2744
2896
|
await this.sleep(delay);
|
|
2745
2897
|
}
|
|
2746
2898
|
}
|
|
@@ -2797,7 +2949,7 @@ import { homedir as homedir2 } from "os";
|
|
|
2797
2949
|
// src/lib/file-push.ts
|
|
2798
2950
|
import { randomUUID } from "crypto";
|
|
2799
2951
|
import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
|
|
2800
|
-
import { basename, dirname as
|
|
2952
|
+
import { basename, dirname as dirname3, isAbsolute, join as join3, relative, resolve as resolve2, sep } from "path";
|
|
2801
2953
|
var FILE_MODE = 384;
|
|
2802
2954
|
var DIRECTORY_MODE = 448;
|
|
2803
2955
|
async function writePushedFile(request) {
|
|
@@ -2828,7 +2980,7 @@ async function writePushedFile(request) {
|
|
|
2828
2980
|
}
|
|
2829
2981
|
try {
|
|
2830
2982
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
2831
|
-
|
|
2983
|
+
dirname3(candidate)
|
|
2832
2984
|
);
|
|
2833
2985
|
const realTarget = join3(existingAncestor, ...missingSegments, basename(candidate));
|
|
2834
2986
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
@@ -2840,8 +2992,8 @@ async function writePushedFile(request) {
|
|
|
2840
2992
|
}
|
|
2841
2993
|
if (missingSegments.length > 0) {
|
|
2842
2994
|
await createMissingDirectories(existingAncestor, missingSegments);
|
|
2843
|
-
const realParent = await realpath(
|
|
2844
|
-
if (realParent !==
|
|
2995
|
+
const realParent = await realpath(dirname3(realTarget));
|
|
2996
|
+
if (realParent !== dirname3(realTarget) || !contains(allowedDirectory, realTarget)) {
|
|
2845
2997
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
2846
2998
|
path: realTarget,
|
|
2847
2999
|
bytes,
|
|
@@ -2884,7 +3036,7 @@ async function resolveNearestExistingAncestor(directory) {
|
|
|
2884
3036
|
try {
|
|
2885
3037
|
return { existingAncestor: await realpath(current), missingSegments };
|
|
2886
3038
|
} catch (err) {
|
|
2887
|
-
const parent =
|
|
3039
|
+
const parent = dirname3(current);
|
|
2888
3040
|
if (err.code !== "ENOENT" || parent === current) {
|
|
2889
3041
|
throw err;
|
|
2890
3042
|
}
|
|
@@ -2945,7 +3097,7 @@ async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
|
2945
3097
|
}
|
|
2946
3098
|
}
|
|
2947
3099
|
async function writeAtomically(realTarget, content) {
|
|
2948
|
-
const temporaryPath = join3(
|
|
3100
|
+
const temporaryPath = join3(dirname3(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
2949
3101
|
let handle;
|
|
2950
3102
|
try {
|
|
2951
3103
|
handle = await open2(temporaryPath, "wx", FILE_MODE);
|
|
@@ -7089,6 +7241,10 @@ async function driveChannels(state, driver) {
|
|
|
7089
7241
|
}
|
|
7090
7242
|
}
|
|
7091
7243
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
7244
|
+
var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
|
|
7245
|
+
function sessionDbPath() {
|
|
7246
|
+
return join4(homedir3(), ".local", "share", "opencode", "opencode.db");
|
|
7247
|
+
}
|
|
7092
7248
|
async function runSweep(state, driver, config) {
|
|
7093
7249
|
const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
|
|
7094
7250
|
try {
|
|
@@ -7131,6 +7287,25 @@ async function runSweep(state, driver, config) {
|
|
|
7131
7287
|
type: "info",
|
|
7132
7288
|
message: `Session cleanup: inspected ${sessions.length}, deleted ${deleted}${failedNote}${skippedNote} (${mode})`
|
|
7133
7289
|
});
|
|
7290
|
+
const reclaimResult = await reclaimSessionDbSpace({
|
|
7291
|
+
dbPath: sessionDbPath(),
|
|
7292
|
+
maxPages: SESSION_DB_RECLAIM_MAX_PAGES,
|
|
7293
|
+
allowFullVacuum: protectedNow.size === 0
|
|
7294
|
+
});
|
|
7295
|
+
if (reclaimResult.ok) {
|
|
7296
|
+
const beforeMib = (reclaimResult.beforeBytes / 1024 / 1024).toFixed(1);
|
|
7297
|
+
const afterMib = (reclaimResult.afterBytes / 1024 / 1024).toFixed(1);
|
|
7298
|
+
const checkpointNote = reclaimResult.checkpoint.busy ? ` (on-disk file truncation deferred: checkpoint busy, ${reclaimResult.checkpoint.log} WAL frames pending)` : "";
|
|
7299
|
+
logActivity(state, {
|
|
7300
|
+
type: "info",
|
|
7301
|
+
message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB${checkpointNote}`
|
|
7302
|
+
});
|
|
7303
|
+
} else {
|
|
7304
|
+
logActivity(state, {
|
|
7305
|
+
type: "info",
|
|
7306
|
+
message: `Session cleanup: session-db space reclaim skipped (${reclaimResult.skipped})`
|
|
7307
|
+
});
|
|
7308
|
+
}
|
|
7134
7309
|
} catch (error2) {
|
|
7135
7310
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
7136
7311
|
logActivity(state, {
|
|
@@ -7151,13 +7326,22 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
7151
7326
|
for (const warning2 of config.warnings) {
|
|
7152
7327
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
7153
7328
|
}
|
|
7154
|
-
const
|
|
7155
|
-
|
|
7156
|
-
|
|
7329
|
+
const dbBytes = statSessionDbBytes(homedir3());
|
|
7330
|
+
void (async () => {
|
|
7331
|
+
const reclaimSkipReason = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
7332
|
+
const sizeWarning = buildSessionStoreSizeWarning({
|
|
7333
|
+
dbBytes,
|
|
7334
|
+
cleanupEnabled: config.enabled,
|
|
7335
|
+
reclaimSkipReason
|
|
7336
|
+
});
|
|
7337
|
+
if (sizeWarning !== null) {
|
|
7338
|
+
logActivity(state, { type: "info", level: "warn", message: sizeWarning });
|
|
7339
|
+
}
|
|
7340
|
+
})().catch((err) => {
|
|
7341
|
+
console.error(
|
|
7342
|
+
`[scheduleSessionCleanup] size-warning preflight failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7343
|
+
);
|
|
7157
7344
|
});
|
|
7158
|
-
if (sizeWarning !== null) {
|
|
7159
|
-
logActivity(state, { type: "info", level: "warn", message: sizeWarning });
|
|
7160
|
-
}
|
|
7161
7345
|
if (!config.enabled) return;
|
|
7162
7346
|
logActivity(state, {
|
|
7163
7347
|
type: "info",
|
|
@@ -7739,6 +7923,13 @@ async function run(options) {
|
|
|
7739
7923
|
logActivity(state, { type: "error", error: error2 });
|
|
7740
7924
|
if (state.interactive) displayStatus(state);
|
|
7741
7925
|
},
|
|
7926
|
+
// `warn`, not `info`: `forwardRunnerActivity`'s FORWARDED_LEVELS floor is
|
|
7927
|
+
// {'warn','error'}, so an `info` entry would never leave the machine and
|
|
7928
|
+
// an operator couldn't correlate a reconnect storm with a relay deploy.
|
|
7929
|
+
onWarning: (message) => {
|
|
7930
|
+
logActivity(state, { type: "info", level: "warn", message });
|
|
7931
|
+
if (state.interactive) displayStatus(state);
|
|
7932
|
+
},
|
|
7742
7933
|
// Web traffic is proxied transparently; note opencode is live and stamp
|
|
7743
7934
|
// proxied activity so the idle loop treats interactive proxy use as work.
|
|
7744
7935
|
// Fires per forwarded response head (incl. every SSE open) and excludes
|