@arnilo/prism-tools 0.0.0-stage → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +410 -0
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/browser/README.md +98 -0
- package/browser/skills/browser-automation/SKILL.md +14 -0
- package/browser/src/actions/cdp.ts +131 -0
- package/browser/src/actions/dialog.ts +25 -0
- package/browser/src/actions/download_release.ts +22 -0
- package/browser/src/actions/index.ts +37 -0
- package/browser/src/actions/locator.ts +70 -0
- package/browser/src/actions/navigate.ts +23 -0
- package/browser/src/actions/screenshot.ts +27 -0
- package/browser/src/actions/scroll.ts +24 -0
- package/browser/src/actions/select_page.ts +21 -0
- package/browser/src/actions/types.ts +114 -0
- package/browser/src/actions/upload.ts +44 -0
- package/browser/src/actions/wait.ts +45 -0
- package/browser/src/browser.ts +197 -0
- package/browser/src/cdp.ts +178 -0
- package/browser/src/checkpoint.ts +189 -0
- package/browser/src/config.ts +72 -0
- package/browser/src/downloads.ts +253 -0
- package/browser/src/errors.ts +31 -0
- package/browser/src/evaluate.ts +101 -0
- package/browser/src/index.ts +129 -0
- package/browser/src/limits.ts +271 -0
- package/browser/src/manager.ts +730 -0
- package/browser/src/network.ts +263 -0
- package/browser/src/observe.ts +178 -0
- package/browser/src/policy.ts +133 -0
- package/browser/src/screenshot.ts +107 -0
- package/browser/src/shared-sandbox.ts +53 -0
- package/browser/src/snapshot.ts +99 -0
- package/browser/src/targets.ts +165 -0
- package/browser/src/tools.ts +480 -0
- package/browser/src/types.ts +316 -0
- package/browser/src/uploads.ts +174 -0
- package/coding/README.md +337 -0
- package/coding/src/agent/acp-operations.ts +146 -0
- package/coding/src/agent/artifacts.ts +38 -0
- package/coding/src/agent/ask-user-decision.ts +623 -0
- package/coding/src/agent/atomic-write.ts +23 -0
- package/coding/src/agent/bounded-file.ts +21 -0
- package/coding/src/agent/checks.ts +301 -0
- package/coding/src/agent/coding-checkpoint.ts +828 -0
- package/coding/src/agent/delete.ts +237 -0
- package/coding/src/agent/diagnostics-tool.ts +163 -0
- package/coding/src/agent/diagnostics.ts +262 -0
- package/coding/src/agent/edit-diff.ts +382 -0
- package/coding/src/agent/edit.ts +322 -0
- package/coding/src/agent/effects.ts +102 -0
- package/coding/src/agent/env.ts +28 -0
- package/coding/src/agent/execution-policy.ts +89 -0
- package/coding/src/agent/file-mutation-queue.ts +72 -0
- package/coding/src/agent/forge/github.ts +603 -0
- package/coding/src/agent/forge/index.ts +16 -0
- package/coding/src/agent/forge/types.ts +185 -0
- package/coding/src/agent/git-aware-repository.ts +305 -0
- package/coding/src/agent/git-exec.ts +326 -0
- package/coding/src/agent/git-status.ts +177 -0
- package/coding/src/agent/git-tools.ts +360 -0
- package/coding/src/agent/git.ts +1074 -0
- package/coding/src/agent/glob-match.ts +146 -0
- package/coding/src/agent/glob.ts +182 -0
- package/coding/src/agent/goal-verify.ts +385 -0
- package/coding/src/agent/grep-worker.ts +74 -0
- package/coding/src/agent/grep.ts +459 -0
- package/coding/src/agent/identity.ts +48 -0
- package/coding/src/agent/index.ts +676 -0
- package/coding/src/agent/language/client.ts +429 -0
- package/coding/src/agent/language/framing.ts +190 -0
- package/coding/src/agent/language/index.ts +23 -0
- package/coding/src/agent/language/intelligence.ts +682 -0
- package/coding/src/agent/language/types.ts +187 -0
- package/coding/src/agent/lifecycle-events.ts +121 -0
- package/coding/src/agent/lifecycle.ts +166 -0
- package/coding/src/agent/limits.ts +214 -0
- package/coding/src/agent/move.ts +178 -0
- package/coding/src/agent/mutation-path.ts +52 -0
- package/coding/src/agent/output-accumulator.ts +304 -0
- package/coding/src/agent/path-utils.ts +120 -0
- package/coding/src/agent/process/index.ts +58 -0
- package/coding/src/agent/process/recovery.ts +508 -0
- package/coding/src/agent/process/sessions-host.ts +222 -0
- package/coding/src/agent/process/sessions-monitor.ts +141 -0
- package/coding/src/agent/process/sessions-recovery.ts +241 -0
- package/coding/src/agent/process/sessions-spawn.ts +613 -0
- package/coding/src/agent/process/sessions-teardown.ts +281 -0
- package/coding/src/agent/process/sessions.ts +30 -0
- package/coding/src/agent/process/types.ts +358 -0
- package/coding/src/agent/read-path-set.ts +126 -0
- package/coding/src/agent/read.ts +713 -0
- package/coding/src/agent/repository/glob.ts +147 -0
- package/coding/src/agent/repository/indexed-search.ts +480 -0
- package/coding/src/agent/repository/list.ts +131 -0
- package/coding/src/agent/repository/operations.ts +22 -0
- package/coding/src/agent/repository/path.ts +98 -0
- package/coding/src/agent/repository/search.ts +277 -0
- package/coding/src/agent/repository/types.ts +243 -0
- package/coding/src/agent/repository/walk.ts +126 -0
- package/coding/src/agent/repository.ts +12 -0
- package/coding/src/agent/shell-background.ts +264 -0
- package/coding/src/agent/shell-output.ts +75 -0
- package/coding/src/agent/shell.ts +526 -0
- package/coding/src/agent/todo.ts +399 -0
- package/coding/src/agent/truncate.ts +251 -0
- package/coding/src/agent/write.ts +170 -0
- package/coding/src/ask-user-host.ts +48 -0
- package/coding/src/config.ts +278 -0
- package/coding/src/hard-deny.ts +97 -0
- package/coding/src/index.ts +318 -0
- package/coding/src/principal-guard.ts +84 -0
- package/coding/src/sandbox.ts +92 -0
- package/coding/src/security/approval.ts +144 -0
- package/coding/src/security/command-rules.ts +57 -0
- package/coding/src/security/docker-cli.ts +222 -0
- package/coding/src/security/docker-sandbox.ts +1205 -0
- package/coding/src/security/e2b-sandbox.ts +853 -0
- package/coding/src/security/egress/dns-pin.ts +88 -0
- package/coding/src/security/egress/index.ts +32 -0
- package/coding/src/security/egress/limits.ts +57 -0
- package/coding/src/security/egress/policy.ts +137 -0
- package/coding/src/security/egress/proxy.ts +599 -0
- package/coding/src/security/egress/types.ts +30 -0
- package/coding/src/security/index.ts +199 -0
- package/coding/src/security/native-sandbox.ts +639 -0
- package/coding/src/security/path-containment.ts +55 -0
- package/coding/src/security/sandbox-coding-operations.ts +392 -0
- package/coding/src/security/sandbox-fs-operations.ts +644 -0
- package/coding/src/security/sandbox-limits.ts +147 -0
- package/coding/src/security/sandbox-tar.ts +308 -0
- package/coding/src/security/sandbox.ts +179 -0
- package/coding/src/security/semaphore.ts +55 -0
- package/coding/src/targets.ts +110 -0
- package/coding/src/workspace/diff.ts +78 -0
- package/coding/src/workspace/errors.ts +28 -0
- package/coding/src/workspace/index.ts +5 -0
- package/coding/src/workspace/merge.ts +151 -0
- package/coding/src/workspace/routing.ts +76 -0
- package/coding/src/workspace/service.ts +545 -0
- package/computer-use/README.md +105 -0
- package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
- package/computer-use/src/classify.ts +53 -0
- package/computer-use/src/command.ts +29 -0
- package/computer-use/src/config.ts +69 -0
- package/computer-use/src/create.ts +170 -0
- package/computer-use/src/devices.ts +247 -0
- package/computer-use/src/env.ts +28 -0
- package/computer-use/src/index.ts +157 -0
- package/computer-use/src/skill.ts +21 -0
- package/openapi/README.md +97 -0
- package/openapi/src/compile.ts +386 -0
- package/openapi/src/config.ts +73 -0
- package/openapi/src/errors.ts +16 -0
- package/openapi/src/execute.ts +241 -0
- package/openapi/src/index.ts +124 -0
- package/openapi/src/limits.ts +63 -0
- package/openapi/src/specs.ts +86 -0
- package/openapi/src/tools.ts +60 -0
- package/openapi/src/types.ts +61 -0
- package/package.json +171 -4
- package/research/README.md +72 -0
- package/research/skills/research-report/SKILL.md +12 -0
- package/research/skills/research-scoping/SKILL.md +10 -0
- package/research/skills/research-synthesis/SKILL.md +10 -0
- package/research/skills/source-triage/SKILL.md +10 -0
- package/research/src/check.ts +71 -0
- package/research/src/config.ts +41 -0
- package/research/src/index.ts +147 -0
- package/research/src/ledger.ts +157 -0
- package/research/src/urls.ts +21 -0
- package/web/README.md +287 -0
- package/web/skills/web-research/SKILL.md +17 -0
- package/web/src/brave.ts +66 -0
- package/web/src/cache.ts +151 -0
- package/web/src/config.ts +210 -0
- package/web/src/controls.ts +128 -0
- package/web/src/domains.ts +114 -0
- package/web/src/exa.ts +60 -0
- package/web/src/firecrawl.ts +160 -0
- package/web/src/index.ts +387 -0
- package/web/src/kagi.ts +73 -0
- package/web/src/limits.ts +55 -0
- package/web/src/native/convert.ts +84 -0
- package/web/src/native/fetch.ts +226 -0
- package/web/src/normalize.ts +131 -0
- package/web/src/obscura/cdp.ts +201 -0
- package/web/src/obscura/classify.ts +35 -0
- package/web/src/obscura/cli.ts +254 -0
- package/web/src/obscura/errors.ts +8 -0
- package/web/src/obscura/index.ts +59 -0
- package/web/src/obscura/limits.ts +92 -0
- package/web/src/obscura/mcp.ts +104 -0
- package/web/src/obscura/playwright.ts +133 -0
- package/web/src/obscura/process.ts +235 -0
- package/web/src/obscura/search-profile.ts +73 -0
- package/web/src/obscura/types.ts +48 -0
- package/web/src/obscura/web.ts +419 -0
- package/web/src/prompt-fetch.ts +88 -0
- package/web/src/render.ts +75 -0
- package/web/src/searxng.ts +78 -0
- package/web/src/slice.ts +139 -0
- package/web/src/tavily.ts +77 -0
- package/web/src/tools.ts +182 -0
- package/web/src/transport.ts +310 -0
- package/web/src/types.ts +199 -0
- package/work/README.md +77 -0
- package/work/THIRD_PARTY_NOTICES +24 -0
- package/work/docling/ocr.py +150 -0
- package/work/sandbox/Dockerfile +36 -0
- package/work/sandbox/soffice.sh +12 -0
- package/work/src/command.ts +29 -0
- package/work/src/config.ts +138 -0
- package/work/src/connectors/cli.ts +343 -0
- package/work/src/connectors/drafts.ts +647 -0
- package/work/src/connectors/errors.ts +8 -0
- package/work/src/connectors/file-bytes.ts +149 -0
- package/work/src/connectors/google-workspace-http.ts +399 -0
- package/work/src/connectors/google-workspace.ts +465 -0
- package/work/src/connectors/gws-tools.ts +378 -0
- package/work/src/connectors/http.ts +123 -0
- package/work/src/connectors/idempotency.ts +235 -0
- package/work/src/connectors/identity.ts +34 -0
- package/work/src/connectors/index.ts +93 -0
- package/work/src/connectors/limits.ts +50 -0
- package/work/src/connectors/m365-tools.ts +308 -0
- package/work/src/connectors/microsoft365-http.ts +428 -0
- package/work/src/connectors/microsoft365.ts +402 -0
- package/work/src/connectors/normalize.ts +120 -0
- package/work/src/connectors/tool-helpers.ts +398 -0
- package/work/src/connectors/tools.ts +42 -0
- package/work/src/connectors/types.ts +399 -0
- package/work/src/diagrams/canonicalize.ts +166 -0
- package/work/src/diagrams/caps.ts +52 -0
- package/work/src/diagrams/embed.ts +340 -0
- package/work/src/diagrams/errors.ts +76 -0
- package/work/src/diagrams/index.ts +81 -0
- package/work/src/diagrams/messages.ts +150 -0
- package/work/src/diagrams/origin.ts +52 -0
- package/work/src/diagrams/telemetry.ts +34 -0
- package/work/src/diagrams/xml.ts +267 -0
- package/work/src/document-extraction/index.ts +563 -0
- package/work/src/document-reader/ambient.d.ts +2 -0
- package/work/src/document-reader/errors.ts +7 -0
- package/work/src/document-reader/index.ts +181 -0
- package/work/src/document-reader/mistral-ocr.ts +318 -0
- package/work/src/document-reader/parsers.ts +171 -0
- package/work/src/documents/caps.ts +135 -0
- package/work/src/documents/diff.ts +269 -0
- package/work/src/documents/errors.ts +44 -0
- package/work/src/documents/fidelity.ts +98 -0
- package/work/src/documents/generate.ts +96 -0
- package/work/src/documents/hash.ts +8 -0
- package/work/src/documents/index.ts +14 -0
- package/work/src/documents/model-schema.ts +639 -0
- package/work/src/documents/parse.ts +252 -0
- package/work/src/documents/patch-history.ts +81 -0
- package/work/src/documents/patch.ts +476 -0
- package/work/src/documents/preview-html.ts +245 -0
- package/work/src/documents/preview.ts +227 -0
- package/work/src/documents/telemetry.ts +36 -0
- package/work/src/documents/translate/docx.ts +292 -0
- package/work/src/documents/translate/pptx.ts +304 -0
- package/work/src/documents/translate/xlsx.ts +137 -0
- package/work/src/documents/types.ts +188 -0
- package/work/src/index.ts +311 -0
- package/work/src/runtime/worker-pool-entry.ts +102 -0
- package/work/src/runtime/worker-pool.ts +267 -0
- package/work/src/sandbox/composition.ts +212 -0
- package/work/src/sandbox/image.ts +6 -0
- package/work/src/sandbox/index.ts +14 -0
- package/work/src/sheets/caps.ts +68 -0
- package/work/src/sheets/csv.ts +351 -0
- package/work/src/sheets/decimal.ts +388 -0
- package/work/src/sheets/errors.ts +52 -0
- package/work/src/sheets/index.ts +62 -0
- package/work/src/sheets/inference.ts +378 -0
- package/work/src/sheets/telemetry.ts +34 -0
- package/work/src/sheets/types.ts +162 -0
- package/work/src/sheets/xlsx.ts +388 -0
- package/work/src/skills/index.ts +8 -0
- package/work/src/skills/load.ts +43 -0
- package/work/src/tools/filesystem.ts +21 -0
- package/work/src/tools/index.ts +1 -0
- package/work/src/tools/local-filesystem.ts +33 -0
- package/work/src/tools/office.ts +311 -0
- package/work/vendor/hermes-agent/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
- package/work/vendor-lock.json +11 -0
package/coding/README.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# @arnilo/prism-tools/coding
|
|
2
|
+
|
|
3
|
+
The first-party coding tools — file, search, and shell — as a Prism package. The default export is a
|
|
4
|
+
`definePackage` definition whose `setup` registers the tools into the session's tool registry.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
prism add @arnilo/prism-tools/coding
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```jsonc
|
|
13
|
+
// manifest entry, with every config field that has no default
|
|
14
|
+
{
|
|
15
|
+
"name": "@arnilo/prism-tools/coding",
|
|
16
|
+
"version": "^0.13.0",
|
|
17
|
+
"integrity": "sha512-…",
|
|
18
|
+
"config": { "root": "." }
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
| Field | Type | Default | Description |
|
|
25
|
+
| --- | --- | --- | --- |
|
|
26
|
+
| `root` | `string` | none (required) | The workspace root every file tool resolves against. Missing `root` fails the package load with a field-naming error. |
|
|
27
|
+
| `roots` | `string[]` | `[]` | Extra roots reported by the `coding.targets` service alongside `root`. |
|
|
28
|
+
| `readOnly` | `boolean` | `false` | Registers only `read`, `grep`, and `glob`. |
|
|
29
|
+
| `shell.enabled` | `boolean` | `true` | When `false`, `shell` is not registered and the effect-classified `delete`/`move` substitutes take its place. |
|
|
30
|
+
| `shell.timeoutMs` | `number` | tool default (600 s) | Default wall timeout for `shell`, converted to the tool's seconds option. |
|
|
31
|
+
| `shell.background.maxProcesses` | `number` | `4` (hard cap 16) | Live background processes per session. At the cap, `shell({ background: true })` fails until one is killed with `shell_output({ id, kill: true })`. |
|
|
32
|
+
| `repository` | `object` | tool defaults | Shared repository limits for `grep` and `glob`: `maxDepth`, `maxEntries`, `maxFiles`, `maxResults`, `maxConcurrency`, `maxScanBytes`, `maxFileBytes`, `maxMatches`, `maxPatternBytes`, `maxLineBytes`, `maxContextLines`, `maxTimeMs`, and `exclude`. |
|
|
33
|
+
| `sandbox` | `"none"` \| `"native"` \| `"docker"` \| `"e2b"` | `"none"` | Shell and file-operation backend. `native` builds the in-repo `unshare` backend; `e2b` is reached only through a dynamic `import()` of the optional peer; `docker` needs a host-provided digest-pinned image and runner. A backend that cannot be built notes a warning and withholds `shell` — it never throws and never runs an unsandboxed shell. |
|
|
34
|
+
| `commandRules` | `{ match, decision, reason? }[]` | `[]` | Hard-deny command rules for `shell`. `decision` is `allow`, `deny`, or `requireApproval`; a `deny` wins over any approval. The built-in deny patterns (sudo, `rm -rf /`, pipe-to-shell, `mkfs`, `dd if=`) always apply. |
|
|
35
|
+
| `egress` | `{ allow?, presets? }` | none | Egress allow-list for sandboxed shells, validated with `createEgressPolicy`. Without a sandbox backend it has no effect and notes a warning. |
|
|
36
|
+
| `git.enabled` | `boolean` | `true` | When `false`, no `git_*` tool is registered. |
|
|
37
|
+
| `language.servers` | server map | none | Language servers behind the opt-in `diagnostics` tool: name → `{ command, args?, languages, env? }`, where `languages` are LSP language ids (`typescript`, `typescriptreact`, `python`, …) and the package maps file extension → language. No servers means no `diagnostics` tool and no server process. |
|
|
38
|
+
| `checks` | named-check map | none | Registers `coding_check` only when at least one check is configured. Each value is `{ file, args, cwd?, env?, timeoutMs? }`. |
|
|
39
|
+
| `todo.enabled` | `boolean` | `true` | When `false`, `todo_write` is not registered. |
|
|
40
|
+
| `todo.continueOnOpen` | `boolean` | `true` | When `false`, `todo_write` stays and the `run.end` continuation is not registered. |
|
|
41
|
+
| `principal.id` | `string` | none | Durable-effect tools fail closed without a host principal. When set, a different `ctx.principal.id` fails closed. |
|
|
42
|
+
| `forge.token` | `env:NAME` \| `keyring:NAME` | none | Ref only. A literal fails config validation. Resolved through `prism.credentials.resolve` into the `coding.forge` service. |
|
|
43
|
+
| `workspace.enabled` | `boolean` | `true` | When `false`, no `workspace` service is provided. |
|
|
44
|
+
| `workspace.worktreeRoot` | `string` | per-repository directory under the OS temp dir | Where worktrees are created. Every path is canonicalized and containment-checked under it. |
|
|
45
|
+
| `workspace.maxWorktrees` | `number` | `4` (hard cap 16) | Maximum active worktrees the service creates. |
|
|
46
|
+
| `workspace.diff.maxFiles` | `number` | `200` (hard cap 2000) | Maximum files `workspace.diff` lists. |
|
|
47
|
+
| `workspace.diff.maxBytes` | `number` | `1 MiB` (hard cap 8 MiB) | Maximum bytes of one file's `diff` text; a larger file is listed with no `diff`. |
|
|
48
|
+
|
|
49
|
+
An unknown key fails `createAgent` with the kernel's config error naming the key.
|
|
50
|
+
|
|
51
|
+
## What it changes
|
|
52
|
+
|
|
53
|
+
Registers thirteen tools into the agent's tool registry (plus `diagnostics` when `language.servers` is configured), each with a declared `effects` annotation:
|
|
54
|
+
|
|
55
|
+
| tool | effects | notes |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `read`, `grep`, `glob`, `diagnostics` | `["read"]` | observation only; never mutate the workspace |
|
|
58
|
+
| `write`, `edit` | `["write"]` | durable local mutation, classified for recovery |
|
|
59
|
+
| `delete`, `move` | `["write"]` | only when `shell` is not registered (`shell.enabled: false`, or a sandbox backend that withheld the shell) |
|
|
60
|
+
| `shell` | `["execute"]` | `exclusive: true`, so a provider turn containing it dispatches sequentially |
|
|
61
|
+
| `shell_output` | `["execute"]` | registered only with `shell`, and only when the backend can start a long-running process |
|
|
62
|
+
| `git_status`, `git_diff` | `["read"]` | registered unless `git.enabled` is `false` or `readOnly` is set |
|
|
63
|
+
| `git_commit` | `["write"]` | durable; needs a principal |
|
|
64
|
+
| `git_pr_handoff` | `["external", "network"]` | durable; forge token is a ref, never a literal |
|
|
65
|
+
| `coding_check` | `["execute"]` | only when `checks` has at least one entry |
|
|
66
|
+
| `todo_write` | `[]` | transcript only; `run.end` id `@arnilo/prism-tools/coding:run.end#todo` continues while items stay open |
|
|
67
|
+
| `ask_user_decision` | `[]` | asks through `host.ui`; `unavailable` suspends |
|
|
68
|
+
|
|
69
|
+
The rule is one tool per job: `shell` covers what no dedicated tool does — `git branch`, `git worktree`,
|
|
70
|
+
`git apply`, `rm`, `mv`, and directory listing are all `shell` calls or `workspace`-service operations,
|
|
71
|
+
not extra tool schemas. `delete` and `move` exist only for a host with no shell, where their
|
|
72
|
+
effect classification is what recovery clears up after.
|
|
73
|
+
|
|
74
|
+
The read subset (`readOnly: true`) registers the three read tools only. Git, checks, todo, and `ask_user` stay off in that mode.
|
|
75
|
+
|
|
76
|
+
The package ships no skill. A `coding-workflow` skill was measured and removed (plan 215 Task 11): on the paired items it changed nothing (the one hazard it targeted, deleting a directory that code still reads, passed in 1 of 9 runs with it and 0 of 9 without), and Qwen never loaded it unprompted. See [the evidence](../../../docs/_evidence/skill-evals/211/v3/coding-workflow.preload.md).
|
|
77
|
+
|
|
78
|
+
## Hard denies and the `coding.targets` service
|
|
79
|
+
|
|
80
|
+
One `tool.call` decide handler, id `@arnilo/prism-tools/coding:tool.call#hard-deny`, returns
|
|
81
|
+
`{ deny }` for:
|
|
82
|
+
|
|
83
|
+
- any path argument of its own tools that resolves, after realpath and symlink resolution, outside
|
|
84
|
+
`root` ∪ `roots` — except a read-effect call (`read`, `glob`, `grep`, `diagnostics`) may also
|
|
85
|
+
resolve inside a skill's directory, which is a read-only extra root;
|
|
86
|
+
- a `shell` command matched by a configured deny `commandRules` entry (or a built-in deny pattern);
|
|
87
|
+
- a mutating tool when `readOnly` is set — defence against a host that re-adds a tool.
|
|
88
|
+
|
|
89
|
+
The handler ignores tools it did not register. The kernel's `tool.call` rule is any-deny-wins, so the
|
|
90
|
+
deny holds under `approval` `auto`, under a host `allow` hook, and from a custom loop. Reasons name
|
|
91
|
+
the root and the tool, never file contents.
|
|
92
|
+
|
|
93
|
+
The package also provides the `coding.targets` service:
|
|
94
|
+
`targets(name, args, context?) → { paths, roots } | undefined`. `paths` are absolute; `roots` is
|
|
95
|
+
`root` plus `roots`, and for `read`, `glob`, `grep`, and `diagnostics` also the directory of every
|
|
96
|
+
effective skill that has an absolute `path` (`dirname(skill.path)` from `prism.skills.list()`).
|
|
97
|
+
Unknown tool names return `undefined`. Approval (plan 154 Task 6) reads it instead of parsing tool
|
|
98
|
+
names, so it sees the same skill directories.
|
|
99
|
+
|
|
100
|
+
Those skill roots are read-only. A mutating tool (`write`, `edit`, `delete`, `move`) never receives
|
|
101
|
+
them, so a write under a skill directory outside the workspace is still hard-denied, and `shell` is
|
|
102
|
+
unaffected. The list is read lazily after setup and the derived directories are cached until the
|
|
103
|
+
list's paths change; a missing or failed list keeps the coding roots. `createCodingTargets` takes an
|
|
104
|
+
optional fourth argument for that source; without it there are no skill roots. A symlink inside a
|
|
105
|
+
skill directory that escapes it is rejected by the same realpath containment as any other path.
|
|
106
|
+
|
|
107
|
+
A call from a session bound to a worktree reports that worktree as the session's root (see
|
|
108
|
+
[Worktree roots for one session](#worktree-roots-for-one-session)): a relative argument resolves
|
|
109
|
+
against the worktree, and a path inside the main `root` is outside the reported roots. Approval and
|
|
110
|
+
the hard-deny hook pass the `tool.call` payload's `sessionId`, so both follow the session.
|
|
111
|
+
|
|
112
|
+
`createCodingApprovalPolicy` and its `ExecutionPolicy` approval callback were removed in plan 154
|
|
113
|
+
Task 4 — prompting is `@arnilo/prism-extensions/approval`'s job. Containment (`assertPathInsideRoots`),
|
|
114
|
+
command rules (`evaluateCommandRules`), and the sandbox adapters stay as named exports.
|
|
115
|
+
|
|
116
|
+
## Workspace service
|
|
117
|
+
|
|
118
|
+
When `root` is inside a git repository the package provides the `workspace` service
|
|
119
|
+
(`prism.services.provide("workspace", …)`); outside one it emits an informational `status.note` and
|
|
120
|
+
provides nothing. It replaces the durable 0.12 workspace lifecycle: records, leases,
|
|
121
|
+
checkpoints, and multi-repository identity are gone, and Phase 16 moves those into
|
|
122
|
+
`@arnilo/prism-extensions/workflows`. The service is available in `readOnly` mode too — it is a service, not a
|
|
123
|
+
tool, and no tool reaches it.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const workspace = prism.services.consume<WorkspaceService>("workspace");
|
|
127
|
+
const wt = await workspace.worktree.create({ sessionId: child.id });
|
|
128
|
+
const overlay = await workspace.excludeOverlay(wt.path, [{ path: ".agents/hooks.json", content: "{}" }]);
|
|
129
|
+
const { changes } = await workspace.diff(wt.path);
|
|
130
|
+
const merged = await workspace.merge(wt.path, { into: "main", commit: { message: "delegated change" } });
|
|
131
|
+
if (merged.status === "conflict") console.log(merged.files);
|
|
132
|
+
await overlay.dispose();
|
|
133
|
+
await wt.dispose({ force: true });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
| member | behavior |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `worktree.create({ branch?, base?, sessionId? })` | Adds a linked worktree under `worktreeRoot` on a new branch (default `prism/<short id>`) from `base` (default `HEAD`), locks it with a `prism-workspace:` reason, and returns `{ path, branch, base, parentBranch, sessionId?, dispose }`. `base` is the resolved commit the branch started at. `parentBranch` is the main tree's checked-out branch at creation, recorded because `merge` only accepts a branch name; `HEAD` means the main tree was detached. `sessionId` binds the worktree for tool routing (see [Worktree roots for one session](#worktree-roots-for-one-session)); under a sandbox the binding fails with `workspace_sandbox_unsupported`. More than `maxWorktrees` active worktrees fails with `workspace_limit`; a destination outside `worktreeRoot` fails with `workspace_path_escape`. |
|
|
139
|
+
| `worktree.bound(sessionId)` | The active worktree bound to that session, or `undefined`. |
|
|
140
|
+
| `worktree.rebind({ path, sessionId, branch?, base? })` | Re-adopts a worktree a dead process left behind and binds it to `sessionId`, so a resumed workflow node runs in its recorded worktree. It fails closed with `workspace_unknown` unless the path is inside `worktreeRoot`, registered with git, locked with this service's `prism-workspace:` marker, and on `branch` when given. `base` defaults to the merge base of the branch and the main tree's `HEAD`. Idempotent for the same session; counts against `maxWorktrees`; refused under a sandbox. Never creates a worktree. |
|
|
141
|
+
| `diff(path, bounds?)` | `{ changes, truncated }` for tracked changes against the worktree's `base` plus untracked files. Paths are worktree-relative and each entry is the driver kit's `FileActivity` shape. A file whose diff exceeds `maxBytes` is listed with no `diff`; more than `maxFiles` sets `truncated`. Overlay files never appear. |
|
|
142
|
+
| `merge(path, { into, commit? })` | Merges the worktree's branch into `into`. Everything is decided before anything moves: the merge tree is probed with `git merge-tree --write-tree`, so a conflict returns `{ status: "conflict", files }` with both trees, the index, and every ref unchanged. A clean merge returns `{ status: "merged", commit }` and advances `into` with a compare-and-swap; when `into` is checked out somewhere, that worktree is fast-forwarded so its index and files stay consistent. |
|
|
143
|
+
| `commit(path, { message })` | Records the worktree's pending changes (tracked and untracked) on the worktree's own branch, without merging it, and returns `{ commit }`. A clean worktree creates nothing and returns its current tip. Use it before keeping a branch for a person, so the kept branch carries the work and not just its commits. Needs a configured commit identity, like `merge`. |
|
|
144
|
+
| `excludeOverlay(path, files)` | Writes the overlay files into the worktree and excludes them through one marked block in the git common directory's `info/exclude` (the same writer the driver kit's `overlay()` uses). Returns `{ dispose }`. |
|
|
145
|
+
| `dispose({ force? })` | Removes the overlays and their exclude blocks, unlocks and removes the worktree, drops the session binding, and deletes the branch when git considers it fully merged. Refuses a dirty worktree unless `force`, and is idempotent. |
|
|
146
|
+
|
|
147
|
+
A service created with `createWorkspaceService({ onEvent })` emits the frozen coding lifecycle event
|
|
148
|
+
`worktree_changed` — `{ action: "add", path }` after `worktree.create` succeeds and
|
|
149
|
+
`{ action: "remove", path }` after `dispose` removes one — through the same
|
|
150
|
+
`createCodingLifecycleEmitter` cap checks as the file tools, so a host that maps coding events (the ACP
|
|
151
|
+
lifecycle mapper) sees worktree movement. Events are host-wired: the package registers no listener and
|
|
152
|
+
the plugin passes no sink, exactly like the tools' `onEvent`.
|
|
153
|
+
|
|
154
|
+
Merge semantics and safety:
|
|
155
|
+
|
|
156
|
+
- A **dirty worktree** is refused without `commit`. With `commit`, the worktree's live state (tracked
|
|
157
|
+
and untracked) is folded into a dangling commit, and the index is restored exactly if the merge
|
|
158
|
+
turns out to conflict — so a conflict changes nothing at all. On success the worktree's branch
|
|
159
|
+
advances to that commit, and the merge commit records it.
|
|
160
|
+
- A **dirty checked-out target** is refused with `workspace_dirty_target`; a clean one is
|
|
161
|
+
fast-forwarded to the merge commit.
|
|
162
|
+
- The service never pushes, never resolves a conflict, and never invents a commit identity: it reads
|
|
163
|
+
the repository's configured `user.name`/`user.email` and fails with `workspace_identity_missing`
|
|
164
|
+
when there is none. A git too old for `merge-tree --write-tree` fails with
|
|
165
|
+
`workspace_git_unsupported`.
|
|
166
|
+
- The **main worktree is never removed, locked, or reset** (`workspace_main_immutable`), and a path
|
|
167
|
+
that is not a worktree this service created is refused (`workspace_unknown`).
|
|
168
|
+
- The remote URL is never stored or logged.
|
|
169
|
+
|
|
170
|
+
Cost: package setup costs one `git rev-parse` probe. Git is spawned only on service calls, through
|
|
171
|
+
the bounded `GitOperations` runner; a worktree's own instance is created on its first use and
|
|
172
|
+
dropped on dispose.
|
|
173
|
+
|
|
174
|
+
### Worktree roots for one session
|
|
175
|
+
|
|
176
|
+
A worktree bound with `worktree.create({ sessionId })` routes that session's tool calls into the
|
|
177
|
+
worktree. One wrapper at registration (`createWorktreeRouting({ base, create, workspace })`) wraps
|
|
178
|
+
each registered tool's `execute` with a lookup of the calling session's worktree and dispatches to
|
|
179
|
+
the instance built for that worktree. Relative paths then resolve inside the worktree, the tools'
|
|
180
|
+
own containment follows it, and `coding.targets` reports the worktree as that session's root. Tool
|
|
181
|
+
names, descriptions, and schemas are unchanged, and no tool reads the binding.
|
|
182
|
+
|
|
183
|
+
- The binding lives only in the service's memory, keyed by session id. It is never read from tool
|
|
184
|
+
arguments or model output, so a model cannot claim a worktree.
|
|
185
|
+
- An unbound session — and any session id the service does not know — runs with `root` exactly as
|
|
186
|
+
before.
|
|
187
|
+
- Instances are built once per bound worktree and dropped on `dispose`; the wrapper also drops a
|
|
188
|
+
session's instances on the `session.closed` hook (there is no `session.deleted` event), and a
|
|
189
|
+
session rebound to another worktree rebuilds.
|
|
190
|
+
- From a bound session a write to the main `root` is denied: the hard-deny hook reports roots
|
|
191
|
+
`[worktree, ...roots]`, and `delete`/`move` refuse it through their own containment too. From any
|
|
192
|
+
other session a path inside the worktree is outside that session's roots and is denied the same
|
|
193
|
+
way.
|
|
194
|
+
- With `sandbox` other than `none`, `worktree.create({ sessionId })` fails with
|
|
195
|
+
`workspace_sandbox_unsupported`: a routed worktree path is outside the sandbox root. `create`
|
|
196
|
+
without `sessionId` still works, for a human to inspect.
|
|
197
|
+
|
|
198
|
+
### Tool reference
|
|
199
|
+
|
|
200
|
+
| tool | input | result |
|
|
201
|
+
| --- | --- | --- |
|
|
202
|
+
| `shell` | `{ command, timeout?, background? }` | Combined output + `metadata.exitCode`; 600-second default timeout and 64 MiB total-output cap. Non-zero exit is **not** an error. With `background: true` the command keeps running: the call returns `Started process <id>` plus the first output, waiting for it only until the one-second window closes, and `metadata.processId` names it. |
|
|
203
|
+
| `shell_output` | `{ id, waitSeconds?, kill? }` | Output produced since the previous call for that id (tail-capped like `shell`), whether it is still running, and its exit code once it ended. `waitSeconds` (default 0, max 60) waits for the process library's next publication — a chunk or the exit — rather than polling, and one call returns everything that accumulated in whole chunks up to twice the tail cap; `kill: true` ends it. Another session's id is an error; every process is killed on `session.closed` and on `agent.dispose()`. |
|
|
204
|
+
| `read` | `{ path, offset?, limit?, findText?, findMode? }` | Streamed bounded text page with each line prefixed by its 1-based number and a tab (`12\treturn 1;`), or bounded `[note, ImageContent]`. The prefix is not part of the file; the page caps count it. |
|
|
205
|
+
| `write` | `{ path, content }` (plus `force?` only when the host set `requireReadBeforeWrite`) | Full overwrite; bounded UTF-8 input. Without the guard the schema has no `force` and the description says nothing about one. |
|
|
206
|
+
| `edit` | `{ path, edits: [{oldText,newText}] }` (plus `force?` only with `requireReadBeforeWrite`) | **Exact-match only**: `oldText` must be copied from `read` without its line-number prefix; a miss fails with a `Nearby` hint (and a "remove the prefixes" hint when every line is numbered) and never strips or edits; duplicates and overlaps fail closed. Success reads `Edited <path>: N block(s).` followed by the changed lines with their new numbers, two lines of context, capped at 40 lines. |
|
|
207
|
+
| `grep` | `{ pattern, literal?, path?, glob?, caseSensitive?, includeHidden?, context?, maxMatches?, outputMode?, mode? }` | Regex by default in a terminate-able worker (`literal: true` matches text); ripgrep-style output — `path:line:text`, `path-line-text` context, `--` between groups, each line once; `outputMode`: `content` \| `files_with_matches` \| `count`; `mode` only when the host enabled an indexed mode. |
|
|
208
|
+
| `glob` | `{ pattern, path?, includeHidden?, maxDepth?, maxResults?, offset? }` | Filename match (`*`/`?`/`**`). Braces expand (`{ts,tsx}`, bounded to 128 alternatives / 4096 bytes; unbalanced, nested, or empty braces are errors) unless the host passed `braceExpansion: false`. Files only. |
|
|
209
|
+
| `diagnostics` | `{ path }` | Only with `language.servers`. The language server's diagnostics for one file as `path:line:col severity message` (1-based), after a document refresh; a server that cannot start is a tool error naming the server plus one `status.note("warn", …)`. |
|
|
210
|
+
| `delete` | `{ path, recursive? }` | Only when `shell` is absent. High-risk: file or empty dir only; **no trash**. |
|
|
211
|
+
| `move` | `{ from, to, overwrite? }` | Only when `shell` is absent. High-risk rename/move; `overwrite` default false; **no trash**. |
|
|
212
|
+
|
|
213
|
+
Factories: `createCodingTools`, `createReadOnlyTools`, `createAllTools`, and the per-tool factories
|
|
214
|
+
`createShellTool`, `createShellOutputTool`, `createReadTool`, `createWriteTool`, `createEditTool`,
|
|
215
|
+
`createGrepTool`, `createGlobTool`, `createDeleteTool`, `createMoveTool`, and `createDiagnosticsTool`; the opt-in
|
|
216
|
+
`createGitTools`, `createCodingCheckTool`, and `createAskUserDecisionTool` stay out of the default
|
|
217
|
+
aggregators.
|
|
218
|
+
|
|
219
|
+
Long-running commands are supported through `shell({ background: true })` + `shell_output`; those
|
|
220
|
+
processes run on the process-session library (the same ownership, lifetime, and output caps the
|
|
221
|
+
`agent/process` API uses), inherit the shell's environment allow-list, and are killed when the
|
|
222
|
+
session closes. A backend that cannot start a process (the native `unshare` sandbox) keeps the
|
|
223
|
+
`background` argument out of the schema and does not register `shell_output`.
|
|
224
|
+
|
|
225
|
+
`diagnostics` starts one language-server process per configured server, on the first call that needs
|
|
226
|
+
it, and stops them when the last open session closes (so `agent.dispose()` leaves none running); a
|
|
227
|
+
configured server that no file needs is never started. With a sandbox configured the servers still run
|
|
228
|
+
on the host, outside it — the package notes that when it loads.
|
|
229
|
+
|
|
230
|
+
Non-goals (unchanged from the 0.12 package): No PDF reader, no document reader, no trash /
|
|
231
|
+
recycle daemon, and no PTY / interactive process control in the default set. See
|
|
232
|
+
[coding-tools.md](README.md) and the
|
|
233
|
+
`coding-tools-capability-gaps.ts` example.
|
|
234
|
+
|
|
235
|
+
The package also re-exports the moved module surface as named exports (the former `/agent` and
|
|
236
|
+
`/security` surfaces of the retired coding-tools family package): the tool factories (`createCodingTools`,
|
|
237
|
+
`createReadOnlyTools`, `createAllTools`, and the per-tool factories), the opt-in Git, check, todo, and
|
|
238
|
+
`ask_user` tools, the workspace/process/language helpers, and the sandbox adapters (native,
|
|
239
|
+
Docker, E2B) and approval/egress helpers. Only the tools in the table above are registered; every other
|
|
240
|
+
export is inert until a host or another package uses it.
|
|
241
|
+
|
|
242
|
+
## Git, checks, todo, ask-user, and principal
|
|
243
|
+
|
|
244
|
+
`git` defaults on. `checks` registers `coding_check` only when the map is non-empty. `todo` registers
|
|
245
|
+
`todo_write` and, unless `continueOnOpen` is `false`, a `run.end` decide handler that returns
|
|
246
|
+
`{ continue: { reason, steer } }` while items stay open. The steer and no-progress caps are the 0.12
|
|
247
|
+
limits; `limits.maxContinuations` still caps the run.
|
|
248
|
+
|
|
249
|
+
`ask_user_decision` asks through `host.ui` `select` (options, plus a custom sentinel when
|
|
250
|
+
`allowCustom`) and `input` (a custom answer). `pros` and `cons` are optional, 0–3 strings each; the UI
|
|
251
|
+
shows whatever the model gave, so nothing enforces a fixed count. `dismissed` is a declined tool result. `unavailable`
|
|
252
|
+
returns `{ ask }`; the tool's elicitation contract suspends the run and `session.resume(decisions)`
|
|
253
|
+
delivers the answer without executing the tool.
|
|
254
|
+
|
|
255
|
+
A durable tool (`write`, `edit`, `delete`, `move`, mutating git, `git_pr_handoff`) fails closed with
|
|
256
|
+
`ERR_PRISM_CODING_PRINCIPAL_REQUIRED` when `ctx.principal` is absent, and with
|
|
257
|
+
`ERR_PRISM_CODING_PRINCIPAL_MISMATCH` when `principal.id` is set and differs. Read-only git needs no
|
|
258
|
+
principal.
|
|
259
|
+
|
|
260
|
+
## Capabilities
|
|
261
|
+
|
|
262
|
+
`host-ui` and `credentials`. `host-ui` is how `ask_user_decision` reaches a person. `credentials` is
|
|
263
|
+
how `forge.token` resolves. The package claims no slot and makes no model call at setup.
|
|
264
|
+
|
|
265
|
+
## Enterprise deployment
|
|
266
|
+
|
|
267
|
+
In enterprise or multi-tenant deployments, executing uncontained shell commands or file operations directly on the host poses security risks. The package provides multiple layers of defense:
|
|
268
|
+
|
|
269
|
+
1. **Sandbox Backends (`sandbox`):**
|
|
270
|
+
By default, `sandbox` is `"none"`, which executes shell commands on the host. When `shell` is registered without a sandbox, setup emits a `warn` status note (`"shell runs on the host without a sandbox; pair it with @arnilo/prism-extensions/approval or set sandbox"`). For production workloads, select an isolated backend:
|
|
271
|
+
- `"docker"`: Runs file operations and shell commands inside a digest-pinned container runner.
|
|
272
|
+
- `"native"`: Uses Linux namespace isolation (`unshare`) to containerize processes on the host.
|
|
273
|
+
- `"e2b"`: Executes operations in isolated cloud microVMs via the optional E2B peer.
|
|
274
|
+
If a configured backend cannot be initialized, the package fails closed by withholding `shell` and noting a warning.
|
|
275
|
+
|
|
276
|
+
2. **Approval Package (`@arnilo/prism-extensions/approval`):**
|
|
277
|
+
Combine coding tools with `@arnilo/prism-extensions/approval` to require explicit human or policy approval before mutating operations (`write`, `edit`, `delete`, `move`) or `shell` commands execute.
|
|
278
|
+
|
|
279
|
+
3. **Command Rules (`commandRules`):**
|
|
280
|
+
Configure granular command policies for `shell` execution:
|
|
281
|
+
```jsonc
|
|
282
|
+
"commandRules": [
|
|
283
|
+
{ "match": "npm publish*", "decision": "deny", "reason": "publishing is prohibited" },
|
|
284
|
+
{ "match": "curl *", "decision": "requireApproval" }
|
|
285
|
+
]
|
|
286
|
+
```
|
|
287
|
+
Built-in deny patterns (sudo, `rm -rf /`, pipe-to-shell, `mkfs`, `dd if=`) always apply and cannot be bypassed.
|
|
288
|
+
|
|
289
|
+
4. **Network Egress Rules (`egress`):**
|
|
290
|
+
When using a sandbox backend (`"docker"` or `"native"`), configure egress policies (`allow` rules and preset bundles) to restrict outbound network access from sandboxed shells:
|
|
291
|
+
```jsonc
|
|
292
|
+
"egress": {
|
|
293
|
+
"allow": [{ "host": "github.com", "port": 443, "protocol": "https" }],
|
|
294
|
+
"presets": ["node"]
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
Egress rules are validated with `createEgressPolicy`; under `sandbox: "none"`, egress filtering has no effect and emits a warning note.
|
|
298
|
+
|
|
299
|
+
## Cost, security, and failure behavior
|
|
300
|
+
|
|
301
|
+
- In-tool path containment stays on: `write`/`edit`/`delete`/`move` resolve through
|
|
302
|
+
`resolveContainedMutationPath`, and `read`/`grep`/`glob` go through the
|
|
303
|
+
repository operations that refuse a path outside the root and never follow symlink escapes.
|
|
304
|
+
The `tool.call` hard deny above is the outer gate, so a custom loop or a host `allow` hook cannot
|
|
305
|
+
skip it.
|
|
306
|
+
- Importing the package evaluates no `playwright-core`, `@office-open/*`, `mammoth`, `pdf-parse`,
|
|
307
|
+
`@firecrawl/anydoc`, or `e2b` module. The optional E2B peer is reached only through
|
|
308
|
+
`createRequire` plus a dynamic `import()` inside `createE2BSandbox`/`connectE2BSandbox`, and only
|
|
309
|
+
when `sandbox: "e2b"` is selected.
|
|
310
|
+
- A durable tool (`write`, `edit`, `delete`, `move`, and the mutating Git tools) needs an ownership
|
|
311
|
+
scope; without one the kernel fails the call closed with `ERR_PRISM_TOOL_EFFECT_CONFLICT` rather
|
|
312
|
+
than recording an unattributed effect.
|
|
313
|
+
- A missing `root` fails the package load, naming the field.
|
|
314
|
+
- A `sandbox` backend that cannot be built fails closed: a warning note, no `shell` registration,
|
|
315
|
+
and the run continues with the local file tools.
|
|
316
|
+
|
|
317
|
+
## Example
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
import toolsCoding from "@arnilo/prism-tools/coding";
|
|
321
|
+
import { createAgent } from "@arnilo/prism";
|
|
322
|
+
|
|
323
|
+
const agent = await createAgent({
|
|
324
|
+
model,
|
|
325
|
+
packages: [toolsCoding({ root: process.cwd(), readOnly: true })],
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`examples/coding-approval.ts` runs this package with `@arnilo/prism-extensions/approval` in `ask` mode and
|
|
330
|
+
`@arnilo/prism-extensions/context-files` on one agent: the scripted host approves an `edit`, the file changes, and
|
|
331
|
+
`disable: { tools: ["shell"] }` removes the shell so a call to it is blocked as an unknown tool.
|
|
332
|
+
|
|
333
|
+
`examples/serve-tools.ts` runs this package under `@arnilo/prism-hosts/mcp-server`'s `serveTools`: the
|
|
334
|
+
package's own `read` and `edit` are the definitions served, `coding.targets` is what decides "inside
|
|
335
|
+
roots" for the served calls, the package's default command rules are what deny a dangerous shell
|
|
336
|
+
command, and the session's bound worktree is what a served `edit` changes — so `workspace.diff`
|
|
337
|
+
reports the edit and not the overlay the same run wrote through `excludeOverlay`.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP text-file client adapter for the coding read/write/edit operations.
|
|
3
|
+
*
|
|
4
|
+
* ACP exposes text reads and writes, not stat, mkdir, binary reads, or image sniffing. This adapter
|
|
5
|
+
* keeps those limits explicit: all reads go through the client, mkdir is inert, and binary/image
|
|
6
|
+
* handling is unavailable rather than falling back to the host filesystem.
|
|
7
|
+
*/
|
|
8
|
+
import { Buffer } from "node:buffer";
|
|
9
|
+
import type { EditOperations } from "./edit.js";
|
|
10
|
+
import type { ReadOperations, ReadTextOptions, ReadTextResult } from "./read.js";
|
|
11
|
+
import type { WriteOperations } from "./write.js";
|
|
12
|
+
|
|
13
|
+
/** Duck-typed subset of the ACP client filesystem adapter. */
|
|
14
|
+
export interface TextFileClient {
|
|
15
|
+
readTextFile(input: { path: string; line?: number; limit?: number }): Promise<{ text: string }>;
|
|
16
|
+
writeTextFile(input: { path: string; content: string }): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function checkAbort(signal?: AbortSignal): void {
|
|
20
|
+
if (signal?.aborted) throw new Error("Operation aborted");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function readTextFile(
|
|
24
|
+
client: TextFileClient,
|
|
25
|
+
path: string,
|
|
26
|
+
input?: { line?: number; limit?: number },
|
|
27
|
+
signal?: AbortSignal,
|
|
28
|
+
): Promise<string> {
|
|
29
|
+
checkAbort(signal);
|
|
30
|
+
const request = input === undefined ? { path } : { path, line: input.line, limit: input.limit };
|
|
31
|
+
const response = await client.readTextFile(request);
|
|
32
|
+
checkAbort(signal);
|
|
33
|
+
if (!response || typeof response.text !== "string") {
|
|
34
|
+
throw new Error("TextFileClient.readTextFile must return { text: string }");
|
|
35
|
+
}
|
|
36
|
+
return response.text;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function splitTextLines(text: string): string[] {
|
|
40
|
+
const lines = text.split("\n");
|
|
41
|
+
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readPage(text: string, options: ReadTextOptions): ReadTextResult {
|
|
46
|
+
const requestedLines = options.limit ?? options.maxLines;
|
|
47
|
+
const lines = splitTextLines(text);
|
|
48
|
+
const totalBytes = Buffer.byteLength(text, "utf8");
|
|
49
|
+
if (totalBytes > options.maxScanBytes) {
|
|
50
|
+
throw new Error(`Text read exceeded ${options.maxScanBytes} byte scan limit`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const output: string[] = [];
|
|
54
|
+
let outputBytes = 0;
|
|
55
|
+
let truncatedBy: "lines" | "bytes" | null = null;
|
|
56
|
+
let firstLineExceedsLimit = false;
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
if (output.length >= requestedLines) {
|
|
59
|
+
truncatedBy = "lines";
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
63
|
+
if (output.length === 0 && lineBytes > options.maxBytes) {
|
|
64
|
+
firstLineExceedsLimit = true;
|
|
65
|
+
truncatedBy = "bytes";
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
const withSeparator = output.length === 0 ? lineBytes : lineBytes + 1;
|
|
69
|
+
if (outputBytes + withSeparator > options.maxBytes) {
|
|
70
|
+
truncatedBy = "bytes";
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
output.push(line);
|
|
74
|
+
outputBytes += withSeparator;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const clientReturnedPage = lines.length >= requestedLines && requestedLines > 0;
|
|
78
|
+
const hasMore = !firstLineExceedsLimit && (output.length < lines.length || clientReturnedPage);
|
|
79
|
+
const nextOffset = hasMore && output.length > 0 ? options.offset + output.length : undefined;
|
|
80
|
+
const totalLines = clientReturnedPage ? undefined : lines.length;
|
|
81
|
+
return {
|
|
82
|
+
content: firstLineExceedsLimit ? "" : output.join("\n"),
|
|
83
|
+
startLine: options.offset,
|
|
84
|
+
outputLines: firstLineExceedsLimit ? 0 : output.length,
|
|
85
|
+
hasMore,
|
|
86
|
+
nextOffset,
|
|
87
|
+
truncatedBy,
|
|
88
|
+
firstLineExceedsLimit,
|
|
89
|
+
scannedBytes: totalBytes,
|
|
90
|
+
totalLines,
|
|
91
|
+
totalBytes,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function createAcpFilesystemOperations(client: TextFileClient): {
|
|
96
|
+
read: ReadOperations;
|
|
97
|
+
write: WriteOperations;
|
|
98
|
+
edit: EditOperations;
|
|
99
|
+
} {
|
|
100
|
+
const readFile = async (path: string, options: { maxBytes: number; signal?: AbortSignal }): Promise<Buffer> => {
|
|
101
|
+
const text = await readTextFile(client, path, undefined, options.signal);
|
|
102
|
+
const buffer = Buffer.from(text, "utf8");
|
|
103
|
+
if (buffer.byteLength > options.maxBytes) {
|
|
104
|
+
throw new Error(`File is ${buffer.byteLength} bytes, exceeds ${options.maxBytes} byte limit`);
|
|
105
|
+
}
|
|
106
|
+
return buffer;
|
|
107
|
+
};
|
|
108
|
+
const writeFile = async (path: string, content: string, options?: { maxBytes?: number; signal?: AbortSignal }): Promise<void> => {
|
|
109
|
+
checkAbort(options?.signal);
|
|
110
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
111
|
+
if (options?.maxBytes !== undefined && bytes > options.maxBytes) {
|
|
112
|
+
throw new Error(`Write input is ${bytes} bytes, exceeds ${options.maxBytes} byte limit`);
|
|
113
|
+
}
|
|
114
|
+
await client.writeTextFile({ path, content });
|
|
115
|
+
};
|
|
116
|
+
const access = (path: string, options?: { signal?: AbortSignal }): Promise<void> =>
|
|
117
|
+
readTextFile(client, path, { line: 1, limit: 1 }, options?.signal).then(() => undefined);
|
|
118
|
+
const statFile = async (path: string, options?: { signal?: AbortSignal }): Promise<{ size: number }> => ({
|
|
119
|
+
size: Buffer.byteLength(await readTextFile(client, path, undefined, options?.signal), "utf8"),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
read: {
|
|
124
|
+
readFile,
|
|
125
|
+
readText: async (path, options) => {
|
|
126
|
+
const text = await readTextFile(client, path, { line: options.offset, limit: options.limit ?? options.maxLines }, options.signal);
|
|
127
|
+
return readPage(text, options);
|
|
128
|
+
},
|
|
129
|
+
access,
|
|
130
|
+
statFile,
|
|
131
|
+
detectImageMimeType: async () => null,
|
|
132
|
+
},
|
|
133
|
+
write: {
|
|
134
|
+
writeFile,
|
|
135
|
+
mkdir: async (_path, options) => {
|
|
136
|
+
checkAbort(options?.signal);
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
edit: {
|
|
140
|
+
readFile,
|
|
141
|
+
writeFile,
|
|
142
|
+
access,
|
|
143
|
+
statFile,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-owned artifact helpers for Git patch/bundle/PR-handoff spill.
|
|
3
|
+
*
|
|
4
|
+
* Prism never pushes, authenticates to GitHub/GitLab, or opens a PR. Artifacts
|
|
5
|
+
* are written only through an explicit host callback or a bounded temp-file writer.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
8
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import type { ArtifactReference, ArtifactWriter } from "./git.js";
|
|
12
|
+
|
|
13
|
+
export function sha256Hex(data: Buffer): string {
|
|
14
|
+
return createHash("sha256").update(data).digest("hex");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Write artifacts under a host directory as `file://` URIs with SHA-256 metadata. */
|
|
18
|
+
export function createDirectoryArtifactWriter(rootDir: string): ArtifactWriter {
|
|
19
|
+
return async ({ kind, filename, bytes }) => {
|
|
20
|
+
const safeName = filename.replace(/[^A-Za-z0-9._-]+/g, "_");
|
|
21
|
+
const dir = join(rootDir, kind);
|
|
22
|
+
await mkdir(dir, { recursive: true });
|
|
23
|
+
const path = join(dir, `${randomBytes(8).toString("hex")}-${safeName}`);
|
|
24
|
+
await writeFile(path, bytes, { mode: 0o600 });
|
|
25
|
+
return {
|
|
26
|
+
kind,
|
|
27
|
+
uri: `file://${path}`,
|
|
28
|
+
sha256: sha256Hex(bytes),
|
|
29
|
+
bytes: bytes.length,
|
|
30
|
+
} satisfies ArtifactReference;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Spill into the process temp directory (tests / ephemeral hosts). */
|
|
35
|
+
export function createTempArtifactWriter(prefix = "prism-git-artifact"): ArtifactWriter {
|
|
36
|
+
const root = join(tmpdir(), `${prefix}-${randomBytes(6).toString("hex")}`);
|
|
37
|
+
return createDirectoryArtifactWriter(root);
|
|
38
|
+
}
|