@arnilo/prism 0.2.7 → 0.2.9
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 +11 -1
- package/README.md +6 -3
- package/dist/agent-loops.js +4 -0
- package/dist/agent-run-lifecycle.js +2 -2
- package/dist/agent-session/helpers.d.ts +1 -1
- package/dist/agent-session/helpers.js +2 -1
- package/dist/agent-session/session.d.ts +1 -1
- package/dist/agent-session/session.js +28 -20
- package/dist/agent-session.d.ts +1 -1
- package/dist/agent-session.js +1 -1
- package/dist/agents.d.ts +1 -1
- package/dist/agents.js +1 -1
- package/dist/contracts-core/agent.d.ts +5 -5
- package/dist/contracts-core/agent.js +2 -0
- package/dist/contracts-core/extensions.d.ts +3 -3
- package/dist/contracts-core/extensions.js +2 -0
- package/dist/contracts-core/loop.d.ts +6 -1
- package/dist/contracts-core/session.d.ts +1 -1
- package/dist/contracts-core.d.ts +6 -6
- package/dist/contracts-core.js +6 -6
- package/dist/contracts-protocol.d.ts +8 -2
- package/dist/contracts.d.ts +1 -1
- package/dist/contracts.js +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +5 -5
- package/dist/input.js +1 -1
- package/dist/oauth-device-code.d.ts +5 -0
- package/dist/oauth-device-code.js +38 -14
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -1
- package/docs/0.1.0-readiness.md +7 -7
- package/docs/acp-agent.md +78 -0
- package/docs/acp.md +21 -10
- package/docs/ag-ui.md +1 -1
- package/docs/agent-definitions.md +1 -1
- package/docs/agent-events.md +2 -2
- package/docs/agent-loops.md +2 -2
- package/docs/caveman.md +3 -2
- package/docs/coding-agent-tools.md +3 -1
- package/docs/coding-security.md +6 -0
- package/docs/context-and-skills.md +2 -2
- package/docs/credential-storage.md +1 -1
- package/docs/credentials-and-redaction.md +2 -2
- package/docs/extensions.md +1 -0
- package/docs/impeccable.md +102 -0
- package/docs/index.md +6 -4
- package/docs/migration.md +25 -0
- package/docs/ponytail.md +2 -2
- package/docs/provider-caching.md +6 -0
- package/docs/provider-packages.md +19 -6
- package/docs/providers/clinepass.md +120 -0
- package/docs/providers/deepseek.md +147 -0
- package/docs/providers/openai.md +1 -1
- package/docs/providers/xai.md +138 -0
- package/docs/release-and-install.md +54 -12
- package/docs/structured-output.md +10 -10
- package/docs/thinking-and-reasoning.md +6 -3
- package/package.json +4 -3
|
@@ -45,6 +45,22 @@ const isTokenSuccessPayload = (value) => {
|
|
|
45
45
|
return false;
|
|
46
46
|
return typeof value.access_token === "string";
|
|
47
47
|
};
|
|
48
|
+
const encodeOAuthBody = (encoding, params) => encoding === "form"
|
|
49
|
+
? { contentType: "application/x-www-form-urlencoded", body: new URLSearchParams(params).toString() }
|
|
50
|
+
: { contentType: "application/json", body: JSON.stringify(params) };
|
|
51
|
+
const requireHttpsVerificationUri = (value, label, errorPrefix, secrets) => {
|
|
52
|
+
let parsed;
|
|
53
|
+
try {
|
|
54
|
+
parsed = new URL(value);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw redactOAuthError(new Error(`${errorPrefix} ${label} must be https`), secrets);
|
|
58
|
+
}
|
|
59
|
+
if (parsed.protocol !== "https:") {
|
|
60
|
+
throw redactOAuthError(new Error(`${errorPrefix} ${label} must be https`), secrets);
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
};
|
|
48
64
|
/**
|
|
49
65
|
* Request a device code, surface it through `onDeviceCode`, then poll the token
|
|
50
66
|
* endpoint until success, expiry, a terminal OAuth error, or abort. All response
|
|
@@ -53,16 +69,19 @@ const isTokenSuccessPayload = (value) => {
|
|
|
53
69
|
* bounded text parsed as JSON with a redacted-text fallback.
|
|
54
70
|
*/
|
|
55
71
|
export async function pollDeviceCodeToken(options) {
|
|
56
|
-
const { fetchImpl, deviceCodeUrl, tokenUrl, clientId, scope, extraTokenParams, callbacks, errorPrefix, parseTokenCredentials } = options;
|
|
72
|
+
const { fetchImpl, deviceCodeUrl, tokenUrl, clientId, scope, extraTokenParams, extraDeviceParams, callbacks, errorPrefix, parseTokenCredentials, } = options;
|
|
73
|
+
const bodyEncoding = options.bodyEncoding === "form" ? "form" : "json";
|
|
57
74
|
const now = options.now ?? Date.now;
|
|
58
75
|
const sleep = options.sleep ?? abortableSleep;
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
const deviceBody = encodeOAuthBody(bodyEncoding, {
|
|
77
|
+
client_id: clientId,
|
|
78
|
+
...(scope ? { scope } : {}),
|
|
79
|
+
...extraDeviceParams,
|
|
80
|
+
});
|
|
62
81
|
const response = await fetchImpl(deviceCodeUrl, {
|
|
63
82
|
method: "POST",
|
|
64
|
-
headers: { "content-type":
|
|
65
|
-
body:
|
|
83
|
+
headers: { "content-type": deviceBody.contentType },
|
|
84
|
+
body: deviceBody.body,
|
|
66
85
|
signal: callbacks?.signal,
|
|
67
86
|
});
|
|
68
87
|
if (!response.ok) {
|
|
@@ -71,10 +90,14 @@ export async function pollDeviceCodeToken(options) {
|
|
|
71
90
|
}
|
|
72
91
|
const json = await readBoundedResponseJson(response, { shape: isDeviceCodePayload });
|
|
73
92
|
const secrets = [json.device_code, json.user_code];
|
|
93
|
+
requireHttpsVerificationUri(json.verification_uri, "verification_uri", errorPrefix, secrets);
|
|
94
|
+
const verificationUri = typeof json.verification_uri_complete === "string" && json.verification_uri_complete.length > 0
|
|
95
|
+
? requireHttpsVerificationUri(json.verification_uri_complete, "verification_uri_complete", errorPrefix, secrets)
|
|
96
|
+
: json.verification_uri;
|
|
74
97
|
const expiresAtMs = now() + (json.expires_in ?? 0) * 1_000;
|
|
75
98
|
await callbacks?.onDeviceCode?.({
|
|
76
99
|
userCode: json.user_code,
|
|
77
|
-
verificationUri
|
|
100
|
+
verificationUri,
|
|
78
101
|
expiresAt: json.expires_in ? new Date(expiresAtMs).toISOString() : undefined,
|
|
79
102
|
});
|
|
80
103
|
let intervalMs = Math.max(1, (json.interval ?? DEFAULT_DEVICE_POLL_INTERVAL_MS / 1_000) * 1_000);
|
|
@@ -82,15 +105,16 @@ export async function pollDeviceCodeToken(options) {
|
|
|
82
105
|
throwIfAborted(callbacks?.signal);
|
|
83
106
|
await sleep(intervalMs, callbacks?.signal);
|
|
84
107
|
throwIfAborted(callbacks?.signal);
|
|
108
|
+
const tokenBody = encodeOAuthBody(bodyEncoding, {
|
|
109
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
110
|
+
client_id: clientId,
|
|
111
|
+
device_code: json.device_code,
|
|
112
|
+
...extraTokenParams,
|
|
113
|
+
});
|
|
85
114
|
const tokenResponse = await fetchImpl(tokenUrl, {
|
|
86
115
|
method: "POST",
|
|
87
|
-
headers: { "content-type":
|
|
88
|
-
body:
|
|
89
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
90
|
-
client_id: clientId,
|
|
91
|
-
device_code: json.device_code,
|
|
92
|
-
...extraTokenParams,
|
|
93
|
-
}),
|
|
116
|
+
headers: { "content-type": tokenBody.contentType },
|
|
117
|
+
body: tokenBody.body,
|
|
94
118
|
signal: callbacks?.signal,
|
|
95
119
|
});
|
|
96
120
|
if (tokenResponse.ok) {
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentEvent, ErrorInfo, Guardrails, JsonObject, OwnershipScope, RunLedger, ToolCallContent, ToolDefinition,
|
|
1
|
+
import type { AgentEvent, ErrorInfo, Guardrails, JsonObject, OwnershipScope, RunLedger, ToolCallContent, ToolDefinition, ToolEffectDeclaration, ToolEffectStore, ToolExecutionContext, ToolRegistry, ToolResult } from "./contracts.js";
|
|
2
2
|
import type { MiddlewareRegistry } from "./middleware.js";
|
|
3
3
|
import { type SecretRedactor } from "./redaction.js";
|
|
4
4
|
import { type DuplicateRegistrationOptions } from "./registry-options.js";
|
package/dist/tools.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createId } from "./ids.js";
|
|
|
5
5
|
import { errorToErrorInfo, redactRunLedgerRecord, redactSecrets } from "./redaction.js";
|
|
6
6
|
import { assertCanRegister } from "./registry-options.js";
|
|
7
7
|
import { assertPermission, assertTrusted } from "./security.js";
|
|
8
|
-
import { deriveToolEffectKey,
|
|
8
|
+
import { deriveToolEffectKey, ToolEffectError, toolEffectArgumentsHash } from "./tool-effects.js";
|
|
9
9
|
/** Wrap a schema adapter as the existing `ToolValidator` seam used by dispatch and the agent runtime. */
|
|
10
10
|
export function createToolParameterValidator(validator, options = {}) {
|
|
11
11
|
const missingSchema = options.missingSchema ?? "allow";
|
package/docs/0.1.0-readiness.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 0.1.0 / 1.0 Readiness Gates
|
|
2
2
|
|
|
3
|
-
Status: **0.2.
|
|
3
|
+
Status: **0.2.8** is the current release line (the 0.2.x review-remediation line: fail-closed runtime/sandbox security, provider completion and outbound trust boundaries, concurrent-state/durability integrity, build/coverage/release-evidence integrity, package/documentation/compatibility truth, maintainability and bounded performance, fully featured coding-agent readiness, enterprise ERP production readiness, ACP adoption fixes); **0.1.7** was the terminal 0.1.x baseline; **1.0** readiness remains operator-gated, not automatic.
|
|
4
4
|
|
|
5
5
|
This page distills runnable readiness gates into one command-per-gate table.
|
|
6
6
|
The **Last evidence** column records the 0.1.0-tree snapshot (plan 012 Tasks
|
|
@@ -20,15 +20,15 @@ Historical release lines (0.0.16 floor → 0.0.27 Phase 10 ACP interop → 0.1.0
|
|
|
20
20
|
keep their per-phase evidence in the pages above; this page records the 0.2.6
|
|
21
21
|
snapshot (plan 026) with the 0.1.x tables below as the historical record.
|
|
22
22
|
|
|
23
|
-
## Current line (0.2.
|
|
23
|
+
## Current line (0.2.9)
|
|
24
24
|
|
|
25
25
|
| Item | Status |
|
|
26
26
|
|---|---|
|
|
27
|
-
| Published graph | **
|
|
28
|
-
| Current-line cut | The 0.2.x review-remediation line, additive-only vs the frozen 0.1.x contract: 0.2.0 fail-closed runtime/sandbox
|
|
29
|
-
| Upgrade path | `docs/migration.md` `0.2.
|
|
30
|
-
| Compat promise | Additive-only vs the frozen 0.1.x contract; `scripts/compat-baseline` regenerated at 0.2.
|
|
31
|
-
| Security policy | `npm audit --audit-level=moderate` 0 at 0.2.
|
|
27
|
+
| Published graph | **55** publishable manifests at exact **0.2.9** (root + 54 workspace packages: 17 provider adapters + 10 `prism-*` family/profile + 27 capability; generated by `node scripts/package-truth.mjs` → `scripts/package-truth.json`) |
|
|
28
|
+
| Current-line cut | The 0.2.x review-remediation line, additive-only vs the frozen 0.1.x contract: 0.2.0 fail-closed runtime/sandbox, 0.2.1 trust boundaries, 0.2.2 concurrent-state, 0.2.3 evidence integrity, 0.2.4 package truth, 0.2.5 maintainability, 0.2.6 coding-agent readiness, 0.2.7 ERP, 0.2.8 ACP adoption fixes, plus 0.2.9 provider adoption (DeepSeek / xAI SuperGrok OAuth / ClinePass), `@arnilo/prism-impeccable`, Ponytail 4.9.0, Caveman v2.1 extras |
|
|
29
|
+
| Upgrade path | `docs/migration.md` `0.2.8 → 0.2.9` (additive; no store migration; rollback = restore 0.2.8 manifests/tag); store-compatible throughout 0.2.x |
|
|
30
|
+
| Compat promise | Additive-only vs the frozen 0.1.x contract; `scripts/compat-baseline` regenerated at 0.2.9 (version literal + additive provider/OAuth/impeccable exports), zero breaking deltas |
|
|
31
|
+
| Security policy | `npm audit --audit-level=moderate` 0 at 0.2.9; threat-suites legs (phase8–11 + phase20–26) green; SuperGrok live login `PRISM_LIVE_XAI_OAUTH` is protected, never a silent pass |
|
|
32
32
|
| Docs freeze | tripwires green including the canonical manifest-count tripwire (50/49/14/9/26), the plan 024 package-truth tests (generator reproducibility + artifact equality + closure asserts + derived docs truth), the plan 025 bounded-accumulation near-limit probe, and the plan 026 freeze tripwires (per-task markers, threat T1–T8 test mapping, exit gate green) |
|
|
33
33
|
| 0.1.x line | **0.1.7** (plan 019) is the terminal 0.1.x baseline; the 0.1.1 table below keeps the plan 013 snapshot; the 0.1.0 table keeps the plan 012 snapshot; the **0.0.16** values remain the historical network-free floor |
|
|
34
34
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Spawnable ACP agent (`@arnilo/prism-acp-agent`)
|
|
2
|
+
|
|
3
|
+
New in 0.2.8 (plan 028 Task 10 / adoption F3). A thin binary that serves [`createPrismAcpAgent`](acp.md) over stdio from a config file — the wiring you would otherwise copy out of [`examples/acp-coding-host.ts`](../examples/acp-coding-host.ts) into every host.
|
|
4
|
+
|
|
5
|
+
## Running
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx prism-acp-agent [--config prism-acp-agent.json]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The agent speaks ACP v1 as newline-delimited JSON on `stdin`/`stdout` (SDK `ndJsonStream` adapter over `Readable.toWeb(process.stdin)` / `Writable.toWeb(process.stdout)`). It serves until the client closes stdin; an `EPIPE` on stdout (client disconnected) is a normal shutdown.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
# a config file must exist; missing/invalid config fails closed with a clear error and exit 1
|
|
15
|
+
printf '%s\n' '{"userId":"local","cwd":"/workspace"}' > prism-acp-agent.json
|
|
16
|
+
npx prism-acp-agent
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Config reference
|
|
20
|
+
|
|
21
|
+
The config file is the trust boundary: unknown keys are rejected (a typo cannot silently disable a security-relevant option), every value is shape-validated, and relative paths resolve against the config file's directory.
|
|
22
|
+
|
|
23
|
+
| Key | Required | Description |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `userId` | yes | Ownership user id for every session (single-local-user `authorize`). |
|
|
26
|
+
| `cwd` | yes | Workspace root the coding tools are bound to (must be an existing directory). Sessions always operate on this root — a client-supplied `cwd` never moves the tools. |
|
|
27
|
+
| `sessionStore` | no | `{ "type": "sqlite", "path": ".prism/sessions.db" }` or `{ "type": "memory" }` (default). SQLite persists sessions, runs, checkpoints, and leases (`createSqlitePersistence`). |
|
|
28
|
+
| `mcp.allow` | no | MCP allow-list. http/sse servers must have a `url` starting with an allow entry; stdio servers require the marker `"stdio"`. The UNSTABLE `acp` transport is never approved. |
|
|
29
|
+
| `modes` | no | Mode table `{ "modes": [{ "id", "name", "description?" }], "defaultModeId"? }`; ids unique, `defaultModeId` must name a mode. |
|
|
30
|
+
| `configOptions` | no | `{ "options": [{ "type": "boolean" \| "select", "id", "name", "defaultValue", ... }] }`; ids unique. Select options are advertised/settable per the B3 gate (see [acp.md](acp.md)). |
|
|
31
|
+
| `limits` | no | AG-UI/ACP caps passthrough (`AgUiLimitOptions`). |
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"userId": "local",
|
|
38
|
+
"cwd": ".",
|
|
39
|
+
"sessionStore": { "type": "sqlite", "path": ".prism/sessions.db" },
|
|
40
|
+
"mcp": { "allow": ["https://mcp.example.com"] },
|
|
41
|
+
"modes": { "modes": [{ "id": "edit", "name": "Edit" }], "defaultModeId": "edit" },
|
|
42
|
+
"configOptions": [{ "type": "boolean", "id": "verbose", "name": "Verbose", "defaultValue": false }]
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## What it wires
|
|
47
|
+
|
|
48
|
+
The binary is pure wiring (~200 lines) — no protocol code lives here. It builds:
|
|
49
|
+
|
|
50
|
+
- `authorize` — single local user; every inbound call is scoped by session id.
|
|
51
|
+
- `sessionFactory` — real Prism sessions over `createAgent` with the nine coding tools (`createCodingTools(config.cwd)`), durable `runState` (`interruptBeforeTool`, checkpoints), ownership-scoped to `userId`.
|
|
52
|
+
- `lifecycle` — `createAgentRunLifecycle` over the same checkpoint store, so approvals suspend/resume durably.
|
|
53
|
+
- `mcp` — allow-list `select` gate with http/sse transports.
|
|
54
|
+
- `modes` / `configOptions` — from config.
|
|
55
|
+
- Provider — **mock by default** (full lifecycle, no tokens). Wire a real provider programmatically:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { createSpawnableAgent, loadConfig } from "@arnilo/prism-acp-agent";
|
|
59
|
+
import { createOpenAIResponsesProvider } from "@arnilo/prism-provider-openai";
|
|
60
|
+
|
|
61
|
+
const agent = createSpawnableAgent({
|
|
62
|
+
config: loadConfig("prism-acp-agent.json"),
|
|
63
|
+
provider: createOpenAIResponsesProvider({ apiKey: process.env.OPENAI_API_KEY }),
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Library surface
|
|
68
|
+
|
|
69
|
+
- `loadConfig(path)` / `parseConfig(text, baseDir)` — read + validate; throw `ConfigError` (code `PRISM_ACP_AGENT_CONFIG`) with a clear message.
|
|
70
|
+
- `createSpawnableAgent({ config, provider? })` — build the ACP `AgentApp`.
|
|
71
|
+
- `selectMcpServers(allow, servers)` — the allow-list gate, exported for reuse in custom hosts.
|
|
72
|
+
|
|
73
|
+
## Security posture
|
|
74
|
+
|
|
75
|
+
- Config file = trust boundary: validated shape, no arbitrary code execution.
|
|
76
|
+
- MCP servers only from the allow-list; the UNSTABLE `acp` transport is never bridged.
|
|
77
|
+
- Coding tools are bound to `config.cwd` only; session ownership is fixed to `userId`.
|
|
78
|
+
- Session store paths are resolved against the config directory and fail closed on invalid config.
|
package/docs/acp.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- `createPrismAcpAgent(options)` — serves ACP as an **agent**: an editor/AI client connects through the SDK transport and drives host-owned Prism sessions with `session/new`, `session/load`, `session/resume`, `session/prompt`, `session/set_mode`, `session/set_config_option`, `session/list`, `session/delete`, `session/close`, and `session/cancel`. The agent is a thin protocol adapter: every capability, decision, and byte cap is wired from host seams, and there is **no second policy engine** on the agent side.
|
|
8
8
|
- `createAcpEventMapper(options)` — maps a Prism `AgentEvent` stream (or `CoWorkEvent`) to ACP `SessionUpdate`s for hosts that stream through their own transport.
|
|
9
9
|
|
|
10
|
-
The adapter builds on the Phase 8/9 shared machinery: redacted event projection (`AgUiProjection`), the durable pending-decision batch model (`
|
|
10
|
+
The adapter builds on the Phase 8/9 shared machinery: redacted event projection (`AgUiProjection`), the durable pending-decision batch model (keyed by the permission optionIds `allow-once` / `allow-for-run` / `reject-once` / `reject-for-run`), `AgentRunLifecycle` resume, `CodingLifecycleEvent` emission, and the AG-UI/ACP package caps. It never ships experimental ACP v2 or UNSTABLE fields (`providers`, `nes`, `positionEncoding`, `sessionCapabilities.fork`, `mcpCapabilities.acp/auth`, `elicitation` is consumed client-side only and never advertised; the UNSTABLE `plan` surface is consumed client-side only — `plan_update`/`plan_removed` are emitted solely to clients that advertised `ClientCapabilities.plan`, F5).
|
|
11
11
|
|
|
12
12
|
## When to use it
|
|
13
13
|
|
|
@@ -24,11 +24,12 @@ Do **not** use it when the host needs a browser/TUI Web endpoint (use [AG-UI](ag
|
|
|
24
24
|
| `authorize` | `(input) => AcpAuthorization \| Promise` | **Required.** Ownership/identity gate for every inbound call, scoped by `sessionId`; unknown sessions fail. |
|
|
25
25
|
| `sessionFactory` | `(input) => AcpSessionBinding \| Promise` | **Required.** Builds the Prism `AgentSession` for `session/new`. Input carries `authorization`, `cwd`, `additionalDirectories`, `mcpServers` (policy-checked), `signal`, optional pre-generated `sessionId`, and `coding` (built client fs/terminal adapters when the client advertised them). |
|
|
26
26
|
| `lifecycle` | `AgentRunLifecycle` | **Required.** `status`/`resume`/`resumeStream` for durable `session/load` and `session/resume`. |
|
|
27
|
-
| `sessions?` | `AcpSessionStoreSeams` | `load` (advertises `sessionCapabilities.loadSession`), `list` (list), `delete` (delete), `resume` (resume), `additionalDirectories` (policy narrowing of `additionalDirectories`). `close` is always advertised. |
|
|
27
|
+
| `sessions?` | `AcpSessionStoreSeams` | `load` (advertises `sessionCapabilities.loadSession`), `list` (list), `delete` (delete), `resume` (resume), `additionalDirectories` (policy narrowing of `additionalDirectories`), `transcript` (F2: replay source for `session/load`/`session/resume`), `title` (F6: host-owned session titles — see below). `close` is always advertised. |
|
|
28
28
|
| `mcp?` | `AcpMcpSeams` | `transports: ("http" \| "sse")[]` and `select({ servers, signal })` — **required** for any client-supplied MCP server; select must approve before the bridge connects. Advertises `mcpCapabilities.http`/`sse` per transport. |
|
|
29
29
|
| `modes?` | `{ modes: AcpSessionMode[], defaultModeId? }` | `AcpSessionMode { id, name, description?, apply? }`; `apply({ sessionId?, fromModeId?, modeId, signal })` is the host hook run on switch. Advertised in `SessionModeState` on new/load/resume; enables `session/set_mode`. |
|
|
30
|
-
| `configOptions?` | `{ options: AcpConfigOption[], onChange? }` | `boolean`/`select` options with `defaultValue`; enables `session/set_config_option` (requires the client to advertise `session.configOptions.boolean`). |
|
|
31
|
-
| `capabilities?` | `AcpCapabilitiesOptions` | `prompt.media`/`prompt.embedded` policy seams, re-checked **live at prompt time**; presence advertises `promptCapabilities.image`/`audio`/`embeddedContext`. |
|
|
30
|
+
| `configOptions?` | `{ options: AcpConfigOption[], onChange? }` | `boolean`/`select` options with `defaultValue`; enables `session/set_config_option` (requires the client to advertise `session.configOptions.boolean`). **B3:** only `boolean` options are advertised in `session/new`/`load`/`resume` responses and `config_option_update`; `select` options are never settable — `set_config_option` on one fails with `ERR_PRISM_ACP_CAPABILITY` until the ACP spec defines a select capability. |
|
|
31
|
+
| `capabilities?` | `AcpCapabilitiesOptions` | `prompt.media`/`prompt.embedded` policy seams, re-checked **live at prompt time**; presence advertises `promptCapabilities.image`/`audio`/`embeddedContext`. `usage.contextWindow({ model, signal })` reports the model's context window in tokens; `usage_update` is emitted only when it returns a positive finite number — absent/undefined/throw ⇒ the update is omitted (never `size = used`). |
|
|
32
|
+
| `commands?` | `AcpCommandsSeam` | F9: `{ list({ sessionId, signal }) => AcpCommand[] }`. Presence emits `available_commands_update` on `session/new`, `session/load`, and `session/resume`. Absent seam ⇒ no update. |
|
|
32
33
|
| `coding?` | `AcpCodingSeams` | `filesystem(client, sessionId)` / `processes(client, sessionId)` factories building `AcpClientFilesystem` / `AcpClientTerminals` over client methods; `lifecycle?: CodingLifecycleEmitter` subscribes `CodingLifecycleEvent`s into ACP updates. |
|
|
33
34
|
| `name?` | `string` | `agentInfo.name` (default `"Prism"`). |
|
|
34
35
|
|
|
@@ -43,20 +44,26 @@ In-stream `SessionUpdate`s:
|
|
|
43
44
|
| Prism event | ACP update |
|
|
44
45
|
|---|---|
|
|
45
46
|
| Assistant text | `agent_message_chunk` |
|
|
46
|
-
|
|
|
47
|
-
| Tool
|
|
48
|
-
|
|
|
49
|
-
|
|
|
47
|
+
| Assistant thinking | `agent_thought_chunk` (same `messageId` scheme as text; through the shared redactor and byte caps) |
|
|
48
|
+
| Tool lifecycle | `tool_call` / `tool_call_update` (title/status/content) — `tool_call.kind` comes from the session's tool registry `kind` metadata when present (B4), else the name heuristic |
|
|
49
|
+
| Tool result, projected | `tool_call_update` with `locations` (≤ `acpLocationsPerUpdate`) and/or a `diff` block (≤ `acpDiffBytes`) — only from `AgUiProjection.toolLocations`/`toolDiff` allow-lists, at `finish()`. `toolResult` may return a string (text content) or `{ type: "image", data, mimeType }` (F8) — the mapper wraps the image as `{ type: "content", content: { type: "image", data, mimeType } }` and drops payloads over `acpImageBytes` (never truncated). Opt-in turnkey: `createCodingToolProjection()` (F7) recognizes first-party `edit` (path + unified patch as `newText`, `firstChangedLine` location) and `write` (path location only — result has no file body) results; default remains deny-by-default. |
|
|
50
|
+
| Provider usage | `usage_update` (only when the `capabilities.usage.contextWindow` seam reports a valid window — absent/undefined/throw ⇒ the update is omitted, never `size = used`) |
|
|
51
|
+
| Run-level failure | No transcript chunk — the `session/prompt` request rejects with `ERR_PRISM_ACP_RUN` (redacted, byte-capped message). Retryable provider-turn failures stay silent and may recover; only a terminal `error` event fails the request. |
|
|
52
|
+
| Run stop reason | `session/prompt` returns the SDK `StopReason` (F4): `cancelled` when the run was aborted, `max_turn_requests` for the tool-round ceiling (`finishReason: "turn_limit"`), `max_tokens` for `"token_limit"`, `refusal` for `"refusal"`, else `end_turn`. The generic `finishReason` field is set on `agent_finished` by loop strategies (single-shot records `turn_limit` at the `maxToolRounds` ceiling); `token_limit`/`refusal` have no core producer yet — the mapping is ready. |
|
|
53
|
+
| Durable suspension | `session/request_permission` with the four options `allow-once`→`allow_once`, `allow-for-run`→`allow_always`, `reject-once`→`reject_once`, `reject-for-run`→`reject_always` (optionId→SDK kind, as emitted by `permission-elicit.ts`); cancel, unknown options, and request failure deny. Sticky decisions expire at run end. |
|
|
50
54
|
| Elicitation suspension (all-elicitation batch + client advertised `elicitation`) | `elicitation/create` (form mode, bounded schema, redacted reason); `accept` → `allow_once` with the typed payload as `RunDecision.elicitation`, `decline`/`cancel` → `reject_once`. Otherwise falls back to the shared four-option permission path. |
|
|
51
55
|
| `file_changed` lifecycle | `tool_call_update` with `locations: [{ path }]` (needs a `toolCallId`; diff only from `fileDiff` allow-list, capped + redacted) |
|
|
52
56
|
| `worktree_changed` / process events | Projection-gated `agent_message_chunk` (deny-by-default: no `lifecycle` projection hook = no update) |
|
|
53
57
|
| `permission_denied` lifecycle | `tool_call_update` status `failed` (never raw args; synthesized id `prism:denied:<approvalId>` when no `toolCallId`) |
|
|
54
58
|
| `configuration_changed` lifecycle | `config_option_update` with the full current set, per streaming session |
|
|
59
|
+
| Plan lifecycle (F5, UNSTABLE-gated) | `plan_changed` → `plan_update` with `plan: { type: "items", planId = planPath, entries: [{ content, priority: "medium", status }] }` — the complete entry list per update (client replaces its plan wholesale); `plan_removed` → `plan_removed` with `planId = planPath`. Emitted only when the client advertised `ClientCapabilities.plan`; mapper stays capability-agnostic (gate in the agent wiring). Entries come from `writeCodingPlanFile`'s `onEvent` (parsed via `parseCodingPlanTodos`) or host-emitted through their `CodingLifecycleEmitter`; text passes the shared redactor and byte caps. |
|
|
60
|
+
| Session title (F6) | `sessions.title({ sessionId, prompt, signal })` resolves on `session/prompt`; a defined value differing from the last emitted title produces `session_info_update` with `{ sessionUpdate: "session_info_update", title }`. Best-effort: `undefined` or a throw means no title and no update (requests never fail on titles); the host owns title storage. Titles pass the shared redactor and are truncated at `maxTextBytes`/`maxEventBytes`. |
|
|
61
|
+
| Slash commands (F9) | `commands.list({ sessionId, signal })` on `session/new`/`load`/`resume` produces `available_commands_update` with `{ name, description, input?: { hint } }` (SDK `AvailableCommand`; description is required). Names/descriptions/hints pass the shared redactor and `maxTextBytes`; the list is sliced at `acpCommandsPerUpdate`. Best-effort: a throw or non-array omits the update (session start never fails on commands). |
|
|
55
62
|
| Session mode/config switch | `current_mode_update` / `config_option_update` |
|
|
56
63
|
|
|
57
|
-
Frozen caps (default / hard, from the Phase 10 freeze manifest): sessions 32/128, additional directories 8/32 (path 4 KiB/16 KiB), MCP servers 8/32 (config 16 KiB/256 KiB, header values 4 KiB/64 KiB), modes 16/64, config options 16/64, list page 20/100, diff bytes 64 KiB/1 MiB, locations per update 32/128, prompt media parts 16/64 and media bytes 64 KiB/1 MiB (shared AG-UI caps), terminal output chunks 51200 B/1 MiB (Phase 9 `process.outputChunkBytes`), stream events/bytes per AG-UI budgets. `session/load`/`session/resume` of a still-registered session rejects with `ERR_PRISM_ACP_INPUT` ("ACP session already exists"); model reconnect as resume of a pre-seeded stored session.
|
|
64
|
+
Frozen caps (default / hard, from the Phase 10 freeze manifest): sessions 32/128, additional directories 8/32 (path 4 KiB/16 KiB), MCP servers 8/32 (config 16 KiB/256 KiB, header values 4 KiB/64 KiB), modes 16/64, config options 16/64, list page 20/100, diff bytes 64 KiB/1 MiB, locations per update 32/128, projected tool-result images `acpImageBytes` 256 KiB/1 MiB (F8; oversize dropped), slash commands `acpCommandsPerUpdate` 32/128 (F9), prompt media parts 16/64 and media bytes 64 KiB/1 MiB (shared AG-UI caps), terminal output chunks 51200 B/1 MiB (Phase 9 `process.outputChunkBytes`), stream events/bytes per AG-UI budgets. `session/load`/`session/resume` of a still-registered session rejects with `ERR_PRISM_ACP_INPUT` ("ACP session already exists"); model reconnect as resume of a pre-seeded stored session.
|
|
58
65
|
|
|
59
|
-
Errors surface as `AcpError` with codes `ERR_PRISM_ACP_INPUT` (malformed), `ERR_PRISM_ACP_LIMIT` (caps), `ERR_PRISM_ACP_POLICY` (host denied), `ERR_PRISM_ACP_CAPABILITY` (not advertised), `ERR_PRISM_ACP_MCP` (MCP bridging). Over the wire they become JSON-RPC `-32603` with the message in `data.details` (SDK behavior).
|
|
66
|
+
Errors surface as `AcpError` with codes `ERR_PRISM_ACP_INPUT` (malformed), `ERR_PRISM_ACP_LIMIT` (caps), `ERR_PRISM_ACP_POLICY` (host denied), `ERR_PRISM_ACP_CAPABILITY` (not advertised), `ERR_PRISM_ACP_MCP` (MCP bridging), `ERR_PRISM_ACP_RUN` (run-level failure — the `session/prompt` request rejects instead of emitting a fake `Agent error:` chunk). Over the wire they become JSON-RPC `-32603` with the message in `data.details` (SDK behavior).
|
|
60
67
|
|
|
61
68
|
## Request/response example
|
|
62
69
|
|
|
@@ -104,6 +111,7 @@ const agent = createPrismAcpAgent({
|
|
|
104
111
|
## Extension and configuration notes
|
|
105
112
|
|
|
106
113
|
- **Seam = capability.** Wiring `sessions.load` advertises `loadSession`; removing it withdraws the method. There is no separate capability flag to keep in sync — the freeze manifest's advertise-when matrix is enforced by construction and asserted by `scripts/phase10-conformance.test.mjs`.
|
|
114
|
+
- **Transcript replay (F2).** When `sessions.transcript` is wired, `session/load` and `session/resume` replay `user_message_chunk`/`agent_message_chunk` text chunks (from `SessionEntry`s with `kind: "message"` and a user/assistant role, text blocks only) before returning `sessionState`. Each chunk passes the shared redactor and is truncated at `maxTextBytes`; replay stops at `maxReplayEvents` chunks and counts against the stream event/byte caps (an oversized transcript fails the load/resume request closed). Absent seam = no replay, behavior unchanged.
|
|
107
115
|
- **Client fs/terminal are adapters, not a second implementation.** `AcpClientFilesystem` / `AcpClientTerminals` wrap the client's `fs/*` and `terminal/*` methods behind the Phase 9 `ProcessSession`-flavored interfaces; the agent pre-generates the session id so terminal requests can carry it. Host repo operations remain default when the client fs is absent.
|
|
108
116
|
- **Modes and config options are a pure host overlay.** The agent stores only a thin per-session registry; `apply`/`onChange` hooks narrow the host's own behavior. Mode switches can narrow or host-authorized widen — never a parallel policy evaluator, never a client-enabled tool.
|
|
109
117
|
- **Lifecycle wiring.** Pass your `createCodingLifecycleEmitter()` as `coding.lifecycle`; `file_changed` etc. then flow to streaming sessions. `configuration_changed` broadcasts `config_option_update` (agent-message fallback if the SDK rejects the kind).
|
|
@@ -141,6 +149,9 @@ const agent = createPrismAcpAgent({
|
|
|
141
149
|
|
|
142
150
|
- **Untrusted client input.** Client-supplied paths, `additionalDirectories`, MCP server configs, terminal env/args, and media are validated at the boundary: count/byte caps, ownership-scoped sessions, path policy via the `sessions.additionalDirectories` seam, MCP servers only through host `select` (never auto-connected), UNSTABLE `acp` transport always rejected.
|
|
143
151
|
- **Deny-closed by default.** Unknown mode ids, unadvertised methods, unprojected lifecycle events, oversize diffs/locations/media, thrown projection hooks, and failed elicitation all fail closed. Raw tool arguments/results are never sent unless a projection allow-list says otherwise.
|
|
152
|
+
- **Slash commands (F9).** `commands.list` is a host-owned slash-command list (not derived from the tool registry). The agent emits `available_commands_update` on session start (`session/new`, `session/load`, `session/resume`). Mid-session refresh is not in this release — re-list by starting a session. Names, descriptions, and input hints pass the shared redactor; the list is sliced at `acpCommandsPerUpdate`. Absent seam or a thrown list ⇒ no update.
|
|
153
|
+
- **Projected images (F8).** `AgUiProjection.toolResult` may return `{ type: "image", data, mimeType }` (return-type widening — existing string returns stay valid). The mapper emits `{ type: "content", content: { type: "image", data, mimeType } }` (SDK v1 `ToolCallContent` has no top-level image variant). `data` is the host-supplied base64; it is not redacted and not truncated — payloads over `acpImageBytes` are dropped. Default (no hook / non-image return) emits no image.
|
|
154
|
+
- **Coding-tool projection (F7).** `createCodingToolProjection({ maxDiffBytes? })` is an opt-in `AgUiProjection` for first-party `@arnilo/prism-coding-agent` `edit`/`write` results: `edit` → `toolDiff` (`path` + unified `patch` as `newText`) and `toolLocations` (`path` + `firstChangedLine`); `write` → `toolLocations` (`path` only — write metadata has no file body, so no honest diff; use `file_changed` + `fileDiff` when bodies are needed). Pass as `projection: createCodingToolProjection()` on the agent/mapper. Mapper still redacts and enforces `acpDiffBytes` / `acpLocationsPerUpdate`; optional `maxDiffBytes` pre-truncates the patch so a slightly-oversize edit is shortened instead of dropped. Without the factory, behavior is unchanged (deny-by-default).
|
|
144
155
|
- **No secrets.** Updates carry no raw file bodies, terminal output is capped by the Phase 9 chunk budget, and the shared redactor is applied before anything leaves the host. `permission_denied` never includes raw args.
|
|
145
156
|
- **Performance.** The adapter is O(1) per update with no unbounded buffering; p95 targets (fs round trip 250 ms, mode switch 250 ms, terminal chunk ack 1000 ms, prompt first update 2000 ms, prompt end 30 s) are recorded by `scripts/benchmark-0.0.27.mjs` and gated in `scripts/budgets.json` `phase10`.
|
|
146
157
|
|
package/docs/ag-ui.md
CHANGED
|
@@ -202,7 +202,7 @@ const renderer = createA2UiRenderer({
|
|
|
202
202
|
const surface = await renderer.surface("chat"); // detached DOM node, kept in sync
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
-
The core is a DOM-free state machine (`reduceA2UiOps`): operations become a surface/component model (adjacency list with `id`/`component`/flat props, A2UI v0.9 JSON-Pointer data model, `deleteSurface`); a thin binding layer renders the model through catalog component renderers (framework-free `(props, ctx, dom) => node` functions). The core is also exported as values from the subpath entry (`A2UiSurfaceState`, `reduceA2UiOps`, `readA2UiBatch`, `resolvePointer`, `A2UI_VERSION`, 0.0.27,
|
|
205
|
+
The core is a DOM-free state machine (`reduceA2UiOps`): operations become a surface/component model (adjacency list with `id`/`component`/flat props, A2UI v0.9 JSON-Pointer data model, `deleteSurface`); a thin binding layer renders the model through catalog component renderers (framework-free `(props, ctx, dom) => node` functions). The core is also exported as values from the subpath entry (`A2UiSurfaceState`, `reduceA2UiOps`, `readA2UiBatch`, `resolvePointer`, `A2UI_VERSION`, 0.0.27, host FR) so framework hosts can drive the validated surface state machine and own the view layer; behavior and frozen caps are unchanged. Snapshots replace a surface's model (streaming mode sends cumulative ops); RFC 6902 deltas append. The same frozen caps as the server painter are enforced client-side: 64/512 ops per message, 64 KiB/1 MiB per op, 16/64 surfaces per run, depth 32/64. Invalid or oversized ops drop closed with one bounded `prism.a2ui.error` event (host logging via `onError`); unknown catalog components render an explicit placeholder. The renderer never executes remote HTML: only `createElement`/`createTextNode`/`appendChild`, no HTML-string assignment, no dynamic code evaluation. Data bindings `{"path": "/pointer"}` resolve against the per-surface data model; `deleteSurface` detaches content. The main `@arnilo/prism-ag-ui` entry stays runtime-agnostic — DOM code lives only behind the `renderer` subpath (the root entry re-exports renderer types only, no values). Hosts embedding it should follow the MCP Apps CSP/sandbox guidance (`docs/ag-ui-adoption.md`) for iframe/worker placement.
|
|
206
206
|
|
|
207
207
|
## Security and performance notes
|
|
208
208
|
|
|
@@ -15,7 +15,7 @@ A third helper, `discoverAgentBundles(options)` (same Node subpath), scans an ap
|
|
|
15
15
|
|
|
16
16
|
Use `resolveAgentDefinition` when an app already holds a `AgentDefinition` (from an extension, a manifest, or hand-written config) and wants to turn it into an `Agent` against its registries — without wiring every field by hand.
|
|
17
17
|
|
|
18
|
-
Use `discoverAgentBundles` + `resolveAgentBundle` when a host app keeps per-agent bundles on disk under an app-controlled config root (for example
|
|
18
|
+
Use `discoverAgentBundles` + `resolveAgentBundle` when a host app keeps per-agent bundles on disk under an app-controlled config root (for example `<appRoot>/extensions/prism/agents/<agentName>/AGENT.md`) and wants to honor them as first-class agents. The bundle layout is host-owned: Prism never picks the config root, never touches the user's home directory, and never auto-runs resolution — the host calls `discoverAgentBundles` and then `resolveAgentBundle` explicitly.
|
|
19
19
|
|
|
20
20
|
Do not use the bundle loader to discover providers — provider/model packages stay config/package-driven (Phase 24; see [Provider packages](provider-packages.md)). Do not use it to auto-activate undeclared tools or skills: omitted `tools` / `skills` means no active capabilities by default; named bundle entries are explicit activation, and runtime skill selection can narrow further with `RunOptions.activeSkills`.
|
|
21
21
|
|
package/docs/agent-events.md
CHANGED
|
@@ -88,7 +88,7 @@ Agent / turn / message events:
|
|
|
88
88
|
| Variant | Fields |
|
|
89
89
|
| --- | --- |
|
|
90
90
|
| `agent_started` | `sessionId`, `runId` |
|
|
91
|
-
| `agent_finished` | `sessionId`, `runId`, `usage?: Usage` (aggregate of all usage-bearing provider turns) |
|
|
91
|
+
| `agent_finished` | `sessionId`, `runId`, `usage?: Usage` (aggregate of all usage-bearing provider turns), `finishReason?: "turn_limit" \| "token_limit" \| "refusal"` (why a limit/ceiling ended the run cleanly — F4; absent = natural end) |
|
|
92
92
|
| `agent_suspended` | `sessionId`, `runId`, redacted `interruption`, checkpoint `version`; no tool side effect has started. |
|
|
93
93
|
| `agent_resumed` | `sessionId`, `runId`, checkpoint `version`. |
|
|
94
94
|
| `agent_denied` | `sessionId`, `runId`, redacted `interruption`, checkpoint `version`; no tool side effect runs. |
|
|
@@ -242,4 +242,4 @@ for await (const event of session.stream("draft", { loop: { strategy: "generate-
|
|
|
242
242
|
- [Observability](observability.md): `ProviderTurnMetadata`; optional adapter builds one parented GenAI span tree from metadata-only lifecycle events and ignores message/progress deltas.
|
|
243
243
|
- [Tools](tools.md): `tool_execution_*` variants.
|
|
244
244
|
- [Compaction and retry policies](compaction-and-retry.md): `compaction_*` and `retry_scheduled` variants.
|
|
245
|
-
- [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional redacted mapping of this stream; durable replay is ledger-backed and at-least-once, never a live-subscriber substitute. [ACP coding-host interop](acp.md) additionally maps `CodingLifecycleEvent`s from `@arnilo/prism-coding-agent` (`file_changed`, `worktree_changed`, `permission_denied`, `configuration_changed`; process events reuse `CodingProcessEvent`) into ACP session updates — locations/diff blocks only through projection allow-lists, terminal chunks under `process.outputChunkBytes
|
|
245
|
+
- [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional redacted mapping of this stream; durable replay is ledger-backed and at-least-once, never a live-subscriber substitute. [ACP coding-host interop](acp.md) additionally maps `CodingLifecycleEvent`s from `@arnilo/prism-coding-agent` (`file_changed`, `worktree_changed`, `permission_denied`, `configuration_changed`, `plan_changed`, `plan_removed`; process events reuse `CodingProcessEvent`) into ACP session updates — locations/diff blocks only through projection allow-lists, terminal chunks under `process.outputChunkBytes`, plan updates only to clients that advertised the UNSTABLE `plan` capability.
|
package/docs/agent-loops.md
CHANGED
|
@@ -232,7 +232,7 @@ await session.run(input, { loop: twoShotLoop });
|
|
|
232
232
|
- `generateValidateReviseLoop` makes at most `1 + maxRevisions + maxToolRounds` provider turns when bounded tools are enabled (otherwise `maxRevisions + 1`); it cannot loop forever. Each revision costs one provider turn plus one store append.
|
|
233
233
|
- Bounded artifact tool calls run sequentially through `dispatchToolCall` (permission + validation + execute); their assistant call and result are persisted before the next provider request. `singleShotLoop` retains its bounded parallel worker pool and original call-order transcript behavior.
|
|
234
234
|
- The loop is a plain object/factory; no class hierarchy, no background work, no extra dependencies. `LoopContext` is a single object literal of bound arrows built once per run.
|
|
235
|
-
- The
|
|
235
|
+
- The host-domain-free boundary is guarded by tests: `src/` imports no host-domain package, and the `Artifact*`/`AgentLoop*`/`LoopContext` contracts contain no `workflow`/`node`/`step` field names. Hosts supply their own schema; no host domain type is imported by `src/`.
|
|
236
236
|
|
|
237
237
|
## Guardrails
|
|
238
238
|
|
|
@@ -241,7 +241,7 @@ Built-in loops and custom loops that use `LoopContext.generate()` / `LoopContext
|
|
|
241
241
|
## Related APIs
|
|
242
242
|
- [Agent/session runtime](agent-session-runtime.md): `RuntimeAgentSession.run()` builds the `LoopContext` and delegates to the resolved loop.
|
|
243
243
|
- [Agent events](agent-events.md): the `artifact_*` event variants and ordering emitted by `generateValidateReviseLoop`.
|
|
244
|
-
- [Structured output](structured-output.md): the `ArtifactParser<T>`/`ArtifactValidator<T>`/`ArtifactRepairer<T>` seam (host-defined `T`, Prism never instantiates it) and a
|
|
244
|
+
- [Structured output](structured-output.md): the `ArtifactParser<T>`/`ArtifactValidator<T>`/`ArtifactRepairer<T>` seam (host-defined `T`, Prism never instantiates it) and a host schema→`ArtifactValidation` mapping example.
|
|
245
245
|
- [Public contracts](public-contracts.md): `AgentLoopStrategy`, `AgentLoopOptions`, `LoopContext`, `ProviderTurnResult`, and the `Artifact*` contracts.
|
|
246
246
|
- [Input and prompt assembly](input-and-prompt-assembly.md): `assembleProviderInput()`, the primitive behind `LoopContext.assemble`.
|
|
247
247
|
- [Tools](tools.md): `dispatchToolCall()`, the primitive behind `LoopContext.dispatchToolCall`.
|
package/docs/caveman.md
CHANGED
|
@@ -37,9 +37,9 @@ Session custom entry shape:
|
|
|
37
37
|
{ "kind": "custom", "data": { "type": "caveman-level", "level": "full" } }
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Required skills (fail closed if missing): `caveman`, `caveman-commit`, `caveman-review`, `caveman-stats`, `caveman-compress`, `caveman-help`, `cavecrew`. Extra `skills/*/SKILL.md` (v2.1 extras like `caveman-explore`) register as optional skills and `load_skill` commands. Dirs without `SKILL.md` (`*.mjs`, `registry.json`, `generated/`) are skipped.
|
|
41
41
|
|
|
42
|
-
Registered commands: `caveman
|
|
42
|
+
Registered commands: `caveman` (level), `caveman-init`, plus one `load_skill` dispatch per registered skill except `caveman`.
|
|
43
43
|
|
|
44
44
|
## Outputs / response / events
|
|
45
45
|
|
|
@@ -110,6 +110,7 @@ See `examples/caveman-ponytail.ts` for progressive catalog + `load_skill` wiring
|
|
|
110
110
|
- `caveman-stats` dispatches skill metadata only; full stats need host session-log integration.
|
|
111
111
|
- `caveman-init` returns upstream guidance text; it does not write files in the host repo.
|
|
112
112
|
- No TUI status bar; optional `caveman:status` events for host UI.
|
|
113
|
+
- Caveman 2 compression proxy/engine is **not** a Prism runtime. Only `SKILL.md` files under `skills/` load.
|
|
113
114
|
|
|
114
115
|
## Security and performance notes
|
|
115
116
|
|
|
@@ -48,6 +48,8 @@ import { createCodingTools } from "@arnilo/prism-coding-agent";
|
|
|
48
48
|
const tools = createToolRegistry(createCodingTools(process.cwd()));
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
Every tool carries an explicit `kind` (`shell`→`execute`, `read`/`repo_list`→`read`, `write`/`edit`→`edit`, `repo_search`/`glob`→`search`, `delete`→`delete`, `move`→`move`) so ACP `tool_call` updates and other consumers can classify tools without name heuristics.
|
|
52
|
+
|
|
51
53
|
## When to use it
|
|
52
54
|
|
|
53
55
|
Use this package when a host wants ready-made coding tools for an agent, session, or run, registered explicitly into a `ToolRegistry` and dispatched through the normal Prism tool harness. The tools perform **real** shell and filesystem operations on the host — they are not mocked or sandboxed. Use the individual factories when you need per-tool options or custom operation backends; use the aggregators when you want the default set.
|
|
@@ -578,5 +580,5 @@ Every configurable value is a positive safe integer (context may be zero); Prism
|
|
|
578
580
|
- [Public contracts](public-contracts.md): `ToolDefinition`, `ToolResult`, `ToolExecutionContext`, `ContentBlock`, and `JsonObject` shapes.
|
|
579
581
|
- [Host security guide](host-security.md): fail-closed checklist for permission policies, tool validation, and trust boundaries that must gate these tools.
|
|
580
582
|
- [Tool conformance](tool-conformance.md): assertions for the tool-dispatch blocked-reason matrix these tools participate in.
|
|
581
|
-
- [ACP coding-host interop](acp.md): host editors drive these tools through stable ACP v1 — client fs/terminal adapters, `CodingLifecycleEvent` emission (`file_changed` etc. via the `onEvent` options), and permission/elicitation through the shared four-outcome decision model.
|
|
583
|
+
- [ACP coding-host interop](acp.md): host editors drive these tools through stable ACP v1 — client fs/terminal adapters, `CodingLifecycleEvent` emission (`file_changed` etc. via the `onEvent` options; `plan_changed` also fires from `writeCodingPlanFile`'s `onEvent`, F5), and permission/elicitation through the shared four-outcome decision model.
|
|
582
584
|
- [LLM compaction package](compaction-llm.md): optional `createCodingCompactionStrategy()` retains bounded paths, patch intent, checks, plan/todo state, blockers, and next verification—not complete diffs or raw command output.
|
package/docs/coding-security.md
CHANGED
|
@@ -210,6 +210,12 @@ The protected coding journey (0.2.6, plan 026 Task 7) exercises these boundaries
|
|
|
210
210
|
|
|
211
211
|
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).
|
|
212
212
|
|
|
213
|
+
## Windows hosts
|
|
214
|
+
|
|
215
|
+
`createNativeSandbox` is Linux-only. On any other platform (including Windows) it throws at creation and does **not** fall back to an unsandboxed process — egress denial cannot be enforced by construction without a network namespace. Do not catch that error and enable `shell` on the host; keep `shell` disabled, or run the agent inside a Docker container using `createDockerSandbox` and the documented [allow-list egress](#allow-list-egress-composition) policy (`network: none` or an attested custom network). Host-mode tools (`read`/`write`/`edit` under `workspaceMode: "host"`) remain available; they never claim containment.
|
|
216
|
+
|
|
217
|
+
A native Windows backend (Job objects / AppContainer) is tracked, not scheduled. Until one exists, Windows hosts that need isolation use Docker. Do not weaken the deny-by-default posture to compensate.
|
|
218
|
+
|
|
213
219
|
## Related APIs
|
|
214
220
|
|
|
215
221
|
- [Coding agent tools](coding-agent-tools.md): durable plan/todo Markdown helpers and `state.coding` checkpoint metadata for restart/resume without a second runtime
|
|
@@ -196,7 +196,7 @@ await agent.createSession().run("…", { activeSkills: ["ponytail"] });
|
|
|
196
196
|
// Turn 1: catalog only. After load_skill({ name: "ponytail" }), later turns include instructions.
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
-
### Third-party behavior packages (Caveman, Ponytail)
|
|
199
|
+
### Third-party behavior packages (Caveman, Ponytail, Impeccable)
|
|
200
200
|
|
|
201
201
|
`@arnilo/prism-caveman` and `@arnilo/prism-ponytail` register upstream skills into the extension kernel skill registry. Hosts should:
|
|
202
202
|
|
|
@@ -205,7 +205,7 @@ await agent.createSession().run("…", { activeSkills: ["ponytail"] });
|
|
|
205
205
|
3. Keep `skillsDisclosure: "progressive"` and register `createLoadSkillTool` — full `SKILL.md` bodies stay catalog-only until `load_skill`.
|
|
206
206
|
4. Select `instructionInjectors: ["caveman-mode", "ponytail-mode"]` (or subset) for mode/level slices **without** forcing `skillsDisclosure: "eager"`.
|
|
207
207
|
|
|
208
|
-
Mode slices and skill bodies are independent: the injector can add `PONYTAIL MODE ACTIVE` while `ponytail-audit` remains catalog-only until loaded. See [Caveman](caveman.md), [Ponytail](ponytail.md), and `examples/caveman-ponytail.ts`.
|
|
208
|
+
Mode slices and skill bodies are independent: the injector can add `PONYTAIL MODE ACTIVE` while `ponytail-audit` remains catalog-only until loaded. See [Caveman](caveman.md), [Ponytail](ponytail.md), [Impeccable](impeccable.md), and `examples/caveman-ponytail.ts`.
|
|
209
209
|
|
|
210
210
|
Pure validation without the tool: `resolveSkillLoad({ registry, name, tools, loaded, activeSkillNames })`.
|
|
211
211
|
|
|
@@ -234,7 +234,7 @@ const providers = createOpenAIProviderPackage({ apiKey });
|
|
|
234
234
|
- Use distinct `namespace` or vault paths per tenant/environment.
|
|
235
235
|
- Keychain `list()` / `listOAuth()` are intentionally unsupported — enumerate credentials through host configuration instead of scanning the OS store.
|
|
236
236
|
- Combine with `createExplicitCredentialResolver()` so runtime overrides still win over stored values.
|
|
237
|
-
- Wire `createOAuthCredentialStoreAdapter(store)` into `refreshOAuthCredential()` only for an OAuth flow explicitly selected by the host and authorized by that provider.
|
|
237
|
+
- Wire `createOAuthCredentialStoreAdapter(store)` into `refreshOAuthCredential()` only for an OAuth flow explicitly selected by the host and authorized by that provider. That means OpenAI Codex, xAI SuperGrok / X Premium, and the Microsoft 365 / Google Workspace workload providers (`createMicrosoft365OAuthProvider` / `createGoogleWorkspaceOAuthProvider`) with least-privilege read/mutation scope bundles. Anthropic and Google *model* packages still accept API keys only. Never import or migrate Claude Code/Gemini CLI/`~/.grok` credential files, setup tokens, browser sessions, or CLI OAuth rows into this store.
|
|
238
238
|
- Enterprise cloud providers (`azure` / `bedrock` / `vertex`) expect host workload-identity callbacks (Entra / IAM / ADC), not this local encrypted/keychain store as a cloud token minting service. Store may hold opaque refresh material only when the host already owns the cloud auth flow.
|
|
239
239
|
|
|
240
240
|
## Security and performance notes
|
|
@@ -106,7 +106,7 @@ console.log(error.message);
|
|
|
106
106
|
|
|
107
107
|
### Subscription OAuth eligibility
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
First-party subscription OAuth is explicit and host-invoked: OpenAI Codex (`createOpenAICodexOAuthProvider()`) and xAI SuperGrok / X Premium (`createXaiOAuthProvider()`). Hosts own login UI and may use `createOAuthCredentialStoreAdapter()` for deliberately selected durable storage. Do not import `~/.grok` or grok-cli auth files.
|
|
110
110
|
|
|
111
111
|
Anthropic and Google provider packages are API-key-only. Do not scrape or import Claude Code/Gemini CLI credential files, setup tokens, environment values, or browser sessions, and do not route a user's Claude.ai/Gemini subscription through Prism. Anthropic states that developers building products must use Claude Console API keys or a supported cloud provider and may not offer Claude.ai login or route Free/Pro/Max credentials ([legal and compliance](https://docs.anthropic.com/en/docs/claude-code/legal-and-compliance)). Gemini CLI states that third-party software using its OAuth to access backend services violates applicable terms; its FAQ names Vertex AI or Google AI Studio API keys as the supported third-party path ([terms](https://github.com/google-gemini/gemini-cli/blob/main/docs/resources/tos-privacy.md), [FAQ](https://github.com/google-gemini/gemini-cli/blob/main/docs/resources/faq.md)).
|
|
112
112
|
|
|
@@ -124,7 +124,7 @@ A future provider-local OAuth adapter needs published permission for third-party
|
|
|
124
124
|
- `resolveCredentialValue()` and `createExplicitCredentialResolver()` do not cache values. Add host-side caching only if a real credential source needs it.
|
|
125
125
|
- `refreshOAuthCredential()` only calls the supplied OAuth provider and optional store; it has no built-in persistence or retry loop.
|
|
126
126
|
- OpenAI Codex device-code OAuth polls inside `createOpenAICodexOAuthProvider().login()` with bounded delays and abort support via `OAuthLoginCallbacks.signal`. Token-endpoint failures redact authorization codes, PKCE verifiers, device/user codes, and access/refresh tokens when those values are known.
|
|
127
|
-
- The shared bounded device/token flow lives in core `pollDeviceCodeToken` (0.2.1) and is used by the OpenAI Codex provider and the credentials-node OAuth 2.0 provider (Microsoft 365 / Google Workspace). It owns the RFC 8628 device-code request and poll loop (`authorization_pending` continue, `slow_down` +5s backoff, expiry deadline, abort), reads every response body under the shared byte ceiling, parses success bodies with a fail-closed shape gate (an `access_token` string is required), and redacts device/user codes, authorization codes, PKCE verifiers, and tokens from every thrown error. Adapter-specific fields (message prefix, extra token params, account binding) are plain options, never subclasses.
|
|
127
|
+
- The shared bounded device/token flow lives in core `pollDeviceCodeToken` (0.2.1) and is used by the OpenAI Codex provider and the credentials-node OAuth 2.0 provider (Microsoft 365 / Google Workspace). It owns the RFC 8628 device-code request and poll loop (`authorization_pending` continue, `slow_down` +5s backoff, expiry deadline, abort), reads every response body under the shared byte ceiling, parses success bodies with a fail-closed shape gate (an `access_token` string is required), and redacts device/user codes, authorization codes, PKCE verifiers, and tokens from every thrown error. Adapter-specific fields (message prefix, extra token params, account binding) are plain options, never subclasses. Optional `bodyEncoding: "form"` POSTs `application/x-www-form-urlencoded` for both the device-code request and every token poll (default remains `json` so existing callers stay byte-compatible). `extraDeviceParams` merge into the device-code body only. `verification_uri` and optional `verification_uri_complete` must be `https:`; the complete URI is what `onDeviceCode` receives when present.
|
|
128
128
|
|
|
129
129
|
## Related APIs
|
|
130
130
|
|
package/docs/extensions.md
CHANGED
|
@@ -142,6 +142,7 @@ await kernel.middleware.run("provider_request", { metadata: {} });
|
|
|
142
142
|
- [Observational memory compaction package](compaction-observational-memory.md): optional extension helper that registers an inert fast memory compaction strategy.
|
|
143
143
|
- [Caveman behavior integration](caveman.md): optional `@arnilo/prism-caveman` upstream Caveman skills, commands, level injector, and session `caveman-level` persistence.
|
|
144
144
|
- [Ponytail behavior integration](ponytail.md): optional `@arnilo/prism-ponytail` upstream Ponytail skills, commands, mode injector, and session `ponytail-mode` persistence.
|
|
145
|
+
- [Impeccable behavior integration](impeccable.md): optional `@arnilo/prism-impeccable` upstream Impeccable skill and `load_skill` command.
|
|
145
146
|
- [Public contracts](public-contracts.md): `Extension`, `ExtensionAPI`, and contribution contract types.
|
|
146
147
|
- [Credentials and redaction](credentials-and-redaction.md): secret-redaction behavior used for extension errors.
|
|
147
148
|
|