@sebastianandreasson/pi-autonomous-agents 0.5.1 → 0.6.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/src/pi-repo.mjs CHANGED
@@ -114,6 +114,57 @@ export function isProcessRunning(pid) {
114
114
  }
115
115
  }
116
116
 
117
+ const ownedChildren = new Map()
118
+
119
+ export function registerOwnedChildProcess(child, options = {}) {
120
+ const pid = normalizePid(child?.pid)
121
+ if (pid <= 0) {
122
+ return () => {}
123
+ }
124
+
125
+ const entry = {
126
+ useProcessGroup: options.useProcessGroup === true && process.platform !== 'win32',
127
+ }
128
+ ownedChildren.set(pid, entry)
129
+
130
+ const unregister = () => {
131
+ ownedChildren.delete(pid)
132
+ }
133
+
134
+ if (typeof child?.once === 'function') {
135
+ child.once('exit', unregister)
136
+ child.once('close', unregister)
137
+ }
138
+
139
+ return unregister
140
+ }
141
+
142
+ export function signalChildProcess(pid, signal, options = {}) {
143
+ const normalizedPid = normalizePid(pid)
144
+ if (normalizedPid <= 0) {
145
+ return false
146
+ }
147
+
148
+ try {
149
+ if (options.useProcessGroup === true && process.platform !== 'win32') {
150
+ process.kill(-normalizedPid, signal)
151
+ } else {
152
+ process.kill(normalizedPid, signal)
153
+ }
154
+ return true
155
+ } catch {
156
+ return false
157
+ }
158
+ }
159
+
160
+ export function signalOwnedChildProcesses(signal) {
161
+ let handled = false
162
+ for (const [pid, entry] of [...ownedChildren.entries()]) {
163
+ handled = signalChildProcess(pid, signal, entry) || handled
164
+ }
165
+ return handled
166
+ }
167
+
117
168
  export async function readJsonFile(filePath, fallback = null) {
118
169
  try {
119
170
  const raw = await fs.readFile(filePath, 'utf8')
@@ -211,20 +262,45 @@ export async function releaseRunLock(lockFile, runId) {
211
262
  }
212
263
 
213
264
  export function signalProcessTree(pid, signal) {
214
- const normalizedPid = normalizePid(pid)
215
- if (normalizedPid <= 0) {
216
- return false
265
+ return signalChildProcess(pid, signal, { useProcessGroup: true })
266
+ }
267
+
268
+ export function watchParentProcess(onParentExit, options = {}) {
269
+ const expectedParentPid = normalizePid(options.parentPid ?? process.ppid)
270
+ if (expectedParentPid <= 0 || typeof onParentExit !== 'function') {
271
+ return () => {}
217
272
  }
218
273
 
219
- try {
220
- if (process.platform !== 'win32') {
221
- process.kill(-normalizedPid, signal)
222
- } else {
223
- process.kill(normalizedPid, signal)
274
+ let active = true
275
+ const intervalMs = Number.isFinite(Number(options.intervalMs))
276
+ ? Math.max(100, Number(options.intervalMs))
277
+ : 1000
278
+
279
+ const interval = setInterval(() => {
280
+ if (!active) {
281
+ return
224
282
  }
225
- return true
226
- } catch {
227
- return false
283
+
284
+ const currentParentPid = normalizePid(process.ppid)
285
+ if (currentParentPid === expectedParentPid && currentParentPid > 1) {
286
+ return
287
+ }
288
+
289
+ active = false
290
+ clearInterval(interval)
291
+ onParentExit({
292
+ expectedParentPid,
293
+ currentParentPid,
294
+ })
295
+ }, intervalMs)
296
+
297
+ if (typeof interval.unref === 'function') {
298
+ interval.unref()
299
+ }
300
+
301
+ return () => {
302
+ active = false
303
+ clearInterval(interval)
228
304
  }
229
305
  }
230
306
 
@@ -474,6 +550,9 @@ export async function runShellCommand({
474
550
  detached: process.platform !== 'win32',
475
551
  stdio: ['pipe', 'pipe', 'pipe'],
476
552
  })
553
+ const unregisterChild = registerOwnedChildProcess(child, {
554
+ useProcessGroup: process.platform !== 'win32',
555
+ })
477
556
 
478
557
  let stdout = ''
479
558
  let stderr = ''
@@ -506,6 +585,7 @@ export async function runShellCommand({
506
585
  })
507
586
 
508
587
  child.on('error', (error) => {
588
+ unregisterChild()
509
589
  if (killTimer) {
510
590
  clearTimeout(killTimer)
511
591
  }
@@ -524,6 +604,7 @@ export async function runShellCommand({
524
604
  })
525
605
 
526
606
  child.on('close', (code) => {
607
+ unregisterChild()
527
608
  if (killTimer) {
528
609
  clearTimeout(killTimer)
529
610
  }