@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.
Files changed (47) hide show
  1. package/CONTRIBUTING.md +234 -0
  2. package/README.llm.md +314 -0
  3. package/README.md +175 -166
  4. package/dist/gui-experimental.cjs +1 -0
  5. package/package.json +27 -16
  6. package/src/audit.ts +136 -38
  7. package/src/classifier.ts +45 -37
  8. package/src/code-mode.ts +55 -0
  9. package/src/config.ts +104 -98
  10. package/src/debug-events.ts +223 -0
  11. package/src/decision.ts +72 -0
  12. package/src/errors.ts +39 -0
  13. package/src/gates.ts +16 -0
  14. package/src/global-options.ts +85 -0
  15. package/src/gui-state.ts +269 -0
  16. package/src/gui.tsx +78 -0
  17. package/src/index.ts +510 -131
  18. package/src/mode.ts +237 -0
  19. package/src/reviewer.ts +71 -85
  20. package/src/rpc.ts +127 -0
  21. package/src/score/files.ts +49 -0
  22. package/src/score/history.ts +141 -0
  23. package/src/score/index.ts +37 -0
  24. package/src/score/limits.ts +24 -0
  25. package/src/score/shell-argv.ts +74 -0
  26. package/src/score/shell-baseline.ts +113 -0
  27. package/src/score/shell-safety.ts +54 -0
  28. package/src/score/shell.ts +30 -0
  29. package/src/score/types.ts +14 -0
  30. package/src/score.ts +9 -0
  31. package/src/shell-policy.ts +1979 -0
  32. package/src/shell.ts +508 -225
  33. package/src/status.ts +85 -0
  34. package/src/tiers.ts +286 -40
  35. package/src/timeout.ts +30 -0
  36. package/src/tui.tsx +44 -34
  37. package/src/user-files.ts +75 -0
  38. package/src/worktree.ts +30 -0
  39. package/src/decisions.ts +0 -213
  40. package/src/logger.ts +0 -26
  41. package/src/otlp.ts +0 -74
  42. package/src/policy.ts +0 -36
  43. package/src/state.ts +0 -135
  44. package/src/storage.ts +0 -26
  45. package/src/traces.ts +0 -84
  46. package/src/transcript.ts +0 -208
  47. package/src/tui-state.ts +0 -7
package/README.md CHANGED
@@ -1,206 +1,215 @@
1
1
  # OpenCode Auto Mode
2
2
 
3
- Reviews OpenCode permission prompts automatically. OpenCode decides what needs
4
- approval; the plugin reviews only `ask` actions.
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
- ## Install and configure
7
+ Auto Mode reduces prompts. It is not a security sandbox, and it cannot make an
8
+ invalid configuration fail closed.
7
9
 
8
- Add this to `~/.config/opencode/opencode.jsonc`. It asks for permission on every
9
- action and reviews those requests using built-in checks. No model or API key is
10
- needed:
10
+ ## Requirements
11
11
 
12
- ```jsonc
13
- {
14
- "$schema": "https://opencode.ai/config.json",
15
- "permissions": [
16
- { "action": "*", "resource": "*", "effect": "ask" }
17
- ],
18
- "plugins": [
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
- Restart OpenCode after saving:
21
+ ## Install
32
22
 
33
- ```sh
34
- opencode service restart
35
- ```
23
+ Register the plugin **once** in your user-owned global config:
36
24
 
37
- There is no default review pipeline, and empty options are invalid. Add at least
38
- one review entry. Built-in checks deny dangerous shell commands, allow common
39
- read-only commands, and send other parsed commands to the configured reviewers.
40
- Dynamic syntax such as command substitution, nested shells, and stateful builtins
41
- still requires manual approval.
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
- "options": {
51
- "classifiers": [
52
- { "type": "static" },
53
- { "type": "decision", "model": "typesafe-ai/jev-latest" }
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
- | Provider | Setup |
60
- | --- | --- |
61
- | `typesafe-ai` | Set `TYPESAFE_API_KEY`. |
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
- Decision entries accept `minConfidence` (default `0.9`), `timeoutMs`, and
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": "llm", "model": "anthropic/claude-sonnet-4-5" }
53
+ { "type": "decision", "model": "<provider>/<decision-model>", "minConfidence": 0.9 }
88
54
  ```
89
55
 
90
- ## Combine reviewers
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
- ```jsonc
95
- {
96
- "options": {
97
- "classifiers": [
98
- { "type": "static" },
99
- { "type": "decision", "model": "typesafe-ai/jev-latest" },
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
- An entry can allow, deny, or ask, which stops the pipeline. If it abstains or
107
- fails, the next entry runs. If nothing decides, the action remains `ask`.
66
+ ## Verify
108
67
 
109
- ## Use it for specific agents
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
- OpenCode allows everything by default. To review only the `build` agent, set its
112
- permissions and leave global permissions unchanged:
71
+ Run:
113
72
 
114
- ```jsonc
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
- ## TUI indicator
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
- A passive indicator shows when the plugin is reviewing the current session.
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
- ## Review options
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
- Each LLM or typed decision reviewer can have its own policy:
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
- ```jsonc
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
- `trusted` describes the trusted boundary, `blocks` describes actions to deny, and
155
- `exceptions` describes narrow acceptable cases. Omitted lists use the built-in
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
- ## Auditing
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
- Enable the audit log in plugin options:
109
+ Stage shapes:
162
110
 
163
- ```jsonc
164
- {
165
- "options": {
166
- "auditLog": true
167
- }
168
- }
169
- ```
170
-
171
- Decisions are appended as JSON lines to
172
- `$XDG_STATE_HOME/opencode-auto-mode/decisions.jsonl`. Replay recorded tool calls
173
- without executing anything:
174
-
175
- ```sh
176
- bun scripts/replay.ts [--days 14] [--session ID] [--agent build|any] [--json]
177
- ```
178
-
179
- To send decisions through OpenCode's OTLP telemetry, configure OTLP for OpenCode
180
- with its standard environment variables, then enable the plugin flag. This sends
181
- correlated decision logs and review traces, including model reviewer attempts:
182
-
183
- ```jsonc
184
- {
185
- "options": {
186
- "otlp": true
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
- ## Development
192
-
193
- ```sh
194
- bun install
195
- bun test
196
- bun run lint
197
- bun run typecheck
198
- bun run test:integration
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
- also credited as the author in [package.json](package.json). This independent
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.0.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/ai": "2.0.30",
40
- "@opencode/plugin": "^2.0.14",
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.5.10",
47
- "@opentui/solid": ">=0.5.10",
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": "^2.0.14",
53
- "@opentui/core": "0.6.6",
54
- "@opentui/keymap": "^0.6.6",
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
  }