@evident-ai/cli 3.4.1-dev.c6e7cf0 → 3.4.1-dev.f505f12
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 +201 -39
- 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
|
@@ -1681,6 +1681,62 @@ function buildOpenCodeVersionWarning(version2) {
|
|
|
1681
1681
|
|
|
1682
1682
|
// src/lib/opencode/process.ts
|
|
1683
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
|
|
1684
1740
|
var OPENCODE_PORT_RANGE = [4096, 4097, 4098, 4099, 4100];
|
|
1685
1741
|
function getProcessCwd(pid) {
|
|
1686
1742
|
const platform = process.platform;
|
|
@@ -1852,23 +1908,20 @@ async function startOpenCode(port) {
|
|
|
1852
1908
|
});
|
|
1853
1909
|
return child;
|
|
1854
1910
|
}
|
|
1855
|
-
function
|
|
1856
|
-
|
|
1857
|
-
return;
|
|
1858
|
-
}
|
|
1859
|
-
try {
|
|
1911
|
+
function stopOpenCodeAndWait(opencodeProcess, timeoutMs) {
|
|
1912
|
+
const sendSignal = (signal) => {
|
|
1860
1913
|
if (process.platform === "win32") {
|
|
1861
|
-
opencodeProcess.kill(
|
|
1914
|
+
opencodeProcess.kill(signal);
|
|
1862
1915
|
} else {
|
|
1863
|
-
process.kill(-opencodeProcess.pid,
|
|
1916
|
+
process.kill(-opencodeProcess.pid, signal);
|
|
1864
1917
|
}
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1918
|
+
};
|
|
1919
|
+
return stopProcessAndWait(
|
|
1920
|
+
opencodeProcess,
|
|
1921
|
+
timeoutMs,
|
|
1922
|
+
() => sendSignal("SIGTERM"),
|
|
1923
|
+
() => sendSignal("SIGKILL")
|
|
1924
|
+
);
|
|
1872
1925
|
}
|
|
1873
1926
|
|
|
1874
1927
|
// src/lib/opencode/install.ts
|
|
@@ -2155,6 +2208,7 @@ async function createOpenCodeSession(port, directory) {
|
|
|
2155
2208
|
return data.id;
|
|
2156
2209
|
}
|
|
2157
2210
|
async function getModelAttachmentCapability(port, model) {
|
|
2211
|
+
const { model: baseModel } = splitModelVariant(model);
|
|
2158
2212
|
try {
|
|
2159
2213
|
const res = await timedFetch(`${opencodeBase(port)}/config/providers`);
|
|
2160
2214
|
if (!res.ok) {
|
|
@@ -2171,9 +2225,9 @@ async function getModelAttachmentCapability(port, model) {
|
|
|
2171
2225
|
);
|
|
2172
2226
|
return null;
|
|
2173
2227
|
}
|
|
2174
|
-
const slash =
|
|
2175
|
-
const providerId = slash > 0 ?
|
|
2176
|
-
let modelId = slash > 0 ?
|
|
2228
|
+
const slash = baseModel ? baseModel.indexOf("/") : -1;
|
|
2229
|
+
const providerId = slash > 0 ? baseModel.slice(0, slash) : void 0;
|
|
2230
|
+
let modelId = slash > 0 ? baseModel.slice(slash + 1) : void 0;
|
|
2177
2231
|
const defaults2 = body?.default && typeof body.default === "object" ? body.default : void 0;
|
|
2178
2232
|
let provider = providerId ? providers.find((p) => p?.id === providerId) : void 0;
|
|
2179
2233
|
if (!provider && !providerId) {
|
|
@@ -2253,6 +2307,29 @@ async function buildFileParts(attachments, capable) {
|
|
|
2253
2307
|
}
|
|
2254
2308
|
return { parts, outcomes, capabilityUnknown };
|
|
2255
2309
|
}
|
|
2310
|
+
function splitModelVariant(raw) {
|
|
2311
|
+
const value = raw?.trim();
|
|
2312
|
+
if (!value) return {};
|
|
2313
|
+
const hashIndex = value.indexOf("#");
|
|
2314
|
+
if (hashIndex === -1) return { model: value };
|
|
2315
|
+
const model = value.slice(0, hashIndex).trim() || void 0;
|
|
2316
|
+
const variant = value.slice(hashIndex + 1).trim() || void 0;
|
|
2317
|
+
return { model, variant };
|
|
2318
|
+
}
|
|
2319
|
+
function applyModelOptions(body, options) {
|
|
2320
|
+
if (options?.agent) body.agent = options.agent;
|
|
2321
|
+
const { model, variant } = splitModelVariant(options?.model);
|
|
2322
|
+
if (model) {
|
|
2323
|
+
const slashIndex = model.indexOf("/");
|
|
2324
|
+
if (slashIndex !== -1) {
|
|
2325
|
+
body.model = {
|
|
2326
|
+
providerID: model.substring(0, slashIndex),
|
|
2327
|
+
modelID: model.substring(slashIndex + 1)
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
if (variant) body.variant = variant;
|
|
2332
|
+
}
|
|
2256
2333
|
function messageText(m) {
|
|
2257
2334
|
if (!m || !Array.isArray(m.parts)) return "";
|
|
2258
2335
|
return m.parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
|
|
@@ -2277,18 +2354,7 @@ async function sendPromptAsync(port, sessionId, content, options, attachments) {
|
|
|
2277
2354
|
const body = {
|
|
2278
2355
|
parts
|
|
2279
2356
|
};
|
|
2280
|
-
|
|
2281
|
-
body.agent = options.agent;
|
|
2282
|
-
}
|
|
2283
|
-
if (options?.model) {
|
|
2284
|
-
const slashIndex = options.model.indexOf("/");
|
|
2285
|
-
if (slashIndex !== -1) {
|
|
2286
|
-
body.model = {
|
|
2287
|
-
providerID: options.model.substring(0, slashIndex),
|
|
2288
|
-
modelID: options.model.substring(slashIndex + 1)
|
|
2289
|
-
};
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2357
|
+
applyModelOptions(body, options);
|
|
2292
2358
|
const res = await timedFetch(`${opencodeBase(port)}/session/${sessionId}/prompt_async`, {
|
|
2293
2359
|
method: "POST",
|
|
2294
2360
|
headers: { "Content-Type": "application/json" },
|
|
@@ -2296,7 +2362,10 @@ async function sendPromptAsync(port, sessionId, content, options, attachments) {
|
|
|
2296
2362
|
});
|
|
2297
2363
|
if (res.status < 200 || res.status >= 300) {
|
|
2298
2364
|
const text = await res.text().catch(() => "");
|
|
2299
|
-
|
|
2365
|
+
const { variant } = splitModelVariant(options?.model);
|
|
2366
|
+
throw new Error(
|
|
2367
|
+
`OpenCode prompt_async failed: HTTP ${res.status}${text ? `: ${text}` : ""}${variant ? ` (variant: ${variant})` : ""}`
|
|
2368
|
+
);
|
|
2300
2369
|
}
|
|
2301
2370
|
const READ_BACK_ATTEMPTS = 5;
|
|
2302
2371
|
const READ_BACK_DELAY_MS = 150;
|
|
@@ -2451,7 +2520,7 @@ function isPreamblePinnedRunning(messages, userMessageId) {
|
|
|
2451
2520
|
return completedOf(reply) != null && finishOf(reply) === "tool-calls";
|
|
2452
2521
|
}
|
|
2453
2522
|
function isB2AbandonmentConfirmed(params) {
|
|
2454
|
-
return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false;
|
|
2523
|
+
return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false && params.rootOngoing === false;
|
|
2455
2524
|
}
|
|
2456
2525
|
function isAmbiguousTerminalFinish(m) {
|
|
2457
2526
|
if (completedOf(m) == null) return false;
|
|
@@ -2464,7 +2533,7 @@ function isAmbiguousFinishPinnedRunning(messages, userMessageId) {
|
|
|
2464
2533
|
return isAmbiguousTerminalFinish(reply);
|
|
2465
2534
|
}
|
|
2466
2535
|
function isAmbiguousFinishResolved(params) {
|
|
2467
|
-
return params.sessionOngoing === false || params.pinnedForMs >= params.maxPinnedMs;
|
|
2536
|
+
return params.sessionOngoing === false || params.sessionOngoing !== true && params.pinnedForMs >= params.maxPinnedMs;
|
|
2468
2537
|
}
|
|
2469
2538
|
function messageError(messages, userMessageId) {
|
|
2470
2539
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
@@ -3246,6 +3315,22 @@ function writeTunnelReadyMarker(path, agentId) {
|
|
|
3246
3315
|
}
|
|
3247
3316
|
}
|
|
3248
3317
|
|
|
3318
|
+
// src/lib/replication.ts
|
|
3319
|
+
import { spawn as spawn2 } from "child_process";
|
|
3320
|
+
function startSessionDbReplication(configPath) {
|
|
3321
|
+
return spawn2("litestream", ["replicate", "-config", configPath], {
|
|
3322
|
+
stdio: "inherit"
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
async function stopSessionDbReplication(child, timeoutMs) {
|
|
3326
|
+
return stopProcessAndWait(
|
|
3327
|
+
child,
|
|
3328
|
+
timeoutMs,
|
|
3329
|
+
() => child.kill("SIGTERM"),
|
|
3330
|
+
() => child.kill("SIGKILL")
|
|
3331
|
+
);
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3249
3334
|
// src/lib/openai-usage.ts
|
|
3250
3335
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3251
3336
|
import { homedir as homedir2 } from "os";
|
|
@@ -5740,6 +5825,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5740
5825
|
deliveryDeadlineAnchored: false,
|
|
5741
5826
|
b2PinnedSinceMs: 0,
|
|
5742
5827
|
b2LastDescendantCheckMs: 0,
|
|
5828
|
+
b2RootOngoingHeldLogged: false,
|
|
5743
5829
|
b2AbandonedSignalled: false,
|
|
5744
5830
|
ambiguousPinnedSinceMs: 0,
|
|
5745
5831
|
ambiguousResolved: false
|
|
@@ -5834,6 +5920,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5834
5920
|
deliveryDeadlineAnchored: false,
|
|
5835
5921
|
b2PinnedSinceMs: 0,
|
|
5836
5922
|
b2LastDescendantCheckMs: 0,
|
|
5923
|
+
b2RootOngoingHeldLogged: false,
|
|
5837
5924
|
b2AbandonedSignalled: false,
|
|
5838
5925
|
ambiguousPinnedSinceMs: 0,
|
|
5839
5926
|
ambiguousResolved: false
|
|
@@ -6187,6 +6274,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6187
6274
|
if (snapshotReadable) {
|
|
6188
6275
|
inFlight.b2PinnedSinceMs = 0;
|
|
6189
6276
|
inFlight.b2LastDescendantCheckMs = 0;
|
|
6277
|
+
inFlight.b2RootOngoingHeldLogged = false;
|
|
6190
6278
|
inFlight.b2AbandonedSignalled = false;
|
|
6191
6279
|
}
|
|
6192
6280
|
} else {
|
|
@@ -6198,11 +6286,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6198
6286
|
const pinnedForMs = this.now() - inFlight.b2PinnedSinceMs;
|
|
6199
6287
|
if (pinnedForMs >= B2_ABANDONMENT_MIN_PINNED_MS && this.now() - inFlight.b2LastDescendantCheckMs >= B2_ABANDONMENT_RECHECK_MS) {
|
|
6200
6288
|
inFlight.b2LastDescendantCheckMs = this.now();
|
|
6201
|
-
const descendantOngoing = await
|
|
6289
|
+
const [descendantOngoing, rootOngoing] = await Promise.all([
|
|
6290
|
+
this.isAnyDescendantSessionOngoing(sessionId),
|
|
6291
|
+
isSessionOngoing(this.port, sessionId)
|
|
6292
|
+
]);
|
|
6202
6293
|
if (isB2AbandonmentConfirmed({
|
|
6203
6294
|
pinnedForMs,
|
|
6204
6295
|
minPinnedMs: B2_ABANDONMENT_MIN_PINNED_MS,
|
|
6205
|
-
descendantOngoing
|
|
6296
|
+
descendantOngoing,
|
|
6297
|
+
rootOngoing
|
|
6206
6298
|
})) {
|
|
6207
6299
|
inFlight.b2AbandonedSignalled = true;
|
|
6208
6300
|
this.log({
|
|
@@ -6211,12 +6303,26 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
6211
6303
|
conversation_id: conv.id,
|
|
6212
6304
|
message_id: id
|
|
6213
6305
|
});
|
|
6306
|
+
const reply = findLastAssistantReplyFor(messages, inFlight.opencodeMessageId);
|
|
6214
6307
|
void this.postSignal(conv.id, id, "b2_abandoned_resolved", {
|
|
6215
|
-
watched_for_ms: pinnedForMs
|
|
6308
|
+
watched_for_ms: pinnedForMs,
|
|
6309
|
+
finish: reply?.info?.finish ?? reply?.finish,
|
|
6310
|
+
...rootOngoing != null ? { root_ongoing: rootOngoing } : {},
|
|
6311
|
+
...descendantOngoing != null ? { descendant_ongoing: descendantOngoing } : {},
|
|
6312
|
+
opencode_message_id: inFlight.opencodeMessageId
|
|
6216
6313
|
});
|
|
6217
6314
|
await this.settleMessageDone(sessionId, watcher, inFlight, messages);
|
|
6218
6315
|
return;
|
|
6219
6316
|
}
|
|
6317
|
+
if (rootOngoing === true && !inFlight.b2RootOngoingHeldLogged) {
|
|
6318
|
+
inFlight.b2RootOngoingHeldLogged = true;
|
|
6319
|
+
this.log({
|
|
6320
|
+
level: "warn",
|
|
6321
|
+
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`,
|
|
6322
|
+
conversation_id: conv.id,
|
|
6323
|
+
message_id: id
|
|
6324
|
+
});
|
|
6325
|
+
}
|
|
6220
6326
|
}
|
|
6221
6327
|
}
|
|
6222
6328
|
const ambiguousPinnedNow = activelyRunning && isAmbiguousFinishPinnedRunning(messages, inFlight.opencodeMessageId);
|
|
@@ -7959,6 +8065,7 @@ var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_
|
|
|
7959
8065
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
7960
8066
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
7961
8067
|
var TELEMETRY_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
8068
|
+
var CHILD_STOP_TIMEOUT_MS = 1e4;
|
|
7962
8069
|
function resolveLogLevel(options) {
|
|
7963
8070
|
const accepted = Object.keys(LOG_LEVELS);
|
|
7964
8071
|
const validate = (value, source) => {
|
|
@@ -8746,15 +8853,31 @@ async function cleanup(state, opts = {}) {
|
|
|
8746
8853
|
}
|
|
8747
8854
|
if (state.opencodeProcess) {
|
|
8748
8855
|
const opencodeProcess = state.opencodeProcess;
|
|
8749
|
-
|
|
8856
|
+
const result = await timeShutdownPhase(
|
|
8857
|
+
state,
|
|
8858
|
+
durations,
|
|
8859
|
+
"opencode_stop",
|
|
8860
|
+
() => stopOpenCodeAndWait(opencodeProcess, CHILD_STOP_TIMEOUT_MS)
|
|
8861
|
+
);
|
|
8750
8862
|
if (state.interactive) {
|
|
8751
|
-
logActivity(state, { type: "info", message:
|
|
8863
|
+
logActivity(state, { type: "info", message: `Stopped OpenCode process (${result.outcome})` });
|
|
8752
8864
|
displayStatus(state);
|
|
8753
8865
|
} else {
|
|
8754
|
-
log2(state,
|
|
8866
|
+
log2(state, `Stopped OpenCode process (${result.outcome})`);
|
|
8755
8867
|
}
|
|
8756
8868
|
state.opencodeProcess = null;
|
|
8757
8869
|
}
|
|
8870
|
+
if (state.litestreamProcess) {
|
|
8871
|
+
const litestreamProcess = state.litestreamProcess;
|
|
8872
|
+
const result = await timeShutdownPhase(
|
|
8873
|
+
state,
|
|
8874
|
+
durations,
|
|
8875
|
+
"litestream_stop",
|
|
8876
|
+
() => stopSessionDbReplication(litestreamProcess, CHILD_STOP_TIMEOUT_MS)
|
|
8877
|
+
);
|
|
8878
|
+
log2(state, `Stopped litestream replication (${result.outcome})`);
|
|
8879
|
+
state.litestreamProcess = null;
|
|
8880
|
+
}
|
|
8758
8881
|
return durations;
|
|
8759
8882
|
}
|
|
8760
8883
|
async function run(options) {
|
|
@@ -8788,6 +8911,7 @@ async function run(options) {
|
|
|
8788
8911
|
opencodeConnected: false,
|
|
8789
8912
|
opencodeVersion: null,
|
|
8790
8913
|
opencodeProcess: null,
|
|
8914
|
+
litestreamProcess: null,
|
|
8791
8915
|
connection: null,
|
|
8792
8916
|
channelDriver: null,
|
|
8793
8917
|
running: true,
|
|
@@ -9052,6 +9176,40 @@ async function run(options) {
|
|
|
9052
9176
|
ocSpinner?.fail(error2.message);
|
|
9053
9177
|
throw error2;
|
|
9054
9178
|
}
|
|
9179
|
+
if (options.litestreamConfig) {
|
|
9180
|
+
const litestreamProcess = startSessionDbReplication(options.litestreamConfig);
|
|
9181
|
+
state.litestreamProcess = litestreamProcess;
|
|
9182
|
+
let failureHandled = false;
|
|
9183
|
+
const failRunForReplication = (message) => {
|
|
9184
|
+
if (failureHandled || state.shuttingDown || !state.running) return;
|
|
9185
|
+
failureHandled = true;
|
|
9186
|
+
state.shuttingDown = true;
|
|
9187
|
+
logActivity(state, { type: "error", error: message });
|
|
9188
|
+
if (state.interactive) displayStatus(state);
|
|
9189
|
+
void (async () => {
|
|
9190
|
+
try {
|
|
9191
|
+
await cleanup(state);
|
|
9192
|
+
await shutdownTelemetry();
|
|
9193
|
+
} catch (error2) {
|
|
9194
|
+
console.error(
|
|
9195
|
+
`[run] replication failure cleanup failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
9196
|
+
);
|
|
9197
|
+
}
|
|
9198
|
+
process.exit(1);
|
|
9199
|
+
})();
|
|
9200
|
+
};
|
|
9201
|
+
litestreamProcess.on("exit", (code, signal) => {
|
|
9202
|
+
failRunForReplication(
|
|
9203
|
+
`Litestream replication stopped unexpectedly (code: ${code ?? "null"}, signal: ${signal ?? "none"})`
|
|
9204
|
+
);
|
|
9205
|
+
});
|
|
9206
|
+
litestreamProcess.on("error", (error2) => {
|
|
9207
|
+
failRunForReplication(
|
|
9208
|
+
`Litestream replication failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
9209
|
+
);
|
|
9210
|
+
});
|
|
9211
|
+
log2(state, `Started litestream replication with config ${options.litestreamConfig}`);
|
|
9212
|
+
}
|
|
9055
9213
|
const tunnelSpinner = interactive && !state.json ? ora3("Connecting tunnel...").start() : null;
|
|
9056
9214
|
const channelDriver = new ChannelDriver({
|
|
9057
9215
|
agentId: state.agentId,
|
|
@@ -9312,6 +9470,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
9312
9470
|
).option(
|
|
9313
9471
|
"--tunnel-ready-file <path>",
|
|
9314
9472
|
"Path to write once the tunnel is connected (opt-in; unused unless an operator/image sets it)"
|
|
9473
|
+
).option(
|
|
9474
|
+
"--litestream-config <path>",
|
|
9475
|
+
"Replicate the OpenCode session database with `litestream replicate` using this config file (written by the runner image). Omit to disable replication."
|
|
9315
9476
|
).action(
|
|
9316
9477
|
(options) => {
|
|
9317
9478
|
run({
|
|
@@ -9343,7 +9504,8 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
9343
9504
|
// Raw values — expansion/validation is single-sourced in run.ts's
|
|
9344
9505
|
// resolveFileSyncDirectories.
|
|
9345
9506
|
enableFileSyncTo: options.enableFileSyncTo,
|
|
9346
|
-
tunnelReadyFile: options.tunnelReadyFile
|
|
9507
|
+
tunnelReadyFile: options.tunnelReadyFile,
|
|
9508
|
+
litestreamConfig: options.litestreamConfig
|
|
9347
9509
|
});
|
|
9348
9510
|
}
|
|
9349
9511
|
);
|