@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
|
@@ -9,7 +9,7 @@ import { assertGuardrailsAllowed, runGuardrails } from "../../guardrails.js";
|
|
|
9
9
|
import { identityTelemetryAttributes, ownershipFromIdentity, resolveRunIdentity } from "../../identity.js";
|
|
10
10
|
import { assembleProviderInput } from "../../input.js";
|
|
11
11
|
import { errorToErrorInfo, redactRunLedgerRecord } from "../../redaction.js";
|
|
12
|
-
import { RunLimitError, RunLimitTracker, resolveRunLimits } from "../../run-limits.js";
|
|
12
|
+
import { describeBudgetExhaustion, RunLimitError, RunLimitTracker, resolveRunLimits } from "../../run-limits.js";
|
|
13
13
|
import { createSessionEntry } from "../../session-stores.js";
|
|
14
14
|
import { resolveSkillsDisclosure } from "../../skill-disclosure.js";
|
|
15
15
|
import { applyRestoredSkillBodies } from "../../skill-load.js";
|
|
@@ -17,15 +17,26 @@ import { assertStructuredOutputRequestSupported, resolveRunProviderOptions } fro
|
|
|
17
17
|
import { composeSystemPrompt, mergeSystemPromptConfig } from "../../system-prompts.js";
|
|
18
18
|
import { resolveToolResultFold } from "../../tool-result-fold.js";
|
|
19
19
|
import { createSearchToolsTool, createToolSearchState, resolveToolsDisclosure } from "../../tool-search.js";
|
|
20
|
-
import { createToolRegistry, selectRunTools } from "../../tools.js";
|
|
20
|
+
import { clampTurnToolNames, createToolRegistry, selectRunTools } from "../../tools.js";
|
|
21
21
|
import { bridgeAbort, createUsageAccumulator, inputToMessages, isDurableLoop, isSteerSoftInterrupt, mergeGuardrails, throwIfAborted, } from "../helpers.js";
|
|
22
|
-
import { checkpointDurableTurn, cleanupRun, persistDurable, persistSucceeded, suspendDurable } from "./persist.js";
|
|
23
|
-
import { generateWithRetry, recordProviderUsage } from "./provider-round.js";
|
|
22
|
+
import { checkpointDurableFold, checkpointDurableTurn, cleanupRun, persistDurable, persistSucceeded, suspendDurable } from "./persist.js";
|
|
23
|
+
import { generateWithRetry, recordProviderUsage, resolveDeterministicTurn } from "./provider-round.js";
|
|
24
24
|
import { bindChargeToolRound, bindDispatchToolCall, replayDurableNestedAndPending, runLoopUntilSettled, suspendGatedRound, } from "./tool-round.js";
|
|
25
25
|
const PROMPT_VERSION_MAX_NAME_BYTES = 256;
|
|
26
26
|
const PROMPT_VERSION_HASH_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
27
27
|
/** Cap on the host stop detail that reaches the result, ledger, and timeline (plan 084 Task 2). */
|
|
28
28
|
const TURN_STOP_DETAIL_MAX_BYTES = 256;
|
|
29
|
+
function lastAssistantText(history) {
|
|
30
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
31
|
+
const message = history[i];
|
|
32
|
+
if (message?.role !== "assistant")
|
|
33
|
+
continue;
|
|
34
|
+
const text = message.content.map((block) => (block.type === "text" ? block.text : "")).join("");
|
|
35
|
+
if (text)
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
29
40
|
/**
|
|
30
41
|
* `RunOptions.turnPolicy` stopped the run at a turn boundary (plan 084 Task 2). Internal control
|
|
31
42
|
* signal: it unwinds any loop shape and `executeRun` turns it into a clean terminal success with
|
|
@@ -45,6 +56,104 @@ class TurnPolicyError extends Error {
|
|
|
45
56
|
this.name = "TurnPolicyError";
|
|
46
57
|
}
|
|
47
58
|
}
|
|
59
|
+
/** Stop-hook misuse: a throwing or malformed hook fails the run closed (plan 106 R1). */
|
|
60
|
+
class StopHookError extends Error {
|
|
61
|
+
code = "ERR_PRISM_STOP_HOOK";
|
|
62
|
+
constructor(message, options) {
|
|
63
|
+
super(message, options);
|
|
64
|
+
this.name = "StopHookError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Validate the merged stop-hook list once per run, before any provider turn (plan 106 R1). */
|
|
68
|
+
function assertStopHooks(hooks) {
|
|
69
|
+
for (const hook of hooks) {
|
|
70
|
+
if (typeof hook !== "object" ||
|
|
71
|
+
hook === null ||
|
|
72
|
+
typeof hook.name !== "string" ||
|
|
73
|
+
hook.name.length === 0 ||
|
|
74
|
+
typeof hook.decide !== "function") {
|
|
75
|
+
throw new TypeError("stopHooks entries must be StopHook objects with a non-empty name and a decide function");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Run stop hooks in order at a natural loop end (plan 106 R1). The first `continue` wins; every
|
|
81
|
+
* `stop` (or no hook continuing) leaves the run finished. Hook context is metadata plus the live
|
|
82
|
+
* transcript — tool arguments, prompts, and results are never reshaped by core.
|
|
83
|
+
*/
|
|
84
|
+
async function evaluateStopHooks(ctx, stopHookActive) {
|
|
85
|
+
const context = {
|
|
86
|
+
sessionId: ctx.session.id,
|
|
87
|
+
runId: ctx.runId,
|
|
88
|
+
turn: ctx.limits.snapshot().turns,
|
|
89
|
+
history: ctx.loopCtx.history,
|
|
90
|
+
metadata: ctx.metadata,
|
|
91
|
+
signal: ctx.controller.signal,
|
|
92
|
+
stopHookActive,
|
|
93
|
+
};
|
|
94
|
+
for (const hook of ctx.stopHooks) {
|
|
95
|
+
let decision;
|
|
96
|
+
try {
|
|
97
|
+
decision = await hook.decide(context);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new StopHookError(`Stop hook "${hook.name}" threw`, { cause: error });
|
|
101
|
+
}
|
|
102
|
+
if (decision === null || typeof decision !== "object") {
|
|
103
|
+
throw new StopHookError(`Stop hook "${hook.name}" must return a StopHookDecision`);
|
|
104
|
+
}
|
|
105
|
+
const action = decision.action;
|
|
106
|
+
if (action === "stop")
|
|
107
|
+
continue;
|
|
108
|
+
if (action !== "continue") {
|
|
109
|
+
throw new StopHookError(`Stop hook "${hook.name}" decision action must be "stop" or "continue"`);
|
|
110
|
+
}
|
|
111
|
+
const reason = decision.reason;
|
|
112
|
+
if (typeof reason !== "string" || reason.length === 0) {
|
|
113
|
+
throw new StopHookError(`Stop hook "${hook.name}" continue decision requires a non-empty reason string`);
|
|
114
|
+
}
|
|
115
|
+
const steer = decision.steer;
|
|
116
|
+
if (!isStopHookSteer(steer)) {
|
|
117
|
+
throw new StopHookError(`Stop hook "${hook.name}" steer must be a string or Message`);
|
|
118
|
+
}
|
|
119
|
+
return steer === undefined ? { reason } : { reason, steer };
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
function isStopHookSteer(value) {
|
|
124
|
+
if (value === undefined || typeof value === "string")
|
|
125
|
+
return true;
|
|
126
|
+
if (typeof value !== "object" || value === null)
|
|
127
|
+
return false;
|
|
128
|
+
const message = value;
|
|
129
|
+
return typeof message.role === "string" && Array.isArray(message.content);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Queue a continuation through the host steer path (plan 106 R1): same redaction, same 8-message /
|
|
133
|
+
* 64 KiB caps, and the same input-guardrail re-check when the loop drains it. A queue failure fails
|
|
134
|
+
* the run closed — the hook asked for something the run cannot deliver.
|
|
135
|
+
*/
|
|
136
|
+
function queueStopHookContinuation(ctx, decision) {
|
|
137
|
+
const messages = [{ role: "user", content: [{ type: "text", text: decision.reason }] }];
|
|
138
|
+
if (decision.steer !== undefined) {
|
|
139
|
+
messages.push(typeof decision.steer === "string" ? { role: "user", content: [{ type: "text", text: decision.steer }] } : decision.steer);
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
ctx.session.steer(messages);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
throw new StopHookError("Stop hook continuation could not be queued", { cause: error });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** The generate-validate-revise loop promises a validated artifact; a bare return is a failure. */
|
|
149
|
+
function assertArtifactOutcome(ctx) {
|
|
150
|
+
if (ctx.loop.name === "generate-validate-revise" && !ctx.artifactFinished) {
|
|
151
|
+
throw Object.assign(new Error(ctx.artifactFailedInfo?.message ?? "artifact loop ended without a validated artifact"), {
|
|
152
|
+
name: "ArtifactFailed",
|
|
153
|
+
code: ctx.artifactFailedInfo?.code ?? "artifact_failed",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
48
157
|
/** Validate `RunOptions.turnPolicy` once, before any provider turn (plan 084 Task 2). */
|
|
49
158
|
function assertTurnPolicy(policy, resolvedLimits) {
|
|
50
159
|
if (policy === undefined)
|
|
@@ -135,12 +244,21 @@ function assertPromptVersionRef(ref) {
|
|
|
135
244
|
return ref;
|
|
136
245
|
}
|
|
137
246
|
async function assembleRoundContext(params) {
|
|
138
|
-
const { session, input, options, runId, resumed, controller, model, startedAt, promptVersion, metadata, limits, runUsage } = params;
|
|
247
|
+
const { session, input, options, runId, resumed, controller, model, startedAt, promptVersion, metadata, limits, runUsage, stopHooks } = params;
|
|
139
248
|
session.resolveRunProvider(options);
|
|
140
249
|
throwIfAborted(controller.signal);
|
|
141
250
|
session.emit({ type: "agent_started", sessionId: session.id, runId });
|
|
142
251
|
if (resumed)
|
|
143
|
-
session.emit({
|
|
252
|
+
session.emit({
|
|
253
|
+
type: "agent_resumed",
|
|
254
|
+
sessionId: session.id,
|
|
255
|
+
runId,
|
|
256
|
+
version: resumed.version,
|
|
257
|
+
...(resumed.restore ? { restore: resumed.restore } : {}),
|
|
258
|
+
});
|
|
259
|
+
// Plan 106 R2: first run start of this session opens it. Awaited after the two emits above so the
|
|
260
|
+
// run's synchronous announce burst stays intact; middleware error policy owns failures.
|
|
261
|
+
await session.openSession(runId);
|
|
144
262
|
const startRecord = {
|
|
145
263
|
id: runId,
|
|
146
264
|
sessionId: session.id,
|
|
@@ -176,6 +294,7 @@ async function assembleRoundContext(params) {
|
|
|
176
294
|
const tools = searchTool ? [...activeToolList, searchTool] : activeToolList;
|
|
177
295
|
const activeSkills = session.resolveRunSkills(options, tools);
|
|
178
296
|
session.activeRunSkills = activeSkills;
|
|
297
|
+
session.tailSegments.clear();
|
|
179
298
|
if (options.model && JSON.stringify(options.model) !== JSON.stringify(session.agent.config.model)) {
|
|
180
299
|
await session.appendEntry(createSessionEntry({
|
|
181
300
|
sessionId: session.id,
|
|
@@ -215,24 +334,41 @@ async function assembleRoundContext(params) {
|
|
|
215
334
|
assertStructuredOutputRequestSupported(options.model ?? session.agent.config.model, providerOptions);
|
|
216
335
|
const validate = options.validate ?? session.agent.config.validator;
|
|
217
336
|
// Resolved once per run, before any provider turn: a bad setting or a widening run overlay
|
|
218
|
-
// fails here rather than on the turn that happens to cross the ratio (plan 074 C12).
|
|
219
|
-
|
|
337
|
+
// fails here rather than on the turn that happens to cross the ratio (plan 074 C12). The
|
|
338
|
+
// resolved run input budget rides the handle so `run_input_ratio` folds against the same cap
|
|
339
|
+
// the run limit enforces (plan 086 T2); `null` (disabled) leaves that axis on the input cap.
|
|
340
|
+
const attentionCompiler = resolveRunAttentionCompiler(session.agent.config.attentionCompiler, options.attentionCompiler, options.model ?? session.agent.config.model, limits.limits.maxInputTokens);
|
|
341
|
+
// Plan 086 T3: durable folding writes the fold ledger to the run checkpoint, so it needs a
|
|
342
|
+
// durable run (the session's durable state is set before this call). Fail at run start, before
|
|
343
|
+
// any provider turn, rather than folding into memory only. The fold state rides that
|
|
344
|
+
// checkpoint independently of `persistSessionState`.
|
|
345
|
+
if (attentionCompiler?.durable && !session.activeDurable) {
|
|
346
|
+
throw new AgentRunStateError("attentionCompiler.durable requires a durable run: set AgentConfig or RunOptions runState with a checkpoint store");
|
|
347
|
+
}
|
|
348
|
+
session.attentionDurable = attentionCompiler?.durable === true;
|
|
220
349
|
// Telemetry seam (plan 074 T6): one `attention_compiled` per mutated turn, counts and the
|
|
221
350
|
// measured ratio inputs only. Under-ratio turns and compiler-off runs emit nothing.
|
|
351
|
+
// Plan 086 T3: a turn that folded new bodies is the fold-boundary durability signal, so it is
|
|
352
|
+
// remembered here (the callback is synchronous) and checkpointed by the assembler below.
|
|
353
|
+
let foldCheckpointPending = false;
|
|
222
354
|
const onAttentionReport = attentionCompiler
|
|
223
|
-
? (report) =>
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
355
|
+
? (report) => {
|
|
356
|
+
if (report.newFoldedBodies > 0)
|
|
357
|
+
foldCheckpointPending = true;
|
|
358
|
+
session.emit({
|
|
359
|
+
type: "attention_compiled",
|
|
360
|
+
sessionId: session.id,
|
|
361
|
+
runId,
|
|
362
|
+
used: report.used,
|
|
363
|
+
usedAfter: report.usedAfter,
|
|
364
|
+
inputCap: report.inputCap,
|
|
365
|
+
triggerRatio: report.triggerRatio,
|
|
366
|
+
droppedThinkingTurns: report.droppedThinkingTurns,
|
|
367
|
+
stubbedToolResults: report.stubbedToolResults,
|
|
368
|
+
stubbedBytes: report.stubbedBytes,
|
|
369
|
+
truncated: report.truncated,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
236
372
|
: undefined;
|
|
237
373
|
const instructionInjectors = options.instructionInjectors ?? session.agent.config.instructionInjectors ?? [];
|
|
238
374
|
const inputLayout = options.inputLayout ?? session.agent.config.inputLayout;
|
|
@@ -269,9 +405,12 @@ async function assembleRoundContext(params) {
|
|
|
269
405
|
artifactFailedInfo: undefined,
|
|
270
406
|
toolCalls: 0,
|
|
271
407
|
toolResults: [],
|
|
408
|
+
stopHooks,
|
|
272
409
|
runUsage,
|
|
273
410
|
loopCtx: undefined,
|
|
274
411
|
};
|
|
412
|
+
const toolNarrowing = options.toolNarrowing ?? session.agent.config.toolNarrowing;
|
|
413
|
+
let narrowedForTurn;
|
|
275
414
|
const loopCtx = {
|
|
276
415
|
sessionId: session.id,
|
|
277
416
|
runId,
|
|
@@ -285,6 +424,38 @@ async function assembleRoundContext(params) {
|
|
|
285
424
|
restoredLoopState: resumed?.state?.loopState?.snapshot,
|
|
286
425
|
assemble: async (nextInput, toolResults, turn) => {
|
|
287
426
|
limits.charge("maxTurns");
|
|
427
|
+
const turnIndex = turn ?? 1;
|
|
428
|
+
let turnTools = tools;
|
|
429
|
+
if (toolNarrowing) {
|
|
430
|
+
if (typeof toolNarrowing !== "function")
|
|
431
|
+
throw new TypeError("toolNarrowing must be a function");
|
|
432
|
+
if (narrowedForTurn?.turn === turnIndex) {
|
|
433
|
+
turnTools = narrowedForTurn.tools;
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
const assistant = lastAssistantText(session.history);
|
|
437
|
+
const requested = await toolNarrowing({
|
|
438
|
+
turn: turnIndex,
|
|
439
|
+
toolIds: tools.map((tool) => tool.name),
|
|
440
|
+
...(assistant !== undefined ? { lastAssistantText: assistant } : {}),
|
|
441
|
+
});
|
|
442
|
+
if (!Array.isArray(requested))
|
|
443
|
+
throw new TypeError("toolNarrowing must return a string array");
|
|
444
|
+
const clamped = clampTurnToolNames(tools, requested);
|
|
445
|
+
if (clamped.dropped.length > 0) {
|
|
446
|
+
session.emit({
|
|
447
|
+
type: "tool_narrowing_clamped",
|
|
448
|
+
sessionId: session.id,
|
|
449
|
+
runId,
|
|
450
|
+
turn: turnIndex,
|
|
451
|
+
dropped: clamped.dropped,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
turnTools = clamped.tools;
|
|
455
|
+
narrowedForTurn = { turn: turnIndex, tools: turnTools };
|
|
456
|
+
}
|
|
457
|
+
ctx.turnAllow = turnTools.map((tool) => tool.name);
|
|
458
|
+
}
|
|
288
459
|
const request = await assembleProviderInput({
|
|
289
460
|
model: options.model ?? session.agent.config.model,
|
|
290
461
|
input: nextInput,
|
|
@@ -304,13 +475,21 @@ async function assembleRoundContext(params) {
|
|
|
304
475
|
toolsSearch: session.agent.config.toolsSearch,
|
|
305
476
|
activatedTools: session.activatedTools,
|
|
306
477
|
toolResultFold: resolveToolResultFold(options.toolResultFold, session.agent.config.toolResultFold),
|
|
478
|
+
contextBudget: session.agent.config.contextBudget,
|
|
307
479
|
attentionCompiler,
|
|
308
480
|
// Session-owned: a stub made earlier stays applied even on a later under-ratio turn, so
|
|
309
481
|
// the prompt-cache prefix is not rewritten (C10). Undefined when the compiler is off.
|
|
310
482
|
attentionSticky: attentionCompiler ? session.attentionStickyFor() : undefined,
|
|
483
|
+
// Folded bodies (plan 086 T3): a row summarized once is re-applied, never re-summarized,
|
|
484
|
+
// so sticky rows stay byte-identical and a resumed run reuses the persisted bodies.
|
|
485
|
+
attentionFold: attentionCompiler ? session.attentionFoldFor() : undefined,
|
|
486
|
+
// Charge-so-far for the `run_input_ratio` axis: the counter only holds completed turns,
|
|
487
|
+
// so the axis projects this turn's estimate onto it.
|
|
488
|
+
runInputTokens: limits.snapshot().inputTokens,
|
|
311
489
|
onAttentionReport,
|
|
312
490
|
loadedSkills: session.loadedSkills,
|
|
313
|
-
|
|
491
|
+
tailSegments: session.tailSegments,
|
|
492
|
+
tools: turnTools,
|
|
314
493
|
resourceLoader: session.agent.config.resourceLoader,
|
|
315
494
|
permission: session.agent.config.permission,
|
|
316
495
|
trust: session.agent.config.trust,
|
|
@@ -323,6 +502,13 @@ async function assembleRoundContext(params) {
|
|
|
323
502
|
signal: controller.signal,
|
|
324
503
|
});
|
|
325
504
|
ctx.assembledTurn = true;
|
|
505
|
+
if (foldCheckpointPending) {
|
|
506
|
+
foldCheckpointPending = false;
|
|
507
|
+
// Fold-boundary durability (plan 086 T3): one write per turn that added folded bodies,
|
|
508
|
+
// after the request is assembled and before the provider sees it, so a crash during this
|
|
509
|
+
// turn resumes with the same ledger. No-op unless the compiler is durable.
|
|
510
|
+
await checkpointDurableFold(session, { runId, model, limits });
|
|
511
|
+
}
|
|
326
512
|
return request;
|
|
327
513
|
},
|
|
328
514
|
chargeToolRound: bindChargeToolRound(ctx),
|
|
@@ -345,10 +531,15 @@ async function assembleRoundContext(params) {
|
|
|
345
531
|
if (session.activeDurable?.options.checkpointPolicy === "every-turn") {
|
|
346
532
|
await checkpointDurableTurn(session, { runId, model, limits });
|
|
347
533
|
}
|
|
534
|
+
// Deterministic no-model turn (plan 096): host middleware answers at the provider boundary,
|
|
535
|
+
// before any provider-round work. No answer → provider path unchanged.
|
|
536
|
+
const deterministic = await resolveDeterministicTurn(session, request, runId, session.activeLoopTurn, controller.signal, ctx.toolResults);
|
|
537
|
+
if (deterministic)
|
|
538
|
+
return deterministic;
|
|
348
539
|
const policyResult = await session.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
|
|
349
540
|
const middlewareRequest = (await session.agent.config.middleware?.run("provider_request", policyResult.request)) ?? policyResult.request;
|
|
350
541
|
try {
|
|
351
|
-
return await generateWithRetry(session, session.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, session.activeLoopTurn, (turnUsage, turn, attempt) => recordProviderUsage(ctx, turnUsage, turn, attempt), ctx.toolResults);
|
|
542
|
+
return await generateWithRetry(session, session.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, session.activeLoopTurn, (turnUsage, turn, attempt) => recordProviderUsage(ctx, turnUsage, turn, attempt, middlewareRequest), ctx.toolResults);
|
|
352
543
|
}
|
|
353
544
|
catch (error) {
|
|
354
545
|
if (isSteerSoftInterrupt(error)) {
|
|
@@ -381,6 +572,50 @@ async function assembleRoundContext(params) {
|
|
|
381
572
|
ctx.loopCtx = loopCtx;
|
|
382
573
|
return ctx;
|
|
383
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* Run the loop to settlement, then apply stop hooks at the natural loop end (plan 106 R1). Each
|
|
577
|
+
* `continue` queues its reason through the steer path and re-enters the loop with a continuation
|
|
578
|
+
* context whose `input`/`inputMessages` are empty — the continuation message is already in
|
|
579
|
+
* `history`, and replaying run-start input would duplicate it. A loop ceiling, a host turn-policy
|
|
580
|
+
* stop, or an artifact failure is not a natural end: hooks never run there, and a continuation leg
|
|
581
|
+
* that hits a ceiling ends the run instead of asking again. `limits.maxStopContinuations`
|
|
582
|
+
* (default 3; `0` observes only; `null` uncapped) bounds continuations as a clean `hook_limit` stop.
|
|
583
|
+
*/
|
|
584
|
+
async function runLoopWithStopHooks(ctx) {
|
|
585
|
+
let usage = await runLoopUntilSettled(ctx);
|
|
586
|
+
assertArtifactOutcome(ctx);
|
|
587
|
+
// Zero overhead when nothing is configured: no wrapper state, no reads.
|
|
588
|
+
if (ctx.stopHooks.length === 0)
|
|
589
|
+
return usage;
|
|
590
|
+
const cap = ctx.limits.limits.maxStopContinuations;
|
|
591
|
+
let continuations = 0;
|
|
592
|
+
let stopHookActive = false;
|
|
593
|
+
for (;;) {
|
|
594
|
+
if (ctx.runStop !== undefined || ctx.loopCtx.finishReason !== undefined)
|
|
595
|
+
return usage;
|
|
596
|
+
const decision = await evaluateStopHooks(ctx, stopHookActive);
|
|
597
|
+
if (!decision)
|
|
598
|
+
return usage;
|
|
599
|
+
if (cap !== null && continuations >= cap) {
|
|
600
|
+
ctx.loopCtx.finishReason = "hook_limit";
|
|
601
|
+
return usage;
|
|
602
|
+
}
|
|
603
|
+
continuations += 1;
|
|
604
|
+
stopHookActive = true;
|
|
605
|
+
queueStopHookContinuation(ctx, decision);
|
|
606
|
+
const continuationCtx = { ...ctx.loopCtx, input: [], inputMessages: [], continuation: true };
|
|
607
|
+
try {
|
|
608
|
+
usage = await runLoopUntilSettled({ ...ctx, loopCtx: continuationCtx });
|
|
609
|
+
}
|
|
610
|
+
finally {
|
|
611
|
+
// The loops set `finishReason` on the context they receive; carry it back so the ceiling
|
|
612
|
+
// survives onto the result, the finish record, and `persistSucceeded`.
|
|
613
|
+
if (continuationCtx.finishReason !== undefined)
|
|
614
|
+
ctx.loopCtx.finishReason = continuationCtx.finishReason;
|
|
615
|
+
}
|
|
616
|
+
assertArtifactOutcome(ctx);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
384
619
|
export async function executeRun(session, input, options, runId, resumed) {
|
|
385
620
|
const legacyMaxToolRounds = options.maxToolRounds;
|
|
386
621
|
if (legacyMaxToolRounds !== undefined) {
|
|
@@ -398,6 +633,8 @@ export async function executeRun(session, input, options, runId, resumed) {
|
|
|
398
633
|
const requestedLimits = options.limits;
|
|
399
634
|
const resolvedLimits = resolveRunLimits(session.agent.config.limits, requestedLimits);
|
|
400
635
|
assertTurnPolicy(options.turnPolicy, resolvedLimits);
|
|
636
|
+
const stopHooks = [...(session.agent.config.stopHooks ?? []), ...(options.stopHooks ?? [])];
|
|
637
|
+
assertStopHooks(stopHooks);
|
|
401
638
|
const durableOptions = options.runState ?? session.agent.config.runState;
|
|
402
639
|
if (session.agent.config.runState && options.runState && session.agent.config.runState !== options.runState) {
|
|
403
640
|
throw new AgentRunStateError("RunOptions cannot replace agent durable run-state configuration");
|
|
@@ -431,8 +668,14 @@ export async function executeRun(session, input, options, runId, resumed) {
|
|
|
431
668
|
if (session.activeIdentity && !session.activeOwnership)
|
|
432
669
|
session.activeOwnership = ownershipFromIdentity(session.activeIdentity);
|
|
433
670
|
session.activeIdempotencyKey = options.idempotencyKey ?? session.agent.config.idempotencyKey;
|
|
434
|
-
session.activeGuardrails = mergeGuardrails(session.agent.config.guardrails, options.guardrails);
|
|
435
671
|
session.activeDurable = resumed ?? (durableOptions ? { options: durableOptions, version: 0 } : undefined);
|
|
672
|
+
// Plan 104 T3: an `ask` rule is gated at charge time when the run can suspend; a run that cannot
|
|
673
|
+
// suspend enforces the same rule as a plain block, so it joins the ordinary stage guardrails.
|
|
674
|
+
const packGuardrails = session.activeDurable ? session.packGuardrails : mergeGuardrails(session.packGuardrails, session.packAskBlocks);
|
|
675
|
+
session.activeGuardrails = mergeGuardrails(mergeGuardrails(session.agent.config.guardrails, packGuardrails), options.guardrails);
|
|
676
|
+
// Plan 086 T3: reset here, so a suspension before the compiler is resolved (input guardrail)
|
|
677
|
+
// cannot inherit the previous run's durable-folding flag. `assembleRoundContext` sets it true.
|
|
678
|
+
session.attentionDurable = false;
|
|
436
679
|
session.activeGatedRound = undefined;
|
|
437
680
|
if (resumed)
|
|
438
681
|
session.invalidateSnapshot();
|
|
@@ -462,6 +705,7 @@ export async function executeRun(session, input, options, runId, resumed) {
|
|
|
462
705
|
deadlineAt: resumed?.state?.deadlineAt,
|
|
463
706
|
});
|
|
464
707
|
session.activeLimits = limits;
|
|
708
|
+
session.activeRecentToolCalls = [];
|
|
465
709
|
const hasFiniteTokenCap = (value) => typeof value === "number" && Number.isFinite(value);
|
|
466
710
|
session.activeLimitOutputBuffer = [session.agent.config.limits, requestedLimits].some((value) => hasFiniteTokenCap(value?.maxOutputTokens) || hasFiniteTokenCap(value?.maxTotalTokens) || value?.maxCost !== undefined);
|
|
467
711
|
try {
|
|
@@ -478,6 +722,7 @@ export async function executeRun(session, input, options, runId, resumed) {
|
|
|
478
722
|
metadata,
|
|
479
723
|
limits,
|
|
480
724
|
runUsage,
|
|
725
|
+
stopHooks,
|
|
481
726
|
});
|
|
482
727
|
await replayDurableNestedAndPending(ctx);
|
|
483
728
|
const resumedLoopState = resumed?.state?.loopState;
|
|
@@ -487,7 +732,7 @@ export async function executeRun(session, input, options, runId, resumed) {
|
|
|
487
732
|
}
|
|
488
733
|
ctx.loop.restore?.(resumedLoopState.snapshot);
|
|
489
734
|
}
|
|
490
|
-
const loopUsage = await
|
|
735
|
+
const loopUsage = await runLoopWithStopHooks(ctx).catch((error) => {
|
|
491
736
|
// Host turn-policy stop (plan 084 Task 2): the loop was unwound on purpose at a turn
|
|
492
737
|
// boundary. Not an error — the run settles cleanly and stays resumable.
|
|
493
738
|
if (error instanceof AgentRunStopped)
|
|
@@ -496,12 +741,6 @@ export async function executeRun(session, input, options, runId, resumed) {
|
|
|
496
741
|
});
|
|
497
742
|
stopReason = ctx.runStop?.reason ?? ctx.loopCtx.finishReason;
|
|
498
743
|
stopDetail = ctx.runStop?.detail;
|
|
499
|
-
if (!ctx.runStop && ctx.loop.name === "generate-validate-revise" && !ctx.artifactFinished) {
|
|
500
|
-
throw Object.assign(new Error(ctx.artifactFailedInfo?.message ?? "artifact loop ended without a validated artifact"), {
|
|
501
|
-
name: "ArtifactFailed",
|
|
502
|
-
code: ctx.artifactFailedInfo?.code ?? "artifact_failed",
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
744
|
usage = runUsage.value() ?? loopUsage;
|
|
506
745
|
return await persistSucceeded(ctx, loopUsage);
|
|
507
746
|
}
|
|
@@ -513,8 +752,18 @@ export async function executeRun(session, input, options, runId, resumed) {
|
|
|
513
752
|
return session.buildRunResult({ runId, status: "suspended", runState: error.state, interruption: error.interruption });
|
|
514
753
|
}
|
|
515
754
|
runError = errorToErrorInfo(error);
|
|
516
|
-
session.emit({ type: "error", sessionId: session.id, runId, error: runError });
|
|
517
755
|
const breach = error instanceof RunLimitError ? error.breach : limits.breach;
|
|
756
|
+
// Terminal attribution before the terminal `error`/finish records, so a subscriber that stops
|
|
757
|
+
// at the first terminal event still sees why the run died (plan 087 T2).
|
|
758
|
+
if (breach) {
|
|
759
|
+
session.emit({
|
|
760
|
+
type: "budget_exhausted",
|
|
761
|
+
sessionId: session.id,
|
|
762
|
+
runId,
|
|
763
|
+
...describeBudgetExhaustion(limits, breach, session.activeRecentToolCalls ?? []),
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
session.emit({ type: "error", sessionId: session.id, runId, error: runError });
|
|
518
767
|
runStatus = breach ? "failed" : controller.signal.aborted ? "aborted" : "failed";
|
|
519
768
|
const runState = session.activeDurable?.state
|
|
520
769
|
? await persistDurable(session, {
|
|
@@ -28,6 +28,17 @@ export declare function checkpointDurableTurn(session: SessionHost, input: {
|
|
|
28
28
|
readonly model: ModelConfig;
|
|
29
29
|
readonly limits: RunLimitTracker;
|
|
30
30
|
}): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Fold-boundary checkpoint (plan 086 T3). Called once per turn that added folded bodies — never
|
|
33
|
+
* per turn — when the resolved compiler is durable, so a crash after a fold resumes with the
|
|
34
|
+
* ledger and frontier already on disk. Independent of `checkpointPolicy`: the fold is the
|
|
35
|
+
* durability point that matters for a long single run, not the turn boundary.
|
|
36
|
+
*/
|
|
37
|
+
export declare function checkpointDurableFold(session: SessionHost, input: {
|
|
38
|
+
readonly runId: string;
|
|
39
|
+
readonly model: ModelConfig;
|
|
40
|
+
readonly limits: RunLimitTracker;
|
|
41
|
+
}): Promise<void>;
|
|
31
42
|
export declare function persistSucceeded(ctx: RoundContext, loopUsage: Usage | undefined): Promise<AgentRunResult>;
|
|
32
43
|
export declare function cleanupRun(input: {
|
|
33
44
|
session: SessionHost;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Finalize/persist phase of runInternal (plan 059). */
|
|
2
|
-
import { boundedLoopSnapshot, initialAgentRunState, publicState, saveAgentRunState } from "../../agent-run-state.js";
|
|
2
|
+
import { boundedLoopSnapshot, initialAgentRunState, publicState, resolveCheckpointMetadata, saveAgentRunState, } from "../../agent-run-state.js";
|
|
3
3
|
import { AgentRunStateError } from "../../contracts.js";
|
|
4
4
|
import { redactRunLedgerRecord } from "../../redaction.js";
|
|
5
5
|
import { isFlushableRunLedger } from "../../run-ledger.js";
|
|
@@ -10,13 +10,20 @@ export async function persistDurable(session, state) {
|
|
|
10
10
|
if (!durable)
|
|
11
11
|
throw new AgentRunStateError("Durable run state is not configured");
|
|
12
12
|
const withGrant = session.activeToolNames !== undefined ? { ...state, toolNames: session.activeToolNames } : state;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const persistSessionState = durable.options.persistSessionState === true;
|
|
14
|
+
// Plan 086 T3: durable folding owns its two keys. A run that opted into `durable` writes the
|
|
15
|
+
// fold ledger and its frontier even when the broader session-state bag stays off; a run that
|
|
16
|
+
// did not keeps exactly today's bytes, where the frontier rides `persistSessionState`.
|
|
17
|
+
const attentionSticky = persistSessionState || session.attentionDurable ? session.serializedAttentionSticky() : undefined;
|
|
18
|
+
const attentionFold = session.attentionDurable ? session.serializedAttentionFold() : undefined;
|
|
19
|
+
// Plan 104 T2: pack refs and pack-owned state; the key only exists under the same opt-in, so a
|
|
20
|
+
// default checkpoint keeps exactly today's bytes.
|
|
21
|
+
const guardrailPacks = persistSessionState ? session.serializedGuardrailPackState() : undefined;
|
|
22
|
+
const sessionState = {
|
|
23
|
+
...(persistSessionState
|
|
24
|
+
? {
|
|
18
25
|
loadedSkillNames: session.loadedSkills.list(),
|
|
19
|
-
...(
|
|
26
|
+
...(guardrailPacks ? { guardrailPacks } : {}),
|
|
20
27
|
...(session.activatedTools.list().length ? { activatedToolNames: session.activatedTools.list() } : {}),
|
|
21
28
|
...(durable.options.includeSkillBodies
|
|
22
29
|
? {
|
|
@@ -25,9 +32,13 @@ export async function persistDurable(session, state) {
|
|
|
25
32
|
: undefined),
|
|
26
33
|
}
|
|
27
34
|
: {}),
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
:
|
|
35
|
+
}
|
|
36
|
+
: {}),
|
|
37
|
+
...(attentionSticky ? { attentionSticky } : {}),
|
|
38
|
+
...(attentionFold ? { attentionFold } : {}),
|
|
39
|
+
};
|
|
40
|
+
const persisted = Object.keys(sessionState).length > 0 ? { ...withGrant, sessionState } : withGrant;
|
|
41
|
+
const metadata = resolveCheckpointMetadata(durable.options.checkpointMetadata) ?? durable.checkpointMetadata;
|
|
31
42
|
const saved = await saveAgentRunState({
|
|
32
43
|
checkpoints: durable.options.checkpoints,
|
|
33
44
|
state: persisted,
|
|
@@ -36,6 +47,7 @@ export async function persistDurable(session, state) {
|
|
|
36
47
|
fencingToken: durable.options.fencingToken,
|
|
37
48
|
redactor: session.activeRedactor,
|
|
38
49
|
maxStateBytes: durable.options.maxStateBytes,
|
|
50
|
+
...(metadata ? { metadata } : {}),
|
|
39
51
|
});
|
|
40
52
|
durable.state = saved.state;
|
|
41
53
|
durable.version = saved.record.version;
|
|
@@ -86,8 +98,25 @@ export async function suspendDurable(session, input) {
|
|
|
86
98
|
* into a resumed run, and loop-local state rides along exactly as it does at suspension.
|
|
87
99
|
*/
|
|
88
100
|
export async function checkpointDurableTurn(session, input) {
|
|
101
|
+
if (session.activeDurable?.options.checkpointPolicy !== "every-turn")
|
|
102
|
+
return;
|
|
103
|
+
await writeRunningCheckpoint(session, input);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Fold-boundary checkpoint (plan 086 T3). Called once per turn that added folded bodies — never
|
|
107
|
+
* per turn — when the resolved compiler is durable, so a crash after a fold resumes with the
|
|
108
|
+
* ledger and frontier already on disk. Independent of `checkpointPolicy`: the fold is the
|
|
109
|
+
* durability point that matters for a long single run, not the turn boundary.
|
|
110
|
+
*/
|
|
111
|
+
export async function checkpointDurableFold(session, input) {
|
|
112
|
+
if (!session.attentionDurable)
|
|
113
|
+
return;
|
|
114
|
+
await writeRunningCheckpoint(session, input);
|
|
115
|
+
}
|
|
116
|
+
/** Shared running-checkpoint write for the turn-boundary and fold-boundary triggers. */
|
|
117
|
+
async function writeRunningCheckpoint(session, input) {
|
|
89
118
|
const durable = session.activeDurable;
|
|
90
|
-
if (durable
|
|
119
|
+
if (!durable)
|
|
91
120
|
return;
|
|
92
121
|
const loop = session.activeLoop;
|
|
93
122
|
const loopState = loop?.snapshot ? boundedLoopSnapshot(loop.name, loop.revision ?? "1", loop.snapshot()) : undefined;
|
|
@@ -135,19 +164,21 @@ export async function persistSucceeded(ctx, loopUsage) {
|
|
|
135
164
|
await session.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, session.activeRedactor));
|
|
136
165
|
}
|
|
137
166
|
await session.drainLedger();
|
|
167
|
+
// Plan 084 Task 2 / plan 106 R1: a clean run-end stop keeps the frontier and marks the state
|
|
168
|
+
// continuable — a host turn-policy stop (`host_policy`) or a stop-hook continuation cap
|
|
169
|
+
// (`hook_limit`). Every other succeeded state drops its loop state and is final.
|
|
170
|
+
const continuableStop = stop ? "host_policy" : ctx.loopCtx.finishReason === "hook_limit" ? "hook_limit" : undefined;
|
|
138
171
|
const runState = session.activeDurable?.state
|
|
139
172
|
? await persistDurable(session, {
|
|
140
173
|
...session.activeDurable.state,
|
|
141
174
|
status: "succeeded",
|
|
142
|
-
|
|
143
|
-
// intact — the loop state is kept and the state is marked continuable.
|
|
144
|
-
...(stop ? { stopReason: "host_policy", leafId: session.currentLeafId } : {}),
|
|
175
|
+
...(continuableStop ? { stopReason: continuableStop, leafId: session.currentLeafId } : {}),
|
|
145
176
|
pending: undefined,
|
|
146
177
|
pendingCalls: undefined,
|
|
147
178
|
nestedRuns: undefined,
|
|
148
179
|
stickyDecisions: undefined,
|
|
149
180
|
interruption: undefined,
|
|
150
|
-
...(
|
|
181
|
+
...(continuableStop ? {} : { loopState: undefined }),
|
|
151
182
|
})
|
|
152
183
|
: undefined;
|
|
153
184
|
session.emit({
|
|
@@ -216,11 +247,12 @@ export async function cleanupRun(input) {
|
|
|
216
247
|
session.activeLimits?.dispose();
|
|
217
248
|
session.activeToolNames = undefined;
|
|
218
249
|
session.activeLimits = undefined;
|
|
250
|
+
session.activeRecentToolCalls = undefined;
|
|
219
251
|
session.activeLimitOutputBuffer = false;
|
|
220
252
|
session.activeRedactor = undefined;
|
|
221
253
|
session.activeProvider = undefined;
|
|
222
254
|
cleanupSignal();
|
|
223
|
-
session.
|
|
255
|
+
session.closeRunSubscribers();
|
|
224
256
|
}
|
|
225
257
|
}
|
|
226
258
|
//# sourceMappingURL=persist.js.map
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
/** Provider-round phase of runInternal (plan 059). */
|
|
2
|
-
import type { ProviderRequest, ProviderTurnResult, RunOptions, ToolResult, Usage } from "../../contracts.js";
|
|
2
|
+
import type { ModelConfig, ProviderRequest, ProviderTurnResult, RunOptions, ToolResult, Usage } from "../../contracts.js";
|
|
3
3
|
import type { RoundContext, SessionHost } from "./types.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/** Resolve the per-request input cap for turn-budget metadata (plan 087 T1). A model without a
|
|
5
|
+
* derivable cap (or a bad attention setting on an unrelated run) omits the field instead of
|
|
6
|
+
* failing an emitting turn; the attention compiler, when enabled, is the cap authority. */
|
|
7
|
+
export declare function resolveTurnInputCap(session: SessionHost, model: ModelConfig): number | undefined;
|
|
8
|
+
export declare function recordProviderUsage(ctx: RoundContext, turnUsage: Usage | undefined, turn: number, attempt: number, request?: ProviderRequest): Promise<Usage | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* Plan 096: host middleware may answer the turn deterministically at the `beforeProviderTurn` seam —
|
|
11
|
+
* no provider request, no usage, mandatory provenance. `undefined` sends the turn to the provider
|
|
12
|
+
* unchanged; a malformed answer fails the run closed instead of falling through to the provider.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveDeterministicTurn(session: SessionHost, request: ProviderRequest, runId: string, turn: number, signal: AbortSignal, toolResults?: readonly ToolResult[]): Promise<ProviderTurnResult | undefined>;
|
|
15
|
+
export declare function generateWithRetry(session: SessionHost, request: ProviderRequest, runId: string, options: RunOptions, signal: AbortSignal, requestSecrets?: readonly (string | undefined)[], turn?: number, recordUsage?: (usage: Usage | undefined, turn: number, attempt: number) => Promise<Usage | undefined>, toolResults?: readonly ToolResult[]): Promise<ProviderTurnResult>;
|
|
16
|
+
export declare function generateProviderTurn(session: SessionHost, request: ProviderRequest, runId: string, signal: AbortSignal, secrets?: readonly (string | undefined)[], turn?: number, attempt?: number, recordUsage?: (usage: Usage | undefined, turn: number, attempt: number) => Promise<Usage | undefined>, toolResults?: readonly ToolResult[]): Promise<ProviderTurnResult>;
|