@mortz.prk/opencode-auto-mode 1.0.1 → 1.1.1
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 +314 -0
- package/README.md +175 -166
- package/dist/gui-experimental.cjs +1 -0
- package/package.json +27 -16
- package/src/audit.ts +136 -38
- package/src/classifier.ts +45 -37
- package/src/code-mode.ts +55 -0
- package/src/config.ts +104 -98
- package/src/debug-events.ts +223 -0
- package/src/decision.ts +72 -0
- package/src/errors.ts +39 -0
- package/src/gates.ts +16 -0
- package/src/global-options.ts +85 -0
- package/src/gui-state.ts +269 -0
- package/src/gui.tsx +78 -0
- package/src/index.ts +510 -131
- package/src/mode.ts +237 -0
- package/src/reviewer.ts +71 -85
- package/src/rpc.ts +127 -0
- package/src/score/files.ts +49 -0
- package/src/score/history.ts +141 -0
- package/src/score/index.ts +37 -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 +54 -0
- package/src/score/shell.ts +30 -0
- package/src/score/types.ts +14 -0
- package/src/score.ts +9 -0
- package/src/shell-policy.ts +1979 -0
- package/src/shell.ts +508 -225
- package/src/status.ts +85 -0
- package/src/tiers.ts +286 -40
- package/src/timeout.ts +30 -0
- package/src/tui.tsx +44 -34
- package/src/user-files.ts +75 -0
- package/src/worktree.ts +30 -0
- package/src/decisions.ts +0 -213
- package/src/logger.ts +0 -26
- package/src/otlp.ts +0 -74
- package/src/policy.ts +0 -36
- package/src/state.ts +0 -135
- package/src/storage.ts +0 -26
- package/src/traces.ts +0 -84
- package/src/transcript.ts +0 -208
- package/src/tui-state.ts +0 -7
package/README.md
CHANGED
|
@@ -1,206 +1,215 @@
|
|
|
1
1
|
# OpenCode Auto Mode
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Automatic review of OpenCode permission requests. While a session's auto mode is
|
|
4
|
+
on, the plugin reviews native **allow** and **ask** effects, including saved
|
|
5
|
+
**Always** approvals. A native **deny** is always final.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
Auto Mode reduces prompts. It is not a security sandbox, and it cannot make an
|
|
8
|
+
invalid configuration fail closed.
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
action and reviews those requests using built-in checks. No model or API key is
|
|
10
|
-
needed:
|
|
10
|
+
## Requirements
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"package": "@mortz.prk/opencode-auto-mode",
|
|
21
|
-
"options": {
|
|
22
|
-
"classifiers": [
|
|
23
|
-
{ "type": "static" }
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
}
|
|
29
|
-
```
|
|
12
|
+
- OpenCode V2. The checked-in plugin SDK and schema targets OpenCode `2.2.0`.
|
|
13
|
+
- Model references only if you use `decision` or `llm` classifier stages. Models
|
|
14
|
+
and provider credentials belong in your personal OpenCode provider config.
|
|
15
|
+
This repository supplies no model, endpoint, credential, or allowlist.
|
|
16
|
+
- **Data egress:** a configured `decision` or `llm` stage sends the action and
|
|
17
|
+
resources (including full Code Mode source), agent, project/worktree, git
|
|
18
|
+
state, and up to 12 KB of user messages to that model's provider, even with
|
|
19
|
+
audit off. Choose a provider you trust.
|
|
30
20
|
|
|
31
|
-
|
|
21
|
+
## Install
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
opencode service restart
|
|
35
|
-
```
|
|
23
|
+
Register the plugin **once** in your user-owned global config:
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
The snippets below change parts of that configuration; keep your `classifiers`
|
|
44
|
-
list when adding other options.
|
|
45
|
-
|
|
46
|
-
## Add a typed decision model
|
|
25
|
+
`~/.config/opencode/opencode.jsonc` (or
|
|
26
|
+
`$XDG_CONFIG_HOME/opencode/opencode.jsonc`). The global config must be outside
|
|
27
|
+
the repository, resolve with no symlink in its path, be user-owned and not
|
|
28
|
+
group/other-writable, be a single hardlink, and use an absolute
|
|
29
|
+
`XDG_CONFIG_HOME` when set.
|
|
47
30
|
|
|
48
31
|
```jsonc
|
|
49
32
|
{
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
33
|
+
"$schema": "https://opencode.ai/config.json",
|
|
34
|
+
"plugins": [{
|
|
35
|
+
"package": "@mortz.prk/opencode-auto-mode",
|
|
36
|
+
"options": {
|
|
37
|
+
"classifiers": [
|
|
38
|
+
{ "type": "static" },
|
|
39
|
+
{ "type": "llm", "model": "<provider>/<reviewer-model>" }
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}]
|
|
56
43
|
}
|
|
57
44
|
```
|
|
58
45
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
| `openai` | Set `OPENAI_API_KEY`. |
|
|
63
|
-
| `laya` | Run a loopback `/v1/systemone` service. `LAYA_API_KEY` is optional. |
|
|
46
|
+
The `static` stage handles low-risk reads and eligible in-project edits without a
|
|
47
|
+
model. Remove the `llm` stage if you want a static-only setup; anything static
|
|
48
|
+
cannot resolve then asks you.
|
|
64
49
|
|
|
65
|
-
|
|
66
|
-
`apiKeyEnv`. `laya` also accepts `endpoint`.
|
|
67
|
-
|
|
68
|
-
## Add an LLM
|
|
69
|
-
|
|
70
|
-
Add an `llm` entry after the built-in checks:
|
|
71
|
-
|
|
72
|
-
```jsonc
|
|
73
|
-
{
|
|
74
|
-
"options": {
|
|
75
|
-
"classifiers": [
|
|
76
|
-
{ "type": "static" },
|
|
77
|
-
{ "type": "llm" }
|
|
78
|
-
]
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
When an `llm` entry has no `model`, the **session model** is used. To choose a
|
|
84
|
-
different model:
|
|
50
|
+
For native typed decisions, add a `decision` stage between `static` and `llm`:
|
|
85
51
|
|
|
86
52
|
```jsonc
|
|
87
|
-
{ "type": "
|
|
53
|
+
{ "type": "decision", "model": "<provider>/<decision-model>", "minConfidence": 0.9 }
|
|
88
54
|
```
|
|
89
55
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Entries run in order. Add as many as you need:
|
|
56
|
+
Only add this stage if your OpenCode provider supports native decisions. The
|
|
57
|
+
plugin has no HTTP decision adapter or custom endpoint option.
|
|
93
58
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
{ "type": "llm" }
|
|
101
|
-
]
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
```
|
|
59
|
+
Keep the plugin registration in global config only. Do not add the full plugin to
|
|
60
|
+
project config, `cli.json`, or `.opencode/plugins/` as well: the plugin reads its
|
|
61
|
+
safety options from exactly one global registration. The server registration
|
|
62
|
+
loads the TUI part automatically. Desktop loads the experimental GUI part from
|
|
63
|
+
global config and needs the built `dist/gui-experimental.cjs` bundle; in a local
|
|
64
|
+
checkout, run `bun run build:gui` first.
|
|
105
65
|
|
|
106
|
-
|
|
107
|
-
fails, the next entry runs. If nothing decides, the action remains `ask`.
|
|
66
|
+
## Verify
|
|
108
67
|
|
|
109
|
-
|
|
68
|
+
Restart nothing for a config-only change. OpenCode watches config files and
|
|
69
|
+
reloads plugins after a save; `/reload` forces a full reload if needed.
|
|
110
70
|
|
|
111
|
-
|
|
112
|
-
permissions and leave global permissions unchanged:
|
|
71
|
+
Run:
|
|
113
72
|
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
"agents": {
|
|
117
|
-
"build": {
|
|
118
|
-
"permissions": [
|
|
119
|
-
{ "action": "*", "resource": "*", "effect": "ask" }
|
|
120
|
-
]
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
73
|
+
```text
|
|
74
|
+
/auto-mode status
|
|
124
75
|
```
|
|
125
76
|
|
|
126
|
-
|
|
77
|
+
A working install reports `Auto mode: on`, today's allow/ask/deny counts, active
|
|
78
|
+
reviews, and breaker state. With invalid configuration, the command reports
|
|
79
|
+
`Auto mode: invalid` and `Review disabled: <error>`; the desktop pill reads
|
|
80
|
+
`Invalid config — review disabled`. A storage or status failure is not a config
|
|
81
|
+
error: the command appends its error and the pill reads `Review unavailable` or
|
|
82
|
+
`status unavailable`. Fix the strict option shapes below rather than expecting a
|
|
83
|
+
migration hint.
|
|
127
84
|
|
|
128
|
-
|
|
129
|
-
It is enabled by default. Set `tui: false` in plugin options to hide it.
|
|
130
|
-
Invalid options show `auto-mode: invalid config` in the same place and an error
|
|
131
|
-
toast at startup. OpenCode's own permission dialog handles manual approvals.
|
|
85
|
+
## Use
|
|
132
86
|
|
|
133
|
-
|
|
87
|
+
- `/auto-mode` toggles the current session.
|
|
88
|
+
- `/auto-mode on` enables review and resets the breaker.
|
|
89
|
+
- `/auto-mode off` restores the native host effect.
|
|
90
|
+
- `/auto-mode status` reports mode, counts, active reviews, and breaker state.
|
|
134
91
|
|
|
135
|
-
|
|
92
|
+
Configure native permissions for ordinary safe work and leave residual risky
|
|
93
|
+
operations as `ask`. Keep hard denials in user-owned global `policies`
|
|
94
|
+
(`tool.use`); the plugin does not install or rewrite native rules.
|
|
136
95
|
|
|
137
|
-
|
|
138
|
-
{
|
|
139
|
-
"options": {
|
|
140
|
-
"classifiers": [
|
|
141
|
-
{
|
|
142
|
-
"type": "llm",
|
|
143
|
-
"policy": {
|
|
144
|
-
"trusted": ["the repository and its CI configuration"],
|
|
145
|
-
"blocks": ["publish packages", "change production infrastructure"],
|
|
146
|
-
"exceptions": ["run the repository's test and lint scripts"]
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
]
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
```
|
|
96
|
+
## Options
|
|
153
97
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
policy; an empty list clears that list. Policies are independent between
|
|
157
|
-
reviewers and do not apply to static checks or replace OpenCode permissions.
|
|
98
|
+
Unknown keys are rejected. `classifiers` is required and must contain 1–64
|
|
99
|
+
ordered stages, with at most one `static` stage.
|
|
158
100
|
|
|
159
|
-
|
|
101
|
+
| Option | Default | Contract |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| `classifiers` | required | Ordered `static`, `decision`, and `llm` stages. No default list and no unconfigured mode. |
|
|
104
|
+
| `reviewTimeoutMs` | `15000` | Whole-review deadline; integer 1–120000. |
|
|
105
|
+
| `breaker` | `3 / 10 / 50` | `{consecutiveDenies, windowDenies, windowSize}`. All three keys are required when supplied. |
|
|
106
|
+
| `tui` | `true` | Show or hide the TUI indicator. UI-only; review still runs server-side. |
|
|
107
|
+
| `audit` | `{enabled:false,maxBytes:10485760}` | Object only. Local audit is off by default. |
|
|
160
108
|
|
|
161
|
-
|
|
109
|
+
Stage shapes:
|
|
162
110
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
111
|
+
| Stage | Shape |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| `static` | `{ "type": "static", "allowInProjectEdits"?: boolean }` |
|
|
114
|
+
| `decision` | `{ "type": "decision", "model": "<provider>/<model>", "timeoutMs"?: number, "minConfidence"?: number }` |
|
|
115
|
+
| `llm` | `{ "type": "llm", "model": "<provider>/<model>", "timeoutMs"?: number }` |
|
|
116
|
+
|
|
117
|
+
## How review works
|
|
118
|
+
|
|
119
|
+
1. Native permissions, policies, and saved approvals produce `allow`, `ask`, or
|
|
120
|
+
`deny`. A native **deny is final** and is never reviewed or weakened.
|
|
121
|
+
2. With mode on, native allows and asks are reviewed. Saved **Always** entries
|
|
122
|
+
are re-reviewed but never deleted or rewritten.
|
|
123
|
+
3. Built-in guards run first: protected paths and credentials, risky shell
|
|
124
|
+
structure, recursive external deletion, history changes, and ambiguous syntax.
|
|
125
|
+
These guards are not user-extensible.
|
|
126
|
+
4. Configured classifiers run in array order. A static stage may resolve low-risk
|
|
127
|
+
work; a decision stage uses OpenCode's native decision API; an LLM stage uses
|
|
128
|
+
its explicit reviewer. The first decisive allow or deny stops the chain.
|
|
129
|
+
5. Exhaustion, unusable context, interruption, failure, or timeout resolves to
|
|
130
|
+
**ask**. A deny ends with `Continue with a safer alternative.`
|
|
131
|
+
|
|
132
|
+
Mode off restores the original host effect. A paused breaker turns a reviewed
|
|
133
|
+
native allow into `ask`; an existing native ask keeps its prompt.
|
|
134
|
+
|
|
135
|
+
Assuming classifiers: [static, decision, llm] (array order is authoritative).
|
|
136
|
+
|
|
137
|
+
```mermaid
|
|
138
|
+
flowchart TD
|
|
139
|
+
A[permission evaluate<br/>allow / ask / deny] --> B{native deny?}
|
|
140
|
+
B -- yes --> B1[final, untouched<br/>audit only]
|
|
141
|
+
B -- no --> C{config valid &<br/>session on?}
|
|
142
|
+
C -- invalid --> C1[passthrough<br/>review disabled]
|
|
143
|
+
C -- off --> C2[restore native<br/>effect + message]
|
|
144
|
+
C -- paused --> C3[allow → ask<br/>ask → passthrough]
|
|
145
|
+
|
|
146
|
+
C -- on --> D[context fetch<br/>size ≤ 64 KiB]
|
|
147
|
+
D --> E[Code Mode hash identity<br/>execute:sha256]
|
|
148
|
+
E --> S
|
|
149
|
+
|
|
150
|
+
subgraph S [scoreRequest — every hand-written safety check lives here]
|
|
151
|
+
S1[shell: guardShell + visibleShellDeny<br/>parse / operand roles / recursive delete]
|
|
152
|
+
S2[tiers: protected paths, credential names,<br/>symlink traversal, project scope]
|
|
153
|
+
S3[read/edit/write: URL, scope, safeTarget]
|
|
154
|
+
S4[webfetch/websearch/execute/unknown → HIGH]
|
|
155
|
+
S5[git history → evaluateVcsGate]
|
|
156
|
+
S1 --> S2 --> S3 --> S4 --> S5
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
S -- deny --> X1[DENY]
|
|
160
|
+
S -- ask --> X2[ASK]
|
|
161
|
+
S -- low / medium / high --> R
|
|
162
|
+
|
|
163
|
+
subgraph R [runClassifiers]
|
|
164
|
+
R0{score deny/ask?} -- yes --> X1
|
|
165
|
+
R0 -- no --> R1[static: low → ALLOW<br/>else abstain]
|
|
166
|
+
R1 -- abstain --> R2[decision: native generate.decisions<br/>confidence ≥ 0.9]
|
|
167
|
+
R2 -- ask / low confidence --> R3[llm: native generate.text]
|
|
168
|
+
R2 -- allow / deny --> T[terminal]
|
|
169
|
+
R3 -- allow / deny --> T
|
|
170
|
+
R3 -- ask / unresolved --> X2
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
X1 --> F[modes.finish<br/>counts + breaker]
|
|
174
|
+
X2 --> F
|
|
175
|
+
T --> F
|
|
176
|
+
F --> G[apply effect to host]
|
|
189
177
|
```
|
|
190
178
|
|
|
191
|
-
##
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
179
|
+
## Safety limits
|
|
180
|
+
|
|
181
|
+
- **Native Auto/YOLO is client-local.** If enabled, OpenCode may auto-approve an
|
|
182
|
+
`ask` without a dialog, including a plugin ask. Plugin checks still run and
|
|
183
|
+
plugin denies still block, but the plugin cannot read, change, or synchronize
|
|
184
|
+
that switch. Disable client Auto/YOLO if you need prompts.
|
|
185
|
+
- **Invalid configuration acts disabled.** The plugin performs no review and
|
|
186
|
+
native behavior applies. That is passthrough, not fail-closed.
|
|
187
|
+
- Coverage is limited to the permission `evaluate` hook and the Code Mode
|
|
188
|
+
`execute.before` bridge. Native path grants are not an OS sandbox.
|
|
189
|
+
- Code Mode stays HIGH: JavaScript can hide egress behind aliases and computed
|
|
190
|
+
URLs. Its permission identity is the SHA-256 of the exact UTF-8 source. Old
|
|
191
|
+
raw-code approvals generally do not match.
|
|
192
|
+
- Other project plugins can affect residual effects; only run code you trust.
|
|
193
|
+
|
|
194
|
+
## Audit
|
|
195
|
+
|
|
196
|
+
Audit is the only plugin-generated telemetry and is **off by default**. It is
|
|
197
|
+
local and unsafe: raw records are unredacted and can contain secrets, file
|
|
198
|
+
contents, prompts, and model outputs. If enabled, the fixed path is
|
|
199
|
+
`$XDG_STATE_HOME/opencode-auto-mode/decisions.jsonl` (or
|
|
200
|
+
`~/.local/state/opencode-auto-mode/decisions.jsonl` when `XDG_STATE_HOME` is not
|
|
201
|
+
absolute). Rotated archives are retained; delete them manually. See
|
|
202
|
+
[README.llm.md](README.llm.md#audit-and-replay) before enabling it.
|
|
203
|
+
|
|
204
|
+
## More documentation
|
|
205
|
+
|
|
206
|
+
- [README.llm.md](README.llm.md) — installation instructions for an AI agent,
|
|
207
|
+
full options, migration from 1.0.x, audit, and troubleshooting.
|
|
208
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) — repository map, local development,
|
|
209
|
+
tests, and release checks. It ships with the package.
|
|
200
210
|
|
|
201
211
|
## Credit
|
|
202
212
|
|
|
203
213
|
Created originally by [Andrew Mellen (mellena1)](https://github.com/mellena1) in
|
|
204
|
-
[mellena1/opencode-auto-mode](https://github.com/mellena1/opencode-auto-mode)
|
|
205
|
-
|
|
206
|
-
project retains the original [MIT license](LICENSE).
|
|
214
|
+
[mellena1/opencode-auto-mode](https://github.com/mellena1/opencode-auto-mode).
|
|
215
|
+
This independent project retains the original [MIT license](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});let e=require("solid-js"),t=require("@opencode/plugin/gui"),n=require("@opencode/plugin/rpc");var r={type:`object`,properties:{enabled:{type:`boolean`},mode:{type:`string`,enum:[`on`,`off`,`paused`,`invalid`]},configError:{type:`string`,maxLength:200},errorKind:{type:`string`,enum:[`config`,`storage`,`status`]},reviewTimeoutMs:{type:`number`},reviewing:{type:`array`,maxItems:256,items:{type:`object`,properties:{sessionID:{type:`string`},reviewID:{type:`string`},startedAt:{type:`number`}},required:[`sessionID`,`reviewID`,`startedAt`],additionalProperties:!1}},counts:{type:`object`,properties:{allow:{type:`integer`,minimum:0},ask:{type:`integer`,minimum:0},deny:{type:`integer`,minimum:0}},required:[`allow`,`ask`,`deny`],additionalProperties:!1},breaker:{type:`boolean`}},required:[`enabled`,`mode`,`reviewTimeoutMs`,`reviewing`,`counts`],additionalProperties:!1},i=n.Rpc.define({id:`opencode-auto-mode`,methods:{status:{input:{type:`object`,properties:{sessionID:{type:`string`,minLength:1,maxLength:256}},additionalProperties:!1},output:r},setMode:{input:{type:`object`,properties:{sessionID:{type:`string`,minLength:1,maxLength:256},mode:{type:`string`,enum:[`on`,`off`,`resume`]}},required:[`sessionID`,`mode`],additionalProperties:!1},output:r}},events:{decision:{schema:{type:`object`,properties:{reviewID:{type:`string`},sessionID:{type:`string`},action:{type:`string`,maxLength:128},decision:{type:`string`,maxLength:64},stage:{type:`string`,maxLength:64},reason:{type:`string`,maxLength:200},durationMs:{type:`number`},at:{type:`number`},errorKind:{type:`string`,maxLength:64}},required:[`reviewID`,`sessionID`,`action`,`decision`,`stage`,`reason`,`durationMs`,`at`],additionalProperties:!1}}}});function a(e,t){return JSON.stringify([e,t.directory,t.workspaceID])}function o(e){if(!(e?.location===void 0||!e.id||e.id.length>256||!e.location.directory))return{key:a(e.key,e.location),sessionID:e.id,ownServer:e.server.builtin,location:{...e.location}}}var s={enabled:!1,mode:`invalid`,configError:`status unavailable`,errorKind:`status`,reviewTimeoutMs:0,reviewing:[],counts:{allow:0,ask:0,deny:0}},c=5e3;function l(e,t,n){let r=new AbortController,i=new Promise((e,n)=>{r.signal.addEventListener(`abort`,()=>n(Error(t)),{once:!0})}),a=setTimeout(()=>r.abort(),e),o=Promise.resolve().then(()=>{if(r.signal.aborted)throw Error(t);return n(r.signal)});return{abort:()=>r.abort(),done:Promise.race([o,i]).finally(()=>clearTimeout(a))}}function u(e,t,n=c){let r,i,a=0,o=!1,u=()=>{i?.request.abort(),i=void 0};return Object.assign(c=>{if(o||(r!==c?.key&&(r=c?.key,a++,u(),t(void 0)),c===void 0||!c.ownServer))return Promise.resolve();if(i!==void 0)return i.done;let d=a,f=l(n,`status request aborted`,t=>e(c,t)),p=f.done.then(e=>{!o&&d===a&&t(e??{...s},c)},()=>{!o&&d===a&&t({...s},c)}).finally(()=>{i?.request===f&&(i=void 0)});return i={request:f,done:p},p},{invalidate(){a++,u()},dispose(){o=!0,a++,u()}})}function d(e,t,n,r=()=>{},i=c){let a=new Map,o=!1,s=Number.isFinite(i)&&i>0?i:c;return{pending:e=>a.has(e.key),run(i,c){if(o||i===void 0||!i.ownServer||c===void 0)return Promise.resolve();let u=a.get(i.key);if(u)return u.done;let d=l(s,`mode change aborted`,t=>e(i,c,t)),f=d.done.then(e=>{!o&&e!==void 0&&t(i,e)},()=>{o||n()}).finally(()=>{a.get(i.key)?.request===d&&a.delete(i.key),o||r()});return a.set(i.key,{request:d,done:f}),r(),f},dispose(){o=!0;for(let e of a.values())e.request.abort();a.clear()}}}function f(e){switch(e?.mode){case`on`:return e.enabled?`off`:`on`;case`off`:return`on`;case`paused`:return`resume`;default:return}}function p(e){if(e!==void 0)switch(e.mode){case`invalid`:return e.errorKind===`status`||e.errorKind===void 0&&e.configError===`status unavailable`?`status unavailable`:e.errorKind===`config`?`Invalid config — review disabled`:`Review unavailable`;case`paused`:return`paused (breaker)`;case`off`:return`off`;case`on`:return e.enabled?`auto`:`off`}}function m(e){if(e?.mode===`invalid`&&e.configError!==void 0&&e.configError!==`status unavailable`)return`${p(e)}: ${e.configError}`}function h(e){if(e!==void 0)return e.mode===`invalid`?`danger`:e.mode===`paused`?`warning`:e.mode===`on`&&e.enabled?`success`:void 0}function g(e,t,n=Date.now){let r=new Set,i=0,a=0;return o=>{let s=e();if(s===void 0||!s.ownServer||o.location.directory!==s.location.directory||o.location.workspaceID!==s.location.workspaceID||o.data===null||typeof o.data!=`object`)return;let c=o.data;if(c.sessionID!==s.sessionID||typeof c.reviewID!=`string`||c.reviewID.length>256)return;let l=c.stage===`breaker`?`breaker`:c.decision===`deny`?`deny`:void 0;if(l===void 0)return;let u=JSON.stringify([s.key,c.reviewID,l]);if(r.has(u))return;r.add(u),r.size>128&&r.delete(r.values().next().value);let d=n();d-i>=5e3&&(i=d,a=0),!(l===`deny`&&a>=4)&&(a++,t({kind:l,message:l===`breaker`?`Auto mode paused (breaker)`:`Auto mode denied`}))}}function _(e){let t,n,r,i=!1,a=()=>{n?.abort(),r?.(),n=void 0,r=void 0};return{update(o,s){if(i||o===t)return;a(),t=o;let c=new AbortController;n=c;try{r=s(t=>{c.signal.aborted||e(t)},c.signal)}catch{a(),t=void 0}},dispose(){i=!0,a(),t=void 0}}}var v=1e3,y=t.Plugin.define({setup(n){let[r,a]=(0,e.createSignal)(void 0),[s,c]=(0,e.createSignal)(0),l=()=>o(n.sessions.current()),y=u((e,t)=>n.client.rpc(i).status({sessionID:e.sessionID},{location:e.location,signal:t}),(e,t)=>{e===void 0?a(void 0):t!==void 0&&l()?.key===t.key&&a({key:t.key,value:e})}),b=d((e,t,r)=>(y.invalidate(),n.client.rpc(i).setMode({sessionID:e.sessionID,mode:t},{location:e.location,signal:r})),(e,t)=>{l()?.key===e.key&&a({key:e.key,value:t})},()=>n.toast.show({title:`Auto-mode`,description:`Could not change auto mode. Try again.`,variant:`error`,duration:5e3}),()=>c(e=>e+1)),x=_(g(l,e=>{n.toast.show({title:`Auto-mode`,description:e.message,variant:e.kind===`deny`?`error`:`default`,duration:5e3})})),S=()=>{let e=n.client;x.update(e,(t,n)=>e.rpc(i).events.on(`decision`,t,{signal:n}));let t=l();t!==void 0&&b.pending(t)||y(t)};(0,t.createKeyed)(()=>l()?.key,S,{otherwise:S});let C=setInterval(S,v);(0,e.onCleanup)(()=>{clearInterval(C),b.dispose(),y.dispose(),x.dispose()}),n.add(t.ComposerPill,{id:`status`,order:20,show:e=>{s();let t=o(e.session),n=r(),i=t?.ownServer&&n?.key===t.key?n.value:void 0,a=f(i),c=t!==void 0&&b.pending(t),l=c?`changing auto mode…`:p(i)??`status unavailable`,u=m(i);return{label:l,tone:h(i),title:c?`Auto-mode: change pending`:u===void 0?a===void 0?`Auto-mode unavailable`:`Auto-mode: ${a}`:`Auto-mode: ${u}`,run:!c&&a!==void 0&&t?.ownServer?()=>{b.run(t,a)}:void 0}}})}});exports.default=y;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mortz.prk/opencode-auto-mode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Configurable automatic permission review for OpenCode v2.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Andrew Mellen",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"type": "module",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"files": [
|
|
20
|
-
"src"
|
|
20
|
+
"src",
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"README.llm.md",
|
|
24
|
+
"CONTRIBUTING.md"
|
|
21
25
|
],
|
|
22
26
|
"exports": {
|
|
23
27
|
".": {
|
|
@@ -27,38 +31,45 @@
|
|
|
27
31
|
"./tui": {
|
|
28
32
|
"import": "./src/tui.tsx",
|
|
29
33
|
"types": "./src/tui.tsx"
|
|
30
|
-
}
|
|
34
|
+
},
|
|
35
|
+
"./rpc": {
|
|
36
|
+
"import": "./src/rpc.ts",
|
|
37
|
+
"types": "./src/rpc.ts"
|
|
38
|
+
},
|
|
39
|
+
"./gui-experimental": "./dist/gui-experimental.cjs"
|
|
31
40
|
},
|
|
32
41
|
"scripts": {
|
|
33
|
-
"lint": "eslint . --max-warnings=0",
|
|
42
|
+
"lint": "eslint . --max-warnings=0 && node scripts/complexity-lint.js",
|
|
43
|
+
"lint:complexity": "node scripts/complexity-lint.js",
|
|
34
44
|
"typecheck": "tsc --noEmit",
|
|
35
45
|
"test": "bun test",
|
|
36
|
-
"test:integration": "bun run integration/opencode.ts"
|
|
46
|
+
"test:integration": "bun run integration/opencode.ts",
|
|
47
|
+
"build:gui": "vite build",
|
|
48
|
+
"prepublishOnly": "bun run build:gui"
|
|
37
49
|
},
|
|
38
50
|
"dependencies": {
|
|
39
|
-
"@opencode/
|
|
40
|
-
"@opencode/
|
|
41
|
-
"@opencode/schema": "^2.0.30",
|
|
42
|
-
"effect": "4.0.1",
|
|
51
|
+
"@opencode/plugin": "2.2.0",
|
|
52
|
+
"@opencode/schema": "2.2.0",
|
|
43
53
|
"shell-quote": "1.12.0"
|
|
44
54
|
},
|
|
45
55
|
"peerDependencies": {
|
|
46
|
-
"@opentui/core": ">=0.
|
|
47
|
-
"@opentui/solid": ">=0.
|
|
56
|
+
"@opentui/core": ">=0.6.6",
|
|
57
|
+
"@opentui/solid": ">=0.6.6",
|
|
48
58
|
"solid-js": ">=1.9.0"
|
|
49
59
|
},
|
|
50
60
|
"devDependencies": {
|
|
51
61
|
"@eslint/js": "^10.0.1",
|
|
52
|
-
"@opencode/theme": "
|
|
53
|
-
"@opentui/core": "0.
|
|
54
|
-
"@opentui/
|
|
55
|
-
"@opentui/solid": "0.6.6",
|
|
62
|
+
"@opencode/theme": "2.2.0",
|
|
63
|
+
"@opentui/core": "0.7.0",
|
|
64
|
+
"@opentui/solid": "0.7.0",
|
|
56
65
|
"@types/node": "^26.6.4",
|
|
57
66
|
"@types/shell-quote": "1.7.5",
|
|
58
67
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
59
68
|
"eslint": "^10.12.0",
|
|
60
69
|
"solid-js": "1.9.17",
|
|
61
70
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
62
|
-
"typescript-eslint": "^8.71.1"
|
|
71
|
+
"typescript-eslint": "^8.71.1",
|
|
72
|
+
"vite": "^8.3.5",
|
|
73
|
+
"vite-plugin-solid": "^2.11.0"
|
|
63
74
|
}
|
|
64
75
|
}
|