@slock-ai/daemon 0.41.0 → 0.41.1-alpha.0

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.
@@ -4,8 +4,8 @@ import {
4
4
  } from "./chunk-JG7ONJZ6.js";
5
5
 
6
6
  // src/core.ts
7
- import path11 from "path";
8
- import os4 from "os";
7
+ import path12 from "path";
8
+ import os5 from "os";
9
9
  import { createRequire } from "module";
10
10
  import { accessSync } from "fs";
11
11
  import { fileURLToPath } from "url";
@@ -1097,7 +1097,7 @@ function normalizeExecOutput(raw) {
1097
1097
  return Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw ?? "");
1098
1098
  }
1099
1099
  function resolveCommandOnWindows(command, env, execFileSyncFn) {
1100
- const script = "$cmd = Get-Command -Name $args[0] -ErrorAction Stop | Select-Object -First 1; if ($cmd.Path) { $cmd.Path } elseif ($cmd.Source) { $cmd.Source } elseif ($cmd.Definition) { $cmd.Definition }";
1100
+ const script = "& {$cmd = Get-Command -Name $args[0] -ErrorAction Stop | Select-Object -First 1; if ($cmd.Path) { $cmd.Path } elseif ($cmd.Source) { $cmd.Source } elseif ($cmd.Definition) { $cmd.Definition } }";
1101
1101
  try {
1102
1102
  const output = normalizeExecOutput(execFileSyncFn("powershell.exe", [
1103
1103
  "-NoProfile",
@@ -4585,6 +4585,112 @@ var ReminderCache = class {
4585
4585
  }
4586
4586
  };
4587
4587
 
4588
+ // src/machineLock.ts
4589
+ import { createHash, randomUUID as randomUUID2 } from "crypto";
4590
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync, writeFileSync as writeFileSync7 } from "fs";
4591
+ import os4 from "os";
4592
+ import path11 from "path";
4593
+ var DEFAULT_MACHINE_STATE_ROOT = path11.join(os4.homedir(), ".slock", "machines");
4594
+ var INCOMPLETE_LOCK_STALE_MS = 3e4;
4595
+ var DaemonMachineLockConflictError = class extends Error {
4596
+ code = "DAEMON_MACHINE_LOCK_HELD";
4597
+ constructor(lockDir, owner) {
4598
+ const ownerText = owner ? `pid=${owner.pid}, startedAt=${owner.startedAt}, host=${owner.hostname}` : "unknown owner";
4599
+ super(
4600
+ `Another Slock daemon is already running for this machine key (${ownerText}). Lock: ${lockDir}. Stop the existing daemon first, or use a different machine key/state directory.`
4601
+ );
4602
+ this.name = "DaemonMachineLockConflictError";
4603
+ }
4604
+ };
4605
+ function apiKeyFingerprint(apiKey) {
4606
+ return createHash("sha256").update(apiKey).digest("hex");
4607
+ }
4608
+ function getDaemonMachineLockId(apiKey) {
4609
+ return `machine-${apiKeyFingerprint(apiKey).slice(0, 16)}`;
4610
+ }
4611
+ function ownerPath(lockDir) {
4612
+ return path11.join(lockDir, "owner.json");
4613
+ }
4614
+ function readOwner(lockDir) {
4615
+ try {
4616
+ return JSON.parse(readFileSync3(ownerPath(lockDir), "utf8"));
4617
+ } catch {
4618
+ return null;
4619
+ }
4620
+ }
4621
+ function lockAgeMs(lockDir) {
4622
+ try {
4623
+ return Date.now() - statSync(lockDir).mtimeMs;
4624
+ } catch {
4625
+ return null;
4626
+ }
4627
+ }
4628
+ function isProcessAlive(pid) {
4629
+ if (!Number.isInteger(pid) || pid <= 0) return false;
4630
+ try {
4631
+ process.kill(pid, 0);
4632
+ return true;
4633
+ } catch (err) {
4634
+ const code = typeof err === "object" && err && "code" in err ? err.code : void 0;
4635
+ return code !== "ESRCH";
4636
+ }
4637
+ }
4638
+ function acquireDaemonMachineLock(options) {
4639
+ const rootDir = options.rootDir ?? DEFAULT_MACHINE_STATE_ROOT;
4640
+ const fingerprint = apiKeyFingerprint(options.apiKey);
4641
+ const lockId = getDaemonMachineLockId(options.apiKey);
4642
+ const machineDir = path11.join(rootDir, lockId);
4643
+ const lockDir = path11.join(machineDir, "daemon.lock");
4644
+ const token = randomUUID2();
4645
+ mkdirSync4(machineDir, { recursive: true });
4646
+ for (let attempt = 0; attempt < 2; attempt += 1) {
4647
+ try {
4648
+ mkdirSync4(lockDir);
4649
+ const owner = {
4650
+ pid: process.pid,
4651
+ token,
4652
+ hostname: os4.hostname(),
4653
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
4654
+ serverUrl: options.serverUrl,
4655
+ apiKeyFingerprint: fingerprint.slice(0, 16)
4656
+ };
4657
+ try {
4658
+ writeFileSync7(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
4659
+ `, { mode: 384 });
4660
+ } catch (err) {
4661
+ rmSync(lockDir, { recursive: true, force: true });
4662
+ throw err;
4663
+ }
4664
+ return {
4665
+ lockId,
4666
+ machineDir,
4667
+ lockDir,
4668
+ release: () => {
4669
+ const currentOwner = readOwner(lockDir);
4670
+ if (currentOwner?.pid === process.pid && currentOwner.token === token) {
4671
+ rmSync(lockDir, { recursive: true, force: true });
4672
+ }
4673
+ }
4674
+ };
4675
+ } catch (err) {
4676
+ const code = typeof err === "object" && err && "code" in err ? err.code : void 0;
4677
+ if (code !== "EEXIST") throw err;
4678
+ const owner = readOwner(lockDir);
4679
+ if (owner?.pid && isProcessAlive(owner.pid)) {
4680
+ throw new DaemonMachineLockConflictError(lockDir, owner);
4681
+ }
4682
+ if (!owner) {
4683
+ const ageMs = lockAgeMs(lockDir);
4684
+ if (ageMs === null || ageMs < INCOMPLETE_LOCK_STALE_MS) {
4685
+ throw new DaemonMachineLockConflictError(lockDir, null);
4686
+ }
4687
+ }
4688
+ rmSync(lockDir, { recursive: true, force: true });
4689
+ }
4690
+ }
4691
+ throw new DaemonMachineLockConflictError(lockDir, readOwner(lockDir));
4692
+ }
4693
+
4588
4694
  // src/core.ts
4589
4695
  var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
4590
4696
  function parseDaemonCliArgs(args) {
@@ -4606,23 +4712,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
4606
4712
  }
4607
4713
  }
4608
4714
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
4609
- const dirname = path11.dirname(fileURLToPath(moduleUrl));
4610
- const jsPath = path11.resolve(dirname, "chat-bridge.js");
4715
+ const dirname = path12.dirname(fileURLToPath(moduleUrl));
4716
+ const jsPath = path12.resolve(dirname, "chat-bridge.js");
4611
4717
  try {
4612
4718
  accessSync(jsPath);
4613
4719
  return jsPath;
4614
4720
  } catch {
4615
- return path11.resolve(dirname, "chat-bridge.ts");
4721
+ return path12.resolve(dirname, "chat-bridge.ts");
4616
4722
  }
4617
4723
  }
4618
4724
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
4619
- const thisDir = path11.dirname(fileURLToPath(moduleUrl));
4620
- const bundledDistPath = path11.resolve(thisDir, "cli", "index.js");
4725
+ const thisDir = path12.dirname(fileURLToPath(moduleUrl));
4726
+ const bundledDistPath = path12.resolve(thisDir, "cli", "index.js");
4621
4727
  try {
4622
4728
  accessSync(bundledDistPath);
4623
4729
  return bundledDistPath;
4624
4730
  } catch {
4625
- const workspaceDistPath = path11.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
4731
+ const workspaceDistPath = path12.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
4626
4732
  accessSync(workspaceDistPath);
4627
4733
  return workspaceDistPath;
4628
4734
  }
@@ -4701,6 +4807,7 @@ var DaemonCore = class {
4701
4807
  connection;
4702
4808
  reminderCache;
4703
4809
  tracer;
4810
+ machineLock = null;
4704
4811
  constructor(options) {
4705
4812
  this.options = options;
4706
4813
  this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
@@ -4731,15 +4838,39 @@ var DaemonCore = class {
4731
4838
  });
4732
4839
  this.connection = connection;
4733
4840
  }
4841
+ resolveMachineStateRoot() {
4842
+ if (this.options.machineStateDir) return this.options.machineStateDir;
4843
+ if (this.options.dataDir) return path12.join(path12.dirname(this.options.dataDir), "machines");
4844
+ return DEFAULT_MACHINE_STATE_ROOT;
4845
+ }
4734
4846
  start() {
4735
4847
  logger.info("[Slock Daemon] Starting...");
4736
- this.connection.connect();
4848
+ if (!this.machineLock) {
4849
+ this.machineLock = acquireDaemonMachineLock({
4850
+ apiKey: this.options.apiKey,
4851
+ serverUrl: this.options.serverUrl,
4852
+ rootDir: this.resolveMachineStateRoot()
4853
+ });
4854
+ logger.info(`[Slock Daemon] Acquired machine lock: ${this.machineLock.lockDir}`);
4855
+ }
4856
+ try {
4857
+ this.connection.connect();
4858
+ } catch (err) {
4859
+ this.machineLock.release();
4860
+ this.machineLock = null;
4861
+ throw err;
4862
+ }
4737
4863
  }
4738
4864
  async stop() {
4739
4865
  logger.info("[Slock Daemon] Shutting down...");
4740
4866
  this.reminderCache.clear();
4741
- await this.agentManager.stopAll();
4742
- this.connection.disconnect();
4867
+ try {
4868
+ await this.agentManager.stopAll();
4869
+ } finally {
4870
+ this.connection.disconnect();
4871
+ this.machineLock?.release();
4872
+ this.machineLock = null;
4873
+ }
4743
4874
  }
4744
4875
  get connected() {
4745
4876
  return this.connection.connected;
@@ -4900,8 +5031,8 @@ var DaemonCore = class {
4900
5031
  capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
4901
5032
  runtimes,
4902
5033
  runningAgents: this.agentManager.getRunningAgentIds(),
4903
- hostname: this.options.hostname ?? os4.hostname(),
4904
- os: this.options.osDescription ?? `${os4.platform()} ${os4.arch()}`,
5034
+ hostname: this.options.hostname ?? os5.hostname(),
5035
+ os: this.options.osDescription ?? `${os5.platform()} ${os5.arch()}`,
4905
5036
  daemonVersion: this.daemonVersion
4906
5037
  });
4907
5038
  for (const agentId of this.agentManager.getRunningAgentIds()) {
package/dist/core.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveSlockCliPath,
10
10
  resolveWorkspaceDirectoryPath,
11
11
  scanWorkspaceDirectories
12
- } from "./chunk-KFVDXO5Y.js";
12
+ } from "./chunk-JAB3HALZ.js";
13
13
  import {
14
14
  subscribeDaemonLogs
15
15
  } from "./chunk-JG7ONJZ6.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
5
  parseDaemonCliArgs
6
- } from "./chunk-KFVDXO5Y.js";
6
+ } from "./chunk-JAB3HALZ.js";
7
7
  import "./chunk-JG7ONJZ6.js";
8
8
 
9
9
  // src/index.ts
@@ -13,7 +13,12 @@ if (!parsedArgs) {
13
13
  process.exit(1);
14
14
  }
15
15
  var daemon = new DaemonCore(parsedArgs);
16
- daemon.start();
16
+ try {
17
+ daemon.start();
18
+ } catch (err) {
19
+ console.error(err instanceof Error ? err.message : err);
20
+ process.exit(1);
21
+ }
17
22
  var shutdown = async () => {
18
23
  await daemon.stop();
19
24
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.41.0",
3
+ "version": "0.41.1-alpha.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"