UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

219 lines • 7.87 kB
"use strict"; /** * work — an admitted operation's charge, and the work under it until that work * has settled. * * Pattern: one object per unit of work that is at once the request guard * (`mcp/requestGuard.ts`) and the count of what is still running. * Role: The pool's third budget (`docs/design/2026-10-mcp-client-pool.md` * § 5.3). The accounting unit is the admitted LOGICAL operation: a * `tools/call`, a listing (one `list(ctx)` for one conversation), an * `acquire` and an ending each take one slot at admission. Setup an * operation performs for itself — the open of its key's client, the * revalidating `tools/list` of a replacement — runs inside that slot. * * AN ABORT IS NOT SETTLEMENT. A slot is released only when the operation's * logical request has its answer AND every piece of work under it has settled: * the `send()` it made, a credential vend, an attempt's `fetch`, an AWS `send`, * the open it owns. A caller's timeout answers the caller; it frees nothing. * Work that never settles keeps its charge — `stats().unsettled` reports it, * and the budget refuses new work rather than admit it against room that does * not exist. There is no release that returns capacity sooner. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.OperationBudget = exports.Operation = exports.Work = void 0; const abort_js_1 = require("../../lib/abort.js"); /** * One unit of pooled work: its requests are checked against it at every * boundary (`check`), and every promise started for it is counted until it * settles (`track`). It is SETTLED once its logical part has finished and the * count is zero. */ class Work { phase; link; pending = 0; finished = false; done = false; /** Still running underneath when the caller was answered: it lingered. */ lingered = false; listeners = []; /** What `check` asks besides the abort — a call's binding. */ extra; /** What the latest answered request proved, if anything. */ evidence; constructor( /** What it is doing now. */ phase, sources) { this.phase = phase; this.link = (0, abort_js_1.linkedController)(...sources); } /** What aborts its own work: its sources, its deadline, its caller detaching. */ get signal() { return this.link.controller.signal; } /** True once its logical part finished and everything under it settled. */ get settled() { return this.done; } /** Its caller was answered while work under it still ran. */ get unsettledPastAnswer() { return this.finished && !this.done; } /** Abort its work with `reason` (a deadline, a detached caller). Idempotent. */ abort(reason) { if (!this.link.controller.signal.aborted) this.link.controller.abort(reason); } /** Ask `gate` too, each time a request is checked. */ guardWith(gate) { this.extra = gate; } /** * The send gate: refused once its caller was answered (nothing more is sent * for an answered operation), once aborted, or when the extra gate refuses. */ check() { if (this.finished) throw workFinished(); const { signal } = this; if (signal.aborted) throw (0, abort_js_1.abortErrorOf)(signal); this.extra?.(); } /** Count `work` until it settles. Never throws; never surfaces its rejection. */ track(work) { if (this.done) return; this.pending += 1; const release = () => { this.pending -= 1; this.settleIfDone(); }; Promise.resolve(work).then(release, release); } /** Record what a request's answer proved. */ note(evidence) { this.evidence = evidence; } /** * Its logical part is over: the caller has its answer. It settles now, or — * when work under it still runs — when that work does. */ finish() { if (this.finished) return; this.finished = true; if (this.pending > 0) this.lingered = true; // Nothing more may be sent for it: `check` refuses from here on. Its signal // is left alone — a stream the SDK is still reading is not this work's to cut. this.settleIfDone(); } /** Did it linger — answered while work under it ran? Read once it settled. */ get settledLate() { return this.lingered; } /** Call `listener` once it has settled (at once when it already has). */ onSettled(listener) { if (this.done) listener(); else this.listeners.push(listener); } /** A promise that resolves once it has settled. */ whenSettled() { return new Promise((resolve) => this.onSettled(resolve)); } settleIfDone() { if (this.done || !this.finished || this.pending > 0) return; this.done = true; this.link.unlink(); for (const listener of this.listeners.splice(0)) { try { listener(); } catch { // A listener is the pool's own bookkeeping; one failing must not stop the rest. } } } } exports.Work = Work; /** The reason a finished operation refuses any later send — a fixed sentence. */ function workFinished() { return Object.assign(new Error('This pooled operation has already been answered.'), { name: 'AbortError', }); } /** An admitted operation: a unit of work that holds one slot of the budget. */ class Operation extends Work { kind; admittedAt; constructor(kind, phase, sources, /** When it was admitted, on the pool's clock. */ admittedAt) { super(phase, sources); this.kind = kind; this.admittedAt = admittedAt; } } exports.Operation = Operation; /** * The pool-wide operation budget: at most `max` operations charged, each from * its admission until it has SETTLED (see the module note). Admission is the * only way in; settlement the only way out. */ class OperationBudget { max; charged = new Set(); onRelease = []; constructor(max) { this.max = max; } /** Operations charged now. */ get count() { return this.charged.size; } /** Is there a free slot? */ get hasRoom() { return this.charged.size < this.max; } /** * Admit one operation, or `undefined` when the budget is full. The slot is * charged from here until the operation settles. */ admit(kind, phase, sources, now) { if (!this.hasRoom) return undefined; const operation = new Operation(kind, phase, sources, now); this.charged.add(operation); operation.onSettled(() => { this.charged.delete(operation); for (const listener of [...this.onRelease]) listener(operation); }); return operation; } /** Call `listener` each time an operation settles and frees its slot. Returns the unsubscribe. */ whenReleased(listener) { this.onRelease.push(listener); return () => { const at = this.onRelease.indexOf(listener); if (at >= 0) this.onRelease.splice(at, 1); }; } /** Charged work whose caller was already answered and which has not settled, by what it is doing. */ unsettled() { const counts = { open: 0, listing: 0, call: 0, ending: 0 }; for (const operation of this.charged) { if (operation.unsettledPastAnswer) counts[operation.phase] += 1; } return counts; } } exports.OperationBudget = OperationBudget; //# sourceMappingURL=work.js.map