@evident-ai/cli 3.6.1-dev.98128d1 → 3.6.1-dev.a11d5f5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -723,6 +723,9 @@ async function reportResourceUsage(agentId, authHeader, usage) {
723
723
  opencode_db_bytes: usage.opencodeDbBytes
724
724
  });
725
725
  }
726
+ async function reportSessionDeletions(agentId, authHeader, deletions) {
727
+ return postBestEffort(`/runners/${agentId}/session-deletions`, authHeader, { deletions });
728
+ }
726
729
  async function getRunnerCredentialVault(agentId, authHeader) {
727
730
  const empty = { prefix: null, vaultId: null, source: null };
728
731
  try {
@@ -1176,9 +1179,9 @@ async function claudeUsage() {
1176
1179
  }
1177
1180
 
1178
1181
  // src/commands/run.ts
1179
- import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
1182
+ import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
1180
1183
  import { homedir as homedir8 } from "node:os";
1181
- import { isAbsolute as isAbsolute4, join as join13, parse, resolve as resolvePath2 } from "node:path";
1184
+ import { isAbsolute as isAbsolute4, join as join14, parse, resolve as resolvePath2 } from "node:path";
1182
1185
  import chalk7 from "chalk";
1183
1186
 
1184
1187
  // ../../packages/types/src/agents/index.ts
@@ -1442,7 +1445,7 @@ import ora4 from "ora";
1442
1445
  import { select as select4 } from "@inquirer/prompts";
1443
1446
 
1444
1447
  // src/lib/telemetry.ts
1445
- var CLI_VERSION = (true ? "3.6.1-dev.98128d1" : void 0) ?? process.env.npm_package_version ?? "unknown";
1448
+ var CLI_VERSION = (true ? "3.6.1-dev.a11d5f5" : void 0) ?? process.env.npm_package_version ?? "unknown";
1446
1449
  function getCliVersion() {
1447
1450
  return CLI_VERSION;
1448
1451
  }
@@ -2638,6 +2641,122 @@ function isSessionDbRecoveryRecord(value) {
2638
2641
  );
2639
2642
  }
2640
2643
 
2644
+ // src/lib/opencode/session-deletion-report.ts
2645
+ import { appendFileSync, mkdirSync, readFileSync as readFileSync3, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
2646
+ import { dirname as dirname4, join as join4 } from "node:path";
2647
+ var SESSION_DELETION_SPOOL_MAX_RECORDS = 1e3;
2648
+ var MAX_DIAGNOSTIC_LENGTH = 200;
2649
+ function sessionDeletionReportPath(homeDir, env) {
2650
+ const override = env.EVIDENT_SESSION_DELETION_REPORT?.trim();
2651
+ return override || join4(homeDir, ".local", "state", "evident", "session-deletions.jsonl");
2652
+ }
2653
+ function appendSessionDeletionRecords(path, records) {
2654
+ if (records.length === 0) return 0;
2655
+ mkdirSync(dirname4(path), { recursive: true });
2656
+ const current = readSessionDeletionRecords(path);
2657
+ if (current.readError !== null) {
2658
+ throw new Error(`cannot enforce the spool limit after reading ${path}: ${current.readError}`);
2659
+ }
2660
+ const combined = [...current.records, ...records];
2661
+ const kept = combined.slice(-SESSION_DELETION_SPOOL_MAX_RECORDS);
2662
+ const dropped = combined.length - kept.length;
2663
+ if (dropped > 0) {
2664
+ writeFileSync(path, `${kept.map((record) => JSON.stringify(record)).join("\n")}
2665
+ `, "utf8");
2666
+ } else {
2667
+ appendFileSync(path, `${records.map((record) => JSON.stringify(record)).join("\n")}
2668
+ `, "utf8");
2669
+ }
2670
+ return dropped;
2671
+ }
2672
+ function readSessionDeletionRecords(path) {
2673
+ let content;
2674
+ try {
2675
+ content = readFileSync3(path, "utf8");
2676
+ } catch (error2) {
2677
+ if (isMissingFile(error2)) return { records: [], skippedLines: 0, readError: null };
2678
+ const readError = boundedError(error2);
2679
+ console.error(`[session-deletion-report] could not read ${path}: ${readError}`);
2680
+ return { records: [], skippedLines: 0, readError };
2681
+ }
2682
+ let skippedLines = 0;
2683
+ const records = content.split("\n").flatMap((line) => {
2684
+ if (!line.trim()) return [];
2685
+ const record = parseRecord(line, path);
2686
+ if (record) return [record];
2687
+ skippedLines++;
2688
+ return [];
2689
+ });
2690
+ return { records, skippedLines, readError: null };
2691
+ }
2692
+ function removeDeliveredSessionDeletionRecords(path, delivered) {
2693
+ let content;
2694
+ try {
2695
+ content = readFileSync3(path, "utf8");
2696
+ } catch (error2) {
2697
+ if (isMissingFile(error2)) return;
2698
+ console.error(
2699
+ `[session-deletion-report] could not read ${path} before removing delivered records: ${boundedError(error2)}`
2700
+ );
2701
+ throw error2;
2702
+ }
2703
+ const toRemove = /* @__PURE__ */ new Map();
2704
+ for (const record of delivered) {
2705
+ toRemove.set(record.opencode_session_id, (toRemove.get(record.opencode_session_id) ?? 0) + 1);
2706
+ }
2707
+ const remaining = [];
2708
+ for (const line of content.split("\n")) {
2709
+ if (!line.trim()) continue;
2710
+ const record = parseRecord(line, path);
2711
+ const count = record ? toRemove.get(record.opencode_session_id) ?? 0 : 0;
2712
+ if (record && count > 0) {
2713
+ if (count === 1) toRemove.delete(record.opencode_session_id);
2714
+ else toRemove.set(record.opencode_session_id, count - 1);
2715
+ } else {
2716
+ remaining.push(line);
2717
+ }
2718
+ }
2719
+ try {
2720
+ if (remaining.length === 0) unlinkSync2(path);
2721
+ else writeFileSync(path, `${remaining.join("\n")}
2722
+ `, "utf8");
2723
+ } catch (error2) {
2724
+ if (isMissingFile(error2)) return;
2725
+ console.error(
2726
+ `[session-deletion-report] could not write ${path} after removing delivered records: ${boundedError(error2)}`
2727
+ );
2728
+ throw error2;
2729
+ }
2730
+ }
2731
+ function parseRecord(line, path) {
2732
+ try {
2733
+ const value = JSON.parse(line);
2734
+ if (isSessionDeletionRecord(value)) return value;
2735
+ console.error(`[session-deletion-report] skipped malformed record in ${path}: invalid shape`);
2736
+ return null;
2737
+ } catch (error2) {
2738
+ console.error(
2739
+ `[session-deletion-report] skipped malformed record in ${path}: ${boundedError(error2)}`
2740
+ );
2741
+ return null;
2742
+ }
2743
+ }
2744
+ function isSessionDeletionRecord(value) {
2745
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2746
+ const record = value;
2747
+ const policy = record.policy;
2748
+ if (!policy || typeof policy !== "object" || Array.isArray(policy)) return false;
2749
+ const fields = policy;
2750
+ return typeof record.opencode_session_id === "string" && record.opencode_session_id.length > 0 && typeof record.deleted_at === "string" && record.reason === "auto_cleanup" && (fields.max_age_ms === null || typeof fields.max_age_ms === "number") && (fields.max_count === null || typeof fields.max_count === "number") && (fields.last_activity_at === null || typeof fields.last_activity_at === "string");
2751
+ }
2752
+ function isMissingFile(error2) {
2753
+ return error2 instanceof Error && "code" in error2 && error2.code === "ENOENT";
2754
+ }
2755
+ function boundedError(error2) {
2756
+ const message = error2 instanceof Error ? error2.message : String(error2);
2757
+ return message.slice(0, MAX_DIAGNOSTIC_LENGTH);
2758
+ }
2759
+
2641
2760
  // src/lib/opencode/auth.ts
2642
2761
  function buildOpenCodeBasicAuthHeader(password) {
2643
2762
  return `Basic ${Buffer.from(["opencode", password].join(":")).toString("base64")}`;
@@ -4537,13 +4656,13 @@ async function readV2SessionErrorStream(client, options) {
4537
4656
  }
4538
4657
 
4539
4658
  // src/lib/opencode/credential-import-v2.ts
4540
- import { readFileSync as readFileSync4 } from "node:fs";
4541
- import { join as join5 } from "node:path";
4659
+ import { readFileSync as readFileSync5 } from "node:fs";
4660
+ import { join as join6 } from "node:path";
4542
4661
 
4543
4662
  // src/lib/openai-usage.ts
4544
- import { readFileSync as readFileSync3 } from "node:fs";
4663
+ import { readFileSync as readFileSync4 } from "node:fs";
4545
4664
  import { homedir as homedir3 } from "node:os";
4546
- import { join as join4 } from "node:path";
4665
+ import { join as join5 } from "node:path";
4547
4666
  var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
4548
4667
  var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
4549
4668
  var OpenAiUsageError = class extends Error {
@@ -4557,7 +4676,7 @@ function isLocalCredentialProblem2(err) {
4557
4676
  }
4558
4677
  function readOpenCodeChatGptCredentials() {
4559
4678
  try {
4560
- const raw = readFileSync3(join4(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4679
+ const raw = readFileSync4(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
4561
4680
  let parsed;
4562
4681
  try {
4563
4682
  parsed = JSON.parse(raw);
@@ -4789,7 +4908,7 @@ function emptyResult(unsupported = [], outcome = "completed") {
4789
4908
  }
4790
4909
  function readOpenCodeCredentials(homeDir, log3) {
4791
4910
  try {
4792
- const raw = readFileSync4(join5(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
4911
+ const raw = readFileSync5(join6(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
4793
4912
  let parsed;
4794
4913
  try {
4795
4914
  parsed = JSON.parse(raw);
@@ -4989,9 +5108,9 @@ async function importOpenCodeV2Credentials({
4989
5108
 
4990
5109
  // src/lib/opencode/session-db-boot.ts
4991
5110
  import { spawn as spawn2 } from "node:child_process";
4992
- import { mkdirSync, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
5111
+ import { mkdirSync as mkdirSync2, statSync as statSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
4993
5112
  import { homedir as homedir4 } from "node:os";
4994
- import { dirname as dirname4, resolve as resolvePath } from "node:path";
5113
+ import { dirname as dirname5, resolve as resolvePath } from "node:path";
4995
5114
 
4996
5115
  // src/lib/runner-synchroniser.ts
4997
5116
  import { spawn } from "node:child_process";
@@ -5107,7 +5226,7 @@ function reportRecord(stage, outcome, reason, litestreamExitCode, options) {
5107
5226
  function clearMarker(options) {
5108
5227
  if (!options.noReplicateMarker) return;
5109
5228
  try {
5110
- unlinkSync2(options.noReplicateMarker);
5229
+ unlinkSync3(options.noReplicateMarker);
5111
5230
  } catch (error2) {
5112
5231
  if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return;
5113
5232
  options.log(
@@ -5119,8 +5238,8 @@ function clearMarker(options) {
5119
5238
  function markNoReplicate(options, message) {
5120
5239
  if (options.noReplicateMarker) {
5121
5240
  try {
5122
- mkdirSync(dirname4(options.noReplicateMarker), { recursive: true });
5123
- writeFileSync(options.noReplicateMarker, "");
5241
+ mkdirSync2(dirname5(options.noReplicateMarker), { recursive: true });
5242
+ writeFileSync2(options.noReplicateMarker, "");
5124
5243
  } catch (error2) {
5125
5244
  options.log(
5126
5245
  `Could not write session-DB no-replicate marker ${options.noReplicateMarker}: ${error2 instanceof Error ? error2.message : String(error2)}`,
@@ -5133,7 +5252,7 @@ function markNoReplicate(options, message) {
5133
5252
  function discardSessionDbDebris(options) {
5134
5253
  for (const path of [options.dbPath, `${options.dbPath}-wal`, `${options.dbPath}-shm`]) {
5135
5254
  try {
5136
- unlinkSync2(path);
5255
+ unlinkSync3(path);
5137
5256
  } catch (error2) {
5138
5257
  if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") continue;
5139
5258
  options.log(
@@ -5274,8 +5393,8 @@ async function ensureLitestreamConfig(options, env) {
5274
5393
  return null;
5275
5394
  }
5276
5395
  try {
5277
- mkdirSync(dirname4(configPath), { recursive: true });
5278
- writeFileSync(configPath, rendered.stdout);
5396
+ mkdirSync2(dirname5(configPath), { recursive: true });
5397
+ writeFileSync2(configPath, rendered.stdout);
5279
5398
  } catch (error2) {
5280
5399
  options.log(
5281
5400
  `Could not write ${configPath}: ${error2 instanceof Error ? error2.message : String(error2)}`,
@@ -5540,8 +5659,8 @@ async function restoreAndVerifySessionDb(options) {
5540
5659
 
5541
5660
  // src/lib/opencode/session-db-provenance.ts
5542
5661
  import { createRequire } from "node:module";
5543
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
5544
- import { dirname as dirname5, join as join6 } from "node:path";
5662
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
5663
+ import { dirname as dirname6, join as join7 } from "node:path";
5545
5664
  var require2 = createRequire(import.meta.url);
5546
5665
  function readSessionDbMigrationIds(dbPath) {
5547
5666
  let db;
@@ -5578,12 +5697,12 @@ function readSessionDbMigrationIds(dbPath) {
5578
5697
  }
5579
5698
  function sessionDbProvenanceStatePath(homeDir, env) {
5580
5699
  const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
5581
- return override || join6(homeDir, ".local", "state", "evident", "session-db-provenance.json");
5700
+ return override || join7(homeDir, ".local", "state", "evident", "session-db-provenance.json");
5582
5701
  }
5583
5702
  function loadSessionDbProvenanceState(path) {
5584
5703
  let value;
5585
5704
  try {
5586
- value = JSON.parse(readFileSync5(path, "utf8"));
5705
+ value = JSON.parse(readFileSync6(path, "utf8"));
5587
5706
  } catch (error2) {
5588
5707
  if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return {};
5589
5708
  console.error(
@@ -5607,8 +5726,8 @@ function loadSessionDbProvenanceState(path) {
5607
5726
  }
5608
5727
  function saveSessionDbProvenanceState(path, state) {
5609
5728
  try {
5610
- mkdirSync2(dirname5(path), { recursive: true });
5611
- writeFileSync2(path, `${JSON.stringify(state, null, 2)}
5729
+ mkdirSync3(dirname6(path), { recursive: true });
5730
+ writeFileSync3(path, `${JSON.stringify(state, null, 2)}
5612
5731
  `, "utf8");
5613
5732
  } catch (error2) {
5614
5733
  console.error(
@@ -6256,15 +6375,15 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
6256
6375
 
6257
6376
  // src/lib/opencode/session-db-size.ts
6258
6377
  import { statSync as statSync4 } from "node:fs";
6259
- import { join as join7 } from "node:path";
6378
+ import { join as join8 } from "node:path";
6260
6379
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
6261
6380
  function statSessionDbBytes(homeDir) {
6262
- const dbPath = join7(homeDir, ".local", "share", "opencode", "opencode.db");
6381
+ const dbPath = join8(homeDir, ".local", "share", "opencode", "opencode.db");
6263
6382
  try {
6264
6383
  return statSync4(dbPath).size;
6265
6384
  } catch (err) {
6266
- const isMissingFile = err instanceof Error && "code" in err && err.code === "ENOENT";
6267
- if (!isMissingFile) {
6385
+ const isMissingFile2 = err instanceof Error && "code" in err && err.code === "ENOENT";
6386
+ if (!isMissingFile2) {
6268
6387
  console.error(
6269
6388
  `[statSessionDbBytes] could not stat ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
6270
6389
  );
@@ -6289,12 +6408,12 @@ function buildSessionStoreSizeWarning(input) {
6289
6408
  // src/lib/opencode/log-tail.ts
6290
6409
  import { statSync as statSync5 } from "node:fs";
6291
6410
  import { homedir as homedir5 } from "node:os";
6292
- import { join as join8 } from "node:path";
6411
+ import { join as join9 } from "node:path";
6293
6412
  import { open as open2, stat } from "node:fs/promises";
6294
6413
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
6295
6414
  function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
6296
- const dataDir = env.XDG_DATA_HOME || join8(homeDir, ".local", "share");
6297
- return join8(dataDir, "opencode", "log", "opencode.log");
6415
+ const dataDir = env.XDG_DATA_HOME || join9(homeDir, ".local", "share");
6416
+ return join9(dataDir, "opencode", "log", "opencode.log");
6298
6417
  }
6299
6418
  function isEnoent(error2) {
6300
6419
  return error2?.code === "ENOENT";
@@ -6961,10 +7080,10 @@ var RunnerConnection = class _RunnerConnection {
6961
7080
  };
6962
7081
 
6963
7082
  // src/lib/tunnel/ready-marker.ts
6964
- import { writeFileSync as writeFileSync3 } from "node:fs";
7083
+ import { writeFileSync as writeFileSync4 } from "node:fs";
6965
7084
  function writeTunnelReadyMarker(path, agentId) {
6966
7085
  try {
6967
- writeFileSync3(path, `${agentId}
7086
+ writeFileSync4(path, `${agentId}
6968
7087
  `);
6969
7088
  return { ok: true };
6970
7089
  } catch (error2) {
@@ -6989,7 +7108,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
6989
7108
  }
6990
7109
 
6991
7110
  // src/lib/process-liveness.ts
6992
- import { readFileSync as readFileSync6 } from "node:fs";
7111
+ import { readFileSync as readFileSync7 } from "node:fs";
6993
7112
  function isProcessAlive(pid) {
6994
7113
  try {
6995
7114
  process.kill(pid, 0);
@@ -7004,7 +7123,7 @@ function isProcessAlive(pid) {
7004
7123
  }
7005
7124
  if (process.platform !== "linux") return true;
7006
7125
  try {
7007
- const status2 = readFileSync6(`/proc/${pid}/status`, "utf8");
7126
+ const status2 = readFileSync7(`/proc/${pid}/status`, "utf8");
7008
7127
  return !/^State:\s+Z(?:\s|$)/m.test(status2);
7009
7128
  } catch (error2) {
7010
7129
  console.error(
@@ -7363,12 +7482,12 @@ function createSessionStoreHealth(options = {}) {
7363
7482
  import { homedir as homedir6 } from "node:os";
7364
7483
 
7365
7484
  // src/lib/runner-file-sync.ts
7366
- import { join as join10 } from "node:path";
7485
+ import { join as join11 } from "node:path";
7367
7486
 
7368
7487
  // src/lib/file-push.ts
7369
7488
  import { randomUUID } from "node:crypto";
7370
7489
  import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
7371
- import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
7490
+ import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join10, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
7372
7491
  var FILE_MODE = 384;
7373
7492
  var DIRECTORY_MODE = 448;
7374
7493
  async function writePushedFile(request3) {
@@ -7399,9 +7518,9 @@ async function writePushedFile(request3) {
7399
7518
  }
7400
7519
  try {
7401
7520
  const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
7402
- dirname6(candidate)
7521
+ dirname7(candidate)
7403
7522
  );
7404
- const realTarget = join9(existingAncestor, ...missingSegments, basename2(candidate));
7523
+ const realTarget = join10(existingAncestor, ...missingSegments, basename2(candidate));
7405
7524
  const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
7406
7525
  if (allowedDirectory === null) {
7407
7526
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
@@ -7411,8 +7530,8 @@ async function writePushedFile(request3) {
7411
7530
  }
7412
7531
  if (missingSegments.length > 0) {
7413
7532
  await createMissingDirectories(existingAncestor, missingSegments);
7414
- const realParent = await realpath(dirname6(realTarget));
7415
- if (realParent !== dirname6(realTarget) || !contains(allowedDirectory, realTarget)) {
7533
+ const realParent = await realpath(dirname7(realTarget));
7534
+ if (realParent !== dirname7(realTarget) || !contains(allowedDirectory, realTarget)) {
7416
7535
  return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
7417
7536
  path: realTarget,
7418
7537
  bytes,
@@ -7437,7 +7556,7 @@ function expandAndValidate(requestedPath, homeDir) {
7437
7556
  if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
7438
7557
  return null;
7439
7558
  }
7440
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join9(homeDir, requestedPath.slice(2)) : requestedPath;
7559
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7441
7560
  if (expanded.split(/[/\\]/).includes("..")) {
7442
7561
  return null;
7443
7562
  }
@@ -7455,7 +7574,7 @@ async function resolveNearestExistingAncestor(directory) {
7455
7574
  try {
7456
7575
  return { existingAncestor: await realpath(current), missingSegments };
7457
7576
  } catch (err) {
7458
- const parent = dirname6(current);
7577
+ const parent = dirname7(current);
7459
7578
  if (err.code !== "ENOENT" || parent === current) {
7460
7579
  throw err;
7461
7580
  }
@@ -7510,13 +7629,13 @@ function contains(realDirectory, realTarget) {
7510
7629
  async function createMissingDirectories(existingAncestor, missingSegments) {
7511
7630
  let current = existingAncestor;
7512
7631
  for (const segment of missingSegments) {
7513
- current = join9(current, segment);
7632
+ current = join10(current, segment);
7514
7633
  await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
7515
7634
  await chmod(current, DIRECTORY_MODE);
7516
7635
  }
7517
7636
  }
7518
7637
  async function writeAtomically(realTarget, content) {
7519
- const temporaryPath = join9(dirname6(realTarget), `.evident-push-${randomUUID()}.tmp`);
7638
+ const temporaryPath = join10(dirname7(realTarget), `.evident-push-${randomUUID()}.tmp`);
7520
7639
  let handle;
7521
7640
  try {
7522
7641
  handle = await open3(temporaryPath, "wx", FILE_MODE);
@@ -7646,12 +7765,12 @@ var NOT_APPLIED = {
7646
7765
  opencodeAuthApplied: false
7647
7766
  };
7648
7767
  function isClaudeCredentialPath(requestedPath, homeDir) {
7649
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7650
- return expanded === join10(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
7768
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join11(homeDir, requestedPath.slice(2)) : requestedPath;
7769
+ return expanded === join11(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
7651
7770
  }
7652
7771
  function isOpenCodeAuthPath(requestedPath, homeDir) {
7653
- const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
7654
- return expanded === join10(homeDir, ...OPENCODE_AUTH_SEGMENTS);
7772
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join11(homeDir, requestedPath.slice(2)) : requestedPath;
7773
+ return expanded === join11(homeDir, ...OPENCODE_AUTH_SEGMENTS);
7655
7774
  }
7656
7775
  async function applyOne(options, file) {
7657
7776
  const label = `${file.id.slice(0, 8)} (${file.path})`;
@@ -13930,7 +14049,7 @@ Port ${port} is already in use.`));
13930
14049
  }
13931
14050
 
13932
14051
  // src/lib/runner-credentials.ts
13933
- import { chmodSync as chmodSync2, writeFileSync as writeFileSync4 } from "node:fs";
14052
+ import { chmodSync as chmodSync2, writeFileSync as writeFileSync5 } from "node:fs";
13934
14053
  import { spawn as spawn5 } from "node:child_process";
13935
14054
  var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
13936
14055
  var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
@@ -14173,8 +14292,8 @@ async function configureGitHubAccess({ env, log: log3 }) {
14173
14292
  }
14174
14293
  try {
14175
14294
  env.GIT_CONFIG_GLOBAL = GIT_CONFIG_GLOBAL;
14176
- writeFileSync4(GIT_CONFIG_GLOBAL, "");
14177
- writeFileSync4(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
14295
+ writeFileSync5(GIT_CONFIG_GLOBAL, "");
14296
+ writeFileSync5(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
14178
14297
  chmodSync2(GIT_CREDENTIAL_HELPER, 448);
14179
14298
  const config = [
14180
14299
  ["user.name", env.GIT_USER_NAME ?? "evident-bot"],
@@ -14207,7 +14326,7 @@ async function configureGitHubAccess({ env, log: log3 }) {
14207
14326
  // src/lib/opencode/config-overlay.ts
14208
14327
  import { execFileSync as execFileSync2 } from "node:child_process";
14209
14328
  import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
14210
- import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
14329
+ import { isAbsolute as isAbsolute3, join as join12, resolve as resolve4 } from "node:path";
14211
14330
  function parseConfigContent(content) {
14212
14331
  if (!content?.trim()) return {};
14213
14332
  const parsed = JSON.parse(content);
@@ -14237,7 +14356,7 @@ function applyRunnerOpenCodeConfig({
14237
14356
  return;
14238
14357
  }
14239
14358
  const source = isAbsolute3(overlayPath) ? overlayPath : resolve4(cwd, overlayPath);
14240
- const target = isFile(join11(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
14359
+ const target = isFile(join12(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
14241
14360
  if (!isFile(source)) {
14242
14361
  log3(
14243
14362
  `RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
@@ -14245,7 +14364,7 @@ function applyRunnerOpenCodeConfig({
14245
14364
  );
14246
14365
  return;
14247
14366
  }
14248
- copyFileSync(source, join11(cwd, target));
14367
+ copyFileSync(source, join12(cwd, target));
14249
14368
  try {
14250
14369
  execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
14251
14370
  stdio: "ignore"
@@ -14254,13 +14373,13 @@ function applyRunnerOpenCodeConfig({
14254
14373
  const detail = error2 instanceof Error ? error2.message : String(error2);
14255
14374
  log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
14256
14375
  }
14257
- log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
14376
+ log3(`Applied runner OpenCode config ${source} to ${join12(cwd, target)}`);
14258
14377
  }
14259
14378
 
14260
14379
  // src/lib/opencode/runtime-provenance.ts
14261
- import { readFileSync as readFileSync7, readdirSync } from "node:fs";
14380
+ import { readFileSync as readFileSync8, readdirSync } from "node:fs";
14262
14381
  import { homedir as homedir7 } from "node:os";
14263
- import { join as join12 } from "node:path";
14382
+ import { join as join13 } from "node:path";
14264
14383
  var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
14265
14384
  var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
14266
14385
  function errorKind(error2) {
@@ -14328,7 +14447,7 @@ function parseJsonc(value) {
14328
14447
  return JSON.parse(output);
14329
14448
  }
14330
14449
  function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
14331
- return join12(env.XDG_CACHE_HOME ?? join12(homeDir, ".cache"), "opencode");
14450
+ return join13(env.XDG_CACHE_HOME ?? join13(homeDir, ".cache"), "opencode");
14332
14451
  }
14333
14452
  function readConfiguredPluginSpecs({
14334
14453
  cwd = process.cwd(),
@@ -14337,10 +14456,10 @@ function readConfiguredPluginSpecs({
14337
14456
  let config;
14338
14457
  try {
14339
14458
  try {
14340
- config = parseJsonc(readFileSync7(join12(cwd, "opencode.jsonc"), "utf8"));
14459
+ config = parseJsonc(readFileSync8(join13(cwd, "opencode.jsonc"), "utf8"));
14341
14460
  } catch (error2) {
14342
14461
  if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
14343
- config = parseJsonc(readFileSync7(join12(cwd, "opencode.json"), "utf8"));
14462
+ config = parseJsonc(readFileSync8(join13(cwd, "opencode.json"), "utf8"));
14344
14463
  }
14345
14464
  } catch (error2) {
14346
14465
  log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
@@ -14368,7 +14487,7 @@ function resolveInstalledPluginVersions({
14368
14487
  homeDir = homedir7(),
14369
14488
  log: log3
14370
14489
  }) {
14371
- const packagesRoot = join12(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
14490
+ const packagesRoot = join13(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
14372
14491
  let directories;
14373
14492
  try {
14374
14493
  directories = readdirSync(packagesRoot, { withFileTypes: true });
@@ -14389,8 +14508,8 @@ function resolveInstalledPluginVersions({
14389
14508
  let resolvedVersion = null;
14390
14509
  try {
14391
14510
  const packageJson = JSON.parse(
14392
- readFileSync7(
14393
- join12(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
14511
+ readFileSync8(
14512
+ join13(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
14394
14513
  "utf8"
14395
14514
  )
14396
14515
  );
@@ -14411,7 +14530,7 @@ function resolveInstalledPluginVersions({
14411
14530
  }
14412
14531
 
14413
14532
  // src/lib/credential-sync.ts
14414
- import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
14533
+ import { renameSync, writeFileSync as writeFileSync6 } from "node:fs";
14415
14534
  var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
14416
14535
  var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
14417
14536
  var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
@@ -14445,7 +14564,7 @@ function writeMarker(markerPath, outcomes, log3) {
14445
14564
  `;
14446
14565
  const temporaryPath = `${markerPath}.tmp`;
14447
14566
  try {
14448
- writeFileSync5(temporaryPath, body, { mode: 384 });
14567
+ writeFileSync6(temporaryPath, body, { mode: 384 });
14449
14568
  renameSync(temporaryPath, markerPath);
14450
14569
  } catch (error2) {
14451
14570
  log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
@@ -14934,7 +15053,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
14934
15053
  if (trimmed === "") {
14935
15054
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
14936
15055
  }
14937
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join13(homeDir, trimmed.slice(2)) : trimmed;
15056
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join14(homeDir, trimmed.slice(2)) : trimmed;
14938
15057
  if (!isAbsolute4(expanded)) {
14939
15058
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
14940
15059
  }
@@ -15590,7 +15709,7 @@ function shouldWarnForReclaimSkip(reason) {
15590
15709
  return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
15591
15710
  }
15592
15711
  function sessionDbPath() {
15593
- return join13(homedir8(), ".local", "share", "opencode", "opencode.db");
15712
+ return join14(homedir8(), ".local", "share", "opencode", "opencode.db");
15594
15713
  }
15595
15714
  function logSessionCleanupActivity(state, phase, entry) {
15596
15715
  logActivity(state, {
@@ -15599,6 +15718,47 @@ function logSessionCleanupActivity(state, phase, entry) {
15599
15718
  metadata: { ...entry.metadata, phase }
15600
15719
  });
15601
15720
  }
15721
+ async function drainSessionDeletionReports(state) {
15722
+ const path = sessionDeletionReportPath(homedir8(), process.env);
15723
+ let delivered = 0;
15724
+ let failure = null;
15725
+ try {
15726
+ const spool = readSessionDeletionRecords(path);
15727
+ if (spool.readError !== null) {
15728
+ failure = `could not read spool: ${spool.readError}`;
15729
+ } else if (spool.records.length === 0) {
15730
+ return;
15731
+ } else if (!state.agentId || !state.authHeader) {
15732
+ failure = "runner authentication is not available";
15733
+ } else {
15734
+ for (let offset = 0; offset < spool.records.length; offset += 200) {
15735
+ const batch = spool.records.slice(offset, offset + 200);
15736
+ const result = await reportSessionDeletions(state.agentId, state.authHeader, batch);
15737
+ if (!result.ok) {
15738
+ failure = result.error ?? "unknown API error";
15739
+ break;
15740
+ }
15741
+ removeDeliveredSessionDeletionRecords(path, batch);
15742
+ delivered += batch.length;
15743
+ }
15744
+ }
15745
+ } catch (error2) {
15746
+ failure = error2 instanceof Error ? error2.message : String(error2);
15747
+ }
15748
+ const pending = readSessionDeletionRecords(path);
15749
+ const pendingCount = pending.readError === null ? pending.records.length : null;
15750
+ const message = `Session deletion reports: delivered ${delivered}, ${pendingCount === null ? "pending count unavailable" : `${pendingCount} pending`}` + (failure === null ? "." : `; delivery failed: ${failure}.`);
15751
+ logActivity(state, {
15752
+ type: "info",
15753
+ level: failure === null ? "info" : "warn",
15754
+ message,
15755
+ metadata: {
15756
+ outcome: failure === null ? "session-deletion-reports-drained" : "session-deletion-reports-pending",
15757
+ delivered,
15758
+ pending: pendingCount
15759
+ }
15760
+ });
15761
+ }
15602
15762
  function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
15603
15763
  const record = {
15604
15764
  v: 1,
@@ -15656,17 +15816,22 @@ async function runSweep(state, driver, config) {
15656
15816
  let failedThrew = 0;
15657
15817
  let skippedNewlyActive = 0;
15658
15818
  let remaining = 0;
15819
+ const deletionRecords = [];
15659
15820
  let protectedNow = driver.protectedSessionIds();
15660
15821
  if (sessions !== null) {
15661
- const toDelete = selectSessionsToDelete(
15662
- sessions.map((s) => ({ id: s.id, lastActivityMs: sessionLastActivityMs(s) })),
15663
- {
15664
- maxAgeMs: config.maxAgeMs,
15665
- maxCount: config.maxCount,
15666
- nowMs: Date.now(),
15667
- protectedIds: protectedNow
15668
- }
15822
+ const snapshots = sessions.map((session) => ({
15823
+ id: session.id,
15824
+ lastActivityMs: sessionLastActivityMs(session)
15825
+ }));
15826
+ const lastActivityById = new Map(
15827
+ snapshots.map(({ id, lastActivityMs }) => [id, lastActivityMs])
15669
15828
  );
15829
+ const toDelete = selectSessionsToDelete(snapshots, {
15830
+ maxAgeMs: config.maxAgeMs,
15831
+ maxCount: config.maxCount,
15832
+ nowMs: Date.now(),
15833
+ protectedIds: protectedNow
15834
+ });
15670
15835
  const eligibleToDelete = toDelete.filter(
15671
15836
  (id) => (state.sessionDeleteFailures.get(id)?.isolatedFailures ?? 0) < SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES
15672
15837
  );
@@ -15686,6 +15851,17 @@ async function runSweep(state, driver, config) {
15686
15851
  const outcome = await deleteSession(state.port, id, state.opencodeClient ?? void 0);
15687
15852
  if (outcome.ok) {
15688
15853
  deleted++;
15854
+ const lastActivityMs = lastActivityById.get(id) ?? null;
15855
+ deletionRecords.push({
15856
+ opencode_session_id: id,
15857
+ deleted_at: (/* @__PURE__ */ new Date()).toISOString(),
15858
+ reason: "auto_cleanup",
15859
+ policy: {
15860
+ max_age_ms: config.maxAgeMs ?? null,
15861
+ max_count: config.maxCount ?? null,
15862
+ last_activity_at: lastActivityMs === null ? null : new Date(lastActivityMs).toISOString()
15863
+ }
15864
+ });
15689
15865
  } else if (outcome.status === "threw") {
15690
15866
  failed++;
15691
15867
  failedThrew++;
@@ -15741,6 +15917,32 @@ async function runSweep(state, driver, config) {
15741
15917
  message: `Session cleanup: inspected ${sessions.length}, deleted ${deleted}${failedNote}${skippedNote}${remainingNote} (${mode})`
15742
15918
  });
15743
15919
  }
15920
+ if (deletionRecords.length > 0) {
15921
+ try {
15922
+ const dropped = appendSessionDeletionRecords(
15923
+ sessionDeletionReportPath(homedir8(), process.env),
15924
+ deletionRecords
15925
+ );
15926
+ if (dropped > 0) {
15927
+ logSessionCleanupActivity(state, "sweep", {
15928
+ type: "info",
15929
+ level: "warn",
15930
+ message: `Session cleanup: dropped ${dropped} oldest pending deletion report(s) after reaching the spool limit`,
15931
+ metadata: { outcome: "deletion-reports-dropped", dropped_count: dropped }
15932
+ });
15933
+ }
15934
+ } catch (error2) {
15935
+ logSessionCleanupActivity(state, "sweep", {
15936
+ type: "info",
15937
+ level: "warn",
15938
+ message: `Session cleanup: could not persist ${deletionRecords.length} confirmed deletion report(s): ${error2 instanceof Error ? error2.message : String(error2)}`,
15939
+ metadata: {
15940
+ outcome: "deletion-report-spool-write-failed",
15941
+ records: deletionRecords.length
15942
+ }
15943
+ });
15944
+ }
15945
+ }
15744
15946
  const sessionMetadata = {
15745
15947
  sessions_inspected: sessions?.length ?? null,
15746
15948
  sessions_deleted: deleted,
@@ -15810,6 +16012,7 @@ async function runSweep(state, driver, config) {
15810
16012
  }
15811
16013
  });
15812
16014
  }
16015
+ await drainSessionDeletionReports(state);
15813
16016
  } catch (error2) {
15814
16017
  const message = error2 instanceof Error ? error2.message : String(error2);
15815
16018
  logSessionCleanupActivity(state, "sweep", {
@@ -16652,6 +16855,7 @@ async function run(options) {
16652
16855
  }
16653
16856
  }
16654
16857
  reportSessionDbRecovery(state);
16858
+ await drainSessionDeletionReports(state);
16655
16859
  if (sessionDbVerifyFatal) {
16656
16860
  throw new Error(
16657
16861
  sessionDbVerifyFatalReason === void 0 ? "SESSION-DB-INTEGRITY-EXHAUSTED: the corrupt session DB could not be proven separated from the active backup prefix or removed from disk" : `SESSION-DB-VERIFY-TIMEOUT: session DB verification timed out (${sessionDbVerifyFatalReason}); refusing to start`
@@ -16782,7 +16986,7 @@ async function run(options) {
16782
16986
  state.opencodeClient = openCodeClient;
16783
16987
  if (options.opencodePidFile && oc.process?.pid !== void 0) {
16784
16988
  try {
16785
- writeFileSync6(options.opencodePidFile, `${oc.process.pid}
16989
+ writeFileSync7(options.opencodePidFile, `${oc.process.pid}
16786
16990
  `, { mode: 384 });
16787
16991
  chmodSync3(options.opencodePidFile, 384);
16788
16992
  } catch (error2) {
@@ -16892,7 +17096,7 @@ async function run(options) {
16892
17096
  let existingPid;
16893
17097
  if (existsSync3(options.litestreamPidFile)) {
16894
17098
  try {
16895
- const rawPid = readFileSync8(options.litestreamPidFile, "utf8").trim();
17099
+ const rawPid = readFileSync9(options.litestreamPidFile, "utf8").trim();
16896
17100
  const parsedPid = Number(rawPid);
16897
17101
  if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
16898
17102
  existingPid = parsedPid;
@@ -16929,7 +17133,7 @@ async function run(options) {
16929
17133
  });
16930
17134
  try {
16931
17135
  if (litestreamProcess.pid !== void 0) {
16932
- writeFileSync6(options.litestreamPidFile, `${litestreamProcess.pid}
17136
+ writeFileSync7(options.litestreamPidFile, `${litestreamProcess.pid}
16933
17137
  `, {
16934
17138
  mode: 384
16935
17139
  });