@iinm/plain-agent 1.10.24 → 1.10.26

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
@@ -50,14 +50,9 @@ Configure what the agent can do automatically using a small DSL with regex match
50
50
  // Require --method to be explicit, so GET calls can be safely auto-approved
51
51
  {
52
52
  "toolName": "exec_command",
53
- "input": { "command": "gh", "args": ["api", "--method"] },
54
- "action": "ask"
55
- },
56
- {
57
- "toolName": "exec_command",
58
- "input": { "command": "gh", "args": ["api"] },
53
+ "input": { "command": "gh", "args": ["api", { "$not": { "$regex": "^--(method|help)" } }] },
59
54
  "action": "deny",
60
- "reason": "--method must be specified"
55
+ "reason": "--method must be specified right after 'api'"
61
56
  }
62
57
  ]
63
58
  }
@@ -72,7 +67,7 @@ String values in tool inputs are treated as file paths and validated against the
72
67
  - No directory traversal (`..` is not allowed)
73
68
  - The file must be tracked by Git (not ignored)
74
69
 
75
- **Note**: validation only applies when the agent explicitly passes file paths to tools. It cannot catch file access inside scripts the agent writes — something like `bash -c "rm -rf /"` is beyond its reach. Always use a sandbox when allowing arbitrary script execution.
70
+ **Note**: validation only applies when the agent explicitly passes file paths to tools. It cannot catch file access inside scripts the agent writes — something like `bash -c "rm -rf /"` is beyond its reach. Always use a sandbox when auto-approving script execution.
76
71
 
77
72
  ### Sandbox
78
73
 
@@ -81,11 +76,12 @@ A Docker-based wrapper called `plain-sandbox` is included, but the interface is
81
76
 
82
77
  ```js
83
78
  {
84
- // Sandbox environment for the exec_command and tmux_command tools
85
79
  "sandbox": {
86
80
  // Commands are wrapped and executed with this command
81
+ // Build the image before first use: plain-sandbox --verbose echo done
87
82
  "command": "plain-sandbox",
88
- "args": ["--allow-write", "--skip-build", "--keep-alive", "30"],
83
+ "args": ["--allow-write", "--mount-readonly", ".plain-agent/config.json", "--skip-build", "--keep-alive", "30"],
84
+ // ↑ --mount-readonly: prevents the agent from overwriting its own config
89
85
  // separator is inserted between sandbox flags and the user command to prevent bypasses
90
86
  "separator": "--",
91
87
 
@@ -117,7 +113,6 @@ A few design choices keep token usage low:
117
113
 
118
114
  - Minimal system prompt — the [system prompt](https://github.com/iinm/plain-agent/blob/main/src/prompt.mjs) contains only what the agent needs to function.
119
115
  - Output truncation — when a command or MCP tool produces large output, it is truncated and saved to a file. The agent can then read only the relevant parts.
120
- - (Experimental) [Hashline-based](https://blog.can.ac/2026/02/12/the-harness-problem/) patch_file tool.
121
116
 
122
117
  ### Claude Code Compatibility
123
118
 
@@ -457,6 +452,31 @@ Files are loaded in the following order. Settings in later files override earlie
457
452
  └── agents/ # Project-specific agent roles
458
453
  ```
459
454
 
455
+
456
+ <details>
457
+ <summary><b>Minimal example (file editing and web search only — no script execution, no sandbox required)</b></summary>
458
+
459
+ ```js
460
+ {
461
+ "autoApproval": {
462
+ "defaultAction": "ask",
463
+ "maxApprovals": 100,
464
+ "patterns": [
465
+ {
466
+ "toolName": { "$regex": "^(write_file|patch_file)$" },
467
+ "action": "allow"
468
+ },
469
+ {
470
+ "toolName": { "$regex": "^(web_search|web_fetch)$" },
471
+ "action": "allow"
472
+ }
473
+ ]
474
+ }
475
+ }
476
+ ```
477
+
478
+ </details>
479
+
460
480
  <details>
461
481
  <summary><b>YOLO mode example (requires a sandbox for safety)</b></summary>
462
482
 
@@ -472,11 +492,11 @@ Files are loaded in the following order. Settings in later files override earlie
472
492
  "action": "allow"
