@claudexor/daemon 1.0.0 → 2.1.2

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 (105) hide show
  1. package/dist/client.d.ts +29 -1
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +24 -4
  4. package/dist/client.js.map +1 -1
  5. package/dist/command-authority.d.ts +11 -0
  6. package/dist/command-authority.d.ts.map +1 -0
  7. package/dist/command-authority.js +15 -0
  8. package/dist/command-authority.js.map +1 -0
  9. package/dist/command-retention.d.ts +5 -0
  10. package/dist/command-retention.d.ts.map +1 -0
  11. package/dist/command-retention.js +18 -0
  12. package/dist/command-retention.js.map +1 -0
  13. package/dist/command-store.d.ts +41 -0
  14. package/dist/command-store.d.ts.map +1 -0
  15. package/dist/command-store.js +197 -0
  16. package/dist/command-store.js.map +1 -0
  17. package/dist/events.d.ts +2 -3
  18. package/dist/events.d.ts.map +1 -1
  19. package/dist/events.js +2 -3
  20. package/dist/events.js.map +1 -1
  21. package/dist/index.d.ts +13 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +13 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/interactions.d.ts +32 -22
  26. package/dist/interactions.d.ts.map +1 -1
  27. package/dist/interactions.js +183 -56
  28. package/dist/interactions.js.map +1 -1
  29. package/dist/journal-events.d.ts +4 -0
  30. package/dist/journal-events.d.ts.map +1 -0
  31. package/dist/journal-events.js +23 -0
  32. package/dist/journal-events.js.map +1 -0
  33. package/dist/journal-manager.d.ts +72 -0
  34. package/dist/journal-manager.d.ts.map +1 -0
  35. package/dist/journal-manager.js +463 -0
  36. package/dist/journal-manager.js.map +1 -0
  37. package/dist/journal-projection.d.ts +11 -0
  38. package/dist/journal-projection.d.ts.map +1 -0
  39. package/dist/journal-projection.js +2 -0
  40. package/dist/journal-projection.js.map +1 -0
  41. package/dist/journal-recovery-files.d.ts +27 -0
  42. package/dist/journal-recovery-files.d.ts.map +1 -0
  43. package/dist/journal-recovery-files.js +209 -0
  44. package/dist/journal-recovery-files.js.map +1 -0
  45. package/dist/operator-decisions.d.ts +30 -0
  46. package/dist/operator-decisions.d.ts.map +1 -0
  47. package/dist/operator-decisions.js +137 -0
  48. package/dist/operator-decisions.js.map +1 -0
  49. package/dist/project-partitions.d.ts +167 -0
  50. package/dist/project-partitions.d.ts.map +1 -0
  51. package/dist/project-partitions.js +365 -0
  52. package/dist/project-partitions.js.map +1 -0
  53. package/dist/projects.d.ts +29 -0
  54. package/dist/projects.d.ts.map +1 -0
  55. package/dist/projects.js +180 -0
  56. package/dist/projects.js.map +1 -0
  57. package/dist/quota-registry.d.ts +75 -0
  58. package/dist/quota-registry.d.ts.map +1 -0
  59. package/dist/quota-registry.js +208 -0
  60. package/dist/quota-registry.js.map +1 -0
  61. package/dist/resource-store.d.ts +22 -0
  62. package/dist/resource-store.d.ts.map +1 -0
  63. package/dist/resource-store.js +272 -0
  64. package/dist/resource-store.js.map +1 -0
  65. package/dist/run-events.d.ts +15 -0
  66. package/dist/run-events.d.ts.map +1 -0
  67. package/dist/run-events.js +29 -0
  68. package/dist/run-events.js.map +1 -0
  69. package/dist/server.d.ts +33 -33
  70. package/dist/server.d.ts.map +1 -1
  71. package/dist/server.js +231 -238
  72. package/dist/server.js.map +1 -1
  73. package/dist/spec-sessions.d.ts +72 -0
  74. package/dist/spec-sessions.d.ts.map +1 -0
  75. package/dist/spec-sessions.js +301 -0
  76. package/dist/spec-sessions.js.map +1 -0
  77. package/dist/terminate.d.ts +52 -0
  78. package/dist/terminate.d.ts.map +1 -0
  79. package/dist/terminate.js +111 -0
  80. package/dist/terminate.js.map +1 -0
  81. package/dist/thread-head-ping.d.ts +35 -0
  82. package/dist/thread-head-ping.d.ts.map +1 -0
  83. package/dist/thread-head-ping.js +67 -0
  84. package/dist/thread-head-ping.js.map +1 -0
  85. package/dist/thread-lifecycle.d.ts +5 -0
  86. package/dist/thread-lifecycle.d.ts.map +1 -0
  87. package/dist/thread-lifecycle.js +57 -0
  88. package/dist/thread-lifecycle.js.map +1 -0
  89. package/dist/thread-store-support.d.ts +37 -0
  90. package/dist/thread-store-support.d.ts.map +1 -0
  91. package/dist/thread-store-support.js +94 -0
  92. package/dist/thread-store-support.js.map +1 -0
  93. package/dist/threads.d.ts +59 -13
  94. package/dist/threads.d.ts.map +1 -1
  95. package/dist/threads.js +254 -141
  96. package/dist/threads.js.map +1 -1
  97. package/dist/token.d.ts +13 -4
  98. package/dist/token.d.ts.map +1 -1
  99. package/dist/token.js +134 -29
  100. package/dist/token.js.map +1 -1
  101. package/dist/writer-lease.d.ts +21 -0
  102. package/dist/writer-lease.d.ts.map +1 -0
  103. package/dist/writer-lease.js +94 -0
  104. package/dist/writer-lease.js.map +1 -0
  105. package/package.json +5 -4
