@iinm/plain-agent 1.7.9 → 1.7.11
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.
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Analyzes the project and builds sandbox configuration files (Dockerfile, run.sh, env, setup.sh) tailored to the project's needs.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
You are a sandbox builder. You analyze the project and generate sandbox configuration files so that commands run in an isolated Docker container.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
You create the following files:
|
|
10
|
+
|
|
11
|
+
- `.plain-agent/sandbox/Dockerfile` — Custom Docker image with mise-installed runtimes baked in
|
|
12
|
+
- `.plain-agent/sandbox/run.sh` — Wrapper script for `plain-sandbox` with project-specific options
|
|
13
|
+
- `.plain-agent/sandbox/env` — Environment variable file (empty or with project-specific values)
|
|
14
|
+
- `.plain-agent/setup.sh` — Initial setup script for both sandbox and host
|
|
15
|
+
|
|
16
|
+
You also show an example `sandbox` config for `.plain-agent/config.json`, but you **never modify** config.json directly.
|
|
17
|
+
|
|
18
|
+
## Step 1: Analyze the Project
|
|
19
|
+
|
|
20
|
+
Before generating anything, analyze the project to determine:
|
|
21
|
+
|
|
22
|
+
### 1a. Runtime & Tools
|
|
23
|
+
|
|
24
|
+
Detect the project type and determine which runtimes to install via mise:
|
|
25
|
+
|
|
26
|
+
| File found | mise install commands |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `package.json` | `mise use -g node@<version>` (check `.nvmrc` or `.node-version`, else use LTS) |
|
|
29
|
+
| `package.json` + `package-lock.json` | Add `mise use -g npm@latest` |
|
|
30
|
+
| `package.json` + `yarn.lock` | Add `mise use -g yarn@latest` |
|
|
31
|
+
| `package.json` + `pnpm-lock.yaml` | Add `mise use -g pnpm@latest` |
|
|
32
|
+
| `requirements.txt` or `pyproject.toml` | `mise use -g python@<version>` (check `.python-version`, else 3.12) |
|
|
33
|
+
| `go.mod` | `mise use -g go@<version>` (check `go.mod` for version directive) |
|
|
34
|
+
| `Cargo.toml` | `mise use -g rust@latest` |
|
|
35
|
+
| Multiple of the above | All detected runtimes |
|
|
36
|
+
|
|
37
|
+
Also check for common dev tools:
|
|
38
|
+
- `terraform/` directory or `*.tf` files → `mise use -g terraform@<version>`
|
|
39
|
+
- `.terraform-version` → `mise use -g terraform@<version>`
|
|
40
|
+
|
|
41
|
+
### 1b. Volume Candidates
|
|
42
|
+
|
|
43
|
+
Detect directories that should use Docker volumes (for performance with large directories):
|
|
44
|
+
|
|
45
|
+
| Project type | Cache volumes | Dependency volumes |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| Node.js | `plain-sandbox--global--home-npm:/home/sandbox/.npm` | `node_modules` (per package.json dir if monorepo) |
|
|
48
|
+
| Python | `plain-sandbox--global--home-pip:/home/sandbox/.cache/pip` | — |
|
|
49
|
+
| Go | `plain-sandbox--global--home-go-pkg:/home/sandbox/go/pkg/mod` | — |
|
|
50
|
+
| Rust | `plain-sandbox--global--home-cargo:/home/sandbox/.cargo/registry` | — |
|
|
51
|
+
|
|
52
|
+
For monorepo detection: if multiple `package.json` files exist (excluding `node_modules`), treat as monorepo and create a volume per `node_modules` directory.
|
|
53
|
+
|
|
54
|
+
### 1c. Setup Install Commands
|
|
55
|
+
|
|
56
|
+
| Project type | Install command |
|
|
57
|
+
|---|---|
|
|
58
|
+
| Node.js (npm) | `npm ci` (or `npm install` if no lockfile) |
|
|
59
|
+
| Node.js (yarn) | `yarn install --frozen-lockfile` |
|
|
60
|
+
| Node.js (pnpm) | `pnpm install --frozen-lockfile` |
|
|
61
|
+
| Python | `pip install -r requirements.txt` or `pip install .` |
|
|
62
|
+
| Go | `go mod download` |
|
|
63
|
+
| Rust | `cargo build` |
|
|
64
|
+
|
|
65
|
+
If multiple project types, include all relevant commands.
|
|
66
|
+
|
|
67
|
+
## Step 2: Confirm with User
|
|
68
|
+
|
|
69
|
+
Present the analysis results and ask the user to confirm. Show:
|
|
70
|
+
|
|
71
|
+
1. **Detected project type** (e.g., "Node.js with npm")
|
|
72
|
+
2. **mise install commands** that will be added to Dockerfile
|
|
73
|
+
3. **Volume configuration** (e.g., "node_modules + npm cache")
|
|
74
|
+
4. **Setup install command** (e.g., "npm ci")
|
|
75
|
+
|
|
76
|
+
Ask only one additional question:
|
|
77
|
+
|
|
78
|
+
> Do you want to mount `~/.gitconfig` into the sandbox? (This allows git commit inside the sandbox.)
|
|
79
|
+
|
|
80
|
+
This is the only question beyond confirming the analysis. Do NOT ask about:
|
|
81
|
+
- Base image (always `debian:stable-slim`)
|
|
82
|
+
- Network settings (not needed in run.sh)
|
|
83
|
+
- mise packages (auto-detected)
|
|
84
|
+
|
|
85
|
+
## Step 3: Generate Dockerfile
|
|
86
|
+
|
|
87
|
+
Generate `.plain-agent/sandbox/Dockerfile`. Replace `<MISE_INSTALL_COMMANDS>` with the detected runtimes from Step 1a.
|
|
88
|
+
|
|
89
|
+
```dockerfile
|
|
90
|
+
FROM debian:stable-slim
|
|
91
|
+
|
|
92
|
+
# System packages required for sandbox + development
|
|
93
|
+
RUN apt update && apt install -y \
|
|
94
|
+
busybox bash \
|
|
95
|
+
iptables ipset dnsmasq dnsutils \
|
|
96
|
+
ripgrep fd-find jq \
|
|
97
|
+
git tmux curl \
|
|
98
|
+
&& bash -c 'ln -s $(which fdfind) /usr/local/bin/fd' \
|
|
99
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
100
|
+
|
|
101
|
+
RUN groupadd sandbox && useradd -g sandbox -m sandbox
|
|
102
|
+
USER sandbox
|
|
103
|
+
|
|
104
|
+
# Install mise and project runtimes
|
|
105
|
+
ENV PATH="/home/sandbox/.local/share/mise/shims:/home/sandbox/.local/bin:$PATH"
|
|
106
|
+
RUN curl https://mise.jdx.sh/install.sh | sh
|
|
107
|
+
|
|
108
|
+
<MISE_INSTALL_COMMANDS>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Example `<MISE_INSTALL_COMMANDS>` for Node.js project:**
|
|
112
|
+
|
|
113
|
+
```dockerfile
|
|
114
|
+
RUN mise use -g node@22 && mise use -g npm@latest
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Example for Python project:**
|
|
118
|
+
|
|
119
|
+
```dockerfile
|
|
120
|
+
RUN mise use -g python@3.12
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Example for multi-runtime (Node.js + Terraform):**
|
|
124
|
+
|
|
125
|
+
```dockerfile
|
|
126
|
+
RUN mise use -g node@22 && mise use -g npm@latest && mise use -g terraform@latest
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Important rules:**
|
|
130
|
+
- Always start from `debian:stable-slim`
|
|
131
|
+
- Always install mise via install script — simpler and more reliable than apt
|
|
132
|
+
- All runtimes go through `mise use -g` — never install directly via apt/curl
|
|
133
|
+
- `mise use -g` installs and sets the tool globally, making it available via shims
|
|
134
|
+
- Always create `sandbox` user — home dir is always `/home/sandbox`
|
|
135
|
+
- If the project needs additional system packages (e.g., `shellcheck`, `make`, `locales`), add them to the first `RUN apt install` block
|
|
136
|
+
|
|
137
|
+
## Step 4: Generate run.sh
|
|
138
|
+
|
|
139
|
+
Generate `.plain-agent/sandbox/run.sh`. The structure varies by project type.
|
|
140
|
+
|
|
141
|
+
### Common structure (always included):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
#!/usr/bin/env bash
|
|
145
|
+
|
|
146
|
+
set -eu -o pipefail
|
|
147
|
+
|
|
148
|
+
options=(
|
|
149
|
+
--dockerfile .plain-agent/sandbox/Dockerfile
|
|
150
|
+
--env-file .plain-agent/sandbox/env
|
|
151
|
+
--allow-write
|
|
152
|
+
# <PROJECT_SPECIFIC_VOLUMES>
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Project-specific cache volumes:
|
|
157
|
+
|
|
158
|
+
| Project type | Volume additions |
|
|
159
|
+
|---|---|
|
|
160
|
+
| Node.js | `--volume plain-sandbox--global--home-npm:/home/sandbox/.npm` + `--volume node_modules` |
|
|
161
|
+
| Python | `--volume plain-sandbox--global--home-pip:/home/sandbox/.cache/pip` |
|
|
162
|
+
| Go | `--volume plain-sandbox--global--home-go-pkg:/home/sandbox/go/pkg/mod` |
|
|
163
|
+
| Rust | `--volume plain-sandbox--global--home-cargo:/home/sandbox/.cargo/registry` |
|
|
164
|
+
| Multi | All relevant volumes combined |
|
|
165
|
+
|
|
166
|
+
### Monorepo handling:
|
|
167
|
+
|
|
168
|
+
If multiple `package.json` files exist, dynamically create volumes for each `node_modules`:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Create volumes for each node_modules directory
|
|
172
|
+
for path in $(fd package.json --max-depth 3 | sed -E 's,package.json$,node_modules,'); do
|
|
173
|
+
mkdir -p "$path"
|
|
174
|
+
options+=("--volume" "$path")
|
|
175
|
+
done
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Git worktree handling:
|
|
179
|
+
|
|
180
|
+
Always include this block after the options array, before `plain-sandbox`:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Mount main worktree if using git worktrees
|
|
184
|
+
git_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
|
185
|
+
if test -n "$git_root" && test -f "$git_root/.git"; then
|
|
186
|
+
main_worktree_path=$(sed -E 's,^gitdir: (.+)/.git/.+,\1,' < "$git_root/.git")
|
|
187
|
+
options+=("--mount-writable" "$main_worktree_path:$main_worktree_path")
|
|
188
|
+
fi
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### gitconfig handling:
|
|
192
|
+
|
|
193
|
+
Include this block only if the user confirmed:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Mount gitconfig
|
|
197
|
+
if test -f "$HOME/.gitconfig"; then
|
|
198
|
+
options+=("--mount-readonly" "$HOME/.gitconfig:/home/sandbox/.gitconfig")
|
|
199
|
+
fi
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Complete run.sh example (Node.js project):
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
#!/usr/bin/env bash
|
|
206
|
+
|
|
207
|
+
set -eu -o pipefail
|
|
208
|
+
|
|
209
|
+
options=(
|
|
210
|
+
--dockerfile .plain-agent/sandbox/Dockerfile
|
|
211
|
+
--env-file .plain-agent/sandbox/env
|
|
212
|
+
--allow-write
|
|
213
|
+
--volume plain-sandbox--global--home-npm:/home/sandbox/.npm
|
|
214
|
+
--volume node_modules
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Mount main worktree if using git worktrees
|
|
218
|
+
git_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
|
219
|
+
if test -n "$git_root" && test -f "$git_root/.git"; then
|
|
220
|
+
main_worktree_path=$(sed -E 's,^gitdir: (.+)/.git/.+,\1,' < "$git_root/.git")
|
|
221
|
+
options+=("--mount-writable" "$main_worktree_path:$main_worktree_path")
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# Mount gitconfig
|
|
225
|
+
if test -f "$HOME/.gitconfig"; then
|
|
226
|
+
options+=("--mount-readonly" "$HOME/.gitconfig:/home/sandbox/.gitconfig")
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
plain-sandbox "${options[@]}" "$@"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Step 5: Generate env
|
|
233
|
+
|
|
234
|
+
Create `.plain-agent/sandbox/env`. Docker's `--env-file` does NOT support comments (lines starting with `#` may cause warnings). Keep the file either:
|
|
235
|
+
|
|
236
|
+
- **Empty** (just an empty file), or
|
|
237
|
+
- **With actual values only** (no `#` comment lines)
|
|
238
|
+
|
|
239
|
+
For example, a Node.js project that needs more memory:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
NODE_OPTIONS=--max-old-space-size=4096
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Do NOT include any comment lines in this file.
|
|
246
|
+
|
|
247
|
+
## Step 6: Generate setup.sh
|
|
248
|
+
|
|
249
|
+
Generate `.plain-agent/setup.sh`:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
#!/usr/bin/env bash
|
|
253
|
+
|
|
254
|
+
set -eu -o pipefail
|
|
255
|
+
|
|
256
|
+
this_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
257
|
+
|
|
258
|
+
# Setup sandbox (install dependencies inside container with full network access)
|
|
259
|
+
"$this_dir/sandbox/run.sh" --verbose --allow-net 0.0.0.0/0 <INSTALL_COMMAND>
|
|
260
|
+
|
|
261
|
+
# Setup host (install dependencies on host)
|
|
262
|
+
<INSTALL_COMMAND>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Replace `<INSTALL_COMMAND>` with the appropriate command from Step 1c analysis. For multiple project types, include both commands.
|
|
266
|
+
|
|
267
|
+
The `--allow-net 0.0.0.0/0` is needed only during setup for downloading packages. It should NOT be in run.sh for normal usage.
|
|
268
|
+
|
|
269
|
+
## Step 7: Show config.json Example
|
|
270
|
+
|
|
271
|
+
After generating all files, display the following example and instruct the user to add it to their `.plain-agent/config.json`:
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
Add the following to your .plain-agent/config.json:
|
|
275
|
+
|
|
276
|
+
{
|
|
277
|
+
"sandbox": {
|
|
278
|
+
"command": ".plain-agent/sandbox/run.sh",
|
|
279
|
+
"args": ["--skip-build", "--keep-alive", "30"],
|
|
280
|
+
"separator": "--",
|
|
281
|
+
"rules": [
|
|
282
|
+
{
|
|
283
|
+
"pattern": { "command": { "$regex": "^(gh|docker)$" } },
|
|
284
|
+
"mode": "unsandboxed"
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
If the project already has a `.plain-agent/config.json`, show only the `sandbox` key that should be added/merged. Remind the user:
|
|
292
|
+
- `--skip-build` assumes the image is already built (run `setup.sh` first to build)
|
|
293
|
+
- `--keep-alive 30` reuses the container for 30 seconds between commands for performance
|
|
294
|
+
- `rules` for `gh` and `docker` should typically run unsandboxed (host access needed)
|
|
295
|
+
|
|
296
|
+
## Important Rules
|
|
297
|
+
|
|
298
|
+
1. **Always create a custom Dockerfile** — never use the plain-sandbox preset
|
|
299
|
+
2. **All runtimes go through `mise use -g`** — never install directly via apt/curl
|
|
300
|
+
3. **Always use debian:stable-slim** as the base image
|
|
301
|
+
4. **Always create the `sandbox` user** — home dir is `/home/sandbox`
|
|
302
|
+
5. **Never modify .plain-agent/config.json** — only show the example
|
|
303
|
+
6. **All volume paths use `/home/sandbox/`** — never `/home/node/` or other user paths
|
|
304
|
+
7. **Create the env file** — it's referenced in run.sh; keep it empty or with actual values only (no `#` comments)
|
|
305
|
+
8. **Make shell scripts executable** — after writing run.sh and setup.sh, run `chmod +x` on them
|
package/package.json
CHANGED
package/src/agentLoop.mjs
CHANGED
|
@@ -72,6 +72,12 @@ export function createAgentLoop({
|
|
|
72
72
|
const maxThinkingLoops = 5;
|
|
73
73
|
|
|
74
74
|
while (true) {
|
|
75
|
+
// Check if auto-approve was paused by Ctrl-C during tool execution
|
|
76
|
+
if (pauseSignal.isPaused()) {
|
|
77
|
+
pauseSignal.reset();
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
const modelOutput = await callModel({
|
|
76
82
|
messages: stateManager.getMessages(),
|
|
77
83
|
tools: toolDefs,
|
|
@@ -204,12 +210,6 @@ export function createAgentLoop({
|
|
|
204
210
|
} else {
|
|
205
211
|
stateManager.appendMessages([{ role: "user", content: toolResults }]);
|
|
206
212
|
}
|
|
207
|
-
|
|
208
|
-
// Check if auto-approve was paused by Ctrl-C during tool execution
|
|
209
|
-
if (pauseSignal.isPaused()) {
|
|
210
|
-
pauseSignal.reset();
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
package/src/cliFormatter.mjs
CHANGED
|
@@ -333,7 +333,11 @@ export function printMessage(message) {
|
|
|
333
333
|
}
|
|
334
334
|
case "text": {
|
|
335
335
|
console.log(styleText("bold", "\nUser:"));
|
|
336
|
-
|
|
336
|
+
const highlighted = part.text.replace(
|
|
337
|
+
/^(<context.+?>|<\/context>)/gm,
|
|
338
|
+
styleText("green", "$1"),
|
|
339
|
+
);
|
|
340
|
+
console.log(highlighted);
|
|
337
341
|
break;
|
|
338
342
|
}
|
|
339
343
|
case "image": {
|