@arnilo/prism 0.1.3 → 0.1.5

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/agent-approval.d.ts +49 -0
  3. package/dist/agent-approval.js +178 -0
  4. package/dist/agent-run-lifecycle.d.ts +5 -1
  5. package/dist/agent-run-lifecycle.js +211 -3
  6. package/dist/agent-session.d.ts +98 -0
  7. package/dist/agent-session.js +1811 -0
  8. package/dist/agent-tool-dispatch.d.ts +13 -0
  9. package/dist/agent-tool-dispatch.js +92 -0
  10. package/dist/agents.d.ts +16 -9
  11. package/dist/agents.js +14 -2267
  12. package/dist/cli-init.d.ts +0 -2
  13. package/dist/cli-init.js +0 -2
  14. package/dist/cli-runner.js +1 -1
  15. package/dist/contracts-core.d.ts +1396 -0
  16. package/dist/contracts-core.js +119 -0
  17. package/dist/contracts-protocol.d.ts +594 -0
  18. package/dist/contracts-protocol.js +2 -0
  19. package/dist/contracts-run-state.d.ts +285 -0
  20. package/dist/contracts-run-state.js +77 -0
  21. package/dist/contracts.d.ts +7 -2269
  22. package/dist/contracts.js +7 -192
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/rpc.js +2 -1
  26. package/docs/agent-loops.md +1 -1
  27. package/docs/agent-session-runtime.md +4 -2
  28. package/docs/browser-automation.md +13 -9
  29. package/docs/coding-agent-tools.md +1 -2
  30. package/docs/compaction-observational-memory.md +5 -6
  31. package/docs/index.md +4 -4
  32. package/docs/migration.md +114 -1
  33. package/docs/performance.md +10 -0
  34. package/docs/provider-conformance.md +2 -2
  35. package/docs/provider-layer.md +1 -1
  36. package/docs/provider-packages.md +1 -1
  37. package/docs/provider-primitives.md +1 -1
  38. package/docs/public-contracts.md +4 -2
  39. package/docs/release-and-install.md +64 -0
  40. package/docs/tool-execution-primitives.md +3 -3
  41. package/docs/tools.md +2 -2
  42. package/docs/use-case-model-selection.md +12 -11
  43. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.5] - 2026-08-11
