@rallycry/conveyor-agent 11.0.11 → 11.0.13

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 writeFile5 } from "fs/promises";
2643
- import { join as join6, dirname } from "path";
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/harness/pty/event-queue.ts
2967
- var AsyncEventQueue = class {
2968
- items = [];
2969
- closed = false;
2970
- wake = null;
2971
- push(item) {
2972
- if (this.closed) return;
2973
- this.items.push(item);
2974
- this.signal();
2975
- }
2976
- close() {
2977
- this.closed = true;
2978
- this.signal();
2979
- }
2980
- get isClosed() {
2981
- return this.closed;
2982
- }
2983
- signal() {
2984
- const wake = this.wake;
2985
- if (wake) {
2986
- this.wake = null;
2987
- wake();
2988
- }
2989
- }
2990
- async *drain() {
2991
- while (!this.closed || this.items.length > 0) {
2992
- if (this.items.length > 0) {
2993
- const item = this.items.shift();
2994
- if (item !== void 0) yield item;
2995
- continue;
2996
- }
2997
- await new Promise((resolve) => {
2998
- this.wake = resolve;
2999
- });
3000
- }
3001
- }
3002
- };
3003
-
3004
- // src/harness/pty/hook-socket.ts
3005
- import { createServer } from "net";
3006
- import { unlink } from "fs/promises";
3007
- function parseEnvelope(line) {
3008
- let parsed;
3009
- try {
3010
- parsed = JSON.parse(line);
3011
- } catch {
3012
- return null;
3013
- }
3014
- if (typeof parsed !== "object" || parsed === null) return null;
3015
- const record = parsed;
3016
- if (record.kind === "pre_tool_use") {
3017
- if (typeof record.id !== "string" || typeof record.tool_name !== "string") return null;
3018
- const toolInput = typeof record.tool_input === "object" && record.tool_input !== null ? record.tool_input : {};
3019
- return {
3020
- kind: "pre_tool_use",
3021
- request: { id: record.id, tool_name: record.tool_name, tool_input: toolInput }
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
- enqueueRead() {
3289
- this.chain = this.chain.then(() => this.readOnce());
3290
- return this.chain;
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
- async readOnce() {
3293
- let handle = null;
3294
- try {
3295
- handle = await open(this.path, "r");
3296
- const stats = await handle.stat();
3297
- if (stats.size <= this.offset) return;
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
- consume(chunk) {
3309
- this.buffer += chunk;
3310
- let index = this.buffer.indexOf("\n");
3311
- while (index >= 0) {
3312
- const line = this.buffer.slice(0, index);
3313
- this.buffer = this.buffer.slice(index + 1);
3314
- this.emitLine(line);
3315
- index = this.buffer.indexOf("\n");
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
- parsed = JSON.parse(trimmed);
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 writeFile2, chmod } from "fs/promises";
3747
+ import { mkdir, writeFile as writeFile3, chmod } from "fs/promises";
3522
3748
  import { homedir } from "os";
3523
- import { join as join2 } from "path";
3749
+ import { join as join3 } from "path";
3524
3750
  function claudeConfigHome() {
3525
- return process.env.CLAUDE_CONFIG_DIR ?? join2(homedir(), ".claude");
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 join2(claudeConfigHome(), "projects", projectSlug(cwd), `${sessionId}.jsonl`);
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 = join2(dir, "hook-helper.cjs");
3735
- const settingsPath = join2(dir, "settings.json");
3960
+ const helperPath = join3(dir, "hook-helper.cjs");
3961
+ const settingsPath = join3(dir, "settings.json");
3736
3962
  await mkdir(dir, { recursive: true });
3737
- await writeFile2(helperPath, HOOK_HELPER_SOURCE, "utf8");
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 writeFile2(settingsPath, JSON.stringify(settings, null, 2), "utf8");
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 writeFile3 } from "fs/promises";
3893
- import { join as join3 } from "path";
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 = join3(tempDir, "mcp-config.json");
4085
- await writeFile3(mcpConfigPath, JSON.stringify({ mcpServers: config }, null, 2), "utf8");
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 writeFile4 } from "fs/promises";
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 join5 } from "path";
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 join4 } from "path";
4263
+ import { join as join5 } from "path";
4097
4264
  function conveyorCredentialsMarkerPath() {
4098
- return join4(claudeConfigHome(), ".conveyor-credentials.json");
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 join5(claudeConfigHome(), ".credentials.json");
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) => writeFile4(path, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode }),
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 ? join5(configDir, ".claude.json") : join5(homedir2(), ".claude.json");
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 join5(claudeConfigHome(), "conveyor-oauth-account.json");
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.capabilities.structuredEvents && this.adapter.id === "opencode") {
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(join6(sessionTempBase(), "conveyor-pty-"));
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(join6(sessionTempBase(), "conveyor-pty-"));
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 = join6(this.tempDir, "conveyor-instructions.md");
5039
- await writeFile5(instructionsPath, systemPrompt, "utf8");
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(join6(sessionTempBase(), "conveyor-pty-"));
5078
- const socketPath = join6(this.tempDir, "hook.sock");
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.tailer?.close();
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 join7 } from "path";
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 join7(homedir3(), ".claude-local");
5928
+ return join8(homedir3(), ".claude-local");
5688
5929
  }
5689
5930
  function sharedConfigHomePath() {
5690
- return join7(homedir3(), ".claude");
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(join7(claudeConfigHome(), "projects", projectSlug(cwd)), { recursive: true });
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(join7(configHome, "projects", projectSlug(cwd)), { recursive: true });
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(join7(fallback, "projects", projectSlug(cwd)), { recursive: true });
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 want = opts.resume ?? opts.options.resume ?? this.parked?.reportedSessionId ?? void 0;
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 join8 } from "path";
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 = join8(dir, binary);
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 join9 } from "path";
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 ?? join9(env.HOME ?? homedir4(), ".local", "share");
6145
- return join9(dataHome, "opencode", "auth.json");
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 ?? join9(env.HOME ?? homedir4(), ".config");
6149
- return join9(configHome, "opencode", "opencode.json");
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 writeFile6 } from "fs/promises";
6327
- import { join as join10 } from "path";
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(join10(sessionTempBase(), "opencode-headless-"));
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 = join10(this.tempDir, "conveyor-instructions.md");
6432
- await writeFile6(path, text, "utf8");
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,205 @@ ${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: [
6961
+ input.options.appendSystemPrompt,
6962
+ "Conveyor workflow skills are stored in .claude/skills in the workspace. When instructed to run a /skill-name skill, read .claude/skills/skill-name/SKILL.md and follow its instructions. This is a file-based workflow, not a native slash command."
6963
+ ].filter(Boolean).join("\n\n"),
6964
+ mcp_servers: codexMcpConfig(input.mcpEntries ?? {}),
6965
+ projects: { [trustedCwd]: { trust_level: "trusted" } }
6966
+ };
6967
+ if (input.pluginPath && input.eventsSinkPath) {
6968
+ const profile = basename(dirname3(input.pluginPath));
6969
+ const profilePath = join14(env.CODEX_HOME, `${profile}.config.toml`);
6970
+ writeFileSync2(
6971
+ profilePath,
6972
+ `hooks = ${tomlValue(codexHookConfig(input.pluginPath, input.eventsSinkPath))}
6973
+ `,
6974
+ { mode: 384 }
6975
+ );
6976
+ cleanupPaths.push(profilePath);
6977
+ config.notify = ["node", input.pluginPath, input.eventsSinkPath];
6978
+ config["features.hooks"] = true;
6979
+ }
6980
+ const args = ["--no-alt-screen", "--dangerously-bypass-hook-trust"];
6981
+ if (input.pluginPath) args.push("--profile", basename(dirname3(input.pluginPath)));
6982
+ for (const [key, value] of Object.entries(config))
6983
+ args.push("-c", `${key}=${tomlValue(value)}`);
6984
+ const model = this.env.CONVEYOR_AGENT_MODEL ?? input.options.model;
6985
+ if (model && !/^(anthropic\/|claude-)/.test(model))
6986
+ args.push("--model", model.replace(/^openai\//, ""));
6987
+ if (input.resume) args.push("resume", input.resume);
6988
+ if (input.initialPrompt) args.push("--", input.initialPrompt);
6989
+ return { file: this.resolveBinary(), args, env, cleanupPaths };
6990
+ }
6991
+ recordSessionId(id, options) {
6992
+ if (options.sessionId) saveCodexLineage(options.sessionId, options.cwd, id, this.env);
6993
+ }
6994
+ prepareEnvironment() {
6995
+ return prepareCodexCredentials(this.env);
6996
+ }
6997
+ spawnFingerprint(input) {
6998
+ return JSON.stringify([this.id, input]);
6999
+ }
7000
+ encodePromptBytes(text) {
7001
+ return buildPromptBytes(text);
7002
+ }
7003
+ buildExitErrors(code, output) {
7004
+ return [`Codex exited (code ${code}) without a result`, cleanTerminalOutput(output)].filter(
7005
+ Boolean
7006
+ );
7007
+ }
7008
+ };
7009
+
6569
7010
  // src/harness/pty/adapters/index.ts
