@sema-agent/core 1.433.0 → 1.434.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.
@@ -1,4 +1,4 @@
1
- import { spawn } from "node:child_process";
1
+ import { spawn, spawnSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  const DEFAULT_GRACE_MS = 3000;
@@ -25,8 +25,18 @@ export function killProcessTree(pid, opts) {
25
25
  return;
26
26
  }
27
27
  const graceMs = normalizeGraceMs(opts?.graceMs);
28
- signalProcessTreeUnix(pid, "SIGTERM", useGroupKill);
28
+ const termDescendants = signalProcessTreeUnix(pid, "SIGTERM", useGroupKill);
29
29
  setTimeout(() => {
30
+ const rediscovered = enumerateDescendantsSync(pid);
31
+ const verifiedCarriedForward = new Map();
32
+ for (const [p, originalEtime] of termDescendants) {
33
+ const currentEtime = rediscovered.etimeOf.get(p);
34
+ if (currentEtime !== undefined && verifyStillSameProcess(originalEtime, currentEtime)) {
35
+ verifiedCarriedForward.set(p, currentEtime);
36
+ }
37
+ }
38
+ const knownDescendants = new Map([...verifiedCarriedForward, ...rediscovered.descendants]);
39
+ killEach(knownDescendants.keys(), "SIGKILL");
30
40
  if (escalationCancelled(opts?.stillRunning)) {
31
41
  if (useGroupKill && isProcessAlive(-pid)) {
32
42
  try {
@@ -43,7 +53,19 @@ export function killProcessTree(pid, opts) {
43
53
  if (!stillAlive) {
44
54
  return;
45
55
  }
46
- signalProcessTreeUnix(pid, "SIGKILL", useGroupKill);
56
+ if (useGroupKill) {
57
+ try {
58
+ process.kill(-pid, "SIGKILL");
59
+ return;
60
+ }
61
+ catch {
62
+ }
63
+ }
64
+ try {
65
+ process.kill(pid, "SIGKILL");
66
+ }
67
+ catch {
68
+ }
47
69
  }, graceMs).unref();
48
70
  }
49
71
  function escalationCancelled(stillRunning) {
@@ -82,19 +104,107 @@ function isProcessAlive(pid) {
82
104
  }
83
105
  }
84
106
  function signalProcessTreeUnix(pid, signal, useGroupKill) {
107
+ const descendants = enumerateDescendantsSync(pid).descendants;
85
108
  if (useGroupKill) {
86
109
  try {
87
110
  process.kill(-pid, signal);
88
- return;
89
111
  }
90
112
  catch {
113
+ try {
114
+ process.kill(pid, signal);
115
+ }
116
+ catch {
117
+ }
91
118
  }
119
+ killEach(descendants.keys(), signal);
120
+ return descendants;
92
121
  }
93
122
  try {
94
123
  process.kill(pid, signal);
95
124
  }
96
125
  catch {
97
126
  }
127
+ killEach(descendants.keys(), signal);
128
+ return descendants;
129
+ }
130
+ function killEach(pids, signal) {
131
+ for (const pid of pids) {
132
+ try {
133
+ process.kill(pid, signal);
134
+ }
135
+ catch {
136
+ }
137
+ }
138
+ }
139
+ const PS_ENUMERATION_TIMEOUT_MS = 500;
140
+ const EMPTY_SNAPSHOT = { descendants: new Map(), etimeOf: new Map() };
141
+ function enumerateDescendantsSync(rootPid) {
142
+ if (process.platform === "win32")
143
+ return EMPTY_SNAPSHOT;
144
+ let out;
145
+ try {
146
+ const result = spawnSync("ps", ["-A", "-o", "pid=", "-o", "ppid=", "-o", "etime="], {
147
+ timeout: PS_ENUMERATION_TIMEOUT_MS,
148
+ encoding: "utf8",
149
+ windowsHide: true,
150
+ });
151
+ if (result.error || typeof result.stdout !== "string")
152
+ return EMPTY_SNAPSHOT;
153
+ out = result.stdout;
154
+ }
155
+ catch {
156
+ return EMPTY_SNAPSHOT;
157
+ }
158
+ const childrenOf = new Map();
159
+ const etimeOf = new Map();
160
+ for (const line of out.split("\n")) {
161
+ const m = /^\s*(\d+)\s+(\d+)\s+(\S+)\s*$/.exec(line);
162
+ if (!m)
163
+ continue;
164
+ const p = Number(m[1]);
165
+ const pp = Number(m[2]);
166
+ etimeOf.set(p, m[3]);
167
+ const list = childrenOf.get(pp);
168
+ if (list)
169
+ list.push(p);
170
+ else
171
+ childrenOf.set(pp, [p]);
172
+ }
173
+ const seen = new Set();
174
+ const queue = [rootPid];
175
+ while (queue.length > 0) {
176
+ const cur = queue.shift();
177
+ for (const kid of childrenOf.get(cur) ?? []) {
178
+ if (kid <= 1 || kid === rootPid || seen.has(kid))
179
+ continue;
180
+ seen.add(kid);
181
+ queue.push(kid);
182
+ }
183
+ }
184
+ const descendants = new Map();
185
+ for (const p of seen) {
186
+ const et = etimeOf.get(p);
187
+ if (et !== undefined)
188
+ descendants.set(p, et);
189
+ }
190
+ return { descendants, etimeOf };
191
+ }
192
+ function verifyStillSameProcess(originalEtime, currentEtime) {
193
+ const orig = parseEtimeSeconds(originalEtime);
194
+ const cur = parseEtimeSeconds(currentEtime);
195
+ if (orig === undefined || cur === undefined)
196
+ return false;
197
+ return cur >= orig;
198
+ }
199
+ function parseEtimeSeconds(etime) {
200
+ const m = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/.exec(etime.trim());
201
+ if (!m)
202
+ return undefined;
203
+ const days = m[1] !== undefined ? Number(m[1]) : 0;
204
+ const hours = m[2] !== undefined ? Number(m[2]) : 0;
205
+ const minutes = Number(m[3]);
206
+ const seconds = Number(m[4]);
207
+ return days * 86400 + hours * 3600 + minutes * 60 + seconds;
98
208
  }
99
209
  function runTaskkill(args) {
100
210
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "1.433.0",
3
+ "version": "1.434.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",