stitchkit 0.103.8 → 0.103.10

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 (79) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/agent-runtime/sandbox-process.d.ts +2 -2
  3. package/dist/agent-runtime/sandbox-process.d.ts.map +1 -1
  4. package/dist/agent-runtime/store-conformance-lifecycle.d.ts +10 -10
  5. package/dist/durability/contract.d.ts +3 -2
  6. package/dist/durability/contract.d.ts.map +1 -1
  7. package/dist/durability/effect.d.ts.map +1 -1
  8. package/dist/durability/engine.d.ts.map +1 -1
  9. package/dist/durability/json.d.ts.map +1 -1
  10. package/dist/entrypoints/agent-runtime/harness.js +2 -2
  11. package/dist/entrypoints/agent-runtime/sandbox.js +29 -81
  12. package/dist/entrypoints/agent-runtime.js +2 -2
  13. package/dist/entrypoints/application/diagnostic-journal.js +2 -1
  14. package/dist/entrypoints/cli.js +1 -1
  15. package/dist/entrypoints/files.d.ts +2 -0
  16. package/dist/entrypoints/files.d.ts.map +1 -1
  17. package/dist/entrypoints/files.js +14 -6
  18. package/dist/entrypoints/primitives.js +77 -57
  19. package/dist/entrypoints/process.d.ts +5 -0
  20. package/dist/entrypoints/process.d.ts.map +1 -0
  21. package/dist/entrypoints/process.js +20 -0
  22. package/dist/entrypoints/tools.js +12 -3
  23. package/dist/files/boundary.d.ts +19 -1
  24. package/dist/files/boundary.d.ts.map +1 -1
  25. package/dist/files/file-io.d.ts +5 -1
  26. package/dist/files/file-io.d.ts.map +1 -1
  27. package/dist/files/read.d.ts +13 -0
  28. package/dist/files/read.d.ts.map +1 -0
  29. package/dist/index-12m2v2a9.js +98 -0
  30. package/dist/index-6m6a9ty4.js +285 -0
  31. package/dist/index-8a9ksm91.js +95 -0
  32. package/dist/{index-90ppdx3q.js → index-9jb61j5a.js} +1 -1
  33. package/dist/{index-5vyd0fkb.js → index-ary5s7k0.js} +40 -61
  34. package/dist/index-c1jmtxa0.js +74 -0
  35. package/dist/index-ga597cvy.js +184 -0
  36. package/dist/index-mbnnw3f2.js +25 -0
  37. package/dist/index-nybn2err.js +213 -0
  38. package/dist/{index-v4a2fjtk.js → index-sybedc8b.js} +124 -55
  39. package/dist/internal/abort-race.d.ts +3 -0
  40. package/dist/internal/abort-race.d.ts.map +1 -0
  41. package/dist/internal/atomic-file.d.ts +20 -2
  42. package/dist/internal/atomic-file.d.ts.map +1 -1
  43. package/dist/internal/atomic-publication.d.ts +19 -0
  44. package/dist/internal/atomic-publication.d.ts.map +1 -0
  45. package/dist/internal/exclusive-lock.d.ts +2 -1
  46. package/dist/internal/exclusive-lock.d.ts.map +1 -1
  47. package/dist/internal/json-data.d.ts +8 -0
  48. package/dist/internal/json-data.d.ts.map +1 -0
  49. package/dist/internal/process-instance.d.ts +46 -9
  50. package/dist/internal/process-instance.d.ts.map +1 -1
  51. package/dist/internal/with-exclusive-lock.d.ts +4 -3
  52. package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
  53. package/dist/primitives/canonical-json.d.ts +11 -0
  54. package/dist/primitives/canonical-json.d.ts.map +1 -0
  55. package/dist/primitives/index.d.ts +1 -0
  56. package/dist/primitives/index.d.ts.map +1 -1
  57. package/dist/process/command-owner.d.ts +13 -0
  58. package/dist/process/command-owner.d.ts.map +1 -0
  59. package/dist/process/command.d.ts +3 -0
  60. package/dist/process/command.d.ts.map +1 -0
  61. package/dist/process/contract.d.ts +45 -0
  62. package/dist/process/contract.d.ts.map +1 -0
  63. package/dist/process/group.d.ts +6 -0
  64. package/dist/process/group.d.ts.map +1 -0
  65. package/dist/process/leader-settlement.d.ts +4 -0
  66. package/dist/process/leader-settlement.d.ts.map +1 -0
  67. package/dist/tools/transfer/managed-file-error.d.ts.map +1 -1
  68. package/llms/files.txt +9 -1
  69. package/llms/primitives.txt +31 -1
  70. package/llms/process.txt +242 -0
  71. package/llms/stitchkit.txt +1 -0
  72. package/llms/tools.txt +18 -0
  73. package/llms-full.txt +292 -2
  74. package/llms.txt +6 -4
  75. package/native/darwin-arm64.node +0 -0
  76. package/native/darwin-x64.node +0 -0
  77. package/package.json +5 -1
  78. package/dist/index-7kxw1awn.js +0 -328
  79. package/dist/index-aan6hqb9.js +0 -65
