labgate 0.5.39 → 0.5.42

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.
Files changed (47) hide show
  1. package/README.md +132 -248
  2. package/dist/cli.js +9 -33
  3. package/dist/cli.js.map +1 -1
  4. package/dist/lib/config.d.ts +19 -3
  5. package/dist/lib/config.js +154 -75
  6. package/dist/lib/config.js.map +1 -1
  7. package/dist/lib/container.d.ts +11 -9
  8. package/dist/lib/container.js +749 -282
  9. package/dist/lib/container.js.map +1 -1
  10. package/dist/lib/dataset-mcp.js +2 -9
  11. package/dist/lib/dataset-mcp.js.map +1 -1
  12. package/dist/lib/display-mcp.d.ts +2 -2
  13. package/dist/lib/display-mcp.js +17 -38
  14. package/dist/lib/display-mcp.js.map +1 -1
  15. package/dist/lib/doctor.js +8 -0
  16. package/dist/lib/doctor.js.map +1 -1
  17. package/dist/lib/explorer-claude.js +36 -1
  18. package/dist/lib/explorer-claude.js.map +1 -1
  19. package/dist/lib/explorer-eval.js +3 -2
  20. package/dist/lib/explorer-eval.js.map +1 -1
  21. package/dist/lib/image-pull-lock.d.ts +18 -0
  22. package/dist/lib/image-pull-lock.js +167 -0
  23. package/dist/lib/image-pull-lock.js.map +1 -0
  24. package/dist/lib/init.js +22 -19
  25. package/dist/lib/init.js.map +1 -1
  26. package/dist/lib/slurm-cli-passthrough.d.ts +12 -2
  27. package/dist/lib/slurm-cli-passthrough.js +401 -143
  28. package/dist/lib/slurm-cli-passthrough.js.map +1 -1
  29. package/dist/lib/startup-stage-lock.d.ts +21 -0
  30. package/dist/lib/startup-stage-lock.js +196 -0
  31. package/dist/lib/startup-stage-lock.js.map +1 -0
  32. package/dist/lib/ui.d.ts +40 -0
  33. package/dist/lib/ui.html +4953 -3366
  34. package/dist/lib/ui.js +1771 -297
  35. package/dist/lib/ui.js.map +1 -1
  36. package/dist/lib/web-terminal-startup-readiness.d.ts +8 -0
  37. package/dist/lib/web-terminal-startup-readiness.js +29 -0
  38. package/dist/lib/web-terminal-startup-readiness.js.map +1 -0
  39. package/dist/lib/web-terminal.d.ts +51 -0
  40. package/dist/lib/web-terminal.js +171 -1
  41. package/dist/lib/web-terminal.js.map +1 -1
  42. package/dist/mcp-bundles/dataset-mcp.bundle.mjs +144 -93
  43. package/dist/mcp-bundles/display-mcp.bundle.mjs +35 -43
  44. package/dist/mcp-bundles/explorer-mcp.bundle.mjs +263 -146
  45. package/dist/mcp-bundles/results-mcp.bundle.mjs +39 -41
  46. package/dist/mcp-bundles/slurm-mcp.bundle.mjs +19 -21
  47. package/package.json +1 -1
