labgate 0.5.45 → 0.5.47

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.
@@ -1710,6 +1725,112 @@ function buildEntrypoint(agent, options = {}) {
1710
1725
  ' chmod +x "$HOME/.labgate-bin/$_cmd"',
1711
1726
  ' done',
1712
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',
1713
1834
  '',
1714
1835
  // Minimal progress indicator for first-run npm installs (keeps logs quiet, avoids "nothing happening" UX).
1715
1836
  'labgate_npm_install_global() {',
@@ -1747,10 +1868,10 @@ function buildEntrypoint(agent, options = {}) {
1747
1868
  '',
1748
1869
  ];
1749
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
+ }
1750
1874
  if (agent === 'claude') {
1751
- if (ensureCommands.length > 0) {
1752
- 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', '');
1753
- }
1754
1875
  if (disableClaudeStatusLine) {
1755
1876
  // Set the statusLine to a no-op command so Claude Code renders an empty
1756
1877
  // status bar instead of falling back to its default "time ➜ at host |".
@@ -2048,18 +2169,32 @@ function setupBrowserHook(options = {}) {
2048
2169
  return false;
2049
2170
  }
2050
2171
  };
2051
- // Watch for the URL file on the host side
2052
- const watcher = (0, fs_1.watch)(labgateDir, (_eventType, filename) => {
2053
- if (handled)
2054
- return;
2055
- const name = filename || '';
2056
- if (name !== 'browser-url')
2057
- return;
2058
- if (processUrlFile())
2059
- return;
2060
- // File may exist but not be fully flushed yet. Retry shortly.
2061
- setTimeout(() => { processUrlFile(); }, 60);
2062
- });
2172
+ // Watch for the URL file on the host side. Some constrained runners can hit
2173
+ // watch limits; the poller below is the fallback path for those cases.
2174
+ let watcher = null;
2175
+ try {
2176
+ watcher = (0, fs_1.watch)(labgateDir, (_eventType, filename) => {
2177
+ if (handled)
2178
+ return;
2179
+ const name = filename || '';
2180
+ if (name !== 'browser-url')
2181
+ return;
2182
+ if (processUrlFile())
2183
+ return;
2184
+ // File may exist but not be fully flushed yet. Retry shortly.
2185
+ setTimeout(() => { processUrlFile(); }, 60);
2186
+ });
2187
+ watcher.on('error', () => {
2188
+ try {
2189
+ watcher?.close();
2190
+ }
2191
+ catch { /* best effort */ }
2192
+ watcher = null;
2193
+ });
2194
+ }
2195
+ catch {
2196
+ watcher = null;
2197
+ }
2063
2198
  // Safety net: if fs.watch misses events, poll for a short window.
2064
2199
  const poll = setInterval(() => {
2065
2200
  if (handled) {
@@ -2072,7 +2207,10 @@ function setupBrowserHook(options = {}) {
2072
2207
  poll.unref?.();
2073
2208
  pollTimeout.unref?.();
2074
2209
  const cleanup = () => {
2075
- watcher.close();
2210
+ try {
2211
+ watcher?.close();
2212
+ }
2213
+ catch { /* best effort */ }
2076
2214
  clearInterval(poll);
2077
2215
  clearTimeout(pollTimeout);
2078
2216
  try {
@@ -3038,6 +3176,9 @@ async function restartSession(id, opts) {
3038
3176
  config,
3039
3177
  dryRun: false,
3040
3178
  footerMode: 'once',
3179
+ dangerouslySkipPermissions: shouldUseDangerousPermissionsByDefault(agent, {
3180
+ linkedWebTerminalId: sessionData.webTerminalId,
3181
+ }),
3041
3182
  sharedSessionsDir: effective.sharedSessionsDir,
3042
3183
  sharedAuditDir: effective.sharedAuditDir,
3043
3184
  });