@@ -1,3 +1,10 @@
1
+ import {
2
+ isJsonData
3
+ } from "./index-c1jmtxa0.js";
4
+ import {
5
+ raceAbort
6
+ } from "./index-mbnnw3f2.js";
7
+
1
8
  // src/durability/contract.ts
2
9
  import { z } from "zod";
3
10
  var DURABILITY_STEP_EVENT_KIND2 = "durability/step";
@@ -93,44 +100,12 @@ class ParkAbortedError2 extends Error {
93
100
 
94
101
  // src/durability/json.ts
95
102
  import { z as z2 } from "zod";
96
- function losslessJson(value, ancestors = new Set, depth = 0) {
97
- if (depth > 100)
98
- return false;
99
- if (value === null || typeof value === "string" || typeof value === "boolean")
100
- return true;
101
- if (typeof value === "number")
102
- return Number.isFinite(value) && !Object.is(value, -0);
103
- if (typeof value !== "object" || ancestors.has(value))
104
- return false;
105
- const array = Array.isArray(value);
106
- const prototype = Object.getPrototypeOf(value);
107
- if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null)
108
- return false;
109
- ancestors.add(value);
110
- try {
111
- const descriptors = Object.getOwnPropertyDescriptors(value);
112
- const keys = Reflect.ownKeys(descriptors);
113
- if (array && keys.length !== value.length + 1)
114
- return false;
115
- for (const key of keys) {
116
- if (array && key === "length")
117
- continue;
118
- if (typeof key !== "string")
119
- return false;
120
- if (array && (!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= value.length))
121
- return false;
122
- const descriptor = descriptors[key];
123
- if (!descriptor?.enumerable || !("value" in descriptor))
124
- return false;
125
- if (!losslessJson(descriptor.value, ancestors, depth + 1))
126
- return false;
127
- }
128
- return true;
129
- } finally {
130
- ancestors.delete(value);
131
- }
132
- }
133
- var DurableJsonSchema = z2.custom((value) => losslessJson(value), "Expected lossless JSON data (finite numbers, plain objects and dense arrays; no accessors)");
103
+ var DurableJsonSchema = z2.custom((value) => isJsonData(value, {
104
+ maxDepth: 100,
105
+ maxNodes: Number.MAX_SAFE_INTEGER,
106
+ maxBytes: Number.MAX_SAFE_INTEGER,
107
+ omitObjectUndefined: false
108
+ }), "Expected lossless JSON data (finite numbers, plain objects and dense arrays; no accessors)");
134
109
 
135
110
  // src/durability/ledger.ts
136
111
  function stepKey(conversationId, runId, stepName) {
@@ -292,30 +267,25 @@ async function append(context, payload, name) {
292
267
  payload: { runId: context.runId, effectName: name, ...payload }
293
268
  });
294
269
  }