@@ -2989,7 +2989,7 @@ var require_compile = __commonJS({
2989
2989
  const schOrFunc = root.refs[ref];
2990
2990
  if (schOrFunc)
2991
2991
  return schOrFunc;
2992
- let _sch = resolve7.call(this, root, ref);
2992
+ let _sch = resolve8.call(this, root, ref);
2993
2993
  if (_sch === void 0) {
2994
2994
  const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
2995
2995
  const { schemaId } = this.opts;
@@ -3016,7 +3016,7 @@ var require_compile = __commonJS({
3016
3016
  function sameSchemaEnv(s1, s2) {
3017
3017
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3018
3018
  }
3019
- function resolve7(root, ref) {
3019
+ function resolve8(root, ref) {
3020
3020
  let sch;
3021
3021
  while (typeof (sch = this.refs[ref]) == "string")
3022
3022
  ref = sch;
@@ -3591,7 +3591,7 @@ var require_fast_uri = __commonJS({
3591
3591
  }
3592
3592
  return uri;
3593
3593
  }
3594
- function resolve7(baseURI, relativeURI, options) {
3594
+ function resolve8(baseURI, relativeURI, options) {
3595
3595
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3596
3596
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3597
3597
  schemelessOptions.skipEscape = true;
@@ -3818,7 +3818,7 @@ var require_fast_uri = __commonJS({
3818
3818
  var fastUri = {
3819
3819
  SCHEMES,
3820
3820
  normalize,
3821
- resolve: resolve7,
3821
+ resolve: resolve8,
3822
3822
  resolveComponent,
3823
3823
  equal,
3824
3824
  serialize,
@@ -7209,6 +7209,7 @@ __export(config_exports, {
7209
7209
  LABGATE_DIR: () => LABGATE_DIR,
7210
7210
  PRIVATE_DIR_MODE: () => PRIVATE_DIR_MODE,
7211
7211
  PRIVATE_FILE_MODE: () => PRIVATE_FILE_MODE,
7212
+ buildConfigFromRaw: () => buildConfigFromRaw,
7212
7213
  ensurePrivateDir: () => ensurePrivateDir,
7213
7214
  ensurePrivateFile: () => ensurePrivateFile,
7214
7215
  findLockedFieldConflicts: () => findLockedFieldConflicts,
@@ -7232,14 +7233,19 @@ __export(config_exports, {
7232
7233
  getSessionsDir: () => getSessionsDir,
7233
7234
  getSlurmDbPath: () => getSlurmDbPath,
7234
7235
  getUiSocketPath: () => getUiSocketPath,
7236
+ isImagesDirOverridden: () => isImagesDirOverridden,
7235
7237
  isKnownPluginId: () => isKnownPluginId,
7236
7238
  loadConfig: () => loadConfig,
7237
7239
  loadEffectiveConfig: () => loadEffectiveConfig,
7240
+ parseRawConfigText: () => parseRawConfigText,
7241
+ readRawConfigFile: () => readRawConfigFile,
7238
7242
  shouldClaudeHeadlessRunWithAllowedPermissions: () => shouldClaudeHeadlessRunWithAllowedPermissions,
7239
- validateConfig: () => validateConfig
7243
+ stripJsonComments: () => stripJsonComments,
7244
+ validateConfig: () => validateConfig,
7245
+ writeRawConfigFile: () => writeRawConfigFile
7240
7246
  });
7241
- import { readFileSync as readFileSync3, existsSync as existsSync3, chmodSync, mkdirSync } from "fs";
7242
- import { join as join3 } from "path";
7247
+ import { readFileSync as readFileSync3, existsSync as existsSync3, chmodSync, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
7248
+ import { dirname as dirname2, join as join3, resolve } from "path";
7243
7249
  import { homedir as homedir3 } from "os";
7244
7250
  function isKnownPluginId(pluginId) {
7245
7251
  return KNOWN_PLUGIN_ID_SET.has(pluginId);
@@ -7254,6 +7260,12 @@ function sanitizePluginMap(rawPlugins) {
7254
7260
  }
7255
7261
  return merged;
7256
7262
  }
7263
+ function resolveImagesDirOverride(raw) {
7264
+ if (typeof raw !== "string") return null;
7265
+ const trimmed = raw.trim();
7266
+ if (!trimmed) return null;
7267
+ return resolve(trimmed.replace(/^~/, homedir3()));
7268
+ }
7257
7269
  function ensurePrivateDir(path) {
7258
7270
  mkdirSync(path, { recursive: true, mode: PRIVATE_DIR_MODE });
7259
7271
  try {
@@ -7277,8 +7289,16 @@ function getSandboxHome() {
7277
7289
  return join3(LABGATE_DIR, "ai-home");
7278
7290
  }
7279
7291
  function getImagesDir() {
7292
+ const envOverride = resolveImagesDirOverride(process.env.LABGATE_IMAGES_DIR);
7293
+ if (envOverride) return envOverride;
7294
+ const configOverride = resolveImagesDirOverride(loadConfig().images_dir);
7295
+ if (configOverride) return configOverride;
7280
7296
  return join3(LABGATE_DIR, "images");
7281
7297
  }
7298
+ function isImagesDirOverridden() {
7299
+ if (resolveImagesDirOverride(process.env.LABGATE_IMAGES_DIR)) return true;
7300
+ return resolveImagesDirOverride(loadConfig().images_dir) !== null;
7301
+ }
7282
7302
  function getEmptyDir() {
7283
7303
  return join3(LABGATE_DIR, "empty");
7284
7304
  }
@@ -7327,12 +7347,104 @@ function getExplorerLockDir(experimentId) {
7327
7347
  function getExplorerWorktreesDir(experimentId) {
7328
7348
  return join3(getExplorerWorktreesRootDir(), experimentId);
7329
7349
  }
7330
- function shouldClaudeHeadlessRunWithAllowedPermissions(config2) {
7331
- return config2.headless?.claude_run_with_allowed_permissions !== false;
7350
+ function shouldClaudeHeadlessRunWithAllowedPermissions(_config) {
7351
+ return false;
7332
7352
  }
7333
7353
  function cloneConfig(config2) {
7334
7354
  return JSON.parse(JSON.stringify(config2));
7335
7355
  }
7356
+ function normalizeLegacyRuntime(rawRuntime, warnOnLegacyRuntime) {
7357
+ if (rawRuntime === "docker") {
7358
+ if (warnOnLegacyRuntime) {
7359
+ console.error('Warning: runtime "docker" is deprecated. Using "podman".');
7360
+ }
7361
+ return "podman";
7362
+ }
7363
+ if (rawRuntime === "singularity") {
7364
+ if (warnOnLegacyRuntime) {
7365
+ console.error('Warning: runtime "singularity" is deprecated. Using "apptainer".');
7366
+ }
7367
+ return "apptainer";
7368
+ }
7369
+ return rawRuntime;
7370
+ }
7371
+ function stripJsonComments(rawText) {
7372
+ return rawText.split("\n").filter((line) => !line.trimStart().startsWith("//")).join("\n");
7373
+ }
7374
+ function parseRawConfigText(rawText) {
7375
+ const stripped = stripJsonComments(rawText).trim();
7376
+ if (!stripped) return {};
7377
+ const parsed = JSON.parse(stripped);
7378
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
7379
+ throw new Error("Config root must be a JSON object");
7380
+ }
7381
+ return parsed;
7382
+ }
7383
+ function buildConfigFromRaw(rawInput, options = {}) {
7384
+ const raw = rawInput;
7385
+ const runtime = normalizeLegacyRuntime(
7386
+ raw.runtime,
7387
+ options.warnOnLegacyRuntime ?? false
7388
+ );
7389
+ return {
7390
+ runtime: runtime ?? DEFAULT_CONFIG.runtime,
7391
+ images_dir: raw.images_dir ?? DEFAULT_CONFIG.images_dir,
7392
+ image: raw.image ?? DEFAULT_CONFIG.image,
7393
+ session_timeout_hours: raw.session_timeout_hours ?? DEFAULT_CONFIG.session_timeout_hours,
7394
+ filesystem: {
7395
+ extra_paths: raw.filesystem?.extra_paths ?? [...DEFAULT_CONFIG.filesystem.extra_paths],
7396
+ blocked_patterns: raw.filesystem?.blocked_patterns ?? [...DEFAULT_CONFIG.filesystem.blocked_patterns]
7397
+ },
7398
+ datasets: Array.isArray(raw.datasets) ? raw.datasets : [],
7399
+ commands: {
7400
+ blacklist: raw.commands?.blacklist ?? [...DEFAULT_CONFIG.commands.blacklist],
7401
+ ensure_commands: raw.commands?.ensure_commands ?? [...DEFAULT_CONFIG.commands.ensure_commands ?? []]
7402
+ },
7403
+ network: {
7404
+ mode: raw.network?.mode ?? DEFAULT_CONFIG.network.mode,
7405
+ allowed_domains: raw.network?.allowed_domains ?? [...DEFAULT_CONFIG.network.allowed_domains]
7406
+ },
7407
+ slurm: {
7408
+ enabled: raw.slurm?.enabled ?? DEFAULT_CONFIG.slurm.enabled,
7409
+ poll_interval_seconds: raw.slurm?.poll_interval_seconds ?? DEFAULT_CONFIG.slurm.poll_interval_seconds,
7410
+ sacct_lookback_hours: raw.slurm?.sacct_lookback_hours ?? DEFAULT_CONFIG.slurm.sacct_lookback_hours,
7411
+ mcp_server: raw.slurm?.mcp_server ?? DEFAULT_CONFIG.slurm.mcp_server
7412
+ },
7413
+ audit: {
7414
+ enabled: raw.audit?.enabled ?? DEFAULT_CONFIG.audit.enabled,
7415
+ log_dir: raw.audit?.log_dir ?? DEFAULT_CONFIG.audit.log_dir
7416
+ },
7417
+ automation: {
7418
+ api_key: raw.automation?.api_key ?? DEFAULT_CONFIG.automation?.api_key,
7419
+ model: raw.automation?.model ?? DEFAULT_CONFIG.automation.model,
7420
+ system_prompt: raw.automation?.system_prompt ?? DEFAULT_CONFIG.automation.system_prompt,
7421
+ trigger_patterns: raw.automation?.trigger_patterns ?? [...DEFAULT_CONFIG.automation.trigger_patterns],
7422
+ context_lines: raw.automation?.context_lines ?? DEFAULT_CONFIG.automation.context_lines,
7423
+ delay_ms: raw.automation?.delay_ms ?? DEFAULT_CONFIG.automation.delay_ms,
7424
+ max_turns: raw.automation?.max_turns ?? DEFAULT_CONFIG.automation.max_turns,
7425
+ max_tokens: raw.automation?.max_tokens ?? DEFAULT_CONFIG.automation.max_tokens
7426
+ },
7427
+ headless: {
7428
+ claude_run_with_allowed_permissions: raw.headless?.claude_run_with_allowed_permissions ?? DEFAULT_CONFIG.headless?.claude_run_with_allowed_permissions ?? false,
7429
+ continuation_in_other_terminals: raw.headless?.continuation_in_other_terminals ?? DEFAULT_CONFIG.headless?.continuation_in_other_terminals ?? true,
7430
+ git_integration: raw.headless?.git_integration ?? DEFAULT_CONFIG.headless?.git_integration ?? false
7431
+ },
7432
+ plugins: sanitizePluginMap(raw.plugins)
7433
+ };
7434
+ }
7435
+ function readRawConfigFile(configPath = getConfigPath()) {
7436
+ if (!existsSync3(configPath)) return {};
7437
+ ensurePrivateFile(configPath);
7438
+ return parseRawConfigText(readFileSync3(configPath, "utf-8"));
7439
+ }
7440
+ function writeRawConfigFile(rawConfig, configPath = getConfigPath()) {
7441
+ ensurePrivateDir(dirname2(configPath));
7442
+ writeFileSync2(configPath, JSON.stringify(rawConfig, null, 2) + "\n", {
7443
+ encoding: "utf-8",
7444
+ mode: PRIVATE_FILE_MODE
7445
+ });
7446
+ ensurePrivateFile(configPath);
7447
+ }
7336
7448
  function validateConfig(config2) {
7337
7449
  const errors = [];
7338
7450
  const networkMode = config2.network?.mode;
@@ -7340,11 +7452,15 @@ function validateConfig(config2) {
7340
7452
  const blockedPatterns = config2.filesystem?.blocked_patterns;
7341
7453
  const extraPaths = config2.filesystem?.extra_paths;
7342
7454
  const blacklist = config2.commands?.blacklist;
7455
+ const ensureCommands = config2.commands?.ensure_commands;
7343
7456
  const plugins = config2.plugins;
7344
7457
  const headless = config2.headless;
7345
7458
  if (!VALID_RUNTIMES2.includes(config2.runtime)) {
7346
7459
  errors.push(`Invalid runtime: "${config2.runtime}". Must be one of: ${VALID_RUNTIMES2.join(", ")}`);
7347
7460
  }
7461
+ if (config2.images_dir !== void 0 && typeof config2.images_dir !== "string") {
7462
+ errors.push("images_dir must be a string");
7463
+ }
7348
7464
  if (!config2.image || typeof config2.image !== "string") {
7349
7465
  errors.push("image must be a non-empty string");
7350
7466
  }
@@ -7366,6 +7482,22 @@ function validateConfig(config2) {
7366
7482
  if (!Array.isArray(blacklist)) {
7367
7483
  errors.push("commands.blacklist must be an array");
7368
7484
  }
7485
+ if (ensureCommands !== void 0) {
7486
+ if (!Array.isArray(ensureCommands)) {
7487
+ errors.push("commands.ensure_commands must be an array");
7488
+ } else {
7489
+ for (let i = 0; i < ensureCommands.length; i++) {
7490
+ const command = ensureCommands[i];
7491
+ if (typeof command !== "string" || !command.trim()) {
7492
+ errors.push(`commands.ensure_commands[${i}] must be a non-empty string`);
7493
+ } else if (!/^[a-zA-Z0-9._+-]+$/.test(command)) {
7494
+ errors.push(
7495
+ `commands.ensure_commands[${i}] contains invalid characters; use letters, digits, ., _, +, - only`
7496
+ );
7497
+ }
7498
+ }
7499
+ }
7500
+ }
7369
7501
  if (plugins !== void 0) {
7370
7502
  if (!plugins || typeof plugins !== "object" || Array.isArray(plugins)) {
7371
7503
  errors.push("plugins must be an object");
@@ -7492,61 +7624,8 @@ function loadConfig() {
7492
7624
  }
7493
7625
  ensurePrivateFile(configPath);
7494
7626
  try {
7495
- const rawText = readFileSync3(configPath, "utf-8");
7496
- const stripped = rawText.split("\n").filter((line) => !line.trimStart().startsWith("//")).join("\n");
7497
- const raw = JSON.parse(stripped);
7498
- const rawRuntime = raw.runtime;
7499
- let runtime = rawRuntime;
7500
- if (rawRuntime === "docker") {
7501
- runtime = "podman";
7502
- console.error('Warning: runtime "docker" is deprecated. Using "podman".');
7503
- } else if (rawRuntime === "singularity") {
7504
- runtime = "apptainer";
7505
- console.error('Warning: runtime "singularity" is deprecated. Using "apptainer".');
7506
- }
7507
- const config2 = {
7508
- runtime: runtime ?? DEFAULT_CONFIG.runtime,
7509
- image: raw.image ?? DEFAULT_CONFIG.image,
7510
- session_timeout_hours: raw.session_timeout_hours ?? DEFAULT_CONFIG.session_timeout_hours,
7511
- filesystem: {
7512
- extra_paths: raw.filesystem?.extra_paths ?? [...DEFAULT_CONFIG.filesystem.extra_paths],
7513
- blocked_patterns: raw.filesystem?.blocked_patterns ?? [...DEFAULT_CONFIG.filesystem.blocked_patterns]
7514
- },
7515
- datasets: Array.isArray(raw.datasets) ? raw.datasets : [],
7516
- commands: {
7517
- blacklist: raw.commands?.blacklist ?? [...DEFAULT_CONFIG.commands.blacklist]
7518
- },
7519
- network: {
7520
- mode: raw.network?.mode ?? DEFAULT_CONFIG.network.mode,
7521
- allowed_domains: raw.network?.allowed_domains ?? [...DEFAULT_CONFIG.network.allowed_domains]
7522
- },
7523
- slurm: {
7524
- enabled: raw.slurm?.enabled ?? DEFAULT_CONFIG.slurm.enabled,
7525
- poll_interval_seconds: raw.slurm?.poll_interval_seconds ?? DEFAULT_CONFIG.slurm.poll_interval_seconds,
7526
- sacct_lookback_hours: raw.slurm?.sacct_lookback_hours ?? DEFAULT_CONFIG.slurm.sacct_lookback_hours,
7527
- mcp_server: raw.slurm?.mcp_server ?? DEFAULT_CONFIG.slurm.mcp_server
7528
- },
7529
- audit: {
7530
- enabled: raw.audit?.enabled ?? DEFAULT_CONFIG.audit.enabled,
7531
- log_dir: raw.audit?.log_dir ?? DEFAULT_CONFIG.audit.log_dir
7532
- },
7533
- automation: {
7534
- api_key: raw.automation?.api_key ?? DEFAULT_CONFIG.automation?.api_key,
7535
- model: raw.automation?.model ?? DEFAULT_CONFIG.automation.model,
7536
- system_prompt: raw.automation?.system_prompt ?? DEFAULT_CONFIG.automation.system_prompt,
7537
- trigger_patterns: raw.automation?.trigger_patterns ?? [...DEFAULT_CONFIG.automation.trigger_patterns],
7538
- context_lines: raw.automation?.context_lines ?? DEFAULT_CONFIG.automation.context_lines,
7539
- delay_ms: raw.automation?.delay_ms ?? DEFAULT_CONFIG.automation.delay_ms,
7540
- max_turns: raw.automation?.max_turns ?? DEFAULT_CONFIG.automation.max_turns,
7541
- max_tokens: raw.automation?.max_tokens ?? DEFAULT_CONFIG.automation.max_tokens
7542
- },
7543
- headless: {
7544
- claude_run_with_allowed_permissions: raw.headless?.claude_run_with_allowed_permissions ?? DEFAULT_CONFIG.headless?.claude_run_with_allowed_permissions ?? true,
7545
- continuation_in_other_terminals: raw.headless?.continuation_in_other_terminals ?? DEFAULT_CONFIG.headless?.continuation_in_other_terminals ?? true,
7546
- git_integration: raw.headless?.git_integration ?? DEFAULT_CONFIG.headless?.git_integration ?? false
7547
- },
7548
- plugins: sanitizePluginMap(raw.plugins)
7549
- };
7627
+ const raw = readRawConfigFile(configPath);
7628
+ const config2 = buildConfigFromRaw(raw, { warnOnLegacyRuntime: true });
7550
7629
  const errors = validateConfig(config2);
7551
7630
  if (errors.length > 0) {
7552
7631
  console.error(`Warning: invalid config in ${configPath}:`);
@@ -7623,6 +7702,7 @@ var init_config = __esm({
7623
7702
  "use strict";
7624
7703
  DEFAULT_CONFIG = {
7625
7704
  runtime: "auto",
7705
+ images_dir: "",
7626
7706
  // Default sandbox image: includes python3 + basic build tools (git/make/g++).
7627
7707
  // Note: pip/venv are not included by default in Debian; users may still need a
7628
7708
  // custom image for richer Python workflows.
@@ -7655,7 +7735,8 @@ var init_config = __esm({
7655
7735
  "mkfs",
7656
7736
  "reboot",
7657
7737
  "shutdown"
7658
- ]
7738
+ ],
7739
+ ensure_commands: ["git"]
7659
7740
  },
7660
7741
  network: {
7661
7742
  mode: "host",
@@ -7681,13 +7762,9 @@ var init_config = __esm({
7681
7762
  },
7682
7763
  plugins: {
7683
7764
  files: true,
7765
+ activity: true,
7684
7766
  datasets: false,
7685
7767
  results: false,
7686
- charting: true,
7687
- molecular: true,
7688
- genomics: true,
7689
- phylogenetics: true,
7690
- network: true,
7691
7768
  slurm: true,
7692
7769
  explorer: true,
7693
7770
  automation: false
@@ -7708,7 +7785,7 @@ var init_config = __esm({
7708
7785
  max_tokens: 1024
7709
7786
  },
7710
7787
  headless: {
7711
- claude_run_with_allowed_permissions: true,
7788
+ claude_run_with_allowed_permissions: false,
7712
7789
  continuation_in_other_terminals: true,
7713
7790
  git_integration: false
7714
7791
  }
@@ -12018,41 +12095,41 @@ var require_queue = __commonJS({
12018
12095
  queue.drained = drained;
12019
12096
  return queue;
12020
12097
  function push(value) {
12021
- var p = new Promise(function(resolve7, reject) {
12098
+ var p = new Promise(function(resolve8, reject) {
12022
12099
  pushCb(value, function(err, result) {
12023
12100
  if (err) {
12024
12101
  reject(err);
12025
12102
  return;
12026
12103
  }
12027
- resolve7(result);
12104
+ resolve8(result);
12028
12105
  });
12029
12106
  });
12030
12107
  p.catch(noop);
12031
12108
  return p;
12032
12109
  }
12033
12110
  function unshift(value) {
12034
- var p = new Promise(function(resolve7, reject) {
12111
+ var p = new Promise(function(resolve8, reject) {
12035
12112
  unshiftCb(value, function(err, result) {
12036
12113
  if (err) {
12037
12114
  reject(err);
12038
12115
  return;
12039
12116
  }
12040
- resolve7(result);
12117
+ resolve8(result);
12041
12118
  });
12042
12119
  });
12043
12120
  p.catch(noop);
12044
12121
  return p;
12045
12122
  }
12046
12123
  function drained() {
12047
- var p = new Promise(function(resolve7) {
12124
+ var p = new Promise(function(resolve8) {
12048
12125
  process.nextTick(function() {
12049
12126
  if (queue.idle()) {
12050
- resolve7();
12127
+ resolve8();
12051
12128
  } else {
12052
12129
  var previousDrain = queue.drain;
12053
12130
  queue.drain = function() {
12054
12131
  if (typeof previousDrain === "function") previousDrain();
12055
- resolve7();
12132
+ resolve8();
12056
12133
  queue.drain = previousDrain;
12057
12134
  };
12058
12135
  }
@@ -12538,9 +12615,9 @@ var require_stream3 = __commonJS({
12538
12615
  });
12539
12616
  }
12540
12617
  _getStat(filepath) {
12541
- return new Promise((resolve7, reject) => {
12618
+ return new Promise((resolve8, reject) => {
12542
12619
  this._stat(filepath, this._fsStatSettings, (error49, stats) => {
12543
- return error49 === null ? resolve7(stats) : reject(error49);
12620
+ return error49 === null ? resolve8(stats) : reject(error49);
12544
12621
  });
12545
12622
  });
12546
12623
  }
@@ -12564,10 +12641,10 @@ var require_async5 = __commonJS({
12564
12641
  this._readerStream = new stream_1.default(this._settings);
12565
12642
  }
12566
12643
  dynamic(root, options) {
12567
- return new Promise((resolve7, reject) => {
12644
+ return new Promise((resolve8, reject) => {
12568
12645
  this._walkAsync(root, options, (error49, entries) => {
12569
12646
  if (error49 === null) {
12570
- resolve7(entries);
12647
+ resolve8(entries);
12571
12648
  } else {
12572
12649
  reject(error49);
12573
12650
  }
@@ -12577,10 +12654,10 @@ var require_async5 = __commonJS({
12577
12654
  async static(patterns, options) {
12578
12655
  const entries = [];
12579
12656
  const stream = this._readerStream.static(patterns, options);
12580
- return new Promise((resolve7, reject) => {
12657
+ return new Promise((resolve8, reject) => {
12581
12658
  stream.once("error", reject);
12582
12659
  stream.on("data", (entry) => entries.push(entry));
12583
- stream.once("end", () => resolve7(entries));
12660
+ stream.once("end", () => resolve8(entries));
12584
12661
  });
12585
12662
  }
12586
12663
  };
@@ -34413,7 +34490,7 @@ var Protocol = class {
34413
34490
  return;
34414
34491
  }
34415
34492
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
34416
- await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
34493
+ await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
34417
34494
  options?.signal?.throwIfAborted();
34418
34495
  }
34419
34496
  } catch (error49) {
@@ -34430,7 +34507,7 @@ var Protocol = class {
34430
34507
  */
34431
34508
  request(request, resultSchema, options) {
34432
34509
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
34433
- return new Promise((resolve7, reject) => {
34510
+ return new Promise((resolve8, reject) => {
34434
34511
  const earlyReject = (error49) => {
34435
34512
  reject(error49);
34436
34513
  };
@@ -34508,7 +34585,7 @@ var Protocol = class {
34508
34585
  if (!parseResult.success) {
34509
34586
  reject(parseResult.error);
34510
34587
  } else {
34511
- resolve7(parseResult.data);
34588
+ resolve8(parseResult.data);
34512
34589
  }
34513
34590
  } catch (error49) {
34514
34591
  reject(error49);
@@ -34769,12 +34846,12 @@ var Protocol = class {
34769
34846
  }
34770
34847
  } catch {
34771
34848
  }
34772
- return new Promise((resolve7, reject) => {
34849
+ return new Promise((resolve8, reject) => {
34773
34850
  if (signal.aborted) {
34774
34851
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
34775
34852
  return;
34776
34853
  }
34777
- const timeoutId = setTimeout(resolve7, interval);
34854
+ const timeoutId = setTimeout(resolve8, interval);
34778
34855
  signal.addEventListener("abort", () => {
34779
34856
  clearTimeout(timeoutId);
34780
34857
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -35733,7 +35810,7 @@ var McpServer = class {
35733
35810
  let task = createTaskResult.task;
35734
35811
  const pollInterval = task.pollInterval ?? 5e3;
35735
35812
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
35736
- await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
35813
+ await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
35737
35814
  const updatedTask = await extra.taskStore.getTask(taskId);
35738
35815
  if (!updatedTask) {
35739
35816
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -36376,12 +36453,12 @@ var StdioServerTransport = class {
36376
36453
  this.onclose?.();
36377
36454
  }
36378
36455
  send(message) {
36379
- return new Promise((resolve7) => {
36456
+ return new Promise((resolve8) => {
36380
36457
  const json2 = serializeMessage(message);
36381
36458
  if (this._stdout.write(json2)) {
36382
- resolve7();
36459
+ resolve8();
36383
36460
  } else {
36384
- this._stdout.once("drain", resolve7);
36461
+ this._stdout.once("drain", resolve8);
36385
36462
  }
36386
36463
  });
36387
36464
  }
@@ -36389,14 +36466,14 @@ var StdioServerTransport = class {
36389
36466
 
36390
36467
  // src/lib/explorer-mcp.ts
36391
36468
  init_config();
36392
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync8 } from "fs";
36469
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
36393
36470
  import { join as join11 } from "path";
36394
36471
 
36395
36472
  // src/lib/explorer-store.ts
36396
36473
  init_config();
36397
36474
  import { randomUUID } from "crypto";
36398
- import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
36399
- import { dirname as dirname2 } from "path";
36475
+ import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
36476
+ import { dirname as dirname3 } from "path";
36400
36477
 
36401
36478
  // src/lib/explorer-retention.ts
36402
36479
  var DEFAULT_RETENTION_POLICY = {
@@ -36634,7 +36711,7 @@ function mapEventRow(row) {
36634
36711
  var ExplorerStore = class {
36635
36712
  constructor(dbPath = getExplorerDbPath()) {
36636
36713
  this.dbPath = dbPath;
36637
- const dir = dirname2(dbPath);
36714
+ const dir = dirname3(dbPath);
36638
36715
  if (!existsSync4(dir)) {
36639
36716
  mkdirSync2(dir, { recursive: true, mode: 448 });
36640
36717
  }
@@ -37285,7 +37362,7 @@ var ExplorerStore = class {
37285
37362
  flushJson() {
37286
37363
  if (!this.jsonPath) return;
37287
37364
  try {
37288
- writeFileSync2(this.jsonPath, JSON.stringify(this.jsonStore, null, 2) + "\n", {
37365
+ writeFileSync3(this.jsonPath, JSON.stringify(this.jsonStore, null, 2) + "\n", {
37289
37366
  encoding: "utf-8",
37290
37367
  mode: 384
37291
37368
  });
@@ -37300,7 +37377,7 @@ var ExplorerStore = class {
37300
37377
 
37301
37378
  // src/lib/explorer-git.ts
37302
37379
  import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
37303
- import { dirname as dirname3, resolve } from "path";
37380
+ import { dirname as dirname4, resolve as resolve2 } from "path";
37304
37381
  import { execFileSync, spawnSync } from "child_process";
37305
37382
  function runGit(args, cwd) {
37306
37383
  return execFileSync("git", args, {
@@ -37332,7 +37409,7 @@ function sanitizeBranchPart(value) {
37332
37409
  return value.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/--+/g, "-").replace(/^-+/, "").replace(/-+$/, "").slice(0, 100);
37333
37410
  }
37334
37411
  function ensureGitRepository(repoPath) {
37335
- const full = resolve(repoPath);
37412
+ const full = resolve2(repoPath);
37336
37413
  const check2 = runGitStatus(["-C", full, "rev-parse", "--git-dir"]);
37337
37414
  if (check2.status !== 0) {
37338
37415
  throw new Error(`Not a git repository: ${full}`);
@@ -37340,20 +37417,20 @@ function ensureGitRepository(repoPath) {
37340
37417
  }
37341
37418
  function resolveGitSha(repoPath, ref = "HEAD") {
37342
37419
  ensureGitRepository(repoPath);
37343
- return runGit(["-C", resolve(repoPath), "rev-parse", ref]);
37420
+ return runGit(["-C", resolve2(repoPath), "rev-parse", ref]);
37344
37421
  }
37345
37422
  function cloneRepository(sourceRepoPath, targetRepoPath) {
37346
- const source = resolve(sourceRepoPath);
37347
- const target = resolve(targetRepoPath);
37348
- const targetParent = dirname3(target);
37423
+ const source = resolve2(sourceRepoPath);
37424
+ const target = resolve2(targetRepoPath);
37425
+ const targetParent = dirname4(target);
37349
37426
  if (!existsSync5(targetParent)) {
37350
37427
  mkdirSync3(targetParent, { recursive: true, mode: 448 });
37351
37428
  }
37352
37429
  runGit(["clone", "--quiet", source, target]);
37353
37430
  }
37354
37431
  function createRunWorkspace(repoPath, worktreePath, parentSha, experimentId, runId) {
37355
- const fullRepo = resolve(repoPath);
37356
- const fullWorktree = resolve(worktreePath);
37432
+ const fullRepo = resolve2(repoPath);
37433
+ const fullWorktree = resolve2(worktreePath);
37357
37434
  const parent = sanitizeBranchPart(parentSha);
37358
37435
  if (!parent) {
37359
37436
  throw new Error("parentSha is required");
@@ -37361,7 +37438,7 @@ function createRunWorkspace(repoPath, worktreePath, parentSha, experimentId, run
37361
37438
  const expPart = sanitizeBranchPart(experimentId) || "exp";
37362
37439
  const runPart = sanitizeBranchPart(runId) || "run";
37363
37440
  const branchName = `explorer/${expPart}/${runPart}`;
37364
- const parentDir = dirname3(fullWorktree);
37441
+ const parentDir = dirname4(fullWorktree);
37365
37442
  if (!existsSync5(parentDir)) {
37366
37443
  mkdirSync3(parentDir, { recursive: true, mode: 448 });
37367
37444
  }
@@ -37372,7 +37449,7 @@ function createRunWorkspace(repoPath, worktreePath, parentSha, experimentId, run
37372
37449
  };
37373
37450
  }
37374
37451
  function commitAll(worktreePath, message, opts) {
37375
- const fullWorktree = resolve(worktreePath);
37452
+ const fullWorktree = resolve2(worktreePath);
37376
37453
  const commitMessage = message.trim() || "explorer run";
37377
37454
  const allowEmpty = opts?.allow_empty === true;
37378
37455
  runGit(["-C", fullWorktree, "add", "-A"]);
@@ -37413,12 +37490,12 @@ ${commitResult.stdout || ""}`.trim();
37413
37490
  return runGit(["-C", fullWorktree, "rev-parse", "HEAD"]);
37414
37491
  }
37415
37492
  function getDiff(repoPath, parentSha, commitSha) {
37416
- const fullRepo = resolve(repoPath);
37493
+ const fullRepo = resolve2(repoPath);
37417
37494
  return runGitRaw(["-C", fullRepo, "diff", "--binary", `${parentSha}..${commitSha}`]);
37418
37495
  }
37419
37496
  function cleanupWorkspace(repoPath, worktreePath, branchName) {
37420
- const fullRepo = resolve(repoPath);
37421
- const fullWorktree = resolve(worktreePath);
37497
+ const fullRepo = resolve2(repoPath);
37498
+ const fullWorktree = resolve2(worktreePath);
37422
37499
  runGit(["-C", fullRepo, "worktree", "remove", "--force", fullWorktree]);
37423
37500
  if (branchName && branchName.trim()) {
37424
37501
  const branch = branchName.trim();
@@ -37430,8 +37507,8 @@ function cleanupWorkspace(repoPath, worktreePath, branchName) {
37430
37507
  }
37431
37508
 
37432
37509
  // src/lib/explorer-eval.ts
37433
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
37434
- import { join as join4, resolve as resolve2 } from "path";
37510
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
37511
+ import { join as join4, resolve as resolve3 } from "path";
37435
37512
  import { spawnSync as spawnSync2 } from "child_process";
37436
37513
  function parseLastNonEmptyLine(text) {
37437
37514
  const lines = text.split(/\r?\n/);
@@ -37445,17 +37522,17 @@ function shellForCurrentPlatform() {
37445
37522
  if (process.platform === "win32") {
37446
37523
  return process.env.COMSPEC || "cmd.exe";
37447
37524
  }
37448
- return process.env.SHELL || "/bin/sh";
37525
+ return "/bin/sh";
37449
37526
  }
37450
37527
  function shellArgsForCommand(command) {
37451
37528
  if (process.platform === "win32") {
37452
37529
  return ["/d", "/s", "/c", command];
37453
37530
  }
37454
- return ["-lc", command];
37531
+ return ["-c", command];
37455
37532
  }
37456
37533
  function runEvaluation(opts) {
37457
- const worktreePath = resolve2(opts.worktree_path);
37458
- const artifactDir = resolve2(opts.artifact_dir);
37534
+ const worktreePath = resolve3(opts.worktree_path);
37535
+ const artifactDir = resolve3(opts.artifact_dir);
37459
37536
  const timeoutSec = Number.isFinite(opts.timeout_sec) ? Math.min(86400, Math.max(1, Math.floor(opts.timeout_sec))) : 120;
37460
37537
  mkdirSync4(artifactDir, { recursive: true, mode: 448 });
37461
37538
  const stdoutPath = join4(artifactDir, "stdout.log");
@@ -37474,8 +37551,8 @@ function runEvaluation(opts) {
37474
37551
  const finishedAt = new Date(started + runtimeMs).toISOString();
37475
37552
  const stdout = String(result.stdout || "");
37476
37553
  const stderr = String(result.stderr || "");
37477
- writeFileSync3(stdoutPath, stdout, "utf-8");
37478
- writeFileSync3(stderrPath, stderr, "utf-8");
37554
+ writeFileSync4(stdoutPath, stdout, "utf-8");
37555
+ writeFileSync4(stderrPath, stderr, "utf-8");
37479
37556
  if (result.error && (result.error.code === "ETIMEDOUT" || result.signal === "SIGTERM")) {
37480
37557
  return {
37481
37558
  status: "timeout",
@@ -37585,17 +37662,17 @@ function runEvaluation(opts) {
37585
37662
  // src/lib/explorer.ts
37586
37663
  init_config();
37587
37664
  import { existsSync as existsSync9, mkdirSync as mkdirSync9 } from "fs";
37588
- import { join as join10, resolve as resolve6 } from "path";
37665
+ import { join as join10, resolve as resolve7 } from "path";
37589
37666
 
37590
37667
  // src/lib/explorer-autopilot.ts
37591
37668
  init_config();
37592
- import { existsSync as existsSync7, mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "fs";
37593
- import { join as join7, resolve as resolve4 } from "path";
37669
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
37670
+ import { join as join7, resolve as resolve5 } from "path";
37594
37671
  import { spawnSync as spawnSync4 } from "child_process";
37595
37672
 
37596
37673
  // src/lib/explorer-lock.ts
37597
37674
  init_config();
37598
- import { closeSync, mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
37675
+ import { closeSync, mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
37599
37676
  import { join as join5 } from "path";
37600
37677
  function parseLockPid(raw) {
37601
37678
  const firstLine = raw.split(/\r?\n/, 1)[0] || "";
@@ -37646,7 +37723,7 @@ function acquireExperimentLock(experimentId, lockName = "autopilot.lock") {
37646
37723
  return null;
37647
37724
  }
37648
37725
  try {
37649
- writeFileSync4(fd, `${process.pid}
37726
+ writeFileSync5(fd, `${process.pid}
37650
37727
  `, { encoding: "utf-8" });
37651
37728
  } catch (err) {
37652
37729
  try {
@@ -37676,12 +37753,13 @@ function acquireExperimentLock(experimentId, lockName = "autopilot.lock") {
37676
37753
 
37677
37754
  // src/lib/explorer-claude.ts
37678
37755
  init_config();
37679
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
37680
- import { basename, dirname as dirname4, join as join6, resolve as resolve3 } from "path";
37756
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
37757
+ import { basename, dirname as dirname5, join as join6, resolve as resolve4 } from "path";
37681
37758
  import { homedir as homedir4 } from "os";
37682
37759
  import { spawnSync as spawnSync3 } from "child_process";
37683
37760
 
37684
37761
  // src/lib/container.ts
37762
+ import { randomBytes as randomBytes2, createHash } from "crypto";
37685
37763
  var import_fast_glob = __toESM(require_out4());
37686
37764
  init_config();
37687
37765
 
@@ -37889,6 +37967,10 @@ function hasCommand(cmd) {
37889
37967
  }
37890
37968
  }
37891
37969
 
37970
+ // src/lib/image-pull-lock.ts
37971
+ var DEFAULT_PULL_LOCK_WAIT_TIMEOUT_MS = 60 * 60 * 1e3;
37972
+ var DEFAULT_PULL_LOCK_STALE_AFTER_MS = 2 * 60 * 60 * 1e3;
37973
+
37892
37974
  // src/lib/audit.ts
37893
37975
  init_config();
37894
37976
 
@@ -37906,9 +37988,16 @@ init_config();
37906
37988
  // src/lib/slurm-cli-passthrough.ts
37907
37989
  init_config();
37908
37990
 
37991
+ // src/lib/startup-stage-lock.ts
37992
+ var DEFAULT_STAGE_LOCK_WAIT_TIMEOUT_MS = 5 * 60 * 1e3;
37993
+ var DEFAULT_STAGE_LOCK_STALE_AFTER_MS = 15 * 60 * 1e3;
37994
+
37909
37995
  // src/lib/container.ts
37910
37996
  function imageToSifName(image) {
37911
- return image.replace(/[/:]/g, "_") + ".sif";
37997
+ const normalized = String(image || "").trim();
37998
+ const readable = normalized.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 160) || "image";
37999
+ const hash2 = createHash("sha256").update(normalized).digest("hex").slice(0, 12);
38000
+ return `${readable}-${hash2}.sif`;
37912
38001
  }
37913
38002
 
37914
38003
  // src/lib/explorer-claude.ts
@@ -38068,12 +38157,37 @@ function runApptainer(args, cwd, timeoutSec) {
38068
38157
  function ensureDisplayDbFile() {
38069
38158
  const displayDbPath = getDisplayDbPath();
38070
38159
  if (existsSync6(displayDbPath)) return;
38071
- ensurePrivateDir(dirname4(displayDbPath));
38072
- writeFileSync5(displayDbPath, JSON.stringify({ version: 1, events: [] }, null, 2) + "\n", {
38160
+ ensurePrivateDir(dirname5(displayDbPath));
38161
+ writeFileSync6(displayDbPath, JSON.stringify({ version: 1, events: [] }, null, 2) + "\n", {
38073
38162
  encoding: "utf-8",
38074
38163
  mode: PRIVATE_FILE_MODE
38075
38164
  });
38076
38165
  }
38166
+ function buildClaudeHeadlessAddDirArgs(config2) {
38167
+ const dirs = /* @__PURE__ */ new Set();
38168
+ let hasExtraMounts = false;
38169
+ let hasDatasets = false;
38170
+ for (const mount of config2.filesystem.extra_paths || []) {
38171
+ if (!mount || typeof mount.path !== "string") continue;
38172
+ const resolved = mount.path.replace(/^~/, homedir4());
38173
+ const target = `/mnt/${basename(resolved)}`;
38174
+ hasExtraMounts = true;
38175
+ dirs.add(target);
38176
+ }
38177
+ for (const ds of config2.datasets || []) {
38178
+ if (!ds || typeof ds.name !== "string") continue;
38179
+ const name = ds.name.trim();
38180
+ if (!name) continue;
38181
+ hasDatasets = true;
38182
+ dirs.add(`/datasets/${name}`);
38183
+ }
38184
+ if (hasExtraMounts) dirs.add("/mnt");
38185
+ if (hasDatasets) dirs.add("/datasets");
38186
+ return Array.from(dirs).flatMap((dir) => ["--add-dir", dir]);
38187
+ }
38188
+ function buildClaudeHeadlessAllowedToolsArgs() {
38189
+ return ["--allowed-tools", "Glob,Read,Grep,WebFetch"];
38190
+ }
38077
38191
  function buildContainerPrefixArgs(config2, worktreePath, sifPath) {
38078
38192
  const sandboxHome = getSandboxHome();
38079
38193
  const args = [
@@ -38169,7 +38283,8 @@ function normalizeTimeout(raw) {
38169
38283
  return Math.max(30, Math.min(4 * 60 * 60, Math.floor(parsed)));
38170
38284
  }
38171
38285
  function runClaudeHeadlessInWorktree(input) {
38172
- const worktreePath = resolve3(String(input.worktree_path || "").trim());
38286
+ const worktreePathRaw = String(input.worktree_path || "").trim();
38287
+ const worktreePath = worktreePathRaw ? resolve4(worktreePathRaw) : "";
38173
38288
  const prompt = String(input.prompt || "").trim();
38174
38289
  const resumeSessionId = String(input.resume_session_id || "").trim();
38175
38290
  const timeoutSec = normalizeTimeout(input.timeout_sec);
@@ -38198,6 +38313,8 @@ function runClaudeHeadlessInWorktree(input) {
38198
38313
  "--output-format",
38199
38314
  "stream-json",
38200
38315
  "--include-partial-messages",
38316
+ ...buildClaudeHeadlessAddDirArgs(config2),
38317
+ ...buildClaudeHeadlessAllowedToolsArgs(),
38201
38318
  ...shouldClaudeHeadlessRunWithAllowedPermissions(config2) ? ["--dangerously-skip-permissions"] : [],
38202
38319
  ...resumeSessionId ? ["--resume", resumeSessionId] : [],
38203
38320
  prompt
@@ -38362,7 +38479,7 @@ function runStubAgent(repoPath, worktreePath, runId, policy) {
38362
38479
  if (!patchRef) {
38363
38480
  return { ok: false, error: "stub agent mode requires policy.stub_patch_file" };
38364
38481
  }
38365
- const patchPath = patchRef.startsWith("/") ? patchRef : resolve4(repoPath, patchRef);
38482
+ const patchPath = patchRef.startsWith("/") ? patchRef : resolve5(repoPath, patchRef);
38366
38483
  if (!existsSync7(patchPath)) {
38367
38484
  return { ok: false, error: `stub patch file not found: ${patchPath}` };
38368
38485
  }
@@ -38505,13 +38622,13 @@ function writeAgentDebugArtifacts(artifactDir, debug) {
38505
38622
  if (!debug) return;
38506
38623
  try {
38507
38624
  if (debug.activity_log) {
38508
- writeFileSync6(join7(artifactDir, "agent.log"), debug.activity_log, "utf-8");
38625
+ writeFileSync7(join7(artifactDir, "agent.log"), debug.activity_log, "utf-8");
38509
38626
  }
38510
38627
  if (debug.stdout) {
38511
- writeFileSync6(join7(artifactDir, "claude.stdout.log"), debug.stdout, "utf-8");
38628
+ writeFileSync7(join7(artifactDir, "claude.stdout.log"), debug.stdout, "utf-8");
38512
38629
  }
38513
38630
  if (debug.stderr) {
38514
- writeFileSync6(join7(artifactDir, "claude.stderr.log"), debug.stderr, "utf-8");
38631
+ writeFileSync7(join7(artifactDir, "claude.stderr.log"), debug.stderr, "utf-8");
38515
38632
  }
38516
38633
  } catch {
38517
38634
  }
@@ -38558,7 +38675,7 @@ function recoverStaleActiveRuns(experimentId, store, staleAfterSec) {
38558
38675
  ].join("\n");
38559
38676
  try {
38560
38677
  mkdirSync7(run.artifact_dir, { recursive: true, mode: 448 });
38561
- writeFileSync6(join7(run.artifact_dir, "summary.md"), summary, "utf-8");
38678
+ writeFileSync7(join7(run.artifact_dir, "summary.md"), summary, "utf-8");
38562
38679
  } catch {
38563
38680
  }
38564
38681
  const updated = store.recordRunResult(run.id, {
@@ -38682,7 +38799,7 @@ function autopilotTick(input) {
38682
38799
  writeAgentDebugArtifacts(artifactDir, agentResult.debug);
38683
38800
  if (!agentResult.ok) {
38684
38801
  const summary2 = buildSummary(run.id, parentScore, "failed_build", null, "", agentResult.error, agentResult.error);
38685
- writeFileSync6(join7(artifactDir, "summary.md"), summary2, "utf-8");
38802
+ writeFileSync7(join7(artifactDir, "summary.md"), summary2, "utf-8");
38686
38803
  store.recordRunResult(run.id, {
38687
38804
  status: "failed_build",
38688
38805
  metrics_json: JSON.stringify(agentResult.metrics || {}),
@@ -38698,7 +38815,7 @@ function autopilotTick(input) {
38698
38815
  }
38699
38816
  store.recordRunCommit(run.id, agentResult.commit_sha);
38700
38817
  const diffText = getDiff(experiment.repo_path, parent.parent_commit_sha, agentResult.commit_sha);
38701
- writeFileSync6(join7(artifactDir, "diff.patch"), diffText, "utf-8");
38818
+ writeFileSync7(join7(artifactDir, "diff.patch"), diffText, "utf-8");
38702
38819
  const evalResult = runEvaluation({
38703
38820
  worktree_path: workspace.worktree_path,
38704
38821
  eval_command: experiment.eval_command,
@@ -38719,7 +38836,7 @@ function autopilotTick(input) {
38719
38836
  stderr_path: evalResult.stderr_path,
38720
38837
  error: evalResult.error || null
38721
38838
  };
38722
- writeFileSync6(join7(artifactDir, "eval.json"), JSON.stringify(evalPayload, null, 2) + "\n", "utf-8");
38839
+ writeFileSync7(join7(artifactDir, "eval.json"), JSON.stringify(evalPayload, null, 2) + "\n", "utf-8");
38723
38840
  const runStatus = evalResult.status;
38724
38841
  const runScore = Number.isFinite(evalResult.score) ? Number(evalResult.score) : null;
38725
38842
  const mergedMetrics = {
@@ -38735,7 +38852,7 @@ function autopilotTick(input) {
38735
38852
  evalResult.error,
38736
38853
  agentResult.note
38737
38854
  );
38738
- writeFileSync6(join7(artifactDir, "summary.md"), summary, "utf-8");
38855
+ writeFileSync7(join7(artifactDir, "summary.md"), summary, "utf-8");
38739
38856
  store.recordRunResult(run.id, {
38740
38857
  status: runStatus,
38741
38858
  score: runScore,
@@ -38777,7 +38894,7 @@ function autopilotTick(input) {
38777
38894
  if (inFlightArtifactDir) {
38778
38895
  try {
38779
38896
  mkdirSync7(inFlightArtifactDir, { recursive: true, mode: 448 });
38780
- writeFileSync6(join7(inFlightArtifactDir, "summary.md"), summary, "utf-8");
38897
+ writeFileSync7(join7(inFlightArtifactDir, "summary.md"), summary, "utf-8");
38781
38898
  } catch {
38782
38899
  }
38783
38900
  }
@@ -38818,7 +38935,7 @@ function autopilotTick(input) {
38818
38935
 
38819
38936
  // src/lib/explorer-gc.ts
38820
38937
  import { existsSync as existsSync8, readdirSync, statSync as statSync3, unlinkSync as unlinkSync2, rmSync } from "fs";
38821
- import { join as join8, resolve as resolve5 } from "path";
38938
+ import { join as join8, resolve as resolve6 } from "path";
38822
38939
  var FAILED_STATUSES = /* @__PURE__ */ new Set([
38823
38940
  "failed_build",
38824
38941
  "failed_eval",
@@ -38868,7 +38985,7 @@ function safeSize(path) {
38868
38985
  }
38869
38986
  }
38870
38987
  function computeArtifactPlan(run, mode) {
38871
- const artifactDir = resolve5(run.artifact_dir);
38988
+ const artifactDir = resolve6(run.artifact_dir);
38872
38989
  const files = listFilesRecursive(artifactDir);
38873
38990
  const keepBasenames = mode === "minimal" ? /* @__PURE__ */ new Set(["eval.json", "summary.md", "diff.patch", "agent.log"]) : mode === "none" ? /* @__PURE__ */ new Set(["eval.json", "summary.md"]) : /* @__PURE__ */ new Set();
38874
38991
  const keepPaths = [];
@@ -39078,7 +39195,7 @@ function experimentGc(experimentId, store, options) {
39078
39195
  // src/lib/explorer-compare.ts
39079
39196
  init_config();
39080
39197
  import { execFileSync as execFileSync3 } from "child_process";
39081
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
39198
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
39082
39199
  import { join as join9 } from "path";
39083
39200
  function runGit2(repoPath, args) {
39084
39201
  return String(execFileSync3("git", ["-C", repoPath, ...args], {
@@ -39203,7 +39320,7 @@ function runCompare(input, store) {
39203
39320
  const safeB = (bRunId || "base").replace(/[^a-zA-Z0-9._-]+/g, "_");
39204
39321
  const target = join9(compareDir, `${aRun.id}_vs_${safeB}.patch`);
39205
39322
  const patchText = runGit2(experiment.repo_path, ["diff", "--binary", `${bCommitSha}..${aRun.commit_sha}`]);
39206
- writeFileSync7(target, patchText, "utf-8");
39323
+ writeFileSync8(target, patchText, "utf-8");
39207
39324
  patchPath = target;
39208
39325
  }
39209
39326
  const aMetrics = parseMetrics(aRun.metrics_json);
@@ -39275,7 +39392,7 @@ function createExplorerExperiment(options, store) {
39275
39392
  const db = store || new ExplorerStore();
39276
39393
  try {
39277
39394
  ensureExplorerDirs();
39278
- const sourceRepoPath = resolve6(options.source_repo_path);
39395
+ const sourceRepoPath = resolve7(options.source_repo_path);
39279
39396
  ensureGitRepository(sourceRepoPath);
39280
39397
  const experimentId = makeExplorerExperimentId();
39281
39398
  const repoPath = join10(getExplorerReposDir(), experimentId, "repo");
@@ -39539,7 +39656,7 @@ function runExperimentBaselineInit(experimentId, store) {
39539
39656
  error: baseline.error || null
39540
39657
  };
39541
39658
  mkdirSync10(artifactDir, { recursive: true, mode: 448 });
39542
- writeFileSync8(join11(artifactDir, "eval.json"), JSON.stringify(evalPayload, null, 2) + "\n", "utf-8");
39659
+ writeFileSync9(join11(artifactDir, "eval.json"), JSON.stringify(evalPayload, null, 2) + "\n", "utf-8");
39543
39660
  store.createEvent(experiment.id, "note", {
39544
39661
  message: "experiment initialized with baseline evaluation",
39545
39662
  status: baseline.status,