@mortz.prk/opencode-auto-mode 1.0.2 → 1.1.2
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/CONTRIBUTING.md +234 -0
- package/README.llm.md +320 -0
- package/README.md +180 -295
- package/dist/gui-experimental.cjs +1 -1
- package/package.json +9 -8
- package/src/audit.ts +130 -49
- package/src/classifier.ts +44 -47
- package/src/code-mode.ts +55 -0
- package/src/config.ts +102 -105
- package/src/debug-events.ts +223 -0
- package/src/decision.ts +72 -0
- package/src/gates.ts +16 -0
- package/src/global-options.ts +85 -0
- package/src/gui-state.ts +230 -121
- package/src/gui.tsx +56 -107
- package/src/index.ts +482 -373
- package/src/mode.ts +237 -0
- package/src/reviewer.ts +70 -94
- package/src/rpc.ts +48 -38
- package/src/score/files.ts +51 -0
- package/src/score/history.ts +141 -0
- package/src/score/index.ts +38 -0
- package/src/score/limits.ts +24 -0
- package/src/score/shell-argv.ts +74 -0
- package/src/score/shell-baseline.ts +113 -0
- package/src/score/shell-safety.ts +57 -0
- package/src/score/shell.ts +32 -0
- package/src/score/types.ts +14 -0
- package/src/score.ts +9 -0
- package/src/shell-policy.ts +672 -30
- package/src/shell.ts +276 -55
- package/src/status.ts +49 -36
- package/src/tiers.ts +292 -19
- package/src/tui.tsx +44 -34
- package/src/user-files.ts +75 -0
- package/src/worktree.ts +30 -0
- package/src/decisions.ts +0 -236
- package/src/logger.ts +0 -26
- package/src/otlp.ts +0 -77
- package/src/policy.ts +0 -36
- package/src/state.ts +0 -135
- package/src/storage.ts +0 -26
- package/src/traces.ts +0 -90
- package/src/transcript.ts +0 -204
- package/src/tui-state.ts +0 -13
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
This repository is the `opencode-auto-mode` plugin for OpenCode V2. Start with
|
|
4
|
+
[README.md](README.md) for user setup and [README.llm.md](README.llm.md) for the
|
|
5
|
+
AI installation contract. This file covers the codebase, local development, and
|
|
6
|
+
review expectations.
|
|
7
|
+
|
|
8
|
+
## Repository map
|
|
9
|
+
|
|
10
|
+
| Path | Responsibility |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `src/index.ts` | Plugin entry. Wires the permission `evaluate` hook, Code Mode `execute.before/after`, mode commands, status RPC, audit, and cleanup. |
|
|
13
|
+
| `src/config.ts` | Strict public option parsing and defaults. Unknown keys fail. |
|
|
14
|
+
| `src/global-options.ts` | Loads safety options from exactly one user-owned global registration. Ignores project-merged `ctx.options`; rejects duplicate registrations and env/file substitution. |
|
|
15
|
+
| `src/score.ts`, `src/score/` | `src/score.ts` is the public `scoreRequest` compatibility re-export. Implementation lives under `src/score/` (`index`, `types`, `limits`, `files`, `history`, `shell`, `shell-argv`, `shell-safety`, `shell-baseline`). Hand-written shell, path, credential, VCS, and tool routing guards live in this ownership area. |
|
|
16
|
+
| `src/shell.ts` | Parsed shell guard: deny/ask classification, protected operands, recursive deletion, history changes, ambiguity floors. |
|
|
17
|
+
| `src/shell-policy.ts` | Shell grammar and command policy vocabulary. Large on purpose; it models parser behavior rather than matching raw text. |
|
|
18
|
+
| `src/tiers.ts` | Protected paths, credential names, symlink traversal, project/worktree containment, safe edit targets. |
|
|
19
|
+
| `src/worktree.ts` | Discovers the session's current worktree root and canonical location. |
|
|
20
|
+
| `src/gates.ts` | VCS preconditions for history-changing commands. Missing facts fail toward review. |
|
|
21
|
+
| `src/classifier.ts` | Ordered classifier pipeline. First decisive allow/deny wins; otherwise ask. |
|
|
22
|
+
| `src/decision.ts` | Native typed decision stage using `ctx.experimental.generate.decisions`. |
|
|
23
|
+
| `src/reviewer.ts` | LLM reviewer prompt, evidence filtering, response parsing, and deny-gate behavior. |
|
|
24
|
+
| `src/code-mode.ts` | Code Mode SHA-256 identity, save entry, identity validation, and reviewer source resource. |
|
|
25
|
+
| `src/mode.ts` | Durable per-session mode, daily counts, breaker window, and cancellation repair. |
|
|
26
|
+
| `src/status.ts`, `src/rpc.ts` | Bounded status surface and typed RPC contract (`status`, `setMode`). |
|
|
27
|
+
| `src/gui-state.ts`, `src/gui.tsx`, `src/tui.tsx` | Shared UI state plus desktop GUI and terminal UI parts. |
|
|
28
|
+
| `src/audit.ts`, `src/debug-events.ts` | Opt-in local JSONL audit and unsafe debug capture, including rotation. |
|
|
29
|
+
| `src/user-files.ts` | JSONC parsing and user-owned-file checks. |
|
|
30
|
+
| `src/timeout.ts`, `src/errors.ts` | Bounded provider calls and stable error categories. |
|
|
31
|
+
| `scripts/replay.ts` | Checkout-only offline audit replay with static diagnostics. |
|
|
32
|
+
| `scripts/no-personal-defaults.test.ts` | Privacy guard for models, endpoints, credentials, and allowlist-shaped defaults. |
|
|
33
|
+
| `scripts/dev-reload.sh` | Optional GUI build plus an explicit `opencode reload` request. Never restarts the live service. |
|
|
34
|
+
| `integration/opencode.ts` | Real `opencode serve` integration harness using scratch HOME/XDG roots and fixture providers. |
|
|
35
|
+
| `integration/transport.ts`, `integration/code-mode-identity.ts`, `integration/spikes.ts` | Fixture transport, Code Mode identity regression, and isolated host-behavior checks. |
|
|
36
|
+
| `.github/workflows/` | PR checks and the manually triggered npm release workflow. |
|
|
37
|
+
|
|
38
|
+
Tests live next to the source as `*.test.ts`. The server entry is exported through
|
|
39
|
+
the package root; `./tui`, `./rpc`, and `./gui-experimental` are separate exports.
|
|
40
|
+
`src/.npmignore` excludes test files from the packaged `src` directory.
|
|
41
|
+
|
|
42
|
+
Documentation is intentionally exactly three root files: `README.md` (human
|
|
43
|
+
setup), `README.llm.md` (AI installation), and `CONTRIBUTING.md` (this file). All
|
|
44
|
+
three are listed in `package.json` `files` and ship in the npm package. There is
|
|
45
|
+
no `docs/` directory; do not recreate one.
|
|
46
|
+
|
|
47
|
+
## Local development
|
|
48
|
+
|
|
49
|
+
Prerequisites: Bun for the repository scripts, and OpenCode V2 for integration
|
|
50
|
+
tests. CI uses Bun `1.4.3` and Node `24`.
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
bun install
|
|
54
|
+
bun test
|
|
55
|
+
bun run lint
|
|
56
|
+
bun run typecheck
|
|
57
|
+
bun run build:gui
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The server part is TypeScript loaded from `src/`; no server build step is needed.
|
|
61
|
+
`bun run build:gui` writes `dist/gui-experimental.cjs` and is required before
|
|
62
|
+
desktop testing or packaging. `prepublishOnly` builds the GUI automatically.
|
|
63
|
+
|
|
64
|
+
To dogfood a checkout, register its absolute directory **once** in your global
|
|
65
|
+
config, preserving your existing options:
|
|
66
|
+
|
|
67
|
+
```jsonc
|
|
68
|
+
{
|
|
69
|
+
"$schema": "https://opencode.ai/config.json",
|
|
70
|
+
"plugins": [{
|
|
71
|
+
"package": "/absolute/path/to/opencode-auto-mode",
|
|
72
|
+
"options": {
|
|
73
|
+
"classifiers": [
|
|
74
|
+
{ "type": "static" },
|
|
75
|
+
{ "type": "llm", "model": "<provider>/<reviewer-model>" }
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Do not also register it in project config, `cli.json`, or `.opencode/plugins/`.
|
|
83
|
+
The global-options loader requires exactly one matching global registration.
|
|
84
|
+
Config files reload automatically. `scripts/dev-reload.sh [--gui]` only requests
|
|
85
|
+
`opencode reload`; source/dependency watching and cached server-code replacement
|
|
86
|
+
remain unverified. Do not restart a shared live service as a routine edit step.
|
|
87
|
+
|
|
88
|
+
## Host behavior notes
|
|
89
|
+
|
|
90
|
+
These were observed against OpenCode `2.2.x`. Re-check them on the eventual
|
|
91
|
+
minimum server version before making release claims.
|
|
92
|
+
|
|
93
|
+
- A configured permission deny can skip `evaluate`, while a `policies` deny can
|
|
94
|
+
still invoke it before the final native block. Do not treat `hostEffect` as the
|
|
95
|
+
final execution outcome.
|
|
96
|
+
- The actual `execute` tool does not invoke permission `evaluate`; the plugin's
|
|
97
|
+
`execute.before` bridge is required for Code Mode review.
|
|
98
|
+
- Project registrations can merge `ctx.options`; `src/global-options.ts` ignores
|
|
99
|
+
those merged options rather than rejecting them by reading only the user-owned
|
|
100
|
+
global registration. That is a narrow configuration boundary, not a sandbox
|
|
101
|
+
against project plugins.
|
|
102
|
+
- Config saves reload plugins. An explicit server reload can pick up a source
|
|
103
|
+
marker, but attribution to the CLI versus automatic watching, and dependency or
|
|
104
|
+
bundle watching, remain unverified.
|
|
105
|
+
- Native permission rules were observed as last-match-wins. Put broad asks
|
|
106
|
+
before exceptions. Allows still reach the permission hook.
|
|
107
|
+
|
|
108
|
+
## Tests
|
|
109
|
+
|
|
110
|
+
Run the full unit suite after source changes settle:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
bun test
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Focused checks while touching docs, scripts, or a narrow module:
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
bun test src/score.test.ts src/score-security.test.ts src/shell.test.ts
|
|
120
|
+
bun test scripts/no-personal-defaults.test.ts scripts/replay.test.ts
|
|
121
|
+
bunx eslint scripts/replay.ts scripts/replay.test.ts scripts/no-personal-defaults.test.ts
|
|
122
|
+
git diff --check
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`bun test` includes the co-located unit tests plus the Bun tests under `scripts/`
|
|
126
|
+
and `integration/transport.test.ts`. The full real-server suite is separate:
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
bun run test:integration
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
It launches `opencode serve` (or `OPENCODE_BINARY`) against a scratch HOME and XDG
|
|
133
|
+
config/data/cache/state/tmp roots, starts loopback fixture providers, and sets
|
|
134
|
+
`OPENCODE_PLUGIN_PATH` to the checkout under test. Cases rewrite only scratch
|
|
135
|
+
configuration and saved approvals. Never use personal config, personal saved
|
|
136
|
+
approvals, personal audit logs, production credentials, or production models.
|
|
137
|
+
|
|
138
|
+
Run `bun run test:integration` after classifier, audit, Code Mode, mode, or
|
|
139
|
+
permission-hook changes. It covers allow/deny/invalid/timeout reviewer paths,
|
|
140
|
+
protected-path floors, native allow and saved-approval review, mode commands,
|
|
141
|
+
breaker behavior, audit isolation, shell execution, hostile project options, and
|
|
142
|
+
Code Mode identity/evidence rejection. Last recorded results: 44 scenarios passed
|
|
143
|
+
against OpenCode `2.2.5`, and `bun test` reported 396 passing. Record the actual
|
|
144
|
+
result of each run rather than assuming a historical count.
|
|
145
|
+
|
|
146
|
+
`bun run build:gui` verifies the desktop bundle. The privacy guard is part of
|
|
147
|
+
`bun test`; run it before packing. It is pattern-based, not a general secret
|
|
148
|
+
scanner. Build output, dependencies, lockfiles, and license text are outside its
|
|
149
|
+
coverage.
|
|
150
|
+
|
|
151
|
+
## Complexity linter
|
|
152
|
+
|
|
153
|
+
All new or extracted functions must meet these limits:
|
|
154
|
+
|
|
155
|
+
- cyclomatic complexity **<= 10**;
|
|
156
|
+
- **<= 60 nonblank/non-comment lines** per function;
|
|
157
|
+
- **max depth <= 4**.
|
|
158
|
+
|
|
159
|
+
`bun run lint` runs ESLint plus `node scripts/complexity-lint.js`. The linter
|
|
160
|
+
applies those limits to all JavaScript/TypeScript and compares each function/rule
|
|
161
|
+
exceedance with `scripts/complexity-baseline.json`. Entries are keyed by file,
|
|
162
|
+
rule, function label, and function or depth-block start `line:column`; anonymous
|
|
163
|
+
arrows, separate depth blocks, and repeated function names therefore each need
|
|
164
|
+
their own exemption.
|
|
165
|
+
New exceedances and increases over a legacy entry fail. Existing legacy entries may
|
|
166
|
+
shrink but not grow. Moving a legacy violation (or lines before it) changes its
|
|
167
|
+
identity and is reported as new until the baseline is refreshed in a reviewed
|
|
168
|
+
change, so avoid unrelated movement around legacy entries. `src/score.ts`,
|
|
169
|
+
`src/score/**`, and `scripts/complexity*` are strict: they cannot be baselined or
|
|
170
|
+
exempted and must pass the limits directly. Tests are not exempt from these
|
|
171
|
+
limits.
|
|
172
|
+
|
|
173
|
+
`node scripts/complexity-lint.js --update` records improvements only. All failure
|
|
174
|
+
checks run before the file is written, so it refuses without modifying the
|
|
175
|
+
baseline while new, increased, or strict scoring/tooling violations exist. Do not
|
|
176
|
+
cite a different threshold.
|
|
177
|
+
|
|
178
|
+
## Coding conventions
|
|
179
|
+
|
|
180
|
+
- TypeScript is strict ESM. Relative imports use the `.js` suffix even from
|
|
181
|
+
`.ts`/`.tsx` sources.
|
|
182
|
+
- Tests use `node:test` in `src/` and Bun's `bun:test` in `scripts/` and
|
|
183
|
+
`integration/transport.test.ts`. Match the surrounding file.
|
|
184
|
+
- Preserve the safety contract:
|
|
185
|
+
- native `deny` is final and is never reviewed or weakened;
|
|
186
|
+
- invalid configuration performs no review and leaves native behavior in
|
|
187
|
+
place;
|
|
188
|
+
- uncertain, failed, interrupted, or timed-out reviews resolve to `ask`;
|
|
189
|
+
- no default model, endpoint, credential, or allowlist is added;
|
|
190
|
+
- safety options come from the user-owned global registration, not merged
|
|
191
|
+
project options;
|
|
192
|
+
- hand-written guards belong in `scoreRequest`; classifiers must not override
|
|
193
|
+
mandatory score deny/ask effects;
|
|
194
|
+
- Code Mode identity is the exact SHA-256 of the source, metadata must validate
|
|
195
|
+
that identity, and the source is supplied to reviewers as a resource.
|
|
196
|
+
- Do not reintroduce removed features: shadow mode, custom rules, unconfigured
|
|
197
|
+
mode, rule IDs, performance metrics, OTLP, or remote log export.
|
|
198
|
+
- Do not log or commit personal audit data. Audit records are unredacted by
|
|
199
|
+
design and may contain secrets, prompts, and file contents.
|
|
200
|
+
- Keep prose in the three docs short and accurate. Prefer verified source
|
|
201
|
+
behavior and linked OpenCode V2 docs over guesses.
|
|
202
|
+
|
|
203
|
+
## Release checks
|
|
204
|
+
|
|
205
|
+
Before a release, run on settled source:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
bun install --frozen-lockfile
|
|
209
|
+
bun test
|
|
210
|
+
bun run lint
|
|
211
|
+
bun run typecheck
|
|
212
|
+
bun run build:gui
|
|
213
|
+
bun run test:integration
|
|
214
|
+
bun test scripts/no-personal-defaults.test.ts scripts/replay.test.ts
|
|
215
|
+
git diff --check
|
|
216
|
+
npm pack --dry-run
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Confirm the packed file list includes the server and GUI entries plus `README.md`,
|
|
220
|
+
`README.llm.md`, and `CONTRIBUTING.md`, that their relative links resolve in the
|
|
221
|
+
packed layout, that `scripts/` and `docs/` are absent, and that no personal
|
|
222
|
+
models, endpoints, credentials, or allowlists are present. Verify the actual
|
|
223
|
+
`npm pack` result rather than assuming the link layout.
|
|
224
|
+
|
|
225
|
+
Last recorded results: 44 integration scenarios passed against OpenCode `2.2.5`;
|
|
226
|
+
the full unit suite also passed (396 tests when recorded, 399 on the current
|
|
227
|
+
tree). Manual desktop acceptance and multi-day dogfood have not been executed. A
|
|
228
|
+
green local unit/lint/typecheck/pack run is not a release readiness claim; record
|
|
229
|
+
the actual real-server, desktop, and dogfood results before claiming one.
|
|
230
|
+
|
|
231
|
+
Do not commit, tag, publish, restart a live service, or change personal
|
|
232
|
+
configuration as part of a code change unless the owner has explicitly authorized
|
|
233
|
+
that action. The release workflow is manual and requires `main` plus owner
|
|
234
|
+
authorization.
|
package/README.llm.md
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# OpenCode Auto Mode: AI installation guide
|
|
2
|
+
|
|
3
|
+
Use this file when an AI agent is installing, auditing, or migrating the plugin
|
|
4
|
+
for a user. The human-facing overview is [README.md](README.md). Code changes are
|
|
5
|
+
covered by [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
6
|
+
|
|
7
|
+
Sources of truth checked for this guide:
|
|
8
|
+
|
|
9
|
+
- [OpenCode V2 configuration](https://opencode.ai/v2/docs/config)
|
|
10
|
+
- [OpenCode V2 plugins](https://opencode.ai/v2/docs/build/plugins)
|
|
11
|
+
- This repository's `package.json` and `src/` implementation.
|
|
12
|
+
|
|
13
|
+
## Contract
|
|
14
|
+
|
|
15
|
+
- Package: `@mortz.prk/opencode-auto-mode` version `1.1.0`.
|
|
16
|
+
- Host: OpenCode V2; the checked-in SDK/schema targets OpenCode `2.2.0`.
|
|
17
|
+
- Register the package **once** in the user-owned global config:
|
|
18
|
+
`~/.config/opencode/opencode.json(c)` or
|
|
19
|
+
`$XDG_CONFIG_HOME/opencode/opencode.json(c)`. `XDG_CONFIG_HOME`, when set,
|
|
20
|
+
must be absolute.
|
|
21
|
+
- The configuration file must be outside the repository, contain no symlink in
|
|
22
|
+
any path component, be user-owned, not group/other-writable, and be a single
|
|
23
|
+
hardlink. System-owned non-writable parents are accepted.
|
|
24
|
+
- Do not add the full plugin to project config, `cli.json`, or
|
|
25
|
+
`.opencode/plugins/` as a second registration. A plain package name, a
|
|
26
|
+
version-suffixed name, a local path (absolute or relative to the config file),
|
|
27
|
+
or a `file://` URL that resolves to this package all count as registrations.
|
|
28
|
+
- Do not put model endpoints, credentials, production model defaults, or
|
|
29
|
+
allowlists in repository files. They belong in the user's OpenCode provider
|
|
30
|
+
config.
|
|
31
|
+
- The plugin reads safety options from its global registration only. It ignores
|
|
32
|
+
project-merged `ctx.options` and rejects duplicate registrations or ambiguous
|
|
33
|
+
global files.
|
|
34
|
+
- **Data egress:** a configured `decision` or `llm` stage sends the action and
|
|
35
|
+
resources (including full Code Mode source), agent, project/worktree, git
|
|
36
|
+
state, and up to 12 KB of user messages to that stage's model provider, even
|
|
37
|
+
when audit is disabled. The `static` stage makes no model call.
|
|
38
|
+
- Registration strings and option values do **not** support OpenCode
|
|
39
|
+
`{env:...}` or `{file:...}` substitution. Write concrete values after the user
|
|
40
|
+
chooses them.
|
|
41
|
+
- Invalid configuration is **passthrough**, not fail-closed. Review stops and
|
|
42
|
+
native OpenCode behavior applies.
|
|
43
|
+
|
|
44
|
+
## Preflight
|
|
45
|
+
|
|
46
|
+
1. Confirm the OpenCode version. Install or test against V2, not V1.
|
|
47
|
+
2. Locate the global config. Prefer `opencode.jsonc`; OpenCode accepts
|
|
48
|
+
`opencode.json` or `opencode.jsonc`.
|
|
49
|
+
3. Verify loader acceptance: the file must be outside the repository, have no
|
|
50
|
+
symlink in any path component, be user-owned, not group/other-writable, and
|
|
51
|
+
be a single hardlink. `XDG_CONFIG_HOME`, when set, must be absolute; when
|
|
52
|
+
unset, the loader uses `~/.config`. Non-writable system-owned parents are
|
|
53
|
+
accepted.
|
|
54
|
+
4. Back up the global config before editing it.
|
|
55
|
+
5. Inspect `plugins` for an existing registration of
|
|
56
|
+
`@mortz.prk/opencode-auto-mode`. There must be exactly one. A plain package
|
|
57
|
+
name, a version-suffixed name such as
|
|
58
|
+
`@mortz.prk/opencode-auto-mode@1.1.0`, a local path (absolute or relative),
|
|
59
|
+
and a `file://` URL that resolves to this package all count as registrations.
|
|
60
|
+
6. If both `opencode.json` and `opencode.jsonc` exist, check both. A matching
|
|
61
|
+
registration in both files is ambiguous and invalid.
|
|
62
|
+
7. Confirm the user has provider/model references for any `decision` or `llm`
|
|
63
|
+
stage. Do not invent model IDs or credentials.
|
|
64
|
+
8. For a local checkout used in the desktop app, run `bun run build:gui` before
|
|
65
|
+
testing; desktop loads `dist/gui-experimental.cjs`.
|
|
66
|
+
9. Preserve every unrelated setting in the file. Add or edit only the plugin
|
|
67
|
+
entry.
|
|
68
|
+
|
|
69
|
+
The config schema is `https://opencode.ai/config.json`. A global example:
|
|
70
|
+
|
|
71
|
+
```jsonc
|
|
72
|
+
{
|
|
73
|
+
"$schema": "https://opencode.ai/config.json",
|
|
74
|
+
"plugins": [{
|
|
75
|
+
"package": "@mortz.prk/opencode-auto-mode",
|
|
76
|
+
"options": {
|
|
77
|
+
"classifiers": [
|
|
78
|
+
{ "type": "static" },
|
|
79
|
+
{ "type": "llm", "model": "<provider>/<reviewer-model>" }
|
|
80
|
+
],
|
|
81
|
+
"reviewTimeoutMs": 15000,
|
|
82
|
+
"breaker": { "consecutiveDenies": 3, "windowDenies": 10, "windowSize": 50 },
|
|
83
|
+
"tui": true,
|
|
84
|
+
"audit": { "enabled": false, "maxBytes": 10485760 }
|
|
85
|
+
}
|
|
86
|
+
}]
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
For a local checkout, use the absolute checkout directory or a `file://` URL as
|
|
91
|
+
`package`. It must resolve to this package's real directory. For a published
|
|
92
|
+
package, a version suffix such as `@mortz.prk/opencode-auto-mode@1.1.0` is valid.
|
|
93
|
+
|
|
94
|
+
## Install procedure
|
|
95
|
+
|
|
96
|
+
1. Edit the chosen global config. Keep exactly one matching plugin entry.
|
|
97
|
+
2. Set `options.classifiers` to at least one stage. Start with `static` only when
|
|
98
|
+
no review model is available:
|
|
99
|
+
|
|
100
|
+
```jsonc
|
|
101
|
+
"classifiers": [{ "type": "static" }]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
3. Add a `decision` stage only when the user has a native decision provider:
|
|
105
|
+
|
|
106
|
+
```jsonc
|
|
107
|
+
{ "type": "decision", "model": "<provider>/<decision-model>", "minConfidence": 0.9, "timeoutMs": 3000 }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
4. Add an `llm` stage for the explicit reasoning reviewer. `model` is required;
|
|
111
|
+
there is no session-model fallback:
|
|
112
|
+
|
|
113
|
+
```jsonc
|
|
114
|
+
{ "type": "llm", "model": "<provider>/<reviewer-model>", "timeoutMs": 5000 }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
5. Save the file. Do not restart OpenCode for a config-only change. Config files
|
|
118
|
+
are watched and reload automatically; `/reload` forces a full reload if the
|
|
119
|
+
plugin does not appear.
|
|
120
|
+
6. In a session, run `/auto-mode status`. Expected for a valid install:
|
|
121
|
+
`Auto mode: on` with counts, active reviews, and breaker state.
|
|
122
|
+
7. If the command reports `Auto mode: invalid` with `Review disabled: <error>`
|
|
123
|
+
(the desktop pill reads `Invalid config — review disabled`), inspect the
|
|
124
|
+
exact error and fix the option shape. Do not add removed 1.0.x keys. A
|
|
125
|
+
desktop pill reading `Review unavailable` or `status unavailable`, or a
|
|
126
|
+
command that appends a storage/status error without `Review disabled:`, is a
|
|
127
|
+
status/storage read failure, not a configuration error.
|
|
128
|
+
8. Exercise a harmless permission request, such as an in-project read the
|
|
129
|
+
configured chain can resolve. Do not test with destructive commands,
|
|
130
|
+
credential files, or production data.
|
|
131
|
+
|
|
132
|
+
## Options
|
|
133
|
+
|
|
134
|
+
Unknown keys fail validation. `options` must be an object with only these keys.
|
|
135
|
+
|
|
136
|
+
| Option | Default | Contract |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| `classifiers` | required | Array of 1–64 ordered stages: `static`, `decision`, `llm`. At most one `static`. Missing or empty is invalid. |
|
|
139
|
+
| `reviewTimeoutMs` | `15000` | Whole-review deadline. Integer 1–120000. |
|
|
140
|
+
| `breaker` | `3 / 10 / 50` | `{consecutiveDenies, windowDenies, windowSize}`. If supplied, all three keys are required; each is an integer 1–10000 and `windowDenies <= windowSize`. |
|
|
141
|
+
| `tui` | `true` | Boolean. Shows or hides the TUI indicator. UI-only; `false` does not disable server review. |
|
|
142
|
+
| `audit` | `{enabled:false,maxBytes:10485760}` | Object only. `maxBytes: 0` disables rotation. Fixed path; no path option. |
|
|
143
|
+
|
|
144
|
+
Classifier stages:
|
|
145
|
+
|
|
146
|
+
| Stage | Shape | Behavior |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| `static` | `{type:"static", allowInProjectEdits?: boolean}` | Allows low-risk reads and verified in-project edits. `allowInProjectEdits: false` keeps edit/write actions out of static allows. |
|
|
149
|
+
| `decision` | `{type:"decision", model, timeoutMs?, minConfidence?}` | Native `ctx.experimental.generate.decisions` only. Defaults: timeout 3000, confidence 0.9. |
|
|
150
|
+
| `llm` | `{type:"llm", model, timeoutMs?}` | Native `ctx.experimental.generate.text` reviewer. `model` is explicit and required. |
|
|
151
|
+
|
|
152
|
+
Stages run in array order. The first decisive allow or deny stops the chain.
|
|
153
|
+
Abstention, low confidence, invalid output, provider errors, and timeouts continue
|
|
154
|
+
to the next stage. A chain that cannot decide asks the user. Per-stage timeouts
|
|
155
|
+
are bounded by `reviewTimeoutMs`.
|
|
156
|
+
|
|
157
|
+
## Decision behavior
|
|
158
|
+
|
|
159
|
+
| State | Native allow | Native ask | Native deny |
|
|
160
|
+
| --- | --- | --- | --- |
|
|
161
|
+
| On | Reviewed | Reviewed | Final; not reviewed |
|
|
162
|
+
| Off | Passthrough | Passthrough | Final |
|
|
163
|
+
| Paused breaker | Resolves to `ask` | Native prompt | Final |
|
|
164
|
+
| Invalid config | No review; native behavior | No review; native behavior | Final |
|
|
165
|
+
|
|
166
|
+
- Native deny is always final. Saved **Always** entries are re-reviewed when mode
|
|
167
|
+
is on but are never deleted, narrowed, rewritten, or migrated.
|
|
168
|
+
- A valid review that fails, times out, is interrupted, or cannot resolve
|
|
169
|
+
uncertainty resolves to `ask`.
|
|
170
|
+
- Mode off restores the original host effect and message.
|
|
171
|
+
- A paused breaker resolves a reviewed native allow to `ask`; an existing native
|
|
172
|
+
ask keeps its prompt. Paused mode never automatically allows.
|
|
173
|
+
- `/auto-mode on` resumes and resets the breaker.
|
|
174
|
+
- Session state is stored through `ctx.storage` and survives plugin reloads.
|
|
175
|
+
|
|
176
|
+
## Guardrails and Code Mode
|
|
177
|
+
|
|
178
|
+
Built-in guards are not a policy language and are not user-extensible. They cover
|
|
179
|
+
protected repository/config paths, credential paths, risky shell structure,
|
|
180
|
+
recursive external deletion, history changes, symlink traversal, project scope,
|
|
181
|
+
and ambiguous syntax. Credential/config shell operands are a hard deny; their
|
|
182
|
+
`read`/`edit` targets and symlink traversal have a mandatory ask floor. `.git`
|
|
183
|
+
metadata access, Git global options, and complex/dynamic POSIX shell syntax stay
|
|
184
|
+
HIGH and reach configured decision/LLM stages; static review abstains instead of
|
|
185
|
+
forcing a user prompt. Visible blocked operations still win, and malformed or
|
|
186
|
+
oversized requests remain bounded. Windows shell dialects retain a mandatory
|
|
187
|
+
ask floor. Loading a `skill` is LOW because it only loads instructions; subsequent
|
|
188
|
+
tool calls are checked independently. `glob` and `grep` scope is not verified,
|
|
189
|
+
so static review abstains. `webfetch`, `websearch`,
|
|
190
|
+
`execute`, and unknown tools remain HIGH.
|
|
191
|
+
|
|
192
|
+
Code Mode is bridged in `execute.before` through `ctx.permission.assert`.
|
|
193
|
+
Permission resources and save entries use
|
|
194
|
+
`execute:sha256:<UTF-8-source-hash>`, the SHA-256 of the exact source. Metadata
|
|
195
|
+
only validates that hash identity, and mismatches are rejected. After validation,
|
|
196
|
+
the reviewer receives the source as a resource; the source is evidence, not
|
|
197
|
+
authorization to execute it. Code Mode stays HIGH because JavaScript can hide
|
|
198
|
+
egress behind aliases, templates, and computed URLs. Old raw-code approvals
|
|
199
|
+
generally do not match the hash identity; broad native `execute` rules still do.
|
|
200
|
+
|
|
201
|
+
Coverage is limited to the permission `evaluate` hook and the Code Mode
|
|
202
|
+
`execute.before` bridge. The `execute.after` hook only adds a UX warning; it makes
|
|
203
|
+
no permission decision. If the plugin is absent, invalid, or disabled, native
|
|
204
|
+
behavior applies. Native path grants are not an OS sandbox. Other project plugins
|
|
205
|
+
can affect residual effects; only run trusted code.
|
|
206
|
+
|
|
207
|
+
## Native Auto/YOLO
|
|
208
|
+
|
|
209
|
+
OpenCode's client-side Auto/YOLO setting can auto-approve asks without a dialog,
|
|
210
|
+
including plugin asks. When it is enabled:
|
|
211
|
+
|
|
212
|
+
- plugin checks still run for requests the host exposes;
|
|
213
|
+
- plugin denies still block;
|
|
214
|
+
- plugin asks are not an interactive gate;
|
|
215
|
+
- the plugin cannot detect, mirror, or change the client switch.
|
|
216
|
+
|
|
217
|
+
Tell the user to disable client Auto/YOLO if they need prompts. Invalid plugin
|
|
218
|
+
configuration also leaves native behavior, including client auto-approval, in
|
|
219
|
+
control.
|
|
220
|
+
|
|
221
|
+
## Audit and replay
|
|
222
|
+
|
|
223
|
+
Audit is the only plugin-generated telemetry. It is **off by default**, local, and
|
|
224
|
+
unsafe. There is no OTLP, metrics, or remote export. Enable it only for temporary
|
|
225
|
+
debugging:
|
|
226
|
+
|
|
227
|
+
```jsonc
|
|
228
|
+
"audit": { "enabled": true, "maxBytes": 10485760 }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- Fixed path: `$XDG_STATE_HOME/opencode-auto-mode/decisions.jsonl` when
|
|
232
|
+
`XDG_STATE_HOME` is absolute; otherwise
|
|
233
|
+
`~/.local/state/opencode-auto-mode/decisions.jsonl`.
|
|
234
|
+
- `audit` is an object only. Booleans, strings, and custom `path` fields are
|
|
235
|
+
invalid.
|
|
236
|
+
- Records are unredacted and may contain secrets, credentials, PII, file
|
|
237
|
+
contents, source, user context, full model prompts, and model outputs.
|
|
238
|
+
- File mode `0600` and directory mode `0700` are access control, not
|
|
239
|
+
sanitization.
|
|
240
|
+
- When a record would exceed `maxBytes`, the active file rotates to a sibling
|
|
241
|
+
archive. Archives are retained; the plugin does not prune or delete them.
|
|
242
|
+
`maxBytes: 0` disables rotation.
|
|
243
|
+
- Host-event capture covers only SDK-visible events. The same JSONL also receives
|
|
244
|
+
plugin lifecycle and full model request/response records. Row counts are not
|
|
245
|
+
decision counts.
|
|
246
|
+
- `hostEffect` is the effect at the hook; `decision` is the applied plugin
|
|
247
|
+
effect. Neither is necessarily the final execution outcome.
|
|
248
|
+
- Never commit, upload, or share audit files without review and redaction. Tests
|
|
249
|
+
must use scratch XDG roots, never personal logs.
|
|
250
|
+
|
|
251
|
+
Offline replay is a repository-checkout tool because `scripts/` is not in the
|
|
252
|
+
published package file list:
|
|
253
|
+
|
|
254
|
+
```sh
|
|
255
|
+
bun scripts/replay.ts --input <audit-jsonl-path> --project <project-root> --json
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Replay does not execute tools or contact models. It reports current static
|
|
259
|
+
routing diagnostics only and cannot reconstruct native policies, saved approvals,
|
|
260
|
+
model decisions, historical settings, VCS state, or invisible events.
|
|
261
|
+
|
|
262
|
+
## Migration from 1.0.x
|
|
263
|
+
|
|
264
|
+
Back up global config first. The plugin does not rewrite or migrate user files.
|
|
265
|
+
A config containing any removed key is invalid and acts disabled.
|
|
266
|
+
|
|
267
|
+
| Removed setting | Required change |
|
|
268
|
+
| --- | --- |
|
|
269
|
+
| `enabled` | Remove it. Mode and the required classifier list control review. |
|
|
270
|
+
| `reviewNativeAllows` | Remove it. Native allows are always reviewed when mode is on. |
|
|
271
|
+
| `shadow` | Remove it. Shadow mode was removed with no replacement. |
|
|
272
|
+
| `auditLog` | Replace with `audit: { enabled, maxBytes }`. The path is fixed. |
|
|
273
|
+
| `otlp` | Remove it. Remote telemetry was removed. |
|
|
274
|
+
| `performanceTelemetry` | Remove it. Metrics export was removed. |
|
|
275
|
+
| `fetchHosts` | Remove it. Code Mode stays HIGH. |
|
|
276
|
+
| `rulesPath` | Remove it. Custom rules were removed; built-in guards remain. Existing rule files are no longer read. |
|
|
277
|
+
|
|
278
|
+
| Removed stage key | Required change |
|
|
279
|
+
| --- | --- |
|
|
280
|
+
| `llm.model` | Now required. There is no implicit session-model fallback. |
|
|
281
|
+
| `policy` | Remove it. Custom policy/rules were removed. |
|
|
282
|
+
| `endpoint` | Remove it. There is no plugin HTTP adapter or endpoint option. |
|
|
283
|
+
| `apiKeyEnv` | Remove it. Credentials belong in provider config. |
|
|
284
|
+
|
|
285
|
+
After migration, ensure `classifiers` is present and non-empty, with 1–64 stages
|
|
286
|
+
and at most one `static`. Preserve saved approvals and native rules; the plugin
|
|
287
|
+
does not delete or migrate them. Audit native permission/policy ordering
|
|
288
|
+
read-only before changing it.
|
|
289
|
+
|
|
290
|
+
## Troubleshooting
|
|
291
|
+
|
|
292
|
+
| Symptom | Check |
|
|
293
|
+
| --- | --- |
|
|
294
|
+
| `Auto mode: invalid` with `Review disabled: <error>`; desktop pill `Invalid config — review disabled` | Exactly one global registration; only the five public option keys; valid classifier shapes and values. Fix the reported error. |
|
|
295
|
+
| `Auto-mode is not registered in user-owned global configuration` | Register the package in global `opencode.json(c)`. A project-only registration does not satisfy the loader. |
|
|
296
|
+
| `Ambiguous auto-mode global registrations` | Remove duplicate entries across `opencode.json` and `opencode.jsonc`, or duplicate objects in one file. A package name, name with version, local path, and `file://` URL resolving to this package can all collide. |
|
|
297
|
+
| `XDG_CONFIG_HOME must be absolute` or a `Policy file`/`Policy path` rejection | Use an absolute `XDG_CONFIG_HOME`; put config outside the repository; remove symlinks; make it user-owned, not group/other-writable, single-hardlinked, and not inside the current project. Non-writable system-owned parents are allowed. |
|
|
298
|
+
| `Unknown auto-mode option '<name>'` | Remove removed/unknown keys. Runtime errors do not suggest replacements. |
|
|
299
|
+
| Review never uses a model | A static-only chain asks when static abstains. Add a valid `decision` or `llm` stage. |
|
|
300
|
+
| Decision stage always abstains | Provider must support native decisions; lower confidence or check provider support. Otherwise remove the stage. |
|
|
301
|
+
| No prompts appear | Check client Auto/YOLO and native permissions. Plugin asks can be auto-approved by the client. |
|
|
302
|
+
| Desktop pill `Review unavailable` or `status unavailable`; command appends a status/storage error without `Review disabled` | This is not necessarily a config error. Retry, then check session storage and the status RPC. A storage failure during review resolves to `ask`. |
|
|
303
|
+
| Review latency too high | Prefer a `static` stage; tune `reviewTimeoutMs` and per-stage timeouts; remove unused model stages. |
|
|
304
|
+
|
|
305
|
+
## Completion checklist
|
|
306
|
+
|
|
307
|
+
- [ ] Exactly one global registration exists; no project/`cli.json` duplicate.
|
|
308
|
+
- [ ] `classifiers` is valid, ordered, and has at most one `static`.
|
|
309
|
+
- [ ] Every `decision`/`llm` model reference is configured in the user's provider
|
|
310
|
+
settings; no credentials or endpoints were added to this repository.
|
|
311
|
+
- [ ] `/auto-mode status` reports `Auto mode: on` and no configuration error.
|
|
312
|
+
- [ ] A harmless permission request is reviewed, and mode commands respond.
|
|
313
|
+
- [ ] Audit remains off unless the user explicitly authorized temporary local
|
|
314
|
+
debugging.
|
|
315
|
+
- [ ] The user knows a configured `decision`/`llm` stage sends action and
|
|
316
|
+
resources (including full Code Mode source), agent, project/worktree, git
|
|
317
|
+
state, and up to 12 KB of user messages to that model's provider even with
|
|
318
|
+
audit off.
|
|
319
|
+
- [ ] The user knows that native deny remains final, invalid config is
|
|
320
|
+
passthrough, and client Auto/YOLO can suppress plugin asks.
|