opencode-resolve 0.1.0 → 0.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/README.md +374 -111
- package/dist/index.js +102 -24
- package/opencode-resolve.example.json +13 -11
- package/opencode-resolve.reference.jsonc +164 -0
- package/package.json +3 -1
- package/scripts/postinstall.mjs +158 -0
package/README.md
CHANGED
|
@@ -3,28 +3,67 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/opencode-resolve)
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Small OpenCode plugin that turns a single instruction into a finished, verified change.
|
|
7
7
|
|
|
8
|
-
`opencode-resolve`
|
|
8
|
+
`opencode-resolve` ships three roles by default — **resolver** (orchestrator), **coder** (implementer), and **reviewer** (read-only auditor) — and runs them with auto-approved permissions so a task drives to completion without prompting at every step. It defines roles, not model providers: agents inherit your OpenCode default model unless you pin them.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
---
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- Injects only `coder` and `reviewer` by default.
|
|
14
|
-
- Uses GLM-style defaults for coding and GPT-style defaults for review.
|
|
15
|
-
- Adds a Context7 MCP preset only when one is not already configured.
|
|
16
|
-
- Validates config strictly so typos fail fast.
|
|
17
|
-
- Keeps Telegram notifications and remote control as companion-plugin concerns.
|
|
12
|
+
## Table of Contents
|
|
18
13
|
|
|
19
|
-
|
|
14
|
+
- [Roles at a glance](#roles-at-a-glance)
|
|
15
|
+
- [Quick Start](#quick-start)
|
|
16
|
+
- [Drop-in setup (give to an LLM)](#drop-in-setup-give-to-an-llm)
|
|
17
|
+
- [Default Behavior](#default-behavior)
|
|
18
|
+
- [Configuration](#configuration)
|
|
19
|
+
- [Configuration Reference](#configuration-reference)
|
|
20
|
+
- [Auto Approval](#auto-approval)
|
|
21
|
+
- [Parallel Subagent Limit](#parallel-subagent-limit)
|
|
22
|
+
- [Upgrade & Migration](#upgrade--migration)
|
|
23
|
+
- [Model Setup](#model-setup)
|
|
24
|
+
- [Agent Reference](#agent-reference)
|
|
25
|
+
- [Optional Commands](#optional-commands)
|
|
26
|
+
- [Local Development](#local-development)
|
|
27
|
+
- [Verification](#verification)
|
|
28
|
+
- [Release](#release)
|
|
29
|
+
- [Design Rules](#design-rules)
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Roles at a glance
|
|
34
|
+
|
|
35
|
+
| Role | Default | Can edit? | Can run shell? | Purpose |
|
|
36
|
+
|---|:---:|:---:|:---:|---|
|
|
37
|
+
| `resolver` | Yes | Yes (auto) | Yes (auto) | Primary orchestrator. Plans, dispatches `coder` (one at a time), verifies, iterates. |
|
|
38
|
+
| `coder` | Yes | Yes (auto) | Yes (auto) | Focused implementer. Makes the smallest correct change and verifies. |
|
|
39
|
+
| `reviewer` | Yes | **Never** | **Never** | Read-only auditor. Inspects only — recommends fixes for `coder` or `resolver` to apply. |
|
|
40
|
+
|
|
41
|
+
Hard rule: **the reviewer cannot modify the project by any means.** Both `edit` and `bash` are denied for the reviewer regardless of `autoApprove`. Any required fix is routed back through `coder` or `resolver`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
Install from npm:
|
|
22
48
|
|
|
23
49
|
```sh
|
|
24
50
|
npm install -g opencode-resolve
|
|
25
51
|
```
|
|
26
52
|
|
|
27
|
-
|
|
53
|
+
The package `postinstall` step:
|
|
54
|
+
|
|
55
|
+
1. Adds `opencode-resolve` to `~/.config/opencode/opencode.json` `plugin` array.
|
|
56
|
+
2. Creates `~/.config/opencode/resolve.json` from [`opencode-resolve.example.json`](./opencode-resolve.example.json) when missing.
|
|
57
|
+
|
|
58
|
+
Restart OpenCode after installation.
|
|
59
|
+
|
|
60
|
+
To skip automatic registration:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
OPENCODE_RESOLVE_SKIP_POSTINSTALL=1 npm install -g opencode-resolve
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Manual fallback: add the plugin to `~/.config/opencode/opencode.json` yourself:
|
|
28
67
|
|
|
29
68
|
```json
|
|
30
69
|
{
|
|
@@ -32,150 +71,390 @@ Then add it to your OpenCode config:
|
|
|
32
71
|
}
|
|
33
72
|
```
|
|
34
73
|
|
|
35
|
-
|
|
74
|
+
---
|
|
36
75
|
|
|
37
|
-
##
|
|
76
|
+
## Drop-in setup (give to an LLM)
|
|
38
77
|
|
|
39
|
-
|
|
78
|
+
Hand the block below to any reasonably capable LLM along with the instruction _"set up opencode-resolve on this machine"_ and it can finish the install end-to-end. Everything below is the entire required configuration — no other MCP servers, environment variables, or files are needed.
|
|
40
79
|
|
|
41
|
-
|
|
42
|
-
- `reviewer`: GPT-based Oracle-style review agent for requirements fit, correctness, security, tests, and maintainability.
|
|
80
|
+
**Step 1 — install the plugin:**
|
|
43
81
|
|
|
44
|
-
|
|
82
|
+
```sh
|
|
83
|
+
npm install -g opencode-resolve
|
|
84
|
+
```
|
|
45
85
|
|
|
46
|
-
|
|
47
|
-
- `gpt-coder`: GPT-based difficult implementation fallback.
|
|
48
|
-
- `debugger`: failure reproduction and root-cause analysis.
|
|
49
|
-
- `researcher`: codebase and documentation research.
|
|
86
|
+
**Step 2 — `~/.config/opencode/opencode.json`** (the only required keys; merge into an existing file rather than replacing if one exists):
|
|
50
87
|
|
|
51
|
-
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"$schema": "https://opencode.ai/config.json",
|
|
91
|
+
"model": "<your-default-model-id>",
|
|
92
|
+
"plugin": ["opencode-resolve"]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
52
95
|
|
|
53
|
-
|
|
96
|
+
**Step 3 — `~/.config/opencode/resolve.json`** (created automatically by `postinstall`; the file below is the canonical default):
|
|
54
97
|
|
|
55
|
-
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"enabled": ["coder", "reviewer", "resolver"],
|
|
101
|
+
"preserveNative": true,
|
|
102
|
+
"context7": true,
|
|
103
|
+
"commands": false,
|
|
104
|
+
"autoApprove": true,
|
|
105
|
+
"maxParallelSubagents": 1
|
|
106
|
+
}
|
|
107
|
+
```
|
|
56
108
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
109
|
+
**Step 4 — restart OpenCode.**
|
|
110
|
+
|
|
111
|
+
That is the entire setup. `context7` is the only MCP server the plugin needs (auto-registered when the key above is `true`) — no other MCP entries are required for `opencode-resolve` itself. Models are inherited from your top-level OpenCode `model` unless you pin per-role models under `models` (see [Model Setup](#model-setup)).
|
|
112
|
+
|
|
113
|
+
If you want stricter behavior, flip `autoApprove` to `false` (every action prompts for approval) and/or raise `maxParallelSubagents` to let the resolver fan out independent subtasks.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Default Behavior
|
|
118
|
+
|
|
119
|
+
| Item | Default |
|
|
120
|
+
|---|---|
|
|
121
|
+
| Enabled agents | `coder`, `reviewer`, `resolver` |
|
|
122
|
+
| Primary agent for new tasks | `resolver` (`mode: "all"`) |
|
|
123
|
+
| Agent model | Inherits top-level OpenCode `model` |
|
|
124
|
+
| Native `plan` / `build` | Preserved untouched |
|
|
125
|
+
| Context7 MCP preset | Added if absent |
|
|
126
|
+
| Optional commands | Disabled |
|
|
127
|
+
| `autoApprove` | `true` (no per-action prompts on coder/resolver) |
|
|
128
|
+
| Reviewer modification | Denied (cannot be auto-approved) |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Configuration
|
|
133
|
+
|
|
134
|
+
The plugin reads the first config file it finds:
|
|
135
|
+
|
|
136
|
+
| Priority | Path |
|
|
137
|
+
|---:|---|
|
|
138
|
+
| 1 | `.opencode/resolve.json` (project) |
|
|
139
|
+
| 2 | `opencode-resolve.json` (project) |
|
|
140
|
+
| 3 | `~/.config/opencode/resolve.json` |
|
|
141
|
+
| 4 | `~/.config/opencode/opencode-resolve.json` |
|
|
142
|
+
|
|
143
|
+
Inline plugin options in `opencode.json` override file config.
|
|
144
|
+
|
|
145
|
+
Config precedence:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
built-in defaults -> first config file found -> inline plugin options
|
|
61
149
|
```
|
|
62
150
|
|
|
63
|
-
|
|
151
|
+
Minimal config (matches defaults):
|
|
64
152
|
|
|
65
|
-
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"enabled": ["coder", "reviewer", "resolver"],
|
|
156
|
+
"autoApprove": true,
|
|
157
|
+
"context7": true,
|
|
158
|
+
"commands": false
|
|
159
|
+
}
|
|
160
|
+
```
|
|
66
161
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
162
|
+
Inline form inside `opencode.json`:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"plugin": [
|
|
167
|
+
[
|
|
168
|
+
"opencode-resolve",
|
|
169
|
+
{
|
|
170
|
+
"enabled": ["coder", "reviewer", "resolver"],
|
|
171
|
+
"autoApprove": true,
|
|
172
|
+
"context7": true,
|
|
173
|
+
"commands": false
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
]
|
|
177
|
+
}
|
|
71
178
|
```
|
|
72
179
|
|
|
73
|
-
|
|
180
|
+
Strict validation rejects unknown agent names, misspelled keys, invalid modes, invalid permission values, and wrong value types — typos fail fast.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Configuration Reference
|
|
185
|
+
|
|
186
|
+
Every accepted top-level option:
|
|
187
|
+
|
|
188
|
+
| Key | Type | Default | Purpose |
|
|
189
|
+
|---|---|---|---|
|
|
190
|
+
| `enabled` | `string[]` | `["coder", "reviewer", "resolver"]` | Which resolve agents to inject. Per-agent `agents.<name>.enabled` overrides this. |
|
|
191
|
+
| `preserveNative` | `boolean` | `true` | Native `plan`/`build` are always preserved. Accepted for readability. |
|
|
192
|
+
| `context7` | `boolean` | `true` | When true, registers the Context7 MCP server unless already configured. |
|
|
193
|
+
| `commands` | `boolean` | `false` | When true, adds `resolve`, `resolve-code`, `resolve-review` commands. |
|
|
194
|
+
| `autoApprove` | `boolean` | `true` | Flips default `"ask"` permissions to `"allow"` on enabled agents. Never touches `"deny"` or user-set keys. |
|
|
195
|
+
| `maxParallelSubagents` | `positive integer` | `1` | Cap on simultaneous subagents the resolver dispatches across coder, reviewer, etc. |
|
|
196
|
+
| `models` | `object` | `{}` | Alias map. Keys are agent names or `glm`/`gpt`. Values are model ids or other aliases. |
|
|
197
|
+
| `agents` | `object` | `{}` | Per-agent overrides (see below). |
|
|
198
|
+
| `config` | `string` | _none_ | Custom path to a config file (relative to the project or absolute). |
|
|
199
|
+
|
|
200
|
+
Per-agent options inside `agents.<name>`:
|
|
201
|
+
|
|
202
|
+
| Key | Type | Notes |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `enabled` | `boolean` | Force-enable or force-disable this agent regardless of top-level `enabled`. |
|
|
205
|
+
| `model` | `string` | Model id or alias. Resolved against the top-level `models` map. |
|
|
206
|
+
| `mode` | `"subagent" \| "primary" \| "all"` | OpenCode agent mode. |
|
|
207
|
+
| `description` | `string` | Override the default description shown to other agents. |
|
|
208
|
+
| `prompt` | `string` | Override the default system prompt. (For `resolver`, this also disables the templated parallel-rule prompt.) |
|
|
209
|
+
| `color` | `string` | UI color. |
|
|
210
|
+
| `maxSteps` | `positive integer` | Per-invocation step budget. |
|
|
211
|
+
| `tools` | `Record<string, boolean>` | Toggle individual OpenCode tools. |
|
|
212
|
+
| `permission` | `object` | Permission overrides — see below. |
|
|
213
|
+
|
|
214
|
+
Permission keys (each takes `"ask"`, `"allow"`, or `"deny"`):
|
|
74
215
|
|
|
75
|
-
|
|
216
|
+
`edit`, `bash`, `webfetch`, `doom_loop`, `external_directory`.
|
|
217
|
+
|
|
218
|
+
`permission.bash` may also be a per-command map:
|
|
76
219
|
|
|
77
220
|
```json
|
|
78
221
|
{
|
|
79
|
-
"
|
|
222
|
+
"permission": {
|
|
223
|
+
"bash": { "npm test": "allow", "rm -rf": "deny" }
|
|
224
|
+
}
|
|
80
225
|
}
|
|
81
226
|
```
|
|
82
227
|
|
|
83
|
-
|
|
228
|
+
A fully-annotated reference config ships with the package as
|
|
229
|
+
[`opencode-resolve.reference.jsonc`](./opencode-resolve.reference.jsonc) — copy
|
|
230
|
+
the keys you need into your `resolve.json` (without the comments).
|
|
84
231
|
|
|
85
|
-
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Auto Approval
|
|
235
|
+
|
|
236
|
+
`autoApprove` (default `true`) flips every `"ask"` permission on the **enabled** agents to `"allow"`, so coder and resolver work continuously without per-action prompts. It never touches `"deny"` and never overrides a permission key the user explicitly set.
|
|
237
|
+
|
|
238
|
+
| Permission state | autoApprove: true | autoApprove: false |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| Default `"ask"` | becomes `"allow"` | stays `"ask"` |
|
|
241
|
+
| Default `"deny"` | stays `"deny"` | stays `"deny"` |
|
|
242
|
+
| User explicit `"ask"` | stays `"ask"` | stays `"ask"` |
|
|
243
|
+
| User explicit `"allow"` | stays `"allow"` | stays `"allow"` |
|
|
244
|
+
| User explicit `"deny"` | stays `"deny"` | stays `"deny"` |
|
|
245
|
+
|
|
246
|
+
Reviewer permissions for `edit` and `bash` are `"deny"` in the defaults, so `autoApprove` cannot grant the reviewer modification rights.
|
|
247
|
+
|
|
248
|
+
Turn it off when you want the conservative ask-every-time behavior:
|
|
86
249
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"autoApprove": false
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Trust note: `autoApprove: true` assumes you trust the workspace and the model you have configured. Use a sandbox or VM for untrusted code, and keep `autoApprove: false` if you want to inspect every action.
|
|
257
|
+
|
|
258
|
+
---
|
|
91
259
|
|
|
92
|
-
|
|
260
|
+
## Parallel Subagent Limit
|
|
261
|
+
|
|
262
|
+
`maxParallelSubagents` (default `1`) caps how many subagents the **resolver** may dispatch in parallel — across `coder`, `reviewer`, or any other subagent. The default of `1` enforces strictly serial dispatch: resolver waits for each subagent to finish before launching the next.
|
|
263
|
+
|
|
264
|
+
Raise it when you want resolver to fan out independent subtasks:
|
|
93
265
|
|
|
94
266
|
```json
|
|
95
267
|
{
|
|
96
|
-
"
|
|
268
|
+
"maxParallelSubagents": 3
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The limit is woven into the resolver's prompt at config-load time, so changing it does not require any code changes — restart OpenCode to pick up the new limit. If you provide a custom `agents.resolver.prompt`, the templated rule is skipped and your prompt wins.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Upgrade & Migration
|
|
277
|
+
|
|
278
|
+
When you upgrade to a newer version of `opencode-resolve`, the `postinstall` script runs an **additive migration** on your existing `~/.config/opencode/resolve.json`:
|
|
279
|
+
|
|
280
|
+
- Adds new top-level keys (e.g. `autoApprove`, `maxParallelSubagents`) with their defaults if they are absent.
|
|
281
|
+
- **Never** modifies keys you have already set.
|
|
282
|
+
- **Never** rewrites your `enabled` list, `models` map, or `agents` overrides.
|
|
283
|
+
- If `enabled` is set and does not include `"resolver"`, prints a one-line tip suggesting you add it. Your file is left untouched.
|
|
284
|
+
|
|
285
|
+
Skip the migration entirely with:
|
|
286
|
+
|
|
287
|
+
```sh
|
|
288
|
+
OPENCODE_RESOLVE_SKIP_POSTINSTALL=1 npm install -g opencode-resolve
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Model Setup
|
|
294
|
+
|
|
295
|
+
`opencode-resolve` does not ship a provider-specific role default.
|
|
296
|
+
|
|
297
|
+
Model resolution order for each resolve agent:
|
|
298
|
+
|
|
299
|
+
1. `agents.<name>.model`
|
|
300
|
+
2. `models.<name>` alias mapping
|
|
301
|
+
3. top-level OpenCode `model`
|
|
302
|
+
4. OpenCode's own fallback when no model is configured
|
|
303
|
+
|
|
304
|
+
Use the default config when all resolve roles should follow your current OpenCode model.
|
|
305
|
+
|
|
306
|
+
Pin models only when you intentionally want fixed role behavior, such as fast coding on one model and deeper review on another.
|
|
307
|
+
|
|
308
|
+
Role-specific aliases:
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"enabled": ["coder", "reviewer", "resolver"],
|
|
97
313
|
"models": {
|
|
98
|
-
"glm": "zai-coding-plan/glm-5",
|
|
99
|
-
"gpt": "openai/gpt-5",
|
|
314
|
+
"glm": "zai-coding-plan/glm-5.1",
|
|
315
|
+
"gpt": "openai/gpt-5.5",
|
|
100
316
|
"coder": "glm",
|
|
317
|
+
"resolver": "gpt",
|
|
101
318
|
"reviewer": "gpt"
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Pin one role directly:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
106
327
|
"agents": {
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
},
|
|
110
|
-
"gpt-coder": {
|
|
111
|
-
"enabled": false,
|
|
112
|
-
"model": "gpt"
|
|
328
|
+
"reviewer": {
|
|
329
|
+
"model": "openai/gpt-5.5"
|
|
113
330
|
}
|
|
114
331
|
}
|
|
115
332
|
}
|
|
116
333
|
```
|
|
117
334
|
|
|
118
|
-
|
|
335
|
+
Native OpenCode agents such as `plan` and `build` are configured through the top-level OpenCode `agent`, not through `opencode-resolve`.
|
|
336
|
+
|
|
337
|
+
Mixed setup example:
|
|
119
338
|
|
|
120
339
|
```json
|
|
121
340
|
{
|
|
341
|
+
"model": "zai-coding-plan/glm-5.1",
|
|
342
|
+
"agent": {
|
|
343
|
+
"plan": {
|
|
344
|
+
"model": "openai/gpt-5.5"
|
|
345
|
+
}
|
|
346
|
+
},
|
|
122
347
|
"plugin": [
|
|
123
348
|
[
|
|
124
349
|
"opencode-resolve",
|
|
125
350
|
{
|
|
126
|
-
"enabled": ["coder", "reviewer", "
|
|
127
|
-
"
|
|
351
|
+
"enabled": ["coder", "reviewer", "resolver"],
|
|
352
|
+
"models": {
|
|
353
|
+
"glm": "zai-coding-plan/glm-5.1",
|
|
354
|
+
"gpt": "openai/gpt-5.5",
|
|
355
|
+
"coder": "glm",
|
|
356
|
+
"resolver": "gpt",
|
|
357
|
+
"reviewer": "gpt"
|
|
358
|
+
}
|
|
128
359
|
}
|
|
129
360
|
]
|
|
130
361
|
]
|
|
131
362
|
}
|
|
132
363
|
```
|
|
133
364
|
|
|
134
|
-
|
|
365
|
+
In this setup, `plan`, `resolver`, and `reviewer` use `openai/gpt-5.5`; native `build` and resolve `coder` use `zai-coding-plan/glm-5.1`.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Agent Reference
|
|
370
|
+
|
|
371
|
+
| Agent | Default | Mode | Edit | Bash | WebFetch | Purpose |
|
|
372
|
+
|---|:---:|---|---|---|---|---|
|
|
373
|
+
| `resolver` | Yes | `all` | ask → allow | ask → allow | ask → allow | Primary orchestrator. Plans, dispatches `coder` (one at a time), verifies, iterates to completion. |
|
|
374
|
+
| `coder` | Yes | `subagent` | ask → allow | ask → allow | ask → allow | Focused implementer. Smallest correct change. |
|
|
375
|
+
| `reviewer` | Yes | `subagent` | **deny** | **deny** | ask → allow | Read-only auditor. Cannot modify by any means. |
|
|
376
|
+
| `architect` | No | `subagent` | deny | ask → allow | ask → allow | Design and task decomposition. |
|
|
377
|
+
| `gpt-coder` | No | `subagent` | ask → allow | ask → allow | ask → allow | Stronger-reasoning implementation fallback. |
|
|
378
|
+
| `debugger` | No | `subagent` | ask → allow | ask → allow | ask → allow | Reproduction and root-cause analysis. |
|
|
379
|
+
| `researcher` | No | `subagent` | deny | ask → allow | ask → allow | Codebase and documentation research. |
|
|
380
|
+
|
|
381
|
+
`ask → allow` means the default is `"ask"` and `autoApprove` (default on) flips it to `"allow"`. Set `autoApprove: false` to keep them as `"ask"`.
|
|
382
|
+
|
|
383
|
+
Supported modes:
|
|
384
|
+
|
|
385
|
+
| Mode | Meaning |
|
|
386
|
+
|---|---|
|
|
387
|
+
| `subagent` | Available only as a subagent |
|
|
388
|
+
| `primary` | Available as a primary agent |
|
|
389
|
+
| `all` | Available as both primary and subagent |
|
|
390
|
+
|
|
391
|
+
Supported permission values: `ask`, `allow`, `deny`.
|
|
392
|
+
|
|
393
|
+
Supported model alias keys: `glm`, `gpt`, and every supported agent name. Aliases only resolve when defined in `models`.
|
|
394
|
+
|
|
395
|
+
`preserveNative` is accepted for readability, but native `plan` and `build` are always preserved. The plugin never rewrites built-in OpenCode agents.
|
|
396
|
+
|
|
397
|
+
### Resolver orchestration rules
|
|
135
398
|
|
|
136
|
-
|
|
399
|
+
The resolver's prompt enforces the following behavior:
|
|
137
400
|
|
|
138
|
-
|
|
401
|
+
- Plan the smallest correct change before dispatching.
|
|
402
|
+
- Dispatch **only one `coder` subagent at a time**. Never call coders in parallel.
|
|
403
|
+
- After each coder run, verify (tests, type checks, targeted checks) when practical.
|
|
404
|
+
- Optionally consult `reviewer` for an independent read-only audit on risky changes; route any required fixes back through `coder`.
|
|
405
|
+
- Iterate until the task is resolved or clearly blocked, then return a concise summary.
|
|
139
406
|
|
|
140
|
-
|
|
407
|
+
---
|
|
141
408
|
|
|
142
|
-
|
|
143
|
-
- `reviewer`
|
|
144
|
-
- `architect`
|
|
145
|
-
- `gpt-coder`
|
|
146
|
-
- `debugger`
|
|
147
|
-
- `researcher`
|
|
409
|
+
## Optional Commands
|
|
148
410
|
|
|
149
|
-
|
|
411
|
+
Set `commands: true` to add helper subtask commands:
|
|
150
412
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
413
|
+
| Command | Description |
|
|
414
|
+
|---|---|
|
|
415
|
+
| `resolve` | Run the `resolver` agent end-to-end on the current task |
|
|
416
|
+
| `resolve-code` | Run the `coder` agent for focused implementation |
|
|
417
|
+
| `resolve-review` | Run the `reviewer` agent for a read-only audit |
|
|
154
418
|
|
|
155
|
-
|
|
419
|
+
---
|
|
156
420
|
|
|
157
|
-
|
|
421
|
+
## Local Development
|
|
158
422
|
|
|
159
|
-
|
|
423
|
+
From this repository:
|
|
160
424
|
|
|
161
|
-
|
|
425
|
+
```sh
|
|
426
|
+
npm install
|
|
427
|
+
npm test
|
|
428
|
+
npm run install:local
|
|
429
|
+
```
|
|
162
430
|
|
|
163
|
-
|
|
431
|
+
`install:local` builds the plugin, links it into the OpenCode global plugin directory, and creates `~/.config/opencode/resolve.json` if it does not exist.
|
|
164
432
|
|
|
165
|
-
|
|
166
|
-
|
|
433
|
+
Manual local install:
|
|
434
|
+
|
|
435
|
+
```sh
|
|
436
|
+
npm run build
|
|
437
|
+
mkdir -p ~/.config/opencode/plugins
|
|
438
|
+
ln -sf "$PWD/dist/index.js" ~/.config/opencode/plugins/opencode-resolve.js
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Local plugin files are loaded automatically by OpenCode.
|
|
442
|
+
|
|
443
|
+
---
|
|
167
444
|
|
|
168
445
|
## Verification
|
|
169
446
|
|
|
447
|
+
Run the normal checks:
|
|
448
|
+
|
|
170
449
|
```sh
|
|
171
450
|
npm run typecheck
|
|
172
451
|
npm test
|
|
173
452
|
npm run build
|
|
174
453
|
```
|
|
175
454
|
|
|
176
|
-
The test suite executes the built plugin and verifies default agent injection, model aliases, file config, plugin option overrides, optional commands, Context7 preservation, and native `plan`/`build` preservation.
|
|
455
|
+
The test suite executes the built plugin and verifies default agent injection, `autoApprove` behavior, model aliases, file config, plugin option overrides, optional commands, Context7 preservation, and native `plan`/`build` preservation.
|
|
177
456
|
|
|
178
|
-
Before publishing
|
|
457
|
+
Before publishing:
|
|
179
458
|
|
|
180
459
|
```sh
|
|
181
460
|
npm run typecheck
|
|
@@ -186,13 +465,17 @@ npm publish --dry-run
|
|
|
186
465
|
|
|
187
466
|
`npm pack` and `npm publish` run `npm test` first through the `prepack` script.
|
|
188
467
|
|
|
468
|
+
---
|
|
469
|
+
|
|
189
470
|
## Release
|
|
190
471
|
|
|
191
472
|
Releases are published by GitHub Actions when a version tag is pushed.
|
|
192
473
|
|
|
193
474
|
Required repository secret:
|
|
194
475
|
|
|
195
|
-
|
|
476
|
+
| Secret | Description |
|
|
477
|
+
|---|---|
|
|
478
|
+
| `NPM_TOKEN` | npm automation token with publish access |
|
|
196
479
|
|
|
197
480
|
Tag release flow:
|
|
198
481
|
|
|
@@ -205,32 +488,12 @@ You can also run the `Publish to npm` workflow manually from GitHub Actions and
|
|
|
205
488
|
|
|
206
489
|
The release workflow runs `npm ci`, `npm run typecheck`, `npm test`, and `npm publish --access public --provenance`.
|
|
207
490
|
|
|
491
|
+
---
|
|
492
|
+
|
|
208
493
|
## Design Rules
|
|
209
494
|
|
|
210
495
|
- Do not overwrite native `plan` or `build` agents.
|
|
211
|
-
- Keep default
|
|
212
|
-
-
|
|
213
|
-
-
|
|
214
|
-
- Verify when practical.
|
|
215
|
-
- Use reviewer for final requirement/risk checks instead of running many agents by default.
|
|
216
|
-
|
|
217
|
-
## Companion Plugins
|
|
218
|
-
|
|
219
|
-
`opencode-resolve` intentionally stays focused on agent injection and task resolution. Use notification or remote-control plugins alongside it instead of baking those concerns into this package.
|
|
220
|
-
|
|
221
|
-
Recommended companion:
|
|
222
|
-
|
|
223
|
-
- [`@jshsakura/opencode-telegram-bot-plugin`](https://github.com/jshsakura/opencode-telegram-bot-plugin): Telegram notifications and permission-response support for OpenCode sessions.
|
|
224
|
-
|
|
225
|
-
Install and configure the Telegram plugin separately. It needs its own bot token/chat setup; see that plugin's README for details.
|
|
226
|
-
|
|
227
|
-
Example:
|
|
228
|
-
|
|
229
|
-
```json
|
|
230
|
-
{
|
|
231
|
-
"plugin": [
|
|
232
|
-
"opencode-resolve",
|
|
233
|
-
"@jshsakura/opencode-telegram-bot-plugin"
|
|
234
|
-
]
|
|
235
|
-
}
|
|
236
|
-
```
|
|
496
|
+
- Keep the default agent set small and role-clear: `resolver` orchestrates, `coder` modifies, `reviewer` only reads.
|
|
497
|
+
- The reviewer never modifies anything — fixes always go through `coder` or `resolver`.
|
|
498
|
+
- The resolver dispatches at most one coder at a time.
|
|
499
|
+
- Search and inspect before editing. Make the smallest correct change. Verify when practical.
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
4
|
-
const DEFAULT_MODELS = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
coder: "zai-coding-plan/glm-5",
|
|
8
|
-
reviewer: "openai/gpt-5",
|
|
9
|
-
architect: "openai/gpt-5",
|
|
10
|
-
"gpt-coder": "openai/gpt-5",
|
|
11
|
-
debugger: "zai-coding-plan/glm-5",
|
|
12
|
-
researcher: "zai-coding-plan/glm-5",
|
|
13
|
-
};
|
|
14
|
-
const DEFAULT_ENABLED = ["coder", "reviewer"];
|
|
15
|
-
const VALID_AGENT_NAMES = ["coder", "reviewer", "architect", "gpt-coder", "debugger", "researcher"];
|
|
4
|
+
const DEFAULT_MODELS = {};
|
|
5
|
+
const DEFAULT_ENABLED = ["coder", "reviewer", "resolver"];
|
|
6
|
+
const VALID_AGENT_NAMES = ["coder", "reviewer", "resolver", "architect", "gpt-coder", "debugger", "researcher"];
|
|
16
7
|
const VALID_AGENT_NAME_SET = new Set(VALID_AGENT_NAMES);
|
|
17
8
|
const VALID_MODEL_ALIASES = [...VALID_AGENT_NAMES, "glm", "gpt"];
|
|
18
9
|
const VALID_MODEL_ALIAS_SET = new Set(VALID_MODEL_ALIASES);
|
|
@@ -25,8 +16,11 @@ const VALID_TOP_LEVEL_KEYS = new Set([
|
|
|
25
16
|
"preserveNative",
|
|
26
17
|
"context7",
|
|
27
18
|
"commands",
|
|
19
|
+
"autoApprove",
|
|
20
|
+
"maxParallelSubagents",
|
|
28
21
|
"config",
|
|
29
22
|
]);
|
|
23
|
+
const DEFAULT_MAX_PARALLEL_SUBAGENTS = 1;
|
|
30
24
|
const VALID_AGENT_KEYS = new Set([
|
|
31
25
|
"enabled",
|
|
32
26
|
"model",
|
|
@@ -38,6 +32,26 @@ const VALID_AGENT_KEYS = new Set([
|
|
|
38
32
|
"tools",
|
|
39
33
|
"permission",
|
|
40
34
|
]);
|
|
35
|
+
function buildResolverPrompt(maxParallelSubagents) {
|
|
36
|
+
const limit = Math.max(1, Math.trunc(maxParallelSubagents));
|
|
37
|
+
const parallelRule = limit === 1
|
|
38
|
+
? "CRITICAL: Dispatch only ONE subagent at a time. Never call multiple subagents (coder, reviewer, or any other) in parallel. Wait for the current subagent to finish and evaluate its result before dispatching the next one."
|
|
39
|
+
: `CRITICAL: Dispatch at most ${limit} subagents in parallel. Never exceed this limit across coder, reviewer, or any other subagent. Wait for in-flight subagents to finish and evaluate their results before launching new ones.`;
|
|
40
|
+
return [
|
|
41
|
+
"You are Resolver, the primary orchestrator agent for OpenCode Resolve.",
|
|
42
|
+
"Your job is to drive the user's task to a verified resolution end-to-end without unnecessary stops.",
|
|
43
|
+
"Workflow:",
|
|
44
|
+
"1. Understand the requirement. Inspect the relevant files briefly before planning.",
|
|
45
|
+
"2. Plan the smallest correct change.",
|
|
46
|
+
"3. Dispatch the coder subagent to implement the change. Pass a focused instruction with the exact files and behavior.",
|
|
47
|
+
`4. ${parallelRule}`,
|
|
48
|
+
"5. After implementation, verify when practical (run tests, type checks, or targeted checks).",
|
|
49
|
+
"6. If issues remain, dispatch the coder again with a focused fix, or apply a small direct edit yourself when it is clearly trivial.",
|
|
50
|
+
"7. Optionally consult the reviewer subagent for an independent read-only review on risky changes. The reviewer cannot modify anything; treat its output as advice and route any required fixes back through the coder. The same parallel-dispatch limit applies to the reviewer.",
|
|
51
|
+
"8. Repeat until the task is resolved or clearly blocked.",
|
|
52
|
+
"Return a concise summary of what changed, verification results, and any remaining blockers.",
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
41
55
|
const DEFAULT_AGENT_CONFIG = {
|
|
42
56
|
coder: {
|
|
43
57
|
mode: "subagent",
|
|
@@ -61,16 +75,29 @@ const DEFAULT_AGENT_CONFIG = {
|
|
|
61
75
|
mode: "subagent",
|
|
62
76
|
color: "#8A7CFF",
|
|
63
77
|
maxSteps: 8,
|
|
64
|
-
description: "
|
|
78
|
+
description: "Read-only Oracle-style reviewer. Inspects code for requirements fit, correctness, security, tests, and maintainability risks. Never modifies anything.",
|
|
65
79
|
prompt: [
|
|
66
|
-
"You are Reviewer,
|
|
67
|
-
"
|
|
80
|
+
"You are Reviewer, a strictly read-only review subagent for OpenCode Resolve.",
|
|
81
|
+
"You MUST NOT modify the project by any means: no file edits, no writes, no shell commands that change state, no git commits, no package installs.",
|
|
82
|
+
"Use read-only tools (read, grep, glob, list, web fetch for documentation) to inspect the work against the user's requirements and the repository's existing patterns.",
|
|
68
83
|
"Prioritize concrete bugs, behavioral regressions, security risks, missing tests, and maintainability issues.",
|
|
69
|
-
"
|
|
70
|
-
"If
|
|
84
|
+
"Return findings ordered by severity with file and line references when available. If there are no findings, say so and mention residual risks or verification gaps.",
|
|
85
|
+
"If a fix is needed, describe it precisely and recommend dispatching the coder or resolver agent. Never apply fixes yourself.",
|
|
71
86
|
].join("\n"),
|
|
72
87
|
permission: {
|
|
73
88
|
edit: "deny",
|
|
89
|
+
bash: "deny",
|
|
90
|
+
webfetch: "ask",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
resolver: {
|
|
94
|
+
mode: "all",
|
|
95
|
+
color: "#FF7AC6",
|
|
96
|
+
maxSteps: 30,
|
|
97
|
+
description: "Primary orchestrator. Drives a task to completion by planning, dispatching subagents (one at a time by default), and verifying results. Iterates until the task is resolved or clearly blocked.",
|
|
98
|
+
prompt: buildResolverPrompt(DEFAULT_MAX_PARALLEL_SUBAGENTS),
|
|
99
|
+
permission: {
|
|
100
|
+
edit: "ask",
|
|
74
101
|
bash: "ask",
|
|
75
102
|
webfetch: "ask",
|
|
76
103
|
},
|
|
@@ -166,6 +193,9 @@ async function loadResolveConfig(directory, opencodeConfig, options) {
|
|
|
166
193
|
function applyResolveConfig(config, resolveConfig) {
|
|
167
194
|
const enabled = new Set(resolveConfig.enabled ?? DEFAULT_ENABLED);
|
|
168
195
|
const models = { ...DEFAULT_MODELS, ...resolveConfig.models };
|
|
196
|
+
const defaultModel = typeof config.model === "string" ? config.model : undefined;
|
|
197
|
+
const autoApprove = resolveConfig.autoApprove !== false;
|
|
198
|
+
const maxParallelSubagents = resolveConfig.maxParallelSubagents ?? DEFAULT_MAX_PARALLEL_SUBAGENTS;
|
|
169
199
|
config.agent ??= {};
|
|
170
200
|
for (const name of Object.keys(DEFAULT_AGENT_CONFIG)) {
|
|
171
201
|
const override = resolveConfig.agents?.[name];
|
|
@@ -173,13 +203,21 @@ function applyResolveConfig(config, resolveConfig) {
|
|
|
173
203
|
if (!isEnabled)
|
|
174
204
|
continue;
|
|
175
205
|
const base = DEFAULT_AGENT_CONFIG[name];
|
|
176
|
-
const { enabled: _enabled, ...agentOverride } = override ?? {};
|
|
177
|
-
const model = resolveModel(
|
|
178
|
-
|
|
206
|
+
const { enabled: _enabled, model: requestedModel, permission: userPermission, ...agentOverride } = override ?? {};
|
|
207
|
+
const model = resolveModel(requestedModel ?? models[name] ?? defaultModel, models);
|
|
208
|
+
const permission = buildPermission(base.permission, userPermission, autoApprove);
|
|
209
|
+
const agentConfig = {
|
|
179
210
|
...base,
|
|
180
211
|
...agentOverride,
|
|
181
|
-
model,
|
|
182
212
|
};
|
|
213
|
+
if (name === "resolver" && agentOverride.prompt === undefined) {
|
|
214
|
+
agentConfig.prompt = buildResolverPrompt(maxParallelSubagents);
|
|
215
|
+
}
|
|
216
|
+
if (permission)
|
|
217
|
+
agentConfig.permission = permission;
|
|
218
|
+
if (model)
|
|
219
|
+
agentConfig.model = model;
|
|
220
|
+
config.agent[name] = agentConfig;
|
|
183
221
|
}
|
|
184
222
|
if (resolveConfig.context7 !== false) {
|
|
185
223
|
config.mcp ??= {};
|
|
@@ -190,9 +228,15 @@ function applyResolveConfig(config, resolveConfig) {
|
|
|
190
228
|
}
|
|
191
229
|
if (resolveConfig.commands) {
|
|
192
230
|
config.command ??= {};
|
|
231
|
+
config.command["resolve"] ??= {
|
|
232
|
+
template: "Drive this task to a verified resolution end-to-end. Plan, dispatch one coder at a time, verify, and iterate. $ARGUMENTS",
|
|
233
|
+
description: "Run the OpenCode Resolve resolver agent end-to-end",
|
|
234
|
+
agent: "resolver",
|
|
235
|
+
subtask: true,
|
|
236
|
+
};
|
|
193
237
|
config.command["resolve-review"] ??= {
|
|
194
|
-
template: "Review the current implementation against the user's requirements. Focus on correctness, tests, security, and maintainability.",
|
|
195
|
-
description: "Run the OpenCode Resolve reviewer agent",
|
|
238
|
+
template: "Review the current implementation against the user's requirements. Focus on correctness, tests, security, and maintainability. Do not modify anything.",
|
|
239
|
+
description: "Run the OpenCode Resolve reviewer agent (read-only)",
|
|
196
240
|
agent: "reviewer",
|
|
197
241
|
subtask: true,
|
|
198
242
|
};
|
|
@@ -207,11 +251,13 @@ function applyResolveConfig(config, resolveConfig) {
|
|
|
207
251
|
function defaultResolveConfig() {
|
|
208
252
|
return {
|
|
209
253
|
enabled: DEFAULT_ENABLED,
|
|
210
|
-
models:
|
|
254
|
+
models: {},
|
|
211
255
|
agents: {},
|
|
212
256
|
preserveNative: true,
|
|
213
257
|
context7: true,
|
|
214
258
|
commands: false,
|
|
259
|
+
autoApprove: true,
|
|
260
|
+
maxParallelSubagents: DEFAULT_MAX_PARALLEL_SUBAGENTS,
|
|
215
261
|
};
|
|
216
262
|
}
|
|
217
263
|
function mergeResolveConfig(...configs) {
|
|
@@ -223,6 +269,8 @@ function mergeResolveConfig(...configs) {
|
|
|
223
269
|
result.preserveNative = config.preserveNative ?? result.preserveNative;
|
|
224
270
|
result.context7 = config.context7 ?? result.context7;
|
|
225
271
|
result.commands = config.commands ?? result.commands;
|
|
272
|
+
result.autoApprove = config.autoApprove ?? result.autoApprove;
|
|
273
|
+
result.maxParallelSubagents = config.maxParallelSubagents ?? result.maxParallelSubagents;
|
|
226
274
|
result.models = { ...result.models, ...config.models };
|
|
227
275
|
result.agents = mergeAgents(result.agents, config.agents);
|
|
228
276
|
}
|
|
@@ -240,6 +288,27 @@ function resolveModel(model, models) {
|
|
|
240
288
|
return undefined;
|
|
241
289
|
return models[model] ?? model;
|
|
242
290
|
}
|
|
291
|
+
function buildPermission(basePermission, userPermission, autoApprove) {
|
|
292
|
+
const merged = {
|
|
293
|
+
...(basePermission ?? {}),
|
|
294
|
+
...(userPermission ?? {}),
|
|
295
|
+
};
|
|
296
|
+
if (Object.keys(merged).length === 0)
|
|
297
|
+
return undefined;
|
|
298
|
+
if (!autoApprove)
|
|
299
|
+
return merged;
|
|
300
|
+
const userKeys = new Set(Object.keys(userPermission ?? {}));
|
|
301
|
+
const result = { ...merged };
|
|
302
|
+
for (const key of Object.keys(result)) {
|
|
303
|
+
if (userKeys.has(key))
|
|
304
|
+
continue;
|
|
305
|
+
const value = result[key];
|
|
306
|
+
if (value === "ask") {
|
|
307
|
+
result[key] = "allow";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
243
312
|
function getPluginOptions(config) {
|
|
244
313
|
for (const entry of config.plugin ?? []) {
|
|
245
314
|
if (Array.isArray(entry) && isResolvePluginEntry(entry[0])) {
|
|
@@ -310,6 +379,15 @@ function normalizeResolveConfig(value, source) {
|
|
|
310
379
|
result.context7 = expectBoolean(config.context7, `${source}.context7`);
|
|
311
380
|
if (config.commands !== undefined)
|
|
312
381
|
result.commands = expectBoolean(config.commands, `${source}.commands`);
|
|
382
|
+
if (config.autoApprove !== undefined)
|
|
383
|
+
result.autoApprove = expectBoolean(config.autoApprove, `${source}.autoApprove`);
|
|
384
|
+
if (config.maxParallelSubagents !== undefined) {
|
|
385
|
+
const limit = expectNumber(config.maxParallelSubagents, `${source}.maxParallelSubagents`);
|
|
386
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
387
|
+
throw new Error(`${source}.maxParallelSubagents must be a positive integer`);
|
|
388
|
+
}
|
|
389
|
+
result.maxParallelSubagents = limit;
|
|
390
|
+
}
|
|
313
391
|
if (config.config !== undefined)
|
|
314
392
|
result.config = expectString(config.config, `${source}.config`);
|
|
315
393
|
return result;
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
|
-
"enabled": ["coder", "reviewer"],
|
|
3
|
-
"models": {
|
|
4
|
-
"glm": "zai-coding-plan/glm-5",
|
|
5
|
-
"gpt": "openai/gpt-5",
|
|
6
|
-
"coder": "glm",
|
|
7
|
-
"reviewer": "gpt",
|
|
8
|
-
"architect": "gpt",
|
|
9
|
-
"gpt-coder": "gpt",
|
|
10
|
-
"debugger": "glm",
|
|
11
|
-
"researcher": "glm"
|
|
12
|
-
},
|
|
2
|
+
"enabled": ["coder", "reviewer", "resolver"],
|
|
13
3
|
"preserveNative": true,
|
|
14
4
|
"context7": true,
|
|
15
5
|
"commands": false,
|
|
6
|
+
"autoApprove": true,
|
|
7
|
+
"maxParallelSubagents": 1,
|
|
8
|
+
"models": {},
|
|
16
9
|
"agents": {
|
|
10
|
+
"coder": {
|
|
11
|
+
"enabled": true
|
|
12
|
+
},
|
|
13
|
+
"reviewer": {
|
|
14
|
+
"enabled": true
|
|
15
|
+
},
|
|
16
|
+
"resolver": {
|
|
17
|
+
"enabled": true
|
|
18
|
+
},
|
|
17
19
|
"architect": {
|
|
18
20
|
"enabled": false
|
|
19
21
|
},
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// opencode-resolve.reference.jsonc
|
|
2
|
+
//
|
|
3
|
+
// This file documents EVERY option opencode-resolve accepts. It is NOT loaded
|
|
4
|
+
// by the plugin (the plugin only reads .json). Copy what you need into
|
|
5
|
+
// `~/.config/opencode/resolve.json`, `.opencode/resolve.json`, or
|
|
6
|
+
// `opencode-resolve.json` (without the comments).
|
|
7
|
+
//
|
|
8
|
+
// Config file lookup order (first one found wins):
|
|
9
|
+
// 1. <project>/.opencode/resolve.json
|
|
10
|
+
// 2. <project>/opencode-resolve.json
|
|
11
|
+
// 3. ~/.config/opencode/resolve.json
|
|
12
|
+
// 4. ~/.config/opencode/opencode-resolve.json
|
|
13
|
+
//
|
|
14
|
+
// Inline plugin options inside opencode.json override the file config.
|
|
15
|
+
//
|
|
16
|
+
// Validation is strict: unknown keys, misspellings, invalid modes, invalid
|
|
17
|
+
// permission values, and wrong types throw an error at load time.
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
// -----------------------------------------------------------------------
|
|
21
|
+
// enabled (string[])
|
|
22
|
+
// Which resolve agents to inject. Default: ["coder", "reviewer", "resolver"].
|
|
23
|
+
// Native OpenCode agents (plan, build) are always preserved and never
|
|
24
|
+
// touched by this list. Per-agent `agents.<name>.enabled` overrides this.
|
|
25
|
+
// -----------------------------------------------------------------------
|
|
26
|
+
"enabled": ["coder", "reviewer", "resolver"],
|
|
27
|
+
|
|
28
|
+
// -----------------------------------------------------------------------
|
|
29
|
+
// preserveNative (boolean)
|
|
30
|
+
// Always true in practice — plan/build are never rewritten. Accepted for
|
|
31
|
+
// readability. Default: true.
|
|
32
|
+
// -----------------------------------------------------------------------
|
|
33
|
+
"preserveNative": true,
|
|
34
|
+
|
|
35
|
+
// -----------------------------------------------------------------------
|
|
36
|
+
// context7 (boolean)
|
|
37
|
+
// When true, registers the Context7 MCP server unless `mcp.context7` is
|
|
38
|
+
// already configured. Default: true.
|
|
39
|
+
// -----------------------------------------------------------------------
|
|
40
|
+
"context7": true,
|
|
41
|
+
|
|
42
|
+
// -----------------------------------------------------------------------
|
|
43
|
+
// commands (boolean)
|
|
44
|
+
// When true, adds three optional commands: `resolve`, `resolve-code`,
|
|
45
|
+
// `resolve-review`. Default: false.
|
|
46
|
+
// -----------------------------------------------------------------------
|
|
47
|
+
"commands": false,
|
|
48
|
+
|
|
49
|
+
// -----------------------------------------------------------------------
|
|
50
|
+
// autoApprove (boolean)
|
|
51
|
+
// When true, every default `"ask"` permission on enabled agents is flipped
|
|
52
|
+
// to `"allow"`. Never touches `"deny"`. Never overrides a permission key
|
|
53
|
+
// you set explicitly under `agents.<name>.permission`. Default: true.
|
|
54
|
+
//
|
|
55
|
+
// Set to false for the conservative ask-every-time behavior.
|
|
56
|
+
// -----------------------------------------------------------------------
|
|
57
|
+
"autoApprove": true,
|
|
58
|
+
|
|
59
|
+
// -----------------------------------------------------------------------
|
|
60
|
+
// maxParallelSubagents (positive integer)
|
|
61
|
+
// Maximum number of subagents the resolver may dispatch in parallel
|
|
62
|
+
// (across coder, reviewer, etc.). Default: 1 — strictly serial.
|
|
63
|
+
// Increase if you want resolver to fan out independent subtasks.
|
|
64
|
+
// -----------------------------------------------------------------------
|
|
65
|
+
"maxParallelSubagents": 1,
|
|
66
|
+
|
|
67
|
+
// -----------------------------------------------------------------------
|
|
68
|
+
// models (object)
|
|
69
|
+
// Alias map. Keys are either an agent name (coder, reviewer, resolver,
|
|
70
|
+
// architect, gpt-coder, debugger, researcher) OR one of the well-known
|
|
71
|
+
// aliases (`glm`, `gpt`). Values are model identifiers OR another alias.
|
|
72
|
+
//
|
|
73
|
+
// Resolution order for a given agent:
|
|
74
|
+
// 1. agents.<name>.model
|
|
75
|
+
// 2. models.<name> (resolved through the alias map)
|
|
76
|
+
// 3. top-level OpenCode `model`
|
|
77
|
+
// 4. OpenCode's own fallback
|
|
78
|
+
// -----------------------------------------------------------------------
|
|
79
|
+
"models": {
|
|
80
|
+
"glm": "zai-coding-plan/glm-5.1",
|
|
81
|
+
"gpt": "openai/gpt-5.5",
|
|
82
|
+
"coder": "glm",
|
|
83
|
+
"resolver": "gpt",
|
|
84
|
+
"reviewer": "gpt"
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// -----------------------------------------------------------------------
|
|
88
|
+
// agents (object)
|
|
89
|
+
// Per-agent overrides. Each key MUST be a valid agent name. Each entry
|
|
90
|
+
// accepts: enabled, model, mode, description, prompt, color, maxSteps,
|
|
91
|
+
// tools, permission. Unknown keys throw.
|
|
92
|
+
//
|
|
93
|
+
// mode: "subagent" | "primary" | "all"
|
|
94
|
+
// permission.edit | bash | webfetch | doom_loop | external_directory:
|
|
95
|
+
// "ask" | "allow" | "deny"
|
|
96
|
+
// permission.bash may also be a per-command map:
|
|
97
|
+
// { "npm test": "allow", "rm -rf": "deny" }
|
|
98
|
+
// -----------------------------------------------------------------------
|
|
99
|
+
"agents": {
|
|
100
|
+
"coder": {
|
|
101
|
+
"enabled": true,
|
|
102
|
+
"model": "glm",
|
|
103
|
+
"mode": "subagent",
|
|
104
|
+
"maxSteps": 20,
|
|
105
|
+
"permission": {
|
|
106
|
+
"edit": "allow",
|
|
107
|
+
"bash": "allow",
|
|
108
|
+
"webfetch": "allow"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
"reviewer": {
|
|
113
|
+
"enabled": true,
|
|
114
|
+
"model": "gpt",
|
|
115
|
+
"mode": "subagent",
|
|
116
|
+
"maxSteps": 8,
|
|
117
|
+
// Reviewer is read-only by design. edit and bash are denied even when
|
|
118
|
+
// autoApprove is true.
|
|
119
|
+
"permission": {
|
|
120
|
+
"edit": "deny",
|
|
121
|
+
"bash": "deny",
|
|
122
|
+
"webfetch": "allow"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
"resolver": {
|
|
127
|
+
"enabled": true,
|
|
128
|
+
"model": "gpt",
|
|
129
|
+
"mode": "all",
|
|
130
|
+
"maxSteps": 30,
|
|
131
|
+
"permission": {
|
|
132
|
+
"edit": "allow",
|
|
133
|
+
"bash": "allow",
|
|
134
|
+
"webfetch": "allow"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Optional agents — disabled by default. Flip enabled:true to use them.
|
|
139
|
+
"architect": {
|
|
140
|
+
"enabled": false,
|
|
141
|
+
"model": "gpt",
|
|
142
|
+
"mode": "subagent",
|
|
143
|
+
"maxSteps": 10
|
|
144
|
+
},
|
|
145
|
+
"gpt-coder": {
|
|
146
|
+
"enabled": false,
|
|
147
|
+
"model": "gpt",
|
|
148
|
+
"mode": "subagent",
|
|
149
|
+
"maxSteps": 20
|
|
150
|
+
},
|
|
151
|
+
"debugger": {
|
|
152
|
+
"enabled": false,
|
|
153
|
+
"model": "glm",
|
|
154
|
+
"mode": "subagent",
|
|
155
|
+
"maxSteps": 14
|
|
156
|
+
},
|
|
157
|
+
"researcher": {
|
|
158
|
+
"enabled": false,
|
|
159
|
+
"model": "glm",
|
|
160
|
+
"mode": "subagent",
|
|
161
|
+
"maxSteps": 8
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-resolve",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OpenCode plugin that adds a small coder/reviewer agent set while preserving native plan/build behavior.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/jshsakura/opencode-resolve#readme",
|
|
@@ -23,12 +23,14 @@
|
|
|
23
23
|
"dist",
|
|
24
24
|
"scripts",
|
|
25
25
|
"opencode-resolve.example.json",
|
|
26
|
+
"opencode-resolve.reference.jsonc",
|
|
26
27
|
"README.md",
|
|
27
28
|
"LICENSE"
|
|
28
29
|
],
|
|
29
30
|
"scripts": {
|
|
30
31
|
"build": "tsc -p tsconfig.json",
|
|
31
32
|
"install:local": "npm run build && node scripts/install-local.mjs",
|
|
33
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
32
34
|
"prepack": "npm test",
|
|
33
35
|
"test": "npm run build && node --test test/*.mjs",
|
|
34
36
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { constants } from "node:fs"
|
|
2
|
+
import { access, copyFile, mkdir, readFile, writeFile } from "node:fs/promises"
|
|
3
|
+
import { homedir } from "node:os"
|
|
4
|
+
import { dirname, join, resolve } from "node:path"
|
|
5
|
+
import { fileURLToPath } from "node:url"
|
|
6
|
+
|
|
7
|
+
const packageName = "opencode-resolve"
|
|
8
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..")
|
|
9
|
+
const configDir = process.env.OPENCODE_CONFIG_HOME || join(homedir(), ".config", "opencode")
|
|
10
|
+
const opencodeConfigPath = join(configDir, "opencode.json")
|
|
11
|
+
const resolveConfigPath = join(configDir, "resolve.json")
|
|
12
|
+
const exampleConfigPath = join(root, "opencode-resolve.example.json")
|
|
13
|
+
|
|
14
|
+
const ADDITIVE_DEFAULTS = {
|
|
15
|
+
autoApprove: true,
|
|
16
|
+
maxParallelSubagents: 1,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (process.env.OPENCODE_RESOLVE_SKIP_POSTINSTALL === "1") {
|
|
20
|
+
process.exit(0)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await registerPlugin()
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.warn(`[${packageName}] automatic OpenCode registration skipped: ${formatError(error)}`)
|
|
27
|
+
console.warn(`[${packageName}] add "${packageName}" to your OpenCode plugin list manually if needed.`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function registerPlugin() {
|
|
31
|
+
await mkdir(configDir, { recursive: true })
|
|
32
|
+
|
|
33
|
+
const config = await readOpenCodeConfig()
|
|
34
|
+
const changed = addPlugin(config)
|
|
35
|
+
|
|
36
|
+
if (changed) {
|
|
37
|
+
await writeFile(opencodeConfigPath, `${JSON.stringify(config, null, 2)}\n`)
|
|
38
|
+
console.log(`[${packageName}] registered in ${opencodeConfigPath}`)
|
|
39
|
+
} else {
|
|
40
|
+
console.log(`[${packageName}] already registered in ${opencodeConfigPath}`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!(await exists(resolveConfigPath))) {
|
|
44
|
+
await assertReadable(exampleConfigPath)
|
|
45
|
+
await copyFile(exampleConfigPath, resolveConfigPath)
|
|
46
|
+
console.log(`[${packageName}] created ${resolveConfigPath}`)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await migrateResolveConfig()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function migrateResolveConfig() {
|
|
54
|
+
let raw
|
|
55
|
+
try {
|
|
56
|
+
raw = await readFile(resolveConfigPath, "utf8")
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.warn(`[${packageName}] could not read ${resolveConfigPath} for migration: ${formatError(error)}`)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let parsed
|
|
63
|
+
try {
|
|
64
|
+
parsed = JSON.parse(raw)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.warn(`[${packageName}] ${resolveConfigPath} is not valid JSON; skipping migration: ${formatError(error)}`)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!isObject(parsed)) {
|
|
71
|
+
console.warn(`[${packageName}] ${resolveConfigPath} must contain a JSON object; skipping migration`)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const updated = { ...parsed }
|
|
76
|
+
const added = []
|
|
77
|
+
for (const [key, value] of Object.entries(ADDITIVE_DEFAULTS)) {
|
|
78
|
+
if (updated[key] === undefined) {
|
|
79
|
+
updated[key] = value
|
|
80
|
+
added.push(`${key}=${JSON.stringify(value)}`)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (added.length > 0) {
|
|
85
|
+
await writeFile(resolveConfigPath, `${JSON.stringify(updated, null, 2)}\n`)
|
|
86
|
+
console.log(`[${packageName}] migrated ${resolveConfigPath}: added ${added.join(", ")}`)
|
|
87
|
+
} else {
|
|
88
|
+
console.log(`[${packageName}] ${resolveConfigPath} already up to date`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (Array.isArray(updated.enabled) && !updated.enabled.includes("resolver")) {
|
|
92
|
+
console.log(
|
|
93
|
+
`[${packageName}] tip: add "resolver" to "enabled" in ${resolveConfigPath} to use the new orchestrator agent.`,
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function readOpenCodeConfig() {
|
|
99
|
+
if (!(await exists(opencodeConfigPath))) {
|
|
100
|
+
return {
|
|
101
|
+
$schema: "https://opencode.ai/config.json",
|
|
102
|
+
plugin: [],
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const raw = await readFile(opencodeConfigPath, "utf8")
|
|
107
|
+
const parsed = JSON.parse(raw)
|
|
108
|
+
if (!isObject(parsed)) throw new Error(`${opencodeConfigPath} must contain a JSON object`)
|
|
109
|
+
return parsed
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function addPlugin(config) {
|
|
113
|
+
config.plugin ??= []
|
|
114
|
+
if (!Array.isArray(config.plugin)) {
|
|
115
|
+
throw new Error(`${opencodeConfigPath}.plugin must be an array`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (config.plugin.some(isRegisteredPluginEntry)) return false
|
|
119
|
+
config.plugin.push(packageName)
|
|
120
|
+
return true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function isRegisteredPluginEntry(entry) {
|
|
124
|
+
if (typeof entry === "string") return isResolvePluginName(entry)
|
|
125
|
+
if (Array.isArray(entry) && typeof entry[0] === "string") return isResolvePluginName(entry[0])
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function isResolvePluginName(value) {
|
|
130
|
+
const name = value.split("/").pop() || value
|
|
131
|
+
return name === packageName || name.startsWith(`${packageName}@`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function assertReadable(path) {
|
|
135
|
+
await access(path, constants.R_OK)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function exists(path) {
|
|
139
|
+
try {
|
|
140
|
+
await access(path)
|
|
141
|
+
return true
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (isMissingFileError(error)) return false
|
|
144
|
+
throw error
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isMissingFileError(error) {
|
|
149
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT"
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isObject(value) {
|
|
153
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function formatError(error) {
|
|
157
|
+
return error instanceof Error ? error.message : String(error)
|
|
158
|
+
}
|