@rallycry/conveyor-agent 10.13.50 → 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-I353EAK6.js → boot-6X5ZZG3B.js} +3 -3
- package/dist/{chunk-3Z4YQNJV.js → chunk-DSP4KR4B.js} +2 -2
- package/dist/{chunk-XR6H326I.js → chunk-UAGIXPYD.js} +109 -1
- package/dist/chunk-UAGIXPYD.js.map +1 -0
- package/dist/{chunk-36VMMHYD.js → chunk-ZTLGCN5G.js} +1070 -209
- package/dist/chunk-ZTLGCN5G.js.map +1 -0
- package/dist/cli.js +73 -241
- package/dist/cli.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-NIOBJ46G.js → server-EQUCOLNY.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-36VMMHYD.js.map +0 -1
- package/dist/chunk-XR6H326I.js.map +0 -1
- /package/dist/{boot-I353EAK6.js.map → boot-6X5ZZG3B.js.map} +0 -0
- /package/dist/{chunk-3Z4YQNJV.js.map → chunk-DSP4KR4B.js.map} +0 -0
- /package/dist/{server-NIOBJ46G.js.map → server-EQUCOLNY.js.map} +0 -0
|
@@ -26,21 +26,30 @@ import {
|
|
|
26
26
|
import {
|
|
27
27
|
MAX_BETWEEN_TURN_BUFFER,
|
|
28
28
|
MAX_DIAGNOSTIC_OUTPUT,
|
|
29
|
+
buildExitErrors,
|
|
29
30
|
buildPromptBytes,
|
|
31
|
+
buildSpawnArgs,
|
|
32
|
+
cleanTerminalOutput,
|
|
30
33
|
inheritedEnv,
|
|
31
34
|
killPtyWithEscalation,
|
|
35
|
+
needsRawReadyGate,
|
|
32
36
|
parseUserQuestions,
|
|
33
37
|
renderPromptContentText,
|
|
38
|
+
resolveClaudeBinary,
|
|
34
39
|
resolvePlanDialogTiming,
|
|
35
40
|
resolvePtySpawn,
|
|
41
|
+
resolveRawTuiProbeTiming,
|
|
36
42
|
resolveSubmitNudgeTiming,
|
|
37
43
|
resolveSubmitRedeliveryMaxAttempts,
|
|
38
44
|
resolveSubmitSettleMs,
|
|
45
|
+
sawTerminalSetup,
|
|
46
|
+
sentinelEchoed,
|
|
39
47
|
sessionTempBase,
|
|
40
48
|
sleep,
|
|
49
|
+
spawnOptionsFingerprint,
|
|
41
50
|
transcriptSize,
|
|
42
51
|
turnOptionsFrom
|
|
43
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-UAGIXPYD.js";
|
|
44
53
|
|
|
45
54
|
// src/setup/bootstrap.ts
|
|
46
55
|
var BOOTSTRAP_TIMEOUT_MS = 3e4;
|
|
@@ -1728,7 +1737,11 @@ import { z as z6 } from "zod";
|
|
|
1728
1737
|
import { z as z7 } from "zod";
|
|
1729
1738
|
var EXTERNAL_AGENT_MESSAGE_SOURCE = "external_agent";
|
|
1730
1739
|
var DEFAULT_SONNET_MODEL = "claude-sonnet-5";
|
|
1740
|
+
var DEFAULT_OPUS_MODEL = "claude-opus-5";
|
|
1741
|
+
var DEFAULT_HAIKU_MODEL = "claude-haiku-4-5-20251001";
|
|
1731
1742
|
var FABLE_MODEL = "claude-fable-5";
|
|
1743
|
+
var PREVIOUS_SONNET_MODEL = "claude-sonnet-4-6";
|
|
1744
|
+
var PREVIOUS_OPUS_MODEL = "claude-opus-4-8";
|
|
1732
1745
|
var PTY_STREAM_PORT_BASE = 7420;
|
|
1733
1746
|
var PTY_STREAM_PORT_ATTEMPTS = 8;
|
|
1734
1747
|
function encodePtyStreamFrame(frame) {
|
|
@@ -1800,6 +1813,14 @@ var checkpointPathSchema = z.string().transform(normalizeCheckpointPath).pipe(
|
|
|
1800
1813
|
var secretNameSchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/);
|
|
1801
1814
|
var checkpointKeySchema = z.string().regex(/^[0-9a-f]{64}$/);
|
|
1802
1815
|
var checkpointDigestRefSchema = z.string().regex(/^[^\s@]+@sha256:[0-9a-f]{64}$/);
|
|
1816
|
+
var ACTIONS_PREBAKE_REGISTRY_PATTERN = /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::(?:[1-9][0-9]{0,4}))?(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$/;
|
|
1817
|
+
var actionsPrebakeRegistrySchema = z.string().trim().min(1).regex(
|
|
1818
|
+
ACTIONS_PREBAKE_REGISTRY_PATTERN,
|
|
1819
|
+
"Actions prebake registry must be a lowercase host[:port] with an optional path prefix"
|
|
1820
|
+
).refine((value) => {
|
|
1821
|
+
const port = /:([0-9]+)(?:\/|$)/.exec(value)?.[1];
|
|
1822
|
+
return !port || Number(port) <= 65535;
|
|
1823
|
+
}, "Actions prebake registry port must be between 1 and 65535");
|
|
1803
1824
|
function uniqueSortedArray(item, minimum = 0) {
|
|
1804
1825
|
return z.array(item).min(minimum).superRefine((values, ctx) => {
|
|
1805
1826
|
if (new Set(values).size !== values.length) {
|
|
@@ -3174,6 +3195,30 @@ var AGENT_STATUS_REASON_USER_QUESTION = "user_question";
|
|
|
3174
3195
|
var TASK_CHAT_HISTORY_LIMIT = 20;
|
|
3175
3196
|
var PM_CHAT_HISTORY_LIMIT = 40;
|
|
3176
3197
|
var AGENT_CHAT_HISTORY_FETCH_LIMIT = Math.max(TASK_CHAT_HISTORY_LIMIT, PM_CHAT_HISTORY_LIMIT) + 10;
|
|
3198
|
+
function formatModelId(provider, model) {
|
|
3199
|
+
return `${provider}/${model}`;
|
|
3200
|
+
}
|
|
3201
|
+
function anthropicEntry(model, label, inputPerMillion, outputPerMillion, experimental = false) {
|
|
3202
|
+
return {
|
|
3203
|
+
provider: "anthropic",
|
|
3204
|
+
model,
|
|
3205
|
+
id: formatModelId("anthropic", model),
|
|
3206
|
+
label,
|
|
3207
|
+
format: "anthropic-messages",
|
|
3208
|
+
inputPrice: inputPerMillion / 1e6,
|
|
3209
|
+
outputPrice: outputPerMillion / 1e6,
|
|
3210
|
+
supportsTools: true,
|
|
3211
|
+
...experimental ? { experimental: true } : {}
|
|
3212
|
+
};
|
|
3213
|
+
}
|
|
3214
|
+
var ANTHROPIC_CATALOG = [
|
|
3215
|
+
anthropicEntry(DEFAULT_OPUS_MODEL, "Opus 5 Latest", 15, 75),
|
|
3216
|
+
anthropicEntry(PREVIOUS_OPUS_MODEL, "Opus 4.8", 15, 75),
|
|
3217
|
+
anthropicEntry(DEFAULT_SONNET_MODEL, "Sonnet 5 Latest", 3, 15),
|
|
3218
|
+
anthropicEntry(PREVIOUS_SONNET_MODEL, "Sonnet 4.6", 3, 15),
|
|
3219
|
+
anthropicEntry(DEFAULT_HAIKU_MODEL, "Haiku 4.5", 0.8, 4),
|
|
3220
|
+
anthropicEntry(FABLE_MODEL, "Fable 5 (experimental)", 10, 50, true)
|
|
3221
|
+
];
|
|
3177
3222
|
var HUMAN_PROSE_WRITING_STYLE = `## Writing style for humans
|
|
3178
3223
|
When you write prose a person will read \u2014 chat messages, plan updates, PR titles and bodies, PR review guides (the \`publish_review_guide\` overview and section explanations), review comments \u2014 follow these rules (based on ASD-STE100 Simplified Technical English):
|
|
3179
3224
|
- Use active voice. Say who does what ("The API rejects the request", not "the request is rejected").
|
|
@@ -3760,6 +3805,8 @@ function defineTool(name, description, schema, handler, options) {
|
|
|
3760
3805
|
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
|
|
3761
3806
|
import { z as z8 } from "zod";
|
|
3762
3807
|
var ClaudeCodeHarness = class {
|
|
3808
|
+
/** The SDK stream is itself the structured-event source. */
|
|
3809
|
+
emitsStructuredEvents = true;
|
|
3763
3810
|
async *executeQuery(opts) {
|
|
3764
3811
|
const sdkEvents = query({
|
|
3765
3812
|
prompt: opts.prompt,
|
|
@@ -3799,8 +3846,237 @@ var ClaudeCodeHarness = class {
|
|
|
3799
3846
|
|
|
3800
3847
|
// src/harness/pty/session.ts
|
|
3801
3848
|
import { randomUUID } from "crypto";
|
|
3802
|
-
import { mkdtemp, mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
3803
|
-
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
|
+
}
|
|
3804
4080
|
|
|
3805
4081
|
// src/harness/pty/event-queue.ts
|
|
3806
4082
|
var AsyncEventQueue = class {
|
|
@@ -4081,14 +4357,16 @@ function mapTranscriptRecord(raw) {
|
|
|
4081
4357
|
// src/harness/pty/jsonl-tailer.ts
|
|
4082
4358
|
var POLL_INTERVAL_MS = 25;
|
|
4083
4359
|
var JsonlTailer = class {
|
|
4084
|
-
constructor(path2, onEvent, onRawRecord) {
|
|
4360
|
+
constructor(path2, onEvent, onRawRecord, mapRecord = mapTranscriptRecord) {
|
|
4085
4361
|
this.path = path2;
|
|
4086
4362
|
this.onEvent = onEvent;
|
|
4087
4363
|
this.onRawRecord = onRawRecord;
|
|
4364
|
+
this.mapRecord = mapRecord;
|
|
4088
4365
|
}
|
|
4089
4366
|
path;
|
|
4090
4367
|
onEvent;
|
|
4091
4368
|
onRawRecord;
|
|
4369
|
+
mapRecord;
|
|
4092
4370
|
offset = 0;
|
|
4093
4371
|
buffer = "";
|
|
4094
4372
|
timer = null;
|
|
@@ -4161,7 +4439,7 @@ var JsonlTailer = class {
|
|
|
4161
4439
|
return;
|
|
4162
4440
|
}
|
|
4163
4441
|
this.onRawRecord?.(parsed);
|
|
4164
|
-
const event =
|
|
4442
|
+
const event = this.mapRecord(parsed);
|
|
4165
4443
|
if (event) this.onEvent(event);
|
|
4166
4444
|
}
|
|
4167
4445
|
};
|
|
@@ -4354,17 +4632,17 @@ function mapChatRecords(raw) {
|
|
|
4354
4632
|
}
|
|
4355
4633
|
|
|
4356
4634
|
// src/harness/pty/settings.ts
|
|
4357
|
-
import { mkdir, writeFile, chmod } from "fs/promises";
|
|
4635
|
+
import { mkdir, writeFile as writeFile2, chmod } from "fs/promises";
|
|
4358
4636
|
import { homedir } from "os";
|
|
4359
|
-
import { join } from "path";
|
|
4637
|
+
import { join as join2 } from "path";
|
|
4360
4638
|
function claudeConfigHome() {
|
|
4361
|
-
return process.env.CLAUDE_CONFIG_DIR ??
|
|
4639
|
+
return process.env.CLAUDE_CONFIG_DIR ?? join2(homedir(), ".claude");
|
|
4362
4640
|
}
|
|
4363
4641
|
function projectSlug(cwd) {
|
|
4364
4642
|
return cwd.replace(/\//g, "-");
|
|
4365
4643
|
}
|
|
4366
4644
|
function sessionTranscriptPath(cwd, sessionId) {
|
|
4367
|
-
return
|
|
4645
|
+
return join2(claudeConfigHome(), "projects", projectSlug(cwd), `${sessionId}.jsonl`);
|
|
4368
4646
|
}
|
|
4369
4647
|
var ALLOW_RULES = [
|
|
4370
4648
|
"Bash",
|
|
@@ -4567,10 +4845,10 @@ function buildPreToolUseHooks(helperPath, gateAllTools) {
|
|
|
4567
4845
|
];
|
|
4568
4846
|
}
|
|
4569
4847
|
async function writeHookSettings(dir, opts = {}) {
|
|
4570
|
-
const helperPath =
|
|
4571
|
-
const settingsPath =
|
|
4848
|
+
const helperPath = join2(dir, "hook-helper.cjs");
|
|
4849
|
+
const settingsPath = join2(dir, "settings.json");
|
|
4572
4850
|
await mkdir(dir, { recursive: true });
|
|
4573
|
-
await
|
|
4851
|
+
await writeFile2(helperPath, HOOK_HELPER_SOURCE, "utf8");
|
|
4574
4852
|
await chmod(helperPath, 493);
|
|
4575
4853
|
const settings = {
|
|
4576
4854
|
// Pre-accept Claude Code's "Bypass Permissions mode" disclaimer. Build-capable
|
|
@@ -4607,80 +4885,10 @@ async function writeHookSettings(dir, opts = {}) {
|
|
|
4607
4885
|
]
|
|
4608
4886
|
}
|
|
4609
4887
|
};
|
|
4610
|
-
await
|
|
4888
|
+
await writeFile2(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
4611
4889
|
return { settingsPath, helperPath };
|
|
4612
4890
|
}
|
|
4613
4891
|
|
|
4614
|
-
// src/harness/pty/spawn-args.ts
|
|
4615
|
-
function resolveClaudeBinary() {
|
|
4616
|
-
return process.env.CONVEYOR_CLAUDE_BIN ?? "claude";
|
|
4617
|
-
}
|
|
4618
|
-
function buildSpawnArgs(input) {
|
|
4619
|
-
const args = [];
|
|
4620
|
-
if (input.resume) {
|
|
4621
|
-
args.push("--resume", input.resume);
|
|
4622
|
-
} else if (input.sessionId) {
|
|
4623
|
-
args.push("--session-id", input.sessionId);
|
|
4624
|
-
}
|
|
4625
|
-
args.push("--model", input.model);
|
|
4626
|
-
if (input.permissionMode === "bypassPermissions") {
|
|
4627
|
-
args.push("--dangerously-skip-permissions");
|
|
4628
|
-
} else {
|
|
4629
|
-
args.push("--permission-mode", "plan");
|
|
4630
|
-
}
|
|
4631
|
-
args.push("--settings", input.settingsPath);
|
|
4632
|
-
if (input.appendSystemPrompt) {
|
|
4633
|
-
args.push("--append-system-prompt", input.appendSystemPrompt);
|
|
4634
|
-
}
|
|
4635
|
-
if (input.mcpConfigPath) {
|
|
4636
|
-
args.push("--mcp-config", input.mcpConfigPath);
|
|
4637
|
-
if (input.strictMcpConfig) {
|
|
4638
|
-
args.push("--strict-mcp-config");
|
|
4639
|
-
}
|
|
4640
|
-
}
|
|
4641
|
-
return args;
|
|
4642
|
-
}
|
|
4643
|
-
function spawnOptionsFingerprint(input) {
|
|
4644
|
-
return JSON.stringify([
|
|
4645
|
-
input.model,
|
|
4646
|
-
input.permissionMode,
|
|
4647
|
-
input.appendSystemPrompt ?? "",
|
|
4648
|
-
input.cwd
|
|
4649
|
-
]);
|
|
4650
|
-
}
|
|
4651
|
-
var ANSI_CSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`, "g");
|
|
4652
|
-
function cleanTerminalOutput(raw, maxChars = 1200) {
|
|
4653
|
-
const noAnsi = raw.replace(ANSI_CSI, "");
|
|
4654
|
-
let out = "";
|
|
4655
|
-
for (const ch of noAnsi) {
|
|
4656
|
-
const code = ch.charCodeAt(0);
|
|
4657
|
-
if (ch === "\r" || ch === "\n") out += "\n";
|
|
4658
|
-
else if (ch === " ") out += ch;
|
|
4659
|
-
else if (code < 32 || code === 127) continue;
|
|
4660
|
-
else out += ch;
|
|
4661
|
-
}
|
|
4662
|
-
const lines = out.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
|
|
4663
|
-
const text = lines.join("\n").trim();
|
|
4664
|
-
return text.length > maxChars ? `\u2026${text.slice(-maxChars)}` : text;
|
|
4665
|
-
}
|
|
4666
|
-
function isMissingBinaryFailure(tail) {
|
|
4667
|
-
return /execvp\(\d+\) failed|no such file or directory|command not found/i.test(tail);
|
|
4668
|
-
}
|
|
4669
|
-
function buildExitErrors(exitCode, rawOutput, binary) {
|
|
4670
|
-
const errors = [`claude exited (code ${exitCode}) without a result`];
|
|
4671
|
-
const tail = cleanTerminalOutput(rawOutput);
|
|
4672
|
-
if (isMissingBinaryFailure(tail)) {
|
|
4673
|
-
errors.push(
|
|
4674
|
-
`The \`${binary}\` CLI could not be started \u2014 it is not installed or not on PATH. Install the Claude Code CLI in this environment (npm i -g @anthropic-ai/claude-code) or set CONVEYOR_CLAUDE_BIN to its absolute path.`
|
|
4675
|
-
);
|
|
4676
|
-
}
|
|
4677
|
-
if (tail) {
|
|
4678
|
-
errors.push(`Last terminal output before exit:
|
|
4679
|
-
${tail}`);
|
|
4680
|
-
}
|
|
4681
|
-
return errors;
|
|
4682
|
-
}
|
|
4683
|
-
|
|
4684
4892
|
// src/execution/redactor.ts
|
|
4685
4893
|
var REDACTED = "<redacted>";
|
|
4686
4894
|
var BEARER_RE = /\b(Bearer\s+)[A-Za-z0-9_\-.]{20,}/g;
|
|
@@ -4795,8 +5003,8 @@ var PtyOutputCoalescer = class {
|
|
|
4795
5003
|
// src/harness/pty/tool-server.ts
|
|
4796
5004
|
import { createServer as createServer2 } from "http";
|
|
4797
5005
|
import { z as z9 } from "zod";
|
|
4798
|
-
import { writeFile as
|
|
4799
|
-
import { join as
|
|
5006
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
5007
|
+
import { join as join3 } from "path";
|
|
4800
5008
|
import { randomBytes } from "crypto";
|
|
4801
5009
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4802
5010
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -4986,20 +5194,20 @@ async function startToolServers(mcpServers, tempDir) {
|
|
|
4986
5194
|
servers.push(server);
|
|
4987
5195
|
config[name] = { type: "http", url, headers: { Authorization: `Bearer ${token}` } };
|
|
4988
5196
|
}
|
|
4989
|
-
if (Object.keys(config).length === 0) return { servers, mcpConfigPath: null };
|
|
4990
|
-
const mcpConfigPath =
|
|
4991
|
-
await
|
|
4992
|
-
return { servers, mcpConfigPath };
|
|
5197
|
+
if (Object.keys(config).length === 0) return { servers, mcpConfigPath: null, entries: config };
|
|
5198
|
+
const mcpConfigPath = join3(tempDir, "mcp-config.json");
|
|
5199
|
+
await writeFile3(mcpConfigPath, JSON.stringify({ mcpServers: config }, null, 2), "utf8");
|
|
5200
|
+
return { servers, mcpConfigPath, entries: config };
|
|
4993
5201
|
}
|
|
4994
5202
|
|
|
4995
5203
|
// src/harness/pty/credentials.ts
|
|
4996
|
-
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";
|
|
4997
5205
|
import { homedir as homedir2 } from "os";
|
|
4998
|
-
import { join as
|
|
5206
|
+
import { join as join4 } from "path";
|
|
4999
5207
|
var SYNTH_TOKEN_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
5000
5208
|
var REFRESH_SKEW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
5001
5209
|
function claudeCredentialsPath() {
|
|
5002
|
-
return
|
|
5210
|
+
return join4(claudeConfigHome(), ".credentials.json");
|
|
5003
5211
|
}
|
|
5004
5212
|
function isConveyorCloudEnv(env = process.env) {
|
|
5005
5213
|
return Boolean(env.CLAUDESPACE_NAME || env.CODESPACE_NAME || env.CODESPACES);
|
|
@@ -5090,7 +5298,7 @@ async function writeWithReadBackRetry(io2, contents, delaysMs = READ_BACK_DELAYS
|
|
|
5090
5298
|
}
|
|
5091
5299
|
function fsWriteIo(path2, mode) {
|
|
5092
5300
|
return {
|
|
5093
|
-
write: (contents) =>
|
|
5301
|
+
write: (contents) => writeFile4(path2, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode }),
|
|
5094
5302
|
read: () => readRaw(path2)
|
|
5095
5303
|
};
|
|
5096
5304
|
}
|
|
@@ -5162,7 +5370,7 @@ async function sanitizeApprovedApiKeys(oauthToken) {
|
|
|
5162
5370
|
}
|
|
5163
5371
|
function claudeJsonPath() {
|
|
5164
5372
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
5165
|
-
return configDir ?
|
|
5373
|
+
return configDir ? join4(configDir, ".claude.json") : join4(homedir2(), ".claude.json");
|
|
5166
5374
|
}
|
|
5167
5375
|
function asRecord(value) {
|
|
5168
5376
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
@@ -5255,7 +5463,7 @@ function planClaudeJsonSeed(existingRaw, trustCwd, oauthIdentity) {
|
|
|
5255
5463
|
return changed ? JSON.stringify(config) : null;
|
|
5256
5464
|
}
|
|
5257
5465
|
function conveyorOauthMarkerPath() {
|
|
5258
|
-
return
|
|
5466
|
+
return join4(claudeConfigHome(), "conveyor-oauth-account.json");
|
|
5259
5467
|
}
|
|
5260
5468
|
function parseOauthIdentity(raw) {
|
|
5261
5469
|
if (!raw || raw.trim() === "") return null;
|
|
@@ -5339,7 +5547,8 @@ var ClaudeTuiAdapter = class {
|
|
|
5339
5547
|
resume: true,
|
|
5340
5548
|
structuredEvents: true,
|
|
5341
5549
|
prefill: true,
|
|
5342
|
-
passiveTurns: true
|
|
5550
|
+
passiveTurns: true,
|
|
5551
|
+
rawPromptGate: false
|
|
5343
5552
|
};
|
|
5344
5553
|
resolveBinary(env = process.env) {
|
|
5345
5554
|
return env.CONVEYOR_CLAUDE_BIN ?? "claude";
|
|
@@ -5415,6 +5624,12 @@ var PtySession = class {
|
|
|
5415
5624
|
lastTurnCleanResult = false;
|
|
5416
5625
|
socket = null;
|
|
5417
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;
|
|
5418
5633
|
pty = null;
|
|
5419
5634
|
tempDir = "";
|
|
5420
5635
|
sawResult = false;
|
|
@@ -5425,6 +5640,20 @@ var PtySession = class {
|
|
|
5425
5640
|
recentOutput = "";
|
|
5426
5641
|
coalescer = null;
|
|
5427
5642
|
_toreDown = false;
|
|
5643
|
+
// Raw-TUI input detection (see awaitRawTuiInputLive). `probeWindow`, when
|
|
5644
|
+
// non-null, accumulates raw output so a probe can look for its own sentinel
|
|
5645
|
+
// coming back.
|
|
5646
|
+
spawnedAt = 0;
|
|
5647
|
+
// Set once the child writes a DEC private mode — it owns the tty from then on,
|
|
5648
|
+
// so the kernel no longer echoes our keystrokes and a sentinel coming back is
|
|
5649
|
+
// attributable to the app's own repaint.
|
|
5650
|
+
sawTerminalSetup = false;
|
|
5651
|
+
probeWindow = null;
|
|
5652
|
+
// Whether this process has already taken a prompt write. Only the FIRST one
|
|
5653
|
+
// waits on readiness: once the TUI is up, later writes (a multi-message
|
|
5654
|
+
// prompt, a follow-up turn) must go straight through — output is flowing
|
|
5655
|
+
// while the model works, so a quiet check would block until the turn ended.
|
|
5656
|
+
wroteToProcess = false;
|
|
5428
5657
|
exitListeners = [];
|
|
5429
5658
|
idleListeners = [];
|
|
5430
5659
|
abortHandler = null;
|
|
@@ -5516,9 +5745,18 @@ var PtySession = class {
|
|
|
5516
5745
|
const tail = cleanTerminalOutput(this.recentOutput, MAX_DIAGNOSTIC_OUTPUT);
|
|
5517
5746
|
return tail ? redact(tail).output : "";
|
|
5518
5747
|
}
|
|
5519
|
-
/**
|
|
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
|
+
*/
|
|
5520
5754
|
get sessionUuid() {
|
|
5521
|
-
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;
|
|
5522
5760
|
}
|
|
5523
5761
|
/** Fingerprint of the spawn-time options a reused process cannot change. */
|
|
5524
5762
|
get spawnFingerprint() {
|
|
@@ -5533,9 +5771,19 @@ var PtySession = class {
|
|
|
5533
5771
|
* Whether this parked process can serve a turn wanting `resume`/`fingerprint`.
|
|
5534
5772
|
* Requires: live (not torn down / not exited), idle (no turn draining), a
|
|
5535
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.
|
|
5536
5783
|
*/
|
|
5537
5784
|
canReuse(resume, fingerprint) {
|
|
5538
|
-
|
|
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;
|
|
5539
5787
|
}
|
|
5540
5788
|
get hookSocketPath() {
|
|
5541
5789
|
return this.socket ? this.socket.socketPath : null;
|
|
@@ -5663,11 +5911,14 @@ var PtySession = class {
|
|
|
5663
5911
|
return;
|
|
5664
5912
|
}
|
|
5665
5913
|
try {
|
|
5666
|
-
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) {
|
|
5667
5918
|
const { settingsPath, socketPath } = await this.startStructuredEventSources(sessionId);
|
|
5668
5919
|
await this.spawn(settingsPath, socketPath);
|
|
5669
5920
|
} else {
|
|
5670
|
-
this.tempDir = await mkdtemp(
|
|
5921
|
+
this.tempDir = await mkdtemp(join5(sessionTempBase(), "conveyor-pty-"));
|
|
5671
5922
|
await this.spawn();
|
|
5672
5923
|
this.pushEvent({
|
|
5673
5924
|
type: "system",
|
|
@@ -5696,6 +5947,61 @@ var PtySession = class {
|
|
|
5696
5947
|
throw err;
|
|
5697
5948
|
}
|
|
5698
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
|
+
}
|
|
5699
6005
|
/**
|
|
5700
6006
|
* Allocate the Claude-style structured-event sources: the PostToolUse hook
|
|
5701
6007
|
* socket, the per-run settings file, the in-process tool servers, and the
|
|
@@ -5704,8 +6010,8 @@ var PtySession = class {
|
|
|
5704
6010
|
* paths spawn() must wire into the child's argv/env.
|
|
5705
6011
|
*/
|
|
5706
6012
|
async startStructuredEventSources(sessionId) {
|
|
5707
|
-
this.tempDir = await mkdtemp(
|
|
5708
|
-
const socketPath =
|
|
6013
|
+
this.tempDir = await mkdtemp(join5(sessionTempBase(), "conveyor-pty-"));
|
|
6014
|
+
const socketPath = join5(this.tempDir, "hook.sock");
|
|
5709
6015
|
this.socket = new HookSocketServer(
|
|
5710
6016
|
socketPath,
|
|
5711
6017
|
(progress) => this.handleProgress(progress),
|
|
@@ -5910,19 +6216,21 @@ var PtySession = class {
|
|
|
5910
6216
|
this.toolServers = servers;
|
|
5911
6217
|
this.mcpConfigPath = mcpConfigPath;
|
|
5912
6218
|
}
|
|
5913
|
-
async spawn(settingsPath, socketPath) {
|
|
6219
|
+
async spawn(settingsPath, socketPath, opencodeExtras) {
|
|
6220
|
+
const resume = this.reportedOpenCodeId ?? this.resume;
|
|
5914
6221
|
const spec = this.adapter.buildSpawn({
|
|
5915
6222
|
options: this.options,
|
|
5916
|
-
...
|
|
6223
|
+
...resume ? { resume } : {},
|
|
5917
6224
|
...settingsPath ? { settingsPath } : {},
|
|
5918
6225
|
...socketPath ? { hookSocketPath: socketPath } : {},
|
|
5919
6226
|
// Only this config: ignore the user's ~/.claude.json / project .mcp.json
|
|
5920
6227
|
// so the agent's tool set is deterministic (and a stale personal conveyor
|
|
5921
6228
|
// server doesn't load).
|
|
5922
|
-
...this.mcpConfigPath ? { mcpConfigPath: this.mcpConfigPath } : {}
|
|
6229
|
+
...this.mcpConfigPath ? { mcpConfigPath: this.mcpConfigPath } : {},
|
|
6230
|
+
...opencodeExtras ?? {}
|
|
5923
6231
|
});
|
|
5924
|
-
const
|
|
5925
|
-
const pty =
|
|
6232
|
+
const spawn2 = await resolvePtySpawn();
|
|
6233
|
+
const pty = spawn2(spec.file, spec.args, {
|
|
5926
6234
|
name: "xterm-color",
|
|
5927
6235
|
cols: this.cols,
|
|
5928
6236
|
rows: this.rows,
|
|
@@ -5940,15 +6248,101 @@ var PtySession = class {
|
|
|
5940
6248
|
}
|
|
5941
6249
|
pty.onData((data) => {
|
|
5942
6250
|
this.coalescer?.write(data);
|
|
6251
|
+
if (!this.sawTerminalSetup && sawTerminalSetup(data)) this.sawTerminalSetup = true;
|
|
6252
|
+
if (this.probeWindow !== null) this.probeWindow += data;
|
|
5943
6253
|
this.recentOutput = (this.recentOutput + data).slice(-MAX_DIAGNOSTIC_OUTPUT);
|
|
5944
6254
|
});
|
|
5945
6255
|
pty.onExit((event) => {
|
|
5946
6256
|
void this.finalizeOnExit(event.exitCode);
|
|
5947
6257
|
});
|
|
6258
|
+
this.spawnedAt = Date.now();
|
|
6259
|
+
this.sawTerminalSetup = false;
|
|
6260
|
+
this.probeWindow = null;
|
|
6261
|
+
this.wroteToProcess = false;
|
|
5948
6262
|
this.pty = pty;
|
|
5949
6263
|
}
|
|
6264
|
+
/** Open a fresh probe-observation window over raw output. */
|
|
6265
|
+
openProbeWindow() {
|
|
6266
|
+
this.probeWindow = "";
|
|
6267
|
+
}
|
|
6268
|
+
/**
|
|
6269
|
+
* Write `bytes`, then wait up to `ackMs` for the TUI to render `sentinel` back.
|
|
6270
|
+
* Positive proof that a live input box consumed the keystrokes.
|
|
6271
|
+
*/
|
|
6272
|
+
async writeAndAwaitEcho(bytes, sentinel, timing) {
|
|
6273
|
+
this.openProbeWindow();
|
|
6274
|
+
this.writeStdin(bytes);
|
|
6275
|
+
const deadline = Date.now() + timing.ackMs;
|
|
6276
|
+
while (Date.now() < deadline) {
|
|
6277
|
+
if (this._toreDown) return false;
|
|
6278
|
+
if (sentinelEchoed(this.probeWindow ?? "", sentinel)) return true;
|
|
6279
|
+
await sleep(timing.pollMs);
|
|
6280
|
+
}
|
|
6281
|
+
return false;
|
|
6282
|
+
}
|
|
6283
|
+
/**
|
|
6284
|
+
* Wait until a raw-relay TUI's input box is DEMONSTRABLY accepting keystrokes,
|
|
6285
|
+
* by typing a short sentinel and watching for the TUI to render it back, then
|
|
6286
|
+
* erasing it. Readiness is detected, never inferred from elapsed time.
|
|
6287
|
+
*
|
|
6288
|
+
* A structured-events TUI does not need this: `armSubmitNudge` re-presses
|
|
6289
|
+
* Enter until a transcript record proves the turn started. A raw adapter has
|
|
6290
|
+
* no such evidence, so the nudge is deliberately never armed for it — its
|
|
6291
|
+
* single paste + Enter is the only shot, and one fired during startup is
|
|
6292
|
+
* discarded with no trace, leaving the card parked on an empty input box.
|
|
6293
|
+
*
|
|
6294
|
+
* See resolveRawTuiProbeTiming for the measurements behind this, including the
|
|
6295
|
+
* signals that were tried and rejected (DECSET 2004, output-quiet windows, and
|
|
6296
|
+
* "any output after a keystroke" — all of which report ready too early).
|
|
6297
|
+
*/
|
|
6298
|
+
async awaitRawTuiInputLive() {
|
|
6299
|
+
if (!needsRawReadyGate(this.adapter.capabilities)) return;
|
|
6300
|
+
const timing = resolveRawTuiProbeTiming();
|
|
6301
|
+
const { sentinel } = timing;
|
|
6302
|
+
const start = this.spawnedAt || Date.now();
|
|
6303
|
+
const setupDeadline = start + timing.firstOutputMaxMs;
|
|
6304
|
+
while (!this._toreDown && Date.now() < setupDeadline) {
|
|
6305
|
+
if (this.sawTerminalSetup) break;
|
|
6306
|
+
await sleep(timing.pollMs);
|
|
6307
|
+
}
|
|
6308
|
+
if (this._toreDown) return;
|
|
6309
|
+
const deadline = start + timing.maxMs;
|
|
6310
|
+
let live = false;
|
|
6311
|
+
let probes = 0;
|
|
6312
|
+
while (!this._toreDown && Date.now() < deadline) {
|
|
6313
|
+
probes++;
|
|
6314
|
+
if (await this.writeAndAwaitEcho(sentinel, sentinel, timing)) {
|
|
6315
|
+
live = true;
|
|
6316
|
+
break;
|
|
6317
|
+
}
|
|
6318
|
+
await sleep(timing.retryMs);
|
|
6319
|
+
}
|
|
6320
|
+
if (this._toreDown) return;
|
|
6321
|
+
if (!live) {
|
|
6322
|
+
process.stderr.write(
|
|
6323
|
+
`[conveyor-agent] raw TUI never echoed the input probe after ${probes} attempt(s) in ${Date.now() - start}ms \u2014 pasting anyway
|
|
6324
|
+
`
|
|
6325
|
+
);
|
|
6326
|
+
this.probeWindow = null;
|
|
6327
|
+
return;
|
|
6328
|
+
}
|
|
6329
|
+
for (let round = 0; round < probes && !this._toreDown; round++) {
|
|
6330
|
+
const gone = !await this.writeAndAwaitEcho(
|
|
6331
|
+
"\x7F".repeat(sentinel.length),
|
|
6332
|
+
sentinel,
|
|
6333
|
+
timing
|
|
6334
|
+
);
|
|
6335
|
+
if (gone) break;
|
|
6336
|
+
}
|
|
6337
|
+
this.probeWindow = null;
|
|
6338
|
+
}
|
|
5950
6339
|
async deliverPrompt(text) {
|
|
5951
6340
|
if (text === "" && !this.adapter.capabilities.prefill) return;
|
|
6341
|
+
if (!this.wroteToProcess) {
|
|
6342
|
+
await this.awaitRawTuiInputLive();
|
|
6343
|
+
if (this._toreDown) return;
|
|
6344
|
+
this.wroteToProcess = true;
|
|
6345
|
+
}
|
|
5952
6346
|
this.writeStdin(this.adapter.encodePromptBytes(text));
|
|
5953
6347
|
if (this.turn.promptDelivery === "prefill") return;
|
|
5954
6348
|
this.deliveredTexts.push(text);
|
|
@@ -6206,7 +6600,7 @@ var PtySession = class {
|
|
|
6206
6600
|
// src/harness/pty/config-home-health.ts
|
|
6207
6601
|
import { lstat, mkdir as mkdir4, symlink, unlink as unlink2 } from "fs/promises";
|
|
6208
6602
|
import { homedir as homedir3 } from "os";
|
|
6209
|
-
import { join as
|
|
6603
|
+
import { join as join6 } from "path";
|
|
6210
6604
|
var MOUNT_DISCONNECT_CODES = /* @__PURE__ */ new Set(["ENOTCONN", "EIO", "ESTALE", "ENXIO"]);
|
|
6211
6605
|
var MOUNT_DISCONNECT_MESSAGES = [
|
|
6212
6606
|
"socket is not connected",
|
|
@@ -6222,10 +6616,10 @@ function isMountDisconnectError(err) {
|
|
|
6222
6616
|
return MOUNT_DISCONNECT_MESSAGES.some((needle) => lower.includes(needle));
|
|
6223
6617
|
}
|
|
6224
6618
|
function podLocalConfigHome() {
|
|
6225
|
-
return
|
|
6619
|
+
return join6(homedir3(), ".claude-local");
|
|
6226
6620
|
}
|
|
6227
6621
|
function sharedConfigHomePath() {
|
|
6228
|
-
return
|
|
6622
|
+
return join6(homedir3(), ".claude");
|
|
6229
6623
|
}
|
|
6230
6624
|
function isConfigHomeFallbackActive() {
|
|
6231
6625
|
return claudeConfigHome() === podLocalConfigHome();
|
|
@@ -6256,7 +6650,7 @@ async function repointSharedConfigHomeSymlink(fallback, log) {
|
|
|
6256
6650
|
}
|
|
6257
6651
|
async function isConfigHomeMountDead(cwd) {
|
|
6258
6652
|
try {
|
|
6259
|
-
await mkdir4(
|
|
6653
|
+
await mkdir4(join6(claudeConfigHome(), "projects", projectSlug(cwd)), { recursive: true });
|
|
6260
6654
|
return false;
|
|
6261
6655
|
} catch (err) {
|
|
6262
6656
|
return isMountDisconnectError(err);
|
|
@@ -6265,7 +6659,7 @@ async function isConfigHomeMountDead(cwd) {
|
|
|
6265
6659
|
async function ensureUsableClaudeConfigHome(cwd, log) {
|
|
6266
6660
|
const configHome = claudeConfigHome();
|
|
6267
6661
|
try {
|
|
6268
|
-
await mkdir4(
|
|
6662
|
+
await mkdir4(join6(configHome, "projects", projectSlug(cwd)), { recursive: true });
|
|
6269
6663
|
return { configHome, fellBack: false };
|
|
6270
6664
|
} catch (err) {
|
|
6271
6665
|
if (!isMountDisconnectError(err)) throw err;
|
|
@@ -6279,7 +6673,7 @@ async function ensureUsableClaudeConfigHome(cwd, log) {
|
|
|
6279
6673
|
}
|
|
6280
6674
|
);
|
|
6281
6675
|
process.env.CLAUDE_CONFIG_DIR = fallback;
|
|
6282
|
-
await mkdir4(
|
|
6676
|
+
await mkdir4(join6(fallback, "projects", projectSlug(cwd)), { recursive: true });
|
|
6283
6677
|
await repointSharedConfigHomeSymlink(fallback, log);
|
|
6284
6678
|
return { configHome: fallback, fellBack: true };
|
|
6285
6679
|
}
|
|
@@ -6300,6 +6694,11 @@ var PtyHarness = class _PtyHarness {
|
|
|
6300
6694
|
bridge;
|
|
6301
6695
|
adapter;
|
|
6302
6696
|
static log = createServiceLogger("pty-harness");
|
|
6697
|
+
/** Delegated to the adapter: Claude tails a transcript + hook socket, while a
|
|
6698
|
+
* raw-relay TUI (opencode) has no trusted event source at all. */
|
|
6699
|
+
get emitsStructuredEvents() {
|
|
6700
|
+
return this.adapter.capabilities.structuredEvents;
|
|
6701
|
+
}
|
|
6303
6702
|
/** Fingerprint of the spawn-time options a reused process cannot change. */
|
|
6304
6703
|
fingerprintOf(options) {
|
|
6305
6704
|
return this.adapter.spawnFingerprint({
|
|
@@ -6355,7 +6754,7 @@ var PtyHarness = class _PtyHarness {
|
|
|
6355
6754
|
}
|
|
6356
6755
|
}
|
|
6357
6756
|
async *executeQuery(opts) {
|
|
6358
|
-
const want = opts.resume ?? opts.options.resume;
|
|
6757
|
+
const want = opts.resume ?? opts.options.resume ?? this.parked?.reportedSessionId ?? void 0;
|
|
6359
6758
|
const fingerprint = this.fingerprintOf(opts.options);
|
|
6360
6759
|
let session;
|
|
6361
6760
|
if (this.parked?.canReuse(want, fingerprint) && !await this.parkedHomeDied(opts.options)) {
|
|
@@ -6610,21 +7009,499 @@ var PtyHarness = class _PtyHarness {
|
|
|
6610
7009
|
}
|
|
6611
7010
|
};
|
|
6612
7011
|
|
|
7012
|
+
// src/harness/opencode/index.ts
|
|
7013
|
+
import { spawn } from "child_process";
|
|
7014
|
+
|
|
7015
|
+
// src/harness/pty/adapters/types.ts
|
|
7016
|
+
import { accessSync, constants, statSync } from "fs";
|
|
7017
|
+
import { join as join7 } from "path";
|
|
7018
|
+
var TuiUnavailableError = class extends Error {
|
|
7019
|
+
constructor(tui, message) {
|
|
7020
|
+
super(message);
|
|
7021
|
+
this.tui = tui;
|
|
7022
|
+
this.name = "TuiUnavailableError";
|
|
7023
|
+
}
|
|
7024
|
+
tui;
|
|
7025
|
+
};
|
|
7026
|
+
function isExecutable(path2) {
|
|
7027
|
+
try {
|
|
7028
|
+
if (!statSync(path2).isFile()) return false;
|
|
7029
|
+
accessSync(path2, constants.X_OK);
|
|
7030
|
+
return true;
|
|
7031
|
+
} catch {
|
|
7032
|
+
return false;
|
|
7033
|
+
}
|
|
7034
|
+
}
|
|
7035
|
+
function findOnPath(binary, env = process.env) {
|
|
7036
|
+
if (binary.includes("/")) {
|
|
7037
|
+
return isExecutable(binary) ? binary : null;
|
|
7038
|
+
}
|
|
7039
|
+
for (const dir of (env.PATH ?? "").split(":")) {
|
|
7040
|
+
if (!dir) continue;
|
|
7041
|
+
const candidate = join7(dir, binary);
|
|
7042
|
+
if (isExecutable(candidate)) return candidate;
|
|
7043
|
+
}
|
|
7044
|
+
return null;
|
|
7045
|
+
}
|
|
7046
|
+
|
|
7047
|
+
// src/harness/pty/adapters/opencode-auth.ts
|
|
7048
|
+
import { promises as fs } from "fs";
|
|
7049
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
7050
|
+
import { homedir as homedir4 } from "os";
|
|
7051
|
+
var logger = createServiceLogger("opencode-auth");
|
|
7052
|
+
var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
|
|
7053
|
+
var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
|
|
7054
|
+
function opencodeAuthPath(env) {
|
|
7055
|
+
const dataHome = env.XDG_DATA_HOME ?? join8(env.HOME ?? homedir4(), ".local", "share");
|
|
7056
|
+
return join8(dataHome, "opencode", "auth.json");
|
|
7057
|
+
}
|
|
7058
|
+
function opencodeConfigPath(env) {
|
|
7059
|
+
const configHome = env.XDG_CONFIG_HOME ?? join8(env.HOME ?? homedir4(), ".config");
|
|
7060
|
+
return join8(configHome, "opencode", "opencode.json");
|
|
7061
|
+
}
|
|
7062
|
+
function parseOauthSeed(b64) {
|
|
7063
|
+
if (!b64) return null;
|
|
7064
|
+
try {
|
|
7065
|
+
const parsed = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
|
|
7066
|
+
if (typeof parsed.access === "string" && typeof parsed.refresh === "string" && typeof parsed.expires === "number") {
|
|
7067
|
+
return { access: parsed.access, refresh: parsed.refresh, expires: parsed.expires };
|
|
7068
|
+
}
|
|
7069
|
+
return null;
|
|
7070
|
+
} catch {
|
|
7071
|
+
return null;
|
|
7072
|
+
}
|
|
7073
|
+
}
|
|
7074
|
+
function shouldSeed(existingEntry, seed) {
|
|
7075
|
+
if (!existingEntry || typeof existingEntry !== "object") return true;
|
|
7076
|
+
const entry = existingEntry;
|
|
7077
|
+
if (entry.type !== "oauth") return true;
|
|
7078
|
+
if (typeof entry.access !== "string" || typeof entry.refresh !== "string") return true;
|
|
7079
|
+
if (typeof entry.expires !== "number") return true;
|
|
7080
|
+
return entry.expires < seed.expires;
|
|
7081
|
+
}
|
|
7082
|
+
async function readJsonFile(path2) {
|
|
7083
|
+
try {
|
|
7084
|
+
return JSON.parse(await fs.readFile(path2, "utf8"));
|
|
7085
|
+
} catch {
|
|
7086
|
+
return {};
|
|
7087
|
+
}
|
|
7088
|
+
}
|
|
7089
|
+
async function writeJsonFile(path2, value) {
|
|
7090
|
+
await fs.mkdir(dirname2(path2), { recursive: true });
|
|
7091
|
+
await fs.writeFile(path2, `${JSON.stringify(value, null, 2)}
|
|
7092
|
+
`, { mode: 384 });
|
|
7093
|
+
}
|
|
7094
|
+
async function ensureAuthEntry(env, seed) {
|
|
7095
|
+
const path2 = opencodeAuthPath(env);
|
|
7096
|
+
const store = await readJsonFile(path2);
|
|
7097
|
+
if (!shouldSeed(store.openai, seed)) {
|
|
7098
|
+
logger.info("opencode oauth store is fresher than the seed; leaving it alone");
|
|
7099
|
+
return;
|
|
7100
|
+
}
|
|
7101
|
+
store.openai = {
|
|
7102
|
+
type: "oauth",
|
|
7103
|
+
access: seed.access,
|
|
7104
|
+
refresh: seed.refresh,
|
|
7105
|
+
expires: seed.expires
|
|
7106
|
+
};
|
|
7107
|
+
await writeJsonFile(path2, store);
|
|
7108
|
+
logger.info("seeded opencode oauth store entry");
|
|
7109
|
+
}
|
|
7110
|
+
async function ensurePluginConfig(env) {
|
|
7111
|
+
const path2 = opencodeConfigPath(env);
|
|
7112
|
+
const config = await readJsonFile(path2);
|
|
7113
|
+
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
7114
|
+
const isOurs = (p) => typeof p === "string" && (p === PLUGIN_PACKAGE || p.startsWith(`${PLUGIN_PACKAGE}@`));
|
|
7115
|
+
const hasExactPin = plugins.includes(OPENCODE_CODEX_PLUGIN);
|
|
7116
|
+
const hasStalePin = plugins.some((p) => isOurs(p) && p !== OPENCODE_CODEX_PLUGIN);
|
|
7117
|
+
if (hasExactPin && !hasStalePin) return;
|
|
7118
|
+
const kept = plugins.filter((p) => !isOurs(p));
|
|
7119
|
+
config.plugin = [...kept, OPENCODE_CODEX_PLUGIN];
|
|
7120
|
+
await writeJsonFile(path2, config);
|
|
7121
|
+
logger.info("ensured opencode codex-auth plugin in config");
|
|
7122
|
+
}
|
|
7123
|
+
async function seedOpenCodeOauth(env) {
|
|
7124
|
+
const seed = parseOauthSeed(env.CONVEYOR_OPENCODE_OAUTH);
|
|
7125
|
+
if (!seed) return;
|
|
7126
|
+
try {
|
|
7127
|
+
await ensureAuthEntry(env, seed);
|
|
7128
|
+
await ensurePluginConfig(env);
|
|
7129
|
+
} catch (err) {
|
|
7130
|
+
logger.warn(
|
|
7131
|
+
`failed to seed opencode oauth store: ${err instanceof Error ? err.message : String(err)}`
|
|
7132
|
+
);
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
|
|
7136
|
+
// src/harness/opencode/credentials.ts
|
|
7137
|
+
var PROVIDER_KEY_ENV = {
|
|
7138
|
+
openai: "OPENAI_API_KEY",
|
|
7139
|
+
anthropic: "ANTHROPIC_API_KEY"
|
|
7140
|
+
};
|
|
7141
|
+
var DEFAULT_OPENCODE_PROVIDER = "openai";
|
|
7142
|
+
function buildOpenCodeCredentialEnv(source) {
|
|
7143
|
+
const set = {};
|
|
7144
|
+
const oauthActive = Boolean(source.CONVEYOR_OPENCODE_OAUTH);
|
|
7145
|
+
const key = source.CONVEYOR_AGENT_KEY;
|
|
7146
|
+
const provider = source.CONVEYOR_AGENT_PROVIDER ?? DEFAULT_OPENCODE_PROVIDER;
|
|
7147
|
+
const keyEnvVar = PROVIDER_KEY_ENV[provider];
|
|
7148
|
+
if (key && keyEnvVar && !oauthActive) set[keyEnvVar] = key;
|
|
7149
|
+
return { set, unset: ["CONVEYOR_AGENT_KEY", "CONVEYOR_OPENCODE_OAUTH"] };
|
|
7150
|
+
}
|
|
7151
|
+
async function prepareOpenCodeCredentials(source = process.env) {
|
|
7152
|
+
await seedOpenCodeOauth(source);
|
|
7153
|
+
}
|
|
7154
|
+
function buildOpenCodeChildEnv(source, configContent) {
|
|
7155
|
+
const env = {};
|
|
7156
|
+
for (const [key, value] of Object.entries(source)) {
|
|
7157
|
+
if (typeof value === "string") env[key] = value;
|
|
7158
|
+
}
|
|
7159
|
+
const { set, unset } = buildOpenCodeCredentialEnv(source);
|
|
7160
|
+
for (const key of unset) delete env[key];
|
|
7161
|
+
Object.assign(env, set);
|
|
7162
|
+
if (configContent) env.OPENCODE_CONFIG_CONTENT = configContent;
|
|
7163
|
+
return env;
|
|
7164
|
+
}
|
|
7165
|
+
function resolveOpenCodeModel(source, optionsModel) {
|
|
7166
|
+
const model = source.CONVEYOR_AGENT_MODEL ?? optionsModel;
|
|
7167
|
+
if (!model) return void 0;
|
|
7168
|
+
if (model.includes("/")) return model;
|
|
7169
|
+
const provider = source.CONVEYOR_AGENT_PROVIDER ?? DEFAULT_OPENCODE_PROVIDER;
|
|
7170
|
+
return `${provider}/${model}`;
|
|
7171
|
+
}
|
|
7172
|
+
|
|
7173
|
+
// src/harness/opencode/spawn.ts
|
|
7174
|
+
function resolveOpenCodeBinary(env = process.env) {
|
|
7175
|
+
const override = env.CONVEYOR_OPENCODE_BIN;
|
|
7176
|
+
const found = override ? findOnPath(override, env) : findOnPath("opencode", env);
|
|
7177
|
+
if (!found) {
|
|
7178
|
+
throw new TuiUnavailableError(
|
|
7179
|
+
"opencode",
|
|
7180
|
+
"The opencode CLI is not available in this environment. It must be baked into the pod image (see Dockerfile.base) \u2014 re-run Build Image for this project, or set CONVEYOR_OPENCODE_BIN to its absolute path."
|
|
7181
|
+
);
|
|
7182
|
+
}
|
|
7183
|
+
return found;
|
|
7184
|
+
}
|
|
7185
|
+
var PROMPT_MAX_CHARS = 96e3;
|
|
7186
|
+
var MUTATING_TOOLS = ["edit", "bash", "external_directory"];
|
|
7187
|
+
function buildRunArgs(input) {
|
|
7188
|
+
const args = ["run", "--dir", input.cwd, "--format", "json", "--auto"];
|
|
7189
|
+
if (input.model) {
|
|
7190
|
+
args.push("--model", input.model);
|
|
7191
|
+
}
|
|
7192
|
+
if (input.resumeSessionId) args.push("--session", input.resumeSessionId);
|
|
7193
|
+
args.push(truncatePrompt(input.prompt));
|
|
7194
|
+
return args;
|
|
7195
|
+
}
|
|
7196
|
+
function truncatePrompt(prompt) {
|
|
7197
|
+
if (prompt.length <= PROMPT_MAX_CHARS) return prompt;
|
|
7198
|
+
return `${prompt.slice(0, PROMPT_MAX_CHARS)}
|
|
7199
|
+
|
|
7200
|
+
[prompt truncated at ${PROMPT_MAX_CHARS} characters]`;
|
|
7201
|
+
}
|
|
7202
|
+
function buildOpenCodeConfigContent(input) {
|
|
7203
|
+
const { entries, systemPromptPath, readOnly, pluginPath } = input;
|
|
7204
|
+
const mcp = {};
|
|
7205
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
7206
|
+
if (entry.type === "http") {
|
|
7207
|
+
mcp[name] = {
|
|
7208
|
+
type: "remote",
|
|
7209
|
+
url: entry.url,
|
|
7210
|
+
enabled: true,
|
|
7211
|
+
...Object.keys(entry.headers).length > 0 ? { headers: entry.headers } : {}
|
|
7212
|
+
};
|
|
7213
|
+
} else {
|
|
7214
|
+
mcp[name] = {
|
|
7215
|
+
type: "local",
|
|
7216
|
+
command: [entry.command, ...entry.args ?? []],
|
|
7217
|
+
enabled: true,
|
|
7218
|
+
...entry.env ? { environment: entry.env } : {}
|
|
7219
|
+
};
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
const config = {};
|
|
7223
|
+
if (Object.keys(mcp).length > 0) config.mcp = mcp;
|
|
7224
|
+
if (systemPromptPath) config.instructions = [systemPromptPath];
|
|
7225
|
+
if (pluginPath) config.plugin = [`file://${pluginPath}`];
|
|
7226
|
+
config.permission = {
|
|
7227
|
+
"*": "allow",
|
|
7228
|
+
...readOnly ? Object.fromEntries(MUTATING_TOOLS.map((tool2) => [tool2, "deny"])) : {}
|
|
7229
|
+
};
|
|
7230
|
+
if (Object.keys(config).length === 0) return null;
|
|
7231
|
+
return JSON.stringify(config);
|
|
7232
|
+
}
|
|
7233
|
+
|
|
7234
|
+
// src/harness/opencode/index.ts
|
|
7235
|
+
import { mkdtemp as mkdtemp2, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
7236
|
+
import { join as join9 } from "path";
|
|
7237
|
+
var MAX_STDERR_TAIL = 4e3;
|
|
7238
|
+
var OpenCodeHeadlessHarness = class {
|
|
7239
|
+
/** NDJSON from `--format json` is a trusted structured source. */
|
|
7240
|
+
emitsStructuredEvents = true;
|
|
7241
|
+
/** opencode's session id from the last run, for `--session` on the next turn. */
|
|
7242
|
+
lastSessionId = null;
|
|
7243
|
+
/** MCP tool servers started for the in-flight run. */
|
|
7244
|
+
toolServers = [];
|
|
7245
|
+
tempDir = "";
|
|
7246
|
+
/** The same in-process MCP handle the PTY path uses — tools are identical. */
|
|
7247
|
+
createMcpServer(config) {
|
|
7248
|
+
return new PtyMcpServer(config.name, config.tools);
|
|
7249
|
+
}
|
|
7250
|
+
/** opencode's own session id, exposed so the runner can persist lineage. */
|
|
7251
|
+
get sessionId() {
|
|
7252
|
+
return this.lastSessionId;
|
|
7253
|
+
}
|
|
7254
|
+
async *executeQuery(opts) {
|
|
7255
|
+
const prompt = await collectPrompt(opts.prompt);
|
|
7256
|
+
const binary = resolveOpenCodeBinary(process.env);
|
|
7257
|
+
await prepareOpenCodeCredentials(process.env);
|
|
7258
|
+
this.tempDir = await mkdtemp2(join9(sessionTempBase(), "opencode-headless-"));
|
|
7259
|
+
const { servers, entries } = await startToolServers(
|
|
7260
|
+
opts.options.mcpServers ?? {},
|
|
7261
|
+
this.tempDir
|
|
7262
|
+
);
|
|
7263
|
+
this.toolServers = servers;
|
|
7264
|
+
const args = buildRunArgs({
|
|
7265
|
+
prompt,
|
|
7266
|
+
cwd: opts.options.cwd,
|
|
7267
|
+
model: resolveOpenCodeModel(process.env, opts.options.model),
|
|
7268
|
+
// `resume` is Claude-transcript-derived, so it is always absent here (there
|
|
7269
|
+
// is no Claude session file for an opencode run). Falling back to the id
|
|
7270
|
+
// opencode itself reported is what makes a multi-turn card keep its
|
|
7271
|
+
// conversation instead of starting fresh on every message.
|
|
7272
|
+
resumeSessionId: opts.resume ?? this.lastSessionId
|
|
7273
|
+
});
|
|
7274
|
+
const readOnly = opts.options.allowDangerouslySkipPermissions === false;
|
|
7275
|
+
const systemPromptPath = await this.writeSystemPrompt(opts.options.appendSystemPrompt);
|
|
7276
|
+
const configContent = buildOpenCodeConfigContent({
|
|
7277
|
+
entries,
|
|
7278
|
+
systemPromptPath,
|
|
7279
|
+
readOnly
|
|
7280
|
+
});
|
|
7281
|
+
const queue = new AsyncEventQueue();
|
|
7282
|
+
const usage = { inputTokens: 0, outputTokens: 0, totalCostUsd: 0 };
|
|
7283
|
+
let assistantText = "";
|
|
7284
|
+
let stderrTail = "";
|
|
7285
|
+
let reportedError = null;
|
|
7286
|
+
const child = spawn(binary, args, {
|
|
7287
|
+
cwd: opts.options.cwd,
|
|
7288
|
+
// stdin IGNORED — an open pipe makes `opencode run` block forever.
|
|
7289
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
7290
|
+
env: buildOpenCodeChildEnv(process.env, configContent)
|
|
7291
|
+
});
|
|
7292
|
+
const abort = opts.options.abortController;
|
|
7293
|
+
const onAbort = () => {
|
|
7294
|
+
child.kill("SIGTERM");
|
|
7295
|
+
};
|
|
7296
|
+
abort?.signal.addEventListener("abort", onAbort, { once: true });
|
|
7297
|
+
let carry = "";
|
|
7298
|
+
const ingest = (line) => {
|
|
7299
|
+
const { text, error } = this.ingestLine(line, usage, queue);
|
|
7300
|
+
if (text) assistantText += text;
|
|
7301
|
+
if (error) reportedError = error;
|
|
7302
|
+
};
|
|
7303
|
+
child.stdout.setEncoding("utf8");
|
|
7304
|
+
child.stdout.on("data", (chunk) => {
|
|
7305
|
+
carry += chunk;
|
|
7306
|
+
const lines = carry.split("\n");
|
|
7307
|
+
carry = lines.pop() ?? "";
|
|
7308
|
+
for (const line of lines) ingest(line);
|
|
7309
|
+
});
|
|
7310
|
+
child.stderr.setEncoding("utf8");
|
|
7311
|
+
child.stderr.on("data", (chunk) => {
|
|
7312
|
+
stderrTail = (stderrTail + chunk).slice(-MAX_STDERR_TAIL);
|
|
7313
|
+
});
|
|
7314
|
+
const exited = new Promise((resolve) => {
|
|
7315
|
+
child.once("error", () => resolve(-1));
|
|
7316
|
+
child.once("close", (code) => resolve(code ?? -1));
|
|
7317
|
+
});
|
|
7318
|
+
const finish = (async () => {
|
|
7319
|
+
const code = await exited;
|
|
7320
|
+
ingest(carry);
|
|
7321
|
+
queue.push(buildResultEvent(code, usage, assistantText.trim(), stderrTail, reportedError));
|
|
7322
|
+
queue.close();
|
|
7323
|
+
})();
|
|
7324
|
+
try {
|
|
7325
|
+
for await (const event of queue.drain()) yield event;
|
|
7326
|
+
await finish;
|
|
7327
|
+
} finally {
|
|
7328
|
+
abort?.signal.removeEventListener("abort", onAbort);
|
|
7329
|
+
if (!child.killed) child.kill("SIGTERM");
|
|
7330
|
+
await this.cleanup();
|
|
7331
|
+
}
|
|
7332
|
+
}
|
|
7333
|
+
/**
|
|
7334
|
+
* Persist the Conveyor system prompt (task context, plan, mode instructions)
|
|
7335
|
+
* so it can be referenced by the config's `instructions`. opencode has no
|
|
7336
|
+
* `--append-system-prompt`; without this the card runs with no context at all.
|
|
7337
|
+
*/
|
|
7338
|
+
async writeSystemPrompt(text) {
|
|
7339
|
+
if (!text || text.trim() === "") return null;
|
|
7340
|
+
const path2 = join9(this.tempDir, "conveyor-instructions.md");
|
|
7341
|
+
await writeFile6(path2, text, "utf8");
|
|
7342
|
+
return path2;
|
|
7343
|
+
}
|
|
7344
|
+
/**
|
|
7345
|
+
* Parse one NDJSON line and push whatever it maps to. Returns assistant text so
|
|
7346
|
+
* the caller can accumulate the turn's summary; the session id is latched here
|
|
7347
|
+
* because every event carries it and any one of them will do.
|
|
7348
|
+
*/
|
|
7349
|
+
ingestLine(line, usage, queue) {
|
|
7350
|
+
const none = { text: "", error: null };
|
|
7351
|
+
const parsed = parseOpenCodeLine(line);
|
|
7352
|
+
if (!parsed) return none;
|
|
7353
|
+
const sid = sessionIdOf(parsed);
|
|
7354
|
+
if (sid) this.lastSessionId = sid;
|
|
7355
|
+
accumulateUsage(parsed, usage);
|
|
7356
|
+
const error = errorMessageOf(parsed);
|
|
7357
|
+
if (error) return { text: "", error };
|
|
7358
|
+
const mapped = mapOpenCodeEvent(parsed);
|
|
7359
|
+
if (!mapped) return none;
|
|
7360
|
+
queue.push(mapped);
|
|
7361
|
+
if (mapped.type !== "assistant") return none;
|
|
7362
|
+
const block = mapped.message.content[0];
|
|
7363
|
+
return { text: block?.type === "text" && block.text ? block.text : "", error: null };
|
|
7364
|
+
}
|
|
7365
|
+
async dispose() {
|
|
7366
|
+
await this.cleanup();
|
|
7367
|
+
}
|
|
7368
|
+
async cleanup() {
|
|
7369
|
+
for (const server of this.toolServers) await server.close().catch(() => void 0);
|
|
7370
|
+
this.toolServers = [];
|
|
7371
|
+
if (this.tempDir) {
|
|
7372
|
+
await rm3(this.tempDir, { recursive: true, force: true }).catch(() => void 0);
|
|
7373
|
+
this.tempDir = "";
|
|
7374
|
+
}
|
|
7375
|
+
}
|
|
7376
|
+
};
|
|
7377
|
+
async function collectPrompt(prompt) {
|
|
7378
|
+
if (typeof prompt === "string") return prompt;
|
|
7379
|
+
const parts = [];
|
|
7380
|
+
for await (const message of prompt) {
|
|
7381
|
+
const content = message?.message?.content;
|
|
7382
|
+
if (typeof content === "string") parts.push(content);
|
|
7383
|
+
else if (Array.isArray(content)) {
|
|
7384
|
+
for (const block of content) {
|
|
7385
|
+
const b = block;
|
|
7386
|
+
if (b?.type === "text" && typeof b.text === "string") parts.push(b.text);
|
|
7387
|
+
}
|
|
7388
|
+
}
|
|
7389
|
+
}
|
|
7390
|
+
return parts.join("\n\n");
|
|
7391
|
+
}
|
|
7392
|
+
|
|
6613
7393
|
// src/harness/index.ts
|
|
6614
|
-
function
|
|
6615
|
-
return kind === "
|
|
7394
|
+
function supportsImageBlocks(kind) {
|
|
7395
|
+
return kind === "sdk";
|
|
7396
|
+
}
|
|
7397
|
+
function createHarness(kind = "sdk", ptyBridge, adapter) {
|
|
7398
|
+
if (kind === "opencode") return new OpenCodeHeadlessHarness();
|
|
7399
|
+
if (kind !== "pty") return new ClaudeCodeHarness();
|
|
7400
|
+
return adapter ? new PtyHarness(ptyBridge, adapter) : new PtyHarness(ptyBridge);
|
|
7401
|
+
}
|
|
7402
|
+
|
|
7403
|
+
// src/harness/pty/adapters/opencode.ts
|
|
7404
|
+
var OpenCodeTuiAdapter = class {
|
|
7405
|
+
constructor(env = process.env) {
|
|
7406
|
+
this.env = env;
|
|
7407
|
+
}
|
|
7408
|
+
env;
|
|
7409
|
+
id = "opencode";
|
|
7410
|
+
capabilities = {
|
|
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.
|
|
7416
|
+
prefill: false,
|
|
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
|
|
7423
|
+
};
|
|
7424
|
+
resolveBinary(env = this.env) {
|
|
7425
|
+
const override = env.CONVEYOR_OPENCODE_BIN;
|
|
7426
|
+
const found = override ? findOnPath(override, env) : findOnPath("opencode", env);
|
|
7427
|
+
if (!found) {
|
|
7428
|
+
throw new TuiUnavailableError(
|
|
7429
|
+
"opencode",
|
|
7430
|
+
"The opencode CLI is not available in this environment. It must be baked into the pod image (see Dockerfile.base) \u2014 re-run Build Image for this project, or set CONVEYOR_OPENCODE_BIN to its absolute path."
|
|
7431
|
+
);
|
|
7432
|
+
}
|
|
7433
|
+
return found;
|
|
7434
|
+
}
|
|
7435
|
+
buildSpawn(input) {
|
|
7436
|
+
const env = { ...inheritedEnv() };
|
|
7437
|
+
const credentials = buildOpenCodeCredentialEnv(this.env);
|
|
7438
|
+
for (const key of credentials.unset) delete env[key];
|
|
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;
|
|
7448
|
+
const model = resolveOpenCodeModel(this.env, input.options.model);
|
|
7449
|
+
const args = [];
|
|
7450
|
+
if (model) args.push("--model", model);
|
|
7451
|
+
if (input.resume) args.push("--session", input.resume);
|
|
7452
|
+
return { file: this.resolveBinary(), args, env };
|
|
7453
|
+
}
|
|
7454
|
+
async prepareEnvironment() {
|
|
7455
|
+
await prepareOpenCodeCredentials(this.env);
|
|
7456
|
+
}
|
|
7457
|
+
spawnFingerprint(input) {
|
|
7458
|
+
return JSON.stringify([
|
|
7459
|
+
"opencode",
|
|
7460
|
+
input.model,
|
|
7461
|
+
input.cwd,
|
|
7462
|
+
input.permissionMode,
|
|
7463
|
+
input.appendSystemPrompt ?? ""
|
|
7464
|
+
]);
|
|
7465
|
+
}
|
|
7466
|
+
encodePromptBytes(text) {
|
|
7467
|
+
return buildPromptBytes(text);
|
|
7468
|
+
}
|
|
7469
|
+
buildExitErrors(exitCode, rawOutput) {
|
|
7470
|
+
const errors = [`opencode exited (code ${exitCode}) without a result`];
|
|
7471
|
+
const tail = cleanTerminalOutput(rawOutput);
|
|
7472
|
+
if (tail) errors.push(`Last terminal output before exit:
|
|
7473
|
+
${tail}`);
|
|
7474
|
+
return errors;
|
|
7475
|
+
}
|
|
7476
|
+
};
|
|
7477
|
+
|
|
7478
|
+
// src/harness/pty/adapters/index.ts
|
|
7479
|
+
function resolveTuiKindFromEnv(env) {
|
|
7480
|
+
const raw = env.CONVEYOR_TUI ?? "claude-code";
|
|
7481
|
+
if (TUI_KINDS.includes(raw)) return raw;
|
|
7482
|
+
throw new Error(`Unknown TUI "${raw}" in CONVEYOR_TUI (expected: ${TUI_KINDS.join(", ")})`);
|
|
7483
|
+
}
|
|
7484
|
+
function resolveTuiAdapter(kind = "claude-code") {
|
|
7485
|
+
switch (kind) {
|
|
7486
|
+
case "claude-code":
|
|
7487
|
+
return new ClaudeTuiAdapter();
|
|
7488
|
+
case "opencode":
|
|
7489
|
+
return new OpenCodeTuiAdapter();
|
|
7490
|
+
default:
|
|
7491
|
+
throw new Error(`Unknown TUI kind: ${kind}`);
|
|
7492
|
+
}
|
|
6616
7493
|
}
|
|
6617
7494
|
|
|
6618
7495
|
// src/harness/pty/stream-server.ts
|
|
6619
7496
|
import net from "net";
|
|
6620
|
-
var
|
|
7497
|
+
var logger2 = createServiceLogger("PtyStreamServer");
|
|
6621
7498
|
var RING_MAX_CHARS = 256 * 1024;
|
|
6622
7499
|
var PtyStreamServer = class {
|
|
6623
7500
|
constructor(options) {
|
|
6624
7501
|
this.options = options;
|
|
6625
7502
|
this.server = net.createServer((socket) => this.handleConnection(socket));
|
|
6626
7503
|
this.server.on("error", (err) => {
|
|
6627
|
-
|
|
7504
|
+
logger2.warn(`PTY stream server error: ${err.message}`);
|
|
6628
7505
|
});
|
|
6629
7506
|
}
|
|
6630
7507
|
options;
|
|
@@ -6665,7 +7542,7 @@ var PtyStreamServer = class {
|
|
|
6665
7542
|
return port;
|
|
6666
7543
|
}
|
|
6667
7544
|
}
|
|
6668
|
-
|
|
7545
|
+
logger2.warn(`PTY stream server could not bind any port in ${base}..${base + attempts - 1}`);
|
|
6669
7546
|
return null;
|
|
6670
7547
|
}
|
|
6671
7548
|
tryListen(port) {
|
|
@@ -6786,7 +7663,7 @@ var PtyStreamServer = class {
|
|
|
6786
7663
|
};
|
|
6787
7664
|
|
|
6788
7665
|
// src/harness/pty/direct-stream.ts
|
|
6789
|
-
var
|
|
7666
|
+
var logger3 = createServiceLogger("PtyDirectStream");
|
|
6790
7667
|
var RELAY_COALESCE_MS = 2e3;
|
|
6791
7668
|
var RELAY_MAX_BUFFER_CHARS = 48 * 1024;
|
|
6792
7669
|
var DirectStreamController = class {
|
|
@@ -6835,7 +7712,7 @@ var DirectStreamController = class {
|
|
|
6835
7712
|
});
|
|
6836
7713
|
void created.listen().then((port) => this.onListening(created, port)).catch((err) => {
|
|
6837
7714
|
this.starting = false;
|
|
6838
|
-
|
|
7715
|
+
logger3.warn(
|
|
6839
7716
|
`PTY stream server failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
6840
7717
|
);
|
|
6841
7718
|
});
|
|
@@ -6848,7 +7725,7 @@ var DirectStreamController = class {
|
|
|
6848
7725
|
}
|
|
6849
7726
|
this.server = created;
|
|
6850
7727
|
this.reporter.reportPtyStream(port);
|
|
6851
|
-
|
|
7728
|
+
logger3.info(`PTY stream server listening on ${port} (session ${this.reporter.sessionId})`);
|
|
6852
7729
|
}
|
|
6853
7730
|
/** Push the min box across both transports to the pty. */
|
|
6854
7731
|
applyDims() {
|
|
@@ -10154,7 +11031,7 @@ function buildMutationTools(connection, config) {
|
|
|
10154
11031
|
}
|
|
10155
11032
|
|
|
10156
11033
|
// src/tools/attachment-tools.ts
|
|
10157
|
-
import { basename, extname, isAbsolute, join as
|
|
11034
|
+
import { basename, extname, isAbsolute, join as join10 } from "path";
|
|
10158
11035
|
var MIME_BY_EXT = {
|
|
10159
11036
|
".png": "image/png",
|
|
10160
11037
|
".jpg": "image/jpeg",
|
|
@@ -10205,7 +11082,7 @@ ${snippet}`;
|
|
|
10205
11082
|
function buildUploadAttachmentTool(connection, config) {
|
|
10206
11083
|
return defineContractTool(uploadAttachmentContract, async ({ path: path2, title, tags }) => {
|
|
10207
11084
|
try {
|
|
10208
|
-
const filePath = isAbsolute(path2) ? path2 :
|
|
11085
|
+
const filePath = isAbsolute(path2) ? path2 : join10(config.workspaceDir, path2);
|
|
10209
11086
|
const mimeType = inferMimeType(filePath);
|
|
10210
11087
|
const info = await statWorkspacePath(filePath);
|
|
10211
11088
|
if (!info.isFile) {
|
|
@@ -10717,7 +11594,7 @@ import { z as z16 } from "zod";
|
|
|
10717
11594
|
|
|
10718
11595
|
// src/execution/context-path-verifier.ts
|
|
10719
11596
|
import { readFile as readFile2 } from "fs/promises";
|
|
10720
|
-
import { isAbsolute as isAbsolute2, join as
|
|
11597
|
+
import { isAbsolute as isAbsolute2, join as join11, normalize } from "path";
|
|
10721
11598
|
var PROBLEM_TEXT = {
|
|
10722
11599
|
not_found: "does not exist in the repo",
|
|
10723
11600
|
expected_folder: "is a file, not a folder \u2014 use type 'file', 'rule', or 'doc'",
|
|
@@ -10762,7 +11639,7 @@ async function verifyContextPaths(links, workspaceDir) {
|
|
|
10762
11639
|
problems.push({ type: link.type, path: link.path, reason: shape });
|
|
10763
11640
|
continue;
|
|
10764
11641
|
}
|
|
10765
|
-
const absolutePath =
|
|
11642
|
+
const absolutePath = join11(workspaceDir, toRelativePath(link.path));
|
|
10766
11643
|
const stat = await statWorkspacePath(absolutePath);
|
|
10767
11644
|
const wantsDirectory = expectsDirectory(link.type);
|
|
10768
11645
|
if (!stat.exists) {
|
|
@@ -11435,38 +12312,6 @@ function createConveyorMcpServer(harness, connection, config, context, agentMode
|
|
|
11435
12312
|
});
|
|
11436
12313
|
}
|
|
11437
12314
|
|
|
11438
|
-
// src/harness/pty/adapters/types.ts
|
|
11439
|
-
import { accessSync, constants, statSync } from "fs";
|
|
11440
|
-
import { join as join8 } from "path";
|
|
11441
|
-
var TuiUnavailableError = class extends Error {
|
|
11442
|
-
constructor(tui, message) {
|
|
11443
|
-
super(message);
|
|
11444
|
-
this.tui = tui;
|
|
11445
|
-
this.name = "TuiUnavailableError";
|
|
11446
|
-
}
|
|
11447
|
-
tui;
|
|
11448
|
-
};
|
|
11449
|
-
function isExecutable(path2) {
|
|
11450
|
-
try {
|
|
11451
|
-
if (!statSync(path2).isFile()) return false;
|
|
11452
|
-
accessSync(path2, constants.X_OK);
|
|
11453
|
-
return true;
|
|
11454
|
-
} catch {
|
|
11455
|
-
return false;
|
|
11456
|
-
}
|
|
11457
|
-
}
|
|
11458
|
-
function findOnPath(binary, env = process.env) {
|
|
11459
|
-
if (binary.includes("/")) {
|
|
11460
|
-
return isExecutable(binary) ? binary : null;
|
|
11461
|
-
}
|
|
11462
|
-
for (const dir of (env.PATH ?? "").split(":")) {
|
|
11463
|
-
if (!dir) continue;
|
|
11464
|
-
const candidate = join8(dir, binary);
|
|
11465
|
-
if (isExecutable(candidate)) return candidate;
|
|
11466
|
-
}
|
|
11467
|
-
return null;
|
|
11468
|
-
}
|
|
11469
|
-
|
|
11470
12315
|
// src/execution/playwright-mcp.ts
|
|
11471
12316
|
var PLAYWRIGHT_MCP_BINARIES = ["playwright-mcp", "mcp-server-playwright"];
|
|
11472
12317
|
var PLAYWRIGHT_MCP_ARGS = ["--browser", "chromium", "--headless", "--no-sandbox", "--isolated"];
|
|
@@ -11479,7 +12324,7 @@ function resolvePlaywrightMcpServer(env = process.env) {
|
|
|
11479
12324
|
}
|
|
11480
12325
|
|
|
11481
12326
|
// src/execution/event-handlers.ts
|
|
11482
|
-
var
|
|
12327
|
+
var logger4 = createServiceLogger("event-handlers");
|
|
11483
12328
|
function safeVoid(promise, context) {
|
|
11484
12329
|
if (promise && typeof promise.catch === "function") {
|
|
11485
12330
|
promise.catch((err) => {
|
|
@@ -11634,7 +12479,7 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
|
|
|
11634
12479
|
}
|
|
11635
12480
|
function handleRateLimitEvent(event, host) {
|
|
11636
12481
|
const { rate_limit_info } = event;
|
|
11637
|
-
|
|
12482
|
+
logger4.info("Rate limit event received", { rate_limit_info });
|
|
11638
12483
|
const status = rate_limit_info.status;
|
|
11639
12484
|
const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
|
|
11640
12485
|
if (utilization !== void 0 && rate_limit_info.rateLimitType) {
|
|
@@ -12357,7 +13202,7 @@ function buildCanUseTool(host) {
|
|
|
12357
13202
|
}
|
|
12358
13203
|
|
|
12359
13204
|
// src/execution/query-executor.ts
|
|
12360
|
-
var
|
|
13205
|
+
var logger5 = createServiceLogger("QueryExecutor");
|
|
12361
13206
|
var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
|
|
12362
13207
|
var RETRY_DELAYS_MS2 = [6e4, 12e4, 18e4, 3e5];
|
|
12363
13208
|
function buildHooks(host) {
|
|
@@ -12449,7 +13294,7 @@ function repairTornSessionFile(path2) {
|
|
|
12449
13294
|
}
|
|
12450
13295
|
if (keepEnd === content.length) return false;
|
|
12451
13296
|
truncateSync(path2, Buffer.byteLength(content.slice(0, keepEnd), "utf8"));
|
|
12452
|
-
|
|
13297
|
+
logger5.warn("Repaired torn transcript before resume", {
|
|
12453
13298
|
path: path2,
|
|
12454
13299
|
trimmedBytes: content.length - keepEnd
|
|
12455
13300
|
});
|
|
@@ -12500,10 +13345,11 @@ function buildQueryOptions(host, context) {
|
|
|
12500
13345
|
permissionMode: needsCanUseTool ? "plan" : "bypassPermissions",
|
|
12501
13346
|
allowDangerouslySkipPermissions: !needsCanUseTool,
|
|
12502
13347
|
canUseTool: buildCanUseTool(host),
|
|
12503
|
-
//
|
|
12504
|
-
//
|
|
12505
|
-
//
|
|
12506
|
-
|
|
13348
|
+
// A spawned CLI never sees `systemPrompt` (an SDK-only option) — deliver the
|
|
13349
|
+
// same text via `appendSystemPrompt`, which each spawning harness routes its
|
|
13350
|
+
// own way (`claude --append-system-prompt`; opencode an `instructions` file).
|
|
13351
|
+
// Without this an opencode card would run with no task context at all.
|
|
13352
|
+
...host.harnessKind !== "sdk" && systemPromptText ? { appendSystemPrompt: systemPromptText } : {},
|
|
12507
13353
|
// Auto mode pre-exit (residual sessions only — auto now boots post-exit):
|
|
12508
13354
|
// after the ExitPlanMode hook allows the call, the CLI's plan dialog still
|
|
12509
13355
|
// renders — press Enter so the autonomous agent continues building in the
|
|
@@ -12529,7 +13375,7 @@ function buildQueryOptions(host, context) {
|
|
|
12529
13375
|
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
12530
13376
|
enableFileCheckpointing: settings.enableFileCheckpointing,
|
|
12531
13377
|
stderr: (data) => {
|
|
12532
|
-
|
|
13378
|
+
logger5.warn("Claude Code stderr", { data: data.trimEnd() });
|
|
12533
13379
|
}
|
|
12534
13380
|
};
|
|
12535
13381
|
}
|
|
@@ -12580,16 +13426,16 @@ async function buildFollowUpPrompt(host, context, followUpContent) {
|
|
|
12580
13426
|
|
|
12581
13427
|
The team says:
|
|
12582
13428
|
${followUpText}` : followUpText;
|
|
12583
|
-
const
|
|
13429
|
+
const skipImages = !supportsImageBlocks(host.harnessKind);
|
|
12584
13430
|
if (isPmMode) {
|
|
12585
|
-
const prompt = buildMultimodalPrompt(textPrompt, context,
|
|
13431
|
+
const prompt = buildMultimodalPrompt(textPrompt, context, skipImages);
|
|
12586
13432
|
if (followUpImages.length > 0 && Array.isArray(prompt)) {
|
|
12587
13433
|
prompt.push(...followUpImages);
|
|
12588
13434
|
}
|
|
12589
13435
|
return prompt;
|
|
12590
13436
|
}
|
|
12591
13437
|
if (followUpImages.length > 0) {
|
|
12592
|
-
if (
|
|
13438
|
+
if (skipImages) {
|
|
12593
13439
|
const refs = followUpImages.map(
|
|
12594
13440
|
() => `[Image attachment \u2014 use list_task_files / get_attachment to view]`
|
|
12595
13441
|
);
|
|
@@ -12639,7 +13485,7 @@ function abortWatchedTurn(host, stderrLine, chatMessage) {
|
|
|
12639
13485
|
host.connection.sendEvent({ type: "error", message: chatMessage });
|
|
12640
13486
|
host.abortController?.abort();
|
|
12641
13487
|
}
|
|
12642
|
-
async function handleTurnSilence(host, state, waitMs, silenceTimeoutMs, probeMountDead) {
|
|
13488
|
+
async function handleTurnSilence(host, state, waitMs, silenceTimeoutMs, probeMountDead, disableWedgeAbort) {
|
|
12643
13489
|
if (state.abortedAsWedged) {
|
|
12644
13490
|
process.stderr.write(
|
|
12645
13491
|
"[conveyor-agent] Wedged turn did not settle after abort \u2014 abandoning the turn stream\n"
|
|
@@ -12657,6 +13503,10 @@ async function handleTurnSilence(host, state, waitMs, silenceTimeoutMs, probeMou
|
|
|
12657
13503
|
return "keep_waiting";
|
|
12658
13504
|
}
|
|
12659
13505
|
if (state.silentMs < silenceTimeoutMs) return "keep_waiting";
|
|
13506
|
+
if (disableWedgeAbort) {
|
|
13507
|
+
state.silentMs = 0;
|
|
13508
|
+
return "keep_waiting";
|
|
13509
|
+
}
|
|
12660
13510
|
if (isHeavyGateActive()) {
|
|
12661
13511
|
state.silentMs = 0;
|
|
12662
13512
|
return "keep_waiting";
|
|
@@ -12693,7 +13543,8 @@ async function* watchForParkedTui(inner, host, opts) {
|
|
|
12693
13543
|
state,
|
|
12694
13544
|
waitMs,
|
|
12695
13545
|
silenceTimeoutMs,
|
|
12696
|
-
opts?.probeMountDead
|
|
13546
|
+
opts?.probeMountDead,
|
|
13547
|
+
opts?.disableWedgeAbort
|
|
12697
13548
|
);
|
|
12698
13549
|
if (outcome === "abandon") return;
|
|
12699
13550
|
continue;
|
|
@@ -12740,7 +13591,7 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
|
|
|
12740
13591
|
await runFollowUpQuery(host, context, options, resume, followUpContent);
|
|
12741
13592
|
return;
|
|
12742
13593
|
}
|
|
12743
|
-
if (isDiscoveryLike && (resume || host.harnessKind
|
|
13594
|
+
if (isDiscoveryLike && (resume || host.harnessKind === "sdk")) {
|
|
12744
13595
|
return;
|
|
12745
13596
|
}
|
|
12746
13597
|
await runInitialQuery(host, context, options, resume, promptDelivery);
|
|
@@ -12795,9 +13646,12 @@ async function runPassiveTurn(host, context) {
|
|
|
12795
13646
|
}
|
|
12796
13647
|
async function trackAndRun(host, context, options, agentQuery) {
|
|
12797
13648
|
if (host.harnessKind === "pty" && options.promptDelivery !== "prefill") {
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
13649
|
+
const rawRelay = host.harness.emitsStructuredEvents === false;
|
|
13650
|
+
agentQuery = watchForParkedTui(
|
|
13651
|
+
agentQuery,
|
|
13652
|
+
host,
|
|
13653
|
+
rawRelay ? { disableWedgeAbort: true } : { probeMountDead: () => isConfigHomeMountDead(options.cwd) }
|
|
13654
|
+
);
|
|
12801
13655
|
}
|
|
12802
13656
|
host.activeQuery = agentQuery;
|
|
12803
13657
|
try {
|
|
@@ -12822,9 +13676,10 @@ function selectInitialPromptInput(promptDelivery, initialPrompt, context, baseAp
|
|
|
12822
13676
|
};
|
|
12823
13677
|
}
|
|
12824
13678
|
return {
|
|
12825
|
-
//
|
|
12826
|
-
// the prompt body already links
|
|
12827
|
-
|
|
13679
|
+
// Only the SDK takes image blocks: a PTY would paste them into the TUI as
|
|
13680
|
+
// text and headless opencode passes argv — the prompt body already links
|
|
13681
|
+
// each image (get_attachment / downloadUrl).
|
|
13682
|
+
prompt: buildMultimodalPrompt(initialPrompt, context, !supportsImageBlocks(harnessKind)),
|
|
12828
13683
|
appendSystemPrompt: baseAppendSystemPrompt
|
|
12829
13684
|
};
|
|
12830
13685
|
}
|
|
@@ -12865,7 +13720,7 @@ async function buildRetryQuery(host, context, options, lastErrorWasImage) {
|
|
|
12865
13720
|
const retryPrompt = buildMultimodalPrompt(
|
|
12866
13721
|
await buildInitialPrompt(host.config.mode, context, host.isAuto, host.agentMode),
|
|
12867
13722
|
context,
|
|
12868
|
-
lastErrorWasImage || host.harnessKind
|
|
13723
|
+
lastErrorWasImage || !supportsImageBlocks(host.harnessKind)
|
|
12869
13724
|
);
|
|
12870
13725
|
return host.harness.executeQuery({
|
|
12871
13726
|
prompt: host.createInputStream(retryPrompt),
|
|
@@ -12894,7 +13749,7 @@ async function handleAuthError(context, host, options) {
|
|
|
12894
13749
|
const freshPrompt = buildMultimodalPrompt(
|
|
12895
13750
|
await buildInitialPrompt(host.config.mode, context, host.isAuto, host.agentMode),
|
|
12896
13751
|
context,
|
|
12897
|
-
host.harnessKind
|
|
13752
|
+
!supportsImageBlocks(host.harnessKind)
|
|
12898
13753
|
);
|
|
12899
13754
|
const freshQuery = host.harness.executeQuery({
|
|
12900
13755
|
prompt: host.createInputStream(freshPrompt),
|
|
@@ -12909,7 +13764,7 @@ async function handleStaleSession(context, host, options) {
|
|
|
12909
13764
|
const freshPrompt = buildMultimodalPrompt(
|
|
12910
13765
|
await buildInitialPrompt(host.config.mode, context, host.isAuto, host.agentMode),
|
|
12911
13766
|
context,
|
|
12912
|
-
host.harnessKind
|
|
13767
|
+
!supportsImageBlocks(host.harnessKind)
|
|
12913
13768
|
);
|
|
12914
13769
|
const freshQuery = host.harness.executeQuery({
|
|
12915
13770
|
prompt: host.createInputStream(freshPrompt),
|
|
@@ -13003,7 +13858,7 @@ async function handleUsageCapRejection(context, host, options, rateLimitType, re
|
|
|
13003
13858
|
const freshPrompt = buildMultimodalPrompt(
|
|
13004
13859
|
await buildInitialPrompt(host.config.mode, context, host.isAuto, host.agentMode),
|
|
13005
13860
|
context,
|
|
13006
|
-
host.harnessKind
|
|
13861
|
+
!supportsImageBlocks(host.harnessKind)
|
|
13007
13862
|
);
|
|
13008
13863
|
const freshQuery = host.harness.executeQuery({
|
|
13009
13864
|
prompt: host.createInputStream(freshPrompt),
|
|
@@ -13076,9 +13931,14 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
13076
13931
|
}
|
|
13077
13932
|
|
|
13078
13933
|
// src/runner/query-bridge.ts
|
|
13079
|
-
var
|
|
13934
|
+
var logger6 = createServiceLogger("QueryBridge");
|
|
13080
13935
|
function resolveHarnessKind() {
|
|
13081
|
-
|
|
13936
|
+
if (process.env.CONVEYOR_FORCE_SDK_CARDS === "1") return "sdk";
|
|
13937
|
+
return "pty";
|
|
13938
|
+
}
|
|
13939
|
+
function resolveCardTui(mode) {
|
|
13940
|
+
if (mode === "code-review") return "claude-code";
|
|
13941
|
+
return resolveTuiKindFromEnv(process.env);
|
|
13082
13942
|
}
|
|
13083
13943
|
function buildPtyBridge(connection, onBackgroundTaskDone) {
|
|
13084
13944
|
return {
|
|
@@ -13109,7 +13969,11 @@ var QueryBridge = class {
|
|
|
13109
13969
|
);
|
|
13110
13970
|
bridge = this.directStream.bridge;
|
|
13111
13971
|
}
|
|
13112
|
-
this.harness = createHarness(
|
|
13972
|
+
this.harness = createHarness(
|
|
13973
|
+
harnessKind,
|
|
13974
|
+
bridge,
|
|
13975
|
+
harnessKind === "pty" ? resolveTuiAdapter(resolveCardTui(runnerConfig.mode)) : void 0
|
|
13976
|
+
);
|
|
13113
13977
|
}
|
|
13114
13978
|
connection;
|
|
13115
13979
|
mode;
|
|
@@ -13249,9 +14113,9 @@ var QueryBridge = class {
|
|
|
13249
14113
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13250
14114
|
const isAbort = this._stopped || /abort/i.test(msg);
|
|
13251
14115
|
if (isAbort) {
|
|
13252
|
-
|
|
14116
|
+
logger6.info("Query stopped by user", { error: msg });
|
|
13253
14117
|
} else {
|
|
13254
|
-
|
|
14118
|
+
logger6.error("Query execution failed", { error: msg });
|
|
13255
14119
|
this.connection.sendEvent({ type: "error", message: msg });
|
|
13256
14120
|
}
|
|
13257
14121
|
} finally {
|
|
@@ -13277,9 +14141,9 @@ var QueryBridge = class {
|
|
|
13277
14141
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13278
14142
|
const isAbort = this._stopped || /abort/i.test(msg);
|
|
13279
14143
|
if (isAbort) {
|
|
13280
|
-
|
|
14144
|
+
logger6.info("Passive turn stopped", { error: msg });
|
|
13281
14145
|
} else {
|
|
13282
|
-
|
|
14146
|
+
logger6.error("Passive turn failed", { error: msg });
|
|
13283
14147
|
this.connection.sendEvent({ type: "error", message: msg });
|
|
13284
14148
|
}
|
|
13285
14149
|
} finally {
|
|
@@ -13466,11 +14330,11 @@ function parseResetInstant(rowText, now, defaultTz = localTimeZone()) {
|
|
|
13466
14330
|
|
|
13467
14331
|
// src/usage/parse-usage.ts
|
|
13468
14332
|
var ESC = "\\u001b";
|
|
13469
|
-
var
|
|
14333
|
+
var ANSI_CSI = new RegExp(`${ESC}\\[[0-9;?]*[ -/]*[@-~]`, "g");
|
|
13470
14334
|
var ANSI_OSC = new RegExp(`${ESC}\\][^\\u0007${ESC}]*(?:\\u0007|${ESC}\\\\)`, "g");
|
|
13471
14335
|
var BAR_GLYPHS = /[─-▟]/g;
|
|
13472
14336
|
function normalizeUsageText(stdout) {
|
|
13473
|
-
return stdout.replace(
|
|
14337
|
+
return stdout.replace(ANSI_CSI, "").replace(ANSI_OSC, "").replace(BAR_GLYPHS, " ").replace(/\r/g, "");
|
|
13474
14338
|
}
|
|
13475
14339
|
function parseUsageGauges(stdout, now = Date.now(), defaultTz) {
|
|
13476
14340
|
const text = normalizeUsageText(stdout);
|
|
@@ -13571,10 +14435,10 @@ var UsageProbeRun = class {
|
|
|
13571
14435
|
}
|
|
13572
14436
|
};
|
|
13573
14437
|
async function runUsageProbe(deps = {}) {
|
|
13574
|
-
let
|
|
13575
|
-
if (!
|
|
14438
|
+
let spawn2 = deps.spawn;
|
|
14439
|
+
if (!spawn2) {
|
|
13576
14440
|
try {
|
|
13577
|
-
|
|
14441
|
+
spawn2 = await resolvePtySpawn();
|
|
13578
14442
|
} catch {
|
|
13579
14443
|
return "";
|
|
13580
14444
|
}
|
|
@@ -13590,7 +14454,7 @@ async function runUsageProbe(deps = {}) {
|
|
|
13590
14454
|
return new Promise((resolve) => {
|
|
13591
14455
|
let child;
|
|
13592
14456
|
try {
|
|
13593
|
-
child =
|
|
14457
|
+
child = spawn2(binary, [], {
|
|
13594
14458
|
name: "xterm-256color",
|
|
13595
14459
|
cols: 120,
|
|
13596
14460
|
rows: 45,
|
|
@@ -13606,7 +14470,7 @@ async function runUsageProbe(deps = {}) {
|
|
|
13606
14470
|
}
|
|
13607
14471
|
|
|
13608
14472
|
// src/execution/usage-sampler.ts
|
|
13609
|
-
var
|
|
14473
|
+
var logger7 = createServiceLogger("usage-sampler");
|
|
13610
14474
|
function isAttributable(identity, sessionToken) {
|
|
13611
14475
|
if (!identity) return { ok: true };
|
|
13612
14476
|
if (identity.hasRefreshToken) {
|
|
@@ -13622,7 +14486,7 @@ async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscript
|
|
|
13622
14486
|
try {
|
|
13623
14487
|
const attributable = isAttributable(await readIdentity(), token);
|
|
13624
14488
|
if (!attributable.ok) {
|
|
13625
|
-
|
|
14489
|
+
logger7.info("usage sample skipped \u2014 credentials not attributable to this session's key", {
|
|
13626
14490
|
reason: attributable.reason
|
|
13627
14491
|
});
|
|
13628
14492
|
return [];
|
|
@@ -13649,14 +14513,14 @@ async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscript
|
|
|
13649
14513
|
});
|
|
13650
14514
|
}
|
|
13651
14515
|
if (samples.length === 0) {
|
|
13652
|
-
|
|
14516
|
+
logger7.info("usage sample produced no gauges", {
|
|
13653
14517
|
stdoutLength: stdout.length,
|
|
13654
14518
|
stdoutHead: stdout.slice(0, 200).replaceAll("\n", " ")
|
|
13655
14519
|
});
|
|
13656
14520
|
}
|
|
13657
14521
|
return samples;
|
|
13658
14522
|
} catch (error) {
|
|
13659
|
-
|
|
14523
|
+
logger7.info("usage sample failed", {
|
|
13660
14524
|
error: error instanceof Error ? error.message : String(error)
|
|
13661
14525
|
});
|
|
13662
14526
|
return [];
|
|
@@ -15232,12 +16096,12 @@ var SessionRunner = class _SessionRunner {
|
|
|
15232
16096
|
};
|
|
15233
16097
|
|
|
15234
16098
|
// src/setup/config.ts
|
|
15235
|
-
import { join as
|
|
16099
|
+
import { join as join12 } from "path";
|
|
15236
16100
|
var DEVCONTAINER_PATH = ".devcontainer/conveyor/devcontainer.json";
|
|
15237
16101
|
var DEVCONTAINER_PORT_DENY_LIST = /* @__PURE__ */ new Set([5432, 6379, 9200]);
|
|
15238
16102
|
async function loadForwardPorts(workspaceDir) {
|
|
15239
16103
|
try {
|
|
15240
|
-
const raw = await readWorkspaceFile(
|
|
16104
|
+
const raw = await readWorkspaceFile(join12(workspaceDir, DEVCONTAINER_PATH));
|
|
15241
16105
|
const parsed = JSON.parse(raw);
|
|
15242
16106
|
const ports = (parsed.forwardPorts ?? []).filter(
|
|
15243
16107
|
(p) => typeof p === "number" && !DEVCONTAINER_PORT_DENY_LIST.has(p)
|
|
@@ -15292,16 +16156,15 @@ export {
|
|
|
15292
16156
|
applyBootstrapToEnv,
|
|
15293
16157
|
AgentConnection,
|
|
15294
16158
|
DEFAULT_SONNET_MODEL,
|
|
15295
|
-
TUI_KINDS,
|
|
15296
16159
|
isPermissionDeniedError,
|
|
15297
16160
|
DEFAULT_LIFECYCLE_CONFIG,
|
|
15298
16161
|
Lifecycle,
|
|
15299
|
-
cleanTerminalOutput,
|
|
15300
16162
|
buildSynthesizedCredentials,
|
|
15301
16163
|
claudeJsonPath,
|
|
15302
|
-
ClaudeTuiAdapter,
|
|
15303
16164
|
createServiceLogger,
|
|
15304
16165
|
PtyHarness,
|
|
16166
|
+
resolveTuiKindFromEnv,
|
|
16167
|
+
resolveTuiAdapter,
|
|
15305
16168
|
workspacePathExists,
|
|
15306
16169
|
GIT_TIMEOUT_MS,
|
|
15307
16170
|
hasUncommittedChanges,
|
|
@@ -15312,8 +16175,6 @@ export {
|
|
|
15312
16175
|
flushPendingChanges,
|
|
15313
16176
|
pushToOrigin,
|
|
15314
16177
|
buildProjectTools,
|
|
15315
|
-
TuiUnavailableError,
|
|
15316
|
-
findOnPath,
|
|
15317
16178
|
resolvePlaywrightMcpServer,
|
|
15318
16179
|
resolveSessionStart,
|
|
15319
16180
|
parseUsageGauges,
|
|
@@ -15328,4 +16189,4 @@ export {
|
|
|
15328
16189
|
loadConveyorConfig,
|
|
15329
16190
|
unshallowRepo
|
|
15330
16191
|
};
|
|
15331
|
-
//# sourceMappingURL=chunk-
|
|
16192
|
+
//# sourceMappingURL=chunk-ZTLGCN5G.js.map
|