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

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 +219 -521
  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: createHash19 } = require("crypto");
3407
+ var { randomBytes: randomBytes13, createHash: createHash18 } = require("crypto");
3408
3408
  var { Duplex, Readable: Readable2 } = require("stream");
3409
3409
  var { URL: URL2 } = require("url");
3410
3410
  var PerMessageDeflate = require_permessage_deflate();
@@ -4061,7 +4061,7 @@ var require_websocket = __commonJS({
4061
4061
  abortHandshake(websocket, socket, "Invalid Upgrade header");
4062
4062
  return;
4063
4063
  }
4064
- const digest = createHash19("sha1").update(key + GUID).digest("base64");
4064
+ const digest = createHash18("sha1").update(key + GUID).digest("base64");
4065
4065
  if (res.headers["sec-websocket-accept"] !== digest) {
4066
4066
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
4067
4067
  return;
@@ -4428,7 +4428,7 @@ var require_websocket_server = __commonJS({
4428
4428
  var EventEmitter = require("events");
4429
4429
  var http2 = require("http");
4430
4430
  var { Duplex } = require("stream");
4431
- var { createHash: createHash19 } = require("crypto");
4431
+ var { createHash: createHash18 } = require("crypto");
4432
4432
  var extension = require_extension();
4433
4433
  var PerMessageDeflate = require_permessage_deflate();
4434
4434
  var subprotocol = require_subprotocol();
@@ -4725,7 +4725,7 @@ var require_websocket_server = __commonJS({
4725
4725
  );
4726
4726
  }
4727
4727
  if (this._state > RUNNING) return abortHandshake(socket, 503);
4728
- const digest = createHash19("sha1").update(key + GUID).digest("base64");
4728
+ const digest = createHash18("sha1").update(key + GUID).digest("base64");
4729
4729
  const headers = [
4730
4730
  "HTTP/1.1 101 Switching Protocols",
4731
4731
  "Upgrade: websocket",
@@ -15451,7 +15451,7 @@ function describeError(error61) {
15451
15451
  }
15452
15452
 
15453
15453
  // src/version.ts
15454
- var AGENT_VERSION = "0.1.95";
15454
+ var AGENT_VERSION = "0.1.96";
15455
15455
 
15456
15456
  // src/daemon-worktree.ts
15457
15457
  var import_node_child_process3 = require("child_process");
@@ -49610,16 +49610,6 @@ async function probeLocalDaemon(port, token, timeoutMs = 750) {
49610
49610
  const probe = await probeLocalDaemonEndpoint(port, token, timeoutMs);
49611
49611
  return probe.state === "running" ? probe.status : null;
49612
49612
  }
49613
- function requestLocalDaemonShutdown(port, token) {
49614
- return fetch(`http://127.0.0.1:${port}/shutdown?force=1`, {
49615
- method: "POST",
49616
- headers: {
49617
- authorization: `Bearer ${token}`,
49618
- "x-alan-shutdown-reason": "runtime_shutdown"
49619
- },
49620
- signal: AbortSignal.timeout(5e3)
49621
- });
49622
- }
49623
49613
  async function adoptLocalDaemonControl(input2) {
49624
49614
  const timeoutMs = input2.timeoutMs ?? 750;
49625
49615
  try {
@@ -76697,13 +76687,13 @@ var RuntimeRunJournal = class {
76697
76687
  * Keep the exact interruption outcome until a durable authenticated control
76698
76688
  * channel can prove both identity and ownership.
76699
76689
  */
76700
- assessRecovery(runId, isProcessAlive2) {
76690
+ assessRecovery(runId, isProcessAlive) {
76701
76691
  const entry = this.entries.get(runId);
76702
76692
  if (!entry) return void 0;
76703
76693
  if (entry.providerPid === void 0) {
76704
76694
  return { reattachable: false, reason: "provider_not_recorded" };
76705
76695
  }
76706
- if (!isProcessAlive2(entry.providerPid)) {
76696
+ if (!isProcessAlive(entry.providerPid)) {
76707
76697
  return {
76708
76698
  reattachable: false,
76709
76699
  reason: "provider_not_running",
@@ -80450,17 +80440,6 @@ async function runLocalDaemonRecoveryLadder(input2) {
80450
80440
  error: "The local daemon exhausted its reconnect and safe-restart recovery budget."
80451
80441
  };
80452
80442
  }
80453
- function writeRegisteredRuntimeConfig(args, previousConfig, registered) {
80454
- writeConfig({
80455
- ...registered,
80456
- localApiPort: getLocalApiPort(args, {}),
80457
- localApiToken: previousConfig.localApiToken ?? (0, import_node_crypto19.randomBytes)(24).toString("hex"),
80458
- localServiceId: previousConfig.localServiceId ?? (0, import_node_crypto19.randomUUID)(),
80459
- localServiceSecret: previousConfig.localServiceSecret ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80460
- eventSpoolKey: previousConfig.eventSpoolKey ?? (0, import_node_crypto19.randomBytes)(32).toString("base64url"),
80461
- daemonEpoch: previousConfig.daemonEpoch
80462
- });
80463
- }
80464
80443
  async function setupDaemon(args) {
80465
80444
  bindConfigPath(args);
80466
80445
  const previousConfig = readConfigForRegistration();
@@ -80521,7 +80500,7 @@ async function setupDaemon(args) {
80521
80500
  credentialGeneration: body2.credentialGeneration,
80522
80501
  configPath: getConfigPath()
80523
80502
  });
80524
- return body2.runtimeId !== liveConfig.runtimeId;
80503
+ return { daemonAlreadyRunning: true };
80525
80504
  }
80526
80505
  if (readLocalDaemonOwnerLease(getConfigPath())) {
80527
80506
  throw new Error(
@@ -80535,17 +80514,23 @@ async function setupDaemon(args) {
80535
80514
  if (setupToken && error61 instanceof RuntimeSetupGrantExpiredError) {
80536
80515
  console.info("[alan-agent] Setup link expired; continuing with browser authorization");
80537
80516
  await loginWithDeviceCode(args);
80538
- return false;
80517
+ return { daemonAlreadyRunning: false };
80539
80518
  }
80540
80519
  throw error61;
80541
80520
  }
80542
- writeRegisteredRuntimeConfig(args, previousConfig, {
80521
+ writeConfig({
80543
80522
  apiUrl,
80544
80523
  wsUrl,
80545
80524
  endpointProfile,
80546
80525
  runtimeId: body.runtimeId,
80547
80526
  runtimeToken: body.runtimeToken,
80548
80527
  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,
80549
80534
  displayName: body.displayName,
80550
80535
  installationId
80551
80536
  });
@@ -80554,7 +80539,7 @@ async function setupDaemon(args) {
80554
80539
  runtimeId: body.runtimeId,
80555
80540
  configPath: getConfigPath()
80556
80541
  });
80557
- return false;
80542
+ return { daemonAlreadyRunning: false };
80558
80543
  }
80559
80544
  async function loginWithDeviceCode(args) {
80560
80545
  bindConfigPath(args);
@@ -80643,13 +80628,19 @@ async function loginWithDeviceCode(args) {
80643
80628
  if (ownedPending?.operationId !== operation.id) {
80644
80629
  throw new Error("browser authorization sidecar ownership was lost before finalization");
80645
80630
  }
80646
- writeRegisteredRuntimeConfig(args, previousConfig, {
80631
+ writeConfig({
80647
80632
  apiUrl,
80648
80633
  wsUrl,
80649
80634
  endpointProfile,
80650
80635
  runtimeId: body.runtime.id,
80651
80636
  runtimeToken: body.runtimeToken,
80652
80637
  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,
80653
80644
  displayName: body.runtime.displayName ?? displayName,
80654
80645
  installationId
80655
80646
  });
@@ -80688,7 +80679,14 @@ async function stopDaemon(args) {
80688
80679
  console.info("[alan-agent] No daemon listening", { port: localApiPort });
80689
80680
  return;
80690
80681
  }
80691
- const response = await requestLocalDaemonShutdown(localApiPort, localApiToken);
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
+ });
80692
80690
  if (!response.ok) {
80693
80691
  throw new Error(`Failed to stop daemon: HTTP ${response.status}`);
80694
80692
  }
@@ -80833,7 +80831,14 @@ async function drainAndStopLocalDaemonBeforeCredentialCleanup(args, stored) {
80833
80831
  `Logout could not safely drain the local daemon (HTTP ${prepare.status}). Stop or update the local service before removing credentials.`
80834
80832
  );
80835
80833
  }
80836
- const shutdown = await requestLocalDaemonShutdown(localApiPort, localApiToken);
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
+ });
80837
80842
  if (!shutdown.ok) {
80838
80843
  throw new Error(`Logout could not stop the local daemon (HTTP ${shutdown.status}).`);
80839
80844
  }
@@ -80994,9 +80999,6 @@ async function printDoctor(args = []) {
80994
80999
  );
80995
81000
  }
80996
81001
 
80997
- // src/install-command.ts
80998
- var import_promises6 = require("timers/promises");
80999
-
81000
81002
  // src/mcp-config-adapters.ts
81001
81003
  var import_node_crypto20 = require("crypto");
81002
81004
  var import_node_fs23 = require("fs");
@@ -82381,14 +82383,12 @@ function runMcpIntegrationCommand(args, options = {}) {
82381
82383
 
82382
82384
  // src/service-manager.ts
82383
82385
  var import_node_child_process9 = require("child_process");
82384
- var import_node_crypto21 = require("crypto");
82385
82386
  var import_node_fs25 = require("fs");
82386
82387
  var import_node_os16 = require("os");
82387
82388
  var import_node_path27 = require("path");
82388
82389
  var SERVICE_LABEL = "ai.tryalan.agent";
82389
82390
  var SYSTEMD_UNIT = "alan-agent.service";
82390
82391
  var WINDOWS_TASK = "Alan Agent";
82391
- var STOP_POLL_INTERVAL_MS = 200;
82392
82392
  function xml(value2) {
82393
82393
  return value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
82394
82394
  }
@@ -82398,18 +82398,6 @@ function systemdArg(value2) {
82398
82398
  function powershellLiteral(value2) {
82399
82399
  return `'${value2.replaceAll("'", "''")}'`;
82400
82400
  }
82401
- function powershellCommand(script, tolerateFailure = false) {
82402
- return {
82403
- command: "powershell.exe",
82404
- args: ["-NoProfile", "-NonInteractive", "-Command", script],
82405
- tolerateFailure
82406
- };
82407
- }
82408
- function windowsCommandLineArgument(value2) {
82409
- if (value2 && !/[\s"]/u.test(value2)) return value2;
82410
- const escaped = value2.replace(/(\\*)"/gu, (_match, slashes) => `${slashes}${slashes}\\"`).replace(/\\+$/u, (slashes) => `${slashes}${slashes}`);
82411
- return `"${escaped}"`;
82412
- }
82413
82401
  function serviceNames(profile) {
82414
82402
  if (profile === "production") {
82415
82403
  return { launchd: SERVICE_LABEL, systemd: SYSTEMD_UNIT, windows: WINDOWS_TASK };
@@ -82431,7 +82419,7 @@ function buildDaemonServicePlan(input2) {
82431
82419
  const domain2 = `gui/${userId}`;
82432
82420
  const serviceTarget = `${domain2}/${names.launchd}`;
82433
82421
  const manifestPath = (0, import_node_path27.join)(input2.homeDir, "Library", "LaunchAgents", `${names.launchd}.plist`);
82434
- const logDir2 = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
82422
+ const logDir = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
82435
82423
  const programArguments = daemonArgs.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
82436
82424
  const environmentEntries = Object.entries(input2.environment ?? {}).map(([key, value2]) => ` <key>${xml(key)}</key>
82437
82425
  <string>${xml(value2)}</string>`).join("\n");
@@ -82460,9 +82448,9 @@ ${environmentBlock} <key>RunAtLoad</key>
82460
82448
  <key>ThrottleInterval</key>
82461
82449
  <integer>5</integer>
82462
82450
  <key>StandardOutPath</key>
82463
- <string>${xml((0, import_node_path27.join)(logDir2, "daemon.log"))}</string>
82451
+ <string>${xml((0, import_node_path27.join)(logDir, "daemon.log"))}</string>
82464
82452
  <key>StandardErrorPath</key>
82465
- <string>${xml((0, import_node_path27.join)(logDir2, "daemon-error.log"))}</string>
82453
+ <string>${xml((0, import_node_path27.join)(logDir, "daemon-error.log"))}</string>
82466
82454
  </dict>
82467
82455
  </plist>
82468
82456
  `;
@@ -82470,7 +82458,7 @@ ${environmentBlock} <key>RunAtLoad</key>
82470
82458
  platform: "darwin",
82471
82459
  manifestPath,
82472
82460
  manifest,
82473
- logDirectory: logDir2,
82461
+ logDirectory: logDir,
82474
82462
  loadDefinitionCommands: [
82475
82463
  {
82476
82464
  command: "launchctl",
@@ -82511,6 +82499,23 @@ ${environmentBlock} <key>RunAtLoad</key>
82511
82499
  tolerateFailure: true
82512
82500
  }
82513
82501
  ],
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
+ ],
82514
82519
  uninstallCommands: [
82515
82520
  {
82516
82521
  command: "launchctl",
@@ -82527,7 +82532,8 @@ ${environmentBlock} <key>RunAtLoad</key>
82527
82532
  command: "launchctl",
82528
82533
  args: ["print", serviceTarget],
82529
82534
  tolerateFailure: true
82530
- }
82535
+ },
82536
+ lingerStatusCommand: null
82531
82537
  };
82532
82538
  }
82533
82539
  if (input2.platform === "linux") {
@@ -82590,6 +82596,18 @@ WantedBy=default.target
82590
82596
  tolerateFailure: true
82591
82597
  }
82592
82598
  ],
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
+ ],
82593
82611
  uninstallCommands: [
82594
82612
  {
82595
82613
  command: "systemctl",
@@ -82611,179 +82629,117 @@ WantedBy=default.target
82611
82629
  command: "systemctl",
82612
82630
  args: ["--user", "is-active", names.systemd],
82613
82631
  tolerateFailure: true
82632
+ },
82633
+ lingerStatusCommand: {
82634
+ command: "loginctl",
82635
+ args: ["show-user", "$(id -un)", "-p", "Linger"],
82636
+ tolerateFailure: true
82614
82637
  }
82615
82638
  };
82616
82639
  }
82617
- const logDir = (0, import_node_path27.join)(resolveAgentConfigDirectory(input2.profile, input2.homeDir), "logs");
82618
- const legacyEnvironmentPrefix = Object.entries(input2.environment ?? {}).map(([key, value2]) => `$env:${key} = ${powershellLiteral(value2)}`).join("; ");
82619
- const legacyDirectArguments = [input2.cliEntry, "daemon", "--profile", input2.profile].map((part) => part.includes(" ") ? `"${part}"` : part).join(" ");
82620
- const legacyExecutable = legacyEnvironmentPrefix ? "powershell.exe" : input2.nodeExecutable;
82621
- const legacyArguments = legacyEnvironmentPrefix ? `-NoProfile -NonInteractive -WindowStyle Hidden -Command "${legacyEnvironmentPrefix}; & ${powershellLiteral(input2.nodeExecutable)} ${powershellLiteral(input2.cliEntry)} daemon --profile ${input2.profile}"` : legacyDirectArguments;
82622
- const daemonArguments = [input2.cliEntry, "daemon", "--profile", input2.profile].map(windowsCommandLineArgument).join(" ");
82623
- const environmentStatements = Object.entries(input2.environment ?? {}).map(
82624
- ([key, value2]) => `$startInfo.EnvironmentVariables[${powershellLiteral(key)}] = ${powershellLiteral(value2)}`
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
82625
82645
  );
82626
- const launcherScript = [
82627
- "$ErrorActionPreference = 'Stop'",
82628
- "$startInfo = [System.Diagnostics.ProcessStartInfo]::new()",
82629
- `$startInfo.FileName = ${powershellLiteral(input2.nodeExecutable)}`,
82630
- `$startInfo.Arguments = ${powershellLiteral(daemonArguments)}`,
82631
- `$startInfo.WorkingDirectory = ${powershellLiteral((0, import_node_path27.dirname)(input2.cliEntry))}`,
82632
- "$startInfo.UseShellExecute = $false",
82633
- "$startInfo.CreateNoWindow = $true",
82634
- "$startInfo.RedirectStandardOutput = $true",
82635
- "$startInfo.RedirectStandardError = $true",
82636
- ...environmentStatements,
82637
- `$stdout = [System.IO.File]::Open(${powershellLiteral((0, import_node_path27.join)(logDir, "daemon-launcher.log"))}, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)`,
82638
- `$stderr = [System.IO.File]::Open(${powershellLiteral((0, import_node_path27.join)(logDir, "daemon-launcher-error.log"))}, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)`,
82639
- "$process = [System.Diagnostics.Process]::new()",
82640
- "$process.StartInfo = $startInfo",
82641
- "try { if (-not $process.Start()) { throw 'Alan daemon process did not start' }; $stdoutCopy = $process.StandardOutput.BaseStream.CopyToAsync($stdout); $stderrCopy = $process.StandardError.BaseStream.CopyToAsync($stderr); $process.WaitForExit(); [System.Threading.Tasks.Task]::WaitAll([System.Threading.Tasks.Task[]]@($stdoutCopy, $stderrCopy)); $exitCode = $process.ExitCode } finally { $stdout.Dispose(); $stderr.Dispose(); $process.Dispose() }",
82642
- "exit $exitCode"
82643
- ].join("; ");
82644
- const executable = powershellLiteral("powershell.exe");
82645
- const launcherArguments = `-NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand ${Buffer.from(launcherScript, "utf16le").toString("base64")}`;
82646
- const settingsScript = "New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries";
82647
- const ownerMarker = `Alan daemon owner v1 ${(0, import_node_crypto21.createHash)("sha256").update(
82648
- JSON.stringify({
82649
- executable: input2.nodeExecutable,
82650
- cliEntry: input2.cliEntry,
82651
- profile: input2.profile,
82652
- taskBaseName: names.windows
82653
- })
82654
- ).digest("hex")}`;
82655
- const definitionMarker = `${ownerMarker}; definition v3 ${(0, import_node_crypto21.createHash)("sha256").update(
82656
- JSON.stringify({
82657
- executable: "powershell.exe",
82658
- launcherArguments,
82659
- workingDirectory: (0, import_node_path27.dirname)(input2.cliEntry),
82660
- settingsScript,
82661
- taskBaseName: names.windows
82662
- })
82663
- ).digest("hex")}`;
82664
- const windowsDefinitionContext = [
82665
- `$expectedLauncherArguments = ${powershellLiteral(launcherArguments)}`,
82666
- `$expectedWorkingDirectory = ${powershellLiteral((0, import_node_path27.dirname)(input2.cliEntry))}`,
82667
- `$expectedOwnerMarker = ${powershellLiteral(`${ownerMarker};`)}`,
82668
- `$expectedDefinitionMarker = ${powershellLiteral(definitionMarker)}`,
82669
- `$expectedLegacyExecutable = ${powershellLiteral(legacyExecutable)}`,
82670
- `$expectedLegacyArguments = ${powershellLiteral(legacyArguments)}`
82671
- ].join("; ");
82672
- const taskContext = [
82673
- "$currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()",
82674
- "$currentUser = $currentIdentity.Name",
82675
- "$currentSid = $currentIdentity.User.Value",
82676
- `$taskName = ${powershellLiteral(names.windows)} + ' ' + $currentSid`
82677
- ].join("; ");
82678
- const resolveTaskSid = "function Resolve-TaskSid([string]$value) { try { return ([System.Security.Principal.NTAccount]$value).Translate([System.Security.Principal.SecurityIdentifier]).Value } catch { try { return [System.Security.Principal.SecurityIdentifier]::new($value).Value } catch { return '' } } }";
82679
- const currentLookup = "$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]";
82680
- const checkCurrentOwnership = [
82681
- currentLookup,
82682
- "$currentOwned = $false",
82683
- "if ($null -ne $task) { try {",
82684
- "$taskAction = @($task.Actions)[0]",
82685
- "$currentOwned = @($task.Actions).Count -eq 1 -and (Resolve-TaskSid ([string]$task.Principal.UserId)) -eq $currentSid -and (([string]$task.Description).StartsWith($expectedOwnerMarker, [System.StringComparison]::Ordinal) -or (([string]$taskAction.Execute) -ieq 'powershell.exe' -and ([string]$taskAction.Arguments) -ceq $expectedLauncherArguments -and ([string]$taskAction.WorkingDirectory) -ieq $expectedWorkingDirectory))",
82686
- "} catch { $currentOwned = $false } }"
82687
- ].join("; ");
82688
- const legacyLookup = `$legacyTask = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq ${powershellLiteral(names.windows)} })[0]`;
82689
- const checkLegacyOwnership = [
82690
- legacyLookup,
82691
- "$legacyOwned = $false",
82692
- "$legacyState = 'Missing'",
82693
- "if ($null -ne $legacyTask) { try {",
82694
- "$legacyState = [string]$legacyTask.State",
82695
- "$legacySid = Resolve-TaskSid ([string]$legacyTask.Principal.UserId)",
82696
- "$legacyAction = @($legacyTask.Actions)[0]",
82697
- "if ($null -ne $legacyAction) {",
82698
- "$legacyOwned = $legacySid -eq $currentSid -and @($legacyTask.Actions).Count -eq 1 -and ((([string]$legacyAction.Execute) -ieq $expectedLegacyExecutable -and ([string]$legacyAction.Arguments) -ieq $expectedLegacyArguments -and ([string]$legacyAction.WorkingDirectory) -eq '') -or (([string]$legacyAction.Execute) -ieq 'powershell.exe' -and ([string]$legacyAction.Arguments) -ceq $expectedLauncherArguments -and ([string]$legacyAction.WorkingDirectory) -ieq $expectedWorkingDirectory))",
82699
- "}",
82700
- "} catch { $legacyOwned = $false } }"
82701
- ].join("; ");
82702
- const assertOwnedTasks = [
82703
- checkCurrentOwnership,
82704
- checkLegacyOwnership,
82705
- `if ($null -ne $task -and -not $currentOwned) { throw ${powershellLiteral(`${names.windows} task is not owned by this Alan installation`)} }`,
82706
- `if ($null -ne $legacyTask -and -not $legacyOwned) { throw ${powershellLiteral(`Legacy ${names.windows} task is not owned by this Alan installation`)} }`,
82707
- "if ($null -ne $task -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$task.State)) { throw 'Alan daemon task became active or has unknown state' }",
82708
- "if ($null -ne $legacyTask -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$legacyTask.State)) { throw 'Legacy Alan daemon task became active or has unknown state' }"
82709
- ].join("; ");
82710
- const cleanupLegacyTask = [
82711
- checkLegacyOwnership,
82712
- `if ($null -ne $legacyTask -and -not $legacyOwned) { throw ${powershellLiteral(`Legacy ${names.windows} task is not owned by this Alan installation`)} }`,
82713
- "if ($null -ne $legacyTask -and @('Ready', 'Disabled', 'Queued') -notcontains ([string]$legacyTask.State)) { throw 'Legacy Alan daemon task became active or has unknown state' }",
82714
- `if ($null -ne $legacyTask) { Unregister-ScheduledTask -TaskName ${powershellLiteral(names.windows)} -TaskPath '\\' -Confirm:$false -ErrorAction Stop }`,
82715
- `$legacyRemaining = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq ${powershellLiteral(names.windows)} })[0]`,
82716
- "if ($null -ne $legacyRemaining) { throw 'Legacy Alan daemon task still exists after migration' }"
82717
- ].join("; ");
82718
82646
  const registerScript = [
82719
- taskContext,
82720
- resolveTaskSid,
82721
- windowsDefinitionContext,
82722
- `$action = New-ScheduledTaskAction -Execute ${executable} -Argument $expectedLauncherArguments -WorkingDirectory $expectedWorkingDirectory`,
82647
+ "$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name",
82648
+ `$action = New-ScheduledTaskAction -Execute ${executable} -Argument ${argument}`,
82723
82649
  "$trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser",
82724
82650
  "$principal = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Limited",
82725
- `$settings = ${settingsScript}`,
82726
- assertOwnedTasks,
82727
- "Register-ScheduledTask -TaskName $taskName -Description $expectedDefinitionMarker -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force -ErrorAction Stop | Out-Null",
82728
- cleanupLegacyTask,
82729
- "$registeredTask = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
82730
- "if ($null -eq $registeredTask -or $registeredTask.Description -cne $expectedDefinitionMarker) { throw 'Alan daemon task definition was not applied' }"
82731
- ].join("; ");
82732
- const uninstallScript = [
82733
- taskContext,
82734
- resolveTaskSid,
82735
- windowsDefinitionContext,
82736
- assertOwnedTasks,
82737
- "if ($null -ne $legacyTask) { Unregister-ScheduledTask -TaskName $legacyTask.TaskName -TaskPath $legacyTask.TaskPath -Confirm:$false -ErrorAction Stop }",
82738
- "$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
82739
- "if ($null -ne $task) { Unregister-ScheduledTask -TaskName $taskName -TaskPath '\\' -Confirm:$false -ErrorAction Stop }",
82740
- "$remaining = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and ($_.TaskName -eq $taskName -or $_.TaskName -eq $legacyTask.TaskName) })",
82741
- "if ($remaining.Count -gt 0) { throw 'Alan daemon task still exists after unregister' }"
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`
82742
82653
  ].join("; ");
82743
- const statusScript = [
82744
- taskContext,
82745
- resolveTaskSid,
82746
- windowsDefinitionContext,
82747
- checkCurrentOwnership,
82748
- checkLegacyOwnership,
82749
- "$definitionMatches = $false",
82750
- "if ($null -eq $task) { Write-Output 'State: Missing' } else {",
82751
- "$taskTrigger = @($task.Triggers)[0]",
82752
- "$definitionMatches = @($task.Actions).Count -eq 1 -and @($task.Triggers).Count -eq 1 -and $task.Description -ceq $expectedDefinitionMarker -and (Resolve-TaskSid ([string]$task.Principal.UserId)) -eq $currentSid -and [string]$task.Principal.LogonType -eq 'Interactive' -and [string]$task.Principal.RunLevel -eq 'Limited' -and [string]$taskAction.Execute -ieq 'powershell.exe' -and [string]$taskAction.Arguments -ceq $expectedLauncherArguments -and [string]$taskAction.WorkingDirectory -ieq $expectedWorkingDirectory -and $taskTrigger.CimClass.CimClassName -eq 'MSFT_TaskLogonTrigger' -and (Resolve-TaskSid ([string]$taskTrigger.UserId)) -eq $currentSid -and -not $task.Settings.DisallowStartIfOnBatteries -and -not $task.Settings.StopIfGoingOnBatteries -and $task.Settings.RestartCount -eq 3 -and [string]$task.Settings.RestartInterval -eq 'PT1M' -and [string]$task.Settings.ExecutionTimeLimit -eq 'PT0S'",
82753
- "Write-Output ('TaskName: ' + $task.TaskName); Write-Output ('State: ' + $task.State); Write-Output ('Definition: ' + $task.Description)",
82754
- "}",
82755
- "Write-Output ('DefinitionMatch: ' + $definitionMatches)",
82756
- "Write-Output ('CurrentOwned: ' + $currentOwned)",
82757
- "$legacyPresent = $null -ne $legacyTask",
82758
- "Write-Output ('LegacyTask: ' + $legacyPresent)",
82759
- "Write-Output ('LegacyOwned: ' + $legacyOwned)",
82760
- "Write-Output ('LegacyState: ' + $legacyState)"
82761
- ].join("; ");
82762
- const activeScript = [
82763
- taskContext,
82764
- "$task = @(Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskPath -eq '\\' -and $_.TaskName -eq $taskName })[0]",
82765
- "if ($null -eq $task) { Write-Output 'Missing' } else { Write-Output $task.State }"
82766
- ].join("; ");
82767
- const registerCommand = powershellCommand(registerScript);
82768
82654
  return {
82769
82655
  platform: "win32",
82770
82656
  manifestPath: null,
82771
82657
  manifest: null,
82772
- logDirectory: logDir,
82773
- loadDefinitionCommands: [registerCommand],
82774
- reloadDefinitionCommands: [registerCommand],
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
+ ],
82775
82673
  enableCommands: [],
82776
- startCommands: [powershellCommand(`${taskContext}; Start-ScheduledTask -TaskName $taskName`)],
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
+ ],
82777
82686
  stopCommands: [
82778
- powershellCommand(
82779
- `${taskContext}; Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue`,
82780
- true
82781
- )
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
+ }
82782
82709
  ],
82783
- uninstallCommands: [powershellCommand(uninstallScript)],
82784
- statusCommand: powershellCommand(statusScript, true),
82785
- isActiveCommand: powershellCommand(activeScript, true),
82786
- definitionMarker
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
+ }
82721
+ ],
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
82787
82743
  };
82788
82744
  }
82789
82745
  function resolveCurrentPlatform() {
@@ -82793,14 +82749,14 @@ function resolveCurrentPlatform() {
82793
82749
  }
82794
82750
  return currentPlatform;
82795
82751
  }
82796
- function currentServicePlan(args, runtime, options = {}) {
82797
- const currentPlatform = options.platform ?? resolveCurrentPlatform();
82752
+ function currentServicePlan(args, runtime) {
82753
+ const currentPlatform = resolveCurrentPlatform();
82798
82754
  const profile = resolveEndpointProfileFromArgs(args);
82799
82755
  const cliEntry = runtime?.cliEntry ?? process.argv[1];
82800
82756
  if (!cliEntry) throw new Error("Cannot determine the alan-agent executable path");
82801
82757
  return buildDaemonServicePlan({
82802
82758
  platform: currentPlatform,
82803
- homeDir: options.homeDir ?? (0, import_node_os16.homedir)(),
82759
+ homeDir: (0, import_node_os16.homedir)(),
82804
82760
  nodeExecutable: runtime?.executable ?? process.execPath,
82805
82761
  cliEntry: (0, import_node_path27.resolve)(cliEntry),
82806
82762
  profile,
@@ -82823,13 +82779,9 @@ function defaultReadManifest(path2) {
82823
82779
  return (0, import_node_fs25.readFileSync)(path2, "utf8");
82824
82780
  }
82825
82781
  function runServiceCommand(command) {
82826
- const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, {
82827
- encoding: "utf8",
82828
- shell: false,
82829
- ...(0, import_node_os16.platform)() === "win32" ? { timeout: 15e3, windowsHide: true } : {}
82830
- });
82782
+ const result = (0, import_node_child_process9.spawnSync)(command.command, command.args, { encoding: "utf8", shell: false });
82831
82783
  const status = result.status ?? 1;
82832
- const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}${result.error?.message ?? ""}`.trim();
82784
+ const output2 = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
82833
82785
  if (status !== 0 && !command.tolerateFailure) {
82834
82786
  throw new Error(
82835
82787
  `Service command failed (${command.command} ${command.args.join(" ")}): ${output2 || `exit ${status}`}`
@@ -82842,23 +82794,6 @@ function defaultSleep(ms) {
82842
82794
  setTimeout(resolveSleep, ms);
82843
82795
  });
82844
82796
  }
82845
- function isProcessAlive(pid) {
82846
- if (pid <= 0) return false;
82847
- try {
82848
- process.kill(pid, 0);
82849
- return true;
82850
- } catch (error61) {
82851
- return error61.code === "EPERM";
82852
- }
82853
- }
82854
- async function requestWindowsDaemonShutdown(owner) {
82855
- try {
82856
- const response = await requestLocalDaemonShutdown(owner.localApiPort, owner.localApiToken);
82857
- return response.ok ? "accepted" : "rejected";
82858
- } catch {
82859
- return "unreachable";
82860
- }
82861
- }
82862
82797
  function parseServiceHostState(servicePlatform, result) {
82863
82798
  const output2 = result.output;
82864
82799
  if (servicePlatform === "linux") {
@@ -82884,62 +82819,9 @@ function parseServiceHostState(servicePlatform, result) {
82884
82819
  const stateMatch = output2.match(/\b(?:State|state)\s*[:=]\s*(\w+)/);
82885
82820
  const state = (stateMatch?.[1] ?? output2.trim()).toLowerCase();
82886
82821
  if (state === "running") return "running";
82887
- if (state === "queued") return "starting";
82888
- if (state === "ready" || state === "disabled") return "stopped";
82822
+ if (state === "ready" || state === "disabled" || state === "queued") return "stopped";
82889
82823
  return "unknown";
82890
82824
  }
82891
- function parseWindowsTaskInspection(result) {
82892
- if (result.status !== 0) {
82893
- throw new Error("Cannot inspect Windows daemon task definition");
82894
- }
82895
- const value2 = (name) => {
82896
- const match = result.output.match(new RegExp(`^${name}:\\s*(.*)$`, "imu"));
82897
- if (!match) throw new Error(`Incomplete Windows daemon task inspection: missing ${name}`);
82898
- return match[1]?.trim() ?? "";
82899
- };
82900
- const boolean4 = (name) => {
82901
- const parsed = value2(name).toLowerCase();
82902
- if (parsed === "true") return true;
82903
- if (parsed === "false") return false;
82904
- throw new Error(`Incomplete Windows daemon task inspection: invalid ${name}`);
82905
- };
82906
- const currentPresent = /^TaskName:\s*.+$/imu.test(result.output);
82907
- const currentStateValue = value2("State");
82908
- const currentOwned = boolean4("CurrentOwned");
82909
- const definitionMatches = boolean4("DefinitionMatch");
82910
- const legacyPresent = boolean4("LegacyTask");
82911
- const legacyOwned = boolean4("LegacyOwned");
82912
- const legacyStateValue = value2("LegacyState");
82913
- if (currentPresent !== (currentStateValue.toLowerCase() !== "missing")) {
82914
- throw new Error("Incomplete Windows daemon task inspection: inconsistent current task state");
82915
- }
82916
- if (!currentPresent && (currentOwned || definitionMatches)) {
82917
- throw new Error(
82918
- "Incomplete Windows daemon task inspection: absent current task is marked owned"
82919
- );
82920
- }
82921
- if (legacyPresent !== (legacyStateValue.toLowerCase() !== "missing") || !legacyPresent && legacyOwned) {
82922
- throw new Error("Incomplete Windows daemon task inspection: inconsistent legacy task state");
82923
- }
82924
- return {
82925
- currentPresent,
82926
- currentOwned,
82927
- currentState: currentPresent ? parseServiceHostState("win32", { status: 0, output: currentStateValue }) : "stopped",
82928
- definitionMatches,
82929
- definitionMarker: result.output.match(/^Definition:\s*(.+)$/imu)?.[1]?.trim(),
82930
- legacyPresent,
82931
- legacyOwned,
82932
- legacyState: legacyPresent ? parseServiceHostState("win32", { status: 0, output: legacyStateValue }) : "stopped"
82933
- };
82934
- }
82935
- function assertWindowsTaskOwnership(tasks) {
82936
- if (tasks.currentPresent && !tasks.currentOwned) {
82937
- throw new Error("Windows daemon task is not owned by this Alan installation");
82938
- }
82939
- if (tasks.legacyPresent && !tasks.legacyOwned) {
82940
- throw new Error("Legacy Windows daemon task is not owned by this Alan installation");
82941
- }
82942
- }
82943
82825
  function resolveEnsureDefinitionOutcome(input2) {
82944
82826
  if (!input2.definitionChanged) {
82945
82827
  return { changed: false, updatePending: false };
@@ -82949,6 +82831,9 @@ function resolveEnsureDefinitionOutcome(input2) {
82949
82831
  }
82950
82832
  return { changed: true, updatePending: false };
82951
82833
  }
82834
+ function shouldStartOnInstall(hostState) {
82835
+ return hostState === "stopped";
82836
+ }
82952
82837
  function resolveUsername(options) {
82953
82838
  if (options?.username) return options.username;
82954
82839
  try {
@@ -82957,14 +82842,17 @@ function resolveUsername(options) {
82957
82842
  return process.env.USER || process.env.USERNAME || "unknown";
82958
82843
  }
82959
82844
  }
82960
- function assertSystemdUserLingerEnabled(options = {}) {
82961
- const username = resolveUsername(options);
82962
- const run = options.runCommand ?? runServiceCommand;
82963
- const result = run({
82845
+ function buildLingerStatusCommand(username) {
82846
+ return {
82964
82847
  command: "loginctl",
82965
82848
  args: ["show-user", username, "-p", "Linger"],
82966
82849
  tolerateFailure: true
82967
- });
82850
+ };
82851
+ }
82852
+ function assertSystemdUserLingerEnabled(options = {}) {
82853
+ const username = resolveUsername(options);
82854
+ const run = options.runCommand ?? runServiceCommand;
82855
+ const result = run(buildLingerStatusCommand(username));
82968
82856
  if (result.status !== 0) {
82969
82857
  throw new Error(
82970
82858
  `Cannot verify systemd user linger for ${username} (loginctl failed). For durable headless VM boot run: sudo loginctl enable-linger ${username}`
@@ -82978,9 +82866,9 @@ function assertSystemdUserLingerEnabled(options = {}) {
82978
82866
  }
82979
82867
  function createHostContext(options = {}) {
82980
82868
  const args = options.args ?? [];
82981
- const plan = currentServicePlan(args, options.runtime, options);
82869
+ const plan = currentServicePlan(args, options.runtime);
82982
82870
  const run = options.runCommand ?? runServiceCommand;
82983
- const sleep5 = options.sleep ?? defaultSleep;
82871
+ const sleep4 = options.sleep ?? defaultSleep;
82984
82872
  const nowMs = options.nowMs ?? Date.now;
82985
82873
  const readManifest = options.readManifest ?? defaultReadManifest;
82986
82874
  const writeManifest = options.writeManifest ?? writeManifestAtomic;
@@ -82992,20 +82880,13 @@ function createHostContext(options = {}) {
82992
82880
  args,
82993
82881
  plan,
82994
82882
  run,
82995
- sleep: sleep5,
82883
+ sleep: sleep4,
82996
82884
  nowMs,
82997
82885
  readManifest,
82998
82886
  writeManifest,
82999
82887
  removeManifest,
83000
82888
  ensureLogDir,
83001
- configPath: options.configPath ?? resolveAgentConfigPath({ profile: resolveEndpointProfileFromArgs(args) }),
83002
- readOwner: options.readOwner ?? readLocalDaemonOwnerLease,
83003
- isProcessAlive: options.isProcessAlive ?? isProcessAlive,
83004
- probeEndpoint: options.probeEndpoint ?? probeLocalDaemonEndpoint,
83005
- requestWindowsShutdown: options.requestWindowsShutdown ?? requestWindowsDaemonShutdown,
83006
- inspectJournal: options.inspectJournal ?? inspectLocalDaemonRunJournal,
83007
- acquireMaintenance: options.acquireMaintenance ?? acquireLocalDaemonMaintenanceLease,
83008
- releaseMaintenance: options.releaseMaintenance ?? releaseLocalDaemonMaintenanceLease
82889
+ profile: resolveEndpointProfileFromArgs(args)
83009
82890
  };
83010
82891
  }
83011
82892
  function inspectDaemonService(options = {}) {
@@ -83021,9 +82902,8 @@ function inspectDaemonService(options = {}) {
83021
82902
  }
83022
82903
  function ensureDaemonServiceDefinition(options = {}) {
83023
82904
  const ctx = createHostContext(options);
83024
- const { plan, run, nowMs, readManifest, writeManifest, ensureLogDir } = ctx;
82905
+ const { plan, run, readManifest, writeManifest, ensureLogDir } = ctx;
83025
82906
  let definitionChanged = false;
83026
- let inspection;
83027
82907
  if (plan.manifestPath && plan.manifest) {
83028
82908
  ensureLogDir();
83029
82909
  const existing = readManifest(plan.manifestPath);
@@ -83032,38 +82912,20 @@ function ensureDaemonServiceDefinition(options = {}) {
83032
82912
  writeManifest(plan.manifestPath, plan.manifest);
83033
82913
  }
83034
82914
  } else if (plan.platform === "win32") {
83035
- ensureLogDir();
83036
- const result = run(plan.statusCommand);
83037
- const tasks = parseWindowsTaskInspection(result);
83038
- assertWindowsTaskOwnership(tasks);
83039
- if (tasks.currentPresent && tasks.currentState === "unknown" || tasks.legacyPresent && tasks.legacyState === "unknown") {
83040
- throw new Error("Cannot safely update a Windows daemon task with unknown state");
83041
- }
83042
- definitionChanged = tasks.definitionMarker !== plan.definitionMarker || !tasks.definitionMatches || tasks.legacyPresent;
83043
- const running = tasks.currentState === "running" || tasks.legacyState === "running";
83044
- inspection = {
83045
- state: running ? "running" : tasks.currentState,
83046
- platform: plan.platform,
83047
- detail: result.output || void 0,
83048
- observedAtMs: nowMs()
83049
- };
82915
+ definitionChanged = true;
83050
82916
  }
83051
- inspection ??= inspectDaemonService(options);
83052
- const definitionHostState = plan.platform === "win32" && inspection.state === "starting" ? "stopped" : inspection.state;
82917
+ const inspection = inspectDaemonService(options);
83053
82918
  const outcome = resolveEnsureDefinitionOutcome({
83054
82919
  definitionChanged,
83055
- hostState: definitionHostState
82920
+ hostState: inspection.state
83056
82921
  });
83057
82922
  if (outcome.updatePending) {
83058
82923
  return outcome;
83059
82924
  }
83060
- if (plan.platform === "win32" && !outcome.changed) {
82925
+ if (inspection.state === "running" || inspection.state === "starting") {
83061
82926
  return outcome;
83062
82927
  }
83063
- if (definitionHostState === "running" || definitionHostState === "starting") {
83064
- return outcome;
83065
- }
83066
- const definitionCommands = definitionHostState === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
82928
+ const definitionCommands = inspection.state === "stopped" ? plan.reloadDefinitionCommands : plan.loadDefinitionCommands;
83067
82929
  for (const command of definitionCommands) run(command);
83068
82930
  return outcome;
83069
82931
  }
@@ -83075,140 +82937,10 @@ function startDaemonService(options = {}) {
83075
82937
  const { plan, run } = createHostContext(options);
83076
82938
  for (const command of plan.startCommands) run(command);
83077
82939
  }
83078
- async function stopDaemonServiceAndWait(identity, timeoutMs, options = {}) {
83079
- const {
83080
- plan,
83081
- run,
83082
- sleep: sleep5,
83083
- nowMs,
83084
- configPath,
83085
- readOwner,
83086
- isProcessAlive: processIsAlive,
83087
- probeEndpoint,
83088
- requestWindowsShutdown: requestShutdown
83089
- } = createHostContext(options);
83090
- let owner;
83091
- try {
83092
- owner = readOwner(configPath);
83093
- } catch {
83094
- return "identity_changed";
83095
- }
83096
- const ownerMatches = (candidate) => candidate.pid === identity.pid && candidate.daemonSessionId === identity.daemonSessionId && candidate.localApiPort === identity.port && (identity.serviceId === void 0 || candidate.localServiceId === identity.serviceId) && (identity.startedAtMs === void 0 || candidate.startedAtMs === identity.startedAtMs);
83097
- if (!owner || !ownerMatches(owner)) {
83098
- return "identity_changed";
83099
- }
83100
- if (plan.platform === "win32") {
83101
- const shutdown = await requestShutdown(owner);
83102
- if (shutdown === "rejected") return "identity_changed";
83103
- if (shutdown === "unreachable") {
83104
- for (const command of plan.stopCommands) run(command);
83105
- }
83106
- const deadline2 = nowMs() + timeoutMs;
83107
- while (nowMs() < deadline2) {
83108
- let currentOwner;
83109
- try {
83110
- currentOwner = readOwner(configPath);
83111
- } catch {
83112
- return "identity_changed";
83113
- }
83114
- if (currentOwner && !ownerMatches(currentOwner)) {
83115
- return "identity_changed";
83116
- }
83117
- if (!processIsAlive(identity.pid)) {
83118
- const endpoint = await probeEndpoint(identity.port, owner.localApiToken, 250);
83119
- if (endpoint.state === "absent") return "exited";
83120
- if (endpoint.state === "listening_unauthorized" || endpoint.status.daemonSessionId !== identity.daemonSessionId) {
83121
- return "identity_changed";
83122
- }
83123
- }
83124
- await sleep5(STOP_POLL_INTERVAL_MS);
83125
- }
83126
- return "timeout";
83127
- }
83128
- for (const command of plan.stopCommands) run(command);
83129
- const deadline = nowMs() + timeoutMs;
83130
- while (nowMs() < deadline) {
83131
- const result = run(plan.isActiveCommand);
83132
- const state = parseServiceHostState(plan.platform, result);
83133
- if (state === "stopped") return "exited";
83134
- if (state === "unknown") {
83135
- } else if (identity.pid > 0) {
83136
- try {
83137
- process.kill(identity.pid, 0);
83138
- } catch {
83139
- return "exited";
83140
- }
83141
- }
83142
- await sleep5(STOP_POLL_INTERVAL_MS);
83143
- }
83144
- return "timeout";
83145
- }
83146
- async function uninstallDaemonService(args = [], options = {}) {
82940
+ function uninstallDaemonService(args = [], options = {}) {
83147
82941
  const ctx = createHostContext({ ...options, args: options.args ?? args });
83148
- if (ctx.plan.platform === "win32") {
83149
- const ownership = ctx.run(ctx.plan.statusCommand);
83150
- let tasks;
83151
- try {
83152
- tasks = parseWindowsTaskInspection(ownership);
83153
- } catch (error61) {
83154
- throw new Error(
83155
- `Cannot inspect Windows daemon task ownership before uninstall: ${error61 instanceof Error ? error61.message : String(error61)}`
83156
- );
83157
- }
83158
- assertWindowsTaskOwnership(tasks);
83159
- if (!tasks.currentPresent && !tasks.legacyPresent) {
83160
- console.info("[alan-agent] Per-user daemon service removed");
83161
- return;
83162
- }
83163
- const acquired = ctx.acquireMaintenance(ctx.configPath, "watchdog", 3e4);
83164
- if (!acquired.acquired) {
83165
- throw new Error("Cannot uninstall the Windows daemon service while maintenance is active");
83166
- }
83167
- try {
83168
- const journal = ctx.inspectJournal(ctx.configPath);
83169
- if (!journal.safeToRestart) {
83170
- throw new Error(
83171
- `Windows daemon service uninstall is deferred while ${journal.activeRunCount} active run${journal.activeRunCount === 1 ? "" : "s"} finish`
83172
- );
83173
- }
83174
- let owner;
83175
- try {
83176
- owner = ctx.readOwner(ctx.configPath);
83177
- } catch (error61) {
83178
- throw new Error(
83179
- `Cannot verify Windows daemon ownership before uninstall: ${error61 instanceof Error ? error61.message : String(error61)}`
83180
- );
83181
- }
83182
- if (owner) {
83183
- const stopped = await stopDaemonServiceAndWait(
83184
- {
83185
- pid: owner.pid,
83186
- serviceId: owner.localServiceId,
83187
- daemonSessionId: owner.daemonSessionId,
83188
- port: owner.localApiPort,
83189
- startedAtMs: owner.startedAtMs
83190
- },
83191
- 1e4,
83192
- { ...options, args: ctx.args }
83193
- );
83194
- if (stopped !== "exited") {
83195
- throw new Error(
83196
- stopped === "identity_changed" ? "Windows daemon ownership changed before uninstall" : "Timed out waiting for the Windows daemon to stop before uninstall"
83197
- );
83198
- }
83199
- } else {
83200
- if (tasks.currentState !== "stopped" || tasks.legacyState !== "stopped") {
83201
- throw new Error("Cannot uninstall a running Windows daemon without verified ownership");
83202
- }
83203
- }
83204
- for (const command of ctx.plan.uninstallCommands) ctx.run(command);
83205
- } finally {
83206
- ctx.releaseMaintenance(ctx.configPath, acquired.lease.id);
83207
- }
83208
- } else {
83209
- for (const command of ctx.plan.uninstallCommands) ctx.run(command);
83210
- if (ctx.plan.manifestPath) ctx.removeManifest(ctx.plan.manifestPath);
83211
- }
82942
+ for (const command of ctx.plan.uninstallCommands) ctx.run(command);
82943
+ if (ctx.plan.manifestPath) ctx.removeManifest(ctx.plan.manifestPath);
83212
82944
  console.info("[alan-agent] Per-user daemon service removed");
83213
82945
  }
83214
82946
  function installDaemonService(args = [], runtime, options = {}) {
@@ -83236,7 +82968,7 @@ function installDaemonService(args = [], runtime, options = {}) {
83236
82968
  });
83237
82969
  return;
83238
82970
  }
83239
- if (inspection.state !== "stopped") {
82971
+ if (!shouldStartOnInstall(inspection.state)) {
83240
82972
  console.info("[alan-agent] Per-user daemon service already present", {
83241
82973
  profile,
83242
82974
  state: inspection.state
@@ -83260,48 +82992,14 @@ ${result.output}
83260
82992
  }
83261
82993
 
83262
82994
  // src/install-command.ts
83263
- var WINDOWS_SETUP_READY_TIMEOUT_MS = 9e3;
83264
- async function runInstallCommand(commandArgs, currentPlatform = process.platform) {
83265
- if (currentPlatform === "win32" && commandArgs.includes("--headless")) {
83266
- throw new Error("--headless is only supported on Linux systemd user services");
83267
- }
82995
+ async function runInstallCommand(commandArgs) {
83268
82996
  bindConfigPath(commandArgs);
83269
82997
  const configured = readConfig();
83270
82998
  const configurationMode = resolveInstallConfigurationMode(commandArgs, configured);
83271
- const isWindowsSetup = currentPlatform === "win32" && configurationMode === "setup";
83272
- const deadline = isWindowsSetup ? Date.now() + WINDOWS_SETUP_READY_TIMEOUT_MS : 0;
83273
- const runtimeIdentityChanged = configurationMode === "setup" ? await setupDaemon(commandArgs) : false;
82999
+ if (configurationMode === "setup") await setupDaemon(commandArgs);
83274
83000
  if (configurationMode === "login") await loginWithDeviceCode(commandArgs);
83275
- if (!isWindowsSetup) {
83276
- installDaemonService(commandArgs);
83277
- runMcpIntegrationCommand(["reconcile", ...commandArgs]);
83278
- return;
83279
- }
83280
- const { runtimeId, localApiPort, localApiToken } = readConfig();
83281
- if (!runtimeId || !localApiPort || !localApiToken) {
83282
- throw new Error("Windows runtime setup completed without a local readiness endpoint");
83283
- }
83284
- if (runtimeIdentityChanged) {
83285
- while (Date.now() < deadline) {
83286
- const status = await probeLocalDaemon(localApiPort, localApiToken, 250);
83287
- if (status?.runtimeId === runtimeId && status.connected) break;
83288
- if (!status) {
83289
- const service = inspectDaemonService({ args: commandArgs });
83290
- if (service.state !== "running" && service.state !== "starting") break;
83291
- }
83292
- await (0, import_promises6.setTimeout)(100);
83293
- }
83294
- }
83295
83001
  installDaemonService(commandArgs);
83296
- while (Date.now() < deadline) {
83297
- const status = await probeLocalDaemon(localApiPort, localApiToken, 250);
83298
- if (status?.runtimeId === runtimeId && status.connected) {
83299
- runMcpIntegrationCommand(["reconcile", ...commandArgs]);
83300
- return;
83301
- }
83302
- await (0, import_promises6.setTimeout)(100);
83303
- }
83304
- throw new Error("Windows runtime did not become ready within 9 seconds");
83002
+ runMcpIntegrationCommand(["reconcile", ...commandArgs]);
83305
83003
  }
83306
83004
 
83307
83005
  // src/lifecycle-logger.ts
@@ -86678,7 +86376,7 @@ var REQUEST_STATE_ONLY_LEG_PACING_MS2 = 250;
86678
86376
  function inputRequiredRoundsExceededMessage2(method, maxRounds) {
86679
86377
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
86680
86378
  }
86681
- function sleep4(ms, signal) {
86379
+ function sleep3(ms, signal) {
86682
86380
  return new Promise((resolve15, reject) => {
86683
86381
  if (signal?.aborted) {
86684
86382
  reject(signal.reason instanceof SdkError2 ? signal.reason : new SdkError2(SdkErrorCode2.RequestTimeout, String(signal.reason)));
@@ -94945,7 +94643,7 @@ var LegacyInputRequiredShim = class {
94945
94643
  } finally {
94946
94644
  roundAbort.dispose();
94947
94645
  }
94948
- } else await sleep4(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
94646
+ } else await sleep3(REQUEST_STATE_ONLY_LEG_PACING_MS2, outerSignal);
94949
94647
  let ctxNext = {
94950
94648
  ...ctx,
94951
94649
  mcpReq: {
@@ -95742,7 +95440,7 @@ async function runMcpStdioProxy(env = process.env, local = new StdioServerTransp
95742
95440
  }
95743
95441
 
95744
95442
  // src/native-hook-command.ts
95745
- var import_node_crypto22 = require("crypto");
95443
+ var import_node_crypto21 = require("crypto");
95746
95444
  var import_node_fs27 = require("fs");
95747
95445
  var import_node_os17 = require("os");
95748
95446
  var import_node_path29 = require("path");
@@ -96111,7 +95809,7 @@ function parseNativeHookEnvelope(input2) {
96111
95809
  const projectPin = eventType === "session_start" && resolvedCwd ? readAlanProjectPin(resolvedCwd) : void 0;
96112
95810
  return {
96113
95811
  schemaVersion: 1,
96114
- eventId: (0, import_node_crypto22.randomUUID)(),
95812
+ eventId: (0, import_node_crypto21.randomUUID)(),
96115
95813
  provider: capability.provider,
96116
95814
  eventType,
96117
95815
  observedAt: (input2.now ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -96607,7 +96305,7 @@ function delay2(ms) {
96607
96305
  }
96608
96306
 
96609
96307
  // src/web-presenter.ts
96610
- var import_node_crypto23 = require("crypto");
96308
+ var import_node_crypto22 = require("crypto");
96611
96309
  var WebPresenter = class {
96612
96310
  constructor(wsClient, conversationId, cwd) {
96613
96311
  this.conversationId = conversationId;
@@ -96793,7 +96491,7 @@ var WebPresenter = class {
96793
96491
  this.applicationProblem = createAlanProblem({
96794
96492
  domain: "application",
96795
96493
  code: input2.code,
96796
- id: (0, import_node_crypto23.randomUUID)(),
96494
+ id: (0, import_node_crypto22.randomUUID)(),
96797
96495
  title: APPLICATION_PROBLEM_TITLES[input2.code],
96798
96496
  detail: input2.message,
96799
96497
  // The schema's slot for the technical cause, so the problem carries it
@@ -96863,7 +96561,7 @@ var WebPresenter = class {
96863
96561
  };
96864
96562
 
96865
96563
  // src/ws-client.ts
96866
- var import_node_crypto25 = require("crypto");
96564
+ var import_node_crypto24 = require("crypto");
96867
96565
 
96868
96566
  // ../shared/dist/agent-liveness.js
96869
96567
  var AGENT_LIVENESS_PROTOCOL_VERSION = 1;
@@ -96891,13 +96589,13 @@ var agentProbeAckSchema = external_exports.object({
96891
96589
  });
96892
96590
 
96893
96591
  // src/sandbox-outbox.ts
96894
- var import_node_crypto24 = require("crypto");
96592
+ var import_node_crypto23 = require("crypto");
96895
96593
  var import_node_fs28 = require("fs");
96896
96594
  function deriveSandboxOutboxKey(sessionToken) {
96897
96595
  if (!sessionToken) {
96898
96596
  throw new Error("sandbox event outbox requires ALAN_SESSION_TOKEN for key derivation");
96899
96597
  }
96900
- return (0, import_node_crypto24.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
96598
+ return (0, import_node_crypto23.createHash)("sha256").update(sessionToken, "utf8").digest("base64url");
96901
96599
  }
96902
96600
  function sandboxOutboxSpoolPath(conversationId) {
96903
96601
  return `/tmp/alan-agent-outbox-${conversationId}.enc`;
@@ -97074,7 +96772,7 @@ var WSClient = class {
97074
96772
  }
97075
96773
  socket;
97076
96774
  /** Stable id for this agent process; fences stale heartbeats server-side. */
97077
- agentSessionId = (0, import_node_crypto25.randomUUID)();
96775
+ agentSessionId = (0, import_node_crypto24.randomUUID)();
97078
96776
  /** Monotonic heartbeat sequence — advances on every hello + heartbeat. */
97079
96777
  heartbeatSeq = 0;
97080
96778
  heartbeatTimer = null;
@@ -97906,7 +97604,7 @@ async function runSandbox(config2) {
97906
97604
  }
97907
97605
 
97908
97606
  // src/skills/skill-cli.ts
97909
- var import_promises7 = require("fs/promises");
97607
+ var import_promises6 = require("fs/promises");
97910
97608
  var import_node_os19 = require("os");
97911
97609
  var import_node_path30 = require("path");
97912
97610
  async function runSkillsCommand(args) {
@@ -97953,7 +97651,7 @@ async function defaultScanInput(homeDirectory2) {
97953
97651
  }
97954
97652
  async function readProtectedJson(path2) {
97955
97653
  const absolutePath = (0, import_node_path30.resolve)(path2);
97956
- const stat2 = await (0, import_promises7.lstat)(absolutePath);
97654
+ const stat2 = await (0, import_promises6.lstat)(absolutePath);
97957
97655
  if (!stat2.isFile()) throw new Error(`Skill input is not a regular file: ${absolutePath}`);
97958
97656
  if (process.platform !== "win32" && (stat2.mode & 63) !== 0) {
97959
97657
  throw new Error(`Skill input must not be accessible by group or other users: ${absolutePath}`);
@@ -97962,7 +97660,7 @@ async function readProtectedJson(path2) {
97962
97660
  throw new Error(`Skill input must be owned by the current user: ${absolutePath}`);
97963
97661
  }
97964
97662
  if (stat2.size > 2 * 1024 * 1024) throw new Error("Skill input exceeds 2 MiB");
97965
- return JSON.parse(await (0, import_promises7.readFile)(absolutePath, "utf8"));
97663
+ return JSON.parse(await (0, import_promises6.readFile)(absolutePath, "utf8"));
97966
97664
  }
97967
97665
  function option(args, name) {
97968
97666
  const index = args.indexOf(name);
@@ -98120,7 +97818,7 @@ async function main() {
98120
97818
  return;
98121
97819
  }
98122
97820
  if (command === "service" && commandArgs[0] === "uninstall") {
98123
- await uninstallDaemonService(commandArgs.slice(1));
97821
+ uninstallDaemonService(commandArgs.slice(1));
98124
97822
  return;
98125
97823
  }
98126
97824
  if (command === "service" && commandArgs[0] === "status") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alan-ai-hq/agent-manager",
3
- "version": "0.1.95",
3
+ "version": "0.1.96",
4
4
  "type": "module",
5
5
  "description": "Alan agent runtime — cloud sandbox and local daemon (alan-agent CLI)",
6
6
  "bin": {
@@ -27,8 +27,8 @@
27
27
  "tsx": "^4.19.0",
28
28
  "typescript": "~5.9.3",
29
29
  "@alan-ai/agent-core": "0.1.0",
30
- "@alan-ai/shared": "0.1.0",
31
- "@alan-ai/sandbox-runtime-spec": "0.1.0"
30
+ "@alan-ai/sandbox-runtime-spec": "0.1.0",
31
+ "@alan-ai/shared": "0.1.0"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "tsup",