@arnilo/prism 0.0.3 → 0.0.4
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 +22 -0
- package/README.md +32 -20
- package/dist/agent-loops.d.ts +8 -1
- package/dist/agent-loops.js +57 -11
- package/dist/agents.js +70 -17
- package/dist/checkpoints.d.ts +11 -0
- package/dist/checkpoints.js +144 -0
- package/dist/compaction.js +9 -1
- package/dist/content.d.ts +102 -0
- package/dist/content.js +410 -0
- package/dist/contracts.d.ts +142 -2
- package/dist/event-multiplexer.d.ts +23 -0
- package/dist/event-multiplexer.js +136 -0
- package/dist/execution-policy.d.ts +28 -0
- package/dist/execution-policy.js +24 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +11 -4
- package/dist/input.js +11 -1
- package/dist/leases.d.ts +8 -0
- package/dist/leases.js +111 -0
- package/dist/node/agent-definitions.js +3 -5
- package/dist/node/config.d.ts +1 -0
- package/dist/node/config.js +5 -3
- package/dist/node/contribution-discovery.js +5 -8
- package/dist/node/session-store-jsonl.js +8 -5
- package/dist/node/settings.js +2 -2
- package/dist/node/trust.js +2 -4
- package/dist/observability.d.ts +3 -0
- package/dist/observability.js +18 -0
- package/dist/providers/media.d.ts +42 -0
- package/dist/providers/media.js +116 -0
- package/dist/providers/openai-compatible.js +18 -119
- package/dist/providers/openai-primitives.d.ts +9 -0
- package/dist/providers/openai-primitives.js +129 -0
- package/dist/providers/transport.d.ts +40 -0
- package/dist/providers/transport.js +221 -0
- package/dist/redaction.js +40 -13
- package/dist/resources.d.ts +5 -0
- package/dist/resources.js +4 -0
- package/dist/structured-output.d.ts +11 -0
- package/dist/structured-output.js +59 -0
- package/dist/testing/persistence-schema.d.ts +102 -0
- package/dist/testing/persistence-schema.js +457 -0
- package/dist/testing/provider-conformance.js +10 -1
- package/dist/testing/run-ledger-conformance.d.ts +33 -0
- package/dist/testing/run-ledger-conformance.js +172 -0
- package/dist/testing/session-store-conformance.d.ts +16 -0
- package/dist/testing/session-store-conformance.js +73 -0
- package/dist/tools.d.ts +17 -0
- package/dist/tools.js +29 -2
- package/docs/agent-events.md +13 -4
- package/docs/agent-loops.md +10 -4
- package/docs/agent-session-runtime.md +1 -0
- package/docs/cli-rpc.md +3 -0
- package/docs/coding-agent-tools.md +41 -7
- package/docs/coding-security.md +84 -0
- package/docs/credential-storage.md +177 -0
- package/docs/credentials-and-redaction.md +2 -1
- package/docs/database-persistence.md +44 -2
- package/docs/host-security.md +15 -1
- package/docs/index.md +28 -12
- package/docs/input-and-prompt-assembly.md +6 -5
- package/docs/mcp-tools.md +139 -0
- package/docs/middleware-hooks.md +2 -0
- package/docs/migration.md +21 -28
- package/docs/model-registry.md +5 -3
- package/docs/multimodal-content.md +148 -0
- package/docs/observability.md +163 -0
- package/docs/performance.md +40 -1
- package/docs/persistence-credentials-multimodality-primitives.md +303 -0
- package/docs/postgres-persistence.md +141 -0
- package/docs/provider-conformance.md +17 -0
- package/docs/provider-layer.md +1 -1
- package/docs/provider-primitives.md +281 -0
- package/docs/providers/kimi.md +1 -0
- package/docs/providers/neuralwatt.md +1 -0
- package/docs/providers/openai-compatible.md +2 -1
- package/docs/providers/openai.md +8 -1
- package/docs/providers/opencode-go.md +1 -0
- package/docs/providers/openrouter.md +1 -0
- package/docs/providers/zai.md +1 -0
- package/docs/public-contracts.md +9 -2
- package/docs/release-and-install.md +209 -25
- package/docs/resource-loading.md +14 -4
- package/docs/review-coverage-2026-07-14.md +260 -0
- package/docs/run-ledger-conformance.md +96 -0
- package/docs/runs-and-usage.md +2 -0
- package/docs/session-store-conformance.md +16 -0
- package/docs/session-stores-and-branching.md +1 -0
- package/docs/settings-auth-trust-security.md +2 -1
- package/docs/sqlite-persistence.md +122 -0
- package/docs/structured-output.md +9 -0
- package/docs/tool-conformance.md +1 -0
- package/docs/tool-execution-primitives.md +374 -0
- package/docs/tools.md +39 -1
- package/docs/workflow-orchestration-primitives.md +565 -0
- package/docs/workflow-tui-primitives.md +5 -0
- package/docs/workflows.md +219 -0
- package/package.json +33 -5
package/docs/tool-conformance.md
CHANGED
|
@@ -77,6 +77,7 @@ await assertToolDispatchConforms(createToolRegistry(), {
|
|
|
77
77
|
## Security and performance notes
|
|
78
78
|
|
|
79
79
|
- No credentials, no network required.
|
|
80
|
+
- Supply `validate` to exercise your policy; use `createJsonSchemaToolArgumentValidator()` from `@arnilo/prism-tool-validator-json-schema` for standards-based `parameters` validation.
|
|
80
81
|
- The helper uses an allow-all permission policy by default; supply `permission` to validate your fail-closed policy.
|
|
81
82
|
- Blocked calls are proven not to execute by the absence of `tool_execution_started`.
|
|
82
83
|
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Tool execution primitives
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
This page freezes the reusable tool validation, parallel dispatch, MCP bridge, and coding execution-policy designs for Plan 055. It inventories existing `@arnilo/prism` tool harness seams, `@arnilo/prism-coding-agent` behavior, extension/contribution boundaries, and the MCP mapping surface Tasks 1–6 will implement against.
|
|
6
|
+
|
|
7
|
+
Implementation is **shipped** for JSON Schema tool argument validation (Plan 055 Task 1), parallel single-shot tool dispatch (Task 2), the MCP client bridge (Task 3), coding execution policy (Task 4), and bounded image reads (Task 5). Task 6 verification evidence is recorded in [review coverage](review-coverage-2026-07-14.md).
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
- **Core and package authors** extending tool dispatch should reuse the seams documented here instead of adding MCP-specific branches to core or duplicating validation in the agent loop.
|
|
12
|
+
- **Host apps** wire JSON Schema validation through the existing `ToolValidator` / `dispatchToolCall({ validate })` path (Phase 25) and opt into parallelism, MCP tools, and coding execution policy through the frozen APIs below.
|
|
13
|
+
- **Security reviewers** use the threat model and conformance matrix on this page as the acceptance baseline for Plan 055 Tasks 1–6.
|
|
14
|
+
|
|
15
|
+
## Inputs / request
|
|
16
|
+
|
|
17
|
+
| Surface | Input |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| JSON Schema validation | `ToolDefinition.parameters` plus bounded validator options |
|
|
20
|
+
| Parallel dispatch | `single-shot` loop `toolConcurrency`; optional `ToolDefinition.exclusive` |
|
|
21
|
+
| MCP | Explicit server id, transport, timeout/cache/result bounds |
|
|
22
|
+
| Coding policy | `ExecutionAction`, roots/rules, approval callback, optional sandbox |
|
|
23
|
+
| Image reads | Path plus `maxImageBytes` and optional `transformImage` |
|
|
24
|
+
|
|
25
|
+
## Outputs / response / events
|
|
26
|
+
|
|
27
|
+
All paths converge on normal `ToolResult` values and `tool_execution_*` events. Validation/permission/policy failures block handlers before side effects. Parallel handlers may finish out of order, but transcript `tool_result` messages append in provider call order; any exclusive tool serializes only its turn.
|
|
28
|
+
|
|
29
|
+
## Request/response example
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"loop": { "strategy": "single-shot", "toolConcurrency": 2 },
|
|
34
|
+
"tool": { "name": "shell", "exclusive": true },
|
|
35
|
+
"result": { "dispatchConcurrencyForTurn": 1 }
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Implementation example
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { createAgent } from "@arnilo/prism";
|
|
43
|
+
import { createJsonSchemaToolArgumentValidator } from "@arnilo/prism-tool-validator-json-schema";
|
|
44
|
+
|
|
45
|
+
const agent = createAgent({
|
|
46
|
+
model,
|
|
47
|
+
provider,
|
|
48
|
+
tools,
|
|
49
|
+
validator: createJsonSchemaToolArgumentValidator({ missingSchema: "reject" }),
|
|
50
|
+
loop: { strategy: "single-shot", toolConcurrency: 2 },
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Inventory (2026-07-14 baseline)
|
|
55
|
+
|
|
56
|
+
Static review of `src/tools.ts`, `src/security.ts`, `src/agent-loops.ts`, `src/agents.ts`, `src/extensions.ts`, `src/contributions.ts`, `packages/coding-agent/src/**`, and `docs/tools.md`.
|
|
57
|
+
|
|
58
|
+
### Core tool harness (shipped)
|
|
59
|
+
|
|
60
|
+
| Surface | Location | Behavior today |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `ToolDefinition` | `src/contracts.ts` | `name`, optional `description`, optional `parameters?: JsonObject`, `execute(args, context)` |
|
|
63
|
+
| `ToolRegistry` / `createToolRegistry` | `src/tools.ts` | Insertion-order registry; `duplicate: "replace" \| "error"` |
|
|
64
|
+
| `filterTools` | `src/tools.ts` | Exact-name allow/deny; deny wins; multiple filters require every non-empty allow |
|
|
65
|
+
| `dispatchToolCall` | `src/tools.ts` | Full lifecycle: lookup → filter → object-args check → permission → validate → execute |
|
|
66
|
+
| `ToolValidator` | `src/tools.ts` | `(tool, args, context) => void \| string \| ErrorInfo \| Promise<...>` |
|
|
67
|
+
| Runtime threading | `src/agents.ts` | `validate: options.validate ?? agent.config.validator` passed to `dispatchToolCall` |
|
|
68
|
+
| `PermissionPolicy` | `src/security.ts` | Keyed `kind:target:action` (tool dispatch uses `tool:<name>:execute`) |
|
|
69
|
+
| Abort | `ToolExecutionContext.signal` | Bridged from `RunOptions.signal` / run `AbortController` |
|
|
70
|
+
| Events | `AgentEvent` | `tool_execution_blocked`, `tool_execution_started`, `tool_execution_progress`, `tool_execution_finished`, `tool_execution_error` |
|
|
71
|
+
| Ledger | `RunLedger` | Optional `ToolCallRecord` rows with redaction |
|
|
72
|
+
| Middleware | `MiddlewareRegistry` | `tool_call` before permission/validate; `tool_result` after execute; dispatch re-checks lookup/filter/args after `tool_call` |
|
|
73
|
+
| Conformance | `src/testing/tool-conformance.ts` | Blocked-reason matrix + success path |
|
|
74
|
+
|
|
75
|
+
### Dispatch order (frozen — do not reorder)
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
middleware tool_call
|
|
79
|
+
→ registry lookup + filter + JSON-object args
|
|
80
|
+
→ assertPermission(tool:execute)
|
|
81
|
+
→ ToolValidator (optional)
|
|
82
|
+
→ tool.execute
|
|
83
|
+
→ middleware tool_result
|
|
84
|
+
→ events + ledger
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Blocked reasons today: `unknown_tool`, `tool_denied`, `invalid_arguments`, `permission_denied`, `validation_failed`.
|
|
88
|
+
|
|
89
|
+
### Agent loop tool execution (shipped)
|
|
90
|
+
|
|
91
|
+
| Surface | Location | Behavior today |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `singleShotLoop` | `src/agent-loops.ts` | Sequential `for (const call of calls) await dispatchToolCall(call)` per provider turn |
|
|
94
|
+
| `maxToolRounds` | `AgentConfig` / `RunOptions` → `LoopContext` | Default `1` in `RuntimeAgentSession.run()` |
|
|
95
|
+
| Transcript ordering | `src/agent-loops.ts` | Tool results appended in call order (Plan 053 R-002 fix shipped) |
|
|
96
|
+
| Parallelism | — | **None** — models may emit multiple calls; runtime executes one at a time |
|
|
97
|
+
|
|
98
|
+
### Extension and contribution boundaries (shipped)
|
|
99
|
+
|
|
100
|
+
| Surface | Location | Behavior today |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| `ExtensionAPI.registerTool` | `src/extensions.ts` | Contributes inert `ToolDefinition` to `ContributionRegistries.tools` |
|
|
103
|
+
| Extension setup permission | `createExtensionKernel({ permission })` | `extension:<name>:setup` before `setup()` |
|
|
104
|
+
| Activation | Host-owned | Contributions never auto-register into an active `ToolRegistry` or dispatch loop |
|
|
105
|
+
| Discovery | `src/node/contribution-discovery.ts` | Opt-in scan; no `import()`, no activation |
|
|
106
|
+
|
|
107
|
+
No MCP, subprocess transport, or remote tool protocol exists in core.
|
|
108
|
+
|
|
109
|
+
### Coding-agent package (shipped — policy gaps)
|
|
110
|
+
|
|
111
|
+
| Tool | Path/shell controls today | Output bounds | Gaps |
|
|
112
|
+
| --- | --- | --- | --- |
|
|
113
|
+
| `shell` | `spawnHook`, `commandPrefix`, `shellPath`; runs arbitrary `-c` command | `maxLines` / `maxBytes` tail + temp spill; timeout + abort kill process tree | No command allow/deny, approval, or sandbox |
|
|
114
|
+
| `read` | `resolveToCwd` / `resolveReadPath` — **absolute paths allowed outside cwd**; no symlink realpath containment | Text: `maxLines` / `maxBytes`; images: `maxImageBytes` (default 10 MB) stat-first reject + optional `transformImage` | No symlink realpath containment in base package |
|
|
115
|
+
| `write` | `resolveToCwd` — absolute paths allowed | Per-path `withFileMutationQueue` | No scope/approval |
|
|
116
|
+
| `edit` | Same as write | Same queue | No scope/approval |
|
|
117
|
+
|
|
118
|
+
Package performs **no** `PermissionPolicy`, `ToolValidator`, or trust checks of its own. Hosts must gate registration and dispatch.
|
|
119
|
+
|
|
120
|
+
### JSON Schema / `parameters` metadata
|
|
121
|
+
|
|
122
|
+
| Concern | Status |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| `ToolDefinition.parameters` | Stored and forwarded to providers; **not validated** by core |
|
|
125
|
+
| `ToolValidator` | Host function hook; Phase 25 threads through agent runtime |
|
|
126
|
+
| Standards-based schema validation | **Not shipped** — capability gap C-001 |
|
|
127
|
+
| Schema compile cache | **None** — every dispatch would re-validate if host validator is naive |
|
|
128
|
+
|
|
129
|
+
### MCP mapping (shipped — Task 3)
|
|
130
|
+
|
|
131
|
+
| MCP concept | Prism mapping (planned) |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `tools/list` `inputSchema` | `ToolDefinition.parameters` |
|
|
134
|
+
| `tools/call` arguments | `ToolCallContent.arguments` after provider parse |
|
|
135
|
+
| `tools/call` content blocks | `ToolResult.content` (`text`, `image`, resource → host-defined blocks) |
|
|
136
|
+
| Tool names | Prefixed `mcp:<serverId>:<name>` to avoid collisions |
|
|
137
|
+
| Transports | Stdio + Streamable HTTP via official MCP TypeScript SDK in optional package |
|
|
138
|
+
| Lifecycle | Explicit `connect` / `close`; list-changed invalidates cache |
|
|
139
|
+
|
|
140
|
+
## Gaps and chosen generic APIs (frozen for Task 1+)
|
|
141
|
+
|
|
142
|
+
### Decision table
|
|
143
|
+
|
|
144
|
+
| Concern | Option A | Option B | **Chosen** | Rationale |
|
|
145
|
+
| --- | --- | --- | --- | --- |
|
|
146
|
+
| JSON Schema validation | Mandatory core dependency | Host `ToolValidator` + optional standards adapter package | **B** | Matches Phase 25 seam; keeps core dependency-free |
|
|
147
|
+
| Validator interface | New `ToolArgumentValidator` replacing `ToolValidator` | `ToolArgumentValidator` factory → `ToolValidator` | **Factory → `ToolValidator`** | Reuses dispatch order and `validation_failed` events unchanged |
|
|
148
|
+
| Schema compile cache | Per-call in core | Once per tool/schema identity in adapter package | **Adapter cache** | Single compilation point; core stays O(validate) only |
|
|
149
|
+
| Parallel tool calls | Always parallel | Opt-in `toolConcurrency` on single-shot loop | **Opt-in** | Sequential remains default; transcript order preserved |
|
|
150
|
+
| Parallel ordering | Completion order | Original call index slots | **Index slots** | Deterministic history/events despite concurrent execute |
|
|
151
|
+
| MCP integration | Core JSON-RPC | Optional `@arnilo/prism-mcp` over official SDK | **Optional package** | No MCP types in core contracts |
|
|
152
|
+
| Coding safety | Extend `PermissionPolicy` with tool-name branches | Generic `ExecutionPolicy` + coding adapter package | **ExecutionPolicy in core** | Permission stays name-based; path/command/risk is structured metadata |
|
|
153
|
+
| Image bounds | Silent truncate | Stat-first reject + optional `transformImage` | **Reject + optional transform** | Avoid decompression bombs; no fake resize |
|
|
154
|
+
| Sandbox | Core process isolation | Pluggable sandbox adapter in optional package | **Pluggable adapter** | Prism does not claim OS isolation unless host provides it |
|
|
155
|
+
|
|
156
|
+
### Task 1 — JSON Schema validation — **shipped**
|
|
157
|
+
|
|
158
|
+
Core (`@arnilo/prism`):
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
export interface ToolArgumentValidationError {
|
|
162
|
+
readonly path?: string;
|
|
163
|
+
readonly message: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface ToolArgumentValidationResult {
|
|
167
|
+
readonly ok: boolean;
|
|
168
|
+
readonly errors?: readonly ToolArgumentValidationError[];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface ToolArgumentValidator {
|
|
172
|
+
validate(schema: JsonObject, value: unknown): ToolArgumentValidationResult;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function createToolParameterValidator(
|
|
176
|
+
validator: ToolArgumentValidator,
|
|
177
|
+
options?: { missingSchema?: "allow" | "reject" },
|
|
178
|
+
): ToolValidator;
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Optional package `@arnilo/prism-tool-validator-json-schema`:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { createJsonSchemaToolArgumentValidator } from "@arnilo/prism-tool-validator-json-schema";
|
|
185
|
+
|
|
186
|
+
createAgent({ model, validator: createJsonSchemaToolArgumentValidator() });
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Cache key:** stable `JSON.stringify(schema)` in adapter-owned `Map`. **Bounds:** configurable depth/properties/string/array limits before Ajv validation. **Security:** remote `$ref` rejected; prototype-pollution keys rejected in schemas and instances.
|
|
190
|
+
|
|
191
|
+
### Task 2 — Parallel tool execution — **shipped**
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
// AgentConfig.loop / RunOptions.loop (single-shot only)
|
|
195
|
+
| {
|
|
196
|
+
readonly strategy: "single-shot";
|
|
197
|
+
readonly toolConcurrency?: number; // default 1
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// LoopContext
|
|
201
|
+
readonly toolConcurrency: number;
|
|
202
|
+
|
|
203
|
+
export async function dispatchToolCallsInOrder(
|
|
204
|
+
calls: readonly ToolCallContent[],
|
|
205
|
+
ctx: LoopContext,
|
|
206
|
+
): Promise<void>;
|
|
207
|
+
export function resolveToolConcurrency(...): number;
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Bounded worker pool: at most `min(toolConcurrency, calls.length)` concurrent `dispatchToolCall` invocations per turn; history/store appends in call order. Abort checks between worker claims and before transcript append.
|
|
211
|
+
|
|
212
|
+
**Exclusive calls:** `ToolDefinition.exclusive: true` clamps only the containing turn to concurrency `1`. Coding-agent shell definitions carry this marker, matching coding-security's `ExecutionDecision.exclusive: true`; later non-exclusive turns use configured concurrency again. Custom tools whose policy can return an exclusive decision must also expose the static marker so the dispatcher can serialize before execution. Permission and validation still run at dispatch before each side effect.
|
|
213
|
+
|
|
214
|
+
### Task 3 — MCP client bridge — **shipped**
|
|
215
|
+
|
|
216
|
+
New optional package `@arnilo/prism-mcp`:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
export interface McpToolBridge {
|
|
220
|
+
readonly tools: readonly ToolDefinition[];
|
|
221
|
+
refresh(): Promise<void>;
|
|
222
|
+
close(): Promise<void>;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export async function connectMcpTools(options: {
|
|
226
|
+
readonly serverId: string;
|
|
227
|
+
readonly transport: McpStdioTransport | McpStreamableHttpTransport;
|
|
228
|
+
readonly namePrefix?: string;
|
|
229
|
+
readonly listCacheTtlMs?: number;
|
|
230
|
+
readonly callTimeoutMs?: number;
|
|
231
|
+
readonly maxResultBytes?: number;
|
|
232
|
+
readonly signal?: AbortSignal;
|
|
233
|
+
}): Promise<McpToolBridge>;
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Bridge `execute` delegates to MCP `tools/call`, maps content to `ToolResult`, and relies on core `dispatchToolCall` for permission + JSON Schema validation when the host registers the returned tools.
|
|
237
|
+
|
|
238
|
+
### Task 4 — Execution policy — **shipped**
|
|
239
|
+
|
|
240
|
+
Core (`@arnilo/prism`) adds a structured pre-execution seam distinct from name-based `PermissionPolicy`:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
export interface ExecutionAction {
|
|
244
|
+
readonly kind: "shell" | "read" | "write" | "edit" | string;
|
|
245
|
+
readonly operation: string;
|
|
246
|
+
readonly paths?: readonly string[];
|
|
247
|
+
readonly command?: string;
|
|
248
|
+
readonly risk?: "low" | "medium" | "high";
|
|
249
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface ExecutionPolicy {
|
|
253
|
+
check(action: ExecutionAction): ExecutionDecision | Promise<ExecutionDecision>;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface ExecutionDecision {
|
|
257
|
+
readonly allowed: boolean;
|
|
258
|
+
readonly reason?: string;
|
|
259
|
+
readonly modified?: Partial<ExecutionAction>;
|
|
260
|
+
readonly exclusive?: boolean;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface ToolDefinition {
|
|
264
|
+
// ...
|
|
265
|
+
readonly exclusive?: boolean;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export async function assertExecutionAllowed(
|
|
269
|
+
policy: ExecutionPolicy | undefined,
|
|
270
|
+
action: ExecutionAction,
|
|
271
|
+
): Promise<ExecutionAction>;
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`@arnilo/prism-coding-agent` tools call `executionPolicy.check()` **inside** `execute` before side effects (after dispatch permission + argument validation). Optional `@arnilo/prism-coding-security` supplies `createCodingApprovalPolicy({ roots, approve, readOnly, commandRules })` with realpath containment, default deny patterns, metacharacter approval, approval caching, and `createSandboxBashOperations()` for pluggable sandbox backends.
|
|
275
|
+
|
|
276
|
+
**Permission vs execution policy:** `PermissionPolicy` remains `tool:<name>:execute` at dispatch. `ExecutionPolicy` adds command/path context for coding tools only — no MCP-specific branches in core.
|
|
277
|
+
|
|
278
|
+
### Task 5 — Image read bounds — **shipped**
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
export const DEFAULT_MAX_IMAGE_BYTES = 10_000_000;
|
|
282
|
+
|
|
283
|
+
export interface TransformImageInput {
|
|
284
|
+
readonly buffer: Buffer;
|
|
285
|
+
readonly mimeType: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export type TransformImage = (input: TransformImageInput) => Promise<Buffer>;
|
|
289
|
+
|
|
290
|
+
export interface ReadToolOptions {
|
|
291
|
+
readonly maxImageBytes?: number; // default DEFAULT_MAX_IMAGE_BYTES
|
|
292
|
+
readonly transformImage?: TransformImage;
|
|
293
|
+
/** @deprecated Use transformImage instead; ignored when transformImage is absent. */
|
|
294
|
+
readonly autoResizeImages?: boolean;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Reject oversize images by `stat` before full read where possible; re-check `buffer.length` after read and after `transformImage`. MIME from magic bytes only (existing behavior). `transformImage` is host-owned; base package stays free of image-processing deps. Implemented in `packages/coding-agent/src/read.ts`.
|
|
299
|
+
|
|
300
|
+
## Conformance and threat-model matrix
|
|
301
|
+
|
|
302
|
+
Tasks 1–6 must pass this matrix (unit tests + `assertToolDispatchConforms` extensions where applicable):
|
|
303
|
+
|
|
304
|
+
| # | Scenario | Expected behavior |
|
|
305
|
+
| ---: | --- | --- |
|
|
306
|
+
| 1 | Args violate declared JSON Schema | `validation_failed`; handler not invoked |
|
|
307
|
+
| 2 | Schema compile cache | Second call with same tool/schema does not recompile |
|
|
308
|
+
| 3 | Malformed schema / remote `$ref` | Adapter rejects at compile time; no handler invocation |
|
|
309
|
+
| 4 | Prototype-pollution keys in args | Rejected before `execute` |
|
|
310
|
+
| 5 | Parallel calls, concurrency N | ≤ N in flight; results/transcript in call order |
|
|
311
|
+
| 6 | Parallel abort mid-turn | Pending calls cancelled; `context.signal` observed |
|
|
312
|
+
| 7 | Permission deny during parallel batch | Blocked call returns error slot; order preserved |
|
|
313
|
+
| 8 | MCP tool name collision | Prefix namespaces remote names |
|
|
314
|
+
| 9 | MCP oversized result / timeout | Bounded error `ToolResult`; transport closed |
|
|
315
|
+
| 10 | MCP list-changed | Cache invalidated; refresh required |
|
|
316
|
+
| 11 | Shell path outside roots | `ExecutionPolicy` denies before spawn |
|
|
317
|
+
| 12 | Symlink escape under root | Realpath containment denies |
|
|
318
|
+
| 13 | Shell metacharacters / escalation pattern | Policy denies or requires approval |
|
|
319
|
+
| 14 | Approval denied / timeout | Abortable wait; no side effect |
|
|
320
|
+
| 15 | Read image over `maxImageBytes` | Clear error; no base64 in result |
|
|
321
|
+
| 16 | Extension-contributed tool | Still requires host activation + dispatch gates |
|
|
322
|
+
|
|
323
|
+
### Threat model summary
|
|
324
|
+
|
|
325
|
+
| Threat | Owner | Mitigation |
|
|
326
|
+
| --- | --- | --- |
|
|
327
|
+
| Untrusted JSON Schema from model/MCP | Adapter + host | Compile bounds, no remote refs, pollution key rejection |
|
|
328
|
+
| Untrusted MCP server output | MCP package | Byte limits, redaction, explicit trusted transport config |
|
|
329
|
+
| Subprocess via MCP stdio | Host | Explicit command/env/cwd; no auto-launch |
|
|
330
|
+
| HTTP SSRF via MCP | Host + docs | URL allow-list guidance; no implicit discovery |
|
|
331
|
+
| Tool-name shadowing | Host registry | `duplicate: "error"`; MCP prefix |
|
|
332
|
+
| Command injection (shell tool) | Execution policy + host | Approval, allow/deny rules, optional sandbox adapter |
|
|
333
|
+
| Path escape (read/write/edit) | Execution policy | Realpath roots; deny absolute out-of-scope |
|
|
334
|
+
| Symlink escape | Execution policy | `realpath` containment before read/write |
|
|
335
|
+
| Decompression bomb (images) | Read tool | `maxImageBytes` + stat-first reject |
|
|
336
|
+
| Unbounded tool output | Coding tools (shipped) | `maxLines`/`maxBytes` accumulators |
|
|
337
|
+
| Cancellation | Core (shipped) | `AbortSignal` on context; shell kills process tree |
|
|
338
|
+
|
|
339
|
+
## Extension and configuration notes
|
|
340
|
+
|
|
341
|
+
Core remains dependency-free: validators, MCP bridges, coding policy, sandboxes, and image transforms are optional host-wired adapters. Register mapped tools through the normal registry and dispatch path; do not bypass permission or validation gates.
|
|
342
|
+
|
|
343
|
+
## Security and performance notes
|
|
344
|
+
|
|
345
|
+
| Concern | Target |
|
|
346
|
+
| --- | --- |
|
|
347
|
+
| Schema compilation | Once per `(toolName, schemaHash)` in adapter; not per dispatch |
|
|
348
|
+
| Validation hot path | O(schema size + instance size) with configured depth/property caps |
|
|
349
|
+
| Parallelism | At most `toolConcurrency` concurrent `execute` calls per turn; queue bounded by calls in that turn |
|
|
350
|
+
| MCP list cache | TTL + explicit invalidation on `list_changed` |
|
|
351
|
+
| Execution policy | Sync fast-path for allow rules; async approval bounded + abortable |
|
|
352
|
+
| Image reject | `stat` before read when size known |
|
|
353
|
+
|
|
354
|
+
## Related APIs
|
|
355
|
+
|
|
356
|
+
- [MCP client bridge](mcp-tools.md): `@arnilo/prism-mcp` package usage and security
|
|
357
|
+
- [Tools](tools.md): registry, dispatch, `ToolValidator`, events, ledger
|
|
358
|
+
- [Tool conformance](tool-conformance.md): blocked-reason matrix
|
|
359
|
+
- [Agent loops](agent-loops.md): single-shot loop and transcript ordering
|
|
360
|
+
- [Host security guide](host-security.md): permission, trust, validation checklist
|
|
361
|
+
- [Coding agent tools](coding-agent-tools.md): first-party tool package behavior and limits
|
|
362
|
+
- [Extensions](extensions.md): inert tool contributions
|
|
363
|
+
- [Review coverage (2026-07-14)](review-coverage-2026-07-14.md): finding → plan traceability
|
|
364
|
+
|
|
365
|
+
## Task ownership map
|
|
366
|
+
|
|
367
|
+
| Finding / capability | Plan 055 task | Primitive / doc |
|
|
368
|
+
| --- | --- | --- |
|
|
369
|
+
| C-001 JSON Schema tool validation | 1 | **shipped** — `ToolArgumentValidator`, `createToolParameterValidator`, `@arnilo/prism-tool-validator-json-schema` |
|
|
370
|
+
| C-003 MCP client bridge | 3 | **shipped** — `@arnilo/prism-mcp` |
|
|
371
|
+
| C-006 Approval/sandbox for coding tools | 4 | **shipped** — `ExecutionPolicy`, `@arnilo/prism-coding-security` |
|
|
372
|
+
| C-007 Parallel tool execution | 2 | **shipped** — `toolConcurrency`, `dispatchToolCallsInOrder`, `resolveToolConcurrency` |
|
|
373
|
+
| R-011 Image size / resize option | 5 | **shipped** — `maxImageBytes`, `transformImage`, `DEFAULT_MAX_IMAGE_BYTES` on read tool |
|
|
374
|
+
| Phase verification | 6 | **verified** — `npm run sdk:ready` + audit + threat-model fixtures; evidence in review coverage |
|
package/docs/tools.md
CHANGED
|
@@ -182,13 +182,49 @@ const agent = createAgent({
|
|
|
182
182
|
await session.run(input, { validate: (_t, args) => args.dry ? "dry-run blocked" : undefined });
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
+
### JSON Schema validation (optional package)
|
|
186
|
+
|
|
187
|
+
Core exposes schema-agnostic adapters that wrap into the same `ToolValidator` seam:
|
|
188
|
+
|
|
189
|
+
- `ToolArgumentValidator` — `validate(schema, value)` with structured errors
|
|
190
|
+
- `createToolParameterValidator(adapter, { missingSchema?: "allow" | "reject" })` — maps `tool.parameters` through the adapter
|
|
191
|
+
|
|
192
|
+
For standards-based validation install `@arnilo/prism-tool-validator-json-schema`:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { createAgent } from "@arnilo/prism";
|
|
196
|
+
import { createJsonSchemaToolArgumentValidator } from "@arnilo/prism-tool-validator-json-schema";
|
|
197
|
+
|
|
198
|
+
const agent = createAgent({
|
|
199
|
+
model,
|
|
200
|
+
provider,
|
|
201
|
+
tools,
|
|
202
|
+
validator: createJsonSchemaToolArgumentValidator(),
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
By default tools without `parameters` skip schema validation (`missingSchema: "allow"`). Use `missingSchema: "reject"` when every active tool must declare a schema. The adapter compiles each schema once (in-memory cache), rejects remote `$ref`, prototype-pollution keys, and oversized/deep argument values before `tool.execute()`. See [Tool execution primitives](tool-execution-primitives.md).
|
|
207
|
+
|
|
208
|
+
### Parallel tool execution (single-shot loop)
|
|
209
|
+
|
|
210
|
+
Opt in through `loop.toolConcurrency` on `AgentConfig` / `RunOptions` (single-shot strategy only). Default is `1` (sequential). Independent calls from one provider turn run concurrently up to the limit; transcript rows and `appendMessage` stay in original call order. Each call still uses `dispatchToolCall` (permission, validation, abort signal). See [Agent loops](agent-loops.md).
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
await session.run(input, {
|
|
214
|
+
maxToolRounds: 3,
|
|
215
|
+
loop: { strategy: "single-shot", toolConcurrency: 4 },
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
185
219
|
## Security and performance notes
|
|
186
220
|
|
|
187
221
|
- Tool lookup uses a `Map` for O(1) name lookup. Strict duplicate mode adds one O(1) `Map.has()` check during registration only.
|
|
188
222
|
- Filtering is exact-name matching over the provided tools and rules.
|
|
189
223
|
- Unknown, denied, malformed, duplicate-in-strict-mode, and validator-blocked calls fail closed.
|
|
190
224
|
- Tool arguments must be JSON object-shaped before validation or execution.
|
|
191
|
-
- `parameters` is pass-through metadata; hosts own schema interpretation
|
|
225
|
+
- `parameters` is pass-through metadata; hosts own schema interpretation unless they wire a `ToolValidator` or the optional JSON Schema package.
|
|
226
|
+
- `exclusive: true` on a `ToolDefinition` makes a single-shot provider turn containing that tool sequential even when `toolConcurrency > 1`. Use it when an execution policy can classify the tool as exclusive before side effects.
|
|
227
|
+
- Optional JSON Schema validation compiles each distinct `tool.parameters` once in the adapter package; dispatch itself adds no schema dependency.
|
|
192
228
|
- Prism does not sandbox host tools and does not include built-in app tools.
|
|
193
229
|
- Contribution registration and registry/filter calls do not perform provider calls, credential resolution, resource loading, network, filesystem discovery, or tool execution.
|
|
194
230
|
- Dispatch performs explicit in-memory checks and executes only the selected host-active tool; it adds no retries, queues, timers, or new dependencies.
|
|
@@ -203,6 +239,8 @@ await session.run(input, { validate: (_t, args) => args.dry ? "dry-run blocked"
|
|
|
203
239
|
- [Middleware hooks](middleware-hooks.md): `tool_call` and `tool_result` middleware used during dispatch.
|
|
204
240
|
- [Credentials and redaction](credentials-and-redaction.md): redaction helpers used for tool execution errors.
|
|
205
241
|
- [Observational memory compaction package](compaction-observational-memory.md): optional exact-id recall tool factory.
|
|
242
|
+
- [Tool execution primitives](tool-execution-primitives.md): JSON Schema adapter, parallelism, MCP bridge, and execution-policy designs.
|
|
243
|
+
- [MCP client bridge](mcp-tools.md): optional `@arnilo/prism-mcp` remote tool mapping.
|
|
206
244
|
- [Coding agent tools](coding-agent-tools.md): optional first-party `@arnilo/prism-coding-agent` `shell`/`read`/`write`/`edit` tools a host registers into this harness.
|
|
207
245
|
|
|
208
246
|
`DispatchToolCallOptions.permission` can provide a `PermissionPolicy`; denial emits `tool_execution_blocked` before validation or `execute()`. Middleware cannot bypass this guard. `AgentConfig.validator`/`RunOptions.validate` run after this guard; their output is redacted through the active `SecretRedactor`. Prism does not sandbox tools. See [Security/auth/trust](settings-auth-trust-security.md).
|