473
493
  },
474
494
  {
475
- "toolName": "exec_command",
495
+ "toolName": { "$regex": "^(web_search|web_fetch)$" },
476
496
  "action": "allow"
477
497
  },
478
498
  {
479
- "toolName": { "$regex": "^(web_search|web_fetch)$" },
499
+ "toolName": "exec_command",
480
500
  "action": "allow"
481
501
  }
482
502
  // ⚠️ Never do this. MCP runs outside the sandbox, so it can send anything externally.
@@ -487,8 +507,10 @@ Files are loaded in the following order. Settings in later files override earlie
487
507
  ]
488
508
  },
489
509
  "sandbox": {
510
+ // ⚠️ Build the image before first use: plain-sandbox --verbose echo done
490
511
  "command": "plain-sandbox",
491
- "args": ["--allow-write", "--skip-build", "--keep-alive", "30"],
512
+ "args": ["--allow-write", "--mount-readonly", ".plain-agent/config.json", "--skip-build", "--keep-alive", "30"],
513
+ // ↑ --mount-readonly: prevents the agent from overwriting its own config
492
514
  "separator": "--"
493
515
  }
494
516
  }
@@ -518,7 +540,7 @@ Files are loaded in the following order. Settings in later files override earlie
518
540
  "action": "allow"
519
541
  },
520
542
 
