@link-assistant/hive-mind 2.12.0 → 2.12.2

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.
@@ -12,7 +12,6 @@ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
12
12
  * @see https://github.com/link-foundation/start
13
13
  * @see https://github.com/link-assistant/hive-mind/issues/380
14
14
  */
15
-
16
15
  import crypto from 'crypto';
17
16
  import { spawn } from 'node:child_process';
18
17
  import { describeChildExit } from './child-exit.lib.mjs';
@@ -22,9 +21,7 @@ import os from 'node:os';
22
21
  import path from 'node:path';
23
22
  import { isExecutingSessionStatus, isTerminalSessionStatus } from './session-status.lib.mjs';
24
23
  import { acquireFormalAiSidecarForTask, attachFormalAiTaskContainer, releaseFormalAiSidecarForTask } from './formal-ai-isolation.lib.mjs';
25
-
26
24
  let commandStreamDollarPromise = null;
27
-
28
25
  async function getCommandStreamDollar() {
29
26
  if (!commandStreamDollarPromise) {
30
27
  commandStreamDollarPromise = (async () => {
@@ -35,7 +32,6 @@ async function getCommandStreamDollar() {
35
32
  return $;
36
33
  })();
37
34
  }
38
-
39
35
  try {
40
36
  return await commandStreamDollarPromise;
41
37
  } catch (error) {
@@ -43,13 +39,8 @@ async function getCommandStreamDollar() {
43
39
  throw error;
44
40
  }
45
41
  }
46
-
47
- // Re-export the shared status predicates so existing callers that reach them via
48
- // the isolation-runner module (e.g. session-monitor's `runner.isExecutingSessionStatus`)
49
- // keep working. The canonical definitions live in session-status.lib.mjs so the
50
- // killed/terminated/oom vocabulary stays consistent everywhere (issue #1927).
42
+ // Re-export the shared status predicates so existing callers that reach them via the isolation-runner module (e.g. session-monitor's `runner.isExecutingSessionStatus`) keep working. The canonical definitions live in session-status.lib.mjs so the killed/terminated/oom vocabulary stays consistent everywhere (issue #1927).
51
43
  export { isExecutingSessionStatus, isTerminalSessionStatus, isKilledSessionStatus } from './session-status.lib.mjs';
52
-
53
44
  // Valid isolation backends
54
45
  const VALID_ISOLATION_BACKENDS = ['screen', 'tmux', 'docker'];
55
46
  const HIVE_MIND_IMAGE_REPO = 'konard/hive-mind';
@@ -57,41 +48,16 @@ const HIVE_MIND_DIND_IMAGE_REPO = 'konard/hive-mind-dind';
57
48
  const DEFAULT_HIVE_MIND_IMAGE_TAG = 'latest';
58
49
  const DOCKER_CONTAINER_HOME = '/home/box';
59
50
  const FORMAL_AI_COMPOSE_HOSTNAME = 'link-assistant-formal-ai';
60
- // Default path where the host Docker socket is bind-mounted inside a DinD
61
- // container so box's host-image passthrough can copy host images into the
62
- // nested daemon. Matches box's own DIND_HOST_DOCKER_SOCK default. The deploy
63
- // must mount it (`-v /var/run/docker.sock:/var/run/host-docker.sock:ro`) or the
64
- // nested daemon starts empty and the first isolated task pulls the full,
65
- // multi-gigabyte image. See issue #1914.
51
+ // Default path where the host Docker socket is bind-mounted inside a DinD container so box's host-image passthrough can copy host images into the nested daemon. Matches box's own DIND_HOST_DOCKER_SOCK default. The deploy must mount it (`-v /var/run/docker.sock:/var/run/host-docker.sock:ro`) or the nested daemon starts empty and the first isolated task pulls the full, multi-gigabyte image. See issue #1914.
66
52
  const DEFAULT_HOST_DOCKER_SOCK = '/var/run/host-docker.sock';
67
- // Force a POSIX shell for the inner command of Docker-isolated tasks. solve/
68
- // hive/task live on the image's baked-in PATH, so `sh -c` resolves them without
69
- // needing a login shell. Forcing the shell (instead of start's 'auto') also
70
- // skips start's shell-detection probe, which would otherwise `docker run` a
71
- // throwaway container — booting the dind image's dockerd entrypoint — purely to
72
- // check whether bash exists. See issue #1914.
53
+ // Force a POSIX shell for the inner command of Docker-isolated tasks. solve/ hive/task live on the image's baked-in PATH, so `sh -c` resolves them without needing a login shell. Forcing the shell (instead of start's 'auto') also skips start's shell-detection probe, which would otherwise `docker run` a throwaway container — booting the dind image's dockerd entrypoint — purely to check whether bash exists. See issue #1914.
73
54
  const DOCKER_ISOLATION_SHELL = 'sh';
74
- // Free-space floor (GiB) below which the preflight warns that an impending
75
- // isolation-image pull may fail with `no space left on device`. The Hive Mind
76
- // isolation images are well over 30 GB extracted, so a host/nested daemon with
77
- // less headroom than this cannot safely pull one. Diagnostic only — never
78
- // blocks startup. See issue #1914.
55
+ // Free-space floor (GiB) below which the preflight warns that an impending isolation-image pull may fail with `no space left on device`. The Hive Mind isolation images are well over 30 GB extracted, so a host/nested daemon with less headroom than this cannot safely pull one. Diagnostic only — never blocks startup. See issue #1914.
79
56
  const DOCKER_ISOLATION_LOW_DISK_GIB = 40;
80
- // Docker-only start gate used to capture the container writable-layer baseline
81
- // before the task command begins cloning or generating files. The parent
82
- // releases the gate immediately after `docker inspect --size`; the fallback
83
- // keeps the task from hanging forever if the parent exits at the wrong time.
57
+ // Docker-only start gate used to capture the container writable-layer baseline before the task command begins cloning or generating files. The parent releases the gate immediately after `docker inspect --size`; the fallback keeps the task from hanging forever if the parent exits at the wrong time.
84
58
  const DOCKER_START_GATE_WAIT_TENTHS = 300;
85
- // Sentinel start-command's detached docker logger records when it cannot capture
86
- // the container's real exit code. A terminal `$ --status` carrying this value is
87
- // ambiguous — the container may still be running — so we cross-check it against
88
- // a live `docker inspect` before concluding the session finished. See #1939.
89
- // The upstream emission of this premature sentinel was fixed in
90
- // start-command 0.29.1 (link-foundation/start#136), which the Hive Mind images
91
- // now pin; this cross-check is retained as defense-in-depth so an older `$` on
92
- // an operator's PATH cannot resurrect the bug.
59
+ // Sentinel start-command's detached docker logger records when it cannot capture the container's real exit code. A terminal `$ --status` carrying this value is ambiguous — the container may still be running — so we cross-check it against a live `docker inspect` before concluding the session finished. See #1939. The upstream emission of this premature sentinel was fixed in start-command 0.29.1 (link-foundation/start#136), which the Hive Mind images now pin; this cross-check is retained as defense-in-depth so an older `$` on an operator's PATH cannot resurrect the bug.
93
60
  const DOCKER_UNKNOWN_EXIT_CODE = -1;
94
-
95
61
  function normalizeProcessIds(value) {
96
62
  if (!value || typeof value !== 'object') return {};
97
63
  const out = {};
@@ -101,43 +67,35 @@ function normalizeProcessIds(value) {
101
67
  }
102
68
  return out;
103
69
  }
104
-
105
70
  function normalizeTool(tool) {
106
71
  return String(tool || 'claude')
107
72
  .trim()
108
73
  .toLowerCase();
109
74
  }
110
-
111
75
  function shellQuote(value) {
112
76
  const stringValue = String(value);
113
77
  if (stringValue === '') return "''";
114
78
  return `'${stringValue.replaceAll("'", "'\\''")}'`;
115
79
  }
116
-
117
80
  function buildShellCommand(command, args = []) {
118
81
  return [command, ...args].map(shellQuote).join(' ');
119
82
  }
120
-
121
83
  function buildDockerStartGatePath(sessionId) {
122
84
  return sessionId ? `/tmp/hive-mind-disk-baseline-${sessionId}` : null;
123
85
  }
124
-
125
86
  function buildDockerStartGatedCommand(taskCommand, sessionId) {
126
87
  const gatePath = buildDockerStartGatePath(sessionId);
127
88
  if (!gatePath) return taskCommand;
128
89
  return `gate=${shellQuote(gatePath)}; i=0; while [ ! -e "$gate" ] && [ "$i" -lt ${DOCKER_START_GATE_WAIT_TENTHS} ]; do i=$((i+1)); sleep 0.1; done; rm -f "$gate"; exec ${taskCommand}`;
129
90
  }
130
-
131
91
  function shouldRunPrivilegedDockerIsolation(image, env = process.env) {
132
92
  return String(env.HIVE_MIND_IMAGE_VARIANT || '').toLowerCase() === 'dind' || String(image || '').includes('hive-mind-dind');
133
93
  }
134
-
135
94
  function maybeAddMount(mounts, source, target, existsSync) {
136
95
  if (!source) return;
137
96
  if (!existsSync(source)) return;
138
97
  mounts.push({ source, target });
139
98
  }
140
-
141
99
  /**
142
100
  * Resolve the tag used for the Docker isolation image.
143
101
  *
@@ -153,7 +111,6 @@ export function resolveDockerIsolationImageTag({ env = process.env } = {}) {
153
111
  const explicit = String(env.HIVE_MIND_DOCKER_ISOLATION_IMAGE_TAG || '').trim();
154
112
  return explicit || DEFAULT_HIVE_MIND_IMAGE_TAG;
155
113
  }
156
-
157
114
  /**
158
115
  * Pick the Docker image used for `--isolation docker`.
159
116
  *
@@ -169,7 +126,6 @@ export function getDockerIsolationImage({ env = process.env } = {}) {
169
126
  const repo = String(env.HIVE_MIND_IMAGE_VARIANT || '').toLowerCase() === 'dind' ? HIVE_MIND_DIND_IMAGE_REPO : HIVE_MIND_IMAGE_REPO;
170
127
  return `${repo}:${resolveDockerIsolationImageTag({ env })}`;
171
128
  }
172
-
173
129
  /**
174
130
  * Resolve the path where the host Docker socket is expected to be mounted inside
175
131
  * a DinD container. box's entrypoint reads this socket to copy host images into
@@ -181,7 +137,6 @@ export function resolveHostDockerSock({ env = process.env } = {}) {
181
137
  const explicit = String(env.DIND_HOST_DOCKER_SOCK || '').trim();
182
138
  return explicit || DEFAULT_HOST_DOCKER_SOCK;
183
139
  }
184
-
185
140
  /**
186
141
  * Build host auth mounts for a Docker-isolated task.
187
142
  *
@@ -197,31 +152,20 @@ export function resolveHostDockerSock({ env = process.env } = {}) {
197
152
  export function getDockerIsolationAuthMounts({ tool = 'claude', env = process.env, homeDir = os.homedir(), existsSync = fs.existsSync } = {}) {
198
153
  const mounts = [];
199
154
  const normalizedTool = normalizeTool(tool);
200
-
201
155
  maybeAddMount(mounts, env.GH_CONFIG_DIR || path.join(homeDir, '.config', 'gh'), path.join(DOCKER_CONTAINER_HOME, '.config', 'gh'), existsSync);
202
-
203
- // Git identity (tool-agnostic, required for commits). Honor the same env vars
204
- // git itself reads for an alternate global config location (GIT_CONFIG_GLOBAL)
205
- // and the XDG base dir, falling back to the conventional `~/.gitconfig` and
206
- // `~/.config/git`. Missing host paths are skipped, so a container image that
207
- // already bakes a git identity is left untouched. See issue #1939.
156
+ // Git identity (tool-agnostic, required for commits). Honor the same env vars git itself reads for an alternate global config location (GIT_CONFIG_GLOBAL) and the XDG base dir, falling back to the conventional `~/.gitconfig` and `~/.config/git`. Missing host paths are skipped, so a container image that already bakes a git identity is left untouched. See issue #1939.
208
157
  maybeAddMount(mounts, env.GIT_CONFIG_GLOBAL || path.join(homeDir, '.gitconfig'), path.join(DOCKER_CONTAINER_HOME, '.gitconfig'), existsSync);
209
158
  maybeAddMount(mounts, env.XDG_CONFIG_HOME ? path.join(env.XDG_CONFIG_HOME, 'git') : path.join(homeDir, '.config', 'git'), path.join(DOCKER_CONTAINER_HOME, '.config', 'git'), existsSync);
210
-
211
159
  if (normalizedTool === 'codex') {
212
160
  maybeAddMount(mounts, path.join(homeDir, '.codex'), path.join(DOCKER_CONTAINER_HOME, '.codex'), existsSync);
213
- // Issue #2074: Codex also discovers persistent user Agent Skills from
214
- // ~/.agents/skills. Propagate that standard location alongside .codex so
215
- // direct and Docker-isolated solver sessions expose the same capabilities.
161
+ // Issue #2074: Codex also discovers persistent user Agent Skills from ~/.agents/skills. Propagate that standard location alongside .codex so direct and Docker-isolated solver sessions expose the same capabilities.
216
162
  maybeAddMount(mounts, path.join(homeDir, '.agents'), path.join(DOCKER_CONTAINER_HOME, '.agents'), existsSync);
217
163
  } else if (normalizedTool === 'claude') {
218
164
  maybeAddMount(mounts, path.join(homeDir, '.claude'), path.join(DOCKER_CONTAINER_HOME, '.claude'), existsSync);
219
165
  maybeAddMount(mounts, path.join(homeDir, '.claude.json'), path.join(DOCKER_CONTAINER_HOME, '.claude.json'), existsSync);
220
166
  }
221
-
222
167
  return mounts;
223
168
  }
224
-
225
169
  /**
226
170
  * Resolve the image-variant marker recorded inside the isolated container.
227
171
  * A `hive-mind-dind` image is always the dind variant; otherwise fall back to
@@ -230,7 +174,6 @@ export function getDockerIsolationAuthMounts({ tool = 'claude', env = process.en
230
174
  function resolveImageVariant(image, env = process.env) {
231
175
  return image.includes('hive-mind-dind') ? 'dind' : env.HIVE_MIND_IMAGE_VARIANT || 'regular';
232
176
  }
233
-
234
177
  /**
235
178
  * Resolve an outer Compose HTTP service before handing its origin to a nested
236
179
  * Docker daemon. The nested daemon has its own DNS namespace, but it can route
@@ -241,23 +184,19 @@ function resolveImageVariant(image, env = process.env) {
241
184
  export async function resolveFormalAiIsolationEnv(env = process.env, { lookup = lookupHost } = {}) {
242
185
  const baseUrl = env.HIVE_MIND_FORMAL_AI_BASE_URL;
243
186
  if (!baseUrl) return env;
244
-
245
187
  let parsed;
246
188
  try {
247
189
  parsed = new URL(baseUrl);
248
190
  } catch {
249
191
  return env;
250
192
  }
251
-
252
193
  if (parsed.protocol !== 'http:' || parsed.hostname !== FORMAL_AI_COMPOSE_HOSTNAME) {
253
194
  return env;
254
195
  }
255
-
256
196
  try {
257
197
  const addresses = await lookup(parsed.hostname, { all: true, verbatim: true });
258
198
  const selected = addresses.find(candidate => candidate.family === 4) || addresses[0];
259
199
  if (!selected?.address) return env;
260
-
261
200
  const host = selected.family === 6 ? `[${selected.address}]` : selected.address;
262
201
  return {
263
202
  ...env,
@@ -268,7 +207,6 @@ export async function resolveFormalAiIsolationEnv(env = process.env, { lookup =
268
207
  return env;
269
208
  }
270
209
  }
271
-
272
210
  /**
273
211
  * Build the `$` (start-command) arguments that launch a Docker-isolated task
274
212
  * using start-command's NATIVE Docker backend (`$ --isolated docker`).
@@ -289,39 +227,25 @@ export async function resolveFormalAiIsolationEnv(env = process.env, { lookup =
289
227
  export function buildDockerIsolationStartArgs(command, args = [], options = {}) {
290
228
  const { sessionId, tool = 'claude', env = process.env, homeDir = os.homedir(), existsSync = fs.existsSync } = options;
291
229
  const image = getDockerIsolationImage({ env });
292
-
293
230
  const startArgs = ['--isolated', 'docker', '--image', image];
294
-
295
231
  if (shouldRunPrivilegedDockerIsolation(image, env)) {
296
232
  startArgs.push('--privileged');
297
233
  }
298
-
299
- // Force the inner shell so start-command does not probe the image to detect
300
- // one (see DOCKER_ISOLATION_SHELL).
234
+ // Force the inner shell so start-command does not probe the image to detect one (see DOCKER_ISOLATION_SHELL).
301
235
  startArgs.push('--shell', DOCKER_ISOLATION_SHELL);
302
-
303
- // The image already sets HOME=/home/box and WORKDIR /home/box; pass HOME
304
- // explicitly anyway so the credential mounts under /home/box resolve even if
305
- // a future image forgets to. start-command has no --workdir flag, so the
306
- // working directory comes from the image's WORKDIR.
236
+ // The image already sets HOME=/home/box and WORKDIR /home/box; pass HOME explicitly anyway so the credential mounts under /home/box resolve even if a future image forgets to. start-command has no --workdir flag, so the working directory comes from the image's WORKDIR.
307
237
  startArgs.push('-e', `HOME=${DOCKER_CONTAINER_HOME}`, '-e', `HIVE_MIND_PARENT_SESSION_ID=${sessionId || ''}`, '-e', `HIVE_MIND_IMAGE_VARIANT=${resolveImageVariant(image, env)}`);
308
-
309
- // A persistent Formal AI server normally runs beside the Telegram/root
310
- // container. Docker-isolated `/solve` jobs must receive the same endpoint;
311
- // otherwise the wrapper starts a per-job server and loses shared memory.
238
+ // A persistent Formal AI server normally runs beside the Telegram/root container. Docker-isolated `/solve` jobs must receive the same endpoint; otherwise the wrapper starts a per-job server and loses shared memory.
312
239
  if (env.HIVE_MIND_FORMAL_AI_BASE_URL) {
313
240
  startArgs.push('-e', `HIVE_MIND_FORMAL_AI_BASE_URL=${env.HIVE_MIND_FORMAL_AI_BASE_URL}`);
314
241
  }
315
-
316
242
  for (const mount of getDockerIsolationAuthMounts({ tool, env, homeDir, existsSync })) {
317
243
  startArgs.push('--volume', `${mount.source}:${mount.target}`);
318
244
  }
319
-
320
245
  const taskCommand = buildShellCommand(command, args);
321
246
  startArgs.push('--detached', '--session', sessionId, '--', buildDockerStartGatedCommand(taskCommand, sessionId));
322
247
  return startArgs;
323
248
  }
324
-
325
249
  export function buildStartCommandArgs(command, args = [], options = {}) {
326
250
  const { backend, sessionId } = options;
327
251
  if (backend === 'docker') {
@@ -329,17 +253,14 @@ export function buildStartCommandArgs(command, args = [], options = {}) {
329
253
  }
330
254
  return ['--isolated', backend, '--detached', '--session', sessionId, '--', buildShellCommand(command, args)];
331
255
  }
332
-
333
256
  async function runStartCommand(binPath, startCommandArgs) {
334
257
  return await new Promise(resolve => {
335
258
  const child = spawn(binPath, startCommandArgs, {
336
259
  stdio: ['ignore', 'pipe', 'pipe'],
337
260
  env: process.env,
338
261
  });
339
-
340
262
  let stdout = '';
341
263
  let stderr = '';
342
-
343
264
  child.stdout.on('data', data => {
344
265
  stdout += data.toString();
345
266
  });
@@ -353,8 +274,7 @@ async function runStartCommand(binPath, startCommandArgs) {
353
274
  error: error.message,
354
275
  });
355
276
  });
356
- // Issue #2135: keep `signal` - the captured session's child was killed by
357
- // one, and `code` alone was null.
277
+ // Issue #2135: keep `signal` - the captured session's child was killed by one, and `code` alone was null.
358
278
  child.on('close', (code, signal) => {
359
279
  const output = (stdout + (stderr ? `\n${stderr}` : '')).trim();
360
280
  if (code === 0) {
@@ -369,7 +289,6 @@ async function runStartCommand(binPath, startCommandArgs) {
369
289
  });
370
290
  });
371
291
  }
372
-
373
292
  /**
374
293
  * Generate a UUID v4 for unique session identification
375
294
  * @returns {string} UUID v4 string
@@ -377,7 +296,6 @@ async function runStartCommand(binPath, startCommandArgs) {
377
296
  export function generateSessionId() {
378
297
  return crypto.randomUUID();
379
298
  }
380
-
381
299
  /**
382
300
  * Parse output from `$ --status <session>`.
383
301
  *
@@ -393,7 +311,6 @@ export function parseSessionStatusOutput(output) {
393
311
  if (!raw) {
394
312
  return { exists: false, uuid: null, status: null, exitCode: null, startTime: null, endTime: null, currentTime: null, logPath: null, command: null, isolation: null, workingDirectory: null, sessionName: null, processIds: {}, oomKilled: null, raw: '' };
395
313
  }
396
-
397
314
  const normalizeBooleanField = value => {
398
315
  if (typeof value === 'boolean') return value;
399
316
  if (value === null || value === undefined) return null;
@@ -402,15 +319,10 @@ export function parseSessionStatusOutput(output) {
402
319
  if (['false', '0', 'no'].includes(normalized)) return false;
403
320
  return null;
404
321
  };
405
-
406
322
  try {
407
323
  const parsed = JSON.parse(raw);
408
324
  const data = Array.isArray(parsed) ? parsed[0] : parsed;
409
- // start-command (link-foundation/start) reports the isolation backend at
410
- // `options.isolated` in both JSON and links-notation output. Older
411
- // hypothetical layouts used `options.isolation` or a top-level `isolation`
412
- // field — keep accepting all three so we are tolerant of future renames.
413
- // See https://github.com/link-assistant/hive-mind/issues/1700.
325
+ // start-command (link-foundation/start) reports the isolation backend at `options.isolated` in both JSON and links-notation output. Older hypothetical layouts used `options.isolation` or a top-level `isolation` field — keep accepting all three so we are tolerant of future renames. See https://github.com/link-assistant/hive-mind/issues/1700.
414
326
  const isolationCandidate = (typeof data?.isolation === 'string' && data.isolation) || (typeof data?.options?.isolated === 'string' && data.options.isolated) || (typeof data?.options?.isolation === 'string' && data.options.isolation) || null;
415
327
  const topPid = Number(data?.pid);
416
328
  const processIds = normalizeProcessIds(data?.processIds);
@@ -435,7 +347,6 @@ export function parseSessionStatusOutput(output) {
435
347
  } catch {
436
348
  // Fall through to text parsing.
437
349
  }
438
-
439
350
  const firstLine =
440
351
  raw
441
352
  .split('\n')
@@ -446,13 +357,9 @@ export function parseSessionStatusOutput(output) {
446
357
  return match ? match[1].trim() : null;
447
358
  };
448
359
  const readBooleanField = name => normalizeBooleanField(readField(name));
449
-
450
360
  const status = readField('status')?.toLowerCase() || null;
451
361
  const exitCodeText = readField('exitCode');
452
- // `start-command` links-notation output nests the isolation backend under
453
- // `options` as `isolated <backend>` (not `isolation`). The leading indent
454
- // varies by depth, but `readField` is anchored with `^\s*` which already
455
- // matches indented lines. Older code only looked for `isolation`, which
362
+ // `start-command` links-notation output nests the isolation backend under `options` as `isolated <backend>` (not `isolation`). The leading indent varies by depth, but `readField` is anchored with `^\s*` which already matches indented lines. Older code only looked for `isolation`, which
456
363
  // returned null for every real session and made /log + /terminal_watch
457
364
  // reject screen/tmux/docker sessions. See issue #1700.
458
365
  const isolationText = readField('isolated') || readField('isolation');
@@ -462,7 +369,6 @@ export function parseSessionStatusOutput(output) {
462
369
  const number = Number(value);
463
370
  if (Number.isInteger(number) && number > 0) processIds[name] = number;
464
371
  }
465
-
466
372
  return {
467
373
  exists: Boolean(status || firstLine),
468
374
  uuid: readField('uuid') || firstLine,
@@ -481,7 +387,6 @@ export function parseSessionStatusOutput(output) {
481
387
  raw,
482
388
  };
483
389
  }
484
-
485
390
  /**
486
391
  * Decide whether a detached-docker exit code is "unknown" (not a real result).
487
392
  *
@@ -499,11 +404,9 @@ export function parseSessionStatusOutput(output) {
499
404
  export function isUnknownDockerExitCode(exitCode) {
500
405
  return exitCode === null || exitCode === undefined || Number(exitCode) === DOCKER_UNKNOWN_EXIT_CODE;
501
406
  }
502
-
503
407
  export function shouldFallbackToScreenStatus(statusResult) {
504
408
  return !statusResult?.exists || !statusResult?.status;
505
409
  }
506
-
507
410
  /**
508
411
  * Parse the footer start-command appends to every execution log when the wrapped
509
412
  * command exits. The footer is authoritative about the terminal exit code even
@@ -537,7 +440,6 @@ export function parseSessionExitFooter(text) {
537
440
  if (!last) return { finished: false, exitCode: null, endTime: null };
538
441
  return { finished: true, exitCode: Number(last[2]), endTime: last[1].trim() };
539
442
  }
540
-
541
443
  /**
542
444
  * Read the terminal exit code from the tail of a start-command execution log.
543
445
  *
@@ -579,7 +481,6 @@ export function readSessionExitFromLog(logPath, options = {}) {
579
481
  return { finished: false, exitCode: null, endTime: null };
580
482
  }
581
483
  }
582
-
583
484
  /**
584
485
  * Find the `$` CLI binary path
585
486
  * @returns {Promise<string|null>} Path to `$` binary or null
@@ -594,7 +495,6 @@ async function findStartCommandBinary() {
594
495
  return null;
595
496
  }
596
497
  }
597
-
598
498
  /**
599
499
  * Verbose post-launch diagnostics for a native docker-isolated session.
600
500
  *
@@ -629,7 +529,6 @@ async function logDockerIsolationPostLaunchDiagnostics(sessionId, env = process.
629
529
  // Diagnostics are best-effort; never let a probe failure affect the task.
630
530
  }
631
531
  }
632
-
633
532
  /**
634
533
  * Execute a command with isolation via `$` from start-command
635
534
  *
@@ -645,7 +544,6 @@ async function logDockerIsolationPostLaunchDiagnostics(sessionId, env = process.
645
544
  export async function executeWithIsolation(command, args, options = {}) {
646
545
  const { backend, verbose = false } = options;
647
546
  const sessionId = options.sessionId || generateSessionId();
648
-
649
547
  if (!VALID_ISOLATION_BACKENDS.includes(backend)) {
650
548
  return {
651
549
  success: false,
@@ -654,7 +552,6 @@ export async function executeWithIsolation(command, args, options = {}) {
654
552
  error: `Invalid isolation backend: '${backend}'. Must be one of: ${VALID_ISOLATION_BACKENDS.join(', ')}`,
655
553
  };
656
554
  }
657
-
658
555
  const binPath = await findStartCommandBinary();
659
556
  if (!binPath) {
660
557
  return {
@@ -665,12 +562,10 @@ export async function executeWithIsolation(command, args, options = {}) {
665
562
  error: 'start-command ($) not found',
666
563
  };
667
564
  }
668
-
669
565
  if (verbose) {
670
566
  console.log(`[VERBOSE] isolation-runner: Using $ binary at: ${binPath}`);
671
567
  console.log(`[VERBOSE] isolation-runner: Backend: ${backend}, Session ID: ${sessionId}`);
672
568
  }
673
-
674
569
  // Issue #2146 / PR #2147 review: a Formal AI task gets its own sidecar,
675
570
  // started on demand and reachable only over an internal Docker network. The
676
571
  // lease is taken before the container is launched so the endpoint is known
@@ -679,7 +574,6 @@ export async function executeWithIsolation(command, args, options = {}) {
679
574
  const hostEnv = options.env || process.env;
680
575
  const { sidecar, error: sidecarError } = await acquireFormalAiSidecarForTask({ backend, args, model: options.model ?? null, tool: options.tool ?? null, sessionId, env: hostEnv, verbose });
681
576
  if (sidecarError) return { success: false, sessionId, output: '', error: sidecarError };
682
-
683
577
  const taskEnv = sidecar ? { ...hostEnv, HIVE_MIND_FORMAL_AI_BASE_URL: sidecar.baseUrl } : hostEnv;
684
578
  const effectiveOptions =
685
579
  backend === 'docker'
@@ -689,7 +583,6 @@ export async function executeWithIsolation(command, args, options = {}) {
689
583
  }
690
584
  : options;
691
585
  const startCommandArgs = buildStartCommandArgs(command, args, { ...effectiveOptions, sessionId });
692
-
693
586
  if (verbose) {
694
587
  console.log(`[VERBOSE] isolation-runner: ${[binPath, ...startCommandArgs].map(shellQuote).join(' ')}`);
695
588
  if (backend === 'docker') {
@@ -705,15 +598,12 @@ export async function executeWithIsolation(command, args, options = {}) {
705
598
  console.log(`[VERBOSE] isolation-runner: Docker isolation git identity propagated: ${gitIdentityMounted ? 'yes' : 'no (host ~/.gitconfig missing — child may fail with "Git identity not configured", issue #1939)'}`);
706
599
  }
707
600
  }
708
-
709
601
  const result = await runStartCommand(binPath, startCommandArgs);
710
-
711
602
  if (verbose) {
712
603
  const stream = result.success ? console.log : console.error;
713
604
  stream(`[VERBOSE] isolation-runner: Output: ${result.output.substring(0, 500)}`);
714
605
  if (result.error) stream(`[VERBOSE] isolation-runner: Error: ${result.error}`);
715
606
  }
716
-
717
607
  let containerFilesystemStartBytes = null;
718
608
  let formalAiAttachError = null;
719
609
  if (result.success && backend === 'docker') {
@@ -732,7 +622,6 @@ export async function executeWithIsolation(command, args, options = {}) {
732
622
  await releaseDockerContainerStartGate(sessionId, verbose);
733
623
  }
734
624
  }
735
-
736
625
  if (sidecar && (!result.success || formalAiAttachError)) {
737
626
  // Fail closed: without the internal network the task cannot reach Formal
738
627
  // AI, and issue #2146 forbids falling back to another model.
@@ -742,7 +631,6 @@ export async function executeWithIsolation(command, args, options = {}) {
742
631
  return { success: false, sessionId, output: result.output, error: `Formal AI task container could not be attached to the internal Formal AI network, so the task was stopped instead of falling back to another model (issue #2146): ${formalAiAttachError}` };
743
632
  }
744
633
  }
745
-
746
634
  // Issue #1939: capture the freshly-launched docker session's reported status
747
635
  // and the live container state together, so the next iteration has the data to
748
636
  // diagnose a premature "executed/-1" status (problem #1) or a surprise image
@@ -750,7 +638,6 @@ export async function executeWithIsolation(command, args, options = {}) {
750
638
  if (verbose && backend === 'docker' && result.success) {
751
639
  await logDockerIsolationPostLaunchDiagnostics(sessionId, options.env || process.env);
752
640
  }
753
-
754
641
  if (result.success) {
755
642
  return {
756
643
  success: true,
@@ -759,7 +646,6 @@ export async function executeWithIsolation(command, args, options = {}) {
759
646
  containerFilesystemStartBytes,
760
647
  };
761
648
  }
762
-
763
649
  return {
764
650
  success: false,
765
651
  sessionId,
@@ -767,7 +653,6 @@ export async function executeWithIsolation(command, args, options = {}) {
767
653
  error: result.error,
768
654
  };
769
655
  }
770
-
771
656
  /**
772
657
  * Query the status of an isolated session via `$ --status <uuid>`
773
658
  *
@@ -783,17 +668,13 @@ export async function querySessionStatus(sessionId, verbose = false) {
783
668
  }
784
669
  return { exists: false, uuid: null, status: null, exitCode: null, startTime: null, endTime: null, currentTime: null, logPath: null, command: null, isolation: null, workingDirectory: null, sessionName: null, processIds: {}, raw: '' };
785
670
  }
786
-
787
671
  try {
788
672
  const $ = await getCommandStreamDollar();
789
673
  const result = await $({ mirror: false })`${binPath} --status ${sessionId} --output-format json`;
790
-
791
674
  const stdout = result.stdout?.toString().trim() || '';
792
-
793
675
  if (verbose) {
794
676
  console.log(`[VERBOSE] isolation-runner: Status query result: ${stdout.substring(0, 300)}`);
795
677
  }
796
-
797
678
  return parseSessionStatusOutput(stdout);
798
679
  } catch (error) {
799
680
  if (verbose) {
@@ -802,7 +683,6 @@ export async function querySessionStatus(sessionId, verbose = false) {
802
683
  return { exists: false, uuid: null, status: null, exitCode: null, startTime: null, endTime: null, currentTime: null, logPath: null, command: null, isolation: null, workingDirectory: null, sessionName: null, processIds: {}, raw: '' };
803
684
  }
804
685
  }
805
-
806
686
  /**
807
687
  * Parse output from `$ --list --output-format json`.
808
688
  *
@@ -824,7 +704,6 @@ export function parseSessionListOutput(output) {
824
704
  return [];
825
705
  }
826
706
  const records = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.executions) ? parsed.executions : Array.isArray(parsed?.sessions) ? parsed.sessions : parsed && typeof parsed === 'object' ? [parsed] : [];
827
-
828
707
  return records
829
708
  .map(data => {
830
709
  if (!data || typeof data !== 'object') return null;
@@ -843,7 +722,6 @@ export function parseSessionListOutput(output) {
843
722
  })
844
723
  .filter(Boolean);
845
724
  }
846
-
847
725
  /**
848
726
  * List all executions known to start-command via `$ --list --output-format json`.
849
727
  *
@@ -875,7 +753,6 @@ export async function listIsolationSessions(verbose = false) {
875
753
  return [];
876
754
  }
877
755
  }
878
-
879
756
  /**
880
757
  * Ask the `$` CLI to gracefully stop an isolated session by sending CTRL+C.
881
758
  *
@@ -899,7 +776,6 @@ export async function stopIsolatedSession(sessionId, verbose = false) {
899
776
  error: '`$` (start-command) binary not found on PATH. Install link-foundation/start to use /stop <UUID>.',
900
777
  };
901
778
  }
902
-
903
779
  try {
904
780
  const $ = await getCommandStreamDollar();
905
781
  const result = await $({ mirror: false })`${binPath} --stop ${sessionId}`;
@@ -925,7 +801,6 @@ export async function stopIsolatedSession(sessionId, verbose = false) {
925
801
  };
926
802
  }
927
803
  }
928
-
929
804
  /**
930
805
  * Check if a screen session exists via `screen -ls`.
931
806
  * Used as a fallback when `$ --status` fails to find or correctly track
@@ -951,7 +826,6 @@ export async function checkScreenSessionRunning(sessionName, verbose = false) {
951
826
  return false;
952
827
  }
953
828
  }
954
-
955
829
  /**
956
830
  * Check whether the Docker container backing a native `$ --isolated docker`
957
831
  * session is still running.
@@ -981,14 +855,12 @@ export async function checkDockerContainerRunning(containerName, verbose = false
981
855
  return false;
982
856
  }
983
857
  }
984
-
985
858
  export function parseDockerContainerWritableLayerSizeOutput(output) {
986
859
  const text = String(output || '').trim();
987
860
  if (!text) return null;
988
861
  const bytes = Number.parseInt(text.split(/\s+/)[0], 10);
989
862
  return Number.isFinite(bytes) && bytes >= 0 ? bytes : null;
990
863
  }
991
-
992
864
  /**
993
865
  * Best-effort size of a Docker task container's writable layer.
994
866
  *
@@ -1019,7 +891,6 @@ export async function getDockerContainerWritableLayerSize(containerName, verbose
1019
891
  return null;
1020
892
  }
1021
893
  }
1022
-
1023
894
  /**
1024
895
  * Release the Docker-only start gate after the writable-layer baseline has been
1025
896
  * captured. Best-effort: the gated task also has a timeout fallback.
@@ -1033,7 +904,6 @@ export async function releaseDockerContainerStartGate(containerName, verbose = f
1033
904
  if (!containerName || !gatePath) return false;
1034
905
  const releaseCommand = `touch ${shellQuote(gatePath)}`;
1035
906
  let lastError = null;
1036
-
1037
907
  for (let attempt = 1; attempt <= 5; attempt++) {
1038
908
  try {
1039
909
  const $ = await getCommandStreamDollar();
@@ -1047,14 +917,12 @@ export async function releaseDockerContainerStartGate(containerName, verbose = f
1047
917
  await new Promise(resolve => setTimeout(resolve, 200));
1048
918
  }
1049
919
  }
1050
-
1051
920
  if (verbose) {
1052
921
  const stderr = lastError?.stderr?.toString?.().trim();
1053
922
  console.log(`[VERBOSE] isolation-runner: could not release docker start gate for '${containerName}': ${stderr || lastError?.message || lastError}`);
1054
923
  }
1055
924
  return false;
1056
925
  }
1057
-
1058
926
  /**
1059
927
  * Best-effort removal for a Docker container backing a native
1060
928
  * `$ --isolated docker` session.
@@ -1073,7 +941,6 @@ export async function removeDockerContainer(containerName, verbose = false) {
1073
941
  if (!containerName) {
1074
942
  return { success: false, output: '', error: 'missing container name' };
1075
943
  }
1076
-
1077
944
  try {
1078
945
  const $ = await getCommandStreamDollar();
1079
946
  const result = await $({ mirror: false })`docker rm -f ${containerName}`;
@@ -1096,7 +963,6 @@ export async function removeDockerContainer(containerName, verbose = false) {
1096
963
  };
1097
964
  }
1098
965
  }
1099
-
1100
966
  /**
1101
967
  * Check whether a tmux session with the given name still exists.
1102
968
  * `tmux has-session -t <name>` exits 0 when it exists and non-zero otherwise,
@@ -1117,7 +983,6 @@ export async function checkTmuxSessionRunning(sessionName, verbose = false) {
1117
983
  return false;
1118
984
  }
1119
985
  }
1120
-
1121
986
  /**
1122
987
  * Directly probe whether the backend session/container is still alive, bypassing
1123
988
  * `$ --status`. This is the cross-check used to detect a session that
@@ -1136,7 +1001,6 @@ export async function checkBackendSessionAlive(sessionId, backend, verbose = fal
1136
1001
  if (backend === 'docker') return checkDockerContainerRunning(sessionId, verbose);
1137
1002
  return null;
1138
1003
  }
1139
-
1140
1004
  /**
1141
1005
  * Check whether an image is present in the local Docker daemon.
1142
1006
  *
@@ -1161,7 +1025,6 @@ export async function checkDockerImagePresent(image, verbose = false) {
1161
1025
  return false;
1162
1026
  }
1163
1027
  }
1164
-
1165
1028
  /**
1166
1029
  * Report the storage driver the (nested) Docker daemon is using.
1167
1030
  *
@@ -1190,7 +1053,6 @@ export async function checkDockerStorageDriver(verbose = false) {
1190
1053
  return null;
1191
1054
  }
1192
1055
  }
1193
-
1194
1056
  /**
1195
1057
  * Report the free space (in GiB) on the Docker daemon's data root.
1196
1058
  *
@@ -1218,7 +1080,6 @@ export async function checkDockerDiskSpace(verbose = false) {
1218
1080
  // Daemon unreachable: fall back to the conventional data root. If df then
1219
1081
  // fails on it (e.g. the path does not exist) we return null below.
1220
1082
  }
1221
-
1222
1083
  const $ = await getCommandStreamDollar();
1223
1084
  const df = await $({ mirror: false })`df -Pk ${dataRoot}`;
1224
1085
  // `df -P` guarantees one logical line per filesystem (no wrapping). The last
@@ -1238,7 +1099,6 @@ export async function checkDockerDiskSpace(verbose = false) {
1238
1099
  return null;
1239
1100
  }
1240
1101
  }
1241
-
1242
1102
  /**
1243
1103
  * Startup preflight for `--isolation docker`.
1244
1104
  *
@@ -1277,7 +1137,6 @@ export async function checkDockerDiskSpace(verbose = false) {
1277
1137
  */
1278
1138
  export async function preflightDockerIsolation(options = {}) {
1279
1139
  const { env = process.env, existsSync = fs.existsSync, verbose = false, logger = console, checkImagePresent = checkDockerImagePresent, checkStorageDriver = checkDockerStorageDriver, checkDiskSpace = checkDockerDiskSpace } = options;
1280
-
1281
1140
  const image = getDockerIsolationImage({ env });
1282
1141
  const sock = resolveHostDockerSock({ env });
1283
1142
  const isDind = shouldRunPrivilegedDockerIsolation(image, env);
@@ -1289,13 +1148,10 @@ export async function preflightDockerIsolation(options = {}) {
1289
1148
  // Unknown driver (probe returned null) is treated as ok — we only flag the
1290
1149
  // one driver known to overflow the disk, never block on missing information.
1291
1150
  const storageDriverOk = storageDriver !== 'vfs';
1292
-
1293
1151
  const result = { image, sock, socketMounted, imagePresent, isDind, storageDriver, storageDriverOk, diskAvailableGiB, ok: imagePresent, warnings: [] };
1294
1152
  const info = typeof logger.log === 'function' ? logger.log.bind(logger) : () => {};
1295
1153
  const warn = typeof logger.warn === 'function' ? logger.warn.bind(logger) : info;
1296
-
1297
1154
  const preload = `node scripts/preload-dind-isolation-image.mjs --image ${image}`;
1298
-
1299
1155
  // Root Cause A of the issue #1914 reopen: a non-copy-on-write storage driver.
1300
1156
  // `vfs` stores a full copy of every image layer, so the multi-GB images
1301
1157
  // consume many times their size on disk and any layer write (pull, run,
@@ -1306,7 +1162,6 @@ export async function preflightDockerIsolation(options = {}) {
1306
1162
  if (storageDriver === 'vfs') {
1307
1163
  result.warnings.push(`The Docker daemon backing '--isolation docker' is using the 'vfs' storage driver, which performs NO copy-on-write: ` + `it stores a full copy of every image layer, so the multi-GB Hive Mind images consume many times their size on disk and isolated tasks can fail with 'failed to register layer: no space left on device' (issue #1914). ` + `Switch to a copy-on-write driver: rebuild/redeploy with the current Dockerfile.dind (it defaults to 'fuse-overlayfs'), or for an already-running container add '-e DIND_STORAGE_DRIVER=fuse-overlayfs' to the bot container's 'docker run' and recreate it.`);
1308
1164
  }
1309
-
1310
1165
  if (!imagePresent) {
1311
1166
  // Image absent: the first isolated task will pull the full image. Explain
1312
1167
  // the most likely cause and the exact fix instead of letting the operator
@@ -1318,7 +1173,6 @@ export async function preflightDockerIsolation(options = {}) {
1318
1173
  } else {
1319
1174
  result.warnings.push(`Docker isolation image '${image}' is not present locally; the first isolated task will pull it. ` + `If this host already has it under a different tag, pin HIVE_MIND_DOCKER_ISOLATION_IMAGE_TAG, or seed it with: ${preload}`);
1320
1175
  }
1321
-
1322
1176
  // Root Cause B of the issue #1914 reopen: too little disk for the pull. The
1323
1177
  // image is well over 30 GB extracted; predict the `no space left on device`
1324
1178
  // failure here rather than hitting it mid-pull.
@@ -1327,14 +1181,12 @@ export async function preflightDockerIsolation(options = {}) {
1327
1181
  result.warnings.push(`Only ~${diskAvailableGiB.toFixed(0)} GiB free on ${root} and the isolation image '${image}' is not present yet. ` + `The Hive Mind isolation image is well over 30 GB extracted, so the first isolated task's pull may fail with 'no space left on device' (issue #1914). ` + `Seed it via host passthrough (mount the host docker socket) or with '${preload}', and free space on the Docker data root.`);
1328
1182
  }
1329
1183
  }
1330
-
1331
1184
  if (imagePresent) {
1332
1185
  info(`✅ Docker isolation image '${image}' is already present locally — isolated tasks reuse it (no multi-GB pull). See issue #1914.`);
1333
1186
  }
1334
1187
  for (const w of result.warnings) warn(`⚠️ ${w}`);
1335
1188
  return result;
1336
1189
  }
1337
-
1338
1190
  /**
1339
1191
  * Host paths that, when present, propagate a git identity into a docker-isolated
1340
1192
  * container via getDockerIsolationAuthMounts. Honors the same env vars git reads
@@ -1344,7 +1196,6 @@ export async function preflightDockerIsolation(options = {}) {
1344
1196
  export function resolveHostGitIdentityPaths({ env = process.env, homeDir = os.homedir() } = {}) {
1345
1197
  return [env.GIT_CONFIG_GLOBAL || path.join(homeDir, '.gitconfig'), env.XDG_CONFIG_HOME ? path.join(env.XDG_CONFIG_HOME, 'git') : path.join(homeDir, '.config', 'git')];
1346
1198
  }
1347
-
1348
1199
  /**
1349
1200
  * True when the host exposes a git identity that getDockerIsolationAuthMounts can
1350
1201
  * mount into an isolated container. See issue #1939.
@@ -1352,7 +1203,6 @@ export function resolveHostGitIdentityPaths({ env = process.env, homeDir = os.ho
1352
1203
  export function hostHasMountableGitIdentity({ env = process.env, homeDir = os.homedir(), existsSync = fs.existsSync } = {}) {
1353
1204
  return resolveHostGitIdentityPaths({ env, homeDir }).some(p => Boolean(existsSync(p)));
1354
1205
  }
1355
-
1356
1206
  /**
1357
1207
  * Startup git-identity preflight for `--isolation docker`.
1358
1208
  *
@@ -1384,13 +1234,11 @@ export async function ensureHostGitIdentityForIsolation(options = {}) {
1384
1234
  const info = typeof logger.log === 'function' ? logger.log.bind(logger) : () => {};
1385
1235
  const warn = typeof logger.warn === 'function' ? logger.warn.bind(logger) : info;
1386
1236
  const result = { present: false, repaired: false, warnings: [] };
1387
-
1388
1237
  if (hostHasMountableGitIdentity({ env, homeDir, existsSync })) {
1389
1238
  result.present = true;
1390
1239
  info('✅ Host git identity present — docker-isolated tasks inherit it via the mounted ~/.gitconfig (issue #1939).');
1391
1240
  return result;
1392
1241
  }
1393
-
1394
1242
  // No mountable identity. Try to derive one from the authenticated gh account
1395
1243
  // so the next isolated task does not fail with "Git identity not configured".
1396
1244
  const repairFn =
@@ -1405,19 +1253,16 @@ export async function ensureHostGitIdentityForIsolation(options = {}) {
1405
1253
  } catch (error) {
1406
1254
  repairOutcome = { success: false, error: error?.message || String(error) };
1407
1255
  }
1408
-
1409
1256
  if (repairOutcome?.success && hostHasMountableGitIdentity({ env, homeDir, existsSync })) {
1410
1257
  result.present = true;
1411
1258
  result.repaired = true;
1412
1259
  info('✅ Host git identity was missing; derived it from the authenticated gh account via gh-setup-git-identity so docker-isolated tasks can mount it (issue #1939).');
1413
1260
  return result;
1414
1261
  }
1415
-
1416
1262
  result.warnings.push(`No host git identity (~/.gitconfig) to mount into docker-isolated containers, so isolated 'solve' tasks will fail with "Git identity not configured" even though gh is authenticated (issue #1939). ` + `Configure one on the bot host: run 'gh-setup-git-identity' (derives it from the authenticated gh account), set 'git config --global user.name/.email', or pass '--auto-gh-configuration-repair' to solve.` + (repairOutcome?.error ? ` Auto-repair attempt failed: ${repairOutcome.error}` : ''));
1417
1263
  for (const w of result.warnings) warn(`⚠️ ${w}`);
1418
1264
  return result;
1419
1265
  }
1420
-
1421
1266
  /**
1422
1267
  * Check if an isolated session is still running.
1423
1268
  * Uses `$ --status` first, with a backend-specific fallback (screen -ls for
@@ -1434,7 +1279,6 @@ export async function isSessionRunning(sessionId, options = {}) {
1434
1279
  // Support legacy call signature: isSessionRunning(sessionId, verbose)
1435
1280
  const opts = typeof options === 'boolean' ? { verbose: options } : options;
1436
1281
  const { backend, verbose = false } = opts;
1437
-
1438
1282
  const result = await querySessionStatus(sessionId, verbose);
1439
1283
  if (result.exists && result.status) {
1440
1284
  if (isExecutingSessionStatus(result.status)) {
@@ -1459,7 +1303,6 @@ export async function isSessionRunning(sessionId, options = {}) {
1459
1303
  return false;
1460
1304
  }
1461
1305
  }
1462
-
1463
1306
  // Fallback used only when `$ --status` has no usable record. This works
1464
1307
  // around older start-command bugs where `$ --status` can't resolve a session
1465
1308
  // by its --session name (only by an internal UUID). See issue #1545.
@@ -1484,10 +1327,8 @@ export async function isSessionRunning(sessionId, options = {}) {
1484
1327
  return containerRunning;
1485
1328
  }
1486
1329
  }
1487
-
1488
1330
  return false;
1489
1331
  }
1490
-
1491
1332
  /**
1492
1333
  * Validate that an isolation backend value is valid
1493
1334
  * @param {string} backend - Backend value to validate
@@ -1496,5 +1337,4 @@ export async function isSessionRunning(sessionId, options = {}) {
1496
1337
  export function isValidIsolationBackend(backend) {
1497
1338
  return VALID_ISOLATION_BACKENDS.includes(backend);
1498
1339
  }
1499
-
1500
1340
  export { VALID_ISOLATION_BACKENDS };