@coryrylan/cradle 0.0.0 → 1.3.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 +38 -0
- package/README.md +63 -37
- package/dist/agent/aliases.d.ts +2 -2
- package/dist/agent/extensions/agent-browser-nono-fallback.d.ts +3 -0
- package/dist/agent/extensions/providers.d.ts +12 -1
- package/dist/agent/folder.d.ts +31 -5
- package/dist/agent/launch.d.ts +35 -21
- package/dist/agent/package-filters.d.ts +13 -0
- package/dist/agent/packages.d.ts +24 -10
- package/dist/commands/doctor.d.ts +5 -6
- package/dist/commands/{start.d.ts → run.d.ts} +50 -16
- package/dist/index.js +45 -28
- package/dist/nono/profiles.d.ts +1 -1
- package/dist/sbx/compose.d.ts +115 -0
- package/dist/util/proc.d.ts +11 -0
- package/package.json +10 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
## [1.3.0](https://github.com/coryrylan/cradle/compare/cradle-v1.2.0...cradle-v1.3.0) (2026-07-26)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **cli:** enhance package filtering and extensions handling ([03ae139](https://github.com/coryrylan/cradle/commit/03ae1390de541f94f701413ac6853fede7b6c73a))
|
|
6
|
+
|
|
7
|
+
## [1.2.0](https://github.com/coryrylan/cradle/compare/cradle-v1.1.0...cradle-v1.2.0) (2026-07-26)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **cli:** enhance sandbox configuration with docker support ([542cd79](https://github.com/coryrylan/cradle/commit/542cd79445cfc275ca8fa49f9b8e046a4fb23c9e))
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **cli:** rename start command to run and update related documentation ([7324f82](https://github.com/coryrylan/cradle/commit/7324f823ad821b25e7d8e3db07b6cb585acfdd4e))
|
|
16
|
+
|
|
17
|
+
## [1.1.0](https://github.com/coryrylan/cradle/compare/cradle-v1.0.1...cradle-v1.1.0) (2026-07-20)
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* **cli:** enhance sandbox configuration for agent-browser ([363e4b1](https://github.com/coryrylan/cradle/commit/363e4b1b8b7896001ac459c9c508f8a1176868e2))
|
|
22
|
+
|
|
23
|
+
## [1.0.1](https://github.com/coryrylan/cradle/compare/cradle-v1.0.0...cradle-v1.0.1) (2026-07-19)
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* **cli:** update agent folder specifications to include system.md ([52f8aa9](https://github.com/coryrylan/cradle/commit/52f8aa9a1aad73d34baf58f647b4ecbedf0dfd5a))
|
|
28
|
+
|
|
29
|
+
## 1.0.0 (2026-07-19)
|
|
30
|
+
|
|
31
|
+
### Features
|
|
32
|
+
|
|
33
|
+
* init ([74684ae](https://github.com/coryrylan/cradle/commit/74684ae89643d062d906e1c9d311501ab18b6db6))
|
|
34
|
+
|
|
35
|
+
### Bug Fixes
|
|
36
|
+
|
|
37
|
+
* **cli:** mise cache paths ([a13f78b](https://github.com/coryrylan/cradle/commit/a13f78b9f65f427ee982bbcb78fa9f1335e285af))
|
|
38
|
+
|
|
1
39
|
# Changelog
|
|
2
40
|
|
|
3
41
|
## 0.0.0
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
A runtime for portable agents defined as folders. `cradle
|
|
5
|
+
A runtime for portable agents defined as folders. `cradle run <dir>` reads an agent folder — a `SYSTEM.md` or `APPEND_SYSTEM.md` (at least one) plus optional pi-native config, skills, extensions, and sandbox posture — and launches the [pi](https://github.com/earendil-works/pi-mono) coding agent configured from it. An agent declaring `sandbox/nono.json` or `sandbox/sbx.json` runs sandboxed — inside the [nono](https://github.com/always-further/nono) filesystem sandbox or the [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/) microVM, respectively. See [ARCHITECTURE.md](../../ARCHITECTURE.md) for the folder format and [`examples/hello`](../../examples/hello) for a minimal agent. Built with Bun and TypeScript; install the standalone binary via `install.sh` (primary) or the npm package [`@coryrylan/cradle`](https://www.npmjs.com/package/@coryrylan/cradle) (alternative).
|
|
6
6
|
|
|
7
7
|
## Dependencies
|
|
8
8
|
|
|
@@ -10,8 +10,9 @@ A runtime for portable agents defined as folders. `cradle start <dir>` reads an
|
|
|
10
10
|
|
|
11
11
|
| Tool | Status | Why |
|
|
12
12
|
| ------------------------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
-
| [`pi`](https://github.com/earendil-works/pi-mono) | **Required** | The coding agent cradle launches. Every `cradle
|
|
14
|
-
| [`nono`](https://github.com/always-further/nono) | **Recommended** | The filesystem sandbox pi runs inside for a
|
|
13
|
+
| [`pi`](https://github.com/earendil-works/pi-mono) | **Required** | The coding agent cradle launches. Every `cradle run` spawns it. |
|
|
14
|
+
| [`nono`](https://github.com/always-further/nono) | **Recommended** | The filesystem sandbox pi runs inside for a nono-backend run — a folder declaring `sandbox/nono.json`, or `--sandbox`/`--sandbox-backend nono`/`--offline`/`--allow-host` with no `sandbox/sbx.json`; required for that run, not needed otherwise. |
|
|
15
|
+
| [`sbx`](https://docs.docker.com/ai/sandboxes/) | **Recommended** | The Docker Sandboxes microVM pi runs inside for an sbx-backend run — a folder declaring `sandbox/sbx.json`, or `--sandbox-backend sbx`; required for that run, not needed otherwise. |
|
|
15
16
|
| [`mise`](https://mise.jdx.dev) | **Recommended** | The supported way to install and manage `pi` and `nono`. cradle doesn't invoke mise directly, but it falls back to mise's shims when resolving the tools, and the generated sandbox profile grants mise's trees so a sandboxed pi finds its runtime. |
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
@@ -53,32 +54,32 @@ bun start
|
|
|
53
54
|
| `bun run test` | Run tests (no coverage) |
|
|
54
55
|
| `bun run test:coverage` | Run tests with coverage; enforces thresholds from `bunfig.toml` |
|
|
55
56
|
| `bun run lint` | Lint this package with ESLint (typescript-eslint strict) |
|
|
56
|
-
| `bun run format` | Check formatting with Prettier |
|
|
57
|
-
| `bun run format:fix` | Auto-fix formatting |
|
|
58
57
|
| `bun run ci` | Run lint + build + test:coverage (used in CI) |
|
|
58
|
+
| `bun run ci:nocache` | Clean `dist/` then run `ci` (useful for cache debugging) |
|
|
59
59
|
| `bun run install:local` | Build and install binary to `~/.local/bin` |
|
|
60
60
|
| `bun run uninstall:local` | Remove locally installed binary |
|
|
61
61
|
|
|
62
|
-
> Knip (unused files / deps / exports)
|
|
62
|
+
> Formatting (Prettier) and Knip (unused files / deps / exports) run at the monorepo root, not per-package. From the repo root: `bun run format` / `bun run format:fix` and `bun run lint:knip`.
|
|
63
63
|
|
|
64
64
|
## CLI Usage
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
67
|
cradle --version
|
|
68
|
-
cradle doctor # check pi (required), nono/mise (recommended) on PATH, with versions
|
|
69
|
-
cradle
|
|
70
|
-
cradle
|
|
71
|
-
cradle
|
|
72
|
-
cradle
|
|
73
|
-
cradle
|
|
74
|
-
cradle
|
|
68
|
+
cradle doctor # check pi (required), nono/sbx/mise (recommended) on PATH, with versions
|
|
69
|
+
cradle run ./my-agent # run an agent folder with pi; sandboxed when sandbox/nono.json or sandbox/sbx.json exists
|
|
70
|
+
cradle run my-agent # run a name from ~/.cradle/settings.json instead of a path
|
|
71
|
+
cradle run . --offline # block all outbound network (exfil protection)
|
|
72
|
+
cradle run . --allow-host api.example.com # restrict network to these hosts (repeatable)
|
|
73
|
+
cradle run . --sandbox-backend sbx # run under the sbx Docker Sandboxes microVM instead of nono
|
|
74
|
+
cradle run . --no-sandbox # run pi directly (debug)
|
|
75
|
+
cradle run . --dry-run -- --resume # print the write plan + command; forward `--resume` to pi
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
The agent runs in _your_ working directory; the agent folder is a parameter (default `.`). Everything after `--` is forwarded verbatim to pi. `--dry-run` prints the generated-extension write plan and the composed command without spawning (and without requiring the bins to be installed). Per-agent state (generated extensions + session history) lives under `~/.cradle/agents/<name>-<hash>/`.
|
|
78
79
|
|
|
79
80
|
### Global agent aliases (`~/.cradle/settings.json`)
|
|
80
81
|
|
|
81
|
-
The `dir` positional accepts a bare name instead of a path — `cradle
|
|
82
|
+
The `dir` positional accepts a bare name instead of a path — `cradle run my-agent` resolves against a global name → folder map, so agent folders you keep far from any project don't need a full path from every cwd:
|
|
82
83
|
|
|
83
84
|
```json
|
|
84
85
|
{
|
|
@@ -90,7 +91,9 @@ The `dir` positional accepts a bare name instead of a path — `cradle start my-
|
|
|
90
91
|
|
|
91
92
|
A bare name (no `/`, not `.`/`~`-led) checks the alias table first, falling back to the cwd-relative path (`./my-agent`) when no alias is defined — anything already path-shaped (`./x`, `../x`, `/abs/x`, `~/x`, `.`) is never looked up as an alias. See [ARCHITECTURE.md](../../ARCHITECTURE.md#global-agent-aliases) for the full resolution rules.
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
An agent folder can declare a sandbox posture for either backend: `sandbox/nono.json` (the OS-policy sandbox — covered in this section) or `sandbox/sbx.json` (the Docker Sandboxes microVM — see [Docker Sandboxes backend](#docker-sandboxes-backend-sandboxsbxjson) below), or both. `--sandbox-backend <nono|sbx>` picks the backend explicitly and forces it on; bare `--sandbox` uses whichever backend the folder declares, defaulting to nono, and nono wins when a folder declares both (with a warning naming `--sandbox-backend sbx` as the override).
|
|
95
|
+
|
|
96
|
+
Each sandboxed nono run generates its own profile at `~/.cradle/agents/<id>/nono-profile.json` — the built-in base merged with that agent's `sandbox/nono.json` grants — and points `nono run --profile` at it. There's no shared global profile and no separate setup step: an agent's whole sandbox posture lives in its own directory. To widen it (e.g. granting a tool's data dir), add a `sandbox/` folder to the agent with a `nono.json`:
|
|
94
97
|
|
|
95
98
|
```json
|
|
96
99
|
{
|
|
@@ -108,20 +111,20 @@ Outbound network is **open by default within a sandboxed run**. A `network` bloc
|
|
|
108
111
|
{
|
|
109
112
|
"network": {
|
|
110
113
|
"block": false,
|
|
111
|
-
"allow_domain": ["api.
|
|
114
|
+
"allow_domain": ["api.example.com", "localhost"],
|
|
112
115
|
"open_port": [11434],
|
|
113
116
|
"listen_port": [8080]
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
```
|
|
117
120
|
|
|
118
|
-
| Key | Effect
|
|
119
|
-
| ----------------- |
|
|
120
|
-
| `block` | `true` denies **all** outbound (full offline).
|
|
121
|
-
| `allow_domain` | Host allowlist. **Presence flips nono to default-deny** — unlisted hosts are refused (403).
|
|
122
|
-
| `open_port` | localhost TCP ports the agent may connect/bind
|
|
123
|
-
| `listen_port` | TCP ports the agent may listen on.
|
|
124
|
-
| `network_profile` | Named nono network-policy profile (opaque pass-through; requires a host `network-policy.json`).
|
|
121
|
+
| Key | Effect |
|
|
122
|
+
| ----------------- | ------------------------------------------------------------------------------------------------ |
|
|
123
|
+
| `block` | `true` denies **all** outbound (full offline). |
|
|
124
|
+
| `allow_domain` | Host allowlist. **Presence flips nono to default-deny** — unlisted hosts are refused (403). |
|
|
125
|
+
| `open_port` | localhost TCP ports the agent may connect/bind; `0` allows any outbound localhost port on macOS. |
|
|
126
|
+
| `listen_port` | TCP ports the agent may listen on. |
|
|
127
|
+
| `network_profile` | Named nono network-policy profile (opaque pass-through; requires a host `network-policy.json`). |
|
|
125
128
|
|
|
126
129
|
An `allow_domain` allowlist blocks localhost too, so a local-model agent must list `localhost`/`127.0.0.1` **and** open its port (see [`examples/hello`](../../examples/hello), locked to Ollama on `localhost:11434`). CLI flags override the folder: `--offline` (full block) and repeatable `--allow-host <host>` (allowlist). Precedence: `--offline` > `--allow-host` > `sandbox/nono.json` > open. Requesting a network policy only means something inside the sandbox, so `--offline`/`--allow-host` force the sandbox on (same as `--sandbox`) unless `--no-sandbox` is passed explicitly, in which case cradle warns `network policy has no effect without the sandbox — pi runs with no network isolation (--sandbox to enforce it)` and nothing is enforced. cradle doesn't echo the resolved posture itself — run with `--verbose` to drop nono's `--silent` flag and see its own capabilities banner (grants + network mode), or read the generated per-agent profile (`nono-profile.json` in the agent's state dir). nono **fails closed** — a malformed `network` key or a platform that can't enforce proxy filtering makes the run refuse to start rather than silently ship an unenforced allowlist.
|
|
127
130
|
|
|
@@ -129,35 +132,58 @@ An `allow_domain` allowlist blocks localhost too, so a local-model agent must li
|
|
|
129
132
|
|
|
130
133
|
Some tools need OS capabilities the conservative base profile denies. `sandbox/nono.json` can append raw macOS Seatbelt rules — s-expressions merged verbatim after the base's rules (nono validates the syntax at load). Each one widens the OS sandbox, so audit them where they live: the folder's `sandbox/nono.json` or the generated per-agent profile (`nono-profile.json` in the agent's state dir) — nono's own capabilities banner (shown with `--verbose`) never lists seatbelt rules. Ignored on Linux.
|
|
131
134
|
|
|
132
|
-
**Browser automation is the motivating case.** [agent-browser](https://agent-browser.dev/) + Chrome for Testing runs sandboxed under nono with exactly two rules
|
|
135
|
+
**Browser automation is the motivating case.** [agent-browser](https://agent-browser.dev/) + Chrome for Testing runs sandboxed under nono with a directory grant, a direct-child Unix socket grant, exactly two macOS rules, and Chrome's own `--no-sandbox` flag:
|
|
133
136
|
|
|
134
137
|
```json
|
|
135
138
|
{
|
|
136
139
|
"filesystem": {
|
|
137
|
-
"allow": ["~/.agent-browser"]
|
|
140
|
+
"allow": ["~/.agent-browser"],
|
|
141
|
+
"unix_socket_dir_bind": ["~/.agent-browser"]
|
|
142
|
+
},
|
|
143
|
+
"network": {
|
|
144
|
+
"open_port": [0]
|
|
138
145
|
},
|
|
139
146
|
"unsafe_macos_seatbelt_rules": ["(allow mach-register)", "(allow iokit-open)"]
|
|
140
147
|
}
|
|
141
148
|
```
|
|
142
149
|
|
|
143
|
-
| Entry
|
|
144
|
-
|
|
|
145
|
-
| `allow ~/.agent-browser`
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
150
|
+
| Entry | Why it's needed |
|
|
151
|
+
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
152
|
+
| `allow ~/.agent-browser` | agent-browser's state dir — downloaded Chrome, config, and daemon socket files. |
|
|
153
|
+
| `unix_socket_dir_bind ~/.agent-browser` | Lets the CLI and daemon connect to and bind direct-child Unix sockets; without it, `connect()` returns `EPERM`. |
|
|
154
|
+
| `open_port 0` | Lets the daemon connect to Chrome's random localhost DevTools port on macOS; without it, the CDP WebSocket returns `EPERM`. |
|
|
155
|
+
| `(allow mach-register)` | Chrome's Crashpad handler registers a Mach service (`bootstrap_check_in org.chromium.crashpad.*`); the base profile denies it, so the browser process aborts. |
|
|
156
|
+
| `(allow iokit-open)` | Chrome opens IOKit user clients during startup even headless; without it the browser process crashes before serving CDP. |
|
|
157
|
+
|
|
158
|
+
`unix_socket_dir_bind` is non-recursive. Point it only at a dedicated socket directory, never a broad parent such as `~` or `/tmp`. Port `0` is nono's macOS-only outbound localhost wildcard; Linux requires explicit ports. The two macOS rules are IPC/IOKit capabilities only — the filesystem and network boundaries stay intact, so the sandbox still denies an ungranted path (verified: a granted read succeeds, `~/some-secret` returns `Operation not permitted`). No grant for Chrome's own `~/Library/…/Chrome for Testing` dir is needed — its Crashpad-database `stat` failure under the sandbox is non-fatal noise.
|
|
148
159
|
|
|
149
|
-
|
|
160
|
+
Chrome's **own** nested sandbox can't initialize inside nono's seatbelt (macOS forbids nesting), so its child processes need `--no-sandbox`. On every sandboxed run, cradle generates and loads `agent-browser-nono-fallback.ts` before package and agent extensions. On macOS it appends `--no-sandbox` to `AGENT_BROWSER_ARGS`; on every platform it maps nono's dynamically injected `HTTPS_PROXY`/`HTTP_PROXY` to `AGENT_BROWSER_PROXY`. Explicit agent-browser proxy configuration still wins. Unsandboxed runs do not load the fallback, so Chrome keeps its own sandbox. See [`examples/browser`](../../examples/browser) for the complete folder.
|
|
150
161
|
|
|
151
|
-
|
|
162
|
+
An agent that still cannot run sandboxed can declare `{ "sandbox": false }` in either sandbox file to opt that backend out. cradle then runs pi bare, warns loudly on every run, and an explicit `--sandbox`/`--sandbox-backend` flag always forces isolation back on.
|
|
152
163
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
### Docker Sandboxes backend (`sandbox/sbx.json`)
|
|
165
|
+
|
|
166
|
+
A folder can opt into the [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/) (`sbx`) microVM instead of (or alongside) nono by declaring `sandbox/sbx.json` — a sibling file with a restricted subset of `sandbox/nono.json`'s schema: `sandbox: false` to opt out, `filesystem` `read`/`write`/`allow` only, `network` `block`/`allow_domain` only. Keys with no VM-boundary equivalent — `unix_socket_dir_bind`, `network_profile`, `open_port`, `listen_port`, `unsafe_macos_seatbelt_rules` — are warned and dropped rather than silently accepted. Force it explicitly with `--sandbox-backend sbx`; see [ARCHITECTURE.md](../../ARCHITECTURE.md#sandboxsbxjson) for the full schema, backend precedence, and run model.
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"network": { "allow_domain": ["api.example.com"] },
|
|
171
|
+
"filesystem": {
|
|
172
|
+
"allow": ["~/scratch"]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
156
175
|
```
|
|
157
176
|
|
|
158
|
-
(
|
|
177
|
+
An sbx run mounts your cwd (rw), the agent folder (ro), the state dir (rw), and `~/.pi/agent` (rw, so a host `pi login` and its OAuth refreshes carry over) into a sandbox named `cradle-<agentId>-<hash8>` — the hash keys the mount set, so a changed grant gets a fresh sandbox rather than attaching to stale mounts (clean those up with `sbx rm`). cradle then provisions `@earendil-works/pi-coding-agent` in-guest, pinned to the host's own `pi` version, and runs `sbx exec` with your host paths preserved verbatim, so the composed pi command is identical to a nono or unsandboxed run.
|
|
178
|
+
|
|
179
|
+
Network policy differs from nono's: `block: true` denies all outbound; `allow_domain` allows the listed hosts (expanded to cover subdomains, with `localhost`/`127.0.0.1` rewritten to `host.docker.internal` — guest loopback is the microVM itself, not the host). Unlike nono's proxy, a per-sandbox `allow` rule only **adds** to your machine's global `sbx` policy and cannot subtract from it — cradle warns on every allowlist run. For strict, nono-style allowlist semantics, run `sbx policy init deny-all` once. One-time machine setup before the first sandboxed `sbx` run:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
sbx login
|
|
183
|
+
sbx policy init deny-all # or allow-all / balanced — see sbx's own docs
|
|
184
|
+
```
|
|
159
185
|
|
|
160
|
-
|
|
186
|
+
`cradle doctor` checks `sbx` alongside `pi`/`nono`/`mise`.
|
|
161
187
|
|
|
162
188
|
## Build Targets
|
|
163
189
|
|
package/dist/agent/aliases.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ export interface ResolveRefDeps {
|
|
|
3
3
|
readonly cwd: string;
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
|
-
* Resolve a `cradle
|
|
6
|
+
* Resolve a `cradle run` ref into a folder path `loadAgentFolder` can load.
|
|
7
7
|
* Path-shaped refs pass through untouched (`loadAgentFolder`'s own `resolve()`
|
|
8
8
|
* handles them, and they never consult `deps.cwd`); a bare name resolves
|
|
9
9
|
* against the global alias table into an ABSOLUTE, normalized path, falling
|
|
10
|
-
* back to the cwd-relative folder when no alias matches (so `cradle
|
|
10
|
+
* back to the cwd-relative folder when no alias matches (so `cradle run
|
|
11
11
|
* hello` from `examples/` keeps working) — and throwing only when neither
|
|
12
12
|
* resolves. Every bare-name branch resolves against the injected `deps.cwd`,
|
|
13
13
|
* never `process.cwd()`, so the dep is the single source of truth for what the
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const AGENT_BROWSER_NONO_FALLBACK_EXTENSION_FILE = "agent-browser-nono-fallback.ts";
|
|
2
|
+
/** Emit the sandbox-only environment bridge agent-browser needs when launched as a pi subprocess. */
|
|
3
|
+
export declare function emitAgentBrowserNonoFallbackExtension(): string;
|
|
@@ -1 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/** Options for `emitProvidersExtension` — see `commands/run.ts`'s `emitExtensionFiles`, the single call site. */
|
|
2
|
+
export interface ProvidersEmitOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Rewrite `localhost`/`127.0.0.1` provider baseUrls to the sbx host gateway
|
|
5
|
+
* (see `sbx/compose.ts`'s `rewriteLocalhostBaseUrl`): guest loopback is the
|
|
6
|
+
* microVM itself, so a host-bound local-model endpoint is unreachable under
|
|
7
|
+
* its own name inside the sbx backend. Off (byte-identical output) for nono
|
|
8
|
+
* and unsandboxed runs.
|
|
9
|
+
*/
|
|
10
|
+
readonly rewriteLocalhostBaseUrls?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function emitProvidersExtension(providersJson: string, options?: ProvidersEmitOptions): string;
|
package/dist/agent/folder.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface AgentSandboxGrants {
|
|
|
16
16
|
readonly read: readonly string[];
|
|
17
17
|
readonly write: readonly string[];
|
|
18
18
|
readonly allow: readonly string[];
|
|
19
|
+
/** Direct-child Unix socket paths the process may connect to and bind. */
|
|
20
|
+
readonly unixSocketDirBind: readonly string[];
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* The agent's declared network posture — a curated subset of nono's profile
|
|
@@ -40,11 +42,20 @@ export interface AgentNetwork {
|
|
|
40
42
|
readonly listenPort?: readonly number[];
|
|
41
43
|
}
|
|
42
44
|
export type AgentSandboxPosture = 'unconfigured' | 'enabled' | 'disabled';
|
|
45
|
+
/**
|
|
46
|
+
* The sandbox enforcement backend a folder can declare a posture for: `nono`
|
|
47
|
+
* (Seatbelt/bubblewrap, `sandbox/nono.json`) or `sbx` (Docker Sandboxes
|
|
48
|
+
* microVM, `sandbox/sbx.json`). Backend resolution — which one actually runs
|
|
49
|
+
* — is a `commands/run.ts` concern; this module only reads each file's
|
|
50
|
+
* declared posture.
|
|
51
|
+
*/
|
|
52
|
+
export type SandboxBackend = 'nono' | 'sbx';
|
|
43
53
|
export interface AgentSandbox {
|
|
44
54
|
/**
|
|
45
|
-
* The
|
|
46
|
-
* no `sandbox/nono.json
|
|
47
|
-
* (
|
|
55
|
+
* The declaring file's sandbox posture. `'unconfigured'` when the folder
|
|
56
|
+
* has no `sandbox/nono.json` (for the `sandbox` field) or no
|
|
57
|
+
* `sandbox/sbx.json` (for the `sbx` field); otherwise mirrors the file's
|
|
58
|
+
* `sandbox` key (absent ⇒ `'enabled'`).
|
|
48
59
|
*/
|
|
49
60
|
readonly posture: AgentSandboxPosture;
|
|
50
61
|
readonly network?: AgentNetwork;
|
|
@@ -62,15 +73,30 @@ export interface AgentSandbox {
|
|
|
62
73
|
}
|
|
63
74
|
export interface AgentFolder {
|
|
64
75
|
readonly dir: string;
|
|
65
|
-
/**
|
|
66
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Abs path of SYSTEM.md — REPLACES pi's default system prompt via
|
|
78
|
+
* `--system-prompt`; `null` when the folder has no SYSTEM.md. `loadAgentFolder`
|
|
79
|
+
* guarantees at least one of `systemFilePath`/`appendSystemFilePath` is non-null.
|
|
80
|
+
*/
|
|
81
|
+
readonly systemFilePath: string | null;
|
|
82
|
+
/** Abs path of APPEND_SYSTEM.md — appended to pi's system prompt via `--append-system-prompt`; `null` when absent. */
|
|
83
|
+
readonly appendSystemFilePath: string | null;
|
|
67
84
|
readonly settings: AgentSettings;
|
|
68
85
|
/** Serialized `providers` object from models.json, `null` when absent. */
|
|
69
86
|
readonly providersJson: string | null;
|
|
70
87
|
readonly skillsDir: string | null;
|
|
71
88
|
/** pi-native extensions (top-level `extensions/*.ts` plus each subdir's `index.ts`), absolute paths. */
|
|
72
89
|
readonly extensionFiles: readonly string[];
|
|
90
|
+
/** `sandbox/nono.json`'s declared posture — the Seatbelt/bubblewrap backend. */
|
|
73
91
|
readonly sandbox: AgentSandbox;
|
|
92
|
+
/**
|
|
93
|
+
* `sandbox/sbx.json`'s declared posture — the Docker Sandboxes microVM
|
|
94
|
+
* backend. A restricted subset of nono's schema: no Unix-socket grants, no
|
|
95
|
+
* named network-profile pass-through, no Seatbelt escape hatch — the VM
|
|
96
|
+
* boundary has no equivalent for them, so those keys are warned-and-dropped
|
|
97
|
+
* rather than silently accepted. See `readSbxSandbox`.
|
|
98
|
+
*/
|
|
99
|
+
readonly sbx: AgentSandbox;
|
|
74
100
|
readonly warnings: readonly string[];
|
|
75
101
|
}
|
|
76
102
|
export declare function loadAgentFolder(dir: string): Promise<AgentFolder>;
|
package/dist/agent/launch.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { AgentFolder } from './folder.js';
|
|
1
|
+
import type { AgentFolder, SandboxBackend } from './folder.js';
|
|
2
2
|
export interface LaunchSpec {
|
|
3
3
|
readonly folder: AgentFolder;
|
|
4
4
|
readonly stateDir: string;
|
|
5
5
|
/**
|
|
6
6
|
* Where the generated providers extension is written (see
|
|
7
7
|
* `folder.providersJson`) and where `--session-dir` points, sourced from the
|
|
8
|
-
* plan's own `extensionsDir`/`sessionsDir` (`commands/
|
|
8
|
+
* plan's own `extensionsDir`/`sessionsDir` (`commands/run.ts` — the single
|
|
9
9
|
* derivation site; re-deriving from `stateDir` here risks silently
|
|
10
|
-
* disagreeing with a plan whose dirs were overridden after `
|
|
10
|
+
* disagreeing with a plan whose dirs were overridden after `planRun`).
|
|
11
11
|
*/
|
|
12
12
|
readonly extensionsDir: string;
|
|
13
13
|
readonly sessionsDir: string;
|
|
@@ -16,21 +16,30 @@ export interface LaunchSpec {
|
|
|
16
16
|
* from the plan's `statePaths` derivation like `extensionsDir`/`sessionsDir`.
|
|
17
17
|
*/
|
|
18
18
|
readonly miseCacheDir: string;
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Which sandbox backend wraps this run, or `null` for unsandboxed. `'nono'`
|
|
21
|
+
* wraps the argv here — see `composeArgv`. `'sbx'` does NOT wrap here: its
|
|
22
|
+
* `sbx exec` wrapper is composed at materialization in `commands/run.ts`,
|
|
23
|
+
* so `composeArgv` returns the bare pi argv for both `'sbx'` and `null`.
|
|
24
|
+
* For `'sbx'`, `piBin` is the literal `pi` — resolved on the guest's PATH,
|
|
25
|
+
* since host paths (mise-shim included) are meaningless inside the
|
|
26
|
+
* microVM.
|
|
27
|
+
*/
|
|
28
|
+
readonly backend: SandboxBackend | null;
|
|
20
29
|
readonly passthrough: readonly string[];
|
|
21
30
|
/** Resolved path, or bare `nono` for dry-run previews. */
|
|
22
31
|
readonly nonoBin: string;
|
|
23
32
|
/**
|
|
24
33
|
* Resolved path (mise-shim fallback included, see `util/which.ts`), or bare
|
|
25
34
|
* `pi` for dry-run previews. Used consistently whether or not the run is
|
|
26
|
-
* sandboxed — the generated profile already grants read on the mise
|
|
27
|
-
* tree (see `nono/cradle-pi.json`), so the absolute path resolves
|
|
28
|
-
* sandbox too; a bare `pi` only resolves on whatever PATH the
|
|
29
|
-
* process happens to have, which is exactly the gap a fresh
|
|
30
|
-
* install falls into.
|
|
35
|
+
* nono-sandboxed — the generated profile already grants read on the mise
|
|
36
|
+
* install tree (see `nono/cradle-pi.json`), so the absolute path resolves
|
|
37
|
+
* inside the sandbox too; a bare `pi` only resolves on whatever PATH the
|
|
38
|
+
* spawning process happens to have, which is exactly the gap a fresh
|
|
39
|
+
* mise-only install falls into. See `backend` for the `'sbx'` exception.
|
|
31
40
|
*/
|
|
32
41
|
readonly piBin: string;
|
|
33
|
-
/** Abs path of the generated per-agent nono profile; used only when `
|
|
42
|
+
/** Abs path of the generated per-agent nono profile; used only when `backend` is `'nono'`. */
|
|
34
43
|
readonly profilePath: string;
|
|
35
44
|
/**
|
|
36
45
|
* When true, nono runs without `--silent` so its capabilities banner
|
|
@@ -41,7 +50,7 @@ export interface LaunchSpec {
|
|
|
41
50
|
/**
|
|
42
51
|
* Abs paths of the pi extension entry files resolved from settings.json's
|
|
43
52
|
* `packages` (see `./packages.js`), installed into `<stateDir>/npm` by
|
|
44
|
-
* `commands/
|
|
53
|
+
* `commands/run.ts`. Absent/empty on the argv-only preview used for
|
|
45
54
|
* `--dry-run` (packages resolve at install time, after the preview is
|
|
46
55
|
* printed) and on folders that declare no packages.
|
|
47
56
|
*/
|
|
@@ -50,15 +59,16 @@ export interface LaunchSpec {
|
|
|
50
59
|
/**
|
|
51
60
|
* Build the bare pi argv. `passthrough` lands last so user-passed flags win
|
|
52
61
|
* under pi's last-wins parsing. The `-e` order is load-bearing: the generated
|
|
53
|
-
* providers extension goes first,
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
62
|
+
* providers extension goes first, followed by the nono-only agent-browser
|
|
63
|
+
* host-socket fallback, the resolved package entries, then the agent's own
|
|
64
|
+
* `extensions/` files — which load with the agent's providers registered,
|
|
65
|
+
* nono subprocess compatibility configured, and any package-provided tools
|
|
66
|
+
* already available, since the agent's own extensions may depend on them.
|
|
57
67
|
*/
|
|
58
68
|
export declare function composePiArgv(spec: LaunchSpec): string[];
|
|
59
69
|
/**
|
|
60
|
-
* Compose the spawn env for a sandboxed run: `{ MISE_CACHE_DIR: spec.miseCacheDir }`;
|
|
61
|
-
* `{}`
|
|
70
|
+
* Compose the spawn env for a nono-sandboxed run: `{ MISE_CACHE_DIR: spec.miseCacheDir }`;
|
|
71
|
+
* `{}` for `'sbx'` and unsandboxed runs alike.
|
|
62
72
|
*
|
|
63
73
|
* The generated profile denies the shared `~/Library/Caches/mise` on purpose
|
|
64
74
|
* — a poisoned `bin_paths` cache would redirect which binaries the user's
|
|
@@ -68,9 +78,11 @@ export declare function composePiArgv(spec: LaunchSpec): string[];
|
|
|
68
78
|
* at a private cache inside the already-granted state dir instead lets mise
|
|
69
79
|
* cache writes succeed (no warnings, working cache) while the shared host
|
|
70
80
|
* cache stays untouched; mise creates the directory itself, cradle never
|
|
71
|
-
* pre-creates or wipes it. Unsandboxed runs
|
|
81
|
+
* pre-creates or wipes it. Unsandboxed runs produce `{}` and keep the shared
|
|
72
82
|
* host cache — no override needed since there's no sandbox denial to work
|
|
73
|
-
* around
|
|
83
|
+
* around; the sbx guest has no mise at all, and its HOME override rides the
|
|
84
|
+
* `sbx exec` argv composed in `commands/run.ts` rather than env, so the
|
|
85
|
+
* argv-only rule holds there too.
|
|
74
86
|
*
|
|
75
87
|
* This deliberately overrides any user-set `MISE_CACHE_DIR` for sandboxed
|
|
76
88
|
* runs — the profile wouldn't grant a custom location either, so honoring one
|
|
@@ -84,8 +96,10 @@ export declare function composePiArgv(spec: LaunchSpec): string[];
|
|
|
84
96
|
*/
|
|
85
97
|
export declare function composeEnv(spec: LaunchSpec): Record<string, string>;
|
|
86
98
|
/**
|
|
87
|
-
* Build the argv for `nono run … -- pi
|
|
88
|
-
*
|
|
99
|
+
* Build the argv for `nono run … -- pi …` when `spec.backend` is `'nono'`, or
|
|
100
|
+
* the bare pi argv otherwise (`'sbx'` or `null`) — the `'sbx'` backend's own
|
|
101
|
+
* `sbx exec` wrapper is composed later, at materialization in
|
|
102
|
+
* `commands/run.ts`, not here (see `LaunchSpec.backend`).
|
|
89
103
|
*
|
|
90
104
|
* All filesystem grants (cwd, agent dir, state dir, and the agent's own
|
|
91
105
|
* `sandbox/nono.json` entries) AND the network posture live inside the
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** `!`/`+`/`-` prefixed entries decide what loads; everything else names a source path or glob. */
|
|
2
|
+
export declare function isOverridePattern(pattern: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Narrow `files` to what `patterns` selects, preserving input order: plain
|
|
5
|
+
* globs include (none listed includes everything), `!` globs then exclude,
|
|
6
|
+
* `+`/`-` exact paths force a file back in or out — force-exclude wins.
|
|
7
|
+
*/
|
|
8
|
+
export declare function applyPatterns(files: readonly string[], patterns: readonly string[], baseDir: string): string[];
|
|
9
|
+
/**
|
|
10
|
+
* pi's `autoload: false` delta: nothing loads except what a pattern names, and
|
|
11
|
+
* for a file matched by several patterns the last one decides.
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyDeltaPatterns(files: readonly string[], patterns: readonly string[], baseDir: string): string[];
|
package/dist/agent/packages.d.ts
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { type JsonValue } from '../setup/utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* The object form's resource filter, narrowed to the one resource type cradle
|
|
4
|
+
* delivers. Absent `extensions` loads everything the package declares, `[]`
|
|
5
|
+
* loads none, patterns decide otherwise (see `./package-filters.js`).
|
|
6
|
+
*/
|
|
7
|
+
interface PackageFilter {
|
|
8
|
+
readonly extensions?: readonly string[];
|
|
9
|
+
/** pi's `autoload: false` — nothing loads except what an `extensions` pattern names. */
|
|
10
|
+
readonly autoloadDisabled?: boolean;
|
|
11
|
+
}
|
|
2
12
|
/** A parsed `npm:<name>[@<version>]` package source from settings.json's `packages` key. */
|
|
3
|
-
export interface NpmPackageSpec {
|
|
13
|
+
export interface NpmPackageSpec extends PackageFilter {
|
|
4
14
|
readonly name: string;
|
|
5
15
|
readonly version: string;
|
|
6
16
|
}
|
|
7
17
|
/**
|
|
8
|
-
* Parse a settings.json record's `packages` key
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
18
|
+
* Parse a settings.json record's `packages` key — pi's source strings, pi's
|
|
19
|
+
* filter objects, or a mix — into validated npm: package specs. Only `npm:`
|
|
20
|
+
* sources are supported — pi also accepts `git:`, `https://`, `ssh://`, and
|
|
21
|
+
* local paths, all warned and dropped here, since cradle installs the package
|
|
22
|
+
* itself rather than delegating to pi's loader.
|
|
12
23
|
*/
|
|
13
24
|
export declare function readPackageSpecs(record: {
|
|
14
25
|
readonly [key: string]: JsonValue;
|
|
@@ -16,13 +27,16 @@ export declare function readPackageSpecs(record: {
|
|
|
16
27
|
/** Deterministic npm manifest for a per-agent packages install, dependencies sorted by name. */
|
|
17
28
|
export declare function emitPackagesManifest(specs: readonly NpmPackageSpec[]): string;
|
|
18
29
|
/**
|
|
19
|
-
* Resolve each spec's installed package into pi extension entry files
|
|
20
|
-
* to pass as explicit `-e` flags. Every
|
|
21
|
-
* manifest, an entry that escapes the
|
|
22
|
-
* file, no declared extensions) is a
|
|
23
|
-
* warn-don't-throw like every other
|
|
30
|
+
* Resolve each spec's installed package into the pi extension entry files its
|
|
31
|
+
* settings.json entry selects, ready to pass as explicit `-e` flags. Every
|
|
32
|
+
* failure (not installed, malformed manifest, an entry that escapes the
|
|
33
|
+
* package directory, a missing entry file, no declared extensions) is a
|
|
34
|
+
* warning + skip — packages are warn-don't-throw like every other
|
|
35
|
+
* agent-folder input. A filter selecting nothing is silent: that is the
|
|
36
|
+
* author asking for nothing, not a failure.
|
|
24
37
|
*/
|
|
25
38
|
export declare function resolvePackageEntries(npmDir: string, specs: readonly NpmPackageSpec[]): Promise<{
|
|
26
39
|
entries: string[];
|
|
27
40
|
warnings: string[];
|
|
28
41
|
}>;
|
|
42
|
+
export {};
|
|
@@ -9,14 +9,13 @@ export interface DoctorCheck {
|
|
|
9
9
|
}
|
|
10
10
|
interface DoctorDeps {
|
|
11
11
|
readonly which?: WhichFn;
|
|
12
|
-
/** Read a bin's `--version`
|
|
13
|
-
readonly readVersion?: (binPath: string) => Promise<string | null>;
|
|
12
|
+
/** Read a bin's version output (default argv `--version`; sbx passes `['version']`); injected so in-process tests never spawn. */
|
|
13
|
+
readonly readVersion?: (binPath: string, args?: readonly string[]) => Promise<string | null>;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Probe the environment cradle depends on: `pi` (always required), `nono`
|
|
17
|
-
* (required for
|
|
18
|
-
*
|
|
19
|
-
* (recommended toolchain manager).
|
|
16
|
+
* Probe the environment cradle depends on: `pi` (always required), `nono` and
|
|
17
|
+
* `sbx` (each required only for runs on its backend — see the notes), and
|
|
18
|
+
* `mise` (recommended toolchain manager).
|
|
20
19
|
*/
|
|
21
20
|
export declare function runDoctor(deps?: DoctorDeps): Promise<DoctorCheck[]>;
|
|
22
21
|
export declare function formatDoctorReport(checks: readonly DoctorCheck[]): string;
|