@arnilo/prism 0.0.11 → 0.0.12

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 CHANGED
@@ -7,6 +7,19 @@ All notable changes to this project will be documented in this file.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.12] - 2026-07-22
11
+
12
+ ### Added
13
+
14
+ - Optional `@arnilo/prism-ag-ui` package with bounded AG-UI mapper/authorized handler/replay and stable `./acp` sibling, built over shared durable resume streams.
15
+ - `createCodingCompactionStrategy()` preset for bounded coding-session handoff.
16
+
17
+ ### Changed
18
+
19
+ - Versioned all 35 first-party manifests and exact internal ranges to `0.0.12`; `@arnilo/prism-all` includes AG-UI while `@arnilo/prism-code` and `@arnilo/prism-sdk` remain free of UI protocol dependencies.
20
+ - Added network-free interoperability/compaction evidence: `scripts/benchmark-0.0.12.mjs`.
21
+
22
+
10
23
  ## [0.0.11] - 2026-07-22
11
24
 
12
25
  ### Added
@@ -1,4 +1,4 @@
1
- import type { Agent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, OwnershipScope } from "./contracts.js";
1
+ import type { Agent, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, OwnershipScope, SubscribeOptions } from "./contracts.js";
2
2
  import type { CheckpointStore } from "./contracts.js";
3
3
  export interface AgentRunLifecycleAgent {
4
4
  readonly agent: Agent;
@@ -20,9 +20,13 @@ export interface AgentRunLifecycleRequest {
20
20
  /** Adapter-selected capability; stored runs for another agent are non-enumerable. */
21
21
  readonly agentId?: string;
22
22
  }
23
+ /** Bounded live-event options for a durable lifecycle resume. */
24
+ export interface AgentRunLifecycleStreamRequest extends AgentRunLifecycleRequest, SubscribeOptions {
25
+ }
23
26
  export interface AgentRunLifecycle {
24
27
  status(ref: AgentRunRef, options?: AgentRunLifecycleRequest): Promise<AgentRunStatusResult>;
25
28
  resume(ref: AgentRunRef, resume: AgentRunResume, options?: AgentRunLifecycleRequest): Promise<AgentRunResult>;
29
+ resumeStream(ref: AgentRunRef, resume: AgentRunResume, options?: AgentRunLifecycleStreamRequest): AsyncIterable<AgentEvent>;
26
30
  }
27
31
  /** Host capability for durable agent status/resume. Adapters supply authorized ownership only. */
28
32
  export declare function createAgentRunLifecycle(options: AgentRunLifecycleOptions): AgentRunLifecycle;
@@ -1,6 +1,6 @@
1
1
  import { AgentRunStateError } from "./contracts.js";
2
2
  import { loadAgentRunState, publicState } from "./agent-run-state.js";
3
- import { resumeAgentRun } from "./agents.js";
3
+ import { resumeAgentRun, resumeAgentRunStream } from "./agents.js";
4
4
  function assertAgentId(actual, expected) {
5
5
  if (expected !== undefined && actual !== expected)
6
6
  throw new AgentRunStateError("Agent run capability mismatch");
@@ -28,6 +28,22 @@ export function createAgentRunLifecycle(options) {
28
28
  definitionRevision: resolved.definitionRevision,
29
29
  });
30
30
  },
31
+ async *resumeStream(ref, resume, request = {}) {
32
+ request.signal?.throwIfAborted();
33
+ const { state } = await loadAgentRunState(options.checkpoints, ref, request.ownership);
34
+ assertAgentId(state.agentId, request.agentId);
35
+ const resolved = await options.resolveAgent({ agentId: state.agentId, ownership: request.ownership, signal: request.signal });
36
+ request.signal?.throwIfAborted();
37
+ yield* resumeAgentRunStream(resolved.agent, ref, resume, {
38
+ checkpoints: options.checkpoints,
39
+ ownership: request.ownership,
40
+ fencingToken: options.fencingToken,
41
+ definitionRevision: resolved.definitionRevision,
42
+ signal: request.signal,
43
+ maxQueuedEvents: request.maxQueuedEvents,
44
+ overflow: request.overflow,
45
+ });
46
+ },
31
47
  };
32
48
  }
33
49
  //# sourceMappingURL=agent-run-lifecycle.js.map
package/dist/agents.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- import type { Agent, AgentConfig, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunRef, AgentSession, AgentSessionConfig } from "./contracts.js";
1
+ import type { Agent, AgentConfig, AgentEvent, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunResumeStreamOptions, AgentRunRef, 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;
5
5
  }): AgentSession;
6
6
  /** Resume a persisted built-in run. A claimed/dispatched tool is never replayed automatically. */
7
7
  export declare function resumeAgentRun(agent: Agent, ref: AgentRunRef, resume: AgentRunResume, options: AgentRunResumeOptions): Promise<AgentRunResult>;
8
+ /** Subscribe before resuming one durable run. Early consumer return aborts that resumed execution. */
9
+ export declare function resumeAgentRunStream(agent: Agent, ref: AgentRunRef, resume: AgentRunResume, options: AgentRunResumeStreamOptions): AsyncGenerator<AgentEvent>;
package/dist/agents.js CHANGED
@@ -30,6 +30,32 @@ export function createAgentSession(config) {
30
30
  }
31
31
  /** Resume a persisted built-in run. A claimed/dispatched tool is never replayed automatically. */
