@neuralnomads/codenomad-dev 0.18.0-dev-20260715-07e8ce68 → 0.18.0-dev-20260716-40cee071

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 (81) hide show
  1. package/README.md +12 -0
  2. package/THIRD_PARTY_NOTICES.md +26 -0
  3. package/dist/events/bus.js +13 -0
  4. package/dist/events/bus.test.js +37 -0
  5. package/dist/index.js +33 -56
  6. package/dist/index.test.js +32 -0
  7. package/dist/server/__tests__/remote-proxy.test.js +102 -6
  8. package/dist/server/http-server.js +2 -0
  9. package/dist/server/remote-proxy.js +96 -9
  10. package/dist/server/routes/usage.js +18 -0
  11. package/dist/server/routes/usage.test.js +26 -0
  12. package/dist/server/routes/workspaces.js +32 -1
  13. package/dist/server/routes/workspaces.test.js +75 -0
  14. package/dist/settings/binaries.js +4 -1
  15. package/dist/settings/binaries.test.js +24 -0
  16. package/dist/shutdown.js +75 -0
  17. package/dist/shutdown.test.js +111 -0
  18. package/dist/usage/providers/api-key.js +171 -0
  19. package/dist/usage/providers/google.js +171 -0
  20. package/dist/usage/providers/minimax.js +109 -0
  21. package/dist/usage/providers/oauth.js +111 -0
  22. package/dist/usage/providers/special.js +148 -0
  23. package/dist/usage/service.js +83 -0
  24. package/dist/usage/service.test.js +59 -0
  25. package/dist/usage/shared.js +130 -0
  26. package/dist/usage/shared.test.js +33 -0
  27. package/dist/usage/types.js +1 -0
  28. package/dist/workspaces/__tests__/spawn.test.js +64 -7
  29. package/dist/workspaces/__tests__/workspace-identity.test.js +177 -25
  30. package/dist/workspaces/launch-cleanup.js +132 -0
  31. package/dist/workspaces/launch-cleanup.test.js +33 -0
  32. package/dist/workspaces/manager.js +393 -135
  33. package/dist/workspaces/manager.test.js +318 -0
  34. package/dist/workspaces/process-identity.js +408 -0
  35. package/dist/workspaces/process-identity.test.js +165 -0
  36. package/dist/workspaces/runtime.js +567 -122
  37. package/dist/workspaces/runtime.test.js +603 -0
  38. package/dist/workspaces/spawn.js +86 -15
  39. package/package.json +1 -1
  40. package/public/assets/{FilesTab-EPvldvn1.js → FilesTab-BjztpSdj.js} +2 -2
  41. package/public/assets/{GitChangesTab-ofjovmv2.js → GitChangesTab-B9XraSoR.js} +2 -2
  42. package/public/assets/{SplitFilePanel-CyoPkcLd.js → SplitFilePanel-Cu_5-Z-E.js} +1 -1
  43. package/public/assets/StatusTab-D_IE79r-.js +1 -0
  44. package/public/assets/{align-justify-Bcf1-ZOl.js → align-justify-BKS9gyRk.js} +1 -1
  45. package/public/assets/{bundle-full-D5dzvToA.js → bundle-full-CXGXXlzR.js} +1 -1
  46. package/public/assets/{diff-viewer-DViXCI-4.js → diff-viewer-CSNOYoEB.js} +1 -1
  47. package/public/assets/{git-diff-vendor-CSgooKT_.js → git-diff-vendor-CYS3ApzA.js} +1 -1
  48. package/public/assets/index-B3YjNCh4.js +1 -0
  49. package/public/assets/index-B3ZD2_SU.js +1 -0
  50. package/public/assets/index-Bh2ofaQo.js +1 -0
  51. package/public/assets/index-CM0c9xRZ.js +1 -0
  52. package/public/assets/index-CdDxouJl.js +2 -0
  53. package/public/assets/index-D06Y2cFq.js +1 -0
  54. package/public/assets/{index-lf4nUOU1.js → index-DVc4ls8c.js} +1 -1
  55. package/public/assets/index-PmE51vPh.js +1 -0
  56. package/public/assets/{index-BMvFMsuy.css → index-Qj63IiSj.css} +1 -1
  57. package/public/assets/index-VY_zwmsG.js +1 -0
  58. package/public/assets/index-qiiH7egV.js +1 -0
  59. package/public/assets/{index-UbL8tz_V.js → index-sLe9hEfQ.js} +1 -1
  60. package/public/assets/{loading-CZ_tt3h0.js → loading-CDmt9OJU.js} +1 -1
  61. package/public/assets/main-CTN6Wwz1.js +68 -0
  62. package/public/assets/{markdown-G11aN6hz.js → markdown-1ZLSxCFT.js} +55 -44
  63. package/public/assets/monaco-viewer-D6JgH76W.js +26 -0
  64. package/public/assets/{tool-call-BsSXWErm.js → tool-call-CyXb63LH.js} +32 -32
  65. package/public/assets/{unified-picker-BRNI3UvE.js → unified-picker-DCCdSVwI.js} +1 -1
  66. package/public/assets/{wrap-text-B7up-uv7.js → wrap-text-pNMdTIq3.js} +1 -1
  67. package/public/index.html +5 -5
  68. package/public/loading.html +5 -5
  69. package/public/sw.js +1 -1
  70. package/public/assets/StatusTab-Bgu1Fh24.js +0 -1
  71. package/public/assets/index-3KsjbWGP.js +0 -1
  72. package/public/assets/index-B0WK8-cF.js +0 -1
  73. package/public/assets/index-BDv_iVtj.js +0 -1
  74. package/public/assets/index-BjxZvyb9.js +0 -2
  75. package/public/assets/index-BleKf9Nj.js +0 -1
  76. package/public/assets/index-CiIQ43NW.js +0 -1
  77. package/public/assets/index-DBquqL6Q.js +0 -1
  78. package/public/assets/index-yf9zolv4.js +0 -1
  79. package/public/assets/index-ypaL7i_s.js +0 -1
  80. package/public/assets/main-CAkg_fp_.js +0 -68
  81. package/public/assets/monaco-viewer-CZefBfct.js +0 -26
