@arnilo/prism 0.8.0 → 0.10.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/CHANGELOG.md +62 -1
- package/README.md +13 -12
- package/dist/agent-approval.d.ts +17 -2
- package/dist/agent-approval.js +15 -6
- package/dist/agent-event-source.d.ts +9 -1
- package/dist/agent-event-source.js +10 -3
- package/dist/agent-loops.js +7 -4
- package/dist/agent-run-lifecycle.d.ts +15 -1
- package/dist/agent-run-lifecycle.js +82 -11
- package/dist/agent-run-state.d.ts +47 -6
- package/dist/agent-run-state.js +154 -6
- package/dist/agent-session/event-subscriber.d.ts +2 -0
- package/dist/agent-session/event-subscriber.js +3 -0
- package/dist/agent-session/helpers.js +14 -0
- package/dist/agent-session/session/assemble.js +281 -32
- package/dist/agent-session/session/persist.d.ts +11 -0
- package/dist/agent-session/session/persist.js +48 -16
- package/dist/agent-session/session/provider-round.d.ts +14 -4
- package/dist/agent-session/session/provider-round.js +226 -19
- package/dist/agent-session/session/tool-round.d.ts +2 -2
- package/dist/agent-session/session/tool-round.js +78 -6
- package/dist/agent-session/session/types.d.ts +44 -3
- package/dist/agent-session/session.d.ts +100 -5
- package/dist/agent-session/session.js +224 -13
- package/dist/attention-compiler.d.ts +51 -2
- package/dist/attention-compiler.js +282 -21
- package/dist/cache-helpers.d.ts +4 -2
- package/dist/cache-helpers.js +8 -6
- package/dist/checkpoint-restore.d.ts +45 -0
- package/dist/checkpoint-restore.js +54 -0
- package/dist/context-budget.d.ts +13 -1
- package/dist/context-budget.js +57 -4
- package/dist/contracts-core/agent.d.ts +52 -1
- package/dist/contracts-core/attention.d.ts +95 -0
- package/dist/contracts-core/content.d.ts +10 -0
- package/dist/contracts-core/extensions.d.ts +3 -0
- package/dist/contracts-core/guardrail-packs.d.ts +46 -0
- package/dist/contracts-core/guardrail-packs.js +2 -0
- package/dist/contracts-core/loop.d.ts +36 -0
- package/dist/contracts-core/provider.d.ts +30 -0
- package/dist/contracts-core/run-limits.d.ts +29 -1
- package/dist/contracts-core/session.d.ts +23 -5
- package/dist/contracts-core/session.js +21 -2
- package/dist/contracts-core/usage.d.ts +40 -0
- package/dist/contracts-core/usage.js +8 -0
- package/dist/contracts-core.d.ts +2 -0
- package/dist/contracts-core.js +2 -0
- package/dist/contracts-protocol.d.ts +81 -5
- package/dist/contracts-run-state.d.ts +91 -2
- package/dist/contributions.d.ts +2 -1
- package/dist/contributions.js +1 -0
- package/dist/extensions.d.ts +15 -1
- package/dist/extensions.js +68 -0
- package/dist/guardrail-packs/coding-standard.d.ts +3 -0
- package/dist/guardrail-packs/coding-standard.js +63 -0
- package/dist/guardrail-packs/destructive-commands.d.ts +3 -0
- package/dist/guardrail-packs/destructive-commands.js +46 -0
- package/dist/guardrail-packs/errors.d.ts +7 -0
- package/dist/guardrail-packs/errors.js +9 -0
- package/dist/guardrail-packs/index.d.ts +4 -0
- package/dist/guardrail-packs/index.js +15 -0
- package/dist/guardrail-packs/secrets-hygiene.d.ts +3 -0
- package/dist/guardrail-packs/secrets-hygiene.js +23 -0
- package/dist/guardrail-packs/types.d.ts +26 -0
- package/dist/guardrail-packs/types.js +2 -0
- package/dist/guardrail-packs/validation-respect.d.ts +3 -0
- package/dist/guardrail-packs/validation-respect.js +69 -0
- package/dist/guardrails.d.ts +61 -1
- package/dist/guardrails.js +377 -0
- package/dist/index.d.ts +16 -11
- package/dist/index.js +10 -7
- package/dist/input.d.ts +8 -1
- package/dist/input.js +68 -6
- package/dist/middleware.d.ts +37 -2
- package/dist/middleware.js +41 -0
- package/dist/node/session-store-jsonl.js +18 -3
- package/dist/observability.js +6 -0
- package/dist/provider-events.d.ts +8 -2
- package/dist/provider-events.js +60 -2
- package/dist/providers/openai-compatible.js +6 -3
- package/dist/run-bundle.d.ts +6 -1
- package/dist/run-bundle.js +5 -1
- package/dist/run-limits.d.ts +11 -1
- package/dist/run-limits.js +59 -0
- package/dist/session-stores.d.ts +12 -1
- package/dist/session-stores.js +21 -4
- package/dist/testing/agent-event-source-conformance.js +41 -2
- package/dist/testing/prefix-stability-conformance.d.ts +59 -0
- package/dist/testing/prefix-stability-conformance.js +172 -0
- package/dist/testing/session-store-conformance.d.ts +3 -2
- package/dist/testing/session-store-conformance.js +48 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +21 -6
- package/dist/usage-estimation.d.ts +29 -0
- package/dist/usage-estimation.js +79 -0
- package/docs/agent-events.md +75 -4
- package/docs/agent-session-runtime.md +10 -6
- package/docs/attention-compiler.md +89 -8
- package/docs/caveman.md +1 -1
- package/docs/coding-agent-tools.md +1 -1
- package/docs/compaction-and-retry.md +1 -1
- package/docs/compaction-llm.md +2 -0
- package/docs/compaction-observational-memory.md +54 -7
- package/docs/durable-runs.md +46 -3
- package/docs/embeddings.md +9 -0
- package/docs/evaluations.md +5 -0
- package/docs/execution-timeline.md +79 -1
- package/docs/extensions.md +20 -3
- package/docs/guardrails.md +50 -4
- package/docs/hooks.md +282 -0
- package/docs/index.md +37 -15
- package/docs/input-and-prompt-assembly.md +4 -4
- package/docs/instruction-injection.md +1 -0
- package/docs/knowledge-sync.md +4 -0
- package/docs/live-testing.md +3 -1
- package/docs/memory-fabric.md +28 -0
- package/docs/middleware-hooks.md +90 -4
- package/docs/migrate-to-0.9.md +210 -0
- package/docs/migration.md +26 -0
- package/docs/multi-agent-patterns.md +25 -2
- package/docs/node-jsonl-session-store.md +7 -1
- package/docs/observability.md +7 -3
- package/docs/options-index.md +4 -1
- package/docs/policy-and-audit.md +26 -1
- package/docs/prefix-stability-conformance.md +143 -0
- package/docs/provider-caching.md +4 -4
- package/docs/provider-conformance.md +16 -0
- package/docs/provider-packages.md +20 -20
- package/docs/public-contracts.md +3 -2
- package/docs/rag.md +188 -3
- package/docs/release-and-install.md +45 -40
- package/docs/runs-and-usage.md +56 -10
- package/docs/scoped-agent-memory.md +270 -0
- package/docs/scoped-memory.md +138 -0
- package/docs/session-store-conformance.md +1 -2
- package/docs/session-stores.md +17 -17
- package/docs/supervisors.md +32 -12
- package/docs/tools.md +18 -1
- package/docs/wiki.md +4 -2
- package/docs/workflows.md +5 -0
- package/package.json +8 -2
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/** Provider-round phase of runInternal (plan 059). */
|
|
2
|
+
import { resolveInputCap } from "../../attention-compiler.js";
|
|
3
|
+
import { cacheUsageReport } from "../../cache-helpers.js";
|
|
4
|
+
import { estimateMessageTokens, estimateRequestExtrasTokens, getContextBudgetReport, resolveHostTokenEstimator, } from "../../context-budget.js";
|
|
2
5
|
import { assertGuardrailsAllowed, GuardrailError, runGuardrails } from "../../guardrails.js";
|
|
6
|
+
import { validateDeterministicTurnAnswer } from "../../middleware.js";
|
|
3
7
|
import { createProviderTurnMetadata, readProviderHttpStatus } from "../../observability.js";
|
|
4
8
|
import { providerError, providerToolCallDeltaContent } from "../../provider-events.js";
|
|
5
9
|
import { errorToErrorInfo, redactRunLedgerRecord, redactSecrets } from "../../redaction.js";
|
|
6
10
|
import { createDefaultRetryPolicy, waitForRetry } from "../../retry.js";
|
|
11
|
+
import { estimateTextTokensForFamily } from "../../usage-estimation.js";
|
|
7
12
|
import { bridgeAbort, errorFromInfo, isSteerSoftInterrupt, jsonBytes, mergeRetry, ProviderTurnFailure, providerContent, randomId, reconstructMissingToolCalls, SteerSoftInterrupt, throwIfAborted, } from "../helpers.js";
|
|
8
13
|
function pushCoalescedContent(content, block) {
|
|
9
14
|
const last = content.at(-1);
|
|
@@ -21,6 +26,51 @@ function pushCoalescedContent(content, block) {
|
|
|
21
26
|
}
|
|
22
27
|
content.push(block);
|
|
23
28
|
}
|
|
29
|
+
/** Resolve the per-request input cap for turn-budget metadata (plan 087 T1). A model without a
|
|
30
|
+
* derivable cap (or a bad attention setting on an unrelated run) omits the field instead of
|
|
31
|
+
* failing an emitting turn; the attention compiler, when enabled, is the cap authority. */
|
|
32
|
+
export function resolveTurnInputCap(session, model) {
|
|
33
|
+
const setting = session.agent.config.attentionCompiler;
|
|
34
|
+
const options = typeof setting === "object" && setting !== null ? setting : undefined;
|
|
35
|
+
try {
|
|
36
|
+
return resolveInputCap(options ? { maxInputTokens: options.maxInputTokens, reserveTokens: options.reserveTokens } : {}, model);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Effective budget snapshot at turn end (plan 087 T1): O(1) from the run limit tracker. */
|
|
43
|
+
function turnBudgets(session, model, usage) {
|
|
44
|
+
const tracker = session.activeLimits;
|
|
45
|
+
if (!tracker)
|
|
46
|
+
return undefined;
|
|
47
|
+
const snapshot = tracker.snapshot();
|
|
48
|
+
const inputCap = resolveTurnInputCap(session, model);
|
|
49
|
+
const runInputBudget = tracker.limits.maxInputTokens;
|
|
50
|
+
return {
|
|
51
|
+
...(usage?.inputTokens === undefined
|
|
52
|
+
? {}
|
|
53
|
+
: { inputTokens: usage.inputTokens, inputTokensSource: usage.estimated === true ? "estimated" : "reported" }),
|
|
54
|
+
...(inputCap === undefined ? {} : { inputCap }),
|
|
55
|
+
...(runInputBudget === null ? {} : { runInputBudget }),
|
|
56
|
+
runInputUsed: snapshot.inputTokens,
|
|
57
|
+
turns: snapshot.turns,
|
|
58
|
+
maxTurns: tracker.limits.maxTurns,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function cacheMetadata(usage) {
|
|
62
|
+
const cache = cacheUsageReport(usage);
|
|
63
|
+
return cache === undefined ? {} : { cache };
|
|
64
|
+
}
|
|
65
|
+
/** Native reason wins, except a generic `end_turn` on a turn that produced tool calls: protocols
|
|
66
|
+
* with one generic completion value (Google `STOP`) are tool-call turns by content (plan 087 T1). */
|
|
67
|
+
function normalizeTurnStopReason(native, calls) {
|
|
68
|
+
if (native === undefined)
|
|
69
|
+
return calls.length > 0 ? "tool_calls" : "end_turn";
|
|
70
|
+
if (native === "end_turn" && calls.length > 0)
|
|
71
|
+
return "tool_calls";
|
|
72
|
+
return native;
|
|
73
|
+
}
|
|
24
74
|
/**
|
|
25
75
|
* Plan 062: price usage through the host's {@link CostCatalog} when the provider
|
|
26
76
|
* did not report a cost itself. Stale/unknown quotes, catalog failures, or
|
|
@@ -46,17 +96,22 @@ async function withCatalogCost(catalog, model, usage, signal) {
|
|
|
46
96
|
return usage; // catalog failure degrades to usage-only
|
|
47
97
|
}
|
|
48
98
|
}
|
|
49
|
-
export async function recordProviderUsage(ctx, turnUsage, turn, attempt) {
|
|
99
|
+
export async function recordProviderUsage(ctx, turnUsage, turn, attempt, request) {
|
|
50
100
|
const { session, limits, runUsage, runId } = ctx;
|
|
51
|
-
const usage = turnUsage
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
101
|
+
const usage = turnUsage ?? estimateTurnUsage(session, ctx.model, request);
|
|
102
|
+
// An estimate is never priced: a catalog quote on estimated tokens would invent billing.
|
|
103
|
+
const effective = usage && usage.estimated !== true
|
|
104
|
+
? await withCatalogCost(session.agent.config.costCatalog, ctx.model, usage, ctx.controller.signal)
|
|
105
|
+
: usage;
|
|
106
|
+
limits.recordUsage(effective);
|
|
107
|
+
if (!effective)
|
|
108
|
+
return undefined;
|
|
109
|
+
if (effective.inputTokens !== undefined) {
|
|
110
|
+
session.activeInputMeter = { tokens: effective.inputTokens, source: effective.estimated === true ? "estimated" : "reported" };
|
|
111
|
+
}
|
|
112
|
+
runUsage.add(effective);
|
|
58
113
|
if (!session.activeLedger)
|
|
59
|
-
return;
|
|
114
|
+
return effective;
|
|
60
115
|
const usageRecord = {
|
|
61
116
|
id: randomId("usage"),
|
|
62
117
|
sessionId: session.id,
|
|
@@ -64,11 +119,134 @@ export async function recordProviderUsage(ctx, turnUsage, turn, attempt) {
|
|
|
64
119
|
scope: "provider_turn",
|
|
65
120
|
turn,
|
|
66
121
|
attempt,
|
|
67
|
-
usage,
|
|
122
|
+
usage: effective,
|
|
68
123
|
recordedAt: new Date().toISOString(),
|
|
69
124
|
...session.activeOwnership,
|
|
70
125
|
};
|
|
71
126
|
await session.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, session.activeRedactor));
|
|
127
|
+
return effective;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Plan 091 T2 missing-usage fallback, plan 103 T6 exact-measurement reuse: when the provider
|
|
131
|
+
* reported nothing and the agent did not turn estimation off, label one estimate of the turn's
|
|
132
|
+
* own request, preferring the most exact measurement that already exists —
|
|
133
|
+
* 1. the budget pass's own `ContextBudgetReport.keptTokens` (whole request, post-eviction,
|
|
134
|
+
* the same figure that decided evictions; excludes content added after the budget pass),
|
|
135
|
+
* 2. the host's `contextBudget.tokenEstimator`, projecting messages plus tool/context portions
|
|
136
|
+
* through the assembler's own `measureAll` text shapes,
|
|
137
|
+
* 3. the plan-091 family heuristic for messages plus those same assembler shapes for extras
|
|
138
|
+
* (no `JSON.stringify` of the schemas, so no drift from what the assembler measured).
|
|
139
|
+
* A host tokenizer's count is still an estimate (`confidence: "high"`, never `"reported"`);
|
|
140
|
+
* a report measured by the built-in ÷4 basis is honestly `"low"`. Returns `undefined` when
|
|
141
|
+
* estimation is not the fallback (`"off"` / `"strict"`) or the request is unavailable —
|
|
142
|
+
* absent stays absent.
|
|
143
|
+
*/
|
|
144
|
+
function estimateTurnUsage(session, model, request) {
|
|
145
|
+
// Omitted is the documented default (`"fallback"`), not a reason to skip estimation.
|
|
146
|
+
const mode = session.agent.config.usageEstimation ?? "fallback";
|
|
147
|
+
if (!request || mode !== "fallback")
|
|
148
|
+
return undefined;
|
|
149
|
+
const hostEstimator = resolveHostTokenEstimator(session.agent.config.contextBudget);
|
|
150
|
+
const report = getContextBudgetReport(request);
|
|
151
|
+
if (report) {
|
|
152
|
+
return { inputTokens: report.keptTokens, estimated: true, confidence: hostEstimator === undefined ? "low" : "high" };
|
|
153
|
+
}
|
|
154
|
+
if (hostEstimator) {
|
|
155
|
+
let tokens = estimateRequestExtrasTokens(request.tools, request.context, hostEstimator);
|
|
156
|
+
for (const message of request.messages)
|
|
157
|
+
tokens += estimateMessageTokens(message, hostEstimator);
|
|
158
|
+
return { inputTokens: tokens, estimated: true, confidence: "high" };
|
|
159
|
+
}
|
|
160
|
+
const estimate = estimateMessageTokens(request.messages, model.model);
|
|
161
|
+
const extras = estimateRequestExtrasTokens(request.tools, request.context, (text) => estimateTextTokensForFamily(text, model.model));
|
|
162
|
+
return { inputTokens: estimate.tokens + extras, estimated: true, confidence: estimate.confidence };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Plan 103 T5: the refusal `usageEstimation: "strict"` gives a completed turn that reported no
|
|
166
|
+
* usage. It rides the existing observable-failure path (one attempt, terminal `error` event) and
|
|
167
|
+
* stamps no `failureClass` — a harness refusal is not a provider failure, so `name`/`code` are
|
|
168
|
+
* what a host matches on. The info carries the turn number and mode only, never request content.
|
|
169
|
+
*/
|
|
170
|
+
function usageMissingFailure(turn) {
|
|
171
|
+
return new ProviderTurnFailure({
|
|
172
|
+
name: "UsageMissingError",
|
|
173
|
+
code: "usage_missing",
|
|
174
|
+
message: `provider reported no usage on turn ${turn} and usageEstimation is "strict"`,
|
|
175
|
+
}, true);
|
|
176
|
+
}
|
|
177
|
+
/** Latest user-role text in the assembled request; steered messages included. */
|
|
178
|
+
function lastUserText(messages) {
|
|
179
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
180
|
+
const message = messages[i];
|
|
181
|
+
if (message?.role !== "user")
|
|
182
|
+
continue;
|
|
183
|
+
return message.content.map((block) => (block.type === "text" ? block.text : "")).join("");
|
|
184
|
+
}
|
|
185
|
+
return "";
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Plan 096: host middleware may answer the turn deterministically at the `beforeProviderTurn` seam —
|
|
189
|
+
* no provider request, no usage, mandatory provenance. `undefined` sends the turn to the provider
|
|
190
|
+
* unchanged; a malformed answer fails the run closed instead of falling through to the provider.
|
|
191
|
+
*/
|
|
192
|
+
export async function resolveDeterministicTurn(session, request, runId, turn, signal, toolResults = []) {
|
|
193
|
+
const middleware = session.agent.config.middleware;
|
|
194
|
+
if (!middleware)
|
|
195
|
+
return undefined;
|
|
196
|
+
const payload = await middleware.run("beforeProviderTurn", {
|
|
197
|
+
sessionId: session.id,
|
|
198
|
+
runId,
|
|
199
|
+
turn,
|
|
200
|
+
userText: lastUserText(request.messages),
|
|
201
|
+
});
|
|
202
|
+
const answer = payload?.answer;
|
|
203
|
+
if (answer === undefined)
|
|
204
|
+
return undefined;
|
|
205
|
+
const validated = validateDeterministicTurnAnswer(answer);
|
|
206
|
+
throwIfAborted(signal);
|
|
207
|
+
const messageId = randomId("msg");
|
|
208
|
+
// Same response-byte axis as provider output: a host answer must not bypass a run ceiling.
|
|
209
|
+
session.activeLimits?.charge("maxResponseBytes", jsonBytes(validated.content));
|
|
210
|
+
if (session.activeGuardrails?.output?.length) {
|
|
211
|
+
assertGuardrailsAllowed(await runGuardrails({
|
|
212
|
+
stage: "output",
|
|
213
|
+
guardrails: session.activeGuardrails,
|
|
214
|
+
value: { content: validated.content, calls: [], messageId, started: true, usage: undefined },
|
|
215
|
+
context: {
|
|
216
|
+
sessionId: session.id,
|
|
217
|
+
runId,
|
|
218
|
+
metadata: session.activeMetadata ?? {},
|
|
219
|
+
signal,
|
|
220
|
+
toolResults,
|
|
221
|
+
},
|
|
222
|
+
redactor: session.activeRedactor,
|
|
223
|
+
emit: (event) => session.emit(event),
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
session.emit({
|
|
227
|
+
type: "deterministic_turn",
|
|
228
|
+
sessionId: session.id,
|
|
229
|
+
runId,
|
|
230
|
+
turn,
|
|
231
|
+
middleware: validated.provenance.middleware,
|
|
232
|
+
});
|
|
233
|
+
session.emit({
|
|
234
|
+
type: "message_started",
|
|
235
|
+
sessionId: session.id,
|
|
236
|
+
runId,
|
|
237
|
+
message: { id: messageId, role: "assistant", content: [] },
|
|
238
|
+
});
|
|
239
|
+
for (const block of validated.content)
|
|
240
|
+
session.emit({ type: "message_delta", sessionId: session.id, runId, content: block });
|
|
241
|
+
// Provenance rides the message into the store (plan 096 Task 2): the transcript alone proves no model ran.
|
|
242
|
+
return {
|
|
243
|
+
content: validated.content,
|
|
244
|
+
calls: [],
|
|
245
|
+
messageId,
|
|
246
|
+
started: true,
|
|
247
|
+
usage: undefined,
|
|
248
|
+
metadata: { deterministic: validated.provenance },
|
|
249
|
+
};
|
|
72
250
|
}
|
|
73
251
|
export async function generateWithRetry(session, request, runId, options, signal, requestSecrets = [], turn = 1, recordUsage, toolResults = []) {
|
|
74
252
|
const retry = mergeRetry(session.agent.config.retry, options.retry);
|
|
@@ -119,7 +297,9 @@ export async function generateProviderTurn(session, request, runId, signal, secr
|
|
|
119
297
|
let messageId;
|
|
120
298
|
let started = false;
|
|
121
299
|
let usage;
|
|
300
|
+
let nativeStopReason;
|
|
122
301
|
let usageRecorded = false;
|
|
302
|
+
let effectiveUsage;
|
|
123
303
|
const bufferedOutput = [];
|
|
124
304
|
const bufferOutput = Boolean(session.activeGuardrails?.output?.length || session.activeLimitOutputBuffer);
|
|
125
305
|
const emitOutput = (event) => {
|
|
@@ -130,9 +310,11 @@ export async function generateProviderTurn(session, request, runId, signal, secr
|
|
|
130
310
|
};
|
|
131
311
|
const recordTurnUsage = async () => {
|
|
132
312
|
if (usageRecorded)
|
|
133
|
-
return;
|
|
313
|
+
return effectiveUsage;
|
|
134
314
|
usageRecorded = true;
|
|
135
|
-
|
|
315
|
+
// The seam may return a labeled estimate (plan 091 T2); without a callback the reported value stands.
|
|
316
|
+
effectiveUsage = (await recordUsage?.(usage, turn, attempt)) ?? usage;
|
|
317
|
+
return effectiveUsage;
|
|
136
318
|
};
|
|
137
319
|
const turnAbort = new AbortController();
|
|
138
320
|
const cleanupTurn = bridgeAbort(signal, turnAbort);
|
|
@@ -153,6 +335,7 @@ export async function generateProviderTurn(session, request, runId, signal, secr
|
|
|
153
335
|
usage = event.usage;
|
|
154
336
|
if (event.type === "done") {
|
|
155
337
|
usage = event.usage ?? usage;
|
|
338
|
+
nativeStopReason = event.stopReason;
|
|
156
339
|
break;
|
|
157
340
|
}
|
|
158
341
|
if (event.type === "message_start") {
|
|
@@ -183,6 +366,14 @@ export async function generateProviderTurn(session, request, runId, signal, secr
|
|
|
183
366
|
calls.push(call);
|
|
184
367
|
emitOutput({ type: "message_delta", sessionId: session.id, runId, content: call });
|
|
185
368
|
}
|
|
369
|
+
// Plan 103 T5: strict refuses a completed turn that reported no usage *before* the usage seam
|
|
370
|
+
// runs, so no estimate is projected, the cost catalog is not consulted, and the fail-closed
|
|
371
|
+
// `recordUsage(undefined)` maxCost breach cannot preempt the refusal. Marking the seam
|
|
372
|
+
// consulted keeps the catch below from re-entering it with the same missing usage.
|
|
373
|
+
if (usage === undefined && session.agent.config.usageEstimation === "strict") {
|
|
374
|
+
usageRecorded = true;
|
|
375
|
+
throw usageMissingFailure(turn);
|
|
376
|
+
}
|
|
186
377
|
await recordTurnUsage();
|
|
187
378
|
if (session.activeGuardrails?.output?.length) {
|
|
188
379
|
assertGuardrailsAllowed(await runGuardrails({
|
|
@@ -209,10 +400,15 @@ export async function generateProviderTurn(session, request, runId, signal, secr
|
|
|
209
400
|
sessionId: session.id,
|
|
210
401
|
runId,
|
|
211
402
|
turn,
|
|
212
|
-
metadata: buildMetadata({
|
|
213
|
-
|
|
403
|
+
metadata: buildMetadata({
|
|
404
|
+
latencyMs,
|
|
405
|
+
stopReason: normalizeTurnStopReason(nativeStopReason, calls),
|
|
406
|
+
budgets: turnBudgets(session, request.model, effectiveUsage),
|
|
407
|
+
...cacheMetadata(effectiveUsage),
|
|
408
|
+
}),
|
|
409
|
+
usage: effectiveUsage,
|
|
214
410
|
});
|
|
215
|
-
return { content, calls, messageId, started, usage };
|
|
411
|
+
return { content, calls, messageId, started, usage: effectiveUsage };
|
|
216
412
|
}
|
|
217
413
|
catch (error) {
|
|
218
414
|
if (isSteerSoftInterrupt(error) || isSteerSoftInterrupt(turnAbort.signal.reason)) {
|
|
@@ -223,8 +419,13 @@ export async function generateProviderTurn(session, request, runId, signal, secr
|
|
|
223
419
|
sessionId: session.id,
|
|
224
420
|
runId,
|
|
225
421
|
turn,
|
|
226
|
-
metadata: buildMetadata({
|
|
227
|
-
|
|
422
|
+
metadata: buildMetadata({
|
|
423
|
+
latencyMs,
|
|
424
|
+
stopReason: "abort",
|
|
425
|
+
budgets: turnBudgets(session, request.model, effectiveUsage),
|
|
426
|
+
...cacheMetadata(effectiveUsage),
|
|
427
|
+
}),
|
|
428
|
+
usage: effectiveUsage,
|
|
228
429
|
});
|
|
229
430
|
throw new SteerSoftInterrupt();
|
|
230
431
|
}
|
|
@@ -236,8 +437,14 @@ export async function generateProviderTurn(session, request, runId, signal, secr
|
|
|
236
437
|
sessionId: session.id,
|
|
237
438
|
runId,
|
|
238
439
|
turn,
|
|
239
|
-
metadata: buildMetadata({
|
|
240
|
-
|
|
440
|
+
metadata: buildMetadata({
|
|
441
|
+
latencyMs,
|
|
442
|
+
httpStatus: readProviderHttpStatus(info),
|
|
443
|
+
stopReason: signal.aborted || turnAbort.signal.aborted ? "abort" : "provider_error",
|
|
444
|
+
budgets: turnBudgets(session, request.model, effectiveUsage),
|
|
445
|
+
...cacheMetadata(effectiveUsage),
|
|
446
|
+
}),
|
|
447
|
+
usage: effectiveUsage,
|
|
241
448
|
error: info,
|
|
242
449
|
});
|
|
243
450
|
if (error instanceof GuardrailError || error instanceof ProviderTurnFailure)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** Tool-round phase of runInternal (plan 059). */
|
|
2
|
-
import type { AgentRunRef, LoopContext, NestedRunRef, PendingDecision, ResumeNestedRun, StickyDecision, ToolCallContent, ToolRegistry, ToolResult, Usage } from "../../contracts.js";
|
|
2
|
+
import type { AgentRunRef, GuardrailRecord, LoopContext, NestedRunRef, PendingDecision, ResumeNestedRun, StickyDecision, ToolCallContent, ToolRegistry, ToolResult, Usage } from "../../contracts.js";
|
|
3
3
|
import { AgentDelegationSuspendedError } from "../../contracts.js";
|
|
4
4
|
import type { RoundContext, SessionHost } from "./types.js";
|
|
5
5
|
export declare function matchNestedSticky(session: SessionHost, decision: PendingDecision): StickyDecision | undefined;
|
|
6
6
|
export declare function matchStickyDecision(session: SessionHost, call: ToolCallContent, registry: ToolRegistry): StickyDecision | undefined;
|
|
7
|
-
export declare function buildPendingDecision(session: SessionHost, call: ToolCallContent, approvalId: string, registry: ToolRegistry, runId: string, metadata: Readonly<Record<string, unknown>>, signal: AbortSignal): PendingDecision;
|
|
7
|
+
export declare function buildPendingDecision(session: SessionHost, call: ToolCallContent, approvalId: string, registry: ToolRegistry, runId: string, metadata: Readonly<Record<string, unknown>>, signal: AbortSignal, ask?: GuardrailRecord): PendingDecision;
|
|
8
8
|
export declare function applyNestedRun(session: SessionHost, input: {
|
|
9
9
|
ref: AgentRunRef;
|
|
10
10
|
toolCall: ToolCallContent;
|
|
@@ -3,6 +3,7 @@ import { AgentRunSuspended, decisionIdentityRef, decisionScopesEqual, nestedAppr
|
|
|
3
3
|
import { toolElicitationRequest } from "../../agent-tool-dispatch.js";
|
|
4
4
|
import { AgentDecisionError, AgentDelegationSuspendedError, AgentRunStateError, DEFAULT_MAX_PENDING_DECISIONS, HARD_MAX_PENDING_DECISIONS, MAX_ATTRIBUTION_DEPTH, } from "../../contracts.js";
|
|
5
5
|
import { toToolResultMessage } from "../../input.js";
|
|
6
|
+
import { runGuardrails } from "../../guardrails.js";
|
|
6
7
|
import { canonicalToolEffectJson, toolEffectArgumentsHash } from "../../tool-effects.js";
|
|
7
8
|
import { dispatchToolCall, resolveToolEffectDeclaration } from "../../tools.js";
|
|
8
9
|
import { randomId } from "../helpers.js";
|
|
@@ -55,7 +56,7 @@ export function matchStickyDecision(session, call, registry) {
|
|
|
55
56
|
return true;
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
|
-
export function buildPendingDecision(session, call, approvalId, registry, runId, metadata, signal) {
|
|
59
|
+
export function buildPendingDecision(session, call, approvalId, registry, runId, metadata, signal, ask) {
|
|
59
60
|
const tool = registry.get(call.name);
|
|
60
61
|
const declaration = tool?.effect
|
|
61
62
|
? resolveToolEffectDeclaration(tool, call.arguments, {
|
|
@@ -74,6 +75,11 @@ export function buildPendingDecision(session, call, approvalId, registry, runId,
|
|
|
74
75
|
signal,
|
|
75
76
|
metadata,
|
|
76
77
|
});
|
|
78
|
+
// Plan 104 T3: the pack `ask` rule that gated this call is named in the bounded reason and carried
|
|
79
|
+
// machine-readably, so a host never parses the name to know which rule raised the approval.
|
|
80
|
+
const pack = ask?.metadata?.pack;
|
|
81
|
+
const rule = ask?.metadata?.rule;
|
|
82
|
+
const guardrailRule = typeof pack === "string" && typeof rule === "string" ? { pack, rule } : undefined;
|
|
77
83
|
return {
|
|
78
84
|
approvalId,
|
|
79
85
|
kind: elicitation ? "elicitation" : "tool_approval",
|
|
@@ -84,10 +90,22 @@ export function buildPendingDecision(session, call, approvalId, registry, runId,
|
|
|
84
90
|
...(declaration && declaration.kind !== "none" ? { effectKind: declaration.kind } : {}),
|
|
85
91
|
...(identityRef ? { identity: identityRef } : {}),
|
|
86
92
|
},
|
|
87
|
-
reason: elicitation?.reason ?? "Tool side effect requires approval",
|
|
93
|
+
reason: elicitation?.reason ?? (ask ? askDecisionReason(ask) : "Tool side effect requires approval"),
|
|
88
94
|
...(elicitation ? { elicitationSchema: elicitation.schema } : {}),
|
|
95
|
+
...(ask && guardrailRule ? { guardrail: ask.guardrail, guardrailRule } : {}),
|
|
89
96
|
};
|
|
90
97
|
}
|
|
98
|
+
const MAX_ASK_DECISION_REASON_BYTES = 200;
|
|
99
|
+
/** `pack:<pack>/<rule>` plus the pack's own reason, bounded like every other decision field. */
|
|
100
|
+
function askDecisionReason(ask) {
|
|
101
|
+
const pack = ask.metadata?.pack;
|
|
102
|
+
const rule = ask.metadata?.rule;
|
|
103
|
+
const defaultReason = typeof pack === "string" && typeof rule === "string" ? `guardrail pack rule ${pack}/${rule}` : undefined;
|
|
104
|
+
const line = `Approval required by guardrail rule ${ask.guardrail}`;
|
|
105
|
+
const text = ask.reason && ask.reason !== defaultReason ? `${line}: ${ask.reason}` : line;
|
|
106
|
+
const bytes = new TextEncoder().encode(text);
|
|
107
|
+
return bytes.length <= MAX_ASK_DECISION_REASON_BYTES ? text : new TextDecoder().decode(bytes.subarray(0, MAX_ASK_DECISION_REASON_BYTES));
|
|
108
|
+
}
|
|
91
109
|
export async function applyNestedRun(session, input) {
|
|
92
110
|
let current = input.pending;
|
|
93
111
|
for (let depth = 0;; depth += 1) {
|
|
@@ -142,6 +160,7 @@ export async function suspendGatedRound(ctx) {
|
|
|
142
160
|
reason: single ? single.reason : `${decisions.length} tool side effects require approval`,
|
|
143
161
|
...(single?.toolCallId ? { toolCallId: single.toolCallId } : {}),
|
|
144
162
|
...(single?.scope.toolName ? { toolName: single.scope.toolName } : {}),
|
|
163
|
+
...(single?.guardrail ? { guardrail: single.guardrail } : {}),
|
|
145
164
|
pendingDecisions: decisions,
|
|
146
165
|
};
|
|
147
166
|
throw new AgentRunSuspended(await suspendDurable(ctx.session, {
|
|
@@ -217,20 +236,27 @@ export async function handleNestedSignal(ctx, error) {
|
|
|
217
236
|
await suspendNested(ctx, { entry: applied.entry, toolCall: error.toolCall, pending: applied.pending });
|
|
218
237
|
}
|
|
219
238
|
export function bindChargeToolRound(ctx) {
|
|
220
|
-
return (calls) => {
|
|
239
|
+
return async (calls) => {
|
|
221
240
|
if (calls.length > 0)
|
|
222
241
|
ctx.limits.charge("maxToolRounds");
|
|
223
242
|
const durable = ctx.session.activeDurable;
|
|
224
|
-
|
|
243
|
+
// A run that cannot suspend never gates here: `activeGuardrails` already carries the pack `ask`
|
|
244
|
+
// rules as plain blocks (assemble.ts), so the ordinary stage path refuses the call.
|
|
245
|
+
if (!durable || calls.length === 0)
|
|
246
|
+
return;
|
|
247
|
+
if (!ctx.session.packAskGate && !durable.options.interruptBeforeTool)
|
|
225
248
|
return;
|
|
226
249
|
for (const call of calls) {
|
|
227
250
|
if (matchStickyDecision(ctx.session, call, ctx.registry))
|
|
228
251
|
continue;
|
|
252
|
+
const ask = await matchAskGate(ctx, call);
|
|
253
|
+
if (!ask && !durable.options.interruptBeforeTool)
|
|
254
|
+
continue;
|
|
229
255
|
const approvalId = randomId("approval");
|
|
230
256
|
ctx.session.activeGatedRound ??= new Map();
|
|
231
257
|
ctx.session.activeGatedRound.set(call.id, {
|
|
232
258
|
entry: { call, status: "ready", approvalId },
|
|
233
|
-
decision: buildPendingDecision(ctx.session, call, approvalId, ctx.registry, ctx.runId, ctx.metadata, ctx.controller.signal),
|
|
259
|
+
decision: buildPendingDecision(ctx.session, call, approvalId, ctx.registry, ctx.runId, ctx.metadata, ctx.controller.signal, ask),
|
|
234
260
|
});
|
|
235
261
|
}
|
|
236
262
|
if (ctx.session.activeGatedRound && ctx.session.activeGatedRound.size > DEFAULT_MAX_PENDING_DECISIONS) {
|
|
@@ -238,6 +264,51 @@ export function bindChargeToolRound(ctx) {
|
|
|
238
264
|
}
|
|
239
265
|
};
|
|
240
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Plan 104 T3: evaluate the pack `ask` rules for one call at charge time. The rules run through the
|
|
269
|
+
* same compiler and stage runner as every other pack rule, so matching, bounds, and redaction are
|
|
270
|
+
* shared; a match emits its `guardrail_decision` (`interrupt`: awaiting a decision) and gates the
|
|
271
|
+
* call before it can dispatch.
|
|
272
|
+
*/
|
|
273
|
+
async function matchAskGate(ctx, call) {
|
|
274
|
+
const gate = ctx.session.packAskGate;
|
|
275
|
+
if (!gate)
|
|
276
|
+
return undefined;
|
|
277
|
+
const result = await runGuardrails({
|
|
278
|
+
stage: "tool_input",
|
|
279
|
+
guardrails: gate,
|
|
280
|
+
value: call,
|
|
281
|
+
context: {
|
|
282
|
+
sessionId: ctx.session.id,
|
|
283
|
+
runId: ctx.runId,
|
|
284
|
+
toolCallId: call.id,
|
|
285
|
+
toolName: call.name,
|
|
286
|
+
metadata: ctx.metadata,
|
|
287
|
+
signal: ctx.controller.signal,
|
|
288
|
+
},
|
|
289
|
+
redactor: ctx.session.activeRedactor,
|
|
290
|
+
emit: (event) => ctx.session.emit(event),
|
|
291
|
+
});
|
|
292
|
+
return result.terminal;
|
|
293
|
+
}
|
|
294
|
+
/** Last-N dispatched tool calls kept for `budget_exhausted` attribution (plan 087 T2); the hash
|
|
295
|
+
* is the same canonical arguments hash the effect store uses, so raw args never enter events. */
|
|
296
|
+
const RECENT_TOOL_CALL_LIMIT = 10;
|
|
297
|
+
function recordRecentToolCall(session, call) {
|
|
298
|
+
const recent = (session.activeRecentToolCalls ??= []);
|
|
299
|
+
recent.push({ id: call.id, name: call.name, argHash: `sha256:${toolEffectArgumentsHash(call.arguments)}` });
|
|
300
|
+
if (recent.length > RECENT_TOOL_CALL_LIMIT)
|
|
301
|
+
recent.shift();
|
|
302
|
+
}
|
|
303
|
+
function dispatchFilter(ctx) {
|
|
304
|
+
const hiddenOk = ctx.options.allowHiddenToolCalls ?? ctx.session.agent.config.allowHiddenToolCalls;
|
|
305
|
+
if (hiddenOk || ctx.turnAllow === undefined) {
|
|
306
|
+
return ctx.tools.length > 0 ? { filter: { allow: ctx.tools.map((tool) => tool.name) } } : {};
|
|
307
|
+
}
|
|
308
|
+
if (ctx.turnAllow.length > 0)
|
|
309
|
+
return { filter: { allow: ctx.turnAllow } };
|
|
310
|
+
return ctx.tools.length > 0 ? { filter: { deny: ctx.tools.map((tool) => tool.name) } } : {};
|
|
311
|
+
}
|
|
241
312
|
export function bindDispatchToolCall(ctx) {
|
|
242
313
|
return async (call) => {
|
|
243
314
|
const sticky = matchStickyDecision(ctx.session, call, ctx.registry);
|
|
@@ -252,6 +323,7 @@ export function bindDispatchToolCall(ctx) {
|
|
|
252
323
|
return { toolCallId: call.id, name: call.name, metadata: { approvalPending: true } };
|
|
253
324
|
}
|
|
254
325
|
ctx.toolCalls += 1;
|
|
326
|
+
recordRecentToolCall(ctx.session, call);
|
|
255
327
|
try {
|
|
256
328
|
const result = await dispatchToolCall({
|
|
257
329
|
call,
|
|
@@ -279,7 +351,7 @@ export function bindDispatchToolCall(ctx) {
|
|
|
279
351
|
ownership: ctx.session.activeOwnership,
|
|
280
352
|
identity: ctx.session.activeIdentity,
|
|
281
353
|
guardrails: ctx.session.activeGuardrails,
|
|
282
|
-
...(ctx
|
|
354
|
+
...dispatchFilter(ctx),
|
|
283
355
|
limitTracker: ctx.limits,
|
|
284
356
|
beforeExecute: async (mediatedCall) => {
|
|
285
357
|
const durable = ctx.session.activeDurable;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** Shared host/round types for runInternal phase split (plan 059). Internal only. */
|
|
2
2
|
import type { ActiveDurableRun } from "../../agent-approval.js";
|
|
3
|
-
import type { PendingToolCall } from "../../agent-run-state.js";
|
|
4
|
-
import type { AttentionStickyFrontier, PersistedAttentionStickyFrontier } from "../../attention-compiler.js";
|
|
5
|
-
import type { Agent, AgentEvent, AgentFinishReason, AgentLoopStrategy, AgentRunResult, AIProvider, ErrorInfo, Guardrails, LoopContext, Message, ModelConfig, OwnershipScope, PendingDecision, PromptVersionRef, ProviderRequest, RunLedger, RunOptions, SessionEntry, SessionStore, Skill, ToolDefinition, ToolEffectStore, ToolRegistry, ToolResult, Usage } from "../../contracts.js";
|
|
3
|
+
import type { PendingToolCall, PersistedGuardrailPacks } from "../../agent-run-state.js";
|
|
4
|
+
import type { AttentionFoldLedger, AttentionStickyFrontier, PersistedAttentionFoldLedger, PersistedAttentionStickyFrontier } from "../../attention-compiler.js";
|
|
5
|
+
import type { Agent, AgentEvent, AgentFinishReason, AgentLoopStrategy, AgentRunResult, AIProvider, ErrorInfo, Guardrails, LoopContext, Message, ModelConfig, OwnershipScope, PendingDecision, PromptVersionRef, ProviderRequest, RunLedger, RunOptions, SessionEntry, SessionStore, Skill, StopHook, ToolCallSummary, ToolDefinition, ToolEffectStore, ToolRegistry, ToolResult, Usage } from "../../contracts.js";
|
|
6
6
|
import type { AgentIdentity } from "../../identity.js";
|
|
7
7
|
import type { AgentInput } from "../../input.js";
|
|
8
8
|
import type { SecretRedactor } from "../../redaction.js";
|
|
@@ -33,9 +33,25 @@ export type SessionHost = {
|
|
|
33
33
|
activeIdentity?: AgentIdentity;
|
|
34
34
|
activeIdempotencyKey?: string;
|
|
35
35
|
activeGuardrails?: Guardrails;
|
|
36
|
+
/** Plan 092 Task 2: packs compiled once at session construction; read-only for phases. */
|
|
37
|
+
readonly packGuardrails?: Guardrails;
|
|
38
|
+
/** Plan 104 T3: `ask` rules as the durable charge-time gate (`interrupt` records) and as plain
|
|
39
|
+
* blocks for a run that cannot suspend. */
|
|
40
|
+
readonly packAskGate?: Guardrails;
|
|
41
|
+
readonly packAskBlocks?: Guardrails;
|
|
42
|
+
/** Plan 104 T2: pack refs + live pack-owned state for a durable checkpoint. */
|
|
43
|
+
serializedGuardrailPackState(): PersistedGuardrailPacks | undefined;
|
|
36
44
|
activeMetadata?: Readonly<Record<string, unknown>>;
|
|
37
45
|
activePromptVersion?: PromptVersionRef;
|
|
38
46
|
activeLimits?: RunLimitTracker;
|
|
47
|
+
/** Plan 091 T2: input tokens of the latest provider turn plus whether the
|
|
48
|
+
* provider reported them. Set by the usage seam; read by `contextMeter()`. */
|
|
49
|
+
activeInputMeter?: {
|
|
50
|
+
readonly tokens: number;
|
|
51
|
+
readonly source: "reported" | "estimated";
|
|
52
|
+
};
|
|
53
|
+
/** Bounded last-N tool-call summaries of the active run (plan 087 T2): ids, names, arg hashes. */
|
|
54
|
+
activeRecentToolCalls?: ToolCallSummary[];
|
|
39
55
|
activeLimitOutputBuffer: boolean;
|
|
40
56
|
activeDurable?: ActiveDurableRun;
|
|
41
57
|
activeLoop?: AgentLoopStrategy;
|
|
@@ -45,6 +61,8 @@ export type SessionHost = {
|
|
|
45
61
|
}>;
|
|
46
62
|
activeLoopTurn: number;
|
|
47
63
|
readonly loadedSkills: LoadedSkillSet;
|
|
64
|
+
/** Run-owned monotonic prompt tail; cleared before each new run. */
|
|
65
|
+
readonly tailSegments: Map<string, Message>;
|
|
48
66
|
readonly activatedTools: ActiveToolSet;
|
|
49
67
|
restoredSkillBodies: readonly LoadedSkillBodiesEntry[];
|
|
50
68
|
activeRunSkills: readonly Skill[];
|
|
@@ -57,11 +75,22 @@ export type SessionHost = {
|
|
|
57
75
|
serializedAttentionSticky(): PersistedAttentionStickyFrontier | undefined;
|
|
58
76
|
/** Plan 074 P3: restore a frontier that was validated when the checkpoint was loaded. */
|
|
59
77
|
restoreAttentionSticky(persisted: PersistedAttentionStickyFrontier): void;
|
|
78
|
+
/** Folded bodies for this session (plan 086 T3); session-owned so a resumed fold re-applies
|
|
79
|
+
* the same stub bytes instead of calling the host `summarize` again. Lazily created. */
|
|
80
|
+
attentionFoldFor(): AttentionFoldLedger;
|
|
81
|
+
/** Plan 086 T3: bounded ledger snapshot for a durable checkpoint (undefined before any fold). */
|
|
82
|
+
serializedAttentionFold(): PersistedAttentionFoldLedger | undefined;
|
|
83
|
+
/** Plan 086 T3: adopt a ledger validated when the checkpoint was loaded. */
|
|
84
|
+
restoreAttentionFold(ledger: AttentionFoldLedger): void;
|
|
85
|
+
/** Plan 086 T3: `attention.compiler.durable` for the current run; set by the run assembler. */
|
|
86
|
+
attentionDurable: boolean;
|
|
60
87
|
invalidateSnapshot(): void;
|
|
61
88
|
resolveRunProvider(options: RunOptions): void;
|
|
62
89
|
emit(event: AgentEvent): void;
|
|
63
90
|
rebuildHistory(): Promise<void>;
|
|
64
91
|
resolveRunSkills(options: RunOptions, tools: readonly ToolDefinition[]): readonly Skill[];
|
|
92
|
+
/** Redacted, cap-checked steer queue push (plan 106 R1 uses it for stop-hook continuations). */
|
|
93
|
+
steer(input: AgentInput): void;
|
|
65
94
|
appendEntry(entry: SessionEntry): Promise<void>;
|
|
66
95
|
redact<T>(value: T): T;
|
|
67
96
|
appendMessage(message: Message, runId: string): Promise<void>;
|
|
@@ -83,7 +112,15 @@ export type SessionHost = {
|
|
|
83
112
|
readonly runState?: import("../../contracts.js").AgentRunState;
|
|
84
113
|
readonly interruption?: import("../../contracts.js").AgentRunInterruption;
|
|
85
114
|
}): AgentRunResult;
|
|
115
|
+
/**
|
|
116
|
+
* Plan 106 R2: dispatch `session_start` middleware once per session (first run start), awaited by
|
|
117
|
+
* the run assembler after the `agent_started`/`agent_resumed` emits. No-op on every later call.
|
|
118
|
+
*/
|
|
119
|
+
openSession(runId: string): Promise<void>;
|
|
120
|
+
/** Session teardown: close every subscriber, run-scoped and `acrossRuns` alike. */
|
|
86
121
|
closeSubscribers(): void;
|
|
122
|
+
/** Run end (finish, suspend, or deny): close only the subscribers that do not opt into `acrossRuns`. */
|
|
123
|
+
closeRunSubscribers(): void;
|
|
87
124
|
snapshot(): Promise<SessionContextSnapshot>;
|
|
88
125
|
};
|
|
89
126
|
export declare function asSessionHost(session: unknown): SessionHost;
|
|
@@ -116,6 +153,8 @@ export type RoundContext = {
|
|
|
116
153
|
loop: AgentLoopStrategy;
|
|
117
154
|
toolConcurrency: number;
|
|
118
155
|
toolsDisclosure: import("../../tool-search.js").ToolsDisclosure;
|
|
156
|
+
/** Per-turn dispatch overlay; undefined when `toolNarrowing` is unset. */
|
|
157
|
+
turnAllow?: readonly string[];
|
|
119
158
|
assembledTurn: boolean;
|
|
120
159
|
artifactFinished: boolean;
|
|
121
160
|
artifactFailedInfo: {
|
|
@@ -128,6 +167,8 @@ export type RoundContext = {
|
|
|
128
167
|
toolResults: ToolResult[];
|
|
129
168
|
/** Set when a `RunOptions.turnPolicy` stop ended the loop (plan 084 Task 2). */
|
|
130
169
|
runStop?: RunStopInfo;
|
|
170
|
+
/** Merged agent + run stop hooks, in invocation order (plan 106 R1). Empty = wrapper skipped. */
|
|
171
|
+
stopHooks: readonly StopHook[];
|
|
131
172
|
runUsage: {
|
|
132
173
|
add(usage: Usage): void;
|
|
133
174
|
value(): Usage | undefined;
|