@rallycry/conveyor-agent 10.7.3 → 10.7.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.
@@ -1540,52 +1540,6 @@ async function backupOtherBranches(cwd, currentBranch) {
1540
1540
  return count;
1541
1541
  }
1542
1542
 
1543
- // src/setup/git-ready.ts
1544
- import { access, stat } from "fs/promises";
1545
- var DEFAULT_FAILED_PATH = "/workspaces/.conveyor-git-failed";
1546
- var DEFAULT_TIMEOUT_MS = 6e5;
1547
- var DEFAULT_POLL_MS = 200;
1548
- async function fileExists(path4) {
1549
- try {
1550
- await access(path4);
1551
- const s = await stat(path4);
1552
- return s.isFile();
1553
- } catch {
1554
- return false;
1555
- }
1556
- }
1557
- function awaitGitReady(opts = {}) {
1558
- const markerPath = opts.markerPath ?? process.env.CONVEYOR_GIT_READY_MARKER;
1559
- if (!markerPath) {
1560
- return Promise.resolve("not-gated");
1561
- }
1562
- const failedPath = opts.failedPath ?? process.env.CONVEYOR_GIT_FAILED_MARKER ?? DEFAULT_FAILED_PATH;
1563
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1564
- const pollMs = opts.pollMs ?? DEFAULT_POLL_MS;
1565
- opts.onLog?.(`waiting for workspace git (marker=${markerPath})`);
1566
- return pollForMarkers(markerPath, failedPath, timeoutMs, pollMs, opts.onLog);
1567
- }
1568
- async function pollForMarkers(markerPath, failedPath, timeoutMs, pollMs, onLog) {
1569
- const deadline = Date.now() + timeoutMs;
1570
- for (; ; ) {
1571
- if (await fileExists(markerPath)) {
1572
- onLog?.("workspace git ready");
1573
- return "ready";
1574
- }
1575
- if (await fileExists(failedPath)) {
1576
- onLog?.("workspace git preparation failed (marker present)");
1577
- return "failed";
1578
- }
1579
- if (Date.now() >= deadline) {
1580
- onLog?.(`workspace git not ready after ${timeoutMs}ms \u2014 giving up`);
1581
- return "timeout";
1582
- }
1583
- await new Promise((resolve) => {
1584
- setTimeout(resolve, pollMs);
1585
- });
1586
- }
1587
- }
1588
-
1589
1543
  // src/runner/work-preservation/index.ts
1590
1544
  import { rm as rm3 } from "fs/promises";
1591
1545
 
