@arnilo/prism 0.0.25 → 0.0.26
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 +21 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/0.1.0-readiness.md +8 -8
- package/docs/a2a.md +24 -0
- package/docs/ag-ui.md +65 -4
- package/docs/agent-events.md +25 -0
- package/docs/coding-agent-tools.md +29 -3
- package/docs/coding-security.md +35 -1
- package/docs/forge-integration.md +113 -0
- package/docs/host-security.md +1 -0
- package/docs/index.md +10 -7
- package/docs/language-intelligence.md +162 -0
- package/docs/migration.md +24 -0
- package/docs/performance.md +16 -0
- package/docs/process-sessions.md +147 -0
- package/docs/release-and-install.md +30 -26
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.26] - 2026-08-06
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Git-aware repository enumeration (`createGitAwareRepositoryOperations`): fixed `git ls-files` with native fallback, host-only `includeIgnored`, frozen ls-files output caps.
|
|
7
|
+
- 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.
|
|
8
|
+
- 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.
|
|
9
|
+
- 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.
|
|
10
|
+
- 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.
|
|
11
|
+
- Network-free Phase 9 conformance + `benchmark-0.0.26.json` evidence; composed example `phase9-coding-intelligence.ts`.
|
|
12
|
+
- AG-UI reasoning encrypted-value helper (`createReasoningEncryptedValue`, FR-3) and MCP Apps UI-initiated mutation retry through `ToolEffectStore` (`reconcileAppEffect`, FR-4).
|
|
13
|
+
- 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.
|
|
14
|
+
- 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.
|
|
15
|
+
- 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.
|
|
16
|
+
- 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.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Publishable graph grows to **48** manifests at **0.0.26** (new `@arnilo/prism-session-store-nats`).
|
|
20
|
+
|
|
21
|
+
### Breaking (none)
|
|
22
|
+
- 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`.
|
|
23
|
+
|
|
3
24
|
## [0.0.25] - 2026-08-06
|
|
4
25
|
|
|
5
26
|
### 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.
|
|
108
|
+
export declare const version = "0.0.26";
|
|
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.
|
|
60
|
+
export const version = "0.0.26";
|
|
61
61
|
export const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
|
62
62
|
//# sourceMappingURL=index.js.map
|
package/docs/0.1.0-readiness.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# 0.1.0 / 1.0 Readiness Gates
|
|
2
2
|
|
|
3
|
-
Status: **0.0.
|
|
3
|
+
Status: **0.0.26** is the current release line (Phase 9 coding intelligence, managed processes, forge, and safe egress); **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.
|
|
9
|
+
cutting 0.0.26 / 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.
|
|
17
|
+
## Current line (0.0.26)
|
|
18
18
|
|
|
19
19
|
| Item | Status |
|
|
20
20
|
|---|---|
|
|
21
|
-
| Published graph | **
|
|
22
|
-
| Phase
|
|
23
|
-
| Docs tripwires | `node --test dist/__tests__/docs.test.js` — migration section `0.0.
|
|
24
|
-
| Network-free Phase
|
|
21
|
+
| Published graph | **48** publishable manifests at **0.0.26** (`docs/release-and-install.md`) |
|
|
22
|
+
| Phase 9 coding intelligence / processes / forge / egress | Git-aware enumeration, LSP language intelligence, managed process sessions, GitHub forge with idempotent handoff, allow-list egress proxy with rebinding defense |
|
|
23
|
+
| Docs tripwires | `node --test dist/__tests__/docs.test.js` — migration section `0.0.25 → 0.0.26 coding intelligence, processes, forge, and egress` |
|
|
24
|
+
| Network-free Phase 9 evidence | `scripts/phase9-conformance.test.mjs`; `benchmark-0.0.26.json` under Task 0 ceilings |
|
|
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.
|
|
26
|
+
| Readiness table below | **0.0.16 measured values** remain historical network-free baseline; 0.0.26 Phase 9 evidence is recorded separately |
|
|
27
27
|
|
|
28
28
|
## Gate table
|
|
29
29
|
|
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/ag-ui.md
CHANGED
|
@@ -54,7 +54,27 @@ ACP maps assistant text to `agent_message_chunk`, safe tool lifecycle to `tool_c
|
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
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). 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 (type-only re-exports). 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;
|
|
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
|
|
package/docs/agent-events.md
CHANGED
|
@@ -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
|
|
@@ -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
|
|
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
|
|
97
|
-
- **
|
|
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,6 +553,9 @@ 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.
|
package/docs/coding-security.md
CHANGED
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
| `createSandboxCodingTools` / `createSandboxReadOnlyTools` | Thin wrappers that return `tools` only (compat); still require `workspaceMode`. |
|
|
14
14
|
| `createSandboxFilesystemOperations` / `createSandboxRepositoryOperations` | Optional execFile-backed FS/list/search backends for a disposable sandbox tree. |
|
|
15
15
|
| `createDockerSandbox(options)` | Creates one disposable non-root Docker container with read-only root/source, bounded tmpfs workspace, typed `execFile`, import/export, and stop/kill/cleanup. |
|
|
16
|
+
| `SandboxProcessHandle` | Optional long-running process handle (`write`/`signal`/`kill`/`release`/`wait`) returned by `DisposableSandbox.startProcess?`. |
|
|
17
|
+
| `createEgressPolicy(options)` | Deny-all allow-list policy: exact host/port/protocol rules plus frozen `npm-registry` / `github` presets; SHA-256 fingerprint. |
|
|
18
|
+
| `createAllowListEgressProxy(options)` | HTTP forward proxy + CONNECT tunnel enforcing the policy: pinned DNS (rebinding defense), private/metadata IP denial, redirect re-validation + hop cap, byte/time caps, per-decision audit, attestation for sandbox composition. |
|
|
19
|
+
| `composeEgressSandboxNetwork(attestation, name)` | Validated custom Docker network carrying proxy attestation; recorded as `prism.egress.*` container labels. |
|
|
20
|
+
| `assertEgressAttestation(attestation)` | Fail-closed validation of proxy attestation evidence. |
|
|
16
21
|
| `assertPathInsideRoots`, `isPathInsideReal` | Symlink-aware path containment helpers. |
|
|
17
22
|
| `evaluateCommandRules`, `hasShellMetacharacters` | Command classification helpers. |
|
|
18
23
|
|
|
@@ -28,6 +33,32 @@ Use this package when coding tools need path scoping, human approval, command ru
|
|
|
28
33
|
|
|
29
34
|
Use `createDockerSandbox()` when the host wants a production-reference containment boundary. Prism does **not** claim OS-level isolation unless the host constructs this adapter (or supplies an equivalent custom `DisposableSandbox`). Default policy denies shell/write/edit/delete/move without an `approve` callback and rejects paths outside configured roots. Coding shell definitions are marked `exclusive: true`, matching the approval policy's shell decision, so a single-shot turn containing shell work runs sequentially even when `toolConcurrency > 1`. Non-shell turns retain configured parallelism.
|
|
30
35
|
|
|
36
|
+
Use `createEgressPolicy()` + `createAllowListEgressProxy()` when a coding agent needs outbound network access under an explicit allow list: package installs, forge API calls, or source fetches — never unrestricted egress. The proxy is inert until `start()`; nothing binds or resolves on import or construction.
|
|
37
|
+
|
|
38
|
+
## Allow-list egress composition
|
|
39
|
+
|
|
40
|
+
`createEgressPolicy({ allow, presets })` builds a deny-all policy. Rules are exact `{ host, port, protocol }` triples — no wildcards, no CIDR, no regex. Presets (`npm-registry`, `github`) expand to explicit rule lists at construction. The policy exposes a stable SHA-256 `fingerprint` over the canonical rule set.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { createEgressPolicy, createAllowListEgressProxy } from "@arnilo/prism-coding-security";
|
|
44
|
+
|
|
45
|
+
const policy = createEgressPolicy({
|
|
46
|
+
allow: [{ host: "api.github.com", port: 443, protocol: "https" }],
|
|
47
|
+
presets: ["npm-registry"],
|
|
48
|
+
});
|
|
49
|
+
const proxy = createAllowListEgressProxy({ policy, audit: (record) => host.recordEgress(record) });
|
|
50
|
+
const endpoint = await proxy.start(); // 127.0.0.1:0 by default; bind a reachable interface for containers
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Every request is checked against the policy before any DNS or connect. HTTPS goes through CONNECT tunnels with TLS passed through untouched — no interception, no MITM. DNS answers are resolved once, pinned, and the connected socket's remote address is verified against the pinned set (rebinding defense); private/link-local/metadata ranges (`10/8`, `172.16/12`, `192.168/16`, `127/8`, `169.254/16` incl. `169.254.169.254`, CGNAT, ULA, `::1`, `fe80::/10`) are denied unless the matching rule sets `allowPrivate: true`. Plain-HTTP redirects are followed up to `redirectHops` with every hop re-validated against policy; redirects to unlisted hosts or non-http targets fail closed. Request/response bytes and total transfer time are capped; oversized or slow-loris transfers are cut with `ERR_PRISM_EGRESS_LIMIT`. Every allow/deny writes an `EgressAuditRecord` (id, ts, decision, host, port, protocol, reason, bytes, duration, client address) — never headers, bodies, or tokens. `reloadPolicy()` is the only way to change rules and bumps `policyVersion`; `attestation()` returns `{ proxyEndpoint, denyDirectEgress: true, policyFingerprint, policyVersion, startedAt }` for sandbox composition.
|
|
54
|
+
|
|
55
|
+
Sandbox composition: `composeEgressSandboxNetwork(proxy.attestation(), networkName)` returns a custom `DockerNetworkConfig` whose attestation is validated and recorded as `prism.egress.endpoint` / `prism.egress.fingerprint` / `prism.egress.policyVersion` / `prism.egress.denyDirect=1` container labels. The adapter records evidence; the host must actually restrict the Docker network so the proxy is the only reachable path (e.g., a dedicated network with only the proxy container attached). A custom network without valid attestation fails closed for egress claims, mirroring `assertBrowserSandboxNetwork`.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const network = composeEgressSandboxNetwork(proxy.attestation(), "egress-net");
|
|
59
|
+
const sandbox = await createDockerSandbox({ docker, image, sourceRoot, user, network, limits });
|
|
60
|
+
```
|
|
61
|
+
|
|
31
62
|
## Inputs / request
|
|
32
63
|
|
|
33
64
|
| Option | Default | Purpose |
|
|
@@ -72,7 +103,7 @@ Use `createDockerSandbox()` when the host wants a production-reference containme
|
|
|
72
103
|
|
|
73
104
|
`createSandboxCodingComposition()` returns `{ tools, composition }` where `SandboxCodingComposition` carries `workspaceMode`, `containmentClaim`, `mixedWiringAllowed`, `warnings`, `workspaceRoot`, and optional `treeIdentity` (from `importIdentity` / `lastExportIdentity`). `containmentClaim` is `true` only for sandbox mode with tree backends bound and mixed wiring denied. Host mode and escape-hatch mixed wiring always set `containmentClaim: false` — never treat host mode as contained execution.
|
|
74
105
|
|
|
75
|
-
`createDockerSandbox()` returns a `DisposableSandbox`: typed `execFile(file, args)`, shell-compatible `exec`, `status`, cooperative `stop`, forced `kill`, and idempotent `close`. Import may surface `importIdentity`; successful export updates `lastExportIdentity`. `close({ export })` can stream a bounded workspace tar plus SHA-256/entry/byte metadata through a host callback; checkpoints should retain only host artifact references/hashes, never whole workspaces.
|
|
106
|
+
`createDockerSandbox()` returns a `DisposableSandbox`: typed `execFile(file, args)`, shell-compatible `exec`, `status`, cooperative `stop`, forced `kill`, and idempotent `close`. Import may surface `importIdentity`; successful export updates `lastExportIdentity`. `close({ export })` can stream a bounded workspace tar plus SHA-256/entry/byte metadata through a host callback; checkpoints should retain only host artifact references/hashes, never whole workspaces. Optional `startProcess?(SandboxExecFileRequest)` returns a `SandboxProcessHandle` for long-running work consumed by coding-agent `createProcessSessions({ sandbox })`; absence means one-shot-only — ProcessSessions fails closed with `ERR_PRISM_PROCESS_UNSUPPORTED` (no native fallback). The Docker reference adapter does not implement `startProcess` yet; capability is detected, never assumed. See [Process sessions](process-sessions.md).
|
|
76
107
|
|
|
77
108
|
## Request/response example
|
|
78
109
|
|
|
@@ -151,11 +182,14 @@ Containment resolves symlinks and rejects paths outside roots. Command rules are
|
|
|
151
182
|
|
|
152
183
|
Docker sandbox containment—not command regexes—enforces filesystem/network/process boundaries for the reference adapter. Network defaults to none; a custom Docker network still requires a host firewall/proxy for DNS/egress claims. Import rejects symlink escapes, devices, FIFOs, and sockets; export counts entries/bytes and hashes before host retention. Secrets in `secrets` are redacted from adapter errors and never exported as environment metadata. Unified workspace mode reuses existing sandbox/repo/coding hard caps and does not introduce unbounded host↔container sync loops. Host mode and `allowMixedWorkspaceWiring` never claim disposable containment. Durable workflow denial/cancellation is terminal and attributable; approved resume still fails if roots, command rules, read-only mode, or other policy changed while suspended. Cache keys are fixed-size SHA-256 digests of selected identity plus action shape; caches remain process-local, retain at most 1,000 decisions with oldest-entry eviction, and have no default/global mode. Path checks and cache lookup are local; sandbox latency belongs to the supplied adapter and Docker daemon.
|
|
153
184
|
|
|
185
|
+
The egress proxy is a policy enforcer, not a firewall: it cannot stop a container whose Docker network reaches the internet directly. Egress attestation (`denyDirectEgress: true`) is a claim the host must make true by network topology; the adapter records it as evidence and fails closed when it is absent or malformed. The proxy performs no TLS interception, no DNS rebinding of its own beyond pinning, and no content filtering; audit records contain no secrets. Frozen caps: 32 concurrent connections (hard 256), 64 MiB request/response bytes (hard 1 GiB), 600 s transfer time (hard 1 h), 128 rules (hard 1,024), 5 redirect hops (hard 10).
|
|
186
|
+
|
|
154
187
|
## Related APIs
|
|
155
188
|
|
|
156
189
|
- [Coding agent tools](coding-agent-tools.md): durable plan/todo Markdown helpers and `state.coding` checkpoint metadata for restart/resume without a second runtime
|
|
157
190
|
- [Workflows](workflows.md): `runWorkflow` / `resumeWorkflow` / `startWorkflowBackground` composition for coding tasks
|
|
158
191
|
- [Host security guide](host-security.md)
|
|
159
192
|
- [Performance limits](performance.md)
|
|
193
|
+
- [Forge integration](forge-integration.md): GitHub adapter whose mutations can be routed through the egress proxy
|
|
160
194
|
- [Tool execution primitives](tool-execution-primitives.md)
|
|
161
195
|
- [Security/auth/trust](settings-auth-trust-security.md)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# GitHub forge integration
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`createGitHubForge` is an optional host-activated adapter in `@arnilo/prism-coding-agent` for the six **proven** GitHub operations: issue context read, authenticated push, pull-request create/update, review comments, and check/status retrieval, plus bounded handoff reconciliation. It is GitHub-first by freeze decision — there is no multi-forge generic abstraction, and no octokit dependency: HTTP uses Node's global `fetch` with a bounded streaming reader, timeout, and rate-limit backoff; push reuses the existing `BoundGitRunner` with the token injected via `GIT_CONFIG_*` environment variables (`http.extraHeader`) — never argv, never persisted, never in logs/events. Every mutation flows through Phase 8 approval (`ExecutionPolicy`) and Phase 7 `ToolEffectStore` idempotency keys; a retry after a completed call returns the existing record instead of duplicating the PR or comment.
|
|
6
|
+
|
|
7
|
+
| Export | Purpose |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `createGitHubForge(options)` | Build a `ForgeOperations` adapter bound to one `"owner/repo"`. |
|
|
10
|
+
| `ForgeOperations` | `issueContext` / `push` / `createPullRequest` / `updatePullRequest` / `createReviewComment` / `checks` / `reconcileHandoff`. |
|
|
11
|
+
| `ForgeIssueContext` / `ForgePullRequest` / `ForgeCheck` | Bounded response shapes (state, head/base, url, check status/conclusion). |
|
|
12
|
+
| `ForgeHandoffReport` | Push/PR/check state, commits, changed paths, diffstat, warnings; never auto-merges. |
|
|
13
|
+
| `ForgeError` | Typed failures: `ERR_PRISM_FORGE_AUTH` / `_API` / `_STALE` / `_RATE_LIMIT` / `_LIMIT` / `_OWNERSHIP`. |
|
|
14
|
+
| `resolveForgeLimits` / `DEFAULT_MAX_FORGE_*` / `HARD_MAX_FORGE_*` | Pages per operation, payload bytes, comments, concurrency ceiling, request timeout. |
|
|
15
|
+
|
|
16
|
+
## When to use it
|
|
17
|
+
|
|
18
|
+
Use when a coding agent needs to open/update PRs, comment on reviews, push a branch with scoped credentials, or verify handoff state against GitHub before deciding the next step. Do not use as a general GitHub SDK, an auto-merge engine (`reconcileHandoff` never merges), or a replacement for host-owned App installation flows — the adapter resolves credentials through the host's `CredentialResolverSource`-compatible resolver and never stores them.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createGitHubForge } from "@arnilo/prism-coding-agent";
|
|
22
|
+
|
|
23
|
+
const forge = createGitHubForge({
|
|
24
|
+
credentials: { name: "github", resolver: myCredentialResolver }, // App installation token preferred; PAT allowed
|
|
25
|
+
repository: "acme/repo",
|
|
26
|
+
cwd: workspaceRoot, // local checkout for push
|
|
27
|
+
git: { gitPath: "/usr/bin/git" },
|
|
28
|
+
policy, // mutations gated here; denials propagate as ERR_PRISM_EXECUTION_DENIED, no request attempted
|
|
29
|
+
effectStore, // REQUIRED: idempotency + unknown-outcome recovery
|
|
30
|
+
identity, ownership, sessionId, runId, // durable context for mutation effect keys
|
|
31
|
+
});
|
|
32
|
+
const pr = await forge.createPullRequest({ head: "feature/x", base: "main", title: "Add x", body: "Closes #1" });
|
|
33
|
+
const report = await forge.reconcileHandoff({ base: "main", head: "feature/x" });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Inputs / request
|
|
37
|
+
|
|
38
|
+
`createGitHubForge` options:
|
|
39
|
+
|
|
40
|
+
| Field | Type | Purpose |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `credentials` | `ForgeCredentialResolverSource` | `{ name, resolver }`; resolver is called per request with `provider: "github"` and `metadata.repository`. |
|
|
43
|
+
| `repository` | `string` | `"owner/repo"`, validated at construction and immutable per instance. |
|
|
44
|
+
| `cwd` | `string` | Local checkout the adapter pushes from. |
|
|
45
|
+
| `git` | `CreateGitRunnerOptions \| BoundGitRunner` | Reused for authenticated push (`git push origin <ref>`). |
|
|
46
|
+
| `policy?` | `ExecutionPolicy` | Every mutation is gated (`kind: "forge"`, risk `high`) before any network or git call. |
|
|
47
|
+
| `effectStore` | `ToolEffectStore` | **Required.** `begin → markDispatched → execute → complete/fail` per mutation. |
|
|
48
|
+
| `identity?` / `ownership?` / `sessionId?` / `runId?` | durable context | Required for mutations; without them mutations fail closed with `ERR_PRISM_FORGE_LIMIT`. Tenant mismatch fails at construction with `ERR_PRISM_FORGE_OWNERSHIP`. |
|
|
49
|
+
| `limits?` | `ForgeLimits` | Pages per operation (default 10, hard 100), payload bytes (1 MiB / 8 MiB), comments per review (100 / 1000), request concurrency ceiling (4 / 8), timeout (30 s / 120 s). |
|
|
50
|
+
| `fetch?` | `typeof fetch` | Host-injectable fetch (defaults to `globalThis.fetch`); route through an egress proxy or inject a mock in tests. |
|
|
51
|
+
|
|
52
|
+
Mutation inputs are validated before any request: refs must not start with `-` or contain NUL/newlines and fit the git ref cap; numbers must be positive integers; PR title/body and comment body are required and bounded by `payloadBytes`. `updatePullRequest` with no fields to change fails closed.
|
|
53
|
+
|
|
54
|
+
## Outputs / response / events
|
|
55
|
+
|
|
56
|
+
- `issueContext({ number })` → `ForgeIssueContext` (title, state, body, labels, author, updatedAt, url). Read-only; no policy gate, no effect record.
|
|
57
|
+
- `push({ refspec? })` → `{ remoteRef }` (`refs/heads/<branch>`). Resolves the current branch with `git rev-parse` when no refspec is given. Token reaches git as `GIT_CONFIG_VALUE_0 = "AUTHORIZATION: basic <base64(x-access-token:<token>)>"`; argv carries only `git push origin <ref>`.
|
|
58
|
+
- `createPullRequest({ head, base, title, body })` → `ForgePullRequest`. Idempotent twice over: the effect key replays completed results, and a 422 `"already exists"` response fetches and returns the open PR instead of failing.
|
|
59
|
+
- `updatePullRequest({ number, title?, body?, state? })` → `ForgePullRequest`. 422 (stale head/base) maps to `ERR_PRISM_FORGE_STALE`.
|
|
60
|
+
- `createReviewComment({ number, path, line, body })` → `{ id }`. Retry with identical args replays the completed effect — no duplicate comment.
|
|
61
|
+
- `checks({ ref })` → `ForgeCheck[]`: check-runs plus commit statuses, deduped by name, paginated up to `pagesPerOperation`.
|
|
62
|
+
- `reconcileHandoff({ base, head })` → `ForgeHandoffReport`: `pushed`, `aheadBy`/`behindBy`, `alreadyUpToDate`, `alreadyMerged`, `pullRequest?`, `checks`, bounded `commits`/`changedPaths`/`diffstat`, `warnings`. A missing head ref reports `pushed: false` with a warning. Never pushes, opens, or merges — the host decides next steps from the report.
|
|
63
|
+
|
|
64
|
+
Every mutation result is recorded in the `effectStore` with a stable key derived from tenant + session + run + operation + canonical arguments. Replay of a completed record returns the stored result; replay of a `dispatched`/`unknown` record fails closed (`ERR_PRISM_FORGE_API`, "requires reconciliation") so a crash mid-mutation never duplicates work — verify actual state via `reconcileHandoff`, then resolve through the store.
|
|
65
|
+
|
|
66
|
+
## Request/response example
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"action": { "kind": "forge", "operation": "create_pull_request", "risk": "high", "metadata": { "repository": "acme/repo", "head": "feature/x", "base": "main" } },
|
|
71
|
+
"decision": { "allowed": true }
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Implementation example
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { createGitHubForge } from "@arnilo/prism-coding-agent";
|
|
79
|
+
|
|
80
|
+
const forge = createGitHubForge({
|
|
81
|
+
credentials: { name: "github-app", resolver },
|
|
82
|
+
repository: "acme/repo",
|
|
83
|
+
cwd: "/srv/jobs/task-1/checkout",
|
|
84
|
+
git: { gitPath: "/usr/bin/git" },
|
|
85
|
+
policy,
|
|
86
|
+
effectStore,
|
|
87
|
+
identity, ownership, sessionId, runId,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await forge.push({ refspec: "feature/x" });
|
|
91
|
+
await forge.createPullRequest({ head: "feature/x", base: "main", title: "Add x", body: "Closes #1" });
|
|
92
|
+
const checks = await forge.checks({ ref: "feature/x" });
|
|
93
|
+
const report = await forge.reconcileHandoff({ base: "main", head: "feature/x" });
|
|
94
|
+
if (!report.alreadyMerged && report.pushed) {
|
|
95
|
+
// host decides: update PR, comment, or stop — the adapter never auto-merges
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Extension and configuration notes
|
|
100
|
+
|
|
101
|
+
Credentials resolve per call through the host resolver; GitHub App installation tokens and PATs are both supported (same `Bearer` REST header and `x-access-token` git header). Least-privilege guidance: App installation tokens with `contents: write` + `pull_requests: write` + `issues: read` cover the six operations; PATs should be fine-grained to the single repository and read/write scope needed. Policy denials propagate as the core `ExecutionDeniedError` (`ERR_PRISM_EXECUTION_DENIED`) — no forge request is attempted — so hosts can distinguish refusal from forge failure. Pagination is sequential (per-request `pagesPerOperation` cap); `requestConcurrency` is a validated ceiling, not a target. The adapter performs no DNS/egress control itself — sandboxed hosts route forge traffic through the Phase 9 egress policy (Task 6).
|
|
102
|
+
|
|
103
|
+
## Security and performance notes
|
|
104
|
+
|
|
105
|
+
Tokens never appear in argv, git config files, logs, model context, or stored events: REST uses the `Authorization` header on a bounded `fetch`, and git uses `GIT_CONFIG_*` environment variables scoped to the single push process. Request bodies and responses are bounded by `payloadBytes` (streamed, content-length pre-checked); timeouts and rate-limit backoff respect `requestTimeoutMs` and `Retry-After`; page fetches stop at `pagesPerOperation`. Repository binding is fixed at construction; tenant binding is checked per mutation; ownership mismatch fails closed. Rate-limit responses map to `ERR_PRISM_FORGE_RATE_LIMIT`, 404 to `ERR_PRISM_FORGE_API`, 422 to `ERR_PRISM_FORGE_STALE`, 401/403 to `ERR_PRISM_FORGE_AUTH`, and cap violations to `ERR_PRISM_FORGE_LIMIT`.
|
|
106
|
+
|
|
107
|
+
## Related APIs
|
|
108
|
+
|
|
109
|
+
- [Tool effects](tool-effects.md): `ToolEffectStore` idempotency and unknown-outcome recovery used by every forge mutation
|
|
110
|
+
- [Coding agent tools](coding-agent-tools.md): `createGitTools`, `createBoundGitRunner`, `createGitOperations` (push rides the same runner)
|
|
111
|
+
- [Coding execution approval and sandboxing](coding-security.md): `ExecutionPolicy` gates, egress policy (Phase 9)
|
|
112
|
+
- [Host security guide](host-security.md)
|
|
113
|
+
- [Performance limits](performance.md)
|
package/docs/host-security.md
CHANGED
|
@@ -148,6 +148,7 @@ Wire those values where they matter: provider adapters receive the resolved cred
|
|
|
148
148
|
- AG-UI A2A requires exact-origin verified client, host-owned task selection/correlation, explicit data/tool/A2UI projection, and reauthorized follow/cancel.
|
|
149
149
|
- `@arnilo/prism-server` exposes no agent/workflow by default and requires `authorize()` for every matched operation. Derive complete tenant/account/user ownership from validated host identity, never request JSON. Workflow active identity and cancellation compare exact ownership; a tenant-only scope intentionally cannot cancel a checkpoint/run carrying account or user identity. The artifact review service (`createArtifactService`) requires authenticated identity + thread ownership on every attach/revise/compare/approve/reject/download, resolves concurrent reviewers via checkpoint CAS (no lost approvals), rejects local filesystem paths in `uri`/citations, redacts records before persist and on response, and serves downloads only through signed expiring links that are reauthorized against the token's ownership per request. Pass the current explicitly revised workflow definition so recursive hash mismatch fails before abort or durable mutation. Configure exact host/origin allow-lists where needed, wire redaction before execution, retain tool/workflow policy checks, and adapt the Web handler behind host TLS/rate limits. Disconnect abort is default; persistent reconnect/status belongs to durable workflow checkpoints, not an invented in-memory agent result cache.
|
|
150
150
|
- Coding tools from `@arnilo/prism-coding-agent` accept an optional `ExecutionPolicy` checked inside each tool before side effects; shared policy propagation includes `createReadOnlyTools()`. They enforce finite text-scan/image/edit/write/shell limits, repository list/search depth/entry/match/scan/time caps, structured Git path/ref/message/output/patch/worktree caps, named-check concurrency/output caps, a 600-second default shell wall time, and a 64 MiB default total-output ceiling. Opt-in `createGitTools()` uses argument arrays with hooks/credential prompts/external diff disabled, requires host `commitIdentity` for commits, and never pushes or opens PRs. Successful truncated shell output leaves a host-owned exclusive `0600` temp file; delete `metadata.fullOutputPath` after use. Error/abort/timeout/overflow removes unpublished spills. Custom read/edit/shell/repository backends must honor supplied caps/signals. Use `@arnilo/prism-coding-security` for path roots, command rules, identity-scoped approval caching, required `workspaceMode` on `createSandboxCodingComposition()` / `createSandboxCodingTools()`, and the optional `createDockerSandbox()` reference adapter. **Host mode is never contained execution** (`containmentClaim: false`). Sandbox mode claims containment only when FS backends target the disposable tree; mixed wiring requires `allowMixedWorkspaceWiring` and still does not claim containment. Limits alone are not containment: construct the Docker adapter (absolute CLI, digest-pinned image, network none by default) or an equivalent host sandbox before treating coding execution as production-safe. Docker daemon/image trust, egress firewall/proxy, and artifact retention remain host-owned.
|
|
151
|
+
- Allow-list egress (0.0.26, `@arnilo/prism-coding-security`): `createEgressPolicy()` is deny-all with exact host/port/protocol rules and frozen `npm-registry`/`github` presets; `createAllowListEgressProxy()` is an HTTP forward proxy + CONNECT tunnel that pins DNS answers and verifies the connected address (rebinding defense), denies private/link-local/metadata ranges unless a rule opts in, re-validates every redirect hop against policy, and cuts oversized/slow transfers at frozen byte/time caps. TLS passes through without interception. Every allow/deny writes an audit record with no secrets. The proxy is inert until `start()`; `reloadPolicy()` is the only rule change path. `composeEgressSandboxNetwork(proxy.attestation(), name)` records validated attestation as `prism.egress.*` container labels — evidence, not enforcement: the host must restrict the Docker network so the proxy is the only reachable path, and `denyDirectEgress: true` is a claim the host makes true by topology. The proxy is not a firewall and cannot stop a container whose network reaches the internet directly.
|
|
151
152
|
- Optional `@arnilo/prism-browser` requires a host-supplied Playwright Browser (`playwright-core@1.61.0` peer). Import is inert. One non-persistent context belongs to one run; actions serialize; refs are snapshot-scoped; CSS/evaluate/CDP/persistent profiles are denied. Context routing + `serviceWorkers: "block"` deny file/data/blob/devtools/private/loopback by default and require contained-proxy attestation for external egress (Playwright routing is defense in depth, not DNS containment). Uploads are realpath-rooted; downloads quarantine with hash/MIME until host `approveRelease`; screenshots return bounded `ImageContent`. Observation vs mutation/high-impact actions map to `ExecutionPolicy`. Treat snapshot/page text as untrusted external content. Close contexts with `browser_close` or `manager.closeRun(runId)` on abort/terminal. Browser control endpoint, binary/image pin, and real egress firewall/proxy remain host-owned. Shared sandbox: `createSharedSandboxBrowserOptions()` + `assertBrowserSandboxNetwork()`.
|
|
152
153
|
- Browser verified-state checkpoints (0.0.14, `createBrowserCheckpointLedger()`) store URL + domain-state hash + host data refs only — never serialized browser internals (cookies/storage/contexts). After any resume/interruption the ledger fails closed (`assertVerifiedBeforeSideEffect`) until the host reloads + verifies, so side effects never replay on stale state.
|
|
153
154
|
- Device adapters (0.0.14, `resolveDevicePolicy`/`assertDeviceAdmit`) are deny-by-default: admission fails closed without explicit `enabled`, an explicit sandbox, approval (when required), an under-budget session count, and shared `RunLimits`. Stream chunks over the frozen cap are dropped with a marker; telemetry is redacted before emit/persist. No vendor voice/desktop package ships in 0.0.14 (demand-gated 0.1.x); device adapters cannot broaden consent/memory/network/file/browser/connector/tool permissions (gate 8).
|
package/docs/index.md
CHANGED
|
@@ -15,11 +15,11 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
|
|
|
15
15
|
- [Agent definitions](agent-definitions.md): resolve declarative `AgentDefinition` values via `resolveAgentDefinition`, and turn app-config `<configRoot>/agents/<name>/AGENT.md` bundles into runnable agents via `discoverAgentBundles` / `resolveAgentBundle` (explicit tool/skill activation by name, fail-closed omitted capabilities, migration-only `activateAllCapabilities`, strict duplicate scope checks, configurable prompt layers, no auto-discovery).
|
|
16
16
|
- [Agent loops](agent-loops.md): replaceable per-run control loops — `singleShotLoop` default, opt-in bounded artifact-loop tool rounds, and durable custom-loop `revision`/`snapshot`/`restore` hooks with fail-closed resume.
|
|
17
17
|
- [Guardrails](guardrails.md): typed fail-closed input/output/tool checks with buffered provider output and redacted decision records.
|
|
18
|
-
- [Agent events](agent-events.md): live `session.subscribe` plus durable `AgentEventSource` page/subscribe/resume for cross-replica reconnect; message/progress deltas never create spans.
|
|
18
|
+
- [Agent events](agent-events.md): live `session.subscribe` plus durable `AgentEventSource` page/subscribe/resume for cross-replica reconnect; message/progress deltas never create spans. Durable sources: PostgreSQL `LISTEN`/`NOTIFY` (reference, `@arnilo/prism-session-store-postgres` root export) and NATS JetStream (`@arnilo/prism-session-store-nats`, FR-5).
|
|
19
19
|
- [Observability](observability.md): OTel GenAI agent/provider/tool hierarchy, host context parenting, bounded trace linkage, safe evaluation events, controlled metrics, and exporter isolation.
|
|
20
20
|
- [Evaluations](evaluations.md): deterministic and bounded trace/model-judge/pairwise scoring, CI thresholds, OTel trace-reference linkage, coding/browser adversarial fixtures, ID-only linkage to immutable owned run feedback, and optional durable PostgreSQL records.
|
|
21
21
|
- [Runs and usage ledger](runs-and-usage.md): durable run/event/tool/usage persistence, optional bounded FIFO durability policies, session snapshot caching, and immutable run/trace feedback.
|
|
22
|
-
- [Performance limits](performance.md): 0.0.25 durable-loop/HITL/A2UI network-free evidence, 0.0.24 distributed event/effect PostgreSQL evidence, 0.0.23 enterprise state evidence, 0.0.15 network-free provider/RAG/memory benchmark evidence and frozen caps, bounded evaluation traces/judges/reports, and production sizing assumptions.
|
|
22
|
+
- [Performance limits](performance.md): 0.0.26 coding-intelligence/process/forge/egress network-free evidence, 0.0.25 durable-loop/HITL/A2UI network-free evidence, 0.0.24 distributed event/effect PostgreSQL evidence, 0.0.23 enterprise state evidence, 0.0.15 network-free provider/RAG/memory benchmark evidence and frozen caps, bounded evaluation traces/judges/reports, and production sizing assumptions.
|
|
23
23
|
- [Structured output](structured-output.md): the `Artifact*` seam plus provider-native `StructuredOutputOptions` / `structuredOutputMode` for capable models.
|
|
24
24
|
|
|
25
25
|
## Compaction/session memory
|
|
@@ -35,7 +35,7 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
|
|
|
35
35
|
- [SQLite persistence](sqlite-persistence.md): optional `better-sqlite3` adapter with session/run storage, checkpoints/leases, feedback, FTS `searchSessions` (migration-v4), and transactionally verified/backfilled migration metadata.
|
|
36
36
|
- [PostgreSQL persistence](postgres-persistence.md): optional pooled `pg` adapter with session/run/checkpoint/lease/feedback storage, FTS `searchSessions` (migration-v4), advisory-locked checksummed/full-shape migrations, and opt-in live conformance.
|
|
37
37
|
- [Enterprise PostgreSQL state](enterprise-postgres-state.md): optional `@arnilo/prism-enterprise-postgres` composition for durable policy/evaluation/work-idempotency/model-router/`toolEffects` state, exact ownership, checksummed migrations, and explicit cleanup.
|
|
38
|
-
- [Migration guide](migration.md): **0.0.25** durable custom loops and shared human-in-the-loop decisions; **0.0.24** distributed events and recoverable tool effects; **0.0.23** enterprise PostgreSQL state adapters (async router and work reconciliation); **0.0.22** third-party behavior integrations (Caveman, Ponytail); **0.0.21** coding-tool capability gaps (`outputMode`, glob, read-before-write, delete/move, aggregator 9/4); **0.0.20** progressive skill disclosure, empty registry default, `load_skill`, priority budget demotion, optional tool-result fold; **0.0.19** observational memory lifecycle; **0.0.15** OpenAI hosted tools/continuation/Realtime, exact AI SDK v4 matrix, RAG lifecycle/reranking/trust/status, and memory export/rebuild; **0.0.14** conversations, memory consent/lifecycle, artifact co-work review, AG-UI co-work events, scoped M365/GWS OAuth connectors, browser checkpoints, device contracts, and Alibaba/Ollama providers; plus prior release migrations.
|
|
38
|
+
- [Migration guide](migration.md): **0.0.26** coding intelligence, managed processes, forge, and safe egress; **0.0.25** durable custom loops and shared human-in-the-loop decisions; **0.0.24** distributed events and recoverable tool effects; **0.0.23** enterprise PostgreSQL state adapters (async router and work reconciliation); **0.0.22** third-party behavior integrations (Caveman, Ponytail); **0.0.21** coding-tool capability gaps (`outputMode`, glob, read-before-write, delete/move, aggregator 9/4); **0.0.20** progressive skill disclosure, empty registry default, `load_skill`, priority budget demotion, optional tool-result fold; **0.0.19** observational memory lifecycle; **0.0.15** OpenAI hosted tools/continuation/Realtime, exact AI SDK v4 matrix, RAG lifecycle/reranking/trust/status, and memory export/rebuild; **0.0.14** conversations, memory consent/lifecycle, artifact co-work review, AG-UI co-work events, scoped M365/GWS OAuth connectors, browser checkpoints, device contracts, and Alibaba/Ollama providers; plus prior release migrations.
|
|
39
39
|
- [Node JSONL session store](node-jsonl-session-store.md): development-only JSONL file adapter for single-process Node hosts; no cross-process safety; `searchSessions` throws `SessionSearchUnsupportedError`.
|
|
40
40
|
- [Persistence, credentials, and multimodality primitives](persistence-credentials-multimodality-primitives.md): Plan 056 inventory — session/run-ledger/persistence contracts, credential/OAuth seams, content/resource/model capabilities, package dependency matrix, conformance matrix, and threat model for production adapters.
|
|
41
41
|
|
|
@@ -73,8 +73,11 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
|
|
|
73
73
|
- [Work connectors](work-connectors.md): connector principles, capability gates, scoped OAuth establishment (0.0.14), and out-of-scope boundaries (Slack/Teams channels not shipped) for Microsoft 365 / Google Workspace.
|
|
74
74
|
- [Browser automation](browser-automation.md): optional `@arnilo/prism-browser` with host-supplied Playwright contexts, AI-mode snapshots/refs, ordered `browser_open`/`browser_snapshot`/`browser_act`/`browser_close`, egress/side-effect/upload/download/screenshot policy, finite page/action/snapshot/network/artifact caps, and 0.0.14 verified-state checkpoints with reload/verify-before-side-effect.
|
|
75
75
|
- [Device adapters](device-adapters.md): deny-by-default realtime voice / desktop-control contract + conformance (0.0.14); no vendor package — admission fails closed without explicit consent+sandbox+approval, stream bounds, shared `RunLimits`, redacted telemetry.
|
|
76
|
-
- [Coding agent tools](coding-agent-tools.md): optional `shell`, `read`, `write`, `edit`, `repo_list`, `repo_search`, `glob`, `delete`, and `move` definitions plus opt-in `createGitTools()` / `coding_check`, opt-in `createAskUserDecisionTool` (single/multi/free-text + durable suspend glue), and `runCodingGoalVerify`; durable plan/todo Markdown helpers with workflow `state.coding` checkpoint metadata; streamed text pages, `repo_search` `outputMode`, bounded glob, optional read-before-write, finite Git/check/plan/ask caps, bounded image/edit reads and write/edit payloads, finite shell wall/total-output limits, secure host-owned spill cleanup, pluggable bounded operation contracts, per-path mutation serialization, and optional `ExecutionPolicy`. No PDF/trash/PTY
|
|
77
|
-
- [
|
|
76
|
+
- [Coding agent tools](coding-agent-tools.md): optional `shell`, `read`, `write`, `edit`, `repo_list`, `repo_search`, `glob`, `delete`, and `move` definitions plus opt-in `createGitTools()` / `coding_check`, opt-in `createAskUserDecisionTool` (single/multi/free-text + durable suspend glue), and `runCodingGoalVerify`; durable plan/todo Markdown helpers with workflow `state.coding` checkpoint metadata; streamed text pages, `repo_search` `outputMode`, bounded glob, optional read-before-write, optional Git-aware (`createGitAwareRepositoryOperations`) ignore-aware enumeration with native fallback, finite Git/check/plan/ask caps, bounded image/edit reads and write/edit payloads, finite shell wall/total-output limits, secure host-owned spill cleanup, pluggable bounded operation contracts, per-path mutation serialization, and optional `ExecutionPolicy`. No PDF/trash/PTY in the 0.0.21 baseline; Phase 9 adds optional language intelligence (separate page). Limits do not sandbox host access—gate with permission/trust policy and `@arnilo/prism-coding-security`.
|
|
77
|
+
- [Language intelligence](language-intelligence.md): optional host-activated `createLanguageIntelligence` — bounded in-package LSP 3.17 JSON-RPC client (Content-Length framing), host-selected server command/args per language, workspace symbols/definitions/references/diagnostics/hover/rename; lazy spawn; URI root confinement; rename gated by `ExecutionPolicy` + atomic write/mutation queue; frozen message/diagnostic/pending/result/timeout/server caps. No `vscode-languageserver-protocol` dependency.
|
|
78
|
+
- [Process sessions](process-sessions.md): optional host-activated `createProcessSessions` — long-running process registry (start/cursor-paged output/input/wait/signal/kill/release), native or sandbox `startProcess` backend (fail closed when absent), ownership/identity + expiry sweep on access, `reconcile` / sandbox-loss → `unknown` (never fabricates exitCode), durable command fingerprint metadata, `CodingProcessEvent` host sink, `ExecutionPolicy` before spawn and on mutate, frozen session/input/lifetime/output caps; PTY fails closed as unsupported.
|
|
79
|
+
- [Forge integration](forge-integration.md): optional host-activated `createGitHubForge` — reference GitHub adapter (issue context, authenticated push via `BoundGitRunner` + `GIT_CONFIG_*` credential injection, PR create/update, review comments, check/status retrieval, bounded `reconcileHandoff`), every mutation gated by `ExecutionPolicy` and recorded in `ToolEffectStore` (retry never duplicates PRs/comments), typed `ForgeError` codes (auth/API/stale/rate-limit/limit/ownership), frozen page/payload/comment/concurrency/timeout caps, no octokit dependency, tokens never in argv/logs/events.
|
|
80
|
+
- [Coding execution approval and sandboxing](coding-security.md): path/command approval, identity-scoped caching, shell-turn exclusivity, required `workspaceMode` (`host`/`sandbox`) with fail-closed mixed wiring, `createSandboxCodingComposition()` containment metadata, disposable Docker/OCI sandbox reference with bounded workspace import/export, optional `DisposableSandbox.startProcess` / `SandboxProcessHandle` for process-session backends, and allow-list egress (`createEgressPolicy` deny-all exact rules + frozen presets, `createAllowListEgressProxy` HTTP/CONNECT proxy with pinned-DNS rebinding defense, private/metadata IP denial, redirect re-validation + hop cap, byte/time caps, per-decision audit, `composeEgressSandboxNetwork` attestation recorded as `prism.egress.*` labels; TLS pass-through, no interception).
|
|
78
81
|
|
|
79
82
|
## Extensions/plugins
|
|
80
83
|
- [Contribution discovery (workspace)](contribution-discovery.md): opt-in, realpath-contained directory scanner turning `SKILL.md`/`manifest.json` into inert `DiscoveredContribution` envelopes the host registers — no `import()`, no auto-activate, no provider scanning. Per-agent bundles remain app-controlled and are documented under Agent/session runtime.
|
|
@@ -93,8 +96,8 @@ Prism is a TypeScript/Node.js agent harness. Host apps and extension packages ow
|
|
|
93
96
|
|
|
94
97
|
## Multi-agent and interoperability
|
|
95
98
|
- [Supervisor delegation](supervisors.md): optional explicit child allow-list, derived memory scopes, narrowing-only permissions, lifecycle hooks, nested delegation, cancellation, finite budgets, host-projected delegation telemetry, and separate A2A durable adapter boundary.
|
|
96
|
-
- [A2A interoperability](a2a.md): A2A 1.0 JSON-RPC/HTTPS cards plus host-owned durable task get/list/cancel/subscribe, shared `AgentEventSource` task adapter, bounded rich parts/replay, principal-scoped push configs, exact-origin verified client,
|
|
97
|
-
- [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional `@arnilo/prism-ag-ui` full AG-UI 0.0.57 input/event/capability mapper, authorized Web handler/distributed source follow, opt-in A2UI painting middleware, explicit hardened MCP/MCP Apps/remote A2A adapters, and stable ACP sibling over shared redacted event and durable-approval seams; 0.0.14 adds reconnectable co-work events.
|
|
99
|
+
- [A2A interoperability](a2a.md): A2A 1.0 JSON-RPC/HTTPS cards plus host-owned durable task get/list/cancel/subscribe, shared `AgentEventSource` task adapter, bounded rich parts/replay, principal-scoped push configs, exact-origin verified client, rich stream seam for explicit AG-UI fronting, and server-side `createAgUiA2AServer` exposure of a local AG-UI agent (0.0.26).
|
|
100
|
+
- [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional `@arnilo/prism-ag-ui` full AG-UI 0.0.57 input/event/capability mapper, authorized Web handler/distributed source follow, opt-in A2UI painting middleware, explicit hardened MCP/MCP Apps/remote A2A adapters, a framework-free reference renderer subpath (`@arnilo/prism-ag-ui/renderer`, 0.0.26), and stable ACP sibling over shared redacted event and durable-approval seams; 0.0.14 adds reconnectable co-work events.
|
|
98
101
|
- [AG-UI adoption evaluation](ag-ui-adoption.md): official 0.0.57 input/event/capability matrix and shipped hardened MCP/MCP Apps/A2A handshake boundaries.
|
|
99
102
|
|
|
100
103
|
## CLI/RPC
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Language intelligence
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`createLanguageIntelligence` is an optional host-activated contract in `@arnilo/prism-coding-agent` that talks to **host-selected** language servers over one bounded in-package JSON-RPC client (LSP 3.17 Content-Length framing). It exposes workspace symbols, definitions, references, diagnostics, hover, and rename/workspace edits. No `vscode-languageserver-protocol` dependency. Nothing spawns on import or construction — servers start lazily on first use and stop on `dispose()`.
|
|
6
|
+
|
|
7
|
+
| Export | Purpose |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `createLanguageIntelligence(options)` | Build a `LanguageIntelligence` instance for one workspace root. |
|
|
10
|
+
| `LanguageIntelligence` | Contract: `workspaceSymbols`, `definitions`, `references`, `diagnostics`, `hover`, `rename`, `dispose`. |
|
|
11
|
+
| `LanguageServerSpec` | Host allow-listed `{ command, args?, languages, env? }`. Never model-supplied. |
|
|
12
|
+
| `LanguageLocation` / `LanguageSymbol` / `LanguageDiagnostic` / `LanguageWorkspaceEdit` | Normalized result shapes (paths workspace-relative; positions LSP 0-based). |
|
|
13
|
+
| `LanguageIntelligenceError` | Typed fail-closed errors (`ERR_PRISM_LSP_*`). |
|
|
14
|
+
| `resolveLanguageIntelligenceLimits` / `DEFAULT_MAX_LSP_*` / `HARD_MAX_LSP_*` | Finite caps for message bytes, diagnostics/file, pending requests, results/query, timeout, servers. |
|
|
15
|
+
| `encodeLspFrame` / `LspFrameReader` | Framing helpers (tests/hosts). |
|
|
16
|
+
|
|
17
|
+
## When to use it
|
|
18
|
+
|
|
19
|
+
Use when a host wants IDE-like language intelligence without embedding a parser framework or trusting model-chosen server commands. Wire host-pinned server binaries (for example `typescript-language-server --stdio`) and gate renames with the same `ExecutionPolicy` used for write/edit tools.
|
|
20
|
+
|
|
21
|
+
Do not use this as a sandbox, tool registry, or process session manager. Optional process-session registration of LSP children can use `createProcessSessions` ([Process sessions](process-sessions.md)).
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createLanguageIntelligence } from "@arnilo/prism-coding-agent";
|
|
25
|
+
|
|
26
|
+
const lang = createLanguageIntelligence({
|
|
27
|
+
workspaceRoot,
|
|
28
|
+
servers: {
|
|
29
|
+
typescript: {
|
|
30
|
+
command: "/usr/bin/typescript-language-server",
|
|
31
|
+
args: ["--stdio"],
|
|
32
|
+
languages: ["typescript", "typescriptreact"],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
policy: hostExecutionPolicy, // rename gated like edit
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const defs = await lang.definitions({ file: "src/a.ts", line: 10, character: 4 });
|
|
39
|
+
await lang.rename({ file: "src/a.ts", line: 10, character: 4, newName: "renamed" });
|
|
40
|
+
await lang.dispose();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Inputs / request
|
|
44
|
+
|
|
45
|
+
`createLanguageIntelligence` options:
|
|
46
|
+
|
|
47
|
+
| Field | Type | Purpose |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `workspaceRoot` | `string` | Absolute or relative workspace root; all file URIs must stay inside. |
|
|
50
|
+
| `servers` | `Record<string, LanguageServerSpec>` | Host map keyed by server name; size capped (`maxServers`). |
|
|
51
|
+
| `limits?` | `LanguageIntelligenceLimits` | Optional overrides; invalid values fail instead of clamping. |
|
|
52
|
+
| `policy?` | `ExecutionPolicy` | Applied before rename writes (`kind: "edit"`, `operation: "rename"`). |
|
|
53
|
+
|
|
54
|
+
`LanguageServerSpec`:
|
|
55
|
+
|
|
56
|
+
| Field | Purpose |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `command` | Host allow-listed executable path. |
|
|
59
|
+
| `args?` | Fixed argv (never from the model). |
|
|
60
|
+
| `languages` | Language ids this server handles (matched from file extension). |
|
|
61
|
+
| `env?` | Extra env merged onto `process.env` for the child. |
|
|
62
|
+
|
|
63
|
+
Operation inputs use workspace-relative `file` plus LSP **0-based** `line` / `character`. `rename` also requires `newName`.
|
|
64
|
+
|
|
65
|
+
## Outputs / response / events
|
|
66
|
+
|
|
67
|
+
| Method | Result |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `workspaceSymbols(query)` | `LanguageSymbol[]` (capped). |
|
|
70
|
+
| `definitions` / `references` | `LanguageLocation[]` (capped). |
|
|
71
|
+
| `diagnostics(file?)` | Normalized `LanguageDiagnostic[]` (per-file and aggregate caps). |
|
|
72
|
+
| `hover` | `{ text }` or `undefined`. |
|
|
73
|
+
| `rename` | `LanguageWorkspaceEdit` after policy-checked atomic writes. |
|
|
74
|
+
| `dispose` | Stops all spawned servers (bounded). |
|
|
75
|
+
|
|
76
|
+
Errors are `LanguageIntelligenceError` with codes: `ERR_PRISM_LSP_FRAMING`, `ERR_PRISM_LSP_SERVER`, `ERR_PRISM_LSP_TIMEOUT`, `ERR_PRISM_LSP_LIMIT`, `ERR_PRISM_LSP_UNSUPPORTED`, `ERR_PRISM_LSP_WORKSPACE`.
|
|
77
|
+
|
|
78
|
+
No package-owned events; hosts observe via their own run/tool wiring.
|
|
79
|
+
|
|
80
|
+
## Request/response example
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
// definitions request (host API, not JSON-RPC wire)
|
|
84
|
+
{ "file": "src/a.ts", "line": 10, "character": 4 }
|
|
85
|
+
|
|
86
|
+
// normalized definition
|
|
87
|
+
{ "file": "src/a.ts", "line": 2, "character": 0 }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
// rename workspace edit (after apply)
|
|
92
|
+
{
|
|
93
|
+
"edits": [
|
|
94
|
+
{
|
|
95
|
+
"file": "src/a.ts",
|
|
96
|
+
"newText": "renamed",
|
|
97
|
+
"range": {
|
|
98
|
+
"start": { "line": 10, "character": 4 },
|
|
99
|
+
"end": { "line": 10, "character": 7 }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Implementation example
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import {
|
|
110
|
+
createLanguageIntelligence,
|
|
111
|
+
DEFAULT_MAX_LSP_TIMEOUT_MS,
|
|
112
|
+
} from "@arnilo/prism-coding-agent";
|
|
113
|
+
import { createCodingApprovalPolicy } from "@arnilo/prism-coding-security";
|
|
114
|
+
|
|
115
|
+
const policy = createCodingApprovalPolicy({
|
|
116
|
+
roots: [workspaceRoot],
|
|
117
|
+
approve: async ({ action }) => host.confirm(action),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const lang = createLanguageIntelligence({
|
|
121
|
+
workspaceRoot,
|
|
122
|
+
servers: {
|
|
123
|
+
ts: {
|
|
124
|
+
command: process.execPath, // example only — pin a real language server in production
|
|
125
|
+
args: ["/path/to/typescript-language-server", "--stdio"],
|
|
126
|
+
languages: ["typescript", "typescriptreact"],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
limits: { requestTimeoutMs: DEFAULT_MAX_LSP_TIMEOUT_MS },
|
|
130
|
+
policy,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const diags = await lang.diagnostics("src/app.ts");
|
|
135
|
+
const hover = await lang.hover({ file: "src/app.ts", line: 0, character: 0 });
|
|
136
|
+
console.log(diags.length, hover?.text);
|
|
137
|
+
} finally {
|
|
138
|
+
await lang.dispose();
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Extension and configuration notes
|
|
143
|
+
|
|
144
|
+
- **Server map is the only language binding.** Extension ids map from common file extensions (`.ts` → `typescript`, `.py` → `python`, …); unknown extensions use `plaintext`. Hosts register servers for the language ids they need.
|
|
145
|
+
- **Lazy start.** First request for a language starts that server (`initialize` / `initialized`); `workspaceSymbols` / aggregate `diagnostics` start all configured servers.
|
|
146
|
+
- **Pluggable policy only.** Renames reuse `assertExecutionAllowed` + `withFileMutationQueue` + `atomicWriteUtf8File`. No second write path.
|
|
147
|
+
- **Framing helpers** (`encodeLspFrame`, `LspFrameReader`) are exported for tests and custom transports; production hosts normally use only `createLanguageIntelligence`.
|
|
148
|
+
- **Not in default tool aggregators.** Hosts call the contract directly or wrap it in their own `ToolDefinition`s.
|
|
149
|
+
|
|
150
|
+
## Security and performance notes
|
|
151
|
+
|
|
152
|
+
- Server `command`/`args` are host-config only — never taken from model tool arguments.
|
|
153
|
+
- File URIs must be `file:` and resolve inside `workspaceRoot`; escapes fail with `ERR_PRISM_LSP_WORKSPACE`.
|
|
154
|
+
- LSP payloads are untrusted: Content-Length framing is bounded; oversized/malformed frames fail closed; result lists and diagnostics are capped.
|
|
155
|
+
- Crash loop: unexpected exit increments a per-server restart counter; after the freeze budget (`LSP_RESTARTS_PER_SERVER` = 3) further starts fail with `ERR_PRISM_LSP_SERVER`.
|
|
156
|
+
- Defaults / hard caps (Phase 9 freeze): message 4 MiB / 32 MiB; diagnostics/file 200 / 1000; pending requests 32 / 128; results/query 500 / 5000; timeout 30 s / 120 s; servers/workspace 4 / 8.
|
|
157
|
+
|
|
158
|
+
## Related APIs
|
|
159
|
+
|
|
160
|
+
- [Coding agent tools](coding-agent-tools.md): shell/read/write/edit/list/search/glob and shared limits/policy seams this contract reuses for rename.
|
|
161
|
+
- [Coding execution approval and sandboxing](coding-security.md): `ExecutionPolicy` / approval composition for gating rename.
|
|
162
|
+
- [Tools](tools.md): host-owned `ToolDefinition` registration if you wrap language intelligence as tools.
|
package/docs/migration.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Migration guide
|
|
2
2
|
|
|
3
|
+
## 0.0.25 → 0.0.26 coding intelligence, managed processes, forge, and safe egress (additive)
|
|
4
|
+
|
|
5
|
+
Release **0.0.26** (Phase 9) adds four opt-in capability families to `@arnilo/prism-coding-agent` and `@arnilo/prism-coding-security`: Git-aware repository enumeration, host-selected LSP language intelligence, managed process sessions, a reference GitHub forge adapter with idempotent handoff, and an allow-list egress proxy with DNS-rebinding defense. All are **additive** — no existing export, event, or persisted shape changes; hosts that do not activate the new factories keep prior behavior. Publishable graph stays **47** manifests.
|
|
6
|
+
|
|
7
|
+
1. **Git-aware enumeration is opt-in.** `createLocalRepositoryOperations` keeps the native walker. `createGitAwareRepositoryOperations(cwd, options?)` runs a fixed `git ls-files --cached --others --exclude-standard -z` and falls back to native enumeration when the directory is not a Git work tree or git is unavailable. `includeIgnored` is host-only (never surfaced to tools). No change to `listLocal`/`searchLocal`/`globLocal` callers.
|
|
8
|
+
2. **Language intelligence is host-activated.** `createLanguageIntelligence(options)` spawns the host-selected LSP server lazily (no spawn at construction) and speaks LSP 3.17 over bounded JSON-RPC. Unsupported languages fail closed with `ERR_PRISM_LSP_UNSUPPORTED`; out-of-workspace URIs fail with `ERR_PRISM_LSP_WORKSPACE`. Rename applies through `ExecutionPolicy` (kind `edit`, risk `high`) and atomic writes; hosts that never call it are unaffected.
|
|
9
|
+
3. **Process sessions are a new contract.** `createProcessSessions(options)` manages start/output/input/wait/signal/kill/release with ownership scoping and expiry sweep. Sessions may run natively or through an optional sandbox `startProcess` backend; sandbox loss marks sessions `unknown` for host reconciliation. PTY is not supported (`ERR_PRISM_PROCESS_PTY_UNSUPPORTED`). No change to the existing `shell`/`bash` primitives.
|
|
10
|
+
4. **Forge adapter is a new contract.** `createGitHubForge(options)` is GitHub-first by freeze decision; mutations require durable context (`identity`/`ownership`/`sessionId`/`runId`) and a `ToolEffectStore`, and are gated by `ExecutionPolicy`. Push injects the token via `GIT_CONFIG_*` environment variables — never argv, never persisted. `CreateGitHubForgeOptions.fetch?` (new in 0.0.26) lets hosts route forge traffic through the egress proxy or inject a mock; it defaults to `globalThis.fetch`.
|
|
11
|
+
5. **Egress is deny-all by default.** `createEgressPolicy()` allows nothing; presets (`npm-registry`, `github`) are explicit allow-lists. `createAllowListEgressProxy` pins DNS and verifies the socket peer before tunneling (rebinding defense), denies private/metadata IPs unless `allowPrivate`, re-validates redirects per hop, and caps bytes/time/concurrency. `composeEgressSandboxNetwork` records the attestation as `prism.egress.*` container labels; `denyDirectEgress` is asserted on sandbox start.
|
|
12
|
+
6. **No migration steps required.** No persisted shape, event schema, or default behavior changed. Hosts upgrading from 0.0.25 can adopt any subset of the new factories; the previous `docs/migration.md` sections remain accurate for their releases.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createGitAwareRepositoryOperations } from "@arnilo/prism-coding-agent";
|
|
16
|
+
const repo = createGitAwareRepositoryOperations(process.cwd());
|
|
17
|
+
const { entries } = await repo.listLocal({ maxDepth: 3 });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Examples: `node examples/phase9-coding-intelligence.ts` (composed, network-free).
|
|
21
|
+
7. **Durable `AgentEventSource` root export (FR-6/FR-7).** `@arnilo/prism-session-store-postgres` now re-exports `createPostgresAgentEventSource`, `ClosablePostgresAgentEventSource`, and `PostgresAgentEventSourceOptions` from the package root — previously reachable only via a `dist/...` subpath. `persistence.events` remains the canonical bundled path and is unchanged. Placement answer: the durable event source stays in this package for the 0.0.26 line; PostgreSQL `LISTEN`/`NOTIFY` remains the reference durable implementation. Any future relocation ships a replacement export with a deprecation note before removal — no migration action today. See [agent events](agent-events.md) and `prism-agent-event-source-export-and-location.md`.
|
|
22
|
+
8. **NATS JetStream `AgentEventSource` (FR-5).** New sibling package `@arnilo/prism-session-store-nats` implements the durable `AgentEventSource` contract over JetStream: per-run subjects, per-subject replay, durable pull consumers with explicit acks (at-least-once, 30s redelivery), idempotent `append` by `record.id` within the stream dedupe window, HMAC-signed resumable cursors, and ownership-scoped `page`/`subscribe`/`cleanup`. The host provisions the stream (`prism.agent-events.>`, retention limits, dedupe window); the package is inert on import. Postgres remains the reference durable implementation — NATS is a sibling adapter for JetStream backbones. See [agent events](agent-events.md).
|
|
23
|
+
9. **A2A server-side exposure (Task 13).** `createAgUiA2AServer()` in `@arnilo/prism-ag-ui` fronts one host-selected local AG-UI agent as an A2A 1.0 server: remote A2A clients start and stream local runs through the AG-UI input allow-list and event mapper (same projection/redaction/caps as the AG-UI SSE path), reusing `@arnilo/prism-supervisor` `createA2AHandler` transport. No new runtime, task store, or worker; no route added to `createPrismHandler()` (A2A stays separately mounted). Optional `durable` wiring replays finished runs from an `AgentEventSource` with cursor event ids. Requires the optional `@arnilo/prism-supervisor` peer only when the factory is called (lazy import). See [A2A interoperability](a2a.md).
|
|
24
|
+
10. **Reference frontend renderer (Task 14).** `@arnilo/prism-ag-ui/renderer` subpath export ships a framework-free client renderer: it consumes an AG-UI event stream (SSE or in-memory `AsyncIterable`) and renders `a2ui-surface` snapshots/deltas into DOM surfaces from a host component catalog. DOM-free core (`reduceA2UiOps` operation state machine) plus a thin binding layer with a built-in default text/container catalog; server-side A2UI caps are enforced client-side (ops/message, op bytes, surfaces/run, component depth); invalid/oversized ops drop closed with a bounded error event; unknown components render an explicit placeholder; remote HTML is never executed (createElement/text nodes only). The main `@arnilo/prism-ag-ui` entry stays runtime-agnostic — DOM code lives only behind the `renderer` subpath. Requires no new dependency and no host build step. See [AG-UI](ag-ui.md).
|
|
25
|
+
11. **Async `AgUiProjection` hooks (Task 15).** Every `AgUiProjection` callback return is now `Awaitable<T>` (`T | Promise<T>`), so projectors can call async host APIs like `session.entries()` directly; the AG-UI and ACP mappers await hooks in event order (never `Promise.all`) with per-event fail-closed exactly like sync throw handling. `createMessagesFromSessionProjection({ getMessages })` accepts an async transcript source and emits `MESSAGES_SNAPSHOT` at `agent_started`/`message_finished`. Sync-only hosts keep exact prior behavior — sync values short-circuit, no behavior change, and the sync-path mapper p95 is budget-gated. `projectCoWorkEvent` is now async (it may await the `coWork` hook). See [AG-UI](ag-ui.md).
|
|
26
|
+
|
|
3
27
|
## 0.0.24 → 0.0.25 durable custom loops and human-in-the-loop (intentional pre-1.0 contract changes)
|
|
4
28
|
|
|
5
29
|
Release **0.0.25** makes custom loops durable and replaces sequential binary approvals with one shared pending-decision model. Protocol adapters (AG-UI, ACP, MCP, coding `ask_user_decision`, server resume, supervisor nesting) map onto that model. Opt-in A2UI painting and standard AG-UI projectors ship in `@arnilo/prism-ag-ui`. Publishable graph stays **47** manifests.
|
package/docs/performance.md
CHANGED
|
@@ -19,6 +19,22 @@ This page states Prism runtime limits that keep slow consumers and long sessions
|
|
|
19
19
|
|
|
20
20
|
Conformance: `scripts/phase8-conformance.test.mjs` (8 network-free cases). Values are environment evidence, not universal SLOs.
|
|
21
21
|
|
|
22
|
+
## Release 0.0.26 coding intelligence, processes, forge, and egress
|
|
23
|
+
|
|
24
|
+
`node scripts/benchmark-0.0.26.mjs` is network-free (fake LSP/forge/proxy, synthetic 100k-file repo, real process spill). Checked `scripts/benchmark-0.0.26.json` (Node v24.18.0/Linux x64): 5 warmups, 20 measured ops, 100k enumeration files, 1 GiB process spill, 1,000 LSP diagnostics, 100 forge pages × 100 items, 64 MiB proxy download.
|
|
25
|
+
|
|
26
|
+
| Scenario | Recorded p95 ms | Ceiling |
|
|
27
|
+
| --- | ---: | ---: |
|
|
28
|
+
| Git-aware enumeration (100k-file repo, ≤ 2 git invocations, 10k results cap) | 299.166 | 2,000 |
|
|
29
|
+
| Process chunk page (50 KiB pages over 1 GiB spill, 64 MiB retained) | 0.051 | 10 |
|
|
30
|
+
| LSP diagnostic normalization (1,000 diagnostics at hard per-file cap) | 0.210 | 100 |
|
|
31
|
+
| Forge pagination (100 pages × 100 check-runs, deduped) | 144.233 | 10,000 |
|
|
32
|
+
| Proxy download (64 MiB at default response cap, resident buffering ≤ 2× maxBytes) | 93.667 | 30,000 |
|
|
33
|
+
| Renderer stream (1,000-op A2UI surface as 16×64-op batches + full tree render) | 2.000 | 100 |
|
|
34
|
+
| AG-UI mapper sync path (4,000 events through the async pipeline, sync hooks only) | 30.924 | 100 |
|
|
35
|
+
|
|
36
|
+
Conformance: `scripts/phase9-conformance.test.mjs` (8 network-free cases: composed enumeration → LSP rename → process → forge → egress, symlink/ignore escape, LSP URI escape, process ownership, forge cross-tenant + token hygiene, egress private/metadata bypass, limit ladder, packed example). Values are environment evidence, not universal SLOs.
|
|
37
|
+
|
|
22
38
|
## Release 0.0.24 distributed events and tool effects
|
|
23
39
|
|
|
24
40
|
`node scripts/benchmark-0.0.24.mjs` is an explicit protected PostgreSQL benchmark behind `PRISM_TEST_POSTGRES_URL`. Checked `scripts/benchmark-0.0.24.json` (Node v24.18.0/Linux x64, PostgreSQL 16.14): 10 tenants × 10 principals × 1,000 events/owner, 16 producers/subscribers, 100 warmups, 1,000 measured ops, 10,000-event sustained replay, 100-row cleanup.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Process sessions
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`createProcessSessions` is an optional host-activated registry in `@arnilo/prism-coding-agent` for **long-running** child processes: start, cursor-paged output, input, wait, signal/kill, and release (detach). Sessions have bounded lifetime (sweep on registry access — no import-time timers), ownership/identity attribution, durable metadata (command fingerprint without env/secrets), and typed `CodingProcessEvent`s via a host callback. Reuses `ExecutionPolicy`, `killProcessTree`, and `OutputAccumulator` (including spill + `readRaw` cursor paging). Optional duck-typed `sandbox` backend uses `startProcess` when present; one-shot adapters fail closed. Nothing spawns on import or construction.
|
|
6
|
+
|
|
7
|
+
| Export | Purpose |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `createProcessSessions(options)` | Build a `ProcessSessions` registry bound to one workspace `cwd`. |
|
|
10
|
+
| `ProcessSessions` | `start`, `get`, `cancelOwned`, `markUnknown`, `reconcile`, `dispose`. |
|
|
11
|
+
| `ProcessSession` | Handle: `output` / `input` / `wait` / `signal` / `kill` / `release` / `metadata`. |
|
|
12
|
+
| `ProcessSessionState` | `starting` \| `running` \| `exited` \| `killed` \| `released` \| `expired` \| `unknown`. |
|
|
13
|
+
| `ProcessSandboxBackend` | Duck-typed optional sandbox (`startProcess?`, `status?`); mirrors coding-security `SandboxProcessHandle`. |
|
|
14
|
+
| `CodingProcessEvent` | Host-sink events (`process_started` / `_exited` / `_killed` / `_released` / `_expired` / `_unknown`). |
|
|
15
|
+
| `ProcessSessionError` | Typed fail-closed errors (`ERR_PRISM_PROCESS_*`). |
|
|
16
|
+
| `resolveProcessSessionLimits` / `DEFAULT_MAX_PROCESS_*` / `HARD_MAX_PROCESS_*` | Session count, input bytes, lifetime, chunk/total output caps. |
|
|
17
|
+
|
|
18
|
+
## When to use it
|
|
19
|
+
|
|
20
|
+
Use when a host needs attachable long-running processes (watch modes, language servers, interactive CLIs) that one-shot `shell` cannot model. Do not use as a job-control language or PTY emulator — `pty: true` fails closed with `ERR_PRISM_PROCESS_PTY_UNSUPPORTED` until a platform capability is wired. Pass a sandbox with `startProcess` for contained long-running work; omit `sandbox` for native spawn.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createProcessSessions } from "@arnilo/prism-coding-agent";
|
|
24
|
+
|
|
25
|
+
const sessions = createProcessSessions({ cwd: workspaceRoot, policy, sandbox, onEvent });
|
|
26
|
+
const p = await sessions.start({ command: "npm", args: ["test", "--", "--watch"] });
|
|
27
|
+
const out = await p.output({ cursor: 0, maxBytes: 8192 });
|
|
28
|
+
await p.input("q\n");
|
|
29
|
+
await p.wait({ timeoutMs: 5000 });
|
|
30
|
+
await sessions.dispose();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Inputs / request
|
|
34
|
+
|
|
35
|
+
`createProcessSessions` options:
|
|
36
|
+
|
|
37
|
+
| Field | Type | Purpose |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `cwd` | `string` | Workspace root; session `cwd` must stay inside. |
|
|
40
|
+
| `policy?` | `ExecutionPolicy` | Gated before spawn and rechecked on input/signal/kill. |
|
|
41
|
+
| `limits?` | `ProcessSessionLimits` | Optional overrides; invalid values fail instead of clamping. |
|
|
42
|
+
| `onEvent?` | `(CodingProcessEvent) => void` | Host-owned sink (core `AgentEvent` unchanged); audit owner + terminal/unknown. |
|
|
43
|
+
| `ownership?` | `OwnershipScope` | Default owner key for sessions. |
|
|
44
|
+
| `identity?` | `AgentIdentity` | When ownership omitted, owner key projects from identity. |
|
|
45
|
+
| `sandbox?` | `ProcessSandboxBackend` | When set: require `startProcess` or fail closed; `status` loss → all running → `unknown`. |
|
|
46
|
+
|
|
47
|
+
`ProcessStartRequest`:
|
|
48
|
+
|
|
49
|
+
| Field | Purpose |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `command` / `args?` | Executable + argv (not a shell string). |
|
|
52
|
+
| `cwd?` | Relative/absolute path contained under registry `cwd`. |
|
|
53
|
+
| `env?` | Extra env merged onto `process.env` (never in fingerprint). |
|
|
54
|
+
| `pty?` | Default false; unsupported → `ERR_PRISM_PROCESS_PTY_UNSUPPORTED`. |
|
|
55
|
+
| `lifetimeMs?` | Bounded by `maxLifetimeMs`. |
|
|
56
|
+
| `owner?` | Override owner string. |
|
|
57
|
+
| `releaseOnCancel?` | If true, `cancelOwned` releases instead of killing. |
|
|
58
|
+
|
|
59
|
+
## Outputs / response / events
|
|
60
|
+
|
|
61
|
+
| Method | Result |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `start` | `ProcessSession` in `running`; emits `process_started`. |
|
|
64
|
+
| `output({ cursor, maxBytes })` | `{ data, cursor, eof }` UTF-8 page from byte cursor. |
|
|
65
|
+
| `input` | Writes stdin; capped by `maxInputBytes`; fails if not running / stdin closed. |
|
|
66
|
+
| `wait` | `{ exitCode, state }` — `exitCode` is `null` for killed/released/expired/unknown. |
|
|
67
|
+
| `signal` / `kill` / `release` | Soft signal, hard kill, or detach (no re-attach). |
|
|
68
|
+
| `cancelOwned(owner)` | Kill (default) or release owned running sessions. |
|
|
69
|
+
| `markUnknown` | Backend-loss terminal state; never fabricates `exitCode`. |
|
|
70
|
+
| `reconcile()` | Host resume: mark every running/starting session `unknown` (O(sessions)). |
|
|
71
|
+
|
|
72
|
+
Events: `process_started`, `process_exited`, `process_killed`, `process_released`, `process_expired`, `process_unknown`.
|
|
73
|
+
|
|
74
|
+
Errors: `ERR_PRISM_PROCESS_POLICY`, `ERR_PRISM_PROCESS_OWNERSHIP`, `ERR_PRISM_PROCESS_STATE`, `ERR_PRISM_PROCESS_LIMIT`, `ERR_PRISM_PROCESS_PTY_UNSUPPORTED`, `ERR_PRISM_PROCESS_UNSUPPORTED`.
|
|
75
|
+
|
|
76
|
+
## Request/response example
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"command": "npm",
|
|
81
|
+
"args": ["test", "--", "--watch"],
|
|
82
|
+
"lifetimeMs": 14400000,
|
|
83
|
+
"releaseOnCancel": false
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Implementation example
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const sessions = createProcessSessions({
|
|
91
|
+
cwd: workspaceRoot,
|
|
92
|
+
ownership: { tenantId: "t1", userId: "u1" },
|
|
93
|
+
sandbox, // DisposableSandbox with startProcess, or omit for native
|
|
94
|
+
limits: { maxSessions: 8 },
|
|
95
|
+
onEvent: (e) => audit.write(e),
|
|
96
|
+
policy: hostExecutionPolicy,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const p = await sessions.start({
|
|
100
|
+
command: process.execPath,
|
|
101
|
+
args: ["server.js"],
|
|
102
|
+
lifetimeMs: 3_600_000,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
let cursor = 0;
|
|
106
|
+
for (;;) {
|
|
107
|
+
const chunk = await p.output({ cursor, maxBytes: 50_000 });
|
|
108
|
+
cursor = chunk.cursor;
|
|
109
|
+
if (chunk.eof) break;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await sessions.cancelOwned(p.owner); // run cancellation
|
|
113
|
+
await sessions.dispose();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Extension and configuration notes
|
|
117
|
+
|
|
118
|
+
- Native when `sandbox` omitted; with `sandbox`, capability is `typeof startProcess === "function"` (never assumed).
|
|
119
|
+
- One-shot sandbox (no `startProcess`) → `ERR_PRISM_PROCESS_UNSUPPORTED` (no native fallback).
|
|
120
|
+
- Sandbox `status` not `running` (or throws) → all live sessions → `unknown`; further `start` fails closed.
|
|
121
|
+
- Host restart: call `reconcile()` on a new registry for in-memory orphans, or listen for `process_unknown` and wire Phase 7 `ToolEffectStore.markUnknown` in the host.
|
|
122
|
+
- Expiry sweep runs on registry/handle access — no timers at import.
|
|
123
|
+
- Command fingerprint is SHA-256 of `[command, ...args]` only (no env).
|
|
124
|
+
- Docker reference adapter does not implement `startProcess` yet — fail closed until a capable runtime is wired.
|
|
125
|
+
|
|
126
|
+
## Security and performance notes
|
|
127
|
+
|
|
128
|
+
| Cap | Default | Hard |
|
|
129
|
+
| --- | ---: | ---: |
|
|
130
|
+
| Sessions per registry | 8 | 32 |
|
|
131
|
+
| Input write bytes | 64 KiB | 1 MiB |
|
|
132
|
+
| Session lifetime | 4 h | 24 h |
|
|
133
|
+
| Output chunk | 50 KiB | 1 MiB |
|
|
134
|
+
| Total output / session | 64 MiB | 1 GiB |
|
|
135
|
+
|
|
136
|
+
- Wrong-owner `get(id, owner)` → `ERR_PRISM_PROCESS_OWNERSHIP`.
|
|
137
|
+
- Released sessions reject all further handle ops.
|
|
138
|
+
- `cwd` outside workspace → `ERR_PRISM_PROCESS_POLICY`.
|
|
139
|
+
- Policy denial before spawn / on mutate → `ERR_PRISM_PROCESS_POLICY`.
|
|
140
|
+
- Unknown outcome never invents an exit code.
|
|
141
|
+
|
|
142
|
+
## Related APIs
|
|
143
|
+
|
|
144
|
+
- [Coding agent tools](coding-agent-tools.md): one-shot `shell` vs long-running sessions.
|
|
145
|
+
- [Language intelligence](language-intelligence.md): LSP servers may later register as managed sessions.
|
|
146
|
+
- [Coding security](coding-security.md): `SandboxProcessHandle` / optional `DisposableSandbox.startProcess`.
|
|
147
|
+
- [Tool effects](tool-effects.md): unknown-outcome vocabulary mirrored by `markUnknown` / `process_unknown` / `reconcile`.
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
4
|
|
|
5
|
-
Prism is published as one core package, forty first-party capability packages, and six pure-manifest family/profile packages (**
|
|
5
|
+
Prism is published as one core package, forty-one first-party capability packages, and six pure-manifest family/profile packages (**48** publishable manifests total). This page describes how they are packed, what each tarball contains, how to install them, the required `@arnilo/prism` peer dependency, the release workflow, and the offline test budget. The measurable 1.0 readiness gates (command-per-gate) live in [`0.1.0-readiness.md`](./0.1.0-readiness.md).
|
|
6
6
|
|
|
7
|
-
Core `@arnilo/prism` ships runtime, CLI, templates, and docs. Every code package has a required `@arnilo/prism@0.0.
|
|
7
|
+
Core `@arnilo/prism` ships runtime, CLI, templates, and docs. Every code package has a required `@arnilo/prism@0.0.26` peer; profiles are pure manifests. Installation activates no provider, listener, database, browser, credential, or tool capability.
|
|
8
8
|
|
|
9
|
-
Current **
|
|
9
|
+
Current **48** publishable manifests:
|
|
10
10
|
|
|
11
11
|
`@arnilo/prism`, `@arnilo/prism-ag-ui`, `@arnilo/prism-browser`, `@arnilo/prism-coding-agent`, `@arnilo/prism-coding-security`, `@arnilo/prism-compaction-llm`
|
|
12
12
|
`@arnilo/prism-compaction-observational-memory`, `@arnilo/prism-credentials-node`, `@arnilo/prism-enterprise-postgres`, `@arnilo/prism-evals`, `@arnilo/prism-mcp`, `@arnilo/prism-memory`
|
|
@@ -14,7 +14,7 @@ Current **47** publishable manifests:
|
|
|
14
14
|
`@arnilo/prism-code`, `@arnilo/prism-compaction`, `@arnilo/prism-ponytail`, `@arnilo/prism-providers`, `@arnilo/prism-sdk`, `@arnilo/prism-provider-ai-sdk`
|
|
15
15
|
`@arnilo/prism-provider-alibaba`, `@arnilo/prism-provider-anthropic`, `@arnilo/prism-provider-azure`, `@arnilo/prism-provider-bedrock`, `@arnilo/prism-provider-google`, `@arnilo/prism-provider-kimi`
|
|
16
16
|
`@arnilo/prism-provider-neuralwatt`, `@arnilo/prism-provider-ollama`, `@arnilo/prism-provider-openai`, `@arnilo/prism-provider-opencode-go`, `@arnilo/prism-provider-openrouter`, `@arnilo/prism-provider-vertex`
|
|
17
|
-
`@arnilo/prism-provider-zai`, `@arnilo/prism-rag`, `@arnilo/prism-server`, `@arnilo/prism-session-store-codecs`, `@arnilo/prism-session-store-postgres`, `@arnilo/prism-session-store-sqlite`
|
|
17
|
+
`@arnilo/prism-provider-zai`, `@arnilo/prism-rag`, `@arnilo/prism-server`, `@arnilo/prism-session-store-codecs`, `@arnilo/prism-session-store-nats`, `@arnilo/prism-session-store-postgres`, `@arnilo/prism-session-store-sqlite`
|
|
18
18
|
`@arnilo/prism-supervisor`, `@arnilo/prism-tool-validator-json-schema`, `@arnilo/prism-web-tools`, `@arnilo/prism-work-tools`, `@arnilo/prism-workflows`
|
|
19
19
|
|
|
20
20
|
Core ships `dist`, docs, templates, and `CHANGELOG.md`; code packages ship compiled output, README, license, and changelog. Family/profile packages ship manifest, README, and changelog. `@arnilo/prism-providers` includes all eleven `@arnilo/prism-provider-*` packages.
|
|
@@ -45,9 +45,9 @@ Consumers install the core package for the runtime and add first-party packages
|
|
|
45
45
|
| Run the default (network-free) test suite | `npm test` |
|
|
46
46
|
| Dry-run pack core + every package | `npm run pack:dry-run` |
|
|
47
47
|
| Local mirror of the release verify gate | `npm run release:dry-run` |
|
|
48
|
-
| Validate clean tag/version/ranges and reject registry collisions | `npm run release:check -- --version 0.0.
|
|
49
|
-
| Preview deterministic publish order | `npm run release:publish -- --version 0.0.
|
|
50
|
-
| Resume interrupted tagged publication | `npm run release:publish -- --version 0.0.
|
|
48
|
+
| Validate clean tag/version/ranges and reject registry collisions | `npm run release:check -- --version 0.0.26` |
|
|
49
|
+
| Preview deterministic publish order | `npm run release:publish -- --version 0.0.26 --dry-run --allow-dirty --allow-untagged` |
|
|
50
|
+
| Resume interrupted tagged publication | `npm run release:publish -- --version 0.0.26 --resume --report release-artifacts/publish-report.json` |
|
|
51
51
|
| Protected PostgreSQL enterprise suite | `PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres` |
|
|
52
52
|
| Full SDK readiness gate (typecheck + offline tests + pack) | `npm run sdk:ready` |
|
|
53
53
|
|
|
@@ -87,7 +87,7 @@ A packed tarball contains only public compiled output and release files:
|
|
|
87
87
|
- Code packages ship `README.md`, `LICENSE`, and `CHANGELOG.md`; family/profile packages ship `README.md` and `CHANGELOG.md`.
|
|
88
88
|
- The core tarball additionally ships the full `docs/` directory (the docs hub) and `templates/init/` used by `prism init`.
|
|
89
89
|
- `dist/cli.js` and the `bin` link in core.
|
|
90
|
-
- **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.0.
|
|
90
|
+
- **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.0.26.tgz`; first-party packages produce `arnilo-prism-provider-<name>-0.0.26.tgz` / `arnilo-prism-compaction-<name>-0.0.26.tgz` / `arnilo-prism-coding-agent-0.0.26.tgz`; family/profile packages produce `arnilo-prism-{providers,compaction,base,code,sdk,all}-0.0.26.tgz`. The CLI bin name `prism` is unaffected by the package name (`npx prism` still works; npm allows the bin field to differ from the package name).
|
|
91
91
|
|
|
92
92
|
Excluded from every tarball by `files` negation:
|
|
93
93
|
|
|
@@ -106,9 +106,9 @@ Excluded from every tarball by `files` negation:
|
|
|
106
106
|
"name": "host-app",
|
|
107
107
|
"type": "module",
|
|
108
108
|
"dependencies": {
|
|
109
|
-
"@arnilo/prism": "0.0.
|
|
110
|
-
"@arnilo/prism-enterprise-postgres": "0.0.
|
|
111
|
-
"@arnilo/prism-provider-openai": "0.0.
|
|
109
|
+
"@arnilo/prism": "0.0.26",
|
|
110
|
+
"@arnilo/prism-enterprise-postgres": "0.0.26",
|
|
111
|
+
"@arnilo/prism-provider-openai": "0.0.26"
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
```
|
|
@@ -151,11 +151,11 @@ For SDK readiness, run the same one-command gate directly. It composes existing
|
|
|
151
151
|
npm run sdk:ready
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
Release publication derives all **
|
|
154
|
+
Release publication derives all **48** manifests from the workspace once, validates exact `0.0.26` manifest/lockfile/internal ranges, then uses deterministic dependency order. `release:check` requires a clean commit tagged `v0.0.26` and rejects any existing registry version. `release:publish --resume` skips only registry versions whose internal dependency fingerprint matches the local manifest; conflicting versions fail closed. Each attempted package is written immediately to the JSON report, so a failed job can rerun safely. `--dry-run` performs registry availability checks and invokes `npm publish --dry-run` with explicit public access, provenance, and `latest` tag, but does not publish.
|
|
155
155
|
|
|
156
156
|
```bash
|
|
157
|
-
npm run release:check -- --version 0.0.
|
|
158
|
-
npm run release:publish -- --version 0.0.
|
|
157
|
+
npm run release:check -- --version 0.0.26
|
|
158
|
+
npm run release:publish -- --version 0.0.26 --dry-run --allow-dirty --allow-untagged
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
`--allow-dirty` and `--allow-untagged` exist only for local preview; real publication and CI never pass them. npm registry calls occur only in these release preflight/publication commands, never build/test/package discovery.
|
|
@@ -166,25 +166,29 @@ Optional live smoke tests stay separate from SDK readiness because they require
|
|
|
166
166
|
PRISM_LIVE_PROVIDER_TESTS=1 npm run test --workspaces --if-present
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
### 0.0.
|
|
169
|
+
### GitHub Actions pipeline (0.0.26+)
|
|
170
170
|
|
|
171
|
-
**
|
|
171
|
+
`.github/workflows/release.yml` is the single pipeline: **push to `main`** runs CI (`verify` = `npm run sdk:ready`, `node20-compat`, `postgres-integration`, `supply-chain`), **push of a `v*` tag** additionally runs `codeql-release` and the `publish` job (deterministic `release:publish` in dependency order with provenance attestation). `security.yml` adds CodeQL/dependency-review/SBOM on push and PR; `live-canaries.yml` and `sandbox-browser.yml` are scheduled. All actions are SHA-pinned (2026-08-06 fix: CodeQL pins were invalid 404 refs and `workflow_dispatch` was missing — re-verified every pin against its upstream repo). Prerequisites outside the repo: Actions enabled in repository settings, and the `NPM_TOKEN` secret (with `id-token: write` for provenance). To re-cut a tag after a fix commit, delete and recreate it (`git push origin :v0.0.26 && git push origin v0.0.26`) so the tag creation event fires.
|
|
172
|
+
|
|
173
|
+
### 0.0.26 publish handoff
|
|
174
|
+
|
|
175
|
+
**Decision: GO after protected operator prerequisites below.** Release **0.0.26** (Phase 9, plan 009) ships Git-aware repository enumeration, host-selected LSP language intelligence, managed process sessions (sandbox-backed, ownership-scoped), a reference GitHub forge adapter with idempotent handoff, and an allow-list egress proxy with DNS-rebinding defense and Docker sandbox attestation. Publishable graph grows to **48** manifests (new `@arnilo/prism-session-store-nats`). See [migration](migration.md) `0.0.25 → 0.0.26`, [language intelligence](language-intelligence.md), [process sessions](process-sessions.md), [forge integration](forge-integration.md), and [coding security](coding-security.md).
|
|
172
176
|
|
|
173
177
|
```bash
|
|
174
178
|
git diff --check
|
|
175
179
|
npm ci
|
|
176
180
|
npm run sdk:ready
|
|
177
|
-
node --test scripts/
|
|
178
|
-
node scripts/benchmark-0.0.
|
|
181
|
+
node --test scripts/phase9-conformance.test.mjs
|
|
182
|
+
node scripts/benchmark-0.0.26.mjs > scripts/benchmark-0.0.26.json
|
|
179
183
|
node --test scripts/budget-gate.test.mjs scripts/tooling-gate.test.mjs
|
|
180
184
|
node scripts/scan-secrets.mjs && node scripts/verify-sbom.mjs
|
|
181
185
|
npm audit --audit-level=moderate
|
|
182
|
-
npm run release:gate -- --version 0.0.
|
|
183
|
-
npm run release:check -- --version 0.0.
|
|
184
|
-
npm run release:publish -- --version 0.0.
|
|
185
|
-
git tag -s v0.0.
|
|
186
|
-
git verify-tag v0.0.
|
|
187
|
-
git push origin v0.0.
|
|
186
|
+
npm run release:gate -- --version 0.0.26 --allow-break --allow-dirty --allow-untagged
|
|
187
|
+
npm run release:check -- --version 0.0.26 --allow-dirty --allow-untagged --report /tmp/prism-0.0.26-preflight.json
|
|
188
|
+
npm run release:publish -- --version 0.0.26 --dry-run --allow-dirty --allow-untagged --report /tmp/prism-0.0.26-dry-run.json
|
|
189
|
+
git tag -s v0.0.26 -m "Prism 0.0.26"
|
|
190
|
+
git verify-tag v0.0.26
|
|
191
|
+
git push origin v0.0.26
|
|
188
192
|
```
|
|
189
193
|
|
|
190
194
|
### 0.0.24 publish handoff
|
|
@@ -284,8 +288,8 @@ Older 0.0.10–0.0.15 handoffs are summarized in [migration](migration.md); hist
|
|
|
284
288
|
|
|
285
289
|
## Extension and configuration notes
|
|
286
290
|
|
|
287
|
-
- **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional `@arnilo/prism@0.0.
|
|
288
|
-
- **Public access.** All
|
|
291
|
+
- **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional `@arnilo/prism@0.0.26` peer (`peerDependenciesMeta` must not mark `@arnilo/prism` optional; other peers such as `playwright-core` may be optional). The range stays pinned to `0.0.26` for the current 0.x release and will widen to `^1.0.0` at the 1.x stable release. Inside the workspace each package also declares `"@arnilo/prism": "file:../.."` in `devDependencies` so `npm install` resolves the peer locally; that devDependency is stripped from consumer installs and is not a runtime dependency.
|
|
292
|
+
- **Public access.** All 48 manifests (42 code packages + 6 family/profile packages) declare `"publishConfig": { "access": "public" }`; the publisher also passes `--access public` explicitly because scoped packages otherwise default to restricted on first publish.
|
|
289
293
|
- **Map retention knob.** Source maps are emitted locally but stripped from tarballs by `!dist/**/*.map`. Removing that `files` negation ships maps in releases (larger tarballs, better consumer stack traces).
|
|
290
294
|
- **Release workflow.** `.github/workflows/release.yml` has six jobs. `verify` runs network-free SDK readiness on Node 24; `node20-compat` builds/imports every public root `exports` default target on Node 20 for declared `engines.node >=20` (docs examples need Node >=22.6 native TypeScript stripping); `postgres-integration` uses `pgvector/pgvector:pg16`; `supply-chain` runs high-severity audit, SPDX/license policy, and tracked-source secret scanning; and tag-only `codeql-release` runs SAST. Tag-only `publish` needs all five gates, preserves clean exact-tag/version/topological publication, and alone receives `NPM_TOKEN`, `id-token: write`, and `attestations: write`. Before npm publish it packs all current tarballs, generates checksums plus SPDX, scans unpacked public artifacts, creates GitHub attestations for tarballs and SBOM, then retains artifacts for 30 days. Registry state remains the resumable journal. Local `npm run release:dry-run` remains network-free SDK readiness; local PostgreSQL coverage is `PRISM_TEST_POSTGRES_URL=... npm run test:postgres`.
|
|
291
295
|
- **Adding a package.** New workspace packages are picked up automatically by `npm run build --workspaces`, `npm test --workspaces`, `npm run pack:dry-run`, the packaging guard (`src/__tests__/packaging.test.ts`), and the install-smoke test (`src/__tests__/install-smoke.test.ts`) via the workspace glob; add the package to both tests' config arrays for explicit per-package assertions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arnilo/prism",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"description": "Agent harness for AI providers, agents, sessions, and tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"clean": "rm -rf dist packages/*/dist",
|
|
142
142
|
"build": "npm run clean && npm run build:core && npm run build --workspaces --if-present",
|
|
143
143
|
"typecheck": "npm run build && npm run typecheck --workspaces --if-present && tsc -p examples --noEmit",
|
|
144
|
-
"test": "npm run build && node --test dist/__tests__/*.test.js && node --test scripts/release-gate.test.mjs scripts/tooling-gate.test.mjs scripts/budget-gate.test.mjs scripts/phase8-conformance.test.mjs && npm run test --workspaces --if-present",
|
|
144
|
+
"test": "npm run build && node --test dist/__tests__/*.test.js && node --test scripts/release-gate.test.mjs scripts/tooling-gate.test.mjs scripts/budget-gate.test.mjs scripts/phase8-conformance.test.mjs scripts/phase9-conformance.test.mjs && npm run test --workspaces --if-present",
|
|
145
145
|
"test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=60 --test-coverage-functions=70 --test-coverage-branches=75 --test-coverage-exclude='**/__tests__/**' --test-coverage-exclude='**/node_modules/**' --test-coverage-exclude='**/scripts/**' dist/__tests__/*.test.js",
|
|
146
146
|
"lint": "biome lint .",
|
|
147
147
|
"format": "biome format --write .",
|