import { AsyncLocalStorage } from 'node:async_hooks'; import { randomUUID } from 'node:crypto'; import { assertProcessing } from './state.mjs'; import { registerRuntime, updateRuntime, unregisterRuntime, processIdentity, createRuntimeSignaller, runtimeGroupState, runtime } from './runtimes.mjs'; const contexts = new AsyncLocalStorage(); export const currentProcessing = () => contexts.getStore(); // Trusted compensation/temporary-file cleanup only; never an agent work entry point. export const withoutProcessing = fn => contexts.run(undefined, fn); export async function withProcessing(dataRoot, fn) { const parent = currentProcessing(); const generation = assertProcessing(dataRoot, parent?.dataRoot === dataRoot ? parent.generation : null).generation; const pending = registerRuntime(dataRoot, { kind: 'effect', business: 'instance', run: randomUUID() }); const controller = new AbortController(); const children = []; const guard = () => assertProcessing(dataRoot, generation); const poll = setInterval(() => { try { guard(); } catch (e) { controller.abort(e); } }, 25); poll.unref(); const scope = { dataRoot, generation, signal: controller.signal, guard, track(child) { const identity = processIdentity(child.pid); if (!identity) return; children.push(new Promise(resolve => child.once('close', resolve))); let record; try { record = registerRuntime(dataRoot, { kind: 'effect', business: 'instance', run: randomUUID() }); } catch (e) { createRuntimeSignaller({ ...identity, kind: 'effect', group: true })('SIGKILL'); throw e; } const running = updateRuntime(dataRoot, record.id, { ...identity, group: true, phase: 'running' }); const signal = createRuntimeSignaller(running, members => updateRuntime(dataRoot, record.id, { members })); let kill = null; const cancel = () => { signal('SIGTERM'); kill = setTimeout(() => signal('SIGKILL'), 5000); kill.unref(); }; controller.signal.addEventListener('abort', cancel, { once: true }); child.once('close', () => { controller.signal.removeEventListener('abort', cancel); clearTimeout(kill); try { if (runtimeGroupState(runtime(dataRoot, record.id)) === false) unregisterRuntime(dataRoot, record.id); } catch { /* keep unverifiable receipts for reconciliation */ } }); try { guard(); } catch (e) { controller.abort(e); } if (controller.signal.aborted && kill === null) cancel(); }, }; try { guard(); const result = await contexts.run(scope, () => fn(scope)); guard(); return result; } finally { clearInterval(poll); await Promise.all(children); unregisterRuntime(dataRoot, pending.id); } }