@warlock.js/process 0.0.0-stage → 5.33.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.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +59 -2
- package/cjs/index.cjs +1020 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/guardian.mjs +131 -0
- package/esm/guardian.mjs.map +1 -0
- package/esm/identity.d.mts +7 -0
- package/esm/identity.d.mts.map +1 -0
- package/esm/identity.mjs +86 -0
- package/esm/identity.mjs.map +1 -0
- package/esm/index.d.mts +5 -0
- package/esm/index.mjs +5 -0
- package/esm/platform.d.mts +7 -0
- package/esm/platform.d.mts.map +1 -0
- package/esm/platform.mjs +156 -0
- package/esm/platform.mjs.map +1 -0
- package/esm/registry.mjs +118 -0
- package/esm/registry.mjs.map +1 -0
- package/esm/resources.mjs +85 -0
- package/esm/resources.mjs.map +1 -0
- package/esm/supervisor.d.mts +5 -0
- package/esm/supervisor.d.mts.map +1 -0
- package/esm/supervisor.mjs +443 -0
- package/esm/supervisor.mjs.map +1 -0
- package/esm/types.d.mts +84 -0
- package/esm/types.d.mts.map +1 -0
- package/llms-full.txt +135 -0
- package/llms.txt +9 -0
- package/package.json +49 -4
- package/skills/supervise-processes/SKILL.md +125 -0
package/esm/platform.mjs
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
//#region ../../packages/process/src/platform.ts
|
|
2
|
+
var UnsupportedProcessPlatformError = class extends Error {
|
|
3
|
+
platform;
|
|
4
|
+
constructor(platform) {
|
|
5
|
+
super(`Process supervision is unsupported on ${platform}`);
|
|
6
|
+
this.platform = platform;
|
|
7
|
+
this.name = "UnsupportedProcessPlatformError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
function supported(platform) {
|
|
11
|
+
if (![
|
|
12
|
+
"linux",
|
|
13
|
+
"darwin",
|
|
14
|
+
"win32"
|
|
15
|
+
].includes(platform)) throw new UnsupportedProcessPlatformError(platform);
|
|
16
|
+
}
|
|
17
|
+
function validPid(pid) {
|
|
18
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) throw new Error("Invalid process PID");
|
|
19
|
+
}
|
|
20
|
+
/** Apple ps(1): lstart uses strftime %c. LC_ALL=C gives %a %b %e %H:%M:%S %Y.
|
|
21
|
+
* https://github.com/apple-oss-distributions/adv_cmds/blob/main/ps/ps.1
|
|
22
|
+
* https://github.com/apple-oss-distributions/adv_cmds/blob/main/ps/print.c (lstarted)
|
|
23
|
+
* https://github.com/apple-oss-distributions/Libc/blob/main/stdtime/FreeBSD/timelocal.c (_C_time_locale)
|
|
24
|
+
*/
|
|
25
|
+
function parseDarwinStart(source) {
|
|
26
|
+
const match = /^(Sun|Mon|Tue|Wed|Thu|Fri|Sat) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2}) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/.exec(source.trim());
|
|
27
|
+
if (!match) throw new Error("Invalid macOS process start time");
|
|
28
|
+
const [, weekday, month, day, hour, minute, second, year] = match;
|
|
29
|
+
const monthIndex = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" ").indexOf(month ?? "");
|
|
30
|
+
const date = new Date(Date.UTC(Number(year), monthIndex, Number(day), Number(hour), Number(minute), Number(second)));
|
|
31
|
+
if (date.getUTCFullYear() !== Number(year) || date.getUTCMonth() !== monthIndex || date.getUTCDate() !== Number(day) || date.getUTCHours() !== Number(hour) || date.getUTCMinutes() !== Number(minute) || date.getUTCSeconds() !== Number(second) || "Sun Mon Tue Wed Thu Fri Sat".split(" ")[date.getUTCDay()] !== weekday) throw new Error("Invalid macOS process start time");
|
|
32
|
+
return String(date.getTime() / 1e3);
|
|
33
|
+
}
|
|
34
|
+
function darwinCommand(pid, tree = false) {
|
|
35
|
+
validPid(pid);
|
|
36
|
+
return {
|
|
37
|
+
file: "/bin/ps",
|
|
38
|
+
args: [
|
|
39
|
+
...tree ? ["-ax"] : ["-p", String(pid)],
|
|
40
|
+
"-o",
|
|
41
|
+
"pid=,ppid=,pgid=,state=,rss=,lstart="
|
|
42
|
+
],
|
|
43
|
+
env: {
|
|
44
|
+
LC_ALL: "C",
|
|
45
|
+
TZ: "UTC"
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function parseDarwinRows(source) {
|
|
50
|
+
return source.trim() ? source.trim().split("\n").map((line) => {
|
|
51
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\d+)\s+(.+?)\s*$/.exec(line);
|
|
52
|
+
if (!match) throw new Error("Invalid macOS process observation");
|
|
53
|
+
return {
|
|
54
|
+
pid: Number(match[1]),
|
|
55
|
+
parent: Number(match[2]),
|
|
56
|
+
group: Number(match[3]),
|
|
57
|
+
state: match[4] ?? "",
|
|
58
|
+
rss: Number(match[5]) * 1024,
|
|
59
|
+
start: parseDarwinStart(match[6] ?? "")
|
|
60
|
+
};
|
|
61
|
+
}) : [];
|
|
62
|
+
}
|
|
63
|
+
/** CIM CreationDate is a DateTime; serialize UTC ticks as text, never a locale date
|
|
64
|
+
* or JSON number (100ns ticks exceed Number's precision). WorkingSetSize is bytes.
|
|
65
|
+
* https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process
|
|
66
|
+
*/
|
|
67
|
+
function windowsCommand(pid, tree = false) {
|
|
68
|
+
validPid(pid);
|
|
69
|
+
return {
|
|
70
|
+
file: "powershell.exe",
|
|
71
|
+
args: [
|
|
72
|
+
"-NoLogo",
|
|
73
|
+
"-NoProfile",
|
|
74
|
+
"-NonInteractive",
|
|
75
|
+
"-Command",
|
|
76
|
+
[
|
|
77
|
+
"$ErrorActionPreference = 'Stop'",
|
|
78
|
+
`$rows = @(${`Get-CimInstance Win32_Process${tree ? "" : ` -Filter 'ProcessId=${pid}'`}`} | ForEach-Object {`,
|
|
79
|
+
`if ([int]$_.ProcessId -eq ${pid} -and $null -eq $_.CreationDate) { throw 'Cannot read process creation time' }`,
|
|
80
|
+
"if ($null -ne $_.CreationDate) {",
|
|
81
|
+
"[pscustomobject]@{ pid = [int]$_.ProcessId; parent = [int]$_.ParentProcessId; start = $_.CreationDate.ToUniversalTime().Ticks.ToString([Globalization.CultureInfo]::InvariantCulture); rss = [double]$_.WorkingSetSize }",
|
|
82
|
+
"}",
|
|
83
|
+
"})",
|
|
84
|
+
"ConvertTo-Json -InputObject $rows -Compress"
|
|
85
|
+
].join("\n")
|
|
86
|
+
]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function parseWindowsRows(source) {
|
|
90
|
+
const value = JSON.parse(source.replace(/^\uFEFF/, ""));
|
|
91
|
+
if (!Array.isArray(value)) throw new Error("Invalid Windows process observation");
|
|
92
|
+
return value.map((row) => {
|
|
93
|
+
if (!row || typeof row !== "object" || !("pid" in row) || typeof row.pid !== "number" || !Number.isSafeInteger(row.pid) || row.pid < 0 || !("parent" in row) || typeof row.parent !== "number" || !Number.isSafeInteger(row.parent) || row.parent < 0 || !("start" in row) || typeof row.start !== "string" || !/^\d{18}$/.test(row.start) || !("rss" in row) || typeof row.rss !== "number" || !Number.isFinite(row.rss) || row.rss < 0) throw new Error("Invalid Windows process observation");
|
|
94
|
+
return {
|
|
95
|
+
pid: row.pid,
|
|
96
|
+
parent: row.parent,
|
|
97
|
+
start: row.start,
|
|
98
|
+
rss: row.rss,
|
|
99
|
+
group: row.pid,
|
|
100
|
+
state: "R"
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function taskkillCommand(pid) {
|
|
105
|
+
validPid(pid);
|
|
106
|
+
return {
|
|
107
|
+
file: "taskkill.exe",
|
|
108
|
+
args: [
|
|
109
|
+
"/PID",
|
|
110
|
+
String(pid),
|
|
111
|
+
"/T",
|
|
112
|
+
"/F"
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function treeMemory(rows, pid) {
|
|
117
|
+
const root = rows.find((row) => row.pid === pid);
|
|
118
|
+
if (!root) return 0;
|
|
119
|
+
const included = /* @__PURE__ */ new Map([[pid, root]]);
|
|
120
|
+
let changed = true;
|
|
121
|
+
while (changed) {
|
|
122
|
+
changed = false;
|
|
123
|
+
for (const row of rows) {
|
|
124
|
+
const parent = included.get(row.parent);
|
|
125
|
+
if (!included.has(row.pid) && parent && BigInt(row.start) >= BigInt(parent.start)) {
|
|
126
|
+
included.set(row.pid, row);
|
|
127
|
+
changed = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return [...included.values()].reduce((total, row) => total + row.rss, 0);
|
|
132
|
+
}
|
|
133
|
+
/** All OS effects are injected; foreign-platform specs never execute real commands. */
|
|
134
|
+
async function portableIdentity(platform, pid, token, execute, readProof) {
|
|
135
|
+
validPid(pid);
|
|
136
|
+
const command = platform === "darwin" ? darwinCommand : windowsCommand;
|
|
137
|
+
const parse = platform === "darwin" ? parseDarwinRows : parseWindowsRows;
|
|
138
|
+
const rows = parse(await execute(command(pid, token)));
|
|
139
|
+
const before = rows.find((row) => row.pid === pid);
|
|
140
|
+
if (!before || /^[ZX]/.test(before.state)) return void 0;
|
|
141
|
+
if (!token) return before;
|
|
142
|
+
const proof = await readProof(pid);
|
|
143
|
+
const after = parse(await execute(command(pid))).find((row) => row.pid === pid);
|
|
144
|
+
if (!after || after.start !== before.start || after.group !== before.group || /^[ZX]/.test(after.state)) return void 0;
|
|
145
|
+
return {
|
|
146
|
+
start: after.start,
|
|
147
|
+
group: after.group,
|
|
148
|
+
state: proof?.pid === pid && proof.gated ? "T" : after.state,
|
|
149
|
+
rss: treeMemory(rows, pid),
|
|
150
|
+
token: proof?.pid === pid ? proof.token : void 0
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
export { UnsupportedProcessPlatformError, darwinCommand, parseDarwinRows, parseDarwinStart, parseWindowsRows, portableIdentity, supported, taskkillCommand, treeMemory, windowsCommand };
|
|
156
|
+
//# sourceMappingURL=platform.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.mjs","names":[],"sources":["../../../../../../../packages/process/src/platform.ts"],"sourcesContent":["import type { Identity } from \"./identity\";\n\nexport type PortablePlatform = \"darwin\" | \"win32\";\nexport type Command = { file: string; args: string[]; env?: Record<string, string> };\nexport type Execute = (command: Command) => Promise<string>;\nexport type OwnershipProof = { pid: number; token: string; gated: boolean };\nexport type ReadProof = (pid: number) => Promise<OwnershipProof | undefined>;\ntype Row = {\n pid: number;\n parent: number;\n start: string;\n rss: number;\n group: number;\n state: string;\n};\n\nexport class UnsupportedProcessPlatformError extends Error {\n constructor(public readonly platform: string) {\n super(`Process supervision is unsupported on ${platform}`);\n this.name = \"UnsupportedProcessPlatformError\";\n }\n}\nexport function supported(platform: string): void {\n if (![\"linux\", \"darwin\", \"win32\"].includes(platform))\n throw new UnsupportedProcessPlatformError(platform);\n}\nfunction validPid(pid: number): void {\n if (!Number.isSafeInteger(pid) || pid <= 1) throw new Error(\"Invalid process PID\");\n}\n\n/** Apple ps(1): lstart uses strftime %c. LC_ALL=C gives %a %b %e %H:%M:%S %Y.\n * https://github.com/apple-oss-distributions/adv_cmds/blob/main/ps/ps.1\n * https://github.com/apple-oss-distributions/adv_cmds/blob/main/ps/print.c (lstarted)\n * https://github.com/apple-oss-distributions/Libc/blob/main/stdtime/FreeBSD/timelocal.c (_C_time_locale)\n */\nexport function parseDarwinStart(source: string): string {\n const match =\n /^(Sun|Mon|Tue|Wed|Thu|Fri|Sat) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s+(\\d{1,2}) (\\d{2}):(\\d{2}):(\\d{2}) (\\d{4})$/.exec(\n source.trim(),\n );\n if (!match) throw new Error(\"Invalid macOS process start time\");\n const [, weekday, month, day, hour, minute, second, year] = match;\n const monthIndex = \"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec\"\n .split(\" \")\n .indexOf(month ?? \"\");\n const date = new Date(\n Date.UTC(Number(year), monthIndex, Number(day), Number(hour), Number(minute), Number(second)),\n );\n if (\n date.getUTCFullYear() !== Number(year) ||\n date.getUTCMonth() !== monthIndex ||\n date.getUTCDate() !== Number(day) ||\n date.getUTCHours() !== Number(hour) ||\n date.getUTCMinutes() !== Number(minute) ||\n date.getUTCSeconds() !== Number(second) ||\n \"Sun Mon Tue Wed Thu Fri Sat\".split(\" \")[date.getUTCDay()] !== weekday\n )\n throw new Error(\"Invalid macOS process start time\");\n return String(date.getTime() / 1000);\n}\nexport function darwinCommand(pid: number, tree = false): Command {\n validPid(pid);\n return {\n file: \"/bin/ps\",\n args: [...(tree ? [\"-ax\"] : [\"-p\", String(pid)]), \"-o\", \"pid=,ppid=,pgid=,state=,rss=,lstart=\"],\n env: { LC_ALL: \"C\", TZ: \"UTC\" },\n };\n}\nexport function parseDarwinRows(source: string): Row[] {\n return source.trim()\n ? source\n .trim()\n .split(\"\\n\")\n .map((line) => {\n const match = /^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\S+)\\s+(\\d+)\\s+(.+?)\\s*$/.exec(line);\n if (!match) throw new Error(\"Invalid macOS process observation\");\n return {\n pid: Number(match[1]),\n parent: Number(match[2]),\n group: Number(match[3]),\n state: match[4] ?? \"\",\n rss: Number(match[5]) * 1024,\n start: parseDarwinStart(match[6] ?? \"\"),\n };\n })\n : [];\n}\n\n/** CIM CreationDate is a DateTime; serialize UTC ticks as text, never a locale date\n * or JSON number (100ns ticks exceed Number's precision). WorkingSetSize is bytes.\n * https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process\n */\nexport function windowsCommand(pid: number, tree = false): Command {\n validPid(pid);\n const query = `Get-CimInstance Win32_Process${tree ? \"\" : ` -Filter 'ProcessId=${pid}'`}`;\n const script = [\n \"$ErrorActionPreference = 'Stop'\",\n `$rows = @(${query} | ForEach-Object {`,\n `if ([int]$_.ProcessId -eq ${pid} -and $null -eq $_.CreationDate) { throw 'Cannot read process creation time' }`,\n \"if ($null -ne $_.CreationDate) {\",\n \"[pscustomobject]@{ pid = [int]$_.ProcessId; parent = [int]$_.ParentProcessId; start = $_.CreationDate.ToUniversalTime().Ticks.ToString([Globalization.CultureInfo]::InvariantCulture); rss = [double]$_.WorkingSetSize }\",\n \"}\",\n \"})\",\n \"ConvertTo-Json -InputObject $rows -Compress\",\n ].join(\"\\n\");\n return {\n file: \"powershell.exe\",\n args: [\"-NoLogo\", \"-NoProfile\", \"-NonInteractive\", \"-Command\", script],\n };\n}\nexport function parseWindowsRows(source: string): Row[] {\n const value: unknown = JSON.parse(source.replace(/^\\uFEFF/, \"\"));\n if (!Array.isArray(value)) throw new Error(\"Invalid Windows process observation\");\n return value.map((row: unknown) => {\n if (\n !row ||\n typeof row !== \"object\" ||\n !(\"pid\" in row) ||\n typeof row.pid !== \"number\" ||\n !Number.isSafeInteger(row.pid) ||\n row.pid < 0 ||\n !(\"parent\" in row) ||\n typeof row.parent !== \"number\" ||\n !Number.isSafeInteger(row.parent) ||\n row.parent < 0 ||\n !(\"start\" in row) ||\n typeof row.start !== \"string\" ||\n !/^\\d{18}$/.test(row.start) ||\n !(\"rss\" in row) ||\n typeof row.rss !== \"number\" ||\n !Number.isFinite(row.rss) ||\n row.rss < 0\n )\n throw new Error(\"Invalid Windows process observation\");\n return {\n pid: row.pid,\n parent: row.parent,\n start: row.start,\n rss: row.rss,\n group: row.pid,\n state: \"R\",\n };\n });\n}\nexport function taskkillCommand(pid: number): Command {\n validPid(pid);\n // Never accept an image name, filter, wildcard or shell interpolation.\n return { file: \"taskkill.exe\", args: [\"/PID\", String(pid), \"/T\", \"/F\"] };\n}\nexport function treeMemory(rows: Row[], pid: number): number {\n const root = rows.find((row) => row.pid === pid);\n if (!root) return 0;\n const included = new Map<number, Row>([[pid, root]]);\n let changed = true;\n while (changed) {\n changed = false;\n for (const row of rows) {\n const parent = included.get(row.parent);\n // Reject a reused parent PID whose creation time is later than the child.\n if (!included.has(row.pid) && parent && BigInt(row.start) >= BigInt(parent.start)) {\n included.set(row.pid, row);\n changed = true;\n }\n }\n }\n return [...included.values()].reduce((total, row) => total + row.rss, 0);\n}\n\n/** All OS effects are injected; foreign-platform specs never execute real commands. */\nexport async function portableIdentity(\n platform: PortablePlatform,\n pid: number,\n token: boolean,\n execute: Execute,\n readProof: ReadProof,\n): Promise<Identity | undefined> {\n validPid(pid);\n const command = platform === \"darwin\" ? darwinCommand : windowsCommand;\n const parse = platform === \"darwin\" ? parseDarwinRows : parseWindowsRows;\n const rows = parse(await execute(command(pid, token)));\n const before = rows.find((row) => row.pid === pid);\n if (!before || /^[ZX]/.test(before.state)) return undefined;\n if (!token) return before;\n const proof = await readProof(pid);\n const after = parse(await execute(command(pid))).find((row) => row.pid === pid);\n if (\n !after ||\n after.start !== before.start ||\n after.group !== before.group ||\n /^[ZX]/.test(after.state)\n )\n return undefined;\n return {\n start: after.start,\n group: after.group,\n state: proof?.pid === pid && proof.gated ? \"T\" : after.state,\n rss: treeMemory(rows, pid),\n token: proof?.pid === pid ? proof.token : undefined,\n };\n}\n"],"mappings":"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"}
|
package/esm/registry.mjs
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { identity } from "./identity.mjs";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
|
|
6
|
+
//#region ../../packages/process/src/registry.ts
|
|
7
|
+
function record(value) {
|
|
8
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function strings(value) {
|
|
11
|
+
return record(value) && Object.values(value).every((item) => typeof item === "string");
|
|
12
|
+
}
|
|
13
|
+
function parseRegistry(source, owner) {
|
|
14
|
+
const value = JSON.parse(source);
|
|
15
|
+
if (!record(value) || value.version !== 1 || value.owner !== owner || !Array.isArray(value.entries)) throw new Error("Invalid process registry or different owner");
|
|
16
|
+
const entries = [];
|
|
17
|
+
for (const entry of value.entries) {
|
|
18
|
+
if (!record(entry) || entry.owner !== owner || typeof entry.pid !== "number" || !Number.isSafeInteger(entry.pid) || Number(entry.pid) <= 1 || typeof entry.token !== "string" || !/^[a-f0-9]{64}$/.test(entry.token) || typeof entry.start !== "string" || typeof entry.type !== "string" || typeof entry.command !== "string" || !Array.isArray(entry.args) || !entry.args.every((arg) => typeof arg === "string") || !strings(entry.labels) || typeof entry.startedAt !== "number" || !Number.isFinite(entry.startedAt) || entry.startedAt < 0 || !record(entry.limits) || !Object.entries(entry.limits).every(([key, limit]) => [
|
|
19
|
+
"timeoutMs",
|
|
20
|
+
"memoryBytes",
|
|
21
|
+
"cpuSeconds",
|
|
22
|
+
"virtualMemoryBytes"
|
|
23
|
+
].includes(key) && typeof limit === "number" && Number.isSafeInteger(limit) && limit > 0) || ![
|
|
24
|
+
"cgroup-v2",
|
|
25
|
+
"rlimit-polling",
|
|
26
|
+
"rss-polling"
|
|
27
|
+
].includes(String(entry.enforcement)) || typeof entry.peakMemoryBytes !== "number" || !Number.isFinite(entry.peakMemoryBytes) || entry.peakMemoryBytes < 0 || entry.cgroup !== void 0 && typeof entry.cgroup !== "string" || entry.gated !== void 0 && typeof entry.gated !== "boolean") throw new Error("Invalid process registry entry");
|
|
28
|
+
entries.push(entry);
|
|
29
|
+
}
|
|
30
|
+
if (new Set(entries.map((entry) => entry.pid)).size !== entries.length) throw new Error("Duplicate registry PID");
|
|
31
|
+
return entries;
|
|
32
|
+
}
|
|
33
|
+
/** One writer per registry. A crash leaves a lease which only a dead identity can release. */
|
|
34
|
+
var Registry = class {
|
|
35
|
+
file;
|
|
36
|
+
owner;
|
|
37
|
+
queue = Promise.resolve();
|
|
38
|
+
locked = false;
|
|
39
|
+
constructor(file, owner) {
|
|
40
|
+
this.file = file;
|
|
41
|
+
this.owner = owner;
|
|
42
|
+
}
|
|
43
|
+
async open() {
|
|
44
|
+
await mkdir(path.dirname(this.file), {
|
|
45
|
+
recursive: true,
|
|
46
|
+
mode: 448
|
|
47
|
+
});
|
|
48
|
+
const self = await identity(process.pid);
|
|
49
|
+
if (!self) throw new Error("Cannot verify registry writer identity");
|
|
50
|
+
const lock = `${this.file}.lock`;
|
|
51
|
+
try {
|
|
52
|
+
await mkdir(lock, { mode: 448 });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error.code !== "EEXIST") throw error;
|
|
55
|
+
const reclaim = `${lock}.reclaim`;
|
|
56
|
+
await mkdir(reclaim, { mode: 448 });
|
|
57
|
+
try {
|
|
58
|
+
const lease = JSON.parse(await readFile(path.join(lock, "writer.json"), "utf8"));
|
|
59
|
+
if (!record(lease) || typeof lease.pid !== "number" || typeof lease.start !== "string") throw new Error("Invalid registry writer lease; inspect it manually");
|
|
60
|
+
if ((await identity(lease.pid))?.start === lease.start) throw new Error("Process registry is already in use");
|
|
61
|
+
const stale = `${lock}.${randomBytes(8).toString("hex")}`;
|
|
62
|
+
await rename(lock, stale);
|
|
63
|
+
await rm(stale, { recursive: true });
|
|
64
|
+
await mkdir(lock, { mode: 448 });
|
|
65
|
+
} finally {
|
|
66
|
+
await rm(reclaim, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.locked = true;
|
|
70
|
+
try {
|
|
71
|
+
await writeFile(path.join(lock, "writer.json"), JSON.stringify({
|
|
72
|
+
pid: process.pid,
|
|
73
|
+
start: self.start
|
|
74
|
+
}), { mode: 384 });
|
|
75
|
+
try {
|
|
76
|
+
return parseRegistry(await readFile(this.file, "utf8"), this.owner);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error.code === "ENOENT") return [];
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
await this.close();
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
save(entries) {
|
|
87
|
+
const source = JSON.stringify({
|
|
88
|
+
version: 1,
|
|
89
|
+
owner: this.owner,
|
|
90
|
+
entries
|
|
91
|
+
}, null, 2) + "\n";
|
|
92
|
+
const next = this.queue.then(async () => {
|
|
93
|
+
const temporary = `${this.file}.${randomBytes(8).toString("hex")}.tmp`;
|
|
94
|
+
try {
|
|
95
|
+
await writeFile(temporary, source, {
|
|
96
|
+
mode: 384,
|
|
97
|
+
flag: "wx"
|
|
98
|
+
});
|
|
99
|
+
await rename(temporary, this.file);
|
|
100
|
+
} finally {
|
|
101
|
+
await rm(temporary, { force: true });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
this.queue = next.catch(() => void 0);
|
|
105
|
+
return next;
|
|
106
|
+
}
|
|
107
|
+
async close() {
|
|
108
|
+
await this.queue;
|
|
109
|
+
if (this.locked) {
|
|
110
|
+
await rm(`${this.file}.lock`, { recursive: true });
|
|
111
|
+
this.locked = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
//#endregion
|
|
117
|
+
export { Registry, parseRegistry };
|
|
118
|
+
//# sourceMappingURL=registry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.mjs","names":[],"sources":["../../../../../../../packages/process/src/registry.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { identity } from \"./identity\";\nimport type { ProcessInfo } from \"./types\";\n\nexport type RegistryEntry = ProcessInfo & {\n token: string;\n start: string;\n cgroup?: string;\n /** Persisted launch gate: recovery may resume an adopted, interrupted launch. */\n gated?: boolean;\n};\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction strings(value: unknown): value is Record<string, string> {\n return record(value) && Object.values(value).every((item) => typeof item === \"string\");\n}\nexport function parseRegistry(source: string, owner: string): RegistryEntry[] {\n const value: unknown = JSON.parse(source);\n if (\n !record(value) ||\n value.version !== 1 ||\n value.owner !== owner ||\n !Array.isArray(value.entries)\n )\n throw new Error(\"Invalid process registry or different owner\");\n const entries: RegistryEntry[] = [];\n for (const entry of value.entries) {\n if (\n !record(entry) ||\n entry.owner !== owner ||\n typeof entry.pid !== \"number\" ||\n !Number.isSafeInteger(entry.pid) ||\n Number(entry.pid) <= 1 ||\n typeof entry.token !== \"string\" ||\n !/^[a-f0-9]{64}$/.test(entry.token) ||\n typeof entry.start !== \"string\" ||\n typeof entry.type !== \"string\" ||\n typeof entry.command !== \"string\" ||\n !Array.isArray(entry.args) ||\n !entry.args.every((arg) => typeof arg === \"string\") ||\n !strings(entry.labels) ||\n typeof entry.startedAt !== \"number\" ||\n !Number.isFinite(entry.startedAt) ||\n entry.startedAt < 0 ||\n !record(entry.limits) ||\n !Object.entries(entry.limits).every(\n ([key, limit]) =>\n [\"timeoutMs\", \"memoryBytes\", \"cpuSeconds\", \"virtualMemoryBytes\"].includes(key) &&\n typeof limit === \"number\" &&\n Number.isSafeInteger(limit) &&\n limit > 0,\n ) ||\n ![\"cgroup-v2\", \"rlimit-polling\", \"rss-polling\"].includes(String(entry.enforcement)) ||\n typeof entry.peakMemoryBytes !== \"number\" ||\n !Number.isFinite(entry.peakMemoryBytes) ||\n entry.peakMemoryBytes < 0 ||\n (entry.cgroup !== undefined && typeof entry.cgroup !== \"string\") ||\n (entry.gated !== undefined && typeof entry.gated !== \"boolean\")\n )\n throw new Error(\"Invalid process registry entry\");\n entries.push(entry as RegistryEntry);\n }\n if (new Set(entries.map((entry) => entry.pid)).size !== entries.length)\n throw new Error(\"Duplicate registry PID\");\n return entries;\n}\n\n/** One writer per registry. A crash leaves a lease which only a dead identity can release. */\nexport class Registry {\n private queue: Promise<void> = Promise.resolve();\n private locked = false;\n constructor(\n public readonly file: string,\n private readonly owner: string,\n ) {}\n async open(): Promise<RegistryEntry[]> {\n await mkdir(path.dirname(this.file), { recursive: true, mode: 0o700 });\n const self = await identity(process.pid);\n if (!self) throw new Error(\"Cannot verify registry writer identity\");\n const lock = `${this.file}.lock`;\n try {\n await mkdir(lock, { mode: 0o700 });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n const reclaim = `${lock}.reclaim`;\n await mkdir(reclaim, { mode: 0o700 });\n try {\n const lease: unknown = JSON.parse(await readFile(path.join(lock, \"writer.json\"), \"utf8\"));\n if (!record(lease) || typeof lease.pid !== \"number\" || typeof lease.start !== \"string\")\n throw new Error(\"Invalid registry writer lease; inspect it manually\");\n const writer = await identity(lease.pid);\n if (writer?.start === lease.start) throw new Error(\"Process registry is already in use\");\n const stale = `${lock}.${randomBytes(8).toString(\"hex\")}`;\n await rename(lock, stale);\n await rm(stale, { recursive: true });\n await mkdir(lock, { mode: 0o700 });\n } finally {\n await rm(reclaim, { recursive: true });\n }\n }\n this.locked = true;\n try {\n await writeFile(\n path.join(lock, \"writer.json\"),\n JSON.stringify({ pid: process.pid, start: self.start }),\n { mode: 0o600 },\n );\n try {\n return parseRegistry(await readFile(this.file, \"utf8\"), this.owner);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw error;\n }\n } catch (error) {\n await this.close();\n throw error;\n }\n }\n save(entries: RegistryEntry[]): Promise<void> {\n const source = JSON.stringify({ version: 1, owner: this.owner, entries }, null, 2) + \"\\n\";\n const next = this.queue.then(async () => {\n const temporary = `${this.file}.${randomBytes(8).toString(\"hex\")}.tmp`;\n try {\n await writeFile(temporary, source, { mode: 0o600, flag: \"wx\" });\n await rename(temporary, this.file);\n } finally {\n await rm(temporary, { force: true });\n }\n });\n this.queue = next.catch(() => undefined);\n return next;\n }\n async close(): Promise<void> {\n await this.queue;\n if (this.locked) {\n await rm(`${this.file}.lock`, { recursive: true });\n this.locked = false;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { mkdir, readFile, rmdir, writeFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
//#region ../../packages/process/src/resources.ts
|
|
5
|
+
function mountedCgroup(membership) {
|
|
6
|
+
const mount = "/sys/fs/cgroup";
|
|
7
|
+
const directory = path.resolve(mount, `.${membership}`);
|
|
8
|
+
return directory === mount || directory.startsWith(`${mount}/`) ? directory : void 0;
|
|
9
|
+
}
|
|
10
|
+
function validateLimits(limits, platform = process.platform) {
|
|
11
|
+
for (const [key, value] of Object.entries(limits)) if (![
|
|
12
|
+
"timeoutMs",
|
|
13
|
+
"memoryBytes",
|
|
14
|
+
"cpuSeconds",
|
|
15
|
+
"virtualMemoryBytes"
|
|
16
|
+
].includes(key) || !Number.isSafeInteger(value) || value <= 0) throw new Error(`Invalid process limit: ${key}`);
|
|
17
|
+
if (platform !== "linux" && limits.virtualMemoryBytes !== void 0) throw new Error("virtualMemoryBytes is supported only on Linux");
|
|
18
|
+
if (platform === "win32" && limits.cpuSeconds !== void 0) throw new Error("cpuSeconds is supported only on POSIX platforms");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* POSIX shell applies inherited limits. Linux stops here; macOS execs a gated guardian.
|
|
22
|
+
* Flags/units: Apple's bash-3.2/builtins/ulimit.def and installed Bash manual.
|
|
23
|
+
* https://github.com/apple-oss-distributions/bash/blob/main/bash-3.2/builtins/ulimit.def
|
|
24
|
+
*/
|
|
25
|
+
function launcher(limits, platform = process.platform) {
|
|
26
|
+
const commands = ["set -e"];
|
|
27
|
+
if (limits.cpuSeconds) commands.push(`ulimit -t ${limits.cpuSeconds}`);
|
|
28
|
+
if (limits.virtualMemoryBytes) commands.push(`ulimit -v ${Math.ceil(limits.virtualMemoryBytes / 1024)}`);
|
|
29
|
+
if (limits.memoryBytes) commands.push(`ulimit -${platform === "darwin" ? "d" : "m"} ${Math.ceil(limits.memoryBytes / 1024)}`);
|
|
30
|
+
if (platform === "linux") commands.push("kill -STOP \"$$\"");
|
|
31
|
+
commands.push("exec \"$@\"");
|
|
32
|
+
return commands.join("\n");
|
|
33
|
+
}
|
|
34
|
+
/** https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html */
|
|
35
|
+
async function attachCgroup(pid, token, limits, root) {
|
|
36
|
+
if (process.platform !== "linux") return void 0;
|
|
37
|
+
let directory;
|
|
38
|
+
try {
|
|
39
|
+
const own = (await readFile("/proc/self/cgroup", "utf8")).split("\n").find((line) => line.startsWith("0::"))?.slice(3);
|
|
40
|
+
if (!root && own === void 0) return void 0;
|
|
41
|
+
const parent = root ?? mountedCgroup(own ?? "/");
|
|
42
|
+
if (!parent) return void 0;
|
|
43
|
+
await readFile(path.join(parent, "cgroup.controllers"), "utf8");
|
|
44
|
+
directory = path.join(parent, `warlock-${token}`);
|
|
45
|
+
await mkdir(directory);
|
|
46
|
+
if (limits.memoryBytes) {
|
|
47
|
+
await writeFile(path.join(directory, "memory.max"), String(limits.memoryBytes));
|
|
48
|
+
await writeFile(path.join(directory, "memory.oom.group"), "1");
|
|
49
|
+
}
|
|
50
|
+
await writeFile(path.join(directory, "cgroup.procs"), String(pid));
|
|
51
|
+
return directory;
|
|
52
|
+
} catch {
|
|
53
|
+
if (directory) await rmdir(directory).catch(() => void 0);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function cgroupMemory(directory) {
|
|
58
|
+
const [peak, events] = await Promise.all([readFile(path.join(directory, "memory.peak"), "utf8").catch(() => "0"), readFile(path.join(directory, "memory.events"), "utf8").catch(() => "")]);
|
|
59
|
+
return {
|
|
60
|
+
peak: Number(peak) || 0,
|
|
61
|
+
oom: Number(/^oom_kill (\d+)/m.exec(events)?.[1] ?? 0) > 0
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Only reattach the token-specific cgroup confirmed by the kernel membership file. */
|
|
65
|
+
async function recoveredCgroup(pid, token) {
|
|
66
|
+
if (process.platform !== "linux") return void 0;
|
|
67
|
+
let source;
|
|
68
|
+
try {
|
|
69
|
+
source = await readFile(`/proc/${pid}/cgroup`, "utf8");
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (["ENOENT", "ESRCH"].includes(String(error.code))) return void 0;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
const membership = source.split("\n").find((line) => line.startsWith("0::"))?.slice(3);
|
|
75
|
+
if (!membership || path.basename(membership) !== `warlock-${token}`) return void 0;
|
|
76
|
+
return mountedCgroup(membership);
|
|
77
|
+
}
|
|
78
|
+
async function cleanCgroup(directory) {
|
|
79
|
+
await writeFile(path.join(directory, "cgroup.kill"), "1").catch(() => void 0);
|
|
80
|
+
await rmdir(directory).catch(() => void 0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
export { attachCgroup, cgroupMemory, cleanCgroup, launcher, recoveredCgroup, validateLimits };
|
|
85
|
+
//# sourceMappingURL=resources.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.mjs","names":[],"sources":["../../../../../../../packages/process/src/resources.ts"],"sourcesContent":["import { mkdir, readFile, rmdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { ProcessLimits } from \"./types\";\n\nfunction mountedCgroup(membership: string): string | undefined {\n const mount = \"/sys/fs/cgroup\";\n const directory = path.resolve(mount, `.${membership}`);\n // A cgroup namespace can report ancestors using /../ components. Never\n // normalize those into writes outside the standard cgroup mount.\n return directory === mount || directory.startsWith(`${mount}/`) ? directory : undefined;\n}\n\nexport function validateLimits(limits: ProcessLimits, platform: string = process.platform): void {\n for (const [key, value] of Object.entries(limits)) {\n if (\n ![\"timeoutMs\", \"memoryBytes\", \"cpuSeconds\", \"virtualMemoryBytes\"].includes(key) ||\n !Number.isSafeInteger(value) ||\n value <= 0\n )\n throw new Error(`Invalid process limit: ${key}`);\n }\n if (platform !== \"linux\" && limits.virtualMemoryBytes !== undefined)\n throw new Error(\"virtualMemoryBytes is supported only on Linux\");\n if (platform === \"win32\" && limits.cpuSeconds !== undefined)\n throw new Error(\"cpuSeconds is supported only on POSIX platforms\");\n}\n\n/**\n * POSIX shell applies inherited limits. Linux stops here; macOS execs a gated guardian.\n * Flags/units: Apple's bash-3.2/builtins/ulimit.def and installed Bash manual.\n * https://github.com/apple-oss-distributions/bash/blob/main/bash-3.2/builtins/ulimit.def\n */\nexport function launcher(limits: ProcessLimits, platform: string = process.platform): string {\n const commands = [\"set -e\"];\n if (limits.cpuSeconds) commands.push(`ulimit -t ${limits.cpuSeconds}`);\n if (limits.virtualMemoryBytes)\n commands.push(`ulimit -v ${Math.ceil(limits.virtualMemoryBytes / 1024)}`);\n // Linux ignores RLIMIT_RSS; polling remains the fallback resident-memory cap.\n // Darwin's RLIMIT_DATA restricts heap growth, not total resident memory.\n if (limits.memoryBytes)\n commands.push(\n `ulimit -${platform === \"darwin\" ? \"d\" : \"m\"} ${Math.ceil(limits.memoryBytes / 1024)}`,\n );\n if (platform === \"linux\") commands.push('kill -STOP \"$$\"');\n commands.push('exec \"$@\"');\n return commands.join(\"\\n\");\n}\n\n/** https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html */\nexport async function attachCgroup(\n pid: number,\n token: string,\n limits: ProcessLimits,\n root?: string,\n): Promise<string | undefined> {\n if (process.platform !== \"linux\") return undefined;\n let directory: string | undefined;\n try {\n const own = (await readFile(\"/proc/self/cgroup\", \"utf8\"))\n .split(\"\\n\")\n .find((line) => line.startsWith(\"0::\"))\n ?.slice(3);\n if (!root && own === undefined) return undefined;\n const parent = root ?? mountedCgroup(own ?? \"/\");\n if (!parent) return undefined;\n // Do not enable controllers or move the app: delegation belongs to the service manager.\n await readFile(path.join(parent, \"cgroup.controllers\"), \"utf8\");\n directory = path.join(parent, `warlock-${token}`);\n await mkdir(directory);\n if (limits.memoryBytes) {\n await writeFile(path.join(directory, \"memory.max\"), String(limits.memoryBytes));\n await writeFile(path.join(directory, \"memory.oom.group\"), \"1\");\n }\n await writeFile(path.join(directory, \"cgroup.procs\"), String(pid));\n return directory;\n } catch {\n if (directory) await rmdir(directory).catch(() => undefined);\n return undefined;\n }\n}\n\nexport async function cgroupMemory(directory: string): Promise<{ peak: number; oom: boolean }> {\n const [peak, events] = await Promise.all([\n readFile(path.join(directory, \"memory.peak\"), \"utf8\").catch(() => \"0\"),\n readFile(path.join(directory, \"memory.events\"), \"utf8\").catch(() => \"\"),\n ]);\n return { peak: Number(peak) || 0, oom: Number(/^oom_kill (\\d+)/m.exec(events)?.[1] ?? 0) > 0 };\n}\n\n/** Only reattach the token-specific cgroup confirmed by the kernel membership file. */\nexport async function recoveredCgroup(pid: number, token: string): Promise<string | undefined> {\n if (process.platform !== \"linux\") return undefined;\n let source: string;\n try {\n source = await readFile(`/proc/${pid}/cgroup`, \"utf8\");\n } catch (error) {\n if ([\"ENOENT\", \"ESRCH\"].includes(String((error as NodeJS.ErrnoException).code)))\n return undefined;\n throw error;\n }\n const membership = source\n .split(\"\\n\")\n .find((line) => line.startsWith(\"0::\"))\n ?.slice(3);\n if (!membership || path.basename(membership) !== `warlock-${token}`) return undefined;\n return mountedCgroup(membership);\n}\n\nexport async function cleanCgroup(directory: string): Promise<void> {\n // This unique cgroup was created for this token, or recovered after identity proof.\n // cgroup.kill also covers descendants that created their own POSIX session.\n await writeFile(path.join(directory, \"cgroup.kill\"), \"1\").catch(() => undefined);\n await rmdir(directory).catch(() => undefined);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.d.mts","names":[],"sources":["../../../../../../../packages/process/src/supervisor.ts"],"mappings":";;wBAqFgB,WAAW,SAAS,oBAAoB"}
|