@@ -1599,7 +1553,7 @@ function selectRestoreSource(probe) {
1599
1553
  // src/runner/work-preservation/snapshot-tar.ts
1600
1554
  import { execFile as execFile2 } from "child_process";
1601
1555
  import { createReadStream, createWriteStream } from "fs";
1602
- import { mkdtemp, rm, stat as stat2, writeFile } from "fs/promises";
1556
+ import { mkdtemp, rm, stat, writeFile } from "fs/promises";
1603
1557
  import { tmpdir } from "os";
1604
1558
  import path from "path";
1605
1559
  import { pipeline } from "stream/promises";
@@ -1691,7 +1645,7 @@ async function buildSnapshotTar(cwd) {
1691
1645
  const tarPath = path.join(staging, "snapshot.tar.gz");
1692
1646
  await pipeline(createReadStream(rawTarPath), createGzip(), createWriteStream(tarPath));
1693
1647
  await rm(rawTarPath, { force: true });
1694
- const { size } = await stat2(tarPath);
1648
+ const { size } = await stat(tarPath);
1695
1649
  return {
1696
1650
  tarPath,
1697
1651
  sizeBytes: size,
@@ -4389,7 +4343,7 @@ async function removeConveyorCredentials(env = process.env) {
4389
4343
  }
4390
4344
 
4391
4345
  // src/harness/pty/pty-support.ts
4392
- import { stat as stat3 } from "fs/promises";
4346
+ import { stat as stat2 } from "fs/promises";
4393
4347
  var MAX_DIAGNOSTIC_OUTPUT = 4e3;
4394
4348
  var MAX_BETWEEN_TURN_BUFFER = 500;
4395
4349
  var SUBMIT_SETTLE_MS = 300;
@@ -4449,9 +4403,9 @@ function extractSpawn(mod) {
4449
4403
  }
4450
4404
  async function loadPtySpawn() {
4451
4405
  const mod = await import("node-pty");
4452
- const spawn3 = extractSpawn(mod);
4453
- if (!spawn3) throw new Error("node-pty: spawn export not found");
4454
- return spawn3;
4406
+ const spawn2 = extractSpawn(mod);
4407
+ if (!spawn2) throw new Error("node-pty: spawn export not found");
4408
+ return spawn2;
4455
4409
  }
4456
4410
  function inheritedEnv(socketPath) {
4457
4411
  const env = {};
@@ -4488,7 +4442,7 @@ function sleep3(ms) {
4488
4442
  }
4489
4443
  async function transcriptSize(path4) {
4490
4444
  try {
4491
- return (await stat3(path4)).size;
4445
+ return (await stat2(path4)).size;
4492
4446
  } catch {
4493
4447
  return 0;
4494
4448
  }
@@ -4953,8 +4907,8 @@ var PtySession = class {
4953
4907
  // server doesn't load).
4954
4908
  ...this.mcpConfigPath ? { mcpConfigPath: this.mcpConfigPath } : {}
4955
4909
  });
4956
- const spawn3 = await loadPtySpawn();
4957
- const pty = spawn3(spec.file, spec.args, {
4910
+ const spawn2 = await loadPtySpawn();
4911
+ const pty = spawn2(spec.file, spec.args, {
4958
4912
  name: "xterm-color",
4959
4913
  cols: this.cols,
4960
4914
  rows: this.rows,
@@ -5606,7 +5560,7 @@ function formatIncidents(incidents) {
5606
5560
  }
5607
5561
 
5608
5562
  // src/execution/tag-context-resolver.ts
5609
- import { readFile as readFile2, readdir, stat as stat4 } from "fs/promises";
5563
+ import { readFile as readFile2, readdir, stat as stat3 } from "fs/promises";
5610
5564
  var TYPE_PRIORITY = { rule: 0, file: 1, folder: 2, doc: 3 };
5611
5565
  var SUMMARY_SCAN_CHARS = 4e3;
5612
5566
  var SUMMARY_MAX_CHARS = 160;
@@ -5680,7 +5634,7 @@ async function readFileSummary(filePath) {
5680
5634
  if (isBinaryPath(filePath)) return null;
5681
5635
  let mtimeMs;
5682
5636
  try {
5683
- const st = await stat4(filePath);
5637
+ const st = await stat3(filePath);
5684
5638
  mtimeMs = st.mtimeMs;
5685
5639
  } catch {
5686
5640
  return null;
@@ -5702,7 +5656,7 @@ async function readFolderListing(folderPath) {
5702
5656
  try {
5703
5657
  let mtimeMs;
5704
5658
  try {
5705
- const st = await stat4(folderPath);
5659
+ const st = await stat3(folderPath);
5706
5660
  mtimeMs = st.mtimeMs;
5707
5661
  } catch {
5708
5662
  return null;
@@ -7306,7 +7260,7 @@ function buildMutationTools(connection, config) {
7306
7260
  }
7307
7261
 
7308
7262
  // src/tools/attachment-tools.ts
7309
- import { readFile as readFile3, stat as stat5 } from "fs/promises";
7263
+ import { readFile as readFile3, stat as stat4 } from "fs/promises";
7310
7264
  import { basename, extname, isAbsolute, join as join5 } from "path";
7311
7265
  import { z as z7 } from "zod";
7312
7266
  var IMAGE_MIME_BY_EXT = {
@@ -7333,7 +7287,7 @@ function buildUploadAttachmentTool(connection, config) {
7333
7287
  `Unsupported file type "${extname(filePath) || "(none)"}". Supported: ${Object.keys(IMAGE_MIME_BY_EXT).join(", ")}`
7334
7288
  );
7335
7289
  }
7336
- const info = await stat5(filePath).catch(() => null);
7290
+ const info = await stat4(filePath).catch(() => null);
7337
7291
  if (!info?.isFile()) {
7338
7292
  return textResult(`File not found: ${filePath}`);
7339
7293
  }
@@ -9650,10 +9604,21 @@ function readAgentVersion() {
9650
9604
  return null;
9651
9605
  }
9652
9606
 
9607
+ // src/execution/usage-sampler.ts
9608
+ import { existsSync as existsSync3 } from "fs";
9609
+
9653
9610
  // src/usage/parse-usage.ts
9611
+ var ESC = "\\u001b";
9612
+ var ANSI_CSI2 = new RegExp(`${ESC}\\[[0-9;?]*[ -/]*[@-~]`, "g");
9613
+ var ANSI_OSC = new RegExp(`${ESC}\\][^\\u0007${ESC}]*(?:\\u0007|${ESC}\\\\)`, "g");
9614
+ var BAR_GLYPHS = /[─-▟]/g;
9615
+ function normalizeUsageText(stdout) {
9616
+ return stdout.replace(ANSI_CSI2, "").replace(ANSI_OSC, "").replace(BAR_GLYPHS, " ").replace(/\r/g, "");
9617
+ }
9654
9618
  function parseUsageGauges(stdout) {
9655
- const session = stdout.match(/Current session:\s*(\d+(?:\.\d+)?)%\s*used/i);
9656
- const weekly = [...stdout.matchAll(/Current week[^:\n]*:\s*(\d+(?:\.\d+)?)%\s*used/gi)];
9619
+ const text = normalizeUsageText(stdout);
9620
+ const session = text.match(/Current session[^%\n]*?(\d+(?:\.\d+)?)\s*%(?:\s*used)?/i);
9621
+ const weekly = [...text.matchAll(/Current week[^%\n]*?(\d+(?:\.\d+)?)\s*%(?:\s*used)?/gi)];
9657
9622
  return {
9658
9623
  sessionUsage: session ? Number(session[1]) / 100 : null,
9659
9624
  weeklyUsage: weekly.length ? Math.max(...weekly.map((m) => Number(m[1]))) / 100 : null
@@ -9661,54 +9626,116 @@ function parseUsageGauges(stdout) {
9661
9626
  }
9662
9627
 
9663
9628
  // src/usage/run-probe.ts
9664
- import { spawn } from "child_process";
9665
- var PROBE_TIMEOUT_MS = 15e3;
9629
+ var PROBE_TIMEOUT_MS = 3e4;
9630
+ var FIRST_SEND_MS = 5e3;
9631
+ var RESEND_INTERVAL_MS = 4e3;
9632
+ var MAX_SENDS = 5;
9633
+ var RENDER_SETTLE_MS = 900;
9666
9634
  function buildProbeEnv(env = process.env) {
9667
- const { CLAUDE_CODE_OAUTH_TOKEN: _oauth, ANTHROPIC_API_KEY: _apiKey, ...rest } = env;
9668
- return rest;
9669
- }
9670
- function runUsageProbe(binary = resolveClaudeBinary(), args = ["-p", "/usage"]) {
9635
+ const clean = {};
9636
+ for (const [key, value] of Object.entries(env)) {
9637
+ if (typeof value === "string") clean[key] = value;
9638
+ }
9639
+ delete clean.CLAUDE_CODE_OAUTH_TOKEN;
9640
+ delete clean.ANTHROPIC_API_KEY;
9641
+ return clean;
9642
+ }
9643
+ function panelRendering(buf) {
9644
+ return /Current session/i.test(buf) && /%/.test(buf);
9645
+ }
9646
+ var UsageProbeRun = class {
9647
+ constructor(resolve, timing) {
9648
+ this.resolve = resolve;
9649
+ this.timing = timing;
9650
+ }
9651
+ resolve;
9652
+ timing;
9653
+ buf = "";
9654
+ settled = false;
9655
+ sends = 0;
9656
+ child = null;
9657
+ hardTimer = null;
9658
+ resendTimer = null;
9659
+ settleTimer = null;
9660
+ start(child, timeoutMs) {
9661
+ this.child = child;
9662
+ child.onData((data) => this.onData(data));
9663
+ child.onExit(() => this.finishBestEffort());
9664
+ this.hardTimer = setTimeout(() => this.finishBestEffort(), timeoutMs);
9665
+ this.hardTimer.unref?.();
9666
+ const first = setTimeout(() => this.trySend(), this.timing.firstSendMs);
9667
+ first.unref?.();
9668
+ this.resendTimer = setInterval(() => this.trySend(), this.timing.resendIntervalMs);
9669
+ this.resendTimer.unref?.();
9670
+ }
9671
+ trySend() {
9672
+ if (this.settled || this.sends >= MAX_SENDS) return;
9673
+ this.sends += 1;
9674
+ try {
9675
+ this.child?.write("/usage\r");
9676
+ } catch {
9677
+ }
9678
+ }
9679
+ onData(chunk) {
9680
+ this.buf += chunk;
9681
+ if (this.settleTimer || !panelRendering(this.buf)) return;
9682
+ this.settleTimer = setTimeout(() => this.finish(this.buf), this.timing.settleMs);
9683
+ }
9684
+ finishBestEffort() {
9685
+ this.finish(panelRendering(this.buf) ? this.buf : "");
9686
+ }
9687
+ finish(out) {
9688
+ if (this.settled) return;
9689
+ this.settled = true;
9690
+ if (this.hardTimer) clearTimeout(this.hardTimer);
9691
+ if (this.resendTimer) clearInterval(this.resendTimer);
9692
+ if (this.settleTimer) clearTimeout(this.settleTimer);
9693
+ try {
9694
+ this.child?.kill();
9695
+ } catch {
9696
+ }
9697
+ this.resolve(out);
9698
+ }
9699
+ };
9700
+ async function runUsageProbe(deps = {}) {
9701
+ let spawn2 = deps.spawn;
9702
+ if (!spawn2) {
9703
+ try {
9704
+ spawn2 = await loadPtySpawn();
9705
+ } catch {
9706
+ return "";
9707
+ }
9708
+ }
9709
+ const binary = deps.binary ?? resolveClaudeBinary();
9710
+ const cwd = deps.cwd ?? process.cwd();
9711
+ const timeoutMs = deps.timeoutMs ?? PROBE_TIMEOUT_MS;
9712
+ const timing = {
9713
+ firstSendMs: deps.firstSendMs ?? FIRST_SEND_MS,
9714
+ resendIntervalMs: deps.resendIntervalMs ?? RESEND_INTERVAL_MS,
9715
+ settleMs: deps.settleMs ?? RENDER_SETTLE_MS
9716
+ };
9671
9717
  return new Promise((resolve) => {
9672
- let stdout = "";
9673
- let settled = false;
9674
- const finish = (out) => {
9675
- if (settled) return;
9676
- settled = true;
9677
- resolve(out);
9678
- };
9679
9718
  let child;
9680
9719
  try {
9681
- child = spawn(binary, args, { stdio: ["ignore", "pipe", "ignore"], env: buildProbeEnv() });
9720
+ child = spawn2(binary, [], {
9721
+ name: "xterm-256color",
9722
+ cols: 120,
9723
+ rows: 45,
9724
+ cwd,
9725
+ env: buildProbeEnv()
9726
+ });
9682
9727
  } catch {
9683
- finish("");
9728
+ resolve("");
9684
9729
  return;
9685
9730
  }
9686
- const timer = setTimeout(() => {
9687
- try {
9688
- child.kill("SIGKILL");
9689
- } catch {
9690
- }
9691
- finish("");
9692
- }, PROBE_TIMEOUT_MS);
9693
- timer.unref?.();
9694
- child.stdout?.on("data", (d) => {
9695
- stdout += d.toString();
9696
- });
9697
- child.on("error", () => {
9698
- clearTimeout(timer);
9699
- finish("");
9700
- });
9701
- child.on("close", (code) => {
9702
- clearTimeout(timer);
9703
- finish(code === 0 ? stdout : "");
9704
- });
9731
+ new UsageProbeRun(resolve, timing).start(child, timeoutMs);
9705
9732
  });
9706
9733
  }
9707
9734
 
9708
9735
  // src/execution/usage-sampler.ts
9709
9736
  var logger4 = createServiceLogger("usage-sampler");
9710
- async function sampleKeyUsage(token, probe = runUsageProbe) {
9711
- if (!token) return [];
9737
+ async function sampleKeyUsage(token, probe = () => runUsageProbe(), hasSubscriptionCredentials = () => existsSync3(claudeCredentialsPath())) {
9738
+ if (!token && !hasSubscriptionCredentials()) return [];
9712
9739
  try {
9713
9740
  const stdout = await probe();
9714
9741
  const { sessionUsage, weeklyUsage } = parseUsageGauges(stdout);
@@ -9734,6 +9761,76 @@ async function sampleKeyUsage(token, probe = runUsageProbe) {
9734
9761
  }
9735
9762
  }
9736
9763
 
9764
+ // src/setup/git-ready.ts
9765
+ import { access, stat as stat5 } from "fs/promises";
9766
+ var DEFAULT_FAILED_PATH = "/workspaces/.conveyor-git-failed";
9767
+ var DEFAULT_TIMEOUT_MS = 6e5;
9768
+ var DEFAULT_POLL_MS = 200;
9769
+ async function fileExists(path4) {
9770
+ try {
9771
+ await access(path4);
9772
+ const s = await stat5(path4);
9773
+ return s.isFile();
9774
+ } catch {
9775
+ return false;
9776
+ }
9777
+ }
9778
+ function awaitGitReady(opts = {}) {
9779
+ if (opts.signal?.aborted) return Promise.reject(abortError());
9780
+ const markerPath = opts.markerPath ?? process.env.CONVEYOR_GIT_READY_MARKER;
9781
+ if (!markerPath) {
9782
+ return Promise.resolve("not-gated");
9783
+ }
9784
+ const failedPath = opts.failedPath ?? process.env.CONVEYOR_GIT_FAILED_MARKER ?? DEFAULT_FAILED_PATH;
9785
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
9786
+ const pollMs = opts.pollMs ?? DEFAULT_POLL_MS;
9787
+ opts.onLog?.(`waiting for workspace git (marker=${markerPath})`);
9788
+ return pollForMarkers(markerPath, failedPath, timeoutMs, pollMs, opts.onLog, opts.signal);
9789
+ }
9790
+ function abortError() {
9791
+ const error = new Error("Operation aborted");
9792
+ error.name = "AbortError";
9793
+ return error;
9794
+ }
9795
+ function abortableDelay(ms, signal) {
9796
+ if (!signal) {
9797
+ return new Promise((resolve) => {
9798
+ setTimeout(resolve, ms);
9799
+ });
9800
+ }
9801
+ if (signal.aborted) return Promise.reject(abortError());
9802
+ return new Promise((resolve, reject) => {
9803
+ const timer = setTimeout(() => {
9804
+ signal.removeEventListener("abort", onAbort);
9805
+ resolve();
9806
+ }, ms);
9807
+ const onAbort = () => {
9808
+ clearTimeout(timer);
9809
+ reject(abortError());
9810
+ };
9811
+ signal.addEventListener("abort", onAbort, { once: true });
9812
+ });
9813
+ }
9814
+ async function pollForMarkers(markerPath, failedPath, timeoutMs, pollMs, onLog, signal) {
9815
+ const deadline = Date.now() + timeoutMs;
9816
+ for (; ; ) {
9817
+ if (signal?.aborted) throw abortError();
9818
+ if (await fileExists(markerPath)) {
9819
+ onLog?.("workspace git ready");
9820
+ return "ready";
9821
+ }
9822
+ if (await fileExists(failedPath)) {
9823
+ onLog?.("workspace git preparation failed (marker present)");
9824
+ return "failed";
9825
+ }
9826
+ if (Date.now() >= deadline) {
9827
+ onLog?.(`workspace git not ready after ${timeoutMs}ms \u2014 giving up`);
9828
+ return "timeout";
9829
+ }
9830
+ await abortableDelay(pollMs, signal);
9831
+ }
9832
+ }
9833
+
9737
9834
  // src/runner/port-discovery.ts
9738
9835
  import { readFile as readFile4 } from "fs/promises";
9739
9836
  import { execFile as execFile3 } from "child_process";
@@ -9847,6 +9944,7 @@ var PortDiscovery = class {
9847
9944
  timer = null;
9848
9945
  ticking = false;
9849
9946
  disabled = false;
9947
+ stopped = false;
9850
9948
  /** Set when the confirmed set changed (or a report failed) — cleared only
9851
9949
  * after a successful report, so transient RPC failures retry next tick. */
9852
9950
  reportPending = false;
@@ -9864,8 +9962,9 @@ var PortDiscovery = class {
9864
9962
  }
9865
9963
  /** Take the baseline scan and start polling. Safe to call once. */
9866
9964
  async start() {
9867
- if (this.timer || this.disabled) return;
9965
+ if (this.timer || this.disabled || this.stopped) return;
9868
9966
  const baseline = await this.scanSafe();
9967
+ if (this.stopped) return;
9869
9968
  if (baseline === null) {
9870
9969
  this.disabled = true;
9871
9970
  this.log("Port discovery disabled: no listening-socket source available");
@@ -9876,6 +9975,7 @@ var PortDiscovery = class {
9876
9975
  this.timer.unref?.();
9877
9976
  }
9878
9977
  stop() {
9978
+ this.stopped = true;
9879
9979
  if (this.timer) {
9880
9980
  clearInterval(this.timer);
9881
9981
  this.timer = null;
@@ -10071,13 +10171,16 @@ var SessionRunner = class _SessionRunner {
10071
10171
  /** Max consecutive gate-deferred ticks (~2 min each) before flushing anyway. */
10072
10172
  static MAX_GATE_SKIPS = 10;
10073
10173
  /** Runtime preview-port poller (v3 dynamic port discovery). */
10074
- portDiscovery = null;
10174
+ portDiscovery;
10075
10175
  /** Main event-loop lag measurement, shared with the heartbeat worker. */
10076
10176
  loopLag = new LoopLagMonitor();
10077
- constructor(config, callbacks) {
10177
+ constructor(config, callbacks, deps = {}) {
10078
10178
  this.config = config;
10079
10179
  this.callbacks = callbacks;
10080
10180
  this.connection = new AgentConnection(config.connection);
10181
+ this.portDiscovery = deps.portDiscovery ?? new PortDiscovery({
10182
+ report: (ports) => this.connection.reportDiscoveredPorts(ports)
10183
+ });
10081
10184
  const initialMode = config.agentMode ?? (config.runnerMode === "pm" ? config.isAuto ? "auto" : "discovery" : "building");
10082
10185
  this.mode = new ModeController(initialMode, config.runnerMode, config.isAuto);
10083
10186
  const lifecycleConfig = { ...DEFAULT_LIFECYCLE_CONFIG, ...config.lifecycle };
@@ -10142,16 +10245,18 @@ var SessionRunner = class _SessionRunner {
10142
10245
  this.pendingMessages.push({ content: msg.content, userId: msg.userId });
10143
10246
  }
10144
10247
  }
10145
- this.portDiscovery = new PortDiscovery({
10146
- report: (ports) => this.connection.reportDiscoveredPorts(ports)
10147
- });
10148
- void this.portDiscovery.start().catch(() => {
10149
- });
10248
+ if (this.stopped) return false;
10249
+ try {
10250
+ await this.portDiscovery.start();
10251
+ } catch {
10252
+ }
10253
+ if (this.stopped) return false;
10150
10254
  const agentVersion = readAgentVersion();
10151
10255
  if (agentVersion) {
10152
10256
  this.connection.call("notifyAgentVersion", { sessionId: this.sessionId, agentVersion }).catch(() => {
10153
10257
  });
10154
10258
  }
10259
+ return true;
10155
10260
  }
10156
10261
  /**
10157
10262
  * Run the main agent lifecycle: fetch context, resolve mode, execute, loop.
@@ -10267,7 +10372,7 @@ var SessionRunner = class _SessionRunner {
10267
10372
  }
10268
10373
  /** Convenience wrapper: connect() then run(). */
10269
10374
  async start() {
10270
- await this.connect();
10375
+ if (!await this.connect()) return;
10271
10376
  await this.run();
10272
10377
  }
10273
10378
  // ── Core loop ──────────────────────────────────────────────────────
@@ -10661,7 +10766,7 @@ var SessionRunner = class _SessionRunner {
10661
10766
  this.queryBridge?.stop();
10662
10767
  void this.queryBridge?.dispose().catch(() => {
10663
10768
  });
10664
- this.portDiscovery?.stop();
10769
+ this.portDiscovery.stop();
10665
10770
  this.loopLag.stop();
10666
10771
  this.lifecycle.destroy();
10667
10772
  this.connection.disconnect();
@@ -10873,7 +10978,7 @@ var SessionRunner = class _SessionRunner {
10873
10978
  process.stderr.write(`[conveyor-agent] Shutdown: reason=${finalState}
10874
10979
  `);
10875
10980
  this.connection.sendEvent({ type: "shutdown", reason: finalState });
10876
- this.portDiscovery?.stop();
10981
+ this.portDiscovery.stop();
10877
10982
  this.loopLag.stop();
10878
10983
  this.lifecycle.destroy();
10879
10984
  try {
@@ -10971,29 +11076,84 @@ function loadConveyorConfig() {
10971
11076
  }
10972
11077
 
10973
11078
  // src/setup/commands.ts
10974
- import { spawn as spawn2, execSync } from "child_process";
10975
- function runSetupCommand(cmd, cwd, onOutput) {
11079
+ import { spawn, execSync } from "child_process";
11080
+ var PROCESS_TERMINATION_GRACE_MS = 5e3;
11081
+ function abortError2() {
11082
+ const error = new Error("Operation aborted");
11083
+ error.name = "AbortError";
11084
+ return error;
11085
+ }
11086
+ function signalProcessGroup(child, signal) {
11087
+ try {
11088
+ if (child.pid) process.kill(-child.pid, signal);
11089
+ else child.kill(signal);
11090
+ } catch {
11091
+ try {
11092
+ child.kill(signal);
11093
+ } catch {
11094
+ }
11095
+ }
11096
+ }
11097
+ function terminateProcessGroup(child, graceMs = PROCESS_TERMINATION_GRACE_MS) {
11098
+ if (child.exitCode !== null) return Promise.resolve();
11099
+ return new Promise((resolve) => {
11100
+ let settled = false;
11101
+ const finish = () => {
11102
+ if (settled) return;
11103
+ settled = true;
11104
+ clearTimeout(timer);
11105
+ child.removeListener("exit", finish);
11106
+ resolve();
11107
+ };
11108
+ const timer = setTimeout(() => {
11109
+ signalProcessGroup(child, "SIGKILL");
11110
+ finish();
11111
+ }, graceMs);
11112
+ timer.unref();
11113
+ child.once("exit", finish);
11114
+ signalProcessGroup(child, "SIGTERM");
11115
+ });
11116
+ }
11117
+ function runSetupCommand(cmd, cwd, onOutput, signal) {
11118
+ if (signal?.aborted) return Promise.reject(abortError2());
10976
11119
  return new Promise((resolve, reject) => {
10977
- const child = spawn2("sh", ["-c", cmd], {
11120
+ const child = spawn("sh", ["-c", cmd], {
10978
11121
  cwd,
10979
11122
  stdio: ["ignore", "pipe", "pipe"],
11123
+ detached: true,
10980
11124
  env: { ...process.env }
10981
11125
  });
11126
+ let settled = false;
11127
+ let aborting = false;
11128
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
11129
+ const settle = (error) => {
11130
+ if (settled) return;
11131
+ settled = true;
11132
+ cleanup();
11133
+ if (error) reject(error);
11134
+ else resolve();
11135
+ };
11136
+ const onAbort = () => {
11137
+ if (settled || aborting) return;
11138
+ aborting = true;
11139
+ void terminateProcessGroup(child).then(() => settle(abortError2()));
11140
+ };
11141
+ signal?.addEventListener("abort", onAbort, { once: true });
11142
+ if (signal?.aborted) onAbort();
10982
11143
  child.stdout.on("data", (chunk) => {
11144
+ if (aborting || signal?.aborted) return;
10983
11145
  onOutput("stdout", chunk.toString());
10984
11146
  });
10985
11147
  child.stderr.on("data", (chunk) => {
11148
+ if (aborting || signal?.aborted) return;
10986
11149
  onOutput("stderr", chunk.toString());
10987
11150
  });
10988
11151
  child.on("close", (code) => {
10989
- if (code === 0) {
10990
- resolve();
10991
- } else {
10992
- reject(new Error(`Setup command exited with code ${code}`));
10993
- }
11152
+ if (aborting) return;
11153
+ settle(code === 0 ? void 0 : new Error(`Setup command exited with code ${code}`));
10994
11154
  });
10995
11155
  child.on("error", (err) => {
10996
- reject(err);
11156
+ if (!aborting) settle(err);
10997
11157
  });
10998
11158
  });
10999
11159
  }
@@ -11013,7 +11173,7 @@ function runAuthTokenCommand(cmd, userEmail, cwd) {
11013
11173
  }
11014
11174
  }
11015
11175
  function runStartCommand(cmd, cwd, onOutput) {
11016
- const child = spawn2("sh", ["-c", cmd], {
11176
+ const child = spawn("sh", ["-c", cmd], {
11017
11177
  cwd,
11018
11178
  stdio: ["ignore", "pipe", "pipe"],
11019
11179
  detached: true,
@@ -11070,6 +11230,7 @@ export {
11070
11230
  resolveSessionStart,
11071
11231
  sampleKeyUsage,
11072
11232
  awaitGitReady,
11233
+ PortDiscovery,
11073
11234
  uploadSnapshotToGcs,
11074
11235
  captureSnapshot,
11075
11236
  startPeriodic,
@@ -11080,9 +11241,10 @@ export {
11080
11241
  loadForwardPorts,
11081
11242
  buildSessionPreviewPorts,
11082
11243
  loadConveyorConfig,
11244
+ terminateProcessGroup,
11083
11245
  runSetupCommand,
11084
11246
  runAuthTokenCommand,
11085
11247
  runStartCommand,
11086
11248
  unshallowRepo
11087
11249
  };
11088
- //# sourceMappingURL=chunk-UOHROQ6A.js.map
11250
+ //# sourceMappingURL=chunk-CWTQXS34.js.map