@arnilo/prism 0.0.25 → 0.0.27

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
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.27] - 2026-08-07
4
+
5
+ ### Added
6
+ - ACP coding-host interop (`@arnilo/prism-ag-ui/acp`, stable ACP v1 over `@agentclientprotocol/sdk@1.3.0`): capability advertisement is a pure function of host seams (`loadSession`/`sessionCapabilities.*`/`promptCapabilities.*`/`mcpCapabilities.*`; `close` always; UNSTABLE cells never advertised), session persistence (`session/load|resume|list|delete`, bounded registry), session modes and config options as host overlays (`set_mode`, `set_config_option`, `current_mode_update`, `config_option_update`), client fs/terminal adapters (`AcpClientFilesystem`/`AcpClientTerminals`), MCP servers only behind host `select`, rich prompt content (`projectAcpPrompt`: media + embedded resources under live policy), tool-call locations/diffs via projection allow-lists, `CodingLifecycleEvent` → ACP update mapping, four-outcome approvals with elicitation (`elicitation/create` when advertised), and `AcpError` codes `ERR_PRISM_ACP_INPUT/LIMIT/POLICY/CAPABILITY/MCP`. Frozen caps in `resolveAgUiLimits` (`caps.acp`/`caps.lifecycle` groups).
7
+ - Phase 10 evidence: network-free `scripts/phase10-conformance.test.mjs` (in `npm test`), operator-gated real-transport smoke (`scripts/acp-client-smoke.mjs` + fixture), `examples/acp-coding-host.ts`, `scripts/benchmark-0.0.27.mjs` + `scripts/benchmark-0.0.27.json` evidence, `scripts/budgets.json` `phase10` gate.
8
+ - Docs: new [docs/acp.md](docs/acp.md) ACP reference; migration `0.0.26 → 0.0.27`; `docs/ag-ui.md` ACP summary + link; ACP pointers across agent-events/coding-agent-tools/coding-security/mcp-tools/host-security; package README.
9
+
10
+ ### Changed
11
+ - `@arnilo/prism-ag-ui` depends on `@arnilo/prism-coding-agent` (workspace) for Phase 9 output-chunk caps and lifecycle types; publishable graph stays **48** manifests.
12
+ - SBOM license policy allows `Unlicense` (tweetnacl via `@nats-io/nkeys`); readiness SBOM evidence refreshed (227 packages / 12 licenses).
13
+ - `@arnilo/prism-ag-ui/renderer` now exports the DOM-free A2UI core values (`A2UiSurfaceState`, `reduceA2UiOps`, `readA2UiBatch`, `resolvePointer`, `A2UI_VERSION`) — Synapta FR, hosts can drive the surface state machine without mounting; `createA2UiRenderer` behavior and frozen A2UI caps unchanged.
14
+
15
+ ### Breaking (advertise/surface for ACP hosts only)
16
+ - `initialize` advertisement now reflects wired seams (previously minimal close-session); new session methods are registered only with their seams; `session/resume` of a live session rejects; `agentInfo.version` now comes from the package.json. Core, AG-UI, and coding-agent behavior unchanged. See [migration guide](docs/migration.md) `0.0.26 → 0.0.27`.
17
+
18
+ ## [0.0.26] - 2026-08-06
19
+
20
+ ### Added
21
+ - Git-aware repository enumeration (`createGitAwareRepositoryOperations`): fixed `git ls-files` with native fallback, host-only `includeIgnored`, frozen ls-files output caps.
22
+ - Language intelligence (`createLanguageIntelligence`): host-selected LSP 3.17 client over bounded JSON-RPC — symbols/definitions/references/diagnostics/hover/rename; lazy spawn; policy-gated atomic rename; `ERR_PRISM_LSP_*` codes.
23
+ - Managed process sessions (`createProcessSessions`): start/output/input/wait/signal/kill/release, ownership + expiry sweep, optional sandbox `startProcess` backend with sandbox-loss → `unknown` reconciliation; `OutputAccumulator.readRaw` cursor paging.
24
+ - Reference GitHub forge adapter (`createGitHubForge`): issue context, authenticated push (`GIT_CONFIG_*` credential injection, never argv), PR create/update, review comments, checks/status, bounded `reconcileHandoff`; `ToolEffectStore` idempotency (retry never duplicates); host-injectable `fetch` option.
25
+ - Allow-list egress (`@arnilo/prism-coding-security`): deny-all `createEgressPolicy` with frozen presets, `createAllowListEgressProxy` (CONNECT tunnel, pinned-DNS rebinding defense, private/metadata IP denial, redirect re-validation, byte/time caps, audit records), `composeEgressSandboxNetwork` attestation labels.
26
+ - Network-free Phase 9 conformance + `benchmark-0.0.26.json` evidence; composed example `phase9-coding-intelligence.ts`.
27
+ - AG-UI reasoning encrypted-value helper (`createReasoningEncryptedValue`, FR-3) and MCP Apps UI-initiated mutation retry through `ToolEffectStore` (`reconcileAppEffect`, FR-4).
28
+ - Durable `AgentEventSource` root export in `@arnilo/prism-session-store-postgres` (FR-6) and new NATS JetStream sibling adapter `@arnilo/prism-session-store-nats` (FR-5): per-run subjects, per-subject replay, durable pull consumers with explicit acks (at-least-once), idempotent append, resumable cursors, ownership-scoped page/subscribe/cleanup.
29
+ - A2A server-side exposure (Task 13): `createAgUiA2AServer` in `@arnilo/prism-ag-ui` fronts a local AG-UI agent as an A2A 1.0 server over supervisor's `createA2AHandler` — remote clients run and stream the agent through the AG-UI input allow-list and event mapper, with a bounded live task registry and optional durable replay.
30
+ - Reference frontend renderer (Task 14): new `@arnilo/prism-ag-ui/renderer` subpath export — `createA2UiRenderer` consumes an AG-UI event stream and renders A2UI v0.9 surfaces into DOM from a host component catalog; DOM-free core with the server-side A2UI caps enforced client-side, fail-closed drops, explicit placeholders for unknown components, and no remote HTML execution.
31
+ - Async `AgUiProjection` hooks (Task 15): all hook returns are `Awaitable<T>`; the AG-UI and ACP mappers await hooks in event order with per-event fail-closed, so projectors can call `session.entries()` directly — `createMessagesFromSessionProjection` now accepts an async `getMessages` transcript source. Sync-only hosts keep exact prior behavior.
32
+
33
+ ### Changed
34
+ - Publishable graph grows to **48** manifests at **0.0.26** (new `@arnilo/prism-session-store-nats`).
35
+
36
+ ### Breaking (none)
37
+ - All Phase 9 additions are opt-in factories; no existing export, event, or persisted shape changed. See [migration guide](docs/migration.md) `0.0.25 → 0.0.26`.
38
+
3
39
  ## [0.0.25] - 2026-08-06
4
40
 
5
41
  ### Added
package/dist/index.d.ts CHANGED
@@ -105,5 +105,5 @@ export type { ToolEffectErrorCode } from "./tool-effects.js";
105
105
  export type { ResolvedUseCaseModel, ResolveUseCaseModelInput, UseCaseModelBinding, } from "./use-case-model.js";
106
106
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
107
107
  export declare const name = "prism";
108
- export declare const version = "0.0.25";
108
+ export declare const version = "0.0.27";
109
109
  export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
