@miraland-labs/conduit-bridge 0.16.154 → 0.16.156

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/dist/driver.js CHANGED
@@ -1885,6 +1885,41 @@ const MAX_AGENT_OUTPUT = 8 * 1024 * 1024;
1885
1885
  export const STDIO_DRAIN_MS = 2_000;
1886
1886
  /** How long a stopped child may take to die before the run reports what it already has. */
1887
1887
  const CHILD_STOP_GRACE_MS = 20_000;
1888
+ /**
1889
+ * An agent runs as the leader of its own process group, so what it starts in the background (a dev
1890
+ * server, a test watcher, a database) is stopped with it. Before, only the agent itself was
1891
+ * stopped: its background processes outlived the run and held files and ports on the computer
1892
+ * (acme-sim SV1b, 2026-10-10). Windows has no process groups; there only the agent is stopped.
1893
+ */
1894
+ const OWN_PROCESS_GROUP = process.platform !== "win32";
1895
+ /** Process groups of the agents running now, stopped when the runner itself is stopped. */
1896
+ const runningGroups = new Set();
1897
+ let shutdownHookInstalled = false;
1898
+ function signalGroup(pid, signal) {
1899
+ try {
1900
+ process.kill(-pid, signal);
1901
+ }
1902
+ catch {
1903
+ // ESRCH: every process of the group is already gone.
1904
+ }
1905
+ }
1906
+ /**
1907
+ * systemd stops a whole cgroup, but launchd stops only the runner's own process group, which an
1908
+ * agent no longer shares. Stop the agents' groups first, then let the signal end the runner as it
1909
+ * did before. A runner killed with SIGKILL cannot do this.
1910
+ */
1911
+ function installShutdownHook() {
1912
+ if (shutdownHookInstalled)
1913
+ return;
1914
+ shutdownHookInstalled = true;
1915
+ for (const signal of ["SIGTERM", "SIGINT"]) {
1916
+ process.once(signal, () => {
1917
+ for (const pid of runningGroups)
1918
+ signalGroup(pid, "SIGKILL");
1919
+ process.kill(process.pid, signal);
1920
+ });
1921
+ }
1922
+ }
1888
1923
  export function execute(executable, args, cwd, timeoutMs, fuel, fuelSource = "conduit", stdinText, signal, extraEnv,
1889
1924
  /** The whole environment, for a run that must see less than an agent does (a probe instrument). */
1890
1925
  env) {
@@ -1893,7 +1928,19 @@ env) {
1893
1928
  cwd,
1894
1929
  stdio: [stdinText !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
1895
1930
  env: env ?? boundedEnvironment(fuel, fuelSource, extraEnv),
1931
+ detached: OWN_PROCESS_GROUP,
1896
1932
  });
1933
+ const group = OWN_PROCESS_GROUP ? child.pid : undefined;
1934
+ if (group !== undefined) {
1935
+ installShutdownHook();
1936
+ runningGroups.add(group);
1937
+ }
1938
+ const stop = (signal) => {
1939
+ if (group !== undefined)
1940
+ signalGroup(group, signal);
1941
+ else
1942
+ child.kill(signal);
1943
+ };
1897
1944
  let stdout = "";
1898
1945
  let stderr = "";
1899
1946
  let timedOut = false;
@@ -1917,6 +1964,12 @@ env) {
1917
1964
  // behind can hold the other end, and a kept handle is one the runner never closes.
1918
1965
  child.stdout?.destroy();
1919
1966
  child.stderr?.destroy();
1967
+ // The agent has answered; whatever it left running in its group stops now.
1968
+ if (group !== undefined) {
1969
+ runningGroups.delete(group);
1970
+ signalGroup(group, "SIGTERM");
1971
+ setTimeout(() => signalGroup(group, "SIGKILL"), 5_000).unref?.();
1972
+ }
1920
1973
  resolve({
1921
1974
  code,
1922
1975
  stdout,
@@ -1928,8 +1981,8 @@ env) {
1928
1981
  // SIGTERM first, then SIGKILL after 10 s: a child that ignores the polite stop must not keep
1929
1982
  // the slot busy. The turn timer and the runner's abort use the same two steps.
1930
1983
  const stopChild = () => {
1931
- child.kill("SIGTERM");
1932
- setTimeout(() => child.kill("SIGKILL"), 10_000).unref?.();
1984
+ stop("SIGTERM");
1985
+ setTimeout(() => stop("SIGKILL"), 10_000).unref?.();
1933
1986
  // A child that is already gone cannot close a pipe another process holds open.
1934
1987
  setTimeout(() => settle(null), CHILD_STOP_GRACE_MS).unref?.();
1935
1988
  };
@@ -1958,6 +2011,8 @@ env) {
1958
2011
  if (settled)
1959
2012
  return;
1960
2013
  settled = true;
2014
+ if (group !== undefined)
2015
+ runningGroups.delete(group);
1961
2016
  clearTimeout(timer);
1962
2017
  signal?.removeEventListener("abort", stopChild);
1963
2018
  reject(error);
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { execFile } from "node:child_process";
7
7
  import { promisify } from "node:util";
8
- import { criterionKey, resolveCriterionReference } from "./driver.js";
8
+ import { boundedEnvironment, criterionKey, resolveCriterionReference } from "./driver.js";
9
9
  import { isBoundedVerificationCommand, isRunnableVerificationCommand } from "./execution-class.js";
10
10
  const execFileAsync = promisify(execFile);
11
11
  const TEST_EVIDENCE_DETAILS_MIN = 32;
@@ -388,7 +388,9 @@ export async function runBoundedVerificationCommand(command, workspace, timeoutM
388
388
  cwd: workspace,
389
389
  timeout: timeoutMs,
390
390
  maxBuffer: VERIFICATION_MAX_BUFFER_BYTES,
391
- env: process.env,
391
+ // The agent's environment, not the runner's: a gate that passes only with a variable the
392
+ // agent never sees ties the project to this computer (acme-sim DATABASE_URL, 2026-10-10).
393
+ env: boundedEnvironment(),
392
394
  });
393
395
  pending.child.stdout?.on("data", collect("stdout"));
394
396
  pending.child.stderr?.on("data", collect("stderr"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.154",
3
+ "version": "0.16.156",
4
4
  "description": "Conduit Bridge CLI \u2014 join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity / Grok Build agents for a Conduit organization",
5
5
  "type": "module",
6
6
  "bin": {