labgate 0.5.44 → 0.5.46

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.
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.shouldUseDangerousPermissionsByDefault = shouldUseDangerousPermissionsByDefault;
39
40
  exports.computeMountFingerprint = computeMountFingerprint;
40
41
  exports.prepareMcpServers = prepareMcpServers;
41
42
  exports.imageToSifName = imageToSifName;
@@ -68,6 +69,20 @@ const slurm_poller_js_1 = require("./slurm-poller.js");
68
69
  const slurm_cli_passthrough_js_1 = require("./slurm-cli-passthrough.js");
69
70
  const startup_stage_lock_js_1 = require("./startup-stage-lock.js");
70
71
  const log = __importStar(require("./log.js"));
72
+ function isTruthyEnvFlag(value) {
73
+ const normalized = String(value || '').trim().toLowerCase();
74
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
75
+ }
76
+ function shouldUseDangerousPermissionsByDefault(agent, options = {}) {
77
+ if (agent !== 'codex')
78
+ return false;
79
+ if (String(options.linkedWebTerminalId || '').trim().length > 0)
80
+ return false;
81
+ const env = options.env ?? process.env;
82
+ if (isTruthyEnvFlag(env.LABGATE_CODEX_STANDARD_PERMISSIONS))
83
+ return false;
84
+ return true;
85
+ }
71
86
  /**
72
87
  * Compute a deterministic fingerprint of mount-affecting config fields.
73
88
  * Used to detect when running sessions need a restart after config changes.
@@ -715,6 +730,8 @@ function imageToSifName(image) {
715
730
  return `${readable}-${hash}.sif`;
716
731
  }
717
732
  const APPTAINER_SIF_INSPECT_TIMEOUT_MS = 15_000;
733
+ const SLOW_APPTAINER_SIF_INSPECT_WARN_MS = 5_000;
734
+ const SLOW_IMAGE_PULL_LOCK_WAIT_WARN_MS = 5_000;
718
735
  function isUsableApptainerSif(runtime, sifPath) {
719
736
  if (!(0, fs_1.existsSync)(sifPath))
720
737
  return false;
@@ -733,19 +750,36 @@ function isUsableApptainerSif(runtime, sifPath) {
733
750
  * Ensure a SIF image exists in the cache directory.
734
751
  * Pulls from docker:// URI if not already cached.
735
752
  */