4
+
5
+ ### Changed
6
+ - **Release 0.1.5 (plan 017)** is the **documented breaking cut** on the frozen 0.1.x line — deprecated-option removal with the full removed-symbols list, replacements, before/after examples, dynamic-config refusal behavior, store compatibility, and rollback in the top `docs/migration.md` `0.1.4 → 0.1.5` section (three stale roadmap labels corrected there: `RunOptions.maxToolRounds` not `AgentConfig.maxToolRounds`, `autoResizeImages` removed with `transformImage` retained, `INIT_PROVIDERS` removed with `listInitProviders()` retained). (1) **Provider run-option aliases** (Task 1): `ProviderRequestOptions.timeoutMs`/`maxRetries`/`maxRetryDelayMs` (inert in first-party providers; host-side abort/retry is the replacement — `RunOptions.signal`, `AgentConfig.retry`/`RunOptions.retry`) and `RunOptions.maxToolRounds` (→ `limits.maxToolRounds`, defaults/caps unchanged) removed; untyped legacy run input is refused at the `runInternal` choke point before the agent starts; CLI `--max-tool-rounds` maps to the nested limit. (2) **Observational-memory compatibility surface** (Task 2): the 10 pre-0.0.19 flat settings keys and top-level `workerProvider`/`workerModel` aliases removed — settings resolution is nested-only with `assertNoRemovedFlatKeys` failing closed (settings-provider JSON or untyped overrides throw naming the key + nested replacement before any worker/provider call, compaction, or session append); `fallbackWorker`/`assertWorkerModelCompatibility`/`conflict()` deleted; workers resolve only from `observation`/`reflection`/`dropper` configs plus `sessionModel` fallback. (3) **Read alias + CLI constant** (Task 3): `ReadToolOptions.autoResizeImages` removed (`transformImage` is the only resize path; untyped callers fail closed at `createReadTool` before filesystem access) and `INIT_PROVIDERS` removed (`listInitProviders()` is the single provider-list API). Compat baselines regenerated after the reviewed `--allow-break` break report (root `arnilo__prism.txt` drops the `INIT_PROVIDERS` line; interface-member removals are verified by the phase-17 direct declaration scanner, not baseline text). Exit gate green (core tests + script gates incl. phase17-freeze 20/20, `sdk:ready`, audit 0, pack dry-run 49/49 twice byte-identical, evidence in `scripts/phase17-baseline.json`). Store compatibility with 0.1.4: **compatible, no migration** (removed options were inert aliases; rollback = restore the 0.1.4 manifests/tag). **Publication remains the operator handoff** (`docs/release-and-install.md` `0.1.5 publish handoff` — signed `v0.1.5` tag + npm OIDC).
7
+
8
+ ## [0.1.4] - 2026-08-10
9
+
10
+ ### Changed
11
+ - **Release 0.1.4 (plan 016)** is the internal god-module split, compat-preserving on the frozen 0.1.x line. (1) **Contracts split** (Task 1): `src/contracts.ts` (2,549 lines) split by concern into `src/contracts-core.ts`, `src/contracts-run-state.ts`, and `src/contracts-protocol.ts` behind a pure `export *` barrel — the 295-name public surface is unchanged (0 added/0 removed/0 changed vs the 0.1.3 baseline, 702 = 702 at the entry). (2) **Agents split** (Task 2): `src/agents.ts` (2,576 lines) split into `src/agent-session.ts` (`RuntimeAgentSession` + factories + shared helpers), `src/agent-run-lifecycle.ts` (resume lifecycle), `src/agent-approval.ts`, `src/agent-tool-dispatch.ts`, with `agent-run-state.ts`/`agent-loops.ts`/`compaction.ts` reused — `agents.ts` is now a barrel of the four public functions; 14 internal cross-module helper exports joined the union `.d.ts` surface but are not consumer-importable (documented deviation #1, `scripts/phase16-freeze-manifest.json`). (3) **Tree-shake verification** (Task 3): measured in `scripts/phase16-baseline.json` — `dist/agents.js` 111,049 → 982 B, `dist/contracts.js` 9,420 → 418 B, `dist/contracts.d.ts` 98,825 → 1,029 B, dist module count 64 → 70 (static-import reachability reported, not gated). (4) **`@arnilo/prism-browser` CDP capabilities** (Tasks 4-5): additive Chrome DevTools Protocol surface riding playwright-core's existing transport — `browser_evaluate` (bounded `Runtime.evaluate`, policy-gated), `browser_observe` (console/network rings, bodies never captured), `block_urls`/`unblock_urls`/`throttle`/`emulate` act actions, and raw `{ css }`/`{ xpath }` targets; capability-gated via `BrowserCdpOptions.mode` with `ERR_PRISM_BROWSER_CDP_UNAVAILABLE`; documented additive deltas vs the 0.1.3 prism-browser baseline (41 added / 0 removed / 18 changed — 15 statement-text artifacts + 3 optional-member/signature-widening, deviation #2), root `@arnilo/prism` zero deltas. Zero new dependencies across the milestone; exit gate green (npm test core 1,425/1,425 + 151 script gates, `sdk:ready`, audit 0, pack dry-run 49/49 twice byte-identical, tree-shake + benchmark evidence in `scripts/phase16-baseline.json`). Store compatibility with 0.1.3: **compatible, no migration** (rollback = restore the 0.1.3 manifests/tag). **Publication remains the operator handoff** (`docs/release-and-install.md` `0.1.4 publish handoff` — signed `v0.1.4` tag + npm OIDC).
12
+
3
13
  ## [0.1.3] - 2026-08-10
4
14
 
5
15
  ### Changed
@@ -0,0 +1,49 @@
1
+ import type { StoredAgentRunState } from "./agent-run-state.js";
2
+ import { Agent, AgentRunState, AgentRunStateOptions, DecisionScope, NestedRunOutcome, PendingDecision, RunDecision, StickyDecision, ToolResult } from "./contracts.js";
3
+ import type { AgentIdentity } from "./identity.js";
4
+ /** Pending decisions of a suspended state, synthesizing the legacy single-approval shape. */
5
+ export declare function pendingDecisionsOf(state: StoredAgentRunState): readonly PendingDecision[] | undefined;
6
+ interface ResolvedRunDecisions {
7
+ readonly decisionsById: ReadonlyMap<string, RunDecision>;
8
+ readonly stickyDecisions: readonly StickyDecision[];
9
+ readonly remaining: readonly PendingDecision[];
10
+ }
11
+ /**
12
+ * Validate one decision batch against the suspended state. Fail-closed and atomic: any
13
+ * invalid entry rejects the whole batch before any CAS, leaving state and version untouched.
14
+ * Unknown and foreign approval ids share one non-enumerating error.
15
+ */
16
+ export declare function resolveRunDecisions(input: {
17
+ readonly agent: Agent;
18
+ readonly state: StoredAgentRunState;
19
+ readonly decisions: readonly RunDecision[];
20
+ readonly signal?: AbortSignal;
21
+ }): Promise<ResolvedRunDecisions>;
22
+ /**
23
+ * Resolve a tool's declared elicitation contract for a gated call. A throwing hook falls back to
24
+ * plain tool approval: malformed model args then surface as a tool error after approval, never
25
+ * as a run failure at the gate. Output is bounded before it enters the pending-decision record.
26
+ */
27
+ export declare class AgentRunSuspended extends Error {
28
+ readonly state: AgentRunState;
29
+ readonly interruption: import("./contracts.js").AgentRunInterruption;
30
+ readonly code = "ERR_PRISM_AGENT_RUN_SUSPENDED";
31
+ constructor(state: AgentRunState, interruption: import("./contracts.js").AgentRunInterruption);
32
+ }
33
+ /** Root-visible nested approval id: hashed so it stays bounded and non-enumerating at any depth. */
34
+ export declare function nestedApprovalId(runId: string, childApprovalId: string): string;
35
+ export declare function pathsEqual(a: readonly string[] | undefined, b: readonly string[] | undefined): boolean;
36
+ export declare function decisionScopesEqual(a: DecisionScope, b: DecisionScope): boolean;
37
+ export declare function nestedOutcomeToolResult(outcome: Exclude<NestedRunOutcome, {
38
+ status: "suspended";
39
+ }>, toolCallId: string, name: string): ToolResult;
40
+ export interface ActiveDurableRun {
41
+ readonly options: AgentRunStateOptions;
42
+ state?: StoredAgentRunState;
43
+ version: number;
44
+ /** Validated decisions driving the pending-call replay on a resumed run. */
45
+ readonly decisions?: ReadonlyMap<string, RunDecision>;
46
+ }
47
+ /** Compact redacted principal reference used in decision scopes; never a credential. */
48
+ export declare function decisionIdentityRef(identity: AgentIdentity | undefined): string | undefined;
49
+ export {};
@@ -0,0 +1,178 @@
1
+ // Approval / pending-decision helpers split from agents.ts at 0.1.4 (verbatim move; internal, not public API).
2
+ import { createHash } from "node:crypto";
3
+ import { activeTools, validateElicitationPayload } from "./agent-tool-dispatch.js";
4
+ import { AgentDecisionError, DEFAULT_MAX_STICKY_DECISIONS, HARD_MAX_PENDING_DECISIONS, MAX_DECISION_REASON_BYTES, MAX_ELICITATION_BYTES, } from "./contracts.js";
5
+ import { runGuardrails } from "./guardrails.js";
6
+ import { canonicalToolEffectJson } from "./tool-effects.js";
7
+ /** Pending decisions of a suspended state, synthesizing the legacy single-approval shape. */
8
+ export function pendingDecisionsOf(state) {
9
+ if (state.interruption?.pendingDecisions)
10
+ return state.interruption.pendingDecisions;
11
+ if (state.pending) {
12
+ return [
13
+ {
14
+ approvalId: state.pending.call.id,
15
+ kind: "tool_approval",
16
+ toolCallId: state.pending.call.id,
17
+ scope: { toolName: state.pending.call.name },
18
+ reason: state.interruption?.reason ?? "Tool side effect requires approval",
19
+ },
20
+ ];
21
+ }
22
+ return undefined;
23
+ }
24
+ /**
25
+ * Validate one decision batch against the suspended state. Fail-closed and atomic: any
26
+ * invalid entry rejects the whole batch before any CAS, leaving state and version untouched.
27
+ * Unknown and foreign approval ids share one non-enumerating error.
28
+ */
29
+ export async function resolveRunDecisions(input) {
30
+ const { agent, state, decisions } = input;
31
+ if (decisions.length === 0)
32
+ throw new AgentDecisionError("ERR_PRISM_DECISION_INVALID", "Decision batch must not be empty");
33
+ if (decisions.length > HARD_MAX_PENDING_DECISIONS) {
34
+ throw new AgentDecisionError("ERR_PRISM_DECISION_LIMIT", `Decision batch exceeds ${HARD_MAX_PENDING_DECISIONS} entries`);
35
+ }
36
+ const pending = pendingDecisionsOf(state);
37
+ if (!pending?.length)
38
+ throw new AgentDecisionError("ERR_PRISM_DECISION_UNKNOWN", "No pending approval decisions for this run");
39
+ const byId = new Map(pending.map((entry) => [entry.approvalId, entry]));
40
+ const seen = new Set();
41
+ const decisionsById = new Map();
42
+ const stickies = [];
43
+ const decidedAt = new Date().toISOString();
44
+ const { registry } = activeTools(agent.config.tools);
45
+ for (const decision of decisions) {
46
+ if (seen.has(decision.approvalId)) {
47
+ throw new AgentDecisionError("ERR_PRISM_DECISION_DUPLICATE", "Duplicate approval decision in batch");
48
+ }
49
+ seen.add(decision.approvalId);
50
+ const target = byId.get(decision.approvalId);
51
+ if (!target)
52
+ throw new AgentDecisionError("ERR_PRISM_DECISION_UNKNOWN", "Unknown approval decision");
53
+ if (decision.reason !== undefined && Buffer.byteLength(decision.reason, "utf8") > MAX_DECISION_REASON_BYTES) {
54
+ throw new AgentDecisionError("ERR_PRISM_DECISION_LIMIT", `Decision reason exceeds ${MAX_DECISION_REASON_BYTES} bytes`);
55
+ }
56
+ if (decision.outcome !== "allow_once" &&
57
+ decision.outcome !== "allow_for_run" &&
58
+ decision.outcome !== "reject_once" &&
59
+ decision.outcome !== "reject_for_run") {
60
+ throw new AgentDecisionError("ERR_PRISM_DECISION_INVALID", "Unknown approval outcome");
61
+ }
62
+ if (decision.modifiedArguments !== undefined) {
63
+ if (target.kind !== "tool_approval" || !target.toolCallId) {
64
+ throw new AgentDecisionError("ERR_PRISM_DECISION_SCOPE", "Modified arguments apply only to tool approvals");
65
+ }
66
+ await validateModifiedArguments(agent, registry, state, target, decision.modifiedArguments, input.signal);
67
+ }
68
+ if (decision.elicitation !== undefined) {
69
+ if (target.kind !== "elicitation") {
70
+ throw new AgentDecisionError("ERR_PRISM_DECISION_SCOPE", "Elicitation payload applies only to elicitation decisions");
71
+ }
72
+ await validateElicitationPayload(agent, state, target, decision.elicitation, input.signal);
73
+ }
74
+ decisionsById.set(decision.approvalId, decision);
75
+ if (decision.outcome === "allow_for_run" || decision.outcome === "reject_for_run") {
76
+ stickies.push({
77
+ // A decision with modified arguments must not stick to the original arguments hash:
78
+ // the modification is one-off, so the sticky scope matches by name/effect/identity only.
79
+ scope: decision.modifiedArguments !== undefined ? { ...target.scope, argumentsHash: undefined } : target.scope,
80
+ outcome: decision.outcome,
81
+ ...(decision.reason !== undefined ? { reason: decision.reason } : {}),
82
+ // Root-owned sticky scope includes the delegation path for nested decisions.
83
+ ...(target.attribution ? { attribution: target.attribution } : {}),
84
+ decidedAt,
85
+ });
86
+ }
87
+ }
88
+ const stickyDecisions = [...(state.stickyDecisions ?? []), ...stickies];
89
+ if (stickyDecisions.length > DEFAULT_MAX_STICKY_DECISIONS) {
90
+ throw new AgentDecisionError("ERR_PRISM_DECISION_LIMIT", `Sticky decisions exceed ${DEFAULT_MAX_STICKY_DECISIONS} per run`);
91
+ }
92
+ return {
93
+ decisionsById,
94
+ stickyDecisions,
95
+ remaining: pending.filter((entry) => !seen.has(entry.approvalId)),
96
+ };
97
+ }
98
+ /** Decision-time revalidation of modified arguments: schema, then input guardrails. Policy and trust re-run at dispatch. */
99
+ async function validateModifiedArguments(agent, registry, state, target, modified, signal) {
100
+ const invalid = (message, cause) => new AgentDecisionError("ERR_PRISM_DECISION_INVALID", message, { cause });
101
+ if (JSON.stringify(modified) === undefined || Buffer.byteLength(JSON.stringify(modified), "utf8") > MAX_ELICITATION_BYTES) {
102
+ throw invalid("Modified arguments must be a bounded JSON object");
103
+ }
104
+ const call = state.pendingCalls?.find((entry) => entry.approvalId === target.approvalId)?.call ??
105
+ (state.pending && state.pending.call.id === target.toolCallId ? state.pending.call : undefined);
106
+ const toolName = target.scope.toolName ?? call?.name ?? "";
107
+ const tool = registry.get(toolName);
108
+ const context = { sessionId: state.sessionId, runId: state.runId, toolCallId: target.toolCallId ?? "", signal };
109
+ if (agent.config.validator && tool) {
110
+ const validation = await agent.config.validator(tool, modified, context);
111
+ if (validation)
112
+ throw invalid("Modified arguments failed schema validation");
113
+ }
114
+ const value = call
115
+ ? { ...call, arguments: modified }
116
+ : { type: "tool_call", id: target.toolCallId ?? "", name: toolName, arguments: modified };
117
+ const guarded = await runGuardrails({
118
+ stage: "tool_input",
119
+ guardrails: agent.config.guardrails,
120
+ value,
121
+ context: {
122
+ sessionId: state.sessionId,
123
+ runId: state.runId,
124
+ toolCallId: target.toolCallId,
125
+ toolName,
126
+ metadata: {},
127
+ signal,
128
+ },
129
+ redactor: agent.config.redactor,
130
+ });
131
+ if (guarded.terminal)
132
+ throw invalid("Modified arguments blocked by guardrail");
133
+ }
134
+ /**
135
+ * Resolve a tool's declared elicitation contract for a gated call. A throwing hook falls back to
136
+ * plain tool approval: malformed model args then surface as a tool error after approval, never
137
+ * as a run failure at the gate. Output is bounded before it enters the pending-decision record.
138
+ */
139
+ export class AgentRunSuspended extends Error {
140
+ state;
141
+ interruption;
142
+ code = "ERR_PRISM_AGENT_RUN_SUSPENDED";
143
+ constructor(state, interruption) {
144
+ super("Agent run suspended");
145
+ this.state = state;
146
+ this.interruption = interruption;
147
+ this.name = "AgentRunSuspended";
148
+ }
149
+ }
150
+ /** Root-visible nested approval id: hashed so it stays bounded and non-enumerating at any depth. */
151
+ export function nestedApprovalId(runId, childApprovalId) {
152
+ return `sub_${createHash("sha256").update(`${runId}:${childApprovalId}`).digest("hex")}`;
153
+ }
154
+ export function pathsEqual(a, b) {
155
+ if (a === undefined || b === undefined)
156
+ return a === b;
157
+ return a.length === b.length && a.every((value, index) => value === b[index]);
158
+ }
159
+ export function decisionScopesEqual(a, b) {
160
+ if (a.toolName !== b.toolName || a.argumentsHash !== b.argumentsHash || a.effectKind !== b.effectKind || a.identity !== b.identity)
161
+ return false;
162
+ if (a.actionConstraints === undefined || b.actionConstraints === undefined)
163
+ return a.actionConstraints === b.actionConstraints;
164
+ const keys = Object.keys(a.actionConstraints);
165
+ return (keys.length === Object.keys(b.actionConstraints).length &&
166
+ keys.every((key) => key in b.actionConstraints &&
167
+ canonicalToolEffectJson(a.actionConstraints[key]) === canonicalToolEffectJson(b.actionConstraints[key])));
168
+ }
169
+ export function nestedOutcomeToolResult(outcome, toolCallId, name) {
170
+ return outcome.status === "completed"
171
+ ? { toolCallId, name, ...(outcome.value !== undefined ? { value: outcome.value } : {}) }
172
+ : { toolCallId, name, error: { code: outcome.code, message: outcome.message } };
173
+ }
174
+ /** Compact redacted principal reference used in decision scopes; never a credential. */
175
+ export function decisionIdentityRef(identity) {
176
+ return identity ? `${identity.tenantId}:${identity.principal.kind}:${identity.principal.id}` : undefined;
177
+ }
178
+ //# sourceMappingURL=agent-approval.js.map
@@ -1,4 +1,4 @@
1
- import type { Agent, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, CheckpointStore, OwnershipScope, SubscribeOptions } from "./contracts.js";
1
+ import type { Agent, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunResumeStreamOptions, AgentRunStatusResult, CheckpointStore, OwnershipScope, SubscribeOptions } from "./contracts.js";
2
2
  export interface AgentRunLifecycleAgent {
3
3
  readonly agent: Agent;
4
4
  /** Current host-authored revision; it must match the stored revision. */
@@ -31,3 +31,7 @@ export interface AgentRunLifecycle {
31
31
  }
32
32
  /** Host capability for durable agent status/resume. Adapters supply authorized ownership only. */
33
33
  export declare function createAgentRunLifecycle(options: AgentRunLifecycleOptions): AgentRunLifecycle;
34
+ /** Resume a persisted built-in run. A claimed/dispatched tool is never replayed automatically. */
35
+ export declare function resumeAgentRun(agent: Agent, ref: AgentRunRef, resume: AgentRunResume, options: AgentRunResumeOptions): Promise<AgentRunResult>;
36
+ /** Subscribe before resuming one durable run. Early consumer return aborts that resumed execution. */
37
+ export declare function resumeAgentRunStream(agent: Agent, ref: AgentRunRef, resume: AgentRunResume, options: AgentRunResumeStreamOptions): AsyncGenerator<AgentEvent>;
@@ -1,6 +1,7 @@
1
- import { loadAgentRunState, publicState } from "./agent-run-state.js";
2
- import { resumeAgentRun, resumeAgentRunStream } from "./agents.js";
3
- import { AgentRunStateError } from "./contracts.js";
1
+ import { agentFingerprint, loadAgentRunState, publicState, saveAgentRunState } from "./agent-run-state.js";
2
+ import { AgentDecisionError, AgentRunStateError } from "./contracts.js";
3
+ import { pendingDecisionsOf, resolveRunDecisions } from "./agent-approval.js";
4
+ import { RuntimeAgentSession, throwIfAbortedSignal } from "./agent-session.js";
4
5
  function assertAgentId(actual, expected) {
5
6
  if (expected !== undefined && actual !== expected)
6
7
  throw new AgentRunStateError("Agent run capability mismatch");
@@ -48,4 +49,211 @@ export function createAgentRunLifecycle(options) {
48
49
  },
49
50
  };
50
51
  }
52
+ // Resume free functions moved from agents.ts at 0.1.4 (verbatim; the barrel re-exports the two public ones).
53
+ /** Resume a persisted built-in run. A claimed/dispatched tool is never replayed automatically. */
54
+ export async function resumeAgentRun(agent, ref, resume, options) {
55
+ return executePreparedAgentRunResume(await prepareAgentRunResume(agent, ref, resume, options));
56
+ }
57
+ /** Subscribe before resuming one durable run. Early consumer return aborts that resumed execution. */
58
+ export async function* resumeAgentRunStream(agent, ref, resume, options) {
59
+ throwIfAbortedSignal(options.signal);
60
+ const prepared = await prepareAgentRunResume(agent, ref, resume, options, options.signal);
61
+ const subscription = prepared.session.subscribe(options);
62
+ let settled = false;
63
+ const runPromise = executePreparedAgentRunResume(prepared, options.signal).finally(() => {
64
+ settled = true;
65
+ });
66
+ try {
67
+ for await (const event of subscription) {
68
+ if ("runId" in event && event.runId !== ref.runId)
69
+ continue;
70
+ yield event;
71
+ }
72
+ await runPromise;
73
+ }
74
+ finally {
75
+ if (!settled) {
76
+ prepared.session.abort(new Error("resume stream consumer closed"));
77
+ await runPromise.catch(() => undefined);
78
+ }
79
+ }
80
+ }
81
+ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
82
+ throwIfAbortedSignal(signal);
83
+ const { record, state } = await loadAgentRunState(options.checkpoints, ref, options.ownership);
84
+ if (state.definitionRevision !== options.definitionRevision ||
85
+ state.agentId !== (agent.config.id ?? agent.config.name) ||
86
+ state.fingerprint !== agentFingerprint(agent, options.definitionRevision)) {
87
+ throw new AgentRunStateError("Agent definition revision or fingerprint mismatch on resume");
88
+ }
89
+ if (record.version !== resume.expectedVersion || state.status !== "suspended") {
90
+ throw new AgentRunStateError("Stale or non-suspended agent run resume");
91
+ }
92
+ const session = new RuntimeAgentSession({ agent, id: state.sessionId, leafId: state.leafId });
93
+ // Opt-in session-state restore (plan 015 Task 4): names only; bodies re-resolve from
94
+ // the live registry the next time the model (re)loads them via load_skill.
95
+ if (options.persistSessionState && state.sessionState?.loadedSkillNames) {
96
+ session.restoreLoadedSkills(state.sessionState.loadedSkillNames);
97
+ }
98
+ if (resume.decision !== undefined && resume.decisions !== undefined) {
99
+ throw new AgentDecisionError("ERR_PRISM_DECISION_INVALID", "Resume accepts exactly one of decision or decisions");
100
+ }
101
+ const pendingDecisions = pendingDecisionsOf(state);
102
+ // Legacy approve maps to allow-once on every pending decision; legacy deny keeps its
103
+ // terminal-denied behavior. Batch decisions are validated and applied atomically below.
104
+ const resolved = resume.decisions !== undefined
105
+ ? await resolveRunDecisions({ agent, state, decisions: resume.decisions, signal })
106
+ : resume.decision === "approve" && pendingDecisions
107
+ ? await resolveRunDecisions({
108
+ agent,
109
+ state,
110
+ decisions: pendingDecisions.map((pending) => ({ approvalId: pending.approvalId, outcome: "allow_once" })),
111
+ signal,
112
+ })
113
+ : undefined;
114
+ if (resume.decision === undefined && resume.decisions === undefined) {
115
+ throw new AgentDecisionError("ERR_PRISM_DECISION_INVALID", "Resume requires a decision or decisions");
116
+ }
117
+ if (resolved && resolved.remaining.length > 0) {
118
+ throwIfAbortedSignal(signal);
119
+ const single = resolved.remaining.length === 1 ? resolved.remaining[0] : undefined;
120
+ const interruption = {
121
+ kind: state.interruption?.kind ?? "tool_approval",
122
+ reason: `${resolved.remaining.length} approval request(s) remain`,
123
+ ...(single?.toolCallId ? { toolCallId: single.toolCallId } : {}),
124
+ ...(single?.scope.toolName ? { toolName: single.scope.toolName } : {}),
125
+ pendingDecisions: resolved.remaining,
126
+ };
127
+ const resuspended = await saveAgentRunState({
128
+ checkpoints: options.checkpoints,
129
+ state: {
130
+ ...state,
131
+ status: "suspended",
132
+ interruption,
133
+ pending: undefined,
134
+ // Decided approvals persist on their entries so a partial batch never loses them;
135
+ // they dispatch (or synthesize their result) when the run finally resumes.
136
+ pendingCalls: state.pendingCalls?.map((entry) => {
137
+ const decision = resolved.decisionsById.get(entry.approvalId);
138
+ return decision ? { ...entry, decision } : entry;
139
+ }),
140
+ // Decided nested approvals persist on their nested-run entries, keyed by
141
+ // root-visible approval id, so a partial batch never loses them either.
142
+ nestedRuns: state.nestedRuns?.map((entry) => {
143
+ const decided = entry.approvals.filter((approval) => resolved.decisionsById.has(approval.id));
144
+ if (decided.length === 0)
145
+ return entry;
146
+ return {
147
+ ...entry,
148
+ decisions: {
149
+ ...entry.decisions,
150
+ ...Object.fromEntries(decided.map((approval) => [approval.id, resolved.decisionsById.get(approval.id)])),
151
+ },
152
+ };
153
+ }),
154
+ stickyDecisions: resolved.stickyDecisions,
155
+ },
156
+ expectedVersion: record.version,
157
+ ownership: options.ownership,
158
+ fencingToken: options.fencingToken,
159
+ });
160
+ return {
161
+ kind: "resuspend",
162
+ session,
163
+ interruption,
164
+ version: resuspended.record.version,
165
+ ownership: options.ownership,
166
+ result: {
167
+ sessionId: state.sessionId,
168
+ runId: state.runId,
169
+ status: "suspended",
170
+ leafId: state.leafId,
171
+ text: "",
172
+ content: [],
173
+ runState: publicState(resuspended.state),
174
+ interruption,
175
+ },
176
+ };
177
+ }
178
+ if (resume.decision === "deny") {
179
+ throwIfAbortedSignal(signal);
180
+ const denied = await saveAgentRunState({
181
+ checkpoints: options.checkpoints,
182
+ state: {
183
+ ...state,
184
+ status: "denied",
185
+ loopState: undefined,
186
+ pendingCalls: undefined,
187
+ nestedRuns: undefined,
188
+ stickyDecisions: undefined,
189
+ },
190
+ expectedVersion: record.version,
191
+ ownership: options.ownership,
192
+ fencingToken: options.fencingToken,
193
+ });
194
+ return {
195
+ kind: "deny",
196
+ session,
197
+ interruption: state.interruption,
198
+ version: denied.record.version,
199
+ ownership: options.ownership,
200
+ result: {
201
+ sessionId: state.sessionId,
202
+ runId: state.runId,
203
+ status: "denied",
204
+ leafId: state.leafId,
205
+ text: "",
206
+ content: [],
207
+ runState: publicState(denied.state),
208
+ interruption: state.interruption,
209
+ },
210
+ };
211
+ }
212
+ if (state.pending?.status === "dispatched" || state.pendingCalls?.some((entry) => entry.status === "dispatched")) {
213
+ throw new AgentRunStateError("Ambiguous dispatched tool requires operator resolution");
214
+ }
215
+ const configured = agent.config.runState;
216
+ if (configured && (configured.checkpoints !== options.checkpoints || configured.definitionRevision !== options.definitionRevision)) {
217
+ throw new AgentRunStateError("Agent durable run-state configuration mismatch on resume");
218
+ }
219
+ throwIfAbortedSignal(signal);
220
+ const claimed = await saveAgentRunState({
221
+ checkpoints: options.checkpoints,
222
+ state: {
223
+ ...state,
224
+ status: "running",
225
+ interruption: undefined,
226
+ stickyDecisions: resolved?.stickyDecisions ?? state.stickyDecisions,
227
+ },
228
+ expectedVersion: record.version,
229
+ ownership: options.ownership,
230
+ fencingToken: options.fencingToken,
231
+ });
232
+ return {
233
+ kind: "approve",
234
+ session,
235
+ state: claimed.state,
236
+ decisions: resolved?.decisionsById,
237
+ ownership: options.ownership,
238
+ runState: configured ?? {
239
+ checkpoints: options.checkpoints,
240
+ definitionRevision: options.definitionRevision,
241
+ interruptBeforeTool: state.interruptBeforeTool,
242
+ fencingToken: options.fencingToken,
243
+ resumeNestedRun: options.resumeNestedRun,
244
+ },
245
+ };
246
+ }
247
+ /** Pending decisions of a suspended state, synthesizing the legacy single-approval shape. */
248
+ async function executePreparedAgentRunResume(prepared, signal) {
249
+ if (prepared.kind === "deny") {
250
+ await prepared.session.recordDurableDenial(prepared.result.runId, prepared.interruption, prepared.version, prepared.ownership);
251
+ return prepared.result;
252
+ }
253
+ if (prepared.kind === "resuspend") {
254
+ await prepared.session.recordDurableResumption(prepared.result.runId, prepared.interruption, prepared.version, prepared.ownership);
255
+ return prepared.result;
256
+ }
257
+ return prepared.session.resumeDurable(prepared.state, prepared.runState, prepared.ownership, signal, prepared.decisions);
258
+ }
51
259
  //# sourceMappingURL=agent-run-lifecycle.js.map
