@ibartel74/pi-automode-ext 1.0.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 +81 -0
- package/LICENSE.md +22 -0
- package/README.md +262 -0
- package/docs/GLOSSARY.md +41 -0
- package/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +46 -0
- package/docs/adr/ADR-002-global-config-in-extension-data-directory.md +60 -0
- package/docs/adr/INDEX.md +6 -0
- package/docs/automode-classifier-flow.md +449 -0
- package/docs/configuration.md +226 -0
- package/docs/defaults.md +178 -0
- package/docs/diagnostics.md +90 -0
- package/docs/observability-logging.md +160 -0
- package/examples/automode.local.json +45 -0
- package/extensions/auto-mode/bash.ts +692 -0
- package/extensions/auto-mode/classifier.ts +940 -0
- package/extensions/auto-mode/config.ts +948 -0
- package/extensions/auto-mode/constants.ts +232 -0
- package/extensions/auto-mode/extension.ts +1118 -0
- package/extensions/auto-mode/hard-deny.ts +429 -0
- package/extensions/auto-mode/jev.ts +338 -0
- package/extensions/auto-mode/log.ts +173 -0
- package/extensions/auto-mode/model-selector.ts +113 -0
- package/extensions/auto-mode/model.ts +13 -0
- package/extensions/auto-mode/paths.ts +303 -0
- package/extensions/auto-mode/permissions.ts +667 -0
- package/extensions/auto-mode/state.ts +106 -0
- package/extensions/auto-mode/transcript.ts +236 -0
- package/extensions/auto-mode/types.ts +210 -0
- package/extensions/auto-mode/utils.ts +54 -0
- package/extensions/auto-mode.ts +27 -0
- package/package.json +61 -0
- package/skills/automode-diagnostics/SKILL.md +63 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This is a fork of [`czottmann/pi-automode`](https://github.com/czottmann/pi-automode) (MIT). 1.0.0 is this fork's first release; it includes upstream 1.16.0 plus the changes below. Earlier version links point at the upstream repository.
|
|
4
|
+
|
|
5
|
+
All notable changes to this project are documented in this file.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.0.0] - 2026-09-20
|
|
10
|
+
|
|
11
|
+
## New features
|
|
12
|
+
|
|
13
|
+
- **Jev classifier backend (OpenRouter + TypeSafe-native)** — Classify with TypeSafe's Jev decision model. One typed-questions call replaces both LLM stages; decisions are computed locally from calibrated probabilities and fail closed. Two transports: `openrouter/typesafe/jev-1.13` uses OpenRouter's Decisions API (key from `OPENROUTER_API_KEY` or a registered `openrouter` provider key; the alpha endpoint intermittently answers a valid key with 401 "User not found", so a 401 is retried once before failing closed), and `typesafe/jev-latest` calls TypeSafe's native System One API directly (`TYPESAFE_API_KEY` or a registered `typesafe` provider key).
|
|
14
|
+
- **Interactive confirmation of classifier blocks** — Classifier blocks (`soft_deny`, `hard_deny`, and fail-closed errors) now prompt the user for confirmation when a UI is available instead of stopping the agent outright, like Claude Code's auto mode. Approval allows the action and is recorded as a `user-confirmed` decision with a `uc:` status counter; declining or running without a UI blocks as before. On by default; opt out with `autoMode.interactiveConfirm: false`. Deterministic denies remain unconditional.
|
|
15
|
+
- **Persistent approvals in the confirmation dialog** — The classifier-block dialog now offers "Always allow" choices that persist an exact-match `permissions.allow` rule to the global config or the project-local `.pi/automode.local.json`, reloading the effective config so the rule applies immediately.
|
|
16
|
+
- **Dev checkouts editable under auto mode** — The deterministic safety-control path check no longer hard-denies every path containing `/pi-automode/`; it now matches installed locations (the `~/.pi/agent/extensions/` prefix and `node_modules` vendoring), so a source checkout of this extension stays editable under auto mode.
|
|
17
|
+
|
|
18
|
+
## [1.16.0] - 2026-09-07
|
|
19
|
+
|
|
20
|
+
## New features
|
|
21
|
+
|
|
22
|
+
- **[Bounded existing-file authorization](docs/defaults.md#soft_deny)** — Allow user authorization for pre-existing local-file changes that names task, worktree, path scope, and allowed operation. (#32)
|
|
23
|
+
|
|
24
|
+
## Bug fixes
|
|
25
|
+
|
|
26
|
+
- **OpenCode classifier routing** — Add OpenCode session headers to every classifier completion path. Preserve Pi's header precedence and exact host matching. (#35)
|
|
27
|
+
- **OMP 18 project trust compatibility** — Treat runtimes without `isProjectTrusted()` as untrusted. Prevent startup and configuration commands from throwing. (#34)
|
|
28
|
+
|
|
29
|
+
## [1.15.0] - 2026-08-28
|
|
30
|
+
|
|
31
|
+
## Bug fixes
|
|
32
|
+
|
|
33
|
+
- **OMP 18 classifier compatibility** — Support OMP 18 model registries that lack `complete()` and `getProvider()`. Load the legacy completion API only for these registries. Keep current Pi on its runtime registry path so extension-registered providers remain available. Thanks, @NarryG! (#29)
|
|
34
|
+
|
|
35
|
+
## [1.14.0] - 2026-08-27
|
|
36
|
+
|
|
37
|
+
## Bug fixes
|
|
38
|
+
|
|
39
|
+
- **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)
|
|
40
|
+
- **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)
|
|
41
|
+
- **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)
|
|
42
|
+
|
|
43
|
+
## [1.13.0] - 2026-08-25
|
|
44
|
+
|
|
45
|
+
## New features
|
|
46
|
+
|
|
47
|
+
- **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)
|
|
48
|
+
- **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)
|
|
49
|
+
|
|
50
|
+
## Bug fixes
|
|
51
|
+
|
|
52
|
+
- **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)
|
|
53
|
+
- **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)
|
|
54
|
+
- **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)
|
|
55
|
+
- **Case-insensitive protected paths** — Match protected paths without case distinctions and normalize Unicode spellings. This closes path bypasses on case-insensitive filesystems. (#25)
|
|
56
|
+
|
|
57
|
+
## [1.12.0] - 2026-08-23
|
|
58
|
+
|
|
59
|
+
## New features
|
|
60
|
+
|
|
61
|
+
- **Deterministic permission allows** — Add user-owned `permissions.allow` patterns that skip classifier review after deterministic checks pass. Accepted ask rules still require classifier review. Thank you, @sergeykonkin! (#14)
|
|
62
|
+
- **Configurable classifier request timeout** — new `autoMode.classifierTimeoutMs` setting. The default is 20000 ms. The timeout applies to each classifier request. The fast stage and the detailed stage each have their own budget. A request that exceeds the timeout is aborted. Auto mode fails closed and blocks the action.
|
|
63
|
+
- **Read-only agent diagnostics** — Add `automode_inspect` tool for status, configuration, defaults, and recent denial metadata. Thank you, @blalor! (#11)
|
|
64
|
+
|
|
65
|
+
## Bug fixes
|
|
66
|
+
|
|
67
|
+
- **Reject invalid config values** — Invalid boolean and log config values (e.g. `enabled: 0`, `log.enabled: 1`) are now rejected at merge time instead of being applied with diagnostics only. (#20)
|
|
68
|
+
- **Preserve defaults for malformed rule lists** — A malformed `hard_deny` entry like `[42]` no longer strips all built-in hard-deny rules. Malformed entries are rejected and defaults preserved conservatively. (#21)
|
|
69
|
+
- **Runtime classifier providers** — Dispatch classifier calls through Pi's runtime model registry so providers registered with `pi.registerProvider()` work immediately. Preserve normalized reasoning and header-only authentication on the temporary simple-completion bridge. (#15)
|
|
70
|
+
- **Bounded wildcard matching** — Replace regex-based permission and denied-path globs with a linear-time matcher. Reject oversized patterns and fail closed for oversized runtime inputs. (#19)
|
|
71
|
+
- **Complete classifier action input** — Send the exact current tool input to both classifier stages in a dedicated message. Block the action if it cannot fit without truncation. (#17)
|
|
72
|
+
- **Path policy normalization** — Use Pi-compatible resolution for file-tool paths, including file URLs, `@` and tilde aliases, and read fallback names. Enforce denied paths across omitted and recursive search scopes and both sides of symlink aliases. (#18)
|
|
73
|
+
- **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)
|
|
74
|
+
[Unreleased]: https://github.com/ibartel/pi-automode-ext/compare/v1.0.0...HEAD
|
|
75
|
+
[1.0.0]: https://github.com/ibartel/pi-automode-ext/compare/v1.16.0...v1.0.0
|
|
76
|
+
|
|
77
|
+
[1.16.0]: https://github.com/czottmann/pi-automode/compare/v1.15.0...v1.16.0
|
|
78
|
+
[1.15.0]: https://github.com/czottmann/pi-automode/compare/v1.14.0...v1.15.0
|
|
79
|
+
[1.14.0]: https://github.com/czottmann/pi-automode/compare/v1.13.0...v1.14.0
|
|
80
|
+
[1.13.0]: https://github.com/czottmann/pi-automode/compare/v1.12.0...v1.13.0
|
|
81
|
+
[1.12.0]: https://github.com/czottmann/pi-automode/compare/v1.11.0...v1.12.0
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Carlo Zottmann
|
|
4
|
+
Copyright (c) 2026 Ingo Bartel
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# pi-automode-ext
|
|
2
|
+
|
|
3
|
+
Claude Code-style auto mode for Pi.
|
|
4
|
+
|
|
5
|
+
This is a fork of [`czottmann/pi-automode`](https://github.com/czottmann/pi-automode) by Ingo Bartel, published as `@ibartel74/pi-automode-ext`.
|
|
6
|
+
|
|
7
|
+
This is a guardrail extension. It intercepts agent tool calls before execution and blocks actions that match permission deny rules, deterministic hard-deny checks, or the auto-mode classifier's block decision.
|
|
8
|
+
|
|
9
|
+
It is not a sandbox. Extensions run in the Pi process. A malicious extension can do anything that your user account can do.
|
|
10
|
+
|
|
11
|
+
Pi-automode does not guard user `!` or `!!` shell commands. It guards only agent tool calls. Use it to reduce unsafe autonomous tool use. Do not use it as an OS security boundary.
|
|
12
|
+
|
|
13
|
+
## Compatibility
|
|
14
|
+
|
|
15
|
+
Pi-automode supports Pi and Oh My Pi (OMP) 18. It automatically uses OMP's legacy completion API. The integration needs no OMP-specific configuration.
|
|
16
|
+
|
|
17
|
+
## Differences from upstream
|
|
18
|
+
|
|
19
|
+
Everything in upstream 1.16.0 is included. On top of that, this fork adds:
|
|
20
|
+
|
|
21
|
+
- **Jev classifier backend** — classify with TypeSafe's Jev decision model via `openrouter/typesafe/jev-1.13` (OpenRouter Decisions API) or `typesafe/jev-latest` (native System One API). One typed-questions call replaces both LLM stages; decisions are computed locally from calibrated probabilities and fail closed. See [docs/configuration.md](docs/configuration.md).
|
|
22
|
+
- **Interactive confirmation** — classifier blocks prompt for confirmation when a UI is available (like Claude Code's auto mode) instead of stopping the agent outright. On by default; opt out with `autoMode.interactiveConfirm: false`. Deterministic denies remain unconditional.
|
|
23
|
+
- **Persistent approvals** — the confirmation dialog offers "Always allow" choices that persist an exact-match `permissions.allow` rule to the global config or the project-local `.pi/automode.local.json` and apply it immediately.
|
|
24
|
+
- **Editable dev checkouts** — the deterministic safety-control path check no longer hard-denies a source checkout of this extension under auto mode; only installed locations stay protected.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
From npm:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pi install npm:@ibartel74/pi-automode-ext
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
From a local checkout:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pi install .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For one run from a local checkout:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pi -e ./extensions/auto-mode.ts
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
/automode status # current state, rules, and classifier
|
|
50
|
+
/automode on # re-enable for this session
|
|
51
|
+
/automode off # disable for this session
|
|
52
|
+
/automode reload # reload config from disk
|
|
53
|
+
/automode reset # reset denial counters only
|
|
54
|
+
/automode defaults # print the built-in rule lists
|
|
55
|
+
/automode config # effective config, resolved log file path, + diagnostics
|
|
56
|
+
/automode denials # denial history for this session
|
|
57
|
+
/automode model # open classifier model selector and save to ~/.pi/agent/extensions/pi-automode/config.json
|
|
58
|
+
/automode model provider/model-id # save classifier model to ~/.pi/agent/extensions/pi-automode/config.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`/auto-mode` is an alias.
|
|
62
|
+
|
|
63
|
+
## Agent diagnostics
|
|
64
|
+
|
|
65
|
+
The package registers one model-callable, read-only tool:
|
|
66
|
+
|
|
67
|
+
`automode_inspect` accepts one `action`:
|
|
68
|
+
|
|
69
|
+
- `status`: active state and counters
|
|
70
|
+
- `config`: active effective configuration, log path, and diagnostics
|
|
71
|
+
- `defaults`: built-in rule lists
|
|
72
|
+
- `denials`: recent denial timestamps, kinds, and tool names
|
|
73
|
+
|
|
74
|
+
The tool reads the same in-memory configuration and state that the guardrail enforces. Permission and deterministic checks run before the bypass.
|
|
75
|
+
|
|
76
|
+
The bypass does not change automode counters, persisted state, or observability logs. The extension verifies the source of the tool before it applies the exemption. Thus, a name collision does not exempt a tool from another extension.
|
|
77
|
+
|
|
78
|
+
The tool cannot enable or disable auto mode. It cannot reload configuration, reset state, select a model, or change configuration.
|
|
79
|
+
|
|
80
|
+
Pi sends tool output to the current model. The `status` and `denials` views omit denial reasons and action payloads.
|
|
81
|
+
|
|
82
|
+
If a diagnosis requires a reason, inspect a known-safe entry in the local observability log. The `config` view includes effective rule text. It removes raw JSON parser details from diagnostics.
|
|
83
|
+
|
|
84
|
+
Do not put credentials or other secrets in automode rules.
|
|
85
|
+
|
|
86
|
+
The bundled `automode-diagnostics` skill uses this tool to diagnose unexpected decisions without asking the user to copy output from slash commands. Configuration edits and automode state changes remain user-controlled. See [Agent diagnostics](docs/diagnostics.md) for the inspection contract, privacy limits, and diagnosis workflow.
|
|
87
|
+
|
|
88
|
+
## Status line
|
|
89
|
+
|
|
90
|
+
When the Pi TUI is available, the extension renders a persistent status line:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
AM● a:12 d:2 ca:5 cd:1
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- `AM` — auto-mode prefix. `●` means enabled. `○` means disabled through configuration or `/automode off`.
|
|
97
|
+
- `a:` — actions allowed so far (checked minus denied).
|
|
98
|
+
- `d:` — actions denied so far, for any reason (permission rule, deterministic hard-deny, or classifier).
|
|
99
|
+
- `ca:` / `cd:` — classifier decisions split into allowed and denied. These segments appear after the first classifier call. `d:` counts all denials, so `d:` is always `>= cd:`.
|
|
100
|
+
- `uc:` — actions allowed after you approved a classifier block in an interactive confirmation. Appears after the first approval.
|
|
101
|
+
|
|
102
|
+
## Jev classifier (OpenRouter or TypeSafe-native)
|
|
103
|
+
|
|
104
|
+
Instead of an LLM, pi-automode can classify with TypeSafe's Jev decision model through OpenRouter's Decisions API. Jev answers typed questions with calibrated probabilities; pi-automode makes the allow/block decision locally from them.
|
|
105
|
+
|
|
106
|
+
Set `OPENROUTER_API_KEY` in Pi's environment (from <https://openrouter.ai/keys>). When the variable is absent, pi-automode falls back to any `openrouter` provider key registered in Pi's model registry (for example one configured through OMP). Then run:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
/automode model openrouter/typesafe/jev-1.13
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`openrouter/~typesafe/jev-latest` also works, but the alias moves between releases; a safety classifier should be reproducible. See [Configuration](docs/configuration.md#jev-classifier-openrouter) for how Jev answers map to decisions.
|
|
113
|
+
|
|
114
|
+
With a TypeSafe API key instead, bypass OpenRouter entirely: set `TYPESAFE_API_KEY` in Pi's environment (from <https://console.typesafe.ai/keys>) and run:
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
/automode model typesafe/jev-latest
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`typesafe/...` specs call TypeSafe's System One API directly. When the variable is absent, pi-automode falls back to any `typesafe` provider key registered in Pi's model registry. GUI-launched sessions (for example Orca) do not inherit shell profile variables — set the variable where the host application sees it, for example `launchctl setenv TYPESAFE_API_KEY <key>` before launching it.
|
|
121
|
+
|
|
122
|
+
## Docs
|
|
123
|
+
|
|
124
|
+
- [Configuration](docs/configuration.md)
|
|
125
|
+
- [Defaults and rule-list behavior](docs/defaults.md)
|
|
126
|
+
- [Auto-mode classifier flow](docs/automode-classifier-flow.md)
|
|
127
|
+
- [Observability logging](docs/observability-logging.md)
|
|
128
|
+
- [Architecture decisions](docs/adr/INDEX.md)
|
|
129
|
+
|
|
130
|
+
## What runs before the classifier
|
|
131
|
+
|
|
132
|
+
The extension blocks these before any allow or classifier decision:
|
|
133
|
+
|
|
134
|
+
- `permissions.deny` matches
|
|
135
|
+
- declined `permissions.ask` matches
|
|
136
|
+
- shell profile writes
|
|
137
|
+
- SSH `authorized_keys` writes
|
|
138
|
+
- cron, launch agent, and system service persistence
|
|
139
|
+
- TLS/certificate/auth weakening patterns
|
|
140
|
+
- 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.
|
|
141
|
+
- edits to `.pi/automode*`, `.pi` auto-mode files, and this extension's safety-control files
|
|
142
|
+
|
|
143
|
+
After these checks, pi-automode applies `permissions.allow`. Protected `write` and `edit` targets continue to the classifier.
|
|
144
|
+
|
|
145
|
+
Accepted ask rules also continue to the classifier. They cannot use an allow tier. Pi-automode then allows the read-only tools `read`, `grep`, `find`, and `ls`. Every remaining action goes to the classifier.
|
|
146
|
+
|
|
147
|
+
A `permissions.allow` rule intentionally skips classifier policy, including classifier `hard_deny` rules. Use narrow patterns and user-owned configuration. Deterministic hard-deny and path controls remain unconditional.
|
|
148
|
+
|
|
149
|
+
Set `classifyReadOnlyTools: true` (default `false`) to route read-only tools through the classifier. This configuration value increases model calls, latency, and session cost.
|
|
150
|
+
|
|
151
|
+
Pi-automode blocks a `deniedPaths` match before classifier review or an allow tier. Thus, file tools cannot send matching secret or system paths to the model.
|
|
152
|
+
|
|
153
|
+
The list does not govern `bash`. The classifier and deterministic hard-deny checks govern shell access to these paths.
|
|
154
|
+
|
|
155
|
+
The value `allowInsideWorkingDirectory: true` allows file access inside the working directory locally. Pi-automode sends all outside file access to the classifier, including reads.
|
|
156
|
+
|
|
157
|
+
Classification starts with a conservative one-token filter. If the filter requests review, pi-automode runs structured review.
|
|
158
|
+
|
|
159
|
+
Both stages receive the complete current tool input in a dedicated message. Transcript truncation cannot remove action content. If the exact input cannot fit in the classifier context, auto mode blocks the call.
|
|
160
|
+
|
|
161
|
+
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.
|
|
162
|
+
|
|
163
|
+
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.
|
|
164
|
+
|
|
165
|
+
## Interactive confirmation of classifier blocks
|
|
166
|
+
|
|
167
|
+
When the classifier blocks an action, pi-automode can ask you instead of stopping the agent outright — like Claude Code's auto mode. The dialog shows the block tier, the classifier's reason, and the action summary, with these choices:
|
|
168
|
+
|
|
169
|
+
- **Allow once** — run the action now.
|
|
170
|
+
- **Always allow (global)** — also persist an exact-match `permissions.allow` rule to the global config, so identical future actions skip classifier review everywhere.
|
|
171
|
+
- **Always allow (this project)** — the same rule, persisted to the project's `.pi/automode.local.json`. In an untrusted project the rule is written but stays inert until the project is trusted.
|
|
172
|
+
- **Custom allow rule (this project / global)** — edit the rule before saving. The dialog is prefilled with the exact rule; you decide the scope, e.g. changing `bash(npm test)` to `bash(npm test*)`. The rule must stay scoped to the same tool; an invalid entry warns and re-prompts, and cancelling the input returns to the choice dialog.
|
|
173
|
+
- **Block** — decline; the block keeps the classifier's reason. Cancelling the dialog also blocks.
|
|
174
|
+
|
|
175
|
+
The exact-match "always allow" choices persist the literal command or path: commands or paths containing wildcards or pattern syntax are not offered as exact permanent choices. Custom rules are the escape hatch when you want a wider pattern — the wildcard is always typed by you, never inferred. Tools without a patternable argument only offer allow-once and block. Saved rules are `permissions.allow` entries, so they skip classifier review — including classifier `hard_deny` rules — for future matching actions.
|
|
176
|
+
|
|
177
|
+
This applies to every classifier block: `soft_deny`, `hard_deny`, and fail-closed errors (classifier unavailable, malformed responses, exceeded budgets). It never applies to deterministic blocks: `permissions.deny`, deterministic hard-deny checks, and `deniedPaths` remain unconditional.
|
|
178
|
+
|
|
179
|
+
Confirmation is on by default when a UI is available. Without a UI (print or JSON mode) the block stands; nothing is silently allowed. Set `autoMode.interactiveConfirm: false` to make classifier blocks unconditional again.
|
|
180
|
+
|
|
181
|
+
Approvals stay auditable: they are logged as `user-confirmed` allow decisions in the observability log and counted as `uc:` in the status line.
|
|
182
|
+
|
|
183
|
+
## Examples
|
|
184
|
+
|
|
185
|
+
- `examples/automode.local.json`: copy to `.pi/automode.local.json` in a project and edit the domains, buckets, and source-control org.
|
|
186
|
+
|
|
187
|
+
### Pro tip
|
|
188
|
+
|
|
189
|
+
The default rule set might cause denials like this:
|
|
190
|
+
|
|
191
|
+
> Auto mode blocked subagent: Delegated workers would modify pre-existing files outside the user's bounded authorization, including migrations, CLI code, and approval documentation.
|
|
192
|
+
|
|
193
|
+
This is to be expected as the included classifier rules are very conservative and err on the side of caution! To explicitly give your robot more leeway, adjust your global `autoMode.allow` entry (or its project-level counterparts). For example, my personal `~/.pi/agent/extensions/pi-automode/config.json` file looks like this:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"autoMode": {
|
|
198
|
+
"allow": [
|
|
199
|
+
"$defaults",
|
|
200
|
+
"Creating, modifying, and deleting local files within the Git repository or worktree. This includes pre-existing source code, migrations, CLI code, tests, and project documentation. This permission applies only to local implementation work and excludes files outside the assigned repository/worktree, external systems, credentials, safety controls, and Git history changes."
|
|
201
|
+
],
|
|
202
|
+
…
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Known limits
|
|
208
|
+
|
|
209
|
+
Claude Code's real classifier and exact built-in rules are private. This package implements the documented precedence and configuration behavior, with a local classifier prompt and deterministic hard-deny checks.
|
|
210
|
+
|
|
211
|
+
## Development
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
npm run check
|
|
215
|
+
npm test
|
|
216
|
+
npm pack --dry-run
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The tests cover these safety-sensitive areas:
|
|
220
|
+
|
|
221
|
+
- scoped permission matching
|
|
222
|
+
- the `permissions.allow` tier and its precedence
|
|
223
|
+
- configuration-source precedence and diagnostics
|
|
224
|
+
- `$defaults` behavior
|
|
225
|
+
- deterministic hard-deny checks and Bash AST analysis
|
|
226
|
+
- classifier routing for `write` and `edit`
|
|
227
|
+
- symlink-aware safety-control checks
|
|
228
|
+
- token-budgeted transcript selection
|
|
229
|
+
- staged classifier parsing and cache behavior
|
|
230
|
+
- hook-level allow and block behavior
|
|
231
|
+
|
|
232
|
+
## Publishing
|
|
233
|
+
|
|
234
|
+
When a maintainer publishes a GitHub Release, GitHub Actions publishes the package to npm. The release tag must match `package.json` exactly. The forms `v1.0.0` and `1.0.0` both match version `1.0.0`.
|
|
235
|
+
|
|
236
|
+
The workflow uses npm Trusted Publishing, so it does not need an npm token secret. Configure this package on npm with this repository and workflow file (`.github/workflows/publish.yml`). The workflow builds the package, runs `npm run check`, and publishes with npm provenance.
|
|
237
|
+
|
|
238
|
+
### Release tag must point at the version bump
|
|
239
|
+
|
|
240
|
+
The publish workflow checks the commit that the release tag identifies. It compares the version in `package.json` with the tag name.
|
|
241
|
+
|
|
242
|
+
**The tag must identify a commit that contains the new version.**
|
|
243
|
+
|
|
244
|
+
1. Commit the version change with `chore: release X.Y.Z`.
|
|
245
|
+
2. Push `main`.
|
|
246
|
+
3. Create the GitHub release from that commit.
|
|
247
|
+
|
|
248
|
+
If the tag identifies an older version, the `Check release tag` step fails. It reports `Release tag (vX.Y.Z) does not match package.json version (x.y.z)`.
|
|
249
|
+
|
|
250
|
+
CAUTION: Do not move a correct release tag. Moving the tag changes a published reference.
|
|
251
|
+
|
|
252
|
+
1. If the tag identifies the wrong commit, force-move it to the version-change commit.
|
|
253
|
+
2. Force-push the corrected tag.
|
|
254
|
+
3. Run `gh workflow run publish.yml --ref vX.Y.Z`.
|
|
255
|
+
|
|
256
|
+
When GitHub creates the tag, the `release` event occurs. A rerun of a failed `release` workflow uses the original reference. It does not use the moved tag.
|
|
257
|
+
|
|
258
|
+
## Credits
|
|
259
|
+
|
|
260
|
+
**pi-automode-ext** is maintained by Ingo Bartel ([@ibartel](https://github.com/ibartel)).
|
|
261
|
+
|
|
262
|
+
It is a fork of **pi-automode** by Carlo Zottmann, <carlo@zottmann.dev> — Website: https://actions.work, GitHub: https://github.com/czottmann, Bluesky: https://bsky.app/profile/zottmann.dev, Mastodon: https://norden.social/@czottmann. The original project is MIT-licensed; its notice is retained in [LICENSE.md](LICENSE.md).
|
package/docs/GLOSSARY.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Glossary
|
|
2
|
+
|
|
3
|
+
This glossary defines project terms for `@ibartel74/pi-automode-ext`. It does not define standard technical words. Each entry links to a longer explanation where applicable.
|
|
4
|
+
|
|
5
|
+
## Enforcement flow
|
|
6
|
+
|
|
7
|
+
The enforcement flow is the ordered pipeline that runs before each agent tool call. See [Auto-mode classifier flow](automode-classifier-flow.md).
|
|
8
|
+
|
|
9
|
+
**Auto mode** — A Claude Code-style guardrail posture. A pre-execution classifier allows routine, reversible actions and blocks risky actions. It replaces routine permission prompts.
|
|
10
|
+
|
|
11
|
+
**Deterministic hard-deny** — Local code checks that block high-risk actions before classifier review. A user or model cannot override them. They are independent of the classifier-level [hard_deny](#classifier-policy-and-rules) rules.
|
|
12
|
+
|
|
13
|
+
**`permissions.allow` tier** — A user-owned list of scoped tool patterns. A match skips classifier review after deterministic checks pass. An accepted `permissions.ask` rule disables this tier for the current call. The tier cannot override a denial or cover protected `write` and `edit` targets. See [ADR-001](adr/ADR-001-permission-precedence-and-trust-boundaries.md).
|
|
14
|
+
|
|
15
|
+
**Read-only bypass** — The default allow tier for `read`, `grep`, `find`, and `ls`. Permission and deterministic checks run before this tier. `classifyReadOnlyTools` sends these calls to the classifier instead.
|
|
16
|
+
|
|
17
|
+
**Staged classifier** — A two-stage safety classifier. A conservative one-token filter controls access to an optional structured review. See [Fast stage](#enforcement-flow) and [Detailed stage](#enforcement-flow).
|
|
18
|
+
|
|
19
|
+
**Fast stage** — The first classifier stage. It returns `0` for a clearly allowed action or `1` for an action that can require review.
|
|
20
|
+
|
|
21
|
+
**Detailed stage** — The second classifier stage. It runs after a fast-stage review result and returns a structured allow or block decision.
|
|
22
|
+
|
|
23
|
+
**Interactive confirmation** — The user prompt shown when the classifier blocks an action and `interactiveConfirm` is on (the default). Choices: allow once, always allow (persisting an exact-match `permissions.allow` rule globally or for this project), or block. A decline, cancel, or missing UI keeps the block. Deterministic denials never prompt.
|
|
24
|
+
|
|
25
|
+
## Classifier policy and rules
|
|
26
|
+
|
|
27
|
+
The classifier policy defines denial tiers and rule-list syntax. See [Defaults and rule-list behavior](defaults.md).
|
|
28
|
+
|
|
29
|
+
**hard_deny** — Classifier rules that block unconditionally against transcript- or model-based overrides. With `interactiveConfirm` on (default), a live user can still approve a blocked action in an [interactive confirmation](#enforcement-flow). They are independent of the code-level [deterministic hard-deny](#enforcement-flow) checks.
|
|
30
|
+
|
|
31
|
+
**soft_deny** — Classifier rules that normally block but support defined overrides, including a live [interactive confirmation](#enforcement-flow). Unlike [hard_deny](#classifier-policy-and-rules), these rules are not unconditional.
|
|
32
|
+
|
|
33
|
+
**explicit_intent** — A classifier tier for direct user authorization in the retained user transcript. It authorizes an action that matches a [soft_deny](#classifier-policy-and-rules) rule. A later user instruction that narrows or revokes authorization controls. For a pre-existing local file outside the repository or worktree, see [Defaults and rule-list behavior](defaults.md).
|
|
34
|
+
|
|
35
|
+
**allow exception** (`autoMode.allow`) — A prose rule that overrides a matching [soft_deny](#classifier-policy-and-rules) rule. It cannot override [hard_deny](#classifier-policy-and-rules). It is independent of the [`permissions.allow` tier](#enforcement-flow).
|
|
36
|
+
|
|
37
|
+
**`$defaults`** — A section-local marker in a rule list. It expands to the built-in entries for that list.
|
|
38
|
+
|
|
39
|
+
## Status
|
|
40
|
+
|
|
41
|
+
**AM status line** — The persistent TUI footer that starts with `AM`. It reports the auto-mode state and action counts.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# ADR-001: Permission precedence and configuration trust boundaries
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-23
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Pi-automode supports `permissions.deny`, `permissions.ask`, and `permissions.allow` tool patterns. These rules affect whether a tool call reaches the classifier.
|
|
8
|
+
|
|
9
|
+
An allow rule reduces classifier coverage. A checked-in repository file must not reduce that coverage. An accepted ask rule must also keep its promise of classifier review.
|
|
10
|
+
|
|
11
|
+
The wildcard matcher limits its input size to keep matching time bounded. Deny and ask rules must fail closed for oversized inputs. The same behavior is unsafe for allow rules.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
Pi-automode uses this permission policy:
|
|
16
|
+
|
|
17
|
+
1. A matching `permissions.deny` rule blocks the tool call.
|
|
18
|
+
2. A matching `permissions.ask` rule requires user confirmation.
|
|
19
|
+
3. If the user declines, pi-automode blocks the tool call.
|
|
20
|
+
4. If the user accepts, deterministic denial checks continue.
|
|
21
|
+
5. After these checks pass, the classifier reviews the accepted tool call.
|
|
22
|
+
6. An accepted ask rule disables all later deterministic allow tiers for that call.
|
|
23
|
+
7. If no ask rule matched, a `permissions.allow` rule can skip classifier review.
|
|
24
|
+
8. Deterministic hard-deny checks, path-deny checks, and protected-path controls take precedence over allow rules.
|
|
25
|
+
|
|
26
|
+
Pi-automode reads `permissions.allow` only from user-owned configuration sources:
|
|
27
|
+
|
|
28
|
+
- `~/.pi/agent/automode.json`
|
|
29
|
+
- trusted `.pi/automode.local.json`
|
|
30
|
+
- `PI_AUTOMODE_SETTINGS_JSON`
|
|
31
|
+
|
|
32
|
+
Shared `.pi/automode.json` can add deny and ask rules after project trust. It cannot add allow rules.
|
|
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
|
+
|
|
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.
|
|
37
|
+
|
|
38
|
+
## Consequences
|
|
39
|
+
|
|
40
|
+
A repository cannot use checked-in configuration to remove classifier review. Users can still add project-specific allow rules in the local configuration file.
|
|
41
|
+
|
|
42
|
+
An accepted ask rule always causes classifier review after deterministic checks pass. This behavior is stricter than a model where confirmation grants direct permission.
|
|
43
|
+
|
|
44
|
+
The matcher needs separate overflow behavior for denial and allow contexts. Tests must cover both behaviors.
|
|
45
|
+
|
|
46
|
+
The permission lists are not symmetric across configuration sources. The documentation and diagnostics must make this difference clear.
|
|
@@ -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.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Architecture Decision Records
|
|
2
|
+
|
|
3
|
+
| # | Decision | Date |
|
|
4
|
+
|---|----------|------|
|
|
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 |
|