736
- async function ensureSifImage(runtime, image) {
753
+ async function ensureSifImage(runtime, image, hooks = {}) {
754
+ const recordTiming = (label, startedAt) => {
755
+ hooks.onTiming?.(label, Math.max(0, Date.now() - startedAt));
756
+ };
737
757
  const imagesDir = (0, config_js_1.getImagesDir)();
738
758
  (0, fs_1.mkdirSync)(imagesDir, { recursive: true });
739
759
  const sifPath = (0, path_1.join)(imagesDir, imageToSifName(image));
740
760
  const pullLockPath = `${sifPath}.pull.lock`;
741
- if (isUsableApptainerSif(runtime, sifPath) && !(0, fs_1.existsSync)(pullLockPath)) {
761
+ const initialInspectStartedAt = Date.now();
762
+ const initialCacheHit = isUsableApptainerSif(runtime, sifPath);
763
+ recordTiming('image_prepare_sif_cache_inspect', initialInspectStartedAt);
764
+ if (Math.max(0, Date.now() - initialInspectStartedAt) >= SLOW_APPTAINER_SIF_INSPECT_WARN_MS) {
765
+ hooks.onWarning?.('slow_sif_cache_inspect');
766
+ }
767
+ if (initialCacheHit && !(0, fs_1.existsSync)(pullLockPath)) {
742
768
  return sifPath;
743
769
  }
770
+ let pullLockTimingRecorded = false;
744
771
  try {
745
772
  await (0, image_pull_lock_js_1.withImagePullFileLock)(pullLockPath, image, async () => {
746
- if (isUsableApptainerSif(runtime, sifPath))
773
+ const reInspectStartedAt = Date.now();
774
+ const cacheHitAfterLock = isUsableApptainerSif(runtime, sifPath);
775
+ recordTiming('image_prepare_sif_cache_reinspect', reInspectStartedAt);
776
+ if (Math.max(0, Date.now() - reInspectStartedAt) >= SLOW_APPTAINER_SIF_INSPECT_WARN_MS) {
777
+ hooks.onWarning?.('slow_sif_cache_reinspect');
778
+ }
779
+ if (cacheHitAfterLock)
747
780
  return;
748
781
  if ((0, fs_1.existsSync)(sifPath)) {
782
+ hooks.onWarning?.('cached_sif_failed_validation');
749
783
  log.warn(`Cached SIF for ${log.dim(image)} failed validation. Re-pulling image.`);
750
784
  try {
751
785
  (0, fs_1.rmSync)(sifPath, { force: true });
@@ -757,10 +791,19 @@ async function ensureSifImage(runtime, image) {
757
791
  const tempSifPath = `${sifPath}.tmp-${process.pid}-${(0, crypto_1.randomBytes)(6).toString('hex')}`;
758
792
  log.info(`Pulling image ${log.dim(image)}`);
759
793
  try {
794
+ hooks.onHint?.('sif_pull_required');
795
+ const pullStartedAt = Date.now();
760
796
  (0, child_process_1.execFileSync)(runtime, ['pull', tempSifPath, `docker://${image}`], {
761
797
  stdio: 'inherit',
762
798
  });
763
- if (!isUsableApptainerSif(runtime, tempSifPath)) {
799
+ recordTiming('image_prepare_sif_pull', pullStartedAt);
800
+ const validatePulledStartedAt = Date.now();
801
+ const pulledSifUsable = isUsableApptainerSif(runtime, tempSifPath);
802
+ recordTiming('image_prepare_sif_pull_validate', validatePulledStartedAt);
803
+ if (Math.max(0, Date.now() - validatePulledStartedAt) >= SLOW_APPTAINER_SIF_INSPECT_WARN_MS) {
804
+ hooks.onWarning?.('slow_sif_pull_validate');
805
+ }
806
+ if (!pulledSifUsable) {
764
807
  throw new Error(`Pulled SIF failed validation: ${tempSifPath}`);
765
808
  }
766
809
  (0, fs_1.renameSync)(tempSifPath, sifPath);
@@ -774,6 +817,13 @@ async function ensureSifImage(runtime, image) {
774
817
  }
775
818
  }
776
819
  }, {
820
+ onAcquired: ({ waitedMs }) => {
821
+ pullLockTimingRecorded = true;
822
+ hooks.onTiming?.('image_prepare_pull_lock_wait', waitedMs);
823
+ if (waitedMs >= SLOW_IMAGE_PULL_LOCK_WAIT_WARN_MS) {
824
+ hooks.onWarning?.('slow_image_pull_lock_wait');
825
+ }
826
+ },
777
827
  onWait: ({ owner }) => {
778
828
  const ownerLabel = owner && owner !== 'unknown' ? ` (owner: ${owner})` : '';
779
829
  log.step(`Waiting for shared image pull lock for ${log.dim(image)}${ownerLabel}...`);
@@ -791,6 +841,9 @@ async function ensureSifImage(runtime, image) {
791
841
  console.error(String(msg).trim().slice(0, 500));
792
842
  process.exit(1);
793
843
  }
844
+ if (!pullLockTimingRecorded) {
845
+ hooks.onTiming?.('image_prepare_pull_lock_wait', 0);
846
+ }
794
847
  return sifPath;
795
848
  }
796
849
  function ensurePodmanImage(runtime, image) {
@@ -1672,6 +1725,112 @@ function buildEntrypoint(agent, options = {}) {
1672
1725
  ' chmod +x "$HOME/.labgate-bin/$_cmd"',
1673
1726
  ' done',
1674
1727
  'fi',
1728
+ 'labgate_install_bzip2_shims() {',
1729
+ ' local needs_bzip2="0"',
1730
+ ' local py=""',
1731
+ ' for _cmd in bzip2 bunzip2 bzcat; do',
1732
+ ' if ! command -v "$_cmd" >/dev/null 2>&1; then',
1733
+ ' needs_bzip2="1"',
1734
+ ' break',
1735
+ ' fi',
1736
+ ' done',
1737
+ ' [ "$needs_bzip2" = "1" ] || return 0',
1738
+ ' if ! command -v python3 >/dev/null 2>&1; then',
1739
+ ' return 0',
1740
+ ' fi',
1741
+ ' cat > "$HOME/.labgate-bin/bzip2" <<\'EOF\'',
1742
+ '#!/usr/bin/env python3',
1743
+ 'import bz2',
1744
+ 'import os',
1745
+ 'import sys',
1746
+ '',
1747
+ 'prog = os.path.basename(sys.argv[0])',
1748
+ 'args = sys.argv[1:]',
1749
+ 'decompress = prog in ("bunzip2", "bzcat")',
1750
+ 'stdout_mode = prog == "bzcat"',
1751
+ 'keep = prog == "bzcat"',
1752
+ 'force = False',
1753
+ 'files = []',
1754
+ '',
1755
+ 'def fail(message: str, code: int = 2) -> None:',
1756
+ ' sys.stderr.write(message + "\\n")',
1757
+ ' raise SystemExit(code)',
1758
+ '',
1759
+ 'def read_bytes(path: str | None) -> bytes:',
1760
+ ' if not path or path == "-":',
1761
+ ' return sys.stdin.buffer.read()',
1762
+ ' with open(path, "rb") as handle:',
1763
+ ' return handle.read()',
1764
+ '',
1765
+ 'def write_bytes(path: str | None, data: bytes) -> None:',
1766
+ ' if not path or path == "-":',
1767
+ ' sys.stdout.buffer.write(data)',
1768
+ ' return',
1769
+ ' with open(path, "wb") as handle:',
1770
+ ' handle.write(data)',
1771
+ '',
1772
+ 'while args:',
1773
+ ' arg = args.pop(0)',
1774
+ ' if arg == "--":',
1775
+ ' files.extend(args)',
1776
+ ' break',
1777
+ ' if arg == "-":',
1778
+ ' files.append(arg)',
1779
+ ' continue',
1780
+ ' if arg.startswith("-") and arg != "-":',
1781
+ ' for flag in arg[1:]:',
1782
+ ' if flag == "d":',
1783
+ ' decompress = True',
1784
+ ' elif flag == "z":',
1785
+ ' decompress = False',
1786
+ ' elif flag == "c":',
1787
+ ' stdout_mode = True',
1788
+ ' elif flag == "k":',
1789
+ ' keep = True',
1790
+ ' elif flag == "f":',
1791
+ ' force = True',
1792
+ ' else:',
1793
+ ' fail(f"bzip2 shim: unsupported option -{flag}")',
1794
+ ' continue',
1795
+ ' files.append(arg)',
1796
+ '',
1797
+ 'if not files:',
1798
+ ' files = ["-"]',
1799
+ '',
1800
+ 'exit_code = 0',
1801
+ 'for path in files:',
1802
+ ' try:',
1803
+ ' raw = read_bytes(path)',
1804
+ ' data = bz2.decompress(raw) if decompress else bz2.compress(raw)',
1805
+ ' if stdout_mode or path == "-":',
1806
+ ' write_bytes(None, data)',
1807
+ ' continue',
1808
+ ' if decompress:',
1809
+ ' target = path[:-4] if path.endswith(".bz2") else path + ".out"',
1810
+ ' else:',
1811
+ ' target = path + ".bz2"',
1812
+ ' if os.path.exists(target) and not force:',
1813
+ ' sys.stderr.write(f"{target} already exists; use -f to overwrite\\n")',
1814
+ ' exit_code = 1',
1815
+ ' continue',
1816
+ ' write_bytes(target, data)',
1817
+ ' if not keep:',
1818
+ ' try:',
1819
+ ' os.unlink(path)',
1820
+ ' except OSError:',
1821
+ ' pass',
1822
+ ' except Exception as exc:',
1823
+ ' label = "stdin" if path == "-" else path',
1824
+ ' sys.stderr.write(f"{label}: {exc}\\n")',
1825
+ ' exit_code = 1',
1826
+ '',
1827
+ 'raise SystemExit(exit_code)',
1828
+ 'EOF',
1829
+ ' chmod +x "$HOME/.labgate-bin/bzip2"',
1830
+ ' ln -sf "$HOME/.labgate-bin/bzip2" "$HOME/.labgate-bin/bunzip2"',
1831
+ ' ln -sf "$HOME/.labgate-bin/bzip2" "$HOME/.labgate-bin/bzcat"',
1832
+ '}',
1833
+ 'labgate_install_bzip2_shims',
1675
1834
  '',
1676
1835
  // Minimal progress indicator for first-run npm installs (keeps logs quiet, avoids "nothing happening" UX).
1677
1836
  'labgate_npm_install_global() {',
@@ -1709,10 +1868,10 @@ function buildEntrypoint(agent, options = {}) {
1709
1868
  '',
1710
1869
  ];
1711
1870
  const preLaunchLines = [];
1871
+ if (ensureCommands.length > 0) {
1872
+ preLaunchLines.push('labgate_install_system_package() {', ' local manager="${1:-}"', ' local pkg="${2:-}"', ' [ -n "$manager" ] && [ -n "$pkg" ] || return 1', ' case "$manager" in', ' apt-get) apt-get install -y "$pkg" >/dev/null 2>&1 ;;', ' apk) apk add --no-cache "$pkg" >/dev/null 2>&1 ;;', ' dnf) dnf install -y "$pkg" >/dev/null 2>&1 ;;', ' yum) yum install -y "$pkg" >/dev/null 2>&1 ;;', ' microdnf) microdnf install -y "$pkg" >/dev/null 2>&1 ;;', ' *) return 1 ;;', ' esac', '}', 'labgate_candidate_packages_for_command() {', ' local cmd="${1:-}"', ' case "$cmd" in', ' rg) echo "ripgrep rg" ;;', ' fd) echo "fd-find fd" ;;', ' pip) echo "python3-pip py3-pip pip" ;;', ' python) echo "python3 python" ;;', ' *) echo "$cmd" ;;', ' esac', '}', 'labgate_ensure_commands() {', ` local required=(${ensureCommands.map((cmd) => JSON.stringify(cmd)).join(' ')})`, ' local missing=()', ' local cmd=""', ' for cmd in "${required[@]}"; do', ' if ! command -v "$cmd" >/dev/null 2>&1; then', ' missing+=("$cmd")', ' fi', ' done', ' if [ "${#missing[@]}" -eq 0 ]; then', ' return 0', ' fi', ' if [ "$(id -u)" != "0" ]; then', ' echo "[labgate] Warning: missing container command(s): ${missing[*]}." >&2', ' echo "[labgate] Configure an image with required tools preinstalled for reliable startup." >&2', ' return 0', ' fi', ' local manager=""', ' if command -v apt-get >/dev/null 2>&1; then', ' manager="apt-get"', ' export DEBIAN_FRONTEND=noninteractive', ' apt-get update >/dev/null 2>&1 || true', ' elif command -v apk >/dev/null 2>&1; then', ' manager="apk"', ' elif command -v dnf >/dev/null 2>&1; then', ' manager="dnf"', ' elif command -v yum >/dev/null 2>&1; then', ' manager="yum"', ' elif command -v microdnf >/dev/null 2>&1; then', ' manager="microdnf"', ' fi', ' if [ -z "$manager" ]; then', ' echo "[labgate] Warning: cannot install missing commands (${missing[*]}); no known package manager found." >&2', ' return 0', ' fi', ' echo "[labgate] Installing missing container tools: ${missing[*]}..."', ' local candidate=""', ' local installed="0"', ' for cmd in "${missing[@]}"; do', ' installed="0"', ' for candidate in $(labgate_candidate_packages_for_command "$cmd"); do', ' if labgate_install_system_package "$manager" "$candidate"; then', ' installed="1"', ' if command -v "$cmd" >/dev/null 2>&1; then', ' break', ' fi', ' fi', ' done', ' if [ "$installed" != "1" ]; then', ' echo "[labgate] Warning: failed to install command ${cmd}." >&2', ' fi', ' done', ' local still_missing=()', ' for cmd in "${required[@]}"; do', ' if ! command -v "$cmd" >/dev/null 2>&1; then', ' still_missing+=("$cmd")', ' fi', ' done', ' if [ "${#still_missing[@]}" -gt 0 ]; then', ' echo "[labgate] Warning: command(s) still unavailable after install attempt: ${still_missing[*]}." >&2', ' echo "[labgate] Configure an image with these tools preinstalled for reliable startup." >&2', ' fi', '}', 'labgate_ensure_commands', '');
1873
+ }
1712
1874
  if (agent === 'claude') {
1713
- if (ensureCommands.length > 0) {
1714
- preLaunchLines.push('labgate_install_system_package() {', ' local manager="${1:-}"', ' local pkg="${2:-}"', ' [ -n "$manager" ] && [ -n "$pkg" ] || return 1', ' case "$manager" in', ' apt-get) apt-get install -y "$pkg" >/dev/null 2>&1 ;;', ' apk) apk add --no-cache "$pkg" >/dev/null 2>&1 ;;', ' dnf) dnf install -y "$pkg" >/dev/null 2>&1 ;;', ' yum) yum install -y "$pkg" >/dev/null 2>&1 ;;', ' microdnf) microdnf install -y "$pkg" >/dev/null 2>&1 ;;', ' *) return 1 ;;', ' esac', '}', 'labgate_candidate_packages_for_command() {', ' local cmd="${1:-}"', ' case "$cmd" in', ' rg) echo "ripgrep rg" ;;', ' fd) echo "fd-find fd" ;;', ' pip) echo "python3-pip py3-pip pip" ;;', ' python) echo "python3 python" ;;', ' *) echo "$cmd" ;;', ' esac', '}', 'labgate_ensure_commands() {', ` local required=(${ensureCommands.map((cmd) => JSON.stringify(cmd)).join(' ')})`, ' local missing=()', ' local cmd=""', ' for cmd in "${required[@]}"; do', ' if ! command -v "$cmd" >/dev/null 2>&1; then', ' missing+=("$cmd")', ' fi', ' done', ' if [ "${#missing[@]}" -eq 0 ]; then', ' return 0', ' fi', ' if [ "$(id -u)" != "0" ]; then', ' echo "[labgate] Warning: missing container command(s): ${missing[*]}." >&2', ' echo "[labgate] Configure an image with required tools preinstalled for best Claude UX." >&2', ' return 0', ' fi', ' local manager=""', ' if command -v apt-get >/dev/null 2>&1; then', ' manager="apt-get"', ' export DEBIAN_FRONTEND=noninteractive', ' apt-get update >/dev/null 2>&1 || true', ' elif command -v apk >/dev/null 2>&1; then', ' manager="apk"', ' elif command -v dnf >/dev/null 2>&1; then', ' manager="dnf"', ' elif command -v yum >/dev/null 2>&1; then', ' manager="yum"', ' elif command -v microdnf >/dev/null 2>&1; then', ' manager="microdnf"', ' fi', ' if [ -z "$manager" ]; then', ' echo "[labgate] Warning: cannot install missing commands (${missing[*]}); no known package manager found." >&2', ' return 0', ' fi', ' echo "[labgate] Installing missing container tools: ${missing[*]}..."', ' local candidate=""', ' local installed="0"', ' for cmd in "${missing[@]}"; do', ' installed="0"', ' for candidate in $(labgate_candidate_packages_for_command "$cmd"); do', ' if labgate_install_system_package "$manager" "$candidate"; then', ' installed="1"', ' if command -v "$cmd" >/dev/null 2>&1; then', ' break', ' fi', ' fi', ' done', ' if [ "$installed" != "1" ]; then', ' echo "[labgate] Warning: failed to install command ${cmd}." >&2', ' fi', ' done', ' local still_missing=()', ' for cmd in "${required[@]}"; do', ' if ! command -v "$cmd" >/dev/null 2>&1; then', ' still_missing+=("$cmd")', ' fi', ' done', ' if [ "${#still_missing[@]}" -gt 0 ]; then', ' echo "[labgate] Warning: command(s) still unavailable after install attempt: ${still_missing[*]}." >&2', ' echo "[labgate] Configure an image with these tools preinstalled for reliable startup." >&2', ' fi', '}', 'labgate_ensure_commands', '');
1715
- }
1716
1875
  if (disableClaudeStatusLine) {
1717
1876
  // Set the statusLine to a no-op command so Claude Code renders an empty
1718
1877
  // status bar instead of falling back to its default "time ➜ at host |".
@@ -2137,10 +2296,12 @@ function createStartupReportData(session, runtime, image) {
2137
2296
  if (session.uiDetected === false)
2138
2297
  warnings.push('ui_not_detected');
2139
2298
  return {
2140
- schema_version: 1,
2299
+ schema_version: 2,
2141
2300
  kind: 'labgate-startup-report',
2142
2301
  generated_at: now,
2143
2302
  updated_at: now,
2303
+ startup_completed_at: null,
2304
+ startup_completion: null,
2144
2305
  pid: process.pid,
2145
2306
  node: (0, os_1.hostname)(),
2146
2307
  agent: session.agent,
@@ -2152,6 +2313,7 @@ function createStartupReportData(session, runtime, image) {
2152
2313
  launch_mode: session.dryRun ? 'dry-run' : null,
2153
2314
  ui_detected: session.uiDetected ?? null,
2154
2315
  total_ms: 0,
2316
+ session_total_ms: 0,
2155
2317
  first_output_ms: null,
2156
2318
  timings_ms: {},
2157
2319
  cold_start_hints: [],
@@ -2280,8 +2442,12 @@ async function startSession(session) {
2280
2442
  const flushStartupReport = () => {
2281
2443
  if (!startupReport || !session.startupReportPath || startupReportWriteFailed)
2282
2444
  return;
2445
+ const elapsedMs = Math.max(0, Date.now() - startupStartedAt);
2283
2446
  startupReport.updated_at = new Date().toISOString();
2284
- startupReport.total_ms = Math.max(0, Date.now() - startupStartedAt);
2447
+ startupReport.session_total_ms = elapsedMs;
2448
+ if (startupReport.startup_completed_at === null) {
2449
+ startupReport.total_ms = elapsedMs;
2450
+ }
2285
2451
  try {
2286
2452
  writeStartupReportFile(session.startupReportPath, startupReport);
2287
2453
  }
@@ -2318,6 +2484,14 @@ async function startSession(session) {
2318
2484
  startupReport.launch_mode = mode;
2319
2485
  flushStartupReport();
2320
2486
  };
2487
+ const completeStartup = (completion) => {
2488
+ if (!startupReport || startupReport.startup_completed_at !== null)
2489
+ return;
2490
+ startupReport.startup_completed_at = new Date().toISOString();
2491
+ startupReport.startup_completion = completion;
2492
+ startupReport.total_ms = Math.max(0, Date.now() - startupStartedAt);
2493
+ flushStartupReport();
2494
+ };
2321
2495
  const noteStartupFirstOutput = (data) => {
2322
2496
  if (!startupReport)
2323
2497
  return;
@@ -2329,6 +2503,7 @@ async function startSession(session) {
2329
2503
  startupReport.first_output_ms = firstOutputMs;
2330
2504
  startupReport.timings_ms.launch_first_output = firstOutputMs;
2331
2505
  startupReport.status = 'running';
2506
+ completeStartup('first-output');
2332
2507
  flushStartupReport();
2333
2508
  };
2334
2509
  const updateSlurmStartupReport = (payload) => {
@@ -2567,9 +2742,21 @@ async function startSession(session) {
2567
2742
  sifPath = (0, path_1.join)((0, config_js_1.getImagesDir)(), imageToSifName(image));
2568
2743
  }
2569
2744
  else {
2570
- sifPath = await ensureSifImage(runtime, image);
2745
+ sifPath = await ensureSifImage(runtime, image, {
2746
+ onTiming: (label, elapsedMs) => {
2747
+ if (startupReport) {
2748
+ startupReport.timings_ms[label] = elapsedMs;
2749
+ flushStartupReport();
2750
+ }
2751
+ startupTimings.push([label, elapsedMs]);
2752
+ },
2753
+ onWarning: noteStartupWarning,
2754
+ onHint: noteColdStartHint,
2755
+ });
2571
2756
  }
2757
+ const buildArgsStartedAt = Date.now();
2572
2758
  args = buildApptainerArgs(session, sifPath, sessionId, runtimeEnvArgs);
2759
+ recordStartupTiming('image_prepare_build_args', buildArgsStartedAt);
2573
2760
  }
2574
2761
  else {
2575
2762
  if (bridgeCodexOauthForPodman) {
@@ -2595,6 +2782,7 @@ async function startSession(session) {
2595
2782
  if (session.dryRun) {
2596
2783
  setStartupLaunchMode('dry-run');
2597
2784
  setStartupStatus('dry-run');
2785
+ completeStartup('dry-run');
2598
2786
  prettyPrintCommand(runtime, args);
2599
2787
  flushStartupReport();
2600
2788
  return;
@@ -2757,6 +2945,7 @@ async function startSession(session) {
2757
2945
  launchHeartbeat.stop();
2758
2946
  codexOauthHeartbeat?.stop();
2759
2947
  if (startupReport) {
2948
+ completeStartup('process-exit');
2760
2949
  startupReport.exit_code = event.exitCode ?? 0;
2761
2950
  startupReport.status = 'exited';
2762
2951
  flushStartupReport();
@@ -2807,12 +2996,14 @@ async function startSession(session) {
2807
2996
  const child = (0, child_process_1.spawn)(runtime, args, { stdio: 'inherit' });
2808
2997
  if (startupReport && startupReport.first_output_ms === null) {
2809
2998
  startupReport.status = 'running';
2999
+ completeStartup('spawn');
2810
3000
  flushStartupReport();
2811
3001
  }
2812
3002
  startDeferredSlurmPassthrough();
2813
3003
  const timeoutHandle = setupSessionTimeout(session, sessionId, runtime, () => child.exitCode !== null, () => child.kill('SIGTERM'));
2814
3004
  child.on('close', (code) => {
2815
3005
  if (startupReport) {
3006
+ completeStartup('process-exit');
2816
3007
  startupReport.exit_code = code ?? 0;
2817
3008
  startupReport.status = 'exited';
2818
3009
  flushStartupReport();
@@ -2968,6 +3159,9 @@ async function restartSession(id, opts) {
2968
3159
  config,
2969
3160
  dryRun: false,
2970
3161
  footerMode: 'once',
3162
+ dangerouslySkipPermissions: shouldUseDangerousPermissionsByDefault(agent, {
3163
+ linkedWebTerminalId: sessionData.webTerminalId,
3164
+ }),
2971
3165
  sharedSessionsDir: effective.sharedSessionsDir,
2972
3166
  sharedAuditDir: effective.sharedAuditDir,
2973
3167
  });