@czottmann/pi-automode 1.2.0 → 1.3.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/README.md +33 -106
- package/docs/automode-classifier-flow.md +320 -0
- package/docs/defaults.md +121 -0
- package/extensions/auto-mode/config.ts +37 -2
- package/extensions/auto-mode/constants.ts +1 -1
- package/extensions/auto-mode/extension.ts +33 -29
- package/extensions/auto-mode/state.ts +1 -5
- package/extensions/auto-mode/types.ts +0 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -37,33 +37,43 @@ pi -e ./extensions/auto-mode.ts
|
|
|
37
37
|
/automode defaults # print the built-in rule lists
|
|
38
38
|
/automode config # current effective config + diagnostics
|
|
39
39
|
/automode denials # denial history for this session
|
|
40
|
-
/automode model # open classifier model selector
|
|
41
|
-
/automode model provider/model-id #
|
|
40
|
+
/automode model # open classifier model selector and save to ~/.pi/agent/automode.json
|
|
41
|
+
/automode model provider/model-id # save classifier model to ~/.pi/agent/automode.json
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
`/auto-mode` is an alias.
|
|
45
45
|
|
|
46
|
+
## Docs
|
|
47
|
+
|
|
48
|
+
- [Defaults and rule-list behavior](docs/defaults.md)
|
|
49
|
+
- [Auto-mode classifier flow](docs/automode-classifier-flow.md)
|
|
50
|
+
|
|
46
51
|
## Configuration
|
|
47
52
|
|
|
48
53
|
The extension follows Claude Code's documented config model where Pi can support it.
|
|
49
54
|
|
|
50
55
|
It reads `autoMode` from Pi-owned config only:
|
|
51
56
|
|
|
52
|
-
- `~/.pi/automode.json`
|
|
57
|
+
- `~/.pi/agent/automode.json`
|
|
53
58
|
- `.pi/automode.local.json`
|
|
54
59
|
- `PI_AUTOMODE_SETTINGS_JSON`
|
|
55
60
|
|
|
56
61
|
It deliberately does not read `autoMode` from shared project `.pi/automode.json`, because a checked-in repo should not be able to weaken auto-mode rules. Shared project config may still contribute `permissions.deny` and `permissions.ask`.
|
|
57
62
|
|
|
63
|
+
Set a global default classifier model in `~/.pi/agent/automode.json`; override it per project in `.pi/automode.local.json`.
|
|
64
|
+
|
|
58
65
|
Example:
|
|
59
66
|
|
|
60
67
|
```json
|
|
61
68
|
{
|
|
62
69
|
"autoMode": {
|
|
70
|
+
"classifierModel": "provider/model-id",
|
|
63
71
|
"environment": [
|
|
64
72
|
"$defaults",
|
|
65
73
|
"Source control: github.example.com/acme-corp and all repos under it",
|
|
66
|
-
"Trusted internal domains: *.corp.example.com"
|
|
74
|
+
"Trusted internal domains: *.corp.example.com, git.example.com",
|
|
75
|
+
"Trusted cloud buckets: s3://acme-dev-artifacts, gs://acme-ci-cache",
|
|
76
|
+
"Key internal services: staging deploy API at deploy.corp.example.com"
|
|
67
77
|
],
|
|
68
78
|
"allow": ["$defaults"],
|
|
69
79
|
"protectedPaths": ["$defaults"],
|
|
@@ -82,105 +92,7 @@ Example:
|
|
|
82
92
|
|
|
83
93
|
### `$defaults`
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
#### `environment`
|
|
88
|
-
|
|
89
|
-
`$defaults` expands to:
|
|
90
|
-
|
|
91
|
-
- trusted repo: the repository Pi started in and its configured git remotes
|
|
92
|
-
- source control: the trusted repo and its configured remotes only
|
|
93
|
-
- trusted internal domains: none configured
|
|
94
|
-
- trusted cloud buckets: none configured
|
|
95
|
-
- key internal services: none configured
|
|
96
|
-
|
|
97
|
-
#### `allow`
|
|
98
|
-
|
|
99
|
-
`$defaults` expands to allow exceptions for:
|
|
100
|
-
|
|
101
|
-
- read-only operations: inspecting files, listing directories, searching, GET requests, and state queries that do not expose secrets
|
|
102
|
-
- local development inside the working tree: creating, editing, building, testing, linting, formatting, and deleting files created during the current task
|
|
103
|
-
- installing dependencies already declared in package manifests or lockfiles
|
|
104
|
-
- using standard credentials only with their intended configured providers
|
|
105
|
-
- pushing to the current non-default working branch or a new branch created for the task
|
|
106
|
-
- bootstrapping language/toolchain installers from official sources
|
|
107
|
-
|
|
108
|
-
These are exceptions to `soft_deny`, not to `hard_deny`.
|
|
109
|
-
|
|
110
|
-
#### `protectedPaths`
|
|
111
|
-
|
|
112
|
-
`$defaults` expands to paths where writes are never auto-approved — they always go to the classifier, regardless of `allow` rules. This matches Claude Code's protected-paths behavior.
|
|
113
|
-
|
|
114
|
-
Protected directories: `.git`, `.config/git`, `.vscode`, `.idea`, `.husky`, `.cargo`, `.devcontainer`, `.yarn`, `.mvn`, `.pi`.
|
|
115
|
-
|
|
116
|
-
Protected files: `.gitconfig`, `.gitmodules`, `.gitignore`, `.gitattributes`, shell profiles (`.bashrc`, `.zshrc`, `.profile`, etc.), `.envrc`, package manager configs (`.npmrc`, `.yarnrc`, `.yarnrc.yml`, `.pnp.cjs`, `bunfig.toml`, etc.), Bazel configs (`.bazelrc`, `.bazelversion`, `.bazeliskrc`), hook configs (`.pre-commit-config.yaml`, `lefthook.yml`), Gradle/Maven wrappers, `.devcontainer.json`, `.ripgreprc`, `pyrightconfig.json`, `.mcp.json`.
|
|
117
|
-
|
|
118
|
-
Read-only tools (`read`, `grep`, `find`, `ls`) bypass this check — reads to protected paths are always allowed. Only `write` and `edit` are affected.
|
|
119
|
-
|
|
120
|
-
#### `soft_deny`
|
|
121
|
-
|
|
122
|
-
`$defaults` expands to soft blocks for:
|
|
123
|
-
|
|
124
|
-
- force push, remote branch deletion, remote history rewrite, or direct push to `main`, `master`, or the default branch
|
|
125
|
-
- downloading and executing external code, including `curl | bash` and `wget | sh`
|
|
126
|
-
- production deploys, production database migrations, or production infrastructure changes
|
|
127
|
-
- deleting, truncating, or overwriting pre-existing local files unless the user named the specific target
|
|
128
|
-
- mass modification/deletion in cloud storage, ticketing systems, collaboration tools, or external APIs
|
|
129
|
-
- writing through remote shells such as `ssh`, `kubectl exec`, or `docker exec` on shared or production hosts
|
|
130
|
-
- changing live/shared infrastructure without a preview, plan, dry-run, or review when one normally exists
|
|
131
|
-
- killing other users' processes, cancelling shared jobs, or disrupting shared infrastructure
|
|
132
|
-
- exposing local files, sockets, credentials, or internal services through a network service
|
|
133
|
-
- outbound messages, purchases, bookings, subscriptions, or other real-world transactions
|
|
134
|
-
|
|
135
|
-
Soft blocks can be overridden by a matching `allow` exception or by direct, specific user intent.
|
|
136
|
-
|
|
137
|
-
#### `hard_deny`
|
|
138
|
-
|
|
139
|
-
`$defaults` expands to unconditional blocks for:
|
|
140
|
-
|
|
141
|
-
- weakening TLS, certificate validation, authentication, authorization, audit logging, or other security controls
|
|
142
|
-
- adding persistence such as SSH keys, shell profile edits, cron jobs, launch agents, login items, or background agents
|
|
143
|
-
- granting admin, owner, IAM, RBAC, repository, or production access
|
|
144
|
-
- exfiltrating repository contents, secrets, credentials, private keys, tokens, or internal data to untrusted places
|
|
145
|
-
- scanning for credentials, tokens, private keys, or secrets beyond what the task requires
|
|
146
|
-
- tampering with logs, audit trails, security monitoring, permission rules, auto-mode config, or safety-control files
|
|
147
|
-
- creating services, endpoints, workflows, or autonomous agents that execute arbitrary code without meaningful approval
|
|
148
|
-
- posting or updating public/external content that is fabricated, misleading, impersonating a user, or claiming approval/action that did not happen
|
|
149
|
-
|
|
150
|
-
Hard-deny rules cannot be overridden by `allow` or by user intent.
|
|
151
|
-
|
|
152
|
-
#### Replacement behavior
|
|
153
|
-
|
|
154
|
-
Use `$defaults` when you want to keep the built-ins and add your own entries:
|
|
155
|
-
|
|
156
|
-
```json
|
|
157
|
-
{
|
|
158
|
-
"autoMode": {
|
|
159
|
-
"allow": [
|
|
160
|
-
"$defaults",
|
|
161
|
-
"Running the staging deploy script is allowed."
|
|
162
|
-
]
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
That means: use all built-in `allow` entries, plus the staging rule.
|
|
168
|
-
|
|
169
|
-
If you omit `$defaults`, you replace the built-ins for that section:
|
|
170
|
-
|
|
171
|
-
```json
|
|
172
|
-
{
|
|
173
|
-
"autoMode": {
|
|
174
|
-
"allow": [
|
|
175
|
-
"Running the staging deploy script is allowed."
|
|
176
|
-
]
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
That means: use only that one `allow` entry. The built-in `allow` entries are not used. Replacing `allow` does not replace `soft_deny`, `hard_deny`, `protectedPaths`, or `environment`.
|
|
182
|
-
|
|
183
|
-
`$defaults` is not used in `permissions.deny` or `permissions.ask`. Those lists contain only explicit Pi tool patterns.
|
|
95
|
+
See [Defaults and rule-list behavior](docs/defaults.md) for built-in `environment`, `allow`, `protectedPaths`, `soft_deny`, and `hard_deny` entries, plus replacement behavior when `$defaults` is omitted.
|
|
184
96
|
|
|
185
97
|
### Permission patterns
|
|
186
98
|
|
|
@@ -201,7 +113,7 @@ The extension blocks these before any allow or classifier decision:
|
|
|
201
113
|
|
|
202
114
|
Read-only Pi tools (`read`, `grep`, `find`, `ls`) are allowed after those checks.
|
|
203
115
|
|
|
204
|
-
Writes to [protected paths](#protectedpaths) (shell profiles, tool configs, `.git`, `.vscode`, `.pi`, etc.) always go to the classifier, even if an `allow` rule matches. The classifier decides whether to permit the write.
|
|
116
|
+
Writes to [protected paths](docs/defaults.md#protectedpaths) (shell profiles, tool configs, `.git`, `.vscode`, `.pi`, etc.) always go to the classifier, even if an `allow` rule matches. The classifier decides whether to permit the write.
|
|
205
117
|
|
|
206
118
|
Everything else goes to the classifier. If the classifier is missing, fails, or returns invalid JSON, the action is blocked.
|
|
207
119
|
|
|
@@ -209,6 +121,10 @@ Everything else goes to the classifier. If the classifier is missing, fails, or
|
|
|
209
121
|
|
|
210
122
|
- `examples/automode.local.json`: copy to `.pi/automode.local.json` in a project and edit the domains, buckets, and source-control org.
|
|
211
123
|
|
|
124
|
+
## Known limits
|
|
125
|
+
|
|
126
|
+
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.
|
|
127
|
+
|
|
212
128
|
## Development
|
|
213
129
|
|
|
214
130
|
```bash
|
|
@@ -219,6 +135,17 @@ npm pack --dry-run
|
|
|
219
135
|
|
|
220
136
|
The tests cover the risky parts: scoped permission matching, config-source precedence, `$defaults` behavior, config diagnostics, deterministic hard-deny checks, shell parsing for risky bash fragments, classifier JSON parsing, hook-level blocking/allowing, classifier mocking, and protected-path enforcement.
|
|
221
137
|
|
|
222
|
-
##
|
|
138
|
+
## Publishing
|
|
223
139
|
|
|
224
|
-
|
|
140
|
+
GitHub Actions publishes the package to npm when a GitHub Release is published. The release tag must match `package.json` exactly, with or without a leading `v` (`v1.0.0` and `1.0.0` both work for version `1.0.0`).
|
|
141
|
+
|
|
142
|
+
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.
|
|
143
|
+
|
|
144
|
+
## Author
|
|
145
|
+
|
|
146
|
+
Carlo Zottmann, <carlo@zottmann.dev>
|
|
147
|
+
|
|
148
|
+
- Website: https://actions.work
|
|
149
|
+
- GitHub: https://github.com/czottmann
|
|
150
|
+
- Bluesky: https://bsky.app/profile/zottmann.dev
|
|
151
|
+
- Mastodon: https://norden.social/@czottmann
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# Auto-mode classifier flow
|
|
2
|
+
|
|
3
|
+
This document describes how `pi-automode` decides whether an agent tool call can run. The classifier is only one part of the flow. Several checks happen before any model call, and some tool calls never reach the classifier at all.
|
|
4
|
+
|
|
5
|
+
## Short version
|
|
6
|
+
|
|
7
|
+
For each Pi `tool_call` event, the extension does this:
|
|
8
|
+
|
|
9
|
+
1. Load the effective auto-mode config for the current session.
|
|
10
|
+
2. Ignore the call if auto-mode is disabled.
|
|
11
|
+
3. Block immediately if the agent turn was cancelled.
|
|
12
|
+
4. Check `permissions.deny` rules.
|
|
13
|
+
5. Check `permissions.ask` rules and ask the user when needed.
|
|
14
|
+
6. Run deterministic hard-deny checks.
|
|
15
|
+
7. Allow read-only built-in tools without a classifier call.
|
|
16
|
+
8. Send every remaining action to the classifier.
|
|
17
|
+
9. Allow or block based on the classifier JSON response.
|
|
18
|
+
10. Persist state and update the UI status/denial history.
|
|
19
|
+
|
|
20
|
+
The default posture is fail-closed. If the classifier cannot be resolved, has no API key, errors, or returns invalid JSON, the action is blocked.
|
|
21
|
+
|
|
22
|
+
## Diagram
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
flowchart TD
|
|
26
|
+
A[Pi emits tool_call] --> B[Build effective config]
|
|
27
|
+
B --> C{Auto-mode enabled?}
|
|
28
|
+
C -- no --> Z[Let tool run]
|
|
29
|
+
C -- yes --> D{ctx.signal aborted?}
|
|
30
|
+
D -- yes --> X[Block: cancelled]
|
|
31
|
+
D -- no --> E[Summarize action]
|
|
32
|
+
|
|
33
|
+
E --> F{Matches permissions.deny?}
|
|
34
|
+
F -- yes --> F1[Block locally]
|
|
35
|
+
F -- no --> G{Matches permissions.ask?}
|
|
36
|
+
|
|
37
|
+
G -- yes --> H{UI available?}
|
|
38
|
+
H -- no --> H1[Block locally]
|
|
39
|
+
H -- yes --> I[Ask user]
|
|
40
|
+
I -- declined --> I1[Block locally]
|
|
41
|
+
I -- allowed --> J[Continue]
|
|
42
|
+
G -- no --> J
|
|
43
|
+
|
|
44
|
+
J --> K{Deterministic hard-deny?}
|
|
45
|
+
K -- yes --> K1[Block locally]
|
|
46
|
+
K -- no --> L{Read-only built-in tool?}
|
|
47
|
+
|
|
48
|
+
L -- yes --> L1[Allow locally]
|
|
49
|
+
L -- no --> M{write/edit protected path?}
|
|
50
|
+
M -- yes --> N[Classify action]
|
|
51
|
+
M -- no --> N[Classify action]
|
|
52
|
+
|
|
53
|
+
N --> O{Classifier available and valid JSON?}
|
|
54
|
+
O -- no --> O1[Block: fail closed]
|
|
55
|
+
O -- yes --> P{decision == allow?}
|
|
56
|
+
P -- yes --> Q[Allow tool]
|
|
57
|
+
P -- no --> R[Block with classifier reason]
|
|
58
|
+
|
|
59
|
+
X --> S[Persist state + update UI]
|
|
60
|
+
F1 --> S
|
|
61
|
+
H1 --> S
|
|
62
|
+
I1 --> S
|
|
63
|
+
K1 --> S
|
|
64
|
+
O1 --> S
|
|
65
|
+
R --> S
|
|
66
|
+
L1 --> T[Persist allow state + update UI]
|
|
67
|
+
Q --> T
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Config loading
|
|
71
|
+
|
|
72
|
+
Config is loaded on `session_start` and can be reloaded with `/automode reload`.
|
|
73
|
+
|
|
74
|
+
The effective config combines these sources:
|
|
75
|
+
|
|
76
|
+
- `~/.pi/agent/automode.json`
|
|
77
|
+
- `.pi/automode.local.json`
|
|
78
|
+
- `PI_AUTOMODE_SETTINGS_JSON`
|
|
79
|
+
- shared `.pi/automode.json`, but only for `permissions.deny` and `permissions.ask`
|
|
80
|
+
|
|
81
|
+
Shared project `.pi/automode.json` cannot change `autoMode` rules. That is deliberate: a checked-in repo must not be able to weaken auto-mode. It may still add Pi permission rules.
|
|
82
|
+
|
|
83
|
+
List settings such as `allow`, `soft_deny`, `hard_deny`, `environment`, and `protectedPaths` support `$defaults`. Omitting `$defaults` replaces the built-ins for that section only. See [Defaults and rule-list behavior](defaults.md).
|
|
84
|
+
|
|
85
|
+
## Context captured before classification
|
|
86
|
+
|
|
87
|
+
On `before_agent_start`, the extension appends `AUTO_MODE_GUIDANCE` to the main agent's system prompt. This reminds the main agent that auto-mode is active and tells it not to bypass or weaken the controls.
|
|
88
|
+
|
|
89
|
+
The same hook also extracts loaded context files from Pi's `systemPromptOptions.contextFiles`. That extracted text becomes `loadedContext`, which is later sent to the classifier. Each context file is formatted as:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
# path/to/file
|
|
93
|
+
<truncated content>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Each file's content is truncated in the middle to 4000 characters.
|
|
97
|
+
|
|
98
|
+
## Local checks before the classifier
|
|
99
|
+
|
|
100
|
+
### `permissions.deny`
|
|
101
|
+
|
|
102
|
+
`permissions.deny` is checked first. A matching rule blocks immediately. The classifier is not consulted.
|
|
103
|
+
|
|
104
|
+
Example rule:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
"bash(git push --force*)"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Permission patterns are scoped to the Pi tool and its primary argument. For `bash`, the primary argument is `input.command`. For file tools such as `read`, `write`, and `edit`, it is the resolved path normalized for matching.
|
|
111
|
+
|
|
112
|
+
### `permissions.ask`
|
|
113
|
+
|
|
114
|
+
`permissions.ask` runs after `permissions.deny`.
|
|
115
|
+
|
|
116
|
+
If a rule matches and no UI is available, the action is blocked. If UI is available, the user sees a confirmation dialog with the matched rule and the action summary.
|
|
117
|
+
|
|
118
|
+
Approving that dialog does not run the tool directly. It only lets the action continue to the normal auto-mode checks, including deterministic hard-deny checks and classifier review.
|
|
119
|
+
|
|
120
|
+
### Deterministic hard-deny checks
|
|
121
|
+
|
|
122
|
+
Some actions are too risky to leave to the classifier. These are blocked locally, before any classifier call.
|
|
123
|
+
|
|
124
|
+
Current deterministic blocks include:
|
|
125
|
+
|
|
126
|
+
- writes to shell profile files;
|
|
127
|
+
- writes to `~/.ssh/authorized_keys`;
|
|
128
|
+
- edits to auto-mode or Pi permission safety-control files;
|
|
129
|
+
- TLS or certificate verification weakening;
|
|
130
|
+
- persistence changes such as cron jobs, launch agents, and system service enablement;
|
|
131
|
+
- dangerous recursive deletes of root, home, or system paths;
|
|
132
|
+
- selected system or SSH permission mutations.
|
|
133
|
+
|
|
134
|
+
The `bash` checks use a small shell lexer. It handles quotes, redirects, pipes, `&&`, `||`, and `;` well enough to catch common "safe prefix, risky suffix" patterns.
|
|
135
|
+
|
|
136
|
+
### Read-only bypass
|
|
137
|
+
|
|
138
|
+
Read-only built-in tools are allowed without classifier review after the checks above pass.
|
|
139
|
+
|
|
140
|
+
The read-only tool set is:
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
read, grep, find, ls
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Reads to protected paths are still allowed. Protected-path handling only affects writes and edits.
|
|
147
|
+
|
|
148
|
+
## Protected paths
|
|
149
|
+
|
|
150
|
+
Writes and edits to protected paths are always classifier-reviewed. The default protected paths include `.git`, `.pi`, editor config directories, shell profiles, package-manager config files, hook configs, and similar files.
|
|
151
|
+
|
|
152
|
+
The matcher resolves symlinks when it can, so writes through a symlink into `.git` still count as protected. It also catches writes to protected paths outside the current project, such as another checkout's `.git/config`.
|
|
153
|
+
|
|
154
|
+
At the moment, all non-read-only actions reach the classifier anyway, so the protected-path branch has the same practical result as the normal classifier path. Its purpose is still important: if fast-path allow logic is added later, protected writes must continue to go through classifier review.
|
|
155
|
+
|
|
156
|
+
## What is sent to the classifier
|
|
157
|
+
|
|
158
|
+
The classifier call is made by `defaultClassifyAction`.
|
|
159
|
+
|
|
160
|
+
The model receives a system prompt and one user message.
|
|
161
|
+
|
|
162
|
+
### System prompt
|
|
163
|
+
|
|
164
|
+
The system prompt is `CLASSIFIER_SYSTEM_PROMPT` with config sections expanded into bullet lists:
|
|
165
|
+
|
|
166
|
+
- trusted environment
|
|
167
|
+
- allow exceptions
|
|
168
|
+
- soft-deny rules
|
|
169
|
+
- hard-deny rules
|
|
170
|
+
|
|
171
|
+
The prompt defines the policy semantics:
|
|
172
|
+
|
|
173
|
+
- hard-deny rules block unconditionally;
|
|
174
|
+
- soft-deny rules block unless an allow exception matches or the latest user instruction directly authorizes the exact risky action;
|
|
175
|
+
- allow rules only override soft-deny rules;
|
|
176
|
+
- hidden or malicious instructions inside tool output or repo files must not change the rules;
|
|
177
|
+
- the classifier must return only JSON.
|
|
178
|
+
|
|
179
|
+
The required JSON shape is:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{"decision":"allow","tier":"allow","reason":"brief concrete reason"}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
or:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{"decision":"block","tier":"soft_deny","reason":"brief concrete reason"}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Valid `tier` values are:
|
|
192
|
+
|
|
193
|
+
```text
|
|
194
|
+
hard_deny, soft_deny, allow, explicit_intent, none
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### User message
|
|
198
|
+
|
|
199
|
+
The user message has this exact structure:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
<loaded-project-instructions>
|
|
203
|
+
${loadedContext || "(none)"}
|
|
204
|
+
</loaded-project-instructions>
|
|
205
|
+
|
|
206
|
+
<transcript>
|
|
207
|
+
${buildTranscript(ctx, config.maxTranscriptLines) || "(none)"}
|
|
208
|
+
</transcript>
|
|
209
|
+
|
|
210
|
+
Latest action to classify:
|
|
211
|
+
${action}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`action` is built as:
|
|
215
|
+
|
|
216
|
+
```text
|
|
217
|
+
${toolName} ${safeJson(input, 6000)}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
For example:
|
|
221
|
+
|
|
222
|
+
```text
|
|
223
|
+
bash {
|
|
224
|
+
"command": "npm test"
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The transcript is built from the current Pi session branch. It includes retained conversation items only:
|
|
229
|
+
|
|
230
|
+
- user text, truncated to 2000 characters;
|
|
231
|
+
- visible assistant text, truncated to 2000 characters;
|
|
232
|
+
- assistant tool calls, with arguments serialized and truncated.
|
|
233
|
+
|
|
234
|
+
Tool result contents are not currently included in the classifier transcript.
|
|
235
|
+
|
|
236
|
+
Hidden assistant reasoning is not sent by this extension. The classifier sees retained visible conversation and tool-call evidence, plus the latest proposed action.
|
|
237
|
+
|
|
238
|
+
## Classifier model resolution
|
|
239
|
+
|
|
240
|
+
The classifier model is selected in this order:
|
|
241
|
+
|
|
242
|
+
1. `autoMode.classifierModel` from config;
|
|
243
|
+
2. the current Pi session model.
|
|
244
|
+
|
|
245
|
+
`/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.
|
|
246
|
+
|
|
247
|
+
The extension asks Pi's model registry for API credentials. If the model cannot be found or credentials are unavailable, classification returns a blocking decision:
|
|
248
|
+
|
|
249
|
+
```text
|
|
250
|
+
No classifier model/API key available; auto mode fails closed.
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Classifier calls use:
|
|
254
|
+
|
|
255
|
+
```text
|
|
256
|
+
temperature: 0
|
|
257
|
+
maxTokens: 700
|
|
258
|
+
signal: ctx.signal
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Parsing the classifier result
|
|
262
|
+
|
|
263
|
+
The parser accepts JSON returned directly, JSON inside a fenced code block, or the first JSON-looking object in the response.
|
|
264
|
+
|
|
265
|
+
A valid response must contain:
|
|
266
|
+
|
|
267
|
+
- `decision: "allow"` or `decision: "block"`
|
|
268
|
+
- `reason` as a string
|
|
269
|
+
|
|
270
|
+
If `tier` is missing, it defaults to `none`.
|
|
271
|
+
|
|
272
|
+
If parsing fails, the action is blocked with this reason:
|
|
273
|
+
|
|
274
|
+
```text
|
|
275
|
+
Classifier response was not valid decision JSON; auto mode fails closed.
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
If the model call throws, the action is blocked with a classifier failure message.
|
|
279
|
+
|
|
280
|
+
## State, UI, and denial history
|
|
281
|
+
|
|
282
|
+
Every checked action increments `checkedActions`.
|
|
283
|
+
|
|
284
|
+
Allowed actions store:
|
|
285
|
+
|
|
286
|
+
- `lastDecision: "allow"`
|
|
287
|
+
- `lastReason`
|
|
288
|
+
|
|
289
|
+
Blocked actions also increment `blockedActions` and add a denial record. Denial records keep:
|
|
290
|
+
|
|
291
|
+
- timestamp;
|
|
292
|
+
- tool name;
|
|
293
|
+
- reason;
|
|
294
|
+
- action summary;
|
|
295
|
+
- denial kind.
|
|
296
|
+
|
|
297
|
+
Recent denial history is capped at 12 entries. State is persisted with `pi.appendEntry("pi-automode-state", state)` so it survives reloads and session restoration.
|
|
298
|
+
|
|
299
|
+
When UI is available, the extension updates the footer status and shows a warning notification for blocked actions.
|
|
300
|
+
|
|
301
|
+
## Command interactions
|
|
302
|
+
|
|
303
|
+
The classifier flow can be inspected or changed through slash commands:
|
|
304
|
+
|
|
305
|
+
```text
|
|
306
|
+
/automode status
|
|
307
|
+
/automode on
|
|
308
|
+
/automode off
|
|
309
|
+
/automode reload
|
|
310
|
+
/automode reset
|
|
311
|
+
/automode defaults
|
|
312
|
+
/automode config
|
|
313
|
+
/automode denials
|
|
314
|
+
/automode model
|
|
315
|
+
/automode model provider/model-id
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
`/auto-mode` is an alias.
|
|
319
|
+
|
|
320
|
+
`/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`.
|
package/docs/defaults.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Defaults and rule-list behavior
|
|
2
|
+
|
|
3
|
+
## `$defaults`
|
|
4
|
+
|
|
5
|
+
`$defaults` expands to this plugin's built-in entries for the section where it appears. It is section-local: `$defaults` in `allow` means the built-in allow rules, not the built-in hard-deny rules.
|
|
6
|
+
|
|
7
|
+
### `environment`
|
|
8
|
+
|
|
9
|
+
`$defaults` expands to:
|
|
10
|
+
|
|
11
|
+
- trusted repo: the repository Pi started in and its configured git remotes
|
|
12
|
+
- source control: the trusted repo and its configured remotes only
|
|
13
|
+
- trusted internal domains: none configured
|
|
14
|
+
- trusted cloud buckets: none configured
|
|
15
|
+
- key internal services: none configured
|
|
16
|
+
|
|
17
|
+
Add your own entries when the classifier should know about trusted company infrastructure:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"autoMode": {
|
|
22
|
+
"environment": [
|
|
23
|
+
"$defaults",
|
|
24
|
+
"Trusted internal domains: *.corp.example.com, git.example.com",
|
|
25
|
+
"Trusted cloud buckets: s3://acme-dev-artifacts, gs://acme-ci-cache",
|
|
26
|
+
"Key internal services: staging deploy API at deploy.corp.example.com"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
These entries give the classifier context. They do not bypass `hard_deny` or automatically allow every action involving those services.
|
|
33
|
+
|
|
34
|
+
### `allow`
|
|
35
|
+
|
|
36
|
+
`$defaults` expands to allow exceptions for:
|
|
37
|
+
|
|
38
|
+
- read-only operations: inspecting files, listing directories, searching, GET requests, and state queries that do not expose secrets
|
|
39
|
+
- local development inside the working tree: creating, editing, building, testing, linting, formatting, and deleting files created during the current task
|
|
40
|
+
- installing dependencies already declared in package manifests or lockfiles
|
|
41
|
+
- using standard credentials only with their intended configured providers
|
|
42
|
+
- pushing to the current non-default working branch or a new branch created for the task
|
|
43
|
+
- bootstrapping language/toolchain installers from official sources
|
|
44
|
+
|
|
45
|
+
These are exceptions to `soft_deny`, not to `hard_deny`.
|
|
46
|
+
|
|
47
|
+
### `protectedPaths`
|
|
48
|
+
|
|
49
|
+
`$defaults` expands to paths where writes are never auto-approved — they always go to the classifier, regardless of `allow` rules. This matches Claude Code's protected-paths behavior.
|
|
50
|
+
|
|
51
|
+
Protected directories: `.git`, `.config/git`, `.vscode`, `.idea`, `.husky`, `.cargo`, `.devcontainer`, `.yarn`, `.mvn`, `.pi`.
|
|
52
|
+
|
|
53
|
+
Protected files: `.gitconfig`, `.gitmodules`, `.gitignore`, `.gitattributes`, shell profiles (`.bashrc`, `.zshrc`, `.profile`, etc.), `.envrc`, package manager configs (`.npmrc`, `.yarnrc`, `.yarnrc.yml`, `.pnp.cjs`, `bunfig.toml`, etc.), Bazel configs (`.bazelrc`, `.bazelversion`, `.bazeliskrc`), hook configs (`.pre-commit-config.yaml`, `lefthook.yml`), Gradle/Maven wrappers, `.devcontainer.json`, `.ripgreprc`, `pyrightconfig.json`, `.mcp.json`.
|
|
54
|
+
|
|
55
|
+
Read-only tools (`read`, `grep`, `find`, `ls`) bypass this check — reads to protected paths are always allowed. Only `write` and `edit` are affected.
|
|
56
|
+
|
|
57
|
+
### `soft_deny`
|
|
58
|
+
|
|
59
|
+
`$defaults` expands to soft blocks for:
|
|
60
|
+
|
|
61
|
+
- force push, remote branch deletion, remote history rewrite, or direct push to `main`, `master`, or the default branch
|
|
62
|
+
- downloading and executing external code, including `curl | bash` and `wget | sh`
|
|
63
|
+
- production deploys, production database migrations, or production infrastructure changes
|
|
64
|
+
- deleting, truncating, or overwriting pre-existing local files unless the user named the specific target
|
|
65
|
+
- mass modification/deletion in cloud storage, ticketing systems, collaboration tools, or external APIs
|
|
66
|
+
- writing through remote shells such as `ssh`, `kubectl exec`, or `docker exec` on shared or production hosts
|
|
67
|
+
- changing live/shared infrastructure without a preview, plan, dry-run, or review when one normally exists
|
|
68
|
+
- killing other users' processes, cancelling shared jobs, or disrupting shared infrastructure
|
|
69
|
+
- exposing local files, sockets, credentials, or internal services through a network service
|
|
70
|
+
- outbound messages, purchases, bookings, subscriptions, or other real-world transactions
|
|
71
|
+
|
|
72
|
+
Soft blocks can be overridden by a matching `allow` exception or by direct, specific user intent.
|
|
73
|
+
|
|
74
|
+
### `hard_deny`
|
|
75
|
+
|
|
76
|
+
`$defaults` expands to unconditional blocks for:
|
|
77
|
+
|
|
78
|
+
- weakening TLS, certificate validation, authentication, authorization, audit logging, or other security controls
|
|
79
|
+
- adding persistence such as SSH keys, shell profile edits, cron jobs, launch agents, login items, or background agents
|
|
80
|
+
- granting admin, owner, IAM, RBAC, repository, or production access
|
|
81
|
+
- exfiltrating repository contents, secrets, credentials, private keys, tokens, or internal data to untrusted places
|
|
82
|
+
- scanning for credentials, tokens, private keys, or secrets beyond what the task requires
|
|
83
|
+
- tampering with logs, audit trails, security monitoring, permission rules, auto-mode config, or safety-control files
|
|
84
|
+
- creating services, endpoints, workflows, or autonomous agents that execute arbitrary code without meaningful approval
|
|
85
|
+
- posting or updating public/external content that is fabricated, misleading, impersonating a user, or claiming approval/action that did not happen
|
|
86
|
+
|
|
87
|
+
Hard-deny rules cannot be overridden by `allow` or by user intent.
|
|
88
|
+
|
|
89
|
+
### Replacement behavior
|
|
90
|
+
|
|
91
|
+
Use `$defaults` when you want to keep the built-ins and add your own entries:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"autoMode": {
|
|
96
|
+
"allow": [
|
|
97
|
+
"$defaults",
|
|
98
|
+
"Running the staging deploy script is allowed."
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
That means: use all built-in `allow` entries, plus the staging rule.
|
|
105
|
+
|
|
106
|
+
If you omit `$defaults`, you replace the built-ins for that section:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"autoMode": {
|
|
111
|
+
"allow": [
|
|
112
|
+
"Running the staging deploy script is allowed."
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
That means: use only that one `allow` entry. The built-in `allow` entries are not used. Replacing `allow` does not replace `soft_deny`, `hard_deny`, `protectedPaths`, or `environment`.
|
|
119
|
+
|
|
120
|
+
`$defaults` is not used in `permissions.deny` or `permissions.ask`. Those lists contain only explicit Pi tool patterns.
|
|
121
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_ALLOW,
|
|
5
5
|
DEFAULT_ENVIRONMENT,
|
|
@@ -397,3 +397,38 @@ export function loadEffectiveConfigWithDiagnostics(
|
|
|
397
397
|
export function loadEffectiveConfig(cwd: string): EffectiveConfig {
|
|
398
398
|
return loadEffectiveConfigWithDiagnostics(cwd).config;
|
|
399
399
|
}
|
|
400
|
+
|
|
401
|
+
function readWritableSettingsFile(path: string): SettingsFile {
|
|
402
|
+
if (!existsSync(path)) return {};
|
|
403
|
+
const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
|
|
404
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
405
|
+
throw new Error(`${path}: root must be a JSON object`);
|
|
406
|
+
}
|
|
407
|
+
const settings = parsed as SettingsFile;
|
|
408
|
+
if (
|
|
409
|
+
settings.autoMode !== undefined &&
|
|
410
|
+
(!settings.autoMode ||
|
|
411
|
+
typeof settings.autoMode !== "object" ||
|
|
412
|
+
Array.isArray(settings.autoMode))
|
|
413
|
+
) {
|
|
414
|
+
throw new Error(`${path}: autoMode must be a JSON object`);
|
|
415
|
+
}
|
|
416
|
+
return settings;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Persist the global default classifier model while preserving other settings. */
|
|
420
|
+
export function writeGlobalClassifierModel(
|
|
421
|
+
classifierModel: string,
|
|
422
|
+
path = PI_GLOBAL_SETTINGS[0],
|
|
423
|
+
): void {
|
|
424
|
+
const settings = readWritableSettingsFile(path);
|
|
425
|
+
const next: SettingsFile = {
|
|
426
|
+
...settings,
|
|
427
|
+
autoMode: {
|
|
428
|
+
...settings.autoMode,
|
|
429
|
+
classifierModel,
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
433
|
+
writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
434
|
+
}
|
|
@@ -149,7 +149,7 @@ Examples:
|
|
|
149
149
|
Respond with JSON exactly matching:
|
|
150
150
|
{"decision":"allow"|"block","tier":"hard_deny"|"soft_deny"|"allow"|"explicit_intent"|"none","reason":"brief concrete reason"}`;
|
|
151
151
|
|
|
152
|
-
export const PI_GLOBAL_SETTINGS = [resolve(HOME, ".pi/automode.json")];
|
|
152
|
+
export const PI_GLOBAL_SETTINGS = [resolve(HOME, ".pi/agent/automode.json")];
|
|
153
153
|
export const PI_PROJECT_LOCAL_SETTINGS = [".pi/automode.local.json"];
|
|
154
154
|
export const PI_PROJECT_SHARED_SETTINGS = [".pi/automode.json"];
|
|
155
155
|
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
loadEffectiveConfig,
|
|
18
18
|
loadEffectiveConfigWithDiagnostics,
|
|
19
|
+
writeGlobalClassifierModel,
|
|
19
20
|
} from "./config.ts";
|
|
20
21
|
import { deterministicHardDeny } from "./hard-deny.ts";
|
|
21
22
|
import { formatModelSpec, parseModelSpec } from "./model.ts";
|
|
@@ -45,6 +46,8 @@ export type PiAutomodeOptions = {
|
|
|
45
46
|
loadConfig?: (cwd: string) => EffectiveConfig;
|
|
46
47
|
/** Override classifier calls in tests so unit tests never need a real LLM/API key. */
|
|
47
48
|
classifyAction?: ClassifyAction;
|
|
49
|
+
/** Override classifier-model persistence in tests. Runtime code writes ~/.pi/agent/automode.json. */
|
|
50
|
+
saveClassifierModel?: (classifierModel: string) => void;
|
|
48
51
|
};
|
|
49
52
|
|
|
50
53
|
/** Create a Pi extension instance. Default export uses production dependencies. */
|
|
@@ -56,6 +59,8 @@ export function createPiAutomode(options: PiAutomodeOptions = {}) {
|
|
|
56
59
|
})
|
|
57
60
|
: loadEffectiveConfigWithDiagnostics;
|
|
58
61
|
const classify = options.classifyAction ?? defaultClassifyAction;
|
|
62
|
+
const saveClassifierModel = options.saveClassifierModel ??
|
|
63
|
+
writeGlobalClassifierModel;
|
|
59
64
|
|
|
60
65
|
return function piAutomode(pi: ExtensionAPI) {
|
|
61
66
|
let loadResult = loadConfigWithDiagnostics(process.cwd());
|
|
@@ -72,8 +77,6 @@ export function createPiAutomode(options: PiAutomodeOptions = {}) {
|
|
|
72
77
|
return {
|
|
73
78
|
...config,
|
|
74
79
|
enabled: state.enabledOverride ?? config.enabled,
|
|
75
|
-
classifierModel: state.classifierModelOverride ??
|
|
76
|
-
config.classifierModel,
|
|
77
80
|
};
|
|
78
81
|
}
|
|
79
82
|
|
|
@@ -294,7 +297,6 @@ export function createPiAutomode(options: PiAutomodeOptions = {}) {
|
|
|
294
297
|
blockedActions: 0,
|
|
295
298
|
recentDenials: [],
|
|
296
299
|
enabledOverride: state.enabledOverride,
|
|
297
|
-
classifierModelOverride: state.classifierModelOverride,
|
|
298
300
|
};
|
|
299
301
|
persist();
|
|
300
302
|
updateUi(ctx);
|
|
@@ -335,36 +337,20 @@ export function createPiAutomode(options: PiAutomodeOptions = {}) {
|
|
|
335
337
|
return;
|
|
336
338
|
}
|
|
337
339
|
if (command === "model") {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
ctx.ui.notify("Classifier model unchanged", "info");
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
const parsed = parseModelSpec(selected);
|
|
348
|
-
const model = parsed
|
|
349
|
-
? ctx.modelRegistry.find(parsed.provider, parsed.id)
|
|
350
|
-
: undefined;
|
|
351
|
-
if (model) {
|
|
352
|
-
state.classifierModelOverride = selected;
|
|
353
|
-
persist();
|
|
354
|
-
updateUi(ctx);
|
|
355
|
-
ctx.ui.notify(
|
|
356
|
-
`pi-automode classifier set for this session: ${selected}`,
|
|
357
|
-
"info",
|
|
358
|
-
);
|
|
359
|
-
}
|
|
340
|
+
const selected = remainder || await promptForClassifierModel(
|
|
341
|
+
ctx,
|
|
342
|
+
effectiveConfig().classifierModel,
|
|
343
|
+
);
|
|
344
|
+
if (!selected) {
|
|
345
|
+
ctx.ui.notify("Classifier model unchanged", "info");
|
|
360
346
|
return;
|
|
361
347
|
}
|
|
362
|
-
const parsed = parseModelSpec(
|
|
348
|
+
const parsed = parseModelSpec(selected);
|
|
363
349
|
const model = parsed
|
|
364
350
|
? ctx.modelRegistry.find(parsed.provider, parsed.id)
|
|
365
351
|
: undefined;
|
|
366
352
|
if (!model) {
|
|
367
|
-
ctx.ui.notify(`Model not found: ${
|
|
353
|
+
ctx.ui.notify(`Model not found: ${selected}`, "error");
|
|
368
354
|
return;
|
|
369
355
|
}
|
|
370
356
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
@@ -372,11 +358,29 @@ export function createPiAutomode(options: PiAutomodeOptions = {}) {
|
|
|
372
358
|
ctx.ui.notify(auth.error, "error");
|
|
373
359
|
return;
|
|
374
360
|
}
|
|
375
|
-
|
|
361
|
+
const modelSpec = formatModelSpec(model);
|
|
362
|
+
try {
|
|
363
|
+
saveClassifierModel(modelSpec);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
ctx.ui.notify(
|
|
366
|
+
`Failed to save classifier model: ${
|
|
367
|
+
error instanceof Error ? error.message : String(error)
|
|
368
|
+
}`,
|
|
369
|
+
"error",
|
|
370
|
+
);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
loadResult = loadConfigWithDiagnostics(ctx.cwd);
|
|
374
|
+
config = loadResult.config;
|
|
375
|
+
configDiagnostics = loadResult.diagnostics;
|
|
376
376
|
persist();
|
|
377
377
|
updateUi(ctx);
|
|
378
|
+
const active = effectiveConfig().classifierModel ??
|
|
379
|
+
"current session model";
|
|
378
380
|
ctx.ui.notify(
|
|
379
|
-
|
|
381
|
+
active === modelSpec
|
|
382
|
+
? `pi-automode classifier saved globally: ${modelSpec}`
|
|
383
|
+
: `pi-automode classifier saved globally: ${modelSpec}; current config uses ${active}`,
|
|
380
384
|
"info",
|
|
381
385
|
);
|
|
382
386
|
return;
|
|
@@ -32,10 +32,7 @@ export function statusText(
|
|
|
32
32
|
): string {
|
|
33
33
|
return [
|
|
34
34
|
`enabled: ${(state.enabledOverride ?? config.enabled) ? "yes" : "no"}`,
|
|
35
|
-
`classifier: ${
|
|
36
|
-
state.classifierModelOverride ?? config.classifierModel ??
|
|
37
|
-
"current session model"
|
|
38
|
-
}`,
|
|
35
|
+
`classifier: ${config.classifierModel ?? "current session model"}`,
|
|
39
36
|
`checked actions: ${state.checkedActions}`,
|
|
40
37
|
`blocked actions: ${state.blockedActions}`,
|
|
41
38
|
`permissions.deny rules: ${config.permissionDeny.length}`,
|
|
@@ -89,7 +86,6 @@ export function restoreState(ctx: ExtensionContext): AutoModeState {
|
|
|
89
86
|
}
|
|
90
87
|
return {
|
|
91
88
|
enabledOverride: entry.data.enabledOverride,
|
|
92
|
-
classifierModelOverride: entry.data.classifierModelOverride,
|
|
93
89
|
lastDecision: entry.data.lastDecision,
|
|
94
90
|
lastReason: entry.data.lastReason,
|
|
95
91
|
checkedActions: entry.data.checkedActions ?? 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@czottmann/pi-automode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Claude Code-style auto mode guardrail for pi.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "https://github.com/czottmann/pi-automode"
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"test": "node --import tsx --test tests/*.test.ts"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
+
"docs",
|
|
28
29
|
"extensions",
|
|
29
30
|
"examples",
|
|
30
31
|
"README.md"
|
|
@@ -36,7 +37,8 @@
|
|
|
36
37
|
},
|
|
37
38
|
"peerDependencies": {
|
|
38
39
|
"@earendil-works/pi-ai": "*",
|
|
39
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
40
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
41
|
+
"@earendil-works/pi-tui": "*"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@earendil-works/pi-ai": "^0.79.3",
|