runcastle 1.4.7 → 1.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/runcastle.js CHANGED
@@ -4640,6 +4640,63 @@ var init_git = __esm(() => {
4640
4640
  ];
4641
4641
  });
4642
4642
 
4643
+ // src/doctor/system-exec.ts
4644
+ import { spawn as spawn4 } from "child_process";
4645
+ function createSystemExec(opts = {}) {
4646
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
4647
+ const containFn = opts.containFn ?? containHostProcess;
4648
+ return (command, args) => new Promise((resolve4) => {
4649
+ const { file, args: spawnArgs } = resolveSpawnTarget(command, args);
4650
+ let stdout = "";
4651
+ let stderr = "";
4652
+ let settled = false;
4653
+ const settle = (outcome) => {
4654
+ if (settled)
4655
+ return;
4656
+ settled = true;
4657
+ clearTimeout(timer);
4658
+ resolve4(outcome);
4659
+ };
4660
+ const child = spawn4(file, spawnArgs, { cwd: opts.cwd, windowsHide: true });
4661
+ const containment = child.pid === undefined ? null : containFn(child.pid);
4662
+ const timer = setTimeout(() => {
4663
+ if (containment)
4664
+ containment.kill();
4665
+ else if (child.pid !== undefined)
4666
+ killProcessTree(child.pid);
4667
+ const note = `${command} ${args.join(" ")} timed out after ${Math.round(timeoutMs / 1000)}s`;
4668
+ settle({
4669
+ ok: true,
4670
+ code: null,
4671
+ stdout,
4672
+ stderr: stderr ? `${stderr}
4673
+ ${note}` : note,
4674
+ timedOut: true
4675
+ });
4676
+ }, timeoutMs);
4677
+ child.stdout?.on("data", (d) => {
4678
+ stdout += d.toString();
4679
+ });
4680
+ child.stderr?.on("data", (d) => {
4681
+ stderr += d.toString();
4682
+ });
4683
+ child.on("error", (err) => {
4684
+ containment?.kill();
4685
+ settle({ ok: false, code: null, stdout, stderr: stderr || String(err) });
4686
+ });
4687
+ child.on("close", (code) => {
4688
+ containment?.kill();
4689
+ settle({ ok: true, code: code ?? 0, stdout, stderr });
4690
+ });
4691
+ });
4692
+ }
4693
+ var DEFAULT_EXEC_TIMEOUT_MS = 60000;
4694
+ var init_system_exec = __esm(() => {
4695
+ init_kill_tree();
4696
+ init_contain_host();
4697
+ init_resolve_executable();
4698
+ });
4699
+
4643
4700
  // src/launcher/edit-guard.ts
4644
4701
  import { isAbsolute, relative, resolve as resolve4 } from "path";
4645
4702
  function guardsEdits(kind) {
@@ -5520,8 +5577,27 @@ function buildClaudeArgs(input) {
5520
5577
  ...input.kickoffLine ? [input.kickoffLine] : []
5521
5578
  ];
5522
5579
  }
