@arnilo/prism 0.3.2 → 0.5.0
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 +50 -1
- package/README.md +42 -62
- package/dist/agent-run-lifecycle.js +4 -0
- package/dist/agent-run-state.d.ts +5 -2
- package/dist/agent-run-state.js +18 -8
- package/dist/agent-session/session/assemble.d.ts +6 -0
- package/dist/agent-session/session/assemble.js +391 -0
- package/dist/agent-session/session/persist.d.ts +28 -0
- package/dist/agent-session/session/persist.js +166 -0
- package/dist/agent-session/session/provider-round.d.ts +6 -0
- package/dist/agent-session/session/provider-round.js +231 -0
- package/dist/agent-session/session/tool-round.d.ts +31 -0
- package/dist/agent-session/session/tool-round.js +473 -0
- package/dist/agent-session/session/types.d.ts +115 -0
- package/dist/agent-session/session/types.js +5 -0
- package/dist/agent-session/session.d.ts +54 -41
- package/dist/agent-session/session.js +23 -1132
- package/dist/capture.d.ts +63 -0
- package/dist/capture.js +67 -0
- package/dist/cli-dev.d.ts +29 -0
- package/dist/cli-dev.js +52 -0
- package/dist/cli-init.d.ts +34 -3
- package/dist/cli-init.js +192 -24
- package/dist/cli-runner.d.ts +6 -2
- package/dist/cli-runner.js +57 -10
- package/dist/content.d.ts +3 -3
- package/dist/content.js +3 -1
- package/dist/contracts-core/agent.d.ts +8 -0
- package/dist/contracts-core/batch.d.ts +97 -0
- package/dist/contracts-core/batch.js +65 -0
- package/dist/contracts-core/content.d.ts +72 -1
- package/dist/contracts-core/embeddings.d.ts +30 -0
- package/dist/contracts-core/embeddings.js +17 -0
- package/dist/contracts-core/images.d.ts +60 -0
- package/dist/contracts-core/images.js +17 -0
- package/dist/contracts-core/moderation.d.ts +46 -0
- package/dist/contracts-core/moderation.js +34 -0
- package/dist/contracts-core/speech.d.ts +39 -0
- package/dist/contracts-core/speech.js +17 -0
- package/dist/contracts-core/transcription.d.ts +48 -0
- package/dist/contracts-core/transcription.js +17 -0
- package/dist/contracts-core/video.d.ts +61 -0
- package/dist/contracts-core/video.js +17 -0
- package/dist/contracts-core.d.ts +7 -0
- package/dist/contracts-core.js +7 -0
- package/dist/contracts-protocol.d.ts +18 -0
- package/dist/contracts-run-state.d.ts +1 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -3
- package/dist/input.d.ts +8 -0
- package/dist/input.js +4 -0
- package/dist/node/agent-definitions.d.ts +1 -8
- package/dist/node/agent-definitions.js +0 -34
- package/dist/node/settings.d.ts +0 -1
- package/dist/node/settings.js +0 -5
- package/dist/pinned-fetch.js +29 -3
- package/dist/provider-events.js +3 -4
- package/dist/providers/media.d.ts +1 -2
- package/dist/providers/media.js +1 -4
- package/dist/rpc.d.ts +1 -1
- package/dist/rpc.js +4 -4
- package/dist/testing/persistence-schema.d.ts +1 -1
- package/dist/testing/persistence-schema.js +32 -28
- package/dist/testing/provider-conformance.d.ts +114 -5
- package/dist/testing/provider-conformance.js +342 -0
- package/dist/testing/tool-conformance.d.ts +25 -0
- package/dist/testing/tool-conformance.js +128 -1
- package/dist/testing/tool-effect-store-conformance.d.ts +0 -1
- package/dist/testing/tool-effect-store-conformance.js +0 -3
- package/dist/thinking.d.ts +48 -9
- package/dist/thinking.js +134 -8
- package/dist/tool-search.d.ts +76 -0
- package/dist/tool-search.js +199 -0
- package/docs/0.1.0-readiness.md +3 -3
- package/docs/a2a.md +2 -2
- package/docs/acp-agent.md +1 -1
- package/docs/acp.md +3 -3
- package/docs/ag-ui-adoption.md +1 -1
- package/docs/ag-ui.md +1 -2
- package/docs/agent-definitions.md +1 -1
- package/docs/agent-events.md +5 -5
- package/docs/agent-identity.md +13 -2
- package/docs/audit-export.md +3 -3
- package/docs/batch-jobs.md +120 -0
- package/docs/browser-automation.md +5 -5
- package/docs/caveman.md +2 -2
- package/docs/cli-rpc.md +43 -9
- package/docs/coding-agent-tools.md +19 -19
- package/docs/coding-review-and-diagnostics.md +2 -2
- package/docs/coding-security.md +5 -5
- package/docs/coding-tools.md +82 -0
- package/docs/coding-workspaces.md +2 -2
- package/docs/compaction-and-retry.md +2 -2
- package/docs/compaction-llm.md +4 -4
- package/docs/compaction-observational-memory.md +3 -3
- package/docs/computer-use-linux.md +13 -2
- package/docs/context-and-skills.md +3 -1
- package/docs/conversations.md +4 -4
- package/docs/core.md +85 -0
- package/docs/credential-storage.md +12 -8
- package/docs/credentials-and-redaction.md +1 -1
- package/docs/data-classification.md +1 -1
- package/docs/database-persistence.md +7 -3
- package/docs/dev-inspector.md +103 -0
- package/docs/device-adapters.md +2 -2
- package/docs/diagrams.md +247 -0
- package/docs/document-reader.md +6 -6
- package/docs/documents.md +214 -0
- package/docs/embeddings.md +112 -0
- package/docs/enterprise-postgres-state.md +7 -7
- package/docs/evaluations.md +41 -7
- package/docs/extensions.md +3 -3
- package/docs/forge-integration.md +3 -3
- package/docs/graft.md +5 -5
- package/docs/guardrails.md +2 -2
- package/docs/host-security.md +16 -15
- package/docs/image-generation.md +129 -0
- package/docs/impeccable.md +7 -5
- package/docs/index.md +84 -46
- package/docs/indexed-code-search.md +2 -2
- package/docs/language-intelligence.md +4 -4
- package/docs/live-testing.md +126 -0
- package/docs/mcp-tools.md +44 -13
- package/docs/middleware-hooks.md +1 -1
- package/docs/migrate-to-0.4.md +312 -0
- package/docs/migrate-to-0.5.md +122 -0
- package/docs/migration.md +51 -1
- package/docs/model-registry.md +38 -0
- package/docs/model-routing.md +6 -6
- package/docs/moderation.md +117 -0
- package/docs/multi-agent-patterns.md +177 -0
- package/docs/multimodal-content.md +27 -3
- package/docs/obscura.md +12 -12
- package/docs/observability.md +32 -7
- package/docs/openapi-tools.md +14 -4
- package/docs/operations.md +11 -0
- package/docs/performance.md +30 -10
- package/docs/persistence-credentials-multimodality-primitives.md +7 -7
- package/docs/policy-and-audit.md +18 -8
- package/docs/ponytail.md +3 -3
- package/docs/postgres-persistence.md +5 -5
- package/docs/process-sessions.md +2 -2
- package/docs/prompt-registry.md +106 -0
- package/docs/provider-caching.md +36 -32
- package/docs/provider-conformance.md +24 -2
- package/docs/provider-packages.md +58 -22
- package/docs/provider-primitives.md +5 -5
- package/docs/provider-request-policies.md +1 -1
- package/docs/providers/ai-sdk.md +18 -6
- package/docs/providers/alibaba.md +10 -6
- package/docs/providers/anthropic.md +10 -6
- package/docs/providers/azure.md +20 -4
- package/docs/providers/bedrock.md +18 -3
- package/docs/providers/clinepass.md +7 -3
- package/docs/providers/commandcode.md +253 -0
- package/docs/providers/deepseek.md +7 -3
- package/docs/providers/google.md +8 -4
- package/docs/providers/hyper.md +284 -0
- package/docs/providers/kimi.md +7 -3
- package/docs/providers/neuralwatt.md +12 -8
- package/docs/providers/ollama.md +18 -3
- package/docs/providers/openai-compatible.md +5 -1
- package/docs/providers/openai.md +9 -5
- package/docs/providers/opencode-go.md +8 -4
- package/docs/providers/openrouter.md +8 -4
- package/docs/providers/vertex.md +21 -5
- package/docs/providers/xai.md +7 -3
- package/docs/providers/zai.md +7 -3
- package/docs/rag.md +31 -9
- package/docs/release-and-install.md +181 -76
- package/docs/resource-loading.md +1 -1
- package/docs/runs-and-usage.md +28 -3
- package/docs/server.md +94 -5
- package/docs/settings-auth-trust-security.md +7 -5
- package/docs/sheets.md +229 -0
- package/docs/speech.md +126 -0
- package/docs/sqlite-persistence.md +4 -4
- package/docs/supervisors.md +4 -3
- package/docs/thinking-and-reasoning.md +93 -60
- package/docs/tool-conformance.md +28 -3
- package/docs/tool-execution-primitives.md +8 -8
- package/docs/tools.md +32 -5
- package/docs/web-tools.md +3 -3
- package/docs/wiki.md +7 -7
- package/docs/work-artifacts-and-review.md +17 -6
- package/docs/work-connectors.md +4 -4
- package/docs/work-tools.md +5 -5
- package/docs/workflow-orchestration-primitives.md +35 -11
- package/docs/workflows.md +74 -13
- package/docs/working-and-semantic-memory.md +53 -5
- package/package.json +14 -31
- package/templates/README.md +23 -0
- package/templates/deep-research/README.md.tmpl +47 -0
- package/templates/deep-research/env.example.tmpl +12 -0
- package/templates/deep-research/gitignore.tmpl +7 -0
- package/templates/deep-research/manifest.json +12 -0
- package/templates/deep-research/package.json.tmpl +23 -0
- package/templates/deep-research/src/agent.ts.tmpl +81 -0
- package/templates/deep-research/src/index.ts.tmpl +53 -0
- package/templates/deep-research/src/tests/research.test.ts.tmpl +114 -0
- package/templates/deep-research/src/tools.ts.tmpl +86 -0
- package/templates/deep-research/src/types.ts.tmpl +45 -0
- package/templates/deep-research/src/workflow.ts.tmpl +156 -0
- package/templates/deep-research/tsconfig.json.tmpl +15 -0
- package/templates/init/manifest.json +5 -0
- package/templates/init/package.json.tmpl +2 -1
- package/templates/init/providers.json +40 -24
- package/docs/antigravity-agent.md +0 -207
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
4
|
|
|
5
|
-
Prism core ships generic `audio`, `file`, and `
|
|
5
|
+
Prism core ships generic `audio`, `file`, `document`, and `video` `ContentBlock` types plus bounded media resolution helpers. Blocks carry MIME type, optional name, and exactly one source: inline base64 `data`, remote `url`, or host `resourceUri`. Optional `transcript` metadata can accompany audio/document blocks.
|
|
6
6
|
|
|
7
7
|
`assembleProviderInput()` calls `assertMessagesSupportModelCapabilities()` so declared `ModelCapabilities.input` tags are enforced before provider calls. First-party provider packages map supported blocks locally; unsupported combinations fail closed with `UnsupportedModalityError` or an explicit provider error.
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ Prism core ships generic `audio`, `file`, and `document` `ContentBlock` types pl
|
|
|
10
10
|
|
|
11
11
|
- **Host apps** attaching PDFs, audio clips, or generic files to user messages before a provider turn.
|
|
12
12
|
- **Resource loaders** returning binary payloads for `resourceUri` references under trust/permission policy.
|
|
13
|
-
- **Provider authors** reading truthful `ModelCapabilities.input` tags (`text`, `image`, `audio`, `file`, `document`) before mapping wire formats.
|
|
13
|
+
- **Provider authors** reading truthful `ModelCapabilities.input` tags (`text`, `image`, `audio`, `file`, `document`, `video`) before mapping wire formats.
|
|
14
14
|
|
|
15
15
|
Do not embed provider upload IDs, tenant-scoped remote file IDs, or API-specific handles in core content blocks.
|
|
16
16
|
|
|
@@ -54,6 +54,7 @@ Known `ModelCapabilities.input` tags are exported as `MODEL_INPUT_CAPABILITIES`:
|
|
|
54
54
|
| `audio` | `audio` | OpenAI Responses (`input_audio`) and Google `generateContent` inline data. OpenAI Realtime instead receives `RealtimeSession.sendAudio()` chunks, not an `audio` `ContentBlock`. |
|
|
55
55
|
| `file` | `file` | OpenAI Responses (`input_file`); Anthropic/Kimi/OpenCode Go Anthropic route accept PDF file/document forms; Google maps inline file data. |
|
|
56
56
|
| `document` | `document` | OpenAI Responses (`input_file`); Anthropic/Kimi/OpenCode Go Anthropic route map PDF; Google maps inline document data. |
|
|
57
|
+
| `video` | `video` | Alibaba (Qwen-VL compatible mode maps to `video_url`, plan 061 Task 5); other providers reject with `UnsupportedModalityError` until they declare and map the tag. Optional `fps` frame-sampling hint and `durationMs` ride on the block. |
|
|
57
58
|
|
|
58
59
|
The AI SDK adapter maps declared user text/image/audio/file/document blocks (and assistant text/image/file/document) to AI SDK file parts; `resourceUri` remains host-resolved before `doStream`. Its output `file`, `reasoning-file`, and `source` parts are deliberately rejected as `unsupported_mapping`, not converted to trusted Prism content. Provider capability metadata is the gate—this matrix never upgrades a model that does not declare the matching input tag.
|
|
59
60
|
|
|
@@ -130,13 +131,36 @@ try {
|
|
|
130
131
|
}
|
|
131
132
|
```
|
|
132
133
|
|
|
134
|
+
## Video generation (output)
|
|
135
|
+
|
|
136
|
+
Video generation is a separate, deliberately minimal contract — jobs run minutes,
|
|
137
|
+
not seconds, so there is no synchronous `generate`. `VideoGenerationProvider`
|
|
138
|
+
(plan 061 Task 5, from `@arnilo/prism`) has two methods:
|
|
139
|
+
|
|
140
|
+
- `submit(request)` → `{ jobId }`; the request carries `model`, `prompt`, optional
|
|
141
|
+
`images` (first entry wins, image-to-video), `size`, `durationSeconds`, `fps`, and
|
|
142
|
+
an `AbortSignal`. Providers without image-to-video reject with a typed
|
|
143
|
+
`VideoGenerationError("unsupported_operation")`-shaped error path.
|
|
144
|
+
- `status(jobId, signal?)` → point-in-time `VideoGenerationJob` with `state`
|
|
145
|
+
(`queued` / `running` / `succeeded` / `failed`), a `video` (with `provider`/`model`
|
|
146
|
+
provenance and `bytes` or `url`) on success, and an `error` message on failure.
|
|
147
|
+
Hosts own the polling loop.
|
|
148
|
+
|
|
149
|
+
Models declare the `capabilities.videoGeneration` flag; hosts gate with
|
|
150
|
+
`modelSupportsVideoGeneration()` / `assertVideoGenerationSupported()`. The Alibaba
|
|
151
|
+
adapter (`createAlibabaVideoGenerationProvider` from
|
|
152
|
+
`@arnilo/prism-providers/alibaba`) runs the DashScope wanx async-task lifecycle
|
|
153
|
+
(text-to-video and image-to-video routes) with an adapter-local `waitFor()`
|
|
154
|
+
convenience poller; conformance runs offline via `runVideoGenerationConformance()`
|
|
155
|
+
from `@arnilo/prism/testing/provider-conformance`.
|
|
156
|
+
|
|
133
157
|
## Extension and configuration notes
|
|
134
158
|
|
|
135
159
|
- URL fetches use the DNS-classifying, address-pinned Node transport by default. `resolveHostname` and `requestUrl` are paired test/custom seams; `requestUrl` must connect to the supplied validated address while preserving the original URL hostname for HTTP Host/TLS verification.
|
|
136
160
|
- Supplying `fetch` is a trusted compatibility/custom-transport escape hatch: Prism still checks URL literals and host allow-lists, but the host-provided fetch owns DNS resolution, rebinding protection, redirects, proxies, TLS, auth, and logging.
|
|
137
161
|
- `resourceUri` resolution requires a caller-provided `ResourceLoader` and optional `ResourceLoadContext.permission` check.
|
|
138
162
|
- Local filesystem paths should use trust policies such as `createPathTrustPolicy()` before exposing URIs to loaders.
|
|
139
|
-
- Provider upload/create/delete lifecycles are provider-package-local. `@arnilo/prism-
|
|
163
|
+
- Provider upload/create/delete lifecycles are provider-package-local. `@arnilo/prism-providers/openai` inlines files under 4 MiB as `data:<mediaType>;base64,...` `file_data`, otherwise uses a bounded per-run upload cache and best-effort `DELETE /v1/files` cleanup after each stream.
|
|
140
164
|
- Shared wire helpers live in `@arnilo/prism/providers/media` (`resolveProviderMediaMessages`, `serializeOpenAIResponsesInputFile`, `serializePdfDocumentWireBlock`, `createBoundedUploadCache`). OpenAI Responses, Kimi, and OpenCode Go Anthropic routes resolve their complete media collection once before serialization or upload.
|
|
141
165
|
- OpenAI Realtime audio is a bidirectional `RealtimeSession` stream, not a `ContentBlock`: provide host-captured `Uint8Array` chunks with `sendAudio()` and consume untrusted `audio_delta` / transcript events. It has a fixed 256 events/s, 1 MiB/s, and 600 s default ceiling.
|
|
142
166
|
|
package/docs/obscura.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Obscura browser engine
|
|
2
2
|
|
|
3
|
-
Optional `@arnilo/prism-obscura` support for a host-installed
|
|
3
|
+
Optional `@arnilo/prism-web-tools/obscura` support for a host-installed
|
|
4
4
|
[Obscura](https://github.com/h4ckf0r0day/obscura) headless browser. Obscura is never
|
|
5
5
|
bundled — install the binary (or use the `h4ckf0r0day/obscura` Docker image) and point
|
|
6
6
|
the package at it.
|
|
@@ -11,7 +11,7 @@ the package at it.
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm install @arnilo/prism-
|
|
14
|
+
npm install @arnilo/prism-web-tools @arnilo/prism-mcp
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Process lifecycle (`spawnObscuraProcess`)
|
|
@@ -23,7 +23,7 @@ environment (`PATH`, `HOME`), and insecure flags (`--allow-private-network`,
|
|
|
23
23
|
is set explicitly.
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
|
-
import { spawnObscuraProcess } from "@arnilo/prism-obscura";
|
|
26
|
+
import { spawnObscuraProcess } from "@arnilo/prism-web-tools/obscura";
|
|
27
27
|
|
|
28
28
|
const obscura = spawnObscuraProcess({
|
|
29
29
|
command: "/usr/local/bin/obscura",
|
|
@@ -53,7 +53,7 @@ Connects to `obscura mcp` (stdio or Streamable HTTP) through
|
|
|
53
53
|
allow-list, so future Obscura tools keep flowing through.
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
|
-
import { createObscuraMcpTools } from "@arnilo/prism-obscura";
|
|
56
|
+
import { createObscuraMcpTools } from "@arnilo/prism-web-tools/obscura";
|
|
57
57
|
|
|
58
58
|
const obscura = await createObscuraMcpTools({
|
|
59
59
|
transport: { type: "stdio", command: "/usr/local/bin/obscura", args: ["mcp"] },
|
|
@@ -68,7 +68,7 @@ await obscura.close();
|
|
|
68
68
|
- Effects: read/diagnostic/waiter/capture tools are effect-free; navigation,
|
|
69
69
|
interaction, evaluation, cookie/storage writes, tabs, and any unknown future tool
|
|
70
70
|
are exclusive, serialized external mutations (Obscura keeps one live page).
|
|
71
|
-
- Naming: default `obscura_` prefix coexists with
|
|
71
|
+
- Naming: default `obscura_` prefix coexists with the `browser` subpath;
|
|
72
72
|
`namePrefix: ""` preserves native Obscura names.
|
|
73
73
|
- Transports: stdio configs are validated with the same fail-closed command policy;
|
|
74
74
|
Streamable HTTP endpoints outside loopback require explicit `allowRemoteHttp` and
|
|
@@ -82,8 +82,8 @@ host's Playwright via `chromium.connectOverCDP`. `connect()` and browser launch
|
|
|
82
82
|
never used; Prism never launches browsers.
|
|
83
83
|
|
|
84
84
|
```ts
|
|
85
|
-
import { connectObscuraCdp } from "@arnilo/prism-obscura";
|
|
86
|
-
import { createBrowserTools } from "@arnilo/prism-browser";
|
|
85
|
+
import { connectObscuraCdp } from "@arnilo/prism-web-tools/obscura";
|
|
86
|
+
import { createBrowserTools } from "@arnilo/prism-web-tools/browser";
|
|
87
87
|
|
|
88
88
|
const session = await connectObscuraCdp({
|
|
89
89
|
command: "/usr/local/bin/obscura",
|
|
@@ -107,7 +107,7 @@ await session.close(); // browser first, then the owned process
|
|
|
107
107
|
APIs (`browser.newBrowserCDPSession()`, `context.newCDPSession(page)`); the package
|
|
108
108
|
adds no CDP command allow-list.
|
|
109
109
|
- Concurrency limit: pages served by one Obscura worker share one V8 isolate —
|
|
110
|
-
CPU-bound page JavaScript can delay sibling pages. Keep
|
|
110
|
+
CPU-bound page JavaScript can delay sibling pages. Keep the `browser` subpath
|
|
111
111
|
limits authoritative; size Obscura's `--workers` for the host.
|
|
112
112
|
- Screenshots/PDF require a render-enabled Obscura build and still obey the browser
|
|
113
113
|
package's artifact/byte policy.
|
|
@@ -119,7 +119,7 @@ child processes. Returns standard Prism `web_search`/`web_fetch` tools plus expl
|
|
|
119
119
|
`obscura_fetch`/`obscura_scrape` (disable with `nativeTools: false`).
|
|
120
120
|
|
|
121
121
|
```ts
|
|
122
|
-
import { createObscuraWebTools } from "@arnilo/prism-obscura";
|
|
122
|
+
import { createObscuraWebTools } from "@arnilo/prism-web-tools/obscura";
|
|
123
123
|
|
|
124
124
|
const web = createObscuraWebTools({ command: "/usr/local/bin/obscura" });
|
|
125
125
|
agent.tools = [...agent.tools, ...web.tools];
|
|
@@ -149,7 +149,7 @@ agent.tools = [...agent.tools, ...web.tools];
|
|
|
149
149
|
- Docker-style invocations work through `argsBefore` (e.g.
|
|
150
150
|
`["run", "--rm", "-i", "h4ckf0r0day/obscura"]`).
|
|
151
151
|
- An opt-in live smoke test runs against a real installed binary with
|
|
152
|
-
`npm run test:live -w @arnilo/prism-
|
|
152
|
+
`npm run test:live -w @arnilo/prism-web-tools` plus `PRISM_LIVE_OBSCURA=1` and
|
|
153
153
|
`PRISM_OBSCURA_BIN=/path/to/obscura`.
|
|
154
154
|
|
|
155
155
|
## Host conformance (one generic integration)
|
|
@@ -158,8 +158,8 @@ agent.tools = [...agent.tools, ...web.tools];
|
|
|
158
158
|
the same `ToolDefinition[]` with one read tool (`web_fetch`) and one mutating tool
|
|
159
159
|
(`obscura_scrape`) — through every Prism host's public API: core agent/session
|
|
160
160
|
execution, the Prism MCP server, the `createPrismHandler` server lifecycle, AG-UI
|
|
161
|
-
MCP-tool injection, ACP fronting, workflow `toolNode`/`agentNode`s, supervisor
|
|
162
|
-
children
|
|
161
|
+
MCP-tool injection, ACP fronting, workflow `toolNode`/`agentNode`s, and supervisor
|
|
162
|
+
children. It verifies host authorization
|
|
163
163
|
and selection deny before execution, that no host needs an Obscura-specific branch,
|
|
164
164
|
and that an aborted in-flight call settles and kills the owned child. Composition
|
|
165
165
|
walkthrough: [`examples/obscura.ts`](../examples/obscura.ts).
|
package/docs/observability.md
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
Prism exposes provider and tool timing through stable, metadata-only `AgentEvent` variants. Hosts subscribe via `session.subscribe()` or persist events through `RunLedger`. Core helpers build `ProviderTurnMetadata` and classify HTTP failures without echoing prompts, tool arguments, or credentials.
|
|
6
6
|
|
|
7
|
-
Optional package `@arnilo/prism-observability
|
|
7
|
+
Optional package `@arnilo/prism-core/governance/observability` maps those events to OpenTelemetry spans and low-cardinality metrics, and adapts `@arnilo/prism-memory/rag`'s dependency-free telemetry seam (`createRagTelemetry()`) onto the same tracer. OpenTelemetry is **not** a dependency of `@arnilo/prism`.
|
|
8
8
|
|
|
9
9
|
APIs:
|
|
10
10
|
|
|
11
11
|
- `ProviderTurnMetadata`, `ToolExecutionMetadata` on `AgentEvent`
|
|
12
12
|
- `createProviderTurnMetadata()`, `readProviderHttpStatus()` in `@arnilo/prism`
|
|
13
|
-
- `createOpenTelemetryInstrumentation()`, `wrapOpenTelemetryApi()`, `createInMemoryTelemetry()` in `@arnilo/prism-observability
|
|
14
|
-
- `createRagTelemetry()` in `@arnilo/prism-observability
|
|
13
|
+
- `createOpenTelemetryInstrumentation()`, `wrapOpenTelemetryApi()`, `createInMemoryTelemetry()` in `@arnilo/prism-core/governance/observability`
|
|
14
|
+
- `createRagTelemetry()` in `@arnilo/prism-core/governance/observability` (RAG spans/events; see span tree below)
|
|
15
15
|
- `handleRunFeedback()` / `handleEvaluation()` for explicit safe post-run projection
|
|
16
16
|
|
|
17
17
|
## When to use it
|
|
@@ -51,7 +51,7 @@ OpenTelemetry adapter:
|
|
|
51
51
|
|
|
52
52
|
```ts
|
|
53
53
|
import { trace, metrics } from "@opentelemetry/api";
|
|
54
|
-
import { createOpenTelemetryInstrumentation, wrapOpenTelemetryApi } from "@arnilo/prism-observability
|
|
54
|
+
import { createOpenTelemetryInstrumentation, wrapOpenTelemetryApi } from "@arnilo/prism-core/governance/observability";
|
|
55
55
|
|
|
56
56
|
const { tracer, meter } = wrapOpenTelemetryApi(
|
|
57
57
|
trace.getTracer("app"),
|
|
@@ -97,7 +97,7 @@ OpenTelemetry mapping (when enabled):
|
|
|
97
97
|
| `handleRunFeedback` | active-run `prism.run.feedback` event or ended-run span | `prism.run.feedback` |
|
|
98
98
|
| `handleEvaluation` | active-run `gen_ai.evaluation.result` event or ended-run span | `prism.run.evaluation` (`status`) |
|
|
99
99
|
|
|
100
|
-
RAG span tree (`@arnilo/prism-rag` + `createRagTelemetry()`):
|
|
100
|
+
RAG span tree (`@arnilo/prism-memory/rag` + `createRagTelemetry()`):
|
|
101
101
|
|
|
102
102
|
| Span | Parent | Notes |
|
|
103
103
|
| --- | --- | --- |
|
|
@@ -148,7 +148,7 @@ High-cardinality identifiers (`sessionId`, `runId`, `requestId`, `toolCallId`) a
|
|
|
148
148
|
|
|
149
149
|
```ts
|
|
150
150
|
import { createAgent, createMockProvider, providerDone, providerTextDelta } from "@arnilo/prism";
|
|
151
|
-
import { createInMemoryTelemetry, createOpenTelemetryInstrumentation } from "@arnilo/prism-observability
|
|
151
|
+
import { createInMemoryTelemetry, createOpenTelemetryInstrumentation } from "@arnilo/prism-core/governance/observability";
|
|
152
152
|
|
|
153
153
|
const memory = createInMemoryTelemetry();
|
|
154
154
|
const telemetry = createOpenTelemetryInstrumentation({ tracer: memory.tracer, meter: memory.meter });
|
|
@@ -176,17 +176,42 @@ const found = await retrieveContext("policy", { embedder, store, scope, telemetr
|
|
|
176
176
|
- Events flow through `redactAgentEvent` before subscribers and ledger writes — configure `createSecretRedactor` on the agent/run.
|
|
177
177
|
- `retry_scheduled` still signals backoff; each retry attempt emits its own `provider_turn_*` pair with `metadata.attempt`.
|
|
178
178
|
- NeuralWatt `neuralwatt:telemetry` provider events remain package-local; hosts may forward numeric cost/energy into custom metrics.
|
|
179
|
-
- `@arnilo/prism-observability
|
|
179
|
+
- `@arnilo/prism-core/governance/observability` is optional and included through `@arnilo/prism-core` family installs; instrumentation remains disabled until a host configures it.
|
|
180
180
|
- Exporter failures are isolated: instrumentation catches tracer/meter errors and invokes `onExporterError` without affecting the run, feedback persistence, or evaluation scoring.
|
|
181
181
|
- Trace grading uses `createPersistenceTraceResolver()` with explicit session/run/ownership and finite pages/bytes. Judge reasons remain evaluation data; `gen_ai.evaluation.result` receives only name, finite score, controlled status, and reason-presence.
|
|
182
182
|
- Run spans parent provider, tool, guardrail, and explicit delegation spans. Pass `{ context, trace }` to `wrapOpenTelemetryApi()` for native parent context creation; `parentContext` can attach the run to host ambient/remote context.
|
|
183
183
|
- `onTraceReference` receives `{ runId, traceId }` when a run starts. `traceId(runId)` keeps only the newest 1,024 mappings by default (`maxTraceReferences`, hard cap 10,000); durable linkage remains host-owned.
|
|
184
184
|
- Run `error`, suspension, denial, and detach close every attributable span. Repeated terminal events are idempotent and cannot end a span twice.
|
|
185
185
|
- Disabled instrumentation performs no per-delta span work (`enabled: false` or missing tracer/meter).
|
|
186
|
+
- `createProviderCapture()` (plan 062) is the opt-in request/response capture middleware: register `capture.middleware()` on the existing `provider_request` hook and feed `provider_turn_finished` events from the session subscriber loop into `capture.observeEvent()`. Entries land in a capped FIFO ring buffer (`policy.maxEvents`, default 100) exposed via `capture.events()`.
|
|
187
|
+
|
|
188
|
+
### Provider request/response capture middleware
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { createProviderCapture, createMiddlewareRegistry } from "@arnilo/prism";
|
|
192
|
+
|
|
193
|
+
const capture = createProviderCapture({
|
|
194
|
+
secrets, // same redactor seam as the logging paths
|
|
195
|
+
policy: { redact: "secrets", maxEvents: 100 },
|
|
196
|
+
});
|
|
197
|
+
const middleware = createMiddlewareRegistry({ secrets });
|
|
198
|
+
middleware.use("provider_request", capture.middleware()); // request entries, pass-through
|
|
199
|
+
|
|
200
|
+
for await (const event of session.subscribe()) {
|
|
201
|
+
if (event.type === "provider_turn_finished") capture.observeEvent(event); // response entries
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const entries = capture.events(); // oldest-first snapshot; capture.clear() resets
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- The `policy.redact` field governs content retention: `"all"` keeps structure only, `"secrets"` (default) also drops message content, `"none"` retains message content for replay debugging. Secret redaction through the shared logging helpers is unconditional in every mode — captured buffers are replay-safe by construction.
|
|
208
|
+
- Captured shapes are already-normalized (`ProviderRequest` on the request side, `provider_turn_finished` metadata/usage on the response side) — never raw HTTP. Request/response `options` and headers are never captured at all (headers are where credentials ride).
|
|
209
|
+
- Disabled by default with zero overhead: an unregistered capture performs no work; the enabled path adds one entry per round plus the pass-through.
|
|
186
210
|
|
|
187
211
|
## Security and performance notes
|
|
188
212
|
|
|
189
213
|
- Default events are metadata-only — no prompts, streamed deltas, tool arguments, or credentials.
|
|
214
|
+
- Capture middleware follows the same default: `redact: "secrets"` drops message content; buffers are capped and secrets are redacted unconditionally, so a captured buffer can be persisted or replayed without leaking credentials.
|
|
190
215
|
- Use `identityTelemetryAttributes(identity)` when attaching enterprise identity to run metadata or OTel attributes; it emits `prism.identity.*` refs only (tenant/principal/scope counts), never credential secrets or raw tokens.
|
|
191
216
|
- Opt-in content in other event types (`message_delta`, tool `result`) is still subject to `redactAgentEvent`.
|
|
192
217
|
- Metric labels stay low-cardinality (`gen_ai.operation.name`, `gen_ai.provider.name`, token type, controlled outcome/status, feedback rating bucket/link presence); never use session/run/request/call IDs, model output, comments, tag values, scorer/evaluation IDs, or arbitrary metadata as labels. Token usage is recorded once at provider operation scope.
|
package/docs/openapi-tools.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
# OpenAPI tools adapter (`@arnilo/prism-
|
|
1
|
+
# OpenAPI tools adapter (`@arnilo/prism-coding-tools/openapi`)
|
|
2
2
|
|
|
3
3
|
Optional `createOpenApiTools` compiles host-selected OpenAPI 3.1 operations into bounded Prism `ToolDefinition`s. Zero dependencies (native fetch + WebCrypto-free); the compile step is pure and separated from the runtime executor.
|
|
4
4
|
|
|
5
5
|
## When to use it
|
|
6
6
|
|
|
7
|
-
Hosts that already expose a JSON API with an OpenAPI 3.1 document and want the agent to call a **fixed, host-chosen subset** of it — never model-driven discovery, never a raw method/path passthrough. For vendor web search/extraction use `@arnilo/prism-web-tools`; for M365/GWS use `@arnilo/prism-work
|
|
7
|
+
Hosts that already expose a JSON API with an OpenAPI 3.1 document and want the agent to call a **fixed, host-chosen subset** of it — never model-driven discovery, never a raw method/path passthrough. For vendor web search/extraction use `@arnilo/prism-web-tools`; for M365/GWS use `@arnilo/prism-core/integrations/work`; this adapter is for arbitrary host APIs.
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
import { createOpenApiTools } from "@arnilo/prism-
|
|
12
|
+
import { createOpenApiTools } from "@arnilo/prism-coding-tools/openapi";
|
|
13
13
|
|
|
14
14
|
const tools = createOpenApiTools({
|
|
15
15
|
document, // OpenAPI 3.1 document (JSON string or parsed object)
|
|
@@ -48,9 +48,19 @@ Register the returned tools with `createToolRegistry` (or pass them to the MCP b
|
|
|
48
48
|
|
|
49
49
|
Defaults and hard caps (frozen in `scripts/phase11-freeze-manifest.json`): `maxDocumentBytes` 2 MiB/16 MiB, `maxOperations` 256/1024, `maxSchemaDepth` 32/128, `maxRefs` 1024/8192, `maxBodyBytes` 1 MiB/16 MiB, `maxResponseBytes` 1 MiB/16 MiB, `maxPages` 20/100, `maxPaginationItems` 1000/10000, `maxRetries` 0/3. Invalid limits throw `ERR_PRISM_OPENAPI_DOCUMENT_BOUNDS`.
|
|
50
50
|
|
|
51
|
+
## Live probe (plans/064 Task 7)
|
|
52
|
+
|
|
53
|
+
A live wire probe compiles the real public Warnely OpenAPI 3.1 spec (petstore serves 3.0 — the compiler requires 3.1) and drives real GET operations against it:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
PRISM_LIVE_OPENAPI_TOOLS=1 node --test packages/prism-coding-tools/dist/openapi/__tests__/live.test.js
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
3 requests total against the allow-listed public host: spec compile, a real 200 tool call, and a real 404 mapped to a status-carrying untrusted result; a missing-argument case proves validation fails closed locally (zero wire calls). Skips (never fails) when `PRISM_LIVE_OPENAPI_TOOLS` is unset. Registered in `scripts/live-matrix.json` as `coding-tools/openapi-live`.
|
|
60
|
+
|
|
51
61
|
## Related
|
|
52
62
|
|
|
53
63
|
- [Tools](tools.md): registry, dispatch, validation
|
|
54
64
|
- [Recoverable tool effects](tool-effects.md): approval + idempotency contracts
|
|
55
65
|
- [Host security guide](host-security.md): permission, trust, validation checklist
|
|
56
|
-
- Package README: [`@arnilo/prism-
|
|
66
|
+
- Package README: [`@arnilo/prism-coding-tools`](../packages/prism-coding-tools/README.md)
|
package/docs/operations.md
CHANGED
|
@@ -94,6 +94,17 @@ tenant's reads, writes, and lease takeover all fail closed.
|
|
|
94
94
|
with bounded, jittered acquisition polls (no hot loops) and reports the
|
|
95
95
|
measured numbers in the evidence JSON.
|
|
96
96
|
|
|
97
|
+
## Live probe (plans/064 Task 9)
|
|
98
|
+
|
|
99
|
+
The outbound webhook notifier has an operator-gated live probe against a receiver you own:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
PRISM_TEST_WEBHOOK_URL=https://ops.example.com/hooks/prism \
|
|
103
|
+
PRISM_TEST_WEBHOOK_SECRET=<at-least-32-byte-shared-key> npm test -w @arnilo/prism-core -- webhooks-live
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Probes: one signed delivery to your receiver (verify `x-prism-signature: sha256=<hex>` over the raw body) and a retry-after-5xx leg over a local loopback receiver (500 then 200, signature verified, retries recorded). Bounded to 1 real request + ≤ 2 loopback requests. Registered in `scripts/live-matrix.json` as `core/webhooks-live`.
|
|
107
|
+
|
|
97
108
|
## Related APIs
|
|
98
109
|
|
|
99
110
|
- `LeaseStore` / `CheckpointStore` — the durable contracts this runbook relies on.
|
package/docs/performance.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Performance limits
|
|
2
2
|
|
|
3
|
-
Evaluation defaults are finite: 100 trace rows × 20 pages and 4 MiB aggregate trace data; one model-judge attempt with 30-second/16-KiB bounds; 8 comparison candidates, 1-MiB candidate results, 10,000 dataset items, and 4-MiB serialized reports. Hard caps are exported by `@arnilo/prism-evals`; overflow fails rather than truncating grading evidence.
|
|
3
|
+
Evaluation defaults are finite: 100 trace rows × 20 pages and 4 MiB aggregate trace data; one model-judge attempt with 30-second/16-KiB bounds; 8 comparison candidates, 1-MiB candidate results, 10,000 dataset items, and 4-MiB serialized reports. Hard caps are exported by `@arnilo/prism-core/governance/evals`; overflow fails rather than truncating grading evidence.
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
@@ -65,6 +65,26 @@ serialized provider event at the exact response-byte cap succeeds; one byte belo
|
|
|
65
65
|
fails closed, including multibyte Unicode deltas. Context-budget omission order and
|
|
66
66
|
newest-history preservation remain covered by the root context-budget tests.
|
|
67
67
|
|
|
68
|
+
## Tool progressive disclosure (plan 041)
|
|
69
|
+
|
|
70
|
+
`node scripts/benchmark.mjs --scenario tool-search` is network-free (mock assembly, in-memory, no credentials). It builds a 128-tool fixture registry and assembles the provider input once per mode through `assembleProviderInput`: `toolsDisclosure "all"` (default, full tool set) vs `"search"` (top-k 16 plus the generated `search_tools` tool), then asserts provider-request tool-definition bytes shrink ≥ 60% and the index+score pass stays well under a turn. Frozen caps live in `scripts/budgets.json#toolSearch` (reduction floor 0.6, index+score ceiling 50 ms, disclosed-count ceiling 33 — sanity bounds, machine-dependent). Schema/caps/network-free gating in `npm test`: `scripts/benchmark-tool-search.test.mjs`.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
node scripts/benchmark.mjs --scenario tool-search --out /tmp/prism-tool-search.json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Recorded 2026-08-30, Node v24.19.0 / Linux x64: tool bytes 31,923 → 4,329 (**86.4% reduction**, floor 60%), index+score 1.9–2.5 ms across three runs, disclosed 17 tools (top-k 16 + `search_tools`). Tool-accuracy fixtures (mock provider picking by name among 64/128 distractors, scripted scanner reading only the disclosed list) show search mode at full-exposure pick accuracy in both sizes — the conformance floor `search ≥ all` holds (`src/__tests__/tool-search.test.ts`).
|
|
77
|
+
|
|
78
|
+
## Workflow loop refinement (plan 045)
|
|
79
|
+
|
|
80
|
+
`node scripts/benchmark.mjs --scenario workflow-loop` is network-free: five serial `loopNode` iterations each run one refinement through a mock provider and an in-memory checkpoint adapter. The frozen budget in `scripts/budgets.json#workflowLoop` allows 50 ms p95 per node execution, or 250 ms across all five iterations. The scenario also checks five provider calls, five finished iteration records, peak provider concurrency of one, and zero active work after completion.
|
|
81
|
+
|
|
82
|
+
Recorded 2026-08-31 on Node v24.19.0 / Linux x64: 5 warmups + 20 measured runs, p50 **2.356 ms**, p95 **6.443 ms** (**1.289 ms/iteration**), 352.88 runs/s. `maxNodes` remains the declared-node count; `maxIterations` is the independent runtime budget and stays hard-capped at 64. These timings are local evidence, not portable SLOs.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
node scripts/benchmark.mjs --scenario workflow-loop --out /tmp/prism-workflow-loop.json
|
|
86
|
+
```
|
|
87
|
+
|
|
68
88
|
## Current-line root artifact diet
|
|
69
89
|
|
|
70
90
|
`npm pack --dry-run --json` on `@arnilo/prism` is gated by `scripts/budget-gate.test.mjs` against `scripts/budgets.json#root` (±5%). Repository-only history stays out of the tarball: `docs/_evidence/**`, `docs/release-*-evidence.md`, `docs/api-page-template.md`, `dist/__tests__`, and `*.map`. Every page linked from shipped `docs/index.md` must be in the pack. Recorded 2026-08-27: **923,045 packed / 3,149,665 unpacked / 375 files** (226 `dist` js+d.ts, 124 index-linked docs, 25 other). 0.1.0 freeze 713,454 / 293 stays historical.
|
|
@@ -357,15 +377,15 @@ Security automation is isolated from `npm test`: CodeQL/supply-chain jobs have 1
|
|
|
357
377
|
|
|
358
378
|
Web tools default/hard ceilings are query 4/16 KiB, results 10/20, URLs 5/20, request 256 KiB/1 MiB, response/aggregate 2/16 MiB, Markdown 1/8 MiB, extraction 256 KiB/1 MiB, schema 64/256 KiB, concurrency 4/16, retries 2/4, polling 20/100, and wall time 60 seconds/30 minutes. Bounds charge before request, retention, retry, or polling; overflow fails rather than truncating citation/extraction evidence.
|
|
359
379
|
|
|
360
|
-
Docker sandbox defaults/hard caps from `@arnilo/prism-coding-security`: startup 30 s/120 s; wall 20 min/30 min; idle 5 min/15 min; CPUs 2/8; memory 2 GiB/16 GiB (swap equal to memory); PIDs 256/1,024; FDs 1,024/8,192; workspace/tmp/download tmpfs 1 GiB/8 GiB, 256 MiB/2 GiB, 64 MiB/512 MiB; commands 100/256 with concurrent execs 1/8; env 64/256 names and 64 KiB/256 KiB values; export 50,000/250,000 entries and 256 MiB/2 GiB bytes with 16/64 retained artifacts; stop grace 5 s/30 s and cleanup 30 s/120 s. Caps validate before `docker create`/exec/export; overflow aborts and cleans the recorded container. Output still streams into the coding-agent `OutputAccumulator` ceilings (64 MiB/1 GiB).
|
|
380
|
+
Docker sandbox defaults/hard caps from `@arnilo/prism-coding-tools/security`: startup 30 s/120 s; wall 20 min/30 min; idle 5 min/15 min; CPUs 2/8; memory 2 GiB/16 GiB (swap equal to memory); PIDs 256/1,024; FDs 1,024/8,192; workspace/tmp/download tmpfs 1 GiB/8 GiB, 256 MiB/2 GiB, 64 MiB/512 MiB; commands 100/256 with concurrent execs 1/8; env 64/256 names and 64 KiB/256 KiB values; export 50,000/250,000 entries and 256 MiB/2 GiB bytes with 16/64 retained artifacts; stop grace 5 s/30 s and cleanup 30 s/120 s. Caps validate before `docker create`/exec/export; overflow aborts and cleans the recorded container. Output still streams into the coding-agent `OutputAccumulator` ceilings (64 MiB/1 GiB).
|
|
361
381
|
|
|
362
|
-
Repository list/search defaults/hard caps from `@arnilo/prism-coding-agent`: depth 32/128; entries/files 10,000/100,000; page/results 1,000/10,000; search scan 64 MiB/1 GiB aggregate and 8 MiB/64 MiB per file; matches 1,000/10,000; pattern 512 B/4 KiB; line 50 KiB/1 MiB; context 5/20; wall 30 s/300 s; concurrency config 8/32. Walks stream via `opendir`/`lstat`, never follow symlink escapes, and stop immediately on aggregate limits or abort.
|
|
382
|
+
Repository list/search defaults/hard caps from `@arnilo/prism-coding-tools/agent`: depth 32/128; entries/files 10,000/100,000; page/results 1,000/10,000; search scan 64 MiB/1 GiB aggregate and 8 MiB/64 MiB per file; matches 1,000/10,000; pattern 512 B/4 KiB; line 50 KiB/1 MiB; context 5/20; wall 30 s/300 s; concurrency config 8/32. Walks stream via `opendir`/`lstat`, never follow symlink escapes, and stop immediately on aggregate limits or abort.
|
|
363
383
|
|
|
364
384
|
Structured Git/check/handoff defaults/hard caps: paths 1,000/10,000; refs 1 KiB/4 KiB; commit message 64 KiB/256 KiB; inline Git output 4 MiB/64 MiB; diff lines 10,000/100,000; changed files 1,000/10,000; patch input 16 MiB/64 MiB; worktrees 4/16; named checks 8/32 names, concurrency 1/4, timeout 10 min/60 min, diagnostic lines 2,000/100,000, output 4 MiB/64 MiB; PR handoff JSON 256 KiB/1 MiB with 100/1,000 commits. Git tools use typed argument arrays (never shell), disable hooks/credential prompts/external diff by default, and emit host-owned PR handoff data only — no push/network/PR client.
|
|
365
385
|
|
|
366
386
|
Durable coding plan/checkpoint defaults/hard caps: plan Markdown 256 KiB/1 MiB; todos 1,000/10,000 with 512 B/4 KiB text; checkpoint metadata 64 KiB/512 KiB; artifact references 16/64 at 256 MiB/2 GiB each; check summaries 1 KiB/8 KiB. Checkpoints store URI/hash/summaries/fingerprints only; resume revalidates workspace root, base branch, plan hash, and tool/policy/image fingerprints before import.
|
|
367
387
|
|
|
368
|
-
Browser automation defaults/hard caps from
|
|
388
|
+
Browser automation defaults/hard caps from the `browser` subpath: pages 4/16; actions 100/256; queued actions 16/64; snapshot refs 2,000/10,000; depth 30/100; snapshot bytes 256 KiB/2 MiB; navigation 30 s/120 s; action 10 s/60 s; wait 30 s/120 s; run wall 20 min/30 min; popups 4/16; dialogs 16/64; listeners 64/256; action input 64 KiB/256 KiB; close grace 5 s/30 s; network requests 1,000/10,000 with 10/32 redirects per request and 8/32 WebSockets; screenshots 16/64 with 16/64 megapixels and 10 MiB/32 MiB encoded; uploads 8/32 files, 16 MiB/64 MiB each, 64 MiB/256 MiB aggregate; downloads 8/32 files, 32 MiB/256 MiB each, 64 MiB/512 MiB aggregate. Caps charge before context/page/action/queue/snapshot/network/artifact retention. Host supplies Playwright and egress proxy attestation; package import launches nothing.
|
|
369
389
|
|
|
370
390
|
0.0.14 co-work defaults/hard caps (frozen in [Phase 9 evidence](_evidence/review-coverage-2026-07-25-phase-9.md)): conversation thread list pages 50/200, active branches per thread 16/64, replay/export page 100/500 events; artifact revisions per artifact 32/128, artifacts per thread 64/256, metadata record 8/64 KiB, preview 16/64 KiB, citations 32/128 (2/8 KiB each), delivery-link TTL 5 min/24 h, delivery token 4/16 KiB, compare exactly 2 revisions; memory retention batch 500/5000; proactive capability TTL 24 h/31 d, capability token record 16 KiB; browser checkpoint URL 8 KiB/16 KiB, domain-state hash 256 B/1 KiB, host-data ref 2 KiB/8 KiB, 16/64 checkpoints per run; device stream chunk 1 MiB/8 MiB, concurrent device sessions per identity 1/4 (device wall/turns/tool calls consume shared `RunLimits`). All caps charge before persist/emit and fail closed on overflow. Benchmark placeholder: `node scripts/benchmark-0.0.14.mjs` (release Task 12) reports conversation replay, memory injection/consent, artifact revision/delivery, AG-UI co-work mapping, and connector refresh overhead against these budgets.
|
|
371
391
|
|
|
@@ -537,7 +557,7 @@ Scope froze at commit `f5128a816ae204c52f3e2f089de71c99bd5de6d4`. Measurement ho
|
|
|
537
557
|
| Root artifact | `@arnilo/prism@0.0.4` dry-run tarball | 346.0 kB packed; 1.3 MB unpacked; 196 files |
|
|
538
558
|
| Installed workspace | Current root `node_modules` | 72 MiB |
|
|
539
559
|
|
|
540
|
-
Synthetic stream/tool/workflow values are medians of seven measured runs after one warm-up and contain no network, database, or exporter I/O. The temporary benchmark reused public `AgentSession`, `dispatchToolCallsInOrder`, and `@arnilo/prism-workflows` APIs; it was not added to CI because this phase records a baseline rather than creating hardware-sensitive tests.
|
|
560
|
+
Synthetic stream/tool/workflow values are medians of seven measured runs after one warm-up and contain no network, database, or exporter I/O. The temporary benchmark reused public `AgentSession`, `dispatchToolCallsInOrder`, and `@arnilo/prism-core/runtime/workflows` APIs; it was not added to CI because this phase records a baseline rather than creating hardware-sensitive tests.
|
|
541
561
|
|
|
542
562
|
Repository size at the same commit, counted from `src/` and `packages/` while excluding `dist/`:
|
|
543
563
|
|
|
@@ -570,7 +590,7 @@ Usage aggregation performs one constant-size accumulator update per terminal pro
|
|
|
570
590
|
|
|
571
591
|
### 0.0.5 Phase 4 verification (2026-07-15)
|
|
572
592
|
|
|
573
|
-
Optional `@arnilo/prism-evals` adds package-local scoring without changing core run latency. Validation stayed within the frozen release gate:
|
|
593
|
+
Optional `@arnilo/prism-core/governance/evals` adds package-local scoring without changing core run latency. Validation stayed within the frozen release gate:
|
|
574
594
|
|
|
575
595
|
| Surface | Result |
|
|
576
596
|
| --- | --- |
|
|
@@ -595,7 +615,7 @@ Experiment concurrency is capped at 32 workers and defaults to 1. Scorers operat
|
|
|
595
615
|
|
|
596
616
|
### 0.0.5 Phase 6 verification (2026-07-15)
|
|
597
617
|
|
|
598
|
-
Optional `@arnilo/prism-
|
|
618
|
+
Optional `@arnilo/prism-providers/ai-sdk` adapts AI SDK `LanguageModelV4` streams to Prism without adding an AI SDK dependency to core.
|
|
599
619
|
|
|
600
620
|
| Surface | Result |
|
|
601
621
|
| --- | --- |
|
|
@@ -651,7 +671,7 @@ Optional `@arnilo/prism-rag` reuses Phase 7 vector contracts and adds no core pa
|
|
|
651
671
|
|
|
652
672
|
### 0.0.5 Phase 10 verification (2026-07-16)
|
|
653
673
|
|
|
654
|
-
Optional `@arnilo/prism-server` and MCP server-direction APIs compose existing agent/workflow/tool/SDK primitives; no core path, framework/listener, auth provider, database, or profile activation was added.
|
|
674
|
+
Optional `@arnilo/prism-core/runtime/server` and MCP server-direction APIs compose existing agent/workflow/tool/SDK primitives; no core path, framework/listener, auth provider, database, or profile activation was added.
|
|
655
675
|
|
|
656
676
|
| Surface | Result |
|
|
657
677
|
| --- | --- |
|
|
@@ -732,7 +752,7 @@ No performance ceiling was raised. Core grew from Phase 0's 346.0 kB packed base
|
|
|
732
752
|
|
|
733
753
|
### 0.0.13 Phase 8 server deployment seams (2026-07-23)
|
|
734
754
|
|
|
735
|
-
Optional health/drain/rate-limit/replay/deployment-lease helpers on `@arnilo/prism-server`. No listener, queue adapter, or concurrency hard-cap raise.
|
|
755
|
+
Optional health/drain/rate-limit/replay/deployment-lease helpers on `@arnilo/prism-core/runtime/server`. No listener, queue adapter, or concurrency hard-cap raise.
|
|
736
756
|
|
|
737
757
|
| Surface | Result |
|
|
738
758
|
| --- | --- |
|
|
@@ -751,7 +771,7 @@ Offline behavior tests (identity propagation, policy export, router deny paths,
|
|
|
751
771
|
|
|
752
772
|
### 0.3.x Phase 39 Obscura browser-engine envelopes (2026-08-29)
|
|
753
773
|
|
|
754
|
-
|
|
774
|
+
`obscura` binary-backed legs, network-free, driven by a deterministic fake CLI: `node scripts/benchmark-obscura.mjs` (3 runs, medians vs reviewed ceilings; artifact `scripts/benchmark-obscura.json`). Startup leg probes SIG-0 liveness after spawn — a real host waits on its readiness endpoint inside the same bound.
|
|
755
775
|
|
|
756
776
|
| Leg | Median (3 runs) | Ceiling | Notes |
|
|
757
777
|
| --- | --- | --- | --- |
|
|
@@ -9,7 +9,7 @@ Implementation is **shipped and phase-verified** (Tasks 0–7). Optional package
|
|
|
9
9
|
## When to use it
|
|
10
10
|
|
|
11
11
|
- **Adapter authors** implementing SQLite/PostgreSQL `SessionStore` + `RunLedger` should start here, then follow [Database persistence](database-persistence.md) and [Session store conformance](session-store-conformance.md).
|
|
12
|
-
- **Host apps** wiring CLI/desktop credential persistence should use the credential seams and package matrix here before choosing `@arnilo/prism-credentials
|
|
12
|
+
- **Host apps** wiring CLI/desktop credential persistence should use the credential seams and package matrix here before choosing `@arnilo/prism-core/credentials/node` backends.
|
|
13
13
|
- **Provider and core authors** extending multimodal input should use the content/resource/capability designs here instead of embedding provider upload IDs in core contracts.
|
|
14
14
|
- **Security reviewers** use the threat model and conformance matrix on this page as the acceptance baseline for Plan 056 Tasks 1–7.
|
|
15
15
|
|
|
@@ -70,7 +70,7 @@ Static review of `src/contracts.ts`, `src/session-stores.ts`, `src/credentials.t
|
|
|
70
70
|
| `refreshOAuthCredential` | `src/credentials.ts` | Calls `OAuthProvider.refresh`; optional `OAuthCredentialStore.set` |
|
|
71
71
|
| `OAuthCredentialStore` | `src/contracts.ts` | `set(provider, credentials)` only — no `get`/`delete` in core contract |
|
|
72
72
|
| `OAuthProvider` / `OAuthCredentials` | `src/contracts.ts` | `login`, optional `refresh`, optional `getCredential` |
|
|
73
|
-
| Device-code OAuth | `packages/
|
|
73
|
+
| Device-code OAuth | `packages/prism-providers/src/openai` | Bounded polling; abort via `OAuthLoginCallbacks.signal` |
|
|
74
74
|
| Redaction | `src/redaction.ts` | Exact known-secret replacement; not secret detection |
|
|
75
75
|
|
|
76
76
|
**Gaps (C-011):** No encrypted file store, no system keychain adapter, no versioned credential envelope, no `OAuthCredentialStore` `get`/`delete`/`list` in core (Task 4 package may extend store interface locally while integrating `refreshOAuthCredential`).
|
|
@@ -108,10 +108,10 @@ Prism `engines.node` is `>=20`. Optional packages stay package-local; core adds
|
|
|
108
108
|
|
|
109
109
|
| Package (planned) | Driver / backend | Pinned version | Node support | Rationale |
|
|
110
110
|
| --- | --- | --- | --- | --- |
|
|
111
|
-
| `@arnilo/prism-
|
|
112
|
-
| `@arnilo/prism-
|
|
113
|
-
| `@arnilo/prism-credentials
|
|
114
|
-
| `@arnilo/prism-credentials
|
|
111
|
+
| `@arnilo/prism-core/sessions/sqlite` | `better-sqlite3` | `^12.11.1` | 20.x–26.x per upstream engines | Synchronous API, WAL/busy_timeout, mature Node 20 baseline; `node:sqlite` rejected — requires Node ≥22.5 and is still experimental vs declared `>=20` baseline |
|
|
112
|
+
| `@arnilo/prism-core/sessions/postgres` | `pg` | `^8.22.0` | ≥16 (satisfies 20+) | Standard pool + parameterized queries; TLS/credentials host-owned |
|
|
113
|
+
| `@arnilo/prism-core/credentials/node` encrypted file | Node `crypto` (AES-256-GCM + scrypt) | built-in | 20+ | No extra deps for AEAD/KDF; atomic rename writes |
|
|
114
|
+
| `@arnilo/prism-core/credentials/node` keychain | `@napi-rs/keyring` | `^1.3.0` | ≥10 (satisfies 20+) | Cross-platform, actively maintained (2026); `keytar@7.9.0` rejected — last release 2022, heavier native rebuild friction |
|
|
115
115
|
|
|
116
116
|
**Rejected options:**
|
|
117
117
|
|
|
@@ -131,7 +131,7 @@ Prism `engines.node` is `>=20`. Optional packages stay package-local; core adds
|
|
|
131
131
|
| Persistence location | Core built-in DB | Optional packages over contracts | **Optional packages** | Matches Plan 053 JSONL boundary and `ProductionPersistenceStore` extension point |
|
|
132
132
|
| Schema/migrations | Core DDL generator | Shared fixture model + dialect-local SQL | **Shared fixtures + local SQL** | Two adapters without ORM |
|
|
133
133
|
| Run ledger conformance | Per-package tests only | Shared conformance module (Task 1) | **Shared module** | Parity with session-store conformance |
|
|
134
|
-
| Credential persistence | Core global store | `@arnilo/prism-credentials
|
|
134
|
+
| Credential persistence | Core global store | `@arnilo/prism-core/credentials/node` | **Optional package** | Host selects file vs keychain |
|
|
135
135
|
| KDF | PBKDF2 default | scrypt with documented minimums | **scrypt** (configurable N/r/p) | Node built-in; calibrate in Task 4 tests |
|
|
136
136
|
| Multimodal content | Provider-specific options only | Generic `ContentBlock` + capability tags | **Generic blocks + capabilities** | Portable input; provider maps/uploads locally |
|
|
137
137
|
| URL/file sources | Loader reads anything | Bounded loader policy + trust integration | **Bounded + trust** | Reuse `createPathTrustPolicy` patterns; SSRF deny-by-default for URLs |
|
package/docs/policy-and-audit.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
4
|
|
|
5
|
-
`@arnilo/prism-policy` records redacted allow/deny/modify/approval decisions with policy version, actor refs from verified `AgentIdentity`, target, reason, expiry, and evidence references. Hosts export cursor-paginated pages to append-only/WORM sinks. The package does not embed a mandatory global policy engine, KMS, or cloud WORM SDK.
|
|
5
|
+
`@arnilo/prism-core/governance/policy` records redacted allow/deny/modify/approval decisions with policy version, actor refs from verified `AgentIdentity`, target, reason, expiry, and evidence references. Hosts export cursor-paginated pages to append-only/WORM sinks. The package does not embed a mandatory global policy engine, KMS, or cloud WORM SDK.
|
|
6
6
|
|
|
7
7
|
## When to use it
|
|
8
8
|
|
|
@@ -64,7 +64,7 @@ import {
|
|
|
64
64
|
evaluateAndAppend,
|
|
65
65
|
exportPolicyDecisions,
|
|
66
66
|
recordToolApprovalDecision,
|
|
67
|
-
} from "@arnilo/prism-policy";
|
|
67
|
+
} from "@arnilo/prism-core/governance/policy";
|
|
68
68
|
|
|
69
69
|
const evaluator = createPolicyEvaluator({
|
|
70
70
|
policyId: "mail",
|
|
@@ -129,7 +129,7 @@ Immutable approval requests carry an action digest, requester, and required role
|
|
|
129
129
|
| `ApprovalRecord` | Request + `status` (`pending/approved/rejected/revoked/consumed`) + `revision` + immutable `decisions` + `policyRevision` |
|
|
130
130
|
| `ApprovalStore.create/decide/revoke/consume/get/query` | Durable transitions; every transition records an `auditRef` |
|
|
131
131
|
| `createMemoryApprovalStore({ authority })` | Single-process reference adapter sharing the pure transition logic |
|
|
132
|
-
| `createPostgresApprovalStore({ pool, schema, authority })` | Cross-replica storage (migration `005_erp_approvals`, `@arnilo/prism-enterprise
|
|
132
|
+
| `createPostgresApprovalStore({ pool, schema, authority })` | Cross-replica storage (migration `005_erp_approvals`, `@arnilo/prism-core/enterprise/postgres`) |
|
|
133
133
|
|
|
134
134
|
Quorum rules:
|
|
135
135
|
|
|
@@ -152,7 +152,7 @@ WHERE status = 'pending' AND expires_at < now();
|
|
|
152
152
|
|
|
153
153
|
Hosts own identity verification and the role source; Prism does not certify NIST compliance. NIST SP 800-53 AC-5 (separation of duties) and AC-6 (least privilege) are control guidance only, not certification claims.
|
|
154
154
|
|
|
155
|
-
## OPA external policy adapter (`@arnilo/prism-policy/opa`, 0.0.28)
|
|
155
|
+
## OPA external policy adapter (`@arnilo/prism-core/governance/policy/opa`, 0.0.28)
|
|
156
156
|
|
|
157
157
|
Optional `createOpaPolicyEvaluator` evaluates `PolicyEvaluateRequest`s against a host-pinned OPA REST endpoint (`POST /v1/data/<path>` with `{"input": <document>}`) and returns a core `PolicyEvaluator` for `evaluateAndAppend`. Native `fetch` only; no OPA SDK dependency.
|
|
158
158
|
|
|
@@ -168,9 +168,9 @@ Optional `createOpaPolicyEvaluator` evaluates `PolicyEvaluateRequest`s against a
|
|
|
168
168
|
| `ssrf` | `SsrfPolicy` for the endpoint; denials surface `MediaContentError` (`ssrf_denied`) |
|
|
169
169
|
|
|
170
170
|
```ts
|
|
171
|
-
import { createOpaPolicyEvaluator } from "@arnilo/prism-policy/opa";
|
|
172
|
-
import { createPostgresEnterpriseState } from "@arnilo/prism-enterprise
|
|
173
|
-
import { evaluateAndAppend } from "@arnilo/prism-policy";
|
|
171
|
+
import { createOpaPolicyEvaluator } from "@arnilo/prism-core/governance/policy/opa";
|
|
172
|
+
import { createPostgresEnterpriseState } from "@arnilo/prism-core/enterprise/postgres";
|
|
173
|
+
import { evaluateAndAppend } from "@arnilo/prism-core/governance/policy";
|
|
174
174
|
|
|
175
175
|
const evaluator = createOpaPolicyEvaluator({
|
|
176
176
|
url: "https://opa.internal:8181/v1/data/prism/allow",
|
|
@@ -194,6 +194,16 @@ await evaluateAndAppend(request, { store: state.policy, evaluator, id: crypto.ra
|
|
|
194
194
|
|
|
195
195
|
`state.close()` leaves a caller-owned pool open. Run `state.cleanup(...)` from an authorized host schedule only when expiration cleanup is needed; it does not run in the background.
|
|
196
196
|
|
|
197
|
+
## Live probe (plans/064 Task 9)
|
|
198
|
+
|
|
199
|
+
The OPA policy evaluator has an operator-gated live probe against a real decision endpoint:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
PRISM_TEST_OPA_URL=https://opa:8181/v1/data/prism/allow npm test -w @arnilo/prism-core -- opa-live
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Probes: two real decision evaluations (any valid outcome) and one fail-closed probe (unroutable endpoint ⇒ `deny` with `OPA endpoint unavailable`). Bounded to ≤ 2 real requests. Registered in `scripts/live-matrix.json` as `core/opa-live`.
|
|
206
|
+
|
|
197
207
|
## Related APIs
|
|
198
208
|
|
|
199
209
|
- [Model routing](model-routing.md)
|
|
@@ -203,4 +213,4 @@ await evaluateAndAppend(request, { store: state.policy, evaluator, id: crypto.ra
|
|
|
203
213
|
- [Workflows](workflows.md): proactive schedule capability enable/revoke events bridge here via `onCapability`.
|
|
204
214
|
- [Host security](host-security.md)
|
|
205
215
|
- [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable policy/evaluation/work/router composition.
|
|
206
|
-
- Package README: [`@arnilo/prism-
|
|
216
|
+
- Package README: [`@arnilo/prism-core`](../packages/prism-core/README.md)
|
package/docs/ponytail.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
4
|
|
|
5
|
-
`@arnilo/prism-ponytail` is an optional package that wires [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail) into Prism contribution contracts.
|
|
5
|
+
`@arnilo/prism-coding-tools/ponytail` is an optional package that wires [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail) into Prism contribution contracts.
|
|
6
6
|
|
|
7
7
|
It registers upstream skills and commands, injects active mode instructions via upstream `getPonytailInstructions` / `filterSkillBodyForMode`, and persists mode as session custom `ponytail-mode` entries. Import is inert; missing upstream fails closed at `setup` with a bounded redacted error.
|
|
8
8
|
|
|
@@ -66,7 +66,7 @@ Deactivation: exact phrases `stop ponytail` and `normal mode`.
|
|
|
66
66
|
## Implementation example
|
|
67
67
|
|
|
68
68
|
```ts
|
|
69
|
-
import { createPonytailExtension } from "@arnilo/prism-ponytail";
|
|
69
|
+
import { createPonytailExtension } from "@arnilo/prism-coding-tools/ponytail";
|
|
70
70
|
import {
|
|
71
71
|
createExtensionKernel,
|
|
72
72
|
createLoadSkillTool,
|
|
@@ -108,7 +108,7 @@ See `examples/caveman-ponytail.ts` for combined Caveman + Ponytail progressive d
|
|
|
108
108
|
- Mode restore scans `getEntries()` for latest `data.type === "ponytail-mode"` (OM attach pattern).
|
|
109
109
|
- `ponytail-subagent` hook is not wired; nested-agent behavior is host responsibility. When hosts wire the upstream hook, `PONYTAIL_SUBAGENT_MATCHER` accepts only the documented safe subset — `"explore|general"` (any literal substring) or `"^general$"` (exact), case-insensitive, max 256 chars. No `RegExp` is compiled from the environment, so arbitrary regex (including catastrophic nested quantifiers) is never evaluated; unset/invalid patterns inject into every subagent.
|
|
110
110
|
- No TUI statusline scripts; use `ponytail status` command or extension events.
|
|
111
|
-
- Not included in `@arnilo/prism-
|
|
111
|
+
- Not included in `@arnilo/prism-coding-tools` or `@arnilo/prism-core` profiles — opt-in install only.
|
|
112
112
|
|
|
113
113
|
## Security and performance notes
|
|
114
114
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
4
|
|
|
5
|
-
The optional `@arnilo/prism-
|
|
5
|
+
The optional `@arnilo/prism-core/sessions/postgres` package ships a production-oriented PostgreSQL adapter that implements:
|
|
6
6
|
|
|
7
7
|
- `SessionStore` — atomic `append` / `list` / `get` / `readBranchPath` / bounded `searchSessions` / bounded `searchSessions`
|
|
8
8
|
- `RunLedger` — durable run, event, tool-call, and usage rows
|
|
@@ -24,13 +24,13 @@ Use this package when you need server-backed persistence with pooled connections
|
|
|
24
24
|
- managed cloud databases (RDS, Cloud SQL, Neon, Supabase, etc.)
|
|
25
25
|
- CI integration tests against a real PostgreSQL service
|
|
26
26
|
|
|
27
|
-
Prefer [`@arnilo/prism-
|
|
27
|
+
Prefer [`@arnilo/prism-core/sessions/sqlite`](sqlite-persistence.md) for local CLI tools, single-writer workloads, and network-free default tests. This adapter stores sessions/runs, not semantic vectors; use the separate [`@arnilo/prism-memory` pgvector path](working-and-semantic-memory.md), which rejects non-finite vectors before SQL, when vector recall is needed. For durable policy decisions, evaluations, work mutation idempotency, and model-router state, use the separate [`@arnilo/prism-core/enterprise/postgres`](enterprise-postgres-state.md) composition; it has its own migration history and does not replace session/run persistence.
|
|
28
28
|
|
|
29
29
|
## Inputs / request
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
32
|
import { Pool } from "pg";
|
|
33
|
-
import { createPostgresPersistence } from "@arnilo/prism-
|
|
33
|
+
import { createPostgresPersistence } from "@arnilo/prism-core/sessions/postgres";
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
| Field | Type | Purpose |
|
|
@@ -82,7 +82,7 @@ Migrations run automatically on open and are idempotent across reopen. Concurren
|
|
|
82
82
|
```ts
|
|
83
83
|
import { Pool } from "pg";
|
|
84
84
|
import { createAgentSession } from "@arnilo/prism";
|
|
85
|
-
import { createPostgresPersistence } from "@arnilo/prism-
|
|
85
|
+
import { createPostgresPersistence } from "@arnilo/prism-core/sessions/postgres";
|
|
86
86
|
import { runSessionStoreConformance } from "@arnilo/prism/testing/session-store-conformance";
|
|
87
87
|
|
|
88
88
|
const pool = new Pool({
|
|
@@ -111,7 +111,7 @@ await pool.end();
|
|
|
111
111
|
Live conformance and integration tests:
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
|
-
PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres --workspace @arnilo/prism-
|
|
114
|
+
PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres --workspace @arnilo/prism-core/sessions/postgres
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
## Extension and configuration notes
|