@evident-ai/cli 3.4.1-dev.d8ffc5f → 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 +175 -34
- 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;
|
|
@@ -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";
|
|
@@ -7980,6 +8065,7 @@ var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_
|
|
|
7980
8065
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
7981
8066
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
7982
8067
|
var TELEMETRY_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
8068
|
+
var CHILD_STOP_TIMEOUT_MS = 1e4;
|
|
7983
8069
|
function resolveLogLevel(options) {
|
|
7984
8070
|
const accepted = Object.keys(LOG_LEVELS);
|
|
7985
8071
|
const validate = (value, source) => {
|
|
@@ -8767,15 +8853,31 @@ async function cleanup(state, opts = {}) {
|
|
|
8767
8853
|
}
|
|
8768
8854
|
if (state.opencodeProcess) {
|
|
8769
8855
|
const opencodeProcess = state.opencodeProcess;
|
|
8770
|
-
|
|
8856
|
+
const result = await timeShutdownPhase(
|
|
8857
|
+
state,
|
|
8858
|
+
durations,
|
|
8859
|
+
"opencode_stop",
|
|
8860
|
+
() => stopOpenCodeAndWait(opencodeProcess, CHILD_STOP_TIMEOUT_MS)
|
|
8861
|
+
);
|
|
8771
8862
|
if (state.interactive) {
|
|
8772
|
-
logActivity(state, { type: "info", message:
|
|
8863
|
+
logActivity(state, { type: "info", message: `Stopped OpenCode process (${result.outcome})` });
|
|
8773
8864
|
displayStatus(state);
|
|
8774
8865
|
} else {
|
|
8775
|
-
log2(state,
|
|
8866
|
+
log2(state, `Stopped OpenCode process (${result.outcome})`);
|
|
8776
8867
|
}
|
|
8777
8868
|
state.opencodeProcess = null;
|
|
8778
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
|
+
}
|
|
8779
8881
|
return durations;
|
|
8780
8882
|
}
|
|
8781
8883
|
async function run(options) {
|
|
@@ -8809,6 +8911,7 @@ async function run(options) {
|
|
|
8809
8911
|
opencodeConnected: false,
|
|
8810
8912
|
opencodeVersion: null,
|
|
8811
8913
|
opencodeProcess: null,
|
|
8914
|
+
litestreamProcess: null,
|
|
8812
8915
|
connection: null,
|
|
8813
8916
|
channelDriver: null,
|
|
8814
8917
|
running: true,
|
|
@@ -9073,6 +9176,40 @@ async function run(options) {
|
|
|
9073
9176
|
ocSpinner?.fail(error2.message);
|
|
9074
9177
|
throw error2;
|
|
9075
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
|
+
}
|
|
9076
9213
|
const tunnelSpinner = interactive && !state.json ? ora3("Connecting tunnel...").start() : null;
|
|
9077
9214
|
const channelDriver = new ChannelDriver({
|
|
9078
9215
|
agentId: state.agentId,
|
|
@@ -9333,6 +9470,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
9333
9470
|
).option(
|
|
9334
9471
|
"--tunnel-ready-file <path>",
|
|
9335
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."
|
|
9336
9476
|
).action(
|
|
9337
9477
|
(options) => {
|
|
9338
9478
|
run({
|
|
@@ -9364,7 +9504,8 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
9364
9504
|
// Raw values — expansion/validation is single-sourced in run.ts's
|
|
9365
9505
|
// resolveFileSyncDirectories.
|
|
9366
9506
|
enableFileSyncTo: options.enableFileSyncTo,
|
|
9367
|
-
tunnelReadyFile: options.tunnelReadyFile
|
|
9507
|
+
tunnelReadyFile: options.tunnelReadyFile,
|
|
9508
|
+
litestreamConfig: options.litestreamConfig
|
|
9368
9509
|
});
|
|
9369
9510
|
}
|
|
9370
9511
|
);
|