@@ -0,0 +1,98 @@
1
+ import { type StoredAgentRunState } from "./agent-run-state.js";
2
+ import type { Agent, AgentConfig, AgentEvent, AgentRunResult, AgentRunStateOptions, AgentSession, AgentSessionConfig, CompactionOptions, CompactionResult, OwnershipScope, RunDecision, RunOptions, SessionEntry, SteerOptions, SubscribeOptions } from "./contracts.js";
3
+ import { type AgentInput } from "./input.js";
4
+ export declare function createAgent(config: AgentConfig): Agent;
5
+ export declare function createAgentSession(config: AgentSessionConfig & {
6
+ readonly agent: Agent;
7
+ }): AgentSession;
8
+ export declare class RuntimeAgentSession implements AgentSession {
9
+ readonly id: string;
10
+ private readonly agent;
11
+ private readonly metadata?;
12
+ private readonly store;
13
+ private readonly subscribers;
14
+ private currentLeafId?;
15
+ private history;
16
+ private activeRun?;
17
+ private activeRunId?;
18
+ private activeProviderTurnAbort?;
19
+ private pendingSoftInterrupt;
20
+ private pendingSteers;
21
+ private pendingSteerBytes;
22
+ private activeRedactor?;
23
+ private activeProvider?;
24
+ private activeLedger?;
25
+ private activeEffectStore?;
26
+ private activeOwnership?;
27
+ private activeIdentity?;
28
+ private activeIdempotencyKey?;
29
+ private activeGuardrails?;
30
+ private activeMetadata?;
31
+ private activeLimits?;
32
+ private activeLimitOutputBuffer;
33
+ private activeDurable?;
34
+ private activeLoop?;
35
+ /** Gated calls of the current tool round awaiting one collected suspension. */
36
+ private activeGatedRound?;
37
+ private activeLoopTurn;
38
+ private readonly loadedSkills;
39
+ /** Plan 015 Task 4: re-add persisted loaded-skill names (names only; bodies re-resolve on demand). */
40
+ restoreLoadedSkills(names: readonly string[]): void;
41
+ private ledgerChain;
42
+ private ledgerFailure;
43
+ private snapshotGeneration;
44
+ private snapshotCache?;
45
+ constructor(config: AgentSessionConfig & {
46
+ readonly agent: Agent;
47
+ });
48
+ get leafId(): string | undefined;
49
+ subscribe(options?: SubscribeOptions): AsyncIterable<AgentEvent>;
50
+ run(input: AgentInput, options?: RunOptions): Promise<AgentRunResult>;
51
+ steer(input: AgentInput, options?: SteerOptions): void;
52
+ resumeDurable(state: StoredAgentRunState, runState: AgentRunStateOptions, ownership?: OwnershipScope, signal?: AbortSignal, decisions?: ReadonlyMap<string, RunDecision>): Promise<AgentRunResult>;
53
+ recordDurableResumption(runId: string, interruption: import("./contracts.js").AgentRunInterruption, version: number, ownership?: OwnershipScope): Promise<void>;
54
+ recordDurableDenial(runId: string, interruption: import("./contracts.js").AgentRunInterruption, version: number, ownership?: OwnershipScope): Promise<void>;
55
+ private runInternal;
56
+ prompt(input: string, options?: RunOptions): Promise<AgentRunResult>;
57
+ stream(input: AgentInput, options?: RunOptions & SubscribeOptions): AsyncGenerator<AgentEvent>;
58
+ private buildRunResult;
59
+ private suspendDurable;
60
+ /** First attributed sticky whose scope and delegation path exactly match a nested decision. */
61
+ private matchNestedSticky;
62
+ /** First sticky decision whose scope exactly matches this call, if any. */
63
+ private matchStickyDecision;
64
+ /** Redacted pending-decision descriptor for one gated call; never carries raw arguments. */
65
+ private buildPendingDecision;
66
+ private persistDurable;
67
+ compact(options?: CompactionOptions): Promise<CompactionResult>;
68
+ abort(reason?: unknown): void;
69
+ entries(): Promise<readonly SessionEntry[]>;
70
+ checkout(leafId?: string): Promise<void>;
71
+ fork(options?: {
72
+ readonly leafId?: string;
73
+ }): AgentSession;
74
+ clone(options?: {
75
+ readonly id?: string;
76
+ readonly leafId?: string;
77
+ }): Promise<AgentSession>;
78
+ private branchReader;
79
+ private resolveRunProvider;
80
+ private resolveRunSkills;
81
+ private emit;
82
+ private closeSubscribers;
83
+ private drainLedger;
84
+ private generateWithRetry;
85
+ private generateProviderTurn;
86
+ private applyPendingSteers;
87
+ private applyProviderRequestPolicies;
88
+ private appendMessage;
89
+ private autoCompact;
90
+ private compactBranch;
91
+ private appendEntry;
92
+ private invalidateSnapshot;
93
+ private redact;
94
+ private redactProviderRequest;
95
+ private rebuildHistory;
96
+ private snapshot;
97
+ }
98
+ export declare function throwIfAbortedSignal(signal: AbortSignal | undefined): void;