@iinm/plain-agent 1.7.12 → 1.7.14

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/README.md CHANGED
@@ -191,7 +191,7 @@ Create the configuration.
191
191
  },
192
192
  {
193
193
  "name": "claude-sonnet-4-6",
194
- "variant": "thinking-16k-bedrock-jp",
194
+ "variant": "thinking-high-bedrock-jp",
195
195
  "platform": {
196
196
  "name": "bedrock",
197
197
  "variant": "jp"
@@ -201,7 +201,8 @@ Create the configuration.
201
201
  "config": {
202
202
  "model": "jp.anthropic.claude-sonnet-4-6",
203
203
  "max_tokens": 32768,
204
- "thinking": { "type": "enabled", "budget_tokens": 16384 }
204
+ "thinking": { "type": "adaptive" },
205
+ "output_config": { "effort": "high" }
205
206
  }
206
207
  },
207
208
  "cost": {
@@ -239,6 +240,18 @@ plain
239
240
  plain -m <model+variant>
240
241
  ```
241
242
 
243
+ (Optional) Set up a sandbox for your project with the `sandbox-configurator` agent.
244
+
245
+ ```
246
+ /agents:sandbox-configurator Set up a sandbox for this project
247
+ ```
248
+
249
+ After the agent finishes, run the generated setup script once to build the sandbox image and install dependencies.
250
+
251
+ ```sh
252
+ ./.plain-agent/setup.sh
253
+ ```
254
+
242
255
  Run in batch mode (non-interactive).
243
256
  In batch mode, config files are not loaded automatically. Only the files specified with `--config` are loaded.
244
257
 
@@ -331,7 +344,7 @@ The agent loads configuration files in the following order. Settings in later fi
331
344
  },
332
345
  "sandbox": {
333
346
  "command": "plain-sandbox",
334
- "args": ["--dockerfile", ".plain-agent/sandbox/Dockerfile", "--allow-write", "--skip-build", "--keep-alive", "30"],
347
+ "args": ["--allow-write", "--skip-build", "--keep-alive", "30"],
335
348
  "separator": "--",
336
349
  "rules": [
337
350
  {
@@ -394,7 +407,7 @@ The agent loads configuration files in the following order. Settings in later fi
394
407
  // https://github.com/iinm/plain-agent/tree/main/sandbox
395
408
  "sandbox": {
396
409
  "command": "plain-sandbox",
397
- "args": ["--dockerfile", ".plain-agent/sandbox/Dockerfile", "--allow-write", "--skip-build", "--keep-alive", "30"],
410
+ "args": ["--allow-write", "--skip-build", "--keep-alive", "30"],
398
411
  // separator is inserted between sandbox flags and the user command to prevent bypasses
399
412
  "separator": "--",
400
413
 
@@ -1,16 +1,14 @@
1
1
  ---
2
- description: Analyzes the project and builds sandbox configuration files (Dockerfile, run.sh, env, setup.sh) tailored to the project's needs.
2
+ description: Analyzes the project and generates sandbox configuration files (run.sh, setup.sh) tailored to the project's needs.
3
3
  ---
4
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.
5
+ You are a sandbox builder. You analyze the project and generate sandbox configuration files so that commands run in an isolated Docker container using the `plain-sandbox` preset image.
6
6
 
7
7
  ## Overview
8
8
 
9
9
  You create the following files:
10
10
 
11
- - `.plain-agent/sandbox/Dockerfile` — Custom Docker image with mise-installed runtimes baked in
12
11
  - `.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
12
  - `.plain-agent/setup.sh` — Initial setup script for both sandbox and host
15
13
 
16
14
  You also show an example `sandbox` config for `.plain-agent/config.json`, but you **never modify** config.json directly.
@@ -21,55 +19,47 @@ Before generating anything, analyze the project to determine:
21
19
 
22
20
  ### 1a. Runtime & Tools
23
21
 
24
- Detect the project type and determine which runtimes to install via mise:
22
+ Detect the project type and determine which runtimes to install via mise. Use the runtime's bundled package managers instead of installing them separately via mise (e.g. Node.js ships with npm; use `corepack enable` for yarn/pnpm).
25
23
 
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 |
24
+ | File found | mise install commands | Version source |
25
+ |---|---|---|
26
+ | `package.json` | `mise use node@<version>` | `.nvmrc` / `.node-version` / `package.json` (`engines.node`) |
27
+ | `requirements.txt` or `pyproject.toml` | `mise use python@<version>` | `.python-version` / `pyproject.toml` (`requires-python`) |
36
28
 
37
29
  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>`
30
+ - `*.tf` files or `.terraform-version` → `mise use terraform@<version>` (version source: `.terraform-version`)
31
+
32
+ If a version cannot be determined from the files above, **ask the user which version to use** rather than falling back to a default.
40
33
 
41
34
  ### 1b. Volume Candidates
42
35
 
43
- Detect directories that should use Docker volumes (for performance with large directories):
36
+ Detect directories that should use Docker volumes. A Docker volume is preferred over a host bind mount for `node_modules` because:
37
+
38
+ - `node_modules` contains many thousands of small files, and bind-mounting it into the container is slow on macOS/Windows (file sync overhead).
39
+ - Native modules compiled for the host OS/arch can be incompatible with the Linux container, so keeping container-side `node_modules` isolated avoids conflicts.
44
40
 
45
41
  | Project type | Cache volumes | Dependency volumes |
46
42
  |---|---|---|
47
- | Node.js | `plain-sandbox--global--home-npm:/home/sandbox/.npm` | `node_modules` (per package.json dir if monorepo) |
43
+ | Node.js | `plain-sandbox--global--home-npm:/home/sandbox/.npm` | `node_modules` (per `package.json` dir if monorepo) |
48
44
  | 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
45
 
52
- For monorepo detection: if multiple `package.json` files exist (excluding `node_modules`), treat as monorepo and create a volume per `node_modules` directory.
46
+ For monorepo detection: if multiple `package.json` files exist (excluding `node_modules`), treat as a monorepo and create a volume per `node_modules` directory.
53
47
 
54
48
  ### 1c. Setup Install Commands
55
49
 
56
50
  | Project type | Install command |
57
51
  |---|---|
58
52
  | 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` |
53
+ | Node.js (yarn) | `corepack enable && yarn install --frozen-lockfile` |
54
+ | Node.js (pnpm) | `corepack enable && pnpm install --frozen-lockfile` |
61
55
  | 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
56
 
67
57
  ## Step 2: Confirm with User
68
58
 
69
59
  Present the analysis results and ask the user to confirm. Show:
70
60
 
71
61
  1. **Detected project type** (e.g., "Node.js with npm")
72
- 2. **mise install commands** that will be added to Dockerfile
62
+ 2. **mise install commands**
73
63
  3. **Volume configuration** (e.g., "node_modules + npm cache")
74
64
  4. **Setup install command** (e.g., "npm ci")
75
65
 
@@ -77,129 +67,9 @@ Ask only one additional question:
77
67
 
78
68
  > Do you want to mount `~/.gitconfig` into the sandbox? (This allows git commit inside the sandbox.)
79
69
 
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.run | 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:
70
+ ## Step 3: Generate run.sh
192
71
 
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):
72
+ Generate `.plain-agent/sandbox/run.sh`. Use the following Node.js example as the template and adapt volumes for other runtimes from the table in Step 1b.
203
73
 
204
74
  ```bash
205
75
  #!/usr/bin/env bash
@@ -207,13 +77,18 @@ fi
207
77
  set -eu -o pipefail
208
78
 
209
79
  options=(
210
- --dockerfile .plain-agent/sandbox/Dockerfile
211
- --env-file .plain-agent/sandbox/env
212
80
  --allow-write
213
81
  --volume plain-sandbox--global--home-npm:/home/sandbox/.npm
214
82
  --volume node_modules
215
83
  )
216
84
 
85
+ # Monorepo: create a volume for each node_modules directory.
86
+ # Include only when multiple package.json files exist.
87
+ # for path in $(fd package.json --max-depth 3 | sed -E 's,package.json$,node_modules,'); do
88
+ # mkdir -p "$path"
89
+ # options+=("--volume" "$path")
90
+ # done
91
+
217
92
  # Mount main worktree if using git worktrees
218
93
  git_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
219
94
  if test -n "$git_root" && test -f "$git_root/.git"; then
@@ -221,7 +96,7 @@ if test -n "$git_root" && test -f "$git_root/.git"; then
221
96
  options+=("--mount-writable" "$main_worktree_path:$main_worktree_path")
222
97
  fi
223
98
 
224
- # Mount gitconfig
99
+ # Mount gitconfig (include only if the user confirmed)
225
100
  if test -f "$HOME/.gitconfig"; then
226
101
  options+=("--mount-readonly" "$HOME/.gitconfig:/home/sandbox/.gitconfig")
227
102
  fi
@@ -229,24 +104,9 @@ fi
229
104
  plain-sandbox "${options[@]}" "$@"
230
105
  ```
231
106
 
232
- ## Step 5: Generate env
107
+ ## Step 4: Generate setup.sh
233
108
 
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`:
109
+ Generate `.plain-agent/setup.sh`. Use the following Node.js example and replace `node@lts` / `npm ci` with the commands chosen in Step 1.
250
110
 
251
111
  ```bash
252
112
  #!/usr/bin/env bash
@@ -255,25 +115,31 @@ set -eu -o pipefail
255
115
 
256
116
  this_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
257
117
 
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>
118
+ # Setup sandbox (install runtime and dependencies with network access)
119
+ "$this_dir/sandbox/run.sh" --verbose --allow-net 0.0.0.0/0 mise use node@lts
120
+ "$this_dir/sandbox/run.sh" --verbose --allow-net 0.0.0.0/0 npm ci
260
121
 
261
- # Setup host (install dependencies on host)
262
- <INSTALL_COMMAND>
122
+ # Setup host
123
+ npm ci
263
124
  ```
264
125
 
265
- Replace `<INSTALL_COMMAND>` with the appropriate command from Step 1c analysis. For multiple project types, include both commands.
126
+ `--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.
266
127
 
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.
128
+ ## Step 5: Show config.json Example
268
129
 
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:
130
+ After generating all files, instruct the user to add the following to their `.plain-agent/config.json`:
275
131
 
132
+ ```json
276
133
  {
134
+ "autoApproval": {
135
+ "patterns": [
136
+ {
137
+ "toolName": "exec_command",
138
+ "input": { "command": { "$regex": "^(gh|docker)$" } },
139
+ "action": "ask"
140
+ }
141
+ ]
142
+ },
277
143
  "sandbox": {
278
144
  "command": ".plain-agent/sandbox/run.sh",
279
145
  "args": ["--skip-build", "--keep-alive", "30"],
@@ -288,18 +154,7 @@ Add the following to your .plain-agent/config.json:
288
154
  }
289
155
  ```
290
156
 
291
- If the project already has a `.plain-agent/config.json`, show only the `sandbox` key that should be added/merged. Remind the user:
157
+ If the project already has a `.plain-agent/config.json`, show only the keys that should be added/merged. Remind the user:
292
158
  - `--skip-build` assumes the image is already built (run `setup.sh` first to build)
293
159
  - `--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
160
+ - `gh` and `docker` run unsandboxed (host access needed), so they should also be set to `ask` in `autoApproval` to avoid being auto-approved alongside other shell commands. Place this `ask` pattern before any broad `allow` pattern for `exec_command`, since `autoApproval` patterns are evaluated in order and the first match wins.
@@ -181,7 +181,7 @@
181
181
  },
182
182
  {
183
183
  "name": "claude-sonnet-4-6",
184
- "variant": "thinking-16k",
184
+ "variant": "thinking-high",
185
185
  "platform": {
186
186
  "name": "anthropic",
187
187
  "variant": "default",
@@ -192,7 +192,8 @@
192
192
  "config": {
193
193
  "model": "claude-sonnet-4-6",
194
194
  "max_tokens": 32768,
195
- "thinking": { "type": "enabled", "budget_tokens": 16384 }
195
+ "thinking": { "type": "adaptive" },
196
+ "output_config": { "effort": "high" }
196
197
  }
197
198
  },
198
199
  "cost": {
@@ -208,7 +209,7 @@
208
209
  },
209
210
  {
210
211
  "name": "claude-sonnet-4-6",
211
- "variant": "thinking-32k",
212
+ "variant": "thinking-max",
212
213
  "platform": {
213
214
  "name": "anthropic",
214
215
  "variant": "default",
@@ -219,7 +220,8 @@
219
220
  "config": {
220
221
  "model": "claude-sonnet-4-6",
221
222
  "max_tokens": 64000,
222
- "thinking": { "type": "enabled", "budget_tokens": 32768 }
223
+ "thinking": { "type": "adaptive" },
224
+ "output_config": { "effort": "max" }
223
225
  }
224
226
  },
225
227
  "cost": {
@@ -234,8 +236,8 @@
234
236
  }
235
237
  },
236
238
  {
237
- "name": "claude-opus-4-6",
238
- "variant": "thinking-16k",
239
+ "name": "claude-opus-4-7",
240
+ "variant": "thinking-high",
239
241
  "platform": {
240
242
  "name": "anthropic",
241
243
  "variant": "default",
@@ -244,9 +246,10 @@
244
246
  "model": {
245
247
  "format": "anthropic",
246
248
  "config": {
247
- "model": "claude-opus-4-6",
249
+ "model": "claude-opus-4-7",
248
250
  "max_tokens": 32768,
249
- "thinking": { "type": "enabled", "budget_tokens": 16384 }
251
+ "thinking": { "type": "adaptive" },
252
+ "output_config": { "effort": "high" }
250
253
  }
251
254
  },
252
255
  "cost": {
@@ -261,8 +264,8 @@
261
264
  }
262
265
  },
263
266
  {
264
- "name": "claude-opus-4-6",
265
- "variant": "thinking-32k",
267
+ "name": "claude-opus-4-7",
268
+ "variant": "thinking-max",
266
269
  "platform": {
267
270
  "name": "anthropic",
268
271
  "variant": "default",
@@ -271,9 +274,10 @@
271
274
  "model": {
272
275
  "format": "anthropic",
273
276
  "config": {
274
- "model": "claude-opus-4-6",
277
+ "model": "claude-opus-4-7",
275
278
  "max_tokens": 64000,
276
- "thinking": { "type": "enabled", "budget_tokens": 32768 }
279
+ "thinking": { "type": "adaptive" },
280
+ "output_config": { "effort": "max" }
277
281
  }
278
282
  },
279
283
  "cost": {
@@ -342,7 +346,7 @@
342
346
  },
343
347
  {
344
348
  "name": "claude-sonnet-4-6",
345
- "variant": "thinking-16k-bedrock",
349
+ "variant": "thinking-high-bedrock",
346
350
  "platform": {
347
351
  "name": "bedrock",
348
352
  "variant": "default"
@@ -352,7 +356,8 @@
352
356
  "config": {
353
357
  "model": "global.anthropic.claude-sonnet-4-6",
354
358
  "max_tokens": 32768,
355
- "thinking": { "type": "enabled", "budget_tokens": 16384 }
359
+ "thinking": { "type": "adaptive" },
360
+ "output_config": { "effort": "high" }
356
361
  }
357
362
  },
358
363
  "cost": {
@@ -368,7 +373,7 @@
368
373
  },
369
374
  {
370
375
  "name": "claude-sonnet-4-6",
371
- "variant": "thinking-32k-bedrock",
376
+ "variant": "thinking-max-bedrock",
372
377
  "platform": {
373
378
  "name": "bedrock",
374
379
  "variant": "default"
@@ -378,7 +383,8 @@
378
383
  "config": {
379
384
  "model": "global.anthropic.claude-sonnet-4-6",
380
385
  "max_tokens": 64000,
381
- "thinking": { "type": "enabled", "budget_tokens": 32768 }
386
+ "thinking": { "type": "adaptive" },
387
+ "output_config": { "effort": "max" }
382
388
  }
383
389
  },
384
390
  "cost": {
@@ -393,8 +399,8 @@
393
399
  }
394
400
  },
395
401
  {
396
- "name": "claude-opus-4-6",
397
- "variant": "thinking-16k-bedrock",
402
+ "name": "claude-opus-4-7",
403
+ "variant": "thinking-high-bedrock",
398
404
  "platform": {
399
405
  "name": "bedrock",
400
406
  "variant": "default"
@@ -402,9 +408,10 @@
402
408
  "model": {
403
409
  "format": "anthropic",
404
410
  "config": {
405
- "model": "global.anthropic.claude-opus-4-6-v1",
411
+ "model": "global.anthropic.claude-opus-4-7",
406
412
  "max_tokens": 32768,
407
- "thinking": { "type": "enabled", "budget_tokens": 16384 }
413
+ "thinking": { "type": "adaptive" },
414
+ "output_config": { "effort": "high" }
408
415
  }
409
416
  },
410
417
  "cost": {
@@ -419,8 +426,8 @@
419
426
  }
420
427
  },
421
428
  {
422
- "name": "claude-opus-4-6",
423
- "variant": "thinking-32k-bedrock",
429
+ "name": "claude-opus-4-7",
430
+ "variant": "thinking-max-bedrock",
424
431
  "platform": {
425
432
  "name": "bedrock",
426
433
  "variant": "default"
@@ -428,9 +435,10 @@
428
435
  "model": {
429
436
  "format": "anthropic",
430
437
  "config": {
431
- "model": "global.anthropic.claude-opus-4-6-v1",
438
+ "model": "global.anthropic.claude-opus-4-7",
432
439
  "max_tokens": 64000,
433
- "thinking": { "type": "enabled", "budget_tokens": 32768 }
440
+ "thinking": { "type": "adaptive" },
441
+ "output_config": { "effort": "max" }
434
442
  }
435
443
  },
436
444
  "cost": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iinm/plain-agent",
3
- "version": "1.7.12",
3
+ "version": "1.7.14",
4
4
  "description": "A lightweight CLI-based coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -78,8 +78,8 @@ Examples:
78
78
  $SCRIPT_NAME --tty --verbose --dry-run zsh
79
79
 
80
80
  Install tools with mise:
81
- $SCRIPT_NAME --allow-net mise-versions.jdx.dev,nodejs.org --verbose mise install node
82
- $SCRIPT_NAME --allow-net mise-versions.jdx.dev,github.com,dl.google.com mise install go
81
+ $SCRIPT_NAME --allow-write --allow-net mise-versions.jdx.dev,nodejs.org \\
82
+ --verbose mise use node@lts
83
83
 
84
84
  Use volume:
85
85
  $SCRIPT_NAME --volume $SCRIPT_NAME--global--home-npm:/home/node/.npm \\
@@ -96,7 +96,7 @@ Examples:
96
96
 
97
97
  Preset Configuration:
98
98
 
99
- When --dockerfile is not specified, a preset Node.js LTS image is used with:
99
+ When --dockerfile is not specified, a preset Debian stable image is used with:
100
100
  - System packages: busybox, bash, zsh (with grml config), ripgrep, fd, dig, curl, git
101
101
  - mise package manager for additional runtime installations
102
102
  - Persistent storage for shell history, git config
@@ -839,20 +839,18 @@ setup_container_user() {
839
839
 
840
840
  print_preset_dockerfile() {
841
841
  cat << 'EOF'
842
- FROM node:lts
842
+ FROM public.ecr.aws/docker/library/debian:stable-slim
843
843
 
844
844
  RUN apt update \
845
845
  && apt install -y \
846
- busybox \
847
- bash zsh \
848
- ripgrep fd-find \
849
- iptables ipset dnsmasq dnsutils \
850
- curl git gpg locales tmux \
846
+ busybox bash zsh locales gpg \
847
+ fd-find ripgrep jq \
848
+ iptables ipset dnsmasq dnsutils curl \
849
+ build-essential git tmux \
851
850
  && bash -c 'ln -s $(which fdfind) /usr/local/bin/fd' \
852
851
  && echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen \
853
852
  && echo 'ja_JP.UTF-8 UTF-8' >> /etc/locale.gen \
854
- && locale-gen \
855
- && npm install -g npm@latest
853
+ && locale-gen
856
854
 
857
855
  # mise: https://mise.jdx.dev/
858
856
  RUN install -dm 755 /etc/apt/keyrings \
@@ -887,8 +885,6 @@ USER_ENTRYPOINT
887
885
  RUN chmod +x /sandbox/bin/user-entrypoint.sh
888
886
 
889
887
  USER sandbox
890
- ENV NPM_CONFIG_PREFIX=/sandbox/npm-global
891
- ENV PATH=/home/sandbox/.local/bin:/sandbox/npm-global/bin:$PATH
892
888
 
893
889
  # Configure shell
894
890
  # - grml zsh config: https://grml.org/zsh/
@@ -52,6 +52,12 @@ export function resolvePastePlaceholders(input) {
52
52
  return text;
53
53
  }
54
54
 
55
+ // Time to wait for a continuation paste chunk before flushing the paste buffer.
56
+ // Some terminals split large pastes into multiple bracketed paste sequences
57
+ // (e.g. `\x1b[200~...\x1b[201~\x1b[200~...\x1b[201~`) that arrive back-to-back.
58
+ // Holding the paste briefly lets us merge them into a single placeholder.
59
+ const PASTE_MERGE_WINDOW_MS = 20;
60
+
55
61
  /**
56
62
  * Create a Transform stream to handle bracketed paste before readline.
57
63
  * @param {() => void} onCtrlC - Called when Ctrl-C or Ctrl-D is detected
@@ -60,8 +66,43 @@ export function resolvePastePlaceholders(input) {
60
66
  export function createPasteTransform(onCtrlC) {
61
67
  let inPasteMode = false;
62
68
  let pasteBuffer = "";
69
+ // True when a paste just ended and we are waiting to see if the next data
70
+ // continues it (i.e. starts with another BRACKETED_PASTE_START).
71
+ let awaitingMerge = false;
72
+ /** @type {NodeJS.Timeout | null} */
73
+ let mergeTimer = null;
74
+ /** @type {Transform} */
75
+ let transform;
76
+
77
+ const clearMergeTimer = () => {
78
+ if (mergeTimer) {
79
+ clearTimeout(mergeTimer);
80
+ mergeTimer = null;
81
+ }
82
+ };
83
+
84
+ const flushPaste = () => {
85
+ clearMergeTimer();
86
+ awaitingMerge = false;
87
+ if (pasteBuffer) {
88
+ // Remove trailing newline for single-line paste detection
89
+ const trimmedPaste = pasteBuffer.replace(/\n$/, "");
90
+
91
+ // For single-line paste, insert directly without placeholder
92
+ if (!trimmedPaste.includes("\n")) {
93
+ transform.push(trimmedPaste);
94
+ } else {
95
+ // For multi-line paste, use placeholder
96
+ const hash = generatePasteHash(pasteBuffer);
97
+ pastedContentStore.set(hash, pasteBuffer);
98
+ const lines = pasteBuffer.split("\n");
99
+ transform.push(`[Pasted text #${hash}, ${lines.length} lines]`);
100
+ }
101
+ }
102
+ pasteBuffer = "";
103
+ };
63
104
 
64
- return new Transform({
105
+ transform = new Transform({
65
106
  transform(chunk, _encoding, callback) {
66
107
  /** @type {string} */
67
108
  let data = chunk.toString("utf8");
@@ -77,32 +118,28 @@ export function createPasteTransform(onCtrlC) {
77
118
  if (inPasteMode) {
78
119
  const endIdx = data.indexOf(BRACKETED_PASTE_END);
79
120
  if (endIdx !== -1) {
80
- // End of paste
121
+ // End of (this chunk of) paste. Hold the buffer briefly in case
122
+ // another paste chunk follows immediately and should be merged.
81
123
  pasteBuffer += data.slice(0, endIdx);
82
124
  data = data.slice(endIdx + BRACKETED_PASTE_END.length);
83
125
  inPasteMode = false;
84
-
85
- // Handle paste content
86
- if (pasteBuffer) {
87
- // Remove trailing newline for single-line paste detection
88
- const trimmedPaste = pasteBuffer.replace(/\n$/, "");
89
-
90
- // For single-line paste, insert directly without placeholder
91
- if (!trimmedPaste.includes("\n")) {
92
- this.push(trimmedPaste);
93
- } else {
94
- // For multi-line paste, use placeholder
95
- const hash = generatePasteHash(pasteBuffer);
96
- pastedContentStore.set(hash, pasteBuffer);
97
- const lines = pasteBuffer.split("\n");
98
- this.push(`[Pasted text #${hash}, ${lines.length} lines]`);
99
- }
100
- }
101
- pasteBuffer = "";
126
+ awaitingMerge = true;
102
127
  } else {
103
128
  // Still in paste mode
104
129
  pasteBuffer += data;
105
- break;
130
+ data = "";
131
+ }
132
+ } else if (awaitingMerge) {
133
+ // If the next data starts with another paste start marker, treat it
134
+ // as a continuation of the previous paste and merge.
135
+ if (data.startsWith(BRACKETED_PASTE_START)) {
136
+ data = data.slice(BRACKETED_PASTE_START.length);
137
+ inPasteMode = true;
138
+ awaitingMerge = false;
139
+ clearMergeTimer();
140
+ } else {
141
+ // Not a continuation; flush pending paste, then process this data.
142
+ flushPaste();
106
143
  }
107
144
  } else {
108
145
  const startIdx = data.indexOf(BRACKETED_PASTE_START);
@@ -118,12 +155,30 @@ export function createPasteTransform(onCtrlC) {
118
155
  } else {
119
156
  // Normal data
120
157
  this.push(data);
121
- break;
158
+ data = "";
122
159
  }
123
160
  }
124
161
  }
125
162
 
163
+ // If the chunk ended while still awaiting a continuation, schedule a
164
+ // short timer to flush the pending paste if nothing else arrives.
165
+ if (awaitingMerge && !mergeTimer) {
166
+ mergeTimer = setTimeout(() => {
167
+ mergeTimer = null;
168
+ flushPaste();
169
+ }, PASTE_MERGE_WINDOW_MS);
170
+ }
171
+
172
+ callback();
173
+ },
174
+
175
+ flush(callback) {
176
+ if (awaitingMerge) {
177
+ flushPaste();
178
+ }
126
179
  callback();
127
180
  },
128
181
  });
182
+
183
+ return transform;
129
184
  }