@@ -0,0 +1,408 @@
1
+ const LINUX_SNAPSHOT_SCRIPT = String.raw `
2
+ boot=$(cat /proc/sys/kernel/random/boot_id 2>/dev/null) || exit 1
3
+ test -n "$boot" || exit 1
4
+ for stat in /proc/[0-9]*/stat; do
5
+ line=$(cat "$stat" 2>/dev/null) || continue
6
+ pid=$(printf '%s\n' "$line" | cut -d' ' -f1)
7
+ rest=$(printf '%s\n' "$line" | sed 's/^.*) //')
8
+ set -- $rest
9
+ printf '%s|%s|%s|%s|%s|%s\n' "$pid" "$2" "$3" "$20" "$boot" "$20"
10
+ done
11
+ `;
12
+ const LINUX_GUARDED_SIGNAL_SCRIPT = String.raw `
13
+ boot=$(cat /proc/sys/kernel/random/boot_id 2>/dev/null) || exit 20
14
+ read_stat() {
15
+ line=$(cat "/proc/$1/stat" 2>/dev/null) || return 1
16
+ stat_pid=$(printf '%s\n' "$line" | cut -d' ' -f1); rest=$(printf '%s\n' "$line" | sed 's/^.*) //'); set -- $rest
17
+ stat_ppid=$2; stat_group=$3; stat_start=$20
18
+ }
19
+ emit_target() {
20
+ printf 'CODENOMAD_TARGET|%s|%s|%s|%s|%s|%s\n' "$stat_pid" "$stat_ppid" "$stat_group" "$stat_start" "$boot" "$stat_start"
21
+ }
22
+ leader_pid=$1; leader_start=$2; leader_boot=$3; expected_group=$4; requested_signal=$5
23
+ shift 5
24
+ matched=0; cutoff=; signal_sent=0
25
+ if read_stat "$leader_pid" && test "$boot" = "$leader_boot" && test "$stat_start" = "$leader_start" && test "$stat_group" = "$expected_group"; then
26
+ matched=1
27
+ for stat in /proc/[0-9]*/stat; do
28
+ candidate=$(basename "$(dirname "$stat")")
29
+ if read_stat "$candidate" && test "$stat_group" = "$expected_group"; then emit_target; fi
30
+ done
31
+ if kill "-$requested_signal" -- "-$expected_group" 2>/dev/null; then
32
+ signal_sent=1
33
+ hz=$(getconf CLK_TCK 2>/dev/null) || exit 21
34
+ uptime=$(cut -d' ' -f1 /proc/uptime 2>/dev/null) || exit 21
35
+ cutoff=$(awk -v uptime="$uptime" -v hz="$hz" 'BEGIN { printf "%.0f", uptime * hz }')
36
+ fi
37
+ else
38
+ while test "$#" -ge 3; do
39
+ expected_pid=$1; expected_start=$2; expected_boot=$3; shift 3
40
+ if read_stat "$expected_pid" && test "$boot" = "$expected_boot" && test "$stat_start" = "$expected_start"; then
41
+ emit_target
42
+ if kill "-$requested_signal" "$expected_pid" 2>/dev/null; then signal_sent=1; fi
43
+ fi
44
+ done
45
+ fi
46
+ printf 'CODENOMAD_RESULT|%s|%s|%s\n' "$matched" "$cutoff" "$signal_sent"
47
+ `;
48
+ const POSIX_GUARDED_SIGNAL_SCRIPT = String.raw `
49
+ encode() { printf '%s' "$1" | base64 | tr -d '\r\n'; }
50
+ read_identity() {
51
+ current_meta=$(ps -p "$1" -o ppid= -o pgid= -o lstart= 2>/dev/null) || return 1
52
+ current_command=$(ps -p "$1" -o command= 2>/dev/null) || return 1
53
+ current_verify=$(ps -p "$1" -o ppid= -o pgid= -o lstart= 2>/dev/null) || return 1
54
+ test "$current_meta" = "$current_verify" || return 1
55
+ set -- $current_meta
56
+ test "$#" -ge 7 || return 1
57
+ current_ppid=$1; current_group=$2; shift 2
58
+ current_start="$1 $2 $3 $4 $5"
59
+ current_identity=$(printf '%s\t%s' "$current_start" "$current_command")
60
+ }
61
+ emit_target() {
62
+ printf 'CODENOMAD_TARGET_B64|%s|%s|%s|' "$current_pid" "$current_ppid" "$current_group"
63
+ encode "$current_start"; printf '|'; encode "$current_command"; printf '\n'
64
+ }
65
+ leader_pid=$1; leader_start=$2; expected_group=$3; requested_signal=$4; shift 4
66
+ matched=0; signal_sent=0
67
+ if read_identity "$leader_pid" && test "$current_group" = "$expected_group" && test "$current_identity" = "$leader_start"; then
68
+ matched=1
69
+ for current_pid in $(ps -eo pid= 2>/dev/null); do
70
+ if read_identity "$current_pid" && test "$current_group" = "$expected_group"; then emit_target; fi
71
+ done
72
+ if kill "-$requested_signal" -- "-$expected_group" 2>/dev/null; then signal_sent=1; fi
73
+ else
74
+ while test "$#" -ge 2; do
75
+ expected_pid=$1; expected_start=$2; shift 2
76
+ current_pid=$expected_pid
77
+ if read_identity "$expected_pid" && test "$current_identity" = "$expected_start"; then
78
+ emit_target
79
+ if kill "-$requested_signal" "$expected_pid" 2>/dev/null; then signal_sent=1; fi
80
+ fi
81
+ done
82
+ fi
83
+ printf 'CODENOMAD_RESULT|%s||%s\n' "$matched" "$signal_sent"
84
+ `;
85
+ const POSIX_OWNED_GROUP_SIGNAL_SCRIPT = String.raw `
86
+ encode() { printf '%s' "$1" | base64 | tr -d '\r\n'; }
87
+ read_identity() {
88
+ current_meta=$(ps -p "$1" -o ppid= -o pgid= -o lstart= 2>/dev/null) || return 1
89
+ current_command=$(ps -p "$1" -o command= 2>/dev/null) || return 1
90
+ current_verify=$(ps -p "$1" -o ppid= -o pgid= -o lstart= 2>/dev/null) || return 1
91
+ test "$current_meta" = "$current_verify" || return 1
92
+ set -- $current_meta
93
+ test "$#" -ge 7 || return 1
94
+ current_ppid=$1; current_group=$2; shift 2
95
+ current_start="$1 $2 $3 $4 $5"
96
+ current_identity=$(printf '%s\t%s' "$current_start" "$current_command")
97
+ }
98
+ emit_target() {
99
+ printf 'CODENOMAD_TARGET_B64|%s|%s|%s|' "$current_pid" "$current_ppid" "$current_group"
100
+ encode "$current_start"; printf '|'; encode "$current_command"; printf '\n'
101
+ }
102
+ root_pid=$1; requested_signal=$2; matched=0; signal_sent=0
103
+ if read_identity "$root_pid" && test "$current_group" = "$root_pid"; then
104
+ matched=1
105
+ for current_pid in $(ps -eo pid= 2>/dev/null); do
106
+ if read_identity "$current_pid" && test "$current_group" = "$root_pid"; then emit_target; fi
107
+ done
108
+ if kill "-$requested_signal" -- "-$root_pid" 2>/dev/null; then signal_sent=1; fi
109
+ for current_pid in $(ps -eo pid= 2>/dev/null); do
110
+ if read_identity "$current_pid" && test "$current_group" = "$root_pid"; then emit_target; fi
111
+ done
112
+ fi
113
+ printf 'CODENOMAD_RESULT|%s||%s\n' "$matched" "$signal_sent"
114
+ `;
115
+ function commandError(result) {
116
+ return result.error?.message || String(result.stderr ?? result.stdout ?? "").trim() || `exit code ${result.status}`;
117
+ }
118
+ function parseDelimitedSnapshot(output, requireBootId = false) {
119
+ const processes = new Map();
120
+ for (const line of output.split(/\r?\n/)) {
121
+ if (!line)
122
+ continue;
123
+ const fields = line.split("|");
124
+ if (fields.length !== 6)
125
+ return null;
126
+ const [pidText, parentPidText, groupIdText, startTime = "", bootId = "", startOrder = ""] = fields;
127
+ const pid = Number.parseInt(pidText ?? "", 10);
128
+ const parentPid = Number.parseInt(parentPidText ?? "", 10);
129
+ const groupId = Number.parseInt(groupIdText ?? "", 10);
130
+ if (!/^\d+$/.test(pidText ?? "") || !/^\d+$/.test(parentPidText ?? "") || !/^\d+$/.test(groupIdText ?? "") ||
131
+ !Number.isInteger(pid) || pid <= 0 || !Number.isInteger(parentPid) || !startTime || (requireBootId && !bootId))
132
+ return null;
133
+ processes.set(pid, {
134
+ pid,
135
+ parentPid,
136
+ groupId: Number.isInteger(groupId) && groupId > 0 ? groupId : pid,
137
+ startTime,
138
+ ...(bootId ? { bootId } : {}),
139
+ ...(startOrder ? { startOrder } : {}),
140
+ });
141
+ }
142
+ return processes;
143
+ }
144
+ function decodeBase64Field(value) {
145
+ if (value.length === 0 || value.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
146
+ return null;
147
+ }
148
+ try {
149
+ const bytes = Buffer.from(value, "base64");
150
+ if (bytes.toString("base64") !== value)
151
+ return null;
152
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
153
+ }
154
+ catch {
155
+ return null;
156
+ }
157
+ }
158
+ function parseBase64Snapshot(output, prefix = "CODENOMAD_B64|") {
159
+ const processes = new Map();
160
+ for (const line of output.split(/\r?\n/)) {
161
+ if (!line)
162
+ continue;
163
+ if (!line.startsWith(prefix))
164
+ return null;
165
+ const fields = line.slice(prefix.length).split("|");
166
+ if (fields.length !== 5)
167
+ return null;
168
+ const [pidText = "", parentPidText = "", groupIdText = "", startEncoded = "", commandEncoded = ""] = fields;
169
+ if (!/^\d+$/.test(pidText) || !/^\d+$/.test(parentPidText) || !/^\d+$/.test(groupIdText))
170
+ return null;
171
+ const pid = Number.parseInt(pidText, 10);
172
+ const parentPid = Number.parseInt(parentPidText, 10);
173
+ const groupId = Number.parseInt(groupIdText, 10);
174
+ const start = decodeBase64Field(startEncoded);
175
+ const command = decodeBase64Field(commandEncoded);
176
+ if (pid <= 0 || parentPid < 0 || groupId <= 0 || start === null || command === null)
177
+ return null;
178
+ processes.set(pid, { pid, parentPid, groupId, startTime: `${start}\t${command}` });
179
+ }
180
+ return processes;
181
+ }
182
+ function parsePortablePosixSnapshot(output) {
183
+ const processes = new Map();
184
+ for (const line of output.split(/\r?\n/)) {
185
+ if (!line.trim())
186
+ continue;
187
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+\s+\S+\s+\d+\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s+(.+)$/);
188
+ if (!match)
189
+ return null;
190
+ const [, pidText = "", parentPidText = "", groupIdText = "", start = "", command = ""] = match;
191
+ const pid = Number.parseInt(pidText, 10);
192
+ const parentPid = Number.parseInt(parentPidText, 10);
193
+ const groupId = Number.parseInt(groupIdText, 10);
194
+ if (pid <= 0 || parentPid < 0 || groupId <= 0)
195
+ return null;
196
+ processes.set(pid, { pid, parentPid, groupId, startTime: `${start}\t${command}` });
197
+ }
198
+ return processes;
199
+ }
200
+ function snapshotOrFailure(processes) {
201
+ return processes && processes.size > 0
202
+ ? { ok: true, processes }
203
+ : { ok: false, error: "process identity query returned no parseable processes" };
204
+ }
205
+ function parseGuardedResult(result) {
206
+ const signaled = new Map();
207
+ const failure = (error) => ({
208
+ ok: false,
209
+ error,
210
+ ...(signaled.size > 0 ? { observed: Array.from(signaled.values()) } : {}),
211
+ });
212
+ let matched;
213
+ let signalSent = false;
214
+ let cutoff;
215
+ for (const line of String(result.stdout ?? "").split(/\r?\n/)) {
216
+ if (line.startsWith("CODENOMAD_TARGET|")) {
217
+ const parsed = parseDelimitedSnapshot(line.slice("CODENOMAD_TARGET|".length));
218
+ if (!parsed)
219
+ return failure("guarded signal command returned a malformed target record");
220
+ for (const identity of parsed.values())
221
+ signaled.set(identity.pid, identity);
222
+ continue;
223
+ }
224
+ if (line.startsWith("CODENOMAD_TARGET_B64|")) {
225
+ const parsed = parseBase64Snapshot(line, "CODENOMAD_TARGET_B64|");
226
+ if (!parsed)
227
+ return failure("guarded signal command returned a malformed target record");
228
+ for (const identity of parsed.values())
229
+ signaled.set(identity.pid, identity);
230
+ continue;
231
+ }
232
+ if (line.startsWith("CODENOMAD_RESULT|")) {
233
+ const fields = line.split("|");
234
+ if (fields.length !== 4 || !/^[01]$/.test(fields[1] ?? "") || !/^[01]$/.test(fields[3] ?? "")) {
235
+ return failure("guarded signal command returned a malformed result record");
236
+ }
237
+ const [, matchedText, cutoffText, signalSentText] = fields;
238
+ matched = matchedText === "1";
239
+ cutoff = cutoffText || undefined;
240
+ signalSent = signalSentText === "1";
241
+ continue;
242
+ }
243
+ if (line)
244
+ return failure("guarded signal command returned unexpected output");
245
+ }
246
+ if (result.status !== 0)
247
+ return failure(commandError(result));
248
+ return matched === undefined
249
+ ? failure("guarded signal command returned no structured result")
250
+ : { ok: true, matched, signalSent, signaled: Array.from(signaled.values()), ...(cutoff ? { cutoff } : {}) };
251
+ }
252
+ function runGuardedCommand(spawnCommand, command, args, timeoutMs) {
253
+ try {
254
+ return parseGuardedResult(spawnCommand(command, args, { encoding: "utf8", timeout: timeoutMs }));
255
+ }
256
+ catch (error) {
257
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
258
+ }
259
+ }
260
+ function signalName(signal) {
261
+ return signal === "SIGKILL" ? "KILL" : "TERM";
262
+ }
263
+ function shellGuardArgs(request, linux) {
264
+ const leader = request.leader;
265
+ const args = linux
266
+ ? [String(leader?.pid ?? 0), leader?.startTime ?? "", leader?.bootId ?? "", String(request.groupId ?? 0), signalName(request.signal)]
267
+ : [String(leader?.pid ?? 0), leader?.startTime ?? "", String(request.groupId ?? 0), signalName(request.signal)];
268
+ for (const member of request.members) {
269
+ args.push(String(member.pid), member.startTime);
270
+ if (linux)
271
+ args.push(member.bootId ?? "");
272
+ }
273
+ return args;
274
+ }
275
+ function quotePowerShell(value) {
276
+ return `'${value.replace(/'/g, "''")}'`;
277
+ }
278
+ function buildWindowsGuardedScript(request) {
279
+ const leaderPid = request.leader?.pid ?? 0;
280
+ const leaderStart = quotePowerShell(request.leader?.startTime ?? "");
281
+ const expected = request.members
282
+ .map((identity) => `@{ Pid = ${identity.pid}; Start = ${quotePowerShell(identity.startTime)} }`)
283
+ .join(", ");
284
+ return [
285
+ "$ErrorActionPreference = 'Stop'",
286
+ `$leaderPid = ${leaderPid}`,
287
+ `$leaderStart = ${leaderStart}`,
288
+ `$expected = @(${expected})`,
289
+ "function Get-CodeNomadStart($process) { return ([datetime]$process.CreationDate).ToUniversalTime().Ticks.ToString() }",
290
+ "$all = @(Get-CimInstance Win32_Process -ErrorAction Stop)",
291
+ "$byPid = @{}; $all | ForEach-Object { $byPid[[int]$_.ProcessId] = $_ }",
292
+ "$leader = $byPid[$leaderPid]",
293
+ "$matched = $null -ne $leader -and (Get-CodeNomadStart $leader) -eq $leaderStart",
294
+ "$selected = @()",
295
+ "if ($matched) {",
296
+ " $ids = @($leaderPid); $changed = $true",
297
+ " while ($changed) { $changed = $false; foreach ($process in $all) { if ($ids -contains [int]$process.ParentProcessId -and $ids -notcontains [int]$process.ProcessId) { $ids += [int]$process.ProcessId; $changed = $true } } }",
298
+ " $selected = @($all | Where-Object { $ids -contains [int]$_.ProcessId } | Sort-Object ProcessId -Descending)",
299
+ "} else {",
300
+ " foreach ($item in $expected) { $process = $byPid[[int]$item.Pid]; if ($null -ne $process -and (Get-CodeNomadStart $process) -eq [string]$item.Start) { $selected += $process } }",
301
+ "}",
302
+ "foreach ($process in $selected) {",
303
+ " $start = Get-CodeNomadStart $process",
304
+ " '{0}|{1}|0|{2}||{2}' -f [int]$process.ProcessId, [int]$process.ParentProcessId, $start | ForEach-Object { 'CODENOMAD_TARGET|' + $_ }",
305
+ "}",
306
+ "foreach ($process in $selected) {",
307
+ " Invoke-CimMethod -InputObject $process -MethodName Terminate -Arguments @{ Reason = 1 } -ErrorAction Stop | Out-Null",
308
+ "}",
309
+ "'CODENOMAD_RESULT|' + ($(if ($matched) { '1' } else { '0' })) + '||' + ($(if ($selected.Count -gt 0) { '1' } else { '0' }))",
310
+ ].join("; ");
311
+ }
312
+ export function sameProcess(left, right) {
313
+ return Boolean(left && right && left.pid === right.pid && left.startTime === right.startTime &&
314
+ (!left.bootId || !right.bootId || left.bootId === right.bootId));
315
+ }
316
+ export function startedNoLaterThan(identity, cutoff) {
317
+ const startOrder = identity.startOrder ?? identity.startTime;
318
+ if (!/^\d+$/.test(startOrder) || !/^\d+$/.test(cutoff))
319
+ return false;
320
+ try {
321
+ return BigInt(startOrder) <= BigInt(cutoff);
322
+ }
323
+ catch {
324
+ return false;
325
+ }
326
+ }
327
+ export function descendantsOf(processes, rootPid) {
328
+ const descendants = [];
329
+ const pending = [rootPid];
330
+ const seen = new Set(pending);
331
+ while (pending.length > 0) {
332
+ const parentPid = pending.shift();
333
+ for (const process of processes.values()) {
334
+ if (process.parentPid !== parentPid || seen.has(process.pid))
335
+ continue;
336
+ seen.add(process.pid);
337
+ pending.push(process.pid);
338
+ descendants.push(process);
339
+ }
340
+ }
341
+ return descendants;
342
+ }
343
+ export function probePosixProcesses(spawnCommand, timeoutMs, platform = process.platform) {
344
+ try {
345
+ if (platform === "linux") {
346
+ const result = spawnCommand("sh", ["-c", LINUX_SNAPSHOT_SCRIPT, "codenomad-posix-identity"], {
347
+ encoding: "utf8",
348
+ timeout: timeoutMs,
349
+ });
350
+ if (result.status !== 0)
351
+ return { ok: false, error: commandError(result) };
352
+ return snapshotOrFailure(parseDelimitedSnapshot(String(result.stdout ?? ""), true));
353
+ }
354
+ // POSIX has no portable pidfd/start ticks; collect one coherent table instead of probing every PID.
355
+ const result = spawnCommand("ps", ["-axo", "pid=,ppid=,pgid=,lstart=,command="], {
356
+ encoding: "utf8",
357
+ timeout: timeoutMs,
358
+ });
359
+ if (result.status !== 0)
360
+ return { ok: false, error: commandError(result) };
361
+ return snapshotOrFailure(parsePortablePosixSnapshot(String(result.stdout ?? "")));
362
+ }
363
+ catch (error) {
364
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
365
+ }
366
+ }
367
+ export function probeWindowsProcesses(spawnCommand, timeoutMs) {
368
+ const script = [
369
+ "$all = @(Get-CimInstance Win32_Process -ErrorAction Stop)",
370
+ "$all | Where-Object { [int]$_.ProcessId -gt 0 } | ForEach-Object { $start = ([datetime]$_.CreationDate).ToUniversalTime().Ticks.ToString(); '{0}|{1}|0|{2}||{2}' -f [int]$_.ProcessId, [int]$_.ParentProcessId, $start }",
371
+ ].join("; ");
372
+ try {
373
+ const result = spawnCommand("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
374
+ encoding: "utf8",
375
+ timeout: timeoutMs,
376
+ });
377
+ if (result.status !== 0)
378
+ return { ok: false, error: commandError(result) };
379
+ return snapshotOrFailure(parseDelimitedSnapshot(String(result.stdout ?? "")));
380
+ }
381
+ catch (error) {
382
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
383
+ }
384
+ }
385
+ export function probeWslProcesses(spawnCommand, distro, timeoutMs) {
386
+ try {
387
+ const result = spawnCommand("wsl.exe", ["--distribution", distro, "--exec", "sh", "-c", LINUX_SNAPSHOT_SCRIPT, "codenomad-wsl-identity"], { encoding: "utf8", timeout: timeoutMs });
388
+ if (result.status !== 0)
389
+ return { ok: false, error: commandError(result) };
390
+ return snapshotOrFailure(parseDelimitedSnapshot(String(result.stdout ?? ""), true));
391
+ }
392
+ catch (error) {
393
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
394
+ }
395
+ }
396
+ export function signalPosixProcesses(spawnCommand, request, timeoutMs, platform) {
397
+ const linux = platform === "linux";
398
+ return runGuardedCommand(spawnCommand, "sh", ["-c", linux ? LINUX_GUARDED_SIGNAL_SCRIPT : POSIX_GUARDED_SIGNAL_SCRIPT, "codenomad-guarded-signal", ...shellGuardArgs(request, linux)], timeoutMs);
399
+ }
400
+ export function signalOwnedPosixProcessGroup(spawnCommand, rootPid, signal, timeoutMs) {
401
+ return runGuardedCommand(spawnCommand, "sh", ["-c", POSIX_OWNED_GROUP_SIGNAL_SCRIPT, "codenomad-owned-group-cleanup", String(rootPid), signalName(signal)], timeoutMs);
402
+ }
403
+ export function signalWslProcesses(spawnCommand, distro, request, timeoutMs) {
404
+ return runGuardedCommand(spawnCommand, "wsl.exe", ["--distribution", distro, "--exec", "sh", "-c", LINUX_GUARDED_SIGNAL_SCRIPT, "codenomad-wsl-guarded-signal", ...shellGuardArgs(request, true)], timeoutMs);
405
+ }
406
+ export function signalWindowsProcesses(spawnCommand, request, timeoutMs) {
407
+ return runGuardedCommand(spawnCommand, "powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", buildWindowsGuardedScript(request)], timeoutMs);
408
+ }
@@ -0,0 +1,165 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { probePosixProcesses, probeWindowsProcesses, probeWslProcesses, sameProcess, signalOwnedPosixProcessGroup, signalPosixProcesses, signalWindowsProcesses, startedNoLaterThan, } from "./process-identity";
4
+ function result(stdout = "", status = 0, stderr = "") {
5
+ return { pid: 1, output: [null, stdout, stderr], stdout, stderr, status, signal: null };
6
+ }
7
+ function b64(value) {
8
+ return Buffer.from(value).toString("base64");
9
+ }
10
+ describe("process identity probes", () => {
11
+ it("parses Linux PID, PGID, and kernel process start ticks", () => {
12
+ let invocation;
13
+ const probe = probePosixProcesses(((command, args) => {
14
+ invocation = { command, args };
15
+ return result("42|1|42|123456|boot-a|123456\n");
16
+ }), 25, "linux");
17
+ assert.equal(invocation?.command, "sh");
18
+ assert.ok(invocation?.args.includes("codenomad-posix-identity"));
19
+ assert.equal(probe.ok, true);
20
+ if (probe.ok) {
21
+ assert.deepEqual(probe.processes.get(42), {
22
+ pid: 42,
23
+ parentPid: 1,
24
+ groupId: 42,
25
+ startTime: "123456",
26
+ bootId: "boot-a",
27
+ startOrder: "123456",
28
+ });
29
+ }
30
+ });
31
+ it("uses one process-table query on non-Linux POSIX platforms", () => {
32
+ let invocation;
33
+ const probe = probePosixProcesses(((command, args) => {
34
+ invocation = { command, args };
35
+ return result(" 42 1 42 Fri Jul 10 12:34:56 2026 /usr/bin/opencode serve\n");
36
+ }), 25, "darwin");
37
+ assert.equal(invocation?.command, "ps");
38
+ assert.deepEqual(invocation?.args, ["-axo", "pid=,ppid=,pgid=,lstart=,command="]);
39
+ assert.equal(probe.ok && probe.processes.get(42)?.startTime, "Fri Jul 10 12:34:56 2026\t/usr/bin/opencode serve");
40
+ });
41
+ it("round trips delimiter-heavy non-Linux POSIX command identities", () => {
42
+ const command = "/opt/opencode 'pipe|value'\t\"quoted\" café";
43
+ const start = "Fri Jul 10 12:34:56 2026";
44
+ const probe = probePosixProcesses((() => result(`42 1 42 ${start} ${command}\n`)), 25, "darwin");
45
+ assert.equal(probe.ok && probe.processes.get(42)?.startTime, `${start}\t${command}`);
46
+ });
47
+ it("uses delimiter-safe identities during non-Linux POSIX escalation", () => {
48
+ const command = "/opt/opencode pipe|value\nnext\t'quoted'";
49
+ const startTime = `Fri Jul 10 12:34:56 2026\t${command}`;
50
+ const identity = { pid: 42, parentPid: 1, groupId: 42, startTime };
51
+ const guarded = signalPosixProcesses((() => result(`CODENOMAD_TARGET_B64|42|1|42|${b64("Fri Jul 10 12:34:56 2026")}|${b64(command)}\nCODENOMAD_RESULT|1||1\n`)), { leader: identity, groupId: 42, members: [identity], signal: "SIGKILL" }, 25, "darwin");
52
+ assert.equal(guarded.ok, true);
53
+ assert.equal(guarded.ok && guarded.signaled[0]?.startTime, startTime);
54
+ });
55
+ it("signals and rescans a still-owned portable POSIX process group", () => {
56
+ const command = "/opt/opencode pipe|value\nchild";
57
+ let script = "";
58
+ const cleanup = signalOwnedPosixProcessGroup(((_command, args) => {
59
+ script = args[1] ?? "";
60
+ return result(`CODENOMAD_TARGET_B64|42|1|42|${b64("Fri Jul 10 12:34:56 2026")}|${b64(command)}\n` +
61
+ "CODENOMAD_RESULT|1||1\n");
62
+ }), 42, "SIGTERM", 25);
63
+ assert.equal(cleanup.ok && cleanup.matched, true);
64
+ assert.equal(cleanup.ok && cleanup.signaled[0]?.startTime, `Fri Jul 10 12:34:56 2026\t${command}`);
65
+ assert.ok(script.indexOf('kill "-$requested_signal"') < script.lastIndexOf("for current_pid"));
66
+ });
67
+ it("rejects malformed portable process rows", () => {
68
+ const probe = probePosixProcesses((() => result("42 malformed process row\n")), 25, "darwin");
69
+ assert.deepEqual(probe, { ok: false, error: "process identity query returned no parseable processes" });
70
+ });
71
+ it("queries WSL identities inside the selected distro", () => {
72
+ let invocation;
73
+ const probe = probeWslProcesses(((command, args) => {
74
+ invocation = { command, args };
75
+ return result("99|1|99|123456|boot-a|123456\n101|99|99|123460|boot-a|123460\n");
76
+ }), "Ubuntu Test", 25);
77
+ assert.equal(invocation?.command, "wsl.exe");
78
+ assert.deepEqual(invocation?.args.slice(0, 4), ["--distribution", "Ubuntu Test", "--exec", "sh"]);
79
+ assert.ok(invocation?.args.includes("codenomad-wsl-identity"));
80
+ assert.equal(probe.ok && probe.processes.get(101)?.startTime, "123460");
81
+ });
82
+ it("parses Windows CIM CreationDate as the immutable identity", () => {
83
+ let script = "";
84
+ const probe = probeWindowsProcesses(((_command, args) => {
85
+ script = args.at(-1) ?? "";
86
+ return result("4242|100|0|20260710123456.123456+000||20260710123456\n");
87
+ }), 25);
88
+ assert.match(script, /Get-CimInstance Win32_Process/);
89
+ assert.match(script, /ProcessId -gt 0/);
90
+ assert.equal(probe.ok && probe.processes.get(4242)?.startTime, "20260710123456.123456+000");
91
+ });
92
+ it("matches only the same numeric PID and start identity", () => {
93
+ const original = { pid: 42, parentPid: 1, groupId: 42, startTime: "start-a" };
94
+ assert.equal(sameProcess(original, { ...original }), true);
95
+ assert.equal(sameProcess(original, { ...original, startTime: "start-b" }), false);
96
+ assert.equal(sameProcess(original, { ...original, pid: 43 }), false);
97
+ });
98
+ it("compares Linux start ticks numerically and rejects non-numeric fallbacks", () => {
99
+ const identity = { pid: 42, parentPid: 1, groupId: 42, startTime: "9", startOrder: "9" };
100
+ assert.equal(startedNoLaterThan(identity, "10"), true);
101
+ assert.equal(startedNoLaterThan({ ...identity, startOrder: "11" }, "10"), false);
102
+ assert.equal(startedNoLaterThan({ ...identity, startOrder: "Fri Jul 10" }, "10"), false);
103
+ });
104
+ it("returns mismatch without scheduling a second POSIX signal command", () => {
105
+ const invocations = [];
106
+ const identity = {
107
+ pid: 42,
108
+ parentPid: 1,
109
+ groupId: 42,
110
+ startTime: "123456",
111
+ bootId: "boot-a",
112
+ startOrder: "123456",
113
+ };
114
+ const guarded = signalPosixProcesses(((command, args) => {
115
+ invocations.push({ command, args });
116
+ return result("CODENOMAD_RESULT|0||0\n");
117
+ }), { leader: identity, groupId: 42, members: [identity], signal: "SIGTERM" }, 25, "linux");
118
+ assert.deepEqual(guarded, { ok: true, matched: false, signalSent: false, signaled: [] });
119
+ assert.equal(invocations.length, 1);
120
+ assert.equal(invocations[0]?.command, "sh");
121
+ assert.match(invocations[0]?.args[2] ?? "", /codenomad-guarded-signal/);
122
+ assert.ok(invocations[0]?.args.includes("123456"));
123
+ const script = invocations[0]?.args[1] ?? "";
124
+ assert.ok(script.indexOf('kill "-$requested_signal"') < script.indexOf("uptime=$(cut"));
125
+ });
126
+ it("uses one guarded Windows CIM selection and termination invocation", () => {
127
+ const invocations = [];
128
+ const identity = { pid: 4242, parentPid: 1, groupId: 4242, startTime: "created" };
129
+ const guarded = signalWindowsProcesses(((command, args) => {
130
+ invocations.push({ command, args });
131
+ return result("CODENOMAD_TARGET|4242|1|0|created||99\nCODENOMAD_RESULT|1||1\n");
132
+ }), { leader: identity, groupId: 4242, members: [identity], signal: "SIGKILL" }, 25);
133
+ assert.equal(guarded.ok && guarded.matched, true);
134
+ assert.equal(invocations.length, 1);
135
+ assert.equal(invocations[0]?.command, "powershell.exe");
136
+ const script = invocations[0]?.args.at(-1) ?? "";
137
+ assert.match(script, /CreationDate/);
138
+ assert.match(script, /Invoke-CimMethod -InputObject/);
139
+ assert.equal(script.match(/foreach \(\$process in \$selected\)/g)?.length, 2);
140
+ assert.ok(script.indexOf("CODENOMAD_TARGET|") < script.indexOf("Invoke-CimMethod"));
141
+ assert.doesNotMatch(script, /taskkill/i);
142
+ });
143
+ it("preserves observed Windows targets when guarded termination fails partway", () => {
144
+ const identity = { pid: 4242, parentPid: 1, groupId: 4242, startTime: "created" };
145
+ const guarded = signalWindowsProcesses((() => result([
146
+ "CODENOMAD_TARGET|4242|1|0|created||99",
147
+ "CODENOMAD_TARGET|4243|4242|0|descendant||100",
148
+ ].join("\n"), 1, "termination failed")), {
149
+ leader: identity,
150
+ groupId: 4242,
151
+ members: [identity],
152
+ signal: "SIGTERM",
153
+ }, 25);
154
+ assert.equal(guarded.ok, false);
155
+ assert.deepEqual(!guarded.ok && guarded.observed?.map((target) => target.pid), [4242, 4243]);
156
+ });
157
+ it("reports command failures without fabricating identities", () => {
158
+ const probe = probeWindowsProcesses((() => result("", 1, "CIM unavailable")), 25);
159
+ assert.deepEqual(probe, { ok: false, error: "CIM unavailable" });
160
+ });
161
+ it("rejects successful commands with unparseable or empty output", () => {
162
+ const probe = probeWslProcesses((() => result("not an identity")), "Ubuntu", 25);
163
+ assert.deepEqual(probe, { ok: false, error: "process identity query returned no parseable processes" });
164
+ });
165
+ });