package/dist/index.js CHANGED
@@ -57,6 +57,6 @@ export { createToolParameterValidator, createToolRegistry, dispatchToolCall, fil
57
57
  export { createMemoryToolEffectStore, ToolEffectError } from "./tool-effects.js";
58
58
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
59
59
  export const name = "prism";
60
- export const version = "0.0.25";
60
+ export const version = "0.0.27";
61
61
  export const description = "Agent harness for AI providers, agents, sessions, and tools.";
62
62
  //# sourceMappingURL=index.js.map
@@ -1,12 +1,12 @@
1
1
  # 0.1.0 / 1.0 Readiness Gates
2
2
 
3
- Status: **0.0.25** is the current release line (Phase 8 durable custom loops and human-in-the-loop); **1.0** readiness remains operator-gated, not automatic.
3
+ Status: **0.0.27** is the current release line (Phase 10 ACP coding-host interop); **1.0** readiness remains operator-gated, not automatic.
4
4
 
5
5
  This page distills runnable readiness gates into one command-per-gate table. The
6
6
  **Last evidence** column records the 2026-07-26 **0.0.16** baseline snapshot
7
7
  (Phase 11, Node v24.18.0, Linux x86_64). Treat it as historical floor evidence,
8
8
  not the current release tag. Re-run each gate on the target release tree before
9
- cutting 0.0.25 / 1.0. The decision to cut 1.0 stays with the operator after
9
+ cutting 0.0.27 / 1.0. The decision to cut 1.0 stays with the operator after
10
10
  operator-gated legs run in a protected environment and Phase 12 demand evidence
11
11
  exists.
12
12
 
@@ -14,16 +14,16 @@ Evidence trail: [`docs/review-coverage-2026-07-26-phase-11.md`](./review-coverag
14
14
  (addenda 0–9), [`docs/release-and-install.md`](./release-and-install.md),
15
15
  [`docs/migration.md`](./migration.md), [`docs/performance.md`](./performance.md).
16
16
 
17
- ## Current line (0.0.25)
17
+ ## Current line (0.0.27)
18
18
 
19
19
  | Item | Status |
20
20
  |---|---|
21
- | Published graph | **47** publishable manifests at **0.0.25** (`docs/release-and-install.md`) |
22
- | Phase 8 durable loops / HITL | Custom-loop snapshot/restore, shared pending decisions, nested attributions, A2UI + standard AG-UI projectors |
23
- | Docs tripwires | `node --test dist/__tests__/docs.test.js` — migration section `0.0.24 → 0.0.25 durable custom loops and human-in-the-loop` |
24
- | Network-free Phase 8 evidence | `scripts/phase8-conformance.test.mjs`; `benchmark-0.0.25.json` under Task 0 ceilings |
21
+ | Published graph | **48** publishable manifests at **0.0.27** (`docs/release-and-install.md`) |
22
+ | Phase 10 ACP coding-host interop | Seam-based capability advertisement, session persistence, modes/config overlays, client fs/terminal adapters, MCP select gate, `CodingLifecycleEvent` → ACP updates, four-outcome approvals + elicitation, frozen caps |
23
+ | Docs tripwires | `node --test dist/__tests__/docs.test.js` — migration section `0.0.26 → 0.0.27 ACP coding-host interop`; `docs/acp.md` reference |
24
+ | Network-free Phase 10 evidence | `scripts/phase10-conformance.test.mjs`; `benchmark-0.0.27.json` under Task 0 p95 ceilings; freeze manifest matched by exports |
25
25
  | Protected database evidence (Phase 7) | `PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres`; `benchmark-0.0.24.json` under prior ceilings |
26
- | Readiness table below | **0.0.16 measured values** remain historical network-free baseline; 0.0.25 loop/HITL evidence is recorded separately |
26
+ | Readiness table below | **0.0.16 measured values** remain historical network-free baseline; 0.0.27 Phase 10 evidence is recorded separately |
27
27
 
28
28
  ## Gate table
29
29
 
@@ -36,7 +36,7 @@ Evidence trail: [`docs/review-coverage-2026-07-26-phase-11.md`](./review-coverag
36
36
  | Deterministic artifact budget | `node --test dist/__tests__/budget-gate.test.mjs` | root 579.2 kB / 2.1 MB / 270 files within +5% of baseline; startup 38 ms < 250 ms ceiling | CI (in `npm test`) |
37
37
  | Performance benchmark medians | `node scripts/benchmark-0.0.16.mjs` | 6 network-free scenarios within ±25%; 0 backpressure / 0 resource-limit signals | On-demand release evidence |
38
38
  | Secret scan | `node scripts/scan-secrets.mjs` | 3095 files / 0 findings | CI |
39
- | License / SBOM | `node scripts/verify-sbom.mjs` | 188 packages / 8 licenses, all allow-listed | CI |
39
+ | License / SBOM | `node scripts/verify-sbom.mjs` | 227 packages / 12 licenses, all allow-listed | CI |
40
40
  | Dependency audit | `npm audit --audit-level=high` | rc=0 (2 moderate, 0 high) at 0.0.16 | CI |
41
41
  | Whitespace hygiene | `git diff --check` | clean | CI |
42
42
  | Publish order + tarball validation | `node scripts/release.mjs publish --version <v> --dry-run --allow-dirty --allow-untagged` | 44/44 packages `dry-run`, deterministic dependency order, no failures | Operator (dry-run), CI |
@@ -114,7 +114,7 @@ protected environment, never faked.
114
114
  | Control | Command / source | 0.0.16 baseline |
115
115
  |---|---|---|
116
116
  | Secret scan | `node scripts/scan-secrets.mjs` | 3095 files / 0 findings |
117
- | License / SBOM | `node scripts/verify-sbom.mjs` | 188 packages / 8 licenses, allow-listed |
117
+ | License / SBOM | `node scripts/verify-sbom.mjs` | 227 packages / 12 licenses, allow-listed |
118
118
  | Dependency audit | `npm audit --audit-level=high` | 0 high (2 moderate) |
119
119
  | SAST | GitHub CodeQL workflow | CI-gated |
120
120
  | Sandbox / protocol / tenant threat suites | `npm test` (coding-security, MCP, policy, guardrail suites) | green at 0.0.16 |
package/docs/a2a.md CHANGED
@@ -39,6 +39,30 @@ const handler = createA2AHandler({
39
39
 
40
40
  Parts, messages, artifacts, histories, metadata, and aggregate responses are untrusted. Rich content remains in A2A task/message/artifact contracts for host mapping; it is never promoted to system instructions or automatically loaded as a Prism resource.
41
41
 
42
+ ## AG-UI server-side exposure (Task 13, 0.0.26)
43
+
44
+ `createAgUiA2AServer()` in `@arnilo/prism-ag-ui` fronts one host-selected **local AG-UI agent** as an A2A 1.0 server, the reverse direction of `createAgUiA2AAdapter()`: remote A2A clients start and stream local runs through the same AG-UI input allow-list and event mapper as the AG-UI SSE path (same projection, redaction, and byte caps). It reuses this package's `createA2AHandler` transport/lifecycle; it creates no second runtime, task store, or worker. Requires the optional `@arnilo/prism-supervisor` peer (imported lazily; plain `@arnilo/prism-ag-ui` imports keep working without it).
45
+
46
+ ```ts
47
+ import { createAgentEventSourceAgUiReplay, createAgUiA2AServer } from "@arnilo/prism-ag-ui";
48
+
49
+ const server = await createAgUiA2AServer({
50
+ card: agentCard, // A2A agent card (streaming: true)
51
+ authorize: (input) => authorizeA2A(input), // A2A auth → { ownership } (also the AG-UI authorization)
52
+ sessionFactory: ({ threadId, authorization, signal, input }) =>
53
+ createAgUiSession(authorization, input), // same shape as createAgUiHandler
54
+ input: { project: projectAgUiInput }, // AG-UI full-input allow-list
55
+ projection, redactor, a2ui, limits, // AG-UI mapper options
56
+ durable: { // optional: GetTask/SubscribeToTask after a run finishes
57
+ source: persistence.events, // durable AgentEventSource
58
+ resolveTask: async ({ id, authorization }) => ({ task, run }), // host-owned task→run correlation
59
+ },
60
+ });
61
+ // host mounts: new Request(url, init) → server(request)
62
+ ```
63
+
64
+ Semantics: `SendMessage` runs the local agent to completion and returns a terminal task with collected text artifacts; `SendStreamingMessage` (client `returnImmediately: true`) streams text/activity/state as bounded A2A artifact updates, then a terminal task. `agent_suspended` closes the stream with `TASK_STATE_INPUT_REQUIRED`; continuation stays host-owned (AG-UI resume). `GetTask`/`ListTasks`/`CancelTask` cover a bounded in-memory registry of tasks started on this instance; with `durable`, `SubscribeToTask`/`GetTask` also resolve host-correlated runs and replay the durable source with cursor event ids (at-least-once; clients dedupe by `eventId`). Text parts become the AG-UI user message; raw/data/url parts stay disabled unless `parts` selects them, and then arrive only in `forwardedProps.a2a` for `input.project`. Task ids default to `task-<uuid>`; hosts may own them via `selectTaskId`. `tasks` may be supplied to replace the built-in lifecycle entirely. A2A remains separately mounted — no route is added to `createPrismHandler()`.
65
+
42
66
  ## Implementation example
43
67
 
44
68
  ```ts
package/docs/acp.md ADDED
@@ -0,0 +1,126 @@
1
+ # Agent Client Protocol (ACP) coding-host interop
2
+
3
+ ## What it does
4
+
5
+ `@arnilo/prism-ag-ui/acp` (stable ACP **v1**, `@agentclientprotocol/sdk@1.3.0` root exports only) exposes two adapters:
6
+
7
+ - `createPrismAcpAgent(options)` — serves ACP as an **agent**: an editor/AI client connects through the SDK transport and drives host-owned Prism sessions with `session/new`, `session/load`, `session/resume`, `session/prompt`, `session/set_mode`, `session/set_config_option`, `session/list`, `session/delete`, `session/close`, and `session/cancel`. The agent is a thin protocol adapter: every capability, decision, and byte cap is wired from host seams, and there is **no second policy engine** on the agent side.
8
+ - `createAcpEventMapper(options)` — maps a Prism `AgentEvent` stream (or `CoWorkEvent`) to ACP `SessionUpdate`s for hosts that stream through their own transport.
9
+
10
+ The adapter builds on the Phase 8/9 shared machinery: redacted event projection (`AgUiProjection`), the durable pending-decision batch model (`allow_once` / `allow_for_run` / `reject_once` / `reject_for_run`), `AgentRunLifecycle` resume, `CodingLifecycleEvent` emission, and the AG-UI/ACP package caps. It never ships experimental ACP v2 or UNSTABLE fields (`providers`, `nes`, `positionEncoding`, `sessionCapabilities.fork`, `mcpCapabilities.acp/auth`, `elicitation` is consumed client-side only and never advertised).
11
+
12
+ ## When to use it
13
+
14
+ Use `createPrismAcpAgent` when an editor/AI client already speaks ACP and you want it to reach host-owned Prism runs: text streaming, safe tool status, usage, four-outcome approvals, session persistence, modes, config options, and — when the client advertises them — editor-buffer filesystem (`fs/read_text_file`, `fs/write_text_file`) and terminal (`terminal/create` … `terminal/kill`) client methods, plus prompt media (`image`, `audio`, `embeddedContext`) and `elicitation` decisions.
15
+
16
+ Do **not** use it when the host needs a browser/TUI Web endpoint (use [AG-UI](ag-ui.md)), remote agent-to-agent tasks ([A2A](a2a.md)), or a full editor integration — ACP is a protocol adapter, not an editor, a TUI, or a credential provider.
17
+
18
+ ## Inputs / request
19
+
20
+ `createPrismAcpAgent(options: CreatePrismAcpAgentOptions)` — every field is host-supplied:
21
+
22
+ | Option | Shape | Effect |
23
+ |---|---|---|
24
+ | `authorize` | `(input) => AcpAuthorization \| Promise` | **Required.** Ownership/identity gate for every inbound call, scoped by `sessionId`; unknown sessions fail. |
25
+ | `sessionFactory` | `(input) => AcpSessionBinding \| Promise` | **Required.** Builds the Prism `AgentSession` for `session/new`. Input carries `authorization`, `cwd`, `additionalDirectories`, `mcpServers` (policy-checked), `signal`, optional pre-generated `sessionId`, and `coding` (built client fs/terminal adapters when the client advertised them). |
26
+ | `lifecycle` | `AgentRunLifecycle` | **Required.** `status`/`resume`/`resumeStream` for durable `session/load` and `session/resume`. |
27
+ | `sessions?` | `AcpSessionStoreSeams` | `load` (advertises `sessionCapabilities.loadSession`), `list` (list), `delete` (delete), `resume` (resume), `additionalDirectories` (policy narrowing of `additionalDirectories`). `close` is always advertised. |
28
+ | `mcp?` | `AcpMcpSeams` | `transports: ("http" \| "sse")[]` and `select({ servers, signal })` — **required** for any client-supplied MCP server; select must approve before the bridge connects. Advertises `mcpCapabilities.http`/`sse` per transport. |
29
+ | `modes?` | `{ modes: AcpSessionMode[], defaultModeId? }` | `AcpSessionMode { id, name, description?, apply? }`; `apply({ sessionId?, fromModeId?, modeId, signal })` is the host hook run on switch. Advertised in `SessionModeState` on new/load/resume; enables `session/set_mode`. |
30
+ | `configOptions?` | `{ options: AcpConfigOption[], onChange? }` | `boolean`/`select` options with `defaultValue`; enables `session/set_config_option` (requires the client to advertise `session.configOptions.boolean`). |
31
+ | `capabilities?` | `AcpCapabilitiesOptions` | `prompt.media`/`prompt.embedded` policy seams, re-checked **live at prompt time**; presence advertises `promptCapabilities.image`/`audio`/`embeddedContext`. |
32
+ | `coding?` | `AcpCodingSeams` | `filesystem(client, sessionId)` / `processes(client, sessionId)` factories building `AcpClientFilesystem` / `AcpClientTerminals` over client methods; `lifecycle?: CodingLifecycleEmitter` subscribes `CodingLifecycleEvent`s into ACP updates. |
33
+ | `name?` | `string` | `agentInfo.name` (default `"Prism"`). |
34
+
35
+ Client capabilities are read at `initialize` and gate **client-method use**, not advertisement: `fs.readTextFile`/`writeTextFile` gate the fs seam, `terminal` the processes seam, `session.configOptions.boolean` the config path, `elicitation` the elicitation route.
36
+
37
+ ## Outputs / response / events
38
+
39
+ `initialize` returns `{ protocolVersion, agentCapabilities, agentInfo }`; `agentInfo.version` comes from the package manifest. Advertised capability = a host seam is wired (pure function, no hand-maintained matrix): `loadSession`/`sessionCapabilities.*` iff the matching `sessions` seam exists (`close` always), `promptCapabilities.*` iff the matching `capabilities.prompt` seam exists, `mcpCapabilities.*` iff `mcp.select` + the transport are wired. Unadvertised agent methods fail naturally with JSON-RPC `-32601`; unadvertised client methods are never called.
40
+
41
+ In-stream `SessionUpdate`s:
42
+
43
+ | Prism event | ACP update |
44
+ |---|---|
45
+ | Assistant text | `agent_message_chunk` |
46
+ | Tool lifecycle | `tool_call` / `tool_call_update` (title/status/content) |
47
+ | Tool result, projected | `tool_call_update` with `locations` (≤ `acpLocationsPerUpdate`) and/or a `diff` block (≤ `acpDiffBytes`) — only from `AgUiProjection.toolLocations`/`toolDiff` allow-lists, at `finish()` |
48
+ | Provider usage / errors | `usage_update`, `error` |
49
+ | Durable suspension | `session/request_permission` with the four outcomes `allow_once` / `allow_for_run` / `reject_once` / `reject_for_run`; cancel, unknown options, and request failure deny. Sticky decisions expire at run end. |
50
+ | Elicitation suspension (all-elicitation batch + client advertised `elicitation`) | `elicitation/create` (form mode, bounded schema, redacted reason); `accept` → `allow_once` with the typed payload as `RunDecision.elicitation`, `decline`/`cancel` → `reject_once`. Otherwise falls back to the shared four-option permission path. |
51
+ | `file_changed` lifecycle | `tool_call_update` with `locations: [{ path }]` (needs a `toolCallId`; diff only from `fileDiff` allow-list, capped + redacted) |
52
+ | `worktree_changed` / process events | Projection-gated `agent_message_chunk` (deny-by-default: no `lifecycle` projection hook = no update) |
53
+ | `permission_denied` lifecycle | `tool_call_update` status `failed` (never raw args; synthesized id `prism:denied:<approvalId>` when no `toolCallId`) |
54
+ | `configuration_changed` lifecycle | `config_option_update` with the full current set, per streaming session |
55
+ | Session mode/config switch | `current_mode_update` / `config_option_update` |
56
+
57
+ Frozen caps (default / hard, from the Phase 10 freeze manifest): sessions 32/128, additional directories 8/32 (path 4 KiB/16 KiB), MCP servers 8/32 (config 16 KiB/256 KiB, header values 4 KiB/64 KiB), modes 16/64, config options 16/64, list page 20/100, diff bytes 64 KiB/1 MiB, locations per update 32/128, prompt media parts 16/64 and media bytes 64 KiB/1 MiB (shared AG-UI caps), terminal output chunks 51200 B/1 MiB (Phase 9 `process.outputChunkBytes`), stream events/bytes per AG-UI budgets. `session/load`/`session/resume` of a still-registered session rejects with `ERR_PRISM_ACP_INPUT` ("ACP session already exists"); model reconnect as resume of a pre-seeded stored session.
58
+
59
+ Errors surface as `AcpError` with codes `ERR_PRISM_ACP_INPUT` (malformed), `ERR_PRISM_ACP_LIMIT` (caps), `ERR_PRISM_ACP_POLICY` (host denied), `ERR_PRISM_ACP_CAPABILITY` (not advertised), `ERR_PRISM_ACP_MCP` (MCP bridging). Over the wire they become JSON-RPC `-32603` with the message in `data.details` (SDK behavior).
60
+
61
+ ## Request/response example
62
+
63
+ ```json
64
+ // initialize → agentCapabilities (all seams wired)
65
+ {
66
+ "protocolVersion": 1,
67
+ "agentCapabilities": {
68
+ "loadSession": {},
69
+ "sessionCapabilities": { "list": {}, "delete": {}, "additionalDirectories": {}, "resume": {}, "close": {} },
70
+ "promptCapabilities": { "image": true, "audio": true, "embeddedContext": true },
71
+ "mcpCapabilities": { "http": true, "sse": true }
72
+ },
73
+ "agentInfo": { "name": "Prism", "version": "0.0.27" }
74
+ }
75
+
76
+ // session/new response (modes + configOptions wired)
77
+ { "sessionId": "acp-9f1c…", "modes": { "currentModeId": "edit", "availableModes": [{ "id": "edit", "name": "Edit" }] },
78
+ "configOptions": [{ "type": "boolean", "id": "verbose", "name": "Verbose", "defaultValue": false, "currentValue": false }] }
79
+ ```
80
+
81
+ ## Implementation example
82
+
83
+ See [`examples/acp-coding-host.ts`](../examples/acp-coding-host.ts) (runs in the demo gate). The host owns all state and policy:
84
+
85
+ ```ts
86
+ import { createPrismAcpAgent } from "@arnilo/prism-ag-ui/acp";
87
+
88
+ const agent = createPrismAcpAgent({
89
+ authorize: ({ sessionId }) => (sessionId ? hostSessions.has(sessionId) : true)
90
+ ? { ownership: { userId: "host-user" } }
91
+ : false,
92
+ sessionFactory: (input) => ({ session: hostSessionFor(input) }), // Prism AgentSession
93
+ lifecycle: { status, resume, resumeStream }, // durable
94
+ sessions: { load, list, delete, resume, additionalDirectories }, // capability seams
95
+ mcp: { transports: ["http", "sse"], select: ({ servers }) => approve(servers) },
96
+ modes: { modes: [{ id: "edit", name: "Edit" }, { id: "review", name: "Review", apply: narrow }], defaultModeId: "edit" },
97
+ configOptions: { options: [{ type: "boolean", id: "verbose", name: "Verbose", defaultValue: false }] },
98
+ capabilities: { prompt: { media: async () => true, embedded: async () => false } },
99
+ coding: { lifecycle, filesystem: clientFsAdapter, processes: clientTerminalAdapter },
100
+ });
101
+ // serve over the host's ACP transport: agent.connect(stream)
102
+ ```
103
+
104
+ ## Extension and configuration notes
105
+
106
+ - **Seam = capability.** Wiring `sessions.load` advertises `loadSession`; removing it withdraws the method. There is no separate capability flag to keep in sync — the freeze manifest's advertise-when matrix is enforced by construction and asserted by `scripts/phase10-conformance.test.mjs`.
107
+ - **Client fs/terminal are adapters, not a second implementation.** `AcpClientFilesystem` / `AcpClientTerminals` wrap the client's `fs/*` and `terminal/*` methods behind the Phase 9 `ProcessSession`-flavored interfaces; the agent pre-generates the session id so terminal requests can carry it. Host repo operations remain default when the client fs is absent.
108
+ - **Modes and config options are a pure host overlay.** The agent stores only a thin per-session registry; `apply`/`onChange` hooks narrow the host's own behavior. Mode switches can narrow or host-authorized widen — never a parallel policy evaluator, never a client-enabled tool.
109
+ - **Lifecycle wiring.** Pass your `createCodingLifecycleEmitter()` as `coding.lifecycle`; `file_changed` etc. then flow to streaming sessions. `configuration_changed` broadcasts `config_option_update` (agent-message fallback if the SDK rejects the kind).
110
+ - **Stream budgets.** Every lifecycle update counts against the same per-run stream event/byte budget as prompt updates; overflowing closes the update, never the run.
111
+
112
+ ## Security and performance notes
113
+
114
+ - **Untrusted client input.** Client-supplied paths, `additionalDirectories`, MCP server configs, terminal env/args, and media are validated at the boundary: count/byte caps, ownership-scoped sessions, path policy via the `sessions.additionalDirectories` seam, MCP servers only through host `select` (never auto-connected), UNSTABLE `acp` transport always rejected.
115
+ - **Deny-closed by default.** Unknown mode ids, unadvertised methods, unprojected lifecycle events, oversize diffs/locations/media, thrown projection hooks, and failed elicitation all fail closed. Raw tool arguments/results are never sent unless a projection allow-list says otherwise.
116
+ - **No secrets.** Updates carry no raw file bodies, terminal output is capped by the Phase 9 chunk budget, and the shared redactor is applied before anything leaves the host. `permission_denied` never includes raw args.
117
+ - **Performance.** The adapter is O(1) per update with no unbounded buffering; p95 targets (fs round trip 250 ms, mode switch 250 ms, terminal chunk ack 1000 ms, prompt first update 2000 ms, prompt end 30 s) are recorded by `scripts/benchmark-0.0.27.mjs` and gated in `scripts/budgets.json` `phase10`.
118
+
119
+ ## Related APIs
120
+
121
+ - [AG-UI](ag-ui.md): sibling frontend protocol; shared projection/redaction/caps and the same pending-decision model. This page is the full ACP reference.
122
+ - [Coding agent tools](coding-agent-tools.md): the `CodingLifecycleEvent` source mapped here; `@arnilo/prism-coding-agent` `process.outputChunkBytes` caps terminal chunks.
123
+ - [Agent events](agent-events.md): the durable `AgentEventSource`/replay story behind `session/load` and `session/resume`.
124
+ - [Host security guide](host-security.md): fail-closed checklist rows for ACP boundaries (authorize, ownership, redaction, untrusted MCP).
125
+ - [Migration guide](migration.md): 0.0.26 → 0.0.27 advertise/surface changes for hosts that parsed the old `initialize`.
126
+ - [AG-UI adoption evaluation](ag-ui-adoption.md): the underlying input/event/capability matrix.
package/docs/ag-ui.md CHANGED
@@ -5,7 +5,7 @@
5
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
6
 
7
7
  - Root export maps Prism events to AG-UI `@ag-ui/core` **0.0.57** events and offers `createAgUiHandler()` (`Request` → SSE `Response`), compatible `createPersistenceAgUiReplay()` pages, distributed `createAgentEventSourceAgUiReplay()` follow, and explicit `createAgUiMcpAdapter()` / `createAgUiMcpAppHandler()` / `createAgUiA2AAdapter()` protocol handshakes.
8
- - `@arnilo/prism-ag-ui/acp` uses stable `@agentclientprotocol/sdk` **1.3.0** root exports for `createAcpEventMapper()` and `createPrismAcpAgent()`.
8
+ - `@arnilo/prism-ag-ui/acp` is the stable ACP **v1** sibling: `createAcpEventMapper()` and `createPrismAcpAgent()` over `@agentclientprotocol/sdk` **1.3.0** root exports. ACP is a protocol adapter — sessions, modes, MCP, fs/terminal, lifecycle mapping, and caps live on the host seams. See [ACP coding-host interop](acp.md) for the full reference; this page covers AG-UI only.
9
9
  - Core remains protocol-free. `resumeAgentRunStream()` / `AgentRunLifecycle.resumeStream()` are generic durable-resume streams shared by adapters.
10
10
 
11
11
  ## When to use it
@@ -50,11 +50,31 @@ A Prism durable `agent_suspended` returns `RUN_FINISHED` with core interrupt id
50
50
 
51
51
  `createPersistenceAgUiReplay()` remains a compatible page adapter. `createAgentEventSourceAgUiReplay()` resolves exact ownership/run once per open, then consumes the shared durable source through terminal or live follow; it never attaches replica-local `session.subscribe()`. Every record must already be redacted. Mapped events carry stable `prismEventId` and bounded opaque `prismCursor`; records with no standard mapping emit `CUSTOM prism.replay_cursor`, so clients can persist progress. Terminal replay never creates a session or reruns a provider/tool.
52
52
 
53
- 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.
53
+ ACP maps assistant text to `agent_message_chunk`, safe tool lifecycle to `tool_call`/`tool_call_update` (locations/diffs only from projection allow-lists), provider usage to `usage_update`, and durable suspension to `session/request_permission` with the four shared outcomes — plus, per the client's advertised capabilities, editor-buffer fs, terminal, prompt media, modes/config options, lifecycle events, and elicitation. Advertisement is a pure function of the wired seams: no seam, no capability, no method. Only `allow_once` approves; reject, cancellation, unknown outcomes, and request failure deny. See [ACP coding-host interop](acp.md).
54
54
 
55
55
  Selected MCP tools use normal `TOOL_CALL_*` core dispatch. Linked Apps add safe `mcp-apps` activity; app-only tools stay model-hidden. The separate reauthorizing Apps proxy allow-lists initialize/ping/logging/tool/resource calls for one bridge; its sandbox helper returns CSP/iframe config and never executes HTML.
56
56
 
57
- `createAgUiA2AAdapter()` maps verified task text/activity. Non-text/tool/A2UI parts need host `projectPart`; non-streaming fallback accepts only a terminal task, otherwise host follows saved correlation.
57
+ ### UI-initiated mutation retry through `ToolEffectStore` (FR-4)
58
+
59
+ `createAgUiMcpAppHandler` accepts an optional `effectStore` (Phase 7 `ToolEffectStore`) plus `effectContext` (identity/ownership; falls back to `authorization.ownership` + `context.identity`). Every approved `tools/call` then records `begin` → `markDispatched` → `complete`/`fail`/`markUnknown` in the store; effect keys derive from identity + ownership + tool name + arguments hash (`deriveAppEffectKey`). The proxy **never auto-retries** — the host decides:
60
+
61
+ ```ts
62
+ import { createAgUiMcpAppHandler, reconcileAppEffect } from "@arnilo/prism-ag-ui";
63
+
64
+ const handler = createAgUiMcpAppHandler({
65
+ apps, authorize, context, approveToolCall, allowedOrigins,
66
+ effectStore, // optional: records UI mutations for idempotent retry
67
+ effectContext: ({ authorization, context }) => ({ identity: context.identity, ownership: authorization.ownership }),
68
+ });
69
+
70
+ // After transport/abort loss the record is `unknown`; the host verifies the
71
+ // actual outcome and resolves it (claim/CAS), then the UI can retry idempotently:
72
+ await reconcileAppEffect({ effectStore, identity, ownership, sessionId, runId, toolName, arguments: args, outcome: "completed", result });
73
+ ```
74
+
75
+ A retried call whose record is `completed` replays the recorded result without re-dispatching; `failed_retryable`/`failed_terminal`/`dispatched`/`unknown` records fail closed with a `409` until the host reconciles. Wrong-owner or unresolvable identity/ownership fails closed; absent `effectStore` keeps 0.0.25 behavior exactly.
76
+
77
+ `createAgUiA2AAdapter()` maps verified task text/activity. Non-text/tool/A2UI parts need host `projectPart`; non-streaming fallback accepts only a terminal task, otherwise host follows saved correlation. `createAgUiA2AServer()` fronts a local AG-UI agent as an A2A 1.0 server for remote A2A clients (reverse direction; see [A2A interoperability](a2a.md)).
58
78
 
59
79
  Co-work uses bounded, redacted `CUSTOM prism.cowork.*` events through `mapCoWork()` / `createCoWorkReplay()`; see [Work artifacts and review](work-artifacts-and-review.md).
60
80
 
@@ -96,7 +116,7 @@ const handle = createAgUiHandler({
96
116
  const response = await handle(request); // adapt this Web Response in host framework
97
117
  ```
98
118
 
99
- 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.
119
+ See runnable network-free [`examples/ag-ui-server.ts`](../examples/ag-ui-server.ts). For ACP, construct `createPrismAcpAgent({ authorize, sessionFactory, lifecycle, ...seams })` and connect the returned stable SDK agent through the host's ACP transport — see [`examples/acp-coding-host.ts`](../examples/acp-coding-host.ts) and [ACP coding-host interop](acp.md).
100
120
 
101
121
  ## Extension and configuration notes
102
122
 
@@ -104,6 +124,23 @@ All identity, authorization, session/thread mapping, durable checkpoint lookup,
104
124
 
105
125
  `AgUiProjection` is an allow-list. Without a callback, raw tool arguments/results/progress, arbitrary state/patches/transcripts/activity/reasoning/raw events, paths, ACP locations/diffs/terminals/raw I/O, and frontend-supplied tools remain absent. Reasoning signatures do not become AG-UI encrypted values automatically: a host must explicitly provide an already client-encrypted opaque value. `input.project` is also an allow-list: do not merge client state/forwarded props into ownership, identity, tools, permissions, provider options, or media fetch policy.
106
126
 
127
+ ### Reasoning encrypted-value helper (FR-3)
128
+
129
+ `createReasoningEncryptedValue({ encrypt, content, event, maxBytes? })` produces the `encryptedValue` fragment for the `reasoning` projection callback (AG-UI `REASONING_ENCRYPTED_VALUE`):
130
+
131
+ ```ts
132
+ import { createReasoningEncryptedValue } from "@arnilo/prism-ag-ui";
133
+
134
+ const mapper = createAgUiEventMapper({
135
+ projection: {
136
+ reasoning: (content, event) =>
137
+ createReasoningEncryptedValue({ encrypt: hostEncryptForClient, content, event }),
138
+ },
139
+ });
140
+ ```
141
+
142
+ `encrypt` is host-owned (client key) and receives the redacted `ThinkingContent` and the Prism event; return `undefined` to decline. The helper is synchronous and pure like the other projection callbacks: it never infers an encrypted value from a Prism reasoning signature, fails closed (returns `undefined`) when `encrypt` is missing, throws, or returns a non-string, and truncates output to `maxBytes` (default `DEFAULT_MAX_REASONING_BYTES`, clamped to `HARD_MAX_REASONING_BYTES`). The mapper additionally caps the emitted value at the resolved `maxReasoningBytes` limit.
143
+
107
144
  Co-work projection reuses the same allow-list: `AgUiProjection.coWork(event)` may return a curated, JSON-serializable payload for a co-work event; absent it, the redacted event fields are exposed. Wire `coWorkContext` to derive thread/artifact/identity from the authorized request (never client JSON) and `coWork` to a `createCoWorkReplay()` over your durable artifact/draft/snapshot stores. The handler projects one bounded page after the run; mount a dedicated cursor-paged co-work endpoint when full pagination is needed.
108
145
 
109
146
  Durable interrupts carry the shared decision batch: the fallback interrupt includes the redacted `pendingDecisions` under `metadata` and its `responseSchema` accepts either the legacy `{ decision: "approve" | "deny" }` or a `{ decisions: [{ approvalId, outcome, reason?, modifiedArguments?, elicitation? }] }` batch. All batch entries are shape- and cap-validated at the boundary (count ≤ 128, ids ≤ 128 chars, four outcomes, reason ≤ 8 KiB, payloads ≤ 64 KiB) and core re-validates each against the recorded pending set under the single CAS. `interrupts.resume` may return the batch form (`{ decisions, expectedVersion? }`); legacy `editedArgs` resume payloads still deny. ACP permission prompts offer the four outcomes (`allow_once` / `allow_always` / `reject_once` / `reject_always`) and map them onto the batch; a cancelled prompt stays deny-closed.
@@ -115,7 +152,11 @@ Three batteries-included factories return `AgUiProjection` fragments. Compose wi
115
152
  ```ts
116
153
  createAgUiHandler({
117
154
  projection: composeAgUiProjections(
118
- createMessagesFromSessionProjection({ getMessages: () => authorizedAgUiMessages, redact }),
155
+ // async transcript source: AgentSession.entries() is async
156
+ createMessagesFromSessionProjection({
157
+ getMessages: async () => (await session.entries()).map(entryToAgUiMessage),
158
+ redact,
159
+ }),
119
160
  createStateFromStoreProjection(runStateStore),
120
161
  createActivityFromToolProgressProjection(),
121
162
  hostCustom,
@@ -123,9 +164,11 @@ createAgUiHandler({
123
164
  });
124
165
  ```
125
166
 
167
+ Every `AgUiProjection` callback may return a promise (types are `Awaitable<T>` — Task 15, 0.0.26), so projectors can call async host APIs like `session.entries()` directly. Sync-only hosts keep exact prior behavior: sync return values short-circuit, hooks are awaited strictly in event order (never `Promise.all`), and a rejected hook fails closed per event (omitted value, stream continues) exactly like today's sync throw handling. `createMessagesFromSessionProjection({ getMessages })` accepts an async transcript source and emits `MESSAGES_SNAPSHOT` from it at `agent_started` and `message_finished` (no sync `getMessages` needed for full session history); `agent_finished` is terminal and the mapper projects nothing after it, so the final snapshot arrives at the last `message_finished`.
168
+
126
169
  | Factory | Emits | Notes |
127
170
  | --- | --- | --- |
128
- | `createMessagesFromSessionProjection` | `MESSAGES_SNAPSHOT` | Host `getMessages()` for authorized history, or live `message_finished` accumulation. Caps 128/1024. Redact drops closed. |
171
+ | `createMessagesFromSessionProjection` | `MESSAGES_SNAPSHOT` | Host `getMessages()` for authorized history (sync or async), or live `message_finished` accumulation. Caps 128/1024. Redact drops closed. |
129
172
  | `createStateFromStoreProjection(store)` | `STATE_SNAPSHOT` on `agent_started`; RFC 6902 `STATE_DELTA` (add/replace/remove) when `store.get()` changes | Host store; optional `subscribe` only marks dirty — no Prism watcher. Oversized/throw → drop closed. |
130
173
  | `createActivityFromToolProgressProjection` | `ACTIVITY_SNAPSHOT` / `ACTIVITY_DELTA` from `tool_execution_progress` | Default `activityType: "tool-progress"`. Missing progress+metadata → drop closed. |
131
174
 
@@ -143,9 +186,27 @@ Host `catalogId` is stamped when absent; model-supplied ids outside `allowedCata
143
186
 
144
187
  User actions arrive as untrusted `AgUiA2UiAction` values on `input.project({ a2uiActions })` (from `forwardedProps.a2uiAction` or activity/tool-result shapes). Without `input.project` they stay default-deny — Prism never synthesizes a `log_a2ui_event` tool call (documented divergence from official `@ag-ui/a2ui-middleware`). Example: `examples/ag-ui-a2ui.ts`.
145
188
 
189
+ ### Reference frontend renderer (Task 14, 0.0.26)
190
+
191
+ `@arnilo/prism-ag-ui/renderer` ships a framework-free client renderer for AG-UI/A2UI surfaces: it consumes an AG-UI event stream (SSE via `@ag-ui/client`, or any `AsyncIterable`) and renders `a2ui-surface` activity snapshots/deltas into DOM surfaces from a host component catalog. No framework dependency, no host build step, no jsdom (tests use an in-memory DOM stub).
192
+
193
+ ```ts
194
+ import { createA2UiRenderer } from "@arnilo/prism-ag-ui/renderer";
195
+
196
+ const renderer = createA2UiRenderer({
197
+ stream: agUiEventStream, // SSE or AsyncIterable of AGUIEvent
198
+ catalog: myComponents, // optional; defaults to Text/Container/Column/Row/Button
199
+ onAction: (action) => sendA2UiAction(action), // optional: Button clicks etc.
200
+ onError: (error) => console.warn(error.code, error.message),
201
+ });
202
+ const surface = await renderer.surface("chat"); // detached DOM node, kept in sync
203
+ ```
204
+
205
+ The core is a DOM-free state machine (`reduceA2UiOps`): operations become a surface/component model (adjacency list with `id`/`component`/flat props, A2UI v0.9 JSON-Pointer data model, `deleteSurface`); a thin binding layer renders the model through catalog component renderers (framework-free `(props, ctx, dom) => node` functions). The core is also exported as values from the subpath entry (`A2UiSurfaceState`, `reduceA2UiOps`, `readA2UiBatch`, `resolvePointer`, `A2UI_VERSION`, 0.0.27, Synapta FR) so framework hosts can drive the validated surface state machine and own the view layer; behavior and frozen caps are unchanged. Snapshots replace a surface's model (streaming mode sends cumulative ops); RFC 6902 deltas append. The same frozen caps as the server painter are enforced client-side: 64/512 ops per message, 64 KiB/1 MiB per op, 16/64 surfaces per run, depth 32/64. Invalid or oversized ops drop closed with one bounded `prism.a2ui.error` event (host logging via `onError`); unknown catalog components render an explicit placeholder. The renderer never executes remote HTML: only `createElement`/`createTextNode`/`appendChild`, no HTML-string assignment, no dynamic code evaluation. Data bindings `{"path": "/pointer"}` resolve against the per-surface data model; `deleteSurface` detaches content. The main `@arnilo/prism-ag-ui` entry stays runtime-agnostic — DOM code lives only behind the `renderer` subpath (the root entry re-exports renderer types only, no values). Hosts embedding it should follow the MCP Apps CSP/sandbox guidance (`docs/ag-ui-adoption.md`) for iframe/worker placement.
206
+
146
207
  ## Security and performance notes
147
208
 
148
- Authorize every start/replay/resume/proxy/follow. Treat protocol fields, MCP metadata/HTML, and A2A cards/parts as untrusted; persist run/task correlation before output and redact streams. MCP Apps requires extension acknowledgement, exact proxy origin, same-bridge visibility, approval, `ui://` HTML/MIME bounds, and sandbox CSP. It never retries UI mutations; Task 4 adds recovery.
209
+ Authorize every start/replay/resume/proxy/follow. Treat protocol fields, MCP metadata/HTML, and A2A cards/parts as untrusted; persist run/task correlation before output and redact streams. MCP Apps requires extension acknowledgement, exact proxy origin, same-bridge visibility, approval, `ui://` HTML/MIME bounds, and sandbox CSP. It never retries UI mutations; with an `effectStore` it records them for host-driven idempotent retry and unknown-outcome reconciliation (FR-4).
149
210
 
150
211
  Defaults / hard caps: request 64 KiB / 1 MiB; input 128 / 1024 messages, 32 / 256 tools/contexts, 8 / 64 interrupts, 16 / 64 media parts, and 64 KiB / 1 MiB text/state/media; frontend tool/context payloads 16 KiB / 256 KiB; projected event/state/activity/reasoning/raw values 64 KiB / 1 MiB; patches 128 / 4096 operations; JSON depth 16 / 64, properties 128 / 4096, arrays 512 / 8192; 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. SSE is declared; WebSocket/protobuf/push are not. Reconnect is at-least-once, so clients de-duplicate stable event/message/tool IDs.
151
212
 
@@ -157,6 +218,7 @@ Defaults / hard caps: request 64 KiB / 1 MiB; input 128 / 1024 messages, 32 / 25
157
218
  - [Web-standard server handler](server.md): generic Prism HTTP API, separate from AG-UI.
158
219
  - [A2A interoperability](a2a.md): remote agent-to-agent tasks, not frontend protocol mapping.
159
220
  - [AG-UI adoption evaluation](ag-ui-adoption.md): official 0.0.57 event/input matrix and shipped explicit MCP/MCP Apps/A2A handshakes.
221
+ - [ACP coding-host interop](acp.md): the full ACP reference — seam-based capability advertisement, session modes/config, MCP select, fs/terminal adapters, lifecycle mapping, elicitation, and caps.
160
222
  - [MCP bridge/server](mcp-tools.md): `mcpApps` negotiation, bounded resources, and remote tool trust.
161
223
  - [A2A interoperability](a2a.md): verified rich task client and remote task lifecycle.
162
224
  - [Host security guide](host-security.md): authorization, ownership, redaction, and credential boundaries.
@@ -22,6 +22,31 @@ Event records preserve emission order within a run because the runtime drains pe
22
22
 
23
23
  `AgentEventSource` (`createMemoryAgentEventSource` / `persistence.events` on PostgreSQL) appends, pages, and subscribes with opaque ownership-bound cursors. `subscribe` registers wake interest before replaying history so replay-to-live handoff has no gap. Delivery is at-least-once; consumers dedupe `record.id`. PostgreSQL uses transactional sequence allocation plus `LISTEN`/`NOTIFY` wakeups with polling fallback. Transport adapters (server SSE `Last-Event-ID`, AG-UI, A2A `afterEventId`) map source envelopes only — they do not invent private replay loops. This is not exactly-once.
24
24
 
25
+ ### Placement (FR-7 answer, 0.0.26)
26
+
27
+ The durable `AgentEventSource` **stays in `@arnilo/prism-session-store-postgres`** for the 0.0.26 line and is importable from the package root (FR-6):
28
+
29
+ ```ts
30
+ import { createPostgresAgentEventSource } from "@arnilo/prism-session-store-postgres";
31
+ const source = createPostgresAgentEventSource({ pool, schema: "prism", cursorSecret });
32
+ ```
33
+
34
+ PostgreSQL `LISTEN`/`NOTIFY` remains the **reference durable implementation**; `createPostgresPersistence` still bundles the same source as `persistence.events` (the canonical path — no behavior change). The standalone root export exists for consumers that want a durable source without full persistence. The migration path from the 0.0.24/0.0.25 API is: `persistence.events` and `createPostgresAgentEventSource` both keep working unchanged; a future relocation (if any) ships a replacement export with a deprecation note before removing the old one. See [migration](migration.md) `0.0.25 → 0.0.26` and the FR-6/FR-7 record `prism-agent-event-source-export-and-location.md`.
35
+
36
+ ### NATS JetStream adapter (FR-5)
37
+
38
+ `@arnilo/prism-session-store-nats` ships a sibling durable `AgentEventSource` over NATS JetStream for JetStream backbones (Postgres remains the reference implementation):
39
+
40
+ ```ts
41
+ import { connect } from "@nats-io/transport-node";
42
+ import { createNatsAgentEventSource, createNatsJetStream } from "@arnilo/prism-session-store-nats";
43
+
44
+ const nc = await connect({ servers: process.env.NATS_URL });
45
+ const source = createNatsAgentEventSource({ connection: await createNatsJetStream(nc), stream: "prism_agent_events" });
46
+ ```
47
+
48
+ One subject per run (`prism.agent-events.<tenant>.<session>.<run>`); the JetStream per-subject sequence is the per-run event sequence. `append` is idempotent by `record.id` within the stream's dedupe window; `page`/`subscribe` replay per subject from HMAC-signed cursors; `subscribe` uses a durable pull consumer with explicit acks (at-least-once, 30s redelivery, dedupe by `record.id`); `cleanup` deletes ownership-scoped messages older than `before`. The host provisions the stream (subjects `prism.agent-events.>`, retention limits, dedupe window). Inert on import; network-free tests use an in-memory fake of the narrow `NatsJetStream` seam.
49
+
25
50
  ## Inputs / request
26
51
 
27
52
  ```ts
@@ -217,4 +242,4 @@ for await (const event of session.stream("draft", { loop: { strategy: "generate-
217
242
  - [Observability](observability.md): `ProviderTurnMetadata`; optional adapter builds one parented GenAI span tree from metadata-only lifecycle events and ignores message/progress deltas.
218
243
  - [Tools](tools.md): `tool_execution_*` variants.
219
244
  - [Compaction and retry policies](compaction-and-retry.md): `compaction_*` and `retry_scheduled` variants.
220
- - [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.
245
+ - [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. [ACP coding-host interop](acp.md) additionally maps `CodingLifecycleEvent`s from `@arnilo/prism-coding-agent` (`file_changed`, `worktree_changed`, `permission_denied`, `configuration_changed`; process events reuse `CodingProcessEvent`) into ACP session updates — locations/diff blocks only through projection allow-lists, terminal chunks under `process.outputChunkBytes`.
@@ -23,6 +23,10 @@
23
23
  | `createCodingCheckTool(cwd, options)` | Named host-declared checks; model selects only a name. |
24
24
  | `createAskUserDecisionTool(options)` | Opt-in user decision tool (`ask_user_decision`); host supplies `ask` callback. Not in default aggregators. |
25
25
  | `createLocalRepositoryOperations(limits?)` | Default streaming Node filesystem backend for list/search/glob. |
26
+ | `createGitAwareRepositoryOperations(cwd, options?)` | Optional Git `ls-files` ignore-aware enumeration with native fallback; host-only `includeIgnored`. |
27
+ | `createLanguageIntelligence(options)` | Optional host-activated LSP language intelligence (symbols/definitions/references/diagnostics/hover/rename); see [Language intelligence](language-intelligence.md). |
28
+ | `createProcessSessions(options)` | Optional managed long-running process sessions (start/output/input/wait/signal/kill/release); see [Process sessions](process-sessions.md). |
29
+ | `createGitHubForge(options)` | Optional reference GitHub forge adapter (issue context, push, PR create/update, review comments, checks, handoff reconcile) with `ToolEffectStore` idempotency; see [Forge integration](forge-integration.md). |
26
30
  | `createGitOperations(options)` | Typed Git operations backend (argument arrays, safe config, finite output). |
27
31
  | `buildCodingCheckpointMetadata` / `validateCodingCheckpointMetadata` / `assertCodingResumeAllowed` | Bounded durable coding-task metadata for workflow `state.coding` (no second runtime). |
28
32
  | `writeCodingPlanFile` / `readCodingPlanFile` / `createCodingPlanMarkdown` / `parseCodingPlanTodos` | Workspace plan/todo Markdown helpers with finite byte/todo caps and hash verification. |
@@ -89,12 +93,14 @@ const tools = createCodingTools(workspaceRoot, {
89
93
 
90
94
  ### Phase 4 non-goals (0.0.21)
91
95
 
92
- These are **out of scope** for this package release (see roadmap Phase 9 / later):
96
+ These are **out of scope** for the 0.0.21 package baseline (see roadmap Phase 9 / later for LSP and process work):
93
97
 
94
98
  - **No PDF / document reader** — text and supported images only via `read`.
95
99
  - **No trash / recycle daemon** — `delete` / `move` are permanent; host undo is not automatic.
96
- - **No PTY / interactive process control** — `shell` is one-shot exec with bounded capture.
97
- - **No LSP / language-server tools** — use host-owned tools if needed.
100
+ - **No PTY / interactive process control in `shell`** — `shell` stays one-shot; optional `createProcessSessions` covers long-running attach/input (PTY still unsupported — see [Process sessions](process-sessions.md)).
101
+ - **LSP language-server tools** — not in default aggregators; optional `createLanguageIntelligence` is Phase 9 (see [Language intelligence](language-intelligence.md)).
102
+ - **Managed process sessions** — not in default aggregators; optional `createProcessSessions` is Phase 9 (see [Process sessions](process-sessions.md)).
103
+ - **GitHub forge adapter** — not in default aggregators; optional `createGitHubForge` is Phase 9 (see [Forge integration](forge-integration.md)); no octokit dependency, no multi-forge abstraction.
98
104
  - **No recursive directory delete** — `delete` refuses non-empty directories.
99
105
  - **No brace-expansion globs** — `glob` supports only `*`, `?`, and `**`.
100
106
 
@@ -226,6 +232,23 @@ A BOM is stripped before matching and re-prepended on write; original line endin
226
232
 
227
233
  List repository entries with deterministic relative paths. Uses Node `opendir`/`lstat` only — no glob dependency. Prefer `glob` when you already know a filename pattern. Prefer `repo_search` to find text inside files. Does not follow symlinks; rejects path escapes outside the workspace root. Hidden names and excluded basenames (default `.git`, `node_modules`, `dist`) are skipped unless `includeHidden` is set / host `exclude` is overridden.
228
234
 
235
+ #### Git-aware enumeration
236
+
237
+ `createGitAwareRepositoryOperations(cwd, options?)` is an optional `RepositoryOperations` backend that enumerates via fixed `git ls-files --cached --others --exclude-standard -z` (honors nested `.gitignore`, `$GIT_DIR/info/exclude`, and exclude-standard rules). Inject it through `ToolsOptions.repository.operations` (or per-tool `repository.operations`).
238
+
239
+ - **Detection:** cached `git rev-parse --is-inside-work-tree`. Outside a Git work tree, or when detection fails, delegates to `options.fallback` (default: `createLocalRepositoryOperations`).
240
+ - **Fail closed:** after successful detection, `ls-files` errors throw `RepositoryError` — no silent mid-session fallback.
241
+ - **Ignored paths:** stay excluded unless the host sets `includeIgnored: true` (factory option only; never a model-facing tool argument). Tracked-but-ignored files remain visible via `--cached` (Git semantics).
242
+ - **Bounds:** at most two Git invocations per operation; stdout capped by `DEFAULT_MAX_LS_FILES_OUTPUT_BYTES` (8 MiB, hard 64 MiB). Existing repo depth/entry/file/result/time caps still apply. No per-file Git spawn; no hand-rolled ignore parser; argv is never model-supplied.
243
+ - **Security:** `.git` internals never listed; paths re-checked against the workspace root; symlink escapes match native fail-closed behavior.
244
+
245
+ ```ts
246
+ import { createCodingTools, createGitAwareRepositoryOperations } from "@arnilo/prism-coding-agent";
247
+
248
+ const operations = createGitAwareRepositoryOperations(cwd); // native fallback outside Git
249
+ const tools = createCodingTools(cwd, { repository: { operations } });
250
+ ```
251
+
229
252
  **Inputs:**
230
253
 
231
254
  | Field | Type | Purpose |
@@ -530,8 +553,12 @@ Every configurable value is a positive safe integer (context may be zero); Prism
530
553
 
531
554
  ## Related APIs
532
555
 
556
+ - [Language intelligence](language-intelligence.md): optional host-activated LSP contract (`createLanguageIntelligence`) — symbols/definitions/references/diagnostics/hover/rename.
557
+ - [Process sessions](process-sessions.md): optional managed long-running processes (`createProcessSessions`) — start/output/input/wait/signal/kill/release.
558
+ - [Forge integration](forge-integration.md): optional GitHub adapter (`createGitHubForge`) — issue context, authenticated push, PR create/update, review comments, checks, bounded handoff reconcile; effect-store idempotency, no duplicate PRs/comments on retry, tokens never in argv/logs/events.
533
559
  - [Tools](tools.md): the host-owned tool harness — `createToolRegistry`, `dispatchToolCall`, filtering, and the `ToolDefinition` contract these factories satisfy.
534
560
  - [Public contracts](public-contracts.md): `ToolDefinition`, `ToolResult`, `ToolExecutionContext`, `ContentBlock`, and `JsonObject` shapes.
535
561
  - [Host security guide](host-security.md): fail-closed checklist for permission policies, tool validation, and trust boundaries that must gate these tools.
536
562
  - [Tool conformance](tool-conformance.md): assertions for the tool-dispatch blocked-reason matrix these tools participate in.
563
+ - [ACP coding-host interop](acp.md): host editors drive these tools through stable ACP v1 — client fs/terminal adapters, `CodingLifecycleEvent` emission (`file_changed` etc. via the `onEvent` options), and permission/elicitation through the shared four-outcome decision model.
537
564
  - [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.