@evident-ai/cli 3.3.1-dev.a4bb60b → 3.3.1-dev.b5645b1
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 +8 -0
- package/dist/index.js +272 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,6 +105,11 @@ Options:
|
|
|
105
105
|
keeps retrying) if no usable login is found; `off` disables it entirely — no
|
|
106
106
|
Claude credential is ever read. An unrecognized value falls back to `auto` with
|
|
107
107
|
a warning. Env: `EVIDENT_CLAUDE_USAGE_REPORTING`.
|
|
108
|
+
- `--no-resource-usage-reporting` — Don't report this machine's CPU
|
|
109
|
+
utilization, total and available memory, and core count to Evident, so it
|
|
110
|
+
shows on the runner page. On by default; nothing else about the machine
|
|
111
|
+
leaves it. Env: `EVIDENT_RESOURCE_USAGE_REPORTING=off` (the flag wins if
|
|
112
|
+
both are set).
|
|
108
113
|
- `--json` — Output in JSON format (forces non-interactive mode).
|
|
109
114
|
- `--session-cleanup-max-age <duration>` — Delete OpenCode sessions idle longer
|
|
110
115
|
than this window (format `<number><unit>`, unit one of `s, m, h, d` — e.g.
|
|
@@ -171,6 +176,9 @@ targets the **production** Evident platform by default.
|
|
|
171
176
|
- `EVIDENT_TUNNEL_URL` — Override the tunnel relay URL (equivalent to `--tunnel`).
|
|
172
177
|
- `EVIDENT_CLAUDE_USAGE_REPORTING` — Equivalent to `--claude-usage-reporting`; the
|
|
173
178
|
flag wins if both are set.
|
|
179
|
+
- `EVIDENT_RESOURCE_USAGE_REPORTING` — Equivalent to
|
|
180
|
+
`--no-resource-usage-reporting`; set it to `off` to disable. The flag wins
|
|
181
|
+
if both are set.
|
|
174
182
|
- `EVIDENT_LOG_LEVEL` — Equivalent to `--log-level`; the flag wins if both are
|
|
175
183
|
set, and `-v`/`--verbose` also outranks this env var.
|
|
176
184
|
- `EVIDENT_OPENCODE_START_TIMEOUT` — Equivalent to `--opencode-start-timeout`
|
package/dist/index.js
CHANGED
|
@@ -746,6 +746,32 @@ async function reportClaudeUsage(agentId, authHeader, snapshot) {
|
|
|
746
746
|
return { ok: false, error: describeBestEffortError(error2) };
|
|
747
747
|
}
|
|
748
748
|
}
|
|
749
|
+
async function reportResourceUsage(agentId, authHeader, usage) {
|
|
750
|
+
try {
|
|
751
|
+
const apiUrl = getApiUrlConfig();
|
|
752
|
+
const response = await fetch(`${apiUrl}/runners/${agentId}/resource-usage`, {
|
|
753
|
+
method: "POST",
|
|
754
|
+
headers: { Authorization: authHeader, "Content-Type": "application/json" },
|
|
755
|
+
body: JSON.stringify({
|
|
756
|
+
cpu_percent: usage.cpuPercent,
|
|
757
|
+
cpu_count: usage.cpuCount,
|
|
758
|
+
memory_total_bytes: usage.memoryTotalBytes,
|
|
759
|
+
memory_available_bytes: usage.memoryAvailableBytes
|
|
760
|
+
}),
|
|
761
|
+
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
762
|
+
});
|
|
763
|
+
if (!response.ok) {
|
|
764
|
+
const serverMessage = await readErrorMessage(response);
|
|
765
|
+
return {
|
|
766
|
+
ok: false,
|
|
767
|
+
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
return { ok: true };
|
|
771
|
+
} catch (error2) {
|
|
772
|
+
return { ok: false, error: describeBestEffortError(error2) };
|
|
773
|
+
}
|
|
774
|
+
}
|
|
749
775
|
async function getAgentInfo(agentId, authHeader) {
|
|
750
776
|
const apiUrl = getApiUrlConfig();
|
|
751
777
|
try {
|
|
@@ -934,6 +960,7 @@ import { homedir } from "os";
|
|
|
934
960
|
import { join } from "path";
|
|
935
961
|
var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
936
962
|
var KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
963
|
+
var CLAUDE_CREDENTIALS_SEGMENTS = [".claude", ".credentials.json"];
|
|
937
964
|
function parseClaudeCliCredentials(raw) {
|
|
938
965
|
let parsed;
|
|
939
966
|
try {
|
|
@@ -967,7 +994,7 @@ function readClaudeCliCredentials() {
|
|
|
967
994
|
}
|
|
968
995
|
}
|
|
969
996
|
try {
|
|
970
|
-
const raw = readFileSync(join(homedir(),
|
|
997
|
+
const raw = readFileSync(join(homedir(), ...CLAUDE_CREDENTIALS_SEGMENTS), "utf-8");
|
|
971
998
|
return parseClaudeCliCredentials(raw);
|
|
972
999
|
} catch (err) {
|
|
973
1000
|
const code = err.code;
|
|
@@ -1063,7 +1090,7 @@ async function claudeUsage() {
|
|
|
1063
1090
|
|
|
1064
1091
|
// src/commands/run.ts
|
|
1065
1092
|
import { homedir as homedir3 } from "os";
|
|
1066
|
-
import { isAbsolute as isAbsolute2, join as
|
|
1093
|
+
import { isAbsolute as isAbsolute2, join as join5, parse, resolve as resolvePath } from "path";
|
|
1067
1094
|
import chalk6 from "chalk";
|
|
1068
1095
|
|
|
1069
1096
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -2961,6 +2988,21 @@ function writeTunnelReadyMarker(path, agentId) {
|
|
|
2961
2988
|
}
|
|
2962
2989
|
}
|
|
2963
2990
|
|
|
2991
|
+
// src/lib/reporting-schedule.ts
|
|
2992
|
+
function jitteredDelayMs(baseMs, jitterFraction, random = Math.random) {
|
|
2993
|
+
const jitterRangeMs = baseMs * jitterFraction;
|
|
2994
|
+
return baseMs - jitterRangeMs + random() * (2 * jitterRangeMs);
|
|
2995
|
+
}
|
|
2996
|
+
function firstReportDelayMs(random = Math.random) {
|
|
2997
|
+
return 5e3 + random() * 1e4;
|
|
2998
|
+
}
|
|
2999
|
+
function reportFailureLogLevel(consecutiveFailures, reescalationTicks) {
|
|
3000
|
+
return consecutiveFailures === 1 || consecutiveFailures % reescalationTicks === 0 ? "warn" : "debug";
|
|
3001
|
+
}
|
|
3002
|
+
function failureStreakSuffix(consecutiveFailures) {
|
|
3003
|
+
return consecutiveFailures > 1 ? ` (${consecutiveFailures} consecutive failures)` : "";
|
|
3004
|
+
}
|
|
3005
|
+
|
|
2964
3006
|
// src/lib/claude-usage-reporting.ts
|
|
2965
3007
|
var VALID_MODES = ["auto", "on", "off"];
|
|
2966
3008
|
function resolveClaudeUsageReportingMode(flagValue, env) {
|
|
@@ -2983,18 +3025,79 @@ function resolveClaudeUsageReportingMode(flagValue, env) {
|
|
|
2983
3025
|
var BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
2984
3026
|
var REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
2985
3027
|
function nextReportDelayMs(random = Math.random) {
|
|
2986
|
-
|
|
2987
|
-
return BASE_REPORT_DELAY_MS - jitterRangeMs + random() * (2 * jitterRangeMs);
|
|
3028
|
+
return jitteredDelayMs(BASE_REPORT_DELAY_MS, REPORT_DELAY_JITTER_FRACTION, random);
|
|
2988
3029
|
}
|
|
2989
|
-
var FIRST_REPORT_DELAY_MS =
|
|
3030
|
+
var FIRST_REPORT_DELAY_MS = firstReportDelayMs();
|
|
2990
3031
|
var CLAUDE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
2991
3032
|
function claudeUsageFailureLogLevel(consecutiveFailures) {
|
|
2992
|
-
return consecutiveFailures
|
|
3033
|
+
return reportFailureLogLevel(consecutiveFailures, CLAUDE_USAGE_FAILURE_REESCALATION_TICKS);
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
// src/lib/resource-usage-reporting.ts
|
|
3037
|
+
var ENABLED_VALUES = /* @__PURE__ */ new Set(["on", "true", "1"]);
|
|
3038
|
+
var DISABLED_VALUES = /* @__PURE__ */ new Set(["off", "false", "0"]);
|
|
3039
|
+
function resolveResourceUsageReportingEnabled(flagValue, env) {
|
|
3040
|
+
if (flagValue === false) {
|
|
3041
|
+
return { enabled: false, warnings: [] };
|
|
3042
|
+
}
|
|
3043
|
+
const raw = env.EVIDENT_RESOURCE_USAGE_REPORTING;
|
|
3044
|
+
if (raw === void 0 || raw === "") {
|
|
3045
|
+
return { enabled: true, warnings: [] };
|
|
3046
|
+
}
|
|
3047
|
+
const normalized = raw.trim().toLowerCase();
|
|
3048
|
+
if (DISABLED_VALUES.has(normalized)) {
|
|
3049
|
+
return { enabled: false, warnings: [] };
|
|
3050
|
+
}
|
|
3051
|
+
if (ENABLED_VALUES.has(normalized)) {
|
|
3052
|
+
return { enabled: true, warnings: [] };
|
|
3053
|
+
}
|
|
3054
|
+
return {
|
|
3055
|
+
enabled: true,
|
|
3056
|
+
warnings: [
|
|
3057
|
+
`Ignoring invalid EVIDENT_RESOURCE_USAGE_REPORTING "${raw}": expected on or off; leaving reporting on`
|
|
3058
|
+
]
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
// src/lib/resource-usage.ts
|
|
3063
|
+
import { cpus, totalmem, freemem } from "os";
|
|
3064
|
+
function readCpuSample() {
|
|
3065
|
+
let busyMs = 0;
|
|
3066
|
+
let idleMs = 0;
|
|
3067
|
+
for (const cpu of cpus()) {
|
|
3068
|
+
busyMs += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.irq;
|
|
3069
|
+
idleMs += cpu.times.idle;
|
|
3070
|
+
}
|
|
3071
|
+
return { busyMs, idleMs };
|
|
3072
|
+
}
|
|
3073
|
+
function cpuPercentBetween(previous, current) {
|
|
3074
|
+
const deltaBusy = current.busyMs - previous.busyMs;
|
|
3075
|
+
const deltaIdle = current.idleMs - previous.idleMs;
|
|
3076
|
+
const total = deltaBusy + deltaIdle;
|
|
3077
|
+
if (total === 0) return null;
|
|
3078
|
+
return Math.round((deltaBusy / total * 100 + Number.EPSILON) * 100) / 100;
|
|
3079
|
+
}
|
|
3080
|
+
function createResourceUsageCollector() {
|
|
3081
|
+
let previous = readCpuSample();
|
|
3082
|
+
return () => {
|
|
3083
|
+
const current = readCpuSample();
|
|
3084
|
+
const cpuPercent = cpuPercentBetween(previous, current);
|
|
3085
|
+
previous = current;
|
|
3086
|
+
return {
|
|
3087
|
+
cpuPercent,
|
|
3088
|
+
cpuCount: cpus().length,
|
|
3089
|
+
memoryTotalBytes: totalmem(),
|
|
3090
|
+
memoryAvailableBytes: freemem()
|
|
3091
|
+
};
|
|
3092
|
+
};
|
|
2993
3093
|
}
|
|
2994
3094
|
|
|
2995
3095
|
// src/lib/channels/driver.ts
|
|
2996
3096
|
import { homedir as homedir2 } from "os";
|
|
2997
3097
|
|
|
3098
|
+
// src/lib/runner-file-sync.ts
|
|
3099
|
+
import { join as join4 } from "path";
|
|
3100
|
+
|
|
2998
3101
|
// src/lib/file-push.ts
|
|
2999
3102
|
import { randomUUID } from "crypto";
|
|
3000
3103
|
import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
|
|
@@ -3188,17 +3291,20 @@ async function syncPendingRunnerFiles(options) {
|
|
|
3188
3291
|
for (const id of options.ackFailures.keys()) {
|
|
3189
3292
|
if (!pendingIds.has(id)) options.ackFailures.delete(id);
|
|
3190
3293
|
}
|
|
3191
|
-
if (pending.length === 0) return 0;
|
|
3294
|
+
if (pending.length === 0) return { applied: 0, claudeCredentialApplied: false };
|
|
3192
3295
|
options.log({
|
|
3193
3296
|
level: "info",
|
|
3194
3297
|
message: `Runner file sync: ${pending.length} file(s) queued for this runner`
|
|
3195
3298
|
});
|
|
3196
3299
|
let applied = 0;
|
|
3300
|
+
let claudeCredentialApplied = false;
|
|
3197
3301
|
for (const file of pending) {
|
|
3198
3302
|
if ((options.ackFailures.get(file.id) ?? 0) >= MAX_ACK_ATTEMPTS) continue;
|
|
3199
|
-
|
|
3303
|
+
const outcome = await applyOne(options, file);
|
|
3304
|
+
if (outcome.applied) applied += 1;
|
|
3305
|
+
if (outcome.claudeCredentialApplied) claudeCredentialApplied = true;
|
|
3200
3306
|
}
|
|
3201
|
-
return applied;
|
|
3307
|
+
return { applied, claudeCredentialApplied };
|
|
3202
3308
|
}
|
|
3203
3309
|
async function listPendingFiles(options) {
|
|
3204
3310
|
let res;
|
|
@@ -3259,6 +3365,11 @@ function asPendingFile(entry) {
|
|
|
3259
3365
|
if (typeof size !== "number" || !Number.isFinite(size) || size < 0) return null;
|
|
3260
3366
|
return { id, path, size };
|
|
3261
3367
|
}
|
|
3368
|
+
var NOT_APPLIED = { applied: false, claudeCredentialApplied: false };
|
|
3369
|
+
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
3370
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join4(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3371
|
+
return expanded === join4(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
3372
|
+
}
|
|
3262
3373
|
async function applyOne(options, file) {
|
|
3263
3374
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
3264
3375
|
if (options.allowedDirectories.length === 0) {
|
|
@@ -3267,7 +3378,7 @@ async function applyOne(options, file) {
|
|
|
3267
3378
|
message: `Runner file ${label} rejected: file sync is not enabled on this runner (start it with --enable-file-sync-to)`
|
|
3268
3379
|
});
|
|
3269
3380
|
await ack(options, file, "rejected", "file_sync_disabled");
|
|
3270
|
-
return
|
|
3381
|
+
return NOT_APPLIED;
|
|
3271
3382
|
}
|
|
3272
3383
|
if (file.size > MAX_FILE_PUSH_BYTES) {
|
|
3273
3384
|
options.log({
|
|
@@ -3275,12 +3386,12 @@ async function applyOne(options, file) {
|
|
|
3275
3386
|
message: `Runner file ${label} rejected: declared ${file.size} bytes, the limit is ${MAX_FILE_PUSH_BYTES}`
|
|
3276
3387
|
});
|
|
3277
3388
|
await ack(options, file, "rejected", "file_too_large");
|
|
3278
|
-
return
|
|
3389
|
+
return NOT_APPLIED;
|
|
3279
3390
|
}
|
|
3280
3391
|
const download = await downloadContent(options, file, label);
|
|
3281
3392
|
if (!download.ok) {
|
|
3282
3393
|
if (download.terminal) await ack(options, file, "rejected", download.code);
|
|
3283
|
-
return
|
|
3394
|
+
return NOT_APPLIED;
|
|
3284
3395
|
}
|
|
3285
3396
|
let outcome;
|
|
3286
3397
|
try {
|
|
@@ -3296,7 +3407,7 @@ async function applyOne(options, file) {
|
|
|
3296
3407
|
message: `Runner file ${label} could not be written: ${describe(err)}`
|
|
3297
3408
|
});
|
|
3298
3409
|
await ack(options, file, "rejected", "write_failed");
|
|
3299
|
-
return
|
|
3410
|
+
return NOT_APPLIED;
|
|
3300
3411
|
}
|
|
3301
3412
|
if (!outcome.ok) {
|
|
3302
3413
|
options.log({
|
|
@@ -3304,14 +3415,17 @@ async function applyOne(options, file) {
|
|
|
3304
3415
|
message: `Runner file ${label} rejected (${outcome.code}): ${outcome.message}`
|
|
3305
3416
|
});
|
|
3306
3417
|
await ack(options, file, "rejected", outcome.code);
|
|
3307
|
-
return
|
|
3418
|
+
return NOT_APPLIED;
|
|
3308
3419
|
}
|
|
3309
3420
|
options.log({
|
|
3310
3421
|
level: "info",
|
|
3311
3422
|
message: `Runner file ${label} applied (${download.content.byteLength} bytes)`
|
|
3312
3423
|
});
|
|
3313
3424
|
await ack(options, file, "applied");
|
|
3314
|
-
return
|
|
3425
|
+
return {
|
|
3426
|
+
applied: true,
|
|
3427
|
+
claudeCredentialApplied: isClaudeCredentialPath(file.path, options.homeDir)
|
|
3428
|
+
};
|
|
3315
3429
|
}
|
|
3316
3430
|
function durableDownloadCode(status2) {
|
|
3317
3431
|
return status2 === 413 ? "file_too_large" : "write_failed";
|
|
@@ -3756,6 +3870,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3756
3870
|
* same trick `lastProxiedActivityAt` uses.
|
|
3757
3871
|
*/
|
|
3758
3872
|
appliedFileCount = 0;
|
|
3873
|
+
/**
|
|
3874
|
+
* Generation counter, NOT a tally (#1656): advances by exactly one per sync
|
|
3875
|
+
* batch that applied the Claude CLI credential file, not by how many
|
|
3876
|
+
* credential files were in that batch. `run.ts` only ever tests inequality
|
|
3877
|
+
* against the value it saw last cycle, so magnitude is meaningless — keep it
|
|
3878
|
+
* that way rather than "fixing" it into a count.
|
|
3879
|
+
*/
|
|
3880
|
+
claudeCredentialApplyCount = 0;
|
|
3759
3881
|
/**
|
|
3760
3882
|
* The currently-executing `drainPending()` promise, or null when idle. Lets a
|
|
3761
3883
|
* graceful shutdown (`waitForInFlight`) await an in-progress drain so a turn it
|
|
@@ -3848,7 +3970,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3848
3970
|
if (this.syncingFiles) return 0;
|
|
3849
3971
|
this.syncingFiles = true;
|
|
3850
3972
|
try {
|
|
3851
|
-
const
|
|
3973
|
+
const result = await syncPendingRunnerFiles({
|
|
3852
3974
|
agentId: this.agentId,
|
|
3853
3975
|
apiUrl: this.apiUrl,
|
|
3854
3976
|
getAuthHeader: this.getAuthHeader,
|
|
@@ -3858,8 +3980,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3858
3980
|
ackFailures: this.fileAckFailures,
|
|
3859
3981
|
log: this.log
|
|
3860
3982
|
});
|
|
3861
|
-
this.appliedFileCount += applied;
|
|
3862
|
-
|
|
3983
|
+
this.appliedFileCount += result.applied;
|
|
3984
|
+
if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
|
|
3985
|
+
return result.applied;
|
|
3863
3986
|
} catch (err) {
|
|
3864
3987
|
this.log({
|
|
3865
3988
|
level: "error",
|
|
@@ -3950,12 +4073,24 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3950
4073
|
* `appliedFiles` is monotonic so a pull that started AND finished between two
|
|
3951
4074
|
* idle checks still shows up as an advance.
|
|
3952
4075
|
*
|
|
4076
|
+
* A THIRD signal, `claudeCredentialApplies`, is a separate re-arm trigger
|
|
4077
|
+
* (#1656), not idle accounting: `run.ts` gates re-probing Claude usage
|
|
4078
|
+
* reporting on it advancing, so an unrelated file sync can never disturb a
|
|
4079
|
+
* healthy reporting cadence (#1627) — it never even reaches that trigger, let
|
|
4080
|
+
* alone gets declined by it. Keep this narrower signal OUT of `appliedFiles`,
|
|
4081
|
+
* whose consumer is idle-timeout suppression and must key on ANY file, not
|
|
4082
|
+
* just a Claude credential.
|
|
4083
|
+
*
|
|
3953
4084
|
* CALLER CONTRACT: sample `inFlight` BEFORE calling `syncPendingFiles()` for
|
|
3954
4085
|
* the cycle. `syncPendingFiles` sets the flag synchronously, so a caller that
|
|
3955
4086
|
* samples afterwards reads `true` every single cycle and can never idle out.
|
|
3956
4087
|
*/
|
|
3957
4088
|
fileSyncActivity() {
|
|
3958
|
-
return {
|
|
4089
|
+
return {
|
|
4090
|
+
appliedFiles: this.appliedFileCount,
|
|
4091
|
+
inFlight: this.syncingFiles,
|
|
4092
|
+
claudeCredentialApplies: this.claudeCredentialApplyCount
|
|
4093
|
+
};
|
|
3959
4094
|
}
|
|
3960
4095
|
/**
|
|
3961
4096
|
* OpenCode session ids the session-cleanup sweep (issue #190) must NOT delete:
|
|
@@ -4815,6 +4950,17 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4815
4950
|
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
4951
|
conversation_id: conv.id
|
|
4817
4952
|
});
|
|
4953
|
+
const watcher = this.watchers.get(bound);
|
|
4954
|
+
if (watcher) {
|
|
4955
|
+
for (const [evidentMessageId, inFlight] of [...watcher.inFlight]) {
|
|
4956
|
+
this.recordReleasedOpencodeId(evidentMessageId, bound, inFlight.opencodeMessageId);
|
|
4957
|
+
this.removeInFlight(watcher, evidentMessageId);
|
|
4958
|
+
void this.postSignal(watcher.conv.id, evidentMessageId, "watcher_recovered", {
|
|
4959
|
+
recovery: "session_gone_released"
|
|
4960
|
+
});
|
|
4961
|
+
}
|
|
4962
|
+
this.watchers.delete(bound);
|
|
4963
|
+
}
|
|
4818
4964
|
this.sessions.delete(conv.id);
|
|
4819
4965
|
return { sessionId: await this.createAndBindSession(conv.id), created: true };
|
|
4820
4966
|
}
|
|
@@ -7280,7 +7426,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
7280
7426
|
if (trimmed === "") {
|
|
7281
7427
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
7282
7428
|
}
|
|
7283
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
7429
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join5(homeDir, trimmed.slice(2)) : trimmed;
|
|
7284
7430
|
if (!isAbsolute2(expanded)) {
|
|
7285
7431
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
7286
7432
|
}
|
|
@@ -7487,6 +7633,7 @@ async function driveChannels(state, driver) {
|
|
|
7487
7633
|
let unreachableMs = 0;
|
|
7488
7634
|
let lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
7489
7635
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
7636
|
+
let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
|
|
7490
7637
|
while (state.running) {
|
|
7491
7638
|
const cycleStartedAtMs = performance.now();
|
|
7492
7639
|
let idleThisCycle = false;
|
|
@@ -7510,11 +7657,15 @@ async function driveChannels(state, driver) {
|
|
|
7510
7657
|
state.messageCount += processed;
|
|
7511
7658
|
const proxiedActivity = state.lastProxiedActivityAt !== lastSeenProxiedActivityAt;
|
|
7512
7659
|
lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
7513
|
-
const
|
|
7660
|
+
const fileActivitySnapshot = driver.fileSyncActivity();
|
|
7661
|
+
const appliedFiles = fileActivitySnapshot.appliedFiles;
|
|
7514
7662
|
const filesApplied = appliedFiles !== lastSeenAppliedFiles;
|
|
7515
7663
|
const fileActivity = carriedOverFileSync || filesApplied;
|
|
7516
7664
|
lastSeenAppliedFiles = appliedFiles;
|
|
7517
|
-
|
|
7665
|
+
const claudeCredentialApplies = fileActivitySnapshot.claudeCredentialApplies;
|
|
7666
|
+
const claudeCredentialApplied = claudeCredentialApplies !== lastSeenClaudeApplies;
|
|
7667
|
+
lastSeenClaudeApplies = claudeCredentialApplies;
|
|
7668
|
+
if (claudeCredentialApplied) state.claudeUsageRearm?.();
|
|
7518
7669
|
if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
|
|
7519
7670
|
idlePolls = 0;
|
|
7520
7671
|
idleMs = 0;
|
|
@@ -7583,7 +7734,7 @@ async function driveChannels(state, driver) {
|
|
|
7583
7734
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
7584
7735
|
var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
|
|
7585
7736
|
function sessionDbPath() {
|
|
7586
|
-
return
|
|
7737
|
+
return join5(homedir3(), ".local", "share", "opencode", "opencode.db");
|
|
7587
7738
|
}
|
|
7588
7739
|
async function runSweep(state, driver, config) {
|
|
7589
7740
|
const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
|
|
@@ -7694,9 +7845,6 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
7694
7845
|
);
|
|
7695
7846
|
state.sessionCleanupTimers.push(interval, firstSweep);
|
|
7696
7847
|
}
|
|
7697
|
-
function claudeUsageFailureStreakSuffix(consecutiveFailures) {
|
|
7698
|
-
return consecutiveFailures > 1 ? ` (${consecutiveFailures} consecutive failures)` : "";
|
|
7699
|
-
}
|
|
7700
7848
|
function scheduleClaudeUsageReporting(state, options) {
|
|
7701
7849
|
const { mode, warnings } = resolveClaudeUsageReportingMode(
|
|
7702
7850
|
options.claudeUsageReporting,
|
|
@@ -7724,13 +7872,13 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7724
7872
|
phase = "probe-pending";
|
|
7725
7873
|
state.claudeUsageTimer = setTimeout(() => void tick(true), FIRST_REPORT_DELAY_MS);
|
|
7726
7874
|
};
|
|
7727
|
-
const scheduleNextTick = (
|
|
7875
|
+
const scheduleNextTick = () => {
|
|
7728
7876
|
if (rearmRequested) {
|
|
7729
7877
|
rearmRequested = false;
|
|
7730
7878
|
armProbe();
|
|
7731
7879
|
return;
|
|
7732
7880
|
}
|
|
7733
|
-
phase =
|
|
7881
|
+
phase = "steady-pending";
|
|
7734
7882
|
state.claudeUsageTimer = setTimeout(() => void tick(false), nextReportDelayMs());
|
|
7735
7883
|
};
|
|
7736
7884
|
const rearm = () => {
|
|
@@ -7740,9 +7888,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7740
7888
|
return;
|
|
7741
7889
|
case "probe-pending":
|
|
7742
7890
|
return;
|
|
7743
|
-
case "steady-pending
|
|
7744
|
-
return;
|
|
7745
|
-
case "steady-pending-retry":
|
|
7891
|
+
case "steady-pending":
|
|
7746
7892
|
if (state.claudeUsageTimer) {
|
|
7747
7893
|
clearTimeout(state.claudeUsageTimer);
|
|
7748
7894
|
state.claudeUsageTimer = null;
|
|
@@ -7780,10 +7926,10 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7780
7926
|
logActivity(state, {
|
|
7781
7927
|
type: "info",
|
|
7782
7928
|
level: claudeUsageFailureLogLevel(consecutiveFailures),
|
|
7783
|
-
message: `Failed to report Claude usage: ${result.error}${
|
|
7929
|
+
message: `Failed to report Claude usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
|
|
7784
7930
|
});
|
|
7785
7931
|
}
|
|
7786
|
-
scheduleNextTick(
|
|
7932
|
+
scheduleNextTick();
|
|
7787
7933
|
} catch (error2) {
|
|
7788
7934
|
if (error2 instanceof ClaudeUsageError && isLocalCredentialProblem(error2)) {
|
|
7789
7935
|
if (mode === "on") {
|
|
@@ -7792,7 +7938,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7792
7938
|
level: "warn",
|
|
7793
7939
|
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
7940
|
});
|
|
7795
|
-
scheduleNextTick(
|
|
7941
|
+
scheduleNextTick();
|
|
7796
7942
|
} else if (isProbe) {
|
|
7797
7943
|
logActivity(state, {
|
|
7798
7944
|
type: "info",
|
|
@@ -7807,7 +7953,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7807
7953
|
level: "debug",
|
|
7808
7954
|
message: `Claude usage reporting: ${error2.message}`
|
|
7809
7955
|
});
|
|
7810
|
-
scheduleNextTick(
|
|
7956
|
+
scheduleNextTick();
|
|
7811
7957
|
}
|
|
7812
7958
|
} else {
|
|
7813
7959
|
consecutiveFailures++;
|
|
@@ -7815,15 +7961,92 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7815
7961
|
logActivity(state, {
|
|
7816
7962
|
type: "info",
|
|
7817
7963
|
level: claudeUsageFailureLogLevel(consecutiveFailures),
|
|
7818
|
-
message: `Claude usage reporting failed: ${message}${
|
|
7964
|
+
message: `Claude usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
7819
7965
|
});
|
|
7820
|
-
scheduleNextTick(
|
|
7966
|
+
scheduleNextTick();
|
|
7821
7967
|
}
|
|
7822
7968
|
}
|
|
7823
7969
|
};
|
|
7824
7970
|
armProbe();
|
|
7825
7971
|
return rearm;
|
|
7826
7972
|
}
|
|
7973
|
+
var RESOURCE_USAGE_BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
7974
|
+
var RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
7975
|
+
var RESOURCE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
7976
|
+
function scheduleResourceUsageReporting(state, options) {
|
|
7977
|
+
const { enabled, warnings } = resolveResourceUsageReportingEnabled(
|
|
7978
|
+
options.resourceUsageReporting,
|
|
7979
|
+
process.env
|
|
7980
|
+
);
|
|
7981
|
+
for (const warning2 of warnings) {
|
|
7982
|
+
logActivity(state, {
|
|
7983
|
+
type: "info",
|
|
7984
|
+
level: "warn",
|
|
7985
|
+
message: `Resource usage reporting: ${warning2}`
|
|
7986
|
+
});
|
|
7987
|
+
}
|
|
7988
|
+
if (!enabled) {
|
|
7989
|
+
logActivity(state, {
|
|
7990
|
+
type: "info",
|
|
7991
|
+
level: "debug",
|
|
7992
|
+
message: "Resource usage reporting is off (--no-resource-usage-reporting)"
|
|
7993
|
+
});
|
|
7994
|
+
return;
|
|
7995
|
+
}
|
|
7996
|
+
const collect = createResourceUsageCollector();
|
|
7997
|
+
let consecutiveFailures = 0;
|
|
7998
|
+
const tick = async () => {
|
|
7999
|
+
try {
|
|
8000
|
+
const usage = collect();
|
|
8001
|
+
const result = await reportResourceUsage(state.agentId, state.authHeader, usage);
|
|
8002
|
+
if (result.ok) {
|
|
8003
|
+
if (consecutiveFailures > 0) {
|
|
8004
|
+
logActivity(state, {
|
|
8005
|
+
type: "info",
|
|
8006
|
+
level: "info",
|
|
8007
|
+
message: "Resource usage reporting recovered"
|
|
8008
|
+
});
|
|
8009
|
+
}
|
|
8010
|
+
consecutiveFailures = 0;
|
|
8011
|
+
logActivity(state, {
|
|
8012
|
+
type: "info",
|
|
8013
|
+
level: "debug",
|
|
8014
|
+
message: "Reported resource usage to Evident"
|
|
8015
|
+
});
|
|
8016
|
+
} else {
|
|
8017
|
+
consecutiveFailures++;
|
|
8018
|
+
logActivity(state, {
|
|
8019
|
+
type: "info",
|
|
8020
|
+
level: reportFailureLogLevel(
|
|
8021
|
+
consecutiveFailures,
|
|
8022
|
+
RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
|
|
8023
|
+
),
|
|
8024
|
+
message: `Failed to report resource usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
|
|
8025
|
+
});
|
|
8026
|
+
}
|
|
8027
|
+
} catch (error2) {
|
|
8028
|
+
consecutiveFailures++;
|
|
8029
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
8030
|
+
logActivity(state, {
|
|
8031
|
+
type: "info",
|
|
8032
|
+
level: reportFailureLogLevel(
|
|
8033
|
+
consecutiveFailures,
|
|
8034
|
+
RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
|
|
8035
|
+
),
|
|
8036
|
+
message: `Resource usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
8037
|
+
});
|
|
8038
|
+
} finally {
|
|
8039
|
+
state.resourceUsageTimer = setTimeout(
|
|
8040
|
+
() => void tick(),
|
|
8041
|
+
jitteredDelayMs(
|
|
8042
|
+
RESOURCE_USAGE_BASE_REPORT_DELAY_MS,
|
|
8043
|
+
RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION
|
|
8044
|
+
)
|
|
8045
|
+
);
|
|
8046
|
+
}
|
|
8047
|
+
};
|
|
8048
|
+
state.resourceUsageTimer = setTimeout(() => void tick(), firstReportDelayMs());
|
|
8049
|
+
}
|
|
7827
8050
|
async function notifyOffline(state) {
|
|
7828
8051
|
if (!state.agentId || !state.authHeader) return;
|
|
7829
8052
|
if (!state.connected) {
|
|
@@ -7864,6 +8087,10 @@ async function cleanup(state, opts = {}) {
|
|
|
7864
8087
|
state.claudeUsageTimer = null;
|
|
7865
8088
|
}
|
|
7866
8089
|
state.claudeUsageRearm = null;
|
|
8090
|
+
if (state.resourceUsageTimer) {
|
|
8091
|
+
clearTimeout(state.resourceUsageTimer);
|
|
8092
|
+
state.resourceUsageTimer = null;
|
|
8093
|
+
}
|
|
7867
8094
|
if (opts.graceful && state.channelDriver) {
|
|
7868
8095
|
state.channelDriver.stop();
|
|
7869
8096
|
log2(state, "Draining in-flight channel work before shutdown...");
|
|
@@ -7946,6 +8173,7 @@ async function run(options) {
|
|
|
7946
8173
|
sessionCleanupTimers: [],
|
|
7947
8174
|
claudeUsageTimer: null,
|
|
7948
8175
|
claudeUsageRearm: null,
|
|
8176
|
+
resourceUsageTimer: null,
|
|
7949
8177
|
authHeader: ""
|
|
7950
8178
|
};
|
|
7951
8179
|
setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
|
|
@@ -8345,6 +8573,7 @@ async function run(options) {
|
|
|
8345
8573
|
}
|
|
8346
8574
|
scheduleSessionCleanup(state, channelDriver, options);
|
|
8347
8575
|
state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
|
|
8576
|
+
scheduleResourceUsageReporting(state, options);
|
|
8348
8577
|
if (!interactive || state.json) {
|
|
8349
8578
|
log2(state, "Driving channel messages...");
|
|
8350
8579
|
}
|
|
@@ -8425,6 +8654,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
8425
8654
|
).option(
|
|
8426
8655
|
"--claude-usage-reporting <mode>",
|
|
8427
8656
|
"Report Claude subscription usage to Evident: auto | on | off (default: auto). Env: EVIDENT_CLAUDE_USAGE_REPORTING"
|
|
8657
|
+
).option(
|
|
8658
|
+
"--no-resource-usage-reporting",
|
|
8659
|
+
"Don't report this machine's CPU and memory usage to Evident (reporting is on by default). Env: EVIDENT_RESOURCE_USAGE_REPORTING=off"
|
|
8428
8660
|
).option(
|
|
8429
8661
|
"--enable-file-sync-to <dir>",
|
|
8430
8662
|
"Allow Evident to write files into this directory (repeatable). Omit to disable file sync entirely.",
|
|
@@ -8457,6 +8689,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
8457
8689
|
// Raw string — the resolver in run.ts single-sources parsing
|
|
8458
8690
|
// (resolveClaudeUsageReportingMode).
|
|
8459
8691
|
claudeUsageReporting: options.claudeUsageReporting,
|
|
8692
|
+
// Raw value — resolution is single-sourced in run.ts's
|
|
8693
|
+
// resolveResourceUsageReportingEnabled.
|
|
8694
|
+
resourceUsageReporting: options.resourceUsageReporting,
|
|
8460
8695
|
// Raw values — expansion/validation is single-sourced in run.ts's
|
|
8461
8696
|
// resolveFileSyncDirectories.
|
|
8462
8697
|
enableFileSyncTo: options.enableFileSyncTo,
|