@evident-ai/cli 3.4.1-dev.aa71e9a → 3.4.1-dev.b45408b
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 +4 -0
- package/dist/index.js +248 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,10 @@ Options:
|
|
|
98
98
|
healthy when the runner starts it itself (default: `180`). On expiry the runner
|
|
99
99
|
warns and comes online anyway rather than failing. Env:
|
|
100
100
|
`EVIDENT_OPENCODE_START_TIMEOUT` (seconds).
|
|
101
|
+
- `--litestream-config <path>` — Start `litestream replicate` for the OpenCode
|
|
102
|
+
session database with this config file. Use this for runner images that persist
|
|
103
|
+
the session database; the file is produced by `runner-synchroniser litestream-config`.
|
|
104
|
+
Omit it to disable replication.
|
|
101
105
|
- `--claude-usage-reporting <mode>` — Whether to report the local Claude Code
|
|
102
106
|
subscription's rate-limit usage to Evident, so it shows on the runner page:
|
|
103
107
|
`auto` (default) reports it when a usable Claude Code login is found on this
|
package/dist/index.js
CHANGED
|
@@ -722,6 +722,14 @@ function toReportedWindow(window) {
|
|
|
722
722
|
if (!window) return null;
|
|
723
723
|
return { utilization: window.utilization, resets_at: window.resetsAt };
|
|
724
724
|
}
|
|
725
|
+
function toReportedOwner(snapshot) {
|
|
726
|
+
if (!snapshot.owner) return null;
|
|
727
|
+
return {
|
|
728
|
+
email: snapshot.owner.email,
|
|
729
|
+
organization_name: snapshot.owner.organizationName,
|
|
730
|
+
rate_limit_tier: snapshot.owner.rateLimitTier
|
|
731
|
+
};
|
|
732
|
+
}
|
|
725
733
|
async function reportClaudeUsage(agentId, authHeader, snapshot) {
|
|
726
734
|
try {
|
|
727
735
|
const apiUrl = getApiUrlConfig();
|
|
@@ -730,7 +738,8 @@ async function reportClaudeUsage(agentId, authHeader, snapshot) {
|
|
|
730
738
|
headers: { Authorization: authHeader, "Content-Type": "application/json" },
|
|
731
739
|
body: JSON.stringify({
|
|
732
740
|
five_hour: toReportedWindow(snapshot.fiveHour),
|
|
733
|
-
seven_day: toReportedWindow(snapshot.sevenDay)
|
|
741
|
+
seven_day: toReportedWindow(snapshot.sevenDay),
|
|
742
|
+
owner: toReportedOwner(snapshot)
|
|
734
743
|
}),
|
|
735
744
|
signal: AbortSignal.timeout(BEST_EFFORT_NOTIFY_TIMEOUT_MS)
|
|
736
745
|
});
|
|
@@ -996,7 +1005,10 @@ import { readFileSync } from "fs";
|
|
|
996
1005
|
import { homedir } from "os";
|
|
997
1006
|
import { join } from "path";
|
|
998
1007
|
var CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
1008
|
+
var CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
|
|
1009
|
+
var CLAUDE_PROFILE_TIMEOUT_MS = 2e3;
|
|
999
1010
|
var KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
1011
|
+
var cachedOwner = null;
|
|
1000
1012
|
var CLAUDE_CREDENTIALS_SEGMENTS = [".claude", ".credentials.json"];
|
|
1001
1013
|
function parseClaudeCliCredentials(raw) {
|
|
1002
1014
|
let parsed;
|
|
@@ -1070,6 +1082,47 @@ function toWindow(value) {
|
|
|
1070
1082
|
}
|
|
1071
1083
|
return { utilization: window.utilization, resetsAt };
|
|
1072
1084
|
}
|
|
1085
|
+
function ownerLookupFailure(error2) {
|
|
1086
|
+
const name = error2?.name;
|
|
1087
|
+
return name === "TimeoutError" || name === "AbortError" ? "timed out" : "request failed";
|
|
1088
|
+
}
|
|
1089
|
+
async function getClaudeUsageOwner(accessToken) {
|
|
1090
|
+
if (cachedOwner?.accessToken === accessToken) {
|
|
1091
|
+
return { owner: cachedOwner.owner, ownerLookupError: null };
|
|
1092
|
+
}
|
|
1093
|
+
try {
|
|
1094
|
+
const response = await fetch(CLAUDE_PROFILE_URL, {
|
|
1095
|
+
headers: {
|
|
1096
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1097
|
+
"Content-Type": "application/json",
|
|
1098
|
+
"anthropic-version": "2023-06-01"
|
|
1099
|
+
},
|
|
1100
|
+
signal: AbortSignal.timeout(CLAUDE_PROFILE_TIMEOUT_MS)
|
|
1101
|
+
});
|
|
1102
|
+
if (!response.ok) {
|
|
1103
|
+
return { owner: null, ownerLookupError: `HTTP ${response.status}` };
|
|
1104
|
+
}
|
|
1105
|
+
let body;
|
|
1106
|
+
try {
|
|
1107
|
+
body = await response.json();
|
|
1108
|
+
} catch (error2) {
|
|
1109
|
+
return { owner: null, ownerLookupError: "malformed response" };
|
|
1110
|
+
}
|
|
1111
|
+
const profile = body;
|
|
1112
|
+
if (typeof profile.account?.email !== "string" || !profile.account.email.trim()) {
|
|
1113
|
+
return { owner: null, ownerLookupError: "malformed response" };
|
|
1114
|
+
}
|
|
1115
|
+
const owner = {
|
|
1116
|
+
email: profile.account.email,
|
|
1117
|
+
organizationName: typeof profile.organization?.name === "string" && profile.organization.name.trim() ? profile.organization.name.trim() : null,
|
|
1118
|
+
rateLimitTier: typeof profile.organization?.rate_limit_tier === "string" && profile.organization.rate_limit_tier.trim() ? profile.organization.rate_limit_tier.trim() : null
|
|
1119
|
+
};
|
|
1120
|
+
cachedOwner = { accessToken, owner };
|
|
1121
|
+
return { owner, ownerLookupError: null };
|
|
1122
|
+
} catch (error2) {
|
|
1123
|
+
return { owner: null, ownerLookupError: ownerLookupFailure(error2) };
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1073
1126
|
async function getClaudeUsage() {
|
|
1074
1127
|
const credentials2 = readClaudeCliCredentials();
|
|
1075
1128
|
if (!credentials2) {
|
|
@@ -1089,15 +1142,19 @@ async function getClaudeUsage() {
|
|
|
1089
1142
|
Authorization: `Bearer ${credentials2.accessToken}`,
|
|
1090
1143
|
"Content-Type": "application/json",
|
|
1091
1144
|
"anthropic-version": "2023-06-01"
|
|
1092
|
-
}
|
|
1145
|
+
},
|
|
1146
|
+
signal: AbortSignal.timeout(CLAUDE_PROFILE_TIMEOUT_MS)
|
|
1093
1147
|
});
|
|
1094
1148
|
if (!res.ok) {
|
|
1095
1149
|
throw new ClaudeUsageError(`Claude usage request failed: HTTP ${res.status}`, "request_failed");
|
|
1096
1150
|
}
|
|
1097
1151
|
const body = await res.json();
|
|
1152
|
+
const { owner, ownerLookupError } = await getClaudeUsageOwner(credentials2.accessToken);
|
|
1098
1153
|
return {
|
|
1099
1154
|
fiveHour: toWindow(body.five_hour),
|
|
1100
|
-
sevenDay: toWindow(body.seven_day)
|
|
1155
|
+
sevenDay: toWindow(body.seven_day),
|
|
1156
|
+
owner,
|
|
1157
|
+
ownerLookupError
|
|
1101
1158
|
};
|
|
1102
1159
|
}
|
|
1103
1160
|
|
|
@@ -1537,6 +1594,12 @@ function buildSessionDbRecoveryActivity(record) {
|
|
|
1537
1594
|
metadata: withoutContractFields(record),
|
|
1538
1595
|
message: "This runner started with a fresh session database because it could not verify where its session history is backed up. Nothing in the backup was changed; check the runner backup configuration and permissions."
|
|
1539
1596
|
};
|
|
1597
|
+
case "session_db_boot_refused":
|
|
1598
|
+
return {
|
|
1599
|
+
level,
|
|
1600
|
+
metadata: withoutContractFields(record),
|
|
1601
|
+
message: `This runner did not come online because its damaged session database could not be safely separated from its active backup or proven removed. Backed-up session history remains readable at ${record.quarantine_destination ?? "its original location or the quarantine destination named in the boot logs"}; any local session-database files that remain were left in place and nothing opened or wrote them. See the runner boot logs for SESSION-DB-LOCAL-DISCARD-FAILED details.`
|
|
1602
|
+
};
|
|
1540
1603
|
default:
|
|
1541
1604
|
return null;
|
|
1542
1605
|
}
|
|
@@ -1550,7 +1613,8 @@ var OUTCOMES = /* @__PURE__ */ new Set([
|
|
|
1550
1613
|
"restore_retried",
|
|
1551
1614
|
"fresh_session_db",
|
|
1552
1615
|
"history_rolled_back",
|
|
1553
|
-
"restore_misconfigured"
|
|
1616
|
+
"restore_misconfigured",
|
|
1617
|
+
"session_db_boot_refused"
|
|
1554
1618
|
]);
|
|
1555
1619
|
var STAGES = /* @__PURE__ */ new Set(["restore", "verify"]);
|
|
1556
1620
|
var SEVERITIES = /* @__PURE__ */ new Set(["warning", "error"]);
|
|
@@ -1617,6 +1681,62 @@ function buildOpenCodeVersionWarning(version2) {
|
|
|
1617
1681
|
|
|
1618
1682
|
// src/lib/opencode/process.ts
|
|
1619
1683
|
import { execSync, spawn } from "child_process";
|
|
1684
|
+
|
|
1685
|
+
// src/lib/process-stop.ts
|
|
1686
|
+
async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
|
|
1687
|
+
if (!child.pid) {
|
|
1688
|
+
return { outcome: "not-running", code: child.exitCode, signal: child.signalCode };
|
|
1689
|
+
}
|
|
1690
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
1691
|
+
return { outcome: "exited", code: child.exitCode, signal: child.signalCode };
|
|
1692
|
+
}
|
|
1693
|
+
return new Promise((resolve3, reject) => {
|
|
1694
|
+
let forced = false;
|
|
1695
|
+
let settled = false;
|
|
1696
|
+
const timer = setTimeout(() => {
|
|
1697
|
+
forced = true;
|
|
1698
|
+
try {
|
|
1699
|
+
sendKill();
|
|
1700
|
+
} catch (error2) {
|
|
1701
|
+
if (error2.code === "ESRCH") {
|
|
1702
|
+
finish({ outcome: "exited", code: child.exitCode, signal: child.signalCode });
|
|
1703
|
+
} else {
|
|
1704
|
+
fail(error2);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
}, timeoutMs);
|
|
1708
|
+
const finish = (result) => {
|
|
1709
|
+
if (settled) return;
|
|
1710
|
+
settled = true;
|
|
1711
|
+
clearTimeout(timer);
|
|
1712
|
+
child.removeListener("exit", onExit);
|
|
1713
|
+
resolve3(result);
|
|
1714
|
+
};
|
|
1715
|
+
const fail = (error2) => {
|
|
1716
|
+
if (settled) return;
|
|
1717
|
+
settled = true;
|
|
1718
|
+
clearTimeout(timer);
|
|
1719
|
+
child.removeListener("exit", onExit);
|
|
1720
|
+
reject(error2);
|
|
1721
|
+
};
|
|
1722
|
+
const onExit = (code, signal) => {
|
|
1723
|
+
finish({ outcome: forced ? "killed" : "exited", code, signal });
|
|
1724
|
+
};
|
|
1725
|
+
child.once("exit", onExit);
|
|
1726
|
+
try {
|
|
1727
|
+
sendTerm();
|
|
1728
|
+
} catch (error2) {
|
|
1729
|
+
if (error2.code === "ESRCH") {
|
|
1730
|
+
finish({ outcome: "exited", code: child.exitCode, signal: child.signalCode });
|
|
1731
|
+
} else {
|
|
1732
|
+
fail(error2);
|
|
1733
|
+
}
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// src/lib/opencode/process.ts
|
|
1620
1740
|
var OPENCODE_PORT_RANGE = [4096, 4097, 4098, 4099, 4100];
|
|
1621
1741
|
function getProcessCwd(pid) {
|
|
1622
1742
|
const platform = process.platform;
|
|
@@ -1788,23 +1908,20 @@ async function startOpenCode(port) {
|
|
|
1788
1908
|
});
|
|
1789
1909
|
return child;
|
|
1790
1910
|
}
|
|
1791
|
-
function
|
|
1792
|
-
|
|
1793
|
-
return;
|
|
1794
|
-
}
|
|
1795
|
-
try {
|
|
1911
|
+
function stopOpenCodeAndWait(opencodeProcess, timeoutMs) {
|
|
1912
|
+
const sendSignal = (signal) => {
|
|
1796
1913
|
if (process.platform === "win32") {
|
|
1797
|
-
opencodeProcess.kill(
|
|
1914
|
+
opencodeProcess.kill(signal);
|
|
1798
1915
|
} else {
|
|
1799
|
-
process.kill(-opencodeProcess.pid,
|
|
1800
|
-
}
|
|
1801
|
-
} catch (err) {
|
|
1802
|
-
if (err.code !== "ESRCH") {
|
|
1803
|
-
console.warn(
|
|
1804
|
-
`stopOpenCode: kill failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1805
|
-
);
|
|
1916
|
+
process.kill(-opencodeProcess.pid, signal);
|
|
1806
1917
|
}
|
|
1807
|
-
}
|
|
1918
|
+
};
|
|
1919
|
+
return stopProcessAndWait(
|
|
1920
|
+
opencodeProcess,
|
|
1921
|
+
timeoutMs,
|
|
1922
|
+
() => sendSignal("SIGTERM"),
|
|
1923
|
+
() => sendSignal("SIGKILL")
|
|
1924
|
+
);
|
|
1808
1925
|
}
|
|
1809
1926
|
|
|
1810
1927
|
// src/lib/opencode/install.ts
|
|
@@ -2387,7 +2504,7 @@ function isPreamblePinnedRunning(messages, userMessageId) {
|
|
|
2387
2504
|
return completedOf(reply) != null && finishOf(reply) === "tool-calls";
|
|
2388
2505
|
}
|
|
2389
2506
|
function isB2AbandonmentConfirmed(params) {
|
|
2390
|
-
return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false;
|
|
2507
|
+
return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false && params.rootOngoing === false;
|
|
2391
2508
|
}
|
|
2392
2509
|
function isAmbiguousTerminalFinish(m) {
|
|
2393
2510
|
if (completedOf(m) == null) return false;
|
|
@@ -2400,7 +2517,7 @@ function isAmbiguousFinishPinnedRunning(messages, userMessageId) {
|
|
|
2400
2517
|
return isAmbiguousTerminalFinish(reply);
|
|
2401
2518
|
}
|
|
2402
2519
|
function isAmbiguousFinishResolved(params) {
|
|
2403
|
-
return params.sessionOngoing === false || params.pinnedForMs >= params.maxPinnedMs;
|
|
2520
|
+
return params.sessionOngoing === false || params.sessionOngoing !== true && params.pinnedForMs >= params.maxPinnedMs;
|
|
2404
2521
|
}
|
|
2405
2522
|
function messageError(messages, userMessageId) {
|
|
2406
2523
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
@@ -3182,6 +3299,22 @@ function writeTunnelReadyMarker(path, agentId) {
|
|
|
3182
3299
|
}
|
|
3183
3300
|
}
|
|
3184
3301
|
|
|
3302
|
+
// src/lib/replication.ts
|
|
3303
|
+
import { spawn as spawn2 } from "child_process";
|
|
3304
|
+
function startSessionDbReplication(configPath) {
|
|
3305
|
+
return spawn2("litestream", ["replicate", "-config", configPath], {
|
|
3306
|
+
stdio: "inherit"
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
async function stopSessionDbReplication(child, timeoutMs) {
|
|
3310
|
+
return stopProcessAndWait(
|
|
3311
|
+
child,
|
|
3312
|
+
timeoutMs,
|
|
3313
|
+
() => child.kill("SIGTERM"),
|
|
3314
|
+
() => child.kill("SIGKILL")
|
|
3315
|
+
);
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3185
3318
|
// src/lib/openai-usage.ts
|
|
3186
3319
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3187
3320
|
import { homedir as homedir2 } from "os";
|
|
@@ -5676,6 +5809,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5676
5809
|
deliveryDeadlineAnchored: false,
|
|
5677
5810
|
b2PinnedSinceMs: 0,
|
|
5678
5811
|
b2LastDescendantCheckMs: 0,
|
|
5812
|
+
b2RootOngoingHeldLogged: false,
|
|
5679
5813
|
b2AbandonedSignalled: false,
|
|
5680
5814
|
ambiguousPinnedSinceMs: 0,
|
|
5681
5815
|
ambiguousResolved: false
|
|
@@ -5770,6 +5904,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5770
5904
|
deliveryDeadlineAnchored: false,
|
|
5771
5905
|
b2PinnedSinceMs: 0,
|
|
5772
5906
|
b2LastDescendantCheckMs: 0,
|
|
5907
|
+
b2RootOngoingHeldLogged: false,
|
|
5773
5908
|
b2AbandonedSignalled: false,
|
|
5774
5909
|
ambiguousPinnedSinceMs: 0,
|
|
5775
5910
|
ambiguousResolved: false
|
|
@@ -6123,6 +6258,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6123
6258
|
if (snapshotReadable) {
|
|
6124
6259
|
inFlight.b2PinnedSinceMs = 0;
|
|
6125
6260
|
inFlight.b2LastDescendantCheckMs = 0;
|
|
6261
|
+
inFlight.b2RootOngoingHeldLogged = false;
|
|
6126
6262
|
inFlight.b2AbandonedSignalled = false;
|
|
6127
6263
|
}
|
|
6128
6264
|
} else {
|
|
@@ -6134,11 +6270,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6134
6270
|
const pinnedForMs = this.now() - inFlight.b2PinnedSinceMs;
|
|
6135
6271
|
if (pinnedForMs >= B2_ABANDONMENT_MIN_PINNED_MS && this.now() - inFlight.b2LastDescendantCheckMs >= B2_ABANDONMENT_RECHECK_MS) {
|
|
6136
6272
|
inFlight.b2LastDescendantCheckMs = this.now();
|
|
6137
|
-
const descendantOngoing = await
|
|
6273
|
+
const [descendantOngoing, rootOngoing] = await Promise.all([
|
|
6274
|
+
this.isAnyDescendantSessionOngoing(sessionId),
|
|
6275
|
+
isSessionOngoing(this.port, sessionId)
|
|
6276
|
+
]);
|
|
6138
6277
|
if (isB2AbandonmentConfirmed({
|
|
6139
6278
|
pinnedForMs,
|
|
6140
6279
|
minPinnedMs: B2_ABANDONMENT_MIN_PINNED_MS,
|
|
6141
|
-
descendantOngoing
|
|
6280
|
+
descendantOngoing,
|
|
6281
|
+
rootOngoing
|
|
6142
6282
|
})) {
|
|
6143
6283
|
inFlight.b2AbandonedSignalled = true;
|
|
6144
6284
|
this.log({
|
|
@@ -6147,12 +6287,26 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6147
6287
|
conversation_id: conv.id,
|
|
6148
6288
|
message_id: id
|
|
6149
6289
|
});
|
|
6290
|
+
const reply = findLastAssistantReplyFor(messages, inFlight.opencodeMessageId);
|
|
6150
6291
|
void this.postSignal(conv.id, id, "b2_abandoned_resolved", {
|
|
6151
|
-
watched_for_ms: pinnedForMs
|
|
6292
|
+
watched_for_ms: pinnedForMs,
|
|
6293
|
+
finish: reply?.info?.finish ?? reply?.finish,
|
|
6294
|
+
...rootOngoing != null ? { root_ongoing: rootOngoing } : {},
|
|
6295
|
+
...descendantOngoing != null ? { descendant_ongoing: descendantOngoing } : {},
|
|
6296
|
+
opencode_message_id: inFlight.opencodeMessageId
|
|
6152
6297
|
});
|
|
6153
6298
|
await this.settleMessageDone(sessionId, watcher, inFlight, messages);
|
|
6154
6299
|
return;
|
|
6155
6300
|
}
|
|
6301
|
+
if (rootOngoing === true && !inFlight.b2RootOngoingHeldLogged) {
|
|
6302
|
+
inFlight.b2RootOngoingHeldLogged = true;
|
|
6303
|
+
this.log({
|
|
6304
|
+
level: "warn",
|
|
6305
|
+
message: `Message ${id.slice(0, 8)} b2-pinned for ${Math.round(pinnedForMs / 1e3)}s with root_ongoing=${rootOngoing} and descendant_ongoing=${descendantOngoing} \u2014 holding until OpenCode confirms the root is idle`,
|
|
6306
|
+
conversation_id: conv.id,
|
|
6307
|
+
message_id: id
|
|
6308
|
+
});
|
|
6309
|
+
}
|
|
6156
6310
|
}
|
|
6157
6311
|
}
|
|
6158
6312
|
const ambiguousPinnedNow = activelyRunning && isAmbiguousFinishPinnedRunning(messages, inFlight.opencodeMessageId);
|
|
@@ -7895,6 +8049,7 @@ var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_
|
|
|
7895
8049
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
7896
8050
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
7897
8051
|
var TELEMETRY_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
8052
|
+
var CHILD_STOP_TIMEOUT_MS = 1e4;
|
|
7898
8053
|
function resolveLogLevel(options) {
|
|
7899
8054
|
const accepted = Object.keys(LOG_LEVELS);
|
|
7900
8055
|
const validate = (value, source) => {
|
|
@@ -8500,7 +8655,17 @@ function scheduleClaudeUsageReporting(state, options) {
|
|
|
8500
8655
|
setTimer: (timer) => {
|
|
8501
8656
|
state.claudeUsageTimer = timer;
|
|
8502
8657
|
},
|
|
8503
|
-
fetchUsage:
|
|
8658
|
+
fetchUsage: async () => {
|
|
8659
|
+
const usage = await getClaudeUsage();
|
|
8660
|
+
if (usage.ownerLookupError) {
|
|
8661
|
+
logActivity(state, {
|
|
8662
|
+
type: "info",
|
|
8663
|
+
level: "debug",
|
|
8664
|
+
message: `Claude usage owner lookup failed: ${usage.ownerLookupError}`
|
|
8665
|
+
});
|
|
8666
|
+
}
|
|
8667
|
+
return usage;
|
|
8668
|
+
},
|
|
8504
8669
|
report: (usage) => reportClaudeUsage(state.agentId, state.authHeader, usage),
|
|
8505
8670
|
isLocalCredentialProblem,
|
|
8506
8671
|
forcedOnHint: "run `claude` to sign in",
|
|
@@ -8672,15 +8837,31 @@ async function cleanup(state, opts = {}) {
|
|
|
8672
8837
|
}
|
|
8673
8838
|
if (state.opencodeProcess) {
|
|
8674
8839
|
const opencodeProcess = state.opencodeProcess;
|
|
8675
|
-
|
|
8840
|
+
const result = await timeShutdownPhase(
|
|
8841
|
+
state,
|
|
8842
|
+
durations,
|
|
8843
|
+
"opencode_stop",
|
|
8844
|
+
() => stopOpenCodeAndWait(opencodeProcess, CHILD_STOP_TIMEOUT_MS)
|
|
8845
|
+
);
|
|
8676
8846
|
if (state.interactive) {
|
|
8677
|
-
logActivity(state, { type: "info", message:
|
|
8847
|
+
logActivity(state, { type: "info", message: `Stopped OpenCode process (${result.outcome})` });
|
|
8678
8848
|
displayStatus(state);
|
|
8679
8849
|
} else {
|
|
8680
|
-
log2(state,
|
|
8850
|
+
log2(state, `Stopped OpenCode process (${result.outcome})`);
|
|
8681
8851
|
}
|
|
8682
8852
|
state.opencodeProcess = null;
|
|
8683
8853
|
}
|
|
8854
|
+
if (state.litestreamProcess) {
|
|
8855
|
+
const litestreamProcess = state.litestreamProcess;
|
|
8856
|
+
const result = await timeShutdownPhase(
|
|
8857
|
+
state,
|
|
8858
|
+
durations,
|
|
8859
|
+
"litestream_stop",
|
|
8860
|
+
() => stopSessionDbReplication(litestreamProcess, CHILD_STOP_TIMEOUT_MS)
|
|
8861
|
+
);
|
|
8862
|
+
log2(state, `Stopped litestream replication (${result.outcome})`);
|
|
8863
|
+
state.litestreamProcess = null;
|
|
8864
|
+
}
|
|
8684
8865
|
return durations;
|
|
8685
8866
|
}
|
|
8686
8867
|
async function run(options) {
|
|
@@ -8714,6 +8895,7 @@ async function run(options) {
|
|
|
8714
8895
|
opencodeConnected: false,
|
|
8715
8896
|
opencodeVersion: null,
|
|
8716
8897
|
opencodeProcess: null,
|
|
8898
|
+
litestreamProcess: null,
|
|
8717
8899
|
connection: null,
|
|
8718
8900
|
channelDriver: null,
|
|
8719
8901
|
running: true,
|
|
@@ -8978,6 +9160,40 @@ async function run(options) {
|
|
|
8978
9160
|
ocSpinner?.fail(error2.message);
|
|
8979
9161
|
throw error2;
|
|
8980
9162
|
}
|
|
9163
|
+
if (options.litestreamConfig) {
|
|
9164
|
+
const litestreamProcess = startSessionDbReplication(options.litestreamConfig);
|
|
9165
|
+
state.litestreamProcess = litestreamProcess;
|
|
9166
|
+
let failureHandled = false;
|
|
9167
|
+
const failRunForReplication = (message) => {
|
|
9168
|
+
if (failureHandled || state.shuttingDown || !state.running) return;
|
|
9169
|
+
failureHandled = true;
|
|
9170
|
+
state.shuttingDown = true;
|
|
9171
|
+
logActivity(state, { type: "error", error: message });
|
|
9172
|
+
if (state.interactive) displayStatus(state);
|
|
9173
|
+
void (async () => {
|
|
9174
|
+
try {
|
|
9175
|
+
await cleanup(state);
|
|
9176
|
+
await shutdownTelemetry();
|
|
9177
|
+
} catch (error2) {
|
|
9178
|
+
console.error(
|
|
9179
|
+
`[run] replication failure cleanup failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
9180
|
+
);
|
|
9181
|
+
}
|
|
9182
|
+
process.exit(1);
|
|
9183
|
+
})();
|
|
9184
|
+
};
|
|
9185
|
+
litestreamProcess.on("exit", (code, signal) => {
|
|
9186
|
+
failRunForReplication(
|
|
9187
|
+
`Litestream replication stopped unexpectedly (code: ${code ?? "null"}, signal: ${signal ?? "none"})`
|
|
9188
|
+
);
|
|
9189
|
+
});
|
|
9190
|
+
litestreamProcess.on("error", (error2) => {
|
|
9191
|
+
failRunForReplication(
|
|
9192
|
+
`Litestream replication failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
9193
|
+
);
|
|
9194
|
+
});
|
|
9195
|
+
log2(state, `Started litestream replication with config ${options.litestreamConfig}`);
|
|
9196
|
+
}
|
|
8981
9197
|
const tunnelSpinner = interactive && !state.json ? ora3("Connecting tunnel...").start() : null;
|
|
8982
9198
|
const channelDriver = new ChannelDriver({
|
|
8983
9199
|
agentId: state.agentId,
|
|
@@ -9238,6 +9454,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
9238
9454
|
).option(
|
|
9239
9455
|
"--tunnel-ready-file <path>",
|
|
9240
9456
|
"Path to write once the tunnel is connected (opt-in; unused unless an operator/image sets it)"
|
|
9457
|
+
).option(
|
|
9458
|
+
"--litestream-config <path>",
|
|
9459
|
+
"Replicate the OpenCode session database with `litestream replicate` using this config file (written by the runner image). Omit to disable replication."
|
|
9241
9460
|
).action(
|
|
9242
9461
|
(options) => {
|
|
9243
9462
|
run({
|
|
@@ -9269,7 +9488,8 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
9269
9488
|
// Raw values — expansion/validation is single-sourced in run.ts's
|
|
9270
9489
|
// resolveFileSyncDirectories.
|
|
9271
9490
|
enableFileSyncTo: options.enableFileSyncTo,
|
|
9272
|
-
tunnelReadyFile: options.tunnelReadyFile
|
|
9491
|
+
tunnelReadyFile: options.tunnelReadyFile,
|
|
9492
|
+
litestreamConfig: options.litestreamConfig
|
|
9273
9493
|
});
|
|
9274
9494
|
}
|
|
9275
9495
|
);
|