UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

102 lines (101 loc) • 4.06 kB
import { traceHookCrash } from "./gate-crash-trace.js"; import { thrownText } from "./thrown-value.js"; export function gateWaitFailureOf(err) { const mark = err?.gateWait; return mark === "timeout" || mark === "aborted" ? mark : undefined; } function gateWaitError(message, kind) { const err = new Error(message); err.gateWait = kind; return err; } export function withGateTimeout(p, ms, signal, label = "reversibilityProbe") { if (ms === undefined && signal === undefined) return p; return new Promise((resolve, reject) => { let settled = false; const done = (fn, v) => { if (settled) return; settled = true; if (timer !== undefined) clearTimeout(timer); signal?.removeEventListener("abort", onAbort); fn(v); }; const onAbort = () => { const reason = signal?.reason; const ended = gateWaitFailureOf(reason) !== undefined ? reason : reason?.name === "TimeoutError" ? gateWaitError(`${label} timed out (its deadline elapsed)`, "timeout") : gateWaitError(`${label} aborted (task ended)`, "aborted"); done(reject, ended); }; const timer = ms !== undefined ? setTimeout(() => done(reject, gateWaitError(`${label} timed out after ${ms}ms`, "timeout")), ms) : undefined; p.then((v) => done(resolve, v), (err) => done(reject, err)); if (signal?.aborted) { onAbort(); return; } signal?.addEventListener("abort", onAbort); }); } export const DEFAULT_GATE_AWAIT_TIMEOUT_MS = 30_000; export function createGateDeadline(input, notifier) { let resolved; return () => { if (resolved !== undefined) return resolved; const supplied = input.approvalTimeoutMs; if (supplied === undefined) resolved = DEFAULT_GATE_AWAIT_TIMEOUT_MS; else if (Number.isFinite(supplied) && supplied >= 0 && supplied <= 2_147_483_647) resolved = supplied; else { resolved = DEFAULT_GATE_AWAIT_TIMEOUT_MS; traceHookCrash(input, new Error(`approvalTimeoutMs must be a non-negative finite number no greater than 2147483647 (got ${String(supplied)}) — the gate's bounded awaits fall back to the ${DEFAULT_GATE_AWAIT_TIMEOUT_MS}ms default`), notifier); } return resolved; }; } export async function fromSource(ask, signal, label) { try { return { ok: true, value: await withGateTimeout(Promise.resolve().then(ask), signal === undefined ? DEFAULT_GATE_AWAIT_TIMEOUT_MS : undefined, signal, label) }; } catch (cause) { return { ok: false, cause }; } } export async function awaitWriteProtectionJudgment(judge, deadlineMs, signal, onFailure) { const deadline = new AbortController(); const judged = judge(deadline.signal); if (judged === null) return undefined; const ms = deadlineMs(); const net = new AbortController(); const endBoth = (reason) => { if (!deadline.signal.aborted) deadline.abort(reason); setImmediate(() => net.abort(reason)); }; const timer = setTimeout(() => endBoth(gateWaitError(`writeProtectionJudge timed out after ${ms}ms`, "timeout")), ms); const onTaskAbort = () => endBoth(gateWaitError("writeProtectionJudge aborted (task ended)", "aborted")); if (signal?.aborted === true) onTaskAbort(); else signal?.addEventListener("abort", onTaskAbort); try { return await withGateTimeout(judged, undefined, net.signal, "writeProtectionJudge"); } catch (err) { onFailure?.(err); return { kind: "unresolvable", cause: `the write-protection judgment did not answer: ${thrownText(err)}`, unresolvedSymlink: false }; } finally { clearTimeout(timer); signal?.removeEventListener("abort", onTaskAbort); } }