@aiden-ade/sandbox-agent 0.1.51 → 0.1.52

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 (2) hide show
  1. package/dist/index.cjs +411 -23
  2. package/package.json +3 -3
package/dist/index.cjs CHANGED
@@ -36,7 +36,7 @@ var require_XMLHttpRequest = __commonJS({
36
36
  "use strict";
37
37
  var fs = require("fs");
38
38
  var Url = require("url");
39
- var spawn6 = require("child_process").spawn;
39
+ var spawn7 = require("child_process").spawn;
40
40
  module2.exports = XMLHttpRequest3;
41
41
  XMLHttpRequest3.XMLHttpRequest = XMLHttpRequest3;
42
42
  function XMLHttpRequest3(opts) {
@@ -332,7 +332,7 @@ var require_XMLHttpRequest = __commonJS({
332
332
  var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
333
333
  fs.writeFileSync(syncFile, "", "utf8");
334
334
  var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
335
- var syncProc = spawn6(process.argv[0], ["-e", execString]);
335
+ var syncProc = spawn7(process.argv[0], ["-e", execString]);
336
336
  var statusText;
337
337
  while (fs.existsSync(syncFile)) {
338
338
  }
@@ -4811,7 +4811,7 @@ var require_websocket_server = __commonJS({
4811
4811
  });
4812
4812
 
4813
4813
  // src/index.ts
4814
- var import_node_child_process9 = require("child_process");
4814
+ var import_node_child_process10 = require("child_process");
4815
4815
 
4816
4816
  // src/cli-executable.ts
4817
4817
  var import_node_child_process = require("child_process");
@@ -5204,7 +5204,7 @@ function resolveCommand(args, env) {
5204
5204
  }
5205
5205
 
5206
5206
  // src/daemon.ts
5207
- var import_node_child_process7 = require("child_process");
5207
+ var import_node_child_process8 = require("child_process");
5208
5208
  var import_node_crypto5 = require("crypto");
5209
5209
  var import_node_fs14 = require("fs");
5210
5210
  var import_node_http = __toESM(require("http"), 1);
@@ -5362,7 +5362,7 @@ Every \`start_task_session\` message must include all four of these:
5362
5362
  3. Write requirements as a team doc (\`create_document\`) before any delegation. Create an artifact only when explicitly requested.
5363
5363
  4. Break work into subtasks ordered by dependency. Delegate one at a time unless subtasks are truly independent.
5364
5364
  5. Track results and update task status as work completes.`;
5365
- var GENERAL_PROMPT = `You are Alan, the General Agent \u2014 a flexible general-purpose agent profile. When identifying yourself, say "I'm Alan, the General Agent."`;
5365
+ var GENERAL_PROMPT = "";
5366
5366
  var SETUP_PROMPT = `## You are the Setup Agent
5367
5367
 
5368
5368
  You are the **Setup Agent** for this cloud environment. When identifying yourself, say "I'm the Setup Agent (an Alan agent profile) for this environment."
@@ -10311,9 +10311,7 @@ Team
10311
10311
 
10312
10312
  ## Task Logging
10313
10313
 
10314
- For non-trivial tasks, log high-signal events as they happen: decisions, changed assumptions, validation results, known-good IDs, blockers, risks, and direction changes. Keep one current task-state summary updated with current state, known-good IDs, open risks, and next action. Do not log routine commands or file reads. Before stopping, ensure the timeline and current state are enough for a human to understand progress and for another agent to resume. If a prior decision is stale, write a newer event that supersedes it.
10315
-
10316
- The specific work you do \u2014 implementation, planning, review, coordination, verification \u2014 is determined by your agent role, described next.`;
10314
+ For non-trivial tasks, log high-signal events as they happen: decisions, changed assumptions, validation results, known-good IDs, blockers, risks, and direction changes. Keep one current task-state summary updated with current state, known-good IDs, open risks, and next action. Do not log routine commands or file reads. Before stopping, ensure the timeline and current state are enough for a human to understand progress and for another agent to resume. If a prior decision is stale, write a newer event that supersedes it.`;
10317
10315
  function buildAlanTeamScopePrompt(teamId) {
10318
10316
  return [
10319
10317
  `You are operating within team \`${teamId}\`.`,
@@ -10338,6 +10336,7 @@ Examples: "where is X configured", "which file handles Y", "how does Z work", "f
10338
10336
  - Do NOT start with \`grep\`, \`rg\`, \`find\`, or broad Bash filesystem search for codebase discovery or implementation-answer questions when \`search_code_context\` is available.
10339
10337
  - Do NOT repeat or rephrase index searches after the first result identifies useful paths. Continue locally instead.
10340
10338
  - Do NOT claim current behavior from an index excerpt without reading the cited local file.
10339
+ - When several repositories are available and repoId is omitted, code context routes automatically using the task workspace, repository-name hints, and indexed evidence. Pass repoId only when the user explicitly names a repository or an existing trusted task selection supplies it. Never invent repo IDs or call list_teams.
10341
10340
 
10342
10341
  ### Local follow-ups
10343
10342
  - Import/caller structure: use local \`code_graph_query\` or narrow symbol search after reading the cited files.
@@ -23395,7 +23394,18 @@ function verifiedRemote(repoRoot, params, includePushUrls) {
23395
23394
  "The configured Git remote does not match the repository selected for migration"
23396
23395
  );
23397
23396
  }
23398
- return { remote, expectedRemoteUrl };
23397
+ return { remote, expectedRemoteUrl, remoteUrl: fetchUrls[0] };
23398
+ }
23399
+ function inspectedRemoteUrl(repoRoot, params) {
23400
+ if (optionalString(params, "expectedRemoteUrl")) {
23401
+ return verifiedRemote(repoRoot, params, false).remoteUrl;
23402
+ }
23403
+ const remote = validatedRemote(params);
23404
+ const result = runGitResult(repoRoot, ["remote", "get-url", "--all", remote]);
23405
+ const urls = result.status === 0 ? result.stdout.split("\n").filter(Boolean) : [];
23406
+ if (urls.length === 0) return null;
23407
+ for (const url2 of urls) canonicalRemoteUrl(repoRoot, url2);
23408
+ return urls[0];
23399
23409
  }
23400
23410
  function isPathContained(childPath, parentPath) {
23401
23411
  const rel = (0, import_node_path4.relative)((0, import_node_path4.resolve)(parentPath), (0, import_node_path4.resolve)(childPath));
@@ -23587,6 +23597,7 @@ function readStatus(repoRoot) {
23587
23597
  }
23588
23598
  return {
23589
23599
  repoRoot,
23600
+ remoteUrl: null,
23590
23601
  branch,
23591
23602
  headSha,
23592
23603
  dirty: porcelain.length > 0,
@@ -24084,7 +24095,11 @@ function verifyHead(params) {
24084
24095
  return { repoRoot, headSha, verified: true };
24085
24096
  }
24086
24097
  function statusBindingSwitchGit(params) {
24087
- return readStatus(resolveAuthorizedRepository(params));
24098
+ const repoRoot = resolveAuthorizedRepository(params);
24099
+ return {
24100
+ ...readStatus(repoRoot),
24101
+ remoteUrl: inspectedRemoteUrl(repoRoot, params)
24102
+ };
24088
24103
  }
24089
24104
  async function executeBindingSwitchGitRpc(method, params) {
24090
24105
  switch (method) {
@@ -26687,6 +26702,33 @@ function classifyTransportFailure(error) {
26687
26702
  return "backend_unreachable";
26688
26703
  }
26689
26704
 
26705
+ // src/observable-map.ts
26706
+ var ObservableMap = class extends Map {
26707
+ /**
26708
+ * Invoked after every membership change.
26709
+ *
26710
+ * Assigned after construction rather than passed to the constructor because
26711
+ * `Map`'s constructor may populate entries before subclass fields exist.
26712
+ */
26713
+ onChange = null;
26714
+ set(key, value2) {
26715
+ const existed = super.has(key);
26716
+ const result = super.set(key, value2);
26717
+ if (!existed) this.onChange?.();
26718
+ return result;
26719
+ }
26720
+ delete(key) {
26721
+ const removed = super.delete(key);
26722
+ if (removed) this.onChange?.();
26723
+ return removed;
26724
+ }
26725
+ clear() {
26726
+ const had = this.size > 0;
26727
+ super.clear();
26728
+ if (had) this.onChange?.();
26729
+ }
26730
+ };
26731
+
26690
26732
  // src/provider-auth-preflight.ts
26691
26733
  var AUTH_PROBES = {
26692
26734
  claude_cli: {
@@ -27313,18 +27355,331 @@ var RunStartGate = class {
27313
27355
  }
27314
27356
  };
27315
27357
 
27358
+ // src/sleep-inhibitor.ts
27359
+ var import_node_child_process5 = require("child_process");
27360
+ var SLEEP_PREVENTION_DISABLED_ENV = "ALAN_DISABLE_SLEEP_PREVENTION";
27361
+ var HEALTH_CHECK_INTERVAL_MS = 3e4;
27362
+ var noopLog = () => {
27363
+ };
27364
+ function isSleepPreventionDisabled(env = process.env) {
27365
+ const raw = env[SLEEP_PREVENTION_DISABLED_ENV];
27366
+ if (raw === void 0) return false;
27367
+ const normalized = raw.trim().toLowerCase();
27368
+ return normalized === "1" || normalized === "true" || normalized === "yes";
27369
+ }
27370
+ var ChildProcessBackend = class {
27371
+ child = null;
27372
+ /**
27373
+ * Set once a candidate command is proven absent (ENOENT). Prevents a spawn
27374
+ * storm from the health watchdog on machines that simply lack the binary.
27375
+ */
27376
+ unavailable = false;
27377
+ log;
27378
+ constructor(log) {
27379
+ this.log = log;
27380
+ }
27381
+ /**
27382
+ * Let a multi-candidate backend react when its active helper terminates.
27383
+ * This is not called for an intentional release because `release()` clears
27384
+ * the active child before signalling it.
27385
+ */
27386
+ onUnexpectedTermination() {
27387
+ }
27388
+ isHealthy() {
27389
+ if (this.unavailable) return true;
27390
+ const child = this.child;
27391
+ return child !== null && child.exitCode === null && child.signalCode === null;
27392
+ }
27393
+ acquire() {
27394
+ if (this.unavailable) return;
27395
+ if (this.isHealthy() && this.child !== null) return;
27396
+ this.child = null;
27397
+ const command = this.command();
27398
+ if (command === null) {
27399
+ this.unavailable = true;
27400
+ this.log(`sleep-inhibitor ${this.name} unavailable: no supported command`);
27401
+ return;
27402
+ }
27403
+ let child;
27404
+ try {
27405
+ child = (0, import_node_child_process5.spawn)(command.file, command.args, { stdio: "ignore", detached: false });
27406
+ } catch (error) {
27407
+ this.unavailable = true;
27408
+ this.log(`sleep-inhibitor ${this.name} spawn threw: ${describeError(error)}`);
27409
+ return;
27410
+ }
27411
+ child.on("error", (error) => {
27412
+ const wasActiveChild = this.child === child;
27413
+ if (wasActiveChild) this.child = null;
27414
+ if (error.code === "ENOENT") this.unavailable = true;
27415
+ this.log(`sleep-inhibitor ${this.name} failed: ${describeError(error)}`);
27416
+ if (wasActiveChild) this.onUnexpectedTermination();
27417
+ });
27418
+ child.on("exit", (code, signal) => {
27419
+ const wasActiveChild = this.child === child;
27420
+ if (wasActiveChild) this.child = null;
27421
+ this.log(`sleep-inhibitor ${this.name} exited code=${code} signal=${signal}`);
27422
+ if (wasActiveChild) this.onUnexpectedTermination();
27423
+ });
27424
+ this.child = child;
27425
+ this.log(`sleep-inhibitor ${this.name} acquired pid=${child.pid}`);
27426
+ }
27427
+ release() {
27428
+ const child = this.child;
27429
+ if (child === null) return;
27430
+ this.child = null;
27431
+ try {
27432
+ child.kill("SIGTERM");
27433
+ } catch (error) {
27434
+ this.log(`sleep-inhibitor ${this.name} release failed: ${describeError(error)}`);
27435
+ }
27436
+ }
27437
+ };
27438
+ var MacCaffeinateBackend = class extends ChildProcessBackend {
27439
+ name = "caffeinate";
27440
+ command() {
27441
+ return { file: "/usr/bin/caffeinate", args: ["-i", "-s", "-w", String(process.pid)] };
27442
+ }
27443
+ };
27444
+ var LinuxIdleInhibitBackend = class extends ChildProcessBackend {
27445
+ name = "systemd-inhibit";
27446
+ candidateIndex = 0;
27447
+ candidateCount = 2;
27448
+ command() {
27449
+ const guard = `while kill -0 ${process.pid} 2>/dev/null; do sleep 5; done`;
27450
+ const candidates = [
27451
+ {
27452
+ file: "systemd-inhibit",
27453
+ args: [
27454
+ "--what=idle:sleep",
27455
+ "--mode=block",
27456
+ "--who=alan-agent",
27457
+ "--why=Alan is running an active agent run",
27458
+ "--",
27459
+ "sh",
27460
+ "-c",
27461
+ guard
27462
+ ]
27463
+ },
27464
+ {
27465
+ file: "gnome-session-inhibit",
27466
+ args: [
27467
+ "--inhibit",
27468
+ "idle",
27469
+ "--reason",
27470
+ "Alan is running an active agent run",
27471
+ "sh",
27472
+ "-c",
27473
+ guard
27474
+ ]
27475
+ }
27476
+ ];
27477
+ return candidates[this.candidateIndex] ?? null;
27478
+ }
27479
+ acquire() {
27480
+ const before = this.candidateIndex;
27481
+ super.acquire();
27482
+ if (this.unavailable && this.candidateIndex === before) {
27483
+ this.tryNextCandidate();
27484
+ }
27485
+ }
27486
+ onUnexpectedTermination() {
27487
+ this.tryNextCandidate();
27488
+ }
27489
+ tryNextCandidate() {
27490
+ this.candidateIndex += 1;
27491
+ if (this.candidateIndex >= this.candidateCount) return;
27492
+ this.unavailable = false;
27493
+ super.acquire();
27494
+ }
27495
+ };
27496
+ var WindowsPowerRequestBackend = class extends ChildProcessBackend {
27497
+ name = "windows-power-request";
27498
+ command() {
27499
+ const guard = `while (Get-Process -Id ${process.pid} -ErrorAction SilentlyContinue) { Start-Sleep -Seconds 5 }`;
27500
+ const script = [
27501
+ `Add-Type -Namespace Alan -Name Power -MemberDefinition '[DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint esFlags);';`,
27502
+ "[Alan.Power]::SetThreadExecutionState(0x80000001) | Out-Null;",
27503
+ guard
27504
+ ].join(" ");
27505
+ return {
27506
+ file: "powershell.exe",
27507
+ args: ["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", script]
27508
+ };
27509
+ }
27510
+ };
27511
+ var externalBackends = /* @__PURE__ */ new Set();
27512
+ function createPlatformBackends(log, hasExternalBackend) {
27513
+ switch (process.platform) {
27514
+ case "darwin":
27515
+ return [new MacCaffeinateBackend(log)];
27516
+ case "linux":
27517
+ return [new LinuxIdleInhibitBackend(log)];
27518
+ case "win32":
27519
+ return hasExternalBackend ? [] : [new WindowsPowerRequestBackend(log)];
27520
+ default:
27521
+ return [];
27522
+ }
27523
+ }
27524
+ var SleepInhibitor = class {
27525
+ backends;
27526
+ log;
27527
+ enabled;
27528
+ active = false;
27529
+ healthTimer = null;
27530
+ exitHandler = null;
27531
+ disposed = false;
27532
+ constructor(options = {}) {
27533
+ this.log = options.log ?? noopLog;
27534
+ this.enabled = !isSleepPreventionDisabled(options.env ?? process.env);
27535
+ this.backends = this.enabled ? [...createPlatformBackends(this.log, externalBackends.size > 0), ...externalBackends] : [];
27536
+ if (!this.enabled) {
27537
+ this.log(`sleep-inhibitor disabled via ${SLEEP_PREVENTION_DISABLED_ENV}`);
27538
+ }
27539
+ }
27540
+ isActive() {
27541
+ return this.active;
27542
+ }
27543
+ /** Names of the backends this inhibitor will drive. Exposed for diagnostics. */
27544
+ backendNames() {
27545
+ return this.backends.map((backend) => backend.name);
27546
+ }
27547
+ /**
27548
+ * Turn sleep prevention on or off. Safe to call repeatedly with the same
27549
+ * value; only edges do work.
27550
+ */
27551
+ setActive(active) {
27552
+ if (this.disposed) return;
27553
+ if (this.active === active) {
27554
+ if (active) this.healBackends();
27555
+ return;
27556
+ }
27557
+ this.active = active;
27558
+ if (active) this.start();
27559
+ else this.stop();
27560
+ }
27561
+ /** Release everything and detach timers/handlers. Idempotent. */
27562
+ dispose() {
27563
+ if (this.disposed) return;
27564
+ this.disposed = true;
27565
+ this.active = false;
27566
+ this.stop();
27567
+ }
27568
+ start() {
27569
+ if (this.backends.length === 0) return;
27570
+ this.healBackends();
27571
+ if (this.healthTimer === null) {
27572
+ this.healthTimer = setInterval(() => this.healBackends(), HEALTH_CHECK_INTERVAL_MS);
27573
+ this.healthTimer.unref?.();
27574
+ }
27575
+ if (this.exitHandler === null) {
27576
+ this.exitHandler = () => {
27577
+ for (const backend of this.backends) backend.release();
27578
+ };
27579
+ process.on("exit", this.exitHandler);
27580
+ }
27581
+ }
27582
+ stop() {
27583
+ if (this.healthTimer !== null) {
27584
+ clearInterval(this.healthTimer);
27585
+ this.healthTimer = null;
27586
+ }
27587
+ if (this.exitHandler !== null) {
27588
+ process.removeListener("exit", this.exitHandler);
27589
+ this.exitHandler = null;
27590
+ }
27591
+ for (const backend of this.backends) {
27592
+ try {
27593
+ backend.release();
27594
+ } catch (error) {
27595
+ this.log(`sleep-inhibitor release error: ${describeError(error)}`);
27596
+ }
27597
+ }
27598
+ }
27599
+ /**
27600
+ * Acquire anything not currently held. This is the self-healing step: a
27601
+ * backend whose helper died is respawned here.
27602
+ *
27603
+ * Healthy backends are skipped rather than re-acquired, so concurrent runs
27604
+ * can never stack assertions even if a backend's own `acquire()` is not
27605
+ * defensively idempotent.
27606
+ */
27607
+ healBackends() {
27608
+ for (const backend of this.backends) {
27609
+ try {
27610
+ if (backend.isHealthy()) continue;
27611
+ backend.acquire();
27612
+ } catch (error) {
27613
+ this.log(`sleep-inhibitor ${backend.name} acquire error: ${describeError(error)}`);
27614
+ }
27615
+ }
27616
+ }
27617
+ };
27618
+ function describeError(error) {
27619
+ return error instanceof Error ? error.message : String(error);
27620
+ }
27621
+
27622
+ // src/suspension-detector.ts
27623
+ var DEFAULT_CHECK_INTERVAL_MS = 15e3;
27624
+ var DEFAULT_THRESHOLD_MS = 6e4;
27625
+ var SuspensionDetector = class {
27626
+ onSuspensionDetected;
27627
+ checkIntervalMs;
27628
+ thresholdMs;
27629
+ now;
27630
+ setIntervalFn;
27631
+ clearIntervalFn;
27632
+ timer = null;
27633
+ lastTickAtMs = null;
27634
+ constructor(options) {
27635
+ this.onSuspensionDetected = options.onSuspensionDetected;
27636
+ this.checkIntervalMs = options.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
27637
+ this.thresholdMs = options.thresholdMs ?? DEFAULT_THRESHOLD_MS;
27638
+ this.now = options.now ?? Date.now;
27639
+ this.setIntervalFn = options.setIntervalFn ?? setInterval;
27640
+ this.clearIntervalFn = options.clearIntervalFn ?? clearInterval;
27641
+ }
27642
+ start() {
27643
+ if (this.timer !== null) return;
27644
+ this.lastTickAtMs = this.now();
27645
+ this.timer = this.setIntervalFn(() => this.tick(), this.checkIntervalMs);
27646
+ this.timer.unref?.();
27647
+ }
27648
+ stop() {
27649
+ if (this.timer === null) return;
27650
+ this.clearIntervalFn(this.timer);
27651
+ this.timer = null;
27652
+ this.lastTickAtMs = null;
27653
+ }
27654
+ isRunning() {
27655
+ return this.timer !== null;
27656
+ }
27657
+ tick() {
27658
+ const nowMs = this.now();
27659
+ const previousMs = this.lastTickAtMs;
27660
+ this.lastTickAtMs = nowMs;
27661
+ if (previousMs === null) return;
27662
+ const overshootMs = nowMs - previousMs - this.checkIntervalMs;
27663
+ if (overshootMs < this.thresholdMs) return;
27664
+ try {
27665
+ this.onSuspensionDetected(overshootMs);
27666
+ } catch {
27667
+ }
27668
+ }
27669
+ };
27670
+
27316
27671
  // src/version.ts
27317
- var AGENT_VERSION = "0.1.51";
27672
+ var AGENT_VERSION = "0.1.52";
27318
27673
 
27319
27674
  // src/workspace-relocation.ts
27320
- var import_node_child_process5 = require("child_process");
27675
+ var import_node_child_process6 = require("child_process");
27321
27676
  var import_node_fs12 = require("fs");
27322
27677
  var import_node_path10 = require("path");
27323
27678
  var MAX_RELOCATION_CANDIDATES = 200;
27324
27679
  var DEFAULT_PROBE = {
27325
27680
  childDirectories: (parentPath, limit) => (0, import_node_fs12.readdirSync)(parentPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).slice(0, limit).map((entry) => (0, import_node_path10.join)(parentPath, entry.name)),
27326
27681
  gitRemoteUrl: (candidatePath) => {
27327
- const result = (0, import_node_child_process5.spawnSync)("git", ["-C", candidatePath, "config", "--get", "remote.origin.url"], {
27682
+ const result = (0, import_node_child_process6.spawnSync)("git", ["-C", candidatePath, "config", "--get", "remote.origin.url"], {
27328
27683
  encoding: "utf8",
27329
27684
  env: getDaemonCliEnvironment(),
27330
27685
  timeout: 2e3
@@ -27386,7 +27741,7 @@ function discoverRelocatedWorkspace(input) {
27386
27741
  }
27387
27742
 
27388
27743
  // src/workspace-run-lease.ts
27389
- var import_node_child_process6 = require("child_process");
27744
+ var import_node_child_process7 = require("child_process");
27390
27745
  var import_node_fs13 = require("fs");
27391
27746
  var import_node_path11 = require("path");
27392
27747
  var WORKSPACE_LEASE_CHECK_INTERVAL_MS = 1e3;
@@ -27399,7 +27754,7 @@ var WORKSPACE_LEASE_GIT_TIMEOUT_MS = parsePositiveMs(
27399
27754
  2e3
27400
27755
  );
27401
27756
  function gitValue(cwd, args) {
27402
- const result = (0, import_node_child_process6.spawnSync)("git", ["-C", cwd, ...args], {
27757
+ const result = (0, import_node_child_process7.spawnSync)("git", ["-C", cwd, ...args], {
27403
27758
  encoding: "utf8",
27404
27759
  env: getDaemonCliEnvironment(),
27405
27760
  timeout: WORKSPACE_LEASE_GIT_TIMEOUT_MS
@@ -27653,7 +28008,7 @@ function assertValidGitRef(ref, label) {
27653
28008
  if (!ref || ref.startsWith("-") || ref.includes("\0")) {
27654
28009
  throw new Error(`${label} is not a safe git ref`);
27655
28010
  }
27656
- const result = (0, import_node_child_process7.spawnSync)("git", ["check-ref-format", "--branch", ref], {
28011
+ const result = (0, import_node_child_process8.spawnSync)("git", ["check-ref-format", "--branch", ref], {
27657
28012
  env: getDaemonCliEnvironment(),
27658
28013
  encoding: "utf8"
27659
28014
  });
@@ -27669,7 +28024,7 @@ function assertPathContained(childPath, parentPath, label) {
27669
28024
  }
27670
28025
  }
27671
28026
  function git(cwd, args) {
27672
- const result = (0, import_node_child_process7.spawnSync)("git", args, {
28027
+ const result = (0, import_node_child_process8.spawnSync)("git", args, {
27673
28028
  cwd,
27674
28029
  env: getDaemonCliEnvironment(),
27675
28030
  encoding: "utf8",
@@ -28627,10 +28982,13 @@ async function acquireLocalDaemonOwnerLease(input) {
28627
28982
  }
28628
28983
  return { acquired: false, lease: existing };
28629
28984
  }
28630
- if (endpoint.state === "listening_unauthorized" || isProviderProcessAlive(existing.pid)) {
28985
+ if (endpoint.state === "listening_unauthorized") {
28986
+ return { acquired: false, lease: existing };
28987
+ }
28988
+ const journal = inspectLocalDaemonRunJournal(input.configPath);
28989
+ if (isProviderProcessAlive(existing.pid) && !journal.safeToRestart) {
28631
28990
  return { acquired: false, lease: existing };
28632
28991
  }
28633
- inspectLocalDaemonRunJournal(input.configPath);
28634
28992
  releaseOwnedJsonFile(daemonOwnerLeasePath(input.configPath), existing.id);
28635
28993
  }
28636
28994
  if (!existing && input.storedLocalApiPort && input.storedLocalApiToken) {
@@ -29998,7 +30356,7 @@ async function startDaemon(args) {
29998
30356
  ownerLeaseHeld = false;
29999
30357
  releaseLocalDaemonOwnerLease(configPath, ownerLease.id);
30000
30358
  };
30001
- const activeAgents = /* @__PURE__ */ new Map();
30359
+ const activeAgents = new ObservableMap();
30002
30360
  const pendingRunStarts = new DurablePendingRunStarts(
30003
30361
  `${configPath}.pending-runs.json`,
30004
30362
  daemonSessionId
@@ -30019,6 +30377,13 @@ async function startDaemon(args) {
30019
30377
  recentLogs.push(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
30020
30378
  if (recentLogs.length > 200) recentLogs.splice(0, recentLogs.length - 200);
30021
30379
  };
30380
+ const sleepInhibitor = new SleepInhibitor({ log: pushLog });
30381
+ activeAgents.onChange = () => {
30382
+ sleepInhibitor.setActive(activeAgents.size > 0);
30383
+ };
30384
+ if (sleepInhibitor.backendNames().length > 0) {
30385
+ pushLog(`sleep-inhibitor backends=${sleepInhibitor.backendNames().join(",")}`);
30386
+ }
30022
30387
  const alanMcpRegisteredRuns = /* @__PURE__ */ new Set();
30023
30388
  const alanMcpRegisteredBackends = /* @__PURE__ */ new Set();
30024
30389
  const finalizeAlanMcpForRun = (runId) => {
@@ -30247,6 +30612,22 @@ async function startDaemon(args) {
30247
30612
  emitRunnerPresence();
30248
30613
  flushWorkspaceRelocations();
30249
30614
  }, 15e3);
30615
+ let pendingWakeGapMs = null;
30616
+ const flushWakeGap = () => {
30617
+ if (pendingWakeGapMs === null || !socket.connected) return;
30618
+ const gapMs = pendingWakeGapMs;
30619
+ pendingWakeGapMs = null;
30620
+ socket.emit("runtime.wake", { gapMs });
30621
+ pushLog(`runtime-wake reported gapMs=${gapMs}`);
30622
+ };
30623
+ const suspensionDetector = new SuspensionDetector({
30624
+ onSuspensionDetected: (gapMs) => {
30625
+ pendingWakeGapMs = Math.max(pendingWakeGapMs ?? 0, gapMs);
30626
+ pushLog(`host-suspension detected gapMs=${gapMs}`);
30627
+ flushWakeGap();
30628
+ }
30629
+ });
30630
+ suspensionDetector.start();
30250
30631
  void refreshProviderVersions();
30251
30632
  const versionRefresh = setInterval(() => {
30252
30633
  void refreshProviderVersions();
@@ -30338,6 +30719,7 @@ async function startDaemon(args) {
30338
30719
  runnerStatus: runnerSummary.runnerStatus
30339
30720
  }
30340
30721
  });
30722
+ flushWakeGap();
30341
30723
  scheduleRuntimeModelScan(() => Promise.resolve(metadata));
30342
30724
  });
30343
30725
  });
@@ -31213,6 +31595,7 @@ async function startDaemon(args) {
31213
31595
  clearInterval(heartbeat);
31214
31596
  clearInterval(versionRefresh);
31215
31597
  clearInterval(workspaceLeaseMonitor);
31598
+ suspensionDetector.stop();
31216
31599
  socket.disconnect();
31217
31600
  releaseOwnerLease();
31218
31601
  releaseStartupLease();
@@ -31224,6 +31607,7 @@ async function startDaemon(args) {
31224
31607
  clearInterval(heartbeat);
31225
31608
  clearInterval(versionRefresh);
31226
31609
  clearInterval(workspaceLeaseMonitor);
31610
+ suspensionDetector.stop();
31227
31611
  socket.disconnect();
31228
31612
  releaseOwnerLease();
31229
31613
  releaseStartupLease();
@@ -31236,6 +31620,7 @@ async function startDaemon(args) {
31236
31620
  clearInterval(heartbeat);
31237
31621
  clearInterval(versionRefresh);
31238
31622
  clearInterval(workspaceLeaseMonitor);
31623
+ suspensionDetector.stop();
31239
31624
  socket.disconnect();
31240
31625
  if (error.code === "EADDRINUSE") {
31241
31626
  void probeLocalDaemonEndpoint(localApiPort).then((probe) => {
@@ -31278,6 +31663,7 @@ async function startDaemon(args) {
31278
31663
  clearInterval(heartbeat);
31279
31664
  clearInterval(versionRefresh);
31280
31665
  clearInterval(workspaceLeaseMonitor);
31666
+ suspensionDetector.stop();
31281
31667
  socket.disconnect();
31282
31668
  await new Promise((resolve7) => localServer.close(() => resolve7()));
31283
31669
  releaseOwnerLease();
@@ -31293,6 +31679,7 @@ async function startDaemon(args) {
31293
31679
  clearInterval(heartbeat);
31294
31680
  clearInterval(versionRefresh);
31295
31681
  clearInterval(workspaceLeaseMonitor);
31682
+ suspensionDetector.stop();
31296
31683
  for (const entry of activeAgents.values()) {
31297
31684
  abortActiveAgent(entry, { userInitiated: true });
31298
31685
  }
@@ -31302,6 +31689,7 @@ async function startDaemon(args) {
31302
31689
  localServer.close(() => resolve7());
31303
31690
  });
31304
31691
  } finally {
31692
+ sleepInhibitor.dispose();
31305
31693
  releaseOwnerLease();
31306
31694
  }
31307
31695
  };
@@ -32729,7 +33117,7 @@ async function runSandbox(config) {
32729
33117
  }
32730
33118
 
32731
33119
  // src/service-manager.ts
32732
- var import_node_child_process8 = require("child_process");
33120
+ var import_node_child_process9 = require("child_process");
32733
33121
  var import_node_fs16 = require("fs");
32734
33122
  var import_node_os10 = require("os");
32735
33123
  var import_node_path13 = require("path");
@@ -32940,7 +33328,7 @@ function writeManifest(path, contents) {
32940
33328
  }
32941
33329
  }
32942
33330
  function runServiceCommand(command) {
32943
- const result = (0, import_node_child_process8.spawnSync)(command.command, command.args, { encoding: "utf8" });
33331
+ const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, { encoding: "utf8" });
32944
33332
  const status = result.status ?? 1;
32945
33333
  const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
32946
33334
  if (status !== 0 && !command.tolerateFailure) {
@@ -32983,7 +33371,7 @@ function printDaemonServiceStatus(args = []) {
32983
33371
  function logEffectiveGitIdentity(lifecycle, projectPath) {
32984
33372
  const readConfig2 = (key) => {
32985
33373
  try {
32986
- const result = (0, import_node_child_process9.spawnSync)("git", ["config", "--get", key], {
33374
+ const result = (0, import_node_child_process10.spawnSync)("git", ["config", "--get", key], {
32987
33375
  cwd: projectPath,
32988
33376
  env: getDaemonCliEnvironment(),
32989
33377
  encoding: "utf8",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiden-ade/sandbox-agent",
3
- "version": "0.1.51",
3
+ "version": "0.1.52",
4
4
  "type": "module",
5
5
  "description": "Alan agent runtime — cloud sandbox and local daemon (alan-agent CLI)",
6
6
  "bin": {
@@ -16,9 +16,9 @@
16
16
  "smol-toml": "^1.6.1"
17
17
  },
18
18
  "devDependencies": {
19
- "@types/node": "^22.0.0",
20
- "socket.io": "^4.8.0",
21
19
  "socket.io-client": "^4.8.0",
20
+ "socket.io": "^4.8.0",
21
+ "@types/node": "^22.0.0",
22
22
  "tsup": "^8.5.1",
23
23
  "tsx": "^4.19.0",
24
24
  "typescript": "~5.9.3",