@evident-ai/cli 3.3.1-dev.8b3848f → 3.3.1-dev.8e3d9ea
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 +378 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -746,6 +746,35 @@ 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
|
+
disk_total_bytes: usage.diskTotalBytes,
|
|
761
|
+
disk_free_bytes: usage.diskFreeBytes,
|
|
762
|
+
opencode_db_bytes: usage.opencodeDbBytes
|
|
763
|
+
}),
|
|
764
|
+
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
765
|
+
});
|
|
766
|
+
if (!response.ok) {
|
|
767
|
+
const serverMessage = await readErrorMessage(response);
|
|
768
|
+
return {
|
|
769
|
+
ok: false,
|
|
770
|
+
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
return { ok: true };
|
|
774
|
+
} catch (error2) {
|
|
775
|
+
return { ok: false, error: describeBestEffortError(error2) };
|
|
776
|
+
}
|
|
777
|
+
}
|
|
749
778
|
async function getAgentInfo(agentId, authHeader) {
|
|
750
779
|
const apiUrl = getApiUrlConfig();
|
|
751
780
|
try {
|
|
@@ -934,6 +963,7 @@ import { homedir } from "os";
|
|
|
934
963
|
import { join } from "path";
|
|
935
964
|
var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
936
965
|
var KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
966
|
+
var CLAUDE_CREDENTIALS_SEGMENTS = [".claude", ".credentials.json"];
|
|
937
967
|
function parseClaudeCliCredentials(raw) {
|
|
938
968
|
let parsed;
|
|
939
969
|
try {
|
|
@@ -967,7 +997,7 @@ function readClaudeCliCredentials() {
|
|
|
967
997
|
}
|
|
968
998
|
}
|
|
969
999
|
try {
|
|
970
|
-
const raw = readFileSync(join(homedir(),
|
|
1000
|
+
const raw = readFileSync(join(homedir(), ...CLAUDE_CREDENTIALS_SEGMENTS), "utf-8");
|
|
971
1001
|
return parseClaudeCliCredentials(raw);
|
|
972
1002
|
} catch (err) {
|
|
973
1003
|
const code = err.code;
|
|
@@ -1063,7 +1093,7 @@ async function claudeUsage() {
|
|
|
1063
1093
|
|
|
1064
1094
|
// src/commands/run.ts
|
|
1065
1095
|
import { homedir as homedir3 } from "os";
|
|
1066
|
-
import { isAbsolute as isAbsolute2, join as
|
|
1096
|
+
import { isAbsolute as isAbsolute2, join as join5, parse, resolve as resolvePath } from "path";
|
|
1067
1097
|
import chalk6 from "chalk";
|
|
1068
1098
|
|
|
1069
1099
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -2961,6 +2991,21 @@ function writeTunnelReadyMarker(path, agentId) {
|
|
|
2961
2991
|
}
|
|
2962
2992
|
}
|
|
2963
2993
|
|
|
2994
|
+
// src/lib/reporting-schedule.ts
|
|
2995
|
+
function jitteredDelayMs(baseMs, jitterFraction, random = Math.random) {
|
|
2996
|
+
const jitterRangeMs = baseMs * jitterFraction;
|
|
2997
|
+
return baseMs - jitterRangeMs + random() * (2 * jitterRangeMs);
|
|
2998
|
+
}
|
|
2999
|
+
function firstReportDelayMs(random = Math.random) {
|
|
3000
|
+
return 5e3 + random() * 1e4;
|
|
3001
|
+
}
|
|
3002
|
+
function reportFailureLogLevel(consecutiveFailures, reescalationTicks) {
|
|
3003
|
+
return consecutiveFailures === 1 || consecutiveFailures % reescalationTicks === 0 ? "warn" : "debug";
|
|
3004
|
+
}
|
|
3005
|
+
function failureStreakSuffix(consecutiveFailures) {
|
|
3006
|
+
return consecutiveFailures > 1 ? ` (${consecutiveFailures} consecutive failures)` : "";
|
|
3007
|
+
}
|
|
3008
|
+
|
|
2964
3009
|
// src/lib/claude-usage-reporting.ts
|
|
2965
3010
|
var VALID_MODES = ["auto", "on", "off"];
|
|
2966
3011
|
function resolveClaudeUsageReportingMode(flagValue, env) {
|
|
@@ -2983,18 +3028,175 @@ function resolveClaudeUsageReportingMode(flagValue, env) {
|
|
|
2983
3028
|
var BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
2984
3029
|
var REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
2985
3030
|
function nextReportDelayMs(random = Math.random) {
|
|
2986
|
-
|
|
2987
|
-
return BASE_REPORT_DELAY_MS - jitterRangeMs + random() * (2 * jitterRangeMs);
|
|
3031
|
+
return jitteredDelayMs(BASE_REPORT_DELAY_MS, REPORT_DELAY_JITTER_FRACTION, random);
|
|
2988
3032
|
}
|
|
2989
|
-
var FIRST_REPORT_DELAY_MS =
|
|
3033
|
+
var FIRST_REPORT_DELAY_MS = firstReportDelayMs();
|
|
2990
3034
|
var CLAUDE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
2991
3035
|
function claudeUsageFailureLogLevel(consecutiveFailures) {
|
|
2992
|
-
return consecutiveFailures
|
|
3036
|
+
return reportFailureLogLevel(consecutiveFailures, CLAUDE_USAGE_FAILURE_REESCALATION_TICKS);
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
// src/lib/resource-usage-reporting.ts
|
|
3040
|
+
var ENABLED_VALUES = /* @__PURE__ */ new Set(["on", "true", "1"]);
|
|
3041
|
+
var DISABLED_VALUES = /* @__PURE__ */ new Set(["off", "false", "0"]);
|
|
3042
|
+
function resolveResourceUsageReportingEnabled(flagValue, env) {
|
|
3043
|
+
if (flagValue === false) {
|
|
3044
|
+
return { enabled: false, warnings: [] };
|
|
3045
|
+
}
|
|
3046
|
+
const raw = env.EVIDENT_RESOURCE_USAGE_REPORTING;
|
|
3047
|
+
if (raw === void 0 || raw === "") {
|
|
3048
|
+
return { enabled: true, warnings: [] };
|
|
3049
|
+
}
|
|
3050
|
+
const normalized = raw.trim().toLowerCase();
|
|
3051
|
+
if (DISABLED_VALUES.has(normalized)) {
|
|
3052
|
+
return { enabled: false, warnings: [] };
|
|
3053
|
+
}
|
|
3054
|
+
if (ENABLED_VALUES.has(normalized)) {
|
|
3055
|
+
return { enabled: true, warnings: [] };
|
|
3056
|
+
}
|
|
3057
|
+
return {
|
|
3058
|
+
enabled: true,
|
|
3059
|
+
warnings: [
|
|
3060
|
+
`Ignoring invalid EVIDENT_RESOURCE_USAGE_REPORTING "${raw}": expected on or off; leaving reporting on`
|
|
3061
|
+
]
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
// src/lib/resource-usage.ts
|
|
3066
|
+
import { cpus, totalmem, freemem } from "os";
|
|
3067
|
+
import { statfsSync as statfsSync2 } from "fs";
|
|
3068
|
+
|
|
3069
|
+
// src/lib/ecs-task-metadata.ts
|
|
3070
|
+
var ECS_METADATA_TIMEOUT_MS = 2e3;
|
|
3071
|
+
function parseEcsTaskLimits(payload) {
|
|
3072
|
+
if (typeof payload !== "object" || payload === null) return null;
|
|
3073
|
+
const limits = payload.Limits;
|
|
3074
|
+
if (typeof limits !== "object" || limits === null) return null;
|
|
3075
|
+
const cpu = limits.CPU;
|
|
3076
|
+
const memory = limits.Memory;
|
|
3077
|
+
if (typeof cpu !== "number" || !Number.isFinite(cpu) || cpu <= 0) return null;
|
|
3078
|
+
if (typeof memory !== "number" || !Number.isFinite(memory) || memory <= 0) return null;
|
|
3079
|
+
return {
|
|
3080
|
+
cpuCount: Math.max(1, Math.round(cpu)),
|
|
3081
|
+
memoryTotalBytes: memory * 1024 * 1024
|
|
3082
|
+
};
|
|
3083
|
+
}
|
|
3084
|
+
async function readEcsTaskLimits(env) {
|
|
3085
|
+
const uri = env.ECS_CONTAINER_METADATA_URI_V4;
|
|
3086
|
+
if (!uri) {
|
|
3087
|
+
return { limits: null };
|
|
3088
|
+
}
|
|
3089
|
+
const url = `${uri}/task`;
|
|
3090
|
+
try {
|
|
3091
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(ECS_METADATA_TIMEOUT_MS) });
|
|
3092
|
+
if (!response.ok) {
|
|
3093
|
+
return {
|
|
3094
|
+
limits: null,
|
|
3095
|
+
warning: `ECS task metadata fetch (${url}) returned HTTP ${response.status}`
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
const payload = await response.json();
|
|
3099
|
+
const limits = parseEcsTaskLimits(payload);
|
|
3100
|
+
if (limits === null) {
|
|
3101
|
+
return {
|
|
3102
|
+
limits: null,
|
|
3103
|
+
warning: `ECS task metadata fetch (${url}) returned an unexpected payload`
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
return { limits };
|
|
3107
|
+
} catch (error2) {
|
|
3108
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
3109
|
+
return { limits: null, warning: `ECS task metadata fetch (${url}) failed: ${message}` };
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
// src/lib/resource-usage.ts
|
|
3114
|
+
function readCpuSample() {
|
|
3115
|
+
let busyMs = 0;
|
|
3116
|
+
let idleMs = 0;
|
|
3117
|
+
for (const cpu of cpus()) {
|
|
3118
|
+
busyMs += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.irq;
|
|
3119
|
+
idleMs += cpu.times.idle;
|
|
3120
|
+
}
|
|
3121
|
+
return { busyMs, idleMs };
|
|
3122
|
+
}
|
|
3123
|
+
function cpuPercentBetween(previous, current) {
|
|
3124
|
+
const deltaBusy = current.busyMs - previous.busyMs;
|
|
3125
|
+
const deltaIdle = current.idleMs - previous.idleMs;
|
|
3126
|
+
const total = deltaBusy + deltaIdle;
|
|
3127
|
+
if (total === 0) return null;
|
|
3128
|
+
return Math.round((deltaBusy / total * 100 + Number.EPSILON) * 100) / 100;
|
|
3129
|
+
}
|
|
3130
|
+
function clamp(value, min, max) {
|
|
3131
|
+
return Math.min(Math.max(value, min), max);
|
|
3132
|
+
}
|
|
3133
|
+
function round2(value) {
|
|
3134
|
+
return Math.round((value + Number.EPSILON) * 100) / 100;
|
|
3135
|
+
}
|
|
3136
|
+
function readDisk(homeDir) {
|
|
3137
|
+
try {
|
|
3138
|
+
const stats = statfsSync2(homeDir);
|
|
3139
|
+
return {
|
|
3140
|
+
totalBytes: stats.bsize * stats.blocks,
|
|
3141
|
+
freeBytes: stats.bsize * stats.bavail
|
|
3142
|
+
};
|
|
3143
|
+
} catch (error2) {
|
|
3144
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
3145
|
+
return {
|
|
3146
|
+
totalBytes: null,
|
|
3147
|
+
freeBytes: null,
|
|
3148
|
+
warning: `Could not read disk usage for ${homeDir}: ${message}`
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
function createResourceUsageCollector(homeDir) {
|
|
3153
|
+
let previous = readCpuSample();
|
|
3154
|
+
return async () => {
|
|
3155
|
+
const current = readCpuSample();
|
|
3156
|
+
const hostCpuPercent = cpuPercentBetween(previous, current);
|
|
3157
|
+
const hostCpuCount = cpus().length;
|
|
3158
|
+
previous = current;
|
|
3159
|
+
const disk = readDisk(homeDir);
|
|
3160
|
+
const opencodeDbBytes = statSessionDbBytes(homeDir);
|
|
3161
|
+
const { limits, warning: ecsWarning } = await readEcsTaskLimits(process.env);
|
|
3162
|
+
const warnings = [];
|
|
3163
|
+
if (disk.warning) warnings.push(disk.warning);
|
|
3164
|
+
if (ecsWarning) warnings.push(ecsWarning);
|
|
3165
|
+
let cpuPercent = hostCpuPercent;
|
|
3166
|
+
let cpuCount = hostCpuCount;
|
|
3167
|
+
let memoryTotalBytes = totalmem();
|
|
3168
|
+
let memoryAvailableBytes = freemem();
|
|
3169
|
+
if (limits !== null) {
|
|
3170
|
+
cpuCount = limits.cpuCount;
|
|
3171
|
+
memoryTotalBytes = limits.memoryTotalBytes;
|
|
3172
|
+
memoryAvailableBytes = clamp(
|
|
3173
|
+
limits.memoryTotalBytes - (totalmem() - freemem()),
|
|
3174
|
+
0,
|
|
3175
|
+
limits.memoryTotalBytes
|
|
3176
|
+
);
|
|
3177
|
+
cpuPercent = hostCpuPercent === null ? null : clamp(round2(hostCpuPercent * hostCpuCount / limits.cpuCount), 0, 100);
|
|
3178
|
+
}
|
|
3179
|
+
return {
|
|
3180
|
+
usage: {
|
|
3181
|
+
cpuPercent,
|
|
3182
|
+
cpuCount,
|
|
3183
|
+
memoryTotalBytes,
|
|
3184
|
+
memoryAvailableBytes,
|
|
3185
|
+
diskTotalBytes: disk.totalBytes,
|
|
3186
|
+
diskFreeBytes: disk.freeBytes,
|
|
3187
|
+
opencodeDbBytes
|
|
3188
|
+
},
|
|
3189
|
+
warnings
|
|
3190
|
+
};
|
|
3191
|
+
};
|
|
2993
3192
|
}
|
|
2994
3193
|
|
|
2995
3194
|
// src/lib/channels/driver.ts
|
|
2996
3195
|
import { homedir as homedir2 } from "os";
|
|
2997
3196
|
|
|
3197
|
+
// src/lib/runner-file-sync.ts
|
|
3198
|
+
import { join as join4 } from "path";
|
|
3199
|
+
|
|
2998
3200
|
// src/lib/file-push.ts
|
|
2999
3201
|
import { randomUUID } from "crypto";
|
|
3000
3202
|
import { chmod, mkdir, open as open2, realpath, rename, unlink } from "fs/promises";
|
|
@@ -3188,17 +3390,20 @@ async function syncPendingRunnerFiles(options) {
|
|
|
3188
3390
|
for (const id of options.ackFailures.keys()) {
|
|
3189
3391
|
if (!pendingIds.has(id)) options.ackFailures.delete(id);
|
|
3190
3392
|
}
|
|
3191
|
-
if (pending.length === 0) return 0;
|
|
3393
|
+
if (pending.length === 0) return { applied: 0, claudeCredentialApplied: false };
|
|
3192
3394
|
options.log({
|
|
3193
3395
|
level: "info",
|
|
3194
3396
|
message: `Runner file sync: ${pending.length} file(s) queued for this runner`
|
|
3195
3397
|
});
|
|
3196
3398
|
let applied = 0;
|
|
3399
|
+
let claudeCredentialApplied = false;
|
|
3197
3400
|
for (const file of pending) {
|
|
3198
3401
|
if ((options.ackFailures.get(file.id) ?? 0) >= MAX_ACK_ATTEMPTS) continue;
|
|
3199
|
-
|
|
3402
|
+
const outcome = await applyOne(options, file);
|
|
3403
|
+
if (outcome.applied) applied += 1;
|
|
3404
|
+
if (outcome.claudeCredentialApplied) claudeCredentialApplied = true;
|
|
3200
3405
|
}
|
|
3201
|
-
return applied;
|
|
3406
|
+
return { applied, claudeCredentialApplied };
|
|
3202
3407
|
}
|
|
3203
3408
|
async function listPendingFiles(options) {
|
|
3204
3409
|
let res;
|
|
@@ -3259,6 +3464,11 @@ function asPendingFile(entry) {
|
|
|
3259
3464
|
if (typeof size !== "number" || !Number.isFinite(size) || size < 0) return null;
|
|
3260
3465
|
return { id, path, size };
|
|
3261
3466
|
}
|
|
3467
|
+
var NOT_APPLIED = { applied: false, claudeCredentialApplied: false };
|
|
3468
|
+
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
3469
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join4(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
3470
|
+
return expanded === join4(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
3471
|
+
}
|
|
3262
3472
|
async function applyOne(options, file) {
|
|
3263
3473
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
3264
3474
|
if (options.allowedDirectories.length === 0) {
|
|
@@ -3267,7 +3477,7 @@ async function applyOne(options, file) {
|
|
|
3267
3477
|
message: `Runner file ${label} rejected: file sync is not enabled on this runner (start it with --enable-file-sync-to)`
|
|
3268
3478
|
});
|
|
3269
3479
|
await ack(options, file, "rejected", "file_sync_disabled");
|
|
3270
|
-
return
|
|
3480
|
+
return NOT_APPLIED;
|
|
3271
3481
|
}
|
|
3272
3482
|
if (file.size > MAX_FILE_PUSH_BYTES) {
|
|
3273
3483
|
options.log({
|
|
@@ -3275,12 +3485,12 @@ async function applyOne(options, file) {
|
|
|
3275
3485
|
message: `Runner file ${label} rejected: declared ${file.size} bytes, the limit is ${MAX_FILE_PUSH_BYTES}`
|
|
3276
3486
|
});
|
|
3277
3487
|
await ack(options, file, "rejected", "file_too_large");
|
|
3278
|
-
return
|
|
3488
|
+
return NOT_APPLIED;
|
|
3279
3489
|
}
|
|
3280
3490
|
const download = await downloadContent(options, file, label);
|
|
3281
3491
|
if (!download.ok) {
|
|
3282
3492
|
if (download.terminal) await ack(options, file, "rejected", download.code);
|
|
3283
|
-
return
|
|
3493
|
+
return NOT_APPLIED;
|
|
3284
3494
|
}
|
|
3285
3495
|
let outcome;
|
|
3286
3496
|
try {
|
|
@@ -3296,7 +3506,7 @@ async function applyOne(options, file) {
|
|
|
3296
3506
|
message: `Runner file ${label} could not be written: ${describe(err)}`
|
|
3297
3507
|
});
|
|
3298
3508
|
await ack(options, file, "rejected", "write_failed");
|
|
3299
|
-
return
|
|
3509
|
+
return NOT_APPLIED;
|
|
3300
3510
|
}
|
|
3301
3511
|
if (!outcome.ok) {
|
|
3302
3512
|
options.log({
|
|
@@ -3304,14 +3514,17 @@ async function applyOne(options, file) {
|
|
|
3304
3514
|
message: `Runner file ${label} rejected (${outcome.code}): ${outcome.message}`
|
|
3305
3515
|
});
|
|
3306
3516
|
await ack(options, file, "rejected", outcome.code);
|
|
3307
|
-
return
|
|
3517
|
+
return NOT_APPLIED;
|
|
3308
3518
|
}
|
|
3309
3519
|
options.log({
|
|
3310
3520
|
level: "info",
|
|
3311
3521
|
message: `Runner file ${label} applied (${download.content.byteLength} bytes)`
|
|
3312
3522
|
});
|
|
3313
3523
|
await ack(options, file, "applied");
|
|
3314
|
-
return
|
|
3524
|
+
return {
|
|
3525
|
+
applied: true,
|
|
3526
|
+
claudeCredentialApplied: isClaudeCredentialPath(file.path, options.homeDir)
|
|
3527
|
+
};
|
|
3315
3528
|
}
|
|
3316
3529
|
function durableDownloadCode(status2) {
|
|
3317
3530
|
return status2 === 413 ? "file_too_large" : "write_failed";
|
|
@@ -3756,6 +3969,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3756
3969
|
* same trick `lastProxiedActivityAt` uses.
|
|
3757
3970
|
*/
|
|
3758
3971
|
appliedFileCount = 0;
|
|
3972
|
+
/**
|
|
3973
|
+
* Generation counter, NOT a tally (#1656): advances by exactly one per sync
|
|
3974
|
+
* batch that applied the Claude CLI credential file, not by how many
|
|
3975
|
+
* credential files were in that batch. `run.ts` only ever tests inequality
|
|
3976
|
+
* against the value it saw last cycle, so magnitude is meaningless — keep it
|
|
3977
|
+
* that way rather than "fixing" it into a count.
|
|
3978
|
+
*/
|
|
3979
|
+
claudeCredentialApplyCount = 0;
|
|
3759
3980
|
/**
|
|
3760
3981
|
* The currently-executing `drainPending()` promise, or null when idle. Lets a
|
|
3761
3982
|
* graceful shutdown (`waitForInFlight`) await an in-progress drain so a turn it
|
|
@@ -3848,7 +4069,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3848
4069
|
if (this.syncingFiles) return 0;
|
|
3849
4070
|
this.syncingFiles = true;
|
|
3850
4071
|
try {
|
|
3851
|
-
const
|
|
4072
|
+
const result = await syncPendingRunnerFiles({
|
|
3852
4073
|
agentId: this.agentId,
|
|
3853
4074
|
apiUrl: this.apiUrl,
|
|
3854
4075
|
getAuthHeader: this.getAuthHeader,
|
|
@@ -3858,8 +4079,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3858
4079
|
ackFailures: this.fileAckFailures,
|
|
3859
4080
|
log: this.log
|
|
3860
4081
|
});
|
|
3861
|
-
this.appliedFileCount += applied;
|
|
3862
|
-
|
|
4082
|
+
this.appliedFileCount += result.applied;
|
|
4083
|
+
if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
|
|
4084
|
+
return result.applied;
|
|
3863
4085
|
} catch (err) {
|
|
3864
4086
|
this.log({
|
|
3865
4087
|
level: "error",
|
|
@@ -3950,12 +4172,24 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
3950
4172
|
* `appliedFiles` is monotonic so a pull that started AND finished between two
|
|
3951
4173
|
* idle checks still shows up as an advance.
|
|
3952
4174
|
*
|
|
4175
|
+
* A THIRD signal, `claudeCredentialApplies`, is a separate re-arm trigger
|
|
4176
|
+
* (#1656), not idle accounting: `run.ts` gates re-probing Claude usage
|
|
4177
|
+
* reporting on it advancing, so an unrelated file sync can never disturb a
|
|
4178
|
+
* healthy reporting cadence (#1627) — it never even reaches that trigger, let
|
|
4179
|
+
* alone gets declined by it. Keep this narrower signal OUT of `appliedFiles`,
|
|
4180
|
+
* whose consumer is idle-timeout suppression and must key on ANY file, not
|
|
4181
|
+
* just a Claude credential.
|
|
4182
|
+
*
|
|
3953
4183
|
* CALLER CONTRACT: sample `inFlight` BEFORE calling `syncPendingFiles()` for
|
|
3954
4184
|
* the cycle. `syncPendingFiles` sets the flag synchronously, so a caller that
|
|
3955
4185
|
* samples afterwards reads `true` every single cycle and can never idle out.
|
|
3956
4186
|
*/
|
|
3957
4187
|
fileSyncActivity() {
|
|
3958
|
-
return {
|
|
4188
|
+
return {
|
|
4189
|
+
appliedFiles: this.appliedFileCount,
|
|
4190
|
+
inFlight: this.syncingFiles,
|
|
4191
|
+
claudeCredentialApplies: this.claudeCredentialApplyCount
|
|
4192
|
+
};
|
|
3959
4193
|
}
|
|
3960
4194
|
/**
|
|
3961
4195
|
* OpenCode session ids the session-cleanup sweep (issue #190) must NOT delete:
|
|
@@ -4815,6 +5049,17 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
4815
5049
|
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
5050
|
conversation_id: conv.id
|
|
4817
5051
|
});
|
|
5052
|
+
const watcher = this.watchers.get(bound);
|
|
5053
|
+
if (watcher) {
|
|
5054
|
+
for (const [evidentMessageId, inFlight] of [...watcher.inFlight]) {
|
|
5055
|
+
this.recordReleasedOpencodeId(evidentMessageId, bound, inFlight.opencodeMessageId);
|
|
5056
|
+
this.removeInFlight(watcher, evidentMessageId);
|
|
5057
|
+
void this.postSignal(watcher.conv.id, evidentMessageId, "watcher_recovered", {
|
|
5058
|
+
recovery: "session_gone_released"
|
|
5059
|
+
});
|
|
5060
|
+
}
|
|
5061
|
+
this.watchers.delete(bound);
|
|
5062
|
+
}
|
|
4818
5063
|
this.sessions.delete(conv.id);
|
|
4819
5064
|
return { sessionId: await this.createAndBindSession(conv.id), created: true };
|
|
4820
5065
|
}
|
|
@@ -7280,7 +7525,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
7280
7525
|
if (trimmed === "") {
|
|
7281
7526
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
7282
7527
|
}
|
|
7283
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
7528
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join5(homeDir, trimmed.slice(2)) : trimmed;
|
|
7284
7529
|
if (!isAbsolute2(expanded)) {
|
|
7285
7530
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
7286
7531
|
}
|
|
@@ -7487,6 +7732,7 @@ async function driveChannels(state, driver) {
|
|
|
7487
7732
|
let unreachableMs = 0;
|
|
7488
7733
|
let lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
7489
7734
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
7735
|
+
let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
|
|
7490
7736
|
while (state.running) {
|
|
7491
7737
|
const cycleStartedAtMs = performance.now();
|
|
7492
7738
|
let idleThisCycle = false;
|
|
@@ -7510,11 +7756,15 @@ async function driveChannels(state, driver) {
|
|
|
7510
7756
|
state.messageCount += processed;
|
|
7511
7757
|
const proxiedActivity = state.lastProxiedActivityAt !== lastSeenProxiedActivityAt;
|
|
7512
7758
|
lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
7513
|
-
const
|
|
7759
|
+
const fileActivitySnapshot = driver.fileSyncActivity();
|
|
7760
|
+
const appliedFiles = fileActivitySnapshot.appliedFiles;
|
|
7514
7761
|
const filesApplied = appliedFiles !== lastSeenAppliedFiles;
|
|
7515
7762
|
const fileActivity = carriedOverFileSync || filesApplied;
|
|
7516
7763
|
lastSeenAppliedFiles = appliedFiles;
|
|
7517
|
-
|
|
7764
|
+
const claudeCredentialApplies = fileActivitySnapshot.claudeCredentialApplies;
|
|
7765
|
+
const claudeCredentialApplied = claudeCredentialApplies !== lastSeenClaudeApplies;
|
|
7766
|
+
lastSeenClaudeApplies = claudeCredentialApplies;
|
|
7767
|
+
if (claudeCredentialApplied) state.claudeUsageRearm?.();
|
|
7518
7768
|
if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity || fileActivity) {
|
|
7519
7769
|
idlePolls = 0;
|
|
7520
7770
|
idleMs = 0;
|
|
@@ -7583,7 +7833,7 @@ async function driveChannels(state, driver) {
|
|
|
7583
7833
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
7584
7834
|
var SESSION_DB_RECLAIM_MAX_PAGES = 2e3;
|
|
7585
7835
|
function sessionDbPath() {
|
|
7586
|
-
return
|
|
7836
|
+
return join5(homedir3(), ".local", "share", "opencode", "opencode.db");
|
|
7587
7837
|
}
|
|
7588
7838
|
async function runSweep(state, driver, config) {
|
|
7589
7839
|
const mode = `age=${config.maxAgeMs ?? "\u2014"} count=${config.maxCount ?? "\u2014"}`;
|
|
@@ -7694,9 +7944,6 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
7694
7944
|
);
|
|
7695
7945
|
state.sessionCleanupTimers.push(interval, firstSweep);
|
|
7696
7946
|
}
|
|
7697
|
-
function claudeUsageFailureStreakSuffix(consecutiveFailures) {
|
|
7698
|
-
return consecutiveFailures > 1 ? ` (${consecutiveFailures} consecutive failures)` : "";
|
|
7699
|
-
}
|
|
7700
7947
|
function scheduleClaudeUsageReporting(state, options) {
|
|
7701
7948
|
const { mode, warnings } = resolveClaudeUsageReportingMode(
|
|
7702
7949
|
options.claudeUsageReporting,
|
|
@@ -7724,13 +7971,13 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7724
7971
|
phase = "probe-pending";
|
|
7725
7972
|
state.claudeUsageTimer = setTimeout(() => void tick(true), FIRST_REPORT_DELAY_MS);
|
|
7726
7973
|
};
|
|
7727
|
-
const scheduleNextTick = (
|
|
7974
|
+
const scheduleNextTick = () => {
|
|
7728
7975
|
if (rearmRequested) {
|
|
7729
7976
|
rearmRequested = false;
|
|
7730
7977
|
armProbe();
|
|
7731
7978
|
return;
|
|
7732
7979
|
}
|
|
7733
|
-
phase =
|
|
7980
|
+
phase = "steady-pending";
|
|
7734
7981
|
state.claudeUsageTimer = setTimeout(() => void tick(false), nextReportDelayMs());
|
|
7735
7982
|
};
|
|
7736
7983
|
const rearm = () => {
|
|
@@ -7740,9 +7987,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7740
7987
|
return;
|
|
7741
7988
|
case "probe-pending":
|
|
7742
7989
|
return;
|
|
7743
|
-
case "steady-pending
|
|
7744
|
-
return;
|
|
7745
|
-
case "steady-pending-retry":
|
|
7990
|
+
case "steady-pending":
|
|
7746
7991
|
if (state.claudeUsageTimer) {
|
|
7747
7992
|
clearTimeout(state.claudeUsageTimer);
|
|
7748
7993
|
state.claudeUsageTimer = null;
|
|
@@ -7780,10 +8025,10 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7780
8025
|
logActivity(state, {
|
|
7781
8026
|
type: "info",
|
|
7782
8027
|
level: claudeUsageFailureLogLevel(consecutiveFailures),
|
|
7783
|
-
message: `Failed to report Claude usage: ${result.error}${
|
|
8028
|
+
message: `Failed to report Claude usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
|
|
7784
8029
|
});
|
|
7785
8030
|
}
|
|
7786
|
-
scheduleNextTick(
|
|
8031
|
+
scheduleNextTick();
|
|
7787
8032
|
} catch (error2) {
|
|
7788
8033
|
if (error2 instanceof ClaudeUsageError && isLocalCredentialProblem(error2)) {
|
|
7789
8034
|
if (mode === "on") {
|
|
@@ -7792,7 +8037,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7792
8037
|
level: "warn",
|
|
7793
8038
|
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
8039
|
});
|
|
7795
|
-
scheduleNextTick(
|
|
8040
|
+
scheduleNextTick();
|
|
7796
8041
|
} else if (isProbe) {
|
|
7797
8042
|
logActivity(state, {
|
|
7798
8043
|
type: "info",
|
|
@@ -7807,7 +8052,7 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7807
8052
|
level: "debug",
|
|
7808
8053
|
message: `Claude usage reporting: ${error2.message}`
|
|
7809
8054
|
});
|
|
7810
|
-
scheduleNextTick(
|
|
8055
|
+
scheduleNextTick();
|
|
7811
8056
|
}
|
|
7812
8057
|
} else {
|
|
7813
8058
|
consecutiveFailures++;
|
|
@@ -7815,15 +8060,99 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
7815
8060
|
logActivity(state, {
|
|
7816
8061
|
type: "info",
|
|
7817
8062
|
level: claudeUsageFailureLogLevel(consecutiveFailures),
|
|
7818
|
-
message: `Claude usage reporting failed: ${message}${
|
|
8063
|
+
message: `Claude usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
7819
8064
|
});
|
|
7820
|
-
scheduleNextTick(
|
|
8065
|
+
scheduleNextTick();
|
|
7821
8066
|
}
|
|
7822
8067
|
}
|
|
7823
8068
|
};
|
|
7824
8069
|
armProbe();
|
|
7825
8070
|
return rearm;
|
|
7826
8071
|
}
|
|
8072
|
+
var RESOURCE_USAGE_BASE_REPORT_DELAY_MS = 10 * 6e4;
|
|
8073
|
+
var RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION = 0.2;
|
|
8074
|
+
var RESOURCE_USAGE_FAILURE_REESCALATION_TICKS = 6;
|
|
8075
|
+
function scheduleResourceUsageReporting(state, options) {
|
|
8076
|
+
const { enabled, warnings } = resolveResourceUsageReportingEnabled(
|
|
8077
|
+
options.resourceUsageReporting,
|
|
8078
|
+
process.env
|
|
8079
|
+
);
|
|
8080
|
+
for (const warning2 of warnings) {
|
|
8081
|
+
logActivity(state, {
|
|
8082
|
+
type: "info",
|
|
8083
|
+
level: "warn",
|
|
8084
|
+
message: `Resource usage reporting: ${warning2}`
|
|
8085
|
+
});
|
|
8086
|
+
}
|
|
8087
|
+
if (!enabled) {
|
|
8088
|
+
logActivity(state, {
|
|
8089
|
+
type: "info",
|
|
8090
|
+
level: "debug",
|
|
8091
|
+
message: "Resource usage reporting is off (--no-resource-usage-reporting)"
|
|
8092
|
+
});
|
|
8093
|
+
return;
|
|
8094
|
+
}
|
|
8095
|
+
const collect = createResourceUsageCollector(homedir3());
|
|
8096
|
+
let consecutiveFailures = 0;
|
|
8097
|
+
const tick = async () => {
|
|
8098
|
+
try {
|
|
8099
|
+
const { usage, warnings: collectWarnings } = await collect();
|
|
8100
|
+
for (const warning2 of collectWarnings) {
|
|
8101
|
+
logActivity(state, {
|
|
8102
|
+
type: "info",
|
|
8103
|
+
level: "debug",
|
|
8104
|
+
message: `Resource usage collection: ${warning2}`
|
|
8105
|
+
});
|
|
8106
|
+
}
|
|
8107
|
+
const result = await reportResourceUsage(state.agentId, state.authHeader, usage);
|
|
8108
|
+
if (result.ok) {
|
|
8109
|
+
if (consecutiveFailures > 0) {
|
|
8110
|
+
logActivity(state, {
|
|
8111
|
+
type: "info",
|
|
8112
|
+
level: "info",
|
|
8113
|
+
message: "Resource usage reporting recovered"
|
|
8114
|
+
});
|
|
8115
|
+
}
|
|
8116
|
+
consecutiveFailures = 0;
|
|
8117
|
+
logActivity(state, {
|
|
8118
|
+
type: "info",
|
|
8119
|
+
level: "debug",
|
|
8120
|
+
message: "Reported resource usage to Evident"
|
|
8121
|
+
});
|
|
8122
|
+
} else {
|
|
8123
|
+
consecutiveFailures++;
|
|
8124
|
+
logActivity(state, {
|
|
8125
|
+
type: "info",
|
|
8126
|
+
level: reportFailureLogLevel(
|
|
8127
|
+
consecutiveFailures,
|
|
8128
|
+
RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
|
|
8129
|
+
),
|
|
8130
|
+
message: `Failed to report resource usage: ${result.error}${failureStreakSuffix(consecutiveFailures)}`
|
|
8131
|
+
});
|
|
8132
|
+
}
|
|
8133
|
+
} catch (error2) {
|
|
8134
|
+
consecutiveFailures++;
|
|
8135
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
8136
|
+
logActivity(state, {
|
|
8137
|
+
type: "info",
|
|
8138
|
+
level: reportFailureLogLevel(
|
|
8139
|
+
consecutiveFailures,
|
|
8140
|
+
RESOURCE_USAGE_FAILURE_REESCALATION_TICKS
|
|
8141
|
+
),
|
|
8142
|
+
message: `Resource usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
8143
|
+
});
|
|
8144
|
+
} finally {
|
|
8145
|
+
state.resourceUsageTimer = setTimeout(
|
|
8146
|
+
() => void tick(),
|
|
8147
|
+
jitteredDelayMs(
|
|
8148
|
+
RESOURCE_USAGE_BASE_REPORT_DELAY_MS,
|
|
8149
|
+
RESOURCE_USAGE_REPORT_DELAY_JITTER_FRACTION
|
|
8150
|
+
)
|
|
8151
|
+
);
|
|
8152
|
+
}
|
|
8153
|
+
};
|
|
8154
|
+
state.resourceUsageTimer = setTimeout(() => void tick(), firstReportDelayMs());
|
|
8155
|
+
}
|
|
7827
8156
|
async function notifyOffline(state) {
|
|
7828
8157
|
if (!state.agentId || !state.authHeader) return;
|
|
7829
8158
|
if (!state.connected) {
|
|
@@ -7864,6 +8193,10 @@ async function cleanup(state, opts = {}) {
|
|
|
7864
8193
|
state.claudeUsageTimer = null;
|
|
7865
8194
|
}
|
|
7866
8195
|
state.claudeUsageRearm = null;
|
|
8196
|
+
if (state.resourceUsageTimer) {
|
|
8197
|
+
clearTimeout(state.resourceUsageTimer);
|
|
8198
|
+
state.resourceUsageTimer = null;
|
|
8199
|
+
}
|
|
7867
8200
|
if (opts.graceful && state.channelDriver) {
|
|
7868
8201
|
state.channelDriver.stop();
|
|
7869
8202
|
log2(state, "Draining in-flight channel work before shutdown...");
|
|
@@ -7946,6 +8279,7 @@ async function run(options) {
|
|
|
7946
8279
|
sessionCleanupTimers: [],
|
|
7947
8280
|
claudeUsageTimer: null,
|
|
7948
8281
|
claudeUsageRearm: null,
|
|
8282
|
+
resourceUsageTimer: null,
|
|
7949
8283
|
authHeader: ""
|
|
7950
8284
|
};
|
|
7951
8285
|
setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
|
|
@@ -8345,6 +8679,7 @@ async function run(options) {
|
|
|
8345
8679
|
}
|
|
8346
8680
|
scheduleSessionCleanup(state, channelDriver, options);
|
|
8347
8681
|
state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
|
|
8682
|
+
scheduleResourceUsageReporting(state, options);
|
|
8348
8683
|
if (!interactive || state.json) {
|
|
8349
8684
|
log2(state, "Driving channel messages...");
|
|
8350
8685
|
}
|
|
@@ -8425,6 +8760,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
8425
8760
|
).option(
|
|
8426
8761
|
"--claude-usage-reporting <mode>",
|
|
8427
8762
|
"Report Claude subscription usage to Evident: auto | on | off (default: auto). Env: EVIDENT_CLAUDE_USAGE_REPORTING"
|
|
8763
|
+
).option(
|
|
8764
|
+
"--no-resource-usage-reporting",
|
|
8765
|
+
"Don't report this machine's CPU and memory usage to Evident (reporting is on by default). Env: EVIDENT_RESOURCE_USAGE_REPORTING=off"
|
|
8428
8766
|
).option(
|
|
8429
8767
|
"--enable-file-sync-to <dir>",
|
|
8430
8768
|
"Allow Evident to write files into this directory (repeatable). Omit to disable file sync entirely.",
|
|
@@ -8457,6 +8795,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
8457
8795
|
// Raw string — the resolver in run.ts single-sources parsing
|
|
8458
8796
|
// (resolveClaudeUsageReportingMode).
|
|
8459
8797
|
claudeUsageReporting: options.claudeUsageReporting,
|
|
8798
|
+
// Raw value — resolution is single-sourced in run.ts's
|
|
8799
|
+
// resolveResourceUsageReportingEnabled.
|
|
8800
|
+
resourceUsageReporting: options.resourceUsageReporting,
|
|
8460
8801
|
// Raw values — expansion/validation is single-sourced in run.ts's
|
|
8461
8802
|
// resolveFileSyncDirectories.
|
|
8462
8803
|
enableFileSyncTo: options.enableFileSyncTo,
|