agentic-workflow-manager 3.13.2 → 3.13.3

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.
@@ -275,17 +275,51 @@ function spawnStructured(argv, cwd, nonce, extraEnv = {}) {
275
275
  * rompia el invariante "JAMAS safe sin evidencia" (safeToReplace
276
276
  * devolvia 'safe' para un proceso vivo). process.kill(pid,0) evita la
277
277
  * capa de emulacion por completo. */
278
+ /** Sleep sincronico REAL, multiplataforma, sin depender de un binario externo
279
+ * (`sleep` no existe nativamente en win32 — ver el comentario de sleepSync
280
+ * mas arriba). `Atomics.wait` sobre un buffer compartido bloquea el hilo
281
+ * actual durante `ms` sin abrir ningun subproceso; funciona identico en
282
+ * cualquier plataforma que soporte Node. Usado SOLO por pidExistsNative
283
+ * para el reintento acotado de abajo — nunca en un hot-path de alta
284
+ * frecuencia. */
285
+ function sleepMsSync(ms) {
286
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
287
+ }
288
+ /** Reintento acotado (R6, post-mortem de CI real): dos corridas reales de
289
+ * windows-latest sobre el MISMO commit (uno vía `release.yml`, uno vía
290
+ * `ci.yml`, ambos re-corriendo el suite completo desde cero) dieron
291
+ * resultados DISTINTOS para el mismo test — `pidExistsNative` reportando
292
+ * ESRCH para un proceso recien spawneado por el propio test, genuinamente
293
+ * vivo — evidencia directa de una condicion de carrera transitoria
294
+ * especifica de esta plataforma/runner, no un bug determinista (el codigo
295
+ * no cambio entre ambas corridas). Un solo intento en el hot-path exacto
296
+ * "acabo de spawnear esto, ¿esta vivo?" no le da tiempo al SO a que el pid
297
+ * sea consultable via OpenProcess bajo carga pesada del runner. Reintentar
298
+ * con una pausa breve ANTES de declarar ESRCH definitivo no debilita el
299
+ * invariante "jamas muerte sin evidencia" — un proceso genuinamente muerto
300
+ * sigue reportando ESRCH en el reintento; esto solo absorbe el falso
301
+ * negativo transitorio. Costo maximo: ~150ms, y SOLO en la rama que ya iba
302
+ * a declarar "no existe" — el camino feliz (proceso vivo, exito inmediato)
303
+ * no paga nada. */
304
+ const PID_EXISTS_RETRY_ATTEMPTS = 3;
305
+ const PID_EXISTS_RETRY_DELAY_MS = 50;
278
306
  function pidExistsNative(pid) {
279
- try {
280
- process.kill(pid, 0);
281
- return true;
282
- }
283
- catch (error) {
284
- // ESRCH = el SO confirmo que el pid no existe. Cualquier otro
285
- // codigo (ej. EPERM: existe pero sin permiso de senializarlo) NO
286
- // es evidencia de muerte falla a favor de "vivo" (R2.1).
287
- return error.code !== 'ESRCH';
307
+ for (let attempt = 0; attempt < PID_EXISTS_RETRY_ATTEMPTS; attempt++) {
308
+ try {
309
+ process.kill(pid, 0);
310
+ return true;
311
+ }
312
+ catch (error) {
313
+ // ESRCH = el SO confirmo que el pid no existe. Cualquier otro
314
+ // codigo (ej. EPERM: existe pero sin permiso de senializarlo) NO
315
+ // es evidencia de muerte — falla a favor de "vivo" (R2.1).
316
+ if (error.code !== 'ESRCH')
317
+ return true;
318
+ if (attempt < PID_EXISTS_RETRY_ATTEMPTS - 1)
319
+ sleepMsSync(PID_EXISTS_RETRY_DELAY_MS);
320
+ }
288
321
  }
322
+ return false;
289
323
  }
290
324
  /** Vivo Y con la MISMA identidad — tupla completa, nunca PID solo (R2.1,
291
325
  * bloqueador 6): startTime + pgid + digest de ps args.
@@ -237,6 +237,22 @@ describe('process identity (win32, mockeado — sin windows real disponible en e
237
237
  expect((0, process_1.refIsAlive)(fakeRef)).toBe(false);
238
238
  expect(killSpy).toHaveBeenCalledWith(999999, 0);
239
239
  });
240
+ test('pidExistsNative reintenta un ESRCH transitorio antes de declarar muerte — no confia en un unico intento (regresion: CI real windows-latest, dos corridas del mismo commit dieron resultados distintos para un proceso recien spawneado)', () => {
241
+ Object.defineProperty(process, 'platform', { value: 'win32' });
242
+ let calls = 0;
243
+ const killSpy = jest.spyOn(process, 'kill').mockImplementation(() => {
244
+ calls++;
245
+ if (calls < 3) {
246
+ const err = new Error('transient');
247
+ err.code = 'ESRCH';
248
+ throw err;
249
+ }
250
+ return true; // el pid "aparece" recien al tercer intento — simula la carrera real observada
251
+ });
252
+ const fakeRef = { pid: 424242, startTime: 'x', spawnNonce: 'n', argvDigest: 'd', processGroup: 424242, psArgsDigest: 'x' };
253
+ expect((0, process_1.refIsAlive)(fakeRef)).toBe(true); // NUNCA declara muerte por el ESRCH transitorio de los primeros 2 intentos
254
+ expect(killSpy).toHaveBeenCalledTimes(3);
255
+ });
240
256
  test('refIsAlive en win32 NUNCA declara muerte por un error que no sea ESRCH (ej. EPERM: el pid existe pero sin permiso de senializarlo) (R2.1)', () => {
241
257
  Object.defineProperty(process, 'platform', { value: 'win32' });
242
258
  jest.spyOn(process, 'kill').mockImplementation(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "3.13.2",
3
+ "version": "3.13.3",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"