@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 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,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 createHash2, randomUUID as randomUUID2 } from "crypto";
6896
- import { existsSync, readFileSync as readFileSync2, renameSync, truncateSync } from "fs";
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 join11 } from "path";
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(readFileSync(join11(workspaceDir, "package.json"), "utf8"));
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 join12 } from "path";
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 : join12(config.workspaceDir, 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 = basename(filePath);
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 readFile2 } from "fs/promises";
11421
- import { isAbsolute as isAbsolute2, join as join13, normalize } from "path";
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 readFile2(absolutePath, "utf8");
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 = join13(workspaceDir, toRelativePath(link.path));
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 = createHash2("sha256").update(lineageKey).digest("hex");
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 = readFileSync2(path, "utf8");
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
- renameSync(path, `${path.replace(/\.jsonl$/, "")}.aborted-${Date.now()}.jsonl`);
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", {