stitchkit 0.103.10 → 0.103.12
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.
- package/CHANGELOG.md +32 -0
- package/dist/agent-runtime/coding-shell-process.d.ts.map +1 -1
- package/dist/agent-runtime/sandbox-process-owner.d.ts +1 -1
- package/dist/agent-runtime/sandbox-process-owner.d.ts.map +1 -1
- package/dist/agent-runtime/store-conformance-lifecycle.d.ts +10 -10
- package/dist/durability/json.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/coding-tools.js +2 -1
- package/dist/entrypoints/agent-runtime/harness.js +2 -2
- package/dist/entrypoints/agent-runtime/sandbox.js +37 -19
- package/dist/entrypoints/agent-runtime.js +2 -2
- package/dist/entrypoints/application/diagnostic-journal.js +2 -2
- package/dist/entrypoints/files.js +8 -2
- package/dist/entrypoints/primitives.js +4 -3
- package/dist/entrypoints/process.js +1 -1
- package/dist/entrypoints/tools.js +1 -1
- package/dist/{index-ary5s7k0.js → index-69nvze1q.js} +1 -2
- package/dist/{index-a4ajxwmb.js → index-806d5phm.js} +114 -131
- package/dist/index-f330qqyg.js +653 -0
- package/dist/index-fm1c1a08.js +138 -0
- package/dist/index-xvdd2rbb.js +265 -0
- package/dist/{index-9jb61j5a.js → index-zp4jpkp0.js} +1 -1
- package/dist/internal/exclusive-lock-read.d.ts +20 -0
- package/dist/internal/exclusive-lock-read.d.ts.map +1 -0
- package/dist/internal/exclusive-lock.d.ts +2 -1
- package/dist/internal/exclusive-lock.d.ts.map +1 -1
- package/dist/internal/json-data.d.ts +8 -2
- package/dist/internal/json-data.d.ts.map +1 -1
- package/dist/internal/json-string-size.d.ts +3 -0
- package/dist/internal/json-string-size.d.ts.map +1 -0
- package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
- package/dist/primitives/canonical-json.d.ts.map +1 -1
- package/dist/process/cleanup.d.ts +12 -0
- package/dist/process/cleanup.d.ts.map +1 -0
- package/dist/process/command-lifetime.d.ts +19 -0
- package/dist/process/command-lifetime.d.ts.map +1 -0
- package/dist/process/command-owner.d.ts +4 -2
- package/dist/process/command-owner.d.ts.map +1 -1
- package/dist/process/contract.d.ts +12 -1
- package/dist/process/contract.d.ts.map +1 -1
- package/dist/process/group.d.ts +6 -1
- package/dist/process/group.d.ts.map +1 -1
- package/dist/process/launch.d.ts +74 -0
- package/dist/process/launch.d.ts.map +1 -0
- package/dist/process/leader-settlement.d.ts +2 -0
- package/dist/process/leader-settlement.d.ts.map +1 -1
- package/dist/process/owned-child.d.ts +10 -0
- package/dist/process/owned-child.d.ts.map +1 -0
- package/dist/process/start-failure.d.ts +4 -0
- package/dist/process/start-failure.d.ts.map +1 -0
- package/dist/process/transport.d.ts +30 -0
- package/dist/process/transport.d.ts.map +1 -0
- package/llms/agent-runtime-sandbox.txt +9 -1
- package/llms/agent-runtime.txt +7 -1
- package/llms/process.txt +70 -7
- package/llms-full.txt +86 -9
- package/llms.txt +3 -3
- package/package.json +2 -2
- package/dist/index-6m6a9ty4.js +0 -285
- package/dist/index-c1jmtxa0.js +0 -74
- package/dist/index-ga597cvy.js +0 -184
package/dist/index-6m6a9ty4.js
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
raceAbort
|
|
3
|
-
} from "./index-mbnnw3f2.js";
|
|
4
|
-
|
|
5
|
-
// src/process/contract.ts
|
|
6
|
-
import { z } from "zod";
|
|
7
|
-
var NativeCommandSettlementSchema = z.discriminatedUnion("kind", [
|
|
8
|
-
z.object({
|
|
9
|
-
kind: z.literal("exit"),
|
|
10
|
-
exitCode: z.number().int().nullable(),
|
|
11
|
-
signal: z.string().nullable()
|
|
12
|
-
}),
|
|
13
|
-
z.object({ kind: z.literal("error"), cause: z.unknown() })
|
|
14
|
-
]);
|
|
15
|
-
var NativeCommandOptionsSchema = z.strictObject({
|
|
16
|
-
executable: z.string().min(1),
|
|
17
|
-
args: z.array(z.string()).default([]),
|
|
18
|
-
cwd: z.string().optional(),
|
|
19
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
20
|
-
envPolicy: z.enum(["declared-only", "ambient"]).default("declared-only"),
|
|
21
|
-
signal: z.custom((v) => v instanceof AbortSignal).optional(),
|
|
22
|
-
timeoutMs: z.number().int().positive().max(2147483647).optional(),
|
|
23
|
-
maxOutputBytes: z.number().int().positive().optional(),
|
|
24
|
-
capture: z.boolean().default(false),
|
|
25
|
-
stdin: z.custom((v) => v instanceof Uint8Array).optional(),
|
|
26
|
-
maxStdinBytes: z.number().int().positive().default(1024 * 1024),
|
|
27
|
-
onOutput: z.custom((v) => typeof v === "function").optional(),
|
|
28
|
-
onLeaderSettled: z.custom((v) => typeof v === "function").optional(),
|
|
29
|
-
killGraceMs: z.number().int().nonnegative().max(1e4).default(100),
|
|
30
|
-
cleanupTimeoutMs: z.number().int().positive().max(30000).default(2000)
|
|
31
|
-
}).superRefine((input, ctx) => {
|
|
32
|
-
if (!input.signal && input.timeoutMs === undefined)
|
|
33
|
-
ctx.addIssue({ code: "custom", message: "Declare a caller signal or a finite timeout" });
|
|
34
|
-
if (input.capture && input.maxOutputBytes === undefined)
|
|
35
|
-
ctx.addIssue({
|
|
36
|
-
code: "custom",
|
|
37
|
-
message: "Capture requires a finite combined output budget"
|
|
38
|
-
});
|
|
39
|
-
if (input.stdin && input.stdin.byteLength > input.maxStdinBytes)
|
|
40
|
-
ctx.addIssue({ code: "custom", message: "stdin exceeds maxStdinBytes" });
|
|
41
|
-
});
|
|
42
|
-
var NativeCommandResultSchema = z.object({
|
|
43
|
-
exitCode: z.number().int().nullable(),
|
|
44
|
-
signal: z.string().nullable(),
|
|
45
|
-
stdout: z.instanceof(Uint8Array),
|
|
46
|
-
stderr: z.instanceof(Uint8Array)
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
class NativeCommandError2 extends Error {
|
|
50
|
-
code;
|
|
51
|
-
constructor(code, message, options) {
|
|
52
|
-
super(message, options);
|
|
53
|
-
this.code = code;
|
|
54
|
-
this.name = "NativeCommandError";
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// src/process/command-owner.ts
|
|
59
|
-
import { spawn } from "node:child_process";
|
|
60
|
-
|
|
61
|
-
// src/process/group.ts
|
|
62
|
-
function deniedOnDarwin(error) {
|
|
63
|
-
return process.platform === "darwin" && error instanceof Error && "code" in error && error.code === "EPERM";
|
|
64
|
-
}
|
|
65
|
-
function signalGroup(pid, signal) {
|
|
66
|
-
if (pid === undefined)
|
|
67
|
-
return;
|
|
68
|
-
try {
|
|
69
|
-
process.kill(-pid, signal);
|
|
70
|
-
} catch (error) {
|
|
71
|
-
if (!(error instanceof Error && ("code" in error) && error.code === "ESRCH"))
|
|
72
|
-
throw error;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function exists(pid) {
|
|
76
|
-
if (pid === undefined)
|
|
77
|
-
return false;
|
|
78
|
-
try {
|
|
79
|
-
process.kill(-pid, 0);
|
|
80
|
-
return true;
|
|
81
|
-
} catch (error) {
|
|
82
|
-
if (error instanceof Error && "code" in error && error.code === "ESRCH")
|
|
83
|
-
return false;
|
|
84
|
-
if (deniedOnDarwin(error))
|
|
85
|
-
return true;
|
|
86
|
-
throw error;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
async function signalUntilSettled(pid, signal, deadline) {
|
|
90
|
-
for (;; ) {
|
|
91
|
-
try {
|
|
92
|
-
signalGroup(pid, signal);
|
|
93
|
-
return;
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (!deniedOnDarwin(error) || Date.now() >= deadline)
|
|
96
|
-
throw error;
|
|
97
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(10, deadline - Date.now())));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async function stopCommandGroup(pid, graceMs, cleanupTimeoutMs = 2000) {
|
|
102
|
-
const deadline = Date.now() + graceMs + cleanupTimeoutMs;
|
|
103
|
-
await signalUntilSettled(pid, "SIGTERM", deadline);
|
|
104
|
-
const until = Date.now() + graceMs;
|
|
105
|
-
while (Date.now() < until && exists(pid))
|
|
106
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(10, until - Date.now())));
|
|
107
|
-
await signalUntilSettled(pid, "SIGKILL", deadline);
|
|
108
|
-
}
|
|
109
|
-
function commandCleanupError(cause) {
|
|
110
|
-
return new NativeCommandError2("COMMAND_CLEANUP", "Command cleanup did not complete", {
|
|
111
|
-
cause
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
function waitForCommandClose(closed, timeoutMs) {
|
|
115
|
-
return raceAbort(closed, AbortSignal.timeout(timeoutMs));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// src/process/leader-settlement.ts
|
|
119
|
-
function createLeaderSettlement(options, signal) {
|
|
120
|
-
let pending;
|
|
121
|
-
return (event) => {
|
|
122
|
-
if (pending)
|
|
123
|
-
return pending;
|
|
124
|
-
pending = (async () => {
|
|
125
|
-
if (!options.onLeaderSettled)
|
|
126
|
-
return;
|
|
127
|
-
const controller = new AbortController;
|
|
128
|
-
const timer = setTimeout(() => controller.abort(new Error("Leader settlement deadline exceeded")), options.cleanupTimeoutMs);
|
|
129
|
-
const abort = () => controller.abort(signal.reason);
|
|
130
|
-
if (!signal.aborted)
|
|
131
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
132
|
-
try {
|
|
133
|
-
await raceAbort(Promise.resolve(options.onLeaderSettled(event, controller.signal)), controller.signal);
|
|
134
|
-
} catch (cause) {
|
|
135
|
-
if (signal.aborted && cause === signal.reason)
|
|
136
|
-
throw cause;
|
|
137
|
-
throw commandCleanupError(new AggregateError([event, cause], "Leader settlement failed"));
|
|
138
|
-
} finally {
|
|
139
|
-
clearTimeout(timer);
|
|
140
|
-
signal.removeEventListener("abort", abort);
|
|
141
|
-
controller.abort(new Error("Leader settlement completed"));
|
|
142
|
-
}
|
|
143
|
-
})();
|
|
144
|
-
pending.catch(() => {
|
|
145
|
-
return;
|
|
146
|
-
});
|
|
147
|
-
return pending;
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// src/process/command-owner.ts
|
|
152
|
-
function startNativeCommand(input, onSpawn) {
|
|
153
|
-
const options = NativeCommandOptionsSchema.parse(input);
|
|
154
|
-
options.signal?.throwIfAborted();
|
|
155
|
-
if (process.platform === "win32")
|
|
156
|
-
throw new NativeCommandError2("COMMAND_UNAVAILABLE", "POSIX process groups are required");
|
|
157
|
-
const controller = new AbortController;
|
|
158
|
-
const abort = () => controller.abort(options.signal?.reason);
|
|
159
|
-
options.signal?.addEventListener("abort", abort, { once: true });
|
|
160
|
-
if (options.signal?.aborted)
|
|
161
|
-
abort();
|
|
162
|
-
const timer = options.timeoutMs === undefined ? undefined : setTimeout(() => controller.abort(new NativeCommandError2("COMMAND_LIMIT", "Command deadline exceeded")), options.timeoutMs);
|
|
163
|
-
let child;
|
|
164
|
-
try {
|
|
165
|
-
child = spawn(options.executable, options.args, {
|
|
166
|
-
cwd: options.cwd,
|
|
167
|
-
env: options.envPolicy === "ambient" ? { ...process.env, ...options.env } : options.env ?? {},
|
|
168
|
-
detached: true,
|
|
169
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
170
|
-
});
|
|
171
|
-
} catch (cause) {
|
|
172
|
-
if (timer !== undefined)
|
|
173
|
-
clearTimeout(timer);
|
|
174
|
-
options.signal?.removeEventListener("abort", abort);
|
|
175
|
-
throw new NativeCommandError2("COMMAND_UNAVAILABLE", "Command could not start", { cause });
|
|
176
|
-
}
|
|
177
|
-
const stdout = [];
|
|
178
|
-
const stderr = [];
|
|
179
|
-
let total = 0;
|
|
180
|
-
let finished = false;
|
|
181
|
-
let spawnFailure;
|
|
182
|
-
const settle = createLeaderSettlement(options, controller.signal);
|
|
183
|
-
const leader = new Promise((resolve) => {
|
|
184
|
-
child.once("exit", (exitCode, signal) => resolve({ kind: "exit", exitCode, signal }));
|
|
185
|
-
child.once("error", (cause) => resolve({ kind: "error", cause }));
|
|
186
|
-
});
|
|
187
|
-
const pendingHandles = new Set(["leader", "stdin", "stdout", "stderr"]);
|
|
188
|
-
const released = Promise.all([
|
|
189
|
-
leader.then(() => pendingHandles.delete("leader")),
|
|
190
|
-
...["stdin", "stdout", "stderr"].map((name) => new Promise((resolve) => child[name].once("close", () => {
|
|
191
|
-
pendingHandles.delete(name);
|
|
192
|
-
resolve();
|
|
193
|
-
})))
|
|
194
|
-
]);
|
|
195
|
-
const closed = new Promise((resolve) => {
|
|
196
|
-
child.once("error", (error) => {
|
|
197
|
-
spawnFailure = new NativeCommandError2("COMMAND_UNAVAILABLE", "Command could not start", {
|
|
198
|
-
cause: error
|
|
199
|
-
});
|
|
200
|
-
controller.abort(spawnFailure);
|
|
201
|
-
});
|
|
202
|
-
child.once("close", (exitCode, signal) => resolve({ exitCode, signal }));
|
|
203
|
-
});
|
|
204
|
-
child.stdin.on("error", (error) => {
|
|
205
|
-
if (!(("code" in error) && error.code === "EPIPE"))
|
|
206
|
-
controller.abort(error);
|
|
207
|
-
});
|
|
208
|
-
const drain = async (stream, channel, captured) => {
|
|
209
|
-
for await (const chunk of stream) {
|
|
210
|
-
controller.signal.throwIfAborted();
|
|
211
|
-
if (!(chunk instanceof Uint8Array))
|
|
212
|
-
throw new TypeError("Expected binary command output");
|
|
213
|
-
total += chunk.byteLength;
|
|
214
|
-
if (options.maxOutputBytes !== undefined && total > options.maxOutputBytes)
|
|
215
|
-
throw new NativeCommandError2("COMMAND_LIMIT", "Command output budget exceeded");
|
|
216
|
-
if (options.capture)
|
|
217
|
-
captured.push(Uint8Array.from(chunk));
|
|
218
|
-
if (options.onOutput)
|
|
219
|
-
await raceAbort(Promise.resolve().then(() => {
|
|
220
|
-
controller.signal.throwIfAborted();
|
|
221
|
-
return options.onOutput?.(chunk, channel, controller.signal);
|
|
222
|
-
}), controller.signal);
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
const result = (async () => {
|
|
226
|
-
try {
|
|
227
|
-
onSpawn?.(child);
|
|
228
|
-
child.stdin.end(options.stdin);
|
|
229
|
-
const drains = Promise.all([
|
|
230
|
-
drain(child.stdout, "stdout", stdout),
|
|
231
|
-
drain(child.stderr, "stderr", stderr)
|
|
232
|
-
]);
|
|
233
|
-
await raceAbort(Promise.all([drains, leader.then(settle)]), controller.signal);
|
|
234
|
-
const status = await raceAbort(closed, controller.signal);
|
|
235
|
-
if (spawnFailure !== undefined)
|
|
236
|
-
throw spawnFailure;
|
|
237
|
-
return { ...status, stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr) };
|
|
238
|
-
} catch (error) {
|
|
239
|
-
controller.abort(error);
|
|
240
|
-
child.stdin.destroy();
|
|
241
|
-
child.stdout.destroy();
|
|
242
|
-
child.stderr.destroy();
|
|
243
|
-
const cleanup = await Promise.allSettled([
|
|
244
|
-
settle({ kind: "error", cause: error }),
|
|
245
|
-
stopCommandGroup(child.pid, options.killGraceMs, options.cleanupTimeoutMs).then(async () => {
|
|
246
|
-
try {
|
|
247
|
-
await waitForCommandClose(released, options.cleanupTimeoutMs);
|
|
248
|
-
} catch (cause) {
|
|
249
|
-
throw new Error(`Command handles remained open: ${[...pendingHandles].join(", ")}`, {
|
|
250
|
-
cause
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
})
|
|
254
|
-
]);
|
|
255
|
-
const failures = cleanup.flatMap((item) => item.status === "rejected" && item.reason !== error ? [item.reason] : []);
|
|
256
|
-
if (failures.length) {
|
|
257
|
-
throw commandCleanupError(new AggregateError([error, ...failures], "Command failed and cleanup failed"));
|
|
258
|
-
}
|
|
259
|
-
throw error;
|
|
260
|
-
} finally {
|
|
261
|
-
finished = true;
|
|
262
|
-
if (timer !== undefined)
|
|
263
|
-
clearTimeout(timer);
|
|
264
|
-
options.signal?.removeEventListener("abort", abort);
|
|
265
|
-
controller.abort(new Error("Command completed"));
|
|
266
|
-
}
|
|
267
|
-
})();
|
|
268
|
-
result.catch(() => {
|
|
269
|
-
return;
|
|
270
|
-
});
|
|
271
|
-
return {
|
|
272
|
-
result,
|
|
273
|
-
async stop() {
|
|
274
|
-
if (!finished)
|
|
275
|
-
controller.abort(new DOMException("Command stopped", "AbortError"));
|
|
276
|
-
await result.then(() => {
|
|
277
|
-
return;
|
|
278
|
-
}, () => {
|
|
279
|
-
return;
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export { NativeCommandError2, startNativeCommand };
|
package/dist/index-c1jmtxa0.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
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 };
|
package/dist/index-ga597cvy.js
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
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 };
|