@evident-ai/cli 3.6.1-dev.8d3e144 → 3.6.1-dev.f151311
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/dist/index.js +456 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1156,9 +1156,9 @@ async function claudeUsage() {
|
|
|
1156
1156
|
}
|
|
1157
1157
|
|
|
1158
1158
|
// src/commands/run.ts
|
|
1159
|
-
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as
|
|
1160
|
-
import { homedir as
|
|
1161
|
-
import { isAbsolute as isAbsolute4, join as
|
|
1159
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1160
|
+
import { homedir as homedir8 } from "node:os";
|
|
1161
|
+
import { isAbsolute as isAbsolute4, join as join13, parse, resolve as resolvePath2 } from "node:path";
|
|
1162
1162
|
import chalk7 from "chalk";
|
|
1163
1163
|
|
|
1164
1164
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -1326,7 +1326,7 @@ import ora4 from "ora";
|
|
|
1326
1326
|
import { select as select4 } from "@inquirer/prompts";
|
|
1327
1327
|
|
|
1328
1328
|
// src/lib/telemetry.ts
|
|
1329
|
-
var CLI_VERSION = (true ? "3.6.1-dev.
|
|
1329
|
+
var CLI_VERSION = (true ? "3.6.1-dev.f151311" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1330
1330
|
function getCliVersion() {
|
|
1331
1331
|
return CLI_VERSION;
|
|
1332
1332
|
}
|
|
@@ -2075,7 +2075,8 @@ function createDependencyInstallDiagnosticReporter({
|
|
|
2075
2075
|
var FORWARDED_LEVELS_BY_SOURCE = {
|
|
2076
2076
|
"cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
|
|
2077
2077
|
opencode: /* @__PURE__ */ new Set(["warn", "error"]),
|
|
2078
|
-
"cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"])
|
|
2078
|
+
"cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"]),
|
|
2079
|
+
"cli.runtime-provenance": /* @__PURE__ */ new Set(["info", "warn", "error"])
|
|
2079
2080
|
};
|
|
2080
2081
|
var SEVERITY_BY_LEVEL = {
|
|
2081
2082
|
info: "info",
|
|
@@ -2871,6 +2872,58 @@ async function listAllSessions(port, client) {
|
|
|
2871
2872
|
}
|
|
2872
2873
|
return { sessions };
|
|
2873
2874
|
}
|
|
2875
|
+
var INTERRUPT_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
2876
|
+
"ABORT_ERR",
|
|
2877
|
+
"EAI_AGAIN",
|
|
2878
|
+
"ECONNREFUSED",
|
|
2879
|
+
"ECONNRESET",
|
|
2880
|
+
"ENETUNREACH",
|
|
2881
|
+
"ENOTFOUND",
|
|
2882
|
+
"ETIMEDOUT",
|
|
2883
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
2884
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
2885
|
+
"UND_ERR_SOCKET"
|
|
2886
|
+
]);
|
|
2887
|
+
function interruptFailureDiagnostic(error2) {
|
|
2888
|
+
const message = error2 instanceof Error ? error2.message : "";
|
|
2889
|
+
const httpStatus = /^OpenCode request failed: HTTP ([1-5]\d{2})(?::|$)/.exec(message)?.[1];
|
|
2890
|
+
if (httpStatus) return `HTTP ${httpStatus}`;
|
|
2891
|
+
const candidates = [error2, error2 instanceof Error ? error2.cause : void 0];
|
|
2892
|
+
for (const candidate of candidates) {
|
|
2893
|
+
if (typeof candidate !== "object" || candidate === null || !("code" in candidate)) continue;
|
|
2894
|
+
const code = candidate.code;
|
|
2895
|
+
if (typeof code === "string" && INTERRUPT_NETWORK_CODES.has(code)) {
|
|
2896
|
+
return redactDiagnosticText(`network ${code}`).slice(0, 120);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
const name = error2 instanceof Error && ["AbortError", "Error", "TimeoutError", "TypeError"].includes(error2.name) ? error2.name : "unknown error";
|
|
2900
|
+
return redactDiagnosticText(`request threw ${name}`).slice(0, 120);
|
|
2901
|
+
}
|
|
2902
|
+
async function interruptSession(port, id, client) {
|
|
2903
|
+
if (client?.version === "v2") {
|
|
2904
|
+
try {
|
|
2905
|
+
await interruptV2Session(client, id);
|
|
2906
|
+
return { kind: "confirmed_stopped" };
|
|
2907
|
+
} catch (error2) {
|
|
2908
|
+
return { kind: "not_confirmed", diagnostic: interruptFailureDiagnostic(error2) };
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
try {
|
|
2912
|
+
const res = await requestWithClient(
|
|
2913
|
+
port,
|
|
2914
|
+
client,
|
|
2915
|
+
`/session/${id}/abort`,
|
|
2916
|
+
{ method: "POST" },
|
|
2917
|
+
{ allowStatuses: ALL_HTTP_STATUSES }
|
|
2918
|
+
);
|
|
2919
|
+
if (res.status >= 200 && res.status < 300) {
|
|
2920
|
+
return { kind: "confirmed_stopped", status: res.status };
|
|
2921
|
+
}
|
|
2922
|
+
return { kind: "not_confirmed", status: res.status };
|
|
2923
|
+
} catch (error2) {
|
|
2924
|
+
return { kind: "threw", diagnostic: interruptFailureDiagnostic(error2) };
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2874
2927
|
async function deleteSession(port, id, client) {
|
|
2875
2928
|
if (client?.version === "v2") return deleteV2Session(client, id);
|
|
2876
2929
|
try {
|
|
@@ -4221,6 +4274,11 @@ async function listV2Permissions(client, sessionId) {
|
|
|
4221
4274
|
return null;
|
|
4222
4275
|
}
|
|
4223
4276
|
}
|
|
4277
|
+
async function interruptV2Session(client, sessionId) {
|
|
4278
|
+
await client.request(`/api/session/${encodeURIComponent(sessionId)}/interrupt`, {
|
|
4279
|
+
method: "POST"
|
|
4280
|
+
});
|
|
4281
|
+
}
|
|
4224
4282
|
async function getV2ActiveSessions(client) {
|
|
4225
4283
|
try {
|
|
4226
4284
|
const body = await readJson(await client.request("/api/session/active"));
|
|
@@ -8891,6 +8949,34 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8891
8949
|
if (state === "failed" && isAbortedTerminalReply(messages, ocId ?? "")) {
|
|
8892
8950
|
const ongoing = await this.isSessionOngoing(sessionId);
|
|
8893
8951
|
if (ongoing === false) {
|
|
8952
|
+
const age = this.absoluteAgeForRow(message.processing_started_at);
|
|
8953
|
+
if (age.tripped && age.anchorMs !== null) {
|
|
8954
|
+
try {
|
|
8955
|
+
await this.markFailed(
|
|
8956
|
+
conv.id,
|
|
8957
|
+
message.id,
|
|
8958
|
+
sessionId,
|
|
8959
|
+
this.absoluteCeilingError(message.id, sessionId, age.anchorMs),
|
|
8960
|
+
void 0,
|
|
8961
|
+
{
|
|
8962
|
+
classification: null,
|
|
8963
|
+
errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
|
|
8964
|
+
}
|
|
8965
|
+
);
|
|
8966
|
+
} catch (err) {
|
|
8967
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
8968
|
+
this.log({
|
|
8969
|
+
level: "warn",
|
|
8970
|
+
message: `Re-drive: failed to report past-ceiling restart orphan ${message.id.slice(0, 8)} terminal; refusing to re-dispatch: ${err instanceof Error ? err.message : String(err)}`,
|
|
8971
|
+
conversation_id: conv.id,
|
|
8972
|
+
message_id: message.id
|
|
8973
|
+
});
|
|
8974
|
+
return "unresolved";
|
|
8975
|
+
}
|
|
8976
|
+
this.clearRedriveUnresolved(message.id);
|
|
8977
|
+
void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
|
|
8978
|
+
return "settled";
|
|
8979
|
+
}
|
|
8894
8980
|
this.log({
|
|
8895
8981
|
level: "info",
|
|
8896
8982
|
message: `Re-drive: message ${message.id.slice(0, 8)} carries an abort-shaped terminal error and session ${sessionId.slice(0, 8)} is not-ongoing per the active-session status check \u2014 restart orphan, re-dispatching instead of marking it permanently failed`,
|
|
@@ -8961,6 +9047,29 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8961
9047
|
message_id: message.id
|
|
8962
9048
|
});
|
|
8963
9049
|
}
|
|
9050
|
+
if (this.absoluteAgeForRow(message.processing_started_at).tripped) {
|
|
9051
|
+
try {
|
|
9052
|
+
await this.markFailed(
|
|
9053
|
+
conv.id,
|
|
9054
|
+
message.id,
|
|
9055
|
+
sessionId,
|
|
9056
|
+
this.absoluteCeilingError(message.id, sessionId, anchorMs)
|
|
9057
|
+
);
|
|
9058
|
+
} catch (err) {
|
|
9059
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
9060
|
+
this.log({
|
|
9061
|
+
level: "warn",
|
|
9062
|
+
message: `Re-drive: failed to report past-ceiling message ${message.id.slice(0, 8)} terminal; refusing to re-attach: ${err instanceof Error ? err.message : String(err)}`,
|
|
9063
|
+
conversation_id: conv.id,
|
|
9064
|
+
message_id: message.id
|
|
9065
|
+
});
|
|
9066
|
+
const bound = await this.boundRedriveOutcome(conv, message, "reattach");
|
|
9067
|
+
return bound === "abandoned" ? "abandoned" : "unresolved";
|
|
9068
|
+
}
|
|
9069
|
+
this.clearRedriveUnresolved(message.id);
|
|
9070
|
+
void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
|
|
9071
|
+
return "abandoned";
|
|
9072
|
+
}
|
|
8964
9073
|
const title = await this.resolveSessionTitle(sessionId, conv.id);
|
|
8965
9074
|
try {
|
|
8966
9075
|
await this.markProcessing(conv.id, message.id, sessionId, ocId, title);
|
|
@@ -9227,8 +9336,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9227
9336
|
const since = this.redriveOutcomeFailingSince.get(message.id);
|
|
9228
9337
|
if (since === void 0) this.redriveOutcomeFailingSince.set(message.id, now);
|
|
9229
9338
|
const durationTripped = now - (since ?? now) >= this.pausedMaxWaitMs;
|
|
9230
|
-
const
|
|
9231
|
-
const absoluteAgeTripped = !Number.isNaN(parsed) && now - parsed >= ABSOLUTE_MAX_PROCESSING_MS;
|
|
9339
|
+
const absoluteAgeTripped = this.absoluteAgeForRow(message.processing_started_at).tripped;
|
|
9232
9340
|
if (!durationTripped && !absoluteAgeTripped) {
|
|
9233
9341
|
this.signalRedriveOutcomeUnreported(conv, message, outcome);
|
|
9234
9342
|
return "retry";
|
|
@@ -9267,6 +9375,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9267
9375
|
});
|
|
9268
9376
|
return "abandoned";
|
|
9269
9377
|
}
|
|
9378
|
+
/** Row-level guards use the durable server stamp; in-flight guards use processingAnchorMs. */
|
|
9379
|
+
absoluteAgeForRow(stamp) {
|
|
9380
|
+
const anchorMs = stamp ? Date.parse(stamp) : NaN;
|
|
9381
|
+
return Number.isNaN(anchorMs) ? { tripped: false, anchorMs: null } : { tripped: this.now() - anchorMs >= ABSOLUTE_MAX_PROCESSING_MS, anchorMs };
|
|
9382
|
+
}
|
|
9383
|
+
absoluteCeilingError(messageId, sessionId, anchorMs, retainedReason = this.readRetainedSessionError(sessionId)) {
|
|
9384
|
+
const baseError = `Message ${messageId.slice(0, 8)} exceeded the absolute processing ceiling (${Math.round((this.now() - anchorMs) / 6e4)}min, session ${sessionId}) while still actively running.`;
|
|
9385
|
+
return retainedReason ? `${baseError} ${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}` : baseError;
|
|
9386
|
+
}
|
|
9270
9387
|
/**
|
|
9271
9388
|
* Record one poll outcome toward the re-drive fence's consecutive-identical-
|
|
9272
9389
|
* failure streak (#1348) and return the resulting count. `signature === null`
|
|
@@ -9708,10 +9825,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9708
9825
|
opencodeMessageId,
|
|
9709
9826
|
message,
|
|
9710
9827
|
dispatchedAt: this.now(),
|
|
9711
|
-
|
|
9712
|
-
// `dispatchedAt` — so a re-adopted zombie's age reflects the real turn duration
|
|
9713
|
-
// and the ceiling fires on the ORIGINAL turn.
|
|
9714
|
-
processingAnchorMs: processedAtMs,
|
|
9828
|
+
processingAnchorMs: this.absoluteAgeForRow(message.processing_started_at).anchorMs ?? processedAtMs,
|
|
9715
9829
|
deadline: watchDeadlineAnchorMs + this.pausedMaxWaitMs,
|
|
9716
9830
|
// The server row is ALREADY `processing`; do not re-fire markProcessing.
|
|
9717
9831
|
started: true,
|
|
@@ -10162,7 +10276,9 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10162
10276
|
}
|
|
10163
10277
|
const { openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk } = await this.pollInteractions(sessionId, watcher, messages);
|
|
10164
10278
|
if (watcher.generation !== generation) return;
|
|
10279
|
+
const ceilingAttemptsThisTick = /* @__PURE__ */ new Set();
|
|
10165
10280
|
for (const inFlight of [...watcher.inFlight.values()]) {
|
|
10281
|
+
if (ceilingAttemptsThisTick.has(inFlight.evidentMessageId)) continue;
|
|
10166
10282
|
await this.serviceInFlightMessage(
|
|
10167
10283
|
sessionId,
|
|
10168
10284
|
watcher,
|
|
@@ -10171,7 +10287,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10171
10287
|
openQuestions,
|
|
10172
10288
|
openPermissions,
|
|
10173
10289
|
questionsPolledOk,
|
|
10174
|
-
permissionsPolledOk
|
|
10290
|
+
permissionsPolledOk,
|
|
10291
|
+
ceilingAttemptsThisTick
|
|
10175
10292
|
);
|
|
10176
10293
|
}
|
|
10177
10294
|
}
|
|
@@ -10212,13 +10329,117 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10212
10329
|
inFlight.deadline = this.now() + this.pausedMaxWaitMs;
|
|
10213
10330
|
}
|
|
10214
10331
|
}
|
|
10332
|
+
async settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick) {
|
|
10333
|
+
const entries = [...watcher.inFlight.values()];
|
|
10334
|
+
const pastCeiling = entries.filter(
|
|
10335
|
+
(entry) => this.now() - entry.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS
|
|
10336
|
+
);
|
|
10337
|
+
const healthySiblings = entries.length - pastCeiling.length;
|
|
10338
|
+
const landed = [];
|
|
10339
|
+
let allRecordsLanded = true;
|
|
10340
|
+
const retainedReason = this.readRetainedSessionError(sessionId);
|
|
10341
|
+
for (const entry of pastCeiling) {
|
|
10342
|
+
if (!watcher.inFlight.has(entry.evidentMessageId)) continue;
|
|
10343
|
+
ceilingAttemptsThisTick.add(entry.evidentMessageId);
|
|
10344
|
+
const error2 = this.absoluteCeilingError(
|
|
10345
|
+
entry.evidentMessageId,
|
|
10346
|
+
sessionId,
|
|
10347
|
+
entry.processingAnchorMs,
|
|
10348
|
+
retainedReason
|
|
10349
|
+
);
|
|
10350
|
+
this.log({
|
|
10351
|
+
level: "warn",
|
|
10352
|
+
message: error2,
|
|
10353
|
+
conversation_id: watcher.conv.id,
|
|
10354
|
+
message_id: entry.evidentMessageId
|
|
10355
|
+
});
|
|
10356
|
+
try {
|
|
10357
|
+
await this.markFailed(
|
|
10358
|
+
watcher.conv.id,
|
|
10359
|
+
entry.evidentMessageId,
|
|
10360
|
+
sessionId,
|
|
10361
|
+
error2,
|
|
10362
|
+
void 0,
|
|
10363
|
+
{
|
|
10364
|
+
classification: null,
|
|
10365
|
+
errorShape: extractReplyErrorShape(
|
|
10366
|
+
findLastAssistantReplyFor(messages, entry.opencodeMessageId)
|
|
10367
|
+
)
|
|
10368
|
+
}
|
|
10369
|
+
);
|
|
10370
|
+
} catch (err) {
|
|
10371
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
10372
|
+
allRecordsLanded = false;
|
|
10373
|
+
const watched_for_ms = this.now() - entry.processingAnchorMs;
|
|
10374
|
+
if (err instanceof ChannelTerminalError) {
|
|
10375
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10376
|
+
watched_for_ms,
|
|
10377
|
+
interrupt_outcome: "record_terminal"
|
|
10378
|
+
});
|
|
10379
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10380
|
+
continue;
|
|
10381
|
+
}
|
|
10382
|
+
if (this.now() >= entry.deadline) {
|
|
10383
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10384
|
+
watched_for_ms,
|
|
10385
|
+
interrupt_outcome: "record_deadline"
|
|
10386
|
+
});
|
|
10387
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10388
|
+
continue;
|
|
10389
|
+
}
|
|
10390
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10391
|
+
watched_for_ms,
|
|
10392
|
+
interrupt_outcome: "record_retry"
|
|
10393
|
+
});
|
|
10394
|
+
this.log({
|
|
10395
|
+
level: "warn",
|
|
10396
|
+
message: `Failed to record the absolute-ceiling failure for message ${entry.evidentMessageId.slice(0, 8)}; leaving its running turn untouched and retrying next tick: ${err instanceof Error ? err.message : String(err)}`,
|
|
10397
|
+
conversation_id: watcher.conv.id,
|
|
10398
|
+
message_id: entry.evidentMessageId
|
|
10399
|
+
});
|
|
10400
|
+
continue;
|
|
10401
|
+
}
|
|
10402
|
+
entry.done = true;
|
|
10403
|
+
landed.push(entry);
|
|
10404
|
+
}
|
|
10405
|
+
if (!allRecordsLanded) {
|
|
10406
|
+
for (const entry of landed) this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10407
|
+
return;
|
|
10408
|
+
}
|
|
10409
|
+
if (healthySiblings > 0) {
|
|
10410
|
+
for (const entry of landed) {
|
|
10411
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10412
|
+
watched_for_ms: this.now() - entry.processingAnchorMs,
|
|
10413
|
+
interrupt_outcome: "deferred_for_healthy_sibling",
|
|
10414
|
+
healthy_siblings: healthySiblings
|
|
10415
|
+
});
|
|
10416
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10417
|
+
}
|
|
10418
|
+
return;
|
|
10419
|
+
}
|
|
10420
|
+
const interrupt = await interruptSession(this.port, sessionId, this.openCodeClient);
|
|
10421
|
+
for (const entry of landed) {
|
|
10422
|
+
void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
|
|
10423
|
+
watched_for_ms: this.now() - entry.processingAnchorMs,
|
|
10424
|
+
interrupt_outcome: interrupt.kind
|
|
10425
|
+
});
|
|
10426
|
+
this.removeInFlight(watcher, entry.evidentMessageId);
|
|
10427
|
+
}
|
|
10428
|
+
if (interrupt.kind !== "confirmed_stopped") {
|
|
10429
|
+
this.log({
|
|
10430
|
+
level: "warn",
|
|
10431
|
+
message: `Absolute-ceiling records landed for session ${sessionId}, but OpenCode interruption was ${interrupt.kind}${interrupt.diagnostic ? ` (${interrupt.diagnostic})` : "status" in interrupt && typeof interrupt.status === "number" ? ` (HTTP ${interrupt.status})` : ""}; the rows are terminal and released, while the session may still be running`,
|
|
10432
|
+
conversation_id: watcher.conv.id
|
|
10433
|
+
});
|
|
10434
|
+
}
|
|
10435
|
+
}
|
|
10215
10436
|
/**
|
|
10216
10437
|
* Drive ONE in-flight message's lifecycle from the tick's message snapshot.
|
|
10217
10438
|
* Fires markProcessing on queued→running and markDone on done (each once),
|
|
10218
10439
|
* applies the idle-path re-dispatch guard, and removes the message from the
|
|
10219
10440
|
* in-flight set on completion or timeout.
|
|
10220
10441
|
*/
|
|
10221
|
-
async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk) {
|
|
10442
|
+
async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk, ceilingAttemptsThisTick) {
|
|
10222
10443
|
const conv = watcher.conv;
|
|
10223
10444
|
const state = messageRunState(messages, inFlight.opencodeMessageId);
|
|
10224
10445
|
const id = inFlight.evidentMessageId;
|
|
@@ -10562,17 +10783,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10562
10783
|
return;
|
|
10563
10784
|
}
|
|
10564
10785
|
}
|
|
10565
|
-
if (activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
10566
|
-
this.
|
|
10567
|
-
level: "warn",
|
|
10568
|
-
message: `Message ${inFlight.evidentMessageId.slice(0, 8)} exceeded the absolute processing ceiling (${Math.round((this.now() - inFlight.processingAnchorMs) / 6e4)}min, session ${sessionId}) while still actively running \u2014 releasing so the cron can reclaim it`,
|
|
10569
|
-
conversation_id: conv.id,
|
|
10570
|
-
message_id: inFlight.evidentMessageId
|
|
10571
|
-
});
|
|
10572
|
-
void this.postSignal(conv.id, inFlight.evidentMessageId, "gave_up", {
|
|
10573
|
-
watched_for_ms: this.now() - inFlight.processingAnchorMs
|
|
10574
|
-
});
|
|
10575
|
-
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
10786
|
+
if (watcher.inFlight.has(inFlight.evidentMessageId) && activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
10787
|
+
await this.settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick);
|
|
10576
10788
|
return;
|
|
10577
10789
|
}
|
|
10578
10790
|
const heartbeatDue = activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS;
|
|
@@ -11195,6 +11407,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11195
11407
|
}
|
|
11196
11408
|
const restartAborted = state === "failed" && sessionOngoing === false && isAbortedTerminalReply(messages, ocId ?? "");
|
|
11197
11409
|
if (restartAborted) {
|
|
11410
|
+
const age = this.absoluteAgeForRow(row.processing_started_at);
|
|
11411
|
+
if (age.tripped && age.anchorMs !== null) {
|
|
11412
|
+
try {
|
|
11413
|
+
await this.markFailed(
|
|
11414
|
+
row.conversation_id,
|
|
11415
|
+
row.id,
|
|
11416
|
+
sessionId,
|
|
11417
|
+
this.absoluteCeilingError(row.id, sessionId, age.anchorMs),
|
|
11418
|
+
void 0,
|
|
11419
|
+
{
|
|
11420
|
+
classification: null,
|
|
11421
|
+
errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
|
|
11422
|
+
}
|
|
11423
|
+
);
|
|
11424
|
+
} catch (err) {
|
|
11425
|
+
if (err instanceof ChannelAuthError) throw err;
|
|
11426
|
+
this.log({
|
|
11427
|
+
level: "warn",
|
|
11428
|
+
message: `Re-adopt: failed to report past-ceiling restart orphan ${row.id.slice(0, 8)} terminal; refusing to re-dispatch: ${err instanceof Error ? err.message : String(err)}`,
|
|
11429
|
+
conversation_id: row.conversation_id,
|
|
11430
|
+
message_id: row.id
|
|
11431
|
+
});
|
|
11432
|
+
return;
|
|
11433
|
+
}
|
|
11434
|
+
void this.postSignal(row.conversation_id, row.id, "redrive_ceiling_refused");
|
|
11435
|
+
return;
|
|
11436
|
+
}
|
|
11198
11437
|
this.log({
|
|
11199
11438
|
level: "info",
|
|
11200
11439
|
message: `Re-adopt: message ${row.id.slice(0, 8)} carries an abort-shaped terminal error and session ${sessionId.slice(0, 8)} is not-ongoing per the active-session status check \u2014 restart orphan, re-dispatching instead of marking it permanently failed`,
|
|
@@ -11740,7 +11979,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11740
11979
|
source_message_id: row.source_message_id,
|
|
11741
11980
|
slack_user_id: row.slack_user_id,
|
|
11742
11981
|
attachments: row.attachments ?? null,
|
|
11743
|
-
opencode_message_id: row.opencode_message_id
|
|
11982
|
+
opencode_message_id: row.opencode_message_id,
|
|
11983
|
+
processing_started_at: row.processing_started_at
|
|
11744
11984
|
};
|
|
11745
11985
|
}
|
|
11746
11986
|
/**
|
|
@@ -13571,6 +13811,159 @@ function applyRunnerOpenCodeConfig({
|
|
|
13571
13811
|
log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
|
|
13572
13812
|
}
|
|
13573
13813
|
|
|
13814
|
+
// src/lib/opencode/runtime-provenance.ts
|
|
13815
|
+
import { readFileSync as readFileSync7, readdirSync } from "node:fs";
|
|
13816
|
+
import { homedir as homedir7 } from "node:os";
|
|
13817
|
+
import { join as join12 } from "node:path";
|
|
13818
|
+
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
|
|
13819
|
+
var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
|
|
13820
|
+
function errorKind(error2) {
|
|
13821
|
+
if (!(error2 instanceof Error)) return "unknown error";
|
|
13822
|
+
const code = "code" in error2 && typeof error2.code === "string" ? ` (${error2.code})` : "";
|
|
13823
|
+
return `${error2.name}${code}`;
|
|
13824
|
+
}
|
|
13825
|
+
function parseRegistryPluginSpec(value) {
|
|
13826
|
+
const separator = value.lastIndexOf("@");
|
|
13827
|
+
if (separator <= 0) return null;
|
|
13828
|
+
const name = value.slice(0, separator);
|
|
13829
|
+
const spec = value.slice(separator + 1);
|
|
13830
|
+
if (!PACKAGE_NAME.test(name) || !REGISTRY_SPEC.test(spec)) return null;
|
|
13831
|
+
return { name, spec };
|
|
13832
|
+
}
|
|
13833
|
+
function parseJsonc(value) {
|
|
13834
|
+
let output = "";
|
|
13835
|
+
let inString = false;
|
|
13836
|
+
let escaped = false;
|
|
13837
|
+
let lineComment = false;
|
|
13838
|
+
let blockComment = false;
|
|
13839
|
+
for (let index = 0; index < value.length; index++) {
|
|
13840
|
+
const character = value[index];
|
|
13841
|
+
const next = value[index + 1];
|
|
13842
|
+
if (lineComment) {
|
|
13843
|
+
if (character === "\n") {
|
|
13844
|
+
lineComment = false;
|
|
13845
|
+
output += character;
|
|
13846
|
+
}
|
|
13847
|
+
continue;
|
|
13848
|
+
}
|
|
13849
|
+
if (blockComment) {
|
|
13850
|
+
if (character === "*" && next === "/") {
|
|
13851
|
+
blockComment = false;
|
|
13852
|
+
index++;
|
|
13853
|
+
} else if (character === "\n") {
|
|
13854
|
+
output += character;
|
|
13855
|
+
}
|
|
13856
|
+
continue;
|
|
13857
|
+
}
|
|
13858
|
+
if (inString) {
|
|
13859
|
+
output += character;
|
|
13860
|
+
if (escaped) escaped = false;
|
|
13861
|
+
else if (character === "\\") escaped = true;
|
|
13862
|
+
else if (character === '"') inString = false;
|
|
13863
|
+
continue;
|
|
13864
|
+
}
|
|
13865
|
+
if (character === '"') {
|
|
13866
|
+
inString = true;
|
|
13867
|
+
output += character;
|
|
13868
|
+
} else if (character === "/" && next === "/") {
|
|
13869
|
+
lineComment = true;
|
|
13870
|
+
index++;
|
|
13871
|
+
} else if (character === "/" && next === "*") {
|
|
13872
|
+
blockComment = true;
|
|
13873
|
+
index++;
|
|
13874
|
+
} else if (character === ",") {
|
|
13875
|
+
let lookahead = index + 1;
|
|
13876
|
+
while (/\s/.test(value[lookahead] ?? "")) lookahead++;
|
|
13877
|
+
if (value[lookahead] !== "]" && value[lookahead] !== "}") output += character;
|
|
13878
|
+
} else {
|
|
13879
|
+
output += character;
|
|
13880
|
+
}
|
|
13881
|
+
}
|
|
13882
|
+
return JSON.parse(output);
|
|
13883
|
+
}
|
|
13884
|
+
function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
|
|
13885
|
+
return join12(env.XDG_CACHE_HOME ?? join12(homeDir, ".cache"), "opencode");
|
|
13886
|
+
}
|
|
13887
|
+
function readConfiguredPluginSpecs({
|
|
13888
|
+
cwd = process.cwd(),
|
|
13889
|
+
log: log3
|
|
13890
|
+
}) {
|
|
13891
|
+
let config;
|
|
13892
|
+
try {
|
|
13893
|
+
try {
|
|
13894
|
+
config = parseJsonc(readFileSync7(join12(cwd, "opencode.jsonc"), "utf8"));
|
|
13895
|
+
} catch (error2) {
|
|
13896
|
+
if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
|
|
13897
|
+
config = parseJsonc(readFileSync7(join12(cwd, "opencode.json"), "utf8"));
|
|
13898
|
+
}
|
|
13899
|
+
} catch (error2) {
|
|
13900
|
+
log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
|
|
13901
|
+
return [];
|
|
13902
|
+
}
|
|
13903
|
+
if (typeof config !== "object" || config === null || !("plugin" in config)) return [];
|
|
13904
|
+
const plugins = config.plugin;
|
|
13905
|
+
if (!Array.isArray(plugins)) {
|
|
13906
|
+
log3("Could not read OpenCode plugin configuration: plugin is not an array");
|
|
13907
|
+
return [];
|
|
13908
|
+
}
|
|
13909
|
+
const configured = plugins.filter(
|
|
13910
|
+
(plugin) => typeof plugin === "string" && parseRegistryPluginSpec(plugin) !== null
|
|
13911
|
+
);
|
|
13912
|
+
if (configured.length !== plugins.length) {
|
|
13913
|
+
log3(
|
|
13914
|
+
`Skipped ${plugins.length - configured.length} non-registry OpenCode plugin spec(s); their values were not recorded`
|
|
13915
|
+
);
|
|
13916
|
+
}
|
|
13917
|
+
return configured;
|
|
13918
|
+
}
|
|
13919
|
+
function resolveInstalledPluginVersions({
|
|
13920
|
+
cacheRoot,
|
|
13921
|
+
env = process.env,
|
|
13922
|
+
homeDir = homedir7(),
|
|
13923
|
+
log: log3
|
|
13924
|
+
}) {
|
|
13925
|
+
const packagesRoot = join12(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
|
|
13926
|
+
let directories;
|
|
13927
|
+
try {
|
|
13928
|
+
directories = readdirSync(packagesRoot, { withFileTypes: true });
|
|
13929
|
+
} catch (error2) {
|
|
13930
|
+
if ("code" in Object(error2) && Object(error2).code === "ENOENT") return [];
|
|
13931
|
+
log3(`Could not inspect the OpenCode plugin cache: ${errorKind(error2)}`);
|
|
13932
|
+
return [];
|
|
13933
|
+
}
|
|
13934
|
+
const plugins = [];
|
|
13935
|
+
let skipped = 0;
|
|
13936
|
+
for (const directory of directories) {
|
|
13937
|
+
if (!directory.isDirectory()) continue;
|
|
13938
|
+
const parsed = parseRegistryPluginSpec(directory.name);
|
|
13939
|
+
if (parsed === null) {
|
|
13940
|
+
skipped++;
|
|
13941
|
+
continue;
|
|
13942
|
+
}
|
|
13943
|
+
let resolvedVersion = null;
|
|
13944
|
+
try {
|
|
13945
|
+
const packageJson = JSON.parse(
|
|
13946
|
+
readFileSync7(
|
|
13947
|
+
join12(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
|
|
13948
|
+
"utf8"
|
|
13949
|
+
)
|
|
13950
|
+
);
|
|
13951
|
+
if (typeof packageJson.version === "string" && REGISTRY_SPEC.test(packageJson.version)) {
|
|
13952
|
+
resolvedVersion = packageJson.version;
|
|
13953
|
+
} else {
|
|
13954
|
+
log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: invalid version`);
|
|
13955
|
+
}
|
|
13956
|
+
} catch (error2) {
|
|
13957
|
+
log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: ${errorKind(error2)}`);
|
|
13958
|
+
}
|
|
13959
|
+
plugins.push({ ...parsed, resolvedVersion });
|
|
13960
|
+
}
|
|
13961
|
+
if (skipped > 0) {
|
|
13962
|
+
log3(`Skipped ${skipped} non-registry OpenCode package cache entry; its value was not recorded`);
|
|
13963
|
+
}
|
|
13964
|
+
return plugins;
|
|
13965
|
+
}
|
|
13966
|
+
|
|
13574
13967
|
// src/lib/credential-sync.ts
|
|
13575
13968
|
import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13576
13969
|
var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
|
|
@@ -13846,7 +14239,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
13846
14239
|
if (trimmed === "") {
|
|
13847
14240
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
13848
14241
|
}
|
|
13849
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
14242
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join13(homeDir, trimmed.slice(2)) : trimmed;
|
|
13850
14243
|
if (!isAbsolute4(expanded)) {
|
|
13851
14244
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
13852
14245
|
}
|
|
@@ -14001,7 +14394,7 @@ function logActivity(state, entry) {
|
|
|
14001
14394
|
}
|
|
14002
14395
|
function reportSessionDbRecovery(state) {
|
|
14003
14396
|
try {
|
|
14004
|
-
const report = drainSessionDbRecoveryReport({ homeDir:
|
|
14397
|
+
const report = drainSessionDbRecoveryReport({ homeDir: homedir8(), env: process.env });
|
|
14005
14398
|
for (const record of report.records) {
|
|
14006
14399
|
const activity = buildSessionDbRecoveryActivity(record);
|
|
14007
14400
|
if (!activity) throw new Error("could not map session-DB recovery record");
|
|
@@ -14082,7 +14475,7 @@ async function importOpenCodeV2CredentialsForState(state) {
|
|
|
14082
14475
|
}
|
|
14083
14476
|
const result = await importOpenCodeV2Credentials({
|
|
14084
14477
|
request: client.request,
|
|
14085
|
-
homeDir:
|
|
14478
|
+
homeDir: homedir8(),
|
|
14086
14479
|
directory,
|
|
14087
14480
|
guard: state.opencodeV2CredentialImportGuard,
|
|
14088
14481
|
log: (message, level = "info") => logActivity(state, { type: "info", level, message })
|
|
@@ -14502,7 +14895,7 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
14502
14895
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
14503
14896
|
}
|
|
14504
14897
|
function sessionDbPath() {
|
|
14505
|
-
return
|
|
14898
|
+
return join13(homedir8(), ".local", "share", "opencode", "opencode.db");
|
|
14506
14899
|
}
|
|
14507
14900
|
function logSessionCleanupActivity(state, phase, entry) {
|
|
14508
14901
|
logActivity(state, {
|
|
@@ -14746,7 +15139,7 @@ function scheduleSessionCleanup(state, driver, options) {
|
|
|
14746
15139
|
for (const warning2 of config.warnings) {
|
|
14747
15140
|
logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
|
|
14748
15141
|
}
|
|
14749
|
-
const dbBytes = statSessionDbBytes(
|
|
15142
|
+
const dbBytes = statSessionDbBytes(homedir8());
|
|
14750
15143
|
void (async () => {
|
|
14751
15144
|
const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
|
|
14752
15145
|
if (reclaimAvailability !== null) {
|
|
@@ -14965,7 +15358,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14965
15358
|
});
|
|
14966
15359
|
return;
|
|
14967
15360
|
}
|
|
14968
|
-
const { collect, stop } = createResourceUsageCollector(
|
|
15361
|
+
const { collect, stop } = createResourceUsageCollector(homedir8());
|
|
14969
15362
|
state.stopResourceUsageSampling = stop;
|
|
14970
15363
|
let consecutiveFailures = 0;
|
|
14971
15364
|
const tick = async () => {
|
|
@@ -15206,7 +15599,7 @@ async function run(options) {
|
|
|
15206
15599
|
let fileSyncDirectories;
|
|
15207
15600
|
try {
|
|
15208
15601
|
logLevel = resolveLogLevel(options);
|
|
15209
|
-
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo,
|
|
15602
|
+
fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir8());
|
|
15210
15603
|
if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
|
|
15211
15604
|
throw new Error(
|
|
15212
15605
|
"--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
|
|
@@ -15492,7 +15885,7 @@ async function run(options) {
|
|
|
15492
15885
|
state.credentialSync?.arm();
|
|
15493
15886
|
const dependencyInstallDiagnosticReporter = createDependencyInstallDiagnosticReporter({
|
|
15494
15887
|
context: {
|
|
15495
|
-
homeDir:
|
|
15888
|
+
homeDir: homedir8(),
|
|
15496
15889
|
cwd: process.cwd(),
|
|
15497
15890
|
microvmId: process.env.MICROVM_ID?.trim(),
|
|
15498
15891
|
euid: process.getuid?.(),
|
|
@@ -15515,7 +15908,7 @@ async function run(options) {
|
|
|
15515
15908
|
dependencyInstallDiagnosticReporter
|
|
15516
15909
|
);
|
|
15517
15910
|
const openCodeLogTail = tailOpenCodeLogFile(
|
|
15518
|
-
resolveOpenCodeLogPath(
|
|
15911
|
+
resolveOpenCodeLogPath(homedir8(), process.env),
|
|
15519
15912
|
openCodeActivityForwarder
|
|
15520
15913
|
);
|
|
15521
15914
|
state.stopOpenCodeLogTail = () => {
|
|
@@ -15616,6 +16009,32 @@ async function run(options) {
|
|
|
15616
16009
|
state.opencodeProcess = options.opencodePidFile ? null : oc.process;
|
|
15617
16010
|
state.opencodeVersion = oc.version;
|
|
15618
16011
|
state.opencodeApiVersion = opencodeVersion;
|
|
16012
|
+
let configuredPlugins = [];
|
|
16013
|
+
let resolvedPlugins = [];
|
|
16014
|
+
try {
|
|
16015
|
+
const provenanceLog = (message) => logActivity(state, { type: "info", level: "warn", message });
|
|
16016
|
+
configuredPlugins = readConfiguredPluginSpecs({ log: provenanceLog });
|
|
16017
|
+
resolvedPlugins = resolveInstalledPluginVersions({ log: provenanceLog });
|
|
16018
|
+
} catch (error2) {
|
|
16019
|
+
const kind = error2 instanceof Error ? error2.name : "unknown error";
|
|
16020
|
+
logActivity(state, {
|
|
16021
|
+
type: "info",
|
|
16022
|
+
level: "warn",
|
|
16023
|
+
message: `Could not collect OpenCode runtime provenance: ${kind}`
|
|
16024
|
+
});
|
|
16025
|
+
}
|
|
16026
|
+
logActivity(state, {
|
|
16027
|
+
type: "info",
|
|
16028
|
+
level: "info",
|
|
16029
|
+
message: "OpenCode runtime provenance",
|
|
16030
|
+
metadata: {
|
|
16031
|
+
opencode_version: state.opencodeVersion,
|
|
16032
|
+
opencode_api_version: state.opencodeApiVersion,
|
|
16033
|
+
configured_plugins: JSON.stringify(configuredPlugins),
|
|
16034
|
+
resolved_plugins: JSON.stringify(resolvedPlugins)
|
|
16035
|
+
},
|
|
16036
|
+
source: "cli.runtime-provenance"
|
|
16037
|
+
});
|
|
15619
16038
|
let opencodePassword = null;
|
|
15620
16039
|
if (opencodeVersion === "v2" && "password" in oc) {
|
|
15621
16040
|
const value = oc.password;
|
|
@@ -15644,7 +16063,7 @@ async function run(options) {
|
|
|
15644
16063
|
const provenance = checkSessionDbProvenance({
|
|
15645
16064
|
dbPath: sessionDbPath(),
|
|
15646
16065
|
currentVersion: state.opencodeVersion,
|
|
15647
|
-
homeDir:
|
|
16066
|
+
homeDir: homedir8(),
|
|
15648
16067
|
env: process.env
|
|
15649
16068
|
});
|
|
15650
16069
|
if (provenance.anomaly) {
|
|
@@ -15716,7 +16135,7 @@ async function run(options) {
|
|
|
15716
16135
|
let existingPid;
|
|
15717
16136
|
if (existsSync3(options.litestreamPidFile)) {
|
|
15718
16137
|
try {
|
|
15719
|
-
const rawPid =
|
|
16138
|
+
const rawPid = readFileSync8(options.litestreamPidFile, "utf8").trim();
|
|
15720
16139
|
const parsedPid = Number(rawPid);
|
|
15721
16140
|
if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
|
|
15722
16141
|
existingPid = parsedPid;
|
|
@@ -15815,7 +16234,7 @@ async function run(options) {
|
|
|
15815
16234
|
// #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
|
|
15816
16235
|
// REJECTED with `file_sync_disabled` on the ack, not silently ignored.
|
|
15817
16236
|
fileSyncDirectories,
|
|
15818
|
-
homeDir:
|
|
16237
|
+
homeDir: homedir8(),
|
|
15819
16238
|
maxActiveSessions,
|
|
15820
16239
|
log: (entry) => (
|
|
15821
16240
|
// Thread the driver's real level straight through so `debug`/`warn`
|