5523
- var CC_NESTING_ENV, KICKOFF_LINES, CLAUDE_BIN = "claude", claudeRuntime;
5580
+ function findBackgroundSessionIn(listing, ccSessionId) {
5581
+ if (!Array.isArray(listing))
5582
+ return;
5583
+ const held = listing.find((entry) => typeof entry === "object" && entry !== null && entry.sessionId === ccSessionId && entry.kind === "background");
5584
+ if (!held)
5585
+ return;
5586
+ return { sessionId: ccSessionId, shortId: typeof held.id === "string" ? held.id : ccSessionId.slice(0, 8) };
5587
+ }
5588
+ async function listClaudeAgents() {
5589
+ try {
5590
+ const out = await createSystemExec({ timeoutMs: AGENTS_LISTING_TIMEOUT_MS })(CLAUDE_BIN, ["agents", "--json"]);
5591
+ if (!out.ok || out.timedOut)
5592
+ return;
5593
+ return JSON.parse(out.stdout);
5594
+ } catch {
5595
+ return;
5596
+ }
5597
+ }
5598
+ var CC_NESTING_ENV, KICKOFF_LINES, AGENTS_LISTING_TIMEOUT_MS = 1e4, CLAUDE_BIN = "claude", claudeRuntime;
5524
5599
  var init_claude = __esm(() => {
5600
+ init_system_exec();
5525
5601
  init_resolve_executable();
5526
5602
  init_artifacts();
5527
5603
  init_skills_root();
@@ -5571,13 +5647,17 @@ var init_claude = __esm(() => {
5571
5647
  }),
5572
5648
  env: {
5573
5649
  RUNCASTLE_SESSION_ID: input.session.id,
5574
- RUNCASTLE_SERVER_URL: input.serverUrl
5650
+ RUNCASTLE_SERVER_URL: input.serverUrl,
5651
+ CLAUDE_CODE_DISABLE_AGENT_VIEW: "1"
5575
5652
  },
5576
5653
  envScrub: CC_NESTING_ENV
5577
5654
  };
5578
5655
  },
5579
5656
  kickoffLine(kind) {
5580
5657
  return KICKOFF_LINES[kind];
5658
+ },
5659
+ async findBackgroundSession(ccSessionId) {
5660
+ return findBackgroundSessionIn(await listClaudeAgents(), ccSessionId);
5581
5661
  }
5582
5662
  };
5583
5663
  });
@@ -8224,7 +8304,7 @@ var init_chat_branch = __esm(() => {
8224
8304
  });
8225
8305
 
8226
8306
  // src/workflows/kill-registry.ts
8227
- import { spawn as spawn4 } from "child_process";
8307
+ import { spawn as spawn5 } from "child_process";
8228
8308
  function log2(msg) {
8229
8309
  console.error(`[kill-registry] ${msg}`);
8230
8310
  }
