@evident-ai/cli 3.3.1-dev.70eaa1c → 3.3.1-dev.8abe4d1

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
@@ -934,6 +934,7 @@ import { homedir } from "os";
934
934
  import { join } from "path";
935
935
  var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
936
936
  var KEYCHAIN_SERVICE = "Claude Code-credentials";
937
+ var CLAUDE_CREDENTIALS_SEGMENTS = [".claude", ".credentials.json"];
937
938
  function parseClaudeCliCredentials(raw) {
938
939
  let parsed;
939
940
  try {
@@ -967,7 +968,7 @@ function readClaudeCliCredentials() {
967
968
  }
968
969
  }
969
970
  try {
970
- const raw = readFileSync(join(homedir(), ".claude", ".credentials.json"), "utf-8");
971
+ const raw = readFileSync(join(homedir(), ...CLAUDE_CREDENTIALS_SEGMENTS), "utf-8");
971
972
  return parseClaudeCliCredentials(raw);
972
973
  } catch (err) {
973
974
  const code = err.code;
@@ -1063,7 +1064,7 @@ async function claudeUsage() {
1063
1064
 
1064
1065
  // src/commands/run.ts
1065
1066
  import { homedir as homedir3 } from "os";
1066
- import { isAbsolute as isAbsolute2, join as join4, parse, resolve as resolvePath } from "path";
1067
+ import { isAbsolute as isAbsolute2, join as join5, parse, resolve as resolvePath } from "path";
1067
1068
  import chalk6 from "chalk";
1068
1069
 
1069
1070
  // ../../packages/types/src/agents/index.ts
@@ -2995,6 +2996,9 @@ function claudeUsageFailureLogLevel(consecutiveFailures) {
2995
2996
  // src/lib/channels/driver.ts
2996
2997
  import { homedir as homedir2 } from "os";
2997
2998
 
2999
+ // src/lib/runner-file-sync.ts
3000
+ import { join as join4 } from "path";
3001
+
2998
3002
  // src/lib/file-push.ts
2999
3003
  import { randomUUID } from "crypto";
3000
3004
  import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
@@ -3188,17 +3192,20 @@ async function syncPendingRunnerFiles(options) {
3188
3192
  for (const id of options.ackFailures.keys()) {
3189
3193
  if (!pendingIds.has(id)) options.ackFailures.delete(id);
3190
3194
  }
3191
- if (pending.length === 0) return 0;
3195
+ if (pending.length === 0) return { applied: 0, claudeCredentialApplied: false };
3192
3196
  options.log({
3193
3197
  level: "info",
3194
3198
  message: `Runner file sync: ${pending.length} file(s) queued for this runner`
3195
3199
  });
3196
3200
  let applied = 0;
3201
+ let claudeCredentialApplied = false;
3197
3202
  for (const file of pending) {
3198
3203
  if ((options.ackFailures.get(file.id) ?? 0) >= MAX_ACK_ATTEMPTS) continue;
3199
- if (await applyOne(options, file)) applied += 1;
3204
+ const outcome = await applyOne(options, file);
3205
+ if (outcome.applied) applied += 1;
3206
+ if (outcome.claudeCredentialApplied) claudeCredentialApplied = true;
3200
3207
  }
3201
- return applied;
3208
+ return { applied, claudeCredentialApplied };
3202
3209
  }
3203
3210
  async function listPendingFiles(options) {
3204
3211
  let res;
@@ -3259,6 +3266,11 @@ function asPendingFile(entry) {
3259
3266
  if (typeof size !== "number" || !Number.isFinite(size) || size < 0) return null;
3260
3267
  return { id, path, size };
3261
3268
  }
3269
+ var NOT_APPLIED = { applied: false, claudeCredentialApplied: false };
3270
+ function isClaudeCredentialPath(requestedPath, homeDir) {
3271
+ const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join4(homeDir, requestedPath.slice(2)) : requestedPath;
3272
+ return expanded === join4(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
3273
+ }
3262
3274
  async function applyOne(options, file) {
3263
3275
  const label = `${file.id.slice(0, 8)} (${file.path})`;
3264
3276
  if (options.allowedDirectories.length === 0) {
@@ -3267,7 +3279,7 @@ async function applyOne(options, file) {
3267
3279
  message: `Runner file ${label} rejected: file sync is not enabled on this runner (start it with --enable-file-sync-to)`
3268
3280
  });
3269
3281
  await ack(options, file, "rejected", "file_sync_disabled");
3270
- return false;
3282
+ return NOT_APPLIED;
3271
3283
  }
3272
3284
  if (file.size > MAX_FILE_PUSH_BYTES) {
3273
3285
  options.log({
@@ -3275,12 +3287,12 @@ async function applyOne(options, file) {
3275
3287
  message: `Runner file ${label} rejected: declared ${file.size} bytes, the limit is ${MAX_FILE_PUSH_BYTES}`
3276
3288
  });
3277
3289
  await ack(options, file, "rejected", "file_too_large");
3278
- return false;
3290
+ return NOT_APPLIED;
3279
3291
  }
3280
3292
  const download = await downloadContent(options, file, label);
3281
3293
  if (!download.ok) {
3282
3294
  if (download.terminal) await ack(options, file, "rejected", download.code);
3283
- return false;
3295
+ return NOT_APPLIED;
3284
3296
  }
3285
3297
  let outcome;
3286
3298
  try {
@@ -3296,7 +3308,7 @@ async function applyOne(options, file) {
3296
3308
  message: `Runner file ${label} could not be written: ${describe(err)}`
3297
3309
  });
3298
3310
  await ack(options, file, "rejected", "write_failed");
3299
- return false;
3311
+ return NOT_APPLIED;
3300
3312
  }
3301
3313
  if (!outcome.ok) {
3302
3314
  options.log({
@@ -3304,14 +3316,17 @@ async function applyOne(options, file) {
3304
3316
  message: `Runner file ${label} rejected (${outcome.code}): ${outcome.message}`
3305
3317
  });
3306
3318
  await ack(options, file, "rejected", outcome.code);
3307
- return false;
3319
+ return NOT_APPLIED;
3308
3320
  }
3309
3321
  options.log({
3310
3322
  level: "info",
3311
3323
  message: `Runner file ${label} applied (${download.content.byteLength} bytes)`
3312
3324
  });
3313
3325
  await ack(options, file, "applied");
3314
- return true;
3326
+ return {
3327
+ applied: true,
3328
+ claudeCredentialApplied: isClaudeCredentialPath(file.path, options.homeDir)
3329
+ };
3315
3330
  }
3316
3331
  function durableDownloadCode(status2) {
3317
3332
  return status2 === 413 ? "file_too_large" : "write_failed";
@@ -3756,6 +3771,14 @@ var ChannelDriver = class _ChannelDriver {
3756
3771
  * same trick `lastProxiedActivityAt` uses.
3757
3772
  */
3758
3773
  appliedFileCount = 0;
3774
+ /**
3775
+ * Generation counter, NOT a tally (#1656): advances by exactly one per sync
3776
+ * batch that applied the Claude CLI credential file, not by how many
3777
+ * credential files were in that batch. `run.ts` only ever tests inequality
3778
+ * against the value it saw last cycle, so magnitude is meaningless — keep it
3779
+ * that way rather than "fixing" it into a count.
3780
+ */
3781
+ claudeCredentialApplyCount = 0;
3759
3782
  /**
3760
3783
  * The currently-executing `drainPending()` promise, or null when idle. Lets a
3761
3784
  * graceful shutdown (`waitForInFlight`) await an in-progress drain so a turn it
@@ -3848,7 +3871,7 @@ var ChannelDriver = class _ChannelDriver {
3848
3871
  if (this.syncingFiles) return 0;
3849
3872
  this.syncingFiles = true;
3850
3873
  try {
3851
- const applied = await syncPendingRunnerFiles({
3874
+ const result = await syncPendingRunnerFiles({
3852
3875
  agentId: this.agentId,
3853
3876
  apiUrl: this.apiUrl,
3854
3877
  getAuthHeader: this.getAuthHeader,
@@ -3858,8 +3881,9 @@ var ChannelDriver = class _ChannelDriver {
3858
3881
  ackFailures: this.fileAckFailures,
3859
3882
  log: this.log
3860
3883
  });
3861
- this.appliedFileCount += applied;
3862
- return applied;
3884
+ this.appliedFileCount += result.applied;
3885
+ if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
3886
+ return result.applied;
3863
3887
  } catch (err) {
3864
3888
  this.log({
3865
3889
  level: "error",
@@ -3950,12 +3974,24 @@ var ChannelDriver = class _ChannelDriver {
3950
3974
  * `appliedFiles` is monotonic so a pull that started AND finished between two
3951
3975
  * idle checks still shows up as an advance.
3952
3976
  *
3977
+ * A THIRD signal, `claudeCredentialApplies`, is a separate re-arm trigger
3978
+ * (#1656), not idle accounting: `run.ts` gates re-probing Claude usage
3979
+ * reporting on it advancing, so an unrelated file sync can never disturb a
3980
+ * healthy reporting cadence (#1627) — it never even reaches that trigger, let
3981
+ * alone gets declined by it. Keep this narrower signal OUT of `appliedFiles`,
3982
+ * whose consumer is idle-timeout suppression and must key on ANY file, not
3983
+ * just a Claude credential.
3984
+ *
3953
3985
  * CALLER CONTRACT: sample `inFlight` BEFORE calling `syncPendingFiles()` for
3954
3986
  * the cycle. `syncPendingFiles` sets the flag synchronously, so a caller that
3955
3987
  * samples afterwards reads `true` every single cycle and can never idle out.
3956
3988
  */
3957
3989
  fileSyncActivity() {
3958
- return { appliedFiles: this.appliedFileCount, inFlight: this.syncingFiles };
3990
+ return {
3991
+ appliedFiles: this.appliedFileCount,
3992
+ inFlight: this.syncingFiles,
3993
+ claudeCredentialApplies: this.claudeCredentialApplyCount
3994
+ };
3959
3995
  }
3960
3996
  /**
3961
3997
  * OpenCode session ids the session-cleanup sweep (issue #190) must NOT delete:
@@ -4815,6 +4851,17 @@ var ChannelDriver = class _ChannelDriver {
4815
4851
  message: `OpenCode session ${bound} for conversation ${conv.id.slice(0, 8)} no longer exists (deleted or DB reset) \u2014 creating a fresh session and rebinding.`,
4816
4852
  conversation_id: conv.id
4817
4853
  });
4854
+ const watcher = this.watchers.get(bound);
4855
+ if (watcher) {
4856
+ for (const [evidentMessageId, inFlight] of [...watcher.inFlight]) {
4857
+ this.recordReleasedOpencodeId(evidentMessageId, bound, inFlight.opencodeMessageId);
4858
+ this.removeInFlight(watcher, evidentMessageId);
4859
+ void this.postSignal(watcher.conv.id, evidentMessageId, "watcher_recovered", {
4860
+ recovery: "session_gone_released"
4861
+ });
4862
+ }
4863
+ this.watchers.delete(bound);
4864
+ }
4818
4865
  this.sessions.delete(conv.id);
4819
4866
  return { sessionId: await this.createAndBindSession(conv.id), created: true };
4820
4867
  }
@@ -7280,7 +7327,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
7280
7327
  if (trimmed === "") {
7281
7328
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
7282
7329
  }
7283
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join4(homeDir, trimmed.slice(2)) : trimmed;
7330
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join5(homeDir, trimmed.slice(2)) : trimmed;
7284
7331
  if (!isAbsolute2(expanded)) {
7285
7332
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
7286
7333
  }
@@ -7487,6 +7534,7 @@ async function driveChannels(state, driver) {
7487
7534
  let unreachableMs = 0;
7488
7535
  let lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
7489
7536
  let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
7537
+ let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
7490
7538
  while (state.running) {
7491
7539
  const cycleStartedAtMs = performance.now();
7492
7540
  let idleThisCycle = false;
@@ -7510,11 +7558,15 @@ async function driveChannels(state, driver) {
7510
7558
  state.messageCount += processed;
7511
7559
  const proxiedActivity = state.lastProxiedActivityAt !== lastSeenProxiedActivityAt;
7512
7560
  lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
7513
- const appliedFiles = driver.fileSyncActivity().appliedFiles;
7561
+ const fileActivitySnapshot = driver.fileSyncActivity();
7562
+ const appliedFiles = fileActivitySnapshot.appliedFiles;
7514
7563
  const filesApplied = appliedFiles !== lastSeenAppliedFiles;
7515
7564
  const fileActivity = carriedOverFileSync || filesApplied;
7516
7565
  lastSeenAppliedFiles = appliedFiles;
7517
- if (filesApplied) state.claudeUsageRearm?.();
7566
+ const claudeCredentialApplies = fileActivitySnapshot.claudeCredentialApplies;
7567
+ const claudeCredentialApplied = claudeCredentialApplies !== lastSeenClaudeApplies;
7568
+ lastSeenClaudeApplies = claudeCredentialApplies;
7569
+ if (claudeCredentialApplied) state.claudeUsageRearm?.();
7518
7570
  if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
7519
7571
  idlePolls = 0;
7520
7572
  idleMs = 0;
@@ -7583,7 +7635,7 @@ async function driveChannels(state, driver) {
7583
7635
  var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
7584
7636
  var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
7585
7637
  function sessionDbPath() {
7586
- return join4(homedir3(), ".local", "share", "opencode", "opencode.db");
7638
+ return join5(homedir3(), ".local", "share", "opencode", "opencode.db");
7587
7639
  }
7588
7640
  async function runSweep(state, driver, config) {
7589
7641
  const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
@@ -7724,13 +7776,13 @@ function scheduleClaudeUsageReporting(state, options) {
7724
7776
  phase = "probe-pending";
7725
7777
  state.claudeUsageTimer = setTimeout(() => void tick(true), FIRST_REPORT_DELAY_MS);
7726
7778
  };
7727
- const scheduleNextTick = (reportedSuccessfully) => {
7779
+ const scheduleNextTick = () => {
7728
7780
  if (rearmRequested) {
7729
7781
  rearmRequested = false;
7730
7782
  armProbe();
7731
7783
  return;
7732
7784
  }
7733
- phase = reportedSuccessfully ? "steady-pending-healthy" : "steady-pending-retry";
7785
+ phase = "steady-pending";
7734
7786
  state.claudeUsageTimer = setTimeout(() => void tick(false), nextReportDelayMs());
7735
7787
  };
7736
7788
  const rearm = () => {
@@ -7740,9 +7792,7 @@ function scheduleClaudeUsageReporting(state, options) {
7740
7792
  return;
7741
7793
  case "probe-pending":
7742
7794
  return;
7743
- case "steady-pending-healthy":
7744
- return;
7745
- case "steady-pending-retry":
7795
+ case "steady-pending":
7746
7796
  if (state.claudeUsageTimer) {
7747
7797
  clearTimeout(state.claudeUsageTimer);
7748
7798
  state.claudeUsageTimer = null;
@@ -7783,7 +7833,7 @@ function scheduleClaudeUsageReporting(state, options) {
7783
7833
  message: `Failed to report Claude usage: ${result.error}${claudeUsageFailureStreakSuffix(consecutiveFailures)}`
7784
7834
  });
7785
7835
  }
7786
- scheduleNextTick(result.ok);
7836
+ scheduleNextTick();
7787
7837
  } catch (error2) {
7788
7838
  if (error2 instanceof ClaudeUsageError && isLocalCredentialProblem(error2)) {
7789
7839
  if (mode === "on") {
@@ -7792,7 +7842,7 @@ function scheduleClaudeUsageReporting(state, options) {
7792
7842
  level: "warn",
7793
7843
  message: "Claude usage reporting is forced on but no usable Claude Code login was found \u2014 run `claude` to sign in; reporting will keep retrying"
7794
7844
  });
7795
- scheduleNextTick(false);
7845
+ scheduleNextTick();
7796
7846
  } else if (isProbe) {
7797
7847
  logActivity(state, {
7798
7848
  type: "info",
@@ -7807,7 +7857,7 @@ function scheduleClaudeUsageReporting(state, options) {
7807
7857
  level: "debug",
7808
7858
  message: `Claude usage reporting: ${error2.message}`
7809
7859
  });
7810
- scheduleNextTick(false);
7860
+ scheduleNextTick();
7811
7861
  }
7812
7862
  } else {
7813
7863
  consecutiveFailures++;
@@ -7817,7 +7867,7 @@ function scheduleClaudeUsageReporting(state, options) {
7817
7867
  level: claudeUsageFailureLogLevel(consecutiveFailures),
7818
7868
  message: `Claude usage reporting failed: ${message}${claudeUsageFailureStreakSuffix(consecutiveFailures)}`
7819
7869
  });
7820
- scheduleNextTick(false);
7870
+ scheduleNextTick();
7821
7871
  }
7822
7872
  }
7823
7873
  };