@rallycry/conveyor-agent 11.0.11 → 11.0.12
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.
|
@@ -346,7 +346,7 @@ var projectCheckpointSettingsSchema = z.object({
|
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
});
|
|
349
|
-
var TUI_KINDS = ["claude-code", "opencode"];
|
|
349
|
+
var TUI_KINDS = ["claude-code", "opencode", "codex"];
|
|
350
350
|
var ACHIEVEMENT_RARITIES = [
|
|
351
351
|
{
|
|
352
352
|
key: "common",
|
|
@@ -2639,8 +2639,8 @@ var ClaudeCodeHarness = class {
|
|
|
2639
2639
|
|
|
2640
2640
|
// src/harness/pty/session.ts
|
|
2641
2641
|
import { randomUUID } from "crypto";
|
|
2642
|
-
import { mkdtemp, mkdir as mkdir3, rm as rm2, writeFile as
|
|
2643
|
-
import { join as
|
|
2642
|
+
import { mkdtemp, mkdir as mkdir3, rm as rm2, writeFile as writeFile6 } from "fs/promises";
|
|
2643
|
+
import { join as join7, dirname } from "path";
|
|
2644
2644
|
|
|
2645
2645
|
// src/harness/opencode/plugin.ts
|
|
2646
2646
|
import { writeFile } from "fs/promises";
|
|
@@ -2963,150 +2963,64 @@ function describeSessionError(event) {
|
|
|
2963
2963
|
return `opencode reported a session error: ${JSON.stringify(event.properties ?? {}).slice(0, 300)}`;
|
|
2964
2964
|
}
|
|
2965
2965
|
|
|
2966
|
-
// src/
|
|
2967
|
-
var
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
}
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
}
|
|
2989
|
-
}
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
return
|
|
3013
|
-
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
}
|
|
3024
|
-
const result = {};
|
|
3025
|
-
if (typeof record.tool_name === "string") result.tool_name = record.tool_name;
|
|
3026
|
-
if (typeof record.elapsed_time_seconds === "number") {
|
|
3027
|
-
result.elapsed_time_seconds = record.elapsed_time_seconds;
|
|
3028
|
-
}
|
|
3029
|
-
return { kind: "progress", progress: result };
|
|
2966
|
+
// src/execution/redactor.ts
|
|
2967
|
+
var REDACTED = "<redacted>";
|
|
2968
|
+
var BEARER_RE = /\b(Bearer\s+)[A-Za-z0-9_\-.]{20,}/g;
|
|
2969
|
+
var VENDOR_KEY_RE = /\b(?:sk-[a-zA-Z0-9_-]{20,}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{22,}|xoxb-[A-Za-z0-9-]+|xai-[A-Za-z0-9-]{20,})\b/g;
|
|
2970
|
+
var AWS_ACCESS_KEY_RE = /\bAKIA[0-9A-Z]{16}\b/g;
|
|
2971
|
+
var JWT_RE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
|
|
2972
|
+
var ENV_SECRET_RE = /^(\s*(?:export\s+)?([A-Z][A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|CREDENTIAL|CREDENTIALS)[A-Z0-9_]*)\s*=\s*)['"]?([^\s'"]+)['"]?/gm;
|
|
2973
|
+
var COOKIE_HEADER_RE = /(Cookie:\s*)[^\r\n]+/gi;
|
|
2974
|
+
var BASIC_AUTH_URL_RE = /(https?:\/\/)([^:@\s/]+):([^@\s]+)@/g;
|
|
2975
|
+
var AWS_SECRET_LINE_RE = /^.*(?:secret|aws_secret).*$/gim;
|
|
2976
|
+
var AWS_SECRET_VALUE_RE = /\b([A-Za-z0-9/+=]{40})\b/g;
|
|
2977
|
+
var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
2978
|
+
function redact(input) {
|
|
2979
|
+
if (!input) return { output: input, redacted: 0 };
|
|
2980
|
+
let count = 0;
|
|
2981
|
+
let output = input;
|
|
2982
|
+
output = output.replace(SYSTEM_REMINDER_RE, () => {
|
|
2983
|
+
count++;
|
|
2984
|
+
return "<!-- stripped injection -->";
|
|
2985
|
+
});
|
|
2986
|
+
output = output.replace(BEARER_RE, (_match, prefix) => {
|
|
2987
|
+
count++;
|
|
2988
|
+
return `${prefix}${REDACTED}`;
|
|
2989
|
+
});
|
|
2990
|
+
output = output.replace(VENDOR_KEY_RE, () => {
|
|
2991
|
+
count++;
|
|
2992
|
+
return REDACTED;
|
|
2993
|
+
});
|
|
2994
|
+
output = output.replace(AWS_ACCESS_KEY_RE, () => {
|
|
2995
|
+
count++;
|
|
2996
|
+
return REDACTED;
|
|
2997
|
+
});
|
|
2998
|
+
output = output.replace(JWT_RE, () => {
|
|
2999
|
+
count++;
|
|
3000
|
+
return REDACTED;
|
|
3001
|
+
});
|
|
3002
|
+
output = output.replace(
|
|
3003
|
+
AWS_SECRET_LINE_RE,
|
|
3004
|
+
(line) => line.replace(AWS_SECRET_VALUE_RE, (match) => {
|
|
3005
|
+
if (/^[a-f0-9]+$/i.test(match)) return match;
|
|
3006
|
+
count++;
|
|
3007
|
+
return REDACTED;
|
|
3008
|
+
})
|
|
3009
|
+
);
|
|
3010
|
+
output = output.replace(ENV_SECRET_RE, (_match, prefix, _key, _value) => {
|
|
3011
|
+
count++;
|
|
3012
|
+
return `${prefix}${REDACTED}`;
|
|
3013
|
+
});
|
|
3014
|
+
output = output.replace(COOKIE_HEADER_RE, (_match, prefix) => {
|
|
3015
|
+
count++;
|
|
3016
|
+
return `${prefix}${REDACTED}`;
|
|
3017
|
+
});
|
|
3018
|
+
output = output.replace(BASIC_AUTH_URL_RE, (_match, scheme, user) => {
|
|
3019
|
+
count++;
|
|
3020
|
+
return `${scheme}${user}:${REDACTED}@`;
|
|
3021
|
+
});
|
|
3022
|
+
return { output, redacted: count };
|
|
3030
3023
|
}
|
|
3031
|
-
var HookSocketServer = class {
|
|
3032
|
-
constructor(socketPath, onProgress, onPreToolUse) {
|
|
3033
|
-
this.socketPath = socketPath;
|
|
3034
|
-
this.onProgress = onProgress;
|
|
3035
|
-
this.onPreToolUse = onPreToolUse;
|
|
3036
|
-
}
|
|
3037
|
-
socketPath;
|
|
3038
|
-
onProgress;
|
|
3039
|
-
onPreToolUse;
|
|
3040
|
-
server = null;
|
|
3041
|
-
_closed = false;
|
|
3042
|
-
async listen() {
|
|
3043
|
-
await unlink(this.socketPath).catch(() => void 0);
|
|
3044
|
-
await new Promise((resolve, reject) => {
|
|
3045
|
-
const server = createServer((socket) => this.handleConnection(socket));
|
|
3046
|
-
server.on("error", reject);
|
|
3047
|
-
server.listen(this.socketPath, () => {
|
|
3048
|
-
resolve();
|
|
3049
|
-
});
|
|
3050
|
-
this.server = server;
|
|
3051
|
-
});
|
|
3052
|
-
}
|
|
3053
|
-
get isClosed() {
|
|
3054
|
-
return this._closed;
|
|
3055
|
-
}
|
|
3056
|
-
async close() {
|
|
3057
|
-
if (this._closed) return;
|
|
3058
|
-
this._closed = true;
|
|
3059
|
-
const server = this.server;
|
|
3060
|
-
this.server = null;
|
|
3061
|
-
if (server) {
|
|
3062
|
-
await new Promise((resolve) => {
|
|
3063
|
-
server.close(() => {
|
|
3064
|
-
resolve();
|
|
3065
|
-
});
|
|
3066
|
-
});
|
|
3067
|
-
}
|
|
3068
|
-
await unlink(this.socketPath).catch(() => void 0);
|
|
3069
|
-
}
|
|
3070
|
-
handleConnection(socket) {
|
|
3071
|
-
socket.on("error", () => void 0);
|
|
3072
|
-
let buffer = "";
|
|
3073
|
-
socket.on("data", (chunk) => {
|
|
3074
|
-
buffer += chunk.toString("utf8");
|
|
3075
|
-
let index = buffer.indexOf("\n");
|
|
3076
|
-
while (index >= 0) {
|
|
3077
|
-
const line = buffer.slice(0, index);
|
|
3078
|
-
buffer = buffer.slice(index + 1);
|
|
3079
|
-
this.dispatch(line, socket);
|
|
3080
|
-
index = buffer.indexOf("\n");
|
|
3081
|
-
}
|
|
3082
|
-
});
|
|
3083
|
-
}
|
|
3084
|
-
dispatch(line, socket) {
|
|
3085
|
-
const envelope = parseEnvelope(line);
|
|
3086
|
-
if (!envelope) return;
|
|
3087
|
-
if (envelope.kind === "progress") {
|
|
3088
|
-
this.onProgress(envelope.progress);
|
|
3089
|
-
return;
|
|
3090
|
-
}
|
|
3091
|
-
void this.respondToPreToolUse(envelope.request, socket);
|
|
3092
|
-
}
|
|
3093
|
-
async respondToPreToolUse(request, socket) {
|
|
3094
|
-
let verdict;
|
|
3095
|
-
try {
|
|
3096
|
-
verdict = this.onPreToolUse ? await this.onPreToolUse(request) : { decision: "allow" };
|
|
3097
|
-
} catch (err) {
|
|
3098
|
-
verdict = {
|
|
3099
|
-
decision: "deny",
|
|
3100
|
-
reason: `Conveyor validation failed: ${err instanceof Error ? err.message : String(err)}. Fix the issue and try again.`
|
|
3101
|
-
};
|
|
3102
|
-
}
|
|
3103
|
-
try {
|
|
3104
|
-
socket.write(`${JSON.stringify({ id: request.id, ...verdict })}
|
|
3105
|
-
`);
|
|
3106
|
-
} catch {
|
|
3107
|
-
}
|
|
3108
|
-
}
|
|
3109
|
-
};
|
|
3110
3024
|
|
|
3111
3025
|
// src/harness/pty/jsonl-tailer.ts
|
|
3112
3026
|
import { open } from "fs/promises";
|
|
@@ -3277,56 +3191,368 @@ var JsonlTailer = class {
|
|
|
3277
3191
|
this.timer = null;
|
|
3278
3192
|
}
|
|
3279
3193
|
}
|
|
3280
|
-
/** Read any remaining bytes and flush a trailing line without a newline. */
|
|
3281
|
-
async flush() {
|
|
3282
|
-
await this.enqueueRead();
|
|
3283
|
-
if (this.buffer.length > 0) {
|
|
3284
|
-
this.emitLine(this.buffer);
|
|
3285
|
-
this.buffer = "";
|
|
3194
|
+
/** Read any remaining bytes and flush a trailing line without a newline. */
|
|
3195
|
+
async flush() {
|
|
3196
|
+
await this.enqueueRead();
|
|
3197
|
+
if (this.buffer.length > 0) {
|
|
3198
|
+
this.emitLine(this.buffer);
|
|
3199
|
+
this.buffer = "";
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
enqueueRead() {
|
|
3203
|
+
this.chain = this.chain.then(() => this.readOnce());
|
|
3204
|
+
return this.chain;
|
|
3205
|
+
}
|
|
3206
|
+
async readOnce() {
|
|
3207
|
+
let handle = null;
|
|
3208
|
+
try {
|
|
3209
|
+
handle = await open(this.path, "r");
|
|
3210
|
+
const stats = await handle.stat();
|
|
3211
|
+
if (stats.size <= this.offset) return;
|
|
3212
|
+
const length = stats.size - this.offset;
|
|
3213
|
+
const buf = Buffer.alloc(length);
|
|
3214
|
+
await handle.read(buf, 0, length, this.offset);
|
|
3215
|
+
this.offset = stats.size;
|
|
3216
|
+
this.consume(buf.toString("utf8"));
|
|
3217
|
+
} catch {
|
|
3218
|
+
} finally {
|
|
3219
|
+
if (handle) await handle.close();
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
consume(chunk) {
|
|
3223
|
+
this.buffer += chunk;
|
|
3224
|
+
let index = this.buffer.indexOf("\n");
|
|
3225
|
+
while (index >= 0) {
|
|
3226
|
+
const line = this.buffer.slice(0, index);
|
|
3227
|
+
this.buffer = this.buffer.slice(index + 1);
|
|
3228
|
+
this.emitLine(line);
|
|
3229
|
+
index = this.buffer.indexOf("\n");
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
emitLine(line) {
|
|
3233
|
+
const trimmed = line.trim();
|
|
3234
|
+
if (trimmed.length === 0) return;
|
|
3235
|
+
let parsed;
|
|
3236
|
+
try {
|
|
3237
|
+
parsed = JSON.parse(trimmed);
|
|
3238
|
+
} catch {
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
this.onRawRecord?.(parsed);
|
|
3242
|
+
const event = this.mapRecord(parsed);
|
|
3243
|
+
if (event) this.onEvent(event);
|
|
3244
|
+
}
|
|
3245
|
+
};
|
|
3246
|
+
|
|
3247
|
+
// src/harness/codex/event-source.ts
|
|
3248
|
+
var CodexEventSource = class {
|
|
3249
|
+
constructor(emit, onSession, chat) {
|
|
3250
|
+
this.emit = emit;
|
|
3251
|
+
this.onSession = onSession;
|
|
3252
|
+
this.chat = chat;
|
|
3253
|
+
}
|
|
3254
|
+
emit;
|
|
3255
|
+
onSession;
|
|
3256
|
+
chat;
|
|
3257
|
+
sessionId = null;
|
|
3258
|
+
completed = /* @__PURE__ */ new Set();
|
|
3259
|
+
transcript = null;
|
|
3260
|
+
handleRecord(raw) {
|
|
3261
|
+
if (!raw || typeof raw !== "object") return;
|
|
3262
|
+
const e = raw;
|
|
3263
|
+
const id = e.session_id ?? e["thread-id"];
|
|
3264
|
+
if (typeof id !== "string") return;
|
|
3265
|
+
if (!this.sessionId) {
|
|
3266
|
+
this.sessionId = id;
|
|
3267
|
+
this.onSession(id);
|
|
3268
|
+
}
|
|
3269
|
+
if (id !== this.sessionId) return;
|
|
3270
|
+
if (e.hook_event_name === "SessionStart") this.watchTranscript(e);
|
|
3271
|
+
if (e.type === "agent-turn-complete") {
|
|
3272
|
+
this.finish(e);
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
if (e.hook_event_name === "UserPromptSubmit") {
|
|
3276
|
+
this.emit({ type: "tool_progress", tool_name: "Codex", elapsed_time_seconds: 0 });
|
|
3277
|
+
if (typeof e.prompt === "string")
|
|
3278
|
+
this.chat({ kind: "user_text", text: e.prompt.slice(0, 16e3) });
|
|
3279
|
+
} else if (e.hook_event_name === "PreToolUse") {
|
|
3280
|
+
this.toolStarted(e);
|
|
3281
|
+
} else if (e.hook_event_name === "PostToolUse") {
|
|
3282
|
+
this.emit({
|
|
3283
|
+
type: "tool_progress",
|
|
3284
|
+
tool_name: String(e.tool_name ?? "tool"),
|
|
3285
|
+
elapsed_time_seconds: 0
|
|
3286
|
+
});
|
|
3287
|
+
} else if (e.hook_event_name === "Interrupt") {
|
|
3288
|
+
this.emit({ type: "result", subtype: "error", errors: ["Codex turn interrupted"] });
|
|
3289
|
+
this.chat({ kind: "turn_end" });
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
async close() {
|
|
3293
|
+
this.transcript?.close();
|
|
3294
|
+
await this.transcript?.flush();
|
|
3295
|
+
this.transcript = null;
|
|
3296
|
+
}
|
|
3297
|
+
watchTranscript(e) {
|
|
3298
|
+
if (this.transcript || typeof e.transcript_path !== "string" || typeof e.transcript_offset !== "number" || e.transcript_offset < 0)
|
|
3299
|
+
return;
|
|
3300
|
+
this.transcript = new JsonlTailer(
|
|
3301
|
+
e.transcript_path,
|
|
3302
|
+
() => void 0,
|
|
3303
|
+
(raw) => this.handleTranscriptRecord(raw),
|
|
3304
|
+
() => null
|
|
3305
|
+
);
|
|
3306
|
+
this.transcript.start(e.transcript_offset);
|
|
3307
|
+
}
|
|
3308
|
+
handleTranscriptRecord(raw) {
|
|
3309
|
+
if (!raw || typeof raw !== "object") return;
|
|
3310
|
+
const record = raw;
|
|
3311
|
+
if (record.type !== "event_msg" || !record.payload || typeof record.payload !== "object")
|
|
3312
|
+
return;
|
|
3313
|
+
const event = record.payload;
|
|
3314
|
+
if (event.type !== "task_complete" || !event.error || typeof event.error !== "object") return;
|
|
3315
|
+
const turn = event.turn_id;
|
|
3316
|
+
if (typeof turn !== "string" || this.completed.has(turn)) return;
|
|
3317
|
+
this.rememberTurn(turn);
|
|
3318
|
+
const error = event.error;
|
|
3319
|
+
this.emit({
|
|
3320
|
+
type: "result",
|
|
3321
|
+
subtype: "error",
|
|
3322
|
+
errors: [redact(String(error.message ?? "Codex turn failed")).output]
|
|
3323
|
+
});
|
|
3324
|
+
this.chat({ kind: "turn_end" });
|
|
3325
|
+
}
|
|
3326
|
+
rememberTurn(turn) {
|
|
3327
|
+
this.completed.add(turn);
|
|
3328
|
+
const oldest = this.completed.values().next().value;
|
|
3329
|
+
if (this.completed.size > 200 && oldest) this.completed.delete(oldest);
|
|
3330
|
+
}
|
|
3331
|
+
toolStarted(e) {
|
|
3332
|
+
const name = typeof e.tool_name === "string" ? e.tool_name : "tool";
|
|
3333
|
+
const id = typeof e.tool_use_id === "string" ? e.tool_use_id : "";
|
|
3334
|
+
this.emit({
|
|
3335
|
+
type: "assistant",
|
|
3336
|
+
message: {
|
|
3337
|
+
role: "assistant",
|
|
3338
|
+
content: [{ type: "tool_use", id, name, input: e.tool_input }]
|
|
3339
|
+
}
|
|
3340
|
+
});
|
|
3341
|
+
this.chat({
|
|
3342
|
+
kind: "tool_use",
|
|
3343
|
+
name,
|
|
3344
|
+
id,
|
|
3345
|
+
input: redact(JSON.stringify(e.tool_input ?? {})).output.slice(0, 1900)
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
finish(e) {
|
|
3349
|
+
const turn = e["turn-id"];
|
|
3350
|
+
if (typeof turn !== "string" || this.completed.has(turn)) return;
|
|
3351
|
+
this.rememberTurn(turn);
|
|
3352
|
+
const text = typeof e["last-assistant-message"] === "string" ? e["last-assistant-message"] : "";
|
|
3353
|
+
if (text) {
|
|
3354
|
+
this.emit({
|
|
3355
|
+
type: "assistant",
|
|
3356
|
+
message: { role: "assistant", content: [{ type: "text", text }] }
|
|
3357
|
+
});
|
|
3358
|
+
this.chat({ kind: "assistant_text", text: text.slice(0, 16e3) });
|
|
3359
|
+
}
|
|
3360
|
+
this.emit({ type: "result", subtype: "success", result: text, total_cost_usd: 0 });
|
|
3361
|
+
this.chat({ kind: "turn_end" });
|
|
3362
|
+
}
|
|
3363
|
+
};
|
|
3364
|
+
|
|
3365
|
+
// src/harness/codex/config.ts
|
|
3366
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
3367
|
+
import { join as join2 } from "path";
|
|
3368
|
+
function tomlValue(value) {
|
|
3369
|
+
if (Array.isArray(value)) return `[${value.map(tomlValue).join(", ")}]`;
|
|
3370
|
+
if (value && typeof value === "object") {
|
|
3371
|
+
return `{ ${Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => `${JSON.stringify(k)} = ${tomlValue(v)}`).join(", ")} }`;
|
|
3372
|
+
}
|
|
3373
|
+
return JSON.stringify(value);
|
|
3374
|
+
}
|
|
3375
|
+
function codexMcpConfig(entries) {
|
|
3376
|
+
return Object.fromEntries(
|
|
3377
|
+
Object.entries(entries).map(([name, entry]) => [
|
|
3378
|
+
name,
|
|
3379
|
+
entry.type === "http" ? { url: entry.url, http_headers: entry.headers, tool_timeout_sec: 600 } : { command: entry.command, args: entry.args ?? [], env: entry.env ?? {} }
|
|
3380
|
+
])
|
|
3381
|
+
);
|
|
3382
|
+
}
|
|
3383
|
+
async function stageCodexEvents(dir) {
|
|
3384
|
+
const pluginPath = join2(dir, "codex-events.cjs");
|
|
3385
|
+
const eventsSinkPath = join2(dir, "codex-events.ndjson");
|
|
3386
|
+
await writeFile2(eventsSinkPath, "", { mode: 384 });
|
|
3387
|
+
await writeFile2(
|
|
3388
|
+
pluginPath,
|
|
3389
|
+
`const fs = require('node:fs');
|
|
3390
|
+
const input = process.argv[3] || fs.readFileSync(0, 'utf8');
|
|
3391
|
+
const event = JSON.parse(input);
|
|
3392
|
+
if (event.hook_event_name === 'SessionStart' && event.transcript_path) {
|
|
3393
|
+
try { event.transcript_offset = fs.statSync(event.transcript_path).size; }
|
|
3394
|
+
catch { event.transcript_offset = 0; }
|
|
3395
|
+
}
|
|
3396
|
+
fs.appendFileSync(process.argv[2], JSON.stringify(event) + '\\n');
|
|
3397
|
+
process.stdout.write('{}');
|
|
3398
|
+
`,
|
|
3399
|
+
{ mode: 384 }
|
|
3400
|
+
);
|
|
3401
|
+
return { pluginPath, eventsSinkPath };
|
|
3402
|
+
}
|
|
3403
|
+
function codexHookConfig(script, sink) {
|
|
3404
|
+
const quote = (s) => `'${s.replaceAll("'", "'\\''")}'`;
|
|
3405
|
+
const command = `node ${quote(script)} ${quote(sink)}`;
|
|
3406
|
+
return Object.fromEntries(
|
|
3407
|
+
["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Interrupt"].map((event) => [
|
|
3408
|
+
event,
|
|
3409
|
+
[{ hooks: [{ type: "command", command, timeout: 3 }] }]
|
|
3410
|
+
])
|
|
3411
|
+
);
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
// src/harness/pty/event-queue.ts
|
|
3415
|
+
var AsyncEventQueue = class {
|
|
3416
|
+
items = [];
|
|
3417
|
+
closed = false;
|
|
3418
|
+
wake = null;
|
|
3419
|
+
push(item) {
|
|
3420
|
+
if (this.closed) return;
|
|
3421
|
+
this.items.push(item);
|
|
3422
|
+
this.signal();
|
|
3423
|
+
}
|
|
3424
|
+
close() {
|
|
3425
|
+
this.closed = true;
|
|
3426
|
+
this.signal();
|
|
3427
|
+
}
|
|
3428
|
+
get isClosed() {
|
|
3429
|
+
return this.closed;
|
|
3430
|
+
}
|
|
3431
|
+
signal() {
|
|
3432
|
+
const wake = this.wake;
|
|
3433
|
+
if (wake) {
|
|
3434
|
+
this.wake = null;
|
|
3435
|
+
wake();
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
async *drain() {
|
|
3439
|
+
while (!this.closed || this.items.length > 0) {
|
|
3440
|
+
if (this.items.length > 0) {
|
|
3441
|
+
const item = this.items.shift();
|
|
3442
|
+
if (item !== void 0) yield item;
|
|
3443
|
+
continue;
|
|
3444
|
+
}
|
|
3445
|
+
await new Promise((resolve) => {
|
|
3446
|
+
this.wake = resolve;
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
};
|
|
3451
|
+
|
|
3452
|
+
// src/harness/pty/hook-socket.ts
|
|
3453
|
+
import { createServer } from "net";
|
|
3454
|
+
import { unlink } from "fs/promises";
|
|
3455
|
+
function parseEnvelope(line) {
|
|
3456
|
+
let parsed;
|
|
3457
|
+
try {
|
|
3458
|
+
parsed = JSON.parse(line);
|
|
3459
|
+
} catch {
|
|
3460
|
+
return null;
|
|
3461
|
+
}
|
|
3462
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
3463
|
+
const record = parsed;
|
|
3464
|
+
if (record.kind === "pre_tool_use") {
|
|
3465
|
+
if (typeof record.id !== "string" || typeof record.tool_name !== "string") return null;
|
|
3466
|
+
const toolInput = typeof record.tool_input === "object" && record.tool_input !== null ? record.tool_input : {};
|
|
3467
|
+
return {
|
|
3468
|
+
kind: "pre_tool_use",
|
|
3469
|
+
request: { id: record.id, tool_name: record.tool_name, tool_input: toolInput }
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
const result = {};
|
|
3473
|
+
if (typeof record.tool_name === "string") result.tool_name = record.tool_name;
|
|
3474
|
+
if (typeof record.elapsed_time_seconds === "number") {
|
|
3475
|
+
result.elapsed_time_seconds = record.elapsed_time_seconds;
|
|
3476
|
+
}
|
|
3477
|
+
return { kind: "progress", progress: result };
|
|
3478
|
+
}
|
|
3479
|
+
var HookSocketServer = class {
|
|
3480
|
+
constructor(socketPath, onProgress, onPreToolUse) {
|
|
3481
|
+
this.socketPath = socketPath;
|
|
3482
|
+
this.onProgress = onProgress;
|
|
3483
|
+
this.onPreToolUse = onPreToolUse;
|
|
3484
|
+
}
|
|
3485
|
+
socketPath;
|
|
3486
|
+
onProgress;
|
|
3487
|
+
onPreToolUse;
|
|
3488
|
+
server = null;
|
|
3489
|
+
_closed = false;
|
|
3490
|
+
async listen() {
|
|
3491
|
+
await unlink(this.socketPath).catch(() => void 0);
|
|
3492
|
+
await new Promise((resolve, reject) => {
|
|
3493
|
+
const server = createServer((socket) => this.handleConnection(socket));
|
|
3494
|
+
server.on("error", reject);
|
|
3495
|
+
server.listen(this.socketPath, () => {
|
|
3496
|
+
resolve();
|
|
3497
|
+
});
|
|
3498
|
+
this.server = server;
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
get isClosed() {
|
|
3502
|
+
return this._closed;
|
|
3503
|
+
}
|
|
3504
|
+
async close() {
|
|
3505
|
+
if (this._closed) return;
|
|
3506
|
+
this._closed = true;
|
|
3507
|
+
const server = this.server;
|
|
3508
|
+
this.server = null;
|
|
3509
|
+
if (server) {
|
|
3510
|
+
await new Promise((resolve) => {
|
|
3511
|
+
server.close(() => {
|
|
3512
|
+
resolve();
|
|
3513
|
+
});
|
|
3514
|
+
});
|
|
3286
3515
|
}
|
|
3516
|
+
await unlink(this.socketPath).catch(() => void 0);
|
|
3287
3517
|
}
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3518
|
+
handleConnection(socket) {
|
|
3519
|
+
socket.on("error", () => void 0);
|
|
3520
|
+
let buffer = "";
|
|
3521
|
+
socket.on("data", (chunk) => {
|
|
3522
|
+
buffer += chunk.toString("utf8");
|
|
3523
|
+
let index = buffer.indexOf("\n");
|
|
3524
|
+
while (index >= 0) {
|
|
3525
|
+
const line = buffer.slice(0, index);
|
|
3526
|
+
buffer = buffer.slice(index + 1);
|
|
3527
|
+
this.dispatch(line, socket);
|
|
3528
|
+
index = buffer.indexOf("\n");
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3291
3531
|
}
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
const length = stats.size - this.offset;
|
|
3299
|
-
const buf = Buffer.alloc(length);
|
|
3300
|
-
await handle.read(buf, 0, length, this.offset);
|
|
3301
|
-
this.offset = stats.size;
|
|
3302
|
-
this.consume(buf.toString("utf8"));
|
|
3303
|
-
} catch {
|
|
3304
|
-
} finally {
|
|
3305
|
-
if (handle) await handle.close();
|
|
3532
|
+
dispatch(line, socket) {
|
|
3533
|
+
const envelope = parseEnvelope(line);
|
|
3534
|
+
if (!envelope) return;
|
|
3535
|
+
if (envelope.kind === "progress") {
|
|
3536
|
+
this.onProgress(envelope.progress);
|
|
3537
|
+
return;
|
|
3306
3538
|
}
|
|
3539
|
+
void this.respondToPreToolUse(envelope.request, socket);
|
|
3307
3540
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3541
|
+
async respondToPreToolUse(request, socket) {
|
|
3542
|
+
let verdict;
|
|
3543
|
+
try {
|
|
3544
|
+
verdict = this.onPreToolUse ? await this.onPreToolUse(request) : { decision: "allow" };
|
|
3545
|
+
} catch (err) {
|
|
3546
|
+
verdict = {
|
|
3547
|
+
decision: "deny",
|
|
3548
|
+
reason: `Conveyor validation failed: ${err instanceof Error ? err.message : String(err)}. Fix the issue and try again.`
|
|
3549
|
+
};
|
|
3316
3550
|
}
|
|
3317
|
-
}
|
|
3318
|
-
emitLine(line) {
|
|
3319
|
-
const trimmed = line.trim();
|
|
3320
|
-
if (trimmed.length === 0) return;
|
|
3321
|
-
let parsed;
|
|
3322
3551
|
try {
|
|
3323
|
-
|
|
3552
|
+
socket.write(`${JSON.stringify({ id: request.id, ...verdict })}
|
|
3553
|
+
`);
|
|
3324
3554
|
} catch {
|
|
3325
|
-
return;
|
|
3326
3555
|
}
|
|
3327
|
-
this.onRawRecord?.(parsed);
|
|
3328
|
-
const event = this.mapRecord(parsed);
|
|
3329
|
-
if (event) this.onEvent(event);
|
|
3330
3556
|
}
|
|
3331
3557
|
};
|
|
3332
3558
|
|
|
@@ -3518,17 +3744,17 @@ function mapChatRecords(raw) {
|
|
|
3518
3744
|
}
|
|
3519
3745
|
|
|
3520
3746
|
// src/harness/pty/settings.ts
|
|
3521
|
-
import { mkdir, writeFile as
|
|
3747
|
+
import { mkdir, writeFile as writeFile3, chmod } from "fs/promises";
|
|
3522
3748
|
import { homedir } from "os";
|
|
3523
|
-
import { join as
|
|
3749
|
+
import { join as join3 } from "path";
|
|
3524
3750
|
function claudeConfigHome() {
|
|
3525
|
-
return process.env.CLAUDE_CONFIG_DIR ??
|
|
3751
|
+
return process.env.CLAUDE_CONFIG_DIR ?? join3(homedir(), ".claude");
|
|
3526
3752
|
}
|
|
3527
3753
|
function projectSlug(cwd) {
|
|
3528
3754
|
return cwd.replace(/\//g, "-");
|
|
3529
3755
|
}
|
|
3530
3756
|
function sessionTranscriptPath(cwd, sessionId) {
|
|
3531
|
-
return
|
|
3757
|
+
return join3(claudeConfigHome(), "projects", projectSlug(cwd), `${sessionId}.jsonl`);
|
|
3532
3758
|
}
|
|
3533
3759
|
var ALLOW_RULES = [
|
|
3534
3760
|
"Bash",
|
|
@@ -3731,10 +3957,10 @@ function buildPreToolUseHooks(helperPath, gateAllTools) {
|
|
|
3731
3957
|
];
|
|
3732
3958
|
}
|
|
3733
3959
|
async function writeHookSettings(dir, opts = {}) {
|
|
3734
|
-
const helperPath =
|
|
3735
|
-
const settingsPath =
|
|
3960
|
+
const helperPath = join3(dir, "hook-helper.cjs");
|
|
3961
|
+
const settingsPath = join3(dir, "settings.json");
|
|
3736
3962
|
await mkdir(dir, { recursive: true });
|
|
3737
|
-
await
|
|
3963
|
+
await writeFile3(helperPath, HOOK_HELPER_SOURCE, "utf8");
|
|
3738
3964
|
await chmod(helperPath, 493);
|
|
3739
3965
|
const settings = {
|
|
3740
3966
|
// Pre-accept Claude Code's "Bypass Permissions mode" disclaimer. Build-capable
|
|
@@ -3771,69 +3997,10 @@ async function writeHookSettings(dir, opts = {}) {
|
|
|
3771
3997
|
]
|
|
3772
3998
|
}
|
|
3773
3999
|
};
|
|
3774
|
-
await
|
|
4000
|
+
await writeFile3(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
3775
4001
|
return { settingsPath, helperPath };
|
|
3776
4002
|
}
|
|
3777
4003
|
|
|
3778
|
-
// src/execution/redactor.ts
|
|
3779
|
-
var REDACTED = "<redacted>";
|
|
3780
|
-
var BEARER_RE = /\b(Bearer\s+)[A-Za-z0-9_\-.]{20,}/g;
|
|
3781
|
-
var VENDOR_KEY_RE = /\b(?:sk-[a-zA-Z0-9_-]{20,}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{22,}|xoxb-[A-Za-z0-9-]+|xai-[A-Za-z0-9-]{20,})\b/g;
|
|
3782
|
-
var AWS_ACCESS_KEY_RE = /\bAKIA[0-9A-Z]{16}\b/g;
|
|
3783
|
-
var JWT_RE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
|
|
3784
|
-
var ENV_SECRET_RE = /^(\s*(?:export\s+)?([A-Z][A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|CREDENTIAL|CREDENTIALS)[A-Z0-9_]*)\s*=\s*)['"]?([^\s'"]+)['"]?/gm;
|
|
3785
|
-
var COOKIE_HEADER_RE = /(Cookie:\s*)[^\r\n]+/gi;
|
|
3786
|
-
var BASIC_AUTH_URL_RE = /(https?:\/\/)([^:@\s/]+):([^@\s]+)@/g;
|
|
3787
|
-
var AWS_SECRET_LINE_RE = /^.*(?:secret|aws_secret).*$/gim;
|
|
3788
|
-
var AWS_SECRET_VALUE_RE = /\b([A-Za-z0-9/+=]{40})\b/g;
|
|
3789
|
-
var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
|
|
3790
|
-
function redact(input) {
|
|
3791
|
-
if (!input) return { output: input, redacted: 0 };
|
|
3792
|
-
let count = 0;
|
|
3793
|
-
let output = input;
|
|
3794
|
-
output = output.replace(SYSTEM_REMINDER_RE, () => {
|
|
3795
|
-
count++;
|
|
3796
|
-
return "<!-- stripped injection -->";
|
|
3797
|
-
});
|
|
3798
|
-
output = output.replace(BEARER_RE, (_match, prefix) => {
|
|
3799
|
-
count++;
|
|
3800
|
-
return `${prefix}${REDACTED}`;
|
|
3801
|
-
});
|
|
3802
|
-
output = output.replace(VENDOR_KEY_RE, () => {
|
|
3803
|
-
count++;
|
|
3804
|
-
return REDACTED;
|
|
3805
|
-
});
|
|
3806
|
-
output = output.replace(AWS_ACCESS_KEY_RE, () => {
|
|
3807
|
-
count++;
|
|
3808
|
-
return REDACTED;
|
|
3809
|
-
});
|
|
3810
|
-
output = output.replace(JWT_RE, () => {
|
|
3811
|
-
count++;
|
|
3812
|
-
return REDACTED;
|
|
3813
|
-
});
|
|
3814
|
-
output = output.replace(
|
|
3815
|
-
AWS_SECRET_LINE_RE,
|
|
3816
|
-
(line) => line.replace(AWS_SECRET_VALUE_RE, (match) => {
|
|
3817
|
-
if (/^[a-f0-9]+$/i.test(match)) return match;
|
|
3818
|
-
count++;
|
|
3819
|
-
return REDACTED;
|
|
3820
|
-
})
|
|
3821
|
-
);
|
|
3822
|
-
output = output.replace(ENV_SECRET_RE, (_match, prefix, _key, _value) => {
|
|
3823
|
-
count++;
|
|
3824
|
-
return `${prefix}${REDACTED}`;
|
|
3825
|
-
});
|
|
3826
|
-
output = output.replace(COOKIE_HEADER_RE, (_match, prefix) => {
|
|
3827
|
-
count++;
|
|
3828
|
-
return `${prefix}${REDACTED}`;
|
|
3829
|
-
});
|
|
3830
|
-
output = output.replace(BASIC_AUTH_URL_RE, (_match, scheme, user) => {
|
|
3831
|
-
count++;
|
|
3832
|
-
return `${scheme}${user}:${REDACTED}@`;
|
|
3833
|
-
});
|
|
3834
|
-
return { output, redacted: count };
|
|
3835
|
-
}
|
|
3836
|
-
|
|
3837
4004
|
// src/harness/pty/output-coalescer.ts
|
|
3838
4005
|
var DEFAULT_FLUSH_MS = 40;
|
|
3839
4006
|
var DEFAULT_MAX_BUFFER_CHARS = 48 * 1024;
|
|
@@ -3889,8 +4056,8 @@ var PtyOutputCoalescer = class {
|
|
|
3889
4056
|
// src/harness/pty/tool-server.ts
|
|
3890
4057
|
import { createServer as createServer2 } from "http";
|
|
3891
4058
|
import { z as z12 } from "zod";
|
|
3892
|
-
import { writeFile as
|
|
3893
|
-
import { join as
|
|
4059
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
4060
|
+
import { join as join4 } from "path";
|
|
3894
4061
|
import { randomBytes } from "crypto";
|
|
3895
4062
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3896
4063
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -4081,21 +4248,21 @@ async function startToolServers(mcpServers, tempDir) {
|
|
|
4081
4248
|
config[name] = { type: "http", url, headers: { Authorization: `Bearer ${token}` } };
|
|
4082
4249
|
}
|
|
4083
4250
|
if (Object.keys(config).length === 0) return { servers, mcpConfigPath: null, entries: config };
|
|
4084
|
-
const mcpConfigPath =
|
|
4085
|
-
await
|
|
4251
|
+
const mcpConfigPath = join4(tempDir, "mcp-config.json");
|
|
4252
|
+
await writeFile4(mcpConfigPath, JSON.stringify({ mcpServers: config }, null, 2), "utf8");
|
|
4086
4253
|
return { servers, mcpConfigPath, entries: config };
|
|
4087
4254
|
}
|
|
4088
4255
|
|
|
4089
4256
|
// src/harness/pty/credentials.ts
|
|
4090
|
-
import { chmod as chmod2, mkdir as mkdir2, readFile, rm, writeFile as
|
|
4257
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile, rm, writeFile as writeFile5 } from "fs/promises";
|
|
4091
4258
|
import { homedir as homedir2 } from "os";
|
|
4092
|
-
import { join as
|
|
4259
|
+
import { join as join6 } from "path";
|
|
4093
4260
|
|
|
4094
4261
|
// src/harness/pty/credentials-marker.ts
|
|
4095
4262
|
import { createHash } from "crypto";
|
|
4096
|
-
import { join as
|
|
4263
|
+
import { join as join5 } from "path";
|
|
4097
4264
|
function conveyorCredentialsMarkerPath() {
|
|
4098
|
-
return
|
|
4265
|
+
return join5(claudeConfigHome(), ".conveyor-credentials.json");
|
|
4099
4266
|
}
|
|
4100
4267
|
function tokenFingerprint(accessToken) {
|
|
4101
4268
|
return createHash("sha256").update(accessToken).digest("hex");
|
|
@@ -4291,7 +4458,7 @@ function planCredentialsWrite(input) {
|
|
|
4291
4458
|
|
|
4292
4459
|
// src/harness/pty/credentials.ts
|
|
4293
4460
|
function claudeCredentialsPath() {
|
|
4294
|
-
return
|
|
4461
|
+
return join6(claudeConfigHome(), ".credentials.json");
|
|
4295
4462
|
}
|
|
4296
4463
|
function isConveyorCloudEnv(env = process.env) {
|
|
4297
4464
|
return Boolean(env.CLAUDESPACE_NAME || env.CODESPACE_NAME || env.CODESPACES);
|
|
@@ -4341,7 +4508,7 @@ async function writeWithReadBackRetry(io, contents, delaysMs = READ_BACK_DELAYS_
|
|
|
4341
4508
|
}
|
|
4342
4509
|
function fsWriteIo(path, mode) {
|
|
4343
4510
|
return {
|
|
4344
|
-
write: (contents) =>
|
|
4511
|
+
write: (contents) => writeFile5(path, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode }),
|
|
4345
4512
|
read: () => readRaw(path)
|
|
4346
4513
|
};
|
|
4347
4514
|
}
|
|
@@ -4421,7 +4588,7 @@ async function sanitizeApprovedApiKeys(oauthToken) {
|
|
|
4421
4588
|
}
|
|
4422
4589
|
function claudeJsonPath() {
|
|
4423
4590
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
4424
|
-
return configDir ?
|
|
4591
|
+
return configDir ? join6(configDir, ".claude.json") : join6(homedir2(), ".claude.json");
|
|
4425
4592
|
}
|
|
4426
4593
|
function asRecord(value) {
|
|
4427
4594
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
@@ -4514,7 +4681,7 @@ function planClaudeJsonSeed(existingRaw, trustCwd, oauthIdentity) {
|
|
|
4514
4681
|
return changed ? JSON.stringify(config) : null;
|
|
4515
4682
|
}
|
|
4516
4683
|
function conveyorOauthMarkerPath() {
|
|
4517
|
-
return
|
|
4684
|
+
return join6(claudeConfigHome(), "conveyor-oauth-account.json");
|
|
4518
4685
|
}
|
|
4519
4686
|
function parseOauthIdentity(raw) {
|
|
4520
4687
|
if (!raw || raw.trim() === "") return null;
|
|
@@ -4754,6 +4921,10 @@ var PtySession = class {
|
|
|
4754
4921
|
// start from the constructor args (turn 1) and are replaced by beginTurn.
|
|
4755
4922
|
turnPrompt;
|
|
4756
4923
|
turn;
|
|
4924
|
+
codexPromptInArgv = false;
|
|
4925
|
+
codexInitialPrompt;
|
|
4926
|
+
codexSource = null;
|
|
4927
|
+
adapterCleanupPaths = [];
|
|
4757
4928
|
onIdle(listener) {
|
|
4758
4929
|
this.idleListeners.push(listener);
|
|
4759
4930
|
}
|
|
@@ -4817,6 +4988,10 @@ var PtySession = class {
|
|
|
4817
4988
|
get reportedSessionId() {
|
|
4818
4989
|
return this.reportedOpenCodeId;
|
|
4819
4990
|
}
|
|
4991
|
+
/** Conveyor's logical conversation, distinct from a native runner's session id. */
|
|
4992
|
+
get lineageId() {
|
|
4993
|
+
return this.options.sessionId;
|
|
4994
|
+
}
|
|
4820
4995
|
/** Fingerprint of the spawn-time options a reused process cannot change. */
|
|
4821
4996
|
get spawnFingerprint() {
|
|
4822
4997
|
return this.adapter.spawnFingerprint({
|
|
@@ -4960,7 +5135,21 @@ var PtySession = class {
|
|
|
4960
5135
|
this.abortHandler = null;
|
|
4961
5136
|
}
|
|
4962
5137
|
}
|
|
5138
|
+
async collectCodexPrompt() {
|
|
5139
|
+
if (this.adapter.id === "codex" && typeof this.turnPrompt !== "string") {
|
|
5140
|
+
const stream = this.turnPrompt;
|
|
5141
|
+
const first = await stream.next();
|
|
5142
|
+
if (first.done) return;
|
|
5143
|
+
const content = first.value.message.content;
|
|
5144
|
+
this.codexInitialPrompt = typeof content === "string" ? content : renderPromptContentText(content);
|
|
5145
|
+
this.turnPrompt = (async function* () {
|
|
5146
|
+
yield first.value;
|
|
5147
|
+
yield* stream;
|
|
5148
|
+
})();
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
4963
5151
|
async start() {
|
|
5152
|
+
await this.collectCodexPrompt();
|
|
4964
5153
|
const sessionId = this.resume ?? this.options.sessionId;
|
|
4965
5154
|
if (!sessionId) {
|
|
4966
5155
|
throw new Error("PtySession requires options.sessionId or a resume target");
|
|
@@ -4973,14 +5162,17 @@ var PtySession = class {
|
|
|
4973
5162
|
return;
|
|
4974
5163
|
}
|
|
4975
5164
|
try {
|
|
4976
|
-
if (this.adapter.
|
|
5165
|
+
if (this.adapter.id === "codex") {
|
|
5166
|
+
const extras = await this.startCodexEventSources();
|
|
5167
|
+
await this.spawn(void 0, void 0, extras);
|
|
5168
|
+
} else if (this.adapter.capabilities.structuredEvents && this.adapter.id === "opencode") {
|
|
4977
5169
|
const extras = await this.startOpenCodeEventSources();
|
|
4978
5170
|
await this.spawn(void 0, void 0, extras);
|
|
4979
5171
|
} else if (this.adapter.capabilities.structuredEvents) {
|
|
4980
5172
|
const { settingsPath, socketPath } = await this.startStructuredEventSources(sessionId);
|
|
4981
5173
|
await this.spawn(settingsPath, socketPath);
|
|
4982
5174
|
} else {
|
|
4983
|
-
this.tempDir = await mkdtemp(
|
|
5175
|
+
this.tempDir = await mkdtemp(join7(sessionTempBase(), "conveyor-pty-"));
|
|
4984
5176
|
await this.spawn();
|
|
4985
5177
|
this.pushEvent({
|
|
4986
5178
|
type: "system",
|
|
@@ -5024,8 +5216,41 @@ var PtySession = class {
|
|
|
5024
5216
|
* synthetic `system init` reports the opencode-assigned session id the
|
|
5025
5217
|
* moment it is known, which is how the runner learns the resume target.
|
|
5026
5218
|
*/
|
|
5219
|
+
async startCodexEventSources() {
|
|
5220
|
+
this.tempDir = await mkdtemp(join7(sessionTempBase(), "conveyor-codex-"));
|
|
5221
|
+
const { servers, entries } = await startToolServers(
|
|
5222
|
+
this.options.mcpServers ?? {},
|
|
5223
|
+
this.tempDir
|
|
5224
|
+
);
|
|
5225
|
+
this.toolServers = servers;
|
|
5226
|
+
const extras = await stageCodexEvents(this.tempDir);
|
|
5227
|
+
const source = new CodexEventSource(
|
|
5228
|
+
(event) => this.handleTranscriptEvent(event),
|
|
5229
|
+
(id) => {
|
|
5230
|
+
this.reportedOpenCodeId = id;
|
|
5231
|
+
this.adapter.recordSessionId?.(id, this.options);
|
|
5232
|
+
this.pushEvent({
|
|
5233
|
+
type: "system",
|
|
5234
|
+
subtype: "init",
|
|
5235
|
+
session_id: id,
|
|
5236
|
+
model: this.options.model
|
|
5237
|
+
});
|
|
5238
|
+
this.sendChatEvent({ kind: "init", model: this.options.model, claudeSessionId: id });
|
|
5239
|
+
},
|
|
5240
|
+
(event) => this.sendChatEvent(event)
|
|
5241
|
+
);
|
|
5242
|
+
this.codexSource = source;
|
|
5243
|
+
this.tailer = new JsonlTailer(
|
|
5244
|
+
extras.eventsSinkPath,
|
|
5245
|
+
() => void 0,
|
|
5246
|
+
(raw) => source.handleRecord(raw),
|
|
5247
|
+
() => null
|
|
5248
|
+
);
|
|
5249
|
+
this.tailer.start(0);
|
|
5250
|
+
return { ...extras, mcpEntries: entries };
|
|
5251
|
+
}
|
|
5027
5252
|
async startOpenCodeEventSources() {
|
|
5028
|
-
this.tempDir = await mkdtemp(
|
|
5253
|
+
this.tempDir = await mkdtemp(join7(sessionTempBase(), "conveyor-pty-"));
|
|
5029
5254
|
const { servers, entries } = await startToolServers(
|
|
5030
5255
|
this.options.mcpServers ?? {},
|
|
5031
5256
|
this.tempDir
|
|
@@ -5035,8 +5260,8 @@ var PtySession = class {
|
|
|
5035
5260
|
let instructionsPath;
|
|
5036
5261
|
const systemPrompt = this.options.appendSystemPrompt;
|
|
5037
5262
|
if (systemPrompt && systemPrompt.trim() !== "") {
|
|
5038
|
-
instructionsPath =
|
|
5039
|
-
await
|
|
5263
|
+
instructionsPath = join7(this.tempDir, "conveyor-instructions.md");
|
|
5264
|
+
await writeFile6(instructionsPath, systemPrompt, "utf8");
|
|
5040
5265
|
}
|
|
5041
5266
|
this.opencodeSource = new OpenCodeEventSource(
|
|
5042
5267
|
(event) => this.handleTranscriptEvent(event),
|
|
@@ -5074,8 +5299,8 @@ var PtySession = class {
|
|
|
5074
5299
|
* paths spawn() must wire into the child's argv/env.
|
|
5075
5300
|
*/
|
|
5076
5301
|
async startStructuredEventSources(sessionId) {
|
|
5077
|
-
this.tempDir = await mkdtemp(
|
|
5078
|
-
const socketPath =
|
|
5302
|
+
this.tempDir = await mkdtemp(join7(sessionTempBase(), "conveyor-pty-"));
|
|
5303
|
+
const socketPath = join7(this.tempDir, "hook.sock");
|
|
5079
5304
|
this.socket = new HookSocketServer(
|
|
5080
5305
|
socketPath,
|
|
5081
5306
|
(progress) => this.handleProgress(progress),
|
|
@@ -5246,8 +5471,7 @@ var PtySession = class {
|
|
|
5246
5471
|
if (pty) killPtyWithEscalation(pty, () => this.exited);
|
|
5247
5472
|
this.coalescer?.dispose();
|
|
5248
5473
|
this.coalescer = null;
|
|
5249
|
-
this.
|
|
5250
|
-
this.tailer = null;
|
|
5474
|
+
await this.stopEventSources();
|
|
5251
5475
|
if (this.socket) {
|
|
5252
5476
|
await this.socket.close();
|
|
5253
5477
|
this.socket = null;
|
|
@@ -5262,11 +5486,19 @@ var PtySession = class {
|
|
|
5262
5486
|
this.mcpConfigPath = null;
|
|
5263
5487
|
this.activeQueue?.close();
|
|
5264
5488
|
this.activeQueue = null;
|
|
5489
|
+
for (const path of this.adapterCleanupPaths) await rm2(path, { force: true });
|
|
5490
|
+
this.adapterCleanupPaths = [];
|
|
5265
5491
|
if (this.tempDir) {
|
|
5266
5492
|
await rm2(this.tempDir, { recursive: true, force: true });
|
|
5267
5493
|
this.tempDir = "";
|
|
5268
5494
|
}
|
|
5269
5495
|
}
|
|
5496
|
+
async stopEventSources() {
|
|
5497
|
+
this.tailer?.close();
|
|
5498
|
+
this.tailer = null;
|
|
5499
|
+
await this.codexSource?.close();
|
|
5500
|
+
this.codexSource = null;
|
|
5501
|
+
}
|
|
5270
5502
|
/**
|
|
5271
5503
|
* Serve the harness's in-process tools to the spawned CLI over loopback HTTP
|
|
5272
5504
|
* (handlers run in THIS process against the live task-token connection) and
|
|
@@ -5283,8 +5515,11 @@ var PtySession = class {
|
|
|
5283
5515
|
}
|
|
5284
5516
|
async spawn(settingsPath, socketPath, opencodeExtras) {
|
|
5285
5517
|
const resume = this.reportedOpenCodeId ?? this.resume;
|
|
5518
|
+
const initialPrompt = this.adapter.id === "codex" && this.turn.promptDelivery !== "prefill" && (typeof this.turnPrompt === "string" || this.codexInitialPrompt !== void 0) ? this.codexInitialPrompt ?? this.turnPrompt : void 0;
|
|
5519
|
+
this.codexPromptInArgv = Boolean(initialPrompt);
|
|
5286
5520
|
const spec = this.adapter.buildSpawn({
|
|
5287
5521
|
options: this.options,
|
|
5522
|
+
...initialPrompt ? { initialPrompt } : {},
|
|
5288
5523
|
...resume ? { resume } : {},
|
|
5289
5524
|
...settingsPath ? { settingsPath } : {},
|
|
5290
5525
|
...socketPath ? { hookSocketPath: socketPath } : {},
|
|
@@ -5295,6 +5530,7 @@ var PtySession = class {
|
|
|
5295
5530
|
...opencodeExtras ?? {}
|
|
5296
5531
|
});
|
|
5297
5532
|
const spawn2 = await resolvePtySpawn();
|
|
5533
|
+
this.adapterCleanupPaths = spec.cleanupPaths ?? [];
|
|
5298
5534
|
const pty = spawn2(spec.file, spec.args, {
|
|
5299
5535
|
name: "xterm-color",
|
|
5300
5536
|
cols: this.cols,
|
|
@@ -5402,6 +5638,11 @@ var PtySession = class {
|
|
|
5402
5638
|
this.probeWindow = null;
|
|
5403
5639
|
}
|
|
5404
5640
|
async deliverPrompt(text) {
|
|
5641
|
+
if (this.codexPromptInArgv) {
|
|
5642
|
+
this.codexPromptInArgv = false;
|
|
5643
|
+
this.wroteToProcess = true;
|
|
5644
|
+
return;
|
|
5645
|
+
}
|
|
5405
5646
|
if (text === "" && !this.adapter.capabilities.prefill) return;
|
|
5406
5647
|
if (!this.wroteToProcess) {
|
|
5407
5648
|
await this.awaitRawTuiInputLive();
|
|
@@ -5592,7 +5833,7 @@ var PtySession = class {
|
|
|
5592
5833
|
if (this.pendingPlanApproval && event.type === "result") {
|
|
5593
5834
|
this.disarmPlanDialogAutoAccept();
|
|
5594
5835
|
}
|
|
5595
|
-
if (this.pendingSubmitNudge && (event.type === "assistant" || event.type === "result")) {
|
|
5836
|
+
if (this.pendingSubmitNudge && (event.type === "assistant" || event.type === "result" || this.adapter.id === "codex" && event.type === "tool_progress")) {
|
|
5596
5837
|
this.disarmSubmitNudge();
|
|
5597
5838
|
}
|
|
5598
5839
|
this.synthesizeRateLimitFromBanner(event);
|
|
@@ -5668,7 +5909,7 @@ var PtySession = class {
|
|
|
5668
5909
|
// src/harness/pty/config-home-health.ts
|
|
5669
5910
|
import { lstat, mkdir as mkdir4, symlink, unlink as unlink2 } from "fs/promises";
|
|
5670
5911
|
import { homedir as homedir3 } from "os";
|
|
5671
|
-
import { join as
|
|
5912
|
+
import { join as join8 } from "path";
|
|
5672
5913
|
var MOUNT_DISCONNECT_CODES = /* @__PURE__ */ new Set(["ENOTCONN", "EIO", "ESTALE", "ENXIO"]);
|
|
5673
5914
|
var MOUNT_DISCONNECT_MESSAGES = [
|
|
5674
5915
|
"socket is not connected",
|
|
@@ -5684,10 +5925,10 @@ function isMountDisconnectError(err) {
|
|
|
5684
5925
|
return MOUNT_DISCONNECT_MESSAGES.some((needle) => lower.includes(needle));
|
|
5685
5926
|
}
|
|
5686
5927
|
function podLocalConfigHome() {
|
|
5687
|
-
return
|
|
5928
|
+
return join8(homedir3(), ".claude-local");
|
|
5688
5929
|
}
|
|
5689
5930
|
function sharedConfigHomePath() {
|
|
5690
|
-
return
|
|
5931
|
+
return join8(homedir3(), ".claude");
|
|
5691
5932
|
}
|
|
5692
5933
|
function isConfigHomeFallbackActive() {
|
|
5693
5934
|
return claudeConfigHome() === podLocalConfigHome();
|
|
@@ -5718,7 +5959,7 @@ async function repointSharedConfigHomeSymlink(fallback, log) {
|
|
|
5718
5959
|
}
|
|
5719
5960
|
async function isConfigHomeMountDead(cwd) {
|
|
5720
5961
|
try {
|
|
5721
|
-
await mkdir4(
|
|
5962
|
+
await mkdir4(join8(claudeConfigHome(), "projects", projectSlug(cwd)), { recursive: true });
|
|
5722
5963
|
return false;
|
|
5723
5964
|
} catch (err) {
|
|
5724
5965
|
return isMountDisconnectError(err);
|
|
@@ -5727,7 +5968,7 @@ async function isConfigHomeMountDead(cwd) {
|
|
|
5727
5968
|
async function ensureUsableClaudeConfigHome(cwd, log) {
|
|
5728
5969
|
const configHome = claudeConfigHome();
|
|
5729
5970
|
try {
|
|
5730
|
-
await mkdir4(
|
|
5971
|
+
await mkdir4(join8(configHome, "projects", projectSlug(cwd)), { recursive: true });
|
|
5731
5972
|
return { configHome, fellBack: false };
|
|
5732
5973
|
} catch (err) {
|
|
5733
5974
|
if (!isMountDisconnectError(err)) throw err;
|
|
@@ -5741,7 +5982,7 @@ async function ensureUsableClaudeConfigHome(cwd, log) {
|
|
|
5741
5982
|
}
|
|
5742
5983
|
);
|
|
5743
5984
|
process.env.CLAUDE_CONFIG_DIR = fallback;
|
|
5744
|
-
await mkdir4(
|
|
5985
|
+
await mkdir4(join8(fallback, "projects", projectSlug(cwd)), { recursive: true });
|
|
5745
5986
|
await repointSharedConfigHomeSymlink(fallback, log);
|
|
5746
5987
|
return { configHome: fallback, fellBack: true };
|
|
5747
5988
|
}
|
|
@@ -5837,7 +6078,8 @@ var PtyHarness = class _PtyHarness {
|
|
|
5837
6078
|
}
|
|
5838
6079
|
}
|
|
5839
6080
|
async *executeQuery(opts) {
|
|
5840
|
-
const
|
|
6081
|
+
const parkedId = this.adapter.id === "codex" && this.parked?.lineageId !== opts.options.sessionId ? void 0 : this.parked?.reportedSessionId;
|
|
6082
|
+
const want = opts.resume ?? opts.options.resume ?? parkedId ?? void 0;
|
|
5841
6083
|
const options = ensureSessionTarget(opts.options, want);
|
|
5842
6084
|
if (options !== opts.options) {
|
|
5843
6085
|
_PtyHarness.log.warn("spawn had no session target \u2014 minted one", {
|
|
@@ -6103,7 +6345,7 @@ import { spawn } from "child_process";
|
|
|
6103
6345
|
|
|
6104
6346
|
// src/harness/pty/adapters/types.ts
|
|
6105
6347
|
import { accessSync, constants, statSync } from "fs";
|
|
6106
|
-
import { join as
|
|
6348
|
+
import { join as join9 } from "path";
|
|
6107
6349
|
var TuiUnavailableError = class extends Error {
|
|
6108
6350
|
constructor(tui, message) {
|
|
6109
6351
|
super(message);
|
|
@@ -6127,7 +6369,7 @@ function findOnPath(binary, env = process.env) {
|
|
|
6127
6369
|
}
|
|
6128
6370
|
for (const dir of (env.PATH ?? "").split(":")) {
|
|
6129
6371
|
if (!dir) continue;
|
|
6130
|
-
const candidate =
|
|
6372
|
+
const candidate = join9(dir, binary);
|
|
6131
6373
|
if (isExecutable(candidate)) return candidate;
|
|
6132
6374
|
}
|
|
6133
6375
|
return null;
|
|
@@ -6135,18 +6377,18 @@ function findOnPath(binary, env = process.env) {
|
|
|
6135
6377
|
|
|
6136
6378
|
// src/harness/pty/adapters/opencode-auth.ts
|
|
6137
6379
|
import { promises as fs } from "fs";
|
|
6138
|
-
import { dirname as dirname2, join as
|
|
6380
|
+
import { dirname as dirname2, join as join10 } from "path";
|
|
6139
6381
|
import { homedir as homedir4 } from "os";
|
|
6140
6382
|
var logger = createServiceLogger("opencode-auth");
|
|
6141
6383
|
var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
|
|
6142
6384
|
var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
|
|
6143
6385
|
function opencodeAuthPath(env) {
|
|
6144
|
-
const dataHome = env.XDG_DATA_HOME ??
|
|
6145
|
-
return
|
|
6386
|
+
const dataHome = env.XDG_DATA_HOME ?? join10(env.HOME ?? homedir4(), ".local", "share");
|
|
6387
|
+
return join10(dataHome, "opencode", "auth.json");
|
|
6146
6388
|
}
|
|
6147
6389
|
function opencodeConfigPath(env) {
|
|
6148
|
-
const configHome = env.XDG_CONFIG_HOME ??
|
|
6149
|
-
return
|
|
6390
|
+
const configHome = env.XDG_CONFIG_HOME ?? join10(env.HOME ?? homedir4(), ".config");
|
|
6391
|
+
return join10(configHome, "opencode", "opencode.json");
|
|
6150
6392
|
}
|
|
6151
6393
|
function parseOauthSeed(b64) {
|
|
6152
6394
|
if (!b64) return null;
|
|
@@ -6323,8 +6565,8 @@ function buildOpenCodeConfigContent(input) {
|
|
|
6323
6565
|
}
|
|
6324
6566
|
|
|
6325
6567
|
// src/harness/opencode/index.ts
|
|
6326
|
-
import { mkdtemp as mkdtemp2, rm as rm3, writeFile as
|
|
6327
|
-
import { join as
|
|
6568
|
+
import { mkdtemp as mkdtemp2, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
6569
|
+
import { join as join11 } from "path";
|
|
6328
6570
|
var MAX_STDERR_TAIL = 4e3;
|
|
6329
6571
|
var OpenCodeHeadlessHarness = class {
|
|
6330
6572
|
/** NDJSON from `--format json` is a trusted structured source. */
|
|
@@ -6346,7 +6588,7 @@ var OpenCodeHeadlessHarness = class {
|
|
|
6346
6588
|
const prompt = await collectPrompt(opts.prompt);
|
|
6347
6589
|
const binary = resolveOpenCodeBinary(process.env);
|
|
6348
6590
|
await prepareOpenCodeCredentials(process.env);
|
|
6349
|
-
this.tempDir = await mkdtemp2(
|
|
6591
|
+
this.tempDir = await mkdtemp2(join11(sessionTempBase(), "opencode-headless-"));
|
|
6350
6592
|
const { servers, entries } = await startToolServers(
|
|
6351
6593
|
opts.options.mcpServers ?? {},
|
|
6352
6594
|
this.tempDir
|
|
@@ -6428,8 +6670,8 @@ var OpenCodeHeadlessHarness = class {
|
|
|
6428
6670
|
*/
|
|
6429
6671
|
async writeSystemPrompt(text) {
|
|
6430
6672
|
if (!text || text.trim() === "") return null;
|
|
6431
|
-
const path =
|
|
6432
|
-
await
|
|
6673
|
+
const path = join11(this.tempDir, "conveyor-instructions.md");
|
|
6674
|
+
await writeFile7(path, text, "utf8");
|
|
6433
6675
|
return path;
|
|
6434
6676
|
}
|
|
6435
6677
|
/**
|
|
@@ -6566,6 +6808,202 @@ ${tail}`);
|
|
|
6566
6808
|
}
|
|
6567
6809
|
};
|
|
6568
6810
|
|
|
6811
|
+
// src/harness/pty/adapters/codex.ts
|
|
6812
|
+
import { realpathSync, writeFileSync as writeFileSync2 } from "fs";
|
|
6813
|
+
import { basename, dirname as dirname3, join as join14 } from "path";
|
|
6814
|
+
|
|
6815
|
+
// src/harness/codex/credentials.ts
|
|
6816
|
+
import { createHash as createHash2 } from "crypto";
|
|
6817
|
+
import { mkdir as mkdir5, readFile as readFile2, writeFile as writeFile8 } from "fs/promises";
|
|
6818
|
+
import { homedir as homedir5, tmpdir } from "os";
|
|
6819
|
+
import { join as join12 } from "path";
|
|
6820
|
+
function codexHome(env = process.env) {
|
|
6821
|
+
const key = env.CONVEYOR_CODING_AGENT_KEY_ID ?? "local";
|
|
6822
|
+
const scope = createHash2("sha256").update(key).digest("hex").slice(0, 24);
|
|
6823
|
+
return env.CONVEYOR_CODEX_HOME ?? join12(env.HOME ?? homedir5(), ".conveyor-codex", scope);
|
|
6824
|
+
}
|
|
6825
|
+
function codexSqliteHome(env = process.env) {
|
|
6826
|
+
const scope = createHash2("sha256").update(codexHome(env)).digest("hex").slice(0, 24);
|
|
6827
|
+
return env.CONVEYOR_CODEX_SQLITE_HOME ?? join12(tmpdir(), "conveyor-codex-state", scope);
|
|
6828
|
+
}
|
|
6829
|
+
async function readAuth(path) {
|
|
6830
|
+
try {
|
|
6831
|
+
return JSON.parse(await readFile2(path, "utf8"));
|
|
6832
|
+
} catch {
|
|
6833
|
+
return {};
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
async function prepareCodexCredentials(env) {
|
|
6837
|
+
const home = codexHome(env);
|
|
6838
|
+
await mkdir5(home, { recursive: true, mode: 448 });
|
|
6839
|
+
await mkdir5(codexSqliteHome(env), { recursive: true, mode: 448 });
|
|
6840
|
+
const path = join12(home, "auth.json");
|
|
6841
|
+
const seed = env.CONVEYOR_CODEX_OAUTH;
|
|
6842
|
+
if (seed) {
|
|
6843
|
+
const tokens = JSON.parse(Buffer.from(seed, "base64").toString("utf8"));
|
|
6844
|
+
if (typeof tokens.access !== "string" || typeof tokens.refresh !== "string" || typeof tokens.idToken !== "string" || typeof tokens.expires !== "number") {
|
|
6845
|
+
throw new Error(
|
|
6846
|
+
"Codex ChatGPT credential is incomplete. Sign in with ChatGPT again in Conveyor."
|
|
6847
|
+
);
|
|
6848
|
+
}
|
|
6849
|
+
const existing = await readAuth(path);
|
|
6850
|
+
const stored = existing.tokens;
|
|
6851
|
+
const seededAt = await readAuth(join12(home, "conveyor-auth-seed.json"));
|
|
6852
|
+
if (stored?.refresh_token && stored.account_id === tokens.accountId && typeof seededAt.expires === "number" && seededAt.expires >= tokens.expires)
|
|
6853
|
+
return;
|
|
6854
|
+
await writeFile8(
|
|
6855
|
+
path,
|
|
6856
|
+
JSON.stringify({
|
|
6857
|
+
auth_mode: "chatgpt",
|
|
6858
|
+
OPENAI_API_KEY: null,
|
|
6859
|
+
tokens: {
|
|
6860
|
+
id_token: tokens.idToken,
|
|
6861
|
+
access_token: tokens.access,
|
|
6862
|
+
refresh_token: tokens.refresh,
|
|
6863
|
+
account_id: tokens.accountId
|
|
6864
|
+
},
|
|
6865
|
+
last_refresh: (/* @__PURE__ */ new Date()).toISOString()
|
|
6866
|
+
}),
|
|
6867
|
+
{ mode: 384 }
|
|
6868
|
+
);
|
|
6869
|
+
await writeFile8(
|
|
6870
|
+
join12(home, "conveyor-auth-seed.json"),
|
|
6871
|
+
JSON.stringify({ expires: tokens.expires }),
|
|
6872
|
+
{ mode: 384 }
|
|
6873
|
+
);
|
|
6874
|
+
} else if (env.CONVEYOR_AGENT_KEY || env.OPENAI_API_KEY) {
|
|
6875
|
+
await writeFile8(
|
|
6876
|
+
path,
|
|
6877
|
+
JSON.stringify({ OPENAI_API_KEY: env.CONVEYOR_AGENT_KEY ?? env.OPENAI_API_KEY }),
|
|
6878
|
+
{ mode: 384 }
|
|
6879
|
+
);
|
|
6880
|
+
}
|
|
6881
|
+
}
|
|
6882
|
+
|
|
6883
|
+
// src/harness/codex/lineage.ts
|
|
6884
|
+
import { createHash as createHash3 } from "crypto";
|
|
6885
|
+
import { readFileSync, mkdirSync, writeFileSync, renameSync } from "fs";
|
|
6886
|
+
import { join as join13 } from "path";
|
|
6887
|
+
function lineagePath(id, cwd, env = process.env) {
|
|
6888
|
+
const key = createHash3("sha256").update(`${cwd}\0${id}`).digest("hex");
|
|
6889
|
+
return join13(codexHome(env), `conveyor-lineage-${key}.json`);
|
|
6890
|
+
}
|
|
6891
|
+
function readCodexLineage(id, cwd) {
|
|
6892
|
+
try {
|
|
6893
|
+
const stored = JSON.parse(readFileSync(lineagePath(id, cwd), "utf8"));
|
|
6894
|
+
return typeof stored.sessionId === "string" ? stored.sessionId : void 0;
|
|
6895
|
+
} catch {
|
|
6896
|
+
return void 0;
|
|
6897
|
+
}
|
|
6898
|
+
}
|
|
6899
|
+
function saveCodexLineage(id, cwd, sessionId, env = process.env) {
|
|
6900
|
+
mkdirSync(codexHome(env), { recursive: true });
|
|
6901
|
+
const path = lineagePath(id, cwd, env);
|
|
6902
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
6903
|
+
writeFileSync(temp, JSON.stringify({ sessionId }), { mode: 384 });
|
|
6904
|
+
renameSync(temp, path);
|
|
6905
|
+
}
|
|
6906
|
+
|
|
6907
|
+
// src/harness/pty/adapters/codex.ts
|
|
6908
|
+
var CodexTuiAdapter = class {
|
|
6909
|
+
constructor(env = process.env) {
|
|
6910
|
+
this.env = env;
|
|
6911
|
+
}
|
|
6912
|
+
env;
|
|
6913
|
+
id = "codex";
|
|
6914
|
+
capabilities = {
|
|
6915
|
+
resume: true,
|
|
6916
|
+
structuredEvents: true,
|
|
6917
|
+
prefill: true,
|
|
6918
|
+
passiveTurns: true,
|
|
6919
|
+
rawPromptGate: true
|
|
6920
|
+
};
|
|
6921
|
+
resolveBinary(env = this.env) {
|
|
6922
|
+
const path = findOnPath(env.CONVEYOR_CODEX_BIN ?? "codex", env);
|
|
6923
|
+
if (!path)
|
|
6924
|
+
throw new TuiUnavailableError(
|
|
6925
|
+
"codex",
|
|
6926
|
+
"Codex CLI is missing. Rebuild the project's image or set CONVEYOR_CODEX_BIN."
|
|
6927
|
+
);
|
|
6928
|
+
return path;
|
|
6929
|
+
}
|
|
6930
|
+
childEnv() {
|
|
6931
|
+
const env = { ...inheritedEnv(), CODEX_HOME: codexHome(this.env) };
|
|
6932
|
+
for (const [key, value] of Object.entries(this.env)) if (value !== void 0) env[key] = value;
|
|
6933
|
+
env.CODEX_HOME = codexHome(this.env);
|
|
6934
|
+
for (const key of [
|
|
6935
|
+
"OPENAI_API_KEY",
|
|
6936
|
+
"CODEX_API_KEY",
|
|
6937
|
+
"CODEX_ACCESS_TOKEN",
|
|
6938
|
+
"CONVEYOR_AGENT_KEY",
|
|
6939
|
+
"CONVEYOR_CODEX_OAUTH",
|
|
6940
|
+
"CONVEYOR_OPENCODE_OAUTH",
|
|
6941
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
6942
|
+
"CONVEYOR_CLAUDE_OAUTH"
|
|
6943
|
+
])
|
|
6944
|
+
delete env[key];
|
|
6945
|
+
return env;
|
|
6946
|
+
}
|
|
6947
|
+
buildSpawn(input) {
|
|
6948
|
+
const env = this.childEnv();
|
|
6949
|
+
const cleanupPaths = [];
|
|
6950
|
+
let trustedCwd = input.options.cwd;
|
|
6951
|
+
try {
|
|
6952
|
+
trustedCwd = realpathSync(trustedCwd);
|
|
6953
|
+
} catch {
|
|
6954
|
+
}
|
|
6955
|
+
const config = {
|
|
6956
|
+
cli_auth_credentials_store: "file",
|
|
6957
|
+
sqlite_home: codexSqliteHome(this.env),
|
|
6958
|
+
approval_policy: "never",
|
|
6959
|
+
sandbox_mode: input.options.permissionMode === "plan" ? "read-only" : "danger-full-access",
|
|
6960
|
+
developer_instructions: input.options.appendSystemPrompt ?? "",
|
|
6961
|
+
mcp_servers: codexMcpConfig(input.mcpEntries ?? {}),
|
|
6962
|
+
projects: { [trustedCwd]: { trust_level: "trusted" } }
|
|
6963
|
+
};
|
|
6964
|
+
if (input.pluginPath && input.eventsSinkPath) {
|
|
6965
|
+
const profile = basename(dirname3(input.pluginPath));
|
|
6966
|
+
const profilePath = join14(env.CODEX_HOME, `${profile}.config.toml`);
|
|
6967
|
+
writeFileSync2(
|
|
6968
|
+
profilePath,
|
|
6969
|
+
`hooks = ${tomlValue(codexHookConfig(input.pluginPath, input.eventsSinkPath))}
|
|
6970
|
+
`,
|
|
6971
|
+
{ mode: 384 }
|
|
6972
|
+
);
|
|
6973
|
+
cleanupPaths.push(profilePath);
|
|
6974
|
+
config.notify = ["node", input.pluginPath, input.eventsSinkPath];
|
|
6975
|
+
config["features.hooks"] = true;
|
|
6976
|
+
}
|
|
6977
|
+
const args = ["--no-alt-screen", "--dangerously-bypass-hook-trust"];
|
|
6978
|
+
if (input.pluginPath) args.push("--profile", basename(dirname3(input.pluginPath)));
|
|
6979
|
+
for (const [key, value] of Object.entries(config))
|
|
6980
|
+
args.push("-c", `${key}=${tomlValue(value)}`);
|
|
6981
|
+
const model = this.env.CONVEYOR_AGENT_MODEL ?? input.options.model;
|
|
6982
|
+
if (model && !/^(anthropic\/|claude-)/.test(model))
|
|
6983
|
+
args.push("--model", model.replace(/^openai\//, ""));
|
|
6984
|
+
if (input.resume) args.push("resume", input.resume);
|
|
6985
|
+
if (input.initialPrompt) args.push("--", input.initialPrompt);
|
|
6986
|
+
return { file: this.resolveBinary(), args, env, cleanupPaths };
|
|
6987
|
+
}
|
|
6988
|
+
recordSessionId(id, options) {
|
|
6989
|
+
if (options.sessionId) saveCodexLineage(options.sessionId, options.cwd, id, this.env);
|
|
6990
|
+
}
|
|
6991
|
+
prepareEnvironment() {
|
|
6992
|
+
return prepareCodexCredentials(this.env);
|
|
6993
|
+
}
|
|
6994
|
+
spawnFingerprint(input) {
|
|
6995
|
+
return JSON.stringify([this.id, input]);
|
|
6996
|
+
}
|
|
6997
|
+
encodePromptBytes(text) {
|
|
6998
|
+
return buildPromptBytes(text);
|
|
6999
|
+
}
|
|
7000
|
+
buildExitErrors(code, output) {
|
|
7001
|
+
return [`Codex exited (code ${code}) without a result`, cleanTerminalOutput(output)].filter(
|
|
7002
|
+
Boolean
|
|
7003
|
+
);
|
|
7004
|
+
}
|
|
7005
|
+
};
|
|
7006
|
+
|
|
6569
7007
|
// src/harness/pty/adapters/index.ts
|
|
6570
7008
|
function resolveTuiKindFromEnv(env) {
|
|
6571
7009
|
const raw = env.CONVEYOR_TUI ?? "claude-code";
|
|
@@ -6574,6 +7012,8 @@ function resolveTuiKindFromEnv(env) {
|
|
|
6574
7012
|
}
|
|
6575
7013
|
function resolveTuiAdapter(kind = "claude-code") {
|
|
6576
7014
|
switch (kind) {
|
|
7015
|
+
case "codex":
|
|
7016
|
+
return new CodexTuiAdapter();
|
|
6577
7017
|
case "claude-code":
|
|
6578
7018
|
return new ClaudeTuiAdapter();
|
|
6579
7019
|
case "opencode":
|
|
@@ -6892,8 +7332,8 @@ function wrapBridgeWithDirectStream(inner, reporter, options = {}) {
|
|
|
6892
7332
|
}
|
|
6893
7333
|
|
|
6894
7334
|
// src/execution/query-executor.ts
|
|
6895
|
-
import { createHash as
|
|
6896
|
-
import { existsSync, readFileSync as
|
|
7335
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
7336
|
+
import { existsSync, readFileSync as readFileSync3, renameSync as renameSync2, truncateSync } from "fs";
|
|
6897
7337
|
|
|
6898
7338
|
// src/execution/chat-instructions.ts
|
|
6899
7339
|
function buildChatInstructions(context, scenario, newMessages) {
|
|
@@ -7973,11 +8413,11 @@ function buildPmRelaunchParts(context, lastAgentIdx, isAuto, agentMode) {
|
|
|
7973
8413
|
}
|
|
7974
8414
|
|
|
7975
8415
|
// src/execution/system-prompt.ts
|
|
7976
|
-
import { readFileSync } from "fs";
|
|
7977
|
-
import { join as
|
|
8416
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
8417
|
+
import { join as join15 } from "path";
|
|
7978
8418
|
function repoHasScript(workspaceDir, script) {
|
|
7979
8419
|
try {
|
|
7980
|
-
const pkg = JSON.parse(
|
|
8420
|
+
const pkg = JSON.parse(readFileSync2(join15(workspaceDir, "package.json"), "utf8"));
|
|
7981
8421
|
return typeof pkg.scripts?.[script] === "string";
|
|
7982
8422
|
} catch {
|
|
7983
8423
|
return false;
|
|
@@ -10751,7 +11191,7 @@ function buildMutationTools(connection, config) {
|
|
|
10751
11191
|
}
|
|
10752
11192
|
|
|
10753
11193
|
// src/tools/attachment-tools.ts
|
|
10754
|
-
import { basename, extname, isAbsolute, join as
|
|
11194
|
+
import { basename as basename2, extname, isAbsolute, join as join16 } from "path";
|
|
10755
11195
|
var MIME_BY_EXT = {
|
|
10756
11196
|
".png": "image/png",
|
|
10757
11197
|
".jpg": "image/jpeg",
|
|
@@ -10804,7 +11244,7 @@ ${snippet}`;
|
|
|
10804
11244
|
function buildUploadAttachmentTool(connection, config) {
|
|
10805
11245
|
return defineContractTool(uploadAttachmentContract, async ({ path, title, tags }) => {
|
|
10806
11246
|
try {
|
|
10807
|
-
const filePath = isAbsolute(path) ? path :
|
|
11247
|
+
const filePath = isAbsolute(path) ? path : join16(config.workspaceDir, path);
|
|
10808
11248
|
const mimeType = inferMimeType(filePath);
|
|
10809
11249
|
const info = await statWorkspacePath(filePath);
|
|
10810
11250
|
if (!info.isFile) {
|
|
@@ -10815,7 +11255,7 @@ function buildUploadAttachmentTool(connection, config) {
|
|
|
10815
11255
|
`File is ${info.size} bytes \u2014 exceeds the ${MAX_FILE_SIZE_BYTES} byte (25MB) upload limit.`
|
|
10816
11256
|
);
|
|
10817
11257
|
}
|
|
10818
|
-
const fileName =
|
|
11258
|
+
const fileName = basename2(filePath);
|
|
10819
11259
|
const { fileId, uploadUrl } = await connection.call("requestFileUpload", {
|
|
10820
11260
|
sessionId: connection.sessionId,
|
|
10821
11261
|
fileName,
|
|
@@ -11417,8 +11857,8 @@ function buildDiscoveryTools(connection) {
|
|
|
11417
11857
|
import { z as z19 } from "zod";
|
|
11418
11858
|
|
|
11419
11859
|
// src/execution/context-path-verifier.ts
|
|
11420
|
-
import { readFile as
|
|
11421
|
-
import { isAbsolute as isAbsolute2, join as
|
|
11860
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
11861
|
+
import { isAbsolute as isAbsolute2, join as join17, normalize } from "path";
|
|
11422
11862
|
var PROBLEM_TEXT = {
|
|
11423
11863
|
not_found: "does not exist in the repo",
|
|
11424
11864
|
expected_folder: "is a file, not a folder \u2014 use type 'file', 'rule', or 'doc'",
|
|
@@ -11433,7 +11873,7 @@ async function verifyLocator(link, absolutePath, fileSize) {
|
|
|
11433
11873
|
if (fileSize !== void 0 && fileSize > MAX_LOCATOR_FILE_BYTES) return null;
|
|
11434
11874
|
let content;
|
|
11435
11875
|
try {
|
|
11436
|
-
content = await
|
|
11876
|
+
content = await readFile3(absolutePath, "utf8");
|
|
11437
11877
|
} catch {
|
|
11438
11878
|
return null;
|
|
11439
11879
|
}
|
|
@@ -11463,7 +11903,7 @@ async function verifyContextPaths(links, workspaceDir) {
|
|
|
11463
11903
|
problems.push({ type: link.type, path: link.path, reason: shape });
|
|
11464
11904
|
continue;
|
|
11465
11905
|
}
|
|
11466
|
-
const absolutePath =
|
|
11906
|
+
const absolutePath = join17(workspaceDir, toRelativePath(link.path));
|
|
11467
11907
|
const stat = await statWorkspacePath(absolutePath);
|
|
11468
11908
|
const wantsDirectory = expectsDirectory(link.type);
|
|
11469
11909
|
if (!stat.exists) {
|
|
@@ -13074,10 +13514,12 @@ function applyCycledKeyEnv(envVars, env = process.env) {
|
|
|
13074
13514
|
env[key] = value;
|
|
13075
13515
|
}
|
|
13076
13516
|
if (!envVars.CONVEYOR_CLAUDE_OAUTH) delete env.CONVEYOR_CLAUDE_OAUTH;
|
|
13517
|
+
if (!envVars.CONVEYOR_CODEX_OAUTH) delete env.CONVEYOR_CODEX_OAUTH;
|
|
13518
|
+
if (!envVars.CONVEYOR_OPENCODE_OAUTH) delete env.CONVEYOR_OPENCODE_OAUTH;
|
|
13077
13519
|
if (envVars.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
13078
13520
|
delete env.ANTHROPIC_API_KEY;
|
|
13079
13521
|
if (!envVars.CONVEYOR_AGENT_KEY) delete env.CONVEYOR_AGENT_KEY;
|
|
13080
|
-
} else if (envVars.CONVEYOR_AGENT_KEY || envVars.CONVEYOR_OPENCODE_OAUTH) {
|
|
13522
|
+
} else if (envVars.CONVEYOR_AGENT_KEY || envVars.CONVEYOR_OPENCODE_OAUTH || envVars.CONVEYOR_CODEX_OAUTH) {
|
|
13081
13523
|
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
13082
13524
|
}
|
|
13083
13525
|
}
|
|
@@ -13550,7 +13992,7 @@ function buildHooks(host) {
|
|
|
13550
13992
|
};
|
|
13551
13993
|
}
|
|
13552
13994
|
function taskIdToSessionUuid(lineageKey) {
|
|
13553
|
-
const hash =
|
|
13995
|
+
const hash = createHash4("sha256").update(lineageKey).digest("hex");
|
|
13554
13996
|
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-8${hash.slice(13, 16)}-a${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
|
|
13555
13997
|
}
|
|
13556
13998
|
function sessionLineageKey(taskId, agentMode, runnerMode) {
|
|
@@ -13567,10 +14009,17 @@ function sessionFileExists(sessionUuid, cwd) {
|
|
|
13567
14009
|
}
|
|
13568
14010
|
function hasExistingSessionFile(taskId, cwd, lineage) {
|
|
13569
14011
|
const key = sessionLineageKey(taskId, lineage.agentMode, lineage.runnerMode);
|
|
14012
|
+
if (process.env.CONVEYOR_TUI === "codex" && lineage.runnerMode !== "code-review") {
|
|
14013
|
+
return !!readCodexLineage(taskIdToSessionUuid(key), cwd);
|
|
14014
|
+
}
|
|
13570
14015
|
return sessionFileExists(taskIdToSessionUuid(key), cwd);
|
|
13571
14016
|
}
|
|
13572
|
-
function resolveSessionStart(lineageKey, cwd) {
|
|
14017
|
+
function resolveSessionStart(lineageKey, cwd, tui = process.env.CONVEYOR_TUI) {
|
|
13573
14018
|
const sessionUuid = taskIdToSessionUuid(lineageKey);
|
|
14019
|
+
if (tui === "codex") {
|
|
14020
|
+
const resume = readCodexLineage(sessionUuid, cwd);
|
|
14021
|
+
return { sessionId: sessionUuid, ...resume ? { resume } : {} };
|
|
14022
|
+
}
|
|
13574
14023
|
if (sessionFileExists(sessionUuid, cwd)) {
|
|
13575
14024
|
repairTornSessionFile(sessionTranscriptPath(cwd, sessionUuid));
|
|
13576
14025
|
return { resume: sessionUuid };
|
|
@@ -13580,7 +14029,7 @@ function resolveSessionStart(lineageKey, cwd) {
|
|
|
13580
14029
|
function repairTornSessionFile(path) {
|
|
13581
14030
|
try {
|
|
13582
14031
|
if (!existsSync(path)) return false;
|
|
13583
|
-
const content =
|
|
14032
|
+
const content = readFileSync3(path, "utf8");
|
|
13584
14033
|
if (content.length === 0) return false;
|
|
13585
14034
|
let keepEnd = content.length;
|
|
13586
14035
|
if (!content.endsWith("\n")) {
|
|
@@ -13879,7 +14328,8 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
|
|
|
13879
14328
|
const isDiscoveryLike = mode === "discovery" || mode === "help";
|
|
13880
14329
|
const sessionStart = resolveSessionStart(
|
|
13881
14330
|
sessionLineageKey(context.taskId, mode, host.config.mode),
|
|
13882
|
-
host.config.workspaceDir
|
|
14331
|
+
host.config.workspaceDir,
|
|
14332
|
+
host.config.mode === "code-review" ? "claude-code" : process.env.CONVEYOR_TUI
|
|
13883
14333
|
);
|
|
13884
14334
|
const hasExistingSession = !!sessionStart.resume;
|
|
13885
14335
|
const promptDelivery = promptDeliveryOverride ?? resolvePromptDelivery({
|
|
@@ -14029,7 +14479,7 @@ function rotateSessionFile(cwd, sessionUuid) {
|
|
|
14029
14479
|
const path = sessionTranscriptPath(cwd, sessionUuid);
|
|
14030
14480
|
try {
|
|
14031
14481
|
if (!existsSync(path)) return true;
|
|
14032
|
-
|
|
14482
|
+
renameSync2(path, `${path.replace(/\.jsonl$/, "")}.aborted-${Date.now()}.jsonl`);
|
|
14033
14483
|
return true;
|
|
14034
14484
|
} catch (error) {
|
|
14035
14485
|
logger5.warn("Could not rotate an aborted transcript aside", {
|