@evident-ai/cli 3.5.1-dev.bbaa769 → 3.5.1
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 +141 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1158,7 +1158,7 @@ import ora4 from "ora";
|
|
|
1158
1158
|
import { select as select4 } from "@inquirer/prompts";
|
|
1159
1159
|
|
|
1160
1160
|
// src/lib/telemetry.ts
|
|
1161
|
-
var CLI_VERSION = (true ? "3.5.1
|
|
1161
|
+
var CLI_VERSION = (true ? "3.5.1" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1162
1162
|
function getCliVersion() {
|
|
1163
1163
|
return CLI_VERSION;
|
|
1164
1164
|
}
|
|
@@ -1620,6 +1620,9 @@ async function checkOpenCodeHealth(port) {
|
|
|
1620
1620
|
signal: AbortSignal.timeout(2e3)
|
|
1621
1621
|
// 2 second timeout
|
|
1622
1622
|
});
|
|
1623
|
+
if (response.status === 401) {
|
|
1624
|
+
return { healthy: false, authFailed: true, error: "HTTP 401" };
|
|
1625
|
+
}
|
|
1623
1626
|
if (!response.ok) {
|
|
1624
1627
|
return { healthy: false, error: `HTTP ${response.status}` };
|
|
1625
1628
|
}
|
|
@@ -1630,12 +1633,18 @@ async function checkOpenCodeHealth(port) {
|
|
|
1630
1633
|
return { healthy: false, error: message };
|
|
1631
1634
|
}
|
|
1632
1635
|
}
|
|
1636
|
+
function bodyExcerpt(body) {
|
|
1637
|
+
const excerpt = body.replace(/\s+/g, " ").trim().slice(0, 160);
|
|
1638
|
+
return excerpt ? ` (body="${excerpt}")` : " (empty body)";
|
|
1639
|
+
}
|
|
1633
1640
|
async function checkOpenCode2Health(port, password) {
|
|
1634
1641
|
try {
|
|
1635
|
-
const
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1642
|
+
const headers = {};
|
|
1643
|
+
if (password !== void 0) {
|
|
1644
|
+
headers.Authorization = buildOpenCodeBasicAuthHeader(password);
|
|
1645
|
+
}
|
|
1646
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
|
|
1647
|
+
headers,
|
|
1639
1648
|
signal: AbortSignal.timeout(2e3)
|
|
1640
1649
|
});
|
|
1641
1650
|
if (response.status === 401) {
|
|
@@ -1644,8 +1653,31 @@ async function checkOpenCode2Health(port, password) {
|
|
|
1644
1653
|
if (!response.ok) {
|
|
1645
1654
|
return { healthy: false, error: `HTTP ${response.status}` };
|
|
1646
1655
|
}
|
|
1647
|
-
const
|
|
1648
|
-
|
|
1656
|
+
const body = await response.text();
|
|
1657
|
+
const excerpt = bodyExcerpt(body);
|
|
1658
|
+
let data;
|
|
1659
|
+
try {
|
|
1660
|
+
data = JSON.parse(body);
|
|
1661
|
+
} catch (error2) {
|
|
1662
|
+
return {
|
|
1663
|
+
healthy: false,
|
|
1664
|
+
error: `Invalid JSON health response${excerpt}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
1668
|
+
return {
|
|
1669
|
+
healthy: false,
|
|
1670
|
+
error: `Invalid health response${excerpt}: expected healthy=true`
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
const health = data;
|
|
1674
|
+
if (health.healthy !== true) {
|
|
1675
|
+
return {
|
|
1676
|
+
healthy: false,
|
|
1677
|
+
error: `Invalid health response${excerpt}: expected healthy=true`
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
return typeof health.version === "string" ? { healthy: true, version: health.version } : { healthy: true };
|
|
1649
1681
|
} catch (error2) {
|
|
1650
1682
|
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
1651
1683
|
return { healthy: false, error: message };
|
|
@@ -1655,7 +1687,7 @@ async function waitForOpenCodeHealth(port, timeoutMs = 3e4) {
|
|
|
1655
1687
|
const startTime = Date.now();
|
|
1656
1688
|
while (Date.now() - startTime < timeoutMs) {
|
|
1657
1689
|
const health = await checkOpenCodeHealth(port);
|
|
1658
|
-
if (health.healthy) {
|
|
1690
|
+
if (health.healthy || health.authFailed) {
|
|
1659
1691
|
return health;
|
|
1660
1692
|
}
|
|
1661
1693
|
await new Promise((resolve4) => setTimeout(resolve4, 1e3));
|
|
@@ -4282,6 +4314,9 @@ function isSessionDbProvenanceRecord(value) {
|
|
|
4282
4314
|
return typeof record.opencodeVersion === "string" && Array.isArray(record.migrationIds) && record.migrationIds.every((id) => typeof id === "string") && typeof record.updatedAt === "string";
|
|
4283
4315
|
}
|
|
4284
4316
|
|
|
4317
|
+
// src/lib/opencode/opencode2-package.ts
|
|
4318
|
+
var OPENCODE2_PACKAGE_SPEC = "@opencode-ai/cli@0.0.0-beta-17823";
|
|
4319
|
+
|
|
4285
4320
|
// src/lib/opencode/opencode-version-gate.ts
|
|
4286
4321
|
var QUEUE_VALIDATED_OPENCODE_VERSIONS = ["1.17.11", "1.18.3"];
|
|
4287
4322
|
function isQueueValidatedVersion(version2) {
|
|
@@ -4572,7 +4607,8 @@ async function startOpenCode(port, options = {}) {
|
|
|
4572
4607
|
const child = spawn3(command, args, {
|
|
4573
4608
|
detached: true,
|
|
4574
4609
|
stdio: options.inheritStdio ? "inherit" : "ignore",
|
|
4575
|
-
cwd: process.cwd()
|
|
4610
|
+
cwd: process.cwd(),
|
|
4611
|
+
env: { ...process.env, OPENCODE_SERVER_PASSWORD: void 0 }
|
|
4576
4612
|
});
|
|
4577
4613
|
return child;
|
|
4578
4614
|
}
|
|
@@ -4588,7 +4624,7 @@ async function startOpenCode2(port, options = {}) {
|
|
|
4588
4624
|
args = [
|
|
4589
4625
|
"-y",
|
|
4590
4626
|
"-p",
|
|
4591
|
-
|
|
4627
|
+
OPENCODE2_PACKAGE_SPEC,
|
|
4592
4628
|
"--",
|
|
4593
4629
|
"opencode2",
|
|
4594
4630
|
"serve",
|
|
@@ -4665,7 +4701,7 @@ async function promptOpenCodeInstall(interactive) {
|
|
|
4665
4701
|
npm: "npm install -g opencode-ai",
|
|
4666
4702
|
curl: "curl -fsSL https://opencode.ai/install.sh | sh",
|
|
4667
4703
|
v2: {
|
|
4668
|
-
npm:
|
|
4704
|
+
npm: `npm install -g ${OPENCODE2_PACKAGE_SPEC}`,
|
|
4669
4705
|
curl: "curl -fsSL https://opencode.ai/v2/install | bash"
|
|
4670
4706
|
}
|
|
4671
4707
|
}
|
|
@@ -6399,6 +6435,7 @@ var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
|
6399
6435
|
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
6400
6436
|
var DEFAULT_STUCK_QUEUED_MS = 6e4;
|
|
6401
6437
|
var HEARTBEAT_MS = 6e4;
|
|
6438
|
+
var ALIVE_WITHHELD_THRESHOLD_MS = 3 * HEARTBEAT_MS;
|
|
6402
6439
|
var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
|
|
6403
6440
|
var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
|
|
6404
6441
|
var AMBIGUOUS_FINISH_MAX_PINNED_MS = 3 * 6e4;
|
|
@@ -7603,7 +7640,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7603
7640
|
return "dispatch";
|
|
7604
7641
|
}
|
|
7605
7642
|
}
|
|
7606
|
-
return this.reattachRedrive(conv, sessionId, message, ocId);
|
|
7643
|
+
return this.reattachRedrive(conv, sessionId, message, ocId, state);
|
|
7607
7644
|
}
|
|
7608
7645
|
if (ongoing === false) {
|
|
7609
7646
|
if (state === "running" && isAmbiguousFinishPinnedRunning(messages, ocId ?? "")) {
|
|
@@ -7620,11 +7657,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7620
7657
|
return "dispatch";
|
|
7621
7658
|
}
|
|
7622
7659
|
/**
|
|
7623
|
-
* The `reattached` outcome (Task 3.3):
|
|
7624
|
-
*
|
|
7625
|
-
*
|
|
7660
|
+
* The `reattached` outcome (Task 3.3): opencode's own status map says the session
|
|
7661
|
+
* is ongoing, so undo the false reclaim instead of starting a second turn. `state`
|
|
7662
|
+
* carries the correlated message's observed state so the outcome does not claim
|
|
7663
|
+
* that a queued message's own turn was already running.
|
|
7626
7664
|
*/
|
|
7627
|
-
async reattachRedrive(conv, sessionId, message, ocId) {
|
|
7665
|
+
async reattachRedrive(conv, sessionId, message, ocId, state) {
|
|
7628
7666
|
let anchorMs;
|
|
7629
7667
|
const parsed = message.processing_started_at ? Date.parse(message.processing_started_at) : NaN;
|
|
7630
7668
|
if (!Number.isNaN(parsed)) {
|
|
@@ -7633,7 +7671,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7633
7671
|
anchorMs = this.now();
|
|
7634
7672
|
this.log({
|
|
7635
7673
|
level: "warn",
|
|
7636
|
-
message: `Re-drive: message ${message.id.slice(0, 8)} has
|
|
7674
|
+
message: `Re-drive: message ${message.id.slice(0, 8)} has no usable processing_started_at (${String(message.processing_started_at)}) \u2014 anchoring the watcher's absolute-age ceiling to now`,
|
|
7637
7675
|
conversation_id: conv.id,
|
|
7638
7676
|
message_id: message.id
|
|
7639
7677
|
});
|
|
@@ -7662,17 +7700,19 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7662
7700
|
return bound === "abandoned" ? "abandoned" : "unresolved";
|
|
7663
7701
|
}
|
|
7664
7702
|
this.clearRedriveUnresolved(message.id);
|
|
7665
|
-
this.registerReadopted(conv, sessionId, message, ocId ?? "", anchorMs);
|
|
7703
|
+
this.registerReadopted(conv, sessionId, message, ocId ?? "", anchorMs, this.now());
|
|
7666
7704
|
this.dispatched.add(message.id);
|
|
7667
7705
|
this.readopted.add(message.id);
|
|
7668
|
-
this.ensureWatcherRunning(sessionId);
|
|
7706
|
+
const watcherState = this.ensureWatcherRunning(sessionId);
|
|
7669
7707
|
const watchedForMs = this.now() - anchorMs;
|
|
7670
7708
|
void this.postSignal(conv.id, message.id, "redrive_reattached", {
|
|
7671
|
-
watched_for_ms: watchedForMs
|
|
7709
|
+
watched_for_ms: watchedForMs,
|
|
7710
|
+
run_state: state,
|
|
7711
|
+
watcher_state: watcherState
|
|
7672
7712
|
});
|
|
7673
7713
|
this.log({
|
|
7674
7714
|
level: "warn",
|
|
7675
|
-
message: `Re-drive: message ${message.id.slice(0, 8)} (session ${sessionId.slice(0, 8)}) was wrongly reclaimed to pending while its turn was still running (watched ${watchedForMs}ms) \u2014 restored to processing instead of re-dispatching`,
|
|
7715
|
+
message: state === "running" ? `Re-drive: message ${message.id.slice(0, 8)} (session ${sessionId.slice(0, 8)}) was wrongly reclaimed to pending while its turn was still running (watched ${watchedForMs}ms) \u2014 restored to processing instead of re-dispatching` : `Re-drive: message ${message.id.slice(0, 8)} (session ${sessionId.slice(0, 8)}) was reclaimed to pending while its own turn had not started and the session was busy (watched ${watchedForMs}ms) \u2014 restored to processing instead of re-dispatching to avoid a duplicate`,
|
|
7676
7716
|
conversation_id: conv.id,
|
|
7677
7717
|
message_id: message.id
|
|
7678
7718
|
});
|
|
@@ -8279,6 +8319,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8279
8319
|
stuckReported: false,
|
|
8280
8320
|
lastAliveAt: 0,
|
|
8281
8321
|
aliveInFlight: false,
|
|
8322
|
+
aliveWithheldSignalled: false,
|
|
8282
8323
|
titleSynced: false,
|
|
8283
8324
|
titleSyncInFlight: false,
|
|
8284
8325
|
awaitingHumanLatched: false,
|
|
@@ -8338,9 +8379,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8338
8379
|
/**
|
|
8339
8380
|
* Register a RE-ADOPTED `processing` message with its session watcher
|
|
8340
8381
|
* (ADR-0046, WI-4). Mirrors `registerInFlight` but anchors the give-up
|
|
8341
|
-
* `deadline` to the
|
|
8342
|
-
* `
|
|
8343
|
-
*
|
|
8382
|
+
* `deadline` to the attempt's SERVER-SIDE `processed_at` (Invariant 1), while
|
|
8383
|
+
* `processingAnchorMs` keeps the absolute-age ceiling tied to the original turn.
|
|
8384
|
+
* The deadline tracks THIS attempt; the ceiling tracks THE TURN. For a fresh
|
|
8385
|
+
* dispatch both anchors are `now`; a restart re-adopt passes the server's
|
|
8386
|
+
* `processed_at` for both, while a re-drive passes the newly stamped attempt time
|
|
8387
|
+
* only for the deadline.
|
|
8344
8388
|
*
|
|
8345
8389
|
* This re-attaches into the SAME watcher, so the ADR-0047 progressing-vs-paused
|
|
8346
8390
|
* give-up (`serviceInFlightMessage`) applies unchanged: a re-adopted turn
|
|
@@ -8361,7 +8405,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8361
8405
|
* server already flipped to `processing`; the running/done transitions still
|
|
8362
8406
|
* fire from the watcher's normal branches.
|
|
8363
8407
|
*/
|
|
8364
|
-
registerReadopted(conv, sessionId, message, opencodeMessageId, processedAtMs) {
|
|
8408
|
+
registerReadopted(conv, sessionId, message, opencodeMessageId, processedAtMs, watchDeadlineAnchorMs) {
|
|
8365
8409
|
let watcher = this.watchers.get(sessionId);
|
|
8366
8410
|
if (!watcher) {
|
|
8367
8411
|
watcher = this.newSessionWatcher(conv);
|
|
@@ -8372,11 +8416,11 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8372
8416
|
opencodeMessageId,
|
|
8373
8417
|
message,
|
|
8374
8418
|
dispatchedAt: this.now(),
|
|
8375
|
-
// Anchor the absolute-age ceiling to the SERVER-SIDE `processed_at
|
|
8376
|
-
//
|
|
8377
|
-
//
|
|
8419
|
+
// Anchor the absolute-age ceiling to the SERVER-SIDE `processed_at`, NOT
|
|
8420
|
+
// `dispatchedAt` — so a re-adopted zombie's age reflects the real turn duration
|
|
8421
|
+
// and the ceiling fires on the ORIGINAL turn.
|
|
8378
8422
|
processingAnchorMs: processedAtMs,
|
|
8379
|
-
deadline:
|
|
8423
|
+
deadline: watchDeadlineAnchorMs + this.pausedMaxWaitMs,
|
|
8380
8424
|
// The server row is ALREADY `processing`; do not re-fire markProcessing.
|
|
8381
8425
|
started: true,
|
|
8382
8426
|
done: false,
|
|
@@ -8393,6 +8437,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8393
8437
|
// with no extra `re_adopted` signal needed (folds old WI-6).
|
|
8394
8438
|
lastAliveAt: 0,
|
|
8395
8439
|
aliveInFlight: false,
|
|
8440
|
+
aliveWithheldSignalled: false,
|
|
8396
8441
|
titleSynced: false,
|
|
8397
8442
|
titleSyncInFlight: false,
|
|
8398
8443
|
awaitingHumanLatched: false,
|
|
@@ -8509,6 +8554,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8509
8554
|
* tracked on the watcher and cleared when it settles; it never rejects (fully
|
|
8510
8555
|
* guarded), so a failed poll/callback can never crash the run loop — the cron
|
|
8511
8556
|
* stays as the safety net.
|
|
8557
|
+
* Returns the branch taken so re-attach telemetry can show whether it left a
|
|
8558
|
+
* ticking loop behind.
|
|
8512
8559
|
*
|
|
8513
8560
|
* The generation started here (#1618) is captured in the `.finally` closure
|
|
8514
8561
|
* so a RETIRED loop settling late — after `reconcileWatchers` has already
|
|
@@ -8522,7 +8569,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8522
8569
|
level: "debug",
|
|
8523
8570
|
message: `No watcher entry exists for session ${sessionId} \u2014 nothing to do`
|
|
8524
8571
|
});
|
|
8525
|
-
return;
|
|
8572
|
+
return "no_watcher";
|
|
8526
8573
|
}
|
|
8527
8574
|
this.ensureSessionErrorStream();
|
|
8528
8575
|
if (watcher.loop) {
|
|
@@ -8531,7 +8578,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8531
8578
|
message: `Watcher loop already running for session ${sessionId}; no-op with ${watcher.inFlight.size} message(s) in flight`,
|
|
8532
8579
|
conversation_id: watcher.conv.id
|
|
8533
8580
|
});
|
|
8534
|
-
return;
|
|
8581
|
+
return "already_running";
|
|
8535
8582
|
}
|
|
8536
8583
|
if (watcher.inFlight.size === 0) {
|
|
8537
8584
|
this.log({
|
|
@@ -8540,7 +8587,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8540
8587
|
conversation_id: watcher.conv.id
|
|
8541
8588
|
});
|
|
8542
8589
|
this.watchers.delete(sessionId);
|
|
8543
|
-
return;
|
|
8590
|
+
return "no_in_flight";
|
|
8544
8591
|
}
|
|
8545
8592
|
const generation = watcher.generation;
|
|
8546
8593
|
this.log({
|
|
@@ -8556,6 +8603,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8556
8603
|
}
|
|
8557
8604
|
});
|
|
8558
8605
|
watcher.loop = loop;
|
|
8606
|
+
return "started";
|
|
8559
8607
|
}
|
|
8560
8608
|
ensureSessionErrorStream() {
|
|
8561
8609
|
if (this.sessionErrorStream || this.stopped) return;
|
|
@@ -9081,7 +9129,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9081
9129
|
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
9082
9130
|
return;
|
|
9083
9131
|
}
|
|
9084
|
-
|
|
9132
|
+
const heartbeatDue = activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS;
|
|
9133
|
+
if (heartbeatDue) {
|
|
9085
9134
|
inFlight.aliveInFlight = true;
|
|
9086
9135
|
this.log({
|
|
9087
9136
|
level: "debug",
|
|
@@ -9091,7 +9140,10 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9091
9140
|
});
|
|
9092
9141
|
void this.postSignal(conv.id, inFlight.evidentMessageId, "alive").then((ok) => {
|
|
9093
9142
|
inFlight.aliveInFlight = false;
|
|
9094
|
-
if (ok)
|
|
9143
|
+
if (ok) {
|
|
9144
|
+
inFlight.lastAliveAt = this.now();
|
|
9145
|
+
inFlight.aliveWithheldSignalled = false;
|
|
9146
|
+
}
|
|
9095
9147
|
this.log({
|
|
9096
9148
|
level: "debug",
|
|
9097
9149
|
message: `Heartbeat POST for message ${inFlight.evidentMessageId} in session ${sessionId} completed with ok=${ok}`,
|
|
@@ -9143,6 +9195,16 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9143
9195
|
inFlight.pausedOnPermission = false;
|
|
9144
9196
|
inFlight.pausedClearConfirmed = false;
|
|
9145
9197
|
}
|
|
9198
|
+
const watchedForMs = this.now() - Math.max(inFlight.lastAliveAt, inFlight.dispatchedAt);
|
|
9199
|
+
if (!heartbeatDue && !inFlight.aliveWithheldSignalled && watchedForMs >= ALIVE_WITHHELD_THRESHOLD_MS) {
|
|
9200
|
+
inFlight.aliveWithheldSignalled = true;
|
|
9201
|
+
const reason = inFlight.awaitingHumanLatched ? "awaiting_human" : inFlight.aliveInFlight ? "alive_in_flight" : "not_running";
|
|
9202
|
+
void this.postSignal(conv.id, inFlight.evidentMessageId, "alive_withheld", {
|
|
9203
|
+
reason,
|
|
9204
|
+
run_state: state,
|
|
9205
|
+
watched_for_ms: watchedForMs
|
|
9206
|
+
});
|
|
9207
|
+
}
|
|
9146
9208
|
const siblingPaused = (sib) => openQuestions.has(sib.evidentMessageId) || openPermissions.has(sib.evidentMessageId) || sib.awaitingHumanLatched || sib.pausedOnQuestion || sib.pausedOnPermission;
|
|
9147
9209
|
const hasActivelyRunningSibling = [...watcher.inFlight.values()].some(
|
|
9148
9210
|
(sib) => sib.evidentMessageId !== inFlight.evidentMessageId && messageRunState(messages, sib.opencodeMessageId) === "running" && !siblingPaused(sib)
|
|
@@ -9153,15 +9215,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9153
9215
|
const attempts = this.recordNeverStartedAttempt(id, sessionId);
|
|
9154
9216
|
if (attempts >= MAX_NEVER_STARTED_ATTEMPTS) {
|
|
9155
9217
|
inFlight.neverStartedResolved = true;
|
|
9156
|
-
const
|
|
9218
|
+
const watchedForMs2 = this.now() - inFlight.dispatchedAt;
|
|
9157
9219
|
this.log({
|
|
9158
9220
|
level: "error",
|
|
9159
|
-
message: `Message ${id.slice(0, 8)} never started after ${attempts} attempts (${
|
|
9221
|
+
message: `Message ${id.slice(0, 8)} never started after ${attempts} attempts (${watchedForMs2}ms watched) \u2014 reporting the run failed; the accepted OpenCode copy was not cancelled`,
|
|
9160
9222
|
conversation_id: conv.id,
|
|
9161
9223
|
message_id: id
|
|
9162
9224
|
});
|
|
9163
9225
|
void this.postSignal(conv.id, id, "never_started_failed", {
|
|
9164
|
-
watched_for_ms:
|
|
9226
|
+
watched_for_ms: watchedForMs2,
|
|
9165
9227
|
attempts
|
|
9166
9228
|
});
|
|
9167
9229
|
await this.settleMessageNeverStarted(sessionId, watcher, inFlight, attempts);
|
|
@@ -9175,7 +9237,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9175
9237
|
message_id: inFlight.evidentMessageId
|
|
9176
9238
|
});
|
|
9177
9239
|
void this.postSignal(conv.id, inFlight.evidentMessageId, "gave_up", {
|
|
9178
|
-
watched_for_ms: this.now() - inFlight.dispatchedAt
|
|
9240
|
+
watched_for_ms: this.now() - inFlight.dispatchedAt,
|
|
9241
|
+
run_state: state
|
|
9179
9242
|
});
|
|
9180
9243
|
this.recordReleasedOpencodeId(
|
|
9181
9244
|
inFlight.evidentMessageId,
|
|
@@ -9632,7 +9695,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9632
9695
|
if ((state === "running" || state === "queued") && ocId) {
|
|
9633
9696
|
const conv = this.convForRow(sessionId, row);
|
|
9634
9697
|
const message = this.queuedMessageForRow(row);
|
|
9635
|
-
|
|
9698
|
+
const processedAtMs = this.processedAtMs(row);
|
|
9699
|
+
this.registerReadopted(conv, sessionId, message, ocId, processedAtMs, processedAtMs);
|
|
9636
9700
|
this.dispatched.add(row.id);
|
|
9637
9701
|
this.readopted.add(row.id);
|
|
9638
9702
|
this.ensureWatcherRunning(sessionId);
|
|
@@ -9927,7 +9991,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9927
9991
|
return;
|
|
9928
9992
|
}
|
|
9929
9993
|
this.unconfirmedDispatchFailures.delete(row.id);
|
|
9930
|
-
|
|
9994
|
+
const processedAtMs = this.processedAtMs(row);
|
|
9995
|
+
this.registerReadopted(
|
|
9996
|
+
readoptConv,
|
|
9997
|
+
sessionId,
|
|
9998
|
+
readoptMessage,
|
|
9999
|
+
ocId,
|
|
10000
|
+
processedAtMs,
|
|
10001
|
+
processedAtMs
|
|
10002
|
+
);
|
|
9931
10003
|
this.dispatched.add(row.id);
|
|
9932
10004
|
this.readopted.add(row.id);
|
|
9933
10005
|
this.awaitingReadopt.delete(row.id);
|
|
@@ -11214,8 +11286,16 @@ function checkNonInteractivePortConflict(port, isPortInUseFn) {
|
|
|
11214
11286
|
);
|
|
11215
11287
|
}
|
|
11216
11288
|
}
|
|
11289
|
+
function unknownPasswordError(port) {
|
|
11290
|
+
return new Error(
|
|
11291
|
+
`OpenCode is already running on port ${port}, but this runner cannot authenticate to it. It may have been started with OPENCODE_SERVER_PASSWORD set in its environment. Free the port or pass --port.`
|
|
11292
|
+
);
|
|
11293
|
+
}
|
|
11217
11294
|
async function ensureOpenCodeRunning(ctx) {
|
|
11218
11295
|
const healthCheck = await checkOpenCodeHealth(ctx.port);
|
|
11296
|
+
if (healthCheck.authFailed) {
|
|
11297
|
+
throw unknownPasswordError(ctx.port);
|
|
11298
|
+
}
|
|
11219
11299
|
if (healthCheck.healthy) {
|
|
11220
11300
|
return {
|
|
11221
11301
|
port: ctx.port,
|
|
@@ -11266,6 +11346,14 @@ async function ensureOpenCodeRunning(ctx) {
|
|
|
11266
11346
|
const proc = await startOpenCode(ctx.port, { inheritStdio: ctx.inheritStdio });
|
|
11267
11347
|
const health = await waitForOpenCodeHealth(ctx.port, ctx.startTimeoutMs);
|
|
11268
11348
|
if (!health.healthy) {
|
|
11349
|
+
if (health.authFailed) {
|
|
11350
|
+
return {
|
|
11351
|
+
port: ctx.port,
|
|
11352
|
+
process: proc,
|
|
11353
|
+
version: null,
|
|
11354
|
+
notReadyReason: "it is answering with an authentication challenge, likely because OPENCODE_SERVER_PASSWORD is set in the environment of the process that started it; a longer start timeout will not resolve this"
|
|
11355
|
+
};
|
|
11356
|
+
}
|
|
11269
11357
|
return {
|
|
11270
11358
|
port: ctx.port,
|
|
11271
11359
|
process: proc,
|
|
@@ -11335,6 +11423,9 @@ Port ${port} is already in use.`));
|
|
|
11335
11423
|
const health = await waitForOpenCodeHealth(port, INTERACTIVE_START_TIMEOUT_MS);
|
|
11336
11424
|
if (!health.healthy) {
|
|
11337
11425
|
spinner.fail("Failed to start OpenCode");
|
|
11426
|
+
if (health.authFailed) {
|
|
11427
|
+
throw unknownPasswordError(port);
|
|
11428
|
+
}
|
|
11338
11429
|
throw new Error("OpenCode failed to start");
|
|
11339
11430
|
}
|
|
11340
11431
|
spinner.stop();
|
|
@@ -11347,35 +11438,16 @@ Port ${port} is already in use.`));
|
|
|
11347
11438
|
import chalk6 from "chalk";
|
|
11348
11439
|
import ora3 from "ora";
|
|
11349
11440
|
import { select as select3 } from "@inquirer/prompts";
|
|
11350
|
-
|
|
11351
|
-
try {
|
|
11352
|
-
const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
|
|
11353
|
-
signal: AbortSignal.timeout(2e3)
|
|
11354
|
-
});
|
|
11355
|
-
if (response.status === 401) {
|
|
11356
|
-
return { healthy: false, authFailed: true, error: "HTTP 401" };
|
|
11357
|
-
}
|
|
11358
|
-
if (!response.ok) {
|
|
11359
|
-
return { healthy: false, error: `HTTP ${response.status}` };
|
|
11360
|
-
}
|
|
11361
|
-
return { healthy: true };
|
|
11362
|
-
} catch (error2) {
|
|
11363
|
-
return {
|
|
11364
|
-
healthy: false,
|
|
11365
|
-
error: error2 instanceof Error ? error2.message : "Unknown error"
|
|
11366
|
-
};
|
|
11367
|
-
}
|
|
11368
|
-
}
|
|
11369
|
-
function unknownPasswordError(port) {
|
|
11441
|
+
function unknownPasswordError2(port) {
|
|
11370
11442
|
return new Error(
|
|
11371
11443
|
`OpenCode V2 (opencode2) is already running on port ${port} with a password this runner doesn't know. Free the port or pass --port.`
|
|
11372
11444
|
);
|
|
11373
11445
|
}
|
|
11374
11446
|
var INTERACTIVE_START_TIMEOUT_MS2 = 3e4;
|
|
11375
11447
|
async function ensureOpenCode2Running(ctx) {
|
|
11376
|
-
const initialHealth = await
|
|
11448
|
+
const initialHealth = await checkOpenCode2Health(ctx.port);
|
|
11377
11449
|
if (initialHealth.authFailed) {
|
|
11378
|
-
throw
|
|
11450
|
+
throw unknownPasswordError2(ctx.port);
|
|
11379
11451
|
}
|
|
11380
11452
|
if (initialHealth.healthy) {
|
|
11381
11453
|
return {
|
|
@@ -11388,7 +11460,7 @@ async function ensureOpenCode2Running(ctx) {
|
|
|
11388
11460
|
}
|
|
11389
11461
|
if (!isOpenCode2Installed()) {
|
|
11390
11462
|
throw new Error(
|
|
11391
|
-
|
|
11463
|
+
`OpenCode V2 (opencode2) is not installed. Install it with: npm install -g ${OPENCODE2_PACKAGE_SPEC}`
|
|
11392
11464
|
);
|
|
11393
11465
|
}
|
|
11394
11466
|
let port = ctx.port;
|
|
@@ -13322,6 +13394,12 @@ async function run(options) {
|
|
|
13322
13394
|
"Skipping session-DB restore: OpenCode is already serving this database",
|
|
13323
13395
|
"debug"
|
|
13324
13396
|
);
|
|
13397
|
+
} else if (health.authFailed) {
|
|
13398
|
+
log2(
|
|
13399
|
+
state,
|
|
13400
|
+
"Skipping session-DB restore: OpenCode answered with an authentication challenge; assuming this database is in use",
|
|
13401
|
+
"debug"
|
|
13402
|
+
);
|
|
13325
13403
|
} else {
|
|
13326
13404
|
const result = await restoreAndVerifySessionDb({
|
|
13327
13405
|
dbPath: sessionDbPath(),
|