@alan-ai-hq/agent-manager 0.1.95 → 0.1.97

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 +428 -539
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -3404,7 +3404,7 @@ var require_websocket = __commonJS({
3404
3404
  var http2 = require("http");
3405
3405
  var net = require("net");
3406
3406
  var tls = require("tls");
3407
- var { randomBytes: randomBytes13, createHash: createHash19 } = require("crypto");
3407
+ var { randomBytes: randomBytes13, createHash: createHash18 } = require("crypto");
3408
3408
  var { Duplex, Readable: Readable2 } = require("stream");
3409
3409
  var { URL: URL2 } = require("url");
3410
3410
  var PerMessageDeflate = require_permessage_deflate();
@@ -4061,7 +4061,7 @@ var require_websocket = __commonJS({
4061
4061
  abortHandshake(websocket, socket, "Invalid Upgrade header");
4062
4062
  return;
4063
4063
  }
4064
- const digest = createHash19("sha1").update(key + GUID).digest("base64");
4064
+ const digest = createHash18("sha1").update(key + GUID).digest("base64");
4065
4065
  if (res.headers["sec-websocket-accept"] !== digest) {
4066
4066
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
4067
4067
  return;
@@ -4428,7 +4428,7 @@ var require_websocket_server = __commonJS({
4428
4428
  var EventEmitter = require("events");
4429
4429
  var http2 = require("http");
4430
4430
  var { Duplex } = require("stream");
4431
- var { createHash: createHash19 } = require("crypto");
4431
+ var { createHash: createHash18 } = require("crypto");
4432
4432
  var extension = require_extension();
4433
4433
  var PerMessageDeflate = require_permessage_deflate();
4434
4434
  var subprotocol = require_subprotocol();
@@ -4725,7 +4725,7 @@ var require_websocket_server = __commonJS({
4725
4725
  );
4726
4726
  }
4727
4727
  if (this._state > RUNNING) return abortHandshake(socket, 503);
4728
- const digest = createHash19("sha1").update(key + GUID).digest("base64");
4728
+ const digest = createHash18("sha1").update(key + GUID).digest("base64");
4729
4729
  const headers = [
4730
4730
  "HTTP/1.1 101 Switching Protocols",
4731
4731
  "Upgrade: websocket",
@@ -14583,6 +14583,15 @@ var import_node_os = require("os");
14583
14583
  var import_node_path = require("path");
14584
14584
 
14585
14585
  // ../sandbox-runtime-spec/dist/index.js
14586
+ var SANDBOX_AGENT_BROWSER_PACKAGE = "agent-browser";
14587
+ var SANDBOX_AGENT_BROWSER_VERSION_POLICY = ">=0.37.1 <1.0.0";
14588
+ var SANDBOX_AGENT_BROWSER_NPM_SPEC = `${SANDBOX_AGENT_BROWSER_PACKAGE}@${SANDBOX_AGENT_BROWSER_VERSION_POLICY}`;
14589
+ var SANDBOX_DESKTOP_BROWSER_ENV = Object.freeze({
14590
+ DISPLAY: ":0",
14591
+ AGENT_BROWSER_HEADED: "true",
14592
+ AGENT_BROWSER_NO_XVFB: "1",
14593
+ AGENT_BROWSER_EXECUTABLE_PATH: "/usr/bin/google-chrome-stable"
14594
+ });
14586
14595
  var definitions = [
14587
14596
  {
14588
14597
  id: "claude",
@@ -15451,7 +15460,7 @@ function describeError(error61) {
15451
15460
  }
15452
15461
 
15453
15462
  // src/version.ts
15454
- var AGENT_VERSION = "0.1.95";
15463
+ var AGENT_VERSION = "0.1.97";
15455
15464
 
15456
15465
  // src/daemon-worktree.ts
15457
15466
  var import_node_child_process3 = require("child_process");
@@ -20292,9 +20301,9 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
20292
20301
  }`;
20293
20302
  doc.write(`const input = payload.value;`);
20294
20303
  const ids = /* @__PURE__ */ Object.create(null);
20295
- let counter = 0;
20304
+ let counter2 = 0;
20296
20305
  for (const key of normalized.allKeys) {
20297
- ids[key] = `key_${counter++}`;
20306
+ ids[key] = `key_${counter2++}`;
20298
20307
  }
20299
20308
  doc.write(memo4 ? `const newResult = memo.alloc(inst, payload, {}, ctx);` : `const newResult = {};`);
20300
20309
  for (const key of normalized.allKeys) {
@@ -49610,16 +49619,6 @@ async function probeLocalDaemon(port, token, timeoutMs = 750) {
49610
49619
  const probe = await probeLocalDaemonEndpoint(port, token, timeoutMs);
49611
49620
  return probe.state === "running" ? probe.status : null;
49612
49621
  }
49613
- function requestLocalDaemonShutdown(port, token) {
49614
- return fetch(`http://127.0.0.1:${port}/shutdown?force=1`, {
49615
- method: "POST",
49616
- headers: {
49617
- authorization: `Bearer ${token}`,
49618
- "x-alan-shutdown-reason": "runtime_shutdown"
49619
- },
49620
- signal: AbortSignal.timeout(5e3)
49621
- });
49622
- }
49623
49622
  async function adoptLocalDaemonControl(input2) {
49624
49623
  const timeoutMs = input2.timeoutMs ?? 750;
49625
49624
  try {
@@ -74439,6 +74438,14 @@ var skillVersionIdSchema = external_exports.uuid().transform((value2) => value2)
74439
74438
  var runtimeIdSchema = external_exports.uuid().transform((value2) => value2);
74440
74439
  var skillPreviewIdSchema = external_exports.uuid().transform((value2) => value2);
74441
74440
  var skillCommandIdSchema = external_exports.uuid().transform((value2) => value2);
74441
+ var skillPackageFileSchema = defineWireSchema()(external_exports.object({
74442
+ path: external_exports.string().min(1),
74443
+ kind: external_exports.enum(["instructions", "script", "reference", "asset", "license", "other"]),
74444
+ size: external_exports.number().int().nonnegative(),
74445
+ mode: external_exports.union([external_exports.literal(420), external_exports.literal(493)]),
74446
+ sha256: sha256Schema,
74447
+ preview: external_exports.enum(["markdown", "text", "code", "image", "binary"])
74448
+ }));
74442
74449
  var skillInvocationSchema = defineWireSchema()(external_exports.discriminatedUnion("kind", [
74443
74450
  external_exports.object({
74444
74451
  kind: external_exports.literal("skill_version"),
@@ -74540,6 +74547,7 @@ function projectSkillFrontmatter(admitted) {
74540
74547
  };
74541
74548
  const license = optionalText("license", 1024);
74542
74549
  const compatibility = optionalText("compatibility", 500);
74550
+ const version2 = optionalText("version", 64);
74543
74551
  const allowedTools = optionalToolList("allowed-tools", 1024);
74544
74552
  const metadata = {};
74545
74553
  const rawMetadata = raw.metadata;
@@ -74588,6 +74596,7 @@ function projectSkillFrontmatter(admitted) {
74588
74596
  description: admitted.description,
74589
74597
  ...license ? { license } : {},
74590
74598
  ...compatibility ? { compatibility } : {},
74599
+ ...version2 ? { version: version2 } : {},
74591
74600
  metadata,
74592
74601
  ...allowedTools ? { allowedTools } : {}
74593
74602
  },
@@ -76697,13 +76706,13 @@ var RuntimeRunJournal = class {
76697
76706
  * Keep the exact interruption outcome until a durable authenticated control
76698
76707
  * channel can prove both identity and ownership.
76699
76708
  */
76700
- assessRecovery(runId, isProcessAlive2) {
76709
+ assessRecovery(runId, isProcessAlive) {
76701
76710
  const entry = this.entries.get(runId);
76702
76711
  if (!entry) return void 0;
76703
76712
  if (entry.providerPid === void 0) {
76704
76713
  return { reattachable: false, reason: "provider_not_recorded" };
76705
76714
  }
76706
- if (!isProcessAlive2(entry.providerPid)) {
76715
+ if (!isProcessAlive(entry.providerPid)) {
76707
76716
  return {
76708
76717
  reattachable: false,
76709
76718
  reason: "provider_not_running",
@@ -80450,17 +80459,6 @@ async function runLocalDaemonRecoveryLadder(input2) {
80450
80459
  error: "The local daemon exhausted its reconnect and safe-restart recovery budget."
80451
80460
  };
80452
80461
  }
80453
- function writeRegisteredRuntimeConfig(args, previousConfig, registered) {
80454
- writeConfig({
80455
- ...registered,
80456
- localApiPort: getLocalApiPort(args, {}),
80457
- localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
80458
- localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
80459
- localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80460
- eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80461
- daemonEpoch: previousConfig.daemonEpoch
80462
- });
80463
- }
80464
80462
  async function setupDaemon(args) {
80465
80463
  bindConfigPath(args);
80466
80464
  const previousConfig = readConfigForRegistration();
@@ -80521,7 +80519,7 @@ async function setupDaemon(args) {
80521
80519
  credentialGeneration: body2.credentialGeneration,
80522
80520
  configPath: getConfigPath()
80523
80521
  });
80524
- return body2.runtimeId !== liveConfig.runtimeId;
80522
+ return { daemonAlreadyRunning: true };
80525
80523
  }
80526
80524
  if (readLocalDaemonOwnerLease(getConfigPath())) {
80527
80525
  throw new Error(
@@ -80535,17 +80533,23 @@ async function setupDaemon(args) {
80535
80533
  if (setupToken && error61 instanceof RuntimeSetupGrantExpiredError) {
80536
80534
  console.info("[alan-agent] Setup link expired; continuing with browser authorization");
80537
80535
  await loginWithDeviceCode(args);
80538
- return false;
80536
+ return { daemonAlreadyRunning: false };
80539
80537
  }
80540
80538
  throw error61;
80541
80539
  }
80542
- writeRegisteredRuntimeConfig(args, previousConfig, {
80540
+ writeConfig({
80543
80541
  apiUrl,
80544
80542
  wsUrl,
80545
80543
  endpointProfile,
80546
80544
  runtimeId: body.runtimeId,
80547
80545
  runtimeToken: body.runtimeToken,
80548
80546
  runtimeRenewalToken: body.runtimeRenewalToken,
80547
+ localApiPort: getLocalApiPort(args, {}),
80548
+ localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
80549
+ localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
80550
+ localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80551
+ eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80552
+ daemonEpoch: previousConfig.daemonEpoch,
80549
80553
  displayName: body.displayName,
80550
80554
  installationId
80551
80555
  });
@@ -80554,7 +80558,7 @@ async function setupDaemon(args) {
80554
80558
  runtimeId: body.runtimeId,
80555
80559
  configPath: getConfigPath()
80556
80560
  });
80557
- return false;
80561
+ return { daemonAlreadyRunning: false };
80558
80562
  }
80559
80563
  async function loginWithDeviceCode(args) {
80560
80564
  bindConfigPath(args);
@@ -80643,13 +80647,19 @@ async function loginWithDeviceCode(args) {
80643
80647
  if (ownedPending?.operationId !== operation.id) {
80644
80648
  throw new Error("browser authorization sidecar ownership was lost before finalization");
80645
80649
  }
80646
- writeRegisteredRuntimeConfig(args, previousConfig, {
80650
+ writeConfig({
80647
80651
  apiUrl,
80648
80652
  wsUrl,
80649
80653
  endpointProfile,
80650
80654
  runtimeId: body.runtime.id,
80651
80655
  runtimeToken: body.runtimeToken,
80652
80656
  runtimeRenewalToken: body.runtimeRenewalToken,
80657
+ localApiPort: getLocalApiPort(args, {}),
80658
+ localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
80659
+ localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
80660
+ localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80661
+ eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80662
+ daemonEpoch: previousConfig.daemonEpoch,
80653
80663
  displayName: body.runtime.displayName ?? displayName,
80654
80664
  installationId
80655
80665
  });
@@ -80688,7 +80698,14 @@ async function stopDaemon(args) {
80688
80698
  console.info("[alan-agent] No daemon listening", { port: localApiPort });
80689
80699
  return;
80690
80700
  }
80691
- const response = await requestLocalDaemonShutdown(localApiPort, localApiToken);
80701
+ const response = await fetch(`http://127.0.0.1:${localApiPort}/shutdown?force=1`, {
80702
+ method: "POST",
80703
+ headers: {
80704
+ authorization: `Bearer ${localApiToken}`,
80705
+ "x-alan-shutdown-reason": "runtime_shutdown"
80706
+ },
80707
+ signal: AbortSignal.timeout(5e3)
80708
+ });
80692
80709
  if (!response.ok) {
80693
80710
  throw new Error(`Failed to stop daemon: HTTP ${response.status}`);
80694
80711
  }
@@ -80833,7 +80850,14 @@ async function drainAndStopLocalDaemonBeforeCredentialCleanup(args, stored) {
80833
80850
  `Logout could not safely drain the local daemon (HTTP ${prepare.status}). Stop or update the local service before removing credentials.`
80834
80851
  );
80835
80852
  }
80836
- const shutdown = await requestLocalDaemonShutdown(localApiPort, localApiToken);
80853
+ const shutdown = await fetch(`http://127.0.0.1:${localApiPort}/shutdown?force=1`, {
80854
+ method: "POST",
80855
+ headers: {
80856
+ authorization: `Bearer ${localApiToken}`,
80857
+ "x-alan-shutdown-reason": "runtime_shutdown"
80858
+ },
80859
+ signal: AbortSignal.timeout(5e3)
80860
+ });
80837
80861
  if (!shutdown.ok) {
80838
80862
  throw new Error(`Logout could not stop the local daemon (HTTP ${shutdown.status}).`);
80839
80863
  }
@@ -80994,9 +81018,6 @@ async function printDoctor(args = []) {
80994
81018
  );
80995
81019
  }
80996
81020
 
80997
- // src/install-command.ts
80998
- var import_promises6 = require("timers/promises");
80999
-
81000
81021
  // src/mcp-config-adapters.ts
81001
81022
  var import_node_crypto20 = require("crypto");
81002
81023
  var import_node_fs23 = require("fs");
@@ -82381,14 +82402,12 @@ function runMcpIntegrationCommand(args, options = {}) {
82381
82402
 
82382
82403
  // src/service-manager.ts
82383
82404
  var import_node_child_process9 = require("child_process");
82384
- var import_node_crypto21 = require("crypto");
82385
82405
  var import_node_fs25 = require("fs");
82386
82406
  var import_node_os16 = require("os");
82387
82407
  var import_node_path27 = require("path");
82388
82408
  var SERVICE_LABEL = "ai.tryalan.agent";
82389
82409
  var SYSTEMD_UNIT = "alan-agent.service";
82390
82410
  var WINDOWS_TASK = "Alan Agent";
82391
- var STOP_POLL_INTERVAL_MS = 200;
82392
82411
  function xml(value2) {
82393
82412
  return value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
82394
82413
  }
@@ -82398,18 +82417,6 @@ function systemdArg(value2) {
82398
82417
  function powershellLiteral(value2) {
82399
82418
  return `'${value2.replaceAll("'", "''")}'`;
82400
82419
  }
82401
- function powershellCommand(script, tolerateFailure = false) {
82402
- return {
82403
- command: "powershell.exe",
82404
- args: ["-NoProfile", "-NonInteractive", "-Command", script],
82405
- tolerateFailure
82406
- };
82407
- }
82408
- function windowsCommandLineArgument(value2) {
82409
- if (value2 && !/[\s"]/u.test(value2)) return value2;
82410
- const escaped = value2.replace(/(\\*)"/gu, (_match, slashes) => `${slashes}${slashes}\\"`).replace(/\\+$/u, (slashes) => `${slashes}${slashes}`);
82411
- return `"${escaped}"`;
82412
- }
82413
82420
  function serviceNames(profile) {
82414
82421
  if (profile === "production") {
82415
82422
  return { launchd: SERVICE_LABEL, systemd: SYSTEMD_UNIT, windows: WINDOWS_TASK };
@@ -82431,7 +82438,7 @@ function buildDaemonServicePlan(input2) {
82431
82438
  const domain2 = `gui/${userId}`;
82432
82439
  const serviceTarget = `${domain2}/${names.launchd}`;
82433
82440
  const manifestPath = (0, import_node_path27.join)(input2.homeDir, "Library", "LaunchAgents", `${names.launchd}.plist`);
82434
- const logDir2 = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
82441
+ const logDir = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
82435
82442
  const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
82436
82443
  const environmentEntries = Object.entries(input2.environment ?? {}).map(([key, value2]) => ` <key>${xml(key)}</key>
82437
82444
  <string>${xml(value2)}</string>`).join("\n");
@@ -82460,9 +82467,9 @@ ${environmentBlock} <key>RunAtLoad</key>
82460
82467
  <key>ThrottleInterval</key>
82461
82468
  <integer>5</integer>
82462
82469
  <key>StandardOutPath</key>
82463
- <string>${xml((0, import_node_path27.join)(logDir2, "daemon.log"))}</string>
82470
+ <string>${xml((0, import_node_path27.join)(logDir, "daemon.log"))}</string>
82464
82471
  <key>StandardErrorPath</key>
82465
- <string>${xml((0, import_node_path27.join)(logDir2, "daemon-error.log"))}</string>
82472
+ <string>${xml((0, import_node_path27.join)(logDir, "daemon-error.log"))}</string>
82466
82473
  </dict>
82467
82474
  </plist>
82468
82475
  `;
@@ -82470,7 +82477,7 @@ ${environmentBlock} <key>RunAtLoad</key>
82470
82477
  platform: "darwin",
82471
82478
  manifestPath,
82472
82479
  manifest,
82473
- logDirectory: logDir2,
82480
+ logDirectory: logDir,
82474
82481
  loadDefinitionCommands: [
82475
82482
  {
82476
82483
  command: "launchctl",
@@ -82511,6 +82518,23 @@ ${environmentBlock} <key>RunAtLoad</key>
82511
82518
  tolerateFailure: true
82512
82519
  }
82513
82520
  ],
82521
+ replaceCommands: [
82522
+ {
82523
+ command: "launchctl",
82524
+ args: ["bootout", domain2, manifestPath],
82525
+ tolerateFailure: true
82526
+ },
82527
+ {
82528
+ command: "launchctl",
82529
+ args: ["bootstrap", domain2, manifestPath],
82530
+ tolerateFailure: false
82531
+ },
82532
+ {
82533
+ command: "launchctl",
82534
+ args: ["kickstart", "-k", serviceTarget],
82535
+ tolerateFailure: false
82536
+ }
82537
+ ],
82514
82538
  uninstallCommands: [
82515
82539
  {
82516
82540
  command: "launchctl",
@@ -82527,7 +82551,8 @@ ${environmentBlock} <key>RunAtLoad</key>
82527
82551
  command: "launchctl",
82528
82552
  args: ["print", serviceTarget],
82529
82553
  tolerateFailure: true
82530
- }
82554
+ },
82555
+ lingerStatusCommand: null
82531
82556
  };
82532
82557
  }
82533
82558
  if (input2.platform === "linux") {
@@ -82590,6 +82615,18 @@ WantedBy=default.target
82590
82615
  tolerateFailure: true
82591
82616
  }
82592
82617
  ],
82618
+ replaceCommands: [
82619
+ {
82620
+ command: "systemctl",
82621
+ args: ["--user", "daemon-reload"],
82622
+ tolerateFailure: false
82623
+ },
82624
+ {
82625
+ command: "systemctl",
82626
+ args: ["--user", "restart", names.systemd],
82627
+ tolerateFailure: false
82628
+ }
82629
+ ],
82593
82630
  uninstallCommands: [
82594
82631
  {
82595
82632
  command: "systemctl",
@@ -82611,179 +82648,117 @@ WantedBy=default.target
82611
82648
  command: "systemctl",
82612
82649
  args: ["--user", "is-active", names.systemd],
82613
82650
  tolerateFailure: true
82651
+ },
82652
+ lingerStatusCommand: {
82653
+ command: "loginctl",
82654
+ args: ["show-user", "$(id -un)", "-p", "Linger"],
82655
+ tolerateFailure: true
82614
82656
  }
82615
82657
  };
82616
82658
  }
82617
- const logDir = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
82618
- const legacyEnvironmentPrefix = Object.entries(input2.environment ?? {}).map(([key, value2]) => `$env:${key} = ${powershellLiteral(value2)}`).join("; ");
82619
- const legacyDirectArguments = [input2.cliEntry, "daemon", "--profile", input2.profile].map((part) => part.includes(" ") ? `"${part}"` : part).join(" ");
82620
- const legacyExecutable = legacyEnvironmentPrefix ? "powershell.exe" : input2.nodeExecutable;
82621
- const legacyArguments = legacyEnvironmentPrefix ? `-NoProfile -NonInteractive -WindowStyle Hidden -Command "${legacyEnvironmentPrefix}; & ${powershellLiteral(input2.nodeExecutable)} ${powershellLiteral(input2.cliEntry)} daemon --profile ${input2.profile}"` : legacyDirectArguments;
82622
- const daemonArguments = [input2.cliEntry, "daemon", "--profile", input2.profile].map(windowsCommandLineArgument).join(" ");
82623
- const environmentStatements = Object.entries(input2.environment ?? {}).map(
82624
- ([key, value2]) => `$startInfo.EnvironmentVariables[${powershellLiteral(key)}] = ${powershellLiteral(value2)}`
82659
+ const environmentPrefix = Object.entries(input2.environment ?? {}).map(([key, value2]) => `$env:${key} = ${powershellLiteral(value2)}`).join("; ");
82660
+ const directArgument = [input2.cliEntry, "daemon", "--profile", input2.profile].map((part) => part.includes(" ") ? `"${part}"` : part).join(" ");
82661
+ const executable = powershellLiteral(environmentPrefix ? "powershell.exe" : input2.nodeExecutable);
82662
+ const argument = powershellLiteral(
82663
+ environmentPrefix ? `-NoProfile -NonInteractive -WindowStyle Hidden -Command "${environmentPrefix}; & ${powershellLiteral(input2.nodeExecutable)} ${powershellLiteral(input2.cliEntry)} daemon --profile ${input2.profile}"` : directArgument
82625
82664
  );
82626
- const launcherScript = [
82627
- "$ErrorActionPreference = 'Stop'",
82628
- "$startInfo = [System.Diagnostics.ProcessStartInfo]::new()",
82629
- `$startInfo.FileName = ${powershellLiteral(input2.nodeExecutable)}`,
82630
- `$startInfo.Arguments = ${powershellLiteral(daemonArguments)}`,
82631
- `$startInfo.WorkingDirectory = ${powershellLiteral((0, import_node_path27.dirname)(input2.cliEntry))}`,
82632
- "$startInfo.UseShellExecute = $false",
82633
- "$startInfo.CreateNoWindow = $true",
82634
- "$startInfo.RedirectStandardOutput = $true",
82635
- "$startInfo.RedirectStandardError = $true",
82636
- ...environmentStatements,
82637
- `$stdout = [System.IO.File]::Open(${powershellLiteral((0, import_node_path27.join)(logDir, "daemon-launcher.log"))}, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)`,
82638
- `$stderr = [System.IO.File]::Open(${powershellLiteral((0, import_node_path27.join)(logDir, "daemon-launcher-error.log"))}, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)`,
82639
- "$process = [System.Diagnostics.Process]::new()",
82640
- "$process.StartInfo = $startInfo",
82641
- "try { if (-not $process.Start()) { throw 'Alan daemon process did not start' }; $stdoutCopy = $process.StandardOutput.BaseStream.CopyToAsync($stdout); $stderrCopy = $process.StandardError.BaseStream.CopyToAsync($stderr); $process.WaitForExit(); [System.Threading.Tasks.Task]::WaitAll([System.Threading.Tasks.Task[]]@($stdoutCopy, $stderrCopy)); $exitCode = $process.ExitCode } finally { $stdout.Dispose(); $stderr.Dispose(); $process.Dispose() }",
82642
- "exit $exitCode"
82643
- ].join("; ");
82644
- const executable = powershellLiteral("powershell.exe");
82645
- const launcherArguments = `-NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand ${Buffer.from(launcherScript, "utf16le").toString("base64")}`;
82646
- const settingsScript = "New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries";
82647
- const ownerMarker = `Alan daemon owner v1 ${(0, import_node_crypto21.createHash)("sha256").update(
82648
- JSON.stringify({
82649
- executable: input2.nodeExecutable,
82650
- cliEntry: input2.cliEntry,
82651
- profile: input2.profile,
82652
- taskBaseName: names.windows
82653
- })
82654
- ).digest("hex")}`;
82655
- const definitionMarker = `${ownerMarker}; definition v3 ${(0, import_node_crypto21.createHash)("sha256").update(
82656
- JSON.stringify({
82657
- executable: "powershell.exe",
82658
- launcherArguments,
82659
- workingDirectory: (0, import_node_path27.dirname)(input2.cliEntry),
82660
- settingsScript,
82661
- taskBaseName: names.windows
82662
- })
82663
- ).digest("hex")}`;
82664
- const windowsDefinitionContext = [
82665
- `$expectedLauncherArguments = ${powershellLiteral(launcherArguments)}`,
82666
- `$expectedWorkingDirectory = ${powershellLiteral((0, import_node_path27.dirname)(input2.cliEntry))}`,
82667
- `$expectedOwnerMarker = ${powershellLiteral(`${ownerMarker};`)}`,
82668
- `$expectedDefinitionMarker = ${powershellLiteral(definitionMarker)}`,
82669
- `$expectedLegacyExecutable = ${powershellLiteral(legacyExecutable)}`,
82670
- `$expectedLegacyArguments = ${powershellLiteral(legacyArguments)}`
82671
- ].join("; ");
82672
- const taskContext = [
82673
- "$currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()",
82674
- "$currentUser = $currentIdentity.Name",
82675
- "$currentSid = $currentIdentity.User.Value",
82676
- `$taskName = ${powershellLiteral(names.windows)} + ' ' + $currentSid`
82677
- ].join("; ");
82678
- const resolveTaskSid = "function Resolve-TaskSid([string]$value) { try { return ([System.Security.Principal.NTAccount]$value).Translate([System.Security.Principal.SecurityIdentifier]).Value } catch { try { return [System.Security.Principal.SecurityIdentifier]::new($value).Value } catch { return '' } } }";
82679
- const currentLookup = "$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]";
82680
- const checkCurrentOwnership = [
82681
- currentLookup,
82682
- "$currentOwned = $false",
82683
- "if ($null -ne $task) { try {",
82684
- "$taskAction = @($task.Actions)[0]",
82685
- "$currentOwned = @($task.Actions).Count -eq 1 -and (Resolve-TaskSid ([string]$task.Principal.UserId)) -eq $currentSid -and (([string]$task.Description).StartsWith($expectedOwnerMarker, [System.StringComparison]::Ordinal) -or (([string]$taskAction.Execute) -ieq 'powershell.exe' -and ([string]$taskAction.Arguments) -ceq $expectedLauncherArguments -and ([string]$taskAction.WorkingDirectory) -ieq $expectedWorkingDirectory))",
82686
- "} catch { $currentOwned = $false } }"
82687
- ].join("; ");
82688
- const legacyLookup = `$legacyTask = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq ${powershellLiteral(names.windows)} })[0]`;
82689
- const checkLegacyOwnership = [
82690
- legacyLookup,
82691
- "$legacyOwned = $false",
82692
- "$legacyState = 'Missing'",
82693
- "if ($null -ne $legacyTask) { try {",
82694
- "$legacyState = [string]$legacyTask.State",
82695
- "$legacySid = Resolve-TaskSid ([string]$legacyTask.Principal.UserId)",
82696
- "$legacyAction = @($legacyTask.Actions)[0]",
82697
- "if ($null -ne $legacyAction) {",
82698
- "$legacyOwned = $legacySid -eq $currentSid -and @($legacyTask.Actions).Count -eq 1 -and ((([string]$legacyAction.Execute) -ieq $expectedLegacyExecutable -and ([string]$legacyAction.Arguments) -ieq $expectedLegacyArguments -and ([string]$legacyAction.WorkingDirectory) -eq '') -or (([string]$legacyAction.Execute) -ieq 'powershell.exe' -and ([string]$legacyAction.Arguments) -ceq $expectedLauncherArguments -and ([string]$legacyAction.WorkingDirectory) -ieq $expectedWorkingDirectory))",
82699
- "}",
82700
- "} catch { $legacyOwned = $false } }"
82701
- ].join("; ");
82702
- const assertOwnedTasks = [
82703
- checkCurrentOwnership,
82704
- checkLegacyOwnership,
82705
- `if ($null -ne $task -and -not $currentOwned) { throw ${powershellLiteral(`${names.windows} task is not owned by this Alan installation`)} }`,
82706
- `if ($null -ne $legacyTask -and -not $legacyOwned) { throw ${powershellLiteral(`Legacy ${names.windows} task is not owned by this Alan installation`)} }`,
82707
- "if ($null -ne $task -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$task.State)) { throw 'Alan daemon task became active or has unknown state' }",
82708
- "if ($null -ne $legacyTask -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$legacyTask.State)) { throw 'Legacy Alan daemon task became active or has unknown state' }"
82709
- ].join("; ");
82710
- const cleanupLegacyTask = [
82711
- checkLegacyOwnership,
82712
- `if ($null -ne $legacyTask -and -not $legacyOwned) { throw ${powershellLiteral(`Legacy ${names.windows} task is not owned by this Alan installation`)} }`,
82713
- "if ($null -ne $legacyTask -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$legacyTask.State)) { throw 'Legacy Alan daemon task became active or has unknown state' }",
82714
- `if ($null -ne $legacyTask) { Unregister-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -TaskPath '\\' -Confirm:$false -ErrorAction Stop }`,
82715
- `$legacyRemaining = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq ${powershellLiteral(names.windows)} })[0]`,
82716
- "if ($null -ne $legacyRemaining) { throw 'Legacy Alan daemon task still exists after migration' }"
82717
- ].join("; ");
82718
82665
  const registerScript = [
82719
- taskContext,
82720
- resolveTaskSid,
82721
- windowsDefinitionContext,
82722
- `$action = New-ScheduledTaskAction -Execute ${executable} -Argument $expectedLauncherArguments -WorkingDirectory $expectedWorkingDirectory`,
82666
+ "$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name",
82667
+ `$action = New-ScheduledTaskAction -Execute ${executable} -Argument ${argument}`,
82723
82668
  "$trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser",
82724
82669
  "$principal = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Limited",
82725
- `$settings = ${settingsScript}`,
82726
- assertOwnedTasks,
82727
- "Register-ScheduledTask -TaskName $taskName -Description $expectedDefinitionMarker -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force -ErrorAction Stop | Out-Null",
82728
- cleanupLegacyTask,
82729
- "$registeredTask = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
82730
- "if ($null -eq $registeredTask -or $registeredTask.Description -cne $expectedDefinitionMarker) { throw 'Alan daemon task definition was not applied' }"
82731
- ].join("; ");
82732
- const uninstallScript = [
82733
- taskContext,
82734
- resolveTaskSid,
82735
- windowsDefinitionContext,
82736
- assertOwnedTasks,
82737
- "if ($null -ne $legacyTask) { Unregister-ScheduledTask -TaskName $legacyTask.TaskName -TaskPath $legacyTask.TaskPath -Confirm:$false -ErrorAction Stop }",
82738
- "$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
82739
- "if ($null -ne $task) { Unregister-ScheduledTask -TaskName $taskName -TaskPath '\\' -Confirm:$false -ErrorAction Stop }",
82740
- "$remaining = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and ($_.TaskName -eq $taskName -or $_.TaskName -eq $legacyTask.TaskName) })",
82741
- "if ($remaining.Count -gt 0) { throw 'Alan daemon task still exists after unregister' }"
82670
+ "$settings = New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero)",
82671
+ `Register-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null`
82742
82672
  ].join("; ");
82743
- const statusScript = [
82744
- taskContext,
82745
- resolveTaskSid,
82746
- windowsDefinitionContext,
82747
- checkCurrentOwnership,
82748
- checkLegacyOwnership,
82749
- "$definitionMatches = $false",
82750
- "if ($null -eq $task) { Write-Output 'State: Missing' } else {",
82751
- "$taskTrigger = @($task.Triggers)[0]",
82752
- "$definitionMatches = @($task.Actions).Count -eq 1 -and @($task.Triggers).Count -eq 1 -and $task.Description -ceq $expectedDefinitionMarker -and (Resolve-TaskSid ([string]$task.Principal.UserId)) -eq $currentSid -and [string]$task.Principal.LogonType -eq 'Interactive' -and [string]$task.Principal.RunLevel -eq 'Limited' -and [string]$taskAction.Execute -ieq 'powershell.exe' -and [string]$taskAction.Arguments -ceq $expectedLauncherArguments -and [string]$taskAction.WorkingDirectory -ieq $expectedWorkingDirectory -and $taskTrigger.CimClass.CimClassName -eq 'MSFT_TaskLogonTrigger' -and (Resolve-TaskSid ([string]$taskTrigger.UserId)) -eq $currentSid -and -not $task.Settings.DisallowStartIfOnBatteries -and -not $task.Settings.StopIfGoingOnBatteries -and $task.Settings.RestartCount -eq 3 -and [string]$task.Settings.RestartInterval -eq 'PT1M' -and [string]$task.Settings.ExecutionTimeLimit -eq 'PT0S'",
82753
- "Write-Output ('TaskName: ' + $task.TaskName); Write-Output ('State: ' + $task.State); Write-Output ('Definition: ' + $task.Description)",
82754
- "}",
82755
- "Write-Output ('DefinitionMatch: ' + $definitionMatches)",
82756
- "Write-Output ('CurrentOwned: ' + $currentOwned)",
82757
- "$legacyPresent = $null -ne $legacyTask",
82758
- "Write-Output ('LegacyTask: ' + $legacyPresent)",
82759
- "Write-Output ('LegacyOwned: ' + $legacyOwned)",
82760
- "Write-Output ('LegacyState: ' + $legacyState)"
82761
- ].join("; ");
82762
- const activeScript = [
82763
- taskContext,
82764
- "$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
82765
- "if ($null -eq $task) { Write-Output 'Missing' } else { Write-Output $task.State }"
82766
- ].join("; ");
82767
- const registerCommand = powershellCommand(registerScript);
82768
82673
  return {
82769
82674
  platform: "win32",
82770
82675
  manifestPath: null,
82771
82676
  manifest: null,
82772
- logDirectory: logDir,
82773
- loadDefinitionCommands: [registerCommand],
82774
- reloadDefinitionCommands: [registerCommand],
82677
+ logDirectory: null,
82678
+ loadDefinitionCommands: [
82679
+ {
82680
+ command: "powershell.exe",
82681
+ args: ["-NoProfile", "-NonInteractive", "-Command", registerScript],
82682
+ tolerateFailure: false
82683
+ }
82684
+ ],
82685
+ reloadDefinitionCommands: [
82686
+ {
82687
+ command: "powershell.exe",
82688
+ args: ["-NoProfile", "-NonInteractive", "-Command", registerScript],
82689
+ tolerateFailure: false
82690
+ }
82691
+ ],
82775
82692
  enableCommands: [],
82776
- startCommands: [powershellCommand(`${taskContext}; Start-ScheduledTask -TaskName $taskName`)],
82693
+ startCommands: [
82694
+ {
82695
+ command: "powershell.exe",
82696
+ args: [
82697
+ "-NoProfile",
82698
+ "-NonInteractive",
82699
+ "-Command",
82700
+ `Start-ScheduledTask -TaskName ${powershellLiteral(names.windows)}`
82701
+ ],
82702
+ tolerateFailure: false
82703
+ }
82704
+ ],
82777
82705
  stopCommands: [
82778
- powershellCommand(
82779
- `${taskContext}; Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`,
82780
- true
82781
- )
82706
+ {
82707
+ command: "powershell.exe",
82708
+ args: [
82709
+ "-NoProfile",
82710
+ "-NonInteractive",
82711
+ "-Command",
82712
+ `Stop-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -ErrorAction SilentlyContinue`
82713
+ ],
82714
+ tolerateFailure: true
82715
+ }
82782
82716
  ],
82783
- uninstallCommands: [powershellCommand(uninstallScript)],
82784
- statusCommand: powershellCommand(statusScript, true),
82785
- isActiveCommand: powershellCommand(activeScript, true),
82786
- definitionMarker
82717
+ replaceCommands: [
82718
+ {
82719
+ command: "powershell.exe",
82720
+ args: [
82721
+ "-NoProfile",
82722
+ "-NonInteractive",
82723
+ "-Command",
82724
+ `${registerScript}; Start-ScheduledTask -TaskName ${powershellLiteral(names.windows)}`
82725
+ ],
82726
+ tolerateFailure: false
82727
+ }
82728
+ ],
82729
+ uninstallCommands: [
82730
+ {
82731
+ command: "powershell.exe",
82732
+ args: [
82733
+ "-NoProfile",
82734
+ "-NonInteractive",
82735
+ "-Command",
82736
+ `Unregister-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -Confirm:$false -ErrorAction SilentlyContinue`
82737
+ ],
82738
+ tolerateFailure: true
82739
+ }
82740
+ ],
82741
+ statusCommand: {
82742
+ command: "powershell.exe",
82743
+ args: [
82744
+ "-NoProfile",
82745
+ "-NonInteractive",
82746
+ "-Command",
82747
+ `Get-ScheduledTask -TaskName ${powershellLiteral(names.windows)} | Format-List TaskName,State`
82748
+ ],
82749
+ tolerateFailure: true
82750
+ },
82751
+ isActiveCommand: {
82752
+ command: "powershell.exe",
82753
+ args: [
82754
+ "-NoProfile",
82755
+ "-NonInteractive",
82756
+ "-Command",
82757
+ `(Get-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -ErrorAction SilentlyContinue).State`
82758
+ ],
82759
+ tolerateFailure: true
82760
+ },
82761
+ lingerStatusCommand: null
82787
82762
  };
82788
82763
  }
82789
82764
  function resolveCurrentPlatform() {
@@ -82793,14 +82768,14 @@ function resolveCurrentPlatform() {
82793
82768
  }
82794
82769
  return currentPlatform;
82795
82770
  }
82796
- function currentServicePlan(args, runtime, options = {}) {
82797
- const currentPlatform = options.platform ?? resolveCurrentPlatform();
82771
+ function currentServicePlan(args, runtime) {
82772
+ const currentPlatform = resolveCurrentPlatform();
82798
82773
  const profile = resolveEndpointProfileFromArgs(args);
82799
82774
  const cliEntry = runtime?.cliEntry ?? process.argv[1];
82800
82775
  if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
82801
82776
  return buildDaemonServicePlan({
82802
82777
  platform: currentPlatform,
82803
- homeDir: options.homeDir ?? (0, import_node_os16.homedir)(),
82778
+ homeDir: (0, import_node_os16.homedir)(),
82804
82779
  nodeExecutable: runtime?.executable ?? process.execPath,
82805
82780
  cliEntry: (0, import_node_path27.resolve)(cliEntry),
82806
82781
  profile,
@@ -82823,13 +82798,9 @@ function defaultReadManifest(path2) {
82823
82798
  return (0, import_node_fs25.readFileSync)(path2, "utf8");
82824
82799
  }
82825
82800
  function runServiceCommand(command) {
82826
- const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, {
82827
- encoding: "utf8",
82828
- shell: false,
82829
- ...(0, import_node_os16.platform)() === "win32" ? { timeout: 15e3, windowsHide: true } : {}
82830
- });
82801
+ const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, { encoding: "utf8", shell: false });
82831
82802
  const status = result.status ?? 1;
82832
- const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}${result.error?.message ?? ""}`.trim();
82803
+ const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
82833
82804
  if (status !== 0 && !command.tolerateFailure) {
82834
82805
  throw new Error(
82835
82806
  `Service command failed (${command.command} ${command.args.join(" ")}): ${output2 || `exit ${status}`}`
@@ -82842,23 +82813,6 @@ function defaultSleep(ms) {
82842
82813
  setTimeout(resolveSleep, ms);
82843
82814
  });
82844
82815
  }
82845
- function isProcessAlive(pid) {
82846
- if (pid <= 0) return false;
82847
- try {
82848
- process.kill(pid, 0);
82849
- return true;
82850
- } catch (error61) {
82851
- return error61.code === "EPERM";
82852
- }
82853
- }
82854
- async function requestWindowsDaemonShutdown(owner) {
82855
- try {
82856
- const response = await requestLocalDaemonShutdown(owner.localApiPort, owner.localApiToken);
82857
- return response.ok ? "accepted" : "rejected";
82858
- } catch {
82859
- return "unreachable";
82860
- }
82861
- }
82862
82816
  function parseServiceHostState(servicePlatform, result) {
82863
82817
  const output2 = result.output;
82864
82818
  if (servicePlatform === "linux") {
@@ -82884,62 +82838,9 @@ function parseServiceHostState(servicePlatform, result) {
82884
82838
  const stateMatch = output2.match(/\b(?:State|state)\s*[:=]\s*(\w+)/);
82885
82839
  const state = (stateMatch?.[1] ?? output2.trim()).toLowerCase();
82886
82840
  if (state === "running") return "running";
82887
- if (state === "queued") return "starting";
82888
- if (state === "ready" || state === "disabled") return "stopped";
82841
+ if (state === "ready" || state === "disabled" || state === "queued") return "stopped";
82889
82842
  return "unknown";
82890
82843
  }
82891
- function parseWindowsTaskInspection(result) {
82892
- if (result.status !== 0) {
82893
- throw new Error("Cannot inspect Windows daemon task definition");
82894
- }
82895
- const value2 = (name) => {
82896
- const match = result.output.match(new RegExp(`^${name}:\\s*(.*)$`, "imu"));
82897
- if (!match) throw new Error(`Incomplete Windows daemon task inspection: missing ${name}`);
82898
- return match[1]?.trim() ?? "";
82899
- };
82900
- const boolean4 = (name) => {
82901
- const parsed = value2(name).toLowerCase();
82902
- if (parsed === "true") return true;
82903
- if (parsed === "false") return false;
82904
- throw new Error(`Incomplete Windows daemon task inspection: invalid ${name}`);
82905
- };
82906
- const currentPresent = /^TaskName:\s*.+$/imu.test(result.output);
82907
- const currentStateValue = value2("State");
82908
- const currentOwned = boolean4("CurrentOwned");
82909
- const definitionMatches = boolean4("DefinitionMatch");
82910
- const legacyPresent = boolean4("LegacyTask");
82911
- const legacyOwned = boolean4("LegacyOwned");
82912
- const legacyStateValue = value2("LegacyState");
82913
- if (currentPresent !== (currentStateValue.toLowerCase() !== "missing")) {
82914
- throw new Error("Incomplete Windows daemon task inspection: inconsistent current task state");
82915
- }
82916
- if (!currentPresent && (currentOwned || definitionMatches)) {
82917
- throw new Error(
82918
- "Incomplete Windows daemon task inspection: absent current task is marked owned"
82919
- );
82920
- }
82921
- if (legacyPresent !== (legacyStateValue.toLowerCase() !== "missing") || !legacyPresent && legacyOwned) {
82922
- throw new Error("Incomplete Windows daemon task inspection: inconsistent legacy task state");
82923
- }
82924
- return {
82925
- currentPresent,
82926
- currentOwned,
82927
- currentState: currentPresent ? parseServiceHostState("win32", { status: 0, output: currentStateValue }) : "stopped",
82928
- definitionMatches,
82929
- definitionMarker: result.output.match(/^Definition:\s*(.+)$/imu)?.[1]?.trim(),
82930
- legacyPresent,
82931
- legacyOwned,
82932
- legacyState: legacyPresent ? parseServiceHostState("win32", { status: 0, output: legacyStateValue }) : "stopped"
82933
- };
82934
- }
82935
- function assertWindowsTaskOwnership(tasks) {
82936
- if (tasks.currentPresent && !tasks.currentOwned) {
82937
- throw new Error("Windows daemon task is not owned by this Alan installation");
82938
- }
82939
- if (tasks.legacyPresent && !tasks.legacyOwned) {
82940
- throw new Error("Legacy Windows daemon task is not owned by this Alan installation");
82941
- }
82942
- }
82943
82844
  function resolveEnsureDefinitionOutcome(input2) {
82944
82845
  if (!input2.definitionChanged) {
82945
82846
  return { changed: false, updatePending: false };
@@ -82949,6 +82850,9 @@ function resolveEnsureDefinitionOutcome(input2) {
82949
82850
  }
82950
82851
  return { changed: true, updatePending: false };
82951
82852
  }
82853
+ function shouldStartOnInstall(hostState) {
82854
+ return hostState === "stopped";
82855
+ }
82952
82856
  function resolveUsername(options) {
82953
82857
  if (options?.username) return options.username;
82954
82858
  try {
@@ -82957,14 +82861,17 @@ function resolveUsername(options) {
82957
82861
  return process.env.USER || process.env.USERNAME || "unknown";
82958
82862
  }
82959
82863
  }
82960
- function assertSystemdUserLingerEnabled(options = {}) {
82961
- const username = resolveUsername(options);
82962
- const run = options.runCommand ?? runServiceCommand;
82963
- const result = run({
82864
+ function buildLingerStatusCommand(username) {
82865
+ return {
82964
82866
  command: "loginctl",
82965
82867
  args: ["show-user", username, "-p", "Linger"],
82966
82868
  tolerateFailure: true
82967
- });
82869
+ };
82870
+ }
82871
+ function assertSystemdUserLingerEnabled(options = {}) {
82872
+ const username = resolveUsername(options);
82873
+ const run = options.runCommand ?? runServiceCommand;
82874
+ const result = run(buildLingerStatusCommand(username));
82968
82875
  if (result.status !== 0) {
82969
82876
  throw new Error(
82970
82877
  `Cannot verify systemd user linger for ${username} (loginctl failed). For durable headless VM boot run: sudo loginctl enable-linger ${username}`
@@ -82978,9 +82885,9 @@ function assertSystemdUserLingerEnabled(options = {}) {
82978
82885
  }
82979
82886
  function createHostContext(options = {}) {
82980
82887
  const args = options.args ?? [];
82981
- const plan = currentServicePlan(args, options.runtime, options);
82888
+ const plan = currentServicePlan(args, options.runtime);
82982
82889
  const run = options.runCommand ?? runServiceCommand;
82983
- const sleep5 = options.sleep ?? defaultSleep;
82890
+ const sleep4 = options.sleep ?? defaultSleep;
82984
82891
  const nowMs = options.nowMs ?? Date.now;
82985
82892
  const readManifest = options.readManifest ?? defaultReadManifest;
82986
82893
  const writeManifest = options.writeManifest ?? writeManifestAtomic;
@@ -82992,20 +82899,13 @@ function createHostContext(options = {}) {
82992
82899
  args,
82993
82900
  plan,
82994
82901
  run,
82995
- sleep: sleep5,
82902
+ sleep: sleep4,
82996
82903
  nowMs,
82997
82904
  readManifest,
82998
82905
  writeManifest,
82999
82906
  removeManifest,
83000
82907
  ensureLogDir,
83001
- configPath: options.configPath ?? resolveAgentConfigPath({ profile: resolveEndpointProfileFromArgs(args) }),
83002
- readOwner: options.readOwner ?? readLocalDaemonOwnerLease,
83003
- isProcessAlive: options.isProcessAlive ?? isProcessAlive,
83004
- probeEndpoint: options.probeEndpoint ?? probeLocalDaemonEndpoint,
83005
- requestWindowsShutdown: options.requestWindowsShutdown ?? requestWindowsDaemonShutdown,
83006
- inspectJournal: options.inspectJournal ?? inspectLocalDaemonRunJournal,
83007
- acquireMaintenance: options.acquireMaintenance ?? acquireLocalDaemonMaintenanceLease,
83008
- releaseMaintenance: options.releaseMaintenance ?? releaseLocalDaemonMaintenanceLease
82908
+ profile: resolveEndpointProfileFromArgs(args)
83009
82909
  };
83010
82910
  }
83011
82911
  function inspectDaemonService(options = {}) {
@@ -83021,9 +82921,8 @@ function inspectDaemonService(options = {}) {
83021
82921
  }
83022
82922
  function ensureDaemonServiceDefinition(options = {}) {
83023
82923
  const ctx = createHostContext(options);
83024
- const { plan, run, nowMs, readManifest, writeManifest, ensureLogDir } = ctx;
82924
+ const { plan, run, readManifest, writeManifest, ensureLogDir } = ctx;
83025
82925
  let definitionChanged = false;
83026
- let inspection;
83027
82926
  if (plan.manifestPath && plan.manifest) {
83028
82927
  ensureLogDir();
83029
82928
  const existing = readManifest(plan.manifestPath);
@@ -83032,38 +82931,20 @@ function ensureDaemonServiceDefinition(options = {}) {
83032
82931
  writeManifest(plan.manifestPath, plan.manifest);
83033
82932
  }
83034
82933
  } else if (plan.platform === "win32") {
83035
- ensureLogDir();
83036
- const result = run(plan.statusCommand);
83037
- const tasks = parseWindowsTaskInspection(result);
83038
- assertWindowsTaskOwnership(tasks);
83039
- if (tasks.currentPresent && tasks.currentState === "unknown" || tasks.legacyPresent && tasks.legacyState === "unknown") {
83040
- throw new Error("Cannot safely update a Windows daemon task with unknown state");
83041
- }
83042
- definitionChanged = tasks.definitionMarker !== plan.definitionMarker || !tasks.definitionMatches || tasks.legacyPresent;
83043
- const running = tasks.currentState === "running" || tasks.legacyState === "running";
83044
- inspection = {
83045
- state: running ? "running" : tasks.currentState,
83046
- platform: plan.platform,
83047
- detail: result.output || void 0,
83048
- observedAtMs: nowMs()
83049
- };
82934
+ definitionChanged = true;
83050
82935
  }
83051
- inspection ??= inspectDaemonService(options);
83052
- const definitionHostState = plan.platform === "win32" && inspection.state === "starting" ? "stopped" : inspection.state;
82936
+ const inspection = inspectDaemonService(options);
83053
82937
  const outcome = resolveEnsureDefinitionOutcome({
83054
82938
  definitionChanged,
83055
- hostState: definitionHostState
82939
+ hostState: inspection.state
83056
82940
  });
83057
82941
  if (outcome.updatePending) {
83058
82942
  return outcome;
83059
82943
  }
83060
- if (plan.platform === "win32" && !outcome.changed) {
83061
- return outcome;
83062
- }
83063
- if (definitionHostState === "running" || definitionHostState === "starting") {
82944
+ if (inspection.state === "running" || inspection.state === "starting") {
83064
82945
  return outcome;
83065
82946
  }
83066
- const definitionCommands = definitionHostState === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
82947
+ const definitionCommands = inspection.state === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
83067
82948
  for (const command of definitionCommands) run(command);
83068
82949
  return outcome;
83069
82950
  }
@@ -83075,140 +82956,10 @@ function startDaemonService(options = {}) {
83075
82956
  const { plan, run } = createHostContext(options);
83076
82957
  for (const command of plan.startCommands) run(command);
83077
82958
  }
83078
- async function stopDaemonServiceAndWait(identity, timeoutMs, options = {}) {
83079
- const {
83080
- plan,
83081
- run,
83082
- sleep: sleep5,
83083
- nowMs,
83084
- configPath,
83085
- readOwner,
83086
- isProcessAlive: processIsAlive,
83087
- probeEndpoint,
83088
- requestWindowsShutdown: requestShutdown
83089
- } = createHostContext(options);
83090
- let owner;
83091
- try {
83092
- owner = readOwner(configPath);
83093
- } catch {
83094
- return "identity_changed";
83095
- }
83096
- const ownerMatches = (candidate) => candidate.pid === identity.pid && candidate.daemonSessionId === identity.daemonSessionId && candidate.localApiPort === identity.port && (identity.serviceId === void 0 || candidate.localServiceId === identity.serviceId) && (identity.startedAtMs === void 0 || candidate.startedAtMs === identity.startedAtMs);
83097
- if (!owner || !ownerMatches(owner)) {
83098
- return "identity_changed";
83099
- }
83100
- if (plan.platform === "win32") {
83101
- const shutdown = await requestShutdown(owner);
83102
- if (shutdown === "rejected") return "identity_changed";
83103
- if (shutdown === "unreachable") {
83104
- for (const command of plan.stopCommands) run(command);
83105
- }
83106
- const deadline2 = nowMs() + timeoutMs;
83107
- while (nowMs() < deadline2) {
83108
- let currentOwner;
83109
- try {
83110
- currentOwner = readOwner(configPath);
83111
- } catch {
83112
- return "identity_changed";
83113
- }
83114
- if (currentOwner && !ownerMatches(currentOwner)) {
83115
- return "identity_changed";
83116
- }
83117
- if (!processIsAlive(identity.pid)) {
83118
- const endpoint = await probeEndpoint(identity.port, owner.localApiToken, 250);
83119
- if (endpoint.state === "absent") return "exited";
83120
- if (endpoint.state === "listening_unauthorized" || endpoint.status.daemonSessionId !== identity.daemonSessionId) {
83121
- return "identity_changed";
83122
- }
83123
- }
83124
- await sleep5(STOP_POLL_INTERVAL_MS);
83125
- }
83126
- return "timeout";
83127
- }
83128
- for (const command of plan.stopCommands) run(command);
83129
- const deadline = nowMs() + timeoutMs;
83130
- while (nowMs() < deadline) {
83131
- const result = run(plan.isActiveCommand);
83132
- const state = parseServiceHostState(plan.platform, result);
83133
- if (state === "stopped") return "exited";
83134
- if (state === "unknown") {
83135
- } else if (identity.pid > 0) {
83136
- try {
83137
- process.kill(identity.pid, 0);
83138
- } catch {
83139
- return "exited";
83140
- }
83141
- }
83142
- await sleep5(STOP_POLL_INTERVAL_MS);
83143
- }
83144
- return "timeout";
83145
- }
83146
- async function uninstallDaemonService(args = [], options = {}) {
82959
+ function uninstallDaemonService(args = [], options = {}) {
83147
82960
  const ctx = createHostContext({ ...options, args: options.args ?? args });
83148
- if (ctx.plan.platform === "win32") {
83149
- const ownership = ctx.run(ctx.plan.statusCommand);
83150
- let tasks;
83151
- try {
83152
- tasks = parseWindowsTaskInspection(ownership);
83153
- } catch (error61) {
83154
- throw new Error(
83155
- `Cannot inspect Windows daemon task ownership before uninstall: ${error61 instanceof Error ? error61.message : String(error61)}`
83156
- );
83157
- }
83158
- assertWindowsTaskOwnership(tasks);
83159
- if (!tasks.currentPresent && !tasks.legacyPresent) {
83160
- console.info("[alan-agent] Per-user daemon service removed");
83161
- return;
83162
- }
83163
- const acquired = ctx.acquireMaintenance(ctx.configPath, "watchdog", 3e4);
83164
- if (!acquired.acquired) {
83165
- throw new Error("Cannot uninstall the Windows daemon service while maintenance is active");
83166
- }
83167
- try {
83168
- const journal = ctx.inspectJournal(ctx.configPath);
83169
- if (!journal.safeToRestart) {
83170
- throw new Error(
83171
- `Windows daemon service uninstall is deferred while ${journal.activeRunCount} active run${journal.activeRunCount === 1 ? "" : "s"} finish`
83172
- );
83173
- }
83174
- let owner;
83175
- try {
83176
- owner = ctx.readOwner(ctx.configPath);
83177
- } catch (error61) {
83178
- throw new Error(
83179
- `Cannot verify Windows daemon ownership before uninstall: ${error61 instanceof Error ? error61.message : String(error61)}`
83180
- );
83181
- }
83182
- if (owner) {
83183
- const stopped = await stopDaemonServiceAndWait(
83184
- {
83185
- pid: owner.pid,
83186
- serviceId: owner.localServiceId,
83187
- daemonSessionId: owner.daemonSessionId,
83188
- port: owner.localApiPort,
83189
- startedAtMs: owner.startedAtMs
83190
- },
83191
- 1e4,
83192
- { ...options, args: ctx.args }
83193
- );
83194
- if (stopped !== "exited") {
83195
- throw new Error(
83196
- stopped === "identity_changed" ? "Windows daemon ownership changed before uninstall" : "Timed out waiting for the Windows daemon to stop before uninstall"
83197
- );
83198
- }
83199
- } else {
83200
- if (tasks.currentState !== "stopped" || tasks.legacyState !== "stopped") {
83201
- throw new Error("Cannot uninstall a running Windows daemon without verified ownership");
83202
- }
83203
- }
83204
- for (const command of ctx.plan.uninstallCommands) ctx.run(command);
83205
- } finally {
83206
- ctx.releaseMaintenance(ctx.configPath, acquired.lease.id);
83207
- }
83208
- } else {
83209
- for (const command of ctx.plan.uninstallCommands) ctx.run(command);
83210
- if (ctx.plan.manifestPath) ctx.removeManifest(ctx.plan.manifestPath);
83211
- }
82961
+ for (const command of ctx.plan.uninstallCommands) ctx.run(command);
82962
+ if (ctx.plan.manifestPath) ctx.removeManifest(ctx.plan.manifestPath);
83212
82963
  console.info("[alan-agent] Per-user daemon service removed");
83213
82964
  }
83214
82965
  function installDaemonService(args = [], runtime, options = {}) {
@@ -83236,7 +82987,7 @@ function installDaemonService(args = [], runtime, options = {}) {
83236
82987
  });
83237
82988
  return;
83238
82989
  }
83239
- if (inspection.state !== "stopped") {
82990
+ if (!shouldStartOnInstall(inspection.state)) {
83240
82991
  console.info("[alan-agent] Per-user daemon service already present", {
83241
82992
  profile,
83242
82993
  state: inspection.state
@@ -83260,48 +83011,14 @@ ${result.output}
83260
83011
  }
83261
83012
 
83262
83013
  // src/install-command.ts
83263
- var WINDOWS_SETUP_READY_TIMEOUT_MS = 9e3;
83264
- async function runInstallCommand(commandArgs, currentPlatform = process.platform) {
83265
- if (currentPlatform === "win32" && commandArgs.includes("--headless")) {
83266
- throw new Error("--headless is only supported on Linux systemd user services");
83267
- }
83014
+ async function runInstallCommand(commandArgs) {
83268
83015
  bindConfigPath(commandArgs);
83269
83016
  const configured = readConfig();
83270
83017
  const configurationMode = resolveInstallConfigurationMode(commandArgs, configured);
83271
- const isWindowsSetup = currentPlatform === "win32" && configurationMode === "setup";
83272
- const deadline = isWindowsSetup ? Date.now() + WINDOWS_SETUP_READY_TIMEOUT_MS : 0;
83273
- const runtimeIdentityChanged = configurationMode === "setup" ? await setupDaemon(commandArgs) : false;
83018
+ if (configurationMode === "setup") await setupDaemon(commandArgs);
83274
83019
  if (configurationMode === "login") await loginWithDeviceCode(commandArgs);
83275
- if (!isWindowsSetup) {
83276
- installDaemonService(commandArgs);
83277
- runMcpIntegrationCommand(["reconcile", ...commandArgs]);
83278
- return;
83279
- }
83280
- const { runtimeId, localApiPort, localApiToken } = readConfig();
83281
- if (!runtimeId || !localApiPort || !localApiToken) {
83282
- throw new Error("Windows runtime setup completed without a local readiness endpoint");
83283
- }
83284
- if (runtimeIdentityChanged) {
83285
- while (Date.now() < deadline) {
83286
- const status = await probeLocalDaemon(localApiPort, localApiToken, 250);
83287
- if (status?.runtimeId === runtimeId && status.connected) break;
83288
- if (!status) {
83289
- const service = inspectDaemonService({ args: commandArgs });
83290
- if (service.state !== "running" && service.state !== "starting") break;
83291
- }
83292
- await (0, import_promises6.setTimeout)(100);
83293
- }
83294
- }
83295
83020
  installDaemonService(commandArgs);
83296
- while (Date.now() < deadline) {
83297
- const status = await probeLocalDaemon(localApiPort, localApiToken, 250);
83298
- if (status?.runtimeId === runtimeId && status.connected) {
83299
- runMcpIntegrationCommand(["reconcile", ...commandArgs]);
83300
- return;
83301
- }
83302
- await (0, import_promises6.setTimeout)(100);
83303
- }
83304
- throw new Error("Windows runtime did not become ready within 9 seconds");
83021
+ runMcpIntegrationCommand(["reconcile", ...commandArgs]);
83305
83022
  }
83306
83023
 
83307
83024
  // src/lifecycle-logger.ts
@@ -86678,7 +86395,7 @@ var REQUEST_STATE_ONLY_LEG_PACING_MS2 = 250;
86678
86395
  function inputRequiredRoundsExceededMessage2(method, maxRounds) {
86679
86396
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
86680
86397
  }
86681
- function sleep4(ms, signal) {
86398
+ function sleep3(ms, signal) {
86682
86399
  return new Promise((resolve15, reject) => {
86683
86400
  if (signal?.aborted) {
86684
86401
  reject(signal.reason instanceof SdkError2 ? signal.reason : new SdkError2(SdkErrorCode2.RequestTimeout, String(signal.reason)));
@@ -94945,7 +94662,7 @@ var LegacyInputRequiredShim = class {
94945
94662
  } finally {
94946
94663
  roundAbort.dispose();
94947
94664
  }
94948
- } else await sleep4(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
94665
+ } else await sleep3(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
94949
94666
  let ctxNext = {
94950
94667
  ...ctx,
94951
94668
  mcpReq: {
@@ -95742,7 +95459,7 @@ async function runMcpStdioProxy(env = process.env, local = new StdioServerTransp
95742
95459
  }
95743
95460
 
95744
95461
  // src/native-hook-command.ts
95745
- var import_node_crypto22 = require("crypto");
95462
+ var import_node_crypto21 = require("crypto");
95746
95463
  var import_node_fs27 = require("fs");
95747
95464
  var import_node_os17 = require("os");
95748
95465
  var import_node_path29 = require("path");
@@ -96111,7 +95828,7 @@ function parseNativeHookEnvelope(input2) {
96111
95828
  const projectPin = eventType === "session_start" && resolvedCwd ? readAlanProjectPin(resolvedCwd) : void 0;
96112
95829
  return {
96113
95830
  schemaVersion: 1,
96114
- eventId: (0, import_node_crypto22.randomUUID)(),
95831
+ eventId: (0, import_node_crypto21.randomUUID)(),
96115
95832
  provider: capability.provider,
96116
95833
  eventType,
96117
95834
  observedAt: (input2.now ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -96278,7 +95995,7 @@ function decodeResumeFallbackContext(encoded) {
96278
95995
  }
96279
95996
 
96280
95997
  // src/sandbox.ts
96281
- var import_node_os18 = require("os");
95998
+ var import_node_os19 = require("os");
96282
95999
 
96283
96000
  // src/execution-activity-tracker.ts
96284
96001
  var ExecutionActivityTracker = class {
@@ -96607,7 +96324,7 @@ function delay2(ms) {
96607
96324
  }
96608
96325
 
96609
96326
  // src/web-presenter.ts
96610
- var import_node_crypto23 = require("crypto");
96327
+ var import_node_crypto22 = require("crypto");
96611
96328
  var WebPresenter = class {
96612
96329
  constructor(wsClient, conversationId, cwd) {
96613
96330
  this.conversationId = conversationId;
@@ -96793,7 +96510,7 @@ var WebPresenter = class {
96793
96510
  this.applicationProblem = createAlanProblem({
96794
96511
  domain: "application",
96795
96512
  code: input2.code,
96796
- id: (0, import_node_crypto23.randomUUID)(),
96513
+ id: (0, import_node_crypto22.randomUUID)(),
96797
96514
  title: APPLICATION_PROBLEM_TITLES[input2.code],
96798
96515
  detail: input2.message,
96799
96516
  // The schema's slot for the technical cause, so the problem carries it
@@ -96863,7 +96580,7 @@ var WebPresenter = class {
96863
96580
  };
96864
96581
 
96865
96582
  // src/ws-client.ts
96866
- var import_node_crypto25 = require("crypto");
96583
+ var import_node_crypto24 = require("crypto");
96867
96584
 
96868
96585
  // ../shared/dist/agent-liveness.js
96869
96586
  var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
@@ -96874,6 +96591,34 @@ var agentRunStateSchema = external_exports.object({
96874
96591
  /** Run ids currently executing (cloud sandbox agent runs one at a time, but keep it a list). */
96875
96592
  activeRunIds: external_exports.array(external_exports.string()).default([])
96876
96593
  });
96594
+ var AGENT_RESOURCE_SAMPLE_INTERVAL_MS = 6e4;
96595
+ var resourceCounter = external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
96596
+ var resourceMeasurement = external_exports.number().nonnegative().max(Number.MAX_SAFE_INTEGER);
96597
+ var pressurePercentage = external_exports.number().min(0).max(100);
96598
+ var agentResourceDiagnosticsSchema = external_exports.object({
96599
+ sampledAtMs: resourceCounter,
96600
+ memoryTotalBytes: resourceCounter,
96601
+ memoryFreeBytes: resourceCounter,
96602
+ /** Linux MemAvailable includes reclaimable memory; free memory alone does not. */
96603
+ memoryAvailableBytes: resourceCounter.optional(),
96604
+ swapTotalBytes: resourceCounter.optional(),
96605
+ swapFreeBytes: resourceCounter.optional(),
96606
+ processRssBytes: resourceCounter,
96607
+ cpuCount: external_exports.number().int().positive().max(65536),
96608
+ /** Omitted on Windows, where os.loadavg() always returns zero. */
96609
+ loadAverage1m: resourceMeasurement.optional(),
96610
+ eventLoopDelayMaxMs: resourceMeasurement.optional(),
96611
+ eventLoopDelayP99Ms: resourceMeasurement.optional(),
96612
+ memoryPressureSomeAvg10: pressurePercentage.optional(),
96613
+ memoryPressureFullAvg10: pressurePercentage.optional(),
96614
+ cpuPressureSomeAvg10: pressurePercentage.optional(),
96615
+ ioPressureSomeAvg10: pressurePercentage.optional(),
96616
+ /** Counters for the agent's current cgroup v2, including its child workload. */
96617
+ cgroupMemoryCurrentBytes: resourceCounter.optional(),
96618
+ cgroupMemoryLimitBytes: resourceCounter.optional(),
96619
+ cgroupMemoryOomEvents: resourceCounter.optional(),
96620
+ cgroupMemoryOomKillEvents: resourceCounter.optional()
96621
+ });
96877
96622
  var agentHeartbeatSchema = external_exports.object({
96878
96623
  /** Stable id for this agent OS process; changes only on a fresh agent start. */
96879
96624
  agentSessionId: external_exports.string().min(1),
@@ -96882,7 +96627,9 @@ var agentHeartbeatSchema = external_exports.object({
96882
96627
  /** Capability marker — presence is what distinguishes a new agent from a legacy one. */
96883
96628
  protocolVersion: external_exports.number().int().positive(),
96884
96629
  runState: agentRunStateSchema,
96885
- agentVersion: external_exports.string().optional()
96630
+ agentVersion: external_exports.string().optional(),
96631
+ // Bad optional diagnostics must never discard otherwise valid liveness evidence.
96632
+ resources: agentResourceDiagnosticsSchema.optional().catch(void 0)
96886
96633
  });
96887
96634
  var agentProbeAckSchema = external_exports.object({
96888
96635
  agentSessionId: external_exports.string().min(1),
@@ -96890,14 +96637,152 @@ var agentProbeAckSchema = external_exports.object({
96890
96637
  protocolVersion: external_exports.number().int().positive().optional()
96891
96638
  });
96892
96639
 
96893
- // src/sandbox-outbox.ts
96894
- var import_node_crypto24 = require("crypto");
96640
+ // src/resource-diagnostics.ts
96895
96641
  var import_node_fs28 = require("fs");
96642
+ var import_node_os18 = require("os");
96643
+ var import_node_path30 = require("path");
96644
+ var import_node_perf_hooks = require("perf_hooks");
96645
+ var MAX_RESOURCE_FILE_BYTES = 16384;
96646
+ var RESOURCE_READ_TIMEOUT_MS = 500;
96647
+ async function readResourceFile(path2) {
96648
+ try {
96649
+ const stream = (0, import_node_fs28.createReadStream)(path2, {
96650
+ encoding: "utf8",
96651
+ start: 0,
96652
+ end: MAX_RESOURCE_FILE_BYTES,
96653
+ signal: AbortSignal.timeout(RESOURCE_READ_TIMEOUT_MS)
96654
+ });
96655
+ let text = "";
96656
+ for await (const chunk of stream) {
96657
+ text += chunk;
96658
+ if (text.length > MAX_RESOURCE_FILE_BYTES) return void 0;
96659
+ }
96660
+ return text;
96661
+ } catch {
96662
+ return void 0;
96663
+ }
96664
+ }
96665
+ function counter(value2) {
96666
+ if (!value2 || !/^\d+$/.test(value2)) return void 0;
96667
+ const parsed = Number(value2);
96668
+ return Number.isSafeInteger(parsed) ? parsed : void 0;
96669
+ }
96670
+ function pressure(text, kind) {
96671
+ const value2 = text?.match(new RegExp(`^${kind} avg10=(\\d+(?:\\.\\d+)?) `, "m"))?.[1];
96672
+ if (value2 === void 0) return void 0;
96673
+ const parsed = Number(value2);
96674
+ return Number.isFinite(parsed) && parsed <= 100 ? parsed : void 0;
96675
+ }
96676
+ function meminfoBytes(text, field) {
96677
+ const kb = counter(text?.match(new RegExp(`^${field}:\\s+(\\d+) kB$`, "m"))?.[1]);
96678
+ return kb !== void 0 && Number.isSafeInteger(kb * 1024) ? kb * 1024 : void 0;
96679
+ }
96680
+ function cgroupDirectory(text) {
96681
+ const path2 = text?.match(/^0::(\/[^\n]*)$/m)?.[1];
96682
+ if (!path2 || path2.split("/").includes("..")) return void 0;
96683
+ return import_node_path30.posix.join("/sys/fs/cgroup", path2);
96684
+ }
96685
+ async function collectAgentResourceDiagnostics(options = {}) {
96686
+ const platform6 = options.platform ?? process.platform;
96687
+ const readFile7 = async (path2) => {
96688
+ try {
96689
+ return await (options.readFile ?? readResourceFile)(path2);
96690
+ } catch {
96691
+ return void 0;
96692
+ }
96693
+ };
96694
+ const sample = {
96695
+ sampledAtMs: Date.now(),
96696
+ memoryTotalBytes: (0, import_node_os18.totalmem)(),
96697
+ memoryFreeBytes: (0, import_node_os18.freemem)(),
96698
+ processRssBytes: process.memoryUsage.rss(),
96699
+ cpuCount: (0, import_node_os18.availableParallelism)(),
96700
+ ...platform6 === "win32" ? {} : { loadAverage1m: (0, import_node_os18.loadavg)()[0] }
96701
+ };
96702
+ if (platform6 !== "linux") return sample;
96703
+ const [meminfo, memoryPsi, cpuPsi, ioPsi, cgroup] = await Promise.all([
96704
+ readFile7("/proc/meminfo"),
96705
+ readFile7("/proc/pressure/memory"),
96706
+ readFile7("/proc/pressure/cpu"),
96707
+ readFile7("/proc/pressure/io"),
96708
+ readFile7("/proc/self/cgroup")
96709
+ ]);
96710
+ sample.memoryAvailableBytes = meminfoBytes(meminfo, "MemAvailable");
96711
+ sample.swapTotalBytes = meminfoBytes(meminfo, "SwapTotal");
96712
+ sample.swapFreeBytes = meminfoBytes(meminfo, "SwapFree");
96713
+ sample.memoryPressureSomeAvg10 = pressure(memoryPsi, "some");
96714
+ sample.memoryPressureFullAvg10 = pressure(memoryPsi, "full");
96715
+ sample.cpuPressureSomeAvg10 = pressure(cpuPsi, "some");
96716
+ sample.ioPressureSomeAvg10 = pressure(ioPsi, "some");
96717
+ const directory = cgroupDirectory(cgroup);
96718
+ if (!directory) return sample;
96719
+ const [current, limit, events] = await Promise.all([
96720
+ readFile7(`${directory}/memory.current`),
96721
+ readFile7(`${directory}/memory.max`),
96722
+ readFile7(`${directory}/memory.events`)
96723
+ ]);
96724
+ sample.cgroupMemoryCurrentBytes = counter(current?.trim());
96725
+ sample.cgroupMemoryLimitBytes = counter(limit?.trim());
96726
+ sample.cgroupMemoryOomEvents = counter(events?.match(/^oom (\d+)$/m)?.[1]);
96727
+ sample.cgroupMemoryOomKillEvents = counter(events?.match(/^oom_kill (\d+)$/m)?.[1]);
96728
+ return sample;
96729
+ }
96730
+ var AgentResourceSampler = class {
96731
+ constructor(collect = collectAgentResourceDiagnostics) {
96732
+ this.collect = collect;
96733
+ }
96734
+ delay = (0, import_node_perf_hooks.monitorEventLoopDelay)({ resolution: 20 });
96735
+ timer;
96736
+ inFlight = false;
96737
+ generation = 0;
96738
+ sample;
96739
+ start() {
96740
+ if (this.timer) return;
96741
+ this.generation += 1;
96742
+ this.delay.reset();
96743
+ this.delay.enable();
96744
+ this.timer = setInterval(() => void this.refresh(), AGENT_RESOURCE_SAMPLE_INTERVAL_MS);
96745
+ this.timer.unref();
96746
+ void this.refresh();
96747
+ }
96748
+ stop() {
96749
+ this.generation += 1;
96750
+ if (this.timer) clearInterval(this.timer);
96751
+ this.timer = void 0;
96752
+ this.delay.disable();
96753
+ this.sample = void 0;
96754
+ }
96755
+ latest() {
96756
+ return this.sample;
96757
+ }
96758
+ async refresh() {
96759
+ if (this.inFlight) return;
96760
+ this.inFlight = true;
96761
+ const generation = this.generation;
96762
+ const delay3 = this.delay.count > 0 ? {
96763
+ eventLoopDelayMaxMs: this.delay.max / 1e6,
96764
+ eventLoopDelayP99Ms: this.delay.percentile(99) / 1e6
96765
+ } : {};
96766
+ this.delay.reset();
96767
+ try {
96768
+ const sample = await this.collect();
96769
+ if (generation === this.generation) this.sample = { ...sample, ...delay3 };
96770
+ } catch {
96771
+ if (generation === this.generation) this.sample = void 0;
96772
+ } finally {
96773
+ this.inFlight = false;
96774
+ }
96775
+ }
96776
+ };
96777
+
96778
+ // src/sandbox-outbox.ts
96779
+ var import_node_crypto23 = require("crypto");
96780
+ var import_node_fs29 = require("fs");
96896
96781
  function deriveSandboxOutboxKey(sessionToken) {
96897
96782
  if (!sessionToken) {
96898
96783
  throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
96899
96784
  }
96900
- return (0, import_node_crypto24.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
96785
+ return (0, import_node_crypto23.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
96901
96786
  }
96902
96787
  function sandboxOutboxSpoolPath(conversationId) {
96903
96788
  return `/tmp/alan-agent-outbox-${conversationId}.enc`;
@@ -96913,24 +96798,24 @@ function assertValidOutboxKey(key, path2) {
96913
96798
  }
96914
96799
  function loadOrCreateSandboxOutboxKey(input2) {
96915
96800
  const keyPath = sandboxOutboxKeyPath(input2.spoolPath);
96916
- if ((0, import_node_fs28.existsSync)(keyPath)) {
96917
- (0, import_node_fs28.chmodSync)(keyPath, 384);
96918
- return assertValidOutboxKey((0, import_node_fs28.readFileSync)(keyPath, "utf8").trim(), keyPath);
96801
+ if ((0, import_node_fs29.existsSync)(keyPath)) {
96802
+ (0, import_node_fs29.chmodSync)(keyPath, 384);
96803
+ return assertValidOutboxKey((0, import_node_fs29.readFileSync)(keyPath, "utf8").trim(), keyPath);
96919
96804
  }
96920
96805
  const initialKey = deriveSandboxOutboxKey(input2.sessionToken);
96921
96806
  try {
96922
- (0, import_node_fs28.writeFileSync)(keyPath, initialKey, { encoding: "utf8", flag: "wx", mode: 384 });
96807
+ (0, import_node_fs29.writeFileSync)(keyPath, initialKey, { encoding: "utf8", flag: "wx", mode: 384 });
96923
96808
  return initialKey;
96924
96809
  } catch (error61) {
96925
96810
  if (error61.code !== "EEXIST") throw error61;
96926
- (0, import_node_fs28.chmodSync)(keyPath, 384);
96927
- return assertValidOutboxKey((0, import_node_fs28.readFileSync)(keyPath, "utf8").trim(), keyPath);
96811
+ (0, import_node_fs29.chmodSync)(keyPath, 384);
96812
+ return assertValidOutboxKey((0, import_node_fs29.readFileSync)(keyPath, "utf8").trim(), keyPath);
96928
96813
  }
96929
96814
  }
96930
96815
  function quarantineUnusableOutbox(path2) {
96931
96816
  const suffix = `corrupt-${Date.now()}-${process.pid}`;
96932
96817
  for (const candidate of [path2, eventJournalDir(path2)]) {
96933
- if ((0, import_node_fs28.existsSync)(candidate)) (0, import_node_fs28.renameSync)(candidate, `${candidate}.${suffix}`);
96818
+ if ((0, import_node_fs29.existsSync)(candidate)) (0, import_node_fs29.renameSync)(candidate, `${candidate}.${suffix}`);
96934
96819
  }
96935
96820
  }
96936
96821
  function createSandboxEventOutbox(input2) {
@@ -97074,10 +96959,11 @@ var WSClient = class {
97074
96959
  }
97075
96960
  socket;
97076
96961
  /** Stable id for this agent process; fences stale heartbeats server-side. */
97077
- agentSessionId = (0, import_node_crypto25.randomUUID)();
96962
+ agentSessionId = (0, import_node_crypto24.randomUUID)();
97078
96963
  /** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
97079
96964
  heartbeatSeq = 0;
97080
96965
  heartbeatTimer = null;
96966
+ resourceSampler = new AgentResourceSampler();
97081
96967
  supervisedReconnectTimer = null;
97082
96968
  supervisedReconnectAttempt = 0;
97083
96969
  connectionDirective = null;
@@ -97126,11 +97012,13 @@ var WSClient = class {
97126
97012
  seq: this.heartbeatSeq,
97127
97013
  protocolVersion: AGENT_LIVENESS_PROTOCOL_VERSION,
97128
97014
  runState: this.currentRunState(),
97129
- agentVersion: this.agentVersion || void 0
97015
+ agentVersion: this.agentVersion || void 0,
97016
+ resources: this.resourceSampler.latest()
97130
97017
  };
97131
97018
  }
97132
97019
  /** Emit `agent.hello` on (re)connect and (re)start the heartbeat interval. */
97133
97020
  startHeartbeat() {
97021
+ this.resourceSampler.start();
97134
97022
  if (this.socket.connected) {
97135
97023
  this.socket.emit("agent.hello", this.buildHeartbeat());
97136
97024
  }
@@ -97142,6 +97030,7 @@ var WSClient = class {
97142
97030
  this.heartbeatTimer.unref?.();
97143
97031
  }
97144
97032
  stopHeartbeat() {
97033
+ this.resourceSampler.stop();
97145
97034
  if (this.heartbeatTimer) {
97146
97035
  clearInterval(this.heartbeatTimer);
97147
97036
  this.heartbeatTimer = null;
@@ -97606,7 +97495,7 @@ async function runSandbox(config2) {
97606
97495
  }
97607
97496
  const presenter = new WebPresenter(wsClient, config2.sessionId, config2.projectPath);
97608
97497
  presenter.setSuppressSessionLifecycle(true);
97609
- const runtimeSkillManager = await RuntimeSkillManager.open({ homeDirectory: (0, import_node_os18.homedir)() });
97498
+ const runtimeSkillManager = await RuntimeSkillManager.open({ homeDirectory: (0, import_node_os19.homedir)() });
97610
97499
  let lastProviderSessionId = config2.providerSessionId;
97611
97500
  let resumeFallbackContext = config2.resumeFallbackContext;
97612
97501
  let activeBackendKind = config2.backendKind || "claude_cli";
@@ -97906,12 +97795,12 @@ async function runSandbox(config2) {
97906
97795
  }
97907
97796
 
97908
97797
  // src/skills/skill-cli.ts
97909
- var import_promises7 = require("fs/promises");
97910
- var import_node_os19 = require("os");
97911
- var import_node_path30 = require("path");
97798
+ var import_promises6 = require("fs/promises");
97799
+ var import_node_os20 = require("os");
97800
+ var import_node_path31 = require("path");
97912
97801
  async function runSkillsCommand(args) {
97913
97802
  const [subcommand, ...options] = args;
97914
- const homeDirectory2 = option(options, "--home") ?? (0, import_node_os19.homedir)();
97803
+ const homeDirectory2 = option(options, "--home") ?? (0, import_node_os20.homedir)();
97915
97804
  const manager = await RuntimeSkillManager.open({ homeDirectory: homeDirectory2 });
97916
97805
  if (subcommand === "scan") {
97917
97806
  const inputPath = option(options, "--input");
@@ -97952,8 +97841,8 @@ async function defaultScanInput(homeDirectory2) {
97952
97841
  });
97953
97842
  }
97954
97843
  async function readProtectedJson(path2) {
97955
- const absolutePath = (0, import_node_path30.resolve)(path2);
97956
- const stat2 = await (0, import_promises7.lstat)(absolutePath);
97844
+ const absolutePath = (0, import_node_path31.resolve)(path2);
97845
+ const stat2 = await (0, import_promises6.lstat)(absolutePath);
97957
97846
  if (!stat2.isFile()) throw new Error(`Skill input is not a regular file: ${absolutePath}`);
97958
97847
  if (process.platform !== "win32" && (stat2.mode & 63) !== 0) {
97959
97848
  throw new Error(`Skill input must not be accessible by group or other users: ${absolutePath}`);
@@ -97962,7 +97851,7 @@ async function readProtectedJson(path2) {
97962
97851
  throw new Error(`Skill input must be owned by the current user: ${absolutePath}`);
97963
97852
  }
97964
97853
  if (stat2.size > 2 * 1024 * 1024) throw new Error("Skill input exceeds 2 MiB");
97965
- return JSON.parse(await (0, import_promises7.readFile)(absolutePath, "utf8"));
97854
+ return JSON.parse(await (0, import_promises6.readFile)(absolutePath, "utf8"));
97966
97855
  }
97967
97856
  function option(args, name) {
97968
97857
  const index = args.indexOf(name);
@@ -98120,7 +98009,7 @@ async function main() {
98120
98009
  return;
98121
98010
  }
98122
98011
  if (command === "service" && commandArgs[0] === "uninstall") {
98123
- await uninstallDaemonService(commandArgs.slice(1));
98012
+ uninstallDaemonService(commandArgs.slice(1));
98124
98013
  return;
98125
98014
  }
98126
98015
  if (command === "service" && commandArgs[0] === "status") {