agentfootprint 9.6.0 → 9.7.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/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/dist/adapters/code/agentcore.js +294 -0
- package/dist/adapters/code/agentcore.js.map +1 -0
- package/dist/adapters/code/local.js +200 -0
- package/dist/adapters/code/local.js.map +1 -0
- package/dist/core/Agent.js +132 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/RunnerBase.js +67 -0
- package/dist/core/RunnerBase.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +90 -0
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/codeRunnerTool.js +252 -0
- package/dist/core/codeRunnerTool.js.map +1 -0
- package/dist/core/toolSessions.js +396 -0
- package/dist/core/toolSessions.js.map +1 -0
- package/dist/core/tools.js.map +1 -1
- package/dist/doors/providers.js +13 -0
- package/dist/doors/providers.js.map +1 -1
- package/dist/esm/adapters/code/agentcore.d.ts +133 -0
- package/dist/esm/adapters/code/agentcore.js +290 -0
- package/dist/esm/adapters/code/agentcore.js.map +1 -0
- package/dist/esm/adapters/code/local.d.ts +99 -0
- package/dist/esm/adapters/code/local.js +196 -0
- package/dist/esm/adapters/code/local.js.map +1 -0
- package/dist/esm/adapters/types.d.ts +87 -0
- package/dist/esm/core/Agent.d.ts +65 -0
- package/dist/esm/core/Agent.js +133 -1
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/RunnerBase.d.ts +51 -0
- package/dist/esm/core/RunnerBase.js +67 -0
- package/dist/esm/core/RunnerBase.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.d.ts +31 -0
- package/dist/esm/core/agent/stages/toolCalls.js +90 -0
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +23 -2
- package/dist/esm/core/codeRunnerTool.d.ts +120 -0
- package/dist/esm/core/codeRunnerTool.js +247 -0
- package/dist/esm/core/codeRunnerTool.js.map +1 -0
- package/dist/esm/core/toolSessions.d.ts +318 -0
- package/dist/esm/core/toolSessions.js +389 -0
- package/dist/esm/core/toolSessions.js.map +1 -0
- package/dist/esm/core/tools.d.ts +60 -0
- package/dist/esm/core/tools.js.map +1 -1
- package/dist/esm/doors/providers.d.ts +7 -0
- package/dist/esm/doors/providers.js +10 -0
- package/dist/esm/doors/providers.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +50 -0
- package/dist/esm/events/registry.d.ts +9 -1
- package/dist/esm/events/registry.js +8 -0
- package/dist/esm/events/registry.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/mcp/mcpServe.js +37 -1
- package/dist/esm/lib/mcp/mcpServe.js.map +1 -1
- package/dist/esm/lib/trace-toolpack/traceToolpack.js +15 -1
- package/dist/esm/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/events/registry.js +8 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/mcp/mcpServe.js +37 -1
- package/dist/lib/mcp/mcpServe.js.map +1 -1
- package/dist/lib/trace-toolpack/traceToolpack.js +15 -1
- package/dist/lib/trace-toolpack/traceToolpack.js.map +1 -1
- package/dist/types/adapters/code/agentcore.d.ts +134 -0
- package/dist/types/adapters/code/agentcore.d.ts.map +1 -0
- package/dist/types/adapters/code/local.d.ts +100 -0
- package/dist/types/adapters/code/local.d.ts.map +1 -0
- package/dist/types/adapters/types.d.ts +87 -0
- package/dist/types/adapters/types.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts +65 -0
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/RunnerBase.d.ts +51 -0
- package/dist/types/core/RunnerBase.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts +31 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +23 -2
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/codeRunnerTool.d.ts +121 -0
- package/dist/types/core/codeRunnerTool.d.ts.map +1 -0
- package/dist/types/core/toolSessions.d.ts +319 -0
- package/dist/types/core/toolSessions.d.ts.map +1 -0
- package/dist/types/core/tools.d.ts +60 -0
- package/dist/types/core/tools.d.ts.map +1 -1
- package/dist/types/doors/providers.d.ts +7 -0
- package/dist/types/doors/providers.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +50 -0
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/events/registry.d.ts +9 -1
- package/dist/types/events/registry.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/mcp/mcpServe.d.ts.map +1 -1
- package/dist/types/lib/trace-toolpack/traceToolpack.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toolSessions — the end-signal a tool can be handed, and the tier that fires it.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: Registrar + Template Method for the firing matrix.
|
|
5
|
+
* Role: core primitive. `ToolExecutionContext.onTeardown` registers here;
|
|
6
|
+
* `Agent.run`'s terminals, `RunnerBase.closeToolSessions` and
|
|
7
|
+
* `RunnerBase.shutdown` fire it.
|
|
8
|
+
* Emits: nothing itself. `register()` ANSWERS what it did (started/reused) so
|
|
9
|
+
* the caller — still inside a stage — can emit with a real
|
|
10
|
+
* `runtimeStageId`, and teardown REPORTS (see {@link ToolSessionReport})
|
|
11
|
+
* so the runner can emit the two that fire after the last stage.
|
|
12
|
+
*
|
|
13
|
+
* ── The gap this closes ─────────────────────────────────────────────────────
|
|
14
|
+
* A session-based tool service (a managed code interpreter, a headless browser)
|
|
15
|
+
* is Start → Invoke ×N → Stop. Before 9.7.0 a `Tool` had nowhere to hold the
|
|
16
|
+
* middle of that: `ToolExecutionContext` carried no run or session identity, and
|
|
17
|
+
* nothing in the framework ever said "this is over". So a tool either paid
|
|
18
|
+
* start-up on every call, or held the session in a module-level map — which, in
|
|
19
|
+
* a standing agent serving many people from one process, hands ONE live sandbox
|
|
20
|
+
* to whoever calls next. That is not a leak of memory; it is a leak of a
|
|
21
|
+
* filesystem, an environment and half-run state, across users.
|
|
22
|
+
*
|
|
23
|
+
* ── Why the registration lives on the CONTEXT ───────────────────────────────
|
|
24
|
+
* Not `Tool.dispose()`: a `Tool` is a singleton, built once and shared by every
|
|
25
|
+
* run and every session, so "dispose the tool" cannot mean "dispose this
|
|
26
|
+
* caller's session".
|
|
27
|
+
* Not a lifecycle PORT the consumer wires: that makes the common case (one tool
|
|
28
|
+
* that happens to hold a session) a wiring exercise, and the tool that knows the
|
|
29
|
+
* key is the one that cannot reach the port.
|
|
30
|
+
* `ctx.onTeardown(cleanup, { scope, key })` is the only seam where the key and
|
|
31
|
+
* the resource are both in hand at the same instant.
|
|
32
|
+
*
|
|
33
|
+
* ── The seven laws ──────────────────────────────────────────────────────────
|
|
34
|
+
* 1. **At most once, ever** per registration — the `stopped`-flag mechanism
|
|
35
|
+
* `strategies/lifecycle.ts` uses for strategies, applied one layer down.
|
|
36
|
+
* 2. **Idempotent by `(tool, scope, key)`, first wins.** A second registration
|
|
37
|
+
* under the same key does not replace the first — the first is the one
|
|
38
|
+
* holding the live handle; replacing it would drop that handle on the
|
|
39
|
+
* floor. The repeat is a TOUCH: it refreshes liveness (that is how the
|
|
40
|
+
* idle sweep and the LRU learn a session is still in use) and answers
|
|
41
|
+
* `'reused'`.
|
|
42
|
+
* 3. **Reverse order of registration**, settled with `Promise.allSettled`.
|
|
43
|
+
* 4. **Bounded** by `timeoutMs` (default 5s). Teardown is on the SIGTERM path;
|
|
44
|
+
* an unbounded `stop()` turns a container stop into a wait for SIGKILL.
|
|
45
|
+
* 5. **Never throws into the run** — but **never silent** either. Every
|
|
46
|
+
* failure, and every timeout, is reported. "Swallowed AND silent" is what
|
|
47
|
+
* separates a passive recorder from a resource that did not get released.
|
|
48
|
+
* 6. **Tolerates "already gone."** The far side reaps idle sessions on its own
|
|
49
|
+
* schedule; a `Stop` on a session AWS already collected is a no-op, not an
|
|
50
|
+
* incident. It still reports, so the difference is visible.
|
|
51
|
+
* 7. **Nothing live is ever persisted.** A checkpoint carries what survives
|
|
52
|
+
* `structuredClone`; a session handle does not. A resumed run re-opens.
|
|
53
|
+
*
|
|
54
|
+
* Zero-cost when unused: no tool registers → the runner never builds a tier →
|
|
55
|
+
* the terminal path is one `undefined` check.
|
|
56
|
+
*/
|
|
57
|
+
import type { ToolExecutionContext } from './tools.js';
|
|
58
|
+
/**
|
|
59
|
+
* How long a registered cleanup is allowed to live.
|
|
60
|
+
*
|
|
61
|
+
* - `'call'` — until `tool.execute` settles (resolve OR throw). Available
|
|
62
|
+
* at every door, including `mcpServe`, where a served call is
|
|
63
|
+
* the only unit there is.
|
|
64
|
+
* - `'run'` — until the run reaches a terminal that is NOT a pause.
|
|
65
|
+
* **A pause is not a terminal**: a check-in on a code
|
|
66
|
+
* interpreter is a person deciding, and tearing the sandbox
|
|
67
|
+
* down there destroys the exact state the resume needs.
|
|
68
|
+
* - `'session'` — until the composition root says the hosting session ended
|
|
69
|
+
* (`agent.closeToolSessions({ sessionId })`). Nobody but the
|
|
70
|
+
* composition root can know that: a request/reply deployment
|
|
71
|
+
* has no end-of-session signal, and inventing one would be a
|
|
72
|
+
* library guessing about somebody else's protocol.
|
|
73
|
+
* - `'shutdown'` — until `agent.shutdown()` (which `standingAgent`'s close
|
|
74
|
+
* calls). The backstop under all three.
|
|
75
|
+
*/
|
|
76
|
+
export type TeardownScope = 'call' | 'run' | 'session' | 'shutdown';
|
|
77
|
+
/** Why a cleanup ran. Reported on `agentfootprint.tools.session_closed`. */
|
|
78
|
+
export type TeardownReason = 'call-end' | 'run-end' | 'session-end' | 'shutdown' | 'idle' | 'evicted';
|
|
79
|
+
/** What a tool says about the cleanup it is registering. */
|
|
80
|
+
export interface TeardownOptions {
|
|
81
|
+
/** Default `'run'`. Refused by name when the door cannot honour it — see
|
|
82
|
+
* {@link ToolExecutionContext.teardownScopes}. */
|
|
83
|
+
readonly scope?: TeardownScope;
|
|
84
|
+
/**
|
|
85
|
+
* Dedup key within `(tool, scope)`. Omitted → the tool gets one registration
|
|
86
|
+
* per scope, which is right for a tool that holds exactly one thing.
|
|
87
|
+
*
|
|
88
|
+
* Derive it with {@link toolSessionKey} rather than by hand: a key that is
|
|
89
|
+
* narrower than the identity it isolates is the cross-binding bug, and a key
|
|
90
|
+
* that is wider is a silent latency change.
|
|
91
|
+
*/
|
|
92
|
+
readonly key?: string;
|
|
93
|
+
/** The adapter holding the resource — `CodeRunner.id`, say. Reported so a
|
|
94
|
+
* row names its backend instead of only its tool. */
|
|
95
|
+
readonly runnerId?: string;
|
|
96
|
+
/** One free-form fact about what was opened (the language, the browser
|
|
97
|
+
* profile). Reported as-is; never a place for user data. */
|
|
98
|
+
readonly label?: string;
|
|
99
|
+
}
|
|
100
|
+
/** The call a registration came from — what the firing matrix filters on. */
|
|
101
|
+
export interface ToolSessionOrigin {
|
|
102
|
+
readonly tool: string;
|
|
103
|
+
readonly toolCallId: string;
|
|
104
|
+
/** Absent when the door has no run (`mcpServe`). */
|
|
105
|
+
readonly runId?: string;
|
|
106
|
+
/** Absent unless the run is bound to a hosting conversation. */
|
|
107
|
+
readonly sessionId?: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* What `register()` did — a new session, or a call joining one already held.
|
|
111
|
+
*
|
|
112
|
+
* Returned rather than reported, because the two halves of a session's life
|
|
113
|
+
* happen in different places and only one of them has a stage to be stamped
|
|
114
|
+
* with. A start and a reuse happen INSIDE `tool.execute`, where the caller
|
|
115
|
+
* still holds the scope and can emit with the real `runtimeStageId`; a close
|
|
116
|
+
* happens after the run's last stage committed, where nothing does. Reporting
|
|
117
|
+
* both through one channel would have meant stamping a live, mid-stage event
|
|
118
|
+
* with the teardown pseudo-stage — a small lie, and exactly the kind that makes
|
|
119
|
+
* a trace disagree with itself.
|
|
120
|
+
*/
|
|
121
|
+
export type RegisterOutcome = 'started' | 'reused';
|
|
122
|
+
/**
|
|
123
|
+
* A teardown that happened. The runner turns it into
|
|
124
|
+
* `agentfootprint.tools.session_closed` / `_close_failed`.
|
|
125
|
+
*/
|
|
126
|
+
export interface ToolSessionReport {
|
|
127
|
+
readonly kind: 'closed' | 'close-failed';
|
|
128
|
+
readonly tool: string;
|
|
129
|
+
readonly scope: TeardownScope;
|
|
130
|
+
/** {@link hashSessionKey} of the isolation key — never the key. */
|
|
131
|
+
readonly keyHash: string;
|
|
132
|
+
readonly runnerId?: string;
|
|
133
|
+
readonly label?: string;
|
|
134
|
+
/** Which firing site ran it. */
|
|
135
|
+
readonly reason: TeardownReason;
|
|
136
|
+
/** Wall-clock from registration to close. */
|
|
137
|
+
readonly durationMs: number;
|
|
138
|
+
/** `close-failed` only. */
|
|
139
|
+
readonly error?: string;
|
|
140
|
+
readonly errorClass?: string;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Derive the isolation key a tool should hold a session under.
|
|
144
|
+
*
|
|
145
|
+
* ONE implementation, exported, because the derivation is the security
|
|
146
|
+
* boundary. Returns `undefined` when the facts the scope needs are absent —
|
|
147
|
+
* which is a refusal to guess, not a failure: the caller decides whether to
|
|
148
|
+
* narrow the scope loudly or refuse the call.
|
|
149
|
+
*
|
|
150
|
+
* ```
|
|
151
|
+
* session → t=<tenant|_>/p=<principal|_>/s=<sessionId> requires sessionId
|
|
152
|
+
* run → t=<tenant|_>/p=<principal|_>/r=<runId> requires runId
|
|
153
|
+
* call → c=<toolCallId> always available
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* **`sessionId` alone must never key a live session.** The hosting port says
|
|
157
|
+
* why in its own words: a `sessionId` "is not identity and must never be
|
|
158
|
+
* trusted as identity on its own: anyone who can reach the host can put any
|
|
159
|
+
* string here, including someone else's." A code interpreter keyed on
|
|
160
|
+
* `sessionId` alone hands a live sandbox — files, environment, half-run state —
|
|
161
|
+
* to anyone who guesses one. Tenant and principal are in the key whenever they
|
|
162
|
+
* exist; a deployment that has no principal is thereby STATING it is
|
|
163
|
+
* single-principal rather than quietly assuming it.
|
|
164
|
+
*
|
|
165
|
+
* `'shutdown'` is not a key scope: it is when everything goes, not a thing to
|
|
166
|
+
* hold one session under. Ask for it and you get `undefined`.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* const key = toolSessionKey(ctx, 'run');
|
|
170
|
+
* if (!key) throw new Error("run_code: scope 'run' needs a run …");
|
|
171
|
+
*/
|
|
172
|
+
export declare function toolSessionKey(ctx: Pick<ToolExecutionContext, 'toolCallId' | 'runId' | 'sessionId' | 'identity'>, scope: TeardownScope): string | undefined;
|
|
173
|
+
/**
|
|
174
|
+
* A short, stable digest of an isolation key.
|
|
175
|
+
*
|
|
176
|
+
* The key carries tenant, principal and the hosting `sessionId`. Publishing it
|
|
177
|
+
* on the event wire would put a user identifier into every exporter's payload —
|
|
178
|
+
* so the wire carries this instead, which is enough to JOIN two rows and not
|
|
179
|
+
* enough to name whose they are. `meta.sessionId` already carries the session
|
|
180
|
+
* legitimately (9.4.0); the payload does not repeat it.
|
|
181
|
+
*
|
|
182
|
+
* SHA-256, first 12 hex chars, wherever `node:crypto` resolves. In a browser
|
|
183
|
+
* bundle, where it does not, this falls back to the package's non-cryptographic
|
|
184
|
+
* FNV-1a digest — stated here rather than implied, because a fallback nobody
|
|
185
|
+
* documented is how "hashed" comes to mean less than a reader assumed.
|
|
186
|
+
*/
|
|
187
|
+
export declare function hashSessionKey(key: string): string;
|
|
188
|
+
/** Defaults, named so a test and a docstring cannot drift from the code. */
|
|
189
|
+
export declare const TOOL_TEARDOWN_TIMEOUT_MS = 5000;
|
|
190
|
+
/** A session untouched this long is swept on the tier's next interaction. */
|
|
191
|
+
export declare const TOOL_SESSION_IDLE_MS = 900000;
|
|
192
|
+
/** How many live registrations one tier holds before it evicts the coldest. */
|
|
193
|
+
export declare const TOOL_SESSION_MAX_LIVE = 64;
|
|
194
|
+
export interface ToolSessionTierOptions {
|
|
195
|
+
/** Per-cleanup ceiling. Default {@link TOOL_TEARDOWN_TIMEOUT_MS}. */
|
|
196
|
+
readonly timeoutMs?: number;
|
|
197
|
+
/** Idle ceiling for the lazy sweep. Default {@link TOOL_SESSION_IDLE_MS}. */
|
|
198
|
+
readonly idleMs?: number;
|
|
199
|
+
/** Live-registration ceiling before LRU eviction. Default
|
|
200
|
+
* {@link TOOL_SESSION_MAX_LIVE}. */
|
|
201
|
+
readonly maxLive?: number;
|
|
202
|
+
/** Where TEARDOWN reports go. The runner wires this to the typed event
|
|
203
|
+
* dispatcher. Starts and reuses are the caller's to announce — see
|
|
204
|
+
* {@link RegisterOutcome}. */
|
|
205
|
+
readonly report?: (report: ToolSessionReport) => void;
|
|
206
|
+
/** Clock seam — tests drive idle and duration without waiting. */
|
|
207
|
+
readonly now?: () => number;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The teardown tier for one runner.
|
|
211
|
+
*
|
|
212
|
+
* Holds nothing until a tool registers, and the runner builds one only when
|
|
213
|
+
* that happens — so an agent whose tools hold no sessions pays a single
|
|
214
|
+
* `undefined` check at each terminal and nothing else.
|
|
215
|
+
*/
|
|
216
|
+
export declare class ToolSessionTier {
|
|
217
|
+
private readonly registrations;
|
|
218
|
+
private readonly timeoutMs;
|
|
219
|
+
private readonly idleMs;
|
|
220
|
+
private readonly maxLive;
|
|
221
|
+
private readonly report;
|
|
222
|
+
private readonly now;
|
|
223
|
+
private seq;
|
|
224
|
+
constructor(options?: ToolSessionTierOptions);
|
|
225
|
+
/** Live registrations. Diagnostics and tests. */
|
|
226
|
+
liveCount(): number;
|
|
227
|
+
/**
|
|
228
|
+
* Resolve once every teardown this tier started IN THE BACKGROUND has
|
|
229
|
+
* settled.
|
|
230
|
+
*
|
|
231
|
+
* Two firings have no caller to await them: the idle sweep and the LRU
|
|
232
|
+
* eviction, both of which are triggered by a SYNCHRONOUS `register()` inside
|
|
233
|
+
* somebody else's `tool.execute`. They cannot be awaited there without making
|
|
234
|
+
* a cleanup's latency the tool's latency, so they run detached — and this is
|
|
235
|
+
* how a shutdown path, or a test, joins them.
|
|
236
|
+
*/
|
|
237
|
+
settled(): Promise<void>;
|
|
238
|
+
/** Fires nobody is awaiting — see {@link settled}. */
|
|
239
|
+
private readonly background;
|
|
240
|
+
/** Start a detached fire and keep it joinable. */
|
|
241
|
+
private detach;
|
|
242
|
+
/**
|
|
243
|
+
* Register a cleanup, or TOUCH the one already holding this key (law 2).
|
|
244
|
+
*
|
|
245
|
+
* Synchronous and total: it never throws, because it runs inside somebody
|
|
246
|
+
* else's `tool.execute`. Scope support is judged one layer up, where
|
|
247
|
+
* `teardownScopes` is known.
|
|
248
|
+
*
|
|
249
|
+
* @returns `'started'` for a new session, `'reused'` when this call joined
|
|
250
|
+
* one already held, with `calls` counting how many have now shared it. The
|
|
251
|
+
* CALLER announces it, because the caller is the one still inside a stage
|
|
252
|
+
* and able to stamp a real `runtimeStageId` — see {@link RegisterOutcome}.
|
|
253
|
+
*/
|
|
254
|
+
register(origin: ToolSessionOrigin, cleanup: () => void | Promise<void>, options?: TeardownOptions): {
|
|
255
|
+
readonly outcome: RegisterOutcome;
|
|
256
|
+
readonly keyHash: string;
|
|
257
|
+
readonly calls: number;
|
|
258
|
+
};
|
|
259
|
+
/** Fire every `'call'` registration this tool call opened. */
|
|
260
|
+
fireCall(toolCallId: string): Promise<void>;
|
|
261
|
+
/**
|
|
262
|
+
* Fire `'run'`-scoped registrations at a run terminal.
|
|
263
|
+
*
|
|
264
|
+
* Called from `Agent.run`/`resume` at a terminal that is NOT a pause — and
|
|
265
|
+
* deliberately not from a `finally`, which also runs on the two pause shapes.
|
|
266
|
+
*
|
|
267
|
+
* **`runId` is optional, and the Agent omits it.** A `'run'` scope means
|
|
268
|
+
* "release this when the TURN that opened it ends", and a turn is not a
|
|
269
|
+
* runId: a pause and its resume are one turn across two runs (`resume()`
|
|
270
|
+
* builds a fresh executor with a fresh id). Filtering on the id would leave
|
|
271
|
+
* every session a paused turn had opened alive forever — the failure would
|
|
272
|
+
* look like nothing at all, because the run answered fine.
|
|
273
|
+
*
|
|
274
|
+
* Firing all of them is exactly right under the runner's own
|
|
275
|
+
* ONE-IN-FLIGHT-RUN-PER-AGENT invariant: at a terminal there is no other turn
|
|
276
|
+
* whose sessions these could be. An ABANDONED pause is the interesting case
|
|
277
|
+
* and it lands the right way round too — the next completed turn releases
|
|
278
|
+
* what the abandoned one left holding.
|
|
279
|
+
*
|
|
280
|
+
* Pass an id where a caller really does mean one specific run.
|
|
281
|
+
*/
|
|
282
|
+
fireRun(runId?: string): Promise<void>;
|
|
283
|
+
/**
|
|
284
|
+
* Fire the `'session'` registrations for one hosting session — or, with no
|
|
285
|
+
* `sessionId`, every `'session'` registration there is.
|
|
286
|
+
*
|
|
287
|
+
* Answers how many it closed, so a composition root can log a number instead
|
|
288
|
+
* of hoping.
|
|
289
|
+
*/
|
|
290
|
+
fireSession(sessionId?: string, reason?: TeardownReason): Promise<number>;
|
|
291
|
+
/** Fire EVERYTHING, whatever scope it asked for. The backstop. */
|
|
292
|
+
fireShutdown(): Promise<number>;
|
|
293
|
+
/**
|
|
294
|
+
* Sweep sessions nobody has touched for `idleMs`.
|
|
295
|
+
*
|
|
296
|
+
* LAZY, on the next tier interaction — never a timer. A library that installs
|
|
297
|
+
* an interval keeps the host process alive, which is the same reason
|
|
298
|
+
* `shutdownOn` refuses to grab signals unless asked.
|
|
299
|
+
*/
|
|
300
|
+
sweepIdle(): void;
|
|
301
|
+
private evictOverflow;
|
|
302
|
+
/**
|
|
303
|
+
* The one firing implementation: select, claim (law 1), reverse (law 3),
|
|
304
|
+
* settle everything (law 5).
|
|
305
|
+
*
|
|
306
|
+
* Selection and claiming happen SYNCHRONOUSLY before the first `await`, so
|
|
307
|
+
* two overlapping fires — a shutdown racing an idle sweep — cannot both take
|
|
308
|
+
* the same registration.
|
|
309
|
+
*/
|
|
310
|
+
private fire;
|
|
311
|
+
/** One cleanup, bounded, reported either way. Never rethrows. */
|
|
312
|
+
private run;
|
|
313
|
+
}
|
|
314
|
+
/** A teardown that outran its budget. Named so an alert can route on it. */
|
|
315
|
+
export declare class ToolTeardownTimeoutError extends Error {
|
|
316
|
+
readonly timeoutMs: number;
|
|
317
|
+
constructor(what: string, timeoutMs: number);
|
|
318
|
+
}
|