package/dist/token.js CHANGED
@@ -1,63 +1,168 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { userConfigDir } from "@claudexor/util";
2
+ import { closeSync, constants, existsSync, fchmodSync, fstatSync, fsyncSync, lstatSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeSync, } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { ensureCanonicalPrivateDirectory, userConfigDir, userHomeDir } from "@claudexor/util";
5
5
  export function daemonDir() {
6
6
  return join(userConfigDir(), "daemon");
7
7
  }
8
+ /**
9
+ * Prove every daemon-owned root component before any token/log/lock writer is
10
+ * allowed to touch it. On first launch the default `~/.claudexor` parent may
11
+ * not exist yet, so create that one owned root before proving the v2 subtree.
12
+ * A custom root must still have a canonical parent supplied by its operator.
13
+ */
14
+ export function ensureDaemonRuntimeRoot() {
15
+ if (!process.env.CLAUDEXOR_CONFIG_DIR?.trim()) {
16
+ const defaultParent = join(userHomeDir(), ".claudexor");
17
+ if (!existsSync(defaultParent))
18
+ ensureCanonicalPrivateDirectory(defaultParent);
19
+ }
20
+ const configRoot = ensureCanonicalPrivateDirectory(userConfigDir());
21
+ const root = ensureCanonicalPrivateDirectory(join(configRoot, "daemon"));
22
+ if (root !== resolve(daemonDir()))
23
+ throw new Error("daemon runtime root is not canonical");
24
+ return root;
25
+ }
8
26
  export function defaultSocketPath() {
9
27
  return process.env.CLAUDEXOR_DAEMON_SOCK || join(daemonDir(), "claudexord.sock");
10
28
  }
11
29
  export function logPath() {
12
30
  return join(daemonDir(), "claudexord.log");
13
31
  }
