@rallycry/conveyor-agent 10.2.4 → 10.2.5

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.
@@ -1909,6 +1909,7 @@ async function restoreOnBoot(bundle, cwd) {
1909
1909
  import { z } from "zod";
1910
1910
  import { z as z2 } from "zod";
1911
1911
  var DEFAULT_SONNET_MODEL = "claude-sonnet-5";
1912
+ var TUI_KINDS = ["claude-code", "opencode"];
1912
1913
  var MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024;
1913
1914
  var EMBED_THRESHOLD_IMAGES = 5 * 1024 * 1024;
1914
1915
  var EMBED_THRESHOLD_TEXT = 2 * 1024 * 1024;
@@ -2444,9 +2445,14 @@ var ListMyLiveSessionsRequestSchema = z2.object({
2444
2445
  /** Admin-only: list another member's sessions instead of the caller's. */
2445
2446
  targetUserId: z2.string().optional()
2446
2447
  });
2448
+ var GetProjectAvailableTuisRequestSchema = z2.object({
2449
+ projectId: z2.string()
2450
+ });
2447
2451
  var StartAdhocSessionRequestSchema = z2.object({
2448
2452
  projectId: z2.string(),
2449
2453
  label: z2.string().max(200).optional(),
2454
+ /** Coding-agent key to launch under — validated pick-time (ownership + TUI availability) in the handler. */
2455
+ codingAgentKeyId: z2.string().optional(),
2450
2456
  /**
2451
2457
  * Session role. Constrained: other task-less modes fall through to the pm
2452
2458
  * runner in the pod entrypoint, and "review" would crash without a task.
@@ -2977,9 +2983,9 @@ var ClaudeCodeHarness = class {
2977
2983
  };
2978
2984
 
2979
2985
  // src/harness/pty/session.ts
2980
- import { mkdtemp as mkdtemp2, mkdir as mkdir2, rm as rm4 } from "fs/promises";
2986
+ import { mkdtemp as mkdtemp2, mkdir as mkdir3, rm as rm5 } from "fs/promises";
2981
2987
  import { tmpdir as tmpdir3 } from "os";
2982
- import { join as join3, dirname } from "path";
2988
+ import { join as join4, dirname } from "path";
2983
2989
 
2984
2990
  // src/harness/pty/event-queue.ts
2985
2991
  var AsyncEventQueue = class {
@@ -3442,76 +3448,6 @@ function mapChatRecords(raw) {
3442
3448
  }
3443
3449
  }
3444
3450
 
3445
- // src/harness/pty/spawn-args.ts
3446
- function resolveClaudeBinary() {
3447
- return process.env.CONVEYOR_CLAUDE_BIN ?? "claude";
3448
- }
3449
- function buildSpawnArgs(input) {
3450
- const args = [];
3451
- if (input.resume) {
3452
- args.push("--resume", input.resume);
3453
- } else if (input.sessionId) {
3454
- args.push("--session-id", input.sessionId);
3455
- }
3456
- args.push("--model", input.model);
3457
- if (input.permissionMode === "bypassPermissions") {
3458
- args.push("--dangerously-skip-permissions");
3459
- } else {
3460
- args.push("--permission-mode", "plan");
3461
- }
3462
- args.push("--settings", input.settingsPath);
3463
- if (input.appendSystemPrompt) {
3464
- args.push("--append-system-prompt", input.appendSystemPrompt);
3465
- }
3466
- if (input.mcpConfigPath) {
3467
- args.push("--mcp-config", input.mcpConfigPath);
3468
- if (input.strictMcpConfig) {
3469
- args.push("--strict-mcp-config");
3470
- }
3471
- }
3472
- return args;
3473
- }
3474
- function spawnOptionsFingerprint(input) {
3475
- return JSON.stringify([
3476
- input.model,
3477
- input.permissionMode,
3478
- input.appendSystemPrompt ?? "",
3479
- input.cwd
3480
- ]);
3481
- }
3482
- var ANSI_CSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`, "g");
3483
- function cleanTerminalOutput(raw, maxChars = 1200) {
3484
- const noAnsi = raw.replace(ANSI_CSI, "");
3485
- let out = "";
3486
- for (const ch of noAnsi) {
3487
- const code = ch.charCodeAt(0);
3488
- if (ch === "\r" || ch === "\n") out += "\n";
3489
- else if (ch === " ") out += ch;
3490
- else if (code < 32 || code === 127) continue;
3491
- else out += ch;
3492
- }
3493
- const lines = out.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
3494
- const text = lines.join("\n").trim();
3495
- return text.length > maxChars ? `\u2026${text.slice(-maxChars)}` : text;
3496
- }
3497
- function isMissingBinaryFailure(tail) {
3498
- return /execvp\(\d+\) failed|no such file or directory|command not found/i.test(tail);
3499
- }
3500
- function buildExitErrors(exitCode, rawOutput, binary) {
3501
- const errors = [`claude exited (code ${exitCode}) without a result`];
3502
- const tail = cleanTerminalOutput(rawOutput);
3503
- if (isMissingBinaryFailure(tail)) {
3504
- errors.push(
3505
- `The \`${binary}\` CLI could not be started \u2014 it is not installed or not on PATH. Install the Claude Code CLI in this environment (npm i -g @anthropic-ai/claude-code) or set CONVEYOR_CLAUDE_BIN to its absolute path.`
3506
- );
3507
- }
3508
- if (tail) {
3509
- errors.push(`Last terminal output before exit:
3510
- ${tail}`);
3511
- }
3512
- return errors;
3513
- }
3514
-
3515
3451
  // src/harness/pty/settings.ts
3516
3452
  import { mkdir, writeFile as writeFile2, chmod } from "fs/promises";
3517
3453
  import { homedir } from "os";
@@ -3982,6 +3918,238 @@ async function startToolServers(mcpServers, tempDir) {
3982
3918
  return { servers, mcpConfigPath };
3983
3919
  }
3984
3920
 
3921
+ // src/harness/pty/spawn-args.ts
3922
+ function buildSpawnArgs(input) {
3923
+ const args = [];
3924
+ if (input.resume) {
3925
+ args.push("--resume", input.resume);
3926
+ } else if (input.sessionId) {
3927
+ args.push("--session-id", input.sessionId);
3928
+ }
3929
+ args.push("--model", input.model);
3930
+ if (input.permissionMode === "bypassPermissions") {
3931
+ args.push("--dangerously-skip-permissions");
3932
+ } else {
3933
+ args.push("--permission-mode", "plan");
3934
+ }
3935
+ args.push("--settings", input.settingsPath);
3936
+ if (input.appendSystemPrompt) {
3937
+ args.push("--append-system-prompt", input.appendSystemPrompt);
3938
+ }
3939
+ if (input.mcpConfigPath) {
3940
+ args.push("--mcp-config", input.mcpConfigPath);
3941
+ if (input.strictMcpConfig) {
3942
+ args.push("--strict-mcp-config");
3943
+ }
3944
+ }
3945
+ return args;
3946
+ }
3947
+ function spawnOptionsFingerprint(input) {
3948
+ return JSON.stringify([
3949
+ input.model,
3950
+ input.permissionMode,
3951
+ input.appendSystemPrompt ?? "",
3952
+ input.cwd
3953
+ ]);
3954
+ }
3955
+ var ANSI_CSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`, "g");
3956
+ function cleanTerminalOutput(raw, maxChars = 1200) {
3957
+ const noAnsi = raw.replace(ANSI_CSI, "");
3958
+ let out = "";
3959
+ for (const ch of noAnsi) {
3960
+ const code = ch.charCodeAt(0);
3961
+ if (ch === "\r" || ch === "\n") out += "\n";
3962
+ else if (ch === " ") out += ch;
3963
+ else if (code < 32 || code === 127) continue;
3964
+ else out += ch;
3965
+ }
3966
+ const lines = out.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
3967
+ const text = lines.join("\n").trim();
3968
+ return text.length > maxChars ? `\u2026${text.slice(-maxChars)}` : text;
3969
+ }
3970
+ function isMissingBinaryFailure(tail) {
3971
+ return /execvp\(\d+\) failed|no such file or directory|command not found/i.test(tail);
3972
+ }
3973
+ function buildExitErrors(exitCode, rawOutput, binary) {
3974
+ const errors = [`claude exited (code ${exitCode}) without a result`];
3975
+ const tail = cleanTerminalOutput(rawOutput);
3976
+ if (isMissingBinaryFailure(tail)) {
3977
+ errors.push(
3978
+ `The \`${binary}\` CLI could not be started \u2014 it is not installed or not on PATH. Install the Claude Code CLI in this environment (npm i -g @anthropic-ai/claude-code) or set CONVEYOR_CLAUDE_BIN to its absolute path.`
3979
+ );
3980
+ }
3981
+ if (tail) {
3982
+ errors.push(`Last terminal output before exit:
3983
+ ${tail}`);
3984
+ }
3985
+ return errors;
3986
+ }
3987
+
3988
+ // src/harness/pty/credentials.ts
3989
+ import { chmod as chmod2, mkdir as mkdir2, readFile, rm as rm4, writeFile as writeFile4 } from "fs/promises";
3990
+ import { homedir as homedir2 } from "os";
3991
+ import { join as join3 } from "path";
3992
+ var SYNTH_TOKEN_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
3993
+ var REFRESH_SKEW_MS = 30 * 24 * 60 * 60 * 1e3;
3994
+ function claudeCredentialsPath() {
3995
+ return join3(claudeConfigHome(), ".credentials.json");
3996
+ }
3997
+ function isConveyorCloudEnv(env = process.env) {
3998
+ return Boolean(env.CLAUDESPACE_NAME || env.CODESPACE_NAME || env.CODESPACES);
3999
+ }
4000
+ function parseClaudeAiOauth(raw) {
4001
+ if (!raw || raw.trim() === "") return null;
4002
+ try {
4003
+ const parsed = JSON.parse(raw);
4004
+ if (typeof parsed !== "object" || parsed === null) return null;
4005
+ const oauth = parsed.claudeAiOauth;
4006
+ if (typeof oauth !== "object" || oauth === null) return null;
4007
+ const record = oauth;
4008
+ return {
4009
+ accessToken: record.accessToken,
4010
+ refreshToken: record.refreshToken,
4011
+ expiresAt: record.expiresAt
4012
+ };
4013
+ } catch {
4014
+ return null;
4015
+ }
4016
+ }
4017
+ function buildSynthesizedCredentials(token, now) {
4018
+ return JSON.stringify({
4019
+ claudeAiOauth: {
4020
+ accessToken: token,
4021
+ expiresAt: now + SYNTH_TOKEN_TTL_MS,
4022
+ scopes: ["user:inference", "user:profile"],
4023
+ subscriptionType: "max"
4024
+ }
4025
+ });
4026
+ }
4027
+ function planCredentialsWrite(input) {
4028
+ if (!input.isCloud) return { action: "skip", reason: "not-cloud" };
4029
+ if (!input.token) return { action: "skip", reason: "no-token" };
4030
+ const contents = buildSynthesizedCredentials(input.token, input.now);
4031
+ const existing = parseClaudeAiOauth(input.existingRaw);
4032
+ if (!existing) return { action: "write", contents };
4033
+ if (typeof existing.refreshToken === "string" && existing.refreshToken.length > 0) {
4034
+ return { action: "skip", reason: "foreign-credentials" };
4035
+ }
4036
+ const fresh = existing.accessToken === input.token && typeof existing.expiresAt === "number" && existing.expiresAt > input.now + REFRESH_SKEW_MS;
4037
+ if (fresh) return { action: "skip", reason: "current" };
4038
+ return { action: "write", contents };
4039
+ }
4040
+ async function readRaw(path4) {
4041
+ try {
4042
+ return await readFile(path4, "utf8");
4043
+ } catch {
4044
+ return null;
4045
+ }
4046
+ }
4047
+ async function ensureClaudeCredentials(env = process.env) {
4048
+ try {
4049
+ const path4 = claudeCredentialsPath();
4050
+ const plan = planCredentialsWrite({
4051
+ isCloud: isConveyorCloudEnv(env),
4052
+ token: env.CLAUDE_CODE_OAUTH_TOKEN,
4053
+ existingRaw: await readRaw(path4),
4054
+ now: Date.now()
4055
+ });
4056
+ if (plan.action === "skip") return;
4057
+ await mkdir2(claudeConfigHome(), { recursive: true });
4058
+ await writeFile4(path4, plan.contents, { encoding: "utf8", mode: 384 });
4059
+ await chmod2(path4, 384).catch(() => {
4060
+ });
4061
+ } catch (err) {
4062
+ const message = err instanceof Error ? err.message : String(err);
4063
+ process.stderr.write(`[conveyor-agent] claude credentials sync failed: ${message}
4064
+ `);
4065
+ }
4066
+ }
4067
+ function claudeJsonPath() {
4068
+ const configDir = process.env.CLAUDE_CONFIG_DIR;
4069
+ return configDir ? join3(configDir, ".claude.json") : join3(homedir2(), ".claude.json");
4070
+ }
4071
+ function asRecord(value) {
4072
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
4073
+ }
4074
+ function parseClaudeJson(existingRaw) {
4075
+ if (!existingRaw || existingRaw.trim() === "") return {};
4076
+ try {
4077
+ return asRecord(JSON.parse(existingRaw));
4078
+ } catch {
4079
+ return {};
4080
+ }
4081
+ }
4082
+ function seedWorkspaceTrust(config, trustCwd) {
4083
+ const projects = asRecord(config.projects);
4084
+ const entry = asRecord(projects[trustCwd]);
4085
+ if (entry.hasTrustDialogAccepted === true) return false;
4086
+ entry.hasTrustDialogAccepted = true;
4087
+ projects[trustCwd] = entry;
4088
+ config.projects = projects;
4089
+ return true;
4090
+ }
4091
+ function planClaudeJsonSeed(existingRaw, trustCwd) {
4092
+ const config = parseClaudeJson(existingRaw);
4093
+ const isFresh = Object.keys(config).length === 0;
4094
+ let changed = false;
4095
+ if (config.hasCompletedOnboarding !== true) {
4096
+ config.hasCompletedOnboarding = true;
4097
+ changed = true;
4098
+ }
4099
+ if (config.bypassPermissionsModeAccepted !== true) {
4100
+ config.bypassPermissionsModeAccepted = true;
4101
+ changed = true;
4102
+ }
4103
+ if (isFresh && typeof config.theme !== "string") {
4104
+ config.theme = "dark";
4105
+ changed = true;
4106
+ }
4107
+ if (trustCwd && seedWorkspaceTrust(config, trustCwd)) {
4108
+ changed = true;
4109
+ }
4110
+ return changed ? JSON.stringify(config) : null;
4111
+ }
4112
+ async function ensureClaudeOnboarding(env = process.env, trustCwd) {
4113
+ try {
4114
+ if (!isConveyorCloudEnv(env)) return;
4115
+ const path4 = claudeJsonPath();
4116
+ const contents = planClaudeJsonSeed(await readRaw(path4), trustCwd);
4117
+ if (contents === null) return;
4118
+ await writeFile4(path4, contents, "utf8");
4119
+ const verify = await readRaw(path4);
4120
+ if (verify === contents) {
4121
+ process.stderr.write(
4122
+ `[conveyor-agent] claude onboarding seeded${trustCwd ? ` (trust: ${trustCwd})` : ""}
4123
+ `
4124
+ );
4125
+ } else {
4126
+ process.stderr.write(
4127
+ `[conveyor-agent] claude onboarding seed read-back MISMATCH at ${path4}: wrote ${contents.length}B, read ${verify?.length ?? 0}B \u2014 CLI may see stale config and park at a startup dialog
4128
+ `
4129
+ );
4130
+ }
4131
+ } catch (err) {
4132
+ const message = err instanceof Error ? err.message : String(err);
4133
+ process.stderr.write(`[conveyor-agent] claude onboarding seed failed: ${message}
4134
+ `);
4135
+ }
4136
+ }
4137
+ async function removeConveyorCredentials(env = process.env) {
4138
+ try {
4139
+ if (!isConveyorCloudEnv(env)) return;
4140
+ const path4 = claudeCredentialsPath();
4141
+ const existing = parseClaudeAiOauth(await readRaw(path4));
4142
+ if (existing && typeof existing.refreshToken === "string" && existing.refreshToken.length > 0) {
4143
+ return;
4144
+ }
4145
+ await rm4(path4, { force: true });
4146
+ } catch (err) {
4147
+ const message = err instanceof Error ? err.message : String(err);
4148
+ process.stderr.write(`[conveyor-agent] claude credentials removal failed: ${message}
4149
+ `);
4150
+ }
4151
+ }
4152
+
3985
4153
  // src/harness/pty/pty-support.ts
3986
4154
  import { stat as stat3 } from "fs/promises";
3987
4155
  var MAX_DIAGNOSTIC_OUTPUT = 4e3;
@@ -4037,7 +4205,9 @@ function inheritedEnv(socketPath) {
4037
4205
  for (const [key, value] of Object.entries(process.env)) {
4038
4206
  if (typeof value === "string") env[key] = value;
4039
4207
  }
4040
- env.CONVEYOR_HOOK_SOCKET = socketPath;
4208
+ if (socketPath) {
4209
+ env.CONVEYOR_HOOK_SOCKET = socketPath;
4210
+ }
4041
4211
  env.MCP_TIMEOUT ??= "60000";
4042
4212
  env.MCP_TOOL_TIMEOUT ??= "180000";
4043
4213
  return env;
@@ -4087,18 +4257,65 @@ function parseUserQuestions(input) {
4087
4257
  return questions;
4088
4258
  }
4089
4259
 
4260
+ // src/harness/pty/adapters/claude.ts
4261
+ var ClaudeTuiAdapter = class {
4262
+ id = "claude-code";
4263
+ capabilities = {
4264
+ resume: true,
4265
+ structuredEvents: true,
4266
+ prefill: true,
4267
+ passiveTurns: true
4268
+ };
4269
+ resolveBinary(env = process.env) {
4270
+ return env.CONVEYOR_CLAUDE_BIN ?? "claude";
4271
+ }
4272
+ buildSpawn(input) {
4273
+ const { options, resume } = input;
4274
+ const args = buildSpawnArgs({
4275
+ ...resume ? { resume } : {},
4276
+ ...options.sessionId ? { sessionId: options.sessionId } : {},
4277
+ model: options.model,
4278
+ permissionMode: options.permissionMode,
4279
+ settingsPath: input.settingsPath ?? "",
4280
+ ...options.appendSystemPrompt ? { appendSystemPrompt: options.appendSystemPrompt } : {},
4281
+ ...input.mcpConfigPath ? { mcpConfigPath: input.mcpConfigPath, strictMcpConfig: true } : {}
4282
+ });
4283
+ return {
4284
+ file: this.resolveBinary(),
4285
+ args,
4286
+ env: inheritedEnv(input.hookSocketPath)
4287
+ };
4288
+ }
4289
+ async prepareEnvironment(opts) {
4290
+ const env = opts.env ?? process.env;
4291
+ await ensureClaudeCredentials(env);
4292
+ await ensureClaudeOnboarding(env, opts.cwd);
4293
+ }
4294
+ spawnFingerprint(input) {
4295
+ return spawnOptionsFingerprint(input);
4296
+ }
4297
+ encodePromptBytes(text) {
4298
+ return buildPromptBytes(text);
4299
+ }
4300
+ buildExitErrors(exitCode, rawOutput) {
4301
+ return buildExitErrors(exitCode, rawOutput, this.resolveBinary());
4302
+ }
4303
+ };
4304
+
4090
4305
  // src/harness/pty/session.ts
4091
4306
  var PtySession = class {
4092
- constructor(prompt, options, resume, bridge) {
4307
+ constructor(prompt, options, resume, bridge, adapter = new ClaudeTuiAdapter()) {
4093
4308
  this.options = options;
4094
4309
  this.resume = resume;
4095
4310
  this.bridge = bridge;
4311
+ this.adapter = adapter;
4096
4312
  this.turnPrompt = prompt;
4097
4313
  this.turn = turnOptionsFrom(options);
4098
4314
  }
4099
4315
  options;
4100
4316
  resume;
4101
4317
  bridge;
4318
+ adapter;
4102
4319
  // The current turn's event stream. Null between turns (process parked) — the
4103
4320
  // process outlives any single turn, so the queue is per-turn, not per-process.
4104
4321
  activeQueue = null;
@@ -4184,7 +4401,7 @@ var PtySession = class {
4184
4401
  }
4185
4402
  /** Fingerprint of the spawn-time options a reused process cannot change. */
4186
4403
  get spawnFingerprint() {
4187
- return spawnOptionsFingerprint({
4404
+ return this.adapter.spawnFingerprint({
4188
4405
  model: this.options.model,
4189
4406
  permissionMode: this.options.permissionMode,
4190
4407
  ...this.options.appendSystemPrompt ? { appendSystemPrompt: this.options.appendSystemPrompt } : {},
@@ -4321,29 +4538,19 @@ var PtySession = class {
4321
4538
  return;
4322
4539
  }
4323
4540
  try {
4324
- this.tempDir = await mkdtemp2(join3(tmpdir3(), "conveyor-pty-"));
4325
- const socketPath = join3(this.tempDir, "hook.sock");
4326
- this.socket = new HookSocketServer(
4327
- socketPath,
4328
- (progress) => this.handleProgress(progress),
4329
- (request) => this.handlePreToolUse(request)
4330
- );
4331
- await this.socket.listen();
4332
- const { settingsPath } = await writeHookSettings(this.tempDir);
4333
- await this.setupToolServers();
4334
- const transcriptPath = sessionTranscriptPath(this.options.cwd, sessionId);
4335
- await mkdir2(dirname(transcriptPath), { recursive: true });
4336
- const startOffset = this.resume ? await transcriptSize(transcriptPath) : 0;
4337
- const sendChat = this.bridge?.sendChatEvent?.bind(this.bridge);
4338
- this.tailer = new JsonlTailer(
4339
- transcriptPath,
4340
- (event) => this.handleTranscriptEvent(event),
4341
- sendChat ? (raw) => {
4342
- for (const chatEvent of mapChatRecords(raw)) sendChat(chatEvent);
4343
- } : void 0
4344
- );
4345
- this.tailer.start(startOffset);
4346
- await this.spawn(settingsPath, socketPath);
4541
+ if (this.adapter.capabilities.structuredEvents) {
4542
+ const { settingsPath, socketPath } = await this.startStructuredEventSources(sessionId);
4543
+ await this.spawn(settingsPath, socketPath);
4544
+ } else {
4545
+ this.tempDir = await mkdtemp2(join4(tmpdir3(), "conveyor-pty-"));
4546
+ await this.spawn();
4547
+ this.pushEvent({
4548
+ type: "system",
4549
+ subtype: "init",
4550
+ session_id: sessionId,
4551
+ model: this.options.model
4552
+ });
4553
+ }
4347
4554
  if (signal) {
4348
4555
  this.abortHandler = () => {
4349
4556
  void this.teardown();
@@ -4364,6 +4571,38 @@ var PtySession = class {
4364
4571
  throw err;
4365
4572
  }
4366
4573
  }
4574
+ /**
4575
+ * Allocate the Claude-style structured-event sources: the PostToolUse hook
4576
+ * socket, the per-run settings file, the in-process tool servers, and the
4577
+ * transcript tailer. Only structured-events adapters call this — raw-terminal
4578
+ * adapters have no trusted event source and skip it entirely. Returns the
4579
+ * paths spawn() must wire into the child's argv/env.
4580
+ */
4581
+ async startStructuredEventSources(sessionId) {
4582
+ this.tempDir = await mkdtemp2(join4(tmpdir3(), "conveyor-pty-"));
4583
+ const socketPath = join4(this.tempDir, "hook.sock");
4584
+ this.socket = new HookSocketServer(
4585
+ socketPath,
4586
+ (progress) => this.handleProgress(progress),
4587
+ (request) => this.handlePreToolUse(request)
4588
+ );
4589
+ await this.socket.listen();
4590
+ const { settingsPath } = await writeHookSettings(this.tempDir);
4591
+ await this.setupToolServers();
4592
+ const transcriptPath = sessionTranscriptPath(this.options.cwd, sessionId);
4593
+ await mkdir3(dirname(transcriptPath), { recursive: true });
4594
+ const startOffset = this.resume ? await transcriptSize(transcriptPath) : 0;
4595
+ const sendChat = this.bridge?.sendChatEvent?.bind(this.bridge);
4596
+ this.tailer = new JsonlTailer(
4597
+ transcriptPath,
4598
+ (event) => this.handleTranscriptEvent(event),
4599
+ sendChat ? (raw) => {
4600
+ for (const chatEvent of mapChatRecords(raw)) sendChat(chatEvent);
4601
+ } : void 0
4602
+ );
4603
+ this.tailer.start(startOffset);
4604
+ return { settingsPath, socketPath };
4605
+ }
4367
4606
  writeStdin(text) {
4368
4607
  this.pty?.write(text);
4369
4608
  }
@@ -4429,7 +4668,7 @@ var PtySession = class {
4429
4668
  this.activeQueue?.close();
4430
4669
  this.activeQueue = null;
4431
4670
  if (this.tempDir) {
4432
- await rm4(this.tempDir, { recursive: true, force: true });
4671
+ await rm5(this.tempDir, { recursive: true, force: true });
4433
4672
  this.tempDir = "";
4434
4673
  }
4435
4674
  }
@@ -4448,25 +4687,23 @@ var PtySession = class {
4448
4687
  this.mcpConfigPath = mcpConfigPath;
4449
4688
  }
4450
4689
  async spawn(settingsPath, socketPath) {
4451
- const args = buildSpawnArgs({
4452
- resume: this.resume,
4453
- sessionId: this.options.sessionId,
4454
- model: this.options.model,
4455
- permissionMode: this.options.permissionMode,
4456
- settingsPath,
4457
- ...this.options.appendSystemPrompt ? { appendSystemPrompt: this.options.appendSystemPrompt } : {},
4690
+ const spec = this.adapter.buildSpawn({
4691
+ options: this.options,
4692
+ ...this.resume ? { resume: this.resume } : {},
4693
+ ...settingsPath ? { settingsPath } : {},
4694
+ ...socketPath ? { hookSocketPath: socketPath } : {},
4458
4695
  // Only this config: ignore the user's ~/.claude.json / project .mcp.json
4459
4696
  // so the agent's tool set is deterministic (and a stale personal conveyor
4460
4697
  // server doesn't load).
4461
- ...this.mcpConfigPath ? { mcpConfigPath: this.mcpConfigPath, strictMcpConfig: true } : {}
4698
+ ...this.mcpConfigPath ? { mcpConfigPath: this.mcpConfigPath } : {}
4462
4699
  });
4463
4700
  const spawn2 = await loadPtySpawn();
4464
- const pty = spawn2(resolveClaudeBinary(), args, {
4701
+ const pty = spawn2(spec.file, spec.args, {
4465
4702
  name: "xterm-color",
4466
4703
  cols: this.cols,
4467
4704
  rows: this.rows,
4468
4705
  cwd: this.options.cwd,
4469
- env: inheritedEnv(socketPath)
4706
+ env: spec.env
4470
4707
  });
4471
4708
  const bridge = this.bridge;
4472
4709
  if (bridge) {
@@ -4487,12 +4724,13 @@ var PtySession = class {
4487
4724
  this.pty = pty;
4488
4725
  }
4489
4726
  async deliverPrompt(text) {
4490
- this.writeStdin(buildPromptBytes(text));
4727
+ if (text === "" && !this.adapter.capabilities.prefill) return;
4728
+ this.writeStdin(this.adapter.encodePromptBytes(text));
4491
4729
  if (this.turn.promptDelivery === "prefill") return;
4492
4730
  await sleep3(SUBMIT_SETTLE_MS);
4493
4731
  if (this._toreDown) return;
4494
4732
  this.writeStdin("\r");
4495
- this.armSubmitNudge();
4733
+ if (this.adapter.capabilities.structuredEvents) this.armSubmitNudge();
4496
4734
  }
4497
4735
  async feedPrompt() {
4498
4736
  if (typeof this.turnPrompt === "string") {
@@ -4649,11 +4887,22 @@ var PtySession = class {
4649
4887
  this.tailer.close();
4650
4888
  await this.tailer.flush();
4651
4889
  }
4652
- if (!this.sawResult && this.activeQueue) {
4890
+ if (!this.adapter.capabilities.structuredEvents) {
4891
+ if (exitCode === 0) {
4892
+ this.pushEvent({ type: "result", subtype: "success", result: "", total_cost_usd: 0 });
4893
+ } else {
4894
+ this.pushEvent({
4895
+ type: "result",
4896
+ subtype: "error",
4897
+ errors: this.adapter.buildExitErrors(exitCode, this.recentOutput)
4898
+ });
4899
+ }
4900
+ this.endTurn(exitCode === 0);
4901
+ } else if (!this.sawResult && this.activeQueue) {
4653
4902
  this.activeQueue.push({
4654
4903
  type: "result",
4655
4904
  subtype: "error",
4656
- errors: buildExitErrors(exitCode, this.recentOutput, resolveClaudeBinary())
4905
+ errors: this.adapter.buildExitErrors(exitCode, this.recentOutput)
4657
4906
  });
4658
4907
  }
4659
4908
  for (const listener of this.exitListeners) listener(exitCode);
@@ -4662,191 +4911,29 @@ var PtySession = class {
4662
4911
  }
4663
4912
  };
4664
4913
 
4665
- // src/harness/pty/credentials.ts
4666
- import { chmod as chmod2, mkdir as mkdir3, readFile, rm as rm5, writeFile as writeFile4 } from "fs/promises";
4667
- import { homedir as homedir2 } from "os";
4668
- import { join as join4 } from "path";
4669
- var SYNTH_TOKEN_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
4670
- var REFRESH_SKEW_MS = 30 * 24 * 60 * 60 * 1e3;
4671
- function claudeCredentialsPath() {
4672
- return join4(claudeConfigHome(), ".credentials.json");
4673
- }
4674
- function isConveyorCloudEnv(env = process.env) {
4675
- return Boolean(env.CLAUDESPACE_NAME || env.CODESPACE_NAME || env.CODESPACES);
4676
- }
4677
- function parseClaudeAiOauth(raw) {
4678
- if (!raw || raw.trim() === "") return null;
4679
- try {
4680
- const parsed = JSON.parse(raw);
4681
- if (typeof parsed !== "object" || parsed === null) return null;
4682
- const oauth = parsed.claudeAiOauth;
4683
- if (typeof oauth !== "object" || oauth === null) return null;
4684
- const record = oauth;
4685
- return {
4686
- accessToken: record.accessToken,
4687
- refreshToken: record.refreshToken,
4688
- expiresAt: record.expiresAt
4689
- };
4690
- } catch {
4691
- return null;
4692
- }
4693
- }
4694
- function buildSynthesizedCredentials(token, now) {
4695
- return JSON.stringify({
4696
- claudeAiOauth: {
4697
- accessToken: token,
4698
- expiresAt: now + SYNTH_TOKEN_TTL_MS,
4699
- scopes: ["user:inference", "user:profile"],
4700
- subscriptionType: "max"
4701
- }
4702
- });
4703
- }
4704
- function planCredentialsWrite(input) {
4705
- if (!input.isCloud) return { action: "skip", reason: "not-cloud" };
4706
- if (!input.token) return { action: "skip", reason: "no-token" };
4707
- const contents = buildSynthesizedCredentials(input.token, input.now);
4708
- const existing = parseClaudeAiOauth(input.existingRaw);
4709
- if (!existing) return { action: "write", contents };
4710
- if (typeof existing.refreshToken === "string" && existing.refreshToken.length > 0) {
4711
- return { action: "skip", reason: "foreign-credentials" };
4712
- }
4713
- const fresh = existing.accessToken === input.token && typeof existing.expiresAt === "number" && existing.expiresAt > input.now + REFRESH_SKEW_MS;
4714
- if (fresh) return { action: "skip", reason: "current" };
4715
- return { action: "write", contents };
4716
- }
4717
- async function readRaw(path4) {
4718
- try {
4719
- return await readFile(path4, "utf8");
4720
- } catch {
4721
- return null;
4722
- }
4723
- }
4724
- async function ensureClaudeCredentials(env = process.env) {
4725
- try {
4726
- const path4 = claudeCredentialsPath();
4727
- const plan = planCredentialsWrite({
4728
- isCloud: isConveyorCloudEnv(env),
4729
- token: env.CLAUDE_CODE_OAUTH_TOKEN,
4730
- existingRaw: await readRaw(path4),
4731
- now: Date.now()
4732
- });
4733
- if (plan.action === "skip") return;
4734
- await mkdir3(claudeConfigHome(), { recursive: true });
4735
- await writeFile4(path4, plan.contents, { encoding: "utf8", mode: 384 });
4736
- await chmod2(path4, 384).catch(() => {
4737
- });
4738
- } catch (err) {
4739
- const message = err instanceof Error ? err.message : String(err);
4740
- process.stderr.write(`[conveyor-agent] claude credentials sync failed: ${message}
4741
- `);
4742
- }
4743
- }
4744
- function claudeJsonPath() {
4745
- const configDir = process.env.CLAUDE_CONFIG_DIR;
4746
- return configDir ? join4(configDir, ".claude.json") : join4(homedir2(), ".claude.json");
4747
- }
4748
- function asRecord(value) {
4749
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
4750
- }
4751
- function parseClaudeJson(existingRaw) {
4752
- if (!existingRaw || existingRaw.trim() === "") return {};
4753
- try {
4754
- return asRecord(JSON.parse(existingRaw));
4755
- } catch {
4756
- return {};
4757
- }
4758
- }
4759
- function seedWorkspaceTrust(config, trustCwd) {
4760
- const projects = asRecord(config.projects);
4761
- const entry = asRecord(projects[trustCwd]);
4762
- if (entry.hasTrustDialogAccepted === true) return false;
4763
- entry.hasTrustDialogAccepted = true;
4764
- projects[trustCwd] = entry;
4765
- config.projects = projects;
4766
- return true;
4767
- }
4768
- function planClaudeJsonSeed(existingRaw, trustCwd) {
4769
- const config = parseClaudeJson(existingRaw);
4770
- const isFresh = Object.keys(config).length === 0;
4771
- let changed = false;
4772
- if (config.hasCompletedOnboarding !== true) {
4773
- config.hasCompletedOnboarding = true;
4774
- changed = true;
4775
- }
4776
- if (config.bypassPermissionsModeAccepted !== true) {
4777
- config.bypassPermissionsModeAccepted = true;
4778
- changed = true;
4779
- }
4780
- if (isFresh && typeof config.theme !== "string") {
4781
- config.theme = "dark";
4782
- changed = true;
4783
- }
4784
- if (trustCwd && seedWorkspaceTrust(config, trustCwd)) {
4785
- changed = true;
4786
- }
4787
- return changed ? JSON.stringify(config) : null;
4788
- }
4789
- async function ensureClaudeOnboarding(env = process.env, trustCwd) {
4790
- try {
4791
- if (!isConveyorCloudEnv(env)) return;
4792
- const path4 = claudeJsonPath();
4793
- const contents = planClaudeJsonSeed(await readRaw(path4), trustCwd);
4794
- if (contents === null) return;
4795
- await writeFile4(path4, contents, "utf8");
4796
- const verify = await readRaw(path4);
4797
- if (verify === contents) {
4798
- process.stderr.write(
4799
- `[conveyor-agent] claude onboarding seeded${trustCwd ? ` (trust: ${trustCwd})` : ""}
4800
- `
4801
- );
4802
- } else {
4803
- process.stderr.write(
4804
- `[conveyor-agent] claude onboarding seed read-back MISMATCH at ${path4}: wrote ${contents.length}B, read ${verify?.length ?? 0}B \u2014 CLI may see stale config and park at a startup dialog
4805
- `
4806
- );
4807
- }
4808
- } catch (err) {
4809
- const message = err instanceof Error ? err.message : String(err);
4810
- process.stderr.write(`[conveyor-agent] claude onboarding seed failed: ${message}
4811
- `);
4812
- }
4813
- }
4814
- async function removeConveyorCredentials(env = process.env) {
4815
- try {
4816
- if (!isConveyorCloudEnv(env)) return;
4817
- const path4 = claudeCredentialsPath();
4818
- const existing = parseClaudeAiOauth(await readRaw(path4));
4819
- if (existing && typeof existing.refreshToken === "string" && existing.refreshToken.length > 0) {
4820
- return;
4821
- }
4822
- await rm5(path4, { force: true });
4823
- } catch (err) {
4824
- const message = err instanceof Error ? err.message : String(err);
4825
- process.stderr.write(`[conveyor-agent] claude credentials removal failed: ${message}
4826
- `);
4827
- }
4828
- }
4829
-
4830
4914
  // src/harness/pty/index.ts
4831
4915
  var ENDED_GRACE_MS = 4e3;
4832
- function fingerprintOf(options) {
4833
- return spawnOptionsFingerprint({
4834
- model: options.model,
4835
- permissionMode: options.permissionMode,
4836
- ...options.appendSystemPrompt ? { appendSystemPrompt: options.appendSystemPrompt } : {},
4837
- cwd: options.cwd
4838
- });
4839
- }
4840
4916
  var PtyHarness = class {
4841
4917
  /**
4842
4918
  * `bridge` relays raw terminal I/O to/from the S2 server (and on to the S5
4843
4919
  * terminal). It is undefined for SDK-only callers and PTY runs that never
4844
4920
  * attach a relay; the session simply discards stdout in that case.
4845
4921
  */
4846
- constructor(bridge) {
4922
+ constructor(bridge, adapter = new ClaudeTuiAdapter()) {
4847
4923
  this.bridge = bridge;
4924
+ this.adapter = adapter;
4848
4925
  }
4849
4926
  bridge;
4927
+ adapter;
4928
+ /** Fingerprint of the spawn-time options a reused process cannot change. */
4929
+ fingerprintOf(options) {
4930
+ return this.adapter.spawnFingerprint({
4931
+ model: options.model,
4932
+ permissionMode: options.permissionMode,
4933
+ ...options.appendSystemPrompt ? { appendSystemPrompt: options.appendSystemPrompt } : {},
4934
+ cwd: options.cwd
4935
+ });
4936
+ }
4850
4937
  /** The session currently streaming events, if any — repaint target. */
4851
4938
  activeSession = null;
4852
4939
  /** A completed-but-alive session kept for the next turn (keep-alive). */
@@ -4865,7 +4952,7 @@ var PtyHarness = class {
4865
4952
  }
4866
4953
  async *executeQuery(opts) {
4867
4954
  const want = opts.resume ?? opts.options.resume;
4868
- const fingerprint = fingerprintOf(opts.options);
4955
+ const fingerprint = this.fingerprintOf(opts.options);
4869
4956
  let session;
4870
4957
  if (this.parked?.canReuse(want, fingerprint)) {
4871
4958
  session = this.parked;
@@ -4879,9 +4966,8 @@ var PtyHarness = class {
4879
4966
  this.cancelEndedTimer();
4880
4967
  await stale.teardown();
4881
4968
  }
4882
- session = new PtySession(opts.prompt, opts.options, want, this.bridge);
4883
- await ensureClaudeCredentials();
4884
- await ensureClaudeOnboarding(process.env, opts.options.cwd);
4969
+ session = new PtySession(opts.prompt, opts.options, want, this.bridge, this.adapter);
4970
+ await this.adapter.prepareEnvironment({ cwd: opts.options.cwd });
4885
4971
  session.onExit(() => this.handleSessionExit(session));
4886
4972
  await session.start();
4887
4973
  }
@@ -10758,8 +10844,13 @@ export {
10758
10844
  applyBootstrapToEnv,
10759
10845
  AgentConnection,
10760
10846
  DEFAULT_SONNET_MODEL,
10847
+ TUI_KINDS,
10761
10848
  DEFAULT_LIFECYCLE_CONFIG,
10762
10849
  Lifecycle,
10850
+ cleanTerminalOutput,
10851
+ inheritedEnv,
10852
+ buildPromptBytes,
10853
+ ClaudeTuiAdapter,
10763
10854
  PtyHarness,
10764
10855
  createServiceLogger,
10765
10856
  hasUncommittedChanges,
@@ -10787,4 +10878,4 @@ export {
10787
10878
  runStartCommand,
10788
10879
  unshallowRepo
10789
10880
  };
10790
- //# sourceMappingURL=chunk-DEMRCBJN.js.map
10881
+ //# sourceMappingURL=chunk-CFELRV35.js.map