521
- // ⚠️ Arbitrary code execution can access unauthorized files and networks. Always use a sandbox.
543
+ // ⚠️ When auto-approving execution, scripts can access unauthorized files and networks. Always use a sandbox.
522
544
  {
523
545
  "toolName": "exec_command",
524
546
  "input": { "command": "npm", "args": ["run", { "$regex": "^(lint|test)$" }] },
@@ -549,8 +571,10 @@ Files are loaded in the following order. Settings in later files override earlie
549
571
  // Sandbox environment for the exec_command and tmux_command tools
550
572
  "sandbox": {
551
573
  // Commands are wrapped and executed with this command
574
+ // Build the image before first use: plain-sandbox --verbose echo done
552
575
  "command": "plain-sandbox",
553
- "args": ["--allow-write", "--skip-build", "--keep-alive", "30"],
576
+ "args": ["--allow-write", "--mount-readonly", ".plain-agent/config.json", "--skip-build", "--keep-alive", "30"],
577
+ // ↑ --mount-readonly: prevents the agent from overwriting its own config
554
578
  // separator is inserted between sandbox flags and the user command to prevent bypasses
555
579
  "separator": "--",
556
580
 
@@ -749,33 +773,6 @@ Press **Ctrl-O** to start recording, then press it again to stop. Partial transc
749
773
  is a letter (a-z) or one of `[ \ ] ^ _`. Defaults to `"ctrl-o"`.
750
774
  - `recorder` — Override automatic recorder detection, e.g. `{ "command": "sox", "args": ["-q", "-d", "-b", "16", "-c", "1", "-r", "24000", "-e", "signed-integer", "-t", "raw", "-"] }`. It must write raw 16-bit little-endian mono PCM to stdout at 24 kHz (OpenAI) or 16 kHz (Gemini).
751
775
 
752
- ## Development
753
-
754
- ```sh
755
- # Run lint, typecheck, and tests
756
- npm run check
757
-
758
- # Fix lint issues
759
- npm run fix
760
- # or
761
- npm run fix -- --unsafe
762
-
763
- # Update dependencies
764
- npx npm-check-updates -t minor -c 3
765
- npx npm-check-updates -t minor -c 3 -u
766
- ```
767
-
768
- ## Release
769
-
770
- ```sh
771
- npm version <major|minor|patch>
772
-
773
- git push --follow-tags
774
- gh release create $(git describe --tags) --generate-notes
775
-
776
- npm publish --access public
777
- ```
778
-
779
776
  ## Appendix: Creating Least-Privilege Users for Cloud Providers
780
777
 
781
778
  <details>
@@ -921,3 +918,19 @@ gcloud iam service-accounts add-iam-policy-binding "$service_account_email" \
921
918
  gcloud auth print-access-token --impersonate-service-account "$service_account_email"
922
919
  ```
923
920
  </details>
921
+
922
+ ## Developer Notes
923
+
924
+ <details>
925
+ <summary>Release</summary>
926
+
927
+ ```sh
928
+ npm version <major|minor|patch>
929
+
930
+ git push --follow-tags
931
+ gh release create $(git describe --tags) --generate-notes
932
+
933
+ npm publish --access public
934
+ ```
935
+ </details>
936
+
@@ -139,18 +139,10 @@
139
139
  "toolName": "exec_command",
140
140
  "input": {
141
141
  "command": "gh",
142
- "args": ["api", "--method"]
143
- },
144
- "action": "ask"
145
- },
146
- {
147
- "toolName": "exec_command",
148
- "input": {
149
- "command": "gh",
150
- "args": ["api"]
142
+ "args": ["api", { "$not": { "$regex": "^--(method|help)" } }]
151
143
  },
152
144
  "action": "deny",
153
- "reason": "--method must be specified"
145
+ "reason": "--method must be specified right after 'api'"
154
146
  }
155
147
  ]
156
148
  },
@@ -1369,6 +1361,29 @@
1369
1361
  }
1370
1362
  }
1371
1363
  },
1364
+ {
1365
+ "name": "minimax-m3",
1366
+ "variant": "novita",
1367
+ "platform": {
1368
+ "name": "openai-compatible",
1369
+ "variant": "novita"
1370
+ },
1371
+ "model": {
1372
+ "format": "openai-messages",
1373
+ "config": {
1374
+ "model": "minimax/minimax-m3"
1375
+ }
1376
+ },
1377
+ "cost": {
1378
+ "currency": "USD",
1379
+ "unit": "1M",
1380
+ "costs": {
1381
+ "prompt_tokens": 0.3,
1382
+ "prompt_tokens_details.cached_tokens": -0.24,
1383
+ "completion_tokens": 1.2
1384
+ }
1385
+ }
1386
+ },
1372
1387
 
1373
1388
  {
1374
1389
  "name": "qwen3.6-plus",
@@ -1415,6 +1430,29 @@
1415
1430
  }
1416
1431
  }
1417
1432
  },
1433
+ {
1434
+ "name": "qwen3.7-max",
1435
+ "variant": "novita",
1436
+ "platform": {
1437
+ "name": "openai-compatible",
1438
+ "variant": "novita"
1439
+ },
1440
+ "model": {
1441
+ "format": "openai-messages",
1442
+ "config": {
1443
+ "model": "qwen/qwen3.7-max"
1444
+ }
1445
+ },
1446
+ "cost": {
1447
+ "currency": "USD",
1448
+ "unit": "1M",
1449
+ "costs": {
1450
+ "prompt_tokens": 1.5625,
1451
+ "prompt_tokens_details.cached_tokens": -1.4375,
1452
+ "completion_tokens": 3.75
1453
+ }
1454
+ }
1455
+ },
1418
1456
 
1419
1457
  {
1420
1458
  "name": "nova-2-lite",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iinm/plain-agent",
3
- "version": "1.10.24",
3
+ "version": "1.10.26",
4
4
  "description": "A lightweight terminal-based coding agent focused on safety and low token cost",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -391,6 +391,8 @@ export function startInteractiveSession({
391
391
 
392
392
  // Handle readline close (e.g., stdin closed externally)
393
393
  cli.on("close", handleExit);
394
+ process.on("SIGTERM", handleExit);
395
+ process.on("SIGHUP", handleExit);
394
396
 
395
397
  const handleCommand = createCommandHandler({
396
398
  agentCommands,
package/src/main.mjs CHANGED
@@ -165,6 +165,7 @@ export async function main(argv = process.argv) {
165
165
  const sandboxStr = [
166
166
  appConfig.sandbox.command,
167
167
  ...(appConfig.sandbox.args || []),
168
+ ...(appConfig.sandbox.separator ? [appConfig.sandbox.separator] : []),
168
169
  ].join(" ");
169
170
  console.log(styleText("green", "\n📦 Sandbox: on"));
170
171
  console.log(` ⤷ ${sandboxStr}`);