@@ -8241,7 +8321,7 @@ function runDockerCommand(args) {
8241
8321
  const timer = setTimeout(() => settle(false), DOCKER_COMMAND_TIMEOUT_MS);
8242
8322
  timer.unref?.();
8243
8323
  try {
8244
- const child = spawn4("docker", args, { windowsHide: true, stdio: "ignore" });
8324
+ const child = spawn5("docker", args, { windowsHide: true, stdio: "ignore" });
8245
8325
  child.on("exit", (code) => settle(code === 0));
8246
8326
  child.on("close", (code) => settle(code === 0));
8247
8327
  child.on("error", () => settle(false));
@@ -8372,63 +8452,6 @@ var init_kill_registry = __esm(() => {
8372
8452
  REGISTRY_KEY2 = Symbol.for("runcastle.kill.registry");
8373
8453
  });
8374
8454
 
8375
- // src/doctor/system-exec.ts
8376
- import { spawn as spawn5 } from "child_process";
8377
- function createSystemExec(opts = {}) {
8378
- const timeoutMs = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
8379
- const containFn = opts.containFn ?? containHostProcess;
8380
- return (command, args) => new Promise((resolve9) => {
8381
- const { file, args: spawnArgs } = resolveSpawnTarget(command, args);
8382
- let stdout = "";
8383
- let stderr = "";
8384
- let settled = false;
8385
- const settle = (outcome) => {
8386
- if (settled)
8387
- return;
8388
- settled = true;
8389
- clearTimeout(timer);
8390
- resolve9(outcome);
8391
- };
8392
- const child = spawn5(file, spawnArgs, { cwd: opts.cwd, windowsHide: true });
8393
- const containment = child.pid === undefined ? null : containFn(child.pid);
8394
- const timer = setTimeout(() => {
8395
- if (containment)
8396
- containment.kill();
8397
- else if (child.pid !== undefined)
8398
- killProcessTree(child.pid);
8399
- const note = `${command} ${args.join(" ")} timed out after ${Math.round(timeoutMs / 1000)}s`;
8400
- settle({
8401
- ok: true,
8402
- code: null,
8403
- stdout,
8404
- stderr: stderr ? `${stderr}
8405
- ${note}` : note,
8406
- timedOut: true
8407
- });
8408
- }, timeoutMs);
8409
- child.stdout?.on("data", (d) => {
8410
- stdout += d.toString();
8411
- });
8412
- child.stderr?.on("data", (d) => {
8413
- stderr += d.toString();
8414
- });
8415
- child.on("error", (err) => {
8416
- containment?.kill();
8417
- settle({ ok: false, code: null, stdout, stderr: stderr || String(err) });
8418
- });
8419
- child.on("close", (code) => {
8420
- containment?.kill();
8421
- settle({ ok: true, code: code ?? 0, stdout, stderr });
8422
- });
8423
- });
8424
- }
8425
- var DEFAULT_EXEC_TIMEOUT_MS = 60000;
8426
- var init_system_exec = __esm(() => {
8427
- init_kill_tree();
8428
- init_contain_host();
8429
- init_resolve_executable();
8430
- });
8431
-
8432
8455
  // src/services/settings.ts
8433
8456
  import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
8434
8457
  import { dirname as dirname6 } from "path";
@@ -57734,7 +57757,8 @@ function runCommand2(bin, args2) {
57734
57757
  const timer = setTimeout(() => settle(false), COMMAND_TIMEOUT_MS);
57735
57758
  timer.unref?.();
57736
57759
  try {
57737
- const child = spawn8(bin, [...args2], { windowsHide: true, stdio: "ignore" });
57760
+ const target = spawnTargetFor(bin, [...args2]);
57761
+ const child = spawn8(target.file, target.args, { windowsHide: true, stdio: "ignore" });
57738
57762
  child.on("exit", (code) => settle(code === 0));
57739
57763
  child.on("close", (code) => settle(code === 0));
57740
57764
  child.on("error", () => settle(false));
@@ -57800,6 +57824,7 @@ async function reapRecorder(ticketId, options = {}) {
57800
57824
  var AGENT_BROWSER_BIN = "agent-browser", DEFAULT_TIMEOUT_MS2 = 1e4, POLL_INTERVAL_MS = 100, COMMAND_TIMEOUT_MS = 5000, REAL_DEPS2;
57801
57825
  var init_recorder_reap = __esm(() => {
57802
57826
  init_paths();
57827
+ init_resolve_executable();
57803
57828
  REAL_DEPS2 = {
57804
57829
  findBrowser: () => findOnPath(AGENT_BROWSER_BIN),
57805
57830
  fileExists: existsSync16,
@@ -58410,7 +58435,10 @@ function couldNotReview(ticket, reason, digest) {
58410
58435
  **${headline2}**` : `**${headline2}**`
58411
58436
  };
58412
58437
  }
58413
- var FFMPEG_BIN = "ffmpeg", spawnVersion = (path2) => spawnSync(path2, ["--version"], { timeout: 3000, stdio: "ignore" }), withheldDrives, DECLARATION, REVIEW_DIRECTORY_FS;
58438
+ var FFMPEG_BIN = "ffmpeg", spawnVersion = (path2) => {
58439
+ const { file, args: args2 } = spawnTargetFor(path2, ["--version"]);
58440
+ return spawnSync(file, args2, { timeout: 3000, stdio: "ignore", windowsHide: true });
58441
+ }, withheldDrives, DECLARATION, REVIEW_DIRECTORY_FS;
58414
58442
  var init_review_ticket = __esm(() => {
58415
58443
  init_src();
58416
58444
  init_paths();
@@ -58420,6 +58448,7 @@ var init_review_ticket = __esm(() => {
58420
58448
  init_agent_stream();
58421
58449
  init_git();
58422
58450
  init_review_findings();
58451
+ init_resolve_executable();
58423
58452
  init_kill_registry();
58424
58453
  init_recorder_reap();
58425
58454
  init_recorder_reap();
@@ -65398,16 +65427,17 @@ function handlePtyExit(ctx, feature, session, meta, exitCode) {
65398
65427
  if (session.runtime === "codex" && feature) {
65399
65428
  noteResolvedMerge(ctx, session, feature).catch(() => {});
65400
65429
  }
65430
+ let resumeFailureReported;
65401
65431
  if (diedBeforeLive && meta.resumeSessionId) {
65402
- emitForSession(ctx, session, {
65403
- type: "session.resume_failed",
65404
- message: `resume failed for ${session.kind} \u2014 the session exited before starting (code ${exitCode ?? "unknown"}); the previous conversation is still resumable`,
65405
- data: {
65406
- sessionId: session.id,
65407
- resumeSessionId: meta.resumeSessionId,
65408
- exitCode: exitCode ?? null
65409
- }
65410
- });
65432
+ const resumeSessionId = meta.resumeSessionId;
65433
+ const report = (background) => emitResumeFailed(ctx, session, resumeSessionId, exitCode, background);
65434
+ if (meta.findBackgroundSession) {
65435
+ resumeFailureReported = meta.findBackgroundSession(resumeSessionId).catch(() => {
65436
+ return;
65437
+ }).then(report);
65438
+ } else {
65439
+ report(undefined);
65440
+ }
65411
65441
  }
65412
65442
  if (session.purpose === "self-plan" && feature) {
65413
65443
  const current2 = getFeatureRow(ctx, feature.id);
@@ -65423,19 +65453,36 @@ function handlePtyExit(ctx, feature, session, meta, exitCode) {
65423
65453
  message: ptyExitMessage(exitCode),
65424
65454
  data: { sessionId: session.id, exitCode: exitCode ?? null }
65425
65455
  });
65456
+ return resumeFailureReported;
65457
+ }
65458
+ function emitResumeFailed(ctx, session, resumeSessionId, exitCode, background) {
65459
+ const exited = `resume failed for ${session.kind} \u2014 the session exited before starting (code ${exitCode ?? "unknown"})`;
65460
+ const short = background?.shortId;
65461
+ emitForSession(ctx, session, {
65462
+ type: "session.resume_failed",
65463
+ message: short ? `${exited}: its conversation is running as a Claude Code background session (${short}), which cannot be resumed here. ` + `Run \`claude attach ${short}\` to look at it, then \`claude stop ${short}\` (stopping keeps the conversation), then relaunch.` : `${exited}; the previous conversation is still resumable`,
65464
+ data: {
65465
+ sessionId: session.id,
65466
+ resumeSessionId,
65467
+ exitCode: exitCode ?? null,
65468
+ ...short ? { backgroundSessionId: short } : {}
65469
+ }
65470
+ });
65426
65471
  }
65427
65472
  function spawnEmbeddedPty(ctx, feature, session, worktreePath, runtime5, spec, meta = {}) {
65428
65473
  const { file, args: args2 } = spawnTargetFor(runtime5.resolveBinary(), spec.argv);
65429
65474
  const env = { ...process.env, ...spec.env };
65430
65475
  for (const key of spec.envScrub)
65431
65476
  delete env[key];
65477
+ const findBackgroundSession = runtime5.findBackgroundSession?.bind(runtime5);
65478
+ const exitMeta = findBackgroundSession ? { ...meta, findBackgroundSession } : meta;
65432
65479
  try {
65433
65480
  const entry = ptyRegistry().create({
65434
65481
  sessionId: session.id,
65435
65482
  cmd: file,
65436
65483
  args: args2,
65437
65484
  opts: { cwd: worktreePath, env, cols: 80, rows: 24, useConpty: true },
65438
- onExit: ({ exitCode }) => handlePtyExit(ctx, feature, session, meta, exitCode)
65485
+ onExit: ({ exitCode }) => void handlePtyExit(ctx, feature, session, exitMeta, exitCode)
65439
65486
  });
65440
65487
  if (feature)
65441
65488
  startDocsWatch(ctx, feature);
package/index.js CHANGED
@@ -4639,6 +4639,63 @@ var init_git = __esm(() => {
4639
4639
  ];
4640
4640
  });
4641
4641
 
4642
+ // src/doctor/system-exec.ts
4643
+ import { spawn as spawn4 } from "child_process";
4644
+ function createSystemExec(opts = {}) {
4645
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
4646
+ const containFn = opts.containFn ?? containHostProcess;
4647
+ return (command, args) => new Promise((resolve4) => {
4648
+ const { file, args: spawnArgs } = resolveSpawnTarget(command, args);
4649
+ let stdout = "";
4650
+ let stderr = "";
4651
+ let settled = false;
4652
+ const settle = (outcome) => {
4653
+ if (settled)
4654
+ return;
4655
+ settled = true;
4656
+ clearTimeout(timer);
4657
+ resolve4(outcome);
4658
+ };
4659
+ const child = spawn4(file, spawnArgs, { cwd: opts.cwd, windowsHide: true });
4660
+ const containment = child.pid === undefined ? null : containFn(child.pid);
4661
+ const timer = setTimeout(() => {
4662
+ if (containment)
4663
+ containment.kill();
4664
+ else if (child.pid !== undefined)
4665
+ killProcessTree(child.pid);
4666
+ const note = `${command} ${args.join(" ")} timed out after ${Math.round(timeoutMs / 1000)}s`;
4667
+ settle({
4668
+ ok: true,
4669
+ code: null,
4670
+ stdout,
4671
+ stderr: stderr ? `${stderr}
4672
+ ${note}` : note,
4673
+ timedOut: true
4674
+ });
4675
+ }, timeoutMs);
4676
+ child.stdout?.on("data", (d) => {
4677
+ stdout += d.toString();
4678
+ });
4679
+ child.stderr?.on("data", (d) => {
4680
+ stderr += d.toString();
4681
+ });
4682
+ child.on("error", (err) => {
4683
+ containment?.kill();
4684
+ settle({ ok: false, code: null, stdout, stderr: stderr || String(err) });
4685
+ });
4686
+ child.on("close", (code) => {
4687
+ containment?.kill();
4688
+ settle({ ok: true, code: code ?? 0, stdout, stderr });
4689
+ });
4690
+ });
4691
+ }
4692
+ var DEFAULT_EXEC_TIMEOUT_MS = 60000;
4693
+ var init_system_exec = __esm(() => {
4694
+ init_kill_tree();
4695
+ init_contain_host();
4696
+ init_resolve_executable();
4697
+ });
4698
+
4642
4699
  // src/launcher/edit-guard.ts
4643
4700
  import { isAbsolute, relative, resolve as resolve4 } from "path";
4644
4701
  function guardsEdits(kind) {
@@ -5519,8 +5576,27 @@ function buildClaudeArgs(input) {
5519
5576
  ...input.kickoffLine ? [input.kickoffLine] : []
5520
5577
  ];
5521
5578
  }
5522
- var CC_NESTING_ENV, KICKOFF_LINES, CLAUDE_BIN = "claude", claudeRuntime;
5579
+ function findBackgroundSessionIn(listing, ccSessionId) {
5580
+ if (!Array.isArray(listing))
5581
+ return;
5582
+ const held = listing.find((entry) => typeof entry === "object" && entry !== null && entry.sessionId === ccSessionId && entry.kind === "background");
5583
+ if (!held)
5584
+ return;
5585
+ return { sessionId: ccSessionId, shortId: typeof held.id === "string" ? held.id : ccSessionId.slice(0, 8) };
5586
+ }
5587
+ async function listClaudeAgents() {
5588
+ try {
5589
+ const out = await createSystemExec({ timeoutMs: AGENTS_LISTING_TIMEOUT_MS })(CLAUDE_BIN, ["agents", "--json"]);
5590
+ if (!out.ok || out.timedOut)
5591
+ return;
5592
+ return JSON.parse(out.stdout);
5593
+ } catch {
5594
+ return;
5595
+ }
5596
+ }
5597
+ var CC_NESTING_ENV, KICKOFF_LINES, AGENTS_LISTING_TIMEOUT_MS = 1e4, CLAUDE_BIN = "claude", claudeRuntime;
5523
5598
  var init_claude = __esm(() => {
5599
+ init_system_exec();
5524
5600
  init_resolve_executable();
5525
5601
  init_artifacts();
5526
5602
  init_skills_root();
@@ -5570,13 +5646,17 @@ var init_claude = __esm(() => {
5570
5646
  }),
5571
5647
  env: {
5572
5648
  RUNCASTLE_SESSION_ID: input.session.id,
5573
- RUNCASTLE_SERVER_URL: input.serverUrl
5649
+ RUNCASTLE_SERVER_URL: input.serverUrl,
5650
+ CLAUDE_CODE_DISABLE_AGENT_VIEW: "1"
5574
5651
  },
5575
5652
  envScrub: CC_NESTING_ENV
5576
5653
  };
5577
5654
  },
5578
5655
  kickoffLine(kind) {
5579
5656
  return KICKOFF_LINES[kind];
5657
+ },
5658
+ async findBackgroundSession(ccSessionId) {
5659
+ return findBackgroundSessionIn(await listClaudeAgents(), ccSessionId);
5580
5660
  }
5581
5661
  };
5582
5662
  });
@@ -8223,7 +8303,7 @@ var init_chat_branch = __esm(() => {
8223
8303
  });
8224
8304
 
8225
8305
  // src/workflows/kill-registry.ts
8226
- import { spawn as spawn4 } from "child_process";
8306
+ import { spawn as spawn5 } from "child_process";
8227
8307
  function log2(msg) {
8228
8308
  console.error(`[kill-registry] ${msg}`);
8229
8309
  }
@@ -8240,7 +8320,7 @@ function runDockerCommand(args) {
8240
8320
  const timer = setTimeout(() => settle(false), DOCKER_COMMAND_TIMEOUT_MS);
8241
8321
  timer.unref?.();
8242
8322
  try {
8243
- const child = spawn4("docker", args, { windowsHide: true, stdio: "ignore" });
8323
+ const child = spawn5("docker", args, { windowsHide: true, stdio: "ignore" });
8244
8324
  child.on("exit", (code) => settle(code === 0));
8245
8325
  child.on("close", (code) => settle(code === 0));
8246
8326
  child.on("error", () => settle(false));
@@ -8371,63 +8451,6 @@ var init_kill_registry = __esm(() => {
8371
8451
  REGISTRY_KEY2 = Symbol.for("runcastle.kill.registry");
8372
8452
  });
8373
8453
 
8374
- // src/doctor/system-exec.ts
8375
- import { spawn as spawn5 } from "child_process";
8376
- function createSystemExec(opts = {}) {
8377
- const timeoutMs = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
8378
- const containFn = opts.containFn ?? containHostProcess;
8379
- return (command, args) => new Promise((resolve9) => {
8380
- const { file, args: spawnArgs } = resolveSpawnTarget(command, args);
8381
- let stdout = "";
8382
- let stderr = "";
8383
- let settled = false;
8384
- const settle = (outcome) => {
8385
- if (settled)
8386
- return;
8387
- settled = true;
8388
- clearTimeout(timer);
8389
- resolve9(outcome);
8390
- };
8391
- const child = spawn5(file, spawnArgs, { cwd: opts.cwd, windowsHide: true });
8392
- const containment = child.pid === undefined ? null : containFn(child.pid);
8393
- const timer = setTimeout(() => {
8394
- if (containment)
8395
- containment.kill();
8396
- else if (child.pid !== undefined)
8397
- killProcessTree(child.pid);
8398
- const note = `${command} ${args.join(" ")} timed out after ${Math.round(timeoutMs / 1000)}s`;
8399
- settle({
8400
- ok: true,
8401
- code: null,
8402
- stdout,
8403
- stderr: stderr ? `${stderr}
8404
- ${note}` : note,
8405
- timedOut: true
8406
- });
8407
- }, timeoutMs);
8408
- child.stdout?.on("data", (d) => {
8409
- stdout += d.toString();
8410
- });
8411
- child.stderr?.on("data", (d) => {
8412
- stderr += d.toString();
8413
- });
8414
- child.on("error", (err) => {
8415
- containment?.kill();
8416
- settle({ ok: false, code: null, stdout, stderr: stderr || String(err) });
8417
- });
8418
- child.on("close", (code) => {
8419
- containment?.kill();
8420
- settle({ ok: true, code: code ?? 0, stdout, stderr });
8421
- });
8422
- });
8423
- }
8424
- var DEFAULT_EXEC_TIMEOUT_MS = 60000;
8425
- var init_system_exec = __esm(() => {
8426
- init_kill_tree();
8427
- init_contain_host();
8428
- init_resolve_executable();
8429
- });
8430
-
8431
8454
  // src/services/settings.ts
8432
8455
  import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
8433
8456
  import { dirname as dirname6 } from "path";
@@ -57733,7 +57756,8 @@ function runCommand2(bin, args2) {
57733
57756
  const timer = setTimeout(() => settle(false), COMMAND_TIMEOUT_MS);
57734
57757
  timer.unref?.();
57735
57758
  try {
57736
- const child = spawn8(bin, [...args2], { windowsHide: true, stdio: "ignore" });
57759
+ const target = spawnTargetFor(bin, [...args2]);
57760
+ const child = spawn8(target.file, target.args, { windowsHide: true, stdio: "ignore" });
57737
57761
  child.on("exit", (code) => settle(code === 0));
57738
57762
  child.on("close", (code) => settle(code === 0));
57739
57763
  child.on("error", () => settle(false));
@@ -57799,6 +57823,7 @@ async function reapRecorder(ticketId, options = {}) {
57799
57823
  var AGENT_BROWSER_BIN = "agent-browser", DEFAULT_TIMEOUT_MS2 = 1e4, POLL_INTERVAL_MS = 100, COMMAND_TIMEOUT_MS = 5000, REAL_DEPS2;
57800
57824
  var init_recorder_reap = __esm(() => {
57801
57825
  init_paths();
57826
+ init_resolve_executable();
57802
57827
  REAL_DEPS2 = {
57803
57828
  findBrowser: () => findOnPath(AGENT_BROWSER_BIN),
57804
57829
  fileExists: existsSync16,
@@ -58409,7 +58434,10 @@ function couldNotReview(ticket, reason, digest) {
58409
58434
  **${headline2}**` : `**${headline2}**`
58410
58435
  };
58411
58436
  }
58412
- var FFMPEG_BIN = "ffmpeg", spawnVersion = (path2) => spawnSync(path2, ["--version"], { timeout: 3000, stdio: "ignore" }), withheldDrives, DECLARATION, REVIEW_DIRECTORY_FS;
58437
+ var FFMPEG_BIN = "ffmpeg", spawnVersion = (path2) => {
58438
+ const { file, args: args2 } = spawnTargetFor(path2, ["--version"]);
58439
+ return spawnSync(file, args2, { timeout: 3000, stdio: "ignore", windowsHide: true });
58440
+ }, withheldDrives, DECLARATION, REVIEW_DIRECTORY_FS;
58413
58441
  var init_review_ticket = __esm(() => {
58414
58442
  init_src();
58415
58443
  init_paths();
@@ -58419,6 +58447,7 @@ var init_review_ticket = __esm(() => {
58419
58447
  init_agent_stream();
58420
58448
  init_git();
58421
58449
  init_review_findings();
58450
+ init_resolve_executable();
58422
58451
  init_kill_registry();
58423
58452
  init_recorder_reap();
58424
58453
  init_recorder_reap();
@@ -65397,16 +65426,17 @@ function handlePtyExit(ctx, feature, session, meta, exitCode) {
65397
65426
  if (session.runtime === "codex" && feature) {
65398
65427
  noteResolvedMerge(ctx, session, feature).catch(() => {});
65399
65428
  }
65429
+ let resumeFailureReported;
65400
65430
  if (diedBeforeLive && meta.resumeSessionId) {
65401
- emitForSession(ctx, session, {
65402
- type: "session.resume_failed",
65403
- message: `resume failed for ${session.kind} \u2014 the session exited before starting (code ${exitCode ?? "unknown"}); the previous conversation is still resumable`,
65404
- data: {
65405
- sessionId: session.id,
65406
- resumeSessionId: meta.resumeSessionId,
65407
- exitCode: exitCode ?? null
65408
- }
65409
- });
65431
+ const resumeSessionId = meta.resumeSessionId;
65432
+ const report = (background) => emitResumeFailed(ctx, session, resumeSessionId, exitCode, background);
65433
+ if (meta.findBackgroundSession) {
65434
+ resumeFailureReported = meta.findBackgroundSession(resumeSessionId).catch(() => {
65435
+ return;
65436
+ }).then(report);
65437
+ } else {
65438
+ report(undefined);
65439
+ }
65410
65440
  }
65411
65441
  if (session.purpose === "self-plan" && feature) {
65412
65442
  const current2 = getFeatureRow(ctx, feature.id);
@@ -65422,19 +65452,36 @@ function handlePtyExit(ctx, feature, session, meta, exitCode) {
65422
65452
  message: ptyExitMessage(exitCode),
65423
65453
  data: { sessionId: session.id, exitCode: exitCode ?? null }
65424
65454
  });
65455
+ return resumeFailureReported;
65456
+ }
65457
+ function emitResumeFailed(ctx, session, resumeSessionId, exitCode, background) {
65458
+ const exited = `resume failed for ${session.kind} \u2014 the session exited before starting (code ${exitCode ?? "unknown"})`;
65459
+ const short = background?.shortId;
65460
+ emitForSession(ctx, session, {
65461
+ type: "session.resume_failed",
65462
+ message: short ? `${exited}: its conversation is running as a Claude Code background session (${short}), which cannot be resumed here. ` + `Run \`claude attach ${short}\` to look at it, then \`claude stop ${short}\` (stopping keeps the conversation), then relaunch.` : `${exited}; the previous conversation is still resumable`,
65463
+ data: {
65464
+ sessionId: session.id,
65465
+ resumeSessionId,
65466
+ exitCode: exitCode ?? null,
65467
+ ...short ? { backgroundSessionId: short } : {}
65468
+ }
65469
+ });
65425
65470
  }
65426
65471
  function spawnEmbeddedPty(ctx, feature, session, worktreePath, runtime5, spec, meta = {}) {
65427
65472
  const { file, args: args2 } = spawnTargetFor(runtime5.resolveBinary(), spec.argv);
65428
65473
  const env = { ...process.env, ...spec.env };
65429
65474
  for (const key of spec.envScrub)
65430
65475
  delete env[key];
65476
+ const findBackgroundSession = runtime5.findBackgroundSession?.bind(runtime5);
65477
+ const exitMeta = findBackgroundSession ? { ...meta, findBackgroundSession } : meta;
65431
65478
  try {
65432
65479
  const entry = ptyRegistry().create({
65433
65480
  sessionId: session.id,
65434
65481
  cmd: file,
65435
65482
  args: args2,
65436
65483
  opts: { cwd: worktreePath, env, cols: 80, rows: 24, useConpty: true },
65437
- onExit: ({ exitCode }) => handlePtyExit(ctx, feature, session, meta, exitCode)
65484
+ onExit: ({ exitCode }) => void handlePtyExit(ctx, feature, session, exitMeta, exitCode)
65438
65485
  });
65439
65486
  if (feature)
65440
65487
  startDocsWatch(ctx, feature);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcastle",
3
- "version": "1.4.7",
3
+ "version": "1.4.8",
4
4
  "description": "Burn tickets into shipped features with Claude Code.",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "type": "module",