@arnilo/prism 0.0.15 → 0.0.17
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 +31 -0
- package/dist/agent-definitions.js +2 -3
- package/dist/agent-loops.js +12 -7
- package/dist/agent-run-lifecycle.d.ts +1 -2
- package/dist/agent-run-lifecycle.js +1 -1
- package/dist/agent-run-state.js +29 -4
- package/dist/agents.d.ts +1 -1
- package/dist/agents.js +163 -61
- package/dist/cache-helpers.js +18 -9
- package/dist/checkpoints.d.ts +4 -0
- package/dist/checkpoints.js +17 -9
- package/dist/cli-init.js +3 -7
- package/dist/cli-runner.d.ts +2 -6
- package/dist/cli-runner.js +71 -33
- package/dist/compaction.js +5 -4
- package/dist/config.js +7 -4
- package/dist/content.js +26 -24
- package/dist/context-budget.js +18 -11
- package/dist/contracts.d.ts +16 -3
- package/dist/contracts.js +4 -1
- package/dist/contribution-parsing.js +6 -2
- package/dist/contributions.d.ts +2 -0
- package/dist/contributions.js +3 -0
- package/dist/conversations.js +2 -1
- package/dist/credentials.d.ts +8 -2
- package/dist/credentials.js +9 -3
- package/dist/event-multiplexer.js +18 -4
- package/dist/extensions.d.ts +7 -1
- package/dist/extensions.js +64 -6
- package/dist/feedback.js +12 -10
- package/dist/guardrails.d.ts +1 -1
- package/dist/guardrails.js +26 -17
- package/dist/identity.js +10 -2
- package/dist/index.d.ts +82 -83
- package/dist/index.js +42 -42
- package/dist/input.d.ts +2 -2
- package/dist/input.js +50 -27
- package/dist/instruction-injection.d.ts +1 -1
- package/dist/middleware.js +9 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.js +3 -0
- package/dist/node/agent-definitions.js +16 -8
- package/dist/node/contribution-discovery.d.ts +1 -2
- package/dist/node/contribution-discovery.js +3 -3
- package/dist/node/session-store-jsonl.js +10 -7
- package/dist/node/settings.d.ts +1 -1
- package/dist/node/settings.js +1 -1
- package/dist/node/system-project-prompts.js +2 -4
- package/dist/node/trust.js +1 -1
- package/dist/persistence-lifecycle.js +1 -3
- package/dist/provider-events.js +3 -1
- package/dist/provider-request-policy.js +3 -4
- package/dist/providers/media.d.ts +1 -1
- package/dist/providers/openai-compatible.d.ts +42 -1
- package/dist/providers/openai-compatible.js +110 -49
- package/dist/providers/openai-primitives.js +7 -7
- package/dist/providers/transport.d.ts +6 -0
- package/dist/providers/transport.js +21 -0
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +3 -0
- package/dist/redaction.d.ts +1 -0
- package/dist/redaction.js +26 -9
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +2 -2
- package/dist/retry.d.ts +5 -0
- package/dist/retry.js +8 -1
- package/dist/rpc.js +42 -9
- package/dist/run-ledger.d.ts +6 -0
- package/dist/run-ledger.js +16 -13
- package/dist/run-limits.js +49 -10
- package/dist/secure-agent.js +1 -1
- package/dist/security.js +7 -2
- package/dist/session-stores.d.ts +1 -1
- package/dist/session-stores.js +28 -24
- package/dist/structured-output.js +2 -2
- package/dist/system-prompts.js +7 -2
- package/dist/testing/compaction-conformance.js +5 -1
- package/dist/testing/extension-conformance.js +15 -3
- package/dist/testing/feedback.d.ts +1 -3
- package/dist/testing/feedback.js +1 -1
- package/dist/testing/persistence-schema.js +206 -37
- package/dist/testing/provider-conformance.js +3 -3
- package/dist/testing/run-ledger-conformance.js +1 -1
- package/dist/testing/session-store-conformance.js +1 -1
- package/dist/testing/tool-conformance.js +30 -5
- package/dist/thinking.js +4 -1
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +24 -5
- package/docs/0.1.0-readiness.md +139 -0
- package/docs/agent-events.md +2 -1
- package/docs/agent-session-runtime.md +2 -2
- package/docs/cli-rpc.md +1 -5
- package/docs/coding-agent-tools.md +2 -0
- package/docs/compaction-and-retry.md +3 -1
- package/docs/contribution-registries.md +1 -0
- package/docs/credentials-and-redaction.md +1 -1
- package/docs/extensions.md +1 -1
- package/docs/guardrails.md +13 -2
- package/docs/index.md +4 -2
- package/docs/input-and-prompt-assembly.md +3 -3
- package/docs/middleware-hooks.md +2 -2
- package/docs/migration.md +40 -0
- package/docs/performance.md +33 -0
- package/docs/providers/openai-compatible.md +28 -1
- package/docs/public-contracts.md +2 -2
- package/docs/release-and-install.md +127 -17
- package/docs/session-stores.md +1 -1
- package/package.json +14 -6
- package/docs/review-coverage-2026-07-14.md +0 -260
- package/docs/review-coverage-2026-07-15.md +0 -193
- package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
- package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
- package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
- package/docs/review-coverage-2026-07-21-phase-5.md +0 -172
- package/docs/review-coverage-2026-07-22-phase-6.md +0 -209
- package/docs/review-coverage-2026-07-22-phase-7.md +0 -173
- package/docs/review-coverage-2026-07-23-phase-8.md +0 -245
- package/docs/review-coverage-2026-07-25-phase-9.md +0 -256
- package/docs/review-coverage-2026-07-26-phase-10.md +0 -132
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.17] - 2026-07-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Extension lifecycle: `ExtensionKernel.load()` returns `LoadedExtension[]` dispose handles; contribution/provider/model registries gain `unregister(...)`; a failed `setup` unwinds its partial registrations.
|
|
7
|
+
- `MemoryCredentialStoreOptions.allowProviderFallback` for strict provider-scoped credential resolution; `createMemoryCheckpointStore` `maxRecords`/`maxValueBytes` bounds; `ShellToolOptions.envAllowlist` (coding-agent); `ErrorInfo.retryAfterMs` plus `retryAfterMs`-aware `createDefaultRetryPolicy` with `jitter`/`random` options; guardrail `steer_rejected` event; `httpStatusError` provider transport helper wired into anthropic, google, kimi, openai, opencode-go, and the shared OpenAI-compatible transport.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Durable runs: run-state load now bounds against the 1 MiB hard cap (states saved with a raised `maxStateBytes` resume correctly); agent fingerprint also covers instructions, system-prompt contributions, and skills; resume-after-interrupt is explicit implicit-approval.
|
|
11
|
+
- Retry/backpressure: HTTP provider errors carry numeric codes and `Retry-After` hints; default retry policy applies ±25% jitter.
|
|
12
|
+
- `input_assembly` middleware runs unconditionally (both plain and context-budget paths, any `InputBuilder`); memory session store rejects cross-session `expectedParentId`; context-budget eviction is O(n) instead of O(n²).
|
|
13
|
+
- Guardrails: `interrupt` errors name the stage; `guardrail_failed` records carry the underlying error message in `metadata.error`; steer `block`/`tripwire` drops the message and emits `steer_rejected` instead of failing the run.
|
|
14
|
+
- Default prompt builder omits the `Available tools:` text for tool-capable models (`capabilities.tools === true`).
|
|
15
|
+
- Middleware registry throws on double `next()` and diagnoses conflicting `next(v)` + return; event multiplexer keeps sorted delivery while a consumer is parked; batched run-ledger dead counters removed.
|
|
16
|
+
|
|
17
|
+
### Breaking (minor, pre-1.0)
|
|
18
|
+
- CLI: `--config`, `--resource`, `--extension`, `--tool` are rejected (`<flag> is not supported in this build`); the dead `CliOptions.config/resources/extensions/tools` fields are removed.
|
|
19
|
+
- `ExtensionKernel.load()` resolves to `LoadedExtension[]` instead of `void`.
|
|
20
|
+
|
|
21
|
+
See [docs/migration.md](docs/migration.md) for the full 0.0.16 → 0.0.17 notes.
|
|
22
|
+
|
|
23
|
+
## [0.0.16] - 2026-07-26
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- Phase 11 simplification/readiness: new public export `resolveRedactor` from `@arnilo/prism` (single survivor of four private copies across evals/memory/rag/workflows) and a new internal `@arnilo/prism-session-store-codecs` package (shared SQLite/Postgres row codecs, not enrolled in any profile family), bringing the exact graph to **44 publishable manifests**.
|
|
27
|
+
- Offline pre-publish release gates: `npm run release:gate` (API-surface `.d.ts` diff vs `scripts/compat-baseline/`, tarball deny-list, exact version ranges), wired into `npm run sdk:ready`.
|
|
28
|
+
- Performance budgets in `scripts/budgets.json`, enforced by `scripts/budget-gate.test.mjs` (in `npm test`) and `scripts/benchmark-0.0.16.mjs`.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
- Dropped historical `docs/review-coverage-*.md` from the root tarball (11 files, ~283 KB): packed size 659,478 → ≈575,680 bytes, 281 → 270 files.
|
|
32
|
+
- All six profiles (`prism-all`, `prism-base`, `prism-code`, `prism-compaction`, `prism-providers`, `prism-sdk`) retained on adoption evidence; zero retirements. No runtime behavior changes.
|
|
33
|
+
|
|
3
34
|
## [0.0.15] - 2026-07-26
|
|
4
35
|
|
|
5
36
|
### Added
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAgent } from "./agents.js";
|
|
2
|
-
import {
|
|
2
|
+
import { createSkillRegistry, resolveActiveSkills } from "./skills.js";
|
|
3
3
|
import { createToolRegistry } from "./tools.js";
|
|
4
4
|
/** Resolve an {@link AgentDefinition} into a runnable {@link Agent}.
|
|
5
5
|
*
|
|
@@ -96,8 +96,7 @@ function hasList(value) {
|
|
|
96
96
|
return typeof value === "object" && value !== null && "list" in value && typeof value.list === "function";
|
|
97
97
|
}
|
|
98
98
|
function resolveSkills(names, tools, context) {
|
|
99
|
-
const registry = context.skillsRegistry ??
|
|
100
|
-
(context.registries?.skills ? createSkillRegistry(context.registries.skills.list()) : undefined);
|
|
99
|
+
const registry = context.skillsRegistry ?? (context.registries?.skills ? createSkillRegistry(context.registries.skills.list()) : undefined);
|
|
101
100
|
if (!names && !context.activateAllCapabilities)
|
|
102
101
|
return undefined;
|
|
103
102
|
if (!registry) {
|
package/dist/agent-loops.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { inputMessages } from "./input.js";
|
|
2
1
|
import { createId } from "./ids.js";
|
|
2
|
+
import { inputMessages } from "./input.js";
|
|
3
3
|
import { artifactStructuredOutputRequest, withoutStructuredOutput } from "./structured-output.js";
|
|
4
4
|
function throwIfAborted(signal) {
|
|
5
5
|
if (signal.aborted)
|
|
@@ -8,7 +8,10 @@ function throwIfAborted(signal) {
|
|
|
8
8
|
function toolResultMessage(result) {
|
|
9
9
|
return {
|
|
10
10
|
role: "tool",
|
|
11
|
-
content: [
|
|
11
|
+
content: [
|
|
12
|
+
{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error },
|
|
13
|
+
...(result.content ?? []),
|
|
14
|
+
],
|
|
12
15
|
metadata: result.metadata,
|
|
13
16
|
};
|
|
14
17
|
}
|
|
@@ -121,7 +124,11 @@ export function generateValidateReviseLoop(opts) {
|
|
|
121
124
|
continue;
|
|
122
125
|
}
|
|
123
126
|
if (toolRounds >= ctx.maxToolRounds) {
|
|
124
|
-
const result = {
|
|
127
|
+
const result = {
|
|
128
|
+
ok: false,
|
|
129
|
+
errors: [{ message: "maximum tool rounds exceeded" }],
|
|
130
|
+
metadata: { reason: "tool_round_limit" },
|
|
131
|
+
};
|
|
125
132
|
ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt: attempts + 1, result });
|
|
126
133
|
return usage;
|
|
127
134
|
}
|
|
@@ -166,7 +173,7 @@ export function generateValidateReviseLoop(opts) {
|
|
|
166
173
|
: undefined;
|
|
167
174
|
const attempt = ++attempts;
|
|
168
175
|
ctx.emit({ type: "artifact_validation_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt });
|
|
169
|
-
const result = parseFailure ?? await opts.validator(parsed.value, artifactCtx);
|
|
176
|
+
const result = parseFailure ?? (await opts.validator(parsed.value, artifactCtx));
|
|
170
177
|
ctx.emit({ type: "artifact_validation_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
171
178
|
if (result.ok) {
|
|
172
179
|
ctx.emit({ type: "artifact_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
@@ -213,9 +220,7 @@ export async function dispatchToolCallsInOrder(calls, ctx) {
|
|
|
213
220
|
if (calls.length === 0)
|
|
214
221
|
return;
|
|
215
222
|
ctx.chargeToolRound?.(calls);
|
|
216
|
-
const concurrency = calls.some((call) => ctx.isToolCallExclusive?.(call))
|
|
217
|
-
? 1
|
|
218
|
-
: Math.max(1, Math.min(ctx.toolConcurrency, calls.length));
|
|
223
|
+
const concurrency = calls.some((call) => ctx.isToolCallExclusive?.(call)) ? 1 : Math.max(1, Math.min(ctx.toolConcurrency, calls.length));
|
|
219
224
|
if (concurrency === 1) {
|
|
220
225
|
for (const call of calls) {
|
|
221
226
|
const result = await ctx.dispatchToolCall(call);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { Agent, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, OwnershipScope, SubscribeOptions } from "./contracts.js";
|
|
2
|
-
import type { CheckpointStore } from "./contracts.js";
|
|
1
|
+
import type { Agent, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, CheckpointStore, OwnershipScope, SubscribeOptions } from "./contracts.js";
|
|
3
2
|
export interface AgentRunLifecycleAgent {
|
|
4
3
|
readonly agent: Agent;
|
|
5
4
|
/** Current host-authored revision; it must match the stored revision. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AgentRunStateError } from "./contracts.js";
|
|
2
1
|
import { loadAgentRunState, publicState } from "./agent-run-state.js";
|
|
3
2
|
import { resumeAgentRun, resumeAgentRunStream } from "./agents.js";
|
|
3
|
+
import { AgentRunStateError } from "./contracts.js";
|
|
4
4
|
function assertAgentId(actual, expected) {
|
|
5
5
|
if (expected !== undefined && actual !== expected)
|
|
6
6
|
throw new AgentRunStateError("Agent run capability mismatch");
|
package/dist/agent-run-state.js
CHANGED
|
@@ -9,19 +9,30 @@ const MAX_PROPERTIES = 256;
|
|
|
9
9
|
export function agentFingerprint(agent, revision) {
|
|
10
10
|
const config = agent.config;
|
|
11
11
|
const tools = !config.tools ? [] : "list" in config.tools ? config.tools.list() : config.tools;
|
|
12
|
+
const skills = !config.skills ? [] : "list" in config.skills ? config.skills.list() : config.skills;
|
|
12
13
|
const guardrails = [
|
|
13
14
|
...(config.guardrails?.input ?? []),
|
|
14
15
|
...(config.guardrails?.output ?? []),
|
|
15
16
|
...(config.guardrails?.toolInput ?? []),
|
|
16
17
|
...(config.guardrails?.toolOutput ?? []),
|
|
17
18
|
];
|
|
19
|
+
const systemPrompt = config.systemPrompt === false || config.systemPrompt === undefined
|
|
20
|
+
? (config.systemPrompt ?? null)
|
|
21
|
+
: (Array.isArray(config.systemPrompt) ? config.systemPrompt : [config.systemPrompt]).map((c) => ({ id: c.id, text: c.text }));
|
|
18
22
|
const value = JSON.stringify({
|
|
19
23
|
id: config.id ?? config.name ?? "agent",
|
|
20
24
|
revision,
|
|
21
25
|
model: config.model,
|
|
26
|
+
// Instructions/prompt text shapes agent behavior as much as the tool set; a change
|
|
27
|
+
// without a definitionRevision bump must not resume stale durable runs silently.
|
|
28
|
+
instructions: config.instructions ?? null,
|
|
29
|
+
systemPrompt,
|
|
30
|
+
skills: skills.map((skill) => ({ name: skill.name, instructions: skill.instructions, toolNames: skill.toolNames })),
|
|
22
31
|
tools: tools.map((tool) => ({ name: tool.name, parameters: tool.parameters, exclusive: tool.exclusive })),
|
|
23
32
|
guardrails: guardrails.map((guardrail) => ({ name: guardrail.name, stage: guardrail.stage, revision: guardrail.revision })),
|
|
24
|
-
loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop
|
|
33
|
+
loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop
|
|
34
|
+
? config.loop.strategy
|
|
35
|
+
: (config.loop?.name ?? "single-shot"),
|
|
25
36
|
});
|
|
26
37
|
return createHash("sha256").update(value).digest("hex");
|
|
27
38
|
}
|
|
@@ -43,7 +54,10 @@ export async function loadAgentRunState(checkpoints, ref, ownership) {
|
|
|
43
54
|
const record = await checkpoints.loadCheckpoint({ namespace: AGENT_RUN_STATE_NAMESPACE, key: ref.runId, ...ownership });
|
|
44
55
|
if (!record)
|
|
45
56
|
throw new AgentRunStateError(`No durable agent run ${ref.runId}`);
|
|
46
|
-
if (ref.sessionId &&
|
|
57
|
+
if (ref.sessionId &&
|
|
58
|
+
record.value &&
|
|
59
|
+
typeof record.value === "object" &&
|
|
60
|
+
record.value.sessionId !== ref.sessionId) {
|
|
47
61
|
throw new AgentRunStateError("Agent run session mismatch");
|
|
48
62
|
}
|
|
49
63
|
return { record, state: parseAgentRunState(record.value, record.version) };
|
|
@@ -95,10 +109,21 @@ export function parseAgentRunState(value, version) {
|
|
|
95
109
|
const state = value;
|
|
96
110
|
if (state.schemaVersion !== AGENT_RUN_STATE_SCHEMA_VERSION)
|
|
97
111
|
throw new AgentRunStateError(`Unsupported agent run state schemaVersion ${String(state.schemaVersion)}`);
|
|
98
|
-
if (!state.agentId ||
|
|
112
|
+
if (!state.agentId ||
|
|
113
|
+
!state.definitionRevision ||
|
|
114
|
+
!state.fingerprint ||
|
|
115
|
+
!state.runId ||
|
|
116
|
+
!state.sessionId ||
|
|
117
|
+
!state.model ||
|
|
118
|
+
!state.status ||
|
|
119
|
+
!state.counters ||
|
|
120
|
+
!state.deadlineAt) {
|
|
99
121
|
throw new AgentRunStateError("Malformed agent run state");
|
|
100
122
|
}
|
|
101
|
-
|
|
123
|
+
// Load bounds against the hard cap, not the default: the configured maxStateBytes is a
|
|
124
|
+
// save-side policy knob, while the load-side bound is only a DoS ceiling. States saved
|
|
125
|
+
// with a raised maxStateBytes must remain resumable.
|
|
126
|
+
return boundState({ ...state, version }, HARD_MAX_AGENT_RUN_STATE_BYTES);
|
|
102
127
|
}
|
|
103
128
|
function boundState(state, maxBytes) {
|
|
104
129
|
checkShape(state, 0);
|
package/dist/agents.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Agent, AgentConfig, AgentEvent, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunResumeStreamOptions,
|
|
1
|
+
import type { Agent, AgentConfig, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunResumeStreamOptions, AgentSession, AgentSessionConfig } from "./contracts.js";
|
|
2
2
|
export declare function createAgent(config: AgentConfig): Agent;
|
|
3
3
|
export declare function createAgentSession(config: AgentSessionConfig & {
|
|
4
4
|
readonly agent: Agent;
|
package/dist/agents.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { AgentRunError, AgentRunStateError, DEFAULT_MAX_PENDING_STEER_BYTES, DEFAULT_MAX_PENDING_STEERS, } from "./contracts.js";
|
|
2
1
|
import { resolveLoop, resolveToolConcurrency } from "./agent-loops.js";
|
|
3
|
-
import {
|
|
4
|
-
import { assertGuardrailsAllowed, GuardrailError, runGuardrails } from "./guardrails.js";
|
|
5
|
-
import { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
|
|
2
|
+
import { agentFingerprint, initialAgentRunState, loadAgentRunState, publicState, saveAgentRunState, validateRunStateOptions, } from "./agent-run-state.js";
|
|
6
3
|
import { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
|
|
4
|
+
import { AgentRunError, AgentRunStateError, DEFAULT_MAX_PENDING_STEER_BYTES, DEFAULT_MAX_PENDING_STEERS } from "./contracts.js";
|
|
5
|
+
import { assertGuardrailsAllowed, GuardrailError, runGuardrails } from "./guardrails.js";
|
|
6
|
+
import { identityTelemetryAttributes, ownershipFromIdentity, resolveRunIdentity } from "./identity.js";
|
|
7
|
+
import { createId } from "./ids.js";
|
|
7
8
|
import { assembleProviderInput } from "./input.js";
|
|
9
|
+
import { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
|
|
8
10
|
import { providerToolCallDeltaContent, reconstructToolCallDeltas } from "./provider-events.js";
|
|
9
11
|
import { createProviderRequestPolicyChain, normalizeProviderRequestPolicyResult } from "./provider-request-policy.js";
|
|
10
|
-
import {
|
|
11
|
-
import { errorToErrorInfo, redactAgentEvent, redactProviderRequest, redactRunLedgerRecord, redactSecrets, redactSessionEntry } from "./redaction.js";
|
|
12
|
-
import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
|
|
12
|
+
import { errorToErrorInfo, redactAgentEvent, redactProviderRequest, redactRunLedgerRecord, redactSecrets, redactSessionEntry, } from "./redaction.js";
|
|
13
13
|
import { createDefaultRetryPolicy, waitForRetry } from "./retry.js";
|
|
14
|
-
import { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, rebuildSessionContext } from "./session-stores.js";
|
|
15
14
|
import { isFlushableRunLedger } from "./run-ledger.js";
|
|
16
|
-
import { createToolRegistry, dispatchToolCall } from "./tools.js";
|
|
17
15
|
import { RunLimitError, RunLimitTracker, resolveRunLimits } from "./run-limits.js";
|
|
18
|
-
import {
|
|
16
|
+
import { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, rebuildSessionContext, } from "./session-stores.js";
|
|
19
17
|
import { resolveActiveSkills } from "./skills.js";
|
|
20
|
-
import {
|
|
18
|
+
import { assertStructuredOutputRequestSupported, resolveRunProviderOptions } from "./structured-output.js";
|
|
19
|
+
import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
|
|
20
|
+
import { createToolRegistry, dispatchToolCall } from "./tools.js";
|
|
21
21
|
export function createAgent(config) {
|
|
22
22
|
return {
|
|
23
23
|
config,
|
|
@@ -39,7 +39,9 @@ export async function* resumeAgentRunStream(agent, ref, resume, options) {
|
|
|
39
39
|
const prepared = await prepareAgentRunResume(agent, ref, resume, options, options.signal);
|
|
40
40
|
const subscription = prepared.session.subscribe(options);
|
|
41
41
|
let settled = false;
|
|
42
|
-
const runPromise = executePreparedAgentRunResume(prepared, options.signal).finally(() => {
|
|
42
|
+
const runPromise = executePreparedAgentRunResume(prepared, options.signal).finally(() => {
|
|
43
|
+
settled = true;
|
|
44
|
+
});
|
|
43
45
|
try {
|
|
44
46
|
for await (const event of subscription) {
|
|
45
47
|
if ("runId" in event && event.runId !== ref.runId)
|
|
@@ -58,7 +60,9 @@ export async function* resumeAgentRunStream(agent, ref, resume, options) {
|
|
|
58
60
|
async function prepareAgentRunResume(agent, ref, resume, options, signal) {
|
|
59
61
|
throwIfAbortedSignal(signal);
|
|
60
62
|
const { record, state } = await loadAgentRunState(options.checkpoints, ref, options.ownership);
|
|
61
|
-
if (state.definitionRevision !== options.definitionRevision ||
|
|
63
|
+
if (state.definitionRevision !== options.definitionRevision ||
|
|
64
|
+
state.agentId !== (agent.config.id ?? agent.config.name) ||
|
|
65
|
+
state.fingerprint !== agentFingerprint(agent, options.definitionRevision)) {
|
|
62
66
|
throw new AgentRunStateError("Agent definition revision or fingerprint mismatch on resume");
|
|
63
67
|
}
|
|
64
68
|
if (record.version !== resume.expectedVersion || state.status !== "suspended") {
|
|
@@ -211,7 +215,11 @@ class RuntimeAgentSession {
|
|
|
211
215
|
}
|
|
212
216
|
}
|
|
213
217
|
async resumeDurable(state, runState, ownership, signal) {
|
|
214
|
-
return this.runInternal(state.input ?? [], { runState, ownership, signal }, state.runId, {
|
|
218
|
+
return this.runInternal(state.input ?? [], { runState, ownership, signal }, state.runId, {
|
|
219
|
+
options: runState,
|
|
220
|
+
state,
|
|
221
|
+
version: state.version,
|
|
222
|
+
});
|
|
215
223
|
}
|
|
216
224
|
async recordDurableDenial(runId, interruption, version, ownership) {
|
|
217
225
|
this.activeLedger = this.agent.config.runLedger;
|
|
@@ -229,7 +237,11 @@ class RuntimeAgentSession {
|
|
|
229
237
|
}
|
|
230
238
|
}
|
|
231
239
|
async runInternal(input, options, runId, resumed) {
|
|
232
|
-
if (this.agent.config.secure &&
|
|
240
|
+
if (this.agent.config.secure &&
|
|
241
|
+
(options.redactor !== undefined ||
|
|
242
|
+
options.ownership !== undefined ||
|
|
243
|
+
options.validate !== undefined ||
|
|
244
|
+
options.runState !== undefined)) {
|
|
233
245
|
throw new AgentRunStateError("Secure agent defaults cannot be replaced per run");
|
|
234
246
|
}
|
|
235
247
|
const requestedLimits = options.maxToolRounds === undefined
|
|
@@ -316,7 +328,14 @@ class RuntimeAgentSession {
|
|
|
316
328
|
const { registry, tools } = activeTools(this.agent.config.tools);
|
|
317
329
|
const activeSkills = this.resolveRunSkills(options, tools);
|
|
318
330
|
if (options.model && JSON.stringify(options.model) !== JSON.stringify(this.agent.config.model)) {
|
|
319
|
-
await this.appendEntry(createSessionEntry({
|
|
331
|
+
await this.appendEntry(createSessionEntry({
|
|
332
|
+
sessionId: this.id,
|
|
333
|
+
parentId: this.currentLeafId,
|
|
334
|
+
runId,
|
|
335
|
+
kind: "model_change",
|
|
336
|
+
previousModel: this.agent.config.model,
|
|
337
|
+
model: options.model,
|
|
338
|
+
}));
|
|
320
339
|
}
|
|
321
340
|
const inputMessages = inputToMessages(input).map((message) => this.redact(message));
|
|
322
341
|
const inputGuardrails = await runGuardrails({
|
|
@@ -327,20 +346,24 @@ class RuntimeAgentSession {
|
|
|
327
346
|
redactor: this.activeRedactor,
|
|
328
347
|
emit: (event) => this.emit(event),
|
|
329
348
|
});
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
349
|
+
// Input-guardrail decision table:
|
|
350
|
+
// - interrupt + durable + fresh run → suspend for approval.
|
|
351
|
+
// - interrupt + durable + resumed run → proceed: resuming IS the operator approval.
|
|
352
|
+
// - interrupt without durable, or block/tripwire → fail via assertGuardrailsAllowed.
|
|
353
|
+
const approvedByResume = resumed !== undefined && inputGuardrails.terminal?.action === "interrupt" && this.activeDurable !== undefined;
|
|
354
|
+
if (inputGuardrails.terminal?.action === "interrupt" && this.activeDurable && !approvedByResume) {
|
|
355
|
+
const interruption = { kind: "input_guardrail", reason: inputGuardrails.terminal.reason ?? "Input requires approval" };
|
|
356
|
+
throw new AgentRunSuspended(await this.suspendDurable({ runId, model, limits, interruption, messages: inputMessages }), interruption);
|
|
335
357
|
}
|
|
336
|
-
|
|
358
|
+
if (inputGuardrails.terminal && !approvedByResume)
|
|
337
359
|
assertGuardrailsAllowed(inputGuardrails);
|
|
338
|
-
}
|
|
339
360
|
for (const message of inputMessages)
|
|
340
361
|
await this.appendMessage(message, runId);
|
|
341
362
|
await this.autoCompact(runId, options, controller.signal, inputMessages);
|
|
342
363
|
const maxToolRounds = resolvedLimits.maxToolRounds;
|
|
343
|
-
const systemInstructions = composeSystemPrompt(mergeSystemPromptConfig(this.agent.config.systemPrompt, options.systemPrompt), {
|
|
364
|
+
const systemInstructions = composeSystemPrompt(mergeSystemPromptConfig(this.agent.config.systemPrompt, options.systemPrompt), {
|
|
365
|
+
base: this.agent.config.instructions,
|
|
366
|
+
});
|
|
344
367
|
const contextProviders = [
|
|
345
368
|
...(this.agent.config.context ?? []),
|
|
346
369
|
// ponytail: skill context after host context; no per-skill token budget yet.
|
|
@@ -429,7 +452,7 @@ class RuntimeAgentSession {
|
|
|
429
452
|
limits.charge("maxTurns");
|
|
430
453
|
assembledTurn = false;
|
|
431
454
|
const policyResult = await this.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
|
|
432
|
-
const middlewareRequest = await this.agent.config.middleware?.run("provider_request", policyResult.request) ?? policyResult.request;
|
|
455
|
+
const middlewareRequest = (await this.agent.config.middleware?.run("provider_request", policyResult.request)) ?? policyResult.request;
|
|
433
456
|
try {
|
|
434
457
|
return await this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, this.activeLoopTurn, recordProviderUsage);
|
|
435
458
|
}
|
|
@@ -468,12 +491,22 @@ class RuntimeAgentSession {
|
|
|
468
491
|
return;
|
|
469
492
|
const pending = durable.state?.pending;
|
|
470
493
|
if (pending?.call.id === mediatedCall.id && pending.status === "ready") {
|
|
471
|
-
await this.persistDurable({
|
|
494
|
+
await this.persistDurable({
|
|
495
|
+
...durable.state,
|
|
496
|
+
status: "running",
|
|
497
|
+
pending: { ...pending, status: "dispatched" },
|
|
498
|
+
interruption: undefined,
|
|
499
|
+
});
|
|
472
500
|
return;
|
|
473
501
|
}
|
|
474
502
|
if (!durable.options.interruptBeforeTool)
|
|
475
503
|
return;
|
|
476
|
-
const interruption = {
|
|
504
|
+
const interruption = {
|
|
505
|
+
kind: "tool_approval",
|
|
506
|
+
reason: "Tool side effect requires approval",
|
|
507
|
+
toolCallId: mediatedCall.id,
|
|
508
|
+
toolName: mediatedCall.name,
|
|
509
|
+
};
|
|
477
510
|
throw new AgentRunSuspended(await this.suspendDurable({
|
|
478
511
|
runId,
|
|
479
512
|
model,
|
|
@@ -508,13 +541,19 @@ class RuntimeAgentSession {
|
|
|
508
541
|
const result = await ctx.dispatchToolCall(resumed.state.pending.call);
|
|
509
542
|
await ctx.appendMessage({
|
|
510
543
|
role: "tool",
|
|
511
|
-
content: [
|
|
544
|
+
content: [
|
|
545
|
+
{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error },
|
|
546
|
+
...(result.content ?? []),
|
|
547
|
+
],
|
|
512
548
|
metadata: result.metadata,
|
|
513
549
|
});
|
|
514
550
|
}
|
|
515
551
|
const loopUsage = await loop.run(ctx);
|
|
516
552
|
if (loop.name === "generate-validate-revise" && !artifactFinished) {
|
|
517
|
-
throw Object.assign(new Error(artifactFailedInfo?.message ?? "artifact loop ended without a validated artifact"), {
|
|
553
|
+
throw Object.assign(new Error(artifactFailedInfo?.message ?? "artifact loop ended without a validated artifact"), {
|
|
554
|
+
name: "ArtifactFailed",
|
|
555
|
+
code: artifactFailedInfo?.code ?? "artifact_failed",
|
|
556
|
+
});
|
|
518
557
|
}
|
|
519
558
|
usage = runUsage.value() ?? loopUsage;
|
|
520
559
|
if (usage && this.activeLedger) {
|
|
@@ -661,21 +700,22 @@ class RuntimeAgentSession {
|
|
|
661
700
|
const durable = this.activeDurable;
|
|
662
701
|
if (!durable)
|
|
663
702
|
throw new AgentRunStateError("Durable interruption is not configured");
|
|
664
|
-
const state = durable.state ??
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
703
|
+
const state = durable.state ??
|
|
704
|
+
initialAgentRunState({
|
|
705
|
+
agent: this.agent,
|
|
706
|
+
options: durable.options,
|
|
707
|
+
runId: input.runId,
|
|
708
|
+
sessionId: this.id,
|
|
709
|
+
leafId: this.currentLeafId,
|
|
710
|
+
model: input.model,
|
|
711
|
+
counters: input.limits.snapshot(),
|
|
712
|
+
deadlineAt: input.limits.deadlineAt,
|
|
713
|
+
status: "suspended",
|
|
714
|
+
interruption: input.interruption,
|
|
715
|
+
messages: input.messages,
|
|
716
|
+
pending: input.pending,
|
|
717
|
+
interruptBeforeTool: durable.options.interruptBeforeTool,
|
|
718
|
+
});
|
|
679
719
|
return this.persistDurable({
|
|
680
720
|
...state,
|
|
681
721
|
leafId: this.currentLeafId,
|
|
@@ -723,7 +763,13 @@ class RuntimeAgentSession {
|
|
|
723
763
|
await this.rebuildHistory();
|
|
724
764
|
}
|
|
725
765
|
fork(options = {}) {
|
|
726
|
-
return createAgentSession({
|
|
766
|
+
return createAgentSession({
|
|
767
|
+
agent: this.agent,
|
|
768
|
+
id: this.id,
|
|
769
|
+
store: this.store,
|
|
770
|
+
leafId: options.leafId ?? this.currentLeafId,
|
|
771
|
+
metadata: this.metadata,
|
|
772
|
+
});
|
|
727
773
|
}
|
|
728
774
|
async clone(options = {}) {
|
|
729
775
|
const id = options.id ?? randomId("session");
|
|
@@ -739,7 +785,13 @@ class RuntimeAgentSession {
|
|
|
739
785
|
const { id: _oldId, parentId: _oldParentId, sessionId: _oldSessionId, ...rest } = entry;
|
|
740
786
|
await this.store.append({ ...rest, id: nextId, parentId: entry.parentId ? remap.get(entry.parentId) : undefined, sessionId: id });
|
|
741
787
|
}
|
|
742
|
-
return createAgentSession({
|
|
788
|
+
return createAgentSession({
|
|
789
|
+
agent: this.agent,
|
|
790
|
+
id,
|
|
791
|
+
store: this.store,
|
|
792
|
+
leafId: branch.length ? remap.get(branch[branch.length - 1].id) : undefined,
|
|
793
|
+
metadata: this.metadata,
|
|
794
|
+
});
|
|
743
795
|
}
|
|
744
796
|
branchReader() {
|
|
745
797
|
// ponytail: prefer the store's readBranchPath (one ancestor-chain query) when present so a
|
|
@@ -753,9 +805,7 @@ class RuntimeAgentSession {
|
|
|
753
805
|
// the resolver entirely; otherwise `RunOptions.providerSource` overrides
|
|
754
806
|
// `AgentConfig.providerSource` for this run. A miss on every source fails
|
|
755
807
|
// closed with `Unknown provider: ${model.provider}` before any provider turn.
|
|
756
|
-
const provider = this.agent.config.provider ??
|
|
757
|
-
options.providerSource?.(model) ??
|
|
758
|
-
this.agent.config.providerSource?.(model);
|
|
808
|
+
const provider = this.agent.config.provider ?? options.providerSource?.(model) ?? this.agent.config.providerSource?.(model);
|
|
759
809
|
if (!provider)
|
|
760
810
|
throw new Error(`Unknown provider: ${model.provider}`);
|
|
761
811
|
this.activeProvider = provider;
|
|
@@ -828,7 +878,10 @@ class RuntimeAgentSession {
|
|
|
828
878
|
throw errorFromInfo(info);
|
|
829
879
|
const context = { sessionId: this.id, runId, attempt, error: info, metadata: retry?.metadata, signal };
|
|
830
880
|
let decision = await policy.decide(context);
|
|
831
|
-
const payload = await this.agent.config.middleware?.run("retry", { context, decision }) ?? {
|
|
881
|
+
const payload = (await this.agent.config.middleware?.run("retry", { context, decision })) ?? {
|
|
882
|
+
context,
|
|
883
|
+
decision,
|
|
884
|
+
};
|
|
832
885
|
decision = payload.decision;
|
|
833
886
|
if (!decision.retry)
|
|
834
887
|
throw errorFromInfo(info);
|
|
@@ -998,7 +1051,22 @@ class RuntimeAgentSession {
|
|
|
998
1051
|
redactor: this.activeRedactor,
|
|
999
1052
|
emit: (event) => this.emit(event),
|
|
1000
1053
|
});
|
|
1001
|
-
|
|
1054
|
+
// Mid-run steer: a terminal decision drops the message (never enters history or
|
|
1055
|
+
// the session store) and the run continues. Run-start input blocking still fails
|
|
1056
|
+
// the run — only the blast radius of steered input is narrowed.
|
|
1057
|
+
const terminal = inputGuardrails.terminal;
|
|
1058
|
+
if (terminal) {
|
|
1059
|
+
if (terminal.action === "interrupt")
|
|
1060
|
+
throw new GuardrailError(terminal);
|
|
1061
|
+
this.emit({
|
|
1062
|
+
type: "steer_rejected",
|
|
1063
|
+
sessionId: this.id,
|
|
1064
|
+
runId,
|
|
1065
|
+
message: this.activeRedactor ? this.activeRedactor.redact(message) : message,
|
|
1066
|
+
record: terminal,
|
|
1067
|
+
});
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1002
1070
|
this.history.push(message);
|
|
1003
1071
|
await this.appendMessage(message, runId);
|
|
1004
1072
|
}
|
|
@@ -1029,16 +1097,35 @@ class RuntimeAgentSession {
|
|
|
1029
1097
|
throwIfAbortedSignal(signal);
|
|
1030
1098
|
const entries = await this.entries();
|
|
1031
1099
|
const secrets = options.secrets ?? [];
|
|
1032
|
-
const strategy = options.strategy ??
|
|
1033
|
-
|
|
1100
|
+
const strategy = options.strategy ??
|
|
1101
|
+
createDefaultCompactionStrategy({ keepRecentEntries: options.keepRecentEntries, maxSummaryChars: options.maxSummaryChars, secrets });
|
|
1102
|
+
const context = {
|
|
1103
|
+
sessionId: this.id,
|
|
1104
|
+
entries,
|
|
1105
|
+
keepRecentEntries: options.keepRecentEntries,
|
|
1106
|
+
trigger,
|
|
1107
|
+
secrets,
|
|
1108
|
+
metadata: options.metadata,
|
|
1109
|
+
signal,
|
|
1110
|
+
};
|
|
1034
1111
|
this.emit({ type: "compaction_started", sessionId: this.id, runId });
|
|
1035
1112
|
let result = await strategy.compact(context);
|
|
1036
1113
|
result = { ...result, summary: redactSecrets(result.summary, secrets) };
|
|
1037
|
-
const payload = await this.agent.config.middleware?.run("compaction", { context, result }) ?? {
|
|
1114
|
+
const payload = (await this.agent.config.middleware?.run("compaction", { context, result })) ?? {
|
|
1115
|
+
context,
|
|
1116
|
+
result,
|
|
1117
|
+
};
|
|
1038
1118
|
result = { ...payload.result, summary: redactSecrets(payload.result.summary, secrets) };
|
|
1039
1119
|
const source = result.entries?.find((entry) => entry.kind === "compaction");
|
|
1040
1120
|
const data = isCompactionEntryData(source?.data) ? source.data : undefined;
|
|
1041
|
-
const entry = createSessionEntry({
|
|
1121
|
+
const entry = createSessionEntry({
|
|
1122
|
+
sessionId: this.id,
|
|
1123
|
+
parentId: this.currentLeafId,
|
|
1124
|
+
runId,
|
|
1125
|
+
kind: "compaction",
|
|
1126
|
+
summary: result.summary,
|
|
1127
|
+
data,
|
|
1128
|
+
});
|
|
1042
1129
|
await this.appendEntry(entry);
|
|
1043
1130
|
const finalResult = { ...result, entries: [entry] };
|
|
1044
1131
|
this.emit({ type: "compaction_finished", sessionId: this.id, runId, summary: finalResult.summary });
|
|
@@ -1181,8 +1268,8 @@ class SteerSoftInterrupt extends Error {
|
|
|
1181
1268
|
}
|
|
1182
1269
|
}
|
|
1183
1270
|
function isSteerSoftInterrupt(error) {
|
|
1184
|
-
return error instanceof SteerSoftInterrupt
|
|
1185
|
-
|
|
1271
|
+
return (error instanceof SteerSoftInterrupt ||
|
|
1272
|
+
(typeof error === "object" && error !== null && error.code === STEER_SOFT_INTERRUPT_CODE));
|
|
1186
1273
|
}
|
|
1187
1274
|
function finalAssistantMessage(history) {
|
|
1188
1275
|
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
@@ -1254,11 +1341,25 @@ function withoutTrailingInput(messages, input) {
|
|
|
1254
1341
|
const next = [...messages];
|
|
1255
1342
|
for (let i = input.length - 1; i >= 0; i -= 1) {
|
|
1256
1343
|
const last = next.at(-1);
|
|
1257
|
-
if (last &&
|
|
1344
|
+
if (last && stableMessageKey(last) === stableMessageKey(input[i]))
|
|
1258
1345
|
next.pop();
|
|
1259
1346
|
}
|
|
1260
1347
|
return next;
|
|
1261
1348
|
}
|
|
1349
|
+
// Key-order-insensitive comparison: a redacted-then-reassembled message with reordered
|
|
1350
|
+
// keys must still dedupe against the trailing input, or auto-compaction duplicates it.
|
|
1351
|
+
function stableMessageKey(value) {
|
|
1352
|
+
if (Array.isArray(value))
|
|
1353
|
+
return `[${value.map(stableMessageKey).join(",")}]`;
|
|
1354
|
+
if (value !== null && typeof value === "object") {
|
|
1355
|
+
const record = value;
|
|
1356
|
+
return `{${Object.keys(record)
|
|
1357
|
+
.sort()
|
|
1358
|
+
.map((key) => `${JSON.stringify(key)}:${stableMessageKey(record[key])}`)
|
|
1359
|
+
.join(",")}}`;
|
|
1360
|
+
}
|
|
1361
|
+
return JSON.stringify(value) ?? "null";
|
|
1362
|
+
}
|
|
1262
1363
|
function bridgeAbort(signal, controller) {
|
|
1263
1364
|
if (!signal)
|
|
1264
1365
|
return () => undefined;
|
|
@@ -1276,9 +1377,10 @@ function throwIfAbortedSignal(signal) {
|
|
|
1276
1377
|
if (signal?.aborted)
|
|
1277
1378
|
throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
|
|
1278
1379
|
}
|
|
1380
|
+
const jsonTextEncoder = new TextEncoder();
|
|
1279
1381
|
function jsonBytes(value) {
|
|
1280
1382
|
try {
|
|
1281
|
-
return
|
|
1383
|
+
return jsonTextEncoder.encode(JSON.stringify(value)).byteLength;
|
|
1282
1384
|
}
|
|
1283
1385
|
catch {
|
|
1284
1386
|
throw new TypeError("Provider request or event must be JSON-serializable for run limits");
|
|
@@ -1295,8 +1397,8 @@ function createUsageAccumulator() {
|
|
|
1295
1397
|
if (value !== undefined)
|
|
1296
1398
|
sums.set(key, (sums.get(key) ?? 0) + value);
|
|
1297
1399
|
}
|
|
1298
|
-
const total = usage.totalTokens
|
|
1299
|
-
|
|
1400
|
+
const total = usage.totalTokens ??
|
|
1401
|
+
(usage.inputTokens !== undefined || usage.outputTokens !== undefined
|
|
1300
1402
|
? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)
|
|
1301
1403
|
: undefined);
|
|
1302
1404
|
if (total !== undefined)
|