@alan-ai-hq/agent-manager 0.1.97 → 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 +614 -238
  2. package/package.json +3 -3
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",
@@ -15460,7 +15460,7 @@ function describeError(error61) {
15460
15460
  }
15461
15461
 
15462
15462
  // src/version.ts
15463
- var AGENT_VERSION = "0.1.97";
15463
+ var AGENT_VERSION = "0.1.98";
15464
15464
 
15465
15465
  // src/daemon-worktree.ts
15466
15466
  var import_node_child_process3 = require("child_process");
@@ -49619,6 +49619,16 @@ async function probeLocalDaemon(port, token, timeoutMs = 750) {
49619
49619
  const probe = await probeLocalDaemonEndpoint(port, token, timeoutMs);
49620
49620
  return probe.state === "running" ? probe.status : null;
49621
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
+ }
49622
49632
  async function adoptLocalDaemonControl(input2) {
49623
49633
  const timeoutMs = input2.timeoutMs ?? 750;
49624
49634
  try {
@@ -76706,13 +76716,13 @@ var RuntimeRunJournal = class {
76706
76716
  * Keep the exact interruption outcome until a durable authenticated control
76707
76717
  * channel can prove both identity and ownership.
76708
76718
  */
76709
- assessRecovery(runId, isProcessAlive) {
76719
+ assessRecovery(runId, isProcessAlive2) {
76710
76720
  const entry = this.entries.get(runId);
76711
76721
  if (!entry) return void 0;
76712
76722
  if (entry.providerPid === void 0) {
76713
76723
  return { reattachable: false, reason: "provider_not_recorded" };
76714
76724
  }
76715
- if (!isProcessAlive(entry.providerPid)) {
76725
+ if (!isProcessAlive2(entry.providerPid)) {
76716
76726
  return {
76717
76727
  reattachable: false,
76718
76728
  reason: "provider_not_running",
@@ -76766,7 +76776,8 @@ var RuntimeSetupGrantExpiredError = class extends Error {
76766
76776
  this.name = "RuntimeSetupGrantExpiredError";
76767
76777
  }
76768
76778
  };
76769
- async function createRuntimeSetupGrant(input2) {
76779
+ async function createRuntimeSetupGrant(input2, signal) {
76780
+ signal?.throwIfAborted();
76770
76781
  const response = await fetch(`${input2.apiUrl}/public/runtimes/setup-tokens`, {
76771
76782
  method: "POST",
76772
76783
  headers: {
@@ -76778,7 +76789,7 @@ async function createRuntimeSetupGrant(input2) {
76778
76789
  ...input2.scope === "team" ? { teamId: input2.teamId } : {},
76779
76790
  ttlMinutes: 5
76780
76791
  }),
76781
- signal: AbortSignal.timeout(1e4)
76792
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(1e4)]) : AbortSignal.timeout(1e4)
76782
76793
  });
76783
76794
  const body = await response.json();
76784
76795
  if (!response.ok || typeof body.setupToken !== "string") {
@@ -76787,9 +76798,10 @@ async function createRuntimeSetupGrant(input2) {
76787
76798
  }
76788
76799
  return body.setupToken;
76789
76800
  }
76790
- async function exchangeRuntimeSetupGrant(input2) {
76801
+ async function exchangeRuntimeSetupGrant(input2, setupSignal) {
76802
+ setupSignal?.throwIfAborted();
76791
76803
  const registrationAttemptId = getOrCreateSetupAttemptId(input2.setupToken);
76792
- const signal = AbortSignal.timeout(15e3);
76804
+ const signal = setupSignal ? AbortSignal.any([setupSignal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3);
76793
76805
  const registrationInit = {
76794
76806
  method: "POST",
76795
76807
  headers: { "content-type": "application/json" },
@@ -76814,6 +76826,7 @@ async function exchangeRuntimeSetupGrant(input2) {
76814
76826
  let response;
76815
76827
  let transportError;
76816
76828
  for (let attempt = 1; attempt <= 3; attempt += 1) {
76829
+ signal.throwIfAborted();
76817
76830
  try {
76818
76831
  response = await fetch(
76819
76832
  `${input2.apiUrl}/public/runtimes/register-with-setup-token`,
@@ -76821,6 +76834,7 @@ async function exchangeRuntimeSetupGrant(input2) {
76821
76834
  );
76822
76835
  transportError = void 0;
76823
76836
  } catch (error61) {
76837
+ signal.throwIfAborted();
76824
76838
  transportError = error61;
76825
76839
  }
76826
76840
  const retriable = response && (response.status === 408 || response.status === 429 || response.status >= 500);
@@ -76858,6 +76872,7 @@ async function exchangeRuntimeSetupGrant(input2) {
76858
76872
  };
76859
76873
  }
76860
76874
  async function handoffRuntimeSetupGrant(input2) {
76875
+ input2.signal?.throwIfAborted();
76861
76876
  const response = await fetch(`http://127.0.0.1:${input2.port}/credentials/setup`, {
76862
76877
  method: "POST",
76863
76878
  headers: {
@@ -76865,7 +76880,7 @@ async function handoffRuntimeSetupGrant(input2) {
76865
76880
  "content-type": "application/json"
76866
76881
  },
76867
76882
  body: JSON.stringify(input2.setup),
76868
- signal: AbortSignal.timeout(input2.timeoutMs ?? 2e4)
76883
+ signal: input2.signal ? AbortSignal.any([input2.signal, AbortSignal.timeout(input2.timeoutMs ?? 2e4)]) : AbortSignal.timeout(input2.timeoutMs ?? 2e4)
76869
76884
  });
76870
76885
  const body = await response.json();
76871
76886
  if (!response.ok) {
@@ -80459,7 +80474,20 @@ async function runLocalDaemonRecoveryLadder(input2) {
80459
80474
  error: "The local daemon exhausted its reconnect and safe-restart recovery budget."
80460
80475
  };
80461
80476
  }
80462
- 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();
80463
80491
  bindConfigPath(args);
80464
80492
  const previousConfig = readConfigForRegistration();
80465
80493
  const { apiUrl, wsUrl, endpointProfile } = resolveEndpoints(args);
@@ -80477,12 +80505,15 @@ async function setupDaemon(args) {
80477
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"
80478
80506
  );
80479
80507
  }
80480
- const registrationGrant = setupToken ?? await createRuntimeSetupGrant({
80481
- apiUrl,
80482
- accessToken: token,
80483
- scope,
80484
- teamId
80485
- });
80508
+ const registrationGrant = setupToken ?? await createRuntimeSetupGrant(
80509
+ {
80510
+ apiUrl,
80511
+ accessToken: token,
80512
+ scope,
80513
+ teamId
80514
+ },
80515
+ signal
80516
+ );
80486
80517
  const setup = {
80487
80518
  apiUrl,
80488
80519
  wsUrl,
@@ -80492,13 +80523,16 @@ async function setupDaemon(args) {
80492
80523
  displayName,
80493
80524
  installationId
80494
80525
  };
80526
+ signal?.throwIfAborted();
80495
80527
  let liveConfig = previousConfig;
80496
80528
  const localApiPort = getLocalApiPort(args, liveConfig);
80497
80529
  let live = liveConfig.localApiToken ? await probeLocalDaemon(localApiPort, liveConfig.localApiToken) : null;
80530
+ signal?.throwIfAborted();
80498
80531
  if (!live && liveConfig.localServiceId && liveConfig.localServiceSecret) {
80499
80532
  live = await adoptConfiguredLocalDaemon(args);
80500
80533
  liveConfig = readConfigForRegistration();
80501
80534
  }
80535
+ signal?.throwIfAborted();
80502
80536
  if (live) {
80503
80537
  if (!liveConfig.localApiToken) {
80504
80538
  throw new Error("running daemon could not be authenticated for credential setup");
@@ -80512,14 +80546,16 @@ async function setupDaemon(args) {
80512
80546
  const body2 = await handoffRuntimeSetupGrant({
80513
80547
  port: localApiPort,
80514
80548
  localApiToken: liveConfig.localApiToken,
80515
- setup: liveSetup
80549
+ setup: liveSetup,
80550
+ signal
80516
80551
  });
80552
+ signal?.throwIfAborted();
80517
80553
  console.info("[alan-agent] Runtime credentials adopted by running daemon", {
80518
80554
  runtimeId: body2.runtimeId,
80519
80555
  credentialGeneration: body2.credentialGeneration,
80520
80556
  configPath: getConfigPath()
80521
80557
  });
80522
- return { daemonAlreadyRunning: true };
80558
+ return body2.runtimeId !== liveConfig.runtimeId;
80523
80559
  }
80524
80560
  if (readLocalDaemonOwnerLease(getConfigPath())) {
80525
80561
  throw new Error(
@@ -80528,28 +80564,27 @@ async function setupDaemon(args) {
80528
80564
  }
80529
80565
  let body;
80530
80566
  try {
80531
- body = await exchangeRuntimeSetupGrant(setup);
80567
+ body = await exchangeRuntimeSetupGrant(setup, signal);
80532
80568
  } catch (error61) {
80569
+ signal?.throwIfAborted();
80533
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
+ }
80534
80574
  console.info("[alan-agent] Setup link expired; continuing with browser authorization");
80535
80575
  await loginWithDeviceCode(args);
80536
- return { daemonAlreadyRunning: false };
80576
+ return false;
80537
80577
  }
80538
80578
  throw error61;
80539
80579
  }
80540
- writeConfig({
80580
+ signal?.throwIfAborted();
80581
+ writeRegisteredRuntimeConfig(args, previousConfig, {
80541
80582
  apiUrl,
80542
80583
  wsUrl,
80543
80584
  endpointProfile,
80544
80585
  runtimeId: body.runtimeId,
80545
80586
  runtimeToken: body.runtimeToken,
80546
80587
  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,
80553
80588
  displayName: body.displayName,
80554
80589
  installationId
80555
80590
  });
@@ -80558,7 +80593,7 @@ async function setupDaemon(args) {
80558
80593
  runtimeId: body.runtimeId,
80559
80594
  configPath: getConfigPath()
80560
80595
  });
80561
- return { daemonAlreadyRunning: false };
80596
+ return false;
80562
80597
  }
80563
80598
  async function loginWithDeviceCode(args) {
80564
80599
  bindConfigPath(args);
@@ -80647,19 +80682,13 @@ async function loginWithDeviceCode(args) {
80647
80682
  if (ownedPending?.operationId !== operation.id) {
80648
80683
  throw new Error("browser authorization sidecar ownership was lost before finalization");
80649
80684
  }
80650
- writeConfig({
80685
+ writeRegisteredRuntimeConfig(args, previousConfig, {
80651
80686
  apiUrl,
80652
80687
  wsUrl,
80653
80688
  endpointProfile,
80654
80689
  runtimeId: body.runtime.id,
80655
80690
  runtimeToken: body.runtimeToken,
80656
80691
  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,
80663
80692
  displayName: body.runtime.displayName ?? displayName,
80664
80693
  installationId
80665
80694
  });
@@ -80698,14 +80727,7 @@ async function stopDaemon(args) {
80698
80727
  console.info("[alan-agent] No daemon listening", { port: localApiPort });
80699
80728
  return;
80700
80729
  }
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
- });
80730
+ const response = await requestLocalDaemonShutdown(localApiPort, localApiToken);
80709
80731
  if (!response.ok) {
80710
80732
  throw new Error(`Failed to stop daemon: HTTP ${response.status}`);
80711
80733
  }
@@ -80850,14 +80872,7 @@ async function drainAndStopLocalDaemonBeforeCredentialCleanup(args, stored) {
80850
80872
  `Logout could not safely drain the local daemon (HTTP ${prepare.status}). Stop or update the local service before removing credentials.`
80851
80873
  );
80852
80874
  }
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
- });
80875
+ const shutdown = await requestLocalDaemonShutdown(localApiPort, localApiToken);
80861
80876
  if (!shutdown.ok) {
80862
80877
  throw new Error(`Logout could not stop the local daemon (HTTP ${shutdown.status}).`);
80863
80878
  }
@@ -81018,6 +81033,9 @@ async function printDoctor(args = []) {
81018
81033
  );
81019
81034
  }
81020
81035
 
81036
+ // src/install-command.ts
81037
+ var import_promises6 = require("timers/promises");
81038
+
81021
81039
  // src/mcp-config-adapters.ts
81022
81040
  var import_node_crypto20 = require("crypto");
81023
81041
  var import_node_fs23 = require("fs");
@@ -82402,12 +82420,14 @@ function runMcpIntegrationCommand(args, options = {}) {
82402
82420
 
82403
82421
  // src/service-manager.ts
82404
82422
  var import_node_child_process9 = require("child_process");
82423
+ var import_node_crypto21 = require("crypto");
82405
82424
  var import_node_fs25 = require("fs");
82406
82425
  var import_node_os16 = require("os");
82407
82426
  var import_node_path27 = require("path");
82408
82427
  var SERVICE_LABEL = "ai.tryalan.agent";
82409
82428
  var SYSTEMD_UNIT = "alan-agent.service";
82410
82429
  var WINDOWS_TASK = "Alan Agent";
82430
+ var STOP_POLL_INTERVAL_MS = 200;
82411
82431
  function xml(value2) {
82412
82432
  return value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
82413
82433
  }
@@ -82417,6 +82437,18 @@ function systemdArg(value2) {
82417
82437
  function powershellLiteral(value2) {
82418
82438
  return `'${value2.replaceAll("'", "''")}'`;
82419
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
+ }
82420
82452
  function serviceNames(profile) {
82421
82453
  if (profile === "production") {
82422
82454
  return { launchd: SERVICE_LABEL, systemd: SYSTEMD_UNIT, windows: WINDOWS_TASK };
@@ -82438,7 +82470,7 @@ function buildDaemonServicePlan(input2) {
82438
82470
  const domain2 = `gui/${userId}`;
82439
82471
  const serviceTarget = `${domain2}/${names.launchd}`;
82440
82472
  const manifestPath = (0, import_node_path27.join)(input2.homeDir, "Library", "LaunchAgents", `${names.launchd}.plist`);
82441
- 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");
82442
82474
  const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
82443
82475
  const environmentEntries = Object.entries(input2.environment ?? {}).map(([key, value2]) => ` <key>${xml(key)}</key>
82444
82476
  <string>${xml(value2)}</string>`).join("\n");
@@ -82467,9 +82499,9 @@ ${environmentBlock} <key>RunAtLoad</key>
82467
82499
  <key>ThrottleInterval</key>
82468
82500
  <integer>5</integer>
82469
82501
  <key>StandardOutPath</key>
82470
- <string>${xml((0, import_node_path27.join)(logDir, "daemon.log"))}</string>
82502
+ <string>${xml((0, import_node_path27.join)(logDir2, "daemon.log"))}</string>
82471
82503
  <key>StandardErrorPath</key>
82472
- <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>
82473
82505
  </dict>
82474
82506
  </plist>
82475
82507
  `;
@@ -82477,7 +82509,7 @@ ${environmentBlock} <key>RunAtLoad</key>
82477
82509
  platform: "darwin",
82478
82510
  manifestPath,
82479
82511
  manifest,
82480
- logDirectory: logDir,
82512
+ logDirectory: logDir2,
82481
82513
  loadDefinitionCommands: [
82482
82514
  {
82483
82515
  command: "launchctl",
@@ -82518,23 +82550,6 @@ ${environmentBlock} <key>RunAtLoad</key>
82518
82550
  tolerateFailure: true
82519
82551
  }
82520
82552
  ],
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
- ],
82538
82553
  uninstallCommands: [
82539
82554
  {
82540
82555
  command: "launchctl",
@@ -82551,8 +82566,7 @@ ${environmentBlock} <key>RunAtLoad</key>
82551
82566
  command: "launchctl",
82552
82567
  args: ["print", serviceTarget],
82553
82568
  tolerateFailure: true
82554
- },
82555
- lingerStatusCommand: null
82569
+ }
82556
82570
  };
82557
82571
  }
82558
82572
  if (input2.platform === "linux") {
@@ -82615,18 +82629,6 @@ WantedBy=default.target
82615
82629
  tolerateFailure: true
82616
82630
  }
82617
82631
  ],
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
- ],
82630
82632
  uninstallCommands: [
82631
82633
  {
82632
82634
  command: "systemctl",
@@ -82648,117 +82650,174 @@ WantedBy=default.target
82648
82650
  command: "systemctl",
82649
82651
  args: ["--user", "is-active", names.systemd],
82650
82652
  tolerateFailure: true
82651
- },
82652
- lingerStatusCommand: {
82653
- command: "loginctl",
82654
- args: ["show-user", "$(id -un)", "-p", "Linger"],
82655
- tolerateFailure: true
82656
82653
  }
82657
82654
  };
82658
82655
  }
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
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)}`
82664
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("; ");
82665
82757
  const registerScript = [
82666
- "$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name",
82667
- `$action = New-ScheduledTaskAction -Execute ${executable} -Argument ${argument}`,
82758
+ taskContext,
82759
+ resolveTaskSid,
82760
+ windowsDefinitionContext,
82761
+ `$action = New-ScheduledTaskAction -Execute ${executable} -Argument $expectedLauncherArguments -WorkingDirectory $expectedWorkingDirectory`,
82668
82762
  "$trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser",
82669
82763
  "$principal = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Limited",
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`
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' }"
82672
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);
82673
82802
  return {
82674
82803
  platform: "win32",
82675
82804
  manifestPath: null,
82676
82805
  manifest: null,
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
- ],
82806
+ logDirectory: logDir,
82807
+ loadDefinitionCommands: [registerCommand],
82808
+ reloadDefinitionCommands: [registerCommand],
82692
82809
  enableCommands: [],
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
- ],
82810
+ startCommands: [powershellCommand(`${taskContext}; Start-ScheduledTask -TaskName $taskName`)],
82705
82811
  stopCommands: [
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
- }
82716
- ],
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
- }
82812
+ powershellCommand(
82813
+ `${taskContext}; Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`,
82814
+ true
82815
+ )
82740
82816
  ],
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
82817
+ uninstallCommands: [powershellCommand(uninstallScript)],
82818
+ statusCommand: powershellCommand(statusScript, true),
82819
+ isActiveCommand: powershellCommand(statusScript, true),
82820
+ definitionMarker
82762
82821
  };
82763
82822
  }
82764
82823
  function resolveCurrentPlatform() {
@@ -82768,16 +82827,16 @@ function resolveCurrentPlatform() {
82768
82827
  }
82769
82828
  return currentPlatform;
82770
82829
  }
82771
- function currentServicePlan(args, runtime) {
82772
- const currentPlatform = resolveCurrentPlatform();
82830
+ function currentServicePlan(args, runtime, options = {}) {
82831
+ const currentPlatform = options.platform ?? resolveCurrentPlatform();
82773
82832
  const profile = resolveEndpointProfileFromArgs(args);
82774
82833
  const cliEntry = runtime?.cliEntry ?? process.argv[1];
82775
82834
  if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
82776
82835
  return buildDaemonServicePlan({
82777
82836
  platform: currentPlatform,
82778
- homeDir: (0, import_node_os16.homedir)(),
82837
+ homeDir: options.homeDir ?? (0, import_node_os16.homedir)(),
82779
82838
  nodeExecutable: runtime?.executable ?? process.execPath,
82780
- cliEntry: (0, import_node_path27.resolve)(cliEntry),
82839
+ cliEntry: currentPlatform === "win32" ? import_node_path27.win32.resolve(cliEntry) : (0, import_node_path27.resolve)(cliEntry),
82781
82840
  profile,
82782
82841
  environment: runtime?.environment
82783
82842
  });
@@ -82797,10 +82856,14 @@ function defaultReadManifest(path2) {
82797
82856
  if (!(0, import_node_fs25.existsSync)(path2)) return null;
82798
82857
  return (0, import_node_fs25.readFileSync)(path2, "utf8");
82799
82858
  }
82800
- function runServiceCommand(command) {
82801
- 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
+ });
82802
82865
  const status = result.status ?? 1;
82803
- const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
82866
+ const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}${result.error?.message ?? ""}`.trim();
82804
82867
  if (status !== 0 && !command.tolerateFailure) {
82805
82868
  throw new Error(
82806
82869
  `Service command failed (${command.command} ${command.args.join(" ")}): ${output2 || `exit ${status}`}`
@@ -82813,6 +82876,23 @@ function defaultSleep(ms) {
82813
82876
  setTimeout(resolveSleep, ms);
82814
82877
  });
82815
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
+ }
82816
82896
  function parseServiceHostState(servicePlatform, result) {
82817
82897
  const output2 = result.output;
82818
82898
  if (servicePlatform === "linux") {
@@ -82838,9 +82918,69 @@ function parseServiceHostState(servicePlatform, result) {
82838
82918
  const stateMatch = output2.match(/\b(?:State|state)\s*[:=]\s*(\w+)/);
82839
82919
  const state = (stateMatch?.[1] ?? output2.trim()).toLowerCase();
82840
82920
  if (state === "running") return "running";
82841
- if (state === "ready" || state === "disabled" || state === "queued") return "stopped";
82921
+ if (state === "queued") return "starting";
82922
+ if (state === "ready" || state === "disabled") return "stopped";
82842
82923
  return "unknown";
82843
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
+ }
82844
82984
  function resolveEnsureDefinitionOutcome(input2) {
82845
82985
  if (!input2.definitionChanged) {
82846
82986
  return { changed: false, updatePending: false };
@@ -82850,9 +82990,6 @@ function resolveEnsureDefinitionOutcome(input2) {
82850
82990
  }
82851
82991
  return { changed: true, updatePending: false };
82852
82992
  }
82853
- function shouldStartOnInstall(hostState) {
82854
- return hostState === "stopped";
82855
- }
82856
82993
  function resolveUsername(options) {
82857
82994
  if (options?.username) return options.username;
82858
82995
  try {
@@ -82861,17 +82998,14 @@ function resolveUsername(options) {
82861
82998
  return process.env.USER || process.env.USERNAME || "unknown";
82862
82999
  }
82863
83000
  }
82864
- function buildLingerStatusCommand(username) {
82865
- return {
82866
- command: "loginctl",
82867
- args: ["show-user", username, "-p", "Linger"],
82868
- tolerateFailure: true
82869
- };
82870
- }
82871
83001
  function assertSystemdUserLingerEnabled(options = {}) {
82872
83002
  const username = resolveUsername(options);
82873
83003
  const run = options.runCommand ?? runServiceCommand;
82874
- const result = run(buildLingerStatusCommand(username));
83004
+ const result = run({
83005
+ command: "loginctl",
83006
+ args: ["show-user", username, "-p", "Linger"],
83007
+ tolerateFailure: true
83008
+ });
82875
83009
  if (result.status !== 0) {
82876
83010
  throw new Error(
82877
83011
  `Cannot verify systemd user linger for ${username} (loginctl failed). For durable headless VM boot run: sudo loginctl enable-linger ${username}`
@@ -82885,10 +83019,18 @@ function assertSystemdUserLingerEnabled(options = {}) {
82885
83019
  }
82886
83020
  function createHostContext(options = {}) {
82887
83021
  const args = options.args ?? [];
82888
- const plan = currentServicePlan(args, options.runtime);
82889
- const run = options.runCommand ?? runServiceCommand;
82890
- const sleep4 = options.sleep ?? defaultSleep;
83022
+ const plan = currentServicePlan(args, options.runtime, options);
83023
+ const sleep5 = options.sleep ?? defaultSleep;
82891
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
+ };
82892
83034
  const readManifest = options.readManifest ?? defaultReadManifest;
82893
83035
  const writeManifest = options.writeManifest ?? writeManifestAtomic;
82894
83036
  const removeManifest = options.removeManifest ?? ((path2) => (0, import_node_fs25.rmSync)(path2, { force: true }));
@@ -82899,19 +83041,37 @@ function createHostContext(options = {}) {
82899
83041
  args,
82900
83042
  plan,
82901
83043
  run,
82902
- sleep: sleep4,
83044
+ sleep: sleep5,
82903
83045
  nowMs,
82904
83046
  readManifest,
82905
83047
  writeManifest,
82906
83048
  removeManifest,
82907
83049
  ensureLogDir,
82908
- 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
82909
83058
  };
82910
83059
  }
82911
83060
  function inspectDaemonService(options = {}) {
82912
83061
  const { plan, run, nowMs } = createHostContext(options);
82913
83062
  const result = run(plan.isActiveCommand);
82914
- 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
+ }
82915
83075
  return {
82916
83076
  state,
82917
83077
  platform: plan.platform,
@@ -82921,8 +83081,9 @@ function inspectDaemonService(options = {}) {
82921
83081
  }
82922
83082
  function ensureDaemonServiceDefinition(options = {}) {
82923
83083
  const ctx = createHostContext(options);
82924
- const { plan, run, readManifest, writeManifest, ensureLogDir } = ctx;
83084
+ const { plan, run, nowMs, readManifest, writeManifest, ensureLogDir } = ctx;
82925
83085
  let definitionChanged = false;
83086
+ let inspection;
82926
83087
  if (plan.manifestPath && plan.manifest) {
82927
83088
  ensureLogDir();
82928
83089
  const existing = readManifest(plan.manifestPath);
@@ -82931,20 +83092,37 @@ function ensureDaemonServiceDefinition(options = {}) {
82931
83092
  writeManifest(plan.manifestPath, plan.manifest);
82932
83093
  }
82933
83094
  } else if (plan.platform === "win32") {
82934
- 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
+ };
82935
83109
  }
82936
- const inspection = inspectDaemonService(options);
83110
+ inspection ??= inspectDaemonService(options);
83111
+ const definitionHostState = plan.platform === "win32" && inspection.state === "starting" ? "stopped" : inspection.state;
82937
83112
  const outcome = resolveEnsureDefinitionOutcome({
82938
83113
  definitionChanged,
82939
- hostState: inspection.state
83114
+ hostState: definitionHostState
82940
83115
  });
82941
83116
  if (outcome.updatePending) {
82942
83117
  return outcome;
82943
83118
  }
82944
- if (inspection.state === "running" || inspection.state === "starting") {
83119
+ if (plan.platform === "win32" && !outcome.changed) {
82945
83120
  return outcome;
82946
83121
  }
82947
- const definitionCommands = inspection.state === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
83122
+ if (definitionHostState === "running" || definitionHostState === "starting") {
83123
+ return outcome;
83124
+ }
83125
+ const definitionCommands = definitionHostState === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
82948
83126
  for (const command of definitionCommands) run(command);
82949
83127
  return outcome;
82950
83128
  }
@@ -82956,10 +83134,140 @@ function startDaemonService(options = {}) {
82956
83134
  const { plan, run } = createHostContext(options);
82957
83135
  for (const command of plan.startCommands) run(command);
82958
83136
  }
82959
- 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 = {}) {
82960
83206
  const ctx = createHostContext({ ...options, args: options.args ?? args });
82961
- for (const command of ctx.plan.uninstallCommands) ctx.run(command);
82962
- 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
+ }
82963
83271
  console.info("[alan-agent] Per-user daemon service removed");
82964
83272
  }
82965
83273
  function installDaemonService(args = [], runtime, options = {}) {
@@ -82987,7 +83295,7 @@ function installDaemonService(args = [], runtime, options = {}) {
82987
83295
  });
82988
83296
  return;
82989
83297
  }
82990
- if (!shouldStartOnInstall(inspection.state)) {
83298
+ if (inspection.state !== "stopped") {
82991
83299
  console.info("[alan-agent] Per-user daemon service already present", {
82992
83300
  profile,
82993
83301
  state: inspection.state
@@ -83011,13 +83319,81 @@ ${result.output}
83011
83319
  }
83012
83320
 
83013
83321
  // src/install-command.ts
83014
- 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
+ }
83015
83387
  bindConfigPath(commandArgs);
83016
83388
  const configured = readConfig();
83017
83389
  const configurationMode = resolveInstallConfigurationMode(commandArgs, configured);
83018
- if (configurationMode === "setup") await setupDaemon(commandArgs);
83019
- if (configurationMode === "login") await loginWithDeviceCode(commandArgs);
83020
- 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
+ }
83021
83397
  runMcpIntegrationCommand(["reconcile", ...commandArgs]);
83022
83398
  }
83023
83399
 
@@ -86395,7 +86771,7 @@ var REQUEST_STATE_ONLY_LEG_PACING_MS2 = 250;
86395
86771
  function inputRequiredRoundsExceededMessage2(method, maxRounds) {
86396
86772
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
86397
86773
  }
86398
- function sleep3(ms, signal) {
86774
+ function sleep4(ms, signal) {
86399
86775
  return new Promise((resolve15, reject) => {
86400
86776
  if (signal?.aborted) {
86401
86777
  reject(signal.reason instanceof SdkError2 ? signal.reason : new SdkError2(SdkErrorCode2.RequestTimeout, String(signal.reason)));
@@ -94662,7 +95038,7 @@ var LegacyInputRequiredShim = class {
94662
95038
  } finally {
94663
95039
  roundAbort.dispose();
94664
95040
  }
94665
- } else await sleep3(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
95041
+ } else await sleep4(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
94666
95042
  let ctxNext = {
94667
95043
  ...ctx,
94668
95044
  mcpReq: {
@@ -95459,7 +95835,7 @@ async function runMcpStdioProxy(env = process.env, local = new StdioServerTransp
95459
95835
  }
95460
95836
 
95461
95837
  // src/native-hook-command.ts
95462
- var import_node_crypto21 = require("crypto");
95838
+ var import_node_crypto22 = require("crypto");
95463
95839
  var import_node_fs27 = require("fs");
95464
95840
  var import_node_os17 = require("os");
95465
95841
  var import_node_path29 = require("path");
@@ -95828,7 +96204,7 @@ function parseNativeHookEnvelope(input2) {
95828
96204
  const projectPin = eventType === "session_start" && resolvedCwd ? readAlanProjectPin(resolvedCwd) : void 0;
95829
96205
  return {
95830
96206
  schemaVersion: 1,
95831
- eventId: (0, import_node_crypto21.randomUUID)(),
96207
+ eventId: (0, import_node_crypto22.randomUUID)(),
95832
96208
  provider: capability.provider,
95833
96209
  eventType,
95834
96210
  observedAt: (input2.now ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -96324,7 +96700,7 @@ function delay2(ms) {
96324
96700
  }
96325
96701
 
96326
96702
  // src/web-presenter.ts
96327
- var import_node_crypto22 = require("crypto");
96703
+ var import_node_crypto23 = require("crypto");
96328
96704
  var WebPresenter = class {
96329
96705
  constructor(wsClient, conversationId, cwd) {
96330
96706
  this.conversationId = conversationId;
@@ -96510,7 +96886,7 @@ var WebPresenter = class {
96510
96886
  this.applicationProblem = createAlanProblem({
96511
96887
  domain: "application",
96512
96888
  code: input2.code,
96513
- id: (0, import_node_crypto22.randomUUID)(),
96889
+ id: (0, import_node_crypto23.randomUUID)(),
96514
96890
  title: APPLICATION_PROBLEM_TITLES[input2.code],
96515
96891
  detail: input2.message,
96516
96892
  // The schema's slot for the technical cause, so the problem carries it
@@ -96580,7 +96956,7 @@ var WebPresenter = class {
96580
96956
  };
96581
96957
 
96582
96958
  // src/ws-client.ts
96583
- var import_node_crypto24 = require("crypto");
96959
+ var import_node_crypto25 = require("crypto");
96584
96960
 
96585
96961
  // ../shared/dist/agent-liveness.js
96586
96962
  var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
@@ -96776,13 +97152,13 @@ var AgentResourceSampler = class {
96776
97152
  };
96777
97153
 
96778
97154
  // src/sandbox-outbox.ts
96779
- var import_node_crypto23 = require("crypto");
97155
+ var import_node_crypto24 = require("crypto");
96780
97156
  var import_node_fs29 = require("fs");
96781
97157
  function deriveSandboxOutboxKey(sessionToken) {
96782
97158
  if (!sessionToken) {
96783
97159
  throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
96784
97160
  }
96785
- 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");
96786
97162
  }
96787
97163
  function sandboxOutboxSpoolPath(conversationId) {
96788
97164
  return `/tmp/alan-agent-outbox-${conversationId}.enc`;
@@ -96959,7 +97335,7 @@ var WSClient = class {
96959
97335
  }
96960
97336
  socket;
96961
97337
  /** Stable id for this agent process; fences stale heartbeats server-side. */
96962
- agentSessionId = (0, import_node_crypto24.randomUUID)();
97338
+ agentSessionId = (0, import_node_crypto25.randomUUID)();
96963
97339
  /** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
96964
97340
  heartbeatSeq = 0;
96965
97341
  heartbeatTimer = null;
@@ -97795,7 +98171,7 @@ async function runSandbox(config2) {
97795
98171
  }
97796
98172
 
97797
98173
  // src/skills/skill-cli.ts
97798
- var import_promises6 = require("fs/promises");
98174
+ var import_promises7 = require("fs/promises");
97799
98175
  var import_node_os20 = require("os");
97800
98176
  var import_node_path31 = require("path");
97801
98177
  async function runSkillsCommand(args) {
@@ -97842,7 +98218,7 @@ async function defaultScanInput(homeDirectory2) {
97842
98218
  }
97843
98219
  async function readProtectedJson(path2) {
97844
98220
  const absolutePath = (0, import_node_path31.resolve)(path2);
97845
- const stat2 = await (0, import_promises6.lstat)(absolutePath);
98221
+ const stat2 = await (0, import_promises7.lstat)(absolutePath);
97846
98222
  if (!stat2.isFile()) throw new Error(`Skill input is not a regular file: ${absolutePath}`);
97847
98223
  if (process.platform !== "win32" && (stat2.mode & 63) !== 0) {
97848
98224
  throw new Error(`Skill input must not be accessible by group or other users: ${absolutePath}`);
@@ -97851,7 +98227,7 @@ async function readProtectedJson(path2) {
97851
98227
  throw new Error(`Skill input must be owned by the current user: ${absolutePath}`);
97852
98228
  }
97853
98229
  if (stat2.size > 2 * 1024 * 1024) throw new Error("Skill input exceeds 2 MiB");
97854
- return JSON.parse(await (0, import_promises6.readFile)(absolutePath, "utf8"));
98230
+ return JSON.parse(await (0, import_promises7.readFile)(absolutePath, "utf8"));
97855
98231
  }
97856
98232
  function option(args, name) {
97857
98233
  const index = args.indexOf(name);
@@ -98009,7 +98385,7 @@ async function main() {
98009
98385
  return;
98010
98386
  }
98011
98387
  if (command === "service" && commandArgs[0] === "uninstall") {
98012
- uninstallDaemonService(commandArgs.slice(1));
98388
+ await uninstallDaemonService(commandArgs.slice(1));
98013
98389
  return;
98014
98390
  }
98015
98391
  if (command === "service" && commandArgs[0] === "status") {