@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 +78 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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(),
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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("~/") ?
|
|
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
|
|
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
|
-
|
|
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
|
|
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 = (
|
|
7779
|
+
const scheduleNextTick = () => {
|
|
7728
7780
|
if (rearmRequested) {
|
|
7729
7781
|
rearmRequested = false;
|
|
7730
7782
|
armProbe();
|
|
7731
7783
|
return;
|
|
7732
7784
|
}
|
|
7733
|
-
phase =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
7870
|
+
scheduleNextTick();
|
|
7821
7871
|
}
|
|
7822
7872
|
}
|
|
7823
7873
|
};
|