@alan-ai-hq/agent-manager 0.1.96 → 0.1.98

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 +823 -256
  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: createHash18 } = require("crypto");
3407
+ var { randomBytes: randomBytes13, createHash: createHash19 } = 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 = createHash18("sha1").update(key + GUID).digest("base64");
4064
+ const digest = createHash19("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: createHash18 } = require("crypto");
4431
+ var { createHash: createHash19 } = 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 = createHash18("sha1").update(key + GUID).digest("base64");
4728
+ const digest = createHash19("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.96";
15463
+ var AGENT_VERSION = "0.1.98";
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,6 +49619,16 @@ 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
  }
49622
+ function requestLocalDaemonShutdown(port, token) {
49623
+ return fetch(`http://127.0.0.1:${port}/shutdown?force=1`, {
49624
+ method: "POST",
49625
+ headers: {
49626
+ authorization: `Bearer ${token}`,
49627
+ "x-alan-shutdown-reason": "runtime_shutdown"
49628
+ },
49629
+ signal: AbortSignal.timeout(5e3)
49630
+ });
49631
+ }
49613
49632
  async function adoptLocalDaemonControl(input2) {
49614
49633
  const timeoutMs = input2.timeoutMs ?? 750;
49615
49634
  try {
@@ -74429,6 +74448,14 @@ var skillVersionIdSchema = external_exports.uuid().transform((value2) => value2)
74429
74448
  var runtimeIdSchema = external_exports.uuid().transform((value2) => value2);
74430
74449
  var skillPreviewIdSchema = external_exports.uuid().transform((value2) => value2);
74431
74450
  var skillCommandIdSchema = external_exports.uuid().transform((value2) => value2);
74451
+ var skillPackageFileSchema = defineWireSchema()(external_exports.object({
74452
+ path: external_exports.string().min(1),
74453
+ kind: external_exports.enum(["instructions", "script", "reference", "asset", "license", "other"]),
74454
+ size: external_exports.number().int().nonnegative(),
74455
+ mode: external_exports.union([external_exports.literal(420), external_exports.literal(493)]),
74456
+ sha256: sha256Schema,
74457
+ preview: external_exports.enum(["markdown", "text", "code", "image", "binary"])
74458
+ }));
74432
74459
  var skillInvocationSchema = defineWireSchema()(external_exports.discriminatedUnion("kind", [
74433
74460
  external_exports.object({
74434
74461
  kind: external_exports.literal("skill_version"),
@@ -74530,6 +74557,7 @@ function projectSkillFrontmatter(admitted) {
74530
74557
  };
74531
74558
  const license = optionalText("license", 1024);
74532
74559
  const compatibility = optionalText("compatibility", 500);
74560
+ const version2 = optionalText("version", 64);
74533
74561
  const allowedTools = optionalToolList("allowed-tools", 1024);
74534
74562
  const metadata = {};
74535
74563
  const rawMetadata = raw.metadata;
@@ -74578,6 +74606,7 @@ function projectSkillFrontmatter(admitted) {
74578
74606
  description: admitted.description,
74579
74607
  ...license ? { license } : {},
74580
74608
  ...compatibility ? { compatibility } : {},
74609
+ ...version2 ? { version: version2 } : {},
74581
74610
  metadata,
74582
74611
  ...allowedTools ? { allowedTools } : {}
74583
74612
  },
@@ -76687,13 +76716,13 @@ var RuntimeRunJournal = class {
76687
76716
  * Keep the exact interruption outcome until a durable authenticated control
76688
76717
  * channel can prove both identity and ownership.
76689
76718
  */
76690
- assessRecovery(runId, isProcessAlive) {
76719
+ assessRecovery(runId, isProcessAlive2) {
76691
76720
  const entry = this.entries.get(runId);
76692
76721
  if (!entry) return void 0;
76693
76722
  if (entry.providerPid === void 0) {
76694
76723
  return { reattachable: false, reason: "provider_not_recorded" };
76695
76724
  }
76696
- if (!isProcessAlive(entry.providerPid)) {
76725
+ if (!isProcessAlive2(entry.providerPid)) {
76697
76726
  return {
76698
76727
  reattachable: false,
76699
76728
  reason: "provider_not_running",
@@ -76747,7 +76776,8 @@ var RuntimeSetupGrantExpiredError = class extends Error {
76747
76776
  this.name = "RuntimeSetupGrantExpiredError";
76748
76777
  }
76749
76778
  };
76750
- async function createRuntimeSetupGrant(input2) {
76779
+ async function createRuntimeSetupGrant(input2, signal) {
76780
+ signal?.throwIfAborted();
76751
76781
  const response = await fetch(`${input2.apiUrl}/public/runtimes/setup-tokens`, {
76752
76782
  method: "POST",
76753
76783
  headers: {
@@ -76759,7 +76789,7 @@ async function createRuntimeSetupGrant(input2) {
76759
76789
  ...input2.scope === "team" ? { teamId: input2.teamId } : {},
76760
76790
  ttlMinutes: 5
76761
76791
  }),
76762
- signal: AbortSignal.timeout(1e4)
76792
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(1e4)]) : AbortSignal.timeout(1e4)
76763
76793
  });
76764
76794
  const body = await response.json();
76765
76795
  if (!response.ok || typeof body.setupToken !== "string") {
@@ -76768,9 +76798,10 @@ async function createRuntimeSetupGrant(input2) {
76768
76798
  }
76769
76799
  return body.setupToken;
76770
76800
  }
76771
- async function exchangeRuntimeSetupGrant(input2) {
76801
+ async function exchangeRuntimeSetupGrant(input2, setupSignal) {
76802
+ setupSignal?.throwIfAborted();
76772
76803
  const registrationAttemptId = getOrCreateSetupAttemptId(input2.setupToken);
76773
- const signal = AbortSignal.timeout(15e3);
76804
+ const signal = setupSignal ? AbortSignal.any([setupSignal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3);
76774
76805
  const registrationInit = {
76775
76806
  method: "POST",
76776
76807
  headers: { "content-type": "application/json" },
@@ -76795,6 +76826,7 @@ async function exchangeRuntimeSetupGrant(input2) {
76795
76826
  let response;
76796
76827
  let transportError;
76797
76828
  for (let attempt = 1; attempt <= 3; attempt += 1) {
76829
+ signal.throwIfAborted();
76798
76830
  try {
76799
76831
  response = await fetch(
76800
76832
  `${input2.apiUrl}/public/runtimes/register-with-setup-token`,
@@ -76802,6 +76834,7 @@ async function exchangeRuntimeSetupGrant(input2) {
76802
76834
  );
76803
76835
  transportError = void 0;
76804
76836
  } catch (error61) {
76837
+ signal.throwIfAborted();
76805
76838
  transportError = error61;
76806
76839
  }
76807
76840
  const retriable = response && (response.status === 408 || response.status === 429 || response.status >= 500);
@@ -76839,6 +76872,7 @@ async function exchangeRuntimeSetupGrant(input2) {
76839
76872
  };
76840
76873
  }
76841
76874
  async function handoffRuntimeSetupGrant(input2) {
76875
+ input2.signal?.throwIfAborted();
76842
76876
  const response = await fetch(`http://127.0.0.1:${input2.port}/credentials/setup`, {
76843
76877
  method: "POST",
76844
76878
  headers: {
@@ -76846,7 +76880,7 @@ async function handoffRuntimeSetupGrant(input2) {
76846
76880
  "content-type": "application/json"
76847
76881
  },
76848
76882
  body: JSON.stringify(input2.setup),
76849
- signal: AbortSignal.timeout(input2.timeoutMs ?? 2e4)
76883
+ signal: input2.signal ? AbortSignal.any([input2.signal, AbortSignal.timeout(input2.timeoutMs ?? 2e4)]) : AbortSignal.timeout(input2.timeoutMs ?? 2e4)
76850
76884
  });
76851
76885
  const body = await response.json();
76852
76886
  if (!response.ok) {
@@ -80440,7 +80474,20 @@ async function runLocalDaemonRecoveryLadder(input2) {
80440
80474
  error: "The local daemon exhausted its reconnect and safe-restart recovery budget."
80441
80475
  };
80442
80476
  }
80443
- async function setupDaemon(args) {
80477
+ function writeRegisteredRuntimeConfig(args, previousConfig, registered) {
80478
+ writeConfig({
80479
+ ...registered,
80480
+ localApiPort: getLocalApiPort(args, {}),
80481
+ localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
80482
+ localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
80483
+ localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80484
+ eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80485
+ daemonEpoch: previousConfig.daemonEpoch
80486
+ });
80487
+ }
80488
+ async function setupDaemon(args, options = {}) {
80489
+ const { signal } = options;
80490
+ signal?.throwIfAborted();
80444
80491
  bindConfigPath(args);
80445
80492
  const previousConfig = readConfigForRegistration();
80446
80493
  const { apiUrl, wsUrl, endpointProfile } = resolveEndpoints(args);
@@ -80458,12 +80505,15 @@ async function setupDaemon(args) {
80458
80505
  "setup requires --setup-token <token> or --token <Alan access token>.\nFor normal setup, copy the setup token from Alan and run: alan-agent setup --setup-token <token>\nFor browser authorization, run: alan-agent login"
80459
80506
  );
80460
80507
  }
80461
- const registrationGrant = setupToken ?? await createRuntimeSetupGrant({
80462
- apiUrl,
80463
- accessToken: token,
80464
- scope,
80465
- teamId
80466
- });
80508
+ const registrationGrant = setupToken ?? await createRuntimeSetupGrant(
80509
+ {
80510
+ apiUrl,
80511
+ accessToken: token,
80512
+ scope,
80513
+ teamId
80514
+ },
80515
+ signal
80516
+ );
80467
80517
  const setup = {
80468
80518
  apiUrl,
80469
80519
  wsUrl,
@@ -80473,13 +80523,16 @@ async function setupDaemon(args) {
80473
80523
  displayName,
80474
80524
  installationId
80475
80525
  };
80526
+ signal?.throwIfAborted();
80476
80527
  let liveConfig = previousConfig;
80477
80528
  const localApiPort = getLocalApiPort(args, liveConfig);
80478
80529
  let live = liveConfig.localApiToken ? await probeLocalDaemon(localApiPort, liveConfig.localApiToken) : null;
80530
+ signal?.throwIfAborted();
80479
80531
  if (!live && liveConfig.localServiceId && liveConfig.localServiceSecret) {
80480
80532
  live = await adoptConfiguredLocalDaemon(args);
80481
80533
  liveConfig = readConfigForRegistration();
80482
80534
  }
80535
+ signal?.throwIfAborted();
80483
80536
  if (live) {
80484
80537
  if (!liveConfig.localApiToken) {
80485
80538
  throw new Error("running daemon could not be authenticated for credential setup");
@@ -80493,14 +80546,16 @@ async function setupDaemon(args) {
80493
80546
  const body2 = await handoffRuntimeSetupGrant({
80494
80547
  port: localApiPort,
80495
80548
  localApiToken: liveConfig.localApiToken,
80496
- setup: liveSetup
80549
+ setup: liveSetup,
80550
+ signal
80497
80551
  });
80552
+ signal?.throwIfAborted();
80498
80553
  console.info("[alan-agent] Runtime credentials adopted by running daemon", {
80499
80554
  runtimeId: body2.runtimeId,
80500
80555
  credentialGeneration: body2.credentialGeneration,
80501
80556
  configPath: getConfigPath()
80502
80557
  });
80503
- return { daemonAlreadyRunning: true };
80558
+ return body2.runtimeId !== liveConfig.runtimeId;
80504
80559
  }
80505
80560
  if (readLocalDaemonOwnerLease(getConfigPath())) {
80506
80561
  throw new Error(
@@ -80509,28 +80564,27 @@ async function setupDaemon(args) {
80509
80564
  }
80510
80565
  let body;
80511
80566
  try {
80512
- body = await exchangeRuntimeSetupGrant(setup);
80567
+ body = await exchangeRuntimeSetupGrant(setup, signal);
80513
80568
  } catch (error61) {
80569
+ signal?.throwIfAborted();
80514
80570
  if (setupToken && error61 instanceof RuntimeSetupGrantExpiredError) {
80571
+ if (signal) {
80572
+ throw new Error("Setup link expired. Run alan-agent login to authorize in your browser.");
80573
+ }
80515
80574
  console.info("[alan-agent] Setup link expired; continuing with browser authorization");
80516
80575
  await loginWithDeviceCode(args);
80517
- return { daemonAlreadyRunning: false };
80576
+ return false;
80518
80577
  }
80519
80578
  throw error61;
80520
80579
  }
80521
- writeConfig({
80580
+ signal?.throwIfAborted();
80581
+ writeRegisteredRuntimeConfig(args, previousConfig, {
80522
80582
  apiUrl,
80523
80583
  wsUrl,
80524
80584
  endpointProfile,
80525
80585
  runtimeId: body.runtimeId,
80526
80586
  runtimeToken: body.runtimeToken,
80527
80587
  runtimeRenewalToken: body.runtimeRenewalToken,
80528
- localApiPort: getLocalApiPort(args, {}),
80529
- localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
80530
- localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
80531
- localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80532
- eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80533
- daemonEpoch: previousConfig.daemonEpoch,
80534
80588
  displayName: body.displayName,
80535
80589
  installationId
80536
80590
  });
@@ -80539,7 +80593,7 @@ async function setupDaemon(args) {
80539
80593
  runtimeId: body.runtimeId,
80540
80594
  configPath: getConfigPath()
80541
80595
  });
80542
- return { daemonAlreadyRunning: false };
80596
+ return false;
80543
80597
  }
80544
80598
  async function loginWithDeviceCode(args) {
80545
80599
  bindConfigPath(args);
@@ -80628,19 +80682,13 @@ async function loginWithDeviceCode(args) {
80628
80682
  if (ownedPending?.operationId !== operation.id) {
80629
80683
  throw new Error("browser authorization sidecar ownership was lost before finalization");
80630
80684
  }
80631
- writeConfig({
80685
+ writeRegisteredRuntimeConfig(args, previousConfig, {
80632
80686
  apiUrl,
80633
80687
  wsUrl,
80634
80688
  endpointProfile,
80635
80689
  runtimeId: body.runtime.id,
80636
80690
  runtimeToken: body.runtimeToken,
80637
80691
  runtimeRenewalToken: body.runtimeRenewalToken,
80638
- localApiPort: getLocalApiPort(args, {}),
80639
- localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
80640
- localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
80641
- localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80642
- eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80643
- daemonEpoch: previousConfig.daemonEpoch,
80644
80692
  displayName: body.runtime.displayName ?? displayName,
80645
80693
  installationId
80646
80694
  });
@@ -80679,14 +80727,7 @@ async function stopDaemon(args) {
80679
80727
  console.info("[alan-agent] No daemon listening", { port: localApiPort });
80680
80728
  return;
80681
80729
  }
80682
- const response = await fetch(`http://127.0.0.1:${localApiPort}/shutdown?force=1`, {
80683
- method: "POST",
80684
- headers: {
80685
- authorization: `Bearer ${localApiToken}`,
80686
- "x-alan-shutdown-reason": "runtime_shutdown"
80687
- },
80688
- signal: AbortSignal.timeout(5e3)
80689
- });
80730
+ const response = await requestLocalDaemonShutdown(localApiPort, localApiToken);
80690
80731
  if (!response.ok) {
80691
80732
  throw new Error(`Failed to stop daemon: HTTP ${response.status}`);
80692
80733
  }
@@ -80831,14 +80872,7 @@ async function drainAndStopLocalDaemonBeforeCredentialCleanup(args, stored) {
80831
80872
  `Logout could not safely drain the local daemon (HTTP ${prepare.status}). Stop or update the local service before removing credentials.`
80832
80873
  );
80833
80874
  }
80834
- const shutdown = await fetch(`http://127.0.0.1:${localApiPort}/shutdown?force=1`, {
80835
- method: "POST",
80836
- headers: {
80837
- authorization: `Bearer ${localApiToken}`,
80838
- "x-alan-shutdown-reason": "runtime_shutdown"
80839
- },
80840
- signal: AbortSignal.timeout(5e3)
80841
- });
80875
+ const shutdown = await requestLocalDaemonShutdown(localApiPort, localApiToken);
80842
80876
  if (!shutdown.ok) {
80843
80877
  throw new Error(`Logout could not stop the local daemon (HTTP ${shutdown.status}).`);
80844
80878
  }
@@ -80999,6 +81033,9 @@ async function printDoctor(args = []) {
80999
81033
  );
81000
81034
  }
81001
81035
 
81036
+ // src/install-command.ts
81037
+ var import_promises6 = require("timers/promises");
81038
+
81002
81039
  // src/mcp-config-adapters.ts
81003
81040
  var import_node_crypto20 = require("crypto");
81004
81041
  var import_node_fs23 = require("fs");
@@ -82383,12 +82420,14 @@ function runMcpIntegrationCommand(args, options = {}) {
82383
82420
 
82384
82421
  // src/service-manager.ts
82385
82422
  var import_node_child_process9 = require("child_process");
82423
+ var import_node_crypto21 = require("crypto");
82386
82424
  var import_node_fs25 = require("fs");
82387
82425
  var import_node_os16 = require("os");
82388
82426
  var import_node_path27 = require("path");
82389
82427
  var SERVICE_LABEL = "ai.tryalan.agent";
82390
82428
  var SYSTEMD_UNIT = "alan-agent.service";
82391
82429
  var WINDOWS_TASK = "Alan Agent";
82430
+ var STOP_POLL_INTERVAL_MS = 200;
82392
82431
  function xml(value2) {
82393
82432
  return value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
82394
82433
  }
@@ -82398,6 +82437,18 @@ function systemdArg(value2) {
82398
82437
  function powershellLiteral(value2) {
82399
82438
  return `'${value2.replaceAll("'", "''")}'`;
82400
82439
  }
82440
+ function powershellCommand(script, tolerateFailure = false) {
82441
+ return {
82442
+ command: "powershell.exe",
82443
+ args: ["-NoProfile", "-NonInteractive", "-Command", script],
82444
+ tolerateFailure
82445
+ };
82446
+ }
82447
+ function windowsCommandLineArgument(value2) {
82448
+ if (value2 && !/[\s"]/u.test(value2)) return value2;
82449
+ const escaped = value2.replace(/(\\*)"/gu, (_match, slashes) => `${slashes}${slashes}\\"`).replace(/\\+$/u, (slashes) => `${slashes}${slashes}`);
82450
+ return `"${escaped}"`;
82451
+ }
82401
82452
  function serviceNames(profile) {
82402
82453
  if (profile === "production") {
82403
82454
  return { launchd: SERVICE_LABEL, systemd: SYSTEMD_UNIT, windows: WINDOWS_TASK };
@@ -82419,7 +82470,7 @@ function buildDaemonServicePlan(input2) {
82419
82470
  const domain2 = `gui/${userId}`;
82420
82471
  const serviceTarget = `${domain2}/${names.launchd}`;
82421
82472
  const manifestPath = (0, import_node_path27.join)(input2.homeDir, "Library", "LaunchAgents", `${names.launchd}.plist`);
82422
- const logDir = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
82473
+ const logDir2 = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
82423
82474
  const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
82424
82475
  const environmentEntries = Object.entries(input2.environment ?? {}).map(([key, value2]) => ` <key>${xml(key)}</key>
82425
82476
  <string>${xml(value2)}</string>`).join("\n");
@@ -82448,9 +82499,9 @@ ${environmentBlock} <key>RunAtLoad</key>
82448
82499
  <key>ThrottleInterval</key>
82449
82500
  <integer>5</integer>
82450
82501
  <key>StandardOutPath</key>
82451
- <string>${xml((0, import_node_path27.join)(logDir, "daemon.log"))}</string>
82502
+ <string>${xml((0, import_node_path27.join)(logDir2, "daemon.log"))}</string>
82452
82503
  <key>StandardErrorPath</key>
82453
- <string>${xml((0, import_node_path27.join)(logDir, "daemon-error.log"))}</string>
82504
+ <string>${xml((0, import_node_path27.join)(logDir2, "daemon-error.log"))}</string>
82454
82505
  </dict>
82455
82506
  </plist>
82456
82507
  `;
@@ -82458,7 +82509,7 @@ ${environmentBlock} <key>RunAtLoad</key>
82458
82509
  platform: "darwin",
82459
82510
  manifestPath,
82460
82511
  manifest,
82461
- logDirectory: logDir,
82512
+ logDirectory: logDir2,
82462
82513
  loadDefinitionCommands: [
82463
82514
  {
82464
82515
  command: "launchctl",
@@ -82499,23 +82550,6 @@ ${environmentBlock} <key>RunAtLoad</key>
82499
82550
  tolerateFailure: true
82500
82551
  }
82501
82552
  ],
82502
- replaceCommands: [
82503
- {
82504
- command: "launchctl",
82505
- args: ["bootout", domain2, manifestPath],
82506
- tolerateFailure: true
82507
- },
82508
- {
82509
- command: "launchctl",
82510
- args: ["bootstrap", domain2, manifestPath],
82511
- tolerateFailure: false
82512
- },
82513
- {
82514
- command: "launchctl",
82515
- args: ["kickstart", "-k", serviceTarget],
82516
- tolerateFailure: false
82517
- }
82518
- ],
82519
82553
  uninstallCommands: [
82520
82554
  {
82521
82555
  command: "launchctl",
@@ -82532,8 +82566,7 @@ ${environmentBlock} <key>RunAtLoad</key>
82532
82566
  command: "launchctl",
82533
82567
  args: ["print", serviceTarget],
82534
82568
  tolerateFailure: true
82535
- },
82536
- lingerStatusCommand: null
82569
+ }
82537
82570
  };
82538
82571
  }
82539
82572
  if (input2.platform === "linux") {
@@ -82596,18 +82629,6 @@ WantedBy=default.target
82596
82629
  tolerateFailure: true
82597
82630
  }
82598
82631
  ],
82599
- replaceCommands: [
82600
- {
82601
- command: "systemctl",
82602
- args: ["--user", "daemon-reload"],
82603
- tolerateFailure: false
82604
- },
82605
- {
82606
- command: "systemctl",
82607
- args: ["--user", "restart", names.systemd],
82608
- tolerateFailure: false
82609
- }
82610
- ],
82611
82632
  uninstallCommands: [
82612
82633
  {
82613
82634
  command: "systemctl",
@@ -82629,117 +82650,174 @@ WantedBy=default.target
82629
82650
  command: "systemctl",
82630
82651
  args: ["--user", "is-active", names.systemd],
82631
82652
  tolerateFailure: true
82632
- },
82633
- lingerStatusCommand: {
82634
- command: "loginctl",
82635
- args: ["show-user", "$(id -un)", "-p", "Linger"],
82636
- tolerateFailure: true
82637
82653
  }
82638
82654
  };
82639
82655
  }
82640
- const environmentPrefix = Object.entries(input2.environment ?? {}).map(([key, value2]) => `$env:${key} = ${powershellLiteral(value2)}`).join("; ");
82641
- const directArgument = [input2.cliEntry, "daemon", "--profile", input2.profile].map((part) => part.includes(" ") ? `"${part}"` : part).join(" ");
82642
- const executable = powershellLiteral(environmentPrefix ? "powershell.exe" : input2.nodeExecutable);
82643
- const argument = powershellLiteral(
82644
- environmentPrefix ? `-NoProfile -NonInteractive -WindowStyle Hidden -Command "${environmentPrefix}; & ${powershellLiteral(input2.nodeExecutable)} ${powershellLiteral(input2.cliEntry)} daemon --profile ${input2.profile}"` : directArgument
82656
+ const logDir = import_node_path27.win32.join(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
82657
+ const legacyEnvironmentPrefix = Object.entries(input2.environment ?? {}).map(([key, value2]) => `$env:${key} = ${powershellLiteral(value2)}`).join("; ");
82658
+ const legacyDirectArguments = [input2.cliEntry, "daemon", "--profile", input2.profile].map((part) => part.includes(" ") ? `"${part}"` : part).join(" ");
82659
+ const legacyExecutable = legacyEnvironmentPrefix ? "powershell.exe" : input2.nodeExecutable;
82660
+ const legacyArguments = legacyEnvironmentPrefix ? `-NoProfile -NonInteractive -WindowStyle Hidden -Command "${legacyEnvironmentPrefix}; & ${powershellLiteral(input2.nodeExecutable)} ${powershellLiteral(input2.cliEntry)} daemon --profile ${input2.profile}"` : legacyDirectArguments;
82661
+ const daemonArguments = [input2.cliEntry, "daemon", "--profile", input2.profile].map(windowsCommandLineArgument).join(" ");
82662
+ const environmentStatements = Object.entries(input2.environment ?? {}).map(
82663
+ ([key, value2]) => `$startInfo.EnvironmentVariables[${powershellLiteral(key)}] = ${powershellLiteral(value2)}`
82645
82664
  );
82665
+ const launcherScript = [
82666
+ "$ErrorActionPreference = 'Stop'",
82667
+ "$startInfo = [System.Diagnostics.ProcessStartInfo]::new()",
82668
+ `$startInfo.FileName = ${powershellLiteral(input2.nodeExecutable)}`,
82669
+ `$startInfo.Arguments = ${powershellLiteral(daemonArguments)}`,
82670
+ `$startInfo.WorkingDirectory = ${powershellLiteral(import_node_path27.win32.dirname(input2.cliEntry))}`,
82671
+ "$startInfo.UseShellExecute = $false",
82672
+ "$startInfo.CreateNoWindow = $true",
82673
+ "$startInfo.RedirectStandardOutput = $true",
82674
+ "$startInfo.RedirectStandardError = $true",
82675
+ ...environmentStatements,
82676
+ `$stdout = [System.IO.File]::Open(${powershellLiteral(import_node_path27.win32.join(logDir, "daemon-launcher.log"))}, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)`,
82677
+ `$stderr = [System.IO.File]::Open(${powershellLiteral(import_node_path27.win32.join(logDir, "daemon-launcher-error.log"))}, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)`,
82678
+ "$process = [System.Diagnostics.Process]::new()",
82679
+ "$process.StartInfo = $startInfo",
82680
+ "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() }",
82681
+ "exit $exitCode"
82682
+ ].join("; ");
82683
+ const executable = powershellLiteral("powershell.exe");
82684
+ const launcherArguments = `-NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand ${Buffer.from(launcherScript, "utf16le").toString("base64")}`;
82685
+ const settingsScript = "New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries";
82686
+ const ownerMarker = `Alan daemon owner v1 ${(0, import_node_crypto21.createHash)("sha256").update(
82687
+ JSON.stringify({
82688
+ executable: input2.nodeExecutable,
82689
+ cliEntry: input2.cliEntry,
82690
+ profile: input2.profile,
82691
+ taskBaseName: names.windows
82692
+ })
82693
+ ).digest("hex")}`;
82694
+ const definitionMarker = `${ownerMarker}; definition v3 ${(0, import_node_crypto21.createHash)("sha256").update(
82695
+ JSON.stringify({
82696
+ executable: "powershell.exe",
82697
+ launcherArguments,
82698
+ workingDirectory: import_node_path27.win32.dirname(input2.cliEntry),
82699
+ settingsScript,
82700
+ taskBaseName: names.windows
82701
+ })
82702
+ ).digest("hex")}`;
82703
+ const windowsDefinitionContext = [
82704
+ `$expectedLauncherArguments = ${powershellLiteral(launcherArguments)}`,
82705
+ `$expectedWorkingDirectory = ${powershellLiteral(import_node_path27.win32.dirname(input2.cliEntry))}`,
82706
+ `$expectedOwnerMarker = ${powershellLiteral(`${ownerMarker};`)}`,
82707
+ `$expectedDefinitionMarker = ${powershellLiteral(definitionMarker)}`,
82708
+ `$expectedLegacyExecutable = ${powershellLiteral(legacyExecutable)}`,
82709
+ `$expectedLegacyArguments = ${powershellLiteral(legacyArguments)}`
82710
+ ].join("; ");
82711
+ const taskContext = [
82712
+ "$currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()",
82713
+ "$currentUser = $currentIdentity.Name",
82714
+ "$currentSid = $currentIdentity.User.Value",
82715
+ `$taskName = ${powershellLiteral(names.windows)} + ' ' + $currentSid`
82716
+ ].join("; ");
82717
+ 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 '' } } }";
82718
+ const currentLookup = "$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]";
82719
+ const checkCurrentOwnership = [
82720
+ currentLookup,
82721
+ "$currentOwned = $false",
82722
+ "if ($null -ne $task) { try {",
82723
+ "$taskAction = @($task.Actions)[0]",
82724
+ "$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))",
82725
+ "} catch { $currentOwned = $false } }"
82726
+ ].join("; ");
82727
+ const legacyLookup = `$legacyTask = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq ${powershellLiteral(names.windows)} })[0]`;
82728
+ const checkLegacyOwnership = [
82729
+ legacyLookup,
82730
+ "$legacyOwned = $false",
82731
+ "$legacyState = 'Missing'",
82732
+ "if ($null -ne $legacyTask) { try {",
82733
+ "$legacyState = [string]$legacyTask.State",
82734
+ "$legacySid = Resolve-TaskSid ([string]$legacyTask.Principal.UserId)",
82735
+ "$legacyAction = @($legacyTask.Actions)[0]",
82736
+ "if ($null -ne $legacyAction) {",
82737
+ "$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))",
82738
+ "}",
82739
+ "} catch { $legacyOwned = $false } }"
82740
+ ].join("; ");
82741
+ const assertOwnedTasks = [
82742
+ checkCurrentOwnership,
82743
+ checkLegacyOwnership,
82744
+ `if ($null -ne $task -and -not $currentOwned) { throw ${powershellLiteral(`${names.windows} task is not owned by this Alan installation`)} }`,
82745
+ `if ($null -ne $legacyTask -and -not $legacyOwned) { throw ${powershellLiteral(`Legacy ${names.windows} task is not owned by this Alan installation`)} }`,
82746
+ "if ($null -ne $task -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$task.State)) { throw 'Alan daemon task became active or has unknown state' }",
82747
+ "if ($null -ne $legacyTask -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$legacyTask.State)) { throw 'Legacy Alan daemon task became active or has unknown state' }"
82748
+ ].join("; ");
82749
+ const cleanupLegacyTask = [
82750
+ checkLegacyOwnership,
82751
+ `if ($null -ne $legacyTask -and -not $legacyOwned) { throw ${powershellLiteral(`Legacy ${names.windows} task is not owned by this Alan installation`)} }`,
82752
+ "if ($null -ne $legacyTask -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$legacyTask.State)) { throw 'Legacy Alan daemon task became active or has unknown state' }",
82753
+ `if ($null -ne $legacyTask) { Unregister-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -TaskPath '\\' -Confirm:$false -ErrorAction Stop }`,
82754
+ `$legacyRemaining = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq ${powershellLiteral(names.windows)} })[0]`,
82755
+ "if ($null -ne $legacyRemaining) { throw 'Legacy Alan daemon task still exists after migration' }"
82756
+ ].join("; ");
82646
82757
  const registerScript = [
82647
- "$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name",
82648
- `$action = New-ScheduledTaskAction -Execute ${executable} -Argument ${argument}`,
82758
+ taskContext,
82759
+ resolveTaskSid,
82760
+ windowsDefinitionContext,
82761
+ `$action = New-ScheduledTaskAction -Execute ${executable} -Argument $expectedLauncherArguments -WorkingDirectory $expectedWorkingDirectory`,
82649
82762
  "$trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser",
82650
82763
  "$principal = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Limited",
82651
- "$settings = New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero)",
82652
- `Register-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null`
82764
+ `$settings = ${settingsScript}`,
82765
+ assertOwnedTasks,
82766
+ "Register-ScheduledTask -TaskName $taskName -Description $expectedDefinitionMarker -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force -ErrorAction Stop | Out-Null",
82767
+ cleanupLegacyTask,
82768
+ "$registeredTask = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
82769
+ "if ($null -eq $registeredTask -or $registeredTask.Description -cne $expectedDefinitionMarker) { throw 'Alan daemon task definition was not applied' }"
82770
+ ].join("; ");
82771
+ const uninstallScript = [
82772
+ taskContext,
82773
+ resolveTaskSid,
82774
+ windowsDefinitionContext,
82775
+ assertOwnedTasks,
82776
+ "if ($null -ne $legacyTask) { Unregister-ScheduledTask -TaskName $legacyTask.TaskName -TaskPath $legacyTask.TaskPath -Confirm:$false -ErrorAction Stop }",
82777
+ "$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
82778
+ "if ($null -ne $task) { Unregister-ScheduledTask -TaskName $taskName -TaskPath '\\' -Confirm:$false -ErrorAction Stop }",
82779
+ "$remaining = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and ($_.TaskName -eq $taskName -or $_.TaskName -eq $legacyTask.TaskName) })",
82780
+ "if ($remaining.Count -gt 0) { throw 'Alan daemon task still exists after unregister' }"
82653
82781
  ].join("; ");
82782
+ const statusScript = [
82783
+ taskContext,
82784
+ resolveTaskSid,
82785
+ windowsDefinitionContext,
82786
+ checkCurrentOwnership,
82787
+ checkLegacyOwnership,
82788
+ "$definitionMatches = $false",
82789
+ "if ($null -eq $task) { Write-Output 'State: Missing' } else {",
82790
+ "$taskTrigger = @($task.Triggers)[0]",
82791
+ "$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'",
82792
+ "Write-Output ('TaskName: ' + $task.TaskName); Write-Output ('State: ' + $task.State); Write-Output ('Definition: ' + $task.Description)",
82793
+ "}",
82794
+ "Write-Output ('DefinitionMatch: ' + $definitionMatches)",
82795
+ "Write-Output ('CurrentOwned: ' + $currentOwned)",
82796
+ "$legacyPresent = $null -ne $legacyTask",
82797
+ "Write-Output ('LegacyTask: ' + $legacyPresent)",
82798
+ "Write-Output ('LegacyOwned: ' + $legacyOwned)",
82799
+ "Write-Output ('LegacyState: ' + $legacyState)"
82800
+ ].join("; ");
82801
+ const registerCommand = powershellCommand(registerScript);
82654
82802
  return {
82655
82803
  platform: "win32",
82656
82804
  manifestPath: null,
82657
82805
  manifest: null,
82658
- logDirectory: null,
82659
- loadDefinitionCommands: [
82660
- {
82661
- command: "powershell.exe",
82662
- args: ["-NoProfile", "-NonInteractive", "-Command", registerScript],
82663
- tolerateFailure: false
82664
- }
82665
- ],
82666
- reloadDefinitionCommands: [
82667
- {
82668
- command: "powershell.exe",
82669
- args: ["-NoProfile", "-NonInteractive", "-Command", registerScript],
82670
- tolerateFailure: false
82671
- }
82672
- ],
82806
+ logDirectory: logDir,
82807
+ loadDefinitionCommands: [registerCommand],
82808
+ reloadDefinitionCommands: [registerCommand],
82673
82809
  enableCommands: [],
82674
- startCommands: [
82675
- {
82676
- command: "powershell.exe",
82677
- args: [
82678
- "-NoProfile",
82679
- "-NonInteractive",
82680
- "-Command",
82681
- `Start-ScheduledTask -TaskName ${powershellLiteral(names.windows)}`
82682
- ],
82683
- tolerateFailure: false
82684
- }
82685
- ],
82810
+ startCommands: [powershellCommand(`${taskContext}; Start-ScheduledTask -TaskName $taskName`)],
82686
82811
  stopCommands: [
82687
- {
82688
- command: "powershell.exe",
82689
- args: [
82690
- "-NoProfile",
82691
- "-NonInteractive",
82692
- "-Command",
82693
- `Stop-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -ErrorAction SilentlyContinue`
82694
- ],
82695
- tolerateFailure: true
82696
- }
82697
- ],
82698
- replaceCommands: [
82699
- {
82700
- command: "powershell.exe",
82701
- args: [
82702
- "-NoProfile",
82703
- "-NonInteractive",
82704
- "-Command",
82705
- `${registerScript}; Start-ScheduledTask -TaskName ${powershellLiteral(names.windows)}`
82706
- ],
82707
- tolerateFailure: false
82708
- }
82709
- ],
82710
- uninstallCommands: [
82711
- {
82712
- command: "powershell.exe",
82713
- args: [
82714
- "-NoProfile",
82715
- "-NonInteractive",
82716
- "-Command",
82717
- `Unregister-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -Confirm:$false -ErrorAction SilentlyContinue`
82718
- ],
82719
- tolerateFailure: true
82720
- }
82812
+ powershellCommand(
82813
+ `${taskContext}; Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`,
82814
+ true
82815
+ )
82721
82816
  ],
82722
- statusCommand: {
82723
- command: "powershell.exe",
82724
- args: [
82725
- "-NoProfile",
82726
- "-NonInteractive",
82727
- "-Command",
82728
- `Get-ScheduledTask -TaskName ${powershellLiteral(names.windows)} | Format-List TaskName,State`
82729
- ],
82730
- tolerateFailure: true
82731
- },
82732
- isActiveCommand: {
82733
- command: "powershell.exe",
82734
- args: [
82735
- "-NoProfile",
82736
- "-NonInteractive",
82737
- "-Command",
82738
- `(Get-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -ErrorAction SilentlyContinue).State`
82739
- ],
82740
- tolerateFailure: true
82741
- },
82742
- lingerStatusCommand: null
82817
+ uninstallCommands: [powershellCommand(uninstallScript)],
82818
+ statusCommand: powershellCommand(statusScript, true),
82819
+ isActiveCommand: powershellCommand(statusScript, true),
82820
+ definitionMarker
82743
82821
  };
82744
82822
  }
82745
82823
  function resolveCurrentPlatform() {
@@ -82749,16 +82827,16 @@ function resolveCurrentPlatform() {
82749
82827
  }
82750
82828
  return currentPlatform;
82751
82829
  }
82752
- function currentServicePlan(args, runtime) {
82753
- const currentPlatform = resolveCurrentPlatform();
82830
+ function currentServicePlan(args, runtime, options = {}) {
82831
+ const currentPlatform = options.platform ?? resolveCurrentPlatform();
82754
82832
  const profile = resolveEndpointProfileFromArgs(args);
82755
82833
  const cliEntry = runtime?.cliEntry ?? process.argv[1];
82756
82834
  if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
82757
82835
  return buildDaemonServicePlan({
82758
82836
  platform: currentPlatform,
82759
- homeDir: (0, import_node_os16.homedir)(),
82837
+ homeDir: options.homeDir ?? (0, import_node_os16.homedir)(),
82760
82838
  nodeExecutable: runtime?.executable ?? process.execPath,
82761
- cliEntry: (0, import_node_path27.resolve)(cliEntry),
82839
+ cliEntry: currentPlatform === "win32" ? import_node_path27.win32.resolve(cliEntry) : (0, import_node_path27.resolve)(cliEntry),
82762
82840
  profile,
82763
82841
  environment: runtime?.environment
82764
82842
  });
@@ -82778,10 +82856,14 @@ function defaultReadManifest(path2) {
82778
82856
  if (!(0, import_node_fs25.existsSync)(path2)) return null;
82779
82857
  return (0, import_node_fs25.readFileSync)(path2, "utf8");
82780
82858
  }
82781
- function runServiceCommand(command) {
82782
- const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, { encoding: "utf8", shell: false });
82859
+ function runServiceCommand(command, timeoutMs = 15e3) {
82860
+ const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, {
82861
+ encoding: "utf8",
82862
+ shell: false,
82863
+ ...(0, import_node_os16.platform)() === "win32" ? { timeout: timeoutMs, windowsHide: true } : {}
82864
+ });
82783
82865
  const status = result.status ?? 1;
82784
- const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
82866
+ const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}${result.error?.message ?? ""}`.trim();
82785
82867
  if (status !== 0 && !command.tolerateFailure) {
82786
82868
  throw new Error(
82787
82869
  `Service command failed (${command.command} ${command.args.join(" ")}): ${output2 || `exit ${status}`}`
@@ -82794,6 +82876,23 @@ function defaultSleep(ms) {
82794
82876
  setTimeout(resolveSleep, ms);
82795
82877
  });
82796
82878
  }
82879
+ function isProcessAlive(pid) {
82880
+ if (pid <= 0) return false;
82881
+ try {
82882
+ process.kill(pid, 0);
82883
+ return true;
82884
+ } catch (error61) {
82885
+ return error61.code === "EPERM";
82886
+ }
82887
+ }
82888
+ async function requestWindowsDaemonShutdown(owner) {
82889
+ try {
82890
+ const response = await requestLocalDaemonShutdown(owner.localApiPort, owner.localApiToken);
82891
+ return response.ok ? "accepted" : "rejected";
82892
+ } catch {
82893
+ return "unreachable";
82894
+ }
82895
+ }
82797
82896
  function parseServiceHostState(servicePlatform, result) {
82798
82897
  const output2 = result.output;
82799
82898
  if (servicePlatform === "linux") {
@@ -82819,9 +82918,69 @@ function parseServiceHostState(servicePlatform, result) {
82819
82918
  const stateMatch = output2.match(/\b(?:State|state)\s*[:=]\s*(\w+)/);
82820
82919
  const state = (stateMatch?.[1] ?? output2.trim()).toLowerCase();
82821
82920
  if (state === "running") return "running";
82822
- if (state === "ready" || state === "disabled" || state === "queued") return "stopped";
82921
+ if (state === "queued") return "starting";
82922
+ if (state === "ready" || state === "disabled") return "stopped";
82823
82923
  return "unknown";
82824
82924
  }
82925
+ function parseWindowsTaskInspection(result) {
82926
+ if (result.status !== 0) {
82927
+ throw new Error("Cannot inspect Windows daemon task definition");
82928
+ }
82929
+ const value2 = (name) => {
82930
+ const match = result.output.match(new RegExp(`^${name}:\\s*(.*)$`, "imu"));
82931
+ if (!match) throw new Error(`Incomplete Windows daemon task inspection: missing ${name}`);
82932
+ return match[1]?.trim() ?? "";
82933
+ };
82934
+ const boolean4 = (name) => {
82935
+ const parsed = value2(name).toLowerCase();
82936
+ if (parsed === "true") return true;
82937
+ if (parsed === "false") return false;
82938
+ throw new Error(`Incomplete Windows daemon task inspection: invalid ${name}`);
82939
+ };
82940
+ const currentPresent = /^TaskName:\s*.+$/imu.test(result.output);
82941
+ const currentStateValue = value2("State");
82942
+ const currentOwned = boolean4("CurrentOwned");
82943
+ const definitionMatches = boolean4("DefinitionMatch");
82944
+ const legacyPresent = boolean4("LegacyTask");
82945
+ const legacyOwned = boolean4("LegacyOwned");
82946
+ const legacyStateValue = value2("LegacyState");
82947
+ if (currentPresent !== (currentStateValue.toLowerCase() !== "missing")) {
82948
+ throw new Error("Incomplete Windows daemon task inspection: inconsistent current task state");
82949
+ }
82950
+ if (!currentPresent && (currentOwned || definitionMatches)) {
82951
+ throw new Error(
82952
+ "Incomplete Windows daemon task inspection: absent current task is marked owned"
82953
+ );
82954
+ }
82955
+ if (legacyPresent !== (legacyStateValue.toLowerCase() !== "missing") || !legacyPresent && legacyOwned) {
82956
+ throw new Error("Incomplete Windows daemon task inspection: inconsistent legacy task state");
82957
+ }
82958
+ return {
82959
+ currentPresent,
82960
+ currentOwned,
82961
+ currentState: currentPresent ? parseServiceHostState("win32", { status: 0, output: currentStateValue }) : "stopped",
82962
+ definitionMatches,
82963
+ definitionMarker: result.output.match(/^Definition:\s*(.+)$/imu)?.[1]?.trim(),
82964
+ legacyPresent,
82965
+ legacyOwned,
82966
+ legacyState: legacyPresent ? parseServiceHostState("win32", { status: 0, output: legacyStateValue }) : "stopped"
82967
+ };
82968
+ }
82969
+ function windowsTaskHostState(tasks) {
82970
+ const states = [tasks.currentState, tasks.legacyState];
82971
+ if (states.includes("unknown")) return "unknown";
82972
+ if (states.includes("running")) return "running";
82973
+ if (states.includes("starting")) return "starting";
82974
+ return "stopped";
82975
+ }
82976
+ function assertWindowsTaskOwnership(tasks) {
82977
+ if (tasks.currentPresent && !tasks.currentOwned) {
82978
+ throw new Error("Windows daemon task is not owned by this Alan installation");
82979
+ }
82980
+ if (tasks.legacyPresent && !tasks.legacyOwned) {
82981
+ throw new Error("Legacy Windows daemon task is not owned by this Alan installation");
82982
+ }
82983
+ }
82825
82984
  function resolveEnsureDefinitionOutcome(input2) {
82826
82985
  if (!input2.definitionChanged) {
82827
82986
  return { changed: false, updatePending: false };
@@ -82831,9 +82990,6 @@ function resolveEnsureDefinitionOutcome(input2) {
82831
82990
  }
82832
82991
  return { changed: true, updatePending: false };
82833
82992
  }
82834
- function shouldStartOnInstall(hostState) {
82835
- return hostState === "stopped";
82836
- }
82837
82993
  function resolveUsername(options) {
82838
82994
  if (options?.username) return options.username;
82839
82995
  try {
@@ -82842,17 +82998,14 @@ function resolveUsername(options) {
82842
82998
  return process.env.USER || process.env.USERNAME || "unknown";
82843
82999
  }
82844
83000
  }
82845
- function buildLingerStatusCommand(username) {
82846
- return {
82847
- command: "loginctl",
82848
- args: ["show-user", username, "-p", "Linger"],
82849
- tolerateFailure: true
82850
- };
82851
- }
82852
83001
  function assertSystemdUserLingerEnabled(options = {}) {
82853
83002
  const username = resolveUsername(options);
82854
83003
  const run = options.runCommand ?? runServiceCommand;
82855
- const result = run(buildLingerStatusCommand(username));
83004
+ const result = run({
83005
+ command: "loginctl",
83006
+ args: ["show-user", username, "-p", "Linger"],
83007
+ tolerateFailure: true
83008
+ });
82856
83009
  if (result.status !== 0) {
82857
83010
  throw new Error(
82858
83011
  `Cannot verify systemd user linger for ${username} (loginctl failed). For durable headless VM boot run: sudo loginctl enable-linger ${username}`
@@ -82866,10 +83019,18 @@ function assertSystemdUserLingerEnabled(options = {}) {
82866
83019
  }
82867
83020
  function createHostContext(options = {}) {
82868
83021
  const args = options.args ?? [];
82869
- const plan = currentServicePlan(args, options.runtime);
82870
- const run = options.runCommand ?? runServiceCommand;
82871
- const sleep4 = options.sleep ?? defaultSleep;
83022
+ const plan = currentServicePlan(args, options.runtime, options);
83023
+ const sleep5 = options.sleep ?? defaultSleep;
82872
83024
  const nowMs = options.nowMs ?? Date.now;
83025
+ const run = (command) => {
83026
+ const remainingMs = options.deadlineMs === void 0 ? 15e3 : options.deadlineMs - nowMs();
83027
+ if (remainingMs <= 0) throw new Error("Windows service setup deadline expired");
83028
+ const result = options.runCommand ? options.runCommand(command) : runServiceCommand(command, Math.min(15e3, remainingMs));
83029
+ if (options.deadlineMs !== void 0 && nowMs() >= options.deadlineMs) {
83030
+ throw new Error("Windows service setup deadline expired");
83031
+ }
83032
+ return result;
83033
+ };
82873
83034
  const readManifest = options.readManifest ?? defaultReadManifest;
82874
83035
  const writeManifest = options.writeManifest ?? writeManifestAtomic;
82875
83036
  const removeManifest = options.removeManifest ?? ((path2) => (0, import_node_fs25.rmSync)(path2, { force: true }));
@@ -82880,19 +83041,37 @@ function createHostContext(options = {}) {
82880
83041
  args,
82881
83042
  plan,
82882
83043
  run,
82883
- sleep: sleep4,
83044
+ sleep: sleep5,
82884
83045
  nowMs,
82885
83046
  readManifest,
82886
83047
  writeManifest,
82887
83048
  removeManifest,
82888
83049
  ensureLogDir,
82889
- profile: resolveEndpointProfileFromArgs(args)
83050
+ configPath: options.configPath ?? resolveAgentConfigPath({ profile: resolveEndpointProfileFromArgs(args) }),
83051
+ readOwner: options.readOwner ?? readLocalDaemonOwnerLease,
83052
+ isProcessAlive: options.isProcessAlive ?? isProcessAlive,
83053
+ probeEndpoint: options.probeEndpoint ?? probeLocalDaemonEndpoint,
83054
+ requestWindowsShutdown: options.requestWindowsShutdown ?? requestWindowsDaemonShutdown,
83055
+ inspectJournal: options.inspectJournal ?? inspectLocalDaemonRunJournal,
83056
+ acquireMaintenance: options.acquireMaintenance ?? acquireLocalDaemonMaintenanceLease,
83057
+ releaseMaintenance: options.releaseMaintenance ?? releaseLocalDaemonMaintenanceLease
82890
83058
  };
82891
83059
  }
82892
83060
  function inspectDaemonService(options = {}) {
82893
83061
  const { plan, run, nowMs } = createHostContext(options);
82894
83062
  const result = run(plan.isActiveCommand);
82895
- const state = parseServiceHostState(plan.platform, result);
83063
+ let state;
83064
+ if (plan.platform === "win32") {
83065
+ try {
83066
+ const tasks = parseWindowsTaskInspection(result);
83067
+ assertWindowsTaskOwnership(tasks);
83068
+ state = windowsTaskHostState(tasks);
83069
+ } catch {
83070
+ state = "unknown";
83071
+ }
83072
+ } else {
83073
+ state = parseServiceHostState(plan.platform, result);
83074
+ }
82896
83075
  return {
82897
83076
  state,
82898
83077
  platform: plan.platform,
@@ -82902,8 +83081,9 @@ function inspectDaemonService(options = {}) {
82902
83081
  }
82903
83082
  function ensureDaemonServiceDefinition(options = {}) {
82904
83083
  const ctx = createHostContext(options);
82905
- const { plan, run, readManifest, writeManifest, ensureLogDir } = ctx;
83084
+ const { plan, run, nowMs, readManifest, writeManifest, ensureLogDir } = ctx;
82906
83085
  let definitionChanged = false;
83086
+ let inspection;
82907
83087
  if (plan.manifestPath && plan.manifest) {
82908
83088
  ensureLogDir();
82909
83089
  const existing = readManifest(plan.manifestPath);
@@ -82912,20 +83092,37 @@ function ensureDaemonServiceDefinition(options = {}) {
82912
83092
  writeManifest(plan.manifestPath, plan.manifest);
82913
83093
  }
82914
83094
  } else if (plan.platform === "win32") {
82915
- definitionChanged = true;
83095
+ ensureLogDir();
83096
+ const result = run(plan.statusCommand);
83097
+ const tasks = parseWindowsTaskInspection(result);
83098
+ assertWindowsTaskOwnership(tasks);
83099
+ if (tasks.currentPresent && tasks.currentState === "unknown" || tasks.legacyPresent && tasks.legacyState === "unknown") {
83100
+ throw new Error("Cannot safely update a Windows daemon task with unknown state");
83101
+ }
83102
+ definitionChanged = tasks.definitionMarker !== plan.definitionMarker || !tasks.definitionMatches || tasks.legacyPresent;
83103
+ inspection = {
83104
+ state: windowsTaskHostState(tasks),
83105
+ platform: plan.platform,
83106
+ detail: result.output || void 0,
83107
+ observedAtMs: nowMs()
83108
+ };
82916
83109
  }
82917
- const inspection = inspectDaemonService(options);
83110
+ inspection ??= inspectDaemonService(options);
83111
+ const definitionHostState = plan.platform === "win32" && inspection.state === "starting" ? "stopped" : inspection.state;
82918
83112
  const outcome = resolveEnsureDefinitionOutcome({
82919
83113
  definitionChanged,
82920
- hostState: inspection.state
83114
+ hostState: definitionHostState
82921
83115
  });
82922
83116
  if (outcome.updatePending) {
82923
83117
  return outcome;
82924
83118
  }
82925
- if (inspection.state === "running" || inspection.state === "starting") {
83119
+ if (plan.platform === "win32" && !outcome.changed) {
83120
+ return outcome;
83121
+ }
83122
+ if (definitionHostState === "running" || definitionHostState === "starting") {
82926
83123
  return outcome;
82927
83124
  }
82928
- const definitionCommands = inspection.state === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
83125
+ const definitionCommands = definitionHostState === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
82929
83126
  for (const command of definitionCommands) run(command);
82930
83127
  return outcome;
82931
83128
  }
@@ -82937,10 +83134,140 @@ function startDaemonService(options = {}) {
82937
83134
  const { plan, run } = createHostContext(options);
82938
83135
  for (const command of plan.startCommands) run(command);
82939
83136
  }
82940
- function uninstallDaemonService(args = [], options = {}) {
83137
+ async function stopDaemonServiceAndWait(identity, timeoutMs, options = {}) {
83138
+ const {
83139
+ plan,
83140
+ run,
83141
+ sleep: sleep5,
83142
+ nowMs,
83143
+ configPath,
83144
+ readOwner,
83145
+ isProcessAlive: processIsAlive,
83146
+ probeEndpoint,
83147
+ requestWindowsShutdown: requestShutdown
83148
+ } = createHostContext(options);
83149
+ let owner;
83150
+ try {
83151
+ owner = readOwner(configPath);
83152
+ } catch {
83153
+ return "identity_changed";
83154
+ }
83155
+ 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);
83156
+ if (!owner || !ownerMatches(owner)) {
83157
+ return "identity_changed";
83158
+ }
83159
+ if (plan.platform === "win32") {
83160
+ const shutdown = await requestShutdown(owner);
83161
+ if (shutdown === "rejected") return "identity_changed";
83162
+ if (shutdown === "unreachable") {
83163
+ for (const command of plan.stopCommands) run(command);
83164
+ }
83165
+ const deadline2 = nowMs() + timeoutMs;
83166
+ while (nowMs() < deadline2) {
83167
+ let currentOwner;
83168
+ try {
83169
+ currentOwner = readOwner(configPath);
83170
+ } catch {
83171
+ return "identity_changed";
83172
+ }
83173
+ if (currentOwner && !ownerMatches(currentOwner)) {
83174
+ return "identity_changed";
83175
+ }
83176
+ if (!processIsAlive(identity.pid)) {
83177
+ const endpoint = await probeEndpoint(identity.port, owner.localApiToken, 250);
83178
+ if (endpoint.state === "absent") return "exited";
83179
+ if (endpoint.state === "listening_unauthorized" || endpoint.status.daemonSessionId !== identity.daemonSessionId) {
83180
+ return "identity_changed";
83181
+ }
83182
+ }
83183
+ await sleep5(STOP_POLL_INTERVAL_MS);
83184
+ }
83185
+ return "timeout";
83186
+ }
83187
+ for (const command of plan.stopCommands) run(command);
83188
+ const deadline = nowMs() + timeoutMs;
83189
+ while (nowMs() < deadline) {
83190
+ const result = run(plan.isActiveCommand);
83191
+ const state = parseServiceHostState(plan.platform, result);
83192
+ if (state === "stopped") return "exited";
83193
+ if (state === "unknown") {
83194
+ } else if (identity.pid > 0) {
83195
+ try {
83196
+ process.kill(identity.pid, 0);
83197
+ } catch {
83198
+ return "exited";
83199
+ }
83200
+ }
83201
+ await sleep5(STOP_POLL_INTERVAL_MS);
83202
+ }
83203
+ return "timeout";
83204
+ }
83205
+ async function uninstallDaemonService(args = [], options = {}) {
82941
83206
  const ctx = createHostContext({ ...options, args: options.args ?? args });
82942
- for (const command of ctx.plan.uninstallCommands) ctx.run(command);
82943
- if (ctx.plan.manifestPath) ctx.removeManifest(ctx.plan.manifestPath);
83207
+ if (ctx.plan.platform === "win32") {
83208
+ const ownership = ctx.run(ctx.plan.statusCommand);
83209
+ let tasks;
83210
+ try {
83211
+ tasks = parseWindowsTaskInspection(ownership);
83212
+ } catch (error61) {
83213
+ throw new Error(
83214
+ `Cannot inspect Windows daemon task ownership before uninstall: ${error61 instanceof Error ? error61.message : String(error61)}`
83215
+ );
83216
+ }
83217
+ assertWindowsTaskOwnership(tasks);
83218
+ if (!tasks.currentPresent && !tasks.legacyPresent) {
83219
+ console.info("[alan-agent] Per-user daemon service removed");
83220
+ return;
83221
+ }
83222
+ const acquired = ctx.acquireMaintenance(ctx.configPath, "watchdog", 3e4);
83223
+ if (!acquired.acquired) {
83224
+ throw new Error("Cannot uninstall the Windows daemon service while maintenance is active");
83225
+ }
83226
+ try {
83227
+ const journal = ctx.inspectJournal(ctx.configPath);
83228
+ if (!journal.safeToRestart) {
83229
+ throw new Error(
83230
+ `Windows daemon service uninstall is deferred while ${journal.activeRunCount} active run${journal.activeRunCount === 1 ? "" : "s"} finish`
83231
+ );
83232
+ }
83233
+ let owner;
83234
+ try {
83235
+ owner = ctx.readOwner(ctx.configPath);
83236
+ } catch (error61) {
83237
+ throw new Error(
83238
+ `Cannot verify Windows daemon ownership before uninstall: ${error61 instanceof Error ? error61.message : String(error61)}`
83239
+ );
83240
+ }
83241
+ if (owner) {
83242
+ const stopped = await stopDaemonServiceAndWait(
83243
+ {
83244
+ pid: owner.pid,
83245
+ serviceId: owner.localServiceId,
83246
+ daemonSessionId: owner.daemonSessionId,
83247
+ port: owner.localApiPort,
83248
+ startedAtMs: owner.startedAtMs
83249
+ },
83250
+ 1e4,
83251
+ { ...options, args: ctx.args }
83252
+ );
83253
+ if (stopped !== "exited") {
83254
+ throw new Error(
83255
+ stopped === "identity_changed" ? "Windows daemon ownership changed before uninstall" : "Timed out waiting for the Windows daemon to stop before uninstall"
83256
+ );
83257
+ }
83258
+ } else {
83259
+ if (tasks.currentState !== "stopped" || tasks.legacyState !== "stopped") {
83260
+ throw new Error("Cannot uninstall a running Windows daemon without verified ownership");
83261
+ }
83262
+ }
83263
+ for (const command of ctx.plan.uninstallCommands) ctx.run(command);
83264
+ } finally {
83265
+ ctx.releaseMaintenance(ctx.configPath, acquired.lease.id);
83266
+ }
83267
+ } else {
83268
+ for (const command of ctx.plan.uninstallCommands) ctx.run(command);
83269
+ if (ctx.plan.manifestPath) ctx.removeManifest(ctx.plan.manifestPath);
83270
+ }
82944
83271
  console.info("[alan-agent] Per-user daemon service removed");
82945
83272
  }
82946
83273
  function installDaemonService(args = [], runtime, options = {}) {
@@ -82968,7 +83295,7 @@ function installDaemonService(args = [], runtime, options = {}) {
82968
83295
  });
82969
83296
  return;
82970
83297
  }
82971
- if (!shouldStartOnInstall(inspection.state)) {
83298
+ if (inspection.state !== "stopped") {
82972
83299
  console.info("[alan-agent] Per-user daemon service already present", {
82973
83300
  profile,
82974
83301
  state: inspection.state
@@ -82992,13 +83319,81 @@ ${result.output}
82992
83319
  }
82993
83320
 
82994
83321
  // src/install-command.ts
82995
- async function runInstallCommand(commandArgs) {
83322
+ var WINDOWS_SETUP_READY_TIMEOUT_MS = 9e3;
83323
+ async function setupWindowsRuntime(commandArgs) {
83324
+ const deadlineMs = Date.now() + WINDOWS_SETUP_READY_TIMEOUT_MS;
83325
+ const controller = new AbortController();
83326
+ const timeoutError = new Error(
83327
+ "Windows runtime did not become ready within 9 seconds. Credential setup may still complete in the running daemon; it has not been rolled back. Check runtime status and retry the same setup command to reconcile."
83328
+ );
83329
+ let timer;
83330
+ const timeout = new Promise((_resolve, reject) => {
83331
+ timer = setTimeout(() => {
83332
+ controller.abort(timeoutError);
83333
+ reject(timeoutError);
83334
+ }, WINDOWS_SETUP_READY_TIMEOUT_MS);
83335
+ });
83336
+ const remainingMs = () => {
83337
+ const remaining = deadlineMs - Date.now();
83338
+ if (remaining <= 0) throw timeoutError;
83339
+ controller.signal.throwIfAborted();
83340
+ return remaining;
83341
+ };
83342
+ try {
83343
+ const runtimeIdentityChanged = await Promise.race([
83344
+ setupDaemon(commandArgs, { signal: controller.signal }),
83345
+ timeout
83346
+ ]);
83347
+ remainingMs();
83348
+ const { runtimeId, localApiPort, localApiToken } = readConfig();
83349
+ if (!runtimeId || !localApiPort || !localApiToken) {
83350
+ throw new Error("Windows runtime setup completed without a local readiness endpoint");
83351
+ }
83352
+ const probe = () => probeLocalDaemon(localApiPort, localApiToken, Math.min(250, remainingMs()));
83353
+ const poll = () => (0, import_promises6.setTimeout)(Math.min(100, remainingMs()), void 0, { signal: controller.signal });
83354
+ if (runtimeIdentityChanged) {
83355
+ while (true) {
83356
+ const status = await probe();
83357
+ if (status?.runtimeId === runtimeId && status.connected) break;
83358
+ if (!status) {
83359
+ const service = inspectDaemonService({ args: commandArgs, deadlineMs });
83360
+ if (service.state !== "running" && service.state !== "starting") break;
83361
+ }
83362
+ await poll();
83363
+ }
83364
+ }
83365
+ remainingMs();
83366
+ installDaemonService(commandArgs, void 0, { deadlineMs });
83367
+ while (true) {
83368
+ const status = await probe();
83369
+ remainingMs();
83370
+ if (status?.runtimeId === runtimeId && status.connected) return;
83371
+ await poll();
83372
+ }
83373
+ } catch (error61) {
83374
+ if (controller.signal.aborted || Date.now() >= deadlineMs) {
83375
+ controller.abort(timeoutError);
83376
+ throw timeoutError;
83377
+ }
83378
+ throw error61;
83379
+ } finally {
83380
+ clearTimeout(timer);
83381
+ }
83382
+ }
83383
+ async function runInstallCommand(commandArgs, currentPlatform = process.platform) {
83384
+ if (currentPlatform === "win32" && commandArgs.includes("--headless")) {
83385
+ throw new Error("--headless is only supported on Linux systemd user services");
83386
+ }
82996
83387
  bindConfigPath(commandArgs);
82997
83388
  const configured = readConfig();
82998
83389
  const configurationMode = resolveInstallConfigurationMode(commandArgs, configured);
82999
- if (configurationMode === "setup") await setupDaemon(commandArgs);
83000
- if (configurationMode === "login") await loginWithDeviceCode(commandArgs);
83001
- installDaemonService(commandArgs);
83390
+ if (currentPlatform === "win32" && configurationMode === "setup") {
83391
+ await setupWindowsRuntime(commandArgs);
83392
+ } else {
83393
+ if (configurationMode === "setup") await setupDaemon(commandArgs);
83394
+ if (configurationMode === "login") await loginWithDeviceCode(commandArgs);
83395
+ installDaemonService(commandArgs);
83396
+ }
83002
83397
  runMcpIntegrationCommand(["reconcile", ...commandArgs]);
83003
83398
  }
83004
83399
 
@@ -86376,7 +86771,7 @@ var REQUEST_STATE_ONLY_LEG_PACING_MS2 = 250;
86376
86771
  function inputRequiredRoundsExceededMessage2(method, maxRounds) {
86377
86772
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
86378
86773
  }
86379
- function sleep3(ms, signal) {
86774
+ function sleep4(ms, signal) {
86380
86775
  return new Promise((resolve15, reject) => {
86381
86776
  if (signal?.aborted) {
86382
86777
  reject(signal.reason instanceof SdkError2 ? signal.reason : new SdkError2(SdkErrorCode2.RequestTimeout, String(signal.reason)));
@@ -94643,7 +95038,7 @@ var LegacyInputRequiredShim = class {
94643
95038
  } finally {
94644
95039
  roundAbort.dispose();
94645
95040
  }
94646
- } else await sleep3(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
95041
+ } else await sleep4(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
94647
95042
  let ctxNext = {
94648
95043
  ...ctx,
94649
95044
  mcpReq: {
@@ -95440,7 +95835,7 @@ async function runMcpStdioProxy(env = process.env, local = new StdioServerTransp
95440
95835
  }
95441
95836
 
95442
95837
  // src/native-hook-command.ts
95443
- var import_node_crypto21 = require("crypto");
95838
+ var import_node_crypto22 = require("crypto");
95444
95839
  var import_node_fs27 = require("fs");
95445
95840
  var import_node_os17 = require("os");
95446
95841
  var import_node_path29 = require("path");
@@ -95809,7 +96204,7 @@ function parseNativeHookEnvelope(input2) {
95809
96204
  const projectPin = eventType === "session_start" && resolvedCwd ? readAlanProjectPin(resolvedCwd) : void 0;
95810
96205
  return {
95811
96206
  schemaVersion: 1,
95812
- eventId: (0, import_node_crypto21.randomUUID)(),
96207
+ eventId: (0, import_node_crypto22.randomUUID)(),
95813
96208
  provider: capability.provider,
95814
96209
  eventType,
95815
96210
  observedAt: (input2.now ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -95976,7 +96371,7 @@ function decodeResumeFallbackContext(encoded) {
95976
96371
  }
95977
96372
 
95978
96373
  // src/sandbox.ts
95979
- var import_node_os18 = require("os");
96374
+ var import_node_os19 = require("os");
95980
96375
 
95981
96376
  // src/execution-activity-tracker.ts
95982
96377
  var ExecutionActivityTracker = class {
@@ -96305,7 +96700,7 @@ function delay2(ms) {
96305
96700
  }
96306
96701
 
96307
96702
  // src/web-presenter.ts
96308
- var import_node_crypto22 = require("crypto");
96703
+ var import_node_crypto23 = require("crypto");
96309
96704
  var WebPresenter = class {
96310
96705
  constructor(wsClient, conversationId, cwd) {
96311
96706
  this.conversationId = conversationId;
@@ -96491,7 +96886,7 @@ var WebPresenter = class {
96491
96886
  this.applicationProblem = createAlanProblem({
96492
96887
  domain: "application",
96493
96888
  code: input2.code,
96494
- id: (0, import_node_crypto22.randomUUID)(),
96889
+ id: (0, import_node_crypto23.randomUUID)(),
96495
96890
  title: APPLICATION_PROBLEM_TITLES[input2.code],
96496
96891
  detail: input2.message,
96497
96892
  // The schema's slot for the technical cause, so the problem carries it
@@ -96561,7 +96956,7 @@ var WebPresenter = class {
96561
96956
  };
96562
96957
 
96563
96958
  // src/ws-client.ts
96564
- var import_node_crypto24 = require("crypto");
96959
+ var import_node_crypto25 = require("crypto");
96565
96960
 
96566
96961
  // ../shared/dist/agent-liveness.js
96567
96962
  var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
@@ -96572,6 +96967,34 @@ var agentRunStateSchema = external_exports.object({
96572
96967
  /** Run ids currently executing (cloud sandbox agent runs one at a time, but keep it a list). */
96573
96968
  activeRunIds: external_exports.array(external_exports.string()).default([])
96574
96969
  });
96970
+ var AGENT_RESOURCE_SAMPLE_INTERVAL_MS = 6e4;
96971
+ var resourceCounter = external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
96972
+ var resourceMeasurement = external_exports.number().nonnegative().max(Number.MAX_SAFE_INTEGER);
96973
+ var pressurePercentage = external_exports.number().min(0).max(100);
96974
+ var agentResourceDiagnosticsSchema = external_exports.object({
96975
+ sampledAtMs: resourceCounter,
96976
+ memoryTotalBytes: resourceCounter,
96977
+ memoryFreeBytes: resourceCounter,
96978
+ /** Linux MemAvailable includes reclaimable memory; free memory alone does not. */
96979
+ memoryAvailableBytes: resourceCounter.optional(),
96980
+ swapTotalBytes: resourceCounter.optional(),
96981
+ swapFreeBytes: resourceCounter.optional(),
96982
+ processRssBytes: resourceCounter,
96983
+ cpuCount: external_exports.number().int().positive().max(65536),
96984
+ /** Omitted on Windows, where os.loadavg() always returns zero. */
96985
+ loadAverage1m: resourceMeasurement.optional(),
96986
+ eventLoopDelayMaxMs: resourceMeasurement.optional(),
96987
+ eventLoopDelayP99Ms: resourceMeasurement.optional(),
96988
+ memoryPressureSomeAvg10: pressurePercentage.optional(),
96989
+ memoryPressureFullAvg10: pressurePercentage.optional(),
96990
+ cpuPressureSomeAvg10: pressurePercentage.optional(),
96991
+ ioPressureSomeAvg10: pressurePercentage.optional(),
96992
+ /** Counters for the agent's current cgroup v2, including its child workload. */
96993
+ cgroupMemoryCurrentBytes: resourceCounter.optional(),
96994
+ cgroupMemoryLimitBytes: resourceCounter.optional(),
96995
+ cgroupMemoryOomEvents: resourceCounter.optional(),
96996
+ cgroupMemoryOomKillEvents: resourceCounter.optional()
96997
+ });
96575
96998
  var agentHeartbeatSchema = external_exports.object({
96576
96999
  /** Stable id for this agent OS process; changes only on a fresh agent start. */
96577
97000
  agentSessionId: external_exports.string().min(1),
@@ -96580,7 +97003,9 @@ var agentHeartbeatSchema = external_exports.object({
96580
97003
  /** Capability marker — presence is what distinguishes a new agent from a legacy one. */
96581
97004
  protocolVersion: external_exports.number().int().positive(),
96582
97005
  runState: agentRunStateSchema,
96583
- agentVersion: external_exports.string().optional()
97006
+ agentVersion: external_exports.string().optional(),
97007
+ // Bad optional diagnostics must never discard otherwise valid liveness evidence.
97008
+ resources: agentResourceDiagnosticsSchema.optional().catch(void 0)
96584
97009
  });
96585
97010
  var agentProbeAckSchema = external_exports.object({
96586
97011
  agentSessionId: external_exports.string().min(1),
@@ -96588,14 +97013,152 @@ var agentProbeAckSchema = external_exports.object({
96588
97013
  protocolVersion: external_exports.number().int().positive().optional()
96589
97014
  });
96590
97015
 
96591
- // src/sandbox-outbox.ts
96592
- var import_node_crypto23 = require("crypto");
97016
+ // src/resource-diagnostics.ts
96593
97017
  var import_node_fs28 = require("fs");
97018
+ var import_node_os18 = require("os");
97019
+ var import_node_path30 = require("path");
97020
+ var import_node_perf_hooks = require("perf_hooks");
97021
+ var MAX_RESOURCE_FILE_BYTES = 16384;
97022
+ var RESOURCE_READ_TIMEOUT_MS = 500;
97023
+ async function readResourceFile(path2) {
97024
+ try {
97025
+ const stream = (0, import_node_fs28.createReadStream)(path2, {
97026
+ encoding: "utf8",
97027
+ start: 0,
97028
+ end: MAX_RESOURCE_FILE_BYTES,
97029
+ signal: AbortSignal.timeout(RESOURCE_READ_TIMEOUT_MS)
97030
+ });
97031
+ let text = "";
97032
+ for await (const chunk of stream) {
97033
+ text += chunk;
97034
+ if (text.length > MAX_RESOURCE_FILE_BYTES) return void 0;
97035
+ }
97036
+ return text;
97037
+ } catch {
97038
+ return void 0;
97039
+ }
97040
+ }
97041
+ function counter(value2) {
97042
+ if (!value2 || !/^\d+$/.test(value2)) return void 0;
97043
+ const parsed = Number(value2);
97044
+ return Number.isSafeInteger(parsed) ? parsed : void 0;
97045
+ }
97046
+ function pressure(text, kind) {
97047
+ const value2 = text?.match(new RegExp(`^${kind} avg10=(\\d+(?:\\.\\d+)?) `, "m"))?.[1];
97048
+ if (value2 === void 0) return void 0;
97049
+ const parsed = Number(value2);
97050
+ return Number.isFinite(parsed) && parsed <= 100 ? parsed : void 0;
97051
+ }
97052
+ function meminfoBytes(text, field) {
97053
+ const kb = counter(text?.match(new RegExp(`^${field}:\\s+(\\d+) kB$`, "m"))?.[1]);
97054
+ return kb !== void 0 && Number.isSafeInteger(kb * 1024) ? kb * 1024 : void 0;
97055
+ }
97056
+ function cgroupDirectory(text) {
97057
+ const path2 = text?.match(/^0::(\/[^\n]*)$/m)?.[1];
97058
+ if (!path2 || path2.split("/").includes("..")) return void 0;
97059
+ return import_node_path30.posix.join("/sys/fs/cgroup", path2);
97060
+ }
97061
+ async function collectAgentResourceDiagnostics(options = {}) {
97062
+ const platform6 = options.platform ?? process.platform;
97063
+ const readFile7 = async (path2) => {
97064
+ try {
97065
+ return await (options.readFile ?? readResourceFile)(path2);
97066
+ } catch {
97067
+ return void 0;
97068
+ }
97069
+ };
97070
+ const sample = {
97071
+ sampledAtMs: Date.now(),
97072
+ memoryTotalBytes: (0, import_node_os18.totalmem)(),
97073
+ memoryFreeBytes: (0, import_node_os18.freemem)(),
97074
+ processRssBytes: process.memoryUsage.rss(),
97075
+ cpuCount: (0, import_node_os18.availableParallelism)(),
97076
+ ...platform6 === "win32" ? {} : { loadAverage1m: (0, import_node_os18.loadavg)()[0] }
97077
+ };
97078
+ if (platform6 !== "linux") return sample;
97079
+ const [meminfo, memoryPsi, cpuPsi, ioPsi, cgroup] = await Promise.all([
97080
+ readFile7("/proc/meminfo"),
97081
+ readFile7("/proc/pressure/memory"),
97082
+ readFile7("/proc/pressure/cpu"),
97083
+ readFile7("/proc/pressure/io"),
97084
+ readFile7("/proc/self/cgroup")
97085
+ ]);
97086
+ sample.memoryAvailableBytes = meminfoBytes(meminfo, "MemAvailable");
97087
+ sample.swapTotalBytes = meminfoBytes(meminfo, "SwapTotal");
97088
+ sample.swapFreeBytes = meminfoBytes(meminfo, "SwapFree");
97089
+ sample.memoryPressureSomeAvg10 = pressure(memoryPsi, "some");
97090
+ sample.memoryPressureFullAvg10 = pressure(memoryPsi, "full");
97091
+ sample.cpuPressureSomeAvg10 = pressure(cpuPsi, "some");
97092
+ sample.ioPressureSomeAvg10 = pressure(ioPsi, "some");
97093
+ const directory = cgroupDirectory(cgroup);
97094
+ if (!directory) return sample;
97095
+ const [current, limit, events] = await Promise.all([
97096
+ readFile7(`${directory}/memory.current`),
97097
+ readFile7(`${directory}/memory.max`),
97098
+ readFile7(`${directory}/memory.events`)
97099
+ ]);
97100
+ sample.cgroupMemoryCurrentBytes = counter(current?.trim());
97101
+ sample.cgroupMemoryLimitBytes = counter(limit?.trim());
97102
+ sample.cgroupMemoryOomEvents = counter(events?.match(/^oom (\d+)$/m)?.[1]);
97103
+ sample.cgroupMemoryOomKillEvents = counter(events?.match(/^oom_kill (\d+)$/m)?.[1]);
97104
+ return sample;
97105
+ }
97106
+ var AgentResourceSampler = class {
97107
+ constructor(collect = collectAgentResourceDiagnostics) {
97108
+ this.collect = collect;
97109
+ }
97110
+ delay = (0, import_node_perf_hooks.monitorEventLoopDelay)({ resolution: 20 });
97111
+ timer;
97112
+ inFlight = false;
97113
+ generation = 0;
97114
+ sample;
97115
+ start() {
97116
+ if (this.timer) return;
97117
+ this.generation += 1;
97118
+ this.delay.reset();
97119
+ this.delay.enable();
97120
+ this.timer = setInterval(() => void this.refresh(), AGENT_RESOURCE_SAMPLE_INTERVAL_MS);
97121
+ this.timer.unref();
97122
+ void this.refresh();
97123
+ }
97124
+ stop() {
97125
+ this.generation += 1;
97126
+ if (this.timer) clearInterval(this.timer);
97127
+ this.timer = void 0;
97128
+ this.delay.disable();
97129
+ this.sample = void 0;
97130
+ }
97131
+ latest() {
97132
+ return this.sample;
97133
+ }
97134
+ async refresh() {
97135
+ if (this.inFlight) return;
97136
+ this.inFlight = true;
97137
+ const generation = this.generation;
97138
+ const delay3 = this.delay.count > 0 ? {
97139
+ eventLoopDelayMaxMs: this.delay.max / 1e6,
97140
+ eventLoopDelayP99Ms: this.delay.percentile(99) / 1e6
97141
+ } : {};
97142
+ this.delay.reset();
97143
+ try {
97144
+ const sample = await this.collect();
97145
+ if (generation === this.generation) this.sample = { ...sample, ...delay3 };
97146
+ } catch {
97147
+ if (generation === this.generation) this.sample = void 0;
97148
+ } finally {
97149
+ this.inFlight = false;
97150
+ }
97151
+ }
97152
+ };
97153
+
97154
+ // src/sandbox-outbox.ts
97155
+ var import_node_crypto24 = require("crypto");
97156
+ var import_node_fs29 = require("fs");
96594
97157
  function deriveSandboxOutboxKey(sessionToken) {
96595
97158
  if (!sessionToken) {
96596
97159
  throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
96597
97160
  }
96598
- return (0, import_node_crypto23.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
97161
+ return (0, import_node_crypto24.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
96599
97162
  }
96600
97163
  function sandboxOutboxSpoolPath(conversationId) {
96601
97164
  return `/tmp/alan-agent-outbox-${conversationId}.enc`;
@@ -96611,24 +97174,24 @@ function assertValidOutboxKey(key, path2) {
96611
97174
  }
96612
97175
  function loadOrCreateSandboxOutboxKey(input2) {
96613
97176
  const keyPath = sandboxOutboxKeyPath(input2.spoolPath);
96614
- if ((0, import_node_fs28.existsSync)(keyPath)) {
96615
- (0, import_node_fs28.chmodSync)(keyPath, 384);
96616
- return assertValidOutboxKey((0, import_node_fs28.readFileSync)(keyPath, "utf8").trim(), keyPath);
97177
+ if ((0, import_node_fs29.existsSync)(keyPath)) {
97178
+ (0, import_node_fs29.chmodSync)(keyPath, 384);
97179
+ return assertValidOutboxKey((0, import_node_fs29.readFileSync)(keyPath, "utf8").trim(), keyPath);
96617
97180
  }
96618
97181
  const initialKey = deriveSandboxOutboxKey(input2.sessionToken);
96619
97182
  try {
96620
- (0, import_node_fs28.writeFileSync)(keyPath, initialKey, { encoding: "utf8", flag: "wx", mode: 384 });
97183
+ (0, import_node_fs29.writeFileSync)(keyPath, initialKey, { encoding: "utf8", flag: "wx", mode: 384 });
96621
97184
  return initialKey;
96622
97185
  } catch (error61) {
96623
97186
  if (error61.code !== "EEXIST") throw error61;
96624
- (0, import_node_fs28.chmodSync)(keyPath, 384);
96625
- return assertValidOutboxKey((0, import_node_fs28.readFileSync)(keyPath, "utf8").trim(), keyPath);
97187
+ (0, import_node_fs29.chmodSync)(keyPath, 384);
97188
+ return assertValidOutboxKey((0, import_node_fs29.readFileSync)(keyPath, "utf8").trim(), keyPath);
96626
97189
  }
96627
97190
  }
96628
97191
  function quarantineUnusableOutbox(path2) {
96629
97192
  const suffix = `corrupt-${Date.now()}-${process.pid}`;
96630
97193
  for (const candidate of [path2, eventJournalDir(path2)]) {
96631
- if ((0, import_node_fs28.existsSync)(candidate)) (0, import_node_fs28.renameSync)(candidate, `${candidate}.${suffix}`);
97194
+ if ((0, import_node_fs29.existsSync)(candidate)) (0, import_node_fs29.renameSync)(candidate, `${candidate}.${suffix}`);
96632
97195
  }
96633
97196
  }
96634
97197
  function createSandboxEventOutbox(input2) {
@@ -96772,10 +97335,11 @@ var WSClient = class {
96772
97335
  }
96773
97336
  socket;
96774
97337
  /** Stable id for this agent process; fences stale heartbeats server-side. */
96775
- agentSessionId = (0, import_node_crypto24.randomUUID)();
97338
+ agentSessionId = (0, import_node_crypto25.randomUUID)();
96776
97339
  /** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
96777
97340
  heartbeatSeq = 0;
96778
97341
  heartbeatTimer = null;
97342
+ resourceSampler = new AgentResourceSampler();
96779
97343
  supervisedReconnectTimer = null;
96780
97344
  supervisedReconnectAttempt = 0;
96781
97345
  connectionDirective = null;
@@ -96824,11 +97388,13 @@ var WSClient = class {
96824
97388
  seq: this.heartbeatSeq,
96825
97389
  protocolVersion: AGENT_LIVENESS_PROTOCOL_VERSION,
96826
97390
  runState: this.currentRunState(),
96827
- agentVersion: this.agentVersion || void 0
97391
+ agentVersion: this.agentVersion || void 0,
97392
+ resources: this.resourceSampler.latest()
96828
97393
  };
96829
97394
  }
96830
97395
  /** Emit `agent.hello` on (re)connect and (re)start the heartbeat interval. */
96831
97396
  startHeartbeat() {
97397
+ this.resourceSampler.start();
96832
97398
  if (this.socket.connected) {
96833
97399
  this.socket.emit("agent.hello", this.buildHeartbeat());
96834
97400
  }
@@ -96840,6 +97406,7 @@ var WSClient = class {
96840
97406
  this.heartbeatTimer.unref?.();
96841
97407
  }
96842
97408
  stopHeartbeat() {
97409
+ this.resourceSampler.stop();
96843
97410
  if (this.heartbeatTimer) {
96844
97411
  clearInterval(this.heartbeatTimer);
96845
97412
  this.heartbeatTimer = null;
@@ -97304,7 +97871,7 @@ async function runSandbox(config2) {
97304
97871
  }
97305
97872
  const presenter = new WebPresenter(wsClient, config2.sessionId, config2.projectPath);
97306
97873
  presenter.setSuppressSessionLifecycle(true);
97307
- const runtimeSkillManager = await RuntimeSkillManager.open({ homeDirectory: (0, import_node_os18.homedir)() });
97874
+ const runtimeSkillManager = await RuntimeSkillManager.open({ homeDirectory: (0, import_node_os19.homedir)() });
97308
97875
  let lastProviderSessionId = config2.providerSessionId;
97309
97876
  let resumeFallbackContext = config2.resumeFallbackContext;
97310
97877
  let activeBackendKind = config2.backendKind || "claude_cli";
@@ -97604,12 +98171,12 @@ async function runSandbox(config2) {
97604
98171
  }
97605
98172
 
97606
98173
  // src/skills/skill-cli.ts
97607
- var import_promises6 = require("fs/promises");
97608
- var import_node_os19 = require("os");
97609
- var import_node_path30 = require("path");
98174
+ var import_promises7 = require("fs/promises");
98175
+ var import_node_os20 = require("os");
98176
+ var import_node_path31 = require("path");
97610
98177
  async function runSkillsCommand(args) {
97611
98178
  const [subcommand, ...options] = args;
97612
- const homeDirectory2 = option(options, "--home") ?? (0, import_node_os19.homedir)();
98179
+ const homeDirectory2 = option(options, "--home") ?? (0, import_node_os20.homedir)();
97613
98180
  const manager = await RuntimeSkillManager.open({ homeDirectory: homeDirectory2 });
97614
98181
  if (subcommand === "scan") {
97615
98182
  const inputPath = option(options, "--input");
@@ -97650,8 +98217,8 @@ async function defaultScanInput(homeDirectory2) {
97650
98217
  });
97651
98218
  }
97652
98219
  async function readProtectedJson(path2) {
97653
- const absolutePath = (0, import_node_path30.resolve)(path2);
97654
- const stat2 = await (0, import_promises6.lstat)(absolutePath);
98220
+ const absolutePath = (0, import_node_path31.resolve)(path2);
98221
+ const stat2 = await (0, import_promises7.lstat)(absolutePath);
97655
98222
  if (!stat2.isFile()) throw new Error(`Skill input is not a regular file: ${absolutePath}`);
97656
98223
  if (process.platform !== "win32" && (stat2.mode & 63) !== 0) {
97657
98224
  throw new Error(`Skill input must not be accessible by group or other users: ${absolutePath}`);
@@ -97660,7 +98227,7 @@ async function readProtectedJson(path2) {
97660
98227
  throw new Error(`Skill input must be owned by the current user: ${absolutePath}`);
97661
98228
  }
97662
98229
  if (stat2.size > 2 * 1024 * 1024) throw new Error("Skill input exceeds 2 MiB");
97663
- return JSON.parse(await (0, import_promises6.readFile)(absolutePath, "utf8"));
98230
+ return JSON.parse(await (0, import_promises7.readFile)(absolutePath, "utf8"));
97664
98231
  }
97665
98232
  function option(args, name) {
97666
98233
  const index = args.indexOf(name);
@@ -97818,7 +98385,7 @@ async function main() {
97818
98385
  return;
97819
98386
  }
97820
98387
  if (command === "service" && commandArgs[0] === "uninstall") {
97821
- uninstallDaemonService(commandArgs.slice(1));
98388
+ await uninstallDaemonService(commandArgs.slice(1));
97822
98389
  return;
97823
98390
  }
97824
98391
  if (command === "service" && commandArgs[0] === "status") {