32
32
  export async function resumeAgentRun(agent, ref, resume, options) {
33
+ return executePreparedAgentRunResume(await prepareAgentRunResume(agent, ref, resume, options));
34
+ }
35
+ /** Subscribe before resuming one durable run. Early consumer return aborts that resumed execution. */
36
+ export async function* resumeAgentRunStream(agent, ref, resume, options) {
37
+ throwIfAbortedSignal(options.signal);
38
+ const prepared = await prepareAgentRunResume(agent, ref, resume, options, options.signal);
39
+ const subscription = prepared.session.subscribe(options);
40
+ let settled = false;
41
+ const runPromise = executePreparedAgentRunResume(prepared, options.signal).finally(() => { settled = true; });
42
+ try {
43
+ for await (const event of subscription) {
44
+ if ("runId" in event && event.runId !== ref.runId)
45
+ continue;
46
+ yield event;
47
+ }
48
+ await runPromise;
49
+ }
50
+ finally {
51
+ if (!settled) {
52
+ prepared.session.abort(new Error("resume stream consumer closed"));
53
+ await runPromise.catch(() => undefined);
54
+ }
55
+ }
56
+ }
57
+ async function prepareAgentRunResume(agent, ref, resume, options, signal) {
58
+ throwIfAbortedSignal(signal);
33
59
  const { record, state } = await loadAgentRunState(options.checkpoints, ref, options.ownership);
34
60
  if (state.definitionRevision !== options.definitionRevision || state.agentId !== (agent.config.id ?? agent.config.name) || state.fingerprint !== agentFingerprint(agent, options.definitionRevision)) {
35
61
  throw new AgentRunStateError("Agent definition revision or fingerprint mismatch on resume");
@@ -37,7 +63,9 @@ export async function resumeAgentRun(agent, ref, resume, options) {
37
63
  if (record.version !== resume.expectedVersion || state.status !== "suspended") {
38
64
  throw new AgentRunStateError("Stale or non-suspended agent run resume");
39
65
  }
66
+ const session = new RuntimeAgentSession({ agent, id: state.sessionId, leafId: state.leafId });
40
67
  if (resume.decision === "deny") {
68
+ throwIfAbortedSignal(signal);
41
69
  const denied = await saveAgentRunState({
42
70
  checkpoints: options.checkpoints,
43
71
  state: { ...state, status: "denied" },
@@ -45,10 +73,23 @@ export async function resumeAgentRun(agent, ref, resume, options) {
45
73
  ownership: options.ownership,
46
74
  fencingToken: options.fencingToken,
47
75
  });
48
- const session = new RuntimeAgentSession({ agent, id: state.sessionId, leafId: state.leafId });
49
- await session.recordDurableDenial(state.runId, state.interruption, denied.record.version, options.ownership);
50
- const runState = publicState(denied.state);
51
- return { sessionId: state.sessionId, runId: state.runId, status: "denied", leafId: state.leafId, text: "", content: [], runState, interruption: state.interruption };
76
+ return {
77
+ kind: "deny",
78
+ session,
79
+ interruption: state.interruption,
80
+ version: denied.record.version,
81
+ ownership: options.ownership,
82
+ result: {
83
+ sessionId: state.sessionId,
84
+ runId: state.runId,
85
+ status: "denied",
86
+ leafId: state.leafId,
87
+ text: "",
88
+ content: [],
89
+ runState: publicState(denied.state),
90
+ interruption: state.interruption,
91
+ },
92
+ };
52
93
  }
53
94
  if (state.pending?.status === "dispatched")
54
95
  throw new AgentRunStateError("Ambiguous dispatched tool requires operator resolution");
@@ -56,6 +97,7 @@ export async function resumeAgentRun(agent, ref, resume, options) {
56
97
  if (configured && (configured.checkpoints !== options.checkpoints || configured.definitionRevision !== options.definitionRevision)) {
57
98
  throw new AgentRunStateError("Agent durable run-state configuration mismatch on resume");
58
99
  }
100
+ throwIfAbortedSignal(signal);
59
101
  const claimed = await saveAgentRunState({
60
102
  checkpoints: options.checkpoints,
61
103
  state: { ...state, status: "running", interruption: undefined },
@@ -63,13 +105,25 @@ export async function resumeAgentRun(agent, ref, resume, options) {
63
105
  ownership: options.ownership,
64
106
  fencingToken: options.fencingToken,
65
107
  });
66
- const session = new RuntimeAgentSession({ agent, id: state.sessionId, leafId: state.leafId });
67
- return session.resumeDurable(claimed.state, configured ?? {
68
- checkpoints: options.checkpoints,
69
- definitionRevision: options.definitionRevision,
70
- interruptBeforeTool: state.interruptBeforeTool,
71
- fencingToken: options.fencingToken,
72
- }, options.ownership);
108
+ return {
109
+ kind: "approve",
110
+ session,
111
+ state: claimed.state,
112
+ ownership: options.ownership,
113
+ runState: configured ?? {
114
+ checkpoints: options.checkpoints,
115
+ definitionRevision: options.definitionRevision,
116
+ interruptBeforeTool: state.interruptBeforeTool,
117
+ fencingToken: options.fencingToken,
118
+ },
119
+ };
120
+ }
121
+ async function executePreparedAgentRunResume(prepared, signal) {
122
+ if (prepared.kind === "deny") {
123
+ await prepared.session.recordDurableDenial(prepared.result.runId, prepared.interruption, prepared.version, prepared.ownership);
124
+ return prepared.result;
125
+ }
126
+ return prepared.session.resumeDurable(prepared.state, prepared.runState, prepared.ownership, signal);
73
127
  }
74
128
  class AgentRunSuspended extends Error {
75
129
  state;
@@ -154,18 +208,23 @@ class RuntimeAgentSession {
154
208
  this.pendingSoftInterrupt = true;
155
209
  }
156
210
  }
157
- async resumeDurable(state, runState, ownership) {
158
- return this.runInternal(state.input ?? [], { runState, ownership }, state.runId, { options: runState, state, version: state.version });
211
+ async resumeDurable(state, runState, ownership, signal) {
212
+ return this.runInternal(state.input ?? [], { runState, ownership, signal }, state.runId, { options: runState, state, version: state.version });
159
213
  }
160
214
  async recordDurableDenial(runId, interruption, version, ownership) {
161
215
  this.activeLedger = this.agent.config.runLedger;
162
216
  this.activeOwnership = ownership ?? this.agent.config.ownership;
163
217
  this.activeRedactor = this.agent.config.redactor;
164
- this.emit({ type: "agent_denied", sessionId: this.id, runId, interruption, version });
165
- await this.drainLedger();
166
- this.activeLedger = undefined;
167
- this.activeOwnership = undefined;
168
- this.activeRedactor = undefined;
218
+ try {
219
+ this.emit({ type: "agent_denied", sessionId: this.id, runId, interruption, version });
220
+ await this.drainLedger();
221
+ }
222
+ finally {
223
+ this.activeLedger = undefined;
224
+ this.activeOwnership = undefined;
225
+ this.activeRedactor = undefined;
226
+ this.closeSubscribers();
227
+ }
169
228
  }
170
229
  async runInternal(input, options, runId, resumed) {
171
230
  if (this.agent.config.secure && (options.redactor !== undefined || options.ownership !== undefined || options.validate !== undefined || options.runState !== undefined)) {
@@ -449,6 +449,10 @@ export interface AgentRunResumeOptions {
449
449
  readonly ownership?: OwnershipScope;
450
450
  readonly fencingToken?: number;
451
451
  }
452
+ /** Bounded, abortable options for `resumeAgentRunStream()`. */
453
+ export interface AgentRunResumeStreamOptions extends AgentRunResumeOptions, SubscribeOptions {
454
+ readonly signal?: AbortSignal;
455
+ }
452
456
  export interface AgentRunRef {
453
457
  readonly runId: string;
454
458
  readonly sessionId?: string;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type * from "./contracts.js";
2
2
  export type { RunLimitCounters, RunLimitName, SecureAgentOptions } from "./contracts.js";
3
3
  export { isSessionEntryKind, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SessionAppendConflictError, isSessionAppendConflict, AgentRunError, AgentRunStateError, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SESSION_SEARCH_UNSUPPORTED_CODE, SessionSearchUnsupportedError, isSessionSearchUnsupported, DEFAULT_SESSION_SEARCH_LIMIT, HARD_MAX_SESSION_SEARCH_LIMIT, DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES, HARD_MAX_SESSION_SEARCH_QUERY_BYTES, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES, DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES, HARD_MAX_SESSION_SEARCH_CURSOR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, HARD_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES, HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES, resolveSessionSearchQuery, DEFAULT_MAX_PENDING_STEERS, HARD_MAX_PENDING_STEERS, DEFAULT_MAX_PENDING_STEER_BYTES, HARD_MAX_PENDING_STEER_BYTES } from "./contracts.js";
4
- export { createAgent, createAgentSession, resumeAgentRun } from "./agents.js";
4
+ export { createAgent, createAgentSession, resumeAgentRun, resumeAgentRunStream } from "./agents.js";
5
5
  export { createBatchedRunLedger, isFlushableRunLedger, DEFAULT_LEDGER_BATCH_ENTRIES, HARD_LEDGER_BATCH_ENTRIES, DEFAULT_LEDGER_BATCH_BYTES, HARD_LEDGER_BATCH_BYTES, DEFAULT_LEDGER_BATCH_DELAY_MS, HARD_LEDGER_BATCH_DELAY_MS, } from "./run-ledger.js";
6
6
  export type { BatchedRunLedgerOptions } from "./run-ledger.js";
7
7
  export { createSecureAgent } from "./secure-agent.js";
@@ -11,7 +11,7 @@ export { CHECKPOINT_CONFLICT_CODE, CheckpointConflictError, createMemoryCheckpoi
11
11
  export { AGENT_RUN_STATE_NAMESPACE, AGENT_RUN_STATE_SCHEMA_VERSION, DEFAULT_MAX_AGENT_RUN_STATE_BYTES, HARD_MAX_AGENT_RUN_STATE_BYTES, agentFingerprint, loadAgentRunState } from "./agent-run-state.js";
12
12
  export type { StoredAgentRunState } from "./agent-run-state.js";
13
13
  export { createAgentRunLifecycle } from "./agent-run-lifecycle.js";
14
- export type { AgentRunLifecycle, AgentRunLifecycleAgent, AgentRunLifecycleOptions, AgentRunLifecycleRequest } from "./agent-run-lifecycle.js";
14
+ export type { AgentRunLifecycle, AgentRunLifecycleAgent, AgentRunLifecycleOptions, AgentRunLifecycleRequest, AgentRunLifecycleStreamRequest } from "./agent-run-lifecycle.js";
15
15
  export type { MemoryCheckpointStoreOptions } from "./checkpoints.js";
16
16
  export { LEASE_CONFLICT_CODE, LeaseConflictError, createMemoryLeaseStore } from "./leases.js";
17
17
  export { createEventMultiplexer } from "./event-multiplexer.js";
@@ -85,5 +85,5 @@ export type { DispatchToolCallOptions, ToolArgumentValidationError, ToolArgument
85
85
  export type { DuplicateRegistrationOptions, DuplicateRegistrationPolicy } from "./registry-options.js";
86
86
  export { dispatchToolCallsInOrder, generateValidateReviseLoop, isAgentLoopOptions, resolveLoop, resolveToolConcurrency, singleShotLoop } from "./agent-loops.js";
87
87
  export declare const name = "prism";
88
- export declare const version = "0.0.11";
88
+ export declare const version = "0.0.12";
89
89
  export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { isSessionEntryKind, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SessionAppendConflictError, isSessionAppendConflict, AgentRunError, AgentRunStateError, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SESSION_SEARCH_UNSUPPORTED_CODE, SessionSearchUnsupportedError, isSessionSearchUnsupported, DEFAULT_SESSION_SEARCH_LIMIT, HARD_MAX_SESSION_SEARCH_LIMIT, DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES, HARD_MAX_SESSION_SEARCH_QUERY_BYTES, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES, DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES, HARD_MAX_SESSION_SEARCH_CURSOR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, HARD_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES, HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES, resolveSessionSearchQuery, DEFAULT_MAX_PENDING_STEERS, HARD_MAX_PENDING_STEERS, DEFAULT_MAX_PENDING_STEER_BYTES, HARD_MAX_PENDING_STEER_BYTES } from "./contracts.js";
2
- export { createAgent, createAgentSession, resumeAgentRun } from "./agents.js";
2
+ export { createAgent, createAgentSession, resumeAgentRun, resumeAgentRunStream } from "./agents.js";
3
3
  export { createBatchedRunLedger, isFlushableRunLedger, DEFAULT_LEDGER_BATCH_ENTRIES, HARD_LEDGER_BATCH_ENTRIES, DEFAULT_LEDGER_BATCH_BYTES, HARD_LEDGER_BATCH_BYTES, DEFAULT_LEDGER_BATCH_DELAY_MS, HARD_LEDGER_BATCH_DELAY_MS, } from "./run-ledger.js";
4
4
  export { createSecureAgent } from "./secure-agent.js";
5
5
  export { createMemoryRunFeedbackStore, prepareRunFeedback, requireRunFeedbackOwnership, runFeedbackPageLimit, RunFeedbackError, } from "./feedback.js";
@@ -46,6 +46,6 @@ export { assertGuardrailsAllowed, GuardrailError, MAX_GUARDRAIL_CONCURRENCY, run
46
46
  export { createRunLimitTracker, DEFAULT_RUN_LIMITS, HARD_MAX_RUN_COST, HARD_RUN_LIMITS, RunLimitError, RunLimitTracker, resolveRunLimits } from "./run-limits.js";
47
47
  export { dispatchToolCallsInOrder, generateValidateReviseLoop, isAgentLoopOptions, resolveLoop, resolveToolConcurrency, singleShotLoop } from "./agent-loops.js";
48
48
  export const name = "prism";
49
- export const version = "0.0.11";
49
+ export const version = "0.0.12";
50
50
  export const description = "Agent harness for AI providers, agents, sessions, and tools.";
51
51
  //# sourceMappingURL=index.js.map
package/docs/a2a.md CHANGED
@@ -92,3 +92,4 @@ Defaults/hard caps include: request 64 KiB/1 MiB; response 1/8 MiB; event 64 KiB
92
92
  - [Agent/session runtime](agent-session-runtime.md)
93
93
  - [Workflows](workflows.md)
94
94
  - [Host security](host-security.md)
95
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): browser/editor protocol adapters over a Prism session; not an A2A card, task lifecycle, or remote-agent transport.
package/docs/ag-ui.md ADDED
@@ -0,0 +1,123 @@
1
+ # Frontend interoperability (AG-UI and ACP)
2
+
3
+ ## What it does
4
+
5
+ `@arnilo/prism-ag-ui` is an optional, framework-free protocol adapter over Prism's existing redacted `AgentEvent`, session, durable-run, and persistence seams.
6
+
7
+ - Root export maps Prism events to AG-UI `@ag-ui/core` **0.0.57** events and offers `createAgUiHandler()` (`Request` → SSE `Response`) plus `createPersistenceAgUiReplay()`.
8
+ - `@arnilo/prism-ag-ui/acp` uses stable `@agentclientprotocol/sdk` **1.3.0** root exports for `createAcpEventMapper()` and `createPrismAcpAgent()`.
9
+ - Core remains protocol-free. `resumeAgentRunStream()` / `AgentRunLifecycle.resumeStream()` are generic durable-resume streams shared by adapters.
10
+
11
+ This is not an app TUI, desktop shell, conversation database, terminal/filesystem bridge, A2A implementation, or frontend tool registry.
12
+
13
+ ## When to use it
14
+
15
+ Use AG-UI when a host already authenticates users, owns sessions and durable run correlation, and needs a bounded Web endpoint for a browser/TUI/desktop client. Use ACP when an editor client already supplies an ACP transport and needs text, safe tool status, usage, and approval updates from a Prism session.
16
+
17
+ Use [A2A interoperability](a2a.md) for remote agent-to-agent JSON-RPC/HTTPS tasks. AG-UI/ACP are frontend/client protocol adapters; neither replaces A2A task lifecycle or storage.
18
+
19
+ ## Inputs / request
20
+
21
+ Install the optional package beside the core runtime (it becomes publishable with the 0.0.12 release graph):
22
+
23
+ ```bash
24
+ npm install @arnilo/prism @arnilo/prism-ag-ui
25
+ ```
26
+
27
+ `createAgUiHandler()` takes host-owned callbacks:
28
+
29
+ | Input | Purpose |
30
+ | --- | --- |
31
+ | `authorize` | Rebinds untrusted AG-UI thread/run selectors to host ownership on every request. `false` returns 403. |
32
+ | `sessionFactory` | Returns an authorized Prism `AgentSession`; client input never selects tools or capabilities. |
33
+ | `lifecycle` + `resolveRun` | Optional durable status/resume path. Required only for a resumed interruption. |
34
+ | `replay` | Optional `createPersistenceAgUiReplay(store, options)` adapter for ownership-scoped durable pages. |
35
+ | `projection` | Explicit safe tool args/results, paths, or state projection. Omit it for default deny. |
36
+ | `redactor`, `limits` | Host redaction and narrowing-only finite caps. |
37
+
38
+ The handler accepts only `POST` JSON validated with AG-UI `RunAgentInputSchema`. IDs are bounded URL-safe values; it uses only the last text user message. Frontend tools and non-empty frontend state are rejected before authorization or session lookup. Start a run with no `resume` and no `?cursor=`; resume has exactly one entry; replay supplies `?cursor=`.
39
+
40
+ ## Outputs / response / events
41
+
42
+ The handler returns `text/event-stream`, one `data: <AG-UI event>\n\n` frame per output. Mapper lifecycle is ordered: Prism `agent_started`/assistant text/tool events map to `RUN_STARTED`, `TEXT_MESSAGE_*`, and `TOOL_CALL_*`; terminal success maps to `RUN_FINISHED`; runtime errors map to `RUN_ERROR`. Active AG-UI message/tool sequences close before an error, interruption, or finish.
43
+
44
+ A Prism durable `agent_suspended` returns `RUN_FINISHED` with interrupt id `${runId}:${version}` and a strict `{ decision: "approve" | "deny" }` schema. A client must address that exact current id. `cancelled` means deny; a resolved resume payload must contain only that decision. The adapter checks host authorization, selected run, suspended status, and checkpoint version, then calls `AgentRunLifecycle.resumeStream()` once. Claimed/dispatched tools are never replayed.
45
+
46
+ `createPersistenceAgUiReplay()` queries only the host-resolved run with ownership and ascending bounded pagination. Every record must already be redacted. Events carry `prismEventId` for at-least-once page-boundary de-duplication; a nonterminal final page may attach a filtered live subscriber. Terminal pages never create a session or rerun a provider/tool.
47
+
48
+ ACP maps assistant text to `agent_message_chunk`, safe tool lifecycle to `tool_call`/`tool_call_update`, provider usage to `usage_update`, and durable suspension to `session/request_permission`. Only `allow_once` approves; reject, cancellation, unknown outcomes, and request failure deny. It advertises only close-session capability—no terminal, filesystem, MCP, editor state, location, diff, or raw input/output capability.
49
+
50
+ ## Request/response example
51
+
52
+ ```json
53
+ {
54
+ "threadId": "thread-1",
55
+ "runId": "run-1",
56
+ "messages": [{ "id": "message-1", "role": "user", "content": "Summarize this" }],
57
+ "tools": [],
58
+ "state": {}
59
+ }
60
+ ```
61
+
62
+ A suspended response includes this resumable interrupt shape:
63
+
64
+ ```json
65
+ {
66
+ "type": "RUN_FINISHED",
67
+ "threadId": "thread-1",
68
+ "runId": "run-1",
69
+ "outcome": {
70
+ "type": "interrupt",
71
+ "interrupts": [{ "id": "run-1:4", "responseSchema": { "required": ["decision"] } }]
72
+ }
73
+ }
74
+ ```
75
+
76
+ Resume the same host thread/run with `resume: [{ "interruptId": "run-1:4", "status": "resolved", "payload": { "decision": "approve" } }]`. Do not send a copied session transcript, tool definitions, or mutable application state.
77
+
78
+ ## Implementation example
79
+
80
+ ```ts
81
+ import { createAgent, createMockProvider, providerDone, providerTextDelta } from "@arnilo/prism";
82
+ import { createAgUiHandler } from "@arnilo/prism-ag-ui";
83
+
84
+ const agent = createAgent({
85
+ model: { provider: "mock", model: "offline" },
86
+ provider: createMockProvider([providerTextDelta("ready"), providerDone()]),
87
+ });
88
+
89
+ const handle = createAgUiHandler({
90
+ authorize: ({ request }) => request.headers.get("authorization") === "Bearer host-checked"
91
+ ? { ownership: { userId: "user-1" } }
92
+ : false,
93
+ sessionFactory: () => agent.createSession({ id: "host-owned-thread" }),
94
+ projection: { toolArguments: () => undefined, toolResult: () => undefined },
95
+ });
96
+
97
+ const response = await handle(request); // adapt this Web Response in host framework
98
+ ```
99
+
100
+ See runnable network-free [`examples/ag-ui-server.ts`](../examples/ag-ui-server.ts). For ACP, construct `createPrismAcpAgent({ authorize, sessionFactory, lifecycle })` and connect the returned stable SDK agent through the host's ACP transport.
101
+
102
+ ## Extension and configuration notes
103
+
104
+ All identity, authorization, session/thread mapping, durable checkpoint lookup, persistence selection, replay cursor persistence, transport adaptation, and optional projection are host-owned. The adapter owns no listener, database, background reconnect loop, credential resolver, or UI state.
105
+
106
+ `AgUiProjection` is an allow-list. Without a callback, raw tool arguments/results/progress, paths, arbitrary state, raw Prism events, ACP locations/diffs/terminals/raw I/O, and frontend-supplied tools remain absent. Use a projector that returns a redacted display value, not a host filesystem path or tool payload.
107
+
108
+ ## Security and performance notes
109
+
110
+ Authorize every start, replay, resume, ACP new/prompt/cancel/close request. Treat thread IDs, run IDs, cursors, client messages, resume payloads, and protocol output as untrusted. Persist run ↔ protocol correlation before exposing an interrupt. Keep `SecretRedactor` active for streaming and ledger writes.
111
+
112
+ Defaults / hard caps: request 64 KiB / 1 MiB; input 128 / 1024 messages and 64 KiB / 1 MiB text; projected event 64 KiB / 1 MiB; error 8 KiB / 64 KiB; cursor 4 / 16 KiB; replay page 100 / 500 records; queue 128 / 4096 events; stream 10,000 / 100,000 events and 10 / 64 MiB; wall time 120 seconds / 30 minutes. Overflow yields a bounded error/closed stream, not an unbounded queue. Reconnect is at-least-once, so clients de-duplicate stable event/message/tool IDs.
113
+
114
+ Benchmark command/result placeholder: Task 8 adds `node scripts/benchmark-0.0.12.mjs` for mapper throughput, replay/handler latency, queue/heap, bytes, and coding-compaction preparation. No 0.0.12 timing result is claimed before that gate.
115
+
116
+ ## Related APIs
117
+
118
+ - [Agent/session runtime](agent-session-runtime.md): `session.stream()`, `resumeAgentRunStream()`, and durable lifecycle.
119
+ - [Agent events](agent-events.md): normalized source events and ledger redaction.
120
+ - [Runs and usage ledger](runs-and-usage.md): durable `AgentEventRecord` query source.
121
+ - [Web-standard server handler](server.md): generic Prism HTTP API, separate from AG-UI.
122
+ - [A2A interoperability](a2a.md): remote agent-to-agent tasks, not frontend protocol mapping.
123
+ - [Host security guide](host-security.md): authorization, ownership, redaction, and credential boundaries.
@@ -191,7 +191,7 @@ for await (const event of session.stream("draft", { loop: { strategy: "generate-
191
191
 
192
192
  - All events flow through `redactAgentEvent(event, activeRedactor)` before subscribers observe them. Configure `AgentConfig.redactor` / `RunOptions.redactor` via `createSecretRedactor([...knownSecretStrings])` so secret values are redacted in `message` content, `errors[].message`, `metadata`, and artifact `result`/`failure` payloads.
193
193
  - The artifact variants are emitted only by `generateValidateReviseLoop`. `singleShotLoop` (the default when no `AgentConfig.loop` / `RunOptions.loop` is set) emits zero artifact events. See [Agent loops](agent-loops.md).
194
- - Subscribers are in-process; the broadcaster is in-memory and live-only. Multiple `subscribe()` calls receive the same stream.
194
+ - Subscribers are in-process; the broadcaster is in-memory and live-only. Multiple `subscribe()` calls receive the same stream. `resumeAgentRunStream()` and `AgentRunLifecycle.resumeStream()` subscribe before resumed execution and yield only their selected durable `runId`; approval emits the normal `agent_started` then `agent_resumed` envelope, denial emits only `agent_denied`.
195
195
  - `session.subscribe(options)` accepts `maxQueuedEvents` (default `1024`, minimum `1`) and `overflow` (default `"close"`). The `close` policy clears queued payload events, queues one `event_subscriber_overflow` notice for that subscriber, then closes it. `drop_oldest` keeps the newest queued events; `drop_newest` ignores new events while full.
196
196
  - The union is additive: new variants are appended without renumbering; subscribers should handle unknown `event.type` gracefully.
197
197
 
@@ -212,3 +212,4 @@ for await (const event of session.stream("draft", { loop: { strategy: "generate-
212
212
  - [Observability](observability.md): `ProviderTurnMetadata`; optional adapter builds one parented GenAI span tree from metadata-only lifecycle events and ignores message/progress deltas.
213
213
  - [Tools](tools.md): `tool_execution_*` variants.
214
214
  - [Compaction and retry policies](compaction-and-retry.md): `compaction_*` and `retry_scheduled` variants.
215
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional redacted mapping of this stream; durable replay is ledger-backed and at-least-once, never a live-subscriber substitute.
@@ -19,6 +19,7 @@ The agent/session runtime adds the minimal shared SDK surface for running provid
19
19
  - `session.fork(options?)`
20
20
  - `session.clone(options?)`
21
21
  - `resumeAgentRun(agent, ref, decision, options)`
22
+ - `resumeAgentRunStream(agent, ref, decision, options)` → owned durable-resume `AsyncIterable<AgentEvent>`
22
23
  - `createAgentRunLifecycle({ checkpoints, resolveAgent })` for host-selected remote status/resume adapters
23
24
 
24
25
  The runtime streams provider text/tool-call content into `AgentEvent` values. Complete `tool_call` events are dispatched through the active host `ToolRegistry`, then returned as tool-result messages on the next provider turn. When a store is supplied, user, assistant, tool-result, and model-change entries are appended under the current branch leaf. Abort propagation and run exclusivity use native `AbortController`.
@@ -56,6 +57,8 @@ string | Message | readonly Message[]
56
57
 
57
58
  `session.stream(input, options?)` subscribes first, starts exactly one run, yields only that run's events, and terminates when the run succeeds, fails, or aborts. Early consumer return aborts the owned run and releases the session. `SubscribeOptions.maxQueuedEvents` / `overflow` may be passed alongside `RunOptions`.
58
59
 
60
+ `resumeAgentRunStream(agent, ref, resume, options?)` does the same for one existing suspended durable run. It validates checkpoint ownership, revision/fingerprint, and `expectedVersion`, then subscribes before emitting `agent_started` / `agent_resumed` and resumed message/tool/terminal events. `AgentRunResumeStreamOptions` combines existing resume options with `signal`, `maxQueuedEvents`, and `overflow`; early return aborts only resumed execution. It does not replay a claimed/dispatched tool, poll a ledger, or retain a worker. `createAgentRunLifecycle().resumeStream(ref, resume, request?)` adds the same behavior after host agent-capability resolution.
61
+
59
62
  `session.subscribe(options?)` remains available for hosts that want a long-lived subscriber across runs. Subscribe before `run()` to observe that run's events. The consumer loop and `session.run()` must run concurrently (e.g. start the `for await` consumer, then `await Promise.all([consumer, session.run("Hi")])`): events are only emitted during a live run, so awaiting the subscribe loop before calling `run()` deadlocks. Prefer `session.stream()` when you only need one run's events. `SubscribeOptions.maxQueuedEvents` defaults to `1024` (minimum `1`) and caps events queued while the consumer is not awaiting `next()`. `SubscribeOptions.overflow` defaults to `"close"`; it clears queued payload events, delivers one `event_subscriber_overflow` notice to that subscriber, then closes it. `"drop_oldest"` keeps newest events; `"drop_newest"` ignores new events while full.
60
63
 
61
64
  For a text-only provider turn, the runtime emits:
@@ -184,7 +187,7 @@ if (result.status === "suspended") {
184
187
  }
185
188
  ```
186
189
 
187
- Resume requires exact checkpoint ownership, version, agent fingerprint, and revision. Prism CAS-claims approval before work, rechecks normal guardrail/permission/validation/limit paths, and marks a pending tool dispatched before its side effect. `createAgentRunLifecycle()` wraps the same core path for server/MCP hosts: adapters pass only authorized ownership, status returns only `{ state, version }`, and `resolveAgent()` supplies current agent/revision. Remote restart requires both checkpoint and session stores to be durable. A crash after that mark is ambiguous and is never replayed automatically; use host tool idempotency keyed by `runId`/`toolCallId` or resolve it manually. Checkpoints contain bounded redacted state plus session/leaf references, never provider objects, callbacks, signals, credentials, or raw secrets. Only built-in loop options are durable; custom `AgentLoopStrategy` rejects before provider work.
190
+ Resume requires exact checkpoint ownership, version, agent fingerprint, and revision. Prism CAS-claims approval before work, rechecks normal guardrail/permission/validation/limit paths, and marks a pending tool dispatched before its side effect. `createAgentRunLifecycle()` wraps the same core path for server/MCP hosts: adapters pass only authorized ownership, status returns only `{ state, version }`, and `resolveAgent()` supplies current agent/revision. `resumeStream()` uses that same claim path and bounded subscriber, so adapters do not poll or duplicate resume logic. Remote restart requires both checkpoint and session stores to be durable. A crash after that mark is ambiguous and is never replayed automatically; use host tool idempotency keyed by `runId`/`toolCallId` or resolve it manually. Checkpoints contain bounded redacted state plus session/leaf references, never provider objects, callbacks, signals, credentials, or raw secrets. Only built-in loop options are durable; custom `AgentLoopStrategy` rejects before provider work.
188
191
 
189
192
  ## Secure composition
190
193
 
@@ -209,6 +212,7 @@ Per-run options may narrow `limits` and append `guardrails`; they cannot replace
209
212
  - [CLI/RPC](cli-rpc.md): terminal and JSONL adapters over this runtime.
210
213
  - [Workflows](workflows.md): optional DAG orchestration that calls `AgentSession.run()` for agent nodes.
211
214
  - [A2A interoperability](a2a.md): direct text exposure calls `AgentSession.run()`; durable/rich/reconnect behavior uses host `A2ATaskLifecycle` over existing checkpoints/persistence, never an in-memory runtime cache.
215
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional adapters use `session.stream()` and `resumeAgentRunStream()` / `AgentRunLifecycle.resumeStream()`; protocol/UI state remains outside core.
212
216
 
213
217
  `AgentConfig.loop` and `RunOptions.loop` select a replaceable per-run control loop (`singleShotLoop` default, or `generate-validate-revise` with host callbacks); see [Agent loops](agent-loops.md). `RunOptions.loop` wins over `AgentConfig.loop`. Built-in loops emit the same normal turn/message envelope around provider turns, and both add the first run input to live history once after the first provider turn so later turns see the same transcript shape.
214
218
 
@@ -379,6 +379,7 @@ const remoteWrite = createWriteTool("/repo", {
379
379
 
380
380
  ## Extension and configuration notes
381
381
 
382
+ - **Long coding sessions.** Use `createCodingCompactionStrategy()` from optional `@arnilo/prism-compaction-llm` when history needs a bounded coding handoff. It is selected explicitly through normal `session.compact()` / agent compaction configuration, preserves raw session entries, and prioritizes file paths, patch intent, checks, plan/todo state, blockers, and verification steps. It does not read files, retain full diffs, or create a second coding runtime.
382
383
  - **Pluggable operation backends.** Every tool accepts an `operations` seam. Custom `ReadOperations` must implement bounded `readText` plus `statFile`; custom `EditOperations` must implement `statFile`; read/write methods receive caps/signals. `BashOperations` must stream through `onData` and honor `signal`/`timeout`. Custom `RepositoryOperations` must honor depth/entry/file/match/scan/time caps and abort. A hostile custom backend can still violate its host-owned contract, so isolate it separately.
383
384
  - **Per-tool options.** `ShellToolOptions` adds `timeout` and `maxTotalOutputBytes`; `ReadToolOptions` adds `maxScanBytes`; `WriteToolOptions` adds `maxInputBytes`; `EditToolOptions` adds `maxFileBytes`, `maxInputBytes`, and `maxEdits`; list/search accept `repository` limits and shared aggregator `ToolsOptions.repository`.
384
385
  - **Aggregator options.** `ToolsOptions` (`{ executionPolicy?, shell?, read?, write?, edit?, list?, search?, repository? }`) threads each sub-object to the matching tool. `createCodingTools()`, `createAllTools()`, and `createReadOnlyTools()` apply the shared policy unless that tool has an explicit per-tool override. Read-only membership is deliberately `read` + `repo_list` + `repo_search` (0.0.9 behavior change).
@@ -425,3 +426,4 @@ Every configurable value is a positive safe integer (context may be zero); Prism
425
426
  - [Public contracts](public-contracts.md): `ToolDefinition`, `ToolResult`, `ToolExecutionContext`, `ContentBlock`, and `JsonObject` shapes.
426
427
  - [Host security guide](host-security.md): fail-closed checklist for permission policies, tool validation, and trust boundaries that must gate these tools.
427
428
  - [Tool conformance](tool-conformance.md): assertions for the tool-dispatch blocked-reason matrix these tools participate in.
429
+ - [LLM compaction package](compaction-llm.md): optional `createCodingCompactionStrategy()` retains bounded paths, patch intent, checks, plan/todo state, blockers, and next verification—not complete diffs or raw command output.
@@ -149,7 +149,7 @@ Retry policies are ordinary `RetryPolicy` implementations and can be registered
149
149
 
150
150
  Compaction strategies are ordinary `CompactionStrategy` implementations. Extensions can register strategies through the existing compaction strategy contribution registry, but registration is inert until a host explicitly selects and passes a strategy to runtime code. Extensions can also register `compaction` middleware; the runtime calls it only when the agent/session has that middleware registry configured.
151
151
 
152
- The default strategy does not call a provider. Hosts that need model-generated summaries can use the optional [`@arnilo/prism-compaction-llm` package](compaction-llm.md); its `maxOutputTokens`/`maxSummaryTokens` budget is passed through `model.parameters.maxTokens` and first-party providers serialize that to provider output-token fields. Hosts that need prepared source-backed memory without a compaction-time model call can use [`@arnilo/prism-compaction-observational-memory`](compaction-observational-memory.md).
152
+ The default strategy does not call a provider. Hosts that need model-generated summaries can use the optional [`@arnilo/prism-compaction-llm` package](compaction-llm.md); its `maxOutputTokens`/`maxSummaryTokens` budget is passed through `model.parameters.maxTokens` and first-party providers serialize that to provider output-token fields. Coding sessions can select that package's `createCodingCompactionStrategy()` preset for paths, patch intent, checks, plans/todos, blockers, and next verification steps; it remains an ordinary `CompactionStrategy` and does not retain complete diffs or add a coding runtime. Hosts that need prepared source-backed memory without a compaction-time model call can use [`@arnilo/prism-compaction-observational-memory`](compaction-observational-memory.md).
153
153
 
154
154
  ## Security and performance notes
155
155
 
@@ -173,5 +173,6 @@ The default strategy does not call a provider. Hosts that need model-generated s
173
173
  - [Configuration and manifests](configuration-and-manifests.md): `compactionStrategy` and `retryPolicy` manifest contribution kinds.
174
174
  - [Provider layer](provider-layer.md): safe provider error codes used by retry classification.
175
175
  - [Credentials and redaction](credentials-and-redaction.md): exact secret redaction helper used by default compaction and retry error handling.
176
+ - [LLM compaction package](compaction-llm.md): `createCodingCompactionStrategy()` is the thin coding-focused preset; see `examples/coding-compaction.ts` for a network-free mock.
176
177
 
177
178
  Runtime redaction composes with compaction and retry secret lists: configured redactors apply at session serialization boundaries, while compaction/retry `secrets` still redact their local summaries and errors.
@@ -12,6 +12,7 @@ Key exports:
12
12
  | Export | Purpose |
13
13
  | --- | --- |
14
14
  | `createLlmCompactionStrategy(options)` | Returns a provider-backed `CompactionStrategy`. |
15
+ | `createCodingCompactionStrategy(options)` | Fixed `coding` preset over the LLM strategy: prioritizes file paths, patch intent, commands/checks, plan/todos, blockers, and next verification while retaining normal limits and raw history. |
15
16
  | `createLlmCompactionExtension(options)` | Registers the strategy into an explicit extension kernel compaction registry. |
16
17
  | `prepareLlmCompaction(context, options?)` | Splits branch entries into summary input, kept suffix, optional split-turn prefix, file details, and compaction data. |
17
18
  | `findLlmCompactionCutPoint(entries, options?)` | Finds the last entry covered by a summary using approximate token budgets. |
@@ -76,6 +77,23 @@ const strategy = createLlmCompactionStrategy({
76
77
  await session.compact({ strategy, secrets: [apiKey] });
77
78
  ```
78
79
 
80
+ Coding-session example:
81
+
82
+ ```ts
83
+ import { createCodingCompactionStrategy } from "@arnilo/prism-compaction-llm";
84
+
85
+ const strategy = createCodingCompactionStrategy({
86
+ provider: summaryProvider,
87
+ summaryModel: { provider: "openai", model: "gpt-4.1-mini" },
88
+ keepRecentTokens: 20_000,
89
+ maxSummaryTokens: 800,
90
+ customInstructions: "Keep migration blockers prominent.",
91
+ });
92
+ await session.compact({ strategy });
93
+ ```
94
+
95
+ The preset always uses strategy name `coding` and enables existing read/modified-file retention. It adds no provider call, parser, worker, filesystem access, or complete-diff retention beyond `createLlmCompactionStrategy()`.
96
+
79
97
  Credential factory example:
80
98
 
81
99
  ```ts
@@ -108,7 +126,7 @@ Preparation is O(n) over branch entries and uses only arrays, strings, and JSON
108
126
 
109
127
  Provider deltas are redacted while retained and stop at `maxSummaryTokens * 4` UTF-16 code units without splitting a surrogate pair. Provider iteration is closed/aborted on overflow. A derived finite event ceiling also stops endless empty/non-text deltas. Final history/turn/file composition receives the same cap. Provider error events, generator throws, provider-factory failures, and policy failures expose only bounded redacted detail; host abort remains authoritative.
110
128
 
111
- The strategy makes only the needed provider call(s): one history summary plus one split-turn prefix summary when needed. It does not discover credentials, read files, start background jobs, or add provider SDK dependencies. Redaction is exact-string only; pass every known secret that may appear in history or provider output.
129
+ The strategy makes only the needed provider call(s): one history summary plus one split-turn prefix summary when needed. The coding preset makes the same calls and uses the same bounded file-operation preparation. Neither discovers credentials, reads files, starts background jobs, or adds provider SDK dependencies. Redaction is exact-string only; pass every known secret that may appear in history, paths, instructions, or provider output.
112
130
 
113
131
  ## Related APIs
114
132
 
@@ -119,3 +137,4 @@ The strategy makes only the needed provider call(s): one history summary plus on
119
137
  - [Agent/session runtime](agent-session-runtime.md): `AgentSession.compact()` and opt-in auto-compaction.
120
138
  - [Provider layer](provider-layer.md): mock providers and provider request contracts.
121
139
  - [Credentials and redaction](credentials-and-redaction.md): exact known-secret redaction behavior.
140
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): separate optional frontend transport; coding compaction adds no UI protocol dependency.
@@ -205,7 +205,7 @@ const providers = createOpenAIProviderPackage({ apiKey });
205
205
  - Use distinct `namespace` or vault paths per tenant/environment.
206
206
  - Keychain `list()` / `listOAuth()` are intentionally unsupported — enumerate credentials through host configuration instead of scanning the OS store.
207
207
  - Combine with `createExplicitCredentialResolver()` so runtime overrides still win over stored values.
208
- - Wire `createOAuthCredentialStoreAdapter(store)` into `refreshOAuthCredential()` so refreshed tokens persist durably.
208
+ - Wire `createOAuthCredentialStoreAdapter(store)` into `refreshOAuthCredential()` only for an OAuth flow explicitly selected by the host and authorized by that provider. In 0.0.12 that means OpenAI Codex; Anthropic and Google packages accept API keys only. Never import or migrate Claude Code/Gemini CLI credential files, setup tokens, browser sessions, or CLI OAuth rows into this store.
209
209
 
210
210
  ## Security and performance notes
211
211
 
@@ -216,6 +216,7 @@ const providers = createOpenAIProviderPackage({ apiKey });
216
216
  - Keychain operations use `@napi-rs/keyring`'s abort-aware `AsyncEntry`, so native work runs outside the JavaScript event loop. A main-loop timer aborts and rejects at `timeoutMs`; native cancellation remains OS/backend-dependent and may briefly retain one libuv worker after rejection.
217
217
  - Keychain payloads are bytes rather than password strings and are zeroed after parse/write. Unknown native errors are mapped to sanitized typed errors; no native message or secret value is echoed.
218
218
  - Never log passphrases, derived keys, or decrypted credential payloads.
219
+ - Storage is not OAuth eligibility. A durable store may persist credentials for a provider only after the host selects a provider-authorized flow; it must not be used to piggyback on a vendor CLI or consumer subscription.
219
220
  - Live keychain tests are opt-in (`PRISM_TEST_KEYCHAIN=1`); default `npm test` stays offline.
220
221
 
221
222
  ## MCP authentication boundary
@@ -102,6 +102,14 @@ console.log(error.message);
102
102
  - Keep resolved credential values local to the request path. Do not put them in registries, model configs, messages, provider events, agent events, session entries, compaction summaries, or logs.
103
103
  - Future settings/config loaders may provide credential resolver instances, but core helpers remain storage-free.
104
104
 
105
+ ### Subscription OAuth eligibility
106
+
107
+ In 0.0.12, OpenAI Codex is Prism's only first-party subscription OAuth flow. It is explicit and host-invoked through `createOpenAICodexOAuthProvider()`; hosts own login UI and may use `createOAuthCredentialStoreAdapter()` for deliberately selected durable storage.
108
+
109
+ Anthropic and Google provider packages are API-key-only. Do not scrape or import Claude Code/Gemini CLI credential files, setup tokens, environment values, or browser sessions, and do not route a user's Claude.ai/Gemini subscription through Prism. Anthropic states that developers building products must use Claude Console API keys or a supported cloud provider and may not offer Claude.ai login or route Free/Pro/Max credentials ([legal and compliance](https://docs.anthropic.com/en/docs/claude-code/legal-and-compliance)). Gemini CLI states that third-party software using its OAuth to access backend services violates applicable terms; its FAQ names Vertex AI or Google AI Studio API keys as the supported third-party path ([terms](https://github.com/google-gemini/gemini-cli/blob/main/docs/resources/tos-privacy.md), [FAQ](https://github.com/google-gemini/gemini-cli/blob/main/docs/resources/faq.md)).
110
+
111
+ A future provider-local OAuth adapter needs published permission for third-party products, documented authorize/token/refresh endpoints and scopes, PKCE/state where required, abort/expiry/bounded-response/redaction/store-round-trip fixtures, and legal review before registration. Until then, absence is intentional.
112
+
105
113
  ## Security and performance notes
106
114
 
107
115
  - Redaction only removes exact known secret values passed to the helper. It is not a general-purpose secret detector.
@@ -117,6 +117,7 @@ Wire those values where they matter: provider adapters receive the resolved cred
117
117
  - Resolve credentials at the provider/request edge, as late as possible. Do not put resolved credentials in configs, manifests, registries, prompts, messages, events, session entries, run ledgers, idempotency keys, cache keys, or logs.
118
118
  - Use `createExplicitCredentialResolver()` to document source order such as runtime override → stored credential → caller-supplied env object → fallback.
119
119
  - Use `createEnvCredentialResolver()` only with an object the host passes in. Prism does not read `process.env` for credentials.
120
+ - Do not treat a local vendor CLI credential file, setup token, browser session, or consumer subscription as a Prism credential source. In 0.0.12 only OpenAI Codex has a first-party subscription OAuth flow; Anthropic and Google providers remain API-key-only under their published third-party restrictions. See [Credentials and redaction](credentials-and-redaction.md#subscription-oauth-eligibility).
120
121
  - Use `createPathTrustPolicy()` for workspace/resource roots and fail closed on symlink escapes.
121
122
  - Use `createContributionRegistries({ duplicate: "error" })` and prefixed names for third-party packages to prevent silent shadowing.
122
123
  - Extension contributions are inert until selected. Loading an extension package runs its `setup(api)` code, so hosts should load only trusted packages or isolate untrusted code outside Prism.
@@ -190,6 +191,7 @@ PostgreSQL TLS/network policy, MCP endpoint trust/credentials and egress policy
190
191
  ## Related APIs
191
192
 
192
193
  - [Web-standard server handler](server.md): remote agent/workflow route, ownership, limits, abort, and deployment boundary.
194
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): authorize every protocol selector/operation; default-deny tool/state/path projection; exact interrupt/version resume; redacted, ownership-scoped replay.
193
195
  - [Supervisor delegation](supervisors.md): local child permission/memory/budget boundary.
194
196
  - [A2A interoperability](a2a.md): remote card/auth/origin/signature boundary.
195
197
  - [Settings, auth, trust, and security controls](settings-auth-trust-security.md): low-level helpers and boundary hardening table.