6570
7011
  function resolveTuiKindFromEnv(env) {
6571
7012
  const raw = env.CONVEYOR_TUI ?? "claude-code";
@@ -6574,6 +7015,8 @@ function resolveTuiKindFromEnv(env) {
6574
7015
  }
6575
7016
  function resolveTuiAdapter(kind = "claude-code") {
6576
7017
  switch (kind) {
7018
+ case "codex":
7019
+ return new CodexTuiAdapter();
6577
7020
  case "claude-code":
6578
7021
  return new ClaudeTuiAdapter();
6579
7022
  case "opencode":
@@ -6892,8 +7335,8 @@ function wrapBridgeWithDirectStream(inner, reporter, options = {}) {
6892
7335
  }
6893
7336
 
6894
7337
  // src/execution/query-executor.ts
6895
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
6896
- import { existsSync, readFileSync as readFileSync2, renameSync, truncateSync } from "fs";
7338
+ import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
7339
+ import { existsSync, readFileSync as readFileSync3, renameSync as renameSync2, truncateSync } from "fs";
6897
7340
 
6898
7341
  // src/execution/chat-instructions.ts
6899
7342
  function buildChatInstructions(context, scenario, newMessages) {
@@ -7973,11 +8416,11 @@ function buildPmRelaunchParts(context, lastAgentIdx, isAuto, agentMode) {
7973
8416
  }
7974
8417
 
7975
8418
  // src/execution/system-prompt.ts
7976
- import { readFileSync } from "fs";
7977
- import { join as join11 } from "path";
8419
+ import { readFileSync as readFileSync2 } from "fs";
8420
+ import { join as join15 } from "path";
7978
8421
  function repoHasScript(workspaceDir, script) {
7979
8422
  try {
7980
- const pkg = JSON.parse(readFileSync(join11(workspaceDir, "package.json"), "utf8"));
8423
+ const pkg = JSON.parse(readFileSync2(join15(workspaceDir, "package.json"), "utf8"));
7981
8424
  return typeof pkg.scripts?.[script] === "string";
7982
8425
  } catch {
7983
8426
  return false;
@@ -10751,7 +11194,7 @@ function buildMutationTools(connection, config) {
10751
11194
  }
10752
11195
 
10753
11196
  // src/tools/attachment-tools.ts
10754
- import { basename, extname, isAbsolute, join as join12 } from "path";
11197
+ import { basename as basename2, extname, isAbsolute, join as join16 } from "path";
10755
11198
  var MIME_BY_EXT = {
10756
11199
  ".png": "image/png",
10757
11200
  ".jpg": "image/jpeg",
@@ -10804,7 +11247,7 @@ ${snippet}`;
10804
11247
  function buildUploadAttachmentTool(connection, config) {
10805
11248
  return defineContractTool(uploadAttachmentContract, async ({ path, title, tags }) => {
10806
11249
  try {
10807
- const filePath = isAbsolute(path) ? path : join12(config.workspaceDir, path);
11250
+ const filePath = isAbsolute(path) ? path : join16(config.workspaceDir, path);
10808
11251
  const mimeType = inferMimeType(filePath);
10809
11252
  const info = await statWorkspacePath(filePath);
10810
11253
  if (!info.isFile) {
@@ -10815,7 +11258,7 @@ function buildUploadAttachmentTool(connection, config) {
10815
11258
  `File is ${info.size} bytes \u2014 exceeds the ${MAX_FILE_SIZE_BYTES} byte (25MB) upload limit.`
10816
11259
  );
10817
11260
  }
10818
- const fileName = basename(filePath);
11261
+ const fileName = basename2(filePath);
10819
11262
  const { fileId, uploadUrl } = await connection.call("requestFileUpload", {
10820
11263
  sessionId: connection.sessionId,
10821
11264
  fileName,
@@ -11417,8 +11860,8 @@ function buildDiscoveryTools(connection) {
11417
11860
  import { z as z19 } from "zod";
11418
11861
 
11419
11862
  // src/execution/context-path-verifier.ts
11420
- import { readFile as readFile2 } from "fs/promises";
11421
- import { isAbsolute as isAbsolute2, join as join13, normalize } from "path";
11863
+ import { readFile as readFile3 } from "fs/promises";
11864
+ import { isAbsolute as isAbsolute2, join as join17, normalize } from "path";
11422
11865
  var PROBLEM_TEXT = {
11423
11866
  not_found: "does not exist in the repo",
11424
11867
  expected_folder: "is a file, not a folder \u2014 use type 'file', 'rule', or 'doc'",
@@ -11433,7 +11876,7 @@ async function verifyLocator(link, absolutePath, fileSize) {
11433
11876
  if (fileSize !== void 0 && fileSize > MAX_LOCATOR_FILE_BYTES) return null;
11434
11877
  let content;
11435
11878
  try {
11436
- content = await readFile2(absolutePath, "utf8");
11879
+ content = await readFile3(absolutePath, "utf8");
11437
11880
  } catch {
11438
11881
  return null;
11439
11882
  }
@@ -11463,7 +11906,7 @@ async function verifyContextPaths(links, workspaceDir) {
11463
11906
  problems.push({ type: link.type, path: link.path, reason: shape });
11464
11907
  continue;
11465
11908
  }
11466
- const absolutePath = join13(workspaceDir, toRelativePath(link.path));
11909
+ const absolutePath = join17(workspaceDir, toRelativePath(link.path));
11467
11910
  const stat = await statWorkspacePath(absolutePath);
11468
11911
  const wantsDirectory = expectsDirectory(link.type);
11469
11912
  if (!stat.exists) {
@@ -13074,10 +13517,12 @@ function applyCycledKeyEnv(envVars, env = process.env) {
13074
13517
  env[key] = value;
13075
13518
  }
13076
13519
  if (!envVars.CONVEYOR_CLAUDE_OAUTH) delete env.CONVEYOR_CLAUDE_OAUTH;
13520
+ if (!envVars.CONVEYOR_CODEX_OAUTH) delete env.CONVEYOR_CODEX_OAUTH;
13521
+ if (!envVars.CONVEYOR_OPENCODE_OAUTH) delete env.CONVEYOR_OPENCODE_OAUTH;
13077
13522
  if (envVars.CLAUDE_CODE_OAUTH_TOKEN) {
13078
13523
  delete env.ANTHROPIC_API_KEY;
13079
13524
  if (!envVars.CONVEYOR_AGENT_KEY) delete env.CONVEYOR_AGENT_KEY;
13080
- } else if (envVars.CONVEYOR_AGENT_KEY || envVars.CONVEYOR_OPENCODE_OAUTH) {
13525
+ } else if (envVars.CONVEYOR_AGENT_KEY || envVars.CONVEYOR_OPENCODE_OAUTH || envVars.CONVEYOR_CODEX_OAUTH) {
13081
13526
  delete env.CLAUDE_CODE_OAUTH_TOKEN;
13082
13527
  }
13083
13528
  }
@@ -13550,7 +13995,7 @@ function buildHooks(host) {
13550
13995
  };
13551
13996
  }
13552
13997
  function taskIdToSessionUuid(lineageKey) {
13553
- const hash = createHash2("sha256").update(lineageKey).digest("hex");
13998
+ const hash = createHash4("sha256").update(lineageKey).digest("hex");
13554
13999
  return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-8${hash.slice(13, 16)}-a${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
13555
14000
  }
13556
14001
  function sessionLineageKey(taskId, agentMode, runnerMode) {
@@ -13567,10 +14012,17 @@ function sessionFileExists(sessionUuid, cwd) {
13567
14012
  }
13568
14013
  function hasExistingSessionFile(taskId, cwd, lineage) {
13569
14014
  const key = sessionLineageKey(taskId, lineage.agentMode, lineage.runnerMode);
14015
+ if (process.env.CONVEYOR_TUI === "codex") {
14016
+ return !!readCodexLineage(taskIdToSessionUuid(key), cwd);
14017
+ }
13570
14018
  return sessionFileExists(taskIdToSessionUuid(key), cwd);
13571
14019
  }
13572
- function resolveSessionStart(lineageKey, cwd) {
14020
+ function resolveSessionStart(lineageKey, cwd, tui = process.env.CONVEYOR_TUI) {
13573
14021
  const sessionUuid = taskIdToSessionUuid(lineageKey);
14022
+ if (tui === "codex") {
14023
+ const resume = readCodexLineage(sessionUuid, cwd);
14024
+ return { sessionId: sessionUuid, ...resume ? { resume } : {} };
14025
+ }
13574
14026
  if (sessionFileExists(sessionUuid, cwd)) {
13575
14027
  repairTornSessionFile(sessionTranscriptPath(cwd, sessionUuid));
13576
14028
  return { resume: sessionUuid };
@@ -13580,7 +14032,7 @@ function resolveSessionStart(lineageKey, cwd) {
13580
14032
  function repairTornSessionFile(path) {
13581
14033
  try {
13582
14034
  if (!existsSync(path)) return false;
13583
- const content = readFileSync2(path, "utf8");
14035
+ const content = readFileSync3(path, "utf8");
13584
14036
  if (content.length === 0) return false;
13585
14037
  let keepEnd = content.length;
13586
14038
  if (!content.endsWith("\n")) {
@@ -13879,7 +14331,8 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
13879
14331
  const isDiscoveryLike = mode === "discovery" || mode === "help";
13880
14332
  const sessionStart = resolveSessionStart(
13881
14333
  sessionLineageKey(context.taskId, mode, host.config.mode),
13882
- host.config.workspaceDir
14334
+ host.config.workspaceDir,
14335
+ host.config.mode === "code-review" && process.env.CONVEYOR_TUI !== "codex" ? "claude-code" : process.env.CONVEYOR_TUI
13883
14336
  );
13884
14337
  const hasExistingSession = !!sessionStart.resume;
13885
14338
  const promptDelivery = promptDeliveryOverride ?? resolvePromptDelivery({
@@ -14029,7 +14482,7 @@ function rotateSessionFile(cwd, sessionUuid) {
14029
14482
  const path = sessionTranscriptPath(cwd, sessionUuid);
14030
14483
  try {
14031
14484
  if (!existsSync(path)) return true;
14032
- renameSync(path, `${path.replace(/\.jsonl$/, "")}.aborted-${Date.now()}.jsonl`);
14485
+ renameSync2(path, `${path.replace(/\.jsonl$/, "")}.aborted-${Date.now()}.jsonl`);
14033
14486
  return true;
14034
14487
  } catch (error) {
14035
14488
  logger5.warn("Could not rotate an aborted transcript aside", {
@@ -14301,7 +14754,7 @@ function resolveHarnessKind() {
14301
14754
  return "pty";
14302
14755
  }
14303
14756
  function resolveCardTui(mode) {
14304
- if (mode === "code-review") return "claude-code";
14757
+ if (mode === "code-review" && process.env.CONVEYOR_TUI !== "codex") return "claude-code";
14305
14758
  return resolveTuiKindFromEnv(process.env);
14306
14759
  }
14307
14760
  function buildPtyBridge(connection, onBackgroundTaskDone) {