@rallycry/conveyor-agent 10.13.51 → 10.13.52
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/{boot-BZE36LT6.js → boot-6X5ZZG3B.js} +3 -3
- package/dist/{chunk-HVSPADKP.js → chunk-DSP4KR4B.js} +2 -2
- package/dist/{chunk-KZQBYSMN.js → chunk-UAGIXPYD.js} +3 -6
- package/dist/chunk-UAGIXPYD.js.map +1 -0
- package/dist/{chunk-EQO7QFGN.js → chunk-ZTLGCN5G.js} +413 -161
- package/dist/chunk-ZTLGCN5G.js.map +1 -0
- package/dist/cli.js +4 -4
- package/dist/index.js +2 -2
- package/dist/{server-PPTELJWY.js → server-EQUCOLNY.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-EQO7QFGN.js.map +0 -1
- package/dist/chunk-KZQBYSMN.js.map +0 -1
- /package/dist/{boot-BZE36LT6.js.map → boot-6X5ZZG3B.js.map} +0 -0
- /package/dist/{chunk-HVSPADKP.js.map → chunk-DSP4KR4B.js.map} +0 -0
- /package/dist/{server-PPTELJWY.js.map → server-EQUCOLNY.js.map} +0 -0
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
spawnOptionsFingerprint,
|
|
50
50
|
transcriptSize,
|
|
51
51
|
turnOptionsFrom
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-UAGIXPYD.js";
|
|
53
53
|
|
|
54
54
|
// src/setup/bootstrap.ts
|
|
55
55
|
var BOOTSTRAP_TIMEOUT_MS = 3e4;
|
|
@@ -3846,8 +3846,237 @@ var ClaudeCodeHarness = class {
|
|
|
3846
3846
|
|
|
3847
3847
|
// src/harness/pty/session.ts
|
|
3848
3848
|
import { randomUUID } from "crypto";
|
|
3849
|
-
import { mkdtemp, mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
3850
|
-
import { join as
|
|
3849
|
+
import { mkdtemp, mkdir as mkdir3, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
3850
|
+
import { join as join5, dirname } from "path";
|
|
3851
|
+
|
|
3852
|
+
// src/harness/opencode/plugin.ts
|
|
3853
|
+
import { writeFile } from "fs/promises";
|
|
3854
|
+
import { join } from "path";
|
|
3855
|
+
var OPENCODE_EVENTS_FILE_ENV = "CONVEYOR_OPENCODE_EVENTS_FILE";
|
|
3856
|
+
var CONVEYOR_PLUGIN_SOURCE = `import { appendFileSync } from "node:fs";
|
|
3857
|
+
|
|
3858
|
+
export const ConveyorEventsPlugin = async () => ({
|
|
3859
|
+
event: async ({ event }) => {
|
|
3860
|
+
const sink = process.env.${OPENCODE_EVENTS_FILE_ENV};
|
|
3861
|
+
if (!sink || !event || event.type === "message.part.delta") return;
|
|
3862
|
+
try {
|
|
3863
|
+
appendFileSync(sink, JSON.stringify(event) + "\\n");
|
|
3864
|
+
} catch {
|
|
3865
|
+
// Never let a sink hiccup break the user's opencode session.
|
|
3866
|
+
}
|
|
3867
|
+
},
|
|
3868
|
+
});
|
|
3869
|
+
`;
|
|
3870
|
+
async function stageOpenCodePlugin(tempDir) {
|
|
3871
|
+
const pluginPath = join(tempDir, "conveyor-events-plugin.mjs");
|
|
3872
|
+
const eventsSinkPath = join(tempDir, "opencode-events.ndjson");
|
|
3873
|
+
await writeFile(pluginPath, CONVEYOR_PLUGIN_SOURCE, "utf8");
|
|
3874
|
+
await writeFile(eventsSinkPath, "", "utf8");
|
|
3875
|
+
return { pluginPath, eventsSinkPath };
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
// src/harness/opencode/events.ts
|
|
3879
|
+
function parseOpenCodeLine(line) {
|
|
3880
|
+
const trimmed = line.trim();
|
|
3881
|
+
if (!trimmed.startsWith("{")) return null;
|
|
3882
|
+
try {
|
|
3883
|
+
return JSON.parse(trimmed);
|
|
3884
|
+
} catch {
|
|
3885
|
+
return null;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
function numberAt(source, ...keys) {
|
|
3889
|
+
let cursor = source;
|
|
3890
|
+
for (const key of keys) {
|
|
3891
|
+
if (typeof cursor !== "object" || cursor === null) return 0;
|
|
3892
|
+
cursor = cursor[key];
|
|
3893
|
+
}
|
|
3894
|
+
return typeof cursor === "number" && Number.isFinite(cursor) ? cursor : 0;
|
|
3895
|
+
}
|
|
3896
|
+
function accumulateUsage(event, into) {
|
|
3897
|
+
const part = event.part;
|
|
3898
|
+
if (!part) return;
|
|
3899
|
+
into.inputTokens += numberAt(part.tokens, "input");
|
|
3900
|
+
into.outputTokens += numberAt(part.tokens, "output");
|
|
3901
|
+
const cost = part.cost;
|
|
3902
|
+
if (typeof cost === "number" && Number.isFinite(cost)) into.totalCostUsd += cost;
|
|
3903
|
+
}
|
|
3904
|
+
function mapOpenCodeEvent(event) {
|
|
3905
|
+
const part = event.part;
|
|
3906
|
+
if (!part) return null;
|
|
3907
|
+
if (part.type === "text") return mapTextPart(part);
|
|
3908
|
+
if (part.type === "tool") return mapToolPart(part);
|
|
3909
|
+
return null;
|
|
3910
|
+
}
|
|
3911
|
+
function mapTextPart(part) {
|
|
3912
|
+
const text = typeof part.text === "string" ? part.text : "";
|
|
3913
|
+
if (text.trim() === "") return null;
|
|
3914
|
+
return {
|
|
3915
|
+
type: "assistant",
|
|
3916
|
+
message: { role: "assistant", content: [{ type: "text", text }] }
|
|
3917
|
+
};
|
|
3918
|
+
}
|
|
3919
|
+
function mapToolPart(part) {
|
|
3920
|
+
const status = part.state?.status;
|
|
3921
|
+
if (status !== "completed" && status !== "error") return null;
|
|
3922
|
+
return {
|
|
3923
|
+
type: "assistant",
|
|
3924
|
+
message: {
|
|
3925
|
+
role: "assistant",
|
|
3926
|
+
content: [
|
|
3927
|
+
{
|
|
3928
|
+
type: "tool_use",
|
|
3929
|
+
name: typeof part.tool === "string" ? part.tool : "unknown",
|
|
3930
|
+
input: part.state?.input ?? {},
|
|
3931
|
+
...typeof part.id === "string" ? { id: part.id } : {}
|
|
3932
|
+
}
|
|
3933
|
+
]
|
|
3934
|
+
}
|
|
3935
|
+
};
|
|
3936
|
+
}
|
|
3937
|
+
function errorMessageOf(event) {
|
|
3938
|
+
if (event.type !== "error") return null;
|
|
3939
|
+
const error = event.error;
|
|
3940
|
+
const message = typeof error?.data?.message === "string" ? error.data.message : null;
|
|
3941
|
+
const name = typeof error?.name === "string" ? error.name : "error";
|
|
3942
|
+
const status = typeof error?.data?.statusCode === "number" ? ` (HTTP ${error.data.statusCode})` : "";
|
|
3943
|
+
return message ? `${name}${status}: ${message}` : `${name}${status}`;
|
|
3944
|
+
}
|
|
3945
|
+
function sessionIdOf(event) {
|
|
3946
|
+
return typeof event.sessionID === "string" && event.sessionID.length > 0 ? event.sessionID : null;
|
|
3947
|
+
}
|
|
3948
|
+
function busSessionIdOf(event) {
|
|
3949
|
+
const direct = event.properties?.sessionID;
|
|
3950
|
+
if (typeof direct === "string" && direct.length > 0) return direct;
|
|
3951
|
+
const viaInfo = event.properties?.info?.sessionID;
|
|
3952
|
+
return typeof viaInfo === "string" && viaInfo.length > 0 ? viaInfo : null;
|
|
3953
|
+
}
|
|
3954
|
+
function isIdleEvent(event) {
|
|
3955
|
+
if (event.type === "session.idle") return true;
|
|
3956
|
+
return event.type === "session.status" && event.properties?.status?.type === "idle";
|
|
3957
|
+
}
|
|
3958
|
+
function isBusyEvent(event) {
|
|
3959
|
+
return event.type === "session.status" && event.properties?.status?.type === "busy";
|
|
3960
|
+
}
|
|
3961
|
+
function busInfoOf(event) {
|
|
3962
|
+
if (event.type !== "message.updated") return null;
|
|
3963
|
+
return event.properties?.info ?? null;
|
|
3964
|
+
}
|
|
3965
|
+
function busPartOf(event) {
|
|
3966
|
+
if (event.type !== "message.part.updated") return null;
|
|
3967
|
+
return event.properties?.part ?? null;
|
|
3968
|
+
}
|
|
3969
|
+
function buildResultEvent(exitCode, usage, assistantText, stderrTail, reportedError) {
|
|
3970
|
+
if (exitCode === 0) {
|
|
3971
|
+
return {
|
|
3972
|
+
type: "result",
|
|
3973
|
+
subtype: "success",
|
|
3974
|
+
// The runner substitutes "Task completed." for an empty summary, so a run
|
|
3975
|
+
// that only used tools still reads sensibly in chat.
|
|
3976
|
+
result: assistantText,
|
|
3977
|
+
total_cost_usd: usage.totalCostUsd
|
|
3978
|
+
};
|
|
3979
|
+
}
|
|
3980
|
+
const errors = reportedError ? [reportedError] : [`opencode run exited with code ${exitCode}`];
|
|
3981
|
+
if (stderrTail.trim()) errors.push(`stderr:
|
|
3982
|
+
${stderrTail.trim()}`);
|
|
3983
|
+
return { type: "result", subtype: "error", errors };
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3986
|
+
// src/harness/opencode/event-source.ts
|
|
3987
|
+
var MAX_TRACKED_MESSAGES = 200;
|
|
3988
|
+
var OpenCodeEventSource = class {
|
|
3989
|
+
constructor(emit, onSessionId) {
|
|
3990
|
+
this.emit = emit;
|
|
3991
|
+
this.onSessionId = onSessionId;
|
|
3992
|
+
}
|
|
3993
|
+
emit;
|
|
3994
|
+
onSessionId;
|
|
3995
|
+
roles = /* @__PURE__ */ new Map();
|
|
3996
|
+
usage = { inputTokens: 0, outputTokens: 0, totalCostUsd: 0 };
|
|
3997
|
+
assistantText = "";
|
|
3998
|
+
/** Busy has been seen and idle has not — a turn is in flight on the bus. */
|
|
3999
|
+
active = false;
|
|
4000
|
+
latchedSessionId = null;
|
|
4001
|
+
/** The opencode-assigned session id, once any event has carried it. */
|
|
4002
|
+
get sessionId() {
|
|
4003
|
+
return this.latchedSessionId;
|
|
4004
|
+
}
|
|
4005
|
+
/**
|
|
4006
|
+
* Ingest one parsed sink record. Self-arming: `busy` opens a turn (resetting
|
|
4007
|
+
* the accumulators) whether it came from our pasted prompt or a human typing
|
|
4008
|
+
* into the parked TUI — the passive case is exactly why turn state cannot
|
|
4009
|
+
* only reset from beginTurn.
|
|
4010
|
+
*/
|
|
4011
|
+
handleRecord(raw) {
|
|
4012
|
+
if (typeof raw !== "object" || raw === null) return;
|
|
4013
|
+
const event = raw;
|
|
4014
|
+
this.latchSessionId(event);
|
|
4015
|
+
const info = busInfoOf(event);
|
|
4016
|
+
if (info?.id && info.role) this.trackRole(info.id, info.role);
|
|
4017
|
+
if (isBusyEvent(event)) {
|
|
4018
|
+
if (!this.active) this.beginBusTurn();
|
|
4019
|
+
return;
|
|
4020
|
+
}
|
|
4021
|
+
if (isIdleEvent(event)) {
|
|
4022
|
+
this.finishBusTurn();
|
|
4023
|
+
return;
|
|
4024
|
+
}
|
|
4025
|
+
if (event.type === "session.error") {
|
|
4026
|
+
this.finishBusTurn(describeSessionError(event));
|
|
4027
|
+
return;
|
|
4028
|
+
}
|
|
4029
|
+
this.handlePart(event);
|
|
4030
|
+
}
|
|
4031
|
+
handlePart(event) {
|
|
4032
|
+
const part = busPartOf(event);
|
|
4033
|
+
if (!part) return;
|
|
4034
|
+
if (!this.active) this.beginBusTurn();
|
|
4035
|
+
if (typeof part.messageID !== "string" || this.roles.get(part.messageID) !== "assistant") {
|
|
4036
|
+
return;
|
|
4037
|
+
}
|
|
4038
|
+
accumulateUsage({ part }, this.usage);
|
|
4039
|
+
const mapped = mapOpenCodeEvent({ part });
|
|
4040
|
+
if (!mapped) return;
|
|
4041
|
+
this.emit(mapped);
|
|
4042
|
+
if (mapped.type === "assistant") {
|
|
4043
|
+
const block = mapped.message.content[0];
|
|
4044
|
+
if (block?.type === "text" && block.text) this.assistantText += block.text;
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
beginBusTurn() {
|
|
4048
|
+
this.active = true;
|
|
4049
|
+
this.usage = { inputTokens: 0, outputTokens: 0, totalCostUsd: 0 };
|
|
4050
|
+
this.assistantText = "";
|
|
4051
|
+
}
|
|
4052
|
+
/** Idle (or an error) closes the turn exactly once. */
|
|
4053
|
+
finishBusTurn(error) {
|
|
4054
|
+
if (!this.active) return;
|
|
4055
|
+
this.active = false;
|
|
4056
|
+
this.emit(
|
|
4057
|
+
error ? buildResultEvent(1, this.usage, "", "", error) : buildResultEvent(0, this.usage, this.assistantText.trim(), "")
|
|
4058
|
+
);
|
|
4059
|
+
}
|
|
4060
|
+
latchSessionId(event) {
|
|
4061
|
+
if (this.latchedSessionId) return;
|
|
4062
|
+
const id = busSessionIdOf(event);
|
|
4063
|
+
if (!id) return;
|
|
4064
|
+
this.latchedSessionId = id;
|
|
4065
|
+
this.onSessionId?.(id);
|
|
4066
|
+
}
|
|
4067
|
+
trackRole(id, role) {
|
|
4068
|
+
this.roles.set(id, role);
|
|
4069
|
+
if (this.roles.size > MAX_TRACKED_MESSAGES) {
|
|
4070
|
+
const oldest = this.roles.keys().next().value;
|
|
4071
|
+
if (oldest !== void 0) this.roles.delete(oldest);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
};
|
|
4075
|
+
function describeSessionError(event) {
|
|
4076
|
+
const error = event.properties?.error;
|
|
4077
|
+
if (error && typeof error.message === "string") return error.message;
|
|
4078
|
+
return `opencode reported a session error: ${JSON.stringify(event.properties ?? {}).slice(0, 300)}`;
|
|
4079
|
+
}
|
|
3851
4080
|
|
|
3852
4081
|
// src/harness/pty/event-queue.ts
|
|
3853
4082
|
var AsyncEventQueue = class {
|
|
@@ -4128,14 +4357,16 @@ function mapTranscriptRecord(raw) {
|
|
|
4128
4357
|
// src/harness/pty/jsonl-tailer.ts
|
|
4129
4358
|
var POLL_INTERVAL_MS = 25;
|
|
4130
4359
|
var JsonlTailer = class {
|
|
4131
|
-
constructor(path2, onEvent, onRawRecord) {
|
|
4360
|
+
constructor(path2, onEvent, onRawRecord, mapRecord = mapTranscriptRecord) {
|
|
4132
4361
|
this.path = path2;
|
|
4133
4362
|
this.onEvent = onEvent;
|
|
4134
4363
|
this.onRawRecord = onRawRecord;
|
|
4364
|
+
this.mapRecord = mapRecord;
|
|
4135
4365
|
}
|
|
4136
4366
|
path;
|
|
4137
4367
|
onEvent;
|
|
4138
4368
|
onRawRecord;
|
|
4369
|
+
mapRecord;
|
|
4139
4370
|
offset = 0;
|
|
4140
4371
|
buffer = "";
|
|
4141
4372
|
timer = null;
|
|
@@ -4208,7 +4439,7 @@ var JsonlTailer = class {
|
|
|
4208
4439
|
return;
|
|
4209
4440
|
}
|
|
4210
4441
|
this.onRawRecord?.(parsed);
|
|
4211
|
-
const event =
|
|
4442
|
+
const event = this.mapRecord(parsed);
|
|
4212
4443
|
if (event) this.onEvent(event);
|
|
4213
4444
|
}
|
|
4214
4445
|
};
|
|
@@ -4401,17 +4632,17 @@ function mapChatRecords(raw) {
|
|
|
4401
4632
|
}
|
|
4402
4633
|
|
|
4403
4634
|
// src/harness/pty/settings.ts
|
|
4404
|
-
import { mkdir, writeFile, chmod } from "fs/promises";
|
|
4635
|
+
import { mkdir, writeFile as writeFile2, chmod } from "fs/promises";
|
|
4405
4636
|
import { homedir } from "os";
|
|
4406
|
-
import { join } from "path";
|
|
4637
|
+
import { join as join2 } from "path";
|
|
4407
4638
|
function claudeConfigHome() {
|
|
4408
|
-
return process.env.CLAUDE_CONFIG_DIR ??
|
|
4639
|
+
return process.env.CLAUDE_CONFIG_DIR ?? join2(homedir(), ".claude");
|
|
4409
4640
|
}
|
|
4410
4641
|
function projectSlug(cwd) {
|
|
4411
4642
|
return cwd.replace(/\//g, "-");
|
|
4412
4643
|
}
|
|
4413
4644
|
function sessionTranscriptPath(cwd, sessionId) {
|
|
4414
|
-
return
|
|
4645
|
+
return join2(claudeConfigHome(), "projects", projectSlug(cwd), `${sessionId}.jsonl`);
|
|
4415
4646
|
}
|
|
4416
4647
|
var ALLOW_RULES = [
|
|
4417
4648
|
"Bash",
|
|
@@ -4614,10 +4845,10 @@ function buildPreToolUseHooks(helperPath, gateAllTools) {
|
|
|
4614
4845
|
];
|
|
4615
4846
|
}
|
|
4616
4847
|
async function writeHookSettings(dir, opts = {}) {
|
|
4617
|
-
const helperPath =
|
|
4618
|
-
const settingsPath =
|
|
4848
|
+
const helperPath = join2(dir, "hook-helper.cjs");
|
|
4849
|
+
const settingsPath = join2(dir, "settings.json");
|
|
4619
4850
|
await mkdir(dir, { recursive: true });
|
|
4620
|
-
await
|
|
4851
|
+
await writeFile2(helperPath, HOOK_HELPER_SOURCE, "utf8");
|
|
4621
4852
|
await chmod(helperPath, 493);
|
|
4622
4853
|
const settings = {
|
|
4623
4854
|
// Pre-accept Claude Code's "Bypass Permissions mode" disclaimer. Build-capable
|
|
@@ -4654,7 +4885,7 @@ async function writeHookSettings(dir, opts = {}) {
|
|
|
4654
4885
|
]
|
|
4655
4886
|
}
|
|
4656
4887
|
};
|
|
4657
|
-
await
|
|
4888
|
+
await writeFile2(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
4658
4889
|
return { settingsPath, helperPath };
|
|
4659
4890
|
}
|
|
4660
4891
|
|
|
@@ -4772,8 +5003,8 @@ var PtyOutputCoalescer = class {
|
|
|
4772
5003
|
// src/harness/pty/tool-server.ts
|
|
4773
5004
|
import { createServer as createServer2 } from "http";
|
|
4774
5005
|
import { z as z9 } from "zod";
|
|
4775
|
-
import { writeFile as
|
|
4776
|
-
import { join as
|
|
5006
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
5007
|
+
import { join as join3 } from "path";
|
|
4777
5008
|
import { randomBytes } from "crypto";
|
|
4778
5009
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4779
5010
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -4964,19 +5195,19 @@ async function startToolServers(mcpServers, tempDir) {
|
|
|
4964
5195
|
config[name] = { type: "http", url, headers: { Authorization: `Bearer ${token}` } };
|
|
4965
5196
|
}
|
|
4966
5197
|
if (Object.keys(config).length === 0) return { servers, mcpConfigPath: null, entries: config };
|
|
4967
|
-
const mcpConfigPath =
|
|
4968
|
-
await
|
|
5198
|
+
const mcpConfigPath = join3(tempDir, "mcp-config.json");
|
|
5199
|
+
await writeFile3(mcpConfigPath, JSON.stringify({ mcpServers: config }, null, 2), "utf8");
|
|
4969
5200
|
return { servers, mcpConfigPath, entries: config };
|
|
4970
5201
|
}
|
|
4971
5202
|
|
|
4972
5203
|
// src/harness/pty/credentials.ts
|
|
4973
|
-
import { chmod as chmod2, mkdir as mkdir2, readFile, rm, writeFile as
|
|
5204
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile, rm, writeFile as writeFile4 } from "fs/promises";
|
|
4974
5205
|
import { homedir as homedir2 } from "os";
|
|
4975
|
-
import { join as
|
|
5206
|
+
import { join as join4 } from "path";
|
|
4976
5207
|
var SYNTH_TOKEN_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
4977
5208
|
var REFRESH_SKEW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
4978
5209
|
function claudeCredentialsPath() {
|
|
4979
|
-
return
|
|
5210
|
+
return join4(claudeConfigHome(), ".credentials.json");
|
|
4980
5211
|
}
|
|
4981
5212
|
function isConveyorCloudEnv(env = process.env) {
|
|
4982
5213
|
return Boolean(env.CLAUDESPACE_NAME || env.CODESPACE_NAME || env.CODESPACES);
|
|
@@ -5067,7 +5298,7 @@ async function writeWithReadBackRetry(io2, contents, delaysMs = READ_BACK_DELAYS
|
|
|
5067
5298
|
}
|
|
5068
5299
|
function fsWriteIo(path2, mode) {
|
|
5069
5300
|
return {
|
|
5070
|
-
write: (contents) =>
|
|
5301
|
+
write: (contents) => writeFile4(path2, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode }),
|
|
5071
5302
|
read: () => readRaw(path2)
|
|
5072
5303
|
};
|
|
5073
5304
|
}
|
|
@@ -5139,7 +5370,7 @@ async function sanitizeApprovedApiKeys(oauthToken) {
|
|
|
5139
5370
|
}
|
|
5140
5371
|
function claudeJsonPath() {
|
|
5141
5372
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
5142
|
-
return configDir ?
|
|
5373
|
+
return configDir ? join4(configDir, ".claude.json") : join4(homedir2(), ".claude.json");
|
|
5143
5374
|
}
|
|
5144
5375
|
function asRecord(value) {
|
|
5145
5376
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
@@ -5232,7 +5463,7 @@ function planClaudeJsonSeed(existingRaw, trustCwd, oauthIdentity) {
|
|
|
5232
5463
|
return changed ? JSON.stringify(config) : null;
|
|
5233
5464
|
}
|
|
5234
5465
|
function conveyorOauthMarkerPath() {
|
|
5235
|
-
return
|
|
5466
|
+
return join4(claudeConfigHome(), "conveyor-oauth-account.json");
|
|
5236
5467
|
}
|
|
5237
5468
|
function parseOauthIdentity(raw) {
|
|
5238
5469
|
if (!raw || raw.trim() === "") return null;
|
|
@@ -5316,7 +5547,8 @@ var ClaudeTuiAdapter = class {
|
|
|
5316
5547
|
resume: true,
|
|
5317
5548
|
structuredEvents: true,
|
|
5318
5549
|
prefill: true,
|
|
5319
|
-
passiveTurns: true
|
|
5550
|
+
passiveTurns: true,
|
|
5551
|
+
rawPromptGate: false
|
|
5320
5552
|
};
|
|
5321
5553
|
resolveBinary(env = process.env) {
|
|
5322
5554
|
return env.CONVEYOR_CLAUDE_BIN ?? "claude";
|
|
@@ -5392,6 +5624,12 @@ var PtySession = class {
|
|
|
5392
5624
|
lastTurnCleanResult = false;
|
|
5393
5625
|
socket = null;
|
|
5394
5626
|
tailer = null;
|
|
5627
|
+
// opencode structured-events state: the sink-record interpreter, and the
|
|
5628
|
+
// opencode-assigned session id it latched (reported via a synthetic init and
|
|
5629
|
+
// used for `--session` resume — opencode manages its own lineage, unlike
|
|
5630
|
+
// Claude's deterministic transcript UUIDs).
|
|
5631
|
+
opencodeSource = null;
|
|
5632
|
+
reportedOpenCodeId = null;
|
|
5395
5633
|
pty = null;
|
|
5396
5634
|
tempDir = "";
|
|
5397
5635
|
sawResult = false;
|
|
@@ -5507,9 +5745,18 @@ var PtySession = class {
|
|
|
5507
5745
|
const tail = cleanTerminalOutput(this.recentOutput, MAX_DIAGNOSTIC_OUTPUT);
|
|
5508
5746
|
return tail ? redact(tail).output : "";
|
|
5509
5747
|
}
|
|
5510
|
-
/**
|
|
5748
|
+
/**
|
|
5749
|
+
* The session identity this process is bound to. For Claude, the
|
|
5750
|
+
* deterministic lineage UUID; for opencode, the `ses_…` id the engine
|
|
5751
|
+
* assigned (latched from the plugin events), which wins once known so a
|
|
5752
|
+
* respawn's explicit resume target matches.
|
|
5753
|
+
*/
|
|
5511
5754
|
get sessionUuid() {
|
|
5512
|
-
return this.resume ?? this.options.sessionId;
|
|
5755
|
+
return this.reportedOpenCodeId ?? this.resume ?? this.options.sessionId;
|
|
5756
|
+
}
|
|
5757
|
+
/** The opencode-assigned session id, or null (always null for Claude). */
|
|
5758
|
+
get reportedSessionId() {
|
|
5759
|
+
return this.reportedOpenCodeId;
|
|
5513
5760
|
}
|
|
5514
5761
|
/** Fingerprint of the spawn-time options a reused process cannot change. */
|
|
5515
5762
|
get spawnFingerprint() {
|
|
@@ -5524,9 +5771,19 @@ var PtySession = class {
|
|
|
5524
5771
|
* Whether this parked process can serve a turn wanting `resume`/`fingerprint`.
|
|
5525
5772
|
* Requires: live (not torn down / not exited), idle (no turn draining), a
|
|
5526
5773
|
* matching resume target, and matching spawn options.
|
|
5774
|
+
*
|
|
5775
|
+
* The resume match is adapter-shaped. Claude's resume tokens are transcript
|
|
5776
|
+
* files, so an undefined `resume` means "start a fresh conversation" and
|
|
5777
|
+
* must NOT reuse a parked process bound to an old one. opencode manages its
|
|
5778
|
+
* own lineage — the executor can never learn its `ses_…` ids from disk, so
|
|
5779
|
+
* every follow-up arrives with `resume` undefined; there, the live parked
|
|
5780
|
+
* process IS the conversation, and reuse is keyed on the id the plugin
|
|
5781
|
+
* events latched. Gating on `reportedOpenCodeId` (which Claude sessions
|
|
5782
|
+
* never set) keeps the Claude semantics byte-identical.
|
|
5527
5783
|
*/
|
|
5528
5784
|
canReuse(resume, fingerprint) {
|
|
5529
|
-
|
|
5785
|
+
const resumeMatches = resume === void 0 ? this.reportedOpenCodeId !== null : resume === this.sessionUuid;
|
|
5786
|
+
return !this._toreDown && !this.exited && this.activeQueue === null && resumeMatches && fingerprint === this.spawnFingerprint;
|
|
5530
5787
|
}
|
|
5531
5788
|
get hookSocketPath() {
|
|
5532
5789
|
return this.socket ? this.socket.socketPath : null;
|
|
@@ -5654,11 +5911,14 @@ var PtySession = class {
|
|
|
5654
5911
|
return;
|
|
5655
5912
|
}
|
|
5656
5913
|
try {
|
|
5657
|
-
if (this.adapter.capabilities.structuredEvents) {
|
|
5914
|
+
if (this.adapter.capabilities.structuredEvents && this.adapter.id === "opencode") {
|
|
5915
|
+
const extras = await this.startOpenCodeEventSources();
|
|
5916
|
+
await this.spawn(void 0, void 0, extras);
|
|
5917
|
+
} else if (this.adapter.capabilities.structuredEvents) {
|
|
5658
5918
|
const { settingsPath, socketPath } = await this.startStructuredEventSources(sessionId);
|
|
5659
5919
|
await this.spawn(settingsPath, socketPath);
|
|
5660
5920
|
} else {
|
|
5661
|
-
this.tempDir = await mkdtemp(
|
|
5921
|
+
this.tempDir = await mkdtemp(join5(sessionTempBase(), "conveyor-pty-"));
|
|
5662
5922
|
await this.spawn();
|
|
5663
5923
|
this.pushEvent({
|
|
5664
5924
|
type: "system",
|
|
@@ -5687,6 +5947,61 @@ var PtySession = class {
|
|
|
5687
5947
|
throw err;
|
|
5688
5948
|
}
|
|
5689
5949
|
}
|
|
5950
|
+
/**
|
|
5951
|
+
* Allocate the opencode structured-event sources: the in-process tool
|
|
5952
|
+
* servers (shared with the Claude path — the same loopback StreamableHTTP
|
|
5953
|
+
* servers, consumed via config `mcp` entries instead of `--mcp-config`),
|
|
5954
|
+
* the instructions file carrying the system prompt, the staged Conveyor
|
|
5955
|
+
* events plugin, and a tailer on its sink. Returns the spawn extras the
|
|
5956
|
+
* adapter bakes into OPENCODE_CONFIG_CONTENT.
|
|
5957
|
+
*
|
|
5958
|
+
* The tailer routes every parsed record to the OpenCodeEventSource, which
|
|
5959
|
+
* owns per-turn state (roles, usage, busy/idle) and feeds the mapped events
|
|
5960
|
+
* through the SAME handleTranscriptEvent path Claude records take — so
|
|
5961
|
+
* nudge-disarm, passive buffering, and result-ends-turn run unchanged. The
|
|
5962
|
+
* synthetic `system init` reports the opencode-assigned session id the
|
|
5963
|
+
* moment it is known, which is how the runner learns the resume target.
|
|
5964
|
+
*/
|
|
5965
|
+
async startOpenCodeEventSources() {
|
|
5966
|
+
this.tempDir = await mkdtemp(join5(sessionTempBase(), "conveyor-pty-"));
|
|
5967
|
+
const { servers, entries } = await startToolServers(
|
|
5968
|
+
this.options.mcpServers ?? {},
|
|
5969
|
+
this.tempDir
|
|
5970
|
+
);
|
|
5971
|
+
this.toolServers = servers;
|
|
5972
|
+
const { pluginPath, eventsSinkPath } = await stageOpenCodePlugin(this.tempDir);
|
|
5973
|
+
let instructionsPath;
|
|
5974
|
+
const systemPrompt = this.options.appendSystemPrompt;
|
|
5975
|
+
if (systemPrompt && systemPrompt.trim() !== "") {
|
|
5976
|
+
instructionsPath = join5(this.tempDir, "conveyor-instructions.md");
|
|
5977
|
+
await writeFile5(instructionsPath, systemPrompt, "utf8");
|
|
5978
|
+
}
|
|
5979
|
+
this.opencodeSource = new OpenCodeEventSource(
|
|
5980
|
+
(event) => this.handleTranscriptEvent(event),
|
|
5981
|
+
(id) => {
|
|
5982
|
+
this.reportedOpenCodeId = id;
|
|
5983
|
+
this.pushEvent({
|
|
5984
|
+
type: "system",
|
|
5985
|
+
subtype: "init",
|
|
5986
|
+
session_id: id,
|
|
5987
|
+
model: this.options.model
|
|
5988
|
+
});
|
|
5989
|
+
}
|
|
5990
|
+
);
|
|
5991
|
+
this.tailer = new JsonlTailer(
|
|
5992
|
+
eventsSinkPath,
|
|
5993
|
+
() => void 0,
|
|
5994
|
+
(raw) => this.opencodeSource?.handleRecord(raw),
|
|
5995
|
+
() => null
|
|
5996
|
+
);
|
|
5997
|
+
this.tailer.start(0);
|
|
5998
|
+
return {
|
|
5999
|
+
mcpEntries: entries,
|
|
6000
|
+
eventsSinkPath,
|
|
6001
|
+
pluginPath,
|
|
6002
|
+
...instructionsPath ? { instructionsPath } : {}
|
|
6003
|
+
};
|
|
6004
|
+
}
|
|
5690
6005
|
/**
|
|
5691
6006
|
* Allocate the Claude-style structured-event sources: the PostToolUse hook
|
|
5692
6007
|
* socket, the per-run settings file, the in-process tool servers, and the
|
|
@@ -5695,8 +6010,8 @@ var PtySession = class {
|
|
|
5695
6010
|
* paths spawn() must wire into the child's argv/env.
|
|
5696
6011
|
*/
|
|
5697
6012
|
async startStructuredEventSources(sessionId) {
|
|
5698
|
-
this.tempDir = await mkdtemp(
|
|
5699
|
-
const socketPath =
|
|
6013
|
+
this.tempDir = await mkdtemp(join5(sessionTempBase(), "conveyor-pty-"));
|
|
6014
|
+
const socketPath = join5(this.tempDir, "hook.sock");
|
|
5700
6015
|
this.socket = new HookSocketServer(
|
|
5701
6016
|
socketPath,
|
|
5702
6017
|
(progress) => this.handleProgress(progress),
|
|
@@ -5901,16 +6216,18 @@ var PtySession = class {
|
|
|
5901
6216
|
this.toolServers = servers;
|
|
5902
6217
|
this.mcpConfigPath = mcpConfigPath;
|
|
5903
6218
|
}
|
|
5904
|
-
async spawn(settingsPath, socketPath) {
|
|
6219
|
+
async spawn(settingsPath, socketPath, opencodeExtras) {
|
|
6220
|
+
const resume = this.reportedOpenCodeId ?? this.resume;
|
|
5905
6221
|
const spec = this.adapter.buildSpawn({
|
|
5906
6222
|
options: this.options,
|
|
5907
|
-
...
|
|
6223
|
+
...resume ? { resume } : {},
|
|
5908
6224
|
...settingsPath ? { settingsPath } : {},
|
|
5909
6225
|
...socketPath ? { hookSocketPath: socketPath } : {},
|
|
5910
6226
|
// Only this config: ignore the user's ~/.claude.json / project .mcp.json
|
|
5911
6227
|
// so the agent's tool set is deterministic (and a stale personal conveyor
|
|
5912
6228
|
// server doesn't load).
|
|
5913
|
-
...this.mcpConfigPath ? { mcpConfigPath: this.mcpConfigPath } : {}
|
|
6229
|
+
...this.mcpConfigPath ? { mcpConfigPath: this.mcpConfigPath } : {},
|
|
6230
|
+
...opencodeExtras ?? {}
|
|
5914
6231
|
});
|
|
5915
6232
|
const spawn2 = await resolvePtySpawn();
|
|
5916
6233
|
const pty = spawn2(spec.file, spec.args, {
|
|
@@ -6283,7 +6600,7 @@ var PtySession = class {
|
|
|
6283
6600
|
// src/harness/pty/config-home-health.ts
|
|
6284
6601
|
import { lstat, mkdir as mkdir4, symlink, unlink as unlink2 } from "fs/promises";
|
|
6285
6602
|
import { homedir as homedir3 } from "os";
|
|
6286
|
-
import { join as
|
|
6603
|
+
import { join as join6 } from "path";
|
|
6287
6604
|
var MOUNT_DISCONNECT_CODES = /* @__PURE__ */ new Set(["ENOTCONN", "EIO", "ESTALE", "ENXIO"]);
|
|
6288
6605
|
var MOUNT_DISCONNECT_MESSAGES = [
|
|
6289
6606
|
"socket is not connected",
|
|
@@ -6299,10 +6616,10 @@ function isMountDisconnectError(err) {
|
|
|
6299
6616
|
return MOUNT_DISCONNECT_MESSAGES.some((needle) => lower.includes(needle));
|
|
6300
6617
|
}
|
|
6301
6618
|
function podLocalConfigHome() {
|
|
6302
|
-
return
|
|
6619
|
+
return join6(homedir3(), ".claude-local");
|
|
6303
6620
|
}
|
|
6304
6621
|
function sharedConfigHomePath() {
|
|
6305
|
-
return
|
|
6622
|
+
return join6(homedir3(), ".claude");
|
|
6306
6623
|
}
|
|
6307
6624
|
function isConfigHomeFallbackActive() {
|
|
6308
6625
|
return claudeConfigHome() === podLocalConfigHome();
|
|
@@ -6333,7 +6650,7 @@ async function repointSharedConfigHomeSymlink(fallback, log) {
|
|
|
6333
6650
|
}
|
|
6334
6651
|
async function isConfigHomeMountDead(cwd) {
|
|
6335
6652
|
try {
|
|
6336
|
-
await mkdir4(
|
|
6653
|
+
await mkdir4(join6(claudeConfigHome(), "projects", projectSlug(cwd)), { recursive: true });
|
|
6337
6654
|
return false;
|
|
6338
6655
|
} catch (err) {
|
|
6339
6656
|
return isMountDisconnectError(err);
|
|
@@ -6342,7 +6659,7 @@ async function isConfigHomeMountDead(cwd) {
|
|
|
6342
6659
|
async function ensureUsableClaudeConfigHome(cwd, log) {
|
|
6343
6660
|
const configHome = claudeConfigHome();
|
|
6344
6661
|
try {
|
|
6345
|
-
await mkdir4(
|
|
6662
|
+
await mkdir4(join6(configHome, "projects", projectSlug(cwd)), { recursive: true });
|
|
6346
6663
|
return { configHome, fellBack: false };
|
|
6347
6664
|
} catch (err) {
|
|
6348
6665
|
if (!isMountDisconnectError(err)) throw err;
|
|
@@ -6356,7 +6673,7 @@ async function ensureUsableClaudeConfigHome(cwd, log) {
|
|
|
6356
6673
|
}
|
|
6357
6674
|
);
|
|
6358
6675
|
process.env.CLAUDE_CONFIG_DIR = fallback;
|
|
6359
|
-
await mkdir4(
|
|
6676
|
+
await mkdir4(join6(fallback, "projects", projectSlug(cwd)), { recursive: true });
|
|
6360
6677
|
await repointSharedConfigHomeSymlink(fallback, log);
|
|
6361
6678
|
return { configHome: fallback, fellBack: true };
|
|
6362
6679
|
}
|
|
@@ -6437,7 +6754,7 @@ var PtyHarness = class _PtyHarness {
|
|
|
6437
6754
|
}
|
|
6438
6755
|
}
|
|
6439
6756
|
async *executeQuery(opts) {
|
|
6440
|
-
const want = opts.resume ?? opts.options.resume;
|
|
6757
|
+
const want = opts.resume ?? opts.options.resume ?? this.parked?.reportedSessionId ?? void 0;
|
|
6441
6758
|
const fingerprint = this.fingerprintOf(opts.options);
|
|
6442
6759
|
let session;
|
|
6443
6760
|
if (this.parked?.canReuse(want, fingerprint) && !await this.parkedHomeDied(opts.options)) {
|
|
@@ -6697,7 +7014,7 @@ import { spawn } from "child_process";
|
|
|
6697
7014
|
|
|
6698
7015
|
// src/harness/pty/adapters/types.ts
|
|
6699
7016
|
import { accessSync, constants, statSync } from "fs";
|
|
6700
|
-
import { join as
|
|
7017
|
+
import { join as join7 } from "path";
|
|
6701
7018
|
var TuiUnavailableError = class extends Error {
|
|
6702
7019
|
constructor(tui, message) {
|
|
6703
7020
|
super(message);
|
|
@@ -6721,7 +7038,7 @@ function findOnPath(binary, env = process.env) {
|
|
|
6721
7038
|
}
|
|
6722
7039
|
for (const dir of (env.PATH ?? "").split(":")) {
|
|
6723
7040
|
if (!dir) continue;
|
|
6724
|
-
const candidate =
|
|
7041
|
+
const candidate = join7(dir, binary);
|
|
6725
7042
|
if (isExecutable(candidate)) return candidate;
|
|
6726
7043
|
}
|
|
6727
7044
|
return null;
|
|
@@ -6729,18 +7046,18 @@ function findOnPath(binary, env = process.env) {
|
|
|
6729
7046
|
|
|
6730
7047
|
// src/harness/pty/adapters/opencode-auth.ts
|
|
6731
7048
|
import { promises as fs } from "fs";
|
|
6732
|
-
import { dirname as dirname2, join as
|
|
7049
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
6733
7050
|
import { homedir as homedir4 } from "os";
|
|
6734
7051
|
var logger = createServiceLogger("opencode-auth");
|
|
6735
7052
|
var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
|
|
6736
7053
|
var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
|
|
6737
7054
|
function opencodeAuthPath(env) {
|
|
6738
|
-
const dataHome = env.XDG_DATA_HOME ??
|
|
6739
|
-
return
|
|
7055
|
+
const dataHome = env.XDG_DATA_HOME ?? join8(env.HOME ?? homedir4(), ".local", "share");
|
|
7056
|
+
return join8(dataHome, "opencode", "auth.json");
|
|
6740
7057
|
}
|
|
6741
7058
|
function opencodeConfigPath(env) {
|
|
6742
|
-
const configHome = env.XDG_CONFIG_HOME ??
|
|
6743
|
-
return
|
|
7059
|
+
const configHome = env.XDG_CONFIG_HOME ?? join8(env.HOME ?? homedir4(), ".config");
|
|
7060
|
+
return join8(configHome, "opencode", "opencode.json");
|
|
6744
7061
|
}
|
|
6745
7062
|
function parseOauthSeed(b64) {
|
|
6746
7063
|
if (!b64) return null;
|
|
@@ -6883,7 +7200,7 @@ function truncatePrompt(prompt) {
|
|
|
6883
7200
|
[prompt truncated at ${PROMPT_MAX_CHARS} characters]`;
|
|
6884
7201
|
}
|
|
6885
7202
|
function buildOpenCodeConfigContent(input) {
|
|
6886
|
-
const { entries, systemPromptPath, readOnly } = input;
|
|
7203
|
+
const { entries, systemPromptPath, readOnly, pluginPath } = input;
|
|
6887
7204
|
const mcp = {};
|
|
6888
7205
|
for (const [name, entry] of Object.entries(entries)) {
|
|
6889
7206
|
if (entry.type === "http") {
|
|
@@ -6905,106 +7222,18 @@ function buildOpenCodeConfigContent(input) {
|
|
|
6905
7222
|
const config = {};
|
|
6906
7223
|
if (Object.keys(mcp).length > 0) config.mcp = mcp;
|
|
6907
7224
|
if (systemPromptPath) config.instructions = [systemPromptPath];
|
|
6908
|
-
if (
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
}
|
|
7225
|
+
if (pluginPath) config.plugin = [`file://${pluginPath}`];
|
|
7226
|
+
config.permission = {
|
|
7227
|
+
"*": "allow",
|
|
7228
|
+
...readOnly ? Object.fromEntries(MUTATING_TOOLS.map((tool2) => [tool2, "deny"])) : {}
|
|
7229
|
+
};
|
|
6914
7230
|
if (Object.keys(config).length === 0) return null;
|
|
6915
7231
|
return JSON.stringify(config);
|
|
6916
7232
|
}
|
|
6917
7233
|
|
|
6918
|
-
// src/harness/opencode/events.ts
|
|
6919
|
-
function parseOpenCodeLine(line) {
|
|
6920
|
-
const trimmed = line.trim();
|
|
6921
|
-
if (!trimmed.startsWith("{")) return null;
|
|
6922
|
-
try {
|
|
6923
|
-
return JSON.parse(trimmed);
|
|
6924
|
-
} catch {
|
|
6925
|
-
return null;
|
|
6926
|
-
}
|
|
6927
|
-
}
|
|
6928
|
-
function numberAt(source, ...keys) {
|
|
6929
|
-
let cursor = source;
|
|
6930
|
-
for (const key of keys) {
|
|
6931
|
-
if (typeof cursor !== "object" || cursor === null) return 0;
|
|
6932
|
-
cursor = cursor[key];
|
|
6933
|
-
}
|
|
6934
|
-
return typeof cursor === "number" && Number.isFinite(cursor) ? cursor : 0;
|
|
6935
|
-
}
|
|
6936
|
-
function accumulateUsage(event, into) {
|
|
6937
|
-
const part = event.part;
|
|
6938
|
-
if (!part) return;
|
|
6939
|
-
into.inputTokens += numberAt(part.tokens, "input");
|
|
6940
|
-
into.outputTokens += numberAt(part.tokens, "output");
|
|
6941
|
-
const cost = part.cost;
|
|
6942
|
-
if (typeof cost === "number" && Number.isFinite(cost)) into.totalCostUsd += cost;
|
|
6943
|
-
}
|
|
6944
|
-
function mapOpenCodeEvent(event) {
|
|
6945
|
-
const part = event.part;
|
|
6946
|
-
if (!part) return null;
|
|
6947
|
-
if (part.type === "text") return mapTextPart(part);
|
|
6948
|
-
if (part.type === "tool") return mapToolPart(part);
|
|
6949
|
-
return null;
|
|
6950
|
-
}
|
|
6951
|
-
function mapTextPart(part) {
|
|
6952
|
-
const text = typeof part.text === "string" ? part.text : "";
|
|
6953
|
-
if (text.trim() === "") return null;
|
|
6954
|
-
return {
|
|
6955
|
-
type: "assistant",
|
|
6956
|
-
message: { role: "assistant", content: [{ type: "text", text }] }
|
|
6957
|
-
};
|
|
6958
|
-
}
|
|
6959
|
-
function mapToolPart(part) {
|
|
6960
|
-
const status = part.state?.status;
|
|
6961
|
-
if (status !== "completed" && status !== "error") return null;
|
|
6962
|
-
return {
|
|
6963
|
-
type: "assistant",
|
|
6964
|
-
message: {
|
|
6965
|
-
role: "assistant",
|
|
6966
|
-
content: [
|
|
6967
|
-
{
|
|
6968
|
-
type: "tool_use",
|
|
6969
|
-
name: typeof part.tool === "string" ? part.tool : "unknown",
|
|
6970
|
-
input: part.state?.input ?? {},
|
|
6971
|
-
...typeof part.id === "string" ? { id: part.id } : {}
|
|
6972
|
-
}
|
|
6973
|
-
]
|
|
6974
|
-
}
|
|
6975
|
-
};
|
|
6976
|
-
}
|
|
6977
|
-
function errorMessageOf(event) {
|
|
6978
|
-
if (event.type !== "error") return null;
|
|
6979
|
-
const error = event.error;
|
|
6980
|
-
const message = typeof error?.data?.message === "string" ? error.data.message : null;
|
|
6981
|
-
const name = typeof error?.name === "string" ? error.name : "error";
|
|
6982
|
-
const status = typeof error?.data?.statusCode === "number" ? ` (HTTP ${error.data.statusCode})` : "";
|
|
6983
|
-
return message ? `${name}${status}: ${message}` : `${name}${status}`;
|
|
6984
|
-
}
|
|
6985
|
-
function sessionIdOf(event) {
|
|
6986
|
-
return typeof event.sessionID === "string" && event.sessionID.length > 0 ? event.sessionID : null;
|
|
6987
|
-
}
|
|
6988
|
-
function buildResultEvent(exitCode, usage, assistantText, stderrTail, reportedError) {
|
|
6989
|
-
if (exitCode === 0) {
|
|
6990
|
-
return {
|
|
6991
|
-
type: "result",
|
|
6992
|
-
subtype: "success",
|
|
6993
|
-
// The runner substitutes "Task completed." for an empty summary, so a run
|
|
6994
|
-
// that only used tools still reads sensibly in chat.
|
|
6995
|
-
result: assistantText,
|
|
6996
|
-
total_cost_usd: usage.totalCostUsd
|
|
6997
|
-
};
|
|
6998
|
-
}
|
|
6999
|
-
const errors = reportedError ? [reportedError] : [`opencode run exited with code ${exitCode}`];
|
|
7000
|
-
if (stderrTail.trim()) errors.push(`stderr:
|
|
7001
|
-
${stderrTail.trim()}`);
|
|
7002
|
-
return { type: "result", subtype: "error", errors };
|
|
7003
|
-
}
|
|
7004
|
-
|
|
7005
7234
|
// src/harness/opencode/index.ts
|
|
7006
|
-
import { mkdtemp as mkdtemp2, rm as rm3, writeFile as
|
|
7007
|
-
import { join as
|
|
7235
|
+
import { mkdtemp as mkdtemp2, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
7236
|
+
import { join as join9 } from "path";
|
|
7008
7237
|
var MAX_STDERR_TAIL = 4e3;
|
|
7009
7238
|
var OpenCodeHeadlessHarness = class {
|
|
7010
7239
|
/** NDJSON from `--format json` is a trusted structured source. */
|
|
@@ -7026,7 +7255,7 @@ var OpenCodeHeadlessHarness = class {
|
|
|
7026
7255
|
const prompt = await collectPrompt(opts.prompt);
|
|
7027
7256
|
const binary = resolveOpenCodeBinary(process.env);
|
|
7028
7257
|
await prepareOpenCodeCredentials(process.env);
|
|
7029
|
-
this.tempDir = await mkdtemp2(
|
|
7258
|
+
this.tempDir = await mkdtemp2(join9(sessionTempBase(), "opencode-headless-"));
|
|
7030
7259
|
const { servers, entries } = await startToolServers(
|
|
7031
7260
|
opts.options.mcpServers ?? {},
|
|
7032
7261
|
this.tempDir
|
|
@@ -7108,8 +7337,8 @@ var OpenCodeHeadlessHarness = class {
|
|
|
7108
7337
|
*/
|
|
7109
7338
|
async writeSystemPrompt(text) {
|
|
7110
7339
|
if (!text || text.trim() === "") return null;
|
|
7111
|
-
const path2 =
|
|
7112
|
-
await
|
|
7340
|
+
const path2 = join9(this.tempDir, "conveyor-instructions.md");
|
|
7341
|
+
await writeFile6(path2, text, "utf8");
|
|
7113
7342
|
return path2;
|
|
7114
7343
|
}
|
|
7115
7344
|
/**
|
|
@@ -7179,10 +7408,18 @@ var OpenCodeTuiAdapter = class {
|
|
|
7179
7408
|
env;
|
|
7180
7409
|
id = "opencode";
|
|
7181
7410
|
capabilities = {
|
|
7182
|
-
|
|
7183
|
-
|
|
7411
|
+
// `--session <ses_…>` continues the id the plugin events latched.
|
|
7412
|
+
resume: true,
|
|
7413
|
+
structuredEvents: true,
|
|
7414
|
+
// The input box tolerates paste-without-submit, but manual prefill has no
|
|
7415
|
+
// card users yet — keep the initial-query behavior unchanged for now.
|
|
7184
7416
|
prefill: false,
|
|
7185
|
-
|
|
7417
|
+
// Plugin events arriving while parked ARE the passive signal.
|
|
7418
|
+
passiveTurns: true,
|
|
7419
|
+
// opencode silently discards early stdin while the TUI paints; the pasted
|
|
7420
|
+
// text itself is lost, so the readiness probe must gate the first write
|
|
7421
|
+
// even though structured events exist now.
|
|
7422
|
+
rawPromptGate: true
|
|
7186
7423
|
};
|
|
7187
7424
|
resolveBinary(env = this.env) {
|
|
7188
7425
|
const override = env.CONVEYOR_OPENCODE_BIN;
|
|
@@ -7200,16 +7437,31 @@ var OpenCodeTuiAdapter = class {
|
|
|
7200
7437
|
const credentials = buildOpenCodeCredentialEnv(this.env);
|
|
7201
7438
|
for (const key of credentials.unset) delete env[key];
|
|
7202
7439
|
Object.assign(env, credentials.set);
|
|
7440
|
+
const configContent = buildOpenCodeConfigContent({
|
|
7441
|
+
entries: input.mcpEntries ?? {},
|
|
7442
|
+
systemPromptPath: input.instructionsPath ?? null,
|
|
7443
|
+
readOnly: input.options.permissionMode === "plan",
|
|
7444
|
+
pluginPath: input.pluginPath ?? null
|
|
7445
|
+
});
|
|
7446
|
+
if (configContent) env.OPENCODE_CONFIG_CONTENT = configContent;
|
|
7447
|
+
if (input.eventsSinkPath) env[OPENCODE_EVENTS_FILE_ENV] = input.eventsSinkPath;
|
|
7203
7448
|
const model = resolveOpenCodeModel(this.env, input.options.model);
|
|
7204
7449
|
const args = [];
|
|
7205
7450
|
if (model) args.push("--model", model);
|
|
7451
|
+
if (input.resume) args.push("--session", input.resume);
|
|
7206
7452
|
return { file: this.resolveBinary(), args, env };
|
|
7207
7453
|
}
|
|
7208
7454
|
async prepareEnvironment() {
|
|
7209
7455
|
await prepareOpenCodeCredentials(this.env);
|
|
7210
7456
|
}
|
|
7211
7457
|
spawnFingerprint(input) {
|
|
7212
|
-
return JSON.stringify([
|
|
7458
|
+
return JSON.stringify([
|
|
7459
|
+
"opencode",
|
|
7460
|
+
input.model,
|
|
7461
|
+
input.cwd,
|
|
7462
|
+
input.permissionMode,
|
|
7463
|
+
input.appendSystemPrompt ?? ""
|
|
7464
|
+
]);
|
|
7213
7465
|
}
|
|
7214
7466
|
encodePromptBytes(text) {
|
|
7215
7467
|
return buildPromptBytes(text);
|
|
@@ -10779,7 +11031,7 @@ function buildMutationTools(connection, config) {
|
|
|
10779
11031
|
}
|
|
10780
11032
|
|
|
10781
11033
|
// src/tools/attachment-tools.ts
|
|
10782
|
-
import { basename, extname, isAbsolute, join as
|
|
11034
|
+
import { basename, extname, isAbsolute, join as join10 } from "path";
|
|
10783
11035
|
var MIME_BY_EXT = {
|
|
10784
11036
|
".png": "image/png",
|
|
10785
11037
|
".jpg": "image/jpeg",
|
|
@@ -10830,7 +11082,7 @@ ${snippet}`;
|
|
|
10830
11082
|
function buildUploadAttachmentTool(connection, config) {
|
|
10831
11083
|
return defineContractTool(uploadAttachmentContract, async ({ path: path2, title, tags }) => {
|
|
10832
11084
|
try {
|
|
10833
|
-
const filePath = isAbsolute(path2) ? path2 :
|
|
11085
|
+
const filePath = isAbsolute(path2) ? path2 : join10(config.workspaceDir, path2);
|
|
10834
11086
|
const mimeType = inferMimeType(filePath);
|
|
10835
11087
|
const info = await statWorkspacePath(filePath);
|
|
10836
11088
|
if (!info.isFile) {
|
|
@@ -11342,7 +11594,7 @@ import { z as z16 } from "zod";
|
|
|
11342
11594
|
|
|
11343
11595
|
// src/execution/context-path-verifier.ts
|
|
11344
11596
|
import { readFile as readFile2 } from "fs/promises";
|
|
11345
|
-
import { isAbsolute as isAbsolute2, join as
|
|
11597
|
+
import { isAbsolute as isAbsolute2, join as join11, normalize } from "path";
|
|
11346
11598
|
var PROBLEM_TEXT = {
|
|
11347
11599
|
not_found: "does not exist in the repo",
|
|
11348
11600
|
expected_folder: "is a file, not a folder \u2014 use type 'file', 'rule', or 'doc'",
|
|
@@ -11387,7 +11639,7 @@ async function verifyContextPaths(links, workspaceDir) {
|
|
|
11387
11639
|
problems.push({ type: link.type, path: link.path, reason: shape });
|
|
11388
11640
|
continue;
|
|
11389
11641
|
}
|
|
11390
|
-
const absolutePath =
|
|
11642
|
+
const absolutePath = join11(workspaceDir, toRelativePath(link.path));
|
|
11391
11643
|
const stat = await statWorkspacePath(absolutePath);
|
|
11392
11644
|
const wantsDirectory = expectsDirectory(link.type);
|
|
11393
11645
|
if (!stat.exists) {
|
|
@@ -13680,9 +13932,9 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
13680
13932
|
|
|
13681
13933
|
// src/runner/query-bridge.ts
|
|
13682
13934
|
var logger6 = createServiceLogger("QueryBridge");
|
|
13683
|
-
function resolveHarnessKind(
|
|
13935
|
+
function resolveHarnessKind() {
|
|
13684
13936
|
if (process.env.CONVEYOR_FORCE_SDK_CARDS === "1") return "sdk";
|
|
13685
|
-
return
|
|
13937
|
+
return "pty";
|
|
13686
13938
|
}
|
|
13687
13939
|
function resolveCardTui(mode) {
|
|
13688
13940
|
if (mode === "code-review") return "claude-code";
|
|
@@ -13707,7 +13959,7 @@ var QueryBridge = class {
|
|
|
13707
13959
|
this.mode = mode;
|
|
13708
13960
|
this.runnerConfig = runnerConfig;
|
|
13709
13961
|
this.callbacks = callbacks;
|
|
13710
|
-
const harnessKind = resolveHarnessKind(
|
|
13962
|
+
const harnessKind = resolveHarnessKind();
|
|
13711
13963
|
this.harnessKind = harnessKind;
|
|
13712
13964
|
let bridge;
|
|
13713
13965
|
if (harnessKind === "pty") {
|
|
@@ -15844,12 +16096,12 @@ var SessionRunner = class _SessionRunner {
|
|
|
15844
16096
|
};
|
|
15845
16097
|
|
|
15846
16098
|
// src/setup/config.ts
|
|
15847
|
-
import { join as
|
|
16099
|
+
import { join as join12 } from "path";
|
|
15848
16100
|
var DEVCONTAINER_PATH = ".devcontainer/conveyor/devcontainer.json";
|
|
15849
16101
|
var DEVCONTAINER_PORT_DENY_LIST = /* @__PURE__ */ new Set([5432, 6379, 9200]);
|
|
15850
16102
|
async function loadForwardPorts(workspaceDir) {
|
|
15851
16103
|
try {
|
|
15852
|
-
const raw = await readWorkspaceFile(
|
|
16104
|
+
const raw = await readWorkspaceFile(join12(workspaceDir, DEVCONTAINER_PATH));
|
|
15853
16105
|
const parsed = JSON.parse(raw);
|
|
15854
16106
|
const ports = (parsed.forwardPorts ?? []).filter(
|
|
15855
16107
|
(p) => typeof p === "number" && !DEVCONTAINER_PORT_DENY_LIST.has(p)
|
|
@@ -15937,4 +16189,4 @@ export {
|
|
|
15937
16189
|
loadConveyorConfig,
|
|
15938
16190
|
unshallowRepo
|
|
15939
16191
|
};
|
|
15940
|
-
//# sourceMappingURL=chunk-
|
|
16192
|
+
//# sourceMappingURL=chunk-ZTLGCN5G.js.map
|