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.
- package/README.md +2 -0
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/container.d.ts +10 -1
- package/dist/lib/container.js +204 -10
- package/dist/lib/container.js.map +1 -1
- package/dist/lib/image-pull-lock.d.ts +5 -0
- package/dist/lib/image-pull-lock.js +4 -0
- package/dist/lib/image-pull-lock.js.map +1 -1
- package/dist/lib/init.js +1 -1
- package/dist/mcp-bundles/dataset-mcp.bundle.mjs +1 -1
- package/dist/mcp-bundles/display-mcp.bundle.mjs +1 -1
- package/dist/mcp-bundles/explorer-mcp.bundle.mjs +1 -1
- package/dist/mcp-bundles/results-mcp.bundle.mjs +1 -1
- package/dist/mcp-bundles/slurm-mcp.bundle.mjs +1 -1
- package/package.json +1 -1
package/dist/lib/container.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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
|
});
|