295
- async function reconcile(context, name, handlers, timeoutMs) {
270
+ async function reconcile(context, name, handlers, timeoutMs, outerSignal) {
271
+ const caller = [context.signal, outerSignal].filter((signal) => signal !== undefined);
296
272
  const controller = new AbortController;
297
- let timer;
298
- const deadline = new Promise((resolve) => {
299
- timer = setTimeout(() => {
300
- controller.abort(new Error(`reconcile did not finish within ${timeoutMs} ms`));
301
- resolve("timeout");
302
- }, timeoutMs);
303
- });
273
+ const timeout = setTimeout(() => controller.abort(new Error(`reconcile did not finish within ${timeoutMs} ms`)), timeoutMs);
274
+ const signal = AbortSignal.any([...caller, controller.signal]);
304
275
  let found;
305
276
  try {
306
- found = await Promise.race([
307
- Promise.resolve().then(() => handlers.reconcile(controller.signal)),
308
- deadline
309
- ]);
277
+ found = await raceAbort(Promise.resolve().then(() => {
278
+ signal.throwIfAborted();
279
+ return handlers.reconcile(signal);
280
+ }), signal);
281
+ signal.throwIfAborted();
310
282
  } catch (cause) {
311
- throw new EffectUnresolvedError2(name, "reconcile-failed", { cause });
283
+ if (caller.some((signal) => signal.aborted))
284
+ throw new StepAbortedError2(name);
285
+ throw new EffectUnresolvedError2(name, controller.signal.aborted ? "reconcile-timeout" : "reconcile-failed", { cause });
312
286
  } finally {
313
- clearTimeout(timer);
314
- }
315
- if (found === "timeout") {
316
- throw new EffectUnresolvedError2(name, "reconcile-timeout", {
317
- cause: controller.signal.reason
318
- });
287
+ clearTimeout(timeout);
288
+ controller.abort(new Error("Reconciliation completed"));
319
289
  }
320
290
  if (found === null) {
321
291
  await append(context, { phase: "uncertain" }, name);
@@ -327,8 +297,8 @@ async function reconcile(context, name, handlers, timeoutMs) {
327
297
  }
328
298
  async function executeEffect(context, name, handlers, options) {
329
299
  const timeoutMs = options?.reconcileTimeoutMs ?? DEFAULT_RECONCILE_TIMEOUT_MS;
330
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
331
- throw new TypeError(`reconcileTimeoutMs must be a positive finite number, got ${timeoutMs}`);
300
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) {
301
+ throw new TypeError(`reconcileTimeoutMs must be a positive finite number at most 2147483647, got ${timeoutMs}`);
332
302
  }
333
303
  const key = effectKey(context.conversationId, context.runId, name);
334
304
  const recorded = (await context.readLedger()).effects.get(key);
@@ -340,7 +310,7 @@ async function executeEffect(context, name, handlers, options) {
340
310
  if (options?.signal?.aborted || context.signal?.aborted)
341
311
  throw new StepAbortedError2(name);
342
312
  if (recorded)
343
- return reconcile(context, name, handlers, timeoutMs);
313
+ return reconcile(context, name, handlers, timeoutMs, options?.signal);
344
314
  await append(context, { phase: "intent" }, name);
345
315
  let proof;
346
316
  try {
@@ -581,7 +551,16 @@ function createLocalStepDurability2(options) {
581
551
  const key = effectKey(conversationId, runId, name);
582
552
  const existing = inFlightEffects.get(key);
583
553
  if (existing) {
584
- return detachedResult(existing);
554
+ const signals = [options.signal, runOptions?.signal].filter((signal) => signal !== undefined);
555
+ const result = detachedResult(existing);
556
+ if (signals.length === 0)
557
+ return result;
558
+ const signal = AbortSignal.any(signals);
559
+ return raceAbort(result, signal).catch((error) => {
560
+ if (signal.aborted)
561
+ throw new StepAbortedError2(name);
562
+ throw error;
563
+ });
585
564
  }
586
565
  const pending = executeEffect({ store, conversationId, runId, signal: options.signal, readLedger }, name, handlers, runOptions);
587
566
  inFlightEffects.set(key, pending);
@@ -0,0 +1,74 @@
1
+ // src/internal/json-data.ts
2
+ function isJsonData(value, limits) {
3
+ const ancestors = new Set;
4
+ let nodes = 0;
5
+ let bytes = 0;
6
+ const charge = (amount) => {
7
+ bytes += amount;
8
+ return bytes <= limits.maxBytes;
9
+ };
10
+ const textSize = (text) => {
11
+ if (text.length > limits.maxBytes)
12
+ return limits.maxBytes + 1;
13
+ return new TextEncoder().encode(JSON.stringify(text)).byteLength;
14
+ };
15
+ const visit = (item, depth) => {
16
+ if (++nodes > limits.maxNodes || depth > limits.maxDepth)
17
+ return false;
18
+ if (item === null)
19
+ return charge(4);
20
+ if (typeof item === "string")
21
+ return charge(textSize(item));
22
+ if (typeof item === "boolean")
23
+ return charge(item ? 4 : 5);
24
+ if (typeof item === "number")
25
+ return Number.isFinite(item) && !Object.is(item, -0) && charge(String(item).length);
26
+ if (typeof item !== "object" || ancestors.has(item))
27
+ return false;
28
+ const array = Array.isArray(item);
29
+ const prototype = Object.getPrototypeOf(item);
30
+ if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null)
31
+ return false;
32
+ if (array && item.length > limits.maxNodes - nodes)
33
+ return false;
34
+ const keys = Reflect.ownKeys(item);
35
+ if (keys.length > limits.maxNodes - nodes + (array ? 1 : 0))
36
+ return false;
37
+ if (array && keys.length !== item.length + 1)
38
+ return false;
39
+ ancestors.add(item);
40
+ try {
41
+ if (!charge(2))
42
+ return false;
43
+ let members = 0;
44
+ for (const key of keys) {
45
+ if (array && key === "length")
46
+ continue;
47
+ if (typeof key !== "string")
48
+ return false;
49
+ if (array && (!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= item.length))
50
+ return false;
51
+ const descriptor = Object.getOwnPropertyDescriptor(item, key);
52
+ if (!descriptor?.enumerable || !("value" in descriptor))
53
+ return false;
54
+ if (!array && limits.omitObjectUndefined && descriptor.value === undefined) {
55
+ if (++nodes > limits.maxNodes)
56
+ return false;
57
+ continue;
58
+ }
59
+ if (members++ > 0 && !charge(1))
60
+ return false;
61
+ if (!array && !charge(textSize(key) + 1))
62
+ return false;
63
+ if (!visit(descriptor.value, depth + 1))
64
+ return false;
65
+ }
66
+ return true;
67
+ } finally {
68
+ ancestors.delete(item);
69
+ }
70
+ };
71
+ return visit(value, 0);
72
+ }
73
+
74
+ export { isJsonData };
@@ -0,0 +1,184 @@
1
+ import {
2
+ machineIdentity,
3
+ ProcessInstanceSchema2,
4
+ readProcessInstance,
5
+ probeProcessOwner2
6
+ } from "./index-nybn2err.js";
7
+ import {
8
+ isRecord
9
+ } from "./index-9jfdm5tg.js";
10
+
11
+ // src/internal/exclusive-lock.ts
12
+ import { constants } from "node:fs";
13
+ import { lstat, open, unlink } from "node:fs/promises";
14
+ import { hostname } from "node:os";
15
+ import { z } from "zod";
16
+ var ExclusiveLockOwnerSchema = z.object({
17
+ pid: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
18
+ host: z.string().min(1),
19
+ acquiredAt: z.string(),
20
+ machine: z.string().min(1).optional().catch(undefined),
21
+ process: ProcessInstanceSchema2.nullable().optional().catch(null)
22
+ });
23
+ function isCode(error, code) {
24
+ return isRecord(error) && error.code === code;
25
+ }
26
+ function readOwner(text) {
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(text);
30
+ } catch {
31
+ return;
32
+ }
33
+ const owner = ExclusiveLockOwnerSchema.safeParse(parsed);
34
+ return owner.success ? owner.data : undefined;
35
+ }
36
+ function attribute(owner, identity) {
37
+ if (identity !== null && owner.machine !== undefined) {
38
+ return owner.machine === identity ? "this-machine" : "another-machine";
39
+ }
40
+ return owner.host === hostname() ? "this-machine" : "unattributable";
41
+ }
42
+ async function diagnose(owner, declaredIdentity) {
43
+ if (!owner)
44
+ return { attribution: "unattributable", liveness: "not-probed", owner: null };
45
+ const attribution = attribute(owner, await machineIdentity(declaredIdentity));
46
+ if (attribution !== "this-machine")
47
+ return { attribution, liveness: "not-probed", owner };
48
+ return { attribution, ...await probeProcessOwner2(owner.pid, owner.process), owner };
49
+ }
50
+ async function readLockFile(path, onFailure) {
51
+ let handle;
52
+ try {
53
+ handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
54
+ const info = await handle.stat();
55
+ return {
56
+ owner: readOwner(await handle.readFile("utf8")),
57
+ ino: info.ino,
58
+ mtimeMs: info.mtimeMs
59
+ };
60
+ } catch (cause) {
61
+ if (!isCode(cause, "ENOENT"))
62
+ onFailure?.(cause);
63
+ return;
64
+ } finally {
65
+ await handle?.close().catch(() => {
66
+ return;
67
+ });
68
+ }
69
+ }
70
+ async function unlinkIfSame(path, ino) {
71
+ try {
72
+ if ((await lstat(path)).ino !== ino)
73
+ return;
74
+ await unlink(path);
75
+ } catch (error) {
76
+ if (!isCode(error, "ENOENT"))
77
+ throw error;
78
+ }
79
+ }
80
+ async function createOwned(path, options, reclaimed) {
81
+ const identity = await machineIdentity(options.machineIdentity);
82
+ const instance = await readProcessInstance(process.pid);
83
+ const handle = await open(path, "wx", options.mode);
84
+ const owner = {
85
+ pid: process.pid,
86
+ process: instance,
87
+ host: hostname(),
88
+ acquiredAt: new Date().toISOString(),
89
+ ...identity !== null && { machine: identity }
90
+ };
91
+ let ino;
92
+ try {
93
+ ino = (await handle.stat()).ino;
94
+ await handle.chmod(options.mode);
95
+ await handle.writeFile(`${JSON.stringify(owner)}
96
+ `, "utf8");
97
+ } catch (error) {
98
+ await handle.close().catch(() => {
99
+ return;
100
+ });
101
+ if (ino !== undefined)
102
+ await unlinkIfSame(path, ino).catch(() => {
103
+ return;
104
+ });
105
+ throw error;
106
+ }
107
+ const ownedInode = ino;
108
+ let released = false;
109
+ return {
110
+ path,
111
+ owner,
112
+ reclaimed,
113
+ async release() {
114
+ if (released)
115
+ return;
116
+ released = true;
117
+ try {
118
+ await handle.close();
119
+ } finally {
120
+ await unlinkIfSame(path, ownedInode);
121
+ }
122
+ }
123
+ };
124
+ }
125
+ function reclaimable(state, diagnosis, graceMs) {
126
+ if (state.owner)
127
+ return diagnosis.liveness === "gone";
128
+ return typeof graceMs === "number" && Date.now() - state.mtimeMs >= graceMs;
129
+ }
130
+ async function reclaimUnderGuard(path, options) {
131
+ const guardPath = `${path}.reclaim`;
132
+ const acquired = await attemptExclusiveLock(guardPath, {
133
+ ...options,
134
+ ownerlessGraceMs: options.ownerlessGraceMs === undefined ? 5000 : options.ownerlessGraceMs
135
+ });
136
+ if (!("held" in acquired))
137
+ return;
138
+ const guard = acquired.held;
139
+ try {
140
+ const state = await readLockFile(path);
141
+ if (state) {
142
+ const diagnosis = await diagnose(state.owner, options.machineIdentity);
143
+ if (!reclaimable(state, diagnosis, options.ownerlessGraceMs))
144
+ return "refused";
145
+ await unlinkIfSame(path, state.ino);
146
+ }
147
+ return await createOwned(path, options, state !== undefined).catch((error) => {
148
+ if (isCode(error, "EEXIST"))
149
+ return "refused";
150
+ throw error;
151
+ });
152
+ } finally {
153
+ await guard.release().catch(() => {
154
+ return;
155
+ });
156
+ }
157
+ }
158
+ async function attemptExclusiveLock(path, options) {
159
+ try {
160
+ return { held: await createOwned(path, options, false) };
161
+ } catch (error) {
162
+ if (!isCode(error, "EEXIST"))
163
+ throw error;
164
+ if (!options.reclaim)
165
+ return { error };
166
+ let readFailure;
167
+ const state = await readLockFile(path, (cause) => {
168
+ readFailure = cause;
169
+ });
170
+ const diagnosis = {
171
+ ...await diagnose(state?.owner, options.machineIdentity),
172
+ ...readFailure !== undefined && { cause: readFailure }
173
+ };
174
+ if (!state || !reclaimable(state, diagnosis, options.ownerlessGraceMs)) {
175
+ return { error, diagnosis };
176
+ }
177
+ const reclaimed = await reclaimUnderGuard(path, options);
178
+ if (reclaimed !== undefined && reclaimed !== "refused")
179
+ return { held: reclaimed };
180
+ return { error, diagnosis };
181
+ }
182
+ }
183
+
184
+ export { attemptExclusiveLock };
@@ -0,0 +1,25 @@
1
+ // src/internal/abort-race.ts
2
+ function raceAbort(operation, signal) {
3
+ if (signal.aborted) {
4
+ operation.catch(() => {
5
+ return;
6
+ });
7
+ return Promise.reject(signal.reason);
8
+ }
9
+ return new Promise((resolve, reject) => {
10
+ const abort = () => {
11
+ signal.removeEventListener("abort", abort);
12
+ reject(signal.reason);
13
+ };
14
+ signal.addEventListener("abort", abort, { once: true });
15
+ operation.then((value) => {
16
+ signal.removeEventListener("abort", abort);
17
+ resolve(value);
18
+ }, (error) => {
19
+ signal.removeEventListener("abort", abort);
20
+ reject(error);
21
+ });
22
+ });
23
+ }
24
+
25
+ export { raceAbort };
@@ -0,0 +1,213 @@
1
+ import {
2
+ loadDarwinBinding
3
+ } from "./index-d4pa2bgs.js";
4
+ import {
5
+ isRecord
6
+ } from "./index-9jfdm5tg.js";
7
+
8
+ // src/internal/process-instance.ts
9
+ import { readFile as readFile2, readlink } from "node:fs/promises";
10
+ import { platform as platform2 } from "node:os";
11
+ import { z } from "zod";
12
+
13
+ // src/internal/process-identity.ts
14
+ import { execFile } from "node:child_process";
15
+ import { readFile } from "node:fs/promises";
16
+ import { platform } from "node:os";
17
+ import { promisify } from "node:util";
18
+ var detectedIdentity;
19
+ async function firstReadableLine(paths) {
20
+ for (const path of paths) {
21
+ try {
22
+ const text = (await readFile(path, "utf8")).trim();
23
+ if (text.length > 0)
24
+ return text;
25
+ } catch {}
26
+ }
27
+ return null;
28
+ }
29
+ var run = promisify(execFile);
30
+ function parsePlatformUuid(dump) {
31
+ return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(dump)?.[1] ?? null;
32
+ }
33
+ async function readDarwinPlatformUuid(command = "ioreg") {
34
+ try {
35
+ const { stdout } = await run(command, ["-rd1", "-c", "IOPlatformExpertDevice"], {
36
+ timeout: 2000,
37
+ maxBuffer: 1 << 20,
38
+ encoding: "utf8"
39
+ });
40
+ return parsePlatformUuid(stdout);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ async function machineIdentity(declared) {
46
+ if (declared !== undefined)
47
+ return declared;
48
+ if (detectedIdentity !== undefined)
49
+ return detectedIdentity;
50
+ detectedIdentity = platform() === "darwin" ? await readDarwinPlatformUuid() : await firstReadableLine(["/etc/machine-id", "/var/lib/dbus/machine-id"]);
51
+ return detectedIdentity;
52
+ }
53
+ async function probeLiveness(pid, reach = (target) => process.kill(target, 0)) {
54
+ try {
55
+ reach(pid);
56
+ } catch (error) {
57
+ return isRecord(error) && error.code === "ESRCH" ? "gone" : "alive";
58
+ }
59
+ return await isZombieProcess(pid) ? "gone" : "alive";
60
+ }
61
+ async function isZombieProcess(pid, seam = {}) {
62
+ const { procRoot = "/proc", psCommand = "ps" } = seam;
63
+ try {
64
+ const stat = await readFile(`${procRoot}/${pid}/stat`, "utf8");
65
+ return stat.slice(stat.lastIndexOf(")") + 1).trimStart().startsWith("Z");
66
+ } catch {}
67
+ try {
68
+ const { stdout } = await run(psCommand, ["-o", "state=", "-p", String(pid)], {
69
+ timeout: 2000,
70
+ maxBuffer: 1 << 16,
71
+ encoding: "utf8"
72
+ });
73
+ return stdout.trim().startsWith("Z");
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ // src/internal/process-instance.ts
80
+ var ProcessInstanceSchema2 = z.object({
81
+ platform: z.enum(["linux", "darwin"]),
82
+ bootId: z.string().min(1),
83
+ namespace: z.string().min(1),
84
+ startId: z.string().regex(/^\d+$/)
85
+ }).strict();
86
+ var ProcessInstanceObservationSchema = z.discriminatedUnion("state", [
87
+ z.object({ state: z.literal("observed"), instance: ProcessInstanceSchema2 }),
88
+ z.object({ state: z.literal("unavailable"), cause: z.unknown() })
89
+ ]);
90
+ async function observeProcessInstance2(pid) {
91
+ z.number().int().positive().max(2147483647).parse(pid);
92
+ return observeProcessInstanceAt(pid, "/proc");
93
+ }
94
+ function linuxProcessStart(stat, pid) {
95
+ if (!stat.startsWith(`${pid} (`))
96
+ return null;
97
+ const end = stat.lastIndexOf(")");
98
+ if (end < 0)
99
+ return null;
100
+ const start = stat.slice(end + 1).trim().split(/\s+/)[19];
101
+ return start !== undefined && /^\d+$/.test(start) ? start : null;
102
+ }
103
+ async function readProcessInstance(pid, procRoot = "/proc") {
104
+ const result = await observeProcessInstanceAt(pid, procRoot);
105
+ return result.state === "observed" ? result.instance : null;
106
+ }
107
+ async function observeProcessInstanceAt(pid, procRoot) {
108
+ try {
109
+ if (platform2() === "darwin") {
110
+ return {
111
+ state: "observed",
112
+ instance: ProcessInstanceSchema2.parse(loadDarwinBinding().processIdentity(pid))
113
+ };
114
+ }
115
+ if (platform2() !== "linux")
116
+ throw new Error("Process lifetime observation requires Linux or Darwin");
117
+ const [bootId, namespace, ownStatus, timeNamespace, ownStat, stat] = await Promise.all([
118
+ readFile2(`${procRoot}/sys/kernel/random/boot_id`, "utf8"),
119
+ readlink(`${procRoot}/self/ns/pid`),
120
+ readFile2(`${procRoot}/self/status`, "utf8"),
121
+ readlink(`${procRoot}/self/ns/time`).catch((error) => {
122
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
123
+ return "";
124
+ throw error;
125
+ }),
126
+ readFile2(`${procRoot}/self/stat`, "utf8"),
127
+ readFile2(`${procRoot}/${pid}/stat`, "utf8")
128
+ ]);
129
+ const namespacePids = ownStatus.match(/^NStgid:[ \t]*(.*)$/m)?.[1]?.trim().split(/\s+/);
130
+ if (namespacePids?.length !== 1 || namespacePids[0] !== String(process.pid) || linuxProcessStart(ownStat, process.pid) === null)
131
+ throw new Error("Process namespace evidence is partial or belongs to a foreign proc mount");
132
+ const startId = linuxProcessStart(stat, pid);
133
+ if (startId === null || !bootId.trim())
134
+ throw new Error("Process boot/start evidence is incomplete");
135
+ return {
136
+ state: "observed",
137
+ instance: {
138
+ platform: "linux",
139
+ bootId: bootId.trim(),
140
+ namespace: `${namespace};${timeNamespace}`,
141
+ startId
142
+ }
143
+ };
144
+ } catch (cause) {
145
+ return { state: "unavailable", cause };
146
+ }
147
+ }
148
+ var ProcessOwnerEvidenceSchema = z.object({
149
+ liveness: z.enum(["alive", "gone", "not-probed"]),
150
+ identity: z.enum([
151
+ "matched",
152
+ "different-boot",
153
+ "reused-pid",
154
+ "pid-gone",
155
+ "legacy",
156
+ "unavailable"
157
+ ]),
158
+ cause: z.unknown().optional()
159
+ });
160
+ async function probeProcessOwner2(pid, recorded) {
161
+ z.number().int().positive().max(2147483647).parse(pid);
162
+ if (recorded != null)
163
+ ProcessInstanceSchema2.parse(recorded);
164
+ return probeProcessOwnerWith(pid, recorded, {
165
+ read: readProcessInstance,
166
+ observe: observeProcessInstance2,
167
+ liveness: probeLiveness
168
+ });
169
+ }
170
+ async function probeProcessOwnerWith(pid, recorded, probes = { read: readProcessInstance, liveness: probeLiveness }) {
171
+ if (recorded === undefined)
172
+ return { liveness: await probes.liveness(pid), identity: "legacy" };
173
+ const unavailable = (cause) => ({
174
+ liveness: "not-probed",
175
+ identity: "unavailable",
176
+ cause
177
+ });
178
+ if (recorded === null)
179
+ return unavailable(new Error("Recorded process lifetime is unavailable"));
180
+ const observe = async (target) => {
181
+ if (probes.observe)
182
+ return probes.observe(target);
183
+ const instance = await probes.read(target);
184
+ return instance ? { state: "observed", instance } : {
185
+ state: "unavailable",
186
+ cause: new Error("Process lifetime reader returned no evidence")
187
+ };
188
+ };
189
+ const own = await observe(process.pid);
190
+ if (own.state === "unavailable")
191
+ return unavailable(own.cause);
192
+ const current = own.instance;
193
+ if (recorded.platform !== current.platform)
194
+ return unavailable(new Error("Process platforms differ"));
195
+ if (recorded.bootId !== current.bootId)
196
+ return { liveness: "gone", identity: "different-boot" };
197
+ if (recorded.namespace !== current.namespace)
198
+ return unavailable(new Error("Process namespaces differ"));
199
+ const liveness = await probes.liveness(pid);
200
+ if (liveness === "gone")
201
+ return { liveness, identity: "pid-gone" };
202
+ const observation = await observe(pid);
203
+ if (observation.state === "unavailable")
204
+ return unavailable(observation.cause);
205
+ const observed = observation.instance;
206
+ if (!observed || observed.bootId !== current.bootId || observed.namespace !== current.namespace)
207
+ return unavailable(new Error("Observed boot or namespace changed during probe"));
208
+ if (recorded.startId !== observed.startId)
209
+ return { liveness: "gone", identity: "reused-pid" };
210
+ return { liveness: "alive", identity: "matched" };
211
+ }
212
+
213
+ export { machineIdentity, ProcessInstanceSchema2, observeProcessInstance2, readProcessInstance, probeProcessOwner2 };