@arnilo/prism 0.2.9 → 0.3.1
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 +39 -0
- package/README.md +12 -5
- package/dist/agent-loops.js +45 -8
- package/dist/agent-session/helpers.js +2 -2
- package/dist/cache-helpers.d.ts +11 -0
- package/dist/cache-helpers.js +29 -5
- package/dist/cli-provider-add.js +2 -1
- package/dist/context-budget.js +9 -6
- package/dist/contracts-core/agent.d.ts +2 -0
- package/dist/contracts-core/provider.d.ts +2 -0
- package/dist/contracts-protocol.d.ts +31 -1
- package/dist/delegated-agent-step.d.ts +20 -0
- package/dist/delegated-agent-step.js +99 -0
- package/dist/event-multiplexer.js +0 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -2
- package/dist/input.js +19 -11
- package/dist/node/session-store-jsonl.js +7 -3
- package/dist/providers/openai-compatible.js +2 -1
- package/dist/providers/openai-primitives.js +2 -1
- package/dist/providers/schema.d.ts +7 -0
- package/dist/providers/schema.js +25 -0
- package/dist/testing/provider-conformance.d.ts +10 -0
- package/dist/testing/provider-conformance.js +37 -0
- package/dist/trim-trailing-slashes.d.ts +8 -0
- package/dist/trim-trailing-slashes.js +14 -0
- package/docs/0.1.0-readiness.md +8 -8
- package/docs/acp.md +5 -3
- package/docs/ag-ui.md +6 -2
- package/docs/agent-events.md +8 -1
- package/docs/agent-loops.md +3 -0
- package/docs/agent-session-runtime.md +1 -0
- package/docs/antigravity-agent.md +207 -0
- package/docs/browser-automation.md +1 -0
- package/docs/coding-agent-tools.md +32 -4
- package/docs/computer-use-linux.md +122 -0
- package/docs/database-persistence.md +1 -1
- package/docs/device-adapters.md +4 -3
- package/docs/graft.md +125 -0
- package/docs/host-security.md +3 -1
- package/docs/index.md +21 -10
- package/docs/input-and-prompt-assembly.md +11 -6
- package/docs/instruction-injection.md +1 -1
- package/docs/mcp-tools.md +2 -1
- package/docs/migration.md +25 -2
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/obscura.md +175 -0
- package/docs/observability.md +21 -1
- package/docs/performance.md +58 -4
- package/docs/ponytail.md +1 -1
- package/docs/provider-caching.md +13 -11
- package/docs/provider-conformance.md +6 -0
- package/docs/provider-packages.md +1 -1
- package/docs/provider-primitives.md +15 -2
- package/docs/providers/ai-sdk.md +1 -1
- package/docs/providers/anthropic.md +1 -1
- package/docs/providers/azure.md +1 -0
- package/docs/providers/bedrock.md +1 -0
- package/docs/providers/kimi.md +2 -1
- package/docs/providers/openai.md +19 -7
- package/docs/providers/opencode-go.md +3 -1
- package/docs/providers/openrouter.md +4 -3
- package/docs/providers/vertex.md +1 -0
- package/docs/public-contracts.md +2 -1
- package/docs/rag.md +55 -8
- package/docs/release-and-install.md +105 -25
- package/docs/server.md +1 -0
- package/docs/supervisors.md +3 -2
- package/docs/system-prompts.md +1 -1
- package/docs/tools.md +1 -1
- package/docs/web-tools.md +2 -0
- package/docs/wiki.md +140 -0
- package/docs/workflows.md +4 -3
- package/docs/working-and-semantic-memory.md +20 -0
- package/package.json +14 -5
- package/docs/api-page-template.md +0 -32
- package/docs/release-0.2.7-evidence.md +0 -514
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Antigravity delegated agent
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-antigravity-agent` provides a delegated agent adapter for the official [Google Antigravity CLI (`agy`)](https://github.com/google/antigravity). It enables Prism applications to delegate complex, multi-step coding tasks to an authenticated Antigravity CLI runner while exposing host-owned Prism tools, resources, and prompts over a per-run Model Context Protocol (MCP) server.
|
|
6
|
+
|
|
7
|
+
The package handles the end-to-end delegated execution lifecycle:
|
|
8
|
+
- Spawns the official headless CLI (`agy --agent <name> --workspace <dir>`) as a managed subprocess.
|
|
9
|
+
- Starts a run-bound loopback HTTP MCP server (`http://127.0.0.1:<port>/mcp`) authorized with an ephemeral Bearer token.
|
|
10
|
+
- Writes ephemeral workspace configuration (`.agents/mcp_config.json` and custom agent instructions) with automatic backup and fail-safe restoration.
|
|
11
|
+
- Parses the CLI's NDJSON output stream and projects steps into standard Prism `AgentEvent`s and [AG-UI](ag-ui.md) timeline activities.
|
|
12
|
+
- Persists and resumes multi-turn conversations via `--conversation <id>`.
|
|
13
|
+
- Provides an optional `createAntigravityDelegationTool` for Prism [supervisors](supervisors.md) and orchestrating agents.
|
|
14
|
+
|
|
15
|
+
Prism does not manage Google OAuth tokens, cookies, or credentials; the host environment owns the official `agy` binary and interactive authentication state (`agy login` / Google AI Pro subscription).
|
|
16
|
+
|
|
17
|
+
## When to use it
|
|
18
|
+
|
|
19
|
+
Use `@arnilo/prism-antigravity-agent` when:
|
|
20
|
+
- You want to delegate autonomous coding sessions to Google Antigravity while exposing host-owned Prism tools and capabilities via MCP.
|
|
21
|
+
- You need structured event streaming, token telemetry, and [AG-UI](ag-ui.md) visual timeline integration for Antigravity executions.
|
|
22
|
+
- You are orchestrating multi-agent workflows where a Prism supervisor or coding agent needs to delegate specialized subtasks to Antigravity.
|
|
23
|
+
- You want conversation continuation across multiple user turns in a persistent session.
|
|
24
|
+
|
|
25
|
+
Do **not** use it:
|
|
26
|
+
- As a generic LLM model provider. For direct Gemini API or Vertex AI foundation model inference without an autonomous loop, use [`@arnilo/prism-provider-google`](providers/google.md) or [`@arnilo/prism-provider-vertex`](providers/vertex.md).
|
|
27
|
+
- If you require step-by-step turn replacement of Antigravity's internal model loop, compaction, or planning strategy.
|
|
28
|
+
- If you require unreleased raw internal chain-of-thought text. Antigravity reasoning effort is projected as token counts and timeline activity steps, not raw hidden thoughts.
|
|
29
|
+
|
|
30
|
+
## Inputs / request
|
|
31
|
+
|
|
32
|
+
`createAntigravityCliAgent(options)` accepts agent configuration:
|
|
33
|
+
|
|
34
|
+
| Field | Type | Default | Purpose |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| `command` | `string` | `"agy"` | Path to the official `agy` executable on the host. |
|
|
37
|
+
| `args` | `readonly string[]` | `[]` | Additional command-line arguments passed to the CLI. |
|
|
38
|
+
| `cwd` | `string` | `process.cwd()` | Working directory for the runner process. |
|
|
39
|
+
| `env` | `Record<string, string | undefined>` | `process.env` | Process environment variables. |
|
|
40
|
+
| `timeoutMs` | `number` | `300000` (5m) | Maximum process execution time. |
|
|
41
|
+
| `toolPolicy` | `AntigravityToolPolicy` | `"hybrid"` | Built-in CLI tool permissions (`"hybrid"`, `"all"`, `"none"`, or custom). |
|
|
42
|
+
| `tools` | `ToolDefinition[]` | `[]` | Prism tools exposed to the agent via loopback MCP. |
|
|
43
|
+
| `resources` | `ResourceDefinition[]` | `[]` | Prism resources exposed via loopback MCP. |
|
|
44
|
+
| `prompts` | `PromptDefinition[]` | `[]` | Prism prompt templates exposed via loopback MCP. |
|
|
45
|
+
| `exposure` | `AntigravityMcpExposure` | auto-created | Custom MCP server exposure handle if sharing an external server. |
|
|
46
|
+
| `conversationStore` | `AntigravityConversationStore` | in-memory | Store for persisting conversation IDs across turns. |
|
|
47
|
+
| `redactor` | `SecretRedactor` | auto | Secret redactor applied to events and process output. |
|
|
48
|
+
| `agentName` | `string` | `"prism-agent"` | Ephemeral agent definition identifier. |
|
|
49
|
+
| `systemPrompt` | `string` | built-in instructions | Custom instructions appended to the agent definition. |
|
|
50
|
+
|
|
51
|
+
`agent.run(runOptions)` executes a prompt run:
|
|
52
|
+
|
|
53
|
+
| Field | Type | Default | Purpose |
|
|
54
|
+
| --- | --- | --- | --- |
|
|
55
|
+
| `prompt` | `string` | required | User task or instruction for Antigravity. |
|
|
56
|
+
| `workspace` | `string` | `agent.cwd` | Target workspace directory path for file modifications. |
|
|
57
|
+
| `sessionId` | `string` | auto-generated | Prism session identifier for conversation persistence. |
|
|
58
|
+
| `branchId` | `string` | `"main"` | Branch identifier for conversation isolation. |
|
|
59
|
+
| `conversationId` | `string` | auto-resolved | Existing Antigravity conversation ID to resume. |
|
|
60
|
+
| `signal` | `AbortSignal` | omitted | Cancellation signal to abort execution and clean up. |
|
|
61
|
+
| `eventSink` | `(event: AgentEvent) => void` | omitted | Real-time event listener for streaming UI updates. |
|
|
62
|
+
| `toolPolicy` | `AntigravityToolPolicy` | agent default | Per-run override for built-in tool policy. |
|
|
63
|
+
|
|
64
|
+
## Outputs / response / events
|
|
65
|
+
|
|
66
|
+
`agent.run()` returns a promise resolving to an `AntigravityRunResult`:
|
|
67
|
+
|
|
68
|
+
| Field | Type | Purpose |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| `text` | `string` | Final synthesized response text from the Antigravity CLI. |
|
|
71
|
+
| `conversationId` | `string` | Antigravity conversation ID for subsequent multi-turn resumption. |
|
|
72
|
+
| `exitCode` | `number` | Process exit status code (0 for success). |
|
|
73
|
+
| `durationMs` | `number` | Total elapsed execution time in milliseconds. |
|
|
74
|
+
| `events` | `readonly AgentEvent[]` | Complete sequence of projected Prism events emitted during the run. |
|
|
75
|
+
| `usage` | `UsageReport` | Aggregated prompt, completion, total, and thinking token counts. |
|
|
76
|
+
| `subagents` | `readonly AntigravitySubagentSummary[]` | Subagents spawned and completed during execution. |
|
|
77
|
+
|
|
78
|
+
### Streamed events
|
|
79
|
+
|
|
80
|
+
The runner emits standardized Prism `AgentEvent` objects to the provided `eventSink`:
|
|
81
|
+
- `delegated_agent_step`: High-level step progression with step name, status, and duration.
|
|
82
|
+
- `message_delta`: Incremental response text chunks.
|
|
83
|
+
- `tool_call_start` / `tool_call_delta` / `tool_call_result`: MCP tool invocations and results.
|
|
84
|
+
- `agent_thought_chunk`: Thinking activity indicators with token counts.
|
|
85
|
+
- `usage`: Token usage telemetry updates.
|
|
86
|
+
- `subagent_spawn` / `subagent_finish`: Internal subagent hierarchy lifecycle.
|
|
87
|
+
|
|
88
|
+
## Request/response example
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"prompt": "Inspect the repository and add unit tests for the auth helper.",
|
|
93
|
+
"workspace": "/home/user/project",
|
|
94
|
+
"sessionId": "session-101",
|
|
95
|
+
"toolPolicy": "hybrid"
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"text": "Added 4 unit tests covering token refresh and validation in auth.test.ts.",
|
|
102
|
+
"conversationId": "conv_9876543210",
|
|
103
|
+
"exitCode": 0,
|
|
104
|
+
"durationMs": 4250,
|
|
105
|
+
"usage": {
|
|
106
|
+
"promptTokens": 1520,
|
|
107
|
+
"completionTokens": 380,
|
|
108
|
+
"totalTokens": 1900,
|
|
109
|
+
"thinkingTokens": 640
|
|
110
|
+
},
|
|
111
|
+
"subagents": []
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Implementation example
|
|
116
|
+
|
|
117
|
+
### Direct runner
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { createAntigravityCliAgent } from "@arnilo/prism-antigravity-agent";
|
|
121
|
+
import { createReadTool, createWriteTool } from "@arnilo/prism-coding-agent";
|
|
122
|
+
|
|
123
|
+
// Configure agent with host-owned Prism tools exposed over MCP
|
|
124
|
+
const agent = createAntigravityCliAgent({
|
|
125
|
+
command: "agy",
|
|
126
|
+
tools: [
|
|
127
|
+
createReadTool({ workspaceRoot: "/home/user/project" }),
|
|
128
|
+
createWriteTool({ workspaceRoot: "/home/user/project" }),
|
|
129
|
+
],
|
|
130
|
+
toolPolicy: "hybrid", // Built-in bash/editor tools enabled; Prism MCP tools added
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Run a task with real-time event streaming
|
|
134
|
+
const result = await agent.run({
|
|
135
|
+
prompt: "Refactor error handling in src/utils.ts to use typed AppError",
|
|
136
|
+
workspace: "/home/user/project",
|
|
137
|
+
sessionId: "session-42",
|
|
138
|
+
eventSink: (event) => {
|
|
139
|
+
if (event.type === "message_delta") {
|
|
140
|
+
process.stdout.write(event.delta.text);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
console.log(`\nCompleted in ${result.durationMs}ms with conversation ${result.conversationId}`);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Supervisor delegation tool
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { createSupervisor } from "@arnilo/prism-supervisor";
|
|
152
|
+
import {
|
|
153
|
+
createAntigravityCliAgent,
|
|
154
|
+
createAntigravityDelegationTool,
|
|
155
|
+
} from "@arnilo/prism-antigravity-agent";
|
|
156
|
+
|
|
157
|
+
const antigravity = createAntigravityCliAgent({
|
|
158
|
+
workspace: "/home/user/project",
|
|
159
|
+
toolPolicy: "hybrid",
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const supervisor = createSupervisor({
|
|
163
|
+
tools: [
|
|
164
|
+
createAntigravityDelegationTool({
|
|
165
|
+
agent: antigravity,
|
|
166
|
+
name: "delegate_to_antigravity",
|
|
167
|
+
description: "Delegate complex coding tasks to Google Antigravity CLI",
|
|
168
|
+
}),
|
|
169
|
+
],
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Extension and configuration notes
|
|
174
|
+
|
|
175
|
+
### Ephemeral workspace configuration
|
|
176
|
+
|
|
177
|
+
During each execution, the adapter dynamically constructs:
|
|
178
|
+
1. `.agents/mcp_config.json`: Configures the local loopback MCP server endpoint (`http://127.0.0.1:<port>/mcp`) and authorization header.
|
|
179
|
+
2. `.agents/agents/<name>/agent.md`: Configures custom instructions and tool permissions.
|
|
180
|
+
|
|
181
|
+
If pre-existing configuration files exist in `.agents/`, they are backed up before the run and restored atomically upon completion, failure, or cancellation.
|
|
182
|
+
|
|
183
|
+
### Tool policies
|
|
184
|
+
|
|
185
|
+
The `toolPolicy` setting controls built-in CLI capabilities:
|
|
186
|
+
- `"hybrid"` (default): Enables built-in editor, terminal, and search tools while exposing configured Prism MCP tools.
|
|
187
|
+
- `"all"`: Enables all built-in CLI tools and MCP tools.
|
|
188
|
+
- `"none"`: Disables built-in tools; the agent relies exclusively on exposed Prism MCP tools.
|
|
189
|
+
- Custom object `{ allow?: string[], deny?: string[] }`: Explicit allow/deny lists for fine-grained governance.
|
|
190
|
+
|
|
191
|
+
## Security and performance notes
|
|
192
|
+
|
|
193
|
+
- **Host-owned authentication**: Prism does not read, store, or forward Google credentials. Authentication state resides in the official `agy` CLI's session store managed via `agy login`.
|
|
194
|
+
- **Loopback isolation**: The ephemeral MCP HTTP server binds exclusively to `127.0.0.1` on a dynamically assigned port, secured with a cryptographically random Bearer token.
|
|
195
|
+
- **Fail-safe cleanup**: Workspace configuration files and HTTP listener ports are cleaned up in `finally` blocks under all exit conditions, including `SIGINT`, timeouts, and unhandled errors.
|
|
196
|
+
- **Secret redaction**: All stdout, stderr, event payloads, and tool arguments are processed through Prism's secret redactor before event emission.
|
|
197
|
+
- **Terms and quota**: Antigravity CLI execution utilizes Google AI Pro subscription quotas through the authenticated official binary. Host operators should verify compliance with their organization's terms of service.
|
|
198
|
+
|
|
199
|
+
## Related APIs
|
|
200
|
+
|
|
201
|
+
- [Frontend interoperability (AG-UI and ACP)](ag-ui.md): Connect Antigravity event streams to AG-UI and web interfaces.
|
|
202
|
+
- [MCP client bridge and server exposure](mcp-tools.md): Core Model Context Protocol integration in Prism.
|
|
203
|
+
- [Supervisor delegation](supervisors.md): Hierarchical multi-agent delegation patterns.
|
|
204
|
+
- [Coding agent tools](coding-agent-tools.md): Native Prism file, edit, and terminal tools.
|
|
205
|
+
- [Google Gemini provider](providers/google.md): Direct Gemini API model inference without CLI delegation.
|
|
206
|
+
- [Google Vertex AI provider](providers/vertex.md): Enterprise cloud Vertex AI model inference.
|
|
207
|
+
- [Public contracts](public-contracts.md): Core message, event, tool, and session types.
|
|
@@ -122,6 +122,7 @@ Default tests use fake Playwright APIs only. Protected live gate: `PRISM_LIVE_PL
|
|
|
122
122
|
|
|
123
123
|
## Related APIs
|
|
124
124
|
|
|
125
|
+
- [Obscura browser engine](obscura.md): optional host-installed Obscura headless browser connected with `chromium.connectOverCDP` through `connectObscuraCdp` — its returned browser plugs directly into `createBrowserTools`/`createBrowserManager` as the host-supplied Playwright browser; pages on one Obscura worker share one V8 isolate, and screenshots/PDF need a render-enabled build.
|
|
125
126
|
- [Tools](tools.md): registry, exclusive dispatch, validation, and ledger.
|
|
126
127
|
- [Web search, fetch, and extraction](web-tools.md): preferred non-interactive retrieval path.
|
|
127
128
|
- [Guardrails](guardrails.md): untrusted external content handling.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
| `createReadTool(cwd, options?)` | `read` tool: read a text or image file into `TextContent` / `ImageContent`. |
|
|
11
11
|
| `createWriteTool(cwd, options?)` | `write` tool: create or overwrite a file, creating parent directories. |
|
|
12
12
|
| `createEditTool(cwd, options?)` | `edit` tool: precise exact-then-fuzzy text replacement in an existing file. |
|
|
13
|
+
| `createAcpFilesystemOperations(client)` | Map an ACP-shaped text-file client to `read`/`write`/`edit` operations; no local-disk fallback, binary/image support, or remote `mkdir`. |
|
|
13
14
|
| `createRepoListTool(cwd, options?)` | `repo_list` tool: bounded deterministic repository listing. |
|
|
14
15
|
| `createRepoSearchTool(cwd, options?)` | `repo_search` tool: bounded literal text search (`outputMode`: content / files_with_matches / count). |
|
|
15
16
|
| `createGlobTool(cwd, options?)` | `glob` tool: bounded filename-pattern match (`*` / `?` / `**`; opt-in bounded `{a,b}` brace expansion via `braceExpansion`). |
|
|
@@ -50,6 +51,23 @@ const tools = createToolRegistry(createCodingTools(process.cwd()));
|
|
|
50
51
|
|
|
51
52
|
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
53
|
|
|
54
|
+
### ACP editor-buffer operations
|
|
55
|
+
|
|
56
|
+
`createAcpFilesystemOperations` adapts any client with `readTextFile({ path, line?, limit? })` and `writeTextFile({ path, content })` methods to the `ReadOperations`, `WriteOperations`, and `EditOperations` seams. All reads and writes stay client-backed; `mkdir` is a no-op, `statFile` measures a bounded UTF-8 text read, and image MIME detection is always `null`.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { createAcpFilesystemOperations, createCodingTools } from "@arnilo/prism-coding-agent";
|
|
60
|
+
|
|
61
|
+
const operations = createAcpFilesystemOperations(clientFilesystem);
|
|
62
|
+
const tools = createCodingTools(cwd, {
|
|
63
|
+
read: { operations: operations.read },
|
|
64
|
+
write: { operations: operations.write },
|
|
65
|
+
edit: { operations: operations.edit },
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This is an editor-buffer adapter, not a repository backend: `repo_list`, `repo_search`, `glob`, `delete`, and `move` remain disk-backed unless separately overridden. Binary/image and document reads are not silently delegated to local disk.
|
|
70
|
+
|
|
53
71
|
## When to use it
|
|
54
72
|
|
|
55
73
|
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.
|
|
@@ -144,8 +162,10 @@ Read a text or image file.
|
|
|
144
162
|
| `path` | `string` | Path to the file (relative or absolute; `~` and `file://` expanded). Required. |
|
|
145
163
|
| `offset` | `number` | Line to start reading from (1-indexed). |
|
|
146
164
|
| `limit` | `number` | Maximum number of lines to read. |
|
|
165
|
+
| `findText` | `string` | Literal substring to search for (no regex). When set, the tool pages through the file from `offset` and returns the page starting at the **first matching line** (re-read at the hit line so the match is the first line). No match is an error result with no file body. |
|
|
166
|
+
| `findMode` | `"exact" \| "case-insensitive"` | Match mode for `findText` (default `"exact"`). Two literals only — no regex, no fuzzy. |
|
|
147
167
|
|
|
148
|
-
**Outputs:** text files are scanned incrementally until one requested page, `maxLines`/`maxBytes`, EOF, or `maxScanBytes` (default 64 MiB scanned per call; 1 GiB hard cap). The default path never loads the complete file and returns a `Use offset=N to continue` footer when more remains. Exact total line count is reported only when EOF was already reached in the bounded scan. Image files (PNG/JPEG/GIF/WebP/BMP by **magic bytes**, not extension) become `[TextContent note, ImageContent]` with base64 `data` and `mimeType`. Oversize images are rejected by `stat` (when available) or `buffer.length` against `maxImageBytes` (default 10 MB) before base64 encoding. An optional `transformImage` callback lets hosts resize or re-encode images without adding image-processing dependencies to the base package. Read failures (missing file, offset beyond end, oversize image, abort) are error results.
|
|
168
|
+
**Outputs:** text files are scanned incrementally until one requested page, `maxLines`/`maxBytes`, EOF, or `maxScanBytes` (default 64 MiB scanned per call; 1 GiB hard cap). The default path never loads the complete file and returns a `Use offset=N to continue` footer when more remains. Exact total line count is reported only when EOF was already reached in the bounded scan. When `findText` is set, the tool pages through `readText` output (in `maxLines`-sized pages) from `offset`, returns the page whose first line is the first match, and stops at the same `maxScanBytes` scan cap — the search is a literal substring scan (per `findMode`), never regex. Image files (PNG/JPEG/GIF/WebP/BMP by **magic bytes**, not extension) become `[TextContent note, ImageContent]` with base64 `data` and `mimeType`. Oversize images are rejected by `stat` (when available) or `buffer.length` against `maxImageBytes` (default 10 MB) before base64 encoding. An optional `transformImage` callback lets hosts resize or re-encode images without adding image-processing dependencies to the base package. Read failures (missing file, offset beyond end, oversize image, abort, findText scan limit) are error results.
|
|
149
169
|
|
|
150
170
|
`read` tool options (via `createReadTool(cwd, options)` or `ToolsOptions.read`):
|
|
151
171
|
|
|
@@ -166,6 +186,14 @@ const read = createReadTool(cwd, {
|
|
|
166
186
|
maxImageBytes: DEFAULT_MAX_IMAGE_BYTES,
|
|
167
187
|
transformImage: async ({ buffer, mimeType }) => host.resizeImage(buffer, mimeType),
|
|
168
188
|
});
|
|
189
|
+
|
|
190
|
+
// jump to the first line containing the needle
|
|
191
|
+
await read.execute({ path: "src/edit.ts", findText: "createEditTool" }, ctx);
|
|
192
|
+
// case-insensitive search starting at offset 50
|
|
193
|
+
await read.execute(
|
|
194
|
+
{ path: "src/edit.ts", findText: "edittool", findMode: "case-insensitive", offset: 50 },
|
|
195
|
+
ctx,
|
|
196
|
+
);
|
|
169
197
|
```
|
|
170
198
|
|
|
171
199
|
`read` result `metadata`:
|
|
@@ -236,13 +264,13 @@ Precise text replacement in an existing file via exact-then-fuzzy matching.
|
|
|
236
264
|
|
|
237
265
|
Each `edits[].oldText` must match a unique, non-overlapping region of the original file. Matching is exact first, then fuzzy (unicode normalization / whitespace collapse).
|
|
238
266
|
|
|
239
|
-
**Fuzzy silent-success tradeoff (loud):** when exact match fails, fuzzy may still apply a replacement **
|
|
267
|
+
**Fuzzy silent-success tradeoff (loud):** when exact match fails, fuzzy may still apply a replacement and is **reported** — both the confirmation text (`Successfully replaced N block(s) in {path} (fuzzy match).`) and `metadata.fuzzy: true`. That can edit the wrong region if `oldText` is slightly off (extra/missing whitespace, unicode lookalikes). Prefer exact `oldText` copied from a fresh `read`. On **no match**, the error lists up to 3 nearby lines (1-indexed, clipped to 120 chars) whose first-line substring matches the edit's first non-empty `oldText` line, so the model can correct its `oldText` (skipped when the needle is shorter than 4 chars or no line contains it). Duplicate / non-unique matches already **fail closed** and leave the file unchanged — ambiguity is not silently resolved by picking the first hit.
|
|
240
268
|
|
|
241
269
|
A BOM is stripped before matching and re-prepended on write; original line endings are restored. Defaults reject targets over 8 MiB, aggregate old/new UTF-8 input over 2 MiB, or more than 100 edits (hard caps: 64 MiB, 16 MiB, and 1,000). Stat and bounded read checks run before matching or mutation. Default local `writeFile` uses same-directory temp + `rename` (crash-safe replace).
|
|
242
270
|
|
|
243
|
-
**Outputs:** a `TextContent` confirmation (`Successfully replaced N block(s) in {path}
|
|
271
|
+
**Outputs:** a `TextContent` confirmation (`Successfully replaced N block(s) in {path}.`, with ` (fuzzy match)` appended when the replacement applied via fuzzy matching) plus `metadata`. Any failure — missing/unreadable file, no match (with nearby line context), duplicate (non-unique) match, overlap, empty `oldText`, no-op edit, or abort — is an error result, and the file is left **unchanged** (the match runs before the write).
|
|
244
272
|
|
|
245
|
-
`edit` result `metadata`: `{ diff, patch, firstChangedLine }` — a display-oriented diff, a standard unified patch,
|
|
273
|
+
`edit` result `metadata`: `{ path, diff, patch, firstChangedLine, fuzzy? }` — the absolute path written, a display-oriented diff, a standard unified patch, the first changed line in the new file, and `fuzzy: true` present only when the replacement applied via fuzzy (not exact) matching. These are host-readable; the model only sees the short confirmation (keeps model context small).
|
|
246
274
|
|
|
247
275
|
### `repo_list`
|
|
248
276
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Linux desktop control
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-computer-use-linux` wraps the host-owned [`computer-use-linux`](https://github.com/agent-sh/computer-use-linux) MCP binary as Prism `ToolDefinition`s. It connects over stdio only when `createComputerUseLinuxTools()` is called, keeps upstream tool names, filters unknown tools, and composes desktop admission, execution approval, result bounds, serialization, redaction, and trust labeling over Prism's existing seams.
|
|
6
|
+
|
|
7
|
+
The package also exports `loadComputerUseLinuxSkill()`, which loads the short Prism-authored desktop procedure bundled at `skills/computer-use-linux/SKILL.md`. It does not resolve or vendor an upstream skill tree.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
Use this package when a Linux host has installed and configured `computer-use-linux` and an agent must inspect or operate that host's desktop. Use the generic [Device adapters](device-adapters.md) contract when implementing another vendor adapter or when the host needs only admission and stream-bound primitives.
|
|
12
|
+
|
|
13
|
+
Do not use it as a desktop launcher, a cross-platform adapter, or a permission bypass. The host owns the binary, desktop session, sandbox, approval decision, and execution policy.
|
|
14
|
+
|
|
15
|
+
## Inputs / request
|
|
16
|
+
|
|
17
|
+
`createComputerUseLinuxTools(options)` accepts:
|
|
18
|
+
|
|
19
|
+
| Field | Type | Default | Purpose |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `command` | `string` | `computer-use-linux` | Host-owned executable. |
|
|
22
|
+
| `args` | `readonly string[]` | `["mcp"]` | MCP server arguments. |
|
|
23
|
+
| `cwd`, `env`, `stderr` | stdio transport options | omitted | Host-owned process configuration. |
|
|
24
|
+
| `serverId` | `string` | `computer-use-linux` | Bridge/error metadata identifier. |
|
|
25
|
+
| `device` | `DeviceAdapter` | required | Must be enabled `desktop-control` with a sandbox. |
|
|
26
|
+
| `runLimits` | `RunLimits` | required | Shared run accounting required by device admission. |
|
|
27
|
+
| `executionPolicy` | `ExecutionPolicy` | omitted | High-risk mutator approval/policy seam. |
|
|
28
|
+
| `approved` | `boolean` | `false` | Host approval for mutating calls. |
|
|
29
|
+
| `includeSetupTools` | `boolean` | `false` | Explicitly expose host setup tools. |
|
|
30
|
+
| `redactor` | `SecretRedactor` | omitted | Redacts returned external data. |
|
|
31
|
+
| `platform` | `NodeJS.Platform` | `process.platform` | Test/host seam; production must be Linux. |
|
|
32
|
+
| `connect` | MCP bridge factory | `connectMcpTools` | Test seam; not needed in production. |
|
|
33
|
+
|
|
34
|
+
`loadComputerUseLinuxSkill()` takes no arguments and reads only the package-local skill file. The file is capped at 64 KiB.
|
|
35
|
+
|
|
36
|
+
## Outputs / response / events
|
|
37
|
+
|
|
38
|
+
| Export | Result |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `createComputerUseLinuxTools` | `{ tools, close }`; `tools` contains known upstream tools returned by the bridge. |
|
|
41
|
+
| `tools` | Read observations (`doctor`, app/window discovery, `get_app_state`, `screenshot`) plus mutators; setup tools are excluded by default. |
|
|
42
|
+
| `close()` | Closes the MCP bridge and its host-owned process transport. |
|
|
43
|
+
| `loadComputerUseLinuxSkill` | Prism `Skill` with name `computer-use-linux` and bounded instructions. |
|
|
44
|
+
| `COMPUTER_USE_LINUX_*` constants | Read, mutating, setup, known-name, and skill-name lists for host filtering and registration. |
|
|
45
|
+
| `MAX_SKILL_FILE_BYTES` | 64 KiB bundled-skill read ceiling. |
|
|
46
|
+
| Tool result | External data with `metadata.trust = "untrusted_external"`; screenshot/app-state oversize results become `dropped_oversize`. |
|
|
47
|
+
|
|
48
|
+
Mutating calls are serialized through one mutex and run `assertDeviceAdmit` plus `assertExecutionAllowed` before the remote MCP call. Read observations bypass per-call approval but still require an admitted device.
|
|
49
|
+
|
|
50
|
+
## Request/response example
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"command": "computer-use-linux",
|
|
55
|
+
"args": ["mcp"],
|
|
56
|
+
"device": {
|
|
57
|
+
"kind": "desktop-control",
|
|
58
|
+
"enabled": true,
|
|
59
|
+
"requireApproval": true,
|
|
60
|
+
"sandbox": "linux-desktop"
|
|
61
|
+
},
|
|
62
|
+
"runLimits": {
|
|
63
|
+
"maxTurns": 32,
|
|
64
|
+
"maxToolCalls": 200
|
|
65
|
+
},
|
|
66
|
+
"approved": false,
|
|
67
|
+
"includeSetupTools": false
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Implementation example
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { createToolRegistry, type Skill } from "@arnilo/prism";
|
|
75
|
+
import {
|
|
76
|
+
createComputerUseLinuxTools,
|
|
77
|
+
loadComputerUseLinuxSkill,
|
|
78
|
+
} from "@arnilo/prism-computer-use-linux";
|
|
79
|
+
|
|
80
|
+
async function installDesktop(hostSkills: { register(skill: Skill): void }, hostApproved: boolean) {
|
|
81
|
+
const desktop = await createComputerUseLinuxTools({
|
|
82
|
+
device: {
|
|
83
|
+
kind: "desktop-control",
|
|
84
|
+
enabled: true,
|
|
85
|
+
requireApproval: true,
|
|
86
|
+
sandbox: "linux-desktop",
|
|
87
|
+
},
|
|
88
|
+
runLimits: { maxTurns: 32, maxToolCalls: 200 },
|
|
89
|
+
approved: hostApproved,
|
|
90
|
+
});
|
|
91
|
+
const tools = createToolRegistry(desktop.tools);
|
|
92
|
+
hostSkills.register(loadComputerUseLinuxSkill());
|
|
93
|
+
|
|
94
|
+
// Keep `desktop` alive while the run can call `tools`; close it at run end.
|
|
95
|
+
return { tools, close: () => desktop.close() };
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Extension and configuration notes
|
|
100
|
+
|
|
101
|
+
- Install and configure the host binary separately: `npm install -g @agent-sh/computer-use-linux` or another host-managed installation. Prism has no runtime dependency on that binary and never downloads it.
|
|
102
|
+
- The factory exposes unprefixed upstream names. Unknown or future upstream names are omitted until Prism classifies them.
|
|
103
|
+
- `setup_accessibility` and `setup_window_targeting` are host-only and omitted unless `includeSetupTools: true` is explicitly selected. The bundled skill never instructs agent turns to perform setup.
|
|
104
|
+
- `connect` is an injectable bridge factory for fake MCP tests. The package's normal path uses `connectMcpTools` with stdio `{ command, args: ["mcp"] }`.
|
|
105
|
+
- `loadComputerUseLinuxSkill()` is inert beyond reading its packaged file; it does not discover peers, resolve paths, or connect to MCP.
|
|
106
|
+
|
|
107
|
+
## Security and performance notes
|
|
108
|
+
|
|
109
|
+
- Construction fails closed on non-Linux hosts and requires `DeviceAdapter.kind = "desktop-control"`, explicit `enabled: true`, a sandbox, and shared `RunLimits` before connecting.
|
|
110
|
+
- Mutators are high-risk external mutations: they require device admission, host approval when configured, and `ExecutionPolicy`; input calls are serialized to prevent concurrent desktop state changes.
|
|
111
|
+
- Observation results are untrusted external content and pass the optional host redactor. Screenshot/app-state payloads pass `acceptDeviceChunk`; oversize payloads are replaced with `dropped_oversize`, not forwarded.
|
|
112
|
+
- Imports are inert. The default setup surface is off, the skill file is capped at 64 KiB, and no full upstream skill tree is shipped.
|
|
113
|
+
- The host must keep credentials, desktop session state, binary paths, sandbox identity, and approval state outside model-controlled arguments.
|
|
114
|
+
|
|
115
|
+
## Related APIs
|
|
116
|
+
|
|
117
|
+
- [Device adapters](device-adapters.md): generic admission, shared limits, chunk bounds, and telemetry redaction contract.
|
|
118
|
+
- [MCP client bridge](mcp-tools.md): host-owned MCP transport and bounded tool mapping.
|
|
119
|
+
- [Tools](tools.md): registry and dispatch lifecycle for the returned `ToolDefinition`s.
|
|
120
|
+
- [Context and skills](context-and-skills.md): explicit skill registration, activation, and progressive disclosure.
|
|
121
|
+
- [Host security](host-security.md): trust, approval, sandbox, and untrusted external-content boundaries.
|
|
122
|
+
- [Upstream computer-use-linux](https://github.com/agent-sh/computer-use-linux): host binary and desktop prerequisites.
|
|
@@ -389,7 +389,7 @@ For document or wide-column stores, map the relational tables above to the store
|
|
|
389
389
|
- **Branches:** In document stores, a branch can be a lightweight document keyed by `leaf_entry_id` that points to the session and root. Rebuild still walks `parent_id` links in entries.
|
|
390
390
|
- **Retention:** Use TTL columns or scheduled map-reduce/streaming jobs. TTL on `expires_at` or entry timestamps is the simplest NoSQL implementation.
|
|
391
391
|
|
|
392
|
-
The Node JSONL session store is a single-process development adapter. It has no cross-process locking, no migrations, no retention enforcement, and no tenant isolation. Do not use it as a production multi-writer store.
|
|
392
|
+
The Node JSONL session store is a single-process development adapter. It has no cross-process locking, no migrations, no retention enforcement, and no tenant isolation. Do not use it as a production multi-writer store. Appends serialize per instance; a rejected append (conflict, duplicate, corrupt file) does not poison later appends on that instance. The rejected write is not committed.
|
|
393
393
|
|
|
394
394
|
## Request/response example
|
|
395
395
|
|
package/docs/device-adapters.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
4
|
|
|
5
|
-
Optional realtime voice and desktop OS / computer-control surface for Prism agents, shipped in 0.0.14 as a **contract + deny-by-default policy
|
|
5
|
+
Optional realtime voice and desktop OS / computer-control surface for Prism agents, shipped in 0.0.14 as a **contract + deny-by-default policy** in `@arnilo/prism` (`src/devices.ts`). The first vendor adapter, `@arnilo/prism-computer-use-linux`, wraps the host-owned `computer-use-linux` MCP binary without changing this generic contract. The contract composes over the existing `PermissionPolicy`, `RunLimits`, approval (`tool_approval`), and redactor seams; it adds no second approval runtime and no device framework.
|
|
6
6
|
|
|
7
7
|
## When to use it
|
|
8
8
|
|
|
@@ -79,7 +79,7 @@ if (chunk.accepted) emit(redactDeviceTelemetry(createSecretRedactor([token]), fr
|
|
|
79
79
|
|
|
80
80
|
- Frozen caps: audio/screenshot/stream chunk **1 MiB / 8 MiB**; concurrent device sessions per identity **1 / 4**. Device wall time / turns / tool calls consume the shared `RunLimits` (admission fails closed without run accounting).
|
|
81
81
|
- `enabled` resolves to `true` only on an explicit `true`; any other value is disabled. `requireApproval` stays `true` unless the host explicitly sets `false` (it should not).
|
|
82
|
-
-
|
|
82
|
+
- `@arnilo/prism-computer-use-linux` is the first vendor package. It remains optional, Linux-only, host-binary-owned, and outside umbrella profiles; this page stays generic so future voice or desktop vendors can satisfy the same contract via `runDevicePolicyConformance`.
|
|
83
83
|
|
|
84
84
|
## Security and performance notes
|
|
85
85
|
|
|
@@ -91,7 +91,8 @@ if (chunk.accepted) emit(redactDeviceTelemetry(createSecretRedactor([token]), fr
|
|
|
91
91
|
## Related APIs
|
|
92
92
|
|
|
93
93
|
- [Browser automation](browser-automation.md): verified-state checkpoints + reload/verify-before-side-effect for browser composition.
|
|
94
|
+
- [Linux desktop control](computer-use-linux.md): first-party host-owned `computer-use-linux` MCP wrapper using this contract.
|
|
94
95
|
- [Conversations](conversations.md): durable threads that own the runs device sessions bind to.
|
|
95
96
|
- [Host security](host-security.md): approval, sandbox, and egress trust boundaries device adapters compose over.
|
|
96
97
|
- [Performance and resource limits](performance.md): shared `RunLimits` accounting.
|
|
97
|
-
- [Migration](migration.md): 0.0.14 additive seams and 0.
|
|
98
|
+
- [Migration](migration.md): historical 0.0.14 additive seams and device-vendor deferral; the 0.3.0 desktop wrapper is additive and optional.
|
package/docs/graft.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Graft context-graph integration
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-graft` is an optional package that wires [nanonets/graft](https://github.com/nanonets/graft) — a repository context-graph CLI (`graft/` directory, INDEX.md orientation, symbol-level wiring graph) — into Prism contribution contracts.
|
|
6
|
+
|
|
7
|
+
It registers six pull tools backed by the graft CLI (`--json`, argv-safe), a push-mode retrieval-pack context provider plus first-turn orientation injector carried on the `graft` skill, commands (`graft`, `graft-build`, `graft-check`, `graft-viz`), and an edit-watch middleware that computes blast radius after mutating tool calls. Import is inert; a missing graft CLI fails closed at `setup` with a bounded redacted error.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
Use it when a host wants agents to locate code by architecture, callers, and coupling before grep-spelunking. Three modes:
|
|
12
|
+
|
|
13
|
+
- `"pull"` (default) — register the tools; the agent decides when to query.
|
|
14
|
+
- `"push"` — per-turn retrieval pack (pointers only) + first-turn orientation, injected automatically.
|
|
15
|
+
- `"both"` — everything.
|
|
16
|
+
|
|
17
|
+
Install optional peer `@nanonets/graft@^0.13.0` **or** pass `packageRoot`/`cliPath` explicitly. Pair with progressive disclosure: the `graft` skill body stays small; tool schemas carry the details. Graft complements indexed code search (`repository_search`): graph/semantic locators vs literal search — neither replaces the other.
|
|
18
|
+
|
|
19
|
+
Zero-code alternative (L0): hosts can skip this package entirely and let agents call `graft <command> --json` through their shell tool, optionally seeding context with graft's own generated instruction files. This package exists for native-tool ergonomics, budgeted subprocesses, session persistence, and push mode.
|
|
20
|
+
|
|
21
|
+
## Inputs / request
|
|
22
|
+
|
|
23
|
+
`createGraftExtension(options)`:
|
|
24
|
+
|
|
25
|
+
| Field | Type | Required | Purpose |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| `cliPath` / `packageRoot` | `string` | no | Explicit stub/binary or checkout root with a manifest-declared bin; default resolves optional peer `@nanonets/graft`. Relative paths rejected; explicit paths existence-checked at resolve time. |
|
|
28
|
+
| `mode` | `"pull" \| "push" \| "both"` | no | Surface selection. Default `pull`. |
|
|
29
|
+
| `projectDir` | `string` | no | Directory graft operates on. Default `process.cwd()` at setup. |
|
|
30
|
+
| `retrievalBudgetMs` | `number` | no | Wall-clock budget per CLI child call (default 8000). |
|
|
31
|
+
| `maxResultBytes` | `number` | no | Stdout cap before parsing (default 512 KiB). |
|
|
32
|
+
| `maxPromptChars` | `number` | no | Prompts longer than this never become ask argv (default 4096). |
|
|
33
|
+
| `allowUpstreamTelemetry` | `boolean` | no | Default false → children run with `DO_NOT_TRACK=1`. |
|
|
34
|
+
| `providerEnv` | `Record<string, string>` | no | Explicit graft provider settings (`GRAFT_API_KEY`, …). Never inherited from host env; only `GRAFT_*` keys reach the child. |
|
|
35
|
+
| `editToolNames` | `readonly string[]` | no | Tools triggering blast-radius lookup. Default `write`, `edit`, `move`. |
|
|
36
|
+
| `quietStartup`, `hideStatus` | `boolean` | no | Suppress startup status events / status reporting. |
|
|
37
|
+
| `appendEntry` | `(entry, opts?) => Promise<void>` | yes | Host session append (OM attach pattern). |
|
|
38
|
+
| `getEntries` | `() => readonly SessionEntry[] \| Promise<...>` | yes | Current branch entries for state restore. |
|
|
39
|
+
|
|
40
|
+
Pull tools (mode includes `pull`): `graft_ask`, `graft_grep`, `graft_callers`, `graft_skeleton`, `graft_map`, `graft_blast`.
|
|
41
|
+
|
|
42
|
+
Push surfaces (mode includes `push`): skill `graft` carrying context provider `graft-context` (per-turn pointers-only pack, gated: ≥12-char prompt, dedup by seen node ids, 32 KiB block ceiling) and instruction injector `graft-orient` (`first_turn`, byte-capped INDEX.md cut + staleness banner).
|
|
43
|
+
|
|
44
|
+
Registered commands: `graft` (`status` \| `build` \| `check` \| `viz` dispatch), plus `graft-build`, `graft-check`, `graft-viz` aliases.
|
|
45
|
+
|
|
46
|
+
## Outputs / response / events
|
|
47
|
+
|
|
48
|
+
| Export | Purpose |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `createGraftExtension(options)` | Returns an inert `Extension` until `kernel.load([...])`; emits `graft:loaded` on setup. |
|
|
51
|
+
| `resolveGraftCli(options)` | Fail-closed CLI resolution (`explicit` → command+argv, `peer-bin` → node + manifest bin). |
|
|
52
|
+
| `runGraftJson(cli, argv, options)` / `childEnv(options)` / `childTimeoutMs` / `DEFAULT_MAX_RESULT_BYTES` | Shared budgeted JSON runner for hosts building custom surfaces. |
|
|
53
|
+
| `readBoundedFile` / `redactPaths` / `GraftResolveError` | Bounded-read and redaction helpers. |
|
|
54
|
+
|
|
55
|
+
Events: `graft:status` (check/build outcomes), `graft:dirty` (post-edit, repo-relative path + optional `staleCountEstimate`), `graft:loaded` (mode + cliKind metadata).
|
|
56
|
+
|
|
57
|
+
Session custom entry shape (`data.type === "graft-state"`, CAS via `expectedParentId`):
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{ "kind": "custom", "data": { "type": "graft-state", "freshness": { "checkedAt": "...", "fresh": true }, "seen": ["node-a"], "savedTokensApprox": 120 } }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The graph never rebuilds itself mid-session (no auto-rebuild): after edits, ask/grep results may lag one turn; graft self-refreshes on the next indexed query, or run `/graft build` for an immediate refresh. The skill text states this contract to the agent.
|
|
64
|
+
|
|
65
|
+
## Request/response example
|
|
66
|
+
|
|
67
|
+
Tool call (pull):
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{ "name": "graft_ask", "arguments": { "query": "where is auth handled?", "count": 3 } }
|
|
71
|
+
→ { "nodes": [{ "id": "auth-guard", "title": "requireAuth", "path": "src/auth.ts", "line": 41 }] }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Status event:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{ "type": "graft:status", "extension": "@arnilo/prism-graft", "metadata": { "fresh": true, "missing": 0, "stale": 2 } }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Implementation example
|
|
81
|
+
|
|
82
|
+
See [`examples/graft-extension.ts`](../examples/graft-extension.ts) — network-free demo against the package fixture stub: one pull-tool call, one push turn with pack injection + dedup, one simulated edit producing blast radius, and the `DO_NOT_TRACK` child-env guard.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { createExtensionKernel, createMemorySessionStore } from "@arnilo/prism";
|
|
86
|
+
import { createGraftExtension } from "@arnilo/prism-graft";
|
|
87
|
+
|
|
88
|
+
const store = createMemorySessionStore();
|
|
89
|
+
const kernel = createExtensionKernel({ errorPolicy: "throw" });
|
|
90
|
+
await kernel.load([
|
|
91
|
+
createGraftExtension({
|
|
92
|
+
packageRoot: "./vendor/graft-checkout",
|
|
93
|
+
mode: "both",
|
|
94
|
+
quietStartup: true,
|
|
95
|
+
appendEntry: async (entry, options) => store.append(entry, options),
|
|
96
|
+
getEntries: async () => store.list("s1"),
|
|
97
|
+
}),
|
|
98
|
+
]);
|
|
99
|
+
// Pull: dispatch graft_ask/… tools. Push: runs assemble the skill-carried
|
|
100
|
+
// provider + graft-orient injector. Edits: middleware emits graft:dirty.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Extension and configuration notes
|
|
104
|
+
|
|
105
|
+
- Import alone registers nothing (`sideEffects: false`); no timers, watchers, or network. The only child processes are budgeted graft CLI calls.
|
|
106
|
+
- Retrieval happens in-process via Prism primitives (context provider, injector, tool_result middleware) — no external hook shims.
|
|
107
|
+
- Ask result shape is parsed tolerantly (`nodes|results|matches|hits`) because graft is pre-1.0; formatters emit pointers (`title` + `file:line` + `[[wikilink]]`), never source bodies.
|
|
108
|
+
- Not included in `@arnilo/prism-code`, `@arnilo/prism-sdk`, or the `prism-all` umbrella (deliberate opt-out, like Caveman/Ponytail) — opt-in install only.
|
|
109
|
+
- Multi-repo layouts work as upstream graft defines them (workspaces, submodules with `--follow-submodules`, sibling repos); point `projectDir` at the graft root that owns the target repo.
|
|
110
|
+
|
|
111
|
+
## Security and performance notes
|
|
112
|
+
|
|
113
|
+
- Telemetry default-off: children always get `DO_NOT_TRACK=1` unless `allowUpstreamTelemetry` is true; child env is fixed-base — host env vars are never inherited, and only explicit `GRAFT_*` keys from `providerEnv` pass through. Route secrets like `GRAFT_API_KEY` through the host's credential resolution when populating `providerEnv`.
|
|
114
|
+
- Upstream output is untrusted: stdout capped (`maxResultBytes`), prompts capped (`maxPromptChars`), injected packs bounded (32 KiB), orientation cut byte-capped (8 KiB); error paths are logged redacted (absolute paths/home dirs).
|
|
115
|
+
- Every CLI call is wall-clock-budgeted (`retrievalBudgetMs`, minus fixed overhead for the timeout math) and every failure degrades silently: pull tools return structured errors, the push pack contributes nothing, edit-watch passes the tool result through untouched.
|
|
116
|
+
- No background workers; state persists through two CAS appends per turn at most (freshness patch, seen-set/saved-tokens update).
|
|
117
|
+
|
|
118
|
+
## Related APIs
|
|
119
|
+
|
|
120
|
+
- [Ponytail behavior integration](ponytail.md): same adapter pattern (optional peer/upstream path, fail-closed setup, session custom entries).
|
|
121
|
+
- [Caveman behavior integration](caveman.md): complementary terse-communication mode package.
|
|
122
|
+
- [Indexed code search](indexed-code-search.md): literal `repository_search` seam — complement, not overlap.
|
|
123
|
+
- [Context and skills](context-and-skills.md): progressive catalog + `load_skill`; skill-carried context providers.
|
|
124
|
+
- [Instruction injection](instruction-injection.md): injector seams (`graft-orient` rides `first_turn`).
|
|
125
|
+
- [Extension kernel and event bus](extensions.md): explicit `kernel.load`, extension events.
|
package/docs/host-security.md
CHANGED
|
@@ -155,6 +155,7 @@ Wire those values where they matter: provider adapters receive the resolved cred
|
|
|
155
155
|
- Optional `@arnilo/prism-browser` requires a host-supplied Playwright Browser (`playwright-core@1.61.0` peer). Import is inert. One non-persistent context belongs to one run; actions serialize; refs are snapshot-scoped; CSS/evaluate/CDP/persistent profiles are denied. Context routing + `serviceWorkers: "block"` deny file/data/blob/devtools/private/loopback by default and require contained-proxy attestation for external egress (Playwright routing is defense in depth, not DNS containment). Uploads are realpath-rooted; downloads quarantine with hash/MIME until host `approveRelease`; screenshots return bounded `ImageContent`. Observation vs mutation/high-impact actions map to `ExecutionPolicy`. Treat snapshot/page text as untrusted external content. Close contexts with `browser_close` or `manager.closeRun(runId)` on abort/terminal. Browser control endpoint, binary/image pin, and real egress firewall/proxy remain host-owned. Shared sandbox: `createSharedSandboxBrowserOptions()` + `assertBrowserSandboxNetwork()`.
|
|
156
156
|
- Browser verified-state checkpoints (0.0.14, `createBrowserCheckpointLedger()`) store URL + domain-state hash + host data refs only — never serialized browser internals (cookies/storage/contexts). After any resume/interruption the ledger fails closed (`assertVerifiedBeforeSideEffect`) until the host reloads + verifies, so side effects never replay on stale state.
|
|
157
157
|
- Device adapters (0.0.14, `resolveDevicePolicy`/`assertDeviceAdmit`) are deny-by-default: admission fails closed without explicit `enabled`, an explicit sandbox, approval (when required), an under-budget session count, and shared `RunLimits`. Stream chunks over the frozen cap are dropped with a marker; telemetry is redacted before emit/persist. No vendor voice/desktop package ships in 0.0.14 (demand-gated 0.1.x); device adapters cannot broaden consent/memory/network/file/browser/connector/tool permissions (gate 8).
|
|
158
|
+
- Optional `@arnilo/prism-wiki` tools treat agent-supplied input as untrusted at the first-party `.wiki/` filesystem boundary. `wiki_read_page` enforces lexical containment (`path.relative` with separator-aware `..`/absolute checks) plus `fs.realpath` containment for the wiki root and every successfully read file, so sibling-prefix (`.wiki-evil`), `..`, absolute, alternate-separator, and symlink escapes are denied before content is returned; missing contained pages report `found: false` while denied paths throw an access-denied error (never mapped to not-found). `wiki_record_insight` rejects empty titles/content, caps titles at 200 characters and content at 65,536 bytes, and collapses control characters and newlines in titles to single-line display text before any page/frontmatter/index/log write, so titles cannot inject Markdown headings, index entries, or log entries; slugs are allow-listed to `[a-z0-9-_]` with a non-empty fallback. See [LLM Wiki](wiki.md).
|
|
158
159
|
- `@arnilo/prism-credentials-node` rejects oversized/malformed envelopes and excessive scrypt work before KDF allocation, uses async scrypt, and requires restrictive existing/new Unix vault modes. Keep vault ownership and parent-directory access host-controlled; review before `chmod 600`, never auto-weaken a file policy. Keychain calls use abort-aware native async work with finite timeout/payload caps and sanitized errors. OS prompts, service availability, and whether a native backend promptly honors cancellation remain host/platform boundaries; no plaintext fallback is attempted.
|
|
159
160
|
- LLM compaction always sends finite summary `maxTokens`, retains bounded deltas/events, and bounds/redacts provider/factory/policy error detail. Observational-memory workers cap turns, calls, arguments, results, transcript, and surfaced errors; unknown tools fail before execution, while invalid results can only be rejected after a host tool returns and may therefore follow side effects. Pass all known provider/credential/tool secrets into compaction/runtime options; exact replacement is not secret discovery.
|
|
160
161
|
- Default remote-media loading resolves every DNS answer, rejects the hostname if any address is non-public, and pins one validated address through the request. Explicit `allowedHostnames` can trust private destinations. A host-supplied `fetch` owns DNS/rebinding/proxy/redirect safety; a custom `requestUrl` must connect to its supplied validated address.
|
|
@@ -172,7 +173,7 @@ Wire those values where they matter: provider adapters receive the resolved cred
|
|
|
172
173
|
- License inventory: 160 locked third-party packages; all declare permissive MIT, ISC, BSD, Apache-2.0, or compatible dual licenses. No GPL, AGPL, SSPL, or missing lockfile license metadata.
|
|
173
174
|
- Install scripts: only `better-sqlite3@12.11.1` runs an install script (`prebuild-install || node-gyp rebuild --release`), required by the explicitly installed SQLite adapter. Core and other optional packages add no install hook.
|
|
174
175
|
- Secret scan: source, tests, docs, workflow files, package metadata, built tests, packed-install canary, and tarball deny-list checks found no private-key block or common live-token prefix. Runtime redaction fixtures cover requests, events, ledgers, stores, checkpoints, provider/OAuth errors, and credential ciphertext.
|
|
175
|
-
- Threat suites pass for parameterized SQL/tenant isolation, HTTP URL/SSRF rejection, realpath/symlink containment, shell-metacharacter approval, schema prototype-pollution/remote-reference bounds, OAuth polling/abort/redaction, credential tamper/wrong-key/KDF floors, MCP result bounds/timeouts, and coding approval/path policy.
|
|
176
|
+
- Threat suites pass for parameterized SQL/tenant isolation, HTTP URL/SSRF rejection, realpath/symlink containment, shell-metacharacter approval, schema prototype-pollution/remote-reference bounds, OAuth polling/abort/redaction, credential tamper/wrong-key/KDF floors, MCP result bounds/timeouts, and coding approval/path policy. `security:threat-suites` also gates CodeQL-remediation regressions (plan 038): linear `trimTrailingSlashes`/parsers with no environment regex evaluation, single-pass HTML sanitization, crypto (not `Math.random`) fixture identifiers, and no clear-text error logging on password-handling paths.
|
|
176
177
|
|
|
177
178
|
PostgreSQL TLS/network policy, MCP endpoint trust/credentials and egress policy beyond package origin/DNS pinning, provider base URLs, OS keychain availability, process sandboxing, workflow tenant identity, and ANSI/control-sequence sanitization in any host terminal renderer remain host boundaries. Prism 0.0.4 ships JSON-line RPC, not an interactive TUI; hosts must render untrusted model/tool text safely. Credential-gated PostgreSQL/provider/keychain tests are separate operator/CI gates, not silently replaced by mocks.
|
|
178
179
|
|
|
@@ -213,6 +214,7 @@ PostgreSQL TLS/network policy, MCP endpoint trust/credentials and egress policy
|
|
|
213
214
|
- **Named threat-suites leg.** `npm run security:threat-suites` aggregates the Phase 8–11 conformance suites (durable-loop/HITL approval, coding sandbox/egress/forge, ACP protocol, OIDC/OPA/MCP-OAuth/OpenAPI/artifact) into one named 0.1.0 security evidence leg — same scripts as `npm test`, no rewrite; the Phase 7 tenant-isolation suite is its protected counterpart under `npm run test:postgres` (missing `PRISM_TEST_POSTGRES_URL` is a named blocked gate).
|
|
214
215
|
- **Supply-chain negative fixtures.** `scripts/release-gate.test.mjs` verifies the tarball deny list rejects tampered content (plans/reviews/maps/tests), unexpected file types and credential material (native binaries, `.pem`/`.key`/`.p12`), and that a provenance flag suppressed in CI is detectable in the `release.mjs` publish dry-run arguments (`--provenance` mandatory under `GITHUB_ACTIONS`, never claimed on local OIDC-less publishes).
|
|
215
216
|
- **Mandatory gate stack.** CodeQL/SAST, PR dependency review (fail on high), secret scan (source + unpacked tarballs), SPDX SBOM + license policy, tarball allow/deny content checks, and provenance (npm OIDC + GitHub build attestations on tarballs and SBOM) all run in `security.yml`/`release.yml`; evidence for the 0.1.0 tree is recorded in [0.1.0 readiness](0.1.0-readiness.md).
|
|
217
|
+
- **CodeQL query suite.** `.github/codeql/codeql-config.yml` selects the `security-extended` suite for `javascript-typescript` (with the default suite) on push/PR/schedule in `security.yml` (10-minute job bound; measured runtime ~3m22s on the audited SHA, last successful main run `33059128198`). The ignore list covers only generated `dist`, `node_modules`, and release/security artifact directories — first-party packages, threat suites, and fixtures that ship or execute are always scanned, so new alerts enter the same plan-038 ledger/remediation loop (config + guardrails asserted in `scripts/phase38-codeql-regression.test.mjs`). Local Task 6 gates (typecheck/lint/format/threat suites/audit/secret scan/SBOM) pass on the remediations; GitHub `state=open` stays non-zero until those remediations are the analyzed head. Groups G (`js/insufficient-password-hash` on RFC 7636 S256) and H (`js/incomplete-url-substring-sanitization` on a negative docs assertion) are maintainer-reviewed false positives queued for narrow dismissal after that analyze, not code changes.
|
|
216
218
|
- **Live canaries are blocked gates, not skips.** The `live-canaries` and `sandbox-browser` workflows always set their gate env (`PRISM_LIVE_CANARIES=1`), so absent credentials fail the job loudly with a named owner (the workflow + dispatching operator) and retained `canary-report.json` evidence; the local silent-skip path exists only when the gate env is not set.
|
|
217
219
|
|
|
218
220
|
## Distributed events and tool effects
|