@czottmann/pi-automode 1.12.0 → 1.14.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 +27 -0
- package/README.md +6 -4
- package/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +2 -0
- package/docs/adr/ADR-002-global-config-in-extension-data-directory.md +60 -0
- package/docs/adr/INDEX.md +1 -0
- package/docs/automode-classifier-flow.md +4 -4
- package/docs/configuration.md +20 -4
- package/docs/observability-logging.md +1 -1
- package/extensions/auto-mode/bash.ts +692 -0
- package/extensions/auto-mode/classifier.ts +55 -4
- package/extensions/auto-mode/config.ts +188 -6
- package/extensions/auto-mode/constants.ts +6 -1
- package/extensions/auto-mode/extension.ts +143 -36
- package/extensions/auto-mode/hard-deny.ts +225 -159
- package/extensions/auto-mode/paths.ts +32 -6
- package/extensions/auto-mode/permissions.ts +344 -22
- package/extensions/auto-mode.ts +1 -0
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [1.14.0] - 2026-08-27
|
|
8
|
+
|
|
9
|
+
## Bug fixes
|
|
10
|
+
|
|
11
|
+
- **Classifier stream timeout** — Apply `classifierTimeoutMs` to the full response stream. Provider behavior cannot keep classifier calls pending after the deadline. Parent cancellation remains active. Reject values above the Node.js timer limit. (#30)
|
|
12
|
+
- **OS temp-directory deletes** — Stop hard-denying recursive-delete subtrees under `os.tmpdir()` and `/tmp`. On macOS these resolve into `/private/tmp` and `/private/var/folders`, which matched the `/private` system root and blocked every temp cleanup. Deleting a temp root itself stays blocked. (#31)
|
|
13
|
+
- **Validated temp-root declarations** — Derive the exempt temp roots only from launcher-declared values that stay safe: reject values such as `/`, empty strings, aliases of `HOME`, `/`, or a system root, and ancestors of `HOME`. Without validation, a malformed `TMPDIR` could disable deterministic denials for protected targets, and a broad `permissions.allow` rule could then allow the action without classifier review. Recompute candidates when the effective tmpdir changes. (#31)
|
|
14
|
+
|
|
15
|
+
## [1.13.0] - 2026-08-25
|
|
16
|
+
|
|
17
|
+
## New features
|
|
18
|
+
|
|
19
|
+
- **Bash AST analysis** — Replace handwritten shell parsing with `unbash`. Permission and hard-deny checks now inspect command structure, nested commands, wrappers, redirects, and malformed input. Bash allow rules require complete structural coverage and fail closed when analysis is unsafe. (#26)
|
|
20
|
+
- **Extension-owned global config** — Store global settings at `~/.pi/agent/extensions/pi-automode/config.json`. Migrate the legacy file automatically, preserve a safe fallback after migration errors, and report conflicts through notifications and diagnostics. (#27)
|
|
21
|
+
|
|
22
|
+
## Bug fixes
|
|
23
|
+
|
|
24
|
+
- **Conservative permission rules** — Malformed deny and ask patterns block actions. Malformed patterns do not expand allow rules. Permission checks examine each Bash subcommand and normalize whitespace. Path checks resolve symlinks and normalize `file://` and Windows paths. (#22)
|
|
25
|
+
- **Recursive deletion hard-deny** — Detect uppercase flags and GNU abbreviations for recursive `rm`, including commands behind `command`, `exec`, and `env`. Parse the `--` delimiter and shell tilde expansion. Protect Linux and macOS system roots without blocking active-home subdirectories, `/opt`, or `/srv`. (#23)
|
|
26
|
+
- **Global Pi safety-control paths** — Hard-deny direct writes and edits to `~/.pi/agent/extensions/`, `~/.pi/agent/settings.json`, and `~/.pi/agent/settings/`. Resolve case variants and symlink targets before matching. (#24)
|
|
27
|
+
- **Case-insensitive protected paths** — Match protected paths without case distinctions and normalize Unicode spellings. This closes path bypasses on case-insensitive filesystems. (#25)
|
|
28
|
+
|
|
5
29
|
## [1.12.0] - 2026-08-23
|
|
6
30
|
|
|
7
31
|
## New features
|
|
@@ -21,4 +45,7 @@ All notable changes to this project are documented in this file.
|
|
|
21
45
|
- **Project config trust gate** — Ignore `.pi/automode.local.json` and `.pi/automode.json` until Pi trusts the project. Apply the trust gate during startup and config reloads. (#16)
|
|
22
46
|
- **In-memory observability logs** — Write logs to an extension-owned directory (`~/.pi/agent/extensions/pi-automode/logs/`) instead of the launching project directory. Thanks, @HerbertGao! (#13)
|
|
23
47
|
|
|
48
|
+
[Unreleased]: https://github.com/czottmann/pi-automode/compare/v1.14.0...HEAD
|
|
49
|
+
[1.14.0]: https://github.com/czottmann/pi-automode/compare/v1.13.0...v1.14.0
|
|
50
|
+
[1.13.0]: https://github.com/czottmann/pi-automode/compare/v1.12.0...v1.13.0
|
|
24
51
|
[1.12.0]: https://github.com/czottmann/pi-automode/compare/v1.11.0...v1.12.0
|
package/README.md
CHANGED
|
@@ -39,8 +39,8 @@ pi -e ./extensions/auto-mode.ts
|
|
|
39
39
|
/automode defaults # print the built-in rule lists
|
|
40
40
|
/automode config # effective config, resolved log file path, + diagnostics
|
|
41
41
|
/automode denials # denial history for this session
|
|
42
|
-
/automode model # open classifier model selector and save to ~/.pi/agent/automode.json
|
|
43
|
-
/automode model provider/model-id # save classifier model to ~/.pi/agent/automode.json
|
|
42
|
+
/automode model # open classifier model selector and save to ~/.pi/agent/extensions/pi-automode/config.json
|
|
43
|
+
/automode model provider/model-id # save classifier model to ~/.pi/agent/extensions/pi-automode/config.json
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
`/auto-mode` is an alias.
|
|
@@ -101,7 +101,7 @@ The extension blocks these before any allow or classifier decision:
|
|
|
101
101
|
- SSH `authorized_keys` writes
|
|
102
102
|
- cron, launch agent, and system service persistence
|
|
103
103
|
- TLS/certificate/auth weakening patterns
|
|
104
|
-
- root, home, and system-path destructive deletes
|
|
104
|
+
- root, home, and system-path destructive deletes. Subtrees of validated launcher-declared temp directories are treated as disposable. Declared roots that alias `HOME`, `/`, or a system root, or that contain `HOME`, are rejected instead.
|
|
105
105
|
- edits to `.pi/automode*`, `.pi` auto-mode files, and this extension's safety-control files
|
|
106
106
|
|
|
107
107
|
After these checks, pi-automode applies `permissions.allow`. Protected `write` and `edit` targets continue to the classifier.
|
|
@@ -124,6 +124,8 @@ Both stages receive the complete current tool input in a dedicated message. Tran
|
|
|
124
124
|
|
|
125
125
|
Both stages use a classifier-specific session key. They request short cache retention from providers that support it. A missing model, provider failure, or malformed response blocks the action.
|
|
126
126
|
|
|
127
|
+
Pi-automode parses Bash structure with `unbash` before permission and deterministic hard-deny checks. The analysis includes nested commands and literal shell-wrapper scripts. A Bash parse error blocks the action.
|
|
128
|
+
|
|
127
129
|
## Examples
|
|
128
130
|
|
|
129
131
|
- `examples/automode.local.json`: copy to `.pi/automode.local.json` in a project and edit the domains, buckets, and source-control org.
|
|
@@ -146,7 +148,7 @@ The tests cover these safety-sensitive areas:
|
|
|
146
148
|
- the `permissions.allow` tier and its precedence
|
|
147
149
|
- configuration-source precedence and diagnostics
|
|
148
150
|
- `$defaults` behavior
|
|
149
|
-
- deterministic hard-deny checks and
|
|
151
|
+
- deterministic hard-deny checks and Bash AST analysis
|
|
150
152
|
- classifier routing for `write` and `edit`
|
|
151
153
|
- symlink-aware safety-control checks
|
|
152
154
|
- token-budgeted transcript selection
|
|
@@ -31,6 +31,8 @@ Pi-automode reads `permissions.allow` only from user-owned configuration sources
|
|
|
31
31
|
|
|
32
32
|
Shared `.pi/automode.json` can add deny and ask rules after project trust. It cannot add allow rules.
|
|
33
33
|
|
|
34
|
+
[ADR-002](ADR-002-global-config-in-extension-data-directory.md) replaces only the global configuration path. Permission precedence and trust boundaries stay unchanged.
|
|
35
|
+
|
|
34
36
|
For an oversized matcher input, deny and ask rules return a match. Allow rules return no match. This behavior keeps denial rules fail-closed without broadening allow rules.
|
|
35
37
|
|
|
36
38
|
## Consequences
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# ADR-002: Store the global config in the extension data directory
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-25
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Pi-automode stores its global configuration at `~/.pi/agent/automode.json`. The extension stores application-owned logs below `~/.pi/agent/extensions/pi-automode/logs/`.
|
|
8
|
+
|
|
9
|
+
The separate global configuration path makes extension-owned data harder to identify and manage. The global configuration belongs beside the existing log directory.
|
|
10
|
+
|
|
11
|
+
An existing installation can contain the legacy file. Migration must not discard configuration data or create different read and write sources.
|
|
12
|
+
|
|
13
|
+
This decision refines the global user-owned configuration source from [ADR-001](ADR-001-permission-precedence-and-trust-boundaries.md). It does not change permission precedence or project trust boundaries.
|
|
14
|
+
|
|
15
|
+
Tracking issue: [#27](https://github.com/czottmann/pi-automode/issues/27)
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Pi-automode stores the global configuration at:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
~/.pi/agent/extensions/pi-automode/config.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The existing log directory stays at:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
~/.pi/agent/extensions/pi-automode/logs/
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Project configuration files keep their current paths and semantics:
|
|
32
|
+
|
|
33
|
+
- `.pi/automode.local.json`
|
|
34
|
+
- `.pi/automode.json`
|
|
35
|
+
|
|
36
|
+
At startup, pi-automode selects one active global configuration path for the session:
|
|
37
|
+
|
|
38
|
+
1. If only the new file is present, pi-automode uses the new file.
|
|
39
|
+
2. If only the legacy file is present, pi-automode moves it to the new path before loading configuration.
|
|
40
|
+
3. If the two files are present, pi-automode uses the new file and does not change the legacy file.
|
|
41
|
+
4. If migration fails, pi-automode uses the legacy file for reads and writes during that session.
|
|
42
|
+
5. If neither file is present, pi-automode uses the new path for the next write.
|
|
43
|
+
|
|
44
|
+
Pi-automode attempts migration once per session. A later session retries a failed migration.
|
|
45
|
+
|
|
46
|
+
A successful migration produces one UI notification. A path conflict produces a warning during every startup. A migration error produces one warning per session.
|
|
47
|
+
|
|
48
|
+
Conflict and migration error details also appear in configuration diagnostics. This behavior makes the details available in headless sessions.
|
|
49
|
+
|
|
50
|
+
## Consequences
|
|
51
|
+
|
|
52
|
+
All extension-owned global data has one identifiable root directory. Configuration backups and extension cleanup become easier to understand.
|
|
53
|
+
|
|
54
|
+
The new configuration inherits the deterministic protection for `~/.pi/agent/extensions/`. Direct agent file tools cannot modify the configuration.
|
|
55
|
+
|
|
56
|
+
The extension must pass the selected active path to configuration reads and writes. This requirement prevents split-brain configuration during migration errors.
|
|
57
|
+
|
|
58
|
+
The migration is one-way. A downgraded pi-automode version does not automatically find the new configuration path.
|
|
59
|
+
|
|
60
|
+
Startup gains a small filesystem operation and migration state. Tests must cover migration, conflicts, errors, notifications, diagnostics, and unchanged project precedence.
|
package/docs/adr/INDEX.md
CHANGED
|
@@ -3,3 +3,4 @@
|
|
|
3
3
|
| # | Decision | Date |
|
|
4
4
|
|---|----------|------|
|
|
5
5
|
| [ADR-001](ADR-001-permission-precedence-and-trust-boundaries.md) | Permission precedence and configuration trust boundaries | 2026-08-23 |
|
|
6
|
+
| [ADR-002](ADR-002-global-config-in-extension-data-directory.md) | Store the global config in the extension data directory | 2026-08-25 |
|
|
@@ -89,7 +89,7 @@ Pi-automode loads global and inline configuration during extension initializatio
|
|
|
89
89
|
|
|
90
90
|
The effective configuration combines these sources:
|
|
91
91
|
|
|
92
|
-
- `~/.pi/agent/automode.json`
|
|
92
|
+
- `~/.pi/agent/extensions/pi-automode/config.json`
|
|
93
93
|
- `.pi/automode.local.json` for trusted projects
|
|
94
94
|
- `PI_AUTOMODE_SETTINGS_JSON`
|
|
95
95
|
- shared `.pi/automode.json` for trusted projects, but only for `permissions.deny` and `permissions.ask`
|
|
@@ -195,7 +195,7 @@ Current deterministic blocks include these actions:
|
|
|
195
195
|
- dangerous recursive deletes of root, home, or system paths
|
|
196
196
|
- selected system or SSH permission mutations
|
|
197
197
|
|
|
198
|
-
The `bash` checks use
|
|
198
|
+
The `bash` checks use the `unbash` abstract syntax tree. They inspect chains, pipelines, compound commands, substitutions, redirects, and literal shell-wrapper scripts.
|
|
199
199
|
|
|
200
200
|
Recursive-delete checks hard-deny `/`, the user home root, and top-level system roots. They exempt subpaths of the user home because these paths contain user data.
|
|
201
201
|
|
|
@@ -324,7 +324,7 @@ Pi-automode selects the classifier model in this order:
|
|
|
324
324
|
1. `autoMode.classifierModel` from configuration
|
|
325
325
|
2. the current Pi session model.
|
|
326
326
|
|
|
327
|
-
`/automode model provider/model-id` and the interactive model picker save `autoMode.classifierModel` to `~/.pi/agent/automode.json`. Project-local `.pi/automode.local.json` can still override that global choice.
|
|
327
|
+
`/automode model provider/model-id` and the interactive model picker save `autoMode.classifierModel` to `~/.pi/agent/extensions/pi-automode/config.json`. Project-local `.pi/automode.local.json` can still override that global choice.
|
|
328
328
|
|
|
329
329
|
`autoMode.classifierReasoningLevel` can request `low`, `medium`, `high`, `xhigh`, or `max` reasoning for both stages.
|
|
330
330
|
|
|
@@ -424,4 +424,4 @@ The classifier flow can be inspected or changed through slash commands:
|
|
|
424
424
|
|
|
425
425
|
`/auto-mode` is an alias.
|
|
426
426
|
|
|
427
|
-
`/automode off` disables the whole flow for the current session. `/automode on` re-enables it. `/automode model` saves the classifier model to `~/.pi/agent/automode.json`.
|
|
427
|
+
`/automode off` disables the whole flow for the current session. `/automode on` re-enables it. `/automode model` saves the classifier model to `~/.pi/agent/extensions/pi-automode/config.json`.
|
package/docs/configuration.md
CHANGED
|
@@ -4,10 +4,12 @@ The extension follows the documented Claude Code configuration model where Pi su
|
|
|
4
4
|
|
|
5
5
|
It reads `autoMode` only from Pi-owned configuration sources:
|
|
6
6
|
|
|
7
|
-
- `~/.pi/agent/automode.json`
|
|
7
|
+
- `~/.pi/agent/extensions/pi-automode/config.json`
|
|
8
8
|
- `.pi/automode.local.json` for trusted projects
|
|
9
9
|
- `PI_AUTOMODE_SETTINGS_JSON`
|
|
10
10
|
|
|
11
|
+
At startup, pi-automode moves a legacy `~/.pi/agent/automode.json` file to the new global path. If both files exist, it uses the new file and reports the conflict. If migration fails, it uses the legacy file for that session and reports the error.
|
|
12
|
+
|
|
11
13
|
It does not read project configuration until Pi trusts the project. For an untrusted project, it ignores `.pi/automode.local.json` and `.pi/automode.json`. `/automode config` reports each ignored file that exists.
|
|
12
14
|
|
|
13
15
|
Shared project `.pi/automode.json` cannot weaken auto mode. For a trusted project, it can add `permissions.deny` and `permissions.ask` rules.
|
|
@@ -26,7 +28,7 @@ To disable pi-automode for the current project, create or edit `.pi/automode.loc
|
|
|
26
28
|
|
|
27
29
|
This file is project-local. Pi reads it only after project trust. Do not commit this file. Shared project `.pi/automode.json` cannot disable auto mode.
|
|
28
30
|
|
|
29
|
-
Set a global default classifier model in `~/.pi/agent/automode.json`. For a trusted project, override it in `.pi/automode.local.json`.
|
|
31
|
+
Set a global default classifier model in `~/.pi/agent/extensions/pi-automode/config.json`. For a trusted project, override it in `.pi/automode.local.json`.
|
|
30
32
|
|
|
31
33
|
`classifierReasoningLevel` requests `low`, `medium`, `high`, `xhigh`, or `max` reasoning for both classifier stages. If the key is absent, pi-automode sends no reasoning preference. The server then selects the level.
|
|
32
34
|
|
|
@@ -140,7 +142,19 @@ Permission patterns use Pi tool names. Examples include `bash(...)`, `write(...)
|
|
|
140
142
|
|
|
141
143
|
Use this tier for a narrow command such as `bash(git status*)`. You can also use it for a side-effect-free extension or MCP tool.
|
|
142
144
|
|
|
143
|
-
The matcher understands primary arguments for `bash`, the file tools, and `grep`. For
|
|
145
|
+
The matcher understands primary arguments for `bash`, the file tools, and `grep`. For file tools, it uses the resolved `input.path`. For `grep`, it uses `input.pattern`.
|
|
146
|
+
|
|
147
|
+
For `bash`, pi-automode parses `input.command` with `unbash`. Deny and ask rules inspect each executable command in the Bash syntax tree. This includes pipelines, logical chains, compound commands, substitutions, and literal scripts passed to `bash -c`, `sh -c`, or `eval`. The analysis also follows these literal shell scripts through transparent `command`, `exec`, and `env` dispatch.
|
|
148
|
+
|
|
149
|
+
The matcher normalizes whitespace between Bash tokens. It preserves whitespace and quoting inside each token. Thus, `bash(git push*)` matches `git push origin main`. Quoted operators do not create extra commands.
|
|
150
|
+
|
|
151
|
+
A Bash allow decision requires coverage for each executable command. A multi-command pattern must match the same AST structure and operators. This structure check also applies to one command inside a group, wrapper, control structure, or background statement. Separate single-command patterns only cover top-level foreground chains and plain pipelines. Other supported structure requires one matching structural pattern. A multi-command pattern must match the same number of commands in the same order. Each pattern command must match its corresponding input command. One wildcard cannot hide an additional command or a different operator.
|
|
152
|
+
|
|
153
|
+
A redirect requires explicit coverage in the allow pattern. The redirect operator, file descriptor, variable name, and target pattern must match. Here-documents and dynamic redirect targets continue to the classifier. Parser errors, dynamic command names, and dynamic wrapper scripts cannot use `permissions.allow`.
|
|
154
|
+
|
|
155
|
+
Control nodes with unrepresented semantic values cannot use `permissions.allow`. This includes loops, functions, coprocesses, case statements, test commands, and arithmetic commands. These scripts continue to the classifier.
|
|
156
|
+
|
|
157
|
+
Pi-automode does not execute shell expansions. It cannot resolve aliases, variables, generated scripts, or dynamic `eval` input. These calls continue to the classifier unless a deterministic rule blocks them.
|
|
144
158
|
|
|
145
159
|
For other tools, the matcher uses the serialized input object. Use a bare tool name for an MCP or extension tool. For example, `example-extension-tool` matches every call to that tool.
|
|
146
160
|
|
|
@@ -150,6 +164,8 @@ A match skips only the classifier call. It cannot skip `permissions.deny`, deter
|
|
|
150
164
|
|
|
151
165
|
Pi-automode reads `permissions.allow` only from global configuration, trusted `.pi/automode.local.json`, and `PI_AUTOMODE_SETTINGS_JSON`. Shared `.pi/automode.json` cannot add allow rules.
|
|
152
166
|
|
|
153
|
-
A pattern can contain at most 4,096 UTF-16 code units.
|
|
167
|
+
A pattern can contain at most 4,096 UTF-16 code units. Bash analysis accepts at most 1,048,576 UTF-16 code units. A longer Bash input is blocked before parsing.
|
|
168
|
+
|
|
169
|
+
For other allow matching, an input can contain at most 1,048,576 UTF-16 code units. A longer input returns no match. Deny and ask patterns match the same oversized input so that they fail closed.
|
|
154
170
|
|
|
155
171
|
`write` and `edit` calls whose resolved target is a protected path are never covered by `permissions.allow`. This includes protected targets reached through symlink aliases.
|
|
@@ -6,7 +6,7 @@ Logging is off by default. Logging fails open, so a write error never changes an
|
|
|
6
6
|
|
|
7
7
|
## Enabling
|
|
8
8
|
|
|
9
|
-
Set `autoMode.log` in a Pi-owned configuration source. These sources include `~/.pi/agent/automode.json`, trusted `.pi/automode.local.json`, and `PI_AUTOMODE_SETTINGS_JSON`.
|
|
9
|
+
Set `autoMode.log` in a Pi-owned configuration source. These sources include `~/.pi/agent/extensions/pi-automode/config.json`, trusted `.pi/automode.local.json`, and `PI_AUTOMODE_SETTINGS_JSON`.
|
|
10
10
|
|
|
11
11
|
```json
|
|
12
12
|
{
|