14
- /** Read or generate a per-user local auth token (0600). */
32
+ /** Read or generate a per-user local auth token (0600), never through links. */
15
33
  export function ensureToken() {
16
- const dir = daemonDir();
17
- mkdirSync(dir, { recursive: true });
18
- const path = join(dir, "token");
34
+ const root = ensureDaemonRuntimeRoot();
35
+ const path = join(root, "token");
19
36
  try {
20
- const existing = readFileSync(path, "utf8").trim();
21
- if (existing)
22
- return existing;
37
+ return readValidatedToken(path, true);
23
38
  }
24
- catch {
25
- /* generate below */
39
+ catch (error) {
40
+ if (error?.code !== "ENOENT")
41
+ throw error;
26
42
  }
27
43
  const token = randomUUID();
28
- writeFileSync(path, token + "\n", { mode: 0o600 });
44
+ let fd;
29
45
  try {
30
- chmodSync(path, 0o600);
46
+ fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
47
+ const stat = fstatSync(fd);
48
+ if (!stat.isFile() || stat.nlink !== 1)
49
+ throw new Error("daemon token target is unsafe");
50
+ fchmodSync(fd, 0o600);
51
+ const bytes = Buffer.from(`${token}\n`, "utf8");
52
+ let offset = 0;
53
+ while (offset < bytes.length)
54
+ offset += writeSync(fd, bytes, offset, bytes.length - offset);
55
+ fsyncSync(fd);
31
56
  }
32
- catch {
33
- /* best-effort */
57
+ catch (error) {
58
+ if (error?.code === "EEXIST")
59
+ return readValidatedToken(path, true);
60
+ throw error;
61
+ }
62
+ finally {
63
+ if (fd !== undefined)
64
+ closeSync(fd);
34
65
  }
66
+ fsyncDirectory(root);
35
67
  return token;
36
68
  }
37
69
  export function readToken() {
38
70
  try {
39
- const t = readFileSync(join(daemonDir(), "token"), "utf8").trim();
40
- return t || null;
71
+ const root = validateDaemonRuntimeRootReadOnly();
72
+ return readValidatedToken(join(root, "token"), false);
41
73
  }
42
74
  catch {
43
75
  return null;
44
76
  }
45
77
  }
46
- /** Rotate the local auth token: a fresh random token replaces the
47
- * old one (0600). Existing daemon sessions keep their in-memory token, so
48
- * rotation takes effect on the next daemon start — the CLI surface says so. */
78
+ function validateDaemonRuntimeRootReadOnly() {
79
+ const root = resolve(daemonDir());
80
+ const stat = lstatSync(root);
81
+ if (stat.isSymbolicLink() ||
82
+ !stat.isDirectory() ||
83
+ realpathSync.native(root) !== root ||
84
+ (stat.mode & 0o077) !== 0 ||
85
+ (typeof process.getuid === "function" && stat.uid !== process.getuid())) {
86
+ throw new Error(`daemon runtime root is not canonical and private: ${root}`);
87
+ }
88
+ return root;
89
+ }
90
+ /**
91
+ * Rotate only a proven owner-controlled token inode. The caller already fences
92
+ * a live daemon; this writer adds no compatibility path that would chmod or
93
+ * overwrite a symlink/hardlink/foreign file.
94
+ */
49
95
  export function rotateToken() {
50
- const dir = daemonDir();
51
- mkdirSync(dir, { recursive: true });
52
- const path = join(dir, "token");
96
+ const root = ensureDaemonRuntimeRoot();
97
+ const path = join(root, "token");
98
+ if (existsSync(path))
99
+ void readValidatedToken(path, false);
53
100
  const token = randomUUID();
54
- writeFileSync(path, token + "\n", { mode: 0o600 });
101
+ const temp = join(root, `.token-${process.pid}-${randomUUID()}.tmp`);
102
+ let fd;
55
103
  try {
56
- chmodSync(path, 0o600);
104
+ fd = openSync(temp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
105
+ const bytes = Buffer.from(`${token}\n`, "utf8");
106
+ let offset = 0;
107
+ while (offset < bytes.length)
108
+ offset += writeSync(fd, bytes, offset, bytes.length - offset);
109
+ fchmodSync(fd, 0o600);
110
+ fsyncSync(fd);
111
+ closeSync(fd);
112
+ fd = undefined;
113
+ if (existsSync(path))
114
+ void readValidatedToken(path, false);
115
+ renameSync(temp, path);
116
+ fsyncDirectory(root);
117
+ return token;
57
118
  }
58
- catch {
59
- /* best-effort */
119
+ finally {
120
+ if (fd !== undefined)
121
+ closeSync(fd);
122
+ try {
123
+ unlinkSync(temp);
124
+ }
125
+ catch {
126
+ /* renamed or never created */
127
+ }
128
+ }
129
+ }
130
+ function readValidatedToken(path, repairMode) {
131
+ let fd;
132
+ try {
133
+ fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
134
+ const opened = fstatSync(fd);
135
+ if (!opened.isFile() ||
136
+ opened.nlink !== 1 ||
137
+ opened.size > 4 * 1024 ||
138
+ (typeof process.getuid === "function" && opened.uid !== process.getuid()))
139
+ throw new Error("daemon token is not an owner-controlled singly-linked regular file");
140
+ if ((opened.mode & 0o077) !== 0) {
141
+ if (!repairMode)
142
+ throw new Error("daemon token permissions are not private");
143
+ // The inode/path identity was fully proven above; only now may startup
144
+ // repair permissions inherited from an older Claudexor version.
145
+ fchmodSync(fd, 0o600);
146
+ fsyncSync(fd);
147
+ }
148
+ const token = readFileSync(fd, "utf8").trim();
149
+ if (!token || token.includes("\n") || token.includes("\r")) {
150
+ throw new Error("daemon token is empty or malformed");
151
+ }
152
+ return token;
153
+ }
154
+ finally {
155
+ if (fd !== undefined)
156
+ closeSync(fd);
157
+ }
158
+ }
159
+ function fsyncDirectory(path) {
160
+ const fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
161
+ try {
162
+ fsyncSync(fd);
163
+ }
164
+ finally {
165
+ closeSync(fd);
60
166
  }
61
- return token;
62
167
  }
63
168
  //# sourceMappingURL=token.js.map
package/dist/token.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,UAAU,SAAS;IACvB,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,QAAQ,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE,iBAAiB,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,gBAAgB,CAAC,CAAC;AAC7C,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,WAAW;IACzB,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACnD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,oBAAoB;IACtB,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;IAC3B,aAAa,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB;IACnB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAClE,OAAO,CAAC,IAAI,IAAI,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;+EAE+E;AAC/E,MAAM,UAAU,WAAW;IACzB,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;IAC3B,aAAa,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB;IACnB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type KnownProcessIdentity, type ProcessIdentityReader } from "@claudexor/core";
2
+ export interface DaemonWriterLease {
3
+ readonly path: string;
4
+ release(): void;
5
+ }
6
+ export interface DaemonLeaseOwner {
7
+ pid: number;
8
+ token: string;
9
+ /** Birth identity of the owning daemon, recorded at acquisition so a later
10
+ * terminator can verify it never signals a recycled pid (W3.5/sol #5).
11
+ * Absent on legacy leases or when the platform cannot observe identity. */
12
+ identity?: KnownProcessIdentity;
13
+ }
14
+ /** Claim single-writer authority before any daemon journal is opened. */
15
+ export declare function acquireDaemonWriterLease(socketPath: string, deps?: {
16
+ identity?: ProcessIdentityReader;
17
+ }): DaemonWriterLease;
18
+ /** Read the current writer-lease owner for a socket path (null when free). */
19
+ export declare function daemonLeaseOwner(socketPath: string): DaemonLeaseOwner | null;
20
+ export declare function processIsAlive(pid: number): boolean;
21
+ //# sourceMappingURL=writer-lease.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer-lease.d.ts","sourceRoot":"","sources":["../src/writer-lease.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd;;+EAE2E;IAC3E,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CACjC;AAED,yEAAyE;AACzE,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,qBAAqB,CAAA;CAAO,GAC9C,iBAAiB,CA4CnB;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAE5E;AAiCD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD"}
@@ -0,0 +1,94 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { defaultProcessIdentityService, isKnownProcessIdentity, } from "@claudexor/core";
4
+ /** Claim single-writer authority before any daemon journal is opened. */
5
+ export function acquireDaemonWriterLease(socketPath, deps = {}) {
6
+ const path = `${socketPath}.writer`;
7
+ const token = randomUUID();
8
+ const ownerPath = `${path}/owner.json`;
9
+ const self = (deps.identity ?? defaultProcessIdentityService).self();
10
+ const owner = {
11
+ pid: process.pid,
12
+ token,
13
+ ...(self.status === "known" ? { identity: self } : {}),
14
+ };
15
+ for (let attempt = 0; attempt < 2; attempt += 1) {
16
+ try {
17
+ mkdirSync(path, { mode: 0o700 });
18
+ writeFileSync(ownerPath, `${JSON.stringify(owner)}\n`, {
19
+ mode: 0o600,
20
+ flag: "wx",
21
+ });
22
+ break;
23
+ }
24
+ catch (error) {
25
+ if (error.code !== "EEXIST")
26
+ throw error;
27
+ const existing = readLeaseOwner(ownerPath);
28
+ if (!existing || processIsAlive(existing.pid))
29
+ throw writerBusy(path);
30
+ const stale = `${path}.stale-${process.pid}-${randomUUID()}`;
31
+ try {
32
+ renameSync(path, stale);
33
+ rmSync(stale, { recursive: true, force: true });
34
+ }
35
+ catch (cleanupError) {
36
+ if (cleanupError.code !== "ENOENT")
37
+ throw cleanupError;
38
+ }
39
+ if (attempt === 1)
40
+ throw new Error(`could not replace stale daemon writer lease ${path}`);
41
+ }
42
+ }
43
+ let released = false;
44
+ return {
45
+ path,
46
+ release: () => {
47
+ if (released)
48
+ return;
49
+ released = true;
50
+ const current = readLeaseOwner(ownerPath);
51
+ if (current?.token === token && current.pid === process.pid) {
52
+ rmSync(path, { recursive: true, force: true });
53
+ }
54
+ },
55
+ };
56
+ }
57
+ /** Read the current writer-lease owner for a socket path (null when free). */
58
+ export function daemonLeaseOwner(socketPath) {
59
+ return readLeaseOwner(`${socketPath}.writer/owner.json`);
60
+ }
61
+ function writerBusy(path) {
62
+ return Object.assign(new Error(`another claudexor daemon owns ${path}`), {
63
+ code: "daemon_writer_busy",
64
+ status: 409,
65
+ });
66
+ }
67
+ function readLeaseOwner(path) {
68
+ try {
69
+ const value = JSON.parse(readFileSync(path, "utf8"));
70
+ if (!Number.isSafeInteger(value.pid) ||
71
+ Number(value.pid) <= 0 ||
72
+ typeof value.token !== "string") {
73
+ return null;
74
+ }
75
+ return {
76
+ pid: Number(value.pid),
77
+ token: value.token,
78
+ ...(isKnownProcessIdentity(value.identity) ? { identity: value.identity } : {}),
79
+ };
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ export function processIsAlive(pid) {
86
+ try {
87
+ process.kill(pid, 0);
88
+ return true;
89
+ }
90
+ catch (error) {
91
+ return error.code === "EPERM";
92
+ }
93
+ }
94
+ //# sourceMappingURL=writer-lease.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer-lease.js","sourceRoot":"","sources":["../src/writer-lease.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudexor/daemon",
3
- "version": "1.0.0",
3
+ "version": "2.1.2",
4
4
  "license": "MIT",
5
5
  "description": "Optional local daemon: Unix-socket JSON-RPC, token auth, task queue. Runner injected (no second scheduler).",
6
6
  "type": "module",
@@ -16,9 +16,10 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
- "@claudexor/event-log": "1.0.0",
20
- "@claudexor/schema": "1.0.0",
21
- "@claudexor/util": "1.0.0"
19
+ "@claudexor/core": "2.1.2",
20
+ "@claudexor/journal": "2.1.2",
21
+ "@claudexor/schema": "2.1.2",
22
+ "@claudexor/util": "2.1.2"
22
23
  },
23
24
  "repository": {
24
25
  "type": "git",