@evident-ai/cli 3.4.1-dev.c03b461 → 3.4.1-dev.cb56b3f
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 +3 -0
- package/dist/index.js +317 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -119,6 +119,9 @@ Options:
|
|
|
119
119
|
- `--opencode-config-overlay <path>` — Apply a runner-provided OpenCode config
|
|
120
120
|
before starting OpenCode. Relative paths are resolved from the working directory;
|
|
121
121
|
an existing `opencode.jsonc` is replaced before `opencode.json`.
|
|
122
|
+
- `--credential-sync-marker <path>` — Own the interval credential sync and write
|
|
123
|
+
this marker once the shutdown flush has finished, so the runner image's lifecycle
|
|
124
|
+
hooks can wait on it.
|
|
122
125
|
- `--claude-usage-reporting <mode>` — Whether to report the local Claude Code
|
|
123
126
|
subscription's rate-limit usage to Evident, so it shows on the runner page:
|
|
124
127
|
`auto` (default) reports it when a usable Claude Code login is found on this
|
package/dist/index.js
CHANGED
|
@@ -1183,7 +1183,7 @@ async function claudeUsage() {
|
|
|
1183
1183
|
}
|
|
1184
1184
|
|
|
1185
1185
|
// src/commands/run.ts
|
|
1186
|
-
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as
|
|
1186
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
1187
1187
|
import { homedir as homedir5 } from "os";
|
|
1188
1188
|
import { isAbsolute as isAbsolute3, join as join9, parse, resolve as resolvePath2 } from "path";
|
|
1189
1189
|
import chalk6 from "chalk";
|
|
@@ -1728,10 +1728,14 @@ function runSynchroniser(args, opts) {
|
|
|
1728
1728
|
let stderr = "";
|
|
1729
1729
|
let settled = false;
|
|
1730
1730
|
const timer = {};
|
|
1731
|
+
let abortListener;
|
|
1732
|
+
let spawnListener;
|
|
1731
1733
|
const finish = (result) => {
|
|
1732
1734
|
if (settled) return;
|
|
1733
1735
|
settled = true;
|
|
1734
1736
|
if (timer.handle) clearTimeout(timer.handle);
|
|
1737
|
+
if (abortListener) opts.signal?.removeEventListener("abort", abortListener);
|
|
1738
|
+
if (spawnListener) child.removeListener("spawn", spawnListener);
|
|
1735
1739
|
resolve4(result);
|
|
1736
1740
|
};
|
|
1737
1741
|
try {
|
|
@@ -1757,6 +1761,25 @@ function runSynchroniser(args, opts) {
|
|
|
1757
1761
|
child.once("close", (code) => {
|
|
1758
1762
|
finish({ code, stdout, stderr, timedOut: false });
|
|
1759
1763
|
});
|
|
1764
|
+
if (opts.signal) {
|
|
1765
|
+
const killChild = () => {
|
|
1766
|
+
if (child.pid === void 0) {
|
|
1767
|
+
if (!spawnListener) {
|
|
1768
|
+
spawnListener = killChild;
|
|
1769
|
+
child.once("spawn", spawnListener);
|
|
1770
|
+
}
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
child.kill("SIGKILL");
|
|
1774
|
+
};
|
|
1775
|
+
abortListener = killChild;
|
|
1776
|
+
if (opts.signal.aborted) {
|
|
1777
|
+
abortListener();
|
|
1778
|
+
} else {
|
|
1779
|
+
opts.signal.addEventListener("abort", abortListener, { once: true });
|
|
1780
|
+
if (opts.signal.aborted) abortListener();
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1760
1783
|
timer.handle = setTimeout(
|
|
1761
1784
|
() => {
|
|
1762
1785
|
child.kill("SIGKILL");
|
|
@@ -3844,8 +3867,8 @@ function connectTunnel(options) {
|
|
|
3844
3867
|
try {
|
|
3845
3868
|
message = JSON.parse(data.toString());
|
|
3846
3869
|
} catch (error2) {
|
|
3847
|
-
const
|
|
3848
|
-
onError?.(`Failed to handle message: ${
|
|
3870
|
+
const errorMessage2 = error2 instanceof Error ? error2.message : "Unknown error";
|
|
3871
|
+
onError?.(`Failed to handle message: ${errorMessage2}`);
|
|
3849
3872
|
return;
|
|
3850
3873
|
}
|
|
3851
3874
|
if (isStreamFrame(message)) {
|
|
@@ -5619,7 +5642,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5619
5642
|
this.signalDispatchNotStarted(conv, message, "session_existence_unknown");
|
|
5620
5643
|
break;
|
|
5621
5644
|
}
|
|
5622
|
-
const
|
|
5645
|
+
const errorMessage2 = err instanceof Error ? err.message : String(err);
|
|
5623
5646
|
this.sessions.delete(conv.id);
|
|
5624
5647
|
this.supersede(conv.id, sessionId);
|
|
5625
5648
|
this.log({
|
|
@@ -5628,7 +5651,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5628
5651
|
conversation_id: conv.id,
|
|
5629
5652
|
message_id: message.id
|
|
5630
5653
|
});
|
|
5631
|
-
await this.markFailed(conv.id, message.id, null,
|
|
5654
|
+
await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
|
|
5632
5655
|
this.log({
|
|
5633
5656
|
level: "warn",
|
|
5634
5657
|
message: `markFailed PATCH for message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
@@ -5639,7 +5662,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5639
5662
|
});
|
|
5640
5663
|
this.log({
|
|
5641
5664
|
level: "error",
|
|
5642
|
-
message: `Message ${message.id.slice(0, 8)} dispatch failed: ${
|
|
5665
|
+
message: `Message ${message.id.slice(0, 8)} dispatch failed: ${errorMessage2}`,
|
|
5643
5666
|
conversation_id: conv.id,
|
|
5644
5667
|
message_id: message.id
|
|
5645
5668
|
});
|
|
@@ -5660,14 +5683,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
5660
5683
|
this.unconfirmedDispatchFailures.delete(message.id);
|
|
5661
5684
|
this.sessions.delete(conv.id);
|
|
5662
5685
|
this.supersede(conv.id, sessionId);
|
|
5663
|
-
const
|
|
5686
|
+
const errorMessage2 = `OpenCode accepted this message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
|
|
5664
5687
|
this.log({
|
|
5665
5688
|
level: "error",
|
|
5666
|
-
message:
|
|
5689
|
+
message: errorMessage2,
|
|
5667
5690
|
conversation_id: conv.id,
|
|
5668
5691
|
message_id: message.id
|
|
5669
5692
|
});
|
|
5670
|
-
await this.markFailed(conv.id, message.id, null,
|
|
5693
|
+
await this.markFailed(conv.id, message.id, null, errorMessage2).catch((markErr) => {
|
|
5671
5694
|
this.log({
|
|
5672
5695
|
level: "warn",
|
|
5673
5696
|
message: `markFailed PATCH for message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
@@ -7631,14 +7654,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7631
7654
|
this.unconfirmedDispatchFailures.delete(row.id);
|
|
7632
7655
|
this.sessions.delete(readoptConv.id);
|
|
7633
7656
|
this.supersede(readoptConv.id, sessionId);
|
|
7634
|
-
const
|
|
7657
|
+
const errorMessage2 = `OpenCode accepted this re-dispatched message ${streak} times in a row but never confirmed its assigned id (session ${sessionId.slice(0, 8)}'s message list could not be read back) \u2014 the session was abandoned; a fresh one is used for further messages.`;
|
|
7635
7658
|
this.log({
|
|
7636
7659
|
level: "error",
|
|
7637
|
-
message:
|
|
7660
|
+
message: errorMessage2,
|
|
7638
7661
|
conversation_id: row.conversation_id,
|
|
7639
7662
|
message_id: row.id
|
|
7640
7663
|
});
|
|
7641
|
-
await this.markFailed(row.conversation_id, row.id, null,
|
|
7664
|
+
await this.markFailed(row.conversation_id, row.id, null, errorMessage2).catch((markErr) => {
|
|
7642
7665
|
this.log({
|
|
7643
7666
|
level: "warn",
|
|
7644
7667
|
message: `markFailed PATCH for message ${row.id.slice(0, 8)} (conversation ${row.conversation_id.slice(0, 8)}) failed (best-effort, not retried): ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
@@ -9079,6 +9102,241 @@ function applyRunnerOpenCodeConfig({
|
|
|
9079
9102
|
log3(`Applied runner OpenCode config ${source} to ${join8(cwd, target)}`);
|
|
9080
9103
|
}
|
|
9081
9104
|
|
|
9105
|
+
// src/lib/credential-sync.ts
|
|
9106
|
+
import { renameSync, writeFileSync as writeFileSync5 } from "fs";
|
|
9107
|
+
var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
|
|
9108
|
+
var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
|
|
9109
|
+
var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
|
|
9110
|
+
var DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS = 60;
|
|
9111
|
+
var STORES = ["claude", "opencode"];
|
|
9112
|
+
var MAX_FLUSH_PASSES = 2;
|
|
9113
|
+
function outcomesWith(outcome) {
|
|
9114
|
+
return { claude: outcome, opencode: outcome };
|
|
9115
|
+
}
|
|
9116
|
+
function errorMessage(error2) {
|
|
9117
|
+
return error2 instanceof Error ? error2.message : String(error2);
|
|
9118
|
+
}
|
|
9119
|
+
function waitForSettlement(promise, timeoutMs) {
|
|
9120
|
+
return new Promise((resolve4) => {
|
|
9121
|
+
let settled = false;
|
|
9122
|
+
const timer = setTimeout(() => finish(false), Math.max(0, timeoutMs));
|
|
9123
|
+
const finish = (value) => {
|
|
9124
|
+
if (settled) return;
|
|
9125
|
+
settled = true;
|
|
9126
|
+
clearTimeout(timer);
|
|
9127
|
+
resolve4(value);
|
|
9128
|
+
};
|
|
9129
|
+
promise.then(
|
|
9130
|
+
() => finish(true),
|
|
9131
|
+
() => finish(true)
|
|
9132
|
+
);
|
|
9133
|
+
});
|
|
9134
|
+
}
|
|
9135
|
+
function writeMarker(markerPath, outcomes, log3) {
|
|
9136
|
+
const body = `${STORES.map((store) => `${store}=${outcomes[store]}`).join("\n")}
|
|
9137
|
+
`;
|
|
9138
|
+
const temporaryPath = `${markerPath}.tmp`;
|
|
9139
|
+
try {
|
|
9140
|
+
writeFileSync5(temporaryPath, body, { mode: 384 });
|
|
9141
|
+
renameSync(temporaryPath, markerPath);
|
|
9142
|
+
} catch (error2) {
|
|
9143
|
+
log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage(error2)}`, "warn");
|
|
9144
|
+
}
|
|
9145
|
+
}
|
|
9146
|
+
function intervalSeconds(env, log3) {
|
|
9147
|
+
const raw = env.CREDS_SYNC_INTERVAL;
|
|
9148
|
+
if (raw === void 0 || /^[1-9][0-9]*$/.test(raw) && Number.isSafeInteger(Number(raw))) {
|
|
9149
|
+
return raw === void 0 ? DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS : Number(raw);
|
|
9150
|
+
}
|
|
9151
|
+
log3(
|
|
9152
|
+
`CREDENTIAL-SYNC-INTERVAL-INVALID: CREDS_SYNC_INTERVAL='${raw}' is not a positive integer; using ${DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS}s`,
|
|
9153
|
+
"warn"
|
|
9154
|
+
);
|
|
9155
|
+
return DEFAULT_CREDENTIAL_SYNC_INTERVAL_SECONDS;
|
|
9156
|
+
}
|
|
9157
|
+
async function runFlushStore(store, env, synchroniserRunner, log3, deadlineAt) {
|
|
9158
|
+
const remainingMs = deadlineAt - Date.now();
|
|
9159
|
+
if (remainingMs <= 0) return { outcome: "timeout", orphaned: false };
|
|
9160
|
+
const controller = new AbortController();
|
|
9161
|
+
let result;
|
|
9162
|
+
let failed = false;
|
|
9163
|
+
const completion = Promise.resolve().then(
|
|
9164
|
+
() => synchroniserRunner(["sync-once", store], {
|
|
9165
|
+
timeoutMs: remainingMs,
|
|
9166
|
+
env,
|
|
9167
|
+
signal: controller.signal
|
|
9168
|
+
})
|
|
9169
|
+
).then(
|
|
9170
|
+
(value) => {
|
|
9171
|
+
result = value;
|
|
9172
|
+
},
|
|
9173
|
+
(error2) => {
|
|
9174
|
+
failed = true;
|
|
9175
|
+
log3(`CREDENTIAL-FLUSH-ERROR: ${store}: ${errorMessage(error2)}`, "warn");
|
|
9176
|
+
}
|
|
9177
|
+
);
|
|
9178
|
+
const abortTimer = setTimeout(() => controller.abort(), remainingMs);
|
|
9179
|
+
const settledBeforeDeadline = await waitForSettlement(completion, remainingMs);
|
|
9180
|
+
clearTimeout(abortTimer);
|
|
9181
|
+
if (!settledBeforeDeadline) {
|
|
9182
|
+
controller.abort();
|
|
9183
|
+
const settledAfterAbort = await waitForSettlement(completion, CREDENTIAL_FLUSH_ABORT_GRACE_MS);
|
|
9184
|
+
if (!settledAfterAbort) return { outcome: "timeout", orphaned: true };
|
|
9185
|
+
return { outcome: "timeout", orphaned: false };
|
|
9186
|
+
}
|
|
9187
|
+
if (failed || !result) return { outcome: "failed", orphaned: false };
|
|
9188
|
+
if (result.timedOut || Date.now() >= deadlineAt) {
|
|
9189
|
+
return { outcome: "timeout", orphaned: false };
|
|
9190
|
+
}
|
|
9191
|
+
return { outcome: result.code === 0 ? "ok" : "failed", orphaned: false };
|
|
9192
|
+
}
|
|
9193
|
+
function createCredentialSync({
|
|
9194
|
+
markerPath,
|
|
9195
|
+
env,
|
|
9196
|
+
log: log3,
|
|
9197
|
+
synchroniserRunner = runSynchroniser
|
|
9198
|
+
}) {
|
|
9199
|
+
const persistenceDisabled = !env.PERSISTENCE_BUCKET;
|
|
9200
|
+
let disabled = persistenceDisabled;
|
|
9201
|
+
let armed = false;
|
|
9202
|
+
let stopped = false;
|
|
9203
|
+
let timer;
|
|
9204
|
+
let inFlight;
|
|
9205
|
+
let activeTickAbort;
|
|
9206
|
+
let lastTickFailed;
|
|
9207
|
+
let flushPromise;
|
|
9208
|
+
const scheduleTick = (intervalMs, startTick2) => {
|
|
9209
|
+
if (stopped) return;
|
|
9210
|
+
timer = setTimeout(() => {
|
|
9211
|
+
timer = void 0;
|
|
9212
|
+
startTick2();
|
|
9213
|
+
}, intervalMs);
|
|
9214
|
+
};
|
|
9215
|
+
const startTick = (intervalMs) => {
|
|
9216
|
+
if (stopped) return;
|
|
9217
|
+
const controller = new AbortController();
|
|
9218
|
+
activeTickAbort = controller;
|
|
9219
|
+
const tick = (async () => {
|
|
9220
|
+
const outcomes = {
|
|
9221
|
+
claude: "failed",
|
|
9222
|
+
opencode: "failed"
|
|
9223
|
+
};
|
|
9224
|
+
for (const store of STORES) {
|
|
9225
|
+
if (controller.signal.aborted) break;
|
|
9226
|
+
try {
|
|
9227
|
+
const result = await synchroniserRunner(["sync-once", store], {
|
|
9228
|
+
timeoutMs: CREDENTIAL_SYNC_TICK_TIMEOUT_MS,
|
|
9229
|
+
env,
|
|
9230
|
+
signal: controller.signal
|
|
9231
|
+
});
|
|
9232
|
+
outcomes[store] = !result.timedOut && result.code === 0 ? "ok" : "failed";
|
|
9233
|
+
} catch (error2) {
|
|
9234
|
+
outcomes[store] = "failed";
|
|
9235
|
+
log3(`CREDENTIAL-SYNC-TICK-ERROR: ${store}: ${errorMessage(error2)}`, "debug");
|
|
9236
|
+
}
|
|
9237
|
+
}
|
|
9238
|
+
const failed = STORES.some((store) => outcomes[store] === "failed");
|
|
9239
|
+
log3(
|
|
9240
|
+
`CREDENTIAL-SYNC-TICK: claude=${outcomes.claude}, opencode=${outcomes.opencode}`,
|
|
9241
|
+
"debug"
|
|
9242
|
+
);
|
|
9243
|
+
if (failed && lastTickFailed !== true) {
|
|
9244
|
+
log3(
|
|
9245
|
+
"CREDENTIAL-SYNC-FAILED: an interval credential sync failed; retrying on the next tick",
|
|
9246
|
+
"warn"
|
|
9247
|
+
);
|
|
9248
|
+
} else if (!failed && lastTickFailed === true) {
|
|
9249
|
+
log3("CREDENTIAL-SYNC-RECOVERED: interval credential sync succeeded again", "warn");
|
|
9250
|
+
}
|
|
9251
|
+
lastTickFailed = failed;
|
|
9252
|
+
})().finally(() => {
|
|
9253
|
+
if (activeTickAbort === controller) activeTickAbort = void 0;
|
|
9254
|
+
if (inFlight === tick) inFlight = void 0;
|
|
9255
|
+
scheduleTick(intervalMs, () => startTick(intervalMs));
|
|
9256
|
+
});
|
|
9257
|
+
inFlight = tick;
|
|
9258
|
+
};
|
|
9259
|
+
const performFlush = async () => {
|
|
9260
|
+
stopped = true;
|
|
9261
|
+
if (timer) {
|
|
9262
|
+
clearTimeout(timer);
|
|
9263
|
+
timer = void 0;
|
|
9264
|
+
}
|
|
9265
|
+
const deadlineAt = Date.now() + CREDENTIAL_FLUSH_DEADLINE_MS;
|
|
9266
|
+
if (inFlight) {
|
|
9267
|
+
const settled = await waitForSettlement(inFlight, CREDENTIAL_FLUSH_DEADLINE_MS);
|
|
9268
|
+
if (!settled) {
|
|
9269
|
+
activeTickAbort?.abort();
|
|
9270
|
+
const settledAfterAbort = await waitForSettlement(
|
|
9271
|
+
inFlight,
|
|
9272
|
+
CREDENTIAL_FLUSH_ABORT_GRACE_MS
|
|
9273
|
+
);
|
|
9274
|
+
if (!settledAfterAbort) {
|
|
9275
|
+
log3(
|
|
9276
|
+
"CREDENTIAL-FLUSH-ORPHANED-TICK: a sync-once child could not be proven settled; skipping the boundary flush rather than becoming a second writer",
|
|
9277
|
+
"warn"
|
|
9278
|
+
);
|
|
9279
|
+
return { outcomes: outcomesWith("timeout"), orphaned: true };
|
|
9280
|
+
}
|
|
9281
|
+
}
|
|
9282
|
+
}
|
|
9283
|
+
if (disabled) return { outcomes: outcomesWith("skipped"), orphaned: false };
|
|
9284
|
+
const outcomes = outcomesWith("timeout");
|
|
9285
|
+
for (const store of STORES) {
|
|
9286
|
+
const result = await runFlushStore(store, env, synchroniserRunner, log3, deadlineAt);
|
|
9287
|
+
if (result.orphaned) {
|
|
9288
|
+
log3(
|
|
9289
|
+
"CREDENTIAL-FLUSH-ORPHANED-CHILD: a flush sync-once child could not be proven settled; refusing another flush pass rather than becoming a second writer",
|
|
9290
|
+
"warn"
|
|
9291
|
+
);
|
|
9292
|
+
return { outcomes: outcomesWith("timeout"), orphaned: true };
|
|
9293
|
+
}
|
|
9294
|
+
outcomes[store] = result.outcome;
|
|
9295
|
+
}
|
|
9296
|
+
return { outcomes, orphaned: false };
|
|
9297
|
+
};
|
|
9298
|
+
let flushPasses = 0;
|
|
9299
|
+
let lastFlush;
|
|
9300
|
+
return {
|
|
9301
|
+
arm() {
|
|
9302
|
+
if (stopped || armed) return;
|
|
9303
|
+
armed = true;
|
|
9304
|
+
if (persistenceDisabled) {
|
|
9305
|
+
disabled = true;
|
|
9306
|
+
log3(
|
|
9307
|
+
"CREDENTIAL-SYNC-DISABLED: LITESTREAM_BUCKET/LITESTREAM_PREFIX are not both set; no interval credential sync this boot",
|
|
9308
|
+
"warn"
|
|
9309
|
+
);
|
|
9310
|
+
return;
|
|
9311
|
+
}
|
|
9312
|
+
disabled = false;
|
|
9313
|
+
const intervalMs = intervalSeconds(env, log3) * 1e3;
|
|
9314
|
+
scheduleTick(intervalMs, () => startTick(intervalMs));
|
|
9315
|
+
},
|
|
9316
|
+
async stopAndFlush(publish) {
|
|
9317
|
+
let result;
|
|
9318
|
+
const runningFlush = flushPromise;
|
|
9319
|
+
if (runningFlush) {
|
|
9320
|
+
result = await runningFlush;
|
|
9321
|
+
} else if (flushPasses >= MAX_FLUSH_PASSES || lastFlush?.orphaned) {
|
|
9322
|
+
result = lastFlush ?? { outcomes: outcomesWith("timeout"), orphaned: true };
|
|
9323
|
+
} else {
|
|
9324
|
+
flushPasses++;
|
|
9325
|
+
const currentFlush = performFlush();
|
|
9326
|
+
flushPromise = currentFlush;
|
|
9327
|
+
try {
|
|
9328
|
+
result = await currentFlush;
|
|
9329
|
+
lastFlush = result;
|
|
9330
|
+
} finally {
|
|
9331
|
+
if (flushPromise === currentFlush) flushPromise = void 0;
|
|
9332
|
+
}
|
|
9333
|
+
}
|
|
9334
|
+
if (publish) writeMarker(markerPath, result.outcomes, log3);
|
|
9335
|
+
return result.outcomes;
|
|
9336
|
+
}
|
|
9337
|
+
};
|
|
9338
|
+
}
|
|
9339
|
+
|
|
9082
9340
|
// src/commands/run.ts
|
|
9083
9341
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
9084
9342
|
var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
|
|
@@ -9418,8 +9676,8 @@ async function driveChannels(state, driver) {
|
|
|
9418
9676
|
state.running = false;
|
|
9419
9677
|
break;
|
|
9420
9678
|
}
|
|
9421
|
-
const
|
|
9422
|
-
logActivity(state, { type: "error", error: `Channel processing error: ${
|
|
9679
|
+
const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
|
|
9680
|
+
logActivity(state, { type: "error", error: `Channel processing error: ${errorMessage2}` });
|
|
9423
9681
|
if (state.interactive) displayStatus(state);
|
|
9424
9682
|
if (driver.hasInFlightWatchers()) {
|
|
9425
9683
|
consecutiveDrainFailures = 0;
|
|
@@ -9889,21 +10147,39 @@ async function cleanup(state, opts = {}) {
|
|
|
9889
10147
|
clearTimeout(state.resourceUsageTimer);
|
|
9890
10148
|
state.resourceUsageTimer = null;
|
|
9891
10149
|
}
|
|
10150
|
+
const credentialSync = state.credentialSync;
|
|
10151
|
+
const flushCredentials = credentialSync ? async (phase, publish) => {
|
|
10152
|
+
await timeShutdownPhase(state, durations, phase, async () => {
|
|
10153
|
+
const outcomes = await credentialSync.stopAndFlush(publish);
|
|
10154
|
+
const level = Object.values(outcomes).every((outcome) => outcome === "ok") ? "info" : "warn";
|
|
10155
|
+
log2(
|
|
10156
|
+
state,
|
|
10157
|
+
`Credential flush: claude=${outcomes.claude}, opencode=${outcomes.opencode}`,
|
|
10158
|
+
level
|
|
10159
|
+
);
|
|
10160
|
+
});
|
|
10161
|
+
} : void 0;
|
|
10162
|
+
let drainSettled = true;
|
|
9892
10163
|
if (opts.graceful && state.channelDriver) {
|
|
9893
10164
|
state.channelDriver.stop();
|
|
10165
|
+
}
|
|
10166
|
+
if (flushCredentials) {
|
|
10167
|
+
await flushCredentials("credential_flush", !(opts.graceful && state.channelDriver));
|
|
10168
|
+
}
|
|
10169
|
+
if (opts.graceful && state.channelDriver) {
|
|
9894
10170
|
log2(state, "Draining in-flight channel work before shutdown...");
|
|
9895
10171
|
if (state.interactive) {
|
|
9896
10172
|
logActivity(state, { type: "info", message: "Draining in-flight work before shutdown..." });
|
|
9897
10173
|
displayStatus(state);
|
|
9898
10174
|
}
|
|
9899
10175
|
const driver = state.channelDriver;
|
|
9900
|
-
|
|
10176
|
+
drainSettled = await timeShutdownPhase(
|
|
9901
10177
|
state,
|
|
9902
10178
|
durations,
|
|
9903
10179
|
"drain",
|
|
9904
10180
|
() => driver.waitForInFlight(SHUTDOWN_DRAIN_TIMEOUT_MS)
|
|
9905
10181
|
);
|
|
9906
|
-
if (!
|
|
10182
|
+
if (!drainSettled) {
|
|
9907
10183
|
logActivity(state, {
|
|
9908
10184
|
type: "info",
|
|
9909
10185
|
message: "Shutdown drain timed out with work still in flight \u2014 leaving it for restart recovery"
|
|
@@ -9911,6 +10187,9 @@ async function cleanup(state, opts = {}) {
|
|
|
9911
10187
|
if (state.interactive) displayStatus(state);
|
|
9912
10188
|
}
|
|
9913
10189
|
}
|
|
10190
|
+
if (opts.graceful && state.channelDriver && flushCredentials && drainSettled) {
|
|
10191
|
+
await flushCredentials("credential_flush_final", true);
|
|
10192
|
+
}
|
|
9914
10193
|
await timeShutdownPhase(state, durations, "offline_notify", () => notifyOffline(state));
|
|
9915
10194
|
if (state.connection) {
|
|
9916
10195
|
const connection = state.connection;
|
|
@@ -9997,9 +10276,23 @@ async function run(options) {
|
|
|
9997
10276
|
openaiUsageTimer: null,
|
|
9998
10277
|
openaiUsageRearm: null,
|
|
9999
10278
|
resourceUsageTimer: null,
|
|
10279
|
+
credentialSync: null,
|
|
10000
10280
|
authHeader: ""
|
|
10001
10281
|
};
|
|
10002
10282
|
setTelemetryAuthProvider(() => ({ authHeader: state.authHeader, agentId: state.agentId }));
|
|
10283
|
+
if (options.credentialSyncMarker) {
|
|
10284
|
+
state.credentialSync = createCredentialSync({
|
|
10285
|
+
markerPath: options.credentialSyncMarker,
|
|
10286
|
+
env: process.env,
|
|
10287
|
+
log: (message, level = "info") => {
|
|
10288
|
+
if (level === "error") {
|
|
10289
|
+
logActivity(state, { type: "error", error: message });
|
|
10290
|
+
} else {
|
|
10291
|
+
logActivity(state, { type: "info", level, message });
|
|
10292
|
+
}
|
|
10293
|
+
}
|
|
10294
|
+
});
|
|
10295
|
+
}
|
|
10003
10296
|
if (fileSyncDirectories.length > 0) {
|
|
10004
10297
|
log2(state, `File sync enabled for: ${fileSyncDirectories.join(", ")}`);
|
|
10005
10298
|
} else {
|
|
@@ -10207,6 +10500,7 @@ async function run(options) {
|
|
|
10207
10500
|
await restoreCredentialStores(credentialContext);
|
|
10208
10501
|
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
10209
10502
|
}
|
|
10503
|
+
state.credentialSync?.arm();
|
|
10210
10504
|
let sessionDbVerifyFatal = false;
|
|
10211
10505
|
if (!options.restoreSessionDb) {
|
|
10212
10506
|
log2(state, "Skipping session-DB restore: --restore-session-db was not passed", "debug");
|
|
@@ -10276,7 +10570,7 @@ async function run(options) {
|
|
|
10276
10570
|
state.opencodeVersion = oc.version;
|
|
10277
10571
|
if (options.opencodePidFile && oc.process?.pid !== void 0) {
|
|
10278
10572
|
try {
|
|
10279
|
-
|
|
10573
|
+
writeFileSync6(options.opencodePidFile, `${oc.process.pid}
|
|
10280
10574
|
`, { mode: 384 });
|
|
10281
10575
|
chmodSync3(options.opencodePidFile, 384);
|
|
10282
10576
|
} catch (error2) {
|
|
@@ -10392,7 +10686,7 @@ async function run(options) {
|
|
|
10392
10686
|
});
|
|
10393
10687
|
try {
|
|
10394
10688
|
if (litestreamProcess.pid !== void 0) {
|
|
10395
|
-
|
|
10689
|
+
writeFileSync6(options.litestreamPidFile, `${litestreamProcess.pid}
|
|
10396
10690
|
`, {
|
|
10397
10691
|
mode: 384
|
|
10398
10692
|
});
|
|
@@ -10722,6 +11016,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
10722
11016
|
).option(
|
|
10723
11017
|
"--opencode-config-overlay <path>",
|
|
10724
11018
|
"Apply this runner-provided OpenCode config before starting OpenCode."
|
|
11019
|
+
).option(
|
|
11020
|
+
"--credential-sync-marker <path>",
|
|
11021
|
+
"Own the interval credential sync and write this marker once the shutdown flush has finished, so the runner image's lifecycle hooks can wait on it."
|
|
10725
11022
|
).action(
|
|
10726
11023
|
(options) => {
|
|
10727
11024
|
run({
|
|
@@ -10760,7 +11057,8 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
10760
11057
|
sessionDbNoReplicateMarker: options.sessionDbNoReplicateMarker,
|
|
10761
11058
|
restoreSessionDb: options.restoreSessionDb,
|
|
10762
11059
|
restoreRunnerCredentials: options.restoreRunnerCredentials,
|
|
10763
|
-
opencodeConfigOverlay: options.opencodeConfigOverlay
|
|
11060
|
+
opencodeConfigOverlay: options.opencodeConfigOverlay,
|
|
11061
|
+
credentialSyncMarker: options.credentialSyncMarker
|
|
10764
11062
|
});
|
|
10765
11063
|
}
|
|
10766
11064
|
);
|