@alook/daemon 0.1.50 → 0.1.51
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 +7 -5
- package/dist/cli/index.js +507 -23
- package/dist/index.js +507 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,10 +108,12 @@ without killing the process.
|
|
|
108
108
|
|
|
109
109
|
### Persistent service runtime (OpenCode)
|
|
110
110
|
|
|
111
|
-
OpenCode starts one authenticated, loopback-only
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
OpenCode starts one authenticated, loopback-only service per logical session.
|
|
112
|
+
The actual CLI major selects its adapter: v1 retains `--pure` and durable event
|
|
113
|
+
replay; v2 uses its native API, persisted inbox/messages, and a new idle record
|
|
114
|
+
after all accepted inputs. Root prompts and busy steers share the same service
|
|
115
|
+
and vendor session. Live SSE handles permissions and tool progress. Unknown
|
|
116
|
+
majors or missing native capabilities fail explicitly.
|
|
115
117
|
|
|
116
118
|
---
|
|
117
119
|
|
|
@@ -123,7 +125,7 @@ live stream handles permissions.
|
|
|
123
125
|
| **codex** | persistent session | JSON-RPC 2.0 (`app-server --listen stdio://`) | `safe_boundary_queue` | `initialize` → `thread/start`/`resume` | JSON-RPC notifications |
|
|
124
126
|
| **pi** | persistent session | `@earendil-works/pi-coding-agent` | `steer` | `session.prompt()` | SDK callback |
|
|
125
127
|
| **cursor** | persistent session | ACP JSON-RPC 2.0 (`cursor-agent acp`) | `steer` | `session/prompt` | `session/update` + correlated prompt response |
|
|
126
|
-
| **opencode** | persistent session | authenticated HTTP + SSE (`opencode serve
|
|
128
|
+
| **opencode** | persistent session | authenticated HTTP + SSE (`opencode serve`; v1 adds `--pure`) | `steer` | CLI-major session prompt API | native recovery + live SSE |
|
|
127
129
|
|
|
128
130
|
(Exact launch flags live in `agent-driver/src/adapters/<backend>/`.)
|
|
129
131
|
|
package/dist/cli/index.js
CHANGED
|
@@ -32556,14 +32556,16 @@ class OpenCodeServiceLane {
|
|
|
32556
32556
|
if (!this.identity || this.identity.generation !== generation || this.isProcessClosed(this.identity.process)) {
|
|
32557
32557
|
if (/EADDRINUSE|address already in use/i.test(this.stderrBuffer))
|
|
32558
32558
|
throw new OpenCodePortBindError("OpenCode service port bind raced");
|
|
32559
|
-
|
|
32559
|
+
const process2 = this.identity?.process;
|
|
32560
|
+
const detail = scrubDriverErrorMessage(this.stderrBuffer.replaceAll(this.password, "[redacted]"), "no native stderr");
|
|
32561
|
+
throw new Error(`OpenCode service exited before readiness (code=${process2?.exitCode ?? "unknown"}, signal=${process2?.signalCode ?? "none"}): ${detail}`);
|
|
32560
32562
|
}
|
|
32561
32563
|
try {
|
|
32562
32564
|
const healthTimeoutMs = Math.max(1, Math.min(1000, deadline - Date.now(), this.options.requestTimeoutMs ?? REQUEST_TIMEOUT_MS));
|
|
32563
|
-
const { response, body } = await this.fetchJsonWithTimeout(
|
|
32565
|
+
const { response, body } = await this.fetchJsonWithTimeout(this.healthPath, { method: "GET" }, "health", healthTimeoutMs);
|
|
32564
32566
|
if (response.ok) {
|
|
32565
32567
|
const health = record5(body);
|
|
32566
|
-
if (health
|
|
32568
|
+
if (!this.acceptsHealth(health)) {
|
|
32567
32569
|
throw new OpenCodeIncompatibleError("Installed OpenCode service did not report healthy status");
|
|
32568
32570
|
}
|
|
32569
32571
|
await this.verifyOpenApi();
|
|
@@ -32580,12 +32582,27 @@ class OpenCodeServiceLane {
|
|
|
32580
32582
|
throw new Error("OpenCode service readiness timed out");
|
|
32581
32583
|
}
|
|
32582
32584
|
async verifyOpenApi() {
|
|
32583
|
-
const { response, body } = await this.fetchJsonWithTimeout(
|
|
32585
|
+
const { response, body } = await this.fetchJsonWithTimeout(this.openApiPath, { method: "GET" }, "OpenAPI");
|
|
32584
32586
|
if (!response.ok)
|
|
32585
32587
|
throw new OpenCodeIncompatibleError("Installed OpenCode service does not expose its OpenAPI document");
|
|
32586
32588
|
const document = record5(body);
|
|
32587
32589
|
const paths = record5(document?.paths);
|
|
32588
|
-
const required2 =
|
|
32590
|
+
const required2 = this.requiredApiPaths;
|
|
32591
|
+
if (!paths || required2.some((path10) => !record5(paths[path10]))) {
|
|
32592
|
+
throw new OpenCodeIncompatibleError("Installed OpenCode service is missing required session capabilities");
|
|
32593
|
+
}
|
|
32594
|
+
}
|
|
32595
|
+
get healthPath() {
|
|
32596
|
+
return "/global/health";
|
|
32597
|
+
}
|
|
32598
|
+
get openApiPath() {
|
|
32599
|
+
return "/doc";
|
|
32600
|
+
}
|
|
32601
|
+
acceptsHealth(health) {
|
|
32602
|
+
return health?.healthy === true;
|
|
32603
|
+
}
|
|
32604
|
+
get requiredApiPaths() {
|
|
32605
|
+
return [
|
|
32589
32606
|
"/api/session",
|
|
32590
32607
|
"/api/session/active",
|
|
32591
32608
|
"/api/session/{sessionID}/prompt",
|
|
@@ -32597,12 +32614,12 @@ class OpenCodeServiceLane {
|
|
|
32597
32614
|
"/api/session/{sessionID}/permission/{requestID}/reply",
|
|
32598
32615
|
"/api/event"
|
|
32599
32616
|
];
|
|
32600
|
-
|
|
32601
|
-
|
|
32602
|
-
|
|
32617
|
+
}
|
|
32618
|
+
get selectedModel() {
|
|
32619
|
+
return parseModelRef(resolveLaunchFieldsOrDefault(this.ctx.config.runtimeConfig).model);
|
|
32603
32620
|
}
|
|
32604
32621
|
async openSession(resumeId) {
|
|
32605
|
-
const model =
|
|
32622
|
+
const model = this.selectedModel;
|
|
32606
32623
|
if (resumeId) {
|
|
32607
32624
|
const { response: response2, body: body2 } = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(resumeId)}`, { method: "GET" }, "session resume");
|
|
32608
32625
|
if (response2.status === 404) {
|
|
@@ -32945,7 +32962,7 @@ class OpenCodeServiceLane {
|
|
|
32945
32962
|
async handleLiveEvent(value) {
|
|
32946
32963
|
const event = record5(value);
|
|
32947
32964
|
const data = record5(event?.data);
|
|
32948
|
-
if (event?.type !==
|
|
32965
|
+
if (event?.type !== this.permissionEventType || data?.sessionID !== this.sessionId)
|
|
32949
32966
|
return;
|
|
32950
32967
|
if (typeof data.id !== "string" || !/^per/.test(data.id)) {
|
|
32951
32968
|
throw new OpenCodeProtocolError("OpenCode emitted a malformed permission request");
|
|
@@ -32968,13 +32985,19 @@ class OpenCodeServiceLane {
|
|
|
32968
32985
|
}
|
|
32969
32986
|
}
|
|
32970
32987
|
}
|
|
32988
|
+
get permissionEventType() {
|
|
32989
|
+
return "permission.v2.asked";
|
|
32990
|
+
}
|
|
32991
|
+
get permissionReply() {
|
|
32992
|
+
return { reply: "once" };
|
|
32993
|
+
}
|
|
32971
32994
|
async replyPermission(requestId) {
|
|
32972
32995
|
if (this.handledPermissions.has(requestId))
|
|
32973
32996
|
return;
|
|
32974
32997
|
const existing = this.permissionFlights.get(requestId);
|
|
32975
32998
|
if (existing)
|
|
32976
32999
|
return existing;
|
|
32977
|
-
const flight = this.requestNoContent(`/api/session/${encodeURIComponent(this.sessionId)}/permission/${encodeURIComponent(requestId)}/reply`, "POST",
|
|
33000
|
+
const flight = this.requestNoContent(`/api/session/${encodeURIComponent(this.sessionId)}/permission/${encodeURIComponent(requestId)}/reply`, "POST", this.permissionReply, "permission reply").catch((error62) => {
|
|
32978
33001
|
if (!(error62 instanceof OpenCodeHttpError) || error62.status !== 404)
|
|
32979
33002
|
throw error62;
|
|
32980
33003
|
}).then(() => {
|
|
@@ -33351,6 +33374,28 @@ async function legacyOpenCodeApiEnvironment(environment, model) {
|
|
|
33351
33374
|
return credential?.type === "api" && typeof credential.key === "string" && credential.key.trim() ? { [name]: credential.key } : {};
|
|
33352
33375
|
}
|
|
33353
33376
|
|
|
33377
|
+
// agent-driver/dist/adapters/opencode/compatibility.js
|
|
33378
|
+
var OPEN_CODE_V1 = {
|
|
33379
|
+
major: 1,
|
|
33380
|
+
modelArgs: ["models", "--pure"],
|
|
33381
|
+
serviceArgs: ["serve", "--pure"],
|
|
33382
|
+
sessionListArgs: (maxCount) => ["session", "list", "--format", "json", "--max-count", String(maxCount), "--pure"]
|
|
33383
|
+
};
|
|
33384
|
+
var OPEN_CODE_V2 = {
|
|
33385
|
+
major: 2,
|
|
33386
|
+
modelArgs: ["models", "--standalone"],
|
|
33387
|
+
serviceArgs: ["serve"],
|
|
33388
|
+
sessionListArgs: (maxCount) => ["api", "--standalone", "GET", `/api/session?parentID=null&order=desc&limit=${maxCount}`]
|
|
33389
|
+
};
|
|
33390
|
+
function resolveOpenCodeCompatibility(version3) {
|
|
33391
|
+
const match = version3?.trim().match(/^(?:opencode\s+)?v?(\d+)\.\d+\.\d+(?:[-+][\w.-]+)?$/i);
|
|
33392
|
+
if (match?.[1] === "1")
|
|
33393
|
+
return OPEN_CODE_V1;
|
|
33394
|
+
if (match?.[1] === "2")
|
|
33395
|
+
return OPEN_CODE_V2;
|
|
33396
|
+
throw new Error("Installed OpenCode CLI version is unrecognized or has an unsupported major version");
|
|
33397
|
+
}
|
|
33398
|
+
|
|
33354
33399
|
// agent-driver/dist/adapters/opencode/recent-context.js
|
|
33355
33400
|
var OPENCODE_DISCOVERY_TIMEOUT_MS = 15000;
|
|
33356
33401
|
var OPENCODE_DISCOVERY_OUTPUT_MAX_BYTES = 4 * 1024 * 1024;
|
|
@@ -33384,6 +33429,7 @@ async function discoverOpenCodeRecentContext(request, dependencies = {}) {
|
|
|
33384
33429
|
const collector = new RecentContextCollector(request, "unavailable");
|
|
33385
33430
|
if (collector.satisfied)
|
|
33386
33431
|
return collector.result();
|
|
33432
|
+
const compatibility = dependencies.compatibility ?? OPEN_CODE_V1;
|
|
33387
33433
|
const startedAt = Date.now();
|
|
33388
33434
|
const timeoutMs = dependencies.timeoutMs ?? OPENCODE_DISCOVERY_TIMEOUT_MS;
|
|
33389
33435
|
let remainingOutputBytes = dependencies.outputMaxBytes ?? OPENCODE_DISCOVERY_OUTPUT_MAX_BYTES;
|
|
@@ -33392,7 +33438,7 @@ async function discoverOpenCodeRecentContext(request, dependencies = {}) {
|
|
|
33392
33438
|
const remainingTimeoutMs = timeoutMs - (Date.now() - startedAt);
|
|
33393
33439
|
if (remainingTimeoutMs <= 0)
|
|
33394
33440
|
throw new Error("OpenCode discovery timed out");
|
|
33395
|
-
const spec = resolveSpawnSpec("opencode", [
|
|
33441
|
+
const spec = resolveSpawnSpec("opencode", [...compatibility.sessionListArgs(maxCount)], request.command);
|
|
33396
33442
|
const commandOutput = await collectCommandOutput(spec.command, spec.args, spec.shell, dependencies, remainingTimeoutMs, remainingOutputBytes);
|
|
33397
33443
|
remainingOutputBytes -= commandOutput.outputBytes;
|
|
33398
33444
|
let parsed;
|
|
@@ -33401,14 +33447,17 @@ async function discoverOpenCodeRecentContext(request, dependencies = {}) {
|
|
|
33401
33447
|
} catch {
|
|
33402
33448
|
throw new Error("OpenCode session listing returned invalid JSON");
|
|
33403
33449
|
}
|
|
33450
|
+
if (compatibility.major === 2)
|
|
33451
|
+
parsed = asRecord(parsed)?.data;
|
|
33404
33452
|
if (!Array.isArray(parsed))
|
|
33405
33453
|
throw new Error("OpenCode session listing returned an invalid response");
|
|
33406
33454
|
for (const value of parsed) {
|
|
33407
33455
|
const session2 = asRecord(value);
|
|
33408
33456
|
if (!session2)
|
|
33409
33457
|
continue;
|
|
33410
|
-
const modifiedAt = session2.updated ?? session2.updatedAt;
|
|
33411
|
-
|
|
33458
|
+
const modifiedAt = compatibility.major === 2 ? asRecord(session2.time)?.updated : session2.updated ?? session2.updatedAt;
|
|
33459
|
+
const projectPath = compatibility.major === 2 ? asRecord(session2.location)?.directory : session2.directory;
|
|
33460
|
+
collector.add({ projectPath, modifiedAt });
|
|
33412
33461
|
}
|
|
33413
33462
|
if (collector.satisfied || parsed.length < maxCount)
|
|
33414
33463
|
break;
|
|
@@ -33420,6 +33469,419 @@ async function discoverOpenCodeRecentContext(request, dependencies = {}) {
|
|
|
33420
33469
|
return collector.result();
|
|
33421
33470
|
}
|
|
33422
33471
|
|
|
33472
|
+
// agent-driver/dist/adapters/opencode/v2-service-lane.js
|
|
33473
|
+
class OpenCodeV2ServiceLane extends OpenCodeServiceLane {
|
|
33474
|
+
root = null;
|
|
33475
|
+
v2AdmissionTail = Promise.resolve();
|
|
33476
|
+
v2EvaluationTimer;
|
|
33477
|
+
v2Evaluating = false;
|
|
33478
|
+
projectedMessages = new Set;
|
|
33479
|
+
liveTools = new Map;
|
|
33480
|
+
settledTools = new Set;
|
|
33481
|
+
usage = new SettledUsageProjector;
|
|
33482
|
+
get healthPath() {
|
|
33483
|
+
return "/api/info";
|
|
33484
|
+
}
|
|
33485
|
+
get openApiPath() {
|
|
33486
|
+
return "/openapi.json";
|
|
33487
|
+
}
|
|
33488
|
+
acceptsHealth(health) {
|
|
33489
|
+
if (typeof health?.version !== "string" || !Number.isInteger(health.pid) || Number(health.pid) < 1)
|
|
33490
|
+
return false;
|
|
33491
|
+
try {
|
|
33492
|
+
return resolveOpenCodeCompatibility(health.version).major === 2;
|
|
33493
|
+
} catch {
|
|
33494
|
+
return false;
|
|
33495
|
+
}
|
|
33496
|
+
}
|
|
33497
|
+
get requiredApiPaths() {
|
|
33498
|
+
return [
|
|
33499
|
+
"/api/session",
|
|
33500
|
+
"/api/session/active",
|
|
33501
|
+
"/api/session/{sessionID}",
|
|
33502
|
+
"/api/session/{sessionID}/prompt",
|
|
33503
|
+
"/api/session/{sessionID}/inbox",
|
|
33504
|
+
"/api/session/{sessionID}/message",
|
|
33505
|
+
"/api/session/{sessionID}/message/{messageID}",
|
|
33506
|
+
"/api/session/{sessionID}/interrupt",
|
|
33507
|
+
"/api/session/{sessionID}/model",
|
|
33508
|
+
"/api/session/{sessionID}/permission",
|
|
33509
|
+
"/api/session/{sessionID}/permission/{requestID}/reply",
|
|
33510
|
+
"/api/event"
|
|
33511
|
+
];
|
|
33512
|
+
}
|
|
33513
|
+
get selectedModel() {
|
|
33514
|
+
const model = super.selectedModel;
|
|
33515
|
+
if (typeof model?.id !== "string")
|
|
33516
|
+
return model;
|
|
33517
|
+
const hash2 = model.id.lastIndexOf("#");
|
|
33518
|
+
return hash2 > 0 && hash2 < model.id.length - 1 ? { ...model, id: model.id.slice(0, hash2), variant: model.id.slice(hash2 + 1) } : model;
|
|
33519
|
+
}
|
|
33520
|
+
get permissionEventType() {
|
|
33521
|
+
return "permission.asked";
|
|
33522
|
+
}
|
|
33523
|
+
get permissionReply() {
|
|
33524
|
+
return { decision: "once" };
|
|
33525
|
+
}
|
|
33526
|
+
protocolFailure(message2, cause) {
|
|
33527
|
+
super.protocolFailure(cause === undefined ? message2 : `${message2}: ${scrubDriverErrorMessage(cause)}`);
|
|
33528
|
+
}
|
|
33529
|
+
async primeHistory() {}
|
|
33530
|
+
startStreams() {
|
|
33531
|
+
const identity = this.identity;
|
|
33532
|
+
this.liveTask = this.runLiveStream(identity).catch((error62) => {
|
|
33533
|
+
if (!this.stopping)
|
|
33534
|
+
this.protocolFailure("OpenCode CLI v2 live stream failed", error62);
|
|
33535
|
+
});
|
|
33536
|
+
}
|
|
33537
|
+
async recoverPendingPermissions() {
|
|
33538
|
+
await super.recoverPendingPermissions();
|
|
33539
|
+
const root = this.root;
|
|
33540
|
+
if (root)
|
|
33541
|
+
for (const message2 of await this.messagesAfter(root))
|
|
33542
|
+
this.projectTools(message2);
|
|
33543
|
+
this.schedule();
|
|
33544
|
+
}
|
|
33545
|
+
async handleLiveEvent(value) {
|
|
33546
|
+
await super.handleLiveEvent(value);
|
|
33547
|
+
const event = record5(value);
|
|
33548
|
+
const data = record5(event?.data);
|
|
33549
|
+
if (data?.sessionID !== this.sessionId || !this.root)
|
|
33550
|
+
return;
|
|
33551
|
+
switch (event?.type) {
|
|
33552
|
+
case "session.step.started":
|
|
33553
|
+
this.events.emit("runtime_event", {
|
|
33554
|
+
kind: "internal_progress",
|
|
33555
|
+
source: "opencode.service",
|
|
33556
|
+
itemType: "step_started"
|
|
33557
|
+
});
|
|
33558
|
+
break;
|
|
33559
|
+
case "session.tool.input.started": {
|
|
33560
|
+
const key = this.toolKey(data.assistantMessageID, data.id);
|
|
33561
|
+
if (key && typeof data.name === "string" && !this.liveTools.has(key) && !this.settledTools.has(key)) {
|
|
33562
|
+
this.liveTools.set(key, { name: data.name, called: false });
|
|
33563
|
+
}
|
|
33564
|
+
break;
|
|
33565
|
+
}
|
|
33566
|
+
case "session.tool.called":
|
|
33567
|
+
this.projectTool(data);
|
|
33568
|
+
break;
|
|
33569
|
+
case "session.tool.success":
|
|
33570
|
+
case "session.tool.failed": {
|
|
33571
|
+
this.finishTool(this.toolKey(data.assistantMessageID, data.id));
|
|
33572
|
+
break;
|
|
33573
|
+
}
|
|
33574
|
+
case "session.compaction.started":
|
|
33575
|
+
this.events.emit("runtime_event", { kind: "compaction_started" });
|
|
33576
|
+
break;
|
|
33577
|
+
case "session.compaction.failed":
|
|
33578
|
+
case "session.compaction.ended":
|
|
33579
|
+
this.events.emit("runtime_event", { kind: "compaction_finished" });
|
|
33580
|
+
break;
|
|
33581
|
+
default:
|
|
33582
|
+
break;
|
|
33583
|
+
}
|
|
33584
|
+
this.schedule();
|
|
33585
|
+
}
|
|
33586
|
+
async send(input2) {
|
|
33587
|
+
if (!this.ready || this.stopping || !this.sessionId || !this.identityMatches())
|
|
33588
|
+
return { ok: false, reason: "closed" };
|
|
33589
|
+
if (input2.mode === "idle") {
|
|
33590
|
+
if (this.root)
|
|
33591
|
+
return { ok: false, reason: "runtime_busy" };
|
|
33592
|
+
return this.beginRoot(input2);
|
|
33593
|
+
}
|
|
33594
|
+
const root = this.root;
|
|
33595
|
+
if (!root)
|
|
33596
|
+
return { ok: false, reason: "not_ready" };
|
|
33597
|
+
return this.admit(input2.text, "steer", this.newMessageId(), root);
|
|
33598
|
+
}
|
|
33599
|
+
async beginRoot(input2) {
|
|
33600
|
+
const page = await this.messagePage(undefined, 1);
|
|
33601
|
+
if (this.stopping || !this.identityMatches())
|
|
33602
|
+
return { ok: false, reason: "closed" };
|
|
33603
|
+
const receipt = input2.terminalOwner?.startsWith("msg_") ? input2.terminalOwner : this.newMessageId();
|
|
33604
|
+
const root = {
|
|
33605
|
+
receipt,
|
|
33606
|
+
baseline: typeof page.data[0]?.id === "string" ? page.data[0].id : null,
|
|
33607
|
+
inputs: new Set,
|
|
33608
|
+
generation: 0,
|
|
33609
|
+
pending: 0
|
|
33610
|
+
};
|
|
33611
|
+
this.root = root;
|
|
33612
|
+
return this.admit(input2.text, "queue", receipt, root);
|
|
33613
|
+
}
|
|
33614
|
+
admit(text2, delivery, id3, root) {
|
|
33615
|
+
root.generation += 1;
|
|
33616
|
+
root.pending += 1;
|
|
33617
|
+
const run = this.v2AdmissionTail.then(async () => {
|
|
33618
|
+
await this.waitForLiveStream();
|
|
33619
|
+
if (this.root !== root || this.stopping || !this.identityMatches())
|
|
33620
|
+
return { ok: false, reason: "closed" };
|
|
33621
|
+
const { response, body } = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(this.sessionId)}/prompt`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ id: id3, text: text2, delivery, resume: true }) }, "prompt admission");
|
|
33622
|
+
if (!response.ok)
|
|
33623
|
+
throw new OpenCodeHttpError(response.status, "prompt admission");
|
|
33624
|
+
const input2 = record5(record5(body)?.data);
|
|
33625
|
+
if (input2?.id !== id3 || input2.sessionID !== this.sessionId || input2.type !== "user" || input2.delivery !== delivery) {
|
|
33626
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned an invalid prompt receipt");
|
|
33627
|
+
}
|
|
33628
|
+
if (this.root !== root || this.stopping || !this.identityMatches())
|
|
33629
|
+
return { ok: false, reason: "closed" };
|
|
33630
|
+
root.inputs.add(id3);
|
|
33631
|
+
return { ok: true, acceptedAs: delivery === "steer" ? "steer" : "prompt", receipt: id3 };
|
|
33632
|
+
}).catch((error62) => {
|
|
33633
|
+
if (this.root === root && !this.stopping)
|
|
33634
|
+
this.protocolFailure("OpenCode CLI v2 prompt admission failed", error62);
|
|
33635
|
+
throw error62;
|
|
33636
|
+
}).finally(() => {
|
|
33637
|
+
root.pending -= 1;
|
|
33638
|
+
this.schedule();
|
|
33639
|
+
});
|
|
33640
|
+
this.v2AdmissionTail = run.then(() => {}, () => {});
|
|
33641
|
+
return run;
|
|
33642
|
+
}
|
|
33643
|
+
async messagePage(cursor, limit = 100) {
|
|
33644
|
+
const query = new URLSearchParams({ limit: String(limit), ...cursor ? { cursor } : { order: "desc" } });
|
|
33645
|
+
const { response, body } = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(this.sessionId)}/message?${query}`, { method: "GET" }, "message recovery");
|
|
33646
|
+
if (!response.ok)
|
|
33647
|
+
throw new OpenCodeHttpError(response.status, "message recovery");
|
|
33648
|
+
const page = record5(body);
|
|
33649
|
+
const cursors = record5(page?.cursor);
|
|
33650
|
+
if (!Array.isArray(page?.data) || !cursors || page.data.some((item) => typeof record5(item)?.id !== "string")) {
|
|
33651
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned an invalid message page");
|
|
33652
|
+
}
|
|
33653
|
+
if (cursors.next !== undefined && cursors.next !== null && typeof cursors.next !== "string")
|
|
33654
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned an invalid message cursor");
|
|
33655
|
+
return { data: page.data, next: typeof cursors.next === "string" ? cursors.next : undefined };
|
|
33656
|
+
}
|
|
33657
|
+
async messagesAfter(root) {
|
|
33658
|
+
const messages = [];
|
|
33659
|
+
const cursors = new Set;
|
|
33660
|
+
let cursor;
|
|
33661
|
+
for (;; ) {
|
|
33662
|
+
const page = await this.messagePage(cursor);
|
|
33663
|
+
if (this.root !== root || this.stopping)
|
|
33664
|
+
return [];
|
|
33665
|
+
for (const message2 of page.data) {
|
|
33666
|
+
if (message2.id === root.baseline)
|
|
33667
|
+
return messages.reverse();
|
|
33668
|
+
messages.push(message2);
|
|
33669
|
+
}
|
|
33670
|
+
if (!page.next) {
|
|
33671
|
+
if (root.baseline !== null)
|
|
33672
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 message recovery lost its root boundary");
|
|
33673
|
+
return messages.reverse();
|
|
33674
|
+
}
|
|
33675
|
+
if (cursors.has(page.next))
|
|
33676
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 message cursor did not advance");
|
|
33677
|
+
cursors.add(page.next);
|
|
33678
|
+
cursor = page.next;
|
|
33679
|
+
}
|
|
33680
|
+
}
|
|
33681
|
+
schedule() {
|
|
33682
|
+
if (!this.root || this.stopping || this.v2EvaluationTimer || this.v2Evaluating)
|
|
33683
|
+
return;
|
|
33684
|
+
this.v2EvaluationTimer = setTimeout(() => {
|
|
33685
|
+
this.v2EvaluationTimer = undefined;
|
|
33686
|
+
this.evaluate();
|
|
33687
|
+
}, this.options.activePollMs ?? 100);
|
|
33688
|
+
this.v2EvaluationTimer.unref?.();
|
|
33689
|
+
}
|
|
33690
|
+
async isActive() {
|
|
33691
|
+
const { response, body } = await this.fetchJsonWithTimeout("/api/session/active", { method: "GET" }, "active session query");
|
|
33692
|
+
if (!response.ok)
|
|
33693
|
+
throw new OpenCodeHttpError(response.status, "active session query");
|
|
33694
|
+
const active = record5(record5(body)?.data);
|
|
33695
|
+
if (!active)
|
|
33696
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned invalid active session data");
|
|
33697
|
+
return active[this.sessionId] !== undefined;
|
|
33698
|
+
}
|
|
33699
|
+
async evaluate() {
|
|
33700
|
+
const root = this.root;
|
|
33701
|
+
const identity = this.identity;
|
|
33702
|
+
if (!root || !identity || this.stopping || root.pending > 0 || root.inputs.size === 0 || this.v2Evaluating)
|
|
33703
|
+
return;
|
|
33704
|
+
this.v2Evaluating = true;
|
|
33705
|
+
const generation = root.generation;
|
|
33706
|
+
const current = () => this.root === root && root.generation === generation && root.pending === 0 && this.isIdentity(identity);
|
|
33707
|
+
try {
|
|
33708
|
+
if (await this.isActive())
|
|
33709
|
+
return;
|
|
33710
|
+
if (!current())
|
|
33711
|
+
return;
|
|
33712
|
+
const messages = await this.messagesAfter(root);
|
|
33713
|
+
if (!current())
|
|
33714
|
+
return;
|
|
33715
|
+
let lastInput = -1;
|
|
33716
|
+
const delivered = new Set;
|
|
33717
|
+
for (const [index2, message2] of messages.entries()) {
|
|
33718
|
+
if (message2.type === "user" && typeof message2.id === "string" && root.inputs.has(message2.id)) {
|
|
33719
|
+
delivered.add(message2.id);
|
|
33720
|
+
lastInput = index2;
|
|
33721
|
+
}
|
|
33722
|
+
}
|
|
33723
|
+
const idle = messages.slice(lastInput + 1).reverse().find((message2) => message2.type === "idle");
|
|
33724
|
+
if (delivered.size !== root.inputs.size) {
|
|
33725
|
+
const { response, body } = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(this.sessionId)}/inbox`, { method: "GET" }, "pending input recovery");
|
|
33726
|
+
if (!response.ok)
|
|
33727
|
+
throw new OpenCodeHttpError(response.status, "pending input recovery");
|
|
33728
|
+
const pending = record5(body)?.data;
|
|
33729
|
+
if (!Array.isArray(pending))
|
|
33730
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned invalid inbox data");
|
|
33731
|
+
if (!current())
|
|
33732
|
+
return;
|
|
33733
|
+
const retained = new Set(pending.map((item) => record5(item)?.id));
|
|
33734
|
+
for (const id3 of root.inputs) {
|
|
33735
|
+
if (delivered.has(id3) || retained.has(id3))
|
|
33736
|
+
continue;
|
|
33737
|
+
const recovered = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(this.sessionId)}/message/${encodeURIComponent(id3)}`, { method: "GET" }, "accepted input recovery");
|
|
33738
|
+
if (!current())
|
|
33739
|
+
return;
|
|
33740
|
+
if (recovered.response.status === 404)
|
|
33741
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 lost an accepted input from both inbox and messages");
|
|
33742
|
+
if (!recovered.response.ok)
|
|
33743
|
+
throw new OpenCodeHttpError(recovered.response.status, "accepted input recovery");
|
|
33744
|
+
const message2 = record5(record5(recovered.body)?.data);
|
|
33745
|
+
if (message2?.id !== id3 || message2.type !== "user")
|
|
33746
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned an invalid recovered input");
|
|
33747
|
+
}
|
|
33748
|
+
return;
|
|
33749
|
+
}
|
|
33750
|
+
for (const message2 of messages) {
|
|
33751
|
+
this.projectTools(message2);
|
|
33752
|
+
this.projectMessage(message2);
|
|
33753
|
+
}
|
|
33754
|
+
if (!idle || await this.isActive() || !current())
|
|
33755
|
+
return;
|
|
33756
|
+
if (!["succeeded", "failed", "interrupted"].includes(String(idle.outcome))) {
|
|
33757
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned an invalid idle outcome");
|
|
33758
|
+
}
|
|
33759
|
+
this.root = null;
|
|
33760
|
+
if (idle.outcome === "failed") {
|
|
33761
|
+
const error62 = record5(idle.error);
|
|
33762
|
+
this.events.emit("runtime_event", {
|
|
33763
|
+
kind: "error",
|
|
33764
|
+
code: "opencode.turn_failed",
|
|
33765
|
+
message: typeof error62?.message === "string" ? error62.message : "OpenCode CLI v2 execution failed"
|
|
33766
|
+
});
|
|
33767
|
+
}
|
|
33768
|
+
this.events.emit("runtime_event", {
|
|
33769
|
+
kind: "turn_end",
|
|
33770
|
+
sessionId: this.sessionId,
|
|
33771
|
+
turnOwner: root.receipt
|
|
33772
|
+
});
|
|
33773
|
+
} catch (error62) {
|
|
33774
|
+
if (!current() || this.stopping)
|
|
33775
|
+
return;
|
|
33776
|
+
if (error62 instanceof OpenCodeProtocolError || error62 instanceof OpenCodeHttpError && error62.status < 500) {
|
|
33777
|
+
this.protocolFailure("OpenCode CLI v2 durable message recovery failed", error62);
|
|
33778
|
+
} else
|
|
33779
|
+
this.diagnostic("warning", "OpenCode CLI v2 message recovery temporarily unavailable; keeping the root active");
|
|
33780
|
+
} finally {
|
|
33781
|
+
this.v2Evaluating = false;
|
|
33782
|
+
this.schedule();
|
|
33783
|
+
}
|
|
33784
|
+
}
|
|
33785
|
+
toolKey(messageId, id3) {
|
|
33786
|
+
return typeof messageId === "string" && typeof id3 === "string" ? `${messageId}:${id3}` : undefined;
|
|
33787
|
+
}
|
|
33788
|
+
projectTool(data) {
|
|
33789
|
+
const key = this.toolKey(data.assistantMessageID, data.id);
|
|
33790
|
+
if (!key || this.settledTools.has(key))
|
|
33791
|
+
return;
|
|
33792
|
+
const tool = this.liveTools.get(key) ?? { name: typeof data.name === "string" ? data.name : "OpenCode tool", called: false };
|
|
33793
|
+
if (tool.called)
|
|
33794
|
+
return;
|
|
33795
|
+
tool.called = true;
|
|
33796
|
+
this.liveTools.set(key, tool);
|
|
33797
|
+
this.events.emit("runtime_event", { kind: "tool_call", callId: key, name: tool.name, input: data.input });
|
|
33798
|
+
}
|
|
33799
|
+
finishTool(key) {
|
|
33800
|
+
if (!key || this.settledTools.has(key))
|
|
33801
|
+
return;
|
|
33802
|
+
const tool = this.liveTools.get(key);
|
|
33803
|
+
if (!tool?.called)
|
|
33804
|
+
return;
|
|
33805
|
+
this.liveTools.delete(key);
|
|
33806
|
+
this.settledTools.add(key);
|
|
33807
|
+
this.events.emit("runtime_event", { kind: "tool_output", callId: key, name: tool.name });
|
|
33808
|
+
}
|
|
33809
|
+
projectTools(message2) {
|
|
33810
|
+
if (message2.type !== "assistant" || !Array.isArray(message2.content) || !this.root || this.stopping)
|
|
33811
|
+
return;
|
|
33812
|
+
for (const value of message2.content) {
|
|
33813
|
+
const tool = record5(value);
|
|
33814
|
+
const state = record5(tool?.state);
|
|
33815
|
+
if (tool?.type !== "tool" || !["running", "completed", "error"].includes(String(state?.status)))
|
|
33816
|
+
continue;
|
|
33817
|
+
this.projectTool({ assistantMessageID: message2.id, id: tool.id, name: tool.name, input: state?.input });
|
|
33818
|
+
if (state?.status !== "running")
|
|
33819
|
+
this.finishTool(this.toolKey(message2.id, tool.id));
|
|
33820
|
+
}
|
|
33821
|
+
}
|
|
33822
|
+
projectMessage(message2) {
|
|
33823
|
+
if (message2.type !== "assistant" || typeof message2.id !== "string" || this.projectedMessages.has(message2.id))
|
|
33824
|
+
return;
|
|
33825
|
+
const time3 = record5(message2.time);
|
|
33826
|
+
if (time3?.completed === undefined)
|
|
33827
|
+
return;
|
|
33828
|
+
this.projectedMessages.add(message2.id);
|
|
33829
|
+
if (Array.isArray(message2.content))
|
|
33830
|
+
for (const value of message2.content) {
|
|
33831
|
+
const content = record5(value);
|
|
33832
|
+
if (typeof content?.text !== "string" || !content.text)
|
|
33833
|
+
continue;
|
|
33834
|
+
if (content.type === "text")
|
|
33835
|
+
this.events.emit("runtime_event", { kind: "assistant_message_completed", text: content.text });
|
|
33836
|
+
if (content.type === "reasoning")
|
|
33837
|
+
this.events.emit("runtime_event", { kind: "assistant_reasoning_completed", text: content.text });
|
|
33838
|
+
}
|
|
33839
|
+
const tokens = record5(message2.tokens);
|
|
33840
|
+
if (!tokens || !this.sessionId)
|
|
33841
|
+
return;
|
|
33842
|
+
const cache = record5(tokens.cache);
|
|
33843
|
+
const identity = { runtime: "opencode", backendSessionId: this.sessionId, providerRecordId: message2.id };
|
|
33844
|
+
const usage = this.usage.project({
|
|
33845
|
+
...identity,
|
|
33846
|
+
source: "opencode.v2",
|
|
33847
|
+
input: tokens.input,
|
|
33848
|
+
output: tokens.output,
|
|
33849
|
+
reasoning: tokens.reasoning,
|
|
33850
|
+
cacheRead: cache?.read,
|
|
33851
|
+
cacheWrite: cache?.write,
|
|
33852
|
+
inputIncludesCache: false,
|
|
33853
|
+
outputIncludesReasoning: false
|
|
33854
|
+
});
|
|
33855
|
+
if (usage)
|
|
33856
|
+
this.events.emit("runtime_event", usage);
|
|
33857
|
+
this.usage.release(identity);
|
|
33858
|
+
}
|
|
33859
|
+
async interrupt(_input) {
|
|
33860
|
+
const root = this.root;
|
|
33861
|
+
if (!root || !this.ready || this.stopping || !this.identityMatches())
|
|
33862
|
+
return false;
|
|
33863
|
+
root.generation += 1;
|
|
33864
|
+
root.pending += 1;
|
|
33865
|
+
try {
|
|
33866
|
+
await this.requestNoContent(`/api/session/${encodeURIComponent(this.sessionId)}/interrupt?resume=false`, "POST", undefined, "session interrupt");
|
|
33867
|
+
return true;
|
|
33868
|
+
} catch (error62) {
|
|
33869
|
+
if (!this.stopping)
|
|
33870
|
+
this.stop({ reason: "interrupt_timeout", forceAfterMs: 0 });
|
|
33871
|
+
throw error62;
|
|
33872
|
+
} finally {
|
|
33873
|
+
root.pending -= 1;
|
|
33874
|
+
this.schedule();
|
|
33875
|
+
}
|
|
33876
|
+
}
|
|
33877
|
+
stop(input2 = {}) {
|
|
33878
|
+
this.root = null;
|
|
33879
|
+
if (this.v2EvaluationTimer)
|
|
33880
|
+
clearTimeout(this.v2EvaluationTimer);
|
|
33881
|
+
return super.stop(input2);
|
|
33882
|
+
}
|
|
33883
|
+
}
|
|
33884
|
+
|
|
33423
33885
|
// agent-driver/dist/adapters/opencode/index.js
|
|
33424
33886
|
function createOpenCodeMessageId() {
|
|
33425
33887
|
return `msg_${randomBytes4(16).toString("hex")}`;
|
|
@@ -33427,6 +33889,7 @@ function createOpenCodeMessageId() {
|
|
|
33427
33889
|
|
|
33428
33890
|
class OpenCodeDriver {
|
|
33429
33891
|
outputProbe;
|
|
33892
|
+
versionProbe;
|
|
33430
33893
|
id = "opencode";
|
|
33431
33894
|
instructionDelivery = { kind: "workspace_file", canonical: "AGENTS.md", aliases: ["CLAUDE.md"] };
|
|
33432
33895
|
execution = {
|
|
@@ -33435,17 +33898,29 @@ class OpenCodeDriver {
|
|
|
33435
33898
|
wakeStart: "immediate",
|
|
33436
33899
|
terminalOwnership: "transport_request"
|
|
33437
33900
|
};
|
|
33438
|
-
constructor(outputProbe = probeCommandOutput) {
|
|
33901
|
+
constructor(outputProbe = probeCommandOutput, versionProbe = probeCliRuntime) {
|
|
33439
33902
|
this.outputProbe = outputProbe;
|
|
33903
|
+
this.versionProbe = versionProbe;
|
|
33440
33904
|
}
|
|
33441
33905
|
discoverRecentContext(request) {
|
|
33442
|
-
|
|
33906
|
+
if (request.recentProjectsTopK === 0)
|
|
33907
|
+
return discoverOpenCodeRecentContext(request);
|
|
33908
|
+
const result = this.versionProbe("opencode", {}, request.command);
|
|
33909
|
+
if (result.status !== "healthy")
|
|
33910
|
+
throw new Error("Installed OpenCode CLI could not report its version");
|
|
33911
|
+
return discoverOpenCodeRecentContext(request, { compatibility: resolveOpenCodeCompatibility(result.version) });
|
|
33443
33912
|
}
|
|
33444
33913
|
probe(command) {
|
|
33445
|
-
const result =
|
|
33914
|
+
const result = this.versionProbe("opencode", {}, command);
|
|
33446
33915
|
if (result.status !== "healthy")
|
|
33447
33916
|
return result;
|
|
33448
|
-
|
|
33917
|
+
let compatibility;
|
|
33918
|
+
try {
|
|
33919
|
+
compatibility = resolveOpenCodeCompatibility(result.version);
|
|
33920
|
+
} catch {
|
|
33921
|
+
return { ...result, status: "unhealthy", lastError: "unsupported_opencode_cli_version" };
|
|
33922
|
+
}
|
|
33923
|
+
const spec = resolveSpawnSpec("opencode", [...compatibility.modelArgs], command);
|
|
33449
33924
|
const output2 = this.outputProbe(spec.command, spec.args);
|
|
33450
33925
|
return {
|
|
33451
33926
|
...result,
|
|
@@ -33456,20 +33931,29 @@ class OpenCodeDriver {
|
|
|
33456
33931
|
return createOpenCodeMessageId();
|
|
33457
33932
|
}
|
|
33458
33933
|
async openLane(ctx, options) {
|
|
33459
|
-
|
|
33934
|
+
const command = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig).command;
|
|
33935
|
+
const result = this.versionProbe("opencode", {}, command);
|
|
33936
|
+
if (result.status !== "healthy")
|
|
33937
|
+
throw new Error("Installed OpenCode CLI could not report its version");
|
|
33938
|
+
const compatibility = resolveOpenCodeCompatibility(result.version);
|
|
33939
|
+
const factory = {
|
|
33940
|
+
spawnService: (launch, port, password) => this.spawnService(launch, port, password, compatibility)
|
|
33941
|
+
};
|
|
33942
|
+
const Lane = compatibility.major === 1 ? OpenCodeServiceLane : OpenCodeV2ServiceLane;
|
|
33943
|
+
return new Lane(factory, ctx, {
|
|
33460
33944
|
onRawStdoutLine: options?.onRawStdoutLine
|
|
33461
33945
|
});
|
|
33462
33946
|
}
|
|
33463
|
-
async spawnService(ctx, port, password) {
|
|
33947
|
+
async spawnService(ctx, port, password, compatibility = OPEN_CODE_V1) {
|
|
33464
33948
|
const { spawnEnv } = await prepareCliTransport(ctx, {
|
|
33465
|
-
OPENCODE_SERVER_PASSWORD: password
|
|
33949
|
+
OPENCODE_SERVER_PASSWORD: password,
|
|
33950
|
+
...compatibility.major === 2 ? { OPENCODE_PASSWORD: password } : {}
|
|
33466
33951
|
});
|
|
33467
33952
|
const fields2 = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
33468
33953
|
Object.assign(spawnEnv, await legacyOpenCodeApiEnvironment(spawnEnv, fields2.model));
|
|
33469
33954
|
const override = fields2.command;
|
|
33470
33955
|
const args = [
|
|
33471
|
-
|
|
33472
|
-
"--pure",
|
|
33956
|
+
...compatibility.serviceArgs,
|
|
33473
33957
|
"--hostname",
|
|
33474
33958
|
"127.0.0.1",
|
|
33475
33959
|
"--port",
|
package/dist/index.js
CHANGED
|
@@ -7065,14 +7065,16 @@ class OpenCodeServiceLane {
|
|
|
7065
7065
|
if (!this.identity || this.identity.generation !== generation || this.isProcessClosed(this.identity.process)) {
|
|
7066
7066
|
if (/EADDRINUSE|address already in use/i.test(this.stderrBuffer))
|
|
7067
7067
|
throw new OpenCodePortBindError("OpenCode service port bind raced");
|
|
7068
|
-
|
|
7068
|
+
const process2 = this.identity?.process;
|
|
7069
|
+
const detail = scrubDriverErrorMessage(this.stderrBuffer.replaceAll(this.password, "[redacted]"), "no native stderr");
|
|
7070
|
+
throw new Error(`OpenCode service exited before readiness (code=${process2?.exitCode ?? "unknown"}, signal=${process2?.signalCode ?? "none"}): ${detail}`);
|
|
7069
7071
|
}
|
|
7070
7072
|
try {
|
|
7071
7073
|
const healthTimeoutMs = Math.max(1, Math.min(1000, deadline - Date.now(), this.options.requestTimeoutMs ?? REQUEST_TIMEOUT_MS));
|
|
7072
|
-
const { response, body } = await this.fetchJsonWithTimeout(
|
|
7074
|
+
const { response, body } = await this.fetchJsonWithTimeout(this.healthPath, { method: "GET" }, "health", healthTimeoutMs);
|
|
7073
7075
|
if (response.ok) {
|
|
7074
7076
|
const health = record4(body);
|
|
7075
|
-
if (health
|
|
7077
|
+
if (!this.acceptsHealth(health)) {
|
|
7076
7078
|
throw new OpenCodeIncompatibleError("Installed OpenCode service did not report healthy status");
|
|
7077
7079
|
}
|
|
7078
7080
|
await this.verifyOpenApi();
|
|
@@ -7089,12 +7091,27 @@ class OpenCodeServiceLane {
|
|
|
7089
7091
|
throw new Error("OpenCode service readiness timed out");
|
|
7090
7092
|
}
|
|
7091
7093
|
async verifyOpenApi() {
|
|
7092
|
-
const { response, body } = await this.fetchJsonWithTimeout(
|
|
7094
|
+
const { response, body } = await this.fetchJsonWithTimeout(this.openApiPath, { method: "GET" }, "OpenAPI");
|
|
7093
7095
|
if (!response.ok)
|
|
7094
7096
|
throw new OpenCodeIncompatibleError("Installed OpenCode service does not expose its OpenAPI document");
|
|
7095
7097
|
const document = record4(body);
|
|
7096
7098
|
const paths = record4(document?.paths);
|
|
7097
|
-
const required =
|
|
7099
|
+
const required = this.requiredApiPaths;
|
|
7100
|
+
if (!paths || required.some((path9) => !record4(paths[path9]))) {
|
|
7101
|
+
throw new OpenCodeIncompatibleError("Installed OpenCode service is missing required session capabilities");
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
get healthPath() {
|
|
7105
|
+
return "/global/health";
|
|
7106
|
+
}
|
|
7107
|
+
get openApiPath() {
|
|
7108
|
+
return "/doc";
|
|
7109
|
+
}
|
|
7110
|
+
acceptsHealth(health) {
|
|
7111
|
+
return health?.healthy === true;
|
|
7112
|
+
}
|
|
7113
|
+
get requiredApiPaths() {
|
|
7114
|
+
return [
|
|
7098
7115
|
"/api/session",
|
|
7099
7116
|
"/api/session/active",
|
|
7100
7117
|
"/api/session/{sessionID}/prompt",
|
|
@@ -7106,12 +7123,12 @@ class OpenCodeServiceLane {
|
|
|
7106
7123
|
"/api/session/{sessionID}/permission/{requestID}/reply",
|
|
7107
7124
|
"/api/event"
|
|
7108
7125
|
];
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7126
|
+
}
|
|
7127
|
+
get selectedModel() {
|
|
7128
|
+
return parseModelRef(resolveLaunchFieldsOrDefault(this.ctx.config.runtimeConfig).model);
|
|
7112
7129
|
}
|
|
7113
7130
|
async openSession(resumeId) {
|
|
7114
|
-
const model =
|
|
7131
|
+
const model = this.selectedModel;
|
|
7115
7132
|
if (resumeId) {
|
|
7116
7133
|
const { response: response2, body: body2 } = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(resumeId)}`, { method: "GET" }, "session resume");
|
|
7117
7134
|
if (response2.status === 404) {
|
|
@@ -7454,7 +7471,7 @@ class OpenCodeServiceLane {
|
|
|
7454
7471
|
async handleLiveEvent(value) {
|
|
7455
7472
|
const event = record4(value);
|
|
7456
7473
|
const data = record4(event?.data);
|
|
7457
|
-
if (event?.type !==
|
|
7474
|
+
if (event?.type !== this.permissionEventType || data?.sessionID !== this.sessionId)
|
|
7458
7475
|
return;
|
|
7459
7476
|
if (typeof data.id !== "string" || !/^per/.test(data.id)) {
|
|
7460
7477
|
throw new OpenCodeProtocolError("OpenCode emitted a malformed permission request");
|
|
@@ -7477,13 +7494,19 @@ class OpenCodeServiceLane {
|
|
|
7477
7494
|
}
|
|
7478
7495
|
}
|
|
7479
7496
|
}
|
|
7497
|
+
get permissionEventType() {
|
|
7498
|
+
return "permission.v2.asked";
|
|
7499
|
+
}
|
|
7500
|
+
get permissionReply() {
|
|
7501
|
+
return { reply: "once" };
|
|
7502
|
+
}
|
|
7480
7503
|
async replyPermission(requestId) {
|
|
7481
7504
|
if (this.handledPermissions.has(requestId))
|
|
7482
7505
|
return;
|
|
7483
7506
|
const existing = this.permissionFlights.get(requestId);
|
|
7484
7507
|
if (existing)
|
|
7485
7508
|
return existing;
|
|
7486
|
-
const flight = this.requestNoContent(`/api/session/${encodeURIComponent(this.sessionId)}/permission/${encodeURIComponent(requestId)}/reply`, "POST",
|
|
7509
|
+
const flight = this.requestNoContent(`/api/session/${encodeURIComponent(this.sessionId)}/permission/${encodeURIComponent(requestId)}/reply`, "POST", this.permissionReply, "permission reply").catch((error) => {
|
|
7487
7510
|
if (!(error instanceof OpenCodeHttpError) || error.status !== 404)
|
|
7488
7511
|
throw error;
|
|
7489
7512
|
}).then(() => {
|
|
@@ -7860,6 +7883,28 @@ async function legacyOpenCodeApiEnvironment(environment, model) {
|
|
|
7860
7883
|
return credential?.type === "api" && typeof credential.key === "string" && credential.key.trim() ? { [name]: credential.key } : {};
|
|
7861
7884
|
}
|
|
7862
7885
|
|
|
7886
|
+
// agent-driver/dist/adapters/opencode/compatibility.js
|
|
7887
|
+
var OPEN_CODE_V1 = {
|
|
7888
|
+
major: 1,
|
|
7889
|
+
modelArgs: ["models", "--pure"],
|
|
7890
|
+
serviceArgs: ["serve", "--pure"],
|
|
7891
|
+
sessionListArgs: (maxCount) => ["session", "list", "--format", "json", "--max-count", String(maxCount), "--pure"]
|
|
7892
|
+
};
|
|
7893
|
+
var OPEN_CODE_V2 = {
|
|
7894
|
+
major: 2,
|
|
7895
|
+
modelArgs: ["models", "--standalone"],
|
|
7896
|
+
serviceArgs: ["serve"],
|
|
7897
|
+
sessionListArgs: (maxCount) => ["api", "--standalone", "GET", `/api/session?parentID=null&order=desc&limit=${maxCount}`]
|
|
7898
|
+
};
|
|
7899
|
+
function resolveOpenCodeCompatibility(version) {
|
|
7900
|
+
const match = version?.trim().match(/^(?:opencode\s+)?v?(\d+)\.\d+\.\d+(?:[-+][\w.-]+)?$/i);
|
|
7901
|
+
if (match?.[1] === "1")
|
|
7902
|
+
return OPEN_CODE_V1;
|
|
7903
|
+
if (match?.[1] === "2")
|
|
7904
|
+
return OPEN_CODE_V2;
|
|
7905
|
+
throw new Error("Installed OpenCode CLI version is unrecognized or has an unsupported major version");
|
|
7906
|
+
}
|
|
7907
|
+
|
|
7863
7908
|
// agent-driver/dist/adapters/opencode/recent-context.js
|
|
7864
7909
|
var OPENCODE_DISCOVERY_TIMEOUT_MS = 15000;
|
|
7865
7910
|
var OPENCODE_DISCOVERY_OUTPUT_MAX_BYTES = 4 * 1024 * 1024;
|
|
@@ -7893,6 +7938,7 @@ async function discoverOpenCodeRecentContext(request, dependencies = {}) {
|
|
|
7893
7938
|
const collector = new RecentContextCollector(request, "unavailable");
|
|
7894
7939
|
if (collector.satisfied)
|
|
7895
7940
|
return collector.result();
|
|
7941
|
+
const compatibility = dependencies.compatibility ?? OPEN_CODE_V1;
|
|
7896
7942
|
const startedAt = Date.now();
|
|
7897
7943
|
const timeoutMs = dependencies.timeoutMs ?? OPENCODE_DISCOVERY_TIMEOUT_MS;
|
|
7898
7944
|
let remainingOutputBytes = dependencies.outputMaxBytes ?? OPENCODE_DISCOVERY_OUTPUT_MAX_BYTES;
|
|
@@ -7901,7 +7947,7 @@ async function discoverOpenCodeRecentContext(request, dependencies = {}) {
|
|
|
7901
7947
|
const remainingTimeoutMs = timeoutMs - (Date.now() - startedAt);
|
|
7902
7948
|
if (remainingTimeoutMs <= 0)
|
|
7903
7949
|
throw new Error("OpenCode discovery timed out");
|
|
7904
|
-
const spec = resolveSpawnSpec("opencode", [
|
|
7950
|
+
const spec = resolveSpawnSpec("opencode", [...compatibility.sessionListArgs(maxCount)], request.command);
|
|
7905
7951
|
const commandOutput = await collectCommandOutput(spec.command, spec.args, spec.shell, dependencies, remainingTimeoutMs, remainingOutputBytes);
|
|
7906
7952
|
remainingOutputBytes -= commandOutput.outputBytes;
|
|
7907
7953
|
let parsed;
|
|
@@ -7910,14 +7956,17 @@ async function discoverOpenCodeRecentContext(request, dependencies = {}) {
|
|
|
7910
7956
|
} catch {
|
|
7911
7957
|
throw new Error("OpenCode session listing returned invalid JSON");
|
|
7912
7958
|
}
|
|
7959
|
+
if (compatibility.major === 2)
|
|
7960
|
+
parsed = asRecord(parsed)?.data;
|
|
7913
7961
|
if (!Array.isArray(parsed))
|
|
7914
7962
|
throw new Error("OpenCode session listing returned an invalid response");
|
|
7915
7963
|
for (const value of parsed) {
|
|
7916
7964
|
const session = asRecord(value);
|
|
7917
7965
|
if (!session)
|
|
7918
7966
|
continue;
|
|
7919
|
-
const modifiedAt = session.updated ?? session.updatedAt;
|
|
7920
|
-
|
|
7967
|
+
const modifiedAt = compatibility.major === 2 ? asRecord(session.time)?.updated : session.updated ?? session.updatedAt;
|
|
7968
|
+
const projectPath = compatibility.major === 2 ? asRecord(session.location)?.directory : session.directory;
|
|
7969
|
+
collector.add({ projectPath, modifiedAt });
|
|
7921
7970
|
}
|
|
7922
7971
|
if (collector.satisfied || parsed.length < maxCount)
|
|
7923
7972
|
break;
|
|
@@ -7929,6 +7978,419 @@ async function discoverOpenCodeRecentContext(request, dependencies = {}) {
|
|
|
7929
7978
|
return collector.result();
|
|
7930
7979
|
}
|
|
7931
7980
|
|
|
7981
|
+
// agent-driver/dist/adapters/opencode/v2-service-lane.js
|
|
7982
|
+
class OpenCodeV2ServiceLane extends OpenCodeServiceLane {
|
|
7983
|
+
root = null;
|
|
7984
|
+
v2AdmissionTail = Promise.resolve();
|
|
7985
|
+
v2EvaluationTimer;
|
|
7986
|
+
v2Evaluating = false;
|
|
7987
|
+
projectedMessages = new Set;
|
|
7988
|
+
liveTools = new Map;
|
|
7989
|
+
settledTools = new Set;
|
|
7990
|
+
usage = new SettledUsageProjector;
|
|
7991
|
+
get healthPath() {
|
|
7992
|
+
return "/api/info";
|
|
7993
|
+
}
|
|
7994
|
+
get openApiPath() {
|
|
7995
|
+
return "/openapi.json";
|
|
7996
|
+
}
|
|
7997
|
+
acceptsHealth(health) {
|
|
7998
|
+
if (typeof health?.version !== "string" || !Number.isInteger(health.pid) || Number(health.pid) < 1)
|
|
7999
|
+
return false;
|
|
8000
|
+
try {
|
|
8001
|
+
return resolveOpenCodeCompatibility(health.version).major === 2;
|
|
8002
|
+
} catch {
|
|
8003
|
+
return false;
|
|
8004
|
+
}
|
|
8005
|
+
}
|
|
8006
|
+
get requiredApiPaths() {
|
|
8007
|
+
return [
|
|
8008
|
+
"/api/session",
|
|
8009
|
+
"/api/session/active",
|
|
8010
|
+
"/api/session/{sessionID}",
|
|
8011
|
+
"/api/session/{sessionID}/prompt",
|
|
8012
|
+
"/api/session/{sessionID}/inbox",
|
|
8013
|
+
"/api/session/{sessionID}/message",
|
|
8014
|
+
"/api/session/{sessionID}/message/{messageID}",
|
|
8015
|
+
"/api/session/{sessionID}/interrupt",
|
|
8016
|
+
"/api/session/{sessionID}/model",
|
|
8017
|
+
"/api/session/{sessionID}/permission",
|
|
8018
|
+
"/api/session/{sessionID}/permission/{requestID}/reply",
|
|
8019
|
+
"/api/event"
|
|
8020
|
+
];
|
|
8021
|
+
}
|
|
8022
|
+
get selectedModel() {
|
|
8023
|
+
const model = super.selectedModel;
|
|
8024
|
+
if (typeof model?.id !== "string")
|
|
8025
|
+
return model;
|
|
8026
|
+
const hash = model.id.lastIndexOf("#");
|
|
8027
|
+
return hash > 0 && hash < model.id.length - 1 ? { ...model, id: model.id.slice(0, hash), variant: model.id.slice(hash + 1) } : model;
|
|
8028
|
+
}
|
|
8029
|
+
get permissionEventType() {
|
|
8030
|
+
return "permission.asked";
|
|
8031
|
+
}
|
|
8032
|
+
get permissionReply() {
|
|
8033
|
+
return { decision: "once" };
|
|
8034
|
+
}
|
|
8035
|
+
protocolFailure(message, cause) {
|
|
8036
|
+
super.protocolFailure(cause === undefined ? message : `${message}: ${scrubDriverErrorMessage(cause)}`);
|
|
8037
|
+
}
|
|
8038
|
+
async primeHistory() {}
|
|
8039
|
+
startStreams() {
|
|
8040
|
+
const identity = this.identity;
|
|
8041
|
+
this.liveTask = this.runLiveStream(identity).catch((error) => {
|
|
8042
|
+
if (!this.stopping)
|
|
8043
|
+
this.protocolFailure("OpenCode CLI v2 live stream failed", error);
|
|
8044
|
+
});
|
|
8045
|
+
}
|
|
8046
|
+
async recoverPendingPermissions() {
|
|
8047
|
+
await super.recoverPendingPermissions();
|
|
8048
|
+
const root = this.root;
|
|
8049
|
+
if (root)
|
|
8050
|
+
for (const message of await this.messagesAfter(root))
|
|
8051
|
+
this.projectTools(message);
|
|
8052
|
+
this.schedule();
|
|
8053
|
+
}
|
|
8054
|
+
async handleLiveEvent(value) {
|
|
8055
|
+
await super.handleLiveEvent(value);
|
|
8056
|
+
const event = record4(value);
|
|
8057
|
+
const data = record4(event?.data);
|
|
8058
|
+
if (data?.sessionID !== this.sessionId || !this.root)
|
|
8059
|
+
return;
|
|
8060
|
+
switch (event?.type) {
|
|
8061
|
+
case "session.step.started":
|
|
8062
|
+
this.events.emit("runtime_event", {
|
|
8063
|
+
kind: "internal_progress",
|
|
8064
|
+
source: "opencode.service",
|
|
8065
|
+
itemType: "step_started"
|
|
8066
|
+
});
|
|
8067
|
+
break;
|
|
8068
|
+
case "session.tool.input.started": {
|
|
8069
|
+
const key = this.toolKey(data.assistantMessageID, data.id);
|
|
8070
|
+
if (key && typeof data.name === "string" && !this.liveTools.has(key) && !this.settledTools.has(key)) {
|
|
8071
|
+
this.liveTools.set(key, { name: data.name, called: false });
|
|
8072
|
+
}
|
|
8073
|
+
break;
|
|
8074
|
+
}
|
|
8075
|
+
case "session.tool.called":
|
|
8076
|
+
this.projectTool(data);
|
|
8077
|
+
break;
|
|
8078
|
+
case "session.tool.success":
|
|
8079
|
+
case "session.tool.failed": {
|
|
8080
|
+
this.finishTool(this.toolKey(data.assistantMessageID, data.id));
|
|
8081
|
+
break;
|
|
8082
|
+
}
|
|
8083
|
+
case "session.compaction.started":
|
|
8084
|
+
this.events.emit("runtime_event", { kind: "compaction_started" });
|
|
8085
|
+
break;
|
|
8086
|
+
case "session.compaction.failed":
|
|
8087
|
+
case "session.compaction.ended":
|
|
8088
|
+
this.events.emit("runtime_event", { kind: "compaction_finished" });
|
|
8089
|
+
break;
|
|
8090
|
+
default:
|
|
8091
|
+
break;
|
|
8092
|
+
}
|
|
8093
|
+
this.schedule();
|
|
8094
|
+
}
|
|
8095
|
+
async send(input) {
|
|
8096
|
+
if (!this.ready || this.stopping || !this.sessionId || !this.identityMatches())
|
|
8097
|
+
return { ok: false, reason: "closed" };
|
|
8098
|
+
if (input.mode === "idle") {
|
|
8099
|
+
if (this.root)
|
|
8100
|
+
return { ok: false, reason: "runtime_busy" };
|
|
8101
|
+
return this.beginRoot(input);
|
|
8102
|
+
}
|
|
8103
|
+
const root = this.root;
|
|
8104
|
+
if (!root)
|
|
8105
|
+
return { ok: false, reason: "not_ready" };
|
|
8106
|
+
return this.admit(input.text, "steer", this.newMessageId(), root);
|
|
8107
|
+
}
|
|
8108
|
+
async beginRoot(input) {
|
|
8109
|
+
const page = await this.messagePage(undefined, 1);
|
|
8110
|
+
if (this.stopping || !this.identityMatches())
|
|
8111
|
+
return { ok: false, reason: "closed" };
|
|
8112
|
+
const receipt = input.terminalOwner?.startsWith("msg_") ? input.terminalOwner : this.newMessageId();
|
|
8113
|
+
const root = {
|
|
8114
|
+
receipt,
|
|
8115
|
+
baseline: typeof page.data[0]?.id === "string" ? page.data[0].id : null,
|
|
8116
|
+
inputs: new Set,
|
|
8117
|
+
generation: 0,
|
|
8118
|
+
pending: 0
|
|
8119
|
+
};
|
|
8120
|
+
this.root = root;
|
|
8121
|
+
return this.admit(input.text, "queue", receipt, root);
|
|
8122
|
+
}
|
|
8123
|
+
admit(text, delivery, id, root) {
|
|
8124
|
+
root.generation += 1;
|
|
8125
|
+
root.pending += 1;
|
|
8126
|
+
const run = this.v2AdmissionTail.then(async () => {
|
|
8127
|
+
await this.waitForLiveStream();
|
|
8128
|
+
if (this.root !== root || this.stopping || !this.identityMatches())
|
|
8129
|
+
return { ok: false, reason: "closed" };
|
|
8130
|
+
const { response, body } = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(this.sessionId)}/prompt`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ id, text, delivery, resume: true }) }, "prompt admission");
|
|
8131
|
+
if (!response.ok)
|
|
8132
|
+
throw new OpenCodeHttpError(response.status, "prompt admission");
|
|
8133
|
+
const input = record4(record4(body)?.data);
|
|
8134
|
+
if (input?.id !== id || input.sessionID !== this.sessionId || input.type !== "user" || input.delivery !== delivery) {
|
|
8135
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned an invalid prompt receipt");
|
|
8136
|
+
}
|
|
8137
|
+
if (this.root !== root || this.stopping || !this.identityMatches())
|
|
8138
|
+
return { ok: false, reason: "closed" };
|
|
8139
|
+
root.inputs.add(id);
|
|
8140
|
+
return { ok: true, acceptedAs: delivery === "steer" ? "steer" : "prompt", receipt: id };
|
|
8141
|
+
}).catch((error) => {
|
|
8142
|
+
if (this.root === root && !this.stopping)
|
|
8143
|
+
this.protocolFailure("OpenCode CLI v2 prompt admission failed", error);
|
|
8144
|
+
throw error;
|
|
8145
|
+
}).finally(() => {
|
|
8146
|
+
root.pending -= 1;
|
|
8147
|
+
this.schedule();
|
|
8148
|
+
});
|
|
8149
|
+
this.v2AdmissionTail = run.then(() => {}, () => {});
|
|
8150
|
+
return run;
|
|
8151
|
+
}
|
|
8152
|
+
async messagePage(cursor, limit = 100) {
|
|
8153
|
+
const query = new URLSearchParams({ limit: String(limit), ...cursor ? { cursor } : { order: "desc" } });
|
|
8154
|
+
const { response, body } = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(this.sessionId)}/message?${query}`, { method: "GET" }, "message recovery");
|
|
8155
|
+
if (!response.ok)
|
|
8156
|
+
throw new OpenCodeHttpError(response.status, "message recovery");
|
|
8157
|
+
const page = record4(body);
|
|
8158
|
+
const cursors = record4(page?.cursor);
|
|
8159
|
+
if (!Array.isArray(page?.data) || !cursors || page.data.some((item) => typeof record4(item)?.id !== "string")) {
|
|
8160
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned an invalid message page");
|
|
8161
|
+
}
|
|
8162
|
+
if (cursors.next !== undefined && cursors.next !== null && typeof cursors.next !== "string")
|
|
8163
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned an invalid message cursor");
|
|
8164
|
+
return { data: page.data, next: typeof cursors.next === "string" ? cursors.next : undefined };
|
|
8165
|
+
}
|
|
8166
|
+
async messagesAfter(root) {
|
|
8167
|
+
const messages = [];
|
|
8168
|
+
const cursors = new Set;
|
|
8169
|
+
let cursor;
|
|
8170
|
+
for (;; ) {
|
|
8171
|
+
const page = await this.messagePage(cursor);
|
|
8172
|
+
if (this.root !== root || this.stopping)
|
|
8173
|
+
return [];
|
|
8174
|
+
for (const message of page.data) {
|
|
8175
|
+
if (message.id === root.baseline)
|
|
8176
|
+
return messages.reverse();
|
|
8177
|
+
messages.push(message);
|
|
8178
|
+
}
|
|
8179
|
+
if (!page.next) {
|
|
8180
|
+
if (root.baseline !== null)
|
|
8181
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 message recovery lost its root boundary");
|
|
8182
|
+
return messages.reverse();
|
|
8183
|
+
}
|
|
8184
|
+
if (cursors.has(page.next))
|
|
8185
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 message cursor did not advance");
|
|
8186
|
+
cursors.add(page.next);
|
|
8187
|
+
cursor = page.next;
|
|
8188
|
+
}
|
|
8189
|
+
}
|
|
8190
|
+
schedule() {
|
|
8191
|
+
if (!this.root || this.stopping || this.v2EvaluationTimer || this.v2Evaluating)
|
|
8192
|
+
return;
|
|
8193
|
+
this.v2EvaluationTimer = setTimeout(() => {
|
|
8194
|
+
this.v2EvaluationTimer = undefined;
|
|
8195
|
+
this.evaluate();
|
|
8196
|
+
}, this.options.activePollMs ?? 100);
|
|
8197
|
+
this.v2EvaluationTimer.unref?.();
|
|
8198
|
+
}
|
|
8199
|
+
async isActive() {
|
|
8200
|
+
const { response, body } = await this.fetchJsonWithTimeout("/api/session/active", { method: "GET" }, "active session query");
|
|
8201
|
+
if (!response.ok)
|
|
8202
|
+
throw new OpenCodeHttpError(response.status, "active session query");
|
|
8203
|
+
const active = record4(record4(body)?.data);
|
|
8204
|
+
if (!active)
|
|
8205
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned invalid active session data");
|
|
8206
|
+
return active[this.sessionId] !== undefined;
|
|
8207
|
+
}
|
|
8208
|
+
async evaluate() {
|
|
8209
|
+
const root = this.root;
|
|
8210
|
+
const identity = this.identity;
|
|
8211
|
+
if (!root || !identity || this.stopping || root.pending > 0 || root.inputs.size === 0 || this.v2Evaluating)
|
|
8212
|
+
return;
|
|
8213
|
+
this.v2Evaluating = true;
|
|
8214
|
+
const generation = root.generation;
|
|
8215
|
+
const current = () => this.root === root && root.generation === generation && root.pending === 0 && this.isIdentity(identity);
|
|
8216
|
+
try {
|
|
8217
|
+
if (await this.isActive())
|
|
8218
|
+
return;
|
|
8219
|
+
if (!current())
|
|
8220
|
+
return;
|
|
8221
|
+
const messages = await this.messagesAfter(root);
|
|
8222
|
+
if (!current())
|
|
8223
|
+
return;
|
|
8224
|
+
let lastInput = -1;
|
|
8225
|
+
const delivered = new Set;
|
|
8226
|
+
for (const [index, message] of messages.entries()) {
|
|
8227
|
+
if (message.type === "user" && typeof message.id === "string" && root.inputs.has(message.id)) {
|
|
8228
|
+
delivered.add(message.id);
|
|
8229
|
+
lastInput = index;
|
|
8230
|
+
}
|
|
8231
|
+
}
|
|
8232
|
+
const idle = messages.slice(lastInput + 1).reverse().find((message) => message.type === "idle");
|
|
8233
|
+
if (delivered.size !== root.inputs.size) {
|
|
8234
|
+
const { response, body } = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(this.sessionId)}/inbox`, { method: "GET" }, "pending input recovery");
|
|
8235
|
+
if (!response.ok)
|
|
8236
|
+
throw new OpenCodeHttpError(response.status, "pending input recovery");
|
|
8237
|
+
const pending = record4(body)?.data;
|
|
8238
|
+
if (!Array.isArray(pending))
|
|
8239
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned invalid inbox data");
|
|
8240
|
+
if (!current())
|
|
8241
|
+
return;
|
|
8242
|
+
const retained = new Set(pending.map((item) => record4(item)?.id));
|
|
8243
|
+
for (const id of root.inputs) {
|
|
8244
|
+
if (delivered.has(id) || retained.has(id))
|
|
8245
|
+
continue;
|
|
8246
|
+
const recovered = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(this.sessionId)}/message/${encodeURIComponent(id)}`, { method: "GET" }, "accepted input recovery");
|
|
8247
|
+
if (!current())
|
|
8248
|
+
return;
|
|
8249
|
+
if (recovered.response.status === 404)
|
|
8250
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 lost an accepted input from both inbox and messages");
|
|
8251
|
+
if (!recovered.response.ok)
|
|
8252
|
+
throw new OpenCodeHttpError(recovered.response.status, "accepted input recovery");
|
|
8253
|
+
const message = record4(record4(recovered.body)?.data);
|
|
8254
|
+
if (message?.id !== id || message.type !== "user")
|
|
8255
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned an invalid recovered input");
|
|
8256
|
+
}
|
|
8257
|
+
return;
|
|
8258
|
+
}
|
|
8259
|
+
for (const message of messages) {
|
|
8260
|
+
this.projectTools(message);
|
|
8261
|
+
this.projectMessage(message);
|
|
8262
|
+
}
|
|
8263
|
+
if (!idle || await this.isActive() || !current())
|
|
8264
|
+
return;
|
|
8265
|
+
if (!["succeeded", "failed", "interrupted"].includes(String(idle.outcome))) {
|
|
8266
|
+
throw new OpenCodeProtocolError("OpenCode CLI v2 returned an invalid idle outcome");
|
|
8267
|
+
}
|
|
8268
|
+
this.root = null;
|
|
8269
|
+
if (idle.outcome === "failed") {
|
|
8270
|
+
const error = record4(idle.error);
|
|
8271
|
+
this.events.emit("runtime_event", {
|
|
8272
|
+
kind: "error",
|
|
8273
|
+
code: "opencode.turn_failed",
|
|
8274
|
+
message: typeof error?.message === "string" ? error.message : "OpenCode CLI v2 execution failed"
|
|
8275
|
+
});
|
|
8276
|
+
}
|
|
8277
|
+
this.events.emit("runtime_event", {
|
|
8278
|
+
kind: "turn_end",
|
|
8279
|
+
sessionId: this.sessionId,
|
|
8280
|
+
turnOwner: root.receipt
|
|
8281
|
+
});
|
|
8282
|
+
} catch (error) {
|
|
8283
|
+
if (!current() || this.stopping)
|
|
8284
|
+
return;
|
|
8285
|
+
if (error instanceof OpenCodeProtocolError || error instanceof OpenCodeHttpError && error.status < 500) {
|
|
8286
|
+
this.protocolFailure("OpenCode CLI v2 durable message recovery failed", error);
|
|
8287
|
+
} else
|
|
8288
|
+
this.diagnostic("warning", "OpenCode CLI v2 message recovery temporarily unavailable; keeping the root active");
|
|
8289
|
+
} finally {
|
|
8290
|
+
this.v2Evaluating = false;
|
|
8291
|
+
this.schedule();
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
toolKey(messageId, id) {
|
|
8295
|
+
return typeof messageId === "string" && typeof id === "string" ? `${messageId}:${id}` : undefined;
|
|
8296
|
+
}
|
|
8297
|
+
projectTool(data) {
|
|
8298
|
+
const key = this.toolKey(data.assistantMessageID, data.id);
|
|
8299
|
+
if (!key || this.settledTools.has(key))
|
|
8300
|
+
return;
|
|
8301
|
+
const tool = this.liveTools.get(key) ?? { name: typeof data.name === "string" ? data.name : "OpenCode tool", called: false };
|
|
8302
|
+
if (tool.called)
|
|
8303
|
+
return;
|
|
8304
|
+
tool.called = true;
|
|
8305
|
+
this.liveTools.set(key, tool);
|
|
8306
|
+
this.events.emit("runtime_event", { kind: "tool_call", callId: key, name: tool.name, input: data.input });
|
|
8307
|
+
}
|
|
8308
|
+
finishTool(key) {
|
|
8309
|
+
if (!key || this.settledTools.has(key))
|
|
8310
|
+
return;
|
|
8311
|
+
const tool = this.liveTools.get(key);
|
|
8312
|
+
if (!tool?.called)
|
|
8313
|
+
return;
|
|
8314
|
+
this.liveTools.delete(key);
|
|
8315
|
+
this.settledTools.add(key);
|
|
8316
|
+
this.events.emit("runtime_event", { kind: "tool_output", callId: key, name: tool.name });
|
|
8317
|
+
}
|
|
8318
|
+
projectTools(message) {
|
|
8319
|
+
if (message.type !== "assistant" || !Array.isArray(message.content) || !this.root || this.stopping)
|
|
8320
|
+
return;
|
|
8321
|
+
for (const value of message.content) {
|
|
8322
|
+
const tool = record4(value);
|
|
8323
|
+
const state = record4(tool?.state);
|
|
8324
|
+
if (tool?.type !== "tool" || !["running", "completed", "error"].includes(String(state?.status)))
|
|
8325
|
+
continue;
|
|
8326
|
+
this.projectTool({ assistantMessageID: message.id, id: tool.id, name: tool.name, input: state?.input });
|
|
8327
|
+
if (state?.status !== "running")
|
|
8328
|
+
this.finishTool(this.toolKey(message.id, tool.id));
|
|
8329
|
+
}
|
|
8330
|
+
}
|
|
8331
|
+
projectMessage(message) {
|
|
8332
|
+
if (message.type !== "assistant" || typeof message.id !== "string" || this.projectedMessages.has(message.id))
|
|
8333
|
+
return;
|
|
8334
|
+
const time = record4(message.time);
|
|
8335
|
+
if (time?.completed === undefined)
|
|
8336
|
+
return;
|
|
8337
|
+
this.projectedMessages.add(message.id);
|
|
8338
|
+
if (Array.isArray(message.content))
|
|
8339
|
+
for (const value of message.content) {
|
|
8340
|
+
const content = record4(value);
|
|
8341
|
+
if (typeof content?.text !== "string" || !content.text)
|
|
8342
|
+
continue;
|
|
8343
|
+
if (content.type === "text")
|
|
8344
|
+
this.events.emit("runtime_event", { kind: "assistant_message_completed", text: content.text });
|
|
8345
|
+
if (content.type === "reasoning")
|
|
8346
|
+
this.events.emit("runtime_event", { kind: "assistant_reasoning_completed", text: content.text });
|
|
8347
|
+
}
|
|
8348
|
+
const tokens = record4(message.tokens);
|
|
8349
|
+
if (!tokens || !this.sessionId)
|
|
8350
|
+
return;
|
|
8351
|
+
const cache = record4(tokens.cache);
|
|
8352
|
+
const identity = { runtime: "opencode", backendSessionId: this.sessionId, providerRecordId: message.id };
|
|
8353
|
+
const usage = this.usage.project({
|
|
8354
|
+
...identity,
|
|
8355
|
+
source: "opencode.v2",
|
|
8356
|
+
input: tokens.input,
|
|
8357
|
+
output: tokens.output,
|
|
8358
|
+
reasoning: tokens.reasoning,
|
|
8359
|
+
cacheRead: cache?.read,
|
|
8360
|
+
cacheWrite: cache?.write,
|
|
8361
|
+
inputIncludesCache: false,
|
|
8362
|
+
outputIncludesReasoning: false
|
|
8363
|
+
});
|
|
8364
|
+
if (usage)
|
|
8365
|
+
this.events.emit("runtime_event", usage);
|
|
8366
|
+
this.usage.release(identity);
|
|
8367
|
+
}
|
|
8368
|
+
async interrupt(_input) {
|
|
8369
|
+
const root = this.root;
|
|
8370
|
+
if (!root || !this.ready || this.stopping || !this.identityMatches())
|
|
8371
|
+
return false;
|
|
8372
|
+
root.generation += 1;
|
|
8373
|
+
root.pending += 1;
|
|
8374
|
+
try {
|
|
8375
|
+
await this.requestNoContent(`/api/session/${encodeURIComponent(this.sessionId)}/interrupt?resume=false`, "POST", undefined, "session interrupt");
|
|
8376
|
+
return true;
|
|
8377
|
+
} catch (error) {
|
|
8378
|
+
if (!this.stopping)
|
|
8379
|
+
this.stop({ reason: "interrupt_timeout", forceAfterMs: 0 });
|
|
8380
|
+
throw error;
|
|
8381
|
+
} finally {
|
|
8382
|
+
root.pending -= 1;
|
|
8383
|
+
this.schedule();
|
|
8384
|
+
}
|
|
8385
|
+
}
|
|
8386
|
+
stop(input = {}) {
|
|
8387
|
+
this.root = null;
|
|
8388
|
+
if (this.v2EvaluationTimer)
|
|
8389
|
+
clearTimeout(this.v2EvaluationTimer);
|
|
8390
|
+
return super.stop(input);
|
|
8391
|
+
}
|
|
8392
|
+
}
|
|
8393
|
+
|
|
7932
8394
|
// agent-driver/dist/adapters/opencode/index.js
|
|
7933
8395
|
function createOpenCodeMessageId() {
|
|
7934
8396
|
return `msg_${randomBytes4(16).toString("hex")}`;
|
|
@@ -7936,6 +8398,7 @@ function createOpenCodeMessageId() {
|
|
|
7936
8398
|
|
|
7937
8399
|
class OpenCodeDriver {
|
|
7938
8400
|
outputProbe;
|
|
8401
|
+
versionProbe;
|
|
7939
8402
|
id = "opencode";
|
|
7940
8403
|
instructionDelivery = { kind: "workspace_file", canonical: "AGENTS.md", aliases: ["CLAUDE.md"] };
|
|
7941
8404
|
execution = {
|
|
@@ -7944,17 +8407,29 @@ class OpenCodeDriver {
|
|
|
7944
8407
|
wakeStart: "immediate",
|
|
7945
8408
|
terminalOwnership: "transport_request"
|
|
7946
8409
|
};
|
|
7947
|
-
constructor(outputProbe = probeCommandOutput) {
|
|
8410
|
+
constructor(outputProbe = probeCommandOutput, versionProbe = probeCliRuntime) {
|
|
7948
8411
|
this.outputProbe = outputProbe;
|
|
8412
|
+
this.versionProbe = versionProbe;
|
|
7949
8413
|
}
|
|
7950
8414
|
discoverRecentContext(request) {
|
|
7951
|
-
|
|
8415
|
+
if (request.recentProjectsTopK === 0)
|
|
8416
|
+
return discoverOpenCodeRecentContext(request);
|
|
8417
|
+
const result = this.versionProbe("opencode", {}, request.command);
|
|
8418
|
+
if (result.status !== "healthy")
|
|
8419
|
+
throw new Error("Installed OpenCode CLI could not report its version");
|
|
8420
|
+
return discoverOpenCodeRecentContext(request, { compatibility: resolveOpenCodeCompatibility(result.version) });
|
|
7952
8421
|
}
|
|
7953
8422
|
probe(command) {
|
|
7954
|
-
const result =
|
|
8423
|
+
const result = this.versionProbe("opencode", {}, command);
|
|
7955
8424
|
if (result.status !== "healthy")
|
|
7956
8425
|
return result;
|
|
7957
|
-
|
|
8426
|
+
let compatibility;
|
|
8427
|
+
try {
|
|
8428
|
+
compatibility = resolveOpenCodeCompatibility(result.version);
|
|
8429
|
+
} catch {
|
|
8430
|
+
return { ...result, status: "unhealthy", lastError: "unsupported_opencode_cli_version" };
|
|
8431
|
+
}
|
|
8432
|
+
const spec = resolveSpawnSpec("opencode", [...compatibility.modelArgs], command);
|
|
7958
8433
|
const output = this.outputProbe(spec.command, spec.args);
|
|
7959
8434
|
return {
|
|
7960
8435
|
...result,
|
|
@@ -7965,20 +8440,29 @@ class OpenCodeDriver {
|
|
|
7965
8440
|
return createOpenCodeMessageId();
|
|
7966
8441
|
}
|
|
7967
8442
|
async openLane(ctx, options) {
|
|
7968
|
-
|
|
8443
|
+
const command = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig).command;
|
|
8444
|
+
const result = this.versionProbe("opencode", {}, command);
|
|
8445
|
+
if (result.status !== "healthy")
|
|
8446
|
+
throw new Error("Installed OpenCode CLI could not report its version");
|
|
8447
|
+
const compatibility = resolveOpenCodeCompatibility(result.version);
|
|
8448
|
+
const factory = {
|
|
8449
|
+
spawnService: (launch, port, password) => this.spawnService(launch, port, password, compatibility)
|
|
8450
|
+
};
|
|
8451
|
+
const Lane = compatibility.major === 1 ? OpenCodeServiceLane : OpenCodeV2ServiceLane;
|
|
8452
|
+
return new Lane(factory, ctx, {
|
|
7969
8453
|
onRawStdoutLine: options?.onRawStdoutLine
|
|
7970
8454
|
});
|
|
7971
8455
|
}
|
|
7972
|
-
async spawnService(ctx, port, password) {
|
|
8456
|
+
async spawnService(ctx, port, password, compatibility = OPEN_CODE_V1) {
|
|
7973
8457
|
const { spawnEnv } = await prepareCliTransport(ctx, {
|
|
7974
|
-
OPENCODE_SERVER_PASSWORD: password
|
|
8458
|
+
OPENCODE_SERVER_PASSWORD: password,
|
|
8459
|
+
...compatibility.major === 2 ? { OPENCODE_PASSWORD: password } : {}
|
|
7975
8460
|
});
|
|
7976
8461
|
const fields2 = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
|
|
7977
8462
|
Object.assign(spawnEnv, await legacyOpenCodeApiEnvironment(spawnEnv, fields2.model));
|
|
7978
8463
|
const override = fields2.command;
|
|
7979
8464
|
const args = [
|
|
7980
|
-
|
|
7981
|
-
"--pure",
|
|
8465
|
+
...compatibility.serviceArgs,
|
|
7982
8466
|
"--hostname",
|
|
7983
8467
|
"127.0.0.1",
|
|
7984
8468
|
"--port",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alook/daemon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
4
4
|
"description": "Alook agent daemon — host-side runtime backend, process manager, credential proxy, and control plane